diff --git a/frontend/scripts/test-document-form-components.mjs b/frontend/scripts/test-document-form-components.mjs index e772444..01b9d60 100644 --- a/frontend/scripts/test-document-form-components.mjs +++ b/frontend/scripts/test-document-form-components.mjs @@ -5,6 +5,7 @@ const files = [ "src/components/documentForms/DocumentPaper.vue", "src/components/documentForms/DocumentGrid.vue", "src/components/documentForms/DocumentLineTable.vue", + "src/components/documentForms/WarehouseDocumentFormShell.vue", "src/components/documentForms/DocumentArchiveActions.vue", "src/styles/main.css" ]; @@ -39,6 +40,17 @@ for (const snippet of ["预览PDF", "下载PDF", "重新生成"]) { } } +const warehouseDocumentFormShell = sources.get("src/components/documentForms/WarehouseDocumentFormShell.vue"); +for (const snippet of ["formatChineseDate", "displayBusinessDate"]) { + if (!warehouseDocumentFormShell.includes(snippet)) { + throw new Error(`WarehouseDocumentFormShell missing business date formatting snippet: ${snippet}`); + } +} + +if (warehouseDocumentFormShell.includes("{{ businessDate || \"-\" }}")) { + throw new Error("WarehouseDocumentFormShell must not render raw businessDate values"); +} + for (const forbiddenSnippet of ["ghost-button", "action-button"]) { if (archiveActions.includes(forbiddenSnippet)) { throw new Error(`DocumentArchiveActions must not use global button class: ${forbiddenSnippet}`); @@ -58,6 +70,25 @@ for (const selector of [".document-paper-shell", ".document-paper-sheet", ".docu } } +for (const selector of [ + ".document-grid-value input", + ".document-grid-value select", + ".document-grid-value textarea", + ".document-line-table input", + ".document-line-table select", + ".document-line-table textarea" +]) { + if (!css.includes(selector)) { + throw new Error(`main.css missing document form overflow guard selector: ${selector}`); + } +} + +for (const snippet of ["box-sizing: border-box", "min-width: 0", "max-width: 100%", "text-overflow: ellipsis"]) { + if (!css.includes(snippet)) { + throw new Error(`document form controls missing overflow guard declaration: ${snippet}`); + } +} + for (const selector of [ ".document-archive-button", ".document-archive-button-preview", diff --git a/frontend/scripts/test-purchase-receipt-inventory-lot-ui.mjs b/frontend/scripts/test-purchase-receipt-inventory-lot-ui.mjs index dab88cc..39bf2e5 100644 --- a/frontend/scripts/test-purchase-receipt-inventory-lot-ui.mjs +++ b/frontend/scripts/test-purchase-receipt-inventory-lot-ui.mjs @@ -10,7 +10,7 @@ assert.doesNotMatch(source, /YL-NB|buildDefaultLotNo/, "purchase receipt UI must assert.doesNotMatch(source, /送货单号|supplier_delivery_no/, "purchase receipt UI should not keep the duplicate supplier delivery number field"); assert.match(source, /后端自动生成库存批次号|系统自动生成库存批次号/, "purchase receipt UI should explain that inventory lot numbers are generated by the backend"); assert.match(source, /uploadResource/, "purchase receipt UI should upload logistics auxiliary photos"); -assert.match(source, /selectedPurchaseTargetType\s*===\s*["']RAW["'][\s\S]*运单号[\s\S]*辅助照片/, "raw purchase receipt UI should show waybill and auxiliary photo fields"); +assert.match(source, /selectedPurchaseTargetType(?:\.value)?\s*===\s*["']RAW["'][\s\S]*运单号[\s\S]*辅助照片/, "raw purchase receipt UI should show waybill and auxiliary photo fields"); assert.match(source, /validateLogisticsForm\s*\(\s*\)/, "raw purchase receipt submit should validate logistics information before posting"); assert.match(source, /waybill_no:\s*selectedPurchaseTargetType\.value\s*===\s*["']RAW["']\s*\?\s*form\.waybill_no/, "purchase receipt payload should include raw waybill number"); assert.match(source, /order_photo_url:\s*selectedPurchaseTargetType\.value\s*===\s*["']RAW["']\s*\?\s*form\.order_photo_url/, "purchase receipt payload should include raw auxiliary photo URL"); diff --git a/frontend/src/components/documentForms/WarehouseDocumentFormShell.vue b/frontend/src/components/documentForms/WarehouseDocumentFormShell.vue index 6988450..d91c1c4 100644 --- a/frontend/src/components/documentForms/WarehouseDocumentFormShell.vue +++ b/frontend/src/components/documentForms/WarehouseDocumentFormShell.vue @@ -10,7 +10,7 @@ 仓库联单 {{ companyName }} 业务日期 - {{ businessDate || "-" }} + {{ displayBusinessDate }}
@@ -21,9 +21,12 @@ diff --git a/frontend/src/styles/main.css b/frontend/src/styles/main.css index 616ea50..ec4705b 100644 --- a/frontend/src/styles/main.css +++ b/frontend/src/styles/main.css @@ -16005,6 +16005,34 @@ body .login-production-layout .login-security-foot p { word-break: break-word; } +.document-grid-value input, +.document-grid-value select, +.document-grid-value textarea, +.document-line-table input, +.document-line-table select, +.document-line-table textarea { + display: block; + width: 100%; + min-width: 0; + max-width: 100%; + box-sizing: border-box; + overflow: hidden; + text-overflow: ellipsis; +} + +.document-grid-value input, +.document-grid-value select, +.document-line-table input, +.document-line-table select { + white-space: nowrap; +} + +.document-grid-value textarea, +.document-line-table textarea { + white-space: normal; + overflow-wrap: anywhere; +} + .document-line-table { margin-top: 18px; border: 2px solid var(--document-paper-grid); diff --git a/frontend/src/utils/formatters.js b/frontend/src/utils/formatters.js index 6123df0..18c9e2b 100644 --- a/frontend/src/utils/formatters.js +++ b/frontend/src/utils/formatters.js @@ -2,14 +2,35 @@ export function formatDate(value) { if (!value) { return "-"; } - return String(value).slice(0, 10); + const parts = resolveDateParts(value); + if (!parts) { + return "-"; + } + return `${parts.year}-${parts.month}-${parts.day}`; } export function formatDateTime(value) { if (!value) { return "-"; } - return String(value).replace("T", " ").slice(0, 16); + const parts = resolveDateParts(value); + if (!parts) { + return "-"; + } + const time = resolveTimeParts(value); + const dateText = `${parts.year}-${parts.month}-${parts.day}`; + return time ? `${dateText} ${time.hour}:${time.minute}` : dateText; +} + +export function formatChineseDate(value) { + if (!value) { + return "-"; + } + const parts = resolveDateParts(value); + if (!parts) { + return "-"; + } + return `${parts.year}年${parts.month}月${parts.day}日`; } export function formatAmount(value) { @@ -33,3 +54,64 @@ export function formatWeight(value) { export function emptyToNull(value) { return value === "" || value === undefined ? null : value; } + +function resolveDateParts(value) { + if (value instanceof Date) { + return datePartsFromDate(value); + } + + const stringValue = String(value).trim(); + const dateMatch = stringValue.match(/^(\d{4})[-/年](\d{1,2})[-/月](\d{1,2})/); + if (dateMatch) { + return { + year: dateMatch[1], + month: padDatePart(dateMatch[2]), + day: padDatePart(dateMatch[3]) + }; + } + + const parsedDate = new Date(stringValue); + return datePartsFromDate(parsedDate); +} + +function resolveTimeParts(value) { + if (value instanceof Date) { + return timePartsFromDate(value); + } + + const stringValue = String(value).trim(); + const timeMatch = stringValue.match(/(?:T|\s)(\d{1,2}):(\d{1,2})/); + if (timeMatch) { + return { + hour: padDatePart(timeMatch[1]), + minute: padDatePart(timeMatch[2]) + }; + } + + return null; +} + +function datePartsFromDate(date) { + if (!(date instanceof Date) || Number.isNaN(date.getTime())) { + return null; + } + return { + year: String(date.getFullYear()), + month: padDatePart(date.getMonth() + 1), + day: padDatePart(date.getDate()) + }; +} + +function timePartsFromDate(date) { + if (!(date instanceof Date) || Number.isNaN(date.getTime())) { + return null; + } + return { + hour: padDatePart(date.getHours()), + minute: padDatePart(date.getMinutes()) + }; +} + +function padDatePart(value) { + return String(value).padStart(2, "0"); +} diff --git a/frontend/src/utils/formatters.test.js b/frontend/src/utils/formatters.test.js new file mode 100644 index 0000000..bef1dd5 --- /dev/null +++ b/frontend/src/utils/formatters.test.js @@ -0,0 +1,34 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; + +import { formatChineseDate, formatDate, formatDateTime } from "./formatters.js"; + +describe("formatDate", () => { + it("formats Date values without browser-native English text", () => { + assert.equal(formatDate(new Date(2026, 5, 13, 9, 30)), "2026-06-13"); + }); +}); + +describe("formatDateTime", () => { + it("formats Date values as numeric date-time text", () => { + assert.equal(formatDateTime(new Date(2026, 5, 13, 9, 30)), "2026-06-13 09:30"); + }); + + it("keeps date-only strings as date text instead of adding synthetic time", () => { + assert.equal(formatDateTime("2026-06-13"), "2026-06-13"); + }); +}); + +describe("formatChineseDate", () => { + it("formats Date values as Chinese year-month-day text", () => { + assert.equal(formatChineseDate(new Date(2026, 5, 13, 9, 30)), "2026年06月13日"); + }); + + it("formats ISO date strings as Chinese year-month-day text", () => { + assert.equal(formatChineseDate("2026-06-13T15:45:00"), "2026年06月13日"); + }); + + it("keeps empty values aligned with other display formatters", () => { + assert.equal(formatChineseDate(""), "-"); + }); +}); diff --git a/frontend/src/views/InventoryLedgerView.test.js b/frontend/src/views/InventoryLedgerView.test.js index 1b8f433..fa81e48 100644 --- a/frontend/src/views/InventoryLedgerView.test.js +++ b/frontend/src/views/InventoryLedgerView.test.js @@ -65,6 +65,11 @@ describe("InventoryLedgerView warehouse action layout", () => { assert.match(source, / { + assert.doesNotMatch(source, /formatDateTime\(new Date\(\)\)/); + assert.match(source, /:business-date="new Date\(\)"/); + }); + it("surfaces generated PDF archive feedback after independent warehouse saves", () => { [ ["submitRawReturnOutbound", "退货出库"], diff --git a/frontend/src/views/InventoryLedgerView.vue b/frontend/src/views/InventoryLedgerView.vue index a9d0e74..b096aa5 100644 --- a/frontend/src/views/InventoryLedgerView.vue +++ b/frontend/src/views/InventoryLedgerView.vue @@ -329,7 +329,7 @@ class="warehouse-document-operation-form" title="客料入库单" :document-no="customerSuppliedLotNoPreview || '提交后生成'" - :business-date="formatDateTime(new Date())" + :business-date="new Date()" @submit="submitGenericOperation" > @@ -447,7 +447,7 @@ class="warehouse-document-operation-form" :title="genericOperationDocumentTitle" document-no="保存后生成" - :business-date="formatDateTime(new Date())" + :business-date="new Date()" @submit="submitGenericOperation" > @@ -806,7 +806,7 @@ class="warehouse-document-operation-form" :title="`${activeInventoryLabel}${specialAdjustmentDirectionText}单`" document-no="保存后生成" - :business-date="formatDateTime(new Date())" + :business-date="new Date()" :tone="specialAdjustmentForm.direction === 'OUT' ? 'red' : 'green'" @submit="submitSpecialAdjustment" > @@ -996,7 +996,7 @@ class="warehouse-document-operation-form" title="原材料退货出库单" document-no="保存后生成" - :business-date="formatDateTime(new Date())" + :business-date="new Date()" tone="red" @submit="submitRawReturnOutbound" > @@ -1114,7 +1114,7 @@ class="warehouse-document-operation-form" title="退货入库单" document-no="保存后生成" - :business-date="formatDateTime(new Date())" + :business-date="new Date()" @submit="submitReturnInbound" > @@ -1212,7 +1212,7 @@ class="warehouse-document-operation-form" title="退货废料入库单" document-no="保存后生成" - :business-date="formatDateTime(new Date())" + :business-date="new Date()" @submit="submitReturnScrapInbound" > @@ -1350,7 +1350,7 @@ class="warehouse-document-operation-form" title="生产出库单" document-no="保存后生成" - :business-date="formatDateTime(new Date())" + :business-date="new Date()" tone="red" novalidate @submit="submitProductionOut" @@ -1461,7 +1461,7 @@ class="warehouse-document-operation-form" :title="isReworkCompletionOperation ? '返工入库单' : '生产入库单'" document-no="保存后生成" - :business-date="formatDateTime(new Date())" + :business-date="new Date()" @submit="submitCompletionInbound" > @@ -1601,7 +1601,7 @@ class="warehouse-document-operation-form" title="销售出库单" document-no="保存后生成" - :business-date="formatDateTime(new Date())" + :business-date="new Date()" tone="red" @submit="submitSalesOut" > @@ -1817,7 +1817,7 @@ class="warehouse-document-operation-form" title="返工出库单" document-no="保存后生成" - :business-date="formatDateTime(new Date())" + :business-date="new Date()" tone="red" @submit="submitReturnReworkOutbound" > @@ -2034,7 +2034,7 @@ class="warehouse-document-operation-form" title="生产台账入库结算单" document-no="保存后生成" - :business-date="formatDateTime(new Date())" + :business-date="new Date()" @submit="submitProductionWorkOrderInbound" > diff --git a/frontend/src/views/PurchaseReceiptView.vue b/frontend/src/views/PurchaseReceiptView.vue index 47cc1f9..2446a0c 100644 --- a/frontend/src/views/PurchaseReceiptView.vue +++ b/frontend/src/views/PurchaseReceiptView.vue @@ -857,10 +857,13 @@ onMounted(async () => { .receipt-document-line-static { display: inline-flex; align-items: center; + min-width: 0; + max-width: 100%; min-height: 34px; color: var(--document-paper-ink, #17120a); font-weight: 800; line-height: 1.45; + overflow-wrap: anywhere; } .receipt-document-line-static {