feat(订单页面): 添加打开柜子功能并优化支付方式选择界面

- 在订单页面添加打开柜子的功能,支持状态为1和5的订单商品
- 优化支付方式选择界面,使用更直观的单元格布局
- 在提交订单时添加手机号格式验证
This commit is contained in:
dzq 2025-04-15 16:54:40 +08:00
parent 09b346eb80
commit 6a3841dd72
5 changed files with 160 additions and 53 deletions

2
.env
View File

@ -7,4 +7,4 @@ VITE_APP_TITLE = 借还柜
VITE_ROUTER_HISTORY = hash VITE_ROUTER_HISTORY = hash
## 是否开启 console 调试工具 ## 是否开启 console 调试工具
VITE_CONSOLE = true VITE_CONSOLE = false

View File

@ -1,7 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted } from 'vue' import { onMounted } from 'vue'
import { useOrderStore } from '@/pinia/stores/order' import { OrderGoods, useOrderStore } from '@/pinia/stores/order'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { openCabinetApi } from '@/common/apis/shop'
import { showSuccessToast, showFailToast } from 'vant'
import { ref } from 'vue'
const orderStore = useOrderStore() const orderStore = useOrderStore()
const route = useRoute() const route = useRoute()
@ -55,6 +58,27 @@ onMounted(() => {
// //
} }
}) })
const isButtonDisabled = ref<Record<number, boolean>>({})
async function handleOpenCabinet(item: OrderGoods) {
const orderGoodsId = item.orderGoods.orderGoodsId
isButtonDisabled.value[orderGoodsId] = true
try {
const result = await openCabinetApi(orderId.value, orderGoodsId)
if (result.code !== 0) {
showFailToast(result.msg || '开启失败,请稍后重试')
return
}
showSuccessToast('柜口已成功开启')
} catch (error) {
showFailToast('开启失败,请稍后重试')
} finally {
setTimeout((currentId) => {
delete isButtonDisabled.value[currentId]
}, 5000, orderGoodsId)
}
}
</script> </script>
<template> <template>
@ -94,16 +118,26 @@ onMounted(() => {
<div class="action-row"> <div class="action-row">
<p>数量: {{ item.orderGoods.quantity }}</p> <p>数量: {{ item.orderGoods.quantity }}</p>
<p>小计: ¥{{ (item.orderGoods.price * item.orderGoods.quantity).toFixed(2) }}</p> <p>小计: ¥{{ (item.orderGoods.price * item.orderGoods.quantity).toFixed(2) }}</p>
<template v-if="item.orderGoods.status === 1"> <div class="button-group">
<van-button <van-button
v-if="[1,5].includes(item.orderGoods.status)"
type="primary" type="primary"
size="mini" size="small"
class="refund-btn" @click="handleOpenCabinet(item)"
:disabled="isButtonDisabled[item.orderGoods.orderGoodsId]"
>
打开柜子
</van-button>
<van-button
v-if="item.orderGoods.status === 1"
type="primary"
size="small"
@click="handleRefund(item)" @click="handleRefund(item)"
> >
退还 退还商品
</van-button> </van-button>
</template> </div>
</div>
<span <span
v-if="[2,5,6].includes(item.orderGoods.status)" v-if="[2,5,6].includes(item.orderGoods.status)"
class="status-text" class="status-text"
@ -112,7 +146,6 @@ onMounted(() => {
{{ getOrderGoodsStatusText(item.orderGoods.status) }} {{ getOrderGoodsStatusText(item.orderGoods.status) }}
</span> </span>
</div> </div>
</div>
</van-cell> </van-cell>
</van-cell-group> </van-cell-group>
</div> </div>
@ -183,20 +216,17 @@ goods-info {
color: #999; color: #999;
} }
.refund-btn { .button-group {
margin-left: auto; display: flex;
color: #fff; justify-content: flex-end;
background: #ee0a24; gap: 8px;
border-radius: 15px; width: 100%;
padding: 8px 20px;
font-size: 14px;
min-width: 80px;
} }
.action-row { .action-row {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px;
align-items: flex-end; align-items: flex-end;
gap: 4px;
} }
</style> </style>

View File

@ -72,7 +72,13 @@ async function handleSubmit() {
}) })
} }
// // handleSubmit
if (!/^1[3-9]\d{9}$/.test(tel.value)) {
return showConfirmDialog({
title: "格式错误",
message: "请输入有效的手机号码"
});
}
submitting.value = true submitting.value = true
try { try {
@ -165,23 +171,40 @@ async function handleSubmit() {
</div> </div>
</div> </div>
</van-cell> </van-cell>
<van-cell>
<van-field
v-model="tel"
label="手机号"
placeholder="请输入联系电话"
required
class="tel-input"
/>
</van-cell>
</van-cell-group> </van-cell-group>
<van-cell-group class="contact-form"> <van-cell-group class="contact-form">
<van-field label="支付方式" :model-value="selectedPayment" readonly> <van-cell v-if="!corpidLogin"
<template #input> :class="['payment-option', { selected: selectedPayment === 'wechat' }]"
<van-radio-group v-model="selectedPayment" direction="horizontal"> @click="selectedPayment = 'wechat'"
<van-radio name="wechat" v-if="!corpidLogin"> >
<van-icon name="wechat" class="method-icon" /> <van-icon name="wechat" class="method-icon" />
微信支付 <span class="method-label">微信支付</span>
</van-radio> <div class="empty"></div>
<van-radio name="balance" :disabled="balance < totalPrice"> <van-icon v-if="selectedPayment === 'wechat'" name="success" class="check-icon" />
</van-cell>
<van-cell
:class="['payment-option', { selected: selectedPayment === 'balance', disabled: balance < totalPrice }]"
@click="selectedPayment = 'balance'"
>
<van-icon name="balance-o" class="method-icon" /> <van-icon name="balance-o" class="method-icon" />
余额支付当前¥{{ balance.toFixed(2) }} <span class="method-label">
</van-radio> 余额支付
</van-radio-group> <span class="balance-amount">当前¥{{ balance.toFixed(2) }}</span>
</template> </span>
</van-field> <div class="empty"></div>
<van-icon v-if="selectedPayment === 'balance'" name="success" class="check-icon" />
</van-cell>
</van-cell-group> </van-cell-group>
<!-- 提交订单栏 --> <!-- 提交订单栏 -->
@ -197,7 +220,7 @@ async function handleSubmit() {
</div> </div>
</template> </template>
<style scoped> <style lang="scss" scoped>
.van-nav-bar { .van-nav-bar {
position: fixed; position: fixed;
top: 0; top: 0;
@ -253,15 +276,6 @@ async function handleSubmit() {
font-size: 13px; font-size: 13px;
} }
.method-icon {
font-size: 24px;
margin-right: 10px;
}
.method-label {
font-size: 15px;
}
.submit-bar { .submit-bar {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
@ -280,4 +294,67 @@ async function handleSubmit() {
color: #e95d5d; color: #e95d5d;
font-weight: bold; font-weight: bold;
} }
.payment-option {
display: flex;
align-items: center;
justify-content: flex-start;
margin: 8px 0;
border: 1px solid #ebedf0;
border-radius: 8px;
transition: all 0.2s;
:deep(.van-cell__value) {
display: flex;
}
}
.check-icon {
font-size: 18px;
}
.payment-option.selected {
border-color: #07c160;
background-color: #f6ffed;
}
.payment-option.disabled {
opacity: 0.6;
filter: grayscale(1);
pointer-events: none;
}
.method-icon {
vertical-align: middle;
margin-right: 12px;
font-size: 24px;
}
.method-label {
vertical-align: middle;
font-size: 15px;
}
.empty {
flex-grow: 1;
margin-right: 20px;
}
.balance-amount {
font-size: 13px;
color: #666;
}
.tel-input {
background-color: #f5f5f5;
border-radius: 4px;
padding: 8px 12px;
}
.check-icon {
margin-left: auto;
color: #07c160;
font-size: 18px;
}
</style> </style>

View File

@ -3,10 +3,12 @@ import { getOrdersByOpenIdApi } from "@@/apis/shop"
import type { ShopOrderEntity, ShopOrderGoodsEntity, Goods } from "@@/apis/shop/type" import type { ShopOrderEntity, ShopOrderGoodsEntity, Goods } from "@@/apis/shop/type"
export interface Order extends ShopOrderEntity { export interface Order extends ShopOrderEntity {
goodsList: Array<{ goodsList: Array<OrderGoods>
}
export interface OrderGoods {
goodsInfo: Goods goodsInfo: Goods
orderGoods: ShopOrderGoodsEntity orderGoods: ShopOrderGoodsEntity
}>
} }
export const useOrderStore = defineStore("order", () => { export const useOrderStore = defineStore("order", () => {

View File

@ -27,8 +27,6 @@ declare module 'vue' {
VanNavBar: typeof import('vant/es')['NavBar'] VanNavBar: typeof import('vant/es')['NavBar']
VanPicker: typeof import('vant/es')['Picker'] VanPicker: typeof import('vant/es')['Picker']
VanPopup: typeof import('vant/es')['Popup'] VanPopup: typeof import('vant/es')['Popup']
VanRadio: typeof import('vant/es')['Radio']
VanRadioGroup: typeof import('vant/es')['RadioGroup']
VanSidebar: typeof import('vant/es')['Sidebar'] VanSidebar: typeof import('vant/es')['Sidebar']
VanSidebarItem: typeof import('vant/es')['SidebarItem'] VanSidebarItem: typeof import('vant/es')['SidebarItem']
VanTabbar: typeof import('vant/es')['Tabbar'] VanTabbar: typeof import('vant/es')['Tabbar']