Compare commits

...

63 Commits

Author SHA1 Message Date
Lei OT 9edfa0c5b8 fix: 市场数据导出, 格式优化 7 days ago
Lei OT 195d0facaa 2.14.7 2 weeks ago
Lei OT 0f2041cdc8 perf: +来源战点 `Thailand` 2 weeks ago
Lei OT ce4e23fe6c perf: 老客户: 状态字段 2 weeks ago
Lei OT 62ba5b5973 # 1 month ago
Lei OT 986f27eb28 2.14.6 1 month ago
Lei OT 9a59a5ac0e perf: 老客户-分析: 目的地城市的省/地区汇总, 去除重复计算的城市 1 month ago
Lei OT 11be41a446 perf: 老客户-分析: 国籍: 按语种汇总 2 months ago
Lei OT da3f4458ff 2.14.5 2 months ago
Lei OT e2d91d9df8 perf: 老客户: 显示原条件占比 2 months ago
Lei OT 594426048c 2.14.4 2 months ago
Lei OT d6c34f145e perf: 老客户: 明细: 走团国家 2 months ago
Lei OT 90714b6f82 perf: 老客户-分析: +目的地城市 2 months ago
Lei OT ae07f7593b feat: 外联业绩×页面类型 3 months ago
Lei OT 460dd47f1c perf: 老客户: 只显示市场占比 3 months ago
Lei OT 46f3a52784 2.14.3 3 months ago
Lei OT 93fa13ac9d perf: 老客户: +字段: 城市, 产品类型 3 months ago
Lei OT 5351239f5f perf: 数据透视: +分销客户 3 months ago
Lei OT a44a05de26 2.14.2 3 months ago
Lei OT c023dd5b68 perf: 老客户-分析: 国籍汇总计算 3 months ago
Lei OT 16e6ec574a feat: 老客户-分析: 增加`目的地国家` 3 months ago
Lei OT ead5b37ded perf: 分销: +显示字段 3 months ago
Lei OT 5f7a942842 fix: 数据透视: 目的地国家/城市 空值 不省略 3 months ago
Lei OT 1f5d6a9047 perf: 字段文字 3 months ago
Lei OT 7cb91abea2 2.14.1 3 months ago
ybc 8581038aeb 优化导游语种组件 3 months ago
Lei OT 3b24aa1373 perf: 分销订单统计: 表头字段 3 months ago
ybc cb994aff29 Merge branch 'main' of github.com:hainatravel-it/dashboard 3 months ago
ybc a48ed8ff03 增加小组, 导游语种, 对比日期 3 months ago
Lei OT 8cd446d8f8 2.14.0 3 months ago
Lei OT 559ba14e35 feat: 分销订单统计 3 months ago
Lei OT a2b920b313 refactor: RenderVSDataCell 组件 3 months ago
Lei OT 9793f62b34 perf: 老客户: 默认选项 + GH 3 months ago
Lei OT a66ed759f8 perf: 老客户. 明细; -->zustand 3 months ago
Lei OT a9a2fa8a29 2.13.3 3 months ago
Lei OT 3da72b3287 refactor: RenderVSDataCell 组件 3 months ago
Lei OT 259afa80f1 perf: 老客户. 明细; -->zustand 3 months ago
Lei OT d9fccc5433 停用财务: 汇率; 信用卡账单 3 months ago
Lei OT 407ac32e93 refactor: `VSDataTag`. 部分 3 months ago
Lei OT 7a9bd5531e fix: Pareto Y轴 3 months ago
Lei OT dfc6515029 refactor: `VSDataTag`. 部分 3 months ago
Lei OT d5bc8a639f 地接接团 --> zustand 3 months ago
Lei OT e6fe06a533 `@haina/utils-pagespy` 3 months ago
Lei OT af9fab4ea8 删除request, commons- 3 months ago
Lei OT 7c00c9167e refactor: `urils/commons` --> `@haina/utils-commons`, `@haina/utils-request` 部分 3 months ago
Lei OT e581f9df73 refactor: `zustand/` : `urils/commons` --> `@haina/utils-commons, `@haina/utils-request`, 3 months ago
Lei OT abb95fc4d0 feat: 客服: 地接社接团信息: 改统计与HT一致; +站外好评, 投诉, 东道主等
refactor: `urils/commons` --> `@haina/utils-commons, `@haina/utils-request`, `@haina/utils-pagespy`
3 months ago
Lei OT b0700feebb 2.13.2 4 months ago
Lei OT b9b12297e1 perf: 老客户: 增加对比: 毛利占比(市场); 订单数占比(市场) 4 months ago
Lei OT 2bfd6e9f7f perf: 综合年度: 小组: 多选 4 months ago
Lei OT a6a8139e78 2.13.1 4 months ago
Lei OT 59bc3d0a18 test: 老客户 4 months ago
Lei OT d86d9f8282 feat: 数据透视: 增加对比 4 months ago
Lei OT 313b9bdaa4 fix: 数据透视: 页面渠道 LineClass 4 months ago
Lei OT e39fe19559 perf: 东道主报告: 选择年份; 排序 4 months ago
Lei OT 97f6bdeb9e fix: 修复导出word 在office打开提示无法查看文件 4 months ago
Lei OT 4f42623896 perf: 市场-订单数据: 表格分页 4 months ago
Lei OT 3116dc8ac3 test: 老客户: 禁用明细 4 months ago
Lei OT 214266fa74 test: 老客户: 禁用明细 4 months ago
Lei OT 18e199deac perf: 导出word 的目录页面后插入分节符 4 months ago
Lei OT d9e9ffa362 perf: 导出word 的目录索引 4 months ago
Lei OT 13aa0b1ea0 2.13.0 4 months ago
Lei OT 63a6604b0b feat: 导出word 4 months ago

306
package-lock.json generated

