From 36e29c5a3f4eb8a357bf574b0e76a21a5bb68aa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B3=E4=BD=A9=E5=BA=AD?= Date: Sun, 21 Sep 2025 20:57:14 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=A9=95=E5=AF=A9=E8=A9=95?= =?UTF-8?q?=E5=88=86=E6=A9=9F=E5=88=B6=E5=95=8F=E9=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/admin/analytics/route.ts | 13 +++- app/api/admin/debug-competition-data/route.ts | 76 ++++++++++++++++++ app/api/judge/scoring-tasks/route.ts | 25 +++++- app/judge-scoring/page.tsx | 49 ++++++++++-- components/admin/analytics-dashboard.tsx | 47 ++++++----- lib/services/database-service.ts | 77 ++++++++++++++++--- 6 files changed, 244 insertions(+), 43 deletions(-) create mode 100644 app/api/admin/debug-competition-data/route.ts diff --git a/app/api/admin/analytics/route.ts b/app/api/admin/analytics/route.ts index 79ffef2..a4031ee 100644 --- a/app/api/admin/analytics/route.ts +++ b/app/api/admin/analytics/route.ts @@ -93,6 +93,8 @@ export async function GET(request: NextRequest) { // 構建每日使用數據 const dailyUsageData = [] + console.log('🔍 開始構建每日使用數據...') + for (let i = 6; i >= 0; i--) { const date = new Date(Date.now() - i * 24 * 60 * 60 * 1000) const dateStr = date.toISOString().split('T')[0] @@ -111,7 +113,7 @@ export async function GET(request: NextRequest) { const memoryPeak = Math.min(85, 25 + dailyUsers * 0.7 + dailySessions * 0.04) const requests = dailySessions + dailyActivity - dailyUsageData.push({ + const dayData = { date: `${date.getMonth() + 1}/${date.getDate()}`, fullDate: date.toLocaleDateString("zh-TW"), dayName: dayName, @@ -121,8 +123,13 @@ export async function GET(request: NextRequest) { avgCpu: Math.round(avgCpu), memoryPeak: Math.round(memoryPeak), requests: requests - }) + } + + dailyUsageData.push(dayData) + console.log(`📊 ${dateStr}:`, dayData) } + + console.log('✅ 每日使用數據構建完成:', dailyUsageData) // 獲取應用類別分布 const categoryDataResult = await db.query(` @@ -146,6 +153,8 @@ export async function GET(request: NextRequest) { apps: item.app_count } }) + + console.log('📊 類別數據:', categoryData) // 獲取熱門應用排行 const topAppsResult = await db.query(` diff --git a/app/api/admin/debug-competition-data/route.ts b/app/api/admin/debug-competition-data/route.ts new file mode 100644 index 0000000..0fbb6c0 --- /dev/null +++ b/app/api/admin/debug-competition-data/route.ts @@ -0,0 +1,76 @@ +// ===================================================== +// 調試競賽數據 API +// ===================================================== + +import { NextRequest, NextResponse } from 'next/server'; +import { DatabaseServiceBase } from '@/lib/services/database-service'; + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + const competitionId = searchParams.get('competitionId') || '07e2303e-9647-11f0-b5d9-6e36c63cdb98'; + + console.log('🔍 開始調試競賽數據...'); + console.log('競賽ID:', competitionId); + + // 1. 檢查競賽是否存在 + const competitionCheck = await DatabaseServiceBase.safeQuery( + 'SELECT * FROM competitions WHERE id = ?', + [competitionId] + ); + + console.log('📊 競賽檢查結果:', competitionCheck); + + // 2. 檢查競賽應用關聯 + const competitionAppsCheck = await DatabaseServiceBase.safeQuery( + 'SELECT * FROM competition_apps WHERE competition_id = ?', + [competitionId] + ); + + console.log('📊 競賽應用關聯檢查結果:', competitionAppsCheck); + + // 3. 檢查所有應用程式 + const allAppsCheck = await DatabaseServiceBase.safeQuery( + 'SELECT id, name, is_active FROM apps WHERE is_active = 1 LIMIT 10', + [] + ); + + console.log('📊 所有應用程式檢查結果:', allAppsCheck); + + // 4. 檢查競賽規則 + const competitionRulesCheck = await DatabaseServiceBase.safeQuery( + 'SELECT * FROM competition_rules WHERE competition_id = ?', + [competitionId] + ); + + console.log('📊 競賽規則檢查結果:', competitionRulesCheck); + + // 5. 檢查評審 + const judgesCheck = await DatabaseServiceBase.safeQuery( + 'SELECT id, name, title, department FROM judges WHERE is_active = 1 LIMIT 5', + [] + ); + + console.log('📊 評審檢查結果:', judgesCheck); + + return NextResponse.json({ + success: true, + message: '調試數據獲取成功', + data: { + competition: competitionCheck, + competitionApps: competitionAppsCheck, + allApps: allAppsCheck, + competitionRules: competitionRulesCheck, + judges: judgesCheck + } + }); + + } catch (error) { + console.error('❌ 調試競賽數據失敗:', error); + return NextResponse.json({ + success: false, + message: '調試競賽數據失敗', + error: error instanceof Error ? error.message : '未知錯誤' + }, { status: 500 }); + } +} diff --git a/app/api/judge/scoring-tasks/route.ts b/app/api/judge/scoring-tasks/route.ts index f7d086e..608cded 100644 --- a/app/api/judge/scoring-tasks/route.ts +++ b/app/api/judge/scoring-tasks/route.ts @@ -8,7 +8,12 @@ export async function GET(request: NextRequest) { const judgeId = searchParams.get('judgeId'); const competitionId = searchParams.get('competitionId'); + console.log('🔍 評審任務API - 接收請求'); + console.log('judgeId:', judgeId); + console.log('competitionId:', competitionId); + if (!judgeId) { + console.log('❌ 缺少評審ID'); return NextResponse.json({ success: false, message: '缺少評審ID', @@ -17,8 +22,12 @@ export async function GET(request: NextRequest) { } // 獲取評審信息 + console.log('🔍 開始獲取評審信息...'); const judge = await JudgeService.getJudgeById(judgeId); + console.log('📊 評審信息查詢結果:', judge); + if (!judge) { + console.log('❌ 評審不存在'); return NextResponse.json({ success: false, message: '評審不存在', @@ -27,17 +36,22 @@ export async function GET(request: NextRequest) { } // 獲取評審的評分任務 + console.log('🔍 開始獲取評分任務...'); let scoringTasks = []; if (competitionId) { // 獲取特定競賽的評分任務 + console.log('📊 獲取特定競賽的評分任務'); scoringTasks = await JudgeService.getJudgeScoringTasks(judgeId, competitionId); } else { // 獲取所有評分任務 + console.log('📊 獲取所有評分任務'); scoringTasks = await JudgeService.getJudgeScoringTasks(judgeId); } - return NextResponse.json({ + console.log('📊 評分任務查詢結果:', scoringTasks); + + const response = { success: true, message: '評分任務獲取成功', data: { @@ -46,14 +60,17 @@ export async function GET(request: NextRequest) { name: judge.name, title: judge.title, department: judge.department, - specialty: judge.specialty || '評審專家' + specialty: '評審專家' }, tasks: scoringTasks } - }); + }; + + console.log('✅ API回應:', response); + return NextResponse.json(response); } catch (error) { - console.error('獲取評分任務失敗:', error); + console.error('❌ 獲取評分任務失敗:', error); return NextResponse.json({ success: false, message: '獲取評分任務失敗', diff --git a/app/judge-scoring/page.tsx b/app/judge-scoring/page.tsx index 778c783..852429f 100644 --- a/app/judge-scoring/page.tsx +++ b/app/judge-scoring/page.tsx @@ -1,6 +1,7 @@ "use client" -import { useState } from "react" +import { useState, useEffect } from "react" +import { useSearchParams } from "next/navigation" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" @@ -22,6 +23,7 @@ interface Judge { interface ScoringItem { id: string name: string + display_name?: string type: "individual" | "team" status: "pending" | "completed" score?: number @@ -29,6 +31,7 @@ interface ScoringItem { } export default function JudgeScoringPage() { + const searchParams = useSearchParams() const [isLoggedIn, setIsLoggedIn] = useState(false) const [judgeId, setJudgeId] = useState("") const [accessCode, setAccessCode] = useState("") @@ -44,6 +47,18 @@ export default function JudgeScoringPage() { const [showAccessCode, setShowAccessCode] = useState(false) const [isLoading, setIsLoading] = useState(false) const [competitionRules, setCompetitionRules] = useState([]) + const [competitionId, setCompetitionId] = useState("") + + // 從URL參數中獲取競賽ID + useEffect(() => { + const competitionIdParam = searchParams.get('competitionId') + if (competitionIdParam) { + setCompetitionId(competitionIdParam) + } else { + // 如果沒有URL參數,使用默認的競賽ID + setCompetitionId('07e2303e-9647-11f0-b5d9-6e36c63cdb98') + } + }, [searchParams]) const handleLogin = async () => { setError("") @@ -62,11 +77,21 @@ export default function JudgeScoringPage() { } try { + console.log('🔍 開始登入流程...') + console.log('評審ID:', judgeId) + console.log('競賽ID:', competitionId) + // 獲取評審的評分任務 - const response = await fetch(`/api/judge/scoring-tasks?judgeId=${judgeId}&competitionId=0fffae9a-9539-11f0-b5d9-6e36c63cdb98`) + const response = await fetch(`/api/judge/scoring-tasks?judgeId=${judgeId}&competitionId=${competitionId}`) const data = await response.json() + console.log('📊 評分任務API回應:', data) + if (data.success) { + console.log('✅ 登入成功') + console.log('評審信息:', data.data.judge) + console.log('評分任務:', data.data.tasks) + setCurrentJudge(data.data.judge) setScoringItems(data.data.tasks) setIsLoggedIn(true) @@ -76,10 +101,11 @@ export default function JudgeScoringPage() { // 載入競賽規則 await loadCompetitionRules() } else { + console.error('❌ 登入失敗:', data.message) setError(data.message || "登入失敗") } } catch (err) { - console.error('登入失敗:', err) + console.error('❌ 登入失敗:', err) setError("登入失敗,請重試") } finally { setIsLoading(false) @@ -88,18 +114,27 @@ export default function JudgeScoringPage() { const loadCompetitionRules = async () => { try { - // 使用正確的競賽ID - const response = await fetch('/api/competitions/0fffae9a-9539-11f0-b5d9-6e36c63cdb98/rules') + console.log('🔍 開始載入競賽規則...') + console.log('競賽ID:', competitionId) + + // 使用動態的競賽ID + const response = await fetch(`/api/competitions/${competitionId}/rules`) const data = await response.json() + console.log('📊 競賽規則API回應:', data) + if (data.success) { + console.log('✅ 競賽規則載入成功:', data.data) setCompetitionRules(data.data) + } else { + console.error('❌ 競賽規則載入失敗:', data.message) } } catch (err) { - console.error('載入競賽規則失敗:', err) + console.error('❌ 載入競賽規則失敗:', err) } } + const handleStartScoring = async (item: ScoringItem) => { setSelectedItem(item) @@ -169,7 +204,7 @@ export default function JudgeScoringPage() { participantType: 'app', scores: scores, comments: comments.trim(), - competitionId: '0fffae9a-9539-11f0-b5d9-6e36c63cdb98', // 正確的競賽ID + competitionId: competitionId, // 動態競賽ID isEdit: selectedItem.status === "completed", // 如果是重新評分,標記為編輯模式 recordId: selectedItem.status === "completed" ? selectedItem.id : null }) diff --git a/components/admin/analytics-dashboard.tsx b/components/admin/analytics-dashboard.tsx index 4464550..68ee146 100644 --- a/components/admin/analytics-dashboard.tsx +++ b/components/admin/analytics-dashboard.tsx @@ -55,16 +55,20 @@ export function AnalyticsDashboard() { const loadAnalyticsData = async () => { try { setIsLoading(true) + console.log('🔄 開始載入分析數據...') const response = await fetch('/api/admin/analytics') const data = await response.json() + console.log('📊 API回應數據:', data) + if (data.success) { setAnalyticsData(data.data) + console.log('✅ 分析數據載入成功') } else { - console.error('載入分析數據失敗:', data.error) + console.error('❌ 載入分析數據失敗:', data.error) } } catch (error) { - console.error('載入分析數據錯誤:', error) + console.error('❌ 載入分析數據錯誤:', error) } finally { setIsLoading(false) } @@ -111,23 +115,30 @@ export function AnalyticsDashboard() { } const recentDates = getRecentDates() - const dailyUsageData = [ - { ...recentDates[0], users: 245, sessions: 189, cpuPeak: 65, avgCpu: 45, memoryPeak: 58, requests: 1240 }, - { ...recentDates[1], users: 267, sessions: 203, cpuPeak: 68, avgCpu: 48, memoryPeak: 62, requests: 1356 }, - { ...recentDates[2], users: 289, sessions: 221, cpuPeak: 72, avgCpu: 52, memoryPeak: 65, requests: 1478 }, - { ...recentDates[3], users: 312, sessions: 245, cpuPeak: 75, avgCpu: 55, memoryPeak: 68, requests: 1589 }, - { ...recentDates[4], users: 298, sessions: 234, cpuPeak: 73, avgCpu: 53, memoryPeak: 66, requests: 1523 }, - { ...recentDates[5], users: 334, sessions: 267, cpuPeak: 78, avgCpu: 58, memoryPeak: 71, requests: 1678 }, - { ...recentDates[6], users: 356, sessions: 289, cpuPeak: 82, avgCpu: 62, memoryPeak: 75, requests: 1789 }, - ] + + // 使用API提供的真實數據,如果沒有則使用默認數據 + const dailyUsageData = analyticsData.dailyUsageData && analyticsData.dailyUsageData.length > 0 + ? analyticsData.dailyUsageData + : [ + { ...recentDates[0], users: 0, sessions: 0, cpuPeak: 20, avgCpu: 15, memoryPeak: 25, requests: 0 }, + { ...recentDates[1], users: 0, sessions: 0, cpuPeak: 20, avgCpu: 15, memoryPeak: 25, requests: 0 }, + { ...recentDates[2], users: 0, sessions: 0, cpuPeak: 20, avgCpu: 15, memoryPeak: 25, requests: 0 }, + { ...recentDates[3], users: 0, sessions: 0, cpuPeak: 20, avgCpu: 15, memoryPeak: 25, requests: 0 }, + { ...recentDates[4], users: 0, sessions: 0, cpuPeak: 20, avgCpu: 15, memoryPeak: 25, requests: 0 }, + { ...recentDates[5], users: 0, sessions: 0, cpuPeak: 20, avgCpu: 15, memoryPeak: 25, requests: 0 }, + { ...recentDates[6], users: 0, sessions: 0, cpuPeak: 20, avgCpu: 15, memoryPeak: 25, requests: 0 }, + ] - const categoryData = [ - { name: "AI工具", value: 35, color: "#3b82f6", users: 3083, apps: 45 }, - { name: "數據分析", value: 25, color: "#ef4444", users: 1565, apps: 32 }, - { name: "自動化", value: 20, color: "#10b981", users: 856, apps: 25 }, - { name: "機器學習", value: 15, color: "#f59e0b", users: 743, apps: 19 }, - { name: "其他", value: 5, color: "#8b5cf6", users: 234, apps: 6 }, - ] + // 使用API提供的真實類別數據,如果沒有則使用默認數據 + const categoryData = analyticsData.categoryData && analyticsData.categoryData.length > 0 + ? analyticsData.categoryData + : [ + { name: "AI工具", value: 35, color: "#3b82f6", users: 0, apps: 0 }, + { name: "數據分析", value: 25, color: "#ef4444", users: 0, apps: 0 }, + { name: "自動化", value: 20, color: "#10b981", users: 0, apps: 0 }, + { name: "機器學習", value: 15, color: "#f59e0b", users: 0, apps: 0 }, + { name: "其他", value: 5, color: "#8b5cf6", users: 0, apps: 0 }, + ] const topApps = [ { name: "智能客服助手", views: 1234, rating: 4.8, category: "AI工具" }, diff --git a/lib/services/database-service.ts b/lib/services/database-service.ts index fe24385..57b741e 100644 --- a/lib/services/database-service.ts +++ b/lib/services/database-service.ts @@ -960,11 +960,16 @@ export class JudgeService extends DatabaseServiceBase { // 獲取評審的評分任務 static async getJudgeScoringTasks(judgeId: string, competitionId?: string): Promise { + console.log('🔍 JudgeService.getJudgeScoringTasks 被調用'); + console.log('judgeId:', judgeId); + console.log('competitionId:', competitionId); + let sql: string; let params: any[]; if (competitionId) { // 獲取特定競賽的評分任務 + // 使用 UNION 來合併個人競賽和團隊競賽的應用程式 sql = ` SELECT DISTINCT a.id, @@ -977,16 +982,37 @@ export class JudgeService extends DatabaseServiceBase { ELSE 'pending' END as status, js.submitted_at, - t.name as team_name, + NULL as team_name, a.name as display_name FROM apps a + INNER JOIN competition_apps ca ON a.id = ca.app_id + LEFT JOIN app_judge_scores js ON a.id = js.app_id AND js.judge_id = ? + WHERE ca.competition_id = ? AND a.is_active = 1 + + UNION ALL + + SELECT DISTINCT + a.id, + a.name, + 'app' as type, + 'team' as participant_type, + COALESCE(js.total_score, 0) as score, + CASE + WHEN js.total_score > 0 THEN 'completed' + ELSE 'pending' + END as status, + js.submitted_at, + t.name as team_name, + CONCAT(t.name, ' - ', a.name) as display_name + FROM apps a + INNER JOIN competition_teams ct ON a.team_id = ct.team_id LEFT JOIN teams t ON a.team_id = t.id - LEFT JOIN competition_apps ca ON a.id = ca.app_id - LEFT JOIN judge_scores js ON a.id = js.app_id AND js.judge_id = ? - WHERE ca.competition_id = ? - ORDER BY a.name + LEFT JOIN app_judge_scores js ON a.id = js.app_id AND js.judge_id = ? + WHERE ct.competition_id = ? AND a.is_active = 1 + + ORDER BY display_name `; - params = [judgeId, competitionId]; + params = [judgeId, competitionId, judgeId, competitionId]; } else { // 獲取所有競賽的任務 sql = ` @@ -1001,19 +1027,46 @@ export class JudgeService extends DatabaseServiceBase { ELSE 'pending' END as status, js.submitted_at, - t.name as team_name, + NULL as team_name, a.name as display_name FROM apps a + INNER JOIN competition_apps ca ON a.id = ca.app_id + LEFT JOIN app_judge_scores js ON a.id = js.app_id AND js.judge_id = ? + WHERE a.is_active = 1 + + UNION ALL + + SELECT DISTINCT + a.id, + a.name, + 'app' as type, + 'team' as participant_type, + COALESCE(js.total_score, 0) as score, + CASE + WHEN js.total_score > 0 THEN 'completed' + ELSE 'pending' + END as status, + js.submitted_at, + t.name as team_name, + CONCAT(t.name, ' - ', a.name) as display_name + FROM apps a + INNER JOIN competition_teams ct ON a.team_id = ct.team_id LEFT JOIN teams t ON a.team_id = t.id - LEFT JOIN competition_apps ca ON a.id = ca.app_id - LEFT JOIN judge_scores js ON a.id = js.app_id AND js.judge_id = ? - WHERE ca.competition_id IS NOT NULL - ORDER BY a.name + LEFT JOIN app_judge_scores js ON a.id = js.app_id AND js.judge_id = ? + WHERE a.is_active = 1 + + ORDER BY display_name `; - params = [judgeId]; + params = [judgeId, judgeId]; } + console.log('🔍 執行SQL查詢:'); + console.log('SQL:', sql); + console.log('參數:', params); + const results = await DatabaseServiceBase.safeQuery(sql, params); + console.log('📊 查詢結果:', results); + return results; } }