From 22c755b02a180b645d9eff01dce744915ba580cb Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 14 Sep 2026 17:22:08 +0800 Subject: [PATCH 01/24] fix: scope smart loading recommendation requests --- .../api/platformContainerLoading.test.ts | 20 +++++++++++++++++++ .../api/platformContainerLoading.ts | 16 +++++++++++++-- .../pages/GlobalSmartLoadingView.test.ts | 4 ++-- .../pages/GlobalSmartLoadingView.vue | 8 ++++++-- 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/modules/container-loading/api/platformContainerLoading.test.ts b/src/modules/container-loading/api/platformContainerLoading.test.ts index 812ad0ff..eca4898c 100644 --- a/src/modules/container-loading/api/platformContainerLoading.test.ts +++ b/src/modules/container-loading/api/platformContainerLoading.test.ts @@ -5,6 +5,7 @@ import { http } from '@/shared/api/http' import { adjustSmartLoadingItem, assignPendingReviewSmartLoadingOrder, assignSmartLoadingGroup, assignSmartLoadingItem, assignSmartLoadingPallet, getContainer, listContainers, listSmartLoadingCandidates, listSmartLoadingWorkbenchContainers, removeAllUnpickedSmartLoadingItems, removeSmartLoadingGroup, removeSmartLoadingItem, removeSmartLoadingPallet, + recommendSmartLoading, getContainerClearance, downloadContainerClearance, uploadContainerClearance, completeContainerClearance, getContainerOverview, updateContainerPickupCode, markContainerException, cancelContainerException, uploadContainerDocument, downloadContainerDocument, downloadContainerPdaPhoto, getCustomerContainerTransitSummary, @@ -131,6 +132,25 @@ describe('platform container loading API', () => { ) }) + it('limits smart recommendation to the current customer and inbound filters', async () => { + vi.mocked(http.post).mockResolvedValue({ code: '200', data: plan }) + + await recommendSmartLoading('10', 5, { + customerId: '9007199254740998', + inboundCode: ' EXXHWK2609140162 ', + }) + + expect(http.post).toHaveBeenCalledWith( + '/platform/v1/first-leg/smart-loading/plans/10/recommend', + expect.objectContaining({ + expectedVersion: 5, + customerId: '9007199254740998', + inboundCode: 'EXXHWK2609140162', + operationId: expect.stringContaining('recommend-'), + }), + ) + }) + it('removes a sealed pallet through the whole-pallet command', async () => { vi.mocked(http.delete).mockResolvedValue({ code: '200', data: plan }) diff --git a/src/modules/container-loading/api/platformContainerLoading.ts b/src/modules/container-loading/api/platformContainerLoading.ts index c5c163ca..0cde8afb 100644 --- a/src/modules/container-loading/api/platformContainerLoading.ts +++ b/src/modules/container-loading/api/platformContainerLoading.ts @@ -380,8 +380,20 @@ export async function assignSmartLoadingGroup(planId: string, inboundId: string, ) return plan(unwrapApiResponse(response)) } -export async function recommendSmartLoading(planId: string, expectedVersion: number) { - const response = await http.post, ApiEnvelope>(`${smartLoadingPath}/plans/${encodeURIComponent(planId)}/recommend`, { expectedVersion, operationId: operationId('recommend') }) +export async function recommendSmartLoading( + planId: string, + expectedVersion: number, + scope: { customerId?: string; inboundCode?: string } = {}, +) { + const response = await http.post, ApiEnvelope>( + `${smartLoadingPath}/plans/${encodeURIComponent(planId)}/recommend`, + { + expectedVersion, + operationId: operationId('recommend'), + ...(scope.customerId ? { customerId: scope.customerId } : {}), + ...(scope.inboundCode?.trim() ? { inboundCode: scope.inboundCode.trim() } : {}), + }, + ) return plan(unwrapApiResponse(response)) } diff --git a/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts b/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts index ac393792..8750bc6b 100644 --- a/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts +++ b/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts @@ -760,8 +760,8 @@ describe('GlobalSmartLoadingView', () => { await button!.trigger('click') await flushPromises() - expect(recommendSmartLoading).toHaveBeenCalledWith(plan.id, 2) - expect(recommendSmartLoading).toHaveBeenCalledWith(plan.id, 3) + expect(recommendSmartLoading).toHaveBeenCalledWith(plan.id, 2, { customerId: '', inboundCode: '' }) + expect(recommendSmartLoading).toHaveBeenCalledWith(plan.id, 3, { customerId: '', inboundCode: '' }) expect(getSmartLoadingPlanByBooking).toHaveBeenCalledTimes(2) expect(warningMessage).not.toHaveBeenCalled() expect(wrapper.text()).not.toContain('装柜操作失败') diff --git a/src/modules/container-loading/pages/GlobalSmartLoadingView.vue b/src/modules/container-loading/pages/GlobalSmartLoadingView.vue index 3e05c6a7..400332ed 100644 --- a/src/modules/container-loading/pages/GlobalSmartLoadingView.vue +++ b/src/modules/container-loading/pages/GlobalSmartLoadingView.vue @@ -549,15 +549,19 @@ function candidateBlocked(candidate: SmartLoadingCandidateRow) { } async function recommend() { if (!plan.value) return + const scope = { + customerId: String(appliedQuery.value.customerId || ''), + inboundCode: String(appliedQuery.value.inboundCode || '').trim(), + } await mutate(async () => { try { - acceptPlan(await recommendSmartLoading(plan.value!.id, plan.value!.version)) + acceptPlan(await recommendSmartLoading(plan.value!.id, plan.value!.version, scope)) } catch (error) { const details = getApiErrorDetails(error, '智能推荐失败') if (details.code !== 'A0409' || !/(版本|并发|已被修改|已变化)/.test(details.message)) throw error await loadSelectedPlan() if (!plan.value) throw error - acceptPlan(await recommendSmartLoading(plan.value.id, plan.value.version)) + acceptPlan(await recommendSmartLoading(plan.value.id, plan.value.version, scope)) } await loadCandidateData() ElMessage.success('智能推荐已生成') From 7e1b9b4cee9de25b3a04ae278f4bf985eff1fd23 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 14 Sep 2026 17:52:00 +0800 Subject: [PATCH 02/24] feat: add desktop dropship pallet printing --- AGENTS.md | 1 + .../core/DropshipPalletPrintPanel.test.ts | 23 ++++ .../core/DropshipPalletPrintPanel.vue | 126 ++++++++++++++++++ src/modules/inbounds/core/api.test.ts | 17 ++- src/modules/inbounds/core/api.ts | 7 + src/modules/inbounds/index.ts | 1 + .../unified-orders/api/unifiedOrders.ts | 1 + .../pages/UnifiedOrdersView.test.ts | 8 ++ .../pages/UnifiedOrdersView.vue | 18 ++- 9 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 src/modules/inbounds/core/DropshipPalletPrintPanel.test.ts create mode 100644 src/modules/inbounds/core/DropshipPalletPrintPanel.vue diff --git a/AGENTS.md b/AGENTS.md index 4d69de1e..ba52b116 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -51,6 +51,7 @@ | 客户原始申报资料编辑与渠道变更 | `modules/unified-orders/components/OrderDeclarationEditorDialog.vue`、`api/unifiedOrders.ts`、`pages/UnifiedOrdersView.vue` | 使用订单版本和货物 `itemNo` 整体提交,不允许增删货物行;只编辑客户申报快照,不覆盖仓库/PDA 实测、测量审核或计费事实。渠道变化需二次确认并由 Backend `order/` 联查 `firstleg/` 的分柜、同柜、装柜与重新计价门禁;国内收货仓或中转仓变化必须重新下单 | | 入库扣货、解除与凭证历史 | `modules/unified-orders/pages/UnifiedOrdersView.vue`、`modules/inbounds/core/api.ts`、`types.ts` | 页面只消费服务端 `holdFlag/currentHoldReason/canHold/canRelease/actionBlockReason/history`;扣货与解除提交当前入库版本、幂等键、必填原因和可选凭证,凭证下载只走 Firstleg 授权入口。Backend `firstleg/` 的 `InboundHoldService`、`InboundHoldGuard`、`InboundHoldEvidenceService`,文件归 `file/` | | 入库、测量、称重库位状态、客户箱规策略与计价主数据 | 测量弹框/工作区 `modules/inbounds/measurement/InboundMeasurementDialog.vue`、`InboundMeasurementPanel.vue`,称重 `modules/inbounds/weight/components/WeightOcrPanel.vue`;主数据 `modules/fba-pricing/`、`modules/dimension-config/`、`modules/customers/components/CustomerBillingRulesPanel.vue`、`modules/fee-items/` | 客户固定四档箱规,默认计算方式为 `ADD/MULTIPLY`;测量行可选实际尺寸、箱规尺寸策略或客户默认尺寸系数,保存后以 Backend `firstleg/` 返回的外箱尺寸、体积重和计费基数为准,不在页面独立重算。费用确认的实际箱数、毛重和体积优先汇总当前 `actualMeasurementRows`,不得用可能滞后的工作区顶层合计覆盖明细。称重页在每张图片卡片只展示服务端 `locationCode/slotStatus/sharedOverflow`,不渲染或自行推导整票预留引导;删除图片须提示槽位仍归本票保留,不得按图片数组下标推导托盘或库位 | +| 一件代发托盘打印 | `modules/unified-orders/pages/UnifiedOrdersView.vue` 的“托盘打印”页签、`modules/inbounds/core/DropshipPalletPrintPanel.vue`、`core/api.ts` | 仅一件代发订单展示;测量审核预生成托盘码后由 Admin 电脑端打印,支持本票全部托盘合并打印和单托补打。PDA 只扫码组托,不承担 PDF 下载或打印机连接 | | 入库单扣货、解除、凭证与历史 | `modules/inbounds/pages/InboundOrdersView.vue`、详情工作区、`modules/inbounds/core/api.ts` | 列表展示服务端 `holdFlag/currentHoldReason` 并支持筛选;详情只按 `canHold/canRelease/actionBlockReason` 开放操作。扣货与解除提交当前 `version`、原因和独立幂等键,可选凭证先按明确客户与入库单上传;历史文件只调用专用下载入口,不把上传成功但未提交命令的文件冒充扣货证据 | | 客户专属价格簿、柜级阶梯运费与受控调价 | 列表/上传 `modules/fba-pricing/carrier-rates/pages/CarrierRatesView.vue`,版本详情/仓库映射/确认 `modules/fba-pricing/carrier-rates/pages/CarrierRateDetailView.vue`,路由 `modules/fba-pricing/routes.ts`;`modules/inbounds/fee/`、`modules/unified-orders/components/InboundFeeDetailManager.vue` | 列表与上传后按 `versionId` 进入 `fba-carrier-rate-detail` 独立详情页,价格行只读;回归入口 `modules/fba-pricing/carrier-rates/pages/CarrierRatesView.test.ts`。价格簿页面显式选择公共报价或具体客户;列表、上传、确认只作用于该 `customerId`,不得混合版本。计价优先消费客户专属 ACTIVE 版本,无专属版本才回退公共 ACTIVE;柜级阶梯金额和各客户分摊只展示服务端结果。调整计费量只能在服务端返回的上下 10% 范围内并提交当前费用版本、原因与二次确认;私人仓尾程派送费使用稳定 `PRIVATE_LAST_MILE_DELIVERY` 代码且只对 `PRIVATE` 目的地开放;一件代发须在预装确认前完成试算,计价后保持服务端 `WAIT_PALLETIZE` 状态 | | 财务、客户资金账户与柜级公共费用分摊 | `modules/finance/`,继续读该目录 `AGENTS.md`;客户账户 `pages/FinanceCustomerAccountsView.vue`,柜费 `modules/loading-lists/components/ContainerFeesPanel.vue`、`api/containerFees.ts` | `finance/`、`firstleg/`、`file/`、`docs/财务管理/`;实际、冻结、可用余额、授信与流水只消费 Finance 权威结果,充值审核和手工充扣携带版本与幂等键。跨客户公共柜费只按服务端柜内客户/订单事实分摊,金额必须守恒,创建与审核、驳回、调整、作废均整组原子处理 | diff --git a/src/modules/inbounds/core/DropshipPalletPrintPanel.test.ts b/src/modules/inbounds/core/DropshipPalletPrintPanel.test.ts new file mode 100644 index 00000000..9030ef22 --- /dev/null +++ b/src/modules/inbounds/core/DropshipPalletPrintPanel.test.ts @@ -0,0 +1,23 @@ +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' +import { describe, expect, it } from 'vitest' + +const source = readFileSync(resolve(process.cwd(), 'src/modules/inbounds/core/DropshipPalletPrintPanel.vue'), 'utf8') + +describe('DropshipPalletPrintPanel', () => { + it('provides desktop batch printing and single-pallet reprinting', () => { + expect(source).toContain('一件代发托盘打印') + expect(source).toContain('打印全部托盘') + expect(source).toContain('单托补打') + expect(source).toContain('downloadAllDropshipPalletLabels') + expect(source).toContain('downloadDropshipPalletLabel') + expect(source).toContain('label="操作" width="120" fixed="left"') + }) + + it('explains the computer-first printing flow and covers loading, empty and error states', () => { + expect(source).toContain('在电脑端打印并贴到空托盘,再交由 PDA 扫码组托') + expect(source).toContain('v-loading="loading"') + expect(source).toContain('测量审核通过并生成托盘码后,可在这里打印') + expect(source).toContain('托盘数据暂时无法加载') + }) +}) diff --git a/src/modules/inbounds/core/DropshipPalletPrintPanel.vue b/src/modules/inbounds/core/DropshipPalletPrintPanel.vue new file mode 100644 index 00000000..52f4b8e5 --- /dev/null +++ b/src/modules/inbounds/core/DropshipPalletPrintPanel.vue @@ -0,0 +1,126 @@ + + + + + diff --git a/src/modules/inbounds/core/api.test.ts b/src/modules/inbounds/core/api.test.ts index 2c817de1..3fce2fed 100644 --- a/src/modules/inbounds/core/api.test.ts +++ b/src/modules/inbounds/core/api.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { http } from '@/shared/api/http' -import { downloadInboundCartonLabels, downloadInboundCartonLabelsBatch, downloadPlatformInboundHoldEvidence, getPlatformInbound, getPlatformInboundHold, holdPlatformInbound, listPlatformInboundDropshipPallets, listPlatformInbounds, releasePlatformInbound, uploadPlatformInboundHoldEvidence } from './api' +import { downloadAllDropshipPalletLabels, downloadDropshipPalletLabel, downloadInboundCartonLabels, downloadInboundCartonLabelsBatch, downloadPlatformInboundHoldEvidence, getPlatformInbound, getPlatformInboundHold, holdPlatformInbound, listPlatformInboundDropshipPallets, listPlatformInbounds, releasePlatformInbound, uploadPlatformInboundHoldEvidence } from './api' vi.mock('@/shared/api/http', () => ({ ApiResponseError: class ApiResponseError extends Error { @@ -87,6 +87,21 @@ describe('platform inbound query API', () => { expect(http.get).toHaveBeenCalledWith('/platform/v1/first-leg/pda-dropship/inbound-orders/19001/pallets', { params: { accountId: '10001' } }) }) + it('downloads one or all inbound-scoped dropship pallet documents for desktop printing', async () => { + const pdf = new Blob(['pdf'], { type: 'application/pdf' }) + vi.mocked(http.get).mockResolvedValue(pdf) + + await expect(downloadDropshipPalletLabel('10001', '19001', '21001')).resolves.toBe(pdf) + await expect(downloadAllDropshipPalletLabels('10001', '19001')).resolves.toBe(pdf) + + expect(http.get).toHaveBeenNthCalledWith(1, + '/platform/v1/first-leg/pda-dropship/inbound-orders/19001/pallets/21001/print', + { params: { accountId: '10001' }, responseType: 'blob', timeout: 60_000 }) + expect(http.get).toHaveBeenNthCalledWith(2, + '/platform/v1/first-leg/pda-dropship/inbound-orders/19001/pallets/print', + { params: { accountId: '10001' }, responseType: 'blob', timeout: 60_000 }) + }) + it('passes the implemented order, process, measurement and fee review filters without renaming them', async () => { vi.mocked(http.get).mockResolvedValue({ code: '200', message: 'success', data: { records: [], page: 1, size: 20, total: 0, pages: 0 }, diff --git a/src/modules/inbounds/core/api.ts b/src/modules/inbounds/core/api.ts index e72c6eda..f5e37f27 100644 --- a/src/modules/inbounds/core/api.ts +++ b/src/modules/inbounds/core/api.ts @@ -291,3 +291,10 @@ export async function downloadDropshipPalletLabel(tenantId: string, inboundId: s { params: { accountId: tenantId }, responseType: 'blob', timeout: 60_000 }, ) } + +export async function downloadAllDropshipPalletLabels(tenantId: string, inboundId: string) { + return http.get( + `/platform/v1/first-leg/pda-dropship/inbound-orders/${encodeURIComponent(inboundId)}/pallets/print`, + { params: { accountId: tenantId }, responseType: 'blob', timeout: 60_000 }, + ) +} diff --git a/src/modules/inbounds/index.ts b/src/modules/inbounds/index.ts index ba2c231d..1aadb9cf 100644 --- a/src/modules/inbounds/index.ts +++ b/src/modules/inbounds/index.ts @@ -1,5 +1,6 @@ export { getInboundMeasurement, saveInboundMeasurementDraft, updateMeasurementCalculationStrategy, updateMeasurementOuterDimensions } from './measurement/api' export { createInboundHoldIdempotencyKey, downloadInboundCartonLabelsBatch, downloadPlatformInboundHoldEvidence, getPlatformInboundHold, holdPlatformInbound, releasePlatformInbound, uploadPlatformInboundHoldEvidence } from './core/api' +export { default as DropshipPalletPrintPanel } from './core/DropshipPalletPrintPanel.vue' export { default as InboundMeasurementPanel } from './measurement/InboundMeasurementPanel.vue' export { default as InboundMeasurementDialog } from './measurement/InboundMeasurementDialog.vue' export { default as MeasurementEvidenceThumbnail } from './measurement/components/MeasurementEvidenceThumbnail.vue' diff --git a/src/modules/unified-orders/api/unifiedOrders.ts b/src/modules/unified-orders/api/unifiedOrders.ts index fc8c4162..3a8565bb 100644 --- a/src/modules/unified-orders/api/unifiedOrders.ts +++ b/src/modules/unified-orders/api/unifiedOrders.ts @@ -125,6 +125,7 @@ export interface UnifiedOrderResource { currentHoldReason: string | null companyName?: string | null businessType?: string | null + fulfillmentType?: string | null pickupMethod?: string | null requestedPickupAt: string | null pickupAt?: string | null diff --git a/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts b/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts index 69e84246..10025636 100644 --- a/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts +++ b/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts @@ -7,6 +7,14 @@ const source = readFileSync(resolve(process.cwd(), 'src/modules/unified-orders/p const routeSource = readFileSync(resolve(process.cwd(), 'src/modules/unified-orders/routes.ts'), 'utf8').replace(/\r\n/g, '\n') describe('UnifiedOrdersView contract', () => { + it('exposes desktop pallet printing only for one-click dropship orders', () => { + expect(source).toContain("detail.value?.summary.fulfillmentType === 'ONE_CLICK_DROPSHIP'") + expect(source).toContain("{ key: 'dropship-print', label: '托盘打印', icon: '🖨️' }") + expect(source).toContain("activeDetailTab === 'dropship-print'") + expect(source).toContain(' { const openDialogSource = source.slice(source.indexOf('async function openFeeConfirmDialog()'), source.indexOf('async function executeConfirmAndSendFee()')) const sendFeeSource = source.slice(source.indexOf('async function executeConfirmAndSendFee()'), source.indexOf('function handleOrderSelectionChange')) diff --git a/src/modules/unified-orders/pages/UnifiedOrdersView.vue b/src/modules/unified-orders/pages/UnifiedOrdersView.vue index 7fea335a..3638b688 100644 --- a/src/modules/unified-orders/pages/UnifiedOrdersView.vue +++ b/src/modules/unified-orders/pages/UnifiedOrdersView.vue @@ -78,6 +78,7 @@ import { InboundFeePanel, InboundMeasurementDialog, MeasurementEvidenceThumbnail, + DropshipPalletPrintPanel, sendInboundFee, type InboundFeeSnapshot, type InboundMeasurementDetail, @@ -324,15 +325,17 @@ const canReleaseInbound = computed(() => Boolean( && holdDetail.value?.canRelease, )) -const detailTabs = [ +const isDropshipOrder = computed(() => detail.value?.summary.fulfillmentType === 'ONE_CLICK_DROPSHIP') +const detailTabs = computed(() => [ { key: 'basic', label: '基本信息', icon: '📋' }, { key: 'cargo', label: '货品信息', icon: '📦' }, { key: 'fees', label: '费用信息', icon: '💰' }, { key: 'history', label: '状态历史', icon: '📝' }, { key: 'measurement', label: '测量数据', icon: '📐' }, + ...(isDropshipOrder.value ? [{ key: 'dropship-print', label: '托盘打印', icon: '🖨️' }] : []), { key: 'other', label: '其他', icon: '🗂️' }, { key: 'fulfillment', label: '拆分履约', icon: '🧩' }, -] +]) const customerMeasurementColumns = [ { key: 'itemNo', label: '#' }, { key: 'lengthCm', label: '箱规长(cm)' }, { key: 'widthCm', label: '箱规宽(cm)' }, @@ -2396,6 +2399,17 @@ onBeforeUnmount(() => { + + - + diff --git a/src/modules/customers/CustomerOverseasStorageFeePanel.vue b/src/modules/customers/CustomerOverseasStorageFeePanel.vue index 782f9a42..2f5744c7 100644 --- a/src/modules/customers/CustomerOverseasStorageFeePanel.vue +++ b/src/modules/customers/CustomerOverseasStorageFeePanel.vue @@ -81,15 +81,15 @@ onMounted(load)
当前客户暂无海外仓费用配置
-

{{ groupIndex === 0 ? '仓租(CBM/天)' : '退货仓储费(托/天)' }} · {{ group.length }}档

新增阶梯
操作开始天数结束天数价格
删除以上
+

{{ groupIndex === 0 ? '仓租(CBM/天)' : '退货仓储费(托/天)' }} · {{ group.length }}档

新增阶梯
操作开始天数结束天数价格
删除以上
diff --git a/src/modules/domestic-containers/pages/DomesticContainersView.test.ts b/src/modules/domestic-containers/pages/DomesticContainersView.test.ts index d0488a20..fadb5e78 100644 --- a/src/modules/domestic-containers/pages/DomesticContainersView.test.ts +++ b/src/modules/domestic-containers/pages/DomesticContainersView.test.ts @@ -15,7 +15,7 @@ describe('DomesticContainersView', () => { for (const fact of ['summary.inboundBoxes', 'summary.outboundBoxes', 'summary.availableBoxes', 'summary.inboundVolumeCbm']) expect(source).toContain(fact) expect(source).toContain("import './domesticContainersSummary.css'") expect(summaryStyles).toContain('display: flex; flex-wrap: wrap') - expect(summaryStyles).toContain('font-size: 14px') + expect(summaryStyles).toContain('font-size: var(--admin-font-section, 14px)') expect(summaryStyles).not.toContain('position: absolute') }) it('does not offer an inventory detail action before an inbound has formed inventory', () => { expect(source).toContain("canViewInventory(row as ContainerOrder | InventoryRecord)"); expect(source).toContain("row.status === 'COMPLETED'") }) diff --git a/src/modules/domestic-containers/pages/DomesticContainersView.vue b/src/modules/domestic-containers/pages/DomesticContainersView.vue index 312f024e..b375a394 100644 --- a/src/modules/domestic-containers/pages/DomesticContainersView.vue +++ b/src/modules/domestic-containers/pages/DomesticContainersView.vue @@ -2,7 +2,7 @@ import '@/styles/adminDensity.css' import { computed, onMounted, reactive, ref, watch } from 'vue' import { ElMessage, type FormInstance, type FormRules } from 'element-plus' -import { Eye, Plus } from '@lucide/vue' +import { Plus } from '@lucide/vue' import { useRouter } from 'vue-router' import AdminPage from '@/shared/components/AdminPage.vue' import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue' @@ -74,7 +74,7 @@ watch([page, pageSize], load); onMounted(async () => { await Promise.all([loadWa - + diff --git a/src/modules/domestic-containers/pages/domesticContainersSummary.css b/src/modules/domestic-containers/pages/domesticContainersSummary.css index 82421555..0b979554 100644 --- a/src/modules/domestic-containers/pages/domesticContainersSummary.css +++ b/src/modules/domestic-containers/pages/domesticContainersSummary.css @@ -1,9 +1,9 @@ .domestic-containers-page .table-panel .inventory-summary { - display: flex; flex-wrap: wrap; gap: 6px 28px; margin: 0; - padding: 8px 12px; border: 0; border-bottom: 1px solid var(--cc-border); background: #fff; + display: flex; flex-wrap: wrap; gap: var(--admin-space-2, 6px) 28px; margin: 0; + padding: var(--admin-space-3, 8px) var(--admin-space-5, 12px); border: 0; border-bottom: 1px solid var(--cc-border); background: var(--admin-surface, #fff); } .domestic-containers-page .table-panel .inventory-summary > div { - display: flex; align-items: baseline; min-width: 0; gap: 8px; padding: 0; border: 0; + display: flex; align-items: baseline; min-width: 0; gap: var(--admin-space-3, 8px); padding: 0; border: 0; } -.domestic-containers-page .table-panel .inventory-summary span { color: var(--cc-muted); font-size: 12px; } -.domestic-containers-page .table-panel .inventory-summary strong { font-size: 14px; font-weight: 600; } +.domestic-containers-page .table-panel .inventory-summary span { color: var(--cc-muted); font-size: var(--admin-font-caption, 12px); } +.domestic-containers-page .table-panel .inventory-summary strong { font-size: var(--admin-font-section, 14px); font-weight: 600; } diff --git a/src/modules/inbounds/components/InboundTaskList.vue b/src/modules/inbounds/components/InboundTaskList.vue index b4612a97..b2bfce49 100644 --- a/src/modules/inbounds/components/InboundTaskList.vue +++ b/src/modules/inbounds/components/InboundTaskList.vue @@ -4,7 +4,7 @@ import '../measurement/measurementDensity.css' import { computed, onActivated, onBeforeUnmount, onMounted, ref, watch } from 'vue' import { useRouter } from 'vue-router' import { ElMessage, ElMessageBox } from 'element-plus' -import { CircleCheckBig, Eye, Ruler, SearchCheck } from '@lucide/vue' +import { CircleCheckBig } from '@lucide/vue' import { useAccessPreviewStore } from '@/core/access/accessPreview' import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam' import { listPlatformWarehouses } from '@/modules/warehouses' @@ -374,14 +374,12 @@ onBeforeUnmount(() => { disposed = true; requestSerial += 1; optionsRequestSeria diff --git a/src/modules/loading-lists/pages/PreloadPlanDetailView.test.ts b/src/modules/loading-lists/pages/PreloadPlanDetailView.test.ts index 3d0bbc0c..9904e3c2 100644 --- a/src/modules/loading-lists/pages/PreloadPlanDetailView.test.ts +++ b/src/modules/loading-lists/pages/PreloadPlanDetailView.test.ts @@ -10,7 +10,7 @@ describe('PreloadPlanDetailView prototype contract', () => { expect(source).toContain('') expect(source).toContain('@back="goBack"') expect(source).toContain("router.push({ name: 'loading-plans' })") - expect(source).toContain('@media (min-width: 1920px) { .preload-detail-page { width: 100%; max-width: none; gap: 12px; } }') + expect(source).toContain('@media (min-width: 1920px) { .preload-detail-page { width: 100%; max-width: none; gap: var(--admin-space-5, 12px); } }') expect(source).not.toContain('AppDrawer') }) diff --git a/src/modules/loading-lists/pages/PreloadPlanDetailView.vue b/src/modules/loading-lists/pages/PreloadPlanDetailView.vue index a1240fd2..18afb1d9 100644 --- a/src/modules/loading-lists/pages/PreloadPlanDetailView.vue +++ b/src/modules/loading-lists/pages/PreloadPlanDetailView.vue @@ -290,13 +290,13 @@ watch([planId, warehouseId], () => void loadDetail()) diff --git a/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts b/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts index 10025636..1b2d4df4 100644 --- a/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts +++ b/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts @@ -6,6 +6,8 @@ import { unifiedOrderStatusOptions } from '../api/unifiedOrders' const source = readFileSync(resolve(process.cwd(), 'src/modules/unified-orders/pages/UnifiedOrdersView.vue'), 'utf8').replace(/\r\n/g, '\n') const routeSource = readFileSync(resolve(process.cwd(), 'src/modules/unified-orders/routes.ts'), 'utf8').replace(/\r\n/g, '\n') +const densitySource = readFileSync(resolve(process.cwd(), 'src/styles/adminDensity.css'), 'utf8') + describe('UnifiedOrdersView contract', () => { it('exposes desktop pallet printing only for one-click dropship orders', () => { expect(source).toContain("detail.value?.summary.fulfillmentType === 'ONE_CLICK_DROPSHIP'") @@ -637,8 +639,9 @@ describe('UnifiedOrdersView contract', () => { }) it('keeps detail tabs compact on one horizontally scrollable row', () => { - expect(source).toContain('.detail-tabs { display: flex; overflow-x: auto; overflow-y: hidden;') - expect(source).toContain('min-width: max-content; min-height: 38px; flex: 0 0 auto;') + expect(source).toContain('class="detail-tabs density-tabs"') + expect(densitySource).toContain('overflow-x: auto; overflow-y: hidden;') + expect(densitySource).toContain('min-height: var(--admin-tab-height)') expect(source).not.toContain('grid-template-columns: repeat(6, minmax(124px, 1fr))') expect(source).not.toContain('.detail-tabs { grid-template-columns: repeat(6, 132px); }') }) @@ -649,9 +652,9 @@ describe('UnifiedOrdersView contract', () => { expect(source).toContain('真实集装箱号') @@ -660,7 +663,7 @@ describe('UnifiedOrdersView contract', () => { it('uses outlined semantic buttons for row business actions while keeping detail navigation explicit', () => { const rowActions = source.slice(source.indexOf(' diff --git a/src/modules/customers/pages/CustomerDetailView.vue b/src/modules/customers/pages/CustomerDetailView.vue index af67629f..3581be5e 100644 --- a/src/modules/customers/pages/CustomerDetailView.vue +++ b/src/modules/customers/pages/CustomerDetailView.vue @@ -223,7 +223,7 @@ onBeforeUnmount(() => {