字符过长过滤 数字截取小数点后两位过滤
filters: {
charsFormat: function (data) {
if (data && data.length > 49) {
data = data.substring(0, 49) + '...'
}
return data
}
},
formatData: function (data) {
let patrn = /^-?\d*\.?\d*$/
if (patrn.test(data)) {
let strings = data.toString()
let index = strings.indexOf('.')
if ( index > 0) {
data = parseFloat(strings.substring(0, index + 3))
return data
}
} else {
return data
}
}
时间过滤组件
index.js
使用
在别的组件中加载
import './filters'
import Vue from 'vue'
// import moment from 'moment'
import format from 'date-fns/format'
// 自定义过滤器
Vue.filter('date-format', function (value, formatStr='YYYY-MM-DD HH:mm:ss') {
// return moment(value).format(formatStr)
return format(value, formatStr)
})
返回异常的问题
router-link加replace
<router-link replace>
图片懒加载插件vue-lazyload的使用
图片懒加载: vue-lazyload
1) Github:
https://github.com/hilongjw/vue-lazyload
2) 下载包
npm install --save vue-loader
3) 使用
import VueLazyload from 'vue-lazyload'
import loading from './common/img/loading.gif'
Vue.use(VueLazyload, {
loading
})
<img v-lazy="food.image">
打包文件分析与优化
1) vue 脚手架提供了一个用于可视化分析打包文件的包 webpack-bundle-analyzer 和配置
2) 启用打包可视化: npm run build --report
优化: 使用 date-fns 代替 moment
// import moment from 'moment'
// import {format} from 'date-fns'
import format from 'date-fns/format'
import Vue from 'vue'
Vue.filter('dateString', function (value, formatStr) {
// return moment(value).format(format || 'YYYY-MM-DD HH:mm:ss')
return format(value, formatStr || 'YYYY-MM-DD HH:mm:ss')
})
缓存路由组件对象
<keep-alive>
<router-view />
</keep-alive>
好处: 复用路由组件对象, 复用路由组件获取的后台数据
滑动betterScroll
数据更新后执行
mounted() {
this.$store.dispatch('getShopGoods', () => {// 数据更新后执行
this.$nextTick(() => { // 列表数据更新显示后执行
this._initScroll()
this._initTops()
})
})
},
//actions/vux
// 异步获取商家商品列表
async getShopGoods({commit}, callback) {
const result = await reqShopGoods()
if (result.code === 0) {
const goods = result.data
commit(RECEIVE_GOODS, {goods})
// 数据更新了, 通知一下组件
callback && callback()
}
},
完整
<template>
<div>
<div class="goods">
<div class="menu-wrapper">
<ul>
<!--current-->
<li class="menu-item" v-for="(good, index) in goods" :key="index"
:class="{current: index===currentIndex}" @click="clickMenuItem(index)">
<span class="text bottom-border-1px">
<img class="icon" :src="good.icon" v-if="good.icon">
{{good.name}}
</span>
</li>
</ul>
</div>
<div class="foods-wrapper">
<ul ref="foodsUl">
<li class="food-list-hook" v-for="(good, index) in goods" :key="index">
<h1 class="title">{{good.name}}</h1>
<ul>
<li class="food-item bottom-border-1px" v-for="(food, index) in good.foods"
:key="index" @click="showFood(food)">
<div class="icon">
<img width="57" height="57" :src="food.icon">
</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-if="food.oldPrice">¥{{food.oldPrice}}</span>
</div>
<div class="cartcontrol-wrapper">
<CartControl :food="food"/>
</div>
</div>
</li>
</ul>
</li>
</ul>
</div>
<ShopCart />
</div>
<Food :food="food" ref="food"/>
</div>
</template>
<script>
import BScroll from 'better-scroll'
import {mapState} from 'vuex'
import CartControl from '../../../components/CartControl/CartControl.vue'
import Food from '../../../components/Food/Food.vue'
import ShopCart from '../../../components/ShopCart/ShopCart.vue'
export default {
data() {
return {
scrollY: 0, // 右侧滑动的Y轴坐标 (滑动过程时实时变化)
tops: [], // 所有右侧分类li的top组成的数组 (列表第一次显示后就不再变化)
food: {}, // 需要显示的food
}
},
mounted() {
this.$store.dispatch('getShopGoods', () => {// 数据更新后执行
this.$nextTick(() => { // 列表数据更新显示后执行
this._initScroll()
this._initTops()
})
})
},
computed: {
...mapState(['goods']),
// 计算得到当前分类的下标
currentIndex() {// 初始和相关数据发生了变化
// 得到条件数据
const {scrollY, tops} = this
// 根据条件计算产生一个结果
const index = tops.findIndex((top, index) => {
// scrollY>=当前top && scrollY<下一个top
return scrollY >= top && scrollY < tops[index + 1]
})
// 返回结果
return index
}
},
methods: {
// 初始化滚动
_initScroll() {
// 列表显示之后创建
new BScroll('.menu-wrapper', {
click: true
})
this.foodsScroll = new BScroll('.foods-wrapper', {
probeType: 2, // 因为惯性滑动不会触发
click: true
})
// 给右侧列表绑定scroll监听
this.foodsScroll.on('scroll', ({x, y}) => {
console.log(x, y)
this.scrollY = Math.abs(y)
})
// 给右侧列表绑定scroll结束的监听
this.foodsScroll.on('scrollEnd', ({x, y}) => {
console.log('scrollEnd', x, y)
this.scrollY = Math.abs(y)
})
},
// 初始化tops
_initTops() {
// 1. 初始化tops
const tops = []
let top = 0
tops.push(top)
// 2. 收集
// 找到所有分类的li
const lis = this.$refs.foodsUl.getElementsByClassName('food-list-hook')
Array.prototype.slice.call(lis).forEach(li => {
top += li.clientHeight
tops.push(top)
})
// 3. 更新数据
this.tops = tops
console.log(tops)
},
clickMenuItem(index) {
// console.log(index)
// 使用右侧列表滑动到对应的位置
// 得到目标位置的scrollY
const scrollY = this.tops[index]
// 立即更新scrollY(让点击的分类项成为当前分类)
this.scrollY = scrollY
// 平滑滑动右侧列表
this.foodsScroll.scrollTo(0, -scrollY, 300)
},
// 显示点击的food
showFood (food) {
// 设置food
this.food = food
// 显示food组件 (在父组件中调用子组件对象的方法)
this.$refs.food.toggleShow()
}
},
components: {
CartControl,
Food,
ShopCart
}
}
</script>
控制商品数量的组件cartControl的使用
cartControl组件
<template>
<div class="cartcontrol">
<transition name="move">//过渡效果
<div class="iconfont icon-remove_circle_outline" v-if="food.count" @click.stop="updateFoodCount(false)"></div>
</transition>
<div class="cart-count" v-if="food.count">{{food.count}}</div>
<div class="iconfont icon-add_circle" @click.stop="updateFoodCount(true)"></div>
</div>
</template>
<script>
export default {
props: {
food: Object
},
methods: {
updateFoodCount (isAdd) {
this.$store.dispatch('updateFoodCount', {isAdd, food: this.food})
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import "../../common/stylus/mixins.styl"
.cartcontrol
font-size: 0
.cart-decrease
display: inline-block
padding: 6px
line-height: 24px
font-size: 24px
color: rgb(0, 160, 220)
.icon-remove_circle_outline
display: inline-block
padding 6px
line-height 24px
font-size 24px
color $green
&.move-enter-active, &.move-leave-active
transition all .3s
&.move-enter, &.move-leave-to
opacity 0
transform translateX(15px) 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)
.icon-add_circle
display: inline-block
padding: 6px
line-height: 24px
font-size: 24px
color $green
</style>
//在使用shopGoods中使用
1 引入和定义
import CartControl from '../../../components/CartControl/CartControl.vue'
components: {
CartControl
}
2使用与传参
<li class="food-list-hook" v-for="(good, index) in goods" :key="index">
<div class="cartcontrol-wrapper">
<CartControl :food="food"/> 传每一个商品对象
</div>
</li>
** 在store中定义每一个food 的count以及控制**
**mutation-types**
export const INCREMENT_FOOD_COUNT = 'increment_food_count' // 增加food中的count
export const DECREMENT_FOOD_COUNT = 'decrement_food_count' // 减少food中的count
**
action**
// 同步更新food中的count值
updateFoodCount({commit}, {isAdd, food}) {
if (isAdd) {
commit(INCREMENT_FOOD_COUNT, {food})
} else {
commit(DECREMENT_FOOD_COUNT, {food})
}
},
** mutation**
import Vue from 'vue'
[INCREMENT_FOOD_COUNT](state, {food}) {
if(!food.count) { // 第一次增加
// food.count = 1 // 新增属性(没有数据绑定)
/*
对象
属性名
属性值
*/
Vue.set(food, 'count', 1) // 让新增的属性也有数据绑定
// 将food添加到cartFoods中
state.cartFoods.push(food)
} else {
food.count++
}
},
[DECREMENT_FOOD_COUNT](state, {food}) {
if(food.count) {// 只有有值才去减
food.count--
if(food.count===0) {
// 将food从cartFoods中移除
state.cartFoods.splice(state.cartFoods.indexOf(food), 1)
}
}
},
** state**
/*
状态对象
*/
export default {
latitude: 40.10038, // 纬度
longitude: 116.36867, // 经度
address: {}, //地址相关信息对象
categorys: [], // 食品分类数组
shops: [], // 商家数组
userInfo: {}, // 用户信息
goods: [], // 商品列表
ratings: [], // 商家评价列表
info: {}, // 商家信息
cartFoods: [], // 购物车中食物的列表
searchShops: [], // 搜索得到的商家列表
}
商品详情弹窗组件
<template>
<div class="food" v-if="isShow">
<div class="food-content">
<div class="image-header">
<img v-lazy="food.image">
<p class="foodpanel-desc">{{food.info}}</p>
<div class="back" @click="toggleShow">
<i class="iconfont icon-arrow_left"></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="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
</div>
<div class="cartcontrol-wrapper">
<CartControl :food="food"/>
</div>
</div>
</div>
<div class="food-cover" @click="toggleShow"></div>
</div>
</template>
<script>
import CartControl from '../CartControl/CartControl.vue'
export default {
props: {
food: Object
},
data () {
return {
isShow: false
}
},
methods: {
toggleShow () {
this.isShow = !this.isShow
}
},
components: {
CartControl
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus" scoped>
@import "../../common/stylus/mixins.styl"
.food
position: fixed
left: 0
top: 0
bottom: 48px
z-index: 101
width: 100%
&.fade-enter-active, &.fade-leave-active
transition opacity .5s
&.fade-enter, &.fade-leave-to
opacity 0
.food-content
position absolute
left 50%
top 50%
transform translate(-50%, -50%)
width 80%
height 65%
z-index 66
background #fff
border-radius 5px
.image-header
position: relative
width: 100%
height: 0
padding-top: 100%
img
position: absolute
top: 0
left: 0
width: 100%
height: 100%
.foodpanel-desc
font-size 10px
color #ddd
letter-spacing 0
position absolute
bottom 0
left 0
right 0
padding 0 10px 10px
.back
position: absolute
top: 10px
left: 0
.icon-arrow_left
display: block
padding: 10px
font-size: 20px
color: #fff
.content
position: relative
padding: 18px
.title
line-height: 14px
margin-bottom: 8px
font-size: 14px
font-weight: 700
color: rgb(7, 17, 27)
.detail
margin-bottom: 18px
line-height: 10px
height: 10px
font-size: 0
.sell-count, .rating
font-size: 10px
color: rgb(147, 153, 159)
.sell-count
margin-right: 12px
.price
font-weight: 700
line-height: 24px
.now
margin-right: 8px
font-size: 14px
color: rgb(240, 20, 20)
.old
text-decoration: line-through
font-size: 10px
color: rgb(147, 153, 159)
.cartcontrol-wrapper
position: absolute
right: 12px
bottom: 12px
.buy
position: absolute
right: 18px
bottom: 18px
z-index: 10
height: 24px
line-height: 24px
padding: 0 12px
box-sizing: border-box
border-radius: 12px
font-size: 10px
color: #fff
background: rgb(0, 160, 220)
&.fade-transition
transition: all 0.2s
opacity: 1
&.fade-enter, &.fade-leave
opacity: 0
.food-cover
position absolute
top 0
right 0
bottom -48px
left 0
z-index 55
background-color rgba(0, 0, 0, 0.5)
</style>
父组件中使用
<li class="food-item bottom-border-1px" v-for="(food, index) in good.foods"
:key="index" @click="showFood(food)">
<div class="icon">
<img width="57" height="57" :src="food.icon">
</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-if="food.oldPrice">¥{{food.oldPrice}}</span>
</div>
<div class="cartcontrol-wrapper">
<CartControl :food="food"/>
</div>
</div>
</li>
<Food :food="food" ref="food"/>
import Food from '../../../components/Food/Food.vue'
data() {
return {
food: {}, // 需要显示的food
}
},
components: {
Food
}
// 显示点击的food
showFood (food) {
// 设置food
this.food = food
// 显示food组件 (在父组件中调用子组件对象的方法)
this.$refs.food.toggleShow()
}
购物车组件
<template>
<div>
<div class="shopcart">
<div class="content">
<div class="content-left" @click="toggleShow">
<div class="logo-wrapper">
<div class="logo" :class="{highlight: totalCount}">
<i class="iconfont icon-shopping_cart" :class="{highlight: totalCount}"></i>
</div>
<div class="num" v-if="totalCount">{{totalCount}}</div>
</div>
<div class="price" :class="{highlight: totalCount}">¥{{totalPrice}}</div>
<div class="desc">另需配送费¥{{info.deliveryPrice}}元</div>
</div>
<div class="content-right">
<div class="pay" :class="payClass">
{{payText}}
</div>
</div>
</div>
<transition name="move">
<div class="shopcart-list" v-show="listShow">
<div class="list-header">
<h1 class="title">购物车</h1>
<span class="empty" @click="clearCart">清空</span>
</div>
<div class="list-content">
<ul>
<li class="food" v-for="(food, index) in cartFoods" :key="index">
<span class="name">{{food.name}}</span>
<div class="price"><span>¥{{food.price}}</span></div>
<div class="cartcontrol-wrapper">
<CartControl :food="food"/>
</div>
</li>
</ul>
</div>
</div>
</transition>
</div>
<div class="list-mask" v-show="listShow" @click="toggleShow"></div>
</div>
</template>
<script>
import { MessageBox } from 'mint-ui'
import BScroll from 'better-scroll'
import {mapState, mapGetters} from 'vuex'
import CartControl from '../CartControl/CartControl.vue'
export default {
data () {
return {
isShow: false
}
},
computed: {
...mapState(['cartFoods', 'info']),
...mapGetters(['totalCount', 'totalPrice']),
payClass () {
const {totalPrice} = this
const {minPrice} = this.info
return totalPrice>=minPrice ? 'enough' : 'not-enough'
},
payText () {
const {totalPrice} = this
const {minPrice} = this.info
if(totalPrice===0) {
return `¥${minPrice}元起送`
} else if(totalPrice<minPrice) {
return `还差¥${minPrice-totalPrice}元起送`
} else {
return '结算'
}
},
listShow () {
// 如果总数量为0, 直接不显示
if(this.totalCount===0) {
this.isShow = false
return false
}
if(this.isShow) {
this.$nextTick(() => {
// 实现BScroll的实例是一个单例
if(!this.scroll) {
this.scroll = new BScroll('.list-content', {
click: true
})
} else {
this.scroll.refresh() // 让滚动条刷新一下: 重新统计内容的高度
}
})
}
return this.isShow
}
},
methods: {
toggleShow () {
// 只有当总数量大于0时切换
if(this.totalCount>0) {
this.isShow = !this.isShow
}
},
clearCart () {
MessageBox.confirm('确定清空购物车吗?').then(action => {
this.$store.dispatch('clearCart')
}, () => {});
}
},
components: {
CartControl
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus" scoped>
@import "../../common/stylus/mixins.styl"
.shopcart
position fixed
left 0
bottom 0
z-index 50
width 100%
height 48px
.content
display flex
background #141d27
font-size 0
color rgba(255, 255, 255, 0.4)
.content-left
flex 1
.logo-wrapper
display inline-block
vertical-align top
position relative
top -10px
margin 0 12px
padding 6px
width 56px
height 56px
box-sizing border-box
border-radius 50%
background #141d27
.logo
width 100%
height 100%
border-radius 50%
text-align center
background #2b343c
&.highlight
background $green
.icon-shopping_cart
line-height 44px
font-size 24px
color #80858a
&.highlight
color #fff
.num
position absolute
top 0
right 0
width 24px
height 16px
line-height 16px
text-align center
border-radius 16px
font-size 9px
font-weight 700
color #ffffff
background rgb(240, 20, 20)
box-shadow 0 4px 8px 0 rgba(0, 0, 0, 0.4)
.price
display inline-block
vertical-align top
margin-top 5px
line-height 24px
padding-right 12px
box-sizing border-box
font-size 16px
font-weight 700
color #fff
&.highlight
color #fff
.desc
display inline-block
vertical-align bottom
margin-bottom 15px
margin-left -45px
font-size 10px
.content-right
flex 0 0 105px
width 105px
.pay
height 48px
line-height 48px
text-align center
font-size 12px
font-weight 700
color #fff
&.not-enough
background #2b333b
&.enough
background #00b43c
color #fff
.ball-container
.ball
position fixed
left 32px
bottom 22px
z-index 200
transition all 0.4s cubic-bezier(0.49, -0.29, 0.75, 0.41)
.inner
width 16px
height 16px
border-radius 50%
background $green
transition all 0.4s linear
.shopcart-list
position absolute
left 0
top 0
z-index -1
width 100%
transform translateY(-100%)
&.move-enter-active, &.move-leave-active
transition transform .3s
&.move-enter, &.move-leave-to
transform translateY(0)
.list-header
height 40px
line-height 40px
padding 0 18px
background #f3f5f7
border-bottom 1px solid rgba(7, 17, 27, 0.1)
.title
float left
font-size 14px
color rgb(7, 17, 27)
.empty
float right
font-size 12px
color rgb(0, 160, 220)
.list-content
padding 0 18px
max-height 217px
overflow hidden
background #fff
.food
position relative
padding 12px 0
box-sizing border-box
bottom-border-1px(rgba(7, 17, 27, 0.1))
.name
line-height 24px
font-size 14px
color rgb(7, 17, 27)
.price
position absolute
right 90px
bottom 12px
line-height 24px
font-size 14px
font-weight 700
color rgb(240, 20, 20)
.cartcontrol-wrapper
position absolute
right 0
bottom 6px
.list-mask
position fixed
top 0
left 0
width 100%
height 100%
z-index 40
backdrop-filter blur(10px)
opacity 1
background rgba(7, 17, 27, 0.6)
&.fade-enter-active, &.fade-leave-active
transition all 0.5s
&.fade-enter, &.fade-leave-to
opacity 0
background rgba(7, 17, 27, 0)
## </style>
##
## 对应的getters
/*
包含多个基于state的getter计算属性的对象
*/
export default {
totalCount (state) {
return state.cartFoods.reduce((preTotal, food) => preTotal + food.count , 0)
},
totalPrice (state) {
return state.cartFoods.reduce((preTotal, food) => preTotal + food.count*food.price , 0)
},
positiveSize (state) {
return state.ratings.reduce((preTotal, rating) => preTotal + (rating.rateType===0?1:0) , 0)
}
}
**action.js**
// 同步更新food中的count值
updateFoodCount({commit}, {isAdd, food}) {
if (isAdd) {
commit(INCREMENT_FOOD_COUNT, {food})
} else {
commit(DECREMENT_FOOD_COUNT, {food})
}
},
// 同步清空购物车
clearCart({commit}) {
commit(CLEAR_CART)
},
** mutations.js**
[CLEAR_CART](state) {
// 清除food中的count
state.cartFoods.forEach(food => food.count = 0)
// 移除购物车中所有购物项
state.cartFoods = []
},
** state.js**
export default {
cartFoods: [], // 购物车中食物的列表
}
评价和分类组件
<template>
<div class="ratings" ref="ratings">
<div class="ratings-content">
<div class="overview">
<div class="overview-left">
<h1 class="score">{{info.score}}</h1>
<div class="title">综合评分</div>
<div class="rank">高于周边商家99%</div>
</div>
<div class="overview-right">
<div class="score-wrapper">
<span class="title">服务态度</span>
<Star :score="info.serviceScore" :size="36" />
<span class="score">{{info.serviceScore}}</span>
</div>
<div class="score-wrapper">
<span class="title">商品评分</span>
<Star :score="info.foodScore" :size="36" />
<span class="score">{{info.foodScore}}</span>
</div>
<div class="delivery-wrapper">
<span class="title">送达时间</span>
<span class="delivery">{{info.deliveryTime}}分钟</span>
</div>
</div>
</div>
<div class="split"></div>
<div class="ratingselect">
<div class="rating-type border-1px">
<span class="block positive" :class="{active: selectType===2}" @click="setSelectType(2)">
全部<span class="count">{{ratings.length}}</span>
</span>
<span class="block positive" :class="{active: selectType===0}" @click="setSelectType(0)">
满意<span class="count">{{positiveSize}}</span>
</span>
<span class="block negative" :class="{active: selectType===1}" @click="setSelectType(1)">
不满意<span class="count">{{ratings.length-positiveSize}}</span>
</span>
</div>
<div class="switch" :class="{on: onlyShowText}" @click="toggleOnlyShowText">
<span class="iconfont icon-check_circle"></span>
<span class="text">只看有内容的评价</span>
</div>
</div>
<div class="rating-wrapper">
<ul>
<li class="rating-item" v-for="(rating, index) in filterRatings" :key="index">
<div class="avatar">
<img width="28" height="28" :src="rating.avatar">
</div>
<div class="content">
<h1 class="name">{{rating.username}}</h1>
<div class="star-wrapper">
<Star :score="rating.score" :size="24" />
<span class="delivery">{{rating.deliveryTime}}</span>
</div>
<p class="text">{{rating.text}}</p>
<div class="recommend">
<span class="iconfont" :class="rating.rateType===0 ? 'icon-thumb_up' : 'icon-thumb_down'"></span>
<span class="item" v-for="(item, index) in rating.recommend" :key="index">{{item}}</span>
</div>
<div class="time">{{rating.rateTime | date-format}}</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
import BScroll from 'better-scroll'
import {mapState, mapGetters} from 'vuex'
import Star from '../../../components/Star/Star.vue'
export default {
data () {
return {
onlyShowText: true, // 是否只显示有文本的
selectType: 2 , // 选择的评价类型: 0满意, 1不满意, 2全部
}
},
mounted () {
this.$store.dispatch('getShopRatings', () => {
this.$nextTick(() => {
new BScroll(this.$refs.ratings, {
click: true
})
})
})
},
computed: {
...mapState(['info', 'ratings']),
...mapGetters(['positiveSize']),
filterRatings () {
// 得到相关的数据
const {ratings, onlyShowText, selectType} = this
// 产生一个过滤新数组
return ratings.filter(rating => {
const {rateType, text} = rating
/*
条件1:
selectType: 0/1/2
rateType: 0/1
selectType===2 || selectType===rateType
条件2
onlyShowText: true/false
text: 有值/没值
!onlyShowText || text.length>0
*/
return (selectType===2 || selectType===rateType) && (!onlyShowText || text.length>0)
})
}
},
methods: {
setSelectType (selectType) {
this.selectType = selectType
},
toggleOnlyShowText () {
this.onlyShowText = !this.onlyShowText
}
},
components: {
Star
},
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import "../../../common/stylus/mixins.styl"
.ratings
position: absolute
top: 195px
bottom: 0
left: 0
width: 100%
overflow: hidden
background: #fff
.overview
display: flex
padding: 18px 0
.overview-left
flex: 0 0 137px
padding: 6px 0
width: 137px
border-right: 1px solid rgba(7, 17, 27, 0.1)
text-align: center
@media only screen and (max-width: 320px)
flex: 0 0 120px
width: 120px
.score
margin-bottom: 6px
line-height: 28px
font-size: 24px
color: rgb(255, 153, 0)
.title
margin-bottom: 8px
line-height: 12px
font-size: 12px
color: rgb(7, 17, 27)
.rank
line-height: 10px
font-size: 10px
color: rgb(147, 153, 159)
.overview-right
flex: 1
padding: 6px 0 6px 24px
@media only screen and (max-width: 320px)
padding-left: 6px
.score-wrapper
margin-bottom: 8px
font-size: 0
.title
display: inline-block
line-height: 18px
vertical-align: top
font-size: 12px
color: rgb(7, 17, 27)
.star
display: inline-block
margin: 0 12px
vertical-align: top
.score
display: inline-block
line-height: 18px
vertical-align: top
font-size: 12px
color: rgb(255, 153, 0)
.delivery-wrapper
font-size: 0
.title
line-height: 18px
font-size: 12px
color: rgb(7, 17, 27)
.delivery
margin-left: 12px
font-size: 12px
color: rgb(147, 153, 159)
.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
.ratingselect
.rating-type
padding: 18px 0
margin: 0 18px
border-1px(rgba(7, 17, 27, 0.1))
font-size: 0
.block
display: inline-block
padding: 8px 12px
margin-right: 8px
line-height: 16px
border-radius: 1px
font-size: 12px
color: rgb(77, 85, 93)
background: rgba(77, 85, 93, 0.2)
&.active
background: $green
color: #fff
.count
margin-left: 2px
font-size: 8px
.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: $green
.icon-check_circle
display: inline-block
vertical-align: top
margin-right: 4px
font-size: 24px
.text
display: inline-block
vertical-align: top
font-size: 12px
.rating-wrapper
padding: 0 18px
.rating-item
display: flex
padding: 18px 0
bottom-border-1px(rgba(7, 17, 27, 0.1))
.avatar
flex: 0 0 28px
width: 28px
margin-right: 12px
img
border-radius: 50%
.content
position: relative
flex: 1
.name
margin-bottom: 4px
line-height: 12px
font-size: 10px
color: rgb(7, 17, 27)
.star-wrapper
margin-bottom: 6px
font-size: 0
.star
display: inline-block
margin-right: 6px
vertical-align: top
.delivery
display: inline-block
vertical-align: top
line-height: 12px
font-size: 10px
color: rgb(147, 153, 159)
.text
margin-bottom: 8px
line-height: 18px
color: rgb(7, 17, 27)
font-size: 12px
.recommend
line-height: 16px
font-size: 0
.icon-thumb_up, .icon-thumb_down, .item
display: inline-block
margin: 0 8px 4px 0
font-size: 9px
.icon-thumb_up
color: $yellow
.icon-thumb_down
color: rgb(147, 153, 159)
.item
padding: 0 6px
border: 1px solid rgba(7, 17, 27, 0.1)
border-radius: 1px
color: rgb(147, 153, 159)
background: #fff
.time
position: absolute
top: 0
right: 0
line-height: 12px
font-size: 10px
color: rgb(147, 153, 159)
</style>
getters.js
positiveSize (state) {
return state.ratings.reduce((preTotal, rating) => preTotal + (rating.rateType===0?1:0) , 0)
}
actions
// 异步获取商家评价列表
async getShopRatings({commit}, callback) {
const result = await reqShopRatings()
if (result.code === 0) {
const ratings = result.data
commit(RECEIVE_RATINGS, {ratings})
// 数据更新了, 通知一下组件
callback && callback()
}
},
mutations.js
[RECEIVE_RATINGS](state, {ratings}) {
state.ratings = ratings
},
state.js
ratings: [], // 商家评价列表
shopinfo组件以及在子组件下刷新数据没回来的处理
<template>
<div class="shop-info">
<div class="info-content">
<section class="section">
<h3 class="section-title">配送信息</h3>
<div class="delivery">
<div>
<span class="delivery-icon">{{info.description}}</span>
<span>由商家配送提供配送,约{{info.deliveryTime}}分钟送达,距离{{info.distance}}</span>
</div>
<div class="delivery-money">配送费¥{{info.deliveryPrice}}</div>
</div>
</section>
<div class="split"></div>
<section class="section">
<h3 class="section-title">活动与服务</h3>
<div class="activity">
<div class="activity-item" v-for="(support, index) in info.supports" :key="index"
:class="supportClasses[support.type]">
<span class="content-tag">
<span class="mini-tag">{{support.name}}</span>
</span>
<span class="activity-content">{{support.content}}</span>
</div>
</div>
</section>
<div class="split"></div>
<section class="section">
<h3 class="section-title">商家实景</h3>
<div class="pic-wrapper">
<ul class="pic-list" ref="picsUl">
<li class="pic-item" v-for="(pic, index) in info.pics" :key="index">
<img width="120" height="90" :src="pic"/>
</li>
</ul>
</div>
</section>
<div class="split"></div>
<section class="section">
<h3 class="section-title">商家信息</h3>
<ul class="detail">
<li><span class="bold">品类</span> <span>{{info.category}}</span></li>
<li><span class="bold">商家电话</span> <span>{{info.phone}}</span></li>
<li><span class="bold">地址</span> <span>{{info.address}}</span></li>
<li><span class="bold">营业时间</span> <span>{{info.workTime}}</span></li>
</ul>
</section>
</div>
</div>
</template>
<script>
import BScroll from 'better-scroll'
import {mapState} from 'vuex'
export default {
data () {
return {
supportClasses: ['activity-green', 'activity-red', 'activity-orange']
}
},
computed: {
...mapState(['info'])
},
mounted () {
// 如果数据还没有, 直接结束
if(!this.info.pics) {
return
}
// 数据有了, 可以创建BScroll对象形成滑动
this._initScroll()
},
methods: {
_initScroll () {
new BScroll('.shop-info')
// 动态计算ul的宽度
const ul = this.$refs.picsUl
const liWidth = 120
const space = 6
const count = this.info.pics.length
ul.style.width = (liWidth + space) * count -space + 'px'
new BScroll('.pic-wrapper', {
scrollX: true // 水平滑动
})
}
},
watch: {
info () {// 刷新流程--> 更新数据
this.$nextTick(() => {
this._initScroll()
})
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import "../../../common/stylus/mixins.styl"
.shop-info
position: absolute
top: 195px
bottom: 0
left: 0
width: 100%
background: #fff;
overflow: hidden
.section
padding 16px 14px 14px
font-size 16px
background-color #fff
color #666
border-bottom 1px solid #eee
position relative
.section-title
color #000
font-weight 700
line-height 16px
> .iconfont
float right
color #ccc
.delivery
margin-top 16px
font-size 13px
line-height 18px
.delivery-icon
width 55px
font-size 11px
margin-right 10px
display inline-block
text-align center
color #fff
background-color #0097ff
padding 1px 0
border-radius 4px
.delivery-money
margin-top 5px
.activity
margin-top 16px
.activity-item
margin-bottom 12px
display flex
font-size 13px
align-items center
&.activity-green
.content-tag
background-color rgb(112, 188, 70)
&.activity-red
.content-tag
background-color rgb(240, 115, 115)
&.activity-orange
.content-tag
background-color: rgb(241, 136, 79)
.content-tag
display inline-block
border-radius 2px
width 36px
height: 18px
margin-right 10px
color #fff
font-style normal
position relative
.mini-tag
position absolute
left 0
top 0
right -100%
bottom -100%
font-size 24px
transform scale(.5)
transform-origin 0 0
display flex
align-items center
justify-content center
.pic-wrapper
width: 100%
overflow: hidden
white-space: nowrap
margin-top 16px
.pic-list
font-size: 0
.pic-item
display: inline-block
margin-right: 6px
width: 120px
height: 90px
&:last-child
margin: 0
.detail
margin-bottom -16px
> li
display flex
justify-content space-between
align-items center
margin-right -10px
padding 16px 12px 16px 0
line-height 16px
bottom-border-1px(#ddd)
font-size 13px
> .bold
font-weight 700
color #333
&:last-child
border-none()
.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>
# search组件
<template>
<section class="search">
<HeaderTop title="搜索"/>
<form class="search_form" @submit.prevent="search">
<input type="search" placeholder="请输入商家名称" class="search_input" v-model="keyword">
<input type="submit" class="search_submit">
</form>
<section class="list" v-if="!noSearchShops">
<ul class="list_container">
<!--:to="'/shop?id='+item.id"-->
<router-link :to="{path:'/shop', query:{id:item.id}}" tag="li"
v-for="item in searchShops" :key="item.id" class="list_li">
<section class="item_left">
<img :src="imgBaseUrl + item.image_path" class="restaurant_img">
</section>
<section class="item_right">
<div class="item_right_text">
<p>
<span>{{item.name}}</span>
</p>
<p>月售 {{item.month_sales||item.recent_order_num}} 单</p>
<p>{{item.delivery_fee||item.float_minimum_order_amount}} 元起送 / 距离{{item.distance}}</p>
</div>
</section>
</router-link>
</ul>
</section>
<div class="search_none" v-else>很抱歉!无搜索结果</div>
</section>
</template>
<script>
import {mapState} from 'vuex'
import HeaderTop from '../../components/HeaderTop/HeaderTop.vue'
export default {
data () {
return {
keyword: '',
imgBaseUrl: 'http://cangdu.org:8001/img/',
noSearchShops: false
}
},
computed: {
...mapState(['searchShops'])
},
methods: {
search () {
// 得到搜索关键字
const keyword = this.keyword.trim()
// 进行搜索
if(keyword) {
this.$store.dispatch('searchShops', keyword)
}
}
},
watch: {
searchShops (value) {
if(!value.length) { // 没有数据
this.noSearchShops = true
} else {// 有数据
this.noSearchShops = false
}
}
},
components: {
HeaderTop
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus" scoped>
@import "../../common/stylus/mixins.styl"
.search
width 100%
height 100%
overflow hidden
.search_form
clearFix()
margin-top 45px
background-color #fff
padding 12px 8px
input
height 35px
padding 0 4px
border-radius 2px
font-weight bold
outline none
&.search_input
float left
width 79%
border 4px solid #f2f2f2
font-size 14px
color #333
background-color #f2f2f2
&.search_submit
float right
width 18%
border 4px solid #02a774
font-size 16px
color #fff
background-color #02a774
.list
.list_container
background-color: #fff;
.list_li
display: flex;
justify-content: center;
padding: 10px
border-bottom: 1px solid $bc;
.item_left
margin-right: 10px
.restaurant_img
width 50px
height 50px
display block
.item_right
font-size 12px
flex 1
.item_right_text
p
line-height 12px
margin-bottom 6px
&:last-child
margin-bottom 0
.search_none
margin: 0 auto
color: #333
background-color: #fff
text-align: center
margin-top: 0.125rem
</style>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。