完成品

This commit is contained in:
2025-09-19 02:58:43 +08:00
parent ffa1e45f63
commit b5e8cce2f3
10 changed files with 926 additions and 16 deletions

232
README.md Normal file
View File

@@ -0,0 +1,232 @@
# AI 展示平台 (AI Showcase Platform)
一個現代化的 AI 應用展示和管理平台,支持競賽管理、評分系統和用戶互動功能。
## 🚀 功能特色
### 📱 應用管理
- **應用展示**: 展示所有 AI 應用,支持分類和搜索
- **分頁功能**: 每頁顯示5個應用支持翻頁瀏覽
- **統計數據**: 實時顯示總應用數、已發布數、已下架數
- **應用詳情**: 查看應用的詳細信息、統計數據和用戶評價
### 🏆 競賽系統
- **競賽管理**: 創建和管理不同類型的競賽(個人、團隊、混合)
- **評審系統**: 支持多評審評分,包含創新性、技術性、實用性等維度
- **評分管理**: 實時追蹤評分進度和統計數據
- **排名系統**: 自動計算和顯示競賽排名
### 👥 用戶管理
- **用戶註冊**: 支持用戶註冊和登錄
- **權限管理**: 區分普通用戶、開發者和管理員權限
- **個人資料**: 用戶可以管理個人信息和偏好設置
### 📊 數據分析
- **統計儀表板**: 實時顯示平台使用統計
- **應用分析**: 查看應用的瀏覽量、點讚數、評分等數據
- **競賽分析**: 競賽參與度和評分統計
## 🛠️ 技術棧
### 前端
- **框架**: Next.js 14 (React 18)
- **樣式**: Tailwind CSS
- **UI 組件**: shadcn/ui
- **狀態管理**: React Context API
- **圖標**: Lucide React
### 後端
- **API**: Next.js API Routes
- **數據庫**: MySQL 8.0
- **ORM**: 自定義數據庫服務層
- **認證**: 自定義 JWT 認證系統
### 開發工具
- **包管理**: pnpm
- **代碼格式化**: Prettier
- **類型檢查**: TypeScript
- **數據庫遷移**: 自定義遷移腳本
## 📦 安裝與設置
### 環境要求
- Node.js 18.0.0 或更高版本
- pnpm (推薦) 或 npm
- MySQL 8.0 或更高版本
### 1. 克隆項目
```bash
git clone <repository-url>
cd ai-showcase-platform
```
### 2. 安裝依賴
```bash
pnpm install
```
### 3. 環境配置
複製環境變數範例文件:
```bash
cp env.example .env.local
```
編輯 `.env.local` 文件,填入您的數據庫配置:
```env
DB_HOST=your-database-host
DB_PORT=3306
DB_USER=your-username
DB_PASSWORD=your-password
DB_NAME=your-database-name
```
### 4. 數據庫設置
運行數據庫遷移:
```bash
# 創建數據庫結構
node scripts/migrate-no-triggers.js
# 填充示例數據
node scripts/populate-sample-data.js
```
### 5. 啟動開發服務器
```bash
pnpm dev
```
訪問 http://localhost:3000 查看應用。
## 📁 項目結構
```
ai-showcase-platform/
├── app/ # Next.js App Router
│ ├── admin/ # 管理員頁面
│ ├── api/ # API 路由
│ │ ├── admin/ # 管理員 API
│ │ ├── auth/ # 認證 API
│ │ ├── competitions/ # 競賽 API
│ │ └── user/ # 用戶 API
│ ├── competition/ # 競賽頁面
│ └── page.tsx # 首頁
├── components/ # React 組件
│ ├── admin/ # 管理員組件
│ ├── auth/ # 認證組件
│ ├── competition/ # 競賽組件
│ └── ui/ # UI 組件庫
├── contexts/ # React Context
├── hooks/ # 自定義 Hooks
├── lib/ # 工具庫
│ ├── services/ # 數據庫服務
│ └── utils.ts # 工具函數
├── scripts/ # 腳本文件
│ ├── migrate-no-triggers.js # 數據庫遷移
│ ├── populate-sample-data.js # 示例數據
│ └── check-*.js # 檢查腳本
├── types/ # TypeScript 類型定義
└── public/ # 靜態資源
```
## 🔧 開發指南
### 數據庫管理
```bash
# 檢查數據庫連接
node scripts/check-server.js
# 檢查應用數據
node scripts/check-apps-count.js
# 清空數據庫
node scripts/clear-database.js
# 重新填充數據
node scripts/populate-sample-data.js
```
### API 測試
```bash
# 測試應用 API
node scripts/test-pagination-api.js
# 測試分頁功能
node scripts/test-pagination.js
```
### 代碼規範
- 使用 TypeScript 進行類型檢查
- 遵循 ESLint 規則
- 使用 Prettier 格式化代碼
- 組件使用函數式組件和 Hooks
## 📊 數據庫設計
### 主要表結構
- **users**: 用戶信息
- **apps**: 應用信息
- **competitions**: 競賽信息
- **judges**: 評審信息
- **app_judge_scores**: 應用評分
- **competition_apps**: 競賽應用關聯
- **competition_judges**: 競賽評審關聯
### 關係設計
- 用戶與應用:一對多
- 競賽與應用:多對多
- 競賽與評審:多對多
- 評審與評分:一對多
## 🚀 部署
### 生產環境設置
1. 設置生產環境變數
2. 配置數據庫連接
3. 運行數據庫遷移
4. 構建應用:`pnpm build`
5. 啟動應用:`pnpm start`
### Docker 部署(可選)
```dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]
```
## 🤝 貢獻指南
1. Fork 項目
2. 創建功能分支:`git checkout -b feature/AmazingFeature`
3. 提交更改:`git commit -m 'Add some AmazingFeature'`
4. 推送到分支:`git push origin feature/AmazingFeature`
5. 開啟 Pull Request
## 📝 更新日誌
### v1.0.0 (2024-09-19)
- ✨ 初始版本發布
- 🎯 應用管理功能
- 🏆 競賽系統
- 👥 用戶管理
- 📊 統計儀表板
- 🔧 分頁功能優化
## 📄 許可證
此項目採用 MIT 許可證 - 查看 [LICENSE](LICENSE) 文件了解詳情。
## 📞 聯繫方式
如有問題或建議,請聯繫:
- 項目維護者:[您的姓名]
- 郵箱:[your-email@example.com]
- 項目地址:[repository-url]
---
**注意**: 這是一個內部使用的 AI 展示平台,請確保在生產環境中進行適當的安全配置。

