From e76cf70a20f4de65e3f99eb083ae7eefc2639089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Mon, 27 Jul 2026 23:58:29 +0800 Subject: [PATCH 01/11] =?UTF-8?q?feat(cargo-type):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=B4=A7=E7=89=A9=E7=B1=BB=E5=9E=8B=E5=AF=BC=E5=85=A5=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E6=98=8E=E7=BB=86=E7=9A=84=E9=94=99=E8=AF=AF=E6=A0=87?= =?UTF-8?q?=E8=AE=B0=E5=92=8C=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增导入失败明细的加工函数,给违规字段自动标红并丰富错误提示信息 - 在导入弹窗调用中增加失败明细装饰器处理,提升用户体验 - 将货物类型组件option配置提取为单独模块,简化代码逻辑 - 调整货物类型表单禁用逻辑,避免非新增时的错误操作 - 移除原有表单校验函数,相关验证由导入失败明细装饰器替代 - 引入exceljs依赖以支持导入失败Excel文件的读取和修改 - 修复导入失败明细导出时的文件命名和内容加工问题 - 优化代码结构,提升维护性和可读性 --- .workbuddy/memory/2026-07-27.md | 46 ++++ package.json | 9 +- pnpm-lock.yaml | 472 ++++++++++++++++++++++++++++++++ src/option/base/cargo-type.js | 180 ++++++++++++ src/utils/import-excel.js | 40 ++- src/views/base/cargo-type.vue | 232 +++++----------- vite.config.mjs | 4 +- 7 files changed, 801 insertions(+), 182 deletions(-) create mode 100644 .workbuddy/memory/2026-07-27.md create mode 100644 src/option/base/cargo-type.js diff --git a/.workbuddy/memory/2026-07-27.md b/.workbuddy/memory/2026-07-27.md new file mode 100644 index 0000000..ac773f2 --- /dev/null +++ b/.workbuddy/memory/2026-07-27.md @@ -0,0 +1,46 @@ +# 2026-07-27 工作日志 + +## 开发环境启动与登录排查 + +- 终止了遗留的 2888 端口 dev server 进程,清理端口占用。 +- 将 `.env.development` 的 `VITE_APP_API` 从 `http://172.16.203.228:8000/api` 改为 `/api`,让前端请求走 Vite 代理。 +- 将 `vite.config.mjs` 的 `/api` 代理 target 从 `http://localhost` 改为 `http://172.16.203.228:8000`,并去掉 `rewrite`(后端接口本身就在 `/api` 前缀下,不能去掉)。 +- 修复后发现登录无响应的根因:后端 CORS 返回 `Access-Control-Allow-Origin: *`,与 axios `withCredentials: true` 冲突,浏览器拦截所有跨域请求;走 Vite 代理后同源,CORS 问题消失。 +- 启动 dev server 于 `http://localhost:2888`。 +- 使用 agent-browser(系统 Chrome)打开登录页,填入账号 `admin` / 密码 `admin`,成功触发行为验证码弹窗。 +- 行为验证码(滑块/拼图)需人工在浏览器窗口完成。 + +## 货物类型导入模板模拟数据 + +- 模板:`/Users/gxwebsoft/Downloads/货物类型导入模板.xlsx`,单 Sheet「货物类型导入模板」,表头 6 列:类型(必填)、上级货物类型、上级货物类型编码、货物类型(必填)、货物类型编码(必填)、备注。一级类型上级字段留空。 +- 在 `/Users/gxwebsoft/Downloads/货物类型导入模板_模拟数据.xlsx` 生成 5 条模拟数据(2 条一级 + 3 条二级,演示层级)。原模板保持不变。 + +## 货物类型模块(/base/cargo-type)按需求完善 + +- 需求:新增默认二级;类型/上级/编码在编辑查看禁编辑;一级隐藏上级字段、二级显示;同上级名称唯一 + 编码全局唯一交后端;提交失败展示后端提示。 +- 抽出 `src/option/base/cargo-type.js`,导出工厂 `getCargoTypeOption(ctx)`,依赖组件的校验/change 通过 `ctx` 闭包访问(`ctx.form`、`ctx.handleTypeLevelChange` 等),避免 Option API 写死 this。 +- `cargo-type.vue` 改动:import 该 option;新增默认 `typeLevel:2`;`beforeOpen` 设置 `dialogType`(add/edit/view);上级下拉框 `:disabled="dialogType!=='add' || typeLevel!==2"`;列配置加 `editDisabled:true`(类型/编码/上级编码)控制编辑禁用;view 模式 Avue 自动禁用其余字段。 +- 提交失败处理:axios 拦截器已对非 200 弹 `ElMessage`(后端 msg),故 rowSave/rowUpdate 的 error 仅 `loading()`,不再 console/重复 toast。 +- 导入失败明细下载已合规:`src/utils/import-excel.js` 的 `handleImportExcel` 已实现 content-type 区分 JSON/Excel、失败下载 `货物类型导入失败明细时间.xlsx`、错误提示;businessName='货物类型'。 +- 注意:导入/下载模板由后端 `/blade-system/cargo-type/import-cargo-type`、`/export-template` 控制,前端模板列名由后端决定,需后端保证与需求卡字段一致。 + +## 货物类型批量导入失败排查(19:12) + +- 失败明细 `货物类型导入失败明细2026-07-27 19_12_24.xlsx`:5 条全部「请选择货物类型级别」。 +- 根因(数据问题,非前端代码):模拟数据「类型」列误填为业务分类 `普通货物/冷链货物`,但需求卡 REQ-CARGO-007/008 规定「类型」是**货物类型级别**,仅允许填 `一级货物类型`/`二级货物类型`;且编码误用字母(`GOODS-ELEC-001`),违反 REQ-CARGO-006「一级 2 位数字、二级 4 位数字」。 +- 已重造 `货物类型导入模板_模拟数据.xlsx`:类型改为一级/二级;编码改为纯数字(01/02 一级,0101/0102/0201 二级继承父级前 2 位)。 +- 风险:二级行依赖一级行已存在,若后端按 DB 校验而非按文件顺序处理,二级行可能报「请选择上级货物类型」,建议先导入 2 条一级、再导入 3 条二级。 + +## 货物类型导入失败复查(23:27) + +- 第二版失败明细 `货物类型导入失败明细2026-07-27 19_17_21.xlsx`:类型已正确,但一级报「货物类型编码格式不正确」、二级报「请选择上级货物类型」。 +- 根因:编码列被存成**数字(int)**,前导零丢失('01'→1、'0101'→101),一级编码非 2 位、二级非 4 位;一级失败未入库 → 二级找不到上级。 +- 修复:写入时强制 C/E 列 `number_format='@'`(文本)保留前导零;编码 '01'/'02'、'0101'/'0102'/'0201'。 +- 为消除级联风险,拆成两个文件:货物类型导入_一级.xlsx(2条)、货物类型导入_二级.xlsx(3条),先导入一级再导入二级确保成功。 + +## 货物类型导入第三次复查(23:30) + +- 第三版失败明细:一级导入也失败,报「该货物类型编码已存在」(01/02 已被占用)。根因:01/02 极可能是用户此前测试新增功能时手动建的,导入撞车;失败导入不写库,说明编码 pre-exist。 +- 同期发现 2888 dev server 进程退出(lsof 无监听),导致 Vite 代理对 localhost:2888 全返回 502(含根路径)。已重启 `pnpm dev`(端口 2888,proxy target 172.16.203.228:8000),代理恢复 200。后端直连一直正常。 +- 改用新编码重造:一级 11/12(电子产品/生鲜食品),二级 1101/1102(父11)、1201(父12),文本格式保留前导零。三份文件(一级/二级/合并)已更新。 +- 结论:导入功能本身 OK,问题一直是造数(类型值、编码格式、编码冲突)+ dev server 偶发退出。建议导入前确认 dev server 在跑。 diff --git a/package.json b/package.json index 4d4f4f2..0123244 100644 --- a/package.json +++ b/package.json @@ -9,21 +9,22 @@ "serve": "vite preview --host" }, "dependencies": { - "element-plus": "^2.14.2", "@element-plus/icons-vue": "^2.3.1", "@open-file-viewer/core": "^0.1.27", "@open-file-viewer/vue": "^0.1.27", - "@smallwei/avue": "~3.9.2", - "avue-plugin-ueditor": "^1.0.4", "@saber/nf-design-base-elp": "^1.3.0", "@saber/nf-form-design-elp": "^1.6.1", "@saber/nf-form-elp": "^1.6.3", + "@smallwei/avue": "~3.9.2", "animate.css": "^4.1.1", + "avue-plugin-ueditor": "^1.0.4", "axios": "^1.13.5", "codemirror": "^5.65.21", "crypto-js": "^4.1.1", "dayjs": "^1.10.6", "disable-devtool": "^0.3.8", + "element-plus": "^2.14.2", + "exceljs": "^4.4.0", "highlight.js": "^11.9.0", "js-base64": "^3.7.4", "js-cookie": "^3.0.0", @@ -40,8 +41,8 @@ "devDependencies": { "@vitejs/plugin-vue": "^5.2.4", "@vue/compiler-sfc": "^3.4.27", - "sass": "^1.85.1", "prettier": "^2.8.7", + "sass": "^1.85.1", "terser": "^5.31.1", "unplugin-auto-import": "^0.11.2", "vite": "^5.4.19", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 886ef6b..61aeeab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,9 @@ importers: element-plus: specifier: ^2.14.2 version: 2.14.2(vue@3.5.34) + exceljs: + specifier: ^4.4.0 + version: 4.4.0 highlight.js: specifier: ^11.9.0 version: 11.11.1 @@ -305,6 +308,12 @@ packages: cpu: [x64] os: [win32] + '@fast-csv/format@4.3.5': + resolution: {integrity: sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==} + + '@fast-csv/parse@4.3.6': + resolution: {integrity: sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==} + '@floating-ui/core@1.7.5': resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} @@ -735,6 +744,9 @@ packages: '@types/lodash@4.17.24': resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} + '@types/node@14.18.63': + resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} + '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -1000,12 +1012,27 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + archiver-utils@2.1.0: + resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} + engines: {node: '>= 6'} + + archiver-utils@3.0.4: + resolution: {integrity: sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==} + engines: {node: '>= 10'} + + archiver@5.3.2: + resolution: {integrity: sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==} + engines: {node: '>= 10'} + argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} async-validator@4.2.5: resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -1021,9 +1048,22 @@ packages: axios@1.16.0: resolution: {integrity: sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==} + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + big-integer@1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} + + binary@0.3.0: + resolution: {integrity: sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + bluebird@3.4.7: resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} @@ -1033,13 +1073,33 @@ packages: bpmn-moddle@8.1.0: resolution: {integrity: sha512-yI5OAFfYVJwViKTsTsonVfCBPtB3MlefADUORwNIxxBOMp21vnoxuxsdgUWlPH/dvAEZh/+mr8UtqOBNu8NC5Q==} + brace-expansion@1.1.16: + resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==} + + brace-expansion@2.1.2: + resolution: {integrity: sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer-indexof-polyfill@1.0.2: + resolution: {integrity: sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==} + engines: {node: '>=0.10'} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + buffers@0.1.1: + resolution: {integrity: sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==} + engines: {node: '>=0.2.0'} + but-unzip@0.1.10: resolution: {integrity: sha512-hLfQ9WlUimmv/okzsRl6AYG3Ew5HNWhWgUslSR93FsDdeL0MAoQvmC/BJfs35lqEAO5t/QD7Y4vCFcPJtijt3A==} @@ -1051,6 +1111,9 @@ packages: resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==} engines: {node: '>=0.8'} + chainsaw@0.1.0: + resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1094,12 +1157,19 @@ packages: component-event@0.2.1: resolution: {integrity: sha512-wGA++isMqiDq1jPYeyv2as/Bt/u+3iLW0rEa+8NQ82jAv3TgqMiCM+B2SaBdn2DfLilLjjq736YcezihRYhfxw==} + compress-commons@4.1.2: + resolution: {integrity: sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==} + engines: {node: '>= 10'} + compute-scroll-into-view@1.0.20: resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} compute-scroll-into-view@3.1.1: resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + concat-stream@2.0.0: resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} engines: {'0': node >= 6.0} @@ -1121,6 +1191,10 @@ packages: engines: {node: '>=0.8'} hasBin: true + crc32-stream@4.0.3: + resolution: {integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==} + engines: {node: '>= 10'} + crypto-js@4.2.0: resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} @@ -1193,6 +1267,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + duplexer2@0.1.4: + resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + echarts@6.1.0: resolution: {integrity: sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==} @@ -1201,6 +1278,9 @@ packages: peerDependencies: vue: ^3.3.7 + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + entities@7.0.1: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} @@ -1251,9 +1331,17 @@ packages: event-emitter@0.3.5: resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + exceljs@4.4.0: + resolution: {integrity: sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==} + engines: {node: '>=8.3.0'} + ext@1.7.0: resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + fast-csv@4.3.6: + resolution: {integrity: sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==} + engines: {node: '>=10.0.0'} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -1282,15 +1370,26 @@ packages: resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==} engines: {node: '>=0.8'} + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + fstream@1.0.12: + resolution: {integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==} + engines: {node: '>=0.6'} + deprecated: This package is no longer supported. + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -1306,6 +1405,10 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + good-listener@1.2.2: resolution: {integrity: sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==} @@ -1371,6 +1474,9 @@ packages: ids@1.0.5: resolution: {integrity: sha512-XQ0yom/4KWTL29sLG+tyuycy7UmeaM/79GRtSJq6IG9cJGIPeBz5kwDCguie3TwxaMNIc3WtPi0cTa1XYHicpw==} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + immediate@3.0.6: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} @@ -1380,6 +1486,10 @@ packages: immutable@5.1.5: resolution: {integrity: sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==} + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + inherits-browser@0.1.0: resolution: {integrity: sha512-CJHHvW3jQ6q7lzsXPpapLdMx5hDpSF3FSh45pwsj6bKxJJ8Nl8v43i5yXnr3BdfOimGHKyniewQtnAIp3vyJJw==} @@ -1430,12 +1540,19 @@ packages: jszip@3.10.1: resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + leaflet@1.9.4: resolution: {integrity: sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==} lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + listenercount@1.0.1: + resolution: {integrity: sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==} + local-pkg@0.4.3: resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} engines: {node: '>=14'} @@ -1459,19 +1576,55 @@ packages: lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + + lodash.difference@4.5.0: + resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==} + + lodash.escaperegexp@4.1.2: + resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} + + lodash.flatten@4.4.0: + resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} + lodash.foreach@4.5.0: resolution: {integrity: sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==} + lodash.groupby@4.6.0: + resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + lodash.isfunction@3.0.9: + resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} + + lodash.isnil@4.0.0: + resolution: {integrity: sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isundefined@3.0.1: + resolution: {integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==} + lodash.throttle@4.1.1: resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} lodash.toarray@4.4.0: resolution: {integrity: sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw==} + lodash.union@4.6.0: + resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} + + lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + lodash@4.18.1: resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} @@ -1537,9 +1690,20 @@ packages: min-dom@4.2.1: resolution: {integrity: sha512-TMoL8SEEIhUWYgkj7XMSgxmwSyGI+4fP2KFFGnN3FbHfbGHVdsLYSz8LoIsgPhz4dWRmLvxWWSMgzZMJW5sZuA==} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + mlly@1.8.2: resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} @@ -1571,6 +1735,10 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + normalize-wheel-es@1.2.0: resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==} @@ -1580,6 +1748,9 @@ packages: object-refs@0.3.0: resolution: {integrity: sha512-eP0ywuoWOaDoiake/6kTJlPJhs+k0qNm4nYRzXLNHj6vh+5M3i9R1epJTdxIPGlhWc4fNRQ7a6XJNCX+/L4FOQ==} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + option@0.2.4: resolution: {integrity: sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==} @@ -1673,6 +1844,9 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -1681,6 +1855,11 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + rollup@4.60.3: resolution: {integrity: sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -1706,6 +1885,10 @@ packages: saxen@8.1.2: resolution: {integrity: sha512-xUOiiFbc3Ow7p8KMxwsGICPx46ZQvy3+qfNVhrkwfz3Vvq45eGt98Ft5IQaA1R/7Tb5B5MKh9fUR9x3c3nDTxw==} + saxes@5.0.1: + resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} + engines: {node: '>=10'} + scroll-into-view-if-needed@2.2.31: resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==} @@ -1798,6 +1981,10 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + terser@5.46.2: resolution: {integrity: sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw==} engines: {node: '>=10'} @@ -1815,6 +2002,10 @@ packages: tiny-warning@1.0.3: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + tmp@0.2.7: + resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} + engines: {node: '>=14.14'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -1823,6 +2014,9 @@ packages: resolution: {integrity: sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==} hasBin: true + traverse@0.3.9: + resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==} + tslib@2.3.0: resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} @@ -1858,12 +2052,19 @@ packages: resolution: {integrity: sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==} engines: {node: '>=14.0.0'} + unzipper@0.10.14: + resolution: {integrity: sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==} + utif@3.1.0: resolution: {integrity: sha512-WEo4D/xOvFW53K5f5QTaTbbiORcm2/pCL9P6qmJnup+17eYfKaEhDeX9PeQkuyEoIxlbGklDuGl8xwuXYMrrXQ==} util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + vite-plugin-compression@0.5.1: resolution: {integrity: sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==} peerDependencies: @@ -1959,6 +2160,9 @@ packages: resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==} engines: {node: '>=0.8'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + xlsx@0.18.5: resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==} engines: {node: '>=0.8'} @@ -1968,10 +2172,17 @@ packages: resolution: {integrity: sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==} engines: {node: '>=4.0'} + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + xz-decompress@0.2.3: resolution: {integrity: sha512-O8v6HG8T0PrKBcpyWA13GkSYWFvncwzuzcLx5A7++l3HsE3atmoetXjIxrZ/JV/nbvSZ7WS4+3XvREZuVn+rEA==} engines: {node: '>=16'} + zip-stream@4.1.1: + resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==} + engines: {node: '>= 10'} + zrender@6.1.0: resolution: {integrity: sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==} @@ -2083,6 +2294,25 @@ snapshots: '@esbuild/win32-x64@0.21.5': optional: true + '@fast-csv/format@4.3.5': + dependencies: + '@types/node': 14.18.63 + lodash.escaperegexp: 4.1.2 + lodash.isboolean: 3.0.3 + lodash.isequal: 4.5.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + + '@fast-csv/parse@4.3.6': + dependencies: + '@types/node': 14.18.63 + lodash.escaperegexp: 4.1.2 + lodash.groupby: 4.6.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + lodash.isundefined: 3.0.1 + lodash.uniq: 4.5.0 + '@floating-ui/core@1.7.5': dependencies: '@floating-ui/utils': 0.2.11 @@ -2432,6 +2662,8 @@ snapshots: '@types/lodash@4.17.24': {} + '@types/node@14.18.63': {} + '@types/trusted-types@2.0.7': optional: true @@ -2766,12 +2998,50 @@ snapshots: dependencies: color-convert: 2.0.1 + archiver-utils@2.1.0: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 2.3.8 + + archiver-utils@3.0.4: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + + archiver@5.3.2: + dependencies: + archiver-utils: 2.1.0 + async: 3.2.6 + buffer-crc32: 0.2.13 + readable-stream: 3.6.2 + readdir-glob: 1.1.3 + tar-stream: 2.2.0 + zip-stream: 4.1.1 + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 async-validator@4.2.5: {} + async@3.2.6: {} + asynckit@0.4.0: {} avue-plugin-ueditor@1.0.4(axios@1.16.0)(vue@3.5.34): @@ -2795,8 +3065,23 @@ snapshots: transitivePeerDependencies: - debug + balanced-match@1.0.2: {} + base64-js@1.5.1: {} + big-integer@1.6.52: {} + + binary@0.3.0: + dependencies: + buffers: 0.1.1 + chainsaw: 0.1.0 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + bluebird@3.4.7: {} bpmn-js@11.5.0: @@ -2817,12 +3102,32 @@ snapshots: moddle: 6.2.3 moddle-xml: 10.1.0 + brace-expansion@1.1.16: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.1.2: + dependencies: + balanced-match: 1.0.2 + braces@3.0.3: dependencies: fill-range: 7.1.1 + buffer-crc32@0.2.13: {} + buffer-from@1.1.2: {} + buffer-indexof-polyfill@1.0.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + buffers@0.1.1: {} + but-unzip@0.1.10: {} call-bind-apply-helpers@1.0.2: @@ -2835,6 +3140,10 @@ snapshots: adler-32: 1.3.1 crc-32: 1.2.2 + chainsaw@0.1.0: + dependencies: + traverse: 0.3.9 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -2872,10 +3181,19 @@ snapshots: component-event@0.2.1: {} + compress-commons@4.1.2: + dependencies: + buffer-crc32: 0.2.13 + crc32-stream: 4.0.3 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + compute-scroll-into-view@1.0.20: {} compute-scroll-into-view@3.1.1: {} + concat-map@0.0.1: {} + concat-stream@2.0.0: dependencies: buffer-from: 1.1.2 @@ -2893,6 +3211,11 @@ snapshots: crc-32@1.2.2: {} + crc32-stream@4.0.3: + dependencies: + crc-32: 1.2.2 + readable-stream: 3.6.2 + crypto-js@4.2.0: {} csstype@3.2.3: {} @@ -2968,6 +3291,10 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + duplexer2@0.1.4: + dependencies: + readable-stream: 2.3.8 + echarts@6.1.0: dependencies: tslib: 2.3.0 @@ -2992,6 +3319,10 @@ snapshots: vue: 3.5.34 vue-component-type-helpers: 3.3.6 + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + entities@7.0.1: {} es-define-property@1.0.1: {} @@ -3069,10 +3400,27 @@ snapshots: d: 1.0.2 es5-ext: 0.10.64 + exceljs@4.4.0: + dependencies: + archiver: 5.3.2 + dayjs: 1.11.20 + fast-csv: 4.3.6 + jszip: 3.10.1 + readable-stream: 3.6.2 + saxes: 5.0.1 + tmp: 0.2.7 + unzipper: 0.10.14 + uuid: 8.3.2 + ext@1.7.0: dependencies: type: 2.7.3 + fast-csv@4.3.6: + dependencies: + '@fast-csv/format': 4.3.5 + '@fast-csv/parse': 4.3.6 + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3101,15 +3449,26 @@ snapshots: frac@1.1.2: {} + fs-constants@1.0.0: {} + fs-extra@10.1.0: dependencies: graceful-fs: 4.2.11 jsonfile: 6.2.1 universalify: 2.0.1 + fs.realpath@1.0.0: {} + fsevents@2.3.3: optional: true + fstream@1.0.12: + dependencies: + graceful-fs: 4.2.11 + inherits: 2.0.4 + mkdirp: 0.5.6 + rimraf: 2.7.1 + function-bind@1.1.2: {} get-intrinsic@1.3.0: @@ -3134,6 +3493,15 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + good-listener@1.2.2: dependencies: delegate: 3.2.0 @@ -3184,12 +3552,19 @@ snapshots: ids@1.0.5: {} + ieee754@1.2.1: {} + immediate@3.0.6: {} immer@9.0.21: {} immutable@5.1.5: {} + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + inherits-browser@0.1.0: {} inherits@2.0.4: {} @@ -3231,12 +3606,18 @@ snapshots: readable-stream: 2.3.8 setimmediate: 1.0.5 + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + leaflet@1.9.4: {} lie@3.3.0: dependencies: immediate: 3.0.6 + listenercount@1.0.1: {} + local-pkg@0.4.3: {} lodash-es@4.18.1: {} @@ -3253,14 +3634,38 @@ snapshots: lodash.debounce@4.0.8: {} + lodash.defaults@4.2.0: {} + + lodash.difference@4.5.0: {} + + lodash.escaperegexp@4.1.2: {} + + lodash.flatten@4.4.0: {} + lodash.foreach@4.5.0: {} + lodash.groupby@4.6.0: {} + + lodash.isboolean@3.0.3: {} + lodash.isequal@4.5.0: {} + lodash.isfunction@3.0.9: {} + + lodash.isnil@4.0.0: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isundefined@3.0.1: {} + lodash.throttle@4.1.1: {} lodash.toarray@4.4.0: {} + lodash.union@4.6.0: {} + + lodash.uniq@4.5.0: {} + lodash@4.18.1: {} lop@0.4.2: @@ -3331,8 +3736,20 @@ snapshots: domify: 1.4.2 min-dash: 4.2.3 + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.16 + + minimatch@5.1.9: + dependencies: + brace-expansion: 2.1.2 + minimist@1.2.8: {} + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + mlly@1.8.2: dependencies: acorn: 8.16.0 @@ -3363,12 +3780,18 @@ snapshots: node-addon-api@7.1.1: optional: true + normalize-path@3.0.0: {} + normalize-wheel-es@1.2.0: {} nprogress@0.2.0: {} object-refs@0.3.0: {} + once@1.4.0: + dependencies: + wrappy: 1.0.2 + option@0.2.4: {} pako@1.0.11: {} @@ -3446,10 +3869,18 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.9 + readdirp@4.1.2: {} reusify@1.1.0: {} + rimraf@2.7.1: + dependencies: + glob: 7.2.3 + rollup@4.60.3: dependencies: '@types/estree': 1.0.8 @@ -3501,6 +3932,10 @@ snapshots: saxen@8.1.2: {} + saxes@5.0.1: + dependencies: + xmlchars: 2.2.0 + scroll-into-view-if-needed@2.2.31: dependencies: compute-scroll-into-view: 1.0.20 @@ -3591,6 +4026,14 @@ snapshots: dependencies: has-flag: 4.0.0 + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + terser@5.46.2: dependencies: '@jridgewell/source-map': 0.3.11 @@ -3606,6 +4049,8 @@ snapshots: tiny-warning@1.0.3: {} + tmp@0.2.7: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -3614,6 +4059,8 @@ snapshots: dependencies: commander: 2.20.3 + traverse@0.3.9: {} + tslib@2.3.0: {} type@2.7.3: {} @@ -3660,12 +4107,27 @@ snapshots: acorn: 8.16.0 webpack-virtual-modules: 0.6.2 + unzipper@0.10.14: + dependencies: + big-integer: 1.6.52 + binary: 0.3.0 + bluebird: 3.4.7 + buffer-indexof-polyfill: 1.0.2 + duplexer2: 0.1.4 + fstream: 1.0.12 + graceful-fs: 4.2.11 + listenercount: 1.0.1 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + utif@3.1.0: dependencies: pako: 1.0.11 util-deprecate@1.0.2: {} + uuid@8.3.2: {} + vite-plugin-compression@0.5.1(vite@5.4.21(sass@1.99.0)(terser@5.46.2)): dependencies: chalk: 4.1.2 @@ -3739,6 +4201,8 @@ snapshots: word@0.3.0: {} + wrappy@1.0.2: {} + xlsx@0.18.5: dependencies: adler-32: 1.3.1 @@ -3751,8 +4215,16 @@ snapshots: xmlbuilder@10.1.1: {} + xmlchars@2.2.0: {} + xz-decompress@0.2.3: {} + zip-stream@4.1.1: + dependencies: + archiver-utils: 3.0.4 + compress-commons: 4.1.2 + readable-stream: 3.6.2 + zrender@6.1.0: dependencies: tslib: 2.3.0 diff --git a/src/option/base/cargo-type.js b/src/option/base/cargo-type.js new file mode 100644 index 0000000..2ceca56 --- /dev/null +++ b/src/option/base/cargo-type.js @@ -0,0 +1,180 @@ +const typeLevelDic = [ + { label: '一级货物类型', value: 1 }, + { label: '二级货物类型', value: 2 }, +]; + +const validateCargoName = (rule, value, callback) => { + const v = String(value || '').trim(); + if (!v) { + callback(new Error('请输入货物类型名称')); + } else if (v.length > 50) { + callback(new Error('货物类型名称不能超过50字符')); + } else { + callback(); + } +}; + +const validateRemark = (rule, value, callback) => { + if (String(value || '').length > 200) { + callback(new Error('备注不能超过200字')); + } else { + callback(); + } +}; + +export const getCargoTypeOption = ctx => ({ + height: 'auto', + calcHeight: 32, + dialogWidth: 760, + labelPosition: 'right', + labelWidth: 'auto', + tip: false, + searchBtnText: '查询', + emptyBtnText: '重置', + saveBtnText: '提交', + updateBtnText: '提交', + searchShow: true, + searchMenuSpan: 24, + searchIcon: true, + searchIndex: 8, + searchMenuPosition: 'right', + border: true, + index: true, + indexLabel: '序号', + indexWidth: 70, + viewBtn: false, + delBtn: false, + editBtn: false, + selection: true, + dialogClickModal: false, + menuWidth: 220, + column: [ + { + label: '类型', + prop: 'typeLevel', + type: 'radio', + dataType: 'number', + dicData: typeLevelDic, + search: true, + value: 2, + span: 24, + minWidth: 120, + editDisabled: true, + rules: [{ required: true, message: '请选择货物类型级别', trigger: 'change' }], + change: ({ value }) => ctx.handleTypeLevelChange(value), + }, + { + label: '上级货物类型', + prop: 'parentId', + formslot: true, + hide: true, + display: false, + span: 24, + rules: [{ required: true, message: '请选择上级货物类型', trigger: 'change' }], + }, + { + label: '上级货物类型', + prop: 'parentCargoName', + slot: true, + addDisplay: false, + editDisplay: false, + display: false, + minWidth: 160, + }, + { + label: '上级货物类型编码', + prop: 'parentCargoCode', + slot: true, + readonly: true, + editDisabled: true, + addDisplay: false, + editDisplay: false, + display: false, + minWidth: 160, + rules: [{ required: true, message: '请选择上级货物类型', trigger: 'change' }], + }, + { + label: '货物类型', + prop: 'cargoName', + minWidth: 150, + maxlength: 50, + showWordLimit: true, + rules: [{ validator: validateCargoName, trigger: 'blur' }], + }, + { + label: '货物类型编码', + prop: 'cargoCode', + minWidth: 140, + search: true, + maxlength: 4, + editDisabled: true, + rules: [ + { + validator: (rule, value, callback) => { + const form = ctx.form || {}; + const code = String(value || '').trim(); + const pattern = form.typeLevel === 1 ? /^\d{2}$/ : /^\d{4}$/; + if (!code) { + callback(new Error('请输入货物类型编码')); + } else if (!pattern.test(code)) { + callback(new Error('货物类型编码格式不正确')); + } else if ( + form.typeLevel === 2 && + form.parentCargoCode && + !code.startsWith(form.parentCargoCode) + ) { + callback(new Error('二级编码前2位必须与上级货物类型编码一致')); + } else { + callback(); + } + }, + trigger: 'blur', + }, + ], + change: ({ value }) => ctx.handleCargoCodeChange(value), + }, + { + label: '创建人', + prop: 'createUserName', + slot: true, + addDisplay: false, + editDisplay: false, + display: false, + minWidth: 120, + }, + { + label: '备注', + prop: 'remark', + type: 'textarea', + minRows: 4, + span: 24, + minWidth: 180, + maxlength: 200, + showWordLimit: true, + overHidden: true, + rules: [{ validator: validateRemark, trigger: 'blur' }], + }, + { + label: '更新时间', + prop: 'updateTime', + type: 'datetime', + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'YYYY-MM-DD HH:mm:ss', + addDisplay: false, + editDisplay: false, + display: false, + minWidth: 160, + }, + { + label: '创建时间', + prop: 'createTime', + type: 'datetime', + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'YYYY-MM-DD HH:mm:ss', + addDisplay: false, + editDisplay: false, + display: false, + minWidth: 160, + }, + ], +}); diff --git a/src/utils/import-excel.js b/src/utils/import-excel.js index 01c2e98..4cbed0a 100644 --- a/src/utils/import-excel.js +++ b/src/utils/import-excel.js @@ -15,14 +15,45 @@ const parseBlobJson = async blob => { return text ? JSON.parse(text) : {}; }; -export const openImportDialog = (vm, businessName, onSuccess) => { +/** + * 加工后端返回的导入失败明细 Excel。 + * 若传入 failDetailDecorator(函数),则动态加载 exceljs 读取 blob 流, + * 交由 decorator 完成标红 / 改写原因列等加工,再导出新的 blob; + * 否则原样返回 blob(保持旧行为,不影响未使用 decorator 的模块)。 + * @param {Blob} blob 后端返回的失败明细 Excel 流 + * @param {String} businessName 业务名称(如「货物类型」) + * @param {Function} [failDetailDecorator] async (workbook, { businessName }) => void + * @returns {Promise} + */ +const decorateFailExcel = async (blob, businessName, failDetailDecorator) => { + if (typeof failDetailDecorator !== 'function') return blob; + const ExcelJS = await import('exceljs'); + const buf = await blob.arrayBuffer(); + const workbook = new ExcelJS.Workbook(); + await workbook.xlsx.load(buf); + await failDetailDecorator(workbook, { businessName }); + const out = await workbook.xlsx.writeBuffer(); + return new Blob([out], { type: 'application/vnd.ms-excel' }); +}; + +/** + * 打开导入弹窗 + * @param {Object} vm 组件实例 + * @param {String} businessName 业务名称,用于失败明细文件命名 + * @param {Function} [onSuccess] 导入成功回调 + * @param {Object} [options] 扩展项 + * @param {Function} [options.failDetailDecorator] 失败明细加工函数,用于对后端返回的失败 Excel 标红/改写原因 + */ +export const openImportDialog = (vm, businessName, onSuccess, options = {}) => { + const { failDetailDecorator } = options; const column = vm.findColumn(vm.excelOption.column, 'excelFile'); column.httpRequest = (option, uploadColumn) => - handleImportExcel(vm, option, uploadColumn, businessName, onSuccess); + handleImportExcel(vm, option, uploadColumn, businessName, onSuccess, { failDetailDecorator }); vm.excelBox = true; }; -export const handleImportExcel = async (vm, option, column, businessName, onSuccess) => { +export const handleImportExcel = async (vm, option, column, businessName, onSuccess, options = {}) => { + const { failDetailDecorator } = options; const file = option.file; const fileName = file && file.name ? file.name.toLowerCase() : ''; if (!excelPattern.test(fileName)) { @@ -37,8 +68,9 @@ export const handleImportExcel = async (vm, option, column, businessName, onSucc try { const res = await importBlob(column.action, file); if (isExcelResponse(res)) { + const decorated = await decorateFailExcel(res.data, businessName, failDetailDecorator); downloadXls( - res.data, + decorated, `${businessName}导入失败明细${vm.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx` ); vm.$message.warning('部分数据导入失败,已下载失败明细'); diff --git a/src/views/base/cargo-type.vue b/src/views/base/cargo-type.vue index 154c5f5..2678d8d 100644 --- a/src/views/base/cargo-type.vue +++ b/src/views/base/cargo-type.vue @@ -73,7 +73,7 @@ clearable :remote-method="loadParentOptions" :loading="parentLoading" - :disabled="form.typeLevel !== 2" + :disabled="dialogType !== 'add' || form.typeLevel !== 2" placeholder="请输入名称或编码搜索" style="width: 100%" @change="handleParentChange" @@ -146,6 +146,7 @@ import { remove, submit, } from '@/api/base/cargo-type'; +import { getCargoTypeOption } from '@/option/base/cargo-type'; import { exportBlob } from '@/api/common'; import { mapGetters } from 'vuex'; import { downloadXls } from '@/utils/util'; @@ -154,45 +155,62 @@ import { getToken } from '@/utils/auth'; import NProgress from 'nprogress'; import 'nprogress/nprogress.css'; -const typeLevelDic = [ - { label: '一级货物类型', value: 1 }, - { label: '二级货物类型', value: 2 }, +// 导入失败明细中,每个错误文本对应的「标红列」与「规则说明」 +const FAIL_RED_FILL = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFFC7CE' } }; +const CARGO_TYPE_FAIL_RULES = [ + { match: '请选择货物类型级别', col: '类型', rule: '仅允许:一级货物类型/二级货物类型' }, + { match: '货物类型编码格式不正确', col: '货物类型编码', rule: '一级2位数字,二级4位数字' }, + { match: '该货物类型编码已存在', col: '货物类型编码', rule: '请更换唯一编码' }, + { match: '请选择上级货物类型', col: '上级货物类型', rule: '二级货物类型必须选择上级' }, + { + match: '二级编码前2位必须与上级货物类型编码一致', + col: '货物类型编码', + rule: '二级编码前2位必须与上级货物类型编码一致', + }, + { match: '货物类型名称不能超过50字符', col: '货物类型', rule: '货物类型名称不能超过50字符' }, + { match: '备注不能超过200字', col: '备注', rule: '备注不能超过200字' }, ]; +/** + * 加工后端返回的导入失败明细 Excel: + * - 依据「错误文本 -> 字段列」映射,给违规的那一栏标红 + * - 将「导入失败原因」列改写为(第N行:错误,规则)格式 + * 未匹配到的未知错误,标红原因列本身并原样提示 + */ +export const decorateCargoTypeFailDetail = async workbook => { + const ws = workbook.worksheets[0]; + if (!ws) return; + const headerRow = ws.getRow(1); + const colMap = {}; + headerRow.eachCell((cell, colNumber) => { + const name = String(cell.value == null ? '' : cell.value).trim(); + if (name) colMap[name] = colNumber; + }); + const reasonCol = colMap['导入失败原因']; + if (!reasonCol) return; + ws.eachRow((row, rowNumber) => { + if (rowNumber === 1) return; // 跳过表头 + const reasonCell = row.getCell(reasonCol); + const raw = String(reasonCell.value == null ? '' : reasonCell.value).trim(); + if (!raw) return; + const parts = raw.split(/[;;\n\r]+/).map(s => s.trim()).filter(Boolean); + if (parts.length === 0) return; + const decorated = parts.map(part => { + const rule = CARGO_TYPE_FAIL_RULES.find(r => part.includes(r.match)); + if (rule) { + const targetCol = colMap[rule.col]; + if (targetCol) row.getCell(targetCol).fill = FAIL_RED_FILL; + return `第${rowNumber}行:${part},${rule.rule}`; + } + reasonCell.fill = FAIL_RED_FILL; + return `第${rowNumber}行:${part}`; + }); + reasonCell.value = decorated.join(';'); + }); +}; + export default { data() { - const validateCargoName = (rule, value, callback) => { - const cargoName = String(value || '').trim(); - if (!cargoName) { - callback(new Error('请输入货物类型名称')); - } else if (cargoName.length > 50) { - callback(new Error('货物类型名称不能超过50字符')); - } else { - callback(); - } - }; - const validateCargoCode = (rule, value, callback) => { - const cargoCode = String(value || '').trim(); - const pattern = this.form.typeLevel === 1 ? /^\d{2}$/ : /^\d{4}$/; - if (!pattern.test(cargoCode)) { - callback(new Error('货物类型编码格式不正确')); - } else if ( - this.form.typeLevel === 2 && - this.form.parentCargoCode && - !cargoCode.startsWith(this.form.parentCargoCode) - ) { - callback(new Error('二级编码前2位必须与上级货物类型编码一致')); - } else { - callback(); - } - }; - const validateRemark = (rule, value, callback) => { - if (String(value || '').length > 200) { - callback(new Error('备注不能超过200字')); - } else { - callback(); - } - }; return { form: {}, query: {}, @@ -202,6 +220,7 @@ export default { excelForm: {}, parentOptions: [], parentLoading: false, + dialogType: 'add', page: { pageSize: 10, pageSizes: [10, 20, 50, 100], @@ -209,137 +228,7 @@ export default { total: 0, }, selectionList: [], - option: { - height: 'auto', - calcHeight: 32, - dialogWidth: 760, - labelPosition: 'right', - labelWidth: 'auto', - tip: false, - searchBtnText: '查询', - emptyBtnText: '重置', - saveBtnText: '提交', - updateBtnText: '提交', - searchShow: true, - searchMenuSpan: 24, - searchIcon: true, - searchIndex: 8, - searchMenuPosition: 'right', - border: true, - index: true, - indexLabel: '序号', - indexWidth: 70, - viewBtn: false, - delBtn: false, - editBtn: false, - selection: true, - dialogClickModal: false, - menuWidth: 220, - column: [ - { - label: '类型', - prop: 'typeLevel', - type: 'radio', - dataType: 'number', - dicData: typeLevelDic, - search: true, - value: 1, - span: 24, - minWidth: 120, - rules: [{ required: true, message: '请选择货物类型级别', trigger: 'change' }], - change: ({ value }) => this.handleTypeLevelChange(value), - }, - { - label: '上级货物类型', - prop: 'parentId', - formslot: true, - hide: true, - display: false, - span: 24, - rules: [{ required: true, message: '请选择上级货物类型', trigger: 'change' }], - }, - { - label: '上级货物类型', - prop: 'parentCargoName', - slot: true, - addDisplay: false, - editDisplay: false, - display: false, - minWidth: 160, - }, - { - label: '上级货物类型编码', - prop: 'parentCargoCode', - slot: true, - readonly: true, - addDisplay: false, - editDisplay: false, - display: false, - minWidth: 160, - rules: [{ required: true, message: '请选择上级货物类型', trigger: 'change' }], - }, - { - label: '货物类型', - prop: 'cargoName', - minWidth: 150, - maxlength: 50, - showWordLimit: true, - rules: [{ validator: validateCargoName, trigger: 'blur' }], - }, - { - label: '货物类型编码', - prop: 'cargoCode', - minWidth: 140, - search: true, - maxlength: 4, - rules: [{ validator: validateCargoCode, trigger: 'blur' }], - change: ({ value }) => this.handleCargoCodeChange(value), - }, - { - label: '创建人', - prop: 'createUserName', - slot: true, - addDisplay: false, - editDisplay: false, - display: false, - minWidth: 120, - }, - { - label: '备注', - prop: 'remark', - type: 'textarea', - minRows: 4, - span: 24, - minWidth: 180, - maxlength: 200, - showWordLimit: true, - overHidden: true, - rules: [{ validator: validateRemark, trigger: 'blur' }], - }, - { - label: '更新时间', - prop: 'updateTime', - type: 'datetime', - format: 'YYYY-MM-DD HH:mm:ss', - valueFormat: 'YYYY-MM-DD HH:mm:ss', - addDisplay: false, - editDisplay: false, - display: false, - minWidth: 160, - }, - { - label: '创建时间', - prop: 'createTime', - type: 'datetime', - format: 'YYYY-MM-DD HH:mm:ss', - valueFormat: 'YYYY-MM-DD HH:mm:ss', - addDisplay: false, - editDisplay: false, - display: false, - minWidth: 160, - }, - ], - }, + option: getCargoTypeOption(this), excelOption: { submitBtn: false, emptyBtn: false, @@ -506,7 +395,6 @@ export default { done(); }, error => { - window.console.log(error); loading(); } ); @@ -524,7 +412,6 @@ export default { done(); }, error => { - window.console.log(error); loading(); } ); @@ -565,6 +452,7 @@ export default { this.onLoad(this.page, this.query); }, beforeOpen(done, type) { + this.dialogType = type || 'add'; if (['edit', 'view'].includes(type)) { getDetail(this.form.id).then(res => { this.form = res.data.data || {}; @@ -575,7 +463,7 @@ export default { return; } this.form = { - typeLevel: 1, + typeLevel: 2, cargoName: '', cargoCode: '', parentId: undefined, @@ -627,7 +515,9 @@ export default { }); }, handleImport() { - openImportDialog(this, '货物类型'); + openImportDialog(this, '货物类型', undefined, { + failDetailDecorator: decorateCargoTypeFailDetail, + }); }, buildExportParams() { const params = { ...this.query }; diff --git a/vite.config.mjs b/vite.config.mjs index 3cbb3d4..fbebcf8 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -54,10 +54,8 @@ export default ({ port: 2888, proxy: { '/api': { - target: 'http://localhost', - //target: 'https://saber3.bladex.cn/api', + target: 'http://172.16.203.228:8000', changeOrigin: true, - rewrite: path => path.replace(/^\/api/, ''), }, }, }, From 8ff9f6bb6693e218a2022b30409bf124d545beaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Tue, 28 Jul 2026 00:16:43 +0800 Subject: [PATCH 02/11] =?UTF-8?q?feat(import):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=B4=A7=E7=89=A9=E7=B1=BB=E5=9E=8B=E5=AF=BC=E5=85=A5=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E6=98=8E=E7=BB=86=E5=8F=8A=E4=B8=8B=E8=BD=BD=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引入 exceljs 动态加载,支持对后端失败明细 Excel 进行内容加工 - 导入失败明细取消标红错误字段,改为清晰错误原因文本说明 - 调整失败明细各列宽度,增加失败原因列换行提升可读性 - 失败明细原因列格式统一为“第N行:错误,规则”方式,便于定位 - 下载模板由后端生成改为前端本地生成,并内置示例数据 - 模板表头严格使用真实模板列名,示例数据涵盖一级及二级货物类型 - 模板编码列设置为文本格式避免前导零丢失,表头自动换行显示完整说明 --- .workbuddy/memory/2026-07-27.md | 31 +++++++++++++++ src/utils/import-excel.js | 2 +- src/views/base/cargo-type.vue | 69 +++++++++++++++++++++++++++------ 3 files changed, 90 insertions(+), 12 deletions(-) diff --git a/.workbuddy/memory/2026-07-27.md b/.workbuddy/memory/2026-07-27.md index ac773f2..cb52001 100644 --- a/.workbuddy/memory/2026-07-27.md +++ b/.workbuddy/memory/2026-07-27.md @@ -44,3 +44,34 @@ - 同期发现 2888 dev server 进程退出(lsof 无监听),导致 Vite 代理对 localhost:2888 全返回 502(含根路径)。已重启 `pnpm dev`(端口 2888,proxy target 172.16.203.228:8000),代理恢复 200。后端直连一直正常。 - 改用新编码重造:一级 11/12(电子产品/生鲜食品),二级 1101/1102(父11)、1201(父12),文本格式保留前导零。三份文件(一级/二级/合并)已更新。 - 结论:导入功能本身 OK,问题一直是造数(类型值、编码格式、编码冲突)+ dev server 偶发退出。建议导入前确认 dev server 在跑。 + +## 导入失败明细标红 + 原因改写(23:50) + +- 需求:导入失败后,失败明细 Excel 中**仅违规那一栏标红**,且「导入失败原因」列改写为清晰格式,如`(第2行:货物类型编码格式不正确,一级2位数字,二级4位数字)`。 +- 现状:失败明细 Excel 此前是后端生成、前端 `import-excel.js` 直接 `downloadXls` 原样下载,无加工。 +- 改动: + 1. `pnpm add exceljs`(4.4.0,运行时依赖;`import('exceljs')` 动态加载,不进主包)。 + 2. `src/utils/import-excel.js`:`openImportDialog` 增加可选 `options.failDetailDecorator`;`handleImportExcel` 在 Excel 响应分支用 exceljs 读取后端失败明细 blob → 调用 decorator 加工(标红+改写原因)→ 再下载。**未传 decorator 的模块保持原行为(向后兼容、通用)**。 + 3. `src/views/base/cargo-type.vue`:新增模块级 `decorateCargoTypeFailDetail(workbook)`,建立「表头列名→列号」映射,按错误文本匹配规则(7 类错误 → 标红列 + 规则说明),遍历数据行标红违规栏并将原因列改写为`(第N行:错误,规则)`;`handleImport` 传入 `{ failDetailDecorator: decorateCargoTypeFailDetail }`。标红填充色 `FFFFC7CE`(淡红),物理行号(表头第1行,数据第2行起)。 +- 验证:Node + exceljs 跑 decorator 等价逻辑全 PASS(标红列命中、原因格式正确);vite 将 `import('exceljs')` 重写为浏览器版 chunk(/node_modules/.vite/deps/exceljs.js,HTTP 200,1.46MB),浏览器运行时无 polyfill 报错;cargo-type.vue 与 import-excel.js 经 vite 编译均 200。 +- 注意:decorator 依赖后端失败明细 Excel 的「导入失败原因」列文本与模板列名(类型/上级货物类型/上级货物类型编码/货物类型/货物类型编码/备注/导入失败原因)精确一致;若后端改列名需同步更新 `CARGO_TYPE_FAIL_RULES` 的 col 值。 + +## 导入失败明细改为「不标红 + 加列宽」(00:08 次晨) + +- 用户反馈两点:① 不要标红,只写清错误原因即可;② 失败明细 xls 列太挤,需加宽。 +- 改动(同一对文件): + - `src/views/base/cargo-type.vue`:删除 `FAIL_RED_FILL` 常量与所有 `cell.fill` 标红逻辑,仅保留原因列改写为`(第N行:错误,规则)`;新增 `CARGO_TYPE_FAIL_COL_WIDTH` 列宽映射(类型14/上级货物类型16/上级货物类型编码20/货物类型16/货物类型编码16/备注18/导入失败原因60),遍历 colMap 设置 `ws.getColumn(n).width`;原因列额外 `alignment={wrapText:true,vertical:'top'}` 提升可读性。 + - `src/utils/import-excel.js`:注释由「标红 / 改写原因列」改为中性「改写原因列、调整列宽等加工」。 +- 验证:Node + exceljs 等价逻辑全 PASS(无红色填充=✅、原因含「第N行」、导入失败原因列宽=60);cargo-type.vue 与 import-excel.js vite 编译均 200,FAIL_RED_FILL 残留=0。 + +## 下载模板改为「前端本地生成 + 带示例」(00:11 次晨) + +- 需求:点击「下载模板」给的导入模板要自带一个数据示例(参照可正常导入的 `货物类型导入模板_模拟数据.xlsx`)。 +- 现状:`handleTemplate` 原调用后端 `/blade-system/cargo-type/export-template` 下载空模板(无示例)。 +- 改动 `src/views/base/cargo-type.vue` 的 `handleTemplate`:改为 `import('exceljs')` 本地生成模板,不再依赖后端空模板。 + - 表头严格使用真实模板列名(含 `*` 与括号说明):`*类型` / `上级货物类型(如为一级则不需填写)` / `上级货物类型编码(如为一级则不需填写)` / `*货物类型` / `*货物类型编码` / `备注`(与可正常导入的本地模板一致)。 + - 示例数据 2 条:一级 `电子产品2/117` + 二级 `手机2/11014`(父117),演示层级,取值与用户确认可导入的模拟数据一致。 + - 列宽加宽(类型14/上级货物类型24/上级货物类型编码26/货物类型16/货物类型编码16/备注18);编码列(3、5) `numFmt='@'` 文本格式防前导零丢失;表头行 `wrapText` 完整显示长说明。 + - 下载文件名仍为 `货物类型导入模板.xlsx`。`exportBlob` 仍被 `handleExport` 使用,import 不冗余。 +- 验证:Node + exceljs 等价逻辑确认表头/示例行/列宽/文本格式正确(稀疏 eachCell 跳过空单元格导致一次断言误报,按列号复核确认数据无误);cargo-type.vue vite 编译 200。 +- 注意:示例行若被直接导入会创建 `电子产品2/手机2` 两条演示数据;用户应按需删除/替换示例再填自己的数据。 diff --git a/src/utils/import-excel.js b/src/utils/import-excel.js index 4cbed0a..f3804b3 100644 --- a/src/utils/import-excel.js +++ b/src/utils/import-excel.js @@ -18,7 +18,7 @@ const parseBlobJson = async blob => { /** * 加工后端返回的导入失败明细 Excel。 * 若传入 failDetailDecorator(函数),则动态加载 exceljs 读取 blob 流, - * 交由 decorator 完成标红 / 改写原因列等加工,再导出新的 blob; + * 交由 decorator 完成改写原因列、调整列宽等加工,再导出新的 blob; * 否则原样返回 blob(保持旧行为,不影响未使用 decorator 的模块)。 * @param {Blob} blob 后端返回的失败明细 Excel 流 * @param {String} businessName 业务名称(如「货物类型」) diff --git a/src/views/base/cargo-type.vue b/src/views/base/cargo-type.vue index 2678d8d..c0ce5e8 100644 --- a/src/views/base/cargo-type.vue +++ b/src/views/base/cargo-type.vue @@ -155,8 +155,7 @@ import { getToken } from '@/utils/auth'; import NProgress from 'nprogress'; import 'nprogress/nprogress.css'; -// 导入失败明细中,每个错误文本对应的「标红列」与「规则说明」 -const FAIL_RED_FILL = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFFC7CE' } }; +// 导入失败明细中,每个错误文本对应的「字段列」与「规则说明」 const CARGO_TYPE_FAIL_RULES = [ { match: '请选择货物类型级别', col: '类型', rule: '仅允许:一级货物类型/二级货物类型' }, { match: '货物类型编码格式不正确', col: '货物类型编码', rule: '一级2位数字,二级4位数字' }, @@ -171,11 +170,22 @@ const CARGO_TYPE_FAIL_RULES = [ { match: '备注不能超过200字', col: '备注', rule: '备注不能超过200字' }, ]; +// 失败明细各列目标宽度(Excel 字符数),避免内容拥挤 +const CARGO_TYPE_FAIL_COL_WIDTH = { + 类型: 14, + 上级货物类型: 16, + 上级货物类型编码: 20, + 货物类型: 16, + 货物类型编码: 16, + 备注: 18, + 导入失败原因: 60, +}; + /** * 加工后端返回的导入失败明细 Excel: - * - 依据「错误文本 -> 字段列」映射,给违规的那一栏标红 - * - 将「导入失败原因」列改写为(第N行:错误,规则)格式 - * 未匹配到的未知错误,标红原因列本身并原样提示 + * - 将「导入失败原因」列改写为(第N行:错误,规则)格式,便于定位 + * - 适当加宽各列、原因列自动换行,避免内容拥挤 + * 不再对单元格标红,仅通过文字清晰说明错误 */ export const decorateCargoTypeFailDetail = async workbook => { const ws = workbook.worksheets[0]; @@ -198,14 +208,17 @@ export const decorateCargoTypeFailDetail = async workbook => { const decorated = parts.map(part => { const rule = CARGO_TYPE_FAIL_RULES.find(r => part.includes(r.match)); if (rule) { - const targetCol = colMap[rule.col]; - if (targetCol) row.getCell(targetCol).fill = FAIL_RED_FILL; return `第${rowNumber}行:${part},${rule.rule}`; } - reasonCell.fill = FAIL_RED_FILL; return `第${rowNumber}行:${part}`; }); reasonCell.value = decorated.join(';'); + reasonCell.alignment = { wrapText: true, vertical: 'top' }; + }); + // 加宽各列(未列在映射中的列给默认宽度) + Object.keys(colMap).forEach(name => { + const col = ws.getColumn(colMap[name]); + col.width = CARGO_TYPE_FAIL_COL_WIDTH[name] || 18; }); }; @@ -544,9 +557,43 @@ export default { }, handleTemplate() { NProgress.start(); - exportBlob('/blade-system/cargo-type/export-template', {}) - .then(res => { - downloadXls(res.data, '货物类型导入模板.xlsx'); + // 本地生成带示例的导入模板(不依赖后端空模板),表头与可正常导入的模板一致 + 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 = '@'; + }); + // 表头自动换行,长说明完整显示 + ws.getRow(1).eachCell(cell => { + cell.alignment = { wrapText: true, vertical: 'middle' }; + }); + return wb.xlsx.writeBuffer(); + }) + .then(buf => { + const blob = new Blob([buf], { type: 'application/vnd.ms-excel' }); + downloadXls(blob, '货物类型导入模板.xlsx'); }) .finally(() => { NProgress.done(); From c695d72ec8eec46cf538e46f34b35cc86840752c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Tue, 28 Jul 2026 00:23:15 +0800 Subject: [PATCH 03/11] =?UTF-8?q?feat(import):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=B4=A7=E7=89=A9=E7=B1=BB=E5=9E=8B=E5=AF=BC=E5=85=A5=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E6=98=8E=E7=BB=86=E5=8F=8A=E4=B8=8B=E8=BD=BD=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引入 exceljs 动态加载,支持对后端失败明细 Excel 进行内容加工 - 导入失败明细取消标红错误字段,改为清晰错误原因文本说明 - 调整失败明细各列宽度,增加失败原因列换行提升可读性 - 失败明细原因列格式统一为“第N行:错误,规则”方式,便于定位 - 下载模板由后端生成改为前端本地生成,并内置示例数据 - 模板表头严格使用真实模板列名,示例数据涵盖一级及二级货物类型 - 模板编码列设置为文本格式避免前导零丢失,表头自动换行显示完整说明 --- .workbuddy/memory/2026-07-28.md | 6 ++++++ src/views/base/cargo-type.vue | 38 ++++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 .workbuddy/memory/2026-07-28.md diff --git a/.workbuddy/memory/2026-07-28.md b/.workbuddy/memory/2026-07-28.md new file mode 100644 index 0000000..c4174b7 --- /dev/null +++ b/.workbuddy/memory/2026-07-28.md @@ -0,0 +1,6 @@ +# 2026-07-28 工作日志 + +## 货物类型导入模板美化 +- 文件:`src/views/base/cargo-type.vue` 的 `handleTemplate()` +- 改动:表头行加主色底色 `#409EFF`(argb `FF409EFF`)+ 白色加粗字体 + 居中对齐 + 自动换行;表头与数据区统一加细边框(`FFD0D7E2`);表头行高设为 42;编码列保持文本格式(`numFmt='@'`)。 +- 验证:Node + exceljs 4.4.0 跑通生成逻辑,确认表头 fill/font/alignment/border 与行高正确。 diff --git a/src/views/base/cargo-type.vue b/src/views/base/cargo-type.vue index c0ce5e8..ee377e5 100644 --- a/src/views/base/cargo-type.vue +++ b/src/views/base/cargo-type.vue @@ -585,10 +585,42 @@ export default { const col = ws.getColumn(idx); if (col) col.numFmt = '@'; }); - // 表头自动换行,长说明完整显示 - ws.getRow(1).eachCell(cell => { - cell.alignment = { wrapText: true, vertical: 'middle' }; + // 表头与数据区样式美化:主色底色表头 + 白字加粗居中 + 细边框 + const headerFill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FF409EFF' }, + }; + 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: 'FFFFFFFF' } }; + cell.alignment = { + wrapText: true, + vertical: 'middle', + horizontal: 'center', + }; + } else { + // 数据行:左对齐,垂直居中 + cell.alignment = { + wrapText: true, + vertical: 'middle', + horizontal: 'left', + }; + } + }); }); + // 表头行高,确保长说明换行后完整显示 + ws.getRow(1).height = 42; return wb.xlsx.writeBuffer(); }) .then(buf => { From 8002fe3d509241a6a6fa4eeb945a1fb4f306048d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Tue, 28 Jul 2026 00:37:42 +0800 Subject: [PATCH 04/11] =?UTF-8?q?style(cargo-type):=20=E7=BE=8E=E5=8C=96?= =?UTF-8?q?=E8=B4=A7=E7=89=A9=E7=B1=BB=E5=9E=8B=E5=AF=BC=E5=85=A5=E6=A8=A1?= =?UTF-8?q?=E6=9D=BFExcel=E6=A0=B7=E5=BC=8F=E5=8F=8A=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E6=98=8E=E7=BB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 表头底色调整为统一灰色 #D9D9D9,字体改为黑色加粗 - 表头行高从42调整为30,增加视觉紧凑感 - 表头与数据区统一添加细边框,居中对齐并自动换行 - 编码列保持文本格式,防止格式识别错误 - 导入失败明细中「导入失败原因」列字体设置为红色 - 失败原因按 A./B./C. 格式编号,使用分号分隔 - 去除后端重复的「第N行:」前缀,避免冗余信息 - 通过 Node + exceljs 验证样式和格式变更正确生效 --- .workbuddy/memory/2026-07-28.md | 6 ++-- src/views/base/cargo-type.vue | 51 +++++++++++++++++++++------------ 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/.workbuddy/memory/2026-07-28.md b/.workbuddy/memory/2026-07-28.md index c4174b7..297ba6a 100644 --- a/.workbuddy/memory/2026-07-28.md +++ b/.workbuddy/memory/2026-07-28.md @@ -2,5 +2,7 @@ ## 货物类型导入模板美化 - 文件:`src/views/base/cargo-type.vue` 的 `handleTemplate()` -- 改动:表头行加主色底色 `#409EFF`(argb `FF409EFF`)+ 白色加粗字体 + 居中对齐 + 自动换行;表头与数据区统一加细边框(`FFD0D7E2`);表头行高设为 42;编码列保持文本格式(`numFmt='@'`)。 -- 验证:Node + exceljs 4.4.0 跑通生成逻辑,确认表头 fill/font/alignment/border 与行高正确。 +- 改动(第一次):表头行加主色底色 `#409EFF` + 白色加粗字体 + 居中对齐 + 细边框;行高 42。 +- 改动(调整后):按用户反馈,表头底色改为统一灰色 `#D9D9D9` + 黑色加粗字体,行高降为 30。 +- 导入失败明细 `decorateCargoTypeFailDetail` 调整:仅「导入失败原因」列字体红色;错误按 `A./B./C.` 编号,分号(;)隔开,并去掉后端重复的「第N行:」前缀;最终格式示例 `第2行:A.该货物类型编码已存在,请更换唯一编码`。 +- 验证:Node + exceljs 4.4.0 跑通生成逻辑,确认表头 fill/font/alignment/height、失败原因编号格式与红色字体均正确。 diff --git a/src/views/base/cargo-type.vue b/src/views/base/cargo-type.vue index ee377e5..ec3d563 100644 --- a/src/views/base/cargo-type.vue +++ b/src/views/base/cargo-type.vue @@ -183,9 +183,9 @@ const CARGO_TYPE_FAIL_COL_WIDTH = { /** * 加工后端返回的导入失败明细 Excel: - * - 将「导入失败原因」列改写为(第N行:错误,规则)格式,便于定位 + * - 「导入失败原因」列按 A. B. C. 编号列出,多条以分号隔开 + * - 每个错误后追加对应规则说明,仅失败原因列标红 * - 适当加宽各列、原因列自动换行,避免内容拥挤 - * 不再对单元格标红,仅通过文字清晰说明错误 */ export const decorateCargoTypeFailDetail = async workbook => { const ws = workbook.worksheets[0]; @@ -200,19 +200,32 @@ export const decorateCargoTypeFailDetail = async workbook => { if (!reasonCol) return; ws.eachRow((row, rowNumber) => { if (rowNumber === 1) return; // 跳过表头 - const reasonCell = row.getCell(reasonCol); - const raw = String(reasonCell.value == null ? '' : reasonCell.value).trim(); - if (!raw) return; - const parts = raw.split(/[;;\n\r]+/).map(s => s.trim()).filter(Boolean); - if (parts.length === 0) return; - const decorated = parts.map(part => { - const rule = CARGO_TYPE_FAIL_RULES.find(r => part.includes(r.match)); - if (rule) { - return `第${rowNumber}行:${part},${rule.rule}`; + // 仅「导入失败原因」列允许红色,其他列红色字体统一清除 + row.eachCell((cell, colNumber) => { + if (colNumber === reasonCol) return; + if (cell.font && cell.font.color) { + const font = { ...cell.font }; + delete font.color; + cell.font = font; } - return `第${rowNumber}行:${part}`; }); - reasonCell.value = decorated.join(';'); + const reasonCell = row.getCell(reasonCol); + let raw = String(reasonCell.value == null ? '' : reasonCell.value).trim(); + if (!raw) return; + // 去掉后端可能已经存在的「第N行:」前缀,避免重复 + raw = raw.replace(/^第\s*\d+\s*行[::]\s*/, ''); + const parts = raw + .split(/[;;\n\r]+/) + .map(s => s.trim().replace(/^第\s*\d+\s*行[::]\s*/, '')) + .filter(Boolean); + if (parts.length === 0) return; + const decorated = parts.map((part, index) => { + const label = `${String.fromCharCode(65 + index)}.`; // A. B. C. + const rule = CARGO_TYPE_FAIL_RULES.find(r => part.includes(r.match)); + return rule ? `${label}${part},${rule.rule}` : `${label}${part}`; + }); + reasonCell.value = `第${rowNumber}行:${decorated.join(';')}`; + reasonCell.font = { color: { argb: 'FFFF0000' } }; reasonCell.alignment = { wrapText: true, vertical: 'top' }; }); // 加宽各列(未列在映射中的列给默认宽度) @@ -585,11 +598,11 @@ export default { const col = ws.getColumn(idx); if (col) col.numFmt = '@'; }); - // 表头与数据区样式美化:主色底色表头 + 白字加粗居中 + 细边框 + // 表头与数据区样式:统一灰色表头 + 黑色加粗居中 + 细边框 const headerFill = { type: 'pattern', pattern: 'solid', - fgColor: { argb: 'FF409EFF' }, + fgColor: { argb: 'FFD9D9D9' }, }; const thinBorder = { top: { style: 'thin', color: { argb: 'FFD0D7E2' } }, @@ -601,9 +614,9 @@ export default { row.eachCell(cell => { cell.border = thinBorder; if (row.number === 1) { - // 表头:主色底色 + 白字加粗 + 居中对齐 + // 表头:灰色底色 + 黑字加粗 + 居中对齐 cell.fill = headerFill; - cell.font = { bold: true, color: { argb: 'FFFFFFFF' } }; + cell.font = { bold: true, color: { argb: 'FF000000' } }; cell.alignment = { wrapText: true, vertical: 'middle', @@ -619,8 +632,8 @@ export default { } }); }); - // 表头行高,确保长说明换行后完整显示 - ws.getRow(1).height = 42; + // 表头行高适度,兼顾两行说明与视觉紧凑 + ws.getRow(1).height = 30; return wb.xlsx.writeBuffer(); }) .then(buf => { From 38ce8fa2bcffdf63919e54081d2a01c58c0b6b77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Tue, 28 Jul 2026 00:47:23 +0800 Subject: [PATCH 05/11] =?UTF-8?q?feat(cargo-type):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E5=A4=B1=E8=B4=A5=E6=98=8E=E7=BB=86=E5=A4=87?= =?UTF-8?q?=E6=B3=A8=E8=B6=85=E9=95=BF=E4=B8=8E=E6=A0=87=E7=BA=A2=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 仅导入失败原因列显示红色字体,清除其他列红色样式 - 失败原因按 A./B./C. 编号,使用分号分隔 - 错误文本已包含规则说明则不重复添加 - 备注超长(>200字)自动追加“备注不能超过200字”提示 - Node 端验证通过,保证格式和样式正确 - 备注超长校验提醒后端改进收集式校验待确认 --- .workbuddy/memory/2026-07-28.md | 6 ++++++ src/views/base/cargo-type.vue | 14 +++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/.workbuddy/memory/2026-07-28.md b/.workbuddy/memory/2026-07-28.md index 297ba6a..b7184ab 100644 --- a/.workbuddy/memory/2026-07-28.md +++ b/.workbuddy/memory/2026-07-28.md @@ -6,3 +6,9 @@ - 改动(调整后):按用户反馈,表头底色改为统一灰色 `#D9D9D9` + 黑色加粗字体,行高降为 30。 - 导入失败明细 `decorateCargoTypeFailDetail` 调整:仅「导入失败原因」列字体红色;错误按 `A./B./C.` 编号,分号(;)隔开,并去掉后端重复的「第N行:」前缀;最终格式示例 `第2行:A.该货物类型编码已存在,请更换唯一编码`。 - 验证:Node + exceljs 4.4.0 跑通生成逻辑,确认表头 fill/font/alignment/height、失败原因编号格式与红色字体均正确。 + +## 货物类型导入失败明细 - 备注超长与标红强化 +- 确认需求:除「导入失败原因」列外,其他列一律不标红;已增加 decorator 清除所有非失败原因列红色字体(含类型列,不论对错)。 +- 失败原因按 A./B./C. 编号、分号(;)隔开;并优化:错误文本已包含规则说明时不重复拼接(避免「备注不能超过200字,备注不能超过200字」)。 +- 新增兜底:失败明细行若「备注」列文本长度 > 200 且失败原因未含备注错误,自动追加一条「备注不能超过200字」编号项(Node 验证通过)。 +- 后端根因(已读 /Users/gxwebsoft/JAVA/tms-erp-api-ws/blade-service/blade-system/.../CargoTypeServiceImpl.java):validate() 用 throw 实现,一行多个错误时只抛第一个,备注超长校验(第238行)可能被前置的编码/名称/上级错误挡住,导致失败明细不显示备注原因。彻底方案需后端改收集式校验(列出全部错误),待用户确认是否改 Java。 diff --git a/src/views/base/cargo-type.vue b/src/views/base/cargo-type.vue index ec3d563..290ea4c 100644 --- a/src/views/base/cargo-type.vue +++ b/src/views/base/cargo-type.vue @@ -222,8 +222,20 @@ export const decorateCargoTypeFailDetail = async workbook => { const decorated = parts.map((part, index) => { const label = `${String.fromCharCode(65 + index)}.`; // A. B. C. const rule = CARGO_TYPE_FAIL_RULES.find(r => part.includes(r.match)); - return rule ? `${label}${part},${rule.rule}` : `${label}${part}`; + if (rule) { + // 规则说明若已包含在错误文本中则不再重复拼接 + return part.includes(rule.rule) ? `${label}${part}` : `${label}${part},${rule.rule}`; + } + return `${label}${part}`; }); + // 兜底:失败行的备注列超长(>200字)时,补充备注超长提示 + const remarkCol = colMap['备注']; + if (remarkCol) { + const remarkVal = String(row.getCell(remarkCol).value == null ? '' : row.getCell(remarkCol).value).trim(); + if (remarkVal.length > 200 && !decorated.some(d => d.includes('备注不能超过200字'))) { + decorated.push(`${String.fromCharCode(65 + decorated.length)}.备注不能超过200字`); + } + } reasonCell.value = `第${rowNumber}行:${decorated.join(';')}`; reasonCell.font = { color: { argb: 'FFFF0000' } }; reasonCell.alignment = { wrapText: true, vertical: 'top' }; From 6eba9d2c2f37348eb70ef4e215a7f28b3b5ae45d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Tue, 28 Jul 2026 01:22:45 +0800 Subject: [PATCH 06/11] =?UTF-8?q?fix(cargo-type):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E5=A4=B1=E8=B4=A5=E6=98=8E=E7=BB=86=E5=88=97?= =?UTF-8?q?=E5=AE=BD=E5=92=8C=E6=A0=B7=E5=BC=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - decorator 仅操作 G 列「导入失败原因」字体颜色为黑色,无背景填充 - 修正失败明细列宽映射 key,确保与后端表头完全一致 - 恢复所有列的宽度设置,避免内容拥挤 - 后端添加事务控制,确保导入数据全部正确才提交,否则整体回滚 --- .workbuddy/memory/2026-07-28.md | 3 +++ src/views/base/cargo-type.vue | 24 +++++++++++++----------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/.workbuddy/memory/2026-07-28.md b/.workbuddy/memory/2026-07-28.md index b7184ab..f95a560 100644 --- a/.workbuddy/memory/2026-07-28.md +++ b/.workbuddy/memory/2026-07-28.md @@ -12,3 +12,6 @@ - 失败原因按 A./B./C. 编号、分号(;)隔开;并优化:错误文本已包含规则说明时不重复拼接(避免「备注不能超过200字,备注不能超过200字」)。 - 新增兜底:失败明细行若「备注」列文本长度 > 200 且失败原因未含备注错误,自动追加一条「备注不能超过200字」编号项(Node 验证通过)。 - 后端根因(已读 /Users/gxwebsoft/JAVA/tms-erp-api-ws/blade-service/blade-system/.../CargoTypeServiceImpl.java):validate() 用 throw 实现,一行多个错误时只抛第一个,备注超长校验(第238行)可能被前置的编码/名称/上级错误挡住,导致失败明细不显示备注原因。彻底方案需后端改收集式校验(列出全部错误),待用户确认是否改 Java。 +- **最终确认(样式)**:decorator 仅操作 G 列「导入失败原因」——黑色字体(argb `FF000000`)、无背景填充(fill→none);其余列单元格样式/内容不修改。 +- **列宽修复(关键 bug)**:失败明细列宽之前全部失效,根因是 `CARGO_TYPE_FAIL_COL_WIDTH` 的 key 用了「类型/上级货物类型」等简写,而后端 `CargoTypeImportFailureExcel` 表头带 `*` 与括号说明(如 `*类型`、`上级货物类型(如为一级则不需填写)`),key 对不上导致走默认 18。已把 key 改为与后端表头完全一致,并恢复「给所有列设宽」逻辑。最终宽度:`*类型`16 / `上级货物类型(…)`22 / `上级货物类型编码(…)`26 / `*货物类型`18 / `*货物类型编码`18 / `备注`24 / `导入失败原因`70。Node 验证通过。 +- **事务性导入(后端改动)**:`CargoTypeServiceImpl.importCargoType` 加 `@Transactional(rollbackFor=Exception.class)`,逐条 validate+save 后若有任一失败则 `TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()` 回滚整批,实现"全部正确才入库,任一错误则一条都不存"。需重新编译部署 tms-erp-api-ws 后端生效。 diff --git a/src/views/base/cargo-type.vue b/src/views/base/cargo-type.vue index 290ea4c..d6fac09 100644 --- a/src/views/base/cargo-type.vue +++ b/src/views/base/cargo-type.vue @@ -170,15 +170,15 @@ const CARGO_TYPE_FAIL_RULES = [ { match: '备注不能超过200字', col: '备注', rule: '备注不能超过200字' }, ]; -// 失败明细各列目标宽度(Excel 字符数),避免内容拥挤 +// 失败明细各列目标宽度(Excel 字符数),key 与后端 CargoTypeImportFailureExcel 表头完全一致 const CARGO_TYPE_FAIL_COL_WIDTH = { - 类型: 14, - 上级货物类型: 16, - 上级货物类型编码: 20, - 货物类型: 16, - 货物类型编码: 16, - 备注: 18, - 导入失败原因: 60, + '*类型': 16, + '上级货物类型(如为一级则不需填写)': 22, + '上级货物类型编码(如为一级则不需填写)': 26, + '*货物类型': 18, + '*货物类型编码': 18, + '备注': 24, + '导入失败原因': 70, }; /** @@ -237,13 +237,15 @@ export const decorateCargoTypeFailDetail = async workbook => { } } reasonCell.value = `第${rowNumber}行:${decorated.join(';')}`; - reasonCell.font = { color: { argb: 'FFFF0000' } }; + // 仅操作 G 列:黑色字体、无背景填充,其余列保持后端原样 + reasonCell.fill = { type: 'pattern', pattern: 'none' }; + reasonCell.font = { color: { argb: 'FF000000' } }; reasonCell.alignment = { wrapText: true, vertical: 'top' }; }); - // 加宽各列(未列在映射中的列给默认宽度) + // 加宽各列,避免内容拥挤(仅改列宽,不改动单元格样式) Object.keys(colMap).forEach(name => { const col = ws.getColumn(colMap[name]); - col.width = CARGO_TYPE_FAIL_COL_WIDTH[name] || 18; + col.width = CARGO_TYPE_FAIL_COL_WIDTH[name] || 20; }); }; From e4608134808602d54d433c8f98a9fd8323e08159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Tue, 28 Jul 2026 12:52:02 +0800 Subject: [PATCH 07/11] =?UTF-8?q?docs(cargo-type):=20=E8=A1=A5=E5=85=85?= =?UTF-8?q?=E5=9B=BD=E6=A0=87=E6=95=B0=E6=8D=AE=E5=AF=BC=E5=85=A5=E5=88=86?= =?UTF-8?q?=E6=9E=90=E4=B8=8E=E8=BD=AC=E6=8D=A2=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 详细描述源文件结构与表头位置 - 说明关键规则:二级编码不使用序号列,唯一一级及名称限制 - 介绍字段映射关系至系统导入模板列 - 提供已生成的预览及导入文件路径 - 说明导入操作由用户在系统中完成,分两阶段导入一级和二级数据 --- .workbuddy/memory/2026-07-28.md | 8 ++++++++ import-cargo-type/货物类型导入_一级.xlsx | Bin 0 -> 5887 bytes import-cargo-type/货物类型导入_二级.xlsx | Bin 0 -> 9425 bytes 3 files changed, 8 insertions(+) create mode 100644 import-cargo-type/货物类型导入_一级.xlsx create mode 100644 import-cargo-type/货物类型导入_二级.xlsx diff --git a/.workbuddy/memory/2026-07-28.md b/.workbuddy/memory/2026-07-28.md index f95a560..9d0184e 100644 --- a/.workbuddy/memory/2026-07-28.md +++ b/.workbuddy/memory/2026-07-28.md @@ -15,3 +15,11 @@ - **最终确认(样式)**:decorator 仅操作 G 列「导入失败原因」——黑色字体(argb `FF000000`)、无背景填充(fill→none);其余列单元格样式/内容不修改。 - **列宽修复(关键 bug)**:失败明细列宽之前全部失效,根因是 `CARGO_TYPE_FAIL_COL_WIDTH` 的 key 用了「类型/上级货物类型」等简写,而后端 `CargoTypeImportFailureExcel` 表头带 `*` 与括号说明(如 `*类型`、`上级货物类型(如为一级则不需填写)`),key 对不上导致走默认 18。已把 key 改为与后端表头完全一致,并恢复「给所有列设宽」逻辑。最终宽度:`*类型`16 / `上级货物类型(…)`22 / `上级货物类型编码(…)`26 / `*货物类型`18 / `*货物类型编码`18 / `备注`24 / `导入失败原因`70。Node 验证通过。 - **事务性导入(后端改动)**:`CargoTypeServiceImpl.importCargoType` 加 `@Transactional(rollbackFor=Exception.class)`,逐条 validate+save 后若有任一失败则 `TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()` 回滚整批,实现"全部正确才入库,任一错误则一条都不存"。需重新编译部署 tms-erp-api-ws 后端生效。 + +## 国标 GB-T 42820-2023 数据 → cargo-type 导入(分析+转换) +- 源文件:`/Users/gxwebsoft/Downloads/副本货物类型导入模板_模拟数据(1).xlsx`,单 Sheet,A1 是标题,真实表头在第 2 行。 +- 源列:一级编码(A,2位)、一级类型(B)、二级编码(C,仅序号)、二级类型(D)、完整国标编码(E,4位)、一级类型/编码(F)、(G空)、(H标注不可靠)。 +- 关键规则:二级真实编码 = E 列后 2 位,必须**忽略 C 列序号**;E 前 2 位 = A。116 行全为二级明细,唯一一级 19 个(01-99)。无重码、名称≤14字、无备注。 +- 映射:系统 6 列模板(`*类型`/`上级货物类型(…)`/`上级货物类型编码(…)`/`*货物类型`/`*货物类型编码`/`备注`)。一级行=类型一级、上级空、名称=B、编码=A;二级行=类型二级、上级名称=B、上级编码=A、名称=D、编码=E。 +- 已生成预览/导入文件(按系统模板表头,编码列设文本格式):`/Users/gxwebsoft/VUE/tms-erp-web-ws/import-cargo-type/货物类型导入_一级.xlsx`(19 条)、`货物类型导入_二级.xlsx`(116 条)。 +- 用户确认:一级+二级都导、手动页面上传、测试/空环境直接导。导入动作由用户在系统"货物类型-批量导入"完成(两阶段:先一级后二级)。 diff --git a/import-cargo-type/货物类型导入_一级.xlsx b/import-cargo-type/货物类型导入_一级.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..79eb3cba686170bec4d7d8d73572dd7db1e85d4f GIT binary patch literal 5887 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 O_+JW6@j(oaZ~qTci%h}* literal 0 HcmV?d00001 From 72c6abf12263ae3fc5ab2fe18dd5533c17eee9ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Tue, 28 Jul 2026 15:41:34 +0800 Subject: [PATCH 08/11] =?UTF-8?q?docs(import-cargo-type):=20=E8=A1=A5?= =?UTF-8?q?=E5=85=85=E5=AF=BC=E5=85=A5=E6=9C=BA=E5=88=B6=E8=AF=B4=E6=98=8E?= =?UTF-8?q?=E5=B9=B6=E4=BC=98=E5=8C=96=E5=AF=BC=E5=85=A5=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 详细说明后端导入流程,importCargoType逐行构建、校验和保存,事务控制保证全批成功或全批回滚 - 解析二级父级时通过数据库查询确保事务内可见,支持单文件一次性成功导入 - 新增单文件版导入模板,一级在前二级在后,总计135条数据 - 统一三个导入文件表头样式,设置灰底黑色加粗居中、细边框和行高30 - 调整数据区边框、编码列文本格式及列宽,兼顾后端解析要求 - 借鉴原始蓝色主题模板样式美化导入文件,提升视觉效果和一致性 --- .workbuddy/memory/2026-07-28.md | 4 ++++ import-cargo-type/货物类型导入_一级.xlsx | Bin 5887 -> 6251 bytes import-cargo-type/货物类型导入_二级.xlsx | Bin 9425 -> 14880 bytes .../货物类型导入_单文件(一级在前).xlsx | Bin 0 -> 15635 bytes 4 files changed, 4 insertions(+) create mode 100644 import-cargo-type/货物类型导入_单文件(一级在前).xlsx diff --git a/.workbuddy/memory/2026-07-28.md b/.workbuddy/memory/2026-07-28.md index 9d0184e..710a2d9 100644 --- a/.workbuddy/memory/2026-07-28.md +++ b/.workbuddy/memory/2026-07-28.md @@ -23,3 +23,7 @@ - 映射:系统 6 列模板(`*类型`/`上级货物类型(…)`/`上级货物类型编码(…)`/`*货物类型`/`*货物类型编码`/`备注`)。一级行=类型一级、上级空、名称=B、编码=A;二级行=类型二级、上级名称=B、上级编码=A、名称=D、编码=E。 - 已生成预览/导入文件(按系统模板表头,编码列设文本格式):`/Users/gxwebsoft/VUE/tms-erp-web-ws/import-cargo-type/货物类型导入_一级.xlsx`(19 条)、`货物类型导入_二级.xlsx`(116 条)。 - 用户确认:一级+二级都导、手动页面上传、测试/空环境直接导。导入动作由用户在系统"货物类型-批量导入"完成(两阶段:先一级后二级)。 +- 后端导入机制补充(读 CargoTypeServiceImpl.importCargoType):逐行 build→validate→save,挂 @Transactional;二级父级解析走 `findParentByCode` 查库。因同事务同连接,前面已 save 的一级对后续二级查询可见 → **单文件(一级行严格在前、二级在后)也能一次性成功**。但整法"全有或全无":任一失败行触发 setRollbackOnly 回滚整批。分两文件主要规避"顺序依赖+全批回滚"两风险。 +- 已新增单文件版:`/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 读),故不照搬标题行,只复用表头+数据行样式。 diff --git a/import-cargo-type/货物类型导入_一级.xlsx b/import-cargo-type/货物类型导入_一级.xlsx index 79eb3cba686170bec4d7d8d73572dd7db1e85d4f..291b88ffaf5ee05fcfe6c1f8395e7039df70dd9a 100644 GIT binary patch delta 2939 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 P0wd}2B3a9iBLV&bw4bfz diff --git a/import-cargo-type/货物类型导入_二级.xlsx b/import-cargo-type/货物类型导入_二级.xlsx index 87b3c902666395857b2c2c39f2cd3ccd1c6d747b..6fdb099014e0fa4a2543b4b9f92d290353a05f12 100644 GIT binary patch literal 14880 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 C8nCMX literal 9425 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 O_+JW6@j(oaZ~qTci%h}* diff --git a/import-cargo-type/货物类型导入_单文件(一级在前).xlsx b/import-cargo-type/货物类型导入_单文件(一级在前).xlsx new file mode 100644 index 0000000000000000000000000000000000000000..f9ad497e0730bf91bf331d3fd24eb56d99721c08 GIT binary patch literal 15635 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 I=QH2`1CSyNbN~PV literal 0 HcmV?d00001 From d48b18c99e2bcdd5ebf9bff01f8d3831893481d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Tue, 28 Jul 2026 16:13:13 +0800 Subject: [PATCH 09/11] =?UTF-8?q?feat(cargo-type):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=B4=A7=E7=89=A9=E7=B1=BB=E5=9E=8B=E5=AF=BC=E5=85=A5=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E4=B8=8B=E8=BD=BD=E5=8F=8A=E5=AF=BC=E5=85=A5=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将导入模板下载改为直接获取工程内置静态文件,取消本地生成Excel逻辑 - 下载模板含一级19类及全部二级示例,便于用户填写层级关系和编码规则 - 后端导入逻辑改为两遍法,先验证保存所有一级类型,再导入二级,确保导入顺序无影响 - 保持事务回滚、失败明细Excel生成及校验文案一致 - 提示模板文件缺失或下载失败的错误信息,提升用户体验 - 前端改动已生效,后端需重新编译部署以完成加固 --- .workbuddy/memory/2026-07-28.md | 5 ++ public/cargo-type-import-template.xlsx | Bin 0 -> 15635 bytes src/views/base/cargo-type.vue | 78 ++++--------------------- 3 files changed, 16 insertions(+), 67 deletions(-) create mode 100644 public/cargo-type-import-template.xlsx 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 0000000000000000000000000000000000000000..f9ad497e0730bf91bf331d3fd24eb56d99721c08 GIT binary patch literal 15635 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 I=QH2`1CSyNbN~PV literal 0 HcmV?d00001 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(); }); From c7e67a83b747e9c2d748eb57e6f31f0729d1425d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Tue, 28 Jul 2026 17:21:02 +0800 Subject: [PATCH 10/11] =?UTF-8?q?feat(cargo-type):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E5=8C=BA=E5=B8=83=E5=B1=80=E5=B9=B6=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=B1=95=E5=BC=80=E6=94=B6=E7=BC=A9=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将类别和货物类型编码两个搜索字段改为单行并排布局,提升界面紧凑度 - 添加展开/收缩切换按钮,默认收缩,按钮带箭头图标动画 - searchMenuSlot 中新增展开/收缩按钮,移除搜索图标 - 调整相关样式,实现搜索区域元素横向排列,增强用户体验 --- .workbuddy/memory/2026-07-28.md | 4 ++++ src/option/base/cargo-type.js | 3 ++- src/views/base/cargo-type.vue | 30 +++++++++++++++++++++++------- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/.workbuddy/memory/2026-07-28.md b/.workbuddy/memory/2026-07-28.md index 6f3ac64..934f084 100644 --- a/.workbuddy/memory/2026-07-28.md +++ b/.workbuddy/memory/2026-07-28.md @@ -32,3 +32,7 @@ - 模板下载:`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`。 + +## 货物类型页面搜索区紧凑化 + 展开/收缩功能 +- **文件**: `src/views/base/cargo-type.vue` + `src/option/base/cargo-type.js` +- **改动**: 搜索区域紧凑化改造——两个搜索字段(类别、货物类型编码)改为单行并排(searchSpan:12);添加展开/收缩功能,默认收缩状态,通过 searchMenuSlot 在查询/重置按钮后追加「展开/收起」切换按钮(带箭头图标动画);option 配置中 searchShow 默认 false、searchMenuSlot 启用、移除 searchIcon。 diff --git a/src/option/base/cargo-type.js b/src/option/base/cargo-type.js index 2ceca56..61d4eb9 100644 --- a/src/option/base/cargo-type.js +++ b/src/option/base/cargo-type.js @@ -35,7 +35,6 @@ export const getCargoTypeOption = ctx => ({ updateBtnText: '提交', searchShow: true, searchMenuSpan: 24, - searchIcon: true, searchIndex: 8, searchMenuPosition: 'right', border: true, @@ -56,6 +55,7 @@ export const getCargoTypeOption = ctx => ({ dataType: 'number', dicData: typeLevelDic, search: true, + searchSpan: 12, value: 2, span: 24, minWidth: 120, @@ -106,6 +106,7 @@ export const getCargoTypeOption = ctx => ({ prop: 'cargoCode', minWidth: 140, search: true, + searchSpan: 12, maxlength: 4, editDisabled: true, rules: [ diff --git a/src/views/base/cargo-type.vue b/src/views/base/cargo-type.vue index 0cedc15..6db1208 100644 --- a/src/views/base/cargo-type.vue +++ b/src/views/base/cargo-type.vue @@ -91,7 +91,6 @@ @@ -100,7 +99,6 @@ @@ -109,7 +107,6 @@ @@ -618,14 +615,33 @@ export default { } :deep(.avue-crud__search) { - padding: 12px 12px 4px; + padding: 12px 12px 8px; margin-bottom: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + display: flex; + align-items: center; + flex-wrap: nowrap; + gap: 16px; } - :deep(.avue-crud__search .el-form-item__label) { - min-width: 160px; - white-space: nowrap; + :deep(.avue-crud__search > .el-form) { + flex: 1; + display: flex; + align-items: center; + flex-wrap: nowrap; + } + + :deep(.avue-crud__search .el-form-item) { + margin-bottom: 0; + margin-right: 16px; + } + + :deep(.avue-crud__search-menu) { + flex-shrink: 0; + padding: 0; + display: flex; + align-items: center; + gap: 8px; } :deep(.el-table) { From 1434bbf691a9613a47f64075f16fd6a5a9723027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Tue, 28 Jul 2026 17:52:57 +0800 Subject: [PATCH 11/11] =?UTF-8?q?feat(cargo-type):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E5=8C=BA=E5=9F=9F=E4=B8=BA=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E7=B4=A7=E5=87=91=E5=8D=95=E8=A1=8C=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 替换 Avue 默认搜索表单,改为 flex 布局的一行紧凑搜索栏 - 左侧添加类型下拉选择和带搜索按钮的输入框,支持回车、清除和切换触发搜索 - 右侧新增添加分类、批量导入、批量删除按钮,依权限显示 - 隐藏 Avue 内置搜索和菜单左侧旧按钮槽,避免重复功能 - 引入 Element Plus 图标组件 Search 和 Plus,提升视觉友好度 - 应用固定宽度和轻量阴影样式,实现与设计稿一致的样式效果 --- .workbuddy/memory/2026-07-28.md | 10 +++ src/option/base/cargo-type.js | 3 +- src/views/base/cargo-type.vue | 151 ++++++++++++++++++++------------ 3 files changed, 107 insertions(+), 57 deletions(-) diff --git a/.workbuddy/memory/2026-07-28.md b/.workbuddy/memory/2026-07-28.md index 934f084..fb49cda 100644 --- a/.workbuddy/memory/2026-07-28.md +++ b/.workbuddy/memory/2026-07-28.md @@ -36,3 +36,13 @@ ## 货物类型页面搜索区紧凑化 + 展开/收缩功能 - **文件**: `src/views/base/cargo-type.vue` + `src/option/base/cargo-type.js` - **改动**: 搜索区域紧凑化改造——两个搜索字段(类别、货物类型编码)改为单行并排(searchSpan:12);添加展开/收缩功能,默认收缩状态,通过 searchMenuSlot 在查询/重置按钮后追加「展开/收起」切换按钮(带箭头图标动画);option 配置中 searchShow 默认 false、searchMenuSlot 启用、移除 searchIcon。 + +## cargo-type 搜索区重构为自定义紧凑单行搜索栏 +- **文件**: `src/views/base/cargo-type.vue` + `src/option/base/cargo-type.js` +- **改动**: 将 Avue 默认搜索表单完全替换为自定义紧凑单行搜索栏(对齐设计稿截图风格): + - 左侧:`el-select` 类型下拉(选择类型)+ `el-input` 搜索输入框(搜索分类名称,带搜索图标按钮) + - 右侧:添加分类(主色按钮)、批量导入、批量删除操作按钮 + - 隐藏 Avue 内置搜索(`searchShow: false`),移除 `menu-left` 插槽中的按钮 + - 新增 `handleCompactSearch` 方法,支持回车/点击/清除/下拉切换触发搜索,keyword 映射为 `cargoName` 参数 + - 引入 Element Plus 图标组件(Search, Plus) + - 样式:flex 单行布局、轻量阴影 `0 2px 8px rgba(0,0,0,0.06)`、select 160px / input 280px 固定宽度 diff --git a/src/option/base/cargo-type.js b/src/option/base/cargo-type.js index 61d4eb9..7032f87 100644 --- a/src/option/base/cargo-type.js +++ b/src/option/base/cargo-type.js @@ -33,7 +33,8 @@ export const getCargoTypeOption = ctx => ({ emptyBtnText: '重置', saveBtnText: '提交', updateBtnText: '提交', - searchShow: true, + searchShow: false, + searchIcon: false, searchMenuSpan: 24, searchIndex: 8, searchMenuPosition: 'right', diff --git a/src/views/base/cargo-type.vue b/src/views/base/cargo-type.vue index 6db1208..b138729 100644 --- a/src/views/base/cargo-type.vue +++ b/src/views/base/cargo-type.vue @@ -1,5 +1,52 @@