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

14 KiB
Raw Blame History

评委邀请码功能实现指南

实施日期: 2025-12-12 页面路径: src/views/martial/judgeInvite/index.vue 与赛事绑定: 已通过 competitionId 实现


📋 实现方案

一、需求分析

根据文档,评委邀请码功能需要实现:

  1. 单个生成为单个评委生成6位邀请码
  2. 批量生成:为多个评委批量生成邀请码
  3. 重新生成:已有邀请码时可重新生成
  4. 复制功能:点击邀请码可复制
  5. 赛事绑定:所有操作都与选中的赛事绑定

二、后端接口(已完成)

后端接口已在 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单个生成邀请码

前置条件

  • 已选择赛事
  • 列表中有评委记录
  • 评委未生成邀请码

操作步骤

  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未选择赛事

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
  • 添加生成邀请码方法
  • 添加重新生成方法
  • 添加批量生成方法
  • 修改批量邀请按钮功能

测试验证

  • 单个生成测试通过
  • 重新生成测试通过
  • 批量生成测试通过
  • 复制功能测试通过
  • 赛事切换测试通过
  • 错误处理测试通过

十、总结

核心要点

  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