lrj
21 小时以前 7ad9c3c93f0cc103347ae2e2429e0122fb512e24
feat: 修复员工管理功能并优化UI

- 修复角色下拉框数据加载问题,确保正确访问GraphQL响应数据
- 修复员工搜索功能,使用正确的GraphQL查询名称和参数
- 优化员工列表操作栏样式,使用无背景的彩色图标
- 重构前端页面结构,统一命名规范
- 修复多个API的数据访问路径问题
- 添加角色管理相关的调试和修复工具
20个文件已修改
7个文件已添加
13 文件已重命名
4个文件已删除
2483 ■■■■ 已修改文件
backend/src/main/java/com/rongyichuang/activity/service/ActivityService.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
backend/src/main/java/com/rongyichuang/player/service/PlayerApplicationService.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
fix-roles.ps1 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
fix_employee_roles.sql 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/check-auth.html 128 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/debug-roles.html 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/fix-employee-roles.html 307 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/api/activity.js 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/api/carousel.js 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/api/employee.ts 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/api/media.js 53 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/api/player.js 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/api/projectReview.js 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/api/projectReviewNew.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/api/promotion.js 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/api/rating.js 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/api/region.js 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/api/role.ts 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/components/JudgeFormSimple.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/config/api.ts 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/router/index.ts 54 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/test/direct-upload-test.js 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/test/upload-logo-test.ts 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/utils/cos-simple.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/utils/cos.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/utils/upload-logo-browser.ts 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/views/ActivityForm.vue 225 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/views/RatingSchemeForm.vue 432 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/views/RatingSchemeList.vue 254 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/views/activity-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
web/src/views/check-detail.vue 补丁 | 查看 | 原始文档 | blame | 历史
web/src/views/check-list.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/views/employee-detail.vue 补丁 | 查看 | 原始文档 | blame | 历史
web/src/views/employee-list.vue 60 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/src/views/judge-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
web/src/views/judge-review-detail.vue 补丁 | 查看 | 原始文档 | blame | 历史
web/src/views/judge-review-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
web/src/views/next-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
web/src/views/rating-detail.vue 补丁 | 查看 | 原始文档 | blame | 历史
web/src/views/rating-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
web/src/views/review-detail.vue 补丁 | 查看 | 原始文档 | blame | 历史
web/src/views/review-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
web/test-employee-roles.html 198 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web/test-role-api.html 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
backend/src/main/java/com/rongyichuang/activity/service/ActivityService.java
@@ -16,6 +16,8 @@
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;
@@ -37,6 +39,8 @@
@Service
@Transactional
public class ActivityService {
    private static final Logger log = LoggerFactory.getLogger(ActivityService.class);
    
    @Autowired
    private ActivityRepository activityRepository;
@@ -185,6 +189,9 @@
        // 保存比赛
        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());
@@ -264,7 +271,11 @@
                stage.setRatingSchemeId(activity.getRatingSchemeId());
            }
            
            activityRepository.save(stage);
            // 保存阶段并获取自增ID
            stage = activityRepository.save(stage);
            // 记录日志以便调试
            log.info("保存阶段成功,阶段ID: {}, 阶段名称: {}", stage.getId(), stage.getName());
        }
    }
    
@@ -273,14 +284,20 @@
     */
    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());
            
@@ -289,12 +306,14 @@
                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 {
@@ -303,7 +322,8 @@
                    // 如果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);
                }
            }
        }
backend/src/main/java/com/rongyichuang/player/service/PlayerApplicationService.java
@@ -23,11 +23,18 @@
    @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;
fix-roles.ps1
New file
@@ -0,0 +1,81 @@
# 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"
fix_employee_roles.sql
New file
@@ -0,0 +1,27 @@
-- 修复员工表中的无效角色代码
-- 将无效的角色代码替换为有效的角色代码
-- 查看当前员工表中的角色代码分布
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;
web/check-auth.html
New file
@@ -0,0 +1,128 @@
<!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>
web/debug-roles.html
New file
@@ -0,0 +1,99 @@
<!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>
web/fix-employee-roles.html
New file
@@ -0,0 +1,307 @@
<!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>
web/src/api/activity.js
@@ -122,8 +122,8 @@
// 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 || '获取比赛列表失败');
  }
@@ -131,8 +131,8 @@
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 || '获取比赛详情失败');
  }
