feat: 导出word

main
Lei OT 4 months ago
parent b4bca91fea
commit 63a6604b0b

280
package-lock.json generated

@ -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",

@ -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",

@ -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: <NavLink to="/cruise">三峡游船</NavLink> },
{ key: 'hotel', label: <NavLink to="/hotel">酒店</NavLink> },
{ key: 'hostcase', label: <NavLink to="/hostcase/count">东道主项目</NavLink> },
{ key: 'hostcasereport', label: <NavLink to="/hostcase/report">东道主报告</NavLink> },
],
},
{
@ -277,7 +279,8 @@ const App = () => {
<Route path="/cruise" element={<Cruise />} />
<Route path="/hotel" element={<Hotel />} />
<Route path="/hostcase/count" element={<HostCaseCount />} />
</Route>
<Route path="/hostcase/report" element={<HostCaseReport />} />
</Route>
<Route element={<ProtectedRoute auth={['admin', 'director_bu', 'financial']} />}>
<Route path="/credit_card_bill" element={<Credit_card_bill />} />
<Route path="/exchange_rate" element={<ExchangeRate />} />

@ -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 (
<>
<Button size='small' onClick={() => {}}>
导出 .docx
</Button>
</>
);
};
export default ExportDocxBtn;

@ -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`);
}

@ -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,
}),
],
});
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

@ -316,7 +316,7 @@ function getFields(props) {
item(
'agency',
99,
<Form.Item name={'agency'}>
<Form.Item name={'agency'} initialValue={at(props, 'initialValue.agency')[0] || undefined} {...fieldProps.agency}>
<SearchInput autoGet url="/service-web/QueryData/GetVEIName" map={{ 'CAV_VEI_SN': 'key', 'VEI2_CompanyBN': 'label' }} resultkey={'result1'} placeholder="所有地接社" {...fieldProps.agency} />
</Form.Item>
),

@ -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 (
<>
<Space direction="vertical" style={{ width: '100%' }}>
<Row gutter={16} className={toJS(date_picker_store.siderBroken) ? '' : 'sticky-top'}>
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...toJS(date_picker_store.formValues),
...searchValues,
},
shows: ['dates', 'agency'],
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple' },
dates: { hide_vs: true },
agency: { rules: [{ required: true, message: '请选择地接社' }] },
},
}}
onSubmit={(_err, obj, form) => {
setSearchValues(obj, form);
getData(obj);
}}
/>
</Col>
</Row>
<div className="max-w-screen-xl " style={{}}>
<Divider orientation="right">
<Button size='small'
onClick={() => {
exportDoc(forExport.agencyName, forExport.title, [
{ tableTitle: '2025年东道主总体情况', tableColumns: summaryCols, tableData: caseSummary },
{ tableTitle: '2025年导游实施东道主情况', tableColumns: guideSummaryCols, tableData: caseSummaryByGuide },
{ tableTitle: '2025年精品案例', tableColumns: featuredCaseCols, tableData: caseFeatured },
]);
}}
>
导出 .docx
</Button>
</Divider>
<Typography.Title level={3}>2025年东道主总体情况</Typography.Title>
<Table dataSource={caseSummary} columns={summaryCols} loading={loading} pagination={false} bordered />
<Typography.Title level={3}>2025年导游实施东道主情况</Typography.Title>
<Table dataSource={caseSummaryByGuide} columns={guideSummaryCols} loading={loading} pagination={false} bordered rowKey={'TGI_SN'} />
<Typography.Title level={3}>2025年精品案例</Typography.Title>
<Table dataSource={caseFeatured} columns={featuredCaseCols} loading={loading} pagination={false} bordered rowKey={'case_id'} />
</div>
</Space>
</>
);
};
export default observer(HostCaseReport);

@ -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;
Loading…
Cancel
Save