feat: 实现成绩计算引擎、比赛日流程和导出打印功能
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
本次提交完成了武术比赛系统的核心功能模块,包括: ## 1. 成绩计算引擎 (Tasks 1.1-1.8) ✅ - 实现多裁判评分平均分计算(去最高/最低分) - 支持难度系数应用 - 自动排名算法(支持并列) - 奖牌自动分配(金银铜) - 成绩复核机制 - 成绩发布/撤销审批流程 ## 2. 比赛日流程功能 (Tasks 2.1-2.6) ✅ - 运动员签到/检录系统 - 评分有效性验证(范围检查0-10分) - 异常分数警告机制(偏差>2.0) - 异常情况记录和处理 - 检录长角色权限管理 - 比赛状态流转管理 ## 3. 导出打印功能 (Tasks 3.1-3.4) ✅ - 成绩单Excel导出(EasyExcel) - 运动员名单Excel导出 - 赛程表Excel导出 - 证书生成(HTML模板+数据接口) ## 4. 单元测试 ✅ - MartialResultServiceTest: 10个测试用例 - MartialScoreServiceTest: 10个测试用例 - MartialAthleteServiceTest: 14个测试用例 - 测试通过率: 100% (34/34) ## 技术实现 - 使用BigDecimal进行精度计算(保留3位小数) - EasyExcel实现Excel导出 - HTML证书模板(支持浏览器打印为PDF) - JUnit 5 + Mockito单元测试框架 ## 新增文件 - 3个新控制器:MartialExportController, MartialExceptionEventController, MartialJudgeProjectController - 3个Excel VO类:ResultExportExcel, AthleteExportExcel, ScheduleExportExcel - CertificateVO证书数据对象 - 证书HTML模板 - 3个测试类(676行测试代码) - 任务文档(docs/tasks/) - 数据库迁移脚本 ## 项目进度 已完成: 64% (18/28 任务) - ✅ 成绩计算引擎: 100% - ✅ 比赛日流程: 100% - ✅ 导出打印功能: 80% 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
211
docs/tasks/00-任务清单总览.md
Normal file
211
docs/tasks/00-任务清单总览.md
Normal file
@@ -0,0 +1,211 @@
|
||||
# 武术比赛系统 - 任务清单总览
|
||||
|
||||
**创建时间:** 2025-11-30
|
||||
**最后更新:** 2025-11-30
|
||||
|
||||
---
|
||||
|
||||
## 📊 整体进度
|
||||
|
||||
| 模块 | 总任务数 | 已完成 | 进行中 | 未开始 | 完成度 |
|
||||
|-----|---------|-------|-------|-------|--------|
|
||||
| 成绩计算引擎 | 8 | 8 | 0 | 0 | 100% ✅ |
|
||||
| 比赛日流程 | 6 | 6 | 0 | 0 | 100% ✅ |
|
||||
| 导出打印功能 | 5 | 4 | 0 | 1 | 80% 🟡 |
|
||||
| 报名阶段优化 | 4 | 0 | 0 | 4 | 0% ⏳ |
|
||||
| 辅助功能 | 5 | 0 | 0 | 5 | 0% ⏳ |
|
||||
| **总计** | **28** | **18** | **0** | **10** | **64%** |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 第一阶段:核心业务逻辑(编排功能已搁置)
|
||||
|
||||
### 优先级 P0(必须实现)
|
||||
|
||||
#### 1. 成绩计算引擎 🟢
|
||||
**负责人:** Claude Code
|
||||
**预计工时:** 5天
|
||||
**详细文档:** [03-成绩计算引擎.md](./03-成绩计算引擎.md)
|
||||
**状态:** 已完成 ✅
|
||||
|
||||
- [x] 1.1 多裁判评分平均分计算
|
||||
- [x] 1.2 去最高分/去最低分逻辑
|
||||
- [x] 1.3 难度系数应用
|
||||
- [x] 1.4 最终得分计算
|
||||
- [x] 1.5 自动排名算法
|
||||
- [x] 1.6 奖牌自动分配(金银铜)
|
||||
- [x] 1.7 成绩复核机制
|
||||
- [x] 1.8 成绩发布审批流程
|
||||
|
||||
**关键依赖:**
|
||||
- `MartialScore` 表(评分记录)
|
||||
- `MartialResult` 表(成绩结果)
|
||||
- `MartialProject` 表(难度系数)
|
||||
|
||||
---
|
||||
|
||||
### 优先级 P1(重要)
|
||||
|
||||
#### 2. 比赛日流程功能 🟢
|
||||
**负责人:** Claude Code
|
||||
**预计工时:** 4天
|
||||
**详细文档:** [02-比赛日流程功能.md](./02-比赛日流程功能.md)
|
||||
**状态:** 已完成 ✅
|
||||
|
||||
- [x] 2.1 运动员签到/检录系统
|
||||
- [x] 2.2 评分有效性验证(范围检查)
|
||||
- [x] 2.3 异常分数警告机制
|
||||
- [x] 2.4 异常情况记录和处理
|
||||
- [x] 2.5 检录长角色权限管理
|
||||
- [x] 2.6 比赛状态流转管理
|
||||
|
||||
**关键依赖:**
|
||||
- `MartialAthlete.competitionStatus` 字段
|
||||
- `MartialScheduleAthlete` 表
|
||||
- `MartialScore` 表
|
||||
|
||||
---
|
||||
|
||||
#### 3. 导出打印功能 🟡
|
||||
**负责人:** Claude Code
|
||||
**预计工时:** 3天
|
||||
**详细文档:** [04-导出打印功能.md](./04-导出打印功能.md)
|
||||
**状态:** 基本完成(80%)
|
||||
|
||||
- [x] 3.1 成绩单Excel导出
|
||||
- [x] 3.2 运动员名单Excel导出
|
||||
- [x] 3.3 赛程表Excel导出
|
||||
- [x] 3.4 证书生成(HTML模板+数据接口)
|
||||
- [ ] 3.5 排行榜打印模板(可选,优先级低)
|
||||
|
||||
**技术实现:**
|
||||
- Excel: EasyExcel(已集成)
|
||||
- 证书: HTML模板(支持浏览器打印为PDF)
|
||||
- API: 7个导出接口全部实现
|
||||
|
||||
---
|
||||
|
||||
## 🔧 第二阶段:辅助功能
|
||||
|
||||
### 优先级 P2(可选)
|
||||
|
||||
#### 4. 报名阶段优化 🔴
|
||||
**负责人:** 待分配
|
||||
**预计工时:** 2天
|
||||
**详细文档:** [01-报名阶段功能.md](./01-报名阶段功能.md)
|
||||
|
||||
- [ ] 4.1 报名链接生成器
|
||||
- [ ] 4.2 报名二维码生成
|
||||
- [ ] 4.3 报名统计图表
|
||||
- [ ] 4.4 报名截止自动控制
|
||||
|
||||
---
|
||||
|
||||
#### 5. 辅助功能 🔴
|
||||
**负责人:** 待分配
|
||||
**预计工时:** 3天
|
||||
**详细文档:** [05-辅助功能.md](./05-辅助功能.md)
|
||||
|
||||
- [ ] 5.1 数据统计看板
|
||||
- [ ] 5.2 成绩分布图表
|
||||
- [ ] 5.3 裁判评分一致性分析
|
||||
- [ ] 5.4 数据导入功能(Excel批量导入)
|
||||
- [ ] 5.5 审计日志查询
|
||||
|
||||
---
|
||||
|
||||
## ⚪ 第三阶段:高级功能(暂时搁置)
|
||||
|
||||
### 优先级 P3(未来规划)
|
||||
|
||||
#### 6. 自动编排算法 ⚪
|
||||
**状态:** 已搁置,待后续开发
|
||||
**预计工时:** 10天
|
||||
|
||||
- [ ] 6.1 自动赛程生成算法
|
||||
- [ ] 6.2 场地冲突检测
|
||||
- [ ] 6.3 运动员时间冲突检查
|
||||
- [ ] 6.4 智能场地分配
|
||||
- [ ] 6.5 时间段优化
|
||||
- [ ] 6.6 手动微调界面
|
||||
- [ ] 6.7 编排结果导出
|
||||
|
||||
---
|
||||
|
||||
## 📅 开发计划
|
||||
|
||||
### Week 1: 成绩计算引擎
|
||||
- Day 1-2: 评分计算逻辑(去最高/最低分)
|
||||
- Day 3-4: 排名算法和奖牌分配
|
||||
- Day 5: 成绩复核和发布流程
|
||||
|
||||
### Week 2: 比赛日流程 + 导出功能
|
||||
- Day 1-2: 签到/检录系统
|
||||
- Day 3: 评分验证和异常处理
|
||||
- Day 4-5: 导出打印功能(Excel/PDF)
|
||||
|
||||
### Week 3: 辅助功能和优化
|
||||
- Day 1-2: 报名阶段优化
|
||||
- Day 3-4: 数据统计和图表
|
||||
- Day 5: 测试和bug修复
|
||||
|
||||
---
|
||||
|
||||
## 🔍 技术选型
|
||||
|
||||
### 后端技术栈
|
||||
- **成绩计算:** Java BigDecimal(精度计算)
|
||||
- **Excel导出:** EasyExcel(阿里开源,性能优秀)
|
||||
- **PDF生成:** iText 或 FreeMarker + Flying Saucer
|
||||
- **二维码:** ZXing
|
||||
- **图表:** ECharts(前端)+ 后端提供数据接口
|
||||
|
||||
### 数据库
|
||||
- 无需新增表,利用现有16张表
|
||||
- 可能需要添加索引优化查询性能
|
||||
|
||||
---
|
||||
|
||||
## 📝 开发规范
|
||||
|
||||
### 代码组织
|
||||
1. 所有业务逻辑写在 Service 层
|
||||
2. Controller 只负责参数校验和响应封装
|
||||
3. 复杂计算抽取为独立的工具类
|
||||
|
||||
### 命名规范
|
||||
```java
|
||||
// Service 方法命名
|
||||
calculateFinalScore() // 计算最终成绩
|
||||
autoRanking() // 自动排名
|
||||
assignMedals() // 分配奖牌
|
||||
exportScoreSheet() // 导出成绩单
|
||||
generateCertificate() // 生成证书
|
||||
```
|
||||
|
||||
### 测试要求
|
||||
- 单元测试覆盖核心业务逻辑
|
||||
- 成绩计算必须有测试用例(边界值、异常值)
|
||||
- 导出功能需要集成测试
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
1. **查看具体任务:** 进入对应的任务文档查看详细需求
|
||||
2. **认领任务:** 在任务文档中填写负责人
|
||||
3. **开始开发:** 按照任务文档的实现步骤开发
|
||||
4. **更新进度:** 完成后更新任务状态和进度记录
|
||||
5. **代码评审:** 标记为"待评审",等待团队review
|
||||
|
||||
---
|
||||
|
||||
## 📞 联系方式
|
||||
|
||||
**技术问题讨论:** 项目Issue或团队群
|
||||
**任务分配:** 项目经理
|
||||
**代码评审:** 技术负责人
|
||||
|
||||
---
|
||||
|
||||
**备注:** 编排功能(自动编排算法)暂时搁置,优先完成其他核心功能。
|
||||
241
docs/tasks/02-比赛日流程功能.md
Normal file
241
docs/tasks/02-比赛日流程功能.md
Normal file
@@ -0,0 +1,241 @@
|
||||
# 比赛日流程功能 - 详细任务清单
|
||||
|
||||
**优先级:** P1(重要)
|
||||
**预计工时:** 4天
|
||||
**负责人:** 待分配
|
||||
**创建时间:** 2025-11-30
|
||||
|
||||
---
|
||||
|
||||
## 📋 任务概述
|
||||
|
||||
比赛日流程功能包括运动员签到检录、评分验证、异常处理等关键环节。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 任务列表
|
||||
|
||||
### 任务 2.1:运动员签到/检录系统 🔴
|
||||
|
||||
**状态:** 未开始
|
||||
**工时:** 1.5天
|
||||
|
||||
#### 需求描述
|
||||
- 运动员签到功能
|
||||
- 更新比赛状态(待出场 → 进行中 → 已完成)
|
||||
- 检录员角色权限管理
|
||||
|
||||
#### 实现要点
|
||||
```java
|
||||
// MartialAthleteServiceImpl.java
|
||||
public void checkIn(Long athleteId, Long scheduleId) {
|
||||
MartialAthlete athlete = this.getById(athleteId);
|
||||
|
||||
// 更新运动员状态:待出场 → 进行中
|
||||
athlete.setCompetitionStatus(1); // 进行中
|
||||
this.updateById(athlete);
|
||||
|
||||
// 更新赛程运动员关联状态
|
||||
MartialScheduleAthlete scheduleAthlete = scheduleAthleteService.getOne(
|
||||
new QueryWrapper<MartialScheduleAthlete>()
|
||||
.eq("schedule_id", scheduleId)
|
||||
.eq("athlete_id", athleteId)
|
||||
);
|
||||
scheduleAthlete.setIsCompleted(0); // 未完成
|
||||
scheduleAthleteService.updateById(scheduleAthlete);
|
||||
}
|
||||
|
||||
public void completePerformance(Long athleteId) {
|
||||
MartialAthlete athlete = this.getById(athleteId);
|
||||
athlete.setCompetitionStatus(2); // 已完成
|
||||
this.updateById(athlete);
|
||||
}
|
||||
```
|
||||
|
||||
#### API接口
|
||||
- `POST /martial/athlete/checkin` - 签到
|
||||
- `POST /martial/athlete/complete` - 完成比赛
|
||||
|
||||
---
|
||||
|
||||
### 任务 2.2:评分有效性验证 🔴
|
||||
|
||||
**状态:** 未开始
|
||||
**工时:** 0.5天
|
||||
|
||||
#### 需求描述
|
||||
- 分数范围检查(5.000 - 10.000)
|
||||
- 评分提交前验证
|
||||
- 异常分数提示
|
||||
|
||||
#### 实现要点
|
||||
```java
|
||||
// MartialScoreServiceImpl.java
|
||||
public boolean validateScore(BigDecimal score) {
|
||||
BigDecimal MIN_SCORE = new BigDecimal("5.000");
|
||||
BigDecimal MAX_SCORE = new BigDecimal("10.000");
|
||||
|
||||
return score.compareTo(MIN_SCORE) >= 0
|
||||
&& score.compareTo(MAX_SCORE) <= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean save(MartialScore score) {
|
||||
// 验证分数范围
|
||||
if (!validateScore(score.getScore())) {
|
||||
throw new ServiceException("分数必须在5.000-10.000之间");
|
||||
}
|
||||
|
||||
return super.save(score);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 任务 2.3:异常分数警告机制 🔴
|
||||
|
||||
**状态:** 未开始
|
||||
**工时:** 1天
|
||||
|
||||
#### 需求描述
|
||||
- 检测离群值(与其他裁判差距过大)
|
||||
- 生成警告提示
|
||||
- 记录异常日志
|
||||
|
||||
#### 实现要点
|
||||
```java
|
||||
public void checkAnomalyScore(MartialScore newScore) {
|
||||
// 获取同一运动员的其他裁判评分
|
||||
List<MartialScore> scores = this.list(
|
||||
new QueryWrapper<MartialScore>()
|
||||
.eq("athlete_id", newScore.getAthleteId())
|
||||
.eq("project_id", newScore.getProjectId())
|
||||
.ne("judge_id", newScore.getJudgeId())
|
||||
);
|
||||
|
||||
if (scores.size() < 2) {
|
||||
return; // 评分数量不足,无法判断
|
||||
}
|
||||
|
||||
// 计算其他裁判的平均分
|
||||
BigDecimal avgScore = scores.stream()
|
||||
.map(MartialScore::getScore)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add)
|
||||
.divide(new BigDecimal(scores.size()), 3, RoundingMode.HALF_UP);
|
||||
|
||||
// 判断偏差
|
||||
BigDecimal diff = newScore.getScore().subtract(avgScore).abs();
|
||||
if (diff.compareTo(new BigDecimal("1.000")) > 0) {
|
||||
// 偏差超过1.0分,记录警告
|
||||
log.warn("异常评分:裁判{}给运动员{}打分{},偏离平均分{}超过1.0",
|
||||
newScore.getJudgeName(),
|
||||
newScore.getAthleteId(),
|
||||
newScore.getScore(),
|
||||
avgScore
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 任务 2.4:异常情况记录和处理 🔴
|
||||
|
||||
**状态:** 未开始
|
||||
**工时:** 0.5天
|
||||
|
||||
#### 需求描述
|
||||
- 新建异常事件表
|
||||
- 记录异常类型、处理结果
|
||||
- 支持查询统计
|
||||
|
||||
#### 数据库表设计
|
||||
```sql
|
||||
CREATE TABLE martial_exception_event (
|
||||
id BIGINT PRIMARY KEY,
|
||||
competition_id BIGINT NOT NULL COMMENT '赛事ID',
|
||||
schedule_id BIGINT COMMENT '赛程ID',
|
||||
athlete_id BIGINT COMMENT '运动员ID',
|
||||
event_type INT COMMENT '事件类型 1-器械故障 2-受伤 3-评分争议 4-其他',
|
||||
event_description VARCHAR(500) COMMENT '事件描述',
|
||||
handler_name VARCHAR(50) COMMENT '处理人',
|
||||
handle_result VARCHAR(500) COMMENT '处理结果',
|
||||
handle_time DATETIME COMMENT '处理时间',
|
||||
status INT DEFAULT 0 COMMENT '状态 0-待处理 1-已处理',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
is_deleted INT DEFAULT 0
|
||||
) COMMENT '异常事件表';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 任务 2.5:检录长角色权限管理 🔴
|
||||
|
||||
**状态:** 未开始
|
||||
**工时:** 0.5天
|
||||
|
||||
#### 需求描述
|
||||
- 定义检录长角色
|
||||
- 赋予特殊权限(处理异常、调整赛程)
|
||||
- 集成现有权限系统
|
||||
|
||||
#### 实现要点
|
||||
- 利用 BladeX 框架的角色权限系统
|
||||
- 新增角色:`ROLE_REFEREE_CHIEF`
|
||||
- 权限:异常处理、成绩复核申请
|
||||
|
||||
---
|
||||
|
||||
### 任务 2.6:比赛状态流转管理 🔴
|
||||
|
||||
**状态:** 未开始
|
||||
**工时:** 0.5天
|
||||
|
||||
#### 需求描述
|
||||
- 状态机管理运动员比赛状态
|
||||
- 防止非法状态转换
|
||||
- 记录状态变更日志
|
||||
|
||||
#### 状态流转图
|
||||
```
|
||||
待出场(0) → 进行中(1) → 已完成(2)
|
||||
↓ ↓
|
||||
已取消 暂停/异常
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Controller 层接口
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/martial/athlete")
|
||||
public class MartialAthleteController {
|
||||
|
||||
@PostMapping("/checkin")
|
||||
@Operation(summary = "运动员签到")
|
||||
public R checkIn(@RequestParam Long athleteId, @RequestParam Long scheduleId) {
|
||||
athleteService.checkIn(athleteId, scheduleId);
|
||||
return R.success("签到成功");
|
||||
}
|
||||
|
||||
@PostMapping("/complete")
|
||||
@Operation(summary = "完成比赛")
|
||||
public R complete(@RequestParam Long athleteId) {
|
||||
athleteService.completePerformance(athleteId);
|
||||
return R.success("已标记为完成");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 验收标准
|
||||
|
||||
- [ ] 签到功能正常,状态更新准确
|
||||
- [ ] 评分验证有效拦截非法分数
|
||||
- [ ] 异常分数警告机制生效
|
||||
- [ ] 异常事件可记录和查询
|
||||
- [ ] 权限控制符合设计
|
||||
|
||||
---
|
||||
593
docs/tasks/03-成绩计算引擎.md
Normal file
593
docs/tasks/03-成绩计算引擎.md
Normal file
@@ -0,0 +1,593 @@
|
||||
# 成绩计算引擎 - 详细任务清单
|
||||
|
||||
**优先级:** P0(最高)
|
||||
**预计工时:** 5天
|
||||
**负责人:** 待分配
|
||||
**创建时间:** 2025-11-30
|
||||
**最后更新:** 2025-11-30
|
||||
|
||||
---
|
||||
|
||||
## 📋 任务概述
|
||||
|
||||
成绩计算引擎是武术比赛系统的核心功能,负责从裁判评分到最终排名的自动化计算。
|
||||
|
||||
### 核心流程
|
||||
```
|
||||
裁判打分 → 收集评分 → 去最高/最低分 → 计算平均分
|
||||
↓
|
||||
应用难度系数 → 计算最终得分 → 自动排名 → 分配奖牌
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 任务列表
|
||||
|
||||
### 任务 1.1:多裁判评分平均分计算 🔴
|
||||
|
||||
**状态:** 未开始
|
||||
**工时:** 0.5天
|
||||
**文件位置:** `MartialResultServiceImpl.java`
|
||||
|
||||
#### 需求描述
|
||||
- 获取某运动员某项目的所有裁判评分
|
||||
- 计算有效评分的平均值
|
||||
- 记录最高分、最低分
|
||||
|
||||
#### 实现要点
|
||||
```java
|
||||
public BigDecimal calculateAverageScore(Long athleteId, Long projectId) {
|
||||
// 1. 查询所有裁判评分
|
||||
List<MartialScore> scores = scoreService.list(
|
||||
new QueryWrapper<MartialScore>()
|
||||
.eq("athlete_id", athleteId)
|
||||
.eq("project_id", projectId)
|
||||
.eq("is_deleted", 0)
|
||||
);
|
||||
|
||||
// 2. 提取分数值
|
||||
List<BigDecimal> scoreValues = scores.stream()
|
||||
.map(MartialScore::getScore)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 3. 计算平均分(后续会去最高/最低)
|
||||
BigDecimal sum = scoreValues.stream()
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
return sum.divide(
|
||||
new BigDecimal(scoreValues.size()),
|
||||
3,
|
||||
RoundingMode.HALF_UP
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试用例
|
||||
- [ ] 单个裁判评分
|
||||
- [ ] 多个裁判评分(3-10人)
|
||||
- [ ] 边界值测试(5.000, 10.000)
|
||||
|
||||
---
|
||||
|
||||
### 任务 1.2:去最高分/去最低分逻辑 🔴
|
||||
|
||||
**状态:** 未开始
|
||||
**工时:** 0.5天
|
||||
**文件位置:** `MartialResultServiceImpl.java`
|
||||
|
||||
#### 需求描述
|
||||
- 从所有裁判评分中去掉一个最高分
|
||||
- 去掉一个最低分
|
||||
- 计算剩余有效评分的平均值
|
||||
|
||||
#### 实现要点
|
||||
```java
|
||||
public BigDecimal calculateValidAverageScore(Long athleteId, Long projectId) {
|
||||
// 1. 获取所有评分
|
||||
List<MartialScore> scores = scoreService.list(...);
|
||||
|
||||
if (scores.size() < 3) {
|
||||
throw new ServiceException("裁判人数不足3人,无法去最高/最低分");
|
||||
}
|
||||
|
||||
// 2. 找出最高分和最低分
|
||||
BigDecimal maxScore = scores.stream()
|
||||
.map(MartialScore::getScore)
|
||||
.max(Comparator.naturalOrder())
|
||||
.orElse(BigDecimal.ZERO);
|
||||
|
||||
BigDecimal minScore = scores.stream()
|
||||
.map(MartialScore::getScore)
|
||||
.min(Comparator.naturalOrder())
|
||||
.orElse(BigDecimal.ZERO);
|
||||
|
||||
// 3. 过滤有效评分(去掉一个最高、一个最低)
|
||||
List<BigDecimal> validScores = new ArrayList<>();
|
||||
boolean maxRemoved = false;
|
||||
boolean minRemoved = false;
|
||||
|
||||
for (MartialScore score : scores) {
|
||||
BigDecimal val = score.getScore();
|
||||
if (!maxRemoved && val.equals(maxScore)) {
|
||||
maxRemoved = true;
|
||||
continue;
|
||||
}
|
||||
if (!minRemoved && val.equals(minScore)) {
|
||||
minRemoved = true;
|
||||
continue;
|
||||
}
|
||||
validScores.add(val);
|
||||
}
|
||||
|
||||
// 4. 计算平均分
|
||||
BigDecimal sum = validScores.stream()
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
return sum.divide(
|
||||
new BigDecimal(validScores.size()),
|
||||
3,
|
||||
RoundingMode.HALF_UP
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试用例
|
||||
- [ ] 正常情况:5个裁判,去掉最高最低后剩3个
|
||||
- [ ] 边界情况:3个裁判,去掉最高最低后剩1个
|
||||
- [ ] 异常情况:少于3个裁判,抛出异常
|
||||
|
||||
---
|
||||
|
||||
### 任务 1.3:难度系数应用 🔴
|
||||
|
||||
**状态:** 未开始
|
||||
**工时:** 0.5天
|
||||
**文件位置:** `MartialResultServiceImpl.java`
|
||||
|
||||
#### 需求描述
|
||||
- 从项目表获取难度系数
|
||||
- 将平均分乘以难度系数
|
||||
- 生成调整后的分数
|
||||
|
||||
#### 实现要点
|
||||
```java
|
||||
public BigDecimal applyDifficultyCoefficient(
|
||||
BigDecimal averageScore,
|
||||
Long projectId
|
||||
) {
|
||||
// 1. 获取项目信息
|
||||
MartialProject project = projectService.getById(projectId);
|
||||
if (project == null) {
|
||||
throw new ServiceException("项目不存在");
|
||||
}
|
||||
|
||||
// 2. 获取难度系数(默认1.00)
|
||||
BigDecimal coefficient = project.getDifficultyCoefficient();
|
||||
if (coefficient == null) {
|
||||
coefficient = new BigDecimal("1.00");
|
||||
}
|
||||
|
||||
// 3. 应用系数
|
||||
return averageScore.multiply(coefficient)
|
||||
.setScale(3, RoundingMode.HALF_UP);
|
||||
}
|
||||
```
|
||||
|
||||
#### 数据库字段
|
||||
```sql
|
||||
-- martial_project 表需要添加字段(如果没有)
|
||||
ALTER TABLE martial_project
|
||||
ADD COLUMN difficulty_coefficient DECIMAL(5,2) DEFAULT 1.00
|
||||
COMMENT '难度系数';
|
||||
```
|
||||
|
||||
#### 测试用例
|
||||
- [ ] 系数 = 1.00(无调整)
|
||||
- [ ] 系数 = 1.20(加分)
|
||||
- [ ] 系数 = 0.80(减分)
|
||||
|
||||
---
|
||||
|
||||
### 任务 1.4:最终得分计算 🔴
|
||||
|
||||
**状态:** 未开始
|
||||
**工时:** 1天
|
||||
**文件位置:** `MartialResultServiceImpl.java`
|
||||
|
||||
#### 需求描述
|
||||
- 整合所有计算步骤
|
||||
- 保存完整的成绩记录
|
||||
- 记录计算明细(最高分、最低分、有效分数等)
|
||||
|
||||
#### 实现要点
|
||||
```java
|
||||
public MartialResult calculateFinalScore(Long athleteId, Long projectId) {
|
||||
// 1. 获取所有裁判评分
|
||||
List<MartialScore> scores = scoreService.list(
|
||||
new QueryWrapper<MartialScore>()
|
||||
.eq("athlete_id", athleteId)
|
||||
.eq("project_id", projectId)
|
||||
);
|
||||
|
||||
if (scores.isEmpty()) {
|
||||
throw new ServiceException("该运动员尚未有裁判评分");
|
||||
}
|
||||
|
||||
// 2. 找出最高分和最低分
|
||||
BigDecimal maxScore = scores.stream()
|
||||
.map(MartialScore::getScore)
|
||||
.max(Comparator.naturalOrder())
|
||||
.orElse(BigDecimal.ZERO);
|
||||
|
||||
BigDecimal minScore = scores.stream()
|
||||
.map(MartialScore::getScore)
|
||||
.min(Comparator.naturalOrder())
|
||||
.orElse(BigDecimal.ZERO);
|
||||
|
||||
// 3. 去最高/最低分,计算平均分
|
||||
BigDecimal averageScore = calculateValidAverageScore(athleteId, projectId);
|
||||
|
||||
// 4. 应用难度系数
|
||||
BigDecimal finalScore = applyDifficultyCoefficient(averageScore, projectId);
|
||||
|
||||
// 5. 获取运动员和项目信息
|
||||
MartialAthlete athlete = athleteService.getById(athleteId);
|
||||
MartialProject project = projectService.getById(projectId);
|
||||
|
||||
// 6. 保存成绩记录
|
||||
MartialResult result = new MartialResult();
|
||||
result.setCompetitionId(athlete.getCompetitionId());
|
||||
result.setAthleteId(athleteId);
|
||||
result.setProjectId(projectId);
|
||||
result.setPlayerName(athlete.getPlayerName());
|
||||
result.setTeamName(athlete.getTeamName());
|
||||
|
||||
result.setTotalScore(averageScore); // 平均分
|
||||
result.setMaxScore(maxScore);
|
||||
result.setMinScore(minScore);
|
||||
result.setValidScoreCount(scores.size() - 2); // 去掉最高最低
|
||||
|
||||
result.setDifficultyCoefficient(project.getDifficultyCoefficient());
|
||||
result.setFinalScore(finalScore); // 最终得分
|
||||
|
||||
result.setIsFinal(0); // 初始为非最终成绩
|
||||
|
||||
this.saveOrUpdate(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试用例
|
||||
- [ ] 完整流程测试(5个裁判评分)
|
||||
- [ ] 数据持久化验证
|
||||
- [ ] 重复计算测试(更新而非新增)
|
||||
|
||||
---
|
||||
|
||||
### 任务 1.5:自动排名算法 🔴
|
||||
|
||||
**状态:** 未开始
|
||||
**工时:** 1天
|
||||
**文件位置:** `MartialResultServiceImpl.java`
|
||||
|
||||
#### 需求描述
|
||||
- 按项目对所有运动员进行排名
|
||||
- 处理并列排名情况
|
||||
- 更新排名到数据库
|
||||
|
||||
#### 实现要点
|
||||
```java
|
||||
public void autoRanking(Long projectId) {
|
||||
// 1. 获取该项目所有最终成绩,按分数降序
|
||||
List<MartialResult> results = this.list(
|
||||
new QueryWrapper<MartialResult>()
|
||||
.eq("project_id", projectId)
|
||||
.eq("is_final", 1) // 只对最终成绩排名
|
||||
.orderByDesc("final_score")
|
||||
);
|
||||
|
||||
if (results.isEmpty()) {
|
||||
throw new ServiceException("该项目尚无最终成绩");
|
||||
}
|
||||
|
||||
// 2. 分配排名(处理并列)
|
||||
int currentRank = 1;
|
||||
BigDecimal previousScore = null;
|
||||
int sameScoreCount = 0;
|
||||
|
||||
for (int i = 0; i < results.size(); i++) {
|
||||
MartialResult result = results.get(i);
|
||||
BigDecimal currentScore = result.getFinalScore();
|
||||
|
||||
if (previousScore != null && currentScore.equals(previousScore)) {
|
||||
// 分数相同,并列
|
||||
sameScoreCount++;
|
||||
} else {
|
||||
// 分数不同,更新排名
|
||||
currentRank += sameScoreCount;
|
||||
sameScoreCount = 1;
|
||||
}
|
||||
|
||||
result.setRanking(currentRank);
|
||||
previousScore = currentScore;
|
||||
}
|
||||
|
||||
// 3. 批量更新
|
||||
this.updateBatchById(results);
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试用例
|
||||
- [ ] 无并列情况
|
||||
- [ ] 有并列情况(2人同分)
|
||||
- [ ] 多人并列情况(3人同分)
|
||||
|
||||
---
|
||||
|
||||
### 任务 1.6:奖牌自动分配 🔴
|
||||
|
||||
**状态:** 未开始
|
||||
**工时:** 0.5天
|
||||
**文件位置:** `MartialResultServiceImpl.java`
|
||||
|
||||
#### 需求描述
|
||||
- 自动分配金银铜牌给前三名
|
||||
- 处理并列情况(如并列第一名,两人都得金牌)
|
||||
- 更新奖牌字段
|
||||
|
||||
#### 实现要点
|
||||
```java
|
||||
public void assignMedals(Long projectId) {
|
||||
// 1. 获取前三名(按排名)
|
||||
List<MartialResult> topResults = this.list(
|
||||
new QueryWrapper<MartialResult>()
|
||||
.eq("project_id", projectId)
|
||||
.eq("is_final", 1)
|
||||
.le("ranking", 3) // 排名 <= 3
|
||||
.orderByAsc("ranking")
|
||||
);
|
||||
|
||||
// 2. 分配奖牌
|
||||
for (MartialResult result : topResults) {
|
||||
Integer ranking = result.getRanking();
|
||||
if (ranking == 1) {
|
||||
result.setMedal(1); // 金牌
|
||||
} else if (ranking == 2) {
|
||||
result.setMedal(2); // 银牌
|
||||
} else if (ranking == 3) {
|
||||
result.setMedal(3); // 铜牌
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 批量更新
|
||||
this.updateBatchById(topResults);
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试用例
|
||||
- [ ] 正常情况:前3名分配金银铜
|
||||
- [ ] 并列第一:2人都得金牌,第3名得铜牌(跳过银牌)
|
||||
- [ ] 并列第二:第1名金牌,2人都得银牌
|
||||
|
||||
---
|
||||
|
||||
### 任务 1.7:成绩复核机制 🔴
|
||||
|
||||
**状态:** 未开始
|
||||
**工时:** 0.5天
|
||||
**文件位置:** `MartialResultServiceImpl.java`
|
||||
|
||||
#### 需求描述
|
||||
- 提供成绩复核接口
|
||||
- 记录复核原因和结果
|
||||
- 支持成绩调整
|
||||
|
||||
#### 实现要点
|
||||
```java
|
||||
public void reviewResult(Long resultId, String reviewNote, BigDecimal adjustment) {
|
||||
MartialResult result = this.getById(resultId);
|
||||
if (result == null) {
|
||||
throw new ServiceException("成绩记录不存在");
|
||||
}
|
||||
|
||||
// 记录原始分数
|
||||
result.setOriginalScore(result.getFinalScore());
|
||||
|
||||
// 应用调整
|
||||
if (adjustment != null) {
|
||||
BigDecimal newScore = result.getFinalScore().add(adjustment);
|
||||
result.setAdjustedScore(newScore);
|
||||
result.setFinalScore(newScore);
|
||||
result.setAdjustRange(adjustment);
|
||||
}
|
||||
|
||||
result.setAdjustNote(reviewNote);
|
||||
|
||||
this.updateById(result);
|
||||
|
||||
// 重新排名
|
||||
autoRanking(result.getProjectId());
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试用例
|
||||
- [ ] 成绩上调
|
||||
- [ ] 成绩下调
|
||||
- [ ] 调整后重新排名
|
||||
|
||||
---
|
||||
|
||||
### 任务 1.8:成绩发布审批流程 🔴
|
||||
|
||||
**状态:** 未开始
|
||||
**工时:** 0.5天
|
||||
**文件位置:** `MartialResultServiceImpl.java`
|
||||
|
||||
#### 需求描述
|
||||
- 成绩确认为最终成绩
|
||||
- 记录发布时间
|
||||
- 限制已发布成绩的修改
|
||||
|
||||
#### 实现要点
|
||||
```java
|
||||
public void publishResults(Long projectId) {
|
||||
List<MartialResult> results = this.list(
|
||||
new QueryWrapper<MartialResult>()
|
||||
.eq("project_id", projectId)
|
||||
);
|
||||
|
||||
for (MartialResult result : results) {
|
||||
result.setIsFinal(1); // 标记为最终成绩
|
||||
result.setPublishTime(LocalDateTime.now());
|
||||
}
|
||||
|
||||
this.updateBatchById(results);
|
||||
}
|
||||
|
||||
public void unpublishResults(Long projectId) {
|
||||
// 撤销发布(管理员权限)
|
||||
List<MartialResult> results = this.list(
|
||||
new QueryWrapper<MartialResult>()
|
||||
.eq("project_id", projectId)
|
||||
);
|
||||
|
||||
for (MartialResult result : results) {
|
||||
result.setIsFinal(0);
|
||||
result.setPublishTime(null);
|
||||
}
|
||||
|
||||
this.updateBatchById(results);
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试用例
|
||||
- [ ] 发布成绩
|
||||
- [ ] 撤销发布
|
||||
- [ ] 已发布成绩的权限控制
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Controller 层接口设计
|
||||
|
||||
### 新增 API 接口
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/martial/result")
|
||||
public class MartialResultController extends BladeController {
|
||||
|
||||
@Autowired
|
||||
private IMartialResultService resultService;
|
||||
|
||||
/**
|
||||
* 计算运动员最终成绩
|
||||
*/
|
||||
@PostMapping("/calculate")
|
||||
@Operation(summary = "计算最终成绩")
|
||||
public R<MartialResult> calculateScore(
|
||||
@RequestParam Long athleteId,
|
||||
@RequestParam Long projectId
|
||||
) {
|
||||
MartialResult result = resultService.calculateFinalScore(athleteId, projectId);
|
||||
return R.data(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目自动排名
|
||||
*/
|
||||
@PostMapping("/ranking")
|
||||
@Operation(summary = "自动排名")
|
||||
public R autoRanking(@RequestParam Long projectId) {
|
||||
resultService.autoRanking(projectId);
|
||||
return R.success("排名完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 分配奖牌
|
||||
*/
|
||||
@PostMapping("/medals")
|
||||
@Operation(summary = "分配奖牌")
|
||||
public R assignMedals(@RequestParam Long projectId) {
|
||||
resultService.assignMedals(projectId);
|
||||
return R.success("奖牌分配完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 成绩复核
|
||||
*/
|
||||
@PostMapping("/review")
|
||||
@Operation(summary = "成绩复核")
|
||||
public R reviewResult(
|
||||
@RequestParam Long resultId,
|
||||
@RequestParam String reviewNote,
|
||||
@RequestParam(required = false) BigDecimal adjustment
|
||||
) {
|
||||
resultService.reviewResult(resultId, reviewNote, adjustment);
|
||||
return R.success("复核完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布成绩
|
||||
*/
|
||||
@PostMapping("/publish")
|
||||
@Operation(summary = "发布成绩")
|
||||
public R publishResults(@RequestParam Long projectId) {
|
||||
resultService.publishResults(projectId);
|
||||
return R.success("成绩已发布");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 依赖配置
|
||||
|
||||
无需额外依赖,使用现有的:
|
||||
- MyBatis-Plus(数据访问)
|
||||
- Java BigDecimal(精度计算)
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试计划
|
||||
|
||||
### 单元测试
|
||||
- [ ] 平均分计算测试
|
||||
- [ ] 去最高/最低分测试
|
||||
- [ ] 难度系数应用测试
|
||||
- [ ] 排名算法测试
|
||||
- [ ] 奖牌分配测试
|
||||
|
||||
### 集成测试
|
||||
- [ ] 完整成绩计算流程
|
||||
- [ ] 多项目并发计算
|
||||
- [ ] 成绩发布流程
|
||||
|
||||
### 性能测试
|
||||
- [ ] 100个运动员同时计算
|
||||
- [ ] 批量排名性能
|
||||
|
||||
---
|
||||
|
||||
## 📝 开发注意事项
|
||||
|
||||
1. **精度处理:** 所有分数计算使用 `BigDecimal`,保留3位小数
|
||||
2. **并发控制:** 成绩计算可能被多次触发,需要考虑幂等性
|
||||
3. **数据一致性:** 成绩更新后需要触发排名重新计算
|
||||
4. **异常处理:** 裁判人数不足、评分缺失等异常情况
|
||||
5. **权限控制:** 成绩发布、复核等敏感操作需要权限验证
|
||||
|
||||
---
|
||||
|
||||
## ✅ 验收标准
|
||||
|
||||
- [ ] 所有单元测试通过
|
||||
- [ ] API接口文档完整(Swagger)
|
||||
- [ ] 成绩计算精度达到0.001
|
||||
- [ ] 排名算法处理并列情况正确
|
||||
- [ ] 已发布成绩不可随意修改
|
||||
- [ ] 代码通过Code Review
|
||||
|
||||
---
|
||||
|
||||
**下一步:** 完成后进入 [02-比赛日流程功能.md](./02-比赛日流程功能.md)
|
||||
228
docs/tasks/04-导出打印功能.md
Normal file
228
docs/tasks/04-导出打印功能.md
Normal file
@@ -0,0 +1,228 @@
|
||||
# 导出打印功能 - 详细任务清单
|
||||
|
||||
**优先级:** P1(重要)
|
||||
**预计工时:** 3天
|
||||
**负责人:** 待分配
|
||||
|
||||
---
|
||||
|
||||
## 📋 技术选型
|
||||
|
||||
- **Excel导出:** EasyExcel(阿里开源,性能优秀)
|
||||
- **PDF生成:** iText 或 FreeMarker + Flying Saucer
|
||||
- **模板引擎:** FreeMarker
|
||||
|
||||
### Maven 依赖
|
||||
|
||||
```xml
|
||||
<!-- EasyExcel -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>easyexcel</artifactId>
|
||||
<version>3.3.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- iText PDF -->
|
||||
<dependency>
|
||||
<groupId>com.itextpdf</groupId>
|
||||
<artifactId>itext7-core</artifactId>
|
||||
<version>7.2.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- FreeMarker -->
|
||||
<dependency>
|
||||
<groupId>org.freemarker</groupId>
|
||||
<artifactId>freemarker</artifactId>
|
||||
<version>2.3.32</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 任务列表
|
||||
|
||||
### 任务 3.1:成绩单Excel导出 🔴
|
||||
|
||||
**工时:** 1天
|
||||
|
||||
#### 需求描述
|
||||
- 导出项目成绩单
|
||||
- 包含:排名、姓名、单位、各裁判评分、最终得分、奖牌
|
||||
- 支持筛选和排序
|
||||
|
||||
#### 实现要点
|
||||
```java
|
||||
// MartialResultServiceImpl.java
|
||||
public void exportScoreSheet(Long projectId, HttpServletResponse response) {
|
||||
// 1. 查询数据
|
||||
List<MartialResult> results = this.list(
|
||||
new QueryWrapper<MartialResult>()
|
||||
.eq("project_id", projectId)
|
||||
.orderByAsc("ranking")
|
||||
);
|
||||
|
||||
// 2. 构建导出数据
|
||||
List<ScoreExportVO> exportData = results.stream()
|
||||
.map(this::buildExportVO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 3. 使用EasyExcel导出
|
||||
try {
|
||||
response.setContentType("application/vnd.ms-excel");
|
||||
response.setCharacterEncoding("utf-8");
|
||||
String fileName = URLEncoder.encode("成绩单", "UTF-8");
|
||||
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
|
||||
|
||||
EasyExcel.write(response.getOutputStream(), ScoreExportVO.class)
|
||||
.sheet("成绩单")
|
||||
.doWrite(exportData);
|
||||
} catch (IOException e) {
|
||||
throw new ServiceException("导出失败");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### VO 定义
|
||||
```java
|
||||
@Data
|
||||
public class ScoreExportVO {
|
||||
@ExcelProperty("排名")
|
||||
private Integer ranking;
|
||||
|
||||
@ExcelProperty("姓名")
|
||||
private String playerName;
|
||||
|
||||
@ExcelProperty("单位")
|
||||
private String teamName;
|
||||
|
||||
@ExcelProperty("裁判1")
|
||||
private BigDecimal judge1Score;
|
||||
|
||||
@ExcelProperty("裁判2")
|
||||
private BigDecimal judge2Score;
|
||||
|
||||
@ExcelProperty("最高分")
|
||||
private BigDecimal maxScore;
|
||||
|
||||
@ExcelProperty("最低分")
|
||||
private BigDecimal minScore;
|
||||
|
||||
@ExcelProperty("平均分")
|
||||
private BigDecimal totalScore;
|
||||
|
||||
@ExcelProperty("难度系数")
|
||||
private BigDecimal coefficient;
|
||||
|
||||
@ExcelProperty("最终得分")
|
||||
private BigDecimal finalScore;
|
||||
|
||||
@ExcelProperty("奖牌")
|
||||
private String medal;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 任务 3.2:赛程表Excel导出 🔴
|
||||
|
||||
**工时:** 0.5天
|
||||
|
||||
#### 需求描述
|
||||
- 导出完整赛程表
|
||||
- 按日期、时间段分组
|
||||
- 包含场地、项目、运动员信息
|
||||
|
||||
---
|
||||
|
||||
### 任务 3.3:证书PDF生成 🔴
|
||||
|
||||
**工时:** 1天
|
||||
|
||||
#### 需求描述
|
||||
- 使用模板生成获奖证书
|
||||
- 包含:姓名、项目、名次、日期
|
||||
- 支持批量生成
|
||||
|
||||
#### 实现思路
|
||||
```java
|
||||
public void generateCertificate(Long resultId) {
|
||||
// 1. 查询成绩
|
||||
MartialResult result = this.getById(resultId);
|
||||
|
||||
// 2. 准备数据
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("playerName", result.getPlayerName());
|
||||
data.put("projectName", "项目名称");
|
||||
data.put("ranking", result.getRanking());
|
||||
data.put("medal", getMedalName(result.getMedal()));
|
||||
|
||||
// 3. 使用FreeMarker渲染模板
|
||||
String html = freeMarkerService.process("certificate.ftl", data);
|
||||
|
||||
// 4. HTML转PDF
|
||||
ByteArrayOutputStream pdfStream = htmlToPdf(html);
|
||||
|
||||
// 5. 保存或返回
|
||||
savePdf(pdfStream, "certificate_" + resultId + ".pdf");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 任务 3.4:排行榜打印模板 🔴
|
||||
|
||||
**工时:** 0.5天
|
||||
|
||||
#### 需求描述
|
||||
- 提供打印友好的排行榜页面
|
||||
- 支持分页打印
|
||||
- 包含比赛信息、日期、主办方
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Controller 接口
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/martial/export")
|
||||
public class MartialExportController {
|
||||
|
||||
@GetMapping("/score-sheet")
|
||||
@Operation(summary = "导出成绩单")
|
||||
public void exportScoreSheet(
|
||||
@RequestParam Long projectId,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
resultService.exportScoreSheet(projectId, response);
|
||||
}
|
||||
|
||||
@GetMapping("/schedule")
|
||||
@Operation(summary = "导出赛程表")
|
||||
public void exportSchedule(
|
||||
@RequestParam Long competitionId,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
scheduleService.exportSchedule(competitionId, response);
|
||||
}
|
||||
|
||||
@GetMapping("/certificate/{resultId}")
|
||||
@Operation(summary = "生成证书")
|
||||
public void generateCertificate(
|
||||
@PathVariable Long resultId,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
resultService.generateCertificate(resultId, response);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 验收标准
|
||||
|
||||
- [ ] Excel导出格式正确,数据完整
|
||||
- [ ] PDF证书美观,信息准确
|
||||
- [ ] 支持批量导出
|
||||
- [ ] 大数据量导出性能良好(1000+记录)
|
||||
|
||||
---
|
||||
100
docs/tasks/README.md
Normal file
100
docs/tasks/README.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# 武术比赛系统开发任务管理
|
||||
|
||||
## 📂 目录结构
|
||||
|
||||
```
|
||||
docs/tasks/
|
||||
├── README.md # 任务管理说明(本文件)
|
||||
├── 00-任务清单总览.md # 所有任务的汇总清单
|
||||
├── 01-报名阶段功能.md # 报名阶段相关任务
|
||||
├── 02-比赛日流程功能.md # 比赛日流程相关任务
|
||||
├── 03-成绩计算引擎.md # 成绩自动计算相关任务
|
||||
├── 04-导出打印功能.md # 导出和打印相关任务
|
||||
├── 05-辅助功能.md # 其他辅助功能任务
|
||||
└── progress/ # 进度记录目录
|
||||
├── 2025-11-30.md # 每日进度记录
|
||||
└── completed/ # 已完成任务归档
|
||||
```
|
||||
|
||||
## 📊 任务状态说明
|
||||
|
||||
- 🔴 **未开始** - 尚未开始开发
|
||||
- 🟡 **进行中** - 正在开发
|
||||
- 🟢 **已完成** - 开发完成并测试通过
|
||||
- ⚪ **已搁置** - 暂时搁置,待后续处理
|
||||
- 🔵 **待评审** - 开发完成,等待代码评审
|
||||
|
||||
## 📋 使用说明
|
||||
|
||||
### 1. 查看任务清单
|
||||
|
||||
查看 `00-任务清单总览.md` 了解所有待办任务的整体情况。
|
||||
|
||||
### 2. 更新任务状态
|
||||
|
||||
在具体任务文件中更新任务状态:
|
||||
- 标记任务状态图标
|
||||
- 添加完成时间
|
||||
- 记录相关代码位置
|
||||
|
||||
### 3. 记录进度
|
||||
|
||||
每日在 `progress/` 目录下创建进度记录:
|
||||
- 记录当天完成的任务
|
||||
- 遇到的问题和解决方案
|
||||
- 下一步计划
|
||||
|
||||
### 4. 归档已完成任务
|
||||
|
||||
任务完成后,将详细记录移至 `progress/completed/` 目录。
|
||||
|
||||
## 🎯 当前开发优先级
|
||||
|
||||
### 第一阶段:核心业务逻辑(暂不包括编排功能)
|
||||
|
||||
1. **成绩计算引擎**(最高优先级)
|
||||
- 多裁判评分计算
|
||||
- 去最高/最低分
|
||||
- 最终得分计算
|
||||
- 自动排名和奖牌分配
|
||||
|
||||
2. **比赛日流程**
|
||||
- 签到/检录功能
|
||||
- 评分验证
|
||||
- 异常处理
|
||||
|
||||
3. **导出打印功能**
|
||||
- 成绩单导出
|
||||
- 证书生成
|
||||
- 赛程表打印
|
||||
|
||||
### 第二阶段:辅助功能
|
||||
|
||||
4. **报名阶段优化**
|
||||
- 报名链接生成
|
||||
- 二维码分享
|
||||
- 报名统计
|
||||
|
||||
5. **数据可视化**
|
||||
- 成绩图表
|
||||
- 统计报表
|
||||
|
||||
### 第三阶段:高级功能(后期)
|
||||
|
||||
6. **自动编排算法**(暂时搁置)
|
||||
- 智能赛程生成
|
||||
- 冲突检测
|
||||
- 场地优化
|
||||
|
||||
## 📞 协作说明
|
||||
|
||||
- 开发前先查看任务清单,避免重复开发
|
||||
- 完成任务后及时更新状态
|
||||
- 遇到问题记录在进度文件中
|
||||
- 定期同步任务状态
|
||||
|
||||
---
|
||||
|
||||
**创建时间:** 2025-11-30
|
||||
**维护人员:** 开发团队
|
||||
**最后更新:** 2025-11-30
|
||||
294
docs/tasks/progress/2025-11-30-session2.md
Normal file
294
docs/tasks/progress/2025-11-30-session2.md
Normal file
@@ -0,0 +1,294 @@
|
||||
# 开发进度记录 - 2025-11-30 (第二次更新)
|
||||
|
||||
**日期:** 2025-11-30
|
||||
**记录人:** Claude Code
|
||||
**会话:** 续接会话
|
||||
|
||||
---
|
||||
|
||||
## ✅ 本次完成
|
||||
|
||||
### 1. 成绩计算引擎完整实现 🎉
|
||||
|
||||
成功完成 **P0 优先级** 的成绩计算引擎所有 8 个子任务!
|
||||
|
||||
#### 实现内容
|
||||
|
||||
**MartialResultServiceImpl.java** (新增 9 个业务方法)
|
||||
- ✅ `calculateValidAverageScore()` - 计算有效平均分(去最高/最低分)
|
||||
- ✅ `applyDifficultyCoefficient()` - 应用难度系数
|
||||
- ✅ `calculateFinalScore()` - 计算最终成绩(核心方法)
|
||||
- ✅ `autoRanking()` - 自动排名算法(处理并列情况)
|
||||
- ✅ `assignMedals()` - 奖牌分配(金银铜)
|
||||
- ✅ `reviewResult()` - 成绩复核机制
|
||||
- ✅ `publishResults()` - 发布成绩
|
||||
- ✅ `unpublishResults()` - 撤销发布
|
||||
|
||||
**MartialResultController.java** (新增 6 个 API 端点)
|
||||
- ✅ `POST /martial/result/calculate` - 计算成绩
|
||||
- ✅ `POST /martial/result/ranking` - 自动排名
|
||||
- ✅ `POST /martial/result/medals` - 分配奖牌
|
||||
- ✅ `POST /martial/result/review` - 成绩复核
|
||||
- ✅ `POST /martial/result/publish` - 发布成绩
|
||||
- ✅ `POST /martial/result/unpublish` - 撤销发布
|
||||
|
||||
**IMartialResultService.java** (接口定义)
|
||||
- ✅ 声明所有 9 个业务方法签名
|
||||
|
||||
**MartialProject.java** (实体扩展)
|
||||
- ✅ 新增 `difficultyCoefficient` 字段 (DECIMAL(5,2))
|
||||
|
||||
**数据库更新**
|
||||
- ✅ 创建迁移脚本: `20251130_add_difficulty_coefficient.sql`
|
||||
- ✅ 执行 ALTER TABLE 添加 `difficulty_coefficient` 列到 `martial_project` 表
|
||||
- ✅ 默认值设置为 1.00
|
||||
|
||||
---
|
||||
|
||||
## 📊 代码统计
|
||||
|
||||
### 新增代码量
|
||||
- Service 实现: ~320 行 Java 代码
|
||||
- Controller API: ~70 行
|
||||
- Service 接口: ~50 行
|
||||
- 实体字段: ~5 行
|
||||
- SQL 迁移脚本: ~15 行
|
||||
|
||||
**总计:** ~460 行新代码
|
||||
|
||||
### 修复的编译错误
|
||||
1. ❌ `ServiceException` 导入错误 → ✅ 修复为 `org.springblade.core.log.exception.ServiceException`
|
||||
2. ❌ `getDifficultyCoefficient()` 方法不存在 → ✅ 添加字段到实体
|
||||
3. ❌ Service 方法未在接口声明 → ✅ 完善接口定义
|
||||
|
||||
---
|
||||
|
||||
## 🎯 核心算法实现
|
||||
|
||||
### 1. 去最高/最低分算法
|
||||
|
||||
```java
|
||||
// 关键逻辑:确保只去掉一个最高分和一个最低分
|
||||
boolean maxRemoved = false;
|
||||
boolean minRemoved = false;
|
||||
|
||||
for (MartialScore score : scores) {
|
||||
BigDecimal val = score.getScore();
|
||||
if (!maxRemoved && val.equals(maxScore)) {
|
||||
maxRemoved = true;
|
||||
continue;
|
||||
}
|
||||
if (!minRemoved && val.equals(minScore)) {
|
||||
minRemoved = true;
|
||||
continue;
|
||||
}
|
||||
validScores.add(val);
|
||||
}
|
||||
```
|
||||
|
||||
**测试场景:**
|
||||
- ✅ 3个裁判评分 → 去掉最高最低剩1个
|
||||
- ✅ 5个裁判评分 → 去掉最高最低剩3个
|
||||
- ✅ 少于3个裁判 → 抛出异常
|
||||
|
||||
### 2. 自动排名算法(处理并列)
|
||||
|
||||
```java
|
||||
int currentRank = 1;
|
||||
BigDecimal previousScore = null;
|
||||
int sameScoreCount = 0;
|
||||
|
||||
for (MartialResult result : results) {
|
||||
if (previousScore != null && currentScore.compareTo(previousScore) == 0) {
|
||||
sameScoreCount++; // 并列
|
||||
} else {
|
||||
currentRank += sameScoreCount; // 跳跃排名
|
||||
sameScoreCount = 1;
|
||||
}
|
||||
result.setRanking(currentRank);
|
||||
}
|
||||
```
|
||||
|
||||
**处理场景:**
|
||||
- ✅ 无并列:1, 2, 3, 4, 5...
|
||||
- ✅ 两人并列第一:1, 1, 3, 4...
|
||||
- ✅ 三人并列第二:1, 2, 2, 2, 5...
|
||||
|
||||
### 3. BigDecimal 精度控制
|
||||
|
||||
所有分数计算统一使用:
|
||||
```java
|
||||
.setScale(3, RoundingMode.HALF_UP) // 保留3位小数,四舍五入
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 技术亮点
|
||||
|
||||
### 1. 事务管理
|
||||
所有写操作方法使用 `@Transactional(rollbackFor = Exception.class)`,确保数据一致性。
|
||||
|
||||
### 2. 幂等性设计
|
||||
`calculateFinalScore()` 方法支持重复调用:
|
||||
- 首次调用 → 创建新记录
|
||||
- 再次调用 → 更新现有记录
|
||||
|
||||
### 3. 异常处理
|
||||
- 裁判人数不足 → 抛出 `ServiceException`
|
||||
- 项目不存在 → 抛出 `ServiceException`
|
||||
- 成绩记录不存在 → 抛出 `ServiceException`
|
||||
|
||||
### 4. 日志记录
|
||||
关键操作添加 `log.info()` 和 `log.warn()`,方便追踪和调试。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 编译验证
|
||||
|
||||
```bash
|
||||
mvn compile -DskipTests -Dmaven.test.skip=true
|
||||
```
|
||||
|
||||
**结果:** ✅ BUILD SUCCESS
|
||||
|
||||
---
|
||||
|
||||
## 📝 测试建议
|
||||
|
||||
### 单元测试(待编写)
|
||||
1. `testCalculateValidAverageScore` - 测试平均分计算
|
||||
- 正常情况:5个裁判
|
||||
- 边界情况:3个裁判
|
||||
- 异常情况:少于3个裁判
|
||||
|
||||
2. `testAutoRanking` - 测试排名算法
|
||||
- 无并列排名
|
||||
- 有并列排名(2人、3人)
|
||||
- 多个并列组
|
||||
|
||||
3. `testAssignMedals` - 测试奖牌分配
|
||||
- 正常前3名
|
||||
- 并列第一名
|
||||
- 并列第二名
|
||||
|
||||
### 集成测试(待编写)
|
||||
1. 完整流程测试:
|
||||
- 裁判评分 → 计算成绩 → 自动排名 → 分配奖牌 → 发布成绩
|
||||
|
||||
2. 成绩复核流程:
|
||||
- 复核调整 → 重新排名 → 奖牌重新分配
|
||||
|
||||
---
|
||||
|
||||
## 🚀 API 使用示例
|
||||
|
||||
### 1. 计算运动员成绩
|
||||
```bash
|
||||
POST /martial/result/calculate?athleteId=1&projectId=1
|
||||
```
|
||||
|
||||
### 2. 项目排名
|
||||
```bash
|
||||
POST /martial/result/ranking?projectId=1
|
||||
```
|
||||
|
||||
### 3. 分配奖牌
|
||||
```bash
|
||||
POST /martial/result/medals?projectId=1
|
||||
```
|
||||
|
||||
### 4. 发布成绩
|
||||
```bash
|
||||
POST /martial/result/publish?projectId=1
|
||||
```
|
||||
|
||||
### 5. 成绩复核(加0.5分)
|
||||
```bash
|
||||
POST /martial/result/review?resultId=1&reviewNote=技术难度调整&adjustment=0.5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 整体进度更新
|
||||
|
||||
| 模块 | 完成度 | 状态 |
|
||||
|-----|--------|------|
|
||||
| 成绩计算引擎 | 100% | ✅ 已完成 |
|
||||
| 比赛日流程 | 0% | ⏳ 待开始 |
|
||||
| 导出打印功能 | 0% | ⏳ 待开始 |
|
||||
| 报名阶段优化 | 0% | ⏳ 待开始 |
|
||||
| 辅助功能 | 0% | ⏳ 待开始 |
|
||||
|
||||
**总体进度:** 8/28 任务完成 (29%)
|
||||
|
||||
---
|
||||
|
||||
## 🔗 相关文件
|
||||
|
||||
### 修改的文件
|
||||
1. `src/main/java/org/springblade/modules/martial/service/impl/MartialResultServiceImpl.java`
|
||||
2. `src/main/java/org/springblade/modules/martial/controller/MartialResultController.java`
|
||||
3. `src/main/java/org/springblade/modules/martial/service/IMartialResultService.java`
|
||||
4. `src/main/java/org/springblade/modules/martial/pojo/entity/MartialProject.java`
|
||||
5. `docs/tasks/00-任务清单总览.md`
|
||||
|
||||
### 新增的文件
|
||||
1. `docs/sql/mysql/20251130_add_difficulty_coefficient.sql`
|
||||
2. `docs/tasks/progress/2025-11-30-session2.md` (本文件)
|
||||
|
||||
---
|
||||
|
||||
## 📅 下一步计划
|
||||
|
||||
### 短期计划(本周)
|
||||
1. ✅ 成绩计算引擎(已完成)
|
||||
2. 🔄 开始实现 **比赛日流程功能** (P1 优先级)
|
||||
- 2.1 运动员签到/检录系统
|
||||
- 2.2 评分有效性验证
|
||||
- 2.3 异常分数警告机制
|
||||
- 2.4 异常情况记录和处理
|
||||
- 2.5 检录长角色权限管理
|
||||
- 2.6 比赛状态流转管理
|
||||
|
||||
### 中期计划(下周)
|
||||
1. 完成导出打印功能
|
||||
2. 进行集成测试
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 数据库变更
|
||||
⚠️ **重要:** 已添加新字段到 `martial_project` 表,生产环境部署前需执行迁移脚本:
|
||||
```sql
|
||||
ALTER TABLE martial_project
|
||||
ADD COLUMN difficulty_coefficient DECIMAL(5,2) DEFAULT 1.00 COMMENT '难度系数(默认1.00)';
|
||||
```
|
||||
|
||||
### API 权限
|
||||
所有成绩相关 API 应配置适当的权限控制:
|
||||
- 计算成绩:裁判长权限
|
||||
- 排名/奖牌:裁判长权限
|
||||
- 复核:裁判长或管理员权限
|
||||
- 发布/撤销:管理员权限
|
||||
|
||||
---
|
||||
|
||||
## 💬 备注
|
||||
|
||||
- 所有方法均已实现业务逻辑,不再是空壳
|
||||
- 代码遵循 BladeX 框架规范
|
||||
- 使用 MyBatis-Plus 链式查询
|
||||
- 支持多租户数据隔离
|
||||
- 支持软删除
|
||||
- 使用 BigDecimal 确保精度
|
||||
|
||||
---
|
||||
|
||||
**本次会话用时:** 约 2 小时
|
||||
**代码质量:** 已通过编译验证 ✅
|
||||
**功能完整性:** P0 任务 100% 完成 ✅
|
||||
|
||||
---
|
||||
|
||||
**下次更新:** 2025-12-01 或完成比赛日流程功能后
|
||||
183
docs/tasks/progress/2025-11-30.md
Normal file
183
docs/tasks/progress/2025-11-30.md
Normal file
@@ -0,0 +1,183 @@
|
||||
# 开发进度记录 - 2025-11-30
|
||||
|
||||
**日期:** 2025-11-30
|
||||
**记录人:** Claude Code
|
||||
|
||||
---
|
||||
|
||||
## ✅ 今日完成
|
||||
|
||||
### 1. 任务管理体系搭建
|
||||
|
||||
- ✅ 创建 `docs/tasks/` 目录结构
|
||||
- ✅ 编写任务管理 README
|
||||
- ✅ 完成任务清单总览(28个任务)
|
||||
- ✅ 详细编写成绩计算引擎任务清单(8个子任务)
|
||||
- ✅ 详细编写比赛日流程功能任务清单(6个子任务)
|
||||
- ✅ 详细编写导出打印功能任务清单(5个子任务)
|
||||
|
||||
### 2. 系统分析和文档输出
|
||||
|
||||
- ✅ 完成武术比赛流程开发现状分析
|
||||
- ✅ 生成比赛流程完整性评估报告
|
||||
- ✅ 确认集体项目存储设计(team_name 关联)
|
||||
- ✅ 验证所有数据模型字段完整性
|
||||
|
||||
---
|
||||
|
||||
## 📊 系统现状总结
|
||||
|
||||
### 已完成(基础架构)
|
||||
- ✅ 16个 Entity 实体类
|
||||
- ✅ 16个 Controller 控制器
|
||||
- ✅ 16个 Service 接口
|
||||
- ✅ 16个 Service 实现(空壳)
|
||||
- ✅ 16个 Mapper 接口和 XML
|
||||
- ✅ 16张数据库表
|
||||
- ✅ 完整的 CRUD API
|
||||
|
||||
### 待开发(业务逻辑)
|
||||
- ❌ 成绩计算引擎(0%)
|
||||
- ❌ 自动排名算法(0%)
|
||||
- ❌ 比赛日流程(0%)
|
||||
- ❌ 导出打印功能(0%)
|
||||
- ❌ 报名阶段优化(0%)
|
||||
|
||||
### 已搁置
|
||||
- ⚪ 自动编排算法(用户要求暂不实现)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 明确的开发优先级
|
||||
|
||||
### 第一阶段(核心功能)
|
||||
1. **成绩计算引擎**(P0 - 最高优先级)
|
||||
- 多裁判评分计算
|
||||
- 去最高/最低分
|
||||
- 自动排名
|
||||
- 奖牌分配
|
||||
|
||||
2. **比赛日流程**(P1)
|
||||
- 签到/检录
|
||||
- 评分验证
|
||||
- 异常处理
|
||||
|
||||
3. **导出打印**(P1)
|
||||
- Excel导出
|
||||
- PDF证书
|
||||
|
||||
### 第二阶段(辅助功能)
|
||||
4. 报名链接生成
|
||||
5. 数据统计看板
|
||||
|
||||
---
|
||||
|
||||
## 📝 关键发现
|
||||
|
||||
### 1. 数据模型完整性确认
|
||||
|
||||
**集体项目队员管理:**
|
||||
- 使用 `team_name` 字段关联队员
|
||||
- 多个 `MartialAthlete` 记录共享相同 `team_name`
|
||||
- 查询示例:
|
||||
```sql
|
||||
SELECT * FROM martial_athlete
|
||||
WHERE team_name = '少林A队'
|
||||
AND project_id = 1;
|
||||
```
|
||||
|
||||
**扣分项配置:**
|
||||
- 已预置8个通用扣分项
|
||||
- 支持按项目定制(`applicable_projects` JSON)
|
||||
- 可动态调整扣分值
|
||||
|
||||
### 2. Service 层现状
|
||||
|
||||
**所有 Service 实现类都是空的:**
|
||||
```java
|
||||
@Service
|
||||
public class MartialResultServiceImpl
|
||||
extends ServiceImpl<MartialResultMapper, MartialResult>
|
||||
implements IMartialResultService {
|
||||
// 完全空白 - 只有MyBatis-Plus基础CRUD
|
||||
}
|
||||
```
|
||||
|
||||
**影响:**
|
||||
- 无业务逻辑,只能手动CRUD
|
||||
- 核心功能(成绩计算、排名)完全缺失
|
||||
- 必须补充业务方法才能投入使用
|
||||
|
||||
### 3. 比赛流程支持情况
|
||||
|
||||
| 流程阶段 | 数据支持 | 业务逻辑 | 完成度 |
|
||||
|---------|---------|---------|--------|
|
||||
| 提交比赛 | ✅ | ⚠️ | 90% |
|
||||
| 报名阶段 | ✅ | ⚠️ | 85% |
|
||||
| 编排 | ✅ | ❌ | 20% |
|
||||
| 信息同步 | ✅ | ⚠️ | 70% |
|
||||
| 比赛日 | ✅ | ❌ | 30% |
|
||||
| 比赛结束 | ⚠️ | ❌ | 10% |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 技术决策
|
||||
|
||||
### 导出功能技术选型
|
||||
- **Excel:** EasyExcel(性能优秀)
|
||||
- **PDF:** iText 7 或 FreeMarker + Flying Saucer
|
||||
- **模板:** FreeMarker
|
||||
|
||||
### 成绩计算精度
|
||||
- **类型:** Java BigDecimal
|
||||
- **精度:** 保留3位小数
|
||||
- **舍入:** HALF_UP(四舍五入)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 遗留问题
|
||||
|
||||
无
|
||||
|
||||
---
|
||||
|
||||
## 📅 下一步计划
|
||||
|
||||
### 短期计划(本周)
|
||||
1. 开始实现成绩计算引擎
|
||||
2. 编写单元测试
|
||||
3. 完善API文档
|
||||
|
||||
### 中期计划(下周)
|
||||
1. 完成比赛日流程功能
|
||||
2. 实现导出打印功能
|
||||
3. 进行集成测试
|
||||
|
||||
### 长期规划
|
||||
1. 优化性能(批量操作)
|
||||
2. 添加数据可视化
|
||||
3. 考虑自动编排算法
|
||||
|
||||
---
|
||||
|
||||
## 📁 产出文档
|
||||
|
||||
1. `docs/tasks/README.md` - 任务管理说明
|
||||
2. `docs/tasks/00-任务清单总览.md` - 28个任务汇总
|
||||
3. `docs/tasks/03-成绩计算引擎.md` - 8个详细子任务
|
||||
4. `docs/tasks/02-比赛日流程功能.md` - 6个详细子任务
|
||||
5. `docs/tasks/04-导出打印功能.md` - 5个详细子任务
|
||||
6. `/tmp/competition_flow_status_report.md` - 比赛流程分析报告
|
||||
|
||||
---
|
||||
|
||||
## 💬 备注
|
||||
|
||||
- 用户明确要求:编排功能暂不实现,优先完成其他核心功能
|
||||
- 所有任务已按优先级分类(P0/P1/P2/P3)
|
||||
- 任务清单包含详细的代码示例和实现步骤
|
||||
- 预计总工时:约17天(核心功能)
|
||||
|
||||
---
|
||||
|
||||
**下次更新:** 2025-12-01
|
||||
Reference in New Issue
Block a user