diff --git a/src/modules/port-schedules/components/CarrierScheduleQueryPanel.behavior.test.ts b/src/modules/port-schedules/components/CarrierScheduleQueryPanel.behavior.test.ts index 14fe2989..a34f5e6a 100644 --- a/src/modules/port-schedules/components/CarrierScheduleQueryPanel.behavior.test.ts +++ b/src/modules/port-schedules/components/CarrierScheduleQueryPanel.behavior.test.ts @@ -108,6 +108,17 @@ describe('CarrierScheduleQueryPanel loading states', () => { expect(getPortSchedule).not.toHaveBeenCalled() }) + it('does not expose a circuit-breaker exception name stored by an earlier scheduler attempt', async () => { + const failed = { ...queryView.query, lastErrorCode: 'PROVIDER_CALL_FAILED', lastErrorMessage: 'NoFallbackAvailableException', resultPortScheduleId: null } + vi.mocked(listCarrierScheduleQueries).mockResolvedValue({ records: [{ ...queryView, query: failed }], page: 1, size: 200, total: 1, pages: 1 }) + vi.mocked(getCarrierScheduleQuery).mockResolvedValue({ query: { ...queryView, query: failed }, nodes: [], exceptions: [], histories: [] }) + const wrapper = mount(CarrierScheduleQueryPanel, { props: { containerBookingId: '3' }, global: { plugins: [ElementPlus, pinia] } }) + await flushPromises() + + expect(wrapper.text()).toContain('轨迹供应商暂时不可用,请稍后重试或人工补录节点。') + expect(wrapper.text()).not.toContain('NoFallbackAvailableException') + }) + it('renders canonical node location, actual time, source and empty-return stop facts from detail DTO', async () => { vi.mocked(listCarrierScheduleQueries).mockResolvedValue({ records: [{ ...queryView, query: { ...queryView.query, status: 'STOPPED', latestActualNode: 'EMPTY_RETURNED', latestActualAt: '2026-08-20T17:45:00', stoppedAt: '2026-08-20T18:00:00', stopReason: '已还柜', resultPortScheduleId: null }, expired: false }], page: 1, size: 200, total: 1, pages: 1 }) vi.mocked(getCarrierScheduleQuery).mockResolvedValue({ diff --git a/src/modules/port-schedules/components/CarrierScheduleQueryPanel.vue b/src/modules/port-schedules/components/CarrierScheduleQueryPanel.vue index 17f1dc10..d91c7598 100644 --- a/src/modules/port-schedules/components/CarrierScheduleQueryPanel.vue +++ b/src/modules/port-schedules/components/CarrierScheduleQueryPanel.vue @@ -91,6 +91,14 @@ function nodeLocation(item?: CarrierScheduleNodeFact | null) { return item ? [it function exceptionLocation(item: CarrierScheduleExceptionFact) { return [item.locationCode, item.locationName].filter(Boolean).join(' · ') || '—' } function nodeTimeText(item: CarrierScheduleNodeFact) { return item.actualAt ? `实际 ${formatTime(item.actualAt)}` : item.estimatedAt ? `预计 ${formatTime(item.estimatedAt)}` : `观测 ${formatTime(item.observedAt)}` } function exceptionText(value: CarrierScheduleExceptionFact['exceptionCode']) { return ({ CUSTOMS_HOLD: '海关扣留', TERMINAL_HOLD: '码头扣留', DOCUMENT_HOLD: '单证扣留', UNKNOWN_HOLD: '其他扣留' } as Record)[value] || value } +function providerErrorText(value?: string | null) { + const message = nonBlank(value) + if (!message) return '自动查询暂不可用,需要人工补充可靠结果。' + if (/(?:NoFallbackAvailableException|FeignException|ConnectException|SocketTimeoutException)/i.test(message)) { + return '轨迹供应商暂时不可用,请稍后重试或人工补录节点。' + } + return message +} function historyText(action: string) { return ({ AUTO_CREATE: '国内出仓自动建任务', CREATE: '人工建立查询任务', NODE_OBSERVED: '记录标准节点', PROVIDER_QUERY: '船司查询', MANUAL_NODE: '人工补录标准节点', STOP: '停止查询' } as Record)[action] || action } function warehouseEventText(value: string) { return ({ STATUS_UPDATE: '状态更新', PUSH_PRELOAD: '推送预装清单', CONFIRM_PRELOAD: '确认预装清单', TRANSPORT_UPDATE: '更新运输信息', PDA_CLAIM: '领取 PDA 任务', PDA_BATCH: '提交拣货结果', PDA_COMPLETE_LOADING: '完成装柜' } as Record)[value] || value || '状态更新' } function nonBlank(value: unknown) { return typeof value === 'string' ? value.trim() : '' } @@ -250,7 +258,7 @@ watch(() => manualForm.actualAt, (actualAt) => {