diff --git a/src/modules/overseas-master-data/PROTOTYPE_MATRIX.md b/src/modules/overseas-master-data/PROTOTYPE_MATRIX.md index 9d310c78..8f28029d 100644 --- a/src/modules/overseas-master-data/PROTOTYPE_MATRIX.md +++ b/src/modules/overseas-master-data/PROTOTYPE_MATRIX.md @@ -1,5 +1,14 @@ # 海外通用资料原型核对矩阵 +## 散板需求只读展示(2026-10-21) + +依据本轮用户确认:仅保留内部余位需求池,人工联系下单;登记与后续拼车直接隐藏,不增加开关,不删除代码。当前范围唯一见[拼车主契约](../../../../../docs/海外仓业务/内部拼车需求池首版开发契约.md#当前页面范围2026-10-21),覆盖下文此前开放登记的页面结论。 + +| 页面/操作 | 当前承载与调整 | 验收 | +|---|---|---| +| 散板需求列表/查询、分页、详情 | 保持独立列表与原余位来源,默认展示车次余位、线路、时间、联系信息;隐藏待匹配散板、我的登记、登记及待办入口,原代码保留 | 43项定向测试及页面、测试源码定向类型检查通过;待人工1920×1080、100%截图确认 | +| 散板需求详情/查看、重新核对 | 保持独立详情,仅展示运输摘要、联系方式和分段余位;隐藏登记、意向列表、来货及履约窗口,旧登记参数不再打开窗口 | 同上 | + ## 2026-10-20 列表顶部紧凑收尾 依据为本轮用户整柜业务截图及“所有列表顶部紧凑”的确认;列表公共工具栏和筛选内距由 `adminWorkbench.css` 统一维护。 diff --git a/src/modules/overseas-master-data/components/CapacityDialogClosing.test.ts b/src/modules/overseas-master-data/components/CapacityDialogClosing.test.ts index 0f7ee28b..d12f529c 100644 --- a/src/modules/overseas-master-data/components/CapacityDialogClosing.test.ts +++ b/src/modules/overseas-master-data/components/CapacityDialogClosing.test.ts @@ -2,11 +2,10 @@ import ElementPlus, { ElMessageBox } from 'element-plus' import { createPinia } from 'pinia' import { mount, flushPromises, type VueWrapper } from '@vue/test-utils' import { afterEach, beforeEach, expect, it, vi } from 'vitest' -import { defineComponent } from 'vue' +import { defineComponent, ref } from 'vue' import { createMemoryHistory, createRouter, RouterView } from 'vue-router' import CapacityInquiryDialog from './CapacityInquiryDialog.vue' import CapacityOfferSettingsDialog from './CapacityOfferSettingsDialog.vue' -import CapacityOfferDetailView from '../pages/CapacityOfferDetailView.vue' import type { CapacityOffer } from '../capacityOfferTypes' import type { FleetTrip } from '../tripTypes' import { useUnsavedWorkStore } from '@/core/unsaved-work/unsavedWork' @@ -64,11 +63,13 @@ it.each(['inquiry', 'settings'] as const)('keeps the real %s dialog and edited i expect(wrapper.emitted('update:modelValue')).toEqual([[false]]) }) -it('blocks browser back with the real router, cached detail and native dialog, without duplicating the global prompt', async () => { +it('preserves route-leave protection in the retained native inquiry dialog without duplicating the global prompt', async () => { const pinia = createPinia(), registry = useUnsavedWorkStore(pinia), confirms = vi.spyOn(ElMessageBox, 'confirm') + // 登记页面入口已隐藏;保留组件自身与真实路由的未保存保护回归。 + const InquiryHost = defineComponent({ components: { CapacityInquiryDialog }, setup: () => ({ offer, open: ref(false) }), template: '' }) const router = createRouter({ history: createMemoryHistory(), routes: [ { path: '/pool', name: 'overseas-master-data-capacity-offers', component: { template: '

运力列表

' }, meta: { requiresAuth: true } }, - { path: '/pool/:offerNo', name: 'overseas-master-data-capacity-offer-detail', component: CapacityOfferDetailView, meta: { requiresAuth: true } }, + { path: '/pool/:offerNo', name: 'overseas-master-data-capacity-offer-detail', component: InquiryHost, meta: { requiresAuth: true } }, { path: '/outside', name: 'outside', component: { template: '

外部页面

