Compare commits
6 Commits
5138937dd4
...
6d5bea10a4
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d5bea10a4 | |||
| bba73fdc7c | |||
| f027838715 | |||
| 34f862bd4d | |||
| 688e2503fa | |||
| cc9fc9adde |
262
package-lock.json
generated
262
package-lock.json
generated
@@ -30,6 +30,27 @@
|
||||
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
|
||||
"dev": true
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://r.cnpmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||
@@ -41,6 +62,40 @@
|
||||
"strip-ansi": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"string-width-cjs": {
|
||||
"version": "npm:string-width@4.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://r.cnpmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
||||
@@ -50,6 +105,23 @@
|
||||
"ansi-regex": "^6.0.1"
|
||||
}
|
||||
},
|
||||
"strip-ansi-cjs": {
|
||||
"version": "npm:strip-ansi@6.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://r.cnpmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||
@@ -60,6 +132,60 @@
|
||||
"string-width": "^5.0.1",
|
||||
"strip-ansi": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"wrap-ansi-cjs": {
|
||||
"version": "npm:wrap-ansi@7.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -18986,7 +19112,6 @@
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"bindings": "^1.5.0",
|
||||
"nan": "^2.12.1",
|
||||
"node-pre-gyp": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -20588,8 +20713,7 @@
|
||||
"nan": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/nan/-/nan-2.5.1.tgz",
|
||||
"integrity": "sha512-Mvo2RwemW12NRql4qU21+Sdtu8CAfn2RaCp8+p6N+4oQQvAM1DfO9R/ZyJOJaLdsMLHw84WJEo2AKZar4KANXA==",
|
||||
"optional": true
|
||||
"integrity": "sha512-Mvo2RwemW12NRql4qU21+Sdtu8CAfn2RaCp8+p6N+4oQQvAM1DfO9R/ZyJOJaLdsMLHw84WJEo2AKZar4KANXA=="
|
||||
},
|
||||
"natural-compare": {
|
||||
"version": "1.4.0",
|
||||
@@ -23305,46 +23429,6 @@
|
||||
"strip-ansi": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"string-width-cjs": {
|
||||
"version": "npm:string-width@4.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"string.prototype.trim": {
|
||||
"version": "1.2.9",
|
||||
"resolved": "https://r.cnpmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz",
|
||||
@@ -23392,23 +23476,6 @@
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi-cjs": {
|
||||
"version": "npm:strip-ansi@6.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"strip-bom": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://r2.cnpmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
|
||||
@@ -25835,81 +25902,6 @@
|
||||
"strip-ansi": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"wrap-ansi-cjs": {
|
||||
"version": "npm:wrap-ansi@7.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://r2.cnpmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
|
||||
@@ -92,7 +92,9 @@ const mainRoutes = {
|
||||
{ path: '/reportList-trainingCourseClassList', component: _import('modules/reportList/trainingCourseClassList'), name: 'reportList-trainingCourseClassList', meta: { title: '培训班报表', isTab: true } },
|
||||
{ path: '/reportList-courseList', component: _import('modules/reportList/courseList'), name: 'reportList-courseList', meta: { title: '课程报表', isTab: true } },
|
||||
{ path: '/reportList-lingshuFullYear', component: _import('modules/reportList/lingshuFullYear'), name: 'reportList-lingshuFullYear', meta: { title: '灵枢年度报表', isTab: true } },
|
||||
{ path: '/content-psychologicalForum', component: _import('modules/content/psychologicalForum'), name: 'content-psychologicalForum', meta: { title: '心理论坛', isTab: true } }
|
||||
{ path: '/content-psychologicalForum', component: _import('modules/content/psychologicalForum'), name: 'content-psychologicalForum', meta: { title: '心理论坛', isTab: true } },
|
||||
{ path: '/statisticsBusiness-courseStatistics', component: _import('modules/statisticsBusiness/courseStatistics/index'), name: 'statisticsBusiness-courseStatistics', meta: { title: '课程统计', isTab: true } },
|
||||
{ path: '/statisticsBusiness-vipStatistics', component: _import('modules/statisticsBusiness/vipStatistics/index'), name: 'statisticsBusiness-vipStatistics', meta: { title: 'VIP统计', isTab: true } }
|
||||
],
|
||||
beforeEnter (to, from, next) {
|
||||
let token = Vue.cookie.get('token')
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<div class="course-statistics-container">
|
||||
<el-form
|
||||
:inline="true"
|
||||
:model="dataForm"
|
||||
@keyup.enter.native="getDataList()"
|
||||
>
|
||||
<el-form-item>
|
||||
<el-date-picker
|
||||
v-model="currentYear"
|
||||
type="year"
|
||||
format="yyyy"
|
||||
value-format="yyyy"
|
||||
:picker-options="pickerOptions"
|
||||
placeholder="选择年份"
|
||||
@change="handleYearChange"
|
||||
size="small"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleYearChange"
|
||||
>刷新</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
@click="exportData"
|
||||
>下载报表</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div ref="tableContainer" class="table-container">
|
||||
<el-table
|
||||
:data="dataList"
|
||||
border
|
||||
:header-cell-style="{ textAlign: 'center' }"
|
||||
:cell-style="{ textAlign: 'center' }"
|
||||
:max-height="tableHeight"
|
||||
>
|
||||
<el-table-column prop="time" label="月份" min-width="120" />
|
||||
<el-table-column prop="sales" label="销售量" min-width="100" />
|
||||
<el-table-column label="月销售额(微信+支付宝+银行+天医币)" min-width="280">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.salesFee }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="月销售额(微信+支付宝+银行)" min-width="250">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.cashFee }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="wx" label="微信支付" min-width="120" />
|
||||
<el-table-column prop="zfb" label="支付宝支付" min-width="120" />
|
||||
<el-table-column prop="bank" label="银行支付" min-width="120" />
|
||||
<el-table-column prop="point" label="天医币支付" min-width="120" />
|
||||
<el-table-column prop="jf" label="积分支付" min-width="120" />
|
||||
<el-table-column label="明细" min-width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="openDetail(scope.row.time)"
|
||||
>详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
:visible.sync="dialogVisible"
|
||||
top="10vh"
|
||||
title="销售详情"
|
||||
width="90%"
|
||||
>
|
||||
<div style="margin-bottom: 10px; text-align: right;">
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
@click="exportDetailData"
|
||||
>下载详情报表</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
:data="detailDataList"
|
||||
border
|
||||
:header-cell-style="{ textAlign: 'center' }"
|
||||
:cell-style="{ textAlign: 'center' }"
|
||||
:max-height="dialogTableHeight"
|
||||
>
|
||||
<el-table-column prop="time" label="日" min-width="100" />
|
||||
<el-table-column prop="sales" label="销售量" min-width="100" />
|
||||
<el-table-column label="日销售额(微信+支付宝+银行+天医币)" min-width="280">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.salesFee }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="日销售额(微信+支付宝+银行)" min-width="250">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.cashFee }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="wx" label="微信支付" min-width="120" />
|
||||
<el-table-column prop="zfb" label="支付宝支付" min-width="120" />
|
||||
<el-table-column prop="bank" label="银行支付" min-width="120" />
|
||||
<el-table-column prop="point" label="天医币支付" min-width="120" />
|
||||
<el-table-column prop="jf" label="积分支付" min-width="120" />
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import http from '@/utils/httpRequest'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
currentYear: '',
|
||||
dataForm: {},
|
||||
pickerOptions: {
|
||||
disabledDate(time) {
|
||||
return time.getFullYear() > new Date().getFullYear()
|
||||
}
|
||||
},
|
||||
dataList: [],
|
||||
detailDataList: [],
|
||||
dialogVisible: false,
|
||||
currentDetailDate: '',
|
||||
tableHeight: null,
|
||||
dialogTableHeight: ''
|
||||
}
|
||||
},
|
||||
activated() {
|
||||
this.currentYear = new Date().getFullYear().toString()
|
||||
this.getDataList()
|
||||
this.$nextTick(() => {
|
||||
this.calculateTableHeight()
|
||||
})
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('resize', this.handleResize)
|
||||
this.$nextTick(() => {
|
||||
this.calculateTableHeight()
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.handleResize)
|
||||
},
|
||||
methods: {
|
||||
handleYearChange() {
|
||||
this.getDataList()
|
||||
},
|
||||
getDataList() {
|
||||
this.getListData(this.currentYear, 'dataList')
|
||||
},
|
||||
calculateTableHeight() {
|
||||
if (this.$refs.tableContainer) {
|
||||
this.tableHeight = this.$refs.tableContainer.offsetHeight || 500
|
||||
} else {
|
||||
this.tableHeight = 500
|
||||
}
|
||||
},
|
||||
getDetailData() {
|
||||
this.getListData(this.currentDetailDate, 'detailDataList')
|
||||
},
|
||||
getListData(date, dataKey) {
|
||||
http({
|
||||
url: http.adornUrl('/master/statisticsBusiness/getCourseSaleInfoByTime'),
|
||||
method: 'post',
|
||||
data: http.adornData({
|
||||
date: date
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 0) {
|
||||
this[dataKey] = data.courseSaleInfo || []
|
||||
}
|
||||
})
|
||||
},
|
||||
openDetail(date) {
|
||||
this.currentDetailDate = date
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
const windowHeight = window.innerHeight
|
||||
this.dialogTableHeight = windowHeight * 0.8 - 140
|
||||
})
|
||||
this.getDetailData()
|
||||
},
|
||||
exportData() {
|
||||
this.exportReport(this.currentYear)
|
||||
},
|
||||
exportDetailData() {
|
||||
this.exportReport(this.currentDetailDate)
|
||||
},
|
||||
exportReport(date) {
|
||||
http({
|
||||
url: http.adornUrl('/master/statisticsBusiness/exportCourseSaleInfoByTime'),
|
||||
method: 'post',
|
||||
data: http.adornData({
|
||||
date: date
|
||||
}),
|
||||
responseType: 'blob'
|
||||
}).then((response) => {
|
||||
const blob = new Blob([response.data])
|
||||
const fileName = `课程统计报表_按金额_${date}.xlsx`
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = fileName
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.course-statistics-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 220px);
|
||||
|
||||
.table-container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.el-form .el-form-item {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
/deep/ .el-dialog__wrapper .el-dialog__body {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,448 @@
|
||||
<template>
|
||||
<div class="course-statistics-container">
|
||||
<el-form
|
||||
:inline="true"
|
||||
:model="dataForm"
|
||||
@keyup.enter.native="getDataList()"
|
||||
>
|
||||
<el-form-item>
|
||||
<el-radio-group v-model="timeType" size="small" @change="handleTimeTypeChange">
|
||||
<el-radio-button label="year">按年份</el-radio-button>
|
||||
<el-radio-button label="month">按月份</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-date-picker
|
||||
v-show="timeType === 'year'"
|
||||
v-model="currentDate"
|
||||
type="year"
|
||||
format="yyyy"
|
||||
placeholder="选择年份"
|
||||
popper-append-to-body
|
||||
@change="getDataList"
|
||||
size="small"
|
||||
/>
|
||||
<el-date-picker
|
||||
v-show="timeType === 'month'"
|
||||
v-model="currentDate"
|
||||
type="month"
|
||||
format="yyyy-MM"
|
||||
placeholder="选择月份"
|
||||
popper-append-to-body
|
||||
@change="getDataList"
|
||||
size="small"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="getDataList"
|
||||
>刷新</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
@click="exportData"
|
||||
>下载报表</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="table-container" ref="tableContainer">
|
||||
<el-table
|
||||
:data="dataList"
|
||||
border
|
||||
:header-cell-style="{ textAlign: 'center' }"
|
||||
:cell-style="{ textAlign: 'center' }"
|
||||
:max-height="tableHeight"
|
||||
>
|
||||
<el-table-column prop="courseTitle" label="课程名称" min-width="150" />
|
||||
<el-table-column prop="catalogueTitle" label="目录名" min-width="150" />
|
||||
<el-table-column prop="courseLabel" label="分类" min-width="120" />
|
||||
<el-table-column
|
||||
:label="timeType === 'year' ? '年销量' : '月销量'"
|
||||
prop="sales"
|
||||
min-width="100"
|
||||
/>
|
||||
<el-table-column
|
||||
:label="timeType === 'year' ? '年销售额(微信+支付宝+银行+天医币)' : '月销售额(微信+支付宝+银行+天医币)'"
|
||||
min-width="280"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.salesFee }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="timeType === 'year' ? '年销售额(微信+支付宝+银行)' : '月销售额(微信+支付宝+银行)'"
|
||||
min-width="250"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.cashFee }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="详情"
|
||||
fixed="right"
|
||||
min-width="100"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="showDetail(scope.row)"
|
||||
>详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<el-pagination
|
||||
:current-page="page"
|
||||
:page-size="limit"
|
||||
:total="total"
|
||||
layout="total, prev, pager, next, sizes"
|
||||
:page-sizes="[10, 20, 30, 50]"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
style="margin-top: 15px; text-align: right;"
|
||||
/>
|
||||
|
||||
<el-dialog
|
||||
:visible.sync="yearDetailDialogVisible"
|
||||
top="10vh"
|
||||
title="课程年份明细"
|
||||
width="90%"
|
||||
>
|
||||
<div class="dialog-header">
|
||||
<span class="dialog-title">{{ yearDialogTitle }}</span>
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
@click="exportDetailData(currentYearDetailDate, true)"
|
||||
>下载详情报表</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
:data="yearDetailDataList"
|
||||
border
|
||||
:header-cell-style="{ textAlign: 'center' }"
|
||||
:cell-style="{ textAlign: 'center' }"
|
||||
:height="yearDetailTableHeight"
|
||||
>
|
||||
<el-table-column prop="monthTime" label="月份" min-width="100" />
|
||||
<el-table-column prop="sales" label="销量" min-width="100" />
|
||||
<el-table-column
|
||||
label="月销售额(微信+支付宝+银行+天医币)"
|
||||
min-width="280"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.salesFee }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="月销售额(微信+支付宝+银行)"
|
||||
min-width="250"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.cashFee }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="wx" label="微信支付" min-width="120" />
|
||||
<el-table-column prop="zfb" label="支付宝支付" min-width="120" />
|
||||
<el-table-column prop="bank" label="银行支付" min-width="120" />
|
||||
<el-table-column prop="point" label="天医币支付" min-width="120" />
|
||||
<el-table-column prop="jf" label="积分支付" min-width="120" />
|
||||
<el-table-column prop="relearnCount" label="复读人数" min-width="100" />
|
||||
<el-table-column
|
||||
label="复读占比(按销量)"
|
||||
min-width="120"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.sales > 0 ? ((scope.row.relearnCount / scope.row.sales * 100).toFixed(2) + '%') : '0%' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="miaoshaCount" label="秒杀人数" min-width="100" />
|
||||
<el-table-column
|
||||
label="秒杀占比(按销量)"
|
||||
min-width="120"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.sales > 0 ? ((scope.row.miaoshaCount / scope.row.sales * 100).toFixed(2) + '%') : '0%' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
:visible.sync="monthDetailDialogVisible"
|
||||
top="10vh"
|
||||
title="课程月份明细"
|
||||
width="90%"
|
||||
>
|
||||
<div class="dialog-header">
|
||||
<span class="dialog-title">{{ monthDialogTitle }}</span>
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
@click="exportDetailData(currentMonthDetailDate, false)"
|
||||
>下载详情报表</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
:data="monthDetailDataList"
|
||||
border
|
||||
:header-cell-style="{ textAlign: 'center' }"
|
||||
:cell-style="{ textAlign: 'center' }"
|
||||
:height="monthDetailTableHeight"
|
||||
>
|
||||
<el-table-column prop="time" label="时间" min-width="160" />
|
||||
<el-table-column prop="name" label="姓名" min-width="100" />
|
||||
<el-table-column prop="tel" label="注册电话" min-width="120" />
|
||||
<el-table-column
|
||||
label="课程时长(1个月/3个月半年期/一年期)"
|
||||
min-width="160"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.days }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="是否复读"
|
||||
min-width="100"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.orderType === '是' ? '是' : '否' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import http from '@/utils/httpRequest'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
timeType: 'year',
|
||||
currentDate: new Date(),
|
||||
dataForm: {},
|
||||
dataList: [],
|
||||
yearDetailDataList: [],
|
||||
monthDetailDataList: [],
|
||||
yearDetailDialogVisible: false,
|
||||
monthDetailDialogVisible: false,
|
||||
yearDialogTitle: '',
|
||||
monthDialogTitle: '',
|
||||
currentYearDetailDate: '',
|
||||
currentMonthDetailDate: '',
|
||||
currentCatalogueId: '',
|
||||
yearDetailTableHeight: '',
|
||||
monthDetailTableHeight: '',
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
tableHeight: null
|
||||
}
|
||||
},
|
||||
activated() {
|
||||
const now = new Date()
|
||||
if (this.timeType === 'year') {
|
||||
this.currentDate = new Date(now.getFullYear(), 0, 1)
|
||||
} else {
|
||||
this.currentDate = now
|
||||
}
|
||||
this.getDataList()
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('resize', this.handleResize)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.handleResize)
|
||||
},
|
||||
methods: {
|
||||
handleResize() {
|
||||
this.calculateTableHeight()
|
||||
},
|
||||
calculateTableHeight() {
|
||||
if (this.$refs.tableContainer) {
|
||||
this.tableHeight = this.$refs.tableContainer.offsetHeight || 500
|
||||
} else {
|
||||
this.tableHeight = 500
|
||||
}
|
||||
},
|
||||
handleTimeTypeChange() {
|
||||
if (this.timeType === 'year') {
|
||||
const now = new Date()
|
||||
this.currentDate = new Date(now.getFullYear(), 0, 1)
|
||||
} else {
|
||||
this.currentDate = new Date()
|
||||
}
|
||||
this.getDataList()
|
||||
},
|
||||
getDataList() {
|
||||
const dateStr = this.timeType === 'year'
|
||||
? this.currentDate.getFullYear().toString()
|
||||
: `${this.currentDate.getFullYear()}-${String(this.currentDate.getMonth() + 1).padStart(2, '0')}`
|
||||
http({
|
||||
url: http.adornUrl('/master/statisticsBusiness/getCourseSaleInfoByCourse'),
|
||||
method: 'post',
|
||||
data: http.adornData({
|
||||
date: dateStr,
|
||||
page: this.page,
|
||||
limit: this.limit
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 0) {
|
||||
this.dataList = data.courseSaleInfo || []
|
||||
this.total = data.totalSize || 0
|
||||
this.calculateTableHeight()
|
||||
}
|
||||
})
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.limit = val
|
||||
this.getDataList()
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.page = val
|
||||
this.getDataList()
|
||||
},
|
||||
exportData() {
|
||||
const dateStr = this.timeType === 'year'
|
||||
? this.currentDate.getFullYear().toString()
|
||||
: `${this.currentDate.getFullYear()}-${String(this.currentDate.getMonth() + 1).padStart(2, '0')}`
|
||||
http({
|
||||
url: http.adornUrl('/master/statisticsBusiness/exportCourseSaleInfoByCourse'),
|
||||
method: 'post',
|
||||
data: http.adornData({
|
||||
date: dateStr
|
||||
}),
|
||||
responseType: 'blob'
|
||||
}).then((response) => {
|
||||
const blob = new Blob([response.data])
|
||||
const fileName = `课程统计报表_按课程_${dateStr}.xlsx`
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = fileName
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
})
|
||||
},
|
||||
showDetail(row) {
|
||||
this.currentCatalogueId = row.catalogueId
|
||||
const dateStr = this.timeType === 'year'
|
||||
? this.currentDate.getFullYear().toString()
|
||||
: `${this.currentDate.getFullYear()}-${String(this.currentDate.getMonth() + 1).padStart(2, '0')}`
|
||||
|
||||
const dialogTitle = `${row.courseTitle} - ${row.catalogueTitle}`
|
||||
const isYear = this.timeType === 'year'
|
||||
|
||||
if (isYear) {
|
||||
this.currentYearDetailDate = dateStr
|
||||
this.yearDialogTitle = dialogTitle
|
||||
this.yearDetailDialogVisible = true
|
||||
|
||||
this.$nextTick(() => {
|
||||
const windowHeight = window.innerHeight
|
||||
this.yearDetailTableHeight = windowHeight * 0.8 - 140
|
||||
})
|
||||
|
||||
http({
|
||||
url: http.adornUrl('/master/statisticsBusiness/getCourseYearInfo'),
|
||||
method: 'post',
|
||||
data: http.adornData({
|
||||
date: dateStr,
|
||||
catalogueId: row.catalogueId
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 0) {
|
||||
this.yearDetailDataList = data.courseSaleInfo || []
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.currentMonthDetailDate = dateStr
|
||||
this.monthDialogTitle = dialogTitle
|
||||
this.monthDetailDialogVisible = true
|
||||
|
||||
this.$nextTick(() => {
|
||||
const windowHeight = window.innerHeight
|
||||
this.monthDetailTableHeight = windowHeight * 0.8 - 140
|
||||
})
|
||||
|
||||
http({
|
||||
url: http.adornUrl('/master/statisticsBusiness/getCourseMonthInfo'),
|
||||
method: 'post',
|
||||
data: http.adornData({
|
||||
date: dateStr,
|
||||
catalogueId: row.catalogueId
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 0) {
|
||||
this.monthDetailDataList = data.courseSaleInfo || []
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
exportDetailData(date, isYear) {
|
||||
const exportUrl = isYear
|
||||
? '/master/statisticsBusiness/exportCourseYearInfo'
|
||||
: '/master/statisticsBusiness/exportCourseMonthInfo'
|
||||
const fileName = isYear
|
||||
? `课程年份明细报表_${this.yearDialogTitle}_${date}.xlsx`
|
||||
: `课程月份明细报表_${this.monthDialogTitle}_${date}.xlsx`
|
||||
|
||||
http({
|
||||
url: http.adornUrl(exportUrl),
|
||||
method: 'post',
|
||||
data: http.adornData({
|
||||
date: date,
|
||||
catalogueId: this.currentCatalogueId
|
||||
}),
|
||||
responseType: 'blob'
|
||||
}).then((response) => {
|
||||
const blob = new Blob([response.data])
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = fileName
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.course-statistics-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 220px);
|
||||
|
||||
.table-container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.el-form .el-form-item {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
/deep/ .el-dialog__wrapper .el-dialog__body {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div class="mod-config">
|
||||
<el-tabs v-model="activeTab" type="card">
|
||||
<el-tab-pane label="按金额统计" name="amount" />
|
||||
<el-tab-pane label="按课程统计" name="course" />
|
||||
<el-tab-pane label="按分类统计" name="label" />
|
||||
</el-tabs>
|
||||
<keep-alive>
|
||||
<component :is="currentComponent" />
|
||||
</keep-alive>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import amountStatistics from './amountStatistics'
|
||||
import courseStatistics from './courseStatistics'
|
||||
import labelStatistics from './labelStatistics'
|
||||
|
||||
export default {
|
||||
components: { amountStatistics, courseStatistics, labelStatistics },
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'amount'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentComponent() {
|
||||
return `${this.activeTab}Statistics`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-form .el-form-item {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,194 @@
|
||||
<template>
|
||||
<div class="course-label-statistics-container">
|
||||
<el-form
|
||||
:inline="true"
|
||||
:model="dataForm"
|
||||
@keyup.enter.native="getDataList()"
|
||||
>
|
||||
<el-form-item>
|
||||
<el-radio-group v-model="timeType" size="small" @change="handleTimeTypeChange">
|
||||
<el-radio-button label="year">按年份</el-radio-button>
|
||||
<el-radio-button label="month">按月份</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-date-picker
|
||||
v-show="timeType === 'year'"
|
||||
v-model="currentDate"
|
||||
type="year"
|
||||
format="yyyy"
|
||||
placeholder="选择年份"
|
||||
popper-append-to-body
|
||||
@change="getDataList"
|
||||
size="small"
|
||||
/>
|
||||
<el-date-picker
|
||||
v-show="timeType === 'month'"
|
||||
v-model="currentDate"
|
||||
type="month"
|
||||
format="yyyy-MM"
|
||||
placeholder="选择月份"
|
||||
popper-append-to-body
|
||||
@change="getDataList"
|
||||
size="small"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="getDataList"
|
||||
>刷新</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
@click="exportData"
|
||||
>下载报表</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="table-container" ref="tableContainer">
|
||||
<el-table
|
||||
:data="dataList"
|
||||
border
|
||||
:header-cell-style="{ textAlign: 'center' }"
|
||||
:cell-style="{ textAlign: 'center' }"
|
||||
:max-height="tableHeight"
|
||||
>
|
||||
<el-table-column prop="courseLabel" label="分类" min-width="150" />
|
||||
<el-table-column prop="courseCount" label="总销售门数" min-width="120" />
|
||||
<el-table-column prop="sales" label="总销量" min-width="100" />
|
||||
<el-table-column
|
||||
:label="timeType === 'year' ? '年销售额(微信+支付宝+银行+天医币)' : '月销售额(微信+支付宝+银行+天医币)'"
|
||||
min-width="280"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.salesFee }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="timeType === 'year' ? '年销售额(微信+支付宝+银行)' : '月销售额(微信+支付宝+银行)'"
|
||||
min-width="250"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.cashFee }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import http from '@/utils/httpRequest'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
timeType: 'year',
|
||||
currentDate: new Date(),
|
||||
dataForm: {},
|
||||
dataList: [],
|
||||
tableHeight: null
|
||||
}
|
||||
},
|
||||
activated() {
|
||||
const now = new Date()
|
||||
if (this.timeType === 'year') {
|
||||
this.currentDate = new Date(now.getFullYear(), 0, 1)
|
||||
} else {
|
||||
this.currentDate = now
|
||||
}
|
||||
this.getDataList()
|
||||
this.$nextTick(() => {
|
||||
this.calculateTableHeight()
|
||||
})
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('resize', this.handleResize)
|
||||
this.$nextTick(() => {
|
||||
this.calculateTableHeight()
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.handleResize)
|
||||
},
|
||||
methods: {
|
||||
handleResize() {
|
||||
this.calculateTableHeight()
|
||||
},
|
||||
calculateTableHeight() {
|
||||
if (this.$refs.tableContainer) {
|
||||
this.tableHeight = this.$refs.tableContainer.offsetHeight || 500
|
||||
} else {
|
||||
this.tableHeight = 500
|
||||
}
|
||||
},
|
||||
handleTimeTypeChange() {
|
||||
if (this.timeType === 'year') {
|
||||
const now = new Date()
|
||||
this.currentDate = new Date(now.getFullYear(), 0, 1)
|
||||
} else {
|
||||
this.currentDate = new Date()
|
||||
}
|
||||
this.getDataList()
|
||||
},
|
||||
getDataList() {
|
||||
const dateStr = this.timeType === 'year'
|
||||
? this.currentDate.getFullYear().toString()
|
||||
: `${this.currentDate.getFullYear()}-${String(this.currentDate.getMonth() + 1).padStart(2, '0')}`
|
||||
http({
|
||||
url: http.adornUrl('/master/statisticsBusiness/getCourseSaleInfoByCourseLabel'),
|
||||
method: 'post',
|
||||
data: http.adornData({
|
||||
date: dateStr
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 0) {
|
||||
this.dataList = data.courseSaleInfo || []
|
||||
}
|
||||
})
|
||||
},
|
||||
exportData() {
|
||||
const dateStr = this.timeType === 'year'
|
||||
? this.currentDate.getFullYear().toString()
|
||||
: `${this.currentDate.getFullYear()}-${String(this.currentDate.getMonth() + 1).padStart(2, '0')}`
|
||||
http({
|
||||
url: http.adornUrl('/master/statisticsBusiness/exportCourseSaleInfoByCourseLabel'),
|
||||
method: 'post',
|
||||
data: http.adornData({
|
||||
date: dateStr
|
||||
}),
|
||||
responseType: 'blob'
|
||||
}).then((response) => {
|
||||
const blob = new Blob([response.data])
|
||||
const fileName = `按分类统计报表_${dateStr}.xlsx`
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = fileName
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.course-label-statistics-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 220px);
|
||||
|
||||
.table-container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.el-form .el-form-item {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
</style>
|
||||
332
src/views/modules/statisticsBusiness/vipStatistics/allVip.vue
Normal file
332
src/views/modules/statisticsBusiness/vipStatistics/allVip.vue
Normal file
@@ -0,0 +1,332 @@
|
||||
<template>
|
||||
<div class="all-vip-statistics" v-loading="loading">
|
||||
<div class="summary-section">
|
||||
<div class="summary-block">
|
||||
<div class="summary-block__title">截止至今到期统计</div>
|
||||
<div class="summary-grid">
|
||||
<div
|
||||
v-for="item in expiredSummaryCards"
|
||||
:key="`expired-${item.key}`"
|
||||
class="summary-card summary-card--expired"
|
||||
>
|
||||
<div class="summary-card__label">{{ item.label }}</div>
|
||||
<div class="summary-card__value">{{ item.value }}人</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="summary-block">
|
||||
<div class="summary-block__title">截止至今在期统计</div>
|
||||
<div class="summary-grid">
|
||||
<div
|
||||
v-for="item in activeSummaryCards"
|
||||
:key="`active-${item.key}`"
|
||||
class="summary-card summary-card--active"
|
||||
>
|
||||
<div class="summary-card__label">{{ item.label }}</div>
|
||||
<div class="summary-card__value">{{ item.value }}人</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-header">
|
||||
<div class="detail-title">全部VIP明细({{ dataList.length }})</div>
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
:loading="exportLoading"
|
||||
@click="exportData"
|
||||
>下载报表</el-button>
|
||||
</div>
|
||||
|
||||
<div ref="tableContainer" class="table-container">
|
||||
<el-table
|
||||
:data="dataList"
|
||||
border
|
||||
:header-cell-style="{ textAlign: 'center', padding: '6px 0' }"
|
||||
:cell-style="{ textAlign: 'center', padding: '6px 0' }"
|
||||
:max-height="tableHeight"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="70" />
|
||||
<el-table-column prop="time" label="办理时间" min-width="140" />
|
||||
<el-table-column prop="name" label="姓名" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
{{ formatText(scope.row.name) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="tel" label="注册电话" min-width="140">
|
||||
<template slot-scope="scope">
|
||||
{{ formatText(scope.row.tel) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="vipType" label="VIP类型" min-width="160">
|
||||
<template slot-scope="scope">
|
||||
{{ formatText(scope.row.vipType) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="year" label="年限" min-width="100">
|
||||
<template slot-scope="scope">
|
||||
{{ formatYear(scope.row.year) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="price" label="缴费金额" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
{{ formatAmount(scope.row.price) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="endTime" label="到期时间" min-width="140">
|
||||
<template slot-scope="scope">
|
||||
{{ formatText(scope.row.endTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import http from '@/utils/httpRequest'
|
||||
|
||||
function createEmptySummary() {
|
||||
return {
|
||||
total: 0,
|
||||
zyCount: 0,
|
||||
zjCount: 0,
|
||||
zxhtCount: 0,
|
||||
zlCount: 0,
|
||||
xlCount: 0,
|
||||
yxSuperCount: 0,
|
||||
gxSuperCount: 0
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
exportLoading: false,
|
||||
tableHeight: null,
|
||||
dataList: [],
|
||||
expiredSummary: createEmptySummary(),
|
||||
activeSummary: createEmptySummary()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
expiredSummaryCards() {
|
||||
return this.buildSummaryCards(this.expiredSummary)
|
||||
},
|
||||
activeSummaryCards() {
|
||||
return this.buildSummaryCards(this.activeSummary)
|
||||
}
|
||||
},
|
||||
activated() {
|
||||
this.getDataList()
|
||||
this.$nextTick(() => {
|
||||
this.calculateTableHeight()
|
||||
})
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('resize', this.handleResize)
|
||||
this.$nextTick(() => {
|
||||
this.calculateTableHeight()
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.handleResize)
|
||||
},
|
||||
methods: {
|
||||
handleResize() {
|
||||
this.calculateTableHeight()
|
||||
},
|
||||
calculateTableHeight() {
|
||||
if (this.$refs.tableContainer) {
|
||||
this.tableHeight = this.$refs.tableContainer.offsetHeight || 500
|
||||
} else {
|
||||
this.tableHeight = 500
|
||||
}
|
||||
},
|
||||
buildSummaryCards(summary) {
|
||||
return [
|
||||
{ key: 'total', label: '总人数', value: summary.total },
|
||||
{ key: 'zyCount', label: '中医学', value: summary.zyCount },
|
||||
{ key: 'zjCount', label: '针灸学', value: summary.zjCount },
|
||||
{ key: 'zxhtCount', label: '中西汇通学', value: summary.zxhtCount },
|
||||
{ key: 'zlCount', label: '肿瘤学', value: summary.zlCount },
|
||||
{ key: 'xlCount', label: '心理学', value: summary.xlCount },
|
||||
{ key: 'yxSuperCount', label: '医学超级', value: summary.yxSuperCount },
|
||||
{ key: 'gxSuperCount', label: '国学心理学超级', value: summary.gxSuperCount }
|
||||
]
|
||||
},
|
||||
normalizeSummary(total, counts = {}) {
|
||||
return {
|
||||
total: total || 0,
|
||||
zyCount: counts.zyCount || 0,
|
||||
zjCount: counts.zjCount || counts.zjcount || 0,
|
||||
zxhtCount: counts.zxhtCount || 0,
|
||||
zlCount: counts.zlCount || counts.zlcount || 0,
|
||||
xlCount: counts.xlCount || 0,
|
||||
yxSuperCount: counts.yxSuperCount || 0,
|
||||
gxSuperCount: counts.gxSuperCount || 0
|
||||
}
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true
|
||||
http({
|
||||
url: http.adornUrl('/master/statisticsBusinessVip/getUserVipByState'),
|
||||
method: 'post',
|
||||
data: http.adornData({})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 0) {
|
||||
this.dataList = data.resultList || []
|
||||
this.expiredSummary = this.normalizeSummary(data.state1Total, data.state1Counts)
|
||||
this.activeSummary = this.normalizeSummary(data.state0Total, data.state0Counts)
|
||||
this.$nextTick(() => {
|
||||
this.calculateTableHeight()
|
||||
})
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
exportData() {
|
||||
this.exportLoading = true
|
||||
http({
|
||||
url: http.adornUrl('/master/statisticsBusinessVip/exportUserVipByState'),
|
||||
method: 'post',
|
||||
data: http.adornData({}),
|
||||
responseType: 'blob'
|
||||
}).then((response) => {
|
||||
const blob = new Blob([response.data])
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = '全部VIP统计报表.xlsx'
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
}).catch(() => {
|
||||
this.$message.error('下载失败,请确认导出接口是否已提供')
|
||||
}).finally(() => {
|
||||
this.exportLoading = false
|
||||
})
|
||||
},
|
||||
formatText(value) {
|
||||
if (value === '' || value === null || value === undefined) {
|
||||
return '--'
|
||||
}
|
||||
return String(value).trim() || '--'
|
||||
},
|
||||
formatYear(year) {
|
||||
if (year === '' || year === null || year === undefined) {
|
||||
return '--'
|
||||
}
|
||||
return year + '年'
|
||||
},
|
||||
formatAmount(amount) {
|
||||
if (amount === '' || amount === null || amount === undefined) {
|
||||
return '--'
|
||||
}
|
||||
return Number(amount).toFixed(2).replace(/\.00$/, '')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/deep/ .el-table .cell,
|
||||
/deep/ .el-table th > .cell {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.all-vip-statistics {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
height: calc(100vh - 220px);
|
||||
}
|
||||
|
||||
.summary-section,
|
||||
.detail-section {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.summary-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.summary-block__title {
|
||||
margin-bottom: 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
padding: 16px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(31, 45, 61, 0.06);
|
||||
}
|
||||
|
||||
.summary-card--expired {
|
||||
background: linear-gradient(180deg, #fff7f5 0%, #ffffff 100%);
|
||||
}
|
||||
|
||||
.summary-card--active {
|
||||
background: linear-gradient(180deg, #f3fbf5 0%, #ffffff 100%);
|
||||
}
|
||||
|
||||
.summary-card__label {
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.summary-card__value {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #1f2d3d;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<expires-statistics-base
|
||||
title="近半年临到期"
|
||||
:month="6"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import expiresStatisticsBase from './expiresStatisticsBase'
|
||||
|
||||
export default {
|
||||
components: { expiresStatisticsBase }
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,283 @@
|
||||
<template>
|
||||
<div class="vip-expires-statistics" v-loading="loading">
|
||||
<div class="summary-section">
|
||||
<div class="section-title">汇总统计</div>
|
||||
<div class="summary-grid">
|
||||
<div
|
||||
v-for="item in summaryCards"
|
||||
:key="item.key"
|
||||
class="summary-card"
|
||||
>
|
||||
<div class="summary-card__label">{{ item.label }}</div>
|
||||
<div class="summary-card__value">{{ item.value }}人</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-header">
|
||||
<div class="detail-title">{{ detailTitle }}</div>
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
:loading="exportLoading"
|
||||
@click="exportData"
|
||||
>下载报表</el-button>
|
||||
</div>
|
||||
|
||||
<div ref="tableContainer" class="table-container">
|
||||
<el-table
|
||||
:data="dataList"
|
||||
border
|
||||
:header-cell-style="{ textAlign: 'center', padding: '6px 0' }"
|
||||
:cell-style="{ textAlign: 'center', padding: '6px 0' }"
|
||||
:max-height="tableHeight"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="70" />
|
||||
<el-table-column prop="time" label="办理时间" min-width="140" />
|
||||
<el-table-column prop="name" label="姓名" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.name || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="tel" label="注册电话" min-width="140" />
|
||||
<el-table-column prop="vipType" label="VIP类型" min-width="160" />
|
||||
<el-table-column prop="year" label="年限" min-width="100">
|
||||
<template slot-scope="scope">
|
||||
{{ formatYear(scope.row.year) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="price" label="缴费金额" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
{{ formatAmount(scope.row.price) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="endTime" label="到期时间" min-width="140" />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import http from '@/utils/httpRequest'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
month: {
|
||||
type: [String, Number],
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
exportLoading: false,
|
||||
tableHeight: null,
|
||||
dataList: [],
|
||||
summaryData: {
|
||||
total: 0,
|
||||
zyCount: 0,
|
||||
zjcount: 0,
|
||||
zxhtCount: 0,
|
||||
zlcount: 0,
|
||||
xlCount: 0,
|
||||
yxSuperCount: 0,
|
||||
gxSuperCount: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
detailTitle() {
|
||||
return `${this.title}明细(${this.summaryData.total || 0})`
|
||||
},
|
||||
summaryCards() {
|
||||
return [
|
||||
{ key: 'total', label: '总人数', value: this.summaryData.total },
|
||||
{ key: 'zyCount', label: '中医学', value: this.summaryData.zyCount },
|
||||
{ key: 'zjcount', label: '针灸学', value: this.summaryData.zjcount },
|
||||
{ key: 'zxhtCount', label: '中西汇通学', value: this.summaryData.zxhtCount },
|
||||
{ key: 'zlcount', label: '肿瘤学', value: this.summaryData.zlcount },
|
||||
{ key: 'xlCount', label: '心理学', value: this.summaryData.xlCount },
|
||||
{ key: 'yxSuperCount', label: '医学超级', value: this.summaryData.yxSuperCount },
|
||||
{ key: 'gxSuperCount', label: '国学心理学超级', value: this.summaryData.gxSuperCount }
|
||||
]
|
||||
}
|
||||
},
|
||||
activated() {
|
||||
this.getDataList()
|
||||
this.$nextTick(() => {
|
||||
this.calculateTableHeight()
|
||||
})
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('resize', this.handleResize)
|
||||
this.$nextTick(() => {
|
||||
this.calculateTableHeight()
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.handleResize)
|
||||
},
|
||||
methods: {
|
||||
handleResize() {
|
||||
this.calculateTableHeight()
|
||||
},
|
||||
calculateTableHeight() {
|
||||
if (this.$refs.tableContainer) {
|
||||
this.tableHeight = this.$refs.tableContainer.offsetHeight || 500
|
||||
} else {
|
||||
this.tableHeight = 500
|
||||
}
|
||||
},
|
||||
getRequestData() {
|
||||
return {
|
||||
month: String(this.month)
|
||||
}
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true
|
||||
http({
|
||||
url: http.adornUrl('/master/statisticsBusinessVip/getExpiresByMonth'),
|
||||
method: 'post',
|
||||
data: http.adornData(this.getRequestData())
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 0) {
|
||||
const countData = data.count || {}
|
||||
this.dataList = data.resultList || []
|
||||
this.summaryData = {
|
||||
total: data.total || 0,
|
||||
zyCount: countData.zyCount || 0,
|
||||
zjcount: countData.zjCount || countData.zjcount || 0,
|
||||
zxhtCount: countData.zxhtCount || 0,
|
||||
zlcount: countData.zlCount || countData.zlcount || 0,
|
||||
xlCount: countData.xlCount || 0,
|
||||
yxSuperCount: countData.yxSuperCount || 0,
|
||||
gxSuperCount: countData.gxSuperCount || 0
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.calculateTableHeight()
|
||||
})
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
exportData() {
|
||||
this.exportLoading = true
|
||||
http({
|
||||
url: http.adornUrl('/master/statisticsBusinessVip/exportExpiresByMonth'),
|
||||
method: 'post',
|
||||
data: http.adornData(this.getRequestData()),
|
||||
responseType: 'blob'
|
||||
}).then((response) => {
|
||||
const blob = new Blob([response.data])
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = `${this.title}VIP统计报表.xlsx`
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
}).finally(() => {
|
||||
this.exportLoading = false
|
||||
})
|
||||
},
|
||||
formatYear(year) {
|
||||
if (year === '' || year === null || year === undefined) {
|
||||
return '--'
|
||||
}
|
||||
return `${year}年`
|
||||
},
|
||||
formatAmount(amount) {
|
||||
if (amount === '' || amount === null || amount === undefined) {
|
||||
return '--'
|
||||
}
|
||||
return Number(amount).toFixed(2).replace(/\.00$/, '')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
/deep/ .el-table .cell,
|
||||
/deep/ .el-table th > .cell {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.vip-expires-statistics {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
height: calc(100vh - 220px);
|
||||
}
|
||||
|
||||
.summary-section,
|
||||
.detail-section {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin-bottom: 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
padding: 16px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(180deg, #f7fbff 0%, #ffffff 100%);
|
||||
box-shadow: 0 4px 12px rgba(31, 45, 61, 0.06);
|
||||
}
|
||||
|
||||
.summary-card__label {
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.summary-card__value {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #1f2d3d;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<expires-statistics-base
|
||||
title="近3个月临到期"
|
||||
:month="3"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import expiresStatisticsBase from './expiresStatisticsBase'
|
||||
|
||||
export default {
|
||||
components: { expiresStatisticsBase }
|
||||
}
|
||||
</script>
|
||||
48
src/views/modules/statisticsBusiness/vipStatistics/index.vue
Normal file
48
src/views/modules/statisticsBusiness/vipStatistics/index.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div class="mod-config">
|
||||
<el-tabs v-model="activeTab" type="card">
|
||||
<el-tab-pane label="近3个月临到期" name="expiresThreeMonths" />
|
||||
<el-tab-pane label="近半年临到期" name="expiresSixMonths" />
|
||||
<el-tab-pane label="全部" name="allVip" />
|
||||
<el-tab-pane label="年" name="yearVip" />
|
||||
<el-tab-pane label="月" name="monthVip" />
|
||||
</el-tabs>
|
||||
<keep-alive>
|
||||
<component :is="currentComponent" />
|
||||
</keep-alive>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import expiresThreeMonths from './expiresThreeMonths'
|
||||
import expiresSixMonths from './expiresSixMonths'
|
||||
import allVip from './allVip'
|
||||
import yearVip from './yearVip'
|
||||
import monthVip from './monthVip'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
expiresThreeMonths,
|
||||
expiresSixMonths,
|
||||
allVip,
|
||||
yearVip,
|
||||
monthVip
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'expiresThreeMonths'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentComponent() {
|
||||
return this.activeTab
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-form .el-form-item {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
</style>
|
||||
356
src/views/modules/statisticsBusiness/vipStatistics/monthVip.vue
Normal file
356
src/views/modules/statisticsBusiness/vipStatistics/monthVip.vue
Normal file
@@ -0,0 +1,356 @@
|
||||
<template>
|
||||
<div class="month-vip-statistics" v-loading="loading">
|
||||
<div class="query-section">
|
||||
<el-form :inline="true" @keyup.enter.native="getDataList">
|
||||
<el-form-item label="统计月份">
|
||||
<el-date-picker
|
||||
v-model="currentMonth"
|
||||
type="month"
|
||||
format="yyyy-MM"
|
||||
value-format="yyyy-MM"
|
||||
placeholder="选择月份"
|
||||
size="small"
|
||||
popper-append-to-body
|
||||
:picker-options="pickerOptions"
|
||||
@change="getDataList"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" size="small" @click="getDataList">刷新</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
:loading="exportLoading"
|
||||
@click="exportData"
|
||||
>下载报表</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="summary-section">
|
||||
<div
|
||||
v-for="(card, index) in summaryCards"
|
||||
:key="card.title + index"
|
||||
:class="[
|
||||
'summary-card',
|
||||
{
|
||||
'summary-card--two-columns': isTwoColumnCard(card)
|
||||
}
|
||||
]"
|
||||
>
|
||||
<div class="summary-card__title">{{ card.title }}</div>
|
||||
<div class="summary-card__body">
|
||||
<div
|
||||
v-for="line in card.lines"
|
||||
:key="card.title + line.label"
|
||||
class="summary-line"
|
||||
>
|
||||
<span class="summary-line__label">{{ line.label }}</span>
|
||||
<span class="summary-line__value">{{ line.value }}{{ line.unit || '' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-header">
|
||||
<div class="detail-title">月度VIP明细({{ dataList.length }})</div>
|
||||
</div>
|
||||
|
||||
<div ref="tableContainer" class="table-container">
|
||||
<el-table
|
||||
:data="dataList"
|
||||
border
|
||||
:header-cell-style="{ textAlign: 'center', padding: '6px 0' }"
|
||||
:cell-style="{ textAlign: 'center', padding: '6px 0' }"
|
||||
:max-height="tableHeight"
|
||||
>
|
||||
<el-table-column prop="time" label="时间" min-width="120" />
|
||||
<el-table-column prop="name" label="姓名" min-width="120" />
|
||||
<el-table-column prop="tel" label="电话" min-width="140" />
|
||||
<el-table-column prop="vipType" label="VIP类型" min-width="150" />
|
||||
<el-table-column prop="isYan" label="是否延期" min-width="110" />
|
||||
<el-table-column prop="currentYear" label="本次年限" min-width="110" />
|
||||
<el-table-column prop="currentAmount" label="本次金额" min-width="130">
|
||||
<template slot-scope="scope">
|
||||
{{ formatAmountCell(scope.row.currentAmount) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="currentStartTime" label="本次开始时间" min-width="140" />
|
||||
<el-table-column prop="currentEndTime" label="本次结束时间" min-width="140" />
|
||||
<el-table-column prop="endTime" label="到期时间" min-width="140" />
|
||||
<el-table-column prop="isReBuy" label="是否为复购" min-width="110" />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import http from '@/utils/httpRequest'
|
||||
import {
|
||||
buildMonthVipViewData,
|
||||
getCurrentMonthValue,
|
||||
getPreviousMonthValue,
|
||||
normalizeMonthValue
|
||||
} from './monthVipDataHelper'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
exportLoading: false,
|
||||
currentMonth: getCurrentMonthValue(),
|
||||
summaryCards: [],
|
||||
dataList: [],
|
||||
tableHeight: null,
|
||||
pickerOptions: {
|
||||
disabledDate(time) {
|
||||
const now = new Date()
|
||||
return time.getTime() > new Date(now.getFullYear(), now.getMonth(), 1).getTime()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
activated() {
|
||||
this.getDataList()
|
||||
this.$nextTick(() => {
|
||||
this.calculateTableHeight()
|
||||
})
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('resize', this.handleResize)
|
||||
this.$nextTick(() => {
|
||||
this.calculateTableHeight()
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.handleResize)
|
||||
},
|
||||
methods: {
|
||||
handleResize() {
|
||||
this.calculateTableHeight()
|
||||
},
|
||||
calculateTableHeight() {
|
||||
if (this.$refs.tableContainer) {
|
||||
this.tableHeight = this.$refs.tableContainer.offsetHeight || 500
|
||||
} else {
|
||||
this.tableHeight = 500
|
||||
}
|
||||
},
|
||||
fetchMonthData(date) {
|
||||
const normalizedDate = normalizeMonthValue(date)
|
||||
return http({
|
||||
url: http.adornUrl('/master/statisticsBusinessVip/getUserVipByMonth'),
|
||||
method: 'post',
|
||||
data: http.adornData({ date: normalizedDate })
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 0) {
|
||||
return data
|
||||
}
|
||||
return {}
|
||||
}).catch(() => ({}))
|
||||
},
|
||||
getDataList() {
|
||||
if (!this.currentMonth) {
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
const currentMonth = normalizeMonthValue(this.currentMonth)
|
||||
const previousMonth = getPreviousMonthValue(currentMonth)
|
||||
this.currentMonth = currentMonth
|
||||
|
||||
Promise.all([
|
||||
this.fetchMonthData(currentMonth),
|
||||
this.fetchMonthData(previousMonth)
|
||||
]).then(([currentData, previousData]) => {
|
||||
const viewData = buildMonthVipViewData(currentData, previousData)
|
||||
this.summaryCards = viewData.cards
|
||||
this.dataList = viewData.tableRows
|
||||
this.$nextTick(() => {
|
||||
this.calculateTableHeight()
|
||||
})
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
exportData() {
|
||||
const currentMonth = normalizeMonthValue(this.currentMonth)
|
||||
this.currentMonth = currentMonth
|
||||
this.exportLoading = true
|
||||
|
||||
http({
|
||||
url: http.adornUrl('/master/statisticsBusinessVip/exportUserVipByMonth'),
|
||||
method: 'post',
|
||||
data: http.adornData({ date: currentMonth }),
|
||||
responseType: 'blob'
|
||||
}).then((response) => {
|
||||
const blob = new Blob([response.data])
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = `${currentMonth}月VIP统计报表.xlsx`
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
}).catch(() => {
|
||||
this.$message.error('下载失败,请稍后重试')
|
||||
}).finally(() => {
|
||||
this.exportLoading = false
|
||||
})
|
||||
},
|
||||
formatAmountCell(value) {
|
||||
if (value === '--') {
|
||||
return value
|
||||
}
|
||||
return value + '元'
|
||||
},
|
||||
isTwoColumnCard(card) {
|
||||
return ['办理统计', '续费率'].includes(card.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/deep/ .el-table .cell,
|
||||
/deep/ .el-table th > .cell {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.month-vip-statistics {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
height: calc(100vh - 220px);
|
||||
}
|
||||
|
||||
.query-section,
|
||||
.summary-section,
|
||||
.detail-section {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.summary-section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 260px;
|
||||
width: 260px;
|
||||
min-height: 190px;
|
||||
padding: 16px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(180deg, #f8fbff 0%, #ffffff 100%);
|
||||
box-shadow: 0 4px 12px rgba(31, 45, 61, 0.06);
|
||||
}
|
||||
|
||||
.summary-card--two-columns {
|
||||
flex-basis: 360px;
|
||||
width: 360px;
|
||||
}
|
||||
|
||||
.summary-card__title {
|
||||
margin-bottom: 14px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.summary-card__body {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.summary-card--two-columns .summary-card__body {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px 30px;
|
||||
}
|
||||
|
||||
.summary-line {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.summary-line__label {
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.summary-line__value {
|
||||
font-weight: 700;
|
||||
color: #1f2d3d;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 1680px) {
|
||||
.summary-card {
|
||||
flex-basis: 240px;
|
||||
width: 240px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.summary-card,
|
||||
.summary-card--two-columns {
|
||||
flex-basis: calc(50% - 6px);
|
||||
width: calc(50% - 6px);
|
||||
}
|
||||
|
||||
.summary-card--two-columns .summary-card__body {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.summary-card,
|
||||
.summary-card--two-columns {
|
||||
flex-basis: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,191 @@
|
||||
function toNumber(value) {
|
||||
const num = Number(value)
|
||||
return Number.isNaN(num) ? 0 : num
|
||||
}
|
||||
|
||||
function getAmount(value) {
|
||||
return toNumber(value).toFixed(2).replace(/\.00$/, '')
|
||||
}
|
||||
|
||||
function formatRate(current, previous) {
|
||||
const currentValue = toNumber(current)
|
||||
const previousValue = toNumber(previous)
|
||||
|
||||
if (previousValue === 0) {
|
||||
return currentValue === 0 ? '0.00%' : '--'
|
||||
}
|
||||
|
||||
return (((currentValue - previousValue) / previousValue) * 100).toFixed(2) + '%'
|
||||
}
|
||||
|
||||
function formatYear(year) {
|
||||
if (year === '' || year === null || year === undefined) {
|
||||
return '--'
|
||||
}
|
||||
return year + '年'
|
||||
}
|
||||
|
||||
function formatText(value) {
|
||||
if (value === '' || value === null || value === undefined) {
|
||||
return '--'
|
||||
}
|
||||
|
||||
const text = String(value).trim()
|
||||
return text || '--'
|
||||
}
|
||||
|
||||
function getDynamicKeys(...sources) {
|
||||
const keys = []
|
||||
|
||||
sources.forEach((source) => {
|
||||
Object.keys(source || {}).forEach((key) => {
|
||||
const text = formatText(key)
|
||||
if (text !== '--' && !keys.includes(text)) {
|
||||
keys.push(text)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
function buildCountLines(totalLabel, totalValue, counts) {
|
||||
const dynamicLines = getDynamicKeys(counts).map((key) => ({
|
||||
label: key,
|
||||
value: toNumber(counts[key]),
|
||||
unit: '人'
|
||||
}))
|
||||
|
||||
return [{ label: totalLabel, value: toNumber(totalValue), unit: '人' }].concat(dynamicLines)
|
||||
}
|
||||
|
||||
function buildRenewalLines(currentData) {
|
||||
const dynamicKeys = getDynamicKeys(currentData.banCounts, currentData.yanCounts)
|
||||
|
||||
return dynamicKeys.map((key) => ({
|
||||
label: key,
|
||||
value: '开发中',
|
||||
unit: ''
|
||||
}))
|
||||
}
|
||||
|
||||
function buildGrowthLines(currentData, previousData) {
|
||||
return [
|
||||
{
|
||||
label: '办理总人数环比增长率',
|
||||
value: formatRate(currentData.banTotalCount, previousData.banTotalCount)
|
||||
},
|
||||
{
|
||||
label: '延期总人数环比增长率',
|
||||
value: formatRate(currentData.yanTotalCount, previousData.yanTotalCount)
|
||||
},
|
||||
{
|
||||
label: '办理金额环比增长率',
|
||||
value: formatRate(currentData.banTotalPrice, previousData.banTotalPrice)
|
||||
},
|
||||
{
|
||||
label: '延期金额环比增长率',
|
||||
value: formatRate(currentData.yanTotalPrice, previousData.yanTotalPrice)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
function buildCards(currentData, previousData) {
|
||||
return [
|
||||
{
|
||||
title: '办理统计',
|
||||
lines: buildCountLines('办理总人数', currentData.banTotalCount, currentData.banCounts)
|
||||
},
|
||||
{
|
||||
title: '延期统计',
|
||||
lines: buildCountLines('延期总人数', currentData.yanTotalCount, currentData.yanCounts)
|
||||
},
|
||||
{
|
||||
title: '金额统计',
|
||||
lines: [
|
||||
{ label: '总办理金额', value: getAmount(currentData.banTotalPrice), unit: '元' },
|
||||
{ label: '总延期金额', value: getAmount(currentData.yanTotalPrice), unit: '元' },
|
||||
{
|
||||
label: '合计总金额',
|
||||
value: getAmount(toNumber(currentData.banTotalPrice) + toNumber(currentData.yanTotalPrice)),
|
||||
unit: '元'
|
||||
}
|
||||
]
|
||||
},
|
||||
// {
|
||||
// title: '续费率',
|
||||
// lines: buildRenewalLines(currentData)
|
||||
// },
|
||||
{
|
||||
title: '环比增长率',
|
||||
lines: buildGrowthLines(currentData, previousData)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
function buildTableRows(list) {
|
||||
return (list || []).map((item) => {
|
||||
return {
|
||||
time: formatText(item.payTime),
|
||||
name: formatText(item.name),
|
||||
tel: formatText(item.tel),
|
||||
vipType: formatText(item.vipType),
|
||||
isYan: formatText(item.isYan),
|
||||
currentYear: formatYear(item.year),
|
||||
currentAmount: getAmount(item.price),
|
||||
currentStartTime: formatText(item.uvlStartTime || item.startTime),
|
||||
currentEndTime: formatText(item.uvlEndTime || item.endTime),
|
||||
endTime: formatText(item.endTime || item.uvlEndTime),
|
||||
isReBuy: formatText(item.isReBuy)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function getCurrentMonthValue() {
|
||||
const now = new Date()
|
||||
const year = now.getFullYear()
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0')
|
||||
return `${year}-${month}`
|
||||
}
|
||||
|
||||
export function normalizeMonthValue(value) {
|
||||
if (!value) {
|
||||
return getCurrentMonthValue()
|
||||
}
|
||||
|
||||
if (Object.prototype.toString.call(value) === '[object Date]' && !Number.isNaN(value.getTime())) {
|
||||
const year = value.getFullYear()
|
||||
const month = String(value.getMonth() + 1).padStart(2, '0')
|
||||
return `${year}-${month}`
|
||||
}
|
||||
|
||||
const text = String(value).trim()
|
||||
const matched = text.match(/^(\d{4})-(\d{1,2})/)
|
||||
if (matched) {
|
||||
return `${matched[1]}-${String(matched[2]).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
return getCurrentMonthValue()
|
||||
}
|
||||
|
||||
export function getPreviousMonthValue(monthValue) {
|
||||
const normalizedMonthValue = normalizeMonthValue(monthValue)
|
||||
const parts = normalizedMonthValue.split('-')
|
||||
if (parts.length !== 2) {
|
||||
return getCurrentMonthValue()
|
||||
}
|
||||
|
||||
const year = Number(parts[0])
|
||||
const month = Number(parts[1])
|
||||
const date = new Date(year, month - 2, 1)
|
||||
const prevYear = date.getFullYear()
|
||||
const prevMonth = String(date.getMonth() + 1).padStart(2, '0')
|
||||
return `${prevYear}-${prevMonth}`
|
||||
}
|
||||
|
||||
export function buildMonthVipViewData(currentData = {}, previousData = {}) {
|
||||
return {
|
||||
cards: buildCards(currentData, previousData),
|
||||
tableRows: buildTableRows(currentData.allResultList)
|
||||
}
|
||||
}
|
||||
348
src/views/modules/statisticsBusiness/vipStatistics/yearVip.vue
Normal file
348
src/views/modules/statisticsBusiness/vipStatistics/yearVip.vue
Normal file
@@ -0,0 +1,348 @@
|
||||
<template>
|
||||
<div class="year-vip-statistics" v-loading="loading">
|
||||
<div class="query-section">
|
||||
<el-form :inline="true" @keyup.enter.native="getDataList">
|
||||
<el-form-item label="统计年份">
|
||||
<el-date-picker
|
||||
v-model="currentYear"
|
||||
type="year"
|
||||
format="yyyy"
|
||||
value-format="yyyy"
|
||||
placeholder="选择年份"
|
||||
size="small"
|
||||
popper-append-to-body
|
||||
:picker-options="pickerOptions"
|
||||
@change="getDataList"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" size="small" @click="getDataList">刷新</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
:loading="exportLoading"
|
||||
@click="exportData"
|
||||
>下载报表</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="summary-section">
|
||||
<div
|
||||
v-for="(card, index) in summaryCards"
|
||||
:key="card.title + index"
|
||||
:class="[
|
||||
'summary-card',
|
||||
{
|
||||
'summary-card--two-columns': isTwoColumnCard(card)
|
||||
}
|
||||
]"
|
||||
>
|
||||
<div class="summary-card__title">{{ card.title }}</div>
|
||||
<div class="summary-card__body">
|
||||
<div
|
||||
v-for="line in card.lines"
|
||||
:key="card.title + line.label"
|
||||
class="summary-line"
|
||||
>
|
||||
<span class="summary-line__label">{{ line.label }}</span>
|
||||
<span class="summary-line__value">{{ line.value }}{{ line.unit || '' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-section table-section--orange">
|
||||
<div class="table-title">VIP办理情况统计</div>
|
||||
<el-table
|
||||
:data="handleTableRows"
|
||||
border
|
||||
:header-cell-style="orangeTableHeaderStyle"
|
||||
:cell-style="orangeTableCellStyle"
|
||||
>
|
||||
<el-table-column prop="typeName" label="" min-width="140" />
|
||||
<el-table-column prop="ban3" label="首次办理三年" min-width="120" />
|
||||
<el-table-column prop="ban4" label="首次办理四年" min-width="120" />
|
||||
<el-table-column prop="banOther" label="首次办理其他" min-width="120" />
|
||||
<el-table-column prop="yan1" label="延期一年" min-width="110" />
|
||||
<el-table-column prop="yan3" label="延期三年" min-width="110" />
|
||||
<el-table-column prop="yan4" label="延期四年" min-width="110" />
|
||||
<el-table-column prop="yanOther" label="延期办理其他" min-width="120" />
|
||||
<el-table-column prop="total" label="合计" min-width="110" />
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<div class="table-section table-section--yellow">
|
||||
<div class="table-title">VIP分类年度办理人数占比</div>
|
||||
<el-table
|
||||
:data="ratioTableRows"
|
||||
border
|
||||
:header-cell-style="yellowTableHeaderStyle"
|
||||
:cell-style="yellowTableCellStyle"
|
||||
>
|
||||
<el-table-column prop="typeName" label="分类" min-width="180" />
|
||||
<el-table-column prop="ratio" label="年度办理人数占比" min-width="180" />
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<div class="table-section table-section--blue">
|
||||
<div class="table-title">每月明细</div>
|
||||
<el-table
|
||||
:data="monthTableRows"
|
||||
border
|
||||
:header-cell-style="blueTableHeaderStyle"
|
||||
:cell-style="blueTableCellStyle"
|
||||
>
|
||||
<el-table-column prop="monthLabel" label="办理时间" min-width="140" />
|
||||
<el-table-column prop="banCount" label="办理人数" min-width="140" />
|
||||
<el-table-column prop="yanCount" label="延期人数" min-width="140" />
|
||||
<el-table-column prop="amount" label="缴费金额" min-width="160">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.amount }}元
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import http from '@/utils/httpRequest'
|
||||
import {
|
||||
buildYearVipViewData,
|
||||
getCurrentYearValue,
|
||||
getPreviousYearValue,
|
||||
normalizeYearValue
|
||||
} from './yearVipDataHelper'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
exportLoading: false,
|
||||
currentYear: getCurrentYearValue(),
|
||||
summaryCards: [],
|
||||
handleTableRows: [],
|
||||
ratioTableRows: [],
|
||||
monthTableRows: [],
|
||||
pickerOptions: {
|
||||
disabledDate(time) {
|
||||
const now = new Date()
|
||||
return time.getFullYear() > now.getFullYear()
|
||||
}
|
||||
},
|
||||
orangeTableHeaderStyle: {
|
||||
textAlign: 'center',
|
||||
color: '#333333',
|
||||
padding: '6px 0'
|
||||
},
|
||||
orangeTableCellStyle: {
|
||||
textAlign: 'center',
|
||||
color: '#333333',
|
||||
padding: '6px 0'
|
||||
},
|
||||
yellowTableHeaderStyle: {
|
||||
textAlign: 'center',
|
||||
color: '#333333',
|
||||
padding: '6px 0'
|
||||
},
|
||||
yellowTableCellStyle: {
|
||||
textAlign: 'center',
|
||||
color: '#333333',
|
||||
padding: '6px 0'
|
||||
},
|
||||
blueTableHeaderStyle: {
|
||||
textAlign: 'center',
|
||||
color: '#333333',
|
||||
padding: '6px 0'
|
||||
},
|
||||
blueTableCellStyle: {
|
||||
textAlign: 'center',
|
||||
color: '#333333',
|
||||
padding: '6px 0'
|
||||
}
|
||||
}
|
||||
},
|
||||
activated() {
|
||||
this.getDataList()
|
||||
},
|
||||
methods: {
|
||||
fetchYearData(year) {
|
||||
const normalizedYear = normalizeYearValue(year)
|
||||
return http({
|
||||
url: http.adornUrl('/master/statisticsBusinessVip/getUserVipByYear'),
|
||||
method: 'post',
|
||||
data: http.adornData({ year: normalizedYear })
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 0) {
|
||||
return data
|
||||
}
|
||||
return {}
|
||||
}).catch(() => ({}))
|
||||
},
|
||||
getDataList() {
|
||||
const currentYear = normalizeYearValue(this.currentYear)
|
||||
const previousYear = getPreviousYearValue(currentYear)
|
||||
this.currentYear = currentYear
|
||||
this.loading = true
|
||||
|
||||
Promise.all([
|
||||
this.fetchYearData(currentYear),
|
||||
this.fetchYearData(previousYear)
|
||||
]).then(([currentData, previousData]) => {
|
||||
const viewData = buildYearVipViewData(currentData, previousData)
|
||||
this.summaryCards = viewData.summaryCards
|
||||
this.handleTableRows = viewData.handleTableRows
|
||||
this.ratioTableRows = viewData.ratioTableRows
|
||||
this.monthTableRows = viewData.monthTableRows
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
exportData() {
|
||||
const year = normalizeYearValue(this.currentYear)
|
||||
this.currentYear = year
|
||||
this.exportLoading = true
|
||||
|
||||
http({
|
||||
url: http.adornUrl('/master/statisticsBusinessVip/exportUserVipByYear'),
|
||||
method: 'post',
|
||||
data: http.adornData({ year }),
|
||||
responseType: 'blob'
|
||||
}).then((response) => {
|
||||
const blob = new Blob([response.data])
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = `${year}年VIP统计报表.xlsx`
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
}).catch(() => {
|
||||
this.$message.error('下载失败,请稍后重试')
|
||||
}).finally(() => {
|
||||
this.exportLoading = false
|
||||
})
|
||||
},
|
||||
isTwoColumnCard(card) {
|
||||
return ['办理统计', '延期统计'].includes(card.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/deep/ .el-table .cell,
|
||||
/deep/ .el-table th > .cell {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.year-vip-statistics {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.query-section,
|
||||
.summary-section,
|
||||
.table-section {
|
||||
padding: 8px;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.summary-section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
display: flex;
|
||||
flex: 1 1 260px;
|
||||
width: 260px;
|
||||
flex-direction: column;
|
||||
min-height: 180px;
|
||||
padding: 16px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(180deg, #f8fbff 0%, #ffffff 100%);
|
||||
box-shadow: 0 4px 12px rgba(31, 45, 61, 0.06);
|
||||
}
|
||||
|
||||
.summary-card--two-columns {
|
||||
flex-basis: 360px;
|
||||
width: 360px;
|
||||
}
|
||||
|
||||
.summary-card__title {
|
||||
margin-bottom: 14px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.summary-card__body {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.summary-card--two-columns .summary-card__body {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px 30px;
|
||||
}
|
||||
|
||||
.summary-line {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.summary-line__label {
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.summary-line__value {
|
||||
font-weight: 700;
|
||||
color: #1f2d3d;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.table-title {
|
||||
margin-bottom: 12px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.summary-card,
|
||||
.summary-card--two-columns {
|
||||
flex-basis: calc(50% - 6px);
|
||||
width: calc(50% - 6px);
|
||||
}
|
||||
|
||||
.summary-card--two-columns .summary-card__body {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.summary-card,
|
||||
.summary-card--two-columns {
|
||||
flex-basis: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,247 @@
|
||||
const TYPE_ORDER = [
|
||||
'中医学',
|
||||
'中西汇通学',
|
||||
'肿瘤学',
|
||||
'针灸学',
|
||||
'心理学',
|
||||
'国学',
|
||||
'医学超级',
|
||||
'心理国学超级'
|
||||
]
|
||||
|
||||
const TYPE_ALIAS_MAP = {
|
||||
国学心理学超级: '心理国学超级'
|
||||
}
|
||||
|
||||
function toNumber(value) {
|
||||
const num = Number(value)
|
||||
return Number.isNaN(num) ? 0 : num
|
||||
}
|
||||
|
||||
function formatAmount(value) {
|
||||
return toNumber(value).toFixed(2).replace(/\.00$/, '')
|
||||
}
|
||||
|
||||
function normalizeTypeName(name) {
|
||||
return TYPE_ALIAS_MAP[name] || name
|
||||
}
|
||||
|
||||
function getCurrentYearValue() {
|
||||
return String(new Date().getFullYear())
|
||||
}
|
||||
|
||||
function normalizeYearValue(value) {
|
||||
if (!value) {
|
||||
return getCurrentYearValue()
|
||||
}
|
||||
|
||||
if (Object.prototype.toString.call(value) === '[object Date]' && !Number.isNaN(value.getTime())) {
|
||||
return String(value.getFullYear())
|
||||
}
|
||||
|
||||
const matched = String(value).trim().match(/^(\d{4})/)
|
||||
if (matched) {
|
||||
return matched[1]
|
||||
}
|
||||
|
||||
return getCurrentYearValue()
|
||||
}
|
||||
|
||||
function getPreviousYearValue(yearValue) {
|
||||
return String(Number(normalizeYearValue(yearValue)) - 1)
|
||||
}
|
||||
|
||||
function getDynamicTypeKeys(...sources) {
|
||||
const keys = []
|
||||
|
||||
sources.forEach((source) => {
|
||||
Object.keys(source || {}).forEach((key) => {
|
||||
const normalizedKey = normalizeTypeName(key)
|
||||
if (!keys.includes(normalizedKey)) {
|
||||
keys.push(normalizedKey)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const orderedKeys = TYPE_ORDER.filter((key) => keys.includes(key))
|
||||
const restKeys = keys.filter((key) => !TYPE_ORDER.includes(key))
|
||||
return orderedKeys.concat(restKeys)
|
||||
}
|
||||
|
||||
function getMappedCount(source, typeName, yearKey) {
|
||||
const originalKey = Object.keys(source || {}).find((key) => normalizeTypeName(key) === typeName)
|
||||
if (!originalKey) {
|
||||
return 0
|
||||
}
|
||||
return toNumber(((source || {})[originalKey] || {})[yearKey])
|
||||
}
|
||||
|
||||
function getMappedValue(source, typeName) {
|
||||
const originalKey = Object.keys(source || {}).find((key) => normalizeTypeName(key) === typeName)
|
||||
if (!originalKey) {
|
||||
return 0
|
||||
}
|
||||
return toNumber((source || {})[originalKey])
|
||||
}
|
||||
|
||||
function buildCountLines(totalLabel, totalValue, counts) {
|
||||
return [{ label: totalLabel, value: toNumber(totalValue), unit: '人' }].concat(
|
||||
getDynamicTypeKeys(counts).map((typeName) => ({
|
||||
label: typeName,
|
||||
value: getMappedValue(counts, typeName),
|
||||
unit: '人'
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
function formatYearRate(current, previous) {
|
||||
const currentValue = toNumber(current)
|
||||
const previousValue = toNumber(previous)
|
||||
|
||||
if (currentValue === 0) {
|
||||
return '0.00%'
|
||||
}
|
||||
|
||||
return `${(((currentValue - previousValue) / currentValue) * 100).toFixed(2)}%`
|
||||
}
|
||||
|
||||
function buildSummaryCards(currentData, previousData) {
|
||||
return [
|
||||
{
|
||||
title: '办理统计',
|
||||
lines: buildCountLines('办理总人数', currentData.banTotalCount, currentData.banTypeTotalCounts)
|
||||
},
|
||||
{
|
||||
title: '延期统计',
|
||||
lines: buildCountLines('延期总人数', currentData.yanTotalCount, currentData.yanTypeTotalCounts)
|
||||
},
|
||||
{
|
||||
title: '金额统计',
|
||||
lines: [
|
||||
{ label: '总办理金额', value: formatAmount(currentData.banTotalPrice), unit: '元' },
|
||||
{ label: '总延期金额', value: formatAmount(currentData.yanTotalPrice), unit: '元' },
|
||||
{
|
||||
label: '合计总金额',
|
||||
value: formatAmount(toNumber(currentData.banTotalPrice) + toNumber(currentData.yanTotalPrice)),
|
||||
unit: '元'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '同比增长率',
|
||||
lines: [
|
||||
{
|
||||
label: '办理总人数同比增长率',
|
||||
value: formatYearRate(currentData.banTotalCount, previousData.banTotalCount)
|
||||
},
|
||||
{
|
||||
label: '延期总人数同比增长率',
|
||||
value: formatYearRate(currentData.yanTotalCount, previousData.yanTotalCount)
|
||||
},
|
||||
{
|
||||
label: '办理金额同比增长率',
|
||||
value: formatYearRate(currentData.banTotalPrice, previousData.banTotalPrice)
|
||||
},
|
||||
{
|
||||
label: '延期金额同比增长率',
|
||||
value: formatYearRate(currentData.yanTotalPrice, previousData.yanTotalPrice)
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
function buildHandleTableRows(currentData) {
|
||||
const typeKeys = getDynamicTypeKeys(currentData.banCounts, currentData.yanCounts)
|
||||
const rows = typeKeys.map((typeName) => {
|
||||
const ban3 = getMappedCount(currentData.banCounts, typeName, '3')
|
||||
const ban4 = getMappedCount(currentData.banCounts, typeName, '4')
|
||||
const banOther = getMappedCount(currentData.banCounts, typeName, '其他')
|
||||
const yan1 = getMappedCount(currentData.yanCounts, typeName, '1')
|
||||
const yan3 = getMappedCount(currentData.yanCounts, typeName, '3')
|
||||
const yan4 = getMappedCount(currentData.yanCounts, typeName, '4')
|
||||
const yanOther = getMappedCount(currentData.yanCounts, typeName, '其他')
|
||||
|
||||
return {
|
||||
typeName,
|
||||
ban3,
|
||||
ban4,
|
||||
banOther,
|
||||
yan1,
|
||||
yan3,
|
||||
yan4,
|
||||
yanOther,
|
||||
total: ban3 + ban4 + banOther + yan1 + yan3 + yan4 + yanOther
|
||||
}
|
||||
})
|
||||
|
||||
const totalRow = rows.reduce((acc, item) => {
|
||||
acc.ban3 += item.ban3
|
||||
acc.ban4 += item.ban4
|
||||
acc.banOther += item.banOther
|
||||
acc.yan1 += item.yan1
|
||||
acc.yan3 += item.yan3
|
||||
acc.yan4 += item.yan4
|
||||
acc.yanOther += item.yanOther
|
||||
acc.total += item.total
|
||||
return acc
|
||||
}, {
|
||||
typeName: '合计',
|
||||
ban3: 0,
|
||||
ban4: 0,
|
||||
banOther: 0,
|
||||
yan1: 0,
|
||||
yan3: 0,
|
||||
yan4: 0,
|
||||
yanOther: 0,
|
||||
total: 0
|
||||
})
|
||||
|
||||
return rows.concat(totalRow)
|
||||
}
|
||||
|
||||
function buildRatioTableRows(currentData) {
|
||||
return getDynamicTypeKeys(currentData.banTypeRatios).map((typeName) => {
|
||||
const originalKey = Object.keys(currentData.banTypeRatios || {}).find((key) => normalizeTypeName(key) === typeName)
|
||||
return {
|
||||
typeName,
|
||||
ratio: (currentData.banTypeRatios || {})[originalKey] || '0%'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function buildMonthTableRows(currentData) {
|
||||
const rows = Array.from({ length: 12 }, (_, index) => {
|
||||
const month = String(index + 1)
|
||||
return {
|
||||
monthLabel: `${month}月`,
|
||||
banCount: toNumber((currentData.banMonthCounts || {})[month]),
|
||||
yanCount: toNumber((currentData.yanMonthCounts || {})[month]),
|
||||
amount: formatAmount((currentData.monthAmounts || {})[month])
|
||||
}
|
||||
})
|
||||
|
||||
rows.push({
|
||||
monthLabel: '合计',
|
||||
banCount: toNumber(currentData.banTotalCount),
|
||||
yanCount: toNumber(currentData.yanTotalCount),
|
||||
amount: formatAmount(currentData.banTotalPrice)
|
||||
})
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
export {
|
||||
getCurrentYearValue,
|
||||
getPreviousYearValue,
|
||||
normalizeYearValue
|
||||
}
|
||||
|
||||
export function buildYearVipViewData(currentData = {}, previousData = {}) {
|
||||
return {
|
||||
summaryCards: buildSummaryCards(currentData, previousData),
|
||||
handleTableRows: buildHandleTableRows(currentData),
|
||||
ratioTableRows: buildRatioTableRows(currentData),
|
||||
monthTableRows: buildMonthTableRows(currentData)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user