文章目录

  • 一、内容介绍
    • 1、内容
    • 2、效果
  • 二、具体实现
    • 1、组件传值
    • 2、点击事件
    • 3、图片展示
    • 4、加入购物车
    • 5、分隔条组件
    • 6、评价展示
      • 布局
      • 评价筛选组件
      • 时间展示
  • 三、源码

一、内容介绍

1、内容

本篇文章主要实现的是商品详情页的展示,主要包括商品图片展示、商品信息展示和商品评价展示

2、效果


二、具体实现

1、组件传值

goods.vue

 <Food :food="selectedFood" ref="food"></Food>

food.vue通过props属性接收

 props: {food: {type: Object}// selectedCount: {//   type: Number,//   default: 0// }},

2、点击事件

当点击goods组件中的商品的时候展开商品详情页,因此给商品添加点击事件,让他能够选择food并保存到selectedFood 中

<li @click="selectedFoods(food,$event)"v-for="(food,index) in item.foods" :key="index" class="food-item"><div class="icon"><img :src="food.icon" alt="" width="57" height="57"></div><div class="content"><h2 class="name">{{food.name}}</h2><p class="desc">{{food.description}}</p><div class="extra"><span class="count">月售{{food.sellCount}}份</span><span>好评率{{food.rating}}%</span></div><div class="price"><span class="now">¥{{food.price}}</span><span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span></div><div class="cart-control"><Cartcontrol :food="food" v-on:car-add="carAdd"></Cartcontrol></div></div></li>
    selectedFoods (food, event) {if (!event._constructed) {return}this.selectedFood = foodthis.$refs.food.show()},

可以给food组件设置show函数。
由于子组件无法直接使用父组件的函数,父组件可以调用子组件的方法
通过观察food组件中的showflag的值实现展开

  data () {return {showFlag: false,selectType: ALL,onlyContent: true,desc: {all: '全部',positive: '推荐',negative: '吐槽'}}},
show () {// 每次加载之前进行初始化this.showFlag = truethis.selectType = ALLthis.onlyContent = truethis.$nextTick(() => {if (!this.scroll) {this.scroll = new BScroll(this.$refs.food, {click: true})} else {this.scroll.refresh()}})},

在父组件goods中调用子组件的方法,使用refs

  <Food :food="selectedFood" ref="food"></Food>

在selectedfoods中使用

selectedFoods (food, event) {if (!event._constructed) {return}this.selectedFood = food// console.log('1')console.log(this.selectedFood)this.$refs.food.show()},

3、图片展示

图片是固定在顶部的,由于图片的高度是不固定的因此一开始不能将模块的高度固定下来,但是可以使用

  height: 0;padding-top: 100%;

将图片达到等比的效果。

<div class="image-header"><img :src="food.image" alt="" /><div class="back" @click="hide"><i class="iconfont icon-fanhui"></i></div></div>

4、加入购物车

在商品详情页中也可以进行购物,该商品的选择数量为0的时候,展示加入购物车,当选择的数量不为0时,展示cartcontrol组件。

<div class="content"><h1 class="title">{{ food.name }}</h1><div class="food-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="old" v-show="food.oldPrice">¥{{ food.oldPrice }}</span></div><divclass="buy"v-show="!food.count || food.count === 0"@click.stop.prevent="addFirst">加入购物车</div><div class="cartcontrol-wrapper"><CartControl :food="food"></CartControl></div></div>

加入购物车,点击加入购物车的时候,派发car-add事件,并且将this.food的count设置为1。

    addFirst () {if (!event._constructed) {return}this.$emit('car-add', event.target)this.$set(this.food, 'count', 1)},

5、分隔条组件

// 分割条组件
<template><div class="split"></div>
</template><script>
export default {}
</script><style>
.split{width: 100%;height: 16px;border-top: 1px solid rgba(7, 17, 27, 0.1);border-bottom: 1px solid rgba(7, 17, 27, 0.1);background: #f3f5f7;
}
</style>

6、评价展示

商品评价部分主要包括评价的展示还有对不同类型的评价的筛选过滤。

布局
 <div class="rating"><h1 class="title">商品评价</h1><RatingSelect:select-type="selectType":only-content="onlyContent":desc="desc":ratings="food.ratings"@type-select="typeSelect"@content-toggle="conToggle"></RatingSelect><div class="rating-wrapper"><ul v-show="food.ratings"><li v-show="needShow(rating.rateType,rating.text)" v-for="(rating, index) in food.ratings" :key="index" class="rating-item"><div class="user"><span class="name">{{rating.username}}</span><img :src="rating.avatar" class="avatar" width="12" height="12" alt=""></div><div class="time">{{rating.rateTime | formatDate}}</div><p class="text"><span class="iconfont" :class='{"icon-dianzan":rating.rateType===0,"icon-chaping":rating.rateType===1}'></span>{{rating.text}}</p></li></ul><div class="no-rating" v-show="!food.ratings">暂无评价</div></div></div>
评价筛选组件

数据接收,需要接收来自父组件的评价、评价类型、是否只看有内容等信息

props: {ratings: {type: Array,default () {return []}},// 评价的类型selectType: {type: Number,default: ALL},// 只看哪一部分onlyContent: {type: Boolean,default: false},// 评价描述desc: {type: Object,default () {return {// 推荐吐槽可以通过使用组件的时候通过参数传入进来all: '全部',positive: '满意',negative: '不满意'}}}},

在评价类型的时候,我们定义了三个常量用来表示正向评价负面评价和全部评价

// 正向评价为0,负向评价为1,所有评价为2
const POSITIVE = 0
const NEGATIVE = 1
const ALL = 2

布局:

<template><div class="ratingselect"><div class="rating-type"><span @click="select(2,$event)" class="block positive" :class="{active1:typeSelected===2}">{{desc.all}}<span class="count">{{ratings.length}}</span></span><span @click="select(0,$event)" class="block positive" :class="{active1:typeSelected===0}">{{desc.positive}}<span class="count">{{positives.length}}</span></span><span @click="select(1,$event)" class="block negative" :class="{active2:typeSelected===1}">{{desc.negative}}<span class="count">{{negatives.length}}</span></span></div><div @click="toggleContent" class="switch" :class="{on:contOnly}"><span class="iconfont icon-success1"></span><span class="text">只看内容的评价</span></div></div>
</template>

父组件数据的定义和传入
定义:

 data () {return {showFlag: false,selectType: ALL,onlyContent: true,desc: {all: '全部',positive: '推荐',negative: '吐槽'}}},

传入:

<RatingSelect:select-type="selectType":only-content="onlyContent":desc="desc":ratings="food.ratings"@type-select="typeSelect"@content-toggle="conToggle"></RatingSelect>

由于每次使用RatingSelect组件的时候,可能都是传入的不同的状态,因此每次在使用的时候我们都应该在父组件food.vue中进行初始化

show () {// 每次加载之前进行初始化this.showFlag = truethis.selectType = ALLthis.onlyContent = truethis.$nextTick(() => {if (!this.scroll) {this.scroll = new BScroll(this.$refs.food, {click: true})} else {this.scroll.refresh()}})},

添加点击事件,实现评价类型切换。由于我们在父组件food.vue中使用selectType来控制初始的评价类型,而在子组件RatingSelect.vue中无法直接改变父组件中的数据,因此触发一个事件,交给父组件来处理。在select函数中,我们将评价类型和事件作为参数。
RatingSelect.vue

 select (type, event) {if (!event._constructed) {return}// console.log('1')this.typeSelected = type// 子组件派发事件,父组件监听事件this.$emit('type-select', type)},

food.vue

 // 评价类型切换typeSelect (type) {this.selectType = type// 由于改变selectType的时候DOM是没有更新的,因此还是需要异步更新this.$nextTick(() => {this.scroll.refresh()})},

同样只展示内容也是一样的子组件派发事件,父组件进行数据的修改
RatingSelect.vue

 // 按钮改变toggleContent (event) {if (!event._constructed) {return}this.contOnly = !this.contOnlythis.$emit('content-toggle', this.contOnly)}

food.vue

 // 有无内容展示conToggle (onlyContent) {this.onlyContent = onlyContentthis.$nextTick(() => {this.scroll.refresh()})}},

计算正向和负向评价

 computed: {// 计算正向评价positives () {return this.ratings.filter((rating) => {return rating.rateType === POSITIVE})},// 计算吐槽评价negatives () {return this.ratings.filter((rating) => {return rating.rateType === NEGATIVE})}}

点击显示对应条件的评价
当我们点击不同的评价类型的时候,需要切换显示对象条件的评价,在这里我们利用v-show来控制

<li v-show="needShow(rating.rateType,rating.text)" v-for="(rating, index) in food.ratings" :key="index" class="rating-item"><div class="user"><span class="name">{{rating.username}}</span><img :src="rating.avatar" class="avatar" width="12" height="12" alt=""></div><div class="time">{{rating.rateTime | formatDate}}</div><p class="text"><span class="iconfont" :class='{"icon-dianzan":rating.rateType===0,"icon-chaping":rating.rateType===1}'></span>{{rating.text}}</p></li>

通过needShow函数返回的Boolean值来实现该功能

 needShow (type, text) {// 判断是否要显示内容if (this.onlyContent && !text) {return false}// 判断选择的类型if (this.selectType === ALL) {return true} else {return (type === this.selectType)}},
时间展示

由于服务器拿到的时间为时间戳,我们在展示的时候需要将时间转化成字符串,因此在这里我们定义一个filters实现

<div class="time">{{rating.rateTime | formatDate}}</div>
 filters: {formatDate (time) {let date = new Date(time)return formatDates(date, 'yyyy-MM-dd hh:mm')}}

formatDate函数的实现
文件位置common/js/date.js

export function formatDates (date, fmt) {if (/(y+)/.test(fmt)) {fmt = fmt.replace(RegExp.$1, (date.getFullYear() + ''.substr(4 - RegExp.$1.length)))}let o = {'M+': date.getMonth() + 1,'d+': date.getDate(),'h+': date.getHours(),'m+': date.getMinutes(),'s+': date.getSeconds()}for (let k in o) {if (new RegExp(`(${k})`).test(fmt)) {let str = o[k] + ''fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str))}}return fmt
}
function padLeftZero (str) {return ('00' + str).substr(str.length)
}

三、源码

goods.vue

<template><div><div class="goods"><!-- 左侧菜单 --><div class="menu-wrapper" ref="menuWrapper"><ul><liv-for="(item,index) in goods":key="index"class="menu-item":class="{'current':currentIndex===index}"@click="selectMenu(index,$event)"><span class="text"><span v-show="item.type>0" class="icon" :class="classMap[item.type]"></span>{{item.name}}</span></li></ul></div><!-- 右侧商品 --><div class="foods-wrapper" ref="foodsWrapper"><ul><li v-for="(item,index) in goods" :key="index" class="food-list food-list-hook"><h1 class="title">{{item.name}}</h1><ul><li @click="selectedFoods(food,$event)"v-for="(food,index) in item.foods" :key="index" class="food-item"><div class="icon"><img :src="food.icon" alt="" width="57" height="57"></div><div class="content"><h2 class="name">{{food.name}}</h2><p class="desc">{{food.description}}</p><div class="extra"><span class="count">月售{{food.sellCount}}份</span><span>好评率{{food.rating}}%</span></div><div class="price"><span class="now">¥{{food.price}}</span><span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span></div><div class="cart-control"><Cartcontrol :food="food" v-on:car-add="carAdd"></Cartcontrol></div></div></li></ul></li></ul></div><!-- 购物车 --><ShopCartref="shopcart":delivery-price="seller.deliveryPrice":min-price="seller.minPrice":select-foods="selectFoods"></ShopCart></div><Food :food="selectedFood" ref="food"></Food></div>
</template>
<script>
import axios from 'axios'
import BScroll from 'better-scroll'
import ShopCart from '@/components/shopcart/shopcart'
import Cartcontrol from '@/components/cartcontrol/cartcontrol'
import Food from '@/components/food/food'
const ERR_OK = 0
export default {props: {seller: {type: Object}},data () {return {goods: [],listHeight: [], // 用来存储每个区间的高度scrollY: 0,selectedFood: {}}},components: {ShopCart,Cartcontrol,Food},computed: {// 计算对应切换的菜单下标currentIndex () {for (let i = 0; i < this.listHeight.length; i++) {let height1 = this.listHeight[i]let height2 = this.listHeight[i + 1]if (!height2 || (this.scrollY >= height1 && this.scrollY < height2)) {return i}}return 0},// 选中的foodselectFoods () {let foods = []// 找到所有被选择的foodsthis.goods.forEach((good) => {good.foods.forEach((food) => {// 如果food.count不为0的话,表示已经被选中过,将food push进foods中if (food.count) {foods.push(food)}})})return foods}},created () {this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee']axios.get('/api/goods').then((response) => {response = response.data// console.log(response)if (response.errno === ERR_OK) {this.goods = response.dataconsole.log(this.goods)this.$nextTick(() => {// 由于DOM对象是异步更新的// $nextTick 是在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后使用 $nextTick,则可以在回调中获取更新后的 DOMthis._initScroll()// 计算每一模块的高度,实现左右联动this._calculateHeight()})}})},methods: {// 滚动函数_initScroll () {// BScroll()有两个参数,第一个是dom对象,第二个是可选对象。给dom对象增加ref属性this.menuScroll = new BScroll(this.$refs.menuWrapper, {click: true})this.foodsScroll = new BScroll(this.$refs.foodsWrapper, {click: true,// 获取实时滚动的位置probeType: 3})// 监听滚动事件this.foodsScroll.on('scroll', (pos) => {this.scrollY = Math.abs(Math.round(pos.y))})},_calculateHeight () {// 使用原生DOM的方法获取高度// 通过food-list-hook获取每一个区间DOMlet foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook')// 高度初始值为0let height = 0this.listHeight.push(height)for (let i = 0; i < foodList.length; i++) {let item = foodList[i]// 函数clientHeight得到的DOM对象的高度height += item.clientHeightthis.listHeight.push(height)}},// 点击左侧menu切换// 点击左边的menu列表时,根据index,通过scrollToElement把右边的列表滚动到对应的位置selectMenu (index, event) {// 当自己触发一个事件的时候event._constructed为true,但是浏览器没有这个event._constructed的话为falseif (!event._constructed) {}let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook')let el = foodList[index]this.foodsScroll.scrollToElement(el, 300)// console.log(index)},selectedFoods (food, event) {if (!event._constructed) {return}this.selectedFood = food// console.log('1')console.log(this.selectedFood)this.$refs.food.show()},carAdd (target) {this.$refs.shopcart.drop(target)}}
}
</script><style>
.goods{display: flex;position: absolute;width: 100%;top: 174px;bottom: 46px;overflow: hidden;
}
.goods .menu-wrapper{flex: 0 0 80px;width: 80px;background: #f3f5f7;
}
.goods .menu-wrapper .menu-item{display: table;  /**垂直居中,不管是一行还是两行 */height: 54px;width: 56px;padding: 0 12px;line-height: 14px;
}
.goods .menu-wrapper .current{font-size: 12px;position: relative;margin-top: -1px;z-index: 10;background: #fff;font-weight: 700;
}
.goods .menu-wrapper .menu-item .icon{display: inline-block;width: 12px;height: 12px;margin-right: 2px;background-size: 12px 12px;background-repeat: no-repeat;
}
.goods .menu-wrapper .menu-item .decrease{background-image: url('./decrease_4@3x.png')
}
.goods .menu-wrapper .menu-item .discount{background-image: url('./discount_4@3x.png')
}
.goods .menu-wrapper .menu-item .guarantee{background-image: url('./guarantee_4@3x.png')
}
.goods .menu-wrapper .menu-item .invoice{background-image: url('./invoice_4@3x.png')
}
.goods .menu-wrapper .menu-item .special{background-image: url('./special_4@3x.png')
}
.goods .menu-wrapper .menu-item .text{font-size: 12px;display: table-cell;width: 56px;vertical-align: middle; /**垂直居中 */
}
.goods .foods-wrapper{flex: 1;
}
.goods .foods-wrapper .title{padding-left:14px;height: 26px;line-height: 26px;border-left: 2px solid #d9dde1;font-size: 12px;color: rgb(147, 153, 159);background: #f3f5f7;
}
.goods .foods-wrapper .food-item{display: flex;margin: 18px;padding-bottom: 18px;border-bottom: 1px solid rgba(7, 17, 27, 0.1);
}
.goods .foods-wrapper .food-item .icon{flex: 0 0 57px;margin-right: 10px;
}
.goods .foods-wrapper .food-item .content{flex: 1;
}
.goods .foods-wrapper .food-item .content .name{margin: 2px 0 8px 0;height: 14px;line-height: 14px;font-size: 14px;color: rgb(7, 17, 27);
}
.goods .foods-wrapper .food-item .content .desc,
.goods .foods-wrapper .food-item .content .extra{line-height: 10px;font-size: 10px;color: rgb(147, 153, 159);
}
.goods .foods-wrapper .food-item .content .desc{margin-bottom: 8px;line-height: 12px;
}
.goods .foods-wrapper .food-item .content .extra .count{margin-right: 12px;
}
.goods .foods-wrapper .food-item .content .price{font-weight: 700;line-height: 24px;
}
.goods .foods-wrapper .food-item .content .price .now{margin-right: 18px;font-size: 14px;color: rgb(240, 20, 20);
}
.goods .foods-wrapper .food-item .content .price .old{text-decoration: line-through;font-size: 10px;color: rgb(147, 153, 159);
}
.goods .foods-wrapper .food-item .content .cart-control{position: absolute;right: 0;/* bottom: 1px; */
}
</style>

food.vue

<template><transition name="move"><div class="food" v-show="showFlag" ref="food"><div class="food-content"><div class="image-header"><img :src="food.image" alt="" /><div class="back" @click="hide"><i class="iconfont icon-fanhui"></i></div></div><div class="content"><h1 class="title">{{ food.name }}</h1><div class="food-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="old" v-show="food.oldPrice">¥{{ food.oldPrice }}</span></div><divclass="buy"v-show="!food.count || food.count === 0"@click.stop.prevent="addFirst">加入购物车</div><div class="cartcontrol-wrapper"><CartControl :food="food"></CartControl></div></div><Split v-show="food.info"></Split><div class="info" v-show="food.info"><h1 class="title">商品信息</h1><p class="text">{{ food.info }}</p></div><Split></Split><div class="rating"><h1 class="title">商品评价</h1><RatingSelect:select-type="selectType":only-content="onlyContent":desc="desc":ratings="food.ratings"@type-select="typeSelect"@content-toggle="conToggle"></RatingSelect><div class="rating-wrapper"><ul v-show="food.ratings"><li v-show="needShow(rating.rateType,rating.text)" v-for="(rating, index) in food.ratings" :key="index" class="rating-item"><div class="user"><span class="name">{{rating.username}}</span><img :src="rating.avatar" class="avatar" width="12" height="12" alt=""></div><div class="time">{{rating.rateTime | formatDate}}</div><p class="text"><span class="iconfont" :class='{"icon-dianzan":rating.rateType===0,"icon-chaping":rating.rateType===1}'></span>{{rating.text}}</p></li></ul><div class="no-rating" v-show="!food.ratings">暂无评价</div></div></div></div></div></transition>
</template>
<script>
import BScroll from 'better-scroll'
import CartControl from '@/components/cartcontrol/cartcontrol'
import Split from '@/components/split/split'
import RatingSelect from '@/components/ratingSelect/ratingSelect'
import {formatDates} from '@/common/js/date.js'
// const POSITIVE = 0
// const NEGATIVE = 1
const ALL = 2
export default {props: {food: {type: Object}// selectedCount: {//   type: Number,//   default: 0// }},data () {return {showFlag: false,selectType: ALL,onlyContent: true,desc: {all: '全部',positive: '推荐',negative: '吐槽'}}},components: {CartControl,Split,RatingSelect},methods: {show () {// 每次加载之前进行初始化this.showFlag = truethis.selectType = ALLthis.onlyContent = truethis.$nextTick(() => {if (!this.scroll) {this.scroll = new BScroll(this.$refs.food, {click: true})} else {this.scroll.refresh()}})},// 关闭商品详情页hide () {this.showFlag = false},addFirst () {if (!event._constructed) {return}this.$emit('car-add', event.target)this.$set(this.food, 'count', 1)},needShow (type, text) {// 判断是否要显示内容if (this.onlyContent && !text) {return false}// 判断选择的类型if (this.selectType === ALL) {return true} else {return (type === this.selectType)}},// 评价类型切换typeSelect (type) {this.selectType = type// 由于改变selectType的时候DOM是没有更新的,因此还是需要异步更新this.$nextTick(() => {this.scroll.refresh()})},// 有无内容展示conToggle (onlyContent) {this.onlyContent = onlyContentthis.$nextTick(() => {this.scroll.refresh()})}},//filters: {formatDate (time) {let date = new Date(time)return formatDates(date, 'yyyy-MM-dd hh:mm')}}
}
</script><style scoped>
.food {/* 覆盖整个屏幕 */position: fixed;left: 0;/* 底部有个购物车 */bottom: 48px;top: 0;z-index: 30;width: 100%;background: #fff;
}
.move-enter-active,
.move-leave-active {transform: translate3d(0, 0, 0);transition: all 0.3s linear;
}
.move-enter,
.move-leave {transform: translate3d(100%, 0, 0);
}
.food .food-content .image-header {position: relative;width: 100%;height: 0;/* 由于手机的屏幕宽度的不确定,因此图片的高度不确定 */padding-top: 100%;
}
.food .food-content .image-header img {position: absolute;left: 0;top: 0;width: 100%;height: 100%;
}
.food .food-content .image-header .back {position: absolute;top: 10px;left: 0;
}
.food .food-content .image-header .iconfont {display: block;/* 增大点击区域 */padding: 10px;font-size: 20px;color: #fff;
}
.food .food-content .content {padding: 18px;
}
.food .food-content .content .title {font-size: 14px;line-height: 14px;margin-bottom: 8px;font-weight: 700;color: rgb(7, 17, 27);
}
.food .food-content .content .food-detail {margin-bottom: 18px;line-height: 10px;font-size: 0;height: 10px;
}
.food .food-content .content .food-detail .sell-count,
.food .food-content .content .food-detail .rating {font-size: 10px;color: rgb(147, 153, 159);
}
.food .food-content .content .food-detail .sell-count {margin-right: 12px;
}
.food .food-content .content .price {font-weight: 700;line-height: 24px;
}
.food .food-content .content .price .now {margin-right: 8px;font-size: 14px;color: rgb(240, 20, 20);
}
.food .food-content .content .price .old {text-decoration: line-through;font-size: 10px;color: rgb(147, 153, 159);
}
.food .food-content .content{position: relative;
}
.food .food-content .cartcontrol-wrapper {position: absolute;right: 12px;bottom: 12px;
}
.food .food-content .buy {position: absolute;right: 18px;bottom: 28px;z-index: 10;height: 24px;line-height: 24px;padding: 0 12px;box-sizing: border-box;font-size: 10px;border-radius: 12px;color: #fff;background: rgb(0, 160, 220);
}
.food .food-content .info{padding: 18px;
}
.food .food-content .info .title{line-height: 14px;margin-bottom: 6px;font-size: 14px;color: rgb(7,17,17);
}
.food .food-content .info .text{line-height: 24px;font-size: 12px;padding: 0 8px;color: rgb(77,85,93);
}
.food .food-content .rating{padding-top: 18px;
}
.food .food-content .rating .title{line-height: 14px;margin-left: 18px;font-size: 14px;color: rgb(7,17,17);
}
.food .food-content .rating .rating-wrapper{padding: 0 18px;
}
.food .food-content .rating .rating-wrapper .rating-item{position: relative;padding: 16px 0;border-bottom: 1px solid rgba(7, 17, 27, 0.1)
}
.food .food-content .rating .rating-wrapper .rating-item .user{position: absolute;right: 0;top: 16px;font-size: 0;line-height: 12px;
}
.food .food-content .rating .rating-wrapper .rating-item .user .name{display: inline-block;vertical-align: top;margin-right: 6px;font-size: 10px;color: rgb(147, 153, 159);
}
.food .food-content .rating .rating-wrapper .rating-item .user .avatar{border-radius: 50%;
}
.food .food-content .rating .rating-wrapper .rating-item .time{margin-bottom: 6px;line-height: 12px;font-size: 10px;color: rgb(147, 153, 159);
}
.food .food-content .rating .rating-wrapper .rating-item .text{line-height: 16px;font-size: 12px;color: rgb(7, 17, 27);
}
.icon-dianzan, .icon-chaping{margin-right: 4px;line-height: 16px;font-size: 12px;
}
.icon-dianzan{color: rgb(0, 160, 220);
}
.icon-chaping{color: rgb(147, 153, 159);
}
.food .food-content .rating .rating-wrapper .no-rating{padding: 16px 0;font-size: 12px;color: rgb(147, 153, 159);
}
</style>

ratingSelect.vue

<template><div class="ratingselect"><div class="rating-type"><span @click="select(2,$event)" class="block positive" :class="{active1:typeSelected===2}">{{desc.all}}<span class="count">{{ratings.length}}</span></span><span @click="select(0,$event)" class="block positive" :class="{active1:typeSelected===0}">{{desc.positive}}<span class="count">{{positives.length}}</span></span><span @click="select(1,$event)" class="block negative" :class="{active2:typeSelected===1}">{{desc.negative}}<span class="count">{{negatives.length}}</span></span></div><div @click="toggleContent" class="switch" :class="{on:contOnly}"><span class="iconfont icon-success1"></span><span class="text">只看内容的评价</span></div></div>
</template><script>
// 正向评价为0,负向评价为1,所有评价为2
const POSITIVE = 0
const NEGATIVE = 1
const ALL = 2
export default {props: {ratings: {type: Array,default () {return []}},// 评价的类型selectType: {type: Number,default: ALL},// 只看哪一部分onlyContent: {type: Boolean,default: false},// 评价描述desc: {type: Object,default () {return {// 推荐吐槽可以通过使用组件的时候通过参数传入进来all: '全部',positive: '满意',negative: '不满意'}}}},data () {return {typeSelected: this.selectType,contOnly: this.onlyContent}},methods: {// 点击区块外层还是有一个// 菜单切换select (type, event) {if (!event._constructed) {return}// console.log('1')this.typeSelected = type// 子组件派发事件,父组件监听事件this.$emit('type-select', type)},// 按钮改变toggleContent (event) {if (!event._constructed) {return}this.contOnly = !this.contOnlythis.$emit('content-toggle', this.contOnly)}},computed: {// 计算正向评价positives () {return this.ratings.filter((rating) => {return rating.rateType === POSITIVE})},// 计算吐槽评价negatives () {return this.ratings.filter((rating) => {return rating.rateType === NEGATIVE})}}
}
</script><style scoped>
.ratingselect .rating-type{padding: 18px 0;margin: 0 18px;/* 消除间隙 */font-size: 0;border-bottom: 1px solid rgba(7, 17, 27,0.1)
}
.ratingselect .rating-type .block{display: inline-block;padding: 8px 12px;border-radius: 2px;margin-right: 8px;line-height: 16px;font-size: 12px;color: rgb(77,85,93);
}
.ratingselect .rating-type .block .count{font-size: 8px;margin-left: 2px;
}
.ratingselect .rating-type .positive{background: rgba(0,160,220,0.2);
}
.ratingselect .rating-type .negative{background: rgba(77,85,93,0.2);
}
/* 权重相同后面的会覆盖前面的 */
.ratingselect .rating-type .active1{background: rgb(0,160,220);
}
.ratingselect .rating-type .active2{color: black;background: rgb(77,85,93);
}
.ratingselect .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;
}
.ratingselect .switch .iconfont{display: inline-block;vertical-align: top;font-size: 24px;margin-right: 4px;
}
/* 被选中 */
.ratingselect .on .iconfont{color: #00c850;
}
.ratingselect .switch .text{font-size: 12px;display: inline-block;vertical-align: top;
}
</style>

Vue.js仿饿了么外卖App--(4)商品详情页实现相关推荐

  1. Vue.js仿饿了么外卖App--(6)商家详情页实现

    文章目录 一.内容介绍 1.内容 2.效果 二.具体实现 1.布局 2.滚动实现 三.源码 一.内容介绍 1.内容 商家详情页主要包括商家的具体信息.公告与活动.商家实景和商家信息.其中商家实景这里实 ...

  2. vue开发饿了吗外卖app⑥——goods商品列表页开发和小球飞入动画

    goods组件显示的区域是固定的,也没有滚动条,所以是采用绝对布局的,左右分为menu栏和foods栏,左边固定布局,右边自适应布局,采用flex布局. 写CSS样式的时候,尽量用class,少用ta ...

  3. Vue.js仿饿了么外卖App--(2)头部相关的组件的实现

    本篇文章主要实现的是App.vue组件.头部组件header.蒙层组件detal和Modal以及路由切换router 文章目录 一.App.vue组件 1.重点说明 2.具体实现 (1).整体布局 ( ...

  4. Vue.js仿饿了么外卖App--(5)评价列表页实现

    文章目录 一.内容介绍 1.内容 2.效果 二.具体实现 1.数据的获取 2.具体布局 3.star组件 4.ratingSelect组件 三.源码 一.内容介绍 1.内容 本篇文章主要介绍的是评价列 ...

  5. Vue.js高仿饿了么外卖App学习记录

    (给达达前端加星标,提升前端技能) 开发一款vue.js开发一款app,使用vue.js是一款高效的mvvm框架,它轻量,高效,组件化,数据驱动等功能便于开发.使用vue.js开发移动端app,学会使 ...

  6. vue结合饿了么_Vue.js 高仿饿了么外卖app 全套_IT教程网

    资源名称:Vue.js  高仿饿了么外卖app  全套 资源目录: vue仿饿了么视频全套 全套 资源 │ files.txt │ project.zip │ resource.zip │ ├─第01 ...

  7. 项目:Vue.js高仿饿了吗外卖APP(一)

    Vue.js高仿饿了吗外卖APP核心知识 使用Vue.js作为项目的技术栈!这是目前最火的MVVM框架(之一),轻量.简洁.高效.数据驱动.组件化的优点,被大家称为"简单却不失优雅,小巧而不 ...

  8. Vue.js 高仿饿了么外卖APP

    第1章 课程简介 介绍课程的学习目标和学习内容. 1-1 课程简介 1-2 课程安排 第2章 Vuejs介绍 从前端开发趋势分析开始,引入 MVVM 开发框架和 Vue.js,接着对比流行框架Angu ...

  9. 【慕课网实战课程笔记】Vue.js高仿饿了么外卖App

    写在前面:该课程为慕课网付费课程,笔记内容代码居多.内容简略,仅供自己日后翻阅.如有疑问或者不妥,欢迎提出指正,我看到了会回复,谢谢! 第1章:课程简介 第2章:Vuejs介绍 Ctrl+Alt+l ...

最新文章

  1. XXL-CONF v1.4.1 发布,分布式配置管理平台
  2. 解析jsonarra_使用JSONReader或JSONObject / JSONArray解析JSON数据
  3. 自走棋电脑版_巨鸟公布自走棋正版自研手游:龙渊发行
  4. AJAX异步原理与实现
  5. ASP.NET Core依赖注入解读使用Autofac替代实现
  6. 什么90%的人,自学python都不能学会,原来问题出在这,赶紧看看!
  7. Objective C 总结(十):Conventions
  8. 看云电子书归档 2016.4
  9. 高德上线“家人地图”惹争议 官方回应:用户确认授权后才能使用
  10. java按时间范围过滤_按日期范围在WooCommerce中过滤产品
  11. 2019php面试题
  12. python如何执行代码漏洞_在漏洞利用Python代码真的很爽
  13. 利用python在word文档中查找关键字(支持多个文档和多个关键字)
  14. VS2019安装和使用教程(超详细)
  15. python dxf_使用Python读取AutoCAD DXF文档
  16. kubernetes使用secrets保存敏感信息
  17. 深度学习实战(4)如何向BERT词汇表中添加token,新增特殊占位符
  18. java好学么_java是什么好学吗
  19. 大学开学初计算机水平测试,请查收这份大学生开学攻略——
  20. 脉冲发生器c语言程序,可编程脉冲信号发生器的.doc

热门文章

  1. iphone光感测试软件,苹果iPhone12增加人物检测辅助功能,利用激光雷达帮盲人群体...
  2. 看到网友晒了新抱枕,我也想换个新的了
  3. STM32 高级定时TIM 死区时间计算--C语言实现
  4. 08.GPIO基础知识和工作原理
  5. Centos+Aria2+AriaNg+Trancers更新
  6. 5. SAP S/4 运维基础知识(Basic Knowledge) - SAP S/4 Basis Learning
  7. Solidity Integer Overflow and Underflow
  8. AI芯片:寒武纪DaDianNao结构分析
  9. 「图文」介绍下微信怎么拉票刷票及微信投票怎样自己拉票方法
  10. go语言:给map上锁