diff --git a/src/modules/container-loading/api/platformContainerLoading.test.ts b/src/modules/container-loading/api/platformContainerLoading.test.ts index 03c487d7..ee354726 100644 --- a/src/modules/container-loading/api/platformContainerLoading.test.ts +++ b/src/modules/container-loading/api/platformContainerLoading.test.ts @@ -15,7 +15,7 @@ vi.mock('@/shared/api/http', () => ({ unwrapApiResponse: (response: { data: T }) => response.data, })) -const container = { id: 9007199254740993, planId: 9007199254740994, warehouseId: 9007199254740995, shippingCompanyId: 9007199254740996, channelCode: 'NY_EXX', channelName: '纽约EXX海铁', version: '2', capacityCbm: '76', maxWeightKg: '28000' } +const container = { id: 9007199254740993, planId: 9007199254740994, warehouseId: 9007199254740995, shippingCompanyId: 9007199254740996, sourceType: 'SIMULATION', recordStatus: 'ACTIVE', mergedIntoContainerBookingId: null, channelCode: 'NY_EXX', channelName: '纽约EXX海铁', version: '2', capacityCbm: '76', maxWeightKg: '28000' } const plan = { id: 9007199254740993, bookingId: 9007199254740994, version: '3', status: 'EDITING', items: [{ id: 9007199254740995, loadingPlanId: 9007199254740993, accountId: 1, inboundId: 2, inboundDetailId: 3, orderId: 4, destinationWarehouseId: 5, packageCount: '6', version: '1' }] } describe('platform container loading API', () => { @@ -51,10 +51,10 @@ describe('platform container loading API', () => { vi.mocked(http.post).mockResolvedValue({ code: '200', data: { ...container, oceanBookingId: 101, oceanBookingRequirementId: 202 } }) vi.mocked(http.delete).mockResolvedValue({ code: '200', data: { ...container, oceanBookingId: null, oceanBookingRequirementId: null } }) await createContainerSimulation({ channelCode: 'NY_EXX', warehouseId: '8', containerType: '40HC', capacityCbm: 76, maxWeightKg: 28000 }) - await linkContainerOceanBooking('7', { oceanBookingId: '101', requirementId: '202', expectedVersion: 3 }) + await linkContainerOceanBooking('7', { realContainerBookingId: '101', expectedVersion: 3 }) await unlinkContainerOceanBooking('7', 4, '空柜改配') expect(http.post).toHaveBeenNthCalledWith(1, '/platform/v1/containers', { channelCode: 'NY_EXX', warehouseId: '8', containerType: '40HC', capacityCbm: 76, maxWeightKg: 28000 }) - expect(http.post).toHaveBeenNthCalledWith(2, '/platform/v1/containers/7/ocean-booking-link', { oceanBookingId: '101', requirementId: '202', expectedVersion: 3 }) + expect(http.post).toHaveBeenNthCalledWith(2, '/platform/v1/containers/7/ocean-booking-link', { realContainerBookingId: '101', expectedVersion: 3 }) expect(http.delete).toHaveBeenCalledWith('/platform/v1/containers/7/ocean-booking-link', { data: { expectedVersion: 4, reason: '空柜改配' } }) }) diff --git a/src/modules/container-loading/api/platformContainerLoading.ts b/src/modules/container-loading/api/platformContainerLoading.ts index ab6ef314..ab9ffb88 100644 --- a/src/modules/container-loading/api/platformContainerLoading.ts +++ b/src/modules/container-loading/api/platformContainerLoading.ts @@ -69,6 +69,7 @@ function container(raw: ContainerBookingResource): ContainerBookingResource { ...raw, id: id(raw.id), planId: optionalId(raw.planId), + mergedIntoContainerBookingId: optionalId(raw.mergedIntoContainerBookingId), warehouseId: id(raw.warehouseId), oceanBookingId: optionalId(raw.oceanBookingId), oceanBookingRequirementId: optionalId(raw.oceanBookingRequirementId), diff --git a/src/modules/container-loading/pages/ContainersView.test.ts b/src/modules/container-loading/pages/ContainersView.test.ts index 2378f46e..d0450448 100644 --- a/src/modules/container-loading/pages/ContainersView.test.ts +++ b/src/modules/container-loading/pages/ContainersView.test.ts @@ -9,17 +9,17 @@ import ContainersView from './ContainersView.vue' import { listAllPlatformWarehouses } from '@/modules/warehouses' import { listPlatformPartners } from '@/modules/master-data' import { listSuppliers } from '@/modules/suppliers' -import { listOceanBookings } from '@/modules/ocean-bookings' +import { getOceanBooking, listOceanBookings } from '@/modules/ocean-bookings' import { getContainer, getContainerOverview, listContainers } from '../api/platformContainerLoading' vi.mock('@/modules/warehouses', () => ({ listAllPlatformWarehouses: vi.fn() })) vi.mock('@/modules/master-data', () => ({ listPlatformPartners: vi.fn() })) vi.mock('@/modules/suppliers', () => ({ listSuppliers: vi.fn() })) -vi.mock('@/modules/ocean-bookings', () => ({ listOceanBookings: vi.fn() })) +vi.mock('@/modules/ocean-bookings', () => ({ getOceanBooking: vi.fn(), listOceanBookings: vi.fn(), listWarehouseLogisticsChannels: vi.fn() })) vi.mock('@/modules/iam', () => ({ listPlatformTenants: vi.fn().mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 0 }) })) const accessCan = vi.hoisted(() => vi.fn()) vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: accessCan }) })) -vi.mock('../api/platformContainerLoading', () => ({ listContainers: vi.fn(), getContainer: vi.fn(), getContainerOverview: vi.fn(), getContainerClearance: vi.fn(), uploadContainerClearance: vi.fn(), completeContainerClearance: vi.fn(), uploadContainerDocument: vi.fn(), downloadContainerDocument: vi.fn(), downloadContainerPdaPhoto: vi.fn(), markContainerException: vi.fn(), cancelContainerException: vi.fn(), createContainer: vi.fn(), updateContainer: vi.fn(), updateContainerTransport: vi.fn() })) +vi.mock('../api/platformContainerLoading', () => ({ listContainers: vi.fn(), getContainer: vi.fn(), getContainerOverview: vi.fn(), getContainerClearance: vi.fn(), uploadContainerClearance: vi.fn(), completeContainerClearance: vi.fn(), uploadContainerDocument: vi.fn(), downloadContainerDocument: vi.fn(), downloadContainerPdaPhoto: vi.fn(), markContainerException: vi.fn(), cancelContainerException: vi.fn(), createContainerSimulation: vi.fn(), linkContainerOceanBooking: vi.fn(), unlinkContainerOceanBooking: vi.fn(), updateContainer: vi.fn(), updateContainerTransport: vi.fn() })) function createTestRouter() { return createRouter({ history: createMemoryHistory(), routes: [ @@ -38,6 +38,7 @@ describe('ContainersView', () => { vi.mocked(listPlatformPartners).mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 1 }) vi.mocked(listSuppliers).mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 1 }) vi.mocked(listOceanBookings).mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 1 }) + vi.mocked(getOceanBooking).mockResolvedValue({} as never) vi.mocked(listContainers).mockResolvedValue({ records: [{ id: '9001', planId: null, bookingNo: 'BK-1', cabinetNo: 'CB-1', containerNo: 'MSCU-1', warehouseId: '8', warehouseName: '义乌仓', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', containerType: '40HC', planStatus: 'WAIT_LOADING', version: 0, billOfLadingNo: null, vesselName: null, voyageNo: null, shippingCompanyId: null, shippingCompanyName: null, departurePortCode: null, departurePortName: '宁波', destinationPortCode: null, destinationPortName: '长滩', destinationWarehouseCode: null, destinationWarehouseName: null, capacityCbm: 76, maxWeightKg: 28000, terminalTruckingCompany: null, loadingAt: null, estimatedSailingAt: null, actualSailingAt: null, estimatedPortArrivalAt: null, actualPortArrivalAt: null, estimatedWarehouseArrivalAt: null, actualWarehouseArrivalAt: null }], page: 1, size: 20, total: 1, pages: 1 }) vi.mocked(getContainer).mockResolvedValue({ id: '9001', planId: null, bookingNo: 'BK-1', cabinetNo: 'CB-1', containerNo: 'MSCU-1', warehouseId: '8', warehouseName: '义乌仓', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', containerType: '40HC', planStatus: 'WAIT_LOADING', version: 0, billOfLadingNo: null, vesselName: null, voyageNo: null, shippingCompanyId: null, shippingCompanyName: null, departurePortCode: null, departurePortName: '宁波', destinationPortCode: null, destinationPortName: '长滩', destinationWarehouseCode: null, destinationWarehouseName: null, capacityCbm: 76, maxWeightKg: 28000, terminalTruckingCompany: null, loadingAt: null, estimatedSailingAt: null, actualSailingAt: null, estimatedPortArrivalAt: null, actualPortArrivalAt: null, estimatedWarehouseArrivalAt: null, actualWarehouseArrivalAt: null }) vi.mocked(getContainerOverview).mockResolvedValue({ container: { id: '9001', bookingNo: 'BK-1', cabinetNo: 'CB-1', containerNo: 'MSCU-1', status: 'WAIT_LOADING', warehouseId: '8', warehouseName: '义乌仓', voyageNo: null, vesselName: null, version: 0 }, loadingPlanId: null, pda: null, documents: [], exception: { active: false, reason: null, markedBy: null, markedByName: null, markedAt: null }, exceptionHistory: [], timeline: [] }) @@ -246,12 +247,14 @@ describe('ContainersView', () => { it('allows first linking after preloading but only offers unlinking for wait-loading containers', () => { const source = readFileSync(resolve(process.cwd(), 'src/modules/container-loading/pages/ContainersView.vue'), 'utf8') - expect(source).toContain("!row.oceanBookingId && ['WAIT_LOADING','EDITING'].includes(row.planStatus)") - expect(source).toContain("row.oceanBookingId && row.planStatus === 'WAIT_LOADING'") + expect(source).toContain("row.sourceType === 'SIMULATION' && row.recordStatus === 'ACTIVE' && !row.oceanBookingId") + expect(source).toContain("row.sourceType === 'SIMULATION' && row.recordStatus === 'ACTIVE' && row.oceanBookingId") expect(source).toContain('已预排货但尚未进入 PDA、取证或单证阶段的模拟柜仍可首次关联') expect(source).toContain('柜次号与装柜明细保持不变') expect(source).toContain('只有尚未排货的空柜可以解除') - expect(source).toContain('linkContainerOceanBooking(target.id, { oceanBookingId: candidate.oceanBookingId, requirementId: candidate.requirementId, expectedVersion: target.version })') + expect(source).toContain('booking.containers') + expect(source).toContain('realContainerBookingId: container.containerBookingId') + expect(source).toContain('linkContainerOceanBooking(target.id, { realContainerBookingId: candidate.realContainerBookingId, expectedVersion: target.version })') expect(source).toContain('unlinkContainerOceanBooking(target.id, target.version, unlinkReason.value.trim())') }) }) diff --git a/src/modules/container-loading/pages/ContainersView.vue b/src/modules/container-loading/pages/ContainersView.vue index d738e47d..47ff5b94 100644 --- a/src/modules/container-loading/pages/ContainersView.vue +++ b/src/modules/container-loading/pages/ContainersView.vue @@ -8,7 +8,7 @@ import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam' import { listAllPlatformWarehouses, type PlatformWarehouseSummaryResource } from '@/modules/warehouses' import { listPlatformPartners, type PlatformPartnerResource } from '@/modules/master-data' import { listSuppliers, type Supplier } from '@/modules/suppliers' -import { getOceanBooking, listOceanBookings, listWarehouseLogisticsChannels, type OceanBookingDetail, type OceanBookingResource, type OceanLogisticsChannel } from '@/modules/ocean-bookings' +import { getOceanBooking, listOceanBookings, listWarehouseLogisticsChannels, type OceanBookingResource, type OceanLogisticsChannel } from '@/modules/ocean-bookings' import { uploadLoadingDeclaration } from '@/modules/loading-lists' import AdminPage from '@/shared/components/AdminPage.vue' import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue' @@ -90,7 +90,7 @@ const linkSaving = ref(false) const linkError = ref(null) const linkTarget = ref(null) const linkCandidateKey = ref('') -const linkCandidates = ref>([]) +const linkCandidates = ref>([]) const selectedTruckingSupplierId = ref('') const unlinkOpen = ref(false) const unlinkReason = ref('') @@ -365,20 +365,17 @@ async function openLink(source: object) { const bookings = (await Promise.all([loadBookingStatus('CONFIRMED'), loadBookingStatus('RELEASED')])).flat() .filter((booking) => booking.warehouseId === row.warehouseId && booking.channelCode === row.channelCode) const details = await Promise.all(bookings.map((booking) => getOceanBooking(booking.id))) - linkCandidates.value = details.flatMap((booking) => { - const allocated = new Set(booking.containerAllocations.filter((item) => item.containerBookingId).map((item) => item.requirementId)) - return booking.containerRequirements.filter((requirement) => requirement.id - && !allocated.has(requirement.id) - && requirement.containerType === row.containerType - && Number(requirement.capacityCbm) === Number(row.capacityCbm) - && Number(requirement.maxWeightKg) === Number(row.maxWeightKg)).map((requirement) => ({ - key: `${booking.id}:${requirement.id}`, - oceanBookingId: booking.id, - requirementId: requirement.id!, - detail: booking, - label: `${booking.bookingNo || '订舱号待补'} · ${requirement.containerType} · ${booking.customerName || '未关联客户'}`, - })) - }) + linkCandidates.value = details.flatMap((booking) => booking.containers + .filter((container) => container.warehouseId === row.warehouseId + && container.channelCode === row.channelCode + && container.containerType === row.containerType + && Number(container.capacityCbm) === Number(row.capacityCbm) + && Number(container.maxWeightKg) === Number(row.maxWeightKg)) + .map((container) => ({ + key: container.containerBookingId, + realContainerBookingId: container.containerBookingId, + label: `${booking.bookingNo || '订舱号待补'} · 集装箱号 ${container.containerNo} · 柜次号 ${container.cabinetNo} · ${container.containerType}`, + }))) } catch (error) { linkError.value = getApiErrorDetails(error, '真实订舱柜候选加载失败') } finally { linkLoading.value = false } } @@ -389,7 +386,7 @@ async function saveLink() { linkSaving.value = true linkError.value = null try { - await linkContainerOceanBooking(target.id, { oceanBookingId: candidate.oceanBookingId, requirementId: candidate.requirementId, expectedVersion: target.version }) + await linkContainerOceanBooking(target.id, { realContainerBookingId: candidate.realContainerBookingId, expectedVersion: target.version }) linkOpen.value = false ElMessage.success('已关联真实订舱柜,柜次号与装柜明细保持不变') await loadRows() @@ -600,7 +597,7 @@ onMounted(() => { void Promise.all([loadCustomers(), loadWarehouses(), loadBusin
- + diff --git a/src/modules/container-loading/types.ts b/src/modules/container-loading/types.ts index f37392ca..afb12fcb 100644 --- a/src/modules/container-loading/types.ts +++ b/src/modules/container-loading/types.ts @@ -6,6 +6,9 @@ export interface ContainerBookingResource { bookingNo: string containerNo: string | null cabinetNo: string + sourceType?: 'SIMULATION' | 'OCEAN_BOOKING' + recordStatus?: 'ACTIVE' | 'MERGED' + mergedIntoContainerBookingId?: string | null warehouseId: string warehouseName: string | null channelCode: string | null @@ -52,8 +55,7 @@ export interface ContainerSimulationCreateRequest { } export interface ContainerOceanBookingLinkRequest { - oceanBookingId: string - requirementId: string + realContainerBookingId: string expectedVersion: number } diff --git a/src/modules/finance/pages/FinanceCustomerAccountsView.test.ts b/src/modules/finance/pages/FinanceCustomerAccountsView.test.ts index 06872a45..c7f90bfe 100644 --- a/src/modules/finance/pages/FinanceCustomerAccountsView.test.ts +++ b/src/modules/finance/pages/FinanceCustomerAccountsView.test.ts @@ -49,9 +49,11 @@ describe('FinanceCustomerAccountsView delivery', () => { it('keeps term editing aligned with the account summary and nearby actions', () => { for (const marker of ['class="term-section"', 'class="term-editor"', '账期天数', '#footer', '保存账期']) expect(page).toContain(marker) expect(page).toContain('grid-template-columns:repeat(4,minmax(0,1fr))') - expect(page).toContain('grid-template-columns:minmax(180px,1fr) auto') + expect(page).toContain('grid-template-columns:120px minmax(0,220px)') + expect(page).toContain('justify-content:start') expect(page).toContain('class="surface-panel finance-account-detail__surface"') - expect(page).toContain('min-height:88px') + expect(page).toContain('min-height:72px') + expect(page).toContain('gap:0;padding:0 0 16px') expect(page).toContain('class="detail-table-scroll"') }) diff --git a/src/modules/finance/pages/FinanceCustomerAccountsView.vue b/src/modules/finance/pages/FinanceCustomerAccountsView.vue index aefa38b3..af43f284 100644 --- a/src/modules/finance/pages/FinanceCustomerAccountsView.vue +++ b/src/modules/finance/pages/FinanceCustomerAccountsView.vue @@ -228,11 +228,11 @@ watch(() => [route.name, String(route.params.customerId || '')], ([name, custome @@ -242,5 +242,5 @@ watch(() => [route.name, String(route.params.customerId || '')], ([name, custome diff --git a/src/modules/finance/pages/FinanceCustomerArapView.vue b/src/modules/finance/pages/FinanceCustomerArapView.vue index 918e1e12..4cf441e0 100644 --- a/src/modules/finance/pages/FinanceCustomerArapView.vue +++ b/src/modules/finance/pages/FinanceCustomerArapView.vue @@ -267,7 +267,7 @@ watch(() => [route.name, String(route.params.feeSourceId || '')], ([name, feeSou - +
@@ -291,8 +291,9 @@ watch(() => [route.name, String(route.params.feeSourceId || '')], ([name, feeSou .finance-arap-detail { width: 100%; min-width: 0; } .finance-arap-detail__surface { width: min(100%, 1600px); margin: 0 auto; overflow: hidden; } .finance-arap-detail__layout { width: 100%; margin: 0 auto; } -.finance-arap-detail__layout :deep(.detail-page-layout__header) { min-height: 88px; align-items: center; padding: 20px 24px; border-bottom: 1px solid var(--cc-border, #e3e8ef); } -.finance-arap-detail__layout :deep(.detail-page-layout__summary) { padding: 20px 24px 0; } +.finance-arap-detail__layout :deep(.detail-page-layout) { gap: 0; } +.finance-arap-detail__layout :deep(.detail-page-layout__header) { min-height: 72px; align-items: center; padding: 16px 24px; border-bottom: 1px solid var(--cc-border, #e3e8ef); } +.finance-arap-detail__layout :deep(.detail-page-layout__summary) { padding: 16px 24px 0; } .finance-arap-detail__layout :deep(.detail-page-layout__content) { padding: 0 24px 20px; } .row-actions { display: flex; align-items: center; gap: 4px; white-space: nowrap; } .row-actions .el-button { margin-left: 0; } @@ -303,22 +304,24 @@ watch(() => [route.name, String(route.params.feeSourceId || '')], ([name, feeSou .detail-heading p { overflow: hidden; margin: 4px 0 0; color: var(--cc-muted); text-overflow: ellipsis; white-space: nowrap; } .detail-actions { margin-left: auto; } .muted-action { color: var(--cc-muted); } -.arap-metrics { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; } -.arap-metric { min-width: 0; padding: 16px 18px; border: 1px solid var(--cc-border, #e3e8ef); border-radius: 6px; background: #fff; } +.arap-metrics { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0; padding: 0 0 16px; border-bottom: 1px solid var(--cc-border, #e3e8ef); } +.arap-metric { min-width: 0; padding: 0 20px; } +.arap-metric:first-child { padding-left: 0; } +.arap-metric + .arap-metric { border-left: 1px solid var(--cc-border, #e3e8ef); } .arap-metric span { display: block; color: var(--cc-muted); font-size: 12px; } -.arap-metric strong { display: inline-block; margin-top: 9px; color: var(--cc-text); font-size: 23px; line-height: 1.1; font-variant-numeric: tabular-nums; } +.arap-metric strong { display: inline-block; margin-top: 7px; color: var(--cc-text); font-size: 20px; line-height: 1.1; font-variant-numeric: tabular-nums; } .arap-metric small { margin-left: 6px; color: var(--cc-muted); font-size: 11px; } -.arap-metric.is-amount { border-color: #c7d2fe; background: #f7f8ff; } .arap-metric.is-amount strong { color: #4f46e5; } -.arap-detail-grid { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(360px, .65fr); gap: 14px; } -.arap-information,.arap-settlement,.operation-log { padding: 20px; border: 1px solid var(--cc-border, #e3e8ef); background: #fff; } +.arap-detail-grid { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(360px, .65fr); gap: 24px; border-bottom: 1px solid var(--cc-border, #e3e8ef); } +.arap-information,.arap-settlement,.operation-log { padding: 16px 0; } +.arap-settlement { padding-left: 24px; border-left: 1px solid var(--cc-border, #e3e8ef); } .operation-log { min-height: 150px; } :deep(.arap-detail .el-descriptions__content) { min-width: 0; overflow-wrap: anywhere; } :deep(.arap-detail .el-descriptions__label) { width: 112px; color: var(--cc-muted); } .operation-log :deep(.el-empty) { padding: 12px 0 4px; } .operation-log :deep(.el-empty__image) { display: none; } .operation-log small { display: block; margin-top: 4px; color: var(--cc-muted); } -@media (max-width: 1080px) { .finance-arap-detail__surface { width: 100%; } .arap-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); } .arap-detail-grid { grid-template-columns: 1fr; } } -@media (max-width: 720px) { .detail-heading { align-items: stretch; flex-direction: column; } .detail-actions { margin-left: 0; } .arap-metrics { grid-template-columns: 1fr; } :deep(.arap-detail .el-descriptions__body .el-descriptions__table) { table-layout: fixed; } } +@media (max-width: 1080px) { .finance-arap-detail__surface { width: 100%; } .arap-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); } .arap-metric:nth-child(3) { padding-left: 0; border-left: 0; } .arap-detail-grid { grid-template-columns: 1fr; gap: 0; } .arap-settlement { padding-left: 0; border-left: 0; border-top: 1px solid var(--cc-border, #e3e8ef); } } +@media (max-width: 720px) { .detail-heading { align-items: stretch; flex-direction: column; } .detail-actions { margin-left: 0; } .arap-metrics { grid-template-columns: 1fr; gap: 12px; } .arap-metric,.arap-metric:first-child,.arap-metric:nth-child(3) { padding-left: 0; } .arap-metric + .arap-metric { border-left: 0; } :deep(.arap-detail .el-descriptions__body .el-descriptions__table) { table-layout: fixed; } } @media (max-width: 480px) { :deep(.arap-detail .el-descriptions__cell) { display: block; width: 100%; } } diff --git a/src/modules/finance/pages/FinanceCustomerStatementsView.vue b/src/modules/finance/pages/FinanceCustomerStatementsView.vue index 2e965af1..c181886c 100644 --- a/src/modules/finance/pages/FinanceCustomerStatementsView.vue +++ b/src/modules/finance/pages/FinanceCustomerStatementsView.vue @@ -457,7 +457,7 @@ onMounted(() => { if (canListCustomers.value) void loadCustomers(); void load()
客户对账单

{{ detail.billNo }}

{{ customerDisplayName(detail.customerId, detail.customerName) }} · {{ detail.periodStart || '—' }} 至 {{ detail.periodEnd || '—' }}

{{ financeStatusLabel(detail.status) }} - + {{ detail.totalAmount.toFixed(2) }} CNY {{ detail.paidAmount.toFixed(2) }} CNY @@ -473,7 +473,7 @@ onMounted(() => { if (canListCustomers.value) void loadCustomers(); void load() - +
@@ -491,7 +491,7 @@ onMounted(() => { if (canListCustomers.value) void loadCustomers(); void load() - +
@@ -523,15 +523,16 @@ onMounted(() => { if (canListCustomers.value) void loadCustomers(); void load() .finance-statements-page { min-width: 0; } :deep(.el-select), :deep(.el-date-editor) { width: 100%; } .statement-detail-panel { min-width: 0; overflow: hidden; } -.statement-detail-header { min-height: 56px; display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 0 24px; border-bottom: 1px solid var(--cc-border-subtle); } +.statement-detail-panel :deep(.detail-page-layout) { gap: 0; } +.statement-detail-header { min-height: 48px; display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 0 24px; border-bottom: 1px solid var(--cc-border-subtle); } .statement-detail-context { display: grid; min-width: 0; gap: 3px; } .statement-detail-context span { color: var(--cc-text-secondary); font-size: 11px; } .statement-detail-context strong { overflow: hidden; color: var(--cc-text-primary); font-size: 14px; text-overflow: ellipsis; white-space: nowrap; } -.statement-detail-head { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: start; gap: 12px; padding: 20px 24px; border-bottom: 1px solid var(--cc-border-subtle); } +.statement-detail-head { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: start; gap: 12px; padding: 16px 24px; border-bottom: 1px solid var(--cc-border-subtle); } .statement-detail-head > div > span { color: var(--cc-primary); font-size: 11px; font-weight: 700; } -.statement-detail-head h2 { margin: 2px 0 0; font-size: 17px; line-height: 1.4; overflow-wrap: anywhere; } +.statement-detail-head h2 { margin: 2px 0 0; font-size: 16px; line-height: 1.4; overflow-wrap: anywhere; } .statement-detail-head p { margin: 5px 0 0; color: var(--cc-text-secondary); font-size: 12px; line-height: 1.6; overflow-wrap: anywhere; } -.statement-summary-section { padding: 20px 24px; border-bottom: 1px solid var(--cc-border-subtle); } +.statement-summary-section { padding: 16px 24px; border-bottom: 1px solid var(--cc-border-subtle); } .statement-outstanding { color: var(--el-color-warning-dark-2); } .statement-detail-tabs { min-width: 0; } .statement-detail-table { min-width: 0; } diff --git a/src/modules/finance/pages/FinanceFormalSettlementLayout.test.ts b/src/modules/finance/pages/FinanceFormalSettlementLayout.test.ts new file mode 100644 index 00000000..b90fe44f --- /dev/null +++ b/src/modules/finance/pages/FinanceFormalSettlementLayout.test.ts @@ -0,0 +1,41 @@ +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' +import { describe, expect, it } from 'vitest' + +const source = readFileSync(resolve(process.cwd(), 'src/modules/finance/pages/FinanceFormalSettlementPage.vue'), 'utf8') + +describe('Finance formal settlement desktop presentation', () => { + it('keeps settlement totals and list actions in one compact toolbar', () => { + expect(source).toContain('
') + expect(source).toContain('
') + expect(source).toContain('
') + expect(source).toContain('.settlement-list-toolbar { display: flex; min-height: 54px;') + expect(source).not.toContain('