fix: complete October 22 workflow feedback and request failure recovery

This commit is contained in:
2026-10-22 23:06:02 +08:00
parent 22baa5b5c0
commit 3ffa706b89
7 changed files with 123 additions and 381 deletions
@@ -85,7 +85,6 @@ const canCreateOrder = computed(() => auth.hasPermission('order:first-leg:create
const canEditOrder = computed(() => auth.hasPermission('order:first-leg:edit'))
const canCancelOrder = computed(() => auth.hasPermission('order:first-leg:cancel'))
const cancellingOrderId = ref('')
const pendingCancellation = shallowRef<{ id: string; expectedVersion: number; reason: string; businessNo: string; statusLabel: string }>()
function canOfferCancellation(order: FirstLegOrder) {
return canCancelOrder.value && ['DRAFT', 'PENDING_REVIEW', 'WAIT_MEASURE'].includes(order.status)
@@ -95,40 +94,20 @@ async function cancelOrder(order: FirstLegOrder) {
if (!canOfferCancellation(order) || cancellingOrderId.value) return
cancellingOrderId.value = order.id
try {
let command = pendingCancellation.value
if (command && command.id !== order.id) {
ElMessage.warning(`请先处理订单 ${command.businessNo} 未确认的取消结果`)
return
}
if (!command) {
const target = await store.prepareCancellation(order.id)
const { value } = await ElMessageBox.prompt(`订单:${target.businessNo};当前状态:${target.statusLabel}。请填写取消原因。`, '取消订单', {
const target = await store.prepareCancellation(order.id)
const { value } = await ElMessageBox.prompt(
`订单:${target.businessNo};当前状态:${target.statusLabel}。请填写取消原因。`, '取消订单', {
type: 'warning', inputType: 'textarea', confirmButtonText: '确认取消订单', cancelButtonText: '返回',
inputValidator: value => Boolean(value?.trim()) && value.trim().length <= 500 || '请填写 1–500 字取消原因',
})
command = { ...target, reason: value.trim() }
pendingCancellation.value = command
} else {
await ElMessageBox.confirm(`订单:${command.businessNo};原状态:${command.statusLabel};原因:${command.reason}。将查询当前状态并重试原取消请求。`, '重试取消订单', {
type: 'warning', confirmButtonText: '重试原请求', cancelButtonText: '返回',
})
}
await store.cancelOrder(command.id, command.expectedVersion, command.reason)
pendingCancellation.value = undefined
ElMessage.success('订单已取消')
await loadOrderPage()
await store.cancelOrder(target.id, target.expectedVersion, value.trim())
ElMessage.success('订单已取消'); await loadOrderPage()
if (isDetailPage.value) await router.push({ name: 'first-leg-orders' })
} catch (error) {
if (error === 'cancel' || error === 'close') return
if (error instanceof ApiError && (error.code === 'A0409' || error.status === 409)) {
pendingCancellation.value = undefined
ElMessage.error(`${error.message}。请刷新订单后重新确认取消。`)
} else {
ElMessage.error(error instanceof Error ? error.message : '取消失败,请重试原请求')
}
} finally {
cancellingOrderId.value = ''
}
ElMessage.error(error instanceof Error ? error.message : '取消失败,请重新操作')
void loadOrderPage()
} finally { cancellingOrderId.value = '' }
}
const canSubmitOrder = computed(() => auth.hasPermission('order:first-leg:submit'))
const canConfirmInboundFee = computed(() => auth.hasPermission('order:first-leg:edit'))
@@ -242,7 +221,6 @@ const tablePageSize = ref(10)
const downloadingMarks = ref(false)
const exportingOrders = ref(false)
const batchSubmitting = ref(false)
const pendingBatchSubmissionIds = ref<string[]>([])
const selectedOrders = ref<FirstLegOrder[]>([])
const selectedOrderCount = computed(() => selectedOrders.value.length)
const selectedDraftOrders = computed(() => selectedOrders.value.filter((order) => order.status === 'DRAFT'))
@@ -1368,7 +1346,7 @@ function getOrderRowActions(row: object): TableAction[] {
actions.push({ key: 'edit', label: '编辑', loading: openingEditor.value, run: () => openEdit(order) })
}
if (canOfferCancellation(order)) {
actions.push({ key: 'cancel', label: pendingCancellation.value?.id === order.id ? '重试取消' : '取消订单', loading: cancellingOrderId.value === order.id, disabled: Boolean(cancellingOrderId.value), run: () => cancelOrder(order) })
actions.push({ key: 'cancel', label: '取消订单', loading: cancellingOrderId.value === order.id, disabled: Boolean(cancellingOrderId.value), run: () => cancelOrder(order) })
}
if (eligibleCustomsDetail(order)) {
actions.push({ key: 'customs', label: '报关资料', run: () => openCustomsDocuments(order) })
@@ -1751,14 +1729,14 @@ function handleOrderSelectionChange(rows: FirstLegOrder[]) {
}
async function submitSelectedDraftOrders() {
const ids = [...new Set([...pendingBatchSubmissionIds.value, ...selectedDraftOrders.value.map(order => order.id)])]
const ids = [...new Set(selectedDraftOrders.value.map(order => order.id))]
if (batchSubmitting.value || !ids.length) return
if (companySubmissionBlocked.value) {
ElMessage.warning('企业资料审核通过后才能提交订单')
return
}
try {
await ElMessageBox.confirm(`确认提交 ${ids.length} 条草稿订单吗?${pendingBatchSubmissionIds.value.length ? '上次结果未确认的订单将使用原提交命令重试。' : ''}`, '批量提交订单', {
await ElMessageBox.confirm(`确认提交 ${ids.length} 条草稿订单吗?`, '批量提交订单', {
type: 'warning',
confirmButtonText: '确认提交',
cancelButtonText: '取消',
@@ -1771,7 +1749,6 @@ async function submitSelectedDraftOrders() {
batchSubmitting.value = true
try {
const results = await Promise.allSettled(ids.map(id => store.submitSaved(id, companyAuditLoaded.value ? tenantStore.company.auditStatus : undefined)))
pendingBatchSubmissionIds.value = ids.filter((id, index) => results[index]?.status === 'rejected' && store.hasPendingSubmission(id))
const successCount = results.filter((result) => result.status === 'fulfilled').length
const failures = results.filter((result): result is PromiseRejectedResult => result.status === 'rejected')
await searchOrders(appliedFilters.value)
@@ -2024,7 +2001,6 @@ async function saveDraft() {
if (!await validateEditorForm()) return
saving.value = true
try {
if (!await confirmSubmissionEdit()) return
const order = await store.saveDraft(cloneDraft(form), tenantStore.company.companyName, editingId.value, editingSession.value)
editingId.value = order.id
editingSession.value = store.beginDraftEdit(order)
@@ -2039,20 +2015,6 @@ async function saveDraft() {
}
}
async function confirmSubmissionEdit() {
if (!editingId.value || !store.hasPendingSubmission(editingId.value)) return true
try {
await ElMessageBox.confirm('上次提交结果尚未确认。确认重新读取订单并按当前表单重新编辑吗?只有服务端仍为草稿时才能开始新的提交决定。', '重新编辑订单', {
type: 'warning', center: true, confirmButtonText: '确认重新编辑', cancelButtonText: '返回',
})
} catch (error) {
if (error === 'cancel' || error === 'close') return false
throw error
}
editingSession.value = await store.beginNewSubmissionEdit(editingId.value)
return true
}
async function submitOrder() {
if (saving.value) return
if (companySubmissionBlocked.value) {
@@ -2071,14 +2033,12 @@ async function submitOrder() {
}
return
}
const retry = store.isSubmissionRetry(editingId.value, cloneDraft(form))
if (!retry && !await validateEditorForm()) return
if (!await validateEditorForm()) return
try {
saving.value = true
if (!retry && !await confirmSubmissionEdit()) return
const knownCompanyAuditStatus = companyAuditLoaded.value ? tenantStore.company.auditStatus : undefined
const savedOrder = await store.saveDraftForSubmission(cloneDraft(form), tenantStore.company.companyName, editingId.value, editingSession.value)
const savedOrder = await store.saveDraft(cloneDraft(form), tenantStore.company.companyName, editingId.value, editingSession.value)
editingId.value = savedOrder.id
editingSession.value = store.beginDraftEdit(savedOrder)
await store.submitSaved(savedOrder.id, knownCompanyAuditStatus)
@@ -2156,7 +2116,7 @@ async function searchOrders(model: QueryModel) {
</el-popover>
<el-button :loading="downloadingMarks" :disabled="selectedOrderCount === 0" @click="downloadShippingMarks"><Download :size="16" />下载唛头</el-button>
<el-button :loading="exportingOrders" :disabled="selectedOrderCount === 0" @click="exportOrderInformation"><Download :size="16" />导出订单信息</el-button>
<el-button v-if="canSubmitOrder" :loading="batchSubmitting" :disabled="(selectedDraftOrders.length === 0 && pendingBatchSubmissionIds.length === 0) || companySubmissionBlocked" @click="submitSelectedDraftOrders"><Send :size="17" />{{ pendingBatchSubmissionIds.length ? '重试批量提交' : '批量提交草稿' }}</el-button>
<el-button v-if="canSubmitOrder" :loading="batchSubmitting" :disabled="selectedDraftOrders.length === 0 || companySubmissionBlocked" @click="submitSelectedDraftOrders"><Send :size="17" />批量提交草稿</el-button>
<el-button v-if="auth.hasPermission('order:first-leg:create')" @click="openBatchImport"><Upload :size="17" />批量导入订单</el-button>
<el-button v-if="props.showCreateAction && auth.hasPermission('order:first-leg:create')" type="primary" :loading="pageDataLoading" :disabled="pageDataLoading || availableWarehouses.length === 0" @click="openCreate"><FilePlus2 :size="17" />创建订单</el-button>
@@ -2423,7 +2383,7 @@ async function searchOrders(model: QueryModel) {
<el-button link type="primary" class="detail-back-action" @click="router.push({ name: 'first-leg-orders' })">← 返回列表</el-button>
<div><h2>{{ detailOrder.businessSummary.inboundCode || (detailOrder.status === 'CANCELLED' ? (detailOrder.orderNo || '—') : detailOrder.status === 'DRAFT' ? '入仓码提交后生成' : '入仓码待生成') }}</h2><p v-if="detailOrder.status === 'CANCELLED'">订单已取消。</p><p v-else>{{ getFirstLegTransportModeLabel(detailOrder.transportMode) }} · {{ detailOrder.origin || '起运地待更新' }} → {{ detailOrder.destination || '目的地待更新' }}</p></div>
</div>
<div class="detail-heading-actions"><el-tag :type="getFirstLegStatusTone(detailOrder.status)" effect="light">{{ getFirstLegStatusLabel(detailOrder.status) }}</el-tag><el-button v-if="canEditOrder && canClientEditFirstLegOrder(detailOrder.status)" type="primary" plain :loading="openingEditor" @click="openEdit(detailOrder)"><Pencil :size="16" />编辑草稿</el-button><el-button v-if="canOfferCancellation(detailOrder)" type="danger" plain :loading="cancellingOrderId === detailOrder.id" :disabled="!!cancellingOrderId" @click="cancelOrder(detailOrder)">{{ pendingCancellation?.id === detailOrder.id ? '重试取消' : '取消订单' }}</el-button></div>
<div class="detail-heading-actions"><el-tag :type="getFirstLegStatusTone(detailOrder.status)" effect="light">{{ getFirstLegStatusLabel(detailOrder.status) }}</el-tag><el-button v-if="canEditOrder && canClientEditFirstLegOrder(detailOrder.status)" type="primary" plain :loading="openingEditor" @click="openEdit(detailOrder)"><Pencil :size="16" />编辑草稿</el-button><el-button v-if="canOfferCancellation(detailOrder)" type="danger" plain :loading="cancellingOrderId === detailOrder.id" :disabled="!!cancellingOrderId" @click="cancelOrder(detailOrder)">取消订单</el-button></div>
</div>
<section class="detail-section">
<h3>订单与运输</h3>