9. 结算
9.1 结算页
9.1.1 静态页面构建
地址区域
vue
<!-- 地址区域 -->
<view class="address-wrap" @click="goAddress">
<!-- 正常信息 -->
<template v-if="address.userName">
<!-- 姓名/手机号信息 -->
<view class="name">
<text>{{ address.userName }}</text>
<text>{{ address.userPhone }}</text>
</view>
<!-- 地址信息 -->
<view class="address">{{ addressInfo }}</view>
</template>
<!-- 空信息 -->
<view class="empty" v-else>
<van-icon class="warning" name="warning" />
<text>请选择收货地址</text>
</view>
<!-- 右箭头图标 -->
<van-icon class="arrow" name="arrow" />
</view>
商品清单区域
vue
<!-- 商品清单区域 -->
<view class="goods-wrap">
<view class="goods-item" v-for="item in cartList" :key="item.cartItemId">
<view class="goods-img"><image :src="item.goodsCoverImg" mode=""></image></view>
<view class="goods-desc">
<view class="goods-title">
<text>{{ item.goodsName }}</text>
<text>x{{ item.goodsCount }}</text>
</view>
<view class="goods-btn">
<view class="price">¥{{ item.sellingPrice }}</view>
</view>
</view>
</view>
</view>
支付区域
vue
<!-- 支付区域 -->
<view class="pay-wrap">
<view class="price">
<text>商品金额</text>
<text>¥{{ totol }}</text>
</view>
<van-button @click="handleCreateOrder" class="pay-btn" color="#1baeae" type="primary" block>生成订单</van-button>
</view>
9.1.2 UI样式
scss
page {
background-color: #f9f9f9;
}
.address-wrap {
margin-bottom: 40rpx;
background-color: #fff;
position: relative;
font-size: 28rpx;
padding: 30rpx;
color: #222333;
.empty {
margin: 40rpx 0;
font-size: 30rpx;
.warning {
margin-right: 20rpx;
}
}
.name,
.address {
margin: 20rpx 0;
text {
margin-right: 6rpx;
}
}
.arrow {
position: absolute;
right: 20rpx;
top: 50%;
transform: translateY(-50%);
font-size: 40rpx;
}
&::before {
content: '';
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 4rpx;
background-image: repeating-linear-gradient(-45deg, #ff6c6c 0, #ff6c6c 20%, transparent 0, transparent 25%, #1989fa 0, #1989fa 45%, transparent 0, transparent 50%);
background-size: 160rpx;
}
}
.goods-wrap {
padding-bottom: 230rpx;
.goods-item {
padding: 20rpx;
background-color: #fff;
display: flex;
.goods-img {
image {
@include wd(200rpx);
}
}
.goods-desc {
@include fj();
flex-direction: column;
flex: 1;
padding: 40rpx;
.goods-title {
@include fj();
}
.goods-btn {
@include fj();
.price {
font-size: 32rpx;
color: red;
line-height: 56rpx;
}
}
}
}
}
.pay-wrap {
position: fixed;
bottom: 0;
left: 0;
right: 0;
width: 100%;
background: #fff;
padding: 20rpx 0;
border-top: 2rpx solid #e9e9e9;
box-sizing: border-box;
.price {
@include fj();
padding: 0 5%;
margin: 20rpx 0;
font-size: 28rpx;
text:nth-child(2) {
color: red;
font-size: 36rpx;
}
}
.pay-btn .van-button {
width: 90%;
margin: 0 auto;
}
}
.pay-view {
width: 90%;
margin: 0 auto;
padding-top: 100rpx;
.van-button {
margin-bottom: 20rpx;
}
}
9.1.3 生成对应订单信息
获取 store中选中商品信息 需要获取选中商品
jsconst { data: list } = await getByCartItemIds({ cartItemIds: cartStore.cartItemIds })
页面显示时加载数据
jsonShow(() => { // 初始化数据 init() })
完整init函数
js// 初始化数据 const init = async () => { try { // 1. 加载数据 uni.showLoading({ title: '加载中...', mask: true }) // 2. 查询数据 【订单信息 / 地址信息】 const { data: list } = await getByCartItemIds({ cartItemIds: cartStore.cartItemIds }) const { data: address } = cartStore.addressId ? await getAddressDetail(cartStore.addressId) : await getDefaultAddress() state.cartList = list state.address = address || {} uni.hideLoading() console.log(address) } catch (e) { // 3. 处理错误 state.address = {} } }
计算商品价格
js// 总价格 const totol = computed(() => state.cartList.reduce((sum, item) => sum + item.goodsCount * item.sellingPrice, 0))
9.2 地址页
页面逻辑: 用户可自行选择 地址 默认加载用户默认地址 若用户选择地址则加载选择地址信息
9.2.1 地址列表
页面静态结构
vue
<view class="address-box">
<!-- 地址列表区域 -->
<view class="address-list">
<view class="address-item" v-for="item in addressList" :key="item.addressId">
<view class="content" @click="select(item.addressId)">
<view class="name">{{ item.userName }} {{ item.userPhone }} <van-tag v-if="item.defaultFlag" type="danger" round>默认</van-tag></view>
<view class="address">{{ item.provinceName }} {{ item.cityName }} {{ item.regionName }} {{ item.detailAddress }}</view>
</view>
<van-icon name="edit" class="address-edit" @click="onEdit(item.addressId)"/>
</view>
</view>
<!-- 新增地址区域 -->
<view class="address-bottom" @click="onAdd"><van-button block round color="#1baeae">新增地址</van-button></view>
</view>
页面UI样式
scss
.address-list {
box-sizing: border-box;
height: 100%;
padding: 24rpx 24rpx 160rpx;
.address-item:not(:last-child) {
margin-bottom: 24rpx;
}
.address-item {
padding: 24rpx;
background-color: #fff;
border-radius: 16rpx;
position: relative;
display: flex;
box-sizing: border-box;
width: 100%;
overflow: hidden;
color: #232323;
background-color: #FFF;
font-size: 28rpx;
line-height: 48rpx;
.content {
flex: 1;
padding-right: 88rpx;
.name {
display: flex;
align-items: center;
margin-bottom: 16rpx;
font-size: 32rpx;
line-height: 44rpx;
.van-tag {
margin-left: 16rpx;
padding-top: 0;
padding-bottom: 0;
flex: 0;
line-height: 34rpx !important;
transform: translateY(-2rpx);
}
}
.address {
color: #323233;
font-size: 26rpx;
line-height: 36rpx;
}
}
.address-edit {
position: absolute;
top: 50%;
right: 32rpx;
color: #969799;
font-size: 40rpx;
transform: translateY(-50%);
}
}
}
.address-bottom {
position: fixed;
bottom: 0;
left: 0;
z-index: 999;
box-sizing: border-box;
width: 100%;
padding: 0 32rpx;
background-color: #fff;
.van-button {
margin: 10rpx 0;
}
}
页面逻辑
页面显示时加载我的地址列表
js// 页面显示 onShow(async () => { // 加载我的地址列表 const { data } = await getAddressList() state.addressList = data })
设置新增/编辑地址跳转
js// * 新增地址 const onAdd = () => { uni.navigateTo({ url: '/subpkg/address-edit/address-edit?type=add' }) } // * 修改地址 const onEdit = (id) => { uni.navigateTo({ url: `/subpkg/address-edit/address-edit?type=edit&addressId=${id}` }) }
设置选中地址
js// * 选择地址 const select = (id) => { // 来源为mine我的 则不进行跳转 if (state.from === 'mine') return cartStore.addressId = id uni.navigateBack({ url: `/subpkg/createOrder/createOrder` }) }
将选中的地址保存到Store中 供结算页使用
9.2.2 地址编辑
页面结构
vue
<view class="address-edit">
<view class="content">
<!-- 输入框组 -->
<van-cell-group>
<van-field data-name="userName" :value="formInput.userName" @change="handleValue" label="姓名" placeholder="收货人姓名" clearable />
<van-field data-name="userPhone" :value="formInput.userPhone" @change="handleValue" type="number" label="电话" placeholder="收货人手机号" clearable />
<van-cell title="地区">
<picker mode="region" :value="region" @change="changeRegion">
<view v-if="!region.length" class="city">选择省 / 市 / 区</view>
<view v-else class="city2">{{ region.join('/') }}</view>
</picker>
</van-cell>
<van-field
data-name="detailAddress"
:value="formInput.detailAddress"
@change="handleValue"
type="textarea"
label="详细地址"
placeholder="街道门排, 楼层房间号等信息"
clearable
/>
<van-cell class="default" title="设为默认收货地址"><switch :checked="formInput.defaultFlag" @change="changeDefault" /></van-cell>
</van-cell-group>
<van-button :loading="loading" color="#1baeae" round block @click="onSave">保存</van-button>
<van-button v-if="state.type === 'edit'" round block @click="onDelete">删除</van-button>
</view>
</view>
- 输入框使用vant输入框 进行UI布局
- 地址 省市区选择 使用原生Picker组件
页面UI样式
scss
.content {
padding: 24rpx;
.van-cell__title {
max-width: 180rpx !important;
color: #646566;
margin-right: 24rpx;
}
.default .van-cell__title {
max-width: 100% !important;
}
.city {
color: #ccc;
text-align: left;
}
.city2 {
color: #000;
text-align: left;
}
switch {
transform: scale(0.8);
}
.van-button {
margin-top: 36rpx;
}
}
页面逻辑
完成基础版本的双向绑定
输入框双向绑定
js// 输入框双向绑定 ** const handleValue = e => { const { name } = e.target.dataset const value = e.detail state.formInput[name] = value }
省市区
js// 选择省市区 const changeRegion = e => { const value = e.detail.value state.region = value state.formInput.provinceName = value[0] state.formInput.cityName = value[1] state.formInput.regionName = value[2] }
选择默认
js// 选择默认 const changeDefault = e => { const isCheck = e.detail.value state.formInput.defaultFlag = isCheck ? 1 : 0 }
**页面初始化加载地址信息 **
js
// 页面加载
onLoad(async (message) => {
// 1. 获取参数
const { type, addressId } = message
uni.setNavigationBarTitle({ title: type === 'add' ? '新增地址' : '修改地址' })
state.type = type
// 2. 初始化地址
if (type !== 'add') {
const { data } = await getAddressDetail(addressId)
state.formInput = data
state.region = [data.provinceName, data.cityName, data.regionName]
}
})
保存地址
js
// * 保存地址
const onSave = async () => {
// 判断值的有效性
const { userName, userPhone, detailAddress } = state.formInput
if (!userName.trim().length) return uni.$Toast('姓名不能为空')
if (!/1[3456789]\d{9}/.test(userPhone)) return uni.$Toast('请填写正确的手机号')
if (!state.region.length) return uni.$Toast('请选择省市区')
if (detailAddress.length < 6) return uni.$Toast('详情地址最少6个字符')
state.loading = true
try{
await state.type === 'add' ? addAddress(state.formInput) : EditAddress(state.formInput)
uni.$Toast('保存成功')
setTimeout(() => {
uni.navigateBack()
}, 500)
state.loading = false
}catch(e){
state.loading = false
}
}
删除地址
js
// * 删除地址
const onDelete = async () => {
await DeleteAddress(state.formInput.addressId)
uni.$Toast('删除成功')
setTimeout(() => {
uni.navigateBack()
}, 500)
}
9.3 结算支付
结算弹窗层
vue
<!-- 弹出层 支付框 -->
<van-popup :show="showPay" closeable position="bottom" custom-style="height: 30%" @close="closePay">
<view class="pay-view">
<!-- 微信 2 其他 1 -->
<van-button color="#4fc08d" block @click="handlePayOrder(2)">微信支付</van-button>
<van-button color="#1989fa" block @click="handlePayOrder(1)">其他支付</van-button>
</view>
</van-popup>
UI样式
scss
.pay-view {
width: 90%;
margin: 0 auto;
padding-top: 100rpx;
.van-button {
margin-bottom: 20rpx;
}
}
结算逻辑
无论是否支付 点击生成订单后 都将跳转订单页
js
// 生成订单
const handleCreateOrder = async () => {
// 校验是否有地址
if (!state.address.addressId) return uni.$Toast('请先选择收货地址')
// 1. 准备参数
const params = {
addressId: state.address.addressId,
cartItemIds: state.cartList.map(item => item.cartItemId)
}
// 2. 发起请求
const { data } = await createOrder(params)
console.log(data)
state.orderNo = data
state.showPay = true
}
// 支付订单
const handlePayOrder = async (type) => {
await payOrder({ orderNo: state.orderNo, payType: type })
uni.$Toast('支付成功')
setTimeout(() => {
uni.redirectTo({ url: '/subpkg/order/order' })
}, 500)
}
// 关闭支付
const closePay = () => {
uni.$Toast('你关闭了支付')
state.showPay = false
uni.redirectTo({ url: '/subpkg/order/order' })
}