diff --git a/.workbuddy/memory/2026-07-28.md b/.workbuddy/memory/2026-07-28.md index 710a2d9..6f3ac64 100644 --- a/.workbuddy/memory/2026-07-28.md +++ b/.workbuddy/memory/2026-07-28.md @@ -27,3 +27,8 @@ - 已新增单文件版:`/Users/gxwebsoft/VUE/tms-erp-web-ws/import-cargo-type/货物类型导入_单文件(一级在前).xlsx`(一级19条在前+二级116条在后,共135数据行)。 - 美化:对 import-cargo-type 下 3 个 xlsx 表头统一加样式(灰底 #D9D9D9 + 黑色加粗居中 + 细边框 #D0D7E2 + 行高30),数据区细边框、编码列文本格式、列宽适配长表头;表头文字保持不变以兼容后端解析。 - 重新美化(按原始模板蓝色主题):提取 Downloads/副本货物类型导入模板_模拟数据(1).xlsx 样式(表头 #0070C0 蓝底白字微软雅黑11居中、数据行 #EBF1F8 浅蓝黑字微软雅黑10、边框 #D9D9D9 细线、表头行高24→因我表头更长设为30、数据行高19),应用到 3 个导入文件。注意:原始第1行是标题"GB-T 42820-2023…",但导入文件必须表头在 row1(后端按 row1 读),故不照搬标题行,只复用表头+数据行样式。 + +## 模板下载改静态资源 + 导入两遍法加固(本次) +- 模板下载:`cargo-type.vue` 的 `handleTemplate()` 不再用 exceljs 本地生成 2 行示例,改为 fetch 工程内置静态文件 `public/cargo-type-import-template.xlsx`(内容=已生成的标准"单文件一级在前"模板,含一级19类+全部二级示例),下载名仍为 `货物类型导入模板.xlsx`。base 用 `import.meta.env.BASE_URL`(当前 `/`)拼路径,无需后端 export-template 接口、离线可用。 +- 后端加固(`CargoTypeServiceImpl.importCargoType`):由单遍逐行改为两遍法——第一遍先 validate+save 全部一级,第二遍再解析父级并 save 全部二级;父级解析仍走 `findParentByCode` 查库(第一遍已入库,同事务可见)。目的:让"完整导入"不受 Excel 行顺序影响(即使某二级行排在所属一级之前也不会整批失败);事务边界(`@Transactional` + 任一失败 `setRollbackOnly` 整批回滚)与失败明细 Excel(表头=CargoTypeExcel+导入失败原因,与前端 decorator 列宽 key 完全一致)保持不变。 +- 注意:后端 Java 改动未在本机编译,需在 tms-erp-api-ws 重新编译部署生效;校验文案(请选择货物类型级别/货物类型编码格式不正确/二级编码前2位必须与上级货物类型编码一致/该货物类型编码已存在/该上级下已存在同名货物类型/货物类型名称不能超过50字符/备注不能超过200字)保持不变以匹配前端 `CARGO_TYPE_FAIL_RULES`。 diff --git a/public/cargo-type-import-template.xlsx b/public/cargo-type-import-template.xlsx new file mode 100644 index 0000000..f9ad497 Binary files /dev/null and b/public/cargo-type-import-template.xlsx differ diff --git a/src/views/base/cargo-type.vue b/src/views/base/cargo-type.vue index d6fac09..0cedc15 100644 --- a/src/views/base/cargo-type.vue +++ b/src/views/base/cargo-type.vue @@ -584,76 +584,20 @@ export default { }, handleTemplate() { NProgress.start(); - // 本地生成带示例的导入模板(不依赖后端空模板),表头与可正常导入的模板一致 - import('exceljs') - .then(ExcelJS => { - const ExcelJSLib = ExcelJS.default || ExcelJS; - const wb = new ExcelJSLib.Workbook(); - const ws = wb.addWorksheet('货物类型导入模板'); - const headers = [ - '*类型', - '上级货物类型(如为一级则不需填写)', - '上级货物类型编码(如为一级则不需填写)', - '*货物类型', - '*货物类型编码', - '备注', - ]; - ws.addRow(headers); - // 示例数据:一级 + 二级各一条,演示层级关系与填写格式(当前环境可正常导入) - ws.addRow(['一级货物类型', null, null, '电子产品2', '117', '消费类电子']); - ws.addRow(['二级货物类型', '电子产品2', '117', '手机2', '11014', '智能手机']); - // 列宽:表头含长说明,适度加宽 - const widths = [14, 24, 26, 16, 16, 18]; - ws.columns.forEach((col, i) => { - col.width = widths[i] || 16; - }); - // 编码列设为文本,避免前导零丢失 - [3, 5].forEach(idx => { - const col = ws.getColumn(idx); - if (col) col.numFmt = '@'; - }); - // 表头与数据区样式:统一灰色表头 + 黑色加粗居中 + 细边框 - const headerFill = { - type: 'pattern', - pattern: 'solid', - fgColor: { argb: 'FFD9D9D9' }, - }; - const thinBorder = { - top: { style: 'thin', color: { argb: 'FFD0D7E2' } }, - left: { style: 'thin', color: { argb: 'FFD0D7E2' } }, - bottom: { style: 'thin', color: { argb: 'FFD0D7E2' } }, - right: { style: 'thin', color: { argb: 'FFD0D7E2' } }, - }; - ws.eachRow(row => { - row.eachCell(cell => { - cell.border = thinBorder; - if (row.number === 1) { - // 表头:灰色底色 + 黑字加粗 + 居中对齐 - cell.fill = headerFill; - cell.font = { bold: true, color: { argb: 'FF000000' } }; - cell.alignment = { - wrapText: true, - vertical: 'middle', - horizontal: 'center', - }; - } else { - // 数据行:左对齐,垂直居中 - cell.alignment = { - wrapText: true, - vertical: 'middle', - horizontal: 'left', - }; - } - }); - }); - // 表头行高适度,兼顾两行说明与视觉紧凑 - ws.getRow(1).height = 30; - return wb.xlsx.writeBuffer(); + // 直接下载工程内置的标准导入模板(public 目录静态资源),表头与可正常导入的模板一致, + // 含一级(19 类)及全部二级示例,便于用户参照填写层级关系与编码规则 + const templateUrl = `${import.meta.env.BASE_URL}cargo-type-import-template.xlsx`; + fetch(templateUrl) + .then(res => { + if (!res.ok) throw new Error('模板文件不存在'); + return res.blob(); }) - .then(buf => { - const blob = new Blob([buf], { type: 'application/vnd.ms-excel' }); + .then(blob => { downloadXls(blob, '货物类型导入模板.xlsx'); }) + .catch(() => { + this.$message.error('模板下载失败,请稍后重试'); + }) .finally(() => { NProgress.done(); });