> 文章列表 > uni-app--》如何实现网上购物小程序(中下)?

uni-app--》如何实现网上购物小程序(中下)?

uni-app--》如何实现网上购物小程序(中下)?

🏍️作者简介:大家好,我是亦世凡华、渴望知识储备自己的一名在校大学生

🛵个人主页:亦世凡华、

🛺系列专栏:uni-app

🚲座右铭:人生亦可燃烧,亦可腐败,我愿燃烧,耗尽所有光芒。

👀引言

        ⚓经过web前端的学习,相信大家对于前端开发有了一定深入的了解,今天我开设了uni-app专栏,主要想从移动端开发方向进一步发展,而对于我来说写移动端博文的第二站就是uni-app开发,希望看到我文章的朋友能对你有所帮助。

目录

商品列表界面

实现上拉加载和下拉刷新

商品详情界面

实现轮播图区域

实现商品信息区域

渲染商品导航区域

实现加入购物车

购物车界面

实现收货地址区域

实现结算区域


商品列表界面

接下来实现商品列表界面,展现的是商品的图片信息和价格,在分包的商品列表中进行展示,通过在主页和分类点击的商品图片传递的参数,决定商品要展现的内容,具体实现过程如下:

通过调用接口,将获取到的数据转存到data中进行保存:

将获取到的数据进行v-for遍历,渲染到页面上如下:

给出如下样式:

如果想设置价格存在小数点,可以通过以下方式:

当然如果后期如果写很多类似这种界面的话,可以将item单独抽离出组件, 将单独抽离的组件存放到components组件当中去,后期如何想调用这种组件进行页面渲染的话传递数据即可,如下:

