fix(appointments): handle synchronization and transport continuation

This commit is contained in:
2026-10-18 22:29:06 +08:00
parent a5a1976924
commit 67d917010d
9 changed files with 226 additions and 11 deletions
@@ -17,6 +17,7 @@ import WorkReceivingPanel from './WorkReceivingPanel.vue'
import WorkBatchContinuation from './WorkBatchContinuation.vue'
import type { FbaAppointmentDto } from '@/modules/overseas-warehouse'
import type { LeaveGuard } from './useWorkDraft'
import { useWorkSyncRead } from './useWorkSyncRead'
const props = defineProps<{ itemKey: string; initialAction?: WorkAction | null; canLeave: LeaveGuard }>()
const emit = defineEmits<{ open: [key: string, action?: WorkAction]; external: [path: string]; saved: [result?: WorkCommandResult, command?: WorkCommand['command']]; updated: []; container: [id: string, label: string] }>()
const detail = ref<WorkDetail | null>(null), loading = ref(false), error = ref<ApiErrorDetails | null>(null), action = ref<WorkAction | null>(null)
@@ -28,9 +29,11 @@ const batchKey = ref<string | null>(null), openedAppointmentNo = ref<string | nu
const receivingKey = ref<string | null>(null)
const continuation = ref<{ batchKey: string; customerId?: string; labels?: Array<{ customerId: string; label: string }> } | null>(null)
let controller: AbortController | null = null, request = 0
const syncRead = useWorkSyncRead(), syncing = syncRead.syncing
async function load() {
const generation = ++request; controller?.abort(); controller = new AbortController(); loading.value = true; error.value = null
try { const result = await getWorkDetail(props.itemKey, controller.signal); if (generation === request) detail.value = result }
const signal = controller.signal
try { const result = await syncRead.read(() => getWorkDetail(props.itemKey, signal), signal); if (generation === request) detail.value = result }
catch (cause) { if (generation === request) error.value = getApiErrorDetails(cause, '工作事项读取失败') }
finally { if (generation === request) loading.value = false }
}
@@ -60,7 +63,9 @@ const secondaryActions = computed(() => detail.value?.item.allowedActions.filter
async function choose(next: WorkAction) {
if (!detail.value || loading.value || error.value || !await props.canLeave()) return
actionNotice.value = ''
if (!actionAllowed(detail.value.item, next)) { actionNotice.value = '操作资格已变化,请刷新事项后查看当前下一步。'; return }
// 跨事项的主要下一步只用于导航,目标面板会重新读取并校验自己的写入资格。
const linkedNext = next.targetWorkItemKey && next.targetWorkItemKey !== props.itemKey && detail.value.item.nextAction && sameAction(next, detail.value.item.nextAction)
if (!linkedNext && !actionAllowed(detail.value.item, next)) { actionNotice.value = '操作资格已变化,请刷新事项后查看当前下一步。'; return }
saveCompleted.value = false
if (next.targetWorkItemKey && next.targetWorkItemKey !== props.itemKey) { emit('open', next.targetWorkItemKey, next); return }
const path = actionRoute(next, detail.value.references, detail.value.item)
@@ -102,7 +107,7 @@ onBeforeUnmount(() => { ++request; controller?.abort() })
<template>
<div :aria-busy="loading">
<LoadErrorState v-if="error" title="工作事项读取失败" :message="error.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
<p v-if="loading">正在读取当前事项与操作资格…</p>
<p v-if="loading" role="status">{{ syncing ? '预约工作清单同步中,正在自动读取最新事项;已保存内容无需重复提交。' : '正在读取当前事项与操作资格…' }}</p>
<template v-if="detail">
<h3>{{ detail.item.title }}</h3><p>{{ typeLabels[detail.item.type] }} · {{ flowLabels[detail.item.flow] }} · {{ workStatus(detail.item) }}</p>
<p>{{ workDate(detail.item) }} · {{ detail.item.timezone || '地点时区待核实' }}</p><p>{{ detail.item.destinationName || detail.item.originName || '地点待核实' }} · {{ detail.item.owner?.name || '待分派' }}</p>