39 Commits

Author SHA1 Message Date
lihui beabec6da5 style:图例 右移一点点 1 week ago
lihui 3acbf1c452 add:工作台左侧卡片,右侧卡片,底部柱状图,柱状图需要优化排名部分 按钮部分 2 weeks ago
zhangrenyuan db1a3dd803 cash-management 和 worspace 2 weeks ago
zhangrenyuan 7f73f8ab09 header 迁移 2 weeks ago
zhangrenyuan 5bdedf5667 Merge branch 'zhangrenyuan/feature-20250917134308-现金管理' into milestone-20250913-现金管理 2 weeks ago
zhangrenyuan 94e32e2048 背景样式基本完成,包括侧边栏,头部,中心区域背景,悬浮按钮等 2 weeks ago
ZhangYong 09d55d3ac0 收款明细 2 weeks ago
ZhangYong 5f424a62db 收款明细 2 weeks ago
zhangrenyuan b78141ba18 merge lh 2 weeks ago
zhangrenyuan 0b2099bbbe 背景样式的更改day1 2 weeks ago
lihui 2b4feb8fef add:工作台的一个卡片 2 weeks ago
ZhangYong 70fc4d1119 客服新增弹窗 2 weeks ago
zhangrenyuan 2cf5ee0ae8 背景样式 2 weeks ago
zhangrenyuan 2119ac35f0 Merge branch 'zhangrenyuan/feature-20250917161935-重构工作台' into zhangrenyuan/feature-20250917134308-现金管理 2 weeks ago
zhangrenyuan b1863fce0f Merge branch 'milestone-20250913-现金管理' into zhangrenyuan/feature-20250917134308-现金管理 2 weeks ago
ZhangYong edeb8c144d 现金管理,收款明细 2 weeks ago
lihuilin 66489ec66a 现金退款部分 2 weeks ago
zhangrenyuan 3bf91fe3d5 Merge branch 'milestone-20250913-现金管理' into zhangrenyuan/feature-20250917134308-现金管理 2 weeks ago
lhl bf21a8c6b6 到底报不报错! 3 weeks ago
zhangrenyuan 448f96850d 工作台样式 3 weeks ago
ZhangYong 0516cdc43f fix现金管理,角色管理 3 weeks ago
ZhangYong 9b95708d37 审核查看权限 3 weeks ago
lihuilin 64ec4d03f1 Merge branch 'lihuilin/feature-20250815155204-金币优化' into milestone-20250913-现金管理 3 weeks ago
lihuilin 9263a3a03c Merge branch 'milestone-20250913-现金管理' of http://39.101.133.168:8807/huangqizhen/gold-vue into milestone-20250913-现金管理 3 weeks ago
lihuilin 5334f9d5fc 权限下半完成 3 weeks ago
ZhangYong 1930dabbb9 审核,新增,消耗,汇率的操作权限 3 weeks ago
lihuilin a5dcc7b0aa 权限2解决冲突 3 weeks ago
lihuilin e47b4b70ca 权限重构2 3 weeks ago
ZhangYong 60cd27eb6d 金币审核 3 weeks ago
ZhangYong 2143865f8e 金币审核 3 weeks ago
zhangrenyuan 363570dc95 样式重构全局 3 weeks ago
ZhangYong 14b78d0fdc 权限初版 3 weeks ago
zhangrenyuan 27fca44c30 Merge branch 'zhangrenyuan/feature-20250917134308-现金管理' into zhangrenyuan/feature-20250917161935-重构工作台 3 weeks ago
zhangrenyuan feea4e4f0c 重构工作台的样式 3 weeks ago
ZhangYong 9a80865354 路由 3 weeks ago
zhangrenyuan a698ca67af Merge branch 'milestone-20250913-现金管理' into zhangrenyuan/feature-20250917134308-现金管理 3 weeks ago
zhangrenyuan ee836f1780 fix本地优化 3 weeks ago
lihuilin bdd037eb6d 权限列表更新 3 weeks ago
ZhangYong bbb918156a 金币审核筛选项优化 3 weeks ago
  1. 187
      package-lock.json
  2. 4
      package.json
  3. 14
      src/assets/SvgIcons/上升箭头.svg
  4. 14
      src/assets/SvgIcons/下降箭头.svg
  5. 10
      src/assets/SvgIcons/周同比.svg
  6. 38
      src/assets/SvgIcons/工作台.svg
  7. 30
      src/assets/SvgIcons/折合新币累计金额.svg
  8. 14
      src/assets/SvgIcons/持平.svg
  9. 30
      src/assets/SvgIcons/昨日充值人数.svg
  10. 19
      src/assets/SvgIcons/昨日新增消费.svg
  11. 30
      src/assets/SvgIcons/昨日新增金币.svg
  12. 18
      src/assets/SvgIcons/消耗.svg
  13. 29
      src/assets/SvgIcons/现金管理.svg
  14. 22
      src/assets/SvgIcons/设置.svg
  15. 27
      src/assets/SvgIcons/金币管理.svg
  16. 9
      src/assets/SvgIcons/金币系统LOGO.svg
  17. BIN
      src/assets/backgroundBlue.png
  18. BIN
      src/assets/半透明background.png
  19. 207
      src/components/workspace/CashManagement.vue
  20. 838
      src/components/workspace/GoldGraph.vue
  21. 667
      src/components/workspace/GoldManagement.vue
  22. 153
      src/router/index.js
  23. 174
      src/utils/menuTreePermission.js
  24. 17
      src/utils/menuUtils.js
  25. 97
      src/views/audit/bean/beanAudit.vue
  26. 20
      src/views/audit/gold/audit.vue
  27. 183
      src/views/audit/gold/rechargeAudit.vue
  28. 236
      src/views/audit/gold/refundAudit.vue
  29. 16
      src/views/consume/bean/beanConsume.vue
  30. 10
      src/views/consume/gold/coinConsume.vue
  31. 228
      src/views/history/history.vue
  32. 119
      src/views/history/newHistory.vue
  33. 117
      src/views/history/oldHistory.vue
  34. 264
      src/views/home.vue
  35. 28
      src/views/managerecharge/rate.vue
  36. 2
      src/views/moneyManage/executor/executor.vue
  37. 450
      src/views/moneyManage/receiveDetail/receiveDetail.vue
  38. 471
      src/views/moneyManage/refundDetail/refundDetail.vue
  39. 33
      src/views/permissions/permissions.vue
  40. 78
      src/views/permissions/rolePermission.vue
  41. 73
      src/views/permissions/userPermission.vue
  42. 12
      src/views/recharge/bean/beanRecharge.vue
  43. 8
      src/views/recharge/gold/coinRecharge.vue
  44. 9
      src/views/refund/gold/addCoinRefund.vue
  45. 726
      src/views/refund/gold/addNewCoinRefund.vue
  46. 12
      src/views/refund/gold/coinRefund.vue
  47. 27
      src/views/refund/gold/coinRefundDetail.vue
  48. 9
      src/views/usergold/bean/userbean.vue
  49. 8
      src/views/usergold/gold/clientCount.vue
  50. 24
      src/views/usergold/gold/clientCountBalance.vue
  51. 53
      src/views/usergold/gold/clientCountDetail.vue
  52. 956
      src/views/workspace/index.vue

187
package-lock.json

@ -14,7 +14,7 @@
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/vue-fontawesome": "^3.0.8",
"@tabler/icons-vue": "^3.34.0",
"axios": "^1.7.8",
"axios": "^1.12.2",
"dayjs": "^1.11.13",
"dayjs-plugin-utc": "^0.1.2",
"echarts": "^5.5.1",
@ -3170,18 +3170,18 @@
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/asynckit/-/asynckit-0.4.0.tgz",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.7.8",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/axios/-/axios-1.7.8.tgz",
"integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==",
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
@ -3330,6 +3330,19 @@
"dev": true,
"license": "MIT"
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001699",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/caniuse-lite/-/caniuse-lite-1.0.30001699.tgz",
@ -3422,7 +3435,7 @@
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/combined-stream/-/combined-stream-1.0.8.tgz",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
@ -3567,7 +3580,7 @@
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/delayed-stream/-/delayed-stream-1.0.0.tgz",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
@ -3599,6 +3612,20 @@
"integrity": "sha512-LN1gO7+u9xjU5oEScGFKvXhYf7Y/empUIIEAGBs1LzUq/rg5duiDrkuH5A2lQGd5jfMOb9X9usDa2oVXwJ0U/Q==",
"license": "MIT"
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/echarts": {
"version": "5.5.1",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/echarts/-/echarts-5.5.1.tgz",
@ -3660,6 +3687,24 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-module-lexer": {
"version": "1.6.0",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
@ -3667,6 +3712,33 @@
"dev": true,
"license": "MIT"
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/esbuild": {
"version": "0.24.2",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/esbuild/-/esbuild-0.24.2.tgz",
@ -3785,13 +3857,15 @@
}
},
"node_modules/form-data": {
"version": "4.0.1",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/form-data/-/form-data-4.0.1.tgz",
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
@ -3839,7 +3913,6 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@ -3862,6 +3935,43 @@
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
@ -3871,11 +3981,49 @@
"node": ">=4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.2"
},
@ -4090,6 +4238,15 @@
"@jridgewell/sourcemap-codec": "^1.5.0"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mathjs": {
"version": "14.0.1",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/mathjs/-/mathjs-14.0.1.tgz",
@ -4161,7 +4318,7 @@
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/mime-db/-/mime-db-1.52.0.tgz",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
@ -4170,7 +4327,7 @@
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/mime-types/-/mime-types-2.1.35.tgz",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {

4
package.json

@ -8,7 +8,7 @@
"dev": "vite --host 0.0.0.0",
"build:test": "vite build --mode test",
"build:prod": "vite build --mode production",
"preview": "vite preview",
"prev` iew": "vite preview",
"serve": "vite - service serve --host 0.0.0.0 --port 8080"
},
"dependencies": {
@ -18,7 +18,7 @@
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/vue-fontawesome": "^3.0.8",
"@tabler/icons-vue": "^3.34.0",
"axios": "^1.7.8",
"axios": "^1.12.2",
"dayjs": "^1.11.13",
"dayjs-plugin-utc": "^0.1.2",
"echarts": "^5.5.1",

14
src/assets/SvgIcons/上升箭头.svg

@ -0,0 +1,14 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 14" class="design-iconfont">
<path d="M3.33333 14H6.66667V7.875V5.25H10L5 0L0 5.25H3.33333V7.875V14Z" fill="url(#9ch16kl2f__paint0_linear_454_32894)"/>
<path d="M6.54883 13.8828V5.13281H9.72656L5 0.170898L0.273438 5.13281H3.45117V13.8828H6.54883Z" stroke="url(#9ch16kl2f__paint1_linear_454_32894)" stroke-opacity=".63" stroke-width=".235129" stroke-miterlimit="10"/>
<defs>
<linearGradient id="9ch16kl2f__paint0_linear_454_32894" x1=".938339" y1="14" x2="12.7734" y2="9.25209" gradientUnits="userSpaceOnUse">
<stop stop-color="#F46C6C"/>
<stop offset="1" stop-color="#FCC4C5"/>
</linearGradient>
<linearGradient id="9ch16kl2f__paint1_linear_454_32894" x1=".975668" y1="14" x2="12.7608" y2="9.22824" gradientUnits="userSpaceOnUse">
<stop stop-color="#FCC4C5"/>
<stop offset="1" stop-color="#fff"/>
</linearGradient>
</defs>
</svg>

14
src/assets/SvgIcons/下降箭头.svg

@ -0,0 +1,14 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 14" class="design-iconfont">
<path d="M3.33333 0H6.66667V6.125V8.75H10L5 14L0 8.75H3.33333V6.125V0Z" fill="url(#3jb4xovw8__paint0_linear_415_444599)"/>
<path d="M6.50977 0.157227V8.90723H9.63379L5 13.7725L0.366211 8.90723H3.49023V0.157227H6.50977Z" stroke="url(#3jb4xovw8__paint1_linear_415_444599)" stroke-opacity=".63" stroke-width=".313505" stroke-miterlimit="10"/>
<defs>
<linearGradient id="3jb4xovw8__paint0_linear_415_444599" x1=".938339" y1="-6.2e-7" x2="12.7734" y2="4.74791" gradientUnits="userSpaceOnUse">
<stop stop-color="#047CF5"/>
<stop offset="1" stop-color="#4CB9F9"/>
</linearGradient>
<linearGradient id="3jb4xovw8__paint1_linear_415_444599" x1=".975668" y1="4.4e-7" x2="12.7608" y2="4.77176" gradientUnits="userSpaceOnUse">
<stop stop-color="#4CB9F9"/>
<stop offset="1" stop-color="#fff"/>
</linearGradient>
</defs>
</svg>

10
src/assets/SvgIcons/周同比.svg
File diff suppressed because it is too large
View File

38
src/assets/SvgIcons/工作台.svg

@ -0,0 +1,38 @@
<svg width="31" height="31" viewBox="0 0 31 31" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.83596 0.471662C11.7158 0.150229 15.1637 -0.182551 20.1364 0.471662C25.1091 1.12588 28.2723 3.89325 29.3265 7.22946C30.3807 10.5657 30.3042 17.0899 29.9158 20.3537C29.5969 24.2602 27.323 28.8843 21.8852 29.6899C16.4475 30.4954 9.76469 30.2806 7.08401 29.3261C4.40334 28.3715 0.853199 26.3391 0.389008 20.6463C-0.0751821 14.9534 -0.381919 7.313 2.51481 4.21499C4.52336 1.82858 7.95615 0.793096 9.83596 0.471662Z" fill="#C0F2FF"/>
<g filter="url(#filter0_d_352_61804)">
<path d="M23.9427 25.6281H6.38731C5.45216 25.6281 4.64453 24.8205 4.64453 23.8854C4.64453 22.9502 5.45216 22.1426 6.38731 22.1426H23.9001C24.8353 22.1426 25.6429 22.9502 25.6429 23.8854C25.6854 24.8205 24.8778 25.6281 23.9427 25.6281Z" fill="url(#paint0_linear_352_61804)"/>
</g>
<g filter="url(#filter1_d_352_61804)">
<path d="M23.9426 4.65332H6.38731C5.45216 4.65332 4.64453 5.46095 4.64453 6.3961V18.6806C4.64453 19.6157 5.45216 20.4234 6.38731 20.4234H23.9001C24.8353 20.4234 25.6429 19.6157 25.6429 18.6806V6.3961C25.6854 5.46095 24.8778 4.65332 23.9426 4.65332Z" fill="url(#paint1_linear_352_61804)"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.2742 9.7675C20.6171 10.0876 20.6357 10.6251 20.3156 10.968L16.6916 14.8509C16.5309 15.023 16.306 15.1207 16.0706 15.1207C15.8352 15.1207 15.6103 15.023 15.4497 14.8509L13.6546 12.9276L11.2555 15.4981C10.9354 15.841 10.3979 15.8595 10.055 15.5395C9.71205 15.2194 9.69352 14.6819 10.0136 14.339L13.0336 11.1032C13.1943 10.9311 13.4191 10.8334 13.6546 10.8334C13.89 10.8334 14.1149 10.9311 14.2755 11.1032L16.0706 13.0265L19.0737 9.8089C19.3938 9.46596 19.9313 9.44743 20.2742 9.7675Z" fill="white"/>
<defs>
<filter id="filter0_d_352_61804" x="3.64453" y="21.1426" width="23" height="5.48535" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="0.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.184314 0 0 0 0 0.717647 0 0 0 0 0.988235 0 0 0 0.3 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_352_61804"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_352_61804" result="shape"/>
</filter>
<filter id="filter1_d_352_61804" x="3.64453" y="3.65332" width="23" height="17.7705" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="0.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.184314 0 0 0 0 0.717647 0 0 0 0 0.988235 0 0 0 0.3 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_352_61804"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_352_61804" result="shape"/>
</filter>
<linearGradient id="paint0_linear_352_61804" x1="39.3932" y1="23.8854" x2="38.3491" y2="16.8465" gradientUnits="userSpaceOnUse">
<stop stop-color="#2FB7FC"/>
<stop offset="1" stop-color="#82DEFF"/>
</linearGradient>
<linearGradient id="paint1_linear_352_61804" x1="39.3932" y1="12.5383" x2="24.3333" y2="-9.90193" gradientUnits="userSpaceOnUse">
<stop stop-color="#2FB7FC"/>
<stop offset="1" stop-color="#82DEFF"/>
</linearGradient>
</defs>
</svg>

30
src/assets/SvgIcons/折合新币累计金额.svg
File diff suppressed because it is too large
View File

14
src/assets/SvgIcons/持平.svg

@ -0,0 +1,14 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 5" class="design-iconfont">
<rect x="1" y="1" width="11" height="3" rx=".6875" fill="url(#12pnzcg9o__paint0_linear_443_32365)" fill-opacity=".7"/>
<rect x=".838349" y=".838349" width="11.3233" height="3.3233" rx=".849151" stroke="url(#12pnzcg9o__paint1_linear_443_32365)" stroke-opacity=".63" stroke-width=".323302" stroke-miterlimit="10"/>
<defs>
<linearGradient id="12pnzcg9o__paint0_linear_443_32365" x1="2.03217" y1="1" x2="4.91594" y2="6.93872" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-color="#8A8A8A"/>
</linearGradient>
<linearGradient id="12pnzcg9o__paint1_linear_443_32365" x1="2.07323" y1="1" x2="4.90949" y2="6.89506" gradientUnits="userSpaceOnUse">
<stop stop-color="#8A8A8A"/>
<stop offset="1" stop-color="#fff"/>
</linearGradient>
</defs>
</svg>

30
src/assets/SvgIcons/昨日充值人数.svg

@ -0,0 +1,30 @@
<svg width="59" height="59" viewBox="0 0 59 59" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_266_9164)">
<path d="M57 29.5C57 44.6878 44.6878 57 29.5 57C14.3122 57 2 44.6878 2 29.5C2 14.3122 14.3122 2 29.5 2C44.6878 2 57 14.3122 57 29.5ZM14.7183 29.5C14.7183 37.6637 21.3363 44.2817 29.5 44.2817C37.6637 44.2817 44.2817 37.6637 44.2817 29.5C44.2817 21.3363 37.6637 14.7183 29.5 14.7183C21.3363 14.7183 14.7183 21.3363 14.7183 29.5Z" fill="#65C9C9"/>
<g filter="url(#filter1_d_266_9164)">
<path d="M52.7209 21.6873C54.1319 25.8811 54.3808 30.3787 53.4412 34.7026C52.5016 39.0264 50.4086 43.0151 47.3844 46.2451C44.3602 49.4751 40.5177 51.8258 36.2649 53.0475C32.0121 54.2693 27.5079 54.3165 23.2304 53.1842C18.953 52.0519 15.062 49.7824 11.9707 46.6165C8.87944 43.4507 6.70321 39.5068 5.67313 35.2035C4.64305 30.9003 4.79759 26.3985 6.12032 22.176C7.44305 17.9536 9.8846 14.1682 13.1857 11.2218L18.0988 16.7263C15.7918 18.7854 14.0856 21.4308 13.1612 24.3817C12.2368 27.3325 12.1288 30.4786 12.8487 33.4859C13.5685 36.4932 15.0894 39.2494 17.2497 41.4618C19.4101 43.6743 22.1293 45.2603 25.1185 46.0516C28.1078 46.8429 31.2556 46.8099 34.2276 45.9561C37.1996 45.1023 39.885 43.4595 41.9984 41.2023C44.1119 38.945 45.5746 36.1575 46.2313 33.1358C46.8879 30.1141 46.7139 26.9709 45.7279 24.0401L52.7209 21.6873Z" fill="#9469D1"/>
</g>
</g>
<defs>
<filter id="filter0_d_266_9164" x="0" y="0" width="59" height="59" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="1"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_266_9164"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_266_9164" result="shape"/>
</filter>
<filter id="filter1_d_266_9164" x="4" y="11.2217" width="51" height="44.7783" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="0.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_266_9164"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_266_9164" result="shape"/>
</filter>
</defs>
</svg>

19
src/assets/SvgIcons/昨日新增消费.svg

@ -0,0 +1,19 @@
<svg width="59" height="59" viewBox="0 0 59 59" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_266_9159)">
<path d="M57 29.5C57 44.6878 44.6878 57 29.5 57C14.3122 57 2 44.6878 2 29.5C2 14.3122 14.3122 2 29.5 2C44.6878 2 57 14.3122 57 29.5ZM11.7063 29.5C11.7063 39.3272 19.6728 47.2937 29.5 47.2937C39.3272 47.2937 47.2937 39.3272 47.2937 29.5C47.2937 19.6728 39.3272 11.7063 29.5 11.7063C19.6728 11.7063 11.7063 19.6728 11.7063 29.5Z" fill="#65C9C9"/>
<path d="M51.708 21.8153C53.6527 27.4352 53.3944 33.5834 50.9852 39.0203C48.576 44.4573 44.1951 48.7787 38.7257 51.1133C33.2563 53.448 27.1052 53.6222 21.5124 51.6009C15.9197 49.5796 11.3012 45.5131 8.58813 40.2212C5.87507 34.9293 5.26918 28.8056 6.89262 23.0847C8.51606 17.3637 12.2481 12.471 17.3363 9.39292C22.4245 6.31482 28.4905 5.28026 34.3114 6.49782C40.1322 7.71537 45.275 11.0945 48.703 15.9539L42.9916 19.9828C40.5832 16.5687 36.97 14.1946 32.8804 13.3392C28.7908 12.4838 24.529 13.2106 20.9541 15.3732C17.3792 17.5358 14.7572 20.9733 13.6166 24.9927C12.476 29.0121 12.9017 33.3145 14.8078 37.0325C16.7139 40.7504 19.9588 43.6074 23.8881 45.0276C27.8175 46.4477 32.1391 46.3253 35.9818 44.685C39.8244 43.0448 42.9024 40.0087 44.595 36.1888C46.2876 32.3689 46.4691 28.0493 45.1028 24.1009L51.708 21.8153Z" fill="#9469D1"/>
<path d="M48.5076 15.6811C49.9077 17.607 51.0071 19.7345 51.7679 21.9906L45.0618 24.2521C44.5301 22.6754 43.7619 21.1886 42.7834 19.8427L48.5076 15.6811Z" fill="#96DB00"/>
</g>
<defs>
<filter id="filter0_d_266_9159" x="0" y="0" width="59" height="59" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="1"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_266_9159"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_266_9159" result="shape"/>
</filter>
</defs>
</svg>

30
src/assets/SvgIcons/昨日新增金币.svg

@ -0,0 +1,30 @@
<svg width="69" height="69" viewBox="0 0 69 69" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_352_61732)">
<path d="M66.0273 34.5C66.0273 51.897 51.9243 66 34.5273 66C17.1304 66 3.02734 51.897 3.02734 34.5C3.02734 17.103 17.1304 3 34.5273 3C51.9243 3 66.0273 17.103 66.0273 34.5ZM17.5956 34.5C17.5956 43.8512 25.1762 51.4318 34.5273 51.4318C43.8785 51.4318 51.4591 43.8512 51.4591 34.5C51.4591 25.1488 43.8785 17.5682 34.5273 17.5682C25.1762 17.5682 17.5956 25.1488 17.5956 34.5Z" fill="#80A6FF"/>
<g filter="url(#filter1_d_352_61732)">
<path d="M6.54257 32.4138C6.99533 26.3434 9.40963 20.5847 13.4214 16.0063C17.4331 11.428 22.8247 8.27814 28.7831 7.03191C34.7415 5.78567 40.9436 6.5106 46.4539 9.09737C51.9643 11.6841 56.4842 15.9925 59.3319 21.3726C62.1796 26.7528 63.2008 32.913 62.2414 38.9242C61.2819 44.9355 58.394 50.4718 54.013 54.6982C49.632 58.9246 43.9956 61.612 37.9538 62.3549C31.912 63.0979 25.7924 61.8563 20.518 58.8172L24.7373 51.4944C28.4233 53.6182 32.7 54.4859 36.9223 53.9667C41.1446 53.4474 45.0835 51.5694 48.1452 48.6158C51.2068 45.6622 53.225 41.7931 53.8955 37.5922C54.566 33.3913 53.8524 29.0862 51.8623 25.3263C49.8721 21.5664 46.7134 18.5556 42.8625 16.7478C39.0116 14.9401 34.6774 14.4334 30.5134 15.3044C26.3494 16.1753 22.5814 18.3765 19.7778 21.5761C16.9743 24.7757 15.287 28.8001 14.9706 33.0424L6.54257 32.4138Z" fill="#F7D47B"/>
</g>
</g>
<defs>
<filter id="filter0_d_352_61732" x="0.736435" y="0.709091" width="67.5818" height="67.5818" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="1.14545"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_352_61732"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_352_61732" result="shape"/>
</filter>
<filter id="filter1_d_352_61732" x="5.39751" y="6.4375" width="58.3417" height="58.4179" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1.14545"/>
<feGaussianBlur stdDeviation="0.572727"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_352_61732"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_352_61732" result="shape"/>
</filter>
</defs>
</svg>

18
src/assets/SvgIcons/消耗.svg

@ -0,0 +1,18 @@
<svg width="63" height="63" viewBox="0 0 63 63" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_266_9155)">
<path d="M59 31.5C59 38.0669 56.65 44.417 52.3751 49.4019C48.1002 54.3868 42.1825 57.6774 35.6924 58.6786C29.2023 59.6797 22.5681 58.3253 16.9897 54.8603C11.4114 51.3953 7.25713 46.0484 5.27827 39.7868C3.29941 33.5251 3.62661 26.762 6.20068 20.7206C8.77476 14.6793 13.4258 9.75842 19.3126 6.8481C25.1993 3.93778 31.9333 3.23011 38.2965 4.85308C44.6596 6.47605 50.232 10.3225 54.0058 15.6968L47.312 20.397C44.6607 16.6212 40.7457 13.9188 36.275 12.7785C31.8044 11.6382 27.0733 12.1354 22.9374 14.1802C18.8015 16.2249 15.5338 19.6821 13.7253 23.9267C11.9168 28.1712 11.6869 32.9228 13.0772 37.3221C14.4675 41.7214 17.3862 45.478 21.3054 47.9124C25.2247 50.3468 29.8857 51.2984 34.4455 50.595C39.0053 49.8916 43.1629 47.5798 46.1664 44.0775C49.1698 40.5752 50.8209 36.1138 50.8209 31.5H59Z" fill="#F7D47C"/>
<path d="M53.7429 15.329C57.8941 21.0388 59.6905 28.1264 58.7601 35.1242C57.8298 42.1219 54.244 48.4939 48.7452 52.9208C43.2464 57.3477 36.2558 59.4905 29.2207 58.9054C22.1857 58.3203 15.645 55.0522 10.9531 49.7777C6.26115 44.5033 3.77725 37.6264 4.01569 30.5711C4.25414 23.5158 7.19666 16.8224 12.234 11.8767C17.2714 6.93111 24.0177 4.11205 31.0762 4.00327C38.1347 3.89448 44.9648 6.5043 50.1521 11.2924L44.535 17.378C40.9098 14.0318 36.1367 12.208 31.2038 12.284C26.271 12.36 21.5564 14.3301 18.036 17.7864C14.5157 21.2426 12.4593 25.9203 12.2927 30.8509C12.1261 35.7814 13.8619 40.5873 17.1409 44.2733C20.4198 47.9594 24.9907 50.2433 29.9071 50.6521C34.8236 51.061 39.7089 49.5636 43.5518 46.4699C47.3946 43.3761 49.9005 38.9231 50.5507 34.0327C51.2008 29.1424 49.9454 24.1893 47.0444 20.199L53.7429 15.329Z" fill="#7DB7FA"/>
</g>
<defs>
<filter id="filter0_d_266_9155" x="0" y="0" width="63" height="63" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_266_9155"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_266_9155" result="shape"/>
</filter>
</defs>
</svg>

29
src/assets/SvgIcons/现金管理.svg
File diff suppressed because it is too large
View File

22
src/assets/SvgIcons/设置.svg

@ -0,0 +1,22 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.5471 0.599735C18.5459 0.0869799 24.0461 -0.443875 31.9786 0.599735C39.9111 1.64335 44.9571 6.05789 46.6388 11.3799C48.3204 16.7019 48.1984 27.1094 47.5788 32.3158C47.07 38.5475 43.4427 45.924 34.7683 47.2091C26.0939 48.4941 15.4335 48.1514 11.1572 46.6287C6.88095 45.1059 1.21772 41.8638 0.477232 32.7825C-0.263252 23.7012 -0.752563 11.5131 3.86834 6.57115C7.07241 2.76432 12.5484 1.11249 15.5471 0.599735Z" fill="#D0DDFF"/>
<g filter="url(#filter0_d_402_35688)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.3024 5.17784C23.5385 4.46416 25.0615 4.46416 26.2976 5.17784L39.6024 12.8593C40.8385 13.573 41.6 14.892 41.6 16.3193V31.6823C41.6 33.1097 40.8385 34.4286 39.6024 35.1423L26.2976 42.8238C25.0615 43.5375 23.5385 43.5375 22.3024 42.8238L8.99763 35.1423C7.76149 34.4286 7 33.1097 7 31.6823V16.3193C7 14.892 7.76149 13.573 8.99763 12.8593L22.3024 5.17784Z" fill="url(#paint0_linear_402_35688)"/>
</g>
<path d="M34.5525 26.275C35.142 23.917 34.4935 22.378 34.4935 22.378L32.2381 22.2673C32.0404 21.5594 31.7509 20.8919 31.3832 20.2744L32.9047 18.5469C31.6542 16.4628 30.1074 15.8331 30.1074 15.8331L28.4321 17.3517C27.808 16.9886 27.1329 16.7074 26.4188 16.517L26.275 14.2476C23.9171 13.658 22.378 14.3066 22.378 14.3066L22.2709 16.488C21.5671 16.6649 20.8975 16.926 20.2785 17.2705L18.6927 15.8331C18.6927 15.8331 17.1458 16.4628 15.8953 18.5469C16.2928 18.9981 17.0748 19.5161 17.1218 20.1795C17.1333 20.3432 16.3691 22.2767 16.3361 22.2784L14.3065 22.378C14.3065 22.378 13.6581 23.917 14.2475 26.275L16.2589 26.4025C16.4489 27.2023 16.7537 27.9556 17.1573 28.6464L15.833 30.1074C15.833 30.1074 16.4628 31.6542 18.5469 32.9047L20.0562 31.5753C20.74 31.9847 21.4865 32.2965 22.2799 32.4951L22.378 34.4935C22.378 34.4935 23.9171 35.142 26.275 34.5525L26.4072 32.4668C27.2107 32.2538 27.9649 31.9246 28.6527 31.4951L30.2531 32.9047C32.3373 31.6543 32.967 30.1074 32.967 30.1074L31.5092 28.4991C31.8683 27.8537 32.1379 27.1538 32.3142 26.4168L34.5525 26.275ZM24.4 29.2449C21.7243 29.2449 19.5552 27.0758 19.5552 24.4C19.5552 21.7243 21.7243 19.5552 24.4 19.5552C27.0757 19.5552 29.2449 21.7243 29.2449 24.4C29.2449 27.0759 27.0757 29.2449 24.4 29.2449Z" fill="white"/>
<defs>
<filter id="filter0_d_402_35688" x="4" y="0.642578" width="42.6016" height="46.7168" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="1"/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.172549 0 0 0 0 0.360784 0 0 0 0 0.968627 0 0 0 0.3 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_402_35688"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_402_35688" result="shape"/>
</filter>
<linearGradient id="paint0_linear_402_35688" x1="57.4583" y1="24.0008" x2="19.4991" y2="-7.09544" gradientUnits="userSpaceOnUse">
<stop stop-color="#2C5CF7"/>
<stop offset="1" stop-color="#80A6FF"/>
</linearGradient>
</defs>
</svg>

