From 63a6604b0baf6fa5afd7710b0b4cdd9b05ec7329 Mon Sep 17 00:00:00 2001 From: Lei OT Date: Wed, 10 Dec 2025 09:51:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AF=BC=E5=87=BAword?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 280 +++++++++- package.json | 2 + src/App.jsx | 5 +- .../TemplateLetter2025/ExportDocxBtn.jsx | 61 +++ src/components/TemplateLetter2025/Index.jsx | 507 ++++++++++++++++++ .../TemplateLetter2025/TemplateLetter2025.jsx | 308 +++++++++++ .../cht letter header logo.png | Bin 0 -> 19398 bytes src/components/search/SearchForm.jsx | 2 +- src/views/HostCaseReport.jsx | 102 ++++ src/zustand/HostCase.js | 103 ++++ 10 files changed, 1362 insertions(+), 8 deletions(-) create mode 100644 src/components/TemplateLetter2025/ExportDocxBtn.jsx create mode 100644 src/components/TemplateLetter2025/Index.jsx create mode 100644 src/components/TemplateLetter2025/TemplateLetter2025.jsx create mode 100644 src/components/TemplateLetter2025/cht letter header logo.png create mode 100644 src/views/HostCaseReport.jsx create mode 100644 src/zustand/HostCase.js diff --git a/package-lock.json b/package-lock.json index d48d3fe..ede07a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,8 @@ "@ant-design/pro-components": "^2.6.16", "antd": "^4.22.6", "dingtalk-jsapi": "^3.0.9", + "docx": "^9.5.1", + "file-saver": "^2.0.5", "immer": "^10.2.0", "insert-css": "^2.0.0", "mobx": "^6.6.1", @@ -5471,9 +5473,13 @@ "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" }, "node_modules/@types/node": { - "version": "18.14.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz", - "integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==" + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -8639,6 +8645,41 @@ "node": ">=6.0.0" } }, + "node_modules/docx": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/docx/-/docx-9.5.1.tgz", + "integrity": "sha512-ABDI7JEirFD2+bHhOBlsGZxaG1UgZb2M/QMKhLSDGgVNhxDesTCDcP+qoDnDGjZ4EOXTRfUjUgwHVuZ6VSTfWQ==", + "license": "MIT", + "dependencies": { + "@types/node": "^24.0.1", + "hash.js": "^1.1.7", + "jszip": "^3.10.1", + "nanoid": "^5.1.3", + "xml": "^1.0.1", + "xml-js": "^1.6.8" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/docx/node_modules/nanoid": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, "node_modules/dom-align": { "version": "1.12.3", "resolved": "https://registry.npmmirror.com/dom-align/-/dom-align-1.12.3.tgz", @@ -10275,6 +10316,12 @@ "webpack": "^4.0.0 || ^5.0.0" } }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==", + "license": "MIT" + }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -11153,6 +11200,16 @@ "node": ">= 0.4" } }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -11468,6 +11525,12 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/immer": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", @@ -14223,6 +14286,48 @@ "node": ">=4.0" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/kdbush": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", @@ -14345,6 +14450,15 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -15323,6 +15437,12 @@ "node": ">=6" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -18881,6 +19001,12 @@ "node": ">=6.9" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -20140,6 +20266,12 @@ "which-boxed-primitive": "^1.0.2" } }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -21267,6 +21399,24 @@ "node": ">=0.8" } }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "license": "MIT" + }, + "node_modules/xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "license": "MIT", + "dependencies": { + "sax": "^1.2.4" + }, + "bin": { + "xml-js": "bin/cli.js" + } + }, "node_modules/xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", @@ -25454,9 +25604,12 @@ "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" }, "@types/node": { - "version": "18.14.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz", - "integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==" + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "requires": { + "undici-types": "~7.16.0" + } }, "@types/parse-json": { "version": "4.0.0", @@ -27881,6 +28034,26 @@ "esutils": "^2.0.2" } }, + "docx": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/docx/-/docx-9.5.1.tgz", + "integrity": "sha512-ABDI7JEirFD2+bHhOBlsGZxaG1UgZb2M/QMKhLSDGgVNhxDesTCDcP+qoDnDGjZ4EOXTRfUjUgwHVuZ6VSTfWQ==", + "requires": { + "@types/node": "^24.0.1", + "hash.js": "^1.1.7", + "jszip": "^3.10.1", + "nanoid": "^5.1.3", + "xml": "^1.0.1", + "xml-js": "^1.6.8" + }, + "dependencies": { + "nanoid": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==" + } + } + }, "dom-align": { "version": "1.12.3", "resolved": "https://registry.npmmirror.com/dom-align/-/dom-align-1.12.3.tgz", @@ -29085,6 +29258,11 @@ "schema-utils": "^3.0.0" } }, + "file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" + }, "filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -29733,6 +29911,15 @@ "has-symbols": "^1.0.2" } }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -29963,6 +30150,11 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==" }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "immer": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", @@ -31980,6 +32172,46 @@ "object.assign": "^4.1.3" } }, + "jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "kdbush": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", @@ -32077,6 +32309,14 @@ "type-check": "~0.4.0" } }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "requires": { + "immediate": "~3.0.5" + } + }, "lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -32817,6 +33057,11 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -35240,6 +35485,11 @@ "resolved": "https://registry.npmmirror.com/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz", "integrity": "sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==" }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -36199,6 +36449,11 @@ "which-boxed-primitive": "^1.0.2" } }, + "undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==" + }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -37064,6 +37319,19 @@ "version": "https://cdn.sheetjs.com/xlsx-0.18.11/xlsx-0.18.11.tgz", "integrity": "sha512-+q621V6l7SsX4c07IFLlW4f7HbblWrrNJMQmVobWKGPmIE7aFIkHFslclK1S75AEjk0L/Y0oupN/aroqONtIyg==" }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==" + }, + "xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "requires": { + "sax": "^1.2.4" + } + }, "xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", diff --git a/package.json b/package.json index 529eb3d..ec2dc47 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,8 @@ "@ant-design/pro-components": "^2.6.16", "antd": "^4.22.6", "dingtalk-jsapi": "^3.0.9", + "docx": "^9.5.1", + "file-saver": "^2.0.5", "immer": "^10.2.0", "insert-css": "^2.0.0", "mobx": "^6.6.1", diff --git a/src/App.jsx b/src/App.jsx index f6f1d75..bd561f8 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -57,6 +57,7 @@ import Cruise from './views/Cruise'; import Hotel from './views/Hotel'; import HostCaseCount from './views/HostCaseCount'; import TrainsUpsell from './views/biz/reports/TrainsUpsell'; +import HostCaseReport from './views/HostCaseReport'; const App = () => { const { Content, Footer, Sider, } = Layout; @@ -170,6 +171,7 @@ const App = () => { { key: 'cruise', label: 三峡游船 }, { key: 'hotel', label: 酒店 }, { key: 'hostcase', label: 东道主项目 }, + { key: 'hostcasereport', label: 东道主报告 }, ], }, { @@ -277,7 +279,8 @@ const App = () => { } /> } /> } /> - + } /> + }> } /> } /> diff --git a/src/components/TemplateLetter2025/ExportDocxBtn.jsx b/src/components/TemplateLetter2025/ExportDocxBtn.jsx new file mode 100644 index 0000000..1579505 --- /dev/null +++ b/src/components/TemplateLetter2025/ExportDocxBtn.jsx @@ -0,0 +1,61 @@ +import { Button } from 'antd'; +// import { useProductsAuditStatesMapVal, useProductsTypesMapVal } from '@/hooks/useProductsSets'; +// import { useTranslation } from 'react-i18next'; +// import useProductsStore, { getAgencyAllExtrasAction } from '@/stores/Products/Index'; +// import RequireAuth from '@/components/RequireAuth'; +// import { PERM_PRODUCTS_OFFER_AUDIT } from '@/config'; +// import dayjs from 'dayjs'; + +// import AgencyContract from '../Print/AgencyContract'; +import { saveAs } from 'file-saver'; +import { Packer } from 'docx'; +// import { isEmpty } from '@/utils/commons'; + +const ExportDocxBtn = ({ ...props }) => { + // const { t } = useTranslation(); + // const [agencyProducts] = useProductsStore((state) => [state.agencyProducts]); + // const [activeAgency] = useProductsStore((state) => [state.activeAgency]); + // const { travel_agency_id, use_year, audit_state } = params; + + // const auditStatesMap = useProductsAuditStatesMapVal(); + // const productsTypesMapVal = useProductsTypesMapVal(); + + // const { getRemarkList } = useProductsStore((selector) => ({ + // getRemarkList: selector.getRemarkList, + // })); + // const handleDownload = async () => { + // // await refresh(); + // const _agencyExtras = await getAgencyAllExtrasAction(params); + // const agencyExtras = Object.keys(_agencyExtras).reduce((acc, pid) => { + // const pitemExtras = _agencyExtras[pid]; + // const _pitem = (pitemExtras || []).map(eitem => ({ ...eitem, info: { ...eitem.info, product_type_name_txt: productsTypesMapVal[eitem.info.product_type_id]?.label || eitem.info.product_type_name } } )); + // return { ...acc, [pid]: _pitem }; + // }, {}); + // const remarks = await getRemarkList(); + // const remarksMappedByType = remarks.reduce((r, v) => ({ ...r, [v.product_type_id]: v }), {}); + // const documentCreator = new AgencyContract(); + // const doc = documentCreator.create([ + // params, + // activeAgency, + // agencyProducts, + // agencyExtras, + // // remarks, + // remarksMappedByType, + // ]); + + // const _d = dayjs().format('YYYYMMDD_HH.mm.ss.SSS'); // Date.now().toString(32) + // // console.log(params); + // const _state = isEmpty(audit_state) ? '' : auditStatesMap[audit_state].label; + // Packer.toBlob(doc).then((blob) => { + // saveAs(blob, `${activeAgency.travel_agency_name}${use_year}年地接合同-${_state}-${_d}.docx`); + // }); + // }; + return ( + <> + + + ); +}; +export default ExportDocxBtn; diff --git a/src/components/TemplateLetter2025/Index.jsx b/src/components/TemplateLetter2025/Index.jsx new file mode 100644 index 0000000..1595ce6 --- /dev/null +++ b/src/components/TemplateLetter2025/Index.jsx @@ -0,0 +1,507 @@ +import { + Packer, + Paragraph, + Table, + TableRow, + TableCell, + WidthType, + TextRun, + AlignmentType, + FrameAnchorType, + HorizontalPositionAlign, + VerticalPositionAlign, + HeadingLevel, + LevelFormat, + NumberFormat, + PageNumber, + BorderStyle, + LineRuleType, + HorizontalPositionRelativeFrom, + VerticalPositionRelativeFrom, +} from 'docx'; +import * as docx from 'docx'; +import { saveAs } from 'file-saver'; +import logoPath from './cht letter header logo.png'; + +const pageMargins = { + top: `10mm`, + bottom: `10mm`, + left: `20mm`, + right: `20mm`, +}; +// Helper function (e.g., in a separate file or within your component logic) +async function getLogoArrayBuffer(logoUrl) { + const response = await fetch(logoUrl); + const arrayBuffer = await response.arrayBuffer(); + return arrayBuffer; +} + +/** + * 只支持两级的嵌套表头 + */ +function buildTable(cols, data) { + const spanCols = cols.some((col) => col?.children?.length) + ? [null].reduce((a, c, ri) => { + const r0col = cols.map((col) => ({ ...col, rowSpan: col?.children?.length ? 1 : 2, columnSpan: col?.children?.length ? col.children.length : 1 })); + a.push(r0col); + const r1col = cols + .filter((cc) => cc?.children?.length) + .reduce((ac, cc) => { + const allChild = cc.children.map((ccc) => ({ ...ccc })); + ac.push(...allChild); + return ac; + }, []); + a.push(r1col); + return a; + }, []) + : [cols]; + const flatCols = cols.some((col) => col?.children?.length) + ? cols.reduce((a, col) => { + col?.children?.length === undefined && a.push({ ...col, rowSpan: col?.children?.length ? 1 : 2, colSpan: col?.children?.length ? col.children.length : 1 }); + const r1col = (col?.children || []).map((ccc) => ({ ...ccc, rowSpan: 1, colSpan: 1 })); + return a.concat(r1col); + }, []) + : cols; + const thead = spanCols.map( + (th) => + new TableRow({ + children: th.map( + (td) => + new TableCell({ + rowSpan: td.rowSpan, + columnSpan: td.columnSpan, + children: [new Paragraph({ text: td.title, alignment: AlignmentType.CENTER, style: 'TableHeader' })], + verticalAlign: AlignmentType.CENTER, + }) + ), + }) + ); + const rows = data.map( + (row, ci) => + new TableRow({ + children: flatCols.map( + (cell) => + new TableCell({ + // width: { size: 100 / flatCols.length, type: WidthType.PERCENTAGE }, + margins: { + top: 100, + bottom: 100, + left: 100, + right: 100, + }, + alignment: typeof row[cell.dataIndex] === 'number' ? AlignmentType.RIGHT : AlignmentType.START, + verticalAlign: AlignmentType.CENTER, + children: [ + new Paragraph({ + text: String(row[cell.dataIndex]), + verticalAlign: AlignmentType.CENTER, + alignment: typeof row[cell.dataIndex] === 'number' ? AlignmentType.RIGHT : AlignmentType.START, + }), + ], + }) + ), + }) + ); + return new Table({ + rows: [...thead, ...rows], + width: { size: 100, type: WidthType.PERCENTAGE }, + }); +} + +function createTitle(text) { + return new Paragraph({ + text, + heading: HeadingLevel.TITLE, + alignment: AlignmentType.CENTER, + pageBreakBefore: true, + }); +} + +const createHeaderText = () => + new Paragraph({ + children: [ + new TextRun('Hello World'), // ✅ Required before Textbox + // new docx.Textbox({ + // // anchor: { + // // horizontal: FrameAnchorType.MARGIN, + // // vertical: FrameAnchorType.MARGIN, + // // }, + // // alignment: { + // // x: HorizontalPositionAlign.RIGHT, + // // y: VerticalPositionAlign.TOP, + // // }, + // alignment: 'right', + // verticalAlign: AlignmentType.END, + // children: [ + // new TextRun({ + // text: '111Tel: 86-773-2885311', + // break: 1, + // }), + // new TextRun({ + // text: 'Fax: 86-773-2827424', + // break: 1, + // }), + // new TextRun({ + // text: 'E-mail: products@chinahighlights.com', + // break: 1, + // }), + // new TextRun({ + // text: 'Web site: https://www.chinahighlights.com', + // break: 1, + // }), + // ], + // positioning: 'Floating', + // floating: { + // horizontalPosition: { + // relative: HorizontalPositionAlign.LEFT, + // offset: 0, + // }, + // verticalPosition: { + // relative: VerticalPositionAlign.TOP, + // offset: 0, + // }, + // }, + // // style: { width: "200pt", height: "auto" } + // }), + ], + }); +const createHeaderRight = () => + new Paragraph({ + frame: { + // position: 0, + width: 4000, + height: 800, + anchor: { + horizontal: FrameAnchorType.MARGIN, + vertical: FrameAnchorType.MARGIN, + }, + alignment: { + x: HorizontalPositionAlign.RIGHT, + y: VerticalPositionAlign.TOP, + }, + }, + alignment: AlignmentType.RIGHT, + verticalAlign: AlignmentType.END, + style: 'Header', + // thematicBreak: true, + // border: { + // top: { color: 'auto', space: 10, value: BorderStyle.DOUBLE, size: 20 }, + // bottom: { color: 'bf192a', space: 10, value: BorderStyle.DOUBLE, size: 20 }, + // left: { color: 'auto', space: 10, value: BorderStyle.DOUBLE, size: 20 }, + // right: { color: 'auto', space: 10, value: BorderStyle.DOUBLE, size: 20 }, + // }, + floating: { + horizontalPosition: { + relative: HorizontalPositionAlign.LEFT, + offset: 0, + }, + verticalPosition: { + relative: VerticalPositionAlign.TOP, + offset: 0, + }, + }, + children: [ + new TextRun({ + text: 'Tel: 86-773-2885311', + break: 1, + }), + new TextRun({ + text: 'Fax: 86-773-2827424', + break: 1, + }), + new TextRun({ + text: 'E-mail: products@chinahighlights.com', + break: 1, + }), + new TextRun({ + text: 'Web site: https://www.chinahighlights.com', + break: 1, + }), + ], + }); +// Template function +const createDoc = async (agencyName, title, sectionsData) => { + const logoArrayBuffer = await getLogoArrayBuffer(logoPath); + + const image = new docx.ImageRun({ + // type: 'png', + data: logoArrayBuffer, + // Set dimensions in **EMUs** (English Metric Units) or **pixels**. + // If using pixels, docx converts them, but EMUs (914400 EMUs = 1 inch) are standard. + // E.g., for a 75x75 pixel image, you might use: + // width: 75, + // height: 75, + transformation: { + width: 100, + height: 66, + }, + // positioning: 'Floating', + // floating: { + // horizontalPosition: { + // relative: HorizontalPositionAlign.LEFT, + // offset: 0, + // }, + // verticalPosition: { + // relative: VerticalPositionAlign.TOP, + // offset: 0, + // }, + // }, + }); + + const doc = new docx.Document({ + creator: 'China Highlights', + subject: 'CH信笺2025', + styles: { + paragraphStyles: [ + { + id: 'Header', + name: 'Header', + quickFormat: true, + basedOn: 'Normal', + next: 'Normal', + run: { + size: 16, + font: { name: 'Verdana' }, + color: '000000', + // underline: { type: 'single', width: 1, color: 'bf192a' }, + }, + paragraph: { + spacing: { + // after: 200, + // line: 240, + // lineRule: LineRuleType.AT_LEAST, + }, + }, + }, + { + id: 'Footer', + name: 'Footer', + quickFormat: true, + run: { + size: 16, + font: { name: 'Verdana' }, + color: '000000', + // underline: { type: 'single', width: 1, color: 'bf192a' }, + }, + paragraph: { + spacing: { + after: 100, + line: 240, + // lineRule: + }, + }, + }, + { + id: 'Normal', + name: 'Normal', + quickFormat: true, + run: { + size: 22, + font: { name: '宋体' }, + color: '000000', + }, + paragraph: { + spacing: { + after: 10, + }, + }, + }, + { + id: 'Title', + name: 'Title', + basedOn: 'Normal', + next: 'Normal', + quickFormat: true, + run: { + size: 44, + font: { name: '宋体' }, + color: '000000', + }, + paragraph: { + spacing: { + before: 200, + after: 200, + }, + }, + }, + { + id: 'Heading1', + name: 'Heading 1', + basedOn: 'Normal', + next: 'Normal', + quickFormat: true, + run: { + size: 32, + font: { name: '宋体' }, + color: '000000', + }, + paragraph: { + spacing: { + before: 200, + after: 200, + }, + }, + }, + { + id: 'Heading2', + name: 'Heading 2', + basedOn: 'Normal', + next: 'Normal', + quickFormat: true, + run: { + size: 28, + font: { name: '宋体' }, + color: '000000', + }, + paragraph: { + spacing: { + before: 120, + after: 120, + }, + }, + }, + { + id: 'TableHeader', + name: 'Table Header', + basedOn: 'Normal', + next: 'Normal', + quickFormat: true, + run: { + size: 22, + font: { name: '宋体' }, + color: '000000', + bold: true, + }, + paragraph: { + spacing: { + before: 80, + after: 80, + }, + }, + }, + ], + }, + numbering: { + config: [ + { + reference: 'header1', + levels: [{ level: 0, text: '%1、', format: LevelFormat.CHINESE_COUNTING }], + }, + ], + }, + // 目录 + features: { + updateFields: true, + }, + sections: [ + { + properties: { + page: { + pageNumbers: { + start: 1, + formatType: NumberFormat.DECIMAL, + }, + margin: pageMargins, + }, + }, + headers: { + default: new docx.Header({ + children: [ + new docx.Paragraph({ + children: [image], + alignment: docx.AlignmentType.LEFT, // Align the image in the header + style: 'Header', + // todo: 边框位置被图文框占用 + // thematicBreak: true, + border: { + top: { style: 'none', size: 10, space: 0, color: 'bf192a' }, + bottom: { style: 'inset', size: 20, space: 0, color: 'bf192a' }, + left: { style: 'none', size: 10, space: 0, color: 'bf192a' }, + right: { style: 'none', size: 10, space: 0, color: 'bf192a' }, + }, + }), + createHeaderRight(), + // createHeaderText(), + ], + }), + }, + footers: { + default: new docx.Footer({ + children: [ + new Paragraph({ + alignment: AlignmentType.END, + style: 'Footer', + // thematicBreak: true, + border: { + top: { style: 'none', size: 10, space: 0, color: 'bf192a' }, + bottom: { style: 'inset', size: 20, space: 0, color: 'bf192a' }, + left: { style: 'none', size: 10, space: 0, color: 'bf192a' }, + right: { style: 'none', size: 10, space: 0, color: 'bf192a' }, + }, + children: [ + new TextRun({ + children: ['- ', PageNumber.CURRENT, ' -'], + size: 18, + }), + ], + }), + new Paragraph({ + alignment: AlignmentType.CENTER, + style: 'Footer', + children: [ + new TextRun({ + text: '中国 桂林市七里店路70号创意产业园6号楼4层 桂林海纳国际旅行社有限公司 邮编541004', + // break: 1, + }), + new TextRun({ + text: 'China Highlights, Discovery Your Way (Since 1959)!', + break: 1, + font: 'Arial', + bold: true, + }), + ], + }), + // new Paragraph({ + // alignment: AlignmentType.RIGHT, + // children: [ + // new TextRun({ + // text: `${new Date().toLocaleString()}系统生成`, + // italics: true, + // size: 20, + // }), + // ], + // }), + ], + }), + }, + children: [ + createTitle(agencyName), + new docx.TableOfContents('toc', { + hyperlink: true, + headingStyleRange: '1-5', + }), + + createTitle(title), + ...sectionsData.reduce((arr, { tableTitle, tableColumns, tableData }) => { + const _tableTitle = new Paragraph({ + text: tableTitle, + heading: HeadingLevel.HEADING_1, + alignment: AlignmentType.START, + numbering: { reference: 'header1', level: 0 }, + }); + const table = buildTable(tableColumns, tableData); + return [...arr, _tableTitle, table]; + }, []), + ], + }, + ], + }); + return doc; +}; + +// Export function +export async function exportDoc(agencyName, title, sectionsData) { + const doc = await createDoc(agencyName, title, sectionsData); + const blob = await Packer.toBlob(doc); + saveAs(blob, `${title}.${agencyName}.docx`); +} diff --git a/src/components/TemplateLetter2025/TemplateLetter2025.jsx b/src/components/TemplateLetter2025/TemplateLetter2025.jsx new file mode 100644 index 0000000..3501e87 --- /dev/null +++ b/src/components/TemplateLetter2025/TemplateLetter2025.jsx @@ -0,0 +1,308 @@ +import { isEmpty } from '@/utils/commons'; +import dayjs from 'dayjs'; +import { + AlignmentType, + BorderStyle, + Document, + Footer, + Header, + HeadingLevel, + LevelFormat, + NumberFormat, + PageNumber, + Paragraph, + Tab, + Table, + TableCell, + TableRow, + TabStopType, + TextRun, + WidthType, + Media, +} from 'docx'; +// import { splitTable_6, splitTable_7, splitTable_B, splitTable_D, splitTable_J, splitTable_Q, splitTable_R, splitTable_8 } from '@/hooks/useProductsQuotationFormat'; +// import { formatGroupSize } from '@/hooks/useProductsSets'; +import logo from './cht letter header logo.png'; + +const unitMap = { + '0': '人', + '1': '团', +}; + +const DOC_TITLE = '地接合同'; +const pageMargins = { + top: `15mm`, + bottom: `15mm`, + left: `10mm`, + right: `10mm`, +}; + +const tableBorderNone = { + top: { style: BorderStyle.NONE, size: 0, color: 'FFFFFF' }, + bottom: { style: BorderStyle.NONE, size: 0, color: 'FFFFFF' }, + left: { style: BorderStyle.NONE, size: 0, color: 'FFFFFF' }, + right: { style: BorderStyle.NONE, size: 0, color: 'FFFFFF' }, +}; +const tableBorderOne = { + top: { style: BorderStyle.SINGLE, space: 0, size: 6, color: 'auto' }, + bottom: { style: BorderStyle.SINGLE, space: 0, size: 6, color: 'auto' }, + left: { style: BorderStyle.SINGLE, space: 0, size: 6, color: 'auto' }, + right: { style: BorderStyle.SINGLE, space: 0, size: 6, color: 'auto' }, +}; +const tableBorderInner = { + top: { style: BorderStyle.NONE, space: 0, size: 6, color: 'auto' }, + bottom: { style: BorderStyle.INSET, space: 0, size: 6, color: 'auto' }, + left: { style: BorderStyle.NONE, space: 0, size: 6, color: 'auto' }, + right: { style: BorderStyle.INSET, space: 0, size: 6, color: 'auto' }, +}; +const tableBorderInnerB = { + top: { style: BorderStyle.NONE, space: 0, size: 6, color: 'auto' }, + bottom: { style: BorderStyle.INSET, space: 0, size: 6, color: 'auto' }, + left: { style: BorderStyle.NONE, space: 0, size: 6, color: 'auto' }, + right: { style: BorderStyle.NONE, space: 0, size: 6, color: 'auto' }, +}; +const tableBorderInnerDashB = { + top: { style: BorderStyle.NONE, space: 0, size: 6, color: 'auto' }, + bottom: { style: BorderStyle.DASHED, space: 0, size: 6, color: 'auto' }, + left: { style: BorderStyle.NONE, space: 0, size: 6, color: 'auto' }, + right: { style: BorderStyle.NONE, space: 0, size: 6, color: 'auto' }, +}; +const tableBorderInnerT = { + top: { style: BorderStyle.INSET, space: 0, size: 6, color: 'auto' }, + bottom: { style: BorderStyle.NONE, space: 0, size: 6, color: 'auto' }, + left: { style: BorderStyle.NONE, space: 0, size: 6, color: 'auto' }, + right: { style: BorderStyle.NONE, space: 0, size: 6, color: 'auto' }, +}; +const tableBorderInnerR = { + top: { style: BorderStyle.NONE, space: 0, size: 6, color: 'auto' }, + bottom: { style: BorderStyle.NONE, space: 0, size: 6, color: 'auto' }, + left: { style: BorderStyle.NONE, space: 0, size: 6, color: 'auto' }, + right: { style: BorderStyle.INSET, space: 0, size: 6, color: 'auto' }, +}; +/** + * @version + * @date 2025-08-20 + */ +export default class TemplateLetter2025 { + #remarkList = {}; + create([headerParams, activeAgency, agencyProducts, agencyExtras, remarks]) { + this.#remarkList = remarks; + const { use_year } = headerParams; + const h1 = `${activeAgency.travel_agency_name}${use_year}年${DOC_TITLE}`; + const yearStart = dayjs(`${use_year}-01-01`).format('YYYY.MM.DD'); + const yearEnd = dayjs(`${use_year}-12-31`).format('YYYY.MM.DD'); + + const document = new Document({ + creator: 'China Highlights', + title: h1, + subject: 'CH信笺2025', + styles: { + paragraphStyles: [ + { + id: 'Normal', + name: 'Normal', + quickFormat: true, + run: { + size: 22, + font: { name: '宋体' }, + color: '000000', + }, + paragraph: { + spacing: { + after: 10, + }, + }, + }, + { + id: 'Title', + name: 'Title', + basedOn: 'Normal', + next: 'Normal', + quickFormat: true, + run: { + size: 44, + font: { name: '宋体' }, + color: '000000', + }, + paragraph: { + spacing: { + before: 200, + after: 200, + }, + }, + }, + { + id: 'Heading1', + name: 'Heading 1', + basedOn: 'Normal', + next: 'Normal', + quickFormat: true, + run: { + size: 32, + font: { name: '宋体' }, + color: '000000', + }, + paragraph: { + spacing: { + before: 200, + after: 200, + }, + }, + }, + { + id: 'Heading2', + name: 'Heading 2', + basedOn: 'Normal', + next: 'Normal', + quickFormat: true, + run: { + size: 28, + font: { name: '宋体' }, + color: '000000', + }, + paragraph: { + spacing: { + before: 120, + after: 120, + }, + }, + }, + { + id: 'TableHeader', + name: 'Table Header', + basedOn: 'Normal', + next: 'Normal', + quickFormat: true, + run: { + size: 22, + font: { name: '宋体' }, + color: '000000', + bold: true, + }, + paragraph: { + spacing: { + before: 80, + after: 80, + }, + }, + }, + ], + }, + numbering: { + config: [ + { + reference: 'products-type', + levels: [{ level: 0, text: '%1、', format: LevelFormat.CHINESE_COUNTING }], + }, + { + reference: 'terms', + levels: [{ level: 0, text: '%1.', format: LevelFormat.DECIMAL }], + }, + ], + }, + sections: [ + { + properties: { + page: { + pageNumbers: { + start: 1, + formatType: NumberFormat.DECIMAL, + }, + margin: pageMargins, + }, + }, + headers: { + default: new Header({ + children: [ + this.createPageHeaderRightText(`Tel: 86-773-2885311`, { italics: false }), + this.createPageHeaderRightText(`Fax: 86-773-2827424`, { italics: false }), + this.createPageHeaderRightText(`E-mail: products@chinahighlights.com`, { italics: false }), + this.createPageHeaderRightText(`Web site: https://www.chinahighlights.com`, { italics: false }), + ], + }), + }, + footers: { + default: new Footer({ + children: [ + new Paragraph({ + alignment: AlignmentType.CENTER, + children: [ + new TextRun({ + children: ['第', PageNumber.CURRENT, '页'], + size: 20, + }), + new TextRun({ + children: [', 共 ', PageNumber.TOTAL_PAGES, '页'], + size: 20, + }), + ], + }), + new Paragraph({ + alignment: AlignmentType.RIGHT, + children: [ + new TextRun({ + text: `${new Date().toLocaleString()}系统生成`, + italics: true, + size: 20, + }), + ], + }), + ], + }), + }, + children: [], + }, + ], + }); + + return document; + } + + createHeading(text) { + return new Paragraph({ + text: text, + heading: HeadingLevel.HEADING_1, + alignment: AlignmentType.CENTER, + }); + } + + createTitle(text) { + return new Paragraph({ + text: text, + heading: HeadingLevel.TITLE, + alignment: AlignmentType.CENTER, + }); + } + + createSubHeading(text, style = {}) { + return new Paragraph({ + text: text, + heading: HeadingLevel.HEADING_1, + ...style, + }); + } + + async createPageHeaderLeftLogo(doc) { + const response = await fetch(logo); + const imageBuffer = await response.arrayBuffer(); + return new Paragraph({ + children: [ + Media.addImage(doc, imageBuffer, 120, 60), // width, height + ], + }); + } + + createPageHeaderRightText(text, style = {}) { + return new Paragraph({ + alignment: AlignmentType.RIGHT, + children: [ + new TextRun({ + text: text, + italics: true, + size: 20, + ...style, + }), + ], + }); + } +} diff --git a/src/components/TemplateLetter2025/cht letter header logo.png b/src/components/TemplateLetter2025/cht letter header logo.png new file mode 100644 index 0000000000000000000000000000000000000000..8aa036fc9679cd5fc2991bfff7391818b322e819 GIT binary patch literal 19398 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 literal 0 HcmV?d00001 diff --git a/src/components/search/SearchForm.jsx b/src/components/search/SearchForm.jsx index 3f94fcc..e62fc09 100644 --- a/src/components/search/SearchForm.jsx +++ b/src/components/search/SearchForm.jsx @@ -316,7 +316,7 @@ function getFields(props) { item( 'agency', 99, - + ), diff --git a/src/views/HostCaseReport.jsx b/src/views/HostCaseReport.jsx new file mode 100644 index 0000000..44ba47b --- /dev/null +++ b/src/views/HostCaseReport.jsx @@ -0,0 +1,102 @@ +import { useContext } from 'react'; +import { Row, Col, Typography, Space, Table, Divider, Button } from 'antd'; +import { stores_Context } from '../config'; +import { observer } from 'mobx-react'; +import { toJS } from 'mobx'; +import SearchForm from '../components/search/SearchForm'; +import useHostCaseStore from '../zustand/HostCase'; +import { useShallow } from 'zustand/shallow'; +import { exportDoc } from '../components/TemplateLetter2025/Index'; + +const HostCaseReport = ({ ...props }) => { + const { date_picker_store } = useContext(stores_Context); + // const host_case_data = customer_store.host_case_data; + + const [loading, reset, searchValues, setSearchValues, forExport] = useHostCaseStore( + useShallow((state) => [state.loading, state.reset, state.searchValues, state.setSearchValues, state.forExport]) + ); + const [caseSummary, caseSummaryByGuide, caseFeatured, getCaseReport] = useHostCaseStore( + useShallow((state) => [state.caseSummary, state.caseSummaryByGuide, state.caseFeatured, state.getCaseReport]) + ); + + const getData = async (formVal) => { + reset(); + await getCaseReport(formVal); + }; + + const summaryCols = [ + { title: '接团数', dataIndex: 'group_count', width: '6rem' }, + { title: '东道主团数', dataIndex: 'group_count_dongdaozhu', width1: '8rem' }, + { title: '东道主个数', dataIndex: 'case_count_dongdaozhu', width1: '8rem' }, + { title: '东道主实施比例', dataIndex: 'dongdaozhu_rate' }, + { + title: '各类型个数', + children: [ + { title: 'Live There', dataIndex: 'live_there_count' }, + { title: '动机圆梦', dataIndex: 'dream_fulfillment_count' }, + { title: '仪式感创造', dataIndex: 'ceremony_creation_count' }, + { title: '遗憾弥补', dataIndex: 'regret_compensation_count' }, + { title: '力挽狂澜', dataIndex: 'rescue_mission_count' }, + ], + }, + ]; + const guideSummaryCols = [{ title: '姓名', dataIndex: 'TGI_Name', width: '6rem' }, ...summaryCols]; + const featuredCaseCols = [ + { title: '团号', dataIndex: 'GRI_No', width: '16rem' }, + { title: '导游', dataIndex: 'chinese_name', width: '8rem' }, + { title: '案例类型', dataIndex: 'case_name', width: '8rem' }, + { title: '案例', dataIndex: 'case_text' }, + ]; + + return ( + <> + + + + { + setSearchValues(obj, form); + getData(obj); + }} + /> + + +
+ + + + 2025年东道主总体情况 + + 2025年导游实施东道主情况 +
+ 2025年精品案例 +
+ + + + ); +}; + +export default observer(HostCaseReport); diff --git a/src/zustand/HostCase.js b/src/zustand/HostCase.js new file mode 100644 index 0000000..e4c247d --- /dev/null +++ b/src/zustand/HostCase.js @@ -0,0 +1,103 @@ +import { create } from 'zustand'; +import { devtools } from 'zustand/middleware'; +import { immer } from 'zustand/middleware/immer'; +import { fetchJSON } from '../utils/request'; +import { HT_HOST } from '../config'; +import moment from 'moment'; + +/** + * Transforms an array of row data into an array of objects, + * using a separate array of column names as keys. + * + * @param {string[]} cols - The array of column names (keys). + * @param {any[][]} rows - The array of row data (values). + * @returns {Object[]} The transformed array of objects. + */ +export const transformRows = (cols, rows) => { + return rows.map((row) => { + return row.reduce((acc, val, i) => { + acc[cols[i].display_name] = val; + return acc; + }, {}); + }); +}; + +export const fetchCaseSummary = async (params) => { + const searchParams = { + ...params, + vei_sn: params.agency, + }; + const { errcode, result } = await fetchJSON(`${HT_HOST}/service-Analyse2/dong_dao_zhu_total`, searchParams); + return errcode !== 0 ? [] : (result || []); +}; +export const fetchCaseSummaryByGuide = async (params) => { + const searchParams = { + ...params, + vei_sn: params.agency, + }; + const { errcode, result } = await fetchJSON(`${HT_HOST}/service-Analyse2/dong_dao_zhu_tour_guide`, searchParams); + return errcode !== 0 ? [] : (result || []); // .sort(sortDescBy('case_count_dongdaozhu')); +}; +export const fetchCaseFeatured = async (params) => { + const searchParams = { + ...params, + vei_sn: params.agency, + }; + const { errcode, result } = await fetchJSON(`${HT_HOST}/service-Analyse2/dong_dao_zhu_case`, searchParams); + return errcode !== 0 ? [] : (result || []); +}; + +/** + * 东道主报告---------------------------------------------------------------------------------------------- + */ +const initialState = { + loading: false, + loadingCase: false, + caseSummary: [], + caseSummaryByGuide: [], + caseFeatured: [], + + forExport: { + agencyName: '', + title: '', + } + +}; + +const useHostCaseStore = create( + devtools( + immer((set, get) => ({ + ...initialState, + searchValues: {}, + searchValuesToSub: {}, + reset: () => set(initialState), + + setLoading: (loading) => set({ loading }), + setSearchValues: (obj, values) => set((state) => ({ searchValues: values, searchValuesToSub: obj })), + + setLoadingCase: (loadingCase) => set({ loadingCase }), + setCaseFeatured: (caseFeatured) => set({ caseFeatured }), + setCaseSummary: (caseSummary) => set({ caseSummary }), + setCaseSummaryByGuide: (caseSummaryByGuide) => set({ caseSummaryByGuide }), + + async getCaseReport(params) { + const { setLoading, searchValues } = get(); + setLoading(true); + const [summary, guideSummary, featured] = await Promise.all([ + fetchCaseSummary(params), + fetchCaseSummaryByGuide(params), + fetchCaseFeatured(params) + ]); + set({ + caseSummary: summary, + caseSummaryByGuide: guideSummary, + caseFeatured: featured, + forExport: { title: `${moment(params.Date1).format('YYYY年MM月')}-${moment(params.Date2).format('YYYY年MM月')}总结`, agencyName: searchValues.agency.label }, + }); + setLoading(false); + }, + })), + { name: 'hostCaseReportStore' } + ) +); +export default useHostCaseStore;