From cec1e2d77c895e61d2308492185f51565dc150e1 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Fri, 9 Oct 2026 11:48:53 +0800 Subject: [PATCH 01/22] fix(fleet): validate pallet destination identity before capacity confirmation --- .../components/TripLoadingPanel.vue | 29 ++++++++++++++----- .../pages/TripsBehavior.test.ts | 24 +++++++++++++++ 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/modules/overseas-master-data/components/TripLoadingPanel.vue b/src/modules/overseas-master-data/components/TripLoadingPanel.vue index 89cf1d2d..d1fc2a1f 100644 --- a/src/modules/overseas-master-data/components/TripLoadingPanel.vue +++ b/src/modules/overseas-master-data/components/TripLoadingPanel.vue @@ -10,7 +10,7 @@ import type { FleetTrip, TripLoading, TripCapacityPlanning, TripMeasurement } fr const props = defineProps<{ trip: FleetTrip; editable: boolean; planning?: boolean }>() const emit = defineEmits<{ saved: [loading: TripLoading]; planned: [plan: TripCapacityPlanning] }>() -type PalletRow = { warehousePalletId: string; palletNo: string; destinations: string; palletSpaces: number; weightKg: number | null; volumeCbm: number | null; deliveryIndex: number | null } +type PalletRow = { warehousePalletId: string; palletNo: string; destinations: string; destinationKeys: string[]; palletSpaces: number; weightKg: number | null; volumeCbm: number | null; deliveryIndex: number | null } const info = ref(null), rows = ref([]), busy = ref(false), loading = ref(false), error = ref(null) let sequence = 0 const columns = computed(() => [{ key: 'palletNo', prop: 'palletNo', label: '实物托盘', minWidth: 150 }, @@ -20,17 +20,29 @@ const columns = computed(() => [{ key: 'palletNo', prop: 'pal const savedLoad = computed(() => info.value && ('capacityPlan' in info.value ? info.value.capacityPlan : info.value.confirmedLoad)) const segments = computed(() => info.value && 'availableSegments' in info.value ? info.value.availableSegments : savedLoad.value?.segments || []) const stalePlan = computed(() => info.value && 'planValid' in info.value && !!info.value.capacityPlan && !info.value.planValid) +function matchingStops(row: PalletRow) { + if (row.destinationKeys.length !== 1 || !row.destinationKeys[0]) return [] + return props.trip.stops.map((stop, index) => ({ ...stop, index })) + .filter(stop => stop.index > 0 && stop.destinationKey === row.destinationKeys[0]) +} +const destinationIssue = computed(() => { + for (const row of rows.value) { + if (row.destinationKeys.length > 1) return `托盘 ${row.palletNo} 包含不同目的地,请先拆托。` + if (!matchingStops(row).length) return `托盘 ${row.palletNo} 的目的地 ${row.destinations} 尚未匹配车次卸货站;站点同名不代表已匹配。请到“分配与货物”点击“按货物匹配卸货站”,再重新核对容量。` + } + return '' +}) function apply(value: TripLoading | TripCapacityPlanning) { info.value = value const pallets = new Map() for (const cargo of value.cargo) { if (!cargo.warehousePalletId) continue const existing = pallets.get(cargo.warehousePalletId) - if (existing) { if (cargo.destinationLabel && !existing.destinations.split(' / ').includes(cargo.destinationLabel)) existing.destinations += ` / ${cargo.destinationLabel}`; continue } + if (existing) { if (!existing.destinationKeys.includes(cargo.destinationKey || '')) existing.destinationKeys.push(cargo.destinationKey || ''); if (cargo.destinationLabel && !existing.destinations.split(' / ').includes(cargo.destinationLabel)) existing.destinations += ` / ${cargo.destinationLabel}`; continue } const saved = ('capacityPlan' in value ? value.capacityPlan : value.confirmedLoad)?.pallets.find(row => row.warehousePalletId === cargo.warehousePalletId) const index = props.trip.stops.findIndex(stop => !!cargo.destinationKey && stop.destinationKey === cargo.destinationKey) - pallets.set(cargo.warehousePalletId, { warehousePalletId: cargo.warehousePalletId, palletNo: cargo.palletNo || '—', destinations: cargo.destinationLabel || '—', - palletSpaces: saved?.palletSpaces ?? 1, weightKg: saved?.weightKg ?? null, volumeCbm: saved?.volumeCbm ?? null, deliveryIndex: saved?.deliveryIndex ?? (index > 0 ? index : null) }) + pallets.set(cargo.warehousePalletId, { warehousePalletId: cargo.warehousePalletId, palletNo: cargo.palletNo || '—', destinations: cargo.destinationLabel || '—', destinationKeys: [cargo.destinationKey || ''], + palletSpaces: saved?.palletSpaces ?? 1, weightKg: saved?.weightKg ?? null, volumeCbm: saved?.volumeCbm ?? null, deliveryIndex: saved && saved.deliveryIndex > 0 && props.trip.stops[saved.deliveryIndex]?.destinationKey === cargo.destinationKey ? saved.deliveryIndex : (index > 0 ? index : null) }) } rows.value = [...pallets.values()] } @@ -41,7 +53,9 @@ async function load() { finally { if (current === sequence) loading.value = false } } async function save() { - if (busy.value || !info.value) return + if (busy.value || loading.value || !props.editable || !info.value) return + if (destinationIssue.value) { ElMessage.warning(destinationIssue.value); return } + if (rows.value.some(row => !matchingStops(row).some(stop => stop.index === row.deliveryIndex))) { ElMessage.warning('请选择与货物目的地匹配的卸货站'); return } if (!rows.value.length || rows.value.some(row => row.weightKg == null || row.volumeCbm == null || row.deliveryIndex == null)) { ElMessage.warning(props.planning ? '请填写每托预计重量、体积和卸货站' : '请填写每托实测重量、体积和卸货站'); return } const current = sequence, snapshot = info.value const pallets: TripMeasurement[] = rows.value.map(row => ({ warehousePalletId: row.warehousePalletId, palletSpaces: row.palletSpaces, @@ -66,6 +80,7 @@ onBeforeUnmount(() => { sequence++ })

先核对本车自有货的预计载荷,查看每段剩余托位、载重和容积。实际装载完成后仍需按实测值确认。

+

{{ destinationIssue }}

货物明细已变化,请重新核对计划容量。

{{ info.blockReason }}

已确认实际装载;车辆、司机或计划调整后需要重新确认。

@@ -74,8 +89,8 @@ onBeforeUnmount(() => { sequence++ }) - - + +
行驶区间占用托位重量(kg)体积(m³)剩余托位 / kg / m³
{{ trip.stops[segment.fromIndex]?.name }} → {{ trip.stops[segment.toIndex]?.name }}{{ segment.occupied.palletSpaces }} / {{ trip.maxPalletSpaces }}{{ segment.occupied.weightKg }} / {{ trip.maxLoadKg }}{{ segment.occupied.volumeCbm }} / {{ trip.maxVolumeCbm }}{{ segment.remaining.palletSpaces }} / {{ segment.remaining.weightKg }} / {{ segment.remaining.volumeCbm }}
diff --git a/src/modules/overseas-master-data/pages/TripsBehavior.test.ts b/src/modules/overseas-master-data/pages/TripsBehavior.test.ts index d74ded12..1949376e 100644 --- a/src/modules/overseas-master-data/pages/TripsBehavior.test.ts +++ b/src/modules/overseas-master-data/pages/TripsBehavior.test.ts @@ -176,3 +176,27 @@ it('opens the loading review section from the dispatch list deep link', async () expect(wrapper.findComponent({ name: 'TripLoadingPanel' }).exists()).toBe(true) expect(wrapper.text()).toContain('理货审核') }) + +it.each([null, 'FBA:OTHER'])('blocks a same-name ACY2 stop with an unmatched identity %s before submitting', async (destinationKey) => { + const cargo = { dispatchNo: 'DP-A', customerId: 'A', dispatchItemId: '100', warehousePalletId: '901', palletNo: 'D0009-101-001', packages: 10, destinationKey: 'FBA:ACY2', destinationLabel: 'ACY2' } + const basis = { tripNo: trip.tripNo, recordVersion: 3, cargo: [cargo], manifestHash: 'hash', capacityPlan: null, planValid: false, blockReason: null, availableSegments: [] } + vi.mocked(api.getTripCapacityPlan).mockResolvedValue(basis) + const currentTrip = { ...trip, stops: [{ name: '起运仓' }, { name: 'ACY2', destinationKey }] } + const wrapper = mount(TripLoadingPanel, { trip: currentTrip, editable: true, planning: true }); await flushPromises() + expect(wrapper.text()).toContain('站点同名不代表已匹配') + expect(wrapper.text()).toContain('按货物匹配卸货站') + expect(button(wrapper, '确认计划容量').attributes('disabled')).toBeDefined() + expect(wrapper.findComponent({ name: 'DataTable' }).props('data')[0].deliveryIndex).toBeNull() + await button(wrapper, '确认计划容量').trigger('click') + expect(api.confirmTripCapacityPlan).not.toHaveBeenCalled() + await wrapper.setProps({ trip: { ...currentTrip, recordVersion: 4, stops: [{ name: '起运仓' }, { name: 'ACY2', destinationKey: 'FBA:ACY2' }] } }); await flushPromises() + expect(wrapper.text()).not.toContain('站点同名不代表已匹配') + expect(wrapper.findComponent({ name: 'DataTable' }).props('data')[0].deliveryIndex).toBe(1) +}) +it('blocks a shared pallet with different identities even when both labels are ACY2', async () => { + const cargo = { dispatchNo: 'DP-A', customerId: 'A', dispatchItemId: '100', warehousePalletId: '901', palletNo: 'PLT-1', packages: 10, destinationKey: 'FBA:ACY2', destinationLabel: 'ACY2' } + vi.mocked(api.getTripCapacityPlan).mockResolvedValue({ tripNo: trip.tripNo, recordVersion: 3, cargo: [cargo, { ...cargo, destinationKey: 'WAREHOUSE:ACY2' }], manifestHash: 'hash', capacityPlan: null, planValid: false, blockReason: null, availableSegments: [] }) + const wrapper = mount(TripLoadingPanel, { trip: { ...trip, stops: [{ name: '起运仓' }, { name: 'ACY2', destinationKey: 'FBA:ACY2' }] }, editable: true, planning: true }); await flushPromises() + expect(wrapper.text()).toContain('包含不同目的地,请先拆托') + expect(button(wrapper, '确认计划容量').attributes('disabled')).toBeDefined() +}) From 3e77c3976e6de9d0d703aea7dc1ee196fe94f30d Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 22 Oct 2026 11:56:54 +0800 Subject: [PATCH 02/22] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/business-index.md | 2 +- .../components/ContainerCustomsPanel.test.ts | 4 +- .../components/ContainerCustomsPanel.vue | 2 +- src/modules/customers/customerDensity.css | 2 + .../customers/pages/CustomersView.test.ts | 6 +- src/modules/customers/pages/CustomersView.vue | 19 +- src/modules/ocean-bookings/AGENTS.md | 6 +- .../ocean-bookings/PROTOTYPE_MATRIX.md | 13 + .../api/platformOceanBookings.test.ts | 13 +- .../api/platformOceanBookings.ts | 12 +- .../OceanBookingPrintDialog.test.ts | 21 + .../components/OceanBookingPrintDialog.vue | 16 +- .../OceanBookingDetailView.release.test.ts | 108 ++++- .../pages/OceanBookingDetailView.test.ts | 17 +- .../pages/OceanBookingDetailView.vue | 439 ++++++++++++++++-- src/modules/ocean-bookings/types.ts | 26 +- src/modules/ocean-bookings/workflow.test.ts | 77 +++ src/modules/ocean-bookings/workflow.ts | 50 ++ .../components/DriverEditorDialog.vue | 8 +- .../overseas-warehouse/PROTOTYPE_MATRIX.md | 10 +- .../components/DeliveryDocumentsDialog.vue | 2 +- .../DispatchDocumentsDialog.test.ts | 30 +- .../components/DispatchDocumentsDialog.vue | 11 +- .../DispatchLoadingDocuments.test.ts | 4 + .../components/DispatchLoadingDocuments.vue | 35 +- .../DispatchVehicleDocuments.test.ts | 4 + .../components/DispatchVehicleDocuments.vue | 27 +- .../SmartDispatchDialog.layout.test.ts | 29 +- .../SmartDispatchDialog.planning.test.ts | 11 +- .../components/SmartDispatchDialog.test.ts | 17 +- .../components/SmartDispatchDialog.vue | 31 +- .../pages/InboundOrdersView.vue | 2 +- .../pages/LastMileOrdersView.vue | 2 +- .../pages/PalletsView.test.ts | 10 +- .../overseas-warehouse/pages/PalletsView.vue | 27 +- .../pages/UnifiedOrdersView.requests.test.ts | 22 + .../pages/UnifiedOrdersView.test.ts | 2 +- .../pages/UnifiedOrdersView.vue | 74 ++- src/styles/adminDensity.css | 5 + src/styles/adminDensity.test.ts | 21 + 40 files changed, 976 insertions(+), 241 deletions(-) create mode 100644 src/modules/ocean-bookings/PROTOTYPE_MATRIX.md create mode 100644 src/modules/ocean-bookings/workflow.test.ts create mode 100644 src/modules/ocean-bookings/workflow.ts diff --git a/docs/business-index.md b/docs/business-index.md index dd0ebd80..0aa1c069 100644 --- a/docs/business-index.md +++ b/docs/business-index.md @@ -37,7 +37,7 @@ | 货柜双工作台、排柜、预装/装柜清单、真实柜号后补与整柜报关 | 国内 `modules/container-loading/pages/DomesticContainersView.vue`、海外 `OverseasContainersView.vue`,共享详情实现 `ContainerDetailView.vue`;预装详情 `modules/loading-lists/pages/PreloadPlanDetailView.vue`,资料/PDA 证据 `components/ContainerOverviewPanel.vue`,报关 `components/ContainerCustomsPanel.vue` | 规则见下文「货柜工作台规则」 | | 合柜报关 / 同柜不可拆组 | `modules/unified-orders/api/coLoadGroups.ts`、`pages/UnifiedOrdersView.vue`;装柜消费 `modules/container-loading/pages/SmartLoadingWorkbenchView.vue` | 前端只提交订单 ID;Backend `firstleg/loading/coload/` 展开并冻结每票全部可用入仓片段。成员数是入仓片段数,不等于订单票数;只要求同国内仓、同渠道,允许跨客户及不同目的仓港;分配、移除、取消均整组处理 | | 全局智能装柜工作台、入仓码候选与箱数分配 | `modules/container-loading/components/SmartLoadingDialog.vue` 内嵌 `pages/GlobalSmartLoadingView.vue`,柜次页 `SmartLoadingWorkbenchView.vue`,以及 `smartLoadingChannelGroups.ts`、`smartLoadingItems.ts`、`api/platformContainerLoading.ts`;全局页当前不注册独立路由 | Backend `firstleg/` 的 `/inbound-candidates`、`/containers`、`/inbound-assignments`、`/plans/{planId}/pending-review-assignments`、`/group-assignments`、`/unpicked-items`。`orderStatuses` 最多四项且未传表示全部未完成状态;候选状态多选覆盖待审核、待测量、待分柜、装柜中,状态只用于筛选展示,按钮资格以服务端 `canAssign` 为准;待审核/待测量可提前占位但不得提前确认预装或启动 PDA。计划箱数编辑上限、已用和未用展示只消费服务端 `totalPackageCount/remainingPackageCount`,不得从候选页或本地规格重新拼算。人工加入允许跨渠道,智能推荐仍按柜次渠道。柜次容量和限重取服务端字段;同柜组作为单卡整组分页、分配和移除,任一成员已拣时整组保留 | -| 国内货柜履约订舱资料、打印、关联候选与出仓前船期 | `modules/ocean-bookings/` 的 `components/OceanBookingDocumentsPanel.vue`、`OceanBookingPrintDialog.vue`,以及 `modules/container-loading/`、`modules/port-schedules/` | 订舱草稿按当前版本维护资料,放舱后资料与表单只读;打印对草稿与已放舱订舱均开放,按当前已保存资料生成,未填写字段显示“—”。`platformOceanBookings.ts` 的 `/link-candidates` 只消费服务端分页,`containerCount` 是柜型需求 `quantity` 总和;Backend `firstleg/oceanbooking/`、`file/`、`print/`、`firstleg/portschedule/` | +| 国内货柜履约订舱资料、打印、关联候选与出仓前船期 | `modules/ocean-bookings/` 的 `components/OceanBookingDocumentsPanel.vue`、`OceanBookingPrintDialog.vue`,以及 `modules/container-loading/`、`modules/port-schedules/` | 订舱草稿按当前版本维护资料,放舱后运输/预报/关系方/柜型/附件只读;客服实货量经 `/actual-cargo` 按编辑权限、版本、幂等键和原因补录;打印对草稿与已放舱订舱均开放,按当前已保存资料生成,托运单取委值,提单确认件取手工实值,未填显示“—”且不以委值回退。`platformOceanBookings.ts` 的 `/link-candidates` 只消费服务端分页,`containerCount` 是柜型需求 `quantity` 总和;Backend `firstleg/oceanbooking/`、`file/`、`print/`、`firstleg/portschedule/` | | 订舱级拖车预约 | `modules/trucking/api/platformTrucking.ts` 的 `/candidates`、`components/TruckAppointmentForm.vue`、`pages/TruckAppointmentCreateView.vue`、`TruckAppointmentsView.vue` | 表单只选择服务端返回的 `RELEASED` 订舱与 `TRUCKING + truckingScope=DOMESTIC` 国内拖车供应商,不选择或提交客户、柜次、装柜计划或真实集装箱号;清关提柜固定选择 `OVERSEAS` 海外车队,类型在供应商主档单选维护;提单号和仓库由订舱冻结事实带入。一个订舱最多一张非终态拖车单,重复创建转入现有单继续编辑;PDA 完成不自动推进拖车。附件为平台作用域且不重复提单文件;Backend `TruckAppointmentService`、`truck-appointments-v1.yaml` | | 目的港码头拖车与还空预警 | 海外清关提柜工作台/详情 `modules/container-loading/pages/OverseasContainersView.vue`、`OverseasContainerDetailView.vue`,API `modules/container-loading/api/terminalDrayages.ts`、`containerArrivalTimes.ts` | 与订舱级拖车预约是不同生命周期;按 `containerBookingId` 读取服务端状态、`allowedActions` 和历史,不能复用订舱级拖车表单或自行推进状态。列表“预计到仓”列直接提供人工日期时间选择器,保存时通过 Lastleg 柜级版本和幂等键原子更新该柜全部尾程订单,已有入库计划后禁止修改;“实际到仓”只读 `ll_receiving_container_session.receiving_at`,即海外 PDA 首次开始收货时间。还空截止、超时天/小时只展示服务端基于权威卸柜完成时间加 72 小时计算的结果;Backend `firstleg/terminaldrayage/`、`lastleg-service/`,邮件记录通过 `terminalDrayages.ts` 查询与重试。 | | 历史报关协同与国内出仓 | `modules/customs/`、`modules/outbound-dispatch/` | `modules/customs/` 是历史报关案件/清单/代理提交与回执入口;装柜完成后的整柜退税/买单资料从货柜详情进入,不在旧模块复制;Backend `firstleg/outbound/dispatch/`、`firstleg/customs/`、`file/` | diff --git a/src/modules/container-loading/components/ContainerCustomsPanel.test.ts b/src/modules/container-loading/components/ContainerCustomsPanel.test.ts index ce80b16a..dd31c346 100644 --- a/src/modules/container-loading/components/ContainerCustomsPanel.test.ts +++ b/src/modules/container-loading/components/ContainerCustomsPanel.test.ts @@ -114,12 +114,12 @@ describe('ContainerCustomsPanel', () => { wrapper.unmount() }) - it('开始报关确认显示柜次号,取消不会冻结资料或调用开始接口', async () => { + it('开始报关确认使用简短文案,取消不会冻结资料或调用开始接口', async () => { const confirm = vi.spyOn(ElMessageBox, 'confirm').mockRejectedValueOnce('cancel') const wrapper = await render('READY_TO_START') await wrapper.findAll('button').find(button => button.text() === '开始报关')!.trigger('click') await flushPromises() - expect(confirm).toHaveBeenCalledWith(expect.any(String), '开始整柜报关 · MCR08290001', expect.objectContaining({ customClass: 'admin-density-confirm' })) + expect(confirm).toHaveBeenCalledWith('确认开始整柜报送?', '整柜报关', expect.objectContaining({ customClass: 'admin-density-confirm', confirmButtonText: '确认' })) expect(api.start).not.toHaveBeenCalled() confirm.mockRestore() wrapper.unmount() diff --git a/src/modules/container-loading/components/ContainerCustomsPanel.vue b/src/modules/container-loading/components/ContainerCustomsPanel.vue index 46549a02..64df6f81 100644 --- a/src/modules/container-loading/components/ContainerCustomsPanel.vue +++ b/src/modules/container-loading/components/ContainerCustomsPanel.vue @@ -183,7 +183,7 @@ async function resolveFormalResult(batch: DeclarationBatch, result: DeclarationB async function start() { if (!customs.value || customs.value.status !== 'READY_TO_START') return try { - await ElMessageBox.confirm('开始后将冻结客户当前退税资料版本、关联入仓码覆盖范围及平台买单准备事实,确认开始整柜报关?', customsDialogTitle('开始整柜报关'), { customClass: 'admin-density-confirm', type: 'warning', confirmButtonText: '确认开始' }) + await ElMessageBox.confirm('确认开始整柜报送?', '整柜报关', { customClass: 'admin-density-confirm', type: 'warning', confirmButtonText: '确认' }) } catch { return } actionLoading.value = true; actionError.value = null try { customs.value = await startContainerCustoms(props.loadingPlanId, customs.value.version); ElMessage.success('整柜报关已开始') } diff --git a/src/modules/customers/customerDensity.css b/src/modules/customers/customerDensity.css index 69d905ac..dddb825e 100644 --- a/src/modules/customers/customerDensity.css +++ b/src/modules/customers/customerDensity.css @@ -59,6 +59,8 @@ /* Customer dialog business geometry; common appearance stays in adminDensity.css. */ +.customer-editor-dialog .density-form--outlined { --el-border-radius-base: var(--cc-radius-lg); } +.customer-editor-dialog .density-form--outlined .el-input-number .el-input__inner { text-align: left; } .customer-editor-dialog .customer-editor-table { table-layout: fixed; } .customer-editor-dialog .customer-editor-table td:not([colspan]) { width: calc(100% / 3); } .customer-review-table th { width: 120px; } diff --git a/src/modules/customers/pages/CustomersView.test.ts b/src/modules/customers/pages/CustomersView.test.ts index 6b9c03b0..bccea54e 100644 --- a/src/modules/customers/pages/CustomersView.test.ts +++ b/src/modules/customers/pages/CustomersView.test.ts @@ -182,7 +182,7 @@ describe('CustomersView', () => { it('searches the customer list by customer name only', () => { expect(customersSource).toContain("label: '客户名称', placeholder: '请输入客户名称'") expect(customersSource).toContain("label: '客户昵称'") - expect(customersSource).toContain('中文拼音大写首字母,如 SZWL') + expect(customersSource).toContain('placeholder="选填,默认自动生成"') expect(customersSource).not.toContain('公司名称、客户编号或联系人') expect(customersSource).not.toContain('row.tenantCode.toLowerCase().includes(keyword)') expect(customersSource).not.toContain('String(row.id).includes(keyword)') @@ -210,12 +210,16 @@ describe('CustomersView', () => { expect(document.querySelector('.customer-editor-dialog .qualification-credit-code .el-form-item__label')?.style.width).toBe('144px') const company = document.querySelector('.customer-editor-dialog input[placeholder="请输入工商登记公司名称"]')! expect(company.closest('.el-form-item')?.querySelector('.el-form-item__label')?.style.width).toBe('120px') + const legalName = document.querySelector('.customer-editor-dialog input[placeholder="请输入营业执照上的企业名称"]')! + expect(legalName.closest('.el-form-item')?.querySelector('.el-form-item__label')?.style.width).toBe('144px') + expect(company.closest('form')?.classList.contains('density-form--outlined')).toBe(true) company.value = '' company.dispatchEvent(new Event('input', { bubbles: true })) await flushPromises() company.dispatchEvent(new FocusEvent('blur')) await vi.waitFor(() => expect(company.closest('.el-form-item')?.querySelector('.el-form-item__error')?.textContent).toContain('请输入公司名称')) expect(document.querySelector('.customer-editor-dialog .el-dialog__footer')?.textContent).toContain('保存资料和资质') + expect(document.querySelector('.customer-editor-dialog .el-dialog__footer')?.textContent).not.toContain('保存资料时会一并上传已选资质') expect(updatePlatformTenant).not.toHaveBeenCalled() }) diff --git a/src/modules/customers/pages/CustomersView.vue b/src/modules/customers/pages/CustomersView.vue index 58b79d13..a294a5f0 100644 --- a/src/modules/customers/pages/CustomersView.vue +++ b/src/modules/customers/pages/CustomersView.vue @@ -1085,10 +1085,10 @@ onMounted(() => { void loadCustomers() }) - +

基础资料

- + @@ -1103,8 +1103,8 @@ onMounted(() => { void loadCustomers() })
-

企业认证资料

营业执照和18位统一社会信用代码为必填业务项;法人身份证、货代资质证书和银行开户许可证均为选填。

支持 PDF、JPG、PNG,单个文件不超过 5MB;* 为提交审核必填项。

{{ statusMeta(editorStatus).label }}
-
{{ form.unifiedSocialCreditCode }}{{ creditCodeEstablished ? '已登记的统一社会信用代码不可直接修改。' : '必须与营业执照一致,并随营业执照共同保存;法人身份证为选填。' }}
+

企业认证资料

营业执照和18位统一社会信用代码为必填业务项;法人身份证、货代资质证书和银行开户许可证均为选填。

支持 PDF、JPG、PNG,单个文件不超过 5MB;* 为提交审核必填项。

{{ statusMeta(editorStatus).label }}

+
{{ form.unifiedSocialCreditCode }}必须与营业执照一致,并随营业执照共同保存;法人身份证为选填。
文件类型上传文件有效期
@@ -1124,7 +1124,7 @@ onMounted(() => { void loadCustomers() })
- + @@ -1183,13 +1183,12 @@ onMounted(() => { void loadCustomers() })