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;