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>

浙公网安备 33010602011771号