fix bugs
This commit is contained in:
181
doc/裁判邀请功能使用说明.md
Normal file
181
doc/裁判邀请功能使用说明.md
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
# 裁判邀请功能使用说明
|
||||||
|
|
||||||
|
## 功能概述
|
||||||
|
|
||||||
|
裁判邀请模块用于为武术赛事邀请裁判,通过生成邀请码的方式让裁判登录系统并回复邀请。
|
||||||
|
|
||||||
|
## 完整操作流程
|
||||||
|
|
||||||
|
### 1. 准备工作
|
||||||
|
|
||||||
|
#### 1.1 创建赛事
|
||||||
|
- 进入"赛事管理"模块
|
||||||
|
- 创建新的赛事
|
||||||
|
- 确保赛事状态为"进行中"
|
||||||
|
|
||||||
|
#### 1.2 添加裁判
|
||||||
|
- 进入"评委管理"模块
|
||||||
|
- 点击"新增评委"按钮
|
||||||
|
- 填写裁判基本信息:
|
||||||
|
- 姓名、性别、手机号、身份证号
|
||||||
|
- 裁判类型(主裁判/普通裁判)
|
||||||
|
- 等级/职称
|
||||||
|
- 擅长项目
|
||||||
|
- 保存裁判信息
|
||||||
|
|
||||||
|
### 2. 生成邀请码
|
||||||
|
|
||||||
|
#### 方式一:从评委库导入(推荐)
|
||||||
|
|
||||||
|
这是为新裁判生成邀请码的主要方式。
|
||||||
|
|
||||||
|
**操作步骤:**
|
||||||
|
1. 进入"裁判邀请"页面
|
||||||
|
2. 选择赛事(页面顶部下拉框)
|
||||||
|
3. 点击"从评委库导入"按钮
|
||||||
|
4. 在弹出的对话框中:
|
||||||
|
- 可以搜索裁判(按姓名、手机号、类型)
|
||||||
|
- 勾选需要邀请的裁判(支持多选)
|
||||||
|
- 查看已选择的裁判数量
|
||||||
|
5. 点击"确定导入"按钮
|
||||||
|
6. 系统自动为选中的裁判批量生成邀请码
|
||||||
|
7. 生成成功后,邀请列表会自动刷新
|
||||||
|
|
||||||
|
**特点:**
|
||||||
|
- ✅ 支持批量操作
|
||||||
|
- ✅ 可以搜索和筛选裁判
|
||||||
|
- ✅ 自动生成邀请码
|
||||||
|
- ✅ 适合首次邀请裁判
|
||||||
|
|
||||||
|
#### 方式二:批量生成邀请码
|
||||||
|
|
||||||
|
用于为已有邀请记录但未生成邀请码的裁判批量生成。
|
||||||
|
|
||||||
|
**操作步骤:**
|
||||||
|
1. 在邀请列表中勾选需要生成邀请码的记录
|
||||||
|
2. 点击"批量生成邀请码"按钮
|
||||||
|
3. 确认操作
|
||||||
|
4. 系统为选中的裁判生成邀请码
|
||||||
|
|
||||||
|
**注意:**
|
||||||
|
- ⚠️ 只能为已有邀请记录的裁判生成
|
||||||
|
- ⚠️ 如果是新裁判,请使用"从评委库导入"
|
||||||
|
|
||||||
|
#### 方式三:单个生成邀请码
|
||||||
|
|
||||||
|
用于为单个裁判生成或重新生成邀请码。
|
||||||
|
|
||||||
|
**操作步骤:**
|
||||||
|
1. 在邀请列表中找到目标裁判
|
||||||
|
2. 如果未生成邀请码:点击"生成邀请码"按钮
|
||||||
|
3. 如果已有邀请码:点击邀请码旁边的刷新图标"重新生成"
|
||||||
|
4. 邀请码会自动复制到剪贴板
|
||||||
|
|
||||||
|
### 3. 发送邀请
|
||||||
|
|
||||||
|
生成邀请码后,需要将邀请码发送给裁判:
|
||||||
|
|
||||||
|
**发送方式:**
|
||||||
|
- 📧 邮件:将邀请码通过邮件发送
|
||||||
|
- 📱 短信:将邀请码通过短信发送
|
||||||
|
- 💬 微信/其他:通过即时通讯工具发送
|
||||||
|
|
||||||
|
**邀请码使用:**
|
||||||
|
- 裁判收到邀请码后,访问系统登录页面
|
||||||
|
- 输入邀请码进行登录
|
||||||
|
- 查看赛事信息并回复邀请(接受/拒绝)
|
||||||
|
|
||||||
|
### 4. 管理邀请
|
||||||
|
|
||||||
|
#### 4.1 查看邀请状态
|
||||||
|
|
||||||
|
邀请状态说明:
|
||||||
|
- 🟡 **待回复**:已发送邀请,裁判尚未回复
|
||||||
|
- 🟢 **已接受**:裁判已接受邀请
|
||||||
|
- 🔴 **已拒绝**:裁判已拒绝邀请
|
||||||
|
- ⚪ **已取消**:管理员已取消邀请
|
||||||
|
|
||||||
|
#### 4.2 邀请操作
|
||||||
|
|
||||||
|
**对于"待回复"状态的邀请:**
|
||||||
|
- **重发**:重新发送邀请通知
|
||||||
|
- **提醒**:发送提醒消息催促裁判回复
|
||||||
|
- **取消**:取消邀请(需填写取消原因)
|
||||||
|
|
||||||
|
**对于"已接受"状态的邀请:**
|
||||||
|
- **确认**:确认裁判参与(可进行后续的场地、项目分配)
|
||||||
|
|
||||||
|
**所有邀请:**
|
||||||
|
- **查看**:查看邀请详细信息
|
||||||
|
- **复制邀请码**:点击邀请码即可复制
|
||||||
|
|
||||||
|
### 5. 统计信息
|
||||||
|
|
||||||
|
页面顶部显示四个统计卡片:
|
||||||
|
- 📊 **总邀请数**:已发送的邀请总数
|
||||||
|
- ⏰ **待回复**:等待裁判回复的邀请数
|
||||||
|
- ✅ **已接受**:裁判已接受的邀请数
|
||||||
|
- ❌ **已拒绝**:裁判已拒绝的邀请数
|
||||||
|
|
||||||
|
### 6. 导出数据
|
||||||
|
|
||||||
|
点击"导出数据"按钮可以导出当前筛选条件下的邀请列表为Excel文件。
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### Q1: 为什么"批量生成邀请码"按钮是灰色的?
|
||||||
|
**A:** 可能的原因:
|
||||||
|
1. 未选择赛事
|
||||||
|
2. 赛事列表正在加载
|
||||||
|
3. 未勾选任何邀请记录
|
||||||
|
|
||||||
|
### Q2: 如何为新裁判生成邀请码?
|
||||||
|
**A:** 使用"从评委库导入"功能:
|
||||||
|
1. 先在"评委管理"中添加裁判
|
||||||
|
2. 在"裁判邀请"页面点击"从评委库导入"
|
||||||
|
3. 选择裁判并确认导入
|
||||||
|
|
||||||
|
### Q3: 邀请码可以重复使用吗?
|
||||||
|
**A:** 不可以。每个邀请码只能使用一次。如果需要重新邀请,请使用"重新生成"功能。
|
||||||
|
|
||||||
|
### Q4: 邀请码有效期是多久?
|
||||||
|
**A:** 默认有效期为30天。过期后需要重新生成。
|
||||||
|
|
||||||
|
### Q5: 如何知道裁判是否收到邀请?
|
||||||
|
**A:**
|
||||||
|
- 查看邀请状态,如果裁判已登录并回复,状态会更新
|
||||||
|
- 可以使用"提醒"功能发送提醒消息
|
||||||
|
- 建议通过电话或其他方式确认裁判是否收到
|
||||||
|
|
||||||
|
## 技术说明
|
||||||
|
|
||||||
|
### 邀请码生成规则
|
||||||
|
- 每个赛事+裁判组合生成唯一邀请码
|
||||||
|
- 邀请码包含角色信息(主裁判/普通裁判)
|
||||||
|
- 可以预分配场地和项目
|
||||||
|
|
||||||
|
### 数据关联
|
||||||
|
```
|
||||||
|
赛事 (Competition)
|
||||||
|
↓
|
||||||
|
邀请记录 (JudgeInvite)
|
||||||
|
↓
|
||||||
|
裁判 (Judge)
|
||||||
|
```
|
||||||
|
|
||||||
|
### API接口
|
||||||
|
- 生成邀请码:`POST /api/blade-martial/judgeInvite/generate`
|
||||||
|
- 批量生成:`POST /api/blade-martial/judgeInvite/generate/batch`
|
||||||
|
- 重新生成:`PUT /api/blade-martial/judgeInvite/regenerate/{inviteId}`
|
||||||
|
- 邀请列表:`GET /api/blade-martial/judgeInvite/list`
|
||||||
|
- 邀请统计:`GET /api/blade-martial/judgeInvite/statistics`
|
||||||
|
|
||||||
|
## 更新日志
|
||||||
|
|
||||||
|
### 2025-12-13
|
||||||
|
- ✨ 新增"从评委库导入"功能
|
||||||
|
- ✨ 支持裁判搜索和筛选
|
||||||
|
- ✨ 优化邀请码生成流程
|
||||||
|
- 🐛 修复按钮禁用逻辑问题
|
||||||
|
- 🐛 修复赛事选择初始化问题
|
||||||
|
- 💄 优化用户界面和交互体验
|
||||||
260
doc/赛事列表加载问题排查指南.md
Normal file
260
doc/赛事列表加载问题排查指南.md
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
# 赛事列表加载问题排查指南
|
||||||
|
|
||||||
|
## 问题现象
|
||||||
|
|
||||||
|
- 赛事下拉框显示"无数据"
|
||||||
|
- 所有按钮(发送邀请、批量生成邀请码等)无法点击
|
||||||
|
- 页面显示"暂无数据"
|
||||||
|
|
||||||
|
## 排查步骤
|
||||||
|
|
||||||
|
### 1. 检查浏览器控制台
|
||||||
|
|
||||||
|
打开浏览器开发者工具(F12),查看Console标签页:
|
||||||
|
|
||||||
|
**查看日志输出:**
|
||||||
|
```
|
||||||
|
赛事列表API返回: {...}
|
||||||
|
解析后的赛事列表: [...]
|
||||||
|
```
|
||||||
|
|
||||||
|
**可能的错误信息:**
|
||||||
|
- `加载赛事列表失败: Network Error` - 网络连接问题
|
||||||
|
- `加载赛事列表失败: 404` - API路径错误
|
||||||
|
- `加载赛事列表失败: 500` - 后端服务错误
|
||||||
|
|
||||||
|
### 2. 检查Network请求
|
||||||
|
|
||||||
|
在开发者工具的Network标签页中:
|
||||||
|
|
||||||
|
1. 刷新页面
|
||||||
|
2. 找到 `/api/martial/competition/list` 请求
|
||||||
|
3. 查看请求状态:
|
||||||
|
- **200**: 请求成功,检查返回数据
|
||||||
|
- **404**: API路径不存在
|
||||||
|
- **500**: 后端服务错误
|
||||||
|
- **Failed**: 网络连接失败
|
||||||
|
|
||||||
|
4. 查看Response数据结构:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"data": {
|
||||||
|
"records": [...], // 赛事列表
|
||||||
|
"total": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 检查后端服务
|
||||||
|
|
||||||
|
#### 3.1 确认后端服务是否启动
|
||||||
|
|
||||||
|
**Windows:**
|
||||||
|
```bash
|
||||||
|
netstat -ano | findstr 8888
|
||||||
|
```
|
||||||
|
|
||||||
|
**Linux/Mac:**
|
||||||
|
```bash
|
||||||
|
netstat -tuln | grep 8888
|
||||||
|
```
|
||||||
|
|
||||||
|
如果没有输出,说明后端服务未启动。
|
||||||
|
|
||||||
|
#### 3.2 启动后端服务
|
||||||
|
|
||||||
|
进入后端项目目录:
|
||||||
|
```bash
|
||||||
|
cd martial-master
|
||||||
|
mvn spring-boot:run
|
||||||
|
```
|
||||||
|
|
||||||
|
或者使用IDE(IDEA/Eclipse)启动。
|
||||||
|
|
||||||
|
### 4. 检查数据库数据
|
||||||
|
|
||||||
|
#### 4.1 连接数据库
|
||||||
|
|
||||||
|
使用数据库客户端(Navicat、DBeaver等)连接:
|
||||||
|
- Host: 127.0.0.1
|
||||||
|
- Port: 3306
|
||||||
|
- Database: martial_db
|
||||||
|
- Username: root
|
||||||
|
- Password: 123456
|
||||||
|
|
||||||
|
#### 4.2 执行检查SQL
|
||||||
|
|
||||||
|
运行项目根目录下的 `check_data.sql` 文件:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 检查赛事数据
|
||||||
|
SELECT * FROM martial_competition LIMIT 10;
|
||||||
|
|
||||||
|
-- 检查赛事总数
|
||||||
|
SELECT COUNT(*) FROM martial_competition;
|
||||||
|
```
|
||||||
|
|
||||||
|
**如果赛事表为空:**
|
||||||
|
需要先创建赛事数据。
|
||||||
|
|
||||||
|
### 5. 创建测试数据
|
||||||
|
|
||||||
|
#### 5.1 创建赛事
|
||||||
|
|
||||||
|
进入"赛事管理"页面,点击"新增赛事",填写:
|
||||||
|
- 赛事名称:测试赛事
|
||||||
|
- 赛事编码:TEST001
|
||||||
|
- 主办单位:测试单位
|
||||||
|
- 地区:北京
|
||||||
|
- 详细地点:测试地点
|
||||||
|
- 报名时间:选择日期范围
|
||||||
|
- 比赛时间:选择日期范围
|
||||||
|
|
||||||
|
保存后,赛事列表应该就能显示了。
|
||||||
|
|
||||||
|
#### 5.2 创建裁判
|
||||||
|
|
||||||
|
进入"评委管理"页面,点击"新增评委",填写:
|
||||||
|
- 姓名:张三
|
||||||
|
- 性别:男
|
||||||
|
- 手机号:13800138000
|
||||||
|
- 身份证号:110101199001011234
|
||||||
|
- 裁判类型:普通裁判
|
||||||
|
- 等级/职称:一级
|
||||||
|
|
||||||
|
### 6. 检查API配置
|
||||||
|
|
||||||
|
#### 6.1 检查前端API配置
|
||||||
|
|
||||||
|
查看 `martial-web/src/axios/index.js` 或类似文件:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const baseURL = process.env.VUE_APP_API_BASE_URL || '/api'
|
||||||
|
```
|
||||||
|
|
||||||
|
确认API基础路径配置正确。
|
||||||
|
|
||||||
|
#### 6.2 检查代理配置
|
||||||
|
|
||||||
|
查看 `martial-web/vite.config.js` 或 `vue.config.js`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:8888',
|
||||||
|
changeOrigin: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
确认代理配置指向正确的后端地址。
|
||||||
|
|
||||||
|
### 7. 常见问题解决
|
||||||
|
|
||||||
|
#### 问题1: CORS跨域错误
|
||||||
|
|
||||||
|
**错误信息:**
|
||||||
|
```
|
||||||
|
Access to XMLHttpRequest at 'http://localhost:8888/api/...' from origin 'http://localhost:5173' has been blocked by CORS policy
|
||||||
|
```
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
1. 检查后端CORS配置
|
||||||
|
2. 使用代理配置
|
||||||
|
3. 确保前后端端口配置正确
|
||||||
|
|
||||||
|
#### 问题2: 404 Not Found
|
||||||
|
|
||||||
|
**可能原因:**
|
||||||
|
1. API路径错误
|
||||||
|
2. 后端Controller路径配置错误
|
||||||
|
3. 后端服务未启动
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
1. 检查API路径:`/api/martial/competition/list`
|
||||||
|
2. 检查后端Controller注解:`@RequestMapping("/martial/competition")`
|
||||||
|
3. 启动后端服务
|
||||||
|
|
||||||
|
#### 问题3: 数据结构不匹配
|
||||||
|
|
||||||
|
**现象:**
|
||||||
|
API返回数据,但前端解析失败。
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
查看控制台日志,确认数据结构:
|
||||||
|
```javascript
|
||||||
|
console.log('赛事列表API返回:', res)
|
||||||
|
console.log('解析后的赛事列表:', records)
|
||||||
|
```
|
||||||
|
|
||||||
|
如果数据结构不对,修改前端解析逻辑。
|
||||||
|
|
||||||
|
### 8. 快速测试
|
||||||
|
|
||||||
|
#### 8.1 使用Postman测试API
|
||||||
|
|
||||||
|
**请求:**
|
||||||
|
```
|
||||||
|
GET http://localhost:8888/api/martial/competition/list?current=1&size=10
|
||||||
|
```
|
||||||
|
|
||||||
|
**期望返回:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"competitionName": "测试赛事",
|
||||||
|
"status": 1,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 8.2 使用curl测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X GET "http://localhost:8888/api/martial/competition/list?current=1&size=10"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9. 临时解决方案
|
||||||
|
|
||||||
|
如果急需使用,可以临时修改代码,手动添加测试数据:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 在 loadCompetitionList 函数中添加
|
||||||
|
competitionList.value = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
competitionName: '测试赛事',
|
||||||
|
status: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
queryParams.competitionId = 1
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意:** 这只是临时方案,不要提交到代码库。
|
||||||
|
|
||||||
|
### 10. 联系支持
|
||||||
|
|
||||||
|
如果以上步骤都无法解决问题,请提供以下信息:
|
||||||
|
|
||||||
|
1. 浏览器控制台完整错误日志
|
||||||
|
2. Network请求的详细信息(Request/Response)
|
||||||
|
3. 后端服务日志
|
||||||
|
4. 数据库查询结果
|
||||||
|
|
||||||
|
## 修改记录
|
||||||
|
|
||||||
|
### 2025-12-13
|
||||||
|
- ✨ 添加详细的日志输出
|
||||||
|
- ✨ 优化数据解析逻辑,支持多种返回格式
|
||||||
|
- ✨ 移除status=1的限制,查询所有赛事
|
||||||
|
- 📝 创建排查指南文档
|
||||||
@@ -45,7 +45,7 @@ export const getDeductionDetail = (id) => {
|
|||||||
*/
|
*/
|
||||||
export const addDeduction = (data) => {
|
export const addDeduction = (data) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/blade-martial/deductionItem/save',
|
url: '/api/blade-martial/deductionItem/submit',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data
|
data
|
||||||
})
|
})
|
||||||
@@ -57,7 +57,7 @@ export const addDeduction = (data) => {
|
|||||||
*/
|
*/
|
||||||
export const updateDeduction = (data) => {
|
export const updateDeduction = (data) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/blade-martial/deductionItem/update',
|
url: '/api/blade-martial/deductionItem/submit',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data
|
data
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export const getDeductionDetail = (id) => {
|
|||||||
*/
|
*/
|
||||||
export const addDeduction = (data) => {
|
export const addDeduction = (data) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/blade-martial/deductionItem/save',
|
url: '/api/blade-martial/deductionItem/submit',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data
|
data
|
||||||
})
|
})
|
||||||
@@ -57,7 +57,7 @@ export const addDeduction = (data) => {
|
|||||||
*/
|
*/
|
||||||
export const updateDeduction = (data) => {
|
export const updateDeduction = (data) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/blade-martial/deductionItem/update',
|
url: '/api/blade-martial/deductionItem/submit',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data
|
data
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import request from '@/axios';
|
|||||||
*/
|
*/
|
||||||
export const getJudgeInviteList = (current, size, params) => {
|
export const getJudgeInviteList = (current, size, params) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/blade-martial/judgeInvite/list',
|
url: '/api/martial/judgeInvite/list',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params: {
|
||||||
current,
|
current,
|
||||||
@@ -30,7 +30,7 @@ export const getJudgeInviteList = (current, size, params) => {
|
|||||||
*/
|
*/
|
||||||
export const getJudgeInviteDetail = (id) => {
|
export const getJudgeInviteDetail = (id) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/blade-martial/judgeInvite/detail',
|
url: '/api/martial/judgeInvite/detail',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: { id }
|
params: { id }
|
||||||
})
|
})
|
||||||
@@ -48,7 +48,7 @@ export const getJudgeInviteDetail = (id) => {
|
|||||||
*/
|
*/
|
||||||
export const sendInvite = (data) => {
|
export const sendInvite = (data) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/blade-martial/judgeInvite/send',
|
url: '/api/martial/judgeInvite/send',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data
|
data
|
||||||
})
|
})
|
||||||
@@ -60,7 +60,7 @@ export const sendInvite = (data) => {
|
|||||||
*/
|
*/
|
||||||
export const updateInvite = (data) => {
|
export const updateInvite = (data) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/blade-martial/judgeInvite/update',
|
url: '/api/martial/judgeInvite/submit',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data
|
data
|
||||||
})
|
})
|
||||||
@@ -72,7 +72,7 @@ export const updateInvite = (data) => {
|
|||||||
*/
|
*/
|
||||||
export const removeInvite = (ids) => {
|
export const removeInvite = (ids) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/blade-martial/judgeInvite/remove',
|
url: '/api/martial/judgeInvite/remove',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
params: { ids }
|
params: { ids }
|
||||||
})
|
})
|
||||||
@@ -87,7 +87,7 @@ export const removeInvite = (ids) => {
|
|||||||
*/
|
*/
|
||||||
export const batchSendInvites = (data) => {
|
export const batchSendInvites = (data) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/blade-martial/judgeInvite/batch-send',
|
url: '/api/martial/judgeInvite/batch-send',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data
|
data
|
||||||
})
|
})
|
||||||
@@ -99,7 +99,7 @@ export const batchSendInvites = (data) => {
|
|||||||
*/
|
*/
|
||||||
export const resendInvite = (id) => {
|
export const resendInvite = (id) => {
|
||||||
return request({
|
return request({
|
||||||
url: `/api/blade-martial/judgeInvite/resend/${id}`,
|
url: `/api/martial/judgeInvite/resend/${id}`,
|
||||||
method: 'post'
|
method: 'post'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -114,7 +114,7 @@ export const resendInvite = (id) => {
|
|||||||
*/
|
*/
|
||||||
export const replyInvite = (data) => {
|
export const replyInvite = (data) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/blade-martial/judgeInvite/reply',
|
url: '/api/martial/judgeInvite/reply',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data
|
data
|
||||||
})
|
})
|
||||||
@@ -127,7 +127,7 @@ export const replyInvite = (data) => {
|
|||||||
*/
|
*/
|
||||||
export const cancelInvite = (id, reason) => {
|
export const cancelInvite = (id, reason) => {
|
||||||
return request({
|
return request({
|
||||||
url: `/api/blade-martial/judgeInvite/cancel/${id}`,
|
url: `/api/martial/judgeInvite/cancel/${id}`,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
params: { reason }
|
params: { reason }
|
||||||
})
|
})
|
||||||
@@ -139,7 +139,7 @@ export const cancelInvite = (id, reason) => {
|
|||||||
*/
|
*/
|
||||||
export const confirmInvite = (id) => {
|
export const confirmInvite = (id) => {
|
||||||
return request({
|
return request({
|
||||||
url: `/api/blade-martial/judgeInvite/confirm/${id}`,
|
url: `/api/martial/judgeInvite/confirm/${id}`,
|
||||||
method: 'post'
|
method: 'post'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -150,7 +150,7 @@ export const confirmInvite = (id) => {
|
|||||||
*/
|
*/
|
||||||
export const getInviteStatistics = (competitionId) => {
|
export const getInviteStatistics = (competitionId) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/blade-martial/judgeInvite/statistics',
|
url: '/api/martial/judgeInvite/statistics',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: { competitionId }
|
params: { competitionId }
|
||||||
})
|
})
|
||||||
@@ -162,7 +162,7 @@ export const getInviteStatistics = (competitionId) => {
|
|||||||
*/
|
*/
|
||||||
export const getAcceptedJudges = (competitionId) => {
|
export const getAcceptedJudges = (competitionId) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/blade-martial/judgeInvite/accepted-judges',
|
url: '/api/martial/judgeInvite/accepted-judges',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: { competitionId }
|
params: { competitionId }
|
||||||
})
|
})
|
||||||
@@ -175,7 +175,7 @@ export const getAcceptedJudges = (competitionId) => {
|
|||||||
*/
|
*/
|
||||||
export const importFromJudgePool = (competitionId, judgeIds) => {
|
export const importFromJudgePool = (competitionId, judgeIds) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/blade-martial/judgeInvite/import/pool',
|
url: '/api/martial/judgeInvite/import/pool',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
params: { competitionId, judgeIds }
|
params: { competitionId, judgeIds }
|
||||||
})
|
})
|
||||||
@@ -187,7 +187,7 @@ export const importFromJudgePool = (competitionId, judgeIds) => {
|
|||||||
*/
|
*/
|
||||||
export const exportInvites = (params) => {
|
export const exportInvites = (params) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/blade-martial/judgeInvite/export',
|
url: '/api/martial/judgeInvite/export',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params,
|
params,
|
||||||
responseType: 'blob'
|
responseType: 'blob'
|
||||||
@@ -201,7 +201,7 @@ export const exportInvites = (params) => {
|
|||||||
*/
|
*/
|
||||||
export const sendReminder = (id, message) => {
|
export const sendReminder = (id, message) => {
|
||||||
return request({
|
return request({
|
||||||
url: `/api/blade-martial/judgeInvite/reminder/${id}`,
|
url: `/api/martial/judgeInvite/reminder/${id}`,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
params: { message }
|
params: { message }
|
||||||
})
|
})
|
||||||
@@ -219,7 +219,7 @@ export const sendReminder = (id, message) => {
|
|||||||
*/
|
*/
|
||||||
export const generateInviteCode = (data) => {
|
export const generateInviteCode = (data) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/blade-martial/judgeInvite/generate',
|
url: '/api/martial/judgeInvite/generate',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data
|
data
|
||||||
})
|
})
|
||||||
@@ -235,7 +235,7 @@ export const generateInviteCode = (data) => {
|
|||||||
*/
|
*/
|
||||||
export const batchGenerateInviteCode = (data) => {
|
export const batchGenerateInviteCode = (data) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/blade-martial/judgeInvite/generate/batch',
|
url: '/api/martial/judgeInvite/generate/batch',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data
|
data
|
||||||
})
|
})
|
||||||
@@ -247,7 +247,7 @@ export const batchGenerateInviteCode = (data) => {
|
|||||||
*/
|
*/
|
||||||
export const regenerateInviteCode = (inviteId) => {
|
export const regenerateInviteCode = (inviteId) => {
|
||||||
return request({
|
return request({
|
||||||
url: `/api/blade-martial/judgeInvite/regenerate/${inviteId}`,
|
url: `/api/martial/judgeInvite/regenerate/${inviteId}`,
|
||||||
method: 'put'
|
method: 'put'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -259,7 +259,7 @@ export const regenerateInviteCode = (inviteId) => {
|
|||||||
*/
|
*/
|
||||||
export const getInviteByJudge = (competitionId, judgeId) => {
|
export const getInviteByJudge = (competitionId, judgeId) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/blade-martial/judgeInvite/byJudge',
|
url: '/api/martial/judgeInvite/byJudge',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: { competitionId, judgeId }
|
params: { competitionId, judgeId }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import request from '@/axios';
|
|||||||
* @param {Object} params - 查询参数
|
* @param {Object} params - 查询参数
|
||||||
* @param {Number} params.competitionId - 赛事ID
|
* @param {Number} params.competitionId - 赛事ID
|
||||||
* @param {String} params.projectName - 项目名称(可选)
|
* @param {String} params.projectName - 项目名称(可选)
|
||||||
* @param {String} params.category - 分组类别(可选)
|
* @param {Number} params.category - 分组类别(可选)
|
||||||
|
* @param {Number} params.eventType - 项目类型(可选)
|
||||||
|
* @param {Number} params.participantType - 参赛类型(可选,1-单人,2-集体)
|
||||||
*/
|
*/
|
||||||
export const getProjectList = (current, size, params) => {
|
export const getProjectList = (current, size, params) => {
|
||||||
return request({
|
return request({
|
||||||
@@ -65,17 +67,16 @@ export const updateProject = (data) => {
|
|||||||
* @param {Number} data.competitionId - 赛事ID
|
* @param {Number} data.competitionId - 赛事ID
|
||||||
* @param {String} data.projectName - 项目名称
|
* @param {String} data.projectName - 项目名称
|
||||||
* @param {String} data.projectCode - 项目编码
|
* @param {String} data.projectCode - 项目编码
|
||||||
* @param {String} data.category - 组别(男子组/女子组)
|
* @param {Number} data.category - 分组类别(1-男子,2-女子,3-团体,4-混合)
|
||||||
* @param {Number} data.type - 类型(1-个人,2-双人,3-集体)
|
* @param {Number} data.eventType - 项目类型(1-套路,2-散打,3-器械,4-对练)
|
||||||
* @param {Number} data.minParticipants - 最少参赛人数
|
* @param {Number} data.participantType - 参赛类型(1-单人,2-集体)
|
||||||
* @param {Number} data.maxParticipants - 最多参赛人数
|
* @param {Number} data.registrationFee - 报名费用
|
||||||
* @param {Number} data.minAge - 最小年龄
|
* @param {String} data.registrationStartTime - 报名开始时间
|
||||||
* @param {Number} data.maxAge - 最大年龄
|
* @param {String} data.registrationEndTime - 报名结束时间
|
||||||
* @param {Number} data.genderLimit - 性别限制(0-不限,1-仅男,2-仅女)
|
* @param {Number} data.maxParticipants - 最大参赛人数
|
||||||
* @param {Number} data.estimatedDuration - 预估时长(分钟)
|
* @param {Number} data.sortOrder - 排序序号
|
||||||
* @param {Number} data.price - 报名费用
|
* @param {String} data.rules - 比赛规则
|
||||||
* @param {Number} data.difficultyCoefficient - 难度系数
|
* @param {String} data.remark - 备注
|
||||||
* @param {String} data.description - 项目描述
|
|
||||||
*/
|
*/
|
||||||
export const submitProject = (data) => {
|
export const submitProject = (data) => {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import request from '@/axios';
|
|||||||
|
|
||||||
export const getList = (current, size, params) => {
|
export const getList = (current, size, params) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/blade-system/menu/list',
|
url: '/api/blade-system/menu/list',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params: {
|
||||||
...params,
|
...params,
|
||||||
@@ -14,7 +14,7 @@ export const getList = (current, size, params) => {
|
|||||||
|
|
||||||
export const getLazyList = (parentId, params) => {
|
export const getLazyList = (parentId, params) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/blade-system/menu/lazy-list',
|
url: '/api/blade-system/menu/lazy-list',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params: {
|
||||||
...params,
|
...params,
|
||||||
@@ -25,7 +25,7 @@ export const getLazyList = (parentId, params) => {
|
|||||||
|
|
||||||
export const getLazyMenuList = (parentId, params) => {
|
export const getLazyMenuList = (parentId, params) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/blade-system/menu/lazy-menu-list',
|
url: '/api/blade-system/menu/lazy-menu-list',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params: {
|
||||||
...params,
|
...params,
|
||||||
@@ -36,7 +36,7 @@ export const getLazyMenuList = (parentId, params) => {
|
|||||||
|
|
||||||
export const getMenuList = (current, size, params) => {
|
export const getMenuList = (current, size, params) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/blade-system/menu/menu-list',
|
url: '/api/blade-system/menu/menu-list',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params: {
|
||||||
...params,
|
...params,
|
||||||
@@ -48,7 +48,7 @@ export const getMenuList = (current, size, params) => {
|
|||||||
|
|
||||||
export const getMenuTree = tenantId => {
|
export const getMenuTree = tenantId => {
|
||||||
return request({
|
return request({
|
||||||
url: '/blade-system/menu/tree',
|
url: '/api/blade-system/menu/tree',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params: {
|
||||||
tenantId,
|
tenantId,
|
||||||
@@ -58,7 +58,7 @@ export const getMenuTree = tenantId => {
|
|||||||
|
|
||||||
export const remove = ids => {
|
export const remove = ids => {
|
||||||
return request({
|
return request({
|
||||||
url: '/blade-system/menu/remove',
|
url: '/api/blade-system/menu/remove',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
params: {
|
params: {
|
||||||
ids,
|
ids,
|
||||||
@@ -68,7 +68,7 @@ export const remove = ids => {
|
|||||||
|
|
||||||
export const add = row => {
|
export const add = row => {
|
||||||
return request({
|
return request({
|
||||||
url: '/blade-system/menu/submit',
|
url: '/api/blade-system/menu/submit',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: row,
|
data: row,
|
||||||
});
|
});
|
||||||
@@ -76,7 +76,7 @@ export const add = row => {
|
|||||||
|
|
||||||
export const update = row => {
|
export const update = row => {
|
||||||
return request({
|
return request({
|
||||||
url: '/blade-system/menu/submit',
|
url: '/api/blade-system/menu/submit',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: row,
|
data: row,
|
||||||
});
|
});
|
||||||
@@ -84,7 +84,7 @@ export const update = row => {
|
|||||||
|
|
||||||
export const getMenu = id => {
|
export const getMenu = id => {
|
||||||
return request({
|
return request({
|
||||||
url: '/blade-system/menu/detail',
|
url: '/api/blade-system/menu/detail',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params: {
|
||||||
id,
|
id,
|
||||||
@@ -94,13 +94,13 @@ export const getMenu = id => {
|
|||||||
|
|
||||||
export const getTopMenu = () =>
|
export const getTopMenu = () =>
|
||||||
request({
|
request({
|
||||||
url: '/blade-system/menu/top-menu',
|
url: '/api/blade-system/menu/top-menu',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getRoutes = topMenuId =>
|
export const getRoutes = topMenuId =>
|
||||||
request({
|
request({
|
||||||
url: '/blade-system/menu/routes',
|
url: '/api/blade-system/menu/routes',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params: {
|
||||||
topMenuId,
|
topMenuId,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import website from '@/config/website';
|
|||||||
|
|
||||||
export const loginByUsername = (tenantId, deptId, roleId, username, password, type, key, code) =>
|
export const loginByUsername = (tenantId, deptId, roleId, username, password, type, key, code) =>
|
||||||
request({
|
request({
|
||||||
url: '/blade-auth/oauth/token',
|
url: '/api/blade-auth/oauth/token',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
headers: {
|
headers: {
|
||||||
'Tenant-Id': tenantId,
|
'Tenant-Id': tenantId,
|
||||||
@@ -25,7 +25,7 @@ export const loginByUsername = (tenantId, deptId, roleId, username, password, ty
|
|||||||
|
|
||||||
export const loginBySocial = (tenantId, source, code, state) =>
|
export const loginBySocial = (tenantId, source, code, state) =>
|
||||||
request({
|
request({
|
||||||
url: '/blade-auth/oauth/token',
|
url: '/api/blade-auth/oauth/token',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
headers: {
|
headers: {
|
||||||
'Tenant-Id': tenantId,
|
'Tenant-Id': tenantId,
|
||||||
@@ -42,7 +42,7 @@ export const loginBySocial = (tenantId, source, code, state) =>
|
|||||||
|
|
||||||
export const loginBySso = (state, code) =>
|
export const loginBySso = (state, code) =>
|
||||||
request({
|
request({
|
||||||
url: '/blade-auth/oauth/token',
|
url: '/api/blade-auth/oauth/token',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
headers: {
|
headers: {
|
||||||
'Tenant-Id': state,
|
'Tenant-Id': state,
|
||||||
@@ -58,7 +58,7 @@ export const loginBySso = (state, code) =>
|
|||||||
|
|
||||||
export const refreshToken = (refresh_token, tenantId, deptId, roleId) =>
|
export const refreshToken = (refresh_token, tenantId, deptId, roleId) =>
|
||||||
request({
|
request({
|
||||||
url: '/blade-auth/oauth/token',
|
url: '/api/blade-auth/oauth/token',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
headers: {
|
headers: {
|
||||||
'Tenant-Id': tenantId,
|
'Tenant-Id': tenantId,
|
||||||
@@ -75,7 +75,7 @@ export const refreshToken = (refresh_token, tenantId, deptId, roleId) =>
|
|||||||
|
|
||||||
export const registerUser = (tenantId, name, account, password, phone, email) =>
|
export const registerUser = (tenantId, name, account, password, phone, email) =>
|
||||||
request({
|
request({
|
||||||
url: '/blade-auth/oauth/token',
|
url: '/api/blade-auth/oauth/token',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
headers: {
|
headers: {
|
||||||
'Tenant-Id': tenantId,
|
'Tenant-Id': tenantId,
|
||||||
@@ -94,7 +94,7 @@ export const registerUser = (tenantId, name, account, password, phone, email) =>
|
|||||||
|
|
||||||
export const registerGuest = (form, oauthId) =>
|
export const registerGuest = (form, oauthId) =>
|
||||||
request({
|
request({
|
||||||
url: '/blade-system/user/register-guest',
|
url: '/api/blade-system/user/register-guest',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
params: {
|
params: {
|
||||||
tenantId: form.tenantId,
|
tenantId: form.tenantId,
|
||||||
@@ -107,40 +107,40 @@ export const registerGuest = (form, oauthId) =>
|
|||||||
|
|
||||||
export const getButtons = () =>
|
export const getButtons = () =>
|
||||||
request({
|
request({
|
||||||
url: '/blade-system/menu/buttons',
|
url: '/api/blade-system/menu/buttons',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getCaptcha = () =>
|
export const getCaptcha = () =>
|
||||||
request({
|
request({
|
||||||
url: '/blade-auth/oauth/captcha',
|
url: '/api/blade-auth/oauth/captcha',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
authorization: false,
|
authorization: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const logout = () =>
|
export const logout = () =>
|
||||||
request({
|
request({
|
||||||
url: '/blade-auth/oauth/logout',
|
url: '/api/blade-auth/oauth/logout',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
authorization: false,
|
authorization: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getUserInfo = () =>
|
export const getUserInfo = () =>
|
||||||
request({
|
request({
|
||||||
url: '/blade-auth/oauth/user-info',
|
url: '/api/blade-auth/oauth/user-info',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
});
|
});
|
||||||
|
|
||||||
export const sendLogs = list =>
|
export const sendLogs = list =>
|
||||||
request({
|
request({
|
||||||
url: '/blade-auth/oauth/logout',
|
url: '/api/blade-auth/oauth/logout',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: list,
|
data: list,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const clearCache = () =>
|
export const clearCache = () =>
|
||||||
request({
|
request({
|
||||||
url: '/blade-auth/oauth/clear-cache',
|
url: '/api/blade-auth/oauth/clear-cache',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
authorization: false,
|
authorization: false,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,12 +3,40 @@
|
|||||||
<!-- 搜索区域 -->
|
<!-- 搜索区域 -->
|
||||||
<el-card shadow="never" class="search-card">
|
<el-card shadow="never" class="search-card">
|
||||||
<el-form :inline="true" :model="queryParams" class="search-form">
|
<el-form :inline="true" :model="queryParams" class="search-form">
|
||||||
|
<el-form-item label="选择赛事" required>
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.competitionId"
|
||||||
|
placeholder="请选择赛事"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
:loading="competitionLoading"
|
||||||
|
:disabled="competitionLoading"
|
||||||
|
style="width: 250px"
|
||||||
|
@change="handleCompetitionChange"
|
||||||
|
>
|
||||||
|
<template #empty>
|
||||||
|
<div style="padding: 20px; text-align: center; color: #909399;">
|
||||||
|
<p>暂无赛事数据</p>
|
||||||
|
<p style="font-size: 12px; margin-top: 5px;">
|
||||||
|
请先在"赛事管理"中创建赛事
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-option
|
||||||
|
v-for="item in competitionList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.competitionName"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="所属项目">
|
<el-form-item label="所属项目">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="queryParams.projectId"
|
v-model="queryParams.projectId"
|
||||||
placeholder="请选择项目"
|
placeholder="请选择项目"
|
||||||
clearable
|
clearable
|
||||||
filterable
|
filterable
|
||||||
|
:disabled="!queryParams.competitionId"
|
||||||
style="width: 250px"
|
style="width: 250px"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
@@ -40,21 +68,31 @@
|
|||||||
<el-card shadow="never" class="toolbar-card">
|
<el-card shadow="never" class="toolbar-card">
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<div class="toolbar-left">
|
<div class="toolbar-left">
|
||||||
<el-button type="primary" :icon="Plus" @click="handleAdd">
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:icon="Plus"
|
||||||
|
:disabled="!queryParams.competitionId"
|
||||||
|
@click="handleAdd"
|
||||||
|
>
|
||||||
新增扣分项
|
新增扣分项
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="danger"
|
type="danger"
|
||||||
:icon="Delete"
|
:icon="Delete"
|
||||||
:disabled="!selection.length"
|
:disabled="!selection.length || !queryParams.competitionId"
|
||||||
@click="handleBatchDelete"
|
@click="handleBatchDelete"
|
||||||
>
|
>
|
||||||
批量删除
|
批量删除
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="success" :icon="DocumentCopy" @click="handleClone">
|
<!-- <el-button type="success" :icon="DocumentCopy" @click="handleClone">
|
||||||
克隆扣分项
|
克隆扣分项
|
||||||
</el-button>
|
</el-button> -->
|
||||||
<el-button type="warning" :icon="Download" @click="handleExport">
|
<el-button
|
||||||
|
type="warning"
|
||||||
|
:icon="Download"
|
||||||
|
:disabled="!queryParams.competitionId"
|
||||||
|
@click="handleExport"
|
||||||
|
>
|
||||||
导出模板
|
导出模板
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -65,7 +103,12 @@
|
|||||||
</el-icon>
|
</el-icon>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip content="刷新" placement="top">
|
<el-tooltip content="刷新" placement="top">
|
||||||
<el-button circle :icon="Refresh" @click="fetchData" />
|
<el-button
|
||||||
|
circle
|
||||||
|
:icon="Refresh"
|
||||||
|
:disabled="!queryParams.competitionId"
|
||||||
|
@click="fetchData"
|
||||||
|
/>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -135,9 +178,6 @@
|
|||||||
<el-button link type="primary" :icon="Edit" @click="handleEdit(row)">
|
<el-button link type="primary" :icon="Edit" @click="handleEdit(row)">
|
||||||
编辑
|
编辑
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button link type="success" :icon="DocumentCopy" @click="handleCloneSingle(row)">
|
|
||||||
克隆
|
|
||||||
</el-button>
|
|
||||||
<el-button link type="danger" :icon="Delete" @click="handleDelete(row)">
|
<el-button link type="danger" :icon="Delete" @click="handleDelete(row)">
|
||||||
删除
|
删除
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -173,6 +213,22 @@
|
|||||||
:rules="rules"
|
:rules="rules"
|
||||||
label-width="120px"
|
label-width="120px"
|
||||||
>
|
>
|
||||||
|
<el-form-item label="所属赛事" prop="competitionId">
|
||||||
|
<el-select
|
||||||
|
v-model="form.competitionId"
|
||||||
|
placeholder="请选择赛事"
|
||||||
|
filterable
|
||||||
|
disabled
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in competitionList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.competitionName"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="所属项目" prop="projectId">
|
<el-form-item label="所属项目" prop="projectId">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="form.projectId"
|
v-model="form.projectId"
|
||||||
@@ -295,7 +351,6 @@ import {
|
|||||||
Delete,
|
Delete,
|
||||||
Edit,
|
Edit,
|
||||||
Download,
|
Download,
|
||||||
DocumentCopy,
|
|
||||||
InfoFilled,
|
InfoFilled,
|
||||||
Rank
|
Rank
|
||||||
} from '@element-plus/icons-vue'
|
} from '@element-plus/icons-vue'
|
||||||
@@ -310,6 +365,7 @@ import {
|
|||||||
import { getProjectList } from '@/api/martial/project'
|
import { getProjectList } from '@/api/martial/project'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import Sortable from 'sortablejs'
|
import Sortable from 'sortablejs'
|
||||||
|
import { getCompetitionList } from '@/api/martial/competition'
|
||||||
|
|
||||||
// 数据定义
|
// 数据定义
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -324,20 +380,22 @@ const cloneDialogVisible = ref(false)
|
|||||||
const dialogTitle = ref('')
|
const dialogTitle = ref('')
|
||||||
const formRef = ref(null)
|
const formRef = ref(null)
|
||||||
const cloneFormRef = ref(null)
|
const cloneFormRef = ref(null)
|
||||||
|
const competitionList = ref([])
|
||||||
|
const competitionLoading = ref(false)
|
||||||
|
|
||||||
// 查询参数
|
// 查询参数
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
current: 1,
|
current: 1,
|
||||||
size: 10,
|
size: 10,
|
||||||
projectId: '',
|
competitionId: null,
|
||||||
itemName: ''
|
projectId: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
id: null,
|
id: null,
|
||||||
projectId: '',
|
competitionId: null,
|
||||||
itemName: '',
|
projectId: null,
|
||||||
deductionPoints: 0.5,
|
deductionPoints: 0.5,
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
description: ''
|
description: ''
|
||||||
@@ -353,6 +411,9 @@ const cloneForm = reactive({
|
|||||||
|
|
||||||
// 表单验证规则
|
// 表单验证规则
|
||||||
const rules = {
|
const rules = {
|
||||||
|
competitionId: [
|
||||||
|
{ required: true, message: '请选择赛事', trigger: 'change' }
|
||||||
|
],
|
||||||
projectId: [
|
projectId: [
|
||||||
{ required: true, message: '请选择所属项目', trigger: 'change' }
|
{ required: true, message: '请选择所属项目', trigger: 'change' }
|
||||||
],
|
],
|
||||||
@@ -375,20 +436,72 @@ const cloneRules = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载项目列表
|
// 加载赛事列表
|
||||||
const loadProjectList = async () => {
|
const loadCompetitionList = async () => {
|
||||||
|
competitionLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await getProjectList(1, 1000, {})
|
const resCompe = await getCompetitionList(1, 1000, {})
|
||||||
if (res.data && res.data.records) {
|
const recordsCompe = resCompe.data?.data?.records || []
|
||||||
projectList.value = res.data.records
|
competitionList.value = recordsCompe
|
||||||
|
|
||||||
|
if (recordsCompe.length === 0) {
|
||||||
|
console.warn('赛事列表为空,请先在"赛事管理"中创建赛事')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载赛事列表失败:', error)
|
||||||
|
ElMessage.error('加载赛事列表失败')
|
||||||
|
} finally {
|
||||||
|
competitionLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载项目列表(根据赛事ID)
|
||||||
|
const loadProjectList = async (competitionId) => {
|
||||||
|
if (!competitionId) {
|
||||||
|
projectList.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await getProjectList(1, 1000, { competitionId })
|
||||||
|
// 根据axios响应拦截器的处理,数据在 res.data.data.records 中
|
||||||
|
const records = res.data?.data?.records || []
|
||||||
|
projectList.value = records
|
||||||
|
|
||||||
|
if (records.length === 0) {
|
||||||
|
console.warn('该赛事下暂无项目,请先在"轮编管理"中创建项目')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载项目列表失败:', error)
|
console.error('加载项目列表失败:', error)
|
||||||
|
ElMessage.error('加载项目列表失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 赛事选择变化
|
||||||
|
const handleCompetitionChange = (competitionId) => {
|
||||||
|
// 清空项目选择和表格数据
|
||||||
|
queryParams.projectId = null
|
||||||
|
tableData.value = []
|
||||||
|
total.value = 0
|
||||||
|
|
||||||
|
// 加载该赛事下的项目
|
||||||
|
if (competitionId) {
|
||||||
|
loadProjectList(competitionId)
|
||||||
|
// 自动查询数据
|
||||||
|
fetchData()
|
||||||
|
} else {
|
||||||
|
projectList.value = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询数据
|
// 查询数据
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
|
// 必须选择赛事才能查询
|
||||||
|
if (!queryParams.competitionId) {
|
||||||
|
ElMessage.warning('请先选择赛事')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await getDeductionList(
|
const res = await getDeductionList(
|
||||||
@@ -396,10 +509,10 @@ const fetchData = async () => {
|
|||||||
queryParams.size,
|
queryParams.size,
|
||||||
queryParams
|
queryParams
|
||||||
)
|
)
|
||||||
if (res.data) {
|
// 根据axios响应拦截器的处理,数据在 res.data.data 中
|
||||||
tableData.value = res.data.records || []
|
const data = res.data?.data || {}
|
||||||
total.value = res.data.total || 0
|
tableData.value = data.records || []
|
||||||
}
|
total.value = data.total || 0
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('获取数据失败')
|
ElMessage.error('获取数据失败')
|
||||||
console.error(error)
|
console.error(error)
|
||||||
@@ -416,19 +529,28 @@ const handleSearch = () => {
|
|||||||
|
|
||||||
// 重置
|
// 重置
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
|
const competitionId = queryParams.competitionId
|
||||||
Object.assign(queryParams, {
|
Object.assign(queryParams, {
|
||||||
current: 1,
|
current: 1,
|
||||||
size: 10,
|
size: 10,
|
||||||
projectId: '',
|
competitionId: competitionId,
|
||||||
|
projectId: null,
|
||||||
itemName: ''
|
itemName: ''
|
||||||
})
|
})
|
||||||
fetchData()
|
if (competitionId) {
|
||||||
|
fetchData()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增
|
// 新增
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
|
if (!queryParams.competitionId) {
|
||||||
|
ElMessage.warning('请先选择赛事')
|
||||||
|
return
|
||||||
|
}
|
||||||
dialogTitle.value = '新增扣分项'
|
dialogTitle.value = '新增扣分项'
|
||||||
resetForm()
|
resetForm()
|
||||||
|
form.competitionId = queryParams.competitionId
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,29 +602,6 @@ const handleBatchDelete = () => {
|
|||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 克隆单个扣分项
|
|
||||||
const handleCloneSingle = (row) => {
|
|
||||||
cloneForm.sourceProjectId = row.projectId
|
|
||||||
cloneForm.sourceProjectName = row.projectName
|
|
||||||
cloneForm.targetProjectId = ''
|
|
||||||
cloneForm.itemCount = 1
|
|
||||||
cloneDialogVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 克隆扣分项(批量)
|
|
||||||
const handleClone = () => {
|
|
||||||
if (!queryParams.projectId) {
|
|
||||||
ElMessage.warning('请先选择源项目(通过搜索筛选)')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const sourceProject = projectList.value.find(p => p.id === queryParams.projectId)
|
|
||||||
cloneForm.sourceProjectId = queryParams.projectId
|
|
||||||
cloneForm.sourceProjectName = sourceProject?.projectName || ''
|
|
||||||
cloneForm.targetProjectId = ''
|
|
||||||
cloneForm.itemCount = total.value
|
|
||||||
cloneDialogVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交克隆
|
// 提交克隆
|
||||||
const handleCloneSubmit = async () => {
|
const handleCloneSubmit = async () => {
|
||||||
@@ -568,7 +667,8 @@ const handleDialogClose = () => {
|
|||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
Object.assign(form, {
|
Object.assign(form, {
|
||||||
id: null,
|
id: null,
|
||||||
projectId: '',
|
competitionId: null,
|
||||||
|
projectId: null,
|
||||||
itemName: '',
|
itemName: '',
|
||||||
deductionPoints: 0.5,
|
deductionPoints: 0.5,
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
@@ -639,8 +739,7 @@ const formatDate = (date) => {
|
|||||||
|
|
||||||
// 生命周期
|
// 生命周期
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadProjectList()
|
loadCompetitionList()
|
||||||
fetchData()
|
|
||||||
initSortable()
|
initSortable()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -9,9 +9,19 @@
|
|||||||
placeholder="请选择赛事"
|
placeholder="请选择赛事"
|
||||||
clearable
|
clearable
|
||||||
filterable
|
filterable
|
||||||
|
:loading="competitionLoading"
|
||||||
|
:disabled="competitionLoading"
|
||||||
style="width: 250px"
|
style="width: 250px"
|
||||||
@change="handleCompetitionChange"
|
@change="handleCompetitionChange"
|
||||||
>
|
>
|
||||||
|
<template #empty>
|
||||||
|
<div style="padding: 20px; text-align: center; color: #909399;">
|
||||||
|
<p>暂无赛事数据</p>
|
||||||
|
<p style="font-size: 12px; margin-top: 5px;">
|
||||||
|
请先在"赛事管理"中创建赛事
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in competitionList"
|
v-for="item in competitionList"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@@ -41,19 +51,6 @@
|
|||||||
<el-option label="三级" value="三级" />
|
<el-option label="三级" value="三级" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="邀请状态">
|
|
||||||
<el-select
|
|
||||||
v-model="queryParams.inviteStatus"
|
|
||||||
placeholder="请选择"
|
|
||||||
clearable
|
|
||||||
style="width: 130px"
|
|
||||||
>
|
|
||||||
<el-option label="待回复" :value="0" />
|
|
||||||
<el-option label="已接受" :value="1" />
|
|
||||||
<el-option label="已拒绝" :value="2" />
|
|
||||||
<el-option label="已取消" :value="3" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" :icon="Search" @click="handleSearch">
|
<el-button type="primary" :icon="Search" @click="handleSearch">
|
||||||
搜索
|
搜索
|
||||||
@@ -63,82 +60,30 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<!-- 统计卡片 -->
|
|
||||||
<el-row :gutter="20" class="stats-row">
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-card shadow="hover" class="stats-card">
|
|
||||||
<div class="stats-content">
|
|
||||||
<div class="stats-icon" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%)">
|
|
||||||
<el-icon :size="30"><User /></el-icon>
|
|
||||||
</div>
|
|
||||||
<div class="stats-info">
|
|
||||||
<div class="stats-value">{{ statistics.totalInvites || 0 }}</div>
|
|
||||||
<div class="stats-label">总邀请数</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-card shadow="hover" class="stats-card">
|
|
||||||
<div class="stats-content">
|
|
||||||
<div class="stats-icon" style="background: linear-gradient(135deg, #fccb90 0%, #d57eeb 100%)">
|
|
||||||
<el-icon :size="30"><Clock /></el-icon>
|
|
||||||
</div>
|
|
||||||
<div class="stats-info">
|
|
||||||
<div class="stats-value">{{ statistics.pendingCount || 0 }}</div>
|
|
||||||
<div class="stats-label">待回复</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-card shadow="hover" class="stats-card">
|
|
||||||
<div class="stats-content">
|
|
||||||
<div class="stats-icon" style="background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%)">
|
|
||||||
<el-icon :size="30"><CircleCheck /></el-icon>
|
|
||||||
</div>
|
|
||||||
<div class="stats-info">
|
|
||||||
<div class="stats-value">{{ statistics.acceptedCount || 0 }}</div>
|
|
||||||
<div class="stats-label">已接受</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<el-card shadow="hover" class="stats-card">
|
|
||||||
<div class="stats-content">
|
|
||||||
<div class="stats-icon" style="background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%)">
|
|
||||||
<el-icon :size="30"><CircleClose /></el-icon>
|
|
||||||
</div>
|
|
||||||
<div class="stats-info">
|
|
||||||
<div class="stats-value">{{ statistics.rejectedCount || 0 }}</div>
|
|
||||||
<div class="stats-label">已拒绝</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<!-- 工具栏 -->
|
<!-- 工具栏 -->
|
||||||
<el-card shadow="never" class="toolbar-card">
|
<el-card shadow="never" class="toolbar-card">
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<div class="toolbar-left">
|
<div class="toolbar-left">
|
||||||
<el-button type="primary" :icon="Plus" @click="handleSendInvite">
|
<el-button
|
||||||
发送邀请
|
type="warning"
|
||||||
</el-button>
|
:icon="FolderOpened"
|
||||||
<el-button type="success" :icon="DocumentCopy" @click="handleBatchGenerateCode">
|
:disabled="competitionLoading || queryParams.competitionId === null || queryParams.competitionId === ''"
|
||||||
批量生成邀请码
|
:loading="competitionLoading"
|
||||||
</el-button>
|
@click="handleImportFromPool"
|
||||||
<el-button type="warning" :icon="FolderOpened" @click="handleImportFromPool">
|
>
|
||||||
从评委库导入
|
从评委库导入
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button :icon="Download" @click="handleExport">
|
<el-button
|
||||||
|
:icon="Download"
|
||||||
|
:disabled="competitionLoading || queryParams.competitionId === null || queryParams.competitionId === ''"
|
||||||
|
@click="handleExport"
|
||||||
|
>
|
||||||
导出数据
|
导出数据
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar-right">
|
<div class="toolbar-right">
|
||||||
<el-tooltip content="刷新" placement="top">
|
<el-tooltip content="刷新" placement="top">
|
||||||
<el-button circle :icon="Refresh" @click="fetchData" />
|
<el-button circle :icon="Refresh" :disabled="competitionLoading" @click="fetchData" />
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -154,15 +99,15 @@
|
|||||||
@selection-change="handleSelectionChange"
|
@selection-change="handleSelectionChange"
|
||||||
>
|
>
|
||||||
<el-table-column type="selection" width="55" align="center" />
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
<el-table-column prop="judgeName" label="评委姓名" width="120" />
|
<el-table-column prop="judgeName" label="评委姓名" />
|
||||||
<el-table-column prop="judgeLevel" label="评委等级" width="100" align="center">
|
<el-table-column prop="judgeLevel" label="评委等级" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag :type="getLevelType(row.judgeLevel)" size="small">
|
<el-tag :type="getLevelType(row.judgeLevel)" size="small">
|
||||||
{{ row.judgeLevel }}
|
{{ row.judgeLevel }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="inviteCode" label="邀请码" width="200" align="center">
|
<el-table-column prop="inviteCode" label="邀请码" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<!-- 已有邀请码:显示邀请码 + 重新生成按钮 -->
|
<!-- 已有邀请码:显示邀请码 + 重新生成按钮 -->
|
||||||
<div v-if="row.inviteCode" style="display: flex; align-items: center; justify-content: center; gap: 8px;">
|
<div v-if="row.inviteCode" style="display: flex; align-items: center; justify-content: center; gap: 8px;">
|
||||||
@@ -198,66 +143,22 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="contactPhone" label="联系电话" width="130" />
|
<el-table-column prop="refereeType" label="裁判类型" align="center">
|
||||||
<el-table-column prop="contactEmail" label="联系邮箱" min-width="180" show-overflow-tooltip />
|
|
||||||
<el-table-column prop="inviteStatus" label="邀请状态" width="100" align="center">
|
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag :type="getStatusType(row.inviteStatus)" size="small">
|
<el-tag :type="row.refereeType === 1 ? 'danger' : 'primary'" size="small">
|
||||||
{{ getStatusText(row.inviteStatus) }}
|
{{ row.refereeType === 1 ? '主裁判' : '普通裁判' }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="inviteTime" label="邀请时间" width="160" align="center">
|
<el-table-column prop="contactPhone" label="联系电话" />
|
||||||
|
<el-table-column prop="contactEmail" label="擅长项目" show-overflow-tooltip />
|
||||||
|
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
{{ formatDateTime(row.inviteTime) }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="replyTime" label="回复时间" width="160" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ row.replyTime ? formatDateTime(row.replyTime) : '-' }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="replyNote" label="回复备注" min-width="150" show-overflow-tooltip />
|
|
||||||
<el-table-column label="操作" width="280" align="center" fixed="right">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-button
|
|
||||||
v-if="row.inviteStatus === 0"
|
|
||||||
link
|
|
||||||
type="primary"
|
|
||||||
:icon="Refresh"
|
|
||||||
@click="handleResend(row)"
|
|
||||||
>
|
|
||||||
重发
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
v-if="row.inviteStatus === 0"
|
|
||||||
link
|
|
||||||
type="warning"
|
|
||||||
:icon="Bell"
|
|
||||||
@click="handleReminder(row)"
|
|
||||||
>
|
|
||||||
提醒
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
v-if="row.inviteStatus === 0"
|
|
||||||
link
|
|
||||||
type="danger"
|
|
||||||
:icon="Close"
|
|
||||||
@click="handleCancel(row)"
|
|
||||||
>
|
|
||||||
取消
|
|
||||||
</el-button>
|
|
||||||
<el-button link type="primary" :icon="View" @click="handleView(row)">
|
<el-button link type="primary" :icon="View" @click="handleView(row)">
|
||||||
查看
|
查看
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button link type="danger" :icon="Delete" @click="handleDelete(row)">
|
||||||
v-if="row.inviteStatus === 1"
|
删除
|
||||||
link
|
|
||||||
type="success"
|
|
||||||
:icon="CircleCheck"
|
|
||||||
@click="handleConfirm(row)"
|
|
||||||
>
|
|
||||||
确认
|
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -276,6 +177,108 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 裁判选择对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="judgeDialogVisible"
|
||||||
|
title="从评委库导入"
|
||||||
|
width="900px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
>
|
||||||
|
<!-- 搜索表单 -->
|
||||||
|
<el-form :inline="true" :model="judgeQueryParams" class="judge-search-form">
|
||||||
|
<el-form-item label="姓名">
|
||||||
|
<el-input
|
||||||
|
v-model="judgeQueryParams.name"
|
||||||
|
placeholder="请输入姓名"
|
||||||
|
clearable
|
||||||
|
style="width: 150px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="手机号">
|
||||||
|
<el-input
|
||||||
|
v-model="judgeQueryParams.phone"
|
||||||
|
placeholder="请输入手机号"
|
||||||
|
clearable
|
||||||
|
style="width: 150px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="裁判类型">
|
||||||
|
<el-select
|
||||||
|
v-model="judgeQueryParams.refereeType"
|
||||||
|
placeholder="请选择"
|
||||||
|
clearable
|
||||||
|
style="width: 130px"
|
||||||
|
>
|
||||||
|
<el-option label="主裁判" :value="1" />
|
||||||
|
<el-option label="普通裁判" :value="2" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" :icon="Search" @click="handleJudgeSearch">
|
||||||
|
搜索
|
||||||
|
</el-button>
|
||||||
|
<el-button :icon="Refresh" @click="handleJudgeReset">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<!-- 裁判列表 -->
|
||||||
|
<el-table
|
||||||
|
v-loading="judgeLoading"
|
||||||
|
:data="judgeList"
|
||||||
|
stripe
|
||||||
|
border
|
||||||
|
max-height="400"
|
||||||
|
@selection-change="handleJudgeSelectionChange"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
|
<el-table-column prop="name" label="姓名" width="100" />
|
||||||
|
<el-table-column prop="gender" label="性别" width="80" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.gender === 1 ? '男' : '女' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="phone" label="手机号" width="130" />
|
||||||
|
<el-table-column prop="refereeType" label="裁判类型" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.refereeType === 1 ? 'danger' : 'primary'" size="small">
|
||||||
|
{{ row.refereeType === 1 ? '主裁判' : '普通裁判' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="level" label="等级/职称" width="120" />
|
||||||
|
<el-table-column prop="specialty" label="擅长项目" min-width="150" show-overflow-tooltip />
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<div class="judge-pagination">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="judgeQueryParams.current"
|
||||||
|
v-model:page-size="judgeQueryParams.size"
|
||||||
|
:page-sizes="[10, 20, 50]"
|
||||||
|
:total="judgeTotal"
|
||||||
|
layout="total, sizes, prev, pager, next"
|
||||||
|
@size-change="loadJudgeList"
|
||||||
|
@current-change="loadJudgeList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<span style="margin-right: auto; color: #909399;">
|
||||||
|
已选择 {{ selectedJudges.length }} 位裁判
|
||||||
|
</span>
|
||||||
|
<el-button @click="judgeDialogVisible = false">取消</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="selectedJudges.length === 0"
|
||||||
|
@click="handleConfirmImport"
|
||||||
|
>
|
||||||
|
确定导入
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -285,36 +288,22 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
|||||||
import {
|
import {
|
||||||
Search,
|
Search,
|
||||||
Refresh,
|
Refresh,
|
||||||
Plus,
|
|
||||||
Delete,
|
Delete,
|
||||||
Download,
|
Download,
|
||||||
DocumentCopy,
|
|
||||||
FolderOpened,
|
FolderOpened,
|
||||||
Upload,
|
|
||||||
View,
|
View,
|
||||||
Close,
|
|
||||||
Bell,
|
|
||||||
CircleCheck,
|
|
||||||
CircleClose,
|
|
||||||
User,
|
|
||||||
Clock
|
|
||||||
} from '@element-plus/icons-vue'
|
} from '@element-plus/icons-vue'
|
||||||
import {
|
import {
|
||||||
getJudgeInviteList,
|
getJudgeInviteList,
|
||||||
sendInvite,
|
|
||||||
batchSendInvites,
|
|
||||||
resendInvite,
|
|
||||||
cancelInvite,
|
|
||||||
confirmInvite,
|
|
||||||
getInviteStatistics,
|
getInviteStatistics,
|
||||||
importFromJudgePool,
|
|
||||||
exportInvites,
|
exportInvites,
|
||||||
sendReminder,
|
|
||||||
generateInviteCode,
|
generateInviteCode,
|
||||||
batchGenerateInviteCode,
|
batchGenerateInviteCode,
|
||||||
regenerateInviteCode
|
regenerateInviteCode,
|
||||||
|
removeInvite
|
||||||
} from '@/api/martial/judgeInvite'
|
} from '@/api/martial/judgeInvite'
|
||||||
import { getCompetitionList } from '@/api/martial/competition'
|
import { getCompetitionList } from '@/api/martial/competition'
|
||||||
|
import { getRefereeList } from '@/api/martial/referee'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
// 数据状态
|
// 数据状态
|
||||||
@@ -323,6 +312,21 @@ const tableData = ref([])
|
|||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const selection = ref([])
|
const selection = ref([])
|
||||||
const competitionList = ref([])
|
const competitionList = ref([])
|
||||||
|
const competitionLoading = ref(false) // 赛事列表加载状态
|
||||||
|
|
||||||
|
// 裁判选择对话框
|
||||||
|
const judgeDialogVisible = ref(false)
|
||||||
|
const judgeLoading = ref(false)
|
||||||
|
const judgeList = ref([])
|
||||||
|
const judgeTotal = ref(0)
|
||||||
|
const selectedJudges = ref([])
|
||||||
|
const judgeQueryParams = reactive({
|
||||||
|
current: 1,
|
||||||
|
size: 10,
|
||||||
|
name: '',
|
||||||
|
phone: '',
|
||||||
|
refereeType: null
|
||||||
|
})
|
||||||
|
|
||||||
// 统计数据
|
// 统计数据
|
||||||
const statistics = ref({
|
const statistics = ref({
|
||||||
@@ -336,7 +340,7 @@ const statistics = ref({
|
|||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
current: 1,
|
current: 1,
|
||||||
size: 10,
|
size: 10,
|
||||||
competitionId: '',
|
competitionId: null,
|
||||||
judgeName: '',
|
judgeName: '',
|
||||||
judgeLevel: '',
|
judgeLevel: '',
|
||||||
inviteStatus: ''
|
inviteStatus: ''
|
||||||
@@ -344,17 +348,33 @@ const queryParams = reactive({
|
|||||||
|
|
||||||
// 加载赛事列表
|
// 加载赛事列表
|
||||||
const loadCompetitionList = async () => {
|
const loadCompetitionList = async () => {
|
||||||
|
competitionLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await getCompetitionList(1, 1000, { status: 1 })
|
// 查询所有赛事
|
||||||
if (res.data && res.data.records) {
|
const res = await getCompetitionList(1, 1000, {})
|
||||||
competitionList.value = res.data.records
|
|
||||||
if (competitionList.value.length > 0 && !queryParams.competitionId) {
|
// 根据axios响应拦截器的处理,数据结构为: res.data.data.records
|
||||||
|
// res.data = 后端返回的整个JSON对象 { code, success, data, msg }
|
||||||
|
// res.data.data = 后端返回的data字段 { records, total, size, current, pages }
|
||||||
|
// res.data.data.records = 实际的赛事列表数组
|
||||||
|
const records = res.data?.data?.records || []
|
||||||
|
|
||||||
|
competitionList.value = records
|
||||||
|
|
||||||
|
if (competitionList.value.length > 0) {
|
||||||
|
// 如果没有选中赛事,自动选择第一个
|
||||||
|
if (queryParams.competitionId === null || queryParams.competitionId === '') {
|
||||||
queryParams.competitionId = competitionList.value[0].id
|
queryParams.competitionId = competitionList.value[0].id
|
||||||
handleCompetitionChange(queryParams.competitionId)
|
handleCompetitionChange(queryParams.competitionId)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.warning('暂无可用赛事,请先创建赛事')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载赛事列表失败:', error)
|
console.error('加载赛事列表失败:', error)
|
||||||
|
ElMessage.error(`加载赛事列表失败: ${error.response?.data?.msg || error.message || '请检查网络连接'}`)
|
||||||
|
} finally {
|
||||||
|
competitionLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,7 +386,7 @@ const handleCompetitionChange = (competitionId) => {
|
|||||||
|
|
||||||
// 加载统计数据
|
// 加载统计数据
|
||||||
const loadStatistics = async () => {
|
const loadStatistics = async () => {
|
||||||
if (!queryParams.competitionId) return
|
if (queryParams.competitionId === null || queryParams.competitionId === '') return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await getInviteStatistics(queryParams.competitionId)
|
const res = await getInviteStatistics(queryParams.competitionId)
|
||||||
@@ -380,7 +400,7 @@ const loadStatistics = async () => {
|
|||||||
|
|
||||||
// 获取数据
|
// 获取数据
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
if (!queryParams.competitionId) return
|
if (queryParams.competitionId === null || queryParams.competitionId === '') return
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
@@ -389,10 +409,10 @@ const fetchData = async () => {
|
|||||||
queryParams.size,
|
queryParams.size,
|
||||||
queryParams
|
queryParams
|
||||||
)
|
)
|
||||||
if (res.data) {
|
// 根据axios响应拦截器的处理,数据在 res.data.data 中
|
||||||
tableData.value = res.data.records || []
|
const data = res.data?.data || {}
|
||||||
total.value = res.data.total || 0
|
tableData.value = data.records || []
|
||||||
}
|
total.value = data.total || 0
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('加载数据失败')
|
ElMessage.error('加载数据失败')
|
||||||
console.error(error)
|
console.error(error)
|
||||||
@@ -419,96 +439,134 @@ const handleReset = () => {
|
|||||||
fetchData()
|
fetchData()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送邀请
|
|
||||||
const handleSendInvite = async () => {
|
|
||||||
if (!queryParams.competitionId) {
|
|
||||||
ElMessage.warning('请先选择赛事')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// TODO: 打开发送邀请对话框
|
|
||||||
ElMessage.info('请先生成邀请码,然后通过邮件/短信发送给评委')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从评委库导入
|
// 从评委库导入
|
||||||
const handleImportFromPool = async () => {
|
const handleImportFromPool = async () => {
|
||||||
if (!queryParams.competitionId) {
|
if (competitionLoading.value) {
|
||||||
|
ElMessage.warning('赛事列表加载中,请稍候...')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (competitionList.value.length === 0) {
|
||||||
|
ElMessage.warning('暂无可用赛事,请先创建赛事')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (queryParams.competitionId === null || queryParams.competitionId === '') {
|
||||||
ElMessage.warning('请先选择赛事')
|
ElMessage.warning('请先选择赛事')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 打开裁判选择对话框
|
||||||
|
judgeDialogVisible.value = true
|
||||||
|
selectedJudges.value = []
|
||||||
|
loadJudgeList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载裁判列表
|
||||||
|
const loadJudgeList = async () => {
|
||||||
|
judgeLoading.value = true
|
||||||
try {
|
try {
|
||||||
// TODO: 打开评委选择对话框
|
const params = {}
|
||||||
ElMessage.info('请先在评委管理中添加评委,然后在此处生成邀请码')
|
if (judgeQueryParams.name) {
|
||||||
|
params.name = judgeQueryParams.name
|
||||||
|
}
|
||||||
|
if (judgeQueryParams.phone) {
|
||||||
|
params.phone = judgeQueryParams.phone
|
||||||
|
}
|
||||||
|
if (judgeQueryParams.refereeType !== null && judgeQueryParams.refereeType !== '') {
|
||||||
|
params.refereeType = judgeQueryParams.refereeType
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await getRefereeList(
|
||||||
|
judgeQueryParams.current,
|
||||||
|
judgeQueryParams.size,
|
||||||
|
params
|
||||||
|
)
|
||||||
|
|
||||||
|
if (res.data && res.data.data && res.data.data.records) {
|
||||||
|
judgeList.value = res.data.data.records
|
||||||
|
judgeTotal.value = res.data.data.total || 0
|
||||||
|
} else if (res.data && res.data.records) {
|
||||||
|
judgeList.value = res.data.records
|
||||||
|
judgeTotal.value = res.data.total || 0
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('导入失败')
|
console.error('加载裁判列表失败:', error)
|
||||||
|
ElMessage.error('加载裁判列表失败')
|
||||||
|
} finally {
|
||||||
|
judgeLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重发
|
// 裁判搜索
|
||||||
const handleResend = async (row) => {
|
const handleJudgeSearch = () => {
|
||||||
try {
|
judgeQueryParams.current = 1
|
||||||
await resendInvite(row.id)
|
loadJudgeList()
|
||||||
ElMessage.success('重发成功')
|
|
||||||
fetchData()
|
|
||||||
} catch (error) {
|
|
||||||
ElMessage.error('重发失败')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提醒
|
// 裁判搜索重置
|
||||||
const handleReminder = async (row) => {
|
const handleJudgeReset = () => {
|
||||||
try {
|
judgeQueryParams.name = ''
|
||||||
await sendReminder(row.id, '请尽快回复邀请')
|
judgeQueryParams.phone = ''
|
||||||
ElMessage.success('提醒发送成功')
|
judgeQueryParams.refereeType = null
|
||||||
} catch (error) {
|
judgeQueryParams.current = 1
|
||||||
ElMessage.error('提醒发送失败')
|
loadJudgeList()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 取消邀请
|
// 裁判选择改变
|
||||||
const handleCancel = async (row) => {
|
const handleJudgeSelectionChange = (val) => {
|
||||||
|
selectedJudges.value = val
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认导入裁判
|
||||||
|
const handleConfirmImport = async () => {
|
||||||
|
if (selectedJudges.value.length === 0) {
|
||||||
|
ElMessage.warning('请至少选择一位裁判')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { value: reason } = await ElMessageBox.prompt('请输入取消原因', '取消邀请', {
|
await ElMessageBox.confirm(
|
||||||
confirmButtonText: '确定',
|
`确定为选中的 ${selectedJudges.value.length} 位裁判生成邀请码吗?`,
|
||||||
cancelButtonText: '取消',
|
'确认导入',
|
||||||
inputPlaceholder: '请输入取消原因',
|
{
|
||||||
inputValidator: (value) => {
|
confirmButtonText: '确定',
|
||||||
if (!value || value.trim() === '') {
|
cancelButtonText: '取消',
|
||||||
return '请输入取消原因'
|
type: 'info'
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
const judgeIds = selectedJudges.value.map(item => item.id)
|
||||||
|
|
||||||
|
const res = await batchGenerateInviteCode({
|
||||||
|
competitionId: queryParams.competitionId,
|
||||||
|
judgeIds: judgeIds,
|
||||||
|
role: 'judge',
|
||||||
|
expireDays: 30
|
||||||
})
|
})
|
||||||
|
|
||||||
await cancelInvite(row.id, reason)
|
// 根据axios响应拦截器的处理,数据在 res.data.data 中
|
||||||
ElMessage.success('取消成功')
|
const invites = res.data?.data || []
|
||||||
fetchData()
|
|
||||||
loadStatistics()
|
if (Array.isArray(invites) && invites.length > 0) {
|
||||||
|
ElMessage.success(`成功为 ${invites.length} 位裁判生成邀请码`)
|
||||||
|
judgeDialogVisible.value = false
|
||||||
|
await fetchData()
|
||||||
|
await loadStatistics()
|
||||||
|
} else if (Array.isArray(invites) && invites.length === 0) {
|
||||||
|
ElMessage.warning('所有裁判都已有有效邀请码,无需重复生成')
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.data?.msg || '生成邀请码失败')
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error !== 'cancel') {
|
if (error !== 'cancel') {
|
||||||
ElMessage.error('取消失败')
|
console.error('导入裁判失败:', error)
|
||||||
|
ElMessage.error(error.response?.data?.msg || error.message || '导入裁判失败')
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确认
|
|
||||||
const handleConfirm = async (row) => {
|
|
||||||
ElMessageBox.confirm('确认接受该评委的邀请回复?', '提示', {
|
|
||||||
confirmButtonText: '确认',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
})
|
|
||||||
.then(async () => {
|
|
||||||
try {
|
|
||||||
await confirmInvite(row.id)
|
|
||||||
ElMessage.success('确认成功')
|
|
||||||
fetchData()
|
|
||||||
} catch (error) {
|
|
||||||
ElMessage.error('确认失败')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查看
|
// 查看
|
||||||
const handleView = async (row) => {
|
const handleView = async (row) => {
|
||||||
try {
|
try {
|
||||||
@@ -547,13 +605,21 @@ const handleView = async (row) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成单个邀请码
|
* 生成单个邀请码
|
||||||
|
* 注意:此功能仅用于已有邀请记录但未生成邀请码的情况
|
||||||
|
* 如果需要为新裁判生成邀请码,请使用"从评委库导入"功能
|
||||||
*/
|
*/
|
||||||
const handleGenerateCode = async (row) => {
|
const handleGenerateCode = async (row) => {
|
||||||
if (!queryParams.competitionId) {
|
if (queryParams.competitionId === null || queryParams.competitionId === '') {
|
||||||
ElMessage.warning('请先选择赛事')
|
ElMessage.warning('请先选择赛事')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查是否有judgeId
|
||||||
|
if (!row.judgeId) {
|
||||||
|
ElMessage.error('数据异常:缺少裁判ID,请使用"从评委库导入"功能')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const res = await generateInviteCode({
|
const res = await generateInviteCode({
|
||||||
@@ -622,52 +688,31 @@ const handleRegenerateCode = async (row) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量生成邀请码
|
* 删除邀请记录
|
||||||
*/
|
*/
|
||||||
const handleBatchGenerateCode = async () => {
|
const handleDelete = async (row) => {
|
||||||
if (!queryParams.competitionId) {
|
|
||||||
ElMessage.warning('请先选择赛事')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selection.value.length === 0) {
|
|
||||||
ElMessage.warning('请先选择评委')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(
|
await ElMessageBox.confirm(
|
||||||
`确定为选中的 ${selection.value.length} 位评委批量生成邀请码吗?`,
|
`确定要删除评委"${row.judgeName}"的邀请记录吗?`,
|
||||||
'批量生成邀请码',
|
'删除确认',
|
||||||
{
|
{
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'info'
|
type: 'warning'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const judgeIds = selection.value.map(item => item.judgeId)
|
await removeInvite(row.id)
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
|
||||||
const res = await batchGenerateInviteCode({
|
// 刷新列表和统计数据
|
||||||
competitionId: queryParams.competitionId,
|
await fetchData()
|
||||||
judgeIds: judgeIds,
|
await loadStatistics()
|
||||||
role: 'judge',
|
|
||||||
expireDays: 30
|
|
||||||
})
|
|
||||||
|
|
||||||
if (res.data && Array.isArray(res.data)) {
|
|
||||||
ElMessage.success(`成功生成 ${res.data.length} 个邀请码`)
|
|
||||||
// 刷新列表
|
|
||||||
await fetchData()
|
|
||||||
await loadStatistics()
|
|
||||||
} else {
|
|
||||||
ElMessage.error(res.msg || '批量生成失败')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error !== 'cancel') {
|
if (error !== 'cancel') {
|
||||||
console.error('批量生成邀请码失败:', error)
|
console.error('删除失败:', error)
|
||||||
ElMessage.error(error.response?.data?.msg || error.message || '批量生成邀请码失败')
|
ElMessage.error(error.response?.data?.msg || error.message || '删除失败')
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -854,4 +899,25 @@ onMounted(() => {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 裁判选择对话框样式
|
||||||
|
.judge-search-form {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
|
||||||
|
.el-form-item {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.judge-pagination {
|
||||||
|
margin-top: 15px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Binary file not shown.
@@ -73,7 +73,6 @@
|
|||||||
type="warning"
|
type="warning"
|
||||||
size="small"
|
size="small"
|
||||||
@click="handleDispatch(scope.row)"
|
@click="handleDispatch(scope.row)"
|
||||||
:disabled="!isScheduleCompleted(scope.row.id)"
|
|
||||||
:title="isScheduleCompleted(scope.row.id) ? '进入调度' : '请先完成编排'"
|
:title="isScheduleCompleted(scope.row.id) ? '进入调度' : '请先完成编排'"
|
||||||
>
|
>
|
||||||
调度
|
调度
|
||||||
|
|||||||
@@ -53,6 +53,17 @@
|
|||||||
<el-option label="对练" value="4" />
|
<el-option label="对练" value="4" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="参赛类型">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.participantType"
|
||||||
|
placeholder="请选择参赛类型"
|
||||||
|
clearable
|
||||||
|
style="width: 150px"
|
||||||
|
>
|
||||||
|
<el-option label="单人" :value="1" />
|
||||||
|
<el-option label="集体" :value="2" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" :icon="Search" @click="handleSearch">
|
<el-button type="primary" :icon="Search" @click="handleSearch">
|
||||||
查询
|
查询
|
||||||
@@ -144,6 +155,12 @@
|
|||||||
<span v-else-if="row.eventType === 4">对练</span>
|
<span v-else-if="row.eventType === 4">对练</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column prop="participantType" label="参赛类型" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag v-if="row.participantType === 1" type="success" size="small">单人</el-tag>
|
||||||
|
<el-tag v-else-if="row.participantType === 2" type="warning" size="small">集体</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
prop="registrationFee"
|
prop="registrationFee"
|
||||||
label="报名费(元)"
|
label="报名费(元)"
|
||||||
@@ -297,6 +314,20 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="参赛类型" prop="participantType">
|
||||||
|
<el-select
|
||||||
|
v-model="form.participantType"
|
||||||
|
placeholder="请选择参赛类型"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option label="单人" :value="1" />
|
||||||
|
<el-option label="集体" :value="2" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="报名费(元)" prop="registrationFee">
|
<el-form-item label="报名费(元)" prop="registrationFee">
|
||||||
<el-input-number
|
<el-input-number
|
||||||
@@ -409,6 +440,10 @@
|
|||||||
<span v-else-if="detailData.eventType === 3">器械</span>
|
<span v-else-if="detailData.eventType === 3">器械</span>
|
||||||
<span v-else-if="detailData.eventType === 4">对练</span>
|
<span v-else-if="detailData.eventType === 4">对练</span>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="参赛类型">
|
||||||
|
<el-tag v-if="detailData.participantType === 1" type="success" size="small">单人</el-tag>
|
||||||
|
<el-tag v-else-if="detailData.participantType === 2" type="warning" size="small">集体</el-tag>
|
||||||
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="报名费">
|
<el-descriptions-item label="报名费">
|
||||||
<span style="color: #f56c6c; font-weight: bold">
|
<span style="color: #f56c6c; font-weight: bold">
|
||||||
¥{{ detailData.registrationFee || 0 }}
|
¥{{ detailData.registrationFee || 0 }}
|
||||||
@@ -490,7 +525,8 @@ const queryParams = reactive({
|
|||||||
competitionId: '',
|
competitionId: '',
|
||||||
projectName: '',
|
projectName: '',
|
||||||
category: '',
|
category: '',
|
||||||
eventType: ''
|
eventType: '',
|
||||||
|
participantType: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
@@ -501,6 +537,7 @@ const form = reactive({
|
|||||||
projectName: '',
|
projectName: '',
|
||||||
category: null,
|
category: null,
|
||||||
eventType: null,
|
eventType: null,
|
||||||
|
participantType: null,
|
||||||
registrationFee: 0,
|
registrationFee: 0,
|
||||||
registrationStartTime: '',
|
registrationStartTime: '',
|
||||||
registrationEndTime: '',
|
registrationEndTime: '',
|
||||||
@@ -539,6 +576,9 @@ const rules = {
|
|||||||
eventType: [
|
eventType: [
|
||||||
{ required: true, message: '请选择项目类型', trigger: 'change' }
|
{ required: true, message: '请选择项目类型', trigger: 'change' }
|
||||||
],
|
],
|
||||||
|
participantType: [
|
||||||
|
{ required: true, message: '请选择参赛类型', trigger: 'change' }
|
||||||
|
],
|
||||||
registrationFee: [
|
registrationFee: [
|
||||||
{ required: true, message: '请输入报名费', trigger: 'blur' }
|
{ required: true, message: '请输入报名费', trigger: 'blur' }
|
||||||
],
|
],
|
||||||
@@ -610,7 +650,8 @@ const handleReset = () => {
|
|||||||
competitionId: '',
|
competitionId: '',
|
||||||
projectName: '',
|
projectName: '',
|
||||||
category: '',
|
category: '',
|
||||||
eventType: ''
|
eventType: '',
|
||||||
|
participantType: ''
|
||||||
})
|
})
|
||||||
fetchData()
|
fetchData()
|
||||||
}
|
}
|
||||||
@@ -721,6 +762,7 @@ const resetForm = () => {
|
|||||||
projectName: '',
|
projectName: '',
|
||||||
category: null,
|
category: null,
|
||||||
eventType: null,
|
eventType: null,
|
||||||
|
participantType: null,
|
||||||
registrationFee: 0,
|
registrationFee: 0,
|
||||||
registrationStartTime: '',
|
registrationStartTime: '',
|
||||||
registrationEndTime: '',
|
registrationEndTime: '',
|
||||||
|
|||||||
@@ -143,7 +143,17 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="等级/职称" prop="level">
|
<el-form-item label="等级/职称" prop="level">
|
||||||
<el-input v-model="formData.level" placeholder="请输入等级/职称" clearable></el-input>
|
<el-select
|
||||||
|
v-model="formData.level"
|
||||||
|
placeholder="请选择等级"
|
||||||
|
clearable
|
||||||
|
style="width: 130px"
|
||||||
|
>
|
||||||
|
<el-option label="国家级" value="国家级" />
|
||||||
|
<el-option label="一级" value="一级" />
|
||||||
|
<el-option label="二级" value="二级" />
|
||||||
|
<el-option label="三级" value="三级" />
|
||||||
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
size="small"
|
size="small"
|
||||||
:type="activeTab === 'competition' ? 'primary' : ''"
|
:type="activeTab === 'competition' ? 'primary' : ''"
|
||||||
@click="activeTab = 'competition'"
|
@click="activeTab = 'competition'"
|
||||||
:disabled="isScheduleCompleted"
|
|
||||||
>
|
>
|
||||||
竞赛分组
|
竞赛分组
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -34,7 +33,6 @@
|
|||||||
size="small"
|
size="small"
|
||||||
:type="activeTab === 'venue' ? 'primary' : ''"
|
:type="activeTab === 'venue' ? 'primary' : ''"
|
||||||
@click="activeTab = 'venue'"
|
@click="activeTab = 'venue'"
|
||||||
:disabled="isScheduleCompleted"
|
|
||||||
>
|
>
|
||||||
场地
|
场地
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -168,10 +166,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<div class="venue-footer-hints">
|
|
||||||
<p class="footer-hint-text">完成编排前的现阶段, 点击放开次文本; 完成编排前在可导列</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user