16 Commits

Author SHA1 Message Date
2e3fcadc14 feat(医疗模块): 更新关联用户功能,优化表单处理
- 将关联用户输入框的大小调整为大号,提升用户体验
- 在表单数据中添加用户ID字段,支持关联用户的选择
- 优化手机号和邮箱的自动补全逻辑,确保用户信息的准确性
- 增加调试信息以便于后续开发和问题排查
2026-03-23 17:42:39 +08:00
30237639c7 feat(医疗模块): 更新新增/修改弹窗表单,添加关联用户功能
- 在新增/修改弹窗中,添加关联用户的输入框,支持手机号/邮箱的自动补全
- 更新表单验证规则,确保关联用户为必填项
- 调整表单项的样式,统一标签宽度,提升用户体验
- 增加用户列表加载功能,优化用户选择流程
2026-03-23 16:02:59 +08:00
301b3a2582 feat(统计业务): 新增全部用户统计页面及相关功能
- 新增用户统计页面,支持按日、月、年统计用户数据
- 引入用户统计基础组件,包含数据展示和导出功能
- 更新主统计页面以集成新的统计选项
2026-03-23 16:02:46 +08:00
b5f280b02f style(统计业务): 统一统计页面样式和布局
- 引入公共样式文件,简化各统计页面的样式管理
- 优化统计页面的容器布局,采用flex布局以提升响应式表现
- 统一表单项的样式,确保一致性和可读性
- 移除冗余的样式定义,提升代码整洁度
2026-03-23 13:19:46 +08:00
44124b6931 feat(统计业务): 新增用户统计页面及相关组件
- 在路由中添加用户统计页面,支持按日、月、年统计用户数据
- 新增用户统计基础组件,包含数据展示和导出功能
- 实现用户统计明细表格,展示用户注册、登录等信息
- 添加用户留存率统计页面,支持选择月份并展示相关数据
- 集成数据导出功能,支持下载用户统计报表
2026-03-23 10:06:31 +08:00
f51dfda073 style(统计业务): 优化VIP统计页面表格样式和布局
- 为所有VIP统计页面的表格添加了统一的类名以便于样式管理
- 调整表格的最小高度,确保在不同数据情况下的显示效果
- 修改统计卡片的布局,采用flex布局以提高响应式表现
- 移除不必要的overflow属性,简化样式结构
2026-03-17 15:32:47 +08:00
6d5bea10a4 fix(统计业务): 修复VIP统计页面加载状态和表格样式
- 在所有VIP统计和到期统计页面中添加加载状态指示
- 更新表格头部和单元格样式,增加内边距以改善可读性
- 调整表格单元格的行高,确保内容显示更为美观
2026-03-16 16:15:41 +08:00
bba73fdc7c feat(统计业务): 新增月度和年度VIP统计页面及数据处理功能
- 添加月度VIP统计页面,支持选择月份并展示相关统计信息
- 添加年度VIP统计页面,支持选择年份并展示年度统计数据
- 实现数据导出功能,支持下载月度和年度VIP统计报表
- 集成表格展示VIP用户的详细信息,包括办理时间、姓名、电话等
- 新增数据处理辅助函数,支持统计数据的格式化和计算
2026-03-16 16:15:28 +08:00
f027838715 feat(统计业务): 新增全部VIP统计页面
- 添加VIP统计页面,展示到期和在期VIP用户的统计信息
- 实现数据导出功能,支持下载VIP统计报表
- 集成表格展示VIP用户的详细信息,包括办理时间、姓名、注册电话等
2026-03-13 18:03:27 +08:00
34f862bd4d feat(统计业务): 新增VIP临到期统计页面
- 添加近3个月和近半年临到期的VIP统计页面
- 引入通用的统计基础组件,支持数据展示和导出功能
- 更新主统计页面以集成新统计选项
2026-03-13 17:47:45 +08:00
688e2503fa feat(统计业务): 新增VIP统计路由配置
- 添加VIP统计页面,支持按VIP用户进行统计
- 在路由配置中新增VIP统计页面路由,集成到导航系统中
2026-03-13 17:46:58 +08:00
cc9fc9adde feat(统计业务): 新增课程统计页面,支持按金额、课程和分类统计
- 添加课程统计主页面,包含三个统计维度的标签页切换
- 实现按金额统计功能,支持年份选择和月度详情查看
- 实现按课程统计功能,支持年份/月份切换和分页展示
- 实现按分类统计功能,支持年份/月份切换
- 各统计页面均支持数据导出为Excel报表
- 添加路由配置,将课程统计页面集成到导航系统中
2026-03-13 16:17:50 +08:00
5138937dd4 feat(报表): 新增灵枢年度报表页面及路由配置
添加灵枢年度报表下载功能页面,包含年份选择器和报表下载功能
在路由配置中新增灵枢年度报表页面路由
2026-03-03 16:02:30 +08:00
537d5993bd refactor(订单模块): 1.统一订单状态和地址相关术语;2.增加心理论坛内容管理
将"发货"相关术语统一改为"发出",包括订单状态、按钮文字、提示信息等
将"收货"相关术语统一改为"收件",包括地址信息、表单标签、提示信息等
新增心理论坛模块,包含列表和新增/修改功能

调整订单状态显示为"待发出"和"已发出"
修改地址相关字段为"收件人"和"收件地址"
添加psychologicalForum.vue和psychologicalForum-add-or-update.vue文件
2026-02-27 16:54:21 +08:00
bc39a62001 feat(广告管理): 添加心灵空间应用类型支持
在广告管理页面中新增对"心灵空间"应用类型的显示和选择支持
2026-02-24 13:20:53 +08:00
8bcdb0c00c refactor(miniClass): 调整学员列表表格列并优化数据获取
- 将学员ID列宽从150调整为100,移除学员身份列
- 将“学员名称”列改为“学员姓名”并更新对应字段为student.name
- 新增邮箱列显示student.email字段
- 修改学员数据获取方式,使用getStudents接口替代getCLassInfo
- 注释掉分页组件及相关变量,为后续功能做准备
2026-02-09 10:25:45 +08:00
43 changed files with 4436 additions and 344 deletions

262
package-lock.json generated
View File

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

View File

