Compare commits
2
Commits
d98f8d7975
...
869d437485
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
869d437485 | ||
|
|
9805a371d8 |
No files matched your search
Generated
+119
-92
@@ -21,35 +21,36 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.1.0",
|
||||
"echarts": "^6.1.0",
|
||||
"esbuild": "^0.25.12",
|
||||
"vite": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||
"version": "7.29.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.11.tgz",
|
||||
"integrity": "sha512-snZbO7MEDOof+hvNa97EjEY7qD7h2kwvQr5AJC3ZhQudKW5dnej6k3kP9NQV88hJwaS8vDqRLFo9rivLEhle2g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
|
||||
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.29.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
|
||||
"integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
|
||||
"version": "7.29.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.12.tgz",
|
||||
"integrity": "sha512-WJ7JBp/Q4+z/ImydtI7jS6NVco5++632ExBemSX+Ysb+G1lCwQ5BVo3SY+jGGOcZXI4ZccbZSUSJLNBW6eEpJg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.29.0"
|
||||
"@babel/types": "^7.29.11"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
@@ -59,13 +60,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
||||
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
||||
"version": "7.29.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.11.tgz",
|
||||
"integrity": "sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.28.5"
|
||||
"@babel/helper-string-parser": "^7.29.11",
|
||||
"@babel/helper-validator-identifier": "^7.29.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -676,9 +677,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
|
||||
"integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@juggle/resize-observer": {
|
||||
@@ -1137,53 +1138,53 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
"version": "3.5.33",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.33.tgz",
|
||||
"integrity": "sha512-3PZLQwFw4Za3TC8t0FvTy3wI16Kt+pmwcgNZca4Pj9iWL2E72a/gZlpBtAJvEdDMdCxdG/qq0C7PN0bsJuv0Rw==",
|
||||
"version": "3.5.44",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.44.tgz",
|
||||
"integrity": "sha512-k34nCytbK8PQ1gR2mGROYOw6Pnv/mumqIdrZ3x2tbyXZDWm7ViKGLTfdB+DRrWqEQsLgGJvn0WN9jBif5zCTLQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.29.2",
|
||||
"@vue/shared": "3.5.33",
|
||||
"@babel/parser": "^7.29.9",
|
||||
"@vue/shared": "3.5.44",
|
||||
"entities": "^7.0.1",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.2.1"
|
||||
"source-map-js": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-dom": {
|
||||
"version": "3.5.33",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.33.tgz",
|
||||
"integrity": "sha512-PXq0yrfCLzzL07rbXO4awtXY1Z06LG2eu6Adg3RJFa/j3Cii217XxxLXG22N330gw7GmALCY0Z8RgXEviwgpjA==",
|
||||
"version": "3.5.44",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.44.tgz",
|
||||
"integrity": "sha512-W5AoBFWdw1lQAKiHowkfgFKFab82oSRT6sGsPfw/q1GRjsy/ZJFLHGhJyBRIFN9APLTUduDH9U2JAiPOIyKuYg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-core": "3.5.33",
|
||||
"@vue/shared": "3.5.33"
|
||||
"@vue/compiler-core": "3.5.44",
|
||||
"@vue/shared": "3.5.44"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc": {
|
||||
"version": "3.5.33",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.33.tgz",
|
||||
"integrity": "sha512-UTUvRO9cY+rROrx/pvN9P5Z7FgA6QGfokUCfhQE4EnmUj3rVnK+CHI0LsEO1pg+I7//iRYMUfcNcCPe7tg0CoA==",
|
||||
"version": "3.5.44",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.44.tgz",
|
||||
"integrity": "sha512-dzHmQ4jXTpdr5FjIQUeykVakN/uzqFbUo9/C5ikBFg3a3PeM6uRxeczx1ZbXBML0/zJstEh3ygYipzR5Z7b2Pw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.29.2",
|
||||
"@vue/compiler-core": "3.5.33",
|
||||
"@vue/compiler-dom": "3.5.33",
|
||||
"@vue/compiler-ssr": "3.5.33",
|
||||
"@vue/shared": "3.5.33",
|
||||
"@babel/parser": "^7.29.9",
|
||||
"@vue/compiler-core": "3.5.44",
|
||||
"@vue/compiler-dom": "3.5.44",
|
||||
"@vue/compiler-ssr": "3.5.44",
|
||||
"@vue/shared": "3.5.44",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.21",
|
||||
"postcss": "^8.5.10",
|
||||
"source-map-js": "^1.2.1"
|
||||
"postcss": "^8.5.29",
|
||||
"source-map-js": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-ssr": {
|
||||
"version": "3.5.33",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.33.tgz",
|
||||
"integrity": "sha512-IErjYdnj1qIupG5xxiVIYiiRvDhGWV4zuh/RCrwfYpuL+HWQzeU6lCk/nF9r7olWMnjKxCAkOctT2qFWFkzb1A==",
|
||||
"version": "3.5.44",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.44.tgz",
|
||||
"integrity": "sha512-xr5gb1JVHlfXJwSlRz1FeP6uxoazdzHuMTVLE7up+4Vrqxk219CvxLPxD9NXBt47yvZW7+p4DXfyp4O1fkByIA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.33",
|
||||
"@vue/shared": "3.5.33"
|
||||
"@vue/compiler-dom": "3.5.44",
|
||||
"@vue/shared": "3.5.44"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/devtools-api": {
|
||||
@@ -1193,53 +1194,51 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
"version": "3.5.33",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.33.tgz",
|
||||
"integrity": "sha512-p8UfIqyIhb0rYGlSgSBV+lPhF2iUSBcRy7enhTmPqKWadHy9kcOFYF1AejYBP9P+avnd3OBbD49DU4pLWX/94A==",
|
||||
"version": "3.5.44",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.44.tgz",
|
||||
"integrity": "sha512-u+QJdKeBBEADkPdevsL+6E12QlYVfBD8Zcw5qjDQKtamI6l14KyqazJMobhCKelhWN5zpyKUiEDoTyHE93cqWw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.5.33"
|
||||
"@vue/shared": "3.5.44"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-core": {
|
||||
"version": "3.5.33",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.33.tgz",
|
||||
"integrity": "sha512-UpFF45RI9//a7rvq7RdOQblb4tup7hHG9QsmIrxkFQLzQ7R8/iNQ5LE15NhLZ1/WcHMU2b47u6P33CPUelHyIQ==",
|
||||
"version": "3.5.44",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.44.tgz",
|
||||
"integrity": "sha512-uEa/ndwjhrrhnOfb2p+sZy6zeoamJOISqaLSJGk/8R6nABT1ZldpzcJoPzKj+zHVomVBPcmQYyC2r4LtvDV6CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.33",
|
||||
"@vue/shared": "3.5.33"
|
||||
"@vue/reactivity": "3.5.44",
|
||||
"@vue/shared": "3.5.44"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-dom": {
|
||||
"version": "3.5.33",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.33.tgz",
|
||||
"integrity": "sha512-IOxMsAOwquhfITgmOgaPYl7/j8gKUxUFoflRc+u4LxyD3+783xne8vNta1PONVCvCV9A0w7hkyEepINDqfO0tw==",
|
||||
"version": "3.5.44",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.44.tgz",
|
||||
"integrity": "sha512-3fc4ARcXVhVBaJ3ujWiNZJARD6pkyRcwG+ZYhMzjKtKUp7MAY0HLwr7r3Smep3Li6ZaWT6ewdmx8XqdaD5oNZg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.33",
|
||||
"@vue/runtime-core": "3.5.33",
|
||||
"@vue/shared": "3.5.33",
|
||||
"@vue/reactivity": "3.5.44",
|
||||
"@vue/runtime-core": "3.5.44",
|
||||
"@vue/shared": "3.5.44",
|
||||
"csstype": "^3.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/server-renderer": {
|
||||
"version": "3.5.33",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.33.tgz",
|
||||
"integrity": "sha512-0xylq/8/h44lVG0pZFknv1XIdEgymq2E9n59uTWJBG+dIgiT0TMCSsxrN7nO16Z0MU0MPjFcguBbZV8Itk52Hw==",
|
||||
"version": "3.5.44",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.44.tgz",
|
||||
"integrity": "sha512-R4S1UoH4h5Frrauv8Z5XE5TxV9oBiUSgWIC4TyVK1FK+RocRnOLwFA5Xcmo2011RlgJp+AbZCnw6afrhlgRuUQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-ssr": "3.5.33",
|
||||
"@vue/shared": "3.5.33"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "3.5.33"
|
||||
"@vue/compiler-ssr": "3.5.44",
|
||||
"@vue/runtime-dom": "3.5.44",
|
||||
"@vue/shared": "3.5.44"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/shared": {
|
||||
"version": "3.5.33",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.33.tgz",
|
||||
"integrity": "sha512-5vR2QIlmaLG77Ygd4pMP6+SGQ5yox9VhtnbDWTy9DzMzdmeLxZ1QqxrywEZ9sa1AVubfIJyaCG3ytyWU81ufcQ==",
|
||||
"version": "3.5.44",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.44.tgz",
|
||||
"integrity": "sha512-H82XLH0DjmSOdbEwO15x3QTo1f5PWEtv237eLpYKc0mqLu+B8hnq16lFWmuBmNurkeiG3wr+oM1FhOll/YVFRg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/async-validator": {
|
||||
@@ -1310,6 +1309,17 @@
|
||||
"date-fns": "^3.0.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/echarts": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/echarts/-/echarts-6.1.0.tgz",
|
||||
"integrity": "sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "2.3.0",
|
||||
"zrender": "6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
|
||||
@@ -1472,9 +1482,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.12",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
||||
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
|
||||
"version": "3.3.20",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
|
||||
"integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -1531,9 +1541,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.13",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz",
|
||||
"integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==",
|
||||
"version": "8.5.32",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz",
|
||||
"integrity": "sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -1550,9 +1560,9 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"nanoid": "^3.3.20",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
"source-map-js": "^1.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
@@ -1616,9 +1626,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
|
||||
"integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -1653,6 +1663,13 @@
|
||||
"integrity": "sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
||||
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/vdirs": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/vdirs/-/vdirs-0.1.8.tgz",
|
||||
@@ -1666,9 +1683,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.4.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz",
|
||||
"integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
|
||||
"version": "6.4.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.5.tgz",
|
||||
"integrity": "sha512-4enwjFfi4IwtEh0w+jPzpLVOFQiPHmmzPxcHXQOyEur47o6u0jE573DmGpjA+7irXoMLYRZD5ZaFUEneS3abPg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1753,16 +1770,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.5.33",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.33.tgz",
|
||||
"integrity": "sha512-1AgChhx5w3ALgT4oK3acm2Es/7jyZhWSVUfs3rOBlGQC0rjEDkS7G4lWlJJGGNQD+BV3reCwbQrOe1mPNwKHBQ==",
|
||||
"version": "3.5.44",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.44.tgz",
|
||||
"integrity": "sha512-dvvmaOCG7lAMkJTPqPwWeGMsPS6DIsFsfZDUgBp9V6mHf9b3+/wyVwYR4Es+WV6uJC0FKRxp81Obcq0ZwUcGFQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.33",
|
||||
"@vue/compiler-sfc": "3.5.33",
|
||||
"@vue/runtime-dom": "3.5.33",
|
||||
"@vue/server-renderer": "3.5.33",
|
||||
"@vue/shared": "3.5.33"
|
||||
"@vue/compiler-dom": "3.5.44",
|
||||
"@vue/compiler-sfc": "3.5.44",
|
||||
"@vue/runtime-dom": "3.5.44",
|
||||
"@vue/server-renderer": "3.5.44",
|
||||
"@vue/shared": "3.5.44"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "*"
|
||||
@@ -1849,6 +1866,16 @@
|
||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/zrender": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/zrender/-/zrender-6.1.0.tgz",
|
||||
"integrity": "sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"tslib": "2.3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
{"name":"aksrt-admin","version":"1.0.0","private":true,"type":"module","scripts":{"dev":"vite","build":"vite build && node extract-naive-styles.mjs","build:public":"node ../../tools/build-public-assets.mjs","preview":"vite preview"},"dependencies":{"@codemirror/lang-css":"^6.3.1","@codemirror/lang-html":"^6.4.11","@codemirror/lang-javascript":"^6.2.5","@codemirror/theme-one-dark":"^6.1.3","codemirror":"^6.0.2","naive-ui":"^2.40.0","pinia":"^2.2.0","vue":"^3.5.0","vue-router":"^4.4.0","vuedraggable":"^4.1.0"},"devDependencies":{"@vitejs/plugin-vue":"^5.1.0","esbuild":"^0.25.12","vite":"^6.0.0"}}
|
||||
{"name":"aksrt-admin","version":"1.0.0","private":true,"type":"module","scripts":{"dev":"vite","build":"vite build && node extract-naive-styles.mjs","build:public":"node ../../tools/build-public-assets.mjs","build:pay-charts":"node ../../tools/build-pay-charts.mjs","preview":"vite preview"},"dependencies":{"@codemirror/lang-css":"^6.3.1","@codemirror/lang-html":"^6.4.11","@codemirror/lang-javascript":"^6.2.5","@codemirror/theme-one-dark":"^6.1.3","codemirror":"^6.0.2","naive-ui":"^2.40.0","pinia":"^2.2.0","vue":"^3.5.0","vue-router":"^4.4.0","vuedraggable":"^4.1.0"},"devDependencies":{"@vitejs/plugin-vue":"^5.1.0","echarts":"^6.1.0","esbuild":"^0.25.12","vite":"^6.0.0"},"allowScripts":{"esbuild@0.25.12":true,"vue-demi@0.14.10":true}}
|
||||
@@ -516,31 +516,6 @@ a.aksrt-native-stat:focus { outline: 2px solid color-mix(in srgb, var(--an-tone)
|
||||
.aksrt-native-callout.is-warning { background: #fff8eb; border-left-color: var(--an-warning); }
|
||||
.aksrt-native-callout.is-danger { background: #fdf0f2; border-left-color: var(--an-danger); }
|
||||
|
||||
/* 快捷入口网格 */
|
||||
.aksrt-native-links {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 10px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.aksrt-native-links li { margin: 0; }
|
||||
.aksrt-native-links a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 14px;
|
||||
color: var(--an-text);
|
||||
background: var(--an-bg-soft);
|
||||
border-radius: var(--an-radius-sm);
|
||||
text-decoration: none;
|
||||
transition: background .15s, color .15s;
|
||||
}
|
||||
.aksrt-native-links a:hover { color: var(--an-primary); background: var(--an-primary-soft); }
|
||||
.aksrt-native-links a::after { content: "\f345"; margin-left: auto; color: var(--an-muted); font: normal 14px/1 dashicons; }
|
||||
.aksrt-native-links .dashicons { color: var(--an-primary); }
|
||||
|
||||
/* ---------- 分页条(AdminListPagination / paginate_links) ---------- */
|
||||
.aksrt-native .tablenav { height: auto; margin: 0; padding: 12px 20px; }
|
||||
.aksrt-native .aksrt-native-card .tablenav.bottom { border-top: 1px solid var(--an-border-soft); }
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
/* 支付中心概览(aksrt_pay):今日实时 + 筛选条 + 指标卡 + ECharts 图表 + 转化与退款 + 热销排行 + 待办 + 快捷入口。
|
||||
* 复用 native-ui.css 的 --an-* 令牌,规则限定在本页类名下。图表由 assets/js/admin-pay-overview.js 绘制。 */
|
||||
.aksrt-po-page [hidden] { display: none !important; }
|
||||
|
||||
/* ---------- 今日实时 ---------- */
|
||||
.aksrt-po-live {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px 24px;
|
||||
margin: 0 0 16px;
|
||||
padding: 14px 20px;
|
||||
background: var(--an-card);
|
||||
border-radius: var(--an-radius);
|
||||
box-shadow: var(--an-shadow);
|
||||
}
|
||||
.aksrt-po-live__main { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; }
|
||||
.aksrt-po-live__dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--an-primary);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 0 4px var(--an-primary-soft);
|
||||
animation: aksrt-po-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes aksrt-po-pulse { 50% { box-shadow: 0 0 0 7px transparent; } }
|
||||
@media (prefers-reduced-motion: reduce) { .aksrt-po-live__dot { animation: none; } }
|
||||
.aksrt-po-live__label { color: var(--an-muted); }
|
||||
.aksrt-po-live__value { color: var(--an-text); font-size: 22px; font-weight: 600; font-variant-numeric: tabular-nums; }
|
||||
.aksrt-po-live__meta { display: flex; flex-wrap: wrap; gap: 4px 16px; color: var(--an-muted); font-size: 12px; }
|
||||
|
||||
/* ---------- 筛选条 ---------- */
|
||||
.aksrt-po-toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 10px 16px;
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
.aksrt-po-seg {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px;
|
||||
padding: 3px;
|
||||
background: var(--an-card);
|
||||
border-radius: var(--an-radius-sm);
|
||||
box-shadow: var(--an-shadow);
|
||||
}
|
||||
.aksrt-po-seg button {
|
||||
padding: 5px 12px;
|
||||
color: var(--an-text-2);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: background .15s, color .15s;
|
||||
}
|
||||
.aksrt-po-seg button:hover { color: var(--an-primary); }
|
||||
.aksrt-po-seg button[aria-pressed="true"] { color: var(--an-primary); font-weight: 600; background: var(--an-primary-soft); }
|
||||
.aksrt-po-seg button:focus-visible { outline: 2px solid var(--an-primary); outline-offset: 1px; }
|
||||
.aksrt-po-range { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0; color: var(--an-muted); }
|
||||
.aksrt-po-range label { display: inline-flex; margin: 0; }
|
||||
.aksrt-po-range-label { color: var(--an-muted); font-size: 12px; }
|
||||
|
||||
/* ---------- 提示 ---------- */
|
||||
.aksrt-po-notice {
|
||||
margin: 0 0 16px;
|
||||
padding: 10px 14px;
|
||||
color: var(--an-text-2);
|
||||
font-size: 12px;
|
||||
background: #fff8eb;
|
||||
border-left: 3px solid var(--an-warning);
|
||||
border-radius: var(--an-radius-sm);
|
||||
}
|
||||
.aksrt-po-page .aksrt-po-error { margin: 0 0 16px; }
|
||||
|
||||
/* ---------- 指标卡 ---------- */
|
||||
.aksrt-po-kpis {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
.aksrt-po-kpi {
|
||||
--po-tone: var(--an-primary);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
padding: 16px 18px 14px;
|
||||
background: var(--an-card);
|
||||
border-radius: var(--an-radius);
|
||||
box-shadow: var(--an-shadow);
|
||||
overflow: hidden;
|
||||
}
|
||||
.aksrt-po-kpi::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: var(--po-tone); opacity: .85; }
|
||||
.aksrt-po-kpi--success { --po-tone: var(--an-primary); }
|
||||
.aksrt-po-kpi--info { --po-tone: var(--an-info); }
|
||||
.aksrt-po-kpi--warning { --po-tone: var(--an-warning); }
|
||||
.aksrt-po-kpi--danger { --po-tone: var(--an-danger); }
|
||||
.aksrt-po-kpi--primary { --po-tone: #8a63d2; }
|
||||
.aksrt-po-kpi__label { color: var(--an-muted); font-size: 13px; }
|
||||
.aksrt-po-kpi__value {
|
||||
color: var(--an-text);
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
font-variant-numeric: tabular-nums;
|
||||
word-break: break-all;
|
||||
}
|
||||
.aksrt-po-kpi__foot { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; font-size: 12px; }
|
||||
.aksrt-po-kpi__meta { color: var(--an-muted); }
|
||||
|
||||
.aksrt-po-delta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 1px 6px;
|
||||
font-size: 12px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.aksrt-po-delta.is-up { color: var(--an-primary); background: var(--an-primary-soft); }
|
||||
.aksrt-po-delta.is-down { color: var(--an-danger); background: #fdf0f2; }
|
||||
.aksrt-po-delta.is-flat { color: var(--an-muted); background: var(--an-bg-soft); }
|
||||
|
||||
/* ---------- 栅格 / 加载态 ---------- */
|
||||
.aksrt-po-body { transition: opacity .15s; }
|
||||
.aksrt-po-body.is-loading { opacity: .55; pointer-events: none; }
|
||||
.aksrt-po-grid {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
.aksrt-po-grid--main { grid-template-columns: minmax(0, 2fr) minmax(300px, 1fr); }
|
||||
.aksrt-po-grid > .aksrt-native-card { margin: 0; }
|
||||
.aksrt-po-card { display: flex; flex-direction: column; min-width: 0; }
|
||||
.aksrt-po-card > .aksrt-native-card__body { flex: 1; }
|
||||
|
||||
/* ---------- 图表 ---------- */
|
||||
.aksrt-po-chart { width: 100%; min-width: 0; }
|
||||
.aksrt-po-chart--lg { height: 340px; }
|
||||
.aksrt-po-chart--md { height: 280px; }
|
||||
|
||||
/* ---------- 转化与退款 ---------- */
|
||||
.aksrt-po-funnel { display: flex; flex-direction: column; gap: 16px; margin: 0; padding: 0; list-style: none; }
|
||||
.aksrt-po-funnel li { display: flex; flex-direction: column; gap: 6px; margin: 0; }
|
||||
.aksrt-po-funnel__head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
|
||||
.aksrt-po-funnel__label { color: var(--an-text-2); }
|
||||
.aksrt-po-funnel__value { color: var(--an-text); font-size: 18px; font-weight: 600; font-variant-numeric: tabular-nums; }
|
||||
.aksrt-po-funnel__bar { display: block; height: 6px; background: var(--an-bg-soft); border-radius: 3px; overflow: hidden; }
|
||||
.aksrt-po-funnel__bar i { display: block; width: 0; height: 100%; background: var(--an-primary); border-radius: 3px; transition: width .3s; }
|
||||
.aksrt-po-funnel li[data-metric="close_rate"] .aksrt-po-funnel__bar i { background: var(--an-warning); }
|
||||
.aksrt-po-funnel li[data-metric="refund_rate"] .aksrt-po-funnel__bar i { background: var(--an-danger); }
|
||||
.aksrt-po-funnel li[data-metric="created_count"] .aksrt-po-funnel__bar { display: none; }
|
||||
.aksrt-po-funnel__hint { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; color: var(--an-muted); font-size: 12px; }
|
||||
|
||||
/* ---------- 热销排行 ---------- */
|
||||
.aksrt-po-top .column-rank { width: 44px; text-align: center; }
|
||||
.aksrt-po-top .column-num { width: 110px; text-align: right; }
|
||||
.aksrt-po-top .column-share { width: 160px; }
|
||||
.aksrt-po-top td { vertical-align: middle; }
|
||||
.aksrt-po-top td.column-num { font-variant-numeric: tabular-nums; }
|
||||
.aksrt-po-top td a { color: var(--an-text); font-weight: 500; text-decoration: none; }
|
||||
.aksrt-po-top td a:hover { color: var(--an-primary); }
|
||||
.aksrt-po-rank {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
color: var(--an-muted);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
background: var(--an-bg-soft);
|
||||
border-radius: 6px;
|
||||
}
|
||||
.aksrt-po-rank.is-top { color: #fff; background: var(--an-primary); }
|
||||
.aksrt-po-share { display: flex; align-items: center; gap: 8px; color: var(--an-muted); font-size: 12px; font-variant-numeric: tabular-nums; }
|
||||
.aksrt-po-share > span { flex: 1; height: 6px; background: var(--an-bg-soft); border-radius: 3px; overflow: hidden; }
|
||||
.aksrt-po-share > span > i { display: block; height: 100%; background: var(--an-primary); border-radius: 3px; }
|
||||
.aksrt-po-top__empty { padding: 32px 12px !important; color: var(--an-muted); text-align: center; }
|
||||
|
||||
/* ---------- 待办 ---------- */
|
||||
.aksrt-po-todo { display: flex; flex-direction: column; gap: 6px; margin: 0; padding: 0; list-style: none; }
|
||||
.aksrt-po-todo li { margin: 0; }
|
||||
.aksrt-po-todo__item {
|
||||
--po-tone: var(--an-muted);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
color: var(--an-text-2);
|
||||
background: var(--an-bg-soft);
|
||||
border-radius: var(--an-radius-sm);
|
||||
text-decoration: none;
|
||||
transition: background .15s, color .15s;
|
||||
}
|
||||
.aksrt-po-todo__item:hover,
|
||||
.aksrt-po-todo__item:focus { color: var(--an-text); background: color-mix(in srgb, var(--po-tone) 10%, #fff); }
|
||||
.aksrt-po-todo__item--danger { --po-tone: var(--an-danger); }
|
||||
.aksrt-po-todo__item--warning { --po-tone: var(--an-warning); }
|
||||
.aksrt-po-todo__item--info { --po-tone: var(--an-info); }
|
||||
.aksrt-po-todo__item--neutral { --po-tone: var(--an-muted); }
|
||||
.aksrt-po-todo__icon.dashicons {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: none;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
color: var(--po-tone);
|
||||
font-size: 16px;
|
||||
background: color-mix(in srgb, var(--po-tone) 12%, #fff);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.aksrt-po-todo__icon.dashicons:before { line-height: 30px; }
|
||||
.aksrt-po-todo__label { flex: 1; min-width: 0; }
|
||||
.aksrt-po-todo__value { color: var(--an-text); font-size: 15px; font-weight: 600; font-variant-numeric: tabular-nums; }
|
||||
.aksrt-po-todo__item--danger .aksrt-po-todo__value { color: var(--an-danger); }
|
||||
.aksrt-po-todo__item--warning .aksrt-po-todo__value { color: #c27c0e; }
|
||||
.aksrt-po-todo__item--neutral .aksrt-po-todo__value { color: var(--an-muted); }
|
||||
|
||||
/* ---------- 快捷入口 ---------- */
|
||||
.aksrt-po-links {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.aksrt-po-links li { margin: 0; }
|
||||
.aksrt-po-links a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
height: 100%;
|
||||
padding: 10px 12px;
|
||||
color: var(--an-text);
|
||||
border: 1px solid var(--an-border-soft);
|
||||
border-radius: var(--an-radius-sm);
|
||||
text-decoration: none;
|
||||
box-sizing: border-box;
|
||||
transition: border-color .15s, background .15s;
|
||||
}
|
||||
.aksrt-po-links a:hover,
|
||||
.aksrt-po-links a:focus { color: var(--an-text); background: var(--an-primary-soft); border-color: color-mix(in srgb, var(--an-primary) 30%, transparent); }
|
||||
.aksrt-po-links__icon.dashicons {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: none;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
color: var(--an-primary);
|
||||
font-size: 17px;
|
||||
background: var(--an-primary-soft);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.aksrt-po-links__icon.dashicons:before { line-height: 32px; }
|
||||
.aksrt-po-links__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
||||
.aksrt-po-links__text strong { font-size: 13px; font-weight: 600; }
|
||||
.aksrt-po-links__text span { color: var(--an-muted); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
|
||||
/* ---------- 响应式 ---------- */
|
||||
@media (max-width: 1440px) {
|
||||
.aksrt-po-kpis { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
}
|
||||
@media (max-width: 1080px) {
|
||||
.aksrt-po-grid--main { grid-template-columns: minmax(0, 1fr); }
|
||||
}
|
||||
@media (max-width: 782px) {
|
||||
.aksrt-po-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
|
||||
.aksrt-po-kpi { padding: 12px 14px; }
|
||||
.aksrt-po-kpi__value { font-size: 19px; }
|
||||
.aksrt-po-chart--lg { height: 280px; }
|
||||
.aksrt-po-chart--md { height: 240px; }
|
||||
.aksrt-po-top .column-share { display: none; }
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
/**
|
||||
* 支付中心概览:区间筛选 + 指标卡 + ECharts 图表(成交趋势 / 分类成交 / 支付方式)+ 转化与退款 + 热销排行。
|
||||
*
|
||||
* 首屏报表来自页面内 #aksrt-pay-overview-data(PayOverview 输出),之后切换区间 / 分类请求
|
||||
* GET aksrt/v1/admin/pay-overview(PayOverviewController)。ECharts 子集由
|
||||
* assets/js/runtime/libs/echarts-pay.min.js 提供(window.echarts)。仅本页入队。
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var root = document.querySelector('.aksrt-po-page');
|
||||
var dataEl = document.getElementById('aksrt-pay-overview-data');
|
||||
if (!root || !dataEl || !window.echarts) return;
|
||||
|
||||
var boot;
|
||||
try { boot = JSON.parse(dataEl.textContent || '{}'); } catch (e) { return; }
|
||||
|
||||
var echarts = window.echarts;
|
||||
var i18n = boot.i18n || {};
|
||||
var currency = boot.currency || '';
|
||||
var COLORS = { paid: '#18a058', net: '#2080f0', refund: '#d03050', count: '#f0a020' };
|
||||
var PALETTE = ['#18a058', '#2080f0', '#f0a020', '#8a63d2', '#86909c', '#13c2c2', '#eb2f96'];
|
||||
var TEXT = '#4e5969', MUTED = '#86909c', LINE = '#f0f1f4';
|
||||
|
||||
var state = { from: boot.initial.from, to: boot.initial.to, group: boot.initial.group || '', report: boot.initial.report };
|
||||
var charts = {};
|
||||
var reqSeq = 0;
|
||||
|
||||
/* ---------------- 格式化 ---------------- */
|
||||
|
||||
function money(v) {
|
||||
return currency + Number(v || 0).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
function int(v) { return Number(v || 0).toLocaleString(); }
|
||||
function compact(v) {
|
||||
var n = Math.abs(v);
|
||||
if (n >= 1e8) return (v / 1e8).toFixed(1).replace(/\.0$/, '') + '亿';
|
||||
if (n >= 1e4) return (v / 1e4).toFixed(1).replace(/\.0$/, '') + '万';
|
||||
return String(Math.round(v * 100) / 100);
|
||||
}
|
||||
function pct(v) { return v === null || v === undefined ? '—' : Number(v).toFixed(1) + '%'; }
|
||||
function shortLabel(label) { return label.length === 10 ? label.slice(5) : label; }
|
||||
|
||||
/** 环比:基期为 0 时,本期有数为「新增」,否则不可比。 */
|
||||
function deltaInfo(cur, prev) {
|
||||
cur = Number(cur || 0); prev = Number(prev || 0);
|
||||
if (prev <= 0) return cur > 0 ? { cls: 'is-up', text: i18n.new } : { cls: 'is-flat', text: '—' };
|
||||
var d = (cur - prev) / prev * 100;
|
||||
var cls = d > 0.05 ? 'is-up' : (d < -0.05 ? 'is-down' : 'is-flat');
|
||||
return { cls: cls, text: (d > 0.05 ? '↑' : (d < -0.05 ? '↓' : '')) + Math.abs(d).toFixed(1) + '%' };
|
||||
}
|
||||
|
||||
/* ---------------- 日期 ---------------- */
|
||||
|
||||
function parse(s) { var p = s.split('-'); return new Date(Date.UTC(+p[0], +p[1] - 1, +p[2])); }
|
||||
function fmt(d) { return d.toISOString().slice(0, 10); }
|
||||
function shift(s, days) { var d = parse(s); d.setUTCDate(d.getUTCDate() + days); return fmt(d); }
|
||||
function spanDays(a, b) { return Math.round((parse(b) - parse(a)) / 864e5) + 1; }
|
||||
|
||||
function presetRange(key) {
|
||||
var today = boot.today;
|
||||
if (key === 'this_month') return [today.slice(0, 8) + '01', today];
|
||||
if (key === 'last_month') {
|
||||
var first = parse(today.slice(0, 8) + '01');
|
||||
var lastEnd = new Date(first.getTime() - 864e5);
|
||||
return [fmt(lastEnd).slice(0, 8) + '01', fmt(lastEnd)];
|
||||
}
|
||||
var n = parseInt(key.slice(1), 10);
|
||||
return [shift(today, -(n - 1)), today];
|
||||
}
|
||||
|
||||
/* ---------------- 数据加载 ---------------- */
|
||||
|
||||
function setBusy(busy) {
|
||||
root.classList.toggle('is-loading', busy);
|
||||
var body = root.querySelector('[data-aksrt-po-body]');
|
||||
if (body) body.setAttribute('aria-busy', busy ? 'true' : 'false');
|
||||
}
|
||||
|
||||
function showError(msg) {
|
||||
var box = root.querySelector('[data-aksrt-po-error]');
|
||||
if (!box) return;
|
||||
box.hidden = !msg;
|
||||
box.querySelector('p').textContent = msg || '';
|
||||
}
|
||||
|
||||
function load(from, to, group) {
|
||||
if (spanDays(from, to) < 1 || spanDays(from, to) > boot.maxDays || to > boot.today) {
|
||||
showError(i18n.badRange);
|
||||
return;
|
||||
}
|
||||
var seq = ++reqSeq;
|
||||
var url = boot.rest.url + (boot.rest.url.indexOf('?') === -1 ? '?' : '&')
|
||||
+ 'from=' + encodeURIComponent(from) + '&to=' + encodeURIComponent(to) + '&group=' + encodeURIComponent(group);
|
||||
setBusy(true);
|
||||
fetch(url, { credentials: 'same-origin', headers: { 'X-WP-Nonce': boot.rest.nonce } })
|
||||
.then(function (res) { return res.json().then(function (json) { return { ok: res.ok, json: json }; }); })
|
||||
.then(function (r) {
|
||||
if (seq !== reqSeq) return;
|
||||
if (!r.ok || !r.json || !r.json.success) throw new Error((r.json && r.json.message) || i18n.loadError);
|
||||
state = { from: from, to: to, group: group, report: r.json.data };
|
||||
showError('');
|
||||
renderAll();
|
||||
})
|
||||
.catch(function (err) { if (seq === reqSeq) showError(err && err.message ? err.message : i18n.loadError); })
|
||||
.then(function () { if (seq === reqSeq) setBusy(false); });
|
||||
}
|
||||
|
||||
/* ---------------- 指标卡 / 转化 / 排行 / 提示 ---------------- */
|
||||
|
||||
function renderKpis(rep) {
|
||||
var t = rep.totals || {}, p = rep.previous || {};
|
||||
var defs = {
|
||||
paid_amount: [money, function () { return int(t.paid_count) + ' ' + i18n.unit; }],
|
||||
net_amount: [money, function () { return i18n.refund + ' ' + money(t.refund_amount); }],
|
||||
paid_count: [int, function () { return ''; }],
|
||||
buyers: [int, function () { return ''; }],
|
||||
avg_amount: [money, function () { return ''; }],
|
||||
refund_amount: [money, function () { return int(t.refund_count) + ' ' + i18n.unit; }],
|
||||
};
|
||||
root.querySelectorAll('[data-kpi]').forEach(function (card) {
|
||||
var key = card.getAttribute('data-kpi'), def = defs[key];
|
||||
if (!def) return;
|
||||
card.querySelector('[data-kpi-value]').textContent = def[0](t[key]);
|
||||
card.querySelector('[data-kpi-meta]').textContent = def[1]();
|
||||
var d = deltaInfo(t[key], p[key]);
|
||||
var el = card.querySelector('[data-kpi-delta]');
|
||||
// 退款上升是坏事:颜色反转。
|
||||
var cls = key === 'refund_amount' ? (d.cls === 'is-up' ? 'is-down' : (d.cls === 'is-down' ? 'is-up' : d.cls)) : d.cls;
|
||||
el.className = 'aksrt-po-delta ' + cls;
|
||||
el.textContent = i18n.basis + ' ' + d.text;
|
||||
});
|
||||
var label = root.querySelector('[data-aksrt-po-range-label]');
|
||||
if (label && rep.range) label.textContent = rep.range.from + ' ~ ' + rep.range.to + '(' + rep.range.days + ' 天)';
|
||||
}
|
||||
|
||||
function renderFunnel(rep) {
|
||||
var t = rep.totals || {}, p = rep.previous || {};
|
||||
root.querySelectorAll('[data-aksrt-po-funnel] [data-metric]').forEach(function (li) {
|
||||
var key = li.getAttribute('data-metric');
|
||||
var isCount = key === 'created_count';
|
||||
li.querySelector('[data-value]').textContent = isCount ? int(t[key]) : pct(t[key]);
|
||||
var bar = li.querySelector('[data-bar]');
|
||||
bar.style.width = isCount ? '100%' : Math.max(0, Math.min(100, Number(t[key] || 0))) + '%';
|
||||
var deltaEl = li.querySelector('[data-delta]');
|
||||
if (isCount) {
|
||||
var d = deltaInfo(t[key], p[key]);
|
||||
deltaEl.className = 'aksrt-po-delta ' + d.cls;
|
||||
deltaEl.textContent = i18n.basis + ' ' + d.text;
|
||||
} else if (p[key] !== null && p[key] !== undefined && t[key] !== null && t[key] !== undefined) {
|
||||
var diff = Number(t[key]) - Number(p[key]);
|
||||
var good = key === 'conversion' ? diff >= 0 : diff <= 0;
|
||||
deltaEl.className = 'aksrt-po-delta ' + (Math.abs(diff) < 0.05 ? 'is-flat' : (good ? 'is-up' : 'is-down'));
|
||||
deltaEl.textContent = i18n.basis + ' ' + (diff > 0 ? '+' : '') + diff.toFixed(1) + 'pt';
|
||||
} else {
|
||||
deltaEl.className = 'aksrt-po-delta is-flat';
|
||||
deltaEl.textContent = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderTop(rep) {
|
||||
var body = root.querySelector('[data-aksrt-po-top]');
|
||||
if (!body) return;
|
||||
body.textContent = '';
|
||||
var rows = rep.top || [];
|
||||
if (!rows.length) {
|
||||
var tr = body.insertRow(), td = tr.insertCell();
|
||||
td.colSpan = 5;
|
||||
td.className = 'aksrt-po-top__empty';
|
||||
td.textContent = i18n.noTop;
|
||||
return;
|
||||
}
|
||||
var total = rows.reduce(function (s, r) { return s + Number(r.amount || 0); }, 0) || 1;
|
||||
rows.forEach(function (r, i) {
|
||||
var tr = body.insertRow();
|
||||
var rank = tr.insertCell();
|
||||
rank.className = 'column-rank';
|
||||
rank.innerHTML = '<span class="aksrt-po-rank' + (i < 3 ? ' is-top' : '') + '"></span>';
|
||||
rank.firstChild.textContent = String(i + 1);
|
||||
var title = tr.insertCell();
|
||||
if (r.url) {
|
||||
var a = document.createElement('a');
|
||||
a.href = r.url;
|
||||
a.textContent = r.title;
|
||||
title.appendChild(a);
|
||||
} else {
|
||||
title.textContent = r.title;
|
||||
}
|
||||
var count = tr.insertCell(); count.className = 'column-num'; count.textContent = int(r.count);
|
||||
var amount = tr.insertCell(); amount.className = 'column-num'; amount.textContent = money(r.amount);
|
||||
var share = tr.insertCell(); share.className = 'column-share';
|
||||
var ratio = Number(r.amount || 0) / total * 100;
|
||||
share.innerHTML = '<span class="aksrt-po-share"><i></i></span><span></span>';
|
||||
share.querySelector('i').style.width = ratio.toFixed(1) + '%';
|
||||
share.lastChild.textContent = ratio.toFixed(1) + '%';
|
||||
});
|
||||
}
|
||||
|
||||
function renderExcluded(rep) {
|
||||
var box = root.querySelector('[data-aksrt-po-excluded]');
|
||||
if (!box) return;
|
||||
var list = rep.excluded || [];
|
||||
box.hidden = !list.length;
|
||||
box.textContent = list.length ? i18n.excluded + list.map(function (x) {
|
||||
return x.currency + ' ' + Number(x.amount).toFixed(2) + '(' + x.count + ' ' + i18n.unit + ')';
|
||||
}).join(';') : '';
|
||||
}
|
||||
|
||||
/* ---------------- 图表 ---------------- */
|
||||
|
||||
function chart(key) {
|
||||
var host = root.querySelector('[data-aksrt-po-chart="' + key + '"]');
|
||||
if (!host) return null;
|
||||
if (!charts[key]) charts[key] = echarts.init(host, null, { renderer: 'svg' });
|
||||
return charts[key];
|
||||
}
|
||||
|
||||
function baseAxis(labels) {
|
||||
return {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: labels,
|
||||
axisLine: { lineStyle: { color: '#e5e6eb' } },
|
||||
axisTick: { show: false },
|
||||
axisLabel: { color: MUTED, hideOverlap: true, formatter: shortLabel },
|
||||
};
|
||||
}
|
||||
function valueAxis(formatter, show) {
|
||||
return {
|
||||
type: 'value',
|
||||
show: show !== false,
|
||||
splitLine: { lineStyle: { color: LINE } },
|
||||
axisLabel: { color: MUTED, formatter: formatter },
|
||||
minInterval: 1,
|
||||
};
|
||||
}
|
||||
function tooltip(formatters) {
|
||||
return {
|
||||
trigger: 'axis',
|
||||
confine: true,
|
||||
axisPointer: { type: 'line', lineStyle: { color: '#c9cdd4', type: 'dashed' } },
|
||||
backgroundColor: '#fff',
|
||||
borderColor: '#e5e6eb',
|
||||
textStyle: { color: TEXT, fontSize: 12 },
|
||||
extraCssText: 'box-shadow:0 8px 24px rgba(15,23,42,.12);border-radius:8px;',
|
||||
valueFormatter: formatters,
|
||||
};
|
||||
}
|
||||
function area(color) {
|
||||
return {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: color + '38' },
|
||||
{ offset: 1, color: color + '00' },
|
||||
]),
|
||||
};
|
||||
}
|
||||
function zoom(n) {
|
||||
return n > 45 ? [{ type: 'inside', start: 0, end: 100 }, { type: 'slider', height: 18, bottom: 6, borderColor: 'transparent', fillerColor: 'rgba(24,160,88,.12)', handleSize: '80%', showDetail: false }] : [];
|
||||
}
|
||||
function emptyGraphic(isEmpty) {
|
||||
return isEmpty ? [{ type: 'text', left: 'center', top: 'middle', silent: true, style: { text: i18n.empty, fill: MUTED, fontSize: 13 } }] : [];
|
||||
}
|
||||
|
||||
function renderTrend(rep) {
|
||||
var c = chart('trend');
|
||||
if (!c) return;
|
||||
var s = rep.series || { labels: [], paid: [], net: [], refund: [], count: [] };
|
||||
var n = s.labels.length;
|
||||
var isEmpty = !s.paid.some(function (v) { return v > 0; });
|
||||
var smoothLine = function (name, data, color, extra) {
|
||||
return Object.assign({ name: name, type: 'line', data: data, smooth: 0.35, showSymbol: false, symbolSize: 6, color: color, lineStyle: { width: 2.25 }, emphasis: { focus: 'series' } }, extra || {});
|
||||
};
|
||||
c.setOption({
|
||||
aria: { enabled: true },
|
||||
color: [COLORS.paid, COLORS.net, COLORS.refund, COLORS.count],
|
||||
grid: { left: 8, right: 8, top: 40, bottom: n > 45 ? 36 : 8, containLabel: true },
|
||||
legend: { top: 0, left: 0, icon: 'roundRect', itemWidth: 12, itemHeight: 4, textStyle: { color: TEXT }, selected: { [i18n.refund]: true } },
|
||||
tooltip: tooltip(function (v) { return typeof v === 'number' && !Number.isInteger(v) ? money(v) : v; }),
|
||||
xAxis: baseAxis(s.labels),
|
||||
yAxis: [valueAxis(compact), valueAxis(function (v) { return v; })],
|
||||
dataZoom: zoom(n),
|
||||
graphic: emptyGraphic(isEmpty),
|
||||
series: [
|
||||
smoothLine(i18n.paid, s.paid, COLORS.paid, { areaStyle: area(COLORS.paid), tooltip: { valueFormatter: money } }),
|
||||
smoothLine(i18n.net, s.net, COLORS.net, { tooltip: { valueFormatter: money } }),
|
||||
smoothLine(i18n.refund, s.refund, COLORS.refund, { lineStyle: { width: 1.5, type: 'dashed' }, tooltip: { valueFormatter: money } }),
|
||||
{
|
||||
name: i18n.count, type: 'line', yAxisIndex: 1, data: s.count, smooth: false,
|
||||
showSymbol: n <= 31, symbol: 'circle', symbolSize: 5, color: COLORS.count,
|
||||
lineStyle: { width: 1.5 }, itemStyle: { borderColor: '#fff', borderWidth: 1.5 },
|
||||
tooltip: { valueFormatter: function (v) { return int(v) + ' ' + i18n.unit; } },
|
||||
},
|
||||
],
|
||||
}, true);
|
||||
}
|
||||
|
||||
function renderGroups(rep) {
|
||||
var c = chart('groups');
|
||||
if (!c) return;
|
||||
var s = rep.series || { labels: [], groups: [] };
|
||||
var groups = s.groups || [];
|
||||
var n = s.labels.length;
|
||||
c.setOption({
|
||||
aria: { enabled: true },
|
||||
color: PALETTE,
|
||||
grid: { left: 8, right: 8, top: 40, bottom: n > 45 ? 36 : 8, containLabel: true },
|
||||
legend: { top: 0, left: 0, icon: 'roundRect', itemWidth: 12, itemHeight: 4, textStyle: { color: TEXT } },
|
||||
tooltip: tooltip(money),
|
||||
xAxis: baseAxis(s.labels),
|
||||
yAxis: valueAxis(compact),
|
||||
dataZoom: zoom(n),
|
||||
graphic: emptyGraphic(!groups.length),
|
||||
series: groups.map(function (g) {
|
||||
return { name: g.name, type: 'line', data: g.data, smooth: 0.35, showSymbol: false, lineStyle: { width: 2 }, emphasis: { focus: 'series' } };
|
||||
}),
|
||||
}, true);
|
||||
}
|
||||
|
||||
function renderMethods(rep) {
|
||||
var c = chart('methods');
|
||||
if (!c) return;
|
||||
var list = rep.methods || [];
|
||||
var total = list.reduce(function (s, m) { return s + Number(m.amount || 0); }, 0);
|
||||
c.setOption({
|
||||
aria: { enabled: true },
|
||||
color: PALETTE,
|
||||
tooltip: {
|
||||
trigger: 'item', confine: true, backgroundColor: '#fff', borderColor: '#e5e6eb', textStyle: { color: TEXT, fontSize: 12 },
|
||||
formatter: function (p) { return p.marker + p.name + '<br>' + money(p.value) + '(' + p.percent + '%,' + int(p.data.count) + ' ' + i18n.unit + ')'; },
|
||||
},
|
||||
legend: { bottom: 0, left: 'center', icon: 'circle', itemWidth: 8, itemHeight: 8, textStyle: { color: TEXT } },
|
||||
graphic: list.length ? [{
|
||||
type: 'group', left: 'center', top: '36%', silent: true,
|
||||
children: [
|
||||
{ type: 'text', style: { text: i18n.total, fill: MUTED, fontSize: 12, align: 'center' }, left: 'center', top: 0 },
|
||||
{ type: 'text', style: { text: money(total), fill: '#1f2329', fontSize: 16, fontWeight: 600, align: 'center' }, left: 'center', top: 18 },
|
||||
],
|
||||
}] : emptyGraphic(true),
|
||||
series: [{
|
||||
type: 'pie', radius: ['52%', '72%'], center: ['50%', '44%'], avoidLabelOverlap: true,
|
||||
itemStyle: { borderColor: '#fff', borderWidth: 2, borderRadius: 4 },
|
||||
label: { show: false }, labelLine: { show: false },
|
||||
emphasis: { scale: true, scaleSize: 4 },
|
||||
data: list.map(function (m) { return { name: m.name, value: m.amount, count: m.count }; }),
|
||||
}],
|
||||
}, true);
|
||||
}
|
||||
|
||||
function renderAll() {
|
||||
var rep = state.report || {};
|
||||
renderKpis(rep);
|
||||
renderFunnel(rep);
|
||||
renderTop(rep);
|
||||
renderExcluded(rep);
|
||||
renderTrend(rep);
|
||||
renderGroups(rep);
|
||||
renderMethods(rep);
|
||||
}
|
||||
|
||||
/* ---------------- 交互 ---------------- */
|
||||
|
||||
var presets = root.querySelector('[data-aksrt-po-presets]');
|
||||
var form = root.querySelector('[data-aksrt-po-range]');
|
||||
|
||||
function syncForm() {
|
||||
if (!form) return;
|
||||
form.elements.from.value = state.from;
|
||||
form.elements.to.value = state.to;
|
||||
form.elements.group.value = state.group;
|
||||
}
|
||||
function pressPreset(btn) {
|
||||
if (!presets) return;
|
||||
presets.querySelectorAll('button').forEach(function (b) { b.setAttribute('aria-pressed', b === btn ? 'true' : 'false'); });
|
||||
}
|
||||
|
||||
if (presets) {
|
||||
presets.addEventListener('click', function (e) {
|
||||
var btn = e.target.closest('button[data-preset]');
|
||||
if (!btn) return;
|
||||
var r = presetRange(btn.getAttribute('data-preset'));
|
||||
pressPreset(btn);
|
||||
state.from = r[0]; state.to = r[1];
|
||||
syncForm();
|
||||
load(r[0], r[1], form ? form.elements.group.value : state.group);
|
||||
});
|
||||
}
|
||||
if (form) {
|
||||
form.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
var from = form.elements.from.value, to = form.elements.to.value;
|
||||
if (!from || !to || from > to) { showError(i18n.badRange); return; }
|
||||
pressPreset(null);
|
||||
load(from, to, form.elements.group.value);
|
||||
});
|
||||
form.elements.group.addEventListener('change', function () { load(state.from, state.to, form.elements.group.value); });
|
||||
}
|
||||
|
||||
var resizeTimer;
|
||||
window.addEventListener('resize', function () {
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(function () { Object.keys(charts).forEach(function (k) { charts[k].resize(); }); }, 120);
|
||||
});
|
||||
|
||||
syncForm();
|
||||
renderAll();
|
||||
})();
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
(()=>{var lM=Object.defineProperty;var er=(t,e)=>{for(var r in e)lM(t,r,{get:e[r],enumerable:!0})};var ay={};er(ay,{Axis:()=>Kp,ChartView:()=>ft,ComponentModel:()=>Se,ComponentView:()=>et,List:()=>Ss,Model:()=>Ce,PRIORITY:()=>vg,SeriesModel:()=>Tt,color:()=>of,connect:()=>UR,dataTool:()=>ZR,dependencies:()=>SR,disConnect:()=>FR,disconnect:()=>uw,dispose:()=>VR,env:()=>oe,extendChartView:()=>Sk,extendComponentModel:()=>yk,extendComponentView:()=>_k,extendSeriesModel:()=>xk,format:()=>Jg,getCoordinateSystemDimensions:()=>HR,getInstanceByDom:()=>gg,getInstanceById:()=>GR,getMap:()=>XR,graphic:()=>Qg,helper:()=>Hg,init:()=>zR,innerDrawElementOnCanvas:()=>lp,matrix:()=>wi,number:()=>Kg,parseGeoJSON:()=>Zp,parseGeoJson:()=>Zp,registerAction:()=>mi,registerCoordinateSystem:()=>Sg,registerCustomSeries:()=>YR,registerLayout:()=>bg,registerLoading:()=>xp,registerLocale:()=>dc,registerMap:()=>wg,registerPostInit:()=>_g,registerPostUpdate:()=>xg,registerPreprocessor:()=>yp,registerProcessor:()=>_p,registerTheme:()=>yg,registerTransform:()=>Cg,registerUpdateLifecycle:()=>eu,registerVisual:()=>Ui,setCanvasCreator:()=>WR,setPlatformAPI:()=>Vu,throttle:()=>Xa,time:()=>jg,use:()=>yt,util:()=>$g,vector:()=>gr,version:()=>xR,zrUtil:()=>pe,zrender:()=>Ef});/*! *****************************************************************************
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
***************************************************************************** */var Ph=function(t,e){return Ph=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(r,i){r.__proto__=i}||function(r,i){for(var n in i)Object.prototype.hasOwnProperty.call(i,n)&&(r[n]=i[n])},Ph(t,e)};function L(t,e){if(typeof e!="function"&&e!==null)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");Ph(t,e);function r(){this.constructor=t}t.prototype=e===null?Object.create(e):(r.prototype=e.prototype,new r)}var Ef={};er(Ef,{dispose:()=>ZI,disposeAll:()=>qI,getElementSSRData:()=>Vv,getInstance:()=>KI,init:()=>Df,registerPainter:()=>Fv,registerSSRDataGetter:()=>Gv,version:()=>jI});var uM=(function(){function t(){this.firefox=!1,this.ie=!1,this.edge=!1,this.newEdge=!1,this.weChat=!1}return t})(),fM=(function(){function t(){this.browser=new uM,this.node=!1,this.wxa=!1,this.worker=!1,this.svgSupported=!1,this.touchEventsSupported=!1,this.pointerEventsSupported=!1,this.domSupported=!1,this.transformSupported=!1,this.transform3dSupported=!1,this.hasGlobalWindow=typeof window!="undefined"}return t})(),Xi=new fM;typeof wx=="object"&&typeof wx.getSystemInfoSync=="function"?(Xi.wxa=!0,Xi.touchEventsSupported=!0):typeof document=="undefined"&&typeof self!="undefined"?Xi.worker=!0:!Xi.hasGlobalWindow||"Deno"in window||typeof navigator!="undefined"&&typeof navigator.userAgent=="string"&&navigator.userAgent.indexOf("Node.js")>-1?(Xi.node=!0,Xi.svgSupported=!0):cM(navigator.userAgent,Xi);function cM(t,e){var r=e.browser,i=t.match(/Firefox\/([\d.]+)/),n=t.match(/MSIE\s([\d.]+)/)||t.match(/Trident\/.+?rv:(([\d.]+))/),a=t.match(/Edge?\/([\d.]+)/),o=/micromessenger/i.test(t);i&&(r.firefox=!0,r.version=i[1]),n&&(r.ie=!0,r.version=n[1]),a&&(r.edge=!0,r.version=a[1],r.newEdge=+a[1].split(".")[0]>18),o&&(r.weChat=!0),e.svgSupported=typeof SVGRect!="undefined",e.touchEventsSupported="ontouchstart"in window&&!r.ie&&!r.edge,e.pointerEventsSupported="onpointerdown"in window&&(r.edge||r.ie&&+r.version>=11);var s=e.domSupported=typeof document!="undefined";if(s){var l=document.documentElement.style;e.transform3dSupported=(r.ie&&"transition"in l||r.edge||"WebKitCSSMatrix"in window&&"m11"in new WebKitCSSMatrix||"MozPerspective"in l)&&!("OTransition"in l),e.transformSupported=e.transform3dSupported||r.ie&&+r.version>=9}}var oe=Xi;var pe={};er(pe,{EPSILON:()=>MM,HashMap:()=>F0,RADIAN_TO_DEGREE:()=>Hn,assert:()=>ze,assignProps:()=>Uh,bind:()=>J,clone:()=>$,concatArray:()=>Vh,createCanvas:()=>bM,createHashMap:()=>G,createObject:()=>bi,curry:()=>be,defaults:()=>re,disableUserSelect:()=>EM,each:()=>T,eqNaN:()=>Si,extend:()=>P,filter:()=>Ae,find:()=>Fh,guid:()=>Vs,hasOwn:()=>Oe,indexOf:()=>ie,inherits:()=>Hs,isArray:()=>k,isArrayLike:()=>je,isBuiltInObject:()=>kh,isDom:()=>zn,isFunction:()=>X,isGradientObject:()=>Un,isImagePatternObject:()=>wM,isNumber:()=>se,isObject:()=>Y,isPrimitive:()=>po,isRegExp:()=>CM,isString:()=>U,isStringSafe:()=>vo,isTypedArray:()=>Ve,keys:()=>ne,logError:()=>Gs,map:()=>z,merge:()=>he,mergeAll:()=>SM,mixin:()=>ct,noop:()=>Me,normalizeCssArray:()=>Vn,reduce:()=>Ct,retrieve:()=>Fn,retrieve2:()=>W,retrieve3:()=>Gt,setAsPrimitive:()=>Gn,slice:()=>Ys,trim:()=>pt});var Fu=12,Nh="sans-serif",Vt=Fu+"px "+Nh,pM=20,hM=100,vM="007LLmW'55;N0500LLLLLLLLLL00NNNLzWW\\\\WQb\\0FWLg\\bWb\\WQ\\WrWWQ000CL5LLFLL0LL**F*gLLLL5F0LF\\FFF5.5N";function dM(t){var e={};if(typeof JSON=="undefined")return e;for(var r=0;r<t.length;r++){var i=String.fromCharCode(r+32),n=(t.charCodeAt(r)-pM)/hM;e[i]=n}return e}var mM=dM(vM),at={createCanvas:function(){return typeof document!="undefined"&&document.createElement("canvas")},measureText:(function(){var t,e;return function(r,i){if(!t){var n=at.createCanvas();t=n&&n.getContext("2d")}if(t)return e!==i&&(e=t.font=i||Vt),t.measureText(r);r=r||"",i=i||Vt;var a=/((?:\d+)?\.?\d*)px/.exec(i),o=a&&+a[1]||Fu,s=0;if(i.indexOf("mono")>=0)s=o*r.length;else for(var l=0;l<r.length;l++){var u=mM[r[l]];s+=u==null?o:u*o}return{width:s}}})(),loadImage:function(t,e,r){var i=new Image;return i.onload=e,i.onerror=r,i.src=t,i},getTime:function(){return Date.now?Date.now():+new Date}};function Vu(t){for(var e in at)at.hasOwnProperty(e)&&t[e]&&(at[e]=t[e])}var k0=Ct(["Function","RegExp","Date","Error","CanvasGradient","CanvasPattern","Image","Canvas"],function(t,e){return t["[object "+e+"]"]=!0,t},{}),B0=Ct(["Int8","Uint8","Uint8Clamped","Int16","Uint16","Int32","Uint32","Float32","Float64"],function(t,e){return t["[object "+e+"Array]"]=!0,t},{}),ho=Object.prototype.toString,Hu=Array.prototype,gM=Hu.forEach,yM=Hu.filter,Bh=Hu.slice,_M=Hu.map,R0=function(){}.constructor,Gu=R0?R0.prototype:null,zh="__proto__",Rh=2311,xM=Math.pow(2,53)-1;function Vs(){return Rh>=xM&&(Rh=0),Rh++}function Gs(){for(var t=[],e=0;e<arguments.length;e++)t[e]=arguments[e];typeof console!="undefined"&&console.error.apply(console,t)}function $(t){if(t==null||typeof t!="object")return t;var e=t,r=ho.call(t);if(r==="[object Array]"){if(Line truncated
|
||||
`),a=n.length;if(a===1)return q_(n[0],e,r,i);for(var o=new ae(0,0,0,0),s=0;s<n.length;s++){var l=q_(n[s],e,r,i);s===0?o.copy(l):o.union(l)}return o}function tn(t,e,r,i){return r==="right"?i?t+=e:t-=e:r==="center"&&(i?t+=e/2:t-=e/2),t}function Di(t,e,r,i){return r==="middle"?i?t+=e/2:t-=e/2:r==="bottom"&&(i?t+=e:t-=e),t}function na(t){return Ur(t).stWideCharWidth}function zr(t,e){return typeof t=="string"?t.lastIndexOf("%")>=0?parseFloat(t)/100*e:parseFloat(t):t}function aa(t,e,r){var i=e.position||"inside",n=e.distance!=null?e.distance:5,a=r.height,o=r.width,s=a/2,l=r.x,u=r.y,f="left",c="top";if(i instanceof Array)l+=zr(i[0],r.width),u+=zr(i[1],r.height),f=null,c=null;else switch(i){case"left":l-=n,u+=s,f="right",c="middle";break;case"right":l+=n+o,u+=s,c="middle";break;case"top":l+=o/2,u-=n,f="center",c="bottom";break;case"bottom":l+=o/2,u+=a+n,f="center";break;case"inside":l+=o/2,u+=s,f="center",c="middle";break;case"insideLeft":l+=n,u+=s,c="middle";break;case"insideRight":l+=o-n,u+=s,f="right",c="middle";break;case"insideTop":l+=o/2,u+=n,f="center";break;case"insideBottom":l+=o/2,u+=a-n,f="center",c="bottom";break;case"insideTopLeft":l+=n,u+=n;break;case"insideTopRight":l+=o-n,u+=n,f="right";break;case"insideBottomLeft":l+=n,u+=a-n,c="bottom";break;case"insideBottomRight":l+=o-n,u+=a-n,f="right",c="bottom";break}return t=t||{},t.x=l,t.y=u,t.align=f,t.verticalAlign=c,t}var Nv="__zr_normal__",Rv=Eo.concat(["ignore"]),zI=Ct(Eo,function(t,e){return t[e]=!0,t},{ignore:!1}),Io={},UI=new ae(0,0,0,0),Tf=[],wf=0,Oo=1,zv=(function(){function t(e){this.id=Vs(),this.animators=[],this.currentStates=[],this.states={},this._init(e)}return t.prototype._init=function(e){this.attr(e)},t.prototype.drift=function(e,r,i){switch(this.draggable){case"horizontal":r=0;break;case"vertical":e=0;break}var n=this.transform;n||(n=this.transform=[1,0,0,1,0,0]),n[4]+=e,n[5]+=r,this.decomposeTransform(),this.markRedraw()},t.prototype.beforeUpdate=function(){},t.prototype.afterUpdate=function(){},t.prototype.update=function(){this.updateTransform(),this.__dirty&&this.updateInnerText()},t.prototype.updateInnerText=function(e){var r=this._textContent;if(r&&(!r.ignore||e)){this.textConfig||(this.textConfig={});var i=this.textConfig,n=i.local,a=r.innerTransformable,o=void 0,s=void 0,l=!1;a.parent=n?this:null;var u=!1;a.copyTransform(r);var f=i.position!=null,c=i.autoOverflowArea,p=void 0;if((c||f)&&(p=UI,i.layoutRect?p.copy(i.layoutRect):p.copy(this.getBoundingRect()),n||p.applyTransform(this.transform)),f){this.calculateTextPosition?this.calculateTextPosition(Io,i,p):aa(Io,i,p),a.x=Io.x,a.y=Io.y,o=Io.align,s=Io.verticalAlign;var h=i.origin;if(h&&i.rotation!=null){var v=void 0,d=void 0;h==="center"?(v=p.width*.5,d=p.height*.5):(v=zr(h[0],p.width),d=zr(h[1],p.height)),u=!0,a.originX=-a.x+v+(n?0:p.x),a.originY=-a.y+d+(n?0:p.y)}}i.rotation!=null&&(a.rotation=i.rotation);var m=i.offset;m&&(a.x+=m[0],a.y+=m[1],u||(a.originX=-m[0],a.originY=-m[1]));var g=this._innerTextDefaultStyle||(this._innerTextDefaultStyle={});if(c){var y=g.overflowRect=g.overflowRect||new ae(0,0,0,0);a.getLocalTransform(Tf),tr(Tf,Tf),ae.copy(y,p),y.applyTransform(Tf)}else g.overflowRect=null;var _=i.inside==null?typeof i.position=="string"&&i.position.indexOf("inside")>=0:i.inside,x=void 0,S=void 0,b=void 0;_&&this.canBeInsideText()?(x=i.insideFill,S=i.insideStroke,(x==null||x==="auto")&&(x=this.getInsideTextFill()),(S==null||S==="auto")&&(S=this.getInsideTextStroke(x),b=!0)):(x=i.outsideFill,S=i.outsideStroke,(x==null||x==="auto")&&(x=this.getOutsideFill()),(S==null||S==="auto")&&(S=this.getOutsideStroke(x),b=!0)),x=x||"#000",(x!==g.fill||S!==g.stroke||b!==g.autoStroke||o!==g.align||s!==g.verticalAlign)&&(l=!0,g.fill=x,g.stroke=S,g.autoStroke=b,g.align=o,g.verticalAlign=s,r.setDefaultTextStyle(g)),r.__dirty|=At,l&&r.dirtyStyle(!0)}},t.prototype.canBeInsideText=function(){return!0},t.prototype.getInsideTextFill=function(){return"#fff"},t.prototype.getInsideTextStroke=function(e){return"#000"},t.prototype.getOutsideFill=function(){return this.__zr&&this.__zr.isDarkMode()?ul:ll},t.prototype.getOutsideStroke=function(e){var r=this.__zr&&this.__zr.getBackgroundColor(),i=typeof r=="string"&&ht(r);i||(i=[255,255,255,1]);for(var n=i[3],a=this.__zr.isDarkMode(),o=0;o<3;o++)i[o]=i[o]*n+(a?0:255)*(1-n);return i[3]=1,ei(i,"rgba")},t.prototype.traverse=function(e,r){},t.prototype.attrKV=function(e,r){e==="textConfig"?this.setTextConfig(r):e==="textContent"?this.setTextContent(r):e==="clipPath"?this.setClipPath(r):e==="extra"?(this.extra=this.extra||{},P(this.extra,r)):this[e]=r},t.prototype.hide=function(){this.ignore=!0,this.markRedraw()},t.prototype.show=function(){this.ignore=!1,this.markRedraw()},t.prototype.attr=function(e,r){if(typeof e=="string")this.attrKV(e,r);else if(Y(e))for(var i=e,n=ne(i),a=0;a<n.length;a++){var o=n[a];this.attrKV(o,e[o])}return this.markRedraw(),this},t.prototype.saveCurrentToNormalState=function(e){this._innerSaveToNormal(e);for(var r=this._normalState,i
Line truncated
|
||||
* ZRender, a high performance 2d drawing library.
|
||||
*
|
||||
* Copyright (c) 2013, Baidu Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* LICENSE
|
||||
* https://github.com/ecomfe/zrender/blob/master/LICENSE
|
||||
*/var Af={},oa={};function YI(t){delete oa[t]}function WI(t){if(!t)return!1;if(typeof t=="string")return ea(t,1)<sl;if(t.colorStops){for(var e=t.colorStops,r=0,i=e.length,n=0;n<i;n++)r+=ea(e[n].color,1);return r/=i,r<sl}return!1}var XI=(function(){function t(e,r,i){var n=this;this._sleepAfterStill=10,this._stillFrameAccum=0,this._needsRefresh=!0,this._needsRefreshHover=!1,this._darkMode=!1,i=i||{},this.dom=r,this.id=e;var a=new u_,o=i.renderer||"canvas";Af[o]||(o=ne(Af)[0]),i.useDirtyRect=i.useDirtyRect==null?!1:i.useDirtyRect;var s=new Af[o](r,a,i,e),l=i.ssr||s.ssrOnly;this.storage=a,this.painter=s;var u=!oe.node&&!oe.worker&&!l?new V_(s.getViewportRoot(),s.root):null,f=i.useCoarsePointer,c=f==null||f==="auto"?oe.touchEventsSupported:!!f,p=44,h;c&&(h=W(i.pointerSize,p)),this.handler=new i_(a,s,u,s.root,h),this.animation=new k_({stage:{update:l?null:function(){return n._flush(!1)}}}),l||this.animation.start()}return t.prototype.add=function(e){this._disposed||!e||(this.storage.addRoot(e),e.addSelfToZr(this),this.refresh())},t.prototype.remove=function(e){this._disposed||!e||(this.storage.delRoot(e),e.removeSelfFromZr(this),this.refresh())},t.prototype.configLayer=function(e,r){this._disposed||(this.painter.configLayer&&this.painter.configLayer(e,r),this.refresh())},t.prototype.setBackgroundColor=function(e){this._disposed||(this.painter.setBackgroundColor&&this.painter.setBackgroundColor(e),this.refresh(),this._backgroundColor=e,this._darkMode=WI(e))},t.prototype.getBackgroundColor=function(){return this._backgroundColor},t.prototype.setDarkMode=function(e){this._darkMode=e},t.prototype.isDarkMode=function(){return this._darkMode},t.prototype.refreshImmediately=function(e){this._disposed||this._refresh({animUpdate:!e,refresh:!0,refreshHover:!1})},t.prototype._refresh=function(e){e.animUpdate&&this.animation.update(!0),this._needsRefresh=this._needsRefreshHover=!1,this.painter.refresh({refresh:e.refresh,refreshHover:e.refreshHover}),this._needsRefresh=this._needsRefreshHover=!1},t.prototype.refresh=function(){this._disposed||(this._needsRefresh=!0,this.animation.start())},t.prototype.flush=function(){this._disposed||this._flush(!0)},t.prototype._flush=function(e){var r,i=ta(),n=this._needsRefresh,a=this._needsRefreshHover;(n||a)&&(r=!0,this._refresh({animUpdate:e,refresh:n,refreshHover:a}));var o=ta();r?(this._stillFrameAccum=0,this.trigger("rendered",{elapsedTime:o-i})):this._sleepAfterStill>0&&(this._stillFrameAccum++,this._stillFrameAccum>this._sleepAfterStill&&this.animation.stop())},t.prototype.setSleepAfterStill=function(e){this._sleepAfterStill=e},t.prototype.wakeUp=function(){this._disposed||(this.animation.start(),this._stillFrameAccum=0)},t.prototype.refreshHover=function(){this._needsRefreshHover=!0},t.prototype.refreshHoverImmediately=function(){this._disposed||this._refresh({animUpdate:!1,refresh:!1,refreshHover:!0})},t.prototype.resize=function(e){this._disposed||(e=e||{},this.painter.resize(e.width,e.height),this.handler.resize())},t.prototype.clearAnimation=function(){this._disposed||this.animation.clear()},t.prototype.getWidth=function(){if(!this._disposed)return this.painter.getWidth()},t.prototype.getHeight=function(){if(!this._disposed)return this.painter.getHeight()},t.prototype.setCursorStyle=function(e){this._disposed||this.handler.setCursorStyle(e)},t.prototype.findHover=function(e,r){if(!this._disposed)return this.handler.findHover(e,r)},t.prototype.on=function(e,r,i){return this._disposed||this.handler.on(e,r,i),this},t.prototype.off=function(e,r){this._disposed||this.handler.off(e,r)},t.prototype.trigger=function(e,r){this._disposed||this.handler.trigger(e,r)},t.prototype.clear=function(){if(!this._disposed){for(var e=this.storage.getRoots(),r=0;r<e.length;r++)e[r]instanceof xe&&e[r].removeSelfFromZr(this);this.storage.delAllRoots(),this.painter.clear()}},t.prototype.dispose=function(){this._disposed||(this.animation.stop(),this.clear(),this.storage.dispose(),this.painter.dispose(),this.handler.dispose(),this.animation=this.storage=this.painter=this.handler=null,this._disposed=!0,YI(this.id))},t})();function Df(t,e){var r=new XI(Vs(),t,e);return oa[r.id]=r,r}function ZI(t){t.dispose()}function qI(){for(var t in oa)oa.hasOwnProperty(t)&&oa[t].dispose();oa={}}function KI(t){return oa[t]}function Fv(t,e){Af[t]=e}var Uv;function Vv(t){if(typeof Uv=="function")return Uv(t)}function Gv(t){Uv=t}var jI="6.1.0";var tx=1e-4,Mf=20;function QI(t){return t.replace(/^\s+|\s+$/g,"")}var Ne=Math.min,ve=Math.max,Ie=Math.abs,Yt=Math.round,Wt=Math.floor,ri=Math.ceil,nn=Math.pow,rn=Math.log,fl=Math.LN10,JI=Math.PI,$I=Math.random;function Le(t,e,r,i){var n=e[0],a=e[1],o=r[0],s=r[1],l=a-n,u=s-o;if(l===0)return u===0?o:(o+s)/2;if(i)if(l>0){if(t<=n)return o;if(t>=a)return s}else{if(t>=n)return o;if(t<=a)return s}else{if(t===n)return o;if(t===a)return s}return(t-n)/l*u+o}var we=eO;function eO(t,e,r){switch(t){case"center":case"middle":t="50%";break;case"left":case"top":t="0%";break;case"right":case"bottomLine truncated
|
||||
`);a=Bx(r,i,n,a);for(var s=!1,l={},u=0,f=o.length;u<f;u++)zx(l,o[u],a),o[u]=l.textLine,s=s||l.isTruncated;t.text=o.join(`
|
||||
`),t.isTruncated=s}function Bx(t,e,r,i){i=i||{};var n=P({},i);r=W(r,"..."),n.maxIterations=W(i.maxIterations,2);var a=n.minChar=W(i.minChar,0),o=n.fontMeasureInfo=Ur(e),s=o.asciiCharWidth;n.placeholder=W(i.placeholder,"");for(var l=t=Math.max(0,t-1),u=0;u<a&&l>=s;u++)l-=s;var f=Fr(o,r);return f>l&&(r="",f=0),l=t-f,n.ellipsis=r,n.ellipsisWidth=f,n.contentWidth=l,n.containerWidth=t,n}function zx(t,e,r){var i=r.containerWidth,n=r.contentWidth,a=r.fontMeasureInfo;if(!i){t.textLine="",t.isTruncated=!1;return}var o=Fr(a,e);if(o<=i){t.textLine=e,t.isTruncated=!1;return}for(var s=0;;s++){if(o<=n||s>=r.maxIterations){e+=r.ellipsis;break}var l=s===0?CO(e,n,a):o>0?Math.floor(e.length*n/o):0;e=e.substr(0,l),o=Fr(a,e)}e===""&&(e=r.placeholder),t.textLine=e,t.isTruncated=!0}function CO(t,e,r){for(var i=0,n=0,a=t.length;n<a&&i<e;n++)i+=Pv(r,t.charCodeAt(n));return n}function Ux(t,e,r,i){var n=ad(t),a=e.overflow,o=e.padding,s=o?o[1]+o[3]:0,l=o?o[0]+o[2]:0,u=e.font,f=a==="truncate",c=na(u),p=W(e.lineHeight,c),h=e.lineOverflow==="truncate",v=!1,d=e.width;d==null&&r!=null&&(d=r-s);var m=e.height;m==null&&i!=null&&(m=i-l);var g;d!=null&&(a==="break"||a==="breakAll")?g=n?Vx(n,e.font,d,a==="breakAll",0).lines:[]:g=n?n.split(`
|
||||
`):[];var y=g.length*p;if(m==null&&(m=y),y>m&&h){var _=Math.floor(m/p);v=v||g.length>_,g=g.slice(0,_),y=g.length*p}if(n&&f&&d!=null)for(var x=Bx(d,u,e.ellipsis,{minChar:e.truncateMinChar,placeholder:e.placeholder}),S={},b=0;b<g.length;b++)zx(S,g[b],x),g[b]=S.textLine,v=v||S.isTruncated;for(var w=m,A=0,C=Ur(u),b=0;b<g.length;b++)A=Math.max(Fr(C,g[b]),A);d==null&&(d=A);var D=d;return w+=l,D+=s,{lines:g,height:m,outerWidth:D,outerHeight:w,lineHeight:p,calculatedLineHeight:c,contentWidth:A,contentHeight:y,width:d,isTruncated:v}}var AO=(function(){function t(){}return t})(),Px=(function(){function t(e){this.tokens=[],e&&(this.tokens=e)}return t})(),DO=(function(){function t(){this.width=0,this.height=0,this.contentWidth=0,this.contentHeight=0,this.outerWidth=0,this.outerHeight=0,this.lines=[],this.isTruncated=!1}return t})();function Fx(t,e,r,i,n){var a=new DO,o=ad(t);if(!o)return a;var s=e.padding,l=s?s[1]+s[3]:0,u=s?s[0]+s[2]:0,f=e.width;f==null&&r!=null&&(f=r-l);var c=e.height;c==null&&i!=null&&(c=i-u);for(var p=e.overflow,h=(p==="break"||p==="breakAll")&&f!=null?{width:f,accumWidth:0,breakAll:p==="breakAll"}:null,v=td.lastIndex=0,d;(d=td.exec(o))!=null;){var m=d.index;m>v&&rd(a,o.substring(v,m),e,h),rd(a,d[2],e,h,d[1]),v=td.lastIndex}v<o.length&&rd(a,o.substring(v,o.length),e,h);var g=[],y=0,_=0,x=p==="truncate",S=e.lineOverflow==="truncate",b={};function w(ye,He,Fe){ye.width=He,ye.lineHeight=Fe,y+=Fe,_=Math.max(_,He)}e:for(var A=0;A<a.lines.length;A++){for(var C=a.lines[A],D=0,E=0,I=0;I<C.tokens.length;I++){var M=C.tokens[I],O=M.styleName&&e.rich[M.styleName]||{},N=M.textPadding=O.padding,R=N?N[1]+N[3]:0,F=M.font=O.font||e.font;M.contentHeight=na(F);var B=W(O.height,M.contentHeight);if(M.innerHeight=B,N&&(B+=N[0]+N[2]),M.height=B,M.lineHeight=Gt(O.lineHeight,e.lineHeight,B),M.align=O&&O.align||n,M.verticalAlign=O&&O.verticalAlign||"middle",S&&c!=null&&y+M.lineHeight>c){var V=a.lines.length;I>0?(C.tokens=C.tokens.slice(0,I),w(C,E,D),a.lines=a.lines.slice(0,A+1)):a.lines=a.lines.slice(0,A),a.isTruncated=a.isTruncated||a.lines.length<V;break e}var H=O.width,Z=H==null||H==="auto";if(typeof H=="string"&&H.charAt(H.length-1)==="%")M.percentWidth=H,g.push(M),M.contentWidth=Fr(Ur(F),M.text);else{if(Z){var Q=O.backgroundColor,ee=Q&&Q.image;ee&&(ee=Lx(ee),Fo(ee)&&(M.width=Math.max(M.width,ee.width*B/ee.height)))}var j=x&&f!=null?f-E:null;j!=null&&j<M.width?!Z||j<R?(M.text="",M.width=M.contentWidth=0):(kx(b,M.text,j-R,F,e.ellipsis,{minChar:e.truncateMinChar}),M.text=b.text,a.isTruncated=a.isTruncated||b.isTruncated,M.width=M.contentWidth=Fr(Ur(F),M.text)):M.contentWidth=Fr(Ur(F),M.text)}M.width+=R,E+=M.width,O&&(D=Math.max(D,M.lineHeight))}w(C,E,D)}a.outerWidth=a.width=W(f,_),a.outerHeight=a.height=W(c,y),a.contentHeight=y,a.contentWidth=_,a.outerWidth+=l,a.outerHeight+=u;for(var A=0;A<g.length;A++){var M=g[A],ce=M.percentWidth;M.width=parseInt(ce,10)/100*a.width}return a}function rd(t,e,r,i,n){var a=e==="",o=n&&r.rich[n]||{},s=t.lines,l=o.font||r.font,u=!1,f,c;if(i){var p=o.padding,h=p?p[1]+p[3]:0;if(o.width!=null&&o.width!=="auto"){var v=zr(o.width,i.width)+h;s.length>0&&v+i.accumWidth>i.width&&(f=e.split(`
|
||||
`),u=!0),i.accumWidth=v}else{var d=Vx(e,l,i.width,i.breakAll,i.accumWidth);i.accumWidth=d.accumWidth+h,c=d.linesWidths,f=d.lines}}f||(f=e.split(`
|
||||
`));for(var m=Ur(l),g=0;g<f.length;g++){var y=f[g],_=new AO;if(_.styleName=n,_.text=y,_.isLineHolder=!y&&!a,typeof o.width=="number"?_.width=o.width:_.width=c?c[g]:Fr(m,y),!g&&!u){var x=(s[s.length-1]||(s[0]=new Px)).tokens,S=x.length;S===1&&x[0].isLineHolder?x[0]=_:(y||!S||a)&&x.push(_)}else s.push(new Px([_]))}}function EO(t){var e=t.charCodeAt(0);return e>=32&&e<=591||e>=880&&e<=4351||e>=4608&&e<=5119||e>=7680&&e<=8303}var MO=Ct(",&?/;] ".split(""),function(t,e){return t[e]=!0,t},{});function IO(t){return EO(t)?!!MO[t]:!0}function Vx(t,e,r,i,n){for(var a=[],o=[],s="",l="",u=0,f=0,c=Ur(e),p=0;p<t.length;p++){var h=t.charAt(p);if(h===`
|
||||
`){l&&(s+=l,f+=u),a.push(s),o.push(f),s="",l="",u=0,f=0;continue}var v=Pv(c,h.charCodeAt(0)),d=i?!1:!IO(h);if(a.length?f+v>r:n+f+v>r){f?(s||l)&&(d?(s||(s=l,l="",u=0,f=u),a.push(s),o.push(f-u),l+=h,u+=v,s="",f=u):(l&&(s+=l,l="",u=0),a.push(s),o.push(f),s=h,f=v)):d?(a.push(l),o.push(u),l=h,u=v):(a.push(h),o.push(v));continue}f+=v,d?(l+=h,u+=v):(l&&(s+=l,l="",u=0),s+=h)}return l&&(s+=l),s&&(a.push(s),o.push(f)),a.length===1&&(f+=n),{accumWidth:f,lines:a,linesWidths:o}}function nd(t,e,r,i,n,a){if(t.baseX=r,t.baseY=i,t.outerWidth=t.outerHeight=null,!!e){var o=e.width*2,s=e.height*2;ae.set(Nx,tn(r,o,n),Di(i,s,a),o,s),ae.intersect(e,Nx,null,Rx);var l=Rx.outIntersectRect;t.outerWidth=l.width,t.outerHeight=l.height,t.baseX=tn(l.x,l.width,n,!0),t.baseY=Di(l.y,l.height,a,!0)}}var Nx=new ae(0,0,0,0),Rx={outIntersectRect:{},clamp:!0};function ad(t){return t!=null?t+="":t=""}function Gx(t){var e=ad(t.text),r=t.font,i=Fr(Ur(r),e),n=na(r);return Rf(t,i,n,null)}function Rf(t,e,r,i){var n=new ae(tn(t.x||0,e,t.textAlign),Di(t.y||0,r,t.textBaseline),e,r),a=i!=null?i:od(t)?t.lineWidth:0;return a>0&&(n.x-=a/2,n.y-=a/2,n.width+=a,n.height+=a),n}function od(t){var e=t.stroke;return e!=null&&e!=="none"&&t.lineWidth>0}var ud="__zr_style_"+Math.round(Math.random()*10),ni={shadowBlur:0,shadowOffsetX:0,shadowOffsetY:0,shadowColor:"#000",opacity:1,blend:"source-over"},ua={style:{shadowBlur:!0,shadowOffsetX:!0,shadowOffsetY:!0,shadowColor:!0,opacity:!0}};ni[ud]=!0;var Hx=["z","z2","invisible"],OO=["invisible"],LO=(function(t){L(e,t);function e(r){return t.call(this,r)||this}return e.prototype._init=function(r){for(var i=ne(r),n=0;n<i.length;n++){var a=i[n];a==="style"?this.useStyle(r[a]):t.prototype.attrKV.call(this,a,r[a])}this.style||this.useStyle({})},e.prototype.beforeBrush=function(r){},e.prototype.afterBrush=function(){},e.prototype.innerBeforeBrush=function(){},e.prototype.innerAfterBrush=function(){},e.prototype.shouldBePainted=function(r,i,n,a){var o=this.transform;if(this.ignore||this.invisible||this.style.opacity===0||this.culling&&PO(this,r,i)||o&&!o[0]&&!o[3])return!1;if(n&&this.__clipPaths&&this.__clipPaths.length){for(var s=0;s<this.__clipPaths.length;++s)if(this.__clipPaths[s].isZeroArea())return!1}if(a&&this.parent)for(var l=this.parent;l;){if(l.ignore)return!1;l=l.parent}return!0},e.prototype.contain=function(r,i){return this.rectContain(r,i)},e.prototype.traverse=function(r,i){r.call(i,this)},e.prototype.rectContain=function(r,i){var n=this.transformCoordToLocal(r,i),a=this.getBoundingRect();return a.contain(n[0],n[1])},e.prototype.getPaintRect=function(){var r=this._paintRect;if(!this._paintRect||this.__dirty){var i=this.transform,n=this.getBoundingRect(),a=this.style,o=a.shadowBlur||0,s=a.shadowOffsetX||0,l=a.shadowOffsetY||0;r=this._paintRect||(this._paintRect=new ae(0,0,0,0)),i?ae.applyTransform(r,n,i):r.copy(n),(o||s||l)&&(r.width+=o*2+Math.abs(s),r.height+=o*2+Math.abs(l),r.x=Math.min(r.x,r.x+s-o),r.y=Math.min(r.y,r.y+l-o));var u=this.dirtyRectTolerance;r.isZero()||(r.x=Math.floor(r.x-u),r.y=Math.floor(r.y-u),r.width=Math.ceil(r.width+1+u*2),r.height=Math.ceil(r.height+1+u*2))}return r},e.prototype.setPrevPaintRect=function(r){r?(this._prevPaintRect=this._prevPaintRect||new ae(0,0,0,0),this._prevPaintRect.copy(r)):this._prevPaintRect=null},e.prototype.getPrevPaintRect=function(){return this._prevPaintRect},e.prototype.animateStyle=function(r){return this.animate("style",r)},e.prototype.updateDuringAnimation=function(r){r==="style"?this.dirtyStyle():this.markRedraw()},e.prototype.attrKV=function(r,i){r!=="style"?t.prototype.attrKV.call(this,r,i):this.style?this.setStyle(i):this.useStyle(i)},e.prototype.setStyle=function(r,i){return typeof r=="string"?this.style[r]=i:P(this.style,r),this.dirtyStyle(),this},e.prototype.dirtyStyle=function(r){r||this.markRedraw(),this.__dirty|=Jn,this._rect&&(this._rect=null)},e.prototype.dirty=function(){this.dirtyStyle()},e.prototype.styleChanged=function(){return!!(this.__dirty&Jn)},e.prototype.styleUpdated=function(){this.__dirty&=~Jn},e.prototype.createStyle=function(r){return bi(ni,r)},e.prototype.useStyle=function(r){r[ud]||(r=this.createStyle(r)),this.style=r,this.dirtyStyle()},e.prototype._useHoverStyle=function(r){this.__hoverStyle=r},e.prototype.isStyleObject=function(r){return r[ud]},e.prototype._innerSaveToNormal=function(r){t.prototype._innerSaveToNormal.call(this,r);var i=this._normalState;r.style&&!i.style&&(i.style=this._mergeStyle(this.createStyle(),this.style)),this._savePrimaryToNormal(r,i,Hx)},e.prototype._applyStateObj=function(r,i,n,a,o,s){t.prototype._applyStateObj.call(this,r,i,n,a,o,s);var l=!(i&&a),u=this.__inHover===Oo,f;if(i&&i.style?o?a?f=i.style:(f=this._mergeStyle(this.createStyle(),n.style),this._mergeStyle(f,i.style)):(f=this._mergeStyle(this.createStyle(),a?this.style:n.style),this._mergeStyle(f,i.style)):l&&(f=n.style),f)if(o){var c=this.style;if(this.style=this.createStyle(l?{}:c),l)for(var p=ne(c),h=0;h<p.length;h++){var v=p[h];v in f&&(f[v]=f[v
Line truncated
|
||||
yyyy`);var i=Pt(e),n=r?"getUTC":"get",a=i[n+"FullYear"](),o=i[n+"Month"]()+1,s=i[n+"Date"](),l=i[n+"Hours"](),u=i[n+"Minutes"](),f=i[n+"Seconds"](),c=i[n+"Milliseconds"]();return t=t.replace("MM",It(o,2)).replace("M",o).replace("yyyy",a).replace("yy",It(a%100+"",2)).replace("dd",It(s,2)).replace("d",s).replace("hh",It(l,2)).replace("h",l).replace("mm",It(u,2)).replace("m",u).replace("ss",It(f,2)).replace("s",f).replace("SSS",It(c,3)),t}function Vb(t){return t&&t.charAt(0).toUpperCase()+t.substr(1)}function Yr(t,e){return e=e||"transparent",U(t)?t:Y(t)&&t.colorStops&&(t.colorStops[0]||{}).color||e}var Mc={},bm={},yP=(function(){function t(){this._normalMasterList=[],this._nonSeriesBoxMasterList=[]}return t.prototype.create=function(e,r){this._nonSeriesBoxMasterList=i(Mc,!0),this._normalMasterList=i(bm,!1);function i(n,a){var o=[];return T(n,function(s,l){var u=s.create(e,r);o=o.concat(u||[])}),o}},t.prototype.update=function(e,r){T(this._normalMasterList,function(i){i.update&&i.update(e,r)})},t.prototype.getCoordinateSystems=function(){return this._normalMasterList.concat(this._nonSeriesBoxMasterList)},t.register=function(e,r){if(e==="matrix"||e==="calendar"){Mc[e]=r;return}bm[e]=r},t.get=function(e){return bm[e]||Mc[e]},t})();function _P(t){return!!Mc[t]}var xP=1,Tm=2;function Gb(t){Hb.set(t.fullType,{getCoord2:void 0}).getCoord2=t.getCoord2}var Hb=G();function Oc(t){var e=t.getShallow("coord",!0),r=xP;if(e==null){var i=Hb.get(t.type);i&&i.getCoord2&&(r=Tm,e=i.getCoord2(t))}return{coord:e,from:r}}var ls=0,Ic=1,SP=2;function bP(t,e){var r=t.getShallow("coordinateSystem"),i=t.getShallow("coordinateSystemUsage",!0),n=i!=null,a=ls;if(r){var o=t.mainType==="series";i==null&&(i=o?"data":"box"),i==="data"?(a=Ic,o||(a=ls)):i==="box"&&(a=SP,!o&&!_P(r)&&(a=ls))}return{coordSysType:r,kind:a}}function Yb(t){var e=t.targetModel,r=t.coordSysType,i=t.coordSysProvider,n=t.isDefaultDataCoordSys,a=t.allowNotFound,o=bP(e,!0),s=o.kind,l=o.coordSysType;if(n&&s!==Ic&&(s=Ic,l=r),s===ls||l!==r)return ls;var u=i(r,e);return u?(s===Ic?e.coordinateSystem=u:e.boxCoordinateSystem=u,s):ls}var us=yP;var Pc=T,TP=["left","right","top","bottom","width","height"],Lc=[["width","left","right"],["height","top","bottom"]];function wm(t,e,r,i,n){var a=0,o=0;i==null&&(i=1/0),n==null&&(n=1/0);var s=0;e.eachChild(function(l,u){var f=l.getBoundingRect(),c=e.childAt(u+1),p=c&&c.getBoundingRect(),h,v;if(t==="horizontal"){var d=f.width+(p?-p.x+f.x:0);h=a+d,h>i||l.newline?(a=0,h=d,o+=s+r,s=f.height):s=Math.max(s,f.height)}else{var m=f.height+(p?-p.y+f.y:0);v=o+m,v>n||l.newline?(a+=s+r,o=0,v=m,s=f.width):s=Math.max(s,f.width)}l.newline||(l.x=a,l.y=o,l.markRedraw(),t==="horizontal"?a=h+r:o=v+r)})}var Ba=wm,H3=be(wm,"vertical"),Y3=be(wm,"horizontal");function Wb(t,e){return{left:t.getShallow("left",e),top:t.getShallow("top",e),right:t.getShallow("right",e),bottom:t.getShallow("bottom",e),width:t.getShallow("width",e),height:t.getShallow("height",e)}}function wP(t,e){var r=za(t,e,{enableLayoutOnlyByCenter:!0}),i=t.getBoxLayoutParams(),n,a;if(r.type===Fl.point)a=r.refPoint,n=jt(i,{width:e.getWidth(),height:e.getHeight()});else{var o=t.get("center"),s=k(o)?o:[o,o];n=jt(i,r.refContainer),a=r.boxCoordFrom===Tm?r.refPoint:[we(s[0],n.width)+n.x,we(s[1],n.height)+n.y]}return{viewRect:n,center:a}}function Xb(t,e){var r=wP(t,e),i=r.viewRect,n=r.center,a=t.get("radius");k(a)||(a=[0,a]);var o=we(i.width,e.getWidth()),s=we(i.height,e.getHeight()),l=Math.min(o,s),u=we(a[0],l/2),f=we(a[1],l/2);return{cx:n[0],cy:n[1],r0:u,r:f,viewRect:i}}function jt(t,e,r){r=di(r||0);var i=e.width,n=e.height,a=we(t.left,i),o=we(t.top,n),s=we(t.right,i),l=we(t.bottom,n),u=we(t.width,i),f=we(t.height,n),c=r[2]+r[0],p=r[1]+r[3],h=t.aspect;switch(isNaN(u)&&(u=i-s-p-a),isNaN(f)&&(f=n-l-c-o),h!=null&&(isNaN(u)&&isNaN(f)&&(h>i/n?u=i*.8:f=n*.8),isNaN(u)&&(u=h*f),isNaN(f)&&(f=u/h)),isNaN(a)&&(a=i-s-u-p),isNaN(o)&&(o=n-l-f-c),t.left||t.right){case"center":a=i/2-u/2-r[3];break;case"right":a=i-u-p;break}switch(t.top||t.bottom){case"middle":case"center":o=n/2-f/2-r[0];break;case"bottom":o=n-f-c;break}a=a||0,o=o||0,isNaN(u)&&(u=i-p-a-(s||0)),isNaN(f)&&(f=n-c-o-(l||0));var v=new ae((e.x||0)+a+r[3],(e.y||0)+o+r[0],u,f);return v.margin=r,v}var Fl={rect:1,point:2};function za(t,e,r){var i,n,a,o=t.boxCoordinateSystem,s;if(o){var l=Oc(t),u=l.coord,f=l.from;if(o.dataToLayout){a=Fl.rect,s=f;var c=o.dataToLayout(u);i=c.contentRect||c.rect}else r&&r.enableLayoutOnlyByCenter&&o.dataToPoint&&(a=Fl.point,s=f,n=o.dataToPoint(u))}return a==null&&(a=Fl.rect),a===Fl.rect&&(i||(i={x:0,y:0,width:e.getWidth(),height:e.getHeight()}),n=[i.x+i.width/2,i.y+i.height/2]),{type:a,refContainer:i,refPoint:n,boxCoordFrom:s}}function Sn(t){var e=t.layoutMode||t.constructor.layoutMode;return Y(e)?e:e?{type:e}:null}function pr(t,e,r){var i=r&&r.ignoreSize;!k(i)&&(i=[i,i]);var n=o(Lc[0],0),a=o(Lc[1],1);l(Lc[0],t,n),l(Lc[1],t,a);function o(u,f){var c={},p=0,h={},v=0,d=2;if(Pc(u,function(y){h[y]=t[y]}),Pc(u,function(y){Oe(e,y)&&(c[y]=h[y
Line truncated
|
||||
`,`
|
||||
|
||||
`,`
|
||||
|
||||
|
||||
`];function Ya(t,e){return e.type=t,e}function Fm(t){return t.type==="section"}function zT(t){return Fm(t)?yN:_N}function UT(t){if(Fm(t)){var e=0,r=t.blocks.length,i=r>1||r>0&&!t.noHeader;return T(t.blocks,function(n){var a=UT(n);a>=e&&(e=a+ +(i&&(!a||Fm(n)&&!n.noHeader)))}),e}return 0}function yN(t,e,r,i){var n=e.noHeader,a=xN(UT(e)),o=[],s=e.blocks||[];ze(!s||k(s)),s=s||[];var l=t.orderMode;if(e.sortBlocks&&l){s=s.slice();var u={valueAsc:"asc",valueDesc:"desc"};if(Oe(u,l)){var f=new DT(u[l],null);s.sort(function(d,m){return f.evaluate(d.sortParam,m.sortParam)})}else l==="seriesDesc"&&s.reverse()}T(s,function(d,m){var g=e.valueFormatter,y=zT(d)(g?P(P({},t),{valueFormatter:g}):t,d,m>0?a.html:0,i);y!=null&&o.push(y)});var c=t.renderMode==="richText"?o.join(a.richText):Vm(i,o.join(""),n?r:a.html);if(n)return c;var p=Dc(e.header,"ordinal",t.useUTC),h=BT(i,t.renderMode).nameStyle,v=kT(i);return t.renderMode==="richText"?FT(t,p,h)+a.richText+c:Vm(i,'<div style="'+h+";"+v+';">'+Je(p)+"</div>"+c,r)}function _N(t,e,r,i){var n=t.renderMode,a=e.noName,o=e.noValue,s=!e.markerType,l=e.name,u=t.useUTC,f=e.valueFormatter||t.valueFormatter||function(x){return x=k(x)?x:[x],z(x,function(S,b){return Dc(S,k(h)?h[b]:h,u)})};if(!(a&&o)){var c=s?"":t.markupStyleCreator.makeTooltipMarker(e.markerType,e.markerColor||K.color.secondary,n),p=a?"":Dc(l,"ordinal",u),h=e.valueType,v=o?[]:f(e.value,e.rawDataIndex),d=!s||!a,m=!s&&a,g=BT(i,n),y=g.nameStyle,_=g.valueStyle;return n==="richText"?(s?"":c)+(a?"":FT(t,p,y))+(o?"":TN(t,v,d,m,_)):Vm(i,(s?"":c)+(a?"":SN(p,!s,y))+(o?"":bN(v,d,m,_)),r)}}function Gm(t,e,r,i,n,a){if(t){var o=zT(t),s={useUTC:n,renderMode:r,orderMode:i,markupStyleCreator:e,valueFormatter:t.valueFormatter};return o(s,t,0,a)}}function xN(t){return{html:mN[t],richText:gN[t]}}function Vm(t,e,r){var i='<div style="clear:both"></div>',n="margin: "+r+"px 0 0",a=kT(t);return'<div style="'+n+";"+a+';">'+e+i+"</div>"}function SN(t,e,r){var i=e?"margin-left:2px":"";return'<span style="'+r+";"+i+'">'+Je(t)+"</span>"}function bN(t,e,r,i){var n=r?"10px":"20px",a=e?"float:right;margin-left:"+n:"";return t=k(t)?t:[t],'<span style="'+a+";"+i+'">'+z(t,function(o){return Je(o)}).join(" ")+"</span>"}function FT(t,e,r){return t.markupStyleCreator.wrapRichTextStyle(e,r)}function TN(t,e,r,i,n){var a=[n],o=i?10:20;return r&&a.push({padding:[0,0,0,o],align:"right"}),t.markupStyleCreator.wrapRichTextStyle(k(e)?e.join(" "):e,a)}function VT(t,e){var r=t.getData().getItemVisual(e,"style"),i=r[t.visualDrawType];return Yr(i)}function qc(t,e){var r=t.get("padding");return r!=null?r:e==="richText"?[8,10]:10}var Kc=(function(){function t(){this.richTextStyles={},this._nextStyleNameId=vl()}return t.prototype._generateStyleName=function(){return"__EC_aUTo_"+this._nextStyleNameId++},t.prototype.makeTooltipMarker=function(e,r,i){var n=i==="richText"?this._generateStyleName():null,a=Ec({color:r,type:e,renderMode:i,markerId:n});return U(a)?a:(this.richTextStyles[n]=a.style,a.content)},t.prototype.wrapRichTextStyle=function(e,r){var i={};k(r)?T(r,function(a){return P(i,a)}):P(i,r);var n=this._generateStyleName();return this.richTextStyles[n]=i,"{"+n+"|"+e+"}"},t})();function GT(t){var e=t.series,r=t.dataIndex,i=t.multipleSeries,n=e.getData(),a=n.mapDimensionsAll("defaultedTooltip"),o=a.length,s=e.getRawValue(r),l=k(s),u=VT(e,r),f,c,p,h;if(o>1||l&&!o){var v=wN(s,e,r,a,u);f=v.inlineValues,c=v.inlineValueTypes,p=v.blocks,h=v.inlineValues[0]}else if(o){var d=n.getDimensionInfo(a[0]);h=f=Bi(n,r,a[0]),c=d.type}else h=f=l?s[0]:s;var m=Ro(e),g=m&&e.name||"",y=n.getName(r),_=i?g:y;return Ya("section",{header:g,noHeader:i||!m,sortParam:h,blocks:[Ya("nameValue",{markerType:"item",markerColor:u,name:_,noName:!pt(_),value:f,valueType:c,rawDataIndex:n.getRawIndex(r)})].concat(p||[])})}function wN(t,e,r,i,n){var a=e.getData(),o=Ct(t,function(c,p,h){var v=a.getDimensionInfo(h);return c=c||v&&v.tooltip!==!1&&v.displayName!=null},!1),s=[],l=[],u=[];i.length?T(i,function(c){f(Bi(a,r,c),c)}):T(t,f);function f(c,p){var h=a.getDimensionInfo(p);!h||h.otherDims.tooltip===!1||(o?u.push(Ya("nameValue",{markerType:"subItem",markerColor:n,name:h.displayName,value:c,valueType:h.type})):(s.push(c),l.push(h.type)))}return{inlineValues:s,inlineValueTypes:l,blocks:u}}var wn=te();function jc(t,e){return t.getName(e)||t.getId(e)}var CN="__universalTransitionEnabled",Qc=(function(t){L(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r._selectedDataIndicesMap={},r}return e.prototype.init=function(r,i,n){this.seriesIndex=this.componentIndex,this.dataTask=Tn({count:DN,reset:EN}),this.dataTask.context={model:this},this.mergeDefaultAndTheme(r,n);var a=wn(this).sourceManager=new RT(this);a.prepareSource();var o=this.getInitialData(r,n);YT(o,this),this.dataTask.context.data=o,wn(this).dataBeforeProcessed=o,HT(this),this._initSelectedMapFromData(o)},e.prototype.mergeDefaultAndTheme=function(r,i){var n=Sn(this),a=n?Dr(r):{},o=this.subType;Se.hasClass(o)&&(o+="Series"),he
Line truncated
|
||||
|
||||
`:"<br/>",g=c.join(m);this._showOrMove(s,function(){this._updateContentNotChangedOnAxis(r,u)?this._updatePosition(s,h,o[0],o[1],this._tooltipContent,u):this._showTooltipContent(s,g,u,Math.random()+"",o[0],o[1],h,null,p)})},e.prototype._showSeriesItemTooltip=function(r,i,n){var a=this._ecModel,o=me(i),s=o.seriesIndex,l=a.getSeriesByIndex(s),u=o.dataModel||l,f=o.dataIndex,c=o.dataType,p=u.getData(c),h=this._renderMode,v=r.positionDefault,d=Du([p.getItemModel(f),u,l&&(l.coordinateSystem||{}).model],this._tooltipModel,v?{position:v}:null),m=d.get("trigger");if(!(m!=null&&m!=="item")){var g=u.getDataParams(f,c),y=new Kc;g.marker=y.makeTooltipMarker("item",Yr(g.color),h);var _=km(u.formatTooltip(f,!1,c)),x=d.get("order"),S=d.get("valueFormatter"),b=_.frag,w=b?Gm(S?P({valueFormatter:S},b):b,y,h,x,a.get("useUTC"),d.get("textStyle")):_.text,A="item_"+u.name+"_"+f;this._showOrMove(d,function(){this._showTooltipContent(d,w,g,A,r.offsetX,r.offsetY,r.position,r.target,y)}),n({type:"showTip",dataIndexInside:f,dataIndex:p.getRawIndex(f),seriesIndex:s,from:this.uid})}},e.prototype._showComponentItemTooltip=function(r,i,n){var a=this._renderMode==="html",o=me(i),s=o.tooltipConfig,l=s.option||{},u=l.encodeHTMLContent;if(U(l)){var f=l;l={content:f,formatter:f},u=!0}u&&a&&l.content&&(l=$(l),l.content=Je(l.content));var c=[l],p=this._ecModel.getComponent(o.componentMainType,o.componentIndex);p&&c.push(p),c.push({formatter:l.content});var h=r.positionDefault,v=Du(c,this._tooltipModel,h?{position:h}:null),d=v.get("content"),m=Math.random()+"",g=new Kc;this._showOrMove(v,function(){var y=$(v.get("formatterParams")||{});this._showTooltipContent(v,d,y,m,r.offsetX,r.offsetY,r.position,i,g)}),n({type:"showTip",from:this.uid})},e.prototype._showTooltipContent=function(r,i,n,a,o,s,l,u,f){if(this._ticket="",!(!r.get("showContent")||!r.get("show"))){var c=this._tooltipContent;c.setEnterable(r.get("enterable"));var p=r.get("formatter");l=l||r.get("position");var h=i,v=this._getNearestPoint([o,s],n,r.get("trigger"),r.get("borderColor"),r.get("defaultBorderColor",!0)),d=v.color;if(p)if(U(p)){var m=r.ecModel.get("useUTC"),g=k(n)?n[0]:n,y=g&&g.axisType&&g.axisType.indexOf("time")>=0;h=p,y&&(h=Ri(g.axisValue,h,m)),h=ss(h,n,!0)}else if(X(p)){var _=J(function(x,S){x===this._ticket&&(c.setContent(S,f,r,d,l),this._updatePosition(r,l,o,s,c,n,u))},this);this._ticket=a,h=p(n,a,_)}else h=p;c.setContent(h,f,r,d,l),c.show(r,d),this._updatePosition(r,l,o,s,c,n,u)}},e.prototype._getNearestPoint=function(r,i,n,a,o){if(n==="axis"||k(i))return{color:a||o};if(!k(i))return{color:a||i.color||i.borderColor}},e.prototype._updatePosition=function(r,i,n,a,o,s,l){var u=this._api.getWidth(),f=this._api.getHeight();i=i||r.get("position");var c=o.getSize(),p=r.get("align"),h=r.get("verticalAlign"),v=l&&l.getBoundingRect().clone();if(l&&v.applyTransform(l.transform),X(i)&&(i=i([n,a],s,o.el,v,{viewSize:[u,f],contentSize:c.slice()})),k(i))n=we(i[0],u),a=we(i[1],f);else if(Y(i)){var d=i;d.width=c[0],d.height=c[1];var m=jt(d,{width:u,height:f});n=m.x,a=m.y,p=null,h=null}else if(U(i)&&l){var g=jz(i,v,c,r.get("borderWidth"));n=g[0],a=g[1]}else{var g=qz(n,a,o,u,f,p?null:20,h?null:20);n=g[0],a=g[1]}if(p&&(n-=dE(p)?c[0]/2:p==="right"?c[0]:0),h&&(a-=dE(h)?c[1]/2:h==="bottom"?c[1]:0),bh(r)){var g=Kz(n,a,o,u,f);n=g[0],a=g[1]}o.moveTo(n,a)},e.prototype._updateContentNotChangedOnAxis=function(r,i){var n=this._lastDataByCoordSys,a=this._cbParamsList,o=!!n&&n.length===r.length;return o&&T(n,function(s,l){var u=s.dataByAxis||[],f=r[l]||{},c=f.dataByAxis||[];o=o&&u.length===c.length,o&&T(u,function(p,h){var v=c[h]||{},d=p.seriesDataIndices||[],m=v.seriesDataIndices||[];o=o&&p.value===v.value&&p.axisType===v.axisType&&p.axisId===v.axisId&&d.length===m.length,o&&T(d,function(g,y){var _=m[y];o=o&&g.seriesIndex===_.seriesIndex&&g.dataIndex===_.dataIndex}),a&&T(p.seriesDataIndices,function(g){var y=g.seriesIndex,_=i[y],x=a[y];_&&x&&x.data!==_.data&&(o=!1)})})}),this._lastDataByCoordSys=r,this._cbParamsList=i,!!o},e.prototype._hide=function(r){this._lastDataByCoordSys=null,this._cbParamsList=null,r({type:"hideTip",from:this.uid})},e.prototype.dispose=function(r,i){oe.node||!i.getDom()||(Za(this,"_updatePosition"),this._tooltipContent.dispose(),wu("itemTooltip",i),this._tooltipContent=null,this._tooltipModel=null,this._lastDataByCoordSys=null,this._cbParamsList=null)},e.type="tooltip",e})(et);function Du(t,e,r){var i=e.ecModel,n;r?(n=new Ce(r,i,i),n=new Ce(e.option,n,i)):n=e;for(var a=t.length-1;a>=0;a--){var o=t[a];o&&(o instanceof Ce&&(o=o.get("tooltip",!0)),U(o)&&(o={formatter:o}),o&&(n=new Ce(o,n,i)))}return n}function vE(t,e){return t.dispatchAction||J(e.dispatchAction,e)}function qz(t,e,r,i,n,a,o){var s=r.getSize(),l=s[0],u=s[1];return a!=null&&(t+l+a+2>i?t-=l+a:t+=a),o!=null&&(e+u+o>n?e-=u+o:e+=o),[t,e]}function Kz(t,e,r,i,n){var a=r.getSize(),o=a[0],s=a[1];return t=Math.min(t+o,i)-o,e=Math.min(e+s,n)-s,t=Math.max(t,0),e=Math.max(e,0),[t,e]}function jz(t,e,r,i){var n=r[0],a=r[1],o=Math.c
Line truncated
|
||||
`)){var y=new Th;y.newline=!0,u.add(y);return}var _=n.getSeriesByName(g)[0];if(!f.get(g))if(_){var x=_.getData(),S=x.getVisual("legendLineStyle")||{},b=x.getVisual("legendIcon"),w=x.getVisual("style"),A=this._createItem(_,g,d,v,i,r,S,w,b,c,a);A.on("click",Us(gE,g,null,a,h)).on("mouseover",Us(t0,_.name,null,a,h)).on("mouseout",Us(r0,_.name,null,a,h)),n.ssr&&A.eachChild(function(C){var D=me(C);D.seriesIndex=_.seriesIndex,D.dataIndex=d,D.ssrType="legend"}),p&&A.eachChild(function(C){m.packEventData(C,i,_,d,g)}),f.set(g,!0)}else n.eachRawSeries(function(C){var D=this;if(!f.get(g)&&C.legendVisualProvider){var E=C.legendVisualProvider;if(!E.containName(g))return;var I=E.indexOfName(g),M=E.getItemVisual(I,"style"),O=E.getItemVisual(I,"legendIcon"),N=ht(M.fill);N&&N[3]===0&&(N[3]=.2,M=P(P({},M),{fill:ei(N,"rgba")}));var R=this._createItem(C,g,d,v,i,r,{},M,O,c,a);R.on("click",Us(gE,null,g,a,h)).on("mouseover",Us(t0,null,g,a,h)).on("mouseout",Us(r0,null,g,a,h)),n.ssr&&R.eachChild(function(F){var B=me(F);B.seriesIndex=C.seriesIndex,B.dataIndex=d,B.ssrType="legend"}),p&&R.eachChild(function(F){D.packEventData(F,i,C,d,g)}),f.set(g,!0)}},this)},this),o&&this._createSelector(o,i,a,s,l)},e.prototype.packEventData=function(r,i,n,a,o){var s={componentType:"legend",componentIndex:i.componentIndex,dataIndex:a,value:o,seriesIndex:n.seriesIndex};me(r).eventData=s},e.prototype._createSelector=function(r,i,n,a,o){var s=this.getSelectorGroup();e0(r,function(u){var f=u.type,c=new De({style:{x:0,y:0,align:"center",verticalAlign:"middle"},onclick:function(){n.dispatchAction({type:f==="all"?"legendAllSelect":"legendInverseSelect",legendId:i.id})}});s.add(c);var p=i.getModel("selectorLabel"),h=i.getModel(["emphasis","selectorLabel"]);hi(c,{normal:p,emphasis:h},{defaultText:u.title}),dn(c)})},e.prototype._createItem=function(r,i,n,a,o,s,l,u,f,c,p){var h=r.visualDrawType,v=o.get("itemWidth"),d=o.get("itemHeight"),m=o.isSelected(i),g=a.get("symbolRotate"),y=a.get("symbolKeepAspect"),_=a.get("icon");f=_||f||"roundRect";var x=tU(f,a,l,u,h,m,p),S=new Th,b=a.getModel("textStyle");if(X(r.getLegendIcon)&&(!_||_==="inherit"))S.add(r.getLegendIcon({itemWidth:v,itemHeight:d,icon:f,iconRotate:g,itemStyle:x.itemStyle,lineStyle:x.lineStyle,symbolKeepAspect:y}));else{var w=_==="inherit"&&r.getData().getVisual("symbol")?g==="inherit"?r.getData().getVisual("symbolRotate"):g:0;S.add(rU({itemWidth:v,itemHeight:d,icon:f,iconRotate:w,itemStyle:x.itemStyle,lineStyle:x.lineStyle,symbolKeepAspect:y}))}var A=s==="left"?v+5:-5,C=s,D=o.get("formatter"),E=i;U(D)&&D?E=D.replace("{name}",i!=null?i:""):X(D)&&(E=D(i));var I=m?b.getTextColor():a.get("inactiveColor");S.add(new De({style:fr(b,{text:E,x:A,y:d/2,fill:I,align:C,verticalAlign:"middle"},{inheritColor:I})}));var M=new ge({shape:S.getBoundingRect(),style:{fill:"transparent"}}),O=a.getModel("tooltip");return O.get("show")&&$o({el:M,componentModel:o,itemName:i,itemTooltipOption:O.option}),S.add(M),S.eachChild(function(N){N.silent=!0}),M.silent=!c,this.getContentGroup().add(S),dn(S),S.__legendDataIndex=n,S},e.prototype.layoutInner=function(r,i,n,a,o,s){var l=this.getContentGroup(),u=this.getSelectorGroup();Ba(r.get("orient"),l,r.get("itemGap"),n.width,n.height);var f=l.getBoundingRect(),c=[-f.x,-f.y];if(u.markRedraw(),l.markRedraw(),o){Ba("horizontal",u,r.get("selectorItemGap",!0));var p=u.getBoundingRect(),h=[-p.x,-p.y],v=r.get("selectorButtonGap",!0),d=r.getOrient().index,m=d===0?"width":"height",g=d===0?"height":"width",y=d===0?"y":"x";s==="end"?h[d]+=f[m]+v:c[d]+=p[m]+v,h[1-d]+=f[g]/2-p[g]/2,u.x=h[0],u.y=h[1],l.x=c[0],l.y=c[1];var _={x:0,y:0};return _[m]=f[m]+v+p[m],_[g]=Math.max(f[g],p[g]),_[y]=Math.min(0,p[y]+h[1-d]),_}else return l.x=c[0],l.y=c[1],this.group.getBoundingRect()},e.prototype.remove=function(){this.getContentGroup().removeAll(),this._isFirstRender=!0},e.type="legend.plain",e})(et);function tU(t,e,r,i,n,a,o){function s(m,g){m.lineWidth==="auto"&&(m.lineWidth=g.lineWidth>0?2:0),e0(m,function(y,_){m[_]==="inherit"&&(m[_]=g[_])})}var l=e.getModel("itemStyle"),u=l.getItemStyle(),f=t.lastIndexOf("empty",0)===0?"fill":"stroke",c=l.getShallow("decal");u.decal=!c||c==="inherit"?i.decal:$l(c,o),u.fill==="inherit"&&(u.fill=i[n]),u.stroke==="inherit"&&(u.stroke=i[f]),u.opacity==="inherit"&&(u.opacity=(n==="fill"?i:r).opacity),s(u,i);var p=e.getModel("lineStyle"),h=p.getLineStyle();if(s(h,r),u.fill==="auto"&&(u.fill=i.fill),u.stroke==="auto"&&(u.stroke=i.fill),h.stroke==="auto"&&(h.stroke=i.fill),!a){var v=e.get("inactiveBorderWidth"),d=u[f];u.lineWidth=v==="auto"?i.lineWidth>0&&d?2:0:u.lineWidth,u.fill=e.get("inactiveColor"),u.stroke=e.get("inactiveBorderColor"),h.stroke=p.get("inactiveColor"),h.lineWidth=p.get("inactiveWidth")}return{itemStyle:u,lineStyle:h}}function rU(t){var e=t.icon||"roundRect",r=Lt(e,0,0,t.itemWidth,t.itemHeight,t.itemStyle.fill,t.symbolKeepAspect);return r.setStyle(t.itemStyle),r.rotation=(t.iconRotate||0)*Math.PI/180,r.setOrigin([t.itemWidth/2,t.itemHeight/2]),e.indexOf("empty")>-1
Line truncated
|
||||
`||n==="")){var a=t.isSelected(n);Oe(r,n)?r[n]=r[n]&&a:r[n]=a}}),r}function _E(t){t.registerAction("legendToggleSelect","legendselectchanged",be(Mu,"toggleSelected")),t.registerAction("legendAllSelect","legendselectall",be(Mu,"allSelect")),t.registerAction("legendInverseSelect","legendinverseselect",be(Mu,"inverseSelect")),t.registerAction("legendSelect","legendselected",be(Mu,"select")),t.registerAction("legendUnSelect","legendunselected",be(Mu,"unSelect"))}var xE=on(iU);function iU(t){var e=t.findComponents({mainType:"legend"});e&&e.length&&t.filterSeries(function(r){for(var i=0;i<e.length;i++)if(!e[i].isSelected(r.name))return!1;return!0})}function Ch(t){t.registerComponentModel(Eu),t.registerComponentView(wh),t.registerProcessor(t.PRIORITY.PROCESSOR.SERIES_FILTER,xE),t.registerSubTypeDefaulter("legend",function(){return"plain"}),_E(t)}var nU=(function(t){L(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.type=e.type,r}return e.prototype.setScrollDataIndex=function(r){this.option.scrollDataIndex=r},e.prototype.init=function(r,i,n){var a=Dr(r);t.prototype.init.call(this,r,i,n),SE(this,r,a)},e.prototype.mergeOption=function(r,i){t.prototype.mergeOption.call(this,r,i),SE(this,this.option,r)},e.type="legend.scroll",e.defaultOption=xn(Eu.defaultOption,{scrollDataIndex:0,pageButtonItemGap:5,pageButtonGap:null,pageButtonPosition:"end",pageFormatter:"{current}/{total}",pageIcons:{horizontal:["M0,0L12,-10L12,10z","M0,0L-12,-10L-12,10z"],vertical:["M0,0L20,0L10,-20z","M0,0L20,0L10,20z"]},pageIconColor:K.color.accent50,pageIconInactiveColor:K.color.accent10,pageIconSize:15,pageTextStyle:{color:K.color.tertiary},animationDurationUpdate:800}),e})(Eu);function SE(t,e,r){var i=t.getOrient(),n=[1,1];n[i.index]=0,pr(e,r,{type:"box",ignoreSize:!!n})}var bE=nU;var TE=xe,i0=["width","height"],n0=["x","y"],aU=(function(t){L(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.type=e.type,r.newlineDisabled=!0,r._currentIndex=0,r}return e.prototype.init=function(){t.prototype.init.call(this),this.group.add(this._containerGroup=new TE),this._containerGroup.add(this.getContentGroup()),this.group.add(this._controllerGroup=new TE)},e.prototype.resetInner=function(){t.prototype.resetInner.call(this),this._controllerGroup.removeAll(),this._containerGroup.removeClipPath(),this._containerGroup.__rectSize=null},e.prototype.renderInner=function(r,i,n,a,o,s,l){var u=this;t.prototype.renderInner.call(this,r,i,n,a,o,s,l);var f=this._controllerGroup,c=i.get("pageIconSize",!0),p=k(c)?c:[c,c];v("pagePrev",0);var h=i.getModel("pageTextStyle");f.add(new De({name:"pageText",style:{text:"xx/xx",fill:h.getTextColor(),font:h.getFont(),verticalAlign:"middle",align:"center"},silent:!0})),v("pageNext",1);function v(d,m){var g=d+"DataIndex",y=Ia(i.get("pageIcons",!0)[i.getOrient().name][m],{onclick:J(u._pageGo,u,g,i,a)},{x:-p[0]/2,y:-p[1]/2,width:p[0],height:p[1]});y.name=d,f.add(y)}},e.prototype.layoutInner=function(r,i,n,a,o,s){var l=this.getSelectorGroup(),u=r.getOrient().index,f=i0[u],c=n0[u],p=i0[1-u],h=n0[1-u];o&&Ba("horizontal",l,r.get("selectorItemGap",!0));var v=r.get("selectorButtonGap",!0),d=l.getBoundingRect(),m=[-d.x,-d.y],g=$(n);o&&(g[f]=n[f]-d[f]-v);var y=this._layoutContentAndController(r,a,g,u,f,p,h,c);if(o){if(s==="end")m[u]+=y[f]+v;else{var _=d[f]+v;m[u]-=_,y[c]-=_}y[f]+=d[f]+v,m[1-u]+=y[h]+y[p]/2-d[p]/2,y[p]=Math.max(y[p],d[p]),y[h]=Math.min(y[h],d[h]+m[1-u]),l.x=m[0],l.y=m[1],l.markRedraw()}return y},e.prototype._layoutContentAndController=function(r,i,n,a,o,s,l,u){var f=this.getContentGroup(),c=this._containerGroup,p=this._controllerGroup;Ba(r.get("orient"),f,r.get("itemGap"),a?n.width:null,a?null:n.height),Ba("horizontal",p,r.get("pageButtonItemGap",!0));var h=f.getBoundingRect(),v=p.getBoundingRect(),d=this._showController=h[o]>n[o],m=[-h.x,-h.y];i||(m[a]=f[u]);var g=[0,0],y=[-v.x,-v.y],_=W(r.get("pageButtonGap",!0),r.get("itemGap",!0));if(d){var x=r.get("pageButtonPosition",!0);x==="end"?y[a]+=n[o]-v[o]:g[a]+=v[o]+_}y[1-a]+=h[s]/2-v[s]/2,f.setPosition(m),c.setPosition(g),p.setPosition(y);var S={x:0,y:0};if(S[o]=d?n[o]:h[o],S[s]=Math.max(h[s],v[s]),S[l]=Math.min(0,v[l]+y[1-a]),c.__rectSize=n[o],d){var b={x:0,y:0};b[o]=Math.max(n[o]-v[o]-_,0),b[s]=S[s],c.setClipPath(new ge({shape:b})),c.__rectSize=b[o]}else p.eachChild(function(A){A.attr({invisible:!0,silent:!0})});var w=this._getPageInfo(r);return w.pageIndex!=null&&Be(f,{x:w.contentPosition[0],y:w.contentPosition[1]},d?r:null),this._updatePageInfoView(r,w),S},e.prototype._pageGo=function(r,i,n){var a=this._getPageInfo(i)[r];a!=null&&n.dispatchAction({type:"legendScroll",scrollDataIndex:a,legendId:i.id})},e.prototype._updatePageInfoView=function(r,i){var n=this._controllerGroup;T(["pagePrev","pageNext"],function(f){var c=f+"DataIndex",p=i[c]!=null,h=n.childOfName(f);h&&(h.setStyle("fill",p?r.get("pageIconColor",!0):r.get("pageIconInactiveColor",!0)),h.cursor=p?"pointer":"default")});var a=n.childOfName("pageText"),o=r.get("pageFormatt
Line truncated
|
||||
`:"";function i(n){var a=n.children,o=n.tag,s=n.attrs,l=n.text;return IU(o,s)+(o!=="style"?Je(l):l||"")+(a?""+r+z(a,function(u){return i(u)}).join(r)+r:"")+OU(o)}return i(t)}function XE(t,e,r){r=r||{};var i=r.newline?`
|
||||
`:"",n=" {"+i,a=i+"}",o=z(ne(t),function(l){return l+n+z(ne(t[l]),function(u){return u+":"+t[l][u]+";"}).join(i)+a}).join(i),s=z(ne(e),function(l){return"@keyframes "+l+n+z(ne(e[l]),function(u){return u+n+z(ne(e[l][u]),function(f){var c=e[l][u][f];return f==="d"&&(c='path("'+c+'")'),f+":"+c+";"}).join(i)+a}).join(i)+a}).join(i);return!o&&!s?"":["<![CDATA[",o,s,"]]>"].join(i)}function Nu(t){return{zrId:t,shadowCache:{},patternCache:{},gradientCache:{},clipPathCache:{},defs:{},cssNodes:{},cssAnims:{},cssStyleCache:{},cssAnimIdx:0,shadowIdx:0,gradientIdx:0,patternIdx:0,clipPathIdx:0}}function x0(t,e,r,i){return Ke("svg","root",{width:t,height:e,xmlns:HE,"xmlns:xlink":y0,version:"1.1",baseProfile:"full",viewBox:i?"0 0 "+t+" "+e:!1},r)}var LU=0;function Eh(){return LU++}var ZE={cubicIn:"0.32,0,0.67,0",cubicOut:"0.33,1,0.68,1",cubicInOut:"0.65,0,0.35,1",quadraticIn:"0.11,0,0.5,0",quadraticOut:"0.5,1,0.89,1",quadraticInOut:"0.45,0,0.55,1",quarticIn:"0.5,0,0.75,0",quarticOut:"0.25,1,0.5,1",quarticInOut:"0.76,0,0.24,1",quinticIn:"0.64,0,0.78,0",quinticOut:"0.22,1,0.36,1",quinticInOut:"0.83,0,0.17,1",sinusoidalIn:"0.12,0,0.39,0",sinusoidalOut:"0.61,1,0.88,1",sinusoidalInOut:"0.37,0,0.63,1",exponentialIn:"0.7,0,0.84,0",exponentialOut:"0.16,1,0.3,1",exponentialInOut:"0.87,0,0.13,1",circularIn:"0.55,0,1,0.45",circularOut:"0,0.55,0.45,1",circularInOut:"0.85,0,0.15,1"},uo="transform-origin";function PU(t,e,r){var i=P({},t.shape);P(i,e),t.buildPath(r,i);var n=new Ah;return n.reset(hf(t)),r.rebuildPath(n,1),n.generateStr(),n.getStr()}function NU(t,e){var r=e.originX,i=e.originY;(r||i)&&(t[uo]=r+"px "+i+"px")}var RU={fill:"fill",opacity:"opacity",lineWidth:"stroke-width",lineDashOffset:"stroke-dashoffset"};function KE(t,e){var r=e.zrId+"-ani-"+e.cssAnimIdx++;return e.cssAnims[r]=t,r}function kU(t,e,r){var i=t.shape.paths,n={},a,o;if(T(i,function(l){var u=Nu(r.zrId);u.animation=!0,Ru(l,{},u,!0);var f=u.cssAnims,c=u.cssNodes,p=ne(f),h=p.length;if(h){o=p[h-1];var v=f[o];for(var d in v){var m=v[d];n[d]=n[d]||{d:""},n[d].d+=m.d||""}for(var g in c){var y=c[g].animation;y.indexOf(o)>=0&&(a=y)}}}),!!a){e.d=!1;var s=KE(n,r);return a.replace(o,s)}}function qE(t){return U(t)?ZE[t]?"cubic-bezier("+ZE[t]+")":To(t)?t:"":""}function Ru(t,e,r,i){var n=t.animators,a=n.length,o=[];if(t instanceof Zo){var s=kU(t,e,r);if(s)o.push(s);else if(!a)return}else if(!a)return;for(var l={},u=0;u<a;u++){var f=n[u],c=[f.getMaxTime()/1e3+"s"],p=qE(f.getClip().easing),h=f.getDelay();p?c.push(p):c.push("linear"),h&&c.push(h/1e3+"s"),f.getLoop()&&c.push("infinite");var v=c.join(" ");l[v]=l[v]||[v,[]],l[v][1].push(f)}function d(y){var _=y[1],x=_.length,S={},b={},w={},A="animation-timing-function";function C(Fe,Te,Pe){for(var fe=Fe.getTracks(),_e=Fe.getMaxTime(),Ut=0;Ut<fe.length;Ut++){var Ye=fe[Ut];if(Ye.needsAnimate()){var Lr=Ye.keyframes,Ft=Ye.propName;if(Pe&&(Ft=Pe(Ft)),Ft)for(var Pr=0;Pr<Lr.length;Pr++){var fo=Lr[Pr],Nr=Math.round(fo.time/_e*100)+"%",Uu=qE(fo.easing),co=fo.rawValue;(U(co)||se(co))&&(Te[Nr]=Te[Nr]||{},Te[Nr][Ft]=fo.rawValue,Uu&&(Te[Nr][A]=Uu))}}}}for(var D=0;D<x;D++){var E=_[D],I=E.targetName;I?I==="shape"&&C(E,b):!i&&C(E,S)}for(var M in S){var O={};en(O,t),P(O,S[M]);var N=vf(O),R=S[M][A];w[M]=N?{transform:N}:{},NU(w[M],O),R&&(w[M][A]=R)}var F,B=!0;for(var M in b){w[M]=w[M]||{};var V=!F,R=b[M][A];V&&(F=new bt);var H=F.len();F.reset(),w[M].d=PU(t,b[M],F);var Z=F.len();if(!V&&H!==Z){B=!1;break}R&&(w[M][A]=R)}if(!B)for(var M in w)delete w[M].d;if(!i)for(var D=0;D<x;D++){var E=_[D],I=E.targetName;I==="style"&&C(E,w,function(fe){return RU[fe]})}for(var Q=ne(w),ee=!0,j,D=1;D<Q.length;D++){var ce=Q[D-1],ye=Q[D];if(w[ce][uo]!==w[ye][uo]){ee=!1;break}j=w[ce][uo]}if(ee&&j){for(var M in w)w[M][uo]&&delete w[M][uo];e[uo]=j}if(Ae(Q,function(Fe){return ne(w[Fe]).length>0}).length){var He=KE(w,r);return He+" "+y[0]+" both"}}for(var m in l){var s=d(l[m]);s&&o.push(s)}if(o.length){var g=r.zrId+"-cls-"+Eh();r.cssNodes["."+g]={animation:o.join(",")},e.class=g}}function QE(t,e,r){if(!t.ignore)if(t.isSilent()){var i={"pointer-events":"none"};jE(i,e,r,!0)}else{var n=t.states.emphasis&&t.states.emphasis.style?t.states.emphasis.style:{},a=n.fill;if(!a){var o=t.style&&t.style.fill,s=t.states.select&&t.states.select.style&&t.states.select.style.fill,l=t.currentStates.indexOf("select")>=0&&s||o;l&&(a=Ao(l))}var u=n.lineWidth;if(u){var f=!n.strokeNoScale&&t.transform?t.transform[0]:1;u=u/f}var i={cursor:"pointer"};a&&(i.fill=a),n.stroke&&(i.stroke=n.stroke),u&&(i["stroke-width"]=u),jE(i,e,r,!0)}}function jE(t,e,r,i){var n=JSON.stringify(t),a=r.cssStyleCache[n];a||(a=r.zrId+"-cls-"+Eh(),r.cssStyleCache[n]=a,r.cssNodes["."+a+(i?":hover":"")]=t),e.class=e.class?e.class+" "+a:a}var ku=Math.round;function tM(t){return t&&U(t.src)}function rM(t){return t&&X(t.toDataURL)}function S0(t,e,r,i){g0(function(n,a){var o=n==="fill"||n==="stroke";o&&pf(a)?C0(e,t,n,i):o&&tl(a)?A0(r,t,n,i):t[n]=a,o&&i.ssr&&a==="none"&&(t["pointer-events"]="visible")},e,r,!1),HU(r,t,i)}function b0(t,e
Line truncated
|
||||
@@ -227,6 +227,10 @@ if (class_exists('AKSRT\\Payment\\CommissionLedger')) {
|
||||
if (class_exists('AKSRT\\Payment\\InviteCode')) {
|
||||
\AKSRT\Payment\InviteCode::get_instance()->init();
|
||||
}
|
||||
// 支付中心统计的按天缓存:订单状态变化 / 删除时失效(需前后台都挂载,回调不走后台)
|
||||
if (class_exists('AKSRT\\Payment\\OrderAnalyticsDaily')) {
|
||||
\AKSRT\Payment\OrderAnalyticsDaily::get_instance()->init();
|
||||
}
|
||||
if (class_exists('\AKSRT\Content\Pay')) {
|
||||
\AKSRT\Content\Pay::get_instance()->init();
|
||||
}
|
||||
|
||||
@@ -185,6 +185,7 @@ class Admin {
|
||||
PermissionsController::class,
|
||||
UserController::class,
|
||||
PaymentController::class,
|
||||
PayOverviewController::class,
|
||||
ToolsController::class,
|
||||
ImportExportController::class,
|
||||
ExtensionController::class,
|
||||
|
||||
@@ -27,23 +27,28 @@ class PayMenus {
|
||||
}
|
||||
|
||||
NativeUi::boot();
|
||||
PayOverview::get_instance()->init();
|
||||
add_action('admin_menu', [$this, 'register_menus']);
|
||||
add_action('admin_notices', [$this, 'pending_notices']);
|
||||
// Domain admin_post handlers are registered by dedicated modules.
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array{slug:string,title:string,callback:callable}>
|
||||
* @return array<string, array{slug:string,title:string,icon:string,desc:string,callback:callable}>
|
||||
*/
|
||||
private function pages(): array {
|
||||
return [
|
||||
'overview' => [
|
||||
'slug' => self::MENU_SLUG,
|
||||
'icon' => 'dashicons-cart',
|
||||
'desc' => '',
|
||||
'title' => __('概览', 'aksrt'),
|
||||
'callback' => [$this, 'render_overview_page'],
|
||||
],
|
||||
'orders' => [
|
||||
'slug' => 'aksrt_orders',
|
||||
'icon' => 'dashicons-list-view',
|
||||
'desc' => __('收款确认、关闭与退款标记', 'aksrt'),
|
||||
'title' => __('订单明细', 'aksrt'),
|
||||
'callback' => static function (): void {
|
||||
if (class_exists('AKSRT\Admin\Native\PayOrders')) {
|
||||
@@ -55,6 +60,8 @@ class PayMenus {
|
||||
],
|
||||
'products' => [
|
||||
'slug' => 'aksrt_pay_products',
|
||||
'icon' => 'dashicons-media-document',
|
||||
'desc' => __('付费文章定价与销量', 'aksrt'),
|
||||
'title' => __('付费文章', 'aksrt'),
|
||||
'callback' => static function (): void {
|
||||
if (class_exists('AKSRT\\Admin\\Native\\PayProducts')) {
|
||||
@@ -66,6 +73,8 @@ class PayMenus {
|
||||
],
|
||||
'coupons' => [
|
||||
'slug' => 'aksrt_coupons',
|
||||
'icon' => 'dashicons-tickets-alt',
|
||||
'desc' => __('创建与批量生成优惠码', 'aksrt'),
|
||||
'title' => __('优惠码', 'aksrt'),
|
||||
'callback' => static function (): void {
|
||||
if (class_exists('AKSRT\\Admin\\Native\\PayCoupons')) {
|
||||
@@ -77,6 +86,8 @@ class PayMenus {
|
||||
],
|
||||
'charge_cards' => [
|
||||
'slug' => 'aksrt_charge_cards',
|
||||
'icon' => 'dashicons-id-alt',
|
||||
'desc' => __('充值卡密生成与导出', 'aksrt'),
|
||||
'title' => __('卡密管理', 'aksrt'),
|
||||
'callback' => static function (): void {
|
||||
if (class_exists('AKSRT\\Admin\\Native\\PayChargeCards')) {
|
||||
@@ -88,6 +99,8 @@ class PayMenus {
|
||||
],
|
||||
'income' => [
|
||||
'slug' => 'aksrt_income',
|
||||
'icon' => 'dashicons-chart-pie',
|
||||
'desc' => __('创作者分成结算', 'aksrt'),
|
||||
'title' => __('创作分成', 'aksrt'),
|
||||
'callback' => static function (): void {
|
||||
if (class_exists('AKSRT\\Admin\\Native\\PayIncome')) {
|
||||
@@ -99,6 +112,8 @@ class PayMenus {
|
||||
],
|
||||
'withdraw' => [
|
||||
'slug' => 'aksrt_withdraw',
|
||||
'icon' => 'dashicons-money-alt',
|
||||
'desc' => __('提现申请审核与打款', 'aksrt'),
|
||||
'title' => __('提现审核', 'aksrt'),
|
||||
'callback' => static function (): void {
|
||||
if (class_exists('AKSRT\\Admin\\Native\\PayWithdrawals')) {
|
||||
@@ -164,72 +179,23 @@ class PayMenus {
|
||||
}
|
||||
}
|
||||
|
||||
private function ensure_cap(): void {
|
||||
if (!current_user_can(self::CAPABILITY)) {
|
||||
wp_die(esc_html__('您没有权限访问此页面。', 'aksrt'));
|
||||
}
|
||||
}
|
||||
|
||||
private function page_url(string $slug, array $args = []): string {
|
||||
return add_query_arg(array_merge(['page' => $slug], $args), admin_url('admin.php'));
|
||||
}
|
||||
|
||||
/** 快捷入口图标,键同 pages()。 */
|
||||
private const PAGE_ICONS = [
|
||||
'orders' => 'dashicons-list-view',
|
||||
'products' => 'dashicons-media-document',
|
||||
'coupons' => 'dashicons-tickets-alt',
|
||||
'charge_cards' => 'dashicons-id-alt',
|
||||
'income' => 'dashicons-chart-pie',
|
||||
'withdraw' => 'dashicons-money-alt',
|
||||
];
|
||||
|
||||
public function render_overview_page(): void {
|
||||
$this->ensure_cap();
|
||||
|
||||
$order_stats = class_exists('\AKSRT\Payment\OrderRepository')
|
||||
? \AKSRT\Payment\OrderRepository::get_instance()->get_stats()
|
||||
: [];
|
||||
$withdraw_stats = class_exists('\AKSRT\Payment\Withdrawal')
|
||||
? \AKSRT\Payment\Withdrawal::get_instance()->get_stats()
|
||||
: [];
|
||||
$card_stats = class_exists('\AKSRT\Payment\ChargeCard')
|
||||
? \AKSRT\Payment\ChargeCard::get_instance()->get_stats()
|
||||
: [];
|
||||
|
||||
NativeUi::page_open();
|
||||
NativeUi::header(
|
||||
__('支付中心', 'aksrt'),
|
||||
__('运营数据入口。配置项仍在 AKSRT 主题设置 SPA。', 'aksrt'),
|
||||
'dashicons-cart',
|
||||
[[
|
||||
'label' => __('打开支付配置(SPA)', 'aksrt'),
|
||||
'url' => admin_url('admin.php?page=aksrt-settings#/payment/gateway'),
|
||||
'icon' => 'dashicons-admin-generic',
|
||||
]]
|
||||
);
|
||||
|
||||
NativeUi::stats([
|
||||
['label' => __('订单总数', 'aksrt'), 'value' => (int) ($order_stats['total'] ?? 0), 'url' => $this->page_url('aksrt_orders'), 'icon' => 'dashicons-list-view', 'tone' => 'info'],
|
||||
['label' => __('待支付', 'aksrt'), 'value' => (int) ($order_stats['pending'] ?? 0), 'url' => $this->page_url('aksrt_orders', ['status' => 'pending']), 'icon' => 'dashicons-clock', 'tone' => 'warning'],
|
||||
['label' => __('已支付', 'aksrt'), 'value' => (int) ($order_stats['paid'] ?? 0), 'url' => $this->page_url('aksrt_orders', ['status' => 'paid']), 'icon' => 'dashicons-yes-alt', 'tone' => 'success'],
|
||||
['label' => __('营收', 'aksrt'), 'value' => (string) ($order_stats['revenue'] ?? '0.00'), 'url' => $this->page_url('aksrt_orders'), 'icon' => 'dashicons-chart-line', 'tone' => 'success'],
|
||||
['label' => __('待审提现', 'aksrt'), 'value' => (int) ($withdraw_stats['pending'] ?? 0), 'url' => $this->page_url('aksrt_withdraw', ['status' => 'pending']), 'icon' => 'dashicons-money-alt', 'tone' => 'danger'],
|
||||
['label' => __('可用卡密', 'aksrt'), 'value' => (int) ($card_stats['active'] ?? 0), 'url' => $this->page_url('aksrt_charge_cards', ['status' => 'active']), 'icon' => 'dashicons-id-alt', 'tone' => 'info'],
|
||||
]);
|
||||
|
||||
NativeUi::card_open(__('快捷入口', 'aksrt'));
|
||||
echo '<ul class="aksrt-native-links">';
|
||||
foreach ($this->pages() as $key => $page) {
|
||||
/**
|
||||
* 概览页快捷入口:除概览外的子页面。
|
||||
*
|
||||
* @return array<int, array{slug:string,title:string,icon:string,desc:string}>
|
||||
*/
|
||||
public function shortcuts(): array {
|
||||
$items = [];
|
||||
foreach ($this->pages() as $page) {
|
||||
if ($page['slug'] === self::MENU_SLUG) {
|
||||
continue;
|
||||
}
|
||||
$icon = self::PAGE_ICONS[$key] ?? 'dashicons-admin-links';
|
||||
echo '<li><a href="' . esc_url($this->page_url($page['slug'])) . '"><span class="dashicons ' . esc_attr($icon) . '" aria-hidden="true"></span>' . esc_html($page['title']) . '</a></li>';
|
||||
$items[] = ['slug' => $page['slug'], 'title' => $page['title'], 'icon' => $page['icon'], 'desc' => $page['desc']];
|
||||
}
|
||||
echo '</ul>';
|
||||
NativeUi::card_close();
|
||||
return $items;
|
||||
}
|
||||
|
||||
NativeUi::page_close();
|
||||
public function render_overview_page(): void {
|
||||
PayOverview::get_instance()->render_page();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
<?php
|
||||
/**
|
||||
* 支付中心「概览」:今日实时、区间指标卡、成交趋势、分类成交、支付方式、转化与退款、热销排行、待办与快捷入口。
|
||||
*
|
||||
* 统计口径见 \AKSRT\Payment\OrderAnalytics。首屏报表(近 30 天)随页面输出为 JSON,
|
||||
* 之后切换区间 / 分类走 REST(aksrt/v1/admin/pay-overview,见 \AKSRT\Admin\PayOverviewController)。
|
||||
* 图表用按需打包的 ECharts(assets/js/runtime/libs/echarts-pay.min.js,tools/build-pay-charts.mjs 生成),
|
||||
* 绘制逻辑在 assets/js/admin-pay-overview.js,样式在 assets/css/admin/pay-overview.css。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Admin\Native;
|
||||
|
||||
use AKSRT\Payment\OrderAnalytics;
|
||||
use AKSRT\Payment\OrderAnalyticsDaily;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class PayOverview {
|
||||
|
||||
private const DEFAULT_DAYS = 30;
|
||||
private const DATA_ID = 'aksrt-pay-overview-data';
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function init(): void {
|
||||
add_action('admin_enqueue_scripts', [$this, 'enqueue_assets']);
|
||||
}
|
||||
|
||||
public function enqueue_assets(): void {
|
||||
$page = isset($_GET['page']) ? sanitize_key((string) wp_unslash($_GET['page'])) : '';
|
||||
if ($page !== PayMenus::MENU_SLUG) {
|
||||
return;
|
||||
}
|
||||
$lib = '/assets/js/runtime/libs/echarts-pay.min.js';
|
||||
$css = '/assets/css/admin/pay-overview.css';
|
||||
$js = '/assets/js/admin-pay-overview.js';
|
||||
wp_enqueue_style('aksrt-admin-pay-overview', AKSRT_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(AKSRT_DIR . $css));
|
||||
wp_enqueue_script('aksrt-echarts-pay', AKSRT_URI . $lib, [], (string) filemtime(AKSRT_DIR . $lib), true);
|
||||
wp_enqueue_script('aksrt-admin-pay-overview', AKSRT_URI . $js, ['aksrt-echarts-pay'], (string) filemtime(AKSRT_DIR . $js), true);
|
||||
}
|
||||
|
||||
public function render_page(): void {
|
||||
if (!current_user_can(PayMenus::CAPABILITY)) {
|
||||
wp_die(esc_html__('您没有权限访问此页面。', 'aksrt'));
|
||||
}
|
||||
|
||||
$analytics = OrderAnalytics::get_instance();
|
||||
$today = current_time('Y-m-d');
|
||||
$from = OrderAnalyticsDaily::shift($today, -(self::DEFAULT_DAYS - 1));
|
||||
$report = $analytics->report($from, $today, '');
|
||||
$realtime = $analytics->today_vs_yesterday();
|
||||
$symbol = $this->currency_symbol();
|
||||
|
||||
NativeUi::page_open('aksrt-po-page');
|
||||
NativeUi::header(
|
||||
__('支付中心', 'aksrt'),
|
||||
__('成交走势、收入结构与待办一览。收款渠道、价格策略等配置在主题设置中修改。', 'aksrt'),
|
||||
'dashicons-cart',
|
||||
[
|
||||
['label' => __('订单明细', 'aksrt'), 'url' => $this->page_url('aksrt_orders'), 'icon' => 'dashicons-list-view'],
|
||||
['label' => __('支付配置', 'aksrt'), 'url' => admin_url('admin.php?page=aksrt-settings#/payment/gateway'), 'icon' => 'dashicons-admin-generic', 'primary' => true],
|
||||
]
|
||||
);
|
||||
|
||||
$this->render_realtime($realtime, $symbol);
|
||||
$this->render_toolbar($from, $today);
|
||||
echo '<div class="aksrt-po-notice" data-aksrt-po-excluded hidden></div>';
|
||||
echo '<div class="aksrt-po-error notice notice-error" data-aksrt-po-error hidden><p></p></div>';
|
||||
$this->render_kpis();
|
||||
|
||||
echo '<div class="aksrt-po-body" data-aksrt-po-body>';
|
||||
echo '<div class="aksrt-po-grid aksrt-po-grid--main">';
|
||||
$this->chart_card('trend', __('成交趋势', 'aksrt'), __('支付金额、净收入、退款金额曲线与支付订单数折线', 'aksrt'), 'lg');
|
||||
$this->render_funnel_card();
|
||||
echo '</div>';
|
||||
|
||||
echo '<div class="aksrt-po-grid aksrt-po-grid--main">';
|
||||
$this->chart_card('groups', __('分类成交', 'aksrt'), __('按商品分类的支付金额曲线', 'aksrt'), 'md');
|
||||
$this->chart_card('methods', __('支付方式', 'aksrt'), __('按支付金额的支付方式占比', 'aksrt'), 'md');
|
||||
echo '</div>';
|
||||
|
||||
echo '<div class="aksrt-po-grid aksrt-po-grid--main">';
|
||||
$this->render_top_card();
|
||||
$this->render_todo_card();
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
|
||||
$this->render_shortcuts_card();
|
||||
|
||||
$payload = [
|
||||
'currency' => $symbol,
|
||||
'today' => $today,
|
||||
'maxDays' => OrderAnalytics::MAX_RANGE_DAYS,
|
||||
'rest' => ['url' => rest_url('aksrt/v1/admin/pay-overview'), 'nonce' => wp_create_nonce('wp_rest')],
|
||||
'initial' => ['from' => $from, 'to' => $today, 'group' => '', 'report' => $report],
|
||||
'i18n' => [
|
||||
'paid' => __('支付金额', 'aksrt'),
|
||||
'net' => __('净收入', 'aksrt'),
|
||||
'refund' => __('退款金额', 'aksrt'),
|
||||
'count' => __('支付订单数', 'aksrt'),
|
||||
'unit' => __('笔', 'aksrt'),
|
||||
'people' => __('人', 'aksrt'),
|
||||
'empty' => __('所选区间暂无成交', 'aksrt'),
|
||||
'loadError' => __('统计数据加载失败,请稍后重试。', 'aksrt'),
|
||||
'basis' => __('较上一周期', 'aksrt'),
|
||||
'new' => __('新增', 'aksrt'),
|
||||
'total' => __('合计', 'aksrt'),
|
||||
'excluded' => __('以下外币订单未计入统计:', 'aksrt'),
|
||||
'noTop' => __('所选区间暂无付费内容成交', 'aksrt'),
|
||||
'badRange' => __('请选择有效的日期区间。', 'aksrt'),
|
||||
],
|
||||
];
|
||||
echo '<script type="application/json" id="' . esc_attr(self::DATA_ID) . '">'
|
||||
. wp_json_encode($payload, JSON_HEX_TAG | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE)
|
||||
. '</script>';
|
||||
|
||||
NativeUi::page_close();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* 今日实时(同时段对比,不随区间变化)
|
||||
* ----------------------------------------------------------------*/
|
||||
|
||||
/** @param array{today:array{amount:float,count:int},yesterday:array{amount:float,count:int},until:string} $data */
|
||||
private function render_realtime(array $data, string $symbol): void {
|
||||
$amount = (float) $data['today']['amount'];
|
||||
$prev = (float) $data['yesterday']['amount'];
|
||||
echo '<section class="aksrt-po-live" aria-label="' . esc_attr__('今日实时', 'aksrt') . '">';
|
||||
echo '<div class="aksrt-po-live__main"><span class="aksrt-po-live__dot" aria-hidden="true"></span>'
|
||||
. '<span class="aksrt-po-live__label">' . esc_html__('今日支付金额', 'aksrt') . '</span>'
|
||||
. '<strong class="aksrt-po-live__value">' . esc_html($symbol . number_format_i18n($amount, 2)) . '</strong>'
|
||||
. $this->delta_html($amount, $prev) . '</div>';
|
||||
echo '<div class="aksrt-po-live__meta">';
|
||||
/* translators: %d order count */
|
||||
echo '<span>' . esc_html(sprintf(__('今日 %d 笔', 'aksrt'), (int) $data['today']['count'])) . '</span>';
|
||||
echo '<span>' . esc_html(sprintf(
|
||||
/* translators: 1 time 2 amount 3 order count */
|
||||
__('昨日 00:00–%1$s:%2$s,%3$d 笔', 'aksrt'),
|
||||
$data['until'],
|
||||
$symbol . number_format_i18n($prev, 2),
|
||||
(int) $data['yesterday']['count']
|
||||
)) . '</span>';
|
||||
echo '</div></section>';
|
||||
}
|
||||
|
||||
/** 同时段环比徽标(服务端渲染,仅今日实时使用;区间指标的环比由前端计算)。 */
|
||||
private function delta_html(float $current, float $previous): string {
|
||||
$basis = __('较昨日同时段', 'aksrt');
|
||||
if ($previous <= 0) {
|
||||
$text = $current > 0 ? __('新增', 'aksrt') : '—';
|
||||
$class = $current > 0 ? 'is-up' : 'is-flat';
|
||||
} else {
|
||||
$delta = round(($current - $previous) / $previous * 100, 1);
|
||||
$class = $delta > 0 ? 'is-up' : ($delta < 0 ? 'is-down' : 'is-flat');
|
||||
$text = ($delta > 0 ? '↑' : ($delta < 0 ? '↓' : '')) . number_format_i18n(abs($delta), 1) . '%';
|
||||
}
|
||||
return '<span class="aksrt-po-delta ' . esc_attr($class) . '">' . esc_html($basis . ' ' . $text) . '</span>';
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* 筛选条 / 指标卡
|
||||
* ----------------------------------------------------------------*/
|
||||
|
||||
private function render_toolbar(string $from, string $to): void {
|
||||
$presets = [
|
||||
'd7' => __('近 7 天', 'aksrt'),
|
||||
'd30' => __('近 30 天', 'aksrt'),
|
||||
'd90' => __('近 90 天', 'aksrt'),
|
||||
'this_month' => __('本月', 'aksrt'),
|
||||
'last_month' => __('上月', 'aksrt'),
|
||||
'd365' => __('近一年', 'aksrt'),
|
||||
];
|
||||
echo '<div class="aksrt-po-toolbar">';
|
||||
echo '<div class="aksrt-po-seg" role="group" data-aksrt-po-presets aria-label="' . esc_attr__('统计区间', 'aksrt') . '">';
|
||||
foreach ($presets as $key => $label) {
|
||||
echo '<button type="button" data-preset="' . esc_attr($key) . '" aria-pressed="' . ($key === 'd30' ? 'true' : 'false') . '">' . esc_html($label) . '</button>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo '<form class="aksrt-po-range" data-aksrt-po-range>';
|
||||
echo '<label><span class="screen-reader-text">' . esc_html__('开始日期', 'aksrt') . '</span>'
|
||||
. '<input type="date" name="from" value="' . esc_attr($from) . '" max="' . esc_attr($to) . '" required></label>';
|
||||
echo '<span aria-hidden="true">–</span>';
|
||||
echo '<label><span class="screen-reader-text">' . esc_html__('结束日期', 'aksrt') . '</span>'
|
||||
. '<input type="date" name="to" value="' . esc_attr($to) . '" max="' . esc_attr($to) . '" required></label>';
|
||||
echo '<label><span class="screen-reader-text">' . esc_html__('商品分类', 'aksrt') . '</span><select name="group">'
|
||||
. '<option value="">' . esc_html__('全部分类', 'aksrt') . '</option>';
|
||||
foreach (OrderAnalytics::group_labels() as $key => $label) {
|
||||
echo '<option value="' . esc_attr($key) . '">' . esc_html($label) . '</option>';
|
||||
}
|
||||
echo '</select></label>';
|
||||
echo '<button type="submit" class="button">' . esc_html__('查询', 'aksrt') . '</button>';
|
||||
echo '</form>';
|
||||
echo '<span class="aksrt-po-range-label" data-aksrt-po-range-label aria-live="polite"></span>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
private function render_kpis(): void {
|
||||
$cards = [
|
||||
['paid_amount', __('支付金额', 'aksrt'), 'success'],
|
||||
['net_amount', __('净收入', 'aksrt'), 'info'],
|
||||
['paid_count', __('支付订单数', 'aksrt'), 'primary'],
|
||||
['buyers', __('支付人数', 'aksrt'), 'primary'],
|
||||
['avg_amount', __('客单价', 'aksrt'), 'warning'],
|
||||
['refund_amount', __('退款金额', 'aksrt'), 'danger'],
|
||||
];
|
||||
echo '<div class="aksrt-po-kpis" data-aksrt-po-kpis>';
|
||||
foreach ($cards as [$key, $label, $tone]) {
|
||||
echo '<div class="aksrt-po-kpi aksrt-po-kpi--' . esc_attr($tone) . '" data-kpi="' . esc_attr($key) . '">'
|
||||
. '<span class="aksrt-po-kpi__label">' . esc_html($label) . '</span>'
|
||||
. '<span class="aksrt-po-kpi__value" data-kpi-value>—</span>'
|
||||
. '<span class="aksrt-po-kpi__foot"><span class="aksrt-po-delta is-flat" data-kpi-delta>—</span>'
|
||||
. '<span class="aksrt-po-kpi__meta" data-kpi-meta></span></span>'
|
||||
. '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* 卡片
|
||||
* ----------------------------------------------------------------*/
|
||||
|
||||
private function chart_card(string $key, string $title, string $label, string $size): void {
|
||||
NativeUi::card_open($title, '', false, 'aksrt-po-card');
|
||||
echo '<div class="aksrt-po-chart aksrt-po-chart--' . esc_attr($size) . '" data-aksrt-po-chart="' . esc_attr($key) . '" role="img" aria-label="' . esc_attr($label) . '"></div>';
|
||||
NativeUi::card_close();
|
||||
}
|
||||
|
||||
private function render_funnel_card(): void {
|
||||
$rows = [
|
||||
['created_count', __('下单数', 'aksrt'), __('区间内创建的订单', 'aksrt')],
|
||||
['conversion', __('支付转化率', 'aksrt'), __('下单后完成支付的比例', 'aksrt')],
|
||||
['close_rate', __('超时关闭率', 'aksrt'), __('未支付而关闭的比例', 'aksrt')],
|
||||
['refund_rate', __('退款率', 'aksrt'), __('退款金额 ÷ 支付金额', 'aksrt')],
|
||||
];
|
||||
NativeUi::card_open(__('转化与退款', 'aksrt'), '', false, 'aksrt-po-card');
|
||||
echo '<ul class="aksrt-po-funnel" data-aksrt-po-funnel>';
|
||||
foreach ($rows as [$key, $label, $hint]) {
|
||||
echo '<li data-metric="' . esc_attr($key) . '"><div class="aksrt-po-funnel__head">'
|
||||
. '<span class="aksrt-po-funnel__label">' . esc_html($label) . '</span>'
|
||||
. '<strong class="aksrt-po-funnel__value" data-value>—</strong></div>'
|
||||
. '<span class="aksrt-po-funnel__bar" aria-hidden="true"><i data-bar></i></span>'
|
||||
. '<span class="aksrt-po-funnel__hint">' . esc_html($hint) . ' <span data-delta></span></span></li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
NativeUi::card_close();
|
||||
}
|
||||
|
||||
private function render_top_card(): void {
|
||||
NativeUi::card_open(__('付费内容热销 TOP 10', 'aksrt'), '', true, 'aksrt-po-card');
|
||||
echo '<div class="aksrt-native-table-wrap"><table class="widefat aksrt-po-top">'
|
||||
. '<caption class="screen-reader-text">' . esc_html__('付费内容热销排行', 'aksrt') . '</caption>'
|
||||
. '<thead><tr><th scope="col" class="column-rank">#</th><th scope="col">' . esc_html__('文章', 'aksrt') . '</th>'
|
||||
. '<th scope="col" class="column-num">' . esc_html__('销量', 'aksrt') . '</th>'
|
||||
. '<th scope="col" class="column-num">' . esc_html__('支付金额', 'aksrt') . '</th>'
|
||||
. '<th scope="col" class="column-share">' . esc_html__('占比', 'aksrt') . '</th></tr></thead>'
|
||||
. '<tbody data-aksrt-po-top></tbody></table></div>';
|
||||
NativeUi::card_close();
|
||||
}
|
||||
|
||||
private function render_todo_card(): void {
|
||||
$orders = class_exists('\AKSRT\Payment\OrderRepository') ? \AKSRT\Payment\OrderRepository::get_instance()->get_orders_status_counts() : [];
|
||||
$withdraw = class_exists('\AKSRT\Payment\Withdrawal') ? \AKSRT\Payment\Withdrawal::get_instance()->get_stats() : [];
|
||||
$income = class_exists('\AKSRT\Payment\Revenue') ? \AKSRT\Payment\Revenue::get_instance()->get_stats() : [];
|
||||
$cards = class_exists('\AKSRT\Payment\ChargeCard') ? \AKSRT\Payment\ChargeCard::get_instance()->get_stats() : [];
|
||||
|
||||
$items = [
|
||||
[__('待审提现', 'aksrt'), (int) ($withdraw['pending'] ?? 0), $this->page_url('aksrt_withdraw', ['status' => 'pending']), 'dashicons-money-alt', 'danger'],
|
||||
[__('打款中提现', 'aksrt'), (int) ($withdraw['processing'] ?? 0), $this->page_url('aksrt_withdraw', ['status' => 'processing']), 'dashicons-update', 'warning'],
|
||||
[__('待结算分成', 'aksrt'), (string) ($income['cash']['pending'] ?? '0.00'), $this->page_url('aksrt_income', ['status' => 'pending']), 'dashicons-chart-pie', 'warning'],
|
||||
[__('待支付订单', 'aksrt'), (int) ($orders['pending'] ?? 0), $this->page_url('aksrt_orders', ['status' => 'pending']), 'dashicons-clock', 'info'],
|
||||
[__('可用卡密', 'aksrt'), (int) ($cards['active'] ?? 0), $this->page_url('aksrt_charge_cards', ['status' => 'active']), 'dashicons-id-alt', 'neutral'],
|
||||
];
|
||||
|
||||
NativeUi::card_open(__('待办事项', 'aksrt'), '', false, 'aksrt-po-card');
|
||||
echo '<ul class="aksrt-po-todo">';
|
||||
foreach ($items as [$label, $value, $url, $icon, $tone]) {
|
||||
$idle = (string) $value === '0' || (string) $value === '0.00';
|
||||
echo '<li><a class="aksrt-po-todo__item aksrt-po-todo__item--' . esc_attr($idle ? 'neutral' : $tone) . '" href="' . esc_url($url) . '">'
|
||||
. '<span class="aksrt-po-todo__icon dashicons ' . esc_attr($icon) . '" aria-hidden="true"></span>'
|
||||
. '<span class="aksrt-po-todo__label">' . esc_html($label) . '</span>'
|
||||
. '<span class="aksrt-po-todo__value">' . esc_html(is_int($value) ? number_format_i18n($value) : $this->currency_symbol() . $value) . '</span>'
|
||||
. '</a></li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
NativeUi::card_close();
|
||||
}
|
||||
|
||||
private function render_shortcuts_card(): void {
|
||||
NativeUi::card_open(__('快捷入口', 'aksrt'), '', false, 'aksrt-po-card');
|
||||
echo '<ul class="aksrt-po-links">';
|
||||
foreach (PayMenus::get_instance()->shortcuts() as $item) {
|
||||
echo '<li><a href="' . esc_url($this->page_url($item['slug'])) . '">'
|
||||
. '<span class="aksrt-po-links__icon dashicons ' . esc_attr($item['icon']) . '" aria-hidden="true"></span>'
|
||||
. '<span class="aksrt-po-links__text"><strong>' . esc_html($item['title']) . '</strong>'
|
||||
. ($item['desc'] !== '' ? '<span>' . esc_html($item['desc']) . '</span>' : '')
|
||||
. '</span></a></li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
NativeUi::card_close();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* 助手
|
||||
* ----------------------------------------------------------------*/
|
||||
|
||||
private function currency_symbol(): string {
|
||||
$paid = \AKSRT\Settings\Settings::get_paid_content_settings();
|
||||
return (string) ($paid['display']['currency_symbol'] ?? '¥');
|
||||
}
|
||||
|
||||
/** @param array<string, string> $args */
|
||||
private function page_url(string $slug, array $args = []): string {
|
||||
return add_query_arg(array_merge(['page' => $slug], $args), admin_url('admin.php'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* 支付中心概览的数据接口:GET aksrt/v1/admin/pay-overview?from=Y-m-d&to=Y-m-d&group=
|
||||
*
|
||||
* 仅管理员(manage_options + wp_rest nonce,见 ControllerBase::check_permission)。
|
||||
* 统计口径见 \AKSRT\Payment\OrderAnalytics。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Admin;
|
||||
|
||||
use AKSRT\Payment\OrderAnalytics;
|
||||
use AKSRT\Payment\OrderAnalyticsDaily;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class PayOverviewController extends ControllerBase {
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function register_routes(): void {
|
||||
register_rest_route(self::API_NAMESPACE, '/admin/pay-overview', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_overview'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
'args' => [
|
||||
'from' => ['type' => 'string', 'required' => true, 'sanitize_callback' => 'sanitize_text_field'],
|
||||
'to' => ['type' => 'string', 'required' => true, 'sanitize_callback' => 'sanitize_text_field'],
|
||||
'group' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_key'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function get_overview(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$from = (string) $request->get_param('from');
|
||||
$to = (string) $request->get_param('to');
|
||||
$group = (string) $request->get_param('group');
|
||||
|
||||
$error = self::validate_range($from, $to);
|
||||
if ($error !== '') {
|
||||
return $this->error($error, 400);
|
||||
}
|
||||
if ($group !== '' && !isset(OrderAnalytics::group_labels()[$group])) {
|
||||
return $this->error(__('商品分类无效。', 'aksrt'), 400);
|
||||
}
|
||||
|
||||
return $this->success(['data' => OrderAnalytics::get_instance()->report($from, $to, $group)]);
|
||||
}
|
||||
|
||||
/** 区间校验:合法日期、起止有序、不晚于今天、不超过上限天数。返回空串表示通过。 */
|
||||
public static function validate_range(string $from, string $to): string {
|
||||
if (!OrderAnalyticsDaily::is_date($from) || !OrderAnalyticsDaily::is_date($to)) {
|
||||
return __('日期格式无效。', 'aksrt');
|
||||
}
|
||||
if ($from > $to) {
|
||||
return __('开始日期不能晚于结束日期。', 'aksrt');
|
||||
}
|
||||
if ($to > current_time('Y-m-d')) {
|
||||
return __('结束日期不能晚于今天。', 'aksrt');
|
||||
}
|
||||
if (count(OrderAnalyticsDaily::dates($from, $to)) > OrderAnalytics::MAX_RANGE_DAYS) {
|
||||
/* translators: %d max days */
|
||||
return sprintf(__('统计区间最长 %d 天。', 'aksrt'), OrderAnalytics::MAX_RANGE_DAYS);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -60,10 +60,6 @@ class Order {
|
||||
return $this->repo()->update_status($order_number, $new_status, $extra);
|
||||
}
|
||||
|
||||
public function get_stats(int $user_id = 0): array {
|
||||
return $this->repo()->get_stats($user_id);
|
||||
}
|
||||
|
||||
public function has_paid_access(int $user_id, string $product_type, int $product_id): bool {
|
||||
return $this->repo()->has_paid_access($user_id, $product_type, $product_id);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,379 @@
|
||||
<?php
|
||||
/**
|
||||
* 支付中心概览报表:在 OrderAnalyticsDaily 的按天记录上汇总区间指标、趋势序列与结构分析。
|
||||
*
|
||||
* 口径(详见 OrderAnalyticsDaily):
|
||||
* - 支付金额按支付时间归日,包含后来退款的订单;退款金额按退款时间归日;净收入 = 支付 − 退款;
|
||||
* - 只统计站点主币种现金订单,不含积分订单与余额充值;外币订单在 excluded 中单独列出;
|
||||
* - 支付转化率 / 超时关闭率按下单时间归日:区间内下单的订单里已支付 / 已关闭的占比。
|
||||
*
|
||||
* 金额在内部一律用「分」(整数)累加,输出时才转成元。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Payment;
|
||||
|
||||
use AKSRT\Core\DomainModel;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class OrderAnalytics {
|
||||
|
||||
public const GROUP_CONTENT = 'content';
|
||||
public const GROUP_VIP = 'vip';
|
||||
public const GROUP_POINTS = 'points';
|
||||
public const GROUP_OTHER = 'other';
|
||||
|
||||
public const MAX_RANGE_DAYS = 366;
|
||||
|
||||
/** 区间超过该天数时趋势按月聚合。 */
|
||||
private const MONTHLY_THRESHOLD = 92;
|
||||
|
||||
private const TOP_LIMIT = 10;
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
/** @var array<string,string>|null */
|
||||
private ?array $method_labels = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/** @return array<string, string> 分组键 => 名称(顺序即图例顺序) */
|
||||
public static function group_labels(): array {
|
||||
return [
|
||||
self::GROUP_CONTENT => __('付费内容', 'aksrt'),
|
||||
self::GROUP_VIP => __('会员', 'aksrt'),
|
||||
self::GROUP_POINTS => __('购买积分', 'aksrt'),
|
||||
self::GROUP_OTHER => __('其他', 'aksrt'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function group_of(string $product_type): string {
|
||||
if (DomainModel::is_resource_product_type($product_type)) {
|
||||
return self::GROUP_CONTENT;
|
||||
}
|
||||
return match ($product_type) {
|
||||
DomainModel::ORDER_PRODUCT_VIP => self::GROUP_VIP,
|
||||
DomainModel::ORDER_PRODUCT_POINTS => self::GROUP_POINTS,
|
||||
default => self::GROUP_OTHER,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 区间报表。$from / $to 为站点时区 Y-m-d(调用方已校验),$group 为空表示全部分类。
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function report(string $from, string $to, string $group = ''): array {
|
||||
$daily = OrderAnalyticsDaily::get_instance();
|
||||
$records = $daily->records($from, $to);
|
||||
$days = count($records);
|
||||
$prev_to = OrderAnalyticsDaily::shift($from, -1);
|
||||
$prev_from = OrderAnalyticsDaily::shift($from, -$days);
|
||||
$previous = $daily->records($prev_from, $prev_to);
|
||||
$granularity = $days > self::MONTHLY_THRESHOLD ? 'month' : 'day';
|
||||
|
||||
return [
|
||||
'range' => ['from' => $from, 'to' => $to, 'days' => $days, 'granularity' => $granularity, 'group' => $group],
|
||||
'previous_range' => ['from' => $prev_from, 'to' => $prev_to],
|
||||
'totals' => $this->totals($records, $group) + ['buyers' => $this->buyers($from, $to, $group)],
|
||||
'previous' => $this->totals($previous, $group) + ['buyers' => $this->buyers($prev_from, $prev_to, $group)],
|
||||
'series' => $this->series($records, $group, $granularity),
|
||||
'methods' => $this->methods($records, $group),
|
||||
'top' => in_array($group, ['', self::GROUP_CONTENT], true) ? $this->top_products($from, $to) : [],
|
||||
'excluded' => $this->excluded($from, $to),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 今日截至此刻 vs 昨日同一时段(支付侧)。
|
||||
*
|
||||
* @return array{today:array{amount:float,count:int},yesterday:array{amount:float,count:int},until:string}
|
||||
*/
|
||||
public function today_vs_yesterday(): array {
|
||||
$today = current_time('Y-m-d');
|
||||
$clock = current_time('H:i:s');
|
||||
$yday = OrderAnalyticsDaily::shift($today, -1);
|
||||
return [
|
||||
'today' => $this->paid_window($today . ' 00:00:00', $today . ' ' . $clock),
|
||||
'yesterday' => $this->paid_window($yday . ' 00:00:00', $yday . ' ' . $clock),
|
||||
'until' => substr($clock, 0, 5),
|
||||
];
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* 基于按天记录的汇总
|
||||
* ----------------------------------------------------------------*/
|
||||
|
||||
/** @param array<string, array<string, array>> $records */
|
||||
private function totals(array $records, string $group): array {
|
||||
$paid = $paid_count = $refund = $refund_count = $created = $created_paid = $closed = 0;
|
||||
foreach ($records as $record) {
|
||||
foreach ($record['paid'] ?? [] as $type => $methods) {
|
||||
if ($this->in_group((string) $type, $group)) {
|
||||
foreach ($methods as $cell) {
|
||||
$paid += (int) $cell[0];
|
||||
$paid_count += (int) $cell[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($record['refund'] ?? [] as $type => $cell) {
|
||||
if ($this->in_group((string) $type, $group)) {
|
||||
$refund += (int) $cell[0];
|
||||
$refund_count += (int) $cell[1];
|
||||
}
|
||||
}
|
||||
foreach ($record['created'] ?? [] as $type => $cell) {
|
||||
if ($this->in_group((string) $type, $group)) {
|
||||
$created += (int) $cell[0];
|
||||
$created_paid += (int) $cell[1];
|
||||
$closed += (int) $cell[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'paid_amount' => self::yuan($paid),
|
||||
'paid_count' => $paid_count,
|
||||
'refund_amount' => self::yuan($refund),
|
||||
'refund_count' => $refund_count,
|
||||
'net_amount' => self::yuan($paid - $refund),
|
||||
'avg_amount' => $paid_count > 0 ? self::yuan((int) round($paid / $paid_count)) : 0.0,
|
||||
'refund_rate' => $paid > 0 ? round($refund / $paid * 100, 1) : null,
|
||||
'created_count' => $created,
|
||||
'conversion' => $created > 0 ? round($created_paid / $created * 100, 1) : null,
|
||||
'close_rate' => $created > 0 ? round($closed / $created * 100, 1) : null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 趋势序列:支付金额、退款金额、净收入、支付订单数,以及按分类的支付金额。
|
||||
*
|
||||
* @param array<string, array<string, array>> $records
|
||||
*/
|
||||
private function series(array $records, string $group, string $granularity): array {
|
||||
$buckets = [];
|
||||
foreach ($records as $date => $record) {
|
||||
$bucket = $granularity === 'month' ? substr((string) $date, 0, 7) : (string) $date;
|
||||
$buckets[$bucket] ??= ['paid' => 0, 'refund' => 0, 'count' => 0, 'groups' => array_fill_keys(array_keys(self::group_labels()), 0)];
|
||||
foreach ($record['paid'] ?? [] as $type => $methods) {
|
||||
$type_group = self::group_of((string) $type);
|
||||
foreach ($methods as $cell) {
|
||||
$buckets[$bucket]['groups'][$type_group] += (int) $cell[0];
|
||||
if ($this->in_group((string) $type, $group)) {
|
||||
$buckets[$bucket]['paid'] += (int) $cell[0];
|
||||
$buckets[$bucket]['count'] += (int) $cell[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($record['refund'] ?? [] as $type => $cell) {
|
||||
if ($this->in_group((string) $type, $group)) {
|
||||
$buckets[$bucket]['refund'] += (int) $cell[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$out = ['labels' => array_keys($buckets), 'paid' => [], 'refund' => [], 'net' => [], 'count' => [], 'groups' => []];
|
||||
$group_data = array_fill_keys(array_keys(self::group_labels()), []);
|
||||
foreach ($buckets as $bucket) {
|
||||
$out['paid'][] = self::yuan($bucket['paid']);
|
||||
$out['refund'][] = self::yuan($bucket['refund']);
|
||||
$out['net'][] = self::yuan($bucket['paid'] - $bucket['refund']);
|
||||
$out['count'][] = $bucket['count'];
|
||||
foreach ($bucket['groups'] as $key => $minor) {
|
||||
$group_data[$key][] = self::yuan($minor);
|
||||
}
|
||||
}
|
||||
foreach (self::group_labels() as $key => $name) {
|
||||
if (array_sum($group_data[$key]) > 0) {
|
||||
$out['groups'][] = ['key' => $key, 'name' => $name, 'data' => $group_data[$key]];
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付方式占比(按支付金额降序)。
|
||||
*
|
||||
* @param array<string, array<string, array>> $records
|
||||
* @return array<int, array{key:string,name:string,amount:float,count:int}>
|
||||
*/
|
||||
private function methods(array $records, string $group): array {
|
||||
$sum = [];
|
||||
foreach ($records as $record) {
|
||||
foreach ($record['paid'] ?? [] as $type => $methods) {
|
||||
if (!$this->in_group((string) $type, $group)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($methods as $method => $cell) {
|
||||
$sum[$method] ??= [0, 0];
|
||||
$sum[$method][0] += (int) $cell[0];
|
||||
$sum[$method][1] += (int) $cell[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
uasort($sum, static fn(array $a, array $b): int => $b[0] <=> $a[0]);
|
||||
|
||||
$out = [];
|
||||
foreach ($sum as $method => $cell) {
|
||||
$out[] = ['key' => (string) $method, 'name' => $this->method_label((string) $method), 'amount' => self::yuan($cell[0]), 'count' => $cell[1]];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* 需要直接查询的指标(跨天去重、明细排行、时段对比)
|
||||
* ----------------------------------------------------------------*/
|
||||
|
||||
/** 区间内的支付人数(按用户去重;游客订单 user_id=0 不计)。 */
|
||||
private function buyers(string $from, string $to, string $group): int {
|
||||
global $wpdb;
|
||||
[$where, $params] = $this->paid_window_where($from . ' 00:00:00', $to . ' 23:59:59', $group);
|
||||
return (int) $wpdb->get_var($wpdb->prepare(
|
||||
'SELECT COUNT(DISTINCT user_id) FROM ' . OrderAnalyticsDaily::table() . " WHERE {$where} AND user_id > 0",
|
||||
$params
|
||||
));
|
||||
}
|
||||
|
||||
/** @return array{amount:float,count:int} */
|
||||
private function paid_window(string $start, string $end): array {
|
||||
global $wpdb;
|
||||
[$where, $params] = $this->paid_window_where($start, $end, '');
|
||||
$row = $wpdb->get_row($wpdb->prepare(
|
||||
'SELECT COUNT(*) AS c, COALESCE(SUM(amount), 0) AS a FROM ' . OrderAnalyticsDaily::table() . " WHERE {$where}",
|
||||
$params
|
||||
), ARRAY_A);
|
||||
return [
|
||||
'amount' => self::yuan(OrderAnalyticsDaily::minor($row['a'] ?? 0)),
|
||||
'count' => (int) ($row['c'] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 付费内容热销排行(按支付金额)。
|
||||
*
|
||||
* @return array<int, array{id:int,title:string,url:string,amount:float,count:int}>
|
||||
*/
|
||||
private function top_products(string $from, string $to): array {
|
||||
global $wpdb;
|
||||
[$where, $params] = $this->paid_window_where($from . ' 00:00:00', $to . ' 23:59:59', self::GROUP_CONTENT);
|
||||
$params[] = self::TOP_LIMIT;
|
||||
$rows = (array) $wpdb->get_results($wpdb->prepare(
|
||||
'SELECT product_id AS id, COUNT(*) AS c, COALESCE(SUM(amount), 0) AS a FROM ' . OrderAnalyticsDaily::table()
|
||||
. " WHERE {$where} AND product_id > 0 GROUP BY product_id ORDER BY a DESC, c DESC LIMIT %d",
|
||||
$params
|
||||
), ARRAY_A);
|
||||
|
||||
$ids = array_map(static fn(array $row): int => (int) $row['id'], $rows);
|
||||
if ($ids !== []) {
|
||||
_prime_post_caches($ids, false, false);
|
||||
}
|
||||
$out = [];
|
||||
foreach ($rows as $row) {
|
||||
$id = (int) $row['id'];
|
||||
$title = (string) get_the_title($id);
|
||||
$out[] = [
|
||||
'id' => $id,
|
||||
'title' => $title !== '' ? $title : sprintf(/* translators: %d post ID */ __('文章 #%d', 'aksrt'), $id),
|
||||
'url' => (string) get_edit_post_link($id, 'raw'),
|
||||
'amount' => self::yuan(OrderAnalyticsDaily::minor($row['a'] ?? 0)),
|
||||
'count' => (int) ($row['c'] ?? 0),
|
||||
];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 未计入主口径的外币现金订单(按币种),供页面提示。
|
||||
*
|
||||
* @return array<int, array{currency:string,amount:float,count:int}>
|
||||
*/
|
||||
private function excluded(string $from, string $to): array {
|
||||
global $wpdb;
|
||||
$paid = OrderAnalyticsDaily::paid_statuses();
|
||||
$in = implode(', ', array_fill(0, count($paid), '%s'));
|
||||
$start = $from . ' 00:00:00';
|
||||
$end = $to . ' 23:59:59';
|
||||
$guard = OrderAnalyticsDaily::shift($from, -OrderAnalyticsDaily::CREATED_SLACK_DAYS) . ' 00:00:00';
|
||||
$rows = (array) $wpdb->get_results($wpdb->prepare(
|
||||
'SELECT UPPER(currency) AS cur, COUNT(*) AS c, COALESCE(SUM(amount), 0) AS a FROM ' . OrderAnalyticsDaily::table()
|
||||
. " WHERE currency NOT IN (%s, 'points') AND payment_method <> 'points' AND product_type <> %s AND status IN ({$in})
|
||||
AND created_at >= %s AND created_at <= %s AND COALESCE(paid_at, created_at) BETWEEN %s AND %s
|
||||
GROUP BY cur ORDER BY c DESC",
|
||||
array_merge([OrderAnalyticsDaily::CURRENCY, DomainModel::ORDER_PRODUCT_RECHARGE], $paid, [$guard, $end, $start, $end])
|
||||
), ARRAY_A);
|
||||
return array_map(static fn(array $row): array => [
|
||||
'currency' => (string) $row['cur'],
|
||||
'amount' => self::yuan(OrderAnalyticsDaily::minor($row['a'] ?? 0)),
|
||||
'count' => (int) ($row['c'] ?? 0),
|
||||
], $rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按支付时间窗口的主口径 WHERE(含 created_at 索引下界)。
|
||||
*
|
||||
* @return array{0:string,1:array<int,mixed>}
|
||||
*/
|
||||
private function paid_window_where(string $start, string $end, string $group): array {
|
||||
[$base, $params] = OrderAnalyticsDaily::base_where();
|
||||
$paid = OrderAnalyticsDaily::paid_statuses();
|
||||
$guard = OrderAnalyticsDaily::shift(substr($start, 0, 10), -OrderAnalyticsDaily::CREATED_SLACK_DAYS) . ' 00:00:00';
|
||||
$where = $base . ' AND status IN (' . implode(', ', array_fill(0, count($paid), '%s')) . ')'
|
||||
. ' AND created_at >= %s AND created_at <= %s AND COALESCE(paid_at, created_at) BETWEEN %s AND %s';
|
||||
$params = array_merge($params, $paid, [$guard, $end, $start, $end]);
|
||||
|
||||
if ($group !== '') {
|
||||
[$group_sql, $group_params] = $this->group_where($group);
|
||||
$where .= ' AND ' . $group_sql;
|
||||
$params = array_merge($params, $group_params);
|
||||
}
|
||||
return [$where, $params];
|
||||
}
|
||||
|
||||
/** @return array{0:string,1:array<int,string>} */
|
||||
private function group_where(string $group): array {
|
||||
$content = DomainModel::resource_order_product_types();
|
||||
$known = array_merge($content, [DomainModel::ORDER_PRODUCT_VIP, DomainModel::ORDER_PRODUCT_POINTS]);
|
||||
$list = static fn(array $types): string => implode(', ', array_fill(0, count($types), '%s'));
|
||||
return match ($group) {
|
||||
self::GROUP_CONTENT => ['product_type IN (' . $list($content) . ')', $content],
|
||||
self::GROUP_VIP => ['product_type = %s', [DomainModel::ORDER_PRODUCT_VIP]],
|
||||
self::GROUP_POINTS => ['product_type = %s', [DomainModel::ORDER_PRODUCT_POINTS]],
|
||||
default => ['product_type NOT IN (' . $list($known) . ')', $known],
|
||||
};
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* 助手
|
||||
* ----------------------------------------------------------------*/
|
||||
|
||||
private function in_group(string $product_type, string $group): bool {
|
||||
return $group === '' || self::group_of($product_type) === $group;
|
||||
}
|
||||
|
||||
private function method_label(string $method): string {
|
||||
if ($this->method_labels === null) {
|
||||
$this->method_labels = [
|
||||
'card_pass' => __('卡密', 'aksrt'),
|
||||
'free' => __('免费', 'aksrt'),
|
||||
'' => __('未知', 'aksrt'),
|
||||
];
|
||||
if (class_exists('\AKSRT\Payment\Gateway')) {
|
||||
foreach (Gateway::get_instance()->get_all() as $id => $gateway) {
|
||||
$this->method_labels[(string) $id] = $gateway->get_name();
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->method_labels[$method] ?? $method;
|
||||
}
|
||||
|
||||
private static function yuan(int $minor): float {
|
||||
return round($minor / 100, 2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
/**
|
||||
* 支付中心统计的按天明细与缓存。
|
||||
*
|
||||
* 每一天一条记录,金额一律用「分」(整数)保存,避免浮点累加误差:
|
||||
* paid [product_type][payment_method] => [金额分, 笔数] 按支付时间归日
|
||||
* refund [product_type] => [金额分, 笔数] 按退款时间归日
|
||||
* created [product_type] => [下单数, 已支付数, 已关闭数] 按下单时间归日
|
||||
*
|
||||
* 统计口径(与 OrderAnalytics 一致):
|
||||
* - 只统计站点主币种(CNY)的现金订单;积分订单与外币订单不进入主口径;
|
||||
* - 不统计余额充值(预收款,消费时已计入对应订单);
|
||||
* - 支付侧包含后来退款的订单(paid / refunding / refunded),退款单独记在退款当天,
|
||||
* 所以历史支付金额不会因为后来的退款而改变。
|
||||
*
|
||||
* 缓存:今天和昨天始终实时查询;更早的日期按月存进 transient(一个月一条),
|
||||
* 订单状态变化时删除受影响月份;清理 / 删除订单时整体换代。
|
||||
*
|
||||
* 性能:支付时间没有索引,查询额外加 created_at 下界(待支付订单最长 24 小时过期,
|
||||
* 支付时间不会晚于下单 2 天),让 MySQL 走 idx_created;退款查询走 idx_status。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Payment;
|
||||
|
||||
use AKSRT\Core\DomainModel;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class OrderAnalyticsDaily {
|
||||
|
||||
/** 主口径币种(订单表 currency 经 sanitize_key 存为小写)。 */
|
||||
public const CURRENCY = 'cny';
|
||||
|
||||
/** 最近 N 天(含今天)不缓存:仍可能有回调、过期关闭等变化。 */
|
||||
private const LIVE_DAYS = 2;
|
||||
|
||||
/** 支付时间与下单时间的最大间隔(天),用于 created_at 索引下界。 */
|
||||
public const CREATED_SLACK_DAYS = 2;
|
||||
|
||||
private const GEN_OPTION = 'aksrt_pay_analytics_gen';
|
||||
private const CACHE_TTL = 30 * DAY_IN_SECONDS;
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function init(): void {
|
||||
add_action('aksrt_order_status_changed', [$this, 'on_status_changed'], 10, 3);
|
||||
add_action('aksrt_orders_purged', [$this, 'flush']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单状态变化:删除该订单下单日 / 支付日 / 退款日所在月份的缓存。
|
||||
*
|
||||
* @param mixed $order_number
|
||||
* @param mixed $status
|
||||
* @param mixed $order
|
||||
*/
|
||||
public function on_status_changed($order_number = '', $status = '', $order = []): void {
|
||||
if (!is_array($order)) {
|
||||
return;
|
||||
}
|
||||
$cutoff = $this->cache_cutoff();
|
||||
$months = [];
|
||||
foreach (['created_at', 'paid_at', 'refunded_at'] as $field) {
|
||||
$date = substr((string) ($order[$field] ?? ''), 0, 10);
|
||||
if (self::is_date($date) && $date <= $cutoff) {
|
||||
$months[substr($date, 0, 7)] = true;
|
||||
}
|
||||
}
|
||||
foreach (array_keys($months) as $month) {
|
||||
delete_transient($this->key($month));
|
||||
}
|
||||
}
|
||||
|
||||
/** 整体失效:换代后旧 key 不再被读取,随 TTL 自然过期。 */
|
||||
public function flush(): void {
|
||||
update_option(self::GEN_OPTION, $this->generation() + 1, false);
|
||||
}
|
||||
|
||||
public function generation(): int {
|
||||
return (int) get_option(self::GEN_OPTION, 0);
|
||||
}
|
||||
|
||||
/** 可缓存的最后一天(Y-m-d)。 */
|
||||
public function cache_cutoff(): string {
|
||||
return self::shift(current_time('Y-m-d'), -self::LIVE_DAYS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 区间内每天的记录(含首尾),键为 Y-m-d,按日期升序。
|
||||
*
|
||||
* @return array<string, array<string, array>>
|
||||
*/
|
||||
public function records(string $from, string $to): array {
|
||||
$cutoff = $this->cache_cutoff();
|
||||
$out = [];
|
||||
$missing = [];
|
||||
$by_month = [];
|
||||
foreach (self::dates($from, $to) as $date) {
|
||||
$by_month[substr($date, 0, 7)][] = $date;
|
||||
}
|
||||
|
||||
foreach ($by_month as $month => $dates) {
|
||||
$cached = $dates[0] <= $cutoff ? get_transient($this->key($month)) : false;
|
||||
$cached = is_array($cached) ? $cached : [];
|
||||
foreach ($dates as $date) {
|
||||
if ($date <= $cutoff && isset($cached[$date])) {
|
||||
$out[$date] = $cached[$date];
|
||||
} else {
|
||||
$missing[] = $date;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($missing !== []) {
|
||||
$fresh = $this->query(min($missing), max($missing));
|
||||
$store = [];
|
||||
foreach ($missing as $date) {
|
||||
$out[$date] = $fresh[$date] ?? self::empty_record();
|
||||
if ($date <= $cutoff) {
|
||||
$store[substr($date, 0, 7)][$date] = $out[$date];
|
||||
}
|
||||
}
|
||||
foreach ($store as $month => $days) {
|
||||
$cached = get_transient($this->key($month));
|
||||
set_transient($this->key($month), array_merge(is_array($cached) ? $cached : [], $days), self::CACHE_TTL);
|
||||
}
|
||||
}
|
||||
|
||||
ksort($out);
|
||||
return $out;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* 查询
|
||||
* ----------------------------------------------------------------*/
|
||||
|
||||
/** @return array<string, array<string, array>> */
|
||||
private function query(string $from, string $to): array {
|
||||
global $wpdb;
|
||||
|
||||
$table = self::table();
|
||||
$start = $from . ' 00:00:00';
|
||||
$end = $to . ' 23:59:59';
|
||||
$guard = self::shift($from, -self::CREATED_SLACK_DAYS) . ' 00:00:00';
|
||||
[$base, $base_params] = self::base_where();
|
||||
$paid = self::paid_statuses();
|
||||
$paid_in = implode(', ', array_fill(0, count($paid), '%s'));
|
||||
$records = [];
|
||||
|
||||
// 支付:按支付时间归日。
|
||||
$rows = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT DATE(COALESCE(paid_at, created_at)) AS d, product_type AS t, payment_method AS m,
|
||||
COUNT(*) AS c, COALESCE(SUM(amount), 0) AS a
|
||||
FROM {$table}
|
||||
WHERE {$base} AND status IN ({$paid_in})
|
||||
AND created_at >= %s AND created_at <= %s
|
||||
AND COALESCE(paid_at, created_at) BETWEEN %s AND %s
|
||||
GROUP BY d, t, m",
|
||||
array_merge($base_params, $paid, [$guard, $end, $start, $end])
|
||||
), ARRAY_A);
|
||||
foreach ((array) $rows as $row) {
|
||||
$date = (string) ($row['d'] ?? '');
|
||||
$type = sanitize_key((string) ($row['t'] ?? ''));
|
||||
$method = sanitize_key((string) ($row['m'] ?? ''));
|
||||
$records[$date] ??= self::empty_record();
|
||||
$cell = $records[$date]['paid'][$type][$method] ?? [0, 0];
|
||||
$records[$date]['paid'][$type][$method] = [$cell[0] + self::minor($row['a'] ?? 0), $cell[1] + (int) ($row['c'] ?? 0)];
|
||||
}
|
||||
|
||||
// 退款:按退款时间归日(缺失时回落支付 / 下单时间)。
|
||||
$rows = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT DATE(COALESCE(refunded_at, paid_at, created_at)) AS d, product_type AS t,
|
||||
COUNT(*) AS c, COALESCE(SUM(amount), 0) AS a
|
||||
FROM {$table}
|
||||
WHERE {$base} AND status = %s
|
||||
AND COALESCE(refunded_at, paid_at, created_at) BETWEEN %s AND %s
|
||||
GROUP BY d, t",
|
||||
array_merge($base_params, [DomainModel::ORDER_STATUS_REFUNDED, $start, $end])
|
||||
), ARRAY_A);
|
||||
foreach ((array) $rows as $row) {
|
||||
$date = (string) ($row['d'] ?? '');
|
||||
$type = sanitize_key((string) ($row['t'] ?? ''));
|
||||
$records[$date] ??= self::empty_record();
|
||||
$cell = $records[$date]['refund'][$type] ?? [0, 0];
|
||||
$records[$date]['refund'][$type] = [$cell[0] + self::minor($row['a'] ?? 0), $cell[1] + (int) ($row['c'] ?? 0)];
|
||||
}
|
||||
|
||||
// 下单:按下单时间归日,用于支付转化率与关闭率。
|
||||
$rows = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT DATE(created_at) AS d, product_type AS t, COUNT(*) AS c,
|
||||
SUM(CASE WHEN status IN ({$paid_in}) THEN 1 ELSE 0 END) AS p,
|
||||
SUM(CASE WHEN status = %s THEN 1 ELSE 0 END) AS x
|
||||
FROM {$table}
|
||||
WHERE {$base} AND created_at BETWEEN %s AND %s
|
||||
GROUP BY d, t",
|
||||
array_merge($paid, [DomainModel::ORDER_STATUS_CLOSED], $base_params, [$start, $end])
|
||||
), ARRAY_A);
|
||||
foreach ((array) $rows as $row) {
|
||||
$date = (string) ($row['d'] ?? '');
|
||||
$type = sanitize_key((string) ($row['t'] ?? ''));
|
||||
$records[$date] ??= self::empty_record();
|
||||
$cell = $records[$date]['created'][$type] ?? [0, 0, 0];
|
||||
$records[$date]['created'][$type] = [
|
||||
$cell[0] + (int) ($row['c'] ?? 0),
|
||||
$cell[1] + (int) ($row['p'] ?? 0),
|
||||
$cell[2] + (int) ($row['x'] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
return $records;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* 共享口径(OrderAnalytics 的区间查询也用这些)
|
||||
* ----------------------------------------------------------------*/
|
||||
|
||||
public static function table(): string {
|
||||
return \AKSRT\Core\DBMigrator::table('order');
|
||||
}
|
||||
|
||||
/** @return array{0:string,1:array<int,string>} 主口径 WHERE 片段(主币种、非积分、非充值)。 */
|
||||
public static function base_where(): array {
|
||||
return [
|
||||
'currency = %s AND payment_method <> %s AND product_type <> %s',
|
||||
[self::CURRENCY, 'points', DomainModel::ORDER_PRODUCT_RECHARGE],
|
||||
];
|
||||
}
|
||||
|
||||
/** @return string[] 曾经支付成功的状态:支付侧统计包含后来退款的订单。 */
|
||||
public static function paid_statuses(): array {
|
||||
return array_values(array_unique(array_merge(
|
||||
DomainModel::paid_order_statuses(),
|
||||
[DomainModel::ORDER_STATUS_REFUNDED]
|
||||
)));
|
||||
}
|
||||
|
||||
/** 数据库 DECIMAL 字符串 → 分。 */
|
||||
public static function minor($value): int {
|
||||
return (int) round(((float) $value) * 100);
|
||||
}
|
||||
|
||||
/** @return array{paid:array,refund:array,created:array} */
|
||||
public static function empty_record(): array {
|
||||
return ['paid' => [], 'refund' => [], 'created' => []];
|
||||
}
|
||||
|
||||
public static function is_date(string $date): bool {
|
||||
if (!preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $date, $m)) {
|
||||
return false;
|
||||
}
|
||||
return checkdate((int) $m[2], (int) $m[3], (int) $m[1]);
|
||||
}
|
||||
|
||||
/** 纯日期加减(UTC 计算,不受夏令时影响)。 */
|
||||
public static function shift(string $date, int $days): string {
|
||||
$base = new \DateTimeImmutable($date, new \DateTimeZone('UTC'));
|
||||
return $base->modify(($days >= 0 ? '+' : '') . $days . ' days')->format('Y-m-d');
|
||||
}
|
||||
|
||||
/** @return string[] 从 $from 到 $to 的连续日期。 */
|
||||
public static function dates(string $from, string $to): array {
|
||||
$dates = [];
|
||||
for ($d = $from; $d <= $to; $d = self::shift($d, 1)) {
|
||||
$dates[] = $d;
|
||||
}
|
||||
return $dates;
|
||||
}
|
||||
|
||||
private function key(string $month): string {
|
||||
return 'aksrt_pa_' . $this->generation() . '_' . str_replace('-', '', $month);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ defined('ABSPATH') || exit;
|
||||
class OrderRepository {
|
||||
use OrderRepositoryCore;
|
||||
use OrderRepositoryAdmin;
|
||||
use OrderRepositoryAttempts;
|
||||
|
||||
private static $instance = null;
|
||||
private int $status_transaction_depth = 0;
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
/**
|
||||
* 订单与支付渠道交易号(out_trade_no)的映射:meta 合并、按交易号反查订单、重复支付时分配新交易号。
|
||||
*
|
||||
* 作为 flat sibling trait 被 OrderRepository 使用(PSR-4:trait 必须与宿主类同目录)。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Payment;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait OrderRepositoryAttempts {
|
||||
|
||||
/**
|
||||
* Merge keys into the order note JSON payload (meta_payload).
|
||||
*
|
||||
* @param array<string,mixed> $patch
|
||||
*/
|
||||
public function merge_meta(string $order_number, array $patch): bool {
|
||||
global $wpdb;
|
||||
|
||||
$order = $this->get($order_number);
|
||||
if (!$order) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$meta = is_array($order['meta_payload'] ?? null) ? $order['meta_payload'] : [];
|
||||
foreach ($patch as $key => $value) {
|
||||
$meta[(string) $key] = $value;
|
||||
}
|
||||
|
||||
$encoded = wp_json_encode($meta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
if (!is_string($encoded)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$updated = $wpdb->update(
|
||||
$this->table(),
|
||||
['note' => $encoded],
|
||||
['order_number' => $order_number],
|
||||
['%s'],
|
||||
['%s']
|
||||
);
|
||||
|
||||
return $updated !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a local order from a provider out_trade_no (base or attempt-suffixed).
|
||||
*
|
||||
* The authoritative mapping is the order's own meta (provider_out_trade_no /
|
||||
* provider_out_trade_nos); the prefix heuristic is only a fallback and must
|
||||
* never be the sole resolver, otherwise a truncated attempt number would
|
||||
* strand a real payment.
|
||||
*/
|
||||
public function resolve_by_out_trade_no(string $out_trade_no): ?array {
|
||||
global $wpdb;
|
||||
|
||||
$out_trade_no = sanitize_text_field($out_trade_no);
|
||||
if ($out_trade_no === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$direct = $this->get($out_trade_no);
|
||||
if ($direct) {
|
||||
return $direct;
|
||||
}
|
||||
|
||||
// Authoritative: the attempt number was recorded in the order note JSON.
|
||||
$needle = '"provider_out_trade_no":"' . $out_trade_no . '"';
|
||||
$row = $wpdb->get_row(
|
||||
$wpdb->prepare(
|
||||
"SELECT * FROM {$this->table()} WHERE note LIKE %s ORDER BY id DESC LIMIT 1",
|
||||
'%' . $wpdb->esc_like($needle) . '%'
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
if (is_array($row)) {
|
||||
return $this->expire_order_if_due($this->normalize_row($row));
|
||||
}
|
||||
|
||||
// Superseded attempts stay in the history array.
|
||||
$history_needle = '"' . $out_trade_no . '"';
|
||||
$row = $wpdb->get_row(
|
||||
$wpdb->prepare(
|
||||
"SELECT * FROM {$this->table()}
|
||||
WHERE note LIKE %s AND note LIKE %s
|
||||
ORDER BY id DESC LIMIT 1",
|
||||
'%' . $wpdb->esc_like('"provider_out_trade_nos"') . '%',
|
||||
'%' . $wpdb->esc_like($history_needle) . '%'
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
if (is_array($row)) {
|
||||
return $this->expire_order_if_due($this->normalize_row($row));
|
||||
}
|
||||
|
||||
// Last resort: {order_number}P{n} prefix heuristic.
|
||||
if (preg_match('/^(.+)P([0-9]{1,3})$/', $out_trade_no, $m)) {
|
||||
$base = $this->get($m[1]);
|
||||
if ($base) {
|
||||
return $base;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider out_trade_no currently bound to this order (falls back to order_number).
|
||||
*
|
||||
* @param array<string,mixed> $order
|
||||
*/
|
||||
public static function provider_out_trade_no(array $order): string {
|
||||
$meta = is_array($order['meta_payload'] ?? null) ? $order['meta_payload'] : [];
|
||||
$out = sanitize_text_field((string) ($meta['provider_out_trade_no'] ?? ''));
|
||||
if ($out !== '') {
|
||||
return $out;
|
||||
}
|
||||
return sanitize_text_field((string) ($order['order_number'] ?? ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate a new provider out_trade_no for a payment attempt.
|
||||
* First attempt uses bare order_number; later attempts append Pn (WeChat limit 32).
|
||||
*
|
||||
* @return array{out_trade_no:string,attempt:int,previous:string}|null
|
||||
*/
|
||||
public function allocate_payment_attempt(string $order_number): ?array {
|
||||
$order = $this->get($order_number);
|
||||
if (!$order) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$meta = is_array($order['meta_payload'] ?? null) ? $order['meta_payload'] : [];
|
||||
$previous = sanitize_text_field((string) ($meta['provider_out_trade_no'] ?? ''));
|
||||
$history = is_array($meta['provider_out_trade_nos'] ?? null) ? $meta['provider_out_trade_nos'] : [];
|
||||
|
||||
// First provider submission keeps the bare order number.
|
||||
// Re-pay (provider_out_trade_no already set) always mints a new suffix so
|
||||
// WeChat/Alipay/Epay accept the order when amount/subject/notify changed.
|
||||
if ($previous === '') {
|
||||
$attempt = 1;
|
||||
$out = $order_number;
|
||||
} else {
|
||||
$attempt = max(1, (int) ($meta['pay_attempt'] ?? 1)) + 1;
|
||||
$suffix = 'P' . $attempt;
|
||||
$base = $order_number;
|
||||
if (strlen($base) + strlen($suffix) > 32) {
|
||||
$base = substr($base, 0, 32 - strlen($suffix));
|
||||
}
|
||||
$out = $base . $suffix;
|
||||
if ($previous !== '' && !in_array($previous, $history, true)) {
|
||||
$history[] = $previous;
|
||||
}
|
||||
}
|
||||
|
||||
if (!in_array($out, $history, true)) {
|
||||
$history[] = $out;
|
||||
}
|
||||
if (count($history) > 20) {
|
||||
$history = array_slice($history, -20);
|
||||
}
|
||||
|
||||
$ok = $this->merge_meta($order_number, [
|
||||
'pay_attempt' => $attempt,
|
||||
'provider_out_trade_no' => $out,
|
||||
'provider_out_trade_nos' => array_values($history),
|
||||
]);
|
||||
if (!$ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'out_trade_no' => $out,
|
||||
'attempt' => $attempt,
|
||||
'previous' => $previous,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -641,7 +641,12 @@ trait OrderRepositoryCore {
|
||||
AND cancelled_at < %s",
|
||||
$cutoff
|
||||
));
|
||||
return $deleted === false ? 0 : (int) $deleted;
|
||||
$deleted = $deleted === false ? 0 : (int) $deleted;
|
||||
if ($deleted > 0) {
|
||||
/** 订单被物理删除:统计缓存需整体失效。 */
|
||||
do_action('aksrt_orders_purged');
|
||||
}
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
|
||||
@@ -656,51 +661,15 @@ trait OrderRepositoryCore {
|
||||
return false;
|
||||
}
|
||||
$ok = $wpdb->delete($this->table(), ['order_number' => $order_number], ['%s']);
|
||||
return $ok !== false && $ok > 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取订单统计
|
||||
*/
|
||||
public function get_stats(int $user_id = 0): array {
|
||||
global $wpdb;
|
||||
|
||||
$this->expire_pending_orders($user_id > 0 ? $user_id : null);
|
||||
|
||||
$where = "WHERE status IN ('pending', 'paid', 'refunding', 'refunded', 'closed')";
|
||||
$params = [];
|
||||
if ($user_id > 0) {
|
||||
$where .= ' AND user_id = %d';
|
||||
$params[] = $user_id;
|
||||
}
|
||||
|
||||
$sql = "SELECT
|
||||
COUNT(*) as total,
|
||||
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending,
|
||||
SUM(CASE WHEN status IN ('paid','refunding') THEN 1 ELSE 0 END) as paid,
|
||||
SUM(CASE WHEN status = 'refunded' THEN 1 ELSE 0 END) as refunded,
|
||||
SUM(CASE WHEN status = 'closed' THEN 1 ELSE 0 END) as closed,
|
||||
SUM(CASE WHEN status IN ('paid','refunding') THEN amount ELSE 0 END) as revenue
|
||||
FROM {$this->table()} {$where}";
|
||||
|
||||
if (!empty($params)) {
|
||||
$row = $wpdb->get_row($wpdb->prepare($sql, $params), ARRAY_A);
|
||||
} else {
|
||||
$row = $wpdb->get_row($sql, ARRAY_A);
|
||||
$ok = $ok !== false && $ok > 0;
|
||||
if ($ok) {
|
||||
do_action('aksrt_orders_purged');
|
||||
}
|
||||
|
||||
return [
|
||||
'total' => (int) ($row['total'] ?? 0),
|
||||
'pending' => (int) ($row['pending'] ?? 0),
|
||||
'paid' => (int) ($row['paid'] ?? 0),
|
||||
'refunded' => (int) ($row['refunded'] ?? 0),
|
||||
'closed' => (int) ($row['closed'] ?? 0),
|
||||
'revenue' => $this->cash_decimal((string) ($row['revenue'] ?? '0')),
|
||||
];
|
||||
return $ok;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function dispatch_managed_status_events(): void {
|
||||
$events = $this->status_after_commit_events;
|
||||
$this->status_after_commit_events = [];
|
||||
@@ -850,16 +819,6 @@ trait OrderRepositoryCore {
|
||||
}
|
||||
}
|
||||
|
||||
private function cash_decimal(string $value): string {
|
||||
try {
|
||||
return \AKSRT\Ledger\AssetAmount::balance($value)->decimal();
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return '0.00';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function find_latest_success_order(int $user_id, string $product_type, int $product_id = 0, bool $ignore_product_id = false): ?array {
|
||||
global $wpdb;
|
||||
|
||||
@@ -913,171 +872,4 @@ trait OrderRepositoryCore {
|
||||
return \AKSRT\Finance\BusinessNumber::allocate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge keys into the order note JSON payload (meta_payload).
|
||||
*
|
||||
* @param array<string,mixed> $patch
|
||||
*/
|
||||
public function merge_meta(string $order_number, array $patch): bool {
|
||||
global $wpdb;
|
||||
|
||||
$order = $this->get($order_number);
|
||||
if (!$order) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$meta = is_array($order['meta_payload'] ?? null) ? $order['meta_payload'] : [];
|
||||
foreach ($patch as $key => $value) {
|
||||
$meta[(string) $key] = $value;
|
||||
}
|
||||
|
||||
$encoded = wp_json_encode($meta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
if (!is_string($encoded)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$updated = $wpdb->update(
|
||||
$this->table(),
|
||||
['note' => $encoded],
|
||||
['order_number' => $order_number],
|
||||
['%s'],
|
||||
['%s']
|
||||
);
|
||||
|
||||
return $updated !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a local order from a provider out_trade_no (base or attempt-suffixed).
|
||||
*
|
||||
* The authoritative mapping is the order's own meta (provider_out_trade_no /
|
||||
* provider_out_trade_nos); the prefix heuristic is only a fallback and must
|
||||
* never be the sole resolver, otherwise a truncated attempt number would
|
||||
* strand a real payment.
|
||||
*/
|
||||
public function resolve_by_out_trade_no(string $out_trade_no): ?array {
|
||||
global $wpdb;
|
||||
|
||||
$out_trade_no = sanitize_text_field($out_trade_no);
|
||||
if ($out_trade_no === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$direct = $this->get($out_trade_no);
|
||||
if ($direct) {
|
||||
return $direct;
|
||||
}
|
||||
|
||||
// Authoritative: the attempt number was recorded in the order note JSON.
|
||||
$needle = '"provider_out_trade_no":"' . $out_trade_no . '"';
|
||||
$row = $wpdb->get_row(
|
||||
$wpdb->prepare(
|
||||
"SELECT * FROM {$this->table()} WHERE note LIKE %s ORDER BY id DESC LIMIT 1",
|
||||
'%' . $wpdb->esc_like($needle) . '%'
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
if (is_array($row)) {
|
||||
return $this->expire_order_if_due($this->normalize_row($row));
|
||||
}
|
||||
|
||||
// Superseded attempts stay in the history array.
|
||||
$history_needle = '"' . $out_trade_no . '"';
|
||||
$row = $wpdb->get_row(
|
||||
$wpdb->prepare(
|
||||
"SELECT * FROM {$this->table()}
|
||||
WHERE note LIKE %s AND note LIKE %s
|
||||
ORDER BY id DESC LIMIT 1",
|
||||
'%' . $wpdb->esc_like('"provider_out_trade_nos"') . '%',
|
||||
'%' . $wpdb->esc_like($history_needle) . '%'
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
if (is_array($row)) {
|
||||
return $this->expire_order_if_due($this->normalize_row($row));
|
||||
}
|
||||
|
||||
// Last resort: {order_number}P{n} prefix heuristic.
|
||||
if (preg_match('/^(.+)P([0-9]{1,3})$/', $out_trade_no, $m)) {
|
||||
$base = $this->get($m[1]);
|
||||
if ($base) {
|
||||
return $base;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider out_trade_no currently bound to this order (falls back to order_number).
|
||||
*
|
||||
* @param array<string,mixed> $order
|
||||
*/
|
||||
public static function provider_out_trade_no(array $order): string {
|
||||
$meta = is_array($order['meta_payload'] ?? null) ? $order['meta_payload'] : [];
|
||||
$out = sanitize_text_field((string) ($meta['provider_out_trade_no'] ?? ''));
|
||||
if ($out !== '') {
|
||||
return $out;
|
||||
}
|
||||
return sanitize_text_field((string) ($order['order_number'] ?? ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate a new provider out_trade_no for a payment attempt.
|
||||
* First attempt uses bare order_number; later attempts append Pn (WeChat limit 32).
|
||||
*
|
||||
* @return array{out_trade_no:string,attempt:int,previous:string}|null
|
||||
*/
|
||||
public function allocate_payment_attempt(string $order_number): ?array {
|
||||
$order = $this->get($order_number);
|
||||
if (!$order) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$meta = is_array($order['meta_payload'] ?? null) ? $order['meta_payload'] : [];
|
||||
$previous = sanitize_text_field((string) ($meta['provider_out_trade_no'] ?? ''));
|
||||
$history = is_array($meta['provider_out_trade_nos'] ?? null) ? $meta['provider_out_trade_nos'] : [];
|
||||
|
||||
// First provider submission keeps the bare order number.
|
||||
// Re-pay (provider_out_trade_no already set) always mints a new suffix so
|
||||
// WeChat/Alipay/Epay accept the order when amount/subject/notify changed.
|
||||
if ($previous === '') {
|
||||
$attempt = 1;
|
||||
$out = $order_number;
|
||||
} else {
|
||||
$attempt = max(1, (int) ($meta['pay_attempt'] ?? 1)) + 1;
|
||||
$suffix = 'P' . $attempt;
|
||||
$base = $order_number;
|
||||
if (strlen($base) + strlen($suffix) > 32) {
|
||||
$base = substr($base, 0, 32 - strlen($suffix));
|
||||
}
|
||||
$out = $base . $suffix;
|
||||
if ($previous !== '' && !in_array($previous, $history, true)) {
|
||||
$history[] = $previous;
|
||||
}
|
||||
}
|
||||
|
||||
if (!in_array($out, $history, true)) {
|
||||
$history[] = $out;
|
||||
}
|
||||
if (count($history) > 20) {
|
||||
$history = array_slice($history, -20);
|
||||
}
|
||||
|
||||
$ok = $this->merge_meta($order_number, [
|
||||
'pay_attempt' => $attempt,
|
||||
'provider_out_trade_no' => $out,
|
||||
'provider_out_trade_nos' => array_values($history),
|
||||
]);
|
||||
if (!$ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'out_trade_no' => $out,
|
||||
'attempt' => $attempt,
|
||||
'previous' => $previous,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -47,9 +47,11 @@ test('filters, selection, table and pagination share one list card', () => {
|
||||
const card = cards[0][0];
|
||||
assert.match(card, /aksrt-inv-list-card/);
|
||||
assert.match(card, /<h2 class="aksrt-native-card__title">邀请码列表<\/h2>/);
|
||||
for (const marker of ['class="subsubsub"', 'class="aksrt-native-filters"', '清除筛选', 'class="aksrt-inv-bulk"', 'data-aksrt-inv-selection', 'aksrt-inv-table', 'class="tablenav bottom"']) {
|
||||
for (const marker of ['class="subsubsub"', 'class="aksrt-native-filters"', '清除筛选', 'data-aksrt-inv-selection', 'aksrt-inv-table', 'class="tablenav bottom"']) {
|
||||
assert.ok(card.includes(marker), `${marker} belongs to the list card`);
|
||||
}
|
||||
// 批量操作行同时带共享的 aksrt-native-bulk 类,按 class 列表中的词匹配,不要求整串相等。
|
||||
assert.match(card, /class="[^"]*\baksrt-inv-bulk\b[^"]*"/, 'aksrt-inv-bulk belongs to the list card');
|
||||
assert.doesNotMatch(html, /aksrt-inv-filter-summary|关键词:/);
|
||||
assert.doesNotMatch(read('assets', 'css', 'admin', 'invite-codes.css'), /aksrt-inv-filter-summary/);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 打包支付中心概览用的 ECharts 子集。
|
||||
*
|
||||
* cd assets/admin && npm run build:pay-charts
|
||||
*
|
||||
* echarts 是 assets/admin 的 devDependency;产物入库,部署时无需安装 node 依赖。
|
||||
*/
|
||||
import { createRequire } from 'node:module';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const adminDir = path.join(repoRoot, 'assets/admin');
|
||||
const require = createRequire(path.join(adminDir, 'package.json'));
|
||||
const { build } = require('esbuild');
|
||||
|
||||
const outfile = path.join(repoRoot, 'assets/js/runtime/libs/echarts-pay.min.js');
|
||||
|
||||
await build({
|
||||
entryPoints: [path.join(repoRoot, 'tools/echarts-pay-overview.entry.mjs')],
|
||||
outfile,
|
||||
bundle: true,
|
||||
format: 'iife',
|
||||
minify: true,
|
||||
target: ['es2018'],
|
||||
legalComments: 'inline',
|
||||
nodePaths: [path.join(adminDir, 'node_modules')],
|
||||
logLevel: 'info',
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 支付中心概览专用的 ECharts 按需入口(由 tools/build-pay-charts.mjs 打包)。
|
||||
*
|
||||
* 只注册概览页用到的图表与组件,打包为 IIFE 并挂到 window.echarts。
|
||||
* 产物:assets/js/runtime/libs/echarts-pay.min.js(入库,后台只在 aksrt_pay 页入队)。
|
||||
*/
|
||||
import * as echarts from 'echarts/core';
|
||||
import { LineChart, BarChart, PieChart } from 'echarts/charts';
|
||||
import {
|
||||
GridComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
DataZoomComponent,
|
||||
AxisPointerComponent,
|
||||
AriaComponent,
|
||||
} from 'echarts/components';
|
||||
import { SVGRenderer } from 'echarts/renderers';
|
||||
|
||||
echarts.use([
|
||||
LineChart,
|
||||
BarChart,
|
||||
PieChart,
|
||||
GridComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
DataZoomComponent,
|
||||
AxisPointerComponent,
|
||||
AriaComponent,
|
||||
SVGRenderer,
|
||||
]);
|
||||
|
||||
window.echarts = echarts;
|
||||
Reference in new issue
Block a user