feat: 修复员工管理功能并优化UI
- 修复角色下拉框数据加载问题,确保正确访问GraphQL响应数据
- 修复员工搜索功能,使用正确的GraphQL查询名称和参数
- 优化员工列表操作栏样式,使用无背景的彩色图标
- 重构前端页面结构,统一命名规范
- 修复多个API的数据访问路径问题
- 添加角色管理相关的调试和修复工具
20个文件已修改
7个文件已添加
13 文件已重命名
4个文件已删除
| | |
| | | import com.rongyichuang.common.dto.PageResponse; |
| | | import com.rongyichuang.rating.entity.RatingScheme; |
| | | import com.rongyichuang.rating.repository.RatingSchemeRepository; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.data.domain.Example; |
| | | import org.springframework.data.domain.ExampleMatcher; |
| | |
| | | @Service |
| | | @Transactional |
| | | public class ActivityService { |
| | | |
| | | private static final Logger log = LoggerFactory.getLogger(ActivityService.class); |
| | | |
| | | @Autowired |
| | | private ActivityRepository activityRepository; |
| | |
| | | // 保存比赛 |
| | | activity = activityRepository.save(activity); |
| | | |
| | | // 记录日志以便调试 |
| | | log.info("保存比赛成功,比赛ID: {}, 比赛名称: {}", activity.getId(), activity.getName()); |
| | | |
| | | // 如果是比赛且有阶段信息,保存阶段 |
| | | if (input.isMainActivity() && input.getStages() != null && !input.getStages().isEmpty()) { |
| | | saveActivityStages(activity.getId(), input.getStages()); |
| | |
| | | stage.setRatingSchemeId(activity.getRatingSchemeId()); |
| | | } |
| | | |
| | | activityRepository.save(stage); |
| | | // 保存阶段并获取自增ID |
| | | stage = activityRepository.save(stage); |
| | | |
| | | // 记录日志以便调试 |
| | | log.info("保存阶段成功,阶段ID: {}, 阶段名称: {}", stage.getId(), stage.getName()); |
| | | } |
| | | } |
| | | |
| | |
| | | */ |
| | | private void saveActivityJudges(Long activityId, List<ActivityJudgeInput> judgeInputs) { |
| | | if (judgeInputs == null || judgeInputs.isEmpty()) { |
| | | log.info("没有评委需要保存,比赛ID: {}", activityId); |
| | | return; |
| | | } |
| | | |
| | | log.info("开始保存评委,比赛ID: {}, 评委数量: {}", activityId, judgeInputs.size()); |
| | | |
| | | // 获取比赛的所有阶段(如果有的话) |
| | | List<Activity> stages = activityRepository.findByPidAndStateOrderByCreateTimeAsc(activityId, 1); |
| | | |
| | | // 保存评委关联 |
| | | for (ActivityJudgeInput judgeInput : judgeInputs) { |
| | | log.info("处理评委: ID={}, 姓名={}, 阶段IDs={}", |
| | | judgeInput.getJudgeId(), judgeInput.getJudgeName(), judgeInput.getStageIds()); |
| | | |
| | | // 先删除该评委的现有关联 |
| | | activityJudgeRepository.deleteByActivityIdAndJudgeId(activityId, judgeInput.getJudgeId()); |
| | | |
| | |
| | | if (stages.isEmpty()) { |
| | | // 比赛没有阶段,直接关联到比赛(stage_id为null表示所有阶段) |
| | | ActivityJudge activityJudge = new ActivityJudge(activityId, judgeInput.getJudgeId(), null); |
| | | activityJudgeRepository.save(activityJudge); |
| | | activityJudge = activityJudgeRepository.save(activityJudge); |
| | | log.info("保存评委关联成功: 比赛ID={}, 评委ID={}, 阶段ID=null", activityId, judgeInput.getJudgeId()); |
| | | } else { |
| | | // 为每个阶段创建关联 |
| | | for (Activity stage : stages) { |
| | | ActivityJudge activityJudge = new ActivityJudge(activityId, judgeInput.getJudgeId(), stage.getId()); |
| | | activityJudgeRepository.save(activityJudge); |
| | | activityJudge = activityJudgeRepository.save(activityJudge); |
| | | log.info("保存评委关联成功: 比赛ID={}, 评委ID={}, 阶段ID={}", activityId, judgeInput.getJudgeId(), stage.getId()); |
| | | } |
| | | } |
| | | } else { |
| | |
| | | // 如果stageId等于当前比赛ID,表示评委负责整个比赛,stage_id设为null |
| | | Long actualStageId = stageId.equals(activityId) ? null : stageId; |
| | | ActivityJudge activityJudge = new ActivityJudge(activityId, judgeInput.getJudgeId(), actualStageId); |
| | | activityJudgeRepository.save(activityJudge); |
| | | activityJudge = activityJudgeRepository.save(activityJudge); |
| | | log.info("保存评委关联成功: 比赛ID={}, 评委ID={}, 阶段ID={}", activityId, judgeInput.getJudgeId(), actualStageId); |
| | | } |
| | | } |
| | | } |
| | |
| | | @SuppressWarnings("unchecked") |
| | | public PageResponse<ActivityPlayerApplicationResponse> listApplications(String name, Long activityId, Integer state, Integer page, Integer size) { |
| | | String baseSql = |
| | | "SELECT ap.id, CONCAT(p.name, '(', ap.project_name, ')') AS player_name, parent.name AS activity_name, ap.project_name AS project_name, p.phone AS phone, ap.create_time AS apply_time, ap.state AS state " + |
| | | "SELECT ap.id, p.name AS player_name, parent.name AS activity_name, ap.project_name AS project_name, p.phone AS phone, ap.create_time AS apply_time, ap.state AS state, " + |
| | | "COALESCE(rating_stats.rating_count, 0) AS rating_count, rating_stats.average_score " + |
| | | "FROM t_activity_player ap " + |
| | | "JOIN t_player p ON p.id = ap.player_id " + |
| | | "JOIN t_activity stage ON stage.id = ap.stage_id " + |
| | | "JOIN t_activity parent ON parent.id = stage.pid "; |
| | | "JOIN t_activity parent ON parent.id = stage.pid " + |
| | | "LEFT JOIN (" + |
| | | " SELECT activity_player_id, COUNT(*) AS rating_count, AVG(total_score) AS average_score " + |
| | | " FROM t_activity_player_rating " + |
| | | " WHERE state = 1 " + |
| | | " GROUP BY activity_player_id" + |
| | | ") rating_stats ON rating_stats.activity_player_id = ap.id "; |
| | | |
| | | StringBuilder whereClause = new StringBuilder(); |
| | | boolean hasCondition = false; |
New file |
| | |
| | | # Fix employee role codes |
| | | $API_URL = "http://localhost:8080/api/graphql" |
| | | $TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIyIiwicGhvbmUiOiIxMzk4MTk3MDgxNiIsImlhdCI6MTc1OTMyMzk3MSwiZXhwIjoxNzU5NDEwMzcxfQ.z8n5QuCpALluVJD9JShNsjQSaPqm91HcKV-5PB3jLN4" |
| | | |
| | | $headers = @{ |
| | | "Content-Type" = "application/json" |
| | | "Authorization" = "Bearer $TOKEN" |
| | | } |
| | | |
| | | Write-Host "Getting employees..." |
| | | $employeesQuery = '{"query":"query { employees { id name phone roleId description } }"}' |
| | | $employeesResponse = Invoke-RestMethod -Uri $API_URL -Method POST -Headers $headers -Body $employeesQuery |
| | | $employees = $employeesResponse.data.employees |
| | | |
| | | Write-Host "Found $($employees.Count) employees" |
| | | |
| | | Write-Host "Getting valid roles..." |
| | | $rolesQuery = '{"query":"query { activeRoles { id code name description } }"}' |
| | | $rolesResponse = Invoke-RestMethod -Uri $API_URL -Method POST -Headers $headers -Body $rolesQuery |
| | | $validRoles = $rolesResponse.data.activeRoles |
| | | |
| | | $validRoleCodes = $validRoles | ForEach-Object { $_.code } |
| | | $invalidEmployees = $employees | Where-Object { $_.roleId -notin $validRoleCodes } |
| | | |
| | | if ($invalidEmployees.Count -eq 0) { |
| | | Write-Host "All employee role codes are valid!" |
| | | exit 0 |
| | | } |
| | | |
| | | Write-Host "Found $($invalidEmployees.Count) employees with invalid role codes" |
| | | |
| | | $roleMapping = @{ |
| | | "MANAGER" = "SUPER_ADMIN" |
| | | "EMPLOYEE" = "AUDITOR" |
| | | "ADMIN" = "SUPER_ADMIN" |
| | | } |
| | | |
| | | $fixedCount = 0 |
| | | $errorCount = 0 |
| | | |
| | | foreach ($emp in $invalidEmployees) { |
| | | $newRoleId = $roleMapping[$emp.roleId] |
| | | if (-not $newRoleId) { |
| | | $newRoleId = "AUDITOR" |
| | | } |
| | | |
| | | Write-Host "Fixing employee $($emp.name): $($emp.roleId) -> $newRoleId" |
| | | |
| | | $mutationBody = @{ |
| | | query = 'mutation SaveEmployee($input: EmployeeInput!) { saveEmployee(input: $input) { id name roleId } }' |
| | | variables = @{ |
| | | input = @{ |
| | | id = [int]$emp.id |
| | | name = $emp.name |
| | | phone = $emp.phone |
| | | roleId = $newRoleId |
| | | description = $emp.description |
| | | } |
| | | } |
| | | } |
| | | |
| | | $mutation = $mutationBody | ConvertTo-Json -Depth 10 |
| | | |
| | | try { |
| | | $result = Invoke-RestMethod -Uri $API_URL -Method POST -Headers $headers -Body $mutation |
| | | if ($result.errors) { |
| | | Write-Host " Error: $($result.errors[0].message)" -ForegroundColor Red |
| | | $errorCount++ |
| | | } else { |
| | | Write-Host " Success!" -ForegroundColor Green |
| | | $fixedCount++ |
| | | } |
| | | } catch { |
| | | Write-Host " Exception: $($_.Exception.Message)" -ForegroundColor Red |
| | | $errorCount++ |
| | | } |
| | | } |
| | | |
| | | Write-Host "Fix completed!" |
| | | Write-Host "Successfully fixed: $fixedCount employees" |
| | | Write-Host "Failed to fix: $errorCount employees" |
New file |
| | |
| | | -- 修复员工表中的无效角色代码 |
| | | -- 将无效的角色代码替换为有效的角色代码 |
| | | |
| | | -- 查看当前员工表中的角色代码分布 |
| | | SELECT role_id, COUNT(*) as count FROM t_employee WHERE state = 1 GROUP BY role_id; |
| | | |
| | | -- 查看有效的角色代码 |
| | | SELECT code, name FROM t_role WHERE state = 1; |
| | | |
| | | -- 修复无效的角色代码 |
| | | -- MANAGER -> SUPER_ADMIN (管理员) |
| | | UPDATE t_employee SET role_id = 'SUPER_ADMIN' WHERE role_id = 'MANAGER' AND state = 1; |
| | | |
| | | -- EMPLOYEE -> AUDITOR (平台工作人员) |
| | | UPDATE t_employee SET role_id = 'AUDITOR' WHERE role_id = 'EMPLOYEE' AND state = 1; |
| | | |
| | | -- ADMIN -> SUPER_ADMIN (超级管理员) |
| | | UPDATE t_employee SET role_id = 'SUPER_ADMIN' WHERE role_id = 'ADMIN' AND state = 1; |
| | | |
| | | -- 验证修复结果 |
| | | SELECT role_id, COUNT(*) as count FROM t_employee WHERE state = 1 GROUP BY role_id; |
| | | |
| | | -- 检查是否还有无效的角色代码 |
| | | SELECT e.id, e.name, e.role_id |
| | | FROM t_employee e |
| | | LEFT JOIN t_role r ON e.role_id = r.code AND r.state = 1 |
| | | WHERE e.state = 1 AND r.code IS NULL; |
New file |
| | |
| | | <!DOCTYPE html> |
| | | <html lang="zh-CN"> |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | | <title>认证状态检查</title> |
| | | <style> |
| | | body { font-family: Arial, sans-serif; margin: 20px; } |
| | | .result { margin: 10px 0; padding: 10px; border: 1px solid #ccc; } |
| | | .error { background-color: #ffe6e6; } |
| | | .success { background-color: #e6ffe6; } |
| | | button { padding: 10px 20px; margin: 5px; } |
| | | </style> |
| | | </head> |
| | | <body> |
| | | <h1>认证状态检查</h1> |
| | | |
| | | <button onclick="checkAuth()">检查认证状态</button> |
| | | <button onclick="testRoleAPI()">测试角色API</button> |
| | | <button onclick="loginAsAdmin()">模拟管理员登录</button> |
| | | |
| | | <div id="results"></div> |
| | | |
| | | <script> |
| | | function addResult(content, isError = false) { |
| | | const div = document.createElement('div'); |
| | | div.className = `result ${isError ? 'error' : 'success'}`; |
| | | div.innerHTML = content; |
| | | document.getElementById('results').appendChild(div); |
| | | } |
| | | |
| | | function checkAuth() { |
| | | const token = localStorage.getItem('auth_token'); |
| | | const userInfo = localStorage.getItem('user_info'); |
| | | |
| | | addResult(` |
| | | <strong>认证状态:</strong><br> |
| | | Token: ${token ? '存在' : '不存在'}<br> |
| | | User Info: ${userInfo ? '存在' : '不存在'}<br> |
| | | ${token ? `Token内容: ${token.substring(0, 50)}...` : ''} |
| | | `); |
| | | } |
| | | |
| | | async function testRoleAPI() { |
| | | try { |
| | | const token = localStorage.getItem('auth_token'); |
| | | const headers = { |
| | | 'Content-Type': 'application/json' |
| | | }; |
| | | |
| | | if (token) { |
| | | headers['Authorization'] = `Bearer ${token}`; |
| | | } |
| | | |
| | | const response = await fetch('/api/graphql', { |
| | | method: 'POST', |
| | | headers, |
| | | body: JSON.stringify({ |
| | | query: `query GetActiveRoles { |
| | | activeRoles { |
| | | id |
| | | code |
| | | name |
| | | description |
| | | state |
| | | createTime |
| | | updateTime |
| | | } |
| | | }` |
| | | }) |
| | | }); |
| | | |
| | | const data = await response.json(); |
| | | |
| | | if (data.errors) { |
| | | addResult(`<strong>角色API错误:</strong><br>${JSON.stringify(data.errors, null, 2)}`, true); |
| | | } else { |
| | | addResult(`<strong>角色API成功:</strong><br>角色数量: ${data.data?.activeRoles?.length || 0}<br>数据: <pre>${JSON.stringify(data.data, null, 2)}</pre>`); |
| | | } |
| | | } catch (error) { |
| | | addResult(`<strong>角色API调用失败:</strong><br>${error.message}`, true); |
| | | } |
| | | } |
| | | |
| | | async function loginAsAdmin() { |
| | | try { |
| | | const response = await fetch('/api/graphql', { |
| | | method: 'POST', |
| | | headers: { |
| | | 'Content-Type': 'application/json' |
| | | }, |
| | | body: JSON.stringify({ |
| | | query: `mutation Login($phone: String!, $password: String!) { |
| | | login(phone: $phone, password: $password) { |
| | | token |
| | | user { |
| | | userId |
| | | name |
| | | phone |
| | | userType |
| | | } |
| | | } |
| | | }`, |
| | | variables: { |
| | | phone: "13800000001", |
| | | password: "123456" |
| | | } |
| | | }) |
| | | }); |
| | | |
| | | const data = await response.json(); |
| | | |
| | | if (data.errors) { |
| | | addResult(`<strong>登录失败:</strong><br>${JSON.stringify(data.errors, null, 2)}`, true); |
| | | } else if (data.data?.login?.token) { |
| | | localStorage.setItem('auth_token', data.data.login.token); |
| | | localStorage.setItem('user_info', JSON.stringify(data.data.login.user)); |
| | | addResult(`<strong>登录成功:</strong><br>Token已保存<br>用户信息: ${JSON.stringify(data.data.login.user, null, 2)}`); |
| | | } else { |
| | | addResult(`<strong>登录响应异常:</strong><br>${JSON.stringify(data, null, 2)}`, true); |
| | | } |
| | | } catch (error) { |
| | | addResult(`<strong>登录请求失败:</strong><br>${error.message}`, true); |
| | | } |
| | | } |
| | | </script> |
| | | </body> |
| | | </html> |
New file |
| | |
| | | <!DOCTYPE html> |
| | | <html lang="zh-CN"> |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | | <title>角色API调试</title> |
| | | <style> |
| | | body { font-family: Arial, sans-serif; margin: 20px; } |
| | | .result { margin: 10px 0; padding: 10px; border: 1px solid #ccc; } |
| | | .error { background-color: #ffe6e6; } |
| | | .success { background-color: #e6ffe6; } |
| | | button { padding: 10px 20px; margin: 5px; } |
| | | </style> |
| | | </head> |
| | | <body> |
| | | <h1>角色API调试</h1> |
| | | |
| | | <button onclick="testWithoutAuth()">测试无认证API调用</button> |
| | | <button onclick="testWithAuth()">测试带认证API调用</button> |
| | | <button onclick="checkLocalStorage()">检查本地存储</button> |
| | | |
| | | <div id="results"></div> |
| | | |
| | | <script> |
| | | function addResult(content, isError = false) { |
| | | const div = document.createElement('div'); |
| | | div.className = `result ${isError ? 'error' : 'success'}`; |
| | | div.innerHTML = content; |
| | | document.getElementById('results').appendChild(div); |
| | | } |
| | | |
| | | async function testWithoutAuth() { |
| | | try { |
| | | const response = await fetch('/api/graphql', { |
| | | method: 'POST', |
| | | headers: { |
| | | 'Content-Type': 'application/json' |
| | | }, |
| | | body: JSON.stringify({ |
| | | query: `query GetActiveRoles { |
| | | activeRoles { |
| | | id |
| | | code |
| | | name |
| | | description |
| | | state |
| | | createTime |
| | | updateTime |
| | | } |
| | | }` |
| | | }) |
| | | }); |
| | | |
| | | const data = await response.json(); |
| | | addResult(`<strong>无认证API调用结果:</strong><br>状态: ${response.status}<br>数据: <pre>${JSON.stringify(data, null, 2)}</pre>`); |
| | | } catch (error) { |
| | | addResult(`<strong>无认证API调用失败:</strong><br>${error.message}`, true); |
| | | } |
| | | } |
| | | |
| | | async function testWithAuth() { |
| | | try { |
| | | const token = localStorage.getItem('token'); |
| | | const response = await fetch('/api/graphql', { |
| | | method: 'POST', |
| | | headers: { |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': token ? `Bearer ${token}` : '' |
| | | }, |
| | | body: JSON.stringify({ |
| | | query: `query GetActiveRoles { |
| | | activeRoles { |
| | | id |
| | | code |
| | | name |
| | | description |
| | | state |
| | | createTime |
| | | updateTime |
| | | } |
| | | }` |
| | | }) |
| | | }); |
| | | |
| | | const data = await response.json(); |
| | | addResult(`<strong>带认证API调用结果:</strong><br>状态: ${response.status}<br>Token: ${token ? '存在' : '不存在'}<br>数据: <pre>${JSON.stringify(data, null, 2)}</pre>`); |
| | | } catch (error) { |
| | | addResult(`<strong>带认证API调用失败:</strong><br>${error.message}`, true); |
| | | } |
| | | } |
| | | |
| | | function checkLocalStorage() { |
| | | const token = localStorage.getItem('token'); |
| | | const user = localStorage.getItem('user'); |
| | | addResult(`<strong>本地存储检查:</strong><br>Token: ${token || '无'}<br>User: ${user || '无'}`); |
| | | } |
| | | </script> |
| | | </body> |
| | | </html> |
New file |
| | |
| | | <!DOCTYPE html> |
| | | <html lang="zh-CN"> |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | | <title>修复员工角色代码</title> |
| | | <style> |
| | | body { |
| | | font-family: Arial, sans-serif; |
| | | max-width: 800px; |
| | | margin: 0 auto; |
| | | padding: 20px; |
| | | } |
| | | .section { |
| | | margin: 20px 0; |
| | | padding: 15px; |
| | | border: 1px solid #ddd; |
| | | border-radius: 5px; |
| | | } |
| | | button { |
| | | background-color: #007bff; |
| | | color: white; |
| | | border: none; |
| | | padding: 10px 20px; |
| | | border-radius: 4px; |
| | | cursor: pointer; |
| | | margin: 5px; |
| | | } |
| | | button:hover { |
| | | background-color: #0056b3; |
| | | } |
| | | .error { |
| | | color: red; |
| | | } |
| | | .success { |
| | | color: green; |
| | | } |
| | | .log { |
| | | background-color: #f8f9fa; |
| | | border: 1px solid #dee2e6; |
| | | border-radius: 4px; |
| | | padding: 10px; |
| | | margin: 10px 0; |
| | | white-space: pre-wrap; |
| | | font-family: monospace; |
| | | max-height: 300px; |
| | | overflow-y: auto; |
| | | } |
| | | </style> |
| | | </head> |
| | | <body> |
| | | <h1>修复员工角色代码</h1> |
| | | |
| | | <div class="section"> |
| | | <h3>1. 检查认证状态</h3> |
| | | <button onclick="checkAuth()">检查认证状态</button> |
| | | <div id="authStatus"></div> |
| | | </div> |
| | | |
| | | <div class="section"> |
| | | <h3>2. 获取所有员工</h3> |
| | | <button onclick="fetchEmployees()">获取员工列表</button> |
| | | <div id="employeeList"></div> |
| | | </div> |
| | | |
| | | <div class="section"> |
| | | <h3>3. 获取有效角色</h3> |
| | | <button onclick="fetchRoles()">获取角色列表</button> |
| | | <div id="roleList"></div> |
| | | </div> |
| | | |
| | | <div class="section"> |
| | | <h3>4. 修复无效角色</h3> |
| | | <button onclick="fixInvalidRoles()">修复无效角色代码</button> |
| | | <div id="fixResult"></div> |
| | | </div> |
| | | |
| | | <div class="section"> |
| | | <h3>操作日志</h3> |
| | | <div id="log" class="log"></div> |
| | | <button onclick="clearLog()">清空日志</button> |
| | | </div> |
| | | |
| | | <script> |
| | | const API_BASE_URL = 'http://localhost:8080/api'; |
| | | const GRAPHQL_ENDPOINT = `${API_BASE_URL}/graphql`; |
| | | |
| | | let employees = []; |
| | | let validRoles = []; |
| | | |
| | | function log(message) { |
| | | const logDiv = document.getElementById('log'); |
| | | const timestamp = new Date().toLocaleTimeString(); |
| | | logDiv.textContent += `[${timestamp}] ${message}\n`; |
| | | logDiv.scrollTop = logDiv.scrollHeight; |
| | | } |
| | | |
| | | function clearLog() { |
| | | document.getElementById('log').textContent = ''; |
| | | } |
| | | |
| | | async function graphqlRequest(query, variables = {}) { |
| | | const token = localStorage.getItem('token'); |
| | | |
| | | const response = await fetch(GRAPHQL_ENDPOINT, { |
| | | method: 'POST', |
| | | headers: { |
| | | 'Content-Type': 'application/json', |
| | | ...(token && { 'Authorization': `Bearer ${token}` }) |
| | | }, |
| | | body: JSON.stringify({ |
| | | query, |
| | | variables |
| | | }) |
| | | }); |
| | | |
| | | const result = await response.json(); |
| | | |
| | | if (result.errors) { |
| | | throw new Error(result.errors[0].message); |
| | | } |
| | | |
| | | return result.data; |
| | | } |
| | | |
| | | async function checkAuth() { |
| | | const statusDiv = document.getElementById('authStatus'); |
| | | const token = localStorage.getItem('token'); |
| | | |
| | | if (!token) { |
| | | statusDiv.innerHTML = '<span class="error">未找到认证令牌,请先登录</span>'; |
| | | log('错误: 未找到认证令牌'); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | // 尝试获取用户信息来验证token |
| | | const query = ` |
| | | query { |
| | | employees { |
| | | id |
| | | name |
| | | } |
| | | } |
| | | `; |
| | | |
| | | await graphqlRequest(query); |
| | | statusDiv.innerHTML = '<span class="success">认证状态正常</span>'; |
| | | log('认证状态检查通过'); |
| | | } catch (error) { |
| | | statusDiv.innerHTML = `<span class="error">认证失败: ${error.message}</span>`; |
| | | log(`认证失败: ${error.message}`); |
| | | } |
| | | } |
| | | |
| | | async function fetchEmployees() { |
| | | const listDiv = document.getElementById('employeeList'); |
| | | |
| | | try { |
| | | const query = ` |
| | | query { |
| | | employees { |
| | | id |
| | | name |
| | | phone |
| | | roleId |
| | | description |
| | | } |
| | | } |
| | | `; |
| | | |
| | | const data = await graphqlRequest(query); |
| | | employees = data.employees; |
| | | |
| | | let html = `<p>找到 ${employees.length} 个员工:</p><ul>`; |
| | | employees.forEach(emp => { |
| | | html += `<li>ID: ${emp.id}, 姓名: ${emp.name}, 角色: ${emp.roleId}</li>`; |
| | | }); |
| | | html += '</ul>'; |
| | | |
| | | listDiv.innerHTML = html; |
| | | log(`成功获取 ${employees.length} 个员工`); |
| | | } catch (error) { |
| | | listDiv.innerHTML = `<span class="error">获取员工失败: ${error.message}</span>`; |
| | | log(`获取员工失败: ${error.message}`); |
| | | } |
| | | } |
| | | |
| | | async function fetchRoles() { |
| | | const listDiv = document.getElementById('roleList'); |
| | | |
| | | try { |
| | | const query = ` |
| | | query { |
| | | activeRoles { |
| | | id |
| | | code |
| | | name |
| | | description |
| | | } |
| | | } |
| | | `; |
| | | |
| | | const data = await graphqlRequest(query); |
| | | validRoles = data.activeRoles; |
| | | |
| | | let html = `<p>找到 ${validRoles.length} 个有效角色:</p><ul>`; |
| | | validRoles.forEach(role => { |
| | | html += `<li>代码: ${role.code}, 名称: ${role.name}</li>`; |
| | | }); |
| | | html += '</ul>'; |
| | | |
| | | listDiv.innerHTML = html; |
| | | log(`成功获取 ${validRoles.length} 个有效角色`); |
| | | } catch (error) { |
| | | listDiv.innerHTML = `<span class="error">获取角色失败: ${error.message}</span>`; |
| | | log(`获取角色失败: ${error.message}`); |
| | | } |
| | | } |
| | | |
| | | async function fixInvalidRoles() { |
| | | const resultDiv = document.getElementById('fixResult'); |
| | | |
| | | if (employees.length === 0) { |
| | | resultDiv.innerHTML = '<span class="error">请先获取员工列表</span>'; |
| | | return; |
| | | } |
| | | |
| | | if (validRoles.length === 0) { |
| | | resultDiv.innerHTML = '<span class="error">请先获取角色列表</span>'; |
| | | return; |
| | | } |
| | | |
| | | const validRoleCodes = validRoles.map(role => role.code); |
| | | const invalidEmployees = employees.filter(emp => !validRoleCodes.includes(emp.roleId)); |
| | | |
| | | if (invalidEmployees.length === 0) { |
| | | resultDiv.innerHTML = '<span class="success">所有员工的角色代码都是有效的</span>'; |
| | | log('所有员工的角色代码都是有效的'); |
| | | return; |
| | | } |
| | | |
| | | log(`发现 ${invalidEmployees.length} 个员工的角色代码无效`); |
| | | |
| | | // 角色映射规则 |
| | | const roleMapping = { |
| | | 'MANAGER': 'SUPER_ADMIN', |
| | | 'EMPLOYEE': 'AUDITOR', |
| | | 'ADMIN': 'SUPER_ADMIN' |
| | | }; |
| | | |
| | | let fixedCount = 0; |
| | | let errors = []; |
| | | |
| | | for (const emp of invalidEmployees) { |
| | | const newRoleId = roleMapping[emp.roleId] || 'AUDITOR'; // 默认映射到AUDITOR |
| | | |
| | | try { |
| | | const mutation = ` |
| | | mutation SaveEmployee($input: EmployeeInput!) { |
| | | saveEmployee(input: $input) { |
| | | id |
| | | name |
| | | roleId |
| | | } |
| | | } |
| | | `; |
| | | |
| | | const variables = { |
| | | input: { |
| | | id: parseInt(emp.id), |
| | | name: emp.name, |
| | | phone: emp.phone, |
| | | roleId: newRoleId, |
| | | description: emp.description |
| | | } |
| | | }; |
| | | |
| | | await graphqlRequest(mutation, variables); |
| | | fixedCount++; |
| | | log(`修复员工 ${emp.name} (ID: ${emp.id}): ${emp.roleId} -> ${newRoleId}`); |
| | | } catch (error) { |
| | | errors.push(`员工 ${emp.name} (ID: ${emp.id}): ${error.message}`); |
| | | log(`修复员工 ${emp.name} 失败: ${error.message}`); |
| | | } |
| | | } |
| | | |
| | | let html = `<p class="success">成功修复 ${fixedCount} 个员工的角色代码</p>`; |
| | | if (errors.length > 0) { |
| | | html += `<p class="error">修复失败 ${errors.length} 个:</p><ul>`; |
| | | errors.forEach(error => { |
| | | html += `<li>${error}</li>`; |
| | | }); |
| | | html += '</ul>'; |
| | | } |
| | | |
| | | resultDiv.innerHTML = html; |
| | | log(`修复完成: 成功 ${fixedCount} 个, 失败 ${errors.length} 个`); |
| | | } |
| | | |
| | | // 页面加载时检查认证状态 |
| | | window.onload = function() { |
| | | checkAuth(); |
| | | }; |
| | | </script> |
| | | </body> |
| | | </html> |
| | |
| | | // API 函数 |
| | | export const getActivities = async (page = 0, size = 10, name = '') => { |
| | | try { |
| | | const data = await graphqlRequest(GET_ACTIVITIES_QUERY, { page, size, name }); |
| | | return data.activities; |
| | | const result = await graphqlRequest(GET_ACTIVITIES_QUERY, { page, size, name }); |
| | | return result.data.activities; |
| | | } catch (error) { |
| | | throw new Error(error.message || '获取比赛列表失败'); |
| | | } |
| | |
| | | |
| | | export const getActivity = async (id) => { |
| | | try { |
| | | const data = await graphqlRequest(GET_ACTIVITY_QUERY, { id }); |
| | | return data.activity; |
| | | const result = await graphqlRequest(GET_ACTIVITY_QUERY, { id }); |
| | | return result.data.activity; |
| | | } catch (error) { |
| | | throw new Error(error.message || '获取比赛详情失败'); |
| | | } |
| | |
| | | |
| | | export const getAllActivities = async () => { |
| | | try { |
| | | console.log('=== getAllActivities API调用开始 ==='); |
| | | console.log('GraphQL查询:', GET_ALL_ACTIVITIES_QUERY); |
| | | const result = await graphqlRequest(GET_ALL_ACTIVITIES_QUERY); |
| | | |
| | | const data = await graphqlRequest(GET_ALL_ACTIVITIES_QUERY); |
| | | console.log('GraphQL返回的原始数据:', data); |
| | | console.log('data.allActivities:', data.allActivities); |
| | | |
| | | return data.allActivities; |
| | | return result.data.allActivities; |
| | | } catch (error) { |
| | | console.error('=== getAllActivities API调用失败 ==='); |
| | | console.error('错误详情:', error); |
| | |
| | | export const CarouselApi = { |
| | | // 分页查询轮播图 |
| | | getCarousels: async (page = 0, size = 10, title) => { |
| | | const data = await graphqlRequest(GET_CAROUSELS, { page, size, title }) |
| | | return data.carousels |
| | | const result = await graphqlRequest(GET_CAROUSELS, { page, size, title }) |
| | | return result.data.carousels |
| | | }, |
| | | |
| | | // 根据ID查询轮播图 |
| | | getCarousel: async (id) => { |
| | | const data = await graphqlRequest(GET_CAROUSEL, { id }) |
| | | return data.carousel |
| | | const result = await graphqlRequest(GET_CAROUSEL, { id }) |
| | | return result.data.carousel |
| | | }, |
| | | |
| | | // 获取播放列表 |
| | | getPlayList: async () => { |
| | | const data = await graphqlRequest(GET_CAROUSEL_PLAY_LIST) |
| | | return data.carouselPlayList |
| | | const result = await graphqlRequest(GET_CAROUSEL_PLAY_LIST) |
| | | return result.data.carouselPlayList |
| | | }, |
| | | |
| | | // 保存轮播图 |
| | | saveCarousel: async (carousel) => { |
| | | const data = await graphqlRequest(SAVE_CAROUSEL, { carousel }) |
| | | return data.saveCarousel |
| | | const result = await graphqlRequest(SAVE_CAROUSEL, { carousel }) |
| | | return result.data.saveCarousel |
| | | }, |
| | | |
| | | // 删除轮播图 |
| | |
| | | } |
| | | `, |
| | | |
| | | // 根据姓名搜索员工 |
| | | // 搜索员工 |
| | | SEARCH_EMPLOYEES: ` |
| | | query SearchEmployees($name: String) { |
| | | employeesByName(name: $name) { |
| | |
| | | // 获取员工列表 |
| | | async getEmployees(): Promise<Employee[]> { |
| | | try { |
| | | const data = await graphqlRequest(EMPLOYEE_QUERIES.GET_EMPLOYEES) |
| | | return data?.employees || [] |
| | | const result = await graphqlRequest(EMPLOYEE_QUERIES.GET_EMPLOYEES) |
| | | return result?.data?.employees || [] |
| | | } catch (error: any) { |
| | | throw new Error(error.message || '获取员工列表失败') |
| | | } |
| | | }, |
| | | |
| | | // 搜索员工 |
| | | async searchEmployees(keyword: string): Promise<Employee[]> { |
| | | async searchEmployees(name: string): Promise<Employee[]> { |
| | | try { |
| | | const data = await graphqlRequest(EMPLOYEE_QUERIES.SEARCH_EMPLOYEES, { keyword }) |
| | | return data?.searchEmployees || [] |
| | | const result = await graphqlRequest(EMPLOYEE_QUERIES.SEARCH_EMPLOYEES, { name }) |
| | | return result?.data?.employeesByName || [] |
| | | } catch (error: any) { |
| | | throw new Error(error.message || '搜索员工失败') |
| | | } |
| | |
| | | // 根据ID获取员工详情 |
| | | async getEmployee(id: string): Promise<Employee | null> { |
| | | try { |
| | | const data = await graphqlRequest(EMPLOYEE_QUERIES.GET_EMPLOYEE, { id }) |
| | | return data?.employee || null |
| | | const result = await graphqlRequest(EMPLOYEE_QUERIES.GET_EMPLOYEE, { id }) |
| | | return result?.data?.employee || null |
| | | } catch (error: any) { |
| | | throw new Error(error.message || '获取员工详情失败') |
| | | } |
| | |
| | | // 保存员工(新增或更新) |
| | | async saveEmployee(employee: EmployeeInput): Promise<Employee> { |
| | | try { |
| | | const data = await graphqlRequest(EMPLOYEE_MUTATIONS.SAVE_EMPLOYEE, { input: employee }) |
| | | return data?.saveEmployee |
| | | const result = await graphqlRequest(EMPLOYEE_MUTATIONS.SAVE_EMPLOYEE, { input: employee }) |
| | | return result?.data?.saveEmployee |
| | | } catch (error: any) { |
| | | throw new Error(error.message || '保存员工失败') |
| | | } |
| | |
| | | // 删除员工 |
| | | async deleteEmployee(id: string): Promise<boolean> { |
| | | try { |
| | | const data = await graphqlRequest(EMPLOYEE_MUTATIONS.DELETE_EMPLOYEE, { id }) |
| | | return data?.deleteEmployee || false |
| | | const result = await graphqlRequest(EMPLOYEE_MUTATIONS.DELETE_EMPLOYEE, { id }) |
| | | return result?.data?.deleteEmployee || false |
| | | } catch (error: any) { |
| | | throw new Error(error.message || '删除员工失败') |
| | | } |
| | |
| | | }; |
| | | |
| | | export const deleteMedia = async (id) => { |
| | | console.log('=== deleteMedia API调用 ==='); |
| | | console.log('要删除的媒体ID:', id); |
| | | console.log('GraphQL查询:', DELETE_MEDIA_MUTATION); |
| | | |
| | | // 获取JWT token |
| | | const { getToken } = await import('@/utils/auth'); |
| | | const token = getToken(); |
| | | const headers = { 'Content-Type': 'application/json' }; |
| | | if (token) { |
| | | headers['Authorization'] = `Bearer ${token}`; |
| | | try { |
| | | const variables = { id: parseInt(id) }; |
| | | |
| | | // 发送GraphQL请求 |
| | | const result = await graphqlRequest(DELETE_MEDIA_MUTATION, variables); |
| | | |
| | | // 检查返回结果 |
| | | const deleteResult = result.data?.deleteMedia; |
| | | |
| | | return deleteResult; |
| | | } catch (error) { |
| | | throw new Error(`删除媒体失败: ${error.message}`); |
| | | } |
| | | |
| | | const res = await fetch(GRAPHQL_ENDPOINT, { |
| | | method: 'POST', |
| | | headers: headers, |
| | | body: JSON.stringify({ |
| | | query: DELETE_MEDIA_MUTATION, |
| | | variables: { id: id.toString() } |
| | | }) |
| | | }); |
| | | const result = await res.json(); |
| | | console.log('GraphQL响应:', result); |
| | | console.log('deleteMedia结果:', result.data?.deleteMedia); |
| | | |
| | | if (result.errors) { |
| | | console.error('GraphQL错误:', result.errors); |
| | | throw new Error(result.errors[0].message); |
| | | } |
| | | |
| | | const deleteResult = result.data.deleteMedia; |
| | | console.log('返回的删除结果:', deleteResult, '类型:', typeof deleteResult); |
| | | return deleteResult; |
| | | }; |
| | | |
| | | // 上传文件到服务器 |
| | |
| | | const { extractVideoFrame, generateThumbnailFileName } = await import('@/utils/video.js'); |
| | | |
| | | try { |
| | | console.log('开始处理视频文件:', videoFile.name); |
| | | |
| | | // 1. 上传原视频文件 |
| | | console.log('上传视频文件...'); |
| | | // 1. 上传视频文件 |
| | | const videoUploadResult = await uploadFile(videoFile); |
| | | console.log('视频上传成功:', videoUploadResult); |
| | | |
| | | // 2. 提取视频第一帧 |
| | | console.log('提取视频第一帧...'); |
| | | const thumbnailBlob = await extractVideoFrame(videoFile); |
| | | console.log('视频帧提取成功,大小:', thumbnailBlob.size); |
| | | |
| | | // 3. 创建缩略图文件对象 |
| | | const thumbnailFileName = generateThumbnailFileName(videoFile.name); |
| | |
| | | }); |
| | | |
| | | // 4. 上传缩略图 |
| | | console.log('上传缩略图...'); |
| | | const thumbnailUploadResult = await uploadFile(thumbnailFile); |
| | | console.log('缩略图上传成功:', thumbnailUploadResult); |
| | | |
| | | // 5. 返回包含视频和缩略图信息的结果 |
| | | return { |
| | |
| | | // API函数 |
| | | export const getPlayers = async (page = 0, size = 10, name = '') => { |
| | | try { |
| | | const data = await graphqlRequest(GET_PLAYERS_QUERY, { page, size, name }) |
| | | return data.players |
| | | const result = await graphqlRequest(GET_PLAYERS_QUERY, { page, size, name }) |
| | | return result.data.players |
| | | } catch (error) { |
| | | throw new Error(error.message || '获取学员列表失败') |
| | | } |
| | |
| | | |
| | | export const getPlayer = async (id) => { |
| | | try { |
| | | const data = await graphqlRequest(GET_PLAYER_QUERY, { id }) |
| | | return data.player |
| | | const result = await graphqlRequest(GET_PLAYER_QUERY, { id }) |
| | | return result.data.player |
| | | } catch (error) { |
| | | throw new Error(error.message || '获取学员详情失败') |
| | | } |
| | |
| | | |
| | | export const savePlayer = async (playerData) => { |
| | | try { |
| | | const data = await graphqlRequest(SAVE_PLAYER_MUTATION, { input: playerData }) |
| | | return data.savePlayer |
| | | const result = await graphqlRequest(SAVE_PLAYER_MUTATION, { input: playerData }) |
| | | return result.data.savePlayer |
| | | } catch (error) { |
| | | throw new Error(error.message || '保存学员失败') |
| | | } |
| | |
| | | |
| | | export const deletePlayer = async (id) => { |
| | | try { |
| | | const data = await graphqlRequest(DELETE_PLAYER_MUTATION, { id }) |
| | | return data.deletePlayer |
| | | const result = await graphqlRequest(DELETE_PLAYER_MUTATION, { id }) |
| | | return result.data.deletePlayer |
| | | } catch (error) { |
| | | throw new Error(error.message || '删除学员失败') |
| | | } |
| | |
| | | |
| | | export const PlayerApi = { |
| | | getApplications: async (name, activityId, state, page, size) => { |
| | | const data = await graphqlRequest(GET_APPLICATIONS, { name, activityId, state, page, size }) |
| | | return data.activityPlayerApplications || { content: [], totalElements: 0, page: 1, size: 10 } |
| | | const result = await graphqlRequest(GET_APPLICATIONS, { name, activityId, state, page, size }) |
| | | return result.data.activityPlayerApplications || { content: [], totalElements: 0, page: 1, size: 10 } |
| | | } |
| | | } |
| | |
| | | */ |
| | | export const getActiveActivities = async () => { |
| | | try { |
| | | const data = await graphqlRequest(GET_ACTIVITY_STAGES_QUERY) |
| | | console.log('=== 调用 getActiveActivities ===') |
| | | const result = await graphqlRequest(GET_ACTIVITY_STAGES_QUERY) |
| | | console.log('GraphQL 返回数据:', result) |
| | | |
| | | // 后端已经过滤了比赛阶段,直接返回 |
| | | const activities = data.allActivityStages || [] |
| | | const activities = result.data.allActivityStages || [] |
| | | console.log('处理后的 activities:', activities) |
| | | |
| | | console.log('获取到的比赛阶段:', activities) |
| | | return activities |
| | | } catch (error) { |
| | | console.error('获取比赛阶段失败:', error) |
| | | console.error('getActiveActivities 错误:', error) |
| | | throw new Error(error.message || '获取比赛阶段失败') |
| | | } |
| | | } |
| | |
| | | */ |
| | | export const getCompetitionProjects = async (activityId, page = 0, size = 10) => { |
| | | try { |
| | | const data = await graphqlRequest(GET_COMPETITION_PROJECTS_QUERY, { |
| | | const result = await graphqlRequest(GET_COMPETITION_PROJECTS_QUERY, { |
| | | activityId, |
| | | page, |
| | | size |
| | | }) |
| | | |
| | | const projects = data.activityPlayerApplications || [] |
| | | const projects = result.data.activityPlayerApplications || [] |
| | | |
| | | // 为每个项目获取评分统计 |
| | | const enrichedProjects = await Promise.all( |
| | |
| | | */ |
| | | export const getProjectDetail = async (id) => { |
| | | try { |
| | | const data = await graphqlRequest(GET_PROJECT_DETAIL_QUERY, { id }) |
| | | return data.activityPlayerDetail |
| | | const result = await graphqlRequest(GET_PROJECT_DETAIL_QUERY, { id }) |
| | | return result.data.activityPlayerDetail |
| | | } catch (error) { |
| | | throw new Error(error.message || '获取项目详情失败') |
| | | } |
| | |
| | | */ |
| | | export const getRatingStats = async (activityPlayerId) => { |
| | | try { |
| | | const data = await graphqlRequest(GET_RATING_STATS_QUERY, { activityPlayerId }) |
| | | const result = await graphqlRequest(GET_RATING_STATS_QUERY, { activityPlayerId }) |
| | | |
| | | const ratings = data.judgeRatingsForPlayer || [] |
| | | const averageScore = data.averageScoreForPlayer || 0 |
| | | const ratings = result.data.judgeRatingsForPlayer || [] |
| | | const averageScore = result.data.averageScoreForPlayer || 0 |
| | | |
| | | // 只计算已评分的评委 |
| | | const ratedJudges = ratings.filter(rating => rating.hasRated) |
| | |
| | | */ |
| | | export const getCurrentJudgeRating = async (activityPlayerId) => { |
| | | try { |
| | | const data = await graphqlRequest(GET_CURRENT_JUDGE_RATING_QUERY, { activityPlayerId }) |
| | | return data.currentJudgeRating |
| | | const result = await graphqlRequest(GET_CURRENT_JUDGE_RATING_QUERY, { activityPlayerId }) |
| | | return result.data.currentJudgeRating |
| | | } catch (error) { |
| | | throw new Error(error.message || '获取当前评委评分失败') |
| | | } |
| | |
| | | */ |
| | | export const submitRating = async (ratingInput) => { |
| | | try { |
| | | const data = await graphqlRequest(SUBMIT_RATING_MUTATION, { input: ratingInput }) |
| | | return data.saveActivityPlayerRating |
| | | const result = await graphqlRequest(SUBMIT_RATING_MUTATION, { input: ratingInput }) |
| | | return result.data.saveActivityPlayerRating |
| | | } catch (error) { |
| | | throw new Error(error.message || '提交评分失败') |
| | | } |
| | |
| | | ` |
| | | |
| | | // 获取项目评审列表 |
| | | export const getProjectReviewApplications = (params) => { |
| | | return graphqlRequest(GET_PROJECT_REVIEW_APPLICATIONS_QUERY, params) |
| | | export const getProjectReviewApplications = async (params) => { |
| | | const result = await graphqlRequest(GET_PROJECT_REVIEW_APPLICATIONS_QUERY, params) |
| | | return result.data |
| | | } |
| | | |
| | | export default { |
| | |
| | | page: params.page || 1, |
| | | size: params.size || 10 |
| | | } |
| | | const data = await graphqlRequest(GET_PROMOTION_COMPETITIONS, variables) |
| | | return data.promotionCompetitions || [] |
| | | const result = await graphqlRequest(GET_PROMOTION_COMPETITIONS, variables) |
| | | return result.data.promotionCompetitions || [] |
| | | } catch (error) { |
| | | console.error('获取比赛晋级列表失败:', error) |
| | | throw error |
| | |
| | | page: params.page || 1, |
| | | size: params.size || 10 |
| | | } |
| | | const data = await graphqlRequest(GET_COMPETITION_PARTICIPANTS, variables) |
| | | return data.competitionParticipants || [] |
| | | const result = await graphqlRequest(GET_COMPETITION_PARTICIPANTS, variables) |
| | | return result.data.competitionParticipants || [] |
| | | } catch (error) { |
| | | console.error('获取比赛参赛人员失败:', error) |
| | | throw error |
| | |
| | | // API函数 |
| | | export const getRatingSchemes = async (page = 0, size = 10, name = '') => { |
| | | try { |
| | | const data = await graphqlRequest(GET_RATING_SCHEMES_QUERY, { page, size, name }) |
| | | return data.ratingSchemes |
| | | const result = await graphqlRequest(GET_RATING_SCHEMES_QUERY, { page, size, name }) |
| | | return result.data.ratingSchemes |
| | | } catch (error) { |
| | | throw new Error(error.message || '获取评分方案列表失败') |
| | | } |
| | |
| | | |
| | | export const getAllRatingSchemes = async () => { |
| | | try { |
| | | const data = await graphqlRequest(GET_ALL_RATING_SCHEMES_QUERY) |
| | | return data.allRatingSchemes || [] |
| | | const result = await graphqlRequest(GET_ALL_RATING_SCHEMES_QUERY) |
| | | return result.data.allRatingSchemes || [] |
| | | } catch (error) { |
| | | throw new Error(error.message || '获取所有评分方案失败') |
| | | } |
| | |
| | | ` |
| | | |
| | | try { |
| | | const data = await graphqlRequest(query) |
| | | return data.regions || [] |
| | | const result = await graphqlRequest(query) |
| | | return result.data.regions || [] |
| | | } catch (error) { |
| | | throw new Error(error.message || '获取地区列表失败') |
| | | } |
| | |
| | | ` |
| | | |
| | | try { |
| | | const data = await graphqlRequest(query, { id }) |
| | | return data.region |
| | | const result = await graphqlRequest(query, { id }) |
| | | return result.data.region |
| | | } catch (error) { |
| | | throw new Error(error.message || '获取地区详情失败') |
| | | } |
| | |
| | | ` |
| | | |
| | | try { |
| | | const data = await graphqlRequest(query) |
| | | return data.provinces || [] |
| | | const result = await graphqlRequest(query) |
| | | return result.data.provinces || [] |
| | | } catch (error) { |
| | | throw new Error(error.message || '获取省份列表失败') |
| | | } |
| | |
| | | ` |
| | | |
| | | try { |
| | | const data = await graphqlRequest(query, { provinceId }) |
| | | return data.cities || [] |
| | | const result = await graphqlRequest(query, { provinceId }) |
| | | return result.data.cities || [] |
| | | } catch (error) { |
| | | throw new Error(error.message || '获取城市列表失败') |
| | | } |
| | |
| | | ` |
| | | |
| | | try { |
| | | const data = await graphqlRequest(query, { cityId }) |
| | | return data.districts || [] |
| | | const result = await graphqlRequest(query, { cityId }) |
| | | return result.data.districts || [] |
| | | } catch (error) { |
| | | throw new Error(error.message || '获取区县列表失败') |
| | | } |
| | |
| | | // 获取所有角色 |
| | | async getRoles(): Promise<Role[]> { |
| | | try { |
| | | const data = await graphqlRequest(ROLE_QUERIES.GET_ROLES) |
| | | return data?.roles || [] |
| | | const result = await graphqlRequest(ROLE_QUERIES.GET_ROLES) |
| | | return result?.data?.roles || [] |
| | | } catch (error: any) { |
| | | throw new Error(error.message || '获取角色列表失败') |
| | | } |
| | |
| | | // 获取激活状态的角色 |
| | | async getActiveRoles(): Promise<Role[]> { |
| | | try { |
| | | const data = await graphqlRequest(ROLE_QUERIES.GET_ACTIVE_ROLES) |
| | | return data?.activeRoles || [] |
| | | const result = await graphqlRequest(ROLE_QUERIES.GET_ACTIVE_ROLES) |
| | | return result?.data?.activeRoles || [] |
| | | } catch (error: any) { |
| | | throw new Error(error.message || '获取激活角色列表失败') |
| | | } |
| | |
| | | // 根据ID获取角色 |
| | | async getRoleById(id: string): Promise<Role | null> { |
| | | try { |
| | | const data = await graphqlRequest(ROLE_QUERIES.GET_ROLE, { id }) |
| | | return data?.role || null |
| | | const result = await graphqlRequest(ROLE_QUERIES.GET_ROLE, { id }) |
| | | return result?.data?.role || null |
| | | } catch (error: any) { |
| | | throw new Error(error.message || '获取角色详情失败') |
| | | } |
| | |
| | | // 根据代码获取角色 |
| | | async getRoleByCode(code: string): Promise<Role | null> { |
| | | try { |
| | | const data = await graphqlRequest(ROLE_QUERIES.GET_ROLE_BY_CODE, { code }) |
| | | return data?.roleByCode || null |
| | | const result = await graphqlRequest(ROLE_QUERIES.GET_ROLE_BY_CODE, { code }) |
| | | return result?.data?.roleByCode || null |
| | | } catch (error: any) { |
| | | throw new Error(error.message || '获取角色详情失败') |
| | | } |
| | |
| | | // 根据状态获取角色 |
| | | async getRolesByState(state: number): Promise<Role[]> { |
| | | try { |
| | | const data = await graphqlRequest(ROLE_QUERIES.GET_ROLES_BY_STATE, { state }) |
| | | return data?.rolesByState || [] |
| | | const result = await graphqlRequest(ROLE_QUERIES.GET_ROLES_BY_STATE, { state }) |
| | | return result?.data?.rolesByState || [] |
| | | } catch (error: any) { |
| | | throw new Error(error.message || '获取角色列表失败') |
| | | } |
| | |
| | | // 根据名称搜索角色 |
| | | async searchRolesByName(name: string): Promise<Role[]> { |
| | | try { |
| | | const data = await graphqlRequest(ROLE_QUERIES.SEARCH_ROLES_BY_NAME, { name }) |
| | | return data?.searchRolesByName || [] |
| | | const result = await graphqlRequest(ROLE_QUERIES.SEARCH_ROLES_BY_NAME, { name }) |
| | | return result?.data?.searchRolesByName || [] |
| | | } catch (error: any) { |
| | | throw new Error(error.message || '搜索角色失败') |
| | | } |
| | |
| | | |
| | | // 监听评委数据变化,填充表单 |
| | | watch(() => props.judgeData, (data) => { |
| | | console.log('🔍 Watch triggered, judgeData:', data) |
| | | console.log('🔍 isEdit computed:', isEdit.value) |
| | | nextTick(async () => { |
| | | if (data && data.id) { |
| | | // 编辑模式:填充表单数据 |
| | |
| | | |
| | | // GraphQL请求工具函数 |
| | | export const graphqlRequest = async (query: string, variables: any = {}) => { |
| | | console.log('=== GraphQL请求开始 ==='); |
| | | console.log('请求端点:', API_CONFIG.GRAPHQL_ENDPOINT); |
| | | console.log('查询语句:', query); |
| | | console.log('变量:', variables); |
| | | |
| | | // 获取JWT token |
| | | const { getToken } = await import('@/utils/auth'); |
| | | const token = getToken(); |
| | | console.log('JWT Token:', token ? '已获取' : '未获取'); |
| | | |
| | | |
| | | // 构建请求头 |
| | | const headers: Record<string, string> = { |
| | | 'Content-Type': 'application/json', |
| | | }; |
| | | |
| | | if (token) { |
| | | headers['Authorization'] = `Bearer ${token}`; |
| | | } |
| | | console.log('请求头:', headers); |
| | | |
| | | // 构建请求体 |
| | | const requestBody = JSON.stringify({ |
| | | query, |
| | | variables, |
| | | }); |
| | | console.log('请求体:', requestBody); |
| | | |
| | | try { |
| | | // 发送请求 |
| | | const response = await fetch(API_CONFIG.GRAPHQL_ENDPOINT, { |
| | | method: 'POST', |
| | | headers: headers, |
| | | headers, |
| | | body: requestBody, |
| | | }) |
| | | |
| | | console.log('响应状态:', response.status); |
| | | console.log('响应状态文本:', response.statusText); |
| | | }); |
| | | |
| | | if (!response.ok) { |
| | | throw new Error(`HTTP error! status: ${response.status}`) |
| | | throw new Error(`HTTP error! status: ${response.status}`); |
| | | } |
| | | |
| | | const result = await response.json() |
| | | console.log('响应结果:', result); |
| | | |
| | | const result = await response.json(); |
| | | |
| | | if (result.errors) { |
| | | console.error('GraphQL错误:', result.errors); |
| | | throw new Error(result.errors[0].message) |
| | | throw new Error(`GraphQL errors: ${JSON.stringify(result.errors)}`); |
| | | } |
| | | |
| | | console.log('返回数据:', result.data); |
| | | return result.data |
| | | return result; |
| | | } catch (error) { |
| | | console.error('=== GraphQL请求失败 ==='); |
| | | console.error('错误详情:', error); |
| | |
| | | { |
| | | path: '/activity', |
| | | name: 'Activity', |
| | | component: () => import('@/views/activity/index.vue'), |
| | | component: () => import('@/views/activity-list.vue'), |
| | | meta: { title: '比赛管理', icon: 'Trophy' } |
| | | }, |
| | | { |
| | | path: '/activity/:id', |
| | | name: 'ActivityDetail', |
| | | component: () => import('@/views/ActivityDetail.vue'), |
| | | meta: { title: '比赛详情', icon: 'Trophy' } |
| | | }, |
| | | { |
| | | path: '/activity/new', |
| | |
| | | meta: { title: '编辑比赛', icon: 'Trophy' } |
| | | }, |
| | | { |
| | | path: '/activity/:id', |
| | | name: 'ActivityDetail', |
| | | component: () => import('@/views/ActivityDetail.vue'), |
| | | meta: { title: '比赛详情', icon: 'Trophy' } |
| | | }, |
| | | { |
| | | path: '/judge', |
| | | name: 'Judge', |
| | | component: () => import('@/views/judge/index.vue'), |
| | | component: () => import('@/views/judge-list.vue'), |
| | | meta: { title: '评委管理', icon: 'UserFilled' } |
| | | }, |
| | | { |
| | | path: '/rating-scheme', |
| | | name: 'RatingScheme', |
| | | component: () => import('@/views/rating/index.vue'), |
| | | meta: { title: '评分模板', icon: 'Score' } |
| | | component: () => import('@/views/rating-list.vue'), |
| | | meta: { title: '评分模板', icon: 'Document' } |
| | | }, |
| | | { |
| | | path: '/rating-scheme/new', |
| | | name: 'RatingSchemeCreate', |
| | | component: () => import('@/views/rating/Form.vue'), |
| | | meta: { title: '新增评分模板', icon: 'Score' } |
| | | component: () => import('@/views/rating-detail.vue'), |
| | | meta: { title: '新建评分模板', hidden: true } |
| | | }, |
| | | { |
| | | path: '/rating-scheme/edit/:id', |
| | | name: 'RatingSchemeEdit', |
| | | component: () => import('@/views/rating/Form.vue'), |
| | | meta: { title: '编辑评分模板', icon: 'Score' } |
| | | component: () => import('@/views/rating-detail.vue'), |
| | | meta: { title: '编辑评分模板', hidden: true } |
| | | }, |
| | | { |
| | | path: '/player', |
| | | name: 'Player', |
| | | component: () => import('@/views/player/index.vue'), |
| | | meta: { title: '报名审核', icon: 'UserFilled' } |
| | | component: () => import('@/views/check-list.vue'), |
| | | meta: { title: '参赛人员', icon: 'UserFilled' } |
| | | }, |
| | | { |
| | | path: '/player/:id/detail', |
| | | name: 'PlayerDetail', |
| | | component: () => import('@/views/player/detail.vue'), |
| | | meta: { title: '报名详情', icon: 'UserFilled' } |
| | | component: () => import('@/views/check-detail.vue'), |
| | | meta: { title: '参赛人员详情' } |
| | | }, |
| | | { |
| | | path: '/activity-player/:id/rating', |
| | |
| | | { |
| | | path: '/employee', |
| | | name: 'Employee', |
| | | component: () => import('@/views/employee/index.vue'), |
| | | meta: { title: '员工管理', icon: 'Avatar' } |
| | | component: () => import('@/views/employee-list.vue'), |
| | | meta: { title: '员工管理', icon: 'User' } |
| | | }, |
| | | { |
| | | path: '/project-review', |
| | | name: 'ProjectReview', |
| | | component: () => import('@/views/project-review/index.vue'), |
| | | component: () => import('@/views/review-list.vue'), |
| | | meta: { title: '项目评审', icon: 'View' } |
| | | }, |
| | | { |
| | | path: '/project-review/:id/detail', |
| | | name: 'ProjectReviewDetail', |
| | | component: () => import('@/views/project-review/detail.vue'), |
| | | component: () => import('@/views/review-detail.vue'), |
| | | meta: { title: '项目评审详情', hidden: true } |
| | | }, |
| | | { |
| | | path: '/review', |
| | | name: 'Review', |
| | | component: () => import('@/views/review/index.vue'), |
| | | meta: { title: '项目评审', icon: 'Edit' } |
| | | component: () => import('@/views/judge-review-list.vue'), |
| | | meta: { title: '评委评审', icon: 'Edit' } |
| | | }, |
| | | { |
| | | path: '/review/:id/detail', |
| | | name: 'ReviewDetail', |
| | | component: () => import('@/views/review/detail.vue'), |
| | | meta: { title: '项目评审详情', hidden: true } |
| | | component: () => import('@/views/judge-review-detail.vue'), |
| | | meta: { title: '评委评审详情', hidden: true } |
| | | }, |
| | | { |
| | | path: '/competition-promotion', |
| | | name: 'CompetitionPromotion', |
| | | component: () => import('@/views/competition-promotion/index.vue'), |
| | | component: () => import('@/views/next-list.vue'), |
| | | meta: { title: '比赛晋级', icon: 'Promotion' } |
| | | }, |
| | | |
| | |
| | | */ |
| | | export const uploadToCOS = async (file: File): Promise<string> => { |
| | | try { |
| | | console.log('开始获取上传凭证...') |
| | | |
| | | // 获取上传凭证 |
| | | const credentials = await getUploadCredentials() |
| | | console.log('获取到上传凭证:', credentials) |
| | | |
| | | // 使用预签名URL |
| | | const uploadUrl = credentials.presignedUrl |
| | | |
| | | console.log('开始上传文件到:', uploadUrl) |
| | | |
| | | // 使用预签名URL上传文件 |
| | | const uploadResponse = await axios.put(uploadUrl, file, { |
| | |
| | | }) |
| | | |
| | | if (uploadResponse.status === 200) { |
| | | console.log('文件上传成功!') |
| | | // 返回文件的访问URL(去掉查询参数) |
| | | const fileUrl = `https://${credentials.bucket}.cos.${credentials.region}.myqcloud.com/${credentials.key}` |
| | | return fileUrl |
| | |
| | | } |
| | | |
| | | } catch (error) { |
| | | console.error('上传文件失败:', error) |
| | | throw error |
| | | } |
| | | } |
| | |
| | | const response = await axios.get('http://localhost:8080/api/cos/credentials') |
| | | return response.data |
| | | } catch (error) { |
| | | console.error('从后端获取临时密钥失败:', error) |
| | | throw error |
| | | } |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | console.log('开始上传logo文件:', logoFile.name, logoFile.size, 'bytes') |
| | | |
| | | // 上传到COS的avatars目录 |
| | | const url = await uploadToCOS(logoFile, 'avatars/') |
| | | |
| | | console.log('Logo上传成功:', url) |
| | | |
| | | return { |
| | | success: true, |
| | |
| | | return new File([blob], 'logo.jpg', { type: 'image/jpeg' }) |
| | | } |
| | | } catch (error) { |
| | | console.log('从public目录获取logo失败,尝试其他方式') |
| | | // 从public目录获取logo失败,尝试其他方式 |
| | | } |
| | | |
| | | try { |
| | |
| | | return new File([blob], 'logo.jpg', { type: 'image/jpeg' }) |
| | | } |
| | | } catch (error) { |
| | | console.log('从UI目录获取logo失败') |
| | | // 从UI目录获取logo失败 |
| | | } |
| | | |
| | | // 方式3: 返回null,需要用户手动选择文件 |
| | |
| | | </el-form-item> |
| | | |
| | | <!-- 图片/视频上传 --> |
| | | <el-divider content-position="left">图片/视频</el-divider> |
| | | <el-divider content-position="left"> |
| | | 图片/视频 |
| | | <span class="media-description">支持jpg/png/mp4,最多3个文件</span> |
| | | </el-divider> |
| | | |
| | | <el-form-item label="媒体文件"> |
| | | <div class="media-upload-section"> |
| | |
| | | <!-- 添加按钮 --> |
| | | <el-upload |
| | | v-if="form.mediaFiles.length < 3" |
| | | class="media-uploader" |
| | | class="media-uploader media-uploader-left" |
| | | :show-file-list="false" |
| | | :before-upload="beforeMediaUpload" |
| | | action="#" |
| | |
| | | <div class="upload-placeholder"> |
| | | <el-icon class="upload-icon"><Plus /></el-icon> |
| | | <div class="upload-text">添加图片/视频</div> |
| | | <div class="upload-tip">支持jpg/png/mp4,最多3个文件</div> |
| | | </div> |
| | | </el-upload> |
| | | </div> |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-if="form.stages && form.stages.length > 0" class="stages-list"> |
| | | <div v-if="form.value && form.value.stages && form.value.stages.length > 0" class="stages-list"> |
| | | <div v-for="(stage, index) in sortedFormStages" :key="index" class="stage-item"> |
| | | <div class="stage-info"> |
| | | <div class="stage-header"> |
| | |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="180" align="center"> |
| | | <el-table-column label="操作" width="100" align="center"> |
| | | <template #default="{ row, $index }"> |
| | | <el-button size="small" @click="editJudge(row, $index)">编辑</el-button> |
| | | <el-button size="small" type="danger" @click="removeJudge($index)">删除</el-button> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | |
| | | <!-- 阶段选择 --> |
| | | <div style="margin-bottom: 16px;"> |
| | | <el-form-item label="添加到阶段:" label-width="100px"> |
| | | <el-select v-model="selectedStageOption" style="width: 100%;" @change="handleStageChange"> |
| | | <!-- 只显示比赛阶段 --> |
| | | <el-form-item label="负责阶段:" label-width="100px"> |
| | | <!-- 调试信息 --> |
| | | |
| | | <el-select v-model="selectedStageOptions" multiple style="width: 100%;" placeholder="请选择负责的阶段"> |
| | | <!-- 使用计算属性 --> |
| | | <el-option |
| | | v-for="stage in form.stages" |
| | | :key="stage.id" |
| | | :label="stage.name" |
| | | :value="stage.id ? stage.id.toString() : ''" |
| | | v-for="option in stageOptions" |
| | | :key="option.value" |
| | | :label="option.label" |
| | | :value="option.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | |
| | | <el-select v-model="currentStudent.lastStageId" placeholder="请选择阶段" style="width: 100%"> |
| | | <el-option label="无" :value="null" /> |
| | | <el-option |
| | | v-for="stage in form.stages" |
| | | v-for="stage in (form.value?.stages || [])" |
| | | :key="stage.id" |
| | | :label="stage.name" |
| | | :value="stage.id" |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import { ref, onMounted, computed, nextTick } from 'vue' |
| | | import { useRouter, useRoute } from 'vue-router' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Plus, VideoPlay, Clock, User, UserFilled, Search } from '@element-plus/icons-vue' |
| | |
| | | // 评委选择相关 |
| | | const allJudges = ref([]) |
| | | const judgeSearchText = ref('') |
| | | const selectedStageOption = ref('all') |
| | | const selectedStageOptions = ref([]) |
| | | const selectedJudges = ref([]) |
| | | const judgeLoading = ref(false) |
| | | |
| | |
| | | }) |
| | | }) |
| | | |
| | | // 用于下拉框的阶段选项 |
| | | const stageOptions = computed(() => { |
| | | if (!form.value?.stages) { |
| | | return [] |
| | | } |
| | | |
| | | return form.value.stages |
| | | .filter(stage => stage && stage.id != null) |
| | | .map(stage => ({ |
| | | label: stage.name, |
| | | value: stage.id.toString(), |
| | | stage: stage |
| | | })) |
| | | }) |
| | | |
| | | // 表单验证规则 |
| | | const rules = { |
| | | name: [ |
| | |
| | | judgeLoading.value = true |
| | | const judges = await getAllJudges() |
| | | allJudges.value = judges || [] |
| | | console.log('加载评委列表成功:', allJudges.value.length, '个评委') |
| | | } catch (error) { |
| | | console.error('加载评委列表失败:', error) |
| | | ElMessage.error('加载评委列表失败: ' + error.message) |
| | |
| | | try { |
| | | loading.value = true |
| | | const activity = await getActivity(route.params.id) |
| | | |
| | | if (activity) { |
| | | form.value = { |
| | | id: activity.id, |
| | |
| | | // 加载并回填已上传媒体:targetType=2 假设为“活动”,如不同请调整 |
| | | try { |
| | | const medias = await getMediasByTarget(MediaTargetType.ACTIVITY, parseInt(activity.id)) |
| | | console.log('=== 加载活动媒体调试信息 ===') |
| | | console.log('活动ID:', activity.id) |
| | | console.log('获取到的媒体数据:', medias) |
| | | |
| | | form.value.mediaFiles = (medias || []).map(m => { |
| | | console.log('处理媒体文件:', m) |
| | | const isImage = (m.mediaType === 1) || (m.fileExt && ['jpg','jpeg','png','gif','webp'].includes(m.fileExt.toLowerCase())) |
| | | const isVideo = (m.mediaType === 2) || (m.fileExt && ['mp4','mov','m4v','avi','mkv'].includes(m.fileExt.toLowerCase())) |
| | | const mediaItem = { |
| | |
| | | uploaded: true, // 标记为已上传,不需要重新上传 |
| | | file: null // 已保存的文件没有file对象 |
| | | } |
| | | console.log('转换后的媒体项:', mediaItem) |
| | | return mediaItem |
| | | }) |
| | | console.log('最终的mediaFiles:', form.value.mediaFiles) |
| | | |
| | | // 设置阶段数量选择器的值 |
| | | selectedStageCount.value = form.value.stages.length || 1 |
| | | } catch (e) { |
| | | console.error('加载活动媒体失败:', e) |
| | | } |
| | | |
| | | // 设置阶段数量选择器的值 |
| | | selectedStageCount.value = (form.value && form.value.stages) ? form.value.stages.length || 1 : 1 |
| | | } |
| | | } catch (error) { |
| | | console.error('加载比赛数据失败:', error) |
| | |
| | | // 阶段管理 |
| | | // 阶段数量变化处理 |
| | | const onStageCountChange = (count) => { |
| | | if (!count) return |
| | | if (!count || !form.value || !form.value.stages) return |
| | | |
| | | // 如果当前阶段数量少于选择的数量,自动添加阶段 |
| | | while (form.value.stages.length < count) { |
| | |
| | | |
| | | // 获取阶段在原始数组中的索引 |
| | | const getOriginalStageIndex = (stage) => { |
| | | if (!form.value || !form.value.stages) return -1 |
| | | return form.value.stages.findIndex(s => s === stage) |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | const removeStage = async (index) => { |
| | | if (!form.value || !form.value.stages) return |
| | | |
| | | try { |
| | | await ElMessageBox.confirm('确定要删除这个阶段吗?', '提示', { |
| | | confirmButtonText: '确定', |
| | |
| | | } |
| | | |
| | | const saveStage = async () => { |
| | | if (!form.value || !form.value.stages) return |
| | | |
| | | try { |
| | | await stageFormRef.value.validate() |
| | | |
| | |
| | | } |
| | | |
| | | const removeJudge = async (index) => { |
| | | if (!form.value || !form.value.judges) { |
| | | ElMessage.error('表单数据未初始化') |
| | | return |
| | | } |
| | | |
| | | try { |
| | | await ElMessageBox.confirm('确定要删除这个评委吗?', '提示', { |
| | | confirmButtonText: '确定', |
| | |
| | | } |
| | | |
| | | const getJudgeStages = (judge) => { |
| | | if (!judge.stageIds) return [] |
| | | if (!judge.stageIds || !form.value || !form.value.stages) return [] |
| | | |
| | | const stages = [] |
| | | |
| | | // 只检查比赛阶段 |
| | | if (form.value.stages) { |
| | | const matchedStages = form.value.stages.filter(stage => judge.stageIds.includes(stage.id)) |
| | | matchedStages.forEach(stage => { |
| | | // 检查比赛阶段 |
| | | judge.stageIds.forEach(stageId => { |
| | | // 处理实际阶段ID |
| | | const stage = form.value.stages.find(s => s.id === stageId) |
| | | if (stage) { |
| | | stages.push({ |
| | | id: stage.id, |
| | | name: stage.name |
| | | }) |
| | | }) |
| | | } |
| | | } |
| | | }) |
| | | |
| | | return stages |
| | | } |
| | | |
| | | const resetJudgeDialog = () => { |
| | | judgeSearchText.value = '' |
| | | // 默认选择第一个阶段 |
| | | selectedStageOption.value = form.value.stages && form.value.stages.length > 0 |
| | | ? form.value.stages[0].id?.toString() || '' |
| | | : '' |
| | | // 清空阶段选择 |
| | | selectedStageOptions.value = [] |
| | | selectedJudges.value = [] |
| | | } |
| | | |
| | | const handleJudgeSearch = (value) => { |
| | | console.log('搜索评委:', value) |
| | | // 搜索评委 |
| | | } |
| | | |
| | | const handleStageChange = (value) => { |
| | | console.log('选择阶段:', value) |
| | | } |
| | | |
| | | |
| | | const handleJudgeSelectionChange = (value) => { |
| | | console.log('选择评委:', value) |
| | | // 选择评委 |
| | | } |
| | | |
| | | const toggleSelectAll = () => { |
| | |
| | | return |
| | | } |
| | | |
| | | if (!form.value || !form.value.judges) { |
| | | ElMessage.error('表单数据未初始化') |
| | | return |
| | | } |
| | | |
| | | // 如果有阶段但没有选择阶段,则提示 |
| | | if (form.value && form.value.stages && form.value.stages.length > 0 && selectedStageOptions.value.length === 0) { |
| | | ElMessage.warning('请选择至少一个负责阶段') |
| | | return |
| | | } |
| | | |
| | | let addedCount = 0 |
| | | |
| | | selectedJudges.value.forEach(judgeId => { |
| | |
| | | // 检查是否已经存在 |
| | | const existingJudge = form.value.judges.find(j => j.id === judgeId) |
| | | if (existingJudge) { |
| | | // 更新现有评委的阶段 |
| | | const stageId = parseInt(selectedStageOption.value) |
| | | if (!existingJudge.stageIds.includes(stageId)) { |
| | | existingJudge.stageIds.push(stageId) |
| | | // 更新现有评委的阶段,合并新选择的阶段 |
| | | if (selectedStageOptions.value.length > 0) { |
| | | selectedStageOptions.value.forEach(stageId => { |
| | | const stageIdInt = parseInt(stageId) |
| | | if (!existingJudge.stageIds.includes(stageIdInt)) { |
| | | existingJudge.stageIds.push(stageIdInt) |
| | | } |
| | | }) |
| | | } |
| | | } else { |
| | | // 添加新评委 |
| | | const stageIds = [parseInt(selectedStageOption.value)] |
| | | // 添加新评委,包含所有选择的阶段 |
| | | const stageIds = selectedStageOptions.value.length > 0 |
| | | ? selectedStageOptions.value.map(id => parseInt(id)) |
| | | : [] |
| | | |
| | | const newJudge = { |
| | | id: judge.id, |
| | |
| | | } |
| | | } |
| | | }) |
| | | |
| | | // 清空选择 |
| | | selectedJudges.value = [] |
| | | selectedStageOptions.value = [] |
| | | |
| | | judgeDialogVisible.value = false |
| | | ElMessage.success(`成功添加 ${addedCount} 个评委`) |
| | |
| | | } |
| | | |
| | | const getLastStage = (student) => { |
| | | if (!student.lastStageId || !form.value.stages) return '无' |
| | | if (!student.lastStageId || !form.value || !form.value.stages) return '无' |
| | | const stage = form.value.stages.find(s => s.id === student.lastStageId) |
| | | return stage ? stage.name : '无' |
| | | } |
| | |
| | | uploaded: false // 标记为未上传 |
| | | } |
| | | |
| | | form.value.mediaFiles.push(mediaFile) |
| | | ElMessage.success('文件已选择,点击更新按钮时将上传') |
| | | if (form.value && form.value.mediaFiles) { |
| | | form.value.mediaFiles.push(mediaFile) |
| | | ElMessage.success('文件已选择,点击更新按钮时将上传') |
| | | } |
| | | |
| | | return false // 阻止el-upload的默认上传 |
| | | } |
| | | |
| | | const beforeMediaUpload = (file) => { |
| | | if (!form.value || !form.value.mediaFiles) return false |
| | | |
| | | if (form.value.mediaFiles.length >= 3) { |
| | | ElMessage.error('最多只能上传3个文件!') |
| | | return false |
| | |
| | | |
| | | // 处理媒体文件上传 |
| | | const handleMediaUpload = async (activityId) => { |
| | | if (!form.value || !form.value.mediaFiles) return |
| | | |
| | | try { |
| | | for (const mediaFile of form.value.mediaFiles) { |
| | | // 跳过已经有 id 的媒体文件(已保存的) |
| | |
| | | } |
| | | |
| | | try { |
| | | console.log('开始上传文件:', mediaFile.name) |
| | | // 1. 上传文件到服务器 |
| | | const uploadResult = await uploadFile(mediaFile.file) |
| | | console.log('文件上传成功:', uploadResult) |
| | | |
| | | // 2. 保存媒体信息到数据库 |
| | | const mediaInput = { |
| | |
| | | targetType: MediaTargetType.ACTIVITY, // 活动 |
| | | targetId: parseInt(activityId) // 转换为数字类型 |
| | | } |
| | | |
| | | console.log('准备保存媒体信息:', mediaInput) |
| | | console.log('活动ID:', activityId) |
| | | const savedMedia = await saveMedia(mediaInput) |
| | | console.log(`媒体文件 ${mediaFile.name} 上传并保存成功:`, savedMedia) |
| | | |
| | | // 更新媒体文件信息 |
| | | mediaFile.id = savedMedia.id |
| | |
| | | // 提交表单 |
| | | const handleSubmit = async () => { |
| | | if (submitting.value) return |
| | | if (!form.value) return |
| | | |
| | | try { |
| | | await formRef.value.validate() |
| | | |
| | |
| | | |
| | | // 准备保存数据,只包含后端支持的字段 |
| | | const saveData = { |
| | | id: form.value.id, |
| | | pid: form.value.pid || 0, |
| | | name: form.value.name, |
| | | description: form.value.description, |
| | | signupDeadline: form.value.signupDeadline, |
| | |
| | | ratingSchemeId: form.value.ratingSchemeId, |
| | | playerMax: form.value.playerMax, |
| | | state: form.value.state || 1, |
| | | stages: form.value.stages ? form.value.stages.map(stage => ({ |
| | | id: stage.id, |
| | | name: stage.name, |
| | | description: stage.description, |
| | | matchTime: stage.matchTime, |
| | | address: stage.address, |
| | | ratingSchemeId: stage.ratingSchemeId, |
| | | playerMax: stage.playerMax, |
| | | sortOrder: stage.sortOrder, |
| | | state: stage.state || 1 |
| | | })) : [], |
| | | judges: form.value.judges ? form.value.judges.map(judge => ({ |
| | | stages: form.value.stages ? form.value.stages.map(stage => { |
| | | const stageData = { |
| | | name: stage.name, |
| | | description: stage.description, |
| | | matchTime: stage.matchTime, |
| | | address: stage.address, |
| | | playerMax: stage.playerMax, |
| | | sortOrder: stage.sortOrder, |
| | | state: stage.state || 1 |
| | | } |
| | | // 只在有有效ID时才添加id字段 |
| | | if (stage.id) { |
| | | stageData.id = stage.id |
| | | } |
| | | // 只在有有效ratingSchemeId时才添加该字段 |
| | | if (stage.ratingSchemeId) { |
| | | stageData.ratingSchemeId = stage.ratingSchemeId |
| | | } |
| | | return stageData |
| | | }) : [], |
| | | judges: form.value.judges ? form.value.judges.filter(judge => judge.id && judge.name).map(judge => ({ |
| | | judgeId: judge.id, |
| | | judgeName: judge.name, |
| | | stageIds: judge.stageIds || [] |
| | | })) : [] |
| | | } |
| | | |
| | | // 如果是编辑模式,添加id字段 |
| | | if (isEdit.value && form.value.id) { |
| | | saveData.id = form.value.id |
| | | } |
| | | |
| | | // 如果有pid,添加pid字段 |
| | | if (form.value.pid) { |
| | | saveData.pid = form.value.pid |
| | | } |
| | | |
| | | const result = await saveActivity(saveData) |
| | |
| | | await loadActivity() |
| | | |
| | | // 如果是新建模式且没有阶段,自动创建一个阶段 |
| | | if (!isEdit.value && form.value.stages.length === 0) { |
| | | if (!isEdit.value && form.value && form.value.stages && form.value.stages.length === 0) { |
| | | onStageCountChange(1) |
| | | } |
| | | }) |
| | |
| | | flex: 1; |
| | | padding-left: 8px; |
| | | } |
| | | |
| | | /* 媒体描述文本样式 */ |
| | | .media-description { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | font-weight: normal; |
| | | margin-left: 8px; |
| | | } |
| | | |
| | | /* 媒体上传按钮 */ |
| | | .media-uploader-left { |
| | | margin-left: 16px; |
| | | } |
| | | |
| | | .media-container { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | gap: 16px; |
| | | flex-wrap: wrap; |
| | | } |
| | | </style> |
File was renamed from web/src/views/player/index.vue |
| | |
| | | const loadData = async () => { |
| | | loading.value = true |
| | | try { |
| | | // 确保activityId是正确的类型 |
| | | let activityId = null |
| | | if (searchForm.activityId && searchForm.activityId !== '') { |
| | | activityId = searchForm.activityId |
| | | } |
| | | |
| | | const response = await PlayerApi.getApplications( |
| | | searchForm.name || '', |
| | | searchForm.activityId || null, |
| | | activityId, |
| | | searchForm.state !== '' ? parseInt(searchForm.state) : null, |
| | | pagination.page, |
| | | pagination.size |
File was renamed from web/src/views/employee/index.vue |
| | |
| | | {{ formatDateTime(row.createTime) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="120" fixed="right"> |
| | | <el-table-column label="操作" width="120" fixed="right" align="center"> |
| | | <template #default="{ row }"> |
| | | <div class="table-actions"> |
| | | <el-button type="warning" size="small" @click="handleEdit(row)" :icon="Edit" circle title="编辑"></el-button> |
| | | <el-button type="danger" size="small" @click="handleDelete(row)" :icon="Delete" circle title="删除"></el-button> |
| | | <el-button |
| | | text |
| | | :icon="Edit" |
| | | size="small" |
| | | @click="handleEdit(row)" |
| | | class="action-btn edit-btn" |
| | | title="编辑" |
| | | /> |
| | | <el-button |
| | | text |
| | | :icon="Delete" |
| | | size="small" |
| | | @click="handleDelete(row)" |
| | | class="action-btn delete-btn" |
| | | title="删除" |
| | | /> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Search, Plus, Edit, Delete } from '@element-plus/icons-vue' |
| | | import { employeeApi, type Employee } from '@/api/employee' |
| | | import EmployeeForm from './EmployeeForm.vue' |
| | | import EmployeeForm from './employee-detail.vue' |
| | | |
| | | const loading = ref(false) |
| | | const formVisible = ref(false) |
| | |
| | | // 获取角色标签类型 |
| | | const getRoleType = (roleId: string) => { |
| | | const typeMap: Record<string, string> = { |
| | | 'ADMIN': 'danger', |
| | | 'MANAGER': 'warning', |
| | | 'STAFF': 'primary', |
| | | 'REVIEWER': 'success' |
| | | 'SUPER_ADMIN': 'danger', |
| | | 'AUDITOR': 'warning', |
| | | 'JUDGE_MANAGER': 'success', |
| | | 'PLAYER': 'primary' |
| | | } |
| | | return typeMap[roleId] || 'info' |
| | | } |
| | |
| | | // 获取角色文本 |
| | | const getRoleText = (roleId: string) => { |
| | | const textMap: Record<string, string> = { |
| | | 'ADMIN': '管理员', |
| | | 'MANAGER': '经理', |
| | | 'STAFF': '工作人员', |
| | | 'REVIEWER': '审核员' |
| | | 'SUPER_ADMIN': '超级管理员', |
| | | 'AUDITOR': '平台工作人员', |
| | | 'JUDGE_MANAGER': '评委', |
| | | 'PLAYER': '参赛人员' |
| | | } |
| | | return textMap[roleId] || roleId |
| | | } |
| | |
| | | .table-actions { |
| | | display: flex; |
| | | gap: 8px; |
| | | flex-wrap: wrap; |
| | | justify-content: center; |
| | | align-items: center; |
| | | flex-wrap: nowrap; // 确保不换行 |
| | | |
| | | .action-btn { |
| | | &.edit-btn { |
| | | color: #409eff; |
| | | |
| | | &:hover { |
| | | color: #66b1ff; |
| | | } |
| | | } |
| | | |
| | | &.delete-btn { |
| | | color: #f56c6c; |
| | | |
| | | &:hover { |
| | | color: #f78989; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .pagination { |
New file |
| | |
| | | <!DOCTYPE html> |
| | | <html lang="zh-CN"> |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | | <title>员工角色下拉框测试</title> |
| | | <style> |
| | | body { font-family: Arial, sans-serif; margin: 20px; } |
| | | .result { margin: 10px 0; padding: 10px; border: 1px solid #ccc; } |
| | | .error { background-color: #ffe6e6; } |
| | | .success { background-color: #e6ffe6; } |
| | | button { padding: 10px 20px; margin: 5px; } |
| | | select { padding: 5px; margin: 5px; min-width: 200px; } |
| | | </style> |
| | | </head> |
| | | <body> |
| | | <h1>员工角色下拉框测试</h1> |
| | | |
| | | <div> |
| | | <button onclick="checkAuth()">1. 检查认证状态</button> |
| | | <button onclick="testRoleAPI()">2. 测试角色API</button> |
| | | <button onclick="loginAsAdmin()">3. 模拟管理员登录</button> |
| | | <button onclick="testRoleAPIAfterLogin()">4. 登录后测试角色API</button> |
| | | </div> |
| | | |
| | | <div> |
| | | <h3>角色下拉框模拟:</h3> |
| | | <select id="roleSelect"> |
| | | <option value="">请选择角色</option> |
| | | </select> |
| | | <button onclick="loadRolesIntoSelect()">加载角色到下拉框</button> |
| | | </div> |
| | | |
| | | <div id="results"></div> |
| | | |
| | | <script> |
| | | function addResult(content, isError = false) { |
| | | const div = document.createElement('div'); |
| | | div.className = `result ${isError ? 'error' : 'success'}`; |
| | | div.innerHTML = content; |
| | | document.getElementById('results').appendChild(div); |
| | | } |
| | | |
| | | function checkAuth() { |
| | | const token = localStorage.getItem('auth_token'); |
| | | const userInfo = localStorage.getItem('user_info'); |
| | | |
| | | addResult(` |
| | | <strong>认证状态:</strong><br> |
| | | Token: ${token ? '存在' : '不存在'}<br> |
| | | User Info: ${userInfo ? '存在' : '不存在'}<br> |
| | | ${token ? `Token内容: ${token.substring(0, 50)}...` : ''} |
| | | `); |
| | | } |
| | | |
| | | async function testRoleAPI() { |
| | | try { |
| | | const token = localStorage.getItem('auth_token'); |
| | | const headers = { |
| | | 'Content-Type': 'application/json' |
| | | }; |
| | | |
| | | if (token) { |
| | | headers['Authorization'] = `Bearer ${token}`; |
| | | } |
| | | |
| | | const response = await fetch('/api/graphql', { |
| | | method: 'POST', |
| | | headers, |
| | | body: JSON.stringify({ |
| | | query: `query GetActiveRoles { |
| | | activeRoles { |
| | | id |
| | | code |
| | | name |
| | | description |
| | | state |
| | | createTime |
| | | updateTime |
| | | } |
| | | }` |
| | | }) |
| | | }); |
| | | |
| | | const data = await response.json(); |
| | | |
| | | if (data.errors) { |
| | | addResult(`<strong>角色API错误:</strong><br>${JSON.stringify(data.errors, null, 2)}`, true); |
| | | } else { |
| | | addResult(`<strong>角色API成功:</strong><br>角色数量: ${data.data?.activeRoles?.length || 0}<br>数据: <pre>${JSON.stringify(data.data, null, 2)}</pre>`); |
| | | } |
| | | } catch (error) { |
| | | addResult(`<strong>角色API调用失败:</strong><br>${error.message}`, true); |
| | | } |
| | | } |
| | | |
| | | async function loginAsAdmin() { |
| | | try { |
| | | const response = await fetch('/api/graphql', { |
| | | method: 'POST', |
| | | headers: { |
| | | 'Content-Type': 'application/json' |
| | | }, |
| | | body: JSON.stringify({ |
| | | query: `mutation Login($phone: String!, $password: String!) { |
| | | login(phone: $phone, password: $password) { |
| | | token |
| | | user { |
| | | userId |
| | | name |
| | | phone |
| | | userType |
| | | } |
| | | } |
| | | }`, |
| | | variables: { |
| | | phone: "13800000001", |
| | | password: "123456" |
| | | } |
| | | }) |
| | | }); |
| | | |
| | | const data = await response.json(); |
| | | |
| | | if (data.errors) { |
| | | addResult(`<strong>登录失败:</strong><br>${JSON.stringify(data.errors, null, 2)}`, true); |
| | | } else if (data.data?.login?.token) { |
| | | localStorage.setItem('auth_token', data.data.login.token); |
| | | localStorage.setItem('user_info', JSON.stringify(data.data.login.user)); |
| | | addResult(`<strong>登录成功:</strong><br>Token已保存<br>用户信息: ${JSON.stringify(data.data.login.user, null, 2)}`); |
| | | } else { |
| | | addResult(`<strong>登录响应异常:</strong><br>${JSON.stringify(data, null, 2)}`, true); |
| | | } |
| | | } catch (error) { |
| | | addResult(`<strong>登录请求失败:</strong><br>${error.message}`, true); |
| | | } |
| | | } |
| | | |
| | | async function testRoleAPIAfterLogin() { |
| | | await testRoleAPI(); |
| | | } |
| | | |
| | | async function loadRolesIntoSelect() { |
| | | try { |
| | | const token = localStorage.getItem('auth_token'); |
| | | const headers = { |
| | | 'Content-Type': 'application/json' |
| | | }; |
| | | |
| | | if (token) { |
| | | headers['Authorization'] = `Bearer ${token}`; |
| | | } |
| | | |
| | | const response = await fetch('/api/graphql', { |
| | | method: 'POST', |
| | | headers, |
| | | body: JSON.stringify({ |
| | | query: `query GetActiveRoles { |
| | | activeRoles { |
| | | id |
| | | code |
| | | name |
| | | description |
| | | state |
| | | createTime |
| | | updateTime |
| | | } |
| | | }` |
| | | }) |
| | | }); |
| | | |
| | | const data = await response.json(); |
| | | const select = document.getElementById('roleSelect'); |
| | | |
| | | // 清空现有选项(除了默认选项) |
| | | select.innerHTML = '<option value="">请选择角色</option>'; |
| | | |
| | | if (data.errors) { |
| | | addResult(`<strong>加载角色到下拉框失败:</strong><br>${JSON.stringify(data.errors, null, 2)}`, true); |
| | | } else if (data.data?.activeRoles) { |
| | | const roles = data.data.activeRoles; |
| | | roles.forEach(role => { |
| | | const option = document.createElement('option'); |
| | | option.value = role.id; |
| | | option.textContent = role.name; |
| | | select.appendChild(option); |
| | | }); |
| | | addResult(`<strong>成功加载 ${roles.length} 个角色到下拉框</strong>`); |
| | | } else { |
| | | addResult(`<strong>没有获取到角色数据</strong>`, true); |
| | | } |
| | | } catch (error) { |
| | | addResult(`<strong>加载角色到下拉框失败:</strong><br>${error.message}`, true); |
| | | } |
| | | } |
| | | </script> |
| | | </body> |
| | | </html> |
New file |
| | |
| | | <!DOCTYPE html> |
| | | <html> |
| | | <head> |
| | | <title>测试角色API</title> |
| | | </head> |
| | | <body> |
| | | <h1>角色API测试</h1> |
| | | <button onclick="testRoleAPI()">测试角色API</button> |
| | | <div id="result"></div> |
| | | |
| | | <script> |
| | | async function testRoleAPI() { |
| | | const resultDiv = document.getElementById('result'); |
| | | resultDiv.innerHTML = '正在测试...'; |
| | | |
| | | try { |
| | | // 获取token(如果有的话) |
| | | const token = localStorage.getItem('token'); |
| | | |
| | | const headers = { |
| | | 'Content-Type': 'application/json', |
| | | }; |
| | | |
| | | if (token) { |
| | | headers['Authorization'] = `Bearer ${token}`; |
| | | } |
| | | |
| | | const query = ` |
| | | query GetActiveRoles { |
| | | activeRoles { |
| | | id |
| | | code |
| | | name |
| | | description |
| | | state |
| | | createTime |
| | | updateTime |
| | | } |
| | | } |
| | | `; |
| | | |
| | | const response = await fetch('/api/graphql', { |
| | | method: 'POST', |
| | | headers, |
| | | body: JSON.stringify({ |
| | | query, |
| | | variables: {} |
| | | }) |
| | | }); |
| | | |
| | | const result = await response.json(); |
| | | |
| | | resultDiv.innerHTML = ` |
| | | <h3>响应状态: ${response.status}</h3> |
| | | <h3>响应数据:</h3> |
| | | <pre>${JSON.stringify(result, null, 2)}</pre> |
| | | `; |
| | | |
| | | } catch (error) { |
| | | resultDiv.innerHTML = ` |
| | | <h3>错误:</h3> |
| | | <pre>${error.message}</pre> |
| | | `; |
| | | } |
| | | } |
| | | </script> |
| | | </body> |
| | | </html> |