完善下单逻辑及其ui展示,修复支付倒计时显示错误bug
This commit is contained in:
@@ -12,39 +12,80 @@ import {
|
||||
HomeServiceTaskType,
|
||||
HomeServiceTimelineItemType
|
||||
} from '@/types/home-service.uts'
|
||||
import {
|
||||
confirmServiceOrder,
|
||||
createServiceOrder,
|
||||
getServiceOrderDetail,
|
||||
listConsumerServiceOrders,
|
||||
rejectServiceOrderAcceptance
|
||||
} from '@/services/serviceOrderService.uts'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import {
|
||||
getServiceOrderStatusText,
|
||||
type ServiceOrderStatus,
|
||||
type ServiceOrderTimelineItemType,
|
||||
type ServiceOrderType
|
||||
} from '@/types/service-order.uts'
|
||||
|
||||
const SERVICE_CATALOG: Array<HomeServiceCatalogType> = [
|
||||
{
|
||||
id: 'svc-001',
|
||||
name: '基础上门护理',
|
||||
category: '日常照护',
|
||||
price: 168,
|
||||
durationText: '约 2 小时',
|
||||
summary: '覆盖生命体征监测、基础照护、风险提醒。',
|
||||
tags: ['适老化', '护理员上门', '支持家属陪同'],
|
||||
suitableFor: '行动不便、术后恢复、慢病随访老人'
|
||||
},
|
||||
{
|
||||
id: 'svc-002',
|
||||
name: '康复训练指导',
|
||||
category: '康复支持',
|
||||
price: 260,
|
||||
durationText: '约 3 小时',
|
||||
summary: '提供肢体训练、步态练习和居家康复建议。',
|
||||
tags: ['康复师', '步骤清晰', '可连续预约'],
|
||||
suitableFor: '卒中恢复、术后康复、失能半失能老人'
|
||||
},
|
||||
{
|
||||
id: 'svc-003',
|
||||
name: '慢病健康随访',
|
||||
category: '健康管理',
|
||||
price: 128,
|
||||
durationText: '约 90 分钟',
|
||||
summary: '完成血压血糖监测、用药核对与健康宣教。',
|
||||
tags: ['随访', '慢病', '可生成记录'],
|
||||
suitableFor: '高血压、糖尿病等长期管理老人'
|
||||
function plainObject(source: any): any {
|
||||
return JSON.parse(JSON.stringify(source)) as any
|
||||
}
|
||||
|
||||
function readString(source: any, key: string): string {
|
||||
const value = plainObject(source)[key]
|
||||
if (value == null) {
|
||||
return ''
|
||||
}
|
||||
]
|
||||
return typeof value == 'string' ? value : String(value)
|
||||
}
|
||||
|
||||
function readNumber(source: any, key: string): number {
|
||||
const value = plainObject(source)[key]
|
||||
if (typeof value == 'number') {
|
||||
return value
|
||||
}
|
||||
const parsed = Number(value)
|
||||
return isNaN(parsed) ? 0 : parsed
|
||||
}
|
||||
|
||||
function readStringArray(source: any, key: string): Array<string> {
|
||||
const value = plainObject(source)[key]
|
||||
if (Array.isArray(value)) {
|
||||
const result = [] as Array<string>
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
result.push(typeof value[i] == 'string' ? value[i] : String(value[i]))
|
||||
}
|
||||
return result
|
||||
}
|
||||
if (typeof value == 'string' && value != '') {
|
||||
try {
|
||||
const parsed = JSON.parse(value) as any
|
||||
if (Array.isArray(parsed)) {
|
||||
const result = [] as Array<string>
|
||||
for (let i = 0; i < parsed.length; i++) {
|
||||
result.push(typeof parsed[i] == 'string' ? parsed[i] : String(parsed[i]))
|
||||
}
|
||||
return result
|
||||
}
|
||||
} catch (error) {
|
||||
return [] as Array<string>
|
||||
}
|
||||
}
|
||||
return [] as Array<string>
|
||||
}
|
||||
|
||||
function parseCatalogItem(source: any): HomeServiceCatalogType {
|
||||
return {
|
||||
id: readString(source, 'id'),
|
||||
name: readString(source, 'name'),
|
||||
category: readString(source, 'category'),
|
||||
price: readNumber(source, 'price'),
|
||||
durationText: readString(source, 'duration_text'),
|
||||
summary: readString(source, 'summary'),
|
||||
tags: readStringArray(source, 'tags_json'),
|
||||
suitableFor: readString(source, 'suitable_for')
|
||||
}
|
||||
}
|
||||
|
||||
function createTimeline(title1: string, title2: string, title3: string): Array<HomeServiceTimelineItemType> {
|
||||
return [
|
||||
@@ -243,31 +284,6 @@ const ADMIN_PLANS: Array<HomeServicePlanType> = [
|
||||
}
|
||||
]
|
||||
|
||||
const CONSUMER_ACCEPTANCE: Array<HomeServiceAcceptanceType> = [
|
||||
{
|
||||
caseId: 'case-001',
|
||||
caseNo: 'HS202605130001',
|
||||
elderName: '李奶奶',
|
||||
serviceName: '基础上门护理',
|
||||
acceptanceStatus: 'waiting',
|
||||
acceptanceStatusText: '待验收',
|
||||
rating: 5,
|
||||
feedback: '护理员按时到达,服务过程清晰,老人状态稳定。',
|
||||
tags: ['准时上门', '沟通清楚', '动作规范']
|
||||
},
|
||||
{
|
||||
caseId: 'case-002',
|
||||
caseNo: 'HS202605130002',
|
||||
elderName: '张爷爷',
|
||||
serviceName: '慢病健康随访',
|
||||
acceptanceStatus: 'waiting',
|
||||
acceptanceStatusText: '待验收',
|
||||
rating: 5,
|
||||
feedback: '已查看记录,等待家属最终确认。',
|
||||
tags: ['记录完整', '态度耐心']
|
||||
}
|
||||
]
|
||||
|
||||
const ADMIN_RECTIFICATIONS: Array<HomeServiceRectificationType> = [
|
||||
{
|
||||
caseId: 'case-001',
|
||||
@@ -355,13 +371,6 @@ function clonePlan(item: HomeServicePlanType): HomeServicePlanType {
|
||||
}
|
||||
}
|
||||
|
||||
function cloneAcceptance(item: HomeServiceAcceptanceType): HomeServiceAcceptanceType {
|
||||
return {
|
||||
...item,
|
||||
tags: item.tags.slice(0)
|
||||
}
|
||||
}
|
||||
|
||||
function cloneRectification(item: HomeServiceRectificationType): HomeServiceRectificationType {
|
||||
return {
|
||||
...item
|
||||
@@ -375,68 +384,196 @@ function cloneSettlement(item: HomeServiceSettlementType): HomeServiceSettlement
|
||||
}
|
||||
|
||||
export async function fetchHomeServiceCatalog(): Promise<Array<HomeServiceCatalogType>> {
|
||||
await delay()
|
||||
return SERVICE_CATALOG.map((item) => ({ ...item, tags: item.tags.slice(0) }))
|
||||
const response = await supa
|
||||
.from('hss_service_catalog')
|
||||
.select('id, name, category, price, duration_text, summary, tags_json, suitable_for, sort_no')
|
||||
.eq('status', 1)
|
||||
.is('deleted_at', null)
|
||||
.order('sort_no', { ascending: true })
|
||||
.execute()
|
||||
if (response.error != null || response.data == null || !Array.isArray(response.data)) {
|
||||
return [] as Array<HomeServiceCatalogType>
|
||||
}
|
||||
const result = [] as Array<HomeServiceCatalogType>
|
||||
for (let i = 0; i < response.data.length; i++) {
|
||||
result.push(parseCatalogItem(response.data[i]))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export async function fetchConsumerHomeServiceCases(): Promise<Array<HomeServiceCaseType>> {
|
||||
await delay()
|
||||
return CASE_STORE.map((item) => cloneCase(item))
|
||||
const orders = await listConsumerServiceOrders()
|
||||
const result = [] as Array<HomeServiceCaseType>
|
||||
for (let i = 0; i < orders.length; i++) {
|
||||
result.push(mapOrderToCase(orders[i]))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export async function fetchConsumerHomeServiceCaseDetail(caseId: string): Promise<HomeServiceCaseType | null> {
|
||||
await delay()
|
||||
const target = CASE_STORE.find((item) => item.id == caseId)
|
||||
return target == null ? null : cloneCase(target)
|
||||
const detail = await getServiceOrderDetail(caseId)
|
||||
if (detail != null) {
|
||||
return mapOrderToCase(detail)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export async function createHomeServiceApplication(draft: HomeServiceApplicationDraftType): Promise<HomeServiceCaseType> {
|
||||
await delay()
|
||||
const matchedService = SERVICE_CATALOG.find((item) => item.id == draft.serviceId)
|
||||
const created: HomeServiceCaseType = {
|
||||
id: 'case-' + String(CASE_STORE.length + 1).padStart(3, '0'),
|
||||
caseNo: 'HS20260513' + String(CASE_STORE.length + 1).padStart(4, '0'),
|
||||
status: 'submitted',
|
||||
statusText: '已提交',
|
||||
statusTone: 'primary',
|
||||
serviceName: draft.serviceName,
|
||||
serviceTime: draft.preferredTime,
|
||||
applicantName: draft.applicantName,
|
||||
elderName: draft.elderName,
|
||||
age: draft.age,
|
||||
phone: draft.phone,
|
||||
address: draft.address,
|
||||
summary: draft.demandSummary,
|
||||
currentStep: 1,
|
||||
totalSteps: 8,
|
||||
staffName: '待分配',
|
||||
staffPhone: '待分配',
|
||||
amount: matchedService != null ? matchedService.price : 0,
|
||||
timeline: [
|
||||
{
|
||||
id: 'new-1',
|
||||
title: '已提交申请',
|
||||
time: '2026-05-13 10:00',
|
||||
description: '系统已接收申请,等待受理。'
|
||||
}
|
||||
]
|
||||
export async function createHomeServiceApplication(draft: HomeServiceApplicationDraftType): Promise<HomeServiceCaseType | null> {
|
||||
const catalog = await fetchHomeServiceCatalog()
|
||||
let matchedService: HomeServiceCatalogType | null = null
|
||||
for (let i = 0; i < catalog.length; i++) {
|
||||
if (catalog[i].id == draft.serviceId) {
|
||||
matchedService = catalog[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if (matchedService != null && draft.serviceAddressSnapshot != null) {
|
||||
const createdOrder = await createServiceOrder({
|
||||
service: matchedService,
|
||||
address: {
|
||||
addressId: draft.serviceAddressSnapshot.addressId,
|
||||
contactName: draft.serviceAddressSnapshot.contactName,
|
||||
contactPhone: draft.serviceAddressSnapshot.contactPhone,
|
||||
province: '',
|
||||
city: '',
|
||||
district: '',
|
||||
detailAddress: draft.serviceAddressSnapshot.addressDetail,
|
||||
fullAddress: draft.serviceAddressSnapshot.fullAddress,
|
||||
latitude: draft.serviceAddressSnapshot.latitude,
|
||||
longitude: draft.serviceAddressSnapshot.longitude,
|
||||
coordinateType: draft.serviceAddressSnapshot.coordinateType,
|
||||
remark: draft.serviceAddressSnapshot.remark
|
||||
},
|
||||
recipientName: draft.elderName,
|
||||
recipientPhone: draft.phone,
|
||||
contactName: draft.applicantName,
|
||||
contactPhone: draft.phone,
|
||||
appointmentTime: draft.preferredTime,
|
||||
remark: draft.demandSummary
|
||||
})
|
||||
if (createdOrder != null) {
|
||||
return mapOrderToCase(createdOrder)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function getCaseStep(status: ServiceOrderStatus): number {
|
||||
if (status == 'created') return 1
|
||||
if (status == 'assigned') return 3
|
||||
if (status == 'accepted') return 4
|
||||
if (status == 'departed') return 5
|
||||
if (status == 'arrived' || status == 'in_service') return 6
|
||||
if (status == 'pending_acceptance') return 7
|
||||
if (status == 'accepted_by_user' || status == 'reviewed' || status == 'settled') return 8
|
||||
if (status == 'rejected' || status == 'cancelled' || status == 'exception') return 7
|
||||
return 2
|
||||
}
|
||||
|
||||
function getCaseTone(status: ServiceOrderStatus): string {
|
||||
if (status == 'created' || status == 'assigned' || status == 'pending_acceptance') return 'warning'
|
||||
if (status == 'accepted' || status == 'departed' || status == 'arrived' || status == 'in_service') return 'primary'
|
||||
if (status == 'accepted_by_user' || status == 'reviewed' || status == 'settled') return 'success'
|
||||
if (status == 'exception' || status == 'cancelled' || status == 'rejected') return 'danger'
|
||||
return 'neutral'
|
||||
}
|
||||
|
||||
function getTimelineDescription(log: ServiceOrderTimelineItemType): string {
|
||||
if (log.remark != '') {
|
||||
return log.remark
|
||||
}
|
||||
if (log.toStatus == 'created') return '平台已接收预约申请,等待后续处理。'
|
||||
if (log.toStatus == 'assigned') return '系统已完成派单,正在通知服务人员。'
|
||||
if (log.toStatus == 'accepted') return '服务人员已接单,正在准备上门。'
|
||||
if (log.toStatus == 'departed') return '服务人员已出发,正在前往服务地点。'
|
||||
if (log.toStatus == 'arrived') return '服务人员已到达服务地点。'
|
||||
if (log.toStatus == 'in_service') return '服务已开始执行,请留意后续进度。'
|
||||
if (log.toStatus == 'completed') return '服务执行已完成,正在整理结果。'
|
||||
if (log.toStatus == 'pending_acceptance') return '服务记录已提交,等待家属验收。'
|
||||
if (log.toStatus == 'accepted_by_user') return '家属已确认本次服务结果。'
|
||||
if (log.toStatus == 'reviewed') return '家属已完成评价反馈。'
|
||||
if (log.toStatus == 'settled') return '本次服务已完成结算归档。'
|
||||
if (log.toStatus == 'cancelled') return '服务单已取消。'
|
||||
if (log.toStatus == 'rejected') return '服务人员未接受该工单。'
|
||||
return '服务过程状态已更新。'
|
||||
}
|
||||
|
||||
function formatServiceAppointmentText(value: string): string {
|
||||
if (value == '') {
|
||||
return ''
|
||||
}
|
||||
if (value.indexOf('上午') >= 0 || value.indexOf('下午') >= 0 || value.indexOf('晚上') >= 0) {
|
||||
return value
|
||||
}
|
||||
const parsed = Date.parse(value)
|
||||
if (!isNaN(parsed)) {
|
||||
const date = new Date(parsed)
|
||||
let year = date.getFullYear()
|
||||
const currentYear = new Date().getFullYear()
|
||||
if (year < currentYear - 1) {
|
||||
year = currentYear
|
||||
}
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hour = String(date.getHours()).padStart(2, '0')
|
||||
const minute = String(date.getMinutes()).padStart(2, '0')
|
||||
return year + '-' + month + '-' + day + ' ' + hour + ':' + minute
|
||||
}
|
||||
const monthDayMatch = value.match(/(\d{2})\/(\d{2})/)
|
||||
const timeMatch = value.match(/(\d{2}:\d{2}(\s*-\s*\d{2}:\d{2})?)/)
|
||||
if (monthDayMatch != null) {
|
||||
const month = monthDayMatch[1] ?? ''
|
||||
const day = monthDayMatch[2] ?? ''
|
||||
const timeText = timeMatch != null ? (timeMatch[1] ?? '') : ''
|
||||
if (month != '' && day != '') {
|
||||
return String(new Date().getFullYear()) + '-' + month + '-' + day + (timeText != '' ? ' ' + timeText.replace(/\s+/g, '') : '')
|
||||
}
|
||||
}
|
||||
return value.replace('T', ' ')
|
||||
}
|
||||
|
||||
function mapLogsToTimeline(logs: Array<ServiceOrderTimelineItemType>): Array<HomeServiceTimelineItemType> {
|
||||
const timeline = [] as Array<HomeServiceTimelineItemType>
|
||||
for (let i = 0; i < logs.length; i++) {
|
||||
timeline.push({
|
||||
id: logs[i].id,
|
||||
title: getServiceOrderStatusText(logs[i].toStatus),
|
||||
time: logs[i].createdAt,
|
||||
description: getTimelineDescription(logs[i])
|
||||
})
|
||||
}
|
||||
return timeline
|
||||
}
|
||||
|
||||
function mapOrderToCase(order: ServiceOrderType): HomeServiceCaseType {
|
||||
return {
|
||||
id: order.id,
|
||||
caseNo: order.orderNo,
|
||||
status: order.status,
|
||||
statusText: getServiceOrderStatusText(order.status),
|
||||
statusTone: getCaseTone(order.status),
|
||||
serviceName: order.serviceName,
|
||||
serviceTime: formatServiceAppointmentText(order.appointmentTime),
|
||||
applicantName: order.contactName,
|
||||
elderName: order.recipientName,
|
||||
age: 0,
|
||||
phone: order.contactPhone,
|
||||
address: order.addressSnapshot.fullAddress,
|
||||
summary: order.remark,
|
||||
currentStep: getCaseStep(order.status),
|
||||
totalSteps: 8,
|
||||
staffName: order.staffName == '' ? '待分配' : order.staffName,
|
||||
staffPhone: order.staffPhone == '' ? '待分配' : order.staffPhone,
|
||||
amount: order.serviceSnapshot.price,
|
||||
checkinTime: order.executionRecord != null ? order.executionRecord.checkinTime : '',
|
||||
checkinAddress: order.executionRecord != null ? order.executionRecord.checkinAddress : '',
|
||||
serviceStartedAt: order.executionRecord != null ? order.executionRecord.serviceStartedAt : order.serviceStartedAt,
|
||||
serviceFinishedAt: order.executionRecord != null ? order.executionRecord.serviceFinishedAt : order.completedAt,
|
||||
executionSummary: order.executionRecord != null ? (order.executionRecord.summary != '' ? order.executionRecord.summary : order.executionRecord.remark) : '',
|
||||
evidenceCount: order.evidenceFiles.length,
|
||||
serviceAddressSnapshot: null,
|
||||
timeline: mapLogsToTimeline(order.logs)
|
||||
}
|
||||
CASE_STORE.unshift(created)
|
||||
ADMIN_APPLICATIONS.unshift({
|
||||
id: 'admin-app-' + String(ADMIN_APPLICATIONS.length + 1).padStart(3, '0'),
|
||||
caseId: created.id,
|
||||
caseNo: created.caseNo,
|
||||
status: 'submitted',
|
||||
statusText: '已提交',
|
||||
statusTone: 'primary',
|
||||
elderName: created.elderName,
|
||||
serviceName: created.serviceName,
|
||||
preferredTime: created.serviceTime,
|
||||
assessmentResult: '待受理',
|
||||
dispatcherName: '待分配',
|
||||
staffName: '待分配'
|
||||
})
|
||||
return cloneCase(created)
|
||||
}
|
||||
|
||||
export async function fetchWorkerTasks(): Promise<Array<HomeServiceTaskType>> {
|
||||
@@ -738,9 +875,21 @@ export async function submitAdminServicePlan(
|
||||
}
|
||||
|
||||
export async function fetchConsumerAcceptanceDetail(caseId: string): Promise<HomeServiceAcceptanceType | null> {
|
||||
await delay()
|
||||
const target = CONSUMER_ACCEPTANCE.find((item) => item.caseId == caseId)
|
||||
return target == null ? null : cloneAcceptance(target)
|
||||
const detail = await getServiceOrderDetail(caseId)
|
||||
if (detail != null) {
|
||||
return {
|
||||
caseId: detail.id,
|
||||
caseNo: detail.orderNo,
|
||||
elderName: detail.recipientName,
|
||||
serviceName: detail.serviceName,
|
||||
acceptanceStatus: detail.status,
|
||||
acceptanceStatusText: getServiceOrderStatusText(detail.status),
|
||||
rating: detail.review != null ? detail.review.rating : 5,
|
||||
feedback: detail.review != null ? detail.review.content : '',
|
||||
tags: detail.review != null ? detail.review.tags : [] as Array<string>
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export async function submitConsumerAcceptance(
|
||||
@@ -750,40 +899,18 @@ export async function submitConsumerAcceptance(
|
||||
feedback: string,
|
||||
tags: Array<string>
|
||||
): Promise<HomeServiceAcceptanceType | null> {
|
||||
await delay()
|
||||
const target = CONSUMER_ACCEPTANCE.find((item) => item.caseId == caseId)
|
||||
if (target == null) {
|
||||
if (approved) {
|
||||
const result = await confirmServiceOrder(caseId, rating, feedback, tags)
|
||||
if (result != null) {
|
||||
return await fetchConsumerAcceptanceDetail(caseId)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
target.acceptanceStatus = approved ? 'approved' : 'rejected'
|
||||
target.acceptanceStatusText = approved ? '已验收' : '已退回'
|
||||
target.rating = rating
|
||||
target.feedback = feedback
|
||||
target.tags = tags.slice(0)
|
||||
|
||||
const relatedCase = CASE_STORE.find((item) => item.id == caseId)
|
||||
if (relatedCase != null) {
|
||||
relatedCase.status = approved ? 'completed' : 'revision_required'
|
||||
relatedCase.statusText = approved ? '已完成' : '待整改'
|
||||
relatedCase.statusTone = approved ? 'success' : 'warning'
|
||||
relatedCase.currentStep = approved ? 8 : 7
|
||||
relatedCase.timeline.unshift({
|
||||
id: 'case-accept-' + String(relatedCase.timeline.length + 1),
|
||||
title: approved ? '家属已验收' : '家属退回整改',
|
||||
time: '2026-05-13 17:10',
|
||||
description: feedback
|
||||
})
|
||||
const rejected = await rejectServiceOrderAcceptance(caseId, feedback)
|
||||
if (rejected != null) {
|
||||
return await fetchConsumerAcceptanceDetail(caseId)
|
||||
}
|
||||
|
||||
const relatedAdmin = ADMIN_APPLICATIONS.find((item) => item.caseId == caseId)
|
||||
if (relatedAdmin != null) {
|
||||
relatedAdmin.status = approved ? 'completed' : 'revision_required'
|
||||
relatedAdmin.statusText = approved ? '已完成' : '待整改'
|
||||
relatedAdmin.statusTone = approved ? 'success' : 'warning'
|
||||
}
|
||||
|
||||
return cloneAcceptance(target)
|
||||
return null
|
||||
}
|
||||
|
||||
export async function fetchAdminRectificationDetail(caseId: string): Promise<HomeServiceRectificationType | null> {
|
||||
|
||||
Reference in New Issue
Block a user