忽略本地报错信息文件

This commit is contained in:
2026-06-17 10:20:43 +08:00
parent 6b11144366
commit 090362c32d
191 changed files with 5434 additions and 76452 deletions

View File

@@ -10,7 +10,7 @@
<text class="summary-title">{{ detail.serviceName }}</text>
<text class="summary-case-no">服务单号:{{ detail.caseNo }}</text>
</view>
<ServiceStatusTag :text="detail.statusText" :tone="detail.statusTone"></ServiceStatusTag>
<ServiceStatusTag :text="displayStatusText" :tone="displayStatusTone"></ServiceStatusTag>
</view>
<text class="summary-desc">{{ detail.summary }}</text>
<view class="summary-price-row">
@@ -102,13 +102,29 @@
<view class="primary-btn" @click="bookAgain">再次预约</view>
</view>
<view v-else class="action-row">
<view v-if="detail.statusText == '派单未成功'" class="secondary-btn" @click="bookAgain">再次预约</view>
<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>
@@ -124,17 +140,81 @@ 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 } from '@/services/serviceOrderService.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
}
@@ -142,14 +222,66 @@ async function ensureLogin(): Promise<boolean> {
}
async function loadData() {
consumerLog('C03_LOAD_DETAIL', 'loadData start', {
caseId: caseId.value
})
if (caseId.value == '') {
consumerWarn('C03_LOAD_DETAIL', 'blocked: caseId empty')
return
}
if (!(await ensureLogin())) {
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
}
detail.value = await fetchConsumerHomeServiceCaseDetail(caseId.value)
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() {
@@ -213,6 +345,126 @@ 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
@@ -258,6 +510,11 @@ const consumerViewState = computed(() => {
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 = '当前预约暂未安排到服务人员'
@@ -310,16 +567,31 @@ function stopDetailRefreshTimer(): void {
}
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()
})
@@ -506,4 +778,72 @@ onUnload(() => {
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>