Files
medical-mall/api/delivery.uts
2026-05-21 15:51:12 +08:00

1746 lines
55 KiB
Plaintext
Raw Permalink 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.
import type {
DeliveryCertificateType,
DeliveryCheckinPayloadType,
DeliveryDashboardType,
DeliveryTimelineItemType,
DeliveryExceptionPayloadType,
DeliveryFinishPayloadType,
DeliveryInfoType,
DeliveryLocationType,
DeliveryLoginPayloadType,
DeliveryLoginResultType,
DeliveryMessageType,
DeliveryOrderQueryType,
DeliveryOrderType,
DeliveryProgressPayloadType,
DeliveryRecordType,
DeliveryEvidenceRecordType,
DeliveryServiceItemType
} from '@/types/delivery.uts'
import supa, { ensureSupabaseReady } from '@/components/supadb/aksupainstance.uts'
import { getCurrentUser } from '@/utils/store.uts'
import type { UserProfile } from '@/types/mall-types.uts'
import { IS_TEST_MODE } from '@/ak/config.uts'
const DELIVERY_RPC_DASHBOARD = 'rpc_delivery_dashboard'
const DELIVERY_RPC_ORDER_LIST = 'rpc_delivery_order_list'
const DELIVERY_RPC_ORDER_DETAIL = 'rpc_delivery_order_detail'
const DELIVERY_RPC_ACCEPT_ORDER = 'rpc_delivery_accept_order'
const DELIVERY_RPC_REJECT_ORDER = 'rpc_delivery_reject_order'
const DELIVERY_RPC_START_DEPART = 'rpc_delivery_start_depart'
const DELIVERY_RPC_ARRIVE_ORDER = 'rpc_delivery_arrive_order'
const DELIVERY_RPC_CHECKIN_ORDER = 'rpc_delivery_checkin_order'
const DELIVERY_RPC_START_SERVICE = 'rpc_delivery_start_service'
const DELIVERY_RPC_SAVE_PROGRESS = 'rpc_delivery_save_progress'
const DELIVERY_RPC_UPLOAD_EVIDENCE = 'rpc_delivery_upload_evidence'
const DELIVERY_RPC_RETRY_EVIDENCE = 'rpc_delivery_retry_evidence'
const DELIVERY_RPC_SUBMIT_EXCEPTION = 'rpc_delivery_submit_exception'
const DELIVERY_RPC_FINISH_SERVICE = 'rpc_delivery_finish_service'
const DELIVERY_RPC_RECORD_LIST = 'rpc_delivery_record_list'
const DELIVERY_RPC_MESSAGE_LIST = 'rpc_delivery_message_list'
const DELIVERY_RPC_SET_ONLINE_STATUS = 'rpc_delivery_set_online_status'
const DELIVERY_MOCK_PROFILE_KEY = 'delivery_mock_profile'
const DELIVERY_MOCK_ORDERS_KEY = 'delivery_mock_orders'
const DELIVERY_FORCE_REMOTE_KEY = 'delivery_force_remote'
const missingDeliveryRpcNames = [] as Array<string>
function isMissingDeliveryRpc(functionName: string): boolean {
for (let i = 0; i < missingDeliveryRpcNames.length; i++) {
if (missingDeliveryRpcNames[i] == functionName) {
return true
}
}
return false
}
function markMissingDeliveryRpc(functionName: string): void {
if (!isMissingDeliveryRpc(functionName)) {
missingDeliveryRpcNames.push(functionName)
}
}
function mapLoginError(rawData: UTSJSONObject): string {
const errorMsg = rawData.getString('msg') ?? rawData.getString('message') ?? rawData.getString('error') ?? ''
const errorCode = rawData.getString('error_code') ?? ''
if ((errorMsg.includes('email') && errorMsg.includes('confirm')) || errorCode === 'email_not_confirmed' || (errorMsg.includes('邮箱') && errorMsg.includes('确认'))) {
return '邮箱未确认,请先检查邮箱并点击确认链接'
}
if (errorMsg.includes('Invalid authentication credentials')) {
return '网关认证失败,请检查 delivery 端的 SUPA_URL 与 SUPA_KEY 是否属于同一套 Supabase 实例'
}
if (errorMsg.includes('Invalid login credentials') || errorCode === 'invalid_credentials' || errorMsg.includes('Invalid credentials') || errorMsg.includes('credentials') || errorMsg.includes('invalid')) {
return '用户名或密码错误'
}
return errorMsg != '' ? errorMsg : '登录失败,请重试'
}
function readObjectField(raw: any, key: string): any {
if (raw == null) {
return null
}
if (typeof raw.getJSONObject === 'function') {
const nested = raw.getJSONObject(key)
if (nested != null) {
return nested
}
}
try {
const plain = JSON.parse(JSON.stringify(raw)) as any
return plain[key]
} catch (error) {}
return null
}
function readStringField(raw: any, key: string): string {
if (raw == null) {
return ''
}
if (typeof raw.getString === 'function') {
return raw.getString(key) ?? ''
}
try {
const plain = JSON.parse(JSON.stringify(raw)) as any
const value = plain[key]
if (typeof value == 'string') {
return value
}
if (value != null) {
return String(value)
}
} catch (error) {}
return ''
}
function readNumberField(raw: any, key: string): number | null {
if (raw == null) {
return null
}
if (typeof raw.getNumber === 'function') {
return raw.getNumber(key)
}
try {
const plain = JSON.parse(JSON.stringify(raw)) as any
const value = plain[key]
if (typeof value == 'number') {
return value
}
} catch (error) {}
return null
}
function readBooleanField(raw: any, key: string): boolean | null {
if (raw == null) {
return null
}
if (typeof raw.getBoolean === 'function') {
return raw.getBoolean(key)
}
try {
const plain = JSON.parse(JSON.stringify(raw)) as any
const value = plain[key]
if (typeof value == 'boolean') {
return value
}
} catch (error) {}
return null
}
function normalizeDeliveryStatus(raw: any): 'active' | 'disabled' | 'locked' | 'suspended' {
const status = readNumberField(raw, 'status')
const isActive = readBooleanField(raw, 'is_active')
if (status === 0 || isActive === false) {
return 'disabled'
}
return 'active'
}
function normalizeOnlineStatus(raw: any): 'online' | 'resting' | 'busy' {
const value = readStringField(raw, 'online_status')
if (value == 'online' || value == 'busy') {
return value
}
return 'resting'
}
function normalizeCertificateStatus(raw: any): 'valid' | 'expired' | 'pending' {
const value = readStringField(raw, 'certificate_status')
if (value == 'valid' || value == 'expired') {
return value
}
return 'pending'
}
function normalizeStringArray(rawValue: string): Array<string> {
const value = rawValue.trim()
if (value == '') {
return [] as Array<string>
}
try {
const parsed = JSON.parse(value)
if (parsed instanceof Array) {
const result = [] as Array<string>
for (let i = 0; i < parsed.length; i++) {
const item = parsed[i]
if (typeof item == 'string') {
result.push(item)
}
}
return result
}
} catch (error) {}
return [] as Array<string>
}
function readStationObject(raw: any): any {
const station = readObjectField(raw, 'station')
if (station != null) {
return station
}
const nested = readObjectField(raw, 'ml_delivery_stations')
if (nested != null) {
return nested
}
return null
}
function buildDeliveryInfoFromStaff(raw: any): DeliveryInfoType {
const id = readStringField(raw, 'id')
const station = readStationObject(raw)
const stationId = readStringField(raw, 'station_id')
const skillsRaw = readStringField(raw, 'skills')
const certificateExpireAt = readStringField(raw, 'certificate_expire_at')
const organizationName = station != null ? readStringField(station, 'name') : ''
return {
id,
userId: readStringField(raw, 'uid'),
staffNo: readStringField(raw, 'staff_no') != '' ? readStringField(raw, 'staff_no') : (id != '' ? 'DEL-' + id.substring(0, Math.min(8, id.length)).toUpperCase() : ''),
name: readStringField(raw, 'nickname') != '' ? readStringField(raw, 'nickname') : '服务人员',
phone: readStringField(raw, 'phone'),
role: 'delivery',
status: normalizeDeliveryStatus(raw),
organizationId: stationId,
organizationName: organizationName != '' ? organizationName : stationId,
certificates: [] as Array<any>,
certificateStatus: normalizeCertificateStatus(raw),
certificateExpireAt,
onlineStatus: normalizeOnlineStatus(raw),
serviceArea: readStringField(raw, 'service_area'),
skills: normalizeStringArray(skillsRaw),
avatarUrl: readStringField(raw, 'avatar'),
todayAccepted: 0,
todayServing: 0,
todayCompleted: 0,
usesMock: false
} as DeliveryInfoType
}
async function fetchDeliveryProfileFromRemote(userId: string): Promise<DeliveryInfoType | null> {
if (userId == '') {
return null
}
const selectFields = '*, station:ml_delivery_stations(id, name)'
try {
let response = await supa.from('ml_delivery_staff').select(selectFields).eq('uid', userId).limit(1).execute()
if (response.error == null && response.data != null) {
const rows = response.data as Array<UTSJSONObject>
if (rows.length > 0) {
return buildDeliveryInfoFromStaff(rows[0])
}
}
response = await supa.from('ml_delivery_staff').select(selectFields).eq('id', userId).limit(1).execute()
if (response.error == null && response.data != null) {
const rows = response.data as Array<UTSJSONObject>
if (rows.length > 0) {
return buildDeliveryInfoFromStaff(rows[0])
}
}
} catch (error) {
console.warn('[delivery api] 读取 ml_delivery_staff 失败,回退 mock', error)
}
return null
}
function isDelivererRole(userInfo: UserProfile | null): boolean {
if (userInfo == null) {
return false
}
const role = userInfo.role ?? ''
return role == 'delivery' || role == 'Deliverer'
}
function shouldBypassDeliveryRpc(): boolean {
if (IS_TEST_MODE !== true) {
return false
}
const forceRemote = uni.getStorageSync(DELIVERY_FORCE_REMOTE_KEY)
if (forceRemote === true) {
return false
}
if (typeof forceRemote == 'string' && (forceRemote == 'true' || forceRemote == '1')) {
return false
}
return true
}
async function callDeliveryRpc(functionName: string, params: UTSJSONObject): Promise<any> {
if (shouldBypassDeliveryRpc()) {
return null
}
if (isMissingDeliveryRpc(functionName)) {
return null
}
try {
await ensureSupabaseReady()
const res: any = await supa.rpc(functionName, params)
if (res?.status === 404) {
markMissingDeliveryRpc(functionName)
console.warn('[delivery api] RPC 不存在,已切换 fallback' + functionName)
return null
}
if (res?.error != null) {
throw res.error
}
return res.data
} catch (error) {
console.warn('[delivery api] RPC 调用失败,回退 mock' + functionName, error)
return null
}
}
function normalizeRpcOrderList(data: any): Array<DeliveryOrderType> | null {
if (!Array.isArray(data)) {
return null
}
return data as Array<DeliveryOrderType>
}
function normalizeRpcMessages(data: any): Array<DeliveryMessageType> | null {
if (!Array.isArray(data)) {
return null
}
return data as Array<DeliveryMessageType>
}
function normalizeRpcRecords(data: any): Array<DeliveryRecordType> | null {
if (!Array.isArray(data)) {
return null
}
return data as Array<DeliveryRecordType>
}
function normalizeRpcEvidenceList(data: any): Array<DeliveryEvidenceRecordType> | null {
if (!Array.isArray(data)) {
return null
}
return data as Array<DeliveryEvidenceRecordType>
}
function normalizeRpcOrder(data: any): DeliveryOrderType | null {
if (data == null) {
return null
}
return data as DeliveryOrderType
}
function normalizeRpcDashboard(data: any): DeliveryDashboardType | null {
if (data == null) {
return null
}
return data as DeliveryDashboardType
}
function normalizeRpcDeliveryInfo(data: any): DeliveryInfoType | null {
if (data == null) {
return null
}
return data as DeliveryInfoType
}
function cloneDeliveryInfo(info: DeliveryInfoType): DeliveryInfoType {
return JSON.parse(JSON.stringify(info)) as DeliveryInfoType
}
function cloneDeliveryOrders(orders: Array<DeliveryOrderType>): Array<DeliveryOrderType> {
return JSON.parse(JSON.stringify(orders)) as Array<DeliveryOrderType>
}
function cloneDeliveryOrder(order: DeliveryOrderType): DeliveryOrderType {
return JSON.parse(JSON.stringify(order)) as DeliveryOrderType
}
function cloneDeliveryMessages(messages: Array<DeliveryMessageType>): Array<DeliveryMessageType> {
return JSON.parse(JSON.stringify(messages)) as Array<DeliveryMessageType>
}
function cloneDeliveryRecords(records: Array<DeliveryRecordType>): Array<DeliveryRecordType> {
return JSON.parse(JSON.stringify(records)) as Array<DeliveryRecordType>
}
function isObjectLike(value: any): boolean {
return value != null && typeof value == 'object'
}
function isValidDeliveryInfoCache(value: any): boolean {
if (!isObjectLike(value)) {
return false
}
const info = value as DeliveryInfoType
return typeof info.id == 'string' && typeof info.userId == 'string' && typeof info.name == 'string'
}
function isValidDeliveryOrdersCache(value: any): boolean {
if (!Array.isArray(value)) {
return false
}
for (let i = 0; i < value.length; i++) {
const item = value[i] as any
if (!isObjectLike(item) || typeof item.id != 'string' || typeof item.orderNo != 'string' || typeof item.status != 'string') {
return false
}
}
return true
}
function currentDateTimeText(): string {
return new Date().toISOString().replace('T', ' ').substring(0, 19)
}
function buildMockCertificates(): Array<DeliveryCertificateType> {
return [
{
id: 'mock-cert-1',
name: '养老护理员职业技能证书',
status: 'valid',
expireAt: '2027-12-31',
issuer: '梅州市居家养老服务协会',
imageUrl: ''
} as DeliveryCertificateType,
{
id: 'mock-cert-2',
name: '慢病随访服务培训合格证',
status: 'valid',
expireAt: '长期有效',
issuer: '粤东健康照护培训中心',
imageUrl: ''
} as DeliveryCertificateType
] as Array<DeliveryCertificateType>
}
function buildMockServiceItems(orderId: string, serviceName: string): Array<DeliveryServiceItemType> {
return [
{
id: orderId + '-item-1',
name: serviceName + '签到核验',
required: true,
completed: true,
incompleteReason: '',
remark: '已完成身份核验与入户风险提示。',
updatedAt: '2026-05-18 08:45:00'
} as DeliveryServiceItemType,
{
id: orderId + '-item-2',
name: '生命体征记录',
required: true,
completed: true,
incompleteReason: '',
remark: '血压、脉搏、血氧记录完整。',
updatedAt: '2026-05-18 09:10:00'
} as DeliveryServiceItemType,
{
id: orderId + '-item-3',
name: '家属沟通与宣教',
required: true,
completed: false,
incompleteReason: '',
remark: '',
updatedAt: ''
} as DeliveryServiceItemType
] as Array<DeliveryServiceItemType>
}
function buildMockTimeline(orderNo: string, phase: string): Array<DeliveryTimelineItemType> {
return [
{
id: orderNo + '-tl-1',
title: '工单已生成',
time: '2026-05-18 07:30:00',
description: '系统已创建服务任务并进入调度流程。'
} as DeliveryTimelineItemType,
{
id: orderNo + '-tl-2',
title: '调度已派发',
time: '2026-05-18 07:50:00',
description: '工单已分配至当前服务人员。'
} as DeliveryTimelineItemType,
{
id: orderNo + '-tl-3',
title: phase,
time: '2026-05-18 08:20:00',
description: '当前环节已进入执行节点。'
} as DeliveryTimelineItemType
] as Array<DeliveryTimelineItemType>
}
function buildMockEvidence(orderId: string): Array<DeliveryEvidenceRecordType> {
return [
{
id: orderId + '-evi-1',
orderId,
phase: 'checkin',
fileType: 'image',
name: '到场签到照片',
url: '',
localPath: '/static/mock/checkin-' + orderId + '.jpg',
status: 'success',
progress: 100,
retryable: false,
createdAt: '2026-05-18 08:42:00'
} as DeliveryEvidenceRecordType
] as Array<DeliveryEvidenceRecordType>
}
function buildMockProfile(userId: string): DeliveryInfoType {
const resolvedUserId = userId != '' ? userId : 'mock-user-delivery-001'
return {
id: 'mock-staff-001',
userId: resolvedUserId,
staffNo: 'DEL-0520',
name: '陈护理',
phone: '13688886666',
role: 'delivery',
status: 'active',
organizationId: 'org-001',
organizationName: '梅州安康居家服务中心',
certificates: buildMockCertificates(),
certificateStatus: 'valid',
certificateExpireAt: '2027-12-31',
onlineStatus: 'online',
serviceArea: '梅江区 / 梅县区重点片区',
skills: ['基础护理', '慢病随访', '康复指导'],
avatarUrl: '',
todayAccepted: 0,
todayServing: 0,
todayCompleted: 0,
usesMock: true
} as DeliveryInfoType
}
function buildMockOrders(profile: DeliveryInfoType): Array<DeliveryOrderType> {
return [
{
id: 'mock-order-001',
orderNo: 'YS202605180001',
serviceType: '基础照护',
serviceName: '基础上门护理',
serviceItems: buildMockServiceItems('mock-order-001', '基础上门护理'),
elderId: 'elder-001',
elderNameMasked: '李奶奶',
elderPhoneMasked: '138****1024',
fullElderName: '李秀珍',
fullPhone: '13800131024',
addressSummary: '梅江区江南路 18 号 2 栋 602',
fullAddress: '广东省梅州市梅江区江南路 18 号 2 栋 602 室',
latitude: 24.2898,
longitude: 116.1179,
appointmentStartTime: '2026-05-18 09:00',
appointmentEndTime: '2026-05-18 11:00',
estimatedDuration: 120,
actualStartTime: '',
actualEndTime: '',
status: 'pending_accept',
statusText: '待接单',
statusTone: 'warning',
riskTags: ['跌倒风险', '晨间血压波动'],
careLevel: '护理二级',
merchantId: 'merchant-001',
merchantName: '梅州安康居家服务中心',
deliveryStaffId: profile.id,
deliveryStaffName: profile.name,
acceptTime: '',
departTime: '',
arriveTime: '',
checkinTime: '',
finishTime: '',
cancelReason: '',
exceptionType: '',
exceptionDesc: '',
evidenceList: [] as Array<DeliveryEvidenceRecordType>,
signatureUrl: '',
signatureName: '',
satisfactionStatus: '待评价',
settlementStatus: '待结算',
archiveStatus: '未归档',
createdAt: '2026-05-18 07:30:00',
updatedAt: '2026-05-18 07:50:00',
contactName: '李晓兰',
contactPhone: '13900139000',
notices: ['入户前需电话确认', '注意老人步态不稳'],
timeline: buildMockTimeline('YS202605180001', '等待服务人员接单'),
serviceSummary: '',
progressNote: '',
distanceKm: '2.4km',
allowCheckinRadiusMeters: 100,
lastLocation: null,
trackPoints: [] as Array<DeliveryLocationType>
} as DeliveryOrderType,
{
id: 'mock-order-002',
orderNo: 'YS202605180002',
serviceType: '康复指导',
serviceName: '居家康复训练指导',
serviceItems: buildMockServiceItems('mock-order-002', '康复训练'),
elderId: 'elder-002',
elderNameMasked: '张爷爷',
elderPhoneMasked: '137****2233',
fullElderName: '张志坤',
fullPhone: '13700132233',
addressSummary: '梅县区新城锦绣花园 5 栋 1403',
fullAddress: '广东省梅州市梅县区新城锦绣花园 5 栋 1403 室',
latitude: 24.3071,
longitude: 116.1365,
appointmentStartTime: '2026-05-18 10:30',
appointmentEndTime: '2026-05-18 12:00',
estimatedDuration: 90,
actualStartTime: '',
actualEndTime: '',
status: 'accepted',
statusText: '待出发',
statusTone: 'primary',
riskTags: ['术后恢复'],
careLevel: '康复随访',
merchantId: 'merchant-001',
merchantName: '梅州安康居家服务中心',
deliveryStaffId: profile.id,
deliveryStaffName: profile.name,
acceptTime: '2026-05-18 08:00:00',
departTime: '',
arriveTime: '',
checkinTime: '',
finishTime: '',
cancelReason: '',
exceptionType: '',
exceptionDesc: '',
evidenceList: [] as Array<DeliveryEvidenceRecordType>,
signatureUrl: '',
signatureName: '',
satisfactionStatus: '待评价',
settlementStatus: '待结算',
archiveStatus: '未归档',
createdAt: '2026-05-18 07:40:00',
updatedAt: '2026-05-18 08:00:00',
contactName: '张春梅',
contactPhone: '13600136666',
notices: ['需携带康复弹力带', '家属会同步在场'],
timeline: buildMockTimeline('YS202605180002', '已接单,等待出发'),
serviceSummary: '',
progressNote: '',
distanceKm: '5.8km',
allowCheckinRadiusMeters: 120,
lastLocation: null,
trackPoints: [] as Array<DeliveryLocationType>
} as DeliveryOrderType,
{
id: 'mock-order-003',
orderNo: 'YS202605180003',
serviceType: '慢病随访',
serviceName: '慢病健康随访',
serviceItems: buildMockServiceItems('mock-order-003', '慢病随访'),
elderId: 'elder-003',
elderNameMasked: '黄阿姨',
elderPhoneMasked: '135****5566',
fullElderName: '黄玉英',
fullPhone: '13500135566',
addressSummary: '梅江区学海路康养公寓 A 座 907',
fullAddress: '广东省梅州市梅江区学海路康养公寓 A 座 907 室',
latitude: 24.2861,
longitude: 116.1231,
appointmentStartTime: '2026-05-18 13:30',
appointmentEndTime: '2026-05-18 15:00',
estimatedDuration: 90,
actualStartTime: '',
actualEndTime: '',
status: 'arrived',
statusText: '待签到',
statusTone: 'primary',
riskTags: ['糖尿病', '需测血糖'],
careLevel: '慢病管理',
merchantId: 'merchant-001',
merchantName: '梅州安康居家服务中心',
deliveryStaffId: profile.id,
deliveryStaffName: profile.name,
acceptTime: '2026-05-18 08:10:00',
departTime: '2026-05-18 13:00:00',
arriveTime: '2026-05-18 13:22:00',
checkinTime: '',
finishTime: '',
cancelReason: '',
exceptionType: '',
exceptionDesc: '',
evidenceList: [] as Array<DeliveryEvidenceRecordType>,
signatureUrl: '',
signatureName: '',
satisfactionStatus: '待评价',
settlementStatus: '待结算',
archiveStatus: '未归档',
createdAt: '2026-05-18 08:00:00',
updatedAt: '2026-05-18 13:22:00',
contactName: '黄晓娟',
contactPhone: '13500130088',
notices: ['入户前请确认血糖试纸数量', '注意老人午后低血糖风险'],
timeline: buildMockTimeline('YS202605180003', '已到达服务地点'),
serviceSummary: '',
progressNote: '已联系家属,准备入户签到。',
distanceKm: '1.1km',
allowCheckinRadiusMeters: 80,
lastLocation: {
latitude: 24.2861,
longitude: 116.1231,
address: '康养公寓门口',
time: '2026-05-18 13:22:00'
} as DeliveryLocationType,
trackPoints: [] as Array<DeliveryLocationType>
} as DeliveryOrderType,
{
id: 'mock-order-004',
orderNo: 'YS202605180004',
serviceType: '居家护理',
serviceName: '压疮护理与翻身指导',
serviceItems: [
{
id: 'mock-order-004-item-1',
name: '压疮护理',
required: true,
completed: true,
incompleteReason: '',
remark: '已完成创面清洁与敷料更换。',
updatedAt: '2026-05-18 14:25:00'
} as DeliveryServiceItemType,
{
id: 'mock-order-004-item-2',
name: '翻身指导',
required: true,
completed: true,
incompleteReason: '',
remark: '已向家属示范翻身角度与频次。',
updatedAt: '2026-05-18 14:40:00'
} as DeliveryServiceItemType,
{
id: 'mock-order-004-item-3',
name: '家属签字确认',
required: true,
completed: false,
incompleteReason: '',
remark: '',
updatedAt: ''
} as DeliveryServiceItemType
] as Array<DeliveryServiceItemType>,
elderId: 'elder-004',
elderNameMasked: '陈伯伯',
elderPhoneMasked: '139****3301',
fullElderName: '陈国辉',
fullPhone: '13900133301',
addressSummary: '梅县区扶老社区 11 栋 203',
fullAddress: '广东省梅州市梅县区扶老社区 11 栋 203 室',
latitude: 24.3022,
longitude: 116.1441,
appointmentStartTime: '2026-05-18 14:00',
appointmentEndTime: '2026-05-18 16:00',
estimatedDuration: 120,
actualStartTime: '2026-05-18 14:05:00',
actualEndTime: '',
status: 'serving',
statusText: '服务中',
statusTone: 'primary',
riskTags: ['卧床护理', '家属需培训'],
careLevel: '护理三级',
merchantId: 'merchant-001',
merchantName: '梅州安康居家服务中心',
deliveryStaffId: profile.id,
deliveryStaffName: profile.name,
acceptTime: '2026-05-18 09:20:00',
departTime: '2026-05-18 13:20:00',
arriveTime: '2026-05-18 13:55:00',
checkinTime: '2026-05-18 14:00:00',
finishTime: '',
cancelReason: '',
exceptionType: '',
exceptionDesc: '',
evidenceList: buildMockEvidence('mock-order-004'),
signatureUrl: '',
signatureName: '',
satisfactionStatus: '待评价',
settlementStatus: '待结算',
archiveStatus: '未归档',
createdAt: '2026-05-18 09:00:00',
updatedAt: '2026-05-18 14:40:00',
contactName: '陈丽芳',
contactPhone: '13800136660',
notices: ['护理过程注意皮肤完整性', '家属需学习翻身技巧'],
timeline: buildMockTimeline('YS202605180004', '服务执行中'),
serviceSummary: '已完成基础护理与翻身指导,待家属签字确认。',
progressNote: '创面情况稳定,已完成阶段性护理。',
distanceKm: '3.6km',
allowCheckinRadiusMeters: 100,
lastLocation: {
latitude: 24.3022,
longitude: 116.1441,
address: '扶老社区 11 栋 203 室',
time: '2026-05-18 14:00:00'
} as DeliveryLocationType,
trackPoints: [
{
latitude: 24.3001,
longitude: 116.1412,
address: '扶老社区北门',
time: '2026-05-18 13:48:00'
} as DeliveryLocationType,
{
latitude: 24.3022,
longitude: 116.1441,
address: '扶老社区 11 栋 203 室',
time: '2026-05-18 14:00:00'
} as DeliveryLocationType
] as Array<DeliveryLocationType>
} as DeliveryOrderType,
{
id: 'mock-order-005',
orderNo: 'YS202605180005',
serviceType: '异常跟进',
serviceName: '用药异常上报处理',
serviceItems: buildMockServiceItems('mock-order-005', '异常处理'),
elderId: 'elder-005',
elderNameMasked: '王阿婆',
elderPhoneMasked: '134****7744',
fullElderName: '王月兰',
fullPhone: '13400137744',
addressSummary: '梅江区东汇城旁康颐楼 3 单元 1204',
fullAddress: '广东省梅州市梅江区东汇城旁康颐楼 3 单元 1204 室',
latitude: 24.2932,
longitude: 116.1205,
appointmentStartTime: '2026-05-18 16:00',
appointmentEndTime: '2026-05-18 17:00',
estimatedDuration: 60,
actualStartTime: '2026-05-18 16:05:00',
actualEndTime: '',
status: 'exception_pending',
statusText: '异常处理中',
statusTone: 'warning',
riskTags: ['用药异常', '需医生回访'],
careLevel: '异常处置',
merchantId: 'merchant-001',
merchantName: '梅州安康居家服务中心',
deliveryStaffId: profile.id,
deliveryStaffName: profile.name,
acceptTime: '2026-05-18 15:20:00',
departTime: '2026-05-18 15:35:00',
arriveTime: '2026-05-18 15:58:00',
checkinTime: '2026-05-18 16:03:00',
finishTime: '',
cancelReason: '',
exceptionType: 'elder_health_abnormal',
exceptionDesc: '老人午后血压异常升高,已建议暂停训练并等待医生回访。',
evidenceList: buildMockEvidence('mock-order-005'),
signatureUrl: '',
signatureName: '',
satisfactionStatus: '待处理',
settlementStatus: '待确认',
archiveStatus: '未归档',
createdAt: '2026-05-18 15:00:00',
updatedAt: '2026-05-18 16:10:00',
contactName: '王秀玲',
contactPhone: '13600139922',
notices: ['优先联系家属', '记录老人异常表现并上传照片'],
timeline: buildMockTimeline('YS202605180005', '异常已上报,等待处理'),
serviceSummary: '发现老人血压异常升高,已完成初步安抚并联系家属。',
progressNote: '异常已同步机构值班护士。',
distanceKm: '4.2km',
allowCheckinRadiusMeters: 100,
lastLocation: {
latitude: 24.2932,
longitude: 116.1205,
address: '康颐楼 3 单元 1204 室',
time: '2026-05-18 16:03:00'
} as DeliveryLocationType,
trackPoints: [] as Array<DeliveryLocationType>
} as DeliveryOrderType,
{
id: 'mock-order-006',
orderNo: 'YS202605170006',
serviceType: '康复护理',
serviceName: '术后恢复陪护',
serviceItems: [
{
id: 'mock-order-006-item-1',
name: '步态训练',
required: true,
completed: true,
incompleteReason: '',
remark: '完成 15 分钟辅助行走训练。',
updatedAt: '2026-05-17 15:10:00'
} as DeliveryServiceItemType,
{
id: 'mock-order-006-item-2',
name: '术后注意事项宣教',
required: true,
completed: true,
incompleteReason: '',
remark: '已向家属说明饮食与休息要求。',
updatedAt: '2026-05-17 15:28:00'
} as DeliveryServiceItemType
] as Array<DeliveryServiceItemType>,
elderId: 'elder-006',
elderNameMasked: '刘叔叔',
elderPhoneMasked: '133****6655',
fullElderName: '刘建华',
fullPhone: '13300136655',
addressSummary: '梅江区和顺苑 9 栋 401',
fullAddress: '广东省梅州市梅江区和顺苑 9 栋 401 室',
latitude: 24.2806,
longitude: 116.1115,
appointmentStartTime: '2026-05-17 14:00',
appointmentEndTime: '2026-05-17 15:30',
estimatedDuration: 90,
actualStartTime: '2026-05-17 14:05:00',
actualEndTime: '2026-05-17 15:32:00',
status: 'completed',
statusText: '已完成',
statusTone: 'success',
riskTags: ['术后恢复'],
careLevel: '康复照护',
merchantId: 'merchant-001',
merchantName: '梅州安康居家服务中心',
deliveryStaffId: profile.id,
deliveryStaffName: profile.name,
acceptTime: '2026-05-17 12:40:00',
departTime: '2026-05-17 13:20:00',
arriveTime: '2026-05-17 13:55:00',
checkinTime: '2026-05-17 14:00:00',
finishTime: '2026-05-17 15:32:00',
cancelReason: '',
exceptionType: '',
exceptionDesc: '',
evidenceList: buildMockEvidence('mock-order-006'),
signatureUrl: '',
signatureName: '刘建华',
satisfactionStatus: '已评价',
settlementStatus: '已结算',
archiveStatus: '已归档',
createdAt: '2026-05-17 12:20:00',
updatedAt: '2026-05-17 15:32:00',
contactName: '刘晓芳',
contactPhone: '13800132299',
notices: ['术后行动缓慢,注意搀扶'],
timeline: buildMockTimeline('YS202605170006', '服务完成待回访'),
serviceSummary: '术后恢复训练顺利完成,家属已确认并签字。',
progressNote: '服务完成,建议两日后再次回访。',
distanceKm: '6.1km',
allowCheckinRadiusMeters: 100,
lastLocation: {
latitude: 24.2806,
longitude: 116.1115,
address: '和顺苑 9 栋 401 室',
time: '2026-05-17 15:32:00'
} as DeliveryLocationType,
trackPoints: [] as Array<DeliveryLocationType>
} as DeliveryOrderType
] as Array<DeliveryOrderType>
}
function readMockDeliveryInfo(): DeliveryInfoType | null {
const cached = uni.getStorageSync(DELIVERY_MOCK_PROFILE_KEY)
if (!isValidDeliveryInfoCache(cached)) {
return null
}
return cached as DeliveryInfoType
}
function persistMockDeliveryInfo(info: DeliveryInfoType): void {
uni.setStorageSync(DELIVERY_MOCK_PROFILE_KEY, cloneDeliveryInfo(info))
}
function readMockOrders(): Array<DeliveryOrderType> | null {
const cached = uni.getStorageSync(DELIVERY_MOCK_ORDERS_KEY)
if (!isValidDeliveryOrdersCache(cached)) {
return null
}
return cached as Array<DeliveryOrderType>
}
function persistMockOrders(orders: Array<DeliveryOrderType>): void {
uni.setStorageSync(DELIVERY_MOCK_ORDERS_KEY, cloneDeliveryOrders(orders))
}
function ensureMockProfile(userId: string): DeliveryInfoType {
const cached = readMockDeliveryInfo()
if (cached != null) {
if (cached.userId == '' && userId != '') {
cached.userId = userId
persistMockDeliveryInfo(cached)
}
return cached
}
const profile = buildMockProfile(userId)
persistMockDeliveryInfo(profile)
return profile
}
function ensureMockOrders(userId: string): Array<DeliveryOrderType> {
const cached = readMockOrders()
if (cached != null && cached.length > 0) {
return cached
}
const profile = ensureMockProfile(userId)
const orders = buildMockOrders(profile)
persistMockOrders(orders)
return orders
}
function isPendingDepartStatus(status: string): boolean {
return status == 'accepted' || status == 'on_the_way' || status == 'arrived'
}
function isServingStatus(status: string): boolean {
return status == 'checked_in' || status == 'serving' || status == 'pending_submit' || status == 'pending_acceptance'
}
function isCompletedStatus(status: string): boolean {
return status == 'completed' || status == 'settled' || status == 'archived'
}
function buildMockProfileWithStats(userId: string): DeliveryInfoType {
const profile = cloneDeliveryInfo(ensureMockProfile(userId))
const orders = ensureMockOrders(userId)
let acceptedCount = 0
let servingCount = 0
let completedCount = 0
for (let i = 0; i < orders.length; i++) {
if (orders[i].acceptTime != '') {
acceptedCount++
}
if (isServingStatus(orders[i].status)) {
servingCount++
}
if (isCompletedStatus(orders[i].status)) {
completedCount++
}
}
profile.todayAccepted = acceptedCount
profile.todayServing = servingCount
profile.todayCompleted = completedCount
profile.usesMock = true
return profile
}
function buildMockMessages(orders: Array<DeliveryOrderType>): Array<DeliveryMessageType> {
return [
{
id: 'mock-msg-1',
title: '新工单待接单',
content: '基础照护 · 基础上门护理已分配给你服务对象李奶奶预约时间2026-05-18 09:00请及时确认接单并做好上门准备。',
type: 'order',
createdAt: '2026-05-18 08:20',
read: false,
orderId: orders[0].id
} as DeliveryMessageType,
{
id: 'mock-msg-2',
title: '出发提醒',
content: '康复指导 · 居家康复训练指导将在 30 分钟后开始,服务地址:梅县区新城锦绣花园 5 栋 1403请提前规划路线并按时出发。',
type: 'reminder',
createdAt: '2026-05-18 10:00',
read: false,
orderId: orders[1].id
} as DeliveryMessageType,
{
id: 'mock-msg-3',
title: '异常处理提醒',
content: '慢病随访工单出现异常:当前暂时无法联系服务对象,请尽快查看工单详情并提交处理结果。',
type: 'exception',
createdAt: '2026-05-18 10:35',
read: false,
orderId: orders[4].id
} as DeliveryMessageType,
{
id: 'mock-msg-4',
title: '工单改派通知',
content: '平台已将“陪诊协助 · 复诊陪同”改派给你预约时间2026-05-18 14:30请及时查看服务要求与上门信息。',
type: 'dispatch',
createdAt: '2026-05-18 11:10',
read: false,
orderId: orders[3].id
} as DeliveryMessageType,
{
id: 'mock-msg-5',
title: '签到提醒',
content: '你已接近服务地点,请在到达后完成签到,签到成功后即可开始服务记录。',
type: 'checkin',
createdAt: '2026-05-18 13:20',
read: true,
orderId: orders[2].id
} as DeliveryMessageType,
{
id: 'mock-msg-6',
title: '服务验收通过',
content: '基础照护工单已完成验收,服务对象家属已确认本次服务,相关记录已同步更新。',
type: 'result',
createdAt: '2026-05-17 18:40',
read: true,
orderId: orders[5].id
} as DeliveryMessageType,
{
id: 'mock-msg-7',
title: '结算通知',
content: '昨日完成的 3 个服务工单已进入结算流程,请在“我的-服务记录”中查看结算进度。',
type: 'settlement',
createdAt: '2026-05-17 09:15',
read: true,
orderId: ''
} as DeliveryMessageType,
{
id: 'mock-msg-8',
title: '系统公告',
content: '请服务人员上门前核对服务对象信息,服务过程中注意留痕记录,并按规范完成异常上报。',
type: 'notice',
createdAt: '2026-05-16 16:00',
read: true,
orderId: ''
} as DeliveryMessageType
] as Array<DeliveryMessageType>
}
function buildMockRecords(orders: Array<DeliveryOrderType>): Array<DeliveryRecordType> {
const result = [] as Array<DeliveryRecordType>
for (let i = 0; i < orders.length; i++) {
const item = orders[i]
if (isCompletedStatus(item.status) || item.status == 'exception_pending') {
result.push({
id: 'record-' + item.id,
orderId: item.id,
orderNo: item.orderNo,
serviceName: item.serviceName,
elderNameMasked: item.elderNameMasked,
status: item.status,
statusText: item.statusText,
appointmentStartTime: item.appointmentStartTime,
actualStartTime: item.actualStartTime,
actualEndTime: item.actualEndTime != '' ? item.actualEndTime : item.updatedAt,
settlementStatus: item.settlementStatus,
acceptanceStatus: item.satisfactionStatus,
exceptionDesc: item.exceptionDesc,
ratingText: item.status == 'completed' ? '五星好评' : '待补充说明'
} as DeliveryRecordType)
}
}
return result
}
function findMockOrderIndex(orders: Array<DeliveryOrderType>, orderId: string): number {
for (let i = 0; i < orders.length; i++) {
if (orders[i].id == orderId) {
return i
}
}
return -1
}
function sortMockRecentOrders(orders: Array<DeliveryOrderType>): Array<DeliveryOrderType> {
const pendingException = [] as Array<DeliveryOrderType>
const actionable = [] as Array<DeliveryOrderType>
const completed = [] as Array<DeliveryOrderType>
for (let i = 0; i < orders.length; i++) {
const item = orders[i]
if (item.status == 'exception_pending') {
pendingException.push(item)
} else if (!isCompletedStatus(item.status)) {
actionable.push(item)
} else {
completed.push(item)
}
}
return pendingException.concat(actionable).concat(completed)
}
function filterMockOrders(orders: Array<DeliveryOrderType>, query: DeliveryOrderQueryType): Array<DeliveryOrderType> {
const keyword = query.keyword.trim()
const result = [] as Array<DeliveryOrderType>
for (let i = 0; i < orders.length; i++) {
const item = orders[i]
let tabMatched = false
if (query.tab == 'all' || query.tab == '') {
tabMatched = true
} else if (query.tab == 'pending_accept') {
tabMatched = item.status == 'pending_accept'
} else if (query.tab == 'accepted') {
tabMatched = isPendingDepartStatus(item.status)
} else if (query.tab == 'serving') {
tabMatched = isServingStatus(item.status)
} else if (query.tab == 'pending_submit') {
tabMatched = item.status == 'pending_submit' || item.status == 'pending_acceptance'
} else if (query.tab == 'completed') {
tabMatched = isCompletedStatus(item.status)
} else if (query.tab == 'exception') {
tabMatched = item.status == 'exception_pending'
}
if (!tabMatched) {
continue
}
if (keyword != '') {
const matched = item.orderNo.includes(keyword) || item.serviceName.includes(keyword) || item.elderNameMasked.includes(keyword) || item.addressSummary.includes(keyword)
if (!matched) {
continue
}
}
result.push(cloneDeliveryOrder(item))
}
return result
}
function readCachedDeliveryInfo(userId: string): DeliveryInfoType | null {
const cached = uni.getStorageSync('delivery_info')
if (!isValidDeliveryInfoCache(cached)) {
return null
}
const info = cached as DeliveryInfoType
if (userId == '' || info.userId == userId || info.id == userId) {
return info
}
return null
}
async function fallbackGetDeliveryProfileByUserId(userId: string): Promise<DeliveryInfoType | null> {
const cached = readCachedDeliveryInfo(userId)
if (cached != null) {
return cached
}
return buildMockProfileWithStats(userId)
}
async function fallbackGetDashboard(staffId: string): Promise<DeliveryDashboardType> {
const profile = buildMockProfileWithStats(staffId)
const orders = ensureMockOrders(staffId)
let pendingAcceptCount = 0
let pendingDepartCount = 0
let servingCount = 0
let completedCount = 0
let exceptionCount = 0
for (let i = 0; i < orders.length; i++) {
const status = orders[i].status
if (status == 'pending_accept') {
pendingAcceptCount++
} else if (isPendingDepartStatus(status)) {
pendingDepartCount++
} else if (isServingStatus(status)) {
servingCount++
} else if (status == 'exception_pending') {
exceptionCount++
} else if (isCompletedStatus(status)) {
completedCount++
}
}
return {
pendingAcceptCount,
pendingDepartCount,
servingCount,
completedCount,
exceptionCount,
onlineStatus: profile.onlineStatus,
recentOrders: sortMockRecentOrders(cloneDeliveryOrders(orders)).slice(0, 5) as Array<DeliveryOrderType>
} as DeliveryDashboardType
}
async function fallbackGetOrders(staffId: string, query: DeliveryOrderQueryType): Promise<Array<DeliveryOrderType>> {
const orders = ensureMockOrders(staffId)
return filterMockOrders(orders, query)
}
async function fallbackGetOrderDetail(orderId: string): Promise<DeliveryOrderType | null> {
const orders = ensureMockOrders('')
const index = findMockOrderIndex(orders, orderId)
if (index < 0) {
return null
}
return cloneDeliveryOrder(orders[index])
}
async function fallbackAcceptOrder(orderId: string): Promise<DeliveryOrderType | null> {
const orders = ensureMockOrders('')
const index = findMockOrderIndex(orders, orderId)
if (index < 0) {
return null
}
orders[index].status = 'accepted'
orders[index].statusText = '待出发'
orders[index].statusTone = 'primary'
orders[index].acceptTime = currentDateTimeText()
orders[index].updatedAt = currentDateTimeText()
orders[index].timeline.push({
id: orders[index].orderNo + '-accepted',
title: '服务人员已接单',
time: orders[index].updatedAt,
description: '已确认接单,待前往服务地点。'
} as DeliveryTimelineItemType)
persistMockOrders(orders)
return cloneDeliveryOrder(orders[index])
}
async function fallbackRejectOrder(orderId: string, _reason: string): Promise<DeliveryOrderType | null> {
const orders = ensureMockOrders('')
const index = findMockOrderIndex(orders, orderId)
if (index < 0) {
return null
}
orders[index].status = 'cancelled'
orders[index].statusText = '已取消'
orders[index].statusTone = 'neutral'
orders[index].updatedAt = currentDateTimeText()
persistMockOrders(orders)
return cloneDeliveryOrder(orders[index])
}
async function fallbackStartDepart(orderId: string, _location: DeliveryLocationType): Promise<DeliveryOrderType | null> {
const orders = ensureMockOrders('')
const index = findMockOrderIndex(orders, orderId)
if (index < 0) {
return null
}
orders[index].status = 'on_the_way'
orders[index].statusText = '前往服务中'
orders[index].statusTone = 'primary'
orders[index].departTime = currentDateTimeText()
orders[index].updatedAt = orders[index].departTime
persistMockOrders(orders)
return cloneDeliveryOrder(orders[index])
}
async function fallbackArriveOrder(orderId: string, _location: DeliveryLocationType): Promise<DeliveryOrderType | null> {
const orders = ensureMockOrders('')
const index = findMockOrderIndex(orders, orderId)
if (index < 0) {
return null
}
orders[index].status = 'arrived'
orders[index].statusText = '待签到'
orders[index].statusTone = 'primary'
orders[index].arriveTime = currentDateTimeText()
orders[index].updatedAt = orders[index].arriveTime
persistMockOrders(orders)
return cloneDeliveryOrder(orders[index])
}
async function fallbackCheckinOrder(orderId: string, payload: DeliveryCheckinPayloadType): Promise<DeliveryOrderType | null> {
const orders = ensureMockOrders('')
const index = findMockOrderIndex(orders, orderId)
if (index < 0) {
return null
}
orders[index].status = 'checked_in'
orders[index].statusText = '已签到'
orders[index].statusTone = 'primary'
orders[index].checkinTime = currentDateTimeText()
orders[index].updatedAt = orders[index].checkinTime
orders[index].lastLocation = payload.location
if (payload.photos.length > 0) {
for (let i = 0; i < payload.photos.length; i++) {
orders[index].evidenceList.push({
id: orderId + '-checkin-photo-' + String(i + 1),
orderId,
phase: 'checkin',
fileType: 'image',
name: '签到照片' + String(i + 1),
url: '',
localPath: payload.photos[i],
status: 'success',
progress: 100,
retryable: false,
createdAt: currentDateTimeText()
} as DeliveryEvidenceRecordType)
}
}
persistMockOrders(orders)
return cloneDeliveryOrder(orders[index])
}
async function fallbackStartService(orderId: string): Promise<DeliveryOrderType | null> {
const orders = ensureMockOrders('')
const index = findMockOrderIndex(orders, orderId)
if (index < 0) {
return null
}
orders[index].status = 'serving'
orders[index].statusText = '服务中'
orders[index].statusTone = 'primary'
if (orders[index].actualStartTime == '') {
orders[index].actualStartTime = currentDateTimeText()
}
orders[index].updatedAt = currentDateTimeText()
persistMockOrders(orders)
return cloneDeliveryOrder(orders[index])
}
async function fallbackSaveProgress(orderId: string, payload: DeliveryProgressPayloadType): Promise<DeliveryOrderType | null> {
const orders = ensureMockOrders('')
const index = findMockOrderIndex(orders, orderId)
if (index < 0) {
return null
}
orders[index].serviceItems = JSON.parse(JSON.stringify(payload.items)) as Array<DeliveryServiceItemType>
orders[index].progressNote = payload.progressNote
orders[index].serviceSummary = payload.serviceSummary
orders[index].updatedAt = currentDateTimeText()
persistMockOrders(orders)
return cloneDeliveryOrder(orders[index])
}
async function fallbackUploadEvidence(orderId: string, phase: string, files: Array<string>): Promise<Array<DeliveryEvidenceRecordType>> {
const orders = ensureMockOrders('')
const index = findMockOrderIndex(orders, orderId)
if (index < 0) {
return [] as Array<DeliveryEvidenceRecordType>
}
const added = [] as Array<DeliveryEvidenceRecordType>
for (let i = 0; i < files.length; i++) {
const item = {
id: orderId + '-evidence-' + String(orders[index].evidenceList.length + i + 1),
orderId,
phase,
fileType: 'image',
name: '服务留痕图片' + String(i + 1),
url: '',
localPath: files[i],
status: 'success',
progress: 100,
retryable: false,
createdAt: currentDateTimeText()
} as DeliveryEvidenceRecordType
orders[index].evidenceList.push(item)
added.push(item)
}
orders[index].updatedAt = currentDateTimeText()
persistMockOrders(orders)
return JSON.parse(JSON.stringify(added)) as Array<DeliveryEvidenceRecordType>
}
async function fallbackRetryEvidence(orderId: string, evidenceId: string): Promise<DeliveryEvidenceRecordType | null> {
const orders = ensureMockOrders('')
const index = findMockOrderIndex(orders, orderId)
if (index < 0) {
return null
}
for (let i = 0; i < orders[index].evidenceList.length; i++) {
if (orders[index].evidenceList[i].id == evidenceId) {
orders[index].evidenceList[i].status = 'success'
orders[index].evidenceList[i].progress = 100
orders[index].evidenceList[i].retryable = false
persistMockOrders(orders)
return orders[index].evidenceList[i]
}
}
return null
}
async function fallbackSubmitException(orderId: string, payload: DeliveryExceptionPayloadType): Promise<DeliveryOrderType | null> {
const orders = ensureMockOrders('')
const index = findMockOrderIndex(orders, orderId)
if (index < 0) {
return null
}
orders[index].status = 'exception_pending'
orders[index].statusText = '异常处理中'
orders[index].statusTone = 'warning'
orders[index].exceptionType = payload.exceptionType
orders[index].exceptionDesc = payload.description
orders[index].updatedAt = currentDateTimeText()
persistMockOrders(orders)
return cloneDeliveryOrder(orders[index])
}
async function fallbackFinishService(orderId: string, payload: DeliveryFinishPayloadType): Promise<DeliveryOrderType | null> {
const orders = ensureMockOrders('')
const index = findMockOrderIndex(orders, orderId)
if (index < 0) {
return null
}
orders[index].status = 'completed'
orders[index].statusText = '已完成'
orders[index].statusTone = 'success'
orders[index].serviceSummary = payload.serviceSummary
orders[index].signatureName = payload.signatureName
orders[index].actualEndTime = currentDateTimeText()
orders[index].finishTime = orders[index].actualEndTime
orders[index].satisfactionStatus = payload.confirmByFamily ? '待回访' : '待确认'
orders[index].settlementStatus = '待结算'
orders[index].archiveStatus = '待归档'
orders[index].updatedAt = orders[index].actualEndTime
persistMockOrders(orders)
return cloneDeliveryOrder(orders[index])
}
async function fallbackGetRecords(staffId: string): Promise<Array<DeliveryRecordType>> {
const orders = ensureMockOrders(staffId)
return cloneDeliveryRecords(buildMockRecords(orders))
}
async function fallbackGetMessages(): Promise<Array<DeliveryMessageType>> {
const orders = ensureMockOrders('')
return cloneDeliveryMessages(buildMockMessages(orders))
}
async function fallbackUpdateOnlineStatus(staffId: string, status: string): Promise<DeliveryInfoType | null> {
const cachedInfo = buildMockProfileWithStats(staffId)
const nextInfo = {
...cachedInfo,
onlineStatus: status as 'online' | 'resting' | 'busy'
} as DeliveryInfoType
persistMockDeliveryInfo(nextInfo)
return nextInfo
}
export async function loginDelivery(payload: DeliveryLoginPayloadType): Promise<DeliveryLoginResultType> {
const account = payload.account.trim()
if (!account.includes('@')) {
throw new Error('请使用真实邮箱账号登录服务人员端')
}
const result = await supa.signIn(account, payload.password)
if (result.user == null) {
const rawData = result.raw as UTSJSONObject
throw new Error(mapLoginError(rawData))
}
const profile = await getCurrentUser()
if (profile == null || profile.id == null || profile.id == '') {
try {
await supa.signOut()
} catch (error) {}
throw new Error('登录状态已失效,请重新登录')
}
if (!isDelivererRole(profile)) {
try {
await supa.signOut()
} catch (error) {}
throw new Error('当前账号不是上门服务人员账号')
}
const deliveryInfo = await fetchDeliveryProfileFromRemote(profile.id)
if (deliveryInfo == null) {
try {
await supa.signOut()
} catch (error) {}
throw new Error('当前账号未绑定服务人员档案')
}
return {
token: result.access_token,
userInfo: profile,
deliveryInfo,
usesMock: false
}
}
export async function getDeliveryProfileByUserId(userId: string): Promise<DeliveryInfoType | null> {
const remoteInfo = await fetchDeliveryProfileFromRemote(userId)
if (remoteInfo != null) {
return remoteInfo
}
return await fallbackGetDeliveryProfileByUserId(userId)
}
export async function getDeliveryDashboardByStaffId(staffId: string): Promise<DeliveryDashboardType> {
const rpcData = await callDeliveryRpc(DELIVERY_RPC_DASHBOARD, {
p_staff_id: staffId
} as UTSJSONObject)
const dashboard = normalizeRpcDashboard(rpcData)
if (dashboard != null) {
return dashboard
}
return await fallbackGetDashboard(staffId)
}
export async function getDeliveryOrdersByStaffId(staffId: string, query: DeliveryOrderQueryType): Promise<Array<DeliveryOrderType>> {
const rpcData = await callDeliveryRpc(DELIVERY_RPC_ORDER_LIST, {
p_staff_id: staffId,
p_tab: query.tab,
p_keyword: query.keyword
} as UTSJSONObject)
const orders = normalizeRpcOrderList(rpcData)
if (orders != null) {
return orders
}
return await fallbackGetOrders(staffId, query)
}
export async function getDeliveryOrderDetailById(orderId: string): Promise<DeliveryOrderType | null> {
const rpcData = await callDeliveryRpc(DELIVERY_RPC_ORDER_DETAIL, {
p_order_id: orderId
} as UTSJSONObject)
const order = normalizeRpcOrder(rpcData)
if (order != null) {
return order
}
return await fallbackGetOrderDetail(orderId)
}
export async function acceptDeliveryOrderById(orderId: string): Promise<DeliveryOrderType | null> {
const rpcData = await callDeliveryRpc(DELIVERY_RPC_ACCEPT_ORDER, {
p_order_id: orderId
} as UTSJSONObject)
const order = normalizeRpcOrder(rpcData)
if (order != null) {
return order
}
return await fallbackAcceptOrder(orderId)
}
export async function rejectDeliveryOrderById(orderId: string, reason: string): Promise<DeliveryOrderType | null> {
const rpcData = await callDeliveryRpc(DELIVERY_RPC_REJECT_ORDER, {
p_order_id: orderId,
p_reason: reason
} as UTSJSONObject)
const order = normalizeRpcOrder(rpcData)
if (order != null) {
return order
}
return await fallbackRejectOrder(orderId, reason)
}
export async function startDepartById(orderId: string, location: DeliveryLocationType): Promise<DeliveryOrderType | null> {
const rpcData = await callDeliveryRpc(DELIVERY_RPC_START_DEPART, {
p_order_id: orderId,
p_location: location
} as UTSJSONObject)
const order = normalizeRpcOrder(rpcData)
if (order != null) {
return order
}
return await fallbackStartDepart(orderId, location)
}
export async function arriveOrderById(orderId: string, location: DeliveryLocationType): Promise<DeliveryOrderType | null> {
const rpcData = await callDeliveryRpc(DELIVERY_RPC_ARRIVE_ORDER, {
p_order_id: orderId,
p_location: location
} as UTSJSONObject)
const order = normalizeRpcOrder(rpcData)
if (order != null) {
return order
}
return await fallbackArriveOrder(orderId, location)
}
export async function checkinOrderById(orderId: string, payload: DeliveryCheckinPayloadType): Promise<DeliveryOrderType | null> {
const rpcData = await callDeliveryRpc(DELIVERY_RPC_CHECKIN_ORDER, {
p_order_id: orderId,
p_payload: payload
} as UTSJSONObject)
const order = normalizeRpcOrder(rpcData)
if (order != null) {
return order
}
return await fallbackCheckinOrder(orderId, payload)
}
export async function startServiceById(orderId: string): Promise<DeliveryOrderType | null> {
const rpcData = await callDeliveryRpc(DELIVERY_RPC_START_SERVICE, {
p_order_id: orderId
} as UTSJSONObject)
const order = normalizeRpcOrder(rpcData)
if (order != null) {
return order
}
return await fallbackStartService(orderId)
}
export async function saveServiceProgressById(orderId: string, payload: DeliveryProgressPayloadType): Promise<DeliveryOrderType | null> {
const rpcData = await callDeliveryRpc(DELIVERY_RPC_SAVE_PROGRESS, {
p_order_id: orderId,
p_payload: payload
} as UTSJSONObject)
const order = normalizeRpcOrder(rpcData)
if (order != null) {
return order
}
return await fallbackSaveProgress(orderId, payload)
}
export async function uploadEvidenceById(orderId: string, phase: string, files: Array<string>): Promise<Array<DeliveryEvidenceRecordType>> {
const rpcData = await callDeliveryRpc(DELIVERY_RPC_UPLOAD_EVIDENCE, {
p_order_id: orderId,
p_phase: phase,
p_files: files
} as UTSJSONObject)
const evidenceList = normalizeRpcEvidenceList(rpcData)
if (evidenceList != null) {
return evidenceList
}
return await fallbackUploadEvidence(orderId, phase, files)
}
export async function retryEvidenceById(orderId: string, evidenceId: string): Promise<DeliveryEvidenceRecordType | null> {
const rpcData = await callDeliveryRpc(DELIVERY_RPC_RETRY_EVIDENCE, {
p_order_id: orderId,
p_evidence_id: evidenceId
} as UTSJSONObject)
if (rpcData != null) {
return rpcData as DeliveryEvidenceRecordType
}
return await fallbackRetryEvidence(orderId, evidenceId)
}
export async function submitExceptionById(orderId: string, payload: DeliveryExceptionPayloadType): Promise<DeliveryOrderType | null> {
const rpcData = await callDeliveryRpc(DELIVERY_RPC_SUBMIT_EXCEPTION, {
p_order_id: orderId,
p_payload: payload
} as UTSJSONObject)
const order = normalizeRpcOrder(rpcData)
if (order != null) {
return order
}
return await fallbackSubmitException(orderId, payload)
}
export async function finishServiceById(orderId: string, payload: DeliveryFinishPayloadType): Promise<DeliveryOrderType | null> {
const rpcData = await callDeliveryRpc(DELIVERY_RPC_FINISH_SERVICE, {
p_order_id: orderId,
p_payload: payload
} as UTSJSONObject)
const order = normalizeRpcOrder(rpcData)
if (order != null) {
return order
}
return await fallbackFinishService(orderId, payload)
}
export async function getDeliveryRecordsByStaffId(staffId: string): Promise<Array<DeliveryRecordType>> {
const rpcData = await callDeliveryRpc(DELIVERY_RPC_RECORD_LIST, {
p_staff_id: staffId
} as UTSJSONObject)
const records = normalizeRpcRecords(rpcData)
if (records != null) {
return records
}
return await fallbackGetRecords(staffId)
}
export async function getDeliveryMessagesList(): Promise<Array<DeliveryMessageType>> {
const rpcData = await callDeliveryRpc(DELIVERY_RPC_MESSAGE_LIST, {} as UTSJSONObject)
const messages = normalizeRpcMessages(rpcData)
if (messages != null && messages.length > 0) {
return messages
}
return await fallbackGetMessages()
}
export async function updateDeliveryOnlineStatusByStaffId(staffId: string, status: string): Promise<DeliveryInfoType | null> {
const rpcData = await callDeliveryRpc(DELIVERY_RPC_SET_ONLINE_STATUS, {
p_staff_id: staffId,
p_status: status
} as UTSJSONObject)
const deliveryInfo = normalizeRpcDeliveryInfo(rpcData)
if (deliveryInfo != null) {
return deliveryInfo
}
return await fallbackUpdateOnlineStatus(staffId, status)
}