@ -1,17 +1,21 @@
{
"name": "haina-dashboard",
"version": "2.12.3",
"version": "2.14.7",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "haina-dashboard",
"version": "2.12.3",
"version": "2.14.7",
"dependencies": {
"@ant-design/charts": "^1.4.2",
"@ant-design/pro-components": "^2.6.16",
"@haina/utils-commons": "https://research.hainatravel.com/npm/utils-commons-0.1.2.tgz",
"@haina/utils-request": "https://research.hainatravel.com/npm/utils-request-0.1.2.tgz",
"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",
@ -3841,6 +3845,18 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@haina/utils-commons": {
"version": "0.1.2",
"resolved": "https://research.hainatravel.com/npm/utils-commons-0.1.2.tgz",
"integrity": "sha512-7BhbQ6aLTzhLm7lHyswVj4Jl2FGfj9/W5xQiLYcEkzZGdQViXa7ILxVHPFLKPeAD4LdcWJBpeS3fOQ/Z3XHwdg==",
"hasInstallScript": true
},
"node_modules/@haina/utils-request": {
"version": "0.1.2",
"resolved": "https://research.hainatravel.com/npm/utils-request-0.1.2.tgz",
"integrity": "sha512-TnaqEMT/WrIGVFxEH0x0qxjwnIuvNoDOJrw/vACYdpSsdx1TjFwOPjh0bLaiBn41tvVGWK8QDUwQqfB9pFFxOg==",
"hasInstallScript": true
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
@ -5471,9 +5487,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 +8659,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 +10330,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 +11214,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 +11539,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 +14300,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 +14464,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 +15451,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 +19015,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 +20280,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 +21413,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",
@ -24241,6 +24405,14 @@
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.46.0.tgz",
"integrity": "sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA=="
},
"@haina/utils-commons": {
"version": "https://research.hainatravel.com/npm/utils-commons-0.1.2.tgz",
"integrity": "sha512-7BhbQ6aLTzhLm7lHyswVj4Jl2FGfj9/W5xQiLYcEkzZGdQViXa7ILxVHPFLKPeAD4LdcWJBpeS3fOQ/Z3XHwdg=="
},
"@haina/utils-request": {
"version": "https://research.hainatravel.com/npm/utils-request-0.1.2.tgz",
"integrity": "sha512-TnaqEMT/WrIGVFxEH0x0qxjwnIuvNoDOJrw/vACYdpSsdx1TjFwOPjh0bLaiBn41tvVGWK8QDUwQqfB9pFFxOg=="
},
"@humanwhocodes/config-array": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
@ -25454,9 +25626,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 +28056,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 +29280,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 +29933,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 +30172,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 +32194,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 +32331,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 +33079,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 +35507,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 +36471,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 +37341,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",

@ -1,12 +1,16 @@
{
"name": "haina-dashboard",
"version": "2.12.3",
"version": "2.14.7",
"private": true,
"dependencies": {
"@ant-design/charts": "^1.4.2",
"@ant-design/pro-components": "^2.6.16",
"@haina/utils-commons": "https://research.hainatravel.com/npm/utils-commons-0.1.2.tgz",
"@haina/utils-request": "https://research.hainatravel.com/npm/utils-request-0.1.2.tgz",
"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,10 @@ 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';
import ToBOrder from './views/toB/ToBOrder';
import ToBOrderSub from './views/toB/ToBOrderSub';
import MeetingSales from './views/reports/MeetingSales';
const App = () => {
const { Content, Footer, Sider, } = Layout;
@ -80,6 +84,10 @@ const App = () => {
key: 'meeting-2024-GH',
label: <NavLink to="/orders/meeting-2024-GH">GH区域数据</NavLink>, // GH-2024
},
{
key: 'sales-insight',
label: <NavLink to="/reports/sales-insight">顾问业绩</NavLink>,
},
],
},
{
@ -92,6 +100,11 @@ const App = () => {
label: <NavLink to="/orders">订单数据</NavLink>,
// icon: <FileProtectOutlined />,
},
{
key: 'tob_orders',
label: <NavLink to="/tob_orders">分销订单</NavLink>,
// icon: <FileProtectOutlined />,
},
{
key: 22,
label: <NavLink to="/dashboard">仪表盘</NavLink>,
@ -104,11 +117,13 @@ const App = () => {
],
},
{
key: 20, label: '商务市场', icon: <RocketOutlined />,
key: 20,
label: '商务市场',
icon: <RocketOutlined />,
children: [
{ key: 201, label: <NavLink to="/biz_orders">订单数据</NavLink> },
{ key: 202, label: <NavLink to="/trains">火车票Upsell</NavLink> },
]
],
},
{
key: 5,
@ -146,11 +161,8 @@ const App = () => {
label: '财务',
icon: <DollarOutlined />,
children: [
{
key: 41,
label: <NavLink to="/credit_card_bill">信用卡账单</NavLink>,
},
{ key: 42, label: <NavLink to="/exchange_rate">汇率</NavLink> },
// { key: 41, label: <NavLink to="/credit_card_bill"></NavLink> },
// { key: 42, label: <NavLink to="/exchange_rate"></NavLink> },
{ key: 'service_person_num', label: <NavLink to="/service_person_num">服务人数</NavLink> },
],
},
@ -170,6 +182,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> },
],
},
{
@ -259,6 +272,8 @@ const App = () => {
<Route path="/:page/pivot" element={<DataPivot />} />
<Route path="/orders/meeting-2024-GH" element={<Meeting2024GH />} />
<Route path="/orders/meeting-2025-GH" element={<Meeting2025GH />} />
<Route path="/tob_orders" element={<ToBOrder />} />
<Route path="/tob_orders_sub/:ordertype/:ordertype_sub/:ordertype_title" element={<ToBOrderSub />} />
<Route path="/biz_orders" element={<BizOrder />} />
<Route path="/biz_orders_sub/:ordertype/:ordertype_sub/:ordertype_title" element={<BizOrderSub />} />
<Route path="/trains" element={<TrainsUpsell />} />
@ -277,7 +292,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 />} />
@ -293,6 +309,8 @@ const App = () => {
<Route path="/sales-crm/process" element={<OPProcess />} />
<Route path="/sales-crm/risk" element={<OPRisk />} />
<Route path="/sales-crm/risk/sales/:opisn" element={<OPRisk />} />
<Route path="/reports/sales-insight" element={<MeetingSales />} />
</Route>
</Routes>
</Content>

@ -6,13 +6,24 @@ import { stores_Context } from '../config';
import { observer } from 'mobx-react';
import SearchForm from './../components/search/SearchForm';
import LineWithAvg from '../components/LineWithAvg';
import { flow } from 'mobx';
import { TableExportBtn } from '../components/Data';
import { toJS } from 'mobx';
import { TableExportBtn, RenderVSDataCell } from '../components/Data';
import useCustomerRelationsStore from '../zustand/CustomerRelations';
import { useShallow } from 'zustand/shallow';
import { fixTo2Decimals, isEmpty } from '@haina/utils-commons';
const Customer_care_regular = () => {
const { orders_store, date_picker_store, customer_store } = useContext(stores_Context);
const { date_picker_store, customer_store } = useContext(stores_Context);
const regular_data = customer_store.regular_data;
const [loading, loading2, searchValues, searchValuesToSub] = useCustomerRelationsStore(useShallow((state) => [state.loading, state.loading2, state.searchValues, state.searchValuesToSub]));
const [setSearchValues] = useCustomerRelationsStore(useShallow((state) => [state.setSearchValues]));
const [regular] = useCustomerRelationsStore(useShallow((state) => [state.regular]));
const getRegularCustomer = useCustomerRelationsStore((state) => state.getRegularCustomer);
const columns = [
{
title: '订单号',
@ -27,9 +38,9 @@ const Customer_care_regular = () => {
{
title: '订单状态',
width: '4rem',
dataIndex: 'OrderState1',
key: 'OrderState1',
render: (text, record) => record.OrderState === 1 ? '成行' : '未成行',
dataIndex: 'orderstate_name',
key: 'orderstate_name',
// render: (text, record) => record.OrderState === 1 ? '' : '',
sorter: (a, b) => b.OrderState - a.OrderState,
},
{
@ -59,10 +70,15 @@ const Customer_care_regular = () => {
},
{
title: '走团国家',
dataIndex: 'recommend_country',
key: 'recommend_country',
dataIndex: 'PassCountry_This',
key: 'PassCountry_This',
width: '4em',
},
{
title: '经过城市',
dataIndex: 'PassCity_This',
key: 'PassCity_This',
},
{
title: '小组',
dataIndex: 'Department',
@ -98,6 +114,11 @@ const Customer_care_regular = () => {
dataIndex: 'COLI_LineClass',
key: 'COLI_LineClass',
},
{
title: '产品类型',
dataIndex: 'TourType_Name',
key: 'TourType_Name',
},
{
title: '券额',
dataIndex: 'Voucher_amount',
@ -147,6 +168,14 @@ const Customer_care_regular = () => {
style: { backgroundColor: '#5B8FF9' + '1A' },
}),
},
{
title: '上次经过城市',
dataIndex: 'PassCity_Last',
key: 'PassCity_Last',
onCell: (r) => ({
style: { backgroundColor: '#5B8FF9' + '1A' },
}),
},
{
title: '复购周期',
dataIndex: 'Repurchase_cycle',
@ -203,43 +232,16 @@ const Customer_care_regular = () => {
},
]);
return (
<>
{/* show a dialog to prevent the browser from closing the page */}
<dialog open={false} style={{
position: 'fixed',
top: 0,
left: '200px',
width: 'calc(100vw - 200px)',
height: '100vh',
background: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
border: 'none',
zIndex: 9999,
margin: 0,
padding: 0
}}>
<div style={{
background: 'white',
padding: '20px',
borderRadius: '8px',
textAlign: 'center'
}}>
维护中, 暂不可用, 敬请期待
</div>
</dialog></>
);
return (
<div>
<Row gutter={16} className={date_picker_store.siderBroken ? '' : 'sticky-top'}>
<Row gutter={16} className={toJS(date_picker_store.siderBroken) ? '' : 'sticky-top'}>
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...date_picker_store.formValues,
...regular_data.searchValues,
...toJS(date_picker_store.formValues),
// ...toJS(regular_data.searchValues),
...searchValues,
},
shows: ['DateType', 'DepartmentList', 'WebCode', 'dates', 'IncludeTickets'],
fieldProps: {
@ -250,21 +252,25 @@ const Customer_care_regular = () => {
}}
onSubmit={async (_err, obj, form, str) => {
customer_store.setSearchValues(obj, form, 'regular_data');
regular_data.data_compare=[];
if (obj.DateDiff1 && obj.DateDiff2){
regular_data.isCompareLine=true;
regular_data.showCompareSum=true;
await customer_store.regular_customer_order();
customer_store.regular_customer_order(false,true);
customer_store.regular_customer_order(true,false,true);
customer_store.regular_customer_order(true,true,true);
}
else{
regular_data.isCompareLine=false;
regular_data.showCompareSum=false;
customer_store.regular_customer_order();
customer_store.regular_customer_order(true);
}
setSearchValues(obj, form);
getRegularCustomer({ ...obj, IsDetail: 0 });
getRegularCustomer({ ...obj, IsDetail: 1 });
// regular_data.data_compare=[];
// if (obj.DateDiff1 && obj.DateDiff2){
// regular_data.isCompareLine=true;
// regular_data.showCompareSum=true;
// await customer_store.regular_customer_order();
// customer_store.regular_customer_order(false,true);
// // customer_store.regular_customer_order(true,false,true);
// customer_store.regular_customer_order(true,true,true);
// }
// else{
// regular_data.isCompareLine=false;
// regular_data.showCompareSum=false;
// customer_store.regular_customer_order();
// customer_store.regular_customer_order(true);
// }
}}
/>
</Col>
@ -275,8 +281,8 @@ const Customer_care_regular = () => {
</Col>
<Col span={24}>
<Table
dataSource={regular_data.data}
loading={regular_data.loading}
dataSource={regular.data}
loading={loading}
columns={[
{
title: '统计条目',
@ -287,7 +293,7 @@ const Customer_care_regular = () => {
title: () => (
<>
订单数{' '}
<Tooltip key='total_data_tips_title' title="总订单: 当同时勾选老客户和推荐时, 将重复计数">
<Tooltip key="total_data_tips_title" title="总订单: 当同时勾选老客户和推荐时, 将重复计数">
<InfoCircleOutlined />
</Tooltip>
</>
@ -296,10 +302,15 @@ const Customer_care_regular = () => {
key: 'OrderNum',
render: (text, record, index) => (
<>
<span>{text}</span>&nbsp;&nbsp;
{<Tooltip key='total_data_tips' title={regular_data.total_data_tips}>
{index === 0 && regular_data.total_data_tips!=='' && <InfoCircleOutlined className='ant-tag-gold' />}
</Tooltip>}
<RenderVSDataCell showDiffData={!isEmpty(searchValuesToSub.DateDiff1)} data1={record.OrderNum} data2={record.diff?.OrderNum} />
{index === 0 && regular.total_data_tips !== '' && (
<>
&nbsp;&nbsp;
<Tooltip key="total_data_tips" title={regular.total_data_tips}>
<InfoCircleOutlined className="ant-tag-gold" />
</Tooltip>
</>
)}
</>
),
},
@ -307,46 +318,84 @@ const Customer_care_regular = () => {
title: '订单数占比',
dataIndex: 'OrderRate',
key: 'OrderRate',
render: (text) => typeof text === 'number'?<span>{parseFloat((text * 100).toFixed(2))}%</span>:text,
},
{
title: '订单数占比(市场)',
dataIndex: 'OrderRate2',
key: 'OrderRate2',
render: (text) => typeof text === 'number'?<span>{parseFloat((text * 100).toFixed(2))}%</span>:text,
render: (text, record) => (
<RenderVSDataCell
showDiffData={!isEmpty(searchValuesToSub.DateDiff1)}
data1={fixTo2Decimals(record.OrderRate * 100)}
data2={fixTo2Decimals(record.diff?.OrderRate * 100)}
dataSuffix="%"
/>
),
},
// {
// title: '()',
// dataIndex: 'OrderRate2',
// key: 'OrderRate2',
// render: (text, record) => (
// <RenderVSDataCell
// showDiffData={!isEmpty(searchValuesToSub.DateDiff1)}
// data1={fixTo2Decimals(record.OrderRate2 * 100)}
// data2={fixTo2Decimals(record.diff?.OrderRate2 * 100)}
// dataSuffix="%"
// />
// ),
// },
{
title: '成行数',
dataIndex: 'SUCOrderNum',
key: 'SUCOrderNum',
render: (text, record) => <RenderVSDataCell showDiffData={!isEmpty(searchValuesToSub.DateDiff1)} data1={record.SUCOrderNum} data2={record.diff?.SUCOrderNum} />,
},
{
title: '成行率',
dataIndex: 'SUCRate',
key: 'SUCRate',
render: (text) => typeof text === 'number'?<span>{Math.round(text * 100)}%</span>:text,
render: (text, record) => (
<RenderVSDataCell
showDiffData={!isEmpty(searchValuesToSub.DateDiff1)}
data1={fixTo2Decimals(record.SUCRate * 100)}
data2={fixTo2Decimals(record.diff?.SUCRate * 100)}
dataSuffix="%"
/>
),
},
{
title: '毛利',
dataIndex: 'ML',
key: 'ML',
render: (text, record) => <RenderVSDataCell showDiffData={!isEmpty(searchValuesToSub.DateDiff1)} data1={record.ML} data2={record.diff?.ML} />,
},
{
title: '毛利占比',
dataIndex: 'OrderMLRate',
key: 'OrderMLRate',
render: (text) => typeof text === 'number'?<span>{parseFloat((text * 100).toFixed(2))}%</span>:text,
},
{
title: '毛利占比(市场)',
dataIndex: 'OrderMLRate2',
key: 'OrderMLRate2',
render: (text) => typeof text === 'number'?<span>{parseFloat((text * 100).toFixed(2))}%</span>:text,
render: (text, record) => (
<RenderVSDataCell
showDiffData={!isEmpty(searchValuesToSub.DateDiff1)}
data1={fixTo2Decimals(record.OrderMLRate * 100)}
data2={fixTo2Decimals(record.diff?.OrderMLRate * 100)}
dataSuffix="%"
/>
),
},
// {
// title: '()',
// dataIndex: 'OrderMLRate2',
// key: 'OrderMLRate2',
// render: (text, record) => (
// <RenderVSDataCell
// showDiffData={!isEmpty(searchValuesToSub.DateDiff1)}
// data1={fixTo2Decimals(record.OrderMLRate2 * 100)}
// data2={fixTo2Decimals(record.diff?.OrderMLRate2 * 100)}
// dataSuffix="%"
// />
// ),
// },
{
title: '人数(含成人+儿童)',
dataIndex: 'PersonNum',
key: 'PersonNum',
render: (text, record) => <RenderVSDataCell showDiffData={!isEmpty(searchValuesToSub.DateDiff1)} data1={record.PersonNum} data2={record.diff?.PersonNum} />,
},
]}
size="small"
@ -356,33 +405,37 @@ const Customer_care_regular = () => {
</Col>
<Col span={24}>
<LineWithAvg dataSource={regular_data.pivotData} loading={regular_data.detail_loading} xField={regular_data.pivotX} yField={regular_data.pivotY}
seriesField='_ylabel' showCompareSum={regular_data.showCompareSum} solidLineTime={regular_data.solidLineTime} solidLineCompareTime={regular_data.solidLineCompareTime}
solidLineDash={regular_data.solidLineDash} isCompareLine={regular_data.isCompareLine}/>
</Col>
<div style={{ height: '100%' }}>
<Col span={24}>
<LineWithAvg
dataSource={regular.pivotData}
loading={loading2}
xField={regular.pivotX}
yField={regular.pivotY}
seriesField="_ylabel"
showCompareSum={false}
solidLineTime={false}
solidLineCompareTime={false}
solidLineDash={false}
isCompareLine={false}
/>
</Col>
<Col span={24}>
<Divider orientation="right" plain>
<a
onClick={() => {
const wb = utils.table_to_book(document.getElementById('table_to_xlsx').getElementsByTagName('table')[0]);
writeFileXLSX(wb, '老客户.xlsx');
}}
>
导出下表
</a>
<TableExportBtn btnTxt='导出详情' label={'老客户-详情'} columns={export_columns} dataSource={regular_data.data_detail} style={{ marginLeft: '10px' }} />
</Divider>
<Table
id="table_to_xlsx"
pagination={false}
loading={regular_data.detail_loading}
dataSource={regular_data.data_detail}
scroll={{ x: 1200 }}
columns={columns}
size="small"
rowKey={(record) => record.COLI_ID}
/>
<Col span={24}>
<Divider orientation="right" plain>
<a
onClick={() => {
const wb = utils.table_to_book(document.getElementById('table_to_xlsx').getElementsByTagName('table')[0]);
writeFileXLSX(wb, '老客户.xlsx');
}}
>
导出下表
</a>
<TableExportBtn btnTxt="导出详情" label={'老客户-详情'} columns={export_columns} dataSource={regular.details} style={{ marginLeft: '10px' }} />
</Divider>
<Table id="table_to_xlsx" pagination={false} loading={loading2} dataSource={regular.details} scroll={{ x: 1200 }} columns={columns} size="small" rowKey={(record) => record.COLI_ID} />
</Col>
</div>
</Col>
</Row>
</div>

@ -1,10 +1,12 @@
import { useContext, useEffect, useState } from 'react';
import { observer } from 'mobx-react';
import { toJS } from 'mobx';
import { stores_Context } from '../config';
import { Table, Row, Col, Divider, Switch, Space, Tabs } from 'antd';
import { Table, Row, Col, Divider, Switch, Space, Tabs, Tooltip } from 'antd';
import { InfoCircleOutlined } from '@ant-design/icons';
import SearchForm from '../components/search/SearchForm';
import { TableExportBtn, VSTag } from '../components/Data';
import { fixTo2Decimals, isEmpty } from '../utils/commons';
import { RenderVSDataCell, TableExportBtn, VSTag } from '../components/Data';
import { fixTo2Decimals, isEmpty } from '@haina/utils-commons';
// TdCellDataTable
const TdCell = (tdprops) => {
@ -16,6 +18,32 @@ const TdCell = (tdprops) => {
const pivotOptions = [
{ key: 'operatorName', label: '顾问' },
{ key: 'country', label: '国籍' },
{
key: 'destinationCountry',
value: 'destinationCountry',
labelX: '目的地国家',
label: (
<>
目的地国家&nbsp;&nbsp;
<Tooltip key="total_data_tips" title={'途径的国家将会重复计算'}>
<InfoCircleOutlined className="ant-tag-gold" />
</Tooltip>
</>
),
},
{
key: 'destinations',
value: 'destinations',
labelX: '目的地城市',
label: (
<>
目的地城市&nbsp;&nbsp;
<Tooltip key="total_data_tips" title={'途径的城市将会重复计算'}>
<InfoCircleOutlined className="ant-tag-gold" />
</Tooltip>
</>
),
},
];
const pivotColOptions = [
{ key: 'hasOld', value: 'hasOld', label: '老客户+推荐' },
@ -25,8 +53,8 @@ const pivotColOptions = [
const CustomerCareRegularPivot = (props) => {
const { date_picker_store: searchFormStore, customer_store } = useContext(stores_Context);
const { formValues, formValuesToSub, siderBroken } = searchFormStore;
const { loading, pivotResult, filterColValues, rawData } = customer_store.sales_regular_data;
const { loading, pivotResult, filterColValues, rawData, countrySummary, citySummary } = customer_store.sales_regular_data;
// console.log('', countrySummary);
const [pivotRow, setPivotRow] = useState('operatorName');
const [pivotCol, setPivotCol] = useState('hasOld');
const [pivotColLabel, setPivotColLabel] = useState('老客户+推荐');
@ -43,36 +71,10 @@ const CustomerCareRegularPivot = (props) => {
useEffect(() => {
if ( ! ifmerge) {
// setDataSource(pageData[pivotRow].data);
setDataSource(pivotResult);
// setDataForExport(
// // pageData[pivotRow].data.reduce(
// pivotResult.reduce(
// (r, c) =>
// r.concat(
// [{ ...c, children: undefined }],
// (c?.children || [])
// .reduce((rc, ele) => rc.concat([{ ...ele, [pivotRow]: ele.rowLabel }], [{ ...ele.vsData, [pivotRow]: ele.vsData.rowLabel, vsData: {} }]), [])
// .filter((ele) => ele.SumOrder !== undefined)
// ),
// []
// )
// );
setDataForExportS(pivotResult.reduce((r, c) => r.concat([{...c, children: undefined}], [{ ...c.vsData, vsData: {} }]), []).filter((ele) => ele.SumOrder !== undefined));
} else {
// setDataSource(pageData[pivotRow].mergedData);
// setDataForExport(
// pageData[pivotRow].mergedData.reduce(
// (r, c) =>
// r.concat(
// [{ ...c, children: undefined }],
// c.children.reduce((rc, ele) => rc.concat([{ ...ele, operatorName: ele.rowLabel }], [{ ...ele.vsData, operatorName: ele.vsData.rowLabel, vsData: {} }]), [])
// .filter((ele) => ele.SumOrder !== undefined)
// ),
// []
// )
// );
// setDataForExportS(pageData[pivotRow].mergedData.reduce((r, c) => r.concat([{...c, children: undefined}], [{ ...c.vsData, vsData: {} }]), []).filter((ele) => ele.SumOrder !== undefined));
//
}
return () => {};
@ -116,9 +118,10 @@ const CustomerCareRegularPivot = (props) => {
{ key: 'ConfirmPersonNum', title: '✔人数(SUM)', dataIndex: 'ConfirmPersonNum', width: '5em', render: (v, r) => renderVS(v, r, 'ConfirmPersonNum') },
{ key: 'confirmTourdays', title: '✔团天数(AVG)', dataIndex: 'confirmTourdays', width: '5em', render: (v, r) => renderVS(v, r, 'confirmTourdays') },
{ key: 'SumML', title: '预计毛利', dataIndex: 'SumML', width: '5em', render: (v, r) => renderVS(v, r, 'SumML') }, // SumML_txt
{ key: 'ConfirmRates', title: '成交率', dataIndex: 'ConfirmRates_txt', width: '5em', render: (v, r) => renderVS(v, r, 'ConfirmRates') },
{ key: 'ConfirmRates', title: '成交率', dataIndex: 'ConfirmRates_txt', width: '5em', render: (v, r) => renderVS(v, r, 'ConfirmRates'), suffix: '%' },
{ key: 'SingleML', title: '单团毛利', dataIndex: 'SingleML', width: '5em', render: (v, r) => renderVS(v, r, 'SingleML') },
].map(c => ({...c, sorter: (a, b) => (a[c.key] - b[c.key])}));
return (
<>
<Row gutter={16} className={siderBroken ? '' : 'sticky-top'}>
@ -153,37 +156,18 @@ const CustomerCareRegularPivot = (props) => {
...ele,
children: (
<>
{/* <h2>{ele.label}-老客户, 含推荐</h2> */}
<>
<Divider orientation={'right'} style={{backgroundColor: '#fff', margin: 0, padding: '10px 0'}} >
{/* {dataSource.length > 0 && pivotRow === 'operatorName' && (
<Switch
unCheckedChildren="各账户"
checkedChildren="合并"
key={'openOrMerge'}
checked={ifmerge}
onChange={(e) => {
setIfmerge(e);
}}
/>
)}
<Divider type={'vertical'} /> */}
<TableExportBtn
btnTxt="导出明细"
label={`${formValuesToSub.Date1}-老客户-明细`}
{...{ columns: [{ title: ele.label, dataIndex: pivotRow, key: pivotRow }, ...rowColumns], dataSource: rawData }}
{...{ columns: [{ title: ele.labelX || ele.label, dataIndex: pivotRow, key: pivotRow }, ...rowColumns], dataSource: rawData }}
/>
{/* <Divider type={'vertical'} />
<TableExportBtn
btnTxt="导出下表-展开"
label={`${formValuesToSub.Date1}-${ele.label}.老客户`}
{...{ columns: [{ title: ele.label, dataIndex: pivotRow, key: pivotRow }, ...columns], dataSource: dataForExport }}
/> */}
<Divider type={'vertical'} />
<TableExportBtn
btnTxt="导出下表"
label={`${formValuesToSub.Date1}-${ele.label}.${pivotColLabel}`}
{...{ columns: [{ title: ele.label, dataIndex: pivotRow, key: pivotRow }, ...columns], dataSource: dataForExportS }}
{...{ columns: [{ title: ele.labelX || ele.label, dataIndex: pivotRow, key: pivotRow }, ...columns], dataSource: dataForExportS }}
/>
</Divider>
</>
@ -195,14 +179,14 @@ const CustomerCareRegularPivot = (props) => {
customer_store.regular_data_pivot(pivotRow, sub);
}}
items={pivotColOptions.map((col, i) => {
// const SubjectTableComponent = subjectComponents[ele.key];
return {
...col,
children: (
<Table
sticky
sticky={{ offsetHeader: 88 }}
dataSource={dataSource}
loading={loading}
components={{ body: { cell: TdCell } }}
columns={[
{
key: ele.key,
@ -215,8 +199,35 @@ const CustomerCareRegularPivot = (props) => {
},
...columns,
]}
summary={() => {
return (
pivotRow === 'country' ?
countrySummary.map((srow) => (
<Table.Summary.Row key={srow.key}>
<Table.Summary.Cell index1={0}><b>{srow.country}</b></Table.Summary.Cell>
{columns.map((td) => (
<Table.Summary.Cell key={td.key}>
<RenderVSDataCell data1={srow[td.dataIndex]} data2={srow.vsData?.[td.dataIndex]} showDiffData={toJS(formValuesToSub.DateDiff1)} dataSuffix={td.suffix} />
</Table.Summary.Cell>
))}
</Table.Summary.Row>
))
: pivotRow === 'destinations' ? citySummary.map((srow) => (
<Table.Summary.Row key={srow.key}>
<Table.Summary.Cell index1={0}><b>{srow.destinations}</b></Table.Summary.Cell>
{columns.map((td) => (
<Table.Summary.Cell key={td.key}>
<RenderVSDataCell data1={srow[td.dataIndex]} data2={srow.vsData?.[td.dataIndex]} showDiffData={toJS(formValuesToSub.DateDiff1)} dataSuffix={td.suffix} />
</Table.Summary.Cell>
))}
</Table.Summary.Row>
)) : null
);
}}
pagination={false}
/>)
/>
),
};
})}
/>

@ -5,7 +5,7 @@ import GroupSelect from "../components/search/GroupSelect";
import DatePickerCharts from "../components/search/DatePickerCharts";
import { stores_Context } from "../config";
import { observer } from "mobx-react";
import * as comm from "../utils/commons";
import { emptyValue } from "@haina/utils-commons";
import { Line } from "@ant-design/charts";
import * as config from "../config";
@ -19,7 +19,7 @@ class ExchangeRate extends Component {
render() {
const { dashboard_store, date_picker_store } = this.context;
const { exchangeRate_data } = dashboard_store;
const line_data_source = comm.empty(exchangeRate_data.data) ? [] : exchangeRate_data.data.CurrencyData1;
const line_data_source = emptyValue(exchangeRate_data.data) ? [] : exchangeRate_data.data.CurrencyData1;
const line_config = {
data: line_data_source,
padding: "auto",

@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import { observer } from 'mobx-react';
import { Bullet } from '@ant-design/plots';
import { sortBy, merge, isEmpty } from '../utils/commons';
import { sortBy, merge, isEmpty } from '@haina/utils-commons';
import { dataFieldAlias } from '../libs/ht';
export default observer((props) => {

@ -1,6 +1,6 @@
import { useEffect, useState } from 'react';
import { observer } from 'mobx-react';
import { sortBy, merge } from '../utils/commons';
import { sortBy, merge } from '@haina/utils-commons';
import { dataFieldAlias } from '../libs/ht';
import { Column } from '@ant-design/plots';

@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
import { Tag, Button, message } from 'antd';
import { CaretUpOutlined, CaretDownOutlined, DownloadOutlined } from '@ant-design/icons';
import { utils, writeFile } from "xlsx";
import { isEmpty, getNestedValue } from "../utils/commons";
import { isEmpty, getNestedValue, fixTo2Decimals } from "@haina/utils-commons";
/**
* @property diffPercent
@ -28,6 +28,53 @@ export const VSTag = (props) => {
);
};
/**
* @property {number} [diffPercent=0]
* @property {number} diffData
* @property {number} data1
* @property {number} data2
* @property {string} dataSuffix
*/
export const VSDataTag = ({ diffPercent=0, diffData=0, data1=0, data2=0, dataSuffix='' }) => {
const _diffPercent = diffPercent || ((data2) ? fixTo2Decimals((data1-data2)/data2*100) : 100);
const _diffPercentSuffix = String(_diffPercent).includes('%') ? '' : '%';
const _diffData = diffData || (isNaN(data2) ? fixTo2Decimals(data1-0) : (fixTo2Decimals(data1-data2)));
const CaretIcon = parseInt(_diffPercent) < 0 ? CaretDownOutlined : CaretUpOutlined;
const tagColor = parseInt(_diffPercent) < 0 ? 'gold' : 'lime';
// parseInt(_diffPercent) === 0 ? (
// '-'
// ) :
return (
<span>
<div>
{data1}{dataSuffix} <b>VS</b> {data2}{dataSuffix}
</div>
{_diffData !== 0 && (
<Tag icon={<CaretIcon />} color={tagColor}>
{_diffPercent}<span>{_diffPercentSuffix}</span>&nbsp;&nbsp;<span>{_diffData}{dataSuffix}</span>
</Tag>
)}
</span>
);
};
/**
* 表格中显示数据对比
*
* @property {boolean | undefined} [showDiffData=false]
* @property {number} [diffPercent=0]
* @property {number} diffData
* @property {number} data1
* @property {number} data2
* @property {string} dataSuffix
*/
export const RenderVSDataCell = ({ showDiffData=false, data1=0, data2=0, dataSuffix = '', ...props }) => {
if (showDiffData) {
return <VSDataTag data1={data1} data2={data2} dataSuffix={dataSuffix} {...props} />;
}
return <div>{data1}{dataSuffix}</div>;
};
/**
* 导出表格数据存为xlsx
* @property label 文件名字
@ -72,9 +119,10 @@ export const TableExportBtn = ({label, columns, dataSource, btnTxt, ...props}) =
const export_val = typeof kset?.dataExport === 'function' ? kset.dataExport('', item) : null;
const render_val = typeof kset?.render === 'function' ? kset.render('', item) : null;
const data_val = kset?.dataIndex ? (Array.isArray(kset.dataIndex) ? getNestedValue(item, kset.dataIndex) : item[kset.dataIndex]) : undefined;
const data_val_str = data_val ? String(data_val) : '';
const x_val = item[`${kset.dataIndex}_X`];
// const _title = kset.title.replace('-[object Object]', '');
const v = { [kset.title]: x_val || export_val || data_val || render_val };
const v = { [kset.title]: x_val || export_val || data_val_str || render_val };
return { ...sv, ...v };
}, {});
return itemMapped;

@ -1,5 +1,5 @@
import moment from 'moment';
import { fixTo2Decimals, groupBy, isEmpty, sortBy } from '../../utils/commons';
import { fixTo2Decimals, groupBy, isEmpty, sortBy } from '@haina/utils-commons';
export const datePartOptions = [
{ label: '日', value: 'day' },

@ -1,6 +1,6 @@
import { observer } from 'mobx-react';
import { Pie, measureTextWidth } from '@ant-design/plots';
import { fixTo2Decimals, merge } from '../utils/commons';
import { fixTo2Decimals, merge } from '@haina/utils-commons';
import { dataFieldAlias } from './../libs/ht';
export default observer((props) => {

@ -4,10 +4,10 @@ import { Line } from '@ant-design/plots';
import { observer } from 'mobx-react';
import { dataFieldAlias } from '../libs/ht';
import DateGroupRadio from '../components/DateGroupRadio';
import { cloneDeep, groupBy, sortBy } from '../utils/commons';
import { cloneDeep, groupBy, sortBy } from '@haina/utils-commons';
export default observer((props) => {
const { dataSource: rawData, showAVG, showCompareSum, loading, solidLineTime,
const { dataSource: rawData, showAVG, showCompareSum, loading, solidLineTime,
solidLineDash, isCompareLine,solidLineCompareTime, ...config } = props;
const { xField, yField, yFieldAlias, seriesField } = config;
@ -105,7 +105,7 @@ export default observer((props) => {
setLineConfig({ ...lineConfig, yField, xField,});
if (showCompareSum) {
const _sumLine = Object.keys(byDays).reduce((r, _d) => {
const summaryVal = byDays[_d].reduce((rows, row) =>
const summaryVal = byDays[_d].reduce((rows, row) =>
{
if (row[seriesField].includes(solidLineTime)){
return rows + row[yField];
@ -115,7 +115,7 @@ export default observer((props) => {
}
}
, 0);
const summaryCompareVal = byDays[_d].reduce((rows, row) =>
const summaryCompareVal = byDays[_d].reduce((rows, row) =>
{
if (row[seriesField].includes(solidLineCompareTime)){
return rows + row[yField];
@ -153,7 +153,7 @@ export default observer((props) => {
// console.log(groupByDate);
const _data = Object.keys(groupByDate).reduce((r, _d) => {
if (showCompareSum) {
const summaryVal = groupByDate[_d].reduce((rows, row) =>
const summaryVal = groupByDate[_d].reduce((rows, row) =>
{
if (row[seriesField].includes(solidLineTime)){
return rows + row[yField];
@ -163,7 +163,7 @@ export default observer((props) => {
}
}
, 0);
const summaryCompareVal = groupByDate[_d].reduce((rows, row) =>
const summaryCompareVal = groupByDate[_d].reduce((rows, row) =>
{
if (row[seriesField].includes(solidLineCompareTime)){
return rows + row[yField];
@ -180,7 +180,7 @@ export default observer((props) => {
const summaryVal = groupByDate[_d].reduce((rows, row) => rows + row[yField], 0);
_sumLine.push({ ...groupByDate[_d][0], [yField]: summaryVal, [seriesField]: '总计' });
}
const xAxisGroup = groupByDate[_d].reduce((a, v) => {
(a[v[seriesField]] || (a[v[seriesField]] = [])).push(v);

@ -1,6 +1,6 @@
import { observer } from 'mobx-react';
import { Line } from '@ant-design/plots';
import { merge, isEmpty, groupBy, sortBy } from '../utils/commons';
import { merge, isEmpty, groupBy, sortBy } from '@haina/utils-commons';
import { dataFieldAlias } from '../libs/ht';
export default observer((props) => {

@ -2,7 +2,7 @@ import { useContext, useState, useEffect, useMemo } from 'react';
import { observer } from 'mobx-react';
import { ChoroplethMap } from '@ant-design/maps';
import { dataFieldAlias } from '../libs/ht';
import { cloneDeep } from '../utils/commons';
import { cloneDeep } from '@haina/utils-commons';
export default observer((props) => {
const { dataSource, sourceField, valueField, containerNode, ...extConfig } = props;

@ -1,7 +1,7 @@
import { observer } from 'mobx-react';
import { message } from 'antd';
import { Mix, getCanvasPattern, } from '@ant-design/plots';
import { merge, isEmpty, cloneDeep } from '../utils/commons';
import { merge, isEmpty, cloneDeep } from '@haina/utils-commons';
import { dataFieldAlias } from '../libs/ht';
const COLOR_SETS = [

@ -1,6 +1,6 @@
import { observer } from 'mobx-react';
import { Mix } from '@ant-design/plots';
import { merge, isEmpty, groupBy, cloneDeep } from '../utils/commons';
import { merge, isEmpty, groupBy, cloneDeep } from '@haina/utils-commons';
import { dataFieldAlias } from '../libs/ht';
export default observer((props) => {

@ -1,6 +1,6 @@
import { observer } from 'mobx-react';
import { Mix, getCanvasPattern } from '@ant-design/plots';
import { merge, isEmpty, cloneDeep } from '../utils/commons';
import { merge, isEmpty, cloneDeep } from '@haina/utils-commons';
import { dataFieldAlias } from '../libs/ht';
const COLOR_SETS = [

@ -1,6 +1,6 @@
import React from 'react';
import { DualAxes } from '@ant-design/charts';
import { fixTo2Decimals, fixTo4Decimals, fixTo1Decimals, groupBy } from '../utils/commons';
import { fixTo2Decimals, fixTo4Decimals, fixTo1Decimals, groupBy } from '@haina/utils-commons';
const ParetoChart = ({ data, xField, yField, thresholds = { A: 80, B: 90 }, title = '帕累托分析', showCategory = true, showThresholds = true, yFieldAlias }) => {
// 1.
@ -58,7 +58,7 @@ const ParetoChart = ({ data, xField, yField, thresholds = { A: 80, B: 90 }, titl
Cumulative: {
// min: 0,
label: {
formatter: (val) => `${val}%`,
formatter: (val) => `${val}`,
},
},
},

@ -1,6 +1,6 @@
import { useEffect, useState } from 'react';
import { observer } from 'mobx-react';
import { sortBy, merge } from '../utils/commons';
import { sortBy, merge } from '@haina/utils-commons';
import { dataFieldAlias } from '../libs/ht';
import { Mix, Scatter } from '@ant-design/plots';

@ -0,0 +1,17 @@
import { Button } from 'antd';
import { exportDoc } from './Index';
const ExportDocxBtn = ({ subject, title, sectionsData, ...props }) => {
return (
<>
<Button size='small'
onClick={() => {
exportDoc({title, subject, sectionsData});
}}
>
导出 .docx
</Button>
</>
);
};
export default ExportDocxBtn;

@ -0,0 +1,517 @@
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,
...(td.width ? { width: { size: 1200, type: WidthType.DXA } } : {}),
})
),
})
);
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 ({title, subject, 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 sectionSettings = {
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,
// }),
// ],
// }),
],
}),
},
};
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: [
{
...sectionSettings,
children: [
createTitle(title),
new docx.TableOfContents('toc', {
hyperlink: true,
headingStyleRange: '1-5',
useAppliedParagraphOutlineLevel: true,
}),
],
},
{
...sectionSettings,
children: [
createTitle(subject),
...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({title, subject, sectionsData}) {
const doc = await createDoc({title, subject, sectionsData});
const blob = await Packer.toBlob(doc);
saveAs(blob, `${subject}.${title}.docx`);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

@ -1,7 +1,7 @@
import { observer } from 'mobx-react';
import { Waterfall } from '@ant-design/plots';
import { dataFieldAlias } from './../libs/ht';
import { fixTo4Decimals, merge } from '../utils/commons';
import { fixTo4Decimals, merge } from '@haina/utils-commons';
export default observer((props) => {
const { dataSource, line, title, ...extProps } = props;

@ -4,7 +4,7 @@ import { stores_Context } from './../../config';
import { Typography, Row, Col, Tabs, } from 'antd';
import SearchForm from './../search/SearchForm';
import { KPIObjects } from './../../libs/ht';
import { merge, pick } from './../../utils/commons';
import { merge, pick } from '@haina/utils-commons';
import ProfitTable from './SubjectTable/Profit';
import Count from './SubjectTable/Count';
import Rates from './SubjectTable/Rates';

@ -7,7 +7,7 @@ import { Button, Table, Switch, Input, Space, Typography, Row, Col, Spin, Radio,
import { EditableProTable, ProCard, ProFormField } from '@ant-design/pro-components';
import SearchForm from './../search/SearchForm';
import { bu } from './../../libs/ht';
import { isEmpty, objectMapper, fixToInt, fixTo2Decimals, fixTo4Decimals, cloneDeep, numberFormatter } from './../../utils/commons';
import { isEmpty, objectMapper, fixToInt, fixTo2Decimals, fixTo4Decimals, cloneDeep, numberFormatter } from '@haina/utils-commons';
const { Text } = Typography;
const initialPercentKey = new Array(12).fill(1).reduce((r, v, i) => ({ ...r, [`M${i + 1}Percent`]: [8, 9].includes(i) ? 10 : 8 }), {});

@ -5,7 +5,7 @@ import { stores_Context } from '../../../config';
import { Button, Switch, Input, Space, Typography, Row, Col, message } from 'antd';
import { EditableProTable } from '@ant-design/pro-components';
import { KPIObjects } from '../../../libs/ht';
import { isEmpty, fixTo2Decimals, fixTo4Decimals, cloneDeep, numberFormatter, fixToInt } from '../../../utils/commons';
import { isEmpty, fixTo2Decimals, fixTo4Decimals, cloneDeep, numberFormatter, fixToInt } from '@haina/utils-commons';
export const KPIObjectsMapped = KPIObjects.reduce((a, c) => ({ ...a, [String(c.key)]: c }), {});
const { Text } = Typography;

@ -5,7 +5,7 @@ import { stores_Context } from './../../../config';
import { Button, Switch, Input, Space, Typography, Row, Col, message } from 'antd';
import { EditableProTable } from '@ant-design/pro-components';
import { KPIObjects } from './../../../libs/ht';
import { isEmpty, fixTo2Decimals, fixTo4Decimals, cloneDeep, numberFormatter, fixToInt } from './../../../utils/commons';
import { isEmpty, fixTo2Decimals, fixTo4Decimals, cloneDeep, numberFormatter, fixToInt } from '@haina/utils-commons';
export const KPIObjectsMapped = KPIObjects.reduce((a, c) => ({ ...a, [String(c.key)]: c }), {});
const { Text } = Typography;

@ -5,7 +5,7 @@ import { stores_Context } from '../../../config';
import { Button, Switch, Input, Space, Typography, Row, Col, message } from 'antd';
import { EditableProTable } from '@ant-design/pro-components';
import { KPIObjects } from '../../../libs/ht';
import { isEmpty, fixTo2Decimals, fixTo4Decimals, cloneDeep, numberFormatter, fixToInt } from '../../../utils/commons';
import { isEmpty, fixTo2Decimals, fixTo4Decimals, cloneDeep, numberFormatter, fixToInt } from '@haina/utils-commons';
export const KPIObjectsMapped = KPIObjects.reduce((a, c) => ({ ...a, [String(c.key)]: c }), {});
const { Text } = Typography;

@ -0,0 +1,40 @@
import React from 'react';
import { Select } from 'antd';
import { observer } from 'mobx-react';
//
export const languageOptions = [
{ value: '', label: '所有语种' },
{ value: '102001', label: '英语' },
{ value: '102002', label: '普通话' },
{ value: '102003', label: '日语' },
{ value: '102004', label: '韩语' },
{ value: '102005', label: '德语' },
{ value: '102006', label: '法语' },
{ value: '102007', label: '意大利语' },
{ value: '102008', label: '西班牙语' },
{ value: '102009', label: '俄语' },
{ value: '102010', label: '粤语' },
{ value: '102011', label: '印尼语' },
{ value: '102012', label: '泰国语' },
{ value: '102013', label: '葡萄牙语' }
];
export const GuideLanguageSelect = ({ value, onChange, ...props }) => {
return (
<div>
<Select
style={{ width: '100%' }}
defaultValue={['']}
placeholder="选择导游语种"
value={value}
onChange={onChange}
allowClear={true}
options={languageOptions}
{...props}
/>
</div>
);
};
export default observer(GuideLanguageSelect);

@ -1,8 +1,8 @@
import React from 'react';
import { Select } from 'antd';
import { fetchJSON } from './../../utils/request';
import { fetchJSON } from '@haina/utils-request';
import { observer } from 'mobx-react';
import { isEmpty, merge, objectMapper } from './../../utils/commons';
import { isEmpty, merge, objectMapper } from '@haina/utils-commons';
const { Option } = Select;

@ -0,0 +1,23 @@
import React from 'react';
import { Select } from 'antd';
import { observer } from 'mobx-react';
import { lineClass } from './../../libs/ht';
export const LineClassSelector = ({ value, onChange, ...props }) => {
return (
<div>
<Select
style={{ width: '100%' }}
placeholder="选择来源类型"
value={value}
onChange={onChange}
allowClear
labelInValue
{...props}
options={lineClass}
/>
</div>
);
};
export default observer(LineClassSelector);

@ -1,7 +1,7 @@
import { createContext, useContext, useEffect } from 'react';
import { toJS } from 'mobx';
import { observer } from 'mobx-react';
import { DATE_FORMAT, SMALL_DATETIME_FORMAT, stores_Context } from './../../config';
import { DATE_FORMAT, HT_HOST, SMALL_DATETIME_FORMAT, stores_Context } from './../../config';
import { SearchOutlined } from '@ant-design/icons';
import { Form, Row, Col, Select, Button, Space, DatePicker, Input, InputNumber } from 'antd';
import moment from 'moment';
@ -13,8 +13,10 @@ import SiteSelect from './SiteSelect';
import DateTypeSelect from './DataTypeSelect';
import DatePickerCharts from './DatePickerCharts';
import YearPickerCharts from './YearPickerCharts';
import GuideLanguageSelect from './GuideLanguageSelect';
import LineClassSeletor from './LineClassSeletor';
import SearchInput from './Input';
import { objectMapper, at, empty, isEmpty } from './../../utils/commons';
import { objectMapper, at, empty, isEmpty } from '@haina/utils-commons';
import { departureDateTypes } from './../../libs/ht';
import './search.css';
@ -80,6 +82,13 @@ export default observer((props) => {
},
default: '',
},
'lineClass': {
key: 'lineClass',
transform: (value) => {
return isEmpty(value) ? '': Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? value.key : '';
},
default: '',
},
'WebCode': {
key: 'WebCode',
transform: (value) => {
@ -100,7 +109,7 @@ export default observer((props) => {
'operator': {
key: 'operator',
// transform: (value) => value?.key || '',
transform: (value) => Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? (!isNaN(parseInt(value.key), 10) ? value.key : '') : '',
transform: (value) => isEmpty(value) ? '': Array.isArray(value) ? value.map((ele) => ele.key).join(',') : value ? (!isNaN(parseInt(value.key), 10) ? value.key : '') : '',
default: '',
},
'date': {
@ -316,8 +325,8 @@ function getFields(props) {
item(
'agency',
99,
<Form.Item name={'agency'}>
<SearchInput autoGet url="/service-web/QueryData/GetVEIName" map={{ 'CAV_VEI_SN': 'key', 'VEI2_CompanyBN': 'label' }} resultkey={'result1'} placeholder="所有地接社" {...fieldProps.agency} />
<Form.Item name={'agency'} initialValue={at(props, 'initialValue.agency')[0] || undefined} {...fieldProps.agency}>
<SearchInput autoGet url={HT_HOST + "/service-web/QueryData/GetVEIName"} map={{ 'CAV_VEI_SN': 'key', 'VEI2_CompanyBN': 'label' }} resultkey={'result1'} placeholder="所有地接社" {...fieldProps.agency} />
</Form.Item>
),
item(
@ -326,7 +335,7 @@ function getFields(props) {
<Form.Item name={'billtype'}>
<SearchInput
autoGet
url="/service-web/QueryData/GetCreditCardBillType"
url={HT_HOST + "/service-web/QueryData/GetCreditCardBillType"}
map={{ 'cb_billtype': ['key', 'label'] }}
// map={{ 'cb_billtype': 'key' }}
resultkey={'billtype'}
@ -360,6 +369,13 @@ function getFields(props) {
</Form.Item>,
fieldProps?.DepartmentList?.col
),
item(
'guide_lgc',
99,
<Form.Item name={`guide_lgc`}>
<GuideLanguageSelect />
</Form.Item>,
),
item(
'WebCode',
99,
@ -498,11 +514,11 @@ function getFields(props) {
item(
'operator',
99,
<Form.Item name={'operator'} dependencies={['DepartmentList']}>
<Form.Item name={'operator'} dependencies={['DepartmentList']} initialValue={at(props, 'initialValue.operator')[0]}>
<SearchInput
{...fieldProps.operator}
autoGet
url="/service-Analyse2/GetOperatorInfo"
url={HT_HOST + "/service-Analyse2/GetOperatorInfo"}
map={{ 'op_id': 'key', 'cn_name': 'label' }}
resultkey={'result'}
placeholder="输入搜索顾问: 中/英名字"
@ -526,21 +542,21 @@ function getFields(props) {
'country',
99,
<Form.Item name={'country'}>
<SearchInput {...fieldProps?.country || {}} autoGet url="/service-Analyse2/GetCountryInfo" map={{ 'c_id': 'key', 'cn_name': 'label' }} resultkey={'result'} placeholder="输入搜索国籍: 中/英名字" />
<SearchInput {...fieldProps?.country || {}} autoGet url={HT_HOST+"/service-Analyse2/GetCountryInfo"} map={{ 'c_id': 'key', 'cn_name': 'label' }} resultkey={'result'} placeholder="输入搜索国籍: 中/英名字" />
</Form.Item>
),
item(
'city',
99,
<Form.Item name={'city'}>
<SearchInput autoGet url="/service-Analyse2/GetDestinationInfo" map={{ 'c_id': 'key', 'cn_name': 'label' }} resultkey={'result'} placeholder="输入搜索城市: 中/英名字" />
<SearchInput autoGet url={HT_HOST + "/service-Analyse2/GetDestinationInfo"} map={{ 'c_id': 'key', 'cn_name': 'label' }} resultkey={'result'} placeholder="输入搜索城市: 中/英名字" />
</Form.Item>
),
item(
'globalCity',
99,
<Form.Item name={'globalCity'}>
<SearchInput autoGet url="/service-Analyse2/GetGlobalDestinationInfo" map={{ 'c_id': 'key', 'cn_name': 'label' }} resultkey={'result'} placeholder="输入搜索城市: 中/英名字" />
<SearchInput autoGet url={HT_HOST + "/service-Analyse2/GetGlobalDestinationInfo"} map={{ 'c_id': 'key', 'cn_name': 'label' }} resultkey={'result'} placeholder="输入搜索城市: 中/英名字" />
</Form.Item>
),
item(
@ -665,6 +681,13 @@ function getFields(props) {
<HotelStarSelect {...fieldProps.hotelStar} labelInValue={true} />
</Form.Item>
),
item(
'lineClass',
99,
<Form.Item name={`lineClass`} initialValue={at(props, 'initialValue.lineClass')[0] || undefined}>
<LineClassSeletor {...fieldProps.lineClass} labelInValue={true} />
</Form.Item>
),
];
baseChildren = baseChildren
.map((x) => {

@ -6,4 +6,4 @@ export const stores_Context = React.createContext();
export const DATE_FORMAT = "YYYY-MM-DD";
export const SMALL_DATETIME_FORMAT = 'YYYY-MM-DD 23:59:00';
export const DATETIME_FORMAT = 'YYYY-MM-DD 23:59:59';
export const HT_HOST = process.env.NODE_ENV === "production" ? "https://p9axztuwd7x8a7.mycht.cn" : "http://202.103.68.144:890";
export const HT_HOST = process.env.NODE_ENV === "production" ? "https://p9axztuwd7x8a7.mycht.cn" : "http://202.103.68.144:889";

@ -1,5 +1,5 @@
import moment from 'moment';
import { fixTo4Decimals, fixTo1Decimals, fixToInt, groupBy, sortBy, cloneDeep, pick, unique, flush, fixTo2Decimals, isEmpty } from '../utils/commons';
import { fixTo4Decimals, fixTo1Decimals, fixToInt, groupBy, sortBy, cloneDeep, pick, unique, flush, fixTo2Decimals, isEmpty } from '@haina/utils-commons';
/**
* 事业部
@ -75,6 +75,7 @@ export const sites = [
{ value: '187', key: '187', label: 'HTravel', code: 'HTravel' },
{ value: '186', key: '186', label: 'JH', code: 'JH' },
{ value: '163', key: '163', label: 'GH', code: 'GH' },
{ value: '188', key: '188', label: 'Thailand', code: 'Thailand' },
{ value: '184', key: '184', label: 'GH站外渠道 (中国)', code: 'ZWQD' },
{ value: '185', key: '185', label: 'GH站外渠道 (海外)', code: 'GH_ZWQD_HW' },
{ value: '28', key: '28', label: '客运中国', code: 'GHKYZG' },
@ -116,6 +117,43 @@ export const departureDateTypes = [
{ key: 'departureDate', value: 'departureDate', label: '抵达日期' },
];
/**
* 附加来源类型
*/
export const lineClass = [
{key:78001, value: 78001, label:"Google PPC"},
{key:78002, value: 78002, label:"页面推荐订单"},
{key:78003, value: 78003, label:"Bing PPC"},
// {key:78004, value: 78004, label:null},
{key:78005, value: 78005, label:"Newsletter"},
{key:78006, value: 78006, label:"Facebook订单"},
{key:78007, value: 78007, label:"travelchinacheaper"},
{key:78008, value: 78008, label:"farwestchina"},
{key:78009, value: 78009, label:"petel.bg"},
{key:78010, value: 78010, label:"Instagram订单"},
{key:78011, value: 78011, label:"Pinterest"},
// {key:78012, value: 78012, label:null},
{key:78013, value: 78013, label:"网前自然订单"},
{key:78014, value: 78014, label:"Youtube"},
{key:78015, value: 78015, label:"国际站内广告"},
{key:78016, value: 78016, label:"WhatsApp"},
{key:78017, value: 78017, label:"Reddit"},
{key:78018, value: 78018, label:"邮件订单"},
{key:78019, value: 78019, label:"老客户网前订单"},
{key:78020, value: 78020, label:"1v1"},
{key:78021, value: 78021, label:"Facebook广告"},
{key:78022, value: 78022, label:"来自GH的订单"},
{key:78023, value: 78023, label:"小红书"},
{key:78024, value: 78024, label:"Yandex PPC"},
// {key:78025, value: 78025, label:null},
{key:78026, value: 78026, label:"TikTok"},
{key:78027, value: 78027, label:"Instagram广告"},
{key:78028, value: 78028, label:"Facebook再营销广告"},
{key:78029, value: 78029, label:"AI推荐"},
{key:78030, value: 78030, label:"合作平台"},
{key:78031, value: 78031, label:"客运前端获取"},
{key:78032, value: 78032, label:"CT站群营销"}];
/**
* 结果字段
*/
@ -318,7 +356,7 @@ export const pivotBy = (_data, [rows, columns, date]) => {
ele.isCusCommend_txt = ele.isCusCommend === '1' ? '老客户推荐' : '否';
const hasOld = (ele.IsOld === '1' || ele.isCusCommend === '1') ? 1 : 0;
ele.hasOld = hasOld;
ele.hasOld_txt = hasOld === 1 ? '老客户(推荐)' : '';
ele.hasOld_txt = hasOld === 1 ? '老客户(推荐)' : '';
// ele.SumML_ctxt1 = ele.ML > 10000 ? '1W+' : '1W-';
// ele.SumML_ctxt1_5 = ele.ML > 15000 ? '1.5W+' : '1.5W-';
// ele.SumML_ctxt2 = ele.ML > 20000 ? '2W+' : '2W-';
@ -332,7 +370,7 @@ export const pivotBy = (_data, [rows, columns, date]) => {
// 数组的字段值, 拆分处理
if (groupbyKeys.includes('destinationCountry')) {
data = data.reduce((r, v, i) => {
const vjson = isEmpty(v.destinationCountry) ? [] : v.destinationCountry;
const vjson = isEmpty(v.destinationCountry) ? [""] : v.destinationCountry;
const xv = (vjson).reduce((rv, cv, vi) => {
rv.push({...v, destinationCountry: cv, key: vi === 0 ? v.key : `${v.key}@${cv}`});
return rv;
@ -344,7 +382,7 @@ export const pivotBy = (_data, [rows, columns, date]) => {
}
if (groupbyKeys.includes('destinations')) {
data = data.reduce((r, v, i) => {
const vjson = isEmpty(v.destinations) ? [] : v.destinations;
const vjson = isEmpty(v.destinations) ? [""] : v.destinations;
const xv = (vjson).reduce((rv, cv, vi) => {
rv.push({...v, destinations: cv, key: vi === 0 ? v.key : `${v.key}@${cv}`});
return rv;

@ -2,8 +2,9 @@ import {makeAutoObservable, runInAction} from "mobx";
import moment from "moment";
import { NavLink } from "react-router-dom";
import * as config from "../config";
import * as req from '../utils/request';
import { groupBy, prepareUrl, isEmpty, show_vs_tag, formatPercent, percentToDecimal } from '../utils/commons';
import * as req from '@haina/utils-request';
import { groupBy, prepareUrl, isEmpty, formatPercent, percentToDecimal } from '@haina/utils-commons';
import { show_vs_tag, } from "./../utils/commons";
class CustomerServices {
@ -37,6 +38,9 @@ class CustomerServices {
});
}
/**
* @deprecated 已迁移到zustand
*/
fetchAgentGroupCount() {
this.inProgress = true;
const fetchUrl = prepareUrl(config.HT_HOST + '/service-web/QueryData/GetAgentGroupInfoALL')
@ -327,6 +331,9 @@ class CustomerServices {
});
}
/**
* @deprecated 已迁移到zustand
*/
fetchGroupListByAgentId(agentId) {
this.inProgress = true;
this.agentCompany = '...';

@ -1,18 +1,121 @@
import {makeAutoObservable, runInAction, toJS } from "mobx";
import { fetchJSON } from '../utils/request';
import { fetchJSON } from '@haina/utils-request';
import * as config from "../config";
import { groupsMappedByKey, sitesMappedByCode, pivotBy } from './../libs/ht';
import { sortBy, show_vs_tag, formatPercent, groupBy, isEmpty, uniqWith, formatPercentToFloat } from "../utils/commons";
import { sortBy, formatPercent, groupBy, isEmpty, uniqWith, formatPercentToFloat, fixTo2Decimals, unique } from "@haina/utils-commons";
import { show_vs_tag, } from "./../utils/commons";
import moment from 'moment';
/**
* 用于透视的数据
*/
const getDetailData = async (param) => {
const json = await fetchJSON('/service-Analyse2/GetTradeApartDetail', param);
const json = await fetchJSON(config.HT_HOST + '/service-Analyse2/GetTradeApartDetail', param);
return json.errcode === 0 ? json.result : [];
};
const initialSummaryRow = {
SumOrder: 0,
ResumeOrder: 0,
ResumeConfirmOrder: 0,
SumPersonNum: 0,
ConfirmPersonNum: 0,
ConfirmOrder: 0,
transactions: 0,
SumML: 0,
SumML_txt: '',
quotePrice: 0,
tourdays: 0,
applyDays: 0,
confirmDays: 0,
SingleML: 0,
OrderValue: 0,
PPPrice: 0,
AvgPPPrice: 0,
confirmTourdays: 0,
PPPriceRange: '',
unitPPPriceRange: '',
};
const calcSummaryRow = (data1, data2) => {
const summaryFields = ['ConfirmOrder', 'SumOrder', 'SumML', 'transactions', 'SumPersonNum', 'ConfirmPersonNum', 'ConfirmOrderKPIvalue', 'OrderKPIvalue', 'MLKPIvalue'];
const xSummary = summaryFields.reduce((r, skey) => ({ ...r, [skey]: data1.reduce((a, c) => a + c[skey], 0) }), {});
xSummary.ConfirmRates = xSummary.SumOrder ? fixTo2Decimals((xSummary.ConfirmOrder / xSummary.SumOrder) * 100) : 0;
xSummary.ConfirmRates_txt = xSummary.ConfirmRates; // + '%';
xSummary.confirmTourdays = '-';
xSummary.SingleML = '-';
xSummary._data = data1;
const xSummary2 = summaryFields.reduce((r, skey) => ({ ...r, [skey]: data2.reduce((a, c) => a + c[skey], 0) }), {});
xSummary2.ConfirmRates = xSummary2.SumOrder ? fixTo2Decimals((xSummary2.ConfirmOrder / xSummary2.SumOrder) * 100) : 0;
xSummary2.ConfirmRates_txt = xSummary2.ConfirmRates; // + '%';
xSummary2.confirmTourdays = '-';
xSummary2.SingleML = '-';
xSummary2._data = data2;
xSummary.vsData = xSummary2;
return xSummary;
};
const calcSummaryUniqueRow = (_data1, _data2, rawData = [[], []]) => {
// console.log('calcSummaryRow', _data1, _data2, rawData);
const uniqueKeysData1 = unique(_data1.reduce((a, r) => a.concat(r.key.split('_').reduce((a1, r1) => a1.concat(r1.split('@')[0]), [])), [])).map(x => Number(x));
const uniqueKeysData2 = unique(_data2.reduce((a, r) => a.concat(r.key.split('_').reduce((a1, r1) => a1.concat(r1.split('@')[0]), [])), [])).map(x => Number(x));
// console.log(uniqueKeysData1, uniqueKeysData2);
const data1 = rawData[0].filterHasOld.filter(row => uniqueKeysData1.includes((row.key)));
const data2 = rawData[1].filterHasOld.filter(row => uniqueKeysData2.includes((row.key)));
// console.log(data1, data2);
const xSummary = data1.reduce((r, v) => {
r.SumOrder += 1;
r.SumPersonNum += v.personNum;
r.ConfirmPersonNum += Number(v.orderState) === 1 ? v.personNum : 0;
r.ConfirmOrder += Number(v.orderState) === 1 ? 1 : 0;
r.ResumeOrder += v.hasOld === 1 ? 1 : 0;
r.ResumeConfirmOrder += Number(v.orderState) === 1 && v.hasOld === 1 ? 1 : 0;
r.transactions += v.transactions;
r.SumML += Number(v.orderState) === 1 ? v.ML : 0;
r.quotePrice += Number(v.orderState) === 1 ? v.quotePrice : 0;
r.tourdays += v.tourdays;
r.applyDays += v.applyDays;
r.confirmDays += v.confirmDays;
// r.PPPrice += Number(v.orderState) === 1 ? v.PPPrice : 0;
r.confirmTourdays += Number(v.orderState) === 1 ? v.tourdays : 0;
return r;
}, structuredClone(initialSummaryRow));
const xSummary2 = data2.reduce((r, v) => {
r.SumOrder += 1;
r.SumPersonNum += v.personNum;
r.ConfirmPersonNum += Number(v.orderState) === 1 ? v.personNum : 0;
r.ConfirmOrder += Number(v.orderState) === 1 ? 1 : 0;
r.ResumeOrder += v.hasOld === 1 ? 1 : 0;
r.ResumeConfirmOrder += Number(v.orderState) === 1 && v.hasOld === 1 ? 1 : 0;
r.transactions += v.transactions;
r.SumML += Number(v.orderState) === 1 ? v.ML : 0;
r.quotePrice += Number(v.orderState) === 1 ? v.quotePrice : 0;
r.tourdays += v.tourdays;
r.applyDays += v.applyDays;
r.confirmDays += v.confirmDays;
// r.PPPrice += Number(v.orderState) === 1 ? v.PPPrice : 0;
r.confirmTourdays += Number(v.orderState) === 1 ? v.tourdays : 0;
return r;
}, structuredClone(initialSummaryRow));
xSummary.ConfirmRates = xSummary.SumOrder ? fixTo2Decimals((xSummary.ConfirmOrder / xSummary.SumOrder) * 100) : 0;
xSummary.ConfirmRates_txt = xSummary.ConfirmRates; // + '%';
xSummary.confirmTourdays = '-';
xSummary.SingleML = '-';
xSummary._data = data1;
xSummary2.ConfirmRates = xSummary2.SumOrder ? fixTo2Decimals((xSummary2.ConfirmOrder / xSummary2.SumOrder) * 100) : 0;
xSummary2.ConfirmRates_txt = xSummary2.ConfirmRates; // + '%';
xSummary2.confirmTourdays = '-';
xSummary2.SingleML = '-';
xSummary2._data = data2;
xSummary.vsData = xSummary2;
return xSummary;
};
class CustomerStore {
constructor(rootStore) {
@ -243,12 +346,16 @@ class CustomerStore {
item1.SUCOrderNum-item2.SUCOrderNum,item1.SUCOrderNum,item2.SUCOrderNum),
OrderRate: show_vs_tag(formatPercent((item1.OrderRate-item2.OrderRate)/item2.OrderRate),
formatPercentToFloat(item1.OrderRate-item2.OrderRate),formatPercentToFloat(item1.OrderRate),formatPercentToFloat(item2.OrderRate)),
OrderRate2: show_vs_tag(formatPercent((item1.OrderRate2-item2.OrderRate2)/item2.OrderRate2),
formatPercentToFloat(item1.OrderRate2-item2.OrderRate2),formatPercentToFloat(item1.OrderRate2),formatPercentToFloat(item2.OrderRate2)),
SUCRate: show_vs_tag(formatPercent((item1.SUCRate-item2.SUCRate)/(item2.SUCRate===0?1:item2.SUCRate)),
formatPercent(item1.SUCRate-item2.SUCRate),formatPercent(item1.SUCRate),formatPercent(item2.SUCRate)),
ML: show_vs_tag(formatPercent((item1.ML-item2.ML)/(item2.ML===0?1:item2.ML)),
(item1.ML-item2.ML).toFixed(2),item1.ML,item2.ML),
OrderMLRate: show_vs_tag(formatPercent((item1.OrderMLRate-item2.OrderMLRate)/item2.OrderMLRate),
formatPercentToFloat(item1.OrderMLRate-item2.OrderMLRate),formatPercentToFloat(item1.OrderMLRate),formatPercentToFloat(item2.OrderMLRate)),
OrderMLRate2: show_vs_tag(formatPercent((item1.OrderMLRate2-item2.OrderMLRate2)/item2.OrderMLRate2),
formatPercentToFloat(item1.OrderMLRate2-item2.OrderMLRate2),formatPercentToFloat(item1.OrderMLRate2),formatPercentToFloat(item2.OrderMLRate2)),
PersonNum: show_vs_tag(formatPercent((item1.PersonNum-item2.PersonNum)/(item2.PersonNum===0?1:item2.PersonNum)),
item1.PersonNum-item2.PersonNum,item1.PersonNum,item2.PersonNum),
});
@ -461,8 +568,8 @@ class CustomerStore {
// mergedData: [],
rawData: [],
searchValues: {
DepartmentList: ['1', '2', '28', '7'].map(kk => groupsMappedByKey[kk]),
WebCode: ['CHT','AH','JH','GH','ZWQD','GH_ZWQD_HW','GHKYZG','GHKYHW'].map(kk => sitesMappedByCode[kk]),
DepartmentList: ['1', '2', '28', '7', '33'].map(kk => groupsMappedByKey[kk]),
WebCode: ['CHT','AH','HTravel','JH','GH','ZWQD','GH_ZWQD_HW','GHKYZG','GHKYHW'].map(kk => sitesMappedByCode[kk]),
DateType: { key: 'applyDate', label: '提交日期'},
IncludeTickets: { key: '0', label: '不含门票' },
},
@ -471,6 +578,8 @@ class CustomerStore {
// country: { loading: false, data: [], rawData: [], mergedData: [], filterColValues: [] },
// },
pivotResult: [],
countrySummary: [],
citySummary: [],
filterColValues: [],
rawDataArr: [],
};
@ -549,38 +658,156 @@ class CustomerStore {
// console.log('regular_data_pivot -------------------------------------------------------------- rawData 000 ', toJS(this.sales_regular_data.rawData));
// console.log('regular_data_pivot ---- ', pivotRow, pivotCol);
const [result1, result2] = this.sales_regular_data.rawDataArr;
const allRows = Array.from(new Set([...result1.filterHasOld.map(row=>row[pivotRow]), ...result2.filterHasOld.map(row=>row[pivotRow])]));
// console.log(' ------ allRows', allRows);
const [pivot1, pivot2] = [result1, result2].map(_result => {
// const allRows = Array.from(new Set([...result1.filterHasOld.map(row=>row[pivotRow]), ...result2.filterHasOld.map(row=>row[pivotRow])]));
// console.log(' ------ allRows', result1, result2);
const [{ pivotResult: pivot1, rowValues: rowValues1 }, { pivotResult: pivot2, rowValues: rowValues2 }] = [result1, result2].map((_result) => {
const dataColField = pivotCol.replace('_txt', '');
const rawData = pivotCol === 'hasOld' ? _result.filterHasOld : _result.filterHasOld.filter((ele) => ele[dataColField] === '1');
const { data: pivotResult } = pivotBy(rawData, [[pivotRow, pivotCol], [], []]);
return pivotResult;
const {
data: pivotResult,
columnValues: [[rowValues], columnsKeys, dateKeys],
} = pivotBy(rawData, [[pivotRow, pivotCol], [], []]);
return { pivotResult, rowValues };
});
const allRowValues = Array.from(new Set([...rowValues1, ...rowValues2]));
// console.log(' ------ xx', rowValues1);
const rows1 = groupBy(pivot1, pivotRow);
const rows2 = groupBy(pivot2, pivotRow);
const pivotResultWithCompare = isEmpty(pivot2) ? pivot1 : allRows.reduce((r, rowName) => {
const _default = { [pivotRow]: rowName, rowLabel: rows1?.[rowName]?.rowLabel || rows2?.[rowName]?.rowLabel, children: rows1?.[rowName], key: rowName};
const operatorRow = {...(rows1?.[rowName]?.[0] || _default), vsData: rows2?.[rowName]?.[0] || {}};
// 展开的两项: '老客户', '老客户推荐'
// const series1Children = rows1?.[rowName]?.[0]?.children || [];
// const series2Children = rows2?.[rowName]?.[0]?.children || [];
// const children = allTypes.reduce((r, type) => {
// const _default = { [pivotRow]: type, rowLabel: type, key: type};
// const _typeRow = series1Children.find(sc => sc[pivotRow] === type) || _default;
// const _typeVSRow = series2Children.find(sc => sc[pivotRow] === type) || {};
// return r.concat({..._typeRow, vsData: _typeVSRow});
// }, []);
// operatorRow.children = children;
return r.concat(operatorRow);
}, []);
const pivotResultWithCompare = isEmpty(pivot2)
? pivot1
: allRowValues.reduce((r, rowName) => {
const _default = { [pivotRow]: rowName, rowLabel: rows1?.[rowName]?.rowLabel || rows2?.[rowName]?.rowLabel, children: rows1?.[rowName], key: rowName };
const operatorRow = { ...(rows1?.[rowName]?.[0] || _default), vsData: rows2?.[rowName]?.[0] || {} };
// 展开的两项: '老客户', '老客户推荐'
// const series1Children = rows1?.[rowName]?.[0]?.children || [];
// const series2Children = rows2?.[rowName]?.[0]?.children || [];
// const children = allTypes.reduce((r, type) => {
// const _default = { [pivotRow]: type, rowLabel: type, key: type};
// const _typeRow = series1Children.find(sc => sc[pivotRow] === type) || _default;
// const _typeVSRow = series2Children.find(sc => sc[pivotRow] === type) || {};
// return r.concat({..._typeRow, vsData: _typeVSRow});
// }, []);
// operatorRow.children = children;
return r.concat(operatorRow);
}, []);
// console.log(' ------ pivot1', pivot1, '\npivot2', pivot2, '\npivotResultWithCompare', pivotResultWithCompare);
const filterColValues = uniqWith(
allRows.map((rr) => ({ text: rr, value: rr })),
allRowValues.map((rr) => ({ text: rr, value: rr })),
(a, b) => JSON.stringify(a) === JSON.stringify(b)
).sort((a, b) => a.text.localeCompare(b.text, 'zh-CN'));
if (pivotRow === 'country') {
const aseanKK = ['新加坡', '马来西亚', '印度尼西亚', '菲律宾', '越南', '泰国', '文莱', '柬埔寨', '东帝汶', '老挝', '缅甸'];
const [asean1, asean2] = [
pivot1.filter((ele) => aseanKK.includes(ele.country)),
pivot2.filter((ele) => aseanKK.includes(ele.country)),
];
const aseanSummary = calcSummaryRow(asean1, asean2);
aseanSummary.key = '东南亚11国';
aseanSummary.country = '东南亚11国';
const eurusdKK = ['美国', '加拿大', '澳大利亚', '英国'];
const [eurusd1, eurusd2] = [
pivot1.filter((ele) => eurusdKK.includes(ele.country)),
pivot2.filter((ele) => eurusdKK.includes(ele.country)),
];
const eurusdSummary = calcSummaryRow(eurusd1, eurusd2);
eurusdSummary.key = '欧美4国';
eurusdSummary.country = '欧美4国';
// 西班牙语国家/地区
const esLgc = ['阿根廷','玻利维亚','智利','哥伦比亚','哥斯达黎加','古巴','多米尼加共和国','厄瓜多尔','萨尔瓦多','赤道几内亚','危地马拉','洪都拉斯','墨西哥','尼加拉瓜','巴拿马','巴拉圭','秘鲁','波多黎各','西班牙','乌拉圭','委内瑞拉'];
const [esLgc1, esLgc2] = [
pivot1.filter((ele) => esLgc.includes(ele.country)),
pivot2.filter((ele) => esLgc.includes(ele.country)),
];
const esLgcSummary = calcSummaryRow(esLgc1, esLgc2);
esLgcSummary.key = '西班牙语国家/地区';
esLgcSummary.country = '西班牙语国家/地区';
// 意大利语国家/地区
const itLgc = ['意大利','圣马力诺','梵蒂冈','瑞士',];
const [itLgc1, itLgc2] = [
pivot1.filter((ele) => itLgc.includes(ele.country)),
pivot2.filter((ele) => itLgc.includes(ele.country)),
];
const itLgcSummary = calcSummaryRow(itLgc1, itLgc2);
itLgcSummary.key = '意大利语国家/地区';
itLgcSummary.country = '意大利语国家/地区';
// 德语国家/地区
const deLgc = ['德国','奥地利','瑞士','列支敦士登','卢森堡','比利时',];
const [deLgc1, deLgc2] = [
pivot1.filter((ele) => deLgc.includes(ele.country)),
pivot2.filter((ele) => deLgc.includes(ele.country)),
];
const deLgcSummary = calcSummaryRow(deLgc1, deLgc2);
deLgcSummary.key = '德语国家/地区';
deLgcSummary.country = '德语国家/地区';
// 葡萄牙语国家
const ptLgc = ['葡萄牙','巴西','安哥拉','莫桑比克','几内亚比绍','佛得角','圣多美和普林西比','东帝汶',];
const [ptLgc1, ptLgc2] = [
pivot1.filter((ele) => ptLgc.includes(ele.country)),
pivot2.filter((ele) => ptLgc.includes(ele.country)),
];
const ptLgcSummary = calcSummaryRow(ptLgc1, ptLgc2);
ptLgcSummary.key = '葡萄牙语国家/地区';
ptLgcSummary.country = '葡萄牙语国家/地区';
this.sales_regular_data.countrySummary = [
aseanSummary, eurusdSummary,
esLgcSummary, itLgcSummary, deLgcSummary, ptLgcSummary,
];
}
if (pivotRow === 'destinations') {
const city1 = ['昆明','大理','丽江','中甸','德钦','西双版纳','普洱','泸沽湖','腾冲'];
const [city11, city12] = [
pivot1.filter((ele) => city1.includes(ele.destinations)),
pivot2.filter((ele) => city1.includes(ele.destinations)),
];
const city1Summary = calcSummaryUniqueRow(city11, city12, toJS(this.sales_regular_data.rawDataArr));
city1Summary.key = '云南';
city1Summary.destinations = '云南';
const city2 = ['上海','苏州','杭州','黄山','婺源','上饶','景德镇'];
const [city21, city22] = [
pivot1.filter((ele) => city2.includes(ele.destinations)),
pivot2.filter((ele) => city2.includes(ele.destinations)),
];
const city2Summary = calcSummaryUniqueRow(city21, city22, toJS(this.sales_regular_data.rawDataArr));
city2Summary.key = '华东';
city2Summary.destinations = '华东';
const city3 = ['乌鲁木齐','喀什','伊宁','昭苏','霍城','那拉提','喀纳斯','禾木','布尔津','吐鲁番','库尔勒','赛里木湖','和田','库车'];
const [city31, city32] = [
pivot1.filter((ele) => city3.includes(ele.destinations)),
pivot2.filter((ele) => city3.includes(ele.destinations)),
];
const city3Summary = calcSummaryUniqueRow(city31, city32, toJS(this.sales_regular_data.rawDataArr));
city3Summary.key = '新疆';
city3Summary.destinations = '新疆';
const city4 = ['拉萨','江孜','林芝','鲁朗','巴松措','然乌','波密','日喀则','定日','泽当','塔钦'];
const [city41, city42] = [
pivot1.filter((ele) => city4.includes(ele.destinations)),
pivot2.filter((ele) => city4.includes(ele.destinations)),
];
const city4Summary = calcSummaryUniqueRow(city41, city42, toJS(this.sales_regular_data.rawDataArr));
city4Summary.key = '西藏';
city4Summary.destinations = '西藏';
const city5 = ['贵阳','安顺','黄果树','榕江','从江','毕节','织金','铜仁','梵净山','荔波','平塘','兴义'];
const [city51, city52] = [
pivot1.filter((ele) => city5.includes(ele.destinations)),
pivot2.filter((ele) => city5.includes(ele.destinations)),
];
const city5Summary = calcSummaryUniqueRow(city51, city52, toJS(this.sales_regular_data.rawDataArr));
city5Summary.key = '贵州';
city5Summary.destinations = '贵州';
this.sales_regular_data.citySummary = [city1Summary, city2Summary, city3Summary, city4Summary, city5Summary];
}
this.sales_regular_data.pivotResult = pivotResultWithCompare;
this.sales_regular_data.filterColValues = filterColValues;

@ -1,7 +1,8 @@
import { makeAutoObservable, runInAction, toJS } from 'mobx';
import { fetchJSON } from '../utils/request';
import { isEmpty, sortBy, pick, merge, fixTo2Decimals, groupBy, sortKeys, fixToInt, cloneDeep } from '../utils/commons';
import { fetchJSON } from '@haina/utils-request';
import { isEmpty, sortBy, pick, merge, fixTo2Decimals, groupBy, sortKeys, fixToInt, cloneDeep } from '@haina/utils-commons';
import { dataFieldAlias } from './../libs/ht';
import { HT_HOST } from '../config';
class Trade {
constructor(rootStore) {
@ -13,15 +14,30 @@ class Trade {
* 明细
*/
getDetailData = async (param, page) => {
this.loading = true;
this.detailData[page] = { loading: true, dataSource: [], originData: [] };
const json = await fetchJSON('/service-Analyse2/GetTradeApartDetail', param);
const json = await fetchJSON(HT_HOST + '/service-Analyse2/GetTradeApartDetail', param);
if (json.errcode === 0) {
runInAction(() => {
this.loading = false;
this.detailData[page].loading = false;
this.detailData[page].dataSource = json.result;
this.detailData[page].originData = json.result;
});
}
if (!isEmpty(param.DateDiff1)) {
this.loading2 = true;
const param2 = { ...cloneDeep(param), Date1: param.DateDiff1, Date2: param.DateDiff2 };
const json2 = await fetchJSON(HT_HOST + '/service-Analyse2/GetTradeApartDetail', param2);
if (json2.errcode === 0) {
runInAction(() => {
this.loading2 = false;
// this.detailData[page].loading = false;
this.detailData[page].dataSource2 = json2.result;
this.detailData[page].originData2 = json2.result;
});
}
}
return json.result;
};
@ -36,15 +52,17 @@ class Trade {
resetData = () => {
this.detailData = {
orders: { loading: false, dataSource: [], originData: [] },
trade: { loading: false, dataSource: [], originData: [] },
orders: { loading: false, dataSource: [], originData: [], dataSource2: [], originData2: [], },
trade: { loading: false, dataSource: [], originData: [], dataSource2: [], originData2: [], },
};
};
loading = false;
loading2 = false;
searchValues = {};
detailData = {
orders: { loading: false, dataSource: [], originData: [] },
trade: { loading: false, dataSource: [], originData: [] },
orders: { loading: false, dataSource: [], originData: [], dataSource2: [], originData2: [], },
trade: { loading: false, dataSource: [], originData: [], dataSource2: [], originData2: [], },
};
}

@ -1,10 +1,11 @@
import { makeAutoObservable, runInAction, toJS } from 'mobx';
import * as req from '../utils/request';
import { isEmpty, sortBy, objectMapper } from '../utils/commons';
import * as req from '@haina/utils-request';
import { isEmpty, sortBy, objectMapper } from '@haina/utils-commons';
import { HT_HOST } from '../config';
const modelMapper = {
'operator': {
url: '/service-Analyse2/GetOperatorInfo',
url: HT_HOST + '/service-Analyse2/GetOperatorInfo',
mapper: {
op_id: [{ key: 'key' }, { key: 'value' }],
cn_name: { key: 'label' },
@ -12,7 +13,7 @@ const modelMapper = {
},
},
'country': {
url: '/service-Analyse2/GetCountryInfo',
url: HT_HOST + '/service-Analyse2/GetCountryInfo',
mapper: {
c_id: [{ key: 'key' }, { key: 'value' }],
cn_name: { key: 'label' },
@ -20,7 +21,7 @@ const modelMapper = {
},
},
'destination': {
url: '/service-Analyse2/GetDestinationInfo/test',
url: HT_HOST + '/service-Analyse2/GetDestinationInfo/test',
mapper: {
id: [{ key: 'key' }, { key: 'value' }],
cn_name: { key: 'label' },
@ -28,14 +29,14 @@ const modelMapper = {
},
},
'vendor': {
url: '/service-web/QueryData/GetVEIName',
url: HT_HOST + '/service-web/QueryData/GetVEIName',
mapper: {
CAV_VEI_SN: [{ key: 'key' }, { key: 'value' }],
VEI2_CompanyBN: { key: 'label' },
},
},
'creditcardbilltype': {
url: '/service-web/QueryData/GetCreditCardBillType',
url: HT_HOST + '/service-web/QueryData/GetCreditCardBillType',
mapper: {
cb_billtype: [{ key: 'key' }, { key: 'value' }, { key: 'label' }],
},

@ -1,19 +1,19 @@
import { makeAutoObservable, runInAction, toJS } from 'mobx';
import * as req from '../utils/request';
import { DATE_FORMAT, SMALL_DATETIME_FORMAT } from './../config';
import * as req from '@haina/utils-request';
import { DATE_FORMAT, SMALL_DATETIME_FORMAT, HT_HOST } from './../config';
import moment from 'moment';
import { isEmpty, pick, sortBy, fixTo2Decimals, cloneDeep, unique } from '../utils/commons';
import { isEmpty, pick, sortBy, fixTo2Decimals, cloneDeep, unique } from '@haina/utils-commons';
const modelMapper = {
'tourDays': { url: '/service-Analyse2/GetTradeApartByTourDays', keySort: true, dynamicsX: false },
'PML': { url: '/service-Analyse2/GetTradeApartByPML', keySort: true, dynamicsX: false },
'ConfirmDays': { url: '/service-Analyse2/GetTradeApartByConfirmDays', keySort: true, dynamicsX: false },
'ApplyDays': { url: '/service-Analyse2/GetTradeApartByApplyDays', keySort: true, dynamicsX: false },
'PersonNum': { url: '/service-Analyse2/GetTradeApartByPersonNum', keySort: true, dynamicsX: false },
'destination': { url: '/service-Analyse2/GetTradeApartByDestination', keySort: false, dynamicsX: true, },
'GlobalDestination': { url: '/service-Analyse2/GetTradeApartByGlobalDestination', keySort: false, dynamicsX: true, },
'destinationCountry': { url: '/service-Analyse2/GetTradeApartByDestinationCountry', keySort: false, dynamicsX: true, },
'guestCountry': { url: '/service-Analyse2/GetTradeApartByGuestCountry', keySort: false, dynamicsX: true, },
'tourDays': { url: HT_HOST + '/service-Analyse2/GetTradeApartByTourDays', keySort: true, dynamicsX: false },
'PML': { url: HT_HOST + '/service-Analyse2/GetTradeApartByPML', keySort: true, dynamicsX: false },
'ConfirmDays': { url: HT_HOST + '/service-Analyse2/GetTradeApartByConfirmDays', keySort: true, dynamicsX: false },
'ApplyDays': { url: HT_HOST + '/service-Analyse2/GetTradeApartByApplyDays', keySort: true, dynamicsX: false },
'PersonNum': { url: HT_HOST + '/service-Analyse2/GetTradeApartByPersonNum', keySort: true, dynamicsX: false },
'destination': { url: HT_HOST + '/service-Analyse2/GetTradeApartByDestination', keySort: false, dynamicsX: true, },
'GlobalDestination': { url: HT_HOST + '/service-Analyse2/GetTradeApartByGlobalDestination', keySort: false, dynamicsX: true, },
'destinationCountry': { url: HT_HOST + '/service-Analyse2/GetTradeApartByDestinationCountry', keySort: false, dynamicsX: true, },
'guestCountry': { url: HT_HOST + '/service-Analyse2/GetTradeApartByGuestCountry', keySort: false, dynamicsX: true, },
};
class Distribution {
constructor(appStore) {
@ -76,7 +76,7 @@ class Distribution {
*/
getDetailData = async (param) => {
this.detailData.loading = true;
const json = await req.fetchJSON('/service-Analyse2/GetTradeApartDetail', param);
const json = await req.fetchJSON(HT_HOST + '/service-Analyse2/GetTradeApartDetail', param);
if (json.errcode === 0) {
runInAction(() => {
this.detailData.loading = false;

@ -1,7 +1,7 @@
import {makeAutoObservable, runInAction} from "mobx";
import * as config from "../config";
import * as comm from '../utils/commons';
import { fetchJSON } from '../utils/request';
import * as comm from '@haina/utils-commons';
import { fetchJSON } from '@haina/utils-request';
// 财务管理
@ -42,7 +42,7 @@ class FinancialStore {
set_bill_filtered(values) {
if (Array.isArray(values)) {
this.credit_card_data.filteredValue = values;
} else if (comm.empty(values)) {
} else if (comm.emptyValue(values)) {
this.credit_card_data.filteredValue = [];
} else {
this.credit_card_data.filteredValue = [values];
@ -55,7 +55,7 @@ class FinancialStore {
set_table_handleChange = (pagination, filters, sorter) => {
const filters_arr = Object.values(filters);
if (!comm.empty(filters_arr)) {
if (!comm.emptyValue(filters_arr)) {
this.set_bill_filtered(filters_arr[0]);
}
};
@ -138,9 +138,9 @@ class FinancialStore {
*/
serviceModelMapper = {
'inbound': { url: '/service-Analyse2/inbound_person_num', keySort: true, dynamicsX: false },
'outbound': { url: '/service-Analyse2/outbound_person_num', keySort: true, dynamicsX: false },
'domestic': { url: '/service-Analyse2/domestic_person_num', keySort: true, dynamicsX: false },
'inbound': { url: config.HT_HOST + '/service-Analyse2/inbound_person_num', keySort: true, dynamicsX: false },
'outbound': { url: config.HT_HOST + '/service-Analyse2/outbound_person_num', keySort: true, dynamicsX: false },
'domestic': { url: config.HT_HOST + '/service-Analyse2/domestic_person_num', keySort: true, dynamicsX: false },
};
servicePersonNum = { curTab: 'inbound', loading: false,

@ -1,8 +1,8 @@
import { makeAutoObservable, runInAction, toJS } from 'mobx';
import { fetchJSON } from '../utils/request';
import { isEmpty, sortDescBy, objectMapper, groupBy, pick, unique, cloneDeep, omit, fixTo2Decimals } from '../utils/commons';
import { fetchJSON } from '@haina/utils-request';
import { isEmpty, sortDescBy, objectMapper, groupBy, pick, unique, cloneDeep, omit, fixTo2Decimals } from '@haina/utils-commons';
import { groupsMappedByCode, dataFieldAlias } from './../libs/ht';
import { DATE_FORMAT, SMALL_DATETIME_FORMAT } from './../config';
import { DATE_FORMAT, SMALL_DATETIME_FORMAT, HT_HOST } from './../config';
import moment from 'moment';
const fetchHotelData = async (param) => {
@ -24,7 +24,7 @@ const fetchHotelData = async (param) => {
CompareDateEnd: '',
Area: '-1',
};
const json = await fetchJSON('/service-Analyse2/HotelReservation', { ...defaultParam, ...param });
const json = await fetchJSON(HT_HOST + '/service-Analyse2/HotelReservation', { ...defaultParam, ...param });
return json.errcode === 0 ? json.result || [] : [];
};
@ -47,7 +47,7 @@ const fetchCruiseData = async (param) => {
PersonNumEnd: '',
Country: '-1',
};
const json = await fetchJSON('/service-Analyse2/CruiseReservation', { ...defaultParam, ...param });
const json = await fetchJSON(HT_HOST + '/service-Analyse2/CruiseReservation', { ...defaultParam, ...param });
return json.errcode === 0 ? json.result || [] : [];
};

@ -1,6 +1,7 @@
import { makeAutoObservable, runInAction, toJS } from 'mobx';
import * as req from '../utils/request';
import { isEmpty, sortBy, groupBy, cloneDeep, fixTo4Decimals, flush } from '../utils/commons';
import * as req from '@haina/utils-request';
import { isEmpty, sortBy, groupBy, cloneDeep, fixTo4Decimals, flush } from '@haina/utils-commons';
import { HT_HOST } from '../config';
import moment from 'moment';
class KPI {
@ -11,13 +12,13 @@ class KPI {
async delByID(ids) {
const data = { 'kpi_ids': ids };
const json = await req.delJSON('/service-Analyse2/delkpi_multi', data);
const json = await req.delJSON(HT_HOST + '/service-Analyse2/delkpi_multi', data);
return json.errcode === 0;
}
async saveOrUpdate(tableData) {
const data = { 'kpis': tableData };
const json = await req.postJSON('/service-Analyse2/setkpi_multi', data);
const json = await req.postJSON(HT_HOST + '/service-Analyse2/setkpi_multi', data);
return json.errcode === 0;
}
@ -39,7 +40,7 @@ class KPI {
};
this.listLoading = true;
this.pageData = [];
const json = await req.fetchJSON('/service-Analyse2/getkpi', _param);
const json = await req.fetchJSON(HT_HOST + '/service-Analyse2/getkpi', _param);
if (json.errcode === 0) {
const yearData = parseKPI(json.result, ['subject', 'object_id']);
runInAction(() => {

@ -1,15 +1,15 @@
import { makeAutoObservable, runInAction } from 'mobx';
import { fetchJSON } from '../utils/request';
import { objectMapper, pick, price_to_number, } from '../utils/commons';
import { fetchJSON } from '@haina/utils-request';
import { objectMapper, pick, price_to_number, } from '@haina/utils-commons';
import { pivotBy } from '../libs/ht';
import moment from "moment";
import { DATE_FORMAT, DATETIME_FORMAT, SMALL_DATETIME_FORMAT } from '../config';
import { DATE_FORMAT, DATETIME_FORMAT, SMALL_DATETIME_FORMAT, HT_HOST } from '../config';
/**
* 用于透视的数据
*/
const getDetailData = async (param) => {
const json = await fetchJSON('/service-Analyse2/GetTradeApartDetail', param);
const json = await fetchJSON(HT_HOST + '/service-Analyse2/GetTradeApartDetail', param);
return json.errcode === 0 ? json.result : [];
};
/**
@ -26,7 +26,7 @@ const getOrderCountByType = async (param) => {
Date2: 'COLI_ApplyDate2',
});
const url = '/service-web/QueryData/GetOrderCountByType';
const json = await fetchJSON(url, paramBody);
const json = await fetchJSON(HT_HOST + url, paramBody);
return json.errcode === 0 ? json : {};
};
const getAgentGroupInfoALL = async (param) => {
@ -37,7 +37,7 @@ const getAgentGroupInfoALL = async (param) => {
// Date2: 'OldDate2',
});
const url = '/service-web/QueryData/GetAgentGroupInfoALL';
const json = await fetchJSON(url, paramBody);
const json = await fetchJSON(HT_HOST + url, paramBody);
return json.errcode === 0 ? json : {};
};
const getDepartmentOrderMLByType = async (param) => {
@ -47,7 +47,7 @@ const getDepartmentOrderMLByType = async (param) => {
OrderType: 'OrderType', // 总览, 产品类型
});
const url = '/service-web/QueryData/GetDepartmentOrderMLByType';
const json = await fetchJSON(url, paramBody);
const json = await fetchJSON(HT_HOST + url, paramBody);
const { result1 } = json.errcode === 0 ? json : { result1: [] };
const total1 = ['COLI_CJCount', 'COLI_ML2',].reduce(
(r, col) => ({

@ -1,15 +1,15 @@
import { makeAutoObservable, runInAction } from 'mobx';
import { fetchJSON } from '../utils/request';
import { objectMapper, pick, price_to_number, } from '../utils/commons';
import { fetchJSON } from '@haina/utils-request';
import { objectMapper, pick, price_to_number, } from '@haina/utils-commons';
import { pivotBy } from './../libs/ht';
import moment from "moment";
import { DATE_FORMAT, DATETIME_FORMAT, SMALL_DATETIME_FORMAT } from '../config';
import { DATE_FORMAT, DATETIME_FORMAT, SMALL_DATETIME_FORMAT, HT_HOST } from '../config';
/**
* 用于透视的数据
*/
const getDetailData = async (param) => {
const json = await fetchJSON('/service-Analyse2/GetTradeApartDetail', param);
const json = await fetchJSON(HT_HOST + '/service-Analyse2/GetTradeApartDetail', param);
return json.errcode === 0 ? json.result : [];
};
/**
@ -26,7 +26,7 @@ const getOrderCountByType = async (param) => {
Date2: 'COLI_ApplyDate2',
});
const url = '/service-web/QueryData/GetOrderCountByType';
const json = await fetchJSON(url, paramBody);
const json = await fetchJSON(HT_HOST + url, paramBody);
return json.errcode === 0 ? json : {};
};
const getAgentGroupInfoALL = async (param) => {
@ -37,7 +37,7 @@ const getAgentGroupInfoALL = async (param) => {
Date2: 'OldDate2',
});
const url = '/service-web/QueryData/GetAgentGroupInfoALL';
const json = await fetchJSON(url, paramBody);
const json = await fetchJSON(HT_HOST + url, paramBody);
return json.errcode === 0 ? json : {};
};
const getDepartmentOrderMLByType = async (param) => {
@ -47,7 +47,7 @@ const getDepartmentOrderMLByType = async (param) => {
OrderType: 'OrderType', // 总览, 产品类型
});
const url = '/service-web/QueryData/GetDepartmentOrderMLByType';
const json = await fetchJSON(url, paramBody);
const json = await fetchJSON(HT_HOST + url, paramBody);
const { result1 } = json.errcode === 0 ? json : { result1: [] };
const total1 = ['COLI_CJCount', 'COLI_ML2',].reduce(
(r, col) => ({

@ -2,7 +2,7 @@ import { makeAutoObservable, runInAction, toJS } from "mobx";
import { CaretUpOutlined, CaretDownOutlined } from "@ant-design/icons";
import { Tag } from "antd";
import * as config from "../config";
import * as comm from "../utils/commons";
import * as comm from '@haina/utils-commons';
import moment from "moment";
import { NavLink } from "react-router-dom";
import { groupsMappedByCode } from './../libs/ht';
@ -99,7 +99,7 @@ class OrdersStore {
// 比如2022-10-01~2022-10-30 vs 2021-10-01~2021-10-30 则需要在2021年的时间段加365天的时间映射成2022起始时间段
// 相差的天数用a.diff(b, 'days')计算
format_data_source(data_source, start_date, end_date) {
const result = [];
const result = {};
for (const item of data_source.ordercount1) {
if (result[item.ApplyDate]) {
result[item.ApplyDate].yField += item.orderCount;
@ -130,7 +130,7 @@ class OrdersStore {
}
}
}
return comm.set_array_index(result);
return Object.values(result);
}
/**

@ -1,10 +1,11 @@
import { makeAutoObservable, runInAction } from 'mobx';
import moment from 'moment';
import * as config from '../config';
import * as comm from '../utils/commons';
import * as comm from '@haina/utils-commons';
import { show_vs_tag, } from "./../utils/commons";
import { NavLink } from 'react-router-dom';
import { groupsMappedByCode, dataFieldAlias } from './../libs/ht';
import * as req from '../utils/request';
import * as req from '@haina/utils-request';
import { parseKPI } from './KPI';
import { parseMergeItem } from './Trade';
@ -107,8 +108,8 @@ class SaleStore {
let result = [];
const date1_start = date_moment.start_date.format(config.DATE_FORMAT);
const date1_end = date_moment.end_date.format(config.DATE_FORMAT);
const date2_start = comm.empty(date_moment.start_date_cp) ? '' : date_moment.start_date_cp.format(config.DATE_FORMAT);
const date2_end = comm.empty(date_moment.end_date_cp) ? '' : date_moment.end_date_cp.format(config.DATE_FORMAT);
const date2_start = comm.emptyValue(date_moment.start_date_cp) ? '' : date_moment.start_date_cp.format(config.DATE_FORMAT);
const date2_end = comm.emptyValue(date_moment.end_date_cp) ? '' : date_moment.end_date_cp.format(config.DATE_FORMAT);
this.loading = true;
this.date_title = `${date1_start}~${date1_end}`;
let url = '/service-web/QueryData/GetDepartmentOrderML';
@ -122,7 +123,7 @@ class SaleStore {
.then((response) => response.json())
.then((json) => {
result = json.result1;
// if (!comm.empty(json.result2) && !comm.empty(date_moment.start_date_cp)) {
// if (!comm.emptyValue(json.result2) && !comm.emptyValue(date_moment.start_date_cp)) {
// let diff_days = date_moment.start_date.diff(date_moment.start_date_cp, "days");
// for (let item of json.result2) {
// result.push({
@ -149,8 +150,8 @@ class SaleStore {
const result = { dataSource: [], columns: [] };
const date1_start = date_moment.start_date.format(config.DATE_FORMAT);
const date1_end = date_moment.end_date.format(config.DATE_FORMAT);
const date2_start = comm.empty(date_moment.start_date_cp) ? '' : date_moment.start_date_cp.format(config.DATE_FORMAT);
const date2_end = comm.empty(date_moment.end_date_cp) ? '' : date_moment.end_date_cp.format(config.DATE_FORMAT);
const date2_start = comm.emptyValue(date_moment.start_date_cp) ? '' : date_moment.start_date_cp.format(config.DATE_FORMAT);
const date2_end = comm.emptyValue(date_moment.end_date_cp) ? '' : date_moment.end_date_cp.format(config.DATE_FORMAT);
this.loading_table = true;
this.date_title = `${date1_start}~${date1_end}`;
let url = '/service-web/QueryData/GetDepartmentOrderMLByType';
@ -163,7 +164,7 @@ class SaleStore {
fetch(config.HT_HOST + url)
.then((response) => response.json())
.then((json) => {
// if (!comm.empty(json.result2) && !comm.empty(date_moment.start_date_cp)) {
// if (!comm.emptyValue(json.result2) && !comm.emptyValue(date_moment.start_date_cp)) {
// eslint-disable-next-line no-constant-condition
if (false) {
} else {
@ -383,7 +384,7 @@ class SaleStore {
const items = mergeDiffData.filter((d) => String(d.OPI_SN) === String(item.OPI_SN)); // 筛选出有当前顾问的记录
const total_data_value = items.length ? items.reduce((a, b) => a + b.COLI_YJLY, 0) : ''; // 记录累加
const total_data_value_diff = items.length ? items.reduce((a, b) => a + b.COLI_YJLY2, 0) : ''; // 记录累加
if (comm.empty(type_data[op_sn])) {
if (comm.emptyValue(type_data[op_sn])) {
type_data[op_sn] = [{ key: item.OPI_SN }, { T_name: item.OPI_Name }, { T_OPI: item.OPI_SN }, { T_total: total_data_value }, { V_total: total_data_value_diff }];
}
const _diff = comm.objectMapper(comm.pick(item, ['COLI_YJLY_diff', 'COLI_YJLY_vs', 'COLI_YJLY2']), {
@ -393,7 +394,7 @@ class SaleStore {
});
type_data[op_sn].push({
['T_' + item.SubTypeSN]: diffDateFlagYes
? comm.show_vs_tag(_diff[`vs_${item.SubTypeSN}`]+'%', _diff[`diff_${item.SubTypeSN}`], item.COLI_YJLY, _diff[`v_${item.SubTypeSN}`])
? show_vs_tag(_diff[`vs_${item.SubTypeSN}`]+'%', _diff[`diff_${item.SubTypeSN}`], item.COLI_YJLY, _diff[`v_${item.SubTypeSN}`])
: item.COLI_YJLY,
..._diff,
[`TV_${item.SubTypeSN}`]: item.COLI_YJLY,
@ -427,7 +428,7 @@ class SaleStore {
children: [
{
title: diffDateFlagYes
? comm.show_vs_tag(totalDiff.vs, totalDiff.diff, totalDiff.val, totalDiff.diffVal)
? show_vs_tag(totalDiff.vs, totalDiff.diff, totalDiff.val, totalDiff.diffVal)
: type_data_arr.reduce((a, b) => a + b.T_total, 0),
dataIndex: 'T_total',
},
@ -447,7 +448,7 @@ class SaleStore {
};
result.columns.push({
title: item.SubTypeName, _val: total_data_value,
children: [{ title: diffDateFlagYes ? comm.show_vs_tag(columnDiff.vs, columnDiff.diff, total_data_value, total_data_value_diff) : total_data_value, dataIndex: data_index }],
children: [{ title: diffDateFlagYes ? show_vs_tag(columnDiff.vs, columnDiff.diff, total_data_value, total_data_value_diff) : total_data_value, dataIndex: data_index }],
sorter: (a, b) => b[`TV_${item.SubTypeSN}`] - a[`TV_${item.SubTypeSN}`],
});
return item;
@ -476,8 +477,8 @@ class SaleStore {
const result = { dataSource: [], columns: [] };
const date1_start = date_moment.start_date.format(config.DATE_FORMAT);
const date1_end = date_moment.end_date.format(config.DATE_FORMAT);
const date2_start = comm.empty(date_moment.start_date_cp) ? '' : date_moment.start_date_cp.format(config.DATE_FORMAT);
const date2_end = comm.empty(date_moment.end_date_cp) ? '' : date_moment.end_date_cp.format(config.DATE_FORMAT);
const date2_start = comm.emptyValue(date_moment.start_date_cp) ? '' : date_moment.start_date_cp.format(config.DATE_FORMAT);
const date2_end = comm.emptyValue(date_moment.end_date_cp) ? '' : date_moment.end_date_cp.format(config.DATE_FORMAT);
let url = '/service-web/QueryData/GetDepartmentOrderMLByType_sub';
url += `?DepartmentList=${this.groups.toString()}&DateType=${this.date_type}&subType=${type_sub}&subTypeVal=-1&WebCode=${this.webcode}&IncludeTickets=${this.include_tickets}`;
url += `&Date1=${date1_start}&Date2=${date1_end}%2023:59:00`;
@ -488,7 +489,7 @@ class SaleStore {
fetch(config.HT_HOST + url)
.then((response) => response.json())
.then((json) => {
// if (!comm.empty(json.result2) && !comm.empty(date_moment.start_date_cp)) {
// if (!comm.emptyValue(json.result2) && !comm.emptyValue(date_moment.start_date_cp)) {
// eslint-disable-next-line no-constant-condition
if (false) {
} else {
@ -533,10 +534,10 @@ class SaleStore {
];
// 数据处理,把相同类型放入同一个数组
const type_data = [];
!comm.empty(json.result1) &&
!comm.emptyValue(json.result1) &&
Object.values(json.result1).map((item) => {
const subtype_sn = 'type_' + item.subType;
if (comm.empty(type_data[subtype_sn])) {
if (comm.emptyValue(type_data[subtype_sn])) {
type_data[subtype_sn] = { subType: item.subType, subType_name: item.subTypeVal, data: [item] };
} else {
type_data[subtype_sn].data.push(item);
@ -641,7 +642,7 @@ class SaleStore {
});
Object.assign(getkpiParam, { object: curObject, subject: 'sum_profit' });
getkpiParam.object_id = curObject === 'overview' ? '' : objects.map((ele) => ele.key).join(',');
const json = await req.fetchJSON('/service-Analyse2/getkpi', getkpiParam);
const json = await req.fetchJSON(config.HT_HOST + '/service-Analyse2/getkpi', getkpiParam);
if (json.errcode === 0) {
const yearData = parseKPI(json.result, ['subject', 'object_id']);
const year = moment(queryData.Date1).year();
@ -654,7 +655,7 @@ class SaleStore {
* 获取业绩数据
*/
async fetchTradeData(queryData) {
const json = await req.fetchJSON('/service-Analyse2/GetTradeProcess', queryData);
const json = await req.fetchJSON(config.HT_HOST + '/service-Analyse2/GetTradeProcess', queryData);
return json;
}
@ -662,7 +663,7 @@ class SaleStore {
* 获取业绩数据: 商务订单
*/
async fetchTradeDataBiz(queryData) {
const json = await req.fetchJSON('/service-Analyse2/GetTradeProcess_biz', queryData);
const json = await req.fetchJSON(config.HT_HOST + '/service-Analyse2/GetTradeProcess_biz', queryData);
return json;
}

@ -1,8 +1,8 @@
import { makeAutoObservable, runInAction } from 'mobx';
import { fetchJSON } from '../utils/request';
import { isEmpty, sortDescBy, groupBy, pick, unique } from '../utils/commons';
import { fetchJSON } from '@haina/utils-request';
import { isEmpty, sortDescBy, groupBy, pick, unique } from '@haina/utils-commons';
import { groupsMappedByCode } from './../libs/ht';
import { DATE_FORMAT, SMALL_DATETIME_FORMAT } from './../config';
import { DATE_FORMAT, SMALL_DATETIME_FORMAT, HT_HOST } from './../config';
import moment from 'moment';
const fetchResultsData = async (param) => {
@ -15,7 +15,7 @@ const fetchResultsData = async (param) => {
groupType: '',
groupDateType: '',
};
const json = await fetchJSON('/service-Analyse2/sales_crm_results', { ...defaultParam, ...param });
const json = await fetchJSON(HT_HOST + '/service-Analyse2/sales_crm_results', { ...defaultParam, ...param });
return json.errcode === 0 ? json.result : [];
};
@ -29,7 +29,7 @@ const fetchProcessData = async (param) => {
groupType: '',
groupDateType: '',
};
const json = await fetchJSON('/service-Analyse2/sales_crm_process', { ...defaultParam, ...param });
const json = await fetchJSON(HT_HOST + '/service-Analyse2/sales_crm_process', { ...defaultParam, ...param });
return json.errcode === 0 ? json.result : [];
};
@ -43,7 +43,7 @@ const fetchRiskDetailData = async (param) => {
Date2: '',
IncludeTickets: '1',
};
const json = await fetchJSON('/service-Analyse2/sales_crm_process_detail', {...defaultParam, ...param});
const json = await fetchJSON(HT_HOST + '/service-Analyse2/sales_crm_process_detail', {...defaultParam, ...param});
return json.errcode === 0 ? json.result : [];
};

@ -1,9 +1,9 @@
import { makeAutoObservable, runInAction, toJS } from 'mobx';
import * as req from '../utils/request';
import * as req from '@haina/utils-request';
import moment from 'moment';
import { isEmpty, sortBy, pick, merge, fixTo2Decimals, groupBy, sortKeys, fixToInt, cloneDeep } from '../utils/commons';
import { isEmpty, sortBy, pick, merge, fixTo2Decimals, groupBy, sortKeys, fixToInt, cloneDeep } from '@haina/utils-commons';
import { dataFieldAlias } from './../libs/ht';
import { DATE_FORMAT, SMALL_DATETIME_FORMAT } from './../config';
import { DATE_FORMAT, SMALL_DATETIME_FORMAT, HT_HOST } from './../config';
class Trade {
constructor(rootStore) {
@ -286,7 +286,7 @@ class Trade {
* 获取业绩数据: 传统订单
*/
async fetchTradeData(queryData) {
const json = await req.fetchJSON('/service-Analyse2/GetTradeProcess', queryData);
const json = await req.fetchJSON(HT_HOST + '/service-Analyse2/GetTradeProcess', queryData);
// if (json.errcode === 0) {
// return json;
// }
@ -297,7 +297,7 @@ class Trade {
* 获取业绩数据: 商务订单
*/
async fetchTradeDataBiz(queryData) {
const json = await req.fetchJSON('/service-Analyse2/GetTradeProcess_biz', queryData);
const json = await req.fetchJSON(HT_HOST + '/service-Analyse2/GetTradeProcess_biz', queryData);
// if (json.errcode === 0) {
// return json;
// }
@ -338,7 +338,10 @@ class Trade {
this.targetTableProps.dataSource = [].concat(Object.values(finalTargetData.targetGuest), Object.values(finalTargetData.targetCountry)); // [finalTargetData.targetTotal], // todo: 总数是重复的
};
searchValues = {};
searchValues = {
// DepartmentList: undefined,
};
setSearch(body, form) {
this.searchPayloadHome = body;
this.searchValues = form;

@ -1,6 +1,6 @@
import {makeAutoObservable, runInAction} from "mobx";
import * as config from "../config";
import * as req from '../utils/request';
import * as req from '@haina/utils-request';
class Wechat {
@ -10,7 +10,7 @@ class Wechat {
}
fetchWechatUserList() {
req.fetchJSON(config.HT_HOST + '/weixin/wxwork/get_permit_user_list')
.then(json => {
if (json.errcode === 0) {
@ -24,7 +24,7 @@ class Wechat {
}
fetchContactList(user) {
req.fetchJSON(config.HT_HOST + '/weixin/wxwork/get_externalcontact_list?userid='+user.userid)
.then(json => {
if (json.errcode === 0) {
@ -36,7 +36,7 @@ class Wechat {
}
});
}
fetchChatMsgList(contact, page, pageSize) {
runInAction(() => {
this.selectedContact = contact;
@ -60,4 +60,4 @@ class Wechat {
}
export default Wechat;
export default Wechat;

@ -1,6 +1,6 @@
import {makeAutoObservable, runInAction} from "mobx";
import * as config from "../config";
import * as req from '../utils/request';
import * as req from '@haina/utils-request';
class WhatsApp {
@ -10,7 +10,7 @@ class WhatsApp {
}
fetchWechatUserList() {
req.fetchJSON(config.HT_HOST + '/weixin/wxwork/get_permit_user_list')
.then(json => {
if (json.errcode === 0) {
@ -24,7 +24,7 @@ class WhatsApp {
}
fetchContactList(user) {
req.fetchJSON(config.HT_HOST + '/weixin/wxwork/get_externalcontact_list?userid='+user.userid)
.then(json => {
if (json.errcode === 0) {
@ -36,7 +36,7 @@ class WhatsApp {
}
});
}
fetchChatMsgList(contact, page, pageSize) {
runInAction(() => {
this.selectedContact = contact;
@ -60,4 +60,4 @@ class WhatsApp {
}
export default WhatsApp;
export default WhatsApp;

@ -1,239 +1,11 @@
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
import { Tag } from "antd";
import { CaretUpOutlined, CaretDownOutlined } from "@ant-design/icons";
import moment from "moment";
if (!String.prototype.padStart) {
String.prototype.padStart = function padStart(targetLength, padString) {
targetLength = targetLength >> 0; // floor if number or convert non-number to 0;
padString = String(typeof padString !== "undefined" ? padString : " ");
if (this.length > targetLength) {
return String(this);
} else {
targetLength = targetLength - this.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength / padString.length); // append to original to ensure we are longer than needed
}
return padString.slice(0, targetLength) + String(this);
}
};
}
if (!Object.fromEntries) {
Object.fromEntries = function(entries) {
const obj = {};
for (let i = 0; i < entries.length; ++i) {
const entry = entries[i];
if (entry && entry.length === 2) {
obj[entry[0]] = entry[1];
}
}
return obj;
};
}
export function copy(obj) {
return JSON.parse(JSON.stringify(obj));
}
export function named(value) {
return function (target) {
target.definedName = value;
};
}
export function formatCurrency(name) {
if (name === "USD") {
return "$";
} else if (name === "RMB") {
return "¥";
} else if (name === "EUR") {
return "€";
} else if (name === "GBP") {
return "£";
} else {
return name + " ";
}
}
export function formatPrice(price) {
return Math.ceil(price).toLocaleString();
}
// 千分符的金额转成数字默认为0
export function price_to_number(price) {
const num_string = (price + "").replace(/,/g, "");
const number = parseFloat(num_string);
return isNaN(number) ? 0 : number;
}
export function formatPercent(number) {
return Math.round(number * 100) + "%";
}
export function formatPercentToFloat(number) {
return parseFloat((number * 100).toFixed(2)) + "%";
}
export function percentToDecimal(number) {
return parseFloat(number) / 100;
}
export function formatDate(date) {
if (isEmpty(date)) {
return "NaN";
}
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const monthStr = ("" + month).padStart(2, 0);
const dayStr = ("" + day).padStart(2, 0);
const formatted = year + "-" + monthStr + "-" + dayStr;
return formatted;
}
export function formatTime(date) {
const hours = date.getHours();
const minutes = date.getMinutes();
const hoursStr = ("" + hours).padStart(2, 0);
const minutesStr = ("" + minutes).padStart(2, 0);
const formatted = hoursStr + ":" + minutesStr;
return formatted;
}
export function formatDatetime(date) {
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const monthStr = ("" + month).padStart(2, 0);
const dayStr = ("" + day).padStart(2, 0);
const hours = date.getHours();
const minutes = date.getMinutes();
const hoursStr = ("" + hours).padStart(2, 0);
const minutesStr = ("" + minutes).padStart(2, 0);
const formatted = year + "-" + monthStr + "-" + dayStr + " " + hoursStr + ":" + minutesStr;
return formatted;
}
export function mixins(...list) {
return function (target) {
list.forEach(val => {
const mixinObj = Object.create(val.prototype, {});
const name = Object.getPrototypeOf(mixinObj).constructor.name;
const camelCase = name.substr(0, 1).toLowerCase() + name.substr(1);
Object.assign(target.prototype, { [camelCase]: mixinObj });
});
};
}
export function camelCase(name) {
return name.substr(0, 1).toLowerCase() + name.substr(1);
}
export class UrlBuilder {
constructor(url) {
this.url = url;
this.paramList = [];
}
append(name, value) {
if (isNotEmpty(value)) {
this.paramList.push({ name, value });
}
return this;
}
build() {
this.paramList.forEach((e, i, a) => {
if (i === 0) {
this.url += "?";
} else {
this.url += "&";
}
this.url += e.name + "=" + e.value;
});
return this.url;
}
}
export function isNotEmpty(val) {
return val !== undefined && val !== null && val !== "";
}
/**
* ! 不支持计算 Set Map
* @param {*} val
* @example
* true if: 0, [], {}, null, '', undefined
* false if: 'false', 'undefined'
* 显示对比标签
* @deprecated
* 使用组件: VSTag VSDataTag RenderVSDataCell
*/
export function isEmpty(val) {
// return val === undefined || val === null || val === "";
return [Object, Array].includes((val || {}).constructor) && !Object.entries((val || {})).length;
}
/**
* @example
* empty(0) => false
*/
export function empty(a) {
if (a === "") return true; // 检验空字符串
if (a === "null") return true; // 检验字符串类型的null
if (a === "undefined") return true; // 检验字符串类型的 undefined
if (!a && a !== 0 && a !== "") return true; // 检验 undefined 和 null
if (Array.prototype.isPrototypeOf(a) && a.length === 0) return true; // 检验空数组
if (Object.prototype.isPrototypeOf(a) && Object.keys(a).length === 0) return true; // 检验空对象
return false;
}
export function prepareUrl(url) {
return new UrlBuilder(url);
}
export function debounce(fn, delay = 500) {
let timer;
return e => {
e.persist();
clearTimeout(timer);
timer = setTimeout(() => {
fn(e);
}, delay);
};
}
export function throttle(fn, delay, atleast) {
let timeout = null;
let startTime = new Date();
return function () {
const curTime = new Date();
clearTimeout(timeout);
if (curTime - startTime >= atleast) {
fn();
startTime = curTime;
} else {
timeout = setTimeout(fn, delay);
}
};
}
export function clickUrl(url) {
const httpLink = document.createElement("a");
httpLink.href = url;
httpLink.target = "_blank";
httpLink.click();
}
export function show_vs_tag(vs, vs_diff, data1, data2) {
let tag = "-";
if (parseInt(vs) < 0) {
@ -258,340 +30,3 @@ export function show_vs_tag(vs, vs_diff, data1, data2) {
</span>
);
}
// 数组去掉重复
export function unique(arr) {
const x = new Set(arr);
return [...x];
}
export const uniqWith = (arr, fn) => arr.filter((element, index) => arr.findIndex((step) => fn(element, step)) === index);
export function getWeek(date) {
// 参数时间戳
const week = moment(date).day();
switch (week) {
case 1:
return "周一";
case 2:
return "周二";
case 3:
return "周三";
case 4:
return "周四";
case 5:
return "周五";
case 6:
return "周六";
case 0:
return "周日";
}
}
// 把非数字下标的数组设置下标因为非数字数组的length为0导致读取失败
export function set_array_index(result) {
const result_array = [];
const result_keys = Object.keys(result);
result_keys.sort(); // 必须做一次排序用for in循环会导致顺序错误
for (const key of result_keys) {
result_array.push(result[key]);
}
return result_array;
}
/**
* 数组排序
*/
export const sortBy = (key) => {
return (a, b) => (a[key] > b[key]) ? 1 : ((b[key] > a[key]) ? -1 : 0);
};
export const sortDescBy = (key) => {
return (a, b) => (a[key] < b[key]) ? 1 : ((b[key] < a[key]) ? -1 : 0);
};
/**
* Object排序keys
*/
export const sortKeys = (obj) =>
Object.keys(obj)
.sort()
.reduce((a, k2) => ({...a, [k2]: obj[k2]}), {});
/**
* 数组排序, 给定排序数组
* @param {array} items 需要排序的数组
* @param {array} keyName 排序的key
* @param {array} keyOrder 给定排序
* @returns
*/
export const sortArrayByOrder = (items, keyName, keyOrder) => {
return items.sort((a, b) => {
return keyOrder.indexOf(a[keyName]) - keyOrder.indexOf(b[keyName]);
});
};
/**
* 合并Object, 递归地
*/
export function merge(...objects) {
const isDeep = objects.some(obj => obj !== null && typeof obj === 'object');
const result = objects[0] || (isDeep ? {} : objects[0]);
for (let i = 1; i < objects.length; i++) {
const obj = objects[i];
if (!obj) continue;
Object.keys(obj).forEach(key => {
const val = obj[key];
if (isDeep) {
if (Array.isArray(val)) {
result[key] = [].concat(Array.isArray(result[key]) ? result[key] : [result[key]], val);
} else if (typeof val === 'object') {
result[key] = merge(result[key], val);
} else {
result[key] = val;
}
} else {
result[key] = typeof val === 'boolean' ? val : result[key];
}
});
}
return result;
}
/**
* 数组分组
* - 相当于 lodash _.groupBy
* @see https://www.lodashjs.com/docs/lodash.groupBy#_groupbycollection-iteratee_identity
*/
export function groupBy(array, callback) {
return array.reduce((groups, item) => {
const key = typeof callback === 'function' ? callback(item) : item[callback];
if (!groups[key]) {
groups[key] = [];
}
groups[key].push(item);
return groups;
}, {});
}
/**
* 创建一个从 object 中选中的属性的对象
* @param {*} object
* @param {array} keys
*/
export function pick(object, keys) {
return keys.reduce((obj, key) => {
if (object && Object.prototype.hasOwnProperty.call(object, key)) {
obj[key] = object[key];
}
return obj;
}, {});
}
/**
* 返回对象的副本经过筛选以省略指定的键
* @param {*} object
* @param {string[]} keysToOmit
* @returns
*/
export function omit(object, keysToOmit) {
return Object.fromEntries(
Object.entries(object).filter(
([key]) => !keysToOmit.includes(key)
)
);
}
/**
* 深拷贝
*/
export function cloneDeep(value) {
if (typeof value !== 'object' || value === null) {
return value;
}
const result = Array.isArray(value) ? [] : {};
for (const key in value) {
if (Object.prototype.hasOwnProperty.call(value, key)) {
result[key] = cloneDeep(value[key]);
}
}
return result;
}
/**
* 向零四舍五入, 固定精度设置
*/
function curriedFix(precision = 0) {
return function(number) {
// Shift number by precision places
const shift = Math.pow(10, precision);
const shiftedNumber = number * shift;
// Round to nearest integer
const roundedNumber = Math.round(shiftedNumber);
// Shift back decimal place
return roundedNumber / shift;
};
}
/**
* 向零四舍五入, 保留2位小数
*/
export const fixTo2Decimals = curriedFix(2);
/**
* 向零四舍五入, 保留4位小数
*/
export const fixTo4Decimals = curriedFix(4);
export const fixTo1Decimals = curriedFix(1);
export const fixToInt = curriedFix(0);
/**
* 映射
* @example
* const keyMap = {
a: [{key: 'a1'}, {key: 'a2', transform: v => v * 2}],
b: {key: 'b1'}
};
const result = objectMapper({a: 1, b: 3}, keyMap);
// result = {a1: 1, a2: 2, b1: 3}
*
*/
export function objectMapper(input, keyMap) {
// Loop through array mapping
if (Array.isArray(input)) {
return input.map((obj) => objectMapper(obj, keyMap));
}
if (typeof input === 'object') {
const mappedObj = {};
Object.keys(input).forEach((key) => {
// Keep original keys not in keyMap
if (!keyMap[key]) {
mappedObj[key] = input[key];
}
// Handle array of maps
if (Array.isArray(keyMap[key])) {
keyMap[key].forEach((map) => {
let value = input[key];
if (map.transform) value = map.transform(value);
mappedObj[map.key] = value;
});
// Handle single map
} else {
const map = keyMap[key];
if (map) {
let value = input[key];
if (map.transform) value = map.transform(value);
if (typeof map === 'string') mappedObj[map] = value;
mappedObj[map.key || key] = value;
}
}
});
return mappedObj;
}
return input;
}
/**
* 创建一个对应于对象路径的值数组
*/
export function at(obj, path) {
let result;
if (Array.isArray(obj)) {
// array case
const indexes = path.split('.').map((i) => parseInt(i));
result = [];
for (let i = 0; i < indexes.length; i++) {
result.push(obj[indexes[i]]);
}
} else {
// object case
const indexes = path.split('.').map((i) => i);
result = [obj];
for (let i = 0; i < indexes.length; i++) {
result = [result[0][indexes[i]]];
}
}
return result;
}
/**
* 删除 null/undefined
*/
export function flush(collection) {
let result, len, i;
if (!collection) {
return undefined;
}
if (Array.isArray(collection)) {
result = [];
len = collection.length;
for (i = 0; i < len; i++) {
const elem = collection[i];
if (elem != null) {
result.push(elem);
}
}
return result;
}
if (typeof collection === 'object') {
result = {};
const keys = Object.keys(collection);
len = keys.length;
for (i = 0; i < len; i++) {
const key = keys[i];
const value = collection[key];
if (value != null) {
result[key] = value;
}
}
return result;
}
return undefined;
}
/**
* 千分位 格式化数字
*/
export const numberFormatter = (number) => {
return new Intl.NumberFormat().format(number);
};
/**
* @example
* const obj = { a: { b: 'c' } };
* const keyArr = ['a', 'b'];
* getNestedValue(obj, keyArr); // Returns: 'c'
*/
export const getNestedValue = (obj, keyArr) => {
return keyArr.reduce((acc, curr) => {
return acc && acc.hasOwnProperty(curr) ? acc[curr] : undefined;
// return acc && acc[curr];
}, obj);
};
/**
* 计算笛卡尔积
*/
export const cartesianProductArray = (arr, sep = '_', index = 0, prefix = '') => {
let result = [];
if(index === arr.length){
return [prefix];
}
arr[index].forEach(item => {
result = result.concat(cartesianProductArray(arr, sep, index+1, prefix ? `${prefix}${sep}${item}` : `${item}`));
});
return result;
};

@ -1,95 +0,0 @@
import { HT_HOST }from "../config";
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
} else {
const message =
'Fetch error: ' + response.url + ' ' + response.status + ' (' +
response.statusText + ')';
const error = new Error(message);
error.response = response;
throw error;
}
}
export function fetchText(url) {
return fetch(url)
.then(checkStatus)
.then(response => response.text())
.catch(error => {
throw error;
});
}
export function fetchJSON(url, data) {
let params = '';
let ifp = '';
if (data) {
params = new URLSearchParams(data).toString();
ifp = params ? '?' : ifp;
}
ifp = url.includes('?') ? '' : ifp;
const host = /^https?:\/\//i.test(url) ? '': HT_HOST;
return fetch(`${host}${url}${ifp}${params}`)
.then(checkStatus)
.then(response => response.json())
.catch(error => {
throw error;
});
}
export function postForm(url, data) {
return fetch(url, {
method: 'POST',
body: data
}).then(checkStatus)
.then(response => response.json())
.catch(error => {
throw error;
});
}
export function postJSON(url, obj) {
const host = /^https?:\/\//i.test(url) ? '': HT_HOST;
return fetch(`${host}${url}`, {
method: 'POST',
body: JSON.stringify(obj),
headers: {
'Content-type': 'application/json; charset=UTF-8'
}
}).then(checkStatus)
.then(response => response.json())
.catch(error => {
throw error;
});
}
export function postStream(url, obj) {
return fetch(url, {
method: 'POST',
body: JSON.stringify(obj),
headers: {
'Content-type': 'application/octet-stream'
}
}).then(checkStatus)
.then(response => response.json())
.catch(error => {
throw error;
});
}
export function delJSON(url, obj) {
const host = /^https?:\/\//i.test(url) ? '': HT_HOST;
return fetch(`${host}${url}`, {
method: 'DELETE',
body: JSON.stringify(obj),
headers: {
'Content-type': 'application/json; charset=UTF-8'
}
}).then(checkStatus)
.then(response => response.json())
.catch(error => {
throw error;
});
}

@ -1,32 +1,238 @@
import { useContext, useEffect } from 'react';
import { useContext } from 'react';
import { NavLink } from "react-router-dom";
import { Row, Col, Typography, Space, Table, Divider } from 'antd';
import { stores_Context } from '../config';
import { observer } from 'mobx-react';
import { toJS } from 'mobx';
import 'moment/locale/zh-cn';
import { utils, writeFileXLSX } from 'xlsx';
import SearchForm from './../components/search/SearchForm';
import { TableExportBtn } from './../components/Data';
import { TableExportBtn, RenderVSDataCell } from './../components/Data';
import useCustomerServicesStore from '../zustand/CustomerServices';
import { useShallow } from 'zustand/shallow';
import { fixTo2Decimals } from "@haina/utils-commons";
const sorter = (a, b, key) => a[key] - b[key];
// TdCellDataTable
const TdCell = (tdprops) => {
// onMouseEnter, onMouseLeave
const { onMouseEnter, onMouseLeave, ...restProps } = tdprops;
return <td {...restProps} />;
};
const AgentGroupCount = () => {
const { customerServicesStore, date_picker_store } = useContext(stores_Context);
const agentGroupList = customerServicesStore.agentGroupList;
const agentGroupListColumns = customerServicesStore.agentGroupListColumns;
const { inProgress } = customerServicesStore;
const [loading, searchValues, setSearchValues, searchValuesToSub] = useCustomerServicesStore(useShallow((state) => [state.loading, state.searchValues, state.setSearchValues, state.searchValuesToSub]));
const [agentGroupList, total1] = useCustomerServicesStore(useShallow((state) => [state.agentCountList, state.agentCountTotal]));
const getAgentGroupCount = useCustomerServicesStore((state) => state.getAgentGroupCount);
useEffect(() => {
customerServicesStore.fetchAllAgent();
}, []);
const columns = [
{
title: '地接社名称',
dataIndex: 'VendorName',
fixed: 'left',
children: [
{
// title: this.startDate.format(config.DATE_FORMAT) + '~' + this.endDate.format(config.DATE_FORMAT),
dataIndex: 'VendorName',
fixed: 'left',
render: (text, record) => <NavLink to={`/agent/${record.EOI_ObjSN}/group/list`}>{record.VendorName}</NavLink>,
},
],
},
{
title: '团数',
dataIndex: 'GroupCount',
sorter: (a, b) => sorter(a, b, 'GroupCount'),
children: [
{
title: <RenderVSDataCell data1={total1.GroupCount} data2={total1.diff?.GroupCount} showDiffData={!!searchValuesToSub.DateDiff1} />, // total1.GroupCount,
dataIndex: 'GroupCount',
render: (text, r) => <RenderVSDataCell data1={r.GroupCount} data2={r.diff?.GroupCount} showDiffData={!!searchValuesToSub.DateDiff1} />,
},
],
},
{
title: '人数',
dataIndex: 'PersonNum',
sorter: (a, b) => sorter(a, b, 'PersonNum'),
children: [
{
title: <RenderVSDataCell data1={total1.PersonNum} data2={total1.diff?.PersonNum} showDiffData={!!searchValuesToSub.DateDiff1} />, // total1.PersonNum,
dataIndex: 'PersonNum',
render: (text, r) => <RenderVSDataCell data1={r.PersonNum} data2={r.diff?.PersonNum} showDiffData={!!searchValuesToSub.DateDiff1} />,
},
],
},
{
title: '团天数',
dataIndex: 'GroupDays',
sorter: (a, b) => sorter(a, b, 'GroupDays'),
children: [
{
title: <RenderVSDataCell data1={total1.GroupDays} data2={total1.diff?.GroupDays} showDiffData={!!searchValuesToSub.DateDiff1} />, // total1.GroupDays,
dataIndex: 'GroupDays',
render: (text, r) => <RenderVSDataCell data1={r.GroupDays} data2={r.diff?.GroupDays} showDiffData={!!searchValuesToSub.DateDiff1} />,
},
],
},
{
title: '交易额',
dataIndex: 'totalcost',
sorter: (a, b) => sorter(a, b, 'totalcost'),
children: [
{
title: <RenderVSDataCell data1={total1.totalcost} data2={total1.diff?.totalcost} showDiffData={!!searchValuesToSub.DateDiff1} />, // total1.totalcost,
dataIndex: 'totalcost',
render: (text, r) => <RenderVSDataCell data1={r.totalcost} data2={r.diff?.totalcost} showDiffData={!!searchValuesToSub.DateDiff1} />,
},
],
},
{
title: '反馈表团数',
dataIndex: 'FKBTS',
sorter: (a, b) => sorter(a, b, 'FKBTS'),
children: [
{
title: <RenderVSDataCell data1={total1.FKBTS} data2={total1.diff?.FKBTS} showDiffData={!!searchValuesToSub.DateDiff1} />, // total1.FKBTS,
dataIndex: 'FKBTS',
render: (text, r) => <RenderVSDataCell data1={r.FKBTS} data2={r.diff?.FKBTS} showDiffData={!!searchValuesToSub.DateDiff1} />,
},
],
},
{
title: '东道主团数',
dataIndex: 'DDZTS',
sorter: (a, b) => sorter(a, b, 'DDZTS'),
children: [
{
title: <RenderVSDataCell data1={total1.DDZTS} data2={total1.diff?.DDZTS} showDiffData={!!searchValuesToSub.DateDiff1} />, // total1.DDZTS,
dataIndex: 'DDZTS',
render: (text, r) => <RenderVSDataCell data1={r.DDZTS} data2={r.diff?.DDZTS} showDiffData={!!searchValuesToSub.DateDiff1} />,
},
],
},
{
title: '东道主案例数',
dataIndex: 'DDZCases',
sorter: (a, b) => sorter(a, b, 'DDZCases'),
children: [
{
title: <RenderVSDataCell data1={total1.DDZCases} data2={total1.diff?.DDZCases} showDiffData={!!searchValuesToSub.DateDiff1} />, // total1.DDZCases,
dataIndex: 'DDZCases',
render: (text, r) => <RenderVSDataCell data1={r.DDZCases} data2={r.diff?.DDZCases} showDiffData={!!searchValuesToSub.DateDiff1} />,
},
],
},
{
title: '东道主案例比例',
dataIndex: 'DDZRate',
sorter: (a, b) => sorter(a, b, 'DDZRate'),
children: [
{
title: <RenderVSDataCell data1={fixTo2Decimals(total1.DDZRate*100)} data2={fixTo2Decimals(total1.diff?.DDZRate*100)} dataSuffix='%' showDiffData={!!searchValuesToSub.DateDiff1} />, // total1.DDZRate,
dataIndex: 'DDZRate',
render: (text, r) => <RenderVSDataCell data1={fixTo2Decimals(r.DDZRate*100)} data2={fixTo2Decimals(r.diff?.DDZRate*100)} dataSuffix='%' showDiffData={!!searchValuesToSub.DateDiff1} />,
},
],
},
{
title: '站外好评团数',
dataIndex: 'ZWHP',
sorter: (a, b) => sorter(a, b, 'ZWHP'),
children: [
{
title: <RenderVSDataCell data1={total1.ZWHP} data2={total1.diff?.ZWHP} showDiffData={!!searchValuesToSub.DateDiff1} />, // total1.ZWHP,
dataIndex: 'ZWHP',
render: (text, r) => <RenderVSDataCell data1={r.ZWHP} data2={r.diff?.ZWHP} showDiffData={!!searchValuesToSub.DateDiff1} />,
},
],
},
{
title: '站外好评数',
dataIndex: 'ZWHPCases',
sorter: (a, b) => sorter(a, b, 'ZWHPCases'),
children: [
{
title: <RenderVSDataCell data1={total1.ZWHPCases} data2={total1.diff?.ZWHPCases} showDiffData={!!searchValuesToSub.DateDiff1} />, // total1.ZWHPCases,
dataIndex: 'ZWHPCases',
render: (text, r) => <RenderVSDataCell data1={r.ZWHPCases} data2={r.diff?.ZWHPCases} showDiffData={!!searchValuesToSub.DateDiff1} />,
},
],
},
{
title: '站外好评率',
dataIndex: 'ZWHPRate',
sorter: (a, b) => sorter(a, b, 'ZWHPRate'), // parseInt(a.ZWHPRate) - parseInt(b.ZWHPRate),
children: [
{
title: <RenderVSDataCell data1={fixTo2Decimals(total1.ZWHPRate*100)} data2={fixTo2Decimals(total1.diff?.ZWHPRate*100)} dataSuffix='%' showDiffData={!!searchValuesToSub.DateDiff1} />, // total1.ZWHPRate,
dataIndex: 'ZWHPRate',
render: (text, r) => <RenderVSDataCell data1={fixTo2Decimals(r.ZWHPRate*100)} data2={fixTo2Decimals(r.diff?.ZWHPRate*100)} dataSuffix='%' showDiffData={!!searchValuesToSub.DateDiff1} />,
},
],
},
{
title: '不满数',
dataIndex: 'BMS',
sorter: (a, b) => sorter(a, b, 'BMS'),
children: [
{
title: <RenderVSDataCell data1={total1.BMS} data2={total1.diff?.BMS} showDiffData={!!searchValuesToSub.DateDiff1} />, // total1.BMS,
dataIndex: 'BMS',
render: (text, r) => <RenderVSDataCell data1={r.BMS} data2={r.diff?.BMS} showDiffData={!!searchValuesToSub.DateDiff1} />,
},
],
},
{
title: '不满率',
dataIndex: 'BMRate',
sorter: (a, b) => sorter(a, b, 'BMRate'), // parseInt(a.BMRate) - parseInt(b.BMRate),
children: [
{
title: <RenderVSDataCell data1={fixTo2Decimals(total1.BMRate*100)} data2={fixTo2Decimals((total1.diff?.BMRate || 0)*100)} dataSuffix='%' showDiffData={!!searchValuesToSub.DateDiff1} />, // total1.BMRate,
dataIndex: 'BMRate',
render: (text, r) => <RenderVSDataCell data1={fixTo2Decimals(r.BMRate*100)} data2={fixTo2Decimals(r.diff?.BMRate*100)} dataSuffix='%' showDiffData={!!searchValuesToSub.DateDiff1} />,
},
],
},
{
title: '投诉数',
dataIndex: 'TS',
sorter: (a, b) => sorter(a, b, 'TS'),
children: [
{
title: <RenderVSDataCell data1={total1.TS} data2={total1.diff?.TS} showDiffData={!!searchValuesToSub.DateDiff1} />, // total1.TS,
dataIndex: 'TS',
render: (text, r) => <RenderVSDataCell data1={r.TS} data2={r.diff?.TS} showDiffData={!!searchValuesToSub.DateDiff1} />,
},
],
},
{
title: '投诉率',
dataIndex: 'TSRate',
sorter: (a, b) => sorter(a, b, 'TSRate'), // parseInt(a.TSRate) - parseInt(b.TSRate),
children: [
{
title: <RenderVSDataCell data1={fixTo2Decimals(total1.TSRate*100)} data2={fixTo2Decimals(total1.diff?.TSRate*100)} dataSuffix='%' showDiffData={!!searchValuesToSub.DateDiff1} />, // total1.TSRate,
dataIndex: 'TSRate',
render: (text, r) => <RenderVSDataCell data1={fixTo2Decimals(r.TSRate*100)} data2={fixTo2Decimals(r.diff?.TSRate*100)} dataSuffix='%' showDiffData={!!searchValuesToSub.DateDiff1} />,
},
],
},
];
return (
<>
<Space direction="vertical" style={{ width: '100%' }}>
<Row gutter={16} className={date_picker_store.siderBroken ? "" : "sticky-top"} >
<Row gutter={16} className={toJS(date_picker_store.siderBroken) ? "" : "sticky-top"} >
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...date_picker_store.formValues,
...toJS(date_picker_store.formValues),
...customerServicesStore.searchValues,
...searchValues,
},
shows: ['agency', 'departureDateType', 'DepartmentList', 'countryArea', 'dates'],
fieldProps: {
@ -38,7 +244,8 @@ const AgentGroupCount = () => {
}}
onSubmit={(_err, obj, form) => {
customerServicesStore.setSearchValues(obj, form);
customerServicesStore.fetchAgentGroupCount();
setSearchValues(obj, form);
getAgentGroupCount(obj);
}}
/>
</Col>
@ -47,18 +254,19 @@ const AgentGroupCount = () => {
<Col span={24}>
<Typography.Title level={3}>地接社团信息</Typography.Title>
<Divider orientation="right" plain>
<TableExportBtn label={'地接社团信息'} {...{ columns: agentGroupListColumns, dataSource: agentGroupList }} />
<TableExportBtn label={'地接社团信息'} {...{ columns, dataSource: agentGroupList }} />
</Divider>
<Table
sticky
id="agentGroupList"
components={{ body: { cell: TdCell } }}
dataSource={agentGroupList}
columns={agentGroupListColumns}
columns={columns}
size="small"
rowKey={(record) => record.key}
loading={inProgress}
rowKey={(record) => record.EOI_ObjSN}
loading={loading}
pagination={false}
scroll={{ x: 1000 }}
scroll={{ x: 2000 }}
/>
</Col>
</Row>

@ -1,22 +1,161 @@
import { useContext, useEffect } from 'react';
import { useContext, useEffect, useMemo } from 'react';
import { Row, Col, Typography, Space, Table, List } from 'antd';
import { stores_Context } from '../config';
import { observer } from 'mobx-react';
import { NavLink, useParams } from 'react-router-dom';
import 'moment/locale/zh-cn';
import SearchForm from './../components/search/SearchForm';
import useCustomerServicesStore from '../zustand/CustomerServices';
import { useShallow } from 'zustand/shallow';
import { isEmpty } from '@haina/utils-commons';
import { toJS } from 'mobx';
const buildColumns = (total) => [
{
title: '团名',
dataIndex: 'GRI_Name',
children: [
{
title: '',
dataIndex: 'GRI_Name',
},
],
},
{
title: '人数',
dataIndex: 'COLI_PersonNum',
sorter: (a, b) => a.COLI_PersonNum - b.COLI_PersonNum,
children: [
{
title: total.COLI_PersonNum,
dataIndex: 'COLI_PersonNum',
},
],
},
{
title: '天数',
dataIndex: 'COLI_Days',
sorter: (a, b) => a.COLI_Days - b.COLI_Days,
children: [
{
title: total.COLI_Days,
dataIndex: 'COLI_Days',
},
],
},
{
title: '交易额',
dataIndex: 'totalcost',
sorter: (a, b) => a.totalcost - b.totalcost,
children: [
{
title: total.totalcost,
dataIndex: 'totalcost',
},
],
},
{
title: '导游',
dataIndex: 'GuideName',
children: [
{
title: '-',
dataIndex: 'GuideName',
},
],
},
{
title: '反馈表',
dataIndex: 'FKB',
sorter: (a, b) => a.FKB - b.FKB,
children: [
{
title: total.FKB,
dataIndex: 'FKB',
},
],
},
{
title: '东道主',
dataIndex: 'DDZCases',
sorter: (a, b) => a.DDZCases - b.DDZCases,
children: [
{
title: total.DDZCases,
dataIndex: 'DDZCases',
},
],
},
{
title: '前勤分',
dataIndex: 'qianqin',
children: [
{
title: total.qianqin,
dataIndex: 'qianqin',
},
],
},
{
title: '好评',
dataIndex: 'Good',
sorter: (a, b) => a.Good - b.Good,
children: [
{
title: total.Good,
dataIndex: 'Good',
},
],
},
{
title: '差评',
dataIndex: 'Bad',
sorter: (a, b) => a.Bad - b.Bad,
children: [
{
title: total.Bad,
dataIndex: 'Bad',
},
],
},
{
title: '投诉',
dataIndex: 'TS',
sorter: (a, b) => a.TS - b.TS,
children: [
{
title: total.TS,
dataIndex: 'TS',
},
],
},
{
title: '不满',
dataIndex: 'BM',
sorter: (a, b) => a.BM - b.BM,
children: [
{
title: total.BM,
dataIndex: 'BM',
},
],
},
];
const AgentGroupList = () => {
const { agentId } = useParams();
const { customerServicesStore, date_picker_store } = useContext(stores_Context);
const [loading, searchValues, setSearchValues] = useCustomerServicesStore(useShallow((state) => [state.loading, state.searchValues, state.setSearchValues]));
const [{ result1, result2, total1, total2, title1, title2 }, agencyName] = useCustomerServicesStore(useShallow((state) => [state.agencyGroups, state.agencyName]));
const getGroupListByAgentId = useCustomerServicesStore((state) => state.getGroupListByAgentId);
useEffect(() => {
customerServicesStore.fetchGroupListByAgentId(agentId);
getGroupListByAgentId(agentId);
}, []);
const groupList = customerServicesStore.groupList;
const groupListColumns = customerServicesStore.groupListColumns;
const { startDate, endDate, dateType, inProgress } = customerServicesStore;
const columns1 = useMemo(() => buildColumns(total1), [total1]);
const columns2 = useMemo(() => buildColumns(total2), [total2]);
return (
<>
@ -29,34 +168,37 @@ const AgentGroupList = () => {
<SearchForm
defaultValue={{
initialValue: {
...date_picker_store.formValues,
...customerServicesStore.searchValues,
...toJS(date_picker_store.formValues),
...toJS(customerServicesStore.searchValues),
...searchValues,
},
shows: ['departureDateType', 'DepartmentList', 'dates'],
fieldProps: {
DepartmentList: { show_all: true },
WebCode: { show_all: false, mode: 'multiple' },
dates: { hide_vs: true },
dates: { hide_vs: false },
departureDateType: { disabledKeys: ['applyDate'] },
},
}}
onSubmit={(_err, obj, form, str) => {
customerServicesStore.setSearchValues(obj, form);
customerServicesStore.fetchGroupListByAgentId(agentId);
setSearchValues(obj, form);
getGroupListByAgentId(agentId);
}}
/>
</Col>
</Row>
<Row>
<Col span={24}>
<Typography.Title level={3}>{customerServicesStore.agentCompany}</Typography.Title>
<Typography.Title level={3}>{agencyName}</Typography.Title>
<Table
title={() => title1}
sticky
dataSource={groupList}
columns={groupListColumns}
dataSource={result1}
columns={columns1}
size="small"
rowKey={(record) => record.key}
loading={inProgress}
rowKey={(record) => record.GRI_SN}
loading={loading}
pagination={false}
scroll={{ x: 1000 }}
expandable={{
@ -72,6 +214,31 @@ const AgentGroupList = () => {
),
}}
/>
{isEmpty(result2) ? null : (
<Table
title={() => title2}
sticky
dataSource={result2}
columns={columns2}
size="small"
rowKey={(record) => record.GRI_SN}
loading={loading}
pagination={false}
scroll={{ x: 1000 }}
expandable={{
expandedRowRender: (record) => (
<List itemLayout="horizontal">
<List.Item>
<List.Item.Meta title="经过城市" description={record.PassCity} />
</List.Item>
<List.Item>
<List.Item.Meta title="评论内容" description={record.ECI_Content} />
</List.Item>
</List>
),
}}
/>
)}
</Col>
</Row>
</Space>

@ -11,7 +11,8 @@ import Business_unit from "../components/search/BusinessSelect";
import DatePickerCharts from "../components/search/DatePickerCharts";
import SearchForm from './../components/search/SearchForm';
import { Line, Pie } from "@ant-design/charts";
import * as comm from "../utils/commons";
import * as comm from '@haina/utils-commons';
import { show_vs_tag } from './../utils/commons';
import * as config from "../config";
const Credit_card_bill = () => {
@ -20,7 +21,7 @@ const Credit_card_bill = () => {
const format_data = data => {
const result = { dataSource: [], columns: [] };
if (!comm.empty(data)) {
if (!comm.emptyValue(data)) {
if (date_picker_store.start_date_cp && date_picker_store.end_date_cp) {
//
const total_data1 = data.BillTypeDataTotal1;
@ -49,7 +50,7 @@ const Credit_card_bill = () => {
title: "美金",
children: [
{
title: comm.show_vs_tag(total_data1.usd_vs, total_data1.usd_diff, total_data1.cb_usd, total_data2.cb_usd),
title: show_vs_tag(total_data1.usd_vs, total_data1.usd_diff, total_data1.cb_usd, total_data2.cb_usd),
dataIndex: "cb_usd",
},
],
@ -58,7 +59,7 @@ const Credit_card_bill = () => {
title: "人民币",
children: [
{
title: comm.show_vs_tag(total_data1.rmb_vs, total_data1.rmb_diff, total_data1.cb_rmb, total_data2.cb_rmb),
title: show_vs_tag(total_data1.rmb_vs, total_data1.rmb_diff, total_data1.cb_rmb, total_data2.cb_rmb),
dataIndex: "cb_rmb",
},
],
@ -71,8 +72,8 @@ const Credit_card_bill = () => {
key: item.key,
cb_billtype: item.cb_billtype,
groups: item.groups,
cb_usd: comm.show_vs_tag(item.usd_vs, item.usd_diff, item.cb_usd, item2.cb_usd),
cb_rmb: comm.show_vs_tag(item.rmb_vs, item.rmb_diff, item.cb_rmb, item2.cb_rmb),
cb_usd: show_vs_tag(item.usd_vs, item.usd_diff, item.cb_usd, item2.cb_usd),
cb_rmb: show_vs_tag(item.rmb_vs, item.rmb_diff, item.cb_rmb, item2.cb_rmb),
});
}
}
@ -124,7 +125,7 @@ const Credit_card_bill = () => {
const format_data_detail = data => {
const result = { dataSource: [], columns: [] };
if (!comm.empty(data)) {
if (!comm.emptyValue(data)) {
let show_vs = false;
let usd_totle1;
let usd_totle2;
@ -185,7 +186,7 @@ const Credit_card_bill = () => {
title: "美金",
children: [
{
title: show_vs ? comm.show_vs_tag(usd_vs, usd_diff, usd_totle1, usd_totle2) : usd_totle1,
title: show_vs ? show_vs_tag(usd_vs, usd_diff, usd_totle1, usd_totle2) : usd_totle1,
dataIndex: "cb_usd",
},
],
@ -195,7 +196,7 @@ const Credit_card_bill = () => {
title: "人民币",
children: [
{
title: show_vs ? comm.show_vs_tag(rmb_vs, rmb_diff, rmb_totle1, rmb_totle2) : rmb_totle1,
title: show_vs ? show_vs_tag(rmb_vs, rmb_diff, rmb_totle1, rmb_totle2) : rmb_totle1,
dataIndex: "cb_rmb",
},
],
@ -231,8 +232,8 @@ const Credit_card_bill = () => {
return data.map(item => ({...item, cb_usd_number: parseFloat(item.cb_usd.replace(/,/g, ""))}));
};
const credit_card_bills = !comm.empty(credit_card_data.data) ? format_data_detail(credit_card_data.data) : format_data_detail([]);
const credit_card_bills_by_type = !comm.empty(credit_card_data.data_by_type) ? format_data(credit_card_data.data_by_type) : format_data([]);
const credit_card_bills = !comm.emptyValue(credit_card_data.data) ? format_data_detail(credit_card_data.data) : format_data_detail([]);
const credit_card_bills_by_type = !comm.emptyValue(credit_card_data.data_by_type) ? format_data(credit_card_data.data_by_type) : format_data([]);
const pie_config = {
appendPadding: 10,

@ -3,12 +3,12 @@ import { observer } from 'mobx-react';
import { useParams, useLocation } from 'react-router-dom';
import { stores_Context } from '../config';
import { Row, Col, Spin, Table, Select, Typography, Card, Button, Space, Divider, Alert } from 'antd';
import { cloneDeep, groupBy, isEmpty, omit, pick, sortBy, unique, cartesianProductArray } from '../utils/commons';
import { cloneDeep, groupBy, isEmpty, omit, pick, sortBy, unique, cartesianProductArray, fixTo2Decimals } from '@haina/utils-commons';
import { dataFieldAlias, pivotBy } from '../libs/ht';
import SearchForm from '../components/search/SearchForm';
import { Line } from '@ant-design/plots';
import DateGroupRadio from '../components/DateGroupRadio';
import { TableExportBtn } from './../components/Data';
import { TableExportBtn, VSTag } from './../components/Data';
const { Text } = Typography;
@ -19,7 +19,8 @@ const filterFields = [
{ key: 'SourceType', value: 'SourceType', label: '来源类型' },
{ key: 'guestGroupType', value: 'guestGroupType', label: '客群类别' },
{ key: 'campaign', value: 'campaign', label: 'PPC广告' },
{ key: 'WebCode', value: 'WebCode', label: '来源站点' },
{ key: 'WebCode', value: 'WebCode', label: '来源站点' },
{ key: 'COLI_LineClass', value: 'COLI_LineClass', label: '页面渠道' }
],
},
{
@ -27,7 +28,7 @@ const filterFields = [
options: [
{ key: 'productType', value: 'productType', label: '产品类型' },
{ key: 'CLI_NO', value: 'CLI_NO', label: '线路' },
{ key: 'destinationCountry', value: 'destinationCountry', label: '目的地国' },
{ key: 'destinationCountry', value: 'destinationCountry', label: '目的地国' },
{ key: 'destinations', value: 'destinations', label: '目的地城市' },
{ key: 'HotelStar', value: 'HotelStar', label: '酒店星级' },
],
@ -39,20 +40,21 @@ const filterFields = [
{ key: 'travelMotivation', value: 'travelMotivation', label: '出行目的' },
{ key: 'IsOld_txt', value: 'IsOld_txt', label: '是否老客户' },
{ key: 'isCusCommend_txt', value: 'isCusCommend_txt', label: '是否老客户推荐' },
{ key: 'hasOld_txt', value: 'hasOld_txt', label: '老客户(推荐)' },
{ key: 'hasOld_txt', value: 'hasOld_txt', label: '老客户(推荐)' },
{ key: 'RTXF_WB_range', value: 'RTXF_WB_range', label: '人天消费(外币)' },
{ key: 'customer_types', value: 'customer_types', label: '分销客户' },
],
},
{
label: '业绩',
options: [
{ key: 'operatorName', value: 'operatorName', label: '顾问' },
{ key: 'SumML_ctxt', value: 'SumML_ctxt', label: '毛利' },
{ key: 'startMonth', value: 'startMonth', label: '出行日期-月份' },
{ key: 'startYearMonth', value: 'startYearMonth', label: '出行日期-年月' },
{ key: 'applyMonth', value: 'applyMonth', label: '预订日期-月份' },
{ key: 'applyYearMonth', value: 'applyYearMonth', label: '预订日期-年月' },
{ key: 'operatorName', value: 'operatorName', label: '顾问' },
{ key: 'PPPriceRange', value: 'PPPriceRange', label: '人均天/单(外币)' },
{ key: 'SumML_ctxt', value: 'SumML_ctxt', label: '毛利' },
{ key: 'dealDays_ctxt', value: 'dealDays_ctxt', label: '成团周期(天)' },
{ key: 'applyDays_ctxt', value: 'applyDays_ctxt', label: '预订周期(天)' },
],
@ -73,7 +75,7 @@ const quickOptions = [
{ label: ' 来源站点 ', fields: [['WebCode'], []] },
{ label: '[ 产品×客群 ]', fields: [['productType', 'guestGroupType'], []] },
{ label: '[ 国籍×客群 ]', fields: [['country', 'guestGroupType'], []] },
{ label: '[ 客群×目的地国 ]', fields: [['guestGroupType', 'destinationCountry'], []] },
{ label: '[ 客群×目的地国 ]', fields: [['guestGroupType', 'destinationCountry'], []] },
// { label: '[ × ]×[ ]', fields: [['country', 'guestGroupType'], []] },
];
@ -83,22 +85,23 @@ const TdCell = (tdprops) => {
const { onMouseEnter, onMouseLeave, ...restProps } = tdprops;
return <td {...restProps} />;
};
const calcDelta = (r, key) => !isEmpty(Number(r?.vsData?.[key])) ? fixTo2Decimals((Number(r[key] || 0) - Number(r.vsData[key]))/Number(r.vsData[key]) *100) : null;
const pageSetting = {
orders: {
xField: 'applyDate',
yField: 'SumOrder',
tableColumns: [
{ key: 'SumOrder', title: '订单数', dataIndex: 'SumOrder', width: '5em' },
{ key: 'SumOrder', title: '订单数', dataIndex: 'SumOrder', width: '5em', sorter: (a,b) => a.SumOrder - b.SumOrder },
// { key: 'ConfirmOrder', title: '', dataIndex: 'ConfirmOrder', width: '5em' },
// { key: 'ConfirmPersonNum', title: '', dataIndex: 'ConfirmPersonNum', width: '5em' },
// { key: 'ConfirmRates', title: '', dataIndex: 'ConfirmRates_txt', width: '5em' },
// { key: 'SumML', title: '', dataIndex: 'SumML_txt', width: '5em' },
],
childrenColumns: [
{ key: 'ConfirmOrder', title: '成交数', dataIndex: 'ConfirmOrder', width: '5em' },
{ key: 'ConfirmOrder', title: '成交数', dataIndex: 'ConfirmOrder', width: '5em', sorter: (a,b) => a.ConfirmOrder - b.ConfirmOrder },
{ key: 'ConfirmRates', title: '成交率', dataIndex: 'ConfirmRates_txt', width: '5em' },
{ key: 'SumML', title: '毛利', dataIndex: 'SumML', width: '5em' }, // SumML_txt
{ key: 'SumML', title: '毛利', dataIndex: 'SumML', width: '5em', sorter: (a,b) => a.SumML - b.SumML }, // SumML_txt
{ key: 'SingleML', title: '单团毛利', dataIndex: 'SingleML', width: '5em' },
{ key: 'tourdays', title: '团天数', dataIndex: 'tourdays', width: '5em' },
{ key: 'confirmTourdays', title: '✅团天数', dataIndex: 'confirmTourdays', width: '5em' },
@ -112,11 +115,11 @@ const pageSetting = {
yField: 'SumML',
yFieldAlias: 'SumML_txt',
tableColumns: [
{ key: 'SumML', title: '毛利', dataIndex: 'SumML', width: '5em' }, // SumML_txt
{ key: 'SumML', title: '毛利', dataIndex: 'SumML', width: '5em', sorter: (a,b) => a.SumML - b.SumML }, // SumML_txt
],
childrenColumns: [
{ key: 'SumOrder', title: '订单数', dataIndex: 'SumOrder', width: '5em' },
{ key: 'ConfirmOrder', title: '成交数', dataIndex: 'ConfirmOrder', width: '5em' },
{ key: 'SumOrder', title: '订单数', dataIndex: 'SumOrder', width: '5em', sorter: (a,b) => a.SumOrder - b.SumOrder },
{ key: 'ConfirmOrder', title: '成交数', dataIndex: 'ConfirmOrder', width: '5em', sorter: (a,b) => a.ConfirmOrder - b.ConfirmOrder },
{ key: 'ConfirmRates', title: '成交率', dataIndex: 'ConfirmRates_txt', width: '5em' },
// { key: 'ResumeOrder', title: '', dataIndex: 'ResumeOrder', width: '5em', render: (_, r) => `${r.ResumeConfirmOrder}/${r.ResumeOrder}` },
{ key: 'SingleML', title: '单团毛利', dataIndex: 'SingleML', width: '5em' },
@ -138,19 +141,22 @@ export default observer((props) => {
const { pathname } = useLocation();
const { date_picker_store: searchFormStore, orders_store, DataPivotStore } = useContext(stores_Context);
const { formValues, formValuesToSub, siderBroken } = searchFormStore;
const { originData } = DataPivotStore.detailData[page];
const { loading, loading2 } = DataPivotStore;
const { originData, originData2 } = DataPivotStore.detailData[page];
const { xField: defaultDateType, yField: defaultValKey, yFieldAlias, tableColumns, childrenColumns, searchInitial } = pageSetting[page];
const [curXfield, setCurXfield] = useState(defaultDateType);
const [loading, setLoading] = useState(false);
// const [loading, setLoading] = useState(false);
const [rawData, setRawData] = useState(originData || []);
const [rawData2, setRawData2] = useState(originData2 || []);
const [dataBeforePick, setDataBeforePick] = useState([]);
const [dataBeforeXChange, setDataBeforeXChange] = useState([]);
const [dataSource, setDataSource] = useState([]);
// const [dataSourceMapped, setDataSourceMapped] = useState({});
const [pivotRow, setPivotRow] = useState({});
const [pivotRowDataSource, setPivotRowDataSource] = useState([]);
const [pivotRowDataSource2, setPivotRowDataSource2] = useState([]);
const [pivotDataSource, setPivotDataSource] = useState([]);
const [pivotTableDataSource, setPivotTableDataSource] = useState([]);
@ -162,7 +168,7 @@ export default observer((props) => {
const [showPassCountryTips, setShowPassCountryTips] = useState(false);
useEffect(() => {
calcDataByDate();
calcDataByDate(originData, originData2);
resetX();
resetItemFilter();
@ -184,27 +190,49 @@ export default observer((props) => {
return () => {};
}, [formValues]);
useEffect(() => {
// console.log('🔰', 1);
if (!isEmpty(originData) && !isEmpty(originData2)) {
// console.log('🔰', 2);
setRawData(originData);
setRawData2(originData2);
calcDataByDate(originData, originData2);
resetX();
resetItemFilter();
} else if (isEmpty(originData2)) {
// console.log('🔰', 3);
setRawData(originData);
calcDataByDate(originData);
resetX();
resetItemFilter();
} else {
//
}
return () => {};
}, [originData, originData2]);
const detailRefresh = async (obj) => {
setLoading(true);
// setLoading(true);
DataPivotStore.getDetailData({
...(obj || formValuesToSub),
}, page).then((resData) => {
setLoading(false);
setRawData(resData);
calcDataByDate(resData);
resetX();
resetItemFilter();
// setLoading(false);
// setRawData(originData);
// calcDataByDate(originData);
// resetX();
// resetItemFilter();
});
};
/**
* 走势的数据
* 汇总
* - 走势的数据
* - 汇总
*/
const calcDataByDate = (_rawData) => {
const calcDataByDate = (_rawData, _rawData2=[]) => {
// console.log(';;;;;', pivotDateColumns);
const { data, columnValues, summaryRows, summaryColumns, pivotKeys, summaryMix } = pivotBy(_rawData || rawData, [].concat(pivotDateColumns, [curXfield]));
const { data, columnValues, summaryRows, summaryColumns, pivotKeys, summaryMix } = pivotBy(_rawData, [].concat(pivotDateColumns, [curXfield]));
// console.log('data====', data, '\ncolumnValues', columnValues, '\nsummaryRows', summaryRows, '\nsummaryColumns', summaryColumns, '\nsummaryMix', summaryMix);
setShowPassCountryTips(pivotKeys.includes('destinationCountry'));
setDataBeforePick(data.sort(sortBy(curXfield)));
@ -233,6 +261,20 @@ export default observer((props) => {
const _c = (pivotDateColumns[1].map(eleC => unique(sortColData.map(ele => ele[eleC]))));
// console.log('_r', _r, '_c', _c);
setPivotDateColumnsValues([_r, _c, columnValues[2]]);
if (!isEmpty(_rawData2)) {
const { data: data2, columnValues: columnValues2, summaryRows: summaryRows2, summaryColumns: summaryColumns2, pivotKeys: pivotKeys2, summaryMix: summaryMix2 } = pivotBy(_rawData2, [].concat(pivotDateColumns, [curXfield]));
// console.log('data=22===', data2, '\ncolumnValues2', columnValues2, '\nsummaryRows2', summaryRows2, '\nsummaryColumns2', summaryColumns2, '\nsummaryMix2', summaryMix2);
//
const sortRowData2 = cloneDeep(summaryRows2).sort(sortBy(defaultValKey)).reverse();
const sortRowData1 = sortRowData.map(row => {
const _diff = sortRowData2.find(r2 => r2.rowLabel === row.rowLabel);
return {...row, vsData: _diff };
});
// console.log('🟢', sortRowData1);
setPivotTableDataSource(sortRowData1);
}
};
const line_config = {
@ -411,14 +453,29 @@ export default observer((props) => {
setAvgLine1(avg1);
};
const renderVS = (v, r, key) => {
const delta = calcDelta(r, key);
return <>
<Space direction={'vertical'}>
<span>
{v || 0}
{r?.vsData?.[key] ? <span type="secondary"> VS {r.vsData[key]}</span> : null}
</span>
{delta && <VSTag diffPercent={delta} />}
</Space>
</>;
};
const targetTableProps = {
loading: false,
// sticky: true,
scroll: { x: 1000, y: 400 },
pagination: false,
columns: [
...pivotDateColumns[0].map((ele) => ({ key: ele, title: filterFieldsMapped[ele].label, dataIndex: ele, width: '6em', fixed: 'left' })),
...(isEmpty(pivotDateColumns[1]) ? [].concat(cloneDeep(tableColumns), childrenColumns) : tableColumns),
{ title: '#', dataIndex: 'index', key: 'index', render: (text, record, index) => index + 1, width: '3rem', fixed: 'left' },
...pivotDateColumns[0].map((ele) => ({ key: ele, title: filterFieldsMapped[ele].label, dataIndex: ele, width: '6em', fixed: 'left', sorter: (a, b) => (a[ele] || '').localeCompare(b[ele]) })),
...(isEmpty(pivotDateColumns[1])
? [].concat(cloneDeep(tableColumns), childrenColumns).map((th) => ({ ...th, render: (v, r) => renderVS(v, r, th.dataIndex) }))
: tableColumns.map((th) => ({ ...th, render: (v, r) => renderVS(v, r, th.dataIndex) }))),
...pivotDateColumns[1].map((ele) => ({
key: ele,
title: filterFieldsMapped[ele].label,
@ -437,9 +494,11 @@ export default observer((props) => {
const detailsTableProps = {
loading: false,
// sticky: true,
size: 'small',
scroll: { x: 1000, y: 400 },
pagination: false,
columns: [
{ title: '#', dataIndex: 'index', key: 'index', render: (text, record, index) => index + 1, width: '3rem' },
{
title: '订单号',
dataIndex: 'o_id',
@ -515,7 +574,7 @@ export default observer((props) => {
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple', col: 5 },
WebCode: { show_all: false, mode: 'multiple', col: 5 },
dates: { hide_vs: true },
dates: { hide_vs: false },
},
}}
onSubmit={(_err, obj, form, str) => {
@ -716,15 +775,21 @@ export default observer((props) => {
<section>
<Spin spinning={loading}>
<h3>
{/* <h3>
透视汇总表: <span style={{ fontSize: 'smaller' }}>{dataFieldAlias[lineConfig.yField].label}</span>
</h3>
<Divider orientation="right">
<TableExportBtn
label={'pivot-' + pivotDateColumns[0].map((ele, ri) => `${filterFieldsMapped[ele].label}`).join('×')}
{...{ columns: targetTableProps.columns, dataSource: pivotTableDataSource }}
/>
</Divider>
</h3> */}
<div style={{ display: 'flex' }}>
<Divider orientation="left" style={{ flex: '1', minWidth: 'unset' }}>
透视汇总表: <span style={{ fontSize: 'smaller' }}>{dataFieldAlias[lineConfig.yField].label}</span>
</Divider>
{loading2 && <Spin size="small">正在获取对比数据</Spin>}
<Divider orientation="right" style={{ flex: '1', minWidth: 'unset' }}>
<TableExportBtn
label={'pivot-' + pivotDateColumns[0].map((ele, ri) => `${filterFieldsMapped[ele].label}`).join('×')}
{...{ columns: targetTableProps.columns, dataSource: pivotTableDataSource }}
/>
</Divider>
</div>
<Table
{...targetTableProps}
dataSource={pivotTableDataSource}
@ -738,6 +803,10 @@ export default observer((props) => {
return record.keys.includes(String(ele.key));
});
setPivotRowDataSource(thisDetail);
const thisDetail2 = rawData2.filter((ele) => {
return (record?.vsData?.keys || []).includes(String(ele.key));
});
setPivotRowDataSource2(thisDetail2);
},
};
}}
@ -746,17 +815,52 @@ export default observer((props) => {
</section>
<section>
<Spin spinning={loading}>
<h3>
{/* <h3>
点击上方表格行查看单行数据的订单明细:{' '}
<span style={{ fontSize: 'smaller' }}>{pivotDateColumns[0].map((ele, ri) => `${filterFieldsMapped[ele].label}: ${pivotRow[pivotDateColumns[0][ri]] || ''}`).join('; ')}</span>
</h3>
<Divider orientation="right">
<TableExportBtn
label={'pivot-' + pivotDateColumns[0].map((ele, ri) => `${pivotRow[pivotDateColumns[0][ri]] || ''}`).join('-')}
{...{ columns: detailsTableProps.columns, dataSource: pivotRowDataSource }}
</h3> */}
<div style={{ display: 'flex' }}>
<Divider orientation="left" style={{ flex: '1', minWidth: 'unset' }}>
点击上方表格行查看单行数据的订单明细:{' '}
<span style={{ fontSize: 'smaller' }}>{pivotDateColumns[0].map((ele, ri) => `${filterFieldsMapped[ele].label}: ${pivotRow[pivotDateColumns[0][ri]] || '(空)'}`).join('; ')}</span>
</Divider>
{/* <Divider orientation="right" style={{ flex: '1', minWidth: 'unset' }}>
<TableExportBtn
label={'pivot-' + pivotDateColumns[0].map((ele, ri) => `${pivotRow[pivotDateColumns[0][ri]] || ''}`).join('-')}
{...{ columns: detailsTableProps.columns, dataSource: pivotRowDataSource }}
/>
</Divider> */}
</div>
<Table
{...detailsTableProps}
dataSource={pivotRowDataSource}
components={{ body: { cell: TdCell } }}
title={() => (
<>
{formValuesToSub.Date1} ~ {formValuesToSub.Date2.substring(10, -1)}
<TableExportBtn
label={'pivot-' + pivotDateColumns[0].map((ele, ri) => `${pivotRow[pivotDateColumns[0][ri]] || ''}`).join('-')}
{...{ columns: detailsTableProps.columns, dataSource: pivotRowDataSource }}
/>
</>
)}
/>
{formValuesToSub.DateDiff1 && (
<Table
{...detailsTableProps}
dataSource={pivotRowDataSource2}
components={{ body: { cell: TdCell } }}
title={() => (
<>
{formValuesToSub.DateDiff1} ~ {formValuesToSub.DateDiff2.substring(10, -1)}
<TableExportBtn
label={'pivot2-' + pivotDateColumns[0].map((ele, ri) => `${pivotRow[pivotDateColumns[0][ri]] || ''}`).join('-')}
{...{ columns: detailsTableProps.columns, dataSource: pivotRowDataSource2 }}
/>
</>
)}
/>
</Divider>
<Table {...detailsTableProps} dataSource={pivotRowDataSource} components={{ body: { cell: TdCell } }} />
)}
</Spin>
</section>
</div>

@ -2,7 +2,7 @@ import { useContext, useEffect, useMemo } from 'react';
import { observer } from 'mobx-react';
import { stores_Context } from '../config';
import { Row, Col, Spin, Space, Radio, Tabs, Table } from 'antd';
import { empty } from '../utils/commons';
import { emptyValue } from '@haina/utils-commons';
import { dataFieldAlias } from '../libs/ht';
import Scatter from './../components/Scatter';
import SearchForm from './../components/search/SearchForm';
@ -20,7 +20,7 @@ export default observer((props) => {
};
useEffect(() => {
if (empty(detailData.dataSource)) {
if (emptyValue(detailData.dataSource)) {
detailRefresh();
}
}, []);

@ -4,7 +4,7 @@ import { stores_Context } from '../config';
import { Row, Col, Spin, Tabs, Table, Space, Typography, Divider } from 'antd';
import { RingProgress } from '@ant-design/plots';
import SearchForm from './../components/search/SearchForm';
import { empty } from '../utils/commons';
import { emptyValue } from '@haina/utils-commons';
import { dataFieldAlias } from '../libs/ht';
import { VSTag, TableExportBtn } from './../components/Data';
import MixYnQ from './../components/MixYnQ';
@ -51,7 +51,7 @@ export default observer(() => {
const onTabsChange = (tab) => {
DistributionStore.setCurTab(tab);
if (empty(DistributionStore[tab].dataSource)) {
if (emptyValue(DistributionStore[tab].dataSource)) {
pageRefresh();
}
};

@ -13,7 +13,7 @@ import LineWithKPI from '../components/LineWithKPI';
import DataFieldRadio from '../components/DataFieldRadio';
import { datePartOptions } from './../components/DateGroupRadio/date';
import SearchForm from './../components/search/SearchForm';
import { empty, cloneDeep, isEmpty } from './../utils/commons';
import { cloneDeep, isEmpty } from '@haina/utils-commons';
import { dataFieldAlias } from './../libs/ht';
import './home.css';
@ -203,8 +203,8 @@ export default observer(() => {
},
shows: ['DateType', 'DepartmentList', 'WebCode', 'IncludeTickets', 'years'],
fieldProps: {
DepartmentList: { show_all: true },
WebCode: { show_all: true },
DepartmentList: { show_all: false, mode: 'multiple' },
WebCode: { show_all: true, mode: 'multiple' },
years: { hide_vs: false },
},
}}

@ -5,7 +5,7 @@ import { observer } from 'mobx-react';
import 'moment/locale/zh-cn';
import SearchForm from '../components/search/SearchForm';
import { TableExportBtn } from '../components/Data';
import * as comm from '../utils/commons';
import * as comm from '@haina/utils-commons';
const HostCaseCount = () => {
const { customer_store, date_picker_store } = useContext(stores_Context);
@ -73,7 +73,7 @@ const HostCaseCount = () => {
title: '团名',
dataIndex: 'GroupBy',
key: 'GroupBy',
},
},
{
title: '顾问名',
dataIndex: 'OPI_Name',

@ -0,0 +1,132 @@
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';
import { RenderVSDataCell } from './../components/Data';
import ExportDocxBtn from '../components/TemplateLetter2025/ExportDocxBtn';
const sorter = (a, b, key) => parseInt(a[key]) - parseInt(b[key]);
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, searchValuesToSub] = useHostCaseStore(
useShallow((state) => [state.loading, state.reset, state.searchValues, state.setSearchValues, state.forExport, state.searchValuesToSub])
);
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',
sorter: (a, b) => sorter(a,b, 'group_count'),
render: (text, r) => <RenderVSDataCell data1={r.group_count} data2={r.diff?.group_count} showDiffData={searchValuesToSub.DateDiff1} />,
},
// { title: 'feedback', dataIndex: 'feedbak_group', width: '6rem' },
{ title: '东道主团数', dataIndex: 'group_count_dongdaozhu', width1: '8rem',
sorter: (a, b) => sorter(a,b, 'group_count_dongdaozhu'),
render: (text, r) => <RenderVSDataCell data1={r.group_count_dongdaozhu} data2={r.diff?.group_count_dongdaozhu} showDiffData={searchValuesToSub.DateDiff1} />,
},
{ title: '东道主个数', dataIndex: 'case_count_dongdaozhu', width1: '8rem',
sorter: (a, b) => sorter(a,b, 'case_count_dongdaozhu'),
render: (text, r) => <RenderVSDataCell data1={r.case_count_dongdaozhu} data2={r.diff?.case_count_dongdaozhu} showDiffData={searchValuesToSub.DateDiff1} />,
},
{ title: '东道主实施比例', dataIndex: 'dongdaozhu_rate',
render: (text, r) => <RenderVSDataCell data1={parseFloat(r.dongdaozhu_rate)} data2={parseFloat(r.diff?.dongdaozhu_rate)} dataSuffix='%' showDiffData={searchValuesToSub.DateDiff1} />,
},
{
title: '各类型个数',
children: [
{ title: 'Live There', dataIndex: 'live_there_count',
sorter: (a, b) => sorter(a, b, 'live_there_count'),
render: (text, r) => <RenderVSDataCell data1={r.live_there_count} data2={r.diff?.live_there_count} showDiffData={searchValuesToSub.DateDiff1} />,
},
{ title: '动机圆梦', dataIndex: 'dream_fulfillment_count',
sorter: (a, b) => sorter(a, b, 'dream_fulfillment_count'),
render: (text, r) => <RenderVSDataCell data1={r.dream_fulfillment_count} data2={r.diff?.dream_fulfillment_count} showDiffData={searchValuesToSub.DateDiff1} />,
},
{ title: '仪式感创造', dataIndex: 'ceremony_creation_count',
sorter: (a, b) => sorter(a, b, 'ceremony_creation_count'),
render: (text, r) => <RenderVSDataCell data1={r.ceremony_creation_count} data2={r.diff?.ceremony_creation_count} showDiffData={searchValuesToSub.DateDiff1} />,
},
{ title: '遗憾弥补', dataIndex: 'regret_compensation_count',
sorter: (a, b) => sorter(a, b, 'regret_compensation_count'),
render: (text, r) => <RenderVSDataCell data1={r.regret_compensation_count} data2={r.diff?.regret_compensation_count} showDiffData={searchValuesToSub.DateDiff1} />,
},
{ title: '力挽狂澜', dataIndex: 'rescue_mission_count',
sorter: (a, b) => sorter(a, b, 'rescue_mission_count'),
render: (text, r) => <RenderVSDataCell data1={r.rescue_mission_count} data2={r.diff?.rescue_mission_count} showDiffData={searchValuesToSub.DateDiff1} />,
},
],
},
];
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 (
<>
<div>
<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: ['years', 'agency','DepartmentList', 'guide_lgc'],
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple' },
dates: { hide_vs: true },
years: { hide_vs: false },
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">
<ExportDocxBtn
subject={forExport.title}
title={forExport.agencyName}
sectionsData={[
{ tableTitle: forExport.year+ '年东道主总体情况', tableColumns: summaryCols, tableData: caseSummary },
{ tableTitle: forExport.year+ '年导游实施东道主情况', tableColumns: guideSummaryCols, tableData: caseSummaryByGuide },
{ tableTitle: forExport.year+ '年精品案例', tableColumns: featuredCaseCols, tableData: caseFeatured },
]}
/>
</Divider>
<Typography.Title level={3}>{forExport.year}年东道主总体情况</Typography.Title>
<Table size='small' dataSource={caseSummary} columns={summaryCols} loading={loading} pagination={false} bordered />
<Typography.Title level={3}>{forExport.year}年导游实施东道主情况</Typography.Title>
<Table size='small' dataSource={caseSummaryByGuide} columns={guideSummaryCols} loading={loading} pagination={false} bordered rowKey={'TGI_SN'} />
<Typography.Title level={3}>{forExport.year}年精品案例</Typography.Title>
<Table size='small' dataSource={caseFeatured} columns={featuredCaseCols} loading={loading} pagination={false} bordered rowKey={'case_id'} />
</div>
</div>
</>
);
};
export default observer(HostCaseReport);

@ -2,7 +2,7 @@ import { useContext, useEffect, useState } from 'react';
import { stores_Context } from './../config';
import { observer } from 'mobx-react';
import { Row, Col, Tabs, Spin } from 'antd';
import { flush, isEmpty, objectMapper, pick } from './../utils/commons';
import { flush, isEmpty, objectMapper, pick } from '@haina/utils-commons';
import { KPIObjects } from './../libs/ht';
import ObjectPanel from '../components/kpi/ObjectPanel';
import OverviewPanel from './../components/kpi/OverviewPanel';

@ -5,7 +5,7 @@ import { Table, Row, Col, Divider, Tooltip } from 'antd';
import { InfoCircleOutlined } from '@ant-design/icons';
import SearchForm from '../components/search/SearchForm';
import { TableExportBtn } from './../components/Data';
import { fixTo2Decimals } from './../utils/commons';
import { fixTo2Decimals } from '@haina/utils-commons';
const numberConvert10K = (number, scale = 10) => {
return fixTo2Decimals(number / (1000 * scale)) + '';

@ -5,7 +5,7 @@ import { Table, Row, Col, Divider, Tooltip } from 'antd';
import { InfoCircleOutlined } from '@ant-design/icons';
import SearchForm from '../components/search/SearchForm';
import { TableExportBtn } from './../components/Data';
import { fixTo2Decimals } from './../utils/commons';
import { fixTo2Decimals } from '@haina/utils-commons';
const numberConvert10K = (number, scale = 10) => {
return fixTo2Decimals(number / (1000 * scale)) + '';

@ -6,12 +6,12 @@ import { Line, Pie, } from "@ant-design/charts";
import { observer } from "mobx-react";
import * as config from "../config";
import { NavLink } from "react-router-dom";
import * as comm from "../utils/commons";
import { utils, writeFileXLSX } from "xlsx";
import * as comm from '@haina/utils-commons';
import DateGroupRadio from '../components/DateGroupRadio';
import SearchForm from './../components/search/SearchForm';
import { TableExportBtn } from './../components/Data';
import { TableExportBtn, RenderVSDataCell } from './../components/Data';
import ParetoChart from "../components/Pareto";
import { toJS } from 'mobx';
class Orders extends Component {
static contextType = stores_Context;
@ -23,229 +23,183 @@ class Orders extends Component {
format_data(data) {
const { date_picker_store, orders_store } = this.context;
const result = { dataSource: [], columns: [] };
if (!comm.empty(data)) {
const ordercountTotal1 = data.ordercountTotal1;
const ordercountTotal2 = data.ordercountTotal2;
if (date_picker_store.start_date_cp && date_picker_store.end_date_cp) {
//
result.columns = [
{
title: '#',
fixed: 'left',
children: [
{
title: (
<span>
<div>
{date_picker_store.start_date.format(config.DATE_FORMAT)}~{date_picker_store.end_date.format(config.DATE_FORMAT)}
</div>
<div>
{date_picker_store.start_date_cp.format(config.DATE_FORMAT)}~{date_picker_store.end_date_cp.format(config.DATE_FORMAT)}
</div>
</span>
),
titleX: `${date_picker_store.start_date.format(config.DATE_FORMAT)}~${date_picker_store.end_date.format(config.DATE_FORMAT)} vs ${date_picker_store.start_date_cp.format(
config.DATE_FORMAT
)}~${date_picker_store.end_date_cp.format(config.DATE_FORMAT)}`,
dataIndex: 'OrderType',
fixed: 'left',
render: (text, record) => <NavLink to={`/orders_sub/${orders_store.active_tab_key}/${record.OrderTypeSN}/${encodeURIComponent(record.OrderType)}`}>{text}</NavLink>,
},
],
},
{
title: '数量',
children: [
{
title: comm.show_vs_tag(ordercountTotal1.OrderCount_vs, ordercountTotal1.OrderCount_diff, ordercountTotal1.OrderCount, ordercountTotal2.OrderCount),
titleX: [ordercountTotal1.OrderCount, ordercountTotal2.OrderCount].join(' vs '),
dataIndex: 'OrderCount',
},
],
},
{
title: '成交数',
children: [
{
title: comm.show_vs_tag(ordercountTotal1.CJCount_vs, ordercountTotal1.CJCount_diff, ordercountTotal1.CJCount, ordercountTotal2.CJCount),
titleX: [ordercountTotal1.CJCount, ordercountTotal2.CJCount].join(' vs '),
dataIndex: 'CJCount',
},
],
},
{
title: '成交人数',
children: [
{
title: comm.show_vs_tag(ordercountTotal1.CJPersonNum_vs, ordercountTotal1.CJPersonNum_diff, ordercountTotal1.CJPersonNum, ordercountTotal2.CJPersonNum),
titleX: [ordercountTotal1.CJPersonNum, ordercountTotal2.CJPersonNum].join(' vs '),
dataIndex: 'CJPersonNum',
},
],
},
{
title: '成交率',
children: [
{
title: comm.show_vs_tag(ordercountTotal1.CJrate_vs, ordercountTotal1.CJrate_diff, ordercountTotal1.CJrate, ordercountTotal2.CJrate),
titleX: [ordercountTotal1.CJrate, ordercountTotal2.CJrate].join(' vs '),
dataIndex: 'CJrate',
},
],
},
{
title: '成交毛利(预计)',
children: [
{
title: comm.show_vs_tag(ordercountTotal1.YJLY_vs, ordercountTotal1.YJLY_diff, ordercountTotal1.YJLY, ordercountTotal2.YJLY),
titleX: [ordercountTotal1.YJLY, ordercountTotal2.YJLY].join(' vs '),
dataIndex: 'YJLY',
},
],
},
if (!comm.isEmpty(data)) {
const rows1Map = data.ordercount1.reduce((a, row1) => ({ ...a, [row1.OrderTypeSN]: row1 }), {});
const rows2Map = data.ordercount2.reduce((a, row2) => ({ ...a, [row2.OrderTypeSN]: row2 }), {});
// Diff: elements in rows2 but not in rows1
const diffKey = [...new Set(Object.keys(rows2Map).filter((x) => !new Set(Object.keys(rows1Map)).has(x)))];
const withDiff1 = (structuredClone(toJS(data.ordercount1)) || []).map((r1) => ({ ...r1, diff: rows2Map[r1.OrderTypeSN] }));
withDiff1.push(
...diffKey.map((key) => ({
diff: rows2Map[key],
EOI_ObjSN: rows2Map[key].EOI_ObjSN,
OrderType: rows2Map[key].OrderType,
CJrate: 0,
CJCount_vs: -100,
CJPersonNum_vs: -100,
CJrate_vs: -100,
YJLY_vs: -100,
OrderCount_vs: -100,
CJCount_diff: -rows2Map[key].CJCount,
CJPersonNum_diff: -rows2Map[key].CJPersonNum,
CJrate_diff: -rows2Map[key].CJrate,
YJLY_diff: -rows2Map[key].YJLY,
OrderCount_diff: -rows2Map[key].OrderCount,
Ordervalue: 0,
Ordervalue_diff: -rows2Map[key].Ordervalue,
Ordervalue_vs: -100,
}))
);
const showDiff = date_picker_store.start_date_cp && date_picker_store.end_date_cp;
result.dataSource = withDiff1;
{
title: '单个订单价值',
children: [
{
title: comm.show_vs_tag(ordercountTotal1.Ordervalue_vs, ordercountTotal1.Ordervalue_diff, ordercountTotal1.Ordervalue, ordercountTotal2.Ordervalue),
titleX: [ordercountTotal1.Ordervalue, ordercountTotal2.Ordervalue].join(' vs '),
dataIndex: 'Ordervalue',
},
],
},
];
// 1.OrderType 2.OrderType
let has_same_type = false; // 12
for (const item of data.ordercount1) {
has_same_type = false;
// 12
for (const item2 of data.ordercount2) {
if (item.OrderType === item2.OrderType) {
has_same_type = true;
result.dataSource.push({
key: item.key,
OrderType: item.OrderType,
OrderTypeSN: item.OrderTypeSN,
OrderCount: comm.show_vs_tag(item.OrderCount_vs, item.OrderCount_diff, item.OrderCount, item2.OrderCount),
OrderCount_X: ([item.OrderCount, item2.OrderCount].join(' vs ')),
CJCount: comm.show_vs_tag(item.CJCount_vs, item.CJCount_diff, item.CJCount, item2.CJCount),
CJCount_X: ([item.CJCount, item2.CJCount].join(' vs ')),
CJPersonNum: comm.show_vs_tag(item.CJPersonNum_vs, item.CJPersonNum_diff, item.CJPersonNum, item2.CJPersonNum),
CJPersonNum_X: ([item.CJPersonNum, item2.CJPersonNum].join(' vs ')),
CJrate: comm.show_vs_tag(item.CJrate_vs, item.CJrate_diff, item.CJrate, item2.CJrate),
CJrate_X: ([item.CJrate, item2.CJrate].join(' vs ')),
YJLY: comm.show_vs_tag(item.YJLY_vs, item.YJLY_diff, item.YJLY, item2.YJLY),
YJLY_X: ([item.YJLY, item2.YJLY].join(' vs ')),
Ordervalue: comm.show_vs_tag(item.Ordervalue_vs, item.Ordervalue_diff, item.Ordervalue, item2.Ordervalue),
Ordervalue_X: ([item.Ordervalue, item2.Ordervalue].join(' vs ')),
});
}
}
// 12
if (has_same_type === false) {
result.dataSource.push({
key: item.key,
OrderType: item.OrderType,
OrderTypeSN: item.OrderTypeSN,
OrderCount: comm.show_vs_tag(comm.formatPercent(item.OrderCount), item.OrderCount, item.OrderCount, 0),
OrderCount_X: ([item.OrderCount, 0].join(' vs ')),
CJCount: comm.show_vs_tag(comm.formatPercent(item.CJCount), item.CJCount, item.CJCount, 0),
CJCount_X: ([item.CJCount, 0].join(' vs ')),
CJPersonNum: comm.show_vs_tag(comm.formatPercent(item.CJPersonNum), item.CJPersonNum, item.CJPersonNum, 0),
CJPersonNum_X: ([item.CJPersonNum, 0].join(' vs ')),
CJrate: comm.show_vs_tag(item.CJrate, item.CJrate, item.CJrate, 0),
CJrate_X: ([item.CJrate, 0].join(' vs ')),
YJLY: comm.show_vs_tag(comm.formatPercent(item.YJLY), item.YJLY, item.YJLY, 0),
YJLY_X: ([item.YJLY, 0].join(' vs ')),
Ordervalue: comm.show_vs_tag(comm.formatPercent(item.Ordervalue), item.Ordervalue, item.Ordervalue, 0),
Ordervalue_X: ([item.Ordervalue, 0].join(' vs ')),
});
}
}
// 21
for (const item2 of data.ordercount2) {
has_same_type = false;
for (const item of data.ordercount1) {
if (item.OrderType === item2.OrderType) {
has_same_type = true;
}
}
if (has_same_type === false) {
result.dataSource.push({
key: item2.key,
OrderType: item2.OrderType,
OrderTypeSN: item2.OrderTypeSN,
OrderCount: comm.show_vs_tag(comm.formatPercent(-item2.OrderCount), -item2.OrderCount, 0, item2.OrderCount),
OrderCount_X: ([ 0, item2.OrderCount].join(' vs ')),
CJCount: comm.show_vs_tag(comm.formatPercent(-item2.CJCount), -item2.CJCount, 0, item2.CJCount),
CJCount_X: ([ 0, item2.CJCount].join(' vs ')),
CJPersonNum: comm.show_vs_tag(comm.formatPercent(-item2.CJPersonNum), -item2.CJPersonNum, 0, item2.CJPersonNum),
CJPersonNum_X: ([0, item2.CJPersonNum].join(' vs ')),
CJrate: comm.show_vs_tag(-item2.CJrate, -item2.CJrate, 0, item2.CJrate),
CJrate_X: ([ 0, item2.CJrate].join(' vs ')),
YJLY: comm.show_vs_tag(comm.formatPercent(-item2.YJLY), -item2.YJLY, 0, item2.YJLY),
YJLY_X: ([0, item2.YJLY].join(' vs ')),
Ordervalue: comm.show_vs_tag(comm.formatPercent(-item2.Ordervalue), -item2.Ordervalue, 0, item2.Ordervalue),
Ordervalue_X: ([ 0, item2.Ordervalue].join(' vs ')),
});
}
}
} else {
result.columns = [
{
title: "#",
fixed: 'left',
children: [
{
title: (
<span>
<div>
{date_picker_store.start_date.format(config.DATE_FORMAT)}~{date_picker_store.end_date.format(config.DATE_FORMAT)}
</div>
</span>
),
titleX: `${date_picker_store.start_date.format(config.DATE_FORMAT)}~${date_picker_store.end_date.format(config.DATE_FORMAT)}`,
fixed: 'left',
dataIndex: "OrderType",
render: (text, record) => <NavLink to={`/orders_sub/${orders_store.active_tab_key}/${record.OrderTypeSN}/${encodeURIComponent(record.OrderType)}`}>{text}</NavLink>,
},
],
},
{
title: "数量",
children: [{ title: ordercountTotal1.OrderCount, dataIndex: "OrderCount" }],
sorter: (a, b) => b.OrderCount - a.OrderCount,
},
{
title: "成交数",
children: [{ title: ordercountTotal1.CJCount, dataIndex: "CJCount" }],
sorter: (a, b) => b.CJCount - a.CJCount,
},
{
title: "成交人数",
children: [{ title: ordercountTotal1.CJPersonNum, dataIndex: "CJPersonNum" }],
sorter: (a, b) => b.CJPersonNum - a.CJPersonNum,
},
{
title: "成交率",
children: [{ title: ordercountTotal1.CJrate, dataIndex: "CJrate" }],
sorter: (a, b) => parseInt(b.CJrate) - parseInt(a.CJrate),
},
{
title: "成交毛利(预计)",
children: [{ title: ordercountTotal1.YJLY, dataIndex: "YJLY" }],
sorter: (a, b) => parseFloat(b.YJLY.replace(/,/g, "")) - parseFloat(a.YJLY.replace(/,/g, "")),
},
const ordercountTotal1 = data.ordercountTotal1;
const ordercountTotal2 = data.ordercountTotal2;
result.columns = [
{
title: '#',
fixed: 'left',
children: [
{
title: (
<span>
<div>
{date_picker_store.start_date.format(config.DATE_FORMAT)}~{date_picker_store.end_date.format(config.DATE_FORMAT)}
</div>
{showDiff ? <div>
{date_picker_store.start_date_cp.format(config.DATE_FORMAT)}~{date_picker_store.end_date_cp.format(config.DATE_FORMAT)}
</div> : null}
</span>
),
titleX: showDiff ? `${
date_picker_store.start_date.format(config.DATE_FORMAT)}~${date_picker_store.end_date.format(config.DATE_FORMAT)
} vs ${
date_picker_store.start_date_cp.format(config.DATE_FORMAT)}~${date_picker_store.end_date_cp.format(config.DATE_FORMAT)
}` : `${date_picker_store.start_date.format(config.DATE_FORMAT)}~${date_picker_store.end_date.format(config.DATE_FORMAT)}`,
dataIndex: 'OrderType',
fixed: 'left',
render: (text, record) => <NavLink to={`/orders_sub/${orders_store.active_tab_key}/${record.OrderTypeSN}/${encodeURIComponent(record.OrderType)}`}>{text}</NavLink>,
},
],
},
{
title: '数量',
children: [
{
title: (
<RenderVSDataCell
showDiffData={showDiff}
data1={ordercountTotal1?.OrderCount}
data2={ordercountTotal2?.OrderCount}
diffPercent={ordercountTotal1?.OrderCount_vs}
diffData={ordercountTotal1?.OrderCount_diff}
/>
),
titleX: [ordercountTotal1.OrderCount, ordercountTotal2.OrderCount].filter(s => s).filter(s => s).join(' vs '),
dataIndex: 'OrderCount',
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.OrderCount} diffPercent={r.OrderCount_vs} diffData={r.OrderCount_diff} />,
},
],
},
{
title: '成交数',
children: [
{
title: (
<RenderVSDataCell
showDiffData={showDiff}
data1={ordercountTotal1?.CJCount}
data2={ordercountTotal2?.CJCount}
diffPercent={ordercountTotal1?.CJCount_vs}
diffData={ordercountTotal1?.CJCount_diff}
/>
),
titleX: [ordercountTotal1.CJCount, ordercountTotal2.CJCount].filter(s => s).join(' vs '),
dataIndex: 'CJCount',
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.CJCount} diffPercent={r.CJCount_vs} diffData={r.CJCount_diff} />,
},
],
},
{
title: '成交人数',
children: [
{
title: (
<RenderVSDataCell
showDiffData={showDiff}
data1={ordercountTotal1?.CJPersonNum}
data2={ordercountTotal2?.CJPersonNum}
diffPercent={ordercountTotal1?.CJPersonNum_vs}
diffData={ordercountTotal1?.CJPersonNum_diff}
/>
),
titleX: [ordercountTotal1.CJPersonNum, ordercountTotal2.CJPersonNum].filter(s => s).join(' vs '),
dataIndex: 'CJPersonNum',
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.CJPersonNum} diffPercent={r.CJPersonNum_vs} diffData={r.CJPersonNum_diff} />,
},
],
},
{
title: '成交率',
children: [
{
title: (
<RenderVSDataCell
showDiffData={showDiff}
data1={ordercountTotal1?.CJrate}
data2={ordercountTotal2?.CJrate}
diffPercent={ordercountTotal1?.CJrate_vs}
diffData={ordercountTotal1?.CJrate_diff}
/>
),
titleX: [ordercountTotal1.CJrate, ordercountTotal2.CJrate].filter(s => s).join(' vs '),
dataIndex: 'CJrate',
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.CJrate} diffPercent={r.CJrate_vs} diffData={r.CJrate_diff} />,
},
],
},
{
title: '成交毛利(预计)',
children: [
{
title: (
<RenderVSDataCell
showDiffData={showDiff}
data1={ordercountTotal1?.YJLY}
data2={ordercountTotal2?.YJLY}
diffPercent={ordercountTotal1?.YJLY_vs}
diffData={ordercountTotal1?.YJLY_diff}
/>
),
titleX: [ordercountTotal1.YJLY, ordercountTotal2.YJLY].filter(s => s).join(' vs '),
dataIndex: 'YJLY',
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.YJLY} diffPercent={r.YJLY_vs} diffData={r.YJLY_diff} />,
},
],
},
{
title: '单个订单价值',
children: [
{
title: (
<RenderVSDataCell
showDiffData={showDiff}
data1={ordercountTotal1?.Ordervalue}
data2={ordercountTotal2?.Ordervalue}
diffPercent={ordercountTotal1?.Ordervalue_vs}
diffData={ordercountTotal1?.Ordervalue_diff}
/>
),
titleX: [ordercountTotal1.Ordervalue, ordercountTotal2.Ordervalue].filter(s => s).join(' vs '),
dataIndex: 'Ordervalue',
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.Ordervalue} diffPercent={r.Ordervalue_vs} diffData={r.Ordervalue_diff} />,
},
],
},
];
return result;
{
title: "单个订单价值",
children: [{ title: ordercountTotal1.Ordervalue, dataIndex: "Ordervalue" }],
sorter: (a, b) => parseFloat(b.Ordervalue.replace(/,/g, "")) - parseFloat(a.Ordervalue.replace(/,/g, "")),
},
];
result.dataSource = data.ordercount1;
}
}
return result;
}
@ -255,10 +209,10 @@ class Orders extends Component {
const table_data = orders_store.orderCountData_Form ? this.format_data(orders_store.orderCountData_Form) : [];
const data_source = orders_store.orderCountData ? orders_store.orderCountData : [];
const avg_line_y = Math.round(orders_store.avgLine1);
const pie_data = comm.empty(orders_store.orderCountData_Form)
const pie_data = comm.isEmpty(orders_store.orderCountData_Form)
? []
: orders_store.orderCountData_Form.ordercount1.map((ele) => ({ ...ele, YJLYx: comm.price_to_number(ele.YJLY) })); //
const pie_data2 = comm.empty(orders_store.orderCountData_Form)
const pie_data2 = comm.isEmpty(orders_store.orderCountData_Form)
? []
: orders_store.orderCountData_Form.ordercount2.map((ele) => ({ ...ele, YJLYx: comm.price_to_number(ele.YJLY) }));
@ -350,7 +304,7 @@ class Orders extends Component {
dataSource: table_data.dataSource,
columns: table_data.columns,
size: 'small',
// pagination: false,
pagination: { pageSize: 30 },
scroll: { x: (100*(table_data.columns.length)) },
loading: orders_store.loading,
};

@ -5,7 +5,7 @@ import { stores_Context } from "../config";
import { Line } from "@ant-design/charts";
import { observer } from "mobx-react";
import { NavLink, useParams } from "react-router-dom";
import * as comm from "../utils/commons";
import * as comm from '@haina/utils-commons';
import * as config from "../config";
import { utils, writeFileXLSX } from "xlsx";
import DateGroupRadio from '../components/DateGroupRadio';
@ -75,12 +75,12 @@ const Orders_sub = () => {
},
},
},
smooth: true,
// smooth: true,
};
const format_data = data => {
const result = { dataSource: [], columns: [] };
if (!comm.empty(data)) {
if (!comm.emptyValue(data)) {
result.columns = [
{
title: "页面",
@ -108,7 +108,7 @@ const Orders_sub = () => {
const format_data_detail = data => {
const result = { dataSource: [], columns: [] };
if (!comm.empty(data)) {
if (!comm.emptyValue(data)) {
result.columns = [
{
title: "订单号",

@ -4,7 +4,7 @@ import { ContainerOutlined, UserSwitchOutlined } from '@ant-design/icons';
import { stores_Context } from '../config';
import { Column, Pie } from '@ant-design/charts';
import { observer } from 'mobx-react';
import * as comm from '../utils/commons';
import * as comm from '@haina/utils-commons';
import { utils, writeFileXLSX } from 'xlsx';
import SearchForm from './../components/search/SearchForm';
import { TableExportBtn } from './../components/Data';
@ -13,7 +13,7 @@ const Sale = () => {
const { sale_store, date_picker_store } = useContext(stores_Context);
//const ml_data = sale_store.ml_data; //
const type_data = comm.empty(sale_store.type_data) ? { dataSource: [], columns: [] } : sale_store.type_data; //
const type_data = comm.emptyValue(sale_store.type_data) ? { dataSource: [], columns: [] } : sale_store.type_data; //
const column_config_create = (tab_name) => {
let average_value = 0; //线

@ -2,7 +2,7 @@ import React, { useContext, useState } from 'react';
import { Row, Col, Table, Select, Space, Typography, Progress, Spin, Divider, Button, Switch } from 'antd';
import { stores_Context } from '../config';
import { observer } from 'mobx-react';
import * as comm from '../utils/commons';
import * as comm from '@haina/utils-commons';
import SearchForm from './../components/search/SearchForm';
import { dataFieldAlias, overviewGroup } from '../libs/ht';
import Donut from './../components/Donut';

@ -6,7 +6,7 @@ import { Column, Pie } from "@ant-design/charts";
import { observer } from "mobx-react";
import DatePickerCharts from "../components/search/DatePickerCharts";
import { NavLink, useParams } from "react-router-dom";
import * as comm from "../utils/commons";
import * as comm from '@haina/utils-commons';
import * as config from "../config";
import GroupSelect from "../components/search/GroupSelect";
@ -18,7 +18,7 @@ const Sale_sub = () => {
sale_store.get_department_order_ml_by_type_sub(date_picker_store, type_sub);
}, []);
const type_data = comm.empty(sale_store.type_data_sub) ? { dataSource: [], columns: [] } : sale_store.type_data_sub;
const type_data = comm.emptyValue(sale_store.type_data_sub) ? { dataSource: [], columns: [] } : sale_store.type_data_sub;
return (
<div>
@ -29,7 +29,7 @@ const Sale_sub = () => {
</Row>
<Spin size="large" spinning={sale_store.spinning}>
<Row gutter={{ sm: 16, lg: 32 }}>
{!comm.empty(type_data.dataSource) &&
{!comm.emptyValue(type_data.dataSource) &&
type_data.dataSource.map(item => {
return (
<Col key={"col-" + item.subType_name} md={24} xxl={12}>

@ -4,7 +4,7 @@ import { stores_Context } from '../config';
import { Spin, Table, Row, Col, Tabs, Switch } from 'antd';
import SearchForm from './../components/search/SearchForm';
import { TableExportBtn } from './../components/Data';
import { empty } from '../utils/commons';
import { emptyValue } from '@haina/utils-commons';
import './kpi.css';
const apartOptions = [
@ -75,7 +75,7 @@ export default observer((props) => {
activeKey={curTab}
onChange={(v) => {
financialStore.setCurTab(v);
if (empty(servicePersonNum[v].dataSource)) {
if (emptyValue(servicePersonNum[v].dataSource)) {
pageRefresh();
}
}}

@ -3,10 +3,11 @@ import { Row, Col, Tabs, Table, Divider, Spin, Checkbox, Space } from 'antd';
import { ContainerOutlined, BlockOutlined, SmileOutlined, MobileOutlined, CustomerServiceOutlined, IeOutlined } from '@ant-design/icons';
import { Line, Pie } from '@ant-design/charts';
import { NavLink } from 'react-router-dom';
import * as comm from '../../utils/commons';
import * as comm from '@haina/utils-commons';
import DateGroupRadio from '../../components/DateGroupRadio';
import SearchForm from '../../components/search/SearchForm';
import { TableExportBtn } from '../../components/Data';
import { RenderVSDataCell } from './../../components/Data';
import { observer } from 'mobx-react';
import { toJS } from 'mobx';
@ -142,12 +143,18 @@ const BizOrder = observer(() => {
title: '数量',
children: [
{
title: !showDiff
? result.ordercountTotal1?.OrderCount
: comm.show_vs_tag(result.ordercountTotal1?.OrderCount_vs, result.ordercountTotal1?.OrderCount_diff, result.ordercountTotal1?.OrderCount, result.ordercountTotal2?.OrderCount),
title: (
<RenderVSDataCell
showDiffData={showDiff}
data1={result.ordercountTotal1?.OrderCount}
data2={result.ordercountTotal2?.OrderCount}
diffPercent={result.ordercountTotal1?.OrderCount_vs}
diffData={result.ordercountTotal1?.OrderCount_diff}
/>
),
titleX: [result.ordercountTotal1?.OrderCount, result.ordercountTotal2?.OrderCount].join(' vs '),
dataIndex: 'OrderCount',
render: (text, r) => (!showDiff ? text : comm.show_vs_tag(r.OrderCount_vs, r.OrderCount_diff, r.OrderCount, r.diff?.OrderCount)),
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.OrderCount} diffPercent={r.OrderCount_vs} diffData={r.OrderCount_diff} />,
},
],
},
@ -155,12 +162,18 @@ const BizOrder = observer(() => {
title: '成交数',
children: [
{
title: !showDiff
? result.ordercountTotal1?.CJCount
: comm.show_vs_tag(result.ordercountTotal1?.CJCount_vs, result.ordercountTotal1?.CJCount_diff, result.ordercountTotal1?.CJCount, result.ordercountTotal2?.CJCount),
title: (
<RenderVSDataCell
showDiffData={showDiff}
data1={result.ordercountTotal1?.CJCount}
data2={result.ordercountTotal2?.CJCount}
diffPercent={result.ordercountTotal1?.CJCount_vs}
diffData={result.ordercountTotal1?.CJCount_diff}
/>
),
titleX: [result.ordercountTotal1?.CJCount, result.ordercountTotal2?.CJCount].join(' vs '),
dataIndex: 'CJCount',
render: (text, r) => (!showDiff ? text : comm.show_vs_tag(r.CJCount_vs, r.CJCount_diff, r.CJCount, r.diff?.CJCount)),
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.CJCount} diffPercent={r.CJCount_vs} diffData={r.CJCount_diff} />,
},
],
},
@ -168,12 +181,18 @@ const BizOrder = observer(() => {
title: '成交人数',
children: [
{
title: !showDiff
? result.ordercountTotal1?.CJPersonNum
: comm.show_vs_tag(result.ordercountTotal1?.CJPersonNum_vs, result.ordercountTotal1?.CJPersonNum_diff, result.ordercountTotal1?.CJPersonNum, result.ordercountTotal2?.CJPersonNum),
title: (
<RenderVSDataCell
showDiffData={showDiff}
data1={result.ordercountTotal1?.CJPersonNum}
data2={result.ordercountTotal2?.CJPersonNum}
diffPercent={result.ordercountTotal1?.CJPersonNum_vs}
diffData={result.ordercountTotal1?.CJPersonNum_diff}
/>
),
titleX: [result.ordercountTotal1?.CJPersonNum, result.ordercountTotal2?.CJPersonNum].join(' vs '),
dataIndex: 'CJPersonNum',
render: (text, r) => (!showDiff ? text : comm.show_vs_tag(r.CJPersonNum_vs, r.CJPersonNum_diff, r.CJPersonNum, r.diff?.CJPersonNum)),
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.CJPersonNum} diffPercent={r.CJPersonNum_vs} diffData={r.CJPersonNum_diff} />,
},
],
},
@ -181,12 +200,18 @@ const BizOrder = observer(() => {
title: '成交率',
children: [
{
title: !showDiff
? result.ordercountTotal1?.CJrate
: comm.show_vs_tag(result.ordercountTotal1?.CJrate_vs, result.ordercountTotal1?.CJrate_diff, result.ordercountTotal1?.CJrate, result.ordercountTotal2?.CJrate),
title: (
<RenderVSDataCell
showDiffData={showDiff}
data1={result.ordercountTotal1?.CJrate}
data2={result.ordercountTotal2?.CJrate}
diffPercent={result.ordercountTotal1?.CJrate_vs}
diffData={result.ordercountTotal1?.CJrate_diff}
/>
),
titleX: [result.ordercountTotal1?.CJrate, result.ordercountTotal2?.CJrate].join(' vs '),
dataIndex: 'CJrate',
render: (text, r) => (!showDiff ? text : comm.show_vs_tag(r.CJrate_vs, r.CJrate_diff, r.CJrate, r.diff?.CJrate)),
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.CJrate} diffPercent={r.CJrate_vs} diffData={r.CJrate_diff} />,
},
],
},
@ -194,12 +219,18 @@ const BizOrder = observer(() => {
title: '成交毛利(预计)',
children: [
{
title: !showDiff
? result.ordercountTotal1?.YJLY
: comm.show_vs_tag(result.ordercountTotal1?.YJLY_vs, result.ordercountTotal1?.YJLY_diff, result.ordercountTotal1?.YJLY, result.ordercountTotal2?.YJLY),
title: (
<RenderVSDataCell
showDiffData={showDiff}
data1={result.ordercountTotal1?.YJLY}
data2={result.ordercountTotal2?.YJLY}
diffPercent={result.ordercountTotal1?.YJLY_vs}
diffData={result.ordercountTotal1?.YJLY_diff}
/>
),
titleX: [result.ordercountTotal1?.YJLY, result.ordercountTotal2?.YJLY].join(' vs '),
dataIndex: 'YJLY',
render: (text, r) => (!showDiff ? text : comm.show_vs_tag(r.YJLY_vs, r.YJLY_diff, r.YJLY, r.diff?.YJLY)),
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.YJLY} diffPercent={r.YJLY_vs} diffData={r.YJLY_diff} />,
},
],
},
@ -208,12 +239,18 @@ const BizOrder = observer(() => {
title: '单个订单价值',
children: [
{
title: !showDiff
? result.ordercountTotal1?.Ordervalue
: comm.show_vs_tag(result.ordercountTotal1?.Ordervalue_vs, result.ordercountTotal1?.Ordervalue_diff, result.ordercountTotal1?.Ordervalue, result.ordercountTotal2?.Ordervalue),
title: (
<RenderVSDataCell
showDiffData={showDiff}
data1={result.ordercountTotal1?.Ordervalue}
data2={result.ordercountTotal2?.Ordervalue}
diffPercent={result.ordercountTotal1?.Ordervalue_vs}
diffData={result.ordercountTotal1?.Ordervalue_diff}
/>
),
titleX: [result.ordercountTotal1?.Ordervalue, result.ordercountTotal2?.Ordervalue].join(' vs '),
dataIndex: 'Ordervalue',
render: (text, r) => (!showDiff ? text : comm.show_vs_tag(r.Ordervalue_vs, r.Ordervalue_diff, r.Ordervalue, r.diff?.Ordervalue)),
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.Ordervalue} diffPercent={r.Ordervalue_vs} diffData={r.Ordervalue_diff} />,
},
],
},

@ -3,7 +3,7 @@ import { Row, Col, Tabs, Table, Divider, Spin, Space } from 'antd';
import { ContainerOutlined, BlockOutlined, SmileOutlined, MobileOutlined } from '@ant-design/icons';
import { Line } from '@ant-design/charts';
import { NavLink, useParams } from 'react-router-dom';
import * as comm from '../../utils/commons';
import { getWeek } from '@haina/utils-commons';
import DateGroupRadio from '../../components/DateGroupRadio';
import SearchForm from '../../components/search/SearchForm';
import { TableExportBtn } from '../../components/Data';
@ -187,7 +187,7 @@ const BizOrderSub = observer(({ ...props }) => {
let ret = title;
switch (activeDateGroupRadioSub) {
case 'day':
ret = `${title} ${comm.getWeek(datum.xField)}`; //
ret = `${title} ${getWeek(datum.xField)}`; //
break;
default:

@ -2,8 +2,9 @@ import { useContext } from 'react';
import { Row, Col, Table, Spin, Space, Divider } from 'antd';
import { Funnel, Pie, Sunburst } from '@ant-design/charts';
import * as comm from '../../../utils/commons';
import { isEmpty } from '@haina/utils-commons';
import SearchForm from '../../../components/search/SearchForm';
import { RenderVSDataCell } from './../../../components/Data';
import { observer } from 'mobx-react';
import { toJS } from 'mobx';
@ -34,7 +35,7 @@ const buildFunnelData = (data1, data2) => {
{ stage: '火车票Upsell', number: (_getTrainUpsell(data1?.data)?.OrderCount || 0) + 0, dateRange: data1?.dateRangeStr },
{ stage: 'Upsell成行', number: _getTrainUpsell(data1?.data)?.CJCount, dateRange: data1?.dateRangeStr },
];
const data02 = !comm.isEmpty(data2)
const data02 = !isEmpty(data2)
? [
{ stage: '火车票服务', number: _getTrainService(data2?.data)?.OrderCount || 0, dateRange: data2?.dateRangeStr },
{ stage: '成行出票', number: _getTrainService(data2?.data)?.CJCount || 0, dateRange: data2?.dateRangeStr },
@ -47,13 +48,13 @@ const buildFunnelData = (data1, data2) => {
};
const buildPieData = (data1, data2) => {
const data01 = !comm.isEmpty(data1)
const data01 = !isEmpty(data1)
? [
{ stage: '火车票出票', number: Number((data1?.data?.[0]?.YJLY || '0').replaceAll(',', '')), dateRange: data1?.dateRangeStr },
{ stage: 'Upsell成行', number: Number((data1?.data?.[1]?.YJLY || '0').replaceAll(',', '')), dateRange: data1?.dateRangeStr },
]
: [];
const data02 = !comm.isEmpty(data2)
const data02 = !isEmpty(data2)
? [
{ stage: '火车票出票', number: Number((data2?.data?.[0]?.YJLY || '0').replaceAll(',', '')), dateRange: data2?.dateRangeStr },
{ stage: 'Upsell成行', number: Number((data2?.data?.[1]?.YJLY || '0').replaceAll(',', '')), dateRange: data2?.dateRangeStr },
@ -70,7 +71,7 @@ const TrainsUpsell = observer(({ ...props }) => {
const [trainsOrdersSummary, compareData] = useTrainsStore((state) => state.trainsOrdersSummary);
const trainsUpsellOrdersDetail = useTrainsStore((state) => state.trainsUpsellOrdersDetail);
// const showDiff = !comm.isEmpty(searchValuesToSub.DateDiff2);
const showDiff = !comm.isEmpty(compareData);
const showDiff = !isEmpty(compareData);
const getTrainsWithUpsell = useTrainsStore((state) => state.getTrainsWithUpsell);
const getTrainsOrderDetail = useTrainsStore((state) => state.getTrainsOrderDetail);
@ -167,33 +168,33 @@ const TrainsUpsell = observer(({ ...props }) => {
{
title: '数量',
dataIndex: 'OrderCount',
render: (text, r) => (!showDiff ? text : comm.show_vs_tag(r.OrderCount_vs, r.OrderCount_diff, r.OrderCount, r.diff?.OrderCount)),
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.OrderCount} diffPercent={r.OrderCount_vs} diffData={r.OrderCount_diff} />,
},
{
title: '成交数',
dataIndex: 'CJCount',
render: (text, r) => (!showDiff ? text : comm.show_vs_tag(r.CJCount_vs, r.CJCount_diff, r.CJCount, r.diff?.CJCount)),
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.CJCount} diffPercent={r.CJCount_vs} diffData={r.CJCount_diff} />,
},
{
title: '成交人数',
dataIndex: 'CJPersonNum',
render: (text, r) => (!showDiff ? text : comm.show_vs_tag(r.CJPersonNum_vs, r.CJPersonNum_diff, r.CJPersonNum, r.diff?.CJPersonNum)),
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.CJPersonNum} diffPercent={r.CJPersonNum_vs} diffData={r.CJPersonNum_diff} />,
},
{
title: '成交率',
dataIndex: 'CJrate',
render: (text, r) => (!showDiff ? text : comm.show_vs_tag(r.CJrate_vs, r.CJrate_diff, r.CJrate, r.diff?.CJrate)),
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.CJrate} diffPercent={r.CJrate_vs} diffData={r.CJrate_diff} />,
},
{
title: '成交毛利(预计)',
dataIndex: 'YJLY',
render: (text, r) => (!showDiff ? text : comm.show_vs_tag(r.YJLY_vs, r.YJLY_diff, r.YJLY, r.diff?.YJLY)),
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.YJLY} diffPercent={r.YJLY_vs} diffData={r.YJLY_diff} />,
},
{
title: '单个订单价值',
dataIndex: 'Ordervalue',
render: (text, r) => (!showDiff ? text : comm.show_vs_tag(r.Ordervalue_vs, r.Ordervalue_diff, r.Ordervalue, r.diff?.Ordervalue)),
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.Ordervalue} diffPercent={r.Ordervalue_vs} diffData={r.Ordervalue_diff} />,
},
];
return (

@ -0,0 +1,273 @@
import { useContext } from 'react';
import { Row, Col, Table, Button } from 'antd';
import { SwapOutlined } from '@ant-design/icons';
import * as comm from '@haina/utils-commons';
import SearchForm from '../../components/search/SearchForm';
import { RenderVSDataCell } from '../../components/Data';
import { observer } from 'mobx-react';
import { toJS } from 'mobx';
import { stores_Context } from '../../config';
import { useShallow } from 'zustand/shallow';
import useSalesInsightStore from '../../zustand/SalesInsight';
// TdCellDataTable
const TdCell = (tdprops) => {
// onMouseEnter, onMouseLeave
const { onMouseEnter, onMouseLeave, ...restProps } = tdprops;
return <td {...restProps} />;
};
const MeetingSales = observer(() => {
const { date_picker_store: searchFormStore } = useContext(stores_Context);
const [searchValues, setSearchValues] = useSalesInsightStore(useShallow((state) => [state.searchValues, state.setSearchValues]));
const [loading, typeLoading, ] = useSalesInsightStore(useShallow((state) => [state.loading, state.typeLoading, ]));
const [tableMajorKey, tableData, salesDataTotal] = useSalesInsightStore(useShallow((state) => [state.matrixtableMajorKey, state.matrixTableData, state.salesDataTotal]));
const getMeetingDataSales = useSalesInsightStore((state) => state.getMeetingDataSales);
const onMatrixChange = useSalesInsightStore((state) => state.onMatrixChange);
// const showDiff = !comm.isEmpty(searchFormStore.start_date_cp);
const columns = [
{
title: (
<>
<Button icon={<SwapOutlined />} onClick={onMatrixChange} />
</>
),
key: 'matrix',
children: [
{
// title: '',
title: tableMajorKey === 'opi' ? '顾问' : '页面类型',
fixed: 'left',
dataIndex: tableMajorKey === 'opi' ? 'OPI_Name' : 'LineClass',
key: 'pk',
onCell: (record) => ({
rowSpan: record.rowSpan,
}),
},
{
title: tableMajorKey === 'opi' ? '页面类型' : '顾问',
dataIndex: tableMajorKey === 'opi' ? 'LineClass' : 'OPI_Name',
key: 'k2',
},
],
},
// { // title: '',</div><div></div><div>
{
title: (
<>
本期<div>订单数按订单提交日期; 成团数按订单确认日期; 入境按团抵达日期</div>
</>
),
key: 'current',
children: [
{
title: '订单数',
dataIndex: 'OrderNum',
key: 'OrderNum',
},
{
title: '成团数',
dataIndex: 'GroupNum',
key: 'GroupNum',
},
{
title: '成团率',
dataIndex: 'SuccessRate',
key: 'SuccessRate',
render: (text, r) => <RenderVSDataCell showDiffData={false} data1={comm.fixTo2Decimals(text * 100)} data2={r.diff?.SuccessRate} dataSuffix="%" />,
},
{
title: '总人数',
dataIndex: 'TotalPersonNum',
key: 'TotalPersonNum',
},
{
title: '总报价',
dataIndex: 'PreTotalPrice',
key: 'PreTotalPrice',
},
{
title: '总利润',
dataIndex: 'PreTotalProfit',
key: 'PreTotalProfit',
},
{
title: '利润率',
dataIndex: 'PreProfitRate',
key: 'PreProfitRate',
render: (text, r) => <RenderVSDataCell showDiffData={false} data1={comm.fixTo2Decimals(text * 100)} data2={r.diff?.PreProfitRate} dataSuffix="%" />,
},
{
title: '入境团数',
dataIndex: 'EntranceGroupNum',
key: 'EntranceGroupNum',
},
{
title: '入境人数',
dataIndex: 'EntrancePersonNum',
key: 'EntrancePersonNum',
},
],
},
{
title: (
<>
到目前<div>按订单提交日期</div>
</>
),
key: 'until',
children: [
{
title: '订单数',
dataIndex: 'LYOrderNum',
key: 'LYOrderNum',
onCell: (r) => ({
style: { backgroundColor: '#5B8FF9' + '1A' },
}),
},
{
title: '订单中的成团数',
dataIndex: 'LYGroupNum',
key: 'LYGroupNum',
onCell: (r) => ({
style: { backgroundColor: '#5B8FF9' + '1A' },
}),
},
{
title: '成团率',
dataIndex: 'LYSuccessRate',
key: 'LYSuccessRate',
render: (text, r) => <RenderVSDataCell showDiffData={false} data1={comm.fixTo2Decimals(text * 100)} data2={r.diff?.LYSuccessRate} dataSuffix="%" />,
onCell: (r) => ({
style: { backgroundColor: '#5B8FF9' + '1A' },
}),
},
],
},
{
title: (
<>
本年<div>按团抵达日期</div>
</>
),
key: 'toyear',
children: [
{
title: '团队数',
dataIndex: 'LYTotalGroupNum',
key: 'LYTotalGroupNum',
onCell: (r) => ({
style: { backgroundColor: '#9FB40F' + '1A' },
}),
},
{
title: '总人数',
dataIndex: 'LYTotalPersonNum',
key: 'LYTotalPersonNum',
onCell: (r) => ({
style: { backgroundColor: '#9FB40F' + '1A' },
}),
},
{
title: '总报价',
dataIndex: 'LYPreTotalPrice',
key: 'LYPreTotalPrice',
onCell: (r) => ({
style: { backgroundColor: '#9FB40F' + '1A' },
}),
},
{
title: '总利润',
dataIndex: 'LYPreTotalProfit',
key: 'LYPreTotalProfit',
onCell: (r) => ({
style: { backgroundColor: '#9FB40F' + '1A' },
}),
},
{
title: '利润率',
dataIndex: 'LYPreProfitRate',
key: 'LYPreProfitRate',
render: (text, r) => <RenderVSDataCell showDiffData={false} data1={comm.fixTo2Decimals(text * 100)} data2={r.diff?.LYPreProfitRate} dataSuffix="%" />,
onCell: (r) => ({
style: { backgroundColor: '#9FB40F' + '1A' },
}),
},
],
},
];
const columnsTotal = (toJS(columns)).slice(1);
const tableProps = {
size: 'small',
pagination: false,
scroll: { x: 100 * 7 },
loading,
};
return (
<>
<div>
<Row gutter={16} className={toJS(searchFormStore.siderBroken) ? '' : 'sticky-top'}>
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...toJS(searchFormStore.formValues),
...searchValues,
},
//
shows: ['WebCode', 'IncludeTickets', 'DepartmentList', 'dates', 'operator', 'lineClass'],
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple' },
WebCode: { show_all: false, mode: 'multiple' },
dates: { hide_vs: true },
operator: { param: {} },
},
}}
onSubmit={(_err, obj, form, str) => {
setSearchValues(obj, form);
getMeetingDataSales({ ...obj, IsDetail: 0 });
getMeetingDataSales({ ...obj, IsDetail: 1 });
// onTabChange(activeTab);
}}
/>
</Col>
</Row>
<Row gutter={[16, { sm: 16, lg: 32 }]}>
<Col span={24}>
<h2 key={'t1'}>业绩</h2>
<Table
sticky={{ offsetHeader: 56 }}
key={`table_to_xlsx_total`}
{...tableProps}
columns={columnsTotal}
loading={typeLoading}
dataSource={salesDataTotal}
bordered
components={{ body: { cell: TdCell } }}
/>
<h2 key={'t2'}>顾问×页面类型业绩</h2>
<Table
sticky={{ offsetHeader: 56 }}
key={`table_to_xlsx_lineclass`}
{...tableProps}
loading={typeLoading}
dataSource={tableData}
columns={columns}
bordered
components={{ body: { cell: TdCell } }}
/>
</Col>
</Row>
</div>
</>
);
});
export default MeetingSales;

@ -6,7 +6,7 @@ import moment from 'moment';
import { Row, Col, Table, Select, Spin, Tag } from 'antd';
import SearchForm from '../../components/search/SearchForm';
import MixFieldsDetail from '../../components/MixFieldsDetail';
import { fixTo2Decimals, isEmpty, pick } from '../../utils/commons';
import { fixTo2Decimals, isEmpty, pick } from '@haina/utils-commons';
import Column from '../../components/Column';
import { groupsMappedByKey } from '../../libs/ht';

@ -5,7 +5,7 @@ import { stores_Context } from '../../config';
import { InfoCircleOutlined } from '@ant-design/icons';
import { Row, Col, Table, Tooltip } from 'antd';
import SearchForm from '../../components/search/SearchForm';
import { pick } from '../../utils/commons';
import { pick } from '@haina/utils-commons';
const COLOR_SETS = [
"#FFFFFF",

@ -4,7 +4,7 @@ import { observer } from 'mobx-react';
import { stores_Context, DATE_FORMAT, SMALL_DATETIME_FORMAT } from '../../config';
import { InfoCircleOutlined } from '@ant-design/icons';
import { Row, Col, Table, Divider, Button, Popover, Tooltip } from 'antd';
import { fixTo2Decimals } from '../../utils/commons';
import { fixTo2Decimals } from '@haina/utils-commons';
import MixFieldsDetail from '../../components/MixFieldsDetail';
const COLOR_SETS = [

@ -0,0 +1,367 @@
import { useContext } from 'react';
import { Row, Col, Tabs, Table, Divider, Spin, Checkbox, Space } from 'antd';
import { ContainerOutlined, BlockOutlined, SmileOutlined, MobileOutlined, CustomerServiceOutlined, IeOutlined } from '@ant-design/icons';
import { Line, Pie } from '@ant-design/charts';
import { NavLink } from 'react-router-dom';
import * as comm from '@haina/utils-commons';
import DateGroupRadio from '../../components/DateGroupRadio';
import SearchForm from '../../components/search/SearchForm';
import { TableExportBtn } from '../../components/Data';
import { RenderVSDataCell } from './../../components/Data';
import { observer } from 'mobx-react';
import { toJS } from 'mobx';
import { stores_Context } from '../../config';
import { useShallow } from 'zustand/shallow';
import useToBOrderStore, { orderCountDataMapper, orderCountDataFieldMapper } from '../../zustand/ToBOrder';
const ToBOrder = observer(() => {
const { date_picker_store: searchFormStore } = useContext(stores_Context);
const [searchValues, setSearchValues] = useToBOrderStore(useShallow((state) => [state.searchValues, state.setSearchValues]));
const [activeTab, setActiveTab] = useToBOrderStore(useShallow((state) => [state.activeTab, state.setActiveTab]));
const [loading, typeLoading, onTabChange] = useToBOrderStore(useShallow((state) => [state.loading, state.typeLoading, state.onTabChange]));
const orderCountDataRaw = useToBOrderStore((state) => state.orderCountDataRaw);
const [orderCountDataLines, avgLineValue] = useToBOrderStore(useShallow((state) => [state.orderCountDataLines, state.avgLineValue]));
const [onChangeDateGroup, activeDateGroupRadio] = useToBOrderStore(useShallow((state) => [state.onChangeDateGroup, state.activeDateGroupRadio]));
const orderCountDataByType = useToBOrderStore((state) => state.orderCountDataByType);
const result = orderCountDataByType[activeTab] || {};
const getToBOrderCount = useToBOrderStore((state) => state.getToBOrderCount);
const showDiff = !comm.isEmpty(searchFormStore.start_date_cp);
const avg_line_y = Math.round(avgLineValue);
const lineConfig = {
data: orderCountDataLines,
padding: 'auto',
xField: 'xField',
yField: 'yField',
seriesField: 'seriesField',
// xAxis: {
// type: "timeCat",
// },
point: {
size: 4,
shape: 'cicle',
},
annotations: [
{
type: 'text',
position: ['start', avg_line_y],
content: avg_line_y,
offsetX: -15,
style: {
fill: '#F4664A',
textBaseline: 'bottom',
},
},
{
type: 'line',
start: [-10, avg_line_y],
end: ['max', avg_line_y],
style: {
stroke: '#F4664A',
lineDash: [2, 2],
},
},
],
label: {}, //
legend: {
itemValue: {
formatter: (text, item) => {
const items = orderCountDataLines.filter((d) => d.seriesField === item.value); //
return items.length ? items.reduce((a, b) => a + b.yField, 0) : ''; //
},
},
},
tooltip: {
customItems: (originalItems) => {
// process originalItems,
return originalItems.map((ele) => ({ ...ele, name: ele.data?.seriesField || ele.data?.xField }));
},
title: (title, datum) => {
let ret = title;
switch (activeDateGroupRadio) {
case 'day':
ret = `${title} ${comm.getWeek(datum.xField)}`; //
break;
default:
break;
}
return ret;
},
},
// smooth: true,
};
const pieConfig = {
appendPadding: 10,
data: [],
angleField: 'OrderCount',
colorField: 'OrderType',
radius: 0.8,
label: {
type: 'outer',
content: '{name} {value} \t {percentage}',
},
legend: false, //
interactions: [
{
type: 'element-selected',
},
{
type: 'element-active',
},
],
};
const tableProps = {
dataSource: result?.ordercount1 || [], // table_data.dataSource,
columns: [
{
title: '#',
fixed: 'left',
children: [
{
title: (
<span>
<div>{result.ordercountTotal1?.groups}</div>
{showDiff ? <div>{result.ordercountTotal2?.groups}</div> : null}
</span>
),
titleX: `${result.ordercountTotal1?.groups}` + (showDiff ? ` vs ${result.ordercountTotal2?.groups}` : ''),
dataIndex: 'OrderType',
fixed: 'left',
render: (text, record) => <NavLink to={`/tob_orders_sub/${activeTab}/${record.OrderTypeSN}/${encodeURIComponent(record.OrderType)}`}>{text}</NavLink>,
},
],
},
{
title: '数量',
children: [
{
title: (
<RenderVSDataCell
showDiffData={showDiff}
data1={result.ordercountTotal1?.OrderCount}
data2={result.ordercountTotal2?.OrderCount}
diffPercent={result.ordercountTotal1?.OrderCount_vs}
diffData={result.ordercountTotal1?.OrderCount_diff}
/>
),
titleX: [result.ordercountTotal1?.OrderCount, result.ordercountTotal2?.OrderCount].join(' vs '),
dataIndex: 'OrderCount',
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.OrderCount} diffPercent={r.OrderCount_vs} diffData={r.OrderCount_diff} />,
},
],
},
{
title: '成交数',
children: [
{
title: (
<RenderVSDataCell
showDiffData={showDiff}
data1={result.ordercountTotal1?.CJCount}
data2={result.ordercountTotal2?.CJCount}
diffPercent={result.ordercountTotal1?.CJCount_vs}
diffData={result.ordercountTotal1?.CJCount_diff}
/>
),
titleX: [result.ordercountTotal1?.CJCount, result.ordercountTotal2?.CJCount].join(' vs '),
dataIndex: 'CJCount',
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.CJCount} diffPercent={r.CJCount_vs} diffData={r.CJCount_diff} />,
},
],
},
{
title: '成交人数',
children: [
{
title: (
<RenderVSDataCell
showDiffData={showDiff}
data1={result.ordercountTotal1?.CJPersonNum}
data2={result.ordercountTotal2?.CJPersonNum}
diffPercent={result.ordercountTotal1?.CJPersonNum_vs}
diffData={result.ordercountTotal1?.CJPersonNum_diff}
/>
),
titleX: [result.ordercountTotal1?.CJPersonNum, result.ordercountTotal2?.CJPersonNum].join(' vs '),
dataIndex: 'CJPersonNum',
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.CJPersonNum} diffPercent={r.CJPersonNum_vs} diffData={r.CJPersonNum_diff} />,
},
],
},
{
title: '成交率',
children: [
{
title: (
<RenderVSDataCell
showDiffData={showDiff}
data1={result.ordercountTotal1?.CJrate}
data2={result.ordercountTotal2?.CJrate}
diffPercent={result.ordercountTotal1?.CJrate_vs}
diffData={result.ordercountTotal1?.CJrate_diff}
/>
),
titleX: [result.ordercountTotal1?.CJrate, result.ordercountTotal2?.CJrate].join(' vs '),
dataIndex: 'CJrate',
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.CJrate} diffPercent={r.CJrate_vs} diffData={r.CJrate_diff} />,
},
],
},
{
title: '成交毛利(预计)',
children: [
{
title: (
<RenderVSDataCell
showDiffData={showDiff}
data1={result.ordercountTotal1?.YJLY}
data2={result.ordercountTotal2?.YJLY}
diffPercent={result.ordercountTotal1?.YJLY_vs}
diffData={result.ordercountTotal1?.YJLY_diff}
/>
),
titleX: [result.ordercountTotal1?.YJLY, result.ordercountTotal2?.YJLY].join(' vs '),
dataIndex: 'YJLY',
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.YJLY} diffPercent={r.YJLY_vs} diffData={r.YJLY_diff} />,
},
],
},
{
title: '单个订单价值',
children: [
{
title: (
<RenderVSDataCell
showDiffData={showDiff}
data1={result.ordercountTotal1?.Ordervalue}
data2={result.ordercountTotal2?.Ordervalue}
diffPercent={result.ordercountTotal1?.Ordervalue_vs}
diffData={result.ordercountTotal1?.Ordervalue_diff}
/>
),
titleX: [result.ordercountTotal1?.Ordervalue, result.ordercountTotal2?.Ordervalue].join(' vs '),
dataIndex: 'Ordervalue',
render: (text, r) => <RenderVSDataCell showDiffData={showDiff} data1={text} data2={r.diff?.Ordervalue} diffPercent={r.Ordervalue_vs} diffData={r.Ordervalue_diff} />,
},
],
},
],
size: 'small',
pagination: false,
scroll: { x: 100 * 7 },
loading,
};
return (
<>
<div>
<Row gutter={16} className={toJS(searchFormStore.siderBroken) ? '' : 'sticky-top'}>
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...toJS(searchFormStore.formValues),
...searchValues,
},
//
shows: ['DateType', 'WebCode', 'IncludeTickets', 'DepartmentList', 'dates'],
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple' },
WebCode: { show_all: false, mode: 'multiple' },
years: { hide_vs: true },
},
}}
onSubmit={(_err, obj, form, str) => {
setSearchValues(obj, form);
getToBOrderCount(obj);
onTabChange(activeTab);
}}
/>
</Col>
</Row>
<Row gutter={[16, { sm: 16, lg: 32 }]}>
<Col span={24} style={{ textAlign: 'right' }}>
<DateGroupRadio
visible={orderCountDataLines.length !== 0}
dataRaw={orderCountDataRaw}
onChange={onChangeDateGroup}
value={activeDateGroupRadio}
dataMapper={orderCountDataMapper}
fieldMapper={orderCountDataFieldMapper}
/>
</Col>
<Col span={24}>
<Spin spinning={loading}>
<Line {...lineConfig} />
</Spin>
</Col>
<Col span={24}>
<Tabs
activeKey={activeTab}
onChange={(active_key) => onTabChange(active_key)}
items={[
{
key: 'customer_types',
label: (
<span>
<CustomerServiceOutlined />
分销客户
</span>
),
},
].map((ele) => {
return {
...ele,
children: (
<>
<Table sticky key={`table_to_xlsx_${ele.key}`} {...tableProps} loading={typeLoading} />
<Divider orientation="right" plain>
<TableExportBtn label={ele.key} {...{ columns: tableProps.columns, dataSource: tableProps.dataSource }} />
</Divider>
</>
),
};
})}
/>
</Col>
</Row>
<div>
<h3>各项占比</h3>
{/* <Checkbox
checked={true}
// onChange={(e) => setIsShowEmpty(e.target.checked)}
>
包含空值
</Checkbox> */}
</div>
<Spin spinning={typeLoading}>
<Row>
<Col sm={24} lg={12}>
<Pie {...pieConfig} data={result?.ordercount1 || []} innerRadius={0.6} statistic={{ title: false, content: { content: '数量' } }} />
<Pie {...pieConfig} data={result?.ordercount1 || []} angleField="YJLYx" innerRadius={0.6} statistic={{ title: false, content: { content: '预计毛利' } }} />
</Col>
{showDiff && (
<Col sm={24} lg={12}>
<Pie {...pieConfig} data={result?.ordercount2 || []} innerRadius={0.6} statistic={{ title: false, content: { content: '数量' } }} />
<Pie {...pieConfig} data={result?.ordercount2 || []} angleField="YJLYx" innerRadius={0.6} statistic={{ title: false, content: { content: '预计毛利' } }} />
</Col>
)}
</Row>
</Spin>
</div>
</>
);
});
export default ToBOrder;

@ -0,0 +1,295 @@
import { useContext, useEffect } from 'react';
import { Row, Col, Tabs, Table, Divider, Spin, Space } from 'antd';
import { ContainerOutlined, BlockOutlined, SmileOutlined, MobileOutlined } from '@ant-design/icons';
import { Line } from '@ant-design/charts';
import { NavLink, useParams } from 'react-router-dom';
import { getWeek } from '@haina/utils-commons';
import DateGroupRadio from '../../components/DateGroupRadio';
import SearchForm from '../../components/search/SearchForm';
import { TableExportBtn } from '../../components/Data';
import { observer } from 'mobx-react';
import { stores_Context } from '../../config';
import { useShallow } from 'zustand/shallow';
import useToBOrderStore, { orderCountDataMapper, orderCountDataFieldMapper } from '../../zustand/ToBOrder';
//
const addLineBreaksAtCommas = (text) => {
if (!text) return '';
return text.replace(/&amp;/g, '&').replace(//g, '\n').replace(/,/g, ',\n').replace(//g, '\n').replace(/;/g, ';\n');
};
const OrderDetailTable = ({ caption, dataSource, loading, ...props }) => {
const columns = [
{
title: '订单号',
dataIndex: 'COLI_ID',
key: 'COLI_ID',
},
{
title: '网站',
dataIndex: 'COLI_WebCode',
key: 'COLI_WebCode',
},
{
title: '国籍',
dataIndex: 'MEI_Country',
key: 'MEI_Country',
},
{
title: '成行',
dataIndex: 'COLI_Success',
key: 'COLI_Success',
render: (text, record) => <span>{text == 1 ? '是' : '否'}</span>,
sorter: (a, b) => b.COLI_Success - a.COLI_Success,
},
{
title: '人数(成/童/婴)',
dataIndex: 'COLI_PersonNum',
key: 'COLI_PersonNum',
render: (text, record) => (
<span>
{record.COLI_PersonNum}/{record.COLI_ChildNum}/{record.COLI_BabyNum}
</span>
),
},
{
title: '天数',
dataIndex: 'COLI_Days',
key: 'COLI_Days',
},
{
title: '预计利润',
dataIndex: 'CGI_YJLY',
key: 'CGI_YJLY',
},
{
title: '预定时间',
dataIndex: 'COLI_ApplyDate',
key: 'COLI_ApplyDate',
},
{
title: '确认日期',
dataIndex: 'COLI_ConfirmDate',
key: 'COLI_ConfirmDate',
},
{
title: '出发日期',
dataIndex: 'CGI_ArriveDate',
key: 'CGI_ArriveDate',
},
// {
// title: '',
// dataIndex: 'COLI_CustomerRequest',
// key: 'COLI_CustomerRequest',
// ellipsis: true,
// },
{
title: '订单内容',
dataIndex: 'COLI_OrderDetailText',
key: 'COLI_OrderDetailText',
ellipsis: true,
},
Table.EXPAND_COLUMN,
];
return (
<div>
<Divider orientation="left" plain>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: '10px' }}>
<div>{caption}</div>
<TableExportBtn label={caption} columns={columns} dataSource={dataSource} />
</div>
</Divider>
<Table
id="table_to_xlsx_form"
dataSource={dataSource}
columns={columns}
loading={loading}
size="small"
// pagination={false}
rowKey={(record) => record.key}
expandable={{
expandedRowRender: (record) => (
<pre style={{ whiteSpace: 'pre-wrap', wordWrap: 'break-word', fontSize: '16px' }}>
{/* <Divider orientation="left" plain>
客户需求
</Divider>
{record.COLI_CustomerRequest} */}
<Divider orientation="left" plain>
订单内容
</Divider>
{record.COLI_OrderDetailText}
</pre>
),
}}
/>
</div>
);
};
const ToBOrderSub = observer(({ ...props }) => {
const { ordertype, ordertype_sub, ordertype_title } = useParams();
const { date_picker_store: searchFormStore } = useContext(stores_Context);
const [searchValues, setSearchValues] = useToBOrderStore(useShallow((state) => [state.searchValues, state.setSearchValues]));
const [searchValuesToSub] = useToBOrderStore(useShallow((state) => [state.searchValuesToSub]));
const [loading, typeLoading] = useToBOrderStore(useShallow((state) => [state.loading, state.typeLoading]));
const orderCountDataRawSub = useToBOrderStore((state) => state.orderCountDataRawSub);
const [orderCountDataLinesSub, avgLineValueSub] = useToBOrderStore(useShallow((state) => [state.orderCountDataLinesSub, state.avgLineValueSub]));
const [onChangeDateGroupSub, activeDateGroupRadioSub] = useToBOrderStore(useShallow((state) => [state.onChangeDateGroupSub, state.activeDateGroupRadioSub]));
const orderDetails = useToBOrderStore((state) => state.orderDetails);
const getToBOrderCount = useToBOrderStore((state) => state.getToBOrderCount);
const getToBOrderDetailByType = useToBOrderStore((state) => state.getToBOrderDetailByType);
useEffect(() => {
getToBOrderCount(searchValuesToSub, ordertype, ordertype_sub);
getToBOrderDetailByType(searchValuesToSub, ordertype, ordertype_sub);
}, []);
const avg_line_y = Math.round(avgLineValueSub);
const lineConfig = {
data: orderCountDataLinesSub,
padding: 'auto',
xField: 'xField',
yField: 'yField',
seriesField: 'seriesField',
// xAxis: {
// type: "timeCat",
// },
// point: {
// size: 4,
// shape: 'cicle',
// },
annotations: [
{
type: 'text',
position: ['start', avg_line_y],
content: avg_line_y,
offsetX: -15,
style: {
fill: '#F4664A',
textBaseline: 'bottom',
},
},
{
type: 'line',
start: [-10, avg_line_y],
end: ['max', avg_line_y],
style: {
stroke: '#F4664A',
lineDash: [2, 2],
},
},
],
label: {}, //
legend: {
itemValue: {
formatter: (text, item) => {
const items = orderCountDataLinesSub.filter((d) => d.seriesField === item.value); //
return items.length ? items.reduce((a, b) => a + b.yField, 0) : ''; //
},
},
},
tooltip: {
customItems: (originalItems) => {
// process originalItems,
return originalItems.map((ele) => ({ ...ele, name: ele.data?.seriesField || ele.data?.xField }));
},
title: (title, datum) => {
let ret = title;
switch (activeDateGroupRadioSub) {
case 'day':
ret = `${title} ${getWeek(datum.xField)}`; //
break;
default:
break;
}
return ret;
},
},
// smooth: true,
};
const tab_items = [
{
key: 'detail',
label: (
<span>
<ContainerOutlined />
订单内容
</span>
),
title: '订单内容',
children: (
<>
<Space direction="vertical">
<OrderDetailTable caption={orderDetails[0]?.dateRangeStr} dataSource={orderDetails[0]?.data || []} loading={typeLoading} />
<OrderDetailTable caption={orderDetails[1]?.dateRangeStr} dataSource={orderDetails[1]?.data || []} loading={typeLoading} />
</Space>
</>
),
},
];
return (
<div>
<Row gutter={{ sm: 16, lg: 32 }} className={searchFormStore.siderBroken ? '' : 'sticky-top'}>
<Col md={24} lg={12} xxl={14}>
<NavLink to={`/tob_orders`}>返回</NavLink>
</Col>
<Col className="gutter-row" span={24}>
<SearchForm
defaultValue={{
initialValue: {
...searchFormStore.formValues,
...searchValues,
},
//
shows: ['DateType', 'DepartmentList', 'WebCode', 'IncludeTickets', 'dates'],
fieldProps: {
DepartmentList: { show_all: false, mode: 'multiple' },
WebCode: { show_all: false, mode: 'multiple' },
// dates: { hide_vs: true },
},
}}
onSubmit={(_err, obj, form, str) => {
setSearchValues(obj, form);
getToBOrderCount(obj, ordertype, ordertype_sub);
getToBOrderDetailByType(obj, ordertype, ordertype_sub);
}}
/>
</Col>
</Row>
<Row gutter={[16, { xs: 8, sm: 16, md: 24, lg: 32 }]}>
<Col span={24} style={{ textAlign: 'right' }}>
<DateGroupRadio
visible={orderCountDataLinesSub.length !== 0}
dataRaw={orderCountDataRawSub}
onChange={onChangeDateGroupSub}
value={activeDateGroupRadioSub}
dataMapper={orderCountDataMapper}
fieldMapper={orderCountDataFieldMapper}
/>
</Col>
<Col className="gutter-row" span={24}>
<Spin spinning={loading}>
<Line {...lineConfig} />
</Spin>
</Col>
<Col className="gutter-row" span={24}>
<Tabs
activeKey={'detail'}
// onChange={onTabsChange}
items={tab_items}
/>
</Col>
</Row>
</div>
);
});
export default ToBOrderSub;

@ -2,10 +2,10 @@ import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import { groupsMappedByCode } from '../libs/ht';
import { fetchJSON } from '../utils/request';
import { fetchJSON } from '@haina/utils-request';
import { HT_HOST } from '../config';
import { resultDataCb } from '../components/DateGroupRadio/date';
import { isEmpty, price_to_number } from '../utils/commons';
import { isEmpty, price_to_number } from '@haina/utils-commons';
const defaultParams = { WebCode: 'all', IncludeTickets: 1, IncludeInternal: 1 };

@ -0,0 +1,172 @@
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import { fetchJSON } from '@haina/utils-request';
import { HT_HOST } from '../config';
import { formatDate, isEmpty, sortBy } from '@haina/utils-commons';
import { groupsMappedByKey, sitesMappedByCode, pivotBy } from './../libs/ht';
const defaultParams = {};
export const fetchRegularCustomer = async (params) => {
const _params = {
Website: params.WebCode || '', // CHT,AH,JH,GH,ZWQD,GH_ZWQD_HW,GHKYZG,GHKYHW,HTravel
DEI_SNList: params.DepartmentList || '', // 1,2,28,7
ApplydateCheck: params.DateType === 'applyDate' ? 1 : 0, //
EntrancedateCheck: params.DateType === 'startDate' ? 1 : 0, //
ConfirmDateCheck: params.DateType === 'confirmDate' ? 1 : 0, //
ApplydateStart: params.Date1 || '',
ApplydateEnd: params.Date2 || '',
EntrancedateStart: params.Date1 || '',
EntrancedateEnd: params.Date2 || '',
ConfirmdateStart: params.Date1 || '',
ConfirmdateEnd: params.Date2 || '',
IsDetail: '', //
IncludeTickets: '', //
...params,
};
const { WebCode, DepartmentList, DateType, Date1, Date2, ...readyParams } = _params;
const [result1=[], result2=[]] = await Promise.all([
fetchJSON(HT_HOST + '/service-tourdesign/RegularCusOrder', { ...defaultParams, ...readyParams }),
...(params.DateDiff1 && params.IsDetail === 0
? [
fetchJSON(HT_HOST + '/service-tourdesign/RegularCusOrder', {
...defaultParams,
...readyParams,
ApplydateStart: params.DateDiff1 || '',
ApplydateEnd: params.DateDiff2 || '',
EntrancedateStart: params.DateDiff1 || '',
EntrancedateEnd: params.DateDiff2 || '',
ConfirmdateStart: params.DateDiff1 || '',
ConfirmdateEnd: params.DateDiff2 || '',
}),
]
: []),
]);
if (params.IsDetail === 1) {
return { result1, result2 };
}
const ret = {};
const result1Mapped = result1.reduce((r, v) => ({ ...r, [v.ItemName]: v }), {});
const allKeys = [...new Set([...result1.map((e) => e.ItemName), ...result2.map((e) => e.ItemName)])];
const result2Mapped = result2.reduce((r, v) => ({ ...r, [v.ItemName]: v }), {});
const x = {};
allKeys.forEach((key) => {
x[key] = { ...(result1Mapped?.[key] || { ItemName: key }), diff: result2Mapped[key] || {} };
});
ret.result1 = Object.values(x);
return { result1: ret.result1 }; // { result1, result2 };
};
// 老客户: 日期对应的数据字段
const dateTypeDataHelper = {
applyDate: 'SumOrder',
startDate: 'ConfirmOrder',
confirmDate: 'ConfirmOrder',
};
/**
* 构建 老客户系列数据
* * 用明细数据计算
* @param {[]} details
* @param {string} pivotByOrder
* @param {string} pivotByDate
* @returns
*/
const buildSeriesDataFromDetails = (details, pivotByOrder, pivotByDate) => {
const dataDetail = (details || []).map((ele) => ({
...ele,
key: ele.COLI_ID,
orderState: ele.OrderState,
applyDate: formatDate(new Date(ele.COLI_ApplyDate)),
startDate: ele.COLI_OrderStartDate,
confirmDate: formatDate(new Date(ele.COLI_ConfirmDate)),
}));
const { data: IsOldData } = pivotBy(
dataDetail.filter((ele) => ele.COLI_IsOld === '是'),
[['COLI_IsOld'], [], pivotByDate]
);
const { data: isCusCommendData } = pivotBy(
dataDetail.filter((ele) => ele.COLI_IsCusCommend === '是'),
[['COLI_IsCusCommend'], [], pivotByDate]
);
// console.log('IsOldData====', IsOldData, '\nisCusCommend', isCusCommendData);
// 合并成两个系列
const seriesData = []
.concat(
IsOldData.map((ele) => ({ ...ele, _ylabel: '老客户' })),
isCusCommendData.map((ele) => ({ ...ele, _ylabel: '老客户推荐' }))
)
.sort(sortBy(pivotByDate));
return seriesData;
};
/**
* --------------------------------------------------------------------------------------------------------
*/
const initialState = {
loading: false,
loading2: false,
searchValues: {
DepartmentList: ['1', '2', '28', '7', '33'].map((kk) => groupsMappedByKey[kk]),
WebCode: ['CHT', 'AH', 'JH', 'GH', 'ZWQD', 'GH_ZWQD_HW', 'GHKYZG', 'GHKYHW', 'HTravel'].map((kk) => sitesMappedByCode[kk]),
DateType: { key: 'applyDate', label: '提交日期' },
IncludeTickets: { key: '0', label: '不含门票' },
},
searchValuesToSub: {},
regular: { data: [], details: [], total_data_tips: '', pivotData: [], pivotY: 'SumOrder', pivotX: '' },
};
const useCustomerRelationsStore = create(
devtools(
immer((set, get) => ({
...initialState,
reset: () => set(initialState),
setLoading: (loading) => set({ loading }),
setLoading2: (loading2) => set({ loading2 }),
setSearchValues: (obj, values) => set((state) => ({ searchValues: values, searchValuesToSub: obj })),
setSearchValuesToSub: (values) => set((state) => ({ searchValuesToSub: values })),
// 获取数据 ---------------------------------------------------------------------------------------------------
// 老客户
getRegularCustomer: async (params) => {
const { setLoading, setLoading2 } = get();
const { IsDetail } = params;
setLoading(true);
setLoading2(IsDetail === 1);
const pivotByOrder = dateTypeDataHelper[params.DateType];
const pivotByDate = params.DateType;
try {
const {result1, result2} = await fetchRegularCustomer(params);
set((state) => {
if (IsDetail === 1) {
state.regular.details = result1;
const dump_l = (result1 || []).filter((ele) => ele.COLI_IsOld !== '' && ele.COLI_IsCusCommend !== '').length;
state.regular.total_data_tips = dump_l > 0 ? `包含 ${dump_l} 条同时勾选的数据` : '';
/** 使用明细数据画图 */
const seriesData = buildSeriesDataFromDetails(result1, pivotByOrder, pivotByDate);
state.regular.pivotData = seriesData;
state.regular.pivotX = pivotByDate;
state.regular.pivotY = pivotByOrder;
} else {
console.log('0000');
state.regular.data = result1;
}
});
} catch (error) {
console.error(error);
} finally {
setLoading(false);
IsDetail === 1 && setLoading2(false);
}
},
})),
{ name: 'CustomerRelations' }
)
);
export default useCustomerRelationsStore;

@ -0,0 +1,132 @@
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import { fetchJSON } from '@haina/utils-request';
import { HT_HOST } from '../config';
import { isEmpty } from '@haina/utils-commons';
const defaultParams = {};
export const fetchAgentGroupCount = async (params) => {
const { errcode, errmsg, ...result } = await fetchJSON(HT_HOST + '/service-web/QueryData/GetAgentGroupInfoALL', {
...defaultParams,
// ...params,
DateType: params.DateType,
VEI_SN: params.agency || '',
DepList: params.DepartmentList || '',
Country: params.countryArea || '',
Date1: params.Date1,
Date2: params.Date2,
OldDate1: params.DateDiff1 || '',
OldDate2: params.DateDiff2 || '',
});
if (errcode !== 0) {
return {};
}
const { result1, result2, total1, total2 } = result;
const result1Mapped = result1.reduce((r, v) => ({ ...r, [v.EOI_ObjSN]: v }), {});
const ret = {};
if (isEmpty(params.DateDiff1)) {
ret.result1 = Object.values(result1Mapped).filter((row) => row.EOI_ObjSN !== -1);
ret.total1 = { ...result1Mapped['-1'] };
return ret;
} else {
const allKeys = [...new Set([...result1.map((e) => e.EOI_ObjSN), ...result2.map((e) => e.EOI_ObjSN)])];
const result2Mapped = result2.reduce((r, v) => ({ ...r, [v.EOI_ObjSN]: v }), {});
const x = {};
allKeys.forEach((key) => {
x[key] = { ...(result1Mapped?.[key] || { EOI_ObjSN: key, vi: key, key, VendorName: result2Mapped[key]?.VendorName || '--' }), diff: result2Mapped[key] || {} };
});
ret.result1 = Object.values(x).filter((row) => row.EOI_ObjSN !== -1);
ret.total1 = { ...result1Mapped['-1'], diff: result2Mapped['-1'] || {} };
return ret;
}
};
export const fetchGroupListByAgentId = async (params) => {
const { agentId, DateType, Date1, Date2, DateDiff1, DateDiff2, DepartmentList, countryArea } = params;
const { errcode, errmsg, ...result } = await fetchJSON(HT_HOST + '/service-web/QueryData/GetAgentGroupInfo', {
VEI_SN: agentId || '',
DateType: DateType || '',
Date1: Date1 || '',
Date2: Date2 || '',
OldDate1: DateDiff1 || '',
OldDate2: DateDiff2 || '',
DepList: DepartmentList || '',
});
if (errcode !== 0) {
return {};
}
result.title1 = Date1 ? `${Date1}~${Date2}` : '';
result.title2 = DateDiff1 ? `${DateDiff1}~${DateDiff2}` : '';
return result;
};
/**
* --------------------------------------------------------------------------------------------------------
*/
const initialState = {
loading: false,
typeLoading: false,
searchValues: {
DateType: { key: 'departureDate', label: '抵达日期' },
WebCode: { key: 'all', label: '所有来源' },
IncludeTickets: { key: '1', label: '含门票' },
DepartmentList: { key: 'All', label: '所有来源' },
countryArea: { key: 'china', label: '国内' },
},
searchValuesToSub: {
DateType: 'departureDate',
WebCode: 'all',
IncludeTickets: '1',
DepartmentList: 'All',
},
agentCountList: [],
agentCountTotal: {},
agencyName: '',
agencyGroups: { total1: {}, result1: [], total2: {}, result2: [] },
};
const useCustomerServicesStore = create(
devtools(
immer((set, get) => ({
...initialState,
reset: () => set(initialState),
setLoading: (loading) => set({ loading }),
setSearchValues: (obj, values) => set((state) => ({ searchValues: values, searchValuesToSub: obj })),
setSearchValuesToSub: (values) => set((state) => ({ searchValuesToSub: values })),
getAgentGroupCount: async (params) => {
const { setLoading } = get();
setLoading(true);
try {
const res = await fetchAgentGroupCount(params);
set({ agentCountList: res.result1 || [], agentCountTotal: res.total1 || {} });
} catch (error) {
} finally {
setLoading(false);
}
},
getGroupListByAgentId: async (agentId) => {
const { setLoading, searchValuesToSub } = get();
setLoading(true);
try {
const res = await fetchGroupListByAgentId({ ...searchValuesToSub, agentId });
set({ agencyGroups: res, agencyName: res.result1?.[0]?.VendorName || '' });
} catch (error) {
} finally {
setLoading(false);
}
},
})),
{ name: 'CustomerServices' }
)
);
export default useCustomerServicesStore;

@ -0,0 +1,141 @@
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import { fetchJSON } from '@haina/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 transSearchParams = (params) => {
const newsearchParams = {
...params,
vei_sn: params.agency,
DepartmentList: params.DepartmentList === 'ALL' ? undefined : params.DepartmentList,
};
const oldseachPararms = {
...params,
Date1: params.DateDiff1,
Date2: params.DateDiff2,
vei_sn: params.agency,
DepartmentList: params.DepartmentList === 'ALL' ? undefined : params.DepartmentList,
};
return {
newsearchParams,
oldseachPararms,
};
};
export const fetchCaseSummary = async (params) => {
const {newsearchParams, oldseachPararms} = transSearchParams(params);
const { errcode, result } = await fetchJSON(`${HT_HOST}/service-Analyse2/dong_dao_zhu_total`, newsearchParams);
let response2;
if(oldseachPararms.DateDiff1){
response2 = await fetchJSON(`${HT_HOST}/service-Analyse2/dong_dao_zhu_total`, oldseachPararms);
}else{
response2 = { errcode: 0, result: [] };
}
const caseSummary = result.map((item, index) => ({
...item,
diff: response2.errcode !== 0 ? {} : (response2.result[index] || {dongdaozhu_rate: "0%"})
}));
return errcode !== 0 ? [] : (caseSummary || []);
};
export const fetchCaseSummaryByGuide = async (params) => {
const {newsearchParams, oldseachPararms} = transSearchParams(params);
const { errcode, result } = await fetchJSON(`${HT_HOST}/service-Analyse2/dong_dao_zhu_tour_guide`, newsearchParams);
let response2;
if(oldseachPararms.DateDiff1){
response2 = await fetchJSON(`${HT_HOST}/service-Analyse2/dong_dao_zhu_tour_guide`, oldseachPararms);
}else{
response2 = { errcode: 0, result: [] };
}
const caseSummaryByGuide = result.map((item, index) => ({
...item,
diff: response2.errcode !== 0 ? {} : (response2.result.find(r => r.TGI_SN === item.TGI_SN) || {dongdaozhu_rate: "0%"})
}));
return errcode !== 0 ? [] : (caseSummaryByGuide || []); // .sort(sortDescBy('case_count_dongdaozhu'));
};
export const fetchCaseFeatured = async (params) => {
const {newsearchParams, oldseachPararms} = transSearchParams(params);
const { errcode, result } = await fetchJSON(`${HT_HOST}/service-Analyse2/dong_dao_zhu_case`, newsearchParams);
return errcode !== 0 ? [] : (result || []);
};
/**
* 东道主报告----------------------------------------------------------------------------------------------
*/
const initialState = {
loading: false,
loadingCase: false,
caseSummary: [],
caseSummaryByGuide: [],
caseFeatured: [],
forExport: {
agencyName: '',
title: '',
year: '',
}
};
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 })),
setSearchValuesToSub: (values) => set((state) => ({ searchValuesToSub: values })),
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年')}总结`,
agencyName: searchValues.agency.label,
year: moment(params.Date1).format('YYYY'),
},
});
setLoading(false);
},
})),
{ name: 'hostCaseReportStore' }
)
);
export default useHostCaseStore;

@ -0,0 +1,122 @@
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import { groupsMappedByCode } from '../libs/ht';
import { fetchJSON } from '@haina/utils-request';
import { HT_HOST } from '../config';
import { groupBy, isEmpty, } from '@haina/utils-commons';
/**
* 顾问业绩 (例会数据)
*/
const defaultParams = { OrderType: 227001, IsDYTJ: '', IncludeTickets: 1, Team: '', WebCodeFX: '', CusType: '', OldCus: 0, lineClass: '', IsDetail: -1 };
export const fetchMeetingDataSales = async (params) => {
const { errcode, errmsg, result } = await fetchJSON(HT_HOST + '/service-web/QueryData/WLCountForMeetingNew', {
...defaultParams,
...params,
WebCode: (params.WebCode || '').replace('all', ''),
OPI_SN: params.operator || '',
});
const ret =
errcode !== 0
? []
: (result || [])
// .filter((ele) =>
// Object.keys(ele)
// .filter((col) => !['OPI_SN', 'OPI_Name', 'COLI_LineClass', 'LineClass', 'vi'].includes(col))
// .some((col) => !isEmpty(ele[col])),
// )
.map((ele) => ({ ...ele, key: `${ele.OPI_SN}_${ele.COLI_LineClass || ''}_${ele.vi || ''}` }));
const byOPI = groupBy(structuredClone(ret), 'OPI_SN');
const OPIValue = Object.keys(byOPI).reduce((r, opisn) => {
byOPI[opisn].forEach((ele, xi) => {
ele.rowSpan = xi === 0 ? byOPI[opisn].length : 0;
});
return [...r, ...byOPI[opisn]];
}, []);
const byLineClass = groupBy(structuredClone(ret), 'LineClass');
const LineClassValue = Object.keys(byLineClass).reduce((r, gkey) => {
byLineClass[gkey].forEach((ele, xi) => {
ele.rowSpan = xi === 0 ? byLineClass[gkey].length : 0;
});
return [...r, ...byLineClass[gkey]];
}, []);
return { result: ret, OPIValue, LineClassValue };
};
/**
* --------------------------------------------------------------------------------------------------------
*/
const initialState = {
loading: false,
typeLoading: false,
searchValues: {
// DateType: { key: 'applyDate', label: '提交日期' },
WebCode: { key: 'all', label: '所有来源' },
IncludeTickets: { key: '1', label: '含门票' },
DepartmentList: groupsMappedByCode.GH, // { key: 'All', label: '所有来源' }, //
},
searchValuesToSub: {
// DateType: 'applyDate',
WebCode: 'all',
IncludeTickets: '1',
DepartmentList: -1, // -1: All
},
salesDataTotal: [],
salesData: [],
matrixData: {},
matrixtableMajorKey: 'opi',
matrixTableData: [],
// 二级页面
};
const useSalesInsightStore = create(
devtools(
immer((set, get) => ({
...initialState,
reset: () => set(initialState),
setLoading: (loading) => set({ loading }),
setTypeLoading: (typeLoading) => set({ typeLoading }),
setSearchValues: (obj, values) => set((state) => ({ searchValues: values, searchValuesToSub: obj })),
setSearchValuesToSub: (values) => set((state) => ({ searchValuesToSub: values })),
setActiveTab: (tab) => set({ activeTab: tab }),
// site effects
getMeetingDataSales: async (params) => {
const { setTypeLoading, } = get();
setTypeLoading(true);
try {
const res = await fetchMeetingDataSales(params);
if (params.IsDetail === 1) {
set({ matrixData: res, matrixTableData: res.OPIValue, matrixtableMajorKey: 'opi', salesData: res.result });
}
else {
set({ salesDataTotal: res.result });
}
} catch (error) {
console.error(error);
} finally {
setTypeLoading(false);
}
},
onMatrixChange: () => {
const { matrixtableMajorKey, matrixData } = get();
const newKey = matrixtableMajorKey === 'opi' ? 'lineclass' : 'opi';
const dataKey = matrixtableMajorKey === 'opi' ? 'LineClassValue' : 'OPIValue' ;
set({ matrixtableMajorKey: newKey, matrixTableData: matrixData?.[dataKey] });
},
// sub
})),
{ name: 'SalesInsight' }
)
);
export default useSalesInsightStore;

@ -0,0 +1,241 @@
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import { groupsMappedByCode } from '../libs/ht';
import { fetchJSON } from '@haina/utils-request';
import { HT_HOST } from '../config';
import { resultDataCb } from '../components/DateGroupRadio/date';
import { isEmpty, price_to_number } from '@haina/utils-commons';
/**
* 分销(ToB)订单
*/
const defaultParams = {};
export const fetchToBOrderCount = async (params, type = '', typeVal = '') => {
const { errcode, errmsg, ...result } = await fetchJSON(HT_HOST + '/service-web/QueryData/GetOrderCount_FX', {
...defaultParams,
...params,
COLI_ApplyDate1: params.Date1,
COLI_ApplyDate2: params.Date2,
COLI_ApplyDateOld1: params.DateDiff1 || '',
COLI_ApplyDateOld2: params.DateDiff2 || '',
OrderType: type,
OrderType_val: typeVal,
});
return errcode !== 0 ? {} : (result || {});
};
const _typeRes = {
'ordercount1': [],
'ordercount2': [],
'ordercountTotal1': {
'OrderType': '合计',
'OrderCount': 0,
'CJCount': 0,
'CJPersonNum': 0,
'YJLY': '',
'CJrate': '0%',
'Ordervalue': '',
'groups': '',
'key': 1,
},
'ordercountTotal2': {},
};
export const fetchToBOrderCountByType = async (type, params) => {
const { errcode, errmsg, ...result } = await fetchJSON(HT_HOST + '/service-web/QueryData/GetOrderCountByType_FX', {
...defaultParams,
...params,
COLI_ApplyDate1: params.Date1,
COLI_ApplyDate2: params.Date2,
COLI_ApplyDateOld1: params.DateDiff1 || '',
COLI_ApplyDateOld2: params.DateDiff2 || '',
OrderType: type,
});
const res = errcode !== 0 ? _typeRes : (result || _typeRes);
const rows1Map = res.ordercount1.reduce((a, row1) => ({ ...a, [row1.OrderTypeSN]: {...row1, YJLYx: price_to_number(row1.YJLY)} }), {});
const rows2Map = res.ordercount2.reduce((a, row2) => ({ ...a, [row2.OrderTypeSN]: {...row2, YJLYx: price_to_number(row2.YJLY)} }), {});
const mixRow1 = res.ordercount1.map((row1) => ({ ...row1, YJLYx: price_to_number(row1.YJLY), diff: rows2Map[row1.OrderTypeSN] || {} }));
// Diff: elements in rows2 but not in rows1
const diff = [...new Set(Object.keys(rows2Map).filter((x) => !new Set(Object.keys(rows1Map)).has(x)))];
mixRow1.push(...diff.map((sn) => ({ diff: rows2Map[sn], OrderType: rows2Map[sn].OrderType, OrderTypeSN: rows2Map[sn].OrderTypeSN })));
return { ...res, ordercount1: mixRow1, ordercount2: res.ordercount2.map((row1) => ({ ...row1, YJLYx: price_to_number(row1.YJLY), })) };
};
const _detailRes = { ordercount1: [], ordercount2: [] };
export const fetchToBOrderDetailByType = async (params, type = '', typeVal = '', orderContent = 'detail') => {
const { errcode, errmsg, ...result } = await fetchJSON(HT_HOST + '/service-web/QueryData/GetOrderCountByType_Sub_FX', {
...defaultParams,
SubOrderType: orderContent,
...params,
COLI_ApplyDate1: params.Date1,
COLI_ApplyDate2: params.Date2,
COLI_ApplyDateOld1: params.DateDiff1 || '',
COLI_ApplyDateOld2: params.DateDiff2 || '',
OrderType: type,
OrderType_val: typeVal,
});
const res = errcode !== 0 ? _detailRes : (result || _detailRes);
const dateStr = [params.Date1, params.Date2].map((d) => d.substring(0, 10)).join('~');
const dateDiffStr = isEmpty(params.DateDiff1) ? '' : [params.DateDiff1, params.DateDiff2].map((d) => d.substring(0, 10)).join('~');
const ret = [
{ dateRangeStr: dateStr, data: res.ordercount1 },
{ dateRangeStr: dateDiffStr, data: res.ordercount2 },
];
return ret;
};
const calculateLineData = (value, data, avg1) => {
const groupByDate = data.reduce((r, v) => {
(r[v.ApplyDate] || (r[v.ApplyDate] = [])).push(v);
return r;
}, {});
const _data = Object.keys(groupByDate)
.reduce((r, _d) => {
const xAxisGroup = groupByDate[_d].reduce((a, v) => {
(a[v.groups] || (a[v.groups] = [])).push(v);
return a;
}, {});
Object.keys(xAxisGroup).map((_group) => {
const summaryVal = xAxisGroup[_group].reduce((rows, row) => rows + row.orderCount, 0);
r.push({ ...xAxisGroup[_group][0], orderCount: summaryVal });
return _group;
});
return r;
}, [])
.map((row) => ({ xField: row.ApplyDate, yField: row.orderCount, seriesField: row.groups }));
return { lines: _data, dateRadioValue: value, avgLineValue: avg1 };
};
export const orderCountDataMapper = { 'data1': 'ordercount1', data2: 'ordercount2' };
export const orderCountDataFieldMapper = { 'dateKey': 'ApplyDate', 'valueKey': 'orderCount', 'seriesKey': 'id', _f: 'sum' };
/**
* --------------------------------------------------------------------------------------------------------
*/
const initialState = {
loading: false,
typeLoading: false,
searchValues: {
DateType: { key: 'applyDate', label: '提交日期' },
WebCode: { key: 'all', label: '所有来源' },
IncludeTickets: { key: '1', label: '含门票' },
DepartmentList: groupsMappedByCode.GH, // { key: 'All', label: '所有来源' }, //
},
searchValuesToSub: {
DateType: 'applyDate',
WebCode: 'all',
IncludeTickets: '1',
DepartmentList: -1, // -1: All
},
activeTab: 'customer_types',
activeDateGroupRadio: 'day',
orderCountDataRaw: {},
orderCountDataLines: [],
avgLineValue: 0,
orderCountDataByType: {},
// 二级页面
orderCountDataRawSub: {},
orderCountDataLinesSub: [],
avgLineValueSub: 0,
activeDateGroupRadioSub: 'day',
orderDetails: [],
};
const useToBOrderStore = create(
devtools(
immer((set, get) => ({
...initialState,
reset: () => set(initialState),
setLoading: (loading) => set({ loading }),
setTypeLoading: (typeLoading) => set({ typeLoading }),
setSearchValues: (obj, values) => set((state) => ({ searchValues: values, searchValuesToSub: obj })),
setSearchValuesToSub: (values) => set((state) => ({ searchValuesToSub: values })),
setActiveTab: (tab) => set({ activeTab: tab }),
setOrderCountDataLines: (data) => set({ orderCountDataLines: data }),
setOrderCountDataByType: (type, data) =>
set((state) => {
state.orderCountDataByType[type] = data;
}),
// data ----
onChangeDateGroup: (value, data, avg1) => {
const { lines, dateRadioValue, avgLineValue } = calculateLineData(value, data, avg1);
set({ orderCountDataLines: lines, avgLineValue, activeDateGroupRadio: dateRadioValue });
},
onChangeDateGroupSub: (value, data, avg1) => {
const { lines, dateRadioValue, avgLineValue } = calculateLineData(value, data, avg1);
set({ orderCountDataLinesSub: lines, avgLineValueSub: avgLineValue, activeDateGroupRadioSub: dateRadioValue });
},
// site effects
getToBOrderCount: async (params, type, typeVal) => {
const { setLoading } = get();
setLoading(true);
try {
const res = await fetchToBOrderCount(params, type, typeVal);
// 第一次得到数据
const { lines, dateRadioValue, avgLineValue } = resultDataCb(res, 'day', orderCountDataMapper, orderCountDataFieldMapper, calculateLineData);
if (isEmpty(type)) {
// index page
set({ orderCountDataRaw: res });
set({ orderCountDataLines: lines, avgLineValue, activeDateGroupRadio: dateRadioValue });
} else {
// sub page
set({ orderCountDataRawSub: res });
set({ orderCountDataLinesSub: lines, avgLineValueSub: avgLineValue, activeDateGroupRadioSub: dateRadioValue });
}
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
},
getToBOrderCount_type: async (type) => {
const { setTypeLoading, searchValuesToSub, setOrderCountDataByType } = get();
setTypeLoading(true);
try {
const res = await fetchToBOrderCountByType(type, searchValuesToSub);
setOrderCountDataByType(type, res);
} catch (error) {
console.error(error);
} finally {
setTypeLoading(false);
}
},
onTabChange: async (tab) => {
const { setActiveTab, getToBOrderCount_type } = get();
setActiveTab(tab);
await getToBOrderCount_type(tab);
},
// sub
getToBOrderDetailByType: async (params, type, typeVal) => {
const { setTypeLoading } = get();
try {
setTypeLoading(true);
const res = await fetchToBOrderDetailByType(params, type, typeVal, 'detail');
set({ orderDetails: res });
} catch (error) {
console.error(error);
} finally {
setTypeLoading(false);
}
},
})),
{ name: 'ToBOrder' }
)
);
export default useToBOrderStore;

@ -2,9 +2,9 @@ import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import { groupsCTplus, groupsMappedByCode } from '../libs/ht';
import { fetchJSON } from '../utils/request';
import { fetchJSON } from '@haina/utils-request';
import { HT_HOST } from '../config';
import { fixTo2Decimals, fixTo4Decimals, groupBy, isEmpty, price_to_number } from '../utils/commons';
import { fixTo2Decimals, fixTo4Decimals, groupBy, isEmpty, price_to_number } from '@haina/utils-commons';
/** 火车票服务 */
export const SERVICETYPE_TRAINSBOOKING = '2';

Loading…
Cancel
Save