27
src/assets/SvgIcons/金币管理.svg

@ -0,0 +1,27 @@
<svg width="31" height="31" viewBox="0 0 31 31" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.83596 0.876936C11.7158 0.555502 15.1637 0.222722 20.1364 0.876936C25.1091 1.53115 28.2723 4.29852 29.3265 7.63474C30.3807 10.971 30.3042 17.4952 29.9158 20.7589C29.5969 24.6654 27.323 29.2896 21.8852 30.0952C16.4475 30.9007 9.76469 30.6859 7.08401 29.7313C4.40334 28.7767 0.853199 26.7444 0.389008 21.0515C-0.0751821 15.3587 -0.381919 7.71828 2.51481 4.62027C4.52336 2.23386 7.95615 1.19837 9.83596 0.876936Z" fill="#FFF8D4"/>
<g filter="url(#filter0_d_352_61805)">
<path d="M7.66202 4H23.1458C25.0511 4 26.0032 4.95261 26.0032 6.85734V24.4109C26.0032 26.3161 25.0506 27.2682 23.1458 27.2682H7.66202C5.7568 27.2682 4.80469 26.3156 4.80469 24.4109V6.85734C4.80469 4.95261 5.7573 4 7.66202 4Z" fill="url(#paint0_linear_352_61805)"/>
</g>
<path d="M6.91658 10.8059H3.43517C3.05454 10.8059 2.6895 10.6547 2.42035 10.3855C2.1512 10.1164 2 9.75135 2 9.37072C2 8.99009 2.1512 8.62505 2.42035 8.3559C2.6895 8.08675 3.05454 7.93555 3.43517 7.93555H6.91708C7.29771 7.93555 7.66275 8.08675 7.9319 8.3559C8.20104 8.62505 8.35225 8.99009 8.35225 9.37072C8.35225 9.75135 8.20104 10.1164 7.9319 10.3855C7.66275 10.6547 7.29771 10.8059 6.91708 10.8059H6.91658ZM6.91658 17.0531H3.43517C3.05454 17.0531 2.6895 16.9019 2.42035 16.6328C2.1512 16.3636 2 15.9986 2 15.618C2 15.2373 2.1512 14.8723 2.42035 14.6031C2.6895 14.334 3.05454 14.1828 3.43517 14.1828H6.91708C7.29771 14.1828 7.66275 14.334 7.9319 14.6031C8.20104 14.8723 8.35225 15.2373 8.35225 15.618C8.35225 15.9986 8.20104 16.3636 7.9319 16.6328C7.66275 16.9019 7.29771 17.0531 6.91708 17.0531H6.91658ZM6.91658 23.3004H3.43517C3.05454 23.3004 2.6895 23.1491 2.42035 22.88C2.1512 22.6109 2 22.2458 2 21.8652C2 21.4846 2.1512 21.1195 2.42035 20.8504C2.6895 20.5812 3.05454 20.43 3.43517 20.43H6.91708C7.15314 20.4298 7.3856 20.4879 7.59383 20.5991C7.80206 20.7103 7.97962 20.8712 8.11076 21.0675C8.2419 21.2638 8.32256 21.4894 8.34558 21.7243C8.3686 21.9593 8.33327 22.1962 8.24273 22.4142C8.13406 22.6765 7.95007 22.9007 7.71403 23.0584C7.47798 23.2161 7.20047 23.3003 6.91658 23.3004Z" fill="url(#paint1_linear_352_61805)"/>
<path d="M20.123 18.3164C19.1741 18.8897 17.6962 19.2285 16 19.2285C14.3038 19.2285 12.8258 18.8898 11.877 18.3164V18.8652C11.9303 18.9713 12.0446 19.1022 12.2461 19.2412C12.4652 19.3922 12.7642 19.5399 13.1338 19.6689C13.8727 19.9269 14.8673 20.1025 16 20.1025C17.1327 20.1025 18.1273 19.9269 18.8662 19.6689C19.2358 19.5399 19.5349 19.3922 19.7539 19.2412C19.9554 19.1022 20.0697 18.9713 20.123 18.8652V18.3164ZM20.999 18.7852L21.248 18.7949C21.2104 19.6752 20.4237 20.2844 19.4668 20.6641C18.4874 21.0525 17.2197 21.25 16 21.25C14.7803 21.25 13.5129 21.0526 12.5332 20.6641C11.5758 20.2843 10.7878 19.6754 10.75 18.7949V12.2383C10.7682 11.3459 11.5549 10.7273 12.5146 10.3428C13.4967 9.94928 14.7725 9.75 16 9.75C17.2346 9.75 18.5185 9.95144 19.5029 10.3496C20.4649 10.7387 21.2498 11.3655 21.25 12.2686V12.2754L21.249 18.7852V18.7949L20.999 18.7852ZM20.123 16.209C19.1741 16.7823 17.6963 17.1211 16 17.1211C14.3037 17.1211 12.8258 16.7824 11.877 16.209V16.8086C11.915 16.9128 12.0126 17.0468 12.207 17.1924C12.4121 17.346 12.7012 17.4974 13.0674 17.6309C13.7991 17.8975 14.8107 18.0811 16 18.0811C17.1889 18.0811 18.1999 17.8973 18.9316 17.6309C19.2978 17.4975 19.5868 17.3459 19.792 17.1924C19.9867 17.0467 20.085 16.9129 20.123 16.8086V16.209ZM20.123 13.8857C19.677 14.147 19.1293 14.3487 18.5391 14.4941C17.7499 14.6886 16.8645 14.7881 16 14.7881C15.1355 14.7881 14.2501 14.6886 13.4609 14.4941C12.8707 14.3487 12.323 14.147 11.877 13.8857V14.7012C11.9149 14.8054 12.0125 14.9393 12.207 15.085C12.4121 15.2385 12.7013 15.39 13.0674 15.5234C13.7991 15.7901 14.8107 15.9746 16 15.9746C17.189 15.9746 18.1999 15.79 18.9316 15.5234C19.2979 15.39 19.5867 15.2385 19.792 15.085C19.9863 14.9396 20.0848 14.8064 20.123 14.7021V13.8857ZM16 10.8975C14.7633 10.8975 13.6919 11.1069 12.9385 11.4033C12.5608 11.5519 12.2736 11.7178 12.0859 11.8809C11.8935 12.0481 11.838 12.1825 11.8379 12.2686C11.8379 12.3546 11.8935 12.489 12.0859 12.6562C12.2736 12.8193 12.5609 12.9852 12.9385 13.1338C13.6919 13.4302 14.7633 13.6406 16 13.6406C17.2367 13.6406 18.3081 13.4302 19.0615 13.1338C19.439 12.9853 19.7254 12.8192 19.9131 12.6562C20.1058 12.4888 20.1621 12.3546 20.1621 12.2686C20.162 12.1825 20.1056 12.0481 19.9131 11.8809C19.7254 11.7179 19.4388 11.5518 19.0615 11.4033C18.3081 11.1069 17.2367 10.8975 16 10.8975Z" fill="white" stroke="white" stroke-width="0.5"/>
<defs>
<filter id="filter0_d_352_61805" x="3.80469" y="3" width="23.1992" height="25.2686" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="0.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 0.701961 0 0 0 0 0.101961 0 0 0 0.3 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_352_61805"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_352_61805" result="shape"/>
</filter>
<linearGradient id="paint0_linear_352_61805" x1="36.6024" y1="15.6341" x2="13.4348" y2="-5.47272" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFB31A"/>
<stop offset="1" stop-color="#FFD86B"/>
</linearGradient>
<linearGradient id="paint1_linear_352_61805" x1="11.5286" y1="15.618" x2="0.678436" y2="11.1321" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFB31A"/>
<stop offset="1" stop-color="#FFD86B"/>
</linearGradient>
</defs>
</svg>

9
src/assets/SvgIcons/金币系统LOGO.svg
File diff suppressed because it is too large
View File

BIN
src/assets/backgroundBlue.png

After

Width: 1440  |  Height: 810  |  Size: 1.3 MiB

BIN
src/assets/半透明background.png

After

Width: 1874  |  Height: 1054  |  Size: 10 KiB

207
src/components/workspace/CashManagement.vue

