52 changed files with 4855 additions and 257 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,393 @@ |
|||||
|
# 各校区综合数据管理系统需求文档 |
||||
|
|
||||
|
## 文档信息 |
||||
|
- **生成时间**: 2025-07-29 00:19:30 |
||||
|
- **数据来源**: 副本各校区月&年综合报表.xlsx |
||||
|
- **文档版本**: v1.0 |
||||
|
- **项目类型**: 教育培训机构多校区数据管理系统 |
||||
|
|
||||
|
## 1. 项目背景与现状 |
||||
|
|
||||
|
### 1.1 业务背景 |
||||
|
该教育培训机构拥有多个校区,需要对各校区的经营数据进行统一管理和分析。当前使用Excel进行手工统计,存在数据分散、统计效率低、易出错等问题。 |
||||
|
|
||||
|
### 1.2 校区分布 |
||||
|
目前包含以下校区: |
||||
|
- **吾悦校区** |
||||
|
- **国兴校区** |
||||
|
- **国贸校区** |
||||
|
- **远大校区** |
||||
|
- **龙湖校区** |
||||
|
|
||||
|
### 1.3 现状问题 |
||||
|
- 数据录入依赖手工Excel操作,效率低下 |
||||
|
- 各校区数据分散,难以进行横向对比分析 |
||||
|
- 缺乏实时数据监控和预警机制 |
||||
|
- 报表生成耗时,影响决策效率 |
||||
|
|
||||
|
## 2. 业务需求分析 |
||||
|
|
||||
|
### 2.1 数据维度分析 |
||||
|
|
||||
|
#### 2.1.1 时间维度 |
||||
|
系统需要支持以下时间维度的数据统计: |
||||
|
- 1月 |
||||
|
- 2月 |
||||
|
- 3月 |
||||
|
- 4月 |
||||
|
- 5月 |
||||
|
- 6月 |
||||
|
- 7月 |
||||
|
- 8月 |
||||
|
- 9月 |
||||
|
- 10月 |
||||
|
- 11月 |
||||
|
- 12月 |
||||
|
- 年度合计 |
||||
|
|
||||
|
#### 2.1.2 校区维度 |
||||
|
支持多校区数据管理: |
||||
|
- 吾悦校区 |
||||
|
- 国兴校区 |
||||
|
- 国贸校区 |
||||
|
- 远大校区 |
||||
|
- 龙湖校区 |
||||
|
|
||||
|
### 2.2 核心业务模块 |
||||
|
|
||||
|
#### 2.2.1 资源管理模块 |
||||
|
**功能描述**: 管理资源管理相关业务数据 |
||||
|
|
||||
|
**核心指标**: |
||||
|
- 线下资源 |
||||
|
- 邀约数 |
||||
|
- 一访到访 |
||||
|
- 一访成交 |
||||
|
- 二访到访 |
||||
|
- 二访成交 |
||||
|
- 成交率 |
||||
|
- 线上资源 |
||||
|
|
||||
|
**业务流程**: |
||||
|
1. 数据录入:相关指标数据录入 |
||||
|
2. 数据统计:自动计算汇总数据 |
||||
|
3. 数据分析:趋势分析和对比分析 |
||||
|
4. 报表生成:生成相关业务报表 |
||||
|
|
||||
|
#### 2.2.2 销售业绩模块 |
||||
|
**功能描述**: 管理销售业绩相关业务数据 |
||||
|
|
||||
|
**核心指标**: |
||||
|
- 成交总课时 |
||||
|
- 成交总金额 |
||||
|
- 均单价 |
||||
|
- (净)成交总单数 |
||||
|
- (净)成交总课时 |
||||
|
- (净)成交总金额 |
||||
|
|
||||
|
**业务流程**: |
||||
|
1. 数据录入:相关指标数据录入 |
||||
|
2. 数据统计:自动计算汇总数据 |
||||
|
3. 数据分析:趋势分析和对比分析 |
||||
|
4. 报表生成:生成相关业务报表 |
||||
|
|
||||
|
#### 2.2.3 转介绍管理模块 |
||||
|
**功能描述**: 管理转介绍管理相关业务数据 |
||||
|
|
||||
|
**核心指标**: |
||||
|
- 转介绍 |
||||
|
- 转介绍资源数 |
||||
|
|
||||
|
**业务流程**: |
||||
|
1. 数据录入:相关指标数据录入 |
||||
|
2. 数据统计:自动计算汇总数据 |
||||
|
3. 数据分析:趋势分析和对比分析 |
||||
|
4. 报表生成:生成相关业务报表 |
||||
|
|
||||
|
#### 2.2.4 续费管理模块 |
||||
|
**功能描述**: 管理续费管理相关业务数据 |
||||
|
|
||||
|
**核心指标**: |
||||
|
- 续费 |
||||
|
- 月应续费人数 |
||||
|
- 续费人数(本月) |
||||
|
- 续费人数(次月) |
||||
|
- 老卡续费人数 |
||||
|
- 总续费数 |
||||
|
- 月卡续费率 |
||||
|
- 续费总课时 |
||||
|
- 续费总金额 |
||||
|
|
||||
|
**业务流程**: |
||||
|
1. 数据录入:相关指标数据录入 |
||||
|
2. 数据统计:自动计算汇总数据 |
||||
|
3. 数据分析:趋势分析和对比分析 |
||||
|
4. 报表生成:生成相关业务报表 |
||||
|
|
||||
|
#### 2.2.5 学员异动模块 |
||||
|
**功能描述**: 管理学员异动相关业务数据 |
||||
|
|
||||
|
**核心指标**: |
||||
|
- 异动 |
||||
|
- 转出人数 |
||||
|
- 转出金额 |
||||
|
- 转入人数 |
||||
|
- 转入金额 |
||||
|
- 退费人数 |
||||
|
- 退费金额 |
||||
|
|
||||
|
**业务流程**: |
||||
|
1. 数据录入:相关指标数据录入 |
||||
|
2. 数据统计:自动计算汇总数据 |
||||
|
3. 数据分析:趋势分析和对比分析 |
||||
|
4. 报表生成:生成相关业务报表 |
||||
|
|
||||
|
#### 2.2.6 经营分析模块 |
||||
|
**功能描述**: 管理经营分析相关业务数据 |
||||
|
|
||||
|
**核心指标**: |
||||
|
- 经营汇总 |
||||
|
- 任务(月) |
||||
|
- 成本(月) |
||||
|
- (净)任务完成率 |
||||
|
- (净)成本完成率 |
||||
|
|
||||
|
**业务流程**: |
||||
|
1. 数据录入:相关指标数据录入 |
||||
|
2. 数据统计:自动计算汇总数据 |
||||
|
3. 数据分析:趋势分析和对比分析 |
||||
|
4. 报表生成:生成相关业务报表 |
||||
|
|
||||
|
|
||||
|
## 3. 功能需求 |
||||
|
|
||||
|
### 3.1 数据录入功能 |
||||
|
- **多校区数据录入**: 支持各校区独立录入数据 |
||||
|
- **批量导入**: 支持Excel文件批量导入 |
||||
|
- **数据校验**: 自动校验数据合理性和完整性 |
||||
|
- **权限控制**: 校区管理员只能录入本校区数据 |
||||
|
|
||||
|
### 3.2 数据统计功能 |
||||
|
- **实时统计**: 数据录入后自动更新统计结果 |
||||
|
- **多维度统计**: 按时间、校区、业务类型等维度统计 |
||||
|
- **自动计算**: 自动计算转化率、完成率等衍生指标 |
||||
|
- **数据汇总**: 自动生成各校区汇总数据 |
||||
|
|
||||
|
### 3.3 数据分析功能 |
||||
|
- **趋势分析**: 各指标的时间趋势分析 |
||||
|
- **对比分析**: 校区间横向对比、同期对比 |
||||
|
- **转化漏斗**: 从资源到成交的完整转化漏斗 |
||||
|
- **异常预警**: 数据异常自动预警 |
||||
|
|
||||
|
### 3.4 报表展示功能 |
||||
|
- **综合仪表盘**: 核心指标实时展示 |
||||
|
- **校区报表**: 单校区详细报表 |
||||
|
- **对比报表**: 多校区对比报表 |
||||
|
- **导出功能**: 支持PDF、Excel格式导出 |
||||
|
|
||||
|
## 4. 技术实现方案 |
||||
|
|
||||
|
### 4.1 系统架构 |
||||
|
- **前端**: Vue.js 3 + Element Plus + ECharts |
||||
|
- **后端**: Node.js + Express + TypeScript |
||||
|
- **数据库**: MySQL 8.0 |
||||
|
- **缓存**: Redis |
||||
|
- **部署**: Docker + Nginx |
||||
|
|
||||
|
### 4.2 数据库设计 |
||||
|
|
||||
|
#### 4.2.1 核心数据表设计 |
||||
|
**校区表 (campus)** |
||||
|
- id: 主键 |
||||
|
- name: 校区名称 |
||||
|
- code: 校区编码 |
||||
|
- status: 状态 |
||||
|
- created_at: 创建时间 |
||||
|
- updated_at: 更新时间 |
||||
|
|
||||
|
**业务数据表 (business_data)** |
||||
|
- id: 主键 |
||||
|
- campus_id: 校区ID |
||||
|
- year: 年份 |
||||
|
- month: 月份 |
||||
|
- metric_category: 指标分类 |
||||
|
- metric_name: 指标名称 |
||||
|
- metric_value: 指标值 |
||||
|
- created_at: 创建时间 |
||||
|
- updated_at: 更新时间 |
||||
|
|
||||
|
**计算指标表 (calculated_metrics)** |
||||
|
- id: 主键 |
||||
|
- campus_id: 校区ID |
||||
|
- year: 年份 |
||||
|
- month: 月份 |
||||
|
- conversion_rate: 成交率 |
||||
|
- renewal_rate: 续费率 |
||||
|
- avg_price: 均单价 |
||||
|
- task_completion_rate: 任务完成率 |
||||
|
- cost_completion_rate: 成本完成率 |
||||
|
|
||||
|
### 4.3 关键算法 |
||||
|
|
||||
|
#### 4.3.1 转化率计算 |
||||
|
- 成交率 = (一访成交 + 二访成交) / (一访到访 + 二访到访) × 100% |
||||
|
- 续费率 = 续费人数(本月) / 月应续费人数 × 100% |
||||
|
- 均单价 = 成交总金额 / 成交总单数 |
||||
|
|
||||
|
#### 4.3.2 数据聚合规则 |
||||
|
- 年度数据 = 各月度数据累加或平均 |
||||
|
- 校区对比 = 同期数据横向对比 |
||||
|
- 趋势分析 = 时间序列数据分析 |
||||
|
|
||||
|
## 5. 界面设计要求 |
||||
|
|
||||
|
### 5.1 主要页面 |
||||
|
1. **数据录入页面**: 表格式录入,支持快速录入和批量导入 |
||||
|
2. **综合仪表盘**: 核心指标卡片式展示,图表可视化 |
||||
|
3. **校区对比页面**: 多校区数据对比分析 |
||||
|
4. **报表管理页面**: 报表生成、导出、历史查看 |
||||
|
5. **系统管理页面**: 用户管理、权限管理、校区管理 |
||||
|
|
||||
|
### 5.2 用户体验要求 |
||||
|
- **响应式设计**: 支持PC端和平板端使用 |
||||
|
- **操作便捷**: 减少点击次数,支持键盘快捷操作 |
||||
|
- **数据可视化**: 图表展示直观清晰 |
||||
|
- **加载性能**: 页面加载时间 < 3秒 |
||||
|
|
||||
|
## 6. 权限与安全 |
||||
|
|
||||
|
### 6.1 角色权限设计 |
||||
|
- **超级管理员**: 全系统权限,可管理所有校区数据 |
||||
|
- **校区管理员**: 只能管理本校区数据,查看对比报表 |
||||
|
- **数据录入员**: 只能录入本校区数据,无查看权限 |
||||
|
- **数据分析师**: 只读权限,可查看所有数据和报表 |
||||
|
|
||||
|
### 6.2 数据安全要求 |
||||
|
- **数据加密**: 敏感数据加密存储 |
||||
|
- **操作日志**: 记录所有数据变更操作 |
||||
|
- **数据备份**: 每日自动备份,保留30天 |
||||
|
- **访问控制**: IP白名单,异常登录预警 |
||||
|
|
||||
|
## 7. 性能要求 |
||||
|
|
||||
|
### 7.1 响应时间要求 |
||||
|
- 页面加载时间 ≤ 3秒 |
||||
|
- 数据查询响应时间 ≤ 2秒 |
||||
|
- 报表生成时间 ≤ 5秒 |
||||
|
- 数据导出时间 ≤ 10秒 |
||||
|
|
||||
|
### 7.2 并发性能要求 |
||||
|
- 支持50个并发用户同时使用 |
||||
|
- 数据库连接池优化 |
||||
|
- 缓存策略优化关键查询 |
||||
|
|
||||
|
## 8. 验收标准 |
||||
|
|
||||
|
### 8.1 功能验收 |
||||
|
- [ ] 各校区数据录入功能正常 |
||||
|
- [ ] 数据统计计算准确无误 |
||||
|
- [ ] 报表展示美观清晰 |
||||
|
- [ ] 权限控制有效 |
||||
|
- [ ] 数据导入导出功能正常 |
||||
|
|
||||
|
### 8.2 性能验收 |
||||
|
- [ ] 响应时间满足要求 |
||||
|
- [ ] 并发性能达标 |
||||
|
- [ ] 数据安全可靠 |
||||
|
- [ ] 系统稳定性良好 |
||||
|
|
||||
|
## 9. 风险评估与应对 |
||||
|
|
||||
|
### 9.1 技术风险 |
||||
|
**风险**: Excel数据迁移可能存在数据丢失 |
||||
|
**应对**: 制定详细的数据迁移方案,多次测试验证 |
||||
|
|
||||
|
**风险**: 多校区数据同步可能存在延迟 |
||||
|
**应对**: 采用事务机制保证数据一致性 |
||||
|
|
||||
|
### 9.2 业务风险 |
||||
|
**风险**: 用户可能不适应新系统操作 |
||||
|
**应对**: 提供详细培训和操作手册 |
||||
|
|
||||
|
**风险**: 数据录入错误可能影响决策 |
||||
|
**应对**: 增加数据校验规则和审核流程 |
||||
|
|
||||
|
## 10. 实施计划 |
||||
|
|
||||
|
### 10.1 开发阶段 (6周) |
||||
|
- **第1周**: 需求确认、系统设计 |
||||
|
- **第2周**: 数据库设计、后端架构搭建 |
||||
|
- **第3-4周**: 后端API开发、前端页面开发 |
||||
|
- **第5周**: 功能集成、测试 |
||||
|
- **第6周**: 用户培训、上线部署 |
||||
|
|
||||
|
### 10.2 验收测试 |
||||
|
- 单元测试覆盖率 > 80% |
||||
|
- 集成测试通过率 100% |
||||
|
- 用户验收测试通过 |
||||
|
- 性能测试达标 |
||||
|
|
||||
|
## 11. 后续优化建议 |
||||
|
|
||||
|
### 11.1 功能扩展 |
||||
|
- **移动端应用**: 开发移动端APP,支持移动办公 |
||||
|
- **智能分析**: 引入AI算法,提供数据预测和建议 |
||||
|
- **第三方集成**: 与财务系统、CRM系统集成 |
||||
|
- **自动化报表**: 定时自动生成和发送报表 |
||||
|
|
||||
|
### 11.2 技术优化 |
||||
|
- **微服务架构**: 系统模块化,提高可维护性 |
||||
|
- **大数据分析**: 引入大数据技术,支持更复杂的分析 |
||||
|
- **实时数据流**: 实现数据实时同步和分析 |
||||
|
- **云原生部署**: 采用云原生技术,提高系统弹性 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
**备注**: |
||||
|
1. 本需求文档基于Excel数据结构分析生成,具体业务规则需与业务方进一步确认 |
||||
|
2. 技术方案需根据实际技术栈和团队能力调整 |
||||
|
3. 项目实施过程中需重点关注数据准确性和业务连续性 |
||||
|
|
||||
|
## 附录:业务指标详细说明 |
||||
|
|
||||
|
### A.1 资源管理指标 |
||||
|
- **线下资源**: 通过线下渠道获得的潜在客户数量 |
||||
|
- **线上资源**: 通过线上渠道获得的潜在客户数量 |
||||
|
- **邀约数**: 成功邀约到访的客户数量 |
||||
|
- **一访到访**: 首次到访的客户数量 |
||||
|
- **一访成交**: 首次到访即成交的客户数量 |
||||
|
- **二访到访**: 二次到访的客户数量 |
||||
|
- **二访成交**: 二次到访成交的客户数量 |
||||
|
- **成交率**: 总成交数/总到访数的比例 |
||||
|
|
||||
|
### A.2 销售业绩指标 |
||||
|
- **成交总课时**: 所有成交订单的课时总和 |
||||
|
- **成交总金额**: 所有成交订单的金额总和 |
||||
|
- **均单价**: 平均每单成交金额 |
||||
|
- **(净)成交总单数**: 扣除退费后的实际成交单数 |
||||
|
- **(净)成交总课时**: 扣除退费后的实际成交课时 |
||||
|
- **(净)成交总金额**: 扣除退费后的实际成交金额 |
||||
|
|
||||
|
### A.3 续费管理指标 |
||||
|
- **月应续费人数**: 当月应该续费的学员数量 |
||||
|
- **续费人数(本月)**: 当月实际续费的学员数量 |
||||
|
- **续费人数(次月)**: 延期到次月续费的学员数量 |
||||
|
- **老卡续费人数**: 使用老卡续费的学员数量 |
||||
|
- **总续费数**: 所有续费的总数量 |
||||
|
- **月卡续费率**: 当月续费率 |
||||
|
- **续费总课时**: 续费的总课时数 |
||||
|
- **续费总金额**: 续费的总金额 |
||||
|
|
||||
|
### A.4 学员异动指标 |
||||
|
- **转出人数**: 转出到其他校区的学员数量 |
||||
|
- **转出金额**: 转出学员对应的金额 |
||||
|
- **转入人数**: 从其他校区转入的学员数量 |
||||
|
- **转入金额**: 转入学员对应的金额 |
||||
|
- **退费人数**: 申请退费的学员数量 |
||||
|
- **退费金额**: 退费的总金额 |
||||
|
|
||||
|
### A.5 经营分析指标 |
||||
|
- **任务(月)**: 月度业绩任务目标 |
||||
|
- **成本(月)**: 月度运营成本 |
||||
|
- **(净)任务完成率**: 净业绩/任务目标的完成比例 |
||||
|
- **(净)成本完成率**: 实际成本/预算成本的比例 |
||||
@ -0,0 +1,251 @@ |
|||||
|
# 教育培训机构数据统计分析系统需求文档 |
||||
|
|
||||
|
## 文档信息 |
||||
|
- **生成时间**: 2025-07-28 23:03:52 |
||||
|
- **数据来源**: 各校区月&年转化汇总表.xlsx |
||||
|
- **文档版本**: v1.0 |
||||
|
|
||||
|
## 1. 项目背景 |
||||
|
|
||||
|
基于现有Excel报表数据分析,该教育培训机构需要一个数字化的数据统计分析系统,用于替代手工Excel报表,实现数据的自动化统计、分析和可视化展示。 |
||||
|
|
||||
|
### 1.1 现状分析 |
||||
|
- 当前使用Excel手工统计各校区数据 |
||||
|
- 包含13个工作表:1月, 2月, 3月, 4月, 5月, 6月, 7月, 8月, 9月, 10月等 |
||||
|
- 数据维度复杂,包含时间、校区、业务指标等多个维度 |
||||
|
|
||||
|
## 2. 业务需求分析 |
||||
|
|
||||
|
### 2.1 数据维度分析 |
||||
|
|
||||
|
#### 时间维度 |
||||
|
- 1月 |
||||
|
- 2月 |
||||
|
- 3月 |
||||
|
- 4月 |
||||
|
- 5月 |
||||
|
- 6月 |
||||
|
- 7月 |
||||
|
- 8月 |
||||
|
- 9月 |
||||
|
- 10月 |
||||
|
- 11月 |
||||
|
- 12月 |
||||
|
- 年度合计 |
||||
|
|
||||
|
#### 业务分类 |
||||
|
- 资源 |
||||
|
|
||||
|
#### 核心指标 |
||||
|
- 线下资源 |
||||
|
- 邀约数 |
||||
|
- 一访到访 |
||||
|
- 一访成交 |
||||
|
- 二访到访 |
||||
|
- 二访成交 |
||||
|
- 成交率 |
||||
|
- 线上资源 |
||||
|
|
||||
|
### 2.2 核心业务流程 |
||||
|
|
||||
|
#### 2.2.1 资源管理流程 |
||||
|
1. **线下资源管理** |
||||
|
- 邀约数统计 |
||||
|
- 一访到访率跟踪 |
||||
|
- 一访成交率分析 |
||||
|
- 二访到访率跟踪 |
||||
|
- 二访成交率分析 |
||||
|
|
||||
|
2. **线上资源管理** |
||||
|
- 线上资源数量统计 |
||||
|
- 线上转化率分析 |
||||
|
|
||||
|
#### 2.2.2 销售转化流程 |
||||
|
1. **成交管理** |
||||
|
- 成交总课时统计 |
||||
|
- 成交总金额统计 |
||||
|
- 均单价计算 |
||||
|
- 成交率分析 |
||||
|
|
||||
|
2. **转介绍管理** |
||||
|
- 转介绍资源数统计 |
||||
|
- 转介绍转化率分析 |
||||
|
|
||||
|
#### 2.2.3 续费管理流程 |
||||
|
1. **续费统计** |
||||
|
- 月应续费人数 |
||||
|
- 续费人数(本月/次月) |
||||
|
- 老卡续费人数 |
||||
|
- 总续费数 |
||||
|
|
||||
|
2. **续费分析** |
||||
|
- 月卡续费率 |
||||
|
- 续费总课时 |
||||
|
- 续费总金额 |
||||
|
|
||||
|
## 3. 功能需求 |
||||
|
|
||||
|
### 3.1 数据录入模块 |
||||
|
- **手工录入**: 支持按日、周、月维度录入数据 |
||||
|
- **批量导入**: 支持Excel文件批量导入 |
||||
|
- **数据校验**: 自动校验数据完整性和合理性 |
||||
|
|
||||
|
### 3.2 数据统计模块 |
||||
|
- **实时统计**: 自动计算各项指标 |
||||
|
- **多维度统计**: 支持按时间、校区、业务类型等维度统计 |
||||
|
- **自动汇总**: 自动生成周报、月报、年报 |
||||
|
|
||||
|
### 3.3 数据分析模块 |
||||
|
- **趋势分析**: 各指标的时间趋势分析 |
||||
|
- **对比分析**: 校区间、时期间对比分析 |
||||
|
- **转化漏斗**: 从邀约到成交的转化漏斗分析 |
||||
|
|
||||
|
### 3.4 报表展示模块 |
||||
|
- **仪表盘**: 核心指标实时展示 |
||||
|
- **图表展示**: 柱状图、折线图、饼图等多种图表 |
||||
|
- **报表导出**: 支持PDF、Excel格式导出 |
||||
|
|
||||
|
## 4. 技术实现方案 |
||||
|
|
||||
|
### 4.1 系统架构 |
||||
|
- **前端**: Vue.js + Element UI |
||||
|
- **后端**: Node.js/Python + Express/FastAPI |
||||
|
- **数据库**: MySQL/PostgreSQL |
||||
|
- **缓存**: Redis |
||||
|
|
||||
|
### 4.2 数据模型设计 |
||||
|
|
||||
|
#### 4.2.1 核心实体 |
||||
|
```sql |
||||
|
-- 校区表 |
||||
|
CREATE TABLE campus ( |
||||
|
id INT PRIMARY KEY, |
||||
|
name VARCHAR(100), |
||||
|
code VARCHAR(50), |
||||
|
status TINYINT |
||||
|
); |
||||
|
|
||||
|
-- 统计数据表 |
||||
|
CREATE TABLE statistics_data ( |
||||
|
id INT PRIMARY KEY, |
||||
|
campus_id INT, |
||||
|
date DATE, |
||||
|
period_type ENUM('daily', 'weekly', 'monthly', 'yearly'), |
||||
|
metric_type VARCHAR(50), |
||||
|
metric_value DECIMAL(10,2), |
||||
|
created_at TIMESTAMP |
||||
|
); |
||||
|
``` |
||||
|
|
||||
|
### 4.3 关键算法 |
||||
|
|
||||
|
#### 4.3.1 转化率计算 |
||||
|
``` |
||||
|
成交率 = 成交人数 / 到访人数 * 100% |
||||
|
续费率 = 续费人数 / 应续费人数 * 100% |
||||
|
``` |
||||
|
|
||||
|
#### 4.3.2 数据聚合规则 |
||||
|
- 日数据 → 周数据:按自然周聚合 |
||||
|
- 周数据 → 月数据:按自然月聚合 |
||||
|
- 月数据 → 年数据:按自然年聚合 |
||||
|
|
||||
|
## 5. 界面设计要求 |
||||
|
|
||||
|
### 5.1 主要页面 |
||||
|
1. **数据录入页面**: 表格式录入界面,支持快速录入 |
||||
|
2. **统计分析页面**: 多维度查询和分析界面 |
||||
|
3. **报表展示页面**: 图表和表格混合展示 |
||||
|
4. **系统管理页面**: 用户、权限、校区管理 |
||||
|
|
||||
|
### 5.2 用户体验要求 |
||||
|
- 响应式设计,支持PC和移动端 |
||||
|
- 操作简单直观,减少学习成本 |
||||
|
- 数据加载快速,支持分页和懒加载 |
||||
|
|
||||
|
## 6. 数据安全要求 |
||||
|
|
||||
|
### 6.1 权限控制 |
||||
|
- 角色权限管理:超级管理员、校区管理员、数据录入员 |
||||
|
- 数据权限隔离:校区间数据隔离 |
||||
|
- 操作日志记录:记录所有数据变更操作 |
||||
|
|
||||
|
### 6.2 数据备份 |
||||
|
- 定期数据备份 |
||||
|
- 数据恢复机制 |
||||
|
- 异地备份策略 |
||||
|
|
||||
|
## 7. 性能要求 |
||||
|
|
||||
|
### 7.1 响应时间 |
||||
|
- 页面加载时间 < 3秒 |
||||
|
- 数据查询响应时间 < 2秒 |
||||
|
- 报表生成时间 < 5秒 |
||||
|
|
||||
|
### 7.2 并发要求 |
||||
|
- 支持100个并发用户 |
||||
|
- 数据库连接池优化 |
||||
|
- 缓存策略优化 |
||||
|
|
||||
|
## 8. 验收标准 |
||||
|
|
||||
|
### 8.1 功能验收 |
||||
|
- [ ] 数据录入功能完整可用 |
||||
|
- [ ] 统计计算准确无误 |
||||
|
- [ ] 报表展示美观清晰 |
||||
|
- [ ] 权限控制有效 |
||||
|
|
||||
|
### 8.2 性能验收 |
||||
|
- [ ] 响应时间满足要求 |
||||
|
- [ ] 并发性能达标 |
||||
|
- [ ] 数据安全可靠 |
||||
|
|
||||
|
## 9. 风险评估 |
||||
|
|
||||
|
### 9.1 技术风险 |
||||
|
- **数据迁移风险**: Excel数据迁移可能存在数据丢失 |
||||
|
- **性能风险**: 大量历史数据可能影响查询性能 |
||||
|
- **兼容性风险**: 不同浏览器兼容性问题 |
||||
|
|
||||
|
### 9.2 业务风险 |
||||
|
- **用户接受度**: 用户可能不适应新系统 |
||||
|
- **数据准确性**: 自动计算可能与手工计算存在差异 |
||||
|
- **业务连续性**: 系统切换期间业务连续性保障 |
||||
|
|
||||
|
## 10. 实施计划 |
||||
|
|
||||
|
### 10.1 开发阶段 |
||||
|
1. **需求确认** (1周) |
||||
|
2. **系统设计** (1周) |
||||
|
3. **数据库设计** (3天) |
||||
|
4. **后端开发** (2周) |
||||
|
5. **前端开发** (2周) |
||||
|
6. **集成测试** (1周) |
||||
|
7. **用户培训** (3天) |
||||
|
8. **上线部署** (2天) |
||||
|
|
||||
|
### 10.2 验收测试 |
||||
|
- 单元测试覆盖率 > 80% |
||||
|
- 集成测试通过率 100% |
||||
|
- 用户验收测试通过 |
||||
|
|
||||
|
## 11. 后续优化建议 |
||||
|
|
||||
|
### 11.1 功能扩展 |
||||
|
- 移动端APP开发 |
||||
|
- 数据预测分析功能 |
||||
|
- 智能报表推荐 |
||||
|
- 第三方系统集成 |
||||
|
|
||||
|
### 11.2 技术优化 |
||||
|
- 微服务架构改造 |
||||
|
- 大数据分析平台集成 |
||||
|
- AI智能分析功能 |
||||
|
- 实时数据流处理 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
**注意事项**: |
||||
|
1. 本需求文档基于Excel数据结构分析生成,具体业务规则需要与业务方进一步确认 |
||||
|
2. 技术实现方案需要根据实际技术栈和团队能力进行调整 |
||||
|
3. 项目实施过程中需要密切关注数据准确性和业务连续性 |
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,406 @@ |
|||||
|
# PHP后端开发规划 |
||||
|
|
||||
|
## 已完成接口 |
||||
|
|
||||
|
### 1. 学员课程信息接口 |
||||
|
|
||||
|
**描述**: 获取学员课程信息,用于前端CourseInfoCard组件使用 |
||||
|
|
||||
|
**接口**: `GET /api/getStudentCourseInfo` |
||||
|
|
||||
|
**请求参数**: |
||||
|
| 参数 | 类型 | 必填 | 说明 | |
||||
|
|--------|------|------|------| |
||||
|
| resource_id | int | 是 | 资源ID | |
||||
|
| member_id | string | 否 | 会员ID | |
||||
|
| student_id | int | 否 | 学员ID(优先级高于member_id) | |
||||
|
|
||||
|
**响应示例**: |
||||
|
```json |
||||
|
{ |
||||
|
"code": 1, |
||||
|
"message": "success", |
||||
|
"data": [ |
||||
|
{ |
||||
|
"id": 1, |
||||
|
"course_name": "篮球课", |
||||
|
"total_count": 24, |
||||
|
"used_count": 8, |
||||
|
"formal_hours": 20, |
||||
|
"gift_hours": 4, |
||||
|
"used_formal_hours": 6, |
||||
|
"used_gift_hours": 2, |
||||
|
"leave_count": 1, |
||||
|
"start_date": "2024-01-01", |
||||
|
"end_date": "2024-12-31", |
||||
|
"expiry_date": "2024-06-30", |
||||
|
"status": "active", |
||||
|
"course_type": "常规课", |
||||
|
"teacher_name": "张老师", |
||||
|
"course_price": 2880.00, |
||||
|
"class_duration": 90, |
||||
|
"create_time": "2024-01-01 10:00:00", |
||||
|
"remark": "备注信息" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
**涉及数据表**: |
||||
|
- `school_customer_resources` - 资源表 |
||||
|
- `school_student` - 学员表 |
||||
|
- `school_course` - 课程表 |
||||
|
- `school_student_course` - 学员课程关联表 |
||||
|
- `school_course_schedule` - 课程排期表 |
||||
|
- `school_attendance` - 考勤表 |
||||
|
|
||||
|
**SQL查询逻辑参考**: |
||||
|
```sql |
||||
|
SELECT |
||||
|
sc.id, |
||||
|
sc.course_name, |
||||
|
ssc.total_count, |
||||
|
ssc.used_count, |
||||
|
ssc.formal_hours, |
||||
|
ssc.gift_hours, |
||||
|
ssc.used_formal_hours, |
||||
|
ssc.used_gift_hours, |
||||
|
ssc.leave_count, |
||||
|
ssc.start_date, |
||||
|
ssc.end_date, |
||||
|
ssc.expiry_date, |
||||
|
ssc.status, |
||||
|
sc.course_type, |
||||
|
sp.name as teacher_name, |
||||
|
ssc.course_price, |
||||
|
sc.class_duration, |
||||
|
ssc.create_time, |
||||
|
ssc.remark |
||||
|
FROM school_student_course ssc |
||||
|
LEFT JOIN school_course sc ON ssc.course_id = sc.id |
||||
|
LEFT JOIN school_personnel sp ON sc.teacher_id = sp.id |
||||
|
WHERE ssc.student_id = ? |
||||
|
AND ssc.resource_id = ? |
||||
|
[AND ssc.member_id = ?] |
||||
|
ORDER BY ssc.create_time DESC |
||||
|
``` |
||||
|
|
||||
|
**状态说明**: |
||||
|
- `active`: 正常使用 |
||||
|
- `completed`: 课程完结 |
||||
|
- `expired`: 已过期 |
||||
|
- `pending`: 待激活 |
||||
|
|
||||
|
**业务逻辑**: |
||||
|
1. 优先使用student_id查询,其次使用member_id |
||||
|
2. 计算剩余课时:remaining_count = total_count - used_count |
||||
|
3. 检查课程是否过期,基于expiry_date |
||||
|
4. 返回完整的课程信息及使用情况 |
||||
|
5. 支持多个课程返回 |
||||
|
|
||||
|
**优化建议**: |
||||
|
- 使用Redis缓存常用查询结果 |
||||
|
- 对学员课程查询添加索引优化 |
||||
|
- 考虑分页处理大量数据 |
||||
|
|
||||
|
## 已新增接口 |
||||
|
|
||||
|
### 2. 服务列表接口 |
||||
|
|
||||
|
**描述**: 获取学员服务记录列表 |
||||
|
|
||||
|
**接口**: `GET /api/xy/service/list` |
||||
|
|
||||
|
**请求参数**: |
||||
|
| 参数 | 类型 | 必填 | 说明 | |
||||
|
|--------|------|------|------| |
||||
|
| student_id | int | 是 | 学员ID | |
||||
|
|
||||
|
**响应示例**: |
||||
|
```json |
||||
|
{ |
||||
|
"code": 1, |
||||
|
"message": "操作成功", |
||||
|
"data": [ |
||||
|
{ |
||||
|
"id": 1, |
||||
|
"service_name": "测试服务", |
||||
|
"preview_image_url": "图片URL", |
||||
|
"description": "服务描述", |
||||
|
"service_type": "服务类型", |
||||
|
"status": "active", |
||||
|
"logs": [ |
||||
|
{ |
||||
|
"id": 1, |
||||
|
"status": 1, |
||||
|
"service_content": "服务内容", |
||||
|
"service_staff": "教练姓名", |
||||
|
"service_time": "服务时间", |
||||
|
"duration": "持续时间", |
||||
|
"customer_feedback": "客户反馈", |
||||
|
"service_rating": 5, |
||||
|
"remark": "备注", |
||||
|
"course_name": "课程名称", |
||||
|
"updated_at": "更新时间" |
||||
|
} |
||||
|
], |
||||
|
"total_count": 3, |
||||
|
"completed_count": 3 |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 3. 体测记录接口(增删改查) |
||||
|
|
||||
|
**描述**: 体测记录的完整CRUD操作 |
||||
|
|
||||
|
**接口列表**: |
||||
|
- `GET /api/xy/physicalTest` - 获取体测记录列表 |
||||
|
- `GET /api/xy/physicalTest/info` - 获取体测记录详情 |
||||
|
- `POST /api/xy/physicalTest/add` - 添加体测记录 |
||||
|
- `POST /api/xy/physicalTest/edit` - 编辑体测记录 |
||||
|
- `POST /api/xy/physicalTest/delete` - 删除体测记录 |
||||
|
|
||||
|
**数据表结构**: |
||||
|
```sql |
||||
|
CREATE TABLE `school_physical_test` ( |
||||
|
`id` int NOT NULL AUTO_INCREMENT COMMENT '体测编号', |
||||
|
`resource_id` int NOT NULL COMMENT '资源ID', |
||||
|
`student_id` int DEFAULT NULL COMMENT '学员ID', |
||||
|
`age` int NOT NULL DEFAULT '0' COMMENT '学员年龄', |
||||
|
`height` decimal(5,2) NOT NULL COMMENT '身高', |
||||
|
`weight` decimal(5,2) NOT NULL COMMENT '体重', |
||||
|
`coach_id` int DEFAULT NULL COMMENT '教练ID', |
||||
|
`seated_forward_bend` decimal(5,2) DEFAULT NULL COMMENT '坐位体前屈', |
||||
|
`sit_ups` decimal(5,2) DEFAULT NULL COMMENT '仰卧起坐', |
||||
|
`push_ups` decimal(5,2) DEFAULT NULL COMMENT '俯卧撑', |
||||
|
`flamingo_balance` decimal(5,2) DEFAULT NULL COMMENT '单脚站立', |
||||
|
`thirty_sec_jump` decimal(5,2) DEFAULT NULL COMMENT '30秒跳绳', |
||||
|
`standing_long_jump` decimal(5,2) DEFAULT NULL COMMENT '立定跳远', |
||||
|
`agility_run` decimal(5,2) DEFAULT NULL COMMENT '敏捷跑', |
||||
|
`balance_beam` decimal(5,2) DEFAULT NULL COMMENT '平衡木', |
||||
|
`tennis_throw` decimal(5,2) DEFAULT NULL COMMENT '网球掷远', |
||||
|
`ten_meter_shuttle_run` decimal(5,2) DEFAULT NULL COMMENT '10米折返跑', |
||||
|
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', |
||||
|
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', |
||||
|
`physical_test_report` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '体测报告附件(多文件)', |
||||
|
PRIMARY KEY (`id`) USING BTREE |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
### 4. 学习计划接口(基于自定义表单) |
||||
|
|
||||
|
**描述**: 基于diy_form自定义表单的学习计划管理系统 |
||||
|
|
||||
|
**接口列表**: |
||||
|
- `GET /api/xy/studyPlan` - 获取学习计划列表 |
||||
|
- `GET /api/xy/studyPlan/info` - 获取学习计划详情 |
||||
|
- `POST /api/xy/studyPlan/add` - 添加学习计划 |
||||
|
- `POST /api/xy/studyPlan/edit` - 编辑学习计划 |
||||
|
- `POST /api/xy/studyPlan/delete` - 删除学习计划 |
||||
|
- `POST /api/xy/studyPlan/updateProgress` - 更新学习计划进度 |
||||
|
|
||||
|
**数据表**: 使用diy_form自定义表单系统 |
||||
|
- `school_diy_form` - 表单定义 (type='study_plan') |
||||
|
- `school_diy_form_fields` - 字段定义 |
||||
|
- `school_diy_form_records` - 记录数据 |
||||
|
|
||||
|
**响应数据结构**: |
||||
|
```json |
||||
|
{ |
||||
|
"code": 1, |
||||
|
"message": "操作成功", |
||||
|
"data": [ |
||||
|
{ |
||||
|
"id": 1, |
||||
|
"student_id": 1, |
||||
|
"plan_name": "基础体能训练计划", |
||||
|
"plan_content": "针对学员的基础体能进行系统性训练", |
||||
|
"plan_type": "体能训练", |
||||
|
"status": "active", |
||||
|
"progress": 65, |
||||
|
"start_date": "2024-01-15", |
||||
|
"end_date": "2024-03-15", |
||||
|
"target_goals": "提升学员整体体能水平", |
||||
|
"learning_materials": "体能训练器材、训练计划表", |
||||
|
"evaluation_criteria": "体能测试成绩、训练完成度", |
||||
|
"remark": "备注信息", |
||||
|
"create_time": "2024-01-10 14:30:00", |
||||
|
"update_time": "2024-01-10 14:30:00" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 前端集成状态 |
||||
|
|
||||
|
### ✅ 已完成联调的接口: |
||||
|
1. **课程信息** - `getStudentCourseInfo` (完整联调,Mock与API一致) |
||||
|
2. **服务列表** - `getStudentServiceList` (API正常工作) |
||||
|
3. **体测记录** - `xy_physicalTest` (列表API + 增删改查API完整) |
||||
|
4. **学习计划** - `getStudyPlanList` (基于自定义表单的完整CRUD) |
||||
|
|
||||
|
### 🔧 技术实现特点: |
||||
|
- **统一响应格式**: 所有接口使用统一的 `{code: 1, data: [], msg: "操作成功"}` 格式 |
||||
|
- **JWT认证**: 所有接口通过token头进行身份验证 |
||||
|
- **参数验证**: 完整的参数校验和错误处理 |
||||
|
- **数据库事务**: 确保数据一致性 |
||||
|
- **错误处理**: 完善的异常处理和日志记录 |
||||
|
|
||||
|
### 5. 个人资料接口(员工信息管理) |
||||
|
|
||||
|
**描述**: 员工个人资料的查看和编辑功能,涉及基础信息和详细信息两个数据表 |
||||
|
|
||||
|
**接口列表**: |
||||
|
- `GET /api/getPersonnelInfo` - 获取员工基础信息 |
||||
|
- `POST /api/updatePersonnelInfo` - 更新员工基础信息 |
||||
|
- `GET /api/getPersonnelDetailInfo` - 获取员工详细信息 |
||||
|
- `POST /api/updatePersonnelDetailInfo` - 更新员工详细信息 |
||||
|
|
||||
|
#### 基础信息接口 |
||||
|
|
||||
|
**接口**: `GET /api/getPersonnelInfo` |
||||
|
|
||||
|
**请求参数**: |
||||
|
| 参数 | 类型 | 必填 | 说明 | |
||||
|
|--------|------|------|------| |
||||
|
| id | int | 否 | 员工ID,不传则获取当前登录用户信息 | |
||||
|
|
||||
|
**响应示例**: |
||||
|
```json |
||||
|
{ |
||||
|
"code": 1, |
||||
|
"message": "操作成功", |
||||
|
"data": { |
||||
|
"id": 1, |
||||
|
"name": "张三", |
||||
|
"head_img": "/uploads/avatar/20240101/avatar.jpg", |
||||
|
"gender": 1, |
||||
|
"birthday": "1990-05-15", |
||||
|
"phone": "13800138000", |
||||
|
"email": "zhangsan@example.com", |
||||
|
"wx": "zhangsan_wx", |
||||
|
"address": "北京市朝阳区XXX小区", |
||||
|
"native_place": "山东济南", |
||||
|
"education": "本科", |
||||
|
"profile": "个人简介内容", |
||||
|
"emergency_contact_phone": "13900139000", |
||||
|
"id_card_front": "/uploads/idcard/front.jpg", |
||||
|
"id_card_back": "/uploads/idcard/back.jpg", |
||||
|
"employee_number": "EMP001", |
||||
|
"status": 2, |
||||
|
"account_type": "teacher", |
||||
|
"join_time": "2024-01-01 09:00:00", |
||||
|
"create_time": "2024-01-01 09:00:00", |
||||
|
"update_time": "2024-01-01 09:00:00" |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
**接口**: `POST /api/updatePersonnelInfo` |
||||
|
|
||||
|
**请求参数**: |
||||
|
```json |
||||
|
{ |
||||
|
"id": 1, |
||||
|
"name": "张三", |
||||
|
"head_img": "/uploads/avatar/20240101/avatar.jpg", |
||||
|
"gender": 1, |
||||
|
"birthday": "1990-05-15", |
||||
|
"phone": "13800138000", |
||||
|
"email": "zhangsan@example.com", |
||||
|
"wx": "zhangsan_wx", |
||||
|
"address": "北京市朝阳区XXX小区", |
||||
|
"native_place": "山东济南", |
||||
|
"education": "本科", |
||||
|
"profile": "个人简介内容", |
||||
|
"emergency_contact_phone": "13900139000", |
||||
|
"id_card_front": "/uploads/idcard/front.jpg", |
||||
|
"id_card_back": "/uploads/idcard/back.jpg" |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### 详细信息接口 |
||||
|
|
||||
|
**接口**: `GET /api/getPersonnelDetailInfo` |
||||
|
|
||||
|
**请求参数**: |
||||
|
| 参数 | 类型 | 必填 | 说明 | |
||||
|
|--------|------|------|------| |
||||
|
| person_id | int | 是 | 员工ID | |
||||
|
|
||||
|
**响应示例**: |
||||
|
```json |
||||
|
{ |
||||
|
"code": 1, |
||||
|
"message": "操作成功", |
||||
|
"data": { |
||||
|
"id": 1, |
||||
|
"person_id": 1, |
||||
|
"name": "张三花名", |
||||
|
"store": "朝阳校区", |
||||
|
"ethnicity": "汉族", |
||||
|
"age": 30, |
||||
|
"tenure": "3年2个月", |
||||
|
"regular_date": "2024-07-01", |
||||
|
"is_regular": "是", |
||||
|
"politics": "群众", |
||||
|
"university": "北京大学", |
||||
|
"major": "体育教育", |
||||
|
"graduation_date": "2015-06-30", |
||||
|
"household_place": "山东省济南市", |
||||
|
"household_type": "城镇户口", |
||||
|
"household_address": "山东省济南市历下区XXX街道", |
||||
|
"current_address": "北京市朝阳区XXX小区", |
||||
|
"emergency_contact": "张父", |
||||
|
"emergency_phone": "13900139000", |
||||
|
"marital_status": "已婚", |
||||
|
"bank_card": "6222021234567890123", |
||||
|
"bank_name": "中国银行北京分行", |
||||
|
"contract_expire": "2025-12-31", |
||||
|
"is_rehired": "否", |
||||
|
"remark": "备注信息", |
||||
|
"created_at": "2024-01-01 09:00:00", |
||||
|
"updated_at": "2024-01-01 09:00:00" |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
**接口**: `POST /api/updatePersonnelDetailInfo` |
||||
|
|
||||
|
**请求参数**: 与详细信息响应数据格式相同 |
||||
|
|
||||
|
#### 涉及数据表: |
||||
|
- `school_personnel` - 员工基础信息表 |
||||
|
- `school_personnel_info` - 员工详细信息表 |
||||
|
|
||||
|
#### 字段重复处理: |
||||
|
基于数据库分析,需要删除以下重复字段: |
||||
|
```sql |
||||
|
-- 删除重复字段 |
||||
|
ALTER TABLE school_personnel_info |
||||
|
DROP COLUMN birthday, |
||||
|
DROP COLUMN education, |
||||
|
DROP COLUMN native_place; |
||||
|
``` |
||||
|
|
||||
|
#### 业务逻辑: |
||||
|
1. **只读字段**: `employee_number`(员工编号)、`tenure`(司龄)只能查看不能修改 |
||||
|
2. **司龄计算**: 根据`join_time`自动计算并更新 |
||||
|
3. **图片上传**: 支持头像、身份证正反面图片上传 |
||||
|
4. **数据验证**: |
||||
|
- 手机号格式验证 |
||||
|
- 邮箱格式验证 |
||||
|
- 身份证号码验证 |
||||
|
5. **权限控制**: 员工只能编辑自己的信息 |
||||
|
|
||||
|
#### 前端页面: |
||||
|
- 路径: `/pages/common/profile/personal_info.vue` |
||||
|
- 功能: 查看、编辑员工个人资料 |
||||
|
- 特性: 响应式设计、表单验证、图片上传、分段展示 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
*最后更新:2024-12-29* |
||||
|
*备注:后续接口开发请及时更新此文档* |
||||
@ -0,0 +1,179 @@ |
|||||
|
<?php |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Niucloud-admin 企业快速开发的多应用管理平台 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | 官方网址:https://www.niucloud.com |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | niucloud团队 版权所有 开源版本可自由商用 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Author: Niucloud Team |
||||
|
// +---------------------------------------------------------------------- |
||||
|
|
||||
|
namespace app\api\controller\apiController; |
||||
|
|
||||
|
use app\Request; |
||||
|
use app\service\api\apiService\StudyPlanService; |
||||
|
use core\base\BaseApiService; |
||||
|
|
||||
|
/** |
||||
|
* 学习计划相关接口 |
||||
|
* Class StudyPlan |
||||
|
* @package app\api\controller\apiController |
||||
|
*/ |
||||
|
class StudyPlan extends BaseApiService |
||||
|
{ |
||||
|
/** |
||||
|
* 获取学习计划列表 |
||||
|
* @param Request $request |
||||
|
* @return \think\Response |
||||
|
*/ |
||||
|
public function index(Request $request) |
||||
|
{ |
||||
|
$student_id = $request->param('student_id', ''); |
||||
|
|
||||
|
if (empty($student_id)) { |
||||
|
return fail('学员ID不能为空'); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
$res = (new StudyPlanService())->getList($student_id); |
||||
|
if (!$res['code']) { |
||||
|
return fail($res['msg']); |
||||
|
} |
||||
|
|
||||
|
return success($res['data']); |
||||
|
} catch (\Exception $e) { |
||||
|
return fail('获取学习计划失败:' . $e->getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取学习计划详情 |
||||
|
* @param Request $request |
||||
|
* @return \think\Response |
||||
|
*/ |
||||
|
public function info(Request $request) |
||||
|
{ |
||||
|
$record_id = $request->param('record_id', ''); |
||||
|
|
||||
|
if (empty($record_id)) { |
||||
|
return fail('记录ID不能为空'); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
$res = (new StudyPlanService())->getInfo($record_id); |
||||
|
if (!$res['code']) { |
||||
|
return fail($res['msg']); |
||||
|
} |
||||
|
|
||||
|
return success($res['data']); |
||||
|
} catch (\Exception $e) { |
||||
|
return fail('获取学习计划详情失败:' . $e->getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 添加学习计划 |
||||
|
* @param Request $request |
||||
|
* @return \think\Response |
||||
|
*/ |
||||
|
public function add(Request $request) |
||||
|
{ |
||||
|
$data = $request->param(); |
||||
|
|
||||
|
// 验证必填字段 |
||||
|
$required_fields = ['student_id', 'plan_name', 'plan_content']; |
||||
|
foreach ($required_fields as $field) { |
||||
|
if (empty($data[$field])) { |
||||
|
return fail("缺少参数:{$field}"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
$res = (new StudyPlanService())->add($data); |
||||
|
if (!$res['code']) { |
||||
|
return fail($res['msg']); |
||||
|
} |
||||
|
return success($res['data'], '添加成功'); |
||||
|
} catch (\Exception $e) { |
||||
|
return fail('添加失败:' . $e->getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 编辑学习计划 |
||||
|
* @param Request $request |
||||
|
* @return \think\Response |
||||
|
*/ |
||||
|
public function edit(Request $request) |
||||
|
{ |
||||
|
$data = $request->param(); |
||||
|
|
||||
|
if (empty($data['record_id'])) { |
||||
|
return fail('缺少参数:record_id'); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
$res = (new StudyPlanService())->edit($data); |
||||
|
if (!$res['code']) { |
||||
|
return fail($res['msg']); |
||||
|
} |
||||
|
return success($res['data'], '修改成功'); |
||||
|
} catch (\Exception $e) { |
||||
|
return fail('修改失败:' . $e->getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除学习计划 |
||||
|
* @param Request $request |
||||
|
* @return \think\Response |
||||
|
*/ |
||||
|
public function delete(Request $request) |
||||
|
{ |
||||
|
$record_id = $request->param('record_id', ''); |
||||
|
|
||||
|
if (empty($record_id)) { |
||||
|
return fail('缺少参数:record_id'); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
$res = (new StudyPlanService())->delete($record_id); |
||||
|
if (!$res['code']) { |
||||
|
return fail($res['msg']); |
||||
|
} |
||||
|
return success([], '删除成功'); |
||||
|
} catch (\Exception $e) { |
||||
|
return fail('删除失败:' . $e->getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新学习计划进度 |
||||
|
* @param Request $request |
||||
|
* @return \think\Response |
||||
|
*/ |
||||
|
public function updateProgress(Request $request) |
||||
|
{ |
||||
|
$record_id = $request->param('record_id', ''); |
||||
|
$progress = $request->param('progress', 0); |
||||
|
|
||||
|
if (empty($record_id)) { |
||||
|
return fail('缺少参数:record_id'); |
||||
|
} |
||||
|
|
||||
|
if ($progress < 0 || $progress > 100) { |
||||
|
return fail('进度值必须在0-100之间'); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
$res = (new StudyPlanService())->updateProgress($record_id, $progress); |
||||
|
if (!$res['code']) { |
||||
|
return fail($res['msg']); |
||||
|
} |
||||
|
return success($res['data'], '更新成功'); |
||||
|
} catch (\Exception $e) { |
||||
|
return fail('更新失败:' . $e->getMessage()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,326 @@ |
|||||
|
<?php |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Niucloud-admin 企业快速开发的多应用管理平台 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | 官方网址:https://www.niucloud.com |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | niucloud团队 版权所有 开源版本可自由商用 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Author: Niucloud Team |
||||
|
// +---------------------------------------------------------------------- |
||||
|
|
||||
|
namespace app\service\api\apiService; |
||||
|
|
||||
|
use app\model\diy_form\DiyForm; |
||||
|
use app\model\diy_form\DiyFormRecords; |
||||
|
use core\base\BaseApiService; |
||||
|
|
||||
|
/** |
||||
|
* 学习计划API服务层 |
||||
|
* Class StudyPlanService |
||||
|
* @package app\service\api\apiService |
||||
|
*/ |
||||
|
class StudyPlanService extends BaseApiService |
||||
|
{ |
||||
|
public function __construct() |
||||
|
{ |
||||
|
parent::__construct(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取学习计划列表 |
||||
|
* @param int $studentId 学员ID |
||||
|
* @return array |
||||
|
*/ |
||||
|
public function getList($studentId) |
||||
|
{ |
||||
|
try { |
||||
|
// 获取学习计划表单ID |
||||
|
$studyPlanForm = DiyForm::where('type', 'study_plan')->find(); |
||||
|
if (!$studyPlanForm) { |
||||
|
return ['code' => 0, 'data' => [], 'msg' => '学习计划表单不存在']; |
||||
|
} |
||||
|
|
||||
|
// 查询学习计划记录 |
||||
|
$records = DiyFormRecords::where('form_id', $studyPlanForm->form_id) |
||||
|
->where('student_id', $studentId) |
||||
|
->order('create_time desc') |
||||
|
->select() |
||||
|
->toArray(); |
||||
|
|
||||
|
$result = []; |
||||
|
foreach ($records as $record) { |
||||
|
$value = is_string($record['value']) ? (json_decode($record['value'], true) ?: []) : (is_array($record['value']) ? $record['value'] : []); |
||||
|
|
||||
|
$result[] = [ |
||||
|
'id' => $record['record_id'], |
||||
|
'student_id' => $record['student_id'], |
||||
|
'plan_name' => $value['plan_name'] ?? '', |
||||
|
'plan_content' => $value['plan_content'] ?? '', |
||||
|
'plan_type' => $value['plan_type'] ?? '常规计划', |
||||
|
'status' => $value['status'] ?? 'active', |
||||
|
'progress' => (int)($value['progress'] ?? 0), |
||||
|
'start_date' => $value['start_date'] ?? '', |
||||
|
'end_date' => $value['end_date'] ?? '', |
||||
|
'target_goals' => $value['target_goals'] ?? '', |
||||
|
'learning_materials' => $value['learning_materials'] ?? '', |
||||
|
'evaluation_criteria' => $value['evaluation_criteria'] ?? '', |
||||
|
'remark' => $value['remark'] ?? '', |
||||
|
'create_time' => is_numeric($record['create_time']) ? date('Y-m-d H:i:s', $record['create_time']) : $record['create_time'], |
||||
|
'update_time' => is_numeric($record['create_time']) ? date('Y-m-d H:i:s', $record['create_time']) : $record['create_time'] |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
return ['code' => 1, 'data' => $result, 'msg' => '获取成功']; |
||||
|
|
||||
|
} catch (\Exception $e) { |
||||
|
return ['code' => 0, 'data' => [], 'msg' => '获取失败:' . $e->getMessage()]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取学习计划详情 |
||||
|
* @param int $recordId 记录ID |
||||
|
* @return array |
||||
|
*/ |
||||
|
public function getInfo($recordId) |
||||
|
{ |
||||
|
try { |
||||
|
$record = DiyFormRecords::find($recordId); |
||||
|
if (!$record) { |
||||
|
return ['code' => 0, 'data' => [], 'msg' => '记录不存在']; |
||||
|
} |
||||
|
|
||||
|
$value = is_string($record->value) ? (json_decode($record->value, true) ?: []) : (is_array($record->value) ? $record->value : []); |
||||
|
|
||||
|
$result = [ |
||||
|
'id' => $record->record_id, |
||||
|
'student_id' => $record->student_id, |
||||
|
'plan_name' => $value['plan_name'] ?? '', |
||||
|
'plan_content' => $value['plan_content'] ?? '', |
||||
|
'plan_type' => $value['plan_type'] ?? '常规计划', |
||||
|
'status' => $value['status'] ?? 'active', |
||||
|
'progress' => (int)($value['progress'] ?? 0), |
||||
|
'start_date' => $value['start_date'] ?? '', |
||||
|
'end_date' => $value['end_date'] ?? '', |
||||
|
'target_goals' => $value['target_goals'] ?? '', |
||||
|
'learning_materials' => $value['learning_materials'] ?? '', |
||||
|
'evaluation_criteria' => $value['evaluation_criteria'] ?? '', |
||||
|
'remark' => $value['remark'] ?? '', |
||||
|
'create_time' => is_numeric($record->create_time) ? date('Y-m-d H:i:s', $record->create_time) : $record->create_time, |
||||
|
'update_time' => is_numeric($record->create_time) ? date('Y-m-d H:i:s', $record->create_time) : $record->create_time |
||||
|
]; |
||||
|
|
||||
|
return ['code' => 1, 'data' => $result, 'msg' => '获取成功']; |
||||
|
|
||||
|
} catch (\Exception $e) { |
||||
|
return ['code' => 0, 'data' => [], 'msg' => '获取失败:' . $e->getMessage()]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 添加学习计划 |
||||
|
* @param array $data |
||||
|
* @return array |
||||
|
*/ |
||||
|
public function add(array $data) |
||||
|
{ |
||||
|
try { |
||||
|
// 获取学习计划表单ID |
||||
|
$studyPlanForm = DiyForm::where('type', 'study_plan')->find(); |
||||
|
if (!$studyPlanForm) { |
||||
|
return ['code' => 0, 'data' => [], 'msg' => '学习计划表单不存在']; |
||||
|
} |
||||
|
|
||||
|
// 构建表单数据 |
||||
|
$formValue = [ |
||||
|
'plan_name' => $data['plan_name'], |
||||
|
'plan_content' => $data['plan_content'], |
||||
|
'plan_type' => $data['plan_type'] ?? '常规计划', |
||||
|
'status' => $data['status'] ?? 'active', |
||||
|
'progress' => (int)($data['progress'] ?? 0), |
||||
|
'start_date' => $data['start_date'] ?? '', |
||||
|
'end_date' => $data['end_date'] ?? '', |
||||
|
'target_goals' => $data['target_goals'] ?? '', |
||||
|
'learning_materials' => $data['learning_materials'] ?? '', |
||||
|
'evaluation_criteria' => $data['evaluation_criteria'] ?? '', |
||||
|
'remark' => $data['remark'] ?? '' |
||||
|
]; |
||||
|
|
||||
|
// 创建记录 |
||||
|
$record = DiyFormRecords::create([ |
||||
|
'form_id' => $studyPlanForm->form_id, |
||||
|
'value' => json_encode($formValue), |
||||
|
'member_id' => $this->member_id ?? 0, |
||||
|
'relate_id' => 0, |
||||
|
'student_id' => $data['student_id'], |
||||
|
'create_time' => time() |
||||
|
]); |
||||
|
|
||||
|
if ($record) { |
||||
|
$result = [ |
||||
|
'id' => $record->record_id, |
||||
|
'student_id' => $record->student_id, |
||||
|
'plan_name' => $formValue['plan_name'], |
||||
|
'plan_content' => $formValue['plan_content'], |
||||
|
'plan_type' => $formValue['plan_type'], |
||||
|
'status' => $formValue['status'], |
||||
|
'progress' => $formValue['progress'], |
||||
|
'start_date' => $formValue['start_date'], |
||||
|
'end_date' => $formValue['end_date'], |
||||
|
'target_goals' => $formValue['target_goals'], |
||||
|
'learning_materials' => $formValue['learning_materials'], |
||||
|
'evaluation_criteria' => $formValue['evaluation_criteria'], |
||||
|
'remark' => $formValue['remark'], |
||||
|
'create_time' => is_numeric($record->create_time) ? date('Y-m-d H:i:s', $record->create_time) : $record->create_time |
||||
|
]; |
||||
|
|
||||
|
return ['code' => 1, 'data' => $result, 'msg' => '添加成功']; |
||||
|
} else { |
||||
|
return ['code' => 0, 'data' => [], 'msg' => '添加失败']; |
||||
|
} |
||||
|
|
||||
|
} catch (\Exception $e) { |
||||
|
return ['code' => 0, 'data' => [], 'msg' => '添加失败:' . $e->getMessage()]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 编辑学习计划 |
||||
|
* @param array $data |
||||
|
* @return array |
||||
|
*/ |
||||
|
public function edit(array $data) |
||||
|
{ |
||||
|
try { |
||||
|
$record = DiyFormRecords::find($data['record_id']); |
||||
|
if (!$record) { |
||||
|
return ['code' => 0, 'data' => [], 'msg' => '记录不存在']; |
||||
|
} |
||||
|
|
||||
|
// 获取现有数据 |
||||
|
$existingValue = is_string($record->value) ? (json_decode($record->value, true) ?: []) : (is_array($record->value) ? $record->value : []); |
||||
|
|
||||
|
// 更新字段 |
||||
|
$formValue = $existingValue; |
||||
|
|
||||
|
$updateFields = [ |
||||
|
'plan_name', 'plan_content', 'plan_type', 'status', 'progress', |
||||
|
'start_date', 'end_date', 'target_goals', 'learning_materials', |
||||
|
'evaluation_criteria', 'remark' |
||||
|
]; |
||||
|
|
||||
|
foreach ($updateFields as $field) { |
||||
|
if (isset($data[$field])) { |
||||
|
$formValue[$field] = ($field === 'progress') ? (int)$data[$field] : $data[$field]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 更新记录 |
||||
|
$result = $record->save([ |
||||
|
'value' => json_encode($formValue) |
||||
|
]); |
||||
|
|
||||
|
if ($result !== false) { |
||||
|
$resultData = [ |
||||
|
'id' => $record->record_id, |
||||
|
'student_id' => $record->student_id, |
||||
|
'plan_name' => $formValue['plan_name'] ?? '', |
||||
|
'plan_content' => $formValue['plan_content'] ?? '', |
||||
|
'plan_type' => $formValue['plan_type'] ?? '常规计划', |
||||
|
'status' => $formValue['status'] ?? 'active', |
||||
|
'progress' => (int)($formValue['progress'] ?? 0), |
||||
|
'start_date' => $formValue['start_date'] ?? '', |
||||
|
'end_date' => $formValue['end_date'] ?? '', |
||||
|
'target_goals' => $formValue['target_goals'] ?? '', |
||||
|
'learning_materials' => $formValue['learning_materials'] ?? '', |
||||
|
'evaluation_criteria' => $formValue['evaluation_criteria'] ?? '', |
||||
|
'remark' => $formValue['remark'] ?? '', |
||||
|
'create_time' => is_numeric($record->create_time) ? date('Y-m-d H:i:s', $record->create_time) : $record->create_time |
||||
|
]; |
||||
|
|
||||
|
return ['code' => 1, 'data' => $resultData, 'msg' => '修改成功']; |
||||
|
} else { |
||||
|
return ['code' => 0, 'data' => [], 'msg' => '修改失败']; |
||||
|
} |
||||
|
|
||||
|
} catch (\Exception $e) { |
||||
|
return ['code' => 0, 'data' => [], 'msg' => '修改失败:' . $e->getMessage()]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除学习计划 |
||||
|
* @param int $recordId |
||||
|
* @return array |
||||
|
*/ |
||||
|
public function delete($recordId) |
||||
|
{ |
||||
|
try { |
||||
|
$record = DiyFormRecords::find($recordId); |
||||
|
if (!$record) { |
||||
|
return ['code' => 0, 'data' => [], 'msg' => '记录不存在']; |
||||
|
} |
||||
|
|
||||
|
$result = $record->delete(); |
||||
|
|
||||
|
if ($result) { |
||||
|
return ['code' => 1, 'data' => [], 'msg' => '删除成功']; |
||||
|
} else { |
||||
|
return ['code' => 0, 'data' => [], 'msg' => '删除失败']; |
||||
|
} |
||||
|
|
||||
|
} catch (\Exception $e) { |
||||
|
return ['code' => 0, 'data' => [], 'msg' => '删除失败:' . $e->getMessage()]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新学习计划进度 |
||||
|
* @param int $recordId |
||||
|
* @param int $progress |
||||
|
* @return array |
||||
|
*/ |
||||
|
public function updateProgress($recordId, $progress) |
||||
|
{ |
||||
|
try { |
||||
|
$record = DiyFormRecords::find($recordId); |
||||
|
if (!$record) { |
||||
|
return ['code' => 0, 'data' => [], 'msg' => '记录不存在']; |
||||
|
} |
||||
|
|
||||
|
// 获取现有数据 |
||||
|
$existingValue = is_string($record->value) ? (json_decode($record->value, true) ?: []) : (is_array($record->value) ? $record->value : []); |
||||
|
$existingValue['progress'] = (int)$progress; |
||||
|
|
||||
|
// 根据进度自动更新状态 |
||||
|
if ($progress >= 100) { |
||||
|
$existingValue['status'] = 'completed'; |
||||
|
} elseif ($progress > 0) { |
||||
|
$existingValue['status'] = 'in_progress'; |
||||
|
} else { |
||||
|
$existingValue['status'] = 'active'; |
||||
|
} |
||||
|
|
||||
|
// 更新记录 |
||||
|
$result = $record->save([ |
||||
|
'value' => json_encode($existingValue) |
||||
|
]); |
||||
|
|
||||
|
if ($result !== false) { |
||||
|
$resultData = [ |
||||
|
'id' => $record->record_id, |
||||
|
'progress' => (int)$progress, |
||||
|
'status' => $existingValue['status'] |
||||
|
]; |
||||
|
|
||||
|
return ['code' => 1, 'data' => $resultData, 'msg' => '更新成功']; |
||||
|
} else { |
||||
|
return ['code' => 0, 'data' => [], 'msg' => '更新失败']; |
||||
|
} |
||||
|
|
||||
|
} catch (\Exception $e) { |
||||
|
return ['code' => 0, 'data' => [], 'msg' => '更新失败:' . $e->getMessage()]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,92 @@ |
|||||
|
✅ 已完成:uniapp/components/service-list-card/index.vue数据问题 |
||||
|
- 前端:已添加完整的Mock数据支持,组件可正常显示测试数据 |
||||
|
- 后端:PHP后端工程师已成功补充测试数据到数据库 |
||||
|
- API测试:接口 `/xy/service/list?student_id=1` 已验证返回完整数据 |
||||
|
- 最终状态:前端组件可以正常获取和显示真实的服务列表数据 |
||||
|
|
||||
|
API返回数据包含: |
||||
|
- 3种不同类型的服务(体测服务、测试服务、1V1+情感沟通) |
||||
|
- 每个服务包含完整的日志记录、评分、反馈信息 |
||||
|
- 服务状态、教练信息、时间等完整字段 |
||||
|
- 数据结构完全符合前端组件期望格式 |
||||
|
✅ 已完成:uniapp/components/service-list-card/index.vue弹窗无法关闭问题 |
||||
|
- 问题原因:事件绑定和微信小程序兼容性问题 |
||||
|
- 修复内容:添加了事件防冒泡处理,优化微信小程序环境下的事件响应 |
||||
|
- 解决方案:增强了响应式更新机制,添加了调试功能 |
||||
|
- 验证状态:已修复弹窗关闭功能,支持微信小程序环境 |
||||
|
|
||||
|
✅ 已处理:components/course-info-card/index.vue页面的接口需求分析 |
||||
|
- 已完成接口需求分析,包含完整的数据结构和字段定义 |
||||
|
- 已将详细的接口规格写入 niucloud/PLANNING.md 中 |
||||
|
- 等待PHP后端工程师按照规格开发接口 |
||||
|
- 开发完成后需要进行联调测试 |
||||
|
✅ 已修复:课程安排、订单列表、服务列表按钮点击无法打开弹窗问题 |
||||
|
- 问题原因:事件冒泡导致按钮点击事件冒泡到遮罩层,立即触发关闭逻辑 |
||||
|
- 修复方案:为所有触发弹窗的按钮添加 `.stop` 事件修饰符阻止冒泡 |
||||
|
- 涉及组件:主页面、OrderListCard、ServiceListCard等所有弹窗相关按钮 |
||||
|
- 验证状态:弹窗打开和关闭功能均正常,微信小程序环境兼容 |
||||
|
|
||||
|
✅ 已修复:pages/market/clue/clue_info.vue页面所有点击事件失灵问题 |
||||
|
- 问题原因:底部弹窗组件的遮罩层一直存在并覆盖整个视口,拦截所有页面点击 |
||||
|
- 核心问题:遮罩层即使弹窗隐藏时仍然监听点击事件,导致所有操作被误判为遮罩点击 |
||||
|
- 修复方案: |
||||
|
- 为BottomPopup组件添加 `v-if="visible"` 条件渲染 |
||||
|
- 关键元素添加 `@click.stop` 阻止事件冒泡 |
||||
|
- 优化弹窗关闭时的数据重置逻辑 |
||||
|
- 影响范围:修复了页面上所有按钮、标签切换、操作项的点击响应 |
||||
|
- 验证状态:页面交互功能完全恢复正常,微信小程序环境兼容 |
||||
|
|
||||
|
📋 体测记录PDF附件预览功能需求 |
||||
|
**背景**:体测记录弹窗需要支持PDF附件预览功能,允许用户查看体测报告 |
||||
|
**需求描述**: |
||||
|
1. 体测记录数据中需要包含 `pdf_files` 字段,包含附件信息 |
||||
|
2. 需要提供PDF文件预览接口,支持在线预览或下载 |
||||
|
3. 前端需要实现PDF预览功能,兼容微信小程序环境 |
||||
|
|
||||
|
**接口需求**: |
||||
|
1. 获取体测记录接口需要返回PDF附件信息 |
||||
|
2. 新增PDF文件预览/下载接口 |
||||
|
- 接口路径:`/api/fitness/record/pdf/preview` |
||||
|
- 请求方式:GET |
||||
|
- 请求参数: |
||||
|
```json |
||||
|
{ |
||||
|
"file_id": "文件ID", |
||||
|
"record_id": "体测记录ID" |
||||
|
} |
||||
|
``` |
||||
|
- 响应数据: |
||||
|
```json |
||||
|
{ |
||||
|
"code": 1, |
||||
|
"msg": "success", |
||||
|
"data": { |
||||
|
"file_url": "PDF文件访问URL", |
||||
|
"file_name": "文件名称", |
||||
|
"file_size": 1024000, |
||||
|
"preview_url": "在线预览URL(可选)", |
||||
|
"download_url": "下载URL" |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
**数据结构扩展**: |
||||
|
体测记录数据中的 `pdf_files` 字段结构: |
||||
|
```json |
||||
|
{ |
||||
|
"pdf_files": [ |
||||
|
{ |
||||
|
"id": "文件ID", |
||||
|
"name": "体测报告.pdf", |
||||
|
"size": 1024000, |
||||
|
"upload_time": "2024-01-15 10:30:00", |
||||
|
"file_path": "/uploads/fitness/2024/01/xxx.pdf" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
**前端实现**: |
||||
|
- 在 FitnessRecordCard 组件中实现PDF预览按钮 |
||||
|
- 支持微信小程序环境下的PDF预览 |
||||
|
- 提供下载功能作为备选方案 |
||||
Binary file not shown.
@ -0,0 +1,424 @@ |
|||||
|
<template> |
||||
|
<uni-popup ref="popup" type="center"> |
||||
|
<view class="popup-container"> |
||||
|
<view class="popup-header"> |
||||
|
<view class="popup-title">{{ isEditing ? '编辑学习计划' : '新增学习计划' }}</view> |
||||
|
<view class="popup-close" @click="close">✕</view> |
||||
|
</view> |
||||
|
<view class="study-plan-form"> |
||||
|
<view class="form-section"> |
||||
|
<view class="form-item"> |
||||
|
<view class="form-label">计划名称</view> |
||||
|
<view class="form-input"> |
||||
|
<input v-model="planData.plan_name" placeholder="请输入计划名称" /> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="form-item"> |
||||
|
<view class="form-label">计划类型</view> |
||||
|
<view class="form-input"> |
||||
|
<picker :value="typeIndex" :range="planTypes" @change="onTypeChange"> |
||||
|
<view class="picker-display"> |
||||
|
{{ planData.plan_type || '请选择计划类型' }} |
||||
|
</view> |
||||
|
</picker> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="form-item"> |
||||
|
<view class="form-label">开始日期</view> |
||||
|
<view class="form-input"> |
||||
|
<input type="date" v-model="planData.start_date" placeholder="请选择开始日期" /> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="form-item"> |
||||
|
<view class="form-label">结束日期</view> |
||||
|
<view class="form-input"> |
||||
|
<input type="date" v-model="planData.end_date" placeholder="请选择结束日期" /> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="form-item"> |
||||
|
<view class="form-label">计划内容</view> |
||||
|
<view class="form-input"> |
||||
|
<textarea |
||||
|
v-model="planData.plan_content" |
||||
|
placeholder="请输入计划详细内容" |
||||
|
maxlength="500" |
||||
|
:show-confirm-bar="false" |
||||
|
></textarea> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="form-item"> |
||||
|
<view class="form-label">状态</view> |
||||
|
<view class="form-input"> |
||||
|
<picker :value="statusIndex" :range="statusOptions" @change="onStatusChange"> |
||||
|
<view class="picker-display"> |
||||
|
{{ getStatusText(planData.status) || '请选择状态' }} |
||||
|
</view> |
||||
|
</picker> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="popup-footer"> |
||||
|
<view class="popup-btn cancel-btn" @click="close">取消</view> |
||||
|
<view class="popup-btn confirm-btn" @click="confirm">确认</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</uni-popup> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
name: 'StudyPlanPopup', |
||||
|
props: { |
||||
|
studentId: { |
||||
|
type: [String, Number], |
||||
|
default: '' |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
isVisible: false, |
||||
|
isEditing: false, |
||||
|
planData: { |
||||
|
id: null, |
||||
|
plan_name: '', |
||||
|
plan_type: '', |
||||
|
start_date: '', |
||||
|
end_date: '', |
||||
|
plan_content: '', |
||||
|
status: 'pending' |
||||
|
}, |
||||
|
planTypes: ['学习计划', '训练计划', '康复计划', '体能提升', '技能培养', '其他'], |
||||
|
typeIndex: 0, |
||||
|
statusOptions: ['pending', 'active', 'completed', 'expired'], |
||||
|
statusIndex: 0 |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
// 打开新增弹窗 |
||||
|
openAdd() { |
||||
|
this.isEditing = false |
||||
|
this.resetData() |
||||
|
this.isVisible = true |
||||
|
this.$refs.popup.open() |
||||
|
}, |
||||
|
|
||||
|
// 打开编辑弹窗 |
||||
|
openEdit(plan) { |
||||
|
this.isEditing = true |
||||
|
this.planData = { |
||||
|
id: plan.id, |
||||
|
plan_name: plan.plan_name || '', |
||||
|
plan_type: plan.plan_type || '', |
||||
|
start_date: plan.start_date || '', |
||||
|
end_date: plan.end_date || '', |
||||
|
plan_content: plan.plan_content || '', |
||||
|
status: plan.status || 'pending' |
||||
|
} |
||||
|
|
||||
|
// 设置选择器索引 |
||||
|
this.typeIndex = this.planTypes.indexOf(this.planData.plan_type) |
||||
|
this.statusIndex = this.statusOptions.indexOf(this.planData.status) |
||||
|
|
||||
|
this.isVisible = true |
||||
|
this.$refs.popup.open() |
||||
|
}, |
||||
|
|
||||
|
// 关闭弹窗 |
||||
|
close() { |
||||
|
this.isVisible = false |
||||
|
this.$refs.popup.close() |
||||
|
this.resetData() |
||||
|
this.$emit('close') |
||||
|
}, |
||||
|
|
||||
|
// 重置数据 |
||||
|
resetData() { |
||||
|
this.planData = { |
||||
|
id: null, |
||||
|
plan_name: '', |
||||
|
plan_type: '', |
||||
|
start_date: '', |
||||
|
end_date: '', |
||||
|
plan_content: '', |
||||
|
status: 'pending' |
||||
|
} |
||||
|
this.typeIndex = 0 |
||||
|
this.statusIndex = 0 |
||||
|
}, |
||||
|
|
||||
|
// 确认保存 |
||||
|
async confirm() { |
||||
|
try { |
||||
|
// 表单验证 |
||||
|
if (!this.planData.plan_name) { |
||||
|
uni.showToast({ |
||||
|
title: '请输入计划名称', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
if (!this.planData.plan_type) { |
||||
|
uni.showToast({ |
||||
|
title: '请选择计划类型', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
if (!this.planData.start_date) { |
||||
|
uni.showToast({ |
||||
|
title: '请选择开始日期', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
if (!this.planData.plan_content) { |
||||
|
uni.showToast({ |
||||
|
title: '请输入计划内容', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// 验证日期逻辑 |
||||
|
if (this.planData.end_date && this.planData.start_date > this.planData.end_date) { |
||||
|
uni.showToast({ |
||||
|
title: '结束日期不能早于开始日期', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
uni.showLoading({ |
||||
|
title: '保存中...', |
||||
|
mask: true |
||||
|
}) |
||||
|
|
||||
|
const params = { |
||||
|
student_id: this.studentId, |
||||
|
plan_name: this.planData.plan_name, |
||||
|
plan_type: this.planData.plan_type, |
||||
|
start_date: this.planData.start_date, |
||||
|
end_date: this.planData.end_date, |
||||
|
plan_content: this.planData.plan_content, |
||||
|
status: this.planData.status |
||||
|
} |
||||
|
|
||||
|
if (this.isEditing) { |
||||
|
params.id = this.planData.id |
||||
|
} |
||||
|
|
||||
|
console.log('保存学习计划参数:', params) |
||||
|
|
||||
|
// 触发确认事件,将数据传递给父组件 |
||||
|
this.$emit('confirm', { |
||||
|
isEditing: this.isEditing, |
||||
|
data: params |
||||
|
}) |
||||
|
|
||||
|
uni.hideLoading() |
||||
|
this.close() |
||||
|
|
||||
|
} catch (error) { |
||||
|
console.error('保存学习计划失败:', error) |
||||
|
uni.showToast({ |
||||
|
title: '保存失败,请重试', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
uni.hideLoading() |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 计划类型选择 |
||||
|
onTypeChange(e) { |
||||
|
this.typeIndex = e.detail.value |
||||
|
this.planData.plan_type = this.planTypes[this.typeIndex] |
||||
|
}, |
||||
|
|
||||
|
// 状态选择 |
||||
|
onStatusChange(e) { |
||||
|
this.statusIndex = e.detail.value |
||||
|
this.planData.status = this.statusOptions[this.statusIndex] |
||||
|
}, |
||||
|
|
||||
|
// 获取状态文本 |
||||
|
getStatusText(status) { |
||||
|
const statusMap = { |
||||
|
'pending': '待开始', |
||||
|
'active': '进行中', |
||||
|
'completed': '已完成', |
||||
|
'expired': '已过期' |
||||
|
} |
||||
|
return statusMap[status] || '' |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="less" scoped> |
||||
|
.popup-container { |
||||
|
width: 90vw; |
||||
|
max-width: 500rpx; |
||||
|
background-color: #1a1a1a; |
||||
|
border-radius: 20rpx; |
||||
|
border: 1px solid #333; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.popup-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 30rpx; |
||||
|
border-bottom: 1px solid #333; |
||||
|
background-color: #2a2a2a; |
||||
|
} |
||||
|
|
||||
|
.popup-title { |
||||
|
color: #ffffff; |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
|
||||
|
.popup-close { |
||||
|
color: #999999; |
||||
|
font-size: 36rpx; |
||||
|
cursor: pointer; |
||||
|
width: 60rpx; |
||||
|
height: 60rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
|
||||
|
&:hover { |
||||
|
color: #ffffff; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.study-plan-form { |
||||
|
padding: 30rpx; |
||||
|
max-height: 60vh; |
||||
|
overflow-y: auto; |
||||
|
} |
||||
|
|
||||
|
.form-section { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.form-item { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.form-label { |
||||
|
color: #ffffff; |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.form-input { |
||||
|
background-color: #333333; |
||||
|
border-radius: 12rpx; |
||||
|
border: 1px solid #404040; |
||||
|
overflow: hidden; |
||||
|
|
||||
|
input, textarea { |
||||
|
width: 100%; |
||||
|
padding: 20rpx; |
||||
|
background-color: transparent; |
||||
|
color: #ffffff; |
||||
|
font-size: 28rpx; |
||||
|
border: none; |
||||
|
outline: none; |
||||
|
|
||||
|
&::placeholder { |
||||
|
color: #999999; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
textarea { |
||||
|
min-height: 120rpx; |
||||
|
resize: none; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.picker-display { |
||||
|
padding: 20rpx; |
||||
|
color: #ffffff; |
||||
|
font-size: 28rpx; |
||||
|
|
||||
|
&:empty::before { |
||||
|
content: attr(placeholder); |
||||
|
color: #999999; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.popup-footer { |
||||
|
display: flex; |
||||
|
gap: 20rpx; |
||||
|
padding: 30rpx; |
||||
|
border-top: 1px solid #333; |
||||
|
background-color: #1a1a1a; |
||||
|
} |
||||
|
|
||||
|
.popup-btn { |
||||
|
flex: 1; |
||||
|
height: 80rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
border-radius: 12rpx; |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 500; |
||||
|
cursor: pointer; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.cancel-btn { |
||||
|
background-color: #333333; |
||||
|
color: #ffffff; |
||||
|
border: 1px solid #404040; |
||||
|
|
||||
|
&:active { |
||||
|
background-color: #404040; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.confirm-btn { |
||||
|
background-color: #29D3B4; |
||||
|
color: #ffffff; |
||||
|
|
||||
|
&:active { |
||||
|
background-color: #24B89E; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 滚动条样式 */ |
||||
|
.study-plan-form::-webkit-scrollbar { |
||||
|
width: 6rpx; |
||||
|
} |
||||
|
|
||||
|
.study-plan-form::-webkit-scrollbar-track { |
||||
|
background: transparent; |
||||
|
} |
||||
|
|
||||
|
.study-plan-form::-webkit-scrollbar-thumb { |
||||
|
background: #29D3B4; |
||||
|
border-radius: 3rpx; |
||||
|
} |
||||
|
|
||||
|
.study-plan-form::-webkit-scrollbar-thumb:hover { |
||||
|
background: #24B89E; |
||||
|
} |
||||
|
</style> |
||||
File diff suppressed because it is too large
@ -0,0 +1,132 @@ |
|||||
|
# 各校区月&年转化汇总表数据统计需求文档 |
||||
|
|
||||
|
## 1. 项目背景 |
||||
|
基于智慧教务系统,需要建立完善的数据统计分析功能,为各校区运营决策提供数据支撑。 |
||||
|
|
||||
|
## 2. 业务目标 |
||||
|
- 实现各校区数据的统一汇总和对比分析 |
||||
|
- 提供月度、年度的转化率趋势分析 |
||||
|
- 为校区运营优化提供数据依据 |
||||
|
|
||||
|
## 3. 功能需求 |
||||
|
|
||||
|
### 3.1 数据统计维度 |
||||
|
|
||||
|
#### 3.1.1 校区维度 |
||||
|
- **数据范围**:所有已开设校区 |
||||
|
- **统计指标**: |
||||
|
- 学员总数 |
||||
|
- 新增学员数 |
||||
|
- 流失学员数 |
||||
|
- 收入金额 |
||||
|
- 转化率 |
||||
|
|
||||
|
#### 3.1.2 时间维度 |
||||
|
- **月度统计**:按自然月统计各项指标 |
||||
|
- **年度统计**:按自然年统计各项指标 |
||||
|
- **对比分析**:同比、环比增长率 |
||||
|
|
||||
|
#### 3.1.3 课程维度 |
||||
|
- **课程类型**:体能课、篮球课、私教课等 |
||||
|
- **统计指标**: |
||||
|
- 各课程类型学员数 |
||||
|
- 各课程类型收入 |
||||
|
- 课程转化率 |
||||
|
|
||||
|
### 3.2 转化率计算规则 |
||||
|
|
||||
|
#### 3.2.1 试听转化率 |
||||
|
``` |
||||
|
试听转化率 = (试听后报名学员数 / 试听学员总数) × 100% |
||||
|
``` |
||||
|
|
||||
|
#### 3.2.2 续费转化率 |
||||
|
``` |
||||
|
续费转化率 = (续费学员数 / 到期学员总数) × 100% |
||||
|
``` |
||||
|
|
||||
|
#### 3.2.3 整体转化率 |
||||
|
``` |
||||
|
整体转化率 = (正式学员数 / 潜在客户总数) × 100% |
||||
|
``` |
||||
|
|
||||
|
### 3.3 报表功能需求 |
||||
|
|
||||
|
#### 3.3.1 数据展示 |
||||
|
- **表格形式**:支持Excel导出 |
||||
|
- **图表形式**:柱状图、折线图、饼图 |
||||
|
- **数据筛选**:按校区、时间、课程类型筛选 |
||||
|
|
||||
|
#### 3.3.2 数据权限 |
||||
|
- **超级管理员**:查看所有校区数据 |
||||
|
- **校区管理员**:仅查看本校区数据 |
||||
|
- **教练**:查看相关课程数据 |
||||
|
|
||||
|
## 4. 技术实现方案 |
||||
|
|
||||
|
### 4.1 数据源 |
||||
|
- **学员信息表**:基础学员数据 |
||||
|
- **课程安排表**:课程相关数据 |
||||
|
- **收费记录表**:财务数据 |
||||
|
- **考勤记录表**:出勤数据 |
||||
|
|
||||
|
### 4.2 技术架构 |
||||
|
- **后端**:PHP (ThinkPHP框架) |
||||
|
- **前端**:Vue.js + Element UI |
||||
|
- **移动端**:uni-app |
||||
|
- **图表库**:uCharts/ECharts |
||||
|
|
||||
|
### 4.3 数据处理流程 |
||||
|
1. **数据采集**:从各业务表采集原始数据 |
||||
|
2. **数据清洗**:处理异常数据和重复数据 |
||||
|
3. **数据计算**:按统计规则计算各项指标 |
||||
|
4. **数据存储**:存储到统计结果表 |
||||
|
5. **数据展示**:通过API提供给前端展示 |
||||
|
|
||||
|
## 5. 界面设计要求 |
||||
|
|
||||
|
### 5.1 PC端管理后台 |
||||
|
- **统计概览页**:关键指标卡片展示 |
||||
|
- **详细报表页**:可筛选的数据表格 |
||||
|
- **图表分析页**:多维度图表展示 |
||||
|
|
||||
|
### 5.2 移动端 |
||||
|
- **数据看板**:关键指标移动端展示 |
||||
|
- **简化报表**:适配移动端的数据表格 |
||||
|
|
||||
|
## 6. 数据安全要求 |
||||
|
- **数据加密**:敏感数据加密存储 |
||||
|
- **访问控制**:基于角色的权限控制 |
||||
|
- **操作日志**:记录数据查看和导出日志 |
||||
|
|
||||
|
## 7. 性能要求 |
||||
|
- **响应时间**:页面加载时间 < 3秒 |
||||
|
- **并发支持**:支持100个用户同时访问 |
||||
|
- **数据更新**:支持实时或定时更新 |
||||
|
|
||||
|
## 8. 验收标准 |
||||
|
- [ ] 数据统计准确性验证 |
||||
|
- [ ] 各维度筛选功能正常 |
||||
|
- [ ] 图表展示效果符合要求 |
||||
|
- [ ] 权限控制功能正常 |
||||
|
- [ ] 导出功能正常 |
||||
|
- [ ] 移动端适配正常 |
||||
|
|
||||
|
## 9. 风险评估 |
||||
|
- **数据一致性风险**:需要确保统计数据与业务数据一致 |
||||
|
- **性能风险**:大数据量统计可能影响系统性能 |
||||
|
- **权限风险**:需要严格控制数据访问权限 |
||||
|
|
||||
|
## 10. 后续优化建议 |
||||
|
- **数据挖掘**:基于历史数据进行趋势预测 |
||||
|
- **智能分析**:提供数据异常预警功能 |
||||
|
- **自动化报告**:定期自动生成运营报告 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
**注意**:本需求文档基于项目结构分析生成,具体的数据字段和计算规则需要根据实际Excel表格内容进行调整。 |
||||
|
|
||||
|
**下一步行动**: |
||||
|
1. 请提供Excel表格的具体内容截图或数据样例 |
||||
|
2. 确认具体的业务规则和计算公式 |
||||
|
3. 明确优先级和开发时间节点 |
||||
Loading…
Reference in new issue