Files
uni-fans-score/pages/order/detail.vue
T
2026-03-16 11:52:27 +08:00

1761 lines
47 KiB
Vue

<template>
<view class="order-detail-container">
<!-- 顶部标题 -->
<view class="page-header">
<view class="header-top">
<view class="header-left">
<view class="header-title-row">
<text class="header-title">{{ getOrderStatusText() }}</text>
<!-- 优惠券/会员卡可用提示标签 -->
<view v-if="orderInfo.orderStatus === 'in_used' && canUsePromotionTag" class="promotion-tag">
{{ $t('order.canUsePromotion') }}
</view>
</view>
<view class="header-desc">{{ getStatusDesc() }}</view>
</view>
<view class="header-right" v-if="orderInfo.orderStatus === 'in_used'">
<view class="device-no-eject-btn" @click="handleDeviceNoEject">
<image src="/static/power_no_popout.png" class="device-no-eject-icon" mode="aspectFit" lazy-load="true"></image>
<text class="device-no-eject-text">{{ $t('order.deviceNoEject') }}</text>
</view>
</view>
</view>
</view>
<!-- 订单信息卡片 -->
<view class="info-card">
<view class="info-row">
<view class="info-left">
<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="info-right" v-if="orderInfo.orderStatus === 'in_used'">
<view class="return-reminder-btn" @click="handleReturnReminderSubscribe">
<uv-icon name="bell" size="20" color="#FFF"></uv-icon>
<text class="return-reminder-text">{{ $t('order.returnReminder') }}</text>
</view>
</view>
</view>
<!-- 计费规则图片 -->
<view class="fee-rule-image">
<image :src="getFeeRuleImageUrl()" mode="widthFix" class="rule-image" lazy-load="true"></image>
</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.discountTypeName">
<view class="rent-label">{{ $t('order.usedPromotion') }}</view>
<view class="rent-value promotion-value">
<image src="/static/promotion-icon.png" class="promotion-icon" mode="aspectFit" lazy-load="true"></image>
{{ orderInfo.discountTypeName }}<text
v-if="orderInfo.discountAmount">{{'-¥'+orderInfo.discountAmount||''}}</text>
</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;">
{{ $t('order.returnProblemTip') }}<text @click="contactService"
style="color:#07c160 ;">{{ $t('user.customerService') }}</text>{{ $t('order.contactStaff') }}
</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" lazy-load="true"></image>
<text>{{ $t('user.customerService') }}</text>
</view>
<view class="bottom-icon-btn" @click="handleDeviceNoEject">
<image src="/static/complaint.png" class="icon" mode="aspectFit" lazy-load="true"></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" lazy-load="true"></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="action-btn convert-btn" @click="handleConvertToOwnedWithMaxFee">
{{ $t('order.convertToOwnWithMaxFee') }}
</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" lazy-load="true"></image>
<text>{{ $t('order.feeAppeal') }}</text>
</view>
<view class="bottom-icon-btn" @click="contactService">
<image src="/static/customer-service.png" class="icon" mode="aspectFit" lazy-load="true"></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>
<!-- 转为自用确认弹窗 -->
<uv-popup ref="convertToOwnPopup" mode="center" round="20" :overlay="true" :closeOnClickOverlay="false"
:safeAreaInsetBottom="false">
<view class="convert-to-own-popup">
<view class="popup-header">
<text class="popup-title">{{ $t('order.convertToOwnWithMaxFeeTitle') }}</text>
</view>
<view class="popup-content">
<text class="popup-text">{{ $t('order.convertToOwnWithMaxFeeConfirm') }}</text>
</view>
<view class="popup-actions">
<view class="popup-btn cancel-btn" @click="closeConvertToOwnPopup">
{{ $t('order.convertToOwnCancelBtn') }}
</view>
<view class="popup-btn confirm-btn" @click="confirmConvertToOwn">
{{ $t('order.convertToOwnConfirmBtn') }}
</view>
</view>
</view>
</uv-popup>
</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,
closeWithMaxFee,
getInUseOrder,
deviceRentByOrderNo
} from '@/config/api/order.js'
import {
withdrawDeposit
} from '@/config/api/user.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
} = 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: '',
canUseMember: false,
canUseCoupon: false,
userMemberCardId: '',
userPurchaseId: '',
discountTypeName: '',
originalFee:'',
discountAmount:'',
returnMapImage: ''
})
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('')
const convertToOwnPopup = ref(null)
const lastDeviceEjectTime = ref(0) // 上次点击"宝未弹出"的时间戳
// 计算属性:是否显示优惠券/会员卡可用提示
const canUsePromotionTag = computed(() => {
return orderInfo.value.canUseMember === true || orderInfo.value.canUseCoupon === true
})
// 计算属性:是否使用了优惠
const hasUsedPromotion = computed(() => {
return !!(orderInfo.value.userMemberCardId || orderInfo.value.userPurchaseId)
})
// 获取已使用优惠的文本
const getUsedPromotionText = () => {
if (orderInfo.value.userMemberCardId) {
return t('user.myCards')
} else if (orderInfo.value.userPurchaseId) {
return t('user.myCoupons')
}
return '-'
}
// 判断订单是否已完成
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: '/subPackages/service/help/index'
})
}
// 获取计费规则图片URL
const getFeeRuleImageUrl = () => {
const locale = instance?.proxy?.$i18n?.locale || 'en_US'
// 如果是中文环境,显示中文图片,否则显示英文图片
if (locale === 'zh_CN' || locale === 'zh-CN' || locale === 'zh') {
return 'https://static.fdzpower.com/order_notice/notice_CN.png'
}
return 'https://static.fdzpower.com/order_notice/notice_EN.png'
}
// 快速归还
const quickReturn = () => {
// 检查是否有 returnMapImage 字段
console.log(orderInfo.value.returnMapImage)
if (orderInfo.value.returnMapImage) {
// 有值则跳转到新的地图页查看大图(支持放大)
const encodedUrl = encodeURIComponent(orderInfo.value.returnMapImage)
uni.navigateTo({
url: `/subPackages/order/return-map?imageUrl=${encodedUrl}`
})
} else {
// 没有值则继续执行原有跳转流程
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}${t('time.hour')}${minutes}${t('time.minute')}` :
`${hours}${t('time.hour')}`
} else {
usedTime = `${totalMinutes}${t('time.minute')}`
}
}
}
// 如果还是没有值,使用默认值
if (!usedTime) {
usedTime = `0${t('time.minute')}`
}
// 解析时长字符串,例如 "1小时5分钟" 或 "5分钟"
const hourMatch = usedTime.match(new RegExp(`(\\d+)${t('time.hour')}`))
const minuteMatch = usedTime.match(new RegExp(`(\\d+)${t('time.minute')}`))
let displayNumber = ''
let displayUnit = ''
if (hourMatch && minuteMatch) {
// 有小时也有分钟,如 "1小时5分钟"
displayNumber = `${hourMatch[1]}`
displayUnit = `${t('time.hour')}${minuteMatch[1]}${t('time.minute')}`
} else if (hourMatch) {
// 只有小时,如 "1小时"
displayNumber = hourMatch[1]
displayUnit = t('time.hour')
} else if (minuteMatch) {
// 只有分钟,如 "5分钟"
displayNumber = minuteMatch[1]
displayUnit = t('time.minute')
} else {
// 默认情况
displayNumber = '0'
displayUnit = t('time.minute')
}
return {
number: displayNumber,
unit: displayUnit
}
}
// 获取使用时长标签文本
const getUsedTimeLabel = () => {
// 使用中状态显示"已使用",已完成状态显示"使用时长"
return orderInfo.value.orderStatus === 'in_used' ? t('order.used') : t('order.duration')
}
// 获取订单费用(不含单位)
const getOrderFee = () => {
let fee;
if(orderInfo.value.originalFee){
fee = orderInfo.value.originalFee || orderInfo.value.originalFee || '0'
}else{
fee = orderInfo.value.currentFee;
}
// 移除可能的"元"字符
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)
}
}
// 从订单监控中移除订单
const removeFromOrderMonitor = () => {
if (orderInfo.value.orderId && $orderMonitor) {
try {
$orderMonitor.removeOrder({
orderId: orderInfo.value.orderId
})
console.log('订单已从监控队列移除:', orderInfo.value.orderId)
} catch (error) {
console.error('从监控队列移除订单失败:', error)
}
}
// 清除本地缓存的 activeOrderId
try {
uni.removeStorageSync('activeOrderId')
console.log('已清除本地缓存的 activeOrderId')
} catch (error) {
console.error('清除 activeOrderId 缓存失败:', error)
}
}
// 处理订单完成事件
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.discountAmount = orderData.discountAmount||''
// 保存优惠券/会员卡相关信息
orderInfo.value.canUseMember = orderData.canUseMember === true
orderInfo.value.canUseCoupon = orderData.canUseCoupon === true
orderInfo.value.userMemberCardId = orderData.userMemberCardId || ''
orderInfo.value.userPurchaseId = orderData.userPurchaseId || ''
orderInfo.value.discountTypeName = orderData.discountTypeName || ''
orderInfo.value.originalFee = orderData.originalFee||''
orderInfo.value.returnMapImage = orderData.returnMapImage||''
// 保存快递归还开始时间(小时为单位)
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
}
// 如果订单已完成,从监控中移除
if (isOrderCompleted()) {
removeFromOrderMonitor()
console.log('订单已完成,已从监控队列移除')
}
// 如果订单状态从 'in_used' 变为其他状态,清理所有定时器
if (oldStatus === 'in_used' && orderInfo.value.orderStatus !== 'in_used') {
console.log('订单状态已从使用中变为:', orderInfo.value.orderStatus, ',清理所有定时器')
clearTimer()
clearStatusCheckTimer()
clearExpressCountdown()
// 显示订单完成提示
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) {
// 如果订单已完成,从监控中移除
if (isOrderCompleted()) {
removeFromOrderMonitor()
console.log('订单已完成,已从监控队列移除:', orderInfo.value.orderId, '当前状态:', orderInfo.value
.orderStatus)
} else {
// 订单未完成,添加到监控队列
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 getInUseOrder()
console.log('通过设备号查询订单结果:', JSON.stringify(inUseRes))
if (inUseRes && inUseRes.code === 200 && inUseRes.data) {
const inUseOrder = inUseRes.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 () => {
uni.navigateTo({
url:'/subPackages/service/feedback/index'
})
// try {
// uni.showLoading({
// title: t('common.processing')
// })
// const res = await withdrawDeposit(orderInfo.value.orderNo)
// if (res && res.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?.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 = async () => {
// 检查是否在30秒间隔内
// const now = Date.now()
// const timeDiff = now - lastDeviceEjectTime.value
// const requiredInterval = 30000 // 30秒
// if (timeDiff < requiredInterval) {
// const remainingSeconds = Math.ceil((requiredInterval - timeDiff) / 1000)
// uni.showToast({
// title: `${t('order.deviceEjectWait')}${remainingSeconds}${t('order.deviceEjectRetry')}`,
// icon: 'none',
// duration: 2000
// })
// return
// }
// // 更新最后点击时间
// lastDeviceEjectTime.value = now
//原有逻辑跳转到投诉反馈页面
// uni.navigateTo({
// url: '/subPackages/service/feedback/index?selectedType=0'
// })
/*
现逻辑使用最新接口二次尝试弹出设备
*/
try {
uni.showLoading({
title: t('order.deviceEjectProcessing')
})
const res = await deviceRentByOrderNo(orderInfo.value.orderNo)
uni.hideLoading()
if (res.code == 200) {
uni.showToast({
title: t('order.deviceEjectSuccess'),
icon: 'success',
duration: 2000
})
} else {
uni.showToast({
title: res.msg || t('order.deviceEjectFailed'),
icon: 'none',
duration: 2000
})
}
} catch (error) {
uni.hideLoading()
console.error('二次弹出设备失败:', error)
uni.showToast({
title: t('order.deviceEjectError'),
icon: 'none',
duration: 2000
})
}
}
// 处理"归还提醒" - 授权微信通知
const handleReturnReminderSubscribe = async () => {
try {
// 订阅微信通知
await new Promise((resolve, reject) => {
uni.requestSubscribeMessage({
tmplIds: ['euJmQA2FjRFPZVSM4Z6U96i2eLRiGTLy3E3qMjIoYYY'],
success: (subscribeRes) => {
console.log('订阅消息success回调', subscribeRes)
resolve(subscribeRes)
},
fail: (subscribeErr) => {
console.log('订阅消息fail回调', subscribeErr)
// 订阅失败不影响主流程
resolve(subscribeErr)
}
})
})
uni.showToast({
title: t('payment.subscriptionSuccess'),
icon: 'success',
duration: 2000
})
} catch (error) {
console.log('订阅消息异常', error)
uni.showToast({
title: t('payment.subscriptionFailed'),
icon: 'none',
duration: 2000
})
}
}
// 处理"不想还了转为自用"(按最高费用)
const handleConvertToOwnedWithMaxFee = () => {
try {
convertToOwnPopup.value && typeof convertToOwnPopup.value.open === 'function' && convertToOwnPopup.value
.open()
} catch (e) {
console.error('打开弹窗失败', e)
}
}
// 关闭转为自用弹窗
const closeConvertToOwnPopup = () => {
try {
convertToOwnPopup.value && typeof convertToOwnPopup.value.close === 'function' && convertToOwnPopup.value
.close()
} catch (e) {
console.error('关闭弹窗失败', e)
}
}
// 确认转为自用
const confirmConvertToOwn = async () => {
try {
closeConvertToOwnPopup()
uni.showLoading({
title: t('common.processing')
})
const result = await closeWithMaxFee(orderInfo.value.orderNo)
if (result.code === 200) {
uni.hideLoading()
uni.showToast({
title: t('order.convertToOwnWithMaxFeeSuccess'),
icon: 'success',
duration: 2000
})
// 刷新订单详情
setTimeout(() => {
getOrderDetails()
}, 2000)
} else {
throw new Error(result.msg || t('order.convertToOwnWithMaxFeeFailed'))
}
} catch (error) {
uni.hideLoading()
uni.showToast({
title: error.message || t('order.convertToOwnWithMaxFeeFailed'),
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-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.header-left {
flex: 1;
}
.header-title-row {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 12rpx;
}
.header-title {
font-size: 48rpx;
font-weight: bold;
color: #333;
}
.header-desc {
font-size: 28rpx;
color: #999;
}
.header-right {
display: flex;
align-items: center;
height: 100%;
text-align: center;
}
.device-no-eject-btn {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
// padding: 12rpx 20rpx;
// background: #E8F5E9;
// border-radius: 12rpx;
// border: 2rpx solid #07c160;
min-width: 120rpx;
.device-no-eject-icon {
width: 68rpx;
height: 68rpx;
margin-bottom: 4rpx;
}
.device-no-eject-text {
font-size: 26rpx;
color: #07c160;
font-weight: 500;
}
&:active {
opacity: 0.8;
}
}
}
.promotion-tag {
padding: 6rpx 16rpx;
background: linear-gradient(135deg, #FFF4E6 0%, #FFE9CC 100%);
border-radius: 10rpx;
border: 1rpx solid #FFB84D;
font-size: 22rpx;
color: #FF8C00;
font-weight: 500;
white-space: nowrap;
flex-shrink: 0;
}
// 订单信息卡片
.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;
align-items: center;
// gap: 20rpx;
.info-left {
flex: 1;
display: flex;
// justify-content: space-between;
align-items: center;
.info-col {
width: 200rpx;
// flex: 1;
// text-align: center;
.info-value-wrapper {
display: flex;
align-items: baseline;
// justify-content: center;
margin-bottom: 8rpx;
.info-value-large {
font-size: 36rpx;
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;
}
}
}
.info-right {
display: flex;
align-items: center;
justify-content: flex-end;
flex-shrink: 0;
.return-reminder-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 12rpx 24rpx;
background: #3EAB64;
border-radius: 50rpx;
border: 2rpx solid #3EAB64;
.return-reminder-text {
font-size: 24rpx;
color: #fff;
font-weight: 500;
}
&:active {
opacity: 0.8;
}
}
}
}
.fee-rule {
background: #E8F5E9;
border-radius: 8rpx;
padding: 16rpx;
font-size: 24rpx;
color: #4CAF50;
line-height: 1.6;
}
.fee-rule-image {
margin-top: 20rpx;
width: 100%;
.rule-image {
// height: 100%;
width: 100%;
display: block;
border-radius: 12rpx;
}
}
}
// 转为自用提示
.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;
&.promotion-value {
display: flex;
align-items: center;
justify-content: flex-end;
color: #FF8C00;
font-weight: 500;
.promotion-icon {
width: 32rpx;
height: 32rpx;
margin-right: 8rpx;
}
}
}
}
.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;
}
}
&.convert-btn {
background: #fff;
color: #07c160;
border: 2rpx solid #07c160;
flex: 1;
&:active {
opacity: 0.8;
}
}
}
}
}
/* 转为自用确认弹窗样式 */
.convert-to-own-popup {
width: 640rpx;
max-width: 86vw;
background: #ffffff;
border-radius: 24rpx;
padding: 40rpx 40rpx 0;
box-sizing: border-box;
overflow: hidden;
.popup-header {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 24rpx;
.popup-title {
font-size: 36rpx;
font-weight: 600;
color: #333;
}
}
.popup-content {
padding: 0 0 40rpx;
box-sizing: border-box;
text-align: left;
.popup-text {
font-size: 28rpx;
color: #666;
line-height: 1.8;
text-align: left;
display: block;
width: 100%;
}
}
.popup-actions {
display: flex;
border-top: 1rpx solid #f0f0f0;
margin-left: -40rpx;
margin-right: -40rpx;
.popup-btn {
flex: 1;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
box-sizing: border-box;
&.cancel-btn {
color: #666;
border-right: 1rpx solid #f0f0f0;
&:active {
background-color: #f5f5f5;
}
}
&.confirm-btn {
color: #07c160;
font-weight: 600;
&:active {
background-color: #f0f9f4;
}
}
}
}
}
</style>