Files
martial-web/doc/评委邀请码功能实现指南.md
宅房 669f29878b
All checks were successful
continuous-integration/drone/push Build is passing
fix bugs
2025-12-12 17:54:40 +08:00

582 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 评委邀请码功能实现指南
> **实施日期**: 2025-12-12
> **页面路径**: `src/views/martial/judgeInvite/index.vue`
> **与赛事绑定**: ✅ 已通过 `competitionId` 实现
---
## 📋 实现方案
### 一、需求分析
根据文档,评委邀请码功能需要实现:
1. **单个生成**为单个评委生成6位邀请码
2. **批量生成**:为多个评委批量生成邀请码
3. **重新生成**:已有邀请码时可重新生成
4. **复制功能**:点击邀请码可复制
5. **赛事绑定**:所有操作都与选中的赛事绑定
### 二、后端接口(已完成)
后端接口已在 `src/api/martial/judgeInvite.js` 中添加:
```javascript
// 1. 生成邀请码
export const generateInviteCode = (data) => {
return request({
url: '/api/blade-martial/judgeInvite/generate',
method: 'post',
data
})
}
// 2. 批量生成邀请码
export const batchGenerateInviteCode = (data) => {
return request({
url: '/api/blade-martial/judgeInvite/generate/batch',
method: 'post',
data
})
}
// 3. 重新生成邀请码
export const regenerateInviteCode = (inviteId) => {
return request({
url: `/api/blade-martial/judgeInvite/regenerate/${inviteId}`,
method: 'put'
})
}
// 4. 查询评委邀请码
export const getInviteByJudge = (competitionId, judgeId) => {
return request({
url: '/api/blade-martial/judgeInvite/byJudge',
method: 'get',
params: { competitionId, judgeId }
})
}
```
---
## 三、前端实现步骤
### 步骤1导入新增的API
`src/views/martial/judgeInvite/index.vue` 第281-292行修改导入语句
```javascript
import {
getJudgeInviteList,
sendInvite,
batchSendInvites,
resendInvite,
cancelInvite,
confirmInvite,
getInviteStatistics,
importFromJudgePool,
exportInvites,
sendReminder,
generateInviteCode, // 新增
batchGenerateInviteCode, // 新增
regenerateInviteCode // 新增
} from '@/api/martial/judgeInvite'
```
### 步骤2修改邀请码列显示第165-179行
将现有的邀请码列替换为:
```vue
<el-table-column prop="inviteCode" label="邀请码" width="200" align="center">
<template #default="{ row }">
<!-- 已有邀请码显示邀请码 + 重新生成按钮 -->
<div v-if="row.inviteCode" style="display: flex; align-items: center; justify-content: center; gap: 8px;">
<el-tag
type="warning"
effect="dark"
size="default"
style="font-family: monospace; font-weight: bold; cursor: pointer;"
@click="copyToClipboard(row.inviteCode, '邀请码')"
title="点击复制"
>
{{ row.inviteCode }}
</el-tag>
<el-button
link
type="primary"
size="small"
@click="handleRegenerateCode(row)"
title="重新生成邀请码"
>
<el-icon><Refresh /></el-icon>
</el-button>
</div>
<!-- 未生成邀请码显示生成按钮 -->
<el-button
v-else
type="primary"
size="small"
@click="handleGenerateCode(row)"
>
生成邀请码
</el-button>
</template>
</el-table-column>
```
### 步骤3添加批量生成按钮第129-131行
修改工具栏的"批量邀请"按钮功能:
```vue
<el-button type="success" :icon="DocumentCopy" @click="handleBatchGenerateCode">
批量生成邀请码
</el-button>
```
### 步骤4添加方法实现
`<script setup>` 部分在第456行之后添加以下方法
```javascript
// ==================== 邀请码生成功能 ====================
/**
* 生成单个邀请码
*/
const handleGenerateCode = async (row) => {
if (!queryParams.competitionId) {
ElMessage.warning('请先选择赛事')
return
}
try {
loading.value = true
const res = await generateInviteCode({
competitionId: queryParams.competitionId,
judgeId: row.judgeId,
role: row.refereeType === 1 ? 'chief_judge' : 'judge', // 根据评委类型设置角色
venueId: row.venueId || null,
projects: row.projects ? JSON.stringify(row.projects) : null,
expireDays: 30
})
if (res.data && res.data.inviteCode) {
ElMessage.success(`邀请码生成成功:${res.data.inviteCode}`)
// 自动复制到剪贴板
copyToClipboard(res.data.inviteCode, '邀请码')
// 刷新列表
await fetchData()
await loadStatistics()
} else {
ElMessage.error(res.msg || '生成失败')
}
} catch (error) {
console.error('生成邀请码失败:', error)
ElMessage.error(error.response?.data?.msg || error.message || '生成邀请码失败')
} finally {
loading.value = false
}
}
/**
* 重新生成邀请码
*/
const handleRegenerateCode = async (row) => {
try {
await ElMessageBox.confirm(
'重新生成后,旧邀请码将失效。确定继续吗?',
'重新生成邀请码',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
loading.value = true
const res = await regenerateInviteCode(row.id)
if (res.data && res.data.inviteCode) {
ElMessage.success(`邀请码已重新生成:${res.data.inviteCode}`)
// 自动复制到剪贴板
copyToClipboard(res.data.inviteCode, '邀请码')
// 刷新列表
await fetchData()
await loadStatistics()
} else {
ElMessage.error(res.msg || '重新生成失败')
}
} catch (error) {
if (error !== 'cancel') {
console.error('重新生成邀请码失败:', error)
ElMessage.error(error.response?.data?.msg || error.message || '重新生成邀请码失败')
}
} finally {
loading.value = false
}
}
/**
* 批量生成邀请码
*/
const handleBatchGenerateCode = async () => {
if (!queryParams.competitionId) {
ElMessage.warning('请先选择赛事')
return
}
if (selection.value.length === 0) {
ElMessage.warning('请先选择评委')
return
}
try {
await ElMessageBox.confirm(
`确定为选中的 ${selection.value.length} 位评委批量生成邀请码吗?`,
'批量生成邀请码',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'info'
}
)
loading.value = true
const judgeIds = selection.value.map(item => item.judgeId)
const res = await batchGenerateInviteCode({
competitionId: queryParams.competitionId,
judgeIds: judgeIds,
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) {
if (error !== 'cancel') {
console.error('批量生成邀请码失败:', error)
ElMessage.error(error.response?.data?.msg || error.message || '批量生成邀请码失败')
}
} finally {
loading.value = false
}
}
```
---
## 四、数据流转说明
### 1. 赛事绑定流程
```
用户操作:
1. 选择赛事(下拉框)
2. queryParams.competitionId 更新
3. 触发 handleCompetitionChange
4. 加载该赛事的评委列表
5. 显示评委及其邀请码状态
```
### 2. 生成邀请码流程
```
单个生成:
1. 点击"生成邀请码"按钮
2. 调用 generateInviteCode API
3. 传入:
- competitionId: 当前选中的赛事ID
- judgeId: 评委ID
- role: 评委角色
- venueId: 场地ID可选
- projects: 项目列表(可选)
- expireDays: 30天
4. 后端生成6位随机码
5. 返回邀请码
6. 前端自动复制到剪贴板
7. 刷新列表显示
```
### 3. 批量生成流程
```
批量生成:
1. 选择多个评委(勾选)
2. 点击"批量生成邀请码"按钮
3. 确认操作
4. 调用 batchGenerateInviteCode API
5. 传入:
- competitionId: 当前选中的赛事ID
- judgeIds: 评委ID数组
- role: 'judge'
- expireDays: 30天
6. 后端循环为每个评委生成邀请码
7. 返回生成的邀请码列表
8. 刷新列表显示
```
---
## 五、关键字段说明
### 1. 数据表字段martial_judge_invite
| 字段 | 类型 | 说明 | 示例值 |
|------|------|------|--------|
| `id` | bigint | 主键ID | 1001 |
| `competition_id` | bigint | **赛事ID绑定** | 1 |
| `judge_id` | bigint | 评委ID | 5 |
| `invite_code` | varchar(50) | 邀请码 | ABC123 |
| `role` | varchar(20) | 角色 | judge/chief_judge |
| `venue_id` | bigint | 场地ID | 2 |
| `projects` | varchar(500) | 项目列表 | ["太极拳","长拳"] |
| `expire_time` | datetime | 过期时间 | 2026-01-11 |
| `is_used` | int | 是否已使用 | 0/1 |
| `status` | int | 状态 | 1-启用0-禁用 |
### 2. 前端查询参数
```javascript
queryParams = {
current: 1, // 当前页
size: 10, // 每页条数
competitionId: '', // ⭐ 赛事ID核心绑定字段
judgeName: '', // 评委姓名
judgeLevel: '', // 评委等级
inviteStatus: '' // 邀请状态
}
```
---
## 六、测试验证
### 测试场景1单个生成邀请码
**前置条件**
- 已选择赛事
- 列表中有评委记录
- 评委未生成邀请码
**操作步骤**
1. 在评委列表中找到一个未生成邀请码的评委
2. 点击"生成邀请码"按钮
3. 等待生成完成
**预期结果**
- ✅ 显示成功提示:`邀请码生成成功ABC123`
- ✅ 邀请码自动复制到剪贴板
- ✅ 列表刷新,显示生成的邀请码
- ✅ 邀请码列变为:邀请码 + 重新生成按钮
### 测试场景2重新生成邀请码
**前置条件**
- 已选择赛事
- 评委已有邀请码
**操作步骤**
1. 点击邀请码旁边的重新生成按钮
2. 确认操作
3. 等待生成完成
**预期结果**
- ✅ 显示警告提示框
- ✅ 确认后生成新邀请码
- ✅ 旧邀请码失效
- ✅ 新邀请码自动复制到剪贴板
### 测试场景3批量生成邀请码
**前置条件**
- 已选择赛事
- 列表中有多个未生成邀请码的评委
**操作步骤**
1. 勾选多个评委如3个
2. 点击"批量生成邀请码"按钮
3. 确认操作
4. 等待生成完成
**预期结果**
- ✅ 显示确认提示框:`确定为选中的 3 位评委批量生成邀请码吗?`
- ✅ 确认后批量生成
- ✅ 显示成功提示:`成功生成 3 个邀请码`
- ✅ 列表刷新,所有评委都显示邀请码
### 测试场景4复制邀请码
**前置条件**
- 评委已有邀请码
**操作步骤**
1. 点击邀请码标签
2. 粘贴到其他地方验证
**预期结果**
- ✅ 显示成功提示:`邀请码已复制ABC123`
- ✅ 剪贴板中有邀请码内容
### 测试场景5赛事切换
**操作步骤**
1. 在赛事A生成邀请码
2. 切换到赛事B
3. 查看列表
**预期结果**
- ✅ 只显示赛事B的评委列表
- ✅ 赛事A的邀请码不显示
- ✅ 每个赛事的邀请码独立管理
---
## 七、错误处理
### 错误1未选择赛事
```javascript
if (!queryParams.competitionId) {
ElMessage.warning('请先选择赛事')
return
}
```
### 错误2评委已有有效邀请码
后端会返回错误信息:
```json
{
"success": false,
"msg": "该评委已有有效邀请码,请使用重新生成功能"
}
```
前端显示:
```javascript
ElMessage.error(error.response?.data?.msg || '生成邀请码失败')
```
### 错误3邀请码重复
后端会自动重试最多10次前端无需处理
---
## 八、UI/UX优化建议
### 1. 视觉优化
- ✅ 邀请码使用等宽字体(`monospace`
- ✅ 使用警告色标签(`type="warning"`
- ✅ 添加复制提示(`title="点击复制"`
- ✅ 按钮使用图标(`<Refresh />`
### 2. 交互优化
- ✅ 点击邀请码自动复制
- ✅ 重新生成前确认提示
- ✅ 批量生成前确认提示
- ✅ 生成成功后自动复制到剪贴板
- ✅ 操作成功后自动刷新列表
### 3. 加载状态
- ✅ 生成时显示 loading
- ✅ 防止重复点击
- ✅ 异常时显示错误提示
---
## 九、实施检查清单
### 后端准备
- [x] DTO类创建完成
- [x] Service方法实现完成
- [x] Controller接口添加完成
- [x] 数据库表结构正确
- [x] 唯一索引配置正确
### 前端准备
- [x] API接口添加完成`judgeInvite.js`
- [ ] 导入新增API
- [ ] 修改邀请码列UI
- [ ] 添加生成邀请码方法
- [ ] 添加重新生成方法
- [ ] 添加批量生成方法
- [ ] 修改批量邀请按钮功能
### 测试验证
- [ ] 单个生成测试通过
- [ ] 重新生成测试通过
- [ ] 批量生成测试通过
- [ ] 复制功能测试通过
- [ ] 赛事切换测试通过
- [ ] 错误处理测试通过
---
## 十、总结
### 核心要点
1. **赛事绑定**:所有操作都基于 `queryParams.competitionId`
2. **数据隔离**:不同赛事的邀请码完全独立
3. **用户友好**:自动复制、确认提示、状态反馈
4. **错误处理**:完善的错误提示和异常处理
5. **性能优化**:操作后自动刷新,保持数据同步
### 实施时间
- 前端修改30分钟
- 测试验证20分钟
- **总计**50分钟
### 下一步
1. 按照本文档修改前端代码
2. 启动项目测试各功能
3. 根据测试结果调整优化
4. 上线部署
---
**祝您实施顺利!** 🚀
如有问题,请参考:
- 后端实施文档:`评委邀请码生成方案实施指南.md`
- API文档`src/api/martial/judgeInvite.js`
- 页面代码:`src/views/martial/judgeInvite/index.vue`