@@ -80,20 +80,22 @@ const mainRoutes = {
{ path: '/talents-list', component: _import('modules/talents/talents-list'), name: 'talents-list', meta: { title: '太湖英才列表', isTab: true } },
{ path: '/talents-articleList', component: _import('modules/talents/talents-articleList'), name: 'talents-articleList', meta: { title: '文章列表', isTab: true } },
{ path: '/talents-commontList', component: _import('modules/talents/talents-commontList'), name: 'talents-commontList', meta: { title: '评论列表', isTab: true } },
{ path: '/course-taihumedList', component: _import('modules/course/taihumedList'), name: 'course-taihumedList', meta: { title: '太湖标签', isTab: true } },
{ path: '/courses-list', component: _import('modules/talents/courses-list'), name: 'courses-list', meta: { title: '查看课程', isTab: true } },
{ path: '/mergeList', component: _import('modules/certificate/mergeList'), name: 'mergeList', meta: { title: '小班、自考证书', isTab: true } },
{ path: '/medicalrecords-medicalList', component: _import('modules/medicalrecords/medicalList'), name: 'medicalrecords-medicalList', meta: { title: '医案列表', isTab: true } },
{ path: '/medicalrecords-userList', component: _import('modules/medicalrecords/userList'), name: 'medicalrecords-userList', meta: { title: '审核人员列表', isTab: true } },
{ path: '/vipList-userList', component: _import('modules/vipList/userList'), name: 'vipList-userList', meta: { title: 'VIP用户列表', isTab: true } },
{ path: '/vipList-vipDetail', component: _import('modules/vipList/vipDetail'), name: 'vipList-vipDetail', meta: { title: 'VIP详情', isTab: true } },
{ path: '/reportList-vipList', component: _import('modules/reportList/vipList'), name: 'reportList-vipList', meta: { title: 'VIP报表', isTab: true } },
{ path: '/reportList-vipList', component: _import('modules/reportList/vipList'), name: 'reportList-vipList', meta: { title: 'VIP 报表', isTab: true } },
{ path: '/reportList-trainingCourseList', component: _import('modules/reportList/trainingCourseList'), name: 'reportList-trainingCourseList', meta: { title: '培训班月度报表', isTab: true } },
{ 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: '/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 } },
{ path: '/statisticsBusiness-userStatistics', component: _import('modules/statisticsBusiness/userStatistics/index'), name: 'statisticsBusiness-userStatistics', meta: { title: '用户统计', isTab: true } },
],
beforeEnter (to, from, next) {
let token = Vue.cookie.get('token')

View File

@@ -60,7 +60,7 @@ export default {
this.getWorkDataList();
},
methods: {
// 待发列表
// 待发列表
getDataList() {
this.dataListLoading = true;
this.$http({

View File

@@ -23,6 +23,7 @@
<span v-if="scope.row.appType == 0">疯子读书</span>
<span v-if="scope.row.appType == 1">吴门医述</span>
<span v-if="scope.row.appType == 2">众妙之门</span>
<span v-if="scope.row.appType == 3">心灵空间</span>
</div>
</template>
</el-table-column>
@@ -178,7 +179,8 @@ export default {
APPList: [
{ label: "疯子读书", value: 0 },
{ label: "吴门医述", value: 1 },
{ label: "众妙之门", value: 2 }
{ label: "众妙之门", value: 2 },
{ label: "心灵空间", value: 3 }
],
dataForm: {
key: ""

View File

@@ -0,0 +1,114 @@
<template>
<el-dialog
width="1260px"
:title="!dataForm.id ? '新增' : '修改'"
:close-on-click-modal="false"
:visible.sync="visible"
@close="handlereset"
style="height: auto;"
>
<div style="margin-bottom: 60px;">
<el-form
style="height: auto;"
class="addFormBox"
:model="dataForm"
:rules="dataRule"
ref="dataForm"
label-width="80px"
>
<el-form-item label="链接地址" prop="url">
<el-input
v-model="dataForm.url"
placeholder="链接地址"
:rows="3"
></el-input>
</el-form-item>
</el-form>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="handlereset">取消</el-button>
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
</span>
</el-dialog>
</template>
<script>
import global from "../../common/common.vue"; //引入共用组间
export default {
data() {
return {
baseUrl: global.baseUrl,
visible: false,
dialogImageUrl: "",
dialogVisible: false,
dataForm: {
id: null,
title: '',
url: ''
},
dataRule: {
url: [
{ required: true, message: "链接地址不能为空", trigger: "blur" }
]
},
urlList: {
info: "/master/message/getMessageById",
add: "/common/wxPublicAccount/addWxPublicAccountArticle",
update: "/common/wxPublicAccount/updateWxPublicAccountArticle"
}
};
},
methods: {
init(row) {
this.dataForm = {
id: row && row.id || null,
url: row && row.url || ''
};
this.visible = true;
},
// 表单提交
dataFormSubmit() {
this.$refs["dataForm"].validate(valid => {
if (valid) {
// 这里应该是提交数据的逻辑
this.$http
.request({
url: this.dataForm.id ? this.$http.adornUrl(this.urlList.update) : this.$http.adornUrl(this.urlList.add),
method: "POST",
data: this.dataForm,
header: {
//默认 无 说明:请求头
"Content-Type": "application/json"
}
})
.then(({ data }) => {
if (data && data.code === 0) {
this.$message({
message: "操作成功",
type: "success",
duration: 1500,
onClose: () => {
this.visible = false;
this.$emit("refreshDataList");
}
});
} else {
this.$message.error(data.msg);
}
});
}
});
},
handlereset() {
this.visible = false;
}
}
};
</script>
<style lang="scss" scoped>
/deep/ .addFormBox.el-form-item {
margin-bottom: 10px !important;
}
</style>

View File

@@ -0,0 +1,330 @@
<template>
<div class="mod-config" style="height: calc(100vh - 180px );">
<el-form
:inline="true"
:model="dataForm"
@keyup.enter.native="getDataList()"
>
<el-form-item label="文章标题">
<el-input
v-model="query.title"
placeholder="文章标题"
clearable
></el-input>
</el-form-item>
<el-form-item>
<el-button
@click="
pageIndex = 1;
getDataList();
"
>查询</el-button
>
<el-button type="primary" @click="addOrUpdateHandle()">新增</el-button>
</el-form-item>
</el-form>
<div style="height: calc(100% - 80px );">
<el-table
:data="dataList"
border
height="90%"
v-loading="dataListLoading"
style="width: 100%;"
>
<el-table-column
prop="imgurl"
:show-overflow-tooltip="true"
header-align="center"
align="center"
label="封面图"
width="150"
>
<template slot-scope="scope">
<img :src="scope.row.imgurl" alt="封面图" height="50px" referrerpolicy="no-referrer">
</template>
</el-table-column>
<el-table-column
prop="title"
:show-overflow-tooltip="true"
header-align="center"
align="center"
label="文章标题"
></el-table-column>
<el-table-column
prop="url"
:show-overflow-tooltip="true"
header-align="center"
align="center"
label="文章链接"
>
<template slot-scope="scope">
<a :href="scope.row.url" target="_blank">{{ scope.row.url }}</a>
</template>
</el-table-column>
<el-table-column
width="200"
prop="createTime"
header-align="center"
align="center"
label="创建日期"
>
</el-table-column>
<el-table-column
fixed="right"
header-align="center"
align="center"
width="180"
label="操作"
>
<template slot-scope="scope">
<div>
<el-button
type="text"
size="small"
@click="addOrUpdateHandle(scope.row)"
>修改</el-button
>
<el-button
type="text"
size="small"
@click="deleteHandle(scope.row.id)"
>删除</el-button
>
</div>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
:current-page="pageIndex"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
:total="totalPage"
style="padding: 10px 0; text-align: center;"
layout="total, sizes, prev, pager, next, jumper"
>
</el-pagination>
</div>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update
v-if="addOrUpdateVisible"
ref="addOrUpdate"
@refreshDataList="getDataList"
></add-or-update>
</div>
</template>
<script>
import AddOrUpdate from "./psychologicalForum-add-or-update";
export default {
data() {
return {
linkList: [{ label: "外链", value: 1 }, { label: "富文本", value: 0 }],
selectQueryApp: null,
dataForm: {
key: ""
},
query: {
title:""
},
dataList: [],
delFlag: false,
pageIndex: 1,
pageSize: 20,
totalPage: 0,
dataListLoading: false,
dataListSelections: [],
addOrUpdateVisible: false,
urlList: {
list: "/common/wxPublicAccount/getWxPublicAccountArticleList"
}
};
},
components: {
AddOrUpdate
},
activated() {
if (this.$route.query.upPageInde != null) {
this.pageIndex = this.$route.query.upPageIndex;
console.log(this.pageIndex);
}
this.getDataList();
},
methods: {
// 获取数据列表
getDataList() {
console.log("this.selectQueryApp at line 195:", this.selectQueryApp);
this.dataListLoading = true;
this.$http
.request({
url: this.$http.adornUrl(this.urlList.list),
method: "POST",
data: {
page: this.pageIndex,
limit: this.pageSize,
...this.query,
},
header: {
//默认 无 说明:请求头
"Content-Type": "application/json"
}
})
.then(({ data }) => {
if (data && data.code === 0) {
this.dataList = data.page.records;
this.totalPage = data.page.total;
} else {
this.dataList = [];
this.totalPage = 0;
}
this.dataListLoading = false;
});
},
// 每页数
sizeChangeHandle(val) {
this.pageSize = val;
this.pageIndex = 1;
this.getDataList();
},
// 当前页
currentChangeHandle(val) {
this.pageIndex = val;
this.getDataList();
},
// 多选
selectionChangeHandle(val) {
this.dataListSelections = val;
},
// 新增 / 修改
addOrUpdateHandle(row) {
this.addOrUpdateVisible = true;
this.$nextTick(() => {
this.$refs.addOrUpdate.init(row);
});
},
// 删除
deleteHandle(id) {
this.$confirm(
`确定对[id=${id}]进行[${id ? "删除" : "删除"}]操作?`,
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}
).then(() => {
this.$http({
url: this.$http.adornUrl("/common/wxPublicAccount/delWxPublicAccountArticle"),
method: "post",
data: {
id: id
}
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message({
message: "操作成功",
type: "success",
duration: 1500,
onClose: () => {
this.getDataList();
}
});
} else {
this.$message.error(data.msg);
}
});
});
},
// 开关变化
SwitchChange(event) {
/*点击时他会自动把你绑定的值变更,直接去请求数据就可以了*/
var parms = {
delFlag: event.delFlag,
id: event.id
};
this.$http({
url: this.$http.adornUrl("/book/book/update"),
method: "post",
data: parms
})
.then(res => {
this.$message({
message: "成功",
type: "success"
});
this.loading = false;
this.getDataList();
})
.catch(error => {
this.loading = false;
console.log(error);
});
console.log(event);
},
chapterHandle(id, row) {
if (row.novel == "") {
this.$alert("请上传电子书文件后在进行此操作", "提示", {
confirmButtonText: "好的"
});
return false;
}
this.$http({
url: this.$http.adornUrl("/book/book/getChapter"),
method: "get",
params: this.$http.adornParams({
id: id
})
}).then(res => {
this.$message({
message: "成功",
type: "success"
});
this.loading = false;
this.getDataList();
});
},
contentHandle(id) {
this.$http({
url: this.$http.adornUrl("/book/bookchaptercontent/getBookVoices"),
method: "get",
params: this.$http.adornParams({
id: id
})
}).then(res => {
this.$message({
message: "成功",
type: "success"
});
this.loading = false;
this.getDataList();
});
},
voicesHandle(id) {
//allVoices
this.$http({
// url: this.$http.adornUrl('/book/bookchaptercontent/allVoices'),
url: this.$http.adornUrl("/book/bookchaptercontent/AllVOices"),
method: "get",
params: this.$http.adornParams({
id: id
})
}).then(res => {
console.log("book/bookchaptercontent/AllVOices");
this.$message({
message: "成功",
type: "success"
});
this.loading = false;
this.getDataList();
});
}
}
};
</script>

View File

@@ -61,7 +61,7 @@
<!-- 弹窗, 新增 / 修改 -->
<el-dialog :visible.sync="addOrUpdateVisible" :close-on-click-modal="false" :append-to-body="true" :title="titlesub"
width="50%" @close="cancleClose">
<el-form :inline="true" :model="addForm" ref="addFormRef" :rules="addFormRule">
<el-form :inline="true" :model="addForm" ref="addFormRef" :rules="addFormRule" label-width="100px">
<el-row type="flex" justify="center">
<el-form-item label="姓名" prop="name">
<el-input style="width:500px" v-model="addForm.name"></el-input>
@@ -76,17 +76,39 @@
</el-form-item>
</el-row>
<el-row type="flex" justify="center">
<el-form-item label="url" prop="url" label-width="48px">
<el-form-item label="关联用户" prop="tel">
<el-autocomplete
size="large"
style="width: 500px;"
v-model="addForm.tel"
:debounce="500"
:fetch-suggestions="loadAll"
placeholder="请输入手机号/邮箱"
@select="handleSelect"
>
<template #default="{ item }">
<div class="custom-item">
<span>{{ item.tel ? item.tel : item.email }}</span>
<span style="color: gray; margin-left: 10px;" v-if="item.name"
>({{ item.name }})</span
>
</div>
</template>
</el-autocomplete>
</el-form-item>
</el-row>
<el-row type="flex" justify="center">
<el-form-item label="url" prop="url">
<el-input style="width:500px" v-model="addForm.url"></el-input>
</el-form-item>
</el-row>
<el-row type="flex" justify="center">
<el-form-item label="内容" prop="content" label-width="48px">
<el-form-item label="内容" prop="content">
<el-input style="width:500px" v-model="addForm.content" :rows="5" type="textarea"></el-input>
</el-form-item>
</el-row>
<el-row type="flex" justify="center">
<el-form-item label="地址" prop="cityId" label-width="48px">
<el-form-item label="地址" prop="cityId">
<el-select v-model="addForm.provId" placeholder="请选择省份" style="width:249px" @change="provinceChange">
<el-option v-for="item in provinceEntity" :key="item.provId" :label="item.provName" :value="item.provId">
</el-option>
@@ -98,17 +120,19 @@
</el-form-item>
</el-row>
<el-row type="flex" justify="center">
<el-form-item label="图片" prop="img" label-width="48px">
<el-upload class="el-uploadfeng " ref="files"
:class="{ uoloadSty: dataForm.showBtnDealImg, disUoloadSty: dataForm.noneBtnImg }"
:action="baseUrl + '/oss/fileoss'" list-type="picture-card" :file-list="fileList"
:on-success="handlePicSuccess" accept=".jpeg,.jpg,.gif,.png" :on-remove="handleRemove">
<i class="el-icon-plus"></i>
</el-upload>
<el-form-item label="图片" prop="img">
<div style="width: 500px;">
<el-upload class="el-uploadfeng " ref="files"
:class="{ uoloadSty: dataForm.showBtnDealImg, disUoloadSty: dataForm.noneBtnImg }"
:action="baseUrl + '/oss/fileoss'" list-type="picture-card" :file-list="fileList"
:on-success="handlePicSuccess" accept=".jpeg,.jpg,.gif,.png" :on-remove="handleRemove">
<i class="el-icon-plus"></i>
</el-upload>
</div>
</el-form-item>
</el-row>
<el-row type="flex" justify="center">
<el-form-item label="排序" label-width="48px">
<el-form-item label="排序">
<el-input style="width:500px" v-model="addForm.sort"></el-input>
</el-form-item>
</el-row>
@@ -131,6 +155,7 @@
cityEntity: [],
provinceEntity: [], //省地址
typeList: [], //类型列表
userList: [], //用户列表
booknameList: [],
dataForm: {
dictType: '', //分类
@@ -145,6 +170,8 @@
content: '',
cityId: '',
provId: '',
userId: '', //关联用户id
tel: '',
sort: 0
},
editId: '',
@@ -153,6 +180,10 @@
required: true,
message: "请选择分类"
}],
tel: [{
required: true,
message: "请选择关联用户"
}],
name: [{
required: true,
message: "请输入姓名"
@@ -239,6 +270,33 @@
this.dataListLoading = false
})
},
loadAll(queryString, cb) {
console.log('queryString', queryString);
if (queryString == "") {
return false;
}
this.$http({
url: this.$http.adornUrl("/book/user/getUserList"),
method: "post",
data: this.$http.adornData({
page: 1,
limit: 20,
key: queryString
})
}).then(({ data }) => {
if (data && data.code === 0) {
var arr = data.user.records;
cb(arr);
} else {
cb([]);
}
});
},
handleSelect(data) {
console.log("data at line 161:", data);
this.addForm.userId = data.id;
this.addForm.tel = data.tel ? data.tel : data.email;
},
provinceChange() {
this.addForm.cityId = ''
this.cityEntity = []
@@ -281,6 +339,8 @@
this.addForm.content = row.content
this.addForm.typeId = row.type.toString()
this.addForm.sort = row.sort
this.addForm.tel = row.tel
this.addForm.userId = row.userId ? row.userId : ''
this.fileList = row.imgList
for (var i = 0; i < this.provinceEntity.length; i++) {
for (var j = 0; j < this.provinceEntity[i].cityList.length; j++) {
@@ -311,6 +371,8 @@
content: '',
cityId: '',
provId: '',
userId: '',
tel: '',
sort: 0
}
this.fileList = []
@@ -382,7 +444,9 @@
"content": this.addForm.content,
"type": this.addForm.typeId,
"cityId": this.addForm.cityId,
"sort": this.addForm.sort
"sort": this.addForm.sort,
"userId": this.addForm.userId,
"tel": this.addForm.tel
})
}).then(({
data

View File

@@ -183,19 +183,15 @@
<el-table-column
property="student.id"
label="学员id"
width="150"
width="100"
></el-table-column>
<el-table-column
property="student.nickname"
label="学员名"
property="student.name"
label="学员名"
width="150"
></el-table-column>
<el-table-column
property="student.vip"
label="学员身份"
width="200"
></el-table-column>
<el-table-column property="student.tel" label="手机号"></el-table-column>
<el-table-column property="student.email" label="邮箱"></el-table-column>
<el-table-column property="" label="操作">
<template slot-scope="scope">
<el-button size="mini" type="warning" @click="outClass(scope.row)"
@@ -204,6 +200,16 @@
</template>
</el-table-column>
</el-table>
<!-- <el-pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
:current-page="pageIndexStudent"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSizeStudent"
:total="totalStudent"
layout="total, sizes, prev, pager, next, jumper"
>
</el-pagination> -->
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="setStudentClose"> </el-button>
@@ -275,6 +281,10 @@ export default {
pageSize: 10,
totalPage: 0,
total: 0,
// pageIndexStudent: 1,
// pageSizeStudent: 10,
// totalStudentPage: 0,
// totalStudent: 0,
dataListLoading: false,
miniClassVisible: false
};
@@ -346,15 +356,36 @@ export default {
});
})
},
// 获取学员列表数据
async getStudents(data) {
return new Promise(async(resolve, reject) => {
await this.$http({
url: this.$http.adornUrl("/common/class/getClassUserList"),
method: "post",
data: this.$http.adornData(data)
})
.then(data => {
resolve(data);
})
.catch(e => {
reject(e)
});
})
},
// 管理学员
async setStudent(data) {
this.miniClass = data;
var _classId = data.id;
this.addForm = {};
var jieguo = await this.getCLassInfo(_classId)
// var jieguo = await this.getCLassInfo(_classId)
var students = await this.getStudents({
classId: _classId,
// page: this.pageIndexStudent,
// limit: this.pageSizeStudent,
})
// console.log('jieguo',jieguo.data.code)
if(jieguo.data.code == 0){
this.studentList = jieguo.data.result.students;
if(students.data.code == 0){
this.studentList = students.data.result.students;
this.setStudentVisible = true;
}else{
this.$message.error("获取学员列表失败");

View File

@@ -12,13 +12,13 @@
<el-radio-group size="mini" v-model="tabChange.tabActiveName" style="margin-bottom: 10px;">
<el-radio-button label="all">全部</el-radio-button>
<el-radio-button label="0">待付款</el-radio-button>
<el-radio-button label="1">待发</el-radio-button>
<el-radio-button label="2">已发</el-radio-button>
<el-radio-button label="1">待发</el-radio-button>
<el-radio-button label="2">已发</el-radio-button>
<el-radio-button label="3">已完成</el-radio-button>
</el-radio-group>
<div style="margin-bottom: 10px;">
<el-radio-group size="mini" v-model="tabChange.isPrint" v-if="tabChange.tabActiveName == 2">
<el-radio-button label="0">已发订单</el-radio-button>
<el-radio-button label="0">已发订单</el-radio-button>
<el-radio-button label="1">可打印面单</el-radio-button>
</el-radio-group>
<!-- <el-radio v-model="tabChange.isPrint" label="" border size="mini">全部</el-radio>
@@ -49,7 +49,7 @@
:disabled="dataListSelections.length <= 0">批量删除</el-button>
<span style="" v-if="tabChange.tabActiveName == 1">
<el-button style="margin-left: 10px;" size="mini" v-if="isAuth('book:buyorder')" type="primary"
:disabled="dataListSelections.length <= 0" @click="setDeliver">批量发</el-button>
:disabled="dataListSelections.length <= 0" @click="setDeliver">批量发</el-button>
</span>
<span style="" v-if="tabChange.tabActiveName == 2 && tabChange.isPrint == 1">
<el-button style="margin-left: 10px;" size="mini" v-if="isAuth('book:buyorder')" type="warning"
@@ -189,7 +189,7 @@
<el-checkbox disabled @change="handleCheckedChange('111')"></el-checkbox>
<span>订单编号:orderSn </span> <span style="margin-left: 20px;">下单时间:2021-11-30 17:22:33</span></el-col>
<el-col :md="12" :lg="12">
<div class="tip"><icon-svg name="zhuyi"></icon-svg> 订单存在可合并发
<div class="tip"><icon-svg name="zhuyi"></icon-svg> 订单存在可合并发
<el-button type="text">查看</el-button>
</div>
</el-col>
@@ -254,17 +254,17 @@
<div class="tabContent">
<div>用户名张三</div>
<div>用户id:12121454545</div>
<div>地址天津市河东区xxxxxxxxxx</div>
<div class="buier_tip">买家备注尽快发</div>
<div>地址天津市河东区xxxxxxxxxx</div>
<div class="buier_tip">买家备注尽快发</div>
</div>
</div>
<div class="buier td4 xcenter borderright">
<div class="orderStatus">待发</div>
<div class="orderStatus">待发</div>
<div class="time">支付时间2023-02-09 14:16:08</div>
<div class="hasSplit"><span style="color:#999">该订单已被拆分发</span> <el-button type="text"
<div class="hasSplit"><span style="color:#999">该订单已被拆分发</span> <el-button type="text"
size="mini">查看面单</el-button></div>
<div><el-button type="primary" size="mini" disabled></el-button>
<el-button type="danger" size="mini">拆分发</el-button>
<div><el-button type="primary" size="mini" disabled></el-button>
<el-button type="danger" size="mini">拆分发</el-button>
</div>
</div>
<div class="td5 flexbox" style="justify-content: center; align-items: center; width: 150px;">
@@ -317,16 +317,16 @@
<div class="tabContent">
<div>用户名张三</div>
<div>用户id:12121454545</div>
<div>地址天津市河东区xxxxxxxxxx</div>
<div class="buier_tip">买家备注尽快发</div>
<div>地址天津市河东区xxxxxxxxxx</div>
<div class="buier_tip">买家备注尽快发</div>
</div>
</div>
<div class="buier td4 xcenter borderright">
<div class="orderStatus">待付款</div>
<div><el-button type="danger" size="mini">改价</el-button></div>
<!-- <div>
<el-button type="primary" size="mini">去发</el-button>
<el-button type="danger" size="mini">拆分发</el-button>
<el-button type="primary" size="mini">去发</el-button>
<el-button type="danger" size="mini">拆分发</el-button>
</div> -->
</div>
<div class="td5 flexbox" style="justify-content: center; align-items: center; width: 150px;">
@@ -392,13 +392,13 @@
<div class="tabContent">
<div>用户名张三</div>
<div>用户id:12121454545</div>
<div>地址天津市河东区xxxxxxxxxx</div>
<div class="buier_tip">买家备注尽快发</div>
<div>地址天津市河东区xxxxxxxxxx</div>
<div class="buier_tip">买家备注尽快发</div>
</div>
</div>
<div class="buier td4 xcenter borderright">
<div class="orderStatus">已发</div>
<div class="time">时间2023-02-09 14:09:23</div>
<div class="orderStatus">已发</div>
<div class="time">时间2023-02-09 14:09:23</div>
<div>
<el-button type="primary" size="mini">面单预览</el-button>
</div>
@@ -496,7 +496,7 @@
prop="shippingUser"
header-align="center"
align="center"
label="收人姓名">
label="收人姓名">
</el-table-column>
<el-table-column
prop="province"
@@ -544,12 +544,12 @@
</el-table-column>
<el-table-column prop="createTime" header-align="center" align="center" label="下单时间">
</el-table-column>
<el-table-column prop="shippingTime" header-align="center" align="center" label="发时间">
<el-table-column prop="shippingTime" header-align="center" align="center" label="发时间">
</el-table-column>
<el-table-column prop="orderStatus" header-align="center" align="center" label="订单状态">
<template slot-scope="scope">
<el-tag v-if="scope.row.orderStatus == 1" type="success">待发</el-tag>
<el-tag v-if="scope.row.orderStatus == 2" type="danger">已发</el-tag>
<el-tag v-if="scope.row.orderStatus == 1" type="success">待发</el-tag>
<el-tag v-if="scope.row.orderStatus == 2" type="danger">已发</el-tag>
<el-tag v-if="scope.row.orderStatus == 3" type="warning">已完成</el-tag>
<el-tag v-if="scope.row.orderStatus == 4" type="info">交易失败</el-tag>
</template>
@@ -570,7 +570,7 @@
<el-button type="text" size="small" @click="addOrUpdateHandle(scope.row.orderId)">修改</el-button>
<span v-if="scope.row.orderStatus">
<el-button type="text" size="small" @click="setDeliver(dataListSelections[0] = scope.row)"
v-if="isAuth('book:buyorderdetail:deliver')"></el-button>
v-if="isAuth('book:buyorderdetail:deliver')"></el-button>
</span>
<span v-if="scope.row.isPrint == 0">
<el-button type="text" size="small" v-if="isAuth('book:buyorderdetail:deliver')"
@@ -589,7 +589,7 @@
<!-- 物流详情弹窗 -->
<deliverDetail ref="printOrderDialog" :visible="deliverDetailVisible" :deliverData="dataListSelections"
@closeDeliverDetailDialog="closeDeliverDetailDialog"></deliverDetail>
<!-- 代发货转为发货 -->
<!-- 待发出转为已发出 -->
<setDeliverDialog ref="setDeliverDialog" :visible="setDeliverVisible" :selectData="dataListSelections"
@closeDeliverDialog='closeDeliverDialog'></setDeliverDialog>
<!-- 设置备注 -->
@@ -616,8 +616,8 @@ export default {
deliverDetailVisible: false,
dataList: [],
tabChange: {
tabActiveName: '2', // tab筛选默认all全部 0待付款 1代发货 2已发 3已完成
isPrint: '1', // 已发列表筛选 0显示订单 1显示可打印列表,
tabActiveName: '2', // tab筛选默认all全部 0待付款 1待发出 2已发 3已完成
isPrint: '1', // 已发列表筛选 0显示订单 1显示可打印列表,
orderName: '0' //订单名称筛选 all:全部 0:健康超市 1:电子书 2:充值订单
},
editBeizhudialogVisible: false, // 修改备注按钮
@@ -823,7 +823,7 @@ export default {
this.setDeliverVisible = false
this.getDataList()
},
// 去发
// 去发
setDeliver() {
this.setDeliverVisible = true
},

View File

@@ -10,8 +10,8 @@
<el-form-item label="下单人ID" prop="userId">
<el-input v-model="dataForm.userId" placeholder="下单人ID"></el-input>
</el-form-item>
<el-form-item label="收人姓名" prop="shippingUser">
<el-input v-model="dataForm.shippingUser" placeholder="收人姓名"></el-input>
<el-form-item label="收人姓名" prop="shippingUser">
<el-input v-model="dataForm.shippingUser" placeholder="收人姓名"></el-input>
</el-form-item>
<el-form-item label="省" prop="province">
<el-input v-model="dataForm.province" placeholder="省"></el-input>
@@ -49,8 +49,8 @@
<el-form-item label="下单时间" prop="createTime">
<el-input v-model="dataForm.createTime" placeholder="下单时间"></el-input>
</el-form-item>
<el-form-item label="发时间" prop="shippingTime">
<el-input v-model="dataForm.shippingTime" placeholder="发时间"></el-input>
<el-form-item label="发时间" prop="shippingTime">
<el-input v-model="dataForm.shippingTime" placeholder="发时间"></el-input>
</el-form-item>
<el-form-item label="订单状态" prop="orderStatus">
<el-input v-model="dataForm.orderStatus" placeholder="订单状态"></el-input>
@@ -100,7 +100,7 @@
{ required: true, message: '下单人ID不能为空', trigger: 'blur' }
],
shippingUser: [
{ required: true, message: '收人姓名不能为空', trigger: 'blur' }
{ required: true, message: '收人姓名不能为空', trigger: 'blur' }
],
province: [
{ required: true, message: '省不能为空', trigger: 'blur' }
@@ -139,7 +139,7 @@
{ required: true, message: '下单时间不能为空', trigger: 'blur' }
],
shippingTime: [
{ required: true, message: '发时间不能为空', trigger: 'blur' }
{ required: true, message: '发时间不能为空', trigger: 'blur' }
],
orderStatus: [
{ required: true, message: '订单状态不能为空', trigger: 'blur' }

View File

@@ -4,7 +4,7 @@
style="display: flex; height:300px; width: 100%; align-items: center; justify-content: center;">
<div class="empty" style="text-align: center;">
<icon-svg style="font-size: 130px;" name="kongbai"></icon-svg>
<div style="color: #999;">当前暂无可合并发的订单哦</div>
<div style="color: #999;">当前暂无可合并发的订单哦</div>
</div>
</div>
<el-form v-else ref="ruleForm" :model="ruleForm" label-width="80px" :rules="rules">
@@ -13,7 +13,7 @@
<el-radio-group class="group3" v-model="radio3" size="mini" @change="radioChange">
<el-radio-button label="allYse">全合并</el-radio-button>
<el-radio-button label="allNo">全不合并</el-radio-button>
<el-button type="primary" size="mini" @click="submit" :disabled="buttonDisable">批量合并发</el-button>
<el-button type="primary" size="mini" @click="submit" :disabled="buttonDisable">批量合并发</el-button>
</el-radio-group>
</div>
<ul style="">
@@ -37,7 +37,7 @@
<div style="padding: 10px;" class="">
<div><span class="infoTitle">用户名</span><span>{{ de.userName }}</span></div>
<div><span class="infoTitle">电话</span><span>{{ de.tel }}</span></div>
<div><span class="infoTitle">货地址</span><span>{{ de.address }}</span></div>
<div><span class="infoTitle">件人姓名</span><span>{{ de.shippingUser }}</span></div>
</div>
</div>
</div>
@@ -81,7 +81,7 @@
</div> -->
<el-button type="warning" size="mini" plain
@click="mergeOne(de.orderList, index)">合并发</el-button>
@click="mergeOne(de.orderList, index)">合并发</el-button>
</div>
</div>
</li>
@@ -125,7 +125,7 @@ export default {
ruleForm: {
list: []
},
// 批量合并发
// 批量合并发
multMergeIdList:[],
rules: {
ruleForm: [
@@ -139,7 +139,7 @@ export default {
this.getMergeOrders()
},
methods: {
// 选择批量合并发
// 选择批量合并发
MergeRadioListChanged(val,index) {
console.log(this.ruleForm.list,'this.ruleForm.list')
this.ruleForm.list[index].isMerge = 'true'
@@ -176,7 +176,7 @@ export default {
})
}).then(({ data }) => {
if (data && data.code === 0) {
// console.log('订单发前的检查')
// console.log('订单发前的检查')
// this.dataList = data.page.list
this.totalPage = data.page.totalCount
loading.close()
@@ -216,7 +216,7 @@ export default {
this.getMergeOrders()
this.merOneList = []
},
// 单个合并订单发
// 单个合并订单发
mergeOne(list, index) {
// console.log(list)
this.merOneList = list.map(item => {
@@ -225,7 +225,7 @@ export default {
console.log(this.merOneList)
// this.$refs['ruleForm'].validate((valid) => {
// if (valid) {
this.$confirm('正在进行合并发, 是否继续?', '提示', {
this.$confirm('正在进行合并发, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'

View File

@@ -10,8 +10,8 @@
<el-radio-group size="mini" v-model="tabChange.tabActiveName">
<el-radio-button label="">全部</el-radio-button>
<el-radio-button label="0">待付款</el-radio-button>
<el-radio-button label="1">待发</el-radio-button>
<el-radio-button label="2">已发</el-radio-button>
<el-radio-button label="1">待发</el-radio-button>
<el-radio-button label="2">已发</el-radio-button>
<el-radio-button label="3">已完成</el-radio-button>
</el-radio-group>
@@ -31,7 +31,7 @@
<div style="margin-bottom: 10px;">
<el-radio-group size="mini" v-model="tabChange.isPrint" v-if="tabChange.tabActiveName == 2">
<el-radio-button label="0">已发订单</el-radio-button>
<el-radio-button label="0">已发订单</el-radio-button>
<el-radio-button label="1">打印面单</el-radio-button>
</el-radio-group>
</div>
@@ -89,11 +89,11 @@
</el-form-item>
</el-form>
<div class="buttonGroup">
<span style="" v-if="tabChange.tabActiveName == 1" style="display: inline-block;margin-bottom: 20px;">
<span v-if="tabChange.tabActiveName == 1" style="display: inline-block;margin-bottom: 20px;">
<el-badge :value="mergeList.length" class="item">
<router-link :to="{ path: 'buyorder-mergeorder', query: {} }">
<el-button style="" size="mini" v-if="isAuth('book:buyorder')" type="primary"
:disabled="mergeList.length > 0 ? false : true">合并发</el-button>
:disabled="mergeList.length > 0 ? false : true">合并发</el-button>
</router-link>
</el-badge>
</span>
@@ -163,7 +163,7 @@
</el-col>
<el-col :span="6">
<div class="tip">
<div v-if="fitem.isSend == 1" class="hasSplit"><span style="color:#999">该订单已被拆分发</span> -->
<div v-if="fitem.isSend == 1" class="hasSplit"><span style="color:#999">该订单已被拆分发</span>
</div>
</div>
</el-col>
@@ -235,7 +235,7 @@
</div>
</div>
<div class="buier td3 xcenter">
<div class="tabName">信息
<div class="tabName">信息
<div style="color: #515a6e;"><span
style="color: #515a6e;">{{ fitem.shippingUser }}</span>&nbsp;&nbsp;&nbsp;<span
style="color: #515a6e;">{{ fitem.userPhone }}</span></div>
@@ -243,7 +243,7 @@
{{ fitem.province }}-{{ fitem.city }}-{{ fitem.district }}-{{ fitem.address }}
</div>
<div style="margin-bottom:10px">
<el-button @click="changeAddressShow(fitem)" type="primary" size="mini" plain>修改收信息</el-button>
<el-button @click="changeAddressShow(fitem)" type="primary" size="mini" plain>修改收信息</el-button>
</div>
</div>
<div class="tabContent">
@@ -251,9 +251,9 @@
</div>
<div class="buier td4 xcenter flexbox" style="align-items: center; justify-content: center;">
<div>
<div class="orderStatus">待发</div>
<div class="orderStatus">待发</div>
<div><el-button style=" line-height: 6px;" type="primary" size="mini"
@click="orderDeliver(fitem)"></el-button>
@click="orderDeliver(fitem)"></el-button>
</div>
</div>
</div>
@@ -295,7 +295,7 @@
</span>
</template>
</el-table-column>
<el-table-column prop="shippingUser" header-align="center" align="center" label="收人信息">
<el-table-column prop="shippingUser" header-align="center" align="center" label="收人信息">
<template slot-scope="scope">
<span
v-if="scope.row.shippingUser && scope.row.shippingUser != ''">{{scope.row.shippingUser}}</span>
@@ -395,13 +395,13 @@
</div>
</template>
</el-table-column>
<!-- <el-table-column prop="shippingTime" header-align="center" align="center" label="发时间">
<!-- <el-table-column prop="shippingTime" header-align="center" align="center" label="发时间">
</el-table-column> -->
<el-table-column prop="orderStatus" header-align="center" align="center" label="订单状态" width="80">
<template slot-scope="scope">
<el-tag v-if="scope.row.orderStatus == 0" type="success">待付款</el-tag>
<el-tag v-if="scope.row.orderStatus == 1" type="success">待发</el-tag>
<el-tag v-if="scope.row.orderStatus == 2" type="danger">已发</el-tag>
<el-tag v-if="scope.row.orderStatus == 1" type="success">待发</el-tag>
<el-tag v-if="scope.row.orderStatus == 2" type="danger">已发</el-tag>
<el-tag v-if="scope.row.orderStatus == 3" type="warning">已完成</el-tag>
<el-tag v-if="scope.row.orderStatus == 4" type="info">交易失败</el-tag>
</template>
@@ -447,17 +447,17 @@
</template>
</el-table-column>
</el-table>
<el-dialog title="修改收信息" :visible.sync="changeAddVisible" width="500" v-loading="addressLoad"
<el-dialog title="修改收信息" :visible.sync="changeAddVisible" width="500" v-loading="addressLoad"
:close="changeAddHandleClose">
<div>
<el-form ref="addressFormRef" :model="addressForm" label-width="120px" :rules="addressFormRule">
<el-form-item label="收人:" prop="name">
<el-form-item label="收人:" prop="name">
<el-input v-model="addressForm.name"></el-input>
</el-form-item>
<el-form-item label="收联系电话:" prop="tel">
<el-form-item label="收联系电话:" prop="tel">
<el-input v-model="addressForm.tel"></el-input>
</el-form-item>
<el-form-item label="收地址:" prop="haveValue">
<el-form-item label="收地址:" prop="haveValue">
<!-- <el-input v-model="addressForm.address"></el-input> -->
<!-- 省市区-->
<!-- -->
@@ -516,7 +516,7 @@
<!-- 物流详情弹窗 -->
<deliverDetail ref="deliverDetail" :visible="deliverDetailVisible" :deliverOrder="oprateOrder"
@closeDeliverDetailDialog="closeDeliverDetailDialog"></deliverDetail>
<!-- 代发转为发 -->
<!-- 代发转为发 -->
<setDeliverDialog ref="setDeliverDialog" :visible="setDeliverVisible" :selectData="checkedOrders"
@closeDeliverDialog='closeDeliverDialog'></setDeliverDialog>
<!-- 设置备注 -->
@@ -524,10 +524,10 @@
:visible="editBeizhudialogVisible" :editBeizhuform="editBeizhuform" @closeDialog='closeBeizhuDialog'
@refreshDataList="getDataList">
</updateOrderBeiZhu>
<!-- 单个订单发 -->
<!-- 单个订单发 -->
<splitDeliver :orderitem="oprateOrder" :visible="splitDeliverVisible" @closeDialog='closeOrder1Dialog'>
</splitDeliver>
<!--点合并发时弹出 可合并订单弹窗 -->
<!--点合并发时弹出 可合并订单弹窗 -->
<mergeDliver v-if="mergeList" :visible="mergeDliverVisible" :mergeList="mergeList"
@closeDialog='closemergeDliverDialog'>
</mergeDliver>
@@ -574,7 +574,7 @@
isIndeterminate: true,
checkAll: false
},
multipleDisabled: false, // 待发多选是否可用
multipleDisabled: false, // 待发多选是否可用
//childrenChecked:[],
checkedOrders: [], // 新的筛选
fullscreenLoading: false,
@@ -584,8 +584,8 @@
sheetVisible: false,
dataList: [],
tabChange: {
tabActiveName: '', // tab筛选默认all全部 0待付款 1待发 2已发 3已完成
isPrint: 0, // 已发列表筛选 0显示订单 1显示可打印列表,
tabActiveName: '', // tab筛选默认all全部 0待付款 1待发 2已发 3已完成
isPrint: 0, // 已发列表筛选 0显示订单 1显示可打印列表,
orderName: '0', //订单名称筛选 all:全部 0:健康超市 1:电子书 2:充值订单
sheetCode: 2, // 面单状态2 未打印1 已打印 0全部
},
@@ -655,17 +655,17 @@
addressFormRule: {
name: [{
required: true,
message: '请输入收人信息',
message: '请输入收人信息',
trigger: 'blur'
}],
tel: [{
required: true,
message: '请输入收联系电话信息',
message: '请输入收联系电话信息',
trigger: 'blur'
}],
county: [{
required: true,
message: '请选择收地址',
message: '请选择收地址',
trigger: 'blur'
}],
addressXX: [{
@@ -768,7 +768,7 @@
}
this.$refs['addressFormRef'].validate((valid) => {
if (valid) {
// console.log('修改收地址')
// console.log('修改收地址')
this.$http({
url: this.$http.adornUrl('/book/buyOrder/modifyConsigneeAddress'),
method: 'post',
@@ -889,9 +889,9 @@
})
this.changeAddVisible = true
// console.log('显示修改收地址')
// console.log('显示修改收地址')
},
// 混合发
// 混合发
showAnyDialog(item) {
if (!item.printString || item.printString == '') return this.$message.error('暂无面单数据')
this.anyDialogContent = item.printString
@@ -935,7 +935,7 @@
this.sheetListLoading = false
})
},
// 检查待发单选按钮是否可用
// 检查待发单选按钮是否可用
isMultipleDisabled() {
this.dataList.forEach(item => {
if (item.isSend === '1') {
@@ -1252,14 +1252,14 @@
closeSheetDliverDialog(val) {
this.sheetVisible = val
},
// 批量发
// 批量发
setDeliver() {
this.setDeliverVisible = true
},
// 发前检查是否有可合并项(废除)
// 发前检查是否有可合并项(废除)
checkDeliver() {
// 确认
this.$confirm(`正在批量发,共选中了${this.checkedOrders.length}条数据, 是否继续?`, '提示', {
this.$confirm(`正在批量发,共选中了${this.checkedOrders.length}条数据, 是否继续?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '点错了',
type: 'warning'
@@ -1270,7 +1270,7 @@
}).catch(() => {});
},
// 单个订单发
// 单个订单发
orderDeliver(item) {
console.log(item)
this.oprateOrder = item
@@ -1307,7 +1307,7 @@
})
return true
},
// 关闭单个订单发弹窗
// 关闭单个订单发弹窗
closeOrder1Dialog(val) {
this.splitDeliverVisible = val
// this.oprateOrder = {}
@@ -1320,7 +1320,7 @@
closeanyDialogDialog() {
this.anyDialogVisible = false
},
// 关闭合批量发合并项弹窗
// 关闭合批量发合并项弹窗
closemergeDliverDialog(val) {
console.log(val)
this.mergeDliverVisible = false

View File

@@ -9,13 +9,13 @@
<el-radio-group size="mini" v-model="tabChange.tabActiveName" style="margin-bottom: 10px;">
<el-radio-button label="">全部</el-radio-button>
<el-radio-button label="0">待付款</el-radio-button>
<el-radio-button label="1">待发</el-radio-button>
<el-radio-button label="2">已发</el-radio-button>
<el-radio-button label="1">待发</el-radio-button>
<el-radio-button label="2">已发</el-radio-button>
<el-radio-button label="3">已完成</el-radio-button>
</el-radio-group>
<div style="margin-bottom: 10px;">
<el-radio-group size="mini" v-model="tabChange.isPrint" v-if="tabChange.tabActiveName == 2">
<el-radio-button label="0">已发订单</el-radio-button>
<el-radio-button label="0">已发订单</el-radio-button>
<el-radio-button label="1">打印面单</el-radio-button>
</el-radio-group>
<!-- <el-radio v-model="tabChange.isPrint" label="" border size="mini">全部</el-radio> -->
@@ -82,17 +82,17 @@
<el-button size="mini" v-if="isAuth('book:buyorder:delete')" type="danger" @click="deleteHandle()"
:disabled="dataListSelections.length <= 0">{{tabChange.tabActiveName}}批量删除</el-button>
</span> -->
<span style="" v-if="tabChange.tabActiveName == 1" style="display: inline-block;margin-bottom: 20px;">
<span v-if="tabChange.tabActiveName == 1" style="display: inline-block;margin-bottom: 20px;">
<el-badge :value="mergeList.length" class="item">
<router-link :to="{ path: 'buyorder-mergeorder', query: {} }">
<el-button style="" size="mini" v-if="isAuth('book:buyorder')" type="primary"
:disabled="mergeList.length > 0 ? false : true">合并发</el-button>
:disabled="mergeList.length > 0 ? false : true">合并发</el-button>
</router-link>
</el-badge>
</span>
<!-- <span style="" v-if="tabChange.tabActiveName == 1">
<el-button style="margin-left: 10px;" size="mini" v-if="isAuth('book:buyorder')" type="primary"
:disabled="checkedOrders.length <= 0" @click="checkDeliver">批量发</el-button>
:disabled="checkedOrders.length <= 0" @click="checkDeliver">批量发</el-button>
</span> -->
<span style="" v-if="tabChange.tabActiveName == 2 && tabChange.isPrint == 1">
<el-button style="margin-left: 10px;" size="mini" v-if="isAuth('book:buyorder')" type="warning"
@@ -165,10 +165,10 @@
</el-col>
<el-col :span="6">
<div class="tip">
<div v-if="fitem.isSend == 1" class="hasSplit"><span style="color:#999">该订单已被拆分发</span>
<div v-if="fitem.isSend == 1" class="hasSplit"><span style="color:#999">该订单已被拆分发</span>
<!-- <el-button type="text" @click="showOrderSheet(fitem.orderId)" size="mini">查看面单</el-button> -->
</div>
<!-- <icon-svg name="zhuyi"></icon-svg> 订单存在可合并发 -->
<!-- <icon-svg name="zhuyi"></icon-svg> 订单存在可合并发 -->
<!-- <router-link :to="{ path: 'order-buyorderdetail', query: { orderId: fitem.orderId , ordertype: fitem.orderStatus} }">
<el-button type="primary" style="color: #515a6e;" size="mini">订单详情</el-button>
</router-link> -->
@@ -239,7 +239,7 @@
</div>
</div>
<div class="buier td3 xcenter">
<div class="tabName">信息
<div class="tabName">信息
<div style="color: #515a6e;"><span
style="color: #515a6e;">{{ fitem.consignee.consigneeName }}</span>&nbsp;&nbsp;&nbsp;<span
style="color: #515a6e;">{{ fitem.consignee.consigneeMobile }}</span></div>
@@ -247,25 +247,25 @@
{{ fitem.consignee.province }}-{{ fitem.consignee.city }}-{{ fitem.consignee.county }}-{{ fitem.consignee.address }}
</div>
<div style="margin-bottom:10px">
<el-button @click="changeAddressShow(fitem)" type="primary" size="mini" plain>修改收信息</el-button>
<!-- <a href="#" v-if="fitem.orderStatus <= 1"><i class="el-icon-edit"></i>修改收信息</a> -->
<el-button @click="changeAddressShow(fitem)" type="primary" size="mini" plain>修改收信息</el-button>
<!-- <a href="#" v-if="fitem.orderStatus <= 1"><i class="el-icon-edit"></i>修改收信息</a> -->
</div>
</div>
<div class="tabContent">
<!-- <div>用户id{{ fitem.userId }}</div> -->
<!-- <div>地址{{ fitem.address }}</div> -->
<!-- <div class="buier_tip">买家备注尽快发</div> -->
<!-- <div>地址{{ fitem.address }}</div> -->
<!-- <div class="buier_tip">买家备注尽快发</div> -->
</div>
</div>
<div class="buier td4 xcenter flexbox" style="align-items: center; justify-content: center;">
<div>
<div class="orderStatus">待发</div>
<div class="orderStatus">待发</div>
<!-- <div class="tabContent">
<div class="time">支付时间2023-02-09 14:16:08</div>
</div> -->
<div><el-button style=" line-height: 6px;" type="primary" size="mini"
@click="orderDeliver(fitem)"></el-button>
@click="orderDeliver(fitem)"></el-button>
</div>
</div>
</div>
@@ -307,7 +307,7 @@
</el-table-column>
<!-- <el-table-column prop="userName" header-align="center" align="center" label="下单人姓名">
</el-table-column> -->
<el-table-column prop="shippingUser" header-align="center" align="center" label="收人信息">
<el-table-column prop="shippingUser" header-align="center" align="center" label="收人信息">
<template slot-scope="scope">
<span
v-if="scope.row.consignee.consigneeName && scope.row.consignee.consigneeName != ''">{{scope.row.consignee.consigneeName}}</span>
@@ -399,13 +399,13 @@
</div>
</template>
</el-table-column>
<!-- <el-table-column prop="shippingTime" header-align="center" align="center" label="发时间">
<!-- <el-table-column prop="shippingTime" header-align="center" align="center" label="发时间">
</el-table-column> -->
<el-table-column prop="orderStatus" header-align="center" align="center" label="订单状态" width="80">
<template slot-scope="scope">
<el-tag v-if="scope.row.orderStatus == 0" type="success">待付款</el-tag>
<el-tag v-if="scope.row.orderStatus == 1" type="success">待发</el-tag>
<el-tag v-if="scope.row.orderStatus == 2" type="danger">已发</el-tag>
<el-tag v-if="scope.row.orderStatus == 1" type="success">待发</el-tag>
<el-tag v-if="scope.row.orderStatus == 2" type="danger">已发</el-tag>
<el-tag v-if="scope.row.orderStatus == 3" type="warning">已完成</el-tag>
<el-tag v-if="scope.row.orderStatus == 4" type="info">交易失败</el-tag>
</template>
@@ -451,17 +451,17 @@
</template>
</el-table-column>
</el-table>
<el-dialog title="修改收信息" :visible.sync="changeAddVisible" width="500" v-loading="addressLoad"
<el-dialog title="修改收信息" :visible.sync="changeAddVisible" width="500" v-loading="addressLoad"
:close="changeAddHandleClose">
<div>
<el-form ref="addressFormRef" :model="addressForm" label-width="120px" :rules="addressFormRule">
<el-form-item label="收人:" prop="name">
<el-form-item label="收人:" prop="name">
<el-input v-model="addressForm.name"></el-input>
</el-form-item>
<el-form-item label="收联系电话:" prop="tel">
<el-form-item label="收联系电话:" prop="tel">
<el-input v-model="addressForm.tel"></el-input>
</el-form-item>
<el-form-item label="收地址:" prop="haveValue">
<el-form-item label="收地址:" prop="haveValue">
<!-- <el-input v-model="addressForm.address"></el-input> -->
<!-- 省市区-->
<!-- -->
@@ -520,7 +520,7 @@
<!-- 物流详情弹窗 -->
<deliverDetail ref="deliverDetail" :visible="deliverDetailVisible" :deliverOrder="oprateOrder"
@closeDeliverDetailDialog="closeDeliverDetailDialog"></deliverDetail>
<!-- 代发转为发货 -->
<!-- 代发转为已发出 -->
<setDeliverDialog ref="setDeliverDialog" :visible="setDeliverVisible" :selectData="checkedOrders"
@closeDeliverDialog='closeDeliverDialog'></setDeliverDialog>
<!-- 设置备注 -->
@@ -528,10 +528,10 @@
:visible="editBeizhudialogVisible" :editBeizhuform="editBeizhuform" @closeDialog='closeBeizhuDialog'
@refreshDataList="getDataList">
</updateOrderBeiZhu>
<!-- 单个订单发 -->
<!-- 单个订单发 -->
<splitDeliver :orderitem="oprateOrder" :visible="splitDeliverVisible" @closeDialog='closeOrder1Dialog'>
</splitDeliver>
<!--点合并发时弹出 可合并订单弹窗 -->
<!--点合并发时弹出 可合并订单弹窗 -->
<mergeDliver v-if="mergeList" :visible="mergeDliverVisible" :mergeList="mergeList"
@closeDialog='closemergeDliverDialog'>
</mergeDliver>
@@ -578,7 +578,7 @@
isIndeterminate: true,
checkAll: false
},
multipleDisabled: false, // 待发多选是否可用
multipleDisabled: false, // 待发多选是否可用
//childrenChecked:[],
checkedOrders: [], // 新的筛选
fullscreenLoading: false,
@@ -588,8 +588,8 @@
sheetVisible: false,
dataList: [],
tabChange: {
tabActiveName: '1', // tab筛选默认all全部 0待付款 1待发 2已发 3已完成
isPrint: 0, // 已发列表筛选 0显示订单 1显示可打印列表,
tabActiveName: '1', // tab筛选默认all全部 0待付款 1待发 2已发 3已完成
isPrint: 0, // 已发列表筛选 0显示订单 1显示可打印列表,
orderName: '0', //订单名称筛选 all:全部 0:健康超市 1:电子书 2:充值订单
sheetCode: 2, // 面单状态2 未打印1 已打印 0全部
},
@@ -659,17 +659,17 @@
addressFormRule: {
name: [{
required: true,
message: '请输入收人信息',
message: '请输入收人信息',
trigger: 'blur'
}],
tel: [{
required: true,
message: '请输入收联系电话信息',
message: '请输入收联系电话信息',
trigger: 'blur'
}],
county: [{
required: true,
message: '请选择收地址',
message: '请选择收地址',
trigger: 'blur'
}],
addressXX: [{
@@ -716,7 +716,7 @@
}
this.$refs['addressFormRef'].validate((valid) => {
if (valid) {
// console.log('修改收地址')
// console.log('修改收地址')
this.$http({
url: this.$http.adornUrl('/book/buyOrder/modifyConsigneeAddress'),
method: 'post',
@@ -837,9 +837,9 @@
})
this.changeAddVisible = true
// console.log('显示修改收地址')
// console.log('显示修改收地址')
},
// 混合发
// 混合发
showAnyDialog(item) {
if (!item.printString || item.printString == '') return this.$message.error('暂无面单数据')
this.anyDialogContent = item.printString
@@ -883,7 +883,7 @@
this.sheetListLoading = false
})
},
// 检查待发货单选按钮是否可用
// 检查待发出订单是否有已选中的订单
isMultipleDisabled() {
this.dataList.forEach(item => {
if (item.isSend === '1') {
@@ -1227,14 +1227,14 @@
closeSheetDliverDialog(val) {
this.sheetVisible = val
},
// 批量发
// 批量发
setDeliver() {
this.setDeliverVisible = true
},
// 发前检查是否有可合并项(废除)
// 发前检查是否有可合并项(废除)
checkDeliver() {
// 确认
this.$confirm(`正在批量发,共选中了${this.checkedOrders.length}条数据, 是否继续?`, '提示', {
this.$confirm(`正在批量发,共选中了${this.checkedOrders.length}条数据, 是否继续?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '点错了',
type: 'warning'
@@ -1245,7 +1245,7 @@
}).catch(() => {});
},
// 单个订单发
// 单个订单发
orderDeliver(item) {
console.log(item)
this.oprateOrder = item
@@ -1282,7 +1282,7 @@
})
return true
},
// 关闭单个订单发弹窗
// 关闭单个订单发弹窗
closeOrder1Dialog(val) {
this.splitDeliverVisible = val
// this.oprateOrder = {}
@@ -1295,7 +1295,7 @@
closeanyDialogDialog() {
this.anyDialogVisible = false
},
// 关闭批量发合并项弹窗
// 关闭批量发合并项弹窗
closemergeDliverDialog(val) {
console.log(val)
this.mergeDliverVisible = false

View File

@@ -6,10 +6,10 @@
>待付款</span
>
<span v-if="orderDetails.orderStatus == 1" class="item hightLight1"
>待发</span
>待发</span
>
<span v-if="orderDetails.orderStatus == 2" class="item hightLight2"
>已发</span
>已发</span
>
<span v-if="orderDetails.orderStatus == 3" class="item hightLight3"
>已完成</span
@@ -48,15 +48,15 @@
>待付款</span
>
<span v-if="orderDetails.orderStatus == 1" class="hightLight"
>待发</span
>待发</span
>
<span v-if="orderDetails.orderStatus == 2" class="hightLight"
>已发</span
>已发</span
>
<span v-if="orderDetails.orderStatus == 3" class="hightLight"
>已完成</span
>
<!-- <el-button v-if="orderDetails.orderStatus == 1" @click="godeliver" class="text_button" size="mini" type="text">去发</el-button> -->
<!-- <el-button v-if="orderDetails.orderStatus == 1" @click="godeliver" class="text_button" size="mini" type="text">去发</el-button> -->
</li>
<!-- <li><span class="infoTitle">商品总数</span><span>{{orderDetails.}}</span></li> -->
<!-- <li>
@@ -394,19 +394,19 @@
href="#"
@click="changeAddressShow"
v-if="orderDetails.orderStatus <= 1 && pageType != 'user'"
><i class="el-icon-edit"></i>修改收信息</a
><i class="el-icon-edit"></i>修改收信息</a
>
</div>
<div style="background:#f9f9f9; padding:5px; overflow:hidden; ">
<li>
<span class="infoTitle">收人:</span
<span class="infoTitle">收人:</span
><span>{{ orderDetails.consignee.consigneeName }}</span
>&nbsp;&nbsp;&nbsp;<span>{{
orderDetails.consignee.consigneeMobile
}}</span>
</li>
<li>
<span class="infoTitle">收地址:</span
<span class="infoTitle">收地址:</span
><span
>{{ orderDetails.consignee.province }}-{{
orderDetails.consignee.city
@@ -570,7 +570,7 @@
</div>
</el-dialog>
<el-dialog
title="修改收信息"
title="修改收信息"
:visible.sync="changeAddVisible"
width="500"
:close="changeAddHandleClose"
@@ -583,13 +583,13 @@
label-width="120px"
:rules="addressFormRule"
>
<el-form-item label="收人:" prop="name">
<el-form-item label="收人:" prop="name">
<el-input v-model="addressForm.name"></el-input>
</el-form-item>
<el-form-item label="收联系电话:" prop="tel">
<el-form-item label="收联系电话:" prop="tel">
<el-input v-model="addressForm.tel"></el-input>
</el-form-item>
<el-form-item label="收地址:">
<el-form-item label="收地址:">
<!-- <el-input v-model="addressForm.address"></el-input> -->
<!-- 省市区-->
<!-- 省 -->
@@ -740,21 +740,21 @@ export default {
name: [
{
required: true,
message: "请输入收人信息",
message: "请输入收人信息",
trigger: "blur"
}
],
tel: [
{
required: true,
message: "请输入收联系电话信息",
message: "请输入收联系电话信息",
trigger: "blur"
}
],
county: [
{
required: true,
message: "请选择收地址",
message: "请选择收地址",
trigger: "blur"
}
],
@@ -931,13 +931,13 @@ export default {
console.log(e, "e");
});
this.changeAddVisible = true;
// console.log('显示修改收地址')
// console.log('显示修改收地址')
},
// 修改收信息
// 修改收信息
changeAddress() {
this.$refs["addressFormRef"].validate(valid => {
if (valid) {
// console.log('修改收地址')
// console.log('修改收地址')
this.$http({
url: this.$http.adornUrl("/book/buyOrder/modifyConsigneeAddress"),
method: "post",
@@ -1056,7 +1056,7 @@ export default {
this.setDeliverVisible = false;
this.getData();
},
// 去发
// 去发
godeliver() {
this.orderList[0] = this.query.orderSn;
this.setDeliverVisible = true;

View File

@@ -7,9 +7,9 @@
<div class="deliverInfo" style="width: 300px; margin: 10px auto;" v-if="activities != []">
<!-- <div style="margin-bottom: 5px;"><i class="el-icon-location-outline"></i><span
style=" margin-left:10px;">地址</span>天津市河东区天津站</div> -->
style=" margin-left:10px;">地址</span>天津市河东区天津站</div> -->
<div style=""><icon-svg name="ren"></icon-svg><span
style=" margin-left:10px;"></span>{{ deliverOrder.userName }}</div>
style=" margin-left:10px;"></span>{{ deliverOrder.userName }}</div>
<div style="margin-bottom: 5px;"><icon-svg name="dianhua"></icon-svg><span style=" margin-left:10px;"> </span>{{ deliverOrder.userPhone }}</div>
</div>

View File

@@ -16,7 +16,7 @@
<div style="padding: 10px;" class="">
<div><span class="infoTitle">用户名</span><span>{{de.userName}}</span></div>
<div><span class="infoTitle">电话</span><span>{{de.tel}}</span></div>
<div><span class="infoTitle">地址</span><span>{{de.address}}</span></div>
<div><span class="infoTitle">地址</span><span>{{de.address}}</span></div>
</div>
</div>
</div>

View File

@@ -1,5 +1,5 @@
<template>
<el-dialog title="订单发" center :visible.sync="visible" width="900px" :before-close="beforeCloseDialog">
<el-dialog title="订单发" center :visible.sync="visible" width="900px" :before-close="beforeCloseDialog">
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="物流公司" prop="deliverLcd">
<el-select size="mini" v-model="ruleForm.deliverLcd" placeholder="请选择物流公司" @change="selectChanged">
@@ -7,10 +7,10 @@
:value="item.expressCode"></el-option>
</el-select>
<el-radio-group v-model="radio3" size="mini" style="float: right;" @change="tabChange">
<!-- 三种发形态-general普通发,merge:合并发,mix:混合发 -->
<el-radio-button label="general">普通发</el-radio-button>
<!-- <el-radio-button label="merge">合并发</el-radio-button> -->
<el-radio-button label="mix">混合发</el-radio-button>
<!-- 三种发形态-general普通发,merge:合并发,mix:混合发 -->
<el-radio-button label="general">普通发</el-radio-button>
<!-- <el-radio-button label="merge">合并发</el-radio-button> -->
<el-radio-button label="mix">混合发</el-radio-button>
</el-radio-group>
</el-form-item>
</el-form>
@@ -63,7 +63,7 @@
<div style="padding-bottom: 10px;" class="">
<div><span class="infoTitle">用户名</span><span>{{mixOrderList[0].consignee.consigneeName}}</span>
<span class="infoTitle">电话</span><span>{{mixOrderList[0].consignee.consigneeMobile}}</span>
<span class="infoTitle">地址</span><span>{{mixOrderList[0].consignee.province}}-{{mixOrderList[0].consignee.city}}-{{mixOrderList[0].consignee.county}}-{{mixOrderList[0].consignee.address}}</span>
<span class="infoTitle">地址</span><span>{{mixOrderList[0].consignee.province}}-{{mixOrderList[0].consignee.city}}-{{mixOrderList[0].consignee.county}}-{{mixOrderList[0].consignee.address}}</span>
</div>
<div></div>
</div>
@@ -76,7 +76,7 @@
<div style="padding: 10px;" class="">
<div><span class="infoTitle">用户名</span><span>{{item.userName}}</span></div>
<div><span class="infoTitle">电话</span><span>{{item.tel}}</span></div>
<div><span class="infoTitle">地址</span><span>{{item.address}}</span></div>
<div><span class="infoTitle">地址</span><span>{{item.address}}</span></div>
</div>
</div>
</div> -->
@@ -122,8 +122,8 @@
</ul>
</div>
<span slot="footer" class="dialog-footer">
<el-button v-if="radio3 === 'mix' && mixOrderList.length != 0" type="primary" @click="submit"> </el-button>
<el-button v-if="radio3 == 'general'" type="primary" @click="submit1"> </el-button>
<el-button v-if="radio3 === 'mix' && mixOrderList.length != 0" type="primary" @click="submit"> </el-button>
<el-button v-if="radio3 == 'general'" type="primary" @click="submit1"> </el-button>
</span>
</el-dialog>
</template>
@@ -141,7 +141,7 @@ export default {
},
data() {
return {
// 普通发list
// 普通发list
productsCodeList:[],
productsIdsList:[],
radio3: 'general',
@@ -157,7 +157,7 @@ export default {
]
},
orderIds: [],
// 混合发数组
// 混合发数组
mixOrderList: []
}
},
@@ -237,7 +237,7 @@ export default {
},
//
// getProductsCodeList
// 普通发
// 普通发
submit1() {
this.getNewProducts()
// console.log(this.productsIdsList)
@@ -264,7 +264,7 @@ export default {
loading.close();
// console.log(data)
this.beforeCloseDialog()
return this.$message.success('发成功')
return this.$message.success('发成功')
}else{
loading.close();
console.log('e',data)
@@ -301,7 +301,7 @@ export default {
this.dataListLoading = false
})
},
//混合发
//混合发
submit() {
this.$refs['ruleForm'].validate((valid) => {
if (valid) {
@@ -309,7 +309,7 @@ export default {
if (this.checkedList.length == 0 && this.radio3 == 'mix') {
return this.$message.error('请至少选择一条商品')
}
// console.log('执行更新发操作')
// console.log('执行更新发操作')
this.$confirm(`正在执行${this.deliverType} , 是否继续?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
@@ -336,7 +336,7 @@ export default {
loading.close()
// console.log(data)
this.beforeCloseDialog()
return this.$message.success('发成功')
return this.$message.success('发成功')
}else{
loading.close();
this.$message.error(data.msg)
@@ -358,12 +358,12 @@ export default {
deliverType() {
let type = ''
if (this.radio3 == 'mix') {
type = `[混合发],当前操作${this.checkedList.length}个商品打包发`
type = `[混合发],当前操作${this.checkedList.length}个商品打包发`
} else if (this.radio3 == 'merge') {
type = `[合并发]`
type = `[合并发]`
} else {
// 混合发
type = '[普通发]'
// 混合发
type = '[普通发]'
}
return type
}

View File

@@ -153,10 +153,10 @@ export default {
ss = '待付款'
break;
case '1':
ss = '待发'
ss = '待发'
break;
case '2':
ss = '已发'
ss = '已发'
break;
case '3':
ss = '已完成'

View File

@@ -1,13 +1,13 @@
<template>
<div>
<el-dialog title="发配置" :close-on-click-modal="false" :visible.sync="visible" width='500px'
<el-dialog title="发配置" :close-on-click-modal="false" :visible.sync="visible" width='500px'
:before-close="beforeCloseDialog" append-to-body>
<!-- <el-steps :active="stepsActive" simple style="margin-bottom: 20px;;">
<el-step title="获取电子面单" icon="el-icon-tickets"></el-step>
<el-step title="打印电子面单" icon="el-icon-printer"></el-step>
</el-steps> -->
<!-- <el-alert style="margin-bottom: 15px;" v-if="selectData.length > 0"
:title="`您正在对 ${selectData.length} 条数据进行发操作。`" :closable="false" type="success">
:title="`您正在对 ${selectData.length} 条数据进行发操作。`" :closable="false" type="success">
</el-alert> -->
<el-form v-if="ruleForm" :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px"
class="demo-ruleForm">
@@ -19,7 +19,7 @@
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer" style="text-align: center;">
<el-button type="primary" @click="setDevliverHandle">立即发</el-button>
<el-button type="primary" @click="setDevliverHandle">立即发</el-button>
</div>
</el-dialog>
<!-- <ul class="print-ul" style="display: block;">
@@ -102,7 +102,7 @@ export default {
/// this.selectData = []
},
// 发操作
// 发操作
setDevliverHandle() {
this.$refs['ruleForm'].validate((valid) => {
if (valid) {
@@ -127,10 +127,10 @@ export default {
if (data && data.code === 0) {
console.log(data)
this.beforeCloseDialog() // 关闭弹窗
return this.$message.success('发成功')
return this.$message.success('发成功')
} else {
this.beforeCloseDialog() // 关闭弹窗
return this.$message.error('发失败')
return this.$message.error('发失败')
}
}).catch((err) => {

View File

@@ -0,0 +1,98 @@
<template>
<div class="mod-config">
<el-form
:inline="true"
:model="dataForm"
@keyup.enter.native="downloadReport()"
>
<el-form-item label="年份选择">
<el-date-picker
v-model="selectedYear"
type="year"
format="yyyy"
value-format="yyyy"
:picker-options="pickerOptions"
placeholder="选择年份"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
size="small"
@click="downloadReport"
:disabled="!selectedYear"
>
下载报表
</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
data() {
return {
dataForm: {},
selectedYear: new Date().getFullYear().toString(),
pickerOptions: {
disabledDate(time) {
// 禁用今年以后的年份
return time.getFullYear() > new Date().getFullYear()
}
}
};
},
methods: {
downloadReport() {
if (!this.selectedYear) {
this.$message.warning('请选择年份');
return;
}
const filename = `灵枢年度报表_${this.selectedYear}年.xlsx`;
this.$message({
message: '请耐心等待...',
type: 'info',
duration: 3000,
showClose: true
});
this.$http({
url: this.$http.adornUrl('/master/statistics/getLSYearStatistics'),
method: 'post',
data: this.$http.adornData({
year: this.selectedYear
}),
responseType: 'blob'
}).then(res => {
const blob = new Blob([res.data], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = filename;
link.click();
window.URL.revokeObjectURL(link.href);
this.$message({
message: '灵枢年度报表文件下载完成,请注意查看!',
type: 'success',
duration: 3000,
showClose: true
});
});
}
}
};
</script>
<style scoped>
.mod-config {
padding: 20px;
}
.el-form {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,229 @@
<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">
@import '../styles/statistics-common.less';
.course-statistics-container {
.sb-page-height-with-table();
}
.sb-form-item-ten();
.sb-dialog-body-no-top-padding();
</style>

View File

@@ -0,0 +1,439 @@
<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">
@import '../styles/statistics-common.less';
.course-statistics-container {
.sb-page-height-with-table();
}
.dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.dialog-title {
font-size: 16px;
font-weight: bold;
color: #303133;
}
.sb-form-item-ten();
.sb-dialog-body-no-top-padding();
</style>

View File

@@ -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 lang="less">
@import '../styles/statistics-common.less';
.sb-form-item-ten();
</style>

View File

@@ -0,0 +1,187 @@
<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">
@import '../styles/statistics-common.less';
.course-label-statistics-container {
.sb-page-height-with-table();
}
.sb-form-item-ten();
</style>

View File

@@ -0,0 +1,153 @@
.sb-form-item-zero() {
.el-form-item {
margin-bottom: 0 !important;
}
}
.sb-form-item-ten() {
.el-form .el-form-item {
margin-bottom: 10px !important;
}
}
.sb-table-compact() {
/deep/ .el-table .cell,
/deep/ .el-table th > .cell {
padding-top: 4px;
padding-bottom: 4px;
line-height: 20px;
}
}
.sb-statistics-table-min() {
/deep/ .statistics-table {
min-height: 500px;
}
/deep/ .statistics-table .el-table__body-wrapper,
/deep/ .statistics-table .el-table__empty-block {
min-height: 452px;
}
}
.sb-page-height-with-table() {
display: flex;
flex-direction: column;
height: calc(100vh - 220px);
.table-container {
flex: 1;
overflow: hidden;
}
}
.sb-dialog-body-no-top-padding() {
/deep/ .el-dialog__wrapper .el-dialog__body {
padding-top: 0 !important;
}
}
.sb-white-panel(@padding: 8px) {
background: #fff;
border-radius: 4px;
padding: @padding;
}
.sb-flex-column-fill() {
display: flex;
flex: 1;
flex-direction: column;
}
.sb-detail-header(@mb: 16px) {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: @mb;
}
.sb-title-16() {
font-size: 16px;
font-weight: 700;
color: #303133;
}
.sb-summary-card-text() {
.summary-card__label {
margin-bottom: 8px;
font-size: 14px;
color: #606266;
}
.summary-card__value {
font-size: 24px;
font-weight: 700;
color: #1f2d3d;
}
}
.sb-summary-section-flex(@gap: 12px) {
display: flex;
flex-wrap: wrap;
gap: @gap;
}
.sb-summary-card-shell(
@basis: 180px,
@width: 180px,
@minHeight: 180px,
@padding: 16px,
@bg: linear-gradient(180deg, #f8fbff 0%, #ffffff 100%)
) {
display: flex;
flex: 1 1 @basis;
width: @width;
flex-direction: column;
min-height: @minHeight;
padding: @padding;
border: 1px solid #ebeef5;
border-radius: 8px;
background: @bg;
box-shadow: 0 4px 12px rgba(31, 45, 61, 0.06);
}
.sb-summary-card-two-columns(@basis: 280px, @width: 280px) {
flex-basis: @basis;
width: @width;
}
.sb-summary-card-title(@mb: 14px) {
margin-bottom: @mb;
.sb-title-16();
}
.sb-summary-card-body(@gap: 10px) {
display: flex;
flex: 1;
flex-direction: column;
gap: @gap;
}
.sb-summary-card-body-two-columns(@rowGap: 10px, @colGap: 30px) {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: @rowGap @colGap;
}
.sb-summary-line(@gap: 12px) {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: @gap;
line-height: 1.5;
}
.sb-summary-line-label() {
color: #606266;
}
.sb-summary-line-value() {
font-weight: 700;
color: #1f2d3d;
text-align: right;
}

View File

@@ -0,0 +1,14 @@
<template>
<userStatisticsBase mode="null" />
</template>
<script>
import userStatisticsBase from './userStatisticsBase'
export default {
components: {
userStatisticsBase
}
}
</script>

View File

@@ -0,0 +1,14 @@
<template>
<userStatisticsBase mode="day" />
</template>
<script>
import userStatisticsBase from './userStatisticsBase'
export default {
components: {
userStatisticsBase
}
}
</script>

View File

@@ -0,0 +1,43 @@
<template>
<div class="mod-config">
<el-tabs v-model="activeTab" type="card">
<el-tab-pane label="当日" name="dayUser" />
<el-tab-pane label="月度" name="monthUser" />
<el-tab-pane label="年度" name="yearUser" />
<el-tab-pane label="全部" name="allUser" />
<el-tab-pane label="留存率" name="retainUser" />
</el-tabs>
<keep-alive>
<component :is="activeTab" />
</keep-alive>
</div>
</template>
<script>
import allUser from './allUser'
import dayUser from './dayUser'
import monthUser from './monthUser'
import yearUser from './yearUser'
import retainUser from './retainUser'
export default {
components: {
dayUser,
monthUser,
yearUser,
allUser,
retainUser
},
data() {
return {
activeTab: 'dayUser'
}
}
}
</script>
<style scoped lang="less">
@import '../styles/statistics-common.less';
.sb-form-item-ten();
</style>

View File

@@ -0,0 +1,14 @@
<template>
<userStatisticsBase mode="month" />
</template>
<script>
import userStatisticsBase from './userStatisticsBase'
export default {
components: {
userStatisticsBase
}
}
</script>

View File

@@ -0,0 +1,230 @@
<template>
<div class="retain-statistics-container" 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="table-section">
<div ref="tableContainer" class="table-container">
<el-table
:data="tableData"
border
:max-height="tableHeight"
:header-cell-style="{ textAlign: 'center', padding: '6px 0' }"
:cell-style="{ textAlign: 'center', padding: '6px 0' }"
>
<el-table-column prop="dayTime" label="日期" min-width="120" />
<el-table-column prop="count" label="新增用户数" min-width="110" />
<el-table-column prop="nextDay" label="次日留存用户数" min-width="130" />
<el-table-column prop="nextRate" label="次日留存率" min-width="110" />
<el-table-column prop="day7" label="7日留存用户数" min-width="120" />
<el-table-column prop="rate7" label="7日留存率" min-width="100" />
<el-table-column prop="day30" label="30日留存用户数" min-width="130" />
<el-table-column prop="rate30" label="30日留存率" min-width="110" />
</el-table>
</div>
</div>
</div>
</template>
<script>
import http from '@/utils/httpRequest'
function formatMonth(date) {
const y = date.getFullYear()
const m = `${date.getMonth() + 1}`.padStart(2, '0')
return `${y}-${m}`
}
function normalizeNumber(value) {
if (value === null || value === undefined || value === '') return 0
const num = Number(value)
return Number.isNaN(num) ? 0 : num
}
function formatRate(value) {
if (value === null || value === undefined || value === '') return '0%'
if (typeof value === 'string') {
const text = value.trim()
if (!text) return '0%'
if (text.includes('%')) return text
const num = Number(text)
if (Number.isNaN(num)) return '0%'
if (num <= 1) return `${(num * 100).toFixed(0)}%`
return `${num.toFixed(0)}%`
}
const num = Number(value)
if (Number.isNaN(num)) return '0%'
if (num <= 1) return `${(num * 100).toFixed(0)}%`
return `${num.toFixed(0)}%`
}
export default {
data() {
return {
loading: false,
exportLoading: false,
currentMonth: '',
tableData: [],
tableHeight: 500,
pickerOptions: {
disabledDate(time) {
const now = new Date()
return time.getTime() > now.getTime()
}
}
}
},
activated() {
if (!this.currentMonth) {
this.currentMonth = formatMonth(new Date())
}
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
}
},
normalizeRows(userList = []) {
return userList.map((item) => ({
dayTime: item.dayTime || '--',
count: normalizeNumber(item.count),
nextDay: normalizeNumber(item.nextDay),
nextRate: formatRate(item.nextRate),
day7: normalizeNumber(item['7Day']),
rate7: formatRate(item['7Rate']),
day30: normalizeNumber(item['30Day']),
rate30: formatRate(item['30Rate'])
}))
},
getDataList() {
if (!this.currentMonth) {
this.currentMonth = formatMonth(new Date())
}
this.loading = true
http({
url: http.adornUrl('/master/statisticsBusinessUser/getUserRegisterStayInfo'),
method: 'post',
data: http.adornData({
date: this.currentMonth
})
}).then(({ data }) => {
if (data && data.code === 0) {
this.tableData = this.normalizeRows(data.userList || [])
} else {
this.tableData = []
}
}).finally(() => {
this.loading = false
this.$nextTick(() => {
this.calculateTableHeight()
})
})
},
exportData() {
if (!this.currentMonth) {
this.currentMonth = formatMonth(new Date())
}
this.exportLoading = true
http({
url: http.adornUrl('/master/statisticsBusinessUser/exportUserRegisterStayInfo'),
method: 'post',
data: http.adornData({
date: this.currentMonth
}),
responseType: 'blob'
}).then((response) => {
const blob = new Blob([response.data])
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = `留存率统计报表-${this.currentMonth}.xlsx`
link.click()
URL.revokeObjectURL(link.href)
}).catch(() => {
this.$message.error('下载失败,请稍后重试')
}).finally(() => {
this.exportLoading = false
})
}
}
}
</script>
<style scoped lang="less">
@import '../styles/statistics-common.less';
.retain-statistics-container {
display: flex;
flex-direction: column;
height: calc(100vh - 220px);
gap: 8px;
}
.query-section,
.table-section {
.sb-white-panel(8px);
}
.table-section {
display: flex;
flex: 1;
min-height: 0;
flex-direction: column;
}
.sb-form-item-zero();
.table-container {
flex: 1;
min-height: 0;
overflow: hidden;
}
.sb-table-compact();
</style>

View File

@@ -0,0 +1,367 @@
<template>
<div class="user-statistics-page" v-loading="loading">
<div v-if="mode !== 'null'" class="query-section">
<el-form :inline="true" @keyup.enter.native="getDataList">
<el-form-item :label="queryLabel">
<el-date-picker
v-model="currentDate"
:type="pickerType"
:format="displayFormat"
:value-format="valueFormat"
placeholder="请选择"
size="small"
popper-append-to-body
:picker-options="pickerOptions"
@change="handleQueryChange"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="refreshData">刷新</el-button>
</el-form-item>
</el-form>
</div>
<div class="summary-section">
<div class="summary-card">
<div class="summary-card__label">{{ timePrefix }}登录人数</div>
<div class="summary-card__value">{{ summary.loginCount }}</div>
</div>
<div class="summary-card">
<div class="summary-card__label">{{ timePrefix }}注册人数</div>
<div class="summary-card__value">{{ summary.createCount }}</div>
</div>
<div class="summary-card">
<div class="summary-card__label">截止今日 近30天内活跃人数</div>
<div class="summary-card__value">{{ summary.userCountFor30Day }}</div>
</div>
</div>
<div class="table-section">
<div class="table-header">
<div class="table-title">用户统计明细{{ total }}</div>
<el-button
type="success"
size="small"
:loading="exportLoading"
@click="exportData"
>下载报表</el-button>
</div>
<el-table
:data="tableData"
border
:height="mode !== 'null' ? 470 : 520"
:header-cell-style="{ textAlign: 'center', padding: '6px 0' }"
:cell-style="{ textAlign: 'center', padding: '6px 0' }"
>
<el-table-column type="index" label="序号" width="70" :index="indexMethod" />
<el-table-column prop="name" label="姓名" min-width="100" />
<el-table-column prop="tel" label="电话" min-width="130" />
<el-table-column prop="createTime" label="注册时间" min-width="120" />
<el-table-column prop="loginCity" label="城市" min-width="100" />
<el-table-column prop="come" label="注册来源" min-width="110" />
<el-table-column prop="socialIdentity" label="社会身份" min-width="120" />
<el-table-column prop="identity" label="app用户身份" min-width="120" />
<el-table-column prop="jf" label="天医币" min-width="90" />
<el-table-column prop="point" label="积分" min-width="90" />
<el-table-column prop="beforVip" label="曾vip" min-width="100" />
<el-table-column prop="nowVip" label="现vip" min-width="100" />
<el-table-column prop="ubo" label="是否买过课程" min-width="110" />
<el-table-column prop="ucbl" label="是否复购" min-width="90" />
<el-table-column prop="ucerCount" label="课程证几张" min-width="100" />
<el-table-column prop="loginTime" label="上次登录时间" min-width="160" />
<el-table-column prop="loginMachine" label="设备" min-width="90" />
<el-table-column prop="sex" label="性别" min-width="80" />
<el-table-column prop="nowWatch" label="当日学习时长" min-width="110" />
<el-table-column prop="watch7" label="近七天学习时间" min-width="120" />
<el-table-column prop="totalWatch" label="总学习时长" min-width="100" />
</el-table>
<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;"
/>
</div>
</div>
</template>
<script>
import http from '@/utils/httpRequest'
function formatDate(date) {
const y = date.getFullYear()
const m = `${date.getMonth() + 1}`.padStart(2, '0')
const d = `${date.getDate()}`.padStart(2, '0')
return `${y}-${m}-${d}`
}
function formatMonth(date) {
const y = date.getFullYear()
const m = `${date.getMonth() + 1}`.padStart(2, '0')
return `${y}-${m}`
}
function formatYear(date) {
return `${date.getFullYear()}`
}
function normalizeText(value) {
if (value === null || value === undefined || value === '') {
return '--'
}
const text = String(value).trim()
return text || '--'
}
function normalizeNumber(value) {
if (value === null || value === undefined || value === '') {
return 0
}
const num = Number(value)
return Number.isNaN(num) ? 0 : num
}
export default {
props: {
mode: {
type: String,
required: true
}
},
data() {
return {
loading: false,
exportLoading: false,
currentDate: '',
summary: {
loginCount: 0,
createCount: 0,
userCountFor30Day: 0
},
tableData: [],
page: 1,
limit: 10,
total: 0
}
},
computed: {
queryLabel() {
if (this.mode === 'day') return '统计日期'
if (this.mode === 'month') return '统计月份'
return '统计年份'
},
pickerType() {
if (this.mode === 'day') return 'date'
if (this.mode === 'month') return 'month'
if (this.mode === 'year') return 'year'
return ''
},
displayFormat() {
if (this.mode === 'day') return 'yyyy-MM-dd'
if (this.mode === 'month') return 'yyyy-MM'
if (this.mode === 'year') return 'yyyy'
return ''
},
valueFormat() {
return this.displayFormat
},
timePrefix() {
// if (this.mode === 'day') return '当日'
// if (this.mode === 'month') return '当月'
// if (this.mode === 'year') return '当年'
return '当日'
},
pickerOptions() {
return {
disabledDate(time) {
const now = new Date()
return time.getTime() > now.getTime()
}
}
}
},
activated() {
if (!this.currentDate) {
this.currentDate = this.getDefaultDate()
}
this.getDataList()
},
methods: {
indexMethod(index) {
return (this.page - 1) * this.limit + index + 1
},
handleQueryChange() {
this.page = 1
this.getDataList()
},
refreshData() {
this.page = 1
this.getDataList()
},
handleSizeChange(val) {
this.limit = val
this.page = 1
this.getDataList()
},
handleCurrentChange(val) {
this.page = val
this.getDataList()
},
getDefaultDate() {
const now = new Date()
if (this.mode === 'day') return formatDate(now)
if (this.mode === 'month') return formatMonth(now)
if (this.mode === 'year') return formatYear(now)
return null
},
normalizeRows(userList = []) {
return userList.map((item) => ({
name: normalizeText(item.name),
tel: normalizeText(item.tel),
createTime: normalizeText(item.createTime),
loginCity: normalizeText(item.loginCity),
come: normalizeText(item.come),
socialIdentity: normalizeText(item.socialIdentity),
identity: normalizeText(item.identity),
jf: normalizeNumber(item.jf),
point: normalizeNumber(item.point),
beforVip: normalizeText(item.beforVip),
nowVip: normalizeText(item.nowVip),
ubo: normalizeText(item.ubo),
ucbl: normalizeText(item.ucbl),
ucerCount: normalizeNumber(item.ucerCount),
loginTime: normalizeText(item.loginTime),
loginMachine: normalizeText(item.loginMachine),
sex: normalizeText(item.sex),
nowWatch: normalizeNumber(item.nowWatch),
watch7: normalizeNumber(item['7Watch']),
totalWatch: normalizeNumber(item.totalWatch)
}))
},
getDataList() {
if (!this.currentDate) {
this.currentDate = this.getDefaultDate()
}
this.loading = true
http({
url: http.adornUrl('/master/statisticsBusinessUser/getUserStatistics'),
method: 'post',
data: http.adornData({
date: this.currentDate,
page: this.page,
limit: this.limit
})
}).then(({ data }) => {
if (data && data.code === 0) {
this.summary = {
loginCount: normalizeNumber(data.loginCount),
createCount: normalizeNumber(data.createCount),
userCountFor30Day: normalizeNumber(data.userCountFor30Day)
}
this.tableData = this.normalizeRows(data.userList || [])
this.total = normalizeNumber(data.totalSize)
} else {
this.summary = { loginCount: 0, createCount: 0, userCountFor30Day: 0 }
this.tableData = []
this.total = 0
}
}).finally(() => {
this.loading = false
})
},
exportData() {
if (!this.currentDate) {
this.currentDate = this.getDefaultDate()
}
this.exportLoading = true
http({
url: http.adornUrl('/master/statisticsBusinessUser/exportUserStatistics'),
method: 'post',
data: http.adornData({
date: this.currentDate
}),
responseType: 'blob',
timeout: 0
}).then((response) => {
const blob = new Blob([response.data])
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = `用户统计报表-${this.currentDate || '全部'}.xlsx`
link.click()
URL.revokeObjectURL(link.href)
}).catch(() => {
this.$message.error('下载失败,请稍后重试')
}).finally(() => {
this.exportLoading = false
})
}
}
}
</script>
<style scoped lang="less">
@import '../styles/statistics-common.less';
.sb-form-item-zero();
.sb-table-compact();
.user-statistics-page {
display: flex;
flex-direction: column;
gap: 8px;
}
.query-section,
.summary-section,
.table-section {
.sb-white-panel(8px);
padding-bottom: 0;
}
.query-section {
padding-top: 0;
}
.summary-section {
display: flex;
flex-wrap: nowrap;
gap: 12px;
}
.summary-card {
.sb-summary-card-shell(180px, auto, auto);
min-width: 180px;
}
.summary-card__label {
margin-bottom: 8px;
}
.table-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.table-title {
.sb-title-16();
}
.sb-summary-card-text();
@media (max-width: 1200px) {
.summary-section {
flex-wrap: wrap;
}
.summary-card {
min-width: 200px;
}
}
</style>

View File

@@ -0,0 +1,14 @@
<template>
<userStatisticsBase mode="year" />
</template>
<script>
import userStatisticsBase from './userStatisticsBase'
export default {
components: {
userStatisticsBase
}
}
</script>

View File

@@ -0,0 +1,304 @@
<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
class="statistics-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">
@import '../styles/statistics-common.less';
.sb-form-item-zero();
.sb-table-compact();
.sb-statistics-table-min();
.all-vip-statistics {
display: flex;
flex-direction: column;
gap: 8px;
}
.summary-section,
.detail-section {
.sb-white-panel(8px);
}
.summary-section {
display: flex;
flex-direction: column;
gap: 16px;
}
.summary-block__title {
margin-bottom: 16px;
.sb-title-16();
}
.summary-grid {
.sb-summary-section-flex(12px);
}
.summary-card {
.sb-summary-card-shell(120px, 120px, auto);
min-width: 120px;
}
.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;
}
.detail-section {
.sb-flex-column-fill();
}
.detail-header {
.sb-detail-header(16px);
}
.detail-title {
.sb-title-16();
}
.sb-summary-card-text();
.table-container {
flex: 1;
}
</style>

View File

@@ -0,0 +1,14 @@
<template>
<expires-statistics-base
title="近半年临到期"
:month="6"
/>
</template>
<script>
import expiresStatisticsBase from './expiresStatisticsBase'
export default {
components: { expiresStatisticsBase }
}
</script>

View File

@@ -0,0 +1,263 @@
<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
class="statistics-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">
@import '../styles/statistics-common.less';
.sb-table-compact();
.sb-statistics-table-min();
.vip-expires-statistics {
display: flex;
flex-direction: column;
gap: 8px;
}
.summary-section,
.detail-section {
.sb-white-panel(8px);
}
.detail-section {
.sb-flex-column-fill();
}
.section-title {
margin-bottom: 16px;
.sb-title-16();
}
.summary-grid {
.sb-summary-section-flex(12px);
}
.summary-card {
.sb-summary-card-shell(
120px,
120px,
auto,
16px,
linear-gradient(180deg, #f7fbff 0%, #ffffff 100%)
);
min-width: 120px;
}
.summary-card__label {
margin-bottom: 8px;
}
.detail-header {
.sb-detail-header(16px);
}
.detail-title {
.sb-title-16();
}
.sb-summary-card-text();
.table-container {
flex: 1;
}
</style>

View File

@@ -0,0 +1,14 @@
<template>
<expires-statistics-base
title="近3个月临到期"
:month="3"
/>
</template>
<script>
import expiresStatisticsBase from './expiresStatisticsBase'
export default {
components: { expiresStatisticsBase }
}
</script>

View 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 lang="less">
@import '../styles/statistics-common.less';
.sb-form-item-ten();
</style>

View File

@@ -0,0 +1,319 @@
<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
class="statistics-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">
@import '../styles/statistics-common.less';
.sb-form-item-zero();
.sb-table-compact();
.sb-statistics-table-min();
.month-vip-statistics {
display: flex;
flex-direction: column;
gap: 8px;
}
.query-section,
.summary-section,
.detail-section {
.sb-white-panel(8px);
}
.query-section {
padding-top: 0;
padding-bottom: 0;
}
.summary-section {
.sb-summary-section-flex(12px);
}
.summary-card {
.sb-summary-card-shell(180px, 180px, 190px);
}
.summary-card--two-columns {
.sb-summary-card-two-columns(280px, 280px);
}
.summary-card__title {
.sb-summary-card-title(14px);
}
.summary-card__body {
.sb-summary-card-body(10px);
}
.summary-card--two-columns .summary-card__body {
.sb-summary-card-body-two-columns(10px, 30px);
}
.summary-line {
.sb-summary-line(12px);
}
.summary-line__label {
.sb-summary-line-label();
}
.summary-line__value {
.sb-summary-line-value();
}
.detail-section {
.sb-flex-column-fill();
}
.detail-header {
.sb-detail-header(16px);
}
.detail-title {
.sb-title-16();
}
.table-container {
flex: 1;
}
@media (max-width: 1680px) {
.summary-card {
flex-basis: 170px;
width: 170px;
}
}
@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>

View File

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

View File

@@ -0,0 +1,317 @@
<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">
@import '../styles/statistics-common.less';
.sb-form-item-zero();
.sb-table-compact();
.year-vip-statistics {
display: flex;
flex-direction: column;
gap: 8px;
}
.query-section,
.summary-section,
.table-section {
.sb-white-panel(8px);
}
.query-section {
padding-top: 0;
padding-bottom: 0;
}
.summary-section {
.sb-summary-section-flex(12px);
}
.summary-card {
.sb-summary-card-shell(180px, 180px, 180px);
}
.summary-card--two-columns {
.sb-summary-card-two-columns(280px, 280px);
}
.summary-card__title {
.sb-summary-card-title(14px);
}
.summary-card__body {
.sb-summary-card-body(10px);
}
.summary-card--two-columns .summary-card__body {
.sb-summary-card-body-two-columns(10px, 30px);
}
.summary-line {
.sb-summary-line(12px);
}
.summary-line__label {
.sb-summary-line-label();
}
.summary-line__value {
.sb-summary-line-value();
}
.table-title {
margin-bottom: 12px;
.sb-title-16();
}
@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>

View File

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

View File

@@ -27,7 +27,7 @@
size="small"
style="width: 100%;"
v-model="dataForm.userKey"
debounce="500"
:debounce="500"
:fetch-suggestions="loadAll"
placeholder="请输入手机号/邮箱"
@select="handleSelect"

View File

@@ -7,10 +7,10 @@
>待付款</span
>
<span v-if="orderDetails.orderStatus == 1" class="item hightLight1"
>待发</span
>待发</span
>
<span v-if="orderDetails.orderStatus == 2" class="item hightLight2"
>已发</span
>已发</span
>
<span v-if="orderDetails.orderStatus == 3" class="item hightLight3"
>已完成</span
@@ -966,13 +966,13 @@ if (this.dataForm.type == "master") {
console.log(e, "e");
});
this.changeAddVisible = true;
// console.log('显示修改收地址')
// console.log('显示修改收地址')
},
// 修改收信息
// 修改收信息
changeAddress() {
this.$refs["addressFormRef"].validate(valid => {
if (valid) {
// console.log('修改收地址')
// console.log('修改收地址')
this.$http({
url: this.$http.adornUrl("/book/buyOrder/modifyConsigneeAddress"),
method: "post",
@@ -1034,7 +1034,7 @@ if (this.dataForm.type == "master") {
this.setDeliverVisible = false;
this.getData();
},
// 去发
// 去发
godeliver() {
this.orderList[0] = this.query.orderSn;
this.setDeliverVisible = true;