@@ -140,14 +140,9 @@
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);
web/src/api/carousel.js
@@ -77,26 +77,26 @@
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
  },
  
  // 删除轮播图
web/src/api/employee.ts
@@ -19,7 +19,7 @@
    }
  `,
  
  // 根据姓名搜索员工
  // 搜索员工
  SEARCH_EMPLOYEES: `
    query SearchEmployees($name: String) {
      employeesByName(name: $name) {
@@ -99,18 +99,18 @@
  // 获取员工列表
  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 || '搜索员工失败')
    }
@@ -119,8 +119,8 @@
  // 根据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 || '获取员工详情失败')
    }
@@ -129,8 +129,8 @@
  // 保存员工(新增或更新)
  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 || '保存员工失败')
    }
@@ -139,8 +139,8 @@
  // 删除员工
  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 || '删除员工失败')
    }
web/src/api/media.js
@@ -86,38 +86,19 @@
};
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;
};
// 上传文件到服务器
@@ -152,17 +133,11 @@
  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);
@@ -171,9 +146,7 @@
    });
    
    // 4. 上传缩略图
    console.log('上传缩略图...');
    const thumbnailUploadResult = await uploadFile(thumbnailFile);
    console.log('缩略图上传成功:', thumbnailUploadResult);
    
    // 5. 返回包含视频和缩略图信息的结果
    return {
web/src/api/player.js
@@ -68,8 +68,8 @@
// 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 || '获取学员列表失败')
  }
@@ -77,8 +77,8 @@
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 || '获取学员详情失败')
  }
@@ -86,8 +86,8 @@
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 || '保存学员失败')
  }
@@ -95,8 +95,8 @@
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 || '删除学员失败')
  }
@@ -117,7 +117,7 @@
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 }
  }
}
web/src/api/projectReview.js
@@ -126,15 +126,17 @@
 */
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 || '获取比赛阶段失败')
  }
}
@@ -144,13 +146,13 @@
 */
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(
@@ -207,8 +209,8 @@
 */
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 || '获取项目详情失败')
  }
@@ -219,10 +221,10 @@
 */
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)
@@ -248,8 +250,8 @@
 */
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 || '获取当前评委评分失败')
  }
@@ -260,8 +262,8 @@
 */
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 || '提交评分失败')
  }
web/src/api/projectReviewNew.js
@@ -23,8 +23,9 @@
`
// 获取项目评审列表
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 {
web/src/api/promotion.js
@@ -78,8 +78,8 @@
        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
@@ -94,8 +94,8 @@
        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
web/src/api/rating.js
@@ -81,8 +81,8 @@
// 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 || '获取评分方案列表失败')
  }
@@ -90,8 +90,8 @@
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 || '获取所有评分方案失败')
  }
web/src/api/region.js
@@ -17,8 +17,8 @@
  `
  
  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 || '获取地区列表失败')
  }
@@ -39,8 +39,8 @@
  `
  
  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 || '获取地区详情失败')
  }
@@ -98,8 +98,8 @@
  `
  
  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 || '获取省份列表失败')
  }
@@ -119,8 +119,8 @@
  `
  
  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 || '获取城市列表失败')
  }
@@ -140,8 +140,8 @@
  `
  
  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 || '获取区县列表失败')
  }
web/src/api/role.ts
@@ -111,8 +111,8 @@
  // 获取所有角色
  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 || '获取角色列表失败')
    }
@@ -121,8 +121,8 @@
  // 获取激活状态的角色
  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 || '获取激活角色列表失败')
    }
@@ -131,8 +131,8 @@
  // 根据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 || '获取角色详情失败')
     }
@@ -141,8 +141,8 @@
  // 根据代码获取角色
  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 || '获取角色详情失败')
    }
@@ -151,8 +151,8 @@
  // 根据状态获取角色
  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 || '获取角色列表失败')
    }
@@ -161,8 +161,8 @@
  // 根据名称搜索角色
  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 || '搜索角色失败')
    }
web/src/components/JudgeFormSimple.vue
@@ -233,8 +233,6 @@
// 监听评委数据变化,填充表单
watch(() => props.judgeData, (data) => {
  console.log('🔍 Watch triggered, judgeData:', data)
  console.log('🔍 isEdit computed:', isEdit.value)
  nextTick(async () => {
    if (data && data.id) {
      // 编辑模式:填充表单数据
web/src/config/api.ts
@@ -17,54 +17,44 @@
// 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);
web/src/router/index.ts
@@ -17,14 +17,8 @@
      {
        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',
@@ -39,40 +33,46 @@
        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',
@@ -95,37 +95,37 @@
      {
        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' }
      },
web/src/test/direct-upload-test.js
File was deleted
web/src/test/upload-logo-test.ts
File was deleted
web/src/utils/cos-simple.ts
@@ -38,16 +38,11 @@
 */
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, {
@@ -57,7 +52,6 @@
    })
    
    if (uploadResponse.status === 200) {
      console.log('文件上传成功!')
      // 返回文件的访问URL(去掉查询参数)
      const fileUrl = `https://${credentials.bucket}.cos.${credentials.region}.myqcloud.com/${credentials.key}`
      return fileUrl
@@ -66,7 +60,6 @@
    }
    
  } catch (error) {
    console.error('上传文件失败:', error)
    throw error
  }
}
web/src/utils/cos.ts
@@ -7,7 +7,6 @@
    const response = await axios.get('http://localhost:8080/api/cos/credentials')
    return response.data
  } catch (error) {
    console.error('从后端获取临时密钥失败:', error)
    throw error
  }
}
web/src/utils/upload-logo-browser.ts
@@ -21,12 +21,8 @@
      }
    }
    
    console.log('开始上传logo文件:', logoFile.name, logoFile.size, 'bytes')
    // 上传到COS的avatars目录
    const url = await uploadToCOS(logoFile, 'avatars/')
    console.log('Logo上传成功:', url)
    
    return {
      success: true,
@@ -56,7 +52,7 @@
      return new File([blob], 'logo.jpg', { type: 'image/jpeg' })
    }
  } catch (error) {
    console.log('从public目录获取logo失败,尝试其他方式')
    // 从public目录获取logo失败,尝试其他方式
  }
  
  try {
@@ -67,7 +63,7 @@
      return new File([blob], 'logo.jpg', { type: 'image/jpeg' })
    }
  } catch (error) {
    console.log('从UI目录获取logo失败')
    // 从UI目录获取logo失败
  }
  
  // 方式3: 返回null,需要用户手动选择文件
web/src/views/ActivityForm.vue
@@ -79,7 +79,10 @@
        </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">
@@ -109,7 +112,7 @@
              <!-- 添加按钮 -->
              <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="#"
@@ -120,7 +123,6 @@
                <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>
@@ -141,7 +143,7 @@
                </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">
@@ -191,9 +193,8 @@
                    </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>
@@ -321,14 +322,16 @@
        
        <!-- 阶段选择 -->
        <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>
@@ -393,7 +396,7 @@
          <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"
