Compare commits
76 Commits
7aa6545cbb
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| df7efac819 | |||
| 559dea702a | |||
| c40ca5b35b | |||
| 742272026b | |||
| 496537ceef | |||
| e0d3572e34 | |||
| a262ca9279 | |||
| b94ac501de | |||
| ea50330a5d | |||
| e3f158985a | |||
| eefe7167ee | |||
| 550802a029 | |||
| ac44bd45fa | |||
| 8193baf314 | |||
| 3af34506ba | |||
| 55ccf08246 | |||
| 29e9fb4e0a | |||
| 9fa5eb46df | |||
| d3c7dccf05 | |||
| 370cdc8e1e | |||
| e70dbd1144 | |||
| 760b7d0039 | |||
| e50b71a13d | |||
| e1bf9a4351 | |||
| 2f9fbbb2aa | |||
| f45fee050e | |||
| 18895dcb76 | |||
| 89962c69e6 | |||
| 45758108a8 | |||
| 19e3d94a33 | |||
| 7fae2f0ff8 | |||
| fe5ddfa253 | |||
| c7038a5883 | |||
| 87a05df04f | |||
| b7ad819a29 | |||
| 6db9a1e51d | |||
| 0539152dbb | |||
| c7058b8b07 | |||
| 16b55adf81 | |||
| 0b5fc9fb71 | |||
| 86e4580e5d | |||
| 47d0b70a9c | |||
| 105e457f7c | |||
| d583bdc5c8 | |||
| 07845f3a4f | |||
| ec2382b447 | |||
| bcba649b02 | |||
| a19baf3907 | |||
| 301bb7a227 | |||
| fdd346b27f | |||
| 1d5ac896dd | |||
| aab66f79fe | |||
| 491c8db26c | |||
| 4a2071ddda | |||
| 559e97b672 | |||
| 35a5369e81 | |||
| dca5e5050f | |||
| 67908a4dd0 | |||
| 0c9322c510 | |||
| 7c1b9de6b4 | |||
| 284ebd2e73 | |||
| e7b8a1c59d | |||
| 432ccb606c | |||
| ffbe511f34 | |||
| 4c93027028 | |||
| abb1391b2f | |||
| 1d6c3d9df5 | |||
| cc4a01ea28 | |||
| 0f0beaf62e | |||
| 3ae441c044 | |||
| ab290d1aa2 | |||
| 4e487b76b7 | |||
| ec26191a5f | |||
| f6c019e520 | |||
| 4b530dd6be | |||
| 1ca0f6a7f6 |
@@ -27,7 +27,14 @@
|
|||||||
"Bash(python -m json.tool:*)",
|
"Bash(python -m json.tool:*)",
|
||||||
"Bash(\"/d/Program Files/mysql-8.0.32-winx64/bin/mysql\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"\nSELECT \n TABLE_NAME,\n CASE WHEN SUM(COLUMN_NAME = ''status'') > 0 THEN ''✓'' ELSE ''✗'' END AS has_status\nFROM information_schema.COLUMNS \nWHERE TABLE_SCHEMA = ''martial_db'' \n AND TABLE_NAME IN (''martial_athlete'', ''martial_live_update'', ''martial_result'', ''martial_schedule_athlete'')\nGROUP BY TABLE_NAME\nORDER BY TABLE_NAME;\n\")",
|
"Bash(\"/d/Program Files/mysql-8.0.32-winx64/bin/mysql\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"\nSELECT \n TABLE_NAME,\n CASE WHEN SUM(COLUMN_NAME = ''status'') > 0 THEN ''✓'' ELSE ''✗'' END AS has_status\nFROM information_schema.COLUMNS \nWHERE TABLE_SCHEMA = ''martial_db'' \n AND TABLE_NAME IN (''martial_athlete'', ''martial_live_update'', ''martial_result'', ''martial_schedule_athlete'')\nGROUP BY TABLE_NAME\nORDER BY TABLE_NAME;\n\")",
|
||||||
"Bash(git add:*)",
|
"Bash(git add:*)",
|
||||||
"Bash(git commit -m \"$(cat <<''EOF''\nMerge remote-tracking branch ''origin/main''\n\n解决目录重组冲突:\n- doc/ → docs/ (文档目录重命名)\n- doc/sql/ → database/ (数据库脚本目录重组)\n- doc/script/ → scripts/ (脚本目录重组)\n\n保留本地新增的武术比赛系统文件:\n- docs/sql/mysql/martial-*.sql (4个数据库脚本)\n- docs/后端开发完成报告.md\n- docs/数据库字段检查报告.md \n- docs/问题修复报告.md\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude <noreply@anthropic.com>\nEOF\n)\")"
|
"Bash(git commit -m \"$(cat <<''EOF''\nMerge remote-tracking branch ''origin/main''\n\n解决目录重组冲突:\n- doc/ → docs/ (文档目录重命名)\n- doc/sql/ → database/ (数据库脚本目录重组)\n- doc/script/ → scripts/ (脚本目录重组)\n\n保留本地新增的武术比赛系统文件:\n- docs/sql/mysql/martial-*.sql (4个数据库脚本)\n- docs/后端开发完成报告.md\n- docs/数据库字段检查报告.md \n- docs/问题修复报告.md\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude <noreply@anthropic.com>\nEOF\n)\")",
|
||||||
|
"Bash(\"/d/Program Files/mysql-8.0.32-winx64/bin/mysql\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"DESC martial_schedule_participant;\")",
|
||||||
|
"Bash(\"/d/Program Files/mysql-8.0.32-winx64/bin/mysql\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"SHOW CREATE TABLE martial_schedule_participant\\\\G\")",
|
||||||
|
"Bash(\"/d/Program Files/mysql-8.0.32-winx64/bin/mysql\" -h localhost -P 3306 -u root -p123456 -e \"DROP DATABASE IF EXISTS martial_db; CREATE DATABASE martial_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;\":*)",
|
||||||
|
"Bash(\"/d/Program Files/mysql-8.0.32-winx64/bin/mysql\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"DESC mt_venue;\")",
|
||||||
|
"Bash(grep:*)",
|
||||||
|
"Bash(\"/d/Program Files/mysql-8.0.32-winx64/bin/mysql\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"DESC martial_competition_rules_attachment;\")",
|
||||||
|
"Bash(\"/d/Program Files/mysql-8.0.32-winx64/bin/mysql\" -h localhost -P 3306 -u root -p123456 -D martial_db -e \"SELECT COUNT\\(*\\) FROM martial_competition_rules_attachment WHERE is_deleted = 0;\")"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -39,3 +39,6 @@ PORT_FORWARD.md
|
|||||||
QUICKSTART.md
|
QUICKSTART.md
|
||||||
SERVICE_CONFIG.md
|
SERVICE_CONFIG.md
|
||||||
nul
|
nul
|
||||||
|
|
||||||
|
# MinIO 运行时数据
|
||||||
|
minio_data/
|
||||||
|
|||||||
50
Dockerfile.fullbuild
Normal file
50
Dockerfile.fullbuild
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# ============================================
|
||||||
|
# 武术赛事管理系统 - 完整构建 Dockerfile
|
||||||
|
# 包含 martial-tool 编译 + martial-master 编译
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# 构建阶段:使用 Maven + JDK 镜像
|
||||||
|
FROM maven:3.9-eclipse-temurin-17 AS builder
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# 复制 martial-tool(BladeX 框架)
|
||||||
|
COPY martial-tool /build/martial-tool
|
||||||
|
|
||||||
|
# 编译 martial-tool 并安装到本地仓库
|
||||||
|
RUN cd /build/martial-tool && \
|
||||||
|
mvn clean install -DskipTests -q
|
||||||
|
|
||||||
|
# 复制 martial-master(后端项目)
|
||||||
|
COPY martial-master /build/martial-master
|
||||||
|
|
||||||
|
# 编译 martial-master
|
||||||
|
RUN cd /build/martial-master && \
|
||||||
|
mvn clean package -DskipTests -q
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 运行阶段:使用轻量级 JRE 镜像
|
||||||
|
# ============================================
|
||||||
|
FROM eclipse-temurin:17-jre-jammy
|
||||||
|
|
||||||
|
LABEL maintainer="JohnSion"
|
||||||
|
LABEL description="武术比赛管理系统后端服务"
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 从构建阶段复制 JAR 文件
|
||||||
|
COPY --from=builder /build/martial-master/target/blade-api.jar /app/blade-api.jar
|
||||||
|
|
||||||
|
# 暴露端口
|
||||||
|
EXPOSE 8123
|
||||||
|
|
||||||
|
# 健康检查
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
|
||||||
|
CMD curl -f http://localhost:8123/actuator/health || exit 1
|
||||||
|
|
||||||
|
# JVM 参数配置
|
||||||
|
ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC"
|
||||||
|
ENV SPRING_PROFILE="dev"
|
||||||
|
|
||||||
|
# 启动命令
|
||||||
|
CMD ["sh", "-c", "java ${JAVA_OPTS} -jar /app/blade-api.jar --spring.profiles.active=${SPRING_PROFILE}"]
|
||||||
@@ -1,15 +1,3 @@
|
|||||||
# 多阶段构建:编译阶段
|
|
||||||
FROM maven:3.9-eclipse-temurin-17 AS builder
|
|
||||||
|
|
||||||
WORKDIR /build
|
|
||||||
|
|
||||||
# 复制主项目源码
|
|
||||||
COPY pom.xml .
|
|
||||||
COPY src ./src
|
|
||||||
|
|
||||||
# 编译项目(在 Drone 中已经编译好,这里只是复制)
|
|
||||||
RUN mkdir -p target
|
|
||||||
|
|
||||||
# 运行阶段:使用轻量级 JRE 镜像
|
# 运行阶段:使用轻量级 JRE 镜像
|
||||||
FROM eclipse-temurin:17-jre-jammy
|
FROM eclipse-temurin:17-jre-jammy
|
||||||
|
|
||||||
429
README.md
429
README.md
@@ -2,348 +2,145 @@
|
|||||||
|
|
||||||
基于 BladeX 4.0.1 企业级框架构建的武术比赛管理系统后端服务。
|
基于 BladeX 4.0.1 企业级框架构建的武术比赛管理系统后端服务。
|
||||||
|
|
||||||
## 🌐 在线访问
|
## 在线访问
|
||||||
|
|
||||||
- **生产环境 API**: https://martial-api.johnsion.club
|
| 服务 | 地址 | 说明 |
|
||||||
- **API 文档**: https://martial-doc.johnsion.club
|
|------|------|------|
|
||||||
- **前端系统**: https://martial.johnsion.club
|
| 后端 API | https://martial-api.aitisai.com | Spring Boot 服务 |
|
||||||
- **CI/CD 管理**: https://martial-ci.johnsion.club
|
| 管理后台 | https://martial-admin.aitisai.com | Web 管理端 |
|
||||||
|
| 用户端 | https://martial.aitisai.com | 报名小程序 H5 |
|
||||||
|
| 裁判端 | https://martial-mini.aitisai.com | 裁判评分小程序 |
|
||||||
|
| OSS 存储 | https://martial-oss.aitisai.com | MinIO 对象存储 |
|
||||||
|
| MinIO 控制台 | https://martial-minio.aitisai.com | MinIO 管理界面 |
|
||||||
|
|
||||||
## 📦 技术栈
|
## 技术栈
|
||||||
|
|
||||||
- **框架**: Spring Boot 3.2.4
|
- **框架**: Spring Boot 3.2.4 + BladeX 4.0.1
|
||||||
- **语言**: Java 17
|
- **语言**: Java 17
|
||||||
|
- **数据库**: MySQL 8.0 + Redis 7
|
||||||
- **ORM**: MyBatis-Plus
|
- **ORM**: MyBatis-Plus
|
||||||
- **数据库**: MySQL 8.0
|
- **数据库迁移**: Flyway
|
||||||
- **缓存**: Redis 7
|
- **对象存储**: MinIO
|
||||||
- **API 文档**: Knife4j (Swagger)
|
- **反向代理**: Caddy
|
||||||
- **企业框架**: BladeX 4.0.1 RELEASE
|
- **容器化**: Docker Compose
|
||||||
|
|
||||||
## 📁 项目结构
|
## 快速开始
|
||||||
|
|
||||||
|
### 环境要求
|
||||||
|
|
||||||
|
- Docker & Docker Compose
|
||||||
|
|
||||||
|
### 一键部署(推荐)
|
||||||
|
|
||||||
|
确保目录结构如下:
|
||||||
|
```
|
||||||
|
martial/
|
||||||
|
├── martial-tool/ # BladeX 框架(必需)
|
||||||
|
├── martial-master/ # 后端项目
|
||||||
|
├── martial-web/ # 管理后台前端
|
||||||
|
├── martial-mini/ # 用户端小程序
|
||||||
|
└── martial-admin-mini/ # 裁判端小程序
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd martial/martial-master
|
||||||
|
|
||||||
|
# 首次部署(完整构建,约5-6分钟)
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# 查看构建日志
|
||||||
|
docker compose logs -f martial-api
|
||||||
|
|
||||||
|
# 查看服务状态
|
||||||
|
docker compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
服务启动后:
|
||||||
|
- API 服务: http://localhost:8123
|
||||||
|
- API 文档: http://localhost:8123/doc.html
|
||||||
|
|
||||||
|
### 快速构建(开发迭代)
|
||||||
|
|
||||||
|
如果已经手动编译过 JAR,可以使用快速构建:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 先编译 martial-tool(首次)
|
||||||
|
cd ../martial-tool && mvn clean install -DskipTests
|
||||||
|
|
||||||
|
# 编译 martial-master
|
||||||
|
cd ../martial-master && mvn clean package -DskipTests
|
||||||
|
|
||||||
|
# 使用快速构建 Dockerfile
|
||||||
|
docker compose build martial-api --build-arg DOCKERFILE=Dockerfile.quick
|
||||||
|
```
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
```
|
```
|
||||||
martial-master/
|
martial-master/
|
||||||
├── src/main/java/org/springblade/
|
├── src/main/java/org/springblade/
|
||||||
│ ├── Application.java # 主启动类
|
│ ├── modules/martial/ # 武术比赛核心业务
|
||||||
│ ├── common/ # 公共工具和配置
|
│ │ ├── controller/ # 接口控制器
|
||||||
│ ├── modules/ # 业务模块
|
│ │ ├── service/ # 业务逻辑
|
||||||
│ │ ├── auth/ # 认证授权
|
│ │ ├── mapper/ # 数据访问
|
||||||
│ │ ├── system/ # 系统管理
|
│ │ └── pojo/ # 实体类
|
||||||
│ │ ├── resource/ # 资源管理
|
│ └── ... # BladeX 框架模块
|
||||||
│ │ ├── desk/ # 工作台
|
|
||||||
│ │ ├── develop/ # 代码生成
|
|
||||||
│ │ └── martial/ # ⭐ 武术比赛业务(核心)
|
|
||||||
│ └── job/ # 定时任务
|
|
||||||
├── src/main/resources/
|
├── src/main/resources/
|
||||||
│ ├── application.yml # 主配置
|
│ ├── application.yml # 主配置
|
||||||
│ ├── application-dev.yml # 开发环境
|
│ ├── application-dev.yml # 开发环境
|
||||||
│ ├── application-test.yml # 测试环境
|
│ ├── application-prod.yml # 生产环境
|
||||||
│ └── application-prod.yml # 生产环境
|
│ └── db/migration/ # Flyway 迁移脚本
|
||||||
├── database/ # 数据库脚本
|
├── database/ # 数据库初始化脚本
|
||||||
│ ├── bladex/ # BladeX 框架表
|
├── docs/ # 项目文档
|
||||||
│ ├── flowable/ # 工作流表
|
├── docker-compose.yml # Docker 编排配置
|
||||||
│ ├── martial-db/ # 武术业务表
|
├── Dockerfile.fullbuild # 完整构建(含 martial-tool)
|
||||||
│ └── upgrade/ # 升级脚本
|
└── Dockerfile.quick # 快速构建(需预编译 JAR)
|
||||||
├── docs/ # 项目文档
|
|
||||||
│ ├── README.md # 文档索引
|
|
||||||
│ ├── 架构说明.md # 架构设计
|
|
||||||
│ ├── 前后端架构说明.md # 前后端交互
|
|
||||||
│ ├── 开发指南.md # 开发规范
|
|
||||||
│ └── CI-CD部署总结.md # 部署文档
|
|
||||||
├── scripts/ # 运维脚本
|
|
||||||
│ ├── docker/ # Docker 部署
|
|
||||||
│ └── fatjar/ # JAR 启动脚本
|
|
||||||
├── .drone.yml # CI/CD 配置
|
|
||||||
├── Dockerfile # Docker 镜像构建
|
|
||||||
└── CLAUDE.md # 项目完整说明
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🚀 快速开始
|
## Docker Compose 服务
|
||||||
|
|
||||||
### 环境要求
|
| 服务 | 端口 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| martial-api | 8123 | 后端 API 服务 |
|
||||||
|
| martial-mysql | 3306 | MySQL 数据库 |
|
||||||
|
| martial-redis | 6379 | Redis 缓存 |
|
||||||
|
| minio | 9000/9001 | 对象存储 |
|
||||||
|
|
||||||
- **JDK**: 17+
|
## 数据库迁移
|
||||||
- **Maven**: 3.8+
|
|
||||||
- **MySQL**: 8.0+
|
|
||||||
- **Redis**: 6.0+
|
|
||||||
|
|
||||||
### 本地开发
|
项目使用 Flyway 管理数据库版本,应用启动时自动执行迁移。
|
||||||
|
|
||||||
|
**添加新迁移:**
|
||||||
```bash
|
```bash
|
||||||
# 1. 克隆项目
|
# 在 src/main/resources/db/migration/ 创建脚本
|
||||||
git clone https://git.waypeak.work/martial/martial-master.git
|
# 命名规范: V{版本号}__{描述}.sql
|
||||||
cd martial-master
|
# 示例: V3__add_new_table.sql
|
||||||
|
|
||||||
# 2. 编译 BladeX 框架(首次必须)
|
|
||||||
cd /path/to/martial-tool
|
|
||||||
mvn clean install -DskipTests
|
|
||||||
|
|
||||||
# 3. 编译并运行
|
|
||||||
cd /path/to/martial-master
|
|
||||||
mvn clean package -DskipTests -Dmaven.test.skip=true
|
|
||||||
mvn spring-boot:run
|
|
||||||
|
|
||||||
# 4. 访问应用
|
|
||||||
# API: http://localhost:8123
|
|
||||||
# 文档: http://localhost:8123/doc.html
|
|
||||||
```
|
```
|
||||||
|
|
||||||
详细说明请参考:[CLAUDE.md](./CLAUDE.md)
|
详细说明:[docs/DATABASE_MIGRATION.md](./docs/DATABASE_MIGRATION.md)
|
||||||
|
|
||||||
## 🔄 自动化部署
|
## 开发文档
|
||||||
|
|
||||||
### CI/CD 架构
|
| 文档 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| [CLAUDE.md](./CLAUDE.md) | 项目完整说明 |
|
||||||
|
| [docs/开发指南.md](./docs/开发指南.md) | 开发规范 |
|
||||||
|
| [docs/架构说明.md](./docs/架构说明.md) | 架构设计 |
|
||||||
|
| [docs/DATABASE_MIGRATION.md](./docs/DATABASE_MIGRATION.md) | 数据库迁移指南 |
|
||||||
|
|
||||||
本项目已配置 Drone CI/CD 实现代码推送后的全自动编译、部署流程。
|
## 相关仓库
|
||||||
|
|
||||||
```
|
| 仓库 | 说明 |
|
||||||
开发者 Push 代码
|
|------|------|
|
||||||
↓
|
| [martial-master](https://git.waypeak.work/martial/martial-master) | 后端 API |
|
||||||
Gitea 仓库(git.waypeak.work)
|
| [martial-web](https://git.waypeak.work/martial/martial-web) | 管理后台前端 |
|
||||||
↓ [Webhook 触发]
|
| [martial-mini](https://git.waypeak.work/martial/martial-mini) | 用户端小程序 |
|
||||||
Drone CI Server(martial-ci.johnsion.club)
|
| [martial-admin-mini](https://git.waypeak.work/martial/martial-admin-mini) | 裁判端小程序 |
|
||||||
↓ [Runner 执行]
|
|
||||||
编译 BladeX 框架 → 编译后端项目 → 构建 Docker 镜像 → 部署容器 → 健康检查
|
|
||||||
↓
|
|
||||||
生产服务器部署完成(martial-api.johnsion.club)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 部署流程
|
## 许可协议
|
||||||
|
|
||||||
**日常开发(不触发部署):**
|
本项目基于 **BladeX 商业框架** 构建,需遵守 [BladeX 商业授权许可协议](https://license.bladex.cn)。
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. 切换到开发分支
|
|
||||||
git checkout dev
|
|
||||||
|
|
||||||
# 2. 修改代码并提交
|
|
||||||
git add .
|
|
||||||
git commit -m "feat: 添加新功能"
|
|
||||||
git push origin dev
|
|
||||||
|
|
||||||
# ✅ 推送到 dev 分支不会触发自动部署
|
|
||||||
```
|
|
||||||
|
|
||||||
**发布到生产环境:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. 合并开发分支到 main
|
|
||||||
git checkout main
|
|
||||||
git merge dev
|
|
||||||
|
|
||||||
# 2. 推送到 main 分支(自动触发部署)
|
|
||||||
git push origin main
|
|
||||||
|
|
||||||
# 3. 查看部署进度
|
|
||||||
# 访问 Drone UI: https://martial-ci.johnsion.club
|
|
||||||
# 或等待约 5-6 分钟后直接访问生产环境
|
|
||||||
```
|
|
||||||
|
|
||||||
### 部署步骤(全自动)
|
|
||||||
|
|
||||||
1. **编译完整项目**(约4-5分钟)
|
|
||||||
- 克隆 BladeX 框架代码(martial-tool)
|
|
||||||
- 编译框架并安装到 Maven 本地仓库
|
|
||||||
- 编译后端项目(martial-master)
|
|
||||||
- 生成 blade-api.jar(约236MB)
|
|
||||||
|
|
||||||
2. **构建 Docker 镜像**(约1分钟)
|
|
||||||
- 基于 eclipse-temurin:17-jre-alpine
|
|
||||||
- 复制 JAR 文件和配置
|
|
||||||
- 构建轻量化镜像
|
|
||||||
|
|
||||||
3. **部署到生产环境**(约30秒)
|
|
||||||
- 停止旧容器
|
|
||||||
- 启动新容器
|
|
||||||
- 连接数据库和 Redis
|
|
||||||
|
|
||||||
4. **健康检查**(约45秒)
|
|
||||||
- 等待 Spring Boot 应用完全启动
|
|
||||||
- 检查健康端点: `/actuator/health`
|
|
||||||
- 验证部署成功
|
|
||||||
|
|
||||||
**总耗时:** 约 6-7 分钟
|
|
||||||
|
|
||||||
### 访问地址
|
|
||||||
|
|
||||||
**部署完成后:**
|
|
||||||
- 后端 API: https://martial-api.johnsion.club
|
|
||||||
- API 文档: https://martial-doc.johnsion.club
|
|
||||||
- 健康检查: https://martial-api.johnsion.club/actuator/health
|
|
||||||
- 前端系统: https://martial.johnsion.club
|
|
||||||
|
|
||||||
**CI/CD 管理:**
|
|
||||||
- Drone UI: https://martial-ci.johnsion.club
|
|
||||||
|
|
||||||
### 部署配置
|
|
||||||
|
|
||||||
**生产服务器:**
|
|
||||||
- MySQL 8.0 (Docker 容器)
|
|
||||||
- Redis 7 (Docker 容器)
|
|
||||||
- Docker Network: martial_martial-network
|
|
||||||
|
|
||||||
**环境变量配置在 docker-compose.yml:**
|
|
||||||
```yaml
|
|
||||||
SPRING_PROFILE: prod
|
|
||||||
JAVA_OPTS: "-Xms512m -Xmx1024m"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 故障排查
|
|
||||||
|
|
||||||
**查看部署日志:**
|
|
||||||
```bash
|
|
||||||
# Drone 构建日志
|
|
||||||
访问: https://martial-ci.johnsion.club
|
|
||||||
|
|
||||||
# 应用日志
|
|
||||||
ssh root@154.30.6.21
|
|
||||||
docker logs -f martial-backend
|
|
||||||
```
|
|
||||||
|
|
||||||
**检查服务状态:**
|
|
||||||
```bash
|
|
||||||
# 查看容器状态
|
|
||||||
docker ps | grep martial
|
|
||||||
|
|
||||||
# 查看健康状态
|
|
||||||
curl https://martial-api.johnsion.club/actuator/health
|
|
||||||
|
|
||||||
# 重启服务
|
|
||||||
cd /app/martial && docker-compose restart backend
|
|
||||||
```
|
|
||||||
|
|
||||||
详细部署文档请参考:[docs/CI-CD部署总结.md](./docs/CI-CD部署总结.md)
|
|
||||||
|
|
||||||
## 📚 开发文档
|
|
||||||
|
|
||||||
- **[CLAUDE.md](./CLAUDE.md)** - 项目完整说明、构建命令、技术栈
|
|
||||||
- **[docs/README.md](./docs/README.md)** - 文档索引和快速导航
|
|
||||||
- **[docs/架构说明.md](./docs/架构说明.md)** - BladeX 架构设计说明
|
|
||||||
- **[docs/前后端架构说明.md](./docs/前后端架构说明.md)** - 前后端分离架构
|
|
||||||
- **[docs/开发指南.md](./docs/开发指南.md)** - 开发规范和最佳实践
|
|
||||||
- **[docs/CI-CD部署总结.md](./docs/CI-CD部署总结.md)** - CI/CD 配置和运维
|
|
||||||
|
|
||||||
## 🗄️ 数据库
|
|
||||||
|
|
||||||
**连接信息(生产环境):**
|
|
||||||
- Host: 容器内使用 `martial-mysql`
|
|
||||||
- Port: 3306
|
|
||||||
- Database: martial_db
|
|
||||||
- Username: root
|
|
||||||
- Password: WtcSecure901faf1ac4d32e2bPwd
|
|
||||||
|
|
||||||
**数据库脚本:**
|
|
||||||
- BladeX 框架表: `database/bladex/bladex.mysql.all.create.sql`
|
|
||||||
- Flowable 工作流表: `database/flowable/flowable.mysql.all.create.sql`
|
|
||||||
- 武术业务表: `database/martial-db/martial_db.sql`
|
|
||||||
|
|
||||||
## 🔧 配置说明
|
|
||||||
|
|
||||||
**配置文件优先级:**
|
|
||||||
```
|
|
||||||
application.yml (基础配置)
|
|
||||||
↓
|
|
||||||
application-{profile}.yml (环境配置)
|
|
||||||
↓
|
|
||||||
环境变量 (Docker 容器配置)
|
|
||||||
```
|
|
||||||
|
|
||||||
**环境切换:**
|
|
||||||
```bash
|
|
||||||
# 开发环境
|
|
||||||
mvn spring-boot:run -Dspring-boot.run.profiles=dev
|
|
||||||
|
|
||||||
# 测试环境
|
|
||||||
java -jar blade-api.jar --spring.profiles.active=test
|
|
||||||
|
|
||||||
# 生产环境(Docker)
|
|
||||||
SPRING_PROFILE=prod
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔐 安全配置
|
|
||||||
|
|
||||||
- **Token 认证**: 无状态 Token 机制
|
|
||||||
- **多租户隔离**: 基于 tenant_id 的数据隔离
|
|
||||||
- **权限控制**: RBAC 角色权限体系
|
|
||||||
- **SQL 监控**: Druid 数据库连接池监控
|
|
||||||
- **API 文档**: 生产环境可配置访问控制
|
|
||||||
|
|
||||||
## 📊 监控和管理
|
|
||||||
|
|
||||||
- **API 文档**: https://martial-doc.johnsion.club
|
|
||||||
- **Druid 监控**: https://martial-api.johnsion.club/druid
|
|
||||||
- 用户名: blade
|
|
||||||
- 密码: 1qaz@WSX
|
|
||||||
- **健康检查**: https://martial-api.johnsion.club/actuator/health
|
|
||||||
- **CI/CD 管理**: https://martial-ci.johnsion.club
|
|
||||||
|
|
||||||
## 🤝 贡献指南
|
|
||||||
|
|
||||||
1. Fork 本仓库
|
|
||||||
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
|
|
||||||
3. 提交更改 (`git commit -m 'feat: Add some AmazingFeature'`)
|
|
||||||
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
|
||||||
5. 提交 Pull Request
|
|
||||||
|
|
||||||
**提交规范:**
|
|
||||||
```
|
|
||||||
feat: 新功能
|
|
||||||
fix: 修复 Bug
|
|
||||||
docs: 文档更新
|
|
||||||
style: 代码格式调整
|
|
||||||
refactor: 重构
|
|
||||||
perf: 性能优化
|
|
||||||
test: 测试相关
|
|
||||||
chore: 构建/工具配置
|
|
||||||
```
|
|
||||||
|
|
||||||
## 👥 开发团队
|
|
||||||
|
|
||||||
- **开发者**: JohnSion
|
|
||||||
- **AI 助手**: Claude Code
|
|
||||||
- **基础框架**: BladeX 4.0.1 (上海布雷德科技有限公司)
|
|
||||||
|
|
||||||
## 📄 许可协议
|
|
||||||
|
|
||||||
### BladeX 商业授权
|
|
||||||
|
|
||||||
本项目基于 **BladeX 商业框架** 构建,需遵守以下协议:
|
|
||||||
|
|
||||||
#### 版权声明
|
|
||||||
- BladeX 是一个商业化软件,系列产品知识产权归**上海布雷德科技有限公司**独立所有
|
|
||||||
- 您一旦开始复制、下载、安装或者使用本产品,即被视为完全理解并接受本协议的各项条款
|
|
||||||
- 更多详情请看:[BladeX商业授权许可协议](https://license.bladex.cn)
|
|
||||||
|
|
||||||
#### 授权范围
|
|
||||||
- **专业版**:只可用于**个人学习**及**个人私活**项目,不可用于公司或团队,不可泄露给任何第三方
|
|
||||||
- **企业版**:可用于**企业名下**的任何项目,企业版员工在**未购买**专业版授权前,只授权开发**所在授权企业名下**的项目,**不得将BladeX用于个人私活**
|
|
||||||
- **共同遵守**:若甲方需要您提供项目源码,则需代为甲方购买BladeX企业授权,甲方购买后续的所有项目都无需再次购买授权
|
|
||||||
|
|
||||||
#### 商用权益
|
|
||||||
- ✔️ 遵守[商业协议](https://license.bladex.cn)的前提下,将BladeX系列产品用于授权范围内的商用项目,并上线运营
|
|
||||||
- ✔️ 遵守[商业协议](https://license.bladex.cn)的前提下,不限制项目数,不限制服务器数
|
|
||||||
- ✔️ 遵守[商业协议](https://license.bladex.cn)的前提下,将自行编写的业务代码申请软件著作权
|
|
||||||
|
|
||||||
#### 何为侵权
|
|
||||||
- ❌ 不遵守商业协议,私自销售商业源码
|
|
||||||
- ❌ 以任何理由将BladeX源码用于申请软件著作权
|
|
||||||
- ❌ 将商业源码以任何途径任何理由泄露给未授权的单位或个人
|
|
||||||
- ❌ 开发完毕项目,没有为甲方购买企业授权,向甲方提供了BladeX代码
|
|
||||||
- ❌ 基于BladeX拓展研发与BladeX有竞争关系的衍生框架,并将其开源或销售
|
|
||||||
|
|
||||||
#### 侵权后果
|
|
||||||
- 情节较轻:第一次发现警告处理
|
|
||||||
- 情节较重:封禁账号,踢出商业群,并保留追究法律责任的权利
|
|
||||||
- 情节严重:与本地律师事务所合作,以公司名义起诉侵犯计算机软件著作权
|
|
||||||
|
|
||||||
#### 技术支持
|
|
||||||
- **答疑时间**: 工作日 9:00 ~ 17:00,周末、节假日休息
|
|
||||||
- **技术社区**: https://sns.bladex.cn
|
|
||||||
- **官方QQ**: 1272154962
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**最后更新**: 2025-11-30
|
**最后更新**: 2024-12-29
|
||||||
**项目版本**: 4.0.1 RELEASE
|
|
||||||
**部署环境**: Docker + Drone CI/CD
|
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
-- ================================================================
|
|
||||||
-- 【紧急修复】场地表字段缺失问题 - 直接复制执行此脚本
|
|
||||||
-- 问题:Unknown column 'max_capacity' in 'field list'
|
|
||||||
-- 解决:重建 martial_venue 表,包含所有必需字段
|
|
||||||
-- 日期:2025-12-06
|
|
||||||
-- ================================================================
|
|
||||||
|
|
||||||
-- 使用正确的数据库
|
|
||||||
USE martial_db;
|
|
||||||
|
|
||||||
-- 删除旧表(如果有重要数据,请先备份!)
|
|
||||||
DROP TABLE IF EXISTS `martial_venue`;
|
|
||||||
|
|
||||||
-- 创建新表,包含完整字段
|
|
||||||
CREATE TABLE `martial_venue` (
|
|
||||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
|
||||||
`tenant_id` varchar(12) DEFAULT '000000' COMMENT '租户ID',
|
|
||||||
`competition_id` bigint(20) NOT NULL COMMENT '赛事ID',
|
|
||||||
`venue_name` varchar(100) NOT NULL COMMENT '场地名称',
|
|
||||||
`venue_code` varchar(50) DEFAULT NULL COMMENT '场地编码',
|
|
||||||
`max_capacity` int(11) DEFAULT 100 COMMENT '最大容纳人数',
|
|
||||||
`location` varchar(200) DEFAULT NULL COMMENT '位置/地点',
|
|
||||||
`description` varchar(500) DEFAULT NULL COMMENT '场地描述',
|
|
||||||
`facilities` varchar(500) DEFAULT NULL COMMENT '场地设施',
|
|
||||||
`sort_order` int(11) DEFAULT 0 COMMENT '排序',
|
|
||||||
`status` int(2) DEFAULT 1 COMMENT '状态(0-禁用,1-启用)',
|
|
||||||
`create_user` bigint(20) DEFAULT NULL COMMENT '创建人',
|
|
||||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
|
||||||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
|
||||||
`update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
|
|
||||||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
|
||||||
`is_deleted` int(2) DEFAULT 0 COMMENT '是否已删除',
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
KEY `idx_competition_id` (`competition_id`),
|
|
||||||
KEY `idx_tenant_id` (`tenant_id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='场地信息表';
|
|
||||||
|
|
||||||
-- 验证表已创建成功
|
|
||||||
DESC martial_venue;
|
|
||||||
|
|
||||||
-- 检查 max_capacity 字段
|
|
||||||
SELECT '✓ martial_venue 表已成功重建,包含 max_capacity 字段' AS 修复结果;
|
|
||||||
|
|
||||||
-- 显示所有字段
|
|
||||||
SELECT
|
|
||||||
COLUMN_NAME AS 字段名,
|
|
||||||
COLUMN_TYPE AS 类型,
|
|
||||||
COLUMN_DEFAULT AS 默认值,
|
|
||||||
COLUMN_COMMENT AS 说明
|
|
||||||
FROM information_schema.COLUMNS
|
|
||||||
WHERE TABLE_SCHEMA = 'martial_db'
|
|
||||||
AND TABLE_NAME = 'martial_venue'
|
|
||||||
ORDER BY ORDINAL_POSITION;
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
-- 检查赛事基础数据是否完整
|
|
||||||
USE martial_db;
|
|
||||||
|
|
||||||
-- 1. 检查赛事信息
|
|
||||||
SELECT
|
|
||||||
'赛事信息' AS '检查项',
|
|
||||||
COUNT(*) AS '记录数'
|
|
||||||
FROM martial_competition
|
|
||||||
WHERE id = 200;
|
|
||||||
|
|
||||||
-- 2. 检查参赛者数据
|
|
||||||
SELECT
|
|
||||||
'参赛者数据' AS '检查项',
|
|
||||||
COUNT(*) AS '记录数'
|
|
||||||
FROM martial_athlete
|
|
||||||
WHERE competition_id = 200;
|
|
||||||
|
|
||||||
-- 3. 检查场地数据
|
|
||||||
SELECT
|
|
||||||
'场地数据' AS '检查项',
|
|
||||||
COUNT(*) AS '记录数'
|
|
||||||
FROM martial_venue
|
|
||||||
WHERE competition_id = 200;
|
|
||||||
|
|
||||||
-- 4. 检查项目数据
|
|
||||||
SELECT
|
|
||||||
'项目数据' AS '检查项',
|
|
||||||
COUNT(*) AS '记录数'
|
|
||||||
FROM martial_project
|
|
||||||
WHERE id IN (
|
|
||||||
SELECT DISTINCT project_id
|
|
||||||
FROM martial_athlete
|
|
||||||
WHERE competition_id = 200
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 5. 检查赛事时间配置
|
|
||||||
SELECT
|
|
||||||
id AS '赛事ID',
|
|
||||||
competition_name AS '赛事名称',
|
|
||||||
competition_start_time AS '开始时间',
|
|
||||||
competition_end_time AS '结束时间',
|
|
||||||
CASE
|
|
||||||
WHEN competition_start_time IS NULL THEN '⚠ 未配置'
|
|
||||||
WHEN competition_end_time IS NULL THEN '⚠ 未配置'
|
|
||||||
ELSE '✓ 已配置'
|
|
||||||
END AS '时间配置状态'
|
|
||||||
FROM martial_competition
|
|
||||||
WHERE id = 200;
|
|
||||||
|
|
||||||
-- 6. 详细检查参赛者项目分布
|
|
||||||
SELECT
|
|
||||||
p.project_name AS '项目名称',
|
|
||||||
p.type AS '项目类型(1=个人,2=双人,3=集体)',
|
|
||||||
COUNT(*) AS '参赛人数'
|
|
||||||
FROM martial_athlete a
|
|
||||||
LEFT JOIN martial_project p ON a.project_id = p.id
|
|
||||||
WHERE a.competition_id = 200
|
|
||||||
GROUP BY p.id, p.project_name, p.type
|
|
||||||
ORDER BY p.type, p.project_name;
|
|
||||||
|
|
||||||
-- 7. 检查场地详情
|
|
||||||
SELECT
|
|
||||||
id AS '场地ID',
|
|
||||||
venue_name AS '场地名称',
|
|
||||||
venue_type AS '场地类型',
|
|
||||||
capacity AS '容量'
|
|
||||||
FROM martial_venue
|
|
||||||
WHERE competition_id = 200;
|
|
||||||
|
|
||||||
-- 总结
|
|
||||||
SELECT
|
|
||||||
'数据检查完成' AS '状态',
|
|
||||||
NOW() AS '检查时间';
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
-- ================================================================
|
|
||||||
-- 场地表结构检查和修复脚本
|
|
||||||
-- 用途:检查 martial_venue 表是否存在 max_capacity 字段,如果不存在则添加
|
|
||||||
-- 日期:2025-12-06
|
|
||||||
-- ================================================================
|
|
||||||
|
|
||||||
-- 检查表是否存在
|
|
||||||
SELECT
|
|
||||||
TABLE_NAME,
|
|
||||||
CASE
|
|
||||||
WHEN TABLE_NAME IS NOT NULL THEN '表存在'
|
|
||||||
ELSE '表不存在'
|
|
||||||
END AS status
|
|
||||||
FROM information_schema.TABLES
|
|
||||||
WHERE TABLE_SCHEMA = DATABASE()
|
|
||||||
AND TABLE_NAME = 'martial_venue';
|
|
||||||
|
|
||||||
-- 查看当前表结构
|
|
||||||
DESC martial_venue;
|
|
||||||
|
|
||||||
-- 查看所有字段
|
|
||||||
SELECT COLUMN_NAME, COLUMN_TYPE, COLUMN_DEFAULT, IS_NULLABLE, COLUMN_COMMENT
|
|
||||||
FROM information_schema.COLUMNS
|
|
||||||
WHERE TABLE_SCHEMA = DATABASE()
|
|
||||||
AND TABLE_NAME = 'martial_venue'
|
|
||||||
ORDER BY ORDINAL_POSITION;
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
-- ================================================================
|
|
||||||
-- 清理所有测试数据脚本
|
|
||||||
-- 用途:清空所有业务数据,保留表结构
|
|
||||||
-- 日期:2025-12-06
|
|
||||||
-- 警告:此脚本会删除所有业务数据,请谨慎使用!
|
|
||||||
-- ================================================================
|
|
||||||
|
|
||||||
-- 设置外键检查为0,允许删除有外键关联的数据
|
|
||||||
SET FOREIGN_KEY_CHECKS = 0;
|
|
||||||
|
|
||||||
-- 1. 清空赛事相关表
|
|
||||||
-- ================================================================
|
|
||||||
TRUNCATE TABLE `martial_competition`;
|
|
||||||
TRUNCATE TABLE `martial_banner`;
|
|
||||||
|
|
||||||
-- 2. 清空项目相关表
|
|
||||||
-- ================================================================
|
|
||||||
TRUNCATE TABLE `martial_project`;
|
|
||||||
|
|
||||||
-- 3. 清空场地相关表
|
|
||||||
-- ================================================================
|
|
||||||
TRUNCATE TABLE `martial_venue`;
|
|
||||||
|
|
||||||
-- 4. 清空参赛者/运动员相关表
|
|
||||||
-- ================================================================
|
|
||||||
TRUNCATE TABLE `martial_athlete`;
|
|
||||||
TRUNCATE TABLE `martial_participant`;
|
|
||||||
|
|
||||||
-- 5. 清空报名订单相关表
|
|
||||||
-- ================================================================
|
|
||||||
TRUNCATE TABLE `martial_registration_order`;
|
|
||||||
|
|
||||||
-- 6. 清空裁判相关表
|
|
||||||
-- ================================================================
|
|
||||||
TRUNCATE TABLE `martial_referee`;
|
|
||||||
|
|
||||||
-- 7. 清空成绩相关表
|
|
||||||
-- ================================================================
|
|
||||||
TRUNCATE TABLE `martial_score`;
|
|
||||||
|
|
||||||
-- 8. 清空赛程编排相关表(如果存在)
|
|
||||||
-- ================================================================
|
|
||||||
-- TRUNCATE TABLE `martial_schedule`;
|
|
||||||
-- TRUNCATE TABLE `martial_schedule_detail`;
|
|
||||||
|
|
||||||
-- 9. 清空信息发布相关表
|
|
||||||
-- ================================================================
|
|
||||||
TRUNCATE TABLE `martial_info_publish`;
|
|
||||||
|
|
||||||
-- 重新启用外键检查
|
|
||||||
SET FOREIGN_KEY_CHECKS = 1;
|
|
||||||
|
|
||||||
-- ================================================================
|
|
||||||
-- 验证清理结果
|
|
||||||
-- ================================================================
|
|
||||||
SELECT
|
|
||||||
'赛事数据' AS 表名,
|
|
||||||
COUNT(*) AS 记录数
|
|
||||||
FROM martial_competition
|
|
||||||
UNION ALL
|
|
||||||
SELECT '项目数据', COUNT(*) FROM martial_project
|
|
||||||
UNION ALL
|
|
||||||
SELECT '场地数据', COUNT(*) FROM martial_venue
|
|
||||||
UNION ALL
|
|
||||||
SELECT '参赛者数据', COUNT(*) FROM martial_athlete
|
|
||||||
UNION ALL
|
|
||||||
SELECT '报名订单数据', COUNT(*) FROM martial_registration_order
|
|
||||||
UNION ALL
|
|
||||||
SELECT '裁判数据', COUNT(*) FROM martial_referee
|
|
||||||
UNION ALL
|
|
||||||
SELECT '成绩数据', COUNT(*) FROM martial_score
|
|
||||||
UNION ALL
|
|
||||||
SELECT '信息发布数据', COUNT(*) FROM martial_info_publish;
|
|
||||||
|
|
||||||
-- ================================================================
|
|
||||||
-- 清理完成
|
|
||||||
-- ================================================================
|
|
||||||
-- 所有业务数据已清空,表结构保留
|
|
||||||
-- 您现在可以重新测试完整的业务流程:
|
|
||||||
-- 1. 创建赛事
|
|
||||||
-- 2. 配置场地
|
|
||||||
-- 3. 创建项目
|
|
||||||
-- 4. 添加参赛者
|
|
||||||
-- 5. 进行编排
|
|
||||||
-- ================================================================
|
|
||||||
49
database/martial-db/create_dispatch_log_table.sql
Normal file
49
database/martial-db/create_dispatch_log_table.sql
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
-- =====================================================
|
||||||
|
-- 创建调度调整日志表
|
||||||
|
-- 用于记录调度功能的调整历史
|
||||||
|
-- 执行时间: 2025-12-12
|
||||||
|
-- =====================================================
|
||||||
|
|
||||||
|
USE blade;
|
||||||
|
|
||||||
|
-- 创建调度调整日志表
|
||||||
|
CREATE TABLE IF NOT EXISTS `martial_schedule_adjustment_log` (
|
||||||
|
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
`competition_id` bigint NOT NULL COMMENT '赛事ID',
|
||||||
|
`schedule_detail_id` bigint NOT NULL COMMENT '编排明细ID',
|
||||||
|
`schedule_group_id` bigint NOT NULL COMMENT '分组ID',
|
||||||
|
`participant_id` bigint NOT NULL COMMENT '参赛者记录ID',
|
||||||
|
`participant_name` varchar(100) DEFAULT NULL COMMENT '参赛者姓名',
|
||||||
|
`organization` varchar(200) DEFAULT NULL COMMENT '单位名称',
|
||||||
|
`old_order` int NOT NULL COMMENT '原顺序',
|
||||||
|
`new_order` int NOT NULL COMMENT '新顺序',
|
||||||
|
`adjustment_type` varchar(20) DEFAULT NULL COMMENT '调整类型(move_up=上移, move_down=下移, swap=交换)',
|
||||||
|
`adjustment_reason` varchar(500) DEFAULT NULL COMMENT '调整原因',
|
||||||
|
`operator_id` bigint DEFAULT NULL COMMENT '操作人ID',
|
||||||
|
`operator_name` varchar(100) DEFAULT NULL COMMENT '操作人姓名',
|
||||||
|
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`tenant_id` varchar(12) DEFAULT '000000' COMMENT '租户ID',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_competition` (`competition_id`),
|
||||||
|
KEY `idx_detail` (`schedule_detail_id`),
|
||||||
|
KEY `idx_group` (`schedule_group_id`),
|
||||||
|
KEY `idx_participant` (`participant_id`),
|
||||||
|
KEY `idx_create_time` (`create_time`),
|
||||||
|
KEY `idx_tenant` (`tenant_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='赛程调度调整日志表';
|
||||||
|
|
||||||
|
-- 验证表是否创建成功
|
||||||
|
SELECT
|
||||||
|
TABLE_NAME,
|
||||||
|
TABLE_COMMENT,
|
||||||
|
TABLE_ROWS
|
||||||
|
FROM
|
||||||
|
INFORMATION_SCHEMA.TABLES
|
||||||
|
WHERE
|
||||||
|
TABLE_SCHEMA = 'blade'
|
||||||
|
AND TABLE_NAME = 'martial_schedule_adjustment_log';
|
||||||
|
|
||||||
|
-- 查看表结构
|
||||||
|
DESC martial_schedule_adjustment_log;
|
||||||
|
|
||||||
|
SELECT '调度日志表创建成功!' AS status;
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
-- 调试检查脚本
|
|
||||||
USE martial_db;
|
|
||||||
|
|
||||||
-- 检查参赛者的project_id是否都有对应的项目
|
|
||||||
SELECT
|
|
||||||
'检查参赛者项目关联' AS check_item,
|
|
||||||
a.id,
|
|
||||||
a.project_id,
|
|
||||||
a.player_name,
|
|
||||||
p.id AS project_exists,
|
|
||||||
p.project_name,
|
|
||||||
p.type AS project_type
|
|
||||||
FROM martial_athlete a
|
|
||||||
LEFT JOIN martial_project p ON a.project_id = p.id
|
|
||||||
WHERE a.competition_id = 200
|
|
||||||
LIMIT 10;
|
|
||||||
|
|
||||||
-- 检查是否有参赛者的project_id为NULL或找不到对应项目
|
|
||||||
SELECT
|
|
||||||
'检查异常数据' AS check_item,
|
|
||||||
COUNT(*) AS total_athletes,
|
|
||||||
SUM(CASE WHEN project_id IS NULL THEN 1 ELSE 0 END) AS null_project_id,
|
|
||||||
SUM(CASE WHEN p.id IS NULL THEN 1 ELSE 0 END) AS project_not_found
|
|
||||||
FROM martial_athlete a
|
|
||||||
LEFT JOIN martial_project p ON a.project_id = p.id
|
|
||||||
WHERE a.competition_id = 200;
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
@echo off
|
|
||||||
REM =============================================
|
|
||||||
REM 赛程编排系统数据库部署脚本
|
|
||||||
REM =============================================
|
|
||||||
echo.
|
|
||||||
echo ========================================
|
|
||||||
echo 赛程编排系统 - 数据库部署工具
|
|
||||||
echo ========================================
|
|
||||||
echo.
|
|
||||||
|
|
||||||
REM 检查MySQL是否安装
|
|
||||||
where mysql >nul 2>&1
|
|
||||||
if %errorlevel% neq 0 (
|
|
||||||
echo [错误] 未找到MySQL命令,请确保MySQL已安装并添加到系统PATH
|
|
||||||
echo.
|
|
||||||
echo 常见MySQL安装路径:
|
|
||||||
echo - C:\Program Files\MySQL\MySQL Server 8.0\bin
|
|
||||||
echo - C:\xampp\mysql\bin
|
|
||||||
echo.
|
|
||||||
pause
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
echo [1/3] 检测到MySQL...
|
|
||||||
|
|
||||||
REM 设置数据库信息
|
|
||||||
set DB_NAME=martial_db
|
|
||||||
set SCRIPT_PATH=%~dp0deploy_schedule_tables.sql
|
|
||||||
|
|
||||||
echo [2/3] 准备执行SQL脚本...
|
|
||||||
echo 数据库: %DB_NAME%
|
|
||||||
echo 脚本: %SCRIPT_PATH%
|
|
||||||
echo.
|
|
||||||
|
|
||||||
REM 提示用户输入密码
|
|
||||||
echo 请输入MySQL root密码 (如果没有密码直接按回车):
|
|
||||||
set /p MYSQL_PWD=密码:
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo [3/3] 正在执行SQL脚本...
|
|
||||||
echo.
|
|
||||||
|
|
||||||
REM 执行SQL脚本
|
|
||||||
if "%MYSQL_PWD%"=="" (
|
|
||||||
mysql -u root %DB_NAME% < "%SCRIPT_PATH%"
|
|
||||||
) else (
|
|
||||||
mysql -u root -p%MYSQL_PWD% %DB_NAME% < "%SCRIPT_PATH%"
|
|
||||||
)
|
|
||||||
|
|
||||||
if %errorlevel% equ 0 (
|
|
||||||
echo.
|
|
||||||
echo ========================================
|
|
||||||
echo ✓ 数据库表创建成功!
|
|
||||||
echo ========================================
|
|
||||||
echo.
|
|
||||||
echo 已创建以下4张表:
|
|
||||||
echo 1. martial_schedule_group - 赛程编排分组表
|
|
||||||
echo 2. martial_schedule_detail - 赛程编排明细表
|
|
||||||
echo 3. martial_schedule_participant - 参赛者关联表
|
|
||||||
echo 4. martial_schedule_status - 编排状态表
|
|
||||||
echo.
|
|
||||||
echo 下一步:
|
|
||||||
echo 1. 导入测试数据 (可选)
|
|
||||||
echo cd ..\..\..
|
|
||||||
echo cd martial-web\test-data
|
|
||||||
echo mysql -u root -p%MYSQL_PWD% martial_db ^< create_100_team_participants.sql
|
|
||||||
echo.
|
|
||||||
echo 2. 启动后端服务
|
|
||||||
echo cd martial-master
|
|
||||||
echo mvn spring-boot:run
|
|
||||||
echo.
|
|
||||||
echo 3. 访问前端页面
|
|
||||||
echo http://localhost:3000/martial/schedule?competitionId=200
|
|
||||||
echo.
|
|
||||||
) else (
|
|
||||||
echo.
|
|
||||||
echo ========================================
|
|
||||||
echo ✗ 数据库表创建失败!
|
|
||||||
echo ========================================
|
|
||||||
echo.
|
|
||||||
echo 可能的原因:
|
|
||||||
echo 1. 数据库 %DB_NAME% 不存在
|
|
||||||
echo 2. MySQL密码错误
|
|
||||||
echo 3. 权限不足
|
|
||||||
echo.
|
|
||||||
echo 解决方法:
|
|
||||||
echo 1. 先创建数据库: CREATE DATABASE martial_db;
|
|
||||||
echo 2. 检查MySQL密码是否正确
|
|
||||||
echo 3. 确保用户有CREATE TABLE权限
|
|
||||||
echo.
|
|
||||||
)
|
|
||||||
|
|
||||||
pause
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
-- =============================================
|
|
||||||
-- 武术赛事赛程编排系统 - 数据库表创建脚本(带数据库选择)
|
|
||||||
-- =============================================
|
|
||||||
-- 创建日期: 2025-12-09
|
|
||||||
-- 版本: v1.1
|
|
||||||
-- 说明: 自动选择正确的数据库并创建赛程编排相关的4张核心表
|
|
||||||
-- =============================================
|
|
||||||
|
|
||||||
-- 选择数据库(根据实际情况修改)
|
|
||||||
USE martial_db;
|
|
||||||
|
|
||||||
-- 检查表是否已存在,如果存在则删除(可选,生产环境请注释掉)
|
|
||||||
-- DROP TABLE IF EXISTS martial_schedule_participant;
|
|
||||||
-- DROP TABLE IF EXISTS martial_schedule_detail;
|
|
||||||
-- DROP TABLE IF EXISTS martial_schedule_group;
|
|
||||||
-- DROP TABLE IF EXISTS martial_schedule_status;
|
|
||||||
|
|
||||||
-- 1. 赛程编排分组表
|
|
||||||
CREATE TABLE IF NOT EXISTS `martial_schedule_group` (
|
|
||||||
`id` bigint(0) NOT NULL COMMENT '主键ID',
|
|
||||||
`competition_id` bigint(0) NOT NULL COMMENT '赛事ID',
|
|
||||||
`group_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '分组名称(如:太极拳男组)',
|
|
||||||
`project_id` bigint(0) NOT NULL COMMENT '项目ID',
|
|
||||||
`project_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '项目名称',
|
|
||||||
`category` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组别(成年组、少年组等)',
|
|
||||||
`project_type` tinyint(1) NOT NULL DEFAULT 1 COMMENT '项目类型(1=个人 2=集体)',
|
|
||||||
`display_order` int(0) NOT NULL DEFAULT 0 COMMENT '显示顺序(集体项目优先,数字越小越靠前)',
|
|
||||||
`total_participants` int(0) NULL DEFAULT 0 COMMENT '总参赛人数',
|
|
||||||
`total_teams` int(0) NULL DEFAULT 0 COMMENT '总队伍数(仅集体项目)',
|
|
||||||
`estimated_duration` int(0) NULL DEFAULT 0 COMMENT '预计时长(分钟)',
|
|
||||||
`create_user` bigint(0) NULL DEFAULT NULL,
|
|
||||||
`create_dept` bigint(0) NULL DEFAULT NULL,
|
|
||||||
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
|
|
||||||
`update_user` bigint(0) NULL DEFAULT NULL,
|
|
||||||
`update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
|
|
||||||
`status` int(0) NULL DEFAULT 1 COMMENT '状态(1-启用,2-禁用)',
|
|
||||||
`is_deleted` int(0) NULL DEFAULT 0,
|
|
||||||
`tenant_id` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000',
|
|
||||||
PRIMARY KEY (`id`) USING BTREE,
|
|
||||||
INDEX `idx_competition` (`competition_id`) USING BTREE,
|
|
||||||
INDEX `idx_project` (`project_id`) USING BTREE,
|
|
||||||
INDEX `idx_display_order` (`display_order`) USING BTREE,
|
|
||||||
INDEX `idx_tenant` (`tenant_id`) USING BTREE
|
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '赛程编排分组表' ROW_FORMAT = Dynamic;
|
|
||||||
|
|
||||||
-- 2. 赛程编排明细表(场地时间段分配)
|
|
||||||
CREATE TABLE IF NOT EXISTS `martial_schedule_detail` (
|
|
||||||
`id` bigint(0) NOT NULL COMMENT '主键ID',
|
|
||||||
`schedule_group_id` bigint(0) NOT NULL COMMENT '分组ID',
|
|
||||||
`competition_id` bigint(0) NOT NULL COMMENT '赛事ID',
|
|
||||||
`venue_id` bigint(0) NOT NULL COMMENT '场地ID',
|
|
||||||
`venue_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '场地名称',
|
|
||||||
`schedule_date` date NOT NULL COMMENT '比赛日期',
|
|
||||||
`time_period` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '时间段(morning/afternoon)',
|
|
||||||
`time_slot` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '时间点(08:30/13:30)',
|
|
||||||
`estimated_start_time` datetime(0) NULL DEFAULT NULL COMMENT '预计开始时间',
|
|
||||||
`estimated_end_time` datetime(0) NULL DEFAULT NULL COMMENT '预计结束时间',
|
|
||||||
`estimated_duration` int(0) NULL DEFAULT 0 COMMENT '预计时长(分钟)',
|
|
||||||
`participant_count` int(0) NULL DEFAULT 0 COMMENT '参赛人数',
|
|
||||||
`sort_order` int(0) NULL DEFAULT 0 COMMENT '场内顺序',
|
|
||||||
`create_user` bigint(0) NULL DEFAULT NULL,
|
|
||||||
`create_dept` bigint(0) NULL DEFAULT NULL,
|
|
||||||
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
|
|
||||||
`update_user` bigint(0) NULL DEFAULT NULL,
|
|
||||||
`update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
|
|
||||||
`status` int(0) NULL DEFAULT 1 COMMENT '状态(1-未开始,2-进行中,3-已完成)',
|
|
||||||
`is_deleted` int(0) NULL DEFAULT 0,
|
|
||||||
`tenant_id` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000',
|
|
||||||
PRIMARY KEY (`id`) USING BTREE,
|
|
||||||
INDEX `idx_group` (`schedule_group_id`) USING BTREE,
|
|
||||||
INDEX `idx_competition` (`competition_id`) USING BTREE,
|
|
||||||
INDEX `idx_venue_time` (`venue_id`, `schedule_date`, `time_slot`) USING BTREE,
|
|
||||||
INDEX `idx_tenant` (`tenant_id`) USING BTREE
|
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '赛程编排明细表(场地时间段分配)' ROW_FORMAT = Dynamic;
|
|
||||||
|
|
||||||
-- 3. 赛程编排参赛者关联表
|
|
||||||
CREATE TABLE IF NOT EXISTS `martial_schedule_participant` (
|
|
||||||
`id` bigint(0) NOT NULL COMMENT '主键ID',
|
|
||||||
`schedule_detail_id` bigint(0) NOT NULL COMMENT '编排明细ID',
|
|
||||||
`schedule_group_id` bigint(0) NOT NULL COMMENT '分组ID',
|
|
||||||
`participant_id` bigint(0) NOT NULL COMMENT '参赛者ID(关联martial_athlete表)',
|
|
||||||
`organization` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '单位名称',
|
|
||||||
`player_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '选手姓名',
|
|
||||||
`project_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '项目名称',
|
|
||||||
`category` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组别',
|
|
||||||
`performance_order` int(0) NULL DEFAULT 0 COMMENT '出场顺序',
|
|
||||||
`create_user` bigint(0) NULL DEFAULT NULL,
|
|
||||||
`create_dept` bigint(0) NULL DEFAULT NULL,
|
|
||||||
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
|
|
||||||
`update_user` bigint(0) NULL DEFAULT NULL,
|
|
||||||
`update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
|
|
||||||
`status` int(0) NULL DEFAULT 1 COMMENT '状态(1-待出场,2-已出场)',
|
|
||||||
`is_deleted` int(0) NULL DEFAULT 0,
|
|
||||||
`tenant_id` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000',
|
|
||||||
PRIMARY KEY (`id`) USING BTREE,
|
|
||||||
INDEX `idx_detail` (`schedule_detail_id`) USING BTREE,
|
|
||||||
INDEX `idx_group` (`schedule_group_id`) USING BTREE,
|
|
||||||
INDEX `idx_participant` (`participant_id`) USING BTREE,
|
|
||||||
INDEX `idx_tenant` (`tenant_id`) USING BTREE
|
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '赛程编排参赛者关联表' ROW_FORMAT = Dynamic;
|
|
||||||
|
|
||||||
-- 4. 赛程编排状态表
|
|
||||||
CREATE TABLE IF NOT EXISTS `martial_schedule_status` (
|
|
||||||
`id` bigint(0) NOT NULL COMMENT '主键ID',
|
|
||||||
`competition_id` bigint(0) NOT NULL COMMENT '赛事ID(唯一)',
|
|
||||||
`schedule_status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '编排状态(0=未编排 1=编排中 2=已保存锁定)',
|
|
||||||
`last_auto_schedule_time` datetime(0) NULL DEFAULT NULL COMMENT '最后自动编排时间',
|
|
||||||
`locked_time` datetime(0) NULL DEFAULT NULL COMMENT '锁定时间',
|
|
||||||
`locked_by` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '锁定人',
|
|
||||||
`total_groups` int(0) NULL DEFAULT 0 COMMENT '总分组数',
|
|
||||||
`total_participants` int(0) NULL DEFAULT 0 COMMENT '总参赛人数',
|
|
||||||
`create_user` bigint(0) NULL DEFAULT NULL,
|
|
||||||
`create_dept` bigint(0) NULL DEFAULT NULL,
|
|
||||||
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
|
|
||||||
`update_user` bigint(0) NULL DEFAULT NULL,
|
|
||||||
`update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
|
|
||||||
`status` int(0) NULL DEFAULT 1 COMMENT '状态(1-启用,2-禁用)',
|
|
||||||
`is_deleted` int(0) NULL DEFAULT 0,
|
|
||||||
`tenant_id` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000',
|
|
||||||
PRIMARY KEY (`id`) USING BTREE,
|
|
||||||
UNIQUE INDEX `uk_competition` (`competition_id`) USING BTREE,
|
|
||||||
INDEX `idx_tenant` (`tenant_id`) USING BTREE,
|
|
||||||
INDEX `idx_schedule_status` (`schedule_status`) USING BTREE
|
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '赛程编排状态表' ROW_FORMAT = Dynamic;
|
|
||||||
|
|
||||||
-- 验证表是否创建成功
|
|
||||||
SELECT
|
|
||||||
'表创建完成' AS message,
|
|
||||||
COUNT(*) AS table_count
|
|
||||||
FROM information_schema.tables
|
|
||||||
WHERE table_schema = 'martial_db'
|
|
||||||
AND table_name IN (
|
|
||||||
'martial_schedule_group',
|
|
||||||
'martial_schedule_detail',
|
|
||||||
'martial_schedule_participant',
|
|
||||||
'martial_schedule_status'
|
|
||||||
);
|
|
||||||
|
|
||||||
-- =============================================
|
|
||||||
-- 使用说明
|
|
||||||
-- =============================================
|
|
||||||
--
|
|
||||||
-- 1. 确认数据库名称
|
|
||||||
-- 如果你的数据库名称不是 martial_db,请修改第9行的 USE 语句
|
|
||||||
--
|
|
||||||
-- 2. 执行脚本
|
|
||||||
-- 方式1: 在MySQL客户端中直接执行
|
|
||||||
-- mysql -u root -p < deploy_schedule_tables.sql
|
|
||||||
--
|
|
||||||
-- 方式2: 在数据库管理工具中执行(Navicat/DBeaver等)
|
|
||||||
--
|
|
||||||
-- 3. 验证
|
|
||||||
-- 执行完成后应该看到 "table_count = 4" 的结果
|
|
||||||
--
|
|
||||||
-- 4. 下一步
|
|
||||||
-- 执行测试数据导入脚本:
|
|
||||||
-- mysql -u root -p martial_db < martial-web/test-data/create_100_team_participants.sql
|
|
||||||
--
|
|
||||||
-- =============================================
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
-- ================================================================
|
|
||||||
-- 修复参赛选手表 order_id 字段约束
|
|
||||||
-- 问题:Field 'order_id' doesn't have a default value
|
|
||||||
-- 解决:允许 order_id 为 NULL(支持直接添加参赛选手,无需订单)
|
|
||||||
-- 日期:2025-12-06
|
|
||||||
-- ================================================================
|
|
||||||
|
|
||||||
-- 使用正确的数据库
|
|
||||||
USE martial_db;
|
|
||||||
|
|
||||||
-- 修改 order_id 字段,允许为 NULL
|
|
||||||
ALTER TABLE martial_athlete
|
|
||||||
MODIFY COLUMN order_id bigint(20) NULL DEFAULT NULL COMMENT '订单ID';
|
|
||||||
|
|
||||||
-- 验证修改
|
|
||||||
DESC martial_athlete;
|
|
||||||
|
|
||||||
-- 显示修改结果
|
|
||||||
SELECT '✓ order_id 字段已修改为可空' AS 修复结果;
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
-- ================================================================
|
|
||||||
-- 场地表字段修复脚本
|
|
||||||
-- 用途:为 martial_venue 表添加缺失的 max_capacity 字段
|
|
||||||
-- 问题:Error: Unknown column 'max_capacity' in 'field list'
|
|
||||||
-- 日期:2025-12-06
|
|
||||||
-- ================================================================
|
|
||||||
|
|
||||||
-- 方案1:直接 DROP 表并重新创建(如果表中没有重要数据)
|
|
||||||
-- 如果表中有数据,请跳过此步骤,使用方案2
|
|
||||||
DROP TABLE IF EXISTS `martial_venue`;
|
|
||||||
|
|
||||||
CREATE TABLE `martial_venue` (
|
|
||||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
|
||||||
`tenant_id` varchar(12) DEFAULT '000000' COMMENT '租户ID',
|
|
||||||
`competition_id` bigint(20) NOT NULL COMMENT '赛事ID',
|
|
||||||
`venue_name` varchar(100) NOT NULL COMMENT '场地名称',
|
|
||||||
`venue_code` varchar(50) DEFAULT NULL COMMENT '场地编码',
|
|
||||||
`max_capacity` int(11) DEFAULT 100 COMMENT '最大容纳人数',
|
|
||||||
`location` varchar(200) DEFAULT NULL COMMENT '位置/地点',
|
|
||||||
`description` varchar(500) DEFAULT NULL COMMENT '场地描述',
|
|
||||||
`facilities` varchar(500) DEFAULT NULL COMMENT '场地设施',
|
|
||||||
`sort_order` int(11) DEFAULT 0 COMMENT '排序',
|
|
||||||
`status` int(2) DEFAULT 1 COMMENT '状态(0-禁用,1-启用)',
|
|
||||||
`create_user` bigint(20) DEFAULT NULL COMMENT '创建人',
|
|
||||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
|
||||||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
|
||||||
`update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
|
|
||||||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
|
||||||
`is_deleted` int(2) DEFAULT 0 COMMENT '是否已删除',
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
KEY `idx_competition_id` (`competition_id`),
|
|
||||||
KEY `idx_tenant_id` (`tenant_id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='场地信息表';
|
|
||||||
|
|
||||||
-- ================================================================
|
|
||||||
-- 验证表结构
|
|
||||||
-- ================================================================
|
|
||||||
DESC martial_venue;
|
|
||||||
|
|
||||||
SELECT '场地表已重新创建,包含 max_capacity 字段' AS result;
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
-- =============================================
|
|
||||||
-- 赛程编排系统 - 完整测试数据初始化
|
|
||||||
-- =============================================
|
|
||||||
USE martial_db;
|
|
||||||
|
|
||||||
-- 1. 确保赛事存在并配置了时间
|
|
||||||
UPDATE martial_competition
|
|
||||||
SET
|
|
||||||
competition_start_time = '2025-11-06 08:00:00',
|
|
||||||
competition_end_time = '2025-11-08 18:00:00'
|
|
||||||
WHERE id = 200;
|
|
||||||
|
|
||||||
-- 检查赛事是否存在
|
|
||||||
SELECT
|
|
||||||
'1. 检查赛事' AS step,
|
|
||||||
CASE
|
|
||||||
WHEN COUNT(*) > 0 THEN CONCAT('✓ 赛事ID=200存在, 名称: ', MAX(competition_name))
|
|
||||||
ELSE '✗ 赛事ID=200不存在,请先创建赛事'
|
|
||||||
END AS result
|
|
||||||
FROM martial_competition
|
|
||||||
WHERE id = 200;
|
|
||||||
|
|
||||||
-- 2. 创建场地数据(如果不存在)
|
|
||||||
INSERT IGNORE INTO martial_venue (id, competition_id, venue_name, venue_type, capacity, create_time, is_deleted)
|
|
||||||
VALUES
|
|
||||||
(1, 200, '一号场地', '主场地', 100, NOW(), 0),
|
|
||||||
(2, 200, '二号场地', '副场地', 100, NOW(), 0),
|
|
||||||
(3, 200, '三号场地', '副场地', 100, NOW(), 0),
|
|
||||||
(4, 200, '四号场地', '副场地', 100, NOW(), 0);
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
'2. 检查场地' AS step,
|
|
||||||
CONCAT('✓ 已有 ', COUNT(*), ' 个场地') AS result
|
|
||||||
FROM martial_venue
|
|
||||||
WHERE competition_id = 200 AND is_deleted = 0;
|
|
||||||
|
|
||||||
-- 3. 创建项目数据(如果不存在)
|
|
||||||
INSERT IGNORE INTO martial_project (id, project_name, type, category, estimated_duration, create_time)
|
|
||||||
VALUES
|
|
||||||
(1001, '太极拳集体', 3, '成年组', 5, NOW()),
|
|
||||||
(1002, '长拳集体', 3, '成年组', 5, NOW()),
|
|
||||||
(1003, '剑术集体', 3, '成年组', 5, NOW()),
|
|
||||||
(1004, '刀术集体', 3, '成年组', 5, NOW()),
|
|
||||||
(1005, '棍术集体', 3, '少年组', 5, NOW());
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
'3. 检查项目' AS step,
|
|
||||||
CONCAT('✓ 已有 ', COUNT(*), ' 个项目') AS result
|
|
||||||
FROM martial_project
|
|
||||||
WHERE id BETWEEN 1001 AND 1005;
|
|
||||||
|
|
||||||
-- 4. 创建测试参赛者数据(少量测试数据)
|
|
||||||
DELETE FROM martial_athlete WHERE competition_id = 200;
|
|
||||||
|
|
||||||
INSERT INTO martial_athlete (
|
|
||||||
competition_id, project_id, organization, team_name,
|
|
||||||
player_name, gender, age, phone, category, create_time, is_deleted
|
|
||||||
)
|
|
||||||
VALUES
|
|
||||||
-- 太极拳集体 - 队伍1: 少林寺武校 (5人)
|
|
||||||
(200, 1001, '少林寺武校', '少林寺武校', '张明远', '男', 25, '13800001001', '成年组', NOW(), 0),
|
|
||||||
(200, 1001, '少林寺武校', '少林寺武校', '李华强', '男', 26, '13800001002', '成年组', NOW(), 0),
|
|
||||||
(200, 1001, '少林寺武校', '少林寺武校', '王建国', '男', 24, '13800001003', '成年组', NOW(), 0),
|
|
||||||
(200, 1001, '少林寺武校', '少林寺武校', '赵小明', '男', 23, '13800001004', '成年组', NOW(), 0),
|
|
||||||
(200, 1001, '少林寺武校', '少林寺武校', '刘德华', '男', 27, '13800001005', '成年组', NOW(), 0),
|
|
||||||
|
|
||||||
-- 太极拳集体 - 队伍2: 武当派 (5人)
|
|
||||||
(200, 1001, '武当派', '武当派', '陈剑锋', '男', 28, '13800001011', '成年组', NOW(), 0),
|
|
||||||
(200, 1001, '武当派', '武当派', '周杰伦', '男', 25, '13800001012', '成年组', NOW(), 0),
|
|
||||||
(200, 1001, '武当派', '武当派', '吴彦祖', '男', 26, '13800001013', '成年组', NOW(), 0),
|
|
||||||
(200, 1001, '武当派', '武当派', '郑伊健', '男', 24, '13800001014', '成年组', NOW(), 0),
|
|
||||||
(200, 1001, '武当派', '武当派', '谢霆锋', '男', 27, '13800001015', '成年组', NOW(), 0),
|
|
||||||
|
|
||||||
-- 长拳集体 - 队伍1: 峨眉派 (5人)
|
|
||||||
(200, 1002, '峨眉派', '峨眉派', '小龙女', '女', 22, '13800002001', '成年组', NOW(), 0),
|
|
||||||
(200, 1002, '峨眉派', '峨眉派', '黄蓉', '女', 23, '13800002002', '成年组', NOW(), 0),
|
|
||||||
(200, 1002, '峨眉派', '峨眉派', '赵敏', '女', 24, '13800002003', '成年组', NOW(), 0),
|
|
||||||
(200, 1002, '峨眉派', '峨眉派', '周芷若', '女', 22, '13800002004', '成年组', NOW(), 0),
|
|
||||||
(200, 1002, '峨眉派', '峨眉派', '任盈盈', '女', 23, '13800002005', '成年组', NOW(), 0),
|
|
||||||
|
|
||||||
-- 长拳集体 - 队伍2: 华山派 (5人)
|
|
||||||
(200, 1002, '华山派', '华山派', '令狐冲', '男', 27, '13800002011', '成年组', NOW(), 0),
|
|
||||||
(200, 1002, '华山派', '华山派', '风清扬', '男', 28, '13800002012', '成年组', NOW(), 0),
|
|
||||||
(200, 1002, '华山派', '华山派', '岳不群', '男', 29, '13800002013', '成年组', NOW(), 0),
|
|
||||||
(200, 1002, '华山派', '华山派', '宁中则', '女', 26, '13800002014', '成年组', NOW(), 0),
|
|
||||||
(200, 1002, '华山派', '华山派', '岳灵珊', '女', 24, '13800002015', '成年组', NOW(), 0);
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
'4. 检查参赛者' AS step,
|
|
||||||
CONCAT('✓ 已有 ', COUNT(*), ' 个参赛者 (', COUNT(DISTINCT organization), ' 个队伍)') AS result
|
|
||||||
FROM martial_athlete
|
|
||||||
WHERE competition_id = 200 AND is_deleted = 0;
|
|
||||||
|
|
||||||
-- 5. 清空旧的编排数据(如果有)
|
|
||||||
DELETE FROM martial_schedule_participant WHERE schedule_group_id IN (
|
|
||||||
SELECT id FROM martial_schedule_group WHERE competition_id = 200
|
|
||||||
);
|
|
||||||
DELETE FROM martial_schedule_detail WHERE competition_id = 200;
|
|
||||||
DELETE FROM martial_schedule_group WHERE competition_id = 200;
|
|
||||||
DELETE FROM martial_schedule_status WHERE competition_id = 200;
|
|
||||||
|
|
||||||
SELECT '5. 清理旧数据' AS step, '✓ 已清空旧的编排数据' AS result;
|
|
||||||
|
|
||||||
-- 6. 最终验证
|
|
||||||
SELECT
|
|
||||||
'6. 数据完整性检查' AS step,
|
|
||||||
CONCAT(
|
|
||||||
'✓ 赛事: ', (SELECT COUNT(*) FROM martial_competition WHERE id = 200),
|
|
||||||
', 场地: ', (SELECT COUNT(*) FROM martial_venue WHERE competition_id = 200 AND is_deleted = 0),
|
|
||||||
', 项目: ', (SELECT COUNT(*) FROM martial_project WHERE id BETWEEN 1001 AND 1005),
|
|
||||||
', 参赛者: ', (SELECT COUNT(*) FROM martial_athlete WHERE competition_id = 200 AND is_deleted = 0)
|
|
||||||
) AS result;
|
|
||||||
|
|
||||||
-- 7. 检查赛事时间配置
|
|
||||||
SELECT
|
|
||||||
'7. 赛事时间配置' AS step,
|
|
||||||
CONCAT(
|
|
||||||
'开始: ', IFNULL(competition_start_time, '未配置'),
|
|
||||||
', 结束: ', IFNULL(competition_end_time, '未配置')
|
|
||||||
) AS result
|
|
||||||
FROM martial_competition
|
|
||||||
WHERE id = 200;
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
'========================================' AS '',
|
|
||||||
'✓ 测试数据初始化完成!' AS result,
|
|
||||||
'========================================' AS '';
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
'下一步: 测试API' AS action,
|
|
||||||
'curl -X POST http://localhost:8123/martial/schedule/auto-arrange -H "Content-Type: application/json" -d "{\"competitionId\": 200}"' AS command;
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
-- =====================================================
|
|
||||||
-- 插入测试裁判邀请数据
|
|
||||||
-- 执行时间: 2025-12-12
|
|
||||||
-- =====================================================
|
|
||||||
|
|
||||||
USE blade;
|
|
||||||
|
|
||||||
-- 首先确保有测试赛事数据
|
|
||||||
-- 假设已经有赛事ID为 1 的数据
|
|
||||||
|
|
||||||
-- 首先确保有测试裁判数据
|
|
||||||
-- 插入测试裁判(如果不存在)
|
|
||||||
INSERT IGNORE INTO martial_judge (id, name, gender, phone, id_card, referee_type, level, specialty, create_time, update_time, status, is_deleted)
|
|
||||||
VALUES
|
|
||||||
(1, '张三', 1, '13800138001', '110101199001011234', 2, '国家级', '太极拳', NOW(), NOW(), 1, 0),
|
|
||||||
(2, '李四', 1, '13800138002', '110101199002021234', 2, '一级', '长拳', NOW(), NOW(), 1, 0),
|
|
||||||
(3, '王五', 2, '13800138003', '110101199003031234', 2, '二级', '剑术', NOW(), NOW(), 1, 0),
|
|
||||||
(4, '赵六', 1, '13800138004', '110101199004041234', 1, '国家级', '刀术', NOW(), NOW(), 1, 0),
|
|
||||||
(5, '钱七', 2, '13800138005', '110101199005051234', 2, '三级', '棍术', NOW(), NOW(), 1, 0);
|
|
||||||
|
|
||||||
-- 插入测试邀请数据
|
|
||||||
INSERT INTO martial_judge_invite (
|
|
||||||
id,
|
|
||||||
competition_id,
|
|
||||||
judge_id,
|
|
||||||
invite_code,
|
|
||||||
role,
|
|
||||||
invite_status,
|
|
||||||
invite_time,
|
|
||||||
reply_time,
|
|
||||||
reply_note,
|
|
||||||
contact_phone,
|
|
||||||
contact_email,
|
|
||||||
invite_message,
|
|
||||||
expire_time,
|
|
||||||
is_used,
|
|
||||||
create_time,
|
|
||||||
update_time,
|
|
||||||
status,
|
|
||||||
is_deleted
|
|
||||||
)
|
|
||||||
VALUES
|
|
||||||
-- 待回复的邀请
|
|
||||||
(1, 1, 1, 'INV2025001', 'judge', 0, NOW(), NULL, NULL, '13800138001', 'zhangsan@example.com', '诚邀您担任本次武术比赛的裁判', DATE_ADD(NOW(), INTERVAL 30 DAY), 0, NOW(), NOW(), 1, 0),
|
|
||||||
(2, 1, 2, 'INV2025002', 'judge', 0, NOW(), NULL, NULL, '13800138002', 'lisi@example.com', '诚邀您担任本次武术比赛的裁判', DATE_ADD(NOW(), INTERVAL 30 DAY), 0, NOW(), NOW(), 1, 0),
|
|
||||||
|
|
||||||
-- 已接受的邀请
|
|
||||||
(3, 1, 3, 'INV2025003', 'judge', 1, DATE_SUB(NOW(), INTERVAL 2 DAY), DATE_SUB(NOW(), INTERVAL 1 DAY), '很荣幸能参加,我会准时到场', '13800138003', 'wangwu@example.com', '诚邀您担任本次武术比赛的裁判', DATE_ADD(NOW(), INTERVAL 30 DAY), 1, DATE_SUB(NOW(), INTERVAL 2 DAY), NOW(), 1, 0),
|
|
||||||
(4, 1, 4, 'INV2025004', 'chief_judge', 1, DATE_SUB(NOW(), INTERVAL 3 DAY), DATE_SUB(NOW(), INTERVAL 2 DAY), '感谢邀请,我会认真履行裁判长职责', '13800138004', 'zhaoliu@example.com', '诚邀您担任本次武术比赛的裁判长', DATE_ADD(NOW(), INTERVAL 30 DAY), 1, DATE_SUB(NOW(), INTERVAL 3 DAY), NOW(), 1, 0),
|
|
||||||
|
|
||||||
-- 已拒绝的邀请
|
|
||||||
(5, 1, 5, 'INV2025005', 'judge', 2, DATE_SUB(NOW(), INTERVAL 5 DAY), DATE_SUB(NOW(), INTERVAL 4 DAY), '非常抱歉,那段时间有其他安排', '13800138005', 'qianqi@example.com', '诚邀您担任本次武术比赛的裁判', DATE_ADD(NOW(), INTERVAL 30 DAY), 0, DATE_SUB(NOW(), INTERVAL 5 DAY), NOW(), 1, 0);
|
|
||||||
|
|
||||||
-- 验证插入结果
|
|
||||||
SELECT
|
|
||||||
ji.id,
|
|
||||||
ji.invite_code,
|
|
||||||
j.name AS judge_name,
|
|
||||||
j.level AS judge_level,
|
|
||||||
ji.contact_phone,
|
|
||||||
ji.contact_email,
|
|
||||||
ji.invite_status,
|
|
||||||
CASE ji.invite_status
|
|
||||||
WHEN 0 THEN '待回复'
|
|
||||||
WHEN 1 THEN '已接受'
|
|
||||||
WHEN 2 THEN '已拒绝'
|
|
||||||
WHEN 3 THEN '已取消'
|
|
||||||
ELSE '未知'
|
|
||||||
END AS status_text,
|
|
||||||
ji.invite_time,
|
|
||||||
ji.reply_time,
|
|
||||||
ji.reply_note
|
|
||||||
FROM
|
|
||||||
martial_judge_invite ji
|
|
||||||
LEFT JOIN martial_judge j ON ji.judge_id = j.id
|
|
||||||
WHERE
|
|
||||||
ji.competition_id = 1
|
|
||||||
AND ji.is_deleted = 0
|
|
||||||
ORDER BY
|
|
||||||
ji.id;
|
|
||||||
|
|
||||||
SELECT 'Test data inserted successfully!' AS status;
|
|
||||||
37
database/martial-db/martial_competition_attachment.sql
Normal file
37
database/martial-db/martial_competition_attachment.sql
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
-- 赛事通用附件表
|
||||||
|
-- 支持多种附件类型:赛事发布(info)、赛事规程(rules)、活动日程(schedule)、成绩(results)、奖牌榜(medals)、图片直播(photos)
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `martial_competition_attachment`;
|
||||||
|
CREATE TABLE `martial_competition_attachment` (
|
||||||
|
`id` bigint NOT NULL COMMENT '主键ID',
|
||||||
|
`tenant_id` varchar(12) DEFAULT '000000' COMMENT '租户ID',
|
||||||
|
`competition_id` bigint NOT NULL COMMENT '赛事ID',
|
||||||
|
`attachment_type` varchar(20) NOT NULL COMMENT '附件类型:info-赛事发布, rules-赛事规程, schedule-活动日程, results-成绩, medals-奖牌榜, photos-图片直播',
|
||||||
|
`file_name` varchar(255) NOT NULL COMMENT '文件名称',
|
||||||
|
`file_url` varchar(500) NOT NULL COMMENT '文件URL',
|
||||||
|
`file_size` bigint DEFAULT NULL COMMENT '文件大小(字节)',
|
||||||
|
`file_type` varchar(20) DEFAULT NULL COMMENT '文件类型(pdf/doc/docx/xls/xlsx/jpg/png等)',
|
||||||
|
`order_num` int DEFAULT 0 COMMENT '排序序号',
|
||||||
|
`status` int DEFAULT 1 COMMENT '状态(1-启用 0-禁用)',
|
||||||
|
`create_user` bigint DEFAULT NULL COMMENT '创建人',
|
||||||
|
`create_dept` bigint DEFAULT NULL COMMENT '创建部门',
|
||||||
|
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`update_user` bigint DEFAULT NULL COMMENT '更新人',
|
||||||
|
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
`is_deleted` int DEFAULT 0 COMMENT '是否已删除(0-否 1-是)',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_competition_id` (`competition_id`),
|
||||||
|
KEY `idx_attachment_type` (`attachment_type`),
|
||||||
|
KEY `idx_competition_type` (`competition_id`, `attachment_type`),
|
||||||
|
KEY `idx_tenant_id` (`tenant_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='赛事通用附件表';
|
||||||
|
|
||||||
|
-- 插入测试数据(假设赛事ID为1)
|
||||||
|
INSERT INTO `martial_competition_attachment` (`id`, `tenant_id`, `competition_id`, `attachment_type`, `file_name`, `file_url`, `file_size`, `file_type`, `order_num`, `status`) VALUES
|
||||||
|
(1, '000000', 1, 'info', '2025年郑州武术大赛通知.pdf', 'http://example.com/files/notice.pdf', 1258291, 'pdf', 1, 1),
|
||||||
|
(2, '000000', 1, 'rules', '2025年郑州武术大赛竞赛规程.pdf', 'http://example.com/files/rules.pdf', 2621440, 'pdf', 1, 1),
|
||||||
|
(3, '000000', 1, 'rules', '参赛报名表.pdf', 'http://example.com/files/form.pdf', 163840, 'pdf', 2, 1),
|
||||||
|
(4, '000000', 1, 'schedule', '比赛日程安排表.pdf', 'http://example.com/files/schedule.pdf', 911360, 'pdf', 1, 1),
|
||||||
|
(5, '000000', 1, 'results', '比赛成绩公告.pdf', 'http://example.com/files/results.pdf', 1887436, 'pdf', 1, 1),
|
||||||
|
(6, '000000', 1, 'medals', '奖牌榜统计.pdf', 'http://example.com/files/medals.pdf', 532480, 'pdf', 1, 1),
|
||||||
|
(7, '000000', 1, 'photos', '比赛精彩瞬间.pdf', 'http://example.com/files/photos.pdf', 16357785, 'pdf', 1, 1);
|
||||||
9108
database/martial-db/martial_db.sql
Normal file
9108
database/martial-db/martial_db.sql
Normal file
File diff suppressed because one or more lines are too long
@@ -1,82 +0,0 @@
|
|||||||
@echo off
|
|
||||||
REM =============================================
|
|
||||||
REM 赛程编排系统 - 数据库升级脚本
|
|
||||||
REM =============================================
|
|
||||||
echo.
|
|
||||||
echo ========================================
|
|
||||||
echo 赛程编排系统 - 数据库升级工具
|
|
||||||
echo ========================================
|
|
||||||
echo.
|
|
||||||
echo 说明: 此脚本会创建新的4张表,不会影响现有数据
|
|
||||||
echo - martial_schedule_group
|
|
||||||
echo - martial_schedule_detail
|
|
||||||
echo - martial_schedule_participant
|
|
||||||
echo - martial_schedule_status
|
|
||||||
echo.
|
|
||||||
|
|
||||||
REM 检查MySQL
|
|
||||||
where mysql >nul 2>&1
|
|
||||||
if %errorlevel% neq 0 (
|
|
||||||
echo [错误] 未找到MySQL命令
|
|
||||||
echo.
|
|
||||||
echo 请使用以下方法之一:
|
|
||||||
echo 方法1: 在Navicat/DBeaver中打开并执行 upgrade_schedule_system.sql
|
|
||||||
echo 方法2: 将MySQL添加到系统PATH后重新运行此脚本
|
|
||||||
echo.
|
|
||||||
pause
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
set DB_NAME=martial_db
|
|
||||||
set SCRIPT_PATH=%~dp0upgrade_schedule_system.sql
|
|
||||||
|
|
||||||
echo [1/2] 检测到MySQL...
|
|
||||||
echo.
|
|
||||||
|
|
||||||
echo 请输入MySQL root密码 (无密码直接回车):
|
|
||||||
set /p MYSQL_PWD=密码:
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo [2/2] 正在执行升级脚本...
|
|
||||||
echo.
|
|
||||||
|
|
||||||
if "%MYSQL_PWD%"=="" (
|
|
||||||
mysql -u root %DB_NAME% < "%SCRIPT_PATH%"
|
|
||||||
) else (
|
|
||||||
mysql -u root -p%MYSQL_PWD% %DB_NAME% < "%SCRIPT_PATH%"
|
|
||||||
)
|
|
||||||
|
|
||||||
if %errorlevel% equ 0 (
|
|
||||||
echo.
|
|
||||||
echo ========================================
|
|
||||||
echo ✓ 数据库升级成功!
|
|
||||||
echo ========================================
|
|
||||||
echo.
|
|
||||||
echo 已创建/检查以下表:
|
|
||||||
echo [新] martial_schedule_group - 赛程编排分组表
|
|
||||||
echo [新] martial_schedule_detail - 赛程编排明细表
|
|
||||||
echo [新] martial_schedule_participant - 参赛者关联表
|
|
||||||
echo [新] martial_schedule_status - 编排状态表
|
|
||||||
echo.
|
|
||||||
echo [旧] martial_schedule - 保留(如果存在)
|
|
||||||
echo [旧] martial_schedule_athlete - 保留(如果存在)
|
|
||||||
echo.
|
|
||||||
echo 下一步:
|
|
||||||
echo 1. 重启后端服务以使新表生效
|
|
||||||
echo 2. 访问前端页面测试:
|
|
||||||
echo http://localhost:3000/martial/schedule?competitionId=200
|
|
||||||
echo.
|
|
||||||
) else (
|
|
||||||
echo.
|
|
||||||
echo ========================================
|
|
||||||
echo ✗ 升级失败!
|
|
||||||
echo ========================================
|
|
||||||
echo.
|
|
||||||
echo 请检查:
|
|
||||||
echo 1. 数据库 martial_db 是否存在
|
|
||||||
echo 2. MySQL密码是否正确
|
|
||||||
echo 3. 用户是否有CREATE TABLE权限
|
|
||||||
echo.
|
|
||||||
)
|
|
||||||
|
|
||||||
pause
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
-- =====================================================
|
|
||||||
-- 升级 martial_judge_invite 表
|
|
||||||
-- 添加邀请状态、时间、联系方式等字段
|
|
||||||
-- 执行时间: 2025-12-12
|
|
||||||
-- =====================================================
|
|
||||||
|
|
||||||
USE blade;
|
|
||||||
|
|
||||||
-- 检查表是否存在
|
|
||||||
SELECT 'Checking martial_judge_invite table...' AS status;
|
|
||||||
|
|
||||||
-- 添加邀请状态字段
|
|
||||||
ALTER TABLE martial_judge_invite
|
|
||||||
ADD COLUMN IF NOT EXISTS invite_status INT DEFAULT 0 COMMENT '邀请状态(0-待回复,1-已接受,2-已拒绝,3-已取消)';
|
|
||||||
|
|
||||||
-- 添加邀请时间字段
|
|
||||||
ALTER TABLE martial_judge_invite
|
|
||||||
ADD COLUMN IF NOT EXISTS invite_time DATETIME COMMENT '邀请时间';
|
|
||||||
|
|
||||||
-- 添加回复时间字段
|
|
||||||
ALTER TABLE martial_judge_invite
|
|
||||||
ADD COLUMN IF NOT EXISTS reply_time DATETIME COMMENT '回复时间';
|
|
||||||
|
|
||||||
-- 添加回复备注字段
|
|
||||||
ALTER TABLE martial_judge_invite
|
|
||||||
ADD COLUMN IF NOT EXISTS reply_note VARCHAR(500) COMMENT '回复备注';
|
|
||||||
|
|
||||||
-- 添加联系电话字段
|
|
||||||
ALTER TABLE martial_judge_invite
|
|
||||||
ADD COLUMN IF NOT EXISTS contact_phone VARCHAR(20) COMMENT '联系电话';
|
|
||||||
|
|
||||||
-- 添加联系邮箱字段
|
|
||||||
ALTER TABLE martial_judge_invite
|
|
||||||
ADD COLUMN IF NOT EXISTS contact_email VARCHAR(100) COMMENT '联系邮箱';
|
|
||||||
|
|
||||||
-- 添加邀请消息字段
|
|
||||||
ALTER TABLE martial_judge_invite
|
|
||||||
ADD COLUMN IF NOT EXISTS invite_message VARCHAR(1000) COMMENT '邀请消息';
|
|
||||||
|
|
||||||
-- 添加取消原因字段
|
|
||||||
ALTER TABLE martial_judge_invite
|
|
||||||
ADD COLUMN IF NOT EXISTS cancel_reason VARCHAR(500) COMMENT '取消原因';
|
|
||||||
|
|
||||||
-- 为邀请状态字段添加索引
|
|
||||||
ALTER TABLE martial_judge_invite
|
|
||||||
ADD INDEX IF NOT EXISTS idx_invite_status (invite_status);
|
|
||||||
|
|
||||||
-- 为赛事ID和邀请状态组合添加索引
|
|
||||||
ALTER TABLE martial_judge_invite
|
|
||||||
ADD INDEX IF NOT EXISTS idx_competition_status (competition_id, invite_status);
|
|
||||||
|
|
||||||
-- 验证字段是否添加成功
|
|
||||||
SELECT
|
|
||||||
COLUMN_NAME,
|
|
||||||
COLUMN_TYPE,
|
|
||||||
COLUMN_COMMENT
|
|
||||||
FROM
|
|
||||||
INFORMATION_SCHEMA.COLUMNS
|
|
||||||
WHERE
|
|
||||||
TABLE_SCHEMA = 'blade'
|
|
||||||
AND TABLE_NAME = 'martial_judge_invite'
|
|
||||||
AND COLUMN_NAME IN (
|
|
||||||
'invite_status',
|
|
||||||
'invite_time',
|
|
||||||
'reply_time',
|
|
||||||
'reply_note',
|
|
||||||
'contact_phone',
|
|
||||||
'contact_email',
|
|
||||||
'invite_message',
|
|
||||||
'cancel_reason'
|
|
||||||
)
|
|
||||||
ORDER BY
|
|
||||||
ORDINAL_POSITION;
|
|
||||||
|
|
||||||
SELECT 'Upgrade completed successfully!' AS status;
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
-- =============================================
|
|
||||||
-- 赛程编排系统 - 增量升级脚本
|
|
||||||
-- =============================================
|
|
||||||
-- 说明: 检查并创建缺失的表,不影响现有数据
|
|
||||||
-- 版本: v1.1
|
|
||||||
-- 日期: 2025-12-09
|
|
||||||
-- =============================================
|
|
||||||
|
|
||||||
USE martial_db;
|
|
||||||
|
|
||||||
-- 检查当前已有的表
|
|
||||||
SELECT
|
|
||||||
table_name,
|
|
||||||
'已存在' AS status
|
|
||||||
FROM information_schema.tables
|
|
||||||
WHERE table_schema = 'martial_db'
|
|
||||||
AND table_name LIKE 'martial_schedule%';
|
|
||||||
|
|
||||||
-- =============================================
|
|
||||||
-- 创建新表(仅当不存在时)
|
|
||||||
-- =============================================
|
|
||||||
|
|
||||||
-- 1. 赛程编排分组表
|
|
||||||
CREATE TABLE IF NOT EXISTS `martial_schedule_group` (
|
|
||||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
|
||||||
`competition_id` bigint(20) NOT NULL COMMENT '赛事ID',
|
|
||||||
`group_name` varchar(200) NOT NULL COMMENT '分组名称(如:太极拳男组)',
|
|
||||||
`project_id` bigint(20) NOT NULL COMMENT '项目ID',
|
|
||||||
`project_name` varchar(100) DEFAULT NULL COMMENT '项目名称',
|
|
||||||
`category` varchar(50) DEFAULT NULL COMMENT '组别(成年组、少年组等)',
|
|
||||||
`project_type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '项目类型(1=个人 2=集体)',
|
|
||||||
`display_order` int(11) NOT NULL DEFAULT '0' COMMENT '显示顺序(集体项目优先,数字越小越靠前)',
|
|
||||||
`total_participants` int(11) DEFAULT '0' COMMENT '总参赛人数',
|
|
||||||
`total_teams` int(11) DEFAULT '0' COMMENT '总队伍数(仅集体项目)',
|
|
||||||
`estimated_duration` int(11) DEFAULT '0' COMMENT '预计时长(分钟)',
|
|
||||||
`create_user` bigint(20) DEFAULT NULL,
|
|
||||||
`create_dept` bigint(20) DEFAULT NULL,
|
|
||||||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
`update_user` bigint(20) DEFAULT NULL,
|
|
||||||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
||||||
`status` int(11) DEFAULT '1' COMMENT '状态(1-启用,2-禁用)',
|
|
||||||
`is_deleted` int(11) DEFAULT '0',
|
|
||||||
`tenant_id` varchar(12) DEFAULT '000000',
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
KEY `idx_competition` (`competition_id`),
|
|
||||||
KEY `idx_project` (`project_id`),
|
|
||||||
KEY `idx_display_order` (`display_order`),
|
|
||||||
KEY `idx_tenant` (`tenant_id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='赛程编排分组表';
|
|
||||||
|
|
||||||
-- 2. 赛程编排明细表(场地时间段分配)
|
|
||||||
CREATE TABLE IF NOT EXISTS `martial_schedule_detail` (
|
|
||||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
|
||||||
`schedule_group_id` bigint(20) NOT NULL COMMENT '分组ID',
|
|
||||||
`competition_id` bigint(20) NOT NULL COMMENT '赛事ID',
|
|
||||||
`venue_id` bigint(20) NOT NULL COMMENT '场地ID',
|
|
||||||
`venue_name` varchar(100) DEFAULT NULL COMMENT '场地名称',
|
|
||||||
`schedule_date` date NOT NULL COMMENT '比赛日期',
|
|
||||||
`time_period` varchar(20) NOT NULL COMMENT '时间段(morning/afternoon)',
|
|
||||||
`time_slot` varchar(20) NOT NULL COMMENT '时间点(08:30/13:30)',
|
|
||||||
`estimated_start_time` datetime DEFAULT NULL COMMENT '预计开始时间',
|
|
||||||
`estimated_end_time` datetime DEFAULT NULL COMMENT '预计结束时间',
|
|
||||||
`estimated_duration` int(11) DEFAULT '0' COMMENT '预计时长(分钟)',
|
|
||||||
`participant_count` int(11) DEFAULT '0' COMMENT '参赛人数',
|
|
||||||
`sort_order` int(11) DEFAULT '0' COMMENT '场内顺序',
|
|
||||||
`create_user` bigint(20) DEFAULT NULL,
|
|
||||||
`create_dept` bigint(20) DEFAULT NULL,
|
|
||||||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
`update_user` bigint(20) DEFAULT NULL,
|
|
||||||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
||||||
`status` int(11) DEFAULT '1' COMMENT '状态(1-未开始,2-进行中,3-已完成)',
|
|
||||||
`is_deleted` int(11) DEFAULT '0',
|
|
||||||
`tenant_id` varchar(12) DEFAULT '000000',
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
KEY `idx_group` (`schedule_group_id`),
|
|
||||||
KEY `idx_competition` (`competition_id`),
|
|
||||||
KEY `idx_venue_time` (`venue_id`,`schedule_date`,`time_slot`),
|
|
||||||
KEY `idx_tenant` (`tenant_id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='赛程编排明细表(场地时间段分配)';
|
|
||||||
|
|
||||||
-- 3. 赛程编排参赛者关联表
|
|
||||||
CREATE TABLE IF NOT EXISTS `martial_schedule_participant` (
|
|
||||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
|
||||||
`schedule_detail_id` bigint(20) NOT NULL COMMENT '编排明细ID',
|
|
||||||
`schedule_group_id` bigint(20) NOT NULL COMMENT '分组ID',
|
|
||||||
`participant_id` bigint(20) NOT NULL COMMENT '参赛者ID(关联martial_athlete表)',
|
|
||||||
`organization` varchar(200) DEFAULT NULL COMMENT '单位名称',
|
|
||||||
`player_name` varchar(100) DEFAULT NULL COMMENT '选手姓名',
|
|
||||||
`project_name` varchar(100) DEFAULT NULL COMMENT '项目名称',
|
|
||||||
`category` varchar(50) DEFAULT NULL COMMENT '组别',
|
|
||||||
`performance_order` int(11) DEFAULT '0' COMMENT '出场顺序',
|
|
||||||
`create_user` bigint(20) DEFAULT NULL,
|
|
||||||
`create_dept` bigint(20) DEFAULT NULL,
|
|
||||||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
`update_user` bigint(20) DEFAULT NULL,
|
|
||||||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
||||||
`status` int(11) DEFAULT '1' COMMENT '状态(1-待出场,2-已出场)',
|
|
||||||
`is_deleted` int(11) DEFAULT '0',
|
|
||||||
`tenant_id` varchar(12) DEFAULT '000000',
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
KEY `idx_detail` (`schedule_detail_id`),
|
|
||||||
KEY `idx_group` (`schedule_group_id`),
|
|
||||||
KEY `idx_participant` (`participant_id`),
|
|
||||||
KEY `idx_tenant` (`tenant_id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='赛程编排参赛者关联表';
|
|
||||||
|
|
||||||
-- 4. 赛程编排状态表
|
|
||||||
CREATE TABLE IF NOT EXISTS `martial_schedule_status` (
|
|
||||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
|
||||||
`competition_id` bigint(20) NOT NULL COMMENT '赛事ID(唯一)',
|
|
||||||
`schedule_status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '编排状态(0=未编排 1=编排中 2=已保存锁定)',
|
|
||||||
`last_auto_schedule_time` datetime DEFAULT NULL COMMENT '最后自动编排时间',
|
|
||||||
`locked_time` datetime DEFAULT NULL COMMENT '锁定时间',
|
|
||||||
`locked_by` varchar(100) DEFAULT NULL COMMENT '锁定人',
|
|
||||||
`total_groups` int(11) DEFAULT '0' COMMENT '总分组数',
|
|
||||||
`total_participants` int(11) DEFAULT '0' COMMENT '总参赛人数',
|
|
||||||
`create_user` bigint(20) DEFAULT NULL,
|
|
||||||
`create_dept` bigint(20) DEFAULT NULL,
|
|
||||||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
`update_user` bigint(20) DEFAULT NULL,
|
|
||||||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
||||||
`status` int(11) DEFAULT '1' COMMENT '状态(1-启用,2-禁用)',
|
|
||||||
`is_deleted` int(11) DEFAULT '0',
|
|
||||||
`tenant_id` varchar(12) DEFAULT '000000',
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `uk_competition` (`competition_id`),
|
|
||||||
KEY `idx_tenant` (`tenant_id`),
|
|
||||||
KEY `idx_schedule_status` (`schedule_status`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='赛程编排状态表';
|
|
||||||
|
|
||||||
-- =============================================
|
|
||||||
-- 验证结果
|
|
||||||
-- =============================================
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
'升级完成' AS message,
|
|
||||||
COUNT(*) AS new_tables_count
|
|
||||||
FROM information_schema.tables
|
|
||||||
WHERE table_schema = 'martial_db'
|
|
||||||
AND table_name IN (
|
|
||||||
'martial_schedule_group',
|
|
||||||
'martial_schedule_detail',
|
|
||||||
'martial_schedule_participant',
|
|
||||||
'martial_schedule_status'
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 显示所有赛程相关表
|
|
||||||
SELECT
|
|
||||||
table_name,
|
|
||||||
table_comment,
|
|
||||||
CASE
|
|
||||||
WHEN table_name IN ('martial_schedule_group', 'martial_schedule_detail',
|
|
||||||
'martial_schedule_participant', 'martial_schedule_status')
|
|
||||||
THEN '新系统'
|
|
||||||
ELSE '旧系统'
|
|
||||||
END AS system_version
|
|
||||||
FROM information_schema.tables
|
|
||||||
WHERE table_schema = 'martial_db'
|
|
||||||
AND table_name LIKE 'martial_schedule%'
|
|
||||||
ORDER BY system_version DESC, table_name;
|
|
||||||
|
|
||||||
-- =============================================
|
|
||||||
-- 说明
|
|
||||||
-- =============================================
|
|
||||||
--
|
|
||||||
-- 执行结果说明:
|
|
||||||
-- 1. 如果 new_tables_count = 4,说明4张新表全部创建成功
|
|
||||||
-- 2. 如果 new_tables_count < 4,说明部分表已存在或创建失败
|
|
||||||
-- 3. 最后一个查询会显示所有赛程相关表及其所属系统版本
|
|
||||||
--
|
|
||||||
-- 新旧系统对比:
|
|
||||||
-- - 旧系统: martial_schedule, martial_schedule_athlete (可能存在)
|
|
||||||
-- - 新系统: martial_schedule_group, martial_schedule_detail,
|
|
||||||
-- martial_schedule_participant, martial_schedule_status
|
|
||||||
--
|
|
||||||
-- 两个系统可以共存,不会互相影响
|
|
||||||
-- 新系统由后端Service层代码使用
|
|
||||||
--
|
|
||||||
-- =============================================
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
-- ================================================================
|
|
||||||
-- 赛事编排智能化升级 SQL 脚本
|
|
||||||
-- 用途:支持智能编排算法(场地容纳人数 + 项目时长限制)
|
|
||||||
-- 日期:2025-12-06
|
|
||||||
-- ================================================================
|
|
||||||
|
|
||||||
-- 1. 创建场地信息表(如果不存在)
|
|
||||||
-- ================================================================
|
|
||||||
-- 注意:使用 capacity 字段名以匹配现有数据库表结构
|
|
||||||
CREATE TABLE IF NOT EXISTS `martial_venue` (
|
|
||||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
|
||||||
`tenant_id` varchar(12) DEFAULT '000000' COMMENT '租户ID',
|
|
||||||
`competition_id` bigint(20) NOT NULL COMMENT '赛事ID',
|
|
||||||
`venue_name` varchar(100) NOT NULL COMMENT '场地名称',
|
|
||||||
`venue_code` varchar(50) DEFAULT NULL COMMENT '场地编码',
|
|
||||||
`location` varchar(200) DEFAULT NULL COMMENT '位置/地点',
|
|
||||||
`capacity` int(11) DEFAULT 100 COMMENT '容纳人数',
|
|
||||||
`facilities` varchar(500) DEFAULT NULL COMMENT '场地设施',
|
|
||||||
`status` int(2) DEFAULT 1 COMMENT '状态(0-禁用,1-启用)',
|
|
||||||
`create_user` bigint(20) DEFAULT NULL COMMENT '创建人',
|
|
||||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
|
||||||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
|
||||||
`update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
|
|
||||||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
|
||||||
`is_deleted` int(2) DEFAULT 0 COMMENT '是否已删除',
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
KEY `idx_competition_id` (`competition_id`),
|
|
||||||
KEY `idx_tenant_id` (`tenant_id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='场地信息表';
|
|
||||||
|
|
||||||
-- 2. 确保 martial_project 表有 estimated_duration 字段
|
|
||||||
-- ================================================================
|
|
||||||
-- 检查字段是否存在,不存在则添加
|
|
||||||
SET @col_exists = 0;
|
|
||||||
SELECT COUNT(*) INTO @col_exists
|
|
||||||
FROM information_schema.COLUMNS
|
|
||||||
WHERE TABLE_SCHEMA = DATABASE()
|
|
||||||
AND TABLE_NAME = 'martial_project'
|
|
||||||
AND COLUMN_NAME = 'estimated_duration';
|
|
||||||
|
|
||||||
SET @sql = IF(@col_exists = 0,
|
|
||||||
'ALTER TABLE martial_project ADD COLUMN estimated_duration int(11) DEFAULT 5 COMMENT ''预估时长(分钟)'' AFTER max_participants',
|
|
||||||
'SELECT ''estimated_duration column already exists'' AS info'
|
|
||||||
);
|
|
||||||
|
|
||||||
PREPARE stmt FROM @sql;
|
|
||||||
EXECUTE stmt;
|
|
||||||
DEALLOCATE PREPARE stmt;
|
|
||||||
|
|
||||||
-- 3. 插入测试数据(仅用于开发测试)
|
|
||||||
-- ================================================================
|
|
||||||
-- 为赛事 ID=100 插入场地数据
|
|
||||||
INSERT INTO `martial_venue` (`competition_id`, `venue_name`, `venue_code`, `capacity`, `location`, `facilities`) VALUES
|
|
||||||
(100, '一号场地', 'VENUE_01', 50, '体育馆一楼东侧', '主会场,配备专业武术地毯,适合集体项目'),
|
|
||||||
(100, '二号场地', 'VENUE_02', 50, '体育馆一楼西侧', '次会场,配备专业武术地毯,适合集体项目'),
|
|
||||||
(100, '三号场地', 'VENUE_03', 30, '体育馆二楼东侧', '小型场地,适合个人项目'),
|
|
||||||
(100, '四号场地', 'VENUE_04', 30, '体育馆二楼西侧', '小型场地,适合个人项目')
|
|
||||||
ON DUPLICATE KEY UPDATE
|
|
||||||
venue_name = VALUES(venue_name),
|
|
||||||
capacity = VALUES(capacity),
|
|
||||||
location = VALUES(location),
|
|
||||||
facilities = VALUES(facilities);
|
|
||||||
|
|
||||||
-- 4. 更新现有项目的预估时长(如果为NULL或0)
|
|
||||||
-- ================================================================
|
|
||||||
UPDATE martial_project
|
|
||||||
SET estimated_duration = CASE
|
|
||||||
WHEN project_name LIKE '%太极%' THEN 5
|
|
||||||
WHEN project_name LIKE '%长拳%' THEN 5
|
|
||||||
WHEN project_name LIKE '%剑%' THEN 4
|
|
||||||
WHEN project_name LIKE '%刀%' THEN 4
|
|
||||||
WHEN project_name LIKE '%棍%' THEN 6
|
|
||||||
WHEN project_name LIKE '%枪%' THEN 6
|
|
||||||
ELSE 5
|
|
||||||
END
|
|
||||||
WHERE estimated_duration IS NULL OR estimated_duration = 0;
|
|
||||||
|
|
||||||
-- 5. 创建视图:场地使用统计(可选)
|
|
||||||
-- ================================================================
|
|
||||||
CREATE OR REPLACE VIEW v_venue_usage_stats AS
|
|
||||||
SELECT
|
|
||||||
v.id AS venue_id,
|
|
||||||
v.competition_id,
|
|
||||||
v.venue_name,
|
|
||||||
v.max_capacity,
|
|
||||||
COUNT(DISTINCT s.group_id) AS assigned_groups,
|
|
||||||
SUM(s.participant_count) AS total_participants,
|
|
||||||
SUM(s.estimated_duration) AS total_duration,
|
|
||||||
v.max_capacity - IFNULL(SUM(s.participant_count), 0) AS remaining_capacity
|
|
||||||
FROM martial_venue v
|
|
||||||
LEFT JOIN (
|
|
||||||
-- 这里假设将来会有 martial_schedule 表来存储编排结果
|
|
||||||
SELECT
|
|
||||||
venue_id,
|
|
||||||
group_id,
|
|
||||||
COUNT(*) AS participant_count,
|
|
||||||
SUM(estimated_duration) AS estimated_duration
|
|
||||||
FROM martial_schedule_detail
|
|
||||||
WHERE is_deleted = 0
|
|
||||||
GROUP BY venue_id, group_id
|
|
||||||
) s ON v.id = s.venue_id
|
|
||||||
WHERE v.is_deleted = 0
|
|
||||||
GROUP BY v.id, v.competition_id, v.venue_name, v.max_capacity;
|
|
||||||
|
|
||||||
-- ================================================================
|
|
||||||
-- 脚本执行完成
|
|
||||||
-- ================================================================
|
|
||||||
-- 说明:
|
|
||||||
-- 1. 场地表已创建,支持最大容纳人数配置
|
|
||||||
-- 2. 项目表 estimated_duration 字段已确保存在
|
|
||||||
-- 3. 测试数据已插入(赛事ID=100)
|
|
||||||
-- 4. 现有项目的预估时长已更新为合理默认值
|
|
||||||
-- ================================================================
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
-- =============================================
|
|
||||||
-- 验证赛程编排系统表创建情况
|
|
||||||
-- =============================================
|
|
||||||
|
|
||||||
USE martial_db;
|
|
||||||
|
|
||||||
-- 1. 检查所有赛程相关表
|
|
||||||
SELECT
|
|
||||||
table_name AS '表名',
|
|
||||||
table_comment AS '说明',
|
|
||||||
CASE
|
|
||||||
WHEN table_name IN ('martial_schedule_group', 'martial_schedule_detail',
|
|
||||||
'martial_schedule_participant', 'martial_schedule_status')
|
|
||||||
THEN '✓ 新系统'
|
|
||||||
ELSE '旧系统'
|
|
||||||
END AS '系统版本',
|
|
||||||
table_rows AS '记录数'
|
|
||||||
FROM information_schema.tables
|
|
||||||
WHERE table_schema = 'martial_db'
|
|
||||||
AND table_name LIKE 'martial_schedule%'
|
|
||||||
ORDER BY
|
|
||||||
CASE
|
|
||||||
WHEN table_name IN ('martial_schedule_group', 'martial_schedule_detail',
|
|
||||||
'martial_schedule_participant', 'martial_schedule_status')
|
|
||||||
THEN 1
|
|
||||||
ELSE 2
|
|
||||||
END,
|
|
||||||
table_name;
|
|
||||||
|
|
||||||
-- 2. 验证新系统4张表是否全部创建
|
|
||||||
SELECT
|
|
||||||
CASE
|
|
||||||
WHEN COUNT(*) = 4 THEN '✓ 新系统表创建成功! 共4张表已就绪'
|
|
||||||
ELSE CONCAT('⚠ 警告: 只创建了 ', COUNT(*), ' 张表,应该是4张')
|
|
||||||
END AS '创建状态'
|
|
||||||
FROM information_schema.tables
|
|
||||||
WHERE table_schema = 'martial_db'
|
|
||||||
AND table_name IN (
|
|
||||||
'martial_schedule_group',
|
|
||||||
'martial_schedule_detail',
|
|
||||||
'martial_schedule_participant',
|
|
||||||
'martial_schedule_status'
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 3. 检查各表的字段数量
|
|
||||||
SELECT
|
|
||||||
table_name AS '表名',
|
|
||||||
COUNT(*) AS '字段数'
|
|
||||||
FROM information_schema.columns
|
|
||||||
WHERE table_schema = 'martial_db'
|
|
||||||
AND table_name IN (
|
|
||||||
'martial_schedule_group',
|
|
||||||
'martial_schedule_detail',
|
|
||||||
'martial_schedule_participant',
|
|
||||||
'martial_schedule_status'
|
|
||||||
)
|
|
||||||
GROUP BY table_name
|
|
||||||
ORDER BY table_name;
|
|
||||||
|
|
||||||
-- 4. 检查索引创建情况
|
|
||||||
SELECT
|
|
||||||
table_name AS '表名',
|
|
||||||
COUNT(DISTINCT index_name) AS '索引数量',
|
|
||||||
GROUP_CONCAT(DISTINCT index_name ORDER BY index_name) AS '索引列表'
|
|
||||||
FROM information_schema.statistics
|
|
||||||
WHERE table_schema = 'martial_db'
|
|
||||||
AND table_name IN (
|
|
||||||
'martial_schedule_group',
|
|
||||||
'martial_schedule_detail',
|
|
||||||
'martial_schedule_participant',
|
|
||||||
'martial_schedule_status'
|
|
||||||
)
|
|
||||||
GROUP BY table_name
|
|
||||||
ORDER BY table_name;
|
|
||||||
|
|
||||||
-- 5. 检查是否有数据(应该为空,因为是新表)
|
|
||||||
SELECT
|
|
||||||
'martial_schedule_group' AS '表名',
|
|
||||||
COUNT(*) AS '记录数'
|
|
||||||
FROM martial_schedule_group
|
|
||||||
UNION ALL
|
|
||||||
SELECT
|
|
||||||
'martial_schedule_detail',
|
|
||||||
COUNT(*)
|
|
||||||
FROM martial_schedule_detail
|
|
||||||
UNION ALL
|
|
||||||
SELECT
|
|
||||||
'martial_schedule_participant',
|
|
||||||
COUNT(*)
|
|
||||||
FROM martial_schedule_participant
|
|
||||||
UNION ALL
|
|
||||||
SELECT
|
|
||||||
'martial_schedule_status',
|
|
||||||
COUNT(*)
|
|
||||||
FROM martial_schedule_status;
|
|
||||||
|
|
||||||
-- 6. 显示最终状态
|
|
||||||
SELECT
|
|
||||||
'🎉 数据库升级完成!' AS '状态',
|
|
||||||
DATABASE() AS '当前数据库',
|
|
||||||
NOW() AS '验证时间';
|
|
||||||
8872
database/martial_db.sql
Normal file
8872
database/martial_db.sql
Normal file
File diff suppressed because one or more lines are too long
117
docker-compose.yml
Normal file
117
docker-compose.yml
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
services:
|
||||||
|
# MySQL 数据库
|
||||||
|
mysql:
|
||||||
|
image: mysql:8.0
|
||||||
|
container_name: martial-mysql
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: 123456
|
||||||
|
MYSQL_DATABASE: martial_db
|
||||||
|
TZ: Asia/Shanghai
|
||||||
|
ports:
|
||||||
|
- "3306:3306"
|
||||||
|
volumes:
|
||||||
|
- mysql_data:/var/lib/mysql
|
||||||
|
- ./database:/docker-entrypoint-initdb.d
|
||||||
|
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p123456"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- martial-network
|
||||||
|
|
||||||
|
# Redis 缓存
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: martial-redis
|
||||||
|
restart: always
|
||||||
|
command: redis-server --requirepass 123456
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "-a", "123456", "ping"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- martial-network
|
||||||
|
|
||||||
|
# MinIO 对象存储
|
||||||
|
minio:
|
||||||
|
image: minio/minio:RELEASE.2024-12-18T13-15-44Z
|
||||||
|
container_name: minio
|
||||||
|
environment:
|
||||||
|
MINIO_ROOT_USER: "JohnSion"
|
||||||
|
MINIO_ROOT_PASSWORD: "v!*BTket4oagDdw"
|
||||||
|
TZ: "Asia/Shanghai"
|
||||||
|
command: server /data --console-address ":9001"
|
||||||
|
volumes:
|
||||||
|
- ./minio_data:/data
|
||||||
|
ports:
|
||||||
|
- "9000:9000"
|
||||||
|
- "9001:9001"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://127.0.0.1:9000/minio/health/live"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- martial-network
|
||||||
|
|
||||||
|
# MinIO 初始化 - 创建桶和设置策略
|
||||||
|
minio-init:
|
||||||
|
image: minio/mc:latest
|
||||||
|
depends_on:
|
||||||
|
minio:
|
||||||
|
condition: service_healthy
|
||||||
|
entrypoint: >
|
||||||
|
sh -c "
|
||||||
|
mc alias set local http://minio:9000 $${MINIO_ROOT_USER} $${MINIO_ROOT_PASSWORD} &&
|
||||||
|
mc mb -p local/assets || true &&
|
||||||
|
mc anonymous set download local/assets || true
|
||||||
|
"
|
||||||
|
environment:
|
||||||
|
MINIO_ROOT_USER: "JohnSion"
|
||||||
|
MINIO_ROOT_PASSWORD: "v!*BTket4oagDdw"
|
||||||
|
restart: "no"
|
||||||
|
networks:
|
||||||
|
- martial-network
|
||||||
|
|
||||||
|
# 后端应用(完整构建模式)
|
||||||
|
martial-api:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.quick
|
||||||
|
container_name: martial-api
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
SPRING_PROFILE: dev
|
||||||
|
JAVA_OPTS: "-Xms512m -Xmx1024m -XX:+UseG1GC"
|
||||||
|
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/martial_db?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
|
||||||
|
SPRING_DATASOURCE_USERNAME: root
|
||||||
|
SPRING_DATASOURCE_PASSWORD: 123456
|
||||||
|
SPRING_DATA_REDIS_HOST: redis
|
||||||
|
SPRING_DATA_REDIS_PORT: 6379
|
||||||
|
SPRING_DATA_REDIS_PASSWORD: 123456
|
||||||
|
ports:
|
||||||
|
- "8123:8123"
|
||||||
|
depends_on:
|
||||||
|
mysql:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- martial-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
martial-network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql_data:
|
||||||
|
redis_data:
|
||||||
224
docs/DATABASE_MIGRATION.md
Normal file
224
docs/DATABASE_MIGRATION.md
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
# 数据库迁移指南
|
||||||
|
|
||||||
|
本项目使用 **Flyway** 进行数据库版本管理和自动迁移。
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
Flyway 是一个数据库迁移工具,它能够:
|
||||||
|
- 自动追踪数据库版本
|
||||||
|
- 按顺序执行迁移脚本
|
||||||
|
- 确保团队成员的数据库结构一致
|
||||||
|
- 支持回滚和修复
|
||||||
|
|
||||||
|
## 工作原理
|
||||||
|
|
||||||
|
1. 应用启动时,Flyway 自动扫描 `src/main/resources/db/migration` 目录
|
||||||
|
2. 检查 `flyway_schema_history` 表,确定已执行的版本
|
||||||
|
3. 按版本号顺序执行未运行的迁移脚本
|
||||||
|
4. 记录执行结果到历史表
|
||||||
|
|
||||||
|
## 迁移脚本命名规范
|
||||||
|
|
||||||
|
```
|
||||||
|
V{版本号}__{描述}.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### 命名规则
|
||||||
|
|
||||||
|
| 规则 | 说明 | 示例 |
|
||||||
|
|------|------|------|
|
||||||
|
| 前缀 | 必须以 `V` 开头(大写) | V1, V2, V10 |
|
||||||
|
| 版本号 | 数字,支持小数点 | 1, 2, 2.1, 10 |
|
||||||
|
| 分隔符 | **两个下划线** | `__` |
|
||||||
|
| 描述 | 用下划线连接单词 | add_user_table |
|
||||||
|
| 后缀 | 必须是 `.sql` | .sql |
|
||||||
|
|
||||||
|
### 正确示例
|
||||||
|
|
||||||
|
```
|
||||||
|
V1__baseline.sql # 基线版本
|
||||||
|
V2__add_project_fields.sql # 添加项目字段
|
||||||
|
V3__create_order_table.sql # 创建订单表
|
||||||
|
V4__add_index_to_user.sql # 添加用户索引
|
||||||
|
V4.1__fix_user_column_type.sql # 修复用户列类型(小版本)
|
||||||
|
V10__major_refactor.sql # 大版本重构
|
||||||
|
```
|
||||||
|
|
||||||
|
### 错误示例
|
||||||
|
|
||||||
|
```
|
||||||
|
v1__init.sql # 错误:v 应该大写
|
||||||
|
V1_init.sql # 错误:只有一个下划线
|
||||||
|
V1-init.sql # 错误:使用了连字符
|
||||||
|
V1__init.SQL # 错误:后缀应该小写
|
||||||
|
init.sql # 错误:缺少版本前缀
|
||||||
|
```
|
||||||
|
|
||||||
|
## 如何添加新的迁移
|
||||||
|
|
||||||
|
### 步骤 1:确定版本号
|
||||||
|
|
||||||
|
查看当前最新版本:
|
||||||
|
```bash
|
||||||
|
ls src/main/resources/db/migration/
|
||||||
|
```
|
||||||
|
|
||||||
|
新版本号 = 最新版本号 + 1
|
||||||
|
|
||||||
|
### 步骤 2:创建迁移脚本
|
||||||
|
|
||||||
|
在 `src/main/resources/db/migration/` 目录创建新文件:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- =====================================================
|
||||||
|
-- 迁移脚本: [描述]
|
||||||
|
-- 版本: V{版本号}
|
||||||
|
-- 描述: [详细说明]
|
||||||
|
-- 日期: YYYY-MM-DD
|
||||||
|
-- =====================================================
|
||||||
|
|
||||||
|
-- 你的 SQL 语句
|
||||||
|
ALTER TABLE xxx ADD COLUMN yyy VARCHAR(100);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 3:测试迁移
|
||||||
|
|
||||||
|
本地启动应用,观察日志:
|
||||||
|
```
|
||||||
|
Flyway Community Edition 9.x.x
|
||||||
|
Successfully validated 3 migrations
|
||||||
|
Current version of schema: 2
|
||||||
|
Migrating schema to version 3 - create_order_table
|
||||||
|
Successfully applied 1 migration
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 4:提交代码
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/main/resources/db/migration/V3__xxx.sql
|
||||||
|
git commit -m "db: 添加xxx迁移脚本"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 1. 幂等性脚本
|
||||||
|
|
||||||
|
编写可重复执行的脚本,避免重复执行报错:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 添加列(如果不存在)
|
||||||
|
SET @exist := (SELECT COUNT(*) FROM information_schema.columns
|
||||||
|
WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'your_table'
|
||||||
|
AND column_name = 'new_column');
|
||||||
|
SET @sql := IF(@exist = 0,
|
||||||
|
'ALTER TABLE your_table ADD COLUMN new_column VARCHAR(100)',
|
||||||
|
'SELECT 1');
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 不要修改已执行的脚本
|
||||||
|
|
||||||
|
一旦迁移脚本被执行(已提交到版本库),**永远不要修改它**。
|
||||||
|
|
||||||
|
如果需要修复,创建新的迁移脚本:
|
||||||
|
```
|
||||||
|
V3__create_table.sql # 已执行,有错误
|
||||||
|
V4__fix_v3_error.sql # 新建脚本修复错误
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 小步迁移
|
||||||
|
|
||||||
|
每个迁移脚本只做一件事:
|
||||||
|
- V2__add_user_email.sql
|
||||||
|
- V3__add_user_phone.sql
|
||||||
|
- 不要: V2__add_user_email_and_phone_and_address.sql
|
||||||
|
|
||||||
|
### 4. 添加注释
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- =====================================================
|
||||||
|
-- 迁移脚本: 添加用户邮箱字段
|
||||||
|
-- 版本: V5
|
||||||
|
-- 描述: 为用户表添加邮箱字段,用于接收通知
|
||||||
|
-- 作者: 张三
|
||||||
|
-- 日期: 2024-12-29
|
||||||
|
-- 关联需求: JIRA-123
|
||||||
|
-- =====================================================
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 备份数据
|
||||||
|
|
||||||
|
生产环境执行迁移前,务必备份数据库:
|
||||||
|
```bash
|
||||||
|
mysqldump -u root -p martial_db > backup_$(date +%Y%m%d).sql
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### Q1: 迁移失败怎么办?
|
||||||
|
|
||||||
|
1. 查看错误日志,定位问题
|
||||||
|
2. 修复数据库中的问题(手动)
|
||||||
|
3. 修复迁移脚本
|
||||||
|
4. 执行 Flyway repair(如需要)
|
||||||
|
|
||||||
|
### Q2: 如何跳过某个版本?
|
||||||
|
|
||||||
|
不建议跳过版本。如果必须跳过,可以创建空脚本:
|
||||||
|
```sql
|
||||||
|
-- V3__placeholder.sql
|
||||||
|
-- 此版本跳过
|
||||||
|
SELECT 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q3: 多人开发版本冲突怎么办?
|
||||||
|
|
||||||
|
使用日期时间作为版本号前缀:
|
||||||
|
```
|
||||||
|
V20241229001__add_field.sql
|
||||||
|
V20241229002__fix_bug.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q4: 如何查看迁移历史?
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT * FROM flyway_schema_history ORDER BY installed_rank;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
src/main/resources/
|
||||||
|
└── db/
|
||||||
|
└── migration/
|
||||||
|
├── V1__baseline.sql # 基线版本
|
||||||
|
├── V2__add_project_fields.sql # 添加项目字段
|
||||||
|
└── V3__xxx.sql # 后续迁移...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置说明
|
||||||
|
|
||||||
|
application.yml 中的 Flyway 配置:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
flyway:
|
||||||
|
enabled: true # 启用 Flyway
|
||||||
|
locations: classpath:db/migration # 迁移脚本位置
|
||||||
|
table: flyway_schema_history # 版本历史表名
|
||||||
|
baseline-version: 0 # 基线版本号
|
||||||
|
baseline-on-migrate: true # 自动执行基线
|
||||||
|
validate-on-migrate: true # 校验迁移脚本
|
||||||
|
encoding: UTF-8 # 脚本编码
|
||||||
|
out-of-order: false # 禁止乱序执行
|
||||||
|
clean-disabled: true # 禁用清理(生产安全)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 参考资料
|
||||||
|
|
||||||
|
- [Flyway 官方文档](https://flywaydb.org/documentation/)
|
||||||
|
- [Spring Boot Flyway 集成](https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.data-initialization.migration-tool.flyway)
|
||||||
418
docs/DISPATCH_FEATURE_SUMMARY.md
Normal file
418
docs/DISPATCH_FEATURE_SUMMARY.md
Normal file
@@ -0,0 +1,418 @@
|
|||||||
|
# 🎯 调度功能实现总结
|
||||||
|
|
||||||
|
## ✅ 功能已全部完成!
|
||||||
|
|
||||||
|
调度功能已经按照设计方案完整实现,包括后端、前端和数据库的所有必要组件。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 交付清单
|
||||||
|
|
||||||
|
### 1. 后端代码(已完成)
|
||||||
|
|
||||||
|
#### DTO类(3个)
|
||||||
|
- ✅ [DispatchDataDTO.java](../src/main/java/org/springblade/modules/martial/pojo/dto/DispatchDataDTO.java) - 调度数据查询DTO
|
||||||
|
- ✅ [AdjustOrderDTO.java](../src/main/java/org/springblade/modules/martial/pojo/dto/AdjustOrderDTO.java) - 调整顺序DTO
|
||||||
|
- ✅ [SaveDispatchDTO.java](../src/main/java/org/springblade/modules/martial/pojo/dto/SaveDispatchDTO.java) - 保存调度DTO
|
||||||
|
|
||||||
|
#### VO类(1个)
|
||||||
|
- ✅ [DispatchDataVO.java](../src/main/java/org/springblade/modules/martial/pojo/vo/DispatchDataVO.java) - 调度数据视图对象
|
||||||
|
|
||||||
|
#### Service层
|
||||||
|
- ✅ [IMartialScheduleService.java](../src/main/java/org/springblade/modules/martial/service/IMartialScheduleService.java) - 添加3个调度方法
|
||||||
|
- ✅ [MartialScheduleServiceImpl.java](../src/main/java/org/springblade/modules/martial/service/impl/MartialScheduleServiceImpl.java) - 实现调度逻辑
|
||||||
|
|
||||||
|
#### Controller层
|
||||||
|
- ✅ [MartialScheduleArrangeController.java](../src/main/java/org/springblade/modules/martial/controller/MartialScheduleArrangeController.java) - 添加3个调度接口
|
||||||
|
|
||||||
|
### 2. 前端代码(已完成)
|
||||||
|
|
||||||
|
#### API接口
|
||||||
|
- ✅ [activitySchedule.js](../../martial-web/src/api/martial/activitySchedule.js) - 添加3个调度API
|
||||||
|
|
||||||
|
#### 页面实现
|
||||||
|
- ✅ 调度功能集成方案(详见 [schedule-dispatch-implementation.md](./schedule-dispatch-implementation.md))
|
||||||
|
|
||||||
|
### 3. 数据库脚本(已完成)
|
||||||
|
|
||||||
|
- ✅ [create_dispatch_log_table.sql](../database/martial-db/create_dispatch_log_table.sql) - 调度日志表(可选)
|
||||||
|
|
||||||
|
### 4. 文档(已完成)
|
||||||
|
|
||||||
|
- ✅ [schedule-dispatch-implementation.md](./schedule-dispatch-implementation.md) - 详细实现文档
|
||||||
|
- ✅ [DISPATCH_FEATURE_SUMMARY.md](./DISPATCH_FEATURE_SUMMARY.md) - 本文档
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔌 后端接口列表
|
||||||
|
|
||||||
|
| 接口 | 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 获取调度数据 | GET | `/api/blade-martial/schedule/dispatch-data` | 获取指定场地和时间段的调度数据 |
|
||||||
|
| 调整出场顺序 | POST | `/api/blade-martial/schedule/adjust-order` | 调整单个参赛者的出场顺序 |
|
||||||
|
| 批量保存调度 | POST | `/api/blade-martial/schedule/save-dispatch` | 批量保存所有调度调整 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💻 核心功能实现
|
||||||
|
|
||||||
|
### 1. 获取调度数据
|
||||||
|
|
||||||
|
**Service层实现**(第454-521行):
|
||||||
|
```java
|
||||||
|
@Override
|
||||||
|
public DispatchDataVO getDispatchData(Long competitionId, Long venueId, Integer timeSlotIndex) {
|
||||||
|
// 1. 查询指定场地和时间段的编排明细
|
||||||
|
// 2. 查询每个明细下的所有参赛者
|
||||||
|
// 3. 转换为VO并返回
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键逻辑**:
|
||||||
|
- 根据场地ID和时间段索引查询编排明细
|
||||||
|
- 关联查询分组信息和参赛者信息
|
||||||
|
- 按 `performance_order` 排序
|
||||||
|
|
||||||
|
### 2. 调整出场顺序
|
||||||
|
|
||||||
|
**Service层实现**(第523-585行):
|
||||||
|
```java
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean adjustOrder(AdjustOrderDTO dto) {
|
||||||
|
// 1. 查询当前参赛者
|
||||||
|
// 2. 查询同一明细下的所有参赛者
|
||||||
|
// 3. 根据动作(move_up/move_down/swap)调整顺序
|
||||||
|
// 4. 批量更新所有参赛者的顺序
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**支持的操作**:
|
||||||
|
- `move_up`: 上移一位
|
||||||
|
- `move_down`: 下移一位
|
||||||
|
- `swap`: 交换到指定位置
|
||||||
|
|
||||||
|
### 3. 批量保存调度
|
||||||
|
|
||||||
|
**Service层实现**(第587-606行):
|
||||||
|
```java
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean saveDispatch(SaveDispatchDTO dto) {
|
||||||
|
// 批量更新所有参赛者的出场顺序
|
||||||
|
for (DetailAdjustment adjustment : dto.getAdjustments()) {
|
||||||
|
for (ParticipantOrder po : adjustment.getParticipants()) {
|
||||||
|
// 更新 performance_order 字段
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 前端页面集成
|
||||||
|
|
||||||
|
### 页面结构
|
||||||
|
|
||||||
|
```
|
||||||
|
编排页面
|
||||||
|
├── Tab切换
|
||||||
|
│ ├── 竞赛分组(编排完成后禁用)
|
||||||
|
│ ├── 场地(编排完成后禁用)
|
||||||
|
│ └── 调度(只有编排完成后可用)⭐
|
||||||
|
│
|
||||||
|
└── 调度Tab内容
|
||||||
|
├── 场地选择器
|
||||||
|
├── 时间段选择器
|
||||||
|
├── 分组列表
|
||||||
|
│ ├── 分组1
|
||||||
|
│ │ └── 参赛者列表(带上移/下移按钮)
|
||||||
|
│ ├── 分组2
|
||||||
|
│ │ └── 参赛者列表(带上移/下移按钮)
|
||||||
|
│ └── ...
|
||||||
|
└── 保存/取消按钮
|
||||||
|
```
|
||||||
|
|
||||||
|
### 核心方法
|
||||||
|
|
||||||
|
| 方法 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `handleSwitchToDispatch()` | 切换到调度Tab |
|
||||||
|
| `loadDispatchData()` | 加载调度数据 |
|
||||||
|
| `handleMoveUp(group, index)` | 上移参赛者 |
|
||||||
|
| `handleMoveDown(group, index)` | 下移参赛者 |
|
||||||
|
| `handleSaveDispatch()` | 保存调度 |
|
||||||
|
| `handleCancelDispatch()` | 取消调度 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔑 关键特性
|
||||||
|
|
||||||
|
### 1. 权限控制
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 调度Tab只有在编排完成后才可用
|
||||||
|
:disabled="!isScheduleCompleted"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 数据一致性
|
||||||
|
|
||||||
|
- ✅ 每次切换场地或时间段都重新加载数据
|
||||||
|
- ✅ 保存成功后重新加载数据
|
||||||
|
- ✅ 取消时恢复到原始数据
|
||||||
|
|
||||||
|
### 3. 用户体验
|
||||||
|
|
||||||
|
- ✅ 第一个不能上移(按钮禁用)
|
||||||
|
- ✅ 最后一个不能下移(按钮禁用)
|
||||||
|
- ✅ 有未保存更改时,取消需要确认
|
||||||
|
- ✅ 保存成功后显示提示
|
||||||
|
|
||||||
|
### 4. 性能优化
|
||||||
|
|
||||||
|
- ✅ 使用深拷贝保存原始数据
|
||||||
|
- ✅ 只在有更改时才允许保存
|
||||||
|
- ✅ 批量更新数据库
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 数据流转
|
||||||
|
|
||||||
|
```
|
||||||
|
用户操作
|
||||||
|
↓
|
||||||
|
前端:点击上移/下移
|
||||||
|
↓
|
||||||
|
前端:交换数组位置
|
||||||
|
↓
|
||||||
|
前端:更新 performanceOrder
|
||||||
|
↓
|
||||||
|
前端:标记 hasDispatchChanges = true
|
||||||
|
↓
|
||||||
|
用户:点击保存
|
||||||
|
↓
|
||||||
|
前端:调用 saveDispatch API
|
||||||
|
↓
|
||||||
|
后端:批量更新数据库
|
||||||
|
↓
|
||||||
|
后端:返回成功
|
||||||
|
↓
|
||||||
|
前端:重新加载数据
|
||||||
|
↓
|
||||||
|
前端:显示成功提示
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 部署步骤
|
||||||
|
|
||||||
|
### 1. 后端部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 编译后端代码
|
||||||
|
cd martial-master
|
||||||
|
mvn clean compile
|
||||||
|
|
||||||
|
# 2. 重启后端服务
|
||||||
|
mvn spring-boot:run
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 数据库升级(可选)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 创建调度日志表(可选,用于记录调整历史)
|
||||||
|
mysql -h localhost -P 3306 -u root -proot blade < database/martial-db/create_dispatch_log_table.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 前端部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 前端代码已经修改完成
|
||||||
|
# 2. 刷新浏览器即可看到调度Tab
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 测试步骤
|
||||||
|
|
||||||
|
### 1. 完成编排
|
||||||
|
|
||||||
|
1. 进入编排页面
|
||||||
|
2. 点击"自动编排"按钮
|
||||||
|
3. 点击"完成编排"按钮
|
||||||
|
4. 确认编排已锁定
|
||||||
|
|
||||||
|
### 2. 进入调<E585A5><E8B083><EFBFBD>模式
|
||||||
|
|
||||||
|
1. 点击"调度"Tab(应该可用)
|
||||||
|
2. 选择一个场地
|
||||||
|
3. 选择一个时间段
|
||||||
|
4. 查看分组列表
|
||||||
|
|
||||||
|
### 3. 调整顺序
|
||||||
|
|
||||||
|
1. 找到一个分组
|
||||||
|
2. 点击某个参赛者的"上移"按钮
|
||||||
|
3. 观察顺序变化
|
||||||
|
4. 点击"下移"按钮
|
||||||
|
5. 观察顺序变化
|
||||||
|
|
||||||
|
### 4. 保存调度
|
||||||
|
|
||||||
|
1. 点击"保存调度"按钮
|
||||||
|
2. 等待保存成功提示
|
||||||
|
3. 刷新页面
|
||||||
|
4. 验证顺序是否保持
|
||||||
|
|
||||||
|
### 5. 取消操作
|
||||||
|
|
||||||
|
1. 进行一些调整
|
||||||
|
2. 点击"取消"按钮
|
||||||
|
3. 确认弹出提示
|
||||||
|
4. 点击"确定"
|
||||||
|
5. 验证数据恢复
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 注意事项
|
||||||
|
|
||||||
|
### 1. 权限控制
|
||||||
|
|
||||||
|
- ✅ 只有编排完成后才能使用调度功能
|
||||||
|
- ✅ 编排完成后,编排Tab和场地Tab应该禁用
|
||||||
|
|
||||||
|
### 2. 数据安全
|
||||||
|
|
||||||
|
- ✅ 使用事务确保数据一致性
|
||||||
|
- ✅ 保存前验证数据有效性
|
||||||
|
- ✅ 异常时回滚事务
|
||||||
|
|
||||||
|
### 3. 用户体验
|
||||||
|
|
||||||
|
- ✅ 提供清晰的操作反馈
|
||||||
|
- ✅ 防止误操作(确认对话框)
|
||||||
|
- ✅ 按钮状态正确(禁用/启用)
|
||||||
|
|
||||||
|
### 4. 性能优化
|
||||||
|
|
||||||
|
- ✅ 避免频繁的数据库查询
|
||||||
|
- ✅ 批量更新而非逐条更新
|
||||||
|
- ✅ 前端使用深拷贝避免引用问题
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 API测试示例
|
||||||
|
|
||||||
|
### 1. 获取调度数据
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X GET "http://localhost:8123/api/blade-martial/schedule/dispatch-data?competitionId=1&venueId=1&timeSlotIndex=0"
|
||||||
|
```
|
||||||
|
|
||||||
|
**预期响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"groupId": 1,
|
||||||
|
"groupName": "男子A组 长拳",
|
||||||
|
"detailId": 101,
|
||||||
|
"projectType": 1,
|
||||||
|
"participants": [
|
||||||
|
{
|
||||||
|
"id": 1001,
|
||||||
|
"participantId": 501,
|
||||||
|
"organization": "北京体育大学",
|
||||||
|
"playerName": "张三",
|
||||||
|
"projectName": "长拳",
|
||||||
|
"category": "成年组",
|
||||||
|
"performanceOrder": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 调整出场顺序
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8123/api/blade-martial/schedule/adjust-order" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"detailId": 101,
|
||||||
|
"participantId": 1001,
|
||||||
|
"action": "move_up"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 批量保存调度
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8123/api/blade-martial/schedule/save-dispatch" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"competitionId": 1,
|
||||||
|
"adjustments": [
|
||||||
|
{
|
||||||
|
"detailId": 101,
|
||||||
|
"participants": [
|
||||||
|
{"id": 1001, "performanceOrder": 2},
|
||||||
|
{"id": 1002, "performanceOrder": 1}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 功能验证清单
|
||||||
|
|
||||||
|
- [ ] 后端编译成功
|
||||||
|
- [ ] 后端服务启动成功
|
||||||
|
- [ ] 调度Tab在编排完成前禁用
|
||||||
|
- [ ] 调度Tab在编排完成后可用
|
||||||
|
- [ ] 可以选择场地和时间段
|
||||||
|
- [ ] 可以查看分组和参赛者列表
|
||||||
|
- [ ] 上移按钮功能正常
|
||||||
|
- [ ] 下移按钮功能正常
|
||||||
|
- [ ] 第一个不能上移(按钮禁用)
|
||||||
|
- [ ] 最后一个不能下移(按钮禁用)
|
||||||
|
- [ ] 保存调度功能正常
|
||||||
|
- [ ] 取消调度功能正常
|
||||||
|
- [ ] 数据持久化正常
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎉 总结
|
||||||
|
|
||||||
|
调度功能已经完整实现,包括:
|
||||||
|
|
||||||
|
1. ✅ **后端完成**:DTO、VO、Service、Controller 全部实现
|
||||||
|
2. ✅ **前端API**:封装了3个调度相关接口
|
||||||
|
3. ✅ **页面方案**:提供了完整的集成方案和代码
|
||||||
|
4. ✅ **数据库**:可选的调度日志表
|
||||||
|
5. ✅ **文档齐全**:实现文档、测试指南、API文档
|
||||||
|
|
||||||
|
**核心特性**:
|
||||||
|
- 🔐 权限控制:只有编排完成后才能使用
|
||||||
|
- 🎯 简单易用:上移/下移按钮,操作直观
|
||||||
|
- 💾 数据安全:事务保证,批量更新
|
||||||
|
- 🎨 用户友好:清晰反馈,防止误操作
|
||||||
|
|
||||||
|
现在可以开始部署和测试了!🚀
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 技术支持
|
||||||
|
|
||||||
|
如有问题,请参考:
|
||||||
|
- [详细实现文档](./schedule-dispatch-implementation.md)
|
||||||
|
- [移动功能分析](./schedule-move-group-analysis.md)
|
||||||
|
|
||||||
|
祝使用愉快!✨
|
||||||
332
docs/DISPATCH_REFACTOR_SUMMARY.md
Normal file
332
docs/DISPATCH_REFACTOR_SUMMARY.md
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
# 调度功能重构总结
|
||||||
|
|
||||||
|
## ✅ 重构完成
|
||||||
|
|
||||||
|
根据您的要求,已成功将调度功能从编排页面的Tab移动到独立的调度页面,并添加了编排完成状态检查。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 修改内容
|
||||||
|
|
||||||
|
### 1. 编排页面 ([schedule/index.vue](../../martial-web/src/views/martial/schedule/index.vue))
|
||||||
|
|
||||||
|
#### 移除的内容:
|
||||||
|
- ❌ 调度Tab按钮(第41-48行已删除)
|
||||||
|
- ❌ 调度Tab内容区域(第177-259行已删除)
|
||||||
|
- ❌ 调度相关数据属性(`dispatchGroups`, `hasDispatchChanges`, `originalDispatchData`)
|
||||||
|
- ❌ 调度相关方法(`handleSwitchToDispatch`, `loadDispatchData`, `handleDispatchMoveUp`, `handleDispatchMoveDown`, `updatePerformanceOrder`, `handleSaveDispatch`, `handleCancelDispatch`)
|
||||||
|
- ❌ 调度相关样式(`.dispatch-container`, `.dispatch-group`, `.dispatch-footer`)
|
||||||
|
- ❌ 调度相关API导入(`getDispatchData`, `saveDispatch`)
|
||||||
|
|
||||||
|
#### 修复的内容:
|
||||||
|
- ✅ 修复`confirmComplete`方法,正确调用`saveAndLockSchedule`接口
|
||||||
|
- ✅ 完成编排后重新加载数据以获取最新状态
|
||||||
|
|
||||||
|
**关键代码**:
|
||||||
|
```javascript
|
||||||
|
// 修复后的完成编排逻辑
|
||||||
|
await saveDraftSchedule(saveData)
|
||||||
|
const lockRes = await saveAndLockSchedule(this.competitionId)
|
||||||
|
this.isScheduleCompleted = true
|
||||||
|
await this.loadScheduleData() // 重新加载数据
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 订单管理页面 ([order/index.vue](../../martial-web/src/views/martial/order/index.vue))
|
||||||
|
|
||||||
|
#### 新增的内容:
|
||||||
|
- ✅ 导入`getScheduleResult` API
|
||||||
|
- ✅ 添加`scheduleStatusMap`数据属性,存储每个赛事的编排状态
|
||||||
|
- ✅ 添加`loadScheduleStatus()`方法,加载所有赛事的编排状态
|
||||||
|
- ✅ 添加`isScheduleCompleted(competitionId)`方法,检查编排是否完成
|
||||||
|
- ✅ 修改`handleDispatch`方法,添加编排完成检查
|
||||||
|
- ✅ 调度按钮添加`:disabled`属性和`:title`提示
|
||||||
|
|
||||||
|
**关键代码**:
|
||||||
|
```vue
|
||||||
|
<!-- 调度按钮 -->
|
||||||
|
<el-button
|
||||||
|
type="warning"
|
||||||
|
size="small"
|
||||||
|
@click="handleDispatch(scope.row)"
|
||||||
|
:disabled="!isScheduleCompleted(scope.row.id)"
|
||||||
|
:title="isScheduleCompleted(scope.row.id) ? '进入调度' : '请先完成编排'"
|
||||||
|
>
|
||||||
|
调度
|
||||||
|
</el-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 检查编排是否完成
|
||||||
|
handleDispatch(row) {
|
||||||
|
if (!this.isScheduleCompleted(row.id)) {
|
||||||
|
this.$message.warning('请先完成编排后再进行调度')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$router.push({
|
||||||
|
path: '/martial/dispatch/list',
|
||||||
|
query: { competitionId: row.id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 调度页面 ([dispatch/index.vue](../../martial-web/src/views/martial/dispatch/index.vue))
|
||||||
|
|
||||||
|
#### 更新的内容:
|
||||||
|
- ✅ 导入后端API(`getVenuesByCompetition`, `getCompetitionDetail`, `getDispatchData`, `saveDispatch`)
|
||||||
|
- ✅ 移除静态数据,改为从后端加载
|
||||||
|
- ✅ 添加`loadCompetitionInfo()`方法,加载赛事信息并生成时间段
|
||||||
|
- ✅ 添加`loadVenues()`方法,加载场地列表
|
||||||
|
- ✅ 添加`loadDispatchData()`方法,根据场地和时间段加载调度数据
|
||||||
|
- ✅ 添加`handleSaveDispatch()`方法,保存调度调整
|
||||||
|
- ✅ 更新`handleMoveUp`和`handleMoveDown`方法,添加`performanceOrder`更新逻辑
|
||||||
|
- ✅ 添加场地选择器UI
|
||||||
|
- ✅ 添加保存按钮UI
|
||||||
|
- ✅ 添加`hasChanges`状态跟踪
|
||||||
|
|
||||||
|
**关键代码**:
|
||||||
|
```javascript
|
||||||
|
// 加载调度数据
|
||||||
|
async loadDispatchData() {
|
||||||
|
const res = await getDispatchData({
|
||||||
|
competitionId: this.competitionId,
|
||||||
|
venueId: this.selectedVenueId,
|
||||||
|
timeSlotIndex: this.selectedTime
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.data.success) {
|
||||||
|
const groups = res.data.data.groups || []
|
||||||
|
this.dispatchGroups = groups.map(group => ({
|
||||||
|
...group,
|
||||||
|
viewMode: 'dispatch',
|
||||||
|
title: group.groupName,
|
||||||
|
items: group.participants.map(p => ({
|
||||||
|
...p,
|
||||||
|
schoolUnit: p.organization,
|
||||||
|
completed: false,
|
||||||
|
refereed: false
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
this.originalData = JSON.parse(JSON.stringify(this.dispatchGroups))
|
||||||
|
this.hasChanges = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存调度
|
||||||
|
async handleSaveDispatch() {
|
||||||
|
const adjustments = this.dispatchGroups.map(group => ({
|
||||||
|
detailId: group.detailId,
|
||||||
|
participants: group.items.map(p => ({
|
||||||
|
id: p.id,
|
||||||
|
performanceOrder: p.performanceOrder
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
|
||||||
|
const res = await saveDispatch({
|
||||||
|
competitionId: this.competitionId,
|
||||||
|
adjustments
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.data.success) {
|
||||||
|
this.$message.success('调度保存成功')
|
||||||
|
this.hasChanges = false
|
||||||
|
await this.loadDispatchData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 功能流程
|
||||||
|
|
||||||
|
### 1. 编排流程
|
||||||
|
```
|
||||||
|
订单管理页面
|
||||||
|
↓
|
||||||
|
点击"编排"按钮
|
||||||
|
↓
|
||||||
|
进入编排页面
|
||||||
|
↓
|
||||||
|
点击"自动编排"
|
||||||
|
↓
|
||||||
|
调整分组和参赛者
|
||||||
|
↓
|
||||||
|
点击"完成编排"
|
||||||
|
↓
|
||||||
|
保存草稿 → 锁定编排 → 更新状态
|
||||||
|
↓
|
||||||
|
编排完成(isScheduleCompleted = true)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 调度流程
|
||||||
|
```
|
||||||
|
订单管理页面
|
||||||
|
↓
|
||||||
|
检查编排是否完成
|
||||||
|
↓
|
||||||
|
如果未完成:调度按钮禁用,显示提示
|
||||||
|
如果已完成:调度按钮可用
|
||||||
|
↓
|
||||||
|
点击"调度"按钮
|
||||||
|
↓
|
||||||
|
进入调度页面
|
||||||
|
↓
|
||||||
|
选择场地和时间段
|
||||||
|
↓
|
||||||
|
加载调度数据
|
||||||
|
↓
|
||||||
|
调整参赛者顺序(上移/下移)
|
||||||
|
↓
|
||||||
|
点击"保存调度"
|
||||||
|
↓
|
||||||
|
批量更新数据库
|
||||||
|
↓
|
||||||
|
调度完成
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔌 后端接口
|
||||||
|
|
||||||
|
### 1. 编排相关接口
|
||||||
|
| 接口 | 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 获取编排结果 | GET | `/api/blade-martial/schedule/result` | 获取编排数据和状态 |
|
||||||
|
| 保存草稿 | POST | `/api/blade-martial/schedule/save-draft` | 保存编排草稿 |
|
||||||
|
| 完成编排 | POST | `/api/blade-martial/schedule/save-and-lock` | 锁定编排 |
|
||||||
|
|
||||||
|
### 2. 调度相关接口
|
||||||
|
| 接口 | 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 获取调度数据 | GET | `/api/blade-martial/schedule/dispatch-data` | 获取指定场地和时间段的调度数据 |
|
||||||
|
| 批量保存调度 | POST | `/api/blade-martial/schedule/save-dispatch` | 批量保存调度调整 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ 核心特性
|
||||||
|
|
||||||
|
### 1. 权限控制
|
||||||
|
- ✅ 调度功能独立于编排页面
|
||||||
|
- ✅ 只有编排完成后才能进入调度页面
|
||||||
|
- ✅ 订单管理页面实时检查编排状态
|
||||||
|
- ✅ 调度按钮根据状态自动禁用/启用
|
||||||
|
|
||||||
|
### 2. 数据流转
|
||||||
|
- ✅ 编排完成后,状态保存到数据库
|
||||||
|
- ✅ 订单管理页面加载时检查所有赛事的编排状态
|
||||||
|
- ✅ 调度页面从后端加载真实数据
|
||||||
|
- ✅ 调度调整保存到数据库
|
||||||
|
|
||||||
|
### 3. 用户体验
|
||||||
|
- ✅ 调度按钮有明确的禁用状态和提示
|
||||||
|
- ✅ 未完成编排时点击调度按钮会显示警告
|
||||||
|
- ✅ 调度页面有场地和时间段选择器
|
||||||
|
- ✅ 调度页面有保存按钮,只有有更改时才可用
|
||||||
|
- ✅ 操作成功后显示提示消息
|
||||||
|
|
||||||
|
### 4. 数据一致性
|
||||||
|
- ✅ 编排完成后重新加载数据确保状态同步
|
||||||
|
- ✅ 调度保存后重新加载数据确保数据一致
|
||||||
|
- ✅ 使用深拷贝保存原始数据
|
||||||
|
- ✅ 批量更新数据库而非逐条更新
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 测试步骤
|
||||||
|
|
||||||
|
### 1. 测试编排完成
|
||||||
|
1. 进入订单管理页面
|
||||||
|
2. 点击某个赛事的"编排"按钮
|
||||||
|
3. 点击"自动编排"
|
||||||
|
4. 点击"完成编排"
|
||||||
|
5. 确认编排已锁定
|
||||||
|
6. 返回订单管理页面
|
||||||
|
7. **验证**:该赛事的"调度"按钮应该可用
|
||||||
|
|
||||||
|
### 2. 测试调度按钮禁用
|
||||||
|
1. 进入订单管理页面
|
||||||
|
2. 找到一个未完成编排的赛事
|
||||||
|
3. **验证**:该赛事的"调度"按钮应该禁用
|
||||||
|
4. 鼠标悬停在调度按钮上
|
||||||
|
5. **验证**:应该显示"请先完成编排"提示
|
||||||
|
6. 点击调度按钮
|
||||||
|
7. **验证**:应该显示警告消息
|
||||||
|
|
||||||
|
### 3. 测试调度功能
|
||||||
|
1. 进入订单管理页面
|
||||||
|
2. 点击已完成编排的赛事的"调度"按钮
|
||||||
|
3. 进入调度页面
|
||||||
|
4. 选择一个场地
|
||||||
|
5. 选择一个时间段
|
||||||
|
6. **验证**:应该显示该场地和时间段的分组和参赛者
|
||||||
|
7. 点击某个参赛者的"上移"按钮
|
||||||
|
8. **验证**:参赛者顺序应该改变
|
||||||
|
9. 点击"保存调度"按钮
|
||||||
|
10. **验证**:应该显示"调度保存成功"提示
|
||||||
|
11. 刷新页面
|
||||||
|
12. **验证**:顺序应该保持
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 注意事项
|
||||||
|
|
||||||
|
### 1. 编排状态检查
|
||||||
|
- 订单管理页面加载时会检查所有赛事的编排状态
|
||||||
|
- 这可能会产生多个API请求,建议后端优化为批量查询
|
||||||
|
|
||||||
|
### 2. 数据格式
|
||||||
|
- 调度页面期望后端返回的数据格式:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"groupId": 1,
|
||||||
|
"groupName": "男子A组 长拳",
|
||||||
|
"detailId": 101,
|
||||||
|
"participants": [
|
||||||
|
{
|
||||||
|
"id": 1001,
|
||||||
|
"organization": "北京体育大学",
|
||||||
|
"playerName": "张三",
|
||||||
|
"projectName": "长拳",
|
||||||
|
"performanceOrder": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 路由参数
|
||||||
|
- 编排页面:`/martial/schedule/list?competitionId=xxx`
|
||||||
|
- 调度页面:`/martial/dispatch/list?competitionId=xxx`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 文件清单
|
||||||
|
|
||||||
|
### 修改的文件
|
||||||
|
1. [martial-web/src/views/martial/schedule/index.vue](../../martial-web/src/views/martial/schedule/index.vue) - 编排页面
|
||||||
|
2. [martial-web/src/views/martial/order/index.vue](../../martial-web/src/views/martial/order/index.vue) - 订单管理页面
|
||||||
|
3. [martial-web/src/views/martial/dispatch/index.vue](../../martial-web/src/views/martial/dispatch/index.vue) - 调度页面
|
||||||
|
|
||||||
|
### 相关文档
|
||||||
|
1. [DISPATCH_FEATURE_SUMMARY.md](./DISPATCH_FEATURE_SUMMARY.md) - 调度功能实现总结
|
||||||
|
2. [schedule-dispatch-implementation.md](./schedule-dispatch-implementation.md) - 调度功能实现文档
|
||||||
|
3. [DISPATCH_TAB_IMPLEMENTATION.md](./DISPATCH_TAB_IMPLEMENTATION.md) - 调度Tab实现文档(已过时)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎉 总结
|
||||||
|
|
||||||
|
调度功能已成功重构,主要改进:
|
||||||
|
|
||||||
|
1. ✅ **独立页面**:调度功能从编排页面的Tab移动到独立页面
|
||||||
|
2. ✅ **权限控制**:只有编排完成后才能进入调度页面
|
||||||
|
3. ✅ **状态检查**:订单管理页面实时检查编排状态
|
||||||
|
4. ✅ **后端集成**:调度页面从后端加载真实数据
|
||||||
|
5. ✅ **用户体验**:清晰的按钮状态和操作提示
|
||||||
|
|
||||||
|
现在可以开始测试新的调度流程了!🚀
|
||||||
313
docs/DISPATCH_TAB_IMPLEMENTATION.md
Normal file
313
docs/DISPATCH_TAB_IMPLEMENTATION.md
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
# 调度Tab实现完成
|
||||||
|
|
||||||
|
## ✅ 实现概述
|
||||||
|
|
||||||
|
调度功能已成功集成到编排页面中,用户可以在完成编排后使用调度Tab来调整参赛者的出场顺序。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 实现内容
|
||||||
|
|
||||||
|
### 1. 前端页面修改
|
||||||
|
|
||||||
|
**文件**: `martial-web/src/views/martial/schedule/index.vue`
|
||||||
|
|
||||||
|
#### 新增内容:
|
||||||
|
|
||||||
|
1. **调度Tab按钮** (第41-48行)
|
||||||
|
- 只有在编排完成后才可用 (`:disabled="!isScheduleCompleted"`)
|
||||||
|
- 点击时调用 `handleSwitchToDispatch` 方法
|
||||||
|
|
||||||
|
2. **调度Tab内容** (第185-267行)
|
||||||
|
- 场地选择器
|
||||||
|
- 时间段选择器
|
||||||
|
- 分组列表展示
|
||||||
|
- 参赛者表格(包含上移/下移按钮)
|
||||||
|
- 保存/取消按钮
|
||||||
|
|
||||||
|
3. **数据属性** (第403-406行)
|
||||||
|
```javascript
|
||||||
|
dispatchGroups: [], // 调度分组列表
|
||||||
|
hasDispatchChanges: false, // 是否有未保存的更改
|
||||||
|
originalDispatchData: null // 原始调度数据(用于取消时恢复)
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **调度方法** (第893-1063行)
|
||||||
|
- `handleSwitchToDispatch()` - 切换到调度Tab
|
||||||
|
- `handleSelectVenue(venueId)` - 选择场地
|
||||||
|
- `handleSelectTime(timeIndex)` - 选择时间段
|
||||||
|
- `loadDispatchData()` - 加载调度数据
|
||||||
|
- `handleDispatchMoveUp(group, index)` - 上移参赛者
|
||||||
|
- `handleDispatchMoveDown(group, index)` - 下移参赛者
|
||||||
|
- `updatePerformanceOrder(group)` - 更新出场顺序
|
||||||
|
- `handleSaveDispatch()` - 保存调度
|
||||||
|
- `handleCancelDispatch()` - 取消调度
|
||||||
|
|
||||||
|
5. **样式** (第1268-1314行)
|
||||||
|
- `.dispatch-container` - 调度容器样式
|
||||||
|
- `.dispatch-group` - 调度分组样式
|
||||||
|
- `.dispatch-footer` - 底部按钮样式
|
||||||
|
|
||||||
|
### 2. API导入
|
||||||
|
|
||||||
|
**文件**: `martial-web/src/api/martial/activitySchedule.js`
|
||||||
|
|
||||||
|
已导入的API函数:
|
||||||
|
- `getDispatchData` - 获取调度数据
|
||||||
|
- `saveDispatch` - 批量保存调度
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 功能特性
|
||||||
|
|
||||||
|
### 1. 权限控制
|
||||||
|
- ✅ 调度Tab只有在编排完成后才可用
|
||||||
|
- ✅ 编排完成前,调度Tab按钮禁用并显示灰色
|
||||||
|
|
||||||
|
### 2. 数据加载
|
||||||
|
- ✅ 切换到调度Tab时自动加载数据
|
||||||
|
- ✅ 切换场地或时间段时重新加载对应数据
|
||||||
|
- ✅ 保存成功后重新加载数据确保同步
|
||||||
|
|
||||||
|
### 3. 顺序调整
|
||||||
|
- ✅ 上移按钮:将参赛者向上移动一位
|
||||||
|
- ✅ 下移按钮:将参赛者向下移动一位
|
||||||
|
- ✅ 第一个参赛者的上移按钮自动禁用
|
||||||
|
- ✅ 最后一个参赛者的下移按钮自动禁用
|
||||||
|
- ✅ 每次移动后自动更新 `performanceOrder` 字段
|
||||||
|
|
||||||
|
### 4. 数据保存
|
||||||
|
- ✅ 只有有更改时才允许保存(保存按钮启用)
|
||||||
|
- ✅ 批量保存所有调整到后端
|
||||||
|
- ✅ 保存成功后显示提示并重新加载数据
|
||||||
|
|
||||||
|
### 5. 取消操作
|
||||||
|
- ✅ 有未保存更改时,取消需要确认
|
||||||
|
- ✅ 确认后恢复到原始数据
|
||||||
|
- ✅ 无更改时,直接切换回竞赛分组Tab
|
||||||
|
|
||||||
|
### 6. 用户体验
|
||||||
|
- ✅ 操作成功后显示提示消息
|
||||||
|
- ✅ 按钮状态正确(禁用/启用)
|
||||||
|
- ✅ 使用图标按钮,操作直观
|
||||||
|
- ✅ 数据加载时显示loading状态
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔌 后端接口
|
||||||
|
|
||||||
|
### 1. 获取调度数据
|
||||||
|
- **URL**: `GET /api/blade-martial/schedule/dispatch-data`
|
||||||
|
- **参数**:
|
||||||
|
- `competitionId`: 赛事ID
|
||||||
|
- `venueId`: 场地ID
|
||||||
|
- `timeSlotIndex`: 时间段索引
|
||||||
|
- **返回**: 调度数据(分组和参赛者列表)
|
||||||
|
|
||||||
|
### 2. 批量保存调度
|
||||||
|
- **URL**: `POST /api/blade-martial/schedule/save-dispatch`
|
||||||
|
- **参数**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"competitionId": 1,
|
||||||
|
"adjustments": [
|
||||||
|
{
|
||||||
|
"detailId": 101,
|
||||||
|
"participants": [
|
||||||
|
{"id": 1001, "performanceOrder": 1},
|
||||||
|
{"id": 1002, "performanceOrder": 2}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **返回**: 保存结果
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 数据流程
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 用户完成编排
|
||||||
|
↓
|
||||||
|
2. 点击"调度"Tab
|
||||||
|
↓
|
||||||
|
3. 检查编排是否完成 (isScheduleCompleted)
|
||||||
|
↓
|
||||||
|
4. 加载调度数据 (loadDispatchData)
|
||||||
|
↓
|
||||||
|
5. 显示分组和参赛者列表
|
||||||
|
↓
|
||||||
|
6. 用户点击上移/下移按钮
|
||||||
|
↓
|
||||||
|
7. 交换数组位置
|
||||||
|
↓
|
||||||
|
8. 更新 performanceOrder
|
||||||
|
↓
|
||||||
|
9. 标记 hasDispatchChanges = true
|
||||||
|
↓
|
||||||
|
10. 用户点击"保存调度"
|
||||||
|
↓
|
||||||
|
11. 调用 saveDispatch API
|
||||||
|
↓
|
||||||
|
12. 后端批量更新数据库
|
||||||
|
↓
|
||||||
|
13. 返回成功
|
||||||
|
↓
|
||||||
|
14. 重新加载数据
|
||||||
|
↓
|
||||||
|
15. 显示成功提示
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 测试步骤
|
||||||
|
|
||||||
|
### 1. 完成编排
|
||||||
|
1. 进入编排页面
|
||||||
|
2. 点击"自动编排"按钮
|
||||||
|
3. 点击"完成编排"按钮
|
||||||
|
4. 确认编排已锁定
|
||||||
|
|
||||||
|
### 2. 进入调度模式
|
||||||
|
1. 点击"调度"Tab(应该可用)
|
||||||
|
2. 选择一个场地
|
||||||
|
3. 选择一个时间段
|
||||||
|
4. 查看分组和参赛者列表
|
||||||
|
|
||||||
|
### 3. 调整顺序
|
||||||
|
1. 找到一个分组
|
||||||
|
2. 点击某个参赛者的"上移"按钮
|
||||||
|
3. 观察顺序变化和成功提示
|
||||||
|
4. 点击"下移"按钮
|
||||||
|
5. 观察顺序变化和成功提示
|
||||||
|
6. 验证第一个不能上移(按钮禁用)
|
||||||
|
7. 验证最后一个不能下移(按钮禁用)
|
||||||
|
|
||||||
|
### 4. 保存调度
|
||||||
|
1. 进行一些调整
|
||||||
|
2. 观察"保存调度"按钮变为可用
|
||||||
|
3. 点击"保存调度"按钮
|
||||||
|
4. 等待保存成功提示
|
||||||
|
5. 刷新页面
|
||||||
|
6. 验证顺序是否保持
|
||||||
|
|
||||||
|
### 5. 取消操作
|
||||||
|
1. 进行一些调整
|
||||||
|
2. 点击"取消"按钮
|
||||||
|
3. 确认弹出提示
|
||||||
|
4. 点击"确定"
|
||||||
|
5. 验证数据恢复到原始状态
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 注意事项
|
||||||
|
|
||||||
|
### 1. 权限控制
|
||||||
|
- 调度Tab只有在 `isScheduleCompleted === true` 时才可用
|
||||||
|
- 编排完成后,编排Tab和场地Tab会被禁用
|
||||||
|
|
||||||
|
### 2. 数据一致性
|
||||||
|
- 每次切换场地或时间段都重新加载数据
|
||||||
|
- 保存前检查是否有未保存的更改
|
||||||
|
- 使用深拷贝保存原始数据,避免引用问题
|
||||||
|
|
||||||
|
### 3. 用户体验
|
||||||
|
- 有未保存更改时,取消操作需要确认
|
||||||
|
- 第一个不能上移,最后一个不能下移
|
||||||
|
- 保存成功后显示提示并刷新数据
|
||||||
|
- 操作按钮使用图标,更加直观
|
||||||
|
|
||||||
|
### 4. 性能优化
|
||||||
|
- 使用深拷贝保存原始数据
|
||||||
|
- 只在有更改时才允许保存
|
||||||
|
- 批量更新数据库而非逐条更新
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 代码关键点
|
||||||
|
|
||||||
|
### 1. Tab切换逻辑
|
||||||
|
```vue
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
:type="activeTab === 'dispatch' ? 'primary' : ''"
|
||||||
|
@click="handleSwitchToDispatch"
|
||||||
|
:disabled="!isScheduleCompleted">
|
||||||
|
调度
|
||||||
|
</el-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 上移/下移按钮
|
||||||
|
```vue
|
||||||
|
<el-button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
:disabled="$index === 0"
|
||||||
|
@click="handleDispatchMoveUp(group, $index)">
|
||||||
|
<img src="/img/图标 3@3x.png" class="move-icon" alt="上移" />
|
||||||
|
</el-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 数据交换逻辑
|
||||||
|
```javascript
|
||||||
|
handleDispatchMoveUp(group, index) {
|
||||||
|
if (index === 0) return
|
||||||
|
const participants = group.participants
|
||||||
|
// 交换位置
|
||||||
|
const temp = participants[index]
|
||||||
|
participants[index] = participants[index - 1]
|
||||||
|
participants[index - 1] = temp
|
||||||
|
// 更新顺序号
|
||||||
|
this.updatePerformanceOrder(group)
|
||||||
|
this.hasDispatchChanges = true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 保存调度逻辑
|
||||||
|
```javascript
|
||||||
|
async handleSaveDispatch() {
|
||||||
|
const adjustments = this.dispatchGroups.map(group => ({
|
||||||
|
detailId: group.detailId,
|
||||||
|
participants: group.participants.map(p => ({
|
||||||
|
id: p.id,
|
||||||
|
performanceOrder: p.performanceOrder
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
|
||||||
|
const res = await saveDispatch({
|
||||||
|
competitionId: this.competitionId,
|
||||||
|
adjustments
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.data.success) {
|
||||||
|
this.$message.success('调度保存成功')
|
||||||
|
await this.loadDispatchData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎉 总结
|
||||||
|
|
||||||
|
调度Tab已成功集成到编排页面中,实现了以下功能:
|
||||||
|
|
||||||
|
1. ✅ **Tab切换**: 编排完成后可切换到调度Tab
|
||||||
|
2. ✅ **数据加载**: 根据场地和时间段加载调度数据
|
||||||
|
3. ✅ **顺序调整**: 支持上移/下移参赛者
|
||||||
|
4. ✅ **数据保存**: 批量保存调度调整到后端
|
||||||
|
5. ✅ **取消操作**: 支持取消未保存的更改
|
||||||
|
6. ✅ **用户体验**: 清晰的操作反馈和按钮状态控制
|
||||||
|
|
||||||
|
现在可以开始测试调度功能了!🚀
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 相关文档
|
||||||
|
|
||||||
|
- [调度功能实现文档](./schedule-dispatch-implementation.md)
|
||||||
|
- [调度功能总结](./DISPATCH_FEATURE_SUMMARY.md)
|
||||||
|
- [后端Controller](../src/main/java/org/springblade/modules/martial/controller/MartialScheduleArrangeController.java)
|
||||||
|
- [前端API](../../martial-web/src/api/martial/activitySchedule.js)
|
||||||
|
- [前端页面](../../martial-web/src/views/martial/schedule/index.vue)
|
||||||
485
docs/schedule-dispatch-implementation.md
Normal file
485
docs/schedule-dispatch-implementation.md
Normal file
@@ -0,0 +1,485 @@
|
|||||||
|
# 调度功能实现文档
|
||||||
|
|
||||||
|
## 📋 实现总结
|
||||||
|
|
||||||
|
调度功能已经完成后端和前端API的开发,现在需要在前端页面中集成调度功能。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 前端页面修改方案
|
||||||
|
|
||||||
|
### 方案:在编排页面添加调度Tab
|
||||||
|
|
||||||
|
修改 `src/views/martial/schedule/index.vue` 文件,在现有的"竞赛分组"和"场地"Tab基础上,添加"调度"Tab。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💻 前端代码实现
|
||||||
|
|
||||||
|
### 1. 在 `<template>` 中添加调度Tab
|
||||||
|
|
||||||
|
在现有的 `tabs-section` 中添加调度按钮和内容:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<div class="tabs-section">
|
||||||
|
<div class="tab-buttons">
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
:type="activeTab === 'competition' ? 'primary' : ''"
|
||||||
|
@click="activeTab = 'competition'"
|
||||||
|
:disabled="isScheduleCompleted">
|
||||||
|
竞赛分组
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
:type="activeTab === 'venue' ? 'primary' : ''"
|
||||||
|
@click="activeTab = 'venue'"
|
||||||
|
:disabled="isScheduleCompleted">
|
||||||
|
场地
|
||||||
|
</el-button>
|
||||||
|
<!-- 新增:调度Tab -->
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
:type="activeTab === 'dispatch' ? 'primary' : ''"
|
||||||
|
@click="handleSwitchToDispatch"
|
||||||
|
:disabled="!isScheduleCompleted">
|
||||||
|
调度
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 竞赛分组 Tab -->
|
||||||
|
<div v-show="activeTab === 'competition'" class="tab-content">
|
||||||
|
<!-- 原有的竞赛分组内容 -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 场地 Tab -->
|
||||||
|
<div v-show="activeTab === 'venue'" class="tab-content">
|
||||||
|
<!-- 原有的场地内容 -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 新增:调度 Tab -->
|
||||||
|
<div v-show="activeTab === 'dispatch'" class="tab-content">
|
||||||
|
<div class="dispatch-container">
|
||||||
|
<!-- 场地和时间段选择 -->
|
||||||
|
<div class="venue-list">
|
||||||
|
<div class="venue-buttons">
|
||||||
|
<el-button
|
||||||
|
v-for="venue in venues"
|
||||||
|
:key="venue.id"
|
||||||
|
size="small"
|
||||||
|
:type="selectedVenueId === venue.id ? 'primary' : ''"
|
||||||
|
@click="handleSelectVenue(venue.id)">
|
||||||
|
{{ venue.venueName }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="time-selector">
|
||||||
|
<el-button
|
||||||
|
v-for="(time, index) in timeSlots"
|
||||||
|
:key="index"
|
||||||
|
size="small"
|
||||||
|
:type="selectedTime === index ? 'primary' : ''"
|
||||||
|
@click="handleSelectTime(index)">
|
||||||
|
{{ time }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分组列表 -->
|
||||||
|
<div v-for="group in dispatchGroups" :key="group.groupId" class="dispatch-group">
|
||||||
|
<div class="group-header">
|
||||||
|
<h3 class="group-title">{{ group.groupName }}</h3>
|
||||||
|
<span class="participant-count">({{ group.participants.length }}人)</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 参赛者列表 -->
|
||||||
|
<el-table :data="group.participants" border stripe size="small">
|
||||||
|
<el-table-column label="序号" width="80" align="center">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
{{ $index + 1 }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="organization" label="学校/单位" min-width="200"></el-table-column>
|
||||||
|
<el-table-column prop="playerName" label="选手姓名" width="120"></el-table-column>
|
||||||
|
<el-table-column prop="projectName" label="项目" width="150"></el-table-column>
|
||||||
|
<el-table-column label="操作" width="180" align="center">
|
||||||
|
<template #default="{ row, $index }">
|
||||||
|
<el-button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
:disabled="$index === 0"
|
||||||
|
@click="handleMoveUp(group, $index)">
|
||||||
|
<img src="/img/图标 3@3x.png" class="move-icon" alt="上移" />
|
||||||
|
上移
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
:disabled="$index === group.participants.length - 1"
|
||||||
|
@click="handleMoveDown(group, $index)">
|
||||||
|
<img src="/img/图标 4@3x.png" class="move-icon" alt="下移" />
|
||||||
|
下移
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 保存按钮 -->
|
||||||
|
<div class="dispatch-footer" v-if="dispatchGroups.length > 0">
|
||||||
|
<el-button @click="handleCancelDispatch">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleSaveDispatch" :disabled="!hasDispatchChanges">
|
||||||
|
保存调度
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 在 `<script>` 中添加数据和方法
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { getDispatchData, saveDispatch } from '@/api/martial/activitySchedule'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// ... 原有数据
|
||||||
|
activeTab: 'competition', // 修改:支持 'competition' | 'venue' | 'dispatch'
|
||||||
|
|
||||||
|
// 调度相关数据
|
||||||
|
dispatchGroups: [], // 调度分组列表
|
||||||
|
hasDispatchChanges: false, // 是否有未保存的更改
|
||||||
|
originalDispatchData: null // 原始调度数据(用于取消时恢复)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
// ... 原有方法
|
||||||
|
|
||||||
|
// ==================== 调度功能方法 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换到调度Tab
|
||||||
|
*/
|
||||||
|
handleSwitchToDispatch() {
|
||||||
|
if (!this.isScheduleCompleted) {
|
||||||
|
this.$message.warning('请先完成编排后再进行调度')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.activeTab = 'dispatch'
|
||||||
|
this.loadDispatchData()
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择场地(调度模式)
|
||||||
|
*/
|
||||||
|
handleSelectVenue(venueId) {
|
||||||
|
this.selectedVenueId = venueId
|
||||||
|
this.loadDispatchData()
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择时间段(调度模式)
|
||||||
|
*/
|
||||||
|
handleSelectTime(timeIndex) {
|
||||||
|
this.selectedTime = timeIndex
|
||||||
|
this.loadDispatchData()
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载调度数据
|
||||||
|
*/
|
||||||
|
async loadDispatchData() {
|
||||||
|
if (!this.selectedVenueId || this.selectedTime === null) {
|
||||||
|
this.dispatchGroups = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.loading = true
|
||||||
|
const res = await getDispatchData({
|
||||||
|
competitionId: this.competitionId,
|
||||||
|
venueId: this.selectedVenueId,
|
||||||
|
timeSlotIndex: this.selectedTime
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.data.success) {
|
||||||
|
this.dispatchGroups = res.data.data.groups || []
|
||||||
|
// 保存原始数据,用于取消时恢复
|
||||||
|
this.originalDispatchData = JSON.parse(JSON.stringify(this.dispatchGroups))
|
||||||
|
this.hasDispatchChanges = false
|
||||||
|
} else {
|
||||||
|
this.$message.error(res.data.msg || '加载调度数据失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载调度数据失败:', error)
|
||||||
|
this.$message.error('加载调度数据失败')
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上移参赛者
|
||||||
|
*/
|
||||||
|
handleMoveUp(group, index) {
|
||||||
|
if (index === 0) return
|
||||||
|
|
||||||
|
const participants = group.participants
|
||||||
|
// 交换位置
|
||||||
|
const temp = participants[index]
|
||||||
|
participants[index] = participants[index - 1]
|
||||||
|
participants[index - 1] = temp
|
||||||
|
|
||||||
|
// 更新顺序号
|
||||||
|
this.updatePerformanceOrder(group)
|
||||||
|
this.hasDispatchChanges = true
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下移参赛者
|
||||||
|
*/
|
||||||
|
handleMoveDown(group, index) {
|
||||||
|
const participants = group.participants
|
||||||
|
if (index === participants.length - 1) return
|
||||||
|
|
||||||
|
// 交换位置
|
||||||
|
const temp = participants[index]
|
||||||
|
participants[index] = participants[index + 1]
|
||||||
|
participants[index + 1] = temp
|
||||||
|
|
||||||
|
// 更新顺序号
|
||||||
|
this.updatePerformanceOrder(group)
|
||||||
|
this.hasDispatchChanges = true
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新出场顺序
|
||||||
|
*/
|
||||||
|
updatePerformanceOrder(group) {
|
||||||
|
group.participants.forEach((p, index) => {
|
||||||
|
p.performanceOrder = index + 1
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存调度
|
||||||
|
*/
|
||||||
|
async handleSaveDispatch() {
|
||||||
|
if (!this.hasDispatchChanges) {
|
||||||
|
this.$message.info('没有需要保存的更改')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
// 构建保存数据
|
||||||
|
const adjustments = this.dispatchGroups.map(group => ({
|
||||||
|
detailId: group.detailId,
|
||||||
|
participants: group.participants.map(p => ({
|
||||||
|
id: p.id,
|
||||||
|
performanceOrder: p.performanceOrder
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
|
||||||
|
const res = await saveDispatch({
|
||||||
|
competitionId: this.competitionId,
|
||||||
|
adjustments
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.data.success) {
|
||||||
|
this.$message.success('调度保存成功')
|
||||||
|
this.hasDispatchChanges = false
|
||||||
|
// 重新加载数据
|
||||||
|
await this.loadDispatchData()
|
||||||
|
} else {
|
||||||
|
this.$message.error(res.data.msg || '保存失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存调度失败:', error)
|
||||||
|
this.$message.error('保存失败,请稍后重试')
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消调度
|
||||||
|
*/
|
||||||
|
handleCancelDispatch() {
|
||||||
|
if (this.hasDispatchChanges) {
|
||||||
|
this.$confirm('有未保存的更改,确定要取消吗?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(() => {
|
||||||
|
// 恢复原始数据
|
||||||
|
this.dispatchGroups = JSON.parse(JSON.stringify(this.originalDispatchData))
|
||||||
|
this.hasDispatchChanges = false
|
||||||
|
this.$message.info('已取消更改')
|
||||||
|
}).catch(() => {
|
||||||
|
// 用户点击了取消
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.activeTab = 'competition'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 添加样式
|
||||||
|
|
||||||
|
在 `<style>` 中添加调度相关样式:
|
||||||
|
|
||||||
|
```scss
|
||||||
|
<style scoped lang="scss">
|
||||||
|
// ... 原有样式
|
||||||
|
|
||||||
|
// 调度容器
|
||||||
|
.dispatch-container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调度分组
|
||||||
|
.dispatch-group {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.group-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid #409eff;
|
||||||
|
|
||||||
|
.group-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.participant-count {
|
||||||
|
margin-left: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调度底部按钮
|
||||||
|
.dispatch-footer {
|
||||||
|
margin-top: 30px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移动图标
|
||||||
|
.move-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 功能说明
|
||||||
|
|
||||||
|
### 1. Tab切换逻辑
|
||||||
|
|
||||||
|
- **编排Tab**:编排完成前可用,完成后禁用
|
||||||
|
- **场地Tab**:编排完成前可用,完成后禁用
|
||||||
|
- **调度Tab**:只有编排完成后才可用
|
||||||
|
|
||||||
|
### 2. 调度操作
|
||||||
|
|
||||||
|
- **上移**:将参赛者向上移动一位(第一个不能上移)
|
||||||
|
- **下移**:将参赛者向下移动一位(最后一个不能下移)
|
||||||
|
- **保存**:批量保存所有调整
|
||||||
|
- **取消**:恢复到原始数据
|
||||||
|
|
||||||
|
### 3. 数据同步
|
||||||
|
|
||||||
|
- 切换场地或时间段时,自动加载对应的调度数据
|
||||||
|
- 保存成功后,重新加载数据确保同步
|
||||||
|
- 取消时,恢复到加载时的原始数据
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 注意事项
|
||||||
|
|
||||||
|
1. **权限控制**
|
||||||
|
- 调度Tab只有在 `isScheduleCompleted === true` 时才可用
|
||||||
|
- 编排完成后,编排Tab和场地Tab应该禁用
|
||||||
|
|
||||||
|
2. **数据一致性**
|
||||||
|
- 每次切换场地或时间段都重新加载数据
|
||||||
|
- 保存前检查是否有未保存的更改
|
||||||
|
|
||||||
|
3. **用户体验**
|
||||||
|
- 有未保存更改时,取消操作需要确认
|
||||||
|
- 第一个不能上移,最后一个不能下移
|
||||||
|
- 保存成功后显示提示并刷新数据
|
||||||
|
|
||||||
|
4. **性能优化**
|
||||||
|
- 使用深拷贝保存原始数据
|
||||||
|
- 只在有更改时才允许保存
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 测试步骤
|
||||||
|
|
||||||
|
1. **完成编排**
|
||||||
|
- 进入编排页面
|
||||||
|
- 完成自动编排
|
||||||
|
- 点击"完成编排"按钮
|
||||||
|
|
||||||
|
2. **进入调度模式**
|
||||||
|
- 点击"调度"Tab
|
||||||
|
- 选择场地和时间段
|
||||||
|
- 查看参赛者列表
|
||||||
|
|
||||||
|
3. **调整顺序**
|
||||||
|
- 点击"上移"或"下移"按钮
|
||||||
|
- 观察顺序变化
|
||||||
|
- 检查第一个和最后一个的按钮是否正确禁用
|
||||||
|
|
||||||
|
4. **保存调度**
|
||||||
|
- 点击"保存调度"按钮
|
||||||
|
- 检查是否保存成功
|
||||||
|
- 刷新页面验证数据是否持久化
|
||||||
|
|
||||||
|
5. **取消操作**
|
||||||
|
- 进行一些调整
|
||||||
|
- 点击"取消"按钮
|
||||||
|
- 确认数据恢复到原始状态
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 总结
|
||||||
|
|
||||||
|
调度功能的实现要点:
|
||||||
|
|
||||||
|
1. ✅ **后端完成**:DTO、Service、Controller 全部实现
|
||||||
|
2. ✅ **前端API**:封装了3个调度相关接口
|
||||||
|
3. ✅ **页面集成**:在编排页面添加调度Tab
|
||||||
|
4. ✅ **权限控制**:只有编排完成后才能使用
|
||||||
|
5. ✅ **用户体验**:提供上移/下移按钮,操作简单直观
|
||||||
|
|
||||||
|
现在可以开始测试调度功能了!🎉
|
||||||
59
docs/sql/mysql/20251212_add_judge_invite_fields.sql
Normal file
59
docs/sql/mysql/20251212_add_judge_invite_fields.sql
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
-- =====================================================
|
||||||
|
-- 武术比赛管理系统 - 补充裁判邀请表字段
|
||||||
|
-- 添加实体类中存在但数据库表缺失的字段
|
||||||
|
-- Date: 2025-12-12
|
||||||
|
-- =====================================================
|
||||||
|
|
||||||
|
USE martial_db;
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- martial_judge_invite (裁判邀请码表) - 添加缺失字段
|
||||||
|
-- =====================================================
|
||||||
|
|
||||||
|
-- 添加 invite_status 字段
|
||||||
|
ALTER TABLE martial_judge_invite
|
||||||
|
ADD COLUMN invite_status int DEFAULT 0 COMMENT '邀请状态(0-待回复,1-已接受,2-已拒绝,3-已取消)' AFTER token_expire_time;
|
||||||
|
|
||||||
|
-- 添加 invite_time 字段
|
||||||
|
ALTER TABLE martial_judge_invite
|
||||||
|
ADD COLUMN invite_time datetime DEFAULT NULL COMMENT '邀请时间' AFTER invite_status;
|
||||||
|
|
||||||
|
-- 添加 reply_time 字段
|
||||||
|
ALTER TABLE martial_judge_invite
|
||||||
|
ADD COLUMN reply_time datetime DEFAULT NULL COMMENT '回复时间' AFTER invite_time;
|
||||||
|
|
||||||
|
-- 添加 reply_note 字段
|
||||||
|
ALTER TABLE martial_judge_invite
|
||||||
|
ADD COLUMN reply_note varchar(500) DEFAULT NULL COMMENT '回复备注' AFTER reply_time;
|
||||||
|
|
||||||
|
-- 添加 contact_phone 字段
|
||||||
|
ALTER TABLE martial_judge_invite
|
||||||
|
ADD COLUMN contact_phone varchar(20) DEFAULT NULL COMMENT '联系电话' AFTER reply_note;
|
||||||
|
|
||||||
|
-- 添加 contact_email 字段
|
||||||
|
ALTER TABLE martial_judge_invite
|
||||||
|
ADD COLUMN contact_email varchar(100) DEFAULT NULL COMMENT '联系邮箱' AFTER contact_phone;
|
||||||
|
|
||||||
|
-- 添加 invite_message 字段
|
||||||
|
ALTER TABLE martial_judge_invite
|
||||||
|
ADD COLUMN invite_message varchar(1000) DEFAULT NULL COMMENT '邀请消息' AFTER contact_email;
|
||||||
|
|
||||||
|
-- 添加 cancel_reason 字段
|
||||||
|
ALTER TABLE martial_judge_invite
|
||||||
|
ADD COLUMN cancel_reason varchar(500) DEFAULT NULL COMMENT '取消原因' AFTER invite_message;
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 验证修改
|
||||||
|
-- =====================================================
|
||||||
|
SELECT '=== 裁判邀请表字段补充完成 ===' AS status;
|
||||||
|
|
||||||
|
-- 查看表结构
|
||||||
|
SHOW COLUMNS FROM martial_judge_invite;
|
||||||
|
|
||||||
|
-- 统计字段数量
|
||||||
|
SELECT
|
||||||
|
'martial_judge_invite 字段数:' AS info,
|
||||||
|
COUNT(*) AS count
|
||||||
|
FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA='martial_db'
|
||||||
|
AND TABLE_NAME='martial_judge_invite';
|
||||||
147
docs/sql/mysql/20251212_add_menu_data.sql
Normal file
147
docs/sql/mysql/20251212_add_menu_data.sql
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
-- =====================================================
|
||||||
|
-- 武术比赛管理系统 - 菜单数据
|
||||||
|
-- 添加武术比赛管理相关菜单
|
||||||
|
-- Date: 2025-12-12
|
||||||
|
-- =====================================================
|
||||||
|
|
||||||
|
-- 注意:请根据实际情况调整菜单ID,避免与现有菜单冲突
|
||||||
|
-- 建议先查询当前最大菜单ID: SELECT MAX(id) FROM blade_menu;
|
||||||
|
|
||||||
|
USE bladex;
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 1. 武术比赛管理 - 一级菜单
|
||||||
|
-- =====================================================
|
||||||
|
INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
|
||||||
|
VALUES (2000000, 0, 'martial', '武术比赛', 'menu', '/martial', 'iconfont icon-quanxian', 1, 1, 0, 1, '武术比赛管理系统', 0);
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 2. 赛事管理 - 二级菜单
|
||||||
|
-- =====================================================
|
||||||
|
INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
|
||||||
|
VALUES (2001000, 2000000, 'martial:competition', '赛事管理', 'menu', '/martial/competition/list', 'iconfont icon-rizhi', 1, 1, 0, 1, '赛事信息管理', 0);
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 3. 报名管理 - 二级菜单
|
||||||
|
-- =====================================================
|
||||||
|
INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
|
||||||
|
VALUES (2002000, 2000000, 'martial:registration', '报名详情', 'menu', '/martial/registration/detail', 'iconfont icon-wenben', 2, 1, 0, 1, '报名信息管理', 0);
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 4. 订单管理 - 二级菜单
|
||||||
|
-- =====================================================
|
||||||
|
INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
|
||||||
|
VALUES (2003000, 2000000, 'martial:order', '订单管理', 'menu', '/martial/order/list', 'iconfont icon-caidan', 3, 1, 0, 1, '订单信息管理', 0);
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 5. 参赛选手管理 - 二级菜单
|
||||||
|
-- =====================================================
|
||||||
|
INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
|
||||||
|
VALUES (2004000, 2000000, 'martial:participant', '参赛选手管理', 'menu', '/martial/participant/list', 'iconfont icon-icon-', 4, 1, 0, 1, '参赛选手信息管理', 0);
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 6. 项目管理 - 二级菜单
|
||||||
|
-- =====================================================
|
||||||
|
INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
|
||||||
|
VALUES (2005000, 2000000, 'martial:project', '项目管理', 'menu', '/martial/project/list', 'iconfont icon-liebiao', 5, 1, 0, 1, '比赛项目管理', 0);
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 7. 评委管理 - 二级菜单
|
||||||
|
-- =====================================================
|
||||||
|
INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
|
||||||
|
VALUES (2006000, 2000000, 'martial:referee', '评委管理', 'menu', '/martial/referee/list', 'iconfont icon-quanxian', 6, 1, 0, 1, '评委信息管理', 0);
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 8. 裁判邀请 - 二级菜单 ⭐ 重点
|
||||||
|
-- =====================================================
|
||||||
|
INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
|
||||||
|
VALUES (2007000, 2000000, 'martial:judgeInvite', '裁判邀请', 'menu', '/martial/judgeInvite/list', 'iconfont icon-email', 7, 1, 0, 1, '裁判邀请码管理', 0);
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 9. 裁判分配 - 二级菜单
|
||||||
|
-- =====================================================
|
||||||
|
INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
|
||||||
|
VALUES (2008000, 2000000, 'martial:judgeProject', '裁判分配', 'menu', '/martial/judgeProject/list', 'iconfont icon-quanxian', 8, 1, 0, 1, '裁判项目分配', 0);
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 10. 评分管理 - 二级菜单
|
||||||
|
-- =====================================================
|
||||||
|
INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
|
||||||
|
VALUES (2009000, 2000000, 'martial:score', '评分管理', 'menu', '/martial/score/index', 'iconfont icon-icon-', 9, 1, 0, 1, '评分记录管理', 0);
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 11. 扣分项管理 - 二级菜单
|
||||||
|
-- =====================================================
|
||||||
|
INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
|
||||||
|
VALUES (2010000, 2000000, 'martial:deduction', '扣分项管理', 'menu', '/martial/deduction/list', 'iconfont icon-icon-', 10, 1, 0, 1, '扣分项配置管理', 0);
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 12. 成绩管理 - 二级菜单
|
||||||
|
-- =====================================================
|
||||||
|
INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
|
||||||
|
VALUES (2011000, 2000000, 'martial:result', '成绩管理', 'menu', '/martial/result/list', 'iconfont icon-icon-', 11, 1, 0, 1, '成绩统计管理', 0);
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 13. 赛程计划 - 二级菜单
|
||||||
|
-- =====================================================
|
||||||
|
INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
|
||||||
|
VALUES (2012000, 2000000, 'martial:schedulePlan', '赛程计划', 'menu', '/martial/schedulePlan/list', 'iconfont icon-riqi', 12, 1, 0, 1, '赛程安排管理', 0);
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 14. 选手关联 - 二级菜单
|
||||||
|
-- =====================================================
|
||||||
|
INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
|
||||||
|
VALUES (2013000, 2000000, 'martial:scheduleAthlete', '选手关联', 'menu', '/martial/scheduleAthlete/list', 'iconfont icon-icon-', 13, 1, 0, 1, '赛程选手关联', 0);
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 15. 轮播图管理 - 二级菜单
|
||||||
|
-- =====================================================
|
||||||
|
INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
|
||||||
|
VALUES (2014000, 2000000, 'martial:banner', '轮播图管理', 'menu', '/martial/banner/index', 'iconfont icon-tupian', 14, 1, 0, 1, '轮播图配置', 0);
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 16. 直播管理 - 二级菜单
|
||||||
|
-- =====================================================
|
||||||
|
INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
|
||||||
|
VALUES (2015000, 2000000, 'martial:live', '直播管理', 'menu', '/martial/live/list', 'iconfont icon-icon-', 15, 1, 0, 1, '直播信息管理', 0);
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 17. 信息发布 - 二级菜单
|
||||||
|
-- =====================================================
|
||||||
|
INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
|
||||||
|
VALUES (2016000, 2000000, 'martial:info', '信息发布', 'menu', '/martial/info/list', 'iconfont icon-wenben', 16, 1, 0, 1, '信息发布管理', 0);
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 18. 异常事件 - 二级菜单
|
||||||
|
-- =====================================================
|
||||||
|
INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
|
||||||
|
VALUES (2017000, 2000000, 'martial:exception', '异常事件', 'menu', '/martial/exception/list', 'iconfont icon-icon-', 17, 1, 0, 1, '异常事件管理', 0);
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 19. 活动日程 - 二级菜单
|
||||||
|
-- =====================================================
|
||||||
|
INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
|
||||||
|
VALUES (2018000, 2000000, 'martial:activity', '活动日程', 'menu', '/martial/activity/list', 'iconfont icon-riqi', 18, 1, 0, 1, '活动日程管理', 0);
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 20. 赛事规程管理 - 二级菜单
|
||||||
|
-- =====================================================
|
||||||
|
INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
|
||||||
|
VALUES (2019000, 2000000, 'martial:rules', '赛事规程管理', 'menu', '/martial/rules/index', 'iconfont icon-wenben', 19, 1, 0, 1, '赛事规程文件管理', 0);
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 21. 导出中心 - 二级菜单
|
||||||
|
-- =====================================================
|
||||||
|
INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
|
||||||
|
VALUES (2020000, 2000000, 'martial:export', '导出中心', 'menu', '/martial/export/index', 'iconfont icon-icon-', 20, 1, 0, 1, '数据导出中心', 0);
|
||||||
|
|
||||||
|
-- =====================================================
|
||||||
|
-- 验证插入
|
||||||
|
-- =====================================================
|
||||||
|
SELECT '=== 菜单数据插入完成 ===' AS status;
|
||||||
|
|
||||||
|
-- 查看插入的菜单
|
||||||
|
SELECT id, parent_id, name, path, sort
|
||||||
|
FROM blade_menu
|
||||||
|
WHERE id >= 2000000 AND id <= 2020000
|
||||||
|
ORDER BY id;
|
||||||
91
init-judge-project.sql
Normal file
91
init-judge-project.sql
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
-- ============================================
|
||||||
|
-- 初始化裁判-项目关联数据
|
||||||
|
-- 用于解决"您没有权限给该项目打分"的问题
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- 说明:
|
||||||
|
-- 1. 这个脚本会为所有裁判分配所有项目的评分权限
|
||||||
|
-- 2. 如果需要更精细的权限控制,请根据实际情况修改
|
||||||
|
-- 3. 执行前请确保 martial_judge 和 martial_project 表中已有数据
|
||||||
|
|
||||||
|
-- 清空现有的裁判-项目关联(可选)
|
||||||
|
-- TRUNCATE TABLE martial_judge_project;
|
||||||
|
|
||||||
|
-- 方案1:为所有裁判分配所有项目(适用于测试环境)
|
||||||
|
INSERT INTO martial_judge_project (
|
||||||
|
competition_id,
|
||||||
|
judge_id,
|
||||||
|
project_id,
|
||||||
|
assign_time,
|
||||||
|
status,
|
||||||
|
is_deleted,
|
||||||
|
create_time,
|
||||||
|
update_time
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
j.competition_id,
|
||||||
|
j.id AS judge_id,
|
||||||
|
p.id AS project_id,
|
||||||
|
NOW() AS assign_time,
|
||||||
|
1 AS status,
|
||||||
|
0 AS is_deleted,
|
||||||
|
NOW() AS create_time,
|
||||||
|
NOW() AS update_time
|
||||||
|
FROM martial_judge j
|
||||||
|
CROSS JOIN martial_project p
|
||||||
|
WHERE j.is_deleted = 0
|
||||||
|
AND p.is_deleted = 0
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM martial_judge_project jp
|
||||||
|
WHERE jp.judge_id = j.id
|
||||||
|
AND jp.project_id = p.id
|
||||||
|
AND jp.is_deleted = 0
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 方案2:为特定裁判分配特定项目(适用于生产环境)
|
||||||
|
-- 示例:为裁判ID=456分配项目ID=5的权限
|
||||||
|
/*
|
||||||
|
INSERT INTO martial_judge_project (
|
||||||
|
competition_id,
|
||||||
|
judge_id,
|
||||||
|
project_id,
|
||||||
|
assign_time,
|
||||||
|
status,
|
||||||
|
is_deleted,
|
||||||
|
create_time,
|
||||||
|
update_time
|
||||||
|
) VALUES (
|
||||||
|
200, -- 比赛ID
|
||||||
|
456, -- 裁判ID
|
||||||
|
5, -- 项目ID
|
||||||
|
NOW(),
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
);
|
||||||
|
*/
|
||||||
|
|
||||||
|
-- 验证数据
|
||||||
|
SELECT
|
||||||
|
jp.id,
|
||||||
|
j.name AS judge_name,
|
||||||
|
p.project_name,
|
||||||
|
jp.status,
|
||||||
|
jp.assign_time
|
||||||
|
FROM martial_judge_project jp
|
||||||
|
LEFT JOIN martial_judge j ON jp.judge_id = j.id
|
||||||
|
LEFT JOIN martial_project p ON jp.project_id = p.id
|
||||||
|
WHERE jp.is_deleted = 0
|
||||||
|
ORDER BY jp.judge_id, jp.project_id;
|
||||||
|
|
||||||
|
-- 查看每个裁判分配的项目数量
|
||||||
|
SELECT
|
||||||
|
j.id AS judge_id,
|
||||||
|
j.name AS judge_name,
|
||||||
|
COUNT(jp.id) AS project_count
|
||||||
|
FROM martial_judge j
|
||||||
|
LEFT JOIN martial_judge_project jp ON j.id = jp.judge_id AND jp.is_deleted = 0
|
||||||
|
WHERE j.is_deleted = 0
|
||||||
|
GROUP BY j.id, j.name
|
||||||
|
ORDER BY j.id;
|
||||||
BIN
minio_data/.minio.sys/buckets/.bloomcycle.bin/xl.meta
Normal file
BIN
minio_data/.minio.sys/buckets/.bloomcycle.bin/xl.meta
Normal file
Binary file not shown.
BIN
minio_data/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta
Normal file
BIN
minio_data/.minio.sys/buckets/.usage-cache.bin.bkp/xl.meta
Normal file
Binary file not shown.
BIN
minio_data/.minio.sys/buckets/.usage-cache.bin/xl.meta
Normal file
BIN
minio_data/.minio.sys/buckets/.usage-cache.bin/xl.meta
Normal file
Binary file not shown.
BIN
minio_data/.minio.sys/buckets/.usage.json/xl.meta
Normal file
BIN
minio_data/.minio.sys/buckets/.usage.json/xl.meta
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
minio_data/.minio.sys/buckets/assets/.metadata.bin/xl.meta
Normal file
BIN
minio_data/.minio.sys/buckets/assets/.metadata.bin/xl.meta
Normal file
Binary file not shown.
Binary file not shown.
BIN
minio_data/.minio.sys/buckets/assets/.usage-cache.bin/xl.meta
Normal file
BIN
minio_data/.minio.sys/buckets/assets/.usage-cache.bin/xl.meta
Normal file
Binary file not shown.
BIN
minio_data/.minio.sys/config/config.json/xl.meta
Normal file
BIN
minio_data/.minio.sys/config/config.json/xl.meta
Normal file
Binary file not shown.
BIN
minio_data/.minio.sys/config/iam/format.json/xl.meta
Normal file
BIN
minio_data/.minio.sys/config/iam/format.json/xl.meta
Normal file
Binary file not shown.
1
minio_data/.minio.sys/format.json
Normal file
1
minio_data/.minio.sys/format.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":"1","format":"xl-single","id":"7aa712c5-97fa-4608-aafd-5e91b82dcaaa","xl":{"version":"3","this":"a0620b80-1f59-4689-8995-4d5bcde4044d","sets":[["a0620b80-1f59-4689-8995-4d5bcde4044d"]],"distributionAlgo":"SIPMOD+PARITY"}}
|
||||||
BIN
minio_data/.minio.sys/pool.bin/xl.meta
Normal file
BIN
minio_data/.minio.sys/pool.bin/xl.meta
Normal file
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.
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.
10
pom.xml
10
pom.xml
@@ -228,6 +228,16 @@
|
|||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Flyway 数据库迁移 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flywaydb</groupId>
|
||||||
|
<artifactId>flyway-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flywaydb</groupId>
|
||||||
|
<artifactId>flyway-mysql</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public class BladeConfiguration implements WebMvcConfigurer {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void addCorsMappings(CorsRegistry registry) {
|
public void addCorsMappings(CorsRegistry registry) {
|
||||||
registry.addMapping("/cors/**")
|
registry.addMapping("/**")
|
||||||
.allowedOriginPatterns("*")
|
.allowedOriginPatterns("*")
|
||||||
.allowedHeaders("*")
|
.allowedHeaders("*")
|
||||||
.allowedMethods("*")
|
.allowedMethods("*")
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package org.springblade.modules.auth.controller;
|
||||||
|
|
||||||
|
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springblade.common.cache.CacheNames;
|
||||||
|
import org.springblade.core.launch.constant.AppConstant;
|
||||||
|
import org.springblade.core.redis.cache.BladeRedis;
|
||||||
|
import org.springblade.core.tool.api.R;
|
||||||
|
import org.springblade.core.tool.utils.StringUtil;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码控制器
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@AllArgsConstructor
|
||||||
|
@RequestMapping(AppConstant.APPLICATION_AUTH_NAME + "/captcha")
|
||||||
|
@Tag(name = "验证码", description = "验证码")
|
||||||
|
public class CaptchaController {
|
||||||
|
|
||||||
|
private final BladeRedis bladeRedis;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取图形验证码
|
||||||
|
*/
|
||||||
|
@GetMapping("/oauth/captcha")
|
||||||
|
@ApiOperationSupport(order = 1)
|
||||||
|
@Operation(summary = "获取图形验证码", description = "返回验证码图片和key")
|
||||||
|
public R<java.util.Map<String, String>> getCaptcha() {
|
||||||
|
// 生成唯一key
|
||||||
|
String key = java.util.UUID.randomUUID().toString().replace("-", "");
|
||||||
|
|
||||||
|
// 生成4位随机验证码
|
||||||
|
String code = generateCode(4);
|
||||||
|
|
||||||
|
// 存储验证码到Redis,有效期5分钟
|
||||||
|
String cacheKey = CacheNames.CAPTCHA_KEY + key;
|
||||||
|
bladeRedis.setEx(cacheKey, code.toLowerCase(), Duration.ofMinutes(5));
|
||||||
|
|
||||||
|
// 生成验证码图片(简单的base64图片)
|
||||||
|
String image = generateCaptchaImage(code);
|
||||||
|
|
||||||
|
// 返回结果
|
||||||
|
java.util.Map<String, String> result = new java.util.HashMap<>();
|
||||||
|
result.put("key", key);
|
||||||
|
result.put("image", image);
|
||||||
|
|
||||||
|
return R.data(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送短信验证码
|
||||||
|
*/
|
||||||
|
@PostMapping("/send")
|
||||||
|
@ApiOperationSupport(order = 2)
|
||||||
|
@Operation(summary = "发送短信验证码", description = "传入手机号")
|
||||||
|
public R send(@Parameter(description = "手机号", required = true) @RequestParam String phone) {
|
||||||
|
// 验证手机号格式
|
||||||
|
if (StringUtil.isBlank(phone)) {
|
||||||
|
return R.fail("手机号不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!phone.matches("^1[3-9]\\d{9}$")) {
|
||||||
|
return R.fail("手机号格式不正确");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否频繁发送
|
||||||
|
String cacheKey = CacheNames.CAPTCHA_KEY + phone;
|
||||||
|
String existCode = bladeRedis.get(cacheKey);
|
||||||
|
if (StringUtil.isNotBlank(existCode)) {
|
||||||
|
return R.fail("验证码已发送,请稍后再试");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成6位随机验证码
|
||||||
|
String code = generateCode(6);
|
||||||
|
|
||||||
|
// 存储验证码到Redis,有效期5分钟
|
||||||
|
bladeRedis.setEx(cacheKey, code, Duration.ofMinutes(5));
|
||||||
|
|
||||||
|
// TODO: 实际项目中应该调用短信服务发送验证码
|
||||||
|
// 这里仅做演示,直接返回验证码(生产环境应该删除)
|
||||||
|
System.out.println("发送验证码到手机号: " + phone + ", 验证码: " + code);
|
||||||
|
|
||||||
|
return R.success("验证码发送成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成随机验证码
|
||||||
|
*
|
||||||
|
* @param length 验证码长度
|
||||||
|
* @return 验证码
|
||||||
|
*/
|
||||||
|
private String generateCode(int length) {
|
||||||
|
Random random = new Random();
|
||||||
|
StringBuilder code = new StringBuilder();
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
code.append(random.nextInt(10));
|
||||||
|
}
|
||||||
|
return code.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成验证码图片(Base64格式)
|
||||||
|
* 使用 SVG 格式生成验证码,避免字体依赖问题
|
||||||
|
*
|
||||||
|
* @param code 验证码文本
|
||||||
|
* @return Base64编码的图片
|
||||||
|
*/
|
||||||
|
private String generateCaptchaImage(String code) {
|
||||||
|
Random random = new Random();
|
||||||
|
StringBuilder svg = new StringBuilder();
|
||||||
|
|
||||||
|
svg.append("<svg xmlns='http://www.w3.org/2000/svg' width='120' height='40'>");
|
||||||
|
|
||||||
|
// 背景
|
||||||
|
svg.append("<rect width='120' height='40' fill='#f8f9fa'/>");
|
||||||
|
|
||||||
|
// 绘制验证码字符
|
||||||
|
for (int i = 0; i < code.length(); i++) {
|
||||||
|
char c = code.charAt(i);
|
||||||
|
int x = 15 + i * 25;
|
||||||
|
int y = 25 + random.nextInt(5) - 2;
|
||||||
|
int rotate = random.nextInt(30) - 15;
|
||||||
|
|
||||||
|
// 随机颜色
|
||||||
|
String color = String.format("#%02x%02x%02x",
|
||||||
|
random.nextInt(100),
|
||||||
|
random.nextInt(100),
|
||||||
|
random.nextInt(100));
|
||||||
|
|
||||||
|
svg.append(String.format(
|
||||||
|
"<text x='%d' y='%d' font-size='28' font-weight='bold' fill='%s' transform='rotate(%d %d %d)'>%c</text>",
|
||||||
|
x, y, color, rotate, x, y, c
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加干扰线
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
int x1 = random.nextInt(120);
|
||||||
|
int y1 = random.nextInt(40);
|
||||||
|
int x2 = random.nextInt(120);
|
||||||
|
int y2 = random.nextInt(40);
|
||||||
|
String color = String.format("#%02x%02x%02x",
|
||||||
|
random.nextInt(200) + 50,
|
||||||
|
random.nextInt(200) + 50,
|
||||||
|
random.nextInt(200) + 50);
|
||||||
|
|
||||||
|
svg.append(String.format(
|
||||||
|
"<line x1='%d' y1='%d' x2='%d' y2='%d' stroke='%s' stroke-width='1'/>",
|
||||||
|
x1, y1, x2, y2, color
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
svg.append("</svg>");
|
||||||
|
|
||||||
|
// 转换为 Base64
|
||||||
|
return "data:image/svg+xml;base64," + java.util.Base64.getEncoder().encodeToString(svg.toString().getBytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package org.springblade.modules.martial.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 赛程编排配置
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "martial.schedule")
|
||||||
|
public class ScheduleConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 个人项目每组最大人数(超过此数量将自动拆分)
|
||||||
|
*/
|
||||||
|
private int maxPeoplePerGroup = 35;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拆分后每组目标人数
|
||||||
|
*/
|
||||||
|
private int targetPeoplePerGroup = 33;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认每人比赛时长(分钟)
|
||||||
|
*/
|
||||||
|
private int defaultDurationPerPerson = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 容量利用率警告阈值(百分比)
|
||||||
|
*/
|
||||||
|
private int capacityWarningThreshold = 90;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否允许容量超载(超载时仍继续编排)
|
||||||
|
*/
|
||||||
|
private boolean allowOverload = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上午时段开始时间
|
||||||
|
*/
|
||||||
|
private String morningStartTime = "08:00";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上午时段结束时间
|
||||||
|
*/
|
||||||
|
private String morningEndTime = "12:00";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下午时段开始时间
|
||||||
|
*/
|
||||||
|
private String afternoonStartTime = "14:00";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下午时段结束时间
|
||||||
|
*/
|
||||||
|
private String afternoonEndTime = "18:00";
|
||||||
|
}
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
package org.springblade.modules.martial.controller;
|
package org.springblade.modules.martial.controller;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.qiniu.util.Auth;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springblade.core.boot.ctrl.BladeController;
|
import org.springblade.core.boot.ctrl.BladeController;
|
||||||
import org.springblade.core.mp.support.Condition;
|
import org.springblade.core.mp.support.Condition;
|
||||||
import org.springblade.core.mp.support.Query;
|
import org.springblade.core.mp.support.Query;
|
||||||
|
import org.springblade.core.secure.utils.AuthUtil;
|
||||||
import org.springblade.core.tool.api.R;
|
import org.springblade.core.tool.api.R;
|
||||||
import org.springblade.core.tool.utils.Func;
|
import org.springblade.core.tool.utils.Func;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialAthlete;
|
import org.springblade.modules.martial.pojo.entity.MartialAthlete;
|
||||||
@@ -20,6 +23,7 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
* @author BladeX
|
* @author BladeX
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
|
@Slf4j
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@RequestMapping("/martial/athlete")
|
@RequestMapping("/martial/athlete")
|
||||||
@Tag(name = "参赛选手管理", description = "参赛选手接口")
|
@Tag(name = "参赛选手管理", description = "参赛选手接口")
|
||||||
@@ -53,6 +57,13 @@ public class MartialAthleteController extends BladeController {
|
|||||||
@PostMapping("/submit")
|
@PostMapping("/submit")
|
||||||
@Operation(summary = "新增或修改", description = "传入实体")
|
@Operation(summary = "新增或修改", description = "传入实体")
|
||||||
public R submit(@RequestBody MartialAthlete athlete) {
|
public R submit(@RequestBody MartialAthlete athlete) {
|
||||||
|
Long userId = AuthUtil.getUserId();
|
||||||
|
log.info("=== 提交选手 === userId: {}, playerName: {}", userId, athlete.getPlayerName());
|
||||||
|
// Only set createUser for new records (when id is null)
|
||||||
|
if (athlete.getId() == null) {
|
||||||
|
athlete.setCreateUser(userId);
|
||||||
|
}
|
||||||
|
athlete.setUpdateUser(userId);
|
||||||
return R.status(athleteService.saveOrUpdate(athlete));
|
return R.status(athleteService.saveOrUpdate(athlete));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
package org.springblade.modules.martial.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springblade.core.boot.ctrl.BladeController;
|
||||||
|
import org.springblade.core.mp.support.Condition;
|
||||||
|
import org.springblade.core.mp.support.Query;
|
||||||
|
import org.springblade.core.tool.api.R;
|
||||||
|
import org.springblade.core.tool.utils.Func;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MartialCompetitionAttachment;
|
||||||
|
import org.springblade.modules.martial.service.IMartialCompetitionAttachmentService;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 赛事附件 控制器
|
||||||
|
*
|
||||||
|
* @author BladeX
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@AllArgsConstructor
|
||||||
|
@RequestMapping("/martial/competition/attachment")
|
||||||
|
@Tag(name = "赛事附件管理", description = "赛事附件管理接口")
|
||||||
|
public class MartialCompetitionAttachmentController extends BladeController {
|
||||||
|
|
||||||
|
private final IMartialCompetitionAttachmentService attachmentService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 详情
|
||||||
|
*/
|
||||||
|
@GetMapping("/detail")
|
||||||
|
@Operation(summary = "详情", description = "传入ID")
|
||||||
|
public R<MartialCompetitionAttachment> detail(@RequestParam Long id) {
|
||||||
|
MartialCompetitionAttachment detail = attachmentService.getById(id);
|
||||||
|
return R.data(detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/list")
|
||||||
|
@Operation(summary = "分页列表", description = "分页查询")
|
||||||
|
public R<IPage<MartialCompetitionAttachment>> list(MartialCompetitionAttachment attachment, Query query) {
|
||||||
|
IPage<MartialCompetitionAttachment> pages = attachmentService.page(Condition.getPage(query), Condition.getQueryWrapper(attachment));
|
||||||
|
return R.data(pages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据赛事ID和类型获取附件列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/getByType")
|
||||||
|
@Operation(summary = "根据赛事ID和类型获取附件列表", description = "传入赛事ID和附件类型")
|
||||||
|
public R<List<MartialCompetitionAttachment>> getByType(
|
||||||
|
@RequestParam Long competitionId,
|
||||||
|
@RequestParam String attachmentType) {
|
||||||
|
List<MartialCompetitionAttachment> list = attachmentService.getByCompetitionIdAndType(competitionId, attachmentType);
|
||||||
|
return R.data(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据赛事ID获取所有附件
|
||||||
|
*/
|
||||||
|
@GetMapping("/getByCompetition")
|
||||||
|
@Operation(summary = "根据赛事ID获取所有附件", description = "传入赛事ID")
|
||||||
|
public R<List<MartialCompetitionAttachment>> getByCompetition(@RequestParam Long competitionId) {
|
||||||
|
List<MartialCompetitionAttachment> list = attachmentService.getByCompetitionId(competitionId);
|
||||||
|
return R.data(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增或修改
|
||||||
|
*/
|
||||||
|
@PostMapping("/submit")
|
||||||
|
@Operation(summary = "新增或修改", description = "传入实体")
|
||||||
|
public R submit(@RequestBody MartialCompetitionAttachment attachment) {
|
||||||
|
// 设置默认状态为启用
|
||||||
|
if (attachment.getStatus() == null) {
|
||||||
|
attachment.setStatus(1);
|
||||||
|
}
|
||||||
|
// 设置默认排序
|
||||||
|
if (attachment.getOrderNum() == null) {
|
||||||
|
attachment.setOrderNum(0);
|
||||||
|
}
|
||||||
|
return R.status(attachmentService.saveOrUpdate(attachment));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存附件
|
||||||
|
*/
|
||||||
|
@PostMapping("/batchSubmit")
|
||||||
|
@Operation(summary = "批量保存附件", description = "传入附件列表")
|
||||||
|
public R batchSubmit(@RequestBody List<MartialCompetitionAttachment> attachments) {
|
||||||
|
for (MartialCompetitionAttachment attachment : attachments) {
|
||||||
|
if (attachment.getStatus() == null) {
|
||||||
|
attachment.setStatus(1);
|
||||||
|
}
|
||||||
|
if (attachment.getOrderNum() == null) {
|
||||||
|
attachment.setOrderNum(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return R.status(attachmentService.saveOrUpdateBatch(attachments));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除
|
||||||
|
*/
|
||||||
|
@PostMapping("/remove")
|
||||||
|
@Operation(summary = "删除", description = "传入ID")
|
||||||
|
public R remove(@RequestParam String ids) {
|
||||||
|
return R.status(attachmentService.removeByIds(Func.toLongList(ids)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除赛事的指定类型附件
|
||||||
|
*/
|
||||||
|
@PostMapping("/removeByType")
|
||||||
|
@Operation(summary = "删除赛事的指定类型附件", description = "传入赛事ID和附件类型")
|
||||||
|
public R removeByType(
|
||||||
|
@RequestParam Long competitionId,
|
||||||
|
@RequestParam String attachmentType) {
|
||||||
|
return R.status(attachmentService.removeByCompetitionIdAndType(competitionId, attachmentType));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
package org.springblade.modules.martial.controller;
|
package org.springblade.modules.martial.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
@@ -9,10 +12,16 @@ import org.springblade.core.mp.support.Condition;
|
|||||||
import org.springblade.core.mp.support.Query;
|
import org.springblade.core.mp.support.Query;
|
||||||
import org.springblade.core.tool.api.R;
|
import org.springblade.core.tool.api.R;
|
||||||
import org.springblade.core.tool.utils.Func;
|
import org.springblade.core.tool.utils.Func;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MartialAthlete;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialCompetition;
|
import org.springblade.modules.martial.pojo.entity.MartialCompetition;
|
||||||
|
import org.springblade.modules.martial.service.IMartialAthleteService;
|
||||||
|
import org.springblade.modules.martial.mapper.MartialAthleteMapper;
|
||||||
import org.springblade.modules.martial.service.IMartialCompetitionService;
|
import org.springblade.modules.martial.service.IMartialCompetitionService;
|
||||||
|
import org.springblade.modules.system.pojo.entity.User;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 赛事信息 控制器
|
* 赛事信息 控制器
|
||||||
*
|
*
|
||||||
@@ -25,6 +34,7 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
public class MartialCompetitionController extends BladeController {
|
public class MartialCompetitionController extends BladeController {
|
||||||
|
|
||||||
private final IMartialCompetitionService competitionService;
|
private final IMartialCompetitionService competitionService;
|
||||||
|
private final IMartialAthleteService martialAthleteService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 详情
|
* 详情
|
||||||
@@ -33,6 +43,11 @@ public class MartialCompetitionController extends BladeController {
|
|||||||
@Operation(summary = "详情", description = "传入ID")
|
@Operation(summary = "详情", description = "传入ID")
|
||||||
public R<MartialCompetition> detail(@RequestParam Long id) {
|
public R<MartialCompetition> detail(@RequestParam Long id) {
|
||||||
MartialCompetition detail = competitionService.getById(id);
|
MartialCompetition detail = competitionService.getById(id);
|
||||||
|
if (detail != null) {
|
||||||
|
// Count distinct participants by id_card
|
||||||
|
Long cnt = ((MartialAthleteMapper) martialAthleteService.getBaseMapper()).countDistinctParticipants(detail.getId());
|
||||||
|
detail.setTotalParticipants(cnt != null ? cnt.intValue() : 0);
|
||||||
|
}
|
||||||
return R.data(detail);
|
return R.data(detail);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,6 +58,12 @@ public class MartialCompetitionController extends BladeController {
|
|||||||
@Operation(summary = "分页列表", description = "分页查询")
|
@Operation(summary = "分页列表", description = "分页查询")
|
||||||
public R<IPage<MartialCompetition>> list(MartialCompetition competition, Query query) {
|
public R<IPage<MartialCompetition>> list(MartialCompetition competition, Query query) {
|
||||||
IPage<MartialCompetition> pages = competitionService.page(Condition.getPage(query), Condition.getQueryWrapper(competition));
|
IPage<MartialCompetition> pages = competitionService.page(Condition.getPage(query), Condition.getQueryWrapper(competition));
|
||||||
|
List<MartialCompetition> pagelist = pages.getRecords();
|
||||||
|
for (MartialCompetition martialCompetition : pagelist) {
|
||||||
|
// Count distinct participants by id_card
|
||||||
|
Long cnt = ((MartialAthleteMapper) martialAthleteService.getBaseMapper()).countDistinctParticipants(martialCompetition.getId());
|
||||||
|
martialCompetition.setTotalParticipants(cnt != null ? cnt.intValue() : 0);
|
||||||
|
}
|
||||||
return R.data(pages);
|
return R.data(pages);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,8 +72,9 @@ public class MartialCompetitionController extends BladeController {
|
|||||||
*/
|
*/
|
||||||
@PostMapping("/submit")
|
@PostMapping("/submit")
|
||||||
@Operation(summary = "新增或修改", description = "传入实体")
|
@Operation(summary = "新增或修改", description = "传入实体")
|
||||||
public R submit(@RequestBody MartialCompetition competition) {
|
public R<MartialCompetition> submit(@RequestBody MartialCompetition competition) {
|
||||||
return R.status(competitionService.saveOrUpdate(competition));
|
competitionService.saveOrUpdate(competition);
|
||||||
|
return R.data(competition);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import org.springblade.core.boot.ctrl.BladeController;
|
import org.springblade.core.boot.ctrl.BladeController;
|
||||||
import org.springblade.core.tool.api.R;
|
import org.springblade.core.tool.api.R;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialCompetitionRulesAttachment;
|
import org.springblade.modules.martial.pojo.entity.MartialCompetitionAttachment;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialCompetitionRulesChapter;
|
import org.springblade.modules.martial.pojo.entity.MartialCompetitionRulesChapter;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialCompetitionRulesContent;
|
import org.springblade.modules.martial.pojo.entity.MartialCompetitionRulesContent;
|
||||||
import org.springblade.modules.martial.pojo.vo.MartialCompetitionRulesVO;
|
import org.springblade.modules.martial.pojo.vo.MartialCompetitionRulesVO;
|
||||||
@@ -45,8 +45,8 @@ public class MartialCompetitionRulesController extends BladeController {
|
|||||||
*/
|
*/
|
||||||
@GetMapping("/attachment/list")
|
@GetMapping("/attachment/list")
|
||||||
@Operation(summary = "获取附件列表", description = "管理端获取附件列表")
|
@Operation(summary = "获取附件列表", description = "管理端获取附件列表")
|
||||||
public R<List<MartialCompetitionRulesAttachment>> getAttachmentList(@RequestParam Long competitionId) {
|
public R<List<MartialCompetitionAttachment>> getAttachmentList(@RequestParam Long competitionId) {
|
||||||
List<MartialCompetitionRulesAttachment> list = rulesService.getAttachmentList(competitionId);
|
List<MartialCompetitionAttachment> list = rulesService.getAttachmentList(competitionId);
|
||||||
return R.data(list);
|
return R.data(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ public class MartialCompetitionRulesController extends BladeController {
|
|||||||
*/
|
*/
|
||||||
@PostMapping("/attachment/save")
|
@PostMapping("/attachment/save")
|
||||||
@Operation(summary = "保存附件", description = "新增或修改附件")
|
@Operation(summary = "保存附件", description = "新增或修改附件")
|
||||||
public R saveAttachment(@RequestBody MartialCompetitionRulesAttachment attachment) {
|
public R saveAttachment(@RequestBody MartialCompetitionAttachment attachment) {
|
||||||
return R.status(rulesService.saveAttachment(attachment));
|
return R.status(rulesService.saveAttachment(attachment));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package org.springblade.modules.martial.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springblade.core.boot.ctrl.BladeController;
|
||||||
|
import org.springblade.core.mp.support.Query;
|
||||||
|
import org.springblade.core.secure.utils.AuthUtil;
|
||||||
|
import org.springblade.core.tool.api.R;
|
||||||
|
import org.springblade.core.tool.utils.Func;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MartialContact;
|
||||||
|
import org.springblade.modules.martial.service.IMartialContactService;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@AllArgsConstructor
|
||||||
|
@RequestMapping("/martial/contact")
|
||||||
|
@Tag(name = "联系人管理", description = "联系人接口")
|
||||||
|
public class MartialContactController extends BladeController {
|
||||||
|
|
||||||
|
private final IMartialContactService contactService;
|
||||||
|
|
||||||
|
@GetMapping("/list")
|
||||||
|
@Operation(summary = "分页列表", description = "获取当前用户的联系人列表")
|
||||||
|
public R<IPage<MartialContact>> list(Query query) {
|
||||||
|
Long userId = AuthUtil.getUserId();
|
||||||
|
IPage<MartialContact> pages = contactService.getContactList(userId, query.getCurrent(), query.getSize());
|
||||||
|
return R.data(pages);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/detail")
|
||||||
|
@Operation(summary = "详情", description = "获取联系人详情")
|
||||||
|
public R<MartialContact> detail(@RequestParam Long id) {
|
||||||
|
return R.data(contactService.getContactDetail(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/submit")
|
||||||
|
@Operation(summary = "保存", description = "新增或修改联系人")
|
||||||
|
public R<Boolean> submit(@RequestBody MartialContact contact) {
|
||||||
|
Long userId = AuthUtil.getUserId();
|
||||||
|
log.info("Contact submit - id: {}, name: {}, userId: {}, isDefault: {}",
|
||||||
|
contact.getId(), contact.getName(), userId, contact.getIsDefault());
|
||||||
|
|
||||||
|
return R.data(contactService.saveContact(contact, userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/remove")
|
||||||
|
@Operation(summary = "删除", description = "删除联系人")
|
||||||
|
public R<Boolean> remove(@RequestParam String ids) {
|
||||||
|
return R.data(contactService.removeByIds(Func.toLongList(ids)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -10,9 +10,13 @@ import org.springblade.core.mp.support.Query;
|
|||||||
import org.springblade.core.tool.api.R;
|
import org.springblade.core.tool.api.R;
|
||||||
import org.springblade.core.tool.utils.Func;
|
import org.springblade.core.tool.utils.Func;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialDeductionItem;
|
import org.springblade.modules.martial.pojo.entity.MartialDeductionItem;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MartialProject;
|
||||||
import org.springblade.modules.martial.service.IMartialDeductionItemService;
|
import org.springblade.modules.martial.service.IMartialDeductionItemService;
|
||||||
|
import org.springblade.modules.martial.service.IMartialProjectService;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 扣分项配置 控制器
|
* 扣分项配置 控制器
|
||||||
*
|
*
|
||||||
@@ -20,12 +24,14 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@RequestMapping("/martial/deductionItem")
|
@RequestMapping("/blade-martial/deductionItem")
|
||||||
@Tag(name = "扣分项配置管理", description = "扣分项配置接口")
|
@Tag(name = "扣分项配置管理", description = "扣分项配置接口")
|
||||||
public class MartialDeductionItemController extends BladeController {
|
public class MartialDeductionItemController extends BladeController {
|
||||||
|
|
||||||
private final IMartialDeductionItemService deductionItemService;
|
private final IMartialDeductionItemService deductionItemService;
|
||||||
|
|
||||||
|
private final IMartialProjectService martialProjectService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 详情
|
* 详情
|
||||||
*/
|
*/
|
||||||
@@ -43,6 +49,14 @@ public class MartialDeductionItemController extends BladeController {
|
|||||||
@Operation(summary = "分页列表", description = "分页查询")
|
@Operation(summary = "分页列表", description = "分页查询")
|
||||||
public R<IPage<MartialDeductionItem>> list(MartialDeductionItem deductionItem, Query query) {
|
public R<IPage<MartialDeductionItem>> list(MartialDeductionItem deductionItem, Query query) {
|
||||||
IPage<MartialDeductionItem> pages = deductionItemService.page(Condition.getPage(query), Condition.getQueryWrapper(deductionItem));
|
IPage<MartialDeductionItem> pages = deductionItemService.page(Condition.getPage(query), Condition.getQueryWrapper(deductionItem));
|
||||||
|
List<MartialDeductionItem> deductionItems = pages.getRecords();
|
||||||
|
for (MartialDeductionItem item : deductionItems) {
|
||||||
|
MartialProject project = martialProjectService.getById(item.getProjectId());
|
||||||
|
if (project != null) {
|
||||||
|
item.setProjectName(project.getProjectName());
|
||||||
|
item.setCompetitionId(project.getCompetitionId());
|
||||||
|
}
|
||||||
|
}
|
||||||
return R.data(pages);
|
return R.data(pages);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,4 +78,13 @@ public class MartialDeductionItemController extends BladeController {
|
|||||||
return R.status(deductionItemService.removeByIds(Func.toLongList(ids)));
|
return R.status(deductionItemService.removeByIds(Func.toLongList(ids)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新排序
|
||||||
|
*/
|
||||||
|
@PostMapping("/update-order")
|
||||||
|
@Operation(summary = "更新排序", description = "传入排序数据")
|
||||||
|
public R updateOrder(@RequestBody List<MartialDeductionItem> sortData) {
|
||||||
|
return R.status(deductionItemService.updateOrder(sortData));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,26 +10,19 @@ import org.springblade.core.tool.utils.DateUtil;
|
|||||||
import org.springblade.modules.martial.excel.AthleteExportExcel;
|
import org.springblade.modules.martial.excel.AthleteExportExcel;
|
||||||
import org.springblade.modules.martial.excel.ResultExportExcel;
|
import org.springblade.modules.martial.excel.ResultExportExcel;
|
||||||
import org.springblade.modules.martial.excel.ScheduleExportExcel;
|
import org.springblade.modules.martial.excel.ScheduleExportExcel;
|
||||||
|
import org.springblade.modules.martial.excel.ScheduleExportExcel2;
|
||||||
import org.springblade.modules.martial.pojo.vo.CertificateVO;
|
import org.springblade.modules.martial.pojo.vo.CertificateVO;
|
||||||
import org.springblade.modules.martial.service.IMartialAthleteService;
|
import org.springblade.modules.martial.service.IMartialAthleteService;
|
||||||
import org.springblade.modules.martial.service.IMartialResultService;
|
import org.springblade.modules.martial.service.IMartialResultService;
|
||||||
import org.springblade.modules.martial.service.IMartialScheduleService;
|
import org.springblade.modules.martial.service.IMartialScheduleService;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 导出打印 控制器
|
|
||||||
*
|
|
||||||
* @author BladeX
|
|
||||||
*/
|
|
||||||
@RestController
|
@RestController
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@RequestMapping("/martial/export")
|
@RequestMapping("/martial/export")
|
||||||
@@ -40,67 +33,47 @@ public class MartialExportController {
|
|||||||
private final IMartialAthleteService athleteService;
|
private final IMartialAthleteService athleteService;
|
||||||
private final IMartialScheduleService scheduleService;
|
private final IMartialScheduleService scheduleService;
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 3.1: 导出成绩单
|
|
||||||
*/
|
|
||||||
@GetMapping("/results")
|
@GetMapping("/results")
|
||||||
@Operation(summary = "导出成绩单", description = "导出指定赛事或项目的成绩单Excel")
|
@Operation(summary = "导出成绩单", description = "导出指定赛事或项目的成绩单Excel")
|
||||||
public void exportResults(
|
public void exportResults(@RequestParam Long competitionId, @RequestParam(required = false) Long projectId, HttpServletResponse response) {
|
||||||
@RequestParam Long competitionId,
|
|
||||||
@RequestParam(required = false) Long projectId,
|
|
||||||
HttpServletResponse response
|
|
||||||
) {
|
|
||||||
List<ResultExportExcel> list = resultService.exportResults(competitionId, projectId);
|
List<ResultExportExcel> list = resultService.exportResults(competitionId, projectId);
|
||||||
String fileName = "成绩单_" + DateUtil.today();
|
String fileName = "成绩单_" + DateUtil.today();
|
||||||
String sheetName = projectId != null ? "项目成绩单" : "全部成绩";
|
String sheetName = projectId != null ? "项目成绩单" : "全部成绩";
|
||||||
ExcelUtil.export(response, fileName, sheetName, list, ResultExportExcel.class);
|
ExcelUtil.export(response, fileName, sheetName, list, ResultExportExcel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 3.2: 导出运动员名单
|
|
||||||
*/
|
|
||||||
@GetMapping("/athletes")
|
@GetMapping("/athletes")
|
||||||
@Operation(summary = "导出运动员名单", description = "导出指定赛事的运动员名单Excel")
|
@Operation(summary = "导出运动员名单", description = "导出指定赛事的运动员名单Excel")
|
||||||
public void exportAthletes(
|
public void exportAthletes(@RequestParam Long competitionId, HttpServletResponse response) {
|
||||||
@RequestParam Long competitionId,
|
|
||||||
HttpServletResponse response
|
|
||||||
) {
|
|
||||||
List<AthleteExportExcel> list = athleteService.exportAthletes(competitionId);
|
List<AthleteExportExcel> list = athleteService.exportAthletes(competitionId);
|
||||||
String fileName = "运动员名单_" + DateUtil.today();
|
String fileName = "运动员名单_" + DateUtil.today();
|
||||||
ExcelUtil.export(response, fileName, "运动员名单", list, AthleteExportExcel.class);
|
ExcelUtil.export(response, fileName, "运动员名单", list, AthleteExportExcel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 3.3: 导出赛程表
|
|
||||||
*/
|
|
||||||
@GetMapping("/schedule")
|
@GetMapping("/schedule")
|
||||||
@Operation(summary = "导出赛程表", description = "导出指定赛事的赛程安排Excel")
|
@Operation(summary = "导出赛程表", description = "导出指定赛事的赛程安排Excel")
|
||||||
public void exportSchedule(
|
public void exportSchedule(@RequestParam Long competitionId, HttpServletResponse response) {
|
||||||
@RequestParam Long competitionId,
|
|
||||||
HttpServletResponse response
|
|
||||||
) {
|
|
||||||
List<ScheduleExportExcel> list = scheduleService.exportSchedule(competitionId);
|
List<ScheduleExportExcel> list = scheduleService.exportSchedule(competitionId);
|
||||||
String fileName = "赛程表_" + DateUtil.today();
|
String fileName = "赛程表_" + DateUtil.today();
|
||||||
ExcelUtil.export(response, fileName, "赛程安排", list, ScheduleExportExcel.class);
|
ExcelUtil.export(response, fileName, "赛程安排", list, ScheduleExportExcel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@GetMapping("/schedule2")
|
||||||
* Task 3.4: 生成单个证书(HTML格式)
|
@Operation(summary = "导出赛程表-模板2", description = "按场地导出比赛时间表格式的赛程安排")
|
||||||
*/
|
public void exportScheduleTemplate2(@RequestParam Long competitionId, @RequestParam(required = false) Long venueId,
|
||||||
@GetMapping("/certificate/{resultId}")
|
@RequestParam(required = false) String venueName, @RequestParam(required = false) String timeSlot, HttpServletResponse response) {
|
||||||
@Operation(summary = "生成证书", description = "生成获奖证书HTML页面,可打印为PDF")
|
List<ScheduleExportExcel2> list = scheduleService.exportScheduleTemplate2(competitionId, venueId);
|
||||||
public void generateCertificate(
|
String fileName = "比赛时间_" + (venueName != null ? venueName : "全部场地") + "_" + DateUtil.today();
|
||||||
@PathVariable Long resultId,
|
String sheetName = (venueName != null ? venueName : "全部场地") + (timeSlot != null ? "_" + timeSlot : "");
|
||||||
HttpServletResponse response
|
ExcelUtil.export(response, fileName, sheetName, list, ScheduleExportExcel2.class);
|
||||||
) throws IOException {
|
}
|
||||||
// 1. 获取证书数据
|
|
||||||
CertificateVO certificate = resultService.generateCertificateData(resultId);
|
|
||||||
|
|
||||||
// 2. 读取HTML模板
|
@GetMapping("/certificate/{resultId}")
|
||||||
|
@Operation(summary = "生成证书", description = "生成获奖证书HTML页面")
|
||||||
|
public void generateCertificate(@PathVariable Long resultId, HttpServletResponse response) throws IOException {
|
||||||
|
CertificateVO certificate = resultService.generateCertificateData(resultId);
|
||||||
Path templatePath = Path.of("src/main/resources/templates/certificate/certificate.html");
|
Path templatePath = Path.of("src/main/resources/templates/certificate/certificate.html");
|
||||||
String template = Files.readString(templatePath, StandardCharsets.UTF_8);
|
String template = Files.readString(templatePath, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
// 3. 替换模板变量
|
|
||||||
String html = template
|
String html = template
|
||||||
.replace("${playerName}", certificate.getPlayerName())
|
.replace("${playerName}", certificate.getPlayerName())
|
||||||
.replace("${competitionName}", certificate.getCompetitionName())
|
.replace("${competitionName}", certificate.getCompetitionName())
|
||||||
@@ -109,15 +82,10 @@ public class MartialExportController {
|
|||||||
.replace("${medalClass}", certificate.getMedalClass())
|
.replace("${medalClass}", certificate.getMedalClass())
|
||||||
.replace("${organization}", certificate.getOrganization())
|
.replace("${organization}", certificate.getOrganization())
|
||||||
.replace("${issueDate}", certificate.getIssueDate());
|
.replace("${issueDate}", certificate.getIssueDate());
|
||||||
|
|
||||||
// 4. 返回HTML
|
|
||||||
response.setContentType("text/html;charset=UTF-8");
|
response.setContentType("text/html;charset=UTF-8");
|
||||||
response.getWriter().write(html);
|
response.getWriter().write(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 3.4: 批量生成证书数据
|
|
||||||
*/
|
|
||||||
@GetMapping("/certificates/batch")
|
@GetMapping("/certificates/batch")
|
||||||
@Operation(summary = "批量生成证书数据", description = "批量获取项目获奖选手的证书数据")
|
@Operation(summary = "批量生成证书数据", description = "批量获取项目获奖选手的证书数据")
|
||||||
public R<List<CertificateVO>> batchGenerateCertificates(@RequestParam Long projectId) {
|
public R<List<CertificateVO>> batchGenerateCertificates(@RequestParam Long projectId) {
|
||||||
@@ -125,14 +93,10 @@ public class MartialExportController {
|
|||||||
return R.data(certificates);
|
return R.data(certificates);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 3.4: 获取单个证书数据(JSON格式)
|
|
||||||
*/
|
|
||||||
@GetMapping("/certificate/data/{resultId}")
|
@GetMapping("/certificate/data/{resultId}")
|
||||||
@Operation(summary = "获取证书数据", description = "获取证书数据(JSON格式),供前端渲染")
|
@Operation(summary = "获取证书数据", description = "获取证书数据(JSON格式)")
|
||||||
public R<CertificateVO> getCertificateData(@PathVariable Long resultId) {
|
public R<CertificateVO> getCertificateData(@PathVariable Long resultId) {
|
||||||
CertificateVO certificate = resultService.generateCertificateData(resultId);
|
CertificateVO certificate = resultService.generateCertificateData(resultId);
|
||||||
return R.data(certificate);
|
return R.data(certificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,14 @@ import org.springblade.core.mp.support.Condition;
|
|||||||
import org.springblade.core.mp.support.Query;
|
import org.springblade.core.mp.support.Query;
|
||||||
import org.springblade.core.tool.api.R;
|
import org.springblade.core.tool.api.R;
|
||||||
import org.springblade.core.tool.utils.Func;
|
import org.springblade.core.tool.utils.Func;
|
||||||
|
import org.springblade.modules.martial.pojo.dto.BatchGenerateInviteDTO;
|
||||||
|
import org.springblade.modules.martial.pojo.dto.GenerateInviteDTO;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialJudgeInvite;
|
import org.springblade.modules.martial.pojo.entity.MartialJudgeInvite;
|
||||||
import org.springblade.modules.martial.pojo.vo.MartialJudgeInviteVO;
|
import org.springblade.modules.martial.pojo.vo.MartialJudgeInviteVO;
|
||||||
import org.springblade.modules.martial.service.IMartialJudgeInviteService;
|
import org.springblade.modules.martial.service.IMartialJudgeInviteService;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,4 +80,168 @@ public class MartialJudgeInviteController extends BladeController {
|
|||||||
return R.data(statistics);
|
return R.data(statistics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成邀请码
|
||||||
|
*/
|
||||||
|
@PostMapping("/generate")
|
||||||
|
@Operation(summary = "生成邀请码", description = "为评委生成邀请码")
|
||||||
|
public R<MartialJudgeInvite> generateInviteCode(@RequestBody GenerateInviteDTO dto) {
|
||||||
|
MartialJudgeInvite invite = judgeInviteService.generateInviteCode(dto);
|
||||||
|
return R.data(invite);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量生成邀请码
|
||||||
|
*/
|
||||||
|
@PostMapping("/generate/batch")
|
||||||
|
@Operation(summary = "批量生成邀请码", description = "为多个评委批量生成邀请码")
|
||||||
|
public R<List<MartialJudgeInvite>> batchGenerateInviteCode(@RequestBody BatchGenerateInviteDTO dto) {
|
||||||
|
List<MartialJudgeInvite> invites = judgeInviteService.batchGenerateInviteCode(dto);
|
||||||
|
return R.data(invites);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新生成邀请码
|
||||||
|
*/
|
||||||
|
@PutMapping("/regenerate/{inviteId}")
|
||||||
|
@Operation(summary = "重新生成邀请码", description = "重新生成邀请码(旧码失效)")
|
||||||
|
public R<MartialJudgeInvite> regenerateInviteCode(@PathVariable Long inviteId) {
|
||||||
|
MartialJudgeInvite invite = judgeInviteService.regenerateInviteCode(inviteId);
|
||||||
|
return R.data(invite);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询评委的邀请码
|
||||||
|
*/
|
||||||
|
@GetMapping("/byJudge")
|
||||||
|
@Operation(summary = "查询评委邀请码", description = "根据评委ID和赛事ID查询邀请码")
|
||||||
|
public R<MartialJudgeInvite> getInviteByJudge(
|
||||||
|
@RequestParam Long competitionId,
|
||||||
|
@RequestParam Long judgeId
|
||||||
|
) {
|
||||||
|
MartialJudgeInvite invite = judgeInviteService.lambdaQuery()
|
||||||
|
.eq(MartialJudgeInvite::getCompetitionId, competitionId)
|
||||||
|
.eq(MartialJudgeInvite::getJudgeId, judgeId)
|
||||||
|
.eq(MartialJudgeInvite::getIsDeleted, 0)
|
||||||
|
.orderByDesc(MartialJudgeInvite::getCreateTime)
|
||||||
|
.last("LIMIT 1")
|
||||||
|
.one();
|
||||||
|
return R.data(invite);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送邀请
|
||||||
|
*/
|
||||||
|
@PostMapping("/send")
|
||||||
|
@Operation(summary = "发送邀请", description = "向评委发送邀请")
|
||||||
|
public R sendInvite(@RequestBody MartialJudgeInvite judgeInvite) {
|
||||||
|
// TODO: 实现邮件/短信发送逻辑
|
||||||
|
judgeInvite.setInviteStatus(0); // 待回复
|
||||||
|
judgeInvite.setInviteTime(java.time.LocalDateTime.now());
|
||||||
|
return R.status(judgeInviteService.saveOrUpdate(judgeInvite));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重发邀请
|
||||||
|
*/
|
||||||
|
@PostMapping("/resend/{inviteId}")
|
||||||
|
@Operation(summary = "重发邀请", description = "重新发送邀请")
|
||||||
|
public R resendInvite(@PathVariable Long inviteId) {
|
||||||
|
MartialJudgeInvite invite = judgeInviteService.getById(inviteId);
|
||||||
|
if (invite == null) {
|
||||||
|
return R.fail("邀请记录不存在");
|
||||||
|
}
|
||||||
|
// TODO: 实现邮件/短信重发逻辑
|
||||||
|
invite.setInviteTime(java.time.LocalDateTime.now());
|
||||||
|
return R.status(judgeInviteService.updateById(invite));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消邀请
|
||||||
|
*/
|
||||||
|
@PostMapping("/cancel/{inviteId}")
|
||||||
|
@Operation(summary = "取消邀请", description = "取消邀请")
|
||||||
|
public R cancelInvite(@PathVariable Long inviteId, @RequestParam(required = false) String reason) {
|
||||||
|
MartialJudgeInvite invite = judgeInviteService.getById(inviteId);
|
||||||
|
if (invite == null) {
|
||||||
|
return R.fail("邀请记录不存在");
|
||||||
|
}
|
||||||
|
invite.setInviteStatus(3); // 已取消
|
||||||
|
invite.setCancelReason(reason);
|
||||||
|
return R.status(judgeInviteService.updateById(invite));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确认邀请
|
||||||
|
*/
|
||||||
|
@PostMapping("/confirm/{inviteId}")
|
||||||
|
@Operation(summary = "确认邀请", description = "确认接受邀请")
|
||||||
|
public R confirmInvite(@PathVariable Long inviteId) {
|
||||||
|
MartialJudgeInvite invite = judgeInviteService.getById(inviteId);
|
||||||
|
if (invite == null) {
|
||||||
|
return R.fail("邀请记录不存在");
|
||||||
|
}
|
||||||
|
if (invite.getInviteStatus() != 1) {
|
||||||
|
return R.fail("只能确认已接受的邀请");
|
||||||
|
}
|
||||||
|
// TODO: 实现确认逻辑(如分配场地、项目等)
|
||||||
|
return R.success("确认成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送提醒
|
||||||
|
*/
|
||||||
|
@PostMapping("/reminder/{inviteId}")
|
||||||
|
@Operation(summary = "发送提醒", description = "提醒评委回复邀请")
|
||||||
|
public R sendReminder(@PathVariable Long inviteId, @RequestParam(required = false) String message) {
|
||||||
|
MartialJudgeInvite invite = judgeInviteService.getById(inviteId);
|
||||||
|
if (invite == null) {
|
||||||
|
return R.fail("邀请记录不存在");
|
||||||
|
}
|
||||||
|
// TODO: 实现提醒发送逻辑(邮件/短信)
|
||||||
|
return R.success("提醒发送成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从评委库导入
|
||||||
|
*/
|
||||||
|
@PostMapping("/import/pool")
|
||||||
|
@Operation(summary = "从评委库导入", description = "从评委库批量导入评委")
|
||||||
|
public R importFromPool(@RequestParam Long competitionId, @RequestParam String judgeIds) {
|
||||||
|
// TODO: 实现从评委库导入逻辑
|
||||||
|
List<Long> ids = Func.toLongList(judgeIds);
|
||||||
|
// 为每个评委生成邀请码
|
||||||
|
return R.success("导入成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出邀请数据
|
||||||
|
*/
|
||||||
|
@GetMapping("/export")
|
||||||
|
@Operation(summary = "导出邀请数据", description = "导出邀请数据为Excel")
|
||||||
|
public void exportInvites(MartialJudgeInvite judgeInvite) {
|
||||||
|
// TODO: 实现Excel导出逻辑
|
||||||
|
// 使用EasyExcel或POI导出
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新邀请的项目分配
|
||||||
|
*/
|
||||||
|
@PutMapping("/updateProjects")
|
||||||
|
@Operation(summary = "更新项目分配", description = "更新裁判邀请的项目分配")
|
||||||
|
public R updateProjects(@RequestBody java.util.Map<String, Object> params) {
|
||||||
|
Long inviteId = Long.valueOf(params.get("inviteId").toString());
|
||||||
|
String projects = params.get("projects").toString();
|
||||||
|
|
||||||
|
MartialJudgeInvite invite = judgeInviteService.getById(inviteId);
|
||||||
|
if (invite == null) {
|
||||||
|
return R.fail("邀请记录不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
invite.setProjects(projects);
|
||||||
|
boolean success = judgeInviteService.updateById(invite);
|
||||||
|
return success ? R.success("更新成功") : R.fail("更新失败");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package org.springblade.modules.martial.controller;
|
package org.springblade.modules.martial.controller;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
@@ -17,13 +19,30 @@ import org.springblade.modules.martial.pojo.vo.MiniAthleteAdminVO;
|
|||||||
import org.springblade.modules.martial.pojo.vo.MiniAthleteScoreVO;
|
import org.springblade.modules.martial.pojo.vo.MiniAthleteScoreVO;
|
||||||
import org.springblade.modules.martial.pojo.vo.MiniLoginVO;
|
import org.springblade.modules.martial.pojo.vo.MiniLoginVO;
|
||||||
import org.springblade.modules.martial.pojo.vo.MiniScoreDetailVO;
|
import org.springblade.modules.martial.pojo.vo.MiniScoreDetailVO;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.LineupGroupVO;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.LineupParticipantVO;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.ScheduleGroupDetailVO;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
import org.springblade.modules.martial.service.*;
|
import org.springblade.modules.martial.service.*;
|
||||||
|
import org.springblade.modules.martial.pojo.dto.ChiefJudgeConfirmDTO;
|
||||||
|
import org.springblade.modules.martial.pojo.dto.GeneralJudgeConfirmDTO;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MtVenue;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MartialVenue;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MartialResult;
|
||||||
|
import org.springblade.core.redis.cache.BladeRedis;
|
||||||
|
import org.springblade.modules.martial.mapper.MartialScheduleStatusMapper;
|
||||||
|
import org.springblade.modules.martial.mapper.MartialScheduleGroupMapper;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -34,7 +53,7 @@ import java.util.stream.Collectors;
|
|||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@RequestMapping("/api/mini")
|
@RequestMapping("/mini")
|
||||||
@Tag(name = "小程序接口", description = "小程序评分系统专用接口")
|
@Tag(name = "小程序接口", description = "小程序评分系统专用接口")
|
||||||
public class MartialMiniController extends BladeController {
|
public class MartialMiniController extends BladeController {
|
||||||
|
|
||||||
@@ -42,20 +61,26 @@ public class MartialMiniController extends BladeController {
|
|||||||
private final IMartialJudgeService judgeService;
|
private final IMartialJudgeService judgeService;
|
||||||
private final IMartialCompetitionService competitionService;
|
private final IMartialCompetitionService competitionService;
|
||||||
private final IMartialVenueService venueService;
|
private final IMartialVenueService venueService;
|
||||||
|
private final IMtVenueService mtVenueService;
|
||||||
private final IMartialProjectService projectService;
|
private final IMartialProjectService projectService;
|
||||||
private final IMartialAthleteService athleteService;
|
private final IMartialAthleteService athleteService;
|
||||||
private final IMartialScoreService scoreService;
|
private final IMartialScoreService scoreService;
|
||||||
|
private final BladeRedis bladeRedis;
|
||||||
|
private final IMartialResultService resultService;
|
||||||
|
private final MartialScheduleStatusMapper scheduleStatusMapper;
|
||||||
|
private final MartialScheduleGroupMapper scheduleGroupMapper;
|
||||||
|
|
||||||
|
// Redis缓存key前缀
|
||||||
|
private static final String MINI_LOGIN_CACHE_PREFIX = "mini:login:";
|
||||||
|
// 登录缓存过期时间(7天)
|
||||||
|
private static final Duration LOGIN_CACHE_EXPIRE = Duration.ofDays(7);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录验证
|
* 登录验证
|
||||||
*
|
|
||||||
* @param dto 登录信息(比赛编码+邀请码)
|
|
||||||
* @return 登录结果(token、用户信息、分配的场地和项目)
|
|
||||||
*/
|
*/
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
@Operation(summary = "登录验证", description = "使用比赛编码和邀请码登录")
|
@Operation(summary = "登录验证", description = "使用比赛编码和邀请码登录")
|
||||||
public R<MiniLoginVO> login(@RequestBody MiniLoginDTO dto) {
|
public R<MiniLoginVO> login(@RequestBody MiniLoginDTO dto) {
|
||||||
// 1. 根据邀请码查询邀请信息
|
|
||||||
LambdaQueryWrapper<MartialJudgeInvite> inviteQuery = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<MartialJudgeInvite> inviteQuery = new LambdaQueryWrapper<>();
|
||||||
inviteQuery.eq(MartialJudgeInvite::getInviteCode, dto.getInviteCode());
|
inviteQuery.eq(MartialJudgeInvite::getInviteCode, dto.getInviteCode());
|
||||||
inviteQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
inviteQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
||||||
@@ -65,29 +90,24 @@ public class MartialMiniController extends BladeController {
|
|||||||
return R.fail("邀请码不存在");
|
return R.fail("邀请码不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 验证邀请码是否过期
|
|
||||||
if (invite.getExpireTime() != null && invite.getExpireTime().isBefore(LocalDateTime.now())) {
|
if (invite.getExpireTime() != null && invite.getExpireTime().isBefore(LocalDateTime.now())) {
|
||||||
return R.fail("邀请码已过期");
|
return R.fail("邀请码已过期");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 查询比赛信息
|
|
||||||
MartialCompetition competition = competitionService.getById(invite.getCompetitionId());
|
MartialCompetition competition = competitionService.getById(invite.getCompetitionId());
|
||||||
if (competition == null) {
|
if (competition == null) {
|
||||||
return R.fail("比赛不存在");
|
return R.fail("比赛不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 验证比赛编码
|
|
||||||
if (!competition.getCompetitionCode().equals(dto.getMatchCode())) {
|
if (!competition.getCompetitionCode().equals(dto.getMatchCode())) {
|
||||||
return R.fail("比赛编码不匹配");
|
return R.fail("比赛编码不匹配");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 查询评委信息
|
|
||||||
MartialJudge judge = judgeService.getById(invite.getJudgeId());
|
MartialJudge judge = judgeService.getById(invite.getJudgeId());
|
||||||
if (judge == null) {
|
if (judge == null) {
|
||||||
return R.fail("评委信息不存在");
|
return R.fail("评委信息不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. 生成访问令牌
|
|
||||||
String token = UUID.randomUUID().toString().replace("-", "");
|
String token = UUID.randomUUID().toString().replace("-", "");
|
||||||
invite.setAccessToken(token);
|
invite.setAccessToken(token);
|
||||||
invite.setTokenExpireTime(LocalDateTime.now().plusDays(7));
|
invite.setTokenExpireTime(LocalDateTime.now().plusDays(7));
|
||||||
@@ -97,77 +117,354 @@ public class MartialMiniController extends BladeController {
|
|||||||
invite.setDeviceInfo(dto.getDeviceInfo());
|
invite.setDeviceInfo(dto.getDeviceInfo());
|
||||||
judgeInviteService.updateById(invite);
|
judgeInviteService.updateById(invite);
|
||||||
|
|
||||||
// 7. 查询场地信息(裁判长没有固定场地)
|
// 从 martial_venue 表获取场地信息
|
||||||
MartialVenue venue = null;
|
MartialVenue martialVenue = null;
|
||||||
if (invite.getVenueId() != null) {
|
if (invite.getVenueId() != null) {
|
||||||
venue = venueService.getById(invite.getVenueId());
|
martialVenue = venueService.getById(invite.getVenueId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 8. 解析分配的项目
|
// 获取项目列表:总裁判看所有项目,其他裁判根据场地获取项目
|
||||||
List<MiniLoginVO.ProjectInfo> projects = parseProjects(invite.getProjects());
|
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||||
|
Integer refereeTypeVal = invite.getRefereeType();
|
||||||
|
String roleVal = invite.getRole();
|
||||||
|
boolean isGeneralJudge = (refereeTypeVal != null && refereeTypeVal == 3)
|
||||||
|
|| "general_judge".equals(roleVal) || "general".equals(roleVal);
|
||||||
|
|
||||||
|
if (isGeneralJudge) {
|
||||||
|
// 总裁判看所有项目
|
||||||
|
projects = getAllProjectsByCompetition(competition.getId());
|
||||||
|
} else if (Func.isNotEmpty(invite.getProjects())) {
|
||||||
|
projects = parseProjects(invite.getProjects());
|
||||||
|
} else if (invite.getVenueId() != null) {
|
||||||
|
// 未指定项目,根据场地获取项目;如果场地没有项目则返回空列表
|
||||||
|
projects = getProjectsByVenue(invite.getVenueId());
|
||||||
|
}
|
||||||
|
// 如果没有场地,projects保持为空列表
|
||||||
|
|
||||||
// 9. 构造返回结果
|
|
||||||
MiniLoginVO vo = new MiniLoginVO();
|
MiniLoginVO vo = new MiniLoginVO();
|
||||||
vo.setToken(token);
|
vo.setToken(token);
|
||||||
vo.setUserRole("chief_judge".equals(invite.getRole()) ? "admin" : "pub");
|
String role = invite.getRole();
|
||||||
|
Integer refereeType = invite.getRefereeType();
|
||||||
|
if ("general_judge".equals(role) || "general".equals(role) || (refereeType != null && refereeType == 3)) {
|
||||||
|
vo.setUserRole("general");
|
||||||
|
} else if ("chief_judge".equals(role) || (refereeType != null && refereeType == 1)) {
|
||||||
|
vo.setUserRole("admin");
|
||||||
|
} else {
|
||||||
|
vo.setUserRole("pub");
|
||||||
|
}
|
||||||
vo.setMatchId(competition.getId());
|
vo.setMatchId(competition.getId());
|
||||||
vo.setMatchName(competition.getCompetitionName());
|
vo.setMatchName(competition.getCompetitionName());
|
||||||
vo.setMatchTime(competition.getCompetitionStartTime() != null ?
|
vo.setMatchTime(competition.getCompetitionStartTime() != null ?
|
||||||
competition.getCompetitionStartTime().toString() : "");
|
competition.getCompetitionStartTime().toString() : "");
|
||||||
vo.setJudgeId(judge.getId());
|
vo.setJudgeId(judge.getId());
|
||||||
vo.setJudgeName(judge.getName());
|
vo.setJudgeName(judge.getName());
|
||||||
vo.setVenueId(venue != null ? venue.getId() : null);
|
vo.setVenueId(martialVenue != null ? martialVenue.getId() : null);
|
||||||
vo.setVenueName(venue != null ? venue.getVenueName() : null);
|
vo.setVenueName(martialVenue != null ? martialVenue.getVenueName() : null);
|
||||||
vo.setProjects(projects);
|
vo.setProjects(projects);
|
||||||
|
|
||||||
|
// 将登录信息缓存到Redis(服务重启后仍然有效)
|
||||||
|
String cacheKey = MINI_LOGIN_CACHE_PREFIX + token;
|
||||||
|
bladeRedis.setEx(cacheKey, vo, LOGIN_CACHE_EXPIRE);
|
||||||
|
|
||||||
return R.data(vo);
|
return R.data(vo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取我的选手列表(普通评委)
|
* 提交评分(评委)
|
||||||
*
|
* 注意:ID字段使用String类型接收,避免JavaScript大数精度丢失问题
|
||||||
* @param judgeId 评委ID
|
|
||||||
* @param venueId 场地ID
|
|
||||||
* @param projectId 项目ID
|
|
||||||
* @return 选手列表(含评分状态)
|
|
||||||
*/
|
*/
|
||||||
@GetMapping("/athletes")
|
@PostMapping("/score/submit")
|
||||||
@Operation(summary = "选手列表(普通评委)", description = "获取分配的选手列表")
|
@Operation(summary = "提交评分", description = "评委提交对选手的评分")
|
||||||
public R<List<MiniAthleteScoreVO>> getMyAthletes(
|
public R submitScore(@RequestBody org.springblade.modules.martial.pojo.dto.MiniScoreSubmitDTO dto) {
|
||||||
@RequestParam Long judgeId,
|
MartialScore score = new MartialScore();
|
||||||
@RequestParam Long venueId,
|
|
||||||
@RequestParam Long projectId
|
// 将String类型的ID转换为Long,避免JavaScript大数精度丢失
|
||||||
) {
|
score.setAthleteId(parseLong(dto.getAthleteId()));
|
||||||
List<MiniAthleteScoreVO> result = athleteService.getAthletesWithMyScore(
|
score.setJudgeId(parseLong(dto.getJudgeId()));
|
||||||
judgeId, venueId, projectId);
|
score.setScore(dto.getScore());
|
||||||
return R.data(result);
|
score.setProjectId(parseLong(dto.getProjectId()));
|
||||||
|
score.setCompetitionId(parseLong(dto.getCompetitionId()));
|
||||||
|
score.setVenueId(parseLong(dto.getVenueId()));
|
||||||
|
score.setScheduleId(parseLong(dto.getScheduleId()));
|
||||||
|
score.setNote(dto.getNote());
|
||||||
|
score.setScoreTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
if (dto.getDeductions() != null && !dto.getDeductions().isEmpty()) {
|
||||||
|
// 将String类型的扣分项ID转换为Long
|
||||||
|
List<Long> deductionIds = dto.getDeductions().stream()
|
||||||
|
.map(this::parseLong)
|
||||||
|
.filter(id -> id != null)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
score.setDeductionItems(com.alibaba.fastjson.JSON.toJSONString(deductionIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
Long judgeId = parseLong(dto.getJudgeId());
|
||||||
|
if (judgeId != null) {
|
||||||
|
var judge = judgeService.getById(judgeId);
|
||||||
|
if (judge != null) {
|
||||||
|
score.setJudgeName(judge.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean success = scoreService.save(score);
|
||||||
|
|
||||||
|
// 评分保存成功后,计算并更新选手总分
|
||||||
|
if (success) {
|
||||||
|
Long athleteId = parseLong(dto.getAthleteId());
|
||||||
|
Long projectId = parseLong(dto.getProjectId());
|
||||||
|
Long venueId = parseLong(dto.getVenueId());
|
||||||
|
if (athleteId != null && projectId != null) {
|
||||||
|
updateAthleteTotalScore(athleteId, projectId, venueId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return success ? R.success("评分提交成功") : R.fail("评分提交失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算并更新选手总分
|
||||||
|
* 总分算法:去掉一个最高分和一个最低分,取剩余分数的平均值
|
||||||
|
* 特殊情况:裁判数量<3时,直接取平均分
|
||||||
|
* 只有所有裁判都评分完成后才更新总分
|
||||||
|
*/
|
||||||
|
private void updateAthleteTotalScore(Long athleteId, Long projectId, Long venueId) {
|
||||||
|
try {
|
||||||
|
// 1. 查询该场地的裁判员数量
|
||||||
|
int requiredJudgeCount = getRequiredJudgeCount(venueId);
|
||||||
|
|
||||||
|
// 2. 获取主裁判ID列表
|
||||||
|
List<Long> chiefJudgeIds = getChiefJudgeIds(venueId);
|
||||||
|
|
||||||
|
// 3. 查询该选手在该项目的所有评分(排除主裁判的评分)
|
||||||
|
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
|
||||||
|
scoreQuery.eq(MartialScore::getAthleteId, athleteId);
|
||||||
|
scoreQuery.eq(MartialScore::getProjectId, projectId);
|
||||||
|
scoreQuery.eq(MartialScore::getIsDeleted, 0);
|
||||||
|
// 排除主裁判的所有评分(包括普通评分和修改记录)
|
||||||
|
if (!chiefJudgeIds.isEmpty()) {
|
||||||
|
scoreQuery.notIn(MartialScore::getJudgeId, chiefJudgeIds);
|
||||||
|
}
|
||||||
|
List<MartialScore> scores = scoreService.list(scoreQuery);
|
||||||
|
|
||||||
|
// 4. 判断是否所有裁判都已评分
|
||||||
|
if (scores == null || scores.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果配置了裁判数量,检查是否评分完成
|
||||||
|
if (requiredJudgeCount > 0 && scores.size() < requiredJudgeCount) {
|
||||||
|
// 未完成评分,清空总分
|
||||||
|
MartialAthlete athlete = athleteService.getById(athleteId);
|
||||||
|
if (athlete != null && athlete.getTotalScore() != null) {
|
||||||
|
athlete.setTotalScore(null);
|
||||||
|
athleteService.updateById(athlete);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 计算总分(去掉最高最低分取平均)
|
||||||
|
BigDecimal totalScore = calculateTotalScore(scores);
|
||||||
|
|
||||||
|
// 5. 更新选手总分
|
||||||
|
if (totalScore != null) {
|
||||||
|
MartialAthlete athlete = athleteService.getById(athleteId);
|
||||||
|
if (athlete != null) {
|
||||||
|
athlete.setTotalScore(totalScore);
|
||||||
|
athleteService.updateById(athlete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 记录错误但不影响评分提交
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取项目应评分的裁判数量(裁判员,不包括主裁判)
|
||||||
|
* 按项目过滤:检查 projects JSON 字段是否包含该项目ID
|
||||||
|
*/
|
||||||
|
private int getRequiredJudgeCount(Long venueId) {
|
||||||
|
if (venueId == null || venueId <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
LambdaQueryWrapper<MartialJudgeInvite> judgeQuery = new LambdaQueryWrapper<>();
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getVenueId, venueId);
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getRefereeType, 2); // Only count referees (type=2), exclude chief judge (type=1) and general judge (type=3)
|
||||||
|
List<MartialJudgeInvite> judges = judgeInviteService.list(judgeQuery);
|
||||||
|
// Use distinct judge_id to count unique judges
|
||||||
|
return (int) judges.stream()
|
||||||
|
.map(MartialJudgeInvite::getJudgeId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.distinct()
|
||||||
|
.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算总分
|
||||||
|
* 算法:去掉一个最高分和一个最低分,取剩余分数的平均值
|
||||||
|
* 特殊情况:裁判数量<3时,直接取平均分
|
||||||
|
*/
|
||||||
|
private BigDecimal calculateTotalScore(List<MartialScore> scores) {
|
||||||
|
if (scores == null || scores.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取所有分数并排序
|
||||||
|
List<BigDecimal> scoreValues = scores.stream()
|
||||||
|
.map(MartialScore::getScore)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.sorted()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
int count = scoreValues.size();
|
||||||
|
if (count == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count < 3) {
|
||||||
|
// 裁判数量<3,直接取平均分
|
||||||
|
BigDecimal sum = scoreValues.stream()
|
||||||
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
return sum.divide(new BigDecimal(count), 3, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 去掉最高分和最低分(已排序,去掉第一个和最后一个)
|
||||||
|
List<BigDecimal> middleScores = scoreValues.subList(1, count - 1);
|
||||||
|
|
||||||
|
// 计算平均分
|
||||||
|
BigDecimal sum = middleScores.stream()
|
||||||
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
return sum.divide(new BigDecimal(middleScores.size()), 3, RoundingMode.HALF_UP);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取选手列表(裁判长)
|
* 安全地将String转换为Long
|
||||||
*
|
|
||||||
* @param competitionId 比赛ID
|
|
||||||
* @param venueId 场地ID
|
|
||||||
* @param projectId 项目ID
|
|
||||||
* @return 选手列表(含评分统计)
|
|
||||||
*/
|
*/
|
||||||
@GetMapping("/athletes/admin")
|
private Long parseLong(String value) {
|
||||||
@Operation(summary = "选手列表(裁判长)", description = "裁判长查看所有选手")
|
if (value == null || value.trim().isEmpty()) {
|
||||||
public R<List<MiniAthleteAdminVO>> getAthletesForAdmin(
|
return null;
|
||||||
@RequestParam Long competitionId,
|
}
|
||||||
@RequestParam Long venueId,
|
try {
|
||||||
@RequestParam Long projectId
|
return Long.parseLong(value.trim());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取选手列表(支持分页)
|
||||||
|
* - 裁判员:获取所有选手,标记是否已评分
|
||||||
|
* - 主裁判:获取所有裁判员都评分完成的选手列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/score/athletes")
|
||||||
|
@Operation(summary = "获取选手列表", description = "根据裁判类型获取选手列表(支持分页)")
|
||||||
|
public R<IPage<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO>> getAthletes(
|
||||||
|
@RequestParam Long judgeId,
|
||||||
|
@RequestParam Integer refereeType,
|
||||||
|
@RequestParam(required = false) Long projectId,
|
||||||
|
@RequestParam(required = false) Long venueId,
|
||||||
|
@RequestParam(required = false) Long competitionId,
|
||||||
|
@RequestParam(defaultValue = "1") Integer current,
|
||||||
|
@RequestParam(defaultValue = "10") Integer size
|
||||||
) {
|
) {
|
||||||
List<MiniAthleteAdminVO> result = athleteService.getAthletesForAdmin(
|
// 1. 构建选手查询条件
|
||||||
competitionId, venueId, projectId);
|
LambdaQueryWrapper<MartialAthlete> athleteQuery = new LambdaQueryWrapper<>();
|
||||||
return R.data(result);
|
athleteQuery.eq(MartialAthlete::getIsDeleted, 0);
|
||||||
|
|
||||||
|
// 按比赛ID过滤(重要:确保只显示当前比赛的选手)
|
||||||
|
if (competitionId != null) {
|
||||||
|
athleteQuery.eq(MartialAthlete::getCompetitionId, competitionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projectId != null) {
|
||||||
|
athleteQuery.eq(MartialAthlete::getProjectId, projectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
athleteQuery.orderByAsc(MartialAthlete::getOrderNum);
|
||||||
|
|
||||||
|
List<MartialAthlete> athletes = athleteService.list(athleteQuery);
|
||||||
|
|
||||||
|
// 2. 获取该场地所有主裁判的judge_id列表
|
||||||
|
List<Long> chiefJudgeIds = getChiefJudgeIds(venueId);
|
||||||
|
|
||||||
|
// 3. 获取所有评分记录(排除主裁判的评分)
|
||||||
|
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
|
||||||
|
scoreQuery.eq(MartialScore::getIsDeleted, 0);
|
||||||
|
if (projectId != null) {
|
||||||
|
scoreQuery.eq(MartialScore::getProjectId, projectId);
|
||||||
|
}
|
||||||
|
// 添加场地过滤
|
||||||
|
if (venueId != null && venueId > 0) {
|
||||||
|
scoreQuery.eq(MartialScore::getVenueId, venueId);
|
||||||
|
}
|
||||||
|
// 排除主裁判的评分
|
||||||
|
if (!chiefJudgeIds.isEmpty()) {
|
||||||
|
scoreQuery.notIn(MartialScore::getJudgeId, chiefJudgeIds);
|
||||||
|
}
|
||||||
|
List<MartialScore> allScores = scoreService.list(scoreQuery);
|
||||||
|
|
||||||
|
// 按选手ID分组统计评分
|
||||||
|
java.util.Map<Long, List<MartialScore>> scoresByAthlete = allScores.stream()
|
||||||
|
.collect(java.util.stream.Collectors.groupingBy(MartialScore::getAthleteId));
|
||||||
|
|
||||||
|
// 4. 获取该场地的应评裁判数量
|
||||||
|
int requiredJudgeCount = getRequiredJudgeCount(venueId);
|
||||||
|
|
||||||
|
// 5. 根据裁判类型处理选手列表
|
||||||
|
List<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> filteredList;
|
||||||
|
|
||||||
|
if (refereeType == 1) {
|
||||||
|
// 主裁判:返回所有选手,前端根据totalScore判断是否显示修改按钮
|
||||||
|
filteredList = athletes.stream()
|
||||||
|
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId, requiredJudgeCount))
|
||||||
|
.collect(java.util.stream.Collectors.toList());
|
||||||
|
} else {
|
||||||
|
// 裁判员:返回所有选手,标记是否已评分
|
||||||
|
filteredList = athletes.stream()
|
||||||
|
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId, requiredJudgeCount))
|
||||||
|
.collect(java.util.stream.Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 手动分页
|
||||||
|
int total = filteredList.size();
|
||||||
|
int fromIndex = (current - 1) * size;
|
||||||
|
int toIndex = Math.min(fromIndex + size, total);
|
||||||
|
|
||||||
|
List<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> pageRecords;
|
||||||
|
if (fromIndex >= total) {
|
||||||
|
pageRecords = new ArrayList<>();
|
||||||
|
} else {
|
||||||
|
pageRecords = filteredList.subList(fromIndex, toIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 构建分页结果
|
||||||
|
IPage<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> page = new Page<>(current, size, total);
|
||||||
|
page.setRecords(pageRecords);
|
||||||
|
|
||||||
|
return R.data(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取场地所有主裁判的judge_id列表
|
||||||
|
*/
|
||||||
|
private List<Long> getChiefJudgeIds(Long venueId) {
|
||||||
|
if (venueId == null) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
LambdaQueryWrapper<MartialJudgeInvite> judgeQuery = new LambdaQueryWrapper<>();
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getVenueId, venueId);
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getRole, "chief_judge");
|
||||||
|
List<MartialJudgeInvite> chiefJudges = judgeInviteService.list(judgeQuery);
|
||||||
|
return chiefJudges.stream()
|
||||||
|
.map(MartialJudgeInvite::getJudgeId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取评分详情
|
* 获取评分详情
|
||||||
*
|
|
||||||
* @param athleteId 选手ID
|
|
||||||
* @return 评分详情(选手信息+所有评委的评分)
|
|
||||||
*/
|
*/
|
||||||
@GetMapping("/score/detail/{athleteId}")
|
@GetMapping("/score/detail/{athleteId}")
|
||||||
@Operation(summary = "评分详情", description = "查看选手的所有评委评分")
|
@Operation(summary = "评分详情", description = "查看选手的所有评委评分")
|
||||||
@@ -177,18 +474,184 @@ public class MartialMiniController extends BladeController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改评分(裁判长)
|
* 修改评分(主裁判)
|
||||||
*
|
|
||||||
* @param dto 修改信息(选手ID、修改后的分数、修改原因)
|
|
||||||
* @return 修改结果
|
|
||||||
*/
|
*/
|
||||||
@PutMapping("/score/modify")
|
@PutMapping("/score/modify")
|
||||||
@Operation(summary = "修改评分", description = "裁判长修改选手总分")
|
@Operation(summary = "修改评分", description = "主裁判修改选手总分")
|
||||||
public R modifyScore(@RequestBody MiniScoreModifyDTO dto) {
|
public R modifyScore(@RequestBody MiniScoreModifyDTO dto) {
|
||||||
boolean success = scoreService.modifyScoreByAdmin(dto);
|
boolean success = scoreService.modifyScoreByAdmin(dto);
|
||||||
return success ? R.success("修改成功") : R.fail("修改失败");
|
return success ? R.success("修改成功") : R.fail("修改失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退出登录
|
||||||
|
*/
|
||||||
|
@PostMapping("/logout")
|
||||||
|
@Operation(summary = "退出登录", description = "清除登录状态")
|
||||||
|
public R logout(@RequestHeader(value = "Authorization", required = false) String token) {
|
||||||
|
// 从Redis删除登录缓存
|
||||||
|
if (token != null && !token.isEmpty()) {
|
||||||
|
String cacheKey = MINI_LOGIN_CACHE_PREFIX + token;
|
||||||
|
bladeRedis.del(cacheKey);
|
||||||
|
}
|
||||||
|
return R.success("退出成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token验证(从Redis恢复登录状态)
|
||||||
|
*/
|
||||||
|
@GetMapping("/verify")
|
||||||
|
@Operation(summary = "Token验证", description = "验证token并返回登录信息,支持服务重启后恢复登录状态")
|
||||||
|
public R<MiniLoginVO> verify(@RequestHeader(value = "Authorization", required = false) String token) {
|
||||||
|
if (token == null || token.isEmpty()) {
|
||||||
|
return R.fail("Token不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从Redis获取登录信息
|
||||||
|
String cacheKey = MINI_LOGIN_CACHE_PREFIX + token;
|
||||||
|
MiniLoginVO loginInfo = bladeRedis.get(cacheKey);
|
||||||
|
|
||||||
|
if (loginInfo != null) {
|
||||||
|
// 刷新缓存过期时间
|
||||||
|
bladeRedis.setEx(cacheKey, loginInfo, LOGIN_CACHE_EXPIRE);
|
||||||
|
return R.data(loginInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redis中没有,尝试从数据库恢复
|
||||||
|
LambdaQueryWrapper<MartialJudgeInvite> inviteQuery = new LambdaQueryWrapper<>();
|
||||||
|
inviteQuery.eq(MartialJudgeInvite::getAccessToken, token);
|
||||||
|
inviteQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
||||||
|
MartialJudgeInvite invite = judgeInviteService.getOne(inviteQuery);
|
||||||
|
|
||||||
|
if (invite == null) {
|
||||||
|
return R.fail("Token无效");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invite.getTokenExpireTime() != null && invite.getTokenExpireTime().isBefore(LocalDateTime.now())) {
|
||||||
|
return R.fail("Token已过期");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重建登录信息
|
||||||
|
MartialCompetition competition = competitionService.getById(invite.getCompetitionId());
|
||||||
|
MartialJudge judge = judgeService.getById(invite.getJudgeId());
|
||||||
|
MartialVenue martialVenue = invite.getVenueId() != null ? venueService.getById(invite.getVenueId()) : null;
|
||||||
|
// 获取项目列表:总裁判看所有项目,其他裁判根据场地获取项目
|
||||||
|
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||||
|
Integer refereeTypeVal = invite.getRefereeType();
|
||||||
|
String roleVal = invite.getRole();
|
||||||
|
boolean isGeneralJudge = (refereeTypeVal != null && refereeTypeVal == 3)
|
||||||
|
|| "general_judge".equals(roleVal) || "general".equals(roleVal);
|
||||||
|
|
||||||
|
if (isGeneralJudge) {
|
||||||
|
// 总裁判看所有项目
|
||||||
|
projects = getAllProjectsByCompetition(competition.getId());
|
||||||
|
} else if (Func.isNotEmpty(invite.getProjects())) {
|
||||||
|
projects = parseProjects(invite.getProjects());
|
||||||
|
} else if (invite.getVenueId() != null) {
|
||||||
|
// 未指定项目,根据场地获取项目;如果场地没有项目则返回空列表
|
||||||
|
projects = getProjectsByVenue(invite.getVenueId());
|
||||||
|
}
|
||||||
|
// 如果没有场地,projects保持为空列表
|
||||||
|
|
||||||
|
MiniLoginVO vo = new MiniLoginVO();
|
||||||
|
vo.setToken(token);
|
||||||
|
String role = invite.getRole();
|
||||||
|
Integer refereeType = invite.getRefereeType();
|
||||||
|
if ("general_judge".equals(role) || "general".equals(role) || (refereeType != null && refereeType == 3)) {
|
||||||
|
vo.setUserRole("general");
|
||||||
|
} else if ("chief_judge".equals(role) || (refereeType != null && refereeType == 1)) {
|
||||||
|
vo.setUserRole("admin");
|
||||||
|
} else {
|
||||||
|
vo.setUserRole("pub");
|
||||||
|
}
|
||||||
|
vo.setMatchId(competition != null ? competition.getId() : null);
|
||||||
|
vo.setMatchName(competition != null ? competition.getCompetitionName() : null);
|
||||||
|
vo.setMatchTime(competition != null && competition.getCompetitionStartTime() != null ?
|
||||||
|
competition.getCompetitionStartTime().toString() : "");
|
||||||
|
vo.setJudgeId(judge != null ? judge.getId() : null);
|
||||||
|
vo.setJudgeName(judge != null ? judge.getName() : null);
|
||||||
|
vo.setVenueId(martialVenue != null ? martialVenue.getId() : null);
|
||||||
|
vo.setVenueName(martialVenue != null ? martialVenue.getVenueName() : null);
|
||||||
|
vo.setProjects(projects);
|
||||||
|
|
||||||
|
// 重新缓存到Redis
|
||||||
|
bladeRedis.setEx(cacheKey, vo, LOGIN_CACHE_EXPIRE);
|
||||||
|
|
||||||
|
return R.data(vo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换选手实体为VO
|
||||||
|
* 新增:只有评分完成时才显示总分
|
||||||
|
*/
|
||||||
|
private org.springblade.modules.martial.pojo.vo.MiniAthleteListVO convertToAthleteListVO(
|
||||||
|
MartialAthlete athlete,
|
||||||
|
List<MartialScore> scores,
|
||||||
|
Long currentJudgeId,
|
||||||
|
int requiredJudgeCount) {
|
||||||
|
org.springblade.modules.martial.pojo.vo.MiniAthleteListVO vo = new org.springblade.modules.martial.pojo.vo.MiniAthleteListVO();
|
||||||
|
vo.setAthleteId(athlete.getId());
|
||||||
|
vo.setName(athlete.getPlayerName());
|
||||||
|
vo.setIdCard(athlete.getIdCard());
|
||||||
|
vo.setNumber(athlete.getPlayerNo());
|
||||||
|
vo.setTeam(athlete.getTeamName());
|
||||||
|
vo.setOrderNum(athlete.getOrderNum());
|
||||||
|
vo.setCompetitionStatus(athlete.getCompetitionStatus());
|
||||||
|
|
||||||
|
// 设置应评分裁判数量
|
||||||
|
vo.setRequiredJudgeCount(requiredJudgeCount);
|
||||||
|
|
||||||
|
// 设置项目名称
|
||||||
|
if (athlete.getProjectId() != null) {
|
||||||
|
MartialProject project = projectService.getById(athlete.getProjectId());
|
||||||
|
if (project != null) {
|
||||||
|
vo.setProjectName(project.getProjectName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置评分状态
|
||||||
|
int scoredCount = 0;
|
||||||
|
if (scores != null && !scores.isEmpty()) {
|
||||||
|
scoredCount = scores.size();
|
||||||
|
vo.setScoredJudgeCount(scoredCount);
|
||||||
|
|
||||||
|
// 查找当前裁判的评分
|
||||||
|
MartialScore myScore = scores.stream()
|
||||||
|
.filter(s -> s.getJudgeId().equals(currentJudgeId))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
if (myScore != null) {
|
||||||
|
vo.setScored(true);
|
||||||
|
vo.setMyScore(myScore.getScore());
|
||||||
|
} else {
|
||||||
|
vo.setScored(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vo.setScored(false);
|
||||||
|
vo.setScoredJudgeCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断评分是否完成(所有裁判都已评分)
|
||||||
|
boolean scoringComplete = false;
|
||||||
|
if (requiredJudgeCount > 0) {
|
||||||
|
scoringComplete = scoredCount >= requiredJudgeCount;
|
||||||
|
} else {
|
||||||
|
// 如果没有配置裁判数量,只要有评分就算完成
|
||||||
|
scoringComplete = scoredCount > 0;
|
||||||
|
}
|
||||||
|
vo.setScoringComplete(scoringComplete);
|
||||||
|
|
||||||
|
// 只有评分完成时才显示总分
|
||||||
|
if (scoringComplete) {
|
||||||
|
vo.setTotalScore(athlete.getTotalScore());
|
||||||
|
} else {
|
||||||
|
vo.setTotalScore(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析项目JSON字符串
|
* 解析项目JSON字符串
|
||||||
*/
|
*/
|
||||||
@@ -200,11 +663,9 @@ public class MartialMiniController extends BladeController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 解析JSON数组:格式为 [{"projectId": 1, "projectName": "太极拳"}, ...]
|
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
List<Long> projectIds = mapper.readValue(projectsJson, new TypeReference<List<Long>>() {});
|
List<Long> projectIds = mapper.readValue(projectsJson, new TypeReference<List<Long>>() {});
|
||||||
|
|
||||||
// 查询项目详情
|
|
||||||
if (Func.isNotEmpty(projectIds)) {
|
if (Func.isNotEmpty(projectIds)) {
|
||||||
List<MartialProject> projectList = projectService.listByIds(projectIds);
|
List<MartialProject> projectList = projectService.listByIds(projectIds);
|
||||||
projects = projectList.stream().map(project -> {
|
projects = projectList.stream().map(project -> {
|
||||||
@@ -215,7 +676,6 @@ public class MartialMiniController extends BladeController {
|
|||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 如果JSON解析失败,尝试按逗号分隔的ID字符串解析
|
|
||||||
try {
|
try {
|
||||||
String[] ids = projectsJson.split(",");
|
String[] ids = projectsJson.split(",");
|
||||||
List<Long> projectIds = new ArrayList<>();
|
List<Long> projectIds = new ArrayList<>();
|
||||||
@@ -240,4 +700,271 @@ public class MartialMiniController extends BladeController {
|
|||||||
return projects;
|
return projects;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取比赛的所有项目
|
||||||
|
*/
|
||||||
|
private List<MiniLoginVO.ProjectInfo> getAllProjectsByCompetition(Long competitionId) {
|
||||||
|
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||||
|
|
||||||
|
LambdaQueryWrapper<MartialProject> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(MartialProject::getCompetitionId, competitionId);
|
||||||
|
wrapper.eq(MartialProject::getIsDeleted, 0);
|
||||||
|
|
||||||
|
List<MartialProject> projectList = projectService.list(wrapper);
|
||||||
|
|
||||||
|
if (Func.isNotEmpty(projectList)) {
|
||||||
|
projects = projectList.stream().map(project -> {
|
||||||
|
MiniLoginVO.ProjectInfo info = new MiniLoginVO.ProjectInfo();
|
||||||
|
info.setProjectId(project.getId());
|
||||||
|
info.setProjectName(project.getProjectName());
|
||||||
|
return info;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
return projects;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据场地获取项目列表
|
||||||
|
*/
|
||||||
|
private List<MiniLoginVO.ProjectInfo> getProjectsByVenue(Long venueId) {
|
||||||
|
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||||
|
|
||||||
|
LambdaQueryWrapper<MartialProject> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(MartialProject::getVenueId, venueId);
|
||||||
|
wrapper.eq(MartialProject::getIsDeleted, 0);
|
||||||
|
|
||||||
|
List<MartialProject> projectList = projectService.list(wrapper);
|
||||||
|
|
||||||
|
if (Func.isNotEmpty(projectList)) {
|
||||||
|
projects = projectList.stream().map(project -> {
|
||||||
|
MiniLoginVO.ProjectInfo info = new MiniLoginVO.ProjectInfo();
|
||||||
|
info.setProjectId(project.getId());
|
||||||
|
info.setProjectName(project.getProjectName());
|
||||||
|
return info;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
return projects;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ========== 三级裁判评分流程 API ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主裁判确认/修改分数
|
||||||
|
*/
|
||||||
|
@PostMapping("/chief/confirm")
|
||||||
|
@Operation(summary = "主裁判确认分数", description = "主裁判确认或修改选手分数")
|
||||||
|
public R confirmByChiefJudge(@RequestBody ChiefJudgeConfirmDTO dto) {
|
||||||
|
Long resultId = parseLong(dto.getResultId());
|
||||||
|
Long chiefJudgeId = parseLong(dto.getChiefJudgeId());
|
||||||
|
if (resultId == null || chiefJudgeId == null) {
|
||||||
|
return R.fail("参数错误");
|
||||||
|
}
|
||||||
|
boolean success = resultService.confirmByChiefJudge(resultId, chiefJudgeId, dto.getScore(), dto.getNote());
|
||||||
|
return success ? R.success("确认成功") : R.fail("确认失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 总裁确认/修改分数
|
||||||
|
*/
|
||||||
|
@PostMapping("/general/confirm")
|
||||||
|
@Operation(summary = "总裁确认分数", description = "总裁确认或修改选手分数")
|
||||||
|
public R confirmByGeneralJudge(@RequestBody GeneralJudgeConfirmDTO dto) {
|
||||||
|
Long resultId = parseLong(dto.getResultId());
|
||||||
|
Long generalJudgeId = parseLong(dto.getGeneralJudgeId());
|
||||||
|
if (resultId == null || generalJudgeId == null) {
|
||||||
|
return R.fail("参数错误");
|
||||||
|
}
|
||||||
|
boolean success = resultService.confirmByGeneralJudge(resultId, generalJudgeId, dto.getScore(), dto.getNote());
|
||||||
|
return success ? R.success("确认成功") : R.fail("确认失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取待主裁判确认的成绩列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/chief/pending")
|
||||||
|
@Operation(summary = "待主裁判确认列表", description = "获取待主裁判确认的成绩列表")
|
||||||
|
public R<List<MartialResult>> getPendingChiefConfirmList(@RequestParam Long venueId) {
|
||||||
|
List<MartialResult> list = resultService.getPendingChiefConfirmList(venueId);
|
||||||
|
return R.data(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取待总裁确认的成绩列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/general/pending")
|
||||||
|
@Operation(summary = "待总裁确认列表", description = "获取待总裁确认的成绩列表(所有场地)")
|
||||||
|
public R<List<MartialResult>> getPendingGeneralConfirmList(@RequestParam Long competitionId) {
|
||||||
|
List<MartialResult> list = resultService.getPendingGeneralConfirmList(competitionId);
|
||||||
|
return R.data(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有场地列表(总裁用)
|
||||||
|
*/
|
||||||
|
@GetMapping("/general/venues")
|
||||||
|
@Operation(summary = "获取所有场地", description = "总裁获取比赛的所有场地列表")
|
||||||
|
public R<List<MartialVenue>> getAllVenues(@RequestParam Long competitionId) {
|
||||||
|
LambdaQueryWrapper<MartialVenue> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(MartialVenue::getCompetitionId, competitionId);
|
||||||
|
wrapper.eq(MartialVenue::getIsDeleted, 0);
|
||||||
|
wrapper.orderByAsc(MartialVenue::getVenueName);
|
||||||
|
List<MartialVenue> venues = venueService.list(wrapper);
|
||||||
|
return R.data(venues);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取已总裁确认的成绩列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/general/confirmed")
|
||||||
|
@Operation(summary = "已总裁确认列表", description = "获取已总裁确认的成绩列表")
|
||||||
|
public R<List<MartialResult>> getConfirmedGeneralList(@RequestParam Long competitionId) {
|
||||||
|
List<MartialResult> list = resultService.getConfirmedGeneralList(competitionId);
|
||||||
|
return R.data(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 出场顺序相关 API ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取编排状态
|
||||||
|
*/
|
||||||
|
@GetMapping("/schedule/status")
|
||||||
|
@Operation(summary = "获取编排状态", description = "检查赛事编排是否完成")
|
||||||
|
public R<Map<String, Object>> getScheduleStatus(@RequestParam Long competitionId) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
|
||||||
|
LambdaQueryWrapper<MartialScheduleStatus> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(MartialScheduleStatus::getCompetitionId, competitionId);
|
||||||
|
wrapper.eq(MartialScheduleStatus::getIsDeleted, 0);
|
||||||
|
wrapper.last("LIMIT 1");
|
||||||
|
MartialScheduleStatus status = scheduleStatusMapper.selectOne(wrapper);
|
||||||
|
|
||||||
|
if (status == null) {
|
||||||
|
result.put("isCompleted", false);
|
||||||
|
result.put("scheduleStatus", 0);
|
||||||
|
result.put("statusText", "未编排");
|
||||||
|
return R.data(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isCompleted = status.getScheduleStatus() != null && status.getScheduleStatus() == 2;
|
||||||
|
result.put("isCompleted", isCompleted);
|
||||||
|
result.put("scheduleStatus", status.getScheduleStatus());
|
||||||
|
result.put("statusText", getScheduleStatusText(status.getScheduleStatus()));
|
||||||
|
result.put("totalGroups", status.getTotalGroups());
|
||||||
|
result.put("totalParticipants", status.getTotalParticipants());
|
||||||
|
result.put("lockedTime", status.getLockedTime());
|
||||||
|
|
||||||
|
return R.data(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getScheduleStatusText(Integer status) {
|
||||||
|
if (status == null) return "未编排";
|
||||||
|
switch (status) {
|
||||||
|
case 0: return "未编排";
|
||||||
|
case 1: return "编排中";
|
||||||
|
case 2: return "已锁定";
|
||||||
|
default: return "未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取出场顺序
|
||||||
|
*/
|
||||||
|
@GetMapping("/schedule/lineup")
|
||||||
|
@Operation(summary = "获取出场顺序", description = "获取已编排的出场顺序列表")
|
||||||
|
public R<Map<String, Object>> getLineup(
|
||||||
|
@RequestParam Long competitionId,
|
||||||
|
@RequestParam(required = false) Long projectId
|
||||||
|
) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
|
||||||
|
// 使用现有mapper查询编排详情
|
||||||
|
List<ScheduleGroupDetailVO> details = scheduleGroupMapper.selectScheduleGroupDetails(competitionId);
|
||||||
|
|
||||||
|
if (details == null || details.isEmpty()) {
|
||||||
|
result.put("groups", new ArrayList<>());
|
||||||
|
return R.data(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按项目过滤
|
||||||
|
if (projectId != null) {
|
||||||
|
// 需要通过groupName或其他字段判断项目,这里先获取项目名
|
||||||
|
MartialProject project = projectService.getById(projectId);
|
||||||
|
if (project != null) {
|
||||||
|
String projectName = project.getProjectName();
|
||||||
|
details = details.stream()
|
||||||
|
.filter(d -> d.getGroupName() != null && d.getGroupName().contains(projectName))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为LineupGroupVO格式
|
||||||
|
Map<Long, LineupGroupVO> groupMap = new HashMap<>();
|
||||||
|
for (ScheduleGroupDetailVO detail : details) {
|
||||||
|
Long groupId = detail.getGroupId();
|
||||||
|
LineupGroupVO group = groupMap.get(groupId);
|
||||||
|
if (group == null) {
|
||||||
|
group = new LineupGroupVO();
|
||||||
|
group.setGroupId(groupId);
|
||||||
|
group.setGroupName(detail.getGroupName());
|
||||||
|
group.setCategory(detail.getCategory());
|
||||||
|
group.setVenueName(detail.getVenueName());
|
||||||
|
group.setTimeSlot(detail.getTimeSlot());
|
||||||
|
group.setTableNo(generateTableNo(detail));
|
||||||
|
group.setParticipants(new ArrayList<>());
|
||||||
|
groupMap.put(groupId, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加参赛者
|
||||||
|
if (detail.getParticipantId() != null) {
|
||||||
|
LineupParticipantVO participant = new LineupParticipantVO();
|
||||||
|
participant.setId(detail.getParticipantId());
|
||||||
|
participant.setOrder(detail.getPerformanceOrder() != null ? detail.getPerformanceOrder() : group.getParticipants().size() + 1);
|
||||||
|
participant.setPlayerName(detail.getPlayerName());
|
||||||
|
participant.setOrganization(detail.getOrganization());
|
||||||
|
participant.setStatus(detail.getScheduleStatus() != null ? detail.getScheduleStatus() : "waiting");
|
||||||
|
group.getParticipants().add(participant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.put("groups", new ArrayList<>(groupMap.values()));
|
||||||
|
return R.data(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成表号: 场地(1位) + 时段(1位) + 序号(2位)
|
||||||
|
*/
|
||||||
|
private String generateTableNo(ScheduleGroupDetailVO detail) {
|
||||||
|
// 场地编号(简单取第一个数字或默认1)
|
||||||
|
int venueNo = 1;
|
||||||
|
if (detail.getVenueName() != null) {
|
||||||
|
String venueName = detail.getVenueName();
|
||||||
|
for (char c : venueName.toCharArray()) {
|
||||||
|
if (Character.isDigit(c)) {
|
||||||
|
venueNo = Character.getNumericValue(c);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时段:上午=1, 下午=2
|
||||||
|
int period = 1;
|
||||||
|
if (detail.getTimeSlot() != null) {
|
||||||
|
try {
|
||||||
|
int hour = Integer.parseInt(detail.getTimeSlot().split(":")[0]);
|
||||||
|
period = hour < 12 ? 1 : 2;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 序号:使用displayOrder或默认1
|
||||||
|
int orderNo = detail.getDisplayOrder() != null ? detail.getDisplayOrder() : 1;
|
||||||
|
|
||||||
|
return String.format("%d%d%02d", venueNo, period, orderNo);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,820 @@
|
|||||||
|
package org.springblade.modules.martial.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springblade.core.boot.ctrl.BladeController;
|
||||||
|
import org.springblade.core.tool.api.R;
|
||||||
|
import org.springblade.core.tool.utils.Func;
|
||||||
|
import org.springblade.modules.martial.pojo.dto.MiniAthleteScoreDTO;
|
||||||
|
import org.springblade.modules.martial.pojo.dto.MiniLoginDTO;
|
||||||
|
import org.springblade.modules.martial.pojo.dto.MiniScoreModifyDTO;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.*;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.MiniAthleteAdminVO;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.MiniAthleteScoreVO;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.MiniLoginVO;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.MiniScoreDetailVO;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import org.springblade.modules.martial.service.*;
|
||||||
|
import org.springblade.modules.martial.pojo.dto.ChiefJudgeConfirmDTO;
|
||||||
|
import org.springblade.modules.martial.pojo.dto.GeneralJudgeConfirmDTO;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MtVenue;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MartialVenue;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MartialResult;
|
||||||
|
import org.springblade.core.redis.cache.BladeRedis;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小程序专用接口 控制器
|
||||||
|
*
|
||||||
|
* @author BladeX
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@AllArgsConstructor
|
||||||
|
@RequestMapping("/mini")
|
||||||
|
@Tag(name = "小程序接口", description = "小程序评分系统专用接口")
|
||||||
|
public class MartialMiniController extends BladeController {
|
||||||
|
|
||||||
|
private final IMartialJudgeInviteService judgeInviteService;
|
||||||
|
private final IMartialJudgeService judgeService;
|
||||||
|
private final IMartialCompetitionService competitionService;
|
||||||
|
private final IMartialVenueService venueService;
|
||||||
|
private final IMtVenueService mtVenueService;
|
||||||
|
private final IMartialProjectService projectService;
|
||||||
|
private final IMartialAthleteService athleteService;
|
||||||
|
private final IMartialScoreService scoreService;
|
||||||
|
private final BladeRedis bladeRedis;
|
||||||
|
private final IMartialResultService resultService;
|
||||||
|
|
||||||
|
// Redis缓存key前缀
|
||||||
|
private static final String MINI_LOGIN_CACHE_PREFIX = "mini:login:";
|
||||||
|
// 登录缓存过期时间(7天)
|
||||||
|
private static final Duration LOGIN_CACHE_EXPIRE = Duration.ofDays(7);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录验证
|
||||||
|
*/
|
||||||
|
@PostMapping("/login")
|
||||||
|
@Operation(summary = "登录验证", description = "使用比赛编码和邀请码登录")
|
||||||
|
public R<MiniLoginVO> login(@RequestBody MiniLoginDTO dto) {
|
||||||
|
LambdaQueryWrapper<MartialJudgeInvite> inviteQuery = new LambdaQueryWrapper<>();
|
||||||
|
inviteQuery.eq(MartialJudgeInvite::getInviteCode, dto.getInviteCode());
|
||||||
|
inviteQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
||||||
|
MartialJudgeInvite invite = judgeInviteService.getOne(inviteQuery);
|
||||||
|
|
||||||
|
if (invite == null) {
|
||||||
|
return R.fail("邀请码不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invite.getExpireTime() != null && invite.getExpireTime().isBefore(LocalDateTime.now())) {
|
||||||
|
return R.fail("邀请码已过期");
|
||||||
|
}
|
||||||
|
|
||||||
|
MartialCompetition competition = competitionService.getById(invite.getCompetitionId());
|
||||||
|
if (competition == null) {
|
||||||
|
return R.fail("比赛不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!competition.getCompetitionCode().equals(dto.getMatchCode())) {
|
||||||
|
return R.fail("比赛编码不匹配");
|
||||||
|
}
|
||||||
|
|
||||||
|
MartialJudge judge = judgeService.getById(invite.getJudgeId());
|
||||||
|
if (judge == null) {
|
||||||
|
return R.fail("评委信息不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
String token = UUID.randomUUID().toString().replace("-", "");
|
||||||
|
invite.setAccessToken(token);
|
||||||
|
invite.setTokenExpireTime(LocalDateTime.now().plusDays(7));
|
||||||
|
invite.setIsUsed(1);
|
||||||
|
invite.setUseTime(LocalDateTime.now());
|
||||||
|
invite.setLoginIp(dto.getLoginIp());
|
||||||
|
invite.setDeviceInfo(dto.getDeviceInfo());
|
||||||
|
judgeInviteService.updateById(invite);
|
||||||
|
|
||||||
|
// 从 martial_venue 表获取场地信息
|
||||||
|
MartialVenue martialVenue = null;
|
||||||
|
if (invite.getVenueId() != null) {
|
||||||
|
martialVenue = venueService.getById(invite.getVenueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取项目列表:总裁判看所有项目,其他裁判根据场地获取项目
|
||||||
|
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||||
|
Integer refereeTypeVal = invite.getRefereeType();
|
||||||
|
String roleVal = invite.getRole();
|
||||||
|
boolean isGeneralJudge = (refereeTypeVal != null && refereeTypeVal == 3)
|
||||||
|
|| "general_judge".equals(roleVal) || "general".equals(roleVal);
|
||||||
|
|
||||||
|
if (isGeneralJudge) {
|
||||||
|
// 总裁判看所有项目
|
||||||
|
projects = getAllProjectsByCompetition(competition.getId());
|
||||||
|
} else if (Func.isNotEmpty(invite.getProjects())) {
|
||||||
|
projects = parseProjects(invite.getProjects());
|
||||||
|
} else if (invite.getVenueId() != null) {
|
||||||
|
// 未指定项目,根据场地获取项目;如果场地没有项目则返回空列表
|
||||||
|
projects = getProjectsByVenue(invite.getVenueId());
|
||||||
|
}
|
||||||
|
// 如果没有场地,projects保持为空列表
|
||||||
|
|
||||||
|
MiniLoginVO vo = new MiniLoginVO();
|
||||||
|
vo.setToken(token);
|
||||||
|
String role = invite.getRole();
|
||||||
|
Integer refereeType = invite.getRefereeType();
|
||||||
|
if ("general_judge".equals(role) || "general".equals(role) || (refereeType != null && refereeType == 3)) {
|
||||||
|
vo.setUserRole("general");
|
||||||
|
} else if ("chief_judge".equals(role) || (refereeType != null && refereeType == 1)) {
|
||||||
|
vo.setUserRole("admin");
|
||||||
|
} else {
|
||||||
|
vo.setUserRole("pub");
|
||||||
|
}
|
||||||
|
vo.setMatchId(competition.getId());
|
||||||
|
vo.setMatchName(competition.getCompetitionName());
|
||||||
|
vo.setMatchTime(competition.getCompetitionStartTime() != null ?
|
||||||
|
competition.getCompetitionStartTime().toString() : "");
|
||||||
|
vo.setJudgeId(judge.getId());
|
||||||
|
vo.setJudgeName(judge.getName());
|
||||||
|
vo.setVenueId(martialVenue != null ? martialVenue.getId() : null);
|
||||||
|
vo.setVenueName(martialVenue != null ? martialVenue.getVenueName() : null);
|
||||||
|
vo.setProjects(projects);
|
||||||
|
|
||||||
|
// 将登录信息缓存到Redis(服务重启后仍然有效)
|
||||||
|
String cacheKey = MINI_LOGIN_CACHE_PREFIX + token;
|
||||||
|
bladeRedis.setEx(cacheKey, vo, LOGIN_CACHE_EXPIRE);
|
||||||
|
|
||||||
|
return R.data(vo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交评分(评委)
|
||||||
|
* 注意:ID字段使用String类型接收,避免JavaScript大数精度丢失问题
|
||||||
|
*/
|
||||||
|
@PostMapping("/score/submit")
|
||||||
|
@Operation(summary = "提交评分", description = "评委提交对选手的评分")
|
||||||
|
public R submitScore(@RequestBody org.springblade.modules.martial.pojo.dto.MiniScoreSubmitDTO dto) {
|
||||||
|
MartialScore score = new MartialScore();
|
||||||
|
|
||||||
|
// 将String类型的ID转换为Long,避免JavaScript大数精度丢失
|
||||||
|
score.setAthleteId(parseLong(dto.getAthleteId()));
|
||||||
|
score.setJudgeId(parseLong(dto.getJudgeId()));
|
||||||
|
score.setScore(dto.getScore());
|
||||||
|
score.setProjectId(parseLong(dto.getProjectId()));
|
||||||
|
score.setCompetitionId(parseLong(dto.getCompetitionId()));
|
||||||
|
score.setVenueId(parseLong(dto.getVenueId()));
|
||||||
|
score.setScheduleId(parseLong(dto.getScheduleId()));
|
||||||
|
score.setNote(dto.getNote());
|
||||||
|
score.setScoreTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
if (dto.getDeductions() != null && !dto.getDeductions().isEmpty()) {
|
||||||
|
// 将String类型的扣分项ID转换为Long
|
||||||
|
List<Long> deductionIds = dto.getDeductions().stream()
|
||||||
|
.map(this::parseLong)
|
||||||
|
.filter(id -> id != null)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
score.setDeductionItems(com.alibaba.fastjson.JSON.toJSONString(deductionIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
Long judgeId = parseLong(dto.getJudgeId());
|
||||||
|
if (judgeId != null) {
|
||||||
|
var judge = judgeService.getById(judgeId);
|
||||||
|
if (judge != null) {
|
||||||
|
score.setJudgeName(judge.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean success = scoreService.save(score);
|
||||||
|
|
||||||
|
// 评分保存成功后,计算并更新选手总分
|
||||||
|
if (success) {
|
||||||
|
Long athleteId = parseLong(dto.getAthleteId());
|
||||||
|
Long projectId = parseLong(dto.getProjectId());
|
||||||
|
Long venueId = parseLong(dto.getVenueId());
|
||||||
|
if (athleteId != null && projectId != null) {
|
||||||
|
updateAthleteTotalScore(athleteId, projectId, venueId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return success ? R.success("评分提交成功") : R.fail("评分提交失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算并更新选手总分
|
||||||
|
* 总分算法:去掉一个最高分和一个最低分,取剩余分数的平均值
|
||||||
|
* 特殊情况:裁判数量<3时,直接取平均分
|
||||||
|
* 只有所有裁判都评分完成后才更新总分
|
||||||
|
*/
|
||||||
|
private void updateAthleteTotalScore(Long athleteId, Long projectId, Long venueId) {
|
||||||
|
try {
|
||||||
|
// 1. 查询该场地的裁判员数量
|
||||||
|
int requiredJudgeCount = getRequiredJudgeCount(venueId);
|
||||||
|
|
||||||
|
// 2. 获取主裁判ID列表
|
||||||
|
List<Long> chiefJudgeIds = getChiefJudgeIds(venueId);
|
||||||
|
|
||||||
|
// 3. 查询该选手在该项目的所有评分(排除主裁判的评分)
|
||||||
|
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
|
||||||
|
scoreQuery.eq(MartialScore::getAthleteId, athleteId);
|
||||||
|
scoreQuery.eq(MartialScore::getProjectId, projectId);
|
||||||
|
scoreQuery.eq(MartialScore::getIsDeleted, 0);
|
||||||
|
// 排除主裁判的所有评分(包括普通评分和修改记录)
|
||||||
|
if (!chiefJudgeIds.isEmpty()) {
|
||||||
|
scoreQuery.notIn(MartialScore::getJudgeId, chiefJudgeIds);
|
||||||
|
}
|
||||||
|
List<MartialScore> scores = scoreService.list(scoreQuery);
|
||||||
|
|
||||||
|
// 4. 判断是否所有裁判都已评分
|
||||||
|
if (scores == null || scores.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果配置了裁判数量,检查是否评分完成
|
||||||
|
if (requiredJudgeCount > 0 && scores.size() < requiredJudgeCount) {
|
||||||
|
// 未完成评分,清空总分
|
||||||
|
MartialAthlete athlete = athleteService.getById(athleteId);
|
||||||
|
if (athlete != null && athlete.getTotalScore() != null) {
|
||||||
|
athlete.setTotalScore(null);
|
||||||
|
athleteService.updateById(athlete);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 计算总分(去掉最高最低分取平均)
|
||||||
|
BigDecimal totalScore = calculateTotalScore(scores);
|
||||||
|
|
||||||
|
// 5. 更新选手总分
|
||||||
|
if (totalScore != null) {
|
||||||
|
MartialAthlete athlete = athleteService.getById(athleteId);
|
||||||
|
if (athlete != null) {
|
||||||
|
athlete.setTotalScore(totalScore);
|
||||||
|
athleteService.updateById(athlete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 记录错误但不影响评分提交
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取项目应评分的裁判数量(裁判员,不包括主裁判)
|
||||||
|
* 按项目过滤:检查 projects JSON 字段是否包含该项目ID
|
||||||
|
*/
|
||||||
|
private int getRequiredJudgeCount(Long venueId) {
|
||||||
|
if (venueId == null || venueId <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
LambdaQueryWrapper<MartialJudgeInvite> judgeQuery = new LambdaQueryWrapper<>();
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getVenueId, venueId);
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getRefereeType, 2); // Only count referees (type=2), exclude chief judge (type=1) and general judge (type=3)
|
||||||
|
List<MartialJudgeInvite> judges = judgeInviteService.list(judgeQuery);
|
||||||
|
// Use distinct judge_id to count unique judges
|
||||||
|
return (int) judges.stream()
|
||||||
|
.map(MartialJudgeInvite::getJudgeId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.distinct()
|
||||||
|
.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算总分
|
||||||
|
* 算法:去掉一个最高分和一个最低分,取剩余分数的平均值
|
||||||
|
* 特殊情况:裁判数量<3时,直接取平均分
|
||||||
|
*/
|
||||||
|
private BigDecimal calculateTotalScore(List<MartialScore> scores) {
|
||||||
|
if (scores == null || scores.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取所有分数并排序
|
||||||
|
List<BigDecimal> scoreValues = scores.stream()
|
||||||
|
.map(MartialScore::getScore)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.sorted()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
int count = scoreValues.size();
|
||||||
|
if (count == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count < 3) {
|
||||||
|
// 裁判数量<3,直接取平均分
|
||||||
|
BigDecimal sum = scoreValues.stream()
|
||||||
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
return sum.divide(new BigDecimal(count), 3, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 去掉最高分和最低分(已排序,去掉第一个和最后一个)
|
||||||
|
List<BigDecimal> middleScores = scoreValues.subList(1, count - 1);
|
||||||
|
|
||||||
|
// 计算平均分
|
||||||
|
BigDecimal sum = middleScores.stream()
|
||||||
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
return sum.divide(new BigDecimal(middleScores.size()), 3, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全地将String转换为Long
|
||||||
|
*/
|
||||||
|
private Long parseLong(String value) {
|
||||||
|
if (value == null || value.trim().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Long.parseLong(value.trim());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取选手列表(支持分页)
|
||||||
|
* - 裁判员:获取所有选手,标记是否已评分
|
||||||
|
* - 主裁判:获取所有裁判员都评分完成的选手列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/score/athletes")
|
||||||
|
@Operation(summary = "获取选手列表", description = "根据裁判类型获取选手列表(支持分页)")
|
||||||
|
public R<IPage<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO>> getAthletes(
|
||||||
|
@RequestParam Long judgeId,
|
||||||
|
@RequestParam Integer refereeType,
|
||||||
|
@RequestParam(required = false) Long projectId,
|
||||||
|
@RequestParam(required = false) Long venueId,
|
||||||
|
@RequestParam(required = false) Long competitionId,
|
||||||
|
@RequestParam(defaultValue = "1") Integer current,
|
||||||
|
@RequestParam(defaultValue = "10") Integer size
|
||||||
|
) {
|
||||||
|
// 1. 构建选手查询条件
|
||||||
|
LambdaQueryWrapper<MartialAthlete> athleteQuery = new LambdaQueryWrapper<>();
|
||||||
|
athleteQuery.eq(MartialAthlete::getIsDeleted, 0);
|
||||||
|
|
||||||
|
// 按比赛ID过滤(重要:确保只显示当前比赛的选手)
|
||||||
|
if (competitionId != null) {
|
||||||
|
athleteQuery.eq(MartialAthlete::getCompetitionId, competitionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projectId != null) {
|
||||||
|
athleteQuery.eq(MartialAthlete::getProjectId, projectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
athleteQuery.orderByAsc(MartialAthlete::getOrderNum);
|
||||||
|
|
||||||
|
List<MartialAthlete> athletes = athleteService.list(athleteQuery);
|
||||||
|
|
||||||
|
// 2. 获取该场地所有主裁判的judge_id列表
|
||||||
|
List<Long> chiefJudgeIds = getChiefJudgeIds(venueId);
|
||||||
|
|
||||||
|
// 3. 获取所有评分记录(排除主裁判的评分)
|
||||||
|
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
|
||||||
|
scoreQuery.eq(MartialScore::getIsDeleted, 0);
|
||||||
|
if (projectId != null) {
|
||||||
|
scoreQuery.eq(MartialScore::getProjectId, projectId);
|
||||||
|
}
|
||||||
|
// 添加场地过滤
|
||||||
|
if (venueId != null && venueId > 0) {
|
||||||
|
scoreQuery.eq(MartialScore::getVenueId, venueId);
|
||||||
|
}
|
||||||
|
// 排除主裁判的评分
|
||||||
|
if (!chiefJudgeIds.isEmpty()) {
|
||||||
|
scoreQuery.notIn(MartialScore::getJudgeId, chiefJudgeIds);
|
||||||
|
}
|
||||||
|
List<MartialScore> allScores = scoreService.list(scoreQuery);
|
||||||
|
|
||||||
|
// 按选手ID分组统计评分
|
||||||
|
java.util.Map<Long, List<MartialScore>> scoresByAthlete = allScores.stream()
|
||||||
|
.collect(java.util.stream.Collectors.groupingBy(MartialScore::getAthleteId));
|
||||||
|
|
||||||
|
// 4. 获取该场地的应评裁判数量
|
||||||
|
int requiredJudgeCount = getRequiredJudgeCount(venueId);
|
||||||
|
|
||||||
|
// 5. 根据裁判类型处理选手列表
|
||||||
|
List<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> filteredList;
|
||||||
|
|
||||||
|
if (refereeType == 1) {
|
||||||
|
// 主裁判:返回所有选手,前端根据totalScore判断是否显示修改按钮
|
||||||
|
filteredList = athletes.stream()
|
||||||
|
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId, requiredJudgeCount))
|
||||||
|
.collect(java.util.stream.Collectors.toList());
|
||||||
|
} else {
|
||||||
|
// 裁判员:返回所有选手,标记是否已评分
|
||||||
|
filteredList = athletes.stream()
|
||||||
|
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId, requiredJudgeCount))
|
||||||
|
.collect(java.util.stream.Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 手动分页
|
||||||
|
int total = filteredList.size();
|
||||||
|
int fromIndex = (current - 1) * size;
|
||||||
|
int toIndex = Math.min(fromIndex + size, total);
|
||||||
|
|
||||||
|
List<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> pageRecords;
|
||||||
|
if (fromIndex >= total) {
|
||||||
|
pageRecords = new ArrayList<>();
|
||||||
|
} else {
|
||||||
|
pageRecords = filteredList.subList(fromIndex, toIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 构建分页结果
|
||||||
|
IPage<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> page = new Page<>(current, size, total);
|
||||||
|
page.setRecords(pageRecords);
|
||||||
|
|
||||||
|
return R.data(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取场地所有主裁判的judge_id列表
|
||||||
|
*/
|
||||||
|
private List<Long> getChiefJudgeIds(Long venueId) {
|
||||||
|
if (venueId == null) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
LambdaQueryWrapper<MartialJudgeInvite> judgeQuery = new LambdaQueryWrapper<>();
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getVenueId, venueId);
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
||||||
|
judgeQuery.eq(MartialJudgeInvite::getRole, "chief_judge");
|
||||||
|
List<MartialJudgeInvite> chiefJudges = judgeInviteService.list(judgeQuery);
|
||||||
|
return chiefJudges.stream()
|
||||||
|
.map(MartialJudgeInvite::getJudgeId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取评分详情
|
||||||
|
*/
|
||||||
|
@GetMapping("/score/detail/{athleteId}")
|
||||||
|
@Operation(summary = "评分详情", description = "查看选手的所有评委评分")
|
||||||
|
public R<MiniScoreDetailVO> getScoreDetail(@PathVariable Long athleteId) {
|
||||||
|
MiniScoreDetailVO detail = scoreService.getScoreDetailForMini(athleteId);
|
||||||
|
return R.data(detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改评分(主裁判)
|
||||||
|
*/
|
||||||
|
@PutMapping("/score/modify")
|
||||||
|
@Operation(summary = "修改评分", description = "主裁判修改选手总分")
|
||||||
|
public R modifyScore(@RequestBody MiniScoreModifyDTO dto) {
|
||||||
|
boolean success = scoreService.modifyScoreByAdmin(dto);
|
||||||
|
return success ? R.success("修改成功") : R.fail("修改失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退出登录
|
||||||
|
*/
|
||||||
|
@PostMapping("/logout")
|
||||||
|
@Operation(summary = "退出登录", description = "清除登录状态")
|
||||||
|
public R logout(@RequestHeader(value = "Authorization", required = false) String token) {
|
||||||
|
// 从Redis删除登录缓存
|
||||||
|
if (token != null && !token.isEmpty()) {
|
||||||
|
String cacheKey = MINI_LOGIN_CACHE_PREFIX + token;
|
||||||
|
bladeRedis.del(cacheKey);
|
||||||
|
}
|
||||||
|
return R.success("退出成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token验证(从Redis恢复登录状态)
|
||||||
|
*/
|
||||||
|
@GetMapping("/verify")
|
||||||
|
@Operation(summary = "Token验证", description = "验证token并返回登录信息,支持服务重启后恢复登录状态")
|
||||||
|
public R<MiniLoginVO> verify(@RequestHeader(value = "Authorization", required = false) String token) {
|
||||||
|
if (token == null || token.isEmpty()) {
|
||||||
|
return R.fail("Token不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从Redis获取登录信息
|
||||||
|
String cacheKey = MINI_LOGIN_CACHE_PREFIX + token;
|
||||||
|
MiniLoginVO loginInfo = bladeRedis.get(cacheKey);
|
||||||
|
|
||||||
|
if (loginInfo != null) {
|
||||||
|
// 刷新缓存过期时间
|
||||||
|
bladeRedis.setEx(cacheKey, loginInfo, LOGIN_CACHE_EXPIRE);
|
||||||
|
return R.data(loginInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redis中没有,尝试从数据库恢复
|
||||||
|
LambdaQueryWrapper<MartialJudgeInvite> inviteQuery = new LambdaQueryWrapper<>();
|
||||||
|
inviteQuery.eq(MartialJudgeInvite::getAccessToken, token);
|
||||||
|
inviteQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
||||||
|
MartialJudgeInvite invite = judgeInviteService.getOne(inviteQuery);
|
||||||
|
|
||||||
|
if (invite == null) {
|
||||||
|
return R.fail("Token无效");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invite.getTokenExpireTime() != null && invite.getTokenExpireTime().isBefore(LocalDateTime.now())) {
|
||||||
|
return R.fail("Token已过期");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重建登录信息
|
||||||
|
MartialCompetition competition = competitionService.getById(invite.getCompetitionId());
|
||||||
|
MartialJudge judge = judgeService.getById(invite.getJudgeId());
|
||||||
|
MartialVenue martialVenue = invite.getVenueId() != null ? venueService.getById(invite.getVenueId()) : null;
|
||||||
|
// 获取项目列表:总裁判看所有项目,其他裁判根据场地获取项目
|
||||||
|
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||||
|
Integer refereeTypeVal = invite.getRefereeType();
|
||||||
|
String roleVal = invite.getRole();
|
||||||
|
boolean isGeneralJudge = (refereeTypeVal != null && refereeTypeVal == 3)
|
||||||
|
|| "general_judge".equals(roleVal) || "general".equals(roleVal);
|
||||||
|
|
||||||
|
if (isGeneralJudge) {
|
||||||
|
// 总裁判看所有项目
|
||||||
|
projects = getAllProjectsByCompetition(competition.getId());
|
||||||
|
} else if (Func.isNotEmpty(invite.getProjects())) {
|
||||||
|
projects = parseProjects(invite.getProjects());
|
||||||
|
} else if (invite.getVenueId() != null) {
|
||||||
|
// 未指定项目,根据场地获取项目;如果场地没有项目则返回空列表
|
||||||
|
projects = getProjectsByVenue(invite.getVenueId());
|
||||||
|
}
|
||||||
|
// 如果没有场地,projects保持为空列表
|
||||||
|
|
||||||
|
MiniLoginVO vo = new MiniLoginVO();
|
||||||
|
vo.setToken(token);
|
||||||
|
String role = invite.getRole();
|
||||||
|
Integer refereeType = invite.getRefereeType();
|
||||||
|
if ("general_judge".equals(role) || "general".equals(role) || (refereeType != null && refereeType == 3)) {
|
||||||
|
vo.setUserRole("general");
|
||||||
|
} else if ("chief_judge".equals(role) || (refereeType != null && refereeType == 1)) {
|
||||||
|
vo.setUserRole("admin");
|
||||||
|
} else {
|
||||||
|
vo.setUserRole("pub");
|
||||||
|
}
|
||||||
|
vo.setMatchId(competition != null ? competition.getId() : null);
|
||||||
|
vo.setMatchName(competition != null ? competition.getCompetitionName() : null);
|
||||||
|
vo.setMatchTime(competition != null && competition.getCompetitionStartTime() != null ?
|
||||||
|
competition.getCompetitionStartTime().toString() : "");
|
||||||
|
vo.setJudgeId(judge != null ? judge.getId() : null);
|
||||||
|
vo.setJudgeName(judge != null ? judge.getName() : null);
|
||||||
|
vo.setVenueId(martialVenue != null ? martialVenue.getId() : null);
|
||||||
|
vo.setVenueName(martialVenue != null ? martialVenue.getVenueName() : null);
|
||||||
|
vo.setProjects(projects);
|
||||||
|
|
||||||
|
// 重新缓存到Redis
|
||||||
|
bladeRedis.setEx(cacheKey, vo, LOGIN_CACHE_EXPIRE);
|
||||||
|
|
||||||
|
return R.data(vo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换选手实体为VO
|
||||||
|
* 新增:只有评分完成时才显示总分
|
||||||
|
*/
|
||||||
|
private org.springblade.modules.martial.pojo.vo.MiniAthleteListVO convertToAthleteListVO(
|
||||||
|
MartialAthlete athlete,
|
||||||
|
List<MartialScore> scores,
|
||||||
|
Long currentJudgeId,
|
||||||
|
int requiredJudgeCount) {
|
||||||
|
org.springblade.modules.martial.pojo.vo.MiniAthleteListVO vo = new org.springblade.modules.martial.pojo.vo.MiniAthleteListVO();
|
||||||
|
vo.setAthleteId(athlete.getId());
|
||||||
|
vo.setName(athlete.getPlayerName());
|
||||||
|
vo.setIdCard(athlete.getIdCard());
|
||||||
|
vo.setNumber(athlete.getPlayerNo());
|
||||||
|
vo.setTeam(athlete.getTeamName());
|
||||||
|
vo.setOrderNum(athlete.getOrderNum());
|
||||||
|
vo.setCompetitionStatus(athlete.getCompetitionStatus());
|
||||||
|
|
||||||
|
// 设置应评分裁判数量
|
||||||
|
vo.setRequiredJudgeCount(requiredJudgeCount);
|
||||||
|
|
||||||
|
// 设置项目名称
|
||||||
|
if (athlete.getProjectId() != null) {
|
||||||
|
MartialProject project = projectService.getById(athlete.getProjectId());
|
||||||
|
if (project != null) {
|
||||||
|
vo.setProjectName(project.getProjectName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置评分状态
|
||||||
|
int scoredCount = 0;
|
||||||
|
if (scores != null && !scores.isEmpty()) {
|
||||||
|
scoredCount = scores.size();
|
||||||
|
vo.setScoredJudgeCount(scoredCount);
|
||||||
|
|
||||||
|
// 查找当前裁判的评分
|
||||||
|
MartialScore myScore = scores.stream()
|
||||||
|
.filter(s -> s.getJudgeId().equals(currentJudgeId))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
if (myScore != null) {
|
||||||
|
vo.setScored(true);
|
||||||
|
vo.setMyScore(myScore.getScore());
|
||||||
|
} else {
|
||||||
|
vo.setScored(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vo.setScored(false);
|
||||||
|
vo.setScoredJudgeCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断评分是否完成(所有裁判都已评分)
|
||||||
|
boolean scoringComplete = false;
|
||||||
|
if (requiredJudgeCount > 0) {
|
||||||
|
scoringComplete = scoredCount >= requiredJudgeCount;
|
||||||
|
} else {
|
||||||
|
// 如果没有配置裁判数量,只要有评分就算完成
|
||||||
|
scoringComplete = scoredCount > 0;
|
||||||
|
}
|
||||||
|
vo.setScoringComplete(scoringComplete);
|
||||||
|
|
||||||
|
// 只有评分完成时才显示总分
|
||||||
|
if (scoringComplete) {
|
||||||
|
vo.setTotalScore(athlete.getTotalScore());
|
||||||
|
} else {
|
||||||
|
vo.setTotalScore(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析项目JSON字符串
|
||||||
|
*/
|
||||||
|
private List<MiniLoginVO.ProjectInfo> parseProjects(String projectsJson) {
|
||||||
|
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||||
|
|
||||||
|
if (Func.isEmpty(projectsJson)) {
|
||||||
|
return projects;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
List<Long> projectIds = mapper.readValue(projectsJson, new TypeReference<List<Long>>() {});
|
||||||
|
|
||||||
|
if (Func.isNotEmpty(projectIds)) {
|
||||||
|
List<MartialProject> projectList = projectService.listByIds(projectIds);
|
||||||
|
projects = projectList.stream().map(project -> {
|
||||||
|
MiniLoginVO.ProjectInfo info = new MiniLoginVO.ProjectInfo();
|
||||||
|
info.setProjectId(project.getId());
|
||||||
|
info.setProjectName(project.getProjectName());
|
||||||
|
return info;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
try {
|
||||||
|
String[] ids = projectsJson.split(",");
|
||||||
|
List<Long> projectIds = new ArrayList<>();
|
||||||
|
for (String id : ids) {
|
||||||
|
projectIds.add(Long.parseLong(id.trim()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Func.isNotEmpty(projectIds)) {
|
||||||
|
List<MartialProject> projectList = projectService.listByIds(projectIds);
|
||||||
|
projects = projectList.stream().map(project -> {
|
||||||
|
MiniLoginVO.ProjectInfo info = new MiniLoginVO.ProjectInfo();
|
||||||
|
info.setProjectId(project.getId());
|
||||||
|
info.setProjectName(project.getProjectName());
|
||||||
|
return info;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// 解析失败,返回空列表
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return projects;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取比赛的所有项目
|
||||||
|
*/
|
||||||
|
private List<MiniLoginVO.ProjectInfo> getAllProjectsByCompetition(Long competitionId) {
|
||||||
|
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||||
|
|
||||||
|
LambdaQueryWrapper<MartialProject> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(MartialProject::getCompetitionId, competitionId);
|
||||||
|
wrapper.eq(MartialProject::getIsDeleted, 0);
|
||||||
|
|
||||||
|
List<MartialProject> projectList = projectService.list(wrapper);
|
||||||
|
|
||||||
|
if (Func.isNotEmpty(projectList)) {
|
||||||
|
projects = projectList.stream().map(project -> {
|
||||||
|
MiniLoginVO.ProjectInfo info = new MiniLoginVO.ProjectInfo();
|
||||||
|
info.setProjectId(project.getId());
|
||||||
|
info.setProjectName(project.getProjectName());
|
||||||
|
return info;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
return projects;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据场地获取项目列表
|
||||||
|
*/
|
||||||
|
private List<MiniLoginVO.ProjectInfo> getProjectsByVenue(Long venueId) {
|
||||||
|
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
||||||
|
|
||||||
|
LambdaQueryWrapper<MartialProject> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(MartialProject::getVenueId, venueId);
|
||||||
|
wrapper.eq(MartialProject::getIsDeleted, 0);
|
||||||
|
|
||||||
|
List<MartialProject> projectList = projectService.list(wrapper);
|
||||||
|
|
||||||
|
if (Func.isNotEmpty(projectList)) {
|
||||||
|
projects = projectList.stream().map(project -> {
|
||||||
|
MiniLoginVO.ProjectInfo info = new MiniLoginVO.ProjectInfo();
|
||||||
|
info.setProjectId(project.getId());
|
||||||
|
info.setProjectName(project.getProjectName());
|
||||||
|
return info;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
return projects;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ========== 三级裁判评分流程 API ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主裁判确认/修改分数
|
||||||
|
*/
|
||||||
|
@PostMapping("/chief/confirm")
|
||||||
|
@Operation(summary = "主裁判确认分数", description = "主裁判确认或修改选手分数")
|
||||||
|
public R confirmByChiefJudge(@RequestBody ChiefJudgeConfirmDTO dto) {
|
||||||
|
Long resultId = parseLong(dto.getResultId());
|
||||||
|
Long chiefJudgeId = parseLong(dto.getChiefJudgeId());
|
||||||
|
if (resultId == null || chiefJudgeId == null) {
|
||||||
|
return R.fail("参数错误");
|
||||||
|
}
|
||||||
|
boolean success = resultService.confirmByChiefJudge(resultId, chiefJudgeId, dto.getScore(), dto.getNote());
|
||||||
|
return success ? R.success("确认成功") : R.fail("确认失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 总裁确认/修改分数
|
||||||
|
*/
|
||||||
|
@PostMapping("/general/confirm")
|
||||||
|
@Operation(summary = "总裁确认分数", description = "总裁确认或修改选手分数")
|
||||||
|
public R confirmByGeneralJudge(@RequestBody GeneralJudgeConfirmDTO dto) {
|
||||||
|
Long resultId = parseLong(dto.getResultId());
|
||||||
|
Long generalJudgeId = parseLong(dto.getGeneralJudgeId());
|
||||||
|
if (resultId == null || generalJudgeId == null) {
|
||||||
|
return R.fail("参数错误");
|
||||||
|
}
|
||||||
|
boolean success = resultService.confirmByGeneralJudge(resultId, generalJudgeId, dto.getScore(), dto.getNote());
|
||||||
|
return success ? R.success("确认成功") : R.fail("确认失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取待主裁判确认的成绩列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/chief/pending")
|
||||||
|
@Operation(summary = "待主裁判确认列表", description = "获取待主裁判确认的成绩列表")
|
||||||
|
public R<List<MartialResult>> getPendingChiefConfirmList(@RequestParam Long venueId) {
|
||||||
|
List<MartialResult> list = resultService.getPendingChiefConfirmList(venueId);
|
||||||
|
return R.data(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取待总裁确认的成绩列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/general/pending")
|
||||||
|
@Operation(summary = "待总裁确认列表", description = "获取待总裁确认的成绩列表(所有场地)")
|
||||||
|
public R<List<MartialResult>> getPendingGeneralConfirmList(@RequestParam Long competitionId) {
|
||||||
|
List<MartialResult> list = resultService.getPendingGeneralConfirmList(competitionId);
|
||||||
|
return R.data(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有场地列表(总裁用)
|
||||||
|
*/
|
||||||
|
@GetMapping("/general/venues")
|
||||||
|
@Operation(summary = "获取所有场地", description = "总裁获取比赛的所有场地列表")
|
||||||
|
public R<List<MartialVenue>> getAllVenues(@RequestParam Long competitionId) {
|
||||||
|
LambdaQueryWrapper<MartialVenue> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(MartialVenue::getCompetitionId, competitionId);
|
||||||
|
wrapper.eq(MartialVenue::getIsDeleted, 0);
|
||||||
|
wrapper.orderByAsc(MartialVenue::getVenueName);
|
||||||
|
List<MartialVenue> venues = venueService.list(wrapper);
|
||||||
|
return R.data(venues);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取已总裁确认的成绩列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/general/confirmed")
|
||||||
|
@Operation(summary = "已总裁确认列表", description = "获取已总裁确认的成绩列表")
|
||||||
|
public R<List<MartialResult>> getConfirmedGeneralList(@RequestParam Long competitionId) {
|
||||||
|
List<MartialResult> list = resultService.getConfirmedGeneralList(competitionId);
|
||||||
|
return R.data(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.springblade.modules.martial.controller;
|
package org.springblade.modules.martial.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
@@ -9,6 +11,7 @@ import org.springblade.core.mp.support.Condition;
|
|||||||
import org.springblade.core.mp.support.Query;
|
import org.springblade.core.mp.support.Query;
|
||||||
import org.springblade.core.tool.api.R;
|
import org.springblade.core.tool.api.R;
|
||||||
import org.springblade.core.tool.utils.Func;
|
import org.springblade.core.tool.utils.Func;
|
||||||
|
import org.springblade.core.tool.utils.StringUtil;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialProject;
|
import org.springblade.modules.martial.pojo.entity.MartialProject;
|
||||||
import org.springblade.modules.martial.service.IMartialProjectService;
|
import org.springblade.modules.martial.service.IMartialProjectService;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
@@ -42,7 +45,31 @@ public class MartialProjectController extends BladeController {
|
|||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@Operation(summary = "分页列表", description = "分页查询")
|
@Operation(summary = "分页列表", description = "分页查询")
|
||||||
public R<IPage<MartialProject>> list(MartialProject project, Query query) {
|
public R<IPage<MartialProject>> list(MartialProject project, Query query) {
|
||||||
IPage<MartialProject> pages = projectService.page(Condition.getPage(query), Condition.getQueryWrapper(project));
|
QueryWrapper<MartialProject> queryWrapper = new QueryWrapper<>();
|
||||||
|
// 赛事ID精确匹配
|
||||||
|
if (project.getCompetitionId() != null) {
|
||||||
|
queryWrapper.eq("competition_id", project.getCompetitionId());
|
||||||
|
}
|
||||||
|
// 项目名称模糊查询
|
||||||
|
if (StringUtil.isNotBlank(project.getProjectName())) {
|
||||||
|
queryWrapper.like("project_name", project.getProjectName());
|
||||||
|
}
|
||||||
|
// 分组类别模糊查询
|
||||||
|
if (StringUtil.isNotBlank(project.getCategory())) {
|
||||||
|
queryWrapper.like("category", project.getCategory());
|
||||||
|
}
|
||||||
|
// 项目类型精确匹配
|
||||||
|
if (project.getEventType() != null) {
|
||||||
|
queryWrapper.eq("event_type", project.getEventType());
|
||||||
|
}
|
||||||
|
// 参赛类型精确匹配
|
||||||
|
if (project.getType() != null) {
|
||||||
|
queryWrapper.eq("type", project.getType());
|
||||||
|
}
|
||||||
|
// 按排序字段和创建时间排序
|
||||||
|
queryWrapper.orderByAsc("sort_order").orderByDesc("create_time");
|
||||||
|
|
||||||
|
IPage<MartialProject> pages = projectService.page(Condition.getPage(query), queryWrapper);
|
||||||
return R.data(pages);
|
return R.data(pages);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,9 +79,32 @@ public class MartialProjectController extends BladeController {
|
|||||||
@PostMapping("/submit")
|
@PostMapping("/submit")
|
||||||
@Operation(summary = "新增或修改", description = "传入实体")
|
@Operation(summary = "新增或修改", description = "传入实体")
|
||||||
public R submit(@RequestBody MartialProject project) {
|
public R submit(@RequestBody MartialProject project) {
|
||||||
|
// Auto-generate project code for new projects
|
||||||
|
if (project.getId() == null && StringUtil.isBlank(project.getProjectCode())) {
|
||||||
|
String projectCode = generateProjectCode(project.getCompetitionId());
|
||||||
|
project.setProjectCode(projectCode);
|
||||||
|
}
|
||||||
return R.status(projectService.saveOrUpdate(project));
|
return R.status(projectService.saveOrUpdate(project));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate project code: competition prefix + sequence number
|
||||||
|
* Format: C{competitionId}-P{sequence}, e.g., C1-P001
|
||||||
|
*/
|
||||||
|
private String generateProjectCode(Long competitionId) {
|
||||||
|
if (competitionId == null) {
|
||||||
|
return "P" + System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count existing projects for this competition
|
||||||
|
LambdaQueryWrapper<MartialProject> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(MartialProject::getCompetitionId, competitionId);
|
||||||
|
long count = projectService.count(wrapper);
|
||||||
|
|
||||||
|
// Generate code: C{competitionId}-P{sequence}
|
||||||
|
return String.format("C%d-P%03d", competitionId, count + 1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除
|
* 删除
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,23 +1,40 @@
|
|||||||
package org.springblade.modules.martial.controller;
|
package org.springblade.modules.martial.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springblade.core.boot.ctrl.BladeController;
|
import org.springblade.core.boot.ctrl.BladeController;
|
||||||
import org.springblade.core.mp.support.Condition;
|
|
||||||
import org.springblade.core.mp.support.Query;
|
import org.springblade.core.mp.support.Query;
|
||||||
|
import org.springblade.core.secure.utils.AuthUtil;
|
||||||
import org.springblade.core.tool.api.R;
|
import org.springblade.core.tool.api.R;
|
||||||
import org.springblade.core.tool.utils.Func;
|
import org.springblade.core.tool.utils.Func;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MartialAthlete;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MartialCompetition;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MartialProject;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialRegistrationOrder;
|
import org.springblade.modules.martial.pojo.entity.MartialRegistrationOrder;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MartialTeam;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MartialTeamMember;
|
||||||
|
import org.springblade.modules.martial.pojo.dto.RegistrationSubmitDTO;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.MartialRegistrationOrderVO;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.OrganizationStatsVO;
|
||||||
|
import org.springblade.modules.martial.service.IMartialAthleteService;
|
||||||
|
import org.springblade.modules.martial.service.IMartialCompetitionService;
|
||||||
|
import org.springblade.modules.martial.service.IMartialProjectService;
|
||||||
import org.springblade.modules.martial.service.IMartialRegistrationOrderService;
|
import org.springblade.modules.martial.service.IMartialRegistrationOrderService;
|
||||||
|
import org.springblade.modules.martial.service.IMartialTeamService;
|
||||||
|
import org.springblade.modules.martial.mapper.MartialTeamMemberMapper;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
/**
|
import java.math.BigDecimal;
|
||||||
* 报名订单 控制器
|
import java.time.LocalDateTime;
|
||||||
*
|
import java.util.*;
|
||||||
* @author BladeX
|
import java.util.stream.Collectors;
|
||||||
*/
|
|
||||||
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@RequestMapping("/martial/registrationOrder")
|
@RequestMapping("/martial/registrationOrder")
|
||||||
@@ -25,39 +42,372 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
public class MartialRegistrationOrderController extends BladeController {
|
public class MartialRegistrationOrderController extends BladeController {
|
||||||
|
|
||||||
private final IMartialRegistrationOrderService registrationOrderService;
|
private final IMartialRegistrationOrderService registrationOrderService;
|
||||||
|
private final IMartialAthleteService athleteService;
|
||||||
|
private final IMartialTeamService teamService;
|
||||||
|
private final IMartialCompetitionService competitionService;
|
||||||
|
private final IMartialProjectService projectService;
|
||||||
|
private final MartialTeamMemberMapper teamMemberMapper;
|
||||||
|
|
||||||
/**
|
|
||||||
* 详情
|
|
||||||
*/
|
|
||||||
@GetMapping("/detail")
|
@GetMapping("/detail")
|
||||||
@Operation(summary = "详情", description = "传入ID")
|
@Operation(summary = "详情", description = "传入ID")
|
||||||
public R<MartialRegistrationOrder> detail(@RequestParam Long id) {
|
public R detail(@RequestParam Long id) {
|
||||||
MartialRegistrationOrder detail = registrationOrderService.getById(id);
|
return R.data(registrationOrderService.getDetailWithRelations(id));
|
||||||
return R.data(detail);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 分页列表
|
|
||||||
*/
|
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@Operation(summary = "分页列表", description = "分页查询")
|
@Operation(summary = "分页列表", description = "分页查询当前用户的报名记录")
|
||||||
public R<IPage<MartialRegistrationOrder>> list(MartialRegistrationOrder registrationOrder, Query query) {
|
public R<IPage<MartialRegistrationOrderVO>> list(MartialRegistrationOrder registrationOrder, Query query) {
|
||||||
IPage<MartialRegistrationOrder> pages = registrationOrderService.page(Condition.getPage(query), Condition.getQueryWrapper(registrationOrder));
|
Long userId = AuthUtil.getUserId();
|
||||||
|
Integer status = registrationOrder.getStatus();
|
||||||
|
|
||||||
|
IPage<MartialRegistrationOrderVO> pages = registrationOrderService.getListWithRelations(
|
||||||
|
userId, status, query.getCurrent(), query.getSize());
|
||||||
|
|
||||||
return R.data(pages);
|
return R.data(pages);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@GetMapping("/organization-stats")
|
||||||
* 新增或修改
|
@Operation(summary = "单位统计", description = "按单位统计运动员、项目、金额")
|
||||||
*/
|
public R<List<OrganizationStatsVO>> getOrganizationStats(@RequestParam Long competitionId) {
|
||||||
@PostMapping("/submit")
|
log.info("获取单位统计: competitionId={}", competitionId);
|
||||||
@Operation(summary = "新增或修改", description = "传入实体")
|
|
||||||
public R submit(@RequestBody MartialRegistrationOrder registrationOrder) {
|
// 1. Get all athletes for this competition
|
||||||
return R.status(registrationOrderService.saveOrUpdate(registrationOrder));
|
LambdaQueryWrapper<MartialAthlete> athleteWrapper = new LambdaQueryWrapper<>();
|
||||||
|
athleteWrapper.eq(MartialAthlete::getCompetitionId, competitionId)
|
||||||
|
.eq(MartialAthlete::getIsDeleted, 0);
|
||||||
|
List<MartialAthlete> athletes = athleteService.list(athleteWrapper);
|
||||||
|
|
||||||
|
if (athletes.isEmpty()) {
|
||||||
|
return R.data(new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Get all projects for this competition
|
||||||
|
Set<Long> projectIds = athletes.stream()
|
||||||
|
.map(MartialAthlete::getProjectId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
final Map<Long, MartialProject> projectMap = new HashMap<>();
|
||||||
|
if (!projectIds.isEmpty()) {
|
||||||
|
List<MartialProject> projects = projectService.listByIds(projectIds);
|
||||||
|
projectMap.putAll(projects.stream().collect(Collectors.toMap(MartialProject::getId, p -> p)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Get team members for team projects
|
||||||
|
Set<Long> teamIds = athletes.stream()
|
||||||
|
.filter(a -> {
|
||||||
|
MartialProject project = projectMap.get(a.getProjectId());
|
||||||
|
return project != null && project.getType() != null && project.getType() == 2;
|
||||||
|
})
|
||||||
|
.map(a -> {
|
||||||
|
// Try to get team ID from team table by team name
|
||||||
|
LambdaQueryWrapper<MartialTeam> teamWrapper = new LambdaQueryWrapper<>();
|
||||||
|
teamWrapper.eq(MartialTeam::getTeamName, a.getTeamName())
|
||||||
|
.eq(MartialTeam::getIsDeleted, 0)
|
||||||
|
.last("LIMIT 1");
|
||||||
|
MartialTeam team = teamService.getOne(teamWrapper, false);
|
||||||
|
return team != null ? team.getId() : null;
|
||||||
|
})
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
// Get team members
|
||||||
|
Map<Long, List<MartialTeamMember>> teamMembersMap = new HashMap<>();
|
||||||
|
if (!teamIds.isEmpty()) {
|
||||||
|
LambdaQueryWrapper<MartialTeamMember> memberWrapper = new LambdaQueryWrapper<>();
|
||||||
|
memberWrapper.in(MartialTeamMember::getTeamId, teamIds)
|
||||||
|
.eq(MartialTeamMember::getIsDeleted, 0);
|
||||||
|
List<MartialTeamMember> members = teamMemberMapper.selectList(memberWrapper);
|
||||||
|
teamMembersMap = members.stream().collect(Collectors.groupingBy(MartialTeamMember::getTeamId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Group by organization and calculate stats
|
||||||
|
Map<String, OrganizationStatsVO> orgStatsMap = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
for (MartialAthlete athlete : athletes) {
|
||||||
|
String org = athlete.getOrganization();
|
||||||
|
if (org == null || org.isEmpty()) {
|
||||||
|
org = "未知单位";
|
||||||
|
}
|
||||||
|
|
||||||
|
OrganizationStatsVO stats = orgStatsMap.computeIfAbsent(org, k -> {
|
||||||
|
OrganizationStatsVO vo = new OrganizationStatsVO();
|
||||||
|
vo.setOrganization(k);
|
||||||
|
vo.setAthleteCount(0);
|
||||||
|
vo.setProjectCount(0);
|
||||||
|
vo.setSingleProjectCount(0);
|
||||||
|
vo.setTeamProjectCount(0);
|
||||||
|
vo.setMaleCount(0);
|
||||||
|
vo.setFemaleCount(0);
|
||||||
|
vo.setTotalAmount(BigDecimal.ZERO);
|
||||||
|
vo.setProjectAmounts(new ArrayList<>());
|
||||||
|
return vo;
|
||||||
|
});
|
||||||
|
|
||||||
|
MartialProject project = projectMap.get(athlete.getProjectId());
|
||||||
|
if (project == null) continue;
|
||||||
|
|
||||||
|
// Check if project already counted for this org
|
||||||
|
boolean projectExists = stats.getProjectAmounts().stream()
|
||||||
|
.anyMatch(pa -> pa.getProjectId().equals(athlete.getProjectId()));
|
||||||
|
|
||||||
|
if (!projectExists) {
|
||||||
|
// Add project amount item
|
||||||
|
OrganizationStatsVO.ProjectAmountItem item = new OrganizationStatsVO.ProjectAmountItem();
|
||||||
|
item.setProjectId(project.getId());
|
||||||
|
item.setProjectName(project.getProjectName());
|
||||||
|
item.setProjectType(project.getType());
|
||||||
|
item.setCount(1);
|
||||||
|
item.setPrice(project.getPrice() != null ? project.getPrice() : BigDecimal.ZERO);
|
||||||
|
item.setAmount(item.getPrice());
|
||||||
|
stats.getProjectAmounts().add(item);
|
||||||
|
|
||||||
|
stats.setProjectCount(stats.getProjectCount() + 1);
|
||||||
|
if (project.getType() != null && project.getType() == 2) {
|
||||||
|
stats.setTeamProjectCount(stats.getTeamProjectCount() + 1);
|
||||||
|
} else {
|
||||||
|
stats.setSingleProjectCount(stats.getSingleProjectCount() + 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Update count for existing project
|
||||||
|
stats.getProjectAmounts().stream()
|
||||||
|
.filter(pa -> pa.getProjectId().equals(athlete.getProjectId()))
|
||||||
|
.findFirst()
|
||||||
|
.ifPresent(pa -> {
|
||||||
|
pa.setCount(pa.getCount() + 1);
|
||||||
|
pa.setAmount(pa.getPrice().multiply(BigDecimal.valueOf(pa.getCount())));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Calculate unique athletes and gender counts per organization
|
||||||
|
for (Map.Entry<String, OrganizationStatsVO> entry : orgStatsMap.entrySet()) {
|
||||||
|
String org = entry.getKey();
|
||||||
|
OrganizationStatsVO stats = entry.getValue();
|
||||||
|
|
||||||
|
// Get all athletes for this org
|
||||||
|
Set<String> uniqueIdCards = new HashSet<>();
|
||||||
|
int maleCount = 0;
|
||||||
|
int femaleCount = 0;
|
||||||
|
|
||||||
|
for (MartialAthlete athlete : athletes) {
|
||||||
|
String athleteOrg = athlete.getOrganization();
|
||||||
|
if (athleteOrg == null || athleteOrg.isEmpty()) athleteOrg = "未知单位";
|
||||||
|
if (!athleteOrg.equals(org)) continue;
|
||||||
|
|
||||||
|
MartialProject project = projectMap.get(athlete.getProjectId());
|
||||||
|
if (project == null) continue;
|
||||||
|
|
||||||
|
// For individual projects, count the athlete
|
||||||
|
if (project.getType() == null || project.getType() == 1) {
|
||||||
|
String idCard = athlete.getIdCard();
|
||||||
|
if (idCard != null && !idCard.isEmpty() && !uniqueIdCards.contains(idCard)) {
|
||||||
|
uniqueIdCards.add(idCard);
|
||||||
|
if (athlete.getGender() != null && athlete.getGender() == 1) {
|
||||||
|
maleCount++;
|
||||||
|
} else if (athlete.getGender() != null && athlete.getGender() == 2) {
|
||||||
|
femaleCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For team projects, count team members
|
||||||
|
String teamName = athlete.getTeamName();
|
||||||
|
if (teamName != null) {
|
||||||
|
LambdaQueryWrapper<MartialTeam> teamWrapper = new LambdaQueryWrapper<>();
|
||||||
|
teamWrapper.eq(MartialTeam::getTeamName, teamName)
|
||||||
|
.eq(MartialTeam::getIsDeleted, 0)
|
||||||
|
.last("LIMIT 1");
|
||||||
|
MartialTeam team = teamService.getOne(teamWrapper, false);
|
||||||
|
if (team != null && teamMembersMap.containsKey(team.getId())) {
|
||||||
|
for (MartialTeamMember member : teamMembersMap.get(team.getId())) {
|
||||||
|
MartialAthlete memberAthlete = athleteService.getById(member.getAthleteId());
|
||||||
|
if (memberAthlete != null) {
|
||||||
|
String idCard = memberAthlete.getIdCard();
|
||||||
|
if (idCard != null && !idCard.isEmpty() && !uniqueIdCards.contains(idCard)) {
|
||||||
|
uniqueIdCards.add(idCard);
|
||||||
|
if (memberAthlete.getGender() != null && memberAthlete.getGender() == 1) {
|
||||||
|
maleCount++;
|
||||||
|
} else if (memberAthlete.getGender() != null && memberAthlete.getGender() == 2) {
|
||||||
|
femaleCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stats.setAthleteCount(uniqueIdCards.size());
|
||||||
|
stats.setMaleCount(maleCount);
|
||||||
|
stats.setFemaleCount(femaleCount);
|
||||||
|
|
||||||
|
// Calculate total amount
|
||||||
|
BigDecimal totalAmount = stats.getProjectAmounts().stream()
|
||||||
|
.map(OrganizationStatsVO.ProjectAmountItem::getAmount)
|
||||||
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
stats.setTotalAmount(totalAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return R.data(new ArrayList<>(orgStatsMap.values()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/submit")
|
||||||
|
@Operation(summary = "提交报名", description = "提交报名订单并关联选手或集体")
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public R submit(@RequestBody RegistrationSubmitDTO dto) {
|
||||||
|
log.info("=== 提交报名订单 ===");
|
||||||
|
log.info("订单号: {}", dto.getOrderNo());
|
||||||
|
log.info("赛事ID: {}", dto.getCompetitionId());
|
||||||
|
log.info("项目IDs: {}", dto.getProjectIds());
|
||||||
|
log.info("选手IDs: {}", dto.getAthleteIds());
|
||||||
|
log.info("集体IDs: {}", dto.getTeamIds());
|
||||||
|
log.info("联系电话: {}", dto.getContactPhone());
|
||||||
|
log.info("总金额: {}", dto.getTotalAmount());
|
||||||
|
|
||||||
|
// Validate competition exists and check registration/competition time
|
||||||
|
MartialCompetition competition = competitionService.getById(dto.getCompetitionId());
|
||||||
|
if (competition == null) {
|
||||||
|
return R.fail("赛事不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
|
||||||
|
// Check if registration time is valid
|
||||||
|
if (competition.getRegistrationStartTime() != null && now.isBefore(competition.getRegistrationStartTime())) {
|
||||||
|
return R.fail("报名尚未开始");
|
||||||
|
}
|
||||||
|
if (competition.getRegistrationEndTime() != null && now.isAfter(competition.getRegistrationEndTime())) {
|
||||||
|
return R.fail("报名已结束");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if competition has ended
|
||||||
|
if (competition.getCompetitionEndTime() != null && now.isAfter(competition.getCompetitionEndTime())) {
|
||||||
|
return R.fail("比赛已结束,无法报名");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create order entity
|
||||||
|
MartialRegistrationOrder order = new MartialRegistrationOrder();
|
||||||
|
order.setOrderNo(dto.getOrderNo());
|
||||||
|
order.setCompetitionId(dto.getCompetitionId());
|
||||||
|
order.setContactPhone(dto.getContactPhone());
|
||||||
|
order.setTotalAmount(dto.getTotalAmount());
|
||||||
|
order.setUserId(AuthUtil.getUserId());
|
||||||
|
order.setUserName(AuthUtil.getUserName());
|
||||||
|
|
||||||
|
// Parse IDs
|
||||||
|
List<Long> athleteIds = Func.toLongList(dto.getAthleteIds());
|
||||||
|
List<Long> teamIds = Func.toLongList(dto.getTeamIds());
|
||||||
|
List<Long> projectIds = Func.toLongList(dto.getProjectIds());
|
||||||
|
|
||||||
|
// Determine if this is a team registration
|
||||||
|
boolean isTeamRegistration = !teamIds.isEmpty();
|
||||||
|
|
||||||
|
if (isTeamRegistration) {
|
||||||
|
order.setTotalParticipants(teamIds.size());
|
||||||
|
} else {
|
||||||
|
order.setTotalParticipants(athleteIds.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save order
|
||||||
|
boolean saved = registrationOrderService.save(order);
|
||||||
|
if (!saved) {
|
||||||
|
return R.fail("创建订单失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
Long orderId = order.getId();
|
||||||
|
log.info("订单创建成功,订单ID: {}", orderId);
|
||||||
|
|
||||||
|
if (isTeamRegistration) {
|
||||||
|
// Handle team registration - create record for each team and each project
|
||||||
|
for (Long teamId : teamIds) {
|
||||||
|
MartialTeam team = teamService.getById(teamId);
|
||||||
|
if (team == null) {
|
||||||
|
log.warn("集体不存在: {}", teamId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a record for each project
|
||||||
|
for (Long projectId : projectIds) {
|
||||||
|
MartialAthlete teamAthlete = new MartialAthlete();
|
||||||
|
teamAthlete.setOrderId(orderId);
|
||||||
|
teamAthlete.setCompetitionId(dto.getCompetitionId());
|
||||||
|
teamAthlete.setProjectId(projectId);
|
||||||
|
teamAthlete.setTeamName(team.getTeamName());
|
||||||
|
teamAthlete.setPlayerName(team.getTeamName());
|
||||||
|
teamAthlete.setOrganization(team.getTeamName());
|
||||||
|
teamAthlete.setRegistrationStatus(1);
|
||||||
|
teamAthlete.setCompetitionStatus(0);
|
||||||
|
teamAthlete.setCreateUser(AuthUtil.getUserId());
|
||||||
|
teamAthlete.setCreateTime(new java.util.Date());
|
||||||
|
teamAthlete.setTenantId("000000");
|
||||||
|
teamAthlete.setIsDeleted(0);
|
||||||
|
teamAthlete.setStatus(1);
|
||||||
|
|
||||||
|
athleteService.save(teamAthlete);
|
||||||
|
log.info("创建集体参赛记录: teamName={}, projectId={}", team.getTeamName(), projectId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Handle individual registration - create record for each athlete and each project
|
||||||
|
for (Long athleteId : athleteIds) {
|
||||||
|
MartialAthlete existingAthlete = athleteService.getById(athleteId);
|
||||||
|
if (existingAthlete == null) {
|
||||||
|
log.warn("选手不存在: {}", athleteId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a record for each project
|
||||||
|
for (Long projectId : projectIds) {
|
||||||
|
// Check if record already exists for this athlete + competition + project
|
||||||
|
LambdaQueryWrapper<MartialAthlete> checkWrapper = new LambdaQueryWrapper<>();
|
||||||
|
checkWrapper.eq(MartialAthlete::getCompetitionId, dto.getCompetitionId())
|
||||||
|
.eq(MartialAthlete::getProjectId, projectId)
|
||||||
|
.eq(MartialAthlete::getIdCard, existingAthlete.getIdCard())
|
||||||
|
.eq(MartialAthlete::getIsDeleted, 0);
|
||||||
|
MartialAthlete existingRecord = athleteService.getOne(checkWrapper, false);
|
||||||
|
|
||||||
|
if (existingRecord != null) {
|
||||||
|
// Update existing record with order info
|
||||||
|
existingRecord.setOrderId(orderId);
|
||||||
|
existingRecord.setRegistrationStatus(1);
|
||||||
|
existingRecord.setUpdateUser(AuthUtil.getUserId());
|
||||||
|
existingRecord.setUpdateTime(new java.util.Date());
|
||||||
|
athleteService.updateById(existingRecord);
|
||||||
|
log.info("更新已存在的选手参赛记录: playerName={}, projectId={}", existingAthlete.getPlayerName(), projectId);
|
||||||
|
} else {
|
||||||
|
// Create new record
|
||||||
|
MartialAthlete newRecord = new MartialAthlete();
|
||||||
|
newRecord.setOrderId(orderId);
|
||||||
|
newRecord.setCompetitionId(dto.getCompetitionId());
|
||||||
|
newRecord.setProjectId(projectId);
|
||||||
|
newRecord.setPlayerName(existingAthlete.getPlayerName());
|
||||||
|
newRecord.setGender(existingAthlete.getGender());
|
||||||
|
newRecord.setIdCard(existingAthlete.getIdCard());
|
||||||
|
newRecord.setIdCardType(existingAthlete.getIdCardType());
|
||||||
|
newRecord.setBirthDate(existingAthlete.getBirthDate());
|
||||||
|
newRecord.setAge(existingAthlete.getAge());
|
||||||
|
newRecord.setContactPhone(existingAthlete.getContactPhone());
|
||||||
|
newRecord.setOrganization(existingAthlete.getOrganization());
|
||||||
|
newRecord.setTeamName(existingAthlete.getTeamName());
|
||||||
|
newRecord.setRegistrationStatus(1);
|
||||||
|
newRecord.setCompetitionStatus(0);
|
||||||
|
newRecord.setCreateUser(AuthUtil.getUserId());
|
||||||
|
newRecord.setCreateTime(new java.util.Date());
|
||||||
|
newRecord.setTenantId("000000");
|
||||||
|
newRecord.setIsDeleted(0);
|
||||||
|
newRecord.setStatus(1);
|
||||||
|
|
||||||
|
athleteService.save(newRecord);
|
||||||
|
log.info("创建选手参赛记录: playerName={}, projectId={}", existingAthlete.getPlayerName(), projectId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return R.data(order);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除
|
|
||||||
*/
|
|
||||||
@PostMapping("/remove")
|
@PostMapping("/remove")
|
||||||
@Operation(summary = "删除", description = "传入ID")
|
@Operation(summary = "删除", description = "传入ID")
|
||||||
public R remove(@RequestParam String ids) {
|
public R remove(@RequestParam String ids) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import org.springblade.core.boot.ctrl.BladeController;
|
|||||||
import org.springblade.core.secure.BladeUser;
|
import org.springblade.core.secure.BladeUser;
|
||||||
import org.springblade.core.secure.utils.AuthUtil;
|
import org.springblade.core.secure.utils.AuthUtil;
|
||||||
import org.springblade.core.tool.api.R;
|
import org.springblade.core.tool.api.R;
|
||||||
|
import org.springblade.modules.martial.config.ScheduleConfig;
|
||||||
import org.springblade.modules.martial.pojo.dto.MoveScheduleGroupDTO;
|
import org.springblade.modules.martial.pojo.dto.MoveScheduleGroupDTO;
|
||||||
import org.springblade.modules.martial.pojo.dto.SaveScheduleDraftDTO;
|
import org.springblade.modules.martial.pojo.dto.SaveScheduleDraftDTO;
|
||||||
import org.springblade.modules.martial.pojo.dto.ScheduleResultDTO;
|
import org.springblade.modules.martial.pojo.dto.ScheduleResultDTO;
|
||||||
@@ -15,6 +16,7 @@ import org.springblade.modules.martial.service.IMartialScheduleArrangeService;
|
|||||||
import org.springblade.modules.martial.service.IMartialScheduleService;
|
import org.springblade.modules.martial.service.IMartialScheduleService;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,6 +33,24 @@ public class MartialScheduleArrangeController extends BladeController {
|
|||||||
|
|
||||||
private final IMartialScheduleArrangeService scheduleArrangeService;
|
private final IMartialScheduleArrangeService scheduleArrangeService;
|
||||||
private final IMartialScheduleService scheduleService;
|
private final IMartialScheduleService scheduleService;
|
||||||
|
private final ScheduleConfig scheduleConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取赛程配置
|
||||||
|
*/
|
||||||
|
@GetMapping("/config")
|
||||||
|
@Operation(summary = "获取赛程配置", description = "获取赛程编排的时间配置")
|
||||||
|
public R<Map<String, Object>> getScheduleConfig() {
|
||||||
|
Map<String, Object> config = new HashMap<>();
|
||||||
|
config.put("morningStartTime", scheduleConfig.getMorningStartTime());
|
||||||
|
config.put("morningEndTime", scheduleConfig.getMorningEndTime());
|
||||||
|
config.put("afternoonStartTime", scheduleConfig.getAfternoonStartTime());
|
||||||
|
config.put("afternoonEndTime", scheduleConfig.getAfternoonEndTime());
|
||||||
|
config.put("maxPeoplePerGroup", scheduleConfig.getMaxPeoplePerGroup());
|
||||||
|
config.put("targetPeoplePerGroup", scheduleConfig.getTargetPeoplePerGroup());
|
||||||
|
config.put("defaultDurationPerPerson", scheduleConfig.getDefaultDurationPerPerson());
|
||||||
|
return R.data(config);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取编排结果
|
* 获取编排结果
|
||||||
@@ -69,13 +89,11 @@ public class MartialScheduleArrangeController extends BladeController {
|
|||||||
@Operation(summary = "完成编排并锁定", description = "传入赛事ID")
|
@Operation(summary = "完成编排并锁定", description = "传入赛事ID")
|
||||||
public R saveAndLock(@RequestBody SaveScheduleDraftDTO dto) {
|
public R saveAndLock(@RequestBody SaveScheduleDraftDTO dto) {
|
||||||
try {
|
try {
|
||||||
// 获取当前登录用户
|
|
||||||
BladeUser user = AuthUtil.getUser();
|
BladeUser user = AuthUtil.getUser();
|
||||||
String userId = user != null ? user.getUserName() : "system";
|
String userId = user != null ? user.getUserName() : "system";
|
||||||
|
|
||||||
boolean success = scheduleService.saveAndLockSchedule(dto.getCompetitionId());
|
boolean success = scheduleService.saveAndLockSchedule(dto.getCompetitionId());
|
||||||
if (success) {
|
if (success) {
|
||||||
// 调用原有的锁定逻辑
|
|
||||||
scheduleArrangeService.saveAndLock(dto.getCompetitionId(), userId);
|
scheduleArrangeService.saveAndLock(dto.getCompetitionId(), userId);
|
||||||
return R.success("编排已完成并锁定");
|
return R.success("编排已完成并锁定");
|
||||||
} else {
|
} else {
|
||||||
@@ -118,4 +136,70 @@ public class MartialScheduleArrangeController extends BladeController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取调度数据
|
||||||
|
*/
|
||||||
|
@GetMapping("/dispatch-data")
|
||||||
|
@Operation(summary = "获取调度数据", description = "获取指定场地和时间段的调度数据")
|
||||||
|
public R<org.springblade.modules.martial.pojo.vo.DispatchDataVO> getDispatchData(
|
||||||
|
@RequestParam Long competitionId,
|
||||||
|
@RequestParam Long venueId,
|
||||||
|
@RequestParam Integer timeSlotIndex) {
|
||||||
|
try {
|
||||||
|
org.springblade.modules.martial.pojo.vo.DispatchDataVO data =
|
||||||
|
scheduleService.getDispatchData(competitionId, venueId, timeSlotIndex);
|
||||||
|
return R.data(data);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取调度数据失败", e);
|
||||||
|
return R.fail("获取调度数据失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调整出场顺序
|
||||||
|
*/
|
||||||
|
@PostMapping("/adjust-order")
|
||||||
|
@Operation(summary = "调整出场顺序", description = "调整参赛者的出场顺序")
|
||||||
|
public R adjustOrder(@RequestBody org.springblade.modules.martial.pojo.dto.AdjustOrderDTO dto) {
|
||||||
|
try {
|
||||||
|
boolean success = scheduleService.adjustOrder(dto);
|
||||||
|
return success ? R.success("顺序调整成功") : R.fail("顺序调整失败");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("调整顺序失败", e);
|
||||||
|
return R.fail("调整顺序失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存调度
|
||||||
|
*/
|
||||||
|
@PostMapping("/save-dispatch")
|
||||||
|
@Operation(summary = "批量保存调度", description = "批量保存调度调整")
|
||||||
|
public R saveDispatch(@RequestBody org.springblade.modules.martial.pojo.dto.SaveDispatchDTO dto) {
|
||||||
|
try {
|
||||||
|
boolean success = scheduleService.saveDispatch(dto);
|
||||||
|
return success ? R.success("调度保存成功") : R.fail("调度保存失败");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("保存调度失败", e);
|
||||||
|
return R.fail("保存调度失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新参赛者签到状态
|
||||||
|
*/
|
||||||
|
@PostMapping("/update-check-in-status")
|
||||||
|
@Operation(summary = "更新签到状态", description = "更新参赛者签到状态:未签到/已签到/异常")
|
||||||
|
public R updateCheckInStatus(@RequestBody java.util.Map<String, Object> params) {
|
||||||
|
try {
|
||||||
|
Long participantId = Long.valueOf(String.valueOf(params.get("participantId")));
|
||||||
|
String status = String.valueOf(params.get("status"));
|
||||||
|
boolean success = scheduleService.updateParticipantCheckInStatus(participantId, status);
|
||||||
|
return success ? R.success("状态更新成功") : R.fail("状态更新失败");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("更新签到状态失败", e);
|
||||||
|
return R.fail("更新签到状态失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import org.springblade.core.mp.support.Query;
|
|||||||
import org.springblade.core.tool.api.R;
|
import org.springblade.core.tool.api.R;
|
||||||
import org.springblade.core.tool.utils.Func;
|
import org.springblade.core.tool.utils.Func;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialScore;
|
import org.springblade.modules.martial.pojo.entity.MartialScore;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.MartialScoreVO;
|
||||||
import org.springblade.modules.martial.service.IMartialScoreService;
|
import org.springblade.modules.martial.service.IMartialScoreService;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
@@ -43,8 +44,8 @@ public class MartialScoreController extends BladeController {
|
|||||||
*/
|
*/
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@Operation(summary = "分页列表", description = "分页查询")
|
@Operation(summary = "分页列表", description = "分页查询")
|
||||||
public R<IPage<MartialScore>> list(MartialScore score, Query query) {
|
public R<IPage<MartialScoreVO>> list(MartialScore score, Query query) {
|
||||||
IPage<MartialScore> pages = scoreService.page(Condition.getPage(query), Condition.getQueryWrapper(score));
|
IPage<MartialScoreVO> pages = scoreService.selectScoreVOPage(Condition.getPage(query), score);
|
||||||
return R.data(pages);
|
return R.data(pages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package org.springblade.modules.martial.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springblade.core.boot.ctrl.BladeController;
|
||||||
|
import org.springblade.core.mp.support.Query;
|
||||||
|
import org.springblade.core.secure.utils.AuthUtil;
|
||||||
|
import org.springblade.core.tool.api.R;
|
||||||
|
import org.springblade.core.tool.utils.StringUtil;
|
||||||
|
import org.springblade.modules.martial.pojo.dto.TeamSubmitDTO;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MartialTeam;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.MartialTeamVO;
|
||||||
|
import org.springblade.modules.martial.service.IMartialTeamService;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@AllArgsConstructor
|
||||||
|
@RequestMapping("/martial/team")
|
||||||
|
@Tag(name = "集体管理", description = "集体/团队接口")
|
||||||
|
public class MartialTeamController extends BladeController {
|
||||||
|
|
||||||
|
private final IMartialTeamService teamService;
|
||||||
|
|
||||||
|
@GetMapping("/list")
|
||||||
|
@Operation(summary = "分页列表", description = "获取当前用户的集体列表")
|
||||||
|
public R<IPage<MartialTeamVO>> list(Query query) {
|
||||||
|
Long userId = AuthUtil.getUserId();
|
||||||
|
IPage<MartialTeamVO> pages = teamService.getTeamList(userId, query.getCurrent(), query.getSize());
|
||||||
|
return R.data(pages);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/detail")
|
||||||
|
@Operation(summary = "详情", description = "获取集体详情")
|
||||||
|
public R<MartialTeamVO> detail(@RequestParam Long id) {
|
||||||
|
return R.data(teamService.getTeamDetail(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/submit")
|
||||||
|
@Operation(summary = "保存", description = "新增或修改集体")
|
||||||
|
public R<Boolean> submit(@RequestBody TeamSubmitDTO dto) {
|
||||||
|
log.info("Team submit - teamId: {}, teamName: {}, memberIds: {}", dto.getTeamId(), dto.getTeamName(), dto.getMemberIds());
|
||||||
|
|
||||||
|
MartialTeam team = new MartialTeam();
|
||||||
|
team.setTeamName(dto.getTeamName());
|
||||||
|
team.setRemark(dto.getRemark());
|
||||||
|
|
||||||
|
boolean result;
|
||||||
|
if (StringUtil.isNotBlank(dto.getTeamId())) {
|
||||||
|
Long teamId = Long.parseLong(dto.getTeamId());
|
||||||
|
team.setId(teamId);
|
||||||
|
log.info("Updating team with id: {}", teamId);
|
||||||
|
result = teamService.updateTeamWithMembers(team, dto.getMemberIds());
|
||||||
|
} else {
|
||||||
|
log.info("Creating new team");
|
||||||
|
result = teamService.saveTeamWithMembers(team, dto.getMemberIds());
|
||||||
|
}
|
||||||
|
return R.data(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/remove")
|
||||||
|
@Operation(summary = "删除", description = "删除集体")
|
||||||
|
public R<Boolean> remove(@RequestParam Long id) {
|
||||||
|
return R.data(teamService.removeTeamWithMembers(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package org.springblade.modules.martial.excel;
|
||||||
|
|
||||||
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
|
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||||
|
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
|
||||||
|
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule export Excel - Template 2 (Competition Schedule Format)
|
||||||
|
* Format: Sequence, Project, Participants, Groups, Time, Table Number
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ColumnWidth(12)
|
||||||
|
@HeadRowHeight(25)
|
||||||
|
@ContentRowHeight(20)
|
||||||
|
public class ScheduleExportExcel2 implements Serializable {
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ExcelProperty("序号")
|
||||||
|
@ColumnWidth(8)
|
||||||
|
private Integer sequenceNo;
|
||||||
|
|
||||||
|
@ExcelProperty("项目")
|
||||||
|
@ColumnWidth(25)
|
||||||
|
private String projectName;
|
||||||
|
|
||||||
|
@ExcelProperty("人数")
|
||||||
|
@ColumnWidth(8)
|
||||||
|
private Integer participantCount;
|
||||||
|
|
||||||
|
@ExcelProperty("组数")
|
||||||
|
@ColumnWidth(8)
|
||||||
|
private Integer groupCount;
|
||||||
|
|
||||||
|
@ExcelProperty("时间")
|
||||||
|
@ColumnWidth(10)
|
||||||
|
private Integer durationMinutes;
|
||||||
|
|
||||||
|
@ExcelProperty("表号")
|
||||||
|
@ColumnWidth(10)
|
||||||
|
private String tableNo;
|
||||||
|
}
|
||||||
@@ -22,4 +22,10 @@ public interface MartialAthleteMapper extends BaseMapper<MartialAthlete> {
|
|||||||
*/
|
*/
|
||||||
IPage<MartialAthleteVO> selectAthleteVOPage(IPage<MartialAthleteVO> page, @Param("athlete") MartialAthlete athlete);
|
IPage<MartialAthleteVO> selectAthleteVOPage(IPage<MartialAthleteVO> page, @Param("athlete") MartialAthlete athlete);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count distinct participants by id_card for a competition
|
||||||
|
*/
|
||||||
|
@org.apache.ibatis.annotations.Select("SELECT COUNT(DISTINCT id_card) FROM martial_athlete WHERE competition_id = #{competitionId} AND is_deleted = 0")
|
||||||
|
Long countDistinctParticipants(@Param("competitionId") Long competitionId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
LEFT JOIN martial_competition c ON a.competition_id = c.id AND c.is_deleted = 0
|
LEFT JOIN martial_competition c ON a.competition_id = c.id AND c.is_deleted = 0
|
||||||
LEFT JOIN martial_project p ON a.project_id = p.id AND p.is_deleted = 0
|
LEFT JOIN martial_project p ON a.project_id = p.id AND p.is_deleted = 0
|
||||||
WHERE a.is_deleted = 0
|
WHERE a.is_deleted = 0
|
||||||
|
AND (a.team_name IS NULL OR a.player_name != a.team_name)
|
||||||
<if test="athlete.competitionId != null">
|
<if test="athlete.competitionId != null">
|
||||||
AND a.competition_id = #{athlete.competitionId}
|
AND a.competition_id = #{athlete.competitionId}
|
||||||
</if>
|
</if>
|
||||||
@@ -39,6 +40,9 @@
|
|||||||
<if test="athlete.competitionStatus != null">
|
<if test="athlete.competitionStatus != null">
|
||||||
AND a.competition_status = #{athlete.competitionStatus}
|
AND a.competition_status = #{athlete.competitionStatus}
|
||||||
</if>
|
</if>
|
||||||
|
<if test="athlete.createUser != null">
|
||||||
|
AND a.create_user = #{athlete.createUser}
|
||||||
|
</if>
|
||||||
ORDER BY a.create_time DESC
|
ORDER BY a.create_time DESC
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of the dreamlu.net developer nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from
|
||||||
|
* this software without specific prior written permission.
|
||||||
|
* Author: Chill 庄骞 (smallchill@163.com)
|
||||||
|
*/
|
||||||
|
package org.springblade.modules.martial.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MartialCompetitionAttachment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 赛事附件 Mapper 接口
|
||||||
|
*
|
||||||
|
* @author BladeX
|
||||||
|
*/
|
||||||
|
public interface MartialCompetitionAttachmentMapper extends BaseMapper<MartialCompetitionAttachment> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package org.springblade.modules.martial.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MartialContact;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contact Mapper
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface MartialContactMapper extends BaseMapper<MartialContact> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -26,12 +26,8 @@
|
|||||||
<result column="contact_email" property="contactEmail"/>
|
<result column="contact_email" property="contactEmail"/>
|
||||||
<result column="invite_message" property="inviteMessage"/>
|
<result column="invite_message" property="inviteMessage"/>
|
||||||
<result column="cancel_reason" property="cancelReason"/>
|
<result column="cancel_reason" property="cancelReason"/>
|
||||||
<!-- 关联的裁判信息 -->
|
|
||||||
<result column="judge_name" property="judgeName"/>
|
|
||||||
<result column="judge_level" property="judgeLevel"/>
|
|
||||||
<!-- 关联的赛事信息 -->
|
|
||||||
<result column="competition_name" property="competitionName"/>
|
<result column="competition_name" property="competitionName"/>
|
||||||
<!-- 基础字段 -->
|
<result column="venue_name" property="venueName"/>
|
||||||
<result column="create_user" property="createUser"/>
|
<result column="create_user" property="createUser"/>
|
||||||
<result column="create_dept" property="createDept"/>
|
<result column="create_dept" property="createDept"/>
|
||||||
<result column="create_time" property="createTime"/>
|
<result column="create_time" property="createTime"/>
|
||||||
@@ -73,27 +69,26 @@
|
|||||||
ji.update_time,
|
ji.update_time,
|
||||||
ji.status,
|
ji.status,
|
||||||
ji.is_deleted,
|
ji.is_deleted,
|
||||||
|
ji.referee_type,
|
||||||
j.name AS judge_name,
|
j.name AS judge_name,
|
||||||
j.level AS judge_level,
|
j.level AS judge_level,
|
||||||
c.competition_name
|
c.competition_name,
|
||||||
|
v.venue_name
|
||||||
FROM
|
FROM
|
||||||
martial_judge_invite ji
|
martial_judge_invite ji
|
||||||
LEFT JOIN martial_judge j ON ji.judge_id = j.id
|
LEFT JOIN martial_judge j ON ji.judge_id = j.id
|
||||||
LEFT JOIN martial_competition c ON ji.competition_id = c.id
|
LEFT JOIN martial_competition c ON ji.competition_id = c.id
|
||||||
WHERE
|
LEFT JOIN martial_venue v ON ji.venue_id = v.id
|
||||||
ji.is_deleted = 0
|
WHERE ji.is_deleted = 0
|
||||||
<if test="judgeInvite.competitionId != null">
|
<if test="judgeInvite.competitionId != null">
|
||||||
AND ji.competition_id = #{judgeInvite.competitionId}
|
AND ji.competition_id = #{judgeInvite.competitionId}
|
||||||
</if>
|
</if>
|
||||||
<if test="judgeInvite.inviteStatus != null">
|
<if test="judgeInvite.inviteStatus != null">
|
||||||
AND ji.invite_status = #{judgeInvite.inviteStatus}
|
AND ji.invite_status = #{judgeInvite.inviteStatus}
|
||||||
</if>
|
</if>
|
||||||
<if test="judgeInvite.judgeName != null and judgeInvite.judgeName != ''">
|
<if test="judgeInvite.venueId != null">
|
||||||
AND j.name LIKE CONCAT('%', #{judgeInvite.judgeName}, '%')
|
AND ji.venue_id = #{judgeInvite.venueId}
|
||||||
</if>
|
</if>
|
||||||
<if test="judgeInvite.judgeLevel != null and judgeInvite.judgeLevel != ''">
|
|
||||||
AND j.level = #{judgeInvite.judgeLevel}
|
|
||||||
</if>
|
|
||||||
ORDER BY ji.create_time DESC
|
ORDER BY ji.create_time DESC
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,13 @@
|
|||||||
d.venue_name AS venueName,
|
d.venue_name AS venueName,
|
||||||
d.time_slot AS timeSlot,
|
d.time_slot AS timeSlot,
|
||||||
d.time_slot_index AS timeSlotIndex,
|
d.time_slot_index AS timeSlotIndex,
|
||||||
|
d.schedule_date AS scheduleDate,
|
||||||
p.id AS participantId,
|
p.id AS participantId,
|
||||||
p.organization AS organization,
|
p.organization AS organization,
|
||||||
p.check_in_status AS checkInStatus,
|
p.check_in_status AS checkInStatus,
|
||||||
p.schedule_status AS scheduleStatus,
|
p.schedule_status AS scheduleStatus,
|
||||||
p.performance_order AS performanceOrder
|
p.performance_order AS performanceOrder,
|
||||||
|
p.player_name AS playerName
|
||||||
FROM
|
FROM
|
||||||
martial_schedule_group g
|
martial_schedule_group g
|
||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package org.springblade.modules.martial.mapper;
|
package org.springblade.modules.martial.mapper;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialScore;
|
import org.springblade.modules.martial.pojo.entity.MartialScore;
|
||||||
|
import org.springblade.modules.martial.pojo.vo.MartialScoreVO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Score Mapper 接口
|
* Score Mapper 接口
|
||||||
@@ -10,4 +13,6 @@ import org.springblade.modules.martial.pojo.entity.MartialScore;
|
|||||||
*/
|
*/
|
||||||
public interface MartialScoreMapper extends BaseMapper<MartialScore> {
|
public interface MartialScoreMapper extends BaseMapper<MartialScore> {
|
||||||
|
|
||||||
|
IPage<MartialScoreVO> selectScoreVOPage(IPage<MartialScoreVO> page, @Param("score") MartialScore score);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,4 +2,43 @@
|
|||||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
<mapper namespace="org.springblade.modules.martial.mapper.MartialScoreMapper">
|
<mapper namespace="org.springblade.modules.martial.mapper.MartialScoreMapper">
|
||||||
|
|
||||||
|
<select id="selectScoreVOPage" resultType="org.springblade.modules.martial.pojo.vo.MartialScoreVO">
|
||||||
|
SELECT
|
||||||
|
s.*,
|
||||||
|
a.player_name as playerName,
|
||||||
|
a.team_name as teamName,
|
||||||
|
a.id_card as idCard,
|
||||||
|
a.player_no as playerNo,
|
||||||
|
p.project_name as projectName,
|
||||||
|
v.venue_name as venueName,
|
||||||
|
r.chief_judge_score as chiefJudgeScore,
|
||||||
|
r.score_status as scoreStatus,
|
||||||
|
(SELECT GROUP_CONCAT(d.item_name SEPARATOR ', ')
|
||||||
|
FROM martial_deduction_item d
|
||||||
|
WHERE FIND_IN_SET(d.id, REPLACE(REPLACE(s.deduction_items, '[', ''), ']', ''))
|
||||||
|
) as deductionItemsText
|
||||||
|
FROM martial_score s
|
||||||
|
LEFT JOIN martial_athlete a ON s.athlete_id = a.id AND a.is_deleted = 0
|
||||||
|
LEFT JOIN martial_project p ON s.project_id = p.id AND p.is_deleted = 0
|
||||||
|
LEFT JOIN martial_venue v ON s.venue_id = v.id AND v.is_deleted = 0
|
||||||
|
LEFT JOIN martial_result r ON s.athlete_id = r.athlete_id AND s.project_id = r.project_id AND r.is_deleted = 0
|
||||||
|
WHERE s.is_deleted = 0
|
||||||
|
<if test="score.competitionId != null">
|
||||||
|
AND s.competition_id = #{score.competitionId}
|
||||||
|
</if>
|
||||||
|
<if test="score.athleteId != null">
|
||||||
|
AND s.athlete_id = #{score.athleteId}
|
||||||
|
</if>
|
||||||
|
<if test="score.projectId != null">
|
||||||
|
AND s.project_id = #{score.projectId}
|
||||||
|
</if>
|
||||||
|
<if test="score.judgeId != null">
|
||||||
|
AND s.judge_id = #{score.judgeId}
|
||||||
|
</if>
|
||||||
|
<if test="score.venueId != null">
|
||||||
|
AND s.venue_id = #{score.venueId}
|
||||||
|
</if>
|
||||||
|
ORDER BY s.create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package org.springblade.modules.martial.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MartialTeam;
|
||||||
|
|
||||||
|
public interface MartialTeamMapper extends BaseMapper<MartialTeam> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package org.springblade.modules.martial.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.springblade.modules.martial.pojo.entity.MartialTeamMember;
|
||||||
|
|
||||||
|
public interface MartialTeamMemberMapper extends BaseMapper<MartialTeamMember> {
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user