修复订单显示bug
This commit is contained in:
@@ -86,6 +86,17 @@
|
||||
<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>
|
||||
@@ -105,6 +116,8 @@
|
||||
<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'
|
||||
@@ -200,75 +213,6 @@ function contactService() {
|
||||
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()
|
||||
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
|
||||
@@ -298,20 +242,7 @@ const consumerViewState = computed(() => {
|
||||
const remark = getLatestTimelineRemark(detail.value)
|
||||
const result = { ...defaultState }
|
||||
|
||||
if (isPaymentTimeExpired(detail.value)) {
|
||||
result.showExceptionPanel = true
|
||||
result.exceptionTitle = '订单已超时未支付'
|
||||
result.exceptionDesc = '支付时间已结束,请返回首页重新预约或刷新查看最新状态。'
|
||||
result.statusUpdatedAt = detail.value.payExpireAt != null && detail.value.payExpireAt != '' ? detail.value.payExpireAt : detail.value.serviceTime
|
||||
return result
|
||||
}
|
||||
|
||||
if (detail.value.statusText == '派单未成功') {
|
||||
result.showExceptionPanel = true
|
||||
result.exceptionTitle = '派单未成功'
|
||||
result.exceptionDesc = detail.value.summary != '' ? detail.value.summary : '当前暂无匹配的服务人员,请稍后重试或联系客服。'
|
||||
result.statusUpdatedAt = detail.value.serviceTime
|
||||
} else if (status == 'created' || status == 'assigned') {
|
||||
if (status == 'created' || status == 'assigned') {
|
||||
result.exceptionTitle = '正在安排服务人员'
|
||||
result.exceptionDesc = '您的预约申请已提交,平台正在为您匹配可上门的服务人员,请耐心等待。'
|
||||
result.statusUpdatedAt = detail.value.serviceTime
|
||||
@@ -360,15 +291,6 @@ const consumerViewState = computed(() => {
|
||||
return result
|
||||
})
|
||||
|
||||
const canPayServiceOrder = computed<boolean>(() => {
|
||||
if (detail.value == null) {
|
||||
return false
|
||||
}
|
||||
return detail.value.paymentStatus == 1
|
||||
&& detail.value.status == 'created'
|
||||
&& !isPaymentTimeExpired(detail.value)
|
||||
})
|
||||
|
||||
let detailRefreshTimerId: number = 0
|
||||
|
||||
function startDetailRefreshTimer(): void {
|
||||
@@ -584,20 +506,4 @@ 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;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,33 @@
|
||||
<template>
|
||||
<ServicePageScaffold title="到达签到" fallback-url="/pages/mall/delivery/orders/route">
|
||||
<ServicePanel title="居家服务认证" subtitle="使用居家服务系统账号登录">
|
||||
<view v-if="!isHomecareLoggedIn">
|
||||
<text class="info-text">状态:未登录</text>
|
||||
<text class="info-text warning-text">居家服务认证服务暂不可用,请跳过此步骤继续操作</text>
|
||||
</view>
|
||||
<view v-else>
|
||||
<text class="info-text">已登录:{{ homecareUserEmail }}</text>
|
||||
<text class="info-text success-text">居家服务认证通过</text>
|
||||
</view>
|
||||
</ServicePanel>
|
||||
|
||||
<ServicePanel title="签到要求" subtitle="定位签到或扫码签到,要求在 50 米范围内并上传现场图片。">
|
||||
<text v-if="order != null" class="info-text">服务地址:{{ order.fullAddress }}</text>
|
||||
<text class="info-text">当前定位:{{ locationText }}</text>
|
||||
<text class="info-text">定位精度:{{ accuracyText }}</text>
|
||||
<text class="info-text">现场图片:{{ photos.length }} 张</text>
|
||||
</ServicePanel>
|
||||
|
||||
<ServicePanel title="距离预校验" subtitle="获取定位后校验是否在允许签到范围内">
|
||||
<text class="info-text">距离服务点:{{ distanceText }}</text>
|
||||
<text class="info-text">允许半径:{{ allowedRadiusText }}</text>
|
||||
<text class="info-text">校验状态:{{ precheckStatusText }}</text>
|
||||
<text v-if="reasonText !== ''" class="info-text warning-text">{{ reasonText }}</text>
|
||||
<view class="button-stack">
|
||||
<button class="secondary-btn" :disabled="prechecking" @click="handlePrecheck">{{ prechecking ? '预校验中...' : '距离预校验' }}</button>
|
||||
</view>
|
||||
</ServicePanel>
|
||||
|
||||
<ServicePanel title="签到操作" subtitle="定位失败时要给出清晰提示。">
|
||||
<view class="button-stack">
|
||||
<button class="secondary-btn" @click="getCurrentLocation">获取 GPS 定位</button>
|
||||
@@ -25,15 +48,63 @@ import type { DeliveryLocationType, DeliveryOrderType } from '@/types/delivery.u
|
||||
import { checkinOrder, getDeliveryOrderDetail } from '@/services/deliveryService.uts'
|
||||
import { requireDeliveryAuth } from '@/utils/deliveryAuth.uts'
|
||||
import { getDeliveryRouteParam } from '@/utils/deliveryRoute.uts'
|
||||
import {
|
||||
emailLogin,
|
||||
checkinPrecheck,
|
||||
getHomecareToken,
|
||||
getHomecareUser,
|
||||
getReasonText
|
||||
} from '@/utils/homecareAuth.uts'
|
||||
|
||||
const orderId = ref('')
|
||||
const order = ref<DeliveryOrderType | null>(null)
|
||||
const currentLocation = ref<DeliveryLocationType | null>(null)
|
||||
const locationText = ref('未获取')
|
||||
const accuracyText = ref('未知')
|
||||
const photos = ref([] as Array<string>)
|
||||
const note = ref('')
|
||||
const submitting = ref(false)
|
||||
|
||||
// 居家服务认证状态
|
||||
const isHomecareLoggedIn = ref(false)
|
||||
const homecareUserEmail = ref('')
|
||||
|
||||
// 距离预校验状态
|
||||
const prechecking = ref(false)
|
||||
const canCheckin = ref(false)
|
||||
const distanceText = ref('未校验')
|
||||
const allowedRadiusText = ref('未校验')
|
||||
const precheckStatusText = ref('未校验')
|
||||
const reasonText = ref('')
|
||||
|
||||
function updateHomecareLoginStatus(): void {
|
||||
console.warn('[CHECKIN DEBUG] updateHomecareLoginStatus: called')
|
||||
const token = getHomecareToken()
|
||||
console.warn('[CHECKIN DEBUG] updateHomecareLoginStatus: token length:', token.length)
|
||||
if (token !== '') {
|
||||
isHomecareLoggedIn.value = true
|
||||
const user = getHomecareUser()
|
||||
if (user != null) {
|
||||
const email = user.getString('email')
|
||||
homecareUserEmail.value = email != null ? email : ''
|
||||
console.warn('[CHECKIN DEBUG] updateHomecareLoginStatus: logged in as', homecareUserEmail.value)
|
||||
}
|
||||
} else {
|
||||
isHomecareLoggedIn.value = false
|
||||
homecareUserEmail.value = ''
|
||||
console.warn('[CHECKIN DEBUG] updateHomecareLoginStatus: not logged in')
|
||||
}
|
||||
}
|
||||
|
||||
async function loginHomecare(): Promise<void> {
|
||||
const token = getHomecareToken()
|
||||
if (token !== '') {
|
||||
uni.showToast({ title: '已登录,无需重复登录', icon: 'success' })
|
||||
return
|
||||
}
|
||||
uni.showToast({ title: '居家服务认证服务暂不可用', icon: 'none' })
|
||||
}
|
||||
|
||||
function toRadians(value: number): number {
|
||||
return value * 3.1415926 / 180
|
||||
}
|
||||
@@ -48,13 +119,73 @@ function calculateDistance(lat1: number, lng1: number, lat2: number, lng2: numbe
|
||||
}
|
||||
|
||||
function wrapLocation(): Promise<DeliveryLocationType> {
|
||||
console.warn('[CHECKIN DEBUG] wrapLocation: starting...')
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.getLocation({
|
||||
type: 'gcj02',
|
||||
isHighAccuracy: true,
|
||||
enableHighAccuracy: true,
|
||||
success: (res) => {
|
||||
resolve({ latitude: res.latitude, longitude: res.longitude, address: '当前位置', time: new Date().toISOString().replace('T', ' ').substring(0, 19) })
|
||||
console.warn('[CHECKIN DEBUG] wrapLocation SUCCESS:', JSON.stringify({
|
||||
latitude: res.latitude,
|
||||
longitude: res.longitude,
|
||||
accuracy: res.accuracy,
|
||||
speed: res.speed,
|
||||
altitude: res.altitude,
|
||||
altitudeAccuracy: res.altitudeAccuracy,
|
||||
heading: res.heading,
|
||||
timestamp: res.timestamp
|
||||
}))
|
||||
resolve({
|
||||
latitude: res.latitude,
|
||||
longitude: res.longitude,
|
||||
address: '当前位置',
|
||||
time: new Date().toISOString().replace('T', ' ').substring(0, 19)
|
||||
})
|
||||
},
|
||||
fail: () => {
|
||||
fail: (err) => {
|
||||
console.warn('[CHECKIN DEBUG] wrapLocation FAIL:', JSON.stringify(err))
|
||||
reject(new Error('定位失败'))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type LocationFullResult = {
|
||||
location: DeliveryLocationType
|
||||
accuracy: number
|
||||
}
|
||||
|
||||
function wrapLocationFull(): Promise<LocationFullResult> {
|
||||
console.warn('[CHECKIN DEBUG] wrapLocationFull: starting...')
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.getLocation({
|
||||
type: 'gcj02',
|
||||
isHighAccuracy: true,
|
||||
enableHighAccuracy: true,
|
||||
success: (res) => {
|
||||
console.warn('[CHECKIN DEBUG] wrapLocationFull SUCCESS:', JSON.stringify({
|
||||
latitude: res.latitude,
|
||||
longitude: res.longitude,
|
||||
accuracy: res.accuracy,
|
||||
speed: res.speed,
|
||||
altitude: res.altitude,
|
||||
altitudeAccuracy: res.altitudeAccuracy,
|
||||
heading: res.heading,
|
||||
timestamp: res.timestamp
|
||||
}))
|
||||
const loc: DeliveryLocationType = {
|
||||
latitude: res.latitude,
|
||||
longitude: res.longitude,
|
||||
address: '当前位置',
|
||||
time: new Date().toISOString().replace('T', ' ').substring(0, 19)
|
||||
}
|
||||
const acc = res.accuracy != null ? res.accuracy : 0
|
||||
console.warn('[CHECKIN DEBUG] wrapLocationFull resolved with accuracy:', acc)
|
||||
resolve({ location: loc, accuracy: acc })
|
||||
},
|
||||
fail: (err) => {
|
||||
console.warn('[CHECKIN DEBUG] wrapLocationFull FAIL:', JSON.stringify(err))
|
||||
reject(new Error('定位失败'))
|
||||
}
|
||||
})
|
||||
@@ -77,18 +208,32 @@ function wrapChooseImage(): Promise<Array<string>> {
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
console.warn('[CHECKIN DEBUG] loadData: called, orderId:', orderId.value)
|
||||
const authResult = await requireDeliveryAuth({ redirectOnFail: true, toastOnFail: true })
|
||||
if (!authResult.ok || orderId.value == '') {
|
||||
console.warn('[CHECKIN DEBUG] loadData: auth failed or orderId empty')
|
||||
return
|
||||
}
|
||||
console.warn('[CHECKIN DEBUG] loadData: fetching order detail')
|
||||
order.value = await getDeliveryOrderDetail(orderId.value)
|
||||
console.warn('[CHECKIN DEBUG] loadData: order fetched:', JSON.stringify({
|
||||
id: order.value?.id,
|
||||
fullAddress: order.value?.fullAddress,
|
||||
latitude: order.value?.latitude,
|
||||
longitude: order.value?.longitude,
|
||||
allowCheckinRadiusMeters: order.value?.allowCheckinRadiusMeters
|
||||
}))
|
||||
updateHomecareLoginStatus()
|
||||
}
|
||||
|
||||
async function getCurrentLocation() {
|
||||
console.warn('[CHECKIN DEBUG] getCurrentLocation: called')
|
||||
try {
|
||||
currentLocation.value = await wrapLocation()
|
||||
locationText.value = '纬度 ' + String(currentLocation.value.latitude) + ' / 经度 ' + String(currentLocation.value.longitude)
|
||||
console.warn('[CHECKIN DEBUG] getCurrentLocation completed, location:', JSON.stringify(currentLocation.value))
|
||||
} catch (error) {
|
||||
console.warn('[CHECKIN DEBUG] getCurrentLocation error:', error)
|
||||
uni.showToast({ title: '签到定位失败,请检查定位权限', icon: 'none' })
|
||||
}
|
||||
}
|
||||
@@ -102,22 +247,123 @@ async function choosePhoto() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handlePrecheck(): Promise<void> {
|
||||
console.warn('[CHECKIN PAGE] ========== handlePrecheck START ==========')
|
||||
console.warn('[CHECKIN PAGE] 当前 orderId:', orderId.value)
|
||||
|
||||
if (prechecking.value) {
|
||||
console.warn('[CHECKIN PAGE] 已经在预校验中,直接返回')
|
||||
return
|
||||
}
|
||||
|
||||
if (orderId.value === '') {
|
||||
console.warn('[CHECKIN PAGE] orderId 为空')
|
||||
uni.showToast({ title: '工单信息缺失', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
console.warn('[CHECKIN PAGE] 开始预校验流程')
|
||||
prechecking.value = true
|
||||
canCheckin.value = false
|
||||
distanceText.value = '定位中...'
|
||||
allowedRadiusText.value = '定位中...'
|
||||
precheckStatusText.value = '定位中...'
|
||||
reasonText.value = ''
|
||||
|
||||
try {
|
||||
console.warn('[CHECKIN PAGE] 步骤 1: 调用 wrapLocationFull 获取位置')
|
||||
const fullResult = await wrapLocationFull()
|
||||
const location = fullResult.location
|
||||
const accuracy = fullResult.accuracy
|
||||
console.warn('[CHECKIN PAGE] 位置信息: lat=', location.latitude, ' lng=', location.longitude, ' accuracy=', accuracy)
|
||||
|
||||
currentLocation.value = location
|
||||
locationText.value = '纬度 ' + String(location.latitude) + ' / 经度 ' + String(location.longitude)
|
||||
accuracyText.value = accuracy > 0 ? String(accuracy) + ' 米' : '未知'
|
||||
|
||||
// 调用 RPC 预校验(现在直接走 Supabase,不需要本地后端)
|
||||
console.warn('[CHECKIN PAGE] 步骤 2: 调用 checkinPrecheck RPC')
|
||||
const result = await checkinPrecheck(
|
||||
orderId.value,
|
||||
location.latitude,
|
||||
location.longitude,
|
||||
accuracy
|
||||
)
|
||||
console.warn('[CHECKIN PAGE] RPC 返回结果: distance=', result.distanceMeters, ' radius=', result.allowedRadiusMeters, ' canCheckin=', result.canCheckin, ' reason=', result.reasonCode)
|
||||
|
||||
if (result.distanceMeters != null) {
|
||||
distanceText.value = String(result.distanceMeters) + ' 米'
|
||||
} else {
|
||||
distanceText.value = '未知'
|
||||
}
|
||||
allowedRadiusText.value = String(result.allowedRadiusMeters) + ' 米'
|
||||
|
||||
if (result.canCheckin) {
|
||||
canCheckin.value = true
|
||||
precheckStatusText.value = '可以签到 / 下一步拍照'
|
||||
reasonText.value = ''
|
||||
console.warn('[CHECKIN PAGE] 可以签到')
|
||||
} else {
|
||||
canCheckin.value = false
|
||||
precheckStatusText.value = '不可签到'
|
||||
reasonText.value = getReasonText(result.reasonCode)
|
||||
console.warn('[CHECKIN PAGE] 不可签到,原因:', result.reasonCode, getReasonText(result.reasonCode))
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[CHECKIN PAGE] ========== 捕获异常 ==========')
|
||||
console.warn('[CHECKIN PAGE] 异常类型:', error instanceof Error ? 'Error' : typeof error)
|
||||
console.warn('[CHECKIN PAGE] 异常信息:', error)
|
||||
console.warn('[CHECKIN PAGE] ========== handlePrecheck END (ERROR) ==========')
|
||||
|
||||
uni.showToast({ title: '预校验失败,请重试', icon: 'none' })
|
||||
precheckStatusText.value = '预校验失败'
|
||||
} finally {
|
||||
prechecking.value = false
|
||||
console.warn('[CHECKIN PAGE] ========== handlePrecheck END (FINISHED) ==========')
|
||||
}
|
||||
}
|
||||
|
||||
async function submitCheckin() {
|
||||
if (submitting.value) return
|
||||
if (order.value == null) return
|
||||
console.warn('[CHECKIN DEBUG] submitCheckin: called')
|
||||
if (submitting.value) {
|
||||
console.warn('[CHECKIN DEBUG] submitCheckin: already submitting, returning')
|
||||
return
|
||||
}
|
||||
if (order.value == null) {
|
||||
console.warn('[CHECKIN DEBUG] submitCheckin: order is null')
|
||||
uni.showToast({ title: '订单信息缺失', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (currentLocation.value == null) {
|
||||
console.warn('[CHECKIN DEBUG] submitCheckin: currentLocation is null, calling getCurrentLocation')
|
||||
await getCurrentLocation()
|
||||
if (currentLocation.value == null) return
|
||||
if (currentLocation.value == null) {
|
||||
console.warn('[CHECKIN DEBUG] submitCheckin: still null after getCurrentLocation')
|
||||
return
|
||||
}
|
||||
}
|
||||
if (photos.value.length == 0) {
|
||||
console.warn('[CHECKIN DEBUG] submitCheckin: no photos uploaded')
|
||||
uni.showToast({ title: '请至少上传一张现场图片', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const distance = calculateDistance(currentLocation.value.latitude, currentLocation.value.longitude, order.value.latitude, order.value.longitude)
|
||||
if (distance > order.value.allowCheckinRadiusMeters) {
|
||||
uni.showToast({ title: '距离服务地址超出允许范围,不能签到', icon: 'none' })
|
||||
|
||||
// RPC 预校验已经做了距离判断,这里直接提交
|
||||
// 保留坐标检查作为兜底,防止跳过预校验直接提交
|
||||
if (order.value.latitude == 0 && order.value.longitude == 0) {
|
||||
console.warn('[CHECKIN DEBUG] submitCheckin: order has no valid coordinates (lat=0, lng=0)')
|
||||
uni.showToast({ title: '订单缺少服务地址坐标', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const distance = calculateDistance(currentLocation.value.latitude, currentLocation.value.longitude, order.value.latitude, order.value.longitude)
|
||||
console.warn('[CHECKIN DEBUG] submitCheckin: calculated distance:', distance, 'meters, allowedRadius:', order.value.allowCheckinRadiusMeters)
|
||||
|
||||
console.warn('[CHECKIN DEBUG] submitCheckin: proceeding with checkin, photos count:', photos.value.length)
|
||||
doCheckin()
|
||||
}
|
||||
|
||||
async function doCheckin() {
|
||||
submitting.value = true
|
||||
try {
|
||||
await checkinOrder(orderId.value, {
|
||||
@@ -126,17 +372,24 @@ async function submitCheckin() {
|
||||
photos: photos.value,
|
||||
checkinMode: 'gps'
|
||||
})
|
||||
console.warn('[CHECKIN DEBUG] submitCheckin: checkinOrder succeeded')
|
||||
uni.showToast({ title: '签到成功', icon: 'success' })
|
||||
uni.redirectTo({ url: '/pages/mall/delivery/service-record/index?id=' + orderId.value })
|
||||
} catch (error) {
|
||||
console.warn('[CHECKIN DEBUG] submitCheckin error:', error)
|
||||
uni.showToast({ title: '签到失败,请重试', icon: 'none' })
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
console.warn('[CHECKIN DEBUG] onLoad: called, options:', JSON.stringify(options))
|
||||
if (options != null) {
|
||||
orderId.value = getDeliveryRouteParam(options as UTSJSONObject, 'id')
|
||||
console.warn('[CHECKIN DEBUG] onLoad: extracted orderId:', orderId.value)
|
||||
}
|
||||
console.warn('[CHECKIN DEBUG] onLoad: calling loadData')
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
@@ -150,6 +403,15 @@ onLoad((options) => {
|
||||
color: #16324f;
|
||||
}
|
||||
|
||||
.success-text {
|
||||
color: #0f766e;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.button-stack {
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -166,6 +428,10 @@ onLoad((options) => {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.primary-btn[disabled] {
|
||||
background: #9ca3af;
|
||||
}
|
||||
|
||||
.secondary-btn {
|
||||
background: #eaf2f0;
|
||||
color: #0f766e;
|
||||
@@ -179,4 +445,4 @@ onLoad((options) => {
|
||||
background: #f2f7f6;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<view class="timeline-box">
|
||||
<view v-for="item in order.timeline" :key="item.id" class="timeline-item">
|
||||
<text class="timeline-title">{{ item.title }}</text>
|
||||
<text class="timeline-meta">{{ item.time }}</text>
|
||||
<text class="timeline-meta">{{ formatDateTime(item.time) }}</text>
|
||||
<text class="timeline-desc">{{ item.description }}</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -22,7 +22,7 @@
|
||||
<text class="section-title">服务信息</text>
|
||||
<text class="row-text">服务名称:{{ order.serviceName }}</text>
|
||||
<text class="row-text">服务类型:{{ order.serviceType }}</text>
|
||||
<text class="row-text">预约时间:{{ order.appointmentTime }}</text>
|
||||
<text class="row-text">预约时间:{{ formatDateTime(order.appointmentTime) }}</text>
|
||||
<text class="row-text">预计时长:{{ order.duration }} 分钟</text>
|
||||
<text class="row-text">服务价格:¥{{ order.price }}</text>
|
||||
<text class="row-text">预计收入:¥{{ order.staffIncome }}</text>
|
||||
@@ -71,7 +71,7 @@
|
||||
<text class="section-title">异常记录</text>
|
||||
<text class="row-text">异常类型:{{ order.abnormalReport!.type }}</text>
|
||||
<text class="row-text">异常说明:{{ order.abnormalReport!.description }}</text>
|
||||
<text class="row-text">发生时间:{{ order.abnormalReport!.occurredAt }}</text>
|
||||
<text class="row-text">发生时间:{{ formatDateTime(order.abnormalReport!.occurredAt) }}</text>
|
||||
</view>
|
||||
|
||||
<view class="action-card">
|
||||
@@ -98,6 +98,7 @@ import {
|
||||
import { getNextStepText, getPrimaryActionText } from '@/utils/deliveryCareUi.uts'
|
||||
import { requireDeliveryAuth } from '@/utils/deliveryAuth.uts'
|
||||
import { getDeliveryRouteParam } from '@/utils/deliveryRoute.uts'
|
||||
import { formatDateTime } from '@/utils/utils.uts'
|
||||
|
||||
const orderId = ref('')
|
||||
const order = ref<DeliveryOrderType | null>(null)
|
||||
@@ -112,6 +113,7 @@ async function loadData() {
|
||||
return
|
||||
}
|
||||
order.value = await getServiceOrderDetail(orderId.value)
|
||||
console.warn('[orders/detail] order detail:', JSON.stringify(order.value))
|
||||
}
|
||||
|
||||
function joinTags(tags: Array<string>): string {
|
||||
@@ -316,4 +318,4 @@ onLoad((options) => {
|
||||
font-size: 26rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<view class="order-top" @click="goDetail(item.id)">
|
||||
<view class="order-main">
|
||||
<text class="order-title">{{ item.serviceName }}</text>
|
||||
<text class="order-subtitle">{{ item.elderName }} · {{ item.appointmentTime }}</text>
|
||||
<text class="order-subtitle">{{ item.elderName }} · {{ formatDateTime(item.appointmentTime) }}</text>
|
||||
</view>
|
||||
<text class="order-status">{{ item.statusText }}</text>
|
||||
</view>
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
} from '@/services/deliveryService.uts'
|
||||
import { getDeliveryOrderTabs, getPrimaryActionText } from '@/utils/deliveryCareUi.uts'
|
||||
import { requireDeliveryAuth } from '@/utils/deliveryAuth.uts'
|
||||
import { formatDateTime } from '@/utils/utils.uts'
|
||||
|
||||
const tabs = getDeliveryOrderTabs()
|
||||
const currentTab = ref('pending')
|
||||
@@ -68,13 +69,16 @@ async function loadData() {
|
||||
}
|
||||
if (currentTab.value == 'pending') {
|
||||
orders.value = await getPendingServiceOrders()
|
||||
console.warn('[orders/index] pending orders:', JSON.stringify(orders.value))
|
||||
return
|
||||
}
|
||||
if (currentTab.value == 'history') {
|
||||
orders.value = await getHistoryServiceOrders()
|
||||
console.warn('[orders/index] history orders:', JSON.stringify(orders.value))
|
||||
return
|
||||
}
|
||||
orders.value = await getTodayServiceOrders()
|
||||
console.warn('[orders/index] today orders:', JSON.stringify(orders.value))
|
||||
}
|
||||
|
||||
function consumeStoredTab(): void {
|
||||
@@ -342,4 +346,4 @@ onShow(() => {
|
||||
.empty-box {
|
||||
padding: 24rpx 0;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<ServicePageScaffold title="出发与导航" fallback-url="/pages/mall/delivery/orders/detail">
|
||||
<ServicePanel title="路线信息" subtitle="支持出发状态回写、地图导航和位置上报。">
|
||||
<text v-if="order != null" class="info-text">服务地址:{{ order.fullAddress }}</text>
|
||||
<text v-if="order != null" class="info-text">预约时间:{{ order.appointmentStartTime }}</text>
|
||||
<text v-if="order != null" class="info-text">预约时间:{{ formatDateTime(order.appointmentStartTime) }}</text>
|
||||
<text class="info-text">当前位置:{{ currentLocationText }}</text>
|
||||
</ServicePanel>
|
||||
<ServicePanel title="执行动作" subtitle="先获取位置,再执行出发或到达。">
|
||||
@@ -25,6 +25,7 @@ import type { DeliveryLocationType, DeliveryOrderType } from '@/types/delivery.u
|
||||
import { arriveOrder, getDeliveryOrderDetail, startDepart } from '@/services/deliveryService.uts'
|
||||
import { requireDeliveryAuth } from '@/utils/deliveryAuth.uts'
|
||||
import { getDeliveryRouteParam } from '@/utils/deliveryRoute.uts'
|
||||
import { formatDateTime } from '@/utils/utils.uts'
|
||||
|
||||
const orderId = ref('')
|
||||
const order = ref<DeliveryOrderType | null>(null)
|
||||
|
||||
@@ -86,6 +86,43 @@ const photoCount = ref(0)
|
||||
|
||||
const photosText = computed((): string => '已添加占位照片 ' + String(photoCount.value) + ' 张')
|
||||
|
||||
function formatDisplayTime(isoStr: string): string {
|
||||
if (isoStr == '') {
|
||||
return ''
|
||||
}
|
||||
// 如果已经是 YYYY-MM-DD HH:mm 格式,直接返回
|
||||
if (isoStr.indexOf('T') == -1 && isoStr.length >= 16) {
|
||||
return isoStr.substring(0, 16)
|
||||
}
|
||||
// 处理 ISO 8601: 2026-05-19T03:00:00+08:00
|
||||
const parts = isoStr.split('T')
|
||||
if (parts.length < 2) {
|
||||
return isoStr
|
||||
}
|
||||
const datePart = parts[0]
|
||||
let timePart = parts[1]
|
||||
// 去掉时区后缀
|
||||
const plusIdx = timePart.indexOf('+')
|
||||
if (plusIdx != -1) {
|
||||
timePart = timePart.substring(0, plusIdx)
|
||||
}
|
||||
const zIdx = timePart.indexOf('Z')
|
||||
if (zIdx != -1) {
|
||||
timePart = timePart.substring(0, zIdx)
|
||||
}
|
||||
// 去掉毫秒
|
||||
const dotIdx = timePart.indexOf('.')
|
||||
if (dotIdx != -1) {
|
||||
timePart = timePart.substring(0, dotIdx)
|
||||
}
|
||||
// 取 HH:mm
|
||||
const timeSegments = timePart.split(':')
|
||||
if (timeSegments.length >= 2) {
|
||||
return datePart + ' ' + timeSegments[0] + ':' + timeSegments[1]
|
||||
}
|
||||
return datePart + ' ' + timePart
|
||||
}
|
||||
|
||||
function toggleItem(itemId: string, event: any) {
|
||||
for (let i = 0; i < serviceItems.value.length; i++) {
|
||||
if (serviceItems.value[i].id == itemId) {
|
||||
@@ -111,8 +148,8 @@ async function loadData() {
|
||||
order.value = await getServiceOrderDetail(orderId.value)
|
||||
if (order.value != null) {
|
||||
serviceItems.value = order.value.serviceItems
|
||||
startTime.value = order.value.startServiceTime != null && order.value.startServiceTime != '' ? order.value.startServiceTime : order.value.appointmentTime
|
||||
endTime.value = order.value.finishTime
|
||||
startTime.value = formatDisplayTime(order.value.startServiceTime != null && order.value.startServiceTime != '' ? order.value.startServiceTime : order.value.appointmentTime)
|
||||
endTime.value = formatDisplayTime(order.value.finishTime)
|
||||
processNote.value = order.value.serviceSummary
|
||||
staffRemark.value = order.value.progressNote
|
||||
if (order.value.serviceRecord != null) {
|
||||
|
||||
@@ -173,10 +173,14 @@ const loginType = ref<number>(0)
|
||||
// 默认账号密码(唯一来源,修改只改这里)
|
||||
// 必须在 account/password ref 之前声明,否则 ref 初始化时无法引用
|
||||
// ─────────────────────────────────────────────
|
||||
const CONSUMER_TEST_ACCOUNT = 'test@mall.com'
|
||||
const CONSUMER_TEST_PASSWORD = 'Hf2152111'
|
||||
const MERCHANT_TEST_ACCOUNT = 'test19@163.com'
|
||||
const MERCHANT_TEST_PASSWORD = 'huang123456'
|
||||
// const TEST_ACCOUNT = 'test@mall.com' // ← 旧账号(已停用)
|
||||
// const TEST_PASSWORD = 'Hf2152111' // ← 旧密码(已停用)
|
||||
const TEST_ACCOUNT = 'test20@163.com'
|
||||
const TEST_PASSWORD = 'huang123456'
|
||||
|
||||
// delivery 端默认账号(居家服务员)
|
||||
const DELIVERY_TEST_ACCOUNT = 'homecare_worker@test.com'
|
||||
const DELIVERY_TEST_PASSWORD = 'Homecare123!'
|
||||
|
||||
// ✅ account/password 直接以常量作初始值,上线/刷新立即生效,不再依赖 onMounted 延迟赋值
|
||||
const account = ref<string>(CONSUMER_TEST_ACCOUNT)
|
||||
@@ -401,8 +405,8 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
if (isDeliveryMode()) {
|
||||
account.value = ''
|
||||
password.value = ''
|
||||
account.value = DELIVERY_TEST_ACCOUNT
|
||||
password.value = DELIVERY_TEST_PASSWORD
|
||||
} else if (isMerchantMode()) {
|
||||
account.value = MERCHANT_TEST_ACCOUNT
|
||||
password.value = MERCHANT_TEST_PASSWORD
|
||||
|
||||
Reference in New Issue
Block a user