刪除不必要檔案、實作 AI 助手
This commit is contained in:
@@ -1,275 +0,0 @@
|
|||||||
# 資料庫備援系統說明
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
|
|
||||||
本系統實現了主機和備機資料庫的自動備援機制,當主機資料庫出現問題時,系統會自動切換到備機資料庫,確保服務的連續性和穩定性。
|
|
||||||
|
|
||||||
## 功能特點
|
|
||||||
|
|
||||||
- ✅ 自動故障檢測和切換
|
|
||||||
- ✅ 健康檢查機制
|
|
||||||
- ✅ 手動切換功能
|
|
||||||
- ✅ 資料同步功能
|
|
||||||
- ✅ 監控面板
|
|
||||||
- ✅ 連接池管理
|
|
||||||
- ✅ 重試機制
|
|
||||||
|
|
||||||
## 環境變數配置
|
|
||||||
|
|
||||||
在 `.env` 文件中添加以下配置:
|
|
||||||
|
|
||||||
```env
|
|
||||||
# ===== 主機資料庫配置 =====
|
|
||||||
DB_HOST=mysql.theaken.com
|
|
||||||
DB_PORT=33306
|
|
||||||
DB_NAME=db_AI_Platform
|
|
||||||
DB_USER=AI_Platform
|
|
||||||
DB_PASSWORD=Aa123456
|
|
||||||
|
|
||||||
# ===== 備機資料庫配置 =====
|
|
||||||
SLAVE_DB_HOST=122.100.99.161
|
|
||||||
SLAVE_DB_PORT=43306
|
|
||||||
SLAVE_DB_NAME=db_nighttime_care_record
|
|
||||||
SLAVE_DB_USER=A999
|
|
||||||
SLAVE_DB_PASSWORD=1023
|
|
||||||
|
|
||||||
# ===== 資料庫備援配置 =====
|
|
||||||
DB_FAILOVER_ENABLED=true
|
|
||||||
DB_HEALTH_CHECK_INTERVAL=30000
|
|
||||||
DB_CONNECTION_TIMEOUT=5000
|
|
||||||
DB_RETRY_ATTEMPTS=3
|
|
||||||
DB_RETRY_DELAY=2000
|
|
||||||
```
|
|
||||||
|
|
||||||
## 初始化步驟
|
|
||||||
|
|
||||||
### 1. 初始化備機資料庫
|
|
||||||
|
|
||||||
首先在備機上創建資料庫結構:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 初始化備機資料庫結構
|
|
||||||
pnpm run db:init-slave
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 同步資料
|
|
||||||
|
|
||||||
將主機的資料同步到備機:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 同步所有資料
|
|
||||||
pnpm run db:sync
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 檢查健康狀態
|
|
||||||
|
|
||||||
檢查主機和備機的連接狀態:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 檢查資料庫健康狀態
|
|
||||||
pnpm run db:health
|
|
||||||
```
|
|
||||||
|
|
||||||
## 使用方法
|
|
||||||
|
|
||||||
### 程式碼中使用
|
|
||||||
|
|
||||||
系統會自動使用備援功能,無需修改現有程式碼:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { db } from '@/lib/database';
|
|
||||||
|
|
||||||
// 查詢資料
|
|
||||||
const users = await db.query('SELECT * FROM users');
|
|
||||||
|
|
||||||
// 插入資料
|
|
||||||
await db.insert('INSERT INTO users (name, email) VALUES (?, ?)', ['John', 'john@example.com']);
|
|
||||||
|
|
||||||
// 獲取備援狀態
|
|
||||||
const status = db.getFailoverStatus();
|
|
||||||
console.log('當前使用資料庫:', status?.currentDatabase);
|
|
||||||
|
|
||||||
// 手動切換資料庫
|
|
||||||
await db.switchDatabase('slave'); // 切換到備機
|
|
||||||
await db.switchDatabase('master'); // 切換到主機
|
|
||||||
```
|
|
||||||
|
|
||||||
### 監控面板
|
|
||||||
|
|
||||||
訪問管理後台查看資料庫狀態:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { DatabaseMonitor } from '@/components/admin/database-monitor';
|
|
||||||
|
|
||||||
// 在管理頁面中使用
|
|
||||||
<DatabaseMonitor />
|
|
||||||
```
|
|
||||||
|
|
||||||
### API 端點
|
|
||||||
|
|
||||||
#### 獲取資料庫狀態
|
|
||||||
|
|
||||||
```http
|
|
||||||
GET /api/admin/database-status
|
|
||||||
```
|
|
||||||
|
|
||||||
回應:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"isEnabled": true,
|
|
||||||
"currentDatabase": "master",
|
|
||||||
"masterHealthy": true,
|
|
||||||
"slaveHealthy": true,
|
|
||||||
"lastHealthCheck": "2024-01-01T12:00:00.000Z",
|
|
||||||
"consecutiveFailures": 0,
|
|
||||||
"uptime": 3600,
|
|
||||||
"timestamp": "2024-01-01T12:00:00.000Z"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 切換資料庫
|
|
||||||
|
|
||||||
```http
|
|
||||||
POST /api/admin/database-status
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"action": "switch",
|
|
||||||
"database": "slave"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 腳本命令
|
|
||||||
|
|
||||||
| 命令 | 說明 |
|
|
||||||
|------|------|
|
|
||||||
| `pnpm run db:init-slave` | 初始化備機資料庫結構 |
|
|
||||||
| `pnpm run db:sync` | 同步主機資料到備機 |
|
|
||||||
| `pnpm run db:health` | 檢查資料庫健康狀態 |
|
|
||||||
| `pnpm run db:monitor` | 執行健康檢查並顯示結果 |
|
|
||||||
|
|
||||||
## 故障處理
|
|
||||||
|
|
||||||
### 自動切換
|
|
||||||
|
|
||||||
當主機資料庫出現以下問題時,系統會自動切換到備機:
|
|
||||||
|
|
||||||
- 連接超時
|
|
||||||
- 連接重置
|
|
||||||
- 協議錯誤
|
|
||||||
- 其他連接相關錯誤
|
|
||||||
|
|
||||||
### 手動切換
|
|
||||||
|
|
||||||
如果需要手動切換資料庫:
|
|
||||||
|
|
||||||
1. 通過監控面板切換
|
|
||||||
2. 通過 API 切換
|
|
||||||
3. 通過程式碼切換
|
|
||||||
|
|
||||||
### 故障恢復
|
|
||||||
|
|
||||||
當主機資料庫恢復後:
|
|
||||||
|
|
||||||
1. 系統會自動檢測到主機恢復
|
|
||||||
2. 可以手動切換回主機
|
|
||||||
3. 建議重新同步資料
|
|
||||||
|
|
||||||
## 監控和日誌
|
|
||||||
|
|
||||||
### 健康檢查
|
|
||||||
|
|
||||||
- 每 30 秒自動檢查一次
|
|
||||||
- 檢查連接狀態和響應時間
|
|
||||||
- 記錄連續失敗次數
|
|
||||||
|
|
||||||
### 日誌記錄
|
|
||||||
|
|
||||||
系統會記錄以下事件:
|
|
||||||
|
|
||||||
- 資料庫切換事件
|
|
||||||
- 連接失敗事件
|
|
||||||
- 健康檢查結果
|
|
||||||
- 同步操作結果
|
|
||||||
|
|
||||||
### 監控指標
|
|
||||||
|
|
||||||
- 當前使用的資料庫
|
|
||||||
- 主機和備機的健康狀態
|
|
||||||
- 最後檢查時間
|
|
||||||
- 連續失敗次數
|
|
||||||
- 系統運行時間
|
|
||||||
|
|
||||||
## 注意事項
|
|
||||||
|
|
||||||
1. **資料一致性**:備機資料可能不是實時同步的,建議定期同步
|
|
||||||
2. **性能影響**:備援機制會增加少量性能開銷
|
|
||||||
3. **網路依賴**:需要確保主機和備機之間的網路連接穩定
|
|
||||||
4. **權限配置**:確保備機資料庫有足夠的權限進行操作
|
|
||||||
|
|
||||||
## 故障排除
|
|
||||||
|
|
||||||
### 常見問題
|
|
||||||
|
|
||||||
1. **備機連接失敗**
|
|
||||||
- 檢查網路連接
|
|
||||||
- 驗證資料庫配置
|
|
||||||
- 確認用戶權限
|
|
||||||
|
|
||||||
2. **同步失敗**
|
|
||||||
- 檢查表結構是否一致
|
|
||||||
- 確認資料庫權限
|
|
||||||
- 查看錯誤日誌
|
|
||||||
|
|
||||||
3. **自動切換不工作**
|
|
||||||
- 確認 `DB_FAILOVER_ENABLED=true`
|
|
||||||
- 檢查健康檢查間隔設定
|
|
||||||
- 查看系統日誌
|
|
||||||
|
|
||||||
### 日誌位置
|
|
||||||
|
|
||||||
- 應用程式日誌:控制台輸出
|
|
||||||
- 資料庫日誌:MySQL 錯誤日誌
|
|
||||||
- 系統日誌:系統日誌文件
|
|
||||||
|
|
||||||
## 最佳實踐
|
|
||||||
|
|
||||||
1. **定期備份**:即使有備援機制,仍建議定期備份資料
|
|
||||||
2. **監控告警**:設定監控告警,及時發現問題
|
|
||||||
3. **測試切換**:定期測試備援切換功能
|
|
||||||
4. **性能監控**:監控資料庫性能指標
|
|
||||||
5. **文檔更新**:保持配置文檔的更新
|
|
||||||
|
|
||||||
## 技術架構
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────┐ ┌─────────────────┐
|
|
||||||
│ 應用程式 │ │ 應用程式 │
|
|
||||||
└─────────┬───────┘ └─────────┬───────┘
|
|
||||||
│ │
|
|
||||||
▼ ▼
|
|
||||||
┌─────────────────┐ ┌─────────────────┐
|
|
||||||
│ 資料庫服務 │ │ 備援服務 │
|
|
||||||
│ (主機) │ │ (備機) │
|
|
||||||
└─────────────────┘ └─────────────────┘
|
|
||||||
│ │
|
|
||||||
▼ ▼
|
|
||||||
┌─────────────────┐ ┌─────────────────┐
|
|
||||||
│ MySQL 主機 │ │ MySQL 備機 │
|
|
||||||
│ mysql.theaken │ │ 122.100.99.161│
|
|
||||||
│ .com:33306 │ │ :43306 │
|
|
||||||
└─────────────────┘ └─────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## 支援
|
|
||||||
|
|
||||||
如有問題,請檢查:
|
|
||||||
|
|
||||||
1. 環境變數配置
|
|
||||||
2. 網路連接狀態
|
|
||||||
3. 資料庫服務狀態
|
|
||||||
4. 系統日誌
|
|
||||||
5. 監控面板狀態
|
|
@@ -1,222 +0,0 @@
|
|||||||
# AI 展示平台資料庫設計
|
|
||||||
|
|
||||||
## 📊 資料庫概述
|
|
||||||
|
|
||||||
本專案使用 **MySQL** 作為主要資料庫,設計了完整的資料表結構來支持 AI 展示平台的所有功能。
|
|
||||||
|
|
||||||
### 🔗 資料庫連接資訊
|
|
||||||
|
|
||||||
- **主機**: `mysql.theaken.com`
|
|
||||||
- **端口**: `33306`
|
|
||||||
- **資料庫名**: `db_AI_Platform`
|
|
||||||
- **用戶名**: `AI_Platform`
|
|
||||||
- **密碼**: `Aa123456`
|
|
||||||
|
|
||||||
## 🏗️ 資料表結構
|
|
||||||
|
|
||||||
### 核心業務表
|
|
||||||
|
|
||||||
#### 1. 用戶管理
|
|
||||||
- **users** - 用戶基本資訊
|
|
||||||
- **user_favorites** - 用戶收藏應用
|
|
||||||
- **user_likes** - 用戶按讚記錄
|
|
||||||
- **user_views** - 用戶瀏覽記錄
|
|
||||||
- **user_ratings** - 用戶評分記錄
|
|
||||||
|
|
||||||
#### 2. 競賽系統
|
|
||||||
- **competitions** - 競賽基本資訊
|
|
||||||
- **competition_rules** - 競賽規則
|
|
||||||
- **competition_award_types** - 競賽獎項類型
|
|
||||||
- **competition_judges** - 競賽評審關聯
|
|
||||||
- **competition_apps** - 競賽參與應用
|
|
||||||
- **competition_teams** - 競賽參與團隊
|
|
||||||
- **competition_proposals** - 競賽參與提案
|
|
||||||
|
|
||||||
#### 3. 評審系統
|
|
||||||
- **judges** - 評審基本資訊
|
|
||||||
- **app_judge_scores** - 應用評分記錄
|
|
||||||
- **proposal_judge_scores** - 提案評分記錄
|
|
||||||
|
|
||||||
#### 4. 團隊管理
|
|
||||||
- **teams** - 團隊基本資訊
|
|
||||||
- **team_members** - 團隊成員關聯
|
|
||||||
|
|
||||||
#### 5. 應用管理
|
|
||||||
- **apps** - AI 應用基本資訊
|
|
||||||
|
|
||||||
#### 6. 提案管理
|
|
||||||
- **proposals** - 提案基本資訊
|
|
||||||
|
|
||||||
#### 7. 獎項系統
|
|
||||||
- **awards** - 獎項記錄
|
|
||||||
|
|
||||||
#### 8. AI 助手
|
|
||||||
- **chat_sessions** - 聊天會話
|
|
||||||
- **chat_messages** - 聊天訊息
|
|
||||||
- **ai_assistant_configs** - AI 助手配置
|
|
||||||
|
|
||||||
#### 9. 系統管理
|
|
||||||
- **system_settings** - 系統設定
|
|
||||||
- **activity_logs** - 活動日誌
|
|
||||||
|
|
||||||
## 📈 統計視圖
|
|
||||||
|
|
||||||
### 1. user_statistics
|
|
||||||
用戶統計視圖,包含:
|
|
||||||
- 基本資訊
|
|
||||||
- 收藏數量
|
|
||||||
- 按讚數量
|
|
||||||
- 瀏覽數量
|
|
||||||
- 平均評分
|
|
||||||
- 團隊參與情況
|
|
||||||
|
|
||||||
### 2. app_statistics
|
|
||||||
應用統計視圖,包含:
|
|
||||||
- 基本資訊
|
|
||||||
- 創作者資訊
|
|
||||||
- 團隊資訊
|
|
||||||
- 用戶互動統計
|
|
||||||
- 評審評分統計
|
|
||||||
|
|
||||||
### 3. competition_statistics
|
|
||||||
競賽統計視圖,包含:
|
|
||||||
- 基本資訊
|
|
||||||
- 評審數量
|
|
||||||
- 參與應用數量
|
|
||||||
- 參與團隊數量
|
|
||||||
- 參與提案數量
|
|
||||||
- 獎項數量
|
|
||||||
|
|
||||||
## ⚙️ 觸發器
|
|
||||||
|
|
||||||
### 1. 自動計算總分
|
|
||||||
- **calculate_app_total_score** - 應用評分總分計算
|
|
||||||
- **calculate_proposal_total_score** - 提案評分總分計算
|
|
||||||
|
|
||||||
## 🚀 快速開始
|
|
||||||
|
|
||||||
### 1. 安裝依賴
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 設置環境變數
|
|
||||||
|
|
||||||
複製 `env.example` 到 `.env.local` 並填入正確的資料庫資訊:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp env.example .env.local
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 執行資料庫遷移
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 創建資料庫結構
|
|
||||||
npm run migrate
|
|
||||||
|
|
||||||
# 重置資料庫(慎用)
|
|
||||||
npm run migrate:reset
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 驗證安裝
|
|
||||||
|
|
||||||
遷移完成後,您應該看到:
|
|
||||||
- 25 個資料表
|
|
||||||
- 3 個統計視圖
|
|
||||||
- 4 個觸發器
|
|
||||||
- 初始系統設定數據
|
|
||||||
|
|
||||||
## 🔧 資料庫服務
|
|
||||||
|
|
||||||
### 使用方式
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { UserService, CompetitionService, AppService } from '@/lib/services/database-service';
|
|
||||||
|
|
||||||
// 創建用戶
|
|
||||||
const user = await UserService.createUser({
|
|
||||||
name: '張三',
|
|
||||||
email: 'zhang@example.com',
|
|
||||||
password_hash: 'hashed_password',
|
|
||||||
department: 'IT部門',
|
|
||||||
role: 'developer',
|
|
||||||
join_date: '2024-01-01',
|
|
||||||
total_likes: 0,
|
|
||||||
total_views: 0,
|
|
||||||
is_active: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// 獲取用戶統計
|
|
||||||
const stats = await UserService.getUserStatistics(user.id);
|
|
||||||
|
|
||||||
// 創建競賽
|
|
||||||
const competition = await CompetitionService.createCompetition({
|
|
||||||
name: '2024年AI創新競賽',
|
|
||||||
year: 2024,
|
|
||||||
month: 3,
|
|
||||||
start_date: '2024-03-01',
|
|
||||||
end_date: '2024-03-31',
|
|
||||||
status: 'upcoming',
|
|
||||||
type: 'individual',
|
|
||||||
is_active: true
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📋 資料表關係圖
|
|
||||||
|
|
||||||
```
|
|
||||||
users (1) ←→ (N) team_members (N) ←→ (1) teams
|
|
||||||
users (1) ←→ (N) apps
|
|
||||||
users (1) ←→ (N) user_favorites
|
|
||||||
users (1) ←→ (N) user_likes
|
|
||||||
users (1) ←→ (N) user_views
|
|
||||||
users (1) ←→ (N) user_ratings
|
|
||||||
|
|
||||||
competitions (1) ←→ (N) competition_judges (N) ←→ (1) judges
|
|
||||||
competitions (1) ←→ (N) competition_apps (N) ←→ (1) apps
|
|
||||||
competitions (1) ←→ (N) competition_teams (N) ←→ (1) teams
|
|
||||||
competitions (1) ←→ (N) competition_proposals (N) ←→ (1) proposals
|
|
||||||
competitions (1) ←→ (N) awards
|
|
||||||
|
|
||||||
judges (1) ←→ (N) app_judge_scores (N) ←→ (1) apps
|
|
||||||
judges (1) ←→ (N) proposal_judge_scores (N) ←→ (1) proposals
|
|
||||||
|
|
||||||
teams (1) ←→ (N) proposals
|
|
||||||
teams (1) ←→ (N) apps
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🛠️ 維護命令
|
|
||||||
|
|
||||||
### 備份資料庫
|
|
||||||
```bash
|
|
||||||
mysqldump -h mysql.theaken.com -P 33306 -u AI_Platform -p db_AI_Platform > backup.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
### 恢復資料庫
|
|
||||||
```bash
|
|
||||||
mysql -h mysql.theaken.com -P 33306 -u AI_Platform -p db_AI_Platform < backup.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
### 檢查資料庫狀態
|
|
||||||
```bash
|
|
||||||
mysql -h mysql.theaken.com -P 33306 -u AI_Platform -p -e "SHOW TABLES;" db_AI_Platform
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔍 常見問題
|
|
||||||
|
|
||||||
### Q: 如何重置資料庫?
|
|
||||||
A: 執行 `npm run migrate:reset` 命令,這會刪除所有表並重新創建。
|
|
||||||
|
|
||||||
### Q: 如何添加新的資料表?
|
|
||||||
A: 在 `database-schema.sql` 中添加新的 CREATE TABLE 語句,然後執行 `npm run migrate`。
|
|
||||||
|
|
||||||
### Q: 如何修改現有表結構?
|
|
||||||
A: 使用 ALTER TABLE 語句,或者創建新的遷移腳本。
|
|
||||||
|
|
||||||
### Q: 資料庫連接失敗怎麼辦?
|
|
||||||
A: 檢查環境變數設置,確保資料庫服務正在運行,並且網路連接正常。
|
|
||||||
|
|
||||||
## 📞 技術支援
|
|
||||||
|
|
||||||
如有任何資料庫相關問題,請聯繫技術團隊或查看專案文檔。
|
|
@@ -1,237 +0,0 @@
|
|||||||
# 資料庫雙寫同步系統說明
|
|
||||||
|
|
||||||
## 🎯 系統概述
|
|
||||||
|
|
||||||
本系統實現了主機和備機資料庫的雙寫同步機制,確保所有新增、刪除、修改的資料都能同時寫入主機和備機資料庫,實現真正的資料同步。
|
|
||||||
|
|
||||||
## ✅ 主要功能
|
|
||||||
|
|
||||||
- ✅ **雙寫同步** - 所有寫入操作同時寫入主機和備機
|
|
||||||
- ✅ **自動故障檢測** - 每30秒檢查資料庫健康狀態
|
|
||||||
- ✅ **自動切換** - 主機故障時自動切換到備機
|
|
||||||
- ✅ **手動切換** - 支援手動切換資料庫
|
|
||||||
- ✅ **資料同步** - 可將主機資料同步到備機
|
|
||||||
- ✅ **監控面板** - 實時監控資料庫狀態
|
|
||||||
- ✅ **健康檢查** - 定期檢查連接狀態
|
|
||||||
|
|
||||||
## 🚀 快速開始
|
|
||||||
|
|
||||||
### 1. 環境變數配置
|
|
||||||
|
|
||||||
在您的 `.env` 文件中添加以下配置:
|
|
||||||
|
|
||||||
```env
|
|
||||||
# ===== 主機資料庫配置 =====
|
|
||||||
DB_HOST=mysql.theaken.com
|
|
||||||
DB_PORT=33306
|
|
||||||
DB_NAME=db_AI_Platform
|
|
||||||
DB_USER=AI_Platform
|
|
||||||
DB_PASSWORD=Aa123456
|
|
||||||
|
|
||||||
# ===== 備機資料庫配置 =====
|
|
||||||
SLAVE_DB_HOST=122.100.99.161
|
|
||||||
SLAVE_DB_PORT=43306
|
|
||||||
SLAVE_DB_NAME=db_AI_Platform
|
|
||||||
SLAVE_DB_USER=A999
|
|
||||||
SLAVE_DB_PASSWORD=1023
|
|
||||||
|
|
||||||
# ===== 資料庫備援配置 =====
|
|
||||||
DB_FAILOVER_ENABLED=true
|
|
||||||
|
|
||||||
# ===== 資料庫雙寫同步配置 =====
|
|
||||||
DB_DUAL_WRITE_ENABLED=true
|
|
||||||
DB_MASTER_PRIORITY=true
|
|
||||||
DB_CONFLICT_RESOLUTION=master
|
|
||||||
DB_HEALTH_CHECK_INTERVAL=30000
|
|
||||||
DB_CONNECTION_TIMEOUT=5000
|
|
||||||
DB_RETRY_ATTEMPTS=3
|
|
||||||
DB_RETRY_DELAY=2000
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 初始化備機資料庫
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 初始化備機資料庫結構
|
|
||||||
pnpm run db:init-slave
|
|
||||||
|
|
||||||
# 同步主機資料到備機
|
|
||||||
pnpm run db:sync
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 測試雙寫功能
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 測試雙寫同步功能
|
|
||||||
pnpm run db:test-dual-write
|
|
||||||
|
|
||||||
# 檢查資料庫健康狀態
|
|
||||||
pnpm run db:health
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📊 工作原理
|
|
||||||
|
|
||||||
### 雙寫流程
|
|
||||||
|
|
||||||
1. **寫入請求** - API 接收到寫入請求
|
|
||||||
2. **雙寫執行** - 同時寫入主機和備機資料庫
|
|
||||||
3. **結果處理** - 根據寫入結果決定回應
|
|
||||||
4. **錯誤處理** - 如果其中一個失敗,記錄錯誤但繼續服務
|
|
||||||
|
|
||||||
### 同步策略
|
|
||||||
|
|
||||||
- **主機優先** - 如果主機和備機都成功,優先返回主機結果
|
|
||||||
- **備機備援** - 如果主機失敗但備機成功,使用備機結果
|
|
||||||
- **錯誤處理** - 如果兩者都失敗,拋出錯誤
|
|
||||||
|
|
||||||
## 🔧 程式碼使用
|
|
||||||
|
|
||||||
### 基本使用
|
|
||||||
|
|
||||||
系統會自動使用雙寫功能,無需修改現有程式碼:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { db } from '@/lib/database';
|
|
||||||
|
|
||||||
// 插入資料 (自動雙寫)
|
|
||||||
await db.insert('INSERT INTO users (name, email) VALUES (?, ?)', ['John', 'john@example.com']);
|
|
||||||
|
|
||||||
// 更新資料 (自動雙寫)
|
|
||||||
await db.update('UPDATE users SET name = ? WHERE id = ?', ['Jane', 'user-id']);
|
|
||||||
|
|
||||||
// 刪除資料 (自動雙寫)
|
|
||||||
await db.delete('DELETE FROM users WHERE id = ?', ['user-id']);
|
|
||||||
|
|
||||||
// 查詢資料 (自動使用備援)
|
|
||||||
const users = await db.query('SELECT * FROM users');
|
|
||||||
```
|
|
||||||
|
|
||||||
### 監控同步狀態
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { dbSync } from '@/lib/database-sync';
|
|
||||||
|
|
||||||
// 獲取同步狀態
|
|
||||||
const status = await dbSync.getSyncStatus();
|
|
||||||
console.log('同步狀態:', status);
|
|
||||||
|
|
||||||
// 手動同步表
|
|
||||||
await dbSync.syncFromMasterToSlave('users');
|
|
||||||
```
|
|
||||||
|
|
||||||
### 獲取備援狀態
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { db } from '@/lib/database';
|
|
||||||
|
|
||||||
// 獲取當前資料庫狀態
|
|
||||||
const status = db.getFailoverStatus();
|
|
||||||
console.log('當前使用資料庫:', status?.currentDatabase);
|
|
||||||
console.log('主機健康狀態:', status?.masterHealthy);
|
|
||||||
console.log('備機健康狀態:', status?.slaveHealthy);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📈 監控和管理
|
|
||||||
|
|
||||||
### 可用命令
|
|
||||||
|
|
||||||
| 命令 | 功能 | 狀態 |
|
|
||||||
|------|------|------|
|
|
||||||
| `pnpm run db:health` | 檢查資料庫健康狀態 | ✅ 可用 |
|
|
||||||
| `pnpm run db:test-dual-write` | 測試雙寫同步功能 | ✅ 可用 |
|
|
||||||
| `pnpm run db:init-slave` | 初始化備機資料庫 | ✅ 可用 |
|
|
||||||
| `pnpm run db:sync` | 同步資料 | ✅ 可用 |
|
|
||||||
| `pnpm run db:monitor` | 監控資料庫狀態 | ✅ 可用 |
|
|
||||||
|
|
||||||
### 管理頁面
|
|
||||||
|
|
||||||
在管理頁面中添加監控組件:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { DatabaseMonitor } from '@/components/admin/database-monitor';
|
|
||||||
|
|
||||||
// 在管理頁面中使用
|
|
||||||
<DatabaseMonitor />
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🚨 故障處理
|
|
||||||
|
|
||||||
### 主機資料庫問題
|
|
||||||
|
|
||||||
**問題**: `Too many connections`
|
|
||||||
**解決方案**:
|
|
||||||
1. 系統已自動切換到備機
|
|
||||||
2. 檢查主機資料庫連接數限制
|
|
||||||
3. 優化連接池配置
|
|
||||||
4. 重啟主機資料庫服務
|
|
||||||
|
|
||||||
### 備機資料庫問題
|
|
||||||
|
|
||||||
**問題**: 備機連接失敗
|
|
||||||
**解決方案**:
|
|
||||||
1. 檢查網路連接
|
|
||||||
2. 驗證備機資料庫配置
|
|
||||||
3. 確認用戶權限
|
|
||||||
4. 檢查備機資料庫服務狀態
|
|
||||||
|
|
||||||
### 雙寫失敗處理
|
|
||||||
|
|
||||||
**問題**: 主機或備機寫入失敗
|
|
||||||
**解決方案**:
|
|
||||||
1. 檢查錯誤日誌
|
|
||||||
2. 驗證資料庫連接狀態
|
|
||||||
3. 檢查資料庫結構一致性
|
|
||||||
4. 重新執行失敗的操作
|
|
||||||
|
|
||||||
## 📋 維護建議
|
|
||||||
|
|
||||||
### 定期維護
|
|
||||||
|
|
||||||
1. **每日檢查**: 執行 `pnpm run db:health`
|
|
||||||
2. **每週測試**: 執行 `pnpm run db:test-dual-write`
|
|
||||||
3. **每月同步**: 執行 `pnpm run db:sync`
|
|
||||||
|
|
||||||
### 監控指標
|
|
||||||
|
|
||||||
- 資料庫連接狀態
|
|
||||||
- 雙寫成功率
|
|
||||||
- 同步延遲時間
|
|
||||||
- 錯誤率統計
|
|
||||||
|
|
||||||
## 🔄 恢復主機
|
|
||||||
|
|
||||||
當主機資料庫恢復後:
|
|
||||||
|
|
||||||
1. 檢查主機狀態: `pnpm run db:health`
|
|
||||||
2. 手動切換回主機: `await db.switchDatabase('master')`
|
|
||||||
3. 重新同步資料: `pnpm run db:sync`
|
|
||||||
|
|
||||||
## 📞 支援
|
|
||||||
|
|
||||||
如有問題,請檢查:
|
|
||||||
|
|
||||||
1. 環境變數配置
|
|
||||||
2. 網路連接狀態
|
|
||||||
3. 資料庫服務狀態
|
|
||||||
4. 系統日誌
|
|
||||||
5. 監控面板狀態
|
|
||||||
|
|
||||||
## 🎉 總結
|
|
||||||
|
|
||||||
您的資料庫雙寫同步系統已經成功設置並運行!系統現在可以:
|
|
||||||
|
|
||||||
- ✅ 自動檢測主機資料庫問題
|
|
||||||
- ✅ 自動切換到備機資料庫
|
|
||||||
- ✅ 實現真正的雙寫同步
|
|
||||||
- ✅ 確保資料一致性
|
|
||||||
- ✅ 提供監控和管理功能
|
|
||||||
- ✅ 確保服務連續性
|
|
||||||
|
|
||||||
即使主機資料庫出現問題,您的應用程式仍然可以正常運行,並且所有資料都會同步到備機!
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@@ -1,56 +0,0 @@
|
|||||||
# 環境變數設定說明
|
|
||||||
|
|
||||||
## DeepSeek API 設定
|
|
||||||
|
|
||||||
本專案使用 DeepSeek API 作為聊天機器人的 AI 服務。請按照以下步驟設定環境變數:
|
|
||||||
|
|
||||||
### 1. 創建環境變數檔案
|
|
||||||
|
|
||||||
在專案根目錄創建 `.env.local` 檔案:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# DeepSeek API Configuration
|
|
||||||
NEXT_PUBLIC_DEEPSEEK_API_KEY=your_deepseek_api_key_here
|
|
||||||
NEXT_PUBLIC_DEEPSEEK_API_URL=https://api.deepseek.com/v1/chat/completions
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 取得 DeepSeek API 金鑰
|
|
||||||
|
|
||||||
1. 前往 [DeepSeek 官網](https://platform.deepseek.com/)
|
|
||||||
2. 註冊或登入帳號
|
|
||||||
3. 在控制台中生成 API 金鑰
|
|
||||||
4. 將金鑰複製到 `.env.local` 檔案中的 `NEXT_PUBLIC_DEEPSEEK_API_KEY`
|
|
||||||
|
|
||||||
### 3. 環境變數說明
|
|
||||||
|
|
||||||
- `NEXT_PUBLIC_DEEPSEEK_API_KEY`: DeepSeek API 金鑰
|
|
||||||
- `NEXT_PUBLIC_DEEPSEEK_API_URL`: DeepSeek API 端點 URL
|
|
||||||
|
|
||||||
### 4. 安全注意事項
|
|
||||||
|
|
||||||
- `.env.local` 檔案已加入 `.gitignore`,不會被提交到版本控制
|
|
||||||
- 請勿將 API 金鑰分享給他人
|
|
||||||
- 在生產環境中,請使用更安全的環境變數管理方式
|
|
||||||
|
|
||||||
### 5. 重新啟動開發伺服器
|
|
||||||
|
|
||||||
設定完成後,請重新啟動開發伺服器:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
# 或
|
|
||||||
pnpm dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. 驗證設定
|
|
||||||
|
|
||||||
聊天機器人應該能夠正常運作,並能夠回答用戶問題。
|
|
||||||
|
|
||||||
## 故障排除
|
|
||||||
|
|
||||||
如果聊天機器人無法運作:
|
|
||||||
|
|
||||||
1. 確認 `.env.local` 檔案存在且格式正確
|
|
||||||
2. 確認 API 金鑰有效且未過期
|
|
||||||
3. 檢查網路連接是否正常
|
|
||||||
4. 查看瀏覽器開發者工具中的錯誤訊息
|
|
@@ -1,195 +0,0 @@
|
|||||||
# 評分機制後端實現文檔
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
|
|
||||||
本文檔描述了AI展示平台評分機制的後端實現,包括資料庫設計、API端點和前端整合。
|
|
||||||
|
|
||||||
## 資料庫設計
|
|
||||||
|
|
||||||
### 評分相關表格
|
|
||||||
|
|
||||||
1. **app_judge_scores** - 應用評分表
|
|
||||||
- 存儲評審對應用的評分記錄
|
|
||||||
- 包含創新性、技術性、實用性、展示效果、影響力等評分項目
|
|
||||||
- 自動計算總分
|
|
||||||
|
|
||||||
2. **proposal_judge_scores** - 提案評分表
|
|
||||||
- 存儲評審對提案的評分記錄
|
|
||||||
- 包含問題識別、解決方案可行性、創新性、影響力、展示效果等評分項目
|
|
||||||
- 自動計算總分
|
|
||||||
|
|
||||||
3. **competition_judges** - 競賽評審關聯表
|
|
||||||
- 管理競賽與評審的關聯關係
|
|
||||||
|
|
||||||
4. **competition_apps** - 競賽應用關聯表
|
|
||||||
- 管理競賽與應用的關聯關係
|
|
||||||
|
|
||||||
5. **competition_proposals** - 競賽提案關聯表
|
|
||||||
- 管理競賽與提案的關聯關係
|
|
||||||
|
|
||||||
## API端點
|
|
||||||
|
|
||||||
### 1. 評分管理 API (`/api/admin/scoring`)
|
|
||||||
|
|
||||||
#### GET - 獲取評分記錄
|
|
||||||
```
|
|
||||||
GET /api/admin/scoring?competitionId={id}&judgeId={id}&status={status}&search={query}
|
|
||||||
```
|
|
||||||
|
|
||||||
**參數:**
|
|
||||||
- `competitionId` (必需): 競賽ID
|
|
||||||
- `judgeId` (可選): 評審ID,用於篩選特定評審的評分
|
|
||||||
- `status` (可選): 狀態篩選 (all/completed/pending)
|
|
||||||
- `search` (可選): 搜尋關鍵字
|
|
||||||
|
|
||||||
**回應:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "評分記錄獲取成功",
|
|
||||||
"data": {
|
|
||||||
"scores": [...],
|
|
||||||
"stats": {...},
|
|
||||||
"total": 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### POST - 提交評分
|
|
||||||
```
|
|
||||||
POST /api/admin/scoring
|
|
||||||
```
|
|
||||||
|
|
||||||
**請求體:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"judgeId": "judge_id",
|
|
||||||
"participantId": "app_id",
|
|
||||||
"participantType": "app",
|
|
||||||
"scores": {
|
|
||||||
"innovation_score": 8,
|
|
||||||
"technical_score": 7,
|
|
||||||
"usability_score": 9,
|
|
||||||
"presentation_score": 8,
|
|
||||||
"impact_score": 7
|
|
||||||
},
|
|
||||||
"comments": "評審意見"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 評分記錄管理 API (`/api/admin/scoring/[id]`)
|
|
||||||
|
|
||||||
#### PUT - 更新評分記錄
|
|
||||||
```
|
|
||||||
PUT /api/admin/scoring/{id}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### DELETE - 刪除評分記錄
|
|
||||||
```
|
|
||||||
DELETE /api/admin/scoring/{id}?type={app|proposal}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 評分統計 API (`/api/admin/scoring/stats`)
|
|
||||||
|
|
||||||
#### GET - 獲取評分統計
|
|
||||||
```
|
|
||||||
GET /api/admin/scoring/stats?competitionId={id}
|
|
||||||
```
|
|
||||||
|
|
||||||
**回應:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "評分統計獲取成功",
|
|
||||||
"data": {
|
|
||||||
"totalScores": 20,
|
|
||||||
"completedScores": 15,
|
|
||||||
"pendingScores": 5,
|
|
||||||
"completionRate": 75,
|
|
||||||
"totalParticipants": 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 後端服務
|
|
||||||
|
|
||||||
### ScoringService 類別
|
|
||||||
|
|
||||||
提供以下主要方法:
|
|
||||||
|
|
||||||
1. **submitAppScore()** - 提交應用評分
|
|
||||||
2. **submitProposalScore()** - 提交提案評分
|
|
||||||
3. **getCompetitionScores()** - 獲取競賽所有評分記錄
|
|
||||||
4. **getCompetitionScoreStats()** - 獲取競賽評分統計
|
|
||||||
5. **getJudgeScores()** - 獲取評審的評分記錄
|
|
||||||
6. **updateAppScore()** - 更新應用評分
|
|
||||||
7. **updateProposalScore()** - 更新提案評分
|
|
||||||
8. **deleteScore()** - 刪除評分記錄
|
|
||||||
|
|
||||||
## 前端整合
|
|
||||||
|
|
||||||
### 組件更新
|
|
||||||
|
|
||||||
1. **ScoringManagement 組件**
|
|
||||||
- 更新 `loadScoringData()` 方法以使用API
|
|
||||||
- 更新 `handleSubmitScore()` 方法以提交到後端
|
|
||||||
- 添加評分統計功能
|
|
||||||
|
|
||||||
2. **CompetitionContext 上下文**
|
|
||||||
- 更新 `submitJudgeScore()` 方法以使用API
|
|
||||||
- 保持向後兼容性
|
|
||||||
|
|
||||||
### 數據流程
|
|
||||||
|
|
||||||
1. 用戶選擇競賽
|
|
||||||
2. 前端調用 `/api/admin/scoring/stats` 獲取統計數據
|
|
||||||
3. 前端調用 `/api/admin/scoring` 獲取評分記錄
|
|
||||||
4. 用戶提交評分時調用 POST `/api/admin/scoring`
|
|
||||||
5. 後端更新資料庫並返回結果
|
|
||||||
6. 前端重新載入數據以顯示最新狀態
|
|
||||||
|
|
||||||
## 評分規則
|
|
||||||
|
|
||||||
### 應用評分項目
|
|
||||||
- 創新性 (innovation_score): 1-10分
|
|
||||||
- 技術性 (technical_score): 1-10分
|
|
||||||
- 實用性 (usability_score): 1-10分
|
|
||||||
- 展示效果 (presentation_score): 1-10分
|
|
||||||
- 影響力 (impact_score): 1-10分
|
|
||||||
|
|
||||||
### 提案評分項目
|
|
||||||
- 問題識別 (problem_identification_score): 1-10分
|
|
||||||
- 解決方案可行性 (solution_feasibility_score): 1-10分
|
|
||||||
- 創新性 (innovation_score): 1-10分
|
|
||||||
- 影響力 (impact_score): 1-10分
|
|
||||||
- 展示效果 (presentation_score): 1-10分
|
|
||||||
|
|
||||||
### 總分計算
|
|
||||||
總分 = (所有評分項目之和) / 評分項目數量
|
|
||||||
|
|
||||||
## 測試
|
|
||||||
|
|
||||||
### 資料庫測試
|
|
||||||
```bash
|
|
||||||
node scripts/test-scoring.js
|
|
||||||
```
|
|
||||||
|
|
||||||
### API測試
|
|
||||||
```bash
|
|
||||||
node scripts/test-scoring-api.js
|
|
||||||
```
|
|
||||||
|
|
||||||
## 部署注意事項
|
|
||||||
|
|
||||||
1. 確保資料庫表格已創建
|
|
||||||
2. 確保觸發器正常工作(自動計算總分)
|
|
||||||
3. 檢查API端點權限設置
|
|
||||||
4. 驗證前端組件與API的整合
|
|
||||||
|
|
||||||
## 未來改進
|
|
||||||
|
|
||||||
1. 添加評分權重配置
|
|
||||||
2. 實現評分審核流程
|
|
||||||
3. 添加評分歷史記錄
|
|
||||||
4. 實現批量評分功能
|
|
||||||
5. 添加評分導出功能
|
|
@@ -1,123 +0,0 @@
|
|||||||
# 評分管理功能
|
|
||||||
|
|
||||||
## 功能概述
|
|
||||||
|
|
||||||
後台評分管理系統提供了完整的評分管理功能,包括:
|
|
||||||
|
|
||||||
- 查看已完成和未完成的評分內容
|
|
||||||
- 手動輸入和編輯評分
|
|
||||||
- 評分進度追蹤
|
|
||||||
- 篩選和搜尋功能
|
|
||||||
|
|
||||||
## 主要功能
|
|
||||||
|
|
||||||
### 1. 競賽選擇
|
|
||||||
- 從下拉選單中選擇要管理的競賽
|
|
||||||
- 顯示競賽基本資訊(名稱、類型、時間等)
|
|
||||||
|
|
||||||
### 2. 評分概覽
|
|
||||||
- **已完成評分**:顯示已完成的評分數量
|
|
||||||
- **待評分**:顯示待評分的數量
|
|
||||||
- **完成度**:顯示評分進度的百分比
|
|
||||||
- **總評分項目**:顯示總評分項目數量
|
|
||||||
- 進度條:視覺化顯示評分進度
|
|
||||||
|
|
||||||
### 3. 評分記錄管理
|
|
||||||
- **評審**:顯示評審姓名和頭像
|
|
||||||
- **參賽者**:顯示參賽者名稱和類型(個人/團隊)
|
|
||||||
- **類型**:標示參賽者類型
|
|
||||||
- **總分**:顯示評分總分
|
|
||||||
- **狀態**:顯示評分狀態(已完成/待評分)
|
|
||||||
- **提交時間**:顯示評分提交時間
|
|
||||||
- **操作**:編輯或新增評分
|
|
||||||
|
|
||||||
### 4. 篩選和搜尋
|
|
||||||
- **狀態篩選**:按評分狀態篩選(全部/已完成/待評分)
|
|
||||||
- **搜尋功能**:搜尋評審或參賽者名稱
|
|
||||||
- **分頁功能**:支援大量數據的分頁顯示
|
|
||||||
|
|
||||||
### 5. 動態評分功能
|
|
||||||
- **評審選擇**:從評審列表中選擇評審
|
|
||||||
- **參賽者選擇**:從參賽者列表中選擇參賽者
|
|
||||||
- **動態評分項目**:根據競賽建立時設定的評比規則動態生成評分項目
|
|
||||||
- **權重計算**:支援不同評分項目的權重設定
|
|
||||||
- **評分驗證**:確保所有評分項目都已評分
|
|
||||||
- **總分計算**:根據權重自動計算總分
|
|
||||||
- **評審意見**:填寫評審意見和建議
|
|
||||||
- **評分提交**:提交或更新評分
|
|
||||||
|
|
||||||
## 使用方式
|
|
||||||
|
|
||||||
### 訪問評分管理
|
|
||||||
1. 進入後台管理系統
|
|
||||||
2. 點擊「評分管理」標籤
|
|
||||||
3. 選擇要管理的競賽
|
|
||||||
|
|
||||||
### 查看評分記錄
|
|
||||||
1. 選擇競賽後,系統會自動載入該競賽的所有評分記錄
|
|
||||||
2. 使用篩選功能查看特定狀態的評分
|
|
||||||
3. 使用搜尋功能快速找到特定評審或參賽者的評分
|
|
||||||
|
|
||||||
### 動態評分輸入
|
|
||||||
1. 點擊「手動輸入評分」按鈕
|
|
||||||
2. 選擇評審和參賽者
|
|
||||||
3. 根據競賽設定的評比項目進行評分
|
|
||||||
4. 為每個評分項目選擇分數(1-10分)
|
|
||||||
5. 系統會根據權重自動計算總分
|
|
||||||
6. 填寫評審意見
|
|
||||||
7. 點擊「提交評分」完成評分
|
|
||||||
|
|
||||||
### 編輯現有評分
|
|
||||||
1. 在評分記錄表格中點擊編輯按鈕
|
|
||||||
2. 修改評審意見
|
|
||||||
3. 點擊「更新評分」保存修改
|
|
||||||
|
|
||||||
## 技術實現
|
|
||||||
|
|
||||||
### 組件結構
|
|
||||||
- `ScoringManagement`:主要評分管理組件
|
|
||||||
- 整合到現有的 `CompetitionManagement` 組件中
|
|
||||||
|
|
||||||
### 動態評分系統
|
|
||||||
- **評比規則讀取**:從競賽的 `rules` 屬性讀取評比項目
|
|
||||||
- **動態評分項目生成**:根據競賽規則動態生成評分表單
|
|
||||||
- **權重計算**:支援不同評分項目的權重設定
|
|
||||||
- **評分驗證**:確保所有評分項目都已評分
|
|
||||||
- **總分計算**:根據權重自動計算總分
|
|
||||||
|
|
||||||
### 數據流
|
|
||||||
1. 從 `useCompetition` 上下文獲取競賽和評分數據
|
|
||||||
2. 根據選擇的競賽載入相關的評審和參賽者
|
|
||||||
3. 讀取競賽的評比規則並動態生成評分項目
|
|
||||||
4. 生成評分記錄列表
|
|
||||||
5. 支援篩選、搜尋和分頁功能
|
|
||||||
|
|
||||||
### 狀態管理
|
|
||||||
- 使用 React hooks 管理組件狀態
|
|
||||||
- 整合現有的競賽上下文
|
|
||||||
- 支援即時數據更新
|
|
||||||
- 動態評分項目的狀態管理
|
|
||||||
|
|
||||||
## 文件結構
|
|
||||||
|
|
||||||
```
|
|
||||||
components/admin/
|
|
||||||
├── scoring-management.tsx # 評分管理組件
|
|
||||||
└── competition-management.tsx # 競賽管理組件(已整合)
|
|
||||||
|
|
||||||
app/admin/
|
|
||||||
└── scoring/
|
|
||||||
└── page.tsx # 評分管理頁面
|
|
||||||
```
|
|
||||||
|
|
||||||
## 注意事項
|
|
||||||
|
|
||||||
1. 評分記錄會根據競賽的評審和參賽者自動生成
|
|
||||||
2. 已完成的評分可以編輯,未完成的評分可以新增
|
|
||||||
3. 評分提交後會即時更新列表
|
|
||||||
4. 支援個人賽和團隊賽的評分管理
|
|
||||||
5. 評分數據與現有的競賽管理系統完全整合
|
|
||||||
6. 評分項目會根據競賽建立時設定的評比規則動態生成
|
|
||||||
7. 如果競賽沒有設定評比規則,會使用預設的評分項目
|
|
||||||
8. 總分會根據各評分項目的權重自動計算
|
|
||||||
9. 系統會驗證所有評分項目都已評分才能提交
|
|
@@ -1,133 +0,0 @@
|
|||||||
# 評分表單完全修復報告
|
|
||||||
|
|
||||||
## 問題診斷結果
|
|
||||||
|
|
||||||
根據用戶提供的Console日誌分析:
|
|
||||||
|
|
||||||
```
|
|
||||||
🏆 當前競賽API回應: {success: true, message: '當前競賽獲取成功', data: {…}}
|
|
||||||
✅ 當前競賽載入成功: AA
|
|
||||||
👨⚖️ 評審列表渲染 - judges (context): [] ← 問題所在
|
|
||||||
```
|
|
||||||
|
|
||||||
**根本原因**:`useCompetition` hook 中的 `judges` 數據沒有被載入,導致評審選項為空。
|
|
||||||
|
|
||||||
## 完整修復方案
|
|
||||||
|
|
||||||
### 1. 修復評審數據載入 (`contexts/competition-context.tsx`)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 載入評審數據
|
|
||||||
console.log('👨⚖️ 開始載入評審數據...')
|
|
||||||
const judgesResponse = await fetch('/api/admin/judges')
|
|
||||||
const judgesData = await judgesResponse.json()
|
|
||||||
console.log('評審API回應:', judgesData)
|
|
||||||
|
|
||||||
if (judgesData.success && judgesData.data) {
|
|
||||||
setJudges(judgesData.data)
|
|
||||||
console.log('✅ 評審數據載入成功:', judgesData.data.length, '個評審')
|
|
||||||
} else {
|
|
||||||
console.error('❌ 評審數據載入失敗:', judgesData.message)
|
|
||||||
setJudges([])
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 修復競賽數據載入 (`components/admin/scoring-management.tsx`)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
useEffect(() => {
|
|
||||||
if (selectedCompetition) {
|
|
||||||
loadScoringData()
|
|
||||||
loadCompetitionData() // 添加這行
|
|
||||||
}
|
|
||||||
}, [selectedCompetition])
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 添加自動競賽選擇
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 自動選擇第一個競賽(如果沒有選中的話)
|
|
||||||
if (!selectedCompetition) {
|
|
||||||
console.log('🎯 自動選擇第一個競賽:', competitions[0].name)
|
|
||||||
setSelectedCompetition(competitions[0])
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 測試結果
|
|
||||||
|
|
||||||
### API測試結果 ✅
|
|
||||||
```
|
|
||||||
📋 競賽API成功: 1 個競賽
|
|
||||||
👨⚖️ 評審API成功: 1 個評審
|
|
||||||
🏆 競賽評審API成功: 1 個評審
|
|
||||||
👥 競賽團隊API成功: 1 個團隊
|
|
||||||
📱 競賽應用API成功: 0 個應用
|
|
||||||
```
|
|
||||||
|
|
||||||
### 數據可用性 ✅
|
|
||||||
- **競賽**: AA (team)
|
|
||||||
- **評審**: 1 個 (aa - ITBU)
|
|
||||||
- **團隊**: 1 個 (aaa - 隊長: Wu Petty)
|
|
||||||
- **應用**: 0 個 (正常,因為這是團隊競賽)
|
|
||||||
|
|
||||||
## 預期Console日誌
|
|
||||||
|
|
||||||
修復後,用戶應該在Console中看到:
|
|
||||||
|
|
||||||
```
|
|
||||||
🔄 開始載入競賽數據...
|
|
||||||
📋 競賽API回應: {success: true, data: [...]}
|
|
||||||
✅ 競賽數據載入成功: 1 個競賽
|
|
||||||
🏆 當前競賽API回應: {success: true, data: {...}}
|
|
||||||
✅ 當前競賽載入成功: AA
|
|
||||||
👨⚖️ 開始載入評審數據...
|
|
||||||
評審API回應: {success: true, data: [...]}
|
|
||||||
✅ 評審數據載入成功: 1 個評審
|
|
||||||
✅ 競賽數據已載入,關閉初始載入狀態
|
|
||||||
🎯 自動選擇第一個競賽: AA
|
|
||||||
🔍 開始載入競賽數據,競賽ID: be47d842-91f1-11f0-8595-bd825523ae01
|
|
||||||
📋 載入競賽評審...
|
|
||||||
✅ 評審數據載入成功: 1 個評審
|
|
||||||
📱 載入競賽參賽者...
|
|
||||||
✅ 團隊數據載入成功: 1 個團隊
|
|
||||||
✅ 參賽者數據載入完成: 1 個參賽者
|
|
||||||
```
|
|
||||||
|
|
||||||
## 功能驗證
|
|
||||||
|
|
||||||
### 1. 評審選擇 ✅
|
|
||||||
- 下拉選單應該顯示 "aa (aa) - ITBU"
|
|
||||||
- 可以正常選擇評審
|
|
||||||
|
|
||||||
### 2. 參賽者選擇 ✅
|
|
||||||
- 下拉選單應該顯示 "aaa (團隊) - Wu Petty"
|
|
||||||
- 可以正常選擇參賽者
|
|
||||||
|
|
||||||
### 3. 評分提交 ✅
|
|
||||||
- 選擇評審和參賽者後可以填寫評分
|
|
||||||
- 評分數據會正確提交到後端
|
|
||||||
|
|
||||||
## 技術特點
|
|
||||||
|
|
||||||
- **完整的數據載入鏈**:競賽 → 評審 → 參賽者
|
|
||||||
- **自動競賽選擇**:無需手動選擇競賽
|
|
||||||
- **詳細的調試日誌**:便於問題排查
|
|
||||||
- **錯誤恢復機制**:防止因API失敗導致的界面崩潰
|
|
||||||
- **用戶友好體驗**:加載狀態指示和自動選擇
|
|
||||||
|
|
||||||
## 使用方式
|
|
||||||
|
|
||||||
1. **刷新頁面**:讓修復生效
|
|
||||||
2. **查看Console**:確認看到完整的載入日誌
|
|
||||||
3. **測試功能**:選擇評審和參賽者進行評分
|
|
||||||
4. **調試頁面**:訪問 `http://localhost:3000/debug-scoring` 查看詳細信息
|
|
||||||
|
|
||||||
## 修復完成 ✅
|
|
||||||
|
|
||||||
現在評分表單應該完全正常工作:
|
|
||||||
- ✅ 評審選項可以選擇
|
|
||||||
- ✅ 參賽者選項可以選擇
|
|
||||||
- ✅ Console顯示詳細的調試信息
|
|
||||||
- ✅ 評分功能完全可用
|
|
||||||
|
|
||||||
所有問題已解決!
|
|
@@ -1,147 +0,0 @@
|
|||||||
# 評分表單調試修復報告
|
|
||||||
|
|
||||||
## 問題分析
|
|
||||||
|
|
||||||
用戶反映手動評審評分表單無法選擇評審和團隊,經過分析發現以下問題:
|
|
||||||
|
|
||||||
### 1. 數據載入問題
|
|
||||||
- 前端組件嘗試從API載入數據,但API回應格式可能不正確
|
|
||||||
- 缺少適當的錯誤處理和加載狀態指示
|
|
||||||
|
|
||||||
### 2. 數據格式不匹配
|
|
||||||
- API回應的數據結構與前端期望的格式不一致
|
|
||||||
- 缺少對空數據的處理
|
|
||||||
|
|
||||||
## 修復內容
|
|
||||||
|
|
||||||
### 1. 增強錯誤處理 (`components/admin/scoring-management.tsx`)
|
|
||||||
|
|
||||||
#### 添加調試日誌
|
|
||||||
```typescript
|
|
||||||
console.log('🔍 開始載入競賽數據,競賽ID:', selectedCompetition.id)
|
|
||||||
console.log('評審API回應:', judgesData)
|
|
||||||
console.log('應用API回應:', appsData)
|
|
||||||
console.log('團隊API回應:', teamsData)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 改善數據驗證
|
|
||||||
```typescript
|
|
||||||
if (judgesData.success && judgesData.data && judgesData.data.judges) {
|
|
||||||
setCompetitionJudges(judgesData.data.judges)
|
|
||||||
} else {
|
|
||||||
console.error('❌ 評審數據載入失敗:', judgesData.message || 'API回應格式錯誤')
|
|
||||||
setCompetitionJudges([])
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 添加加載狀態指示
|
|
||||||
|
|
||||||
#### 新增狀態管理
|
|
||||||
```typescript
|
|
||||||
const [isLoadingData, setIsLoadingData] = useState(false)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 加載狀態UI
|
|
||||||
```typescript
|
|
||||||
{isLoadingData ? (
|
|
||||||
<SelectItem value="" disabled>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Loader2 className="w-4 h-4 animate-spin" />
|
|
||||||
<span>載入評審中...</span>
|
|
||||||
</div>
|
|
||||||
</SelectItem>
|
|
||||||
) : competitionJudges.length > 0 ? (
|
|
||||||
// 顯示評審選項
|
|
||||||
) : (
|
|
||||||
<SelectItem value="" disabled>
|
|
||||||
<span className="text-gray-500">暫無評審數據</span>
|
|
||||||
</SelectItem>
|
|
||||||
)}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 改善數據映射
|
|
||||||
|
|
||||||
#### 修正API數據結構
|
|
||||||
```typescript
|
|
||||||
// 評審數據
|
|
||||||
if (judgesData.success && judgesData.data && judgesData.data.judges) {
|
|
||||||
setCompetitionJudges(judgesData.data.judges)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 應用數據
|
|
||||||
if (appsData.success && appsData.data && appsData.data.apps) {
|
|
||||||
participants.push(...appsData.data.apps.map((app: any) => ({
|
|
||||||
id: app.id,
|
|
||||||
name: app.name,
|
|
||||||
type: 'individual',
|
|
||||||
creator: app.creator
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 團隊數據
|
|
||||||
if (teamsData.success && teamsData.data && teamsData.data.teams) {
|
|
||||||
participants.push(...teamsData.data.teams.map((team: any) => ({
|
|
||||||
id: team.id,
|
|
||||||
name: team.name,
|
|
||||||
type: 'team',
|
|
||||||
creator: team.members && team.members.find((m: any) => m.role === '隊長')?.name || '未知隊長'
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 創建測試工具
|
|
||||||
|
|
||||||
#### 資料庫測試腳本 (`scripts/test-data-loading.js`)
|
|
||||||
- 直接測試資料庫連接
|
|
||||||
- 驗證競賽、評審、應用、團隊數據
|
|
||||||
- 檢查數據關聯性
|
|
||||||
|
|
||||||
#### API測試頁面 (`app/test-api/page.tsx`)
|
|
||||||
- 提供Web界面測試API端點
|
|
||||||
- 實時查看API回應
|
|
||||||
- 方便調試數據格式問題
|
|
||||||
|
|
||||||
## 測試結果
|
|
||||||
|
|
||||||
### 資料庫測試結果
|
|
||||||
```
|
|
||||||
🏆 競賽: AA (ID: be47d842-91f1-11f0-8595-bd825523ae01)
|
|
||||||
👨⚖️ 評審: 1 個 (aa - ITBU)
|
|
||||||
📱 應用: 0 個
|
|
||||||
👥 團隊: 1 個 (aaa - 隊長: Wu Petty)
|
|
||||||
📊 評分記錄: 0 個
|
|
||||||
```
|
|
||||||
|
|
||||||
### 發現的問題
|
|
||||||
1. **競賽有評審和團隊數據** ✅
|
|
||||||
2. **沒有應用數據** ⚠️ (這可能是正常的,如果競賽只允許團隊參賽)
|
|
||||||
3. **API端點存在且正常** ✅
|
|
||||||
|
|
||||||
## 使用方式
|
|
||||||
|
|
||||||
### 1. 測試API端點
|
|
||||||
訪問 `http://localhost:3000/test-api` 來測試API回應
|
|
||||||
|
|
||||||
### 2. 查看調試日誌
|
|
||||||
打開瀏覽器開發者工具的Console,查看詳細的載入日誌
|
|
||||||
|
|
||||||
### 3. 檢查數據載入
|
|
||||||
- 選擇競賽後,查看Console中的載入日誌
|
|
||||||
- 確認API回應格式正確
|
|
||||||
- 檢查是否有錯誤訊息
|
|
||||||
|
|
||||||
## 下一步建議
|
|
||||||
|
|
||||||
1. **檢查競賽設置**:確認競賽是否正確配置了評審和參賽者
|
|
||||||
2. **驗證API回應**:使用測試頁面檢查API是否返回正確數據
|
|
||||||
3. **檢查網路請求**:在瀏覽器Network標籤中查看API請求狀態
|
|
||||||
4. **添加更多調試信息**:如果問題持續,可以添加更詳細的日誌
|
|
||||||
|
|
||||||
## 技術特點
|
|
||||||
|
|
||||||
- **完整的錯誤處理**:防止因API失敗導致的界面崩潰
|
|
||||||
- **用戶友好的加載狀態**:清楚顯示數據載入進度
|
|
||||||
- **詳細的調試信息**:便於問題排查和修復
|
|
||||||
- **測試工具**:提供多種方式驗證系統狀態
|
|
||||||
|
|
||||||
修復完成後,評分表單應該能夠正確載入和顯示評審及參賽者選項。
|
|
@@ -1,177 +0,0 @@
|
|||||||
# 評分表單最終修復報告
|
|
||||||
|
|
||||||
## 問題診斷
|
|
||||||
|
|
||||||
用戶反映評分表單無法選擇評審和團隊,Console也沒有資訊輸出。經過深入診斷發現:
|
|
||||||
|
|
||||||
### 根本原因
|
|
||||||
1. **API端點正常** ✅ - 所有API端點都正常工作
|
|
||||||
2. **資料庫連接正常** ✅ - 資料庫連接和查詢都正常
|
|
||||||
3. **前端數據載入問題** ❌ - 前端組件沒有正確等待數據載入完成
|
|
||||||
|
|
||||||
### 具體問題
|
|
||||||
- `useCompetition` hook 中的數據載入是異步的
|
|
||||||
- `ScoringManagement` 組件在數據載入完成前就嘗試渲染
|
|
||||||
- 缺少適當的加載狀態指示
|
|
||||||
- 調試日誌沒有正確輸出
|
|
||||||
|
|
||||||
## 修復內容
|
|
||||||
|
|
||||||
### 1. 增強調試日誌 (`contexts/competition-context.tsx`)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 載入所有競賽和當前競賽
|
|
||||||
useEffect(() => {
|
|
||||||
const loadCompetitions = async () => {
|
|
||||||
try {
|
|
||||||
console.log('🔄 開始載入競賽數據...')
|
|
||||||
|
|
||||||
// 載入所有競賽
|
|
||||||
const competitionsResponse = await fetch('/api/competitions')
|
|
||||||
const competitionsData = await competitionsResponse.json()
|
|
||||||
console.log('📋 競賽API回應:', competitionsData)
|
|
||||||
|
|
||||||
if (competitionsData.success && competitionsData.data) {
|
|
||||||
setCompetitions(competitionsData.data)
|
|
||||||
console.log('✅ 競賽數據載入成功:', competitionsData.data.length, '個競賽')
|
|
||||||
} else {
|
|
||||||
console.error('❌ 競賽數據載入失敗:', competitionsData.message)
|
|
||||||
setCompetitions([])
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ 載入競賽數據失敗:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadCompetitions()
|
|
||||||
}, [])
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 添加加載狀態管理 (`components/admin/scoring-management.tsx`)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 新增狀態
|
|
||||||
const [isInitialLoading, setIsInitialLoading] = useState(true)
|
|
||||||
|
|
||||||
// 檢查初始載入狀態
|
|
||||||
useEffect(() => {
|
|
||||||
if (competitions && competitions.length > 0) {
|
|
||||||
console.log('✅ 競賽數據已載入,關閉初始載入狀態')
|
|
||||||
setIsInitialLoading(false)
|
|
||||||
}
|
|
||||||
}, [competitions])
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 添加加載指示器
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 顯示初始載入狀態
|
|
||||||
if (isInitialLoading) {
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<Card>
|
|
||||||
<CardContent className="flex items-center justify-center py-12">
|
|
||||||
<div className="text-center space-y-4">
|
|
||||||
<Loader2 className="w-8 h-8 animate-spin mx-auto" />
|
|
||||||
<p className="text-lg font-medium">載入競賽數據中...</p>
|
|
||||||
<p className="text-sm text-gray-500">請稍候,正在從服務器獲取數據</p>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 改善錯誤處理
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 改善數據驗證
|
|
||||||
if (judgesData.success && judgesData.data && judgesData.data.judges) {
|
|
||||||
setCompetitionJudges(judgesData.data.judges)
|
|
||||||
console.log('✅ 評審數據載入成功:', judgesData.data.judges.length, '個評審')
|
|
||||||
} else {
|
|
||||||
console.error('❌ 評審數據載入失敗:', judgesData.message || 'API回應格式錯誤')
|
|
||||||
setCompetitionJudges([])
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 創建調試工具
|
|
||||||
|
|
||||||
#### 調試頁面 (`app/debug-scoring/page.tsx`)
|
|
||||||
- 提供實時調試界面
|
|
||||||
- 顯示詳細的載入日誌
|
|
||||||
- 測試API端點回應
|
|
||||||
|
|
||||||
#### 資料庫測試腳本 (`scripts/test-db-connection.js`)
|
|
||||||
- 直接測試資料庫連接
|
|
||||||
- 驗證數據存在性
|
|
||||||
|
|
||||||
#### API測試腳本 (`scripts/test-api-simple.js`)
|
|
||||||
- 測試API端點功能
|
|
||||||
- 驗證數據格式
|
|
||||||
|
|
||||||
## 測試結果
|
|
||||||
|
|
||||||
### API測試結果
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "競賽列表獲取成功",
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"id": "be47d842-91f1-11f0-8595-bd825523ae01",
|
|
||||||
"name": "AA",
|
|
||||||
"type": "team",
|
|
||||||
"year": 2025,
|
|
||||||
"month": 9
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 資料庫測試結果
|
|
||||||
```
|
|
||||||
🏆 競賽: 1 個 (AA - team)
|
|
||||||
👨⚖️ 評審: 1 個 (aa - ITBU)
|
|
||||||
📱 應用: 1 個 (陳管理)
|
|
||||||
👥 團隊: 1 個 (aaa - 隊長: Wu Petty)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 使用方式
|
|
||||||
|
|
||||||
### 1. 查看調試日誌
|
|
||||||
打開瀏覽器開發者工具的Console,查看詳細的載入日誌:
|
|
||||||
- `🔄 開始載入競賽數據...`
|
|
||||||
- `📋 競賽API回應: {...}`
|
|
||||||
- `✅ 競賽數據載入成功: 1 個競賽`
|
|
||||||
|
|
||||||
### 2. 使用調試頁面
|
|
||||||
訪問 `http://localhost:3000/debug-scoring` 來:
|
|
||||||
- 實時查看數據載入過程
|
|
||||||
- 測試API端點回應
|
|
||||||
- 查看詳細的調試日誌
|
|
||||||
|
|
||||||
### 3. 檢查加載狀態
|
|
||||||
- 初始載入時會顯示加載指示器
|
|
||||||
- 數據載入完成後才會顯示評分表單
|
|
||||||
- 選擇競賽後會載入對應的評審和參賽者
|
|
||||||
|
|
||||||
## 技術特點
|
|
||||||
|
|
||||||
- **完整的異步處理**:正確處理數據載入的異步特性
|
|
||||||
- **用戶友好的加載狀態**:清楚顯示載入進度
|
|
||||||
- **詳細的調試信息**:便於問題排查和修復
|
|
||||||
- **錯誤恢復機制**:防止因API失敗導致的界面崩潰
|
|
||||||
- **測試工具**:提供多種方式驗證系統狀態
|
|
||||||
|
|
||||||
## 預期結果
|
|
||||||
|
|
||||||
修復完成後,評分表單應該:
|
|
||||||
1. ✅ 正確載入競賽列表
|
|
||||||
2. ✅ 顯示加載狀態指示器
|
|
||||||
3. ✅ 在Console中輸出詳細的調試日誌
|
|
||||||
4. ✅ 選擇競賽後載入評審和參賽者數據
|
|
||||||
5. ✅ 提供可用的評審和參賽者選項
|
|
||||||
|
|
||||||
現在評分表單應該能夠正常工作,並且在Console中顯示詳細的調試信息!
|
|
@@ -1,118 +0,0 @@
|
|||||||
# 評分表單修復報告
|
|
||||||
|
|
||||||
## 問題描述
|
|
||||||
手動評審評分表單無法選擇評審和參賽者,無法進行評分操作。
|
|
||||||
|
|
||||||
## 根本原因
|
|
||||||
1. 前端組件使用空的mock數據而非從後端API獲取真實數據
|
|
||||||
2. 評審和參賽者選項沒有與資料庫整合
|
|
||||||
3. 缺少團隊評分支持
|
|
||||||
|
|
||||||
## 修復內容
|
|
||||||
|
|
||||||
### 1. 前端組件修復 (`components/admin/scoring-management.tsx`)
|
|
||||||
|
|
||||||
#### 新增狀態管理
|
|
||||||
```typescript
|
|
||||||
// 新增狀態:從後端獲取的評審和參賽者數據
|
|
||||||
const [competitionJudges, setCompetitionJudges] = useState<any[]>([])
|
|
||||||
const [competitionParticipants, setCompetitionParticipants] = useState<any[]>([])
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 新增數據載入函數
|
|
||||||
```typescript
|
|
||||||
const loadCompetitionData = async () => {
|
|
||||||
// 載入競賽評審
|
|
||||||
const judgesResponse = await fetch(`/api/competitions/${selectedCompetition.id}/judges`)
|
|
||||||
|
|
||||||
// 載入競賽參賽者(應用和團隊)
|
|
||||||
const [appsResponse, teamsResponse] = await Promise.all([
|
|
||||||
fetch(`/api/competitions/${selectedCompetition.id}/apps`),
|
|
||||||
fetch(`/api/competitions/${selectedCompetition.id}/teams`)
|
|
||||||
])
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 更新評審選擇器
|
|
||||||
- 從 `judges` 改為 `competitionJudges`
|
|
||||||
- 顯示評審的職稱和部門信息
|
|
||||||
|
|
||||||
#### 更新參賽者選擇器
|
|
||||||
- 從mock數據改為 `competitionParticipants`
|
|
||||||
- 支持個人和團隊兩種參賽者類型
|
|
||||||
- 顯示創作者/隊長信息
|
|
||||||
|
|
||||||
### 2. 後端API修復
|
|
||||||
|
|
||||||
#### 新增團隊評分支持 (`lib/services/database-service.ts`)
|
|
||||||
```typescript
|
|
||||||
// 提交團隊評分(使用應用評分表,但標記為團隊類型)
|
|
||||||
static async submitTeamScore(scoreData: Omit<AppJudgeScore, 'id' | 'submitted_at'> & { teamId: string }): Promise<AppJudgeScore>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 更新API端點 (`app/api/admin/scoring/route.ts`)
|
|
||||||
- 支持 `team` 參賽者類型
|
|
||||||
- 根據參賽者類型選擇適當的評分方法
|
|
||||||
|
|
||||||
### 3. 評分提交邏輯修復
|
|
||||||
|
|
||||||
#### 動態參賽者類型判斷
|
|
||||||
```typescript
|
|
||||||
const participantType = selectedParticipant?.type === 'individual' ? 'app' :
|
|
||||||
selectedParticipant?.type === 'team' ? 'team' : 'proposal'
|
|
||||||
```
|
|
||||||
|
|
||||||
## 修復後的功能
|
|
||||||
|
|
||||||
### ✅ 評審選擇
|
|
||||||
- 從資料庫載入競賽的評審列表
|
|
||||||
- 顯示評審姓名、職稱、部門
|
|
||||||
- 支持選擇評審進行評分
|
|
||||||
|
|
||||||
### ✅ 參賽者選擇
|
|
||||||
- 從資料庫載入競賽的應用和團隊
|
|
||||||
- 支持個人賽和團隊賽參賽者
|
|
||||||
- 顯示參賽者名稱和創作者信息
|
|
||||||
|
|
||||||
### ✅ 評分提交
|
|
||||||
- 支持應用評分 (participantType: 'app')
|
|
||||||
- 支持提案評分 (participantType: 'proposal')
|
|
||||||
- 支持團隊評分 (participantType: 'team')
|
|
||||||
- 自動計算總分
|
|
||||||
- 保存評審意見
|
|
||||||
|
|
||||||
### ✅ 數據整合
|
|
||||||
- 完全與後端資料庫整合
|
|
||||||
- 實時載入競賽相關數據
|
|
||||||
- 支持評分記錄的CRUD操作
|
|
||||||
|
|
||||||
## 測試驗證
|
|
||||||
|
|
||||||
### 資料庫測試
|
|
||||||
```bash
|
|
||||||
node scripts/test-scoring.js
|
|
||||||
```
|
|
||||||
|
|
||||||
### API功能測試
|
|
||||||
```bash
|
|
||||||
node scripts/test-scoring-form.js
|
|
||||||
```
|
|
||||||
|
|
||||||
## 使用方式
|
|
||||||
|
|
||||||
1. 管理員選擇競賽
|
|
||||||
2. 系統自動載入該競賽的評審和參賽者
|
|
||||||
3. 點擊「手動輸入評分」
|
|
||||||
4. 選擇評審和參賽者
|
|
||||||
5. 填寫評分項目和意見
|
|
||||||
6. 提交評分到資料庫
|
|
||||||
|
|
||||||
## 技術特點
|
|
||||||
|
|
||||||
- **完全整合**:前端與後端資料庫完全整合
|
|
||||||
- **類型安全**:支持多種參賽者類型
|
|
||||||
- **實時數據**:動態載入競賽相關數據
|
|
||||||
- **用戶友好**:清晰的界面和錯誤提示
|
|
||||||
- **可擴展**:易於添加新的評分類型
|
|
||||||
|
|
||||||
修復完成後,手動評審評分功能已完全可用,支持選擇評審和參賽者進行評分操作。
|
|
@@ -15,6 +15,7 @@ import {
|
|||||||
User,
|
User,
|
||||||
Loader2
|
Loader2
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
|
import { generateSystemPrompt } from "@/lib/ai-knowledge-base"
|
||||||
|
|
||||||
interface Message {
|
interface Message {
|
||||||
id: string
|
id: string
|
||||||
@@ -24,38 +25,10 @@ interface Message {
|
|||||||
quickQuestions?: string[]
|
quickQuestions?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEEPSEEK_API_KEY = process.env.NEXT_PUBLIC_DEEPSEEK_API_KEY || "sk-3640dcff23fe4a069a64f536ac538d75"
|
const DEEPSEEK_API_KEY = process.env.NEXT_PUBLIC_DEEPSEEK_API_KEY || "sk-30cac9533e5b451fa1e277fe34a7f64b"
|
||||||
const DEEPSEEK_API_URL = process.env.NEXT_PUBLIC_DEEPSEEK_API_URL || "https://api.deepseek.com/v1/chat/completions"
|
const DEEPSEEK_API_URL = process.env.NEXT_PUBLIC_DEEPSEEK_API_URL || "https://api.deepseek.com/v1/chat/completions"
|
||||||
|
|
||||||
const systemPrompt = `你是一個競賽管理系統的AI助手,專門幫助用戶了解如何使用這個系統。
|
const systemPrompt = generateSystemPrompt()
|
||||||
|
|
||||||
系統功能包括:
|
|
||||||
|
|
||||||
後台管理功能:
|
|
||||||
1. 競賽管理 - 創建、編輯、刪除競賽
|
|
||||||
2. 評審管理 - 管理評審團成員
|
|
||||||
3. 評分系統 - 手動輸入評分或讓評審自行評分
|
|
||||||
4. 團隊管理 - 管理參賽團隊
|
|
||||||
5. 獎項管理 - 設定各種獎項
|
|
||||||
6. 評審連結 - 提供評審登入連結
|
|
||||||
|
|
||||||
前台功能:
|
|
||||||
1. 競賽瀏覽 - 查看所有競賽資訊和詳細內容
|
|
||||||
2. 團隊註冊 - 如何註冊參賽團隊和提交作品
|
|
||||||
3. 作品展示 - 瀏覽參賽作品和投票功能
|
|
||||||
4. 排行榜 - 查看人氣排行榜和得獎名單
|
|
||||||
5. 個人中心 - 管理個人資料和參賽記錄
|
|
||||||
6. 收藏功能 - 如何收藏喜歡的作品
|
|
||||||
7. 評論系統 - 如何對作品進行評論和互動
|
|
||||||
8. 搜尋功能 - 如何搜尋特定競賽或作品
|
|
||||||
9. 通知系統 - 查看競賽更新和個人通知
|
|
||||||
10. 幫助中心 - 常見問題和使用指南
|
|
||||||
|
|
||||||
請用友善、專業的語氣回答用戶問題,並提供具體的操作步驟。回答要簡潔明瞭,避免過長的文字。
|
|
||||||
|
|
||||||
重要:請不要使用任何Markdown格式,只使用純文字回答。不要使用**、*、#、-等符號。
|
|
||||||
|
|
||||||
回答時請使用繁體中文。`
|
|
||||||
|
|
||||||
export function ChatBot() {
|
export function ChatBot() {
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
@@ -106,6 +79,15 @@ export function ChatBot() {
|
|||||||
|
|
||||||
const callDeepSeekAPI = async (userMessage: string): Promise<string> => {
|
const callDeepSeekAPI = async (userMessage: string): Promise<string> => {
|
||||||
try {
|
try {
|
||||||
|
// 構建對話歷史,只保留最近的幾條對話
|
||||||
|
const recentMessages = messages
|
||||||
|
.filter(msg => msg.sender === "user")
|
||||||
|
.slice(-5) // 只保留最近5條用戶消息
|
||||||
|
.map(msg => ({
|
||||||
|
role: "user" as const,
|
||||||
|
content: msg.text
|
||||||
|
}))
|
||||||
|
|
||||||
const response = await fetch(DEEPSEEK_API_URL, {
|
const response = await fetch(DEEPSEEK_API_URL, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -119,31 +101,47 @@ export function ChatBot() {
|
|||||||
role: "system",
|
role: "system",
|
||||||
content: systemPrompt
|
content: systemPrompt
|
||||||
},
|
},
|
||||||
...messages
|
...recentMessages,
|
||||||
.filter(msg => msg.sender === "user")
|
|
||||||
.map(msg => ({
|
|
||||||
role: "user" as const,
|
|
||||||
content: msg.text
|
|
||||||
})),
|
|
||||||
{
|
{
|
||||||
role: "user",
|
role: "user",
|
||||||
content: userMessage
|
content: userMessage
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
max_tokens: 200, // 減少 token 數量以獲得更簡潔的回答
|
max_tokens: 300,
|
||||||
temperature: 0.7
|
temperature: 0.7,
|
||||||
|
stream: false
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`API request failed: ${response.status}`)
|
const errorText = await response.text()
|
||||||
|
console.error("API Error:", response.status, errorText)
|
||||||
|
throw new Error(`API request failed: ${response.status} - ${errorText}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
const rawResponse = data.choices[0]?.message?.content || "抱歉,我現在無法回答您的問題,請稍後再試。"
|
|
||||||
|
if (!data.choices || !data.choices[0] || !data.choices[0].message) {
|
||||||
|
console.error("Invalid API response:", data)
|
||||||
|
throw new Error("Invalid API response format")
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawResponse = data.choices[0].message.content || "抱歉,我現在無法回答您的問題,請稍後再試。"
|
||||||
return cleanResponse(rawResponse)
|
return cleanResponse(rawResponse)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("DeepSeek API error:", error)
|
console.error("DeepSeek API error:", error)
|
||||||
|
|
||||||
|
// 根據錯誤類型提供不同的錯誤信息
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (error.message.includes('401')) {
|
||||||
|
return "API 密鑰無效,請聯繫管理員檢查配置。"
|
||||||
|
} else if (error.message.includes('429')) {
|
||||||
|
return "API 請求過於頻繁,請稍後再試。"
|
||||||
|
} else if (error.message.includes('500')) {
|
||||||
|
return "AI 服務暫時不可用,請稍後再試。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return "抱歉,我現在無法連接到AI服務,請檢查網路連接或稍後再試。"
|
return "抱歉,我現在無法連接到AI服務,請檢查網路連接或稍後再試。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,48 +151,90 @@ export function ChatBot() {
|
|||||||
const question = userQuestion.toLowerCase()
|
const question = userQuestion.toLowerCase()
|
||||||
|
|
||||||
// 前台相關問題
|
// 前台相關問題
|
||||||
if (question.includes('註冊') || question.includes('團隊')) {
|
if (question.includes('註冊') || question.includes('團隊') || question.includes('報名')) {
|
||||||
return [
|
return [
|
||||||
"如何提交作品?",
|
"如何提交作品?",
|
||||||
"怎麼查看競賽詳情?",
|
"怎麼查看競賽詳情?",
|
||||||
"如何收藏作品?",
|
"如何收藏應用?",
|
||||||
"怎麼進行投票?"
|
"怎麼查看我的參賽記錄?"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
if (question.includes('作品') || question.includes('提交')) {
|
if (question.includes('作品') || question.includes('提交') || question.includes('應用')) {
|
||||||
return [
|
return [
|
||||||
"如何修改作品?",
|
"如何修改作品信息?",
|
||||||
"怎麼查看作品狀態?",
|
"怎麼查看作品狀態?",
|
||||||
"如何刪除作品?",
|
"如何刪除作品?",
|
||||||
"怎麼下載作品?"
|
"怎麼查看作品評價?"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
if (question.includes('投票') || question.includes('排行榜')) {
|
if (question.includes('投票') || question.includes('排行榜') || question.includes('評分')) {
|
||||||
return [
|
return [
|
||||||
"如何查看排行榜?",
|
"如何查看排行榜?",
|
||||||
"怎麼收藏作品?",
|
"怎麼收藏喜歡的應用?",
|
||||||
"如何評論作品?",
|
"如何對應用進行評論?",
|
||||||
"怎麼分享作品?"
|
"怎麼查看應用詳情?"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
if (question.includes('個人') || question.includes('資料') || question.includes('設置')) {
|
||||||
|
return [
|
||||||
|
"如何修改個人資料?",
|
||||||
|
"怎麼查看我的收藏?",
|
||||||
|
"如何修改密碼?",
|
||||||
|
"怎麼查看通知?"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 後台相關問題
|
// 後台管理相關問題
|
||||||
if (question.includes('競賽') || question.includes('創建')) {
|
if (question.includes('競賽') || question.includes('創建') || question.includes('管理')) {
|
||||||
return [
|
return [
|
||||||
"如何編輯競賽?",
|
"如何編輯競賽信息?",
|
||||||
"怎麼設定評分標準?",
|
"怎麼設定評分標準?",
|
||||||
"如何管理參賽團隊?",
|
"如何管理參賽團隊?",
|
||||||
"怎麼設定獎項?"
|
"怎麼設定獎項?"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
if (question.includes('評審') || question.includes('評分')) {
|
if (question.includes('評審') || question.includes('評分') || question.includes('評委')) {
|
||||||
return [
|
return [
|
||||||
"如何新增評審?",
|
"如何新增評審成員?",
|
||||||
"怎麼設定評審權限?",
|
"怎麼設定評審權限?",
|
||||||
"如何查看評分結果?",
|
"如何查看評分結果?",
|
||||||
"怎麼生成評審連結?"
|
"怎麼生成評審連結?"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
if (question.includes('應用') || question.includes('app') || question.includes('作品管理')) {
|
||||||
|
return [
|
||||||
|
"如何審核應用?",
|
||||||
|
"怎麼管理應用狀態?",
|
||||||
|
"如何查看應用統計?",
|
||||||
|
"怎麼處理應用舉報?"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
if (question.includes('用戶') || question.includes('成員') || question.includes('邀請')) {
|
||||||
|
return [
|
||||||
|
"如何邀請新用戶?",
|
||||||
|
"怎麼管理用戶角色?",
|
||||||
|
"如何查看用戶統計?",
|
||||||
|
"怎麼處理用戶問題?"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 技術支持相關問題
|
||||||
|
if (question.includes('忘記') || question.includes('密碼') || question.includes('登入')) {
|
||||||
|
return [
|
||||||
|
"如何重設密碼?",
|
||||||
|
"怎麼修改個人資料?",
|
||||||
|
"如何聯繫管理員?",
|
||||||
|
"怎麼查看使用說明?"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
if (question.includes('錯誤') || question.includes('問題') || question.includes('無法')) {
|
||||||
|
return [
|
||||||
|
"如何聯繫技術支持?",
|
||||||
|
"怎麼查看常見問題?",
|
||||||
|
"如何回報問題?",
|
||||||
|
"怎麼查看系統狀態?"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
// 通用問題
|
// 通用問題
|
||||||
return [
|
return [
|
||||||
|
253
lib/ai-knowledge-base.ts
Normal file
253
lib/ai-knowledge-base.ts
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
// AI 助手知識庫
|
||||||
|
export const platformKnowledge = {
|
||||||
|
// 平台概述
|
||||||
|
overview: {
|
||||||
|
name: "強茂集團 AI 展示平台",
|
||||||
|
description: "企業內部 AI 應用展示與競賽管理系統",
|
||||||
|
purpose: "提供 AI 應用展示、競賽管理、評分系統和用戶互動功能"
|
||||||
|
},
|
||||||
|
|
||||||
|
// 主要功能模塊
|
||||||
|
modules: {
|
||||||
|
// 前台功能
|
||||||
|
frontend: {
|
||||||
|
"應用展示": {
|
||||||
|
description: "瀏覽和查看所有 AI 應用",
|
||||||
|
features: [
|
||||||
|
"應用列表瀏覽",
|
||||||
|
"應用詳情查看",
|
||||||
|
"應用分類篩選",
|
||||||
|
"應用搜索功能",
|
||||||
|
"應用評分和評論"
|
||||||
|
],
|
||||||
|
access: "所有用戶都可以訪問"
|
||||||
|
},
|
||||||
|
"競賽系統": {
|
||||||
|
description: "參與和管理競賽",
|
||||||
|
features: [
|
||||||
|
"競賽瀏覽和報名",
|
||||||
|
"作品提交",
|
||||||
|
"評分查看",
|
||||||
|
"排行榜查看",
|
||||||
|
"獎項展示"
|
||||||
|
],
|
||||||
|
access: "註冊用戶可以參與"
|
||||||
|
},
|
||||||
|
"用戶中心": {
|
||||||
|
description: "個人資料和活動管理",
|
||||||
|
features: [
|
||||||
|
"個人資料管理",
|
||||||
|
"參賽記錄查看",
|
||||||
|
"收藏應用管理",
|
||||||
|
"通知查看",
|
||||||
|
"設置偏好"
|
||||||
|
],
|
||||||
|
access: "需要登入"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 後台管理功能
|
||||||
|
backend: {
|
||||||
|
"應用管理": {
|
||||||
|
description: "管理平台上的所有 AI 應用",
|
||||||
|
features: [
|
||||||
|
"應用列表查看(分頁顯示,每頁5個)",
|
||||||
|
"應用詳情管理",
|
||||||
|
"應用狀態管理(發布/下架)",
|
||||||
|
"應用統計數據",
|
||||||
|
"應用審核和批准"
|
||||||
|
],
|
||||||
|
access: "管理員和開發者",
|
||||||
|
path: "/admin/apps"
|
||||||
|
},
|
||||||
|
"競賽管理": {
|
||||||
|
description: "創建和管理競賽活動",
|
||||||
|
features: [
|
||||||
|
"競賽創建和編輯",
|
||||||
|
"競賽類型設置(個人/團隊/混合)",
|
||||||
|
"競賽時間管理",
|
||||||
|
"參賽者管理",
|
||||||
|
"競賽規則設定"
|
||||||
|
],
|
||||||
|
access: "管理員",
|
||||||
|
path: "/admin/competitions"
|
||||||
|
},
|
||||||
|
"評審管理": {
|
||||||
|
description: "管理評審團和評分系統",
|
||||||
|
features: [
|
||||||
|
"評審團成員管理",
|
||||||
|
"評分標準設定",
|
||||||
|
"評分進度追蹤",
|
||||||
|
"評分統計分析",
|
||||||
|
"評審連結生成"
|
||||||
|
],
|
||||||
|
access: "管理員",
|
||||||
|
path: "/admin/judges"
|
||||||
|
},
|
||||||
|
"用戶管理": {
|
||||||
|
description: "管理平台用戶",
|
||||||
|
features: [
|
||||||
|
"用戶列表查看",
|
||||||
|
"用戶角色管理",
|
||||||
|
"用戶邀請功能",
|
||||||
|
"用戶統計數據",
|
||||||
|
"用戶活動監控"
|
||||||
|
],
|
||||||
|
access: "管理員",
|
||||||
|
path: "/admin/users"
|
||||||
|
},
|
||||||
|
"評分管理": {
|
||||||
|
description: "管理評分流程和結果",
|
||||||
|
features: [
|
||||||
|
"手動評分輸入",
|
||||||
|
"評分進度監控",
|
||||||
|
"評分統計分析",
|
||||||
|
"評分報告生成",
|
||||||
|
"評分連結管理"
|
||||||
|
],
|
||||||
|
access: "管理員",
|
||||||
|
path: "/admin/scoring"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 詳細操作指南
|
||||||
|
guides: {
|
||||||
|
"如何註冊參賽團隊": {
|
||||||
|
steps: [
|
||||||
|
"1. 點擊首頁的「競賽」按鈕",
|
||||||
|
"2. 選擇要參加的競賽",
|
||||||
|
"3. 點擊「立即報名」按鈕",
|
||||||
|
"4. 填寫團隊信息(團隊名稱、成員信息等)",
|
||||||
|
"5. 確認報名信息並提交"
|
||||||
|
],
|
||||||
|
tips: "確保所有團隊成員都已註冊平台帳號"
|
||||||
|
},
|
||||||
|
"怎麼提交作品": {
|
||||||
|
steps: [
|
||||||
|
"1. 登入您的帳號",
|
||||||
|
"2. 進入「我的競賽」頁面",
|
||||||
|
"3. 找到對應的競賽",
|
||||||
|
"4. 點擊「提交作品」",
|
||||||
|
"5. 填寫作品信息(名稱、描述、技術棧等)",
|
||||||
|
"6. 上傳作品文件或提供作品連結",
|
||||||
|
"7. 確認提交"
|
||||||
|
],
|
||||||
|
tips: "作品提交後無法修改,請仔細檢查"
|
||||||
|
},
|
||||||
|
"如何創建新競賽": {
|
||||||
|
steps: [
|
||||||
|
"1. 以管理員身份登入",
|
||||||
|
"2. 進入「競賽管理」頁面",
|
||||||
|
"3. 點擊「新增競賽」按鈕",
|
||||||
|
"4. 填寫競賽基本信息(名稱、描述、類型等)",
|
||||||
|
"5. 設定競賽時間和規則",
|
||||||
|
"6. 配置評審團成員",
|
||||||
|
"7. 發布競賽"
|
||||||
|
],
|
||||||
|
tips: "競賽發布前請仔細檢查所有設置"
|
||||||
|
},
|
||||||
|
"怎麼管理評審團": {
|
||||||
|
steps: [
|
||||||
|
"1. 進入「評審管理」頁面",
|
||||||
|
"2. 點擊「新增評審」",
|
||||||
|
"3. 填寫評審信息(姓名、職位、專業領域等)",
|
||||||
|
"4. 分配評審到特定競賽",
|
||||||
|
"5. 生成評審登入連結",
|
||||||
|
"6. 發送連結給評審"
|
||||||
|
],
|
||||||
|
tips: "確保評審有足夠的專業知識來評分作品"
|
||||||
|
},
|
||||||
|
"如何查看應用詳情": {
|
||||||
|
steps: [
|
||||||
|
"1. 在首頁或應用列表中找到感興趣的應用",
|
||||||
|
"2. 點擊應用卡片或「查看詳情」按鈕",
|
||||||
|
"3. 在詳情頁面可以查看:",
|
||||||
|
" - 應用描述和功能",
|
||||||
|
" - 技術棧信息",
|
||||||
|
" - 創建者信息",
|
||||||
|
" - 用戶評價和評分",
|
||||||
|
" - 使用統計數據"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"如何收藏應用": {
|
||||||
|
steps: [
|
||||||
|
"1. 進入應用詳情頁面",
|
||||||
|
"2. 點擊「收藏」按鈕(心形圖標)",
|
||||||
|
"3. 收藏的應用會出現在「我的收藏」中",
|
||||||
|
"4. 可以隨時取消收藏"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"如何進行評分": {
|
||||||
|
steps: [
|
||||||
|
"1. 以評審身份登入",
|
||||||
|
"2. 使用管理員提供的評審連結",
|
||||||
|
"3. 選擇要評分的競賽",
|
||||||
|
"4. 查看參賽作品列表",
|
||||||
|
"5. 對每個作品進行評分(創新性、技術性、實用性、展示性、影響力)",
|
||||||
|
"6. 添加評分評論",
|
||||||
|
"7. 提交評分"
|
||||||
|
],
|
||||||
|
tips: "評分標準:1-5分,5分為最高分"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 常見問題
|
||||||
|
faq: {
|
||||||
|
"忘記密碼怎麼辦": "點擊登入頁面的「忘記密碼」連結,輸入註冊時的電子郵件,系統會發送重設密碼的連結到您的信箱。",
|
||||||
|
"如何修改個人資料": "登入後點擊右上角頭像,選擇「個人資料」,即可修改姓名、部門、頭像等信息。",
|
||||||
|
"為什麼看不到某些競賽": "可能是因為競賽尚未開始、已結束,或者您沒有參與權限。請聯繫管理員確認。",
|
||||||
|
"評分什麼時候會公布": "評分結果會在競賽結束後由管理員統一公布,請關注平台通知。",
|
||||||
|
"如何聯繫管理員": "可以通過平台內的通知系統或直接發送郵件給管理員。",
|
||||||
|
"作品提交後可以修改嗎": "作品提交後無法修改,請在提交前仔細檢查所有信息。",
|
||||||
|
"如何查看我的參賽記錄": "登入後進入「我的競賽」頁面,可以查看所有參賽記錄和狀態。"
|
||||||
|
},
|
||||||
|
|
||||||
|
// 技術信息
|
||||||
|
technical: {
|
||||||
|
"支持的瀏覽器": "Chrome、Firefox、Safari、Edge 最新版本",
|
||||||
|
"文件上傳限制": "單個文件最大 10MB,支持 JPG、PNG、PDF、DOC、PPT 等格式",
|
||||||
|
"系統要求": "需要 JavaScript 啟用,建議使用現代瀏覽器",
|
||||||
|
"數據安全": "所有數據都經過加密傳輸和存儲,符合企業安全標準"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成 AI 助手的系統提示詞
|
||||||
|
export function generateSystemPrompt(): string {
|
||||||
|
return `
|
||||||
|
你是一個專業的 AI 助手,專門為「${platformKnowledge.overview.name}」提供技術支持和用戶指導。
|
||||||
|
|
||||||
|
平台概述:
|
||||||
|
${platformKnowledge.overview.description}
|
||||||
|
${platformKnowledge.overview.purpose}
|
||||||
|
|
||||||
|
主要功能模塊:
|
||||||
|
|
||||||
|
前台功能:
|
||||||
|
${Object.entries(platformKnowledge.modules.frontend).map(([key, value]) =>
|
||||||
|
`- ${key}: ${value.description}`
|
||||||
|
).join('\n')}
|
||||||
|
|
||||||
|
後台管理功能:
|
||||||
|
${Object.entries(platformKnowledge.modules.backend).map(([key, value]) =>
|
||||||
|
`- ${key}: ${value.description}`
|
||||||
|
).join('\n')}
|
||||||
|
|
||||||
|
回答指南:
|
||||||
|
1. 用友善、專業的語氣回答用戶問題
|
||||||
|
2. 提供具體的操作步驟和實用建議
|
||||||
|
3. 回答要簡潔明瞭,避免過長的文字
|
||||||
|
4. 如果問題涉及具體操作,請提供詳細步驟
|
||||||
|
5. 如果不知道答案,請誠實說明並建議聯繫管理員
|
||||||
|
6. 不要使用任何 Markdown 格式,只使用純文字回答
|
||||||
|
7. 不要使用 **、*、#、- 等符號
|
||||||
|
8. 回答長度控制在 200 字以內
|
||||||
|
|
||||||
|
常見問題快速回答:
|
||||||
|
${Object.entries(platformKnowledge.faq).map(([question, answer]) =>
|
||||||
|
`Q: ${question}\nA: ${answer}`
|
||||||
|
).join('\n\n')}
|
||||||
|
|
||||||
|
請根據用戶的問題,提供準確、有用的回答。
|
||||||
|
`
|
||||||
|
}
|
@@ -1,80 +0,0 @@
|
|||||||
# 測試數據插入說明
|
|
||||||
|
|
||||||
## 問題修復
|
|
||||||
|
|
||||||
✅ **已修復重複約束錯誤**:原來的腳本會因為同一個用戶對同一個應用重複評分而出現 `Duplicate entry` 錯誤。現在已修復為使用不同的用戶 ID。
|
|
||||||
|
|
||||||
✅ **已修復 null 值錯誤**:修復了 `user_id` 不能為 null 的問題,提供了簡化版本的腳本。
|
|
||||||
|
|
||||||
## 腳本版本
|
|
||||||
|
|
||||||
### 版本 1:完整版本 (`insert-test-data.sql`)
|
|
||||||
- 使用不同的用戶 ID 避免重複約束
|
|
||||||
- 需要資料庫中有至少 10 個活躍用戶
|
|
||||||
|
|
||||||
### 版本 2:簡化版本 (`insert-test-data-simple.sql`)
|
|
||||||
- 使用單一用戶 ID,避免 null 值問題
|
|
||||||
- 只需要資料庫中有至少 1 個活躍用戶
|
|
||||||
- 只有 1 條評分記錄
|
|
||||||
|
|
||||||
### 版本 3:多用戶版本 (`insert-test-data-multi-user.sql`) ⭐ 推薦
|
|
||||||
- 使用 5 個不同用戶,避免重複約束
|
|
||||||
- 需要資料庫中有至少 5 個活躍用戶
|
|
||||||
- 包含 25 條瀏覽、8 條按讚、5 條評分記錄
|
|
||||||
- 平衡了數據豐富度和穩定性
|
|
||||||
|
|
||||||
## 方法一:使用 MySQL 命令行
|
|
||||||
|
|
||||||
1. 打開命令提示符或 PowerShell
|
|
||||||
2. 連接到 MySQL:
|
|
||||||
```bash
|
|
||||||
mysql -u root -p
|
|
||||||
```
|
|
||||||
3. 選擇資料庫:
|
|
||||||
```sql
|
|
||||||
USE ai_showcase_platform;
|
|
||||||
```
|
|
||||||
4. 執行 SQL 腳本(推薦使用多用戶版本):
|
|
||||||
```sql
|
|
||||||
source E:/ai-showcase-platform/scripts/insert-test-data-multi-user.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
## 方法二:使用 MySQL Workbench 或其他 GUI 工具
|
|
||||||
|
|
||||||
1. 打開 MySQL Workbench
|
|
||||||
2. 連接到您的 MySQL 服務器
|
|
||||||
3. 選擇 `ai_showcase_platform` 資料庫
|
|
||||||
4. 打開 `scripts/insert-test-data-multi-user.sql` 文件(推薦)
|
|
||||||
5. 執行整個腳本
|
|
||||||
|
|
||||||
## 方法三:使用 phpMyAdmin
|
|
||||||
|
|
||||||
1. 打開 phpMyAdmin
|
|
||||||
2. 選擇 `ai_showcase_platform` 資料庫
|
|
||||||
3. 點擊 "SQL" 標籤
|
|
||||||
4. 複製 `scripts/insert-test-data-multi-user.sql` 的內容(推薦)
|
|
||||||
5. 貼上並執行
|
|
||||||
|
|
||||||
## 預期結果
|
|
||||||
|
|
||||||
執行成功後,您應該看到:
|
|
||||||
- 25 條瀏覽記錄(使用 5 個不同用戶)
|
|
||||||
- 8 條按讚記錄(使用 5 個不同用戶)
|
|
||||||
- 5 條評分記錄(使用 5 個不同用戶)
|
|
||||||
- 應用統計數據更新為:25 瀏覽、8 讚、4.2 平均評分
|
|
||||||
|
|
||||||
## 驗證
|
|
||||||
|
|
||||||
執行完成後,您可以:
|
|
||||||
1. 重新載入應用管理頁面
|
|
||||||
2. 點擊任何應用的「查看詳情」
|
|
||||||
3. 切換到「統計數據」標籤頁查看真實數據
|
|
||||||
4. 切換到「評價管理」標籤頁查看評價列表
|
|
||||||
|
|
||||||
## 注意事項
|
|
||||||
|
|
||||||
- 腳本會先清空現有的測試數據,避免重複
|
|
||||||
- **簡化版本**:使用單一用戶 ID,只有 1 條評分記錄
|
|
||||||
- **多用戶版本**:使用 5 個不同用戶,平衡數據豐富度和穩定性 ⭐ 推薦
|
|
||||||
- **完整版本**:使用不同用戶 ID,需要至少 10 個活躍用戶
|
|
||||||
- 如果您的資料庫中用戶數量少於 5 個,建議使用簡化版本
|
|
@@ -1,72 +0,0 @@
|
|||||||
// =====================================================
|
|
||||||
// 檢查APP與競賽的關聯關係
|
|
||||||
// =====================================================
|
|
||||||
|
|
||||||
const mysql = require('mysql2/promise');
|
|
||||||
|
|
||||||
async function checkAppCompetitionRelation() {
|
|
||||||
console.log('🔍 檢查APP與競賽的關聯關係...\n');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 連接數據庫
|
|
||||||
const connection = await mysql.createConnection({
|
|
||||||
host: 'mysql.theaken.com',
|
|
||||||
port: 33306,
|
|
||||||
user: 'AI_Platform',
|
|
||||||
password: 'Aa123456',
|
|
||||||
database: 'db_AI_Platform'
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('✅ 數據庫連接成功');
|
|
||||||
|
|
||||||
// 檢查特定APP
|
|
||||||
const appId = "7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7";
|
|
||||||
console.log(`\n📊 檢查APP ${appId}:`);
|
|
||||||
|
|
||||||
// 檢查APP是否存在
|
|
||||||
const [apps] = await connection.execute('SELECT * FROM apps WHERE id = ?', [appId]);
|
|
||||||
console.log('APP信息:', apps);
|
|
||||||
|
|
||||||
// 檢查APP的競賽關聯
|
|
||||||
const [competitionApps] = await connection.execute(
|
|
||||||
'SELECT ca.*, c.name as competition_name FROM competition_apps ca LEFT JOIN competitions c ON ca.competition_id = c.id WHERE ca.app_id = ?',
|
|
||||||
[appId]
|
|
||||||
);
|
|
||||||
console.log('競賽關聯:', competitionApps);
|
|
||||||
|
|
||||||
// 檢查所有競賽
|
|
||||||
console.log('\n📊 所有競賽:');
|
|
||||||
const [competitions] = await connection.execute('SELECT id, name, type FROM competitions');
|
|
||||||
console.log(competitions);
|
|
||||||
|
|
||||||
// 檢查所有競賽APP關聯
|
|
||||||
console.log('\n📊 所有競賽APP關聯:');
|
|
||||||
const [allCompetitionApps] = await connection.execute('SELECT * FROM competition_apps LIMIT 10');
|
|
||||||
console.log(allCompetitionApps);
|
|
||||||
|
|
||||||
// 如果沒有關聯,創建一個
|
|
||||||
if (competitionApps.length === 0 && apps.length > 0 && competitions.length > 0) {
|
|
||||||
console.log('\n🔧 創建APP與競賽的關聯...');
|
|
||||||
const competitionId = competitions[0].id;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await connection.execute(
|
|
||||||
'INSERT INTO competition_apps (id, competition_id, app_id) VALUES (UUID(), ?, ?)',
|
|
||||||
[competitionId, appId]
|
|
||||||
);
|
|
||||||
console.log('✅ 關聯創建成功');
|
|
||||||
} catch (error) {
|
|
||||||
console.log('❌ 關聯創建失敗:', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await connection.end();
|
|
||||||
console.log('\n✅ 數據庫連接已關閉');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ 檢查失敗:', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行檢查
|
|
||||||
checkAppCompetitionRelation();
|
|
@@ -1,69 +0,0 @@
|
|||||||
// =====================================================
|
|
||||||
// 檢查競賽詳細數據
|
|
||||||
// =====================================================
|
|
||||||
|
|
||||||
const mysql = require('mysql2/promise');
|
|
||||||
|
|
||||||
async function checkCompetitionDataDetails() {
|
|
||||||
console.log('🔍 檢查競賽詳細數據...\n');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 連接數據庫
|
|
||||||
const connection = await mysql.createConnection({
|
|
||||||
host: 'mysql.theaken.com',
|
|
||||||
port: 33306,
|
|
||||||
user: 'AI_Platform',
|
|
||||||
password: 'Aa123456',
|
|
||||||
database: 'db_AI_Platform'
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('✅ 數據庫連接成功');
|
|
||||||
|
|
||||||
const competitionId = "be4b0a71-91f1-11f0-bb38-4adff2d0e33e";
|
|
||||||
|
|
||||||
// 檢查競賽評審關聯
|
|
||||||
console.log('\n📊 競賽評審關聯:');
|
|
||||||
const [competitionJudges] = await connection.execute(`
|
|
||||||
SELECT cj.*, j.name as judge_name
|
|
||||||
FROM competition_judges cj
|
|
||||||
LEFT JOIN judges j ON cj.judge_id = j.id
|
|
||||||
WHERE cj.competition_id = ?
|
|
||||||
`, [competitionId]);
|
|
||||||
console.log(competitionJudges);
|
|
||||||
|
|
||||||
// 檢查競賽APP關聯
|
|
||||||
console.log('\n📊 競賽APP關聯:');
|
|
||||||
const [competitionApps] = await connection.execute(`
|
|
||||||
SELECT ca.*, a.name as app_name
|
|
||||||
FROM competition_apps ca
|
|
||||||
LEFT JOIN apps a ON ca.app_id = a.id
|
|
||||||
WHERE ca.competition_id = ?
|
|
||||||
`, [competitionId]);
|
|
||||||
console.log(competitionApps);
|
|
||||||
|
|
||||||
// 檢查評分記錄
|
|
||||||
console.log('\n📊 評分記錄:');
|
|
||||||
const [judgeScores] = await connection.execute(`
|
|
||||||
SELECT js.*, j.name as judge_name, a.name as app_name
|
|
||||||
FROM judge_scores js
|
|
||||||
LEFT JOIN judges j ON js.judge_id = j.id
|
|
||||||
LEFT JOIN apps a ON js.app_id = a.id
|
|
||||||
WHERE js.competition_id = ?
|
|
||||||
`, [competitionId]);
|
|
||||||
console.log(judgeScores);
|
|
||||||
|
|
||||||
// 檢查所有競賽
|
|
||||||
console.log('\n📊 所有競賽:');
|
|
||||||
const [competitions] = await connection.execute('SELECT id, name FROM competitions');
|
|
||||||
console.log(competitions);
|
|
||||||
|
|
||||||
await connection.end();
|
|
||||||
console.log('\n✅ 數據庫連接已關閉');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ 檢查失敗:', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行檢查
|
|
||||||
checkCompetitionDataDetails();
|
|
@@ -1,60 +0,0 @@
|
|||||||
// =====================================================
|
|
||||||
// 檢查競賽相關數據
|
|
||||||
// =====================================================
|
|
||||||
|
|
||||||
const mysql = require('mysql2/promise');
|
|
||||||
|
|
||||||
async function checkCompetitionData() {
|
|
||||||
console.log('🔍 檢查競賽相關數據...\n');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 連接數據庫
|
|
||||||
const connection = await mysql.createConnection({
|
|
||||||
host: 'mysql.theaken.com',
|
|
||||||
port: 33306,
|
|
||||||
user: 'AI_Platform',
|
|
||||||
password: 'Aa123456',
|
|
||||||
database: 'db_AI_Platform'
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('✅ 數據庫連接成功');
|
|
||||||
|
|
||||||
// 檢查競賽數據
|
|
||||||
console.log('\n📊 競賽數據:');
|
|
||||||
const [competitions] = await connection.execute('SELECT id, name, type FROM competitions LIMIT 5');
|
|
||||||
console.log(competitions);
|
|
||||||
|
|
||||||
// 檢查競賽規則
|
|
||||||
console.log('\n📊 競賽規則:');
|
|
||||||
const [rules] = await connection.execute('SELECT * FROM competition_rules LIMIT 10');
|
|
||||||
console.log(rules);
|
|
||||||
|
|
||||||
// 檢查競賽APP關聯
|
|
||||||
console.log('\n📊 競賽APP關聯:');
|
|
||||||
const [competitionApps] = await connection.execute('SELECT * FROM competition_apps LIMIT 10');
|
|
||||||
console.log(competitionApps);
|
|
||||||
|
|
||||||
// 檢查APP數據
|
|
||||||
console.log('\n📊 APP數據:');
|
|
||||||
const [apps] = await connection.execute('SELECT id, name, team_id FROM apps LIMIT 5');
|
|
||||||
console.log(apps);
|
|
||||||
|
|
||||||
// 檢查特定APP的競賽關聯
|
|
||||||
const appId = "7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7";
|
|
||||||
console.log(`\n📊 APP ${appId} 的競賽關聯:`);
|
|
||||||
const [appCompetition] = await connection.execute(
|
|
||||||
'SELECT ca.*, c.name as competition_name FROM competition_apps ca LEFT JOIN competitions c ON ca.competition_id = c.id WHERE ca.app_id = ?',
|
|
||||||
[appId]
|
|
||||||
);
|
|
||||||
console.log(appCompetition);
|
|
||||||
|
|
||||||
await connection.end();
|
|
||||||
console.log('\n✅ 數據庫連接已關閉');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ 檢查失敗:', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行檢查
|
|
||||||
checkCompetitionData();
|
|
@@ -1,60 +0,0 @@
|
|||||||
// =====================================================
|
|
||||||
// 檢查環境變數載入情況
|
|
||||||
// =====================================================
|
|
||||||
|
|
||||||
console.log('🔍 檢查環境變數載入情況...\n');
|
|
||||||
|
|
||||||
// 檢查所有相關的環境變數
|
|
||||||
const envVars = [
|
|
||||||
'DB_HOST',
|
|
||||||
'DB_PORT',
|
|
||||||
'DB_NAME',
|
|
||||||
'DB_USER',
|
|
||||||
'DB_PASSWORD',
|
|
||||||
'SLAVE_DB_HOST',
|
|
||||||
'SLAVE_DB_PORT',
|
|
||||||
'SLAVE_DB_NAME',
|
|
||||||
'SLAVE_DB_USER',
|
|
||||||
'SLAVE_DB_PASSWORD',
|
|
||||||
'DB_DUAL_WRITE_ENABLED',
|
|
||||||
'DB_MASTER_PRIORITY'
|
|
||||||
];
|
|
||||||
|
|
||||||
console.log('📋 環境變數檢查結果:');
|
|
||||||
console.log('='.repeat(50));
|
|
||||||
|
|
||||||
envVars.forEach(varName => {
|
|
||||||
const value = process.env[varName];
|
|
||||||
if (value) {
|
|
||||||
console.log(`✅ ${varName}: ${value}`);
|
|
||||||
} else {
|
|
||||||
console.log(`❌ ${varName}: undefined`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('\n🔍 檢查 .env 文件是否存在...');
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
const envPath = path.join(__dirname, '..', '.env');
|
|
||||||
if (fs.existsSync(envPath)) {
|
|
||||||
console.log('✅ .env 文件存在');
|
|
||||||
console.log('📄 .env 文件內容:');
|
|
||||||
console.log('-'.repeat(30));
|
|
||||||
const envContent = fs.readFileSync(envPath, 'utf8');
|
|
||||||
console.log(envContent);
|
|
||||||
} else {
|
|
||||||
console.log('❌ .env 文件不存在');
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n🔍 檢查 Next.js 配置...');
|
|
||||||
const nextConfigPath = path.join(__dirname, '..', 'next.config.mjs');
|
|
||||||
if (fs.existsSync(nextConfigPath)) {
|
|
||||||
console.log('✅ next.config.mjs 存在');
|
|
||||||
const nextConfig = fs.readFileSync(nextConfigPath, 'utf8');
|
|
||||||
console.log('📄 Next.js 配置內容:');
|
|
||||||
console.log('-'.repeat(30));
|
|
||||||
console.log(nextConfig);
|
|
||||||
} else {
|
|
||||||
console.log('❌ next.config.mjs 不存在');
|
|
||||||
}
|
|
@@ -1,73 +0,0 @@
|
|||||||
// =====================================================
|
|
||||||
// 檢查現有的APP記錄
|
|
||||||
// =====================================================
|
|
||||||
|
|
||||||
async function checkExistingApps() {
|
|
||||||
console.log('🔍 檢查現有的APP記錄...\n');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 等待服務器啟動
|
|
||||||
console.log('⏳ 等待服務器啟動...');
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('http://localhost:3000/api/competitions/be4b0a71-91f1-11f0-bb38-4adff2d0e33e/apps');
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
console.log('\n📊 競賽APP列表:');
|
|
||||||
console.log('狀態碼:', response.status);
|
|
||||||
console.log('APP數量:', data.data?.apps?.length || 0);
|
|
||||||
|
|
||||||
if (data.data?.apps?.length > 0) {
|
|
||||||
console.log('\n📋 APP列表:');
|
|
||||||
data.data.apps.forEach((app, index) => {
|
|
||||||
console.log(`${index + 1}. ID: ${app.id}`);
|
|
||||||
console.log(` 名稱: ${app.name}`);
|
|
||||||
console.log(` 創建者: ${app.creator}`);
|
|
||||||
console.log(` 類型: ${app.type}`);
|
|
||||||
console.log('---');
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log('❌ 沒有找到APP記錄');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log('❌ API 調用失敗:', error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 檢查團隊APP
|
|
||||||
try {
|
|
||||||
const response = await fetch('http://localhost:3000/api/competitions/be4b0a71-91f1-11f0-bb38-4adff2d0e33e/teams');
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
console.log('\n📊 競賽團隊列表:');
|
|
||||||
console.log('狀態碼:', response.status);
|
|
||||||
console.log('團隊數量:', data.data?.teams?.length || 0);
|
|
||||||
|
|
||||||
if (data.data?.teams?.length > 0) {
|
|
||||||
console.log('\n📋 團隊列表:');
|
|
||||||
data.data.teams.forEach((team, index) => {
|
|
||||||
console.log(`${index + 1}. 團隊ID: ${team.id}`);
|
|
||||||
console.log(` 團隊名稱: ${team.name}`);
|
|
||||||
console.log(` 隊長: ${team.leader_name}`);
|
|
||||||
console.log(` APP數量: ${team.apps?.length || 0}`);
|
|
||||||
if (team.apps && team.apps.length > 0) {
|
|
||||||
team.apps.forEach((app, appIndex) => {
|
|
||||||
console.log(` APP ${appIndex + 1}: ${app.id} - ${app.name}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
console.log('---');
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log('❌ 沒有找到團隊記錄');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log('❌ 團隊API調用失敗:', error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ 檢查失敗:', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行檢查
|
|
||||||
checkExistingApps();
|
|
@@ -1,32 +0,0 @@
|
|||||||
// =====================================================
|
|
||||||
// 檢查服務器狀態腳本
|
|
||||||
// =====================================================
|
|
||||||
|
|
||||||
async function checkServer() {
|
|
||||||
console.log('🔍 檢查服務器狀態...\n');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 等待服務器啟動
|
|
||||||
console.log('⏳ 等待服務器啟動...');
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
||||||
|
|
||||||
// 測試基本連接
|
|
||||||
console.log('🌐 測試基本連接...');
|
|
||||||
const response = await fetch('http://localhost:3000/api/test-db');
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
console.error('❌ 服務器回應錯誤:', response.status, response.statusText);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
console.log('✅ 服務器正常運行');
|
|
||||||
console.log('📊 回應數據:', JSON.stringify(data, null, 2));
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ 服務器連接失敗:', error.message);
|
|
||||||
console.log('\n💡 提示: 請確保開發服務器正在運行 (npm run dev)');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkServer();
|
|
@@ -1,34 +0,0 @@
|
|||||||
const mysql = require('mysql2/promise');
|
|
||||||
|
|
||||||
async function clearDatabase() {
|
|
||||||
const connection = await mysql.createConnection({
|
|
||||||
host: 'mysql.theaken.com',
|
|
||||||
port: 33306,
|
|
||||||
user: 'AI_Platform',
|
|
||||||
password: 'Aa123456',
|
|
||||||
database: 'db_AI_Platform'
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('🗑️ 清空資料庫...');
|
|
||||||
|
|
||||||
// 清空所有表(按依賴順序)
|
|
||||||
const tables = [
|
|
||||||
'app_judge_scores',
|
|
||||||
'competition_apps',
|
|
||||||
'competition_judges',
|
|
||||||
'apps',
|
|
||||||
'judges',
|
|
||||||
'competitions',
|
|
||||||
'users'
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const table of tables) {
|
|
||||||
await connection.execute(`DELETE FROM ${table}`);
|
|
||||||
console.log(`✅ 清空了 ${table} 表`);
|
|
||||||
}
|
|
||||||
|
|
||||||
await connection.end();
|
|
||||||
console.log('🎉 資料庫清空完成!');
|
|
||||||
}
|
|
||||||
|
|
||||||
clearDatabase().catch(console.error);
|
|
@@ -1,66 +0,0 @@
|
|||||||
// =====================================================
|
|
||||||
// 創建團隊評分表
|
|
||||||
// =====================================================
|
|
||||||
|
|
||||||
const mysql = require('mysql2/promise');
|
|
||||||
|
|
||||||
async function createTeamScoresTable() {
|
|
||||||
console.log('🔧 創建團隊評分表...\n');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 連接資料庫
|
|
||||||
const connection = await mysql.createConnection({
|
|
||||||
host: process.env.DB_HOST || '122.100.99.161',
|
|
||||||
port: parseInt(process.env.DB_PORT || '43306'),
|
|
||||||
user: process.env.DB_USER || 'AI_Platform',
|
|
||||||
password: process.env.DB_PASSWORD || 'Aa123456',
|
|
||||||
database: process.env.DB_NAME || 'db_AI_Platform'
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('✅ 資料庫連接成功');
|
|
||||||
|
|
||||||
// 創建團隊評分表
|
|
||||||
const createTableSQL = `
|
|
||||||
CREATE TABLE IF NOT EXISTS team_judge_scores (
|
|
||||||
id VARCHAR(36) PRIMARY KEY,
|
|
||||||
judge_id VARCHAR(36) NOT NULL,
|
|
||||||
team_id VARCHAR(36) NOT NULL,
|
|
||||||
innovation_score INT NOT NULL CHECK (innovation_score >= 1 AND innovation_score <= 10),
|
|
||||||
technical_score INT NOT NULL CHECK (technical_score >= 1 AND technical_score <= 10),
|
|
||||||
usability_score INT NOT NULL CHECK (usability_score >= 1 AND usability_score <= 10),
|
|
||||||
presentation_score INT NOT NULL CHECK (presentation_score >= 1 AND presentation_score <= 10),
|
|
||||||
impact_score INT NOT NULL CHECK (impact_score >= 1 AND impact_score <= 10),
|
|
||||||
total_score DECIMAL(5,2) NOT NULL,
|
|
||||||
comments TEXT,
|
|
||||||
submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
|
|
||||||
FOREIGN KEY (judge_id) REFERENCES judges(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE,
|
|
||||||
UNIQUE KEY unique_judge_team (judge_id, team_id),
|
|
||||||
INDEX idx_judge (judge_id),
|
|
||||||
INDEX idx_team (team_id),
|
|
||||||
INDEX idx_total_score (total_score)
|
|
||||||
)
|
|
||||||
`;
|
|
||||||
|
|
||||||
await connection.execute(createTableSQL);
|
|
||||||
console.log('✅ 團隊評分表創建成功');
|
|
||||||
|
|
||||||
// 檢查表是否創建成功
|
|
||||||
const [tables] = await connection.execute("SHOW TABLES LIKE 'team_judge_scores'");
|
|
||||||
if (tables.length > 0) {
|
|
||||||
console.log('✅ 表存在確認成功');
|
|
||||||
} else {
|
|
||||||
console.log('❌ 表創建失敗');
|
|
||||||
}
|
|
||||||
|
|
||||||
await connection.end();
|
|
||||||
console.log('\n✅ 團隊評分表創建完成!');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ 創建表失敗:', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行創建
|
|
||||||
createTeamScoresTable();
|
|
@@ -1,66 +0,0 @@
|
|||||||
// =====================================================
|
|
||||||
// 通過 API 創建虛擬應用記錄
|
|
||||||
// =====================================================
|
|
||||||
|
|
||||||
async function createVirtualApp() {
|
|
||||||
console.log('🔧 通過 API 創建虛擬應用記錄...\n');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 等待服務器啟動
|
|
||||||
console.log('⏳ 等待服務器啟動...');
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
||||||
|
|
||||||
// 創建虛擬應用記錄
|
|
||||||
const virtualAppData = {
|
|
||||||
id: 'team_t1757702332911zcl6iafq1',
|
|
||||||
name: '[團隊評分] aaa',
|
|
||||||
description: '團隊 aaa 的評分記錄',
|
|
||||||
creator_id: '00000000-0000-0000-0000-000000000000',
|
|
||||||
category: 'team_scoring',
|
|
||||||
type: 'team',
|
|
||||||
app_url: null,
|
|
||||||
icon: 'Users',
|
|
||||||
icon_color: 'from-gray-500 to-gray-600',
|
|
||||||
likes_count: 0,
|
|
||||||
views_count: 0,
|
|
||||||
rating: 0.00,
|
|
||||||
is_active: true
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('📝 創建虛擬應用數據:');
|
|
||||||
console.log(JSON.stringify(virtualAppData, null, 2));
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('http://localhost:3000/api/apps', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(virtualAppData)
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
console.log('\n📊 API 回應:');
|
|
||||||
console.log('狀態碼:', response.status);
|
|
||||||
console.log('回應數據:', JSON.stringify(data, null, 2));
|
|
||||||
|
|
||||||
if (data.success) {
|
|
||||||
console.log('✅ 虛擬應用創建成功!');
|
|
||||||
} else {
|
|
||||||
console.log('❌ 虛擬應用創建失敗:', data.message);
|
|
||||||
if (data.error) {
|
|
||||||
console.log('錯誤詳情:', data.error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log('❌ API 調用失敗:', error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ 測試失敗:', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行創建
|
|
||||||
createVirtualApp();
|
|
@@ -1,18 +0,0 @@
|
|||||||
-- 創建虛擬應用記錄用於團隊評分
|
|
||||||
INSERT IGNORE INTO apps (
|
|
||||||
id, name, description, creator_id, category, type,
|
|
||||||
app_url, icon, icon_color, likes_count, views_count,
|
|
||||||
rating, is_active, created_at, updated_at
|
|
||||||
) VALUES (
|
|
||||||
'team_t1757702332911zcl6iafq1',
|
|
||||||
'[團隊評分] aaa',
|
|
||||||
'團隊 aaa 的評分記錄',
|
|
||||||
'00000000-0000-0000-0000-000000000000',
|
|
||||||
'team_scoring',
|
|
||||||
'team',
|
|
||||||
NULL, 'Users', 'from-gray-500 to-gray-600',
|
|
||||||
0, 0, 0.00, TRUE, NOW(), NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 驗證創建結果
|
|
||||||
SELECT id, name, type FROM apps WHERE id = 'team_t1757702332911zcl6iafq1';
|
|
@@ -1,83 +0,0 @@
|
|||||||
// =====================================================
|
|
||||||
// 通過 API 創建虛擬應用記錄
|
|
||||||
// =====================================================
|
|
||||||
|
|
||||||
async function createVirtualAppsViaAPI() {
|
|
||||||
console.log('🔧 通過 API 創建虛擬應用記錄...\n');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 等待服務器啟動
|
|
||||||
console.log('⏳ 等待服務器啟動...');
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
||||||
|
|
||||||
// 獲取團隊數據
|
|
||||||
console.log('📋 獲取團隊數據...');
|
|
||||||
const teamsResponse = await fetch('http://localhost:3000/api/competitions');
|
|
||||||
const competitionsData = await teamsResponse.json();
|
|
||||||
|
|
||||||
if (!competitionsData.success || !competitionsData.data || competitionsData.data.length === 0) {
|
|
||||||
console.log('❌ 無法獲取競賽數據');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const competition = competitionsData.data[0];
|
|
||||||
console.log('🎯 選擇競賽:', competition.name);
|
|
||||||
|
|
||||||
// 獲取競賽的團隊數據
|
|
||||||
const teamsDataResponse = await fetch(`http://localhost:3000/api/competitions/${competition.id}/teams`);
|
|
||||||
const teamsData = await teamsDataResponse.json();
|
|
||||||
|
|
||||||
if (!teamsData.success || !teamsData.data.teams || teamsData.data.teams.length === 0) {
|
|
||||||
console.log('❌ 競賽沒有團隊數據');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const teams = teamsData.data.teams;
|
|
||||||
console.log('✅ 獲取到', teams.length, '個團隊');
|
|
||||||
|
|
||||||
// 為每個團隊創建虛擬應用
|
|
||||||
for (const team of teams) {
|
|
||||||
const virtualAppData = {
|
|
||||||
name: `[團隊評分] ${team.name}`,
|
|
||||||
description: `團隊 ${team.name} 的評分記錄`,
|
|
||||||
creator_id: '00000000-0000-0000-0000-000000000000', // 虛擬創建者ID
|
|
||||||
category: 'team_scoring',
|
|
||||||
type: 'team',
|
|
||||||
app_url: null,
|
|
||||||
icon: 'Users',
|
|
||||||
icon_color: 'from-gray-500 to-gray-600'
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log(`📝 創建虛擬應用: ${team.name}...`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('http://localhost:3000/api/apps', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(virtualAppData)
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.success) {
|
|
||||||
console.log(`✅ 虛擬應用創建成功: ${data.app?.id || '未知ID'}`);
|
|
||||||
} else {
|
|
||||||
console.log(`⚠️ 虛擬應用創建失敗: ${data.message}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`❌ 創建虛擬應用時出錯: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n✅ 虛擬應用記錄創建完成!');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ 創建虛擬應用失敗:', error.message);
|
|
||||||
console.log('\n💡 提示: 請確保開發服務器正在運行 (npm run dev)');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行創建
|
|
||||||
createVirtualAppsViaAPI();
|
|
@@ -1,71 +0,0 @@
|
|||||||
// =====================================================
|
|
||||||
// 創建虛擬應用記錄用於團隊評分
|
|
||||||
// =====================================================
|
|
||||||
|
|
||||||
const mysql = require('mysql2/promise');
|
|
||||||
|
|
||||||
async function createVirtualApps() {
|
|
||||||
console.log('🔧 創建虛擬應用記錄...\n');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 連接資料庫
|
|
||||||
const connection = await mysql.createConnection({
|
|
||||||
host: process.env.DB_HOST || '122.100.99.161',
|
|
||||||
port: parseInt(process.env.DB_PORT || '43306'),
|
|
||||||
user: process.env.DB_USER || 'AI_Platform',
|
|
||||||
password: process.env.DB_PASSWORD || 'Aa123456',
|
|
||||||
database: process.env.DB_NAME || 'db_AI_Platform'
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('✅ 資料庫連接成功');
|
|
||||||
|
|
||||||
// 獲取所有團隊
|
|
||||||
const [teams] = await connection.execute('SELECT id, name FROM teams WHERE is_active = TRUE');
|
|
||||||
console.log('📋 找到', teams.length, '個團隊');
|
|
||||||
|
|
||||||
// 為每個團隊創建虛擬應用
|
|
||||||
for (const team of teams) {
|
|
||||||
const virtualAppId = `team_${team.id}`;
|
|
||||||
|
|
||||||
// 檢查是否已存在
|
|
||||||
const [existing] = await connection.execute('SELECT id FROM apps WHERE id = ?', [virtualAppId]);
|
|
||||||
|
|
||||||
if (existing.length === 0) {
|
|
||||||
const sql = `
|
|
||||||
INSERT INTO apps (id, name, description, creator_id, category, type, app_url, icon, icon_color, likes_count, views_count, rating, is_active, created_at, updated_at)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
|
||||||
`;
|
|
||||||
|
|
||||||
const params = [
|
|
||||||
virtualAppId,
|
|
||||||
`[團隊評分] ${team.name}`,
|
|
||||||
`團隊 ${team.name} 的評分記錄`,
|
|
||||||
'00000000-0000-0000-0000-000000000000', // 虛擬創建者ID
|
|
||||||
'team_scoring',
|
|
||||||
'team',
|
|
||||||
null,
|
|
||||||
'Users',
|
|
||||||
'from-gray-500 to-gray-600',
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0.00,
|
|
||||||
true
|
|
||||||
];
|
|
||||||
|
|
||||||
await connection.execute(sql, params);
|
|
||||||
console.log(`✅ 創建虛擬應用: ${virtualAppId} (${team.name})`);
|
|
||||||
} else {
|
|
||||||
console.log(`⏭️ 虛擬應用已存在: ${virtualAppId}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await connection.end();
|
|
||||||
console.log('\n✅ 虛擬應用記錄創建完成!');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ 創建虛擬應用失敗:', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行創建
|
|
||||||
createVirtualApps();
|
|
@@ -1,107 +0,0 @@
|
|||||||
-- =====================================================
|
|
||||||
-- 修復外鍵約束問題的 SQL 腳本
|
|
||||||
-- =====================================================
|
|
||||||
|
|
||||||
-- 問題:app_judge_scores 表的 app_id 外鍵約束失敗
|
|
||||||
-- 原因:團隊評分使用的 teamId 不存在於 apps 表中
|
|
||||||
|
|
||||||
-- 解決方案:為團隊創建對應的虛擬應用記錄
|
|
||||||
|
|
||||||
-- 1. 先查看現有的團隊數據
|
|
||||||
SELECT '=== 現有團隊 ===' as info;
|
|
||||||
SELECT id, name, department FROM teams WHERE is_active = TRUE;
|
|
||||||
|
|
||||||
-- 2. 為團隊 t1757702332911zcl6iafq1 (aaa) 創建虛擬應用
|
|
||||||
INSERT IGNORE INTO apps (
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
creator_id,
|
|
||||||
category,
|
|
||||||
type,
|
|
||||||
app_url,
|
|
||||||
icon,
|
|
||||||
icon_color,
|
|
||||||
likes_count,
|
|
||||||
views_count,
|
|
||||||
rating,
|
|
||||||
is_active,
|
|
||||||
created_at,
|
|
||||||
updated_at
|
|
||||||
) VALUES (
|
|
||||||
'team_t1757702332911zcl6iafq1',
|
|
||||||
'[團隊評分] aaa',
|
|
||||||
'團隊 aaa 的評分記錄 - 用於存儲團隊評分數據',
|
|
||||||
'00000000-0000-0000-0000-000000000000',
|
|
||||||
'team_scoring',
|
|
||||||
'team',
|
|
||||||
NULL,
|
|
||||||
'Users',
|
|
||||||
'from-gray-500 to-gray-600',
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0.00,
|
|
||||||
TRUE,
|
|
||||||
NOW(),
|
|
||||||
NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 3. 驗證虛擬應用是否創建成功
|
|
||||||
SELECT '=== 虛擬應用創建結果 ===' as info;
|
|
||||||
SELECT id, name, type, category, is_active FROM apps WHERE id = 'team_t1757702332911zcl6iafq1';
|
|
||||||
|
|
||||||
-- 4. 現在可以插入團隊評分記錄了
|
|
||||||
-- 測試插入團隊評分(使用真實的評審ID)
|
|
||||||
INSERT INTO app_judge_scores (
|
|
||||||
id,
|
|
||||||
judge_id,
|
|
||||||
app_id,
|
|
||||||
innovation_score,
|
|
||||||
technical_score,
|
|
||||||
usability_score,
|
|
||||||
presentation_score,
|
|
||||||
impact_score,
|
|
||||||
total_score,
|
|
||||||
comments,
|
|
||||||
submitted_at
|
|
||||||
) VALUES (
|
|
||||||
UUID(),
|
|
||||||
'fed0a353-8ffe-11f0-bb38-4adff2d0e33e', -- 評審ID
|
|
||||||
'team_t1757702332911zcl6iafq1', -- 虛擬應用ID
|
|
||||||
8, -- innovation_score
|
|
||||||
7, -- technical_score
|
|
||||||
9, -- usability_score
|
|
||||||
8, -- presentation_score
|
|
||||||
7, -- impact_score
|
|
||||||
7.8, -- total_score (平均分)
|
|
||||||
'測試團隊評分記錄',
|
|
||||||
NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 5. 驗證評分記錄是否插入成功
|
|
||||||
SELECT '=== 評分記錄插入結果 ===' as info;
|
|
||||||
SELECT
|
|
||||||
ajs.id,
|
|
||||||
ajs.judge_id,
|
|
||||||
ajs.app_id,
|
|
||||||
ajs.innovation_score,
|
|
||||||
ajs.technical_score,
|
|
||||||
ajs.usability_score,
|
|
||||||
ajs.presentation_score,
|
|
||||||
ajs.impact_score,
|
|
||||||
ajs.total_score,
|
|
||||||
ajs.comments,
|
|
||||||
ajs.submitted_at,
|
|
||||||
a.name as app_name
|
|
||||||
FROM app_judge_scores ajs
|
|
||||||
LEFT JOIN apps a ON ajs.app_id = a.id
|
|
||||||
WHERE ajs.app_id = 'team_t1757702332911zcl6iafq1'
|
|
||||||
ORDER BY ajs.submitted_at DESC;
|
|
||||||
|
|
||||||
-- 6. 如果有其他團隊,也需要創建對應的虛擬應用
|
|
||||||
-- 格式:team_{teamId}
|
|
||||||
-- 例如:team_另一個團隊ID
|
|
||||||
|
|
||||||
-- 7. 清理測試數據(可選)
|
|
||||||
-- DELETE FROM app_judge_scores WHERE app_id = 'team_t1757702332911zcl6iafq1';
|
|
||||||
-- DELETE FROM apps WHERE id = 'team_t1757702332911zcl6iafq1';
|
|
@@ -1,319 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
// =====================================================
|
|
||||||
// 填充示例數據腳本
|
|
||||||
// =====================================================
|
|
||||||
|
|
||||||
const mysql = require('mysql2/promise');
|
|
||||||
const { v4: uuidv4 } = require('uuid');
|
|
||||||
|
|
||||||
// 資料庫配置
|
|
||||||
const dbConfig = {
|
|
||||||
host: process.env.DB_HOST || 'mysql.theaken.com',
|
|
||||||
port: parseInt(process.env.DB_PORT || '33306'),
|
|
||||||
user: process.env.DB_USER || 'AI_Platform',
|
|
||||||
password: process.env.DB_PASSWORD || 'Aa123456',
|
|
||||||
database: process.env.DB_NAME || 'db_AI_Platform',
|
|
||||||
charset: 'utf8mb4',
|
|
||||||
timezone: '+08:00',
|
|
||||||
};
|
|
||||||
|
|
||||||
async function populateSampleData() {
|
|
||||||
let connection;
|
|
||||||
|
|
||||||
try {
|
|
||||||
console.log('🚀 開始填充示例數據...');
|
|
||||||
|
|
||||||
// 創建連接
|
|
||||||
connection = await mysql.createConnection(dbConfig);
|
|
||||||
console.log('✅ 資料庫連接成功');
|
|
||||||
|
|
||||||
// 1. 創建示例用戶
|
|
||||||
console.log('👥 創建示例用戶...');
|
|
||||||
const users = [
|
|
||||||
{
|
|
||||||
id: uuidv4(),
|
|
||||||
name: '張小明',
|
|
||||||
email: 'zhang.xiaoming@company.com',
|
|
||||||
password_hash: '$2b$10$example.hash.here', // 示例哈希
|
|
||||||
department: 'HQBU',
|
|
||||||
role: 'developer',
|
|
||||||
join_date: '2024-01-15',
|
|
||||||
total_likes: 25,
|
|
||||||
total_views: 150,
|
|
||||||
is_active: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: uuidv4(),
|
|
||||||
name: '李美華',
|
|
||||||
email: 'li.meihua@company.com',
|
|
||||||
password_hash: '$2b$10$example.hash.here',
|
|
||||||
department: 'ITBU',
|
|
||||||
role: 'developer',
|
|
||||||
join_date: '2024-02-01',
|
|
||||||
total_likes: 18,
|
|
||||||
total_views: 120,
|
|
||||||
is_active: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: uuidv4(),
|
|
||||||
name: '王大偉',
|
|
||||||
email: 'wang.dawei@company.com',
|
|
||||||
password_hash: '$2b$10$example.hash.here',
|
|
||||||
department: 'MBU1',
|
|
||||||
role: 'developer',
|
|
||||||
join_date: '2024-01-20',
|
|
||||||
total_likes: 32,
|
|
||||||
total_views: 200,
|
|
||||||
is_active: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: uuidv4(),
|
|
||||||
name: '陳小芳',
|
|
||||||
email: 'chen.xiaofang@company.com',
|
|
||||||
password_hash: '$2b$10$example.hash.here',
|
|
||||||
department: 'SBU',
|
|
||||||
role: 'developer',
|
|
||||||
join_date: '2024-02-10',
|
|
||||||
total_likes: 15,
|
|
||||||
total_views: 90,
|
|
||||||
is_active: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: uuidv4(),
|
|
||||||
name: '劉志強',
|
|
||||||
email: 'liu.zhiqiang@company.com',
|
|
||||||
password_hash: '$2b$10$example.hash.here',
|
|
||||||
department: 'HQBU',
|
|
||||||
role: 'admin',
|
|
||||||
join_date: '2023-12-01',
|
|
||||||
total_likes: 5,
|
|
||||||
total_views: 50,
|
|
||||||
is_active: true
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const user of users) {
|
|
||||||
await connection.execute(
|
|
||||||
`INSERT INTO users (id, name, email, password_hash, department, role, join_date, total_likes, total_views, status, created_at, updated_at)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`,
|
|
||||||
[user.id, user.name, user.email, user.password_hash, user.department, user.role, user.join_date, user.total_likes, user.total_views, 'active']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
console.log(`✅ 創建了 ${users.length} 個用戶`);
|
|
||||||
|
|
||||||
// 2. 創建示例評審
|
|
||||||
console.log('👨⚖️ 創建示例評審...');
|
|
||||||
const judges = [
|
|
||||||
{
|
|
||||||
id: uuidv4(),
|
|
||||||
name: '王教授',
|
|
||||||
title: '技術總監',
|
|
||||||
department: 'ITBU',
|
|
||||||
expertise: JSON.stringify(['AI', '機器學習', '深度學習']),
|
|
||||||
is_active: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: uuidv4(),
|
|
||||||
name: '李博士',
|
|
||||||
title: '產品經理',
|
|
||||||
department: 'HQBU',
|
|
||||||
expertise: JSON.stringify(['產品設計', '用戶體驗', '商業分析']),
|
|
||||||
is_active: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: uuidv4(),
|
|
||||||
name: '陳工程師',
|
|
||||||
title: '資深工程師',
|
|
||||||
department: 'MBU1',
|
|
||||||
expertise: JSON.stringify(['軟體開發', '系統架構', '資料庫']),
|
|
||||||
is_active: true
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const judge of judges) {
|
|
||||||
await connection.execute(
|
|
||||||
`INSERT INTO judges (id, name, title, department, expertise, is_active, created_at, updated_at)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW())`,
|
|
||||||
[judge.id, judge.name, judge.title, judge.department, judge.expertise, judge.is_active]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
console.log(`✅ 創建了 ${judges.length} 個評審`);
|
|
||||||
|
|
||||||
// 3. 創建示例競賽
|
|
||||||
console.log('🏆 創建示例競賽...');
|
|
||||||
const competitions = [
|
|
||||||
{
|
|
||||||
id: uuidv4(),
|
|
||||||
name: '2024年AI創新競賽',
|
|
||||||
description: '展示最新的AI技術創新成果',
|
|
||||||
type: 'individual',
|
|
||||||
year: 2024,
|
|
||||||
month: 3,
|
|
||||||
start_date: '2024-03-01',
|
|
||||||
end_date: '2024-03-31',
|
|
||||||
status: 'active',
|
|
||||||
is_current: true,
|
|
||||||
is_active: true,
|
|
||||||
evaluation_focus: JSON.stringify(['創新性', '技術性', '實用性']),
|
|
||||||
max_team_size: 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: uuidv4(),
|
|
||||||
name: '2024年團隊協作競賽',
|
|
||||||
description: '團隊協作開發的AI應用',
|
|
||||||
type: 'team',
|
|
||||||
year: 2024,
|
|
||||||
month: 4,
|
|
||||||
start_date: '2024-04-01',
|
|
||||||
end_date: '2024-04-30',
|
|
||||||
status: 'upcoming',
|
|
||||||
is_current: false,
|
|
||||||
is_active: true,
|
|
||||||
evaluation_focus: JSON.stringify(['團隊協作', '技術實現', '創新應用']),
|
|
||||||
max_team_size: 8
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const competition of competitions) {
|
|
||||||
await connection.execute(
|
|
||||||
`INSERT INTO competitions (id, name, description, type, year, month, start_date, end_date, status, is_current, is_active, evaluation_focus, max_team_size, created_at, updated_at)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`,
|
|
||||||
[competition.id, competition.name, competition.description, competition.type, competition.year, competition.month,
|
|
||||||
competition.start_date, competition.end_date, competition.status, competition.is_current, competition.is_active,
|
|
||||||
competition.evaluation_focus, competition.max_team_size]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
console.log(`✅ 創建了 ${competitions.length} 個競賽`);
|
|
||||||
|
|
||||||
// 4. 創建示例應用
|
|
||||||
console.log('📱 創建示例應用...');
|
|
||||||
const apps = [
|
|
||||||
{
|
|
||||||
id: uuidv4(),
|
|
||||||
name: '智能對話助手',
|
|
||||||
description: '基於大語言模型的智能對話系統',
|
|
||||||
creator_id: users[0].id,
|
|
||||||
team_id: null,
|
|
||||||
category: '文字處理',
|
|
||||||
technology_stack: JSON.stringify(['Python', 'OpenAI API', 'React']),
|
|
||||||
github_url: 'https://github.com/example/chatbot',
|
|
||||||
demo_url: 'https://demo.example.com/chatbot',
|
|
||||||
status: 'published',
|
|
||||||
is_active: true,
|
|
||||||
total_likes: 25,
|
|
||||||
total_views: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: uuidv4(),
|
|
||||||
name: '圖像生成工具',
|
|
||||||
description: 'AI驅動的創意圖像生成平台',
|
|
||||||
creator_id: users[1].id,
|
|
||||||
team_id: null,
|
|
||||||
category: '圖像生成',
|
|
||||||
technology_stack: JSON.stringify(['Python', 'Stable Diffusion', 'FastAPI']),
|
|
||||||
github_url: 'https://github.com/example/image-gen',
|
|
||||||
demo_url: 'https://demo.example.com/image-gen',
|
|
||||||
status: 'published',
|
|
||||||
is_active: true,
|
|
||||||
total_likes: 18,
|
|
||||||
total_views: 120
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: uuidv4(),
|
|
||||||
name: '語音識別系統',
|
|
||||||
description: '高精度多語言語音識別服務',
|
|
||||||
creator_id: users[2].id,
|
|
||||||
team_id: null,
|
|
||||||
category: '語音辨識',
|
|
||||||
technology_stack: JSON.stringify(['Python', 'Whisper', 'Docker']),
|
|
||||||
github_url: 'https://github.com/example/speech-recognition',
|
|
||||||
demo_url: 'https://demo.example.com/speech',
|
|
||||||
status: 'published',
|
|
||||||
is_active: true,
|
|
||||||
total_likes: 32,
|
|
||||||
total_views: 200
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const app of apps) {
|
|
||||||
await connection.execute(
|
|
||||||
`INSERT INTO apps (id, name, description, creator_id, team_id, category, type, app_url, likes_count, views_count, is_active, created_at, updated_at)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`,
|
|
||||||
[app.id, app.name, app.description, app.creator_id, app.team_id, app.category, 'web', app.demo_url, app.total_likes, app.total_views, 1]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
console.log(`✅ 創建了 ${apps.length} 個應用`);
|
|
||||||
|
|
||||||
// 5. 關聯競賽和應用
|
|
||||||
console.log('🔗 關聯競賽和應用...');
|
|
||||||
const currentCompetition = competitions[0]; // 當前競賽
|
|
||||||
for (const app of apps) {
|
|
||||||
await connection.execute(
|
|
||||||
`INSERT INTO competition_apps (id, competition_id, app_id, submitted_at) VALUES (?, ?, ?, NOW())`,
|
|
||||||
[uuidv4(), currentCompetition.id, app.id]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
console.log(`✅ 關聯了 ${apps.length} 個應用到當前競賽`);
|
|
||||||
|
|
||||||
// 6. 關聯競賽和評審
|
|
||||||
console.log('🔗 關聯競賽和評審...');
|
|
||||||
for (const judge of judges) {
|
|
||||||
await connection.execute(
|
|
||||||
`INSERT INTO competition_judges (id, competition_id, judge_id, assigned_at) VALUES (?, ?, ?, NOW())`,
|
|
||||||
[uuidv4(), currentCompetition.id, judge.id]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
console.log(`✅ 關聯了 ${judges.length} 個評審到當前競賽`);
|
|
||||||
|
|
||||||
// 7. 創建示例評分
|
|
||||||
console.log('📊 創建示例評分...');
|
|
||||||
for (const app of apps) {
|
|
||||||
for (const judge of judges) {
|
|
||||||
const scores = {
|
|
||||||
innovation_score: Math.floor(Math.random() * 5) + 1,
|
|
||||||
technical_score: Math.floor(Math.random() * 5) + 1,
|
|
||||||
usability_score: Math.floor(Math.random() * 5) + 1,
|
|
||||||
presentation_score: Math.floor(Math.random() * 5) + 1,
|
|
||||||
impact_score: Math.floor(Math.random() * 5) + 1
|
|
||||||
};
|
|
||||||
|
|
||||||
const totalScore = (scores.innovation_score + scores.technical_score + scores.usability_score +
|
|
||||||
scores.presentation_score + scores.impact_score) / 5;
|
|
||||||
|
|
||||||
await connection.execute(
|
|
||||||
`INSERT INTO app_judge_scores (id, judge_id, app_id, innovation_score, technical_score, usability_score, presentation_score, impact_score, total_score, comments, submitted_at)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())`,
|
|
||||||
[uuidv4(), judge.id, app.id, scores.innovation_score, scores.technical_score, scores.usability_score,
|
|
||||||
scores.presentation_score, scores.impact_score, totalScore, '示例評分']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log(`✅ 創建了 ${apps.length * judges.length} 個評分記錄`);
|
|
||||||
|
|
||||||
console.log('🎉 示例數據填充完成!');
|
|
||||||
console.log('\n📊 數據摘要:');
|
|
||||||
console.log(`- 用戶: ${users.length} 個`);
|
|
||||||
console.log(`- 評審: ${judges.length} 個`);
|
|
||||||
console.log(`- 競賽: ${competitions.length} 個 (其中 1 個為當前競賽)`);
|
|
||||||
console.log(`- 應用: ${apps.length} 個`);
|
|
||||||
console.log(`- 評分記錄: ${apps.length * judges.length} 個`);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ 填充失敗:', error.message);
|
|
||||||
console.error('詳細錯誤:', error);
|
|
||||||
process.exit(1);
|
|
||||||
} finally {
|
|
||||||
if (connection) {
|
|
||||||
await connection.end();
|
|
||||||
console.log('🔌 資料庫連接已關閉');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行填充
|
|
||||||
if (require.main === module) {
|
|
||||||
populateSampleData().catch(console.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { populateSampleData };
|
|
@@ -1,22 +0,0 @@
|
|||||||
-- =====================================================
|
|
||||||
-- 快速修復外鍵約束問題
|
|
||||||
-- =====================================================
|
|
||||||
|
|
||||||
-- 插入虛擬應用記錄
|
|
||||||
INSERT IGNORE INTO apps (
|
|
||||||
id, name, description, creator_id, category, type,
|
|
||||||
app_url, icon, icon_color, likes_count, views_count,
|
|
||||||
rating, is_active, created_at, updated_at
|
|
||||||
) VALUES (
|
|
||||||
'team_t1757702332911zcl6iafq1',
|
|
||||||
'[團隊評分] aaa',
|
|
||||||
'團隊 aaa 的評分記錄',
|
|
||||||
'00000000-0000-0000-0000-000000000000',
|
|
||||||
'team_scoring',
|
|
||||||
'team',
|
|
||||||
NULL, 'Users', 'from-gray-500 to-gray-600',
|
|
||||||
0, 0, 0.00, TRUE, NOW(), NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 驗證創建結果
|
|
||||||
SELECT id, name, type FROM apps WHERE id = 'team_t1757702332911zcl6iafq1';
|
|
@@ -1,145 +0,0 @@
|
|||||||
-- =====================================================
|
|
||||||
-- 重新設計評分數據庫架構
|
|
||||||
-- 讓評分系統完全基於 competition_rules 的動態內容
|
|
||||||
-- =====================================================
|
|
||||||
|
|
||||||
-- 1. 創建新的評分記錄表(基於競賽規則)
|
|
||||||
CREATE TABLE `judge_scores` (
|
|
||||||
`id` VARCHAR(36) PRIMARY KEY,
|
|
||||||
`judge_id` VARCHAR(36) NOT NULL,
|
|
||||||
`app_id` VARCHAR(36) NOT NULL,
|
|
||||||
`competition_id` VARCHAR(36) NOT NULL,
|
|
||||||
`total_score` DECIMAL(5,2) NOT NULL,
|
|
||||||
`comments` TEXT,
|
|
||||||
`submitted_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
|
|
||||||
FOREIGN KEY (`judge_id`) REFERENCES `judges`(`id`) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (`app_id`) REFERENCES `apps`(`id`) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (`competition_id`) REFERENCES `competitions`(`id`) ON DELETE CASCADE,
|
|
||||||
UNIQUE KEY `unique_judge_app_competition` (`judge_id`, `app_id`, `competition_id`),
|
|
||||||
INDEX `idx_judge` (`judge_id`),
|
|
||||||
INDEX `idx_app` (`app_id`),
|
|
||||||
INDEX `idx_competition` (`competition_id`),
|
|
||||||
INDEX `idx_total_score` (`total_score`)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 2. 創建評分項目詳情表(存儲具體的評分項目和分數)
|
|
||||||
CREATE TABLE `judge_score_details` (
|
|
||||||
`id` VARCHAR(36) PRIMARY KEY,
|
|
||||||
`judge_score_id` VARCHAR(36) NOT NULL,
|
|
||||||
`rule_id` VARCHAR(36) NOT NULL,
|
|
||||||
`rule_name` VARCHAR(200) NOT NULL,
|
|
||||||
`score` INT NOT NULL CHECK (`score` >= 1 AND `score` <= 10),
|
|
||||||
`weight` DECIMAL(5,2) NOT NULL DEFAULT 0.00,
|
|
||||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
|
|
||||||
FOREIGN KEY (`judge_score_id`) REFERENCES `judge_scores`(`id`) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (`rule_id`) REFERENCES `competition_rules`(`id`) ON DELETE CASCADE,
|
|
||||||
UNIQUE KEY `unique_score_rule` (`judge_score_id`, `rule_id`),
|
|
||||||
INDEX `idx_judge_score` (`judge_score_id`),
|
|
||||||
INDEX `idx_rule` (`rule_id`)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 3. 備份現有的 app_judge_scores 數據(如果需要)
|
|
||||||
CREATE TABLE `app_judge_scores_backup` AS SELECT * FROM `app_judge_scores`;
|
|
||||||
|
|
||||||
-- 4. 遷移現有數據到新結構
|
|
||||||
INSERT INTO `judge_scores` (
|
|
||||||
`id`, `judge_id`, `app_id`, `competition_id`, `total_score`, `comments`, `submitted_at`
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
`id`,
|
|
||||||
`judge_id`,
|
|
||||||
`app_id`,
|
|
||||||
COALESCE(
|
|
||||||
(SELECT ca.competition_id FROM competition_apps ca WHERE ca.app_id = ajs.app_id LIMIT 1),
|
|
||||||
'unknown-competition'
|
|
||||||
) as competition_id,
|
|
||||||
`total_score`,
|
|
||||||
`comments`,
|
|
||||||
`submitted_at`
|
|
||||||
FROM `app_judge_scores` ajs;
|
|
||||||
|
|
||||||
-- 5. 遷移評分詳情數據
|
|
||||||
INSERT INTO `judge_score_details` (
|
|
||||||
`id`, `judge_score_id`, `rule_id`, `rule_name`, `score`, `weight`
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
UUID() as id,
|
|
||||||
ajs.id as judge_score_id,
|
|
||||||
'migration-innovation' as rule_id,
|
|
||||||
'創新程度' as rule_name,
|
|
||||||
ajs.innovation_score as score,
|
|
||||||
20.00 as weight
|
|
||||||
FROM `app_judge_scores` ajs
|
|
||||||
WHERE ajs.innovation_score > 0
|
|
||||||
|
|
||||||
UNION ALL
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
UUID() as id,
|
|
||||||
ajs.id as judge_score_id,
|
|
||||||
'migration-technical' as rule_id,
|
|
||||||
'技術實現' as rule_name,
|
|
||||||
ajs.technical_score as score,
|
|
||||||
20.00 as weight
|
|
||||||
FROM `app_judge_scores` ajs
|
|
||||||
WHERE ajs.technical_score > 0
|
|
||||||
|
|
||||||
UNION ALL
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
UUID() as id,
|
|
||||||
ajs.id as judge_score_id,
|
|
||||||
'migration-usability' as rule_id,
|
|
||||||
'實用性' as rule_name,
|
|
||||||
ajs.usability_score as score,
|
|
||||||
20.00 as weight
|
|
||||||
FROM `app_judge_scores` ajs
|
|
||||||
WHERE ajs.usability_score > 0
|
|
||||||
|
|
||||||
UNION ALL
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
UUID() as id,
|
|
||||||
ajs.id as judge_score_id,
|
|
||||||
'migration-presentation' as rule_id,
|
|
||||||
'展示效果' as rule_name,
|
|
||||||
ajs.presentation_score as score,
|
|
||||||
20.00 as weight
|
|
||||||
FROM `app_judge_scores` ajs
|
|
||||||
WHERE ajs.presentation_score > 0
|
|
||||||
|
|
||||||
UNION ALL
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
UUID() as id,
|
|
||||||
ajs.id as judge_score_id,
|
|
||||||
'migration-impact' as rule_id,
|
|
||||||
'影響力' as rule_name,
|
|
||||||
ajs.impact_score as score,
|
|
||||||
20.00 as weight
|
|
||||||
FROM `app_judge_scores` ajs
|
|
||||||
WHERE ajs.impact_score > 0;
|
|
||||||
|
|
||||||
-- 6. 刪除舊的 app_judge_scores 表
|
|
||||||
-- DROP TABLE `app_judge_scores`;
|
|
||||||
|
|
||||||
-- 7. 創建視圖以保持向後兼容性
|
|
||||||
CREATE VIEW `app_judge_scores` AS
|
|
||||||
SELECT
|
|
||||||
js.id,
|
|
||||||
js.judge_id,
|
|
||||||
js.app_id,
|
|
||||||
js.total_score,
|
|
||||||
js.comments,
|
|
||||||
js.submitted_at,
|
|
||||||
-- 動態生成評分字段(基於競賽規則)
|
|
||||||
COALESCE(MAX(CASE WHEN jsd.rule_name = '創新程度' THEN jsd.score END), 0) as innovation_score,
|
|
||||||
COALESCE(MAX(CASE WHEN jsd.rule_name = '技術實現' THEN jsd.score END), 0) as technical_score,
|
|
||||||
COALESCE(MAX(CASE WHEN jsd.rule_name = '實用性' THEN jsd.score END), 0) as usability_score,
|
|
||||||
COALESCE(MAX(CASE WHEN jsd.rule_name = '展示效果' THEN jsd.score END), 0) as presentation_score,
|
|
||||||
COALESCE(MAX(CASE WHEN jsd.rule_name = '影響力' THEN jsd.score END), 0) as impact_score
|
|
||||||
FROM `judge_scores` js
|
|
||||||
LEFT JOIN `judge_score_details` jsd ON js.id = jsd.judge_score_id
|
|
||||||
GROUP BY js.id, js.judge_id, js.app_id, js.total_score, js.comments, js.submitted_at;
|
|
@@ -1,47 +0,0 @@
|
|||||||
// 測試邀請連結生成
|
|
||||||
console.log('🧪 測試邀請連結生成...\n');
|
|
||||||
|
|
||||||
// 模擬環境變數
|
|
||||||
process.env.NEXT_PUBLIC_APP_URL = 'https://ai-showcase.company.com';
|
|
||||||
|
|
||||||
// 測試不同的環境變數設置
|
|
||||||
const testCases = [
|
|
||||||
{
|
|
||||||
name: '使用 NEXT_PUBLIC_APP_URL',
|
|
||||||
env: { NEXT_PUBLIC_APP_URL: 'https://ai-showcase.company.com' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '未設置 NEXT_PUBLIC_APP_URL (使用 fallback)',
|
|
||||||
env: {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '設置為空字符串 (使用 fallback)',
|
|
||||||
env: { NEXT_PUBLIC_APP_URL: '' }
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
testCases.forEach((testCase, index) => {
|
|
||||||
console.log(`${index + 1}. ${testCase.name}`);
|
|
||||||
|
|
||||||
// 設置環境變數
|
|
||||||
Object.keys(testCase.env).forEach(key => {
|
|
||||||
process.env[key] = testCase.env[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
// 生成邀請連結
|
|
||||||
const invitationToken = 'test123456789';
|
|
||||||
const email = 'test@company.com';
|
|
||||||
const role = 'developer';
|
|
||||||
|
|
||||||
const invitationLink = `${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/register?token=${invitationToken}&email=${encodeURIComponent(email)}&role=${role}`;
|
|
||||||
|
|
||||||
console.log(` 邀請連結: ${invitationLink}`);
|
|
||||||
console.log(` 環境變數: NEXT_PUBLIC_APP_URL = ${process.env.NEXT_PUBLIC_APP_URL || 'undefined'}`);
|
|
||||||
console.log('');
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('✅ 測試完成!');
|
|
||||||
console.log('\n📝 說明:');
|
|
||||||
console.log('- 如果設置了 NEXT_PUBLIC_APP_URL,將使用該值');
|
|
||||||
console.log('- 如果未設置或為空,將使用 fallback: http://localhost:3000');
|
|
||||||
console.log('- 在生產環境中,請確保設置正確的 NEXT_PUBLIC_APP_URL');
|
|
@@ -1,51 +0,0 @@
|
|||||||
// =====================================================
|
|
||||||
// 測試評分進度功能
|
|
||||||
// =====================================================
|
|
||||||
|
|
||||||
async function testScoringProgress() {
|
|
||||||
console.log('🔧 測試評分進度功能...\n');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 等待服務器啟動
|
|
||||||
console.log('⏳ 等待服務器啟動...');
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
||||||
|
|
||||||
// 獲取競賽列表
|
|
||||||
console.log('📊 獲取競賽列表...');
|
|
||||||
const competitionsResponse = await fetch('http://localhost:3000/api/competitions');
|
|
||||||
const competitionsData = await competitionsResponse.json();
|
|
||||||
|
|
||||||
if (!competitionsData.success || !competitionsData.data || competitionsData.data.length === 0) {
|
|
||||||
console.log('❌ 沒有找到競賽數據');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const competition = competitionsData.data[0];
|
|
||||||
console.log('✅ 找到競賽:', competition.name, '(ID:', competition.id + ')');
|
|
||||||
|
|
||||||
// 測試評分進度 API
|
|
||||||
console.log('\n📊 測試評分進度 API...');
|
|
||||||
const progressResponse = await fetch(`http://localhost:3000/api/competitions/scoring-progress?competitionId=${competition.id}`);
|
|
||||||
const progressData = await progressResponse.json();
|
|
||||||
|
|
||||||
console.log('📊 評分進度 API 回應:');
|
|
||||||
console.log('狀態碼:', progressResponse.status);
|
|
||||||
console.log('回應數據:', JSON.stringify(progressData, null, 2));
|
|
||||||
|
|
||||||
if (progressData.success) {
|
|
||||||
console.log('✅ 評分進度獲取成功!');
|
|
||||||
console.log(`📈 評分進度: ${progressData.data.completed}/${progressData.data.total} (${progressData.data.percentage}%)`);
|
|
||||||
} else {
|
|
||||||
console.log('❌ 評分進度獲取失敗:', progressData.message);
|
|
||||||
if (progressData.error) {
|
|
||||||
console.log('錯誤詳情:', progressData.error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ 測試失敗:', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行測試
|
|
||||||
testScoringProgress();
|
|
@@ -1,79 +0,0 @@
|
|||||||
// =====================================================
|
|
||||||
// 測試評分完成度匯總功能
|
|
||||||
// =====================================================
|
|
||||||
|
|
||||||
async function testScoringSummary() {
|
|
||||||
console.log('🔧 測試評分完成度匯總功能...\n');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 等待服務器啟動
|
|
||||||
console.log('⏳ 等待服務器啟動...');
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
||||||
|
|
||||||
// 獲取競賽列表
|
|
||||||
console.log('📊 獲取競賽列表...');
|
|
||||||
const competitionsResponse = await fetch('http://localhost:3000/api/competitions');
|
|
||||||
const competitionsData = await competitionsResponse.json();
|
|
||||||
|
|
||||||
if (!competitionsData.success || !competitionsData.data || competitionsData.data.length === 0) {
|
|
||||||
console.log('❌ 沒有找到競賽數據');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const competition = competitionsData.data[0];
|
|
||||||
console.log('✅ 找到競賽:', competition.name, '(ID:', competition.id + ')');
|
|
||||||
|
|
||||||
// 測試評分完成度匯總 API
|
|
||||||
console.log('\n📊 測試評分完成度匯總 API...');
|
|
||||||
const summaryResponse = await fetch(`http://localhost:3000/api/admin/scoring/summary?competitionId=${competition.id}`);
|
|
||||||
const summaryData = await summaryResponse.json();
|
|
||||||
|
|
||||||
console.log('📊 評分完成度匯總 API 回應:');
|
|
||||||
console.log('狀態碼:', summaryResponse.status);
|
|
||||||
console.log('回應數據:', JSON.stringify(summaryData, null, 2));
|
|
||||||
|
|
||||||
if (summaryData.success) {
|
|
||||||
console.log('✅ 評分完成度匯總獲取成功!');
|
|
||||||
|
|
||||||
const { judges, apps, overallStats } = summaryData.data;
|
|
||||||
|
|
||||||
console.log('\n📈 總體統計:');
|
|
||||||
console.log(`- 評審總數: ${overallStats.totalJudges}`);
|
|
||||||
console.log(`- 參賽APP數: ${overallStats.totalApps}`);
|
|
||||||
console.log(`- 已完成評分: ${overallStats.completedScores}`);
|
|
||||||
console.log(`- 總完成率: ${overallStats.overallCompletionRate}%`);
|
|
||||||
|
|
||||||
console.log('\n👨⚖️ 評審完成度:');
|
|
||||||
judges.forEach((judge, index) => {
|
|
||||||
console.log(`${index + 1}. ${judge.name} (${judge.email})`);
|
|
||||||
console.log(` - 完成度: ${judge.completedCount}/${judge.totalCount} (${judge.completionRate}%)`);
|
|
||||||
console.log(` - 狀態: ${judge.status}`);
|
|
||||||
if (judge.lastScoredAt) {
|
|
||||||
console.log(` - 最後評分時間: ${judge.lastScoredAt}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('\n📱 參賽APP完成度:');
|
|
||||||
apps.forEach((app, index) => {
|
|
||||||
console.log(`${index + 1}. ${app.name}${app.teamName ? ` (團隊: ${app.teamName})` : ''}`);
|
|
||||||
console.log(` - 完成度: ${app.scoredCount}/${app.totalJudges} 評審 (${app.completionRate}%)`);
|
|
||||||
console.log(` - 狀態: ${app.status}`);
|
|
||||||
if (app.averageScore) {
|
|
||||||
console.log(` - 平均分: ${app.averageScore}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
console.log('❌ 評分完成度匯總獲取失敗:', summaryData.message);
|
|
||||||
if (summaryData.error) {
|
|
||||||
console.log('錯誤詳情:', summaryData.error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ 測試失敗:', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 執行測試
|
|
||||||
testScoringSummary();
|
|
Reference in New Issue
Block a user