目录
从Home跳转到search
优化typenav的数据请求
在home和search组件中的typenav组件都在mounted的时候请求了同一组数据,在切换路由的时候会反复重新申请,所以可以把请求的动作放在根目录(App.vue)的mounted里面,并且用props传递参数这样就只会请求一次,不能放在main.js里面,虽然他也是执行一次,但是只有组件身上才有$store属性
合并params和query参数
去search组件有两个方法:分类列表与搜索框,分类列表带过去的时query参数,搜索框带过去的时params参数, 传递参数之前做一个判断,如果从分类列表过去,看原本的params有没有参数,有参数就将这个参数一起带过去,从搜索框过去也是一样。
为什么要分别用query和params参数?因为这样就可以同时有两种方式的keyword
Home首页的开发
mock.js
mock数据(模拟后台数据) 用mockjs mock的数据不会和服务器进行通信。拦截ajax请求
使用步骤:
1、在src目录下创建mock文件
2、准备JSON数据,在mock文件中创建相应的JSON文件 一定要格式化,不能有空格
3、把mock数据需要的数据放在public文件夹中,因为public文件夹在打包的时候会原封不懂把资源打包到dist文件夹里面
4、创建mockServer.js开始mock虚拟数据源,通过mockjs模块实现
1)引入mock import Mork from ‘mockjs’
2)引入json数据(json数据格式和图片是默认对外暴露的)
3)Mock.mock(‘路径(这里是指接口路径)’.’{code:200,data:数据}’)
Mock.mock( rurl, template ):记录数据模板。当拦截到匹配
rurl
的 Ajax 请求时,将根据数据模板template
生成模拟数据,并作为响应数据返回。
5、mockServer.js文件在入口文件(main.js)中引入(至少执行一次,才能模拟数据)
补充:前端mock数据的方法
$nextTick解决swiper使用的问题(项目难点2)
最新版本可能会有问题,这里安装的是5版本,使用方法可以上官网查询
只引入样式的时候只要import路径就可以了
要new swiper 必须要先有样式结构,因为new swiper的时候要操作DOM
如果上面用了v-for并且从仓库获取数据,修改仓库中的数据在new swiper后面(因为ajax请求是异步的),所以new swiper的时候结构还没有生成,导致不能正常使用
- update:由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子,但是只要任何数据更新,都会重新执行
- 加一个定时器,使new swiper在仓库更新后执行
- watch加$nectTick:能够保证函数在由于watch监听的数据的变化所进行的DOM更新完毕之后执行
watch:监听已有数据的变化,监听组件实例身上的属性的属性值的变化,有对象的写法和函数的写法
watch中的immediate:true代表立即监听,不论数据有没有变化,上来就立即监听一次
对象写法里面hander(newValue,oldValue)函数,把new swiper放里面只能保证此时数据已经发生变化但是不能保证v-for执行完毕
$nextTick:在下次DOM更新循环结束之后执行延迟回调,在修改数据之后立即使用这个方法,获取更新后的DOM【$nextTick:能够保证DOM已经更新】
homeFloor组件开发
数据获取
获取homefloor数据的时候要在home组件里面dispatch因为有两个homefloor,要v-for遍历(v-for也能够在自定义标签当中),然后用使用props传递数据
组件间通信方式有那些?
1)props:用于父传子组件之间
2)自定义事件@on @emit 在父组件定义自定义事件在子组件标签上加@自定义事件,在子组件中用this.$emit.自定义事件触发
3)全局事件总线$bus 全能
4)pubsub-js vue中几乎不用 全能
5)插槽
6)vuex
轮播图
homeFloor中的轮播图:因为是在home组件中发的getfloor请求,所以在floor组件中已经有完整的数据传入,所以直接在mounted中new swiper
把轮播图拆分成一个组件,复用结构,以后在页面中的复用结构最好弄成全局组件
search模块开发
请求数据以及Vuex
1)Post方式请求,传递参数至少是个空对象 可以用params={},每传递参数的时候就默认是个空对象,有参数就用参数
//接口设置
export const reqSearch = ((params) => {
return requests({ url: '/list', method: 'post', data: params })
})
const actions = {
async getSearch(context, params = {}) {
let result = await reqSearch(params);
// console.log(result)
if (result.code == 200) {
context.commit('GETSEARCH', result.data)
}
}
};
2)Getters可以简化state中的数据,return的时候要注意,如果没有state中没有数据那么return的会是undefine会出现错误,所以最好||[]传递一个空数组
3)因为search中根据输入的参数不同要发不同的请求,所以请求不能放在mounted里面可以把请求封装成函数,在需要的时候进行调用
4)在组件挂载之前(就是在获取后台数据之前)修改传递的参数,可以在mounted,created,
beforemouted里面
5)合并对象:es6新增 用Object.assign(1,2,3) 会对比1与23之间的key如果23中有key与1一样就会替换掉1
beforeMount(){
Object.assign(this.searchInfo,this.$route.query,this.$route.params);
},
6)监听路由变化再次发送请求获取数据:
watch:{
$route:{
handler(newV,oldV){
Object.assign(this.searchInfo,this.$route.query,this.$route.params)
this.getSearch();
//因为这些可能不会被覆盖掉
this.searchInfo.category1Id='';
this.searchInfo.category2Id='';
this.searchInfo.category3Id='';
}
}
}
面包屑
面包屑有四个种类
前三个用v-if或者v-show,有则显示无则隐藏,最后一个用v-for因为可能不止一个
一、全部商品分类中的标签(query参数)
点击面包屑上的删除按键的时候,要将对应的标签对应的参数设置为undfined,注意对应的id也要置为undfined【设置成undefined的属性不会被传给服务器,能够节省内存,设置成空一样会被传】
removeCategoryName(){
//设置成undefine就不会被传过去,能够节省内存
this.searchInfo.category1Id=undefined;
this.searchInfo.category2Id=undefined;
this.searchInfo.category3Id=undefined;
this.searchInfo.categoryName=undefined;
//地址栏也需要修改
this.$router.push({name:'search',params:this.$route.params});
},
地址路径也需要修改,进行路由跳转,因为标签对应的是query参数,此时query参数已经没有了,所以只要携带params参数就行。
二、搜索框输入的关键字(params参数)
点击删除面包屑,除了清除关键字,还要清除搜索栏上的字,利用全局事件总线清除
removeKeyword(){
//设置成undefine就不会被传过去,能够节省内存
this.searchInfo.keyword=undefined;
this.$bus.$emit('clear');
//地址栏也需要修改
this.$router.push({name:'search',query:this.$route.query});
}
配置全局事件总线:
在main中的beforecreate中写Vue.prototype.$bus=this
然后在需要被修改的组件里面绑定this.$bus.$on(‘事件名’,()=>{}),mounted里面,在触发的组件里面写this.$bus.$emit(‘事件名)
三、品牌名称
添加面包屑:
在search的子组件searchselector里面,利用自定义事件来通信
在子组件里面触发事件this.$emit
//子组件
trademarkInfo(trademark){
this.$emit('trademarkInfo',trademark);
}
父组件在子组件的标签上绑定自定义事件
//父组件
<SearchSelector @trademarkInfo="trademarkInfo"/>
父组件中修改参数
trademarkInfo(trademark){
this.searchInfo.trademark=`${trademark.tmId}:${trademark.tmName}`;
},
展示面包屑
<li class="with-x" v-if="searchInfo.trademark" >{{searchInfo.trademark.split(':')[1]}}<i @click="removeTrand">×</i></li>
点击删除该面包屑,清空品牌名
removeTrand(){
this.searchInfo.trademark=undefined;
this.getSearch()
}
四、平台售卖属性
添加与删除方法和三差不多,但是因为是数组,不是单个值,所以要根据索引值从数组中删除相应的属性值
removeattr(index){
this.searchInfo.props.splice(index,1);
this.getSearch()
}
排序
searchInfo中代表排序的属性为"order": "1:desc",1代表综合,2代表价格,desc代表降序,asc代表升序。
对应地设置了四个计算属性来代表这四个值,再根据这四个值来判断是否显示类,以及箭头是否显示,指示的是上还是下(动态类)
<span v-show="isOne" class="iconfont" :class="{'icon-download':isDown,'icon-upload':isUp}"></span>
Iconfont使用:上网站将需要的图标加入购物车,下载或复制在线链接,在public的index.html里面link引入,记得加协议,然后在要使用的地方添加类名iconfont和类名(图标名)
当点击价格或综合标签的时候进行的一些逻辑:
首先获取原来是排序状态,flag代表是点击了价格还是综合,原来的标签和现在的标签一样就不变标签,但是修改箭头的方向,如果变了就改变标签并且为降序,最后再次请求数据
changeActiveOne(flag){
let orginFlag=this.searchInfo.order.split(':')[0];
let orginDeriction=this.searchInfo.order.split(':')[1];
if(flag==orginFlag){
this.searchInfo.order=`${flag}:${orginDeriction=="desc"?"asc":"desc"}`;
}else{
this.searchInfo.order=`${flag}:desc`;
}
this.getSearch()
}
分页器(自己封装的单独组件,还有轮播图组件)
因为数据很多,所以展示的时候会用到分页器
分页器需要的数据
pageNow:当前是第几页
pageSize:每一页展示多少数据
totalPages:一共有多少页
continus:连续的页码数,即分页器中显示的页码,其他部分用...隐藏,一般是5或者7,因为这样的话会对称
这些数据都通过props从父组件传到分页器子组件
<Pagination :pageNow=searchInfo.pageNo :pageSize=searchInfo.pageSize :total=total :continues="5" @changePage="changePage"/>
分页器起始与结束数字计算
通过计算属性来计算要展示的页码区间
首先计算出要显示当前页的前后几页before,after
分为几种情况:
1)如果totalPages小于continues,那么start=1,end=totalPages
2)如果当前页的前before页小于1,那么start=1,end=continues
3)如果当前页的后after页大于totalPages,那么start=totalPages-continues+1,end=totalPages
4)其余情况下start=pageNow-before,end=pageNow+after
5)将start和after之间的数组成数组,并返回
showPages(){
const {continues,totalPages,pageNow}=this;
let start=0,end=0;
let before=Math.floor(continues/2);
let after=continues-Math.floor(continues/2)-1;
let showArr=[];
if(continues>totalPages){
start=1;
end=totalPages;
}else if(pageNow-before<1){
start=1;
end=continues;
}else if(pageNow+after>totalPages){
start=totalPages-continues+1;
end=totalPages;
}else{ start=pageNow-before;
end=pageNow+after;}
for(let i=start;i<=end;i++){
showArr.push(i)
}
return {showArr,end,start}
},
分页器动态展示
v-for可以遍历数字
标签分为八个button:上一页、页码1、【...】、中间页码、【...】、最终页码、下一页、共有几页
标签的显示与隐藏:
页码1:当start>1的时候显示
【...】:当start>2的时候显示
【...】:当end<totalPages-1的时候显示
最终页码:当end<totalPages的时候显示
动态添加类名
当前页为标签名的时候动态添加active类名
并且当页码为1的时候禁用上一页,页码为totalPages的时候禁用下一页
将子组件中选择的页码传回父组件
用自定义事件,当修改页码的时候$emit触发修改当前页码
<div class="pagination">
<button @click="$emit('changePage',pageNow-1)" :disabled="pageNow==1">上一页</button>
<button :class="{active:pageNow==1}" v-show="showPages.start>1" @click="$emit('changePage',1)" >1</button>
<button v-show="showPages.start>2">...</button>
<button :class="{active:pageNow==p}" v-for="(p,index) in showPages.showArr" :key="index" @click="$emit('changePage',p)">{{p}}</button>
<button v-show="showPages.end<totalPages-1">···</button>
<button :class="{active:pageNow==totalPages}" v-show="showPages.end<totalPages" @click="$emit('changePage',totalPages)">{{totalPages}}</button>
<button @click="$emit('changePage',pageNow+1)" :disabled="pageNow==totalPages">下一页</button>
<button style="margin-left: 30px">共{{total}}条</button>
</div>
//父组件中的方法
changePage(page){
this.searchInfo.pageNo=page;
this.getSearch()
}