小程序学习(二十七)之“购物车模块”
一、列表渲染1、页面静态结构//src\pages\cart\cart.vue script setup langts // /script template scroll-view scroll-y classscroll-view !-- 已登录: 显示购物车 -- template v-iftrue !-- 购物车列表 -- view classcart-list v-iftrue !-- 优惠提示 -- view classtips text classlabel满减/text text classdesc满1件, 即可享受9折优惠/text /view !-- 滑动操作分区 -- uni-swipe-action !-- 滑动操作项 -- uni-swipe-action-item v-foritem in 2 :keyitem classcart-swipe !-- 商品信息 -- view classgoods !-- 选中状态 -- text classcheckbox :class{ checked: true }/text navigator :url/pages/goods/goods?id1435025 hover-classnone classnavigator image modeaspectFill classpicture srchttps://yanxuan-item.nosdn.127.net/da7143e0103304f0f3230715003181ee.jpg /image view classmeta view classname ellipsis人手必备儿童轻薄透气防蚊裤73-140cm/view view classattrsText ellipsis黄色小象 140cm/view view classprice69.00/view /view /navigator !-- 商品数量 -- view classcount text classtext-/text input classinput typenumber value1 / text classtext/text /view /view !-- 右侧删除按钮 -- template #right view classcart-swipe-right button classbutton delete-button删除/button /view /template /uni-swipe-action-item /uni-swipe-action /view !-- 购物车空状态 -- view classcart-blank v-else image src/static/images/blank_cart.png classimage / text classtext购物车还是空的快来挑选好货吧/text navigator open-typeswitchTab url/pages/index/index hover-classnone button classbutton去首页看看/button /navigator /view !-- 吸底工具栏 -- view classtoolbar text classall :class{ checked: true }全选/text text classtext合计:/text text classamount100/text view classbutton-grounp view classbutton payment-button :class{ disabled: true } 去结算(10) /view /view /view /template !-- 未登录: 提示登录 -- view classlogin-blank v-else text classtext登录后可查看购物车中的商品/text navigator url/pages/login/login hover-classnone button classbutton去登录/button /navigator /view !-- 猜你喜欢 -- XtxGuess refguessRef/XtxGuess !-- 底部占位空盒子 -- view classtoolbar-height/view /scroll-view /template style langscss // 根元素 :host { height: 100vh; display: flex; flex-direction: column; overflow: hidden; background-color: #f7f7f8; } // 滚动容器 .scroll-view { flex: 1; } // 购物车列表 .cart-list { padding: 0 20rpx; // 优惠提示 .tips { display: flex; align-items: center; line-height: 1; margin: 30rpx 10rpx; font-size: 26rpx; color: #666; .label { color: #fff; padding: 7rpx 15rpx 5rpx; border-radius: 4rpx; font-size: 24rpx; background-color: #27ba9b; margin-right: 10rpx; } } // 购物车商品 .goods { display: flex; padding: 20rpx 20rpx 20rpx 80rpx; border-radius: 10rpx; background-color: #fff; position: relative; .navigator { display: flex; } .checkbox { position: absolute; top: 0; left: 0; display: flex; align-items: center; justify-content: center; width: 80rpx; height: 100%; ::before { content: \e6cd; font-family: erabbit !important; font-size: 40rpx; color: #444; } .checked::before { content: \e6cc; color: #27ba9b; } } .picture { width: 170rpx; height: 170rpx; } .meta { flex: 1; display: flex; flex-direction: column; justify-content: space-between; margin-left: 20rpx; } .name { height: 72rpx; font-size: 26rpx; color: #444; } .attrsText { line-height: 1.8; padding: 0 15rpx; font-size: 24rpx; align-self: flex-start; border-radius: 4rpx; color: #888; background-color: #f7f7f8; } .price { line-height: 1; font-size: 26rpx; color: #444; margin-bottom: 2rpx; color: #cf4444; ::before { content: ; font-size: 80%; } } // 商品数量 .count { position: absolute; bottom: 20rpx; right: 5rpx; display: flex; justify-content: space-between; align-items: center; width: 220rpx; height: 48rpx; .text { height: 100%; padding: 0 20rpx; font-size: 32rpx; color: #444; } .input { height: 100%; text-align: center; border-radius: 4rpx; font-size: 24rpx; color: #444; background-color: #f6f6f6; } } } .cart-swipe { display: block; margin: 20rpx 0; } .cart-swipe-right { display: flex; height: 100%; .button { display: flex; justify-content: center; align-items: center; width: 50px; padding: 6px; line-height: 1.5; color: #fff; font-size: 26rpx; border-radius: 0; } .delete-button { background-color: #cf4444; } } } // 空状态 .cart-blank, .login-blank { display: flex; justify-content: center; align-items: center; flex-direction: column; height: 60vh; .image { width: 400rpx; height: 281rpx; } .text { color: #444; font-size: 26rpx; margin: 20rpx 0; } .button { width: 240rpx !important; height: 60rpx; line-height: 60rpx; margin-top: 20rpx; font-size: 26rpx; border-radius: 60rpx; color: #fff; background-color: #27ba9b; } } // 吸底工具栏 .toolbar { position: fixed; left: 0; right: 0; bottom: var(--window-bottom); z-index: 1; height: 100rpx; padding: 0 20rpx; display: flex; align-items: center; border-top: 1rpx solid #ededed; border-bottom: 1rpx solid #ededed; background-color: #fff; box-sizing: content-box; .all { margin-left: 25rpx; font-size: 14px; color: #444; display: flex; align-items: center; } .all::before { font-family: erabbit !important; content: \e6cd; font-size: 40rpx; margin-right: 8rpx; } .checked::before { content: \e6cc; color: #27ba9b; } .text { margin-right: 8rpx; margin-left: 32rpx; color: #444; font-size: 14px; } .amount { font-size: 20px; color: #cf4444; .decimal { font-size: 12px; } ::before { content: ; font-size: 12px; } } .button-grounp { margin-left: auto; display: flex; justify-content: space-between; text-align: center; line-height: 72rpx; font-size: 13px; color: #fff; .button { width: 240rpx; margin: 0 10rpx; border-radius: 72rpx; } .payment-button { background-color: #27ba9b; .disabled { opacity: 0.6; } } } } // 底部占位空盒子 .toolbar-height { height: 100rpx; } /style2、登录状态已登录显示购物车列表否则应引导用户去登录。import { useMemberStore } from /stores // 获取会员Store const memberStore useMemberStore() /script template scroll-view scroll-y classscroll-view !-- 已登录: 显示购物车 -- template v-ifmemberStore.profile !-- 购物车列表 -- /template !-- 未登录: 提示登录 -- view classlogin-blank v-else text classtext登录后可查看购物车中的商品/text navigator url/pages/login/login hover-classnone button classbutton去登录/button /navigator /view /scroll-view /template3、购物车列表API/** * 获取购物车列表 */ export const getMemberCartAPI () { return httpCartItem[]({ method: GET, url: /member/cart, }) }4、声明类型/** 购物车类型 */ export type CartItem { /** 商品 ID */ id: string /** SKU ID */ skuId: string /** 商品名称 */ name: string /** 图片 */ picture: string /** 数量 */ count: number /** 加入时价格 */ price: number /** 当前的价格 */ nowPrice: number /** 库存 */ stock: number /** 是否选中 */ selected: boolean /** 属性文字 */ attrsText: string /** 是否为有效商品 */ isEffective: boolean }5、初始化调用import { getMemberCartAPI } from /services/cart import { useMemberStore } from /stores import type { CartItem } from /types/cart import { onShow } from dcloudio/uni-app import { ref } from vue // 获取会员Store const memberStore useMemberStore() // 获取购物车数据 const cartList refCartItem[]([]) const getMemberCartData async () { const res await getMemberCartAPI() cartList.value res.result } // 初始化调用: 页面显示触发 onShow(() { // 用户已登录才允许调用 if (memberStore.profile) { getMemberCartData() } })6、列表渲染template scroll-view scroll-y classscroll-view !-- 已登录: 显示购物车 -- template v-ifmemberStore.profile !-- 购物车列表 -- view classcart-list v-ifcartList.length !-- 优惠提示 -- view classtips text classlabel满减/text text classdesc满1件, 即可享受9折优惠/text /view !-- 滑动操作分区 -- uni-swipe-action !-- 滑动操作项 -- uni-swipe-action-item v-foritem in cartList :keyitem.skuId classcart-swipe !-- 商品信息 -- view classgoods !-- 选中状态 -- text classcheckbox :class{ checked: item.selected }/text navigator :url/pages/goods/goods?id$(item.id) hover-classnone classnavigator image modeaspectFill classpicture :srcitem.picture /image view classmeta view classname ellipsis{{item.name}}/view view classattrsText ellipsis{{ item.attrsText }}/view view classprice{{ item.price }}/view /view /navigator !-- 商品数量 -- view classcount text classtext-/text input classinput typenumber :valueitem.count.toString() / text classtext/text /view /view !-- 右侧删除按钮 -- template #right view classcart-swipe-right button classbutton delete-button删除/button /view /template /uni-swipe-action-item /uni-swipe-action /view !-- 购物车空状态 -- view classcart-blank v-else image src/static/images/blank_cart.png classimage / text classtext购物车还是空的快来挑选好货吧/text navigator open-typeswitchTab url/pages/index/index hover-classnone button classbutton去首页看看/button /navigator /view !-- 吸底工具栏 -- view classtoolbar text classall :class{ checked: true }全选/text text classtext合计:/text text classamount100/text view classbutton-grounp view classbutton payment-button :class{ disabled: true } 去结算(10) /view /view /view /template !-- 未登录: 提示登录 -- view classlogin-blank v-else text classtext登录后可查看购物车中的商品/text navigator url/pages/login/login hover-classnone button classbutton去登录/button /navigator /view !-- 猜你喜欢 -- XtxGuess refguessRef/XtxGuess !-- 底部占位空盒子 -- view classtoolbar-height/view /scroll-view /template二、删除单品1、接口封装/** * 删除/清空购物车单品 * param data 请求体参数 ids SKUID 集合 */ export const deleteMemberCartAPI (data: { ids: string[] }) { return http({ method: DELETE, url: /member/cart, data, }) }2、调用删除// 点击删除按钮 const onDeleteCart (skuId: string) { // 弹窗二次确认 uni.showModal({ content: 是否删除, success: async (res) { if (res.confirm) { // 后端删除单品 await deleteMemberCartAPI({ ids: [skuId] }) // 重新获取列表 getMemberCartData() } }, }) } /script template !-- 右侧删除按钮 -- template #right view classcart-swipe-right button taponDeleteCart(item.skuId) classbutton delete-button删除/button /view /template /template三、修改单品数量1、类型声明文件//src\components\vk-data-input-number-box\vk-data-input-number-box.d.ts import { Component } from uni-helper/uni-app-types /** 步进器 */ export type InputNumberBox ComponentInputNumberBoxProps /** 步进器实例 */ export type InputNumberBoxInstance InstanceTypeInputNumberBox /** 步进器属性 */ export type InputNumberBoxProps { /** 输入框初始值默认1 */ modelValue: number /** 用户可输入的最小值默认0 */ min: number /** 用户可输入的最大值默认99999 */ max: number /** 步长每次加或减的值默认1 */ step: number /** 是否禁用操作包括输入框加减按钮 */ disabled: boolean /** 输入框宽度单位rpx默认80 */ inputWidth: string | number /** 输入框和按钮的高度单位rpx默认50 */ inputHeight: string | number /** 输入框和按钮的背景颜色默认#F2F3F5 */ bgColor: string /** 步进器标识符 */ index: string /** 输入框内容发生变化时触发 */ onChange: (event: InputNumberBoxEvent) void /** 输入框失去焦点时触发 */ onBlur: (event: InputNumberBoxEvent) void /** 点击增加按钮时触发 */ onPlus: (event: InputNumberBoxEvent) void /** 点击减少按钮时触发 */ onMinus: (event: InputNumberBoxEvent) void } /** 步进器事件对象 */ export type InputNumberBoxEvent { /** 输入框当前值 */ value: number /** 步进器标识符 */ index: string } /** 全局组件类型声明 */ declare module vue { export interface GlobalComponents { vk-data-input-number-box: InputNumberBox } }2、封装接口/** * 修改购物车单品 * param skuId SKUID * param data selected 选中状态 count 商品数量 */ export const putMemberCartBySkuIdAPI ( skuId: string, data: { selected?: boolean; count?: number }, ) { return http({ method: PUT, url: /member/cart/${skuId}, data, }) }3、调用接口// 修改商品数量 const onChangeCount (ev: InputNumberBoxEvent) { putMemberCartBySkuIdAPI(ev.index, { count: ev.value }) } /script template !-- 商品数量 -- view classcount vk-data-input-number-box v-modelitem.count :min1 :maxitem.stock :indexitem.skuId changeonChangeCount / /view /template四、修改选中状态1、封装接口/** * 购物车全选/取消全选 * param data selected 是否选中 */ export const putMemberCartSelectedAPI (data: { selected: boolean }) { return http({ method: PUT, url: /member/cart/selected, data, }) }2、调用APIscript setup langts // 修改选中状态-单品修改 const onChangeSelected (item: CartItem) { // 前端数据更新-是否选中取反 item.selected !item.selected // 后端数据更新 putMemberCartBySkuIdAPI(item.skuId, { selected: item.selected }) } // 计算全选状态 const isSelectedAll computed(() { return cartList.value.length cartList.value.every((v) v.selected) }) // 修改选中状态-全选修改 const onChangeSelectedAll () { // 全选状态取反 const _isSelectedAll !isSelectedAll.value // 前端数据更新 cartList.value.forEach((item) { item.selected _isSelectedAll }) // 后端数据更新 putMemberCartSelectedAPI({ selected: _isSelectedAll }) } /script template !-- 商品信息 -- view classgoods !-- 选中状态 -- text taponChangeSelected(item) classcheckbox :class{ checked: item.selected } /text /view !-- 吸底工具栏 -- view classtoolbar text taponChangeSelectedAll classall :class{ checked: isSelectedAll }全选/text /view /template五、底部结算信息计算并展示购物车中选中商品所要支付的总金额在用户切换商品选中状态和改变购数量后总的金额也要相应的进行重新计算。六、两个购物车页面tabBar页小程序跳转到 tabBar 页面时会关闭其他所有非 tabBar 页面所以小程序的tabBar 页没有后退按钮。1、封装组件//src\pages\cart\components\CartMain.vue script setup langts import { getMemberCartAPI, deleteMemberCartAPI, putMemberCartBySkuIdAPI, putMemberCartSelectedAPI } from /services/cart import { useMemberStore } from /stores import type { CartItem } from /types/cart import { onShow } from dcloudio/uni-app import { ref, computed } from vue import type { InputNumberBoxEvent } from /components/vk-data-input-number-box/vk-data-input-number-box // 修改商品数量 const onChangeCount (ev: InputNumberBoxEvent) { putMemberCartBySkuIdAPI(ev.index, { count: ev.value }) } // 获取会员Store const memberStore useMemberStore() // 获取购物车数据 const cartList refCartItem[]([]) const getMemberCartData async () { const res await getMemberCartAPI() cartList.value res.result } // 初始化调用: 页面显示触发 onShow(() { // 用户已登录才允许调用 if (memberStore.profile) { getMemberCartData() } }) // 点击删除按钮 const onDeleteCart (skuId: string) { // 弹窗二次确认 uni.showModal({ content: 是否删除, success: async (res) { if (res.confirm) { // 后端删除单品 await deleteMemberCartAPI({ ids: [skuId] }) // 重新获取列表 getMemberCartData() } }, }) } // 修改选中状态-单品修改 const onChangeSelected (item: CartItem) { // 前端数据更新-是否选中取反 item.selected !item.selected // 后端数据更新 putMemberCartBySkuIdAPI(item.skuId, { selected: item.selected }) } // 计算全选状态 const isSelectedAll computed(() { return cartList.value.length cartList.value.every((v) v.selected) }) // 修改选中状态-全选修改 const onChangeSelectedAll () { // 全选状态取反 const _isSelectedAll !isSelectedAll.value // 前端数据更新 cartList.value.forEach((item) { item.selected _isSelectedAll }) // 后端数据更新 putMemberCartSelectedAPI({ selected: _isSelectedAll }) } // 计算选中单品列表 const selectedCartList computed(() { return cartList.value.filter((v) v.selected) }) // 计算选中总件数 const selectedCartListCount computed(() { return selectedCartList.value.reduce((sum, item) sum item.count, 0) }) // 计算选中总金额 const selectedCartListMoney computed(() { return selectedCartList.value .reduce((sum, item) sum item.count * item.nowPrice, 0) .toFixed(2) }) // 结算按钮 const gotoPayment () { if (selectedCartListCount.value 0) { return uni.showToast({ icon: none, title: 请选择商品, }) } // 跳转到结算页 uni.navigateTo({ url: /pagesOrder/create/create }) } /script template scroll-view scroll-y classscroll-view !-- 已登录: 显示购物车 -- template v-ifmemberStore.profile !-- 购物车列表 -- view classcart-list v-ifcartList.length !-- 优惠提示 -- view classtips text classlabel满减/text text classdesc满1件, 即可享受9折优惠/text /view !-- 滑动操作分区 -- uni-swipe-action !-- 滑动操作项 -- uni-swipe-action-item v-foritem in cartList :keyitem.skuId classcart-swipe !-- 商品信息 -- view classgoods !-- 选中状态 -- text taponChangeSelected(item) classcheckbox :class{ checked: item.selected }/text navigator :url/pages/goods/goods?id$(item.id) hover-classnone classnavigator image modeaspectFill classpicture :srcitem.picture /image view classmeta view classname ellipsis{{item.name}}/view view classattrsText ellipsis{{ item.attrsText }}/view view classprice{{ item.price }}/view /view /navigator !-- 商品数量 -- view classcount vk-data-input-number-box v-modelitem.count :min1 :maxitem.stock :indexitem.skuId changeonChangeCount / text classtext-/text input classinput typenumber :valueitem.count.toString() / text classtext/text /view /view !-- 右侧删除按钮 -- template #right view classcart-swipe-right button taponDeleteCart(item.skuId) classbutton delete-button删除/button /view /template /uni-swipe-action-item /uni-swipe-action /view !-- 购物车空状态 -- view classcart-blank v-else image src/static/images/blank_cart.png classimage / text classtext购物车还是空的快来挑选好货吧/text navigator open-typeswitchTab url/pages/index/index hover-classnone button classbutton去首页看看/button /navigator /view !-- 吸底工具栏 -- view classtoolbar text taponChangeSelectedAll classall :class{ checked: isSelectedAll }全选/text text classtext合计:/text text classamount{{ selectedCartListMoney }}/text view classbutton-grounp view classbutton payment-button :class{ disabled: selectedCartListCount 0 } tapgotoPayment去结算({{ selectedCartListCount }})/view /view /view /template !-- 未登录: 提示登录 -- view classlogin-blank v-else text classtext登录后可查看购物车中的商品/text navigator url/pages/login/login hover-classnone button classbutton去登录/button /navigator /view !-- 猜你喜欢 -- XtxGuess refguessRef/XtxGuess !-- 底部占位空盒子 -- view classtoolbar-height/view /scroll-view /template style langscss // 根元素 :host { height: 100vh; display: flex; flex-direction: column; overflow: hidden; background-color: #f7f7f8; } // 滚动容器 .scroll-view { flex: 1; } // 购物车列表 .cart-list { padding: 0 20rpx; // 优惠提示 .tips { display: flex; align-items: center; line-height: 1; margin: 30rpx 10rpx; font-size: 26rpx; color: #666; .label { color: #fff; padding: 7rpx 15rpx 5rpx; border-radius: 4rpx; font-size: 24rpx; background-color: #27ba9b; margin-right: 10rpx; } } // 购物车商品 .goods { display: flex; padding: 20rpx 20rpx 20rpx 80rpx; border-radius: 10rpx; background-color: #fff; position: relative; .navigator { display: flex; } .checkbox { position: absolute; top: 0; left: 0; display: flex; align-items: center; justify-content: center; width: 80rpx; height: 100%; ::before { content: \e6cd; font-family: erabbit !important; font-size: 40rpx; color: #444; } .checked::before { content: \e6cc; color: #27ba9b; } } .picture { width: 170rpx; height: 170rpx; } .meta { flex: 1; display: flex; flex-direction: column; justify-content: space-between; margin-left: 20rpx; } .name { height: 72rpx; font-size: 26rpx; color: #444; } .attrsText { line-height: 1.8; padding: 0 15rpx; font-size: 24rpx; align-self: flex-start; border-radius: 4rpx; color: #888; background-color: #f7f7f8; } .price { line-height: 1; font-size: 26rpx; color: #444; margin-bottom: 2rpx; color: #cf4444; ::before { content: ; font-size: 80%; } } // 商品数量 .count { position: absolute; bottom: 20rpx; right: 5rpx; display: flex; justify-content: space-between; align-items: center; width: 220rpx; height: 48rpx; .text { height: 100%; padding: 0 20rpx; font-size: 32rpx; color: #444; } .input { height: 100%; text-align: center; border-radius: 4rpx; font-size: 24rpx; color: #444; background-color: #f6f6f6; } } } .cart-swipe { display: block; margin: 20rpx 0; } .cart-swipe-right { display: flex; height: 100%; .button { display: flex; justify-content: center; align-items: center; width: 50px; padding: 6px; line-height: 1.5; color: #fff; font-size: 26rpx; border-radius: 0; } .delete-button { background-color: #cf4444; } } } // 空状态 .cart-blank, .login-blank { display: flex; justify-content: center; align-items: center; flex-direction: column; height: 60vh; .image { width: 400rpx; height: 281rpx; } .text { color: #444; font-size: 26rpx; margin: 20rpx 0; } .button { width: 240rpx !important; height: 60rpx; line-height: 60rpx; margin-top: 20rpx; font-size: 26rpx; border-radius: 60rpx; color: #fff; background-color: #27ba9b; } } // 吸底工具栏 .toolbar { position: fixed; left: 0; right: 0; bottom: var(--window-bottom); z-index: 1; height: 100rpx; padding: 0 20rpx; display: flex; align-items: center; border-top: 1rpx solid #ededed; border-bottom: 1rpx solid #ededed; background-color: #fff; box-sizing: content-box; .all { margin-left: 25rpx; font-size: 14px; color: #444; display: flex; align-items: center; } .all::before { font-family: erabbit !important; content: \e6cd; font-size: 40rpx; margin-right: 8rpx; } .checked::before { content: \e6cc; color: #27ba9b; } .text { margin-right: 8rpx; margin-left: 32rpx; color: #444; font-size: 14px; } .amount { font-size: 20px; color: #cf4444; .decimal { font-size: 12px; } ::before { content: ; font-size: 12px; } } .button-grounp { margin-left: auto; display: flex; justify-content: space-between; text-align: center; line-height: 72rpx; font-size: 13px; color: #fff; .button { width: 240rpx; margin: 0 10rpx; border-radius: 72rpx; } .payment-button { background-color: #27ba9b; .disabled { opacity: 0.6; } } } } // 底部占位空盒子 .toolbar-height { height: 100rpx; } /style觉得有帮助的话打赏一下呗。。