项目地址
一、ShopRatings组件
1、计算满意度
<div class="rating-type border-1px">
<span class="block positive " :class="{active:selectType==2 }">
全部
<span class="count">{{ratings.length}}</span>
</span>
<span class="block positive" :class="{active:selectType==0 }">
满意
<span class="count">{{positiveSize}}</span>
</span>
<span class="block negative" :class="{active:selectType==1 }">
不满意
<span class="count">{{ratings.length - positiveSize}}</span>
</span>
</div>
<script>
positiveSize(state) {
return state.ratings.reduce((preTotal, rating) => preTotal + (rating.rateType===0?1:0) ,0)
}
data() {
return {
selectType: 2
}
},
computed: {
...mapGetters(['positiveSize']),
}
</script>
2、列表的过滤显示
- 查看全部/满意/不满意的评价 && 是否只有内容的评价
- 通过点击按钮改变当前显示状态,设计过滤新数组,判断符合条件的评价显示在页面上
<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"></span>
<span class="text">只看有内容的评价</span>
</div>
</div>
<script>
data() {
return {
onlyShowText: true,
selectType: 2
};
},
computed: {
...mapState(["info", "ratings"]),
fillterRatings() {
const { ratings, onlyShowText, selectType } = this;
return ratings.filter(rating => {
const {rateType, text} = rating
return (selectType===2 || selectType===rateType) && (!onlyShowText || text.length>0)
})
}
},
methods: {
setSelectType(selectType) {
this.selectType = selectType;
},
toggleOnlyShowText () {
this.onlyShowText = !this.onlyShowText
}
},
</script>
二、ShopInfo组件
- 实现上下左右滑动,动态计算ul总宽度
- 总宽度=(li宽度+边距宽度)*图片数量-最后一个边距宽度
- 通过watch监视数据更新,数据刷新之后创建滑动对象,
<section class="section">
<h3 class="section-title">活动与服务</h3>
<div class="activity">
<div
class="activity-item"
:class="supportClasses[support.type]"
v-for="(support, index) in info.supports"
:key="index"
>
<span class="content-tag">
<span class="mini-tag">{{support.name}}</span>
</span>
<span class="activity-content">{{support.content}}</span>
</div>
</div>
</section>
<section class="section">
<h3 class="section-title">商家实景</h3>
<div class="pic-wrapper">
<ul class="pic-list" ref="picUl">
<li class="pic-item" v-for="(pic, index) in info.pics" :key="index">
<img width="120" height="90" :src="pic">
</li>
</ul>
</div>
</section>
<script>
data() {
return {
supportClasses: ["activity-green", "activity-red", "activity-orange"]
};
},
methods: {
_initScroll() {
new BSscroll(".shop-info");
const ul = this.$refs.picUl;
const liWidth = 120;
const space = 6;
const count = this.info.pics.length;
ul.style.width = (liWidth + space) * count - space + "px";
new BSscroll(".pic-wrapper", {
scrollX: true
});
}
},
watch: {
info() {
this.$nextTick(() => {
this._initScroll();
});
}
},
mounted() {
if (!this.info.pics) {
return;
}
this._initScroll();
}
</script>
三、Search组件
- 根据关键字异步搜索显示匹配的商家列表
- 实现如果没有搜索结果的提示显示,v-if,v-else进行判断
- router-link 通过tag属性改变显示的标签
<template>
<section class="search">
<HeaderTop title="搜索"/>
<form class="search_form" @submit.prevent="search">
<input
type="search"
name="search"
placeholder="请输入商家或美食名称"
class="search_input"
v-model="keyword"
>
<input type="submit" name="submit" class="search_submit">
</form>
<section class="list" v-if="!noSearchShops">
<ul class="list_container">
<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'])
},
watch: {
searchShops (value) {
if(!value.length) {
this.noSearchShops = true
} else {
this.noSearchShops = false
}
}
},
methods: {
search() {
const keyword = this.keyword.trim()
if (keyword) {
this.$store.dispatch('searchShops',keyword)
}
}
},
components: {
HeaderTop
}
};
</script>
四、项目优化
1、缓存路由组件对象
- 通过keep-alive标签,缓存商家信息路由组件中的数据
<template>
<div>
<ShopHeader></ShopHeader>
<div class="tab">
<div class="tab-item">
<router-link to="/shop/goods" replace>点餐</router-link>
</div>
<div class="tab-item">
<router-link to="/shop/ratings" replace>评价</router-link>
</div>
<div class="tab-item">
<router-link to="/shop/info" replace>商家</router-link>
</div>
</div>
<keep-alive>
<router-view/>
</keep-alive>
</div>
</template>
2、路由组件懒加载
- 使用函数的形式调用组件,实现进入组件时才进行加载,没有进入不加载
- 返回路由组件的函数, 只有执行此函数才会加载路由组件, 这个函数在请求对应的路由路径时才会执行
const Msite = () => import('../pages/Msite/Msite.vue')
const Search = () => import('../pages/Search/Search.vue')
const Order = () => import('../pages/Order/Order.vue')
const Profile = () => import('../pages/Profile/Profile.vue')
3、图片懒加载
- 第一种:引入vue-lazyload插件
import loading from './common/imgs/loading.gif'
import VueLazyload from 'vue-lazyload'
Vue.use(VueLazyload, {
loading
})
<img v-lazy="img">
- 调用vant封装的图片懒加载模块
import loading from './common/imgs/loading.gif'
import { Lazyload } from 'vant';
Vue.use(Lazyload, {
loading
});
<img v-lazy="img">
4、分析项目打包并优化
- 使用
npm run build --report
生成可视化页面,查看加载包的大小,对其进行优化
- 日期过滤器
- 使用moment插件格式化日期格式,在build之后发现插件使用少,占用内存多,换用date-fns插件对项目进行优化
import Vue from 'vue'
import format from 'date-fns/format'
Vue.filter('date-format', function (value, formatStr='YYYY-MM-DD HH:mm:ss') {
if (!value) return ''
return format(value, formatStr)
})
<div class="time">{{rating.rateTime | date-format}}</div>