diff --git a/src/modules/overseas-business/components/FleetArrangementSummary.test.ts b/src/modules/overseas-business/components/FleetArrangementSummary.test.ts new file mode 100644 index 00000000..f3c74b72 --- /dev/null +++ b/src/modules/overseas-business/components/FleetArrangementSummary.test.ts @@ -0,0 +1,62 @@ +import ElementPlus from 'element-plus' +import { mount, enableAutoUnmount } from '@vue/test-utils' +import { afterEach, beforeEach, expect, it, vi } from 'vitest' +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' +import { compileStyle, parse } from 'vue/compiler-sfc' +import FleetArrangementSummary from './FleetArrangementSummary.vue' +import type { FleetArrangement } from '../fleetArrangement' + +const state = vi.hoisted(() => ({ writableLinks: true, push: vi.fn() })) +vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => state.writableLinks }) })) +vi.mock('vue-router', () => ({ useRouter: () => ({ push: state.push }) })) +enableAutoUnmount(afterEach) +beforeEach(() => { state.writableLinks = true; state.push.mockClear() }) +const arrangement: FleetArrangement = { + sourceType: 'FLEET_CAPACITY_OFFER', businessType: 'LOCAL_LOOSEBOARD', businessOrderNo: 'LB-ONT-20261007-309123', bookingNo: 'FB-2107769452587171841', tripNo: 'TR-76DB64B4457C4206', + plannedDepartureAt: '2026-10-07T13:46:07Z', originWarehouseName: '洛杉矶仓', destinationLabel: 'ONT8', deliveryMode: 'SELF_DELIVERY', deliveryInstructions: '交货说明完整保留', fulfillmentStage: 'WAITING_INBOUND', stageOccurredAt: null, + agreedFreightAmount: 30, currency: 'CNY', incomingNo: 'WT-ONT-20261007-12345678901234567890', dispatchNo: 'DP-ONT-20261007-12345678901234567890', deliveryNo: 'DL-ONT-20261007-12345678901234567890', + signedPackages: 0, unsignedPackages: null, returnedPackages: 0, exceptionSummary: null, readOnlyReason: '此订单由拼车承运实际履约推进', +} +function render() { return mount(FleetArrangementSummary, { props: { arrangement }, global: { plugins: [ElementPlus] } }) } +it('retains all long business numbers, labels and authorized navigation', async () => { + const wrapper = render() + const rows = wrapper.findAll('.fleet-arrangement-fields > div') + expect(rows).toHaveLength(14) + for (const [label, value] of [['订单号', arrangement.businessOrderNo], ['外接预订号', arrangement.bookingNo], ['车次号', arrangement.tripNo], ['来货单号', arrangement.incomingNo], ['排车单号', arrangement.dispatchNo], ['派送单号', arrangement.deliveryNo]]) { + expect(rows.find(row => row.get('dt').text() === label)!.get('dd').text()).toBe(value) + } + expect(rows.find(row => row.get('dt').text() === '已签 / 未签 / 退回箱数')!.get('dd').text()).toBe('0 / — / 0') + const links = wrapper.findAll('button.fleet-arrangement-reference') + expect(links.map(link => link.text())).toEqual([arrangement.tripNo, arrangement.dispatchNo]) + await links[0]!.trigger('click'); await links[1]!.trigger('click') + expect(state.push).toHaveBeenNthCalledWith(1, { name: 'overseas-master-data-trip-detail', params: { tripNo: arrangement.tripNo } }) + expect(state.push).toHaveBeenNthCalledWith(2, { name: 'overseas-dispatch-detail', params: { dispatchNo: arrangement.dispatchNo } }) +}) +it('keeps full reference text without links when target permissions are absent', () => { + state.writableLinks = false + const wrapper = render() + expect(wrapper.findAll('button')).toHaveLength(0) + expect(wrapper.text()).toContain(arrangement.tripNo); expect(wrapper.text()).toContain(arrangement.dispatchNo) +}) +it('uses a scoped two-column layout and wrap-capable reference controls rather than truncation', () => { + const source = readFileSync(resolve(process.cwd(), 'src/modules/overseas-business/components/FleetArrangementSummary.vue'), 'utf8') + const style = parse(source).descriptor.styles[0]! + expect(style.scoped).toBe(true) + const compiled = compileStyle({ source: style.content, filename: 'FleetArrangementSummary.vue', id: 'data-v-arrangement', scoped: true }) + expect(compiled.errors).toEqual([]) + const rules = new Map>() + compiled.rawResult!.root.walkRules(rule => { + const declarations: Record = {} + rule.walkDecls(declaration => { declarations[declaration.prop] = declaration.value }) + rules.set(rule.selector, declarations) + }) + const grid = [...rules].find(([selector]) => selector.includes('.fleet-arrangement-fields.density-fields'))! + expect(grid[0]).toContain('[data-v-arrangement]') + expect(grid[1]).toMatchObject({ 'grid-template-columns': 'repeat(2, minmax(0, 1fr))', '--admin-field-label-width': '160px' }) + const button = [...rules].find(([selector]) => selector.endsWith('.fleet-arrangement-reference[data-v-arrangement]'))! + expect(button[1]).toMatchObject({ 'max-width': '100%', 'min-width': '0', height: 'auto', 'white-space': 'normal', 'text-align': 'left' }) + const text = [...rules].find(([selector]) => selector.endsWith(' span'))! + expect(text[1]).toMatchObject({ 'white-space': 'normal', 'overflow-wrap': 'anywhere', 'min-width': '0' }) + expect(style.content).not.toMatch(/text-overflow:\s*ellipsis|overflow:\s*hidden|white-space:\s*nowrap/) +}) diff --git a/src/modules/overseas-business/components/FleetArrangementSummary.vue b/src/modules/overseas-business/components/FleetArrangementSummary.vue index ff0d95ae..50adb4cb 100644 --- a/src/modules/overseas-business/components/FleetArrangementSummary.vue +++ b/src/modules/overseas-business/components/FleetArrangementSummary.vue @@ -9,10 +9,10 @@ const time = (value: string | null) => value ? new Date(value).toLocaleString('z + +