14 KiB
14 KiB
评委邀请码功能实现指南
实施日期: 2025-12-12 页面路径:
src/views/martial/judgeInvite/index.vue与赛事绑定: ✅ 已通过competitionId实现
📋 实现方案
一、需求分析
根据文档,评委邀请码功能需要实现:
- 单个生成:为单个评委生成6位邀请码
- 批量生成:为多个评委批量生成邀请码
- 重新生成:已有邀请码时可重新生成
- 复制功能:点击邀请码可复制
- 赛事绑定:所有操作都与选中的赛事绑定
二、后端接口(已完成)
后端接口已在 src/api/martial/judgeInvite.js 中添加:
// 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行,修改导入语句:
import {
getJudgeInviteList,
sendInvite,
batchSendInvites,
resendInvite,
cancelInvite,
confirmInvite,
getInviteStatistics,
importFromJudgePool,
exportInvites,
sendReminder,
generateInviteCode, // 新增
batchGenerateInviteCode, // 新增
regenerateInviteCode // 新增
} from '@/api/martial/judgeInvite'
步骤2:修改邀请码列显示(第165-179行)
将现有的邀请码列替换为:
<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行)
修改工具栏的"批量邀请"按钮功能:
<el-button type="success" :icon="DocumentCopy" @click="handleBatchGenerateCode">
批量生成邀请码
</el-button>
步骤4:添加方法实现
在 <script setup> 部分,在第456行之后添加以下方法:
// ==================== 邀请码生成功能 ====================
/**
* 生成单个邀请码
*/
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. 前端查询参数
queryParams = {
current: 1, // 当前页
size: 10, // 每页条数
competitionId: '', // ⭐ 赛事ID(核心绑定字段)
judgeName: '', // 评委姓名
judgeLevel: '', // 评委等级
inviteStatus: '' // 邀请状态
}
六、测试验证
测试场景1:单个生成邀请码
前置条件:
- 已选择赛事
- 列表中有评委记录
- 评委未生成邀请码
操作步骤:
- 在评委列表中找到一个未生成邀请码的评委
- 点击"生成邀请码"按钮
- 等待生成完成
预期结果:
- ✅ 显示成功提示:
邀请码生成成功:ABC123 - ✅ 邀请码自动复制到剪贴板
- ✅ 列表刷新,显示生成的邀请码
- ✅ 邀请码列变为:邀请码 + 重新生成按钮
测试场景2:重新生成邀请码
前置条件:
- 已选择赛事
- 评委已有邀请码
操作步骤:
- 点击邀请码旁边的重新生成按钮
- 确认操作
- 等待生成完成
预期结果:
- ✅ 显示警告提示框
- ✅ 确认后生成新邀请码
- ✅ 旧邀请码失效
- ✅ 新邀请码自动复制到剪贴板
测试场景3:批量生成邀请码
前置条件:
- 已选择赛事
- 列表中有多个未生成邀请码的评委
操作步骤:
- 勾选多个评委(如3个)
- 点击"批量生成邀请码"按钮
- 确认操作
- 等待生成完成
预期结果:
- ✅ 显示确认提示框:
确定为选中的 3 位评委批量生成邀请码吗? - ✅ 确认后批量生成
- ✅ 显示成功提示:
成功生成 3 个邀请码 - ✅ 列表刷新,所有评委都显示邀请码
测试场景4:复制邀请码
前置条件:
- 评委已有邀请码
操作步骤:
- 点击邀请码标签
- 粘贴到其他地方验证
预期结果:
- ✅ 显示成功提示:
邀请码已复制:ABC123 - ✅ 剪贴板中有邀请码内容
测试场景5:赛事切换
操作步骤:
- 在赛事A生成邀请码
- 切换到赛事B
- 查看列表
预期结果:
- ✅ 只显示赛事B的评委列表
- ✅ 赛事A的邀请码不显示
- ✅ 每个赛事的邀请码独立管理
七、错误处理
错误1:未选择赛事
if (!queryParams.competitionId) {
ElMessage.warning('请先选择赛事')
return
}
错误2:评委已有有效邀请码
后端会返回错误信息:
{
"success": false,
"msg": "该评委已有有效邀请码,请使用重新生成功能"
}
前端显示:
ElMessage.error(error.response?.data?.msg || '生成邀请码失败')
错误3:邀请码重复
后端会自动重试(最多10次),前端无需处理
八、UI/UX优化建议
1. 视觉优化
- ✅ 邀请码使用等宽字体(
monospace) - ✅ 使用警告色标签(
type="warning") - ✅ 添加复制提示(
title="点击复制") - ✅ 按钮使用图标(
<Refresh />)
2. 交互优化
- ✅ 点击邀请码自动复制
- ✅ 重新生成前确认提示
- ✅ 批量生成前确认提示
- ✅ 生成成功后自动复制到剪贴板
- ✅ 操作成功后自动刷新列表
3. 加载状态
- ✅ 生成时显示 loading
- ✅ 防止重复点击
- ✅ 异常时显示错误提示
九、实施检查清单
后端准备
- DTO类创建完成
- Service方法实现完成
- Controller接口添加完成
- 数据库表结构正确
- 唯一索引配置正确
前端准备
- API接口添加完成(
judgeInvite.js) - 导入新增API
- 修改邀请码列UI
- 添加生成邀请码方法
- 添加重新生成方法
- 添加批量生成方法
- 修改批量邀请按钮功能
测试验证
- 单个生成测试通过
- 重新生成测试通过
- 批量生成测试通过
- 复制功能测试通过
- 赛事切换测试通过
- 错误处理测试通过
十、总结
核心要点
- 赛事绑定:所有操作都基于
queryParams.competitionId - 数据隔离:不同赛事的邀请码完全独立
- 用户友好:自动复制、确认提示、状态反馈
- 错误处理:完善的错误提示和异常处理
- 性能优化:操作后自动刷新,保持数据同步
实施时间
- 前端修改:30分钟
- 测试验证:20分钟
- 总计:50分钟
下一步
- 按照本文档修改前端代码
- 启动项目测试各功能
- 根据测试结果调整优化
- 上线部署
祝您实施顺利! 🚀
如有问题,请参考:
- 后端实施文档:
评委邀请码生成方案实施指南.md - API文档:
src/api/martial/judgeInvite.js - 页面代码:
src/views/martial/judgeInvite/index.vue