1.商品分类展示
注:此项目和尚品汇有所不同,所以这个略,不做三级联动数据展示与隐藏、背景颜色相关操作。
尚品汇的三级联动通过编程式导航+事件委派,在a标签中加上自定义属性与其他子节点进行区分,加三级,每一级都加不同的。
获取event.target,对其进行解构,取出自定义属性来判断。
详情查看其源码。
我们只有二级分类,每个一级分类对应sub-menu的一级项,二级分类对应每个menu下的menu-item。
商品展示区结构如下:
// GoodsCate.vue
<template>
<!-- 商品展示部分 -->
<div class="goods_cate_box">
<!-- 商品分类导航 -->
<div class="cate_list">
...
</div>
<!-- 商品展示栏 -->
<div v-if="this.realGoodsList.length" class="goos_list">
<div class="good_box" v-for="item in this.realGoodsList" :key="item.id">
<!-- replace属性进行清除缓存 -->
<router-link :to="{name:'detail',params:{id:item.id}}" style="text-decoration: none;" replace>
<!-- 商品卡片组件 -->
<GoodCard :goodInfo="item"></GoodCard>
</router-link>
</div>
</div>
<div v-else style="height: 400px;">
<a-empty style="margin-top: 180px;"/>
</div>
</div>
</template>
在GoodsCate.vue中,一挂载就对分类、商品数据进行派发,此处有通过mapGetter对数据进行处理
...
data(){
return{
pageData:{
// 目前展示的分类id,id=0为默认(全部)
cateId:0,
// 页数
page:1,
// 每页条数
limit:10,
}
}
},
computed:{
...mapState({
// 右侧需要的是一个函数,当使用这个计算属性的时候,右侧函数会立即执行一次
// 注入一个参数state,其实即为大仓库中的数据
categoryList:state => state.square.categoryList,
goodsList:state => state.square.goodsList,
total:state => state.square.total
}),
},
methods:{
toDetail(id){
// resolve返回路由地址的标准化版本。还包括一个包含任何现有 base 的 href 属性。
let routeData = this.$router.resolve({
name:'detail',
params:{
id:id
}
});
window.open(routeData.href,'_blank');
}
},
mounted(){
this.$store.dispatch('categoryList')
// 挂载的时候先不分类,展示所有商品
this.$store.dispatch('allGoodsList')
}
先看到分类导航(Category.vue组件):
在每个item上绑定对应的点击事件,通过控制对应仓库中的数据,进而来展示对应分的商品
methods:{
// 获取所有分类
getAllCate(){
this.$store.dispatch('allGoodsList')
},
// 获取子分类
getSubCate(cateId){
this.$store.dispatch('GoodsListByCate',cateId)
}
},
仓库中,square.js:
// square首页信息小仓库
import { reqCategoryList, reqAllGoodsList, reqGoodsByCate } from '@/api/square'
// state只能由mutations修改,actions获取到的数据用commit提交给mutations
// dispatch是异步操作,用actions接收,actions出来的是promise,需要async await处理
// Category、GoodsCate相关组件需要用到这个的数据,需要挂载
const state = {
// state中默认初始数据别瞎写,这个是根据接口的返回值去初始化
categoryList: [],
// 目前展示的商品列表,默认的时候是展示所有商品列表,当点击分类是,就切换展示该分类下的商品
// 页面展示的最终是goodList,下面两个只是缓存
goodsList: [],
// 所有商品列表
allGoodsList: [],
// 某分类的商品列表
goodListByCate: [],
}
const mutations = {
CATEGORYLIST(state, categoryList) {
...
},
ALLGOODLIST(state, allGoodsList) {
state.allGoodsList = allGoodsList
state.goodsList = state.allGoodsList
},
GOODLISTBYCATE(state, goodListByCate) {
state.goodListByCate = goodListByCate
state.goodsList = state.goodListByCate // 将state中用来展示的list切换成分类的list
}
}
const actions = {
// 通过API里面的接口函数调用,向服务器发请求,获取服务器的数据
async categoryList({ commit }) {
...
},
// 获取所有商品列表
async allGoodsList({ commit }, pathData) {
const res = await reqAllGoodsList(pathData);
if (res.code === 20000) { // 请求成功则提交数据
commit('ALLGOODLIST', res.data.records)
return 'ok'
} else {
return Promise.reject(new Error('获取商品列表失败!'))
}
},
// 获取商品分类
async GoodsListByCate({ commit }, cateId) {
...
}
}
const getters = {
...
}
export default {
state,
mutations,
actions,
getters
}
2.无限滚动(按需加载)操作
最终还是为了优化性能,防止一次请求数据过多而造成页面卡顿,所以进行按需加载。
主要的两个接口,写在对应的api仓库中square.js:
/**
* 获取所有商品数据的接口
* @param {page,limit} page 每次传输的页数 limit 每页的商品数目
* @returns
*/
export const reqAllGoodsList = (page = 1, limit = 10) => requests({ url: `/goods/list/${page}/${limit}`, method: 'GET' });
/**
*
* @param {*} cid 分类id
* @param {page,limit} page 每次传输的页数 limit 每页的商品数目
* @returns
*/
export const reqGoodsByCate = (cid, { page = 1, limit = 10 } = {}) => requests({ url: `/goods/list/${cid}/${page}/${limit}`, method: 'GET' });
square.js仓库中,添加重置列表、设置商品总数的操作,并在ALLGOODLIST、GOODLISTBYCATE两个mutations中,改用push操作,将后面请求得到的数据添加进商品列表:
// square首页信息小仓库
import { reqCategoryList, reqAllGoodsList, reqGoodsDetail, reqGoodsByCate } from '@/api/square'
const state = {
// state中默认初始数据别瞎写,这个是根据接口的返回值去初始化
categoryList: [],
// 目前展示的商品列表,默认的时候是展示所有商品列表,当点击分类是,就切换展示该分类下的商品
goodsList: [],
// 商品总数
total: 0
}
const mutations = {
...
ALLGOODLIST(state, allGoodsList) {
state.goodsList.push(...allGoodsList);
},
GOODLISTBYCATE(state, goodListByCate) {
state.goodsList.push(...goodListByCate);
},
SETTOTAL(state, total) {
state.total = total;
},
// 重置列表
INIT(state) {
state.total = 0;
state.goodsList = [];
state.allGoodsList = [];
state.goodListByCate = [];
}
}
const actions = {
...
// 重置列表
init_square({ commit }) {
commit('INIT');
return 'ok';
}
}
const getters = {
}
export default {
state,
mutations,
actions,
getters
}
然后在对应的GoodsCate.vue中,挂载的时候,先派发action清除仓库中的商品列表,再请求商品列表数据:
mounted(){
this.$store.dispatch('categoryList');
// 初始化商品列表
this.$store.dispatch('init_square');
// 挂载的时候先不分类,展示所有商品
this.$store.dispatch('allGoodsList',this.pageData);
}
记得修改获取所有分类和子分类的方法,在每次点击的时候,都对列表进行清空(初始化):
// 获取所有分类
getAllCate(){
// 切换回所有分类就将page,cateId初始化
this.pageData.page = 1;
this.pageData.cateId = 0;
this.$store.dispatch('init_square');
this.$store.dispatch('allGoodsList',this.pageData);
},
// 获取子分类
getSubCate(cateId){
// 切换到子分类分类就将page初始化
this.pageData.page = 1;
this.pageData.cateId = cateId
this.$store.dispatch('init_square');
this.$store.dispatch('GoodsListByCate',this.pageData);
}
这里是搞了个加载更多的按钮,每次点击时就继续加载数据。
按钮绑定的方法:
// 加载更多
loadingMore(){
if(this.pageData.page * this.pageData.limit >= this.total){
this.$message.warning('到底了~');
return;
}
this.pageData.page = this.pageData.page + 1;
if(this.pageData.cateId !== 0){
// 注意只能带一个参数过去,所以得包装成对象
this.$store.dispatch('GoodsListByCate',this.pageData);
}else{
this.$store.dispatch('allGoodsList',this.pageData);
}
},
效果如下:
当然,也可以在created中,通过window对象监视滚动条,判断是否触底,触底就自动调用加载更多的方法,效果也是一样。
created(){
let _this = this; // 注意保存this
this.$nextTick(() => {
window.onscroll = function() {
//scrollTop是滚动条滚动时,距离顶部的距离,也就是被滚出可视区的高度
let scrollTop = document.documentElement.scrollTop;
//windowHeight是可视区的高度
let windowHeight = document.documentElement.clientHeight;
//scrollHeight是滚动条的总高度
let scrollHeight = document.documentElement.scrollHeight;
//滚动条到底部的条件
if(scrollTop + windowHeight === scrollHeight){
// 自动调用加载更多
_this.loadingMore()
}
}
})
}