@ -0,0 +1,207 @@
<template>
<div class="cash-management">
<div class="cash-title">
<div class="text1"> 现金管理
<span class="text1-update-time">最后更新时间{{
workDataUpdateTime && workDataUpdateTime !== '1970-01-01 08:00:00' ? workDataUpdateTime : '该地区暂无数据'
}}</span>
</div>
</div>
<div class="text2"><span class="text2-income">总营收{{ cashData.totalIncome }}</span></div>
<div class="chart-container">
<!-- 左侧数据列表 -->
<div class="market-data">
<div v-for="market in cashData.markets" :key="market.name" class="market-item">
<span class="market-name">{{ market.name }}:</span>
<span class="market-value">{{ market.value.toLocaleString() }}</span>
</div>
</div>
<!-- 图表区域 -->
<div ref="chartRef" class="chart"></div>
</div>
</div>
</template>
<script setup>
import * as echarts from 'echarts'
import {ref, onMounted} from 'vue'
//
const cashData = ref({
updateTime: '2025-09-24 12:00:00',
totalIncome: 1200000,
markets: [
{name: '北京', value: 450000},
{name: '上海', value: 300000},
{name: '广州', value: 200000},
{name: '深圳', value: 150000},
{name: '其他', value: 100000}
]
})
const chartRef = ref(null)
let chartInstance = null
const renderChart = () => {
if (!chartInstance && chartRef.value) {
chartInstance = echarts.init(chartRef.value)
}
const option = {
tooltip: {trigger: 'item'},
legend: {
bottom: 5, //
left: 'center'
},
series: [
{
type: 'pie',
radius: ['40%', '70%'],
data: cashData.value.markets,
center: ['60%', '45%'] //
}
]
}
chartInstance.setOption(option)
}
onMounted(() => {
renderChart()
})
</script>
<style scoped>
/* 背景卡片大小 */
.cash-management {
margin: 10px 5px;
width: 100%;
height: 50vh;
flex-shrink: 0;
border-radius: 8px;
background: #E7F4FD;
box-shadow: 0 2px 2px 0 #00000040;
display: flex;
flex-direction: column;
align-items: center;
}
/*
.cash-card {
width: 100%;
}
.chart {
width: 100%;
height: 200px;
} */
.cash-title {
width: 100%;
height: 5vh;
flex-shrink: 0;
border-radius: 8px;
background: linear-gradient(90deg, #E4F0FC 0%, #C6ADFF 50%, #E4F0FC 100%);
box-shadow: 0 2px 2px 0 #00152940;
display: flex;
align-items: center;
justify-content: center;
}
.text1 {
color: #040a2d;
font-family: "PingFang SC";
font-size: 28px;
font-style: normal;
font-weight: 900;
line-height: 31.79px;
}
.text1-update-time {
width: 100%;
height: 26px;
flex-shrink: 0;
color: #040a2d;
font-family: "PingFang SC";
font-size: 20px;
font-style: normal;
font-weight: 700;
line-height: 31.79px;
}
/* 总收入的渐变框 */
.text2 {
margin: 13px;
width: 95%;
height: 48px;
flex-shrink: 0;
border-radius: 8px;
background: linear-gradient(90deg, #E4F0FC 0%, #C1DCF8 50%, #E4F0FC 100%);
box-shadow: 0 2px 2px 0 #00152940;
display: flex;
align-items: center;
justify-content: center;
}
/* 总收入字体 */
.text2-income {
width: 215px;
height: 26px;
flex-shrink: 0;
color: #040a2d;
font-family: "PingFang SC";
font-size: 20px;
font-style: normal;
font-weight: 900;
line-height: 31.79px;
}
/* 图表容器 */
.chart-container {
display: flex;
align-items: center;
width: 100%;
height: 100%;
padding: 10px;
}
/* 左侧数据列表,使用您指定的样式 */
.market-data {
display: flex;
width: 179px;
flex-direction: column;
align-items: flex-start;
gap: 12px;
padding: 10px;
margin-left: 80px;
}
.market-item {
display: flex;
justify-content: space-between;
width: 100%;
font-family: "PingFang SC";
font-size: 16px;
color: #040a2d;
}
.market-name {
font-weight: 700;
}
.market-value {
font-weight: 500;
}
/* 图表样式 */
.chart {
flex: 1;
height: 300px;
margin-top: 10px;
}
</style>

838
src/components/workspace/GoldGraph.vue

@ -0,0 +1,838 @@
<template>
<div class="graph">
<el-card style="width:100%;" class="graph-card">
<div>
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
<el-tab-pane label="金币充值" name="recharge"></el-tab-pane>
<el-tab-pane label="金币消费" name="consume"></el-tab-pane>
</el-tabs>
</div>
<div class="condition">
<div class="stats">
<div v-if="activeTab === 'consume'">合计{{ sumConsume / 100 }}</div>&nbsp;&nbsp;
永久金币: {{ activeTab === 'recharge' ? sumRechargePermanent / 100 : sumConsumePermanent / 100 }}&nbsp;&nbsp;
免费金币: {{ activeTab === 'recharge' ? sumRechargeFree / 100 : sumConsumeFree / 100 }}&nbsp;&nbsp;
任务金币: {{ activeTab === 'recharge' ? sumRechargeTask / 100 : sumConsumeTask / 100 }}&nbsp;&nbsp;
</div>
<div>
<el-button @click="getYes()" size="small" :type="activeTimeRange === 'yes' ? 'primary' : ''">昨天
</el-button>
<el-button @click="getToday()" size="small" :type="activeTimeRange === 'today' ? 'primary' : ''">今天
</el-button>
<el-button @click="getWeek()" size="small" :type="activeTimeRange === 'week' ? 'primary' : ''">本周
</el-button>
<el-button @click="getMonth()" size="small" :type="activeTimeRange === 'month' ? 'primary' : ''">本月
</el-button>
<el-button @click="getYear()" size="small" :type="activeTimeRange === 'year' ? 'primary' : ''">本年
</el-button>
</div>
<div>
<el-date-picker size="small" v-model="dateRange" type="datetimerange" range-separator=""
start-placeholder="开始时间" end-placeholder="结束时间" format="YYYY-MM-DD HH:mm:ss"
style="width:20vw;margin-left:0.5vw;" value-format="YYYY-MM-DD HH:mm:ss"
:default-time="defaultTime"
:disabled-date="disabledDate" @change="handleDatePickerChange"/>
<el-button type="primary" size="small" style="margin-left: 0.5vw" @click="getChartData">查询</el-button>
</div>
</div>
<div class="graph-content">
<div ref="chartRef" class="left"></div>
<div class="right">
<el-card class="graph-card-list">
<div class="card-large">金币{{ activeTab === 'recharge' ? '充值' : '消费' }}排名</div>
<el-select class="card-select" v-model="selectedType" style="width: 100%; margin-bottom: 15px">
<el-option label="全部类型" value="all"></el-option>
<el-option label="永久金币" value="permanent"></el-option>
<el-option label="免费金币" value="free"></el-option>
<el-option label="任务金币" value="task"></el-option>
</el-select>
<el-table class="card-table" :data="tableData" height="320px">
<el-table-column prop="rank" label="排名" width="60" align="center"></el-table-column>
<el-table-column prop="market" label="地区" align="center">
<template #default="scope">
<span>{{ marketMapping[scope.row.market] || scope.row.market }}</span>
</template>
</el-table-column>
<el-table-column prop="coinAmount" label="金币数量" align="center">
<template #default="{ row }">
{{ row.coinAmount.toLocaleString() }}
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</div>
</el-card>
</div>
</template>
<script setup>
import * as echarts from 'echarts'
import {onMounted, onUnmounted, ref, watch} from 'vue'
import API from '@/util/http'
import {ElMessage} from 'element-plus'
import dayjs from 'dayjs';
import utc from 'dayjs-plugin-utc'
import {marketMapping} from "@/utils/marketMap.js";
dayjs.extend(utc)
const defaultTime = [
new Date(2000, 1, 1, 0, 0, 0),
new Date(2000, 2, 1, 23, 59, 59),
]
//
const markets = ref([])
//
const dateRange = ref([])
const activeTab = ref('recharge')
const selectedType = ref('all')
const tableData = ref([])
const chartRef = ref(null)
let chartInstance = null
//
const sumRechargePermanent = ref(0)
const sumRechargeFree = ref(0)
const sumRechargeTask = ref(0)
const sumConsumePermanent = ref(0)
const sumConsumeFree = ref(0)
const sumConsumeTask = ref(0)
const sumConsume = ref(0)
//
const adminData = ref({})
//
const currentGold = ref(0)
const dailyChange = ref(0)
const currentPermanent = ref(0)
const currentFree = ref(0)
const currentFreeJune = ref(0)
const currentFreeDecember = ref(0)
const currentTask = ref(0)
const yearlyRecharge = ref(0)
const yearlyMoney = ref(0)
const recharge = ref(0)
const money = ref(0)
const yearlyReduce = ref(0)
const yearlyConsume = ref(0)
const yearlyRefund = ref(0)
const dailyReduce = ref(0)
const dailyConsume = ref(0)
const dailyRefund = ref(0)
const yearlyRechargeNum = ref(0)
const sumWow = ref(0)
const sumDaily = ref(0)
const rechargeNum = ref(0)
const ydayRechargeNum = ref(0)
const firstRecharge = ref(0)
const length = ref(0)
//
const chartLoading = ref(true)
const handleResize = () => {
if (chartInstance.value) {
try {
chartInstance.value.resize()
console.log('resize一下')
} catch (error) {
console.error('图表resize失败:', error)
}
}
}
//
const initChart = () => {
if (!chartInstance && chartRef.value) {
chartInstance = echarts.init(chartRef.value)
window.addEventListener('resize', handleResize)
}
}
//
const destroyChart = () => {
if (chartInstance.value) {
try {
chartInstance.value.dispose()
} catch (error) {
console.error('图表销毁失败:', error)
}
chartInstance.value = null
}
window.removeEventListener('resize', handleResize)
}
const formatDate = function (date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
//
const getYes = function () {
const yesterday = dayjs().subtract(1, 'day')
const startTime = yesterday.startOf('day').format('YYYY-MM-DD HH:mm:ss')
const endTime = yesterday.endOf('day').format('YYYY-MM-DD HH:mm:ss')
dateRange.value = [startTime, endTime]
console.log('看看dateRange', dateRange.value)
activeTimeRange.value = 'yes' //
getChartData()
}
//
const getToday = function () {
const today = dayjs()
const startTime = today.startOf('day').format('YYYY-MM-DD HH:mm:ss')
const endTime = today.endOf('day').format('YYYY-MM-DD HH:mm:ss')
// const endTime = today.add(1, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss')
dateRange.value = [startTime, endTime]
console.log('看看dateRange', dateRange.value)
activeTimeRange.value = 'today' //
getChartData()
}
//
const getWeek = function () {
const today = dayjs();
// 01...6
const day = today.day();
// 6
let monday = today.subtract(day === 0 ? 6 : day - 1, 'day');
// (7 - day)
let sunday = today.add(day === 0 ? 0 : 7 - day, 'day');
//
const startTime = monday.startOf('day').format('YYYY-MM-DD HH:mm:ss');
const endTime = sunday.endOf('day').format('YYYY-MM-DD HH:mm:ss');
dateRange.value = [startTime, endTime];
console.log('本周时间范围(周一到周日):', dateRange.value);
activeTimeRange.value = 'week';
getChartData();
};
//
const getMonth = function () {
const today = dayjs()
const startTime = today.startOf('month').format('YYYY-MM-DD HH:mm:ss')
// const endTime = today.add(1, 'month').startOf('month').format('YYYY-MM-DD HH:mm:ss')
const endTime = today.endOf('month').format('YYYY-MM-DD HH:mm:ss')
dateRange.value = [startTime, endTime]
console.log('看看dateRange', dateRange.value)
activeTimeRange.value = 'month' //
getChartData()
}
//
const getYear = function () {
const today = dayjs()
const startTime = today.startOf('year').format('YYYY-MM-DD HH:mm:ss')
const endTime = today.endOf('year').format('YYYY-MM-DD HH:mm:ss')
// const endTime = today.add(1, 'year').startOf('year').format('YYYY-MM-DD HH:mm:ss')
dateRange.value = [startTime, endTime]
console.log('看看dateRange', dateRange.value)
activeTimeRange.value = 'year' //
getChartData()
}
// ( = + 6 + 12 + + )
const processData = (data) => {
const summary = {
currentGold: 0,
dailyChange: 0,
currentPermanent: 0,
currentFreeJune: 0,
currentFreeDecember: 0,
currentTask: 0,
currentFree: 0,
recharge: 0,
money: 0,
yearlyRecharge: 0,
yearlyMoney: 0,
consumePermanent: 0,
consumeFreeJune: 0,
consumeFreeDecember: 0,
consumeTask: 0,
refundPermanent: 0,
refundFreeJune: 0,
refundFreeDecember: 0,
refundTask: 0,
dailyReduce: 0,
yearlyConsume: 0,
yearlyRefund: 0,
yearlyReduce: 0,
rechargeNum: 0,
ydayRechargeNum: 0,
firstRecharge: 0,
sumWow: 0,
sumDaily: 0,
yearlyRechargeNum: 0
}
//
data.marketCards.forEach(market => {
for (const i in summary) {
if (market[i] !== undefined && market[i] !== null) { // number
summary[i] += market[i]
}
}
})
// wowdaily
length.value = data.markets.length
console.log(length.value)
// 退
const yesterdayConsume = summary.consumePermanent + summary.consumeFreeJune + summary.consumeFreeDecember + summary.consumeTask
const yesterdayRefund = summary.refundPermanent + summary.refundFreeJune + summary.refundFreeDecember + summary.refundTask
//
currentGold.value = summary.currentGold.toFixed(2)
dailyChange.value = summary.dailyChange.toFixed(2)
currentPermanent.value = summary.currentPermanent.toFixed(2)
currentFree.value = summary.currentFree.toFixed(2)
currentFreeJune.value = summary.currentFreeJune.toFixed(2)
currentFreeDecember.value = summary.currentFreeDecember.toFixed(2)
currentTask.value = summary.currentTask.toFixed(2)
yearlyRecharge.value = summary.yearlyRecharge.toFixed(2)
yearlyMoney.value = summary.yearlyMoney.toFixed(2)
recharge.value = summary.recharge.toFixed(2)
money.value = summary.money.toFixed(2)
yearlyReduce.value = summary.yearlyReduce.toFixed(2)
yearlyConsume.value = summary.yearlyConsume.toFixed(2)
yearlyRefund.value = summary.yearlyRefund.toFixed(2)
dailyReduce.value = summary.dailyReduce.toFixed(2)
dailyConsume.value = yesterdayConsume.toFixed(2)
dailyRefund.value = yesterdayRefund.toFixed(2)
yearlyRechargeNum.value = summary.yearlyRechargeNum
// //
// sumWow.value = (marketCards.sumWow / length.value).toFixed(2)
// //
// sumDaily.value = (marketCards.sumDaily / length.value).toFixed(2)
// rechargeNum.value = summary.rechargeNum
ydayRechargeNum.value = summary.ydayRechargeNum
firstRecharge.value = summary.firstRecharge
}
//
const disabledDate = (time) => {
const limitDate = new Date(2025, 0, 1);
return time.getTime() < limitDate.getTime();
}
//
const getMarkets = async () => {
console.log("adminData", adminData.value.account)
try {
const response = await API({
url: '/general/adminMarkets',
data: {
account: adminData.value.account
}
})
if (Array.isArray(response.data)) {
// markets.value = response.data.filter(data => data !== "1")
markets.value = response.data
console.log('市场列表获取成功:', markets.value)
} else {
console.error('获取市场列表失败', response)
ElMessage.error('获取市场列表失败')
}
} catch (error) {
console.error('获取市场列表失败:', error)
ElMessage.error('获取市场列表失败')
}
}
//
const getChartData = async () => {
try {
//
if (!markets.value || markets.value.length === 0) {
await getMarkets()
}
//
if (!dateRange.value || dateRange.value.length === 0) {
getYear()
}
const params = {
markets: markets.value,
startDate: dateRange.value[0],
endDate: dateRange.value[1]
};
const response = await API({
url: '/workbench/getGraph',
data: params
})
console.log('看看params', params)
if (Array.isArray(response.marketGraphs)) {
// const filteredGraphs = response.marketGraphs.filter(data => data.market !== "1");
//
processChartData(response.marketGraphs)
//
processRankingData(response.marketGraphs)
} else {
console.error('获取图表数据失败:', response)
ElMessage.error('获取图表数据失败')
}
} catch (error) {
console.error('获取图表数据失败:', error)
ElMessage.error('获取图表数据失败')
}
}
//
const processChartData = (marketCards) => {
const chartData = {
rechargePermanent: [],
rechargeFree: [],
rechargeTask: [],
consumePermanent: [],
consumeFree: [],
consumeTask: [],
sumConsume: []
}
//
const sumRechargePermanent1 = ref(0)
const sumRechargeFree1 = ref(0)
const sumRechargeTask1 = ref(0)
const sumConsumePermanent1 = ref(0)
const sumConsumeFree1 = ref(0)
const sumConsumeTask1 = ref(0)
const sumConsume1 = ref(0)
marketCards.forEach(market => {
chartData.rechargePermanent.push(market.sumRechargePermanent / 100 || 0)
chartData.rechargeFree.push(market.sumRechargeFree / 100 || 0)
chartData.rechargeTask.push(market.sumRechargeTask / 100 || 0)
chartData.consumePermanent.push(market.sumConsumePermanent / 100 || 0)
chartData.consumeFree.push(market.sumConsumeFree / 100 || 0)
chartData.consumeTask.push(market.sumConsumeTask / 100 || 0)
chartData.sumConsume.push(market.sumConsume / 100 || 0)
//
sumRechargePermanent1.value += (market.sumRechargePermanent || 0)
sumRechargeFree1.value += (market.sumRechargeFree || 0)
//sumRechargeTask1.value += (market.sumRechargeTask || 0)
sumConsumePermanent1.value += (market.sumConsumePermanent || 0)
sumConsumeFree1.value += (market.sumConsumeFree || 0)
sumConsumeTask1.value += (market.sumConsumeTask || 0)
sumConsume1.value += (market.sumConsume || 0)
})
sumRechargePermanent.value = sumRechargePermanent1.value
sumRechargeFree.value = sumRechargeFree1.value
sumRechargeTask.value = 0
sumConsumePermanent.value = sumConsumePermanent1.value
sumConsumeFree.value = sumConsumeFree1.value
sumConsumeTask.value = sumConsumeTask1.value
sumConsume.value = sumConsume1.value
updateChart(chartData)
}
const processRankingData = (marketCards) => {
//
const rankingData = marketCards.map(market => {
let coinAmount = 0;
if (activeTab.value === 'recharge') {
//
switch (selectedType.value) {
case 'all':
coinAmount = (market.sumRechargePermanent / 100 || 0) + (market.sumRechargeFree / 100 || 0) + (market.sumRechargeTask / 100 || 0);
break;
case 'permanent':
coinAmount = market.sumRechargePermanent / 100 || 0;
break;
case 'free':
coinAmount = market.sumRechargeFree / 100 || 0;
break;
case 'task':
coinAmount = market.sumRechargeTask / 100 || 0;
break;
}
} else {
//
switch (selectedType.value) {
case 'all':
coinAmount = (market.sumConsumePermanent / 100 || 0) + (market.sumConsumeFree / 100 || 0) + (market.sumConsumeTask / 100 || 0);
break;
case 'permanent':
coinAmount = market.sumConsumePermanent / 100 || 0;
break;
case 'free':
coinAmount = market.sumConsumeFree / 100 || 0;
break;
case 'task':
coinAmount = market.sumConsumeTask / 100 || 0;
break;
}
}
return {
market: market.market,
coinAmount: coinAmount
};
});
//
rankingData.sort((a, b) => b.coinAmount - a.coinAmount);
//
tableData.value = rankingData.map((item, index) => ({
rank: index + 1,
...item
}));
}
watch(selectedType, () => {
getChartData();
});
//
const updateChart = (chartData) => {
if (!chartInstance) {
initChart()
}
chartLoading.value = true
try {
let series = []
let legend = []
if (activeTab.value === 'recharge') {
series = [
{
name: '永久金币',
type: 'bar',
stack: 'recharge',
data: chartData.rechargePermanent,
itemStyle: {color: '#5470c6'},
barWidth: 30
},
{
name: '免费金币',
type: 'bar',
stack: 'recharge',
data: chartData.rechargeFree,
itemStyle: {color: '#91cc75'},
barWidth: 30
},
{
name: '任务金币',
type: 'bar',
stack: 'recharge',
data: chartData.rechargeTask,
itemStyle: {color: '#fac858'},
barWidth: 30
}
]
legend = ['永久金币', '免费金币', '任务金币']
} else {
series = [
{
name: '永久金币',
type: 'bar',
stack: 'consume',
data: chartData.consumePermanent,
itemStyle: {color: '#5470c6'},
barWidth: 30
},
{
name: '免费金币',
type: 'bar',
stack: 'consume',
data: chartData.consumeFree,
itemStyle: {color: '#91cc75'},
barWidth: 30
},
{
name: '任务金币',
type: 'bar',
stack: 'consume',
data: chartData.consumeTask,
itemStyle: {color: '#fac858'},
barWidth: 30
}
]
legend = ['永久金币', '免费金币', '任务金币']
}
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: function (params) {
let result = params[0].name + '<br/>'
let total = 0;
params.forEach(param => {
result += `${param.seriesName}: ${param.value.toLocaleString()}<br/>`;
total += param.value;
})
result += `${activeTab.value === 'recharge' ? '充值' : '消费'}: ${total.toLocaleString()}`;
return result
}
},
legend: {
data: legend,
bottom: 10
},
grid: {
left: '3%',
right: '4%',
bottom: '10%',
containLabel: true
},
xAxis: {
type: 'category',
data: markets.value,
axisLabel: {
interval: 0,
rotate: 0
}
},
yAxis: {
type: 'value',
splitLine: {
lineStyle: {
type: 'dashed',
width: 1,
color: '#000000'
}
},
axisLabel: {
formatter: function (value) {
return value.toLocaleString()
}
},
},
series: series,
// dataZoom: [
// {
// type: 'slider',
// show: true,
// start: 0,
// end: 100,
// maxSpan: 100,
// minSpan: 100,
//
// height: 2,
// },
// ]
}
chartInstance.setOption(option)
} catch (error) {
console.error('图表更新失败:', error)
ElMessage.error('图表渲染失败')
} finally {
setTimeout(() => {
chartLoading.value = false
}, 300)
}
}
//
const handleTabChange = () => {
getChartData()
console.log('标签切换调用图表')
}
const getAdminData = async function () {
try {
const result = await API({url: '/admin/userinfo', data: {}})
adminData.value = result
console.log('用户信息', adminData.value)
} catch (error) {
console.log('请求失败', error)
}
}
//
const getCardData = async () => {
try {
const response = await API({url: '/workbench/getCard', data: {}})
workDataUpdateTime.value = response.updateTime
//
sumWow.value = response.sumWow.toFixed(2)
//
sumDaily.value = response.sumDaily.toFixed(2)
if (response && response.data) {
processData(response.data)
} else if (Array.isArray(response?.marketCards)) {
processData(response)
} else {
console.error('无效的API响应结构:', response)
}
} catch (error) {
console.error('获取卡片数据失败:', error)
}
}
const workDataUpdateTime = ref(null)
//
const activeTimeRange = ref('')
//
const handleDatePickerChange = () => {
activeTimeRange.value = ''
}
onMounted(async () => {
await getAdminData()
await getCardData()
await getMarkets()
getYear()
window.addEventListener('resize', () => {
chartInstance.resize()
})
})
onUnmounted(() => {
destroyChart()
})
</script>
<style scoped lang="scss">
.graph {
.condition {
width: 100%;
height: 1%;
display: flex;
align-items: center;
.stats {
display: flex;
align-items: center;
width: 35vw;
font-size: 15px;
}
}
.graph-content {
flex: 1;
height: auto;
display: flex;
.left {
width: 70%;
height: auto;
}
.right {
flex: 1;
padding: 0.5vw 2vh;
}
}
}
.center-card {
display: flex;
justify-content: center;
align-items: center;
}
.margin-bottom {
margin-bottom: 0.5vh;
}
.card-item {
width: 25%;
height: 28vh;
display: flex;
flex-direction: column;
justify-content: center;
margin-right: 0.25vw;
}
.card-title {
font-weight: bold;
margin-bottom: 1vh;
display: flex;
justify-content: center;
align-items: center;
}
.card-large {
font-weight: bold;
font-size: 16px;
text-align: center;
margin-bottom: 15px;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.graph-card {
background: #F3FAFF;
box-shadow: 0 0 8px 0 #00000040;
}
.graph-card-list {
background: #F3FAFF;
box-shadow: 0 0 8px 0 #00000040;
padding: 12px;
.card-select {
:deep(.el-input__wrapper) {
background-color: #E7F4FD !important;
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25) !important;
border: none !important;
}
:deep(.el-input__inner) {
background-color: transparent !important;
}
:deep(.el-input__suffix) {
background-color: transparent !important;
}
}
/* 表格整体背景:把表格容器设为卡片背景 */
:deep(.el-table) {
background-color: #F3FAFF !important;
box-shadow: none !important;
}
/* 表头/表体 wrapper 与 table body 单元格 */
:deep(.el-table__header-wrapper),
:deep(.el-table__body-wrapper),
:deep(.el-table__body),
:deep(.el-table__header),
:deep(.el-table__body tbody),
:deep(.el-table__body tr),
:deep(.el-table__row),
:deep(.el-table__cell),
:deep(.el-table__body td) {
background-color: transparent !important;
}
/* 表头 */
:deep(.el-table__header th) {
background-color: #F3FAFF !important;
}
/* 行之间的分隔线(更像卡片内表格) */
:deep(.el-table .el-table__row):not(:last-child) {
border-bottom: 1px solid rgba(0,0,0,0.06);
}
}
</style>

667
src/components/workspace/GoldManagement.vue

@ -0,0 +1,667 @@
<template>
<div class="gold-management">
<div class="gold-title">
<div class="text1">
金币管理
<span class="text1-update-time">最后更新时间{{
workDataUpdateTime && workDataUpdateTime !== '1970-01-01 08:00:00' ? workDataUpdateTime : '该地区暂无数据'
}} </span>
</div>
</div>
<!-- 第一行包含两个横向格子 -->
<el-row>
<el-col :span="12">
<!-- 第一个卡片 -->
<div class="card-item-row1">
<div class="card-title">当前金币余量
<span style="font-weight: bold">{{
currentGold / 100
}}</span>&nbsp;&nbsp;&nbsp;&nbsp;较前一日
{{ dailyChange / 100 }}&nbsp;
<template v-if="dailyChange > 0">
<el-image :src="upArrow" style="width: 14px;"/>
</template>
<template v-else-if="dailyChange < 0">
<el-image :src="downArrow" style="width: 14px;"/>
</template>
<template v-else>
<el-image :src="pingArrow" style="width: 14px; padding-top: 12px"/>
</template>
</div>
<div>
<el-row>
<!-- 左边文本信息 -->
<el-col :span="12">
<div class="margin-bottom" style="white-space: nowrap;">
永久金币<b>{{ currentPermanent / 100 }}</b>
</div>
<div class="margin-bottom">&nbsp;</div>
<div class="margin-bottom">免费金币{{ currentFree / 100 }}</div>
<!-- <div class="margin-bottom">&nbsp</div>-->
<!-- <div class="margin-bottom">&nbsp</div>-->
<div class="margin-bottom">
[6月到期{{ currentFreeJune / 100 }}]
</div>
<div class="margin-bottom">&nbsp;</div>
<div class="margin-bottom">任务金币{{ currentTask / 100 }}</div>
</el-col>
<!-- 右边图表 -->
<el-col :span="12">
<!-- <div ref="goldTypeChart" style="width: 100%; height: 100px;"></div>-->
<div style="width: 100%; height: 60px;">&nbsp;</div>
<div class="margin-bottom">
[12月到期{{ currentFreeDecember / 100 }}]
</div>
</el-col>
</el-row>
</div>
</div>
</el-col>
<el-col :span="12">
<!-- 第二个卡片 -->
<div class="card-item-row1">
<div class="card-title">全年累计充值金币数{{ yearlyRecharge / 100 }}</div>
<el-row>
<el-col :span="12">
<div class="center-card">折合新币累计金额</div>
<el-image :src="svg1" style="width: 68px; display: block;margin: 0 auto;"/>
<div class="center-card">{{ yearlyMoney / 100 }}新币</div>
</el-col>
<el-col :span="12" style="border-left: 2px solid #CFE6FE; height: 120px">
<div class="center-card" style="white-space: nowrap;">昨日新增金币{{ recharge / 100 }}</div>
<div ref="rechargeGoldChart" style="width: 68px; height: 68px; display: block;margin: 0 auto;"></div>
<div class="center-card" style="white-space: nowrap;">其中永久金币{{ money / 100 }}</div>
</el-col>
</el-row>
</div>
</el-col>
</el-row>
<!-- 第二行包含两个横向格子 -->
<el-row>
<el-col :span="12">
<!-- 第三个卡片 -->
<div class="card-item">
<div class="card-title">全年累计消费金币数{{ yearlyReduce / 100 }}</div>
<el-row style="height: 200px;">
<el-col :span="12">
<div ref="consumeChart" style="width:100%; height: 68%;"></div>
</el-col>
<el-col :span="12">
<div ref="consumeDetailChart" style="width: 100%; height: 88%;"></div>
</el-col>
</el-row>
</div>
</el-col>
<el-col :span="12">
<!-- 第四个卡片 -->
<div class="card-item">
<div class="card-title">全年累计充值人头数{{ yearlyRechargeNum }}</div>
<el-row style="height: 200px;">
<el-col :span="12" style="border-right: 2px solid #CFE6FE; height: 150px">
<div class="chart5">
<el-image :src="svg2" style="width: 68px; display: block;margin: 0 auto;"/>
<div class="margin-bottom">
<div style="display: flex; gap: 10px; font-size: 14px;">周同比{{ sumWow }}%
<el-image v-if="sumWow > 0" :src="upArrow" style="width: 10px;"/>
<el-image v-else-if="sumWow < 0" :src="downArrow" style="width: 10px;"/>
<el-image v-else :src="pingArrow" style="width: 10px;"/>
</div>
<div style="display: flex; gap: 10px; font-size: 14px;">
日环比{{ sumDaily }}%
<el-image v-if="sumDaily > 0" :src="upArrow" style="width: 10px;"/>
<el-image v-else-if="sumDaily < 0" :src="downArrow" style="width: 10px;"/>
<el-image v-else :src="pingArrow" style="width: 10px; "/>
</div>
</div>
</div>
</el-col>
<!-- 新增的环形图容器 -->
<el-col :span="12">
<div ref="rechargePeopleChart" style="width:100%; height: 68%;"></div>
</el-col>
</el-row>
</div>
</el-col>
</el-row>
</div>
</template>
<script setup>
import {onMounted, ref, nextTick} from 'vue'
import * as echarts from 'echarts'
import API from '@/util/http'
import dayjs from 'dayjs';
import utc from 'dayjs-plugin-utc'
import {ArrowDownBold, ArrowUpBold, SemiSelect} from '@element-plus/icons-vue'
import svg1 from '@/assets/SvgIcons/折合新币累计金额.svg'
import svg2 from '@/assets/SvgIcons/周同比.svg'
import upArrow from '@/assets/SvgIcons/上升箭头.svg'
import downArrow from '@/assets/SvgIcons/下降箭头.svg'
import pingArrow from '@/assets/SvgIcons/持平.svg'
dayjs.extend(utc)
//
const adminData = ref({})
//
const currentGold = ref(0)
const dailyChange = ref(0)
const currentPermanent = ref(0)
const currentFree = ref(0)
const currentFreeJune = ref(0)
const currentFreeDecember = ref(0)
const currentTask = ref(0)
const yearlyRecharge = ref(0)
const yearlyMoney = ref(0)
const recharge = ref(0)
const money = ref(0)
const yearlyReduce = ref(0)
const yearlyConsume = ref(0)
const yearlyRefund = ref(0)
const dailyReduce = ref(0)
const dailyConsume = ref(0)
const dailyRefund = ref(0)
const yearlyRechargeNum = ref(0)
const sumWow = ref(0)
const sumDaily = ref(0)
const rechargeNum = ref(0)
const ydayRechargeNum = ref(0)
const firstRecharge = ref(0)
const length = ref(0)
// ECharts
const goldTypeChart = ref(null)
const rechargeGoldChart = ref(null)
const consumeChart = ref(null)
const consumeDetailChart = ref(null)
const rechargePeopleChart = ref(null)
// ( = + 6 + 12 + + )
const processData = (data) => {
const summary = {
currentGold: 0,
dailyChange: 0,
currentPermanent: 0,
currentFreeJune: 0,
currentFreeDecember: 0,
currentTask: 0,
currentFree: 0,
recharge: 0,
money: 0,
yearlyRecharge: 0,
yearlyMoney: 0,
consumePermanent: 0,
consumeFreeJune: 0,
consumeFreeDecember: 0,
consumeTask: 0,
refundPermanent: 0,
refundFreeJune: 0,
refundFreeDecember: 0,
refundTask: 0,
dailyReduce: 0,
yearlyConsume: 0,
yearlyRefund: 0,
yearlyReduce: 0,
rechargeNum: 0,
ydayRechargeNum: 0,
firstRecharge: 0,
sumWow: 0,
sumDaily: 0,
yearlyRechargeNum: 0
}
//
data.marketCards.forEach(market => {
for (const i in summary) {
if (market[i] !== undefined && market[i] !== null) {
summary[i] += market[i]
}
}
})
// wowdaily
length.value = data.markets.length
console.log(length.value)
// 退
const yesterdayConsume = summary.consumePermanent + summary.consumeFreeJune + summary.consumeFreeDecember + summary.consumeTask
const yesterdayRefund = summary.refundPermanent + summary.refundFreeJune + summary.refundFreeDecember + summary.refundTask
//
currentGold.value = summary.currentGold.toFixed(2)
dailyChange.value = summary.dailyChange.toFixed(2)
currentPermanent.value = summary.currentPermanent.toFixed(2)
currentFree.value = summary.currentFree.toFixed(2)
currentFreeJune.value = summary.currentFreeJune.toFixed(2)
currentFreeDecember.value = summary.currentFreeDecember.toFixed(2)
currentTask.value = summary.currentTask.toFixed(2)
yearlyRecharge.value = summary.yearlyRecharge.toFixed(2)
yearlyMoney.value = summary.yearlyMoney.toFixed(2)
recharge.value = summary.recharge.toFixed(2)
money.value = summary.money.toFixed(2)
yearlyReduce.value = summary.yearlyReduce.toFixed(2)
yearlyConsume.value = summary.yearlyConsume.toFixed(2)
yearlyRefund.value = summary.yearlyRefund.toFixed(2)
dailyReduce.value = summary.dailyReduce.toFixed(2)
dailyConsume.value = yesterdayConsume.toFixed(2)
dailyRefund.value = yesterdayRefund.toFixed(2)
yearlyRechargeNum.value = summary.yearlyRechargeNum
ydayRechargeNum.value = summary.ydayRechargeNum
firstRecharge.value = summary.firstRecharge
//
nextTick(() => {
// initGoldTypeChart();
initRechargeGoldChart();
initConsumeChart();
initConsumeDetailChart();
initRechargePeopleChart();
});
}
//
const initGoldTypeChart = () => {
const myChart = echarts.init(goldTypeChart.value);
const option = {
tooltip: {
trigger: 'item',
formatter: function (params) {
let realValue = 0
if (params.name === '永久金币') realValue = currentPermanent.value / 100
else if (params.name === '免费金币') realValue = (currentFreeJune.value / 100 + currentFreeDecember.value / 100)
else realValue = currentTask.value / 100
return `${params.name}: ${realValue}`
}
},
toolbox: {
show: true,
feature: {}
},
series: [
{
name: 'Nightingale Chart',
type: 'pie',
radius: ['0%', '100%'],
center: ['50%', '50%'],
roseType: 'area',
itemStyle: {
borderRadius: 5
},
data: [
{value: Math.log(currentPermanent.value / 100 + 1), name: '永久金币'},
{value: Math.log((currentFreeJune.value / 100 + currentFreeDecember.value / 100) + 1), name: '免费金币'},
{value: Math.log(currentTask.value / 100 + 1), name: '任务金币'}
],
labelLine: {show: false},
label: {show: false}
}
]
};
myChart.setOption(option);
}
//
const initRechargeGoldChart = () => {
const myChart = echarts.init(rechargeGoldChart.value);
const option = {
series: [
{
type: 'pie',
radius: ['60%', '85%'],
silent: true,
clockwise: true,
label: {show: false},
data: [
{
value: recharge.value / 100,
itemStyle: {color: '#80aaff'}
}
]
},
{
type: 'pie',
radius: ['60%', '75%'],
startAngle: 180,
silent: true,
clockwise: true,
label: {show: false},
data: [
{
value: money.value / 100,
itemStyle: {color: '#f2c97d'}
},
{
value: (recharge.value / 100 - money.value / 100),
itemStyle: {color: 'transparent'}
}
]
}
]
};
myChart.setOption(option);
}
// 退
const initConsumeChart = () => {
const myChart = echarts.init(consumeChart.value);
const option = {
legend: {
orient: 'vertical',
left: '10%',
top: '85',
icon: 'circle',
iconSize: 5,
textSize: 12,
itemWidth: 7,
itemHeight: 7,
},
series: [
{
type: 'pie',
radius: ['30%', '45%'],
center: ['50%', '35%'],
silent: true,
clockwise: true,
label: {show: false},
data: [
{
value: yearlyConsume.value / 100,
name: '消耗:' + yearlyConsume.value / 100,
// name: '' + 1234567890,
itemStyle: {color: '#7DB7FA'}
},
{
value: yearlyRefund.value / 100,
name: '退款:' + yearlyRefund.value / 100,
itemStyle: {color: '#F7D47C'}
}
],
}
]
};
myChart.setOption(option);
};
//
const initConsumeDetailChart = () => {
const myChart = echarts.init(consumeDetailChart.value);
const option = {
//
legend: {
orient: 'vertical',
left: '20%',
top: '85',
icon: 'circle',
iconSize: 5,
itemWidth: 7,
itemHeight: 7,
},
series: [
{
type: 'pie',
radius: ['25%', '40%'],
center: ['50%', '25%'],
silent: true,
clockwise: true,
label: {show: false},
data: [
{
value: dailyConsume.value / 100,
name: '昨日新增消费:' + dailyConsume.value / 100,
itemStyle: {color: '#65C9C9'}
}
]
},
{
type: 'pie',
radius: ['25%', '35%'],
center: ['50%', '25%'],
startAngle: 180,
silent: true,
clockwise: true,
label: {show: false},
data: [
{
value: dailyReduce.value / 100,
name: '昨日新增消耗:' + dailyReduce.value / 100,
// name: '' + 1234567890,
itemStyle: {color: '#9469D1'}
},
{
value: dailyRefund.value / 100,
name: '昨日新增退款:' + dailyRefund.value / 100,
itemStyle: {color: '#B8DB6E'}
}
]
}
]
};
myChart.setOption(option);
};
//
const initRechargePeopleChart = () => {
const myChart = echarts.init(rechargePeopleChart.value);
const option = {
legend: {
orient: 'vertical',
left: '20%',
top: '85',
icon: 'circle',
iconSize: 5,
textSize: 18,
itemWidth: 7,
itemHeight: 7,
},
series: [
{
type: 'pie',
radius: ['30%', '50%'],
center: ['50%', '35%'],
silent: true,
clockwise: true,
label: {show: false},
data: [
{
value: ydayRechargeNum.value,
name: '昨日充值人数:' + ydayRechargeNum.value,
itemStyle: {color: '#65C9C9'}
},
],
},
{
type: 'pie',
radius: ['30%', '45%'],
center: ['50%', '35%'],
silent: true,
clockwise: true,
label: {show: false},
data: [
{
value: firstRecharge.value,
name: '其中首充:' + firstRecharge.value,
itemStyle: {color: '#9469D1'}
},
{
value: ydayRechargeNum.value - firstRecharge.value,
itemStyle: {color: 'transparent'}
}
],
}
]
};
myChart.setOption(option);
}
//
const getCardData = async () => {
try {
const response = await API({url: '/workbench/getCard', data: {}})
workDataUpdateTime.value = response.updateTime
//
sumWow.value = response.sumWow.toFixed(2)
//
sumDaily.value = response.sumDaily.toFixed(2)
if (response && response.data) {
processData(response.data)
} else if (Array.isArray(response?.marketCards)) {
processData(response)
} else {
console.error('无效的API响应结构:', response)
}
} catch (error) {
console.error('获取卡片数据失败:', error)
}
}
const workDataUpdateTime = ref(null)
onMounted(async () => {
await getCardData()
})
</script>
<style scoped>
.center-card {
display: flex;
justify-content: center;
align-items: center;
}
.card-item-row1 {
height: 160px;
width: auto;
background: #E4F0FC;
box-shadow: 0 0 4px 0 #00000040;
border-radius: 10px;
margin-top: 20px;
margin-left: 5px;
margin-right: 5px;
margin-bottom: -5px;
padding-bottom: 10px;
}
.card-item {
height: 200px;
width: auto;
background: #E4F0FC;
box-shadow: 0 0 4px 0 #00000040;
border-radius: 10px;
margin-top: 20px;
margin-left: 5px;
margin-right: 5px;
margin-bottom: -5px;
padding-bottom: 10px;
}
.card-title {
font-weight: bold;
height: 36px;
width: 100%;
flex-shrink: 0;
border-radius: 8px;
background: linear-gradient(90deg, #E4F0FC 0%, #C1DCF8 50%, #E4F0FC 100%);
box-shadow: 0 0 2px 0 #00152940;
display: flex;
align-items: center;
justify-content: center;
margin-top: -5px;
margin-bottom: 10px;
}
.card-item .el-col {
overflow: visible;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.gold-title {
width: 100%;
height: 5vh;
flex-shrink: 0;
border-radius: 8px;
background: linear-gradient(90deg, #E4F0FC 0%, #FFF178 50%, #E4F0FC 100%);
box-shadow: 0 2px 2px 0 #00152940;
display: flex;
align-items: center;
justify-content: center;
}
.text1 {
color: #040a2d;
font-family: " PingFang SC ";
font-size: 28px;
font-style: normal;
font-weight: 900;
line-height: 31.79px;
}
.text1-update-time {
width: 100%;
height: 26px;
flex-shrink: 0;
color: #040a2d;
font-family: "PingFang SC";
font-size: 20px;
font-style: normal;
font-weight: 700;
line-height: 31.79px;
}
/* 背景卡片大小 */
.gold-management {
margin: 10px 5px;
width: 100%;
height: 50vh;
flex-shrink: 0;
border-radius: 8px;
background: #E7F4FD;
box-shadow: 0 2px 2px 0 #00000040;
flex-direction: column;
align-items: center;
}
.margin-bottom {
padding-left: 20px;
}
.chart5 {
margin-top: 15px;
.margin-bottom {
margin-top: 10px;
padding-left: 20px;
}
}
</style>

153
src/router/index.js

@ -1,7 +1,7 @@
import {createRouter, createWebHashHistory} from 'vue-router';
import { createRouter, createWebHashHistory } from 'vue-router';
import axios from "axios";
import {storeToRefs} from "pinia";
import {useAdminStore} from "@/store/index.js";
import { storeToRefs } from "pinia";
import { useAdminStore } from "@/store/index.js";
// 路由定义(包含权限映射 meta.permissionId)
@ -16,25 +16,29 @@ const routes = [
component: () => import("../views/login.vue"),
},
{
meta: {requireAuth: true},
meta: { requireAuth: true },
path: '/',
component: () => import("../views/home.vue"),
children: [
// 工作台
{
path: '/workspace',
name: "workspace",
path: 'workbench',
name: "workbench",
component: () => import("../views/workspace/index.vue"),
meta: {permissionId: 10} // 对应"工作台展示"id=10
meta: { permissionId: 13 } //
},
//金币管理
{
path: '/goldManage',
name: 'goldManage',
meta:{permissionId:3},
children: [
// 审核
{
path: '/audit',
name: "audit",
component: () => import("../views/audit/gold/audit.vue"),
meta: {permissionId: 40},
meta: { permissionId: 14 },
// redirect: '/index',
children: [
// 充值审核
@ -42,21 +46,30 @@ const routes = [
path: 'rechargeAudit',
name: "rechargeAudit",
component: () => import("../views/audit/gold/rechargeAudit.vue"),
meta: {permissionId: [11, 12]} // 对应"查看充值审核"id=11、"充值审批"id=12
meta: { permissionId: [28,30,31,32,33,34,35] } // 对应"查看充值审核"id=11、"充值审批"id=12
},
// 退款审核
{
path: 'refundAudit',
name: "refundAudit",
component: () => import("../views/audit/gold/refundAudit.vue"),
meta: {permissionId: [13, 14]} // 对应"查看退款审核"id=13、"退款审批"id=14
meta: { permissionId: [29,36,37,38,39,40,41] } // 对应"查看退款审核"id=13、"退款审批"id=14
},
]
},{
path: 'beanAudit',
}, {
path: '/beanAudit',
name: "beanAudit",
component: () => import("../views/audit/bean/beanAudit.vue"),
meta: {permissionId: [54,55]}
meta: { permissionId: 15 },
children: [
// 充值审核
{
path: 'addbeanAudit',
name: "addbeanAudit",
component: () => import("../views/audit/bean/beanAudit.vue"),
meta: { permissionId: [42,43,44,45,46,47] } // 对应"查看充值审核"id=11、"充值审批"id=12
},
]
},
@ -66,21 +79,21 @@ const routes = [
name: "coinConsume",
component: () => import("../views/consume/gold/coinConsume.vue"),
// redirect: '/coinConsume/add',
meta: {permissionId: 6},
meta: { permissionId: 19 },
children: [
// 金币新增消耗
{
path: 'add',
name: "addCoinConsume",
component: () => import("../views/consume/gold/addCoinConsume.vue"),
meta: {permissionId: 19} // 对应"提交金币消耗"id=19
meta: { permissionId: 55 } // 对应"提交金币消耗"id=19
},
// 金币消耗明细详情
{
path: 'detail',
name: "coinConsumeDetail",
component: () => import("../views/consume/gold/coinConsumeDetail.vue"),
meta: {permissionId: 20} // 对应"查看金币消耗明细"id=20
meta: { permissionId: 56 } // 对应"查看金币消耗明细"id=20
}
]
},
@ -89,35 +102,35 @@ const routes = [
path: '/beanConsume',
name: "beanConsume",
component: () => import("../views/consume/bean/beanConsume.vue"),
meta: {permissionId: 42},
meta: { permissionId: 20 },
children: [
// 金豆新增消耗
{
path: 'add',
name: "addBeanConsume",
component: () => import("../views/consume/bean/addBeanConsume.vue"),
meta: {permissionId: 49} // 对应"提交金豆消耗"id=49
meta: { permissionId: 57 } // 对应"提交金豆消耗"id=49
},
// 直播
{
path: 'live',
name: "liveStream",
component: () => import("../views/consume/bean/liveStream.vue"),
meta: {permissionId: 50} // 对应"直播"id=50
meta: { permissionId: 58 } // 对应"直播"id=50
},
// 铁粉
{
path: 'fan',
name: "dieHardFan",
component: () => import("../views/consume/bean/dieHardFan.vue"),
meta: {permissionId: 51} // 对应"铁粉"id=51
meta: { permissionId: 59 } // 对应"铁粉"id=51
},
// 文章视频
{
path: 'article',
name: "articleVideo",
component: () => import("../views/consume/bean/articleVideo.vue"),
meta: {permissionId: 52} // 对应"文章视频"id=52
meta: { permissionId: 60 } // 对应"文章视频"id=52
}
]
@ -128,13 +141,14 @@ const routes = [
path: '/rate',
name: "rate",
component: () => import("../views/managerecharge/rate.vue"),
meta: {permissionId: [15, 16]} // 对应"汇率查看"id=15、"汇率修改"id=16
meta: { permissionId: [16,48,49] } // 对应"汇率查看"id=15、"汇率修改"id=16
},
// 金币充值
{
path: '/coinRecharge',
name: "coinRecharge",
component: () => import("../views/recharge/gold/coinRecharge.vue"),
meta: { permissionId: 17 },
// redirect: '/coinRecharge/add',
children: [
// 金币新增充值
@ -142,14 +156,14 @@ const routes = [
path: 'add',
name: "addCoinRecharge",
component: () => import("../views/recharge/gold/addCoinRecharge.vue"),
meta: {permissionId: 17} // 对应"提交金币充值"id=17
meta: { permissionId: 50 } // 对应"提交金币充值"id=17
},
// 金币充值明细详情
{
path: 'detail',
name: "coinRechargeDetail",
component: () => import("../views/recharge/gold/coinRechargeDetail.vue"),
meta: {permissionId: 18} // 对应"查看金币充值明细"id=18
meta: { permissionId: 51 } // 对应"查看金币充值明细"id=18
}
]
},
@ -160,27 +174,28 @@ const routes = [
name: "beanRecharge",
component: () => import("../views/recharge/bean/beanRecharge.vue"),
// redirect: '/coinRecharge/add',
meta: { permissionId: 18 },
children: [
// 金豆新增充值
{
path: 'add',
name: "addBeanRecharge",
component: () => import("../views/recharge/bean/addBeanRecharge.vue"),
meta: {permissionId: 46} // 对应"提交金豆充值"id=46
meta: { permissionId: 52 } // 对应"提交金豆充值"id=46
},
// 金豆系统充值
{
path: 'system',
name: "beanSystemRecharge",
component: () => import("../views/recharge/bean/beanSystemRecharge.vue"),
meta: {permissionId: 47} // 对应"查看金豆系统充值明细"id=47
meta: { permissionId: 53 } // 对应"查看金豆系统充值明细"id=47
},
// 金豆线上充值
{
path: 'online',
name: "beanOnlineRecharge",
component: () => import("../views/recharge/bean/beanOnlineRecharge.vue"),
meta: {permissionId: 48} // 对应"查看金豆线上充值明细"id=48
meta: { permissionId: 54 } // 对应"查看金豆线上充值明细"id=48
}
]
},
@ -190,28 +205,21 @@ const routes = [
path: '/coinRefund',
name: "coinRefund",
component: () => import("../views/refund/gold/coinRefund.vue"),
// redirect: '/coinRefund/add',
meta: {permissionId: 7},
meta: { permissionId: 9 },
children: [
// 金币新增退款
{
path: 'add',
name: "addCoinRefund",
component: () => import("../views/refund/gold/addCoinRefund.vue"),
meta: {permissionId: 21} // 对应"提交金币退款"id=21
meta: { permissionId: 61 }
},
// 金币退款明细详情
{
path: 'detail',
name: "coinRefundDetail",
component: () => import("../views/refund/gold/coinRefundDetail.vue"),
meta: {permissionId: 22} // 对应"查看金币退款明细"id=22
},
{
path: 'addRefund',
name: "addNewCoinRefund",
component: () => import("../views/refund/gold/addNewCoinRefund.vue"),
meta: {permissionId: 58}
meta: { permissionId: 62 }
}
]
},
@ -221,36 +229,49 @@ const routes = [
path: '/usergold',
name: "usergold",
component: () => import("../views/usergold/gold/clientCount.vue"),
// redirect: '/usergold/detail',
meta: {permissionId: 8},
meta: { permissionId: 10 },
children: [
// 金币明细
{
path: 'detail',
name: "clientCountDetail",
component: () => import("../views/usergold/gold/clientCountDetail.vue"),
meta: {permissionId: 23} // 对应"查看金币明细"id=23
meta: { permissionId: 63 }
},
// 金币余额
{
path: 'balance',
name: "clientCountBalance",
component: () => import("../views/usergold/gold/clientCountBalance.vue"),
meta: {permissionId: 24} // 对应"查看金币余额"id=24
meta: { permissionId: 64 }
},
]
},
{
path: 'userbean',
path: '/userbean',
name: "userbean",
component: () => import("../views/usergold/bean/userbean.vue"),
meta:{ permissionId: 45 }
meta: { permissionId: 45 }
},
{
path: '/history',
name: "history",
component: () => import("../views/history/history.vue"),
meta:{ permissionId: [56,57] }
meta: { permissionId: 12 },
children: [
{
path: 'newHistory',
name: "newHistory",
component: () => import("../views/history/newHistory.vue"),
meta: { permissionId: 26 }
},
{
path: 'oldHistory',
name: "oldHistory",
component: () => import("../views/history/oldHistory.vue"),
meta: { permissionId: 27 }
}
]
},
// 权限管理
@ -258,25 +279,54 @@ const routes = [
path: '/permissions',
name: "permissions",
component: () => import("../views/permissions/permissions.vue"),
meta: {permissionId: 9},
meta: { permissionId: 11 },
children: [
// 用户权限
{
path: 'userPermission',
name: "userPermission",
component: () => import("../views/permissions/userPermission.vue"),
meta: {permissionId: 25}
meta: { permissionId: 24 }
},
// 角色权限
{
path: 'rolePermission',
name: "rolePermission",
component: () => import("../views/permissions/rolePermission.vue"),
meta: {permissionId: 30}
meta: { permissionId: 25 }
}
]
}
]
},
{
path:'/moneyManage',
name:'moneyManage',
meta:{permissionId:4},
children:[
// 收款明细
{
path: 'receiveDetail',
name: "receiveDetail",
component: () => import("../views/moneyManage/receiveDetail/receiveDetail.vue"),
meta: { permissionId: 74 }
},
// 退款明细
{
path: 'refundDetail',
name: "refundDetail",
component: () => import("../views/moneyManage/refundDetail/refundDetail.vue"),
meta: { permissionId: 75 }
},
// 执行明细
{
path: 'executor',
name: "executor",
component: () => import("../views/moneyManage/executor/executor.vue"),
meta: { permissionId: 76 }
},
]
},
// 没有权限
{
path: '/noPermission',
@ -320,7 +370,7 @@ const getAllPermissionIds = (menuTree) => {
router.beforeEach(async (to, from, next) => {
const adminStore = useAdminStore()
const {adminData, menuTree} = storeToRefs(adminStore)
const { adminData, menuTree } = storeToRefs(adminStore)
const token = localStorage.getItem("token");
const machineId = localStorage.getItem("machineId");
@ -356,6 +406,7 @@ router.beforeEach(async (to, from, next) => {
// 拿权限id
userPermissionIds = getAllPermissionIds(menuTree.value)
console.log("userPermissionIds",userPermissionIds)
// 2.4 权限验证(逻辑不变)
console.log('to.meta:', to.meta)

174
src/utils/menuTreePermission.js

@ -1,86 +1,98 @@
// 菜单权限映射(按 menu_type 排序)
export const permissionMapping = {
System_Management: 1, // 系统管理
// menu_type 2: 主功能菜单
Workbench: 2, // 工作台
Financial_Audit: 3, // 财务审核
Exchange_Rate_Management: 4, // 汇率管理
Recharge_Management: 5, // 充值管理
Consumption_Management: 6, // 消耗管理
Refund_Management: 7, // 退款管理
Customer_Account_Details: 8, // 客户账户明细
History: 56, // 历史数据查询
Permission_Management: 9, // 权限管理
// menu_type 3: 子功能菜单
Gold_Coin_Recharge: 34, // 金币充值
Gold_Coin_Consumption: 35, // 金币消耗
Gold_Coin_Refund: 37, // 金币退款
Gold_Coin_Audit: 40, // 金币审核
Golden_Bean_Recharge: 41, // 金豆充值
Golden_Bean_Consumption: 42, // 金豆消耗
Golden_Bean_Audit: 43, // 金豆审核
Gold_Coin_Customer_Account_Details: 44, // 金币客户账户明细
Golden_Bean_Customer_Account_Details: 45, // 金豆客户账户明细
// menu_type 4: 功能操作权限
Workbench_Display: 10, // 工作台展示
View_Recharge_Audit: 11, // 查看充值审核 // 有这个页面权限的就有
Recharge_Approval: 12, // 充值审批 //细致划分
View_Refund_Audit: 13, // 查看退款审核 // 有这个页面权限的就有
Refund_Approval: 14, // 退款审批 //细致划分
Exchange_Rate_View: 15, // 汇率查看
Exchange_Rate_Modification: 16, // 汇率修改
Submit_Gold_Coin_Recharge: 17, // 提交金币充值 // coinRecharge页面
View_Gold_Coin_Recharge_Details: 18, // 查看金币充值明细 // coinRecharge页面
Submit_Gold_Coin_Consumption: 19, // 提交金币消耗 // coinConsume页面
View_Gold_Coin_Consumption_Details: 20, // 查看金币消耗明细 // coinConsume页面
Submit_Gold_Coin_Refund: 21, // 提交金币退款 // coinRefund页面
View_Gold_Coin_Refund_Details: 22, // 查看金币退款明细 // coinRefund页面
Submit_Gold_Coin_RefundS: 58, // 提交金币退款 // coinRefund页面
View_Gold_Coin_Details: 23, // 查看金币明细 //usergold页面
View_Gold_Coin_Balance: 24, // 查看金币余额 //usergold页面
View_Permission: 25, // 查看权限
Add_User: 26, // 新增用户
Change_Status: 27, // 改变状态
Modify_Permission: 28, // 修改权限
Delete_User: 29, // 删除用户
View_Role: 30, // 查看角色
Edit_Role: 36, // 编辑角色
Recharge_Audit: 31, // 充值审核(金币) // audit页面
Refund_Audit: 32, // 退款审核(金币) // audit页面,
History_Query: 57, // 历史数据查询
// 新增的金豆相关权限
Submit_Golden_Bean_Recharge: 46, // 提交金豆充值
View_Golden_Bean_System_Recharge_Details: 47, // 查看金豆系统充值明细
View_Golden_Bean_Online_Recharge_Details: 48, // 查看金豆线上充值明细
Submit_Golden_Bean_Consumption: 49, // 提交金豆消耗
View_Golden_Bean_Live_Consumption_Details: 50, // 查看金豆直播消耗明细
View_Golden_Bean_Fan_Consumption_Details: 51, // 查看金豆铁粉消耗明细
View_Golden_Bean_Article_Video_Consumption_Details: 52, // 查看金豆文章/视频消耗明细
View_Golden_Bean_Balance: 53, // 查看金豆余额
View_Golden_Bean_Recharge_Audit: 54, // 查看金豆充值审核
Golden_Bean_Recharge_Approval: 55 // 金豆充值审批
};
// 大系统和分系统,0级1级
system:1, // 总系统
workbench: 2, // 工作台
goldManage:3, // 金币管理
moneyManage:4, // 现金管理
// 模块,2级
financialAudit:5, // 财务审核
rateManage:6, // 汇率管理
rechargeManage:7, // 充值管理
consumeManage:8, // 消耗管理
refundManage:9, // 退款管理
accountDetail:10, // 客户账户明细
permissionManage:11, // 权限管理
historyData:12, // 历史数据查询
// 页面,3级
workbenchShow:13, // 工作台查看
coinAudit:14, // 金币审核
beanAudit:15, // 金豆审核
moneyRate:16, // 现金汇率
coinRecharge:17, // 金币充值
beanRecharge:18, // 金豆充值
coinConsume:19, // 金币消耗
beanConsume:20, // 金豆消耗
coinRefund:21, // 金币退款
coinDetail:22, // 金币客户账户明细
beanDetail:23, // 金豆客户账户明细
userManage:24, // 用户管理
roleManage:25, // 角色管理
newCoinSelect:26, // 金币查询(新)
oldCoinSelect:27, // 金币查询(旧)
// 功能,4级
rechargeAudit:28, // 充值审核
refundAudit:29, // 退款审核
rechargeThrough:30, // 充值审核已通过
rechargeReject:31, // 充值审核已驳回
rechargeWait:32, // 充值审核待审核
rechargeWaitShow:33, // 充值审核待审核查看
rechargeWaitThough:34, // 充值审核等待通过
rechargeWaitReject:35, // 充值审核等待驳回
refundThrough:36, // 退款审核已通过
refundReject:37, // 退款审核已驳回
refundWait:38, // 退款审核待审核
refundWaitShow:39, // 退款审核待审核查看
refundWaitThough:40, // 退款审核等待通过
refundWaitReject:41, // 退款审核等待驳回
beanWait:42, // 金豆审核待审核
beanThrough:43, // 金豆审核已通过
beanReject:44, // 金豆审核已驳回
beanWaitShow:45, // 金豆审核待审核查看
beanWaitThough:46, // 金豆审核通过
beanWaitReject:47, // 金豆审核驳回
rateShow:48, // 现金汇率查看
rateEdit:49, // 现金汇率编辑
addCoinRecharge:50, // 新增金币充值
coinRechargeDetail:51, // 金币充值明细
addBeanRecharge:52, // 新增金豆充值
beanSystemRecharge:53, // 金豆系统充值
beanOnlineRecharge:54, // 金豆线上充值
addCoinConsume:55, // 新增金币消耗
coinConsumeDetail:56, // 金币消耗明细
addBeanConsume:57, // 新增金豆消耗
beanZBDetail:58, // 金豆直播消耗
beanTFDetail:59, // 金豆铁粉消耗
beanSPWZDetail:60, // 金豆视频文章消耗
addCoinRefund:61, // 新增金币退款
coinRefundDetail:62, // 金币退款明细
coinCustomerDetail:63, // 金币客户账户明细
coinCustomerMoney:64, // 金币客户账户金额
userManageShow:65, // 用户管理展示
addUserInfo:66, // 新增用户信息
editUserInfo:67, // 编辑用户信息
updateUserInfo:68, // 重置密码
deleteUserInfo:69, // 删除用户信息
changeStatus:70, // 启用禁用用户
roleManageShow:73, // 查看角色信息
addRoleInfo:71, // 新增角色信息
editRoleInfo:72, // 编辑角色信息
receiveDetail:74, // 收款明细
refundDetail:75, // 退款明细
executor:76, // 执行人
}
// 递归查找菜单中是否存在目标id
export const findMenuById = (menuList, targetId) => {

17
src/utils/menuUtils.js

@ -43,12 +43,14 @@ export const getRoutePath = (menu) => {
// 路由映射表:key为接口menuName,value为对应路由路径
const routeMap = {
'工作台展示': '/workspace',
'工作台查看': '/workbench',
'金币管理': '/goldManage',
'现金管理': '/moneyManage',
'金币审核': '/audit',
'金豆审核': '/beanAudit',
'汇率管理': '/rate',
'现金汇率': '/rate',
'金币充值': '/coinRecharge',
@ -62,13 +64,24 @@ export const getRoutePath = (menu) => {
// '金豆退款': '/beanRefund',
'历史数据查询': '/history',
'金币查询(新)': '/history/newHistory',
'金币查询(旧)': '/history/oldHistory',
'权限管理': '/permissions',
'用户管理': '/permissions/userPermission',
'角色管理': '/permissions/rolePermission',
'金币客户账户明细': '/usergold',
'金豆客户账户明细': '/userbean',
'用户管理': '/permissions/userPermission',
'角色管理': '/permissions/rolePermission',
'收款明细': '/moneyManage/receiveDetail',
'退款明细': '/moneyManage/refundDetail',
'执行人': '/moneyManage/executor/executor',
};
// 未匹配的菜单默认使用id作为路由(可根据实际需求调整)

97
src/views/audit/bean/beanAudit.vue

@ -22,11 +22,11 @@
</el-col>
</el-card>
<el-card>
<el-card >
<el-tabs v-model="checkTab" type="card" @tab-click="handleClick">
<el-tab-pane label="待审核" name="pending"></el-tab-pane>
<el-tab-pane label="已通过" name="pass"></el-tab-pane>
<el-tab-pane label="已驳回" name="reject"></el-tab-pane>
<el-tab-pane label="待审核" name="pending" v-if="hasbeanWait&&hasbeanWaitShow"></el-tab-pane>
<el-tab-pane label="已通过" name="pass" v-if="hasbeanThrough"></el-tab-pane>
<el-tab-pane label="已驳回" name="reject" v-if="hasbeanReject"></el-tab-pane>
</el-tabs>
<div>
@ -61,12 +61,12 @@
{{ row.auditTime ? moment(row.auditTime).format('YYYY-MM-DD HH:mm:ss') : '--' }}
</template>
</el-table-column>
<el-table-column v-if="checkTab === 'pending'" fixed="right" prop="operation" label="操作" width="400px">
<el-table-column v-if="checkTab === 'pending' &&(hasbeanWaitThough||hasbeanWaitReject)" fixed="right" prop="operation" label="操作" width="400px">
<template #default="scope">
<div class="operation">
<el-popconfirm title="确定要通过此条记录吗?" @confirm="handleApprove(scope.row)">
<template #reference>
<el-button :disabled="clicked || cancelClicked" type="primary" text>
<el-button v-if="hasbeanWaitThough" :disabled="clicked || cancelClicked" type="primary" text>
通过
</el-button>
</template>
@ -77,7 +77,7 @@
</el-button>
</template>
</el-popconfirm>
<el-button :disabled="clicked || cancelClicked" type="primary" text @click="showRejectDialog(scope.row)">
<el-button v-if="hasbeanWaitReject" :disabled="clicked || cancelClicked" type="primary" text @click="showRejectDialog(scope.row)">
驳回
</el-button>
</div>
@ -111,13 +111,13 @@ import moment from 'moment'
import { useAdminStore } from "@/store/index.js";
import { storeToRefs } from "pinia";
import _ from 'lodash'
import { permissionMapping, hasMenuPermission } from "@/utils/menuTreePermission.js"
const defaultTime = [
new Date(2000, 1, 1, 0, 0, 0),
new Date(2000, 2, 1, 23, 59, 59),
]
const adminStore = useAdminStore();
const { adminData, menuTree } = storeToRefs(adminStore);
import { permissionMapping, findMenuById } from "@/utils/menuTreePermission.js"
import dayjs from "dayjs";
const tableData = ref([])
@ -132,6 +132,25 @@ const reason = ref('')
const rejectRow = ref({
id: null
})//
//
const hasbeanWait = ref(false) // beanWait:42
const hasbeanThrough = ref(false) // beanThrough:43
const hasbeanReject = ref(false) // beanReject:44
const hasbeanWaitShow = ref(false) // beanWaitShow:45
const hasbeanWaitThough = ref(false) // beanWaitThough:46
const hasbeanWaitReject = ref(false) // beanWaitReject:47
//
const initPermissions = async() => {
if (!menuTree.value || !menuTree.value.length) return;
hasbeanWait.value = hasMenuPermission(menuTree.value, permissionMapping.beanWait);
hasbeanThrough.value = hasMenuPermission(menuTree.value, permissionMapping.beanThrough);
hasbeanReject.value = hasMenuPermission(menuTree.value, permissionMapping.beanReject);
hasbeanWaitShow.value = hasMenuPermission(menuTree.value, permissionMapping.beanWaitShow);
hasbeanWaitThough.value = hasMenuPermission(menuTree.value, permissionMapping.beanWaitThough);
hasbeanWaitReject.value = hasMenuPermission(menuTree.value, permissionMapping.beanWaitReject);
};
const clicked = ref(false)
//
const STATUS = {
@ -192,7 +211,10 @@ const handleSearch = async function () {
await getStats()
}
const get = async function () {
if (findMenuById(menuTree.value, permissionMapping.View_Golden_Bean_Recharge_Audit)) {
if (!hasbeanWaitShow) {
ElMessage.error('暂无权限')
return
}
try {
if (dateRange.value && dateRange.value.length === 2) {
if (checkTab.value === 'pending') {
@ -206,10 +228,10 @@ const get = async function () {
searchForm.value.createStartTime = ''
searchForm.value.createEndTime = ''
}
if(checkTab.value === 'pending'){
if (checkTab.value === 'pending') {
sortField.value = 'createTime'
sortOrder.value = 'desc'
}else{
} else {
sortField.value = 'auditTime'
sortOrder.value = 'desc'
}
@ -234,12 +256,12 @@ const get = async function () {
} catch (error) {
console.error('获取数据失败', error)
}
} else {
ElMessage.error('无此权限')
}
}
const getStats = async () => {
if (findMenuById(menuTree.value, permissionMapping.View_Golden_Bean_Recharge_Audit)) {
if (!hasbeanWaitShow) {
ElMessage.error('暂无权限')
return
}
try {
const params = {
jwcode: searchForm.value.jwcode,
@ -263,14 +285,14 @@ const getStats = async () => {
} catch (error) {
console.log('请求失败', error)
}
} else {
ElMessage.error('无此权限')
}
}
//
const handleApprove = async (row) => {
if (!hasbeanWaitThough) {
ElMessage.error('暂无权限')
return
}
clicked.value = true
if (findMenuById(menuTree.value, permissionMapping.Golden_Bean_Recharge_Approval)) {
try {
const params = {
id: row.id,
@ -285,15 +307,14 @@ const handleApprove = async (row) => {
console.error('审核通过失败', error)
ElMessage.error('操作失败')
}
} else {
ElMessage.error('无此权限')
clicked.value = false
}
}
//
const handleReject = async () => {
if (!hasbeanWaitReject) {
ElMessage.error('暂无权限')
return
}
cancelClicked.value = true
if (findMenuById(menuTree.value, permissionMapping.Golden_Bean_Recharge_Approval)) {
if (!reason.value.trim()) {
ElMessage.warning('请输入驳回理由')
return
@ -314,10 +335,6 @@ const handleReject = async () => {
console.error('驳回失败', error)
ElMessage.error('操作失败')
}
} else {
ElMessage.error('无此权限')
cancelClicked.value = false
}
}
const getToday = async function () {
const today = dayjs()
@ -370,10 +387,22 @@ const resetSearch = async function () {
const handleClick = async function (tab) {
checkTab.value = tab.props.name
if (tab.props.name === 'pending') {
if (!hasbeanWait) {
ElMessage.error('暂无权限')
return
}
adminWait()
} else if (tab.props.name === 'pass') {
if (!hasbeanThrough) {
ElMessage.error('暂无权限')
return
}
adminPass()
} else if (tab.props.name === 'reject') {
if (!hasbeanReject) {
ElMessage.error('暂无权限')
return
}
adminReject()
}
}
@ -429,6 +458,10 @@ const throttledHandleApprove = _.throttle(handleApprove, 5000, {
trailing: false
})
const showRejectDialog = (row) => {
if (!hasbeanWaitReject) {
ElMessage.error('暂无权限')
return
}
rejectRow.value.id = row.id
reason.value = ''
rejectVisible.value = true
@ -467,6 +500,14 @@ const trimJwCode = () => {
}
}
onMounted(async () => {
await initPermissions()
if(hasbeanWaitShow.value){
searchForm.value.status = 0
}else if(hasbeanThrough.value){
searchForm.value.status = 1
}else if(hasbeanReject){
searchForm.value.status = 2
}
getmarkets()
await get()
await getStats()

20
src/views/audit/gold/audit.vue

@ -3,11 +3,11 @@
<div style="height:4vh;width:82vw;">
<el-button-group>
<el-button :type="activeTab === 'rechargeAudit' ? 'primary' : 'default'" @click="navigateTo('rechargeAudit')"
:disabled="!hasRecharge">
v-if="hasRecharge">
充值审核
</el-button>
<el-button :type="activeTab === 'refundAudit' ? 'primary' : 'default'" @click="navigateTo('refundAudit')"
:disabled="!hasRefund">
v-if="hasRefund">
退款审核
</el-button>
</el-button-group>
@ -22,6 +22,7 @@ import { useRouter, useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useAdminStore } from '@/store/index.js';
import { hasMenuPermission, permissionMapping } from "@/utils/menuTreePermission.js";
import { ElMessage } from 'element-plus';
const router = useRouter();
const route = useRoute();
@ -33,6 +34,17 @@ const hasRecharge = ref(false);
const hasRefund = ref(false);
//
const navigateTo = (name) => {
if(name == 'rechargeAudit'){
if(!hasRecharge){
ElMessage.error('您暂无充值审核操作权限')
return;
}
}else if(name = 'refundAudit'){
if(!hasRefund){
ElMessage.error('您暂无退款审核操作权限')
return;
}
}
activeTab.value = name;
router.push({ name });
};
@ -42,8 +54,8 @@ const navigateTo = (name) => {
const initPermissions = () => {
if (!menuTree.value || !menuTree.value.length) return;
hasRecharge.value = hasMenuPermission(menuTree.value, permissionMapping.Recharge_Audit);
hasRefund.value = hasMenuPermission(menuTree.value, permissionMapping.Refund_Audit);
hasRecharge.value = hasMenuPermission(menuTree.value, permissionMapping.rechargeAudit);
hasRefund.value = hasMenuPermission(menuTree.value, permissionMapping.refundAudit);
};
//

183
src/views/audit/gold/rechargeAudit.vue

@ -1,48 +1,63 @@
<template>
<el-card style="margin-bottom: 0.5vh;">
<el-col style="margin-bottom: 0.5vh">
<el-text size="large">精网号</el-text>
<el-input v-model="rechargeAudit.jwcode" placeholder="请输入精网号" style="width: 12vw;margin-right:1vw" clearable />
<el-text size="large">活动名称</el-text>
<el-select v-model="rechargeAudit.activity" placeholder="请选择活动名称" style="width: 12vw;margin-right:1vw" clearable>
<el-col style="margin-bottom: 1vh">
<div class="select">
<div class="selectRow">
<el-text class="text" size="large">精网号</el-text>
<el-input class="selectContent" v-model="rechargeAudit.jwcode" placeholder="请输入精网号" clearable />
</div>
<div class="selectRow">
<el-text class="text" size="large">活动名称</el-text>
<el-select class="selectContent" v-model="rechargeAudit.activity" placeholder="请选择活动名称" clearable>
<el-option v-for="item in activity" :key="item" :label="item" :value="item" />
</el-select>
<el-text size="large">支付方式</el-text>
<el-select v-model="rechargeAudit.payModel" placeholder="请选择支付方式" style="width: 12vw;margin-right:1vw" clearable>
</div>
<div class="selectRow">
<el-text class="text" size="large">支付方式</el-text>
<el-select class="selectContent" v-model="rechargeAudit.payModel" placeholder="请选择支付方式" clearable>
<el-option v-for="item in payModel" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-text size="large">所属地区</el-text>
<el-cascader v-model="selectedMarketPath" :options="market" placeholder="请选择所属地区" clearable style="width:12vw"
@change="handleMarketChange" />
</div>
<div class="selectRow">
<el-text class="text" size="large">所属地区</el-text>
<el-cascader class="selectContent" v-model="selectedMarketPath" :options="market" placeholder="请选择所属地区"
clearable @change="handleMarketChange" />
</div>
</div>
</el-col>
<el-col>
<el-text size="large">
<div class="select">
<div class="selectRow" style="width: 36vw;">
<el-text class="text" size="large">
{{ activeName === 'wait' ? '提交时间:' : '审核时间:' }}
</el-text>
<el-date-picker v-model="getTime" type="datetimerange" range-separator="" start-placeholder="起始时间"
end-placeholder="结束时间" style="margin-right:1vw;width:25vw"
<el-date-picker class="selectContent" v-model="getTime" type="datetimerange" range-separator=""
start-placeholder="起始时间" end-placeholder="结束时间" style="margin-right:1vw;width:25vw"
@change="handleDatePickerChange" :default-time="defaultTime" :disabled-date="disabledDate" />
<div v-if="false">
<el-button @click="getToday()" :type="activeTimeRange === 'today' ? 'primary' : ''"></el-button>
<el-button @click="getYesterday()" :type="activeTimeRange === 'yesterday' ? 'primary' : ''"></el-button>
<el-button @click="get7Days()" :type="activeTimeRange === '7days' ? 'primary' : ''">近7天</el-button>
</div>
</div>
<div class="selectRow" style="justify-content: flex-start;">
<el-button @click="resetSearch" type="success">重置</el-button>
<el-button @click="handleSearch" type="primary">查询</el-button>
</div>
</div>
</el-col>
</el-card>
<el-card>
<el-tabs v-model="activeName" type="card" @tab-click="handleClick">
<el-tab-pane label="待审核" name="wait"></el-tab-pane>
<el-tab-pane label="已通过" name="pass"></el-tab-pane>
<el-tab-pane label="已驳回" name="reject"></el-tab-pane>
<el-tab-pane label="待审核" name="wait" v-if="hasrechargeWait&&hasrechargeWaitShow"></el-tab-pane>
<el-tab-pane label="已通过" name="pass" v-if="hasrechargeThrough"></el-tab-pane>
<el-tab-pane label="已驳回" name="reject" v-if="hasrechargeReject"></el-tab-pane>
<div>
<!-- 总条数{{ format3(stats.totalNum) }}&nbsp;&nbsp;&nbsp;&nbsp;-->
<!-- 总条数{{ format3(stats.totalNum) }}&nbsp;&nbsp;&nbsp;&nbsp;-->
充值新币{{ format3(stats.permanentGolds) }}新币&nbsp;&nbsp;&nbsp;&nbsp;
总金币数{{ format3((stats.permanentGolds + stats.freeGolds + stats.taskGolds).toFixed(2)) }}金币&nbsp;&nbsp;&nbsp;&nbsp;
总金币数{{ format3((stats.permanentGolds + stats.freeGolds + stats.taskGolds).toFixed(2))
}}金币&nbsp;&nbsp;&nbsp;&nbsp;
永久金币{{ format3(stats.permanentGolds.toFixed(2)) }}金币&nbsp;&nbsp;&nbsp;&nbsp;
免费金币{{ format3(stats.freeGolds.toFixed(2)) }}金币&nbsp;&nbsp;&nbsp;&nbsp;
</div>
@ -55,10 +70,10 @@
</template>
</el-table-column>
<el-table-column fixed="left" prop="name" label="姓名" width="150px" show-overflow-tooltip />
<el-table-column fixed="left" prop="jwcode" label="精网号" width="110px"/>
<el-table-column prop="market" label="所属地区" width="100px"/>
<el-table-column prop="activity" label="活动名称" width="100px" show-overflow-tooltip/>
<el-table-column prop="rateName" label="货币名称" width="110px"/>
<el-table-column fixed="left" prop="jwcode" label="精网号" width="110px" />
<el-table-column prop="market" label="所属地区" width="100px" />
<el-table-column prop="activity" label="活动名称" width="100px" show-overflow-tooltip />
<el-table-column prop="rateName" label="货币名称" width="110px" />
<el-table-column prop="money" label="充值金额" sortable="custom" width="110px">
<template #default="scope">{{ scope.row.money / 100 }}</template>
</el-table-column>
@ -104,12 +119,12 @@
{{ moment(scope.row.auditTime).format('YYYY-MM-DD HH:mm:ss') }}
</template>
</el-table-column>
<el-table-column v-if="activeName === 'wait'" fixed="right" prop="operation" label="操作" width="150px">
<el-table-column v-if="activeName === 'wait'&&(hasrechargeWaitThough||hasrechargeWaitReject)&&hasrechargeWaitShow" fixed="right" prop="operation" label="操作" width="150px">
<template #default="scope">
<div class="operation">
<el-popconfirm title="确定要通过此条记录吗?" @confirm="handleApprove(scope.row)">
<template #reference>
<el-button :disabled="clicked || cancelClicked" type="primary" text>
<el-button v-if="hasrechargeWaitThough" :disabled="clicked || cancelClicked" type="primary" text>
通过
</el-button>
</template>
@ -120,7 +135,7 @@
</el-button>
</template>
</el-popconfirm>
<el-button :disabled="clicked || cancelClicked" type="primary" text @click="showRejectDialog(scope.row)">
<el-button v-if="hasrechargeWaitReject" :disabled="clicked || cancelClicked" type="primary" text @click="showRejectDialog(scope.row)">
驳回
</el-button>
</div>
@ -161,9 +176,31 @@ import { storeToRefs } from "pinia";
const adminStore = useAdminStore();
const { adminData, menuTree } = storeToRefs(adminStore);
import { findMenuById, permissionMapping } from "@/utils/menuTreePermission.js"
import { permissionMapping, hasMenuPermission } from "@/utils/menuTreePermission.js"
import dayjs from "dayjs";
//
const hasrechargeThrough = ref(false) //
const hasrechargeReject = ref(false) //
const hasrechargeWait = ref(false) //
const hasrechargeWaitShow = ref(false) //
const hasrechargeWaitThough = ref(false) //
const hasrechargeWaitReject = ref(false) //
//
const initPermissions = async() => {
if (!menuTree.value || !menuTree.value.length) return;
hasrechargeThrough.value = hasMenuPermission(menuTree.value, permissionMapping.rechargeThrough);
hasrechargeReject.value = hasMenuPermission(menuTree.value, permissionMapping.rechargeReject);
hasrechargeWait.value = hasMenuPermission(menuTree.value, permissionMapping.rechargeWait);
hasrechargeWaitShow.value = hasMenuPermission(menuTree.value, permissionMapping.rechargeWaitShow);
hasrechargeWaitThough.value = hasMenuPermission(menuTree.value, permissionMapping.rechargeWaitThough);
hasrechargeWaitReject.value = hasMenuPermission(menuTree.value, permissionMapping.rechargeWaitReject);
console.log(hasrechargeWaitThough.value)
console.log(hasrechargeWaitReject.value)
};
const defaultTime = [
new Date(2000, 1, 1, 0, 0, 0),
new Date(2000, 2, 1, 23, 59, 59),
@ -199,16 +236,19 @@ const rechargeAudit = ref({
startTime: "", //
endTime: "", //
market: "", //
auditStatus: "0",
auditStatus: '0',
})
//
const getObj = ref({
pageNum: 1,
pageSize: 50
})
//
const payModel = [
{
{
value: '银行转账',
label: '银行转账'
},
@ -267,6 +307,11 @@ const rules = reactive({
})
//
const getRecharge = async function (val) {
if (!hasrechargeWaitShow) {
ElMessage.error('暂无权限')
return
}
try {
if (getTime.value && getTime.value.length === 2) {
rechargeAudit.value.startTime = formatTime(getTime.value[0])
@ -305,9 +350,13 @@ const getRecharge = async function (val) {
} catch (error) {
console.log('请求失败', error)
}
}
const getStats = async () => {
if (!hasrechargeWaitShow) {
return
}
try {
const params = {
pageNum: getObj.value.pageNum,
@ -431,10 +480,24 @@ const adminReject = async function () {
const handleClick = function (tab, event) {
activeName.value = tab.props.name
if (tab.props.name === 'wait') {
if(!hasrechargeWait){
ElMessage.error('暂无权限')
return
}
if(hasrechargeWaitShow){
adminWait()
}
} else if (tab.props.name === 'pass') {
if(!hasrechargeThrough){
ElMessage.error('暂无权限')
return
}
adminPass()
} else if (tab.props.name === 'reject') {
if(!hasrechargeReject){
ElMessage.error('暂无权限')
return
}
adminReject()
}
}
@ -462,8 +525,11 @@ const handlePagination = (type, val) => {
const clicked = ref(false);
//
const handleApprove = async (row) => {
if(!hasrechargeWaitThough){
ElMessage.error('暂无权限')
return
}
clicked.value = true
if (findMenuById(menuTree.value, permissionMapping.Recharge_Approval)) {
try {
const params = {
orderCode: row.orderCode,
@ -480,27 +546,27 @@ const handleApprove = async (row) => {
console.error('审核通过失败', error)
ElMessage.error('操作失败')
}
} else {
clicked.value = false
ElMessage.error('无权限')
}
}
const showRejectDialog = (row) => {
if(!hasrechargeWaitReject){
ElMessage.error('暂无权限')
return
}
currentRecord.value = row
rejectReason.value = ''
if (findMenuById(menuTree.value, permissionMapping.Recharge_Approval)) {
rejectDialogVisible.value = true
} else {
ElMessage.error('无权限')
}
}
//
const cancelClicked = ref(false)
//
const handleReject = async () => {
if(!hasrechargeWaitReject){
ElMessage.error('暂无权限')
return
}
cancelClicked.value = true
if (findMenuById(menuTree.value, permissionMapping.Recharge_Approval)) {
if (!rejectReason.value.trim()) {
ElMessage.warning('请输入驳回理由')
@ -524,10 +590,6 @@ const handleReject = async () => {
console.error('驳回操作失败', error)
ElMessage.error('操作失败')
}
} else {
ElMessage.error('无权限')
cancelClicked.value = false
}
}
//
const handleSortChange = (column) => {
@ -637,6 +699,14 @@ const cancelClick = () => {
const formatTime = (val) => val ? moment(val).format('YYYY-MM-DD HH:mm:ss') : ''
//
onMounted(async function () {
await initPermissions()
if(hasrechargeWaitShow.value){
rechargeAudit.value.auditStatus = '0'
}else if(hasrechargeThrough.value){
rechargeAudit.value.auditStatus = '1'
}else if(hasrechargeReject){
rechargeAudit.value.auditStatus = '2'
}
await getActivity()
await getMarket()
await getRecharge()
@ -654,4 +724,25 @@ onMounted(async function () {
.operation {
display: flex;
}
.select {
display: flex;
.selectRow {
width: 17vw;
display: flex;
align-items: center;
justify-content: center;
padding: 0 0.5vw;
.text {
width: 5vw;
font-size: 15px;
}
.selectContent {
flex: 1;
}
}
}
</style>

236
src/views/audit/gold/refundAudit.vue

@ -1,69 +1,83 @@
<template>
<el-card style="margin-bottom: 0.5vh;">
<el-col style="margin-bottom: 0.5vh">
<el-text size="large">精网号</el-text>
<el-input v-model="searchForm.jwcode" placeholder="请输入精网号" style="width: 12vw;margin-right:1vw" clearable/>
<el-text size="large">商品名称</el-text>
<el-select v-model="searchForm.goodsName" placeholder="请输入商品名称" style="width: 12vw;margin-right:1vw"
clearable>
<el-col style="margin-bottom: 1vh">
<div class="select">
<div class="selectRow">
<el-text class="text" size="large">精网号</el-text>
<el-input class="selectContent" v-model="searchForm.jwcode" placeholder="请输入精网号" clearable />
</div>
<div class="selectRow">
<el-text class="text" size="large">商品名称</el-text>
<el-select class="selectContent" v-model="searchForm.goodsName" placeholder="请输入商品名称" clearable>
<el-option v-for="item in refundGoodsOptions" :key="item" :label="item" :value="item"></el-option>
</el-select>
<el-text size="large">退款类型</el-text>
<el-select v-model="searchForm.refundType" placeholder="请选择" style="width: 12vw;margin-right:1vw" clearable>
<el-option label="商品退款" value="商品退款"/>
<el-option label="金币退款" value="金币退款"/>
</div>
<div class="selectRow">
<el-text class="text" size="large">退款类型</el-text>
<el-select class="selectContent" v-model="searchForm.refundType" placeholder="请选择退款类型" clearable>
<el-option label="商品退款" value="商品退款" />
<el-option label="金币退款" value="金币退款" />
</el-select>
<el-text size="large">所属地区</el-text>
<el-cascader v-model="selectedMarketPath" :options="market" placeholder="请选择所属地区" clearable
style="width:12vw"
@change="handleMarketChange"/>
</div>
<div class="selectRow">
<el-text class="text" size="large">所属地区</el-text>
<el-cascader class="selectContent" style="width: 12vw;" v-model="selectedMarketPath" :options="market"
placeholder="请选择所属地区" clearable @change="handleMarketChange" />
</div>
</div>
</el-col>
<el-col>
<el-text size="large">
<div class="select">
<div class="selectRow" style="width: 36vw;">
<el-text class="text" size="large">
{{ activeName === 'wait' ? '提交时间:' : '审核时间:' }}
</el-text>
<el-date-picker v-model="dateRange" type="datetimerange" range-separator="" start-placeholder="开始时间"
end-placeholder="结束时间" style="margin-right:1vw;width:25vw"
@change="handleDatePickerChange" :default-time="defaultTime" :disabled-date="disabledDate"/>
<el-date-picker class="selectContent" v-model="dateRange" type="datetimerange" range-separator=""
start-placeholder="起始时间" end-placeholder="结束时间" style="margin-right:1vw;width:25vw"
@change="handleDatePickerChange" :default-time="defaultTime" :disabled-date="disabledDate" />
<div v-if="false">
<el-button @click="getToday()" :type="activeTimeRange === 'today' ? 'primary' : ''"></el-button>
<el-button @click="getYesterday()" :type="activeTimeRange === 'yesterday' ? 'primary' : ''"></el-button>
<el-button @click="get7Days()" :type="activeTimeRange === '7days' ? 'primary' : ''">近7天</el-button>
<el-button type="success" @click="resetSearch">重置</el-button>
<el-button type="primary" @click="handleSearch">查询</el-button>
</div>
</div>
<div class="selectRow" style="justify-content: flex-start;">
<el-button @click="resetSearch" type="success">重置</el-button>
<el-button @click="handleSearch" type="primary">查询</el-button>
</div>
</div>
</el-col>
</el-card>
<el-card>
<el-card >
<el-tabs v-model="activeName" type="card" @tab-click="handleClick">
<el-tab-pane label="待审核" name="wait"></el-tab-pane>
<el-tab-pane label="已通过" name="pass"></el-tab-pane>
<el-tab-pane label="已驳回" name="reject"></el-tab-pane>
<el-tab-pane label="待审核" name="wait" v-if="hasrefundWait&&hasrefundWaitShow"></el-tab-pane>
<el-tab-pane label="已通过" name="pass" v-if="hasrefundThrough"></el-tab-pane>
<el-tab-pane label="已驳回" name="reject" v-if="hasrefundReject"></el-tab-pane>
</el-tabs>
<div>
退款总金币数{{ format3((stats.permanentGolds + stats.freeGolds + stats.taskGolds).toFixed(2)) }}金币&nbsp;&nbsp;&nbsp;&nbsp;
退款总金币数{{ format3((stats.permanentGolds + stats.freeGolds + stats.taskGolds).toFixed(2))
}}金币&nbsp;&nbsp;&nbsp;&nbsp;
永久金币{{ format3(stats.permanentGolds.toFixed(2)) }}金币&nbsp;&nbsp;&nbsp;&nbsp;
免费金币{{ format3(stats.freeGolds.toFixed(2)) }}金币&nbsp;&nbsp;&nbsp;&nbsp;
任务金币{{ format3(stats.taskGolds.toFixed(2)) }}金币
</div>
<el-table :data="tableData" style="height:54vh;width:82vw" @sort-change="handleSortChange">
<el-table-column type="index" label="序号" width="60"/>
<el-table-column prop="name" label="姓名" width="120" show-overflow-tooltip/>
<el-table-column prop="jwcode" label="精网号" width="120"/>
<el-table-column prop="market" label="所属地区" width="120"/>
<el-table-column prop="orderCode" label="订单号" width="260px" show-overflow-tooltip/>
<el-table-column type="index" label="序号" width="60" />
<el-table-column prop="name" label="姓名" width="120" show-overflow-tooltip />
<el-table-column prop="jwcode" label="精网号" width="120" />
<el-table-column prop="market" label="所属地区" width="120" />
<el-table-column prop="orderCode" label="订单号" width="260px" show-overflow-tooltip />
<el-table-column prop="refundType" label="退款类型" width="120"/>
<el-table-column prop="refundType" label="退款类型" width="120" />
<el-table-column prop="refundModel" label="退款方式" width="120">
<template #default="{ row }">
{{ row.refundModel === 0 ? '全部退款' : '部分退款' }}
</template>
</el-table-column>
<el-table-column prop="goodsName" label="退款商品" width="120" show-overflow-tooltip/>
<el-table-column prop="goodsName" label="退款商品" width="120" show-overflow-tooltip />
<el-table-column prop="sumGold" label="退款金币总数" width="160" sortable="custom">
<template #default="{ row }">
{{ row.sumGold / 100 }}
@ -84,11 +98,11 @@
{{ row.taskGold / 100 }}
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" width="150" show-overflow-tooltip/>
<el-table-column prop="adminName" label="提交人" width="120"/>
<el-table-column prop="remark" label="备注" width="150" show-overflow-tooltip />
<el-table-column prop="adminName" label="提交人" width="120" />
<el-table-column v-if="checkTab === 'reject'" prop="rejectReason" label="驳回理由" width="150"
show-overflow-tooltip/>
<el-table-column v-if="checkTab !== 'pending'" prop="auditName" label="审核人" width="120"/>
show-overflow-tooltip />
<el-table-column v-if="checkTab !== 'pending'" prop="auditName" label="审核人" width="120" />
<el-table-column prop="createTime" label="提交时间" width="180" sortable="custom">
<template #default="{ row }">
{{
@ -104,12 +118,13 @@
{{ row.auditTime ? moment(row.auditTime).format('YYYY-MM-DD HH:mm:ss') : '--' }}
</template>
</el-table-column>
<el-table-column v-if="checkTab === 'pending'" fixed="right" prop="operation" label="操作" width="150px">
<el-table-column v-if="checkTab === 'pending' && (hasrefundWaitThough || hasrefundWaitReject)&&hasrefundWaitShow" fixed="right"
prop="operation" label="操作" width="150px">
<template #default="scope">
<div class="operation">
<el-popconfirm title="确定要通过此条记录吗?" @confirm="handleApprove(scope.row)">
<template #reference>
<el-button :disabled="clicked || cancelClicked" type="primary" text>
<el-button v-if="hasrefundWaitThough" :disabled="clicked || cancelClicked" type="primary" text>
通过
</el-button>
</template>
@ -120,7 +135,8 @@
</el-button>
</template>
</el-popconfirm>
<el-button :disabled="clicked || cancelClicked" type="primary" text @click="showRejectDialog(scope.row)">
<el-button v-if="hasrefundWaitReject" :disabled="clicked || cancelClicked" type="primary" text
@click="showRejectDialog(scope.row)">
驳回
</el-button>
</div>
@ -128,8 +144,7 @@
</el-table-column>
</el-table>
<el-pagination class="pagination" v-model:current-page="pagination.pageNum" v-model:page-size="pagination.pageSize"
layout="total, sizes, prev, pager, next, jumper" :total="pagination.total"
@size-change="handlePageSizeChange"
layout="total, sizes, prev, pager, next, jumper" :total="pagination.total" @size-change="handlePageSizeChange"
@current-change="handleCurrentChange"></el-pagination>
</el-card>
@ -137,7 +152,7 @@
<el-form>
<el-form-item label="驳回理由" required>
<el-input v-model="rejectReason" type="textarea" :rows="4" placeholder="请输入驳回理由" maxlength="200"
show-word-limit/>
show-word-limit />
</el-form-item>
</el-form>
<template #footer>
@ -150,17 +165,16 @@
</template>
<script setup>
import {onMounted, reactive, ref} from 'vue'
import {ElMessage} from 'element-plus'
import { onMounted, reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import API from '@/util/http.js'
import moment from 'moment'
import {useAdminStore} from "@/store/index.js";
import {storeToRefs} from "pinia";
import {findMenuById, permissionMapping} from "@/utils/menuTreePermission.js"
import { useAdminStore } from "@/store/index.js";
import { storeToRefs } from "pinia";
import dayjs from "dayjs";
import { permissionMapping, hasMenuPermission } from "@/utils/menuTreePermission.js"
const adminStore = useAdminStore();
const {adminData, menuTree} = storeToRefs(adminStore);
const { adminData, menuTree } = storeToRefs(adminStore);
const defaultTime = [
new Date(2000, 1, 1, 0, 0, 0),
new Date(2000, 2, 1, 23, 59, 59),
@ -219,6 +233,28 @@ const activeName = ref('wait')
const sortField = ref('')
const sortOrder = ref('')
const market = ref("")
//退
const hasrefundThrough = ref(false) // 退
const hasrefundReject = ref(false) // 退
const hasrefundWait = ref(false) // 退
const hasrefundWaitShow = ref(false) // 退
const hasrefundWaitThough = ref(false) // 退
const hasrefundWaitReject = ref(false) // 退
//
const initPermissions = async() => {
if (!menuTree.value || !menuTree.value.length) return;
// 退
hasrefundThrough.value = hasMenuPermission(menuTree.value, permissionMapping.refundThrough);
hasrefundReject.value = hasMenuPermission(menuTree.value, permissionMapping.refundReject);
hasrefundWait.value = hasMenuPermission(menuTree.value, permissionMapping.refundWait);
hasrefundWaitShow.value = hasMenuPermission(menuTree.value, permissionMapping.refundWaitShow);
hasrefundWaitThough.value = hasMenuPermission(menuTree.value, permissionMapping.refundWaitThough);
hasrefundWaitReject.value = hasMenuPermission(menuTree.value, permissionMapping.refundWaitReject);
console.log('退款权限赋值完成');
};
//
const handleSortChange = (column) => {
if (column.prop === 'sumGold') {
@ -243,6 +279,10 @@ const handleSortChange = (column) => {
}
//
const showRejectDialog = (row) => {
if (!hasrefundWaitReject) {
ElMessage.error('暂无权限')
return
}
scopeValue.value = row
rejectReason.value = ''
rejectDialogVisible.value = true
@ -250,7 +290,7 @@ const showRejectDialog = (row) => {
//
const getRefundGoods = async () => {
try {
const res = await API({url: '/general/goods'})
const res = await API({ url: '/general/goods' })
refundGoodsOptions.value = res.data || []
} catch (error) {
console.error('获取商品列表失败', error)
@ -259,6 +299,10 @@ const getRefundGoods = async () => {
//
const get = async function (val) {
if (!hasrefundWaitShow) {
ElMessage.error('暂无权限')
return
}
try {
if (typeof val === 'number') {
pagination.value.pageNum = val
@ -295,7 +339,7 @@ const get = async function (val) {
return
}
}
const res = await API({url: '/audit/selectRefund', data: params})
const res = await API({ url: '/audit/selectRefund', data: params })
tableData.value = res.list || []
pagination.value.total = res.total || 0
console.log('查全部的total', pagination.value.total, res.total)
@ -306,8 +350,11 @@ const get = async function (val) {
const clicked = ref(false);
//
const handleApprove = async (row) => {
if (!hasrefundWaitThough) {
ElMessage.error('暂无权限')
return
}
clicked.value = true
if (findMenuById(menuTree.value, permissionMapping.Refund_Approval)) {
try {
const params = {
orderCode: row.orderCode,
@ -315,7 +362,7 @@ const handleApprove = async (row) => {
action: 1,// action1,2
rejectReason: ''
}
await API({url: '/audit/audit', data: params})
await API({ url: '/audit/audit', data: params })
ElMessage.success('审核通过成功')
await get()
clicked.value = false
@ -324,17 +371,17 @@ const handleApprove = async (row) => {
console.error('审核通过失败', error)
ElMessage.error('操作失败')
}
} else {
ElMessage.warning('没有权限')
clicked.value = false
}
}
//
const cancelClicked = ref(false)
//
const handleReject = async () => {
if (!hasrefundWaitReject) {
ElMessage.error('暂无权限')
return
}
cancelClicked.value = true
if (findMenuById(menuTree.value, permissionMapping.Refund_Approval)) {
if (!rejectReason.value.trim()) {
ElMessage.warning('请输入驳回理由')
@ -347,7 +394,7 @@ const handleReject = async () => {
action: 2,
rejectReason: rejectReason.value
}
await API({url: '/audit/audit', data: params})
await API({ url: '/audit/audit', data: params })
ElMessage.success('驳回成功')
rejectDialogVisible.value = false
await get()
@ -358,13 +405,12 @@ const handleReject = async () => {
console.error('驳回失败', error)
ElMessage.error('操作失败')
}
} else {
ElMessage.warning('没有权限')
cancelClicked.value = false
}
}
const getStats = async () => {
if (!hasrefundWaitShow) {
return
}
try {
const params = {
pageNum: pagination.value.pageNum,
@ -460,26 +506,27 @@ const get7Days = function () {
const handleClick = function (tab, event) {
activeName.value = tab.props.name
if (tab.props.name === 'wait') {
console.log(hasrefundWait.value);
if (!hasrefundWait) {
ElMessage.error('暂无权限')
return
}
adminWait()
} else if (tab.props.name === 'pass') {
if (!hasrefundThrough.value) {
ElMessage.error('暂无权限')
return
}
adminPass()
} else if (tab.props.name === 'reject') {
adminReject()
if (!hasrefundReject) {
ElMessage.error('暂无权限')
return
}
}
//
const getCurrentStatus = () => {
switch (activeName.value) {
case 'wait':
return STATUS.PENDING
case 'pass':
return STATUS.APPROVED
case 'reject':
return STATUS.REJECTED
default:
return ''
adminReject()
}
}
//
const adminWait = async function () {
checkTab.value = 'pending'
@ -556,7 +603,7 @@ const format3 = (num) => {
}
//
const rules = reactive({
reason: [{required: true, message: '请输入驳回理由', trigger: 'blur'}]
reason: [{ required: true, message: '请输入驳回理由', trigger: 'blur' }]
})
const handlePageSizeChange = function (val) {
pagination.value.pageSize = val
@ -567,6 +614,14 @@ const handleCurrentChange = function (val) {
get()
}
onMounted(async () => {
await initPermissions()
if(hasrefundWaitShow.value){
searchForm.value.auditStatus = '0'
}else if(hasrefundThrough.value){
searchForm.value.auditStatus = '1'
}else if(hasrefundReject){
searchForm.value.auditStatus = '2'
}
getRefundGoods()
await getMarket()
await get()
@ -583,4 +638,25 @@ onMounted(async () => {
.operation {
display: flex;
}
.select {
display: flex;
.selectRow {
width: 17vw;
display: flex;
align-items: center;
justify-content: center;
padding: 0 0.5vw;
.text {
width: 5vw;
font-size: 15px;
}
.selectContent {
flex: 1;
}
}
}
</style>

16
src/views/consume/bean/beanConsume.vue

@ -4,7 +4,7 @@
<el-button
:type="activeTab === 'addBeanConsume' ? 'primary' : 'default'"
@click="navigateTo('addBeanConsume')"
:disabled="!hasAdd"
v-if="hasAdd"
style="width: 120px;"
>
新增消耗
@ -12,7 +12,7 @@
<el-button
:type="activeTab === 'liveStream' ? 'primary' : 'default'"
@click="navigateTo('liveStream')"
:disabled="!hasLive"
v-if="hasLive"
style="width: 120px;"
>
直播
@ -20,7 +20,7 @@
<el-button
:type="activeTab === 'dieHardFan' ? 'primary' : 'default'"
@click="navigateTo('dieHardFan')"
:disabled="!hasFan"
v-if="hasFan"
style="width: 120px;"
>
铁粉
@ -28,7 +28,7 @@
<el-button
:type="activeTab === 'articleVideo' ? 'primary' : 'default'"
@click="navigateTo('articleVideo')"
:disabled="!hasArticleVideo"
v-if="hasArticleVideo"
style="width: 120px;"
>
文章/视频
@ -68,10 +68,10 @@ const navigateTo = (name) => {
const initPermissions = () => {
if (!menuTree.value || !menuTree.value.length) return;
hasAdd.value = hasMenuPermission(menuTree.value, permissionMapping.Submit_Golden_Bean_Consumption);
hasLive.value = hasMenuPermission(menuTree.value, permissionMapping.View_Golden_Bean_Live_Consumption_Details);
hasFan.value = hasMenuPermission(menuTree.value, permissionMapping.View_Golden_Bean_Fan_Consumption_Details);
hasArticleVideo.value = hasMenuPermission(menuTree.value, permissionMapping.View_Golden_Bean_Article_Video_Consumption_Details);
hasAdd.value = hasMenuPermission(menuTree.value, permissionMapping.addBeanConsume);
hasLive.value = hasMenuPermission(menuTree.value, permissionMapping.beanZBDetail);
hasFan.value = hasMenuPermission(menuTree.value, permissionMapping.beanTFDetail);
hasArticleVideo.value = hasMenuPermission(menuTree.value, permissionMapping.beanSPWZDetail);
};
//

10
src/views/consume/gold/coinConsume.vue

@ -3,11 +3,11 @@
<div class="fatherTop">
<el-button-group>
<el-button :type="activeTab === 'coinConsumeDetail' ? 'primary' : 'default'"
@click="navigateTo('coinConsumeDetail')" :disabled="!hasDetail">
@click="navigateTo('coinConsumeDetail')" v-if="hasDetail">
金币消耗明细
</el-button>
<el-button :type="activeTab === 'addCoinConsume' ? 'primary' : 'default'" @click="navigateTo('addCoinConsume')"
:disabled="!hasAdd">
v-if="hasAdd">
新增消耗
</el-button>
</el-button-group>
@ -42,8 +42,8 @@ const navigateTo = (name) => {
//
const initPermissions = () => {
if (!menuTree.value || !menuTree.value.length) return;
hasAdd.value = hasMenuPermission(menuTree.value, permissionMapping.Submit_Gold_Coin_Consumption);
hasDetail.value = hasMenuPermission(menuTree.value, permissionMapping.View_Gold_Coin_Consumption_Details);
hasAdd.value = hasMenuPermission(menuTree.value, permissionMapping.addCoinConsume);
hasDetail.value = hasMenuPermission(menuTree.value, permissionMapping.coinConsumeDetail);
};
//
@ -51,7 +51,7 @@ const getDefaultAuditRoute = () => {
initPermissions();
if (hasDetail.value) return 'coinConsumeDetail';
if (hasAdd.value) return 'addCoinConsume';
return 'addCoinConsume';
return 'coinConsumeDetail';
};
//

228
src/views/history/history.vue

@ -1,179 +1,71 @@
<template>
<el-tabs v-model="activeName" type="card" @tab-click="handleClick">
<el-tab-pane label="金币查询(新)" name="new"></el-tab-pane>
<el-tab-pane label="金币查询(旧)" name="old"></el-tab-pane>
</el-tabs>
<el-card>
<el-text size="large">姓名</el-text>
<el-input v-model="searchObj.name" placeholder="请输入姓名" style="width: 12vw;margin-right:1vw"
clearable></el-input>
<router-view></router-view>
</template>
<el-text size="large">精网号</el-text>
<el-input v-model="searchObj.jwcode" placeholder="请输入精网号" style="width: 12vw;margin-right:1vw"
clearable></el-input>
<script setup>
import {onMounted, ref, watch} from 'vue';
import {useRoute, useRouter} from 'vue-router';
import {storeToRefs} from "pinia";
import {useAdminStore} from "@/store/index.js";
import {hasMenuPermission, permissionMapping} from "@/utils/menuTreePermission.js";
<el-text size="large" style="width: 80px">更新时间</el-text>
<el-date-picker v-model="dateRange" type="datetimerange" :default-time="defaultTime" range-separator=""
start-placeholder="开始时间" end-placeholder="结束时间" style="width: 25vw;margin-right:1vw" />
const router = useRouter();
const route = useRoute();
const adminStore = useAdminStore();
const {menuTree} = storeToRefs(adminStore);
<el-button type="success" @click="resetSearch">重置</el-button>
<el-button type="primary" v-if="activeName == 'new'" @click="get">查询</el-button>
<el-button type="primary" v-else-if="activeName == 'old'" @click="getOld">查询</el-button>
</el-card>
const activeTab = ref('');
const hasNew = ref(false);
const hasOld = ref(false);
//
const navigateTo = (name) => {
activeTab.value = name;
router.push({name});
};
<el-card style="margin-top:10px" v-show="tableData.length > 0">
<el-table :data="tableData" style="width: 82vw;height:60vh">
<el-table-column type="index" label="序号" width="100" header-align="center" align="center">
<template #default="scope">
{{ scope.$index + 1 + (pagination.pageNum - 1) * pagination.pageSize }}
</template>
</el-table-column>
<el-table-column prop="name" label="客户姓名" width="180" show-overflow-tooltip />
<el-table-column prop="jwcode" label="精网号" width="180" header-align="center" align="center" />
<el-table-column prop="num" label="数量" width="180" header-align="center" align="center" />
<el-table-column prop="updateType" show-overflow-tooltip label="更新类型" width="180" align="center" />
<el-table-column v-if="activeName === 'new'" prop="freeGold" label="免费金币" width="180" align="center" />
<el-table-column v-if="activeName === 'new'" prop="permanentGold" label="永久金币" width="180" align="center" />
<el-table-column v-if="activeName === 'new'" prop="taskGold" label="任务金币" width="180" align="center" />
<el-table-column prop="operator" label="操作人" width="180" />
<el-table-column prop="createTime" label="更新时间" width="200" header-align="center" align="center" />
<el-table-column prop="remark" label="备注" show-overflow-tooltip width="200" align="center" />
</el-table>
<el-pagination background style="margin-top:20px" v-model:current-page="pagination.pageNum"
v-model:page-size="pagination.pageSize" layout="total, sizes, prev, pager, next, jumper"
:total="pagination.total" @size-change="handlePageSizeChange"
@current-change="handleCurrentChange"></el-pagination>
</el-card>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { ElMessage } from 'element-plus'
import API from '@/util/http.js'
import moment from 'moment'
import { useAdminStore } from "@/store/index.js"
import { storeToRefs } from "pinia"
import dayjs from 'dayjs'
const adminStore = useAdminStore()
const { adminData, menuTree } = storeToRefs(adminStore)
import { permissionMapping, findMenuById } from "@/utils/menuTreePermission.js"
const defaultTime = [
new Date(2000, 1, 1, 0, 0, 0),
new Date(2000, 2, 1, 23, 59, 59),
]
const activeName = ref('new')
const handleClick = function (tab) {
activeName.value = tab.props.name
if (tab.props.name === 'new') {
resetSearch()
pagination.value.pageNum = 1
tableData.value = []
console.log('新的');
} else if (tab.props.name === 'old') {
resetSearch()
pagination.value.pageNum = 1
console.log('旧的');
tableData.value = []
}
}
const tableData = ref([])
const dateRange = ref([])
const searchObj = ref({
name: '',
jwcode: ''
})
const pagination = ref({
pageNum: 1,
pageSize: 50,
total: 0
})
//
const getOld = async function () {
if (findMenuById(menuTree.value, permissionMapping.History_Query)) {
try {
if ((searchObj.value.name || searchObj.value.jwcode) && activeName.value == 'old') {
const startTime = dateRange.value && dateRange.value[0] ? moment(dateRange.value[0]).format('YYYY-MM-DD HH:mm:ss') : ""
const endTime = dateRange.value && dateRange.value[1] ? moment(dateRange.value[1]).format('YYYY-MM-DD HH:mm:ss') : ""
const res = await API({
url: '/history/getOldHistoryRecord',
data: {
...searchObj.value,
startTime: startTime,
endTime: endTime,
pageNum: pagination.value.pageNum,
pageSize: pagination.value.pageSize,
}
})
if (res.code == 200) {
ElMessage.success('查询成功')
tableData.value = res.data.list
console.log('tableData.value', res.data.list);
pagination.value.total = res.data.total
}
} else {
ElMessage.error('请输入姓名或精网号')
return
}
} catch (e) {
ElMessage.error(e.message)
}
}
}
//
const get = async function () {
if (findMenuById(menuTree.value, permissionMapping.History_Query)) {
try {
if ((searchObj.value.name || searchObj.value.jwcode) && activeName.value == 'new') {
const startTime = dateRange.value && dateRange.value[0] ? moment(dateRange.value[0]).format('YYYY-MM-DD HH:mm:ss') : ""
const endTime = dateRange.value && dateRange.value[1] ? moment(dateRange.value[1]).format('YYYY-MM-DD HH:mm:ss') : ""
const res = await API({
url: '/history/getNewHistoryRecord',
data: {
...searchObj.value,
startTime: startTime,
endTime: endTime,
pageNum: pagination.value.pageNum,
pageSize: pagination.value.pageSize,
//
const initPermissions = () => {
if (!menuTree.value || !menuTree.value.length) return
hasNew.value = hasMenuPermission(menuTree.value, permissionMapping.newCoinSelect)
hasOld.value = hasMenuPermission(menuTree.value, permissionMapping.oldCoinSelect)
}
})
if (res.code == 200) {
ElMessage.success('查询成功')
tableData.value = res.data.list
console.log('tableData.value', res.data.list)
pagination.value.total = res.data.total
//
const getDefaultAuditRoute = () => {
initPermissions()
if (hasNew.value) return 'newHistory'
if (hasOld.value) return 'oldHistory'
return 'newHistory'
};
//
watch(() => route.name, (newName) => {
initPermissions()
if (newName === 'newHistory' || newName === 'oldHistory') {
activeTab.value = newName;
} else if (newName === 'history') {
// 访 /history
const defaultRoute = getDefaultAuditRoute();
navigateTo(defaultRoute);
}
});
//
onMounted(() => {
initPermissions()
console.log('============================',hasNew.value,hasOld.value)
if (route.name === 'history') {
const defaultRoute = getDefaultAuditRoute();
navigateTo(defaultRoute);
} else {
ElMessage.error('请输入姓名或精网号')
return
}
} catch (e) {
ElMessage.error(e.message)
//
if (route.name === 'newHistory' || route.name === 'oldHistory') {
activeTab.value = route.name;
}
}
}
const resetSearch = function () {
searchObj.value = {
name: '',
jwcode: ''
}
dateRange.value = []
}
const handlePageSizeChange = function (val) {
pagination.value.pageSize = val
if (activeName.value == 'new') {
get()
} else if (activeName.value == 'old') {
getOld()
}
}
const handleCurrentChange = function (val) {
pagination.value.pageNum = val
if (activeName.value == 'new') {
get()
} else if (activeName.value == 'old') {
getOld()
}
}
</script>
<style scoped></style>
});
</script>
<style>
</style>

119
src/views/history/newHistory.vue

@ -0,0 +1,119 @@
<template>
<el-card>
<el-text size="large">姓名</el-text>
<el-input v-model="searchObj.name" placeholder="请输入姓名" style="width: 12vw;margin-right:1vw"
clearable></el-input>
<el-text size="large">精网号</el-text>
<el-input v-model="searchObj.jwcode" placeholder="请输入精网号" style="width: 12vw;margin-right:1vw"
clearable></el-input>
<el-text size="large" style="width: 80px">更新时间</el-text>
<el-date-picker v-model="dateRange" type="datetimerange" :default-time="defaultTime" range-separator=""
start-placeholder="开始时间" end-placeholder="结束时间" style="width: 25vw;margin-right:1vw" />
<el-button type="success" @click="resetSearch">重置</el-button>
<el-button type="primary" @click="get">查询</el-button>
</el-card>
<el-card style="margin-top:10px" v-show="tableData.length > 0">
<el-table :data="tableData" style="width: 82vw;height:60vh">
<el-table-column type="index" label="序号" width="100" header-align="center" align="center">
<template #default="scope">
{{ scope.$index + 1 + (pagination.pageNum - 1) * pagination.pageSize }}
</template>
</el-table-column>
<el-table-column prop="name" label="客户姓名" width="180" show-overflow-tooltip />
<el-table-column prop="jwcode" label="精网号" width="180" header-align="center" align="center" />
<el-table-column prop="num" label="数量" width="180" header-align="center" align="center" />
<el-table-column prop="updateType" show-overflow-tooltip label="更新类型" width="180" align="center" />
<el-table-column prop="freeGold" label="免费金币" width="180" align="center" />
<el-table-column prop="permanentGold" label="永久金币" width="180" align="center" />
<el-table-column prop="taskGold" label="任务金币" width="180" align="center" />
<el-table-column prop="operator" label="操作人" width="180" />
<el-table-column prop="createTime" label="更新时间" width="200" header-align="center" align="center" />
<el-table-column prop="remark" label="备注" show-overflow-tooltip width="200" align="center" />
</el-table>
<el-pagination background style="margin-top:20px" v-model:current-page="pagination.pageNum"
v-model:page-size="pagination.pageSize" layout="total, sizes, prev, pager, next, jumper"
:total="pagination.total" @size-change="handlePageSizeChange"
@current-change="handleCurrentChange"></el-pagination>
</el-card>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { ElMessage } from 'element-plus'
import API from '@/util/http.js'
import moment from 'moment'
import { useAdminStore } from "@/store/index.js"
import { storeToRefs } from "pinia"
import dayjs from 'dayjs'
const adminStore = useAdminStore()
const { adminData, menuTree } = storeToRefs(adminStore)
import { permissionMapping, findMenuById } from "@/utils/menuTreePermission.js"
const defaultTime = [
new Date(2000, 1, 1, 0, 0, 0),
new Date(2000, 2, 1, 23, 59, 59),
]
const tableData = ref([])
const dateRange = ref([])
const searchObj = ref({
name: '',
jwcode: ''
})
const pagination = ref({
pageNum: 1,
pageSize: 50,
total: 0
})
//
const get = async function () {
try {
if (searchObj.value.name || searchObj.value.jwcode) {
const startTime = dateRange.value && dateRange.value[0] ? moment(dateRange.value[0]).format('YYYY-MM-DD HH:mm:ss') : ""
const endTime = dateRange.value && dateRange.value[1] ? moment(dateRange.value[1]).format('YYYY-MM-DD HH:mm:ss') : ""
const res = await API({
url: '/history/getNewHistoryRecord',
data: {
...searchObj.value,
startTime: startTime,
endTime: endTime,
pageNum: pagination.value.pageNum,
pageSize: pagination.value.pageSize,
}
})
if (res.code == 200) {
ElMessage.success('查询成功')
tableData.value = res.data.list
console.log('tableData.value', res.data.list)
pagination.value.total = res.data.total
}
} else {
ElMessage.error('请输入姓名或精网号')
return
}
} catch (e) {
ElMessage.error(e.message)
}
}
const resetSearch = function () {
searchObj.value = {
name: '',
jwcode: ''
}
dateRange.value = []
}
const handlePageSizeChange = function (val) {
pagination.value.pageSize = val
get()
}
const handleCurrentChange = function (val) {
pagination.value.pageNum = val
get()
}
</script>
<style scoped></style>

117
src/views/history/oldHistory.vue

@ -0,0 +1,117 @@
<template>
<el-card>
<el-text size="large">姓名</el-text>
<el-input v-model="searchObj.name" placeholder="请输入姓名" style="width: 12vw;margin-right:1vw"
clearable></el-input>
<el-text size="large">精网号</el-text>
<el-input v-model="searchObj.jwcode" placeholder="请输入精网号" style="width: 12vw;margin-right:1vw"
clearable></el-input>
<el-text size="large" style="width: 80px">更新时间</el-text>
<el-date-picker v-model="dateRange" type="datetimerange" :default-time="defaultTime" range-separator=""
start-placeholder="开始时间" end-placeholder="结束时间" style="width: 25vw;margin-right:1vw" />
<el-button type="success" @click="resetSearch">重置</el-button>
<el-button type="primary" @click="getOld">查询</el-button>
</el-card>
<el-card style="margin-top:10px" v-show="tableData.length > 0">
<el-table :data="tableData" style="width: 82vw;height:60vh">
<el-table-column type="index" label="序号" width="100" header-align="center" align="center">
<template #default="scope">
{{ scope.$index + 1 + (pagination.pageNum - 1) * pagination.pageSize }}
</template>
</el-table-column>
<el-table-column prop="name" label="客户姓名" width="180" show-overflow-tooltip />
<el-table-column prop="jwcode" label="精网号" width="180" header-align="center" align="center" />
<el-table-column prop="num" label="数量" width="180" header-align="center" align="center" />
<el-table-column prop="updateType" show-overflow-tooltip label="更新类型" width="180" align="center" />
<el-table-column prop="operator" label="操作人" width="180" />
<el-table-column prop="createTime" label="更新时间" width="200" header-align="center" align="center" />
<el-table-column prop="remark" label="备注" show-overflow-tooltip width="200" align="center" />
</el-table>
<el-pagination background style="margin-top:20px" v-model:current-page="pagination.pageNum"
v-model:page-size="pagination.pageSize" layout="total, sizes, prev, pager, next, jumper"
:total="pagination.total" @size-change="handlePageSizeChange"
@current-change="handleCurrentChange"></el-pagination>
</el-card>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { ElMessage } from 'element-plus'
import API from '@/util/http.js'
import moment from 'moment'
import { useAdminStore } from "@/store/index.js"
import { storeToRefs } from "pinia"
import dayjs from 'dayjs'
const adminStore = useAdminStore()
const { adminData, menuTree } = storeToRefs(adminStore)
import { permissionMapping, findMenuById } from "@/utils/menuTreePermission.js"
const defaultTime = [
new Date(2000, 1, 1, 0, 0, 0),
new Date(2000, 2, 1, 23, 59, 59),
]
const tableData = ref([])
const dateRange = ref([])
const searchObj = ref({
name: '',
jwcode: ''
})
const pagination = ref({
pageNum: 1,
pageSize: 50,
total: 0
})
//
const getOld = async function () {
try {
if (searchObj.value.name || searchObj.value.jwcode) {
const startTime = dateRange.value && dateRange.value[0] ? moment(dateRange.value[0]).format('YYYY-MM-DD HH:mm:ss') : ""
const endTime = dateRange.value && dateRange.value[1] ? moment(dateRange.value[1]).format('YYYY-MM-DD HH:mm:ss') : ""
const res = await API({
url: '/history/getOldHistoryRecord',
data: {
...searchObj.value,
startTime: startTime,
endTime: endTime,
pageNum: pagination.value.pageNum,
pageSize: pagination.value.pageSize,
}
})
if (res.code == 200) {
ElMessage.success('查询成功')
tableData.value = res.data.list
console.log('tableData.value', res.data.list);
pagination.value.total = res.data.total
}
} else {
ElMessage.error('请输入姓名或精网号')
return
}
} catch (e) {
ElMessage.error(e.message)
}
}
const resetSearch = function () {
searchObj.value = {
name: '',
jwcode: ''
}
dateRange.value = []
}
const handlePageSizeChange = function (val) {
pagination.value.pageSize = val
getOld()
}
const handleCurrentChange = function (val) {
pagination.value.pageNum = val
getOld()
}
</script>
<style scoped></style>

264
src/views/home.vue

@ -8,6 +8,8 @@ import ChangePassword from '@/components/changePassword.vue'
import { useAdminStore } from '@/store'
import { storeToRefs } from 'pinia'
import { filterMenu, getRoutePath } from "@/utils/menuUtils.js";
import SettingsIcon from '@/assets/blue.png';
//
const menuList = ref([])
@ -46,6 +48,10 @@ function findBestMatch(menuList, path) {
return bestMatch || path // fallback
}
// require
//
const activeMenu = computed(() => {
return findBestMatch(menuList.value, route.path)
@ -93,38 +99,55 @@ function logout() {
</script>
<template>
<div>
<el-container>
<el-aside >
<div class="main-container">
<!-- 背景毛玻璃层作为内容容器 -->
<div class="background-glass">
<!-- 侧边栏 -->
<div class="sidebar-container">
<el-aside class="sidebar-layout">
<div class="logo">
<img src="../assets/新logo.png" alt="logo" style="width: 9vh; height: 9vh" />
</div>
<div class="menu-scroll-container">
<el-menu :router="true" :default-active="activeMenu" style="min-height: 80vh;border:none;">
<!-- 递归渲染菜单层级 -->
<template v-for="menu in menuList" :key="menu.id">
<!-- 有子菜单的父级菜单menuType=2 且存在children -->
<el-sub-menu v-if="menu.children && menu.children.length > 0" :index="menu.id.toString()">
<template #title>
<el-icon>
<Folder />
</el-icon>
<img
:src="`/src/assets/SvgIcons/${menu.menuName}.svg`"
:alt="`${menu.menuName}图标`"
style="width: 4vh; height: 4vh; margin-right: 4px;"
>
<span>{{ menu.menuName }}</span>
</template>
<!-- 子菜单 -->
<template v-for="child in menu.children" :key="child.id">
<!-- 子菜单为叶子节点无children -->
<el-menu-item v-if="!child.children || child.children.length === 0" :index="getRoutePath(child)">
<el-icon style="margin-right: 4px;">
<Folder />
</el-icon>
<span>{{ child.menuName }}</span>
</el-menu-item>
<!-- 子菜单有下级 -->
<el-sub-menu v-else :index="child.id.toString()">
<template #title>
<el-icon style="margin-right: 4px;">
<Folder />
</el-icon>
<span>{{ child.menuName }}</span>
</template>
<!-- 递归 下一级-->
<template v-for="grandChild in child.children" :key="grandChild.id">
<el-menu-item :index="getRoutePath(grandChild)">
<el-icon style="margin-right: 4px;">
<Folder />
</el-icon>
<span>{{ grandChild.menuName }}</span>
</el-menu-item>
</template>
@ -134,34 +157,54 @@ function logout() {
<!-- 无子菜单的一级菜单 -->
<el-menu-item v-else :index="getRoutePath(menu)">
<el-icon>
<Folder />
</el-icon>
<img
:src="`@/assets/SvgIcons/${menu.menuName}.svg`"
:alt="`${menu.menuName}图标`"
style="width: 4vh; height: 4vh; margin-right: 4px;"
>
<span>{{ menu.menuName }}</span>
</el-menu-item>
</template>
</el-menu>
</div>
<!-- 底部固定的设置中心 -->
<div class="settings-container">
<el-dropdown placement="top-start">
<span class="el-dropdown-link">
<img src="@/assets/SvgIcons/设置.svg" alt="设置" style="width: 4vh; height: 4vh" />
<span>设置中心</span>
<el-icon class="arrow-icon">
<ArrowUp />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="message()">查看个人信息</el-dropdown-item>
<el-dropdown-item @click="openChangePassword">修改密码</el-dropdown-item>
<el-dropdown-item @click="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</el-aside>
<el-container style="margin-left: 15vw;">
</div>
<!-- 右侧内容区域 -->
<div class="content-container">
<!-- 头部
<el-header class="header">
<el-menu class="el-menu-demo" mode="horizontal" :ellipsis="false">
<el-sub-menu index="1">
<template #title>
<el-image :src="imgrule1" style="width: 50px; height: 50px" />
<span style="margin-left: 10px">{{ adminData.name }}</span>
</template>
<el-menu-item @click="message()">查看个人信息</el-menu-item>
<el-menu-item @click="openChangePassword">修改密码</el-menu-item>
<el-menu-item @click="logout">退出登录</el-menu-item>
</el-sub-menu>
</el-menu>
</el-header>
<el-main style="margin-top: 6vh;height: auto;">
</el-header> -->
<!-- 主内容区域 -->
<div class="main-area">
<el-main>
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</div>
</div>
</div>
<!-- 查看个人信息 -->
<el-dialog v-model="messageVisible" title="查看个人信息" width="500px">
@ -194,36 +237,173 @@ function logout() {
</template>
<style scoped>
.header {
/* 主容器,设置背景图并居中 */
.main-container {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 80;
background: white;
height: 8vh;
bottom: 0;
background-image: url('@/assets/backgroundBlue.png');
background-size: cover;
background-position: center center;
background-repeat: no-repeat;
overflow: hidden;
}
.message-font {
font-size: 16px;
font-weight: bold;
/* 背景毛玻璃层(作为内容容器) */
.background-glass {
position: absolute;
top: 1vh;
left: 1vh;
right: 1vh;
bottom: 1vh;
background-image: url('@/assets/半透明background.png');
background-size: cover;
z-index: 1;
display: flex;
flex-direction: row;
padding: 10px;
border-radius: 12px;
}
.el-aside {
width: 15vw;
position: fixed;
z-index: 100;
height: 90vh;
}
/* 侧边栏容器 */
.sidebar-container {
flex-shrink: 0;
}
.logo {
display: flex;
align-items: center;
justify-content: center;
height: 12vh;
}
/* 中间可滚动菜单容器 */
.menu-scroll-container {
flex: 1;
overflow-y: auto;
padding: 10px 0;
}
/* 底部设置中心样式 */
.settings-container {
padding: 10px 0 10px 20px; /* 上,右, 下,左 */
display: flex;
align-items: center; /* 垂直居中 */
}
/* 调整下拉菜单的样式,确保它向上弹出 */
.el-dropdown-link:focus {
/* 移除底部的异常效果 */
outline: none;
text-decoration: none;
}
.el-dropdown-link {
display: flex;
align-items: center;
cursor: pointer;
gap:10px; /* 图标和文字左右间距 */
}
.sidebar-layout {
width: 15vw;
height: 100%;
background: #E7F4FD; /* 浅蓝色背景 */
/* backdrop-filter: blur(5px); 毛玻璃效果 --消耗性能 */
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); /* 添加阴影增强层次感 */
border-radius: 12px;
display: flex;
flex-direction: column;
position: relative;
transition: all 0.3s ease;
}
/* 内容区域容器 */
.content-container {
flex: 1;
display: flex;
flex-direction: column;
margin-left: 5px;
gap: 5px;
height: 100%;
overflow: hidden;
}
/* 主内容区域容器 */
.main-area {
flex: 1;
background: #E7F4FD;
/* 半透明浅色背景 */
/* backdrop-filter: blur(5px); */
/* 毛玻璃效果 */
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
/* 添加阴影增强层次感 */
overflow: hidden;
display: flex;
flex-direction: column;
}
/* 主内容区域样式 */
.el-main {
height: 100%;
padding: 20px;
background: transparent;
overflow-y: auto;
/* 应用自定义滚动条 */
}
/* 确保el-menu撑满容器 */
.sidebar-layout .el-menu {
width: 100%;
}
/* 侧边栏菜单样式 适配浅色背景 */
.el-menu {
background: transparent !important;
}
/* 工作台,金币管理,现金管理 */
::v-deep(.el-sub-menu__title:hover),
::v-deep(.el-menu-item:hover) {
background: #E5EBFE;
}
/* 子菜单展开时和背景同色 */
::v-deep(.el-sub-menu__title),
::v-deep(.el-menu-item) {
background: #E7F4FD;
}
.message-font {
/* 个人信息字体样式 */
font-size: 16px;
font-weight: bold;
}
/* 确保全局el-container适应容器 */
:deep(.el-container) {
/* vue3的深度选择器,用于覆盖element-plus的默认样式 */
min-height: 100%;
width: 100%;
background: transparent;
}
.el-menu-demo {
border: none;
padding: 0;
float: right;
/* 为侧边栏和主内容区域添加滚动条样式 */
.menu-scroll-container,
.el-main {
scrollbar-width: thin;
/* Firefox */
scrollbar-color: rgba(0, 0, 0, 0.3) rgba(255, 255, 255, 0.2);
/* Firefox滑块和轨道颜色 */
}
</style>

28
src/views/managerecharge/rate.vue

@ -4,7 +4,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
import request from '@/util/http'
import { useAdminStore } from "@/store/index.js";
import { storeToRefs } from "pinia";
import { findMenuById, permissionMapping } from "@/utils/menuTreePermission.js"
import { permissionMapping, hasMenuPermission } from "@/utils/menuTreePermission.js"
const adminStore = useAdminStore()
const { adminData, menuTree } = storeToRefs(adminStore)
const regeEdit = ref(false)
@ -121,11 +121,22 @@ const getEditData = async function (row) {
console.log('请求失败', error)
}
}
const editRole = ref(true)
editRole.value = findMenuById(menuTree.value, permissionMapping.Exchange_Rate_Modification)
const hasrateShow = ref(false)
const hasrateEdit = ref(false)
//
const initPermissions = () => {
if (!menuTree.value || !menuTree.value.length) return;
hasrateShow.value = hasMenuPermission(menuTree.value, permissionMapping.rateShow);
hasrateEdit.value = hasMenuPermission(menuTree.value, permissionMapping.rateEdit);
};
//
const editRate = async function () {
if (findMenuById(menuTree.value, permissionMapping.Exchange_Rate_Modification)) {
if(!hasrateEdit){
ElMessage.error('暂无权限')
return
}
//
rateEdit.value.num = parseFloat(rateEdit.value.num);
try {
@ -139,9 +150,7 @@ const editRate = async function () {
} catch (error) {
console.log('请求失败', error)
}
} else {
ElMessage.error('没有权限')
}
}
//
const edit = () => {
@ -237,12 +246,13 @@ function handleInput(value) {
return value
}
onMounted(async function () {
initPermissions()
await getAllRate()
})
</script>
<template>
<el-card style="width:82vw;height:85vh">
<el-card style="width:82vw;height:85vh" v-if="hasrateShow">
<el-table :data="tableData" v-if="(tableData.flag = 1)">
<el-table-column type="index" label="序号" width="100px" fixed="left">
<template #default="scope">
@ -265,7 +275,7 @@ onMounted(async function () {
<span>{{ formatDate(scope.row.updateTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" :span="3" v-if="editRole">
<el-table-column v-if="hasrateEdit" label="操作" :span="3">
<template #default="scope">
<el-button type="text" @click="() => {
regeEdit = true

2
src/views/moneyManage/executor/executor.vue

@ -0,0 +1,2 @@
<template></template>
<script setup></script>

450
src/views/moneyManage/receiveDetail/receiveDetail.vue

@ -0,0 +1,450 @@
<template>
<div class="content">
<div class="card1">
<el-card style="margin-bottom: 0.5vh; min-height: 110px;">
<div class="row">
<div class="rowItem">
<el-text style="width: 4vw;">精网号</el-text>
<el-input placeholder="请输入精网号" style="width:10vw;" clearable></el-input>
</div>
<div class="rowItem">
<text style="width: 4vw;">客户姓名</text>
<el-input placeholder="请输入客户姓名" style="width:10vw;" clearable></el-input>
</div>
<div class="rowItem">
<text style="width: 4vw;">所属地区</text>
<el-select placeholder="请选择所属地区" style="width:10vw;" clearable></el-select>
</div>
<div class="rowItem">
<text style="width: 4vw;">订单状态</text>
<el-select placeholder="请选择订单状态" style="width: 10vw;" clearable></el-select>
</div>
<div class="rowItem">
<text style="width: 4vw;">支付方式</text>
<el-select placeholder="请选择支付方式" style="width: 10vw;" clearable></el-select>
</div>
</div>
<div class="row" style="margin-top: 10px;">
<div class="rowItem">
<text style="width: 4vw;">活动名称</text>
<el-select placeholdert="请选择活动方式" style="width: 10vw;" clearable></el-select>
</div>
<div class="rowItem">
<text style="width: 4vw;">产品名称</text>
<el-select placeholder="请选择产品名称" style="width: 10vw;" clearable></el-select>
</div>
<div class="rowItem" style="width: 30vw">
<text style="width: 4vw; margin-left: 0.5vw;">付款时间</text>
<el-date-picker v-model="getTime" type="datetimerange" range-separator=""
start-placeholder="起始时间" end-placeholder="结束时间" style="width: 22vw; "
@change="handleDatePickerChange" :default-time="defaultTime"
:disabled-date="disabledDate" />
</div>
</div>
<div class="row" style="margin-top: 10px;">
<div class="buttons">
<el-button type="primary">查询</el-button>
<el-button type="success">重置</el-button>
<el-button type="primary">导出excel</el-button>
<el-button type="primary" style="background-color: #5870FF;">导出列表</el-button>
</div>
</div>
</el-card>
</div>
<div class="card2">
<el-card>
<div class="btns">
<div class="tabs">
<el-button-group>
<el-button class="btnItem"
:style="{ backgroundColor: activeTab === 'wait' ? '#2741DE' : '#E5EBFE', color: activeTab === 'wait' ? 'white' : '#666' }"
@click="navigateTo('wait')">
待审核
</el-button>
<el-button class="btnItem"
:style="{ backgroundColor: activeTab === 'pass' ? '#2741DE' : '#E5EBFE', color: activeTab === 'pass' ? 'white' : '#666' }"
@click="navigateTo('pass')">
已通过
</el-button>
<el-button class="btnItem"
:style="{ backgroundColor: activeTab === 'reject' ? '#2741DE' : '#E5EBFE', color: activeTab === 'reject' ? 'white' : '#666' }"
@click="navigateTo('reject')">
已驳回
</el-button>
</el-button-group>
</div>
<div class="btnAdd">
<el-button @click="openAddForm" type="success">添加收款</el-button>
</div>
</div>
<div class="table">
<el-table :data="tableData" style="width: 80vw;height:54vh;" @sort-change="handleSortChange"
:row-style="{ height: '60px' }" :header-cell-style="{ textAlign: 'center' }"
:cell-style="{ textAlign: 'center' }">
<el-table-column type="index" label="序号" width="100px" fixed="left">
<template #default="scope">
<span>{{ scope.$index + 1 + (pageInfo.pageNum - 1) * pageInfo.pageSize }}</span>
</template>
</el-table-column>
<el-table-column fixed="left" prop="jwcode" label="Homily ID" width="110px" />
<el-table-column fixed="left" prop="name" label="姓名" width="110px" />
<el-table-column prop="market" label="所属地区" width="80px" />
<el-table-column prop="activity" label="活动名称" width="120px" />
<el-table-column prop="rateName" label="产品名称" width="120px" />
<el-table-column prop="money" label="付款币种" width="100px">
</el-table-column>
<el-table-column prop="permanentGold" label="付款金额" width="120px">
</el-table-column>
<el-table-column prop="freeGold" label="支付方式" width="110px">
</el-table-column>
<el-table-column prop="remark" label="付款时间" width="200px" />
<el-table-column prop="payModel" label="转账凭证" width="110px">
<template #default="scope">
<div v-if="scope.row.voucher"
style="display: flex; justify-content: center; align-items: center; cursor: pointer;"
@click="previewImage(scope.row.voucher)">
<img :src="scope.row.payModel" alt="支付凭证" style="width: auto; height: 40px;">
</div>
<div v-else
style="display: flex; justify-content: center; align-items: center; height: 40px;">
--
</div>
</template>
</el-table-column>
<el-table-column prop="" label="备注" width="150px" show-overflow-tooltip>
</el-table-column>
<el-table-column fixed="right" prop="adminName" label="订单状态" width="100px" />
<el-table-column fixed="right" prop="rejectReason" label="操作" width="100px" />
</el-table>
</div>
<div class="pagination">
<el-pagination :page-size="pageInfo.pageSize" :page-sizes="[5, 10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper" :total="total"
@size-change="handlePagination('size', $event)"
@current-change="handlePagination('page', $event)"></el-pagination>
</div>
</el-card>
</div>
<el-dialog class="adddialog" v-model="addFormisible" width="20vw" :before-close="closeAddForm">
<el-form class="addForm" label-width="4vw" label-position="left">
<el-form-item label="精网号" required>
<el-input v-model="addFormData.jwcode" placeholder="请输入驳回理由" />
</el-form-item>
<el-form-item label="客户姓名" required>
<el-input v-model="addFormData.userName" placeholder="请输入客户姓名" />
</el-form-item>
<el-form-item label="所属地区" required>
<el-input disabled="true" v-model="addFormData.market" placeholder="请输入所属地区" />
</el-form-item>
<el-form-item label="活动名称" required>
<el-input v-model="addFormData.jwcode" placeholder="请输入活动名称" />
</el-form-item>
<el-form-item label="产品名称" required>
<el-select placeholder="请选择产品名称" clearable></el-select>
</el-form-item>
<el-form-item label="付款币种" required>
<el-select placeholder="请选择付款币种" clearable></el-select>
</el-form-item>
<el-form-item label="付款金额" required>
<el-input v-model="addFormData.jwcode" placeholder="请输入付款金额" />
</el-form-item>
<el-form-item label="支付方式" required>
<el-select placeholder="请选择支付方式" clearable></el-select>
</el-form-item>
<el-form-item label="付款时间" required>
<el-time-picker v-model="addFormData.time" placeholder="请选择付款时间" />
</el-form-item>
<el-form-item label="转账凭证" required>
<el-upload ref="uploadRef" class="uploader" :show-file-list="false" list-type="picture-card"
:auto-upload="false" :before-upload="beforeUpload" :on-error="handelImgErr"
:on-change="handleImageChange" :http-request="customUpload">
<img v-if="addFormData.imageUrl" :src="addFormData.imageUrl" class="avatar"
style="height: 100%; width: 100%; object-fit: cover;" />
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
</el-upload>
</el-form-item>
<el-form-item label="驳回理由" required>
<el-input v-model="addFormData.mask" type="textarea" :rows="4" placeholder="请输入驳回理由" maxlength="200"
show-word-limit />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button style="background-color: #7E91FF;" @click="closeAddForm">取消</el-button>
<el-button style="background-color: #2741DE; margin-left: 2.5vw;" type="primary"
@click="handleReject">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { ElMessage } from 'element-plus'
import API from '@/util/http.js'
//==================== =========================
const activeTab = ref('wait')
const navigateTo = (tab) => {
if (tab === 'wait') {
activeTab.value = 'wait'
} else if (tab === 'pass') {
activeTab.value = 'pass'
}
else if (tab === 'reject') {
activeTab.value = 'reject'
}
}
//================= ==================
const addFormData = ref({
})
const addFormisible = ref(false)
const uploadRef = ref()
const openAddForm = () => {
addFormisible.value = true
}
const closeAddForm = () => {
addFormisible.value = false
addFormData.value = {}
}
const handleImgSuccess = (response, uploadFile) => {
try {
console.log('11', response)
addFormData.value.imageUrl = response.data.url
console.log('22', response.data.url)
} catch (error) {
console.log('报错信息', error)
ElMessage.error(response.msg || '图片上传失败')
}
}
const handelImgErr = (err) => {
console.log(err);
addFormData.value.imageUrl = null
ElMessage.error("图片上传失败")
}
const beforeUpload = (rawFile) => {
if (rawFile.type !== 'image/jpeg' && rawFile.type !== 'image/png') {
ElMessage.error('图片必须是jpg或png类型!')
return false
} else if (rawFile.size / 1024 / 1024 > 1) {
ElMessage.error('图片大小不能超过 1MB!')
return false
}
return true
}
const handleImageChange = (file) => {
uploadRef.value.submit()
}
const customUpload = async (options) => {
try {
const formData = new FormData()
formData.append('file', options.file)
const response = await API({
url: 'https://api.homilychart.com/hljw/api/aws/upload',
method: 'POST',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
if (response.code === 200 && response.data) {
handleImgSuccess(response, options.file)
ElMessage.success(response.msg || '上传成功')
} else {
options.onError(response)
ElMessage.error(response.msg || '上传失败')
}
} catch (error) {
console.error('上传错误:', error)
ElMessage.error(`上传失败: ${error.msg || error.message || '网络异常'}`)
}
}
//==========================================
const pageInfo = ref({
pageSize: 10,
pageNum: 1
})
const total = ref(0)
const tableData = [
{
jwcode: 'HW2023001',
name: '张三',
market: '北京',
activity: '春季促销活动',
rateName: '黄金投资产品A',
money: '人民币',
permanentGold: '5000.00',
freeGold: '银行转账',
remark: '2023-03-15 10:30:00',
voucher: 'https://example.com/images/voucher1.jpg',
adminName: '已支付',
rejectReason: '无',
},
{
jwcode: 'HW2023002',
name: '李四',
market: '上海',
activity: '夏日特惠活动',
rateName: '白银理财产品B',
money: '人民币',
permanentGold: '3000.00',
freeGold: '支付宝',
remark: '2023-06-20 14:15:00',
voucher: 'https://example.com/images/voucher2.jpg',
adminName: '已支付',
rejectReason: '无',
},
{
jwcode: 'HW2023003',
name: '王五',
market: '广州',
activity: '秋季回馈活动',
rateName: '铂金收藏产品C',
money: '美元',
permanentGold: '1000.00',
freeGold: '微信支付',
remark: '2023-09-10 09:20:00',
voucher: '',
adminName: '待支付',
rejectReason: '无',
},
{
jwcode: 'HW2023004',
name: '赵六',
market: '深圳',
activity: '冬季感恩活动',
rateName: '钻石尊享产品D',
money: '欧元',
permanentGold: '2000.00',
freeGold: '信用卡',
remark: '2023-12-05 16:40:00',
voucher: 'https://example.com/images/voucher4.jpg',
adminName: '已支付',
rejectReason: '无',
},
{
jwcode: 'HW2023005',
name: '孙七',
market: '成都',
activity: '周年庆典活动',
rateName: '翡翠投资产品E',
money: '人民币',
permanentGold: '8000.00',
freeGold: '银行转账',
remark: '2024-01-20 11:10:00',
voucher: '',
adminName: '已驳回',
rejectReason: '支付金额有误',
}
];
</script>
<style scoped lang="scss">
.content {
height: 100%;
width: 83vw;
.card1 {
width: 100%;
.row {
height: 4vh;
width: 80vw;
display: flex;
min-height: 40px;
.rowItem {
display: flex;
width: 15vw;
align-items: center;
justify-content: center;
margin-right: 0.5vw;
}
.buttons {
display: flex;
justify-content: center;
align-items: center;
margin-left: 10px;
}
}
}
.card2 {
width: 100%;
margin-top: 2vh;
.btns {
display: flex;
align-items: center;
justify-content: space-between;
.tabs{
min-width: 300px;
.btnItem {
margin-left: 10px;
border-radius: 5px;
}
}
.btnAdd {
flex: 1;
justify-content: flex-end;
margin-right: 1vw;
}
}
.table {
margin: 10px 3vw;
border-radius: 20px;
}
.pagination {
margin-top: 10px;
padding: 10px 10px;
}
}
:deep(.adddialog) {
min-width: 450px;
background-color: #F3FAFE !important;
.addForm {
padding: 0 3vw 1vh 1vw;
.el-date-editor {
display: flex;
flex: 1;
}
}
.dialog-footer {
display: flex;
justify-content: center;
padding-bottom: 1.5vh;
}
}
:deep(.adddialog .el-form-item__label) {
min-width: 80px;
width: auto;
font-weight: 800;
padding-bottom: 15px;
}
}
</style>

471
src/views/moneyManage/refundDetail/refundDetail.vue

@ -0,0 +1,471 @@
<template>
<el-card style="margin-bottom: 0.5vh;">
<div class="condition1">
<div class="condition-item">
<el-text size="large" style="width:4vw;">精网号</el-text>
<el-input v-model="searchForm.jwcode" placeholder="请输入精网号" style="width:10vw;" clearable />
</div>
<div class="condition-item">
<el-text size="large" style="width:4vw;">客户姓名</el-text>
<el-input v-model="searchForm.username" placeholder="请输入客户姓名" style="width:10vw;" clearable />
</div>
<div class="condition-item">
<el-text size="large" style="width:4vw;">所属地区</el-text>
<el-input v-model="searchForm.area" placeholder="请输入所属地区" style="width:10vw;" clearable />
</div>
<div class="condition-item">
<el-text size="large" style="width:4vw;">提交人</el-text>
<el-input v-model="searchForm.adminName" placeholder="请输入提交人" style="width:10vw;" clearable />
</div>
<div class="condition-item">
<el-text size="large" style="width:4vw;">支付方式</el-text>
<el-select v-model="searchForm.payType" style="width:10vw;">
<el-option v-for="item in payments" :key="item" :label="item" :value="item" />
</el-select>
</div>
</div>
<div class="condition1">
<div class="condition-item2">
<el-text size="large" style="width:4vw;">活动名称</el-text>
<el-input v-model="searchForm.activity" placeholder="请输入活动名称" style="width:10vw;" clearable />
</div>
<div class="condition-item2">
<el-text size="large" style="width:4vw;">产品名称</el-text>
<el-input v-model="searchForm.productName" placeholder="请输入产品名称" style="width:10vw;" clearable />
</div>
<div class="condition-item2" style="width: 30vw;">
<el-text size="large" style="width:4vw;">付款时间</el-text>
<el-date-picker v-model="dateRange" type="datetimerange" range-separator="" start-placeholder="起始时间"
end-placeholder="结束时间" style="width:22vw;" @change="handleDatePickerChange"
:disabled-date="disabledDate" />
</div>
<div>
<el-button type="success">重置</el-button>
<el-button type="primary">查询</el-button>
<el-button type="primary">导出excel</el-button>
<el-button type="primary">导出列表</el-button>
</div>
</div>
</el-card>
<div style="display: flex;">
<el-button type="primary" @click="showAdd = true">新增收款</el-button>
<el-button type="primary" @click="showAudit1 = true">新增收款</el-button>
<el-button type="primary" @click="showAudit2 = true">新增收款</el-button>
</div>
<el-card style="margin-top: 0.5vh;">
<el-table :data="tableData" style="height:64vh;width:82vw">
<el-table-column type="index" label="序号" width="60" fixed="left" />
<el-table-column prop="name" label="Homily ID" width="120" fixed="left" />
<el-table-column prop="jwcode" label="姓名" width="120" fixed="left" show-overflow-tooltip />
<el-table-column prop="market" label="所属地区" width="120" />
<el-table-column prop="orderCode" label="活动名称" width="120px" show-overflow-tooltip />
<el-table-column prop="refundType" label="产品名称" width="120" />
<el-table-column prop="refundType" label="退款类型" width="120" />
<el-table-column prop="refundType" label="提交人" width="120" />
<el-table-column prop="refundType" label="退款理由" width="120" />
<el-table-column prop="refundType" label="付款币种" width="120" />
<el-table-column prop="refundType" label="付款金额" width="120" />
<el-table-column prop="refundType" label="支付方式" width="120" />
<el-table-column prop="refundType" label="付款时间" width="120" />
<el-table-column prop="refundType" label="转账凭证" width="120" />
<el-table-column prop="remark" label="备注" width="150" show-overflow-tooltip />
<el-table-column prop="adminName" label="订单状态" width="120" />
<el-table-column prop="operation" label="操作" fixed="right" width="150px">
<template #default="scope">
<div class="operation">
<el-popconfirm title="确定要通过此条记录吗?" @confirm="handleApprove(scope.row)">
<template #reference>
<el-button v-if="hasrefundWaitThough" :disabled="clicked || cancelClicked"
type="primary" text>
通过
</el-button>
</template>
<template #actions="{ confirm, cancel }">
<el-button size="small" @click="cancel">取消</el-button>
<el-button type="primary" size="small" :disabled="clicked" @click="confirm">
确认
</el-button>
</template>
</el-popconfirm>
<el-button v-if="hasrefundWaitReject" :disabled="clicked || cancelClicked" type="primary" text
@click="showRejectDialog(scope.row)">
驳回
</el-button>
</div>
</template>
</el-table-column>
</el-table>
<el-pagination v-model:current-page="pagination.pageNum" v-model:page-size="pagination.pageSize"
layout="total, sizes, prev, pager, next, jumper" :total="pagination.total"
@size-change="handlePageSizeChange" @current-change="handleCurrentChange"
style="margin-top: 1vh;"></el-pagination>
</el-card>
<el-dialog v-model="showAdd" title="退款" class="addDialog" overflow draggable style="width: 40vw;">
<div style="display: flex;">
<div class="left">
<div class="dialog-item">
<el-text style="width:4vw;">精网号</el-text>
<el-input v-model="addForm.jwcode" placeholder="请输入精网号" style="width:10vw;" clearable />
</div>
<div class="dialog-item">
<el-text style="width:4vw;">客户姓名</el-text>
<el-input v-model="addForm.name" placeholder="请输入客户姓名" style="width:10vw;" clearable />
</div>
<div class="dialog-item">
<el-text style="width:4vw;">所属地区</el-text>
<el-input v-model="addForm.market" placeholder="请输入所属地区" style="width:10vw;" clearable />
</div>
<div class="dialog-item">
<el-text style="width:4vw;">活动名称</el-text>
<el-input v-model="addForm.orderCode" placeholder="请输入活动名称" style="width:10vw;" clearable />
</div>
<div class="dialog-item">
<el-text style="width:4vw;">产品名称</el-text>
<el-select v-model="addForm.refundType" placeholder="请选择产品名称" style="width:10vw;" clearable>
<el-option v-for="item in products" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<div class="dialog-item">
<el-text style="width:4vw;">付款金额</el-text>
<el-input v-model="addForm.refundAmount" placeholder="请输入付款金额" style="width:10vw;" clearable />
</div>
<div class="dialog-item">
<el-text style="width:4vw;">付款币种</el-text>
<el-select v-model="addForm.refundCurrency" placeholder="请选择付款币种" style="width:10vw;" clearable>
<el-option v-for="item in currencies" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</div>
<div class="dialog-item">
<el-text style="width:4vw;">支付方式</el-text>
<el-select v-model="addForm.paymentMethod" placeholder="请选择支付方式" style="width:10vw;">
<el-option v-for="item in payments" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<div class="dialog-item">
<el-text style="width:4vw;">转账凭证</el-text>
<el-form-item :rules="{ required: true, message: '请上传图片', trigger: 'change' }">
<el-upload ref="uploadRef" list-type="picture-card" :auto-upload="false"
:http-request="customUpload" :on-change="handleImageChange"
:on-success="handleUploadSuccess" :on-error="handleUploadError"
:before-upload="beforeUpload" :show-file-list="false">
<template #default>
<img v-if="addForm.imageUrl" :src="addForm.imageUrl"
style="width: 100%; height: 100%; object-fit: cover;">
<el-icon v-else>
<Plus />
</el-icon>
</template>
</el-upload>
</el-form-item>
</div>
<div class="dialog-item">
<el-text style="width:4vw;">备注</el-text>
<el-input v-model="addForm.remark" placeholder="请输入备注" style="width:10vw;" clearable />
</div>
</div>
<div class="right">
<div class="dialog-item">
<el-text style="width:4vw;">退款模式</el-text>
<el-radio-group v-model="addForm.refundMode">
<el-radio value="1">全部退款</el-radio>
<el-radio value="2">部分退款</el-radio>
</el-radio-group>
</div>
<div class="dialog-item">
<el-text style="width:4vw;">退款理由</el-text>
<el-input v-model="addForm.refundReason" placeholder="请输入退款理由" style="width:10vw;" :rows="5"
maxlength="100" show-word-limit type="textarea" />
</div>
<div style="display:flex;justify-content: center;margin-top: 5vh;">
<el-button type="primary" @click="">重置</el-button>
<el-button type="primary" @click="">提交</el-button>
</div>
</div>
</div>
</el-dialog>
<el-dialog v-model="showAudit1" title="审核" overflow draggable width="20vw">
<div class="audit1">
<div class="dialog-item">
<el-text style="width:4vw;">精网号</el-text>
<el-input v-model="addForm.jwcode" placeholder="请输入精网号" style="width:10vw;" clearable />
</div>
<div class="dialog-item">
<el-text style="width:4vw;">姓名</el-text>
<el-input v-model="addForm.name" placeholder="请输入客户姓名" style="width:10vw;" clearable />
</div>
<div class="dialog-item">
<el-text style="width:4vw;">所属地区</el-text>
<el-input v-model="addForm.market" placeholder="请输入所属地区" style="width:10vw;" clearable />
</div>
<div class="dialog-item">
<el-text style="width:4vw;">活动名称</el-text>
<el-input v-model="addForm.orderCode" placeholder="请输入活动名称" style="width:10vw;" clearable />
</div>
<div class="dialog-item">
<el-text style="width:4vw;">商品名称</el-text>
<el-input v-model="addForm.orderCode" placeholder="请输入活动名称" style="width:10vw;" clearable />
</div>
<div class="dialog-item">
<el-text style="width:4vw;">付款币种</el-text>
<el-input v-model="addForm.refundAmount" placeholder="请输入付款金额" style="width:10vw;" clearable />
</div>
<div class="dialog-item">
<el-text style="width:4vw;">付款金额</el-text>
<el-input v-model="addForm.refundAmount" placeholder="请输入付款金额" style="width:10vw;" clearable />
</div>
<div class="dialog-item">
<el-text style="width:4vw;">支付方式</el-text>
<el-input v-model="addForm.refundAmount" placeholder="请输入付款金额" style="width:10vw;" clearable />
</div>
<div class="dialog-item">
<el-text style="width:4vw;">付款时间</el-text>
<el-input v-model="addForm.refundAmount" placeholder="请输入付款金额" style="width:10vw;" clearable />
</div>
<div class="dialog-item">
<el-text style="width:4vw;">银行流水订单号</el-text>
<el-input v-model="addForm.refundAmount" placeholder="请输入付款金额" style="width:10vw;" clearable />
</div>
</div>
<div style="display:flex;justify-content: center;">
<el-button type="primary" @click="">驳回</el-button>
<el-button type="primary" @click="">通过</el-button>
</div>
</el-dialog>
<el-dialog v-model="showAudit2" title="审核">
<el-button type="primary" @click="">驳回</el-button>
</el-dialog>
</template>
<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import API from '@/util/http.js'
const uploadUrl = 'https://api.homilychart.com/hljw/api/aws/upload'
const searchForm = ref({
jwcode: ''
})
const addForm = ref({
jwcode: ''
})
const pagination = ref({
pageNum: 1,
pageSize: 50,
total: 0
})
const tableData = ref([])
const showAdd = ref(false)
const showAudit1 = ref(false)
const showAudit2 = ref(false)
const uploadRef = ref(null)
const products = ref([
{
value: '1',
label: '产品1'
},
{
value: '2',
label: '产品2'
}
])
const currencies = ref([
{
value: 'CNY',
label: '人民币'
},
{
value: 'USD',
label: '美元'
}
])
const payments = ref([{
value: '银行转账',
label: '银行转账'
},
{
value: '现金',
label: '现金'
},
{
value: '支票',
label: '支票'
},
{
value: '刷卡',
label: '刷卡'
},
{
value: 'Grabpay',
label: 'Grabpay'
},
{
value: 'Nets',
label: 'Nets'
},
{
value: 'PayPal',
label: 'PayPal'
},
{
value: 'Stripe-链接收款',
label: 'Stripe-链接收款'
},
{
value: 'Ipay88-链接收款',
label: 'Ipay88-链接收款'
},
{
value: 'PaymentAsia-链接收款',
label: 'PaymentAsia-链接收款'
},
{
value: 'Stripe-Link平台',
label: 'Stripe-Link平台'
},
{
value: 'PaymentAsia-Link平台',
label: 'PaymentAsia-Link平台'
},
{
value: 'FirstData-Link平台-Link平台',
label: 'FirstData-Link平台-Link平台'
},
{
value: 'IOS-Link平台',
label: 'IOS-Link平台'
},
{
value: 'Ipay88-Link平台',
label: 'Ipay88-Link平台'
}
])
const handleImageChange = (file) => {
uploadRef.value.submit()
}
const beforeUpload = (file) => {
const isJPG = file.type === 'image/jpeg'
const isPNG = file.type === 'image/png'
const isLt1 = file.size / 1024 < 1024
if (!isJPG && !isPNG) {
ElMessage.error('上传图片只能是 JPG 或 PNG 格式')
return false
}
if (!isLt1) {
ElMessage.error('上传图片大小不能超过1MB')
return false
}
return true
}
const handleUploadSuccess = (response, file, fileList) => {
try {
addForm.value.imageUrl = response.data.url
console.log('==========================', response.data.url)
} catch (error) {
console.log('看看报错信息', error)
ElMessage.error(response.data.msg || '图片上传失败')
}
}
const handleUploadError = (error) => {
console.error('上传失败:', error)
addForm.value.imageUrl = null
ElMessage.error('图片上传失败,请重试')
}
const customUpload = async (options) => {
try {
const formData = new FormData()
formData.append('file', options.file)
const response = await API({
url: uploadUrl,
method: 'POST',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
if (response.code === 200 && response.data) {
handleUploadSuccess(response, options.file, [options.file])
ElMessage.success(response.msg || '上传成功')
} else {
ElMessage.error(response.msg || '上传失败')
}
} catch (error) {
console.error('上传错误:', error)
ElMessage.error(`上传失败: ${error.msg || error.message || '网络异常'}`)
}
}
</script>
<style scoped lang="scss">
.condition1 {
width: 82vw;
display: flex;
height: 4vh;
.condition-item {
width: 19%;
display: flex;
align-items: center;
margin-bottom: 1vh;
margin-right: 0.5vw;
}
.condition-item2 {
width: 19%;
display: flex;
align-items: center;
margin-right: 0.5vw;
}
}
.addDialog {
.left {
width: 50%;
height: 60vh;
.dialog-item {
display: flex;
align-items: center;
margin-bottom: 1vh;
}
}
.right {
width: 50%;
height: 50vh;
.dialog-item {
display: flex;
align-items: center;
margin-bottom: 1vh;
}
}
}
.audit1 {
height: 47vh;
.dialog-item {
display: flex;
align-items: center;
margin-bottom: 1vh;
}
.audit-btn {
display: flex;
justify-content: center;
align-items: center;
}
}
.audit2{
}
</style>

33
src/views/permissions/permissions.vue

@ -1,19 +1,5 @@
<template>
<div class="father">
<el-button-group>
<el-button :type="activeTab === 'userPermission' ? 'primary' : 'default'" @click="navigateTo('userPermission')"
:disabled="!hasDetail">
用户管理
</el-button>
<el-button :type="activeTab === 'rolePermission' ? 'primary' : 'default'" @click="navigateTo('rolePermission')"
:disabled="!hasBalance">
角色管理
</el-button>
</el-button-group>
</div>
<div>
<router-view></router-view>
</div>
</template>
<script setup>
@ -28,8 +14,8 @@ const adminStore = useAdminStore();
const { menuTree } = storeToRefs(adminStore);
const activeTab = ref('');
const hasDetail = ref(false);
const hasBalance = ref(false);
const hasUser = ref(false);
const hasRole = ref(false);
//
const navigateTo = (name) => {
activeTab.value = name;
@ -41,15 +27,16 @@ const navigateTo = (name) => {
const initPermissions = () => {
if (!menuTree.value || !menuTree.value.length) return;
hasDetail.value = hasMenuPermission(menuTree.value, permissionMapping.View_Permission);
hasBalance.value = hasMenuPermission(menuTree.value, permissionMapping.View_Role);
hasUser.value = hasMenuPermission(menuTree.value, permissionMapping.userManage);
hasRole.value = hasMenuPermission(menuTree.value, permissionMapping.roleManage);
console.log('====================================', hasUser.value, hasRole.value)
};
//
const getDefaultAuditRoute = () => {
initPermissions();
if (hasDetail.value) return 'userPermission';
if (hasBalance.value) return 'rolePermission';
if (hasUser.value) return 'userPermission';
if (hasRole.value) return 'rolePermission';
return 'userPermission';
};
@ -79,9 +66,3 @@ onMounted(() => {
}
});
</script>
<style>
.father{
width:82vw;
height:4vh;
}
</style>

78
src/views/permissions/rolePermission.vue

@ -39,18 +39,12 @@ const addRole = ref({
market: ''
})
const addRoleMarket = ref([])
const viewRole = ref([])
const getRolePermission = async function () {
const result = await request({
url: '/menu/tree',
data: {
"id": adminData.value.roleId
}
})
viewRole.value = collectIds(result.data)
console.log('result111', viewRole.value);
}
const getRoleList = async function (val) {
if(!findMenuById(menuTree.value, permissionMapping.roleManageShow)){
ElMessage.error('无此权限')
return
}
try {
if (typeof val === 'number') {
getObj.value.pageNum = val
@ -178,6 +172,10 @@ function processTreeData(data) {
}
const handleAddRole = async function () {
if(!findMenuById(menuTree.value, permissionMapping.addRoleInfo)){
ElMessage.error('无此权限')
return
}
try {
await new Promise((resolve, reject) => {
Ref.value.validate((valid) => {
@ -242,23 +240,10 @@ const getLists = async function () {
data: { id: roleId }
})
data.value = res.data
// let originalData = res.data.filter(item => item.id !== 9);
//
// data.value = filterGoldenBeanMenus(originalData);
// data.value = data.value.filter(item => item.id !== 9);
data.value = data.value.filter(item => item.id !== 9);
data.value = filterPermission(data.value)
console.log('看看data', data.value)
console.log('parentID:', addRole.value.parentId, 'roleId:', roleId)
/* // 根据地区过滤金豆菜单
if (addRole.value.market !== '总部') {
//
originalData = filterGoldenBeanMenus(originalData);
}
data.value = originalData; // */
if (addRole.value.parentId && addRole.value.parentId !== 2) {
const result = await API({
url: '/general/roleMarket',
@ -292,11 +277,15 @@ const getLists = async function () {
//
const goldenBeanMenuIds = new Set([
43, 55, 54, // ->
41, 47, 46, 48, // ->
42, 50, 49, 52, 51, // ->
45, 53 // ->
permissionMapping.beanAudit,
permissionMapping.beanRecharge,
permissionMapping.beanConsume,
permissionMapping.beanDetail
])
// 15,43,44,45,46,47,//
// 18,52,53,54,//
// 20,57,58,59,60,//
// 23//
const filterGoldenBeanMenus = (tree) => {
return tree
.filter(item => {
@ -311,6 +300,18 @@ const filterGoldenBeanMenus = (tree) => {
return true
})
}
// (????????)
const filterPermission = (tree) => {
return tree.filter(item =>{
if(item.id === permissionMapping.permissionManage){
return false
}
else if(item.children && item.children.length > 0){
item.children = filterPermission(item.children)
}
return true
})
}
//
const handleEditRolePermissionCheck = (checkedNodes, checkedInfo) => {
const { checkedKeys, checkedNodes: allCheckedNodes } = checkedInfo
@ -438,8 +439,8 @@ const permissionEditRoleInit = async function (row) {
data: { id: roleId }
});
data.value = res.data;
data.value = data.value.filter(item => item.id !== 9);
data.value = filterGoldenBeanMenus(data.value);
data.value = filterPermission(data.value)
//data.value = filterGoldenBeanMenus(data.value);
// id
if (row.tree && row.tree.length > 0) {
@ -467,6 +468,10 @@ const permissionEditRoleInit = async function (row) {
//
const permissionEditRole = async function () {
if(!findMenuById(menuTree.value, permissionMapping.editRoleInfo)){
ElMessage.error('无此权限')
return
}
try {
await new Promise((resolve, reject) => {
Ref.value.validate((valid) => {
@ -569,11 +574,12 @@ const Rolerules = reactive({
const throttledHandleAddRole = _.throttle(handleAddRole, 5000, {
trailing: false
})
const canLook = findMenuById(menuTree.value,permissionMapping.roleManageShow)
const canAdd = findMenuById(menuTree.value, permissionMapping.addRoleInfo)
const canEdit = findMenuById(menuTree.value, permissionMapping.editRoleInfo)
//
onMounted(async function () {
await getRoleList()
await getRolePermission()
await getRoleArea()
})
@ -586,14 +592,14 @@ onMounted(async function () {
<el-input v-model="role.name" style="width: 240px" placeholder="请输入角色名称" clearable />
<div style="margin-left: auto;">
<el-button type="success" @click="reset()">重置</el-button>
<el-button type="primary" @click="searchRole()">查询</el-button>
<el-button type="primary" @click="searchRole()" :disabled="!canLook" v-if="canLook">查询</el-button>
</div>
</div>
</el-card>
<el-card>
<div class="add-item">
<el-button style="color: #048efb; border: 1px solid #048efb" @click="permissionAddInit()">新增角色</el-button>
<el-button style="color: #048efb; border: 1px solid #048efb" @click="permissionAddInit()" :disabled="!canAdd" v-if="canAdd">新增角色</el-button>
</div>
<div>
<el-table :data="roleData" style="width: 100%" show-overflow-tooltip>
@ -620,7 +626,7 @@ onMounted(async function () {
</el-table-column>
<el-table-column prop="operation" label="操作" width="200px">
<template #default="scope">
<el-button type="warning" text @click="permissionEditRoleInit(scope.row)" :disabled="(scope.row.id === 2)|| (scope.row.id === 1)">
<el-button type="warning" text @click="permissionEditRoleInit(scope.row)" :disabled="(scope.row.id === 2)|| (scope.row.id === 1) || !canEdit" v-if="canEdit">
编辑
</el-button>
</template>

73
src/views/permissions/userPermission.vue

@ -5,6 +5,12 @@ import { InfoFilled } from '@element-plus/icons-vue'
import _ from 'lodash'
import request from '@/util/http'
import API from '@/util/http'
import { useAdminStore } from "@/store/index.js"
import { storeToRefs } from "pinia"
import { findMenuById, permissionMapping } from "@/utils/menuTreePermission.js"
const adminStore = useAdminStore()
const { adminData, menuTree } = storeToRefs(adminStore)
//
const tableData = ref([])
const total = ref(100)
@ -97,20 +103,6 @@ const addAdmin = ref({
machineId: '',
remark: ''
})
//
const adminData = ref({})
// todo
const getAdminData = async function () {
try {
const result = await API({ url: '/admin/userinfo', data: {} })
adminData.value = result
// console.log('', result)
console.log('管理员用户信息', adminData.value)
} catch (error) {
console.log('请求失败', error)
}
}
//
const permissionEditObj = ref({
checkedKeys: [],
@ -204,7 +196,6 @@ const getRoleArea = async function () {
console.log('请求失败', error)
}
}
//
const marketsTree = ref([])
const getArea = async function () {
@ -275,6 +266,10 @@ const userAddInit = function () {
//
const permissionAdd = async function () {
if (!findMenuById(menuTree.value, permissionMapping.addUserInfo)) {
ElMessage.error('无此权限')
return
}
try {
await new Promise((resolve, reject) => {
Ref.value.validate((valid) => {
@ -327,7 +322,6 @@ const Ref = ref(null)
//
const permissionList = ref([])
const getRoles = async function () {
try {
const res = await API({ url: '/role/selectAll' })
@ -341,8 +335,6 @@ const getRoles = async function () {
console.error('获取权限列表失败:', error)
}
}
//
const openUserEditVisible = function () {
userEditVisible.value = true
@ -504,6 +496,10 @@ const editAdminRules = {
};
//
const permissionEdit = async function () {
if (!findMenuById(menuTree.value, permissionMapping.editUserInfo)) {
ElMessage.error('无此权限')
return
}
let { adminName: userName, roleName: oldRole, roleId: newRoleId } = permissionEditObj.value;
if (oldRole == null) {
oldRole = '暂未分配角色'
@ -576,6 +572,10 @@ const del = function (row) {
}
//
const delConfirm = async function () {
if (!findMenuById(menuTree.value, permissionMapping.deleteUserInfo)) {
ElMessage.error('无此权限')
return
}
try {
const result = await request({
url: '/permission/deleteAdmin',
@ -594,6 +594,10 @@ const delConfirm = async function () {
}
//
const editStatus = async function (row) {
if (!findMenuById(menuTree.value, permissionMapping.changeStatus)) {
ElMessage.error('无此权限')
return
}
const { id, account, targetStatus, ...restRow } = currentStatusRow.value
try {
@ -783,6 +787,10 @@ const resetPassword = function (row) {
//
const confirmResetPassword = async function () {
if (!findMenuById(menuTree.value, permissionMapping.updateUserInfo)) {
ElMessage.error('无此权限')
return
}
console.log('adminData', adminData.value)
// markets
if (typeof adminData.value.markets === 'string' && adminData.value.markets) {
@ -883,16 +891,21 @@ const throttledPermissionEdit = _.throttle(permissionEdit, 5000, {
const throttledPermissionAdd = _.throttle(permissionAdd, 5000, {
trailing: false
})
const canLook = findMenuById(menuTree.value, permissionMapping.userManageShow)//
const canAdd = findMenuById(menuTree.value, permissionMapping.addUserInfo)
const canEdit = findMenuById(menuTree.value, permissionMapping.editUserInfo)
const canReset = findMenuById(menuTree.value, permissionMapping.updateUserInfo)
const canDel = findMenuById(menuTree.value, permissionMapping.deleteUserInfo)
const change = findMenuById(menuTree.value, permissionMapping.changeStatus)
//
onMounted(async function () {
await getAdminData()
await getPermission()
await getArea()
await getStore()
await getRoles()
await getRoleArea()
})
console.log('看看权限', canAdd, canEdit, canReset, canDel, change)
})
</script>
<template>
@ -918,14 +931,14 @@ onMounted(async function () {
<div class="head-card-btn">
<el-button type="success" @click="reset()">重置</el-button>
<el-button type="primary" @click="search()">查询</el-button>
<el-button type="primary" @click="search()" v-if="canLook">查询</el-button>
</div>
</div>
</el-card>
<el-card>
<!-- 展示表单 -->
<div class="add-item">
<el-button style="color: #048efb; border: 1px solid #048efb" @click="userAddInit()">新增用户</el-button>
<el-button style="color: #048efb; border: 1px solid #048efb" :disabled="!canAdd" v-if="canAdd" @click="userAddInit()">新增用户</el-button>
</div>
<div>
@ -947,17 +960,17 @@ onMounted(async function () {
<el-table-column prop="remark" label="备注" />
<el-table-column prop="operation" label="操作" width="280px">
<template #default="scope">
<el-button type="warning" text @click="resetPassword(scope.row)">
<el-button type="warning" text :disabled="!canReset" @click="resetPassword(scope.row)" v-if="canReset">
重置密码
</el-button>
<el-button type="primary" text @click="permissionEditInit(scope.row)"
:disabled="scope.row.adminStatus === 0 || scope.row.account === adminData.account">
<el-button type="primary" text @click="permissionEditInit(scope.row)" v-if="canEdit"
:disabled="!canEdit || scope.row.adminStatus === 0 || scope.row.account === adminData.account">
修改权限
</el-button>
<el-popconfirm title="确定将此用户删除吗?" @confirm="delConfirm">
<template #reference>
<el-button type="danger" text @click="del(scope.row)"
:disabled="scope.row.adminStatus === 0 || scope.row.account === adminData.account">
<el-button type="danger" text @click="del(scope.row)" v-if="canDel"
:disabled="!canDel ||scope.row.adminStatus === 0 || scope.row.account === adminData.account">
删除
</el-button>
</template>
@ -972,8 +985,8 @@ onMounted(async function () {
</el-table-column>
<el-table-column prop="adminStatus" label="状态">
<template #default="scope">
<el-switch :model-value="scope.row.adminStatus" :active-value="1" :inactive-value="0" size="large"
:disabled="scope.row.account === adminData.account || statusLoading[scope.row.id]"
<el-switch :model-value="scope.row.adminStatus" :active-value="1" :inactive-value="0" size="large" v-if="change"
:disabled="!change || scope.row.account === adminData.account || statusLoading[scope.row.id]"
@change="(targetStatus) => showStatusConfirm(scope.row, targetStatus)" style="
--el-switch-on-color: #13ce66;
--el-switch-off-color: #ff4949;

12
src/views/recharge/bean/beanRecharge.vue

@ -2,15 +2,15 @@
<div class="father">
<el-button-group>
<el-button :type="activeTab === 'addBeanRecharge' ? 'primary' : 'default'" @click="navigateTo('addBeanRecharge')"
:disabled="!hasAdd" style="width: 6.5vw">
v-if="hasAdd" style="width: 6.5vw">
新增充值
</el-button>
<el-button :type="activeTab === 'beanSystemRecharge' ? 'primary' : 'default'"
@click="navigateTo('beanSystemRecharge')" :disabled="!hasSystem" style="width: 6.5vw;">
@click="navigateTo('beanSystemRecharge')" v-if="hasSystem" style="width: 6.5vw;">
系统充值
</el-button>
<el-button :type="activeTab === 'beanOnlineRecharge' ? 'primary' : 'default'"
@click="navigateTo('beanOnlineRecharge')" :disabled="!hasOnline" style="width: 6.5vw;">
@click="navigateTo('beanOnlineRecharge')" v-if="hasOnline" style="width: 6.5vw;">
线上充值
</el-button>
</el-button-group>
@ -46,9 +46,9 @@ const navigateTo = (name) => {
const initPermissions = () => {
if (!menuTree.value || !menuTree.value.length) return;
hasAdd.value = hasMenuPermission(menuTree.value, permissionMapping.Submit_Golden_Bean_Recharge);
hasSystem.value = hasMenuPermission(menuTree.value, permissionMapping.View_Golden_Bean_System_Recharge_Details);
hasOnline.value = hasMenuPermission(menuTree.value, permissionMapping.View_Golden_Bean_Online_Recharge_Details);
hasAdd.value = hasMenuPermission(menuTree.value, permissionMapping.addBeanRecharge);
hasSystem.value = hasMenuPermission(menuTree.value, permissionMapping.beanSystemRecharge);
hasOnline.value = hasMenuPermission(menuTree.value, permissionMapping.beanOnlineRecharge);
};
//

8
src/views/recharge/gold/coinRecharge.vue

@ -2,11 +2,11 @@
<div style="height:4vh;">
<el-button-group>
<el-button :type="activeTab === 'coinRechargeDetail' ? 'primary' : 'default'"
@click="navigateTo('coinRechargeDetail')" :disabled="!hasDetail">
@click="navigateTo('coinRechargeDetail')" v-if="hasDetail">
金币充值明细
</el-button>
<el-button :type="activeTab === 'addCoinRecharge' ? 'primary' : 'default'" @click="navigateTo('addCoinRecharge')"
:disabled="!hasAdd">
v-if="hasAdd">
新增充值
</el-button>
</el-button-group>
@ -40,8 +40,8 @@ const navigateTo = (name) => {
const initPermissions = () => {
if (!menuTree.value || !menuTree.value.length) return;
hasAdd.value = hasMenuPermission(menuTree.value, permissionMapping.Submit_Gold_Coin_Recharge);
hasDetail.value = hasMenuPermission(menuTree.value, permissionMapping.View_Gold_Coin_Recharge_Details);
hasAdd.value = hasMenuPermission(menuTree.value, permissionMapping.addCoinRecharge);
hasDetail.value = hasMenuPermission(menuTree.value, permissionMapping.coinRechargeDetail);
};
//

9
src/views/refund/gold/addCoinRefund.vue

@ -111,6 +111,11 @@ const getRefund = async function () {
}
// 退
const add = async function () {
//
if (!findMenuById(menuTree.value, permissionMapping.addCoinRefund)) {
ElMessage.error('无此权限')
return
}
try {
if (addRefund.value.refundType === '商品退款') {
addRefund.value.type = 1
@ -408,7 +413,7 @@ const calculatedRechargeGoods = computed(() => {
const taskGold = addRefund.value.taskGold === '' ? 0 : +addRefund.value.taskGold;
return Number((permanentGold + freeGold + taskGold).toFixed(2));
})
const canAdd = findMenuById(menuTree.value,permissionMapping.addCoinRefund)
watch(calculatedRechargeGoods, (newVal) => {
addRefund.value.sumGold = newVal
})
@ -497,7 +502,7 @@ onMounted(() =>{
</div>
<el-button type="success" @click="cancel()" style="margin-left: 200px">重置</el-button>
<el-button type="primary" :disabled="addDisabled" @click="addBefore"> 提交</el-button>
<el-button type="primary" :disabled="addDisabled" @click="addBefore" v-if="canAdd"> 提交</el-button>
</el-form>
</div>
</div>

726
src/views/refund/gold/addNewCoinRefund.vue

@ -1,726 +0,0 @@
<script setup>
import {onMounted, reactive, ref, watch} from "vue";
import {ElMessage} from "element-plus";
import moment from "moment";
import request from "@/util/http.js";
import Cookies from 'js-cookie';
import {useAdminStore} from "@/store/index.js";
import {storeToRefs} from "pinia";
import {WarnTriangleFilled} from "@element-plus/icons-vue";
import dayjs from "dayjs";
const adminStore = useAdminStore();
const { adminData, menuTree } = storeToRefs(adminStore);
const addDisabled = ref(false);
const user = ref({
jwcode: null,
name: "",
market: "",
historySumGold: null,
historyPermanentGold: null,
historyFreeGold: null,
historyTaskGold: null,
rechargeNum: null,
consumeNum: null,
firstRecharge: "",
nowPermanentGold: null,
nowFreeJune: null,
nowTaskGold: null,
nowFreeDecember: null,
nowFreeGold: null,
nowSumGold: null
});
const addConsume = ref({
jwcode: null,
goodsName: "金币退款",
sumGold: null,
freeGold: null,
permanentGold: null,
taskGold: null,
remark: "",
refundModel: null,
adminId: adminData.value.id,
adminName: adminData.value.adminName
});
const Ref = ref(null);
const rules = reactive({
jwcode: [
{ required: true, message: "请输入精网号", trigger: "blur" },
],
remark: [
{ required: true, message: "请输入备注", trigger: "blur" },
],
permanentGold: [
{ required: true, message: "请输入永久金币数", trigger: "blur" },
{
validator: (rule, value, callback) => {
if (value === null || value === undefined || !Number.isInteger(value) || value < 0) {
callback(new Error("永久金币必须为非负整数"));
} else {
callback();
}
},
trigger: "blur"
}
],
freeGold: [
{ required: true, message: "请输入免费金币数", trigger: "blur" },
{
validator: (rule, value, callback) => {
if (value === null || value === undefined || !Number.isInteger(value) || value < 0) {
callback(new Error("免费金币必须为非负整数"));
} else {
callback();
}
},
trigger: "blur"
}
],
taskGold: [
{ required: true, message: "请输入任务金币数", trigger: "blur" },
{
validator: (rule, value, callback) => {
if (value === null || value === undefined || !Number.isInteger(value) || value < 0) {
callback(new Error("任务金币必须为非负整数"));
} else {
callback();
}
},
trigger: "blur"
}
],
sumGold: [
{
validator: (rule, value, callback) => {
if ((value || 0) <= 0) {
callback(new Error("三类金币总数必须大于0"));
} else {
callback();
}
},
trigger: "blur"
}
]
});
// watch
watch(
() => [addConsume.value.freeGold, addConsume.value.permanentGold, addConsume.value.taskGold],
([free, permanent, task]) => {
const sum = (parseInt(free) || 0) + (parseInt(permanent) || 0) + (parseInt(task) || 0);
addConsume.value.sumGold = sum;
},
{ deep: true }
);
// sumGold > 0
function validateInputToFreeGold() {
if (!user.value.jwcode) {
ElMessage.warning("请先查询用户信息");
resetForm()
return false;
}
const { freeGold, permanentGold, taskGold, sumGold } = addConsume.value;
if(freeGold > user.value.nowFreeGold){
ElMessage.warning("该用户免费金币不足,请重新输入!");
addConsume.value.freeGold = null;
return
}
return true;
}
// sumGold > 0
function validateInputToPermanentGold() {
if (!user.value.jwcode) {
ElMessage.warning("请先查询用户信息");
resetForm()
return false;
}
const { freeGold, permanentGold, taskGold, sumGold } = addConsume.value;
if(permanentGold > user.value.nowPermanentGold){
ElMessage.warning("该用户永久金币不足,请重新输入!");
addConsume.value.permanentGold = null;
return
}
return true;
}
// sumGold > 0
function validateInputToTaskGold() {
if (!user.value.jwcode) {
ElMessage.warning("请先查询用户信息");
resetForm()
return false;
}
const { freeGold, permanentGold, taskGold, sumGold } = addConsume.value;
if(taskGold > user.value.nowTaskGold){
ElMessage.warning("该用户任务金币不足,请重新输入!");
addConsume.value.taskGold = null;
return
}
return true;
}
// sumGold > 0
function validateInputTOSumGold() {
if (!user.value.jwcode) {
ElMessage.warning("请先查询用户信息");
resetForm()
return false;
}
const { freeGold, permanentGold, taskGold, sumGold } = addConsume.value;
if (sumGold <= 0) {
ElMessage.warning("三类金币总数必须大于0");
resetForm()
return false;
}
return true;
}
//
const ConsumeDialogVisible = ref(false);
//
const ConsumeDialogVisiblehandleClose = () => {
ConsumeDialogVisible.value = false;
//
resetForm()
user.value = {}
};
``
// 使cookie
const ConsumeDialogVisibleContinue = () => {
ConsumeDialogVisible.value = false;
add();
};
const ConsumeDialogVisibleCancel = () => {
ConsumeDialogVisible.value = false
resetForm()
user.value = {}
};
//
//
const FirstConsumeDialogVisible = ref(false);
//
const FirstConsumeDialogVisiblehandleClose = () => {
FirstConsumeDialogVisible.value = false;
//
resetForm()
user.value = {}
};
//
const FirstConsumeDialogVisibleContinue = () => {
FirstConsumeDialogVisible.value = false;
add();
};
const FirstConsumeDialogVisibleCancel = () => {
FirstConsumeDialogVisible.value = false
resetForm()
user.value = {}
};
//
const goods = ref([]);
// cookie key
const WriteCookies = ref(null)
// cookie value
const WriteCookiesTime = ref(null)
// cookie key
const ReadCookies = ref(null)
// cookie value
const ReadCookiesTime = ref(null)
//
const add = async function () {
addDisabled.value = true;
try {
const result = await request({
url: "/consume/addRefund",
data: {
jwcode: addConsume.value.jwcode,
adminId: adminData.value.id,
sumGold: addConsume.value.sumGold *100 ,
freeGold: addConsume.value.freeGold *100 ,
taskGold: addConsume.value.taskGold *100 ,
permanentGold: addConsume.value.permanentGold *100 ,
remark: addConsume.value.remark,
adminName: adminData.value.adminName
}
});
// jwcode
// jwcode:permanentGold:freeGold
WriteCookies.value = `coinRefund:${addConsume.value.jwcode}:${addConsume.value.goodsName}`
//value
WriteCookiesTime.value = dayjs().format("YYYY-MM-DD HH:mm:ss");
// cookies,jwcodekeyvaluejwcode1
Cookies.set(WriteCookies.value, WriteCookiesTime.value, {
expires:
1, path: '/'
});
addDisabled.value = false;
if (result.code === 200) {
ElMessage.success("添加成功");
resetForm();
} else {
ElMessage.error(result.msg || "添加失败");
}
} catch (err) {
console.error(err);
ElMessage.error("添加失败,请检查网络");
addDisabled.value = false;
}
};
//
function resetForm() {
Ref.value.resetFields();
addConsume.value.sumGold = null;
addConsume.value.freeGold = null;
addConsume.value.permanentGold = null;
addConsume.value.taskGold = null;
addConsume.value.sumGold = null;
addConsume.value.remark = "";
addConsume.value.jwcode = null;
user.value = {
jwcode: null,
name: "",
market: "",
historySumGold: null,
historyPermanentGold: null,
historyFreeGold: null,
historyTaskGold: null,
rechargeNum: null,
consumeNum: null,
firstRecharge: "",
nowPermanentGold: null,
nowFreeJune: null,
nowTaskGold: null,
nowFreeDecember: null,
nowFreeGold: null,
nowSumGold: null
};
}
//
const getGoods = async function () {
try {
const result = await request({ url: "https://api.homilychart.com/live_mall/api/product/all" });
goods.value = result.data.map(item => ({ id: item.id, label: item.name, value: item.name }));
} catch (err) {
console.error(err);
}
}
//
const addBefore = () => {
Ref.value.validate(async (valid) => {
// cookie
if (!valid) {
ElMessage({
type: 'error',
message: '请检查输入内容'
});
return;
}
// sumGold > 0
if (!validateInputTOSumGold()) return;
ReadCookies.value = `coinRefund:${addConsume.value.jwcode}:${addConsume.value.goodsName}`
// cookie
const cookie = Cookies.get(ReadCookies.value)
console.log("cookie++++++++++++++++++++++", cookie)
console.log("time", WriteCookiesTime.value)
//
ReadCookiesTime.value = moment(cookie).format('YYYY-MM-DD HH:mm:ss')
console.log("cookie========", cookie)
if (cookie) {
ConsumeDialogVisible.value = true;
} else {
FirstConsumeDialogVisible.value = true;
}
});
};
//
const getUser = async function (jwcode) {
if (!jwcode || !/^\d{1,9}$/.test(jwcode)) {
ElMessage.warning("精网号不能为空或格式不正确");
return;
}
try {
const result = await request({ url: "/user/selectUser", data: { jwcode } });
if (result.code === 200 && result.data) {
user.value = { ...result.data };
ElMessage.success("查询成功");
} else {
ElMessage.warning(result.msg || "用户不存在");
}
} catch (err) {
console.error(err);
ElMessage.error("查询失败,请检查网络或精网号");
}
};
onMounted(async function () {
await getGoods();
});
</script>
<template>
<div class="father1">
<div class="left">
<el-form :model="addConsume" ref="Ref" :rules="rules" style="min-width: 420px;" class="add-form"
label-width="auto" label-position="right">
<el-form-item prop="jwcode" label="精网号" style="margin-top: 50px">
<el-input v-model="addConsume.jwcode" style="width: 200px;" />
<el-button type="primary" @click="getUser(addConsume.jwcode)" style="margin-left: 20px">查询
</el-button>
</el-form-item>
<el-form-item prop="goodsName" label="商品名称" >
<el-input v-model="addConsume.goodsName" disabled style="width: 120px" />
</el-form-item>
<!-- 三类金币可编辑 -->
<el-form-item prop="permanentGold" label="永久金币">
<el-input v-model.number="addConsume.permanentGold" style="width: 120px" @input="validateInputToPermanentGold()"/>
<p>&nbsp;&nbsp;</p>
</el-form-item>
<el-form-item prop="freeGold" label="免费金币">
<el-input v-model.number="addConsume.freeGold" style="width: 120px" @input="validateInputToFreeGold()" />
<p>&nbsp;&nbsp;</p>
</el-form-item>
<el-form-item prop="taskGold" label="任务金币">
<el-input v-model.number="addConsume.taskGold" style="width: 120px" @input="validateInputToTaskGold()" />
<p>&nbsp;&nbsp;</p>
</el-form-item>
<el-form-item prop="sumGold" label="消耗金币总数">
<el-input v-model.number="addConsume.sumGold" style="width: 120px" disabled />
</el-form-item>
<el-form-item prop="remark" label="备注">
<el-input v-model="addConsume.remark" style="width: 250px" :rows="4" maxlength="100" show-word-limit
type="textarea" />
</el-form-item>
<el-button type="success" @click="resetForm()" style="margin-left: 200px;margin-top:10px">重置</el-button>
<el-button type="primary" :disabled="addDisabled" @click="addBefore" style="margin-top:10px"> 提交</el-button>
</el-form>
</div>
<div class="right">
<!-- 客户信息栏 -->
<el-card v-if="user.jwcode" style="width: 800px; float: right" class="customer-info">
<el-form :model="user" label-width="auto" style="max-width: 1000px" label-position="left">
<el-text size="large" style="margin-left: 20px">客户信息</el-text>
<!-- 第一行姓名 + 历史金币 -->
<el-row style="margin-top: 20px">
<el-col :span="9">
<el-form-item label="姓名">
<p>{{ user.name }}</p>
</el-form-item>
</el-col>
<el-col :span="14">
<el-form-item label="当前金币总数" style="width: 500px">
<span style="color: #2fa1ff; margin-right: 5px" v-if="user.nowSumGold !== undefined">{{
user.nowSumGold
}}</span>
</el-form-item>
<!-- 金币详情独立显示 -->
<el-form-item style="margin-top: -23px"> <!-- 负边距减少间距 -->
<span style="color: #b1b1b1; margin-left: 0px" v-if="user.nowPermanentGold !== undefined">(永久金币:{{
user.nowPermanentGold
}};
免费金币:{{ user.nowFreeGold }};
任务金币:{{ user.nowTaskGold }})</span>
</el-form-item>
</el-col>
</el-row>
<!-- 第二行精网号 + 当前金币独立行 -->
<el-row>
<el-col :span="9">
<el-form-item label="精网号">
<p>{{ user.jwcode }}</p>
</el-form-item>
</el-col>
<el-col :span="14">
<el-form-item label="消费次数">
<p style="color: #2fa1ff">{{ user.consumeNum }} </p>
</el-form-item>
<el-form-item style="margin-top: -23px"> <!-- 负边距减少间距 -->
<p style="font-size: small; color: #b1b1b1">(仅统计2025-01-01后的数据)</p>
</el-form-item>
</el-col>
</el-row>
<!-- 第三行首次充值日期 + 充值次数 -->
<!-- <el-row >
<el-col :span="9">
<el-form-item label="首次充值日期">
<p v-if="user.firstRecharge">
{{ moment(user.firstRecharge).format('YYYY-MM-DD HH:mm:ss') }}
</p>
</el-form-item>
</el-col>
</el-row> -->
<!-- 第四行消费次数 + 所属门店 -->
<el-row>
<el-col :span="9">
<el-form-item label="所属门店">
<p>{{ user.market }}</p>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<el-dialog v-model="FirstConsumeDialogVisible" title="操作确认" :before-close="FirstConsumeDialogVisiblehandleClose"
:close-on-click-modal="false" width="480px">
<!-- 内容整体居中且收窄 -->
<div class="confirm-body">
<!-- 用户信息 -->
<div>
<div class="field-label">用户信息</div>
<el-input :model-value="user.jwcode + (user.name ? '' + user.name + '' : '')" disabled />
</div>
<!-- 活动名称 -->
<div class="field">
<div class="field-label">商品名称</div>
<el-input v-model="addConsume.goodsName" disabled />
</div>
<!--金币总数 -->
<div class="field">
<div class="field-label">金币总数</div>
<el-input v-model="addConsume.sumGold" disabled />
</div>
<!-- 金币详细信息同一行左右排列 -->
<el-row :gutter="20" class="coins-row">
<el-col :span="8">
<div class="field">
<div class="field-label">永久金币</div>
<el-input v-model="addConsume.permanentGold" disabled />
</div>
</el-col>
<el-col :span="8">
<div class="field">
<div class="field-label">免费金币</div>
<el-input v-model="addConsume.freeGold" disabled />
</div>
</el-col>
<el-col :span="8">
<div class="field">
<div class="field-label">任务金币</div>
<el-input v-model="addConsume.taskGold" disabled />
</div>
</el-col>
</el-row>
<div class="field">
<div class="field-label">备注</div>
<el-input v-model="addConsume.remark" disabled />
</div>
</div>
<!-- 底部按钮居中 -->
<template #footer>
<div class="dialog-footer-center">
<el-button @click="FirstConsumeDialogVisibleCancel"> </el-button>
<el-button type="primary" @click="FirstConsumeDialogVisibleContinue">确认退款</el-button>
</div>
</template>
</el-dialog>
<el-dialog v-model="ConsumeDialogVisible" title="操作确认" :before-close="ConsumeDialogVisiblehandleClose"
:close-on-click-modal="false" width="480px">
<!-- 内容整体居中且收窄 -->
<div class="confirm-body">
<!-- 用户信息 -->
<div>
<div class="field-label">用户信息</div>
<el-input :model-value="user.jwcode + (user.name ? '' + user.name + '' : '')" disabled />
</div>
<!-- 活动名称 -->
<div class="field">
<div class="field-label">商品名称</div>
<el-input v-model="addConsume.goodsName" disabled />
</div>
<!--金币总数 -->
<div class="field">
<div class="field-label">金币总数</div>
<el-input v-model="addConsume.sumGold" disabled />
</div>
<!-- 金币详细信息同一行左右排列 -->
<el-row :gutter="20" class="coins-row">
<el-col :span="8">
<div class="field">
<div class="field-label">永久金币</div>
<el-input v-model="addConsume.permanentGold" disabled />
</div>
</el-col>
<el-col :span="8">
<div class="field">
<div class="field-label">免费金币</div>
<el-input v-model="addConsume.freeGold" disabled />
</div>
</el-col>
<el-col :span="8">
<div class="field">
<div class="field-label">任务金币</div>
<el-input v-model="addConsume.taskGold" disabled />
</div>
</el-col>
</el-row>
<!-- 风险提示 -->
<div style="display: flex; align-items: center; margin-top: 20px;">
<el-icon :size="24" color="#FFD700">
<WarnTriangleFilled />
</el-icon>
<p>重复购买风险提示</p>
</div>
<!-- 记录 + 虚线分隔 -->
<div>
<el-divider border-style="dashed" />
<p>检测到该用户近期有相似退款记录</p>
· {{ ReadCookiesTime }} 退款 {{ addConsume.goodsName }}(操作人: {{ adminData.adminName }})
</div>
<div style="margin-top: 10px">
<p>是否继续操作</p>
</div>
</div>
<!-- 底部按钮居中 -->
<template #footer>
<div class="dialog-footer-center">
<el-button @click="ConsumeDialogVisibleCancel"> </el-button>
<el-button type="primary" @click="ConsumeDialogVisibleContinue">确认退款</el-button>
</div>
</template>
</el-dialog>
</div>
</div>
</template>
<style scoped lang="scss">
p {
margin: 0px;
}
/* 上传图片的格式 */
.avatar-uploader .avatar {
width: 50px;
height: 50px;
display: block;
}
.add-form {
width: 400px;
float: left;
}
/* 标题居中 */
.el-dialog__header {
text-align: center;
}
.confirm-body {
width: 350px;
margin: 0 auto;
}
/* 字段块与标签样式 */
.field {
margin-bottom: 14px;
}
.field-label {
font-size: 14px;
color: #606266;
margin-bottom: 6px;
}
/* 金币行紧凑 */
.coins-row .field {
margin-bottom: 0;
}
/* 底部按钮居中 */
.dialog-footer-center {
display: flex;
justify-content: center;
gap: 12px;
}
.father1 {
width: 82vw;
height: 80vh;
display: flex;
.left {
width: 500px;
float: left;
display: flex;
}
.right {
flex: 1;
height: 50vh;
display: flex;
align-items: center;
.customer-info {
width: 300px;
margin-left: 20px;
display: flex;
justify-content: center;
align-items: center;
}
}
}
</style>

12
src/views/refund/gold/coinRefund.vue

@ -3,12 +3,12 @@
<!-- 这里放置标签切换的按钮 -->
<el-button-group>
<el-button :type="activeTab === 'coinRefundDetail' ? 'primary' : 'default'"
@click="navigateTo('coinRefundDetail')" :disabled="!hasDetail">
@click="navigateTo('coinRefundDetail')" :disabled="!hasDetail" v-if="hasDetail">
金币退款明细
</el-button>
<!-- 切换后状态显示 primary 样式否则是默认样式 -->
<el-button :type="activeTab === 'addCoinRefund' ? 'primary' : 'default'" @click="navigateTo('addCoinRefund')"
:disabled="!hasAdd">
:disabled="!hasAdd" v-if="hasAdd">
新增退款
</el-button>
</el-button-group>
@ -45,8 +45,8 @@ const navigateTo = (name) => {
const initPermissions = () => {
if (!menuTree.value || !menuTree.value.length) return;
hasAdd.value = hasMenuPermission(menuTree.value, permissionMapping.Submit_Gold_Coin_Refund);
hasDetail.value = hasMenuPermission(menuTree.value, permissionMapping.View_Gold_Coin_Refund_Details);
hasAdd.value = hasMenuPermission(menuTree.value, permissionMapping.addCoinRefund);
hasDetail.value = hasMenuPermission(menuTree.value, permissionMapping.coinRefundDetail);
};
//
@ -54,7 +54,7 @@ const getDefaultAuditRoute = () => {
initPermissions();
if (hasDetail.value) return 'coinRefundDetail';
if (hasAdd.value) return 'addCoinRefund';
return 'addCoinRefund';
return 'coinRefundDetail';
};
//
@ -77,7 +77,7 @@ onMounted(() => {
navigateTo(defaultRoute);
} else {
//
if (route.name === 'addCoinRefund' || route.name === 'coinRefundDetail') {
if (route.name === 'coinRefundDetail' || route.name === 'addCoinRefund') {
activeTab.value = route.name;
}
}

27
src/views/refund/gold/coinRefundDetail.vue

@ -6,10 +6,15 @@ import moment from 'moment'
import API from '@/util/http.js'
import request from '@/util/http.js'
import { reverseMarketMapping } from "@/utils/marketMap.js";
import { useAdminStore } from "@/store/index.js";
import { storeToRefs } from "pinia";
import { findMenuById, permissionMapping } from "@/utils/menuTreePermission.js"
import dayjs from "dayjs";
const adminStore = useAdminStore();
const { adminData, menuTree } = storeToRefs(adminStore);
const showEmployeeData = ref(false)
const canLook = findMenuById(menuTree.value,permissionMapping.coinRefundDetail)
const defaultTime = [
new Date(2000, 1, 1, 0, 0, 0),
@ -48,18 +53,6 @@ const activeTimeRange = ref('')
const handleDatePickerChange = () => {
activeTimeRange.value = ''
}
const adminData = ref({})
const getAdminData = async function () {
try {
const result = await API({ url: '/admin/userinfo', data: {} })
adminData.value = result
console.log('请求成功', result)
console.log('用户信息', adminData.value)
} catch (error) {
console.log('请求失败', error)
}
}
//
const tableData = ref([])
// ======================================
@ -121,6 +114,10 @@ const getRefundTypes = async function () {
// ==============================================================
//
const getSelectBy = async function (val) {
if (!findMenuById(menuTree.value, permissionMapping.coinRefundDetail)) {
ElMessage.error('无此权限')
return
}
try {
//
if (typeof val === 'number') {
@ -312,7 +309,7 @@ const getGoods = async function () {
//
onMounted(async function () {
await getAdminData()
console.log('=======',adminData.value)
await getSelectBy()
await getMarket()
await getRefundTypes()
@ -547,7 +544,7 @@ const getMarket = async function () {
</el-button>
<el-button type="success" @click="reset()">重置</el-button>
<el-button type="primary" @click="search()">查询</el-button>
<el-button type="primary" @click="search()" v-if="canLook">查询</el-button>
<el-button type="primary" @click="exportExcel">导出Excel</el-button>
<el-button type="primary" @click="openExportList">查看导出列表</el-button>
</div>

9
src/views/usergold/bean/userbean.vue

@ -74,7 +74,6 @@ const pagination = ref({
total: 0
})
const get = async function () {
if(findMenuById(menuTree.value, permissionMapping.View_Golden_Bean_Balance)){
try {
trim()
if(searchObj.value.jwcode){
@ -105,12 +104,9 @@ const get = async function () {
} catch (error) {
console.log(error)
}
}else{
ElMessage.error('无此权限')
}
}
const getStats = async () => {
if (findMenuById(menuTree.value, permissionMapping.View_Golden_Bean_Balance)) {
try {
const params = {
beanUser:{
@ -131,10 +127,7 @@ const getStats = async () => {
} catch (error) {
console.log('请求失败', error)
}
}else{
ElMessage.error('无此权限')
}
}
const handleSortChange = (column) => {
if (column.prop === 'beanNum') {
searchObj.value.sortField = 'jinbi'

8
src/views/usergold/gold/clientCount.vue

@ -6,14 +6,14 @@
<el-button
:type="activeTab === 'clientCountDetail' ? 'primary' : 'default'"
@click="navigateTo('clientCountDetail')"
:disabled="!hasDetail"
:disabled="!hasDetail" v-if="hasDetail"
>
金币明细
</el-button>
<el-button
:type="activeTab === 'clientCountBalance' ? 'primary' : 'default'"
@click="navigateTo('clientCountBalance')"
:disabled="!hasBalance"
:disabled="!hasBalance" v-if="hasBalance"
>
金币余额
</el-button>
@ -51,8 +51,8 @@ const navigateTo = (name) => {
const initPermissions = () => {
if (!menuTree.value || !menuTree.value.length) return;
hasDetail.value = hasMenuPermission(menuTree.value, permissionMapping.View_Gold_Coin_Details);
hasBalance.value = hasMenuPermission(menuTree.value, permissionMapping.View_Gold_Coin_Balance);
hasDetail.value = hasMenuPermission(menuTree.value, permissionMapping.coinCustomerDetail);
hasBalance.value = hasMenuPermission(menuTree.value, permissionMapping.coinCustomerMoney);
};
//

24
src/views/usergold/gold/clientCountBalance.vue

@ -1,5 +1,10 @@
<script setup>
//
import { useAdminStore } from "@/store/index.js"
import { storeToRefs } from "pinia"
import { findMenuById, permissionMapping } from "@/utils/menuTreePermission.js"
const adminStore = useAdminStore()
const { adminData, menuTree } = storeToRefs(adminStore)
import {onMounted, ref} from 'vue'
import {ElMessage} from 'element-plus'
import moment from 'moment'
@ -11,19 +16,7 @@ const showEmployeeData = ref(false)
//
//
const adminData = ref({})
const dialogVisible = ref(false)
const getAdminData = async function () {
try {
const result = await API({url: '/admin/userinfo', data: {}})
adminData.value = result
// console.log('', result)
console.log('管理员用户信息', adminData.value)
} catch (error) {
console.log('请求失败', error)
}
}
//
const isLoadingmarket = ref(false);
@ -72,6 +65,10 @@ const sortOrder = ref('')
// ===========================================================================
//
const get = async function (val) {
if(!findMenuById(menuTree.value, permissionMapping.coinCustomerMoney)){
ElMessage.error('无此权限')
return
}
try {
//
if (typeof val === 'number') {
@ -228,7 +225,6 @@ const handleSortChange = (column) => {
//
onMounted(async function () {
await getAdminData()
await get()
await getMarket()
await getExportList()
@ -422,7 +418,7 @@ const format3 = (num) => {
</div>
<!-- 设置表格容器的高度和滚动样式 -->
<div style="height: 60vh; overflow-y: auto">
<el-table :data="tableData" @cellClick="cellClick" style="width: 82vw;"height="60vh"
<el-table :data="tableData" @cellClick="cellClick" style="width: 82vw; height:60vh"
@sort-change="handleSortChange">
<el-table-column type="index" label="序号" width="100px" fixed="left">
<template #default="scope">

53
src/views/usergold/gold/clientCountDetail.vue

@ -6,7 +6,11 @@ import moment from 'moment'
import API from '@/util/http.js'
import {reverseMarketMapping} from "../../../utils/marketMap.js";
import dayjs from "dayjs";
import { useAdminStore } from "@/store/index.js"
import { storeToRefs } from "pinia"
import { findMenuById, permissionMapping } from "@/utils/menuTreePermission.js"
const adminStore = useAdminStore()
const { adminData, menuTree } = storeToRefs(adminStore)
//
const showEmployeeData = ref(false)
@ -29,18 +33,6 @@ const totalTaskGold = ref(0)
const totalGoldTotal = ref(0)
//
const adminData = ref({})
const getAdminData = async function () {
try {
const result = await API({url: '/admin/userinfo', data: {}})
adminData.value = result
console.log('请求成功', result)
console.log('管理员用户信息', adminData.value)
} catch (error) {
console.log('请求失败', error)
}
}
// goldDetail jwcode
const trimJwCode = () => {
if (goldDetail.value.jwcode) {
@ -128,6 +120,10 @@ const getObj = ref({
// ===========================================================================
//
const get = async function (val) {
if(!findMenuById(menuTree.value, permissionMapping.coinCustomerDetail)){
ElMessage.error('无此权限')
return
}
try {
//
if (typeof val === 'number') {
@ -174,18 +170,6 @@ const get = async function (val) {
console.log('响应数据', result)
tableData.value = result.data.list
total.value = result.data.total
//
// permanentGold.value = tableData.value.reduce((total, row) => {
// return total + (Number(row.permanentGold) || 0);
// }, 0);
// taskGold.value = tableData.value.reduce((total, row) => {
// return total + (Number(row.taskGold) || 0);
// }, 0);
//6+12
const totalResult = await API({
url: '/goldDetail/getTotal',
method: 'post',
@ -278,28 +262,12 @@ const get7Days = function () {
search()
}
// //
// const sumGoldTotal = computed(() => {
// return tableData.value.reduce((total, row) => {
// return total + (Number(row.sumGold) || 0);
// }, 0);
// });
// 6+12
const calculateFreeGold = (row) => {
const freeJune = row.freeJune || 0;
const freeDecember = row.freeDecember || 0;
return (freeJune + freeDecember);
};
//
// const totalFreeGold = computed(() => {
// return tableData.value.reduce((total, row) => {
// return total + calculateFreeGold(row);
// }, 0);
// });
}
//
const sortField = ref('')
const sortOrder = ref('')
@ -355,7 +323,6 @@ const handleCurrentChange = function (val) {
//
onMounted(async function () {
await getAdminData()
await get()
await getMarket()

956
src/views/workspace/index.vue
File diff suppressed because it is too large
View File

Loading…
Cancel
Save