View File

@@ -7,7 +7,7 @@ export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)
const page = parseInt(searchParams.get('page') || '1')
const limit = parseInt(searchParams.get('limit') || '10')
const limit = parseInt(searchParams.get('limit') || '5')
const search = searchParams.get('search') || ''
const category = searchParams.get('category') || 'all'
const type = searchParams.get('type') || 'all'

View File

@@ -165,7 +165,7 @@ export function AppManagement() {
})
const [pagination, setPagination] = useState({
page: 1,
limit: 10,
limit: 5,
total: 0,
totalPages: 0
})
@@ -608,7 +608,7 @@ export function AppManagement() {
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600"></p>
<p className="text-2xl font-bold">{apps.length}</p>
<p className="text-2xl font-bold">{stats.totalApps}</p>
</div>
<Bot className="w-8 h-8 text-blue-600" />
</div>
@@ -620,7 +620,7 @@ export function AppManagement() {
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600"></p>
<p className="text-2xl font-bold">{apps.filter((a) => a.status === "published").length}</p>
<p className="text-2xl font-bold">{stats.activeApps}</p>
</div>
<CheckCircle className="w-8 h-8 text-green-600" />
</div>
@@ -632,7 +632,7 @@ export function AppManagement() {
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-gray-600"></p>
<p className="text-2xl font-bold">{apps.filter((a) => a.status === "draft").length}</p>
<p className="text-2xl font-bold">{stats.inactiveApps}</p>
</div>
<XCircle className="w-8 h-8 text-red-600" />
</div>
@@ -695,7 +695,7 @@ export function AppManagement() {
{/* Apps Table */}
<Card>
<CardHeader>
<CardTitle> ({filteredApps.length})</CardTitle>
<CardTitle> ({pagination.total})</CardTitle>
<CardDescription> AI </CardDescription>
</CardHeader>
<CardContent>
@@ -842,6 +842,65 @@ export function AppManagement() {
</CardContent>
</Card>
{/* 分頁控制元件 */}
{pagination.totalPages > 1 && (
<div className="flex flex-col items-center space-y-4 mt-6">
<div className="text-sm text-gray-600">
{pagination.page} {pagination.totalPages} ( {pagination.total} )
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => setPagination(prev => ({ ...prev, page: Math.max(1, prev.page - 1) }))}
disabled={pagination.page === 1}
className="flex items-center space-x-1"
>
<span></span>
<span></span>
</Button>
<div className="flex items-center space-x-1">
{Array.from({ length: Math.min(pagination.totalPages, 5) }, (_, i) => {
let page;
if (pagination.totalPages <= 5) {
page = i + 1;
} else if (pagination.page <= 3) {
page = i + 1;
} else if (pagination.page >= pagination.totalPages - 2) {
page = pagination.totalPages - 4 + i;
} else {
page = pagination.page - 2 + i;
}
return (
<Button
key={page}
variant={pagination.page === page ? "default" : "outline"}
size="sm"
onClick={() => setPagination(prev => ({ ...prev, page }))}
className="w-10 h-10 p-0"
>
{page}
</Button>
)
})}
</div>
<Button
variant="outline"
size="sm"
onClick={() => setPagination(prev => ({ ...prev, page: Math.min(prev.totalPages, prev.page + 1) }))}
disabled={pagination.page === pagination.totalPages}
className="flex items-center space-x-1"
>
<span></span>
<span></span>
</Button>
</div>
</div>
)}
{/* Add App Dialog */}
<Dialog open={showAddApp} onOpenChange={setShowAddApp}>
<DialogContent className="max-w-4xl max-h-[85vh] overflow-y-auto">

View File

@@ -475,7 +475,7 @@ BEGIN
NEW.presentation_score +
NEW.impact_score
) / 5.0;
END//
END;
CREATE TRIGGER `calculate_app_total_score_update`
BEFORE UPDATE ON `app_judge_scores`
@@ -488,7 +488,7 @@ BEGIN
NEW.presentation_score +
NEW.impact_score
) / 5.0;
END//
END;
CREATE TRIGGER `calculate_proposal_total_score`
BEFORE INSERT ON `proposal_judge_scores`
@@ -501,7 +501,7 @@ BEGIN
NEW.impact_score +
NEW.presentation_score
) / 5.0;
END//
END;
CREATE TRIGGER `calculate_proposal_total_score_update`
BEFORE UPDATE ON `proposal_judge_scores`
@@ -514,9 +514,7 @@ BEGIN
NEW.impact_score +
NEW.presentation_score
) / 5.0;
END//
DELIMITER ;
END;
-- 視圖:用戶統計視圖
CREATE VIEW `user_statistics` AS

