import type { UserProfile } from '@/types/mall-types.uts' import { DeliveryCheckinPayloadType, DeliveryDashboardType, DeliveryExceptionPayloadType, DeliveryEvidenceRecordType, DeliveryFinishPayloadType, DeliveryInfoType, DeliveryLocationType, DeliveryLoginPayloadType, DeliveryLoginResultType, DeliveryMessageType, DeliveryOrderQueryType, DeliveryOrderStatus, DeliveryOrderType, DeliveryProgressPayloadType, DeliveryRecordType, DeliveryServiceItemType, DeliveryTimelineItemType } from '@/types/delivery.uts' type MockAccountType = { account: string password: string userInfo: UserProfile } export const DELIVERY_ROLE_ALIASES: Array = ['Deliverer'] const MOCK_ACCOUNTS: Array = [ { account: 'service01', password: '123456', userInfo: { id: 'delivery-user-01', username: '陈护理', email: 'service01@demo.local', avatar_url: '/static/logo.png', role: 'Deliverer' } as UserProfile }, { account: 'care01', password: '123456', userInfo: { id: 'delivery-user-02', username: '黄巡护', email: 'care01@demo.local', avatar_url: '/static/logo.png', role: 'Deliverer' } as UserProfile }, { account: 'delivery-no-profile', password: '123456', userInfo: { id: 'delivery-user-03', username: '未建档账号', email: 'delivery-no-profile@demo.local', role: 'Deliverer' } as UserProfile }, { account: 'delivery-disabled', password: '123456', userInfo: { id: 'delivery-user-04', username: '停用账号', email: 'delivery-disabled@demo.local', role: 'Deliverer' } as UserProfile }, { account: 'admin01', password: '123456', userInfo: { id: 'admin-user-01', username: '后台账号', email: 'admin01@demo.local', role: 'admin' } as UserProfile }, { account: 'consumer01', password: '123456', userInfo: { id: 'consumer-user-01', username: '普通用户', email: 'consumer01@demo.local', role: 'consumer' } as UserProfile }, { account: 'merchant01', password: '123456', userInfo: { id: 'merchant-user-01', username: '机构账号', email: 'merchant01@demo.local', role: 'merchant' } as UserProfile } ] function createCertificate(id: string, name: string, expireAt: string): any { return { id, name, status: expireAt > '2026-05-15' ? 'valid' : 'expired', expireAt, issuer: '梅州市民政培训中心', imageUrl: '' } } let DELIVERY_STAFF_STORE: Array = [ { id: 'staff-01', userId: 'delivery-user-01', staffNo: 'DEL-202605-001', name: '陈护理', phone: '13600001111', role: 'Deliverer', status: 'active', organizationId: 'org-01', organizationName: '梅江区居家康护中心', certificates: [createCertificate('cert-01', '护理员证', '2027-08-31'), createCertificate('cert-02', '健康证', '2027-05-20')], certificateStatus: 'valid', certificateExpireAt: '2027-08-31', onlineStatus: 'online', serviceArea: '梅江区', skills: ['基础护理', '慢病随访', '上门巡视'], avatarUrl: '/static/logo.png', todayAccepted: 1, todayServing: 1, todayCompleted: 2, usesMock: true }, { id: 'staff-02', userId: 'delivery-user-02', staffNo: 'DEL-202605-002', name: '黄巡护', phone: '13600002222', role: 'Deliverer', status: 'active', organizationId: 'org-01', organizationName: '梅江区居家康护中心', certificates: [createCertificate('cert-03', '巡护员培训证', '2027-11-30')], certificateStatus: 'valid', certificateExpireAt: '2027-11-30', onlineStatus: 'resting', serviceArea: '梅县区', skills: ['巡护', '风险上报'], avatarUrl: '/static/logo.png', todayAccepted: 0, todayServing: 0, todayCompleted: 1, usesMock: true }, { id: 'staff-03', userId: 'delivery-user-04', staffNo: 'DEL-202605-003', name: '停用账号', phone: '13600003333', role: 'Deliverer', status: 'disabled', organizationId: 'org-02', organizationName: '梅县区银龄服务站', certificates: [createCertificate('cert-04', '护理员证', '2027-01-31')], certificateStatus: 'valid', certificateExpireAt: '2027-01-31', onlineStatus: 'resting', serviceArea: '梅县区', skills: ['基础护理'], avatarUrl: '/static/logo.png', todayAccepted: 0, todayServing: 0, todayCompleted: 0, usesMock: true } ] function nowText(): string { return new Date().toISOString().replace('T', ' ').substring(0, 19) } function makeTimeline(idPrefix: string, items: Array): Array { const timeline: Array = [] for (let i = 0; i < items.length; i++) { timeline.push({ id: idPrefix + '-' + String(i + 1), title: items[i], time: nowText(), description: items[i] + ' 已记录' }) } return timeline } function makeItems(prefix: string): Array { return [ { id: prefix + '-1', name: '生命体征监测', required: true, completed: false, incompleteReason: '', remark: '', updatedAt: '' }, { id: prefix + '-2', name: '基础照护', required: true, completed: false, incompleteReason: '', remark: '', updatedAt: '' }, { id: prefix + '-3', name: '健康宣教', required: false, completed: false, incompleteReason: '', remark: '', updatedAt: '' } ] } function statusMeta(status: DeliveryOrderStatus): any { if (status == 'pending_accept') return { text: '待接单', tone: 'warning' } if (status == 'accepted') return { text: '待出发', tone: 'primary' } if (status == 'on_the_way') return { text: '出发中', tone: 'primary' } if (status == 'arrived') return { text: '已到达', tone: 'primary' } if (status == 'checked_in') return { text: '已签到', tone: 'success' } if (status == 'serving') return { text: '服务中', tone: 'primary' } if (status == 'pending_submit') return { text: '待提交', tone: 'warning' } if (status == 'pending_acceptance') return { text: '待验收', tone: 'success' } if (status == 'completed') return { text: '已完成', tone: 'success' } if (status == 'exception_pending') return { text: '异常待处理', tone: 'warning' } if (status == 'settled') return { text: '已结算', tone: 'success' } if (status == 'archived') return { text: '已归档', tone: 'neutral' } return { text: '已取消', tone: 'neutral' } } function buildOrder(id: string, status: DeliveryOrderStatus, elderName: string, address: string, serviceName: string, distanceKm: string): DeliveryOrderType { const meta = statusMeta(status) return { id, orderNo: 'HS-' + id.toUpperCase(), serviceType: '居家上门服务', serviceName, serviceItems: makeItems(id), elderId: 'elder-' + id, elderNameMasked: elderName.substring(0, 1) + '**', elderPhoneMasked: '138****' + id.substring(id.length - 2), fullElderName: elderName, fullPhone: '1380013' + id.substring(id.length - 4), addressSummary: address, fullAddress: address + ' 详细门牌', latitude: 24.289, longitude: 116.123, appointmentStartTime: '2026-05-15 09:00', appointmentEndTime: '2026-05-15 11:00', estimatedDuration: 120, actualStartTime: '', actualEndTime: '', status, statusText: meta.text, statusTone: meta.tone, riskTags: ['跌倒风险', '高血压'], careLevel: '护理二级', merchantId: 'merchant-org-01', merchantName: '梅江区居家康护中心', deliveryStaffId: 'staff-01', deliveryStaffName: '陈护理', acceptTime: '', departTime: '', arriveTime: '', checkinTime: '', finishTime: '', cancelReason: '', exceptionType: '', exceptionDesc: '', evidenceList: [], signatureUrl: '', signatureName: '', satisfactionStatus: 'pending', settlementStatus: 'pending', archiveStatus: 'pending', createdAt: nowText(), updatedAt: nowText(), contactName: '家属联系人', contactPhone: '13900139000', notices: ['接单前仅显示脱敏信息', '签到需在 50 米范围内完成'], timeline: makeTimeline(id, ['工单创建', '调度派单']), serviceSummary: '', progressNote: '', distanceKm, allowCheckinRadiusMeters: 50, lastLocation: null, trackPoints: [] } } let ORDER_STORE: Array = [ buildOrder('1001', 'pending_accept', '李奶奶', '梅江区学海路 18 号', '基础护理上门', '2.1km'), buildOrder('1002', 'accepted', '张爷爷', '梅县区华侨城康宁路 66 号', '慢病随访', '3.8km'), buildOrder('1003', 'on_the_way', '陈阿婆', '梅江区团结路 9 号', '康复训练指导', '1.5km'), buildOrder('1004', 'checked_in', '林伯伯', '梅江区江南路 27 号', '基础护理上门', '0.6km'), buildOrder('1005', 'serving', '温阿姨', '梅县区扶大高新区 12 号', '慢病随访', '4.2km'), buildOrder('1006', 'pending_acceptance', '丘奶奶', '梅江区芹洋路 8 号', '基础护理上门', '6.0km') ] ORDER_STORE[4].actualStartTime = '2026-05-15 08:40' ORDER_STORE[4].serviceItems[0].completed = true ORDER_STORE[4].serviceItems[0].updatedAt = '2026-05-15 08:50' ORDER_STORE[4].serviceItems[0].remark = '血压 132/82,血糖 6.8' ORDER_STORE[4].timeline.unshift({ id: '1005-serve', title: '开始服务', time: '2026-05-15 08:40', description: '服务人员已开始执行服务项目' }) ORDER_STORE[5].actualStartTime = '2026-05-14 15:00' ORDER_STORE[5].actualEndTime = '2026-05-14 16:25' ORDER_STORE[5].finishTime = '2026-05-14 16:25' ORDER_STORE[5].serviceSummary = '已完成基础护理和家属宣教,待家属验收。' let MESSAGE_STORE: Array = [ { id: 'msg-01', title: '新工单提醒', content: '工单 HS-1001 已派发,请在 10 分钟内处理。', type: 'new_order', createdAt: nowText(), read: false, orderId: '1001' }, { id: 'msg-02', title: '超时提醒', content: '工单 HS-1003 已接近预约时间,请尽快到达。', type: 'timeout', createdAt: nowText(), read: false, orderId: '1003' }, { id: 'msg-03', title: '验收结果', content: '工单 HS-1006 已进入待验收,请关注后续反馈。', type: 'acceptance', createdAt: nowText(), read: true, orderId: '1006' } ] function delay(): Promise { return new Promise((resolve) => { setTimeout(() => { resolve() }, 80) }) } function cloneUserProfile(profile: UserProfile): UserProfile { return JSON.parse(JSON.stringify(profile)) as UserProfile } function cloneDeliveryInfo(info: DeliveryInfoType): DeliveryInfoType { return JSON.parse(JSON.stringify(info)) as DeliveryInfoType } function cloneOrder(order: DeliveryOrderType): DeliveryOrderType { return JSON.parse(JSON.stringify(order)) as DeliveryOrderType } function cloneRecord(record: DeliveryRecordType): DeliveryRecordType { return JSON.parse(JSON.stringify(record)) as DeliveryRecordType } function getDeliveryInfoByUserId(userId: string): DeliveryInfoType | null { for (let i = 0; i < DELIVERY_STAFF_STORE.length; i++) { if (DELIVERY_STAFF_STORE[i].userId == userId) { return DELIVERY_STAFF_STORE[i] } } return null } function getOrderById(orderId: string): DeliveryOrderType | null { for (let i = 0; i < ORDER_STORE.length; i++) { if (ORDER_STORE[i].id == orderId) { return ORDER_STORE[i] } } return null } function refreshStatus(order: DeliveryOrderType): void { const meta = statusMeta(order.status) order.statusText = meta.text order.statusTone = meta.tone order.updatedAt = nowText() } function appendTimeline(order: DeliveryOrderType, title: string, description: string): void { order.timeline.unshift({ id: order.id + '-tl-' + String(order.timeline.length + 1), title, time: nowText(), description }) } function buildRecords(): Array { const list: Array = [] for (let i = 0; i < ORDER_STORE.length; i++) { const order = ORDER_STORE[i] if (order.status == 'pending_acceptance' || order.status == 'completed' || order.status == 'exception_pending') { list.push({ id: 'record-' + order.id, orderId: order.id, orderNo: order.orderNo, serviceName: order.serviceName, elderNameMasked: order.elderNameMasked, status: order.status, statusText: order.statusText, appointmentStartTime: order.appointmentStartTime, actualStartTime: order.actualStartTime, actualEndTime: order.actualEndTime, settlementStatus: order.settlementStatus, acceptanceStatus: order.satisfactionStatus, exceptionDesc: order.exceptionDesc, ratingText: order.satisfactionStatus == 'done' ? '已评价' : '待评价' }) } } return list } export async function mockLoginDelivery(payload: DeliveryLoginPayloadType): Promise { await delay() for (let i = 0; i < MOCK_ACCOUNTS.length; i++) { const account = MOCK_ACCOUNTS[i] if (account.account == payload.account && account.password == payload.password) { const deliveryInfo = account.userInfo.id != null ? getDeliveryInfoByUserId(account.userInfo.id as string) : null return { token: 'mock-delivery-token-' + payload.account + '-' + String(Date.now()), userInfo: cloneUserProfile(account.userInfo), deliveryInfo: deliveryInfo == null ? null : cloneDeliveryInfo(deliveryInfo), usesMock: true } } } throw new Error('演示环境仅开放以下账号:service01、care01、delivery-no-profile、delivery-disabled、consumer01、merchant01、admin01,密码均为 123456') } export async function mockGetDeliveryProfileByUserId(userId: string): Promise { await delay() const info = getDeliveryInfoByUserId(userId) return info == null ? null : cloneDeliveryInfo(info) } export async function mockGetDashboard(staffId: string): Promise { await delay() let pendingAcceptCount = 0 let pendingDepartCount = 0 let servingCount = 0 let completedCount = 0 let exceptionCount = 0 const recentOrders: Array = [] for (let i = 0; i < ORDER_STORE.length; i++) { const order = ORDER_STORE[i] if (order.deliveryStaffId != staffId) { continue } if (order.status == 'pending_accept') pendingAcceptCount++ if (order.status == 'accepted') pendingDepartCount++ if (order.status == 'on_the_way' || order.status == 'arrived' || order.status == 'checked_in' || order.status == 'serving') servingCount++ if (order.status == 'completed' || order.status == 'pending_acceptance') completedCount++ if (order.status == 'exception_pending') exceptionCount++ if (recentOrders.length < 3) { recentOrders.push(cloneOrder(order)) } } const info = DELIVERY_STAFF_STORE.find((item) => item.id == staffId) return { pendingAcceptCount, pendingDepartCount, servingCount, completedCount, exceptionCount, onlineStatus: info != null ? info.onlineStatus : 'resting', recentOrders } } function matchTab(order: DeliveryOrderType, tab: string): boolean { if (tab == '' || tab == 'all') return true if (tab == 'pending_accept') return order.status == 'pending_accept' if (tab == 'accepted') return order.status == 'accepted' if (tab == 'serving') return order.status == 'on_the_way' || order.status == 'arrived' || order.status == 'checked_in' || order.status == 'serving' if (tab == 'pending_submit') return order.status == 'pending_submit' if (tab == 'completed') return order.status == 'pending_acceptance' || order.status == 'completed' if (tab == 'exception') return order.status == 'exception_pending' return true } export async function mockGetOrders(staffId: string, query: DeliveryOrderQueryType): Promise> { await delay() const list: Array = [] for (let i = 0; i < ORDER_STORE.length; i++) { const order = ORDER_STORE[i] if (order.deliveryStaffId != staffId) { continue } if (!matchTab(order, query.tab)) { continue } if (query.keyword != '') { const keyword = query.keyword if (order.orderNo.indexOf(keyword) < 0 && order.serviceName.indexOf(keyword) < 0 && order.addressSummary.indexOf(keyword) < 0) { continue } } list.push(cloneOrder(order)) } return list } export async function mockGetOrderDetail(orderId: string): Promise { await delay() const order = getOrderById(orderId) return order == null ? null : cloneOrder(order) } export async function mockAcceptOrder(orderId: string): Promise { await delay() const order = getOrderById(orderId) if (order == null) return null order.status = 'accepted' order.acceptTime = nowText() refreshStatus(order) appendTimeline(order, '已接单', '服务人员已确认接单,待出发') MESSAGE_STORE.unshift({ id: 'msg-' + String(Date.now()), title: '接单成功', content: order.orderNo + ' 已进入待出发状态。', type: 'order_update', createdAt: nowText(), read: false, orderId }) return cloneOrder(order) } export async function mockRejectOrder(orderId: string, reason: string): Promise { await delay() const order = getOrderById(orderId) if (order == null) return null order.status = 'cancelled' order.cancelReason = reason refreshStatus(order) appendTimeline(order, '已拒单', reason) return cloneOrder(order) } export async function mockStartDepart(orderId: string, location: DeliveryLocationType): Promise { await delay() const order = getOrderById(orderId) if (order == null) return null order.status = 'on_the_way' order.departTime = location.time order.lastLocation = location order.trackPoints.push(location) refreshStatus(order) appendTimeline(order, '已出发', '当前位置已上报,正在前往服务地址') return cloneOrder(order) } export async function mockArriveOrder(orderId: string, location: DeliveryLocationType): Promise { await delay() const order = getOrderById(orderId) if (order == null) return null order.status = 'arrived' order.arriveTime = location.time order.lastLocation = location order.trackPoints.push(location) refreshStatus(order) appendTimeline(order, '已到达', '服务人员已到达服务地点') return cloneOrder(order) } export async function mockCheckinOrder(orderId: string, payload: DeliveryCheckinPayloadType): Promise { await delay() const order = getOrderById(orderId) if (order == null) return null order.status = 'checked_in' order.checkinTime = payload.location.time order.lastLocation = payload.location order.trackPoints.push(payload.location) refreshStatus(order) appendTimeline(order, '签到完成', payload.note != '' ? payload.note : '已完成定位签到和现场拍照') for (let i = 0; i < payload.photos.length; i++) { const evidence: DeliveryEvidenceRecordType = { id: 'checkin-' + orderId + '-' + String(order.evidenceList.length + 1), orderId, phase: 'before', fileType: 'image', name: '签到照片' + String(i + 1), url: payload.photos[i], localPath: payload.photos[i], status: 'success', progress: 100, retryable: false, createdAt: nowText() } order.evidenceList.push(evidence) } return cloneOrder(order) } export async function mockStartService(orderId: string): Promise { await delay() const order = getOrderById(orderId) if (order == null) return null order.status = 'serving' if (order.actualStartTime == '') { order.actualStartTime = nowText() } refreshStatus(order) appendTimeline(order, '开始服务', '服务计时已开始') return cloneOrder(order) } export async function mockSaveProgress(orderId: string, payload: DeliveryProgressPayloadType): Promise { await delay() const order = getOrderById(orderId) if (order == null) return null order.progressNote = payload.progressNote order.serviceSummary = payload.serviceSummary order.serviceItems = JSON.parse(JSON.stringify(payload.items)) as Array appendTimeline(order, '服务记录暂存', payload.progressNote != '' ? payload.progressNote : '已保存当前服务进度') if (order.status == 'serving') { order.status = 'pending_submit' refreshStatus(order) } return cloneOrder(order) } let duringUploadFailedOnce = false export async function mockUploadEvidence(orderId: string, phase: string, files: Array): Promise> { await delay() const order = getOrderById(orderId) const result: Array = [] if (order == null) return result for (let i = 0; i < files.length; i++) { let status = 'success' let progress = 100 let retryable = false if (phase == 'during' && duringUploadFailedOnce == false) { status = 'failed' progress = 0 retryable = true duringUploadFailedOnce = true } const evidence: DeliveryEvidenceRecordType = { id: 'evidence-' + orderId + '-' + String(order.evidenceList.length + 1), orderId, phase, fileType: 'image', name: phase + '-image-' + String(i + 1), url: status == 'success' ? files[i] : '', localPath: files[i], status, progress, retryable, createdAt: nowText() } order.evidenceList.push(evidence) result.push(JSON.parse(JSON.stringify(evidence)) as DeliveryEvidenceRecordType) } appendTimeline(order, '证据材料更新', '已上传 ' + phase + ' 阶段材料') return result } export async function mockRetryEvidence(orderId: string, evidenceId: string): Promise { await delay() const order = getOrderById(orderId) if (order == null) return null for (let i = 0; i < order.evidenceList.length; i++) { const evidence = order.evidenceList[i] if (evidence.id == evidenceId) { evidence.status = 'success' evidence.progress = 100 evidence.retryable = false evidence.url = evidence.localPath appendTimeline(order, '证据重试成功', evidence.name + ' 已重新上传') return JSON.parse(JSON.stringify(evidence)) as DeliveryEvidenceRecordType } } return null } export async function mockSubmitException(orderId: string, payload: DeliveryExceptionPayloadType): Promise { await delay() const order = getOrderById(orderId) if (order == null) return null order.status = 'exception_pending' order.exceptionType = payload.exceptionType order.exceptionDesc = payload.description refreshStatus(order) appendTimeline(order, '异常上报', payload.description) MESSAGE_STORE.unshift({ id: 'msg-' + String(Date.now()), title: '异常已上报', content: order.orderNo + ' 已提交异常,请等待调度处理。', type: 'exception', createdAt: nowText(), read: false, orderId }) return cloneOrder(order) } function hasRequiredEvidence(order: DeliveryOrderType): boolean { let count = 0 for (let i = 0; i < order.evidenceList.length; i++) { if (order.evidenceList[i].status == 'success') { count++ } } return count >= 2 } function hasServiceRecord(order: DeliveryOrderType): boolean { if (order.progressNote != '' || order.serviceSummary != '') { return true } for (let i = 0; i < order.serviceItems.length; i++) { if (order.serviceItems[i].completed == true) { return true } } return false } export async function mockFinishService(orderId: string, payload: DeliveryFinishPayloadType): Promise { await delay() const order = getOrderById(orderId) if (order == null) return null if (!hasServiceRecord(order)) { throw new Error('请至少填写一项服务记录后再提交完成') } if (!hasRequiredEvidence(order)) { throw new Error('请至少上传两份有效证据材料后再提交完成') } order.status = 'pending_acceptance' order.actualEndTime = nowText() order.finishTime = order.actualEndTime order.signatureName = payload.signatureName order.signatureUrl = payload.signatureName != '' ? 'mock-signature://' + payload.signatureName : '' order.serviceSummary = payload.serviceSummary refreshStatus(order) appendTimeline(order, '完成提交', '服务记录、证据和签名已提交,等待验收') return cloneOrder(order) } export async function mockGetRecords(staffId: string): Promise> { await delay() const records = buildRecords() const list: Array = [] for (let i = 0; i < records.length; i++) { const order = getOrderById(records[i].orderId) if (order != null && order.deliveryStaffId == staffId) { list.push(cloneRecord(records[i])) } } return list } export async function mockGetMessages(): Promise> { await delay() return JSON.parse(JSON.stringify(MESSAGE_STORE)) as Array } export async function mockUpdateOnlineStatus(staffId: string, status: string): Promise { await delay() for (let i = 0; i < DELIVERY_STAFF_STORE.length; i++) { if (DELIVERY_STAFF_STORE[i].id == staffId) { DELIVERY_STAFF_STORE[i].onlineStatus = status as any return cloneDeliveryInfo(DELIVERY_STAFF_STORE[i]) } } return null }