Files
medical-mall/pages/mall/consumer/home-service/order-detail.uvue

850 lines
26 KiB
Plaintext
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>
<ServicePageScaffold title="服务单详情" fallback-url="/pages/mall/consumer/home-service/index">
<view v-if="detail == null" class="empty-box">
<text class="empty-text">未找到对应服务单</text>
</view>
<view v-else>
<view class="summary-card">
<view class="summary-top-row">
<view>
<text class="summary-title">{{ detail.serviceName }}</text>
<text class="summary-case-no">服务单号:{{ detail.caseNo }}</text>
</view>
<ServiceStatusTag :text="displayStatusText" :tone="displayStatusTone"></ServiceStatusTag>
</view>
<text class="summary-desc">{{ detail.summary }}</text>
<view class="summary-price-row">
<text class="summary-price-prefix">¥</text>
<text class="summary-price">{{ detail.amount }}</text>
<text class="summary-price-unit">服务金额</text>
</view>
<view class="summary-meta-grid">
<view class="summary-meta-item">
<text class="summary-meta-label">上门时间</text>
<text class="summary-meta-value">{{ detail.serviceTime }}</text>
</view>
<view class="summary-meta-item">
<text class="summary-meta-label">当前进度</text>
<text class="summary-meta-value">第 {{ detail.currentStep }} / {{ detail.totalSteps }} 步</text>
</view>
<view class="summary-meta-item">
<text class="summary-meta-label">服务机构 / 人员</text>
<text class="summary-meta-value">{{ detail.staffName }}</text>
</view>
<view class="summary-meta-item">
<text class="summary-meta-label">联系电话</text>
<text class="summary-meta-value">{{ detail.staffPhone }}</text>
</view>
</view>
<view v-if="detail.statusText == '派单未成功' && detail.summary != ''" class="dispatch-fail-banner">
<text class="dispatch-fail-text">{{ detail.summary }}</text>
</view>
</view>
<ServicePanel title="预约信息" subtitle="围绕联系人、地址和服务对象展示当前预约信息。">
<ServiceInfoList
:items="[
{ label: '联系人:', value: detail.applicantName },
{ label: '服务对象:', value: detail.elderName + '' + detail.age + ' 岁' },
{ label: '联系电话:', value: detail.phone },
{ label: '服务地址:', value: detail.address },
{ label: '预约备注:', value: detail.summary }
]"
></ServiceInfoList>
</ServicePanel>
<ServicePanel title="服务保障" subtitle="用户在预约后仍可看到平台保障与追溯承诺。">
<view class="guarantee-row">
<text class="guarantee-chip">平台认证</text>
<text class="guarantee-chip">明码标价</text>
<text class="guarantee-chip">服务可追溯</text>
<text class="guarantee-chip">异常可申诉</text>
</view>
</ServicePanel>
<ServicePanel title="服务过程" subtitle="基于真实状态日志展示预约受理、派单、上门与验收进度。">
<ServiceInfoList
:items="[
{ label: '签到时间:', value: detail.checkinTime != '' ? detail.checkinTime : '暂未签到' },
{ label: '签到地点:', value: detail.checkinAddress != '' ? detail.checkinAddress : '暂未记录' },
{ label: '开始服务:', value: detail.serviceStartedAt != '' ? detail.serviceStartedAt : '暂未开始' },
{ label: '完成服务:', value: detail.serviceFinishedAt != '' ? detail.serviceFinishedAt : '暂未完成' },
{ label: '执行摘要:', value: detail.executionSummary != '' ? detail.executionSummary : '服务人员暂未提交执行摘要' },
{ label: '证据数量:', value: detail.evidenceCount > 0 ? String(detail.evidenceCount) + ' 份' : '暂未上传' }
]"
></ServiceInfoList>
<ServiceTimeline :items="detail.timeline"></ServiceTimeline>
</ServicePanel>
<view v-if="consumerViewState.showExceptionPanel" class="exception-panel">
<text class="exception-title">{{ consumerViewState.exceptionTitle }}</text>
<text class="exception-desc">{{ consumerViewState.exceptionDesc }}</text>
<view v-if="consumerViewState.exceptionReason != ''" class="exception-reason">
<text class="exception-reason-label">异常原因:</text>
<text class="exception-reason-value">{{ consumerViewState.exceptionReason }}</text>
</view>
<text class="exception-update-time">状态更新时间:{{ consumerViewState.statusUpdatedAt }}</text>
</view>
<view v-if="consumerViewState.showExceptionPanel" class="exception-panel">
<text class="exception-title">{{ consumerViewState.exceptionTitle }}</text>
<text class="exception-desc">{{ consumerViewState.exceptionDesc }}</text>
<view v-if="consumerViewState.exceptionReason != ''" class="exception-reason">
<text class="exception-reason-label">异常原因:</text>
<text class="exception-reason-value">{{ consumerViewState.exceptionReason }}</text>
</view>
<text class="exception-update-time">状态更新时间:{{ consumerViewState.statusUpdatedAt }}</text>
</view>
<view v-if="isServicePaymentExpired" class="action-row">
<view class="secondary-btn" @click="goHome">返回首页</view>
<view class="primary-btn" @click="bookAgain">再次预约</view>
</view>
<view v-else class="action-row">
<view v-if="isArrivalPending" class="secondary-btn" @click="confirmArrival(false, '用户反馈服务人员未实际到达')">未到达 / 有异议</view>
<view v-else-if="detail.statusText == '派单未成功'" class="secondary-btn" @click="bookAgain">再次预约</view>
<view v-else class="secondary-btn" @click="bookAgain">再次预约</view>
<view v-if="detail.status == 'pending_acceptance'" class="primary-btn" @click="goFeedback">去验收反馈</view>
<view v-else-if="isArrivalPending" class="primary-btn" @click="confirmArrival(true, '用户确认服务人员已到达')">确认已到达</view>
<view v-else-if="consumerViewState.showRescheduleBtn" class="primary-btn" @click="bookAgain">重新选择时间</view>
<view v-else-if="detail.statusText == '派单未成功'" class="primary-btn" @click="retryDispatch">重新派单</view>
<view v-else class="primary-btn" @click="contactService">联系客服</view>
</view>
<!-- ARRIVAL_PENDING: 确认到达模块 -->
<view v-if="isArrivalPending" class="arrival-confirm-card">
<view class="arrival-confirm-header">
<text class="arrival-confirm-title">等待消费者确认到达</text>
</view>
<view class="arrival-confirm-desc">
<text>服务人员已提交到达签到,请确认其是否已到达服务地点。</text>
</view>
<view class="arrival-confirm-actions">
<view class="arrival-confirm-secondary-btn" @click="confirmArrival(false, '用户反馈服务人员未实际到达')">未到达 / 有异议</view>
<view class="arrival-confirm-primary-btn" @click="confirmArrival(true, '用户确认服务人员已到达')">确认已到达</view>
</view>
</view>
</view>
</ServicePageScaffold>
</template>
<script setup lang="uts">
import { computed, ref } from 'vue'
import { onLoad, onShow, onUnload } from '@dcloudio/uni-app'
import { computed, ref } from 'vue'
import { onLoad, onShow, onUnload } from '@dcloudio/uni-app'
import ServicePageScaffold from '@/components/homeService/ServicePageScaffold.uvue'
import ServiceInfoList from '@/components/homeService/ServiceInfoList.uvue'
import ServicePanel from '@/components/homeService/ServicePanel.uvue'
import ServiceStatusTag from '@/components/homeService/ServiceStatusTag.uvue'
import ServiceTimeline from '@/components/homeService/ServiceTimeline.uvue'
import { fetchConsumerHomeServiceCaseDetail } from '@/services/homeServiceService.uts'
import { dispatchPaidHomecareOrder, HOMECARE_DISPATCH_STATUS_FAILED, showHomecareDispatchFailureModal, confirmHomecareArrival } from '@/services/serviceOrderService.uts'
import { HomeServiceCaseType } from '@/types/home-service.uts'
import { getCurrentUser, getCurrentUserId } from '@/utils/store.uts'
import { goToLogin } from '@/utils/utils.uts'
const ENABLE_ARRIVAL_CONFIRM = false
const caseId = ref('')
const detail = ref<HomeServiceCaseType | null>(null)
const consumerOrderTraceId = ref('')
function normalizeOrderStatusCode(status: string): string {
if (status == null) return ''
return status.trim().toUpperCase()
}
function isArrivalPendingStatus(status: string): boolean {
const s = normalizeOrderStatusCode(status)
return s == 'ARRIVAL_PENDING'
|| s == 'WAITING_CONSUMER_CONFIRM'
|| s == 'WAITING_ARRIVAL_CONFIRM'
}
function consumerLog(step: string, message: string, data: any | null = null) {
const tid = consumerOrderTraceId.value
if (data == null) {
console.log(`[CONSUMER_ORDER_DETAIL_TRACE][${step}][traceId=${tid}][${new Date().toISOString()}] ${message}`)
} else {
console.log(`[CONSUMER_ORDER_DETAIL_TRACE][${step}][traceId=${tid}][${new Date().toISOString()}] ${message}`, data)
}
}
function consumerWarn(step: string, message: string, data: any | null = null) {
const tid = consumerOrderTraceId.value
if (data == null) {
console.warn(`[CONSUMER_ORDER_DETAIL_TRACE][${step}][traceId=${tid}][${new Date().toISOString()}] ${message}`)
} else {
console.warn(`[CONSUMER_ORDER_DETAIL_TRACE][${step}][traceId=${tid}][${new Date().toISOString()}] ${message}`, data)
}
}
function consumerError(step: string, message: string, e: any, extra: any | null = null) {
const tid = consumerOrderTraceId.value
console.error(`[CONSUMER_ORDER_DETAIL_TRACE][${step}][traceId=${tid}][${new Date().toISOString()}] ${message}`, {
errorMessage: e != null ? String(e) : '',
errorStack: '',
extra: extra
})
}
const isArrivalPending = computed<boolean>(() => {
if (!ENABLE_ARRIVAL_CONFIRM) return false
if (detail.value == null) return false
return isArrivalPendingStatus(detail.value.status)
|| detail.value.waitingConsumerConfirm == true
})
const displayStatusText = computed<string>(() => {
if (detail.value == null) return ''
if (isArrivalPending.value) return '等待消费者确认到达'
return detail.value.statusText
})
const displayStatusTone = computed<string>(() => {
if (detail.value == null) return 'neutral'
if (isArrivalPending.value) return 'warning'
return detail.value.statusTone
})
async function ensureLogin(): Promise<boolean> {
const user = await getCurrentUser()
if (user == null || getCurrentUserId() == '') {
consumerWarn('C04_AUTH', 'redirect to login because current user is empty', {
caseId: caseId.value
})
goToLogin('/pages/mall/consumer/home-service/order-detail?id=' + caseId.value)
return false
}
return true
}
async function loadData() {
consumerLog('C03_LOAD_DETAIL', 'loadData start', {
caseId: caseId.value
})
if (caseId.value == '') {
consumerWarn('C03_LOAD_DETAIL', 'blocked: caseId empty')
return
}
const loginOk = await ensureLogin()
consumerLog('C04_AUTH', 'ensureLogin result', {
ok: loginOk,
userId: getCurrentUserId()
})
if (!loginOk) {
consumerWarn('C04_AUTH', 'blocked: not logged in', {
caseId: caseId.value
})
detail.value = null
return
}
try {
consumerLog('C05_DETAIL_RPC', 'fetchConsumerHomeServiceCaseDetail request', {
caseId: caseId.value
})
const result = await fetchConsumerHomeServiceCaseDetail(caseId.value)
consumerLog('C06_DETAIL_RESULT_RAW', 'fetchConsumerHomeServiceCaseDetail raw result', result)
detail.value = result
consumerLog('C07_DETAIL_RESULT_PARSED', 'detail parsed', {
id: detail.value != null ? detail.value.id : '',
orderNo: detail.value != null ? detail.value.caseNo : '',
status: detail.value != null ? detail.value.status : '',
statusText: detail.value != null ? detail.value.statusText : '',
dispatchStatus: '',
paymentStatus: detail.value != null && detail.value.paymentStatus != null ? detail.value.paymentStatus : 0,
checkinTime: detail.value != null ? detail.value.checkinTime : '',
arriveTime: detail.value != null ? detail.value.checkinTime : '',
evidenceCount: detail.value != null ? detail.value.evidenceCount : 0,
isArrivalPending: isArrivalPending.value,
timelineCount: detail.value != null ? detail.value.timeline.length : 0
})
if (detail.value != null) {
consumerLog('C08_STATUS_CHECK', 'detail status resolved after reload', {
status: detail.value.status,
statusText: detail.value.statusText,
checkinTime: detail.value.checkinTime,
arriveTime: detail.value.checkinTime,
timeline: detail.value.timeline
})
}
if (detail.value != null && ENABLE_ARRIVAL_CONFIRM && isArrivalPending.value == true) {
consumerLog('C08_STATUS_CHECK', 'arrival pending detected, confirm card should display', {
status: detail.value.status,
statusText: detail.value.statusText
})
}
} catch (e) {
consumerError('C05_DETAIL_RPC', 'fetchConsumerHomeServiceCaseDetail failed', e, {
caseId: caseId.value
})
detail.value = null
}
}
function goFeedback() {
if (caseId.value == '') {
return
}
ensureLogin().then((ok) => {
if (!ok) {
return
}
uni.navigateTo({ url: '/pages/mall/consumer/home-service/feedback?id=' + caseId.value })
})
}
async function goPayment(): Promise<void> {
if (caseId.value == '') {
uni.showToast({ title: '订单信息异常', icon: 'none' })
return
}
if (detail.value == null) {
uni.showToast({ title: '订单信息加载失败,请稍后重试', icon: 'none' })
return
}
// 刷新一次订单状态
await loadData()
if (detail.value == null) {
uni.showToast({ title: '订单信息加载失败,请稍后重试', icon: 'none' })
return
}
if (isServicePaymentExpired.value) {
uni.showToast({ title: '订单已超时未支付,请重新预约', icon: 'none' })
return
}
if (!canPayServiceOrder.value) {
uni.showToast({ title: '当前订单已不可支付,请刷新查看最新状态', icon: 'none' })
return
}
uni.navigateTo({
url: '/pages/mall/consumer/payment'
+ '?orderId=' + encodeURIComponent(detail.value.id)
+ '&source=service'
+ '&bizType=service'
})
}
function bookAgain() {
if (detail.value == null) {
return
}
let serviceTargetId = 'svc-001'
if (detail.value.serviceName == '康复训练指导') {
serviceTargetId = 'svc-002'
}
if (detail.value.serviceName == '慢病健康随访') {
serviceTargetId = 'svc-003'
}
uni.navigateTo({ url: '/pages/mall/consumer/home-service/service-detail?id=' + serviceTargetId + '&mode=booking' })
}
function contactService() {
uni.showToast({ title: '即将接入专属客服入口', icon: 'none' })
}
async function confirmArrival(confirm: boolean, reason: string): Promise<void> {
consumerLog('C09_CONFIRM_ARRIVAL', 'confirmArrival start', {
id: detail.value != null ? detail.value.id : '',
status: detail.value != null ? detail.value.status : '',
confirm: confirm,
reason: reason
})
if (detail.value == null || detail.value.id == '') {
consumerWarn('C09_CONFIRM_ARRIVAL', 'blocked: detail missing', {
confirm: confirm,
reason: reason
})
uni.showToast({ title: '订单信息异常', icon: 'none' })
return
}
uni.showLoading({ title: confirm ? '正在确认' : '正在提交反馈', mask: true })
try {
consumerLog('C10_CONFIRM_RPC', 'confirmHomecareArrival request', {
id: detail.value.id,
confirm: confirm,
reason: reason
})
const result = await confirmHomecareArrival(detail.value.id, confirm, reason)
consumerLog('C11_CONFIRM_RESULT', 'confirmHomecareArrival result', result)
uni.hideLoading()
if (confirm) {
uni.showToast({ title: '已确认到达', icon: 'success' })
} else {
uni.showToast({ title: '已提交反馈', icon: 'none' })
}
// 重新拉取订单详情
await loadData()
consumerLog('C12_CONFIRM_RELOAD', 'reload after confirmArrival finished', {
status: detail.value != null ? detail.value.status : '',
statusText: detail.value != null ? detail.value.statusText : ''
})
} catch (e) {
uni.hideLoading()
consumerError('C10_CONFIRM_RPC', 'confirmHomecareArrival failed', e, {
id: detail.value != null ? detail.value.id : '',
confirm: confirm,
reason: reason
})
uni.showToast({ title: '操作失败,请稍后重试', icon: 'none' })
}
}
function goHome() {
uni.navigateTo({ url: '/pages/mall/consumer/home-service/index' })
}
function getPayExpireMs(caseDetail: HomeServiceCaseType): number {
if (caseDetail.payExpireAt == null || caseDetail.payExpireAt == '') {
return 0
}
const parsed = Date.parse(caseDetail.payExpireAt)
return isNaN(parsed) ? 0 : parsed
}
function isPaymentTimeExpired(caseDetail: HomeServiceCaseType): boolean {
if (caseDetail.paymentStatus != 1 || caseDetail.status != 'created') {
return false
}
const expireMs = getPayExpireMs(caseDetail)
if (expireMs <= 0) {
return false
}
return expireMs <= Date.now()
}
const isServicePaymentExpired = computed<boolean>(() => {
if (detail.value == null) {
return false
}
return isPaymentTimeExpired(detail.value)
})
let isRetryDispatching = false
function retryDispatch() {
if (isRetryDispatching || detail.value == null) {
return
}
const currentId = detail.value.id
isRetryDispatching = true
uni.showLoading({ title: '正在重新派单', mask: true })
dispatchPaidHomecareOrder(currentId).then((result) => {
uni.hideLoading()
console.log('[retryDispatch] 派单结果:', JSON.stringify(result))
if (result.success) {
uni.showToast({ title: '派单成功', icon: 'success' })
loadData()
return
}
showHomecareDispatchFailureModal(currentId, result, (id: string) => {
retryDispatch()
})
}).catch((e) => {
uni.hideLoading()
console.error('[retryDispatch] 重新派单异常:', e)
uni.showModal({
title: '派单服务异常',
content: '派单服务暂时异常,请稍后重试',
showCancel: true,
cancelText: '稍后再试',
confirmText: '重新派单',
success: (res) => {
if (res.confirm) {
retryDispatch()
}
}
})
}).finally(() => {
isRetryDispatching = false
})
}
function getLatestTimelineRemark(caseDetail: HomeServiceCaseType): string {
if (caseDetail.timeline.length > 0) {
return caseDetail.timeline[0].description
}
return ''
}
function isTerminalStatus(status: string): boolean {
return status == 'accepted_by_user' || status == 'reviewed' || status == 'settled' || status == 'cancelled' || status == 'exception'
}
const consumerViewState = computed(() => {
const defaultState = {
showExceptionPanel: false,
exceptionTitle: '',
exceptionDesc: '',
exceptionReason: '',
statusUpdatedAt: '',
showRescheduleBtn: false,
showCancelBtn: false,
showRefundBtn: false
}
if (detail.value == null) {
return defaultState
}
const status = detail.value.status
const remark = getLatestTimelineRemark(detail.value)
const result = { ...defaultState }
if (status == 'created' || status == 'assigned') {
result.exceptionTitle = '正在安排服务人员'
result.exceptionDesc = '您的预约申请已提交,平台正在为您匹配可上门的服务人员,请耐心等待。'
result.statusUpdatedAt = detail.value.serviceTime
} else if (status == 'accepted') {
result.exceptionTitle = '服务人员已接单'
result.exceptionDesc = '服务人员已确认接单,正在准备上门,请保持电话畅通。'
result.statusUpdatedAt = detail.value.serviceTime
} else if (status == 'departed') {
result.exceptionTitle = '服务人员正在前往'
result.exceptionDesc = '服务人员已出发,正在前往服务地点,请做好接待准备。'
result.statusUpdatedAt = detail.value.serviceTime
} else if (status == 'arrived' || status == 'in_service') {
result.exceptionTitle = '服务人员已到达'
result.exceptionDesc = '服务人员已到达服务地点,服务正在进行中。'
result.statusUpdatedAt = detail.value.serviceTime
} else if (isArrivalPending.value) {
result.showExceptionPanel = true
result.exceptionTitle = '待确认到达'
result.exceptionDesc = '服务人员已到达现场并提交签到,请确认是否已到达。'
result.statusUpdatedAt = detail.value.serviceTime
} else if (status == 'rejected') {
result.showExceptionPanel = true
result.exceptionTitle = '当前预约暂未安排到服务人员'
result.exceptionDesc = '很抱歉,服务人员未接受该工单。您可以重新选择服务时间,或取消本次服务申请。'
result.exceptionReason = remark != '' ? remark : '服务人员未接单'
result.statusUpdatedAt = detail.value.serviceTime
result.showRescheduleBtn = true
} else if (status == 'exception') {
result.showExceptionPanel = true
result.exceptionTitle = '当前预约暂未安排到服务人员'
result.exceptionDesc = '很抱歉,当前所选预约时间暂未匹配到可服务人员。您可以重新选择服务时间,或取消本次服务申请。'
result.exceptionReason = remark != '' ? remark : '履约异常,请稍后重试或联系客服'
result.statusUpdatedAt = detail.value.serviceTime
result.showRescheduleBtn = true
} else if (status == 'cancelled') {
result.showExceptionPanel = true
result.exceptionTitle = '服务申请已取消'
result.exceptionDesc = '该服务申请已被取消。如有疑问,请联系客服了解详情。'
result.exceptionReason = remark != '' ? remark : '已取消'
result.statusUpdatedAt = detail.value.serviceTime
} else if (status == 'pending_acceptance') {
result.exceptionTitle = '服务已完成,等待验收'
result.exceptionDesc = '服务人员已提交服务记录,请您确认服务结果并进行评价。'
result.statusUpdatedAt = detail.value.serviceTime
} else if (status == 'completed' || status == 'accepted_by_user' || status == 'reviewed' || status == 'settled') {
result.exceptionTitle = '服务已完成'
result.exceptionDesc = '本次服务已结束,感谢您的使用。'
result.statusUpdatedAt = detail.value.serviceTime
}
return result
})
let detailRefreshTimerId: number = 0
function startDetailRefreshTimer(): void {
stopDetailRefreshTimer()
if (detail.value != null && !isTerminalStatus(detail.value.status)) {
detailRefreshTimerId = setInterval(() => {
loadData()
}, 15000)
}
}
function stopDetailRefreshTimer(): void {
if (detailRefreshTimerId > 0) {
clearInterval(detailRefreshTimerId)
detailRefreshTimerId = 0
}
}
onLoad((options) => {
consumerOrderTraceId.value = Date.now().toString()
consumerLog('C01_ROUTE', 'consumer order detail onLoad start', {
rawOptions: options,
caseId: options != null && options['id'] != null ? String(options['id']) : ''
})
const id = options['id']
if (id != null) {
caseId.value = id as string
consumerLog('C01_ROUTE', 'caseId resolved', {
caseId: caseId.value
})
loadData().then(() => {
startDetailRefreshTimer()
})
} else {
consumerWarn('C01_ROUTE', 'missing caseId, stop load', {
rawOptions: options
})
}
})
onShow(() => {
consumerLog('C02_PAGE_SHOW', 'page show, reload detail', {
caseId: caseId.value
})
loadData().then(() => {
startDetailRefreshTimer()
})
})
onUnload(() => {
stopDetailRefreshTimer()
})
</script>
<style scoped>
.summary-card {
background: #ffffff;
border-radius: 32rpx;
padding: 28rpx;
box-shadow: 0 12rpx 24rpx rgba(15, 23, 42, 0.06);
margin-bottom: 24rpx;
}
.summary-top-row,
.summary-price-row,
.action-row,
.guarantee-row {
flex-direction: row;
align-items: center;
}
.summary-top-row,
.action-row {
justify-content: space-between;
}
.summary-title {
font-size: 34rpx;
font-weight: 700;
color: #16324f;
}
.summary-case-no,
.summary-desc,
.summary-meta-label,
.summary-meta-value,
.empty-text {
margin-top: 10rpx;
font-size: 24rpx;
line-height: 34rpx;
color: #66788a;
}
.summary-desc {
margin-top: 18rpx;
}
.summary-price-row {
margin-top: 18rpx;
align-items: flex-end;
}
.summary-price-prefix,
.summary-price {
font-size: 40rpx;
font-weight: 700;
color: #0f766e;
}
.summary-price-unit {
font-size: 22rpx;
color: #64748b;
margin-left: 10rpx;
margin-bottom: 6rpx;
}
.summary-meta-grid {
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
margin-top: 22rpx;
}
.summary-meta-item {
width: 48%;
padding: 22rpx;
border-radius: 24rpx;
background: #f8fbfd;
box-sizing: border-box;
margin-bottom: 16rpx;
}
.guarantee-row {
flex-wrap: wrap;
}
.guarantee-chip {
padding: 12rpx 18rpx;
border-radius: 999rpx;
background: #eef6ff;
font-size: 22rpx;
color: #476072;
margin-right: 12rpx;
margin-bottom: 12rpx;
}
.action-row {
margin-top: 10rpx;
margin-bottom: 12rpx;
}
.secondary-btn,
.primary-btn {
width: 48%;
height: 78rpx;
border-radius: 999rpx;
font-size: 26rpx;
font-weight: 700;
align-items: center;
justify-content: center;
}
.secondary-btn {
background: #ffffff;
border-width: 2rpx;
border-style: solid;
border-color: #cbd5e1;
color: #476072;
}
.primary-btn {
background: #16a085;
color: #ffffff;
}
.empty-text {
margin-top: 10rpx;
}
.empty-box {
padding: 120rpx 0;
align-items: center;
}
.exception-panel {
background: #ffffff;
border-radius: 32rpx;
padding: 28rpx;
box-shadow: 0 12rpx 24rpx rgba(15, 23, 42, 0.06);
margin-bottom: 24rpx;
display: flex;
flex-direction: column;
}
.exception-title {
font-size: 30rpx;
font-weight: 700;
color: #b45309;
margin-bottom: 12rpx;
}
.exception-desc {
font-size: 24rpx;
color: #66788a;
line-height: 36rpx;
margin-bottom: 16rpx;
}
.exception-reason {
flex-direction: row;
flex-wrap: wrap;
margin-bottom: 12rpx;
}
.exception-reason-label {
font-size: 24rpx;
color: #66788a;
font-weight: 600;
}
.exception-reason-value {
font-size: 24rpx;
color: #b45309;
}
.exception-update-time {
font-size: 22rpx;
color: #94a3b8;
}
.dispatch-fail-banner {
margin-top: 18rpx;
padding: 18rpx 24rpx;
background: #fff7ed;
border-radius: 16rpx;
border-width: 1rpx;
border-style: solid;
border-color: #fed7aa;
}
.dispatch-fail-text {
font-size: 26rpx;
color: #c2410c;
line-height: 40rpx;
}
.arrival-confirm-card {
background: linear-gradient(180deg, #fffbeb 0%, #fef3c7 100%);
border-radius: 32rpx;
padding: 28rpx;
box-shadow: 0 12rpx 24rpx rgba(180, 83, 9, 0.1);
margin-bottom: 24rpx;
flex-direction: column;
}
.arrival-confirm-header {
margin-bottom: 16rpx;
}
.arrival-confirm-title {
font-size: 34rpx;
font-weight: 700;
color: #92400e;
}
.arrival-confirm-desc {
margin-bottom: 24rpx;
}
.arrival-confirm-actions {
flex-direction: row;
justify-content: space-between;
gap: 20rpx;
}
.arrival-confirm-secondary-btn,
.arrival-confirm-primary-btn {
flex: 1;
padding: 20rpx 0;
border-radius: 24rpx;
text-align: center;
font-size: 28rpx;
font-weight: 600;
}
.arrival-confirm-secondary-btn {
background: #ffffff;
color: #66788a;
border-width: 2rpx;
border-style: solid;
border-color: #cbd5e1;
}
.arrival-confirm-primary-btn {
background: #f59e0b;
color: #ffffff;
}
</style>