vuejs2-6 商品详情页面
1 src/components/goods/goods.vue
<li v-for="food in item.foods" class="foods-item border-1px" @click="selectFood(food,$event)"> </li> <food @add="addFood" :food="selectedFood" ref="food"></food> data() { // 数据 return { selectedFood: {} // 保存右侧点击的商品的信息 } } methods: { selectFood(food, event) { // 点击右侧内容区,跳转到当前商品详情 if (!event._constructed) { // 使用了BS return; }; this.selectedFood = food; // 将当前选中的food赋值给selectFood,再传递给food.vue的组件 this.$refs.food.show(); // 执行food.vue的show()方法 } }
2 src/components/food/food.vue
2.1 src/components/ratingselect/ratingselect.vue
<template> <div class="ratingselect"> <div class="rating-type border-1px"> <span class="block positive" :class="{active:selectType===2}" @click="select(2,$event)">{{desc.all}}<span class="count">{{ratings.length}}</span></span> <span class="block positive" :class="{active:selectType===0}" @click="select(0,$event)">{{desc.positive}}<span class="count">{{positives.length}}</span></span> <span class="block negative" :class="{active:selectType===1}" @click="select(1,$event)">{{desc.negative}}<span class="count">{{negatives.length}}</span></span> </div> <div class="switch" :class="{on:onlyContent}" @click="toggleContent"> <i class="icon-check_circle"></i> <span class="text">只看有评价的内容</span> </div> </div> </template> <script type="text/ecmascript-6"> const POSITIVE = 0; const NEGATIVE = 1; const ALL = 2; export default { props: { ratings: { type: Array, default() { return []; } }, selectType: { // 接收 food.vue传递的 :selectType="selectType" type: Number, default: ALL }, onlyContent: { // 接收 food.vue传递的 :onlyContent="onlyContent" type: Boolean, default: false // 默认可以看到所有评论 }, desc: { type: Object, default() { // 默认文字显示 return { all: '全部', positive: '满意', negative: '不满意' }; } } }, methods: { select(type, ev) { // 选择全部 满意 不满意 if (!ev._constructed) { // 使用了BS return; }; this.$emit('select', type); // 将selectType变化的值交给父组件food.vue处理(改变selectType的值) }, toggleContent(ev) { // 点击了只看有评价的内容 if (!ev._constructed) { // 使用了BS return; }; this.$emit('toggle'); // } }, computed: { positives() { // 推荐数量 return this.ratings.filter((rating) => { return rating.rateType === POSITIVE; }); }, negatives() { // 不满意数量 return this.ratings.filter((rating) => { return rating.rateType === NEGATIVE; }); } } }; </script> <style lang="stylus" rel="stylesheet/stylus"> @import "../../common/stylus/mixin.styl" .ratingselect .rating-type padding:18px 0 margin:0 18px border-1px(rgba(7,17,27,0.1)) font-size:0px .block display:inline-block padding:8px 12px border-radius:1px margin-right:8px color:rgb(77,85,93) font-size:12px line-height:16px &.active color:#fff .count font-size:8px margin-left:2px &.positive background: rgba(0,160,220,0.2) &.active background:rgb(0,160,220) &.negative background-color:rgba(77,85,93,0.2) &.active background-color:rgb(77,85,93) .switch padding:12px 18px line-height:24px border-bottom:1px solid rgba(7,17,27,0.1) color:rgb(147,153,159) font-size:0 &.on .icon-check_circle color:#00c850 .icon-check_circle display:inline-block; vertical-align:middle font-size:24px margin-right: 4px .text font-size:12px display:inline-block vertical-align:middle </style>
2.2 src/components/food/food.vue
<template> <transition name="move"> <div class="food" v-show="showFlag" ref="food"> <div class="food-content"> <div class="img-header"> <img :src="food.image" alt=""> <div class="back" @click="showFlag=false"> <i class="icon-arrow_lift"></i> </div> </div> <div class="content"> <h1 class="title">{{food.name}}</h1> <div class="detail"> <span class="sell-count">月售{{food.sellCount}}份</span> <span class="rating">好评率{{food.rating}}%</span> </div> <div class="price"> <span class="now">¥{{food.price}}</span> <span class="odd" v-if="food.oldPrice">¥{{food.oldPrice}}</span> </div> <div class="cartcontroll-wrapper"> <cart-controll :food="food" @add="addFood"></cart-controll> </div> <transition name="fade"> <div class="buy" v-show="!food.count || food.count===0" @click.stop.prevent="addFirst" transition="fade"> 加入购物车 </div> </transition> </div> <!--商品介绍--> <split :food="food"></split> <!--商品评价--> <div class="rating"> <h2 class="title">商品评价</h2> <ratingselect @select="selectRating" @toggle="toggleContent" :selectType="selectType" :onlyContent="onlyContent" :desc="desc" :ratings="food.ratings"></ratingselect> <div class="rating-wrapper"> <ul v-show="food.ratings && food.ratings.length"> <li v-for="rating in food.ratings" class="rating-item border-1px" v-show="needShow(rating.rateType,rating.text)"> <div class="user"> <span class="name">{{rating.username}}</span> <img :src="rating.avatar" alt="" class="avatar" width="12" height="12"> </div> <div class="time">{{rating.rateTime | formatDate}}</div> <p class="text"> <span :class="{'icon-thumb_up':rating.rateType===0,'icon-thumb_down':rating.rateType===1}"></span>{{rating.text}} </p> </li> </ul> <div class="no-rating" v-show="!food.ratings || !food.ratings.length">没有相关评论</div> </div> </div> </div> </div> </transition> </template> <script type="text/ecmascript-6"> import BScroll from 'better-scroll'; import {formatDate} from 'common/js/date'; import cartcontroll from 'components/cartcontroll/cartcontroll'; import split from 'components/split/split'; import ratingselect from 'components/ratingselect/ratingselect'; import Vue from 'vue'; const ALL = 2; export default { props: { food: { // 获取goods.vue传递过来 :food="selectedFood" type: Object } }, data() { return { showFlag: false, selectType: ALL, // ratingselect.vue默认被选中的评价类型(全部 推荐 吐槽) onlyContent: true, // 只看到有评论的内容默认是否勾选 desc: { // 默认向评价类型传递全部 推荐 吐槽 all: '全部', positive: '推荐', negative: '吐槽' } }; }, methods: { show() { // 内部组件自己的方法 _xxx this.showFlag = true; this.selectType = ALL; this.onlyContent = true; this.$nextTick(() => { if (!this.scroll) { this.scroll = new BScroll(this.$refs.food, { click: true }); } else { this.scroll.refresh(); }; }); }, addFirst(event) { // 点击加入购物车购物车消失 并将当前商品被选中数量加1 if (!event._constructed) { return; }; this.$emit('add', event.target); // 添加小球动画(将当前点击的位置传递给goods.vue) Vue.set(this.food, 'count', 1); // 这个商品被选中数量变为1(避免food没有count这个属性) }, needShow(type, text) { // 评论列表显示什么内容 type显示的类型(全部,推荐,吐槽) text if (this.onlyContent && !text) { // 当勾选了只显示有内容的评论,并且评论内容没有文本时 return false; // 符合这个条件的评论不显示 };
// 当这条评论有内容 或者 没有勾选‘只显示有内容的评论时’ if (this.selectType === ALL) { // 点击‘全部’ return true; // 这个评论显示 } else { // 当点击了其他'满意'或'吐槽' return type === this.selectType; // 当前评论数据的rating.rateType 如果与selectType一致就显示 } }, addFood(target) { this.$emit('add', target); }, // 使用ratingselect组件时传递@select="selectRating" @toggle="toggleContent" // ratingsselect.vue自身改变后 $emit这些事件使得子组件ratingselect.vue与父组件food.vue可以互相通信 // ratingselect.vue中this.$emit('select', type); this.$emit('toggle'); selectRating(type) { this.selectType = type; // 改变selectType的值 this.$nextTick(() => { this.scroll.refresh(); }); }, toggleContent() { this.onlyContent = !this.onlyContent; 改变onlyContent的值 this.$nextTick(() => { this.scroll.refresh(); }); } }, filters: { formatDate(time) { // 格式化时间 let date = new Date(time); return formatDate(date, 'yyyy-MM-dd hh:mm'); } }, components: { // 注册组件 cartControll: cartcontroll, split: split, ratingselect: ratingselect } }; </script> <style lang="stylus" rel="stylesheet/stylus"> @import "../../common/stylus/mixin.styl" .food position:fixed left:0 top:0 bottom:48px z-index:30 width:100% background-color:#fff transform: translate3d(0,0,0) /*food.vue从右平移到左*/ &.move-enter-active, &.move-leave-active transition:all 0.2s linear &.move-enter,&.move-leave-active transform:translate3d(100%,0,0) .img-header position:relative; width:100% height:0 padding-top:100% img position:absolute left:0 top:0 width:100% height:100% .back position:absolute top:10px left:0 .icon-arrow_lift display:block padding:10px font-size:20px color:#fff .content padding:18px position:relative .title line-height:14px margin-bottom:8px font-size:14px font-weight:700 color:rgb(7,17,27) .detail margin-bottom:10px line-height:10px font-size:0px height:10px .sell-count,.rating color:rgb(147,153,159) font-size:10px .sell-count margin-right:12px .price .now,add line-height:24px font-weight:700 .now font-size:14px color:rgb(240,20,20) margin-right:8px .odd font-size:10px color:rgb(147,153,159) text-decoration:line-through .cartcontroll-wrapper position:absolute right:12px bottom:12px .buy position:absolute right:18px bottom:18px z-index:10 height:24px line-height:24px text-align:center padding:0 12px box-sizing:border-box font-size:10px border-radius:12px color:#fff background-color:rgb(0,160,220) opacity:1 /*加入购车车延时消失*/ &.fade-enter-active, &fade.leave-active transition:all 0.2s &.fade-enter,&.fade-leave-active opacity:0 z-index:-1 .rating padding-top:18px .title line-height:14px margin-left:18px font-size:14px color:rgb(7,17,27) .rating-wrapper padding: 0 18px .rating-item position:relative padding:16px 0 border-1px(rgba(7,17,27,0.1)) .user position:absolute right:0 top:16px line-height:12px font-size:0 .name display:inline-block vertical-align:top font-size:10px color:rgb(147,153,159) margin-right:6px .avatar border-radius:50% .time margin-bottom:6px line-height:12px font-size:10px color:rgb(147,153,159) .text line-height:16px font-size:12px color:rgb(7,17,27) .icon-thumb_up,.icon-thumb_down line-height:16px margin-right:4px font-size:12px .icon-thumb_up color:rgb(0,160,220) .icon-thumb_down color:rgb(147,153,159) .no-rating padding:16px font-size:12px color:rgb(17,153,159) </style>
2.3 src/components/cartcontroll.vue
<template> <div class="cartcontroll"> <transition name="move"> <div class="cart-decrease" v-show="food.count>0" @click.stop.prevent="decreaseCart"> <span class="inner icon-remove_circle_outline"></span> </div> </transition> <div class="cart-count" v-show="food.count>0">{{food.count}}</div> <div class="cart-add icon-add_circle" @click.stop.prevent="addCart"></div> </div> </template> <script type="text/ecmascript-6"> import Vue from 'vue'; export default { props: { // 接收其他组件传递过来的数据 food: { type: Object } }, created() { // 该组件被引入时就会执行 }, methods: { // 指令方法 addCart: function(ev) { // 增加商品到购物车里 if (!ev._constructed) { // 去掉触发pc的事件--保证pc移动事件相同 return false; }; if (!this.food.count) { // this.food.count = 1; 不生效 Vue.set(this.food, 'count', 1); // 此时这个属性变化就能被观测到 } else { this.food.count++; }; // this.$dispatch('cart.add', ev.target); 2.0- 已被废除 // console.log(event.target); 点击的按钮的dom this.$emit('add', event.target); // 触发当前实例上的事件。附加参数都会传给监听器回调。 }, decreaseCart: function (ev) { // 减少购物车里面商品的数量 if (!ev._constructed) { return false; }; if (this.food.count) { this.food.count--; }; } } }; </script> <style lang="less" rel="stylesheet/less"> .cartcontroll{ font-size:0px; .cart-decrease{ display:inline-block; line-height: 24px; font-size: 24px; transition: all 0.4s linear; opacity: 1; transform: translate3d(0,0,0); .inner{ display:inline-block; padding: 6px; color:#00A0DC; transition:all 0.4s linear; transform: rotate(0deg); } &.move-enter-active, &.move-leave-active { transition: all 0.4s linear; } &.move-enter, &.move-leave-active{ opacity: 0; transform: translate3d(24px,0,0); .inner{ transform: rotate(180deg); } } } .cart-count{ display:inline-block; vertical-align: top; width:12px; padding-top:6px; line-height: 24px; text-align: center; font-size: 10px; color:rgb(147,153,159); } .cart-add{ display:inline-block; line-height: 24px; font-size: 24px; padding: 6px; color:#00A0DC; } } </style>