This commit is contained in:
2026-10-08 17:08:03 +08:00
parent cc37a42298
commit 7b0df1db37
44 changed files with 574 additions and 405 deletions
@@ -0,0 +1,27 @@
import { flushPromises, shallowMount } from '@vue/test-utils'
import { afterEach, describe, expect, it, vi } from 'vitest'
import LoadingListDetailDrawer from './LoadingListDetailDrawer.vue'
import type { LoadingDetail } from '../api/platformLoadingLists'
afterEach(() => vi.restoreAllMocks())
describe('shared manifest presentation', () => {
it.each([undefined, '返回货柜概览'])('keeps print in the common header and emits return for %s', async (backLabel) => {
const print = vi.spyOn(window, 'print').mockImplementation(() => {})
const wrapper = shallowMount(LoadingListDetailDrawer, {
props: { mode: 'confirmed', dense: true, backLabel, canEdit: false, loading: false, error: null,
detail: { summary: { planId: '1', warehouseId: '11', loadingListNo: 'LP-TEST', status: 'COMPLETED' }, items: [], histories: [] } as unknown as LoadingDetail },
global: { directives: { loading: {} }, stubs: { ElButton: { template: '<button><slot /></button>' } } },
})
expect(wrapper.classes()).toContain('loading-density')
expect(wrapper.get('h1').text()).toBe('装柜清单 · LP-TEST')
const button = wrapper.get('.detail-page-header__actions button')
expect(button.text()).toBe('打印装柜清单')
await button.trigger('click'); await flushPromises()
expect(print).toHaveBeenCalledTimes(1)
expect(wrapper.get('.detail-page-header__back').text()).toBe(backLabel || '返回列表')
await wrapper.get('.detail-page-header__back').trigger('click')
expect(wrapper.emitted('back')).toHaveLength(1)
wrapper.unmount()
})
})
@@ -1,7 +1,7 @@
<script setup lang="ts">
import '@/styles/adminDensity.css'
import { computed, ref, watch } from 'vue'
import { ArrowLeft, Check, ChevronRight, Pencil } from '@lucide/vue'
import { ArrowLeft, Check, ChevronRight, Pencil, Printer } from '@lucide/vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import DetailPageLayout from '@/shared/components/DetailPageLayout.vue'
@@ -9,6 +9,7 @@ import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import StatusBadge from '@/shared/components/StatusBadge.vue'
import PalletLabelPrintDialog from './PalletLabelPrintDialog.vue'
import { loadingListStatusLabel } from '../status'
import { printLoadingList } from '../printLoadingList'
import type { ApiErrorDetails } from '@/shared/api/http'
import type { LoadingDetail, LoadingHistory, LoadingItem, LoadingListMode, LoadingListSummary, PreloadSummary } from '../api/platformLoadingLists'
@@ -22,6 +23,7 @@ const props = defineProps<{
pdaAssigneeSelected?: boolean
dense?: boolean
embedded?: boolean
backLabel?: string
canEditTransport?: boolean
confirmSaving?: boolean
}>()
@@ -161,12 +163,13 @@ function formatNumber(value?: number | null, digits = 3) {
<section v-loading="loading" class="loading-detail-content" :class="{ 'admin-density loading-density': mode === 'preload' || dense }" :aria-label="title" :aria-busy="loading">
<section class="detail-overview" aria-label="清单概览">
<header class="detail-page-header" :class="{ 'detail-page-header--embedded': embedded }">
<el-button link type="primary" class="detail-page-header__back" :class="{ 'density-back': mode === 'preload' || dense }" @click="emit('back')"><ArrowLeft :size="16" />返回列表</el-button>
<el-button link type="primary" class="detail-page-header__back" :class="{ 'density-back': mode === 'preload' || dense }" @click="emit('back')"><ArrowLeft :size="16" />{{ backLabel || '返回列表' }}</el-button>
<div v-if="!embedded" class="detail-page-header__identity">
<h1>{{ title }}</h1>
<p v-if="mode !== 'preload'">查看已完成装柜明细和柜次费用</p>
<p v-if="mode !== 'preload'">查看已完成装柜明细和操作历史</p>
</div>
<div v-if="detail && summary" class="detail-page-header__actions">
<el-button v-if="mode === 'confirmed'" :disabled="loading" @click="printLoadingList"><Printer :size="16" />打印装柜清单</el-button>
<PalletLabelPrintDialog :dense="mode === 'preload' || dense" v-if="summary.warehouseId" :plan-id="summary.planId" :warehouse-id="summary.warehouseId" :disabled="!summary.containerNo || summary.status === 'EDITING'" />
<template v-if="canEditItemRemark">
<el-button v-if="canEditTransport" @click="emit('edit')"><Pencil :size="16" />{{ summary.containerNo ? '纠正集装箱号 / 航次' : '补录集装箱号 / 航次' }}</el-button>
@@ -6,6 +6,7 @@ const source = readFileSync(resolve(process.cwd(), 'src/modules/loading-lists/co
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 sharedLoaderSource = readFileSync(resolve(process.cwd(), 'src/modules/loading-lists/useLoadingListDetail.ts'), 'utf8')
const routesSource = readFileSync(resolve(process.cwd(), 'src/modules/loading-lists/routes.ts'), 'utf8')
describe('LoadingListsView', () => {
@@ -31,7 +32,7 @@ describe('LoadingListsView', () => {
expect(source).not.toContain("ElMessage.warning('请先选择仓库')")
expect(source).not.toContain("'请先选择仓库后查询'")
expect(preloadDetailSource).toContain('MISSING_WAREHOUSE_CONTEXT')
expect(confirmedDetailSource).toContain('MISSING_WAREHOUSE_CONTEXT')
expect(sharedLoaderSource).toContain('MISSING_WAREHOUSE_CONTEXT')
})
it('uses the shared query and table components while keeping details on route pages', () => {
@@ -63,7 +64,7 @@ describe('LoadingListsView', () => {
expect(source).not.toContain('confirmed-inline-detail')
expect(preloadDetailSource).toContain('返回列表')
expect(preloadDetailSource).toContain('router.back()')
expect(confirmedDetailSource).toContain('返回列表')
expect(detailSource).toContain("backLabel || '返回列表'")
expect(confirmedDetailSource).toContain('router.back()')
expect(confirmedDetailSource).toContain('@media (min-width: 1920px) { .confirmed-loading-detail-page { width: 100%; max-width: none; gap: 12px; } }')
expect(routesSource).toContain("path: 'loading-lists/:planId'")
@@ -112,10 +113,10 @@ describe('LoadingListsView', () => {
expect(source).toContain('route.query.planId')
expect(source).toContain('route.query.warehouseId')
expect(source).toContain("await router.replace({ name: 'loading-list-detail'")
expect(confirmedDetailSource).toContain("result.summary.status !== 'COMPLETED'")
expect(confirmedDetailSource).toContain('MODE_STATUS_MISMATCH')
expect(sharedLoaderSource).toContain("result.summary.status !== 'COMPLETED'")
expect(sharedLoaderSource).toContain('MODE_STATUS_MISMATCH')
expect(confirmedDetailSource).toContain('watch([planId, warehouseId], () => void loadDetail())')
expect(confirmedDetailSource).toContain('if (sequence !== loadSequence) return')
expect(sharedLoaderSource).toContain('if (request !== sequence) return null')
expect(preloadDetailSource).toContain('watch([planId, warehouseId], () => {')
})