1.地址模块
1.1 静态结构
1.1.1 address的静态结构
<script setup lang="ts">
//
</script>
<template>
<view class="viewport">
<!-- 地址列表 -->
<scroll-view class="scroll-view" scroll-y>
<view v-if="true" class="address">
<view class="address-list">
<!-- 收货地址项 -->
<view class="item">
<view class="item-content">
<view class="user">
黑马小王子
<text class="contact">13111111111</text>
<text v-if="true" class="badge">默认</text>
</view>
<view class="locate">广东省 广州市 天河区 黑马程序员</view>
<navigator
class="edit"
hover-class="none"
:url="`/pagesMember/address-form/address-form?id=1`"
>
修改
</navigator>
</view>
</view>
<!-- 收货地址项 -->
<view class="item">
<view class="item-content">
<view class="user">
黑马小公主
<text class="contact">13222222222</text>
<text v-if="false" class="badge">默认</text>
</view>
<view class="locate">北京市 北京市 顺义区 黑马程序员</view>
<navigator
class="edit"
hover-class="none"
:url="`/pagesMember/address-form/address-form?id=2`"
>
修改
</navigator>
</view>
</view>
</view>
</view>
<view v-else class="blank">暂无收货地址</view>
</scroll-view>
<!-- 添加按钮 -->
<view class="add-btn">
<navigator hover-class="none" url="/pagesMember/address-form/address-form">
新建地址
</navigator>
</view>
</view>
</template>
<style lang="scss">
page {
height: 100%;
overflow: hidden;
}
/* 删除按钮 */
.delete-button {
display: flex;
justify-content: center;
align-items: center;
width: 50px;
height: 100%;
font-size: 28rpx;
color: #fff;
border-radius: 0;
padding: 0;
background-color: #cf4444;
}
.viewport {
display: flex;
flex-direction: column;
height: 100%;
background-color: #f4f4f4;
.scroll-view {
padding-top: 20rpx;
}
}
.address {
padding: 0 20rpx;
margin: 0 20rpx;
border-radius: 10rpx;
background-color: #fff;
.item-content {
line-height: 1;
padding: 40rpx 10rpx 38rpx;
border-bottom: 1rpx solid #ddd;
position: relative;
.edit {
position: absolute;
top: 36rpx;
right: 30rpx;
padding: 2rpx 0 2rpx 20rpx;
border-left: 1rpx solid #666;
font-size: 26rpx;
color: #666;
line-height: 1;
}
}
.item:last-child .item-content {
border: none;
}
.user {
font-size: 28rpx;
margin-bottom: 20rpx;
color: #333;
.contact {
color: #666;
}
.badge {
display: inline-block;
padding: 4rpx 10rpx 2rpx 14rpx;
margin: 2rpx 0 0 10rpx;
font-size: 26rpx;
color: #27ba9b;
border-radius: 6rpx;
border: 1rpx solid #27ba9b;
}
}
.locate {
line-height: 1.6;
font-size: 26rpx;
color: #333;
}
}
.blank {
margin-top: 300rpx;
text-align: center;
font-size: 32rpx;
color: #888;
}
.add-btn {
height: 80rpx;
text-align: center;
line-height: 80rpx;
margin: 30rpx 20rpx;
color: #fff;
border-radius: 80rpx;
font-size: 30rpx;
background-color: #27ba9b;
}
</style>
点击跳转
1.1.2 address-form的静态结构
<script setup lang="ts">
import { ref } from 'vue'
// 表单数据
const form = ref({
receiver: '', // 收货人
contact: '', // 联系方式
fullLocation: '', // 省市区(前端展示)
provinceCode: '', // 省份编码(后端参数)
cityCode: '', // 城市编码(后端参数)
countyCode: '', // 区/县编码(后端参数)
address: '', // 详细地址
isDefault: 0, // 默认地址,1为是,0为否
})
</script>
<template>
<view class="content">
<form>
<!-- 表单内容 -->
<view class="form-item">
<text class="label">收货人</text>
<input class="input" placeholder="请填写收货人姓名" value="" />
</view>
<view class="form-item">
<text class="label">手机号码</text>
<input class="input" placeholder="请填写收货人手机号码" value="" />
</view>
<view class="form-item">
<text class="label">所在地区</text>
<picker class="picker" mode="region" value="">
<view v-if="false">广东省 广州市 天河区</view>
<view v-else class="placeholder">请选择省/市/区(县)</view>
</picker>
</view>
<view class="form-item">
<text class="label">详细地址</text>
<input class="input" placeholder="街道、楼牌号等信息" value="" />
</view>
<view class="form-item">
<label class="label">设为默认地址</label>
<switch class="switch" color="#27ba9b" :checked="true" />
</view>
</form>
</view>
<!-- 提交按钮 -->
<button class="button">保存并使用</button>
</template>
<style lang="scss">
page {
background-color: #f4f4f4;
}
.content {
margin: 20rpx 20rpx 0;
padding: 0 20rpx;
border-radius: 10rpx;
background-color: #fff;
.form-item,
.uni-forms-item {
display: flex;
align-items: center;
min-height: 96rpx;
padding: 25rpx 10rpx 40rpx;
background-color: #fff;
font-size: 28rpx;
border-bottom: 1rpx solid #ddd;
position: relative;
margin-bottom: 0;
// 调整 uni-forms 样式
.uni-forms-item__content {
display: flex;
}
.uni-forms-item__error {
margin-left: 200rpx;
}
&:last-child {
border: none;
}
.label {
width: 200rpx;
color: #333;
}
.input {
flex: 1;
display: block;
height: 46rpx;
}
.switch {
position: absolute;
right: -20rpx;
transform: scale(0.8);
}
.picker {
flex: 1;
}
.placeholder {
color: #808080;
}
}
}
.button {
height: 80rpx;
margin: 30rpx 20rpx;
color: #fff;
border-radius: 80rpx;
font-size: 30rpx;
background-color: #27ba9b;
}
</style>
跳转成功
1.1.2.1 动态设置标题
如果是新建地址的话是没有id值的,如果是修改地址的话是有id值的,所以我们可以根据是否有id值来动态渲染标题
代码实现:
1.2 新建地址
1.2.1 封装API接口并定义参数类型
注意注意:后端接收的地址数据是地址的编码,而前端渲染的是中文
定义接口:
定义参数类型:
1.2.2 收集表单数据
1.2.2.1 收获人姓名(直接使用vue的v-model进行双向绑定)
1.2.2.2 手机号码(直接使用vue的v-model进行双向绑定)
1.2.2.3 地区(使用picker事件获取)
前端
后端
1.2.2.4 收集是否为默认收货地址
1.2.2.5 绑定提交表单事件
提交成功后给个轻提示
1.3 列表渲染
1.3.1 封装API接口
1.3.2 页面中使用接口
1.3.3 数据类型声明文件
合并复用类型
1.3.4 保存数组
1.3.5 渲染页面
成功:
1.3.5 新加页面后不会自动更新,因为只写了onLoad,即在页面加载的时候刷新,所以要有所更改
改为onShow,每次页面加载的时候就刷新
1.4 修改地址-数据回显
1.4.1 封装API接口
1.4.2 获取数据并使用onLoad加载(点击获取id)
1.4.3 表单数据的回显
1.4.4 成功回显
1.5 修改地址-保存修改
1.5.1 根据id的有无来判断是修改地址还是新建地址
1.5.2 封装修改收货地址的API接口
结果:
1.6 表单校验
1.6.1 指定校验规则
按照vue文档,结合自己的变量名,编写rules:
1.6.2 修改表单结构
使用uniapp中的uni-forms
1.6.3 表单组件实例
成功:
1.6.4 和前面流程一样,把其他字段也进行一个校验(手机号码,再给校验规则额外加个手机格式)
结果:
其他的字段校验步骤一模一样,此处做省略
1.6.5 效果展示:
1.7 地址-删除地址(使用uni-swiper-action)
1.7.1 修改列表结构
结果:
1.7.2 绑定删除事件
效果展示:
1.7.3 删除收获地址的API
1.7.4 删除成功
2 SKU模块-了解SKU(stock-keeping-unit)
2.1 安装插件后将文件夹放置项目根目录下的components文件夹中
2.2 复制示例代码
<!-- 静态数据演示版本 适合任何后端 -->
<template>
<view class="app">
<button @click="openSkuPopup()">打开SKU组件</button>
<vk-data-goods-sku-popup
ref="skuPopup"
v-model="skuKey"
border-radius="20"
:z-index="990"
:localdata="goodsInfo"
:mode="skuMode"
@open="onOpenSkuPopup"
@close="onCloseSkuPopup"
@add-cart="addCart"
@buy-now="buyNow"
></vk-data-goods-sku-popup>
</view>
</template>
<script>
export default {
data() {
return {
// 是否打开SKU弹窗
skuKey: false,
// SKU弹窗模式
skuMode: 1,
// 后端返回的商品信息
goodsInfo: {}
};
},
// 监听 - 页面每次【加载时】执行(如:前进)
onLoad(options) {
this.init(options);
},
methods: {
// 初始化
init(options = {}) {},
// 获取商品信息,并打开sku弹出
openSkuPopup() {
/**
* 获取商品信息
* 这里可以看到每次打开SKU都会去重新请求商品信息,为的是每次打开SKU组件可以实时看到剩余库存
*/
// 此处写接口请求,并将返回的数据进行处理成goodsInfo的数据格式,
// goodsInfo是后端返回的数据
this.goodsInfo = {
"_id": "001",
"name": "iphone11",
"goods_thumb": "https://img14.360buyimg.com/n0/jfs/t1/59022/28/10293/141808/5d78088fEf6e7862d/68836f52ffaaad96.jpg",
"sku_list": [
{
"_id": "001",
"goods_id": "001",
"goods_name": "iphone11",
"image": "https://img14.360buyimg.com/n0/jfs/t1/79668/22/9987/159271/5d780915Ebf9bf3f4/6a1b2703a9ed8737.jpg",
"price": 19800,
"sku_name_arr": ["红色", "128G", "公开版"],
"stock": 1000
},
{
"_id": "002",
"goods_id": "001",
"goods_name": "iphone11",
"image": "https://img14.360buyimg.com/n0/jfs/t1/52252/35/10516/124064/5d7808e0E46202391/7100f3733a1c1f00.jpg",
"price": 9800,
"sku_name_arr": ["白色", "256G", "公开版"],
"stock": 100
},
{
"_id": "003",
"goods_id": "001",
"goods_name": "iphone11",
"image": "https://img14.360buyimg.com/n0/jfs/t1/79668/22/9987/159271/5d780915Ebf9bf3f4/6a1b2703a9ed8737.jpg",
"price": 19800,
"sku_name_arr": ["红色", "256G", "公开版"],
"stock": 1
}
],
"spec_list": [
{
"name": "颜色",
"list": [
{ "name": "红色" },
{ "name": "黑色" },
{ "name": "白色" }
]
},
{
"name": "内存",
"list": [
{ "name": "128G" },
{ "name": "256G" }
],
},
{
"name": "版本",
"list": [
{ "name": "公开版" },
{ "name": "非公开版" }
]
}
]
};
this.skuKey = true;
},
// sku组件 开始-----------------------------------------------------------
onOpenSkuPopup() {
console.log("监听 - 打开sku组件");
},
onCloseSkuPopup() {
console.log("监听 - 关闭sku组件");
},
// 加入购物车前的判断
addCartFn(obj) {
let { selectShop } = obj;
// 模拟添加到购物车,请替换成你自己的添加到购物车逻辑
let res = {};
let name = selectShop.goods_name;
if (selectShop.sku_name != "默认") {
name += "-" + selectShop.sku_name_arr;
}
res.msg = `${name} 已添加到购物车`;
if (typeof obj.success == "function") obj.success(res);
},
// 加入购物车按钮
addCart(selectShop) {
console.log("监听 - 加入购物车");
this.addCartFn({
selectShop: selectShop,
success: res => {
// 实际业务时,请替换自己的加入购物车逻辑
this.toast(res.msg);
setTimeout(() => {
this.skuKey = false;
}, 300);
}
});
},
// 立即购买
buyNow(selectShop) {
console.log("监听 - 立即购买");
this.addCartFn({
selectShop: selectShop,
success: res => {
// 实际业务时,请替换自己的立即购买逻辑
this.toast("立即购买");
}
});
},
toast(msg) {
uni.showToast({
title: msg,
icon: "none"
});
}
}
};
</script>
<style lang="scss" scoped>
.app {
padding: 30rpx;
font-size: 28rpx;
}
</style>
2.3 打开对应页面
2.4 SKU实战-渲染商品信息
2.4.1 类型声明文件
import { Component } from '@uni-helper/uni-app-types'
/** SKU 弹出层 */
export type SkuPopup = Component<SkuPopupProps>
/** SKU 弹出层实例 */
export type SkuPopupInstanceType = InstanceType<SkuPopup>
/** SKU 弹出层属性 */
export type SkuPopupProps = {
/** 双向绑定,true 为打开组件,false 为关闭组件 */
modelValue: boolean
/** 商品信息本地数据源 */
localdata: SkuPopupLocaldata
/** 按钮模式 1:都显示 2:只显示购物车 3:只显示立即购买 */
mode?: 1 | 2 | 3
/** 该商品已抢完时的按钮文字 */
noStockText?: string
/** 库存文字 */
stockText?: string
/** 点击遮罩是否关闭组件 */
maskCloseAble?: boolean
/** 顶部圆角值 */
borderRadius?: string | number
/** 最小购买数量 */
minBuyNum?: number
/** 最大购买数量 */
maxBuyNum?: number
/** 每次点击后的数量 */
stepBuyNum?: number
/** 是否只能输入 step 的倍数 */
stepStrictly?: boolean
/** 是否隐藏库存的显示 */
hideStock?: false
/** 主题风格 */
theme?: 'default' | 'red-black' | 'black-white' | 'coffee' | 'green'
/** 默认金额会除以100(即100=1元),若设置为0,则不会除以100(即1=1元) */
amountType?: 1 | 0
/** 自定义获取商品信息的函数(已知支付宝不支持,支付宝请改用localdata属性) */
customAction?: () => void
/** 是否显示右上角关闭按钮 */
showClose?: boolean
/** 关闭按钮的图片地址 */
closeImage?: string
/** 价格的字体颜色 */
priceColor?: string
/** 立即购买 - 按钮的文字 */
buyNowText?: string
/** 立即购买 - 按钮的字体颜色 */
buyNowColor?: string
/** 立即购买 - 按钮的背景颜色 */
buyNowBackgroundColor?: string
/** 加入购物车 - 按钮的文字 */
addCartText?: string
/** 加入购物车 - 按钮的字体颜色 */
addCartColor?: string
/** 加入购物车 - 按钮的背景颜色 */
addCartBackgroundColor?: string
/** 商品缩略图背景颜色 */
goodsThumbBackgroundColor?: string
/** 样式 - 不可点击时,按钮的样式 */
disableStyle?: object
/** 样式 - 按钮点击时的样式 */
activedStyle?: object
/** 样式 - 按钮常态的样式 */
btnStyle?: object
/** 字段名 - 商品表id的字段名 */
goodsIdName?: string
/** 字段名 - sku表id的字段名 */
skuIdName?: string
/** 字段名 - 商品对应的sku列表的字段名 */
skuListName?: string
/** 字段名 - 商品规格名称的字段名 */
specListName?: string
/** 字段名 - sku库存的字段名 */
stockName?: string
/** 字段名 - sku组合路径的字段名 */
skuArrName?: string
/** 字段名 - 商品缩略图字段名(未选择sku时) */
goodsThumbName?: string
/** 被选中的值 */
selectArr?: string[]
/** 打开弹出层 */
onOpen: () => void
/** 关闭弹出层 */
onClose: () => void
/** 点击加入购物车时(需选择完SKU才会触发)*/
onAddCart: (event: SkuPopupEvent) => void
/** 点击立即购买时(需选择完SKU才会触发)*/
onBuyNow: (event: SkuPopupEvent) => void
}
/** 商品信息本地数据源 */
export type SkuPopupLocaldata = {
/** 商品 ID */
_id: string
/** 商品名称 */
name: string
/** 商品图片 */
goods_thumb: string
/** 商品规格列表 */
spec_list: SkuPopupSpecItem[]
/** 商品SKU列表 */
sku_list: SkuPopupSkuItem[]
}
/** 商品规格名称的集合 */
export type SkuPopupSpecItem = {
/** 规格名称 */
name: string
/** 规格集合 */
list: { name: string }[]
}
/** 商品SKU列表 */
export type SkuPopupSkuItem = {
/** SKU ID */
_id: string
/** 商品 ID */
goods_id: string
/** 商品名称 */
goods_name: string
/** 商品图片 */
image: string
/** SKU 价格 * 100, 注意:需要乘以 100 */
price: number
/** SKU 规格组成, 注意:需要与 spec_list 数组顺序对应 */
sku_name_arr: string[]
/** SKU 库存 */
stock: number
}
/** 当前选择的sku数据 */
export type SkuPopupEvent = SkuPopupSkuItem & {
/** 商品购买数量 */
buy_num: number
}
/** 全局组件类型声明 */
declare module 'vue' {
export interface GlobalComponents {
'vk-data-goods-sku-popup': SkuPopup
}
}
2.4.2 添加弹窗组件
2.4.3 准备localdata以及与SKU的商品格式进行关联(与我们的真实的后端数据格式进行匹配)
2.4.4 将后端数据处理为SKU所需的格式
处理后端数据为我们所需的 有了类型声明文件,现在所需的字段为:
前三个是简单的字段,从后端的数据中.提取出来就好了,后两个是数组,我们需要先去类型声明文件观察数组类型
2.4.4.1 spec_list:规格
2.4.4.2 sku_list
2.4.5 注意,通过点击‘选择商品规格’,才显示为true
2.4.6 成功
2.5 SKU-打开弹窗交互
2.5.1 绑定mode(通过传参来实现打开购物车还是购买还是两者都有)
2.5.2 绑定函数(与h5标签)
2.5.3 成功
2.6 渲染被选中的值
需要使用到组件内部的值:selectArr
2.6.1 通过获取组件的实例,再获取组件内部的值‘selectArr’
2.6.2 计算被选中的值
2.6.3 效果:
2.7 加入购物车
2.7.1 绑定一个加入购物车事件
2.7.2 加入购物车接口
2.7.3 页面中调用接口
2.7.4 成功
2.8 渲染购物车列表
2.8.1 购物车静态结构
<script setup lang="ts">
//
</script>
<template>
<scroll-view scroll-y class="scroll-view">
<!-- 已登录: 显示购物车 -->
<template v-if="true">
<!-- 购物车列表 -->
<view class="cart-list" v-if="true">
<!-- 优惠提示 -->
<view class="tips">
<text class="label">满减</text>
<text class="desc">满1件, 即可享受9折优惠</text>
</view>
<!-- 滑动操作分区 -->
<uni-swipe-action>
<!-- 滑动操作项 -->
<uni-swipe-action-item v-for="item in 2" :key="item" class="cart-swipe">
<!-- 商品信息 -->
<view class="goods">
<!-- 选中状态 -->
<text class="checkbox" :class="{ checked: true }"></text>
<navigator
:url="`/pages/goods/goods?id=1435025`"
hover-class="none"
class="navigator"
>
<image
mode="aspectFill"
class="picture"
src="https://yanxuan-item.nosdn.127.net/da7143e0103304f0f3230715003181ee.jpg"
></image>
<view class="meta">
<view class="name ellipsis">人手必备,儿童轻薄透气防蚊裤73-140cm</view>
<view class="attrsText ellipsis">黄色小象 140cm</view>
<view class="price">69.00</view>
</view>
</navigator>
<!-- 商品数量 -->
<view class="count">
<text class="text">-</text>
<input class="input" type="number" value="1" />
<text class="text">+</text>
</view>
</view>
<!-- 右侧删除按钮 -->
<template #right>
<view class="cart-swipe-right">
<button class="button delete-button">删除</button>
</view>
</template>
</uni-swipe-action-item>
</uni-swipe-action>
</view>
<!-- 购物车空状态 -->
<view class="cart-blank" v-else>
<image src="/static/images/blank_cart.png" class="image" />
<text class="text">购物车还是空的,快来挑选好货吧</text>
<navigator open-type="switchTab" url="/pages/index/index" hover-class="none">
<button class="button">去首页看看</button>
</navigator>
</view>
<!-- 吸底工具栏 -->
<view class="toolbar">
<text class="all" :class="{ checked: true }">全选</text>
<text class="text">合计:</text>
<text class="amount">100</text>
<view class="button-grounp">
<view class="button payment-button" :class="{ disabled: true }"> 去结算(10) </view>
</view>
</view>
</template>
<!-- 未登录: 提示登录 -->
<view class="login-blank" v-else>
<text class="text">登录后可查看购物车中的商品</text>
<navigator url="/pages/login/login" hover-class="none">
<button class="button">去登录</button>
</navigator>
</view>
<!-- 猜你喜欢 -->
<XtxGuess ref="guessRef"></XtxGuess>
<!-- 底部占位空盒子 -->
<view class="toolbar-height"></view>
</scroll-view>
</template>
<style lang="scss">
// 根元素
: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>
2.8.3 获取会员Store,判断是否登录
登录状态时的购物车栏:
在‘我的’页面退出登录,退出登录状态的购物车栏:
2.8.4 封装获取购物车列表接口
2.8.5 处理接口获取到的数据
2.8.6 购物车列表的类型声明文件
2.8.7 页面中使用
2.8.8 页面中渲染
2.8.9 成功
再添加一件商品
2.9 删除单品
侧滑显示删除的插槽,
2.9.1 封装删除的API
2.9.2 给按钮绑定事件
需要传入参数来确认删除的目标购物车单品
使用模态弹窗向用户确认是否删除购物车单品
2.9.3 效果
2.10 修改购物车单品数量
这是sku组件里已经自带了的
2.10.1 准备步进器的类型声明文件
// 步进器类型声明文件
import { Component } from '@uni-helper/uni-app-types';
/** 步进器 */
export type InputNumberBox = Component<InputNumberBoxProps>;
/** 步进器实例 */
export type InputNumberBoxInstance = InstanceType<InputNumberBox>;
/** 步进器属性 */
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.10.2 在页面中使用sku的官方步近期,并基于步进器设置商品最小值(1),和商品最大值(库存)
2.10.3 绑定事件
2.10.4 封装修改购物车的API
实现:
2.11 购物车-修改选中状态
2.11.1 给选中状态绑定点击事件
2.11.2 前端勾选改变
2.11.3 后端选中状态发送
实现:
2.11.4 与全选勾选统一
实时更新:当购物车中 任何一个商品 的 selected 状态发生变化时, isSelectedAll 会自动重新计算
使用every,computed,根据购物车列表中的每个的selected属性来计算全选的勾选
2.11.5 全选状态取反
2.11.5.1 前端
2.11.5.2 后端
2.11.5.2.1 需要调用切封装购物车取消全选的API
2.11.5.2.2 主页面调用API
结果:
2.12 购物车-底部结算信息
2.12.1 计算被选中的单品,并计算选中的总数
2.12.2 在页面中导入
效果:
2.12.3 计算被选中的单品的总价格
效果: