1870 lines
49 KiB
Vue
1870 lines
49 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 class="return-map-popup" v-if="showReturnMapPopup" @click.self="closeReturnMapPopup">
|
||
<view class="popup-mask"></view>
|
||
<view class="popup-content">
|
||
<view class="popup-header">
|
||
<text class="popup-title">{{ $t('order.returnLocationMap') }}</text>
|
||
<view class="close-btn" @click="closeReturnMapPopup">
|
||
<text class="close-icon">×</text>
|
||
</view>
|
||
</view>
|
||
<view class="popup-body">
|
||
<image :src="returnMapImageUrl" mode="widthFix" class="map-image" lazy-load="true"></image>
|
||
</view>
|
||
<view class="popup-footer">
|
||
<view class="save-btn" @click="saveReturnMapImage">
|
||
{{ $t('common.saveImage') }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</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,
|
||
closeWithMaxFee,
|
||
getInUseOrder
|
||
} 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 showReturnMapPopup = ref(false)
|
||
const returnMapImageUrl = ref('')
|
||
|
||
// 计算属性:是否显示优惠券/会员卡可用提示
|
||
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) {
|
||
// 有值则弹窗显示图片
|
||
returnMapImageUrl.value = orderInfo.value.returnMapImage
|
||
showReturnMapPopup.value = true
|
||
} else {
|
||
// 没有值则继续执行跳转流程
|
||
uni.navigateTo({
|
||
url: '/pages/search/index'
|
||
})
|
||
}
|
||
}
|
||
|
||
// 关闭归还地图弹窗
|
||
const closeReturnMapPopup = () => {
|
||
showReturnMapPopup.value = false
|
||
}
|
||
|
||
// 保存归还地图图片
|
||
const saveReturnMapImage = () => {
|
||
uni.showLoading({
|
||
title: t('common.saving')
|
||
})
|
||
|
||
uni.downloadFile({
|
||
url: returnMapImageUrl.value,
|
||
success: (res) => {
|
||
if (res.statusCode === 200) {
|
||
uni.saveImageToPhotosAlbum({
|
||
filePath: res.tempFilePath,
|
||
success: () => {
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: t('common.saveSuccess'),
|
||
icon: 'success'
|
||
})
|
||
},
|
||
fail: (err) => {
|
||
uni.hideLoading()
|
||
console.error('保存图片失败:', err)
|
||
uni.showToast({
|
||
title: t('common.saveFailed'),
|
||
icon: 'none'
|
||
})
|
||
}
|
||
})
|
||
} else {
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: t('common.downloadFailed'),
|
||
icon: 'none'
|
||
})
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
uni.hideLoading()
|
||
console.error('下载图片失败:', err)
|
||
uni.showToast({
|
||
title: t('common.downloadFailed'),
|
||
icon: 'none'
|
||
})
|
||
}
|
||
})
|
||
}
|
||
|
||
// 再次租借
|
||
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 = () => {
|
||
|
||
const fee = orderInfo.value.originalFee || orderInfo.value.originalFee || '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)
|
||
}
|
||
}
|
||
|
||
// 从订单监控中移除订单
|
||
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 = () => {
|
||
uni.navigateTo({
|
||
url: '/pages/feedback/index?selectedType=0'
|
||
})
|
||
}
|
||
|
||
// 处理"归还提醒" - 授权微信通知
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 归还地图弹窗样式 */
|
||
.return-map-popup {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
z-index: 9999;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.popup-mask {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
}
|
||
|
||
.popup-content {
|
||
position: relative;
|
||
width: 90%;
|
||
max-width: 600rpx;
|
||
background: #ffffff;
|
||
border-radius: 24rpx;
|
||
overflow: hidden;
|
||
z-index: 10000;
|
||
|
||
.popup-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 30rpx;
|
||
border-bottom: 1rpx solid #f0f0f0;
|
||
|
||
.popup-title {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.close-btn {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 50%;
|
||
background: #f5f5f5;
|
||
|
||
.close-icon {
|
||
font-size: 40rpx;
|
||
color: #666;
|
||
line-height: 1;
|
||
}
|
||
|
||
&:active {
|
||
opacity: 0.7;
|
||
}
|
||
}
|
||
}
|
||
|
||
.popup-body {
|
||
padding: 30rpx;
|
||
max-height: 60vh;
|
||
overflow-y: auto;
|
||
|
||
.map-image {
|
||
width: 100%;
|
||
display: block;
|
||
border-radius: 12rpx;
|
||
}
|
||
}
|
||
|
||
.popup-footer {
|
||
padding: 20rpx 30rpx 30rpx;
|
||
|
||
.save-btn {
|
||
width: 100%;
|
||
height: 88rpx;
|
||
background: linear-gradient(135deg, #07c160, #10d673);
|
||
border-radius: 44rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: #fff;
|
||
|
||
&:active {
|
||
opacity: 0.8;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style> |