fix: align audit and dispatch workflows and release failed requests
This commit is contained in:
@@ -19,7 +19,6 @@ const access = useAccessPreviewStore(), rows = ref<PickupTask[]>([]), loading =
|
||||
const selected = ref<PickupTask | null>(null), open = ref(false), actual = ref(1), remark = ref(''), evidence = ref<PickupEvidence[]>([])
|
||||
const needsTripRefresh = ref(false)
|
||||
const pending = ref<{ action: 'depart' | 'complete' | 'picked' | 'arrive'; row: PickupTask | null; body: PickupCommand | PickupConfirmation } | null>(null)
|
||||
// 命令结果未知时阻止父页面刷新和改派,保留原请求用于幂等重试。
|
||||
watch(() => busy.value || !!pending.value, value => emit('locked', value), { flush: 'sync' })
|
||||
let sequence = 0
|
||||
const canComplete = computed(() => rows.value.length > 0 && rows.value.every(row => row.status === 'ARRIVED'))
|
||||
@@ -57,9 +56,8 @@ async function upload(event: Event) {
|
||||
busy.value = true; error.value = ''; const current = sequence
|
||||
try { const value = await uploadPickupEvidence(props.detail.trip.tripNo, selected.value, file); if (current === sequence) evidence.value.push(value) }
|
||||
catch (cause) { if (current === sequence) error.value = getApiErrorDetails(cause, '取货凭证上传失败').message }
|
||||
finally { if (current === sequence) busy.value = false; input.value = '' }
|
||||
finally { pending.value = null; if (current === sequence) busy.value = false; input.value = '' }
|
||||
}
|
||||
/** 失败结果未知时固定原请求,成功后只刷新车次版本和成员。 */
|
||||
async function execute() {
|
||||
if (!pending.value || busy.value) return
|
||||
const request = pending.value, current = sequence, number = props.detail.trip.tripNo
|
||||
@@ -77,7 +75,7 @@ async function execute() {
|
||||
}
|
||||
} catch (cause) {
|
||||
if (current === sequence) { error.value = getApiErrorDetails(cause, '取货操作未完成').message; if (cause instanceof ApiResponseError) pending.value = null }
|
||||
} finally { if (current === sequence) busy.value = false }
|
||||
} finally { pending.value = null; if (current === sequence) busy.value = false }
|
||||
}
|
||||
/** 发车、送仓和结案提交前说明本次事实。 */
|
||||
async function command(action: 'depart' | 'arrive' | 'complete', row: PickupTask | null = null) {
|
||||
@@ -89,7 +87,6 @@ async function command(action: 'depart' | 'arrive' | 'complete', row: PickupTask
|
||||
}
|
||||
/** 照片和实提箱数为取货确认的必要信息。 */
|
||||
async function confirmPickup() {
|
||||
if (pending.value) { await execute(); return }
|
||||
if (needsTripRefresh.value) { ElMessage.warning('请先刷新车次后继续'); return }
|
||||
if (!selected.value || !Number.isInteger(actual.value) || actual.value <= 0 || !evidence.value.length) { ElMessage.warning('请填写实提箱数并上传至少一份取货凭证'); return }
|
||||
pending.value = { action: 'picked', row: selected.value, body: { recordVersion: props.detail.trip.recordVersion, idempotencyKey: crypto.randomUUID(), actualPackages: actual.value, evidenceFileIds: evidence.value.map(file => file.fileId), remark: remark.value.trim() } }
|
||||
@@ -100,9 +97,8 @@ async function download(row: PickupTask, file: PickupEvidence) {
|
||||
try { const blob = await downloadPickupEvidence(row, file.fileId); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = file.fileName; link.click(); setTimeout(() => URL.revokeObjectURL(url), 1000) }
|
||||
catch (cause) { error.value = getApiErrorDetails(cause, '凭证下载失败').message }
|
||||
}
|
||||
/** 原命令结果未知时保持弹框与请求快照。 */
|
||||
function close(done: () => void) { if (busy.value || pending.value) { ElMessage.warning('请先确认原取货操作的结果'); return }; done() }
|
||||
onBeforeRouteLeave(() => { if (busy.value || pending.value) { ElMessage.warning('请先重试并确认原取货操作'); return false }; return true })
|
||||
function close(done: () => void) { if (busy.value || pending.value) { ElMessage.warning('操作正在提交,请稍候'); return }; done() }
|
||||
onBeforeRouteLeave(() => { if (busy.value || pending.value) { ElMessage.warning('操作正在提交,请稍候'); return false }; return true })
|
||||
watch(() => props.detail.trip.tripNo, () => { pending.value = null; open.value = false; rows.value = []; void load() }, { immediate: true })
|
||||
onBeforeUnmount(() => { sequence++ })
|
||||
</script>
|
||||
@@ -112,15 +108,15 @@ onBeforeUnmount(() => { sequence++ })
|
||||
<el-button v-if="detail.trip.status === 'PLANNED' && access.can(permission.tripAssign)" type="primary" :disabled="busy || !!pending || needsTripRefresh || !detail.trip.vehicleId || !detail.trip.driverId" @click="command('depart')">确认取货发车</el-button>
|
||||
<el-button v-if="detail.trip.status === 'IN_TRANSIT' && access.can(permission.tripComplete)" type="primary" :disabled="busy || !!pending || needsTripRefresh || !canComplete" @click="command('complete')">取货运输结案</el-button>
|
||||
<el-button :disabled="busy || !!pending" @click="load">刷新取货任务</el-button>
|
||||
<el-button v-if="pending && !open" type="primary" :loading="busy" @click="execute">重试原操作</el-button>
|
||||
|
||||
</div>
|
||||
<LoadErrorState v-if="error && !open" title="取货操作未完成" :message="error" @retry="pending ? execute() : load()" />
|
||||
<LoadErrorState v-if="error && !open" title="取货操作未完成" :message="error" @retry="load()" />
|
||||
<div class="workbench-table"><DataTable :data="rows" :columns="columns" :loading="loading" row-key="transferNo" empty-text="暂无取货成员">
|
||||
<template #actions="{ row }"><div class="density-actions"><el-button v-if="detail.trip.status === 'IN_TRANSIT' && row.status === 'ASSIGNED' && access.can(permission.tripEdit)" :disabled="busy || !!pending || needsTripRefresh" @click="edit(row as PickupTask)">确认取货</el-button><el-button v-if="detail.trip.status === 'IN_TRANSIT' && row.status === 'PICKED' && access.can(permission.tripEdit)" :disabled="busy || !!pending || needsTripRefresh" @click="command('arrive', row as PickupTask)">确认送仓</el-button></div></template>
|
||||
<template #status="{ row }">{{ pickupStatusLabels[row.status] || row.status }}</template><template #actual="{ row }">{{ row.actualPackages ?? '待取货' }}</template>
|
||||
<template #evidence="{ row }"><div v-for="file in row.evidence" :key="file.fileId"><el-button link @click="download(row as PickupTask, file)">{{ file.fileName }}</el-button></div></template>
|
||||
</DataTable></div>
|
||||
<AppDialog v-model="open" class="admin-density-dialog admin-workbench is-dialog" :before-close="close" title="确认本单取货" width="680px" :confirm-loading="busy" :confirm-text="pending ? '重试原取货确认' : '确认已取货'" @confirm="confirmPickup">
|
||||
<AppDialog v-model="open" class="admin-density-dialog admin-workbench is-dialog" :before-close="close" title="确认本单取货" width="680px" :confirm-loading="busy" :confirm-text="'确认已取货'" @confirm="confirmPickup">
|
||||
<p>{{ selected?.transferNo }} · {{ selected?.pickupAddress }}</p>
|
||||
<el-form label-position="top" :disabled="busy || !!pending">
|
||||
<el-form-item label="实提箱数" required><el-input-number v-model="actual" :min="1" :max="5000" :precision="0" placeholder="请输入实提箱数" /></el-form-item>
|
||||
|
||||
Reference in New Issue
Block a user