Vue2项目中的商品分类展示与无限滚动(按需加载)操作

591 阅读2分钟

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组件):

20221231_151016Edit 00_00_00-00_00_30.gif

在每个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仓库中,添加重置列表、设置商品总数的操作,并在ALLGOODLISTGOODLISTBYCATE两个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);
            }
        },

效果如下:

20221231_151614Edit 00_00_00-00_00_30.gif

当然,也可以在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()
            }  
        }
    })
}