<template><view class="goods-item"><!-- 左侧的盒子 --><view class="goods-item-left"><image :src="item.goods_small_logo || defaultPic" class="goods-pic"></image></view><!-- 右侧的盒子 --><view class="goods-item-right"><!-- 商品名称 --><view class="goods-name">{{item.goods_name}}</view><!-- 商品价格 --><view class="goods-info-box"><view class="goods-price">¥{{item.goods_price}}</view></view></view></view>
</template><script>export default {name:"my-goods",props:{item:{type:Object,default:{}}},data() {return {// 默认的空图片defaultPic: 'https://img3.doubanio.com/f/movie/8dd0c794499fe925ae2ae89ee30cd225750457b4/pics/movie/celebrity-default-medium.png',};}}
</script><style lang="scss">
.goods-item{display: flex;padding: 10px 5px;border-bottom: 1px solid #f0f0f0;.goods-item-left{margin-right: 5px;.goods-pic{width: 100px;height: 100px;display: block;}}.goods-item-right{display: flex;flex-direction: column;justify-content: space-between;.goods-name{font-size: 13px;}.goods-info-box{.goods-price{color: #c00000;font-size: 16px;}}}
}
</style>

自定义好my-goods组件之后,我们可以在goods-list组件中调用我们自定义的组件,通过props传递参数即可,如下:

实现上拉加载和下拉刷新

接下来实现商品列表的上拉加载更多的功能,当页面向下滑动时触发上拉触底函数,关系page页数,具体实现如下:

在项目的pages.json文件中,设置如下的上拉触底的距离。

接下来调用页面上拉触底函数进行页码值+1操作,并在调用接口赋值data数据时,将原本数据和现在获取到的数据进行一个数组的拼接,如下:

因为在进行上来触底的时候如果频繁的进行上拉下拉会导致频繁的请求页码值加+1,所以要设置一个节流阀,在下一条数据加载完成后,才去调用上拉触底函数,现在好像这个bug修复了,我目前是不需要设置节流阀也能实现效果,但是如果遇到老项目还是需要设置节流阀的,这里简单提一下设置节流阀的过程思路,如下:

判断数据是否加载完成,如果数据渲染完成,再次触发上拉触底函数时,就让page不再自增加1,如果下面的公式成立,则证明没有下一页数据了,如下:

当前的页码值 * 每页显示多少条数据 >= 总数条数
pagenum * pagesize >= total

修改上拉触底函数如下:

接下来实现上拉刷新的效果,首先进行如下配置:

设置下拉刷新函数,需要重置数据如下:

接下来通过传递的参数设置调用阻止下拉刷新的函数,如下:

接下来给goods_list设置点击事件,进行路由跳转到详情页界面,如下:

商品详情界面

接下来实现商品详情,通过点击页面列表的商品数据从而跳转到商品详情界面,并传递相关query参数来标识当前商品详情页面代表的是哪个商品数据,所以接下来通过调用get接口来获取到当前商品的相关数据,如下:

实现轮播图区域

接下来实现轮播图区域的内容,将我们get接口获取到的相关图片数据进行轮播图的实现,如下:

实现商品信息区域

接下来实现商品的信息区域,定义如下的商品文字信息结构:

// 商品文字信息区域
.goods-info-box{padding: 10px;padding-right: 0;// 商品价格.price{color: #c00000;font-size: 18px;margin: 10px 0;}// 商品信息主体区域.goods-info-body{display: flex;justify-content: space-between;// 商品名字.goods_name{font-size: 13px;margin-right: 10px;}// 收藏.favi{width: 120px;font-size: 12px;display: flex;flex-direction: column;align-items: center;justify-content: center;border-left: 1px solid #efefef;color: gray;}}// 运费区域.yf{font-size: 12px;color: gray;margin: 10px 0;}
}

接下来实现商品的图文区域,使用uni-app的API来识别后端传递过来的html标签,如下:

渲染商品导航区域

接下来实现渲染商品的导航区域,这里使用的uni-app的一个插件需要自行下载,如下:

使用官方提供给我们的一个基础案例,复制到项目代码中,得到如下界面

接下来给其设置固定定位,使其固定在最下方:

因为是固定定位,会覆盖掉页面的一部分内容,所以我们要设置内容的下内边距为导航区域的高度

给导航区域设置点击事件,当点击购物车时,跳转到购物车界面,如下:

实现加入购物车

接下来实现商品详情界面将商品添加到购物车的功能,因为有页面有好多都需要该数据,所以可以设置将数据存放到store仓库里面,那么这里的话就需要使用vuex来进行集中式状态管理了,具体的实现过程如下:

首先先创建一个新的仓库,作为存放整合各个数据和方法的容器:

import Vue from 'vue'
import Vuex from 'vuex'
import muduleCart from '@/store/cart.js'Vue.use(Vuex)const store = new Vuex.Store({// 挂载 store 模块modules:{'m_cart':muduleCart}
})export default store

将创建好的容器在main.js入口文件上进行挂载到Vue实例上,如下:

在store文件夹下新建一个cart.js模块,封装方法来实现加入购物车的功能,代码如下:

export default {namespaced:true,state:()=>({// 购物车的数组,用来存放购物车中每个商品的信息对象cart:[]}),mutations:{addToCart(state,goods){// 根据提交的商品的Id,查询购物车中是否存在这件商品// 如果不存在,则 findResult 为 undefined;否则,为查找到的商品信息对象const findResult = state.cart.find(x=>x.goods_id === goods.goods_id)if(!findResult){// 如果购物车中没有这件商品,则直接 pushstate.cart.push(goods)}else{// 如果购物车中有这件商品,则只更新数量即可findResult.goods_count++}}},getters:{// 统计购物车中商品的总数量total(state){let c = 0state.cart.forEach(x => c += x.goods_count)return c}}
}

给加入购物车按钮设置点击事件,如下:

通过以下方法调用store中的数据

虽然我们点击加入购物车按钮,购物车会出现商品数量,但是我们一旦刷新编译器的话数据就会消失,这里需要我们设置一下本地存储,如下:

开启watch深度监听,一旦数据发生变化则立即调用

接下来处理购物车数字徽标的问题,当点击购物车时跳转到购物车页面,在购物车页面的tabBar处会出现你选中商品数量的一个徽标,因为这个徽标不管你从哪个页面点击进入,购物车选中的数量是不会改变的,这里的话可以将设置徽标的功能设置为mixins混入文件,如下:

购物车界面

接下来实现购物车界面,将vuex保存的状态数据来渲染到购物车界面,如下:

<template><view><!-- 商品列表的标题区域 --><view class="cart-title"><!-- 左侧的图标 --><uni-icons type="shop" size="18"></uni-icons><!-- 右侧的文本 --><text class="cart-title-text">购物车</text></view><!-- 循环渲染购物车中的商品信息 --><block v-for="(item,index) in cart" :key="index"><my-goods :item="item"></my-goods></block></view>
</template><script>import badgeMix from '@/mixins/tabbar-badge.js'import { mapState } from 'vuex'export default {computed:{...mapState('m_cart',['cart'])},mixins:[badgeMix],data() {return {};},}
</script><style lang="scss">
.cart-title{height: 40px;display: flex;align-items: center;padding-left: 5px;border-bottom: 1px solid #EFEFEF;.cart-title-text{font-size: 14px;margin-left: 10px;}
}
</style>

完成购物车商品列表的展示之后,我们需要在购物车界面单独渲染出一个单选框按钮,这里的话可以通过props传参来控制只有购物车界面才出现单选框按钮,实现过程如下:

界面实现如下:

接下来实现将单选框勾选的商品id和勾选状态进行vuex状态管理并存储在本地当中,实现过程如下

父组件调用子组件的方法,并拿到当前商品的id和状态:

在store中定义能更新状态的方法,来根据 goods_id 查询购物车中对应商品的信息对象:

接下来实现数字输入框的样式,可以参看uni-app官网提供给我们的组件,这里就不再赘述,简单的说一下如何调用吧,和单选框功能实现基本一致,通过v-if来动态的创建数字输入框的显示和隐藏,这里也一样,如下:

通过在store中设置更新数量的函数并存储在本地中

接下来实现滑动删除的功能,这里也是借用uni-app官网提供的一个组件,

在store中定义一个删除的方法,如下:

通过mapMutations方法,将store中定义的方法映射出来,然后进行传递参数即可。

实现收货地址区域

接下来实现收获地址区域的创建,收获地址区域我们定义在components组件当中去,然后设置相关样式在购物车页面中调用该组件即可,如下:

接下来开始设置收获地址组件的相关样式,点击增加地址组件的功能,是调用uni-app官方给我们提供的API,这里直接通过await使用即可,给出如下完整代码:

<template><view><!-- 选择收货地址的盒子 --><view class="address-choose-box" v-if="JSON.stringify(address) === '{}'"><button type="primary" size="mini" class="bunChooseAddress" @click="chooseAddress">请选择收货地址+</button></view><!-- 渲染收货信息的盒子 --><view class="address-info-box" v-else><view class="row1"><view class="row1-left"><view class="username">收货人:{{address.userName}}</view></view><view class="row1-right"><view class="phone">电话:{{address.telNumber}}</view><uni-icons type="arrowright" size="16"></uni-icons></view></view><view class="row2"><view class="row2-left">收货地址:</view><view class="row2-right">{{addStr}}</view></view></view><!-- 底部的边框线 --><image src="../../static/images/cart_border@2x.png" class="address-border"></image></view>
</template><script>export default {name:"my-address",data() {return {// 收获地址address:{}};},methods:{async chooseAddress(){const res = await uni.chooseAddress()if(res.errMsg === "chooseAddress:ok"){// 为data里面的收获地址对象赋值this.address = res}}},computed:{addStr(){if (!this.address.provinceName) return ''// 拼接 省,市,区,详细地址 的字符串并返回给用户return this.address.provinceName + this.address.cityName + this.address.countyName + this.address.detailInfo}}}
</script><style lang="scss">
.address-choose-box{display: flex;height: 90px;justify-content: center;align-items: center;
}
.address-border{display: block;width: 100%;height: 5px;
}
// 渲染收货信息的盒子
.address-info-box {font-size: 12px;height: 90px;display: flex;flex-direction: column;justify-content: center;padding: 0 5px;// 第一行.row1 {display: flex;justify-content: space-between;.row1-right {display: flex;align-items: center;.phone {margin-right: 5px;}}}// 第二行.row2 {display: flex;align-items: center;margin-top: 10px;.row2-left {white-space: nowrap;}}
}
</style>

因为数据是暂时性的,并没有存储到浏览器缓存中,一刷新界面数据就消失了,这里需要我们使用vuex进行数据的状态管理,如下:

在store文件夹中重新建一个user.js文件,用于存放收获地址的数据和方法:

export default {// 开启命名空间namespaced: true,// state数据state:()=>({// 收获地址address:JSON.parse(uni.getStorageSync('address') || '{}'),}),// 方法mutations:{// 更新收获地址updateAddress(state,address){state.address = addressthis.commit('m_user/saveAddressToStorage')},// 持久化存储addresssaveAddressToStorage(state){uni.setStorageSync('address',JSON.stringify(state.address))}},// 数据包装器getters:{addStr(state){if (!state.address.provinceName) return ''// 拼接 省,市,区,详细地址 的字符串并返回给用户return state.address.provinceName + state.address.cityName + state.address.countyName + state.address.detailInfo}},
}

在store.js文件中进行挂载:

挂载完成之后,在收获地址组件中进行数据和方法的调用:

实现结算区域

接下来实现结算区域,需要在购物车界面底部建立一个导航按钮用于显示购买的价格,全选和结算按钮的功能实现,具体操作如下:

<template><view class="my-settle-container"><!-- 全选 --><label class="radio" @click="changeAllState"><radio color="#C00000" :checked="isFullChecked" /><text>全选</text></label><!-- 合计 --><view class="amount-box">合计:<text class="amount">¥{{checkedGoodsAmount}}</text></view><!-- 结算按钮 --><view class="btn-settle">结算({{checkedCount}})</view></view>
</template><script>import { mapGetters,mapMutations } from 'vuex'export default {name:"my-settle",data() {return {};},computed:{...mapGetters('m_cart',['checkedCount','total','checkedGoodsAmount']),isFullChecked(){return this.total === this.checkedCount}},methods:{...mapMutations('m_cart',['updateAllGoodsState']),changeAllState(){this.updateAllGoodsState(!this.isFullChecked)}}}
</script><style lang="scss">
.my-settle-container{position: fixed;z-index: 999;bottom: 0;left: 0;width: 100%;height: 60px;background-color: white;display: flex;justify-content: space-between;align-items: center;font-size: 14px;padding-left: 5px;.radio{display: flex;align-items: center;}.amount-box{.amount{color: #C00000;font-weight: bold;}}.btn-settle{background-color: #C00000;height: 60px;color: white;line-height: 60px;padding: 0 10px;min-width: 100px;text-align: center;}
}
</style>

在vuex中调用数据和方法,具体操作如下:

实现的功能为:

当然如果购物车界面一件商品都没有的话,展示个图片显示无数据即可,这里使用v-if和v-else即可