From 8e4d8b2d76c793fe1553b6c02f43274b4a857700 Mon Sep 17 00:00:00 2001 From: huwei <3313749341@qq.com> Date: Fri, 14 Aug 2026 18:14:12 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 5 +- pnpm-lock.yaml | 72 +++++++++++++++++++ .../MonitorManage/SurveyMonitor/index.js | 51 +++++++++---- 3 files changed, 112 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 5e3eb69..a1f4301 100644 --- a/package.json +++ b/package.json @@ -28,18 +28,19 @@ "@eigenpal/docx-editor-react": "^1.9.0", "@rc-component/motion": "^1.0.0", "@rc-component/util": "^1.11.1", - "pizzip": "latest", - "docxtemplater": "latest", "antd": "^5.29.2", "china-division": "^2.7.0", "dayjs": "^1.11.7", + "docxtemplater": "latest", "echarts": "^6.1.0", "history": "^4.10.1", "html2canvas": "^1.4.1", "jspdf": "^2.5.2", "lodash-es": "^4.17.21", + "pizzip": "latest", "react": "^18.2.0", "react-dom": "^18.2.0", + "xlsx": "^0.18.5", "zy-react-library": "^1.2.3" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b27a05f..83a4171 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -68,6 +68,9 @@ importers: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + xlsx: + specifier: ^0.18.5 + version: 0.18.5 zy-react-library: specifier: ^1.2.3 version: 1.3.26(@types/react@19.2.17)(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.0.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.16)(slate@0.72.0)(snabbdom@3.6.4))(debug@4.4.3(supports-color@8.0.0))(prop-types@15.8.1)(rc-field-form@2.7.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react-router@5.1.2(react@18.2.0))(supports-color@8.0.0) @@ -2570,6 +2573,10 @@ packages: add-dom-event-listener@1.1.0: resolution: {integrity: sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==} + adler-32@1.3.1: + resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==} + engines: {node: '>=0.8'} + agent-base@6.0.0: resolution: {integrity: sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==} engines: {node: '>= 6.0.0'} @@ -2797,6 +2804,10 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + cfb@1.2.2: + resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==} + engines: {node: '>=0.8'} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -2857,6 +2868,10 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + codepage@1.15.0: + resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==} + engines: {node: '>=0.8'} + color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -2980,6 +2995,11 @@ packages: resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} engines: {node: '>= 0.10'} + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + cropperjs@1.6.2: resolution: {integrity: sha512-nhymn9GdnV3CqiEHJVai54TULFAE3VshJTXSqSJKa8yXAKyBKDWdhHarnlIPrshJ0WMFTGuFvG02YjLXfPiuOA==} @@ -3721,6 +3741,10 @@ packages: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} + frac@1.1.2: + resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==} + engines: {node: '>=0.8'} + fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} @@ -5689,6 +5713,10 @@ packages: resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} engines: {node: '>=6'} + ssf@0.11.2: + resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==} + engines: {node: '>=0.8'} + ssr-window@3.0.0: resolution: {integrity: sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==} @@ -6140,10 +6168,18 @@ packages: wildcard@1.1.2: resolution: {integrity: sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==} + wmf@1.0.2: + resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==} + engines: {node: '>=0.8'} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + word@0.3.0: + resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==} + engines: {node: '>=0.8'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -6180,6 +6216,11 @@ packages: resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} engines: {node: '>=18'} + xlsx@0.18.5: + resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==} + engines: {node: '>=0.8'} + hasBin: true + xml-js@1.6.11: resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==} hasBin: true @@ -9643,6 +9684,8 @@ snapshots: dependencies: object-assign: 4.1.1 + adler-32@1.3.1: {} + agent-base@6.0.0(supports-color@8.0.0): dependencies: debug: 4.4.3(supports-color@8.0.0) @@ -9997,6 +10040,11 @@ snapshots: ccount@2.0.1: {} + cfb@1.2.2: + dependencies: + adler-32: 1.3.1 + crc-32: 1.2.2 + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -10055,6 +10103,8 @@ snapshots: clsx@2.1.1: {} + codepage@1.15.0: {} + color-convert@1.9.3: dependencies: color-name: 1.1.3 @@ -10162,6 +10212,8 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 + crc-32@1.2.2: {} + cropperjs@1.6.2: {} cross-env@7.0.3: @@ -11079,6 +11131,8 @@ snapshots: forwarded@0.2.0: {} + frac@1.1.2: {} + fresh@0.5.2: {} fsevents@2.3.3: @@ -13662,6 +13716,10 @@ snapshots: split-on-first@1.1.0: {} + ssf@0.11.2: + dependencies: + frac: 1.1.2 + ssr-window@3.0.0: {} ssr-window@4.0.2: {} @@ -14133,8 +14191,12 @@ snapshots: wildcard@1.1.2: {} + wmf@1.0.2: {} + word-wrap@1.2.5: {} + word@0.3.0: {} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.1.0 @@ -14155,6 +14217,16 @@ snapshots: dependencies: is-wsl: 3.1.1 + xlsx@0.18.5: + dependencies: + adler-32: 1.3.1 + cfb: 1.2.2 + codepage: 1.15.0 + crc-32: 1.2.2 + ssf: 0.11.2 + wmf: 1.0.2 + word: 0.3.0 + xml-js@1.6.11: dependencies: sax: 1.6.1 diff --git a/src/pages/Container/MonitorManage/SurveyMonitor/index.js b/src/pages/Container/MonitorManage/SurveyMonitor/index.js index 2418c67..4170fa7 100644 --- a/src/pages/Container/MonitorManage/SurveyMonitor/index.js +++ b/src/pages/Container/MonitorManage/SurveyMonitor/index.js @@ -17,6 +17,8 @@ import { } from "antd"; import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd"; import { DownloadOutlined } from "@ant-design/icons"; +// 2026-08-14 导出打卡记录使用 xlsx 库生成标准 Excel 文件 +import * as XLSX from "xlsx"; import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout"; import SearchForm from "~/components/SearchForm"; import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper"; @@ -126,7 +128,7 @@ const parseRoleList = (v) => { const parsed = JSON.parse(trimmed); return Array.isArray(parsed) ? parsed : [parsed]; } catch (e) { - // 解析失败按逗号拆分兜底 + // 解析失败按逗号拆分兜底(过滤空白元素) return trimmed.slice(1, -1).split(",").map((s) => s.trim()).filter(Boolean); } } @@ -135,10 +137,16 @@ const parseRoleList = (v) => { return []; }; -/** 项目角色编码 → 中文展示(多角色用"、"连接),未匹配到则原样展示 */ +/** 项目角色编码 → 中文展示(多角色用"、"连接),未匹配到则原样展示。 + * 2026-08-14 修复:过滤空白元素,并清理 join 后开头/结尾多余的分隔符,避免"角色1、角色2、"。 */ const roleText = (v) => { - const list = parseRoleList(v); - return list.map((r) => ROLE_DEFINITIONS_MAP[r] || r).join("、") || "-"; + const list = parseRoleList(v) + .map((r) => (r || "").toString().trim()) + .filter(Boolean) + .map((r) => ROLE_DEFINITIONS_MAP[r] || r); + if (!list.length) return "-"; + // join 后去掉开头/结尾可能残留的顿号(兼容解析异常场景) + return list.join("、").replace(/^、+|、+$/g, ""); }; const STAT_ITEMS = [ @@ -273,7 +281,10 @@ const SurveyMonitor = (props) => { } }; - // 2026-08-14 新增:导出打卡记录(前端 CSV 导出,无需后端接口) + // 2026-08-14 新增:导出打卡记录(前端生成真正的 xlsx 文件,列严格对齐) + // 原因:CSV/HTML 表格在 WPS/Excel 中可能列错位或格子被隐藏。 + // 采用 xlsx 库生成标准 Excel 文件,cell 内容由 sheet 严格按行列放置, + // 并显式设置列宽,保证所有格子内容完整显示、不被隐藏。 // 字段对应原型打卡弹窗表格:人员/项目角色/人脸识别/ GPS 定位/签到时间/签退时间/现场照片/本人签字 const handleExportClockIn = () => { if (!clockInData.length) { @@ -285,7 +296,7 @@ const SurveyMonitor = (props) => { ]; const rows = clockInData.map((row) => [ row.userName || "-", - // 2026-08-14 修复:复用全局 roleText,兼容后端 JSON 数组字符串 + // 2026-08-14 修复:复用全局 roleText,兼容后端 JSON 数组字符串,并清理末尾分隔符 roleText(row.role), row.faceState === 1 ? "通过" : "未通过", row.gpsState === 1 ? "正常" : "异常", @@ -294,14 +305,26 @@ const SurveyMonitor = (props) => { row.sceneUrl ? row.sceneUrl.split(",").filter(Boolean).length + "张" : "-", row.signatureUrl ? "已签字" : "未签字", ]); - // 构造 CSV(含 BOM 头,保证 Excel 正确识别中文) - const csv = "" + [header, ...rows].map((r) => r.join(",")).join("\r\n"); - const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" }); - const link = document.createElement("a"); - link.href = URL.createObjectURL(blob); - link.download = `${clockInProjectName || "项目"}_打卡记录.csv`; - link.click(); - URL.revokeObjectURL(link.href); + + // 2026-08-14 使用 xlsx 库生成标准 .xlsx 文件 + // 引入方式:为避免打包 tree-shaking 问题,用 `import * as XLSX from "xlsx"`(文件顶部已导入) + const aoa = [header, ...rows]; + const ws = XLSX.utils.aoa_to_sheet(aoa); + // 显式设置每列宽度,避免内容被隐藏/截断(宽度按列索引,单位约为字符数) + ws["!cols"] = [ + { wch: 18 }, // 人员 + { wch: 32 }, // 项目角色(多角色较长) + { wch: 12 }, // 人脸识别 + { wch: 12 }, // GPS定位 + { wch: 22 }, // 签到时间 + { wch: 22 }, // 签退时间 + { wch: 12 }, // 现场照片 + { wch: 12 }, // 本人签字 + ]; + const wb = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(wb, ws, "打卡记录"); + // writeFile 直接触发浏览器下载,生成真正的 .xlsx + XLSX.writeFile(wb, `${clockInProjectName || "项目"}_打卡记录.xlsx`); }; const clockInColumns = [