From fc80d09faaa412bf9ab2fad5c361bba36c3049ab Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Sat, 15 Aug 2026 12:33:29 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/LoadingListsView.test.ts | 31 +-- .../components/LoadingListsView.vue | 73 +------ .../pages/ConfirmedLoadingListDetailView.vue | 184 ++++++++++++++++++ src/modules/loading-lists/routes.ts | 10 + 4 files changed, 220 insertions(+), 78 deletions(-) create mode 100644 src/modules/loading-lists/pages/ConfirmedLoadingListDetailView.vue diff --git a/src/modules/loading-lists/components/LoadingListsView.test.ts b/src/modules/loading-lists/components/LoadingListsView.test.ts index 8cd1f04b..22f7f71f 100644 --- a/src/modules/loading-lists/components/LoadingListsView.test.ts +++ b/src/modules/loading-lists/components/LoadingListsView.test.ts @@ -5,6 +5,8 @@ import { resolve } from 'node:path' const source = readFileSync(resolve(process.cwd(), 'src/modules/loading-lists/components/LoadingListsView.vue'), 'utf8') const detailSource = readFileSync(resolve(process.cwd(), 'src/modules/loading-lists/components/LoadingListDetailDrawer.vue'), 'utf8') const preloadDetailSource = readFileSync(resolve(process.cwd(), 'src/modules/loading-lists/pages/PreloadPlanDetailView.vue'), 'utf8') +const confirmedDetailSource = readFileSync(resolve(process.cwd(), 'src/modules/loading-lists/pages/ConfirmedLoadingListDetailView.vue'), 'utf8') +const routesSource = readFileSync(resolve(process.cwd(), 'src/modules/loading-lists/routes.ts'), 'utf8') describe('LoadingListsView', () => { it('filters both list modes by an active name-only customer selector', () => { @@ -41,12 +43,16 @@ describe('LoadingListsView', () => { expect(detailSource).toContain('ContainerFeesPanel') }) - it('opens preload detail as a route and confirmed detail inline on the list page', () => { + it('opens both preload and confirmed details as independent route pages', () => { expect(source).toContain("name: 'preload-plan-detail'") - expect(source).toContain('v-if="!isPreload && (detailLoading || detailError || detail)"') - expect(source).toContain('class="confirmed-inline-detail surface-panel"') + expect(source).toContain("name: 'loading-list-detail'") + expect(source).not.toContain('confirmed-inline-detail') expect(preloadDetailSource).toContain('返回列表') expect(preloadDetailSource).toContain('router.back()') + expect(confirmedDetailSource).toContain('返回列表') + expect(confirmedDetailSource).toContain('router.back()') + expect(routesSource).toContain("path: 'loading-lists/:planId'") + expect(routesSource).toContain("name: 'loading-list-detail'") }) it('keeps the real transport edit contract in a 620px centered modal', () => { @@ -88,8 +94,9 @@ describe('LoadingListsView', () => { expect(source).toContain('useRoute()') expect(source).toContain('route.query.planId') expect(source).toContain('route.query.warehouseId') - expect(source).toContain("const expectedStatuses = isPreload.value ? ['EDITING', 'LOADING'] : ['COMPLETED']") - expect(source).toContain('MODE_STATUS_MISMATCH') + expect(source).toContain("router.replace({ name: 'loading-list-detail'") + expect(confirmedDetailSource).toContain("result.summary.status !== 'COMPLETED'") + expect(confirmedDetailSource).toContain('MODE_STATUS_MISMATCH') }) it('passes translated status text directly to the badge', () => { @@ -125,12 +132,14 @@ describe('LoadingListsView', () => { }) it('uploads declaration documents from a file picker and freezes them after completion', () => { - expect(source).toContain(' import { computed, onMounted, reactive, ref, watch } from 'vue' import { useRoute, useRouter } from 'vue-router' -import { ElMessage, type FormInstance, type FormRules, type UploadFile } from 'element-plus' -import { Eye, FileUp, Pencil } from '@lucide/vue' +import { ElMessage, type FormInstance, type FormRules } from 'element-plus' +import { Eye, Pencil } from '@lucide/vue' import { useAccessPreviewStore } from '@/core/access/accessPreview' import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam' import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http' @@ -15,11 +15,7 @@ import LoadErrorState from '@/shared/components/LoadErrorState.vue' import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue' import StatusBadge from '@/shared/components/StatusBadge.vue' import { loadingListStatusLabel } from '../status' -import LoadingListDetailDrawer from './LoadingListDetailDrawer.vue' import { - uploadLoadingDeclaration, - completeLoadingDeclaration, - getConfirmedLoadingList, getPreloadPlan, listConfirmedLoadingLists, listPreloadPlans, @@ -56,10 +52,6 @@ const editError = ref(null) const editFormRef = ref() const editForm = reactive({ containerNo: '', voyageNo: '', operationReason: '' }) const handledDeepLink = ref('') -const customsDialogOpen = ref(false) -const customsFile = ref(null) -const customsSaving = ref(false) -const customsError = ref(null) const isPreload = computed(() => props.mode === 'preload') const title = computed(() => isPreload.value ? '预装清单' : '装柜清单') @@ -67,7 +59,6 @@ const listPermission = computed(() => isPreload.value ? 'platform:firstleg:prelo const canList = computed(() => access.can(listPermission.value)) const canView = computed(() => canList.value) const canEdit = computed(() => isPreload.value && access.can('platform:firstleg:preload:edit')) -const canManageCustoms = computed(() => !isPreload.value && access.can('platform:firstleg:customs:manage')) const fields = computed(() => [ { key: 'customerId', type: 'select', label: '客户', placeholder: '全部客户', width: 220, options: customers.value.filter((customer) => customer.status === 'ACTIVE').map((customer) => ({ label: customer.tenantName, value: String(customer.id) })), props: { filterable: true, loading: customersLoading.value } }, { key: 'keyword', type: 'input', label: '关键词', placeholder: isPreload.value ? '预装单号/集装箱号/柜号' : '装柜清单号/集装箱号/柜号', width: 250 }, @@ -165,20 +156,7 @@ async function openDeepLinkedDetail() { await router.replace({ name: 'preload-plan-detail', params: { planId }, query: { warehouseId } }) return } - selectedPlanId.value = planId - selectedWarehouseId.value = warehouseId - detail.value = null - detailError.value = null - await loadDetail() - const expectedStatuses = isPreload.value ? ['EDITING', 'LOADING'] : ['COMPLETED'] - const loadedDetail = detail.value as LoadingDetail | null - if (loadedDetail && !expectedStatuses.includes(loadedDetail.summary.status)) { - detail.value = null - detailError.value = { - code: 'MODE_STATUS_MISMATCH', - message: `该计划不属于当前${title.value}状态,请从对应清单入口查看。`, - } - } + await router.replace({ name: 'loading-list-detail', params: { planId }, query: { warehouseId } }) } function search() { @@ -200,9 +178,7 @@ async function loadDetail() { detailLoading.value = true detailError.value = null try { - detail.value = isPreload.value - ? await getPreloadPlan(selectedPlanId.value, selectedWarehouseId.value) - : await getConfirmedLoadingList(selectedPlanId.value, selectedWarehouseId.value) + detail.value = await getPreloadPlan(selectedPlanId.value, selectedWarehouseId.value) } catch (cause) { detailError.value = getApiErrorDetails(cause, '清单详情加载失败,请稍后重试') } finally { @@ -216,11 +192,7 @@ function openDetail(rowValue: object) { void router.push({ name: 'preload-plan-detail', params: { planId: String(row.planId) }, query: { warehouseId: String(row.warehouseId || '') } }) return } - selectedPlanId.value = String(row.planId) - selectedWarehouseId.value = String(row.warehouseId || '') - detail.value = null - detailError.value = null - void loadDetail() + void router.push({ name: 'loading-list-detail', params: { planId: String(row.planId) }, query: { warehouseId: String(row.warehouseId || '') } }) } async function openEditFromRow(rowValue: object) { @@ -287,20 +259,6 @@ async function loadCustomers() { customersLoading.value = false } } -function openCustomsDocument() { customsFile.value = null; customsError.value = null; customsDialogOpen.value = true } -function selectCustomsFile(uploadFile: UploadFile) { customsFile.value = uploadFile.raw ?? null; customsError.value = null } -function removeCustomsFile() { customsFile.value = null } -async function uploadCustomsDocument() { - if (!detail.value || !customsFile.value) { customsError.value = { code: 'FILE_REQUIRED', message: '请选择报关资料文件' }; return } - customsSaving.value = true; customsError.value = null - try { detail.value.customsDocument = await uploadLoadingDeclaration(selectedPlanId.value, selectedWarehouseId.value, customsFile.value, detail.value.customsDocument?.version || 0, '上传装柜清单报关资料'); customsFile.value = null; customsDialogOpen.value = false; ElMessage.success('报关资料已上传') } catch (cause) { customsError.value = getApiErrorDetails(cause, '报关资料上传失败') } finally { customsSaving.value = false } -} -async function completeCustomsDocument() { - if (!detail.value?.customsDocument) return - customsSaving.value = true; customsError.value = null - try { detail.value.customsDocument = await completeLoadingDeclaration(selectedPlanId.value, selectedWarehouseId.value, detail.value.customsDocument.version, '确认装柜清单报关完成'); ElMessage.success('已完成报关') } catch (cause) { customsError.value = getApiErrorDetails(cause, '报关完成失败') } finally { customsSaving.value = false } -} - watch([page, pageSize], () => void load()) watch(() => [route.query.planId, route.query.warehouseId], () => void openDeepLinkedDetail()) onMounted(() => { @@ -361,24 +319,6 @@ onMounted(() => { - - - 选择文件 - { diff --git a/src/modules/loading-lists/pages/ConfirmedLoadingListDetailView.vue b/src/modules/loading-lists/pages/ConfirmedLoadingListDetailView.vue new file mode 100644 index 00000000..cec36e1a --- /dev/null +++ b/src/modules/loading-lists/pages/ConfirmedLoadingListDetailView.vue @@ -0,0 +1,184 @@ + + + + + diff --git a/src/modules/loading-lists/routes.ts b/src/modules/loading-lists/routes.ts index 63e6c823..3177b8b6 100644 --- a/src/modules/loading-lists/routes.ts +++ b/src/modules/loading-lists/routes.ts @@ -31,4 +31,14 @@ export const loadingListRoutes = [ permission: 'platform:firstleg:loading-list:list', }, }, + { + path: 'loading-lists/:planId', + name: 'loading-list-detail', + component: () => import('./pages/ConfirmedLoadingListDetailView.vue'), + meta: { + title: '装柜清单详情', + description: '查看装柜清单基本信息、货物明细、费用和操作历史', + permission: 'platform:firstleg:loading-list:list', + }, + }, ] satisfies RouteRecordRaw[]