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 0000000..8aa036f Binary files /dev/null and b/src/components/TemplateLetter2025/cht letter header logo.png differ 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;