@@ -413,7 +416,7 @@
</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'
@@ -466,7 +469,7 @@
// 评委选择相关
const allJudges = ref([])
const judgeSearchText = ref('')
const selectedStageOption = ref('all')
const selectedStageOptions = ref([])
const selectedJudges = ref([])
const judgeLoading = ref(false)
@@ -521,6 +524,21 @@
  })
})
// 用于下拉框的阶段选项
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: [
@@ -563,7 +581,6 @@
    judgeLoading.value = true
    const judges = await getAllJudges()
    allJudges.value = judges || []
    console.log('加载评委列表成功:', allJudges.value.length, '个评委')
  } catch (error) {
    console.error('加载评委列表失败:', error)
    ElMessage.error('加载评委列表失败: ' + error.message)
@@ -580,6 +597,7 @@
  try {
    loading.value = true
    const activity = await getActivity(route.params.id)
    if (activity) {
      form.value = {
        id: activity.id,
@@ -599,12 +617,8 @@
      // 加载并回填已上传媒体: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 = {
@@ -615,16 +629,14 @@
            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)
@@ -637,7 +649,7 @@
// 阶段管理
// 阶段数量变化处理
const onStageCountChange = (count) => {
  if (!count) return
  if (!count || !form.value || !form.value.stages) return
  
  // 如果当前阶段数量少于选择的数量,自动添加阶段
  while (form.value.stages.length < count) {
@@ -672,6 +684,7 @@
// 获取阶段在原始数组中的索引
const getOriginalStageIndex = (stage) => {
  if (!form.value || !form.value.stages) return -1
  return form.value.stages.findIndex(s => s === stage)
}
@@ -688,6 +701,8 @@
}
const removeStage = async (index) => {
  if (!form.value || !form.value.stages) return
  try {
    await ElMessageBox.confirm('确定要删除这个阶段吗?', '提示', {
      confirmButtonText: '确定',
@@ -725,6 +740,8 @@
}
const saveStage = async () => {
  if (!form.value || !form.value.stages) return
  try {
    await stageFormRef.value.validate()
    
@@ -787,6 +804,11 @@
}
const removeJudge = async (index) => {
  if (!form.value || !form.value.judges) {
    ElMessage.error('表单数据未初始化')
    return
  }
  try {
    await ElMessageBox.confirm('确定要删除这个评委吗?', '提示', {
      confirmButtonText: '确定',
@@ -801,43 +823,40 @@
}
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 = () => {
@@ -859,6 +878,17 @@
    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 => {
@@ -867,14 +897,20 @@
      // 检查是否已经存在
      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,
@@ -886,6 +922,10 @@
      }
    }
  })
  // 清空选择
  selectedJudges.value = []
  selectedStageOptions.value = []
  
  judgeDialogVisible.value = false
  ElMessage.success(`成功添加 ${addedCount} 个评委`)
@@ -940,7 +980,7 @@
}
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 : '无'
}
@@ -985,13 +1025,17 @@
    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
@@ -1056,6 +1100,8 @@
// 处理媒体文件上传
const handleMediaUpload = async (activityId) => {
  if (!form.value || !form.value.mediaFiles) return
  try {
    for (const mediaFile of form.value.mediaFiles) {
      // 跳过已经有 id 的媒体文件(已保存的)
@@ -1074,10 +1120,8 @@
      }
      
      try {
        console.log('开始上传文件:', mediaFile.name)
        // 1. 上传文件到服务器
        const uploadResult = await uploadFile(mediaFile.file)
        console.log('文件上传成功:', uploadResult)
        
        // 2. 保存媒体信息到数据库
        const mediaInput = {
@@ -1089,11 +1133,7 @@
          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
@@ -1117,6 +1157,8 @@
// 提交表单
const handleSubmit = async () => {
  if (submitting.value) return
  if (!form.value) return
  try {
    await formRef.value.validate()
    
@@ -1124,8 +1166,6 @@
    
    // 准备保存数据,只包含后端支持的字段
    const saveData = {
      id: form.value.id,
      pid: form.value.pid || 0,
      name: form.value.name,
      description: form.value.description,
      signupDeadline: form.value.signupDeadline,
@@ -1134,22 +1174,41 @@
      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)
@@ -1195,7 +1254,7 @@
  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)
  }
})
@@ -1572,4 +1631,24 @@
  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>
web/src/views/RatingSchemeForm.vue
File was deleted
web/src/views/RatingSchemeList.vue
File was deleted
web/src/views/activity-list.vue
web/src/views/check-detail.vue
web/src/views/check-list.vue
File was renamed from web/src/views/player/index.vue
@@ -230,9 +230,15 @@
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
web/src/views/employee-detail.vue
web/src/views/employee-list.vue
File was renamed from web/src/views/employee/index.vue
@@ -35,11 +35,25 @@
            {{ 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>
@@ -65,7 +79,7 @@
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)
@@ -82,10 +96,10 @@
// 获取角色标签类型
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'
}
@@ -93,10 +107,10 @@
// 获取角色文本
const getRoleText = (roleId: string) => {
  const textMap: Record<string, string> = {
    'ADMIN': '管理员',
    'MANAGER': '经理',
    'STAFF': '工作人员',
    'REVIEWER': '审核员'
    'SUPER_ADMIN': '超级管理员',
    'AUDITOR': '平台工作人员',
    'JUDGE_MANAGER': '评委',
    'PLAYER': '参赛人员'
  }
  return textMap[roleId] || roleId
}
@@ -202,7 +216,27 @@
  .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 {
web/src/views/judge-list.vue
web/src/views/judge-review-detail.vue
web/src/views/judge-review-list.vue
web/src/views/next-list.vue
web/src/views/rating-detail.vue
web/src/views/rating-list.vue
web/src/views/review-detail.vue
web/src/views/review-list.vue
web/test-employee-roles.html
New file
@@ -0,0 +1,198 @@
<!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>
web/test-role-api.html
New file
@@ -0,0 +1,68 @@
<!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>