Files
uni-fans-score/pages/order/detail.vue
T

1324 lines
37 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="order-detail-container">
<!-- 顶部标题 -->
<view class="page-header">
<view class="header-title">{{ getOrderStatusText() }}</view>
<view class="header-desc">{{ getStatusDesc() }}</view>
</view>
<!-- 订单信息卡片 -->
<view class="info-card">
<view class="info-row">
<view class="info-col">
<view class="info-value-wrapper">
<text class="info-value-large">{{ getUsedTimeDisplay().number }}</text>
<text class="info-value-unit">{{ getUsedTimeDisplay().unit }}</text>
</view>
<view class="info-label">{{ getUsedTimeLabel() }}</view>
</view>
<view class="info-col">
<view class="info-value-wrapper">
<text class="info-value-large">{{ getOrderFee() }}</text>
<text class="info-value-unit">{{ $t('unit.yuan') }}</text>
</view>
<view class="info-label">{{ $t('order.totalAmount') }}</view>
</view>
</view>
<!-- <view class="fee-rule">
{{ feeRuleText }}
</view> -->
</view>
<!-- 租借信息卡片 -->
<view class="rent-card">
<view class="rent-title">{{ $t('order.rentInfo') }}</view>
<view class="rent-item">
<view class="rent-label">{{ $t('order.orderNo') }}</view>
<view class="rent-value">{{ orderInfo.orderNo || '-' }}</view>
</view>
<view class="rent-item">
<view class="rent-label">{{ $t('order.fanNo') }}</view>
<view class="rent-value">{{ deviceId || '-' }}</view>
</view>
<view class="rent-item">
<view class="rent-label">{{ $t('order.rentTime') }}</view>
<view class="rent-value">{{ orderInfo.startTime || '-' }}</view>
</view>
<view class="rent-item">
<view class="rent-label">{{ $t('order.rentLocation') }}</view>
<view class="rent-value">{{ orderInfo.positionName || '-' }}</view>
</view>
<view class="rent-item" v-if="orderInfo.freeRentTime&&orderInfo.freeRentTime!=='0'">
<view class="rent-label">{{ $t('order.freeRentTime') }}</view>
<view class="rent-value">{{ getFreeRentTimeDisplay() }}</view>
</view>
<view class="rent-item" v-if="orderInfo.unitPrice && orderInfo.orderType">
<view class="rent-label">{{ $t('order.pricingRule') }}</view>
<view class="rent-value">{{ getPricingRuleDisplay() }}</view>
</view>
<view class="rent-item">
<view class="rent-label">{{ $t('order.paymentMethod') }}</view>
<view class="rent-value">{{ getPayWayText() }}</view>
</view>
<view class="rent-item" v-if="isOrderCompleted() && orderInfo.endTime">
<view class="rent-label">{{ $t('order.returnTime') }}</view>
<view class="rent-value">{{ orderInfo.endTime }}</view>
</view>
<view class="rent-item" v-if="isOrderCompleted() && orderInfo.returnPosition">
<view class="rent-label">{{ $t('order.returnLocation') }}</view>
<view class="rent-value">{{ orderInfo.returnPosition || '-' }}</view>
</view>
<view class="rent-paid" v-if="isOrderCompleted()">
<text class="paid-label">{{ $t('order.paid') }}</text>
<text class="paid-value">{{ orderInfo.currentFee || orderInfo.payAmount || '10' }}</text>
<text class="paid-unit">{{ $t('unit.yuan') }}</text>
</view>
</view>
<view class="">
<view class="" style="font-size: 24rpx;text-align: center;">
产品归还入仓后订单仍未结束请前往<text @click="contactService" style="color:#07c160 ;">客服中心</text>联系工作人员
</view>
</view>
<!-- 转为自用提示 - 在使用中状态时显示 -->
<view v-if="orderInfo.orderStatus === 'in_used'" class="convert-tip" @click="handleConvertToOwned">
{{ $t('order.convertToOwn') }}
</view>
<!-- 底部操作栏 -->
<view class="bottom-bar">
<!-- 支付成功状态 -->
<template v-if="orderInfo.orderStatus === 'payment_successful'">
<view class="bottom-icon-btn" @click="contactService">
<image src="/static/customer-service.png" class="icon" mode="aspectFit"></image>
<text>{{ $t('user.customerService') }}</text>
</view>
<view class="bottom-icon-btn" @click="handleDeviceNoEject">
<image src="/static/complaint.png" class="icon" mode="aspectFit"></image>
<text>{{ $t('order.deviceNoEject') }}</text>
</view>
</template>
<!-- 使用中状态 -->
<template v-if="orderInfo.orderStatus === 'in_used'">
<view class="bottom-icon-btn" @click="contactService">
<image src="/static/customer-service.png" class="icon" mode="aspectFit"></image>
<text>{{ $t('user.customerService') }}</text>
</view>
<!-- 只有支持快递归还时才显示倒计时和快递归还按钮 -->
<template v-if="orderInfo.isSupportExpressReturn !== 'no'">
<view v-if="!showExpressAction" class="countdown-btn">
{{ formatCountdown(countdownRemaining) }}{{ $t('order.canExpressReturn') }}
</view>
<view v-if="showExpressAction" class="action-btn secondary" @click="expressRetrunOrder">
{{ $t('order.pauseBilling') }}
</view>
<view v-if="showExpressAction" class="action-btn primary" @click="quickReturn">
{{ $t('order.quickReturn') }}
</view>
</template>
<!-- 不支持快递归还时只显示快速归还按钮 -->
<view v-else class="action-btn primary" @click="quickReturn">
{{ $t('order.quickReturn') }}
</view>
<!-- 宝未弹出按钮 - 放在最后 -->
<view class="bottom-icon-btn" @click="handleDeviceNoEject">
<image src="/static/complaint.png" class="icon" mode="aspectFit"></image>
<text>{{ $t('order.deviceNoEject') }}</text>
</view>
</template>
<!-- 已完成状态 -->
<template v-if="isOrderCompleted()">
<view class="bottom-icon-btn" @click="handleWithdraw" v-if="!orderInfo.isWithdrawn && orderInfo.refundAmount > 0">
<image src="/static/suggess.png" class="icon" mode="aspectFit"></image>
<text>{{ $t('order.feeAppeal') }}</text>
</view>
<view class="bottom-icon-btn" @click="contactService">
<image src="/static/customer-service.png" class="icon" mode="aspectFit"></image>
<text>{{ $t('user.customerService') }}</text>
</view>
<view class="action-btn primary" @click="rentAgain">
{{ $t('order.rentAgain') }}
</view>
</template>
<!-- 待支付状态 -->
<template v-if="orderInfo.orderStatus === 'waiting_for_payment'">
<view class="action-btn secondary" @click="handleCancelOrder">{{ $t('order.cancelOrder') }}</view>
<view class="action-btn primary" @click="handlePayment">{{ $t('order.payNow') }}</view>
</template>
<!-- 已取消状态 -->
<template v-if="orderInfo.orderStatus === 'order_cancelled'">
<view class="action-btn primary full-width" @click="goToHome">{{ $t('order.backToHome') }}</view>
</template>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted, getCurrentInstance } from 'vue'
import { onLoad, onShow, onHide, onUnload } from '@dcloudio/uni-app'
import {
queryById,
cancelOrder,
reportDeviceNoEject,
convertToOwned
} from '@/config/api/order.js'
import {
addUserFeedback
} from '@/config/api/feedback.js'
import {
getSystemConfig
} from '@/config/api/system.js'
import {
URL
} from "@/config/url.js"
import { useI18n } from '@/utils/i18n.js'
const { t: $t } = useI18n()
const instance = getCurrentInstance()
const $orderMonitor = instance?.proxy?.$orderMonitor || null
// 响应式数据
const deviceId = ref('')
const orderInfo = ref({
orderId: '',
orderNo: '',
startTime: '',
endTime: '',
usedTime: '0分钟',
currentFee: '0.00',
orderStatus: '',
payWay: '',
depositAmount: '',
packageTime: '',
packagePrice: '',
payAmount: '0.00',
phone: '',
refundAmount: '0.00',
withdrawStatus: 'waiting',
isWithdrawn: false,
positionName: '',
returnPosition: '',
expressReturnStart: null,
isSupportExpressReturn: 'yes',
freeRentTime: '',
unitPrice: '',
orderType: ''
})
const timer = ref(null)
const statusCheckTimer = ref(null)
const maxStatusChecks = ref(60)
const currentStatusChecks = ref(0)
const statusCheckInterval = ref(3000)
const isPageActive = ref(false)
const isLoadingOrder = ref(false)
const expressThresholdSeconds = ref(180)
const countdownRemaining = ref(0)
const showExpressAction = ref(false)
const countdownTimer = ref(null)
const feeRuleText = ref('5.0元/60分钟 前15分钟内归还免费 不足60分钟按60分钟计费 封顶99元 持续计费至99元视为买断')
// 判断订单是否已完成
const isOrderCompleted = () => {
return orderInfo.value.orderStatus === 'used_done' ||
orderInfo.value.orderStatus === 'used_down'
}
// 获取订单状态文字
const getOrderStatusText = () => {
const statusMap = {
'waiting_for_payment': $t('order.waitingForPayment'),
'payment_in_progress': $t('order.paymentInProgress'),
'payment_successful': $t('order.paymentSuccess'),
'in_used': $t('order.inUse'),
'payment_failed': $t('order.paymentFailed'),
'order_cancelled': $t('order.cancelled'),
'used_done': $t('order.finished'),
'used_down': $t('order.finished')
}
return statusMap[orderInfo.value.orderStatus] || $t('order.orderDetail')
}
// 获取状态描述
const getStatusDesc = () => {
const descMap = {
'waiting_for_payment': $t('order.pleasePaySoon'),
'in_used': $t('order.pleaseReturnInTime'),
'used_done': $t('order.returnedThankYou'),
'used_down': $t('order.returnedThankYou'),
'order_cancelled': $t('order.orderCancelled'),
'payment_failed': $t('order.paymentFailedRetry')
}
return descMap[orderInfo.value.orderStatus] || ''
}
// 获取支付方式文本
const getPayWayText = () => {
const payWayMap = {
'wx_score_pay': $t('order.depositFree'),
'wx_member_pay': $t('order.memberOrder'),
'wx_pay': $t('order.depositPay')
}
return payWayMap[orderInfo.value.payWay] || $t('order.depositFree')
}
// 获取免费时长显示
const getFreeRentTimeDisplay = () => {
if (!orderInfo.value.freeRentTime) return '-'
return orderInfo.value.freeRentTime
}
// 获取计费规则显示
const getPricingRuleDisplay = () => {
if (!orderInfo.value.unitPrice || !orderInfo.value.orderType) return '-'
const orderTypeMap = {
'hours': $t('time.hours'),
'minutes': $t('time.minutes'),
'halfhours': $t('time.halfHours')
}
const orderTypeText = orderTypeMap[orderInfo.value.orderType] || orderInfo.value.orderType
return `${orderInfo.value.unitPrice}${$t('unit.yuan')}/${orderTypeText}`
}
// 格式化倒计时(显示为 HH:MM:SS 格式)
const formatCountdown = (totalSeconds) => {
if (typeof totalSeconds !== 'number' || totalSeconds < 0) return '00:00:00'
const hours = Math.floor(totalSeconds / 3600)
const minutes = Math.floor((totalSeconds % 3600) / 60)
const seconds = totalSeconds % 60
const pad = (n) => n.toString().padStart(2, '0')
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`
}
// 联系客服
const contactService = () => {
uni.navigateTo({
url: '/pages/help/index'
})
}
// 快速归还
const quickReturn = () => {
uni.navigateTo({
url: '/pages/search/index'
})
}
// 再次租借
const rentAgain = () => {
uni.reLaunch({
url: '/pages/index/index'
})
}
// 获取使用时长显示信息
const getUsedTimeDisplay = () => {
let usedTime = orderInfo.value.usedTime
// 如果 usedTime 为空,通过开始时间和结束时间计算
if (usedTime === '0分钟' && orderInfo.value.startTime && orderInfo.value.endTime) {
const startMs = parseStartTimeToMs(orderInfo.value.startTime)
const endMs = parseStartTimeToMs(orderInfo.value.endTime)
if (!isNaN(startMs) && !isNaN(endMs)) {
const diffMs = endMs - startMs
const totalMinutes = Math.floor(diffMs / (1000 * 60))
// 格式化为 "X小时X分钟" 或 "X分钟"
if (totalMinutes >= 60) {
const hours = Math.floor(totalMinutes / 60)
const minutes = totalMinutes % 60
usedTime = minutes > 0 ? `${hours}小时${minutes}分钟` : `${hours}小时`
} else {
usedTime = `${totalMinutes}分钟`
}
}
}
// 如果还是没有值,使用默认值
if (!usedTime) {
usedTime = '0分钟'
}
// 解析时长字符串,例如 "1小时5分钟" 或 "5分钟"
const hourMatch = usedTime.match(/(\d+)小时/)
const minuteMatch = usedTime.match(/(\d+)分钟/)
let displayNumber = ''
let displayUnit = ''
if (hourMatch && minuteMatch) {
// 有小时也有分钟,如 "1小时5分钟"
displayNumber = `${hourMatch[1]}`
displayUnit = `小时${minuteMatch[1]}分钟`
} else if (hourMatch) {
// 只有小时,如 "1小时"
displayNumber = hourMatch[1]
displayUnit = '小时'
} else if (minuteMatch) {
// 只有分钟,如 "5分钟"
displayNumber = minuteMatch[1]
displayUnit = '分钟'
} else {
// 默认情况
displayNumber = '0'
displayUnit = '分钟'
}
return {
number: displayNumber,
unit: displayUnit
}
}
// 获取使用时长标签文本
const getUsedTimeLabel = () => {
// 使用中状态显示"已使用",已完成状态显示"使用时长"
return orderInfo.value.orderStatus === 'in_used' ? $t('order.used') : $t('order.duration')
}
// 获取订单费用(不含单位)
const getOrderFee = () => {
const fee = orderInfo.value.currentFee || orderInfo.value.payAmount || '0'
// 移除可能的"元"字符
return String(fee).replace(/[元¥]/g, '')
}
// 解析开始时间
const parseStartTimeToMs = (timeStr) => {
if (!timeStr) return NaN
if (typeof timeStr === 'number') {
return timeStr < 1e12 ? timeStr * 1000 : timeStr
}
let normalized = String(timeStr).trim()
normalized = normalized.replace('T', ' ').replace(/\.\d+Z?$/, '')
const candidates = [
normalized,
normalized.replace(/-/g, '/')
]
for (let i = 0; i < candidates.length; i++) {
const ts = Date.parse(candidates[i])
if (!isNaN(ts)) return ts
}
const m = normalized.match(/(\d{4})[\/-](\d{1,2})[\/-](\d{1,2})\s+(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?/)
if (m) {
const y = parseInt(m[1])
const mon = parseInt(m[2]) - 1
const d = parseInt(m[3])
const h = parseInt(m[4])
const min = parseInt(m[5])
const s = m[6] ? parseInt(m[6]) : 0
return new Date(y, mon, d, h, min, s).getTime()
}
const num = Number(normalized)
if (!isNaN(num)) {
return num < 1e12 ? num * 1000 : num
}
return NaN
}
// 清除倒计时
const clearExpressCountdown = () => {
if (countdownTimer.value) {
clearInterval(countdownTimer.value)
countdownTimer.value = null
}
}
// 重新计算倒计时
const recomputeExpressCountdownFromStartTime = () => {
// 如果不支持快递归还,直接返回
if (orderInfo.value.isSupportExpressReturn === 'no') {
showExpressAction.value = false
countdownRemaining.value = 0
return
}
if (orderInfo.value.orderStatus !== 'in_used') {
showExpressAction.value = false
countdownRemaining.value = 0
return
}
const startMs = parseStartTimeToMs(orderInfo.value.startTime)
if (isNaN(startMs)) {
showExpressAction.value = false
countdownRemaining.value = 0
return
}
const nowMs = Date.now()
const elapsedSeconds = Math.max(0, Math.floor((nowMs - startMs) / 1000))
const remaining = expressThresholdSeconds.value - elapsedSeconds
if (remaining <= 0) {
countdownRemaining.value = 0
showExpressAction.value = true
} else {
countdownRemaining.value = remaining
showExpressAction.value = false
}
}
// 启动快递归还倒计时
const startExpressCountdown = () => {
// 如果不支持快递归还,直接返回
if (orderInfo.value.isSupportExpressReturn === 'no') {
console.log('订单不支持快递归还,不启动倒计时')
showExpressAction.value = false
countdownRemaining.value = 0
return
}
clearExpressCountdown()
recomputeExpressCountdownFromStartTime()
if (showExpressAction.value) return
countdownTimer.value = setInterval(() => {
if (!isPageActive.value) {
clearExpressCountdown()
return
}
if (orderInfo.value.orderStatus !== 'in_used') {
clearExpressCountdown()
return
}
// 再次检查是否支持快递归还
if (orderInfo.value.isSupportExpressReturn === 'no') {
clearExpressCountdown()
showExpressAction.value = false
countdownRemaining.value = 0
return
}
recomputeExpressCountdownFromStartTime()
if (showExpressAction.value) {
clearExpressCountdown()
}
}, 1000)
}
// 加载系统配置
const loadSystemConfig = async () => {
try {
// 优先使用订单数据中的 expressReturnStart(小时转秒)
if (orderInfo.value.expressReturnStart && typeof orderInfo.value.expressReturnStart === 'number' && orderInfo.value.expressReturnStart > 0) {
expressThresholdSeconds.value = orderInfo.value.expressReturnStart * 3600
console.log('使用订单配置的快递归还阈值:', orderInfo.value.expressReturnStart, '小时 =>', expressThresholdSeconds.value, '秒')
} else {
// 如果订单数据中没有,则使用系统配置
const res = await getSystemConfig()
if (res && res.code === 200 && res.data && typeof res.data.expressReturnCountdownSeconds === 'number') {
const seconds = res.data.expressReturnCountdownSeconds
if (seconds > 0) {
expressThresholdSeconds.value = seconds
console.log('使用系统配置的快递归还阈值:', seconds, '秒')
}
}
}
if (orderInfo.value.orderStatus === 'in_used' && orderInfo.value.startTime) {
recomputeExpressCountdownFromStartTime()
}
} catch (e) {
console.log('加载系统配置失败:', e)
}
}
// 从订单详情页角度不再主动移除订单监控
// 订单监控的生命周期完全交给全局 orderMonitor,在订单真正完成时自动移除
const removeFromOrderMonitor = () => {
// 保留函数以兼容已有调用,但不再从全局监控中删除订单
console.log('removeFromOrderMonitor 调用:已不再从全局监控中移除订单,交由 orderMonitor 在订单完成时处理')
}
// 处理订单完成事件
const handleOrderCompleted = (orderData) => {
console.log('收到订单完成事件:', orderData)
if (orderData.orderId === orderInfo.value.orderId || orderData.orderNo === orderInfo.value.orderNo) {
// 刷新订单详情
getOrderDetails()
}
}
// 清除定时器
const clearTimer = () => {
if (timer.value) {
clearInterval(timer.value)
timer.value = null
console.log('已清除使用时长更新计时器')
}
}
// 清除状态检查定时器
const clearStatusCheckTimer = () => {
if (statusCheckTimer.value) {
clearInterval(statusCheckTimer.value)
statusCheckTimer.value = null
console.log('已清除归还状态检查计时器')
}
}
// 更新使用时长的定时器(每30秒更新一次)
const startTimer = () => {
clearTimer()
timer.value = setInterval(() => {
if (!isPageActive.value) {
console.log('页面已不活跃,停止计时器')
clearTimer()
return
}
if (orderInfo.value.orderStatus !== 'in_used') {
console.log('订单状态已变更,停止计时器')
clearTimer()
return
}
console.log('执行定时更新订单信息')
getOrderDetails()
}, 30000) // 改为30秒更新一次
console.log('已启动使用时长更新计时器(30秒间隔)')
}
// 开始状态检查定时器(优化版)
const startStatusCheckTimer = () => {
currentStatusChecks.value = 0
clearStatusCheckTimer()
statusCheckTimer.value = setInterval(() => {
if (!isPageActive.value) {
console.log('页面已不活跃,停止状态检查计时器')
clearStatusCheckTimer()
return
}
if (orderInfo.value.orderStatus !== 'in_used') {
console.log('订单状态已变更,停止状态检查计时器')
clearStatusCheckTimer()
return
}
currentStatusChecks.value++
console.log(`执行归还状态检查 (${currentStatusChecks.value}/${maxStatusChecks.value})`)
checkReturnStatus()
if (currentStatusChecks.value >= maxStatusChecks.value) {
clearStatusCheckTimer()
uni.showToast({
title: $t('order.pleaseRefreshManually'),
icon: 'none',
duration: 3000
})
}
}, statusCheckInterval.value)
console.log('已启动归还状态检查计时器(3秒间隔)')
}
// 检查归还状态
const checkReturnStatus = async () => {
try {
if (isPageActive.value) {
await getOrderDetails()
}
} catch (error) {
console.error('检查归还状态失败:', error)
}
}
// 更新订单信息
const updateOrderInfo = (orderData) => {
const oldStatus = orderInfo.value.orderStatus
orderInfo.value.usedTime = orderData.usedTime || '0分钟'
orderInfo.value.currentFee = orderData.currentFee || orderData.actualDeviceAmount || orderData.payAmount || '0.00'
orderInfo.value.orderStatus = orderData.orderStatus || ''
orderInfo.value.payWay = orderData.payWay || ''
orderInfo.value.startTime = orderData.startTime || orderData.createTime || ''
orderInfo.value.endTime = orderData.endTime || ''
orderInfo.value.depositAmount = orderData.depositAmount || ''
orderInfo.value.packageTime = orderData.packageTime || ''
orderInfo.value.packagePrice = orderData.packagePrice || ''
orderInfo.value.payAmount = orderData.payAmount || '0.00'
orderInfo.value.phone = orderData.phone || ''
orderInfo.value.orderNo = orderData.orderNo || ''
orderInfo.value.refundAmount = orderData.residueAmount || '0.00'
orderInfo.value.withdrawStatus = orderData.withdrawStatus || 'waiting'
orderInfo.value.isWithdrawn = orderData.withdrawStatus === 'success'
orderInfo.value.positionName = orderData.positionName || orderData.positionLocation || ''
orderInfo.value.returnPosition = orderData.returnPosition || orderData.positionName || orderData.positionLocation || ''
orderInfo.value.freeRentTime = orderData.freeRentTime || ''
orderInfo.value.unitPrice = orderData.unitPrice || ''
orderInfo.value.orderType = orderData.orderType || ''
// 保存快递归还开始时间(小时为单位)
orderInfo.value.expressReturnStart = orderData.expressReturnStart || null
// 保存是否支持快递归还
orderInfo.value.isSupportExpressReturn = orderData.isSupportExpressReturn || 'yes'
// 如果有有效的 expressReturnStart,立即更新倒计时阈值(小时转秒)
if (orderInfo.value.expressReturnStart && typeof orderInfo.value.expressReturnStart === 'number' && orderInfo.value.expressReturnStart > 0) {
expressThresholdSeconds.value = orderInfo.value.expressReturnStart * 3600
console.log('从订单数据更新快递归还阈值:', orderInfo.value.expressReturnStart, '小时 =>', expressThresholdSeconds.value, '秒')
}
if (orderData.deviceNo && !deviceId.value) {
deviceId.value = orderData.deviceNo
}
// 如果订单状态从 'in_used' 变为其他状态,清理所有定时器
if (oldStatus === 'in_used' && orderInfo.value.orderStatus !== 'in_used') {
console.log('订单状态已从使用中变为:', orderInfo.value.orderStatus, ',清理所有定时器')
clearTimer()
clearStatusCheckTimer()
clearExpressCountdown()
removeFromOrderMonitor()
// 显示订单完成提示
if (orderInfo.value.orderStatus === 'used_done' || orderInfo.value.orderStatus === 'used_down') {
uni.showToast({
title: $t('order.orderCompleted'),
icon: 'success'
})
}
}
}
// 获取订单详情
const getOrderDetails = async () => {
if (!isPageActive.value) {
console.log('页面已不活跃,跳过订单详情请求')
return
}
// 防止并发请求
if (isLoadingOrder.value) {
console.log('正在加载订单,跳过重复请求')
return
}
try {
if (!orderInfo.value.orderId) {
throw new Error($t('order.orderIdRequired'))
}
isLoadingOrder.value = true
const result = await queryById(orderInfo.value.orderId)
if (result.code === 200 && result.data) {
const orderData = result.data
console.log('订单数据:', orderData)
// 更新订单信息
updateOrderInfo(orderData)
// 只有使用中的订单才启动本页的计时器逻辑
if (orderInfo.value.orderStatus === 'in_used') {
startTimer()
startStatusCheckTimer()
// 只有支持快递归还时才启动倒计时
if (orderInfo.value.isSupportExpressReturn !== 'no') {
startExpressCountdown()
}
}
// 无论当前订单状态如何,只要进入订单详情页,就把当前订单交给全局订单监控
if (orderInfo.value.orderId) {
uni.setStorageSync('activeOrderId', orderInfo.value.orderId)
// 添加/更新到监控队列
try {
if ($orderMonitor) {
$orderMonitor.removeOrder({
orderId: orderInfo.value.orderId
})
$orderMonitor.addOrder({
orderId: orderInfo.value.orderId
}, 'detail')
console.log('订单已添加到监控队列(无论当前状态):', orderInfo.value.orderId, '当前状态:', orderInfo.value.orderStatus)
} else {
console.warn('$orderMonitor 未定义,无法添加订单到监控队列')
}
} catch (error) {
console.error('添加订单到监控队列失败:', error)
}
}
} else {
throw new Error(result.msg || $t('order.getOrderFailed'))
}
} catch (error) {
console.error('获取订单详情错误:', error)
uni.showToast({
title: error.message || $t('order.getOrderFailed'),
icon: 'none'
})
setTimeout(() => {
goToHome()
}, 1500)
} finally {
isLoadingOrder.value = false
uni.hideLoading()
}
}
// 通过设备号查询使用中的订单
const getOrderByDevice = async () => {
try {
if (!deviceId.value) {
throw new Error($t('device.deviceNoRequired'))
}
const inUseRes = await uni.request({
url: `${URL || 'http://127.0.0.1:8080'}/app/order/inUse`,
method: 'GET',
header: {
'Authorization': "Bearer " + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
}
})
console.log('通过设备号查询订单结果:', JSON.stringify(inUseRes))
if (inUseRes.statusCode === 200 && inUseRes.data.code === 200 && inUseRes.data.data) {
const inUseOrder = inUseRes.data.data
console.log('使用中的订单:', inUseOrder)
orderInfo.value.orderId = inUseOrder.orderId
orderInfo.value.orderStatus = inUseOrder.orderStatus || ''
orderInfo.value.payWay = inUseOrder.payWay || ''
orderInfo.value.startTime = inUseOrder.startTime || ''
getOrderDetails()
} else {
throw new Error($t('order.noOrderInUse'))
}
} catch (error) {
console.error('通过设备号查询订单失败:', error)
uni.showToast({
title: error.message || $t('order.getOrderFailed'),
icon: 'none'
})
setTimeout(() => {
goToHome()
}, 1500)
} finally {
uni.hideLoading()
}
}
// 快递归还
const expressRetrunOrder = () => {
uni.navigateTo({
url: `/pages/expressReturn/addExpressReturn?orderId=${orderInfo.value.orderId}`
})
}
// 取消订单
const handleCancelOrder = () => {
uni.showModal({
title: $t('order.confirmCancel'),
content: $t('order.confirmCancelContent'),
success: async (res) => {
if (res.confirm) {
try {
uni.showLoading({
title: $t('common.processing')
})
const result = await cancelOrder({
orderId: orderInfo.value.orderId
})
if (result.code === 200) {
uni.hideLoading()
uni.showToast({
title: $t('order.cancelSuccess'),
icon: 'success'
})
await getOrderDetails()
} else {
throw new Error(result.msg || $t('order.cancelFailed'))
}
} catch (error) {
uni.hideLoading()
uni.showToast({
title: error.message || $t('order.cancelFailed'),
icon: 'none'
})
}
}
}
})
}
// 立即支付
const handlePayment = () => {
uni.navigateTo({
url: `/pages/order/payment?orderId=${orderInfo.value.orderId}`
})
}
// 申请退款
const handleWithdraw = async () => {
try {
uni.showLoading({
title: $t('common.processing')
})
const res = await uni.request({
url: `${URL || 'http://127.0.0.1:8080'}/app/withdraw/add/${orderInfo.value.orderNo}`,
method: 'GET',
header: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + uni.getStorageSync('token'),
'Clientid': uni.getStorageSync('client_id')
}
})
if (res.statusCode === 200 && res.data.code === 200) {
uni.showToast({
title: $t('order.refundSuccess'),
icon: 'success'
})
orderInfo.value.withdrawStatus = 'processing'
orderInfo.value.isWithdrawn = true
setTimeout(() => {
getOrderDetails()
}, 1500)
} else {
throw new Error(res.data.msg || $t('order.refundFailed'))
}
} catch (error) {
console.error('退款申请错误:', error)
uni.showToast({
title: error.message || $t('order.refundFailed'),
icon: 'none'
})
} finally {
uni.hideLoading()
}
}
// 返回首页
const goToHome = () => {
uni.reLaunch({
url: '/pages/index/index'
})
}
// 处理"宝未弹出"反馈
const handleDeviceNoEject = () => {
uni.showModal({
title: $t('order.deviceNoEjectTitle'),
content: $t('order.deviceNoEjectConfirm'),
confirmText: $t('common.confirm'),
cancelText: $t('common.cancel'),
success: async (res) => {
if (res.confirm) {
try {
uni.showLoading({
title: $t('common.submitting')
})
// 调用反馈API提交工单
const feedbackData = {
type: 'device_issue',
content: `订单号:${orderInfo.value.orderNo}\n设备号:${deviceId.value}\n问题:充电宝未弹出`,
phone: orderInfo.value.phone || '',
orderId: orderInfo.value.orderId,
orderNo: orderInfo.value.orderNo
}
const result = await addUserFeedback(feedbackData)
if (result.code === 200) {
uni.hideLoading()
uni.showToast({
title: $t('order.deviceNoEjectSuccess'),
icon: 'success',
duration: 3000
})
} else {
throw new Error(result.msg || $t('order.deviceNoEjectFailed'))
}
} catch (error) {
uni.hideLoading()
uni.showToast({
title: error.message || $t('order.deviceNoEjectFailed'),
icon: 'none',
duration: 2000
})
}
}
}
})
}
// 处理"转为自用"
const handleConvertToOwned = () => {
uni.showModal({
title: $t('order.convertToOwnTitle'),
content: $t('order.convertToOwnConfirm'),
confirmText: $t('common.confirm'),
cancelText: $t('common.cancel'),
success: async (res) => {
if (res.confirm) {
try {
uni.showLoading({
title: $t('common.processing')
})
const result = await convertToOwned(orderInfo.value.orderId)
if (result.code === 200) {
uni.hideLoading()
uni.showToast({
title: $t('order.convertToOwnSuccess'),
icon: 'success',
duration: 2000
})
// 刷新订单详情
setTimeout(() => {
getOrderDetails()
}, 2000)
} else {
throw new Error(result.msg || $t('order.convertToOwnFailed'))
}
} catch (error) {
uni.hideLoading()
uni.showToast({
title: error.message || $t('order.convertToOwnFailed'),
icon: 'none',
duration: 2000
})
}
}
}
})
}
// 生命周期钩子
onLoad((options) => {
console.log('订单详情页加载,参数:', JSON.stringify(options))
// 设置页面标题
uni.setNavigationBarTitle({
title: $t('order.orderDetail')
})
isPageActive.value = true
// 从缓存读取通知内容(计费规则)
try {
const cachedNotice = uni.getStorageSync('noticeContent')
if (cachedNotice) {
console.log('从缓存读取计费规则:', cachedNotice)
feeRuleText.value = cachedNotice
}
} catch (e) {
console.warn('读取缓存通知内容失败:', e)
}
orderInfo.value.orderId = options.orderId || ''
deviceId.value = options.deviceNo || options.deviceId || ''
if (!orderInfo.value.orderId && deviceId.value) {
getOrderByDevice()
} else if (orderInfo.value.orderId) {
getOrderDetails()
} else {
uni.showToast({
title: $t('order.orderInfoMissing'),
icon: 'none'
})
setTimeout(() => {
goToHome()
}, 1500)
}
// 注册全局订单完成事件监听器
uni.$on('orderCompleted', handleOrderCompleted)
})
onShow(() => {
isPageActive.value = true
if (orderInfo.value.orderStatus === 'in_used' && orderInfo.value.isSupportExpressReturn !== 'no') {
startExpressCountdown()
}
})
onHide(() => {
console.log('订单详情页隐藏,清理资源')
isPageActive.value = false
clearTimer()
clearStatusCheckTimer()
clearExpressCountdown()
removeFromOrderMonitor()
})
onUnload(() => {
console.log('订单详情页卸载,清理所有资源')
isPageActive.value = false
clearTimer()
clearStatusCheckTimer()
clearExpressCountdown()
removeFromOrderMonitor()
uni.$off('orderCompleted', handleOrderCompleted)
})
</script>
<style lang="scss" scoped>
.order-detail-container {
min-height: 100vh;
background: #f7f8fa;
padding: 30rpx;
padding-bottom: 180rpx;
box-sizing: border-box;
// 顶部标题
.page-header {
margin-bottom: 30rpx;
.header-title {
font-size: 48rpx;
font-weight: bold;
color: #333;
margin-bottom: 12rpx;
}
.header-desc {
font-size: 28rpx;
color: #999;
}
}
// 订单信息卡片
.info-card {
background: #fff;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
.info-row {
display: flex;
justify-content: space-between;
// margin-bottom: 20rpx;
.info-col {
flex: 1;
text-align: center;
.info-value-wrapper {
display: flex;
align-items: baseline;
justify-content: center;
margin-bottom: 8rpx;
.info-value-large {
font-size: 48rpx;
font-weight: bold;
color: #333;
line-height: 1;
}
.info-value-unit {
font-size: 24rpx;
color: #666;
margin-left: 4rpx;
}
}
.info-label {
font-size: 24rpx;
color: #999;
}
}
}
.fee-rule {
background: #E8F5E9;
border-radius: 8rpx;
padding: 16rpx;
font-size: 24rpx;
color: #4CAF50;
line-height: 1.6;
}
}
// 转为自用提示
.convert-tip {
position: fixed;
left: 30rpx;
right: 30rpx;
bottom: calc(120rpx + env(safe-area-inset-bottom));
color: #000;
text-align: center;
padding: 20rpx 30rpx;
border-radius: 12rpx;
font-size: 26rpx;
z-index: 9;
&:active {
opacity: 0.85;
}
}
// 租借信息卡片
.rent-card {
background: #fff;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
.rent-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 24rpx;
}
.rent-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 0;
border-bottom: 1rpx solid #f0f0f0;
&:last-of-type {
border-bottom: none;
}
.rent-label {
font-size: 28rpx;
color: #999;
}
.rent-value {
font-size: 28rpx;
color: #333;
text-align: right;
max-width: 400rpx;
}
}
.rent-paid {
display: flex;
justify-content: flex-end;
align-items: baseline;
padding-top: 20rpx;
margin-top: 10rpx;
border-top: 1rpx solid #f0f0f0;
.paid-label {
font-size: 28rpx;
color: #999;
margin-right: 8rpx;
}
.paid-value {
font-size: 48rpx;
font-weight: bold;
color: #ff6b6b;
}
.paid-unit {
font-size: 28rpx;
color: #ff6b6b;
margin-left: 4rpx;
}
}
}
// 底部操作栏
.bottom-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
background: #fff;
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.04);
z-index: 10;
display: flex;
justify-content: space-between;
align-items: center;
gap: 20rpx;
.bottom-icon-btn {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-width: 100rpx;
.icon {
width: 48rpx;
height: 48rpx;
margin-bottom: 8rpx;
}
text {
font-size: 24rpx;
color: #666;
}
&:active {
opacity: 0.7;
}
}
.countdown-btn {
flex: 1;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: #07c160;
background: #E8F5E9;
border-radius: 44rpx;
border: 2rpx solid #07c160;
}
.action-btn {
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 30rpx;
border-radius: 44rpx;
padding: 0 40rpx;
white-space: nowrap;
&.full-width {
flex: 1;
}
&.primary {
background: #07c160;
color: #fff;
flex: 1;
&:active {
opacity: 0.8;
}
}
&.secondary {
background: #f5f5f5;
color: #333;
border: 1rpx solid #e0e0e0;
&:active {
opacity: 0.8;
}
}
}
}
}
</style>