This commit is contained in:
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. ✅ **用户体验**:提供上移/下移按钮,操作简单直观
|
||||
|
||||
现在可以开始测试调度功能了!🎉
|
||||
Reference in New Issue
Block a user