' }, meta: { workspaceTab: false } }, ] }) // Match the application's intentional policy: cross-workspace switching skips the global prompt. @@ -81,7 +82,7 @@ it('blocks browser back with the real router, cached detail and native dialog, w await router.push('/pool'); await router.isReady() const wrapper = mount(Host, { attachTo: document.body, global: { plugins: [pinia, router, ElementPlus] } }); wrappers.push(wrapper) await router.push('/pool/CO-CLOSE'); await flushPromises() - await wrapper.findAll('button').find(button => button.text() === '登记客户货物')!.trigger('click'); await flushPromises() + await wrapper.findAll('button').find(button => button.text() === '打开保留的登记组件')!.trigger('click'); await flushPromises() await wrapper.get('details summary').trigger('click') const input = wrapper.get('input[placeholder="填写货物名称(确认承运前必填)"]') await input.setValue('UI-修复后保留输入'); await flushPromises() @@ -92,15 +93,15 @@ it('blocks browser back with the real router, cached detail and native dialog, w expect(confirms).toHaveBeenCalledOnce(); expect(wrapper.get('.app-dialog').isVisible()).toBe(true) await continueEditing() expect(router.currentRoute.value.name).toBe('overseas-master-data-capacity-offer-detail') - expect(input.element.value).toBe('UI-修复后保留输入'); expect(getCapacityOffer).toHaveBeenCalledExactlyOnceWith('CO-CLOSE') + expect(input.element.value).toBe('UI-修复后保留输入'); expect(getCapacityOffer).not.toHaveBeenCalled() confirms.mockClear(); router.back(); await flushPromises() const message = [...document.querySelectorAll('.el-message-box')].at(-1)! const leave = [...message.querySelectorAll('button')].find(button => button.textContent?.trim() === '放弃并离开') as HTMLButtonElement expect(leave).toBeTruthy(); leave.click(); await flushPromises() expect(router.currentRoute.value.name).toBe('overseas-master-data-capacity-offers') - expect(confirms).toHaveBeenCalledOnce(); expect(getCapacityOffer).toHaveBeenCalledExactlyOnceWith('CO-CLOSE') + expect(confirms).toHaveBeenCalledOnce(); expect(getCapacityOffer).not.toHaveBeenCalled() await new Promise(resolve => setTimeout(resolve, 50)); await router.push('/pool/CO-CLOSE'); await flushPromises() - await wrapper.findAll('button').find(button => button.text() === '登记客户货物')!.trigger('click'); await flushPromises() + await wrapper.findAll('button').find(button => button.text() === '打开保留的登记组件')!.trigger('click'); await flushPromises() await wrapper.get('input[placeholder="填写货物名称(确认承运前必填)"]').setValue('另一次修改'); await flushPromises() confirms.mockClear(); const navigation = router.push('/outside'); await flushPromises() expect(confirms).toHaveBeenCalledOnce(); expect(document.body.textContent).toContain('全局离开确认') diff --git a/src/modules/overseas-master-data/pages/CapacityOfferDetailView.vue b/src/modules/overseas-master-data/pages/CapacityOfferDetailView.vue index 263fc536..a8738fbe 100644 --- a/src/modules/overseas-master-data/pages/CapacityOfferDetailView.vue +++ b/src/modules/overseas-master-data/pages/CapacityOfferDetailView.vue @@ -54,13 +54,13 @@ async function load() { const value = await getCapacityOffer(String(route.params.offerNo)) let checked: CapacityInquiry | null = null // 当前只读展示余位,旧登记深链不再打开登记窗口;原流程保留。 - if (false) { + /* if (typeof route.query?.inquiry === 'string' && /^[A-Z][A-Z0-9-]{1,63}$/.test(route.query.inquiry)) { const returned = await getCapacityInquiry(route.query.inquiry) if (returned.offerNo !== value.offer.offerNo) throw new Error('登记不属于当前发布') if (current === sequence) { checked = returned; readonly.value = true; confirmMode.value = false; editorOpen.value = true } } else if (editing.value && editorOpen.value) checked = value.inquiries.records.find(row => row.bookingNo === editing.value?.bookingNo) || await getCapacityInquiry(editing.value.bookingNo) - } + */ if (current === sequence) { resetting = true; detail.value = value; if (checked) editing.value = checked; page.value = value.inquiries.page; size.value = value.inquiries.size; queueMicrotask(() => { resetting = false }) } } catch (cause) { if (current === sequence) { detail.value = null; error.value = getApiErrorDetails(cause, '发布详情读取失败') } } @@ -93,18 +93,29 @@ watch([page, size], loadInquiries) onBeforeUnmount(() => { followupSequence++; sequence++; inquirySequence++ })