完善下单逻辑及其ui展示,修复支付倒计时显示错误bug

This commit is contained in:
2026-05-25 15:35:41 +08:00
parent d25f80ccdd
commit cecb51a8e2
40 changed files with 13040 additions and 3217 deletions

View File

@@ -9,6 +9,7 @@
<view class="status-title-row">
<text class="status-emoji">{{ getStatusIcon() }}</text>
<text class="status-text">{{ getStatusText() }}</text>
<text v-if="getPendingCountdownText() != ''" class="status-countdown">{{ getPendingCountdownText() }}</text>
</view>
<text class="status-desc">{{ getStatusDesc() }}</text>
</view>
@@ -135,7 +136,7 @@
</view>
</view>
<view class="action-right">
<view v-if="order?.order_status === 1" class="btn-group">
<view v-if="order?.order_status === 1 && !isTimeoutOrder()" class="btn-group">
<button class="btn" @click="cancelOrder">取消订单</button>
<button class="btn primary" @click="payOrder">立即支付</button>
</view>
@@ -157,9 +158,11 @@
<button class="btn primary" @click="goToReview">评价订单</button>
</view>
<view v-if="order?.order_status === 5" class="btn-group">
<button class="btn primary" @click="rePurchase">重新购买</button>
</view>
<view v-if="shouldShowCancelledActions()" class="btn-group">
<button class="btn" @click="deleteOrder">删除订单</button>
<button class="btn" @click="viewSimilar">看相似</button>
<button class="btn primary" @click="rePurchase">再次购买</button>
</view>
</view>
</view>
</view>
@@ -167,16 +170,21 @@
</template>
<script setup lang="uts">
import { ref, onMounted, computed } from 'vue'
import { onLoad, onBackPress } from '@dcloudio/uni-app'
import { ref } from 'vue'
import { onBackPress, onHide, onLoad, onShow, onUnload } from '@dcloudio/uni-app'
import { supabaseService } from '@/utils/supabaseService.uts'
import { goToLogin } from '@/utils/utils.uts'
import supa from '@/components/supadb/aksupainstance.uts'
import { formatCountdownHMS, getOrderDisplayStatus, getRemainingSeconds, isOrderPayExpired, ORDER_STATUS_CANCELLED, ORDER_STATUS_PENDING, ORDER_TIMEOUT_CANCEL_REASON, PAYMENT_STATUS_TIMEOUT, PAYMENT_STATUS_UNPAID, type OrderStatusSource } from '@/utils/orderStatus.uts'
// 定义订单类型
type OrderType = {
order_no: string,
order_status: number,
payment_status: number,
cancel_reason: string,
pay_expire_at: string,
consumer_deleted_at: string,
total_amount: number,
product_amount: number,
shipping_fee: number,
@@ -221,10 +229,51 @@ const orderItems = ref<OrderItemType[]>([])
const shopName = ref('店铺名称')
const deliveryAddress = ref<AddressType | null>(null)
const deliveryInfo = ref<DeliveryInfoType | null>(null)
const nowTick = ref<number>(Date.now())
let detailTicker = 0
const toOrderStatusSource = (): OrderStatusSource | null => {
const currentOrder = order.value
if (currentOrder == null) return null
return {
order_status: currentOrder.order_status,
payment_status: currentOrder.payment_status,
pay_expire_at: currentOrder.pay_expire_at,
created_at: currentOrder.created_at,
cancel_reason: currentOrder.cancel_reason
}
}
const isTimeoutOrder = (): boolean => {
const source = toOrderStatusSource()
if (source == null) return false
return isOrderPayExpired(source)
}
const getPendingCountdownText = (): string => {
const source = toOrderStatusSource()
if (source == null) return ''
const currentTick = nowTick.value
if (currentTick < 0) return ''
if (getOrderDisplayStatus(source) != 'pending') return ''
return formatCountdownHMS(getRemainingSeconds(source))
}
const shouldShowCancelledActions = (): boolean => {
const source = toOrderStatusSource()
if (source == null) return false
return getOrderDisplayStatus(source) == 'cancelled'
}
// 辅助函数 - 必须在调用前定义
const getStatusText = (): string => {
const status = order.value?.order_status ?? 0
const source = toOrderStatusSource()
if (source != null) {
const displayStatus = getOrderDisplayStatus(source)
if (displayStatus == 'pending') return '待付款'
if (displayStatus == 'cancelled') return '已取消'
}
if (status == 1) return '待付款'
if (status == 2) return '待发货'
if (status == 3) return '待收货'
@@ -237,6 +286,20 @@ const getStatusText = (): string => {
const getStatusDesc = (): string => {
const status = order.value?.order_status ?? 0
const source = toOrderStatusSource()
if (source != null) {
const displayStatus = getOrderDisplayStatus(source)
if (displayStatus == 'pending') {
return '请在 ' + getPendingCountdownText() + ' 内支付'
}
if (displayStatus == 'cancelled') {
const currentReason = order.value?.cancel_reason ?? ''
if (currentReason.indexOf('超时') >= 0) {
return '订单超时未支付,已自动取消'
}
return '订单已取消'
}
}
if (status == 1) return '请尽快完成支付'
if (status == 2) return '商家正在打包商品'
if (status == 3) return '商品正在赶往您的地址'
@@ -249,6 +312,7 @@ const getStatusDesc = (): string => {
const getStatusIcon = (): string => {
const status = order.value?.order_status ?? 0
if (shouldShowCancelledActions()) return '⏰'
if (status === 1) return '💳'
if (status === 2) return '📦'
if (status === 3) return '🚚'
@@ -257,8 +321,14 @@ const getStatusIcon = (): string => {
}
const getStatusClass = (): string => {
const source = toOrderStatusSource()
if (source != null) {
const displayStatus = getOrderDisplayStatus(source)
if (displayStatus == 'pending') return 'status-pending'
if (displayStatus == 'cancelled') return 'status-cancelled'
}
const status = order.value?.order_status ?? 0
return `status-${status}`
return 'status-' + status
}
const getFullAddress = (addr: any): string => {
@@ -389,6 +459,10 @@ const loadOrderDetail = async () => {
order.value = {
order_no: (dataObj.get('order_no') ?? '') as string,
order_status: (dataObj.get('order_status') ?? 1) as number,
payment_status: (dataObj.get('payment_status') ?? 1) as number,
cancel_reason: (dataObj.get('cancel_reason') ?? '') as string,
pay_expire_at: (dataObj.get('pay_expire_at') ?? '') as string,
consumer_deleted_at: (dataObj.get('consumer_deleted_at') ?? '') as string,
total_amount: (dataObj.get('total_amount') ?? 0) as number,
product_amount: (dataObj.get('product_amount') ?? 0) as number,
shipping_fee: (dataObj.get('shipping_fee') ?? 0) as number,
@@ -424,6 +498,15 @@ const loadOrderDetail = async () => {
orderItems.value.push(orderItem)
}
}
if (order.value.consumer_deleted_at != '') {
order.value = null
uni.showToast({ title: '订单不存在', icon: 'none' })
setTimeout(() => {
uni.redirectTo({ url: '/pages/mall/consumer/orders' })
}, 600)
return
}
const addressRaw = dataObj.get('shipping_address')
console.log('[loadOrderDetail] 收货地址数据:', addressRaw)
@@ -470,6 +553,7 @@ const loadOrderDetail = async () => {
}
console.log('[loadOrderDetail] 订单详情加载成功,商品数量:', orderItems.value.length)
syncTimeoutState()
} else {
uni.showToast({ title: '订单不存在', icon: 'none' })
}
@@ -481,6 +565,35 @@ const loadOrderDetail = async () => {
}
}
const syncTimeoutState = (): void => {
nowTick.value = Date.now()
const currentOrder = order.value
if (currentOrder == null) return
if (currentOrder.order_status == ORDER_STATUS_PENDING && currentOrder.payment_status == PAYMENT_STATUS_UNPAID && isTimeoutOrder()) {
currentOrder.order_status = ORDER_STATUS_CANCELLED
currentOrder.payment_status = PAYMENT_STATUS_TIMEOUT
if (currentOrder.cancel_reason == '') {
currentOrder.cancel_reason = ORDER_TIMEOUT_CANCEL_REASON
}
supabaseService.expireOrder(orderId.value)
}
}
const startDetailTicker = (): void => {
if (detailTicker > 0) return
syncTimeoutState()
detailTicker = setInterval(() => {
syncTimeoutState()
}, 1000)
}
const stopDetailTicker = (): void => {
if (detailTicker > 0) {
clearInterval(detailTicker)
detailTicker = 0
}
}
// 动作函数
const contactService = () => {
const userId = supabaseService.getCurrentUserId()
@@ -508,13 +621,49 @@ const contactService = () => {
}
}
const payOrder = () => {
const payOrder = async () => {
if (isTimeoutOrder()) {
uni.showToast({ title: '订单已取消,不能继续支付', icon: 'none' })
loadOrderDetail()
return
}
const totalAmount = order.value?.total_amount ?? 0
const userId = supabaseService.getCurrentUserId()
if (userId == null || userId === '') {
goToLogin(`/pages/mall/consumer/payment?orderId=${orderId.value}&amount=${totalAmount}`)
return
}
const latestOrder = await supabaseService.getOrderDetail(orderId.value)
if (latestOrder != null) {
const latestObj = JSON.parse(JSON.stringify(latestOrder)) as UTSJSONObject
const latestStatus = latestObj.getNumber('order_status') ?? 1
const latestPaymentStatus = latestObj.getNumber('payment_status') ?? 1
const latestCancelReason = latestObj.getString('cancel_reason') ?? ''
const latestPayExpireAt = latestObj.getString('pay_expire_at') ?? ''
if (order.value != null) {
order.value.order_status = latestStatus
order.value.payment_status = latestPaymentStatus
order.value.cancel_reason = latestCancelReason
order.value.pay_expire_at = latestPayExpireAt
}
if (isTimeoutOrder()) {
await supabaseService.expireOrder(orderId.value)
if (order.value != null) {
order.value.order_status = ORDER_STATUS_CANCELLED
order.value.payment_status = PAYMENT_STATUS_TIMEOUT
order.value.cancel_reason = ORDER_TIMEOUT_CANCEL_REASON
}
uni.showToast({ title: '订单已取消,不能继续支付', icon: 'none' })
return
}
if (latestStatus != ORDER_STATUS_PENDING || latestPaymentStatus != PAYMENT_STATUS_UNPAID) {
uni.showToast({ title: '订单状态已变更,不能继续支付', icon: 'none' })
return
}
}
uni.navigateTo({
url: `/pages/mall/consumer/payment?orderId=${orderId.value}&amount=${totalAmount}`
})
@@ -522,23 +671,14 @@ const payOrder = () => {
const doCancelOrder = async () => {
try {
const updatePayload = new UTSJSONObject()
updatePayload.set('order_status', 5)
updatePayload.set('updated_at', new Date().toISOString())
const result = await supa
.from('ml_orders')
.update(updatePayload)
.eq('id', orderId.value)
.execute()
if (result.error == null) {
const result = await supabaseService.cancelOrder(orderId.value)
if (result) {
if (order.value != null) {
order.value.order_status = 5
order.value.order_status = ORDER_STATUS_CANCELLED
order.value.payment_status = PAYMENT_STATUS_TIMEOUT
}
uni.showToast({ title: '订单已取消' })
} else {
console.error('[doCancelOrder] 取消订单失败:', result.error)
uni.showToast({ title: '取消失败', icon: 'none' })
}
} catch (e) {
@@ -547,6 +687,34 @@ const doCancelOrder = async () => {
}
}
const deleteOrder = async () => {
uni.showLoading({ title: '删除中...' })
try {
const success = await supabaseService.softDeleteOrderForConsumer(orderId.value)
uni.hideLoading()
if (!success) {
uni.showToast({ title: '删除失败', icon: 'none' })
return
}
uni.showToast({ title: '订单已删除', icon: 'success' })
setTimeout(() => {
uni.redirectTo({ url: '/pages/mall/consumer/orders' })
}, 600)
} catch (e) {
uni.hideLoading()
uni.showToast({ title: '删除失败', icon: 'none' })
}
}
const viewSimilar = () => {
if (orderItems.value.length == 0) {
uni.showToast({ title: '暂无相似商品', icon: 'none' })
return
}
const keyword = orderItems.value[0].product_name != '' ? orderItems.value[0].product_name : '商品'
uni.navigateTo({ url: `/pages/mall/consumer/search?keyword=${encodeURIComponent(keyword)}` })
}
const cancelOrder = () => {
uni.showModal({
title: '提示',
@@ -801,6 +969,21 @@ onLoad((options) => {
}
})
onShow(() => {
if (orderId.value != '') {
loadOrderDetail()
}
startDetailTicker()
})
onHide(() => {
stopDetailTicker()
})
onUnload(() => {
stopDetailTicker()
})
</script>
<style scoped>
@@ -868,12 +1051,26 @@ onLoad((options) => {
letter-spacing: 1px;
}
.status-countdown {
font-size: 18px;
font-weight: bold;
margin-left: 8px;
}
.status-desc {
font-size: 14px;
opacity: 0.95;
text-align: center;
}
.status-pending {
background: linear-gradient(135deg, #ff9000, #ff5000);
}
.status-cancelled {
background: linear-gradient(135deg, #9aa5b1, #6b7280);
}
/* 分享免单入口 */
.share-free-entry {
margin-top: 20px;