View File

@@ -1976,7 +1976,7 @@ export class AppService {
limit?: number;
} = {}): Promise<{ apps: any[]; total: number }> {
try {
const { search = '', category = 'all', type = 'all', status = 'all', page = 1, limit = 10 } = filters;
const { search = '', category = 'all', type = 'all', status = 'all', page = 1, limit = 5 } = filters;
// 構建查詢條件
let whereConditions: string[] = [];
@@ -2846,11 +2846,12 @@ export class AppService {
const appStatsSql = `
SELECT
COUNT(*) as total_apps,
COUNT(CASE WHEN is_active = TRUE THEN 1 END) as active_apps,
COUNT(CASE WHEN is_active = FALSE THEN 1 END) as inactive_apps,
COUNT(CASE WHEN created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) THEN 1 END) as new_apps_this_month,
COALESCE(SUM(views_count), 0) as total_views,
COALESCE(SUM(likes_count), 0) as total_likes
FROM apps
WHERE is_active = TRUE
`;
const appStats = await this.queryOne(appStatsSql);
@@ -2888,6 +2889,8 @@ export class AppService {
totalUsers: userStats.totalUsers,
activeUsers: userStats.activeUsers,
totalApps: appStats?.total_apps || 0,
activeApps: appStats?.active_apps || 0,
inactiveApps: appStats?.inactive_apps || 0,
totalCompetitions: competitionStats?.total_competitions || 0,
totalReviews: reviewStats?.total_reviews || 0,
totalViews: appStats?.total_views || 0,

View File

@@ -4,7 +4,7 @@
"private": true,
"scripts": {
"build": "next build",
"dev": "next dev",
"dev": "next dev -p 12016",
"lint": "next lint",
"start": "next start",
"migrate": "node scripts/migrate.js",

34
scripts/clear-database.js Normal file
View File

@@ -0,0 +1,34 @@
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);

141
scripts/migrate-fixed.js Normal file
View File

@@ -0,0 +1,141 @@
#!/usr/bin/env node
// =====================================================
// 資料庫遷移腳本 (修復版)
// =====================================================
const mysql = require('mysql2/promise');
const fs = require('fs');
const path = require('path');
// 資料庫配置
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 runMigration() {
let connection;
try {
console.log('🚀 開始資料庫遷移...');
// 創建連接
connection = await mysql.createConnection({
...dbConfig,
multipleStatements: true
});
console.log('✅ 資料庫連接成功');
// 讀取 SQL 文件
const sqlFile = path.join(__dirname, '..', 'database-schema-simple.sql');
const sqlContent = fs.readFileSync(sqlFile, 'utf8');
console.log('📖 讀取 SQL 文件成功');
// 處理 SQL 內容,正確分割語句
console.log('⚡ 處理 SQL 語句...');
// 先處理觸發器,因為它們包含分號
const triggerRegex = /CREATE TRIGGER[\s\S]*?END;/g;
const triggers = sqlContent.match(triggerRegex) || [];
// 移除觸發器部分,處理其他語句
let remainingSql = sqlContent.replace(triggerRegex, '');
// 分割其他語句
const otherStatements = remainingSql
.split(';')
.map(stmt => stmt.trim())
.filter(stmt => stmt.length > 0 && !stmt.startsWith('--'));
// 合併所有語句
const allStatements = [...otherStatements, ...triggers];
console.log(`📊 共找到 ${allStatements.length} 個語句`);
// 執行語句
for (let i = 0; i < allStatements.length; i++) {
const statement = allStatements[i];
if (statement.trim()) {
try {
// 特殊處理 USE 語句和觸發器
if (statement.toUpperCase().startsWith('USE') ||
statement.toUpperCase().startsWith('CREATE TRIGGER')) {
await connection.query(statement + ';');
} else {
await connection.execute(statement + ';');
}
console.log(`✅ 執行語句 ${i + 1}/${allStatements.length}`);
} catch (error) {
console.error(`❌ 語句 ${i + 1} 執行失敗:`, error.message);
console.error(`語句內容: ${statement.substring(0, 100)}...`);
// 對於某些錯誤,我們可以繼續執行
if (error.message.includes('already exists') ||
error.message.includes('Duplicate entry') ||
error.message.includes('Table') && error.message.includes('already exists')) {
console.log('⚠️ 跳過已存在的項目');
continue;
}
throw error;
}
}
}
console.log('✅ 資料庫結構創建成功!');
// 驗證表是否創建成功
console.log('🔍 驗證表結構...');
const [tables] = await connection.execute('SHOW TABLES');
console.log(`📊 共創建了 ${tables.length} 個表:`);
tables.forEach((table, index) => {
const tableName = Object.values(table)[0];
console.log(` ${index + 1}. ${tableName}`);
});
// 檢查視圖
console.log('🔍 驗證視圖...');
const [views] = await connection.execute('SHOW FULL TABLES WHERE Table_type = "VIEW"');
console.log(`📈 共創建了 ${views.length} 個視圖:`);
views.forEach((view, index) => {
const viewName = Object.values(view)[0];
console.log(` ${index + 1}. ${viewName}`);
});
// 檢查觸發器
console.log('🔍 驗證觸發器...');
const [triggerList] = await connection.execute('SHOW TRIGGERS');
console.log(`⚙️ 共創建了 ${triggerList.length} 個觸發器:`);
triggerList.forEach((trigger, index) => {
console.log(` ${index + 1}. ${trigger.Trigger}`);
});
console.log('🎉 資料庫遷移完成!');
} catch (error) {
console.error('❌ 遷移失敗:', error.message);
console.error('詳細錯誤:', error);
process.exit(1);
} finally {
if (connection) {
await connection.end();
console.log('🔌 資料庫連接已關閉');
}
}
}
// 執行遷移
if (require.main === module) {
runMigration().catch(console.error);
}
module.exports = { runMigration };

View File

@@ -0,0 +1,124 @@
#!/usr/bin/env node
// =====================================================
// 資料庫遷移腳本 (無觸發器版)
// =====================================================
const mysql = require('mysql2/promise');
const fs = require('fs');
const path = require('path');
// 資料庫配置
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 runMigration() {
let connection;
try {
console.log('🚀 開始資料庫遷移...');
// 創建連接
connection = await mysql.createConnection({
...dbConfig,
multipleStatements: true
});
console.log('✅ 資料庫連接成功');
// 讀取 SQL 文件
const sqlFile = path.join(__dirname, '..', 'database-schema-simple.sql');
const sqlContent = fs.readFileSync(sqlFile, 'utf8');
console.log('📖 讀取 SQL 文件成功');
// 移除觸發器部分
console.log('⚡ 處理 SQL 語句...');
const triggerRegex = /CREATE TRIGGER[\s\S]*?END;/g;
let sqlWithoutTriggers = sqlContent.replace(triggerRegex, '');
// 分割語句
const statements = sqlWithoutTriggers
.split(';')
.map(stmt => stmt.trim())
.filter(stmt => stmt.length > 0 && !stmt.startsWith('--'));
console.log(`📊 共找到 ${statements.length} 個語句`);
// 執行語句
for (let i = 0; i < statements.length; i++) {
const statement = statements[i];
if (statement.trim()) {
try {
// 特殊處理 USE 語句
if (statement.toUpperCase().startsWith('USE')) {
await connection.query(statement + ';');
} else {
await connection.execute(statement + ';');
}
console.log(`✅ 執行語句 ${i + 1}/${statements.length}`);
} catch (error) {
console.error(`❌ 語句 ${i + 1} 執行失敗:`, error.message);
console.error(`語句內容: ${statement.substring(0, 100)}...`);
// 對於某些錯誤,我們可以繼續執行
if (error.message.includes('already exists') ||
error.message.includes('Duplicate entry') ||
error.message.includes('Table') && error.message.includes('already exists')) {
console.log('⚠️ 跳過已存在的項目');
continue;
}
throw error;
}
}
}
console.log('✅ 資料庫結構創建成功!');
// 驗證表是否創建成功
console.log('🔍 驗證表結構...');
const [tables] = await connection.execute('SHOW TABLES');
console.log(`📊 共創建了 ${tables.length} 個表:`);
tables.forEach((table, index) => {
const tableName = Object.values(table)[0];
console.log(` ${index + 1}. ${tableName}`);
});
// 檢查視圖
console.log('🔍 驗證視圖...');
const [views] = await connection.execute('SHOW FULL TABLES WHERE Table_type = "VIEW"');
console.log(`📈 共創建了 ${views.length} 個視圖:`);
views.forEach((view, index) => {
const viewName = Object.values(view)[0];
console.log(` ${index + 1}. ${viewName}`);
});
console.log('🎉 資料庫遷移完成!');
console.log('⚠️ 注意:觸發器由於權限限制未創建,但基本表結構已就緒');
} catch (error) {
console.error('❌ 遷移失敗:', error.message);
console.error('詳細錯誤:', error);
process.exit(1);
} finally {
if (connection) {
await connection.end();
console.log('🔌 資料庫連接已關閉');
}
}
}
// 執行遷移
if (require.main === module) {
runMigration().catch(console.error);
}
module.exports = { runMigration };

View File

@@ -0,0 +1,319 @@
#!/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 };