實作前台 APP 呈現、APP 詳細頁面

This commit is contained in:
2025-09-10 00:27:26 +08:00
parent 900e33aefa
commit bc2104d374
17 changed files with 318 additions and 1565 deletions

View File

@@ -1,296 +0,0 @@
# AI智能助手 (ChatBot) 組件分析
## 1. 組件概述
### 1.1 功能定位
AI智能助手是一個內嵌的聊天機器人組件為用戶提供即時的系統使用指導和問題解答服務。
### 1.2 核心特性
- **即時對話**: 與AI助手進行自然語言對話
- **智能回答**: 基於DeepSeek API的智能回應
- **快速問題**: 提供相關問題的快速選擇
- **上下文記憶**: 保持對話的連續性
## 2. 技術實現
### 2.1 技術棧
```typescript
// 核心技術
- React 19 (Hooks)
- TypeScript 5
- DeepSeek Chat API
- Tailwind CSS
- shadcn/ui 組件庫
```
### 2.2 組件結構
```typescript
// 主要接口定義
interface Message {
id: string
text: string
sender: "user" | "bot"
timestamp: Date
quickQuestions?: string[]
}
// 組件狀態
const [isOpen, setIsOpen] = useState(false) // 對話框開關
const [messages, setMessages] = useState<Message[]>() // 訊息列表
const [inputValue, setInputValue] = useState("") // 輸入值
const [isTyping, setIsTyping] = useState(false) // 打字狀態
const [isLoading, setIsLoading] = useState(false) // 載入狀態
```
### 2.3 API整合
```typescript
// DeepSeek API 配置
const DEEPSEEK_API_KEY = "sk-3640dcff23fe4a069a64f536ac538d75"
const DEEPSEEK_API_URL = "https://api.deepseek.com/v1/chat/completions"
// API 調用函數
const callDeepSeekAPI = async (userMessage: string): Promise<string> => {
// 實現細節...
}
```
## 3. 功能詳解
### 3.1 對話能力
#### 3.1.1 前台功能指導
- **註冊流程**: 如何註冊參賽團隊
- **作品提交**: 如何提交和管理作品
- **投票系統**: 如何參與投票和收藏
- **個人中心**: 如何管理個人資料
#### 3.1.2 後台管理協助
- **競賽創建**: 如何創建和管理競賽
- **評審管理**: 如何管理評審團成員
- **評分系統**: 如何設定評分標準
- **獎項設定**: 如何配置獎項類型
#### 3.1.3 系統使用指南
- **操作步驟**: 提供具體的操作指引
- **常見問題**: 解答用戶常見疑問
- **最佳實踐**: 推薦最佳使用方法
### 3.2 智能特性
#### 3.2.1 內容清理
```typescript
const cleanResponse = (text: string): string => {
return text
// 移除 Markdown 格式
.replace(/\*\*(.*?)\*\*/g, '$1')
.replace(/\*(.*?)\*/g, '$1')
.replace(/`(.*?)`/g, '$1')
.replace(/#{1,6}\s/g, '')
.replace(/^- /g, '• ')
.replace(/^\d+\.\s/g, '')
// 移除多餘空行
.replace(/\n\s*\n\s*\n/g, '\n\n')
// 限制文字長度
.slice(0, 300)
.trim()
}
```
#### 3.2.2 快速問題生成
```typescript
const generateQuickQuestions = (userQuestion: string): string[] => {
const question = userQuestion.toLowerCase()
// 根據問題類型生成相關建議
if (question.includes('註冊') || question.includes('團隊')) {
return [
"如何提交作品?",
"怎麼查看競賽詳情?",
"如何收藏作品?",
"怎麼進行投票?"
]
}
// 更多邏輯...
}
```
### 3.3 用戶體驗
#### 3.3.1 界面設計
- **浮動按鈕**: 固定在右下角的聊天入口
- **模態對話框**: 全屏遮罩的聊天界面
- **響應式設計**: 適配不同螢幕尺寸
- **無障礙設計**: 支持鍵盤導航
#### 3.3.2 交互體驗
- **即時反饋**: 輸入狀態和載入動畫
- **自動滾動**: 新訊息自動滾動到底部
- **快捷操作**: Enter鍵發送訊息
- **錯誤處理**: 網路錯誤的優雅處理
## 4. 系統提示詞 (System Prompt)
### 4.1 提示詞結構
```typescript
const systemPrompt = `你是一個競賽管理系統的AI助手專門幫助用戶了解如何使用這個系統。
系統功能包括:
後台管理功能:
1. 競賽管理 - 創建、編輯、刪除競賽
2. 評審管理 - 管理評審團成員
3. 評分系統 - 手動輸入評分或讓評審自行評分
4. 團隊管理 - 管理參賽團隊
5. 獎項管理 - 設定各種獎項
6. 評審連結 - 提供評審登入連結
前台功能:
1. 競賽瀏覽 - 查看所有競賽資訊和詳細內容
2. 團隊註冊 - 如何註冊參賽團隊和提交作品
3. 作品展示 - 瀏覽參賽作品和投票功能
4. 排行榜 - 查看人氣排行榜和得獎名單
5. 個人中心 - 管理個人資料和參賽記錄
6. 收藏功能 - 如何收藏喜歡的作品
7. 評論系統 - 如何對作品進行評論和互動
8. 搜尋功能 - 如何搜尋特定競賽或作品
9. 通知系統 - 查看競賽更新和個人通知
10. 幫助中心 - 常見問題和使用指南
請用友善、專業的語氣回答用戶問題,並提供具體的操作步驟。回答要簡潔明瞭,避免過長的文字。
重要請不要使用任何Markdown格式只使用純文字回答。不要使用**、*、#、-等符號。
回答時請使用繁體中文。`
```
### 4.2 回答規範
- **語言**: 繁體中文
- **格式**: 純文字無Markdown
- **長度**: 限制在300字以內
- **語氣**: 友善、專業
- **內容**: 具體操作步驟
## 5. 錯誤處理
### 5.1 API錯誤處理
```typescript
try {
const response = await fetch(DEEPSEEK_API_URL, {
// API 調用配置...
})
if (!response.ok) {
throw new Error(`API request failed: ${response.status}`)
}
const data = await response.json()
return cleanResponse(data.choices[0]?.message?.content || "抱歉,我現在無法回答您的問題,請稍後再試。")
} catch (error) {
console.error("DeepSeek API error:", error)
return "抱歉我現在無法連接到AI服務請檢查網路連接或稍後再試。"
}
```
### 5.2 用戶體驗錯誤處理
- **網路錯誤**: 提示檢查網路連接
- **API限制**: 提示稍後再試
- **輸入驗證**: 防止空訊息發送
- **載入狀態**: 防止重複發送
## 6. 性能優化
### 6.1 API優化
```typescript
// 限制token數量以獲得更簡潔的回答
max_tokens: 200,
temperature: 0.7
```
### 6.2 組件優化
- **訊息虛擬化**: 大量訊息時的效能優化
- **防抖處理**: 避免頻繁API調用
- **記憶化**: 重複問題的快取處理
- **懶加載**: 按需載入組件
## 7. 安全考量
### 7.1 API密鑰安全
- **環境變數**: API密鑰存儲在環境變數中
- **加密存儲**: 敏感資訊加密處理
- **訪問控制**: 限制API調用頻率
### 7.2 數據隱私
- **聊天記錄**: 本地存儲,不上傳服務器
- **個人資訊**: 不收集敏感個人資訊
- **數據清理**: 定期清理過期數據
## 8. 擴展性設計
### 8.1 多語言支持
```typescript
interface LocalizationConfig {
language: string
systemPrompt: Record<string, string>
quickQuestions: Record<string, string[]>
errorMessages: Record<string, string>
}
```
### 8.2 多AI模型支持
```typescript
interface AIModelConfig {
provider: 'deepseek' | 'openai' | 'anthropic'
model: string
apiKey: string
apiUrl: string
maxTokens: number
temperature: number
}
```
### 8.3 自定義功能
- **知識庫整合**: 連接企業知識庫
- **FAQ系統**: 自動回答常見問題
- **工單系統**: 複雜問題轉人工處理
- **分析報告**: 聊天數據分析
## 9. 使用指南
### 9.1 基本使用
1. 點擊右下角的聊天按鈕
2. 在輸入框中輸入問題
3. 按Enter鍵或點擊發送按鈕
4. 查看AI助手的回答
5. 點擊快速問題進行後續對話
### 9.2 進階功能
- **上下文記憶**: 對話會保持上下文
- **快速問題**: 點擊建議問題快速提問
- **錯誤重試**: 網路錯誤時可重新發送
- **對話重置**: 關閉重開可開始新對話
### 9.3 最佳實踐
- **具體問題**: 提出具體明確的問題
- **分步驟**: 複雜操作分步驟詢問
- **耐心等待**: AI需要時間處理複雜問題
- **反饋提供**: 對回答不滿意時可重新提問
## 10. 未來規劃
### 10.1 短期目標
- [ ] 添加語音輸入功能
- [ ] 支持圖片上傳和識別
- [ ] 增加更多快速問題模板
- [ ] 優化回答品質和速度
### 10.2 長期目標
- [ ] 整合企業知識庫
- [ ] 支持多語言對話
- [ ] 添加情感分析功能
- [ ] 實現智能推薦系統
---
**文檔版本**: v1.0
**最後更新**: 2024年12月
**負責人**: 前端開發團隊

View File

@@ -1,670 +0,0 @@
# AI展示平台軟體規格書
## 1. 專案概述
### 1.1 專案名稱
AI展示平台 (AI Showcase Platform)
### 1.2 專案目標
建立一個企業內部的AI應用展示、競賽管理和評審系統促進AI技術的創新與應用。
### 1.3 專案範圍
- 用戶認證與權限管理
- AI應用展示與管理
- 競賽系統與評審流程
- 團隊協作與提案管理
- 數據分析與報表生成
- 管理員後台系統
- AI智能助手系統
## 2. 系統架構
### 2.1 技術棧
#### 前端技術
- **框架**: Next.js 15.2.4 (App Router)
- **語言**: TypeScript 5
- **UI庫**:
- Radix UI (無障礙組件)
- shadcn/ui (設計系統)
- Tailwind CSS (樣式框架)
- **狀態管理**: React Context API
- **表單處理**: React Hook Form + Zod
- **圖表**: Recharts
- **包管理器**: pnpm
#### 開發工具
- **代碼品質**: ESLint + TypeScript
- **樣式處理**: PostCSS + Tailwind CSS
- **圖標**: Lucide React
- **版本控制**: Git
### 2.2 目錄結構
```
ai-showcase-platform/
├── app/ # Next.js App Router
│ ├── admin/ # 管理員頁面
│ ├── competition/ # 競賽頁面
│ ├── judge-scoring/ # 評審評分頁面
│ ├── register/ # 註冊頁面
│ └── globals.css # 全域樣式
├── components/ # React 組件
│ ├── admin/ # 管理員專用組件
│ ├── auth/ # 認證相關組件
│ ├── competition/ # 競賽相關組件
│ ├── reviews/ # 評論系統組件
│ ├── chat-bot.tsx # AI智能助手組件
│ └── ui/ # 通用UI組件
├── contexts/ # React Context
│ ├── auth-context.tsx # 認證狀態管理
│ └── competition-context.tsx # 競賽狀態管理
├── hooks/ # 自定義 Hooks
├── lib/ # 工具函數
├── types/ # TypeScript 類型定義
└── public/ # 靜態資源
```
## 3. 功能需求
### 3.1 用戶管理系統
#### 3.1.1 用戶角色
- **一般用戶 (user)**: 瀏覽應用、參與投票
- **開發者 (developer)**: 提交AI應用、參與競賽
- **管理員 (admin)**: 系統管理、數據分析
#### 3.1.2 用戶功能
- 註冊/登入/登出
- 個人資料管理
- 收藏應用
- 按讚功能 (每日限制)
- 瀏覽記錄
- 權限控制
### 3.2 競賽系統
#### 3.2.1 競賽類型
- **個人賽 (individual)**: 個人開發者競賽
- **團隊賽 (team)**: 團隊協作競賽
- **提案賽 (proposal)**: 創新提案競賽
- **混合賽 (mixed)**: 綜合性競賽
#### 3.2.2 競賽狀態
- **upcoming**: 即將開始
- **active**: 進行中
- **judging**: 評審中
- **completed**: 已完成
#### 3.2.3 評審系統
- 多維度評分 (創新性、技術性、實用性、展示效果、影響力)
- 評審管理
- 分數統計與排名
- 評審意見記錄
### 3.3 獎項系統
#### 3.3.1 獎項類型
- **金獎/銀獎/銅獎**: 排名獎項
- **最佳創新獎**: 創新性獎項
- **最佳技術獎**: 技術實現獎項
- **人氣獎**: 受歡迎程度獎項
- **自定義獎項**: 可配置的獎項
#### 3.3.2 獎項分類
- **innovation**: 創新類
- **technical**: 技術類
- **practical**: 實用類
- **popular**: 人氣類
- **teamwork**: 團隊協作類
- **solution**: 解決方案類
- **creativity**: 創意類
### 3.4 管理員系統
#### 3.4.1 用戶管理
- 用戶列表查看
- 用戶權限管理
- 用戶資料編輯
- 用戶統計分析
#### 3.4.2 競賽管理
- 競賽創建與編輯
- 競賽狀態管理
- 參賽者管理
- 評審分配
#### 3.4.3 評審管理
- 評審帳號管理
- 評審分配
- 評分進度追蹤
- 評審意見管理
#### 3.4.4 數據分析
- 競賽統計
- 用戶活躍度分析
- 應用熱度分析
- 評分趨勢分析
### 3.5 AI智能助手系統
#### 3.5.1 核心功能
- **即時對話**: 與AI助手進行自然語言對話
- **智能回答**: 基於DeepSeek API的智能回應
- **快速問題**: 提供相關問題的快速選擇
- **上下文記憶**: 保持對話的連續性
#### 3.5.2 對話能力
- **前台功能指導**: 註冊、提交作品、投票、收藏等
- **後台管理協助**: 競賽創建、評審管理、評分系統等
- **系統使用指南**: 提供具體的操作步驟
- **問題分類處理**: 根據問題類型提供相關建議
#### 3.5.3 用戶體驗
- **浮動按鈕**: 固定在右下角的聊天入口
- **模態對話框**: 全屏遮罩的聊天界面
- **即時反饋**: 輸入狀態和載入動畫
- **響應式設計**: 適配不同螢幕尺寸
#### 3.5.4 技術特性
- **API整合**: 與DeepSeek Chat API無縫整合
- **內容清理**: 自動清理Markdown格式和過長文字
- **錯誤處理**: 網路錯誤和API錯誤的優雅處理
- **性能優化**: 限制token數量以獲得更簡潔的回答
## 4. 數據模型
### 4.1 用戶模型
```typescript
interface User {
id: string
name: string
email: string
avatar?: string
department: string
role: "user" | "developer" | "admin"
joinDate: string
favoriteApps: string[]
recentApps: string[]
totalLikes: number
totalViews: number
}
```
### 4.2 競賽模型
```typescript
interface Competition {
id: string
name: string
year: number
month: number
startDate: string
endDate: string
status: "upcoming" | "active" | "judging" | "completed"
description: string
type: "individual" | "team" | "mixed" | "proposal"
judges: string[]
participatingApps: string[]
participatingTeams: string[]
participatingProposals: string[]
rules: CompetitionRule[]
awardTypes: CompetitionAwardType[]
evaluationFocus: string
maxTeamSize?: number
}
```
### 4.3 評審模型
```typescript
interface Judge {
id: string
name: string
title: string
department: string
expertise: string[]
avatar?: string
}
interface JudgeScore {
judgeId: string
appId: string
scores: {
innovation: number
technical: number
usability: number
presentation: number
impact: number
}
comments: string
submittedAt: string
}
```
### 4.4 團隊模型
```typescript
interface TeamMember {
id: string
name: string
department: string
role: string
}
interface Team {
id: string
name: string
members: TeamMember[]
leader: string
department: string
contactEmail: string
apps: string[]
totalLikes: number
}
```
### 4.5 獎項模型
```typescript
interface Award {
id: string
competitionId: string
appId?: string
teamId?: string
proposalId?: string
appName?: string
teamName?: string
proposalTitle?: string
creator: string
awardType: "gold" | "silver" | "bronze" | "popular" | "innovation" | "technical" | "custom"
awardName: string
score: number
year: number
month: number
icon: string
customAwardTypeId?: string
competitionType: "individual" | "team" | "proposal"
rank: number
category: "innovation" | "technical" | "practical" | "popular" | "teamwork" | "solution" | "creativity"
}
```
### 4.6 AI助手模型
```typescript
interface Message {
id: string
text: string
sender: "user" | "bot"
timestamp: Date
quickQuestions?: string[]
}
interface ChatSession {
id: string
userId: string
messages: Message[]
createdAt: Date
updatedAt: Date
}
interface AIAssistantConfig {
apiKey: string
apiUrl: string
model: string
maxTokens: number
temperature: number
systemPrompt: string
}
```
## 5. API 設計
### 5.1 認證 API
```
POST /api/auth/login # 用戶登入
POST /api/auth/register # 用戶註冊
POST /api/auth/logout # 用戶登出
GET /api/auth/profile # 獲取用戶資料
PUT /api/auth/profile # 更新用戶資料
```
### 5.2 競賽 API
```
GET /api/competitions # 獲取競賽列表
POST /api/competitions # 創建競賽
GET /api/competitions/:id # 獲取競賽詳情
PUT /api/competitions/:id # 更新競賽
DELETE /api/competitions/:id # 刪除競賽
GET /api/competitions/:id/scores # 獲取競賽評分
POST /api/competitions/:id/scores # 提交評分
```
### 5.3 用戶 API
```
GET /api/users # 獲取用戶列表
GET /api/users/:id # 獲取用戶詳情
PUT /api/users/:id # 更新用戶資料
DELETE /api/users/:id # 刪除用戶
GET /api/users/:id/apps # 獲取用戶應用
GET /api/users/:id/teams # 獲取用戶團隊
```
### 5.4 評審 API
```
GET /api/judges # 獲取評審列表
POST /api/judges # 創建評審帳號
GET /api/judges/:id # 獲取評審詳情
PUT /api/judges/:id # 更新評審資料
DELETE /api/judges/:id # 刪除評審
GET /api/judges/:id/scores # 獲取評審評分
POST /api/judges/:id/scores # 提交評審評分
```
### 5.5 團隊 API
```
GET /api/teams # 獲取團隊列表
POST /api/teams # 創建團隊
GET /api/teams/:id # 獲取團隊詳情
PUT /api/teams/:id # 更新團隊資料
DELETE /api/teams/:id # 刪除團隊
GET /api/teams/:id/members # 獲取團隊成員
POST /api/teams/:id/members # 添加團隊成員
```
### 5.6 獎項 API
```
GET /api/awards # 獲取獎項列表
POST /api/awards # 創建獎項
GET /api/awards/:id # 獲取獎項詳情
PUT /api/awards/:id # 更新獎項
DELETE /api/awards/:id # 刪除獎項
GET /api/awards/by-year/:year # 按年份獲取獎項
GET /api/awards/by-type/:type # 按類型獲取獎項
```
### 5.7 AI助手 API
```
POST /api/chat/send # 發送聊天訊息
GET /api/chat/history # 獲取聊天歷史
DELETE /api/chat/history # 清除聊天歷史
POST /api/chat/feedback # 提交聊天反饋
GET /api/chat/quick-questions # 獲取快速問題建議
```
## 6. 數據庫設計
### 6.1 用戶表 (users)
```sql
CREATE TABLE users (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
avatar VARCHAR(500),
department VARCHAR(100) NOT NULL,
role ENUM('user', 'developer', 'admin') DEFAULT 'user',
join_date DATE NOT NULL,
total_likes INT DEFAULT 0,
total_views INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```
### 6.2 競賽表 (competitions)
```sql
CREATE TABLE competitions (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(200) NOT NULL,
year INT NOT NULL,
month INT NOT NULL,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
status ENUM('upcoming', 'active', 'judging', 'completed') DEFAULT 'upcoming',
description TEXT,
type ENUM('individual', 'team', 'mixed', 'proposal') NOT NULL,
evaluation_focus TEXT,
max_team_size INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```
### 6.3 評審表 (judges)
```sql
CREATE TABLE judges (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(100) NOT NULL,
title VARCHAR(100) NOT NULL,
department VARCHAR(100) NOT NULL,
expertise JSON,
avatar VARCHAR(500),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```
### 6.4 評分表 (judge_scores)
```sql
CREATE TABLE judge_scores (
id VARCHAR(36) PRIMARY KEY,
judge_id VARCHAR(36) NOT NULL,
app_id VARCHAR(36),
proposal_id VARCHAR(36),
scores JSON NOT NULL,
comments TEXT,
submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (judge_id) REFERENCES judges(id),
FOREIGN KEY (app_id) REFERENCES apps(id),
FOREIGN KEY (proposal_id) REFERENCES proposals(id)
);
```
### 6.5 團隊表 (teams)
```sql
CREATE TABLE teams (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(200) NOT NULL,
leader_id VARCHAR(36) NOT NULL,
department VARCHAR(100) NOT NULL,
contact_email VARCHAR(255) NOT NULL,
total_likes INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (leader_id) REFERENCES users(id)
);
```
### 6.6 團隊成員表 (team_members)
```sql
CREATE TABLE team_members (
id VARCHAR(36) PRIMARY KEY,
team_id VARCHAR(36) NOT NULL,
user_id VARCHAR(36) NOT NULL,
role VARCHAR(50) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (team_id) REFERENCES teams(id),
FOREIGN KEY (user_id) REFERENCES users(id)
);
```
### 6.7 應用表 (apps)
```sql
CREATE TABLE apps (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(200) NOT NULL,
description TEXT,
creator_id VARCHAR(36) NOT NULL,
team_id VARCHAR(36),
likes_count INT DEFAULT 0,
views_count INT DEFAULT 0,
rating DECIMAL(3,2) DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (creator_id) REFERENCES users(id),
FOREIGN KEY (team_id) REFERENCES teams(id)
);
```
### 6.8 獎項表 (awards)
```sql
CREATE TABLE awards (
id VARCHAR(36) PRIMARY KEY,
competition_id VARCHAR(36) NOT NULL,
app_id VARCHAR(36),
team_id VARCHAR(36),
proposal_id VARCHAR(36),
award_type ENUM('gold', 'silver', 'bronze', 'popular', 'innovation', 'technical', 'custom') NOT NULL,
award_name VARCHAR(200) NOT NULL,
score DECIMAL(5,2) NOT NULL,
year INT NOT NULL,
month INT NOT NULL,
icon VARCHAR(50),
custom_award_type_id VARCHAR(36),
competition_type ENUM('individual', 'team', 'proposal') NOT NULL,
rank INT DEFAULT 0,
category ENUM('innovation', 'technical', 'practical', 'popular', 'teamwork', 'solution', 'creativity') NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (competition_id) REFERENCES competitions(id),
FOREIGN KEY (app_id) REFERENCES apps(id),
FOREIGN KEY (team_id) REFERENCES teams(id)
);
```
### 6.9 聊天會話表 (chat_sessions)
```sql
CREATE TABLE chat_sessions (
id VARCHAR(36) PRIMARY KEY,
user_id VARCHAR(36) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
```
### 6.10 聊天訊息表 (chat_messages)
```sql
CREATE TABLE chat_messages (
id VARCHAR(36) PRIMARY KEY,
session_id VARCHAR(36) NOT NULL,
text TEXT NOT NULL,
sender ENUM('user', 'bot') NOT NULL,
quick_questions JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (session_id) REFERENCES chat_sessions(id)
);
```
### 6.11 AI助手配置表 (ai_assistant_configs)
```sql
CREATE TABLE ai_assistant_configs (
id VARCHAR(36) PRIMARY KEY,
api_key VARCHAR(255) NOT NULL,
api_url VARCHAR(500) NOT NULL,
model VARCHAR(100) NOT NULL,
max_tokens INT DEFAULT 200,
temperature DECIMAL(3,2) DEFAULT 0.7,
system_prompt TEXT NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```
## 7. 非功能性需求
### 7.1 性能需求
- 頁面載入時間 < 3秒
- 支持同時1000+用戶在線
- 數據庫查詢響應時間 < 500ms
- 圖片優化和CDN加速
- AI助手回應時間 < 5秒
- 聊天訊息實時更新
### 7.2 安全需求
- 用戶密碼加密存儲
- JWT Token認證
- CSRF防護
- XSS防護
- SQL注入防護
- 權限驗證
- AI API密鑰安全存儲
- 聊天數據隱私保護
### 7.3 可用性需求
- 系統可用性 > 99.5%
- 響應式設計,支持多設備
- 無障礙設計 (WCAG 2.1)
- 多語言支持準備
### 7.4 可維護性需求
- 模組化架構
- 完整的API文檔
- 代碼註釋和文檔
- 單元測試覆蓋率 > 80%
- 錯誤日誌和監控
## 8. 部署架構
### 8.1 開發環境
- **前端**: Next.js 開發服務器
- **後端**: Node.js/Express 或 Python/FastAPI
- **數據庫**: PostgreSQL 或 MySQL
- **緩存**: Redis
- **文件存儲**: 本地存儲或雲存儲
### 8.2 生產環境
- **前端**: Vercel 或 AWS S3 + CloudFront
- **後端**: AWS EC2 或 Docker 容器
- **數據庫**: AWS RDS 或自建數據庫
- **緩存**: AWS ElastiCache (Redis)
- **文件存儲**: AWS S3
- **CDN**: CloudFront 或 Cloudflare
## 9. 開發計劃
### 9.1 第一階段 (4週)
- [x] 前端架構搭建
- [x] 基礎組件開發
- [x] 認證系統實現
- [x] 競賽管理基礎功能
### 9.2 第二階段 (4週)
- [ ] 後端API開發
- [ ] 數據庫設計與實現
- [ ] 評審系統完善
- [ ] 獎項系統實現
### 9.3 第三階段 (3週)
- [ ] 數據分析功能
- [ ] 管理員後台完善
- [ ] 性能優化
- [ ] 安全加固
### 9.4 第四階段 (2週)
- [ ] 測試與調試
- [ ] 文檔完善
- [ ] 部署上線
- [ ] 用戶培訓
## 10. 風險評估
### 10.1 技術風險
- **數據庫性能**: 大量數據查詢可能影響性能
- **並發處理**: 高並發場景下的數據一致性
- **安全性**: 用戶數據保護和系統安全
### 10.2 項目風險
- **時間風險**: 開發進度可能延遲
- **需求變更**: 功能需求可能調整
- **資源風險**: 開發資源不足
### 10.3 緩解措施
- 採用成熟的技術棧
- 實施敏捷開發方法
- 建立完善的測試體系
- 制定詳細的項目計劃
- 定期進行代碼審查
---
**文檔版本**: v1.0
**最後更新**: 2025年07月
**負責人**: 敏捷小組 - 佩庭
**審核人**: 強茂集團

71
app/api/apps/route.ts Normal file
View File

@@ -0,0 +1,71 @@
import { NextRequest, NextResponse } from 'next/server'
import { AppService } from '@/lib/services/database-service'
const appService = new AppService()
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') || '6')
const search = searchParams.get('search') || ''
const category = searchParams.get('category') || 'all'
const type = searchParams.get('type') || 'all'
const department = searchParams.get('department') || 'all'
// 獲取應用列表(只顯示已發布的應用)
const { apps, total } = await appService.getAllApps({
search,
category,
type,
status: 'published', // 只顯示已發布的應用
page,
limit
})
// 獲取部門列表用於篩選
const departments = await appService.getAppDepartments()
return NextResponse.json({
success: true,
data: {
apps: apps.map(app => ({
id: app.id,
name: app.name,
description: app.description,
category: app.category,
type: app.type,
views: app.views_count || 0,
likes: app.likes_count || 0,
rating: app.rating || 0,
reviewCount: app.reviewCount || 0,
creator: app.creator_name || '未知',
department: app.creator_department || '未知',
createdAt: app.created_at ? new Date(app.created_at).toLocaleDateString('zh-TW') : '-',
icon: app.icon || 'Bot',
iconColor: app.icon_color || 'from-blue-500 to-purple-500',
appUrl: app.app_url,
isActive: app.is_active
})),
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit)
},
departments: departments.map(dept => ({
value: dept.department,
label: dept.department,
count: dept.count
}))
}
})
} catch (error) {
console.error('獲取應用列表錯誤:', error)
return NextResponse.json(
{ success: false, error: '獲取應用列表時發生錯誤' },
{ status: 500 }
)
}
}

View File

@@ -1,6 +1,6 @@
"use client" "use client"
import { useState } from "react" import { useState, useEffect } from "react"
import { useAuth } from "@/contexts/auth-context" import { useAuth } from "@/contexts/auth-context"
import { useCompetition } from "@/contexts/competition-context" import { useCompetition } from "@/contexts/competition-context"
import { import {
@@ -47,8 +47,7 @@ import { AwardDetailDialog } from "@/components/competition/award-detail-dialog"
import { LikeButton } from "@/components/like-button" import { LikeButton } from "@/components/like-button"
// AI applications data - empty for production // AI applications data - will be loaded from API
const aiApps: any[] = []
// Pagination component // Pagination component
interface PaginationProps { interface PaginationProps {
@@ -156,6 +155,15 @@ export default function AIShowcasePlatform() {
const [showCompetition, setShowCompetition] = useState(false) const [showCompetition, setShowCompetition] = useState(false)
const appsPerPage = 6 const appsPerPage = 6
// 新增狀態管理
const [aiApps, setAiApps] = useState<any[]>([])
const [isLoadingApps, setIsLoadingApps] = useState(true)
const [totalPages, setTotalPages] = useState(0)
const [totalApps, setTotalApps] = useState(0)
const [departments, setDepartments] = useState<{value: string, label: string, count: number}[]>([])
const [types, setTypes] = useState<{value: string, label: string, count: number}[]>([])
const [categories, setCategories] = useState<{value: string, label: string, count: number}[]>([])
// Competition page states // Competition page states
const [selectedCompetitionTypeFilter, setSelectedCompetitionTypeFilter] = useState("all") const [selectedCompetitionTypeFilter, setSelectedCompetitionTypeFilter] = useState("all")
const [selectedMonthFilter, setSelectedMonthFilter] = useState("all") const [selectedMonthFilter, setSelectedMonthFilter] = useState("all")
@@ -170,21 +178,61 @@ export default function AIShowcasePlatform() {
const [showAwardDetail, setShowAwardDetail] = useState(false) const [showAwardDetail, setShowAwardDetail] = useState(false)
const [selectedAward, setSelectedAward] = useState<any>(null) const [selectedAward, setSelectedAward] = useState<any>(null)
const filteredApps = aiApps.filter((app) => { // 載入應用數據
const matchesSearch = const loadApps = async () => {
app.name.toLowerCase().includes(searchTerm.toLowerCase()) || try {
app.description.toLowerCase().includes(searchTerm.toLowerCase()) setIsLoadingApps(true)
const matchesDepartment = selectedDepartment === "all" || app.department === selectedDepartment const params = new URLSearchParams({
const matchesType = selectedType === "all" || app.type === selectedType page: currentPage.toString(),
limit: appsPerPage.toString(),
return matchesSearch && matchesDepartment && matchesType search: searchTerm,
department: selectedDepartment,
type: selectedType
}) })
// Pagination logic const response = await fetch(`/api/apps?${params}`)
const totalPages = Math.ceil(filteredApps.length / appsPerPage) const data = await response.json()
const startIndex = (currentPage - 1) * appsPerPage
const endIndex = startIndex + appsPerPage if (data.success) {
const currentApps = filteredApps.slice(startIndex, endIndex) setAiApps(data.data.apps)
setTotalPages(data.data.pagination.totalPages)
setTotalApps(data.data.pagination.total)
setDepartments(data.data.departments || [])
} else {
console.error('載入應用數據失敗:', data.error)
setAiApps([])
}
} catch (error) {
console.error('載入應用數據錯誤:', error)
setAiApps([])
} finally {
setIsLoadingApps(false)
}
}
// 載入篩選選項
const loadFilterOptions = async () => {
try {
const response = await fetch('/api/apps?page=1&limit=1')
const data = await response.json()
if (data.success) {
setDepartments(data.data.departments || [])
// 可以添加更多篩選選項的載入
}
} catch (error) {
console.error('載入篩選選項錯誤:', error)
}
}
// 初始載入
useEffect(() => {
loadApps()
loadFilterOptions()
}, [currentPage, searchTerm, selectedDepartment, selectedType])
// 當前顯示的應用(已經由 API 處理分頁和篩選)
const currentApps = aiApps
// Reset to first page when filters change // Reset to first page when filters change
const handleFilterChange = (filterType: string, value: string) => { const handleFilterChange = (filterType: string, value: string) => {
@@ -201,6 +249,40 @@ export default function AIShowcasePlatform() {
setSearchTerm(value) setSearchTerm(value)
} }
const handlePageChange = (page: number) => {
setCurrentPage(page)
}
// 圖標映射函數
const getIconComponent = (iconName: string) => {
const iconMap: { [key: string]: any } = {
'Bot': Brain,
'ImageIcon': ImageIcon,
'Mic': Mic,
'MessageSquare': MessageSquare,
'Settings': Settings,
'Zap': Zap,
'TrendingUp': TrendingUp,
'Star': Star,
'Heart': Heart,
'Eye': Eye,
'Trophy': Trophy,
'Award': Award,
'Medal': Medal,
'Target': Target,
'Users': Users,
'Lightbulb': Lightbulb,
'Search': Search,
'Plus': Plus,
'X': X,
'ChevronLeft': ChevronLeft,
'ChevronRight': ChevronRight,
'ArrowLeft': ArrowLeft
}
return iconMap[iconName] || Brain // 預設使用 Brain 圖標
}
const getTypeColor = (type: string) => { const getTypeColor = (type: string) => {
const colors = { const colors = {
: "bg-blue-100 text-blue-800 border-blue-200", : "bg-blue-100 text-blue-800 border-blue-200",
@@ -866,19 +948,30 @@ export default function AIShowcasePlatform() {
{/* Results summary */} {/* Results summary */}
<div className="mt-4 text-sm text-gray-600"> <div className="mt-4 text-sm text-gray-600">
{filteredApps.length} {currentPage} {totalPages} {totalApps} {currentPage} {totalPages}
</div> </div>
</div> </div>
{/* All Apps with Pagination */} {/* All Apps with Pagination */}
<div> <div>
<h3 className="text-2xl font-bold text-gray-900 mb-6"> ({filteredApps.length})</h3> <h3 className="text-2xl font-bold text-gray-900 mb-6">
({totalApps})
{isLoadingApps && <span className="text-sm text-gray-500 ml-2">...</span>}
</h3>
{currentApps.length > 0 ? ( {isLoadingApps ? (
<div className="text-center py-12">
<div className="text-gray-400 mb-4">
<Search className="w-16 h-16 mx-auto animate-pulse" />
</div>
<h3 className="text-xl font-semibold text-gray-600 mb-2">...</h3>
<p className="text-gray-500"></p>
</div>
) : currentApps.length > 0 ? (
<> <>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{currentApps.map((app) => { {currentApps.map((app) => {
const IconComponent = app.icon const IconComponent = getIconComponent(app.icon || 'Bot')
const likes = getAppLikes(app.id.toString()) const likes = getAppLikes(app.id.toString())
const views = getViewCount(app.id.toString()) const views = getViewCount(app.id.toString())
const rating = getAppRating(app.id.toString()) const rating = getAppRating(app.id.toString())
@@ -944,7 +1037,7 @@ export default function AIShowcasePlatform() {
{/* Pagination */} {/* Pagination */}
{totalPages > 1 && ( {totalPages > 1 && (
<Pagination currentPage={currentPage} totalPages={totalPages} onPageChange={setCurrentPage} /> <Pagination currentPage={currentPage} totalPages={totalPages} onPageChange={handlePageChange} />
)} )}
</> </>
) : ( ) : (

View File

@@ -1,6 +1,6 @@
"use client" "use client"
import { useState } from "react" import { useState, useEffect } from "react"
import { useAuth } from "@/contexts/auth-context" import { useAuth } from "@/contexts/auth-context"
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
@@ -127,7 +127,7 @@ export function AppDetailDialog({ open, onOpenChange, app }: AppDetailDialogProp
} }
// 當對話框打開時載入統計數據 // 當對話框打開時載入統計數據
React.useEffect(() => { useEffect(() => {
if (open && app.id) { if (open && app.id) {
loadAppStats() loadAppStats()
} }

View File

@@ -154,6 +154,8 @@ CREATE TABLE `apps` (
`team_id` VARCHAR(36) NULL, `team_id` VARCHAR(36) NULL,
`category` VARCHAR(100) NOT NULL, `category` VARCHAR(100) NOT NULL,
`type` VARCHAR(100) NOT NULL, `type` VARCHAR(100) NOT NULL,
`icon` VARCHAR(50) DEFAULT 'Bot',
`icon_color` VARCHAR(100) DEFAULT 'from-blue-500 to-purple-500',
`likes_count` INT DEFAULT 0, `likes_count` INT DEFAULT 0,
`views_count` INT DEFAULT 0, `views_count` INT DEFAULT 0,
`rating` DECIMAL(3,2) DEFAULT 0.00, `rating` DECIMAL(3,2) DEFAULT 0.00,
@@ -353,6 +355,7 @@ CREATE TABLE `user_ratings` (
`user_id` VARCHAR(36) NOT NULL, `user_id` VARCHAR(36) NOT NULL,
`app_id` VARCHAR(36) NOT NULL, `app_id` VARCHAR(36) NOT NULL,
`rating` DECIMAL(3,2) NOT NULL CHECK (`rating` >= 1.0 AND `rating` <= 5.0), `rating` DECIMAL(3,2) NOT NULL CHECK (`rating` >= 1.0 AND `rating` <= 5.0),
`comment` TEXT NULL,
`rated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `rated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE, FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`app_id`) REFERENCES `apps`(`id`) ON DELETE CASCADE, FOREIGN KEY (`app_id`) REFERENCES `apps`(`id`) ON DELETE CASCADE,

View File

@@ -176,6 +176,8 @@ CREATE TABLE `apps` (
`team_id` VARCHAR(36) NULL, `team_id` VARCHAR(36) NULL,
`category` VARCHAR(100) NOT NULL, `category` VARCHAR(100) NOT NULL,
`type` VARCHAR(100) NOT NULL, `type` VARCHAR(100) NOT NULL,
`icon` VARCHAR(50) DEFAULT 'Bot',
`icon_color` VARCHAR(100) DEFAULT 'from-blue-500 to-purple-500',
`likes_count` INT DEFAULT 0, `likes_count` INT DEFAULT 0,
`views_count` INT DEFAULT 0, `views_count` INT DEFAULT 0,
`rating` DECIMAL(3,2) DEFAULT 0.00, `rating` DECIMAL(3,2) DEFAULT 0.00,
@@ -408,6 +410,7 @@ CREATE TABLE `user_ratings` (
`user_id` VARCHAR(36) NOT NULL, `user_id` VARCHAR(36) NOT NULL,
`app_id` VARCHAR(36) NOT NULL, `app_id` VARCHAR(36) NOT NULL,
`rating` DECIMAL(3,2) NOT NULL CHECK (`rating` >= 1.0 AND `rating` <= 5.0), `rating` DECIMAL(3,2) NOT NULL CHECK (`rating` >= 1.0 AND `rating` <= 5.0),
`comment` TEXT NULL,
`rated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `rated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE, FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,

View File

@@ -353,6 +353,7 @@ CREATE TABLE `user_ratings` (
`user_id` VARCHAR(36) NOT NULL, `user_id` VARCHAR(36) NOT NULL,
`app_id` VARCHAR(36) NOT NULL, `app_id` VARCHAR(36) NOT NULL,
`rating` DECIMAL(3,2) NOT NULL CHECK (`rating` >= 1.0 AND `rating` <= 5.0), `rating` DECIMAL(3,2) NOT NULL CHECK (`rating` >= 1.0 AND `rating` <= 5.0),
`comment` TEXT NULL,
`rated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `rated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE, FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`app_id`) REFERENCES `apps`(`id`) ON DELETE CASCADE, FOREIGN KEY (`app_id`) REFERENCES `apps`(`id`) ON DELETE CASCADE,

View File

@@ -18,6 +18,14 @@ const dbConfig = {
reconnect: true, reconnect: true,
connectionLimit: 10, connectionLimit: 10,
queueLimit: 0, queueLimit: 0,
// 添加連接重試和錯誤處理配置
retryDelay: 2000,
maxRetries: 3,
// 添加連接池配置
idleTimeout: 300000,
maxIdle: 10,
// 添加 SSL 配置(如果需要)
ssl: false,
}; };
// 創建連接池 // 創建連接池
@@ -46,19 +54,51 @@ export class Database {
// 執行查詢 // 執行查詢
public async query<T = any>(sql: string, params?: any[]): Promise<T[]> { public async query<T = any>(sql: string, params?: any[]): Promise<T[]> {
const connection = await this.getConnection(); let connection;
let retries = 0;
const maxRetries = 3;
while (retries < maxRetries) {
try { try {
connection = await this.getConnection();
const [rows] = await connection.execute(sql, params); const [rows] = await connection.execute(sql, params);
return rows as T[]; return rows as T[];
} finally { } catch (error: any) {
console.error(`資料庫查詢錯誤 (嘗試 ${retries + 1}/${maxRetries}):`, error.message);
if (connection) {
connection.release(); connection.release();
} }
if (error.code === 'ECONNRESET' || error.code === 'PROTOCOL_CONNECTION_LOST') {
retries++;
if (retries < maxRetries) {
console.log(`等待 ${2000 * retries}ms 後重試...`);
await new Promise(resolve => setTimeout(resolve, 2000 * retries));
continue;
}
}
throw error;
} finally {
if (connection) {
connection.release();
}
}
}
throw new Error('資料庫連接失敗,已達到最大重試次數');
} }
// 執行單一查詢 // 執行單一查詢
public async queryOne<T = any>(sql: string, params?: any[]): Promise<T | null> { public async queryOne<T = any>(sql: string, params?: any[]): Promise<T | null> {
try {
const results = await this.query<T>(sql, params); const results = await this.query<T>(sql, params);
return results.length > 0 ? results[0] : null; return results.length > 0 ? results[0] : null;
} catch (error) {
console.error('資料庫單一查詢錯誤:', error);
throw error;
}
} }
// 執行插入 // 執行插入

View File

@@ -239,6 +239,7 @@ export interface UserRating {
user_id: string; user_id: string;
app_id: string; app_id: string;
rating: number; rating: number;
comment?: string;
rated_at: string; rated_at: string;
} }

View File

@@ -1396,6 +1396,7 @@ export class AppService {
SELECT SELECT
ur.id, ur.id,
ur.rating, ur.rating,
ur.comment,
ur.rated_at, ur.rated_at,
u.name as user_name, u.name as user_name,
u.department as user_department, u.department as user_department,
@@ -1411,7 +1412,7 @@ export class AppService {
return reviews.map((review: any) => ({ return reviews.map((review: any) => ({
id: review.id, id: review.id,
rating: review.rating, rating: review.rating,
review: '用戶評價', // 暫時使用固定文字,因為資料庫中沒有 review 欄位 review: review.comment || '用戶評價', // 使用 comment 欄位,如果為空則顯示預設文字
ratedAt: review.rated_at, ratedAt: review.rated_at,
userName: review.user_name, userName: review.user_name,
userDepartment: review.user_department, userDepartment: review.user_department,
@@ -1464,6 +1465,79 @@ export class AppService {
return { success: false, error: '刪除評價時發生錯誤' }; return { success: false, error: '刪除評價時發生錯誤' };
} }
} }
// 獲取應用部門列表
async getAppDepartments(): Promise<{ department: string; count: number }[]> {
try {
const sql = `
SELECT
u.department,
COUNT(DISTINCT a.id) as count
FROM apps a
JOIN users u ON a.creator_id = u.id
WHERE a.is_active = TRUE
GROUP BY u.department
ORDER BY count DESC, u.department ASC
`;
const departments = await this.query(sql);
return departments.map((dept: any) => ({
department: dept.department,
count: parseInt(dept.count)
}));
} catch (error) {
console.error('獲取應用部門列表錯誤:', error);
return [];
}
}
// 獲取應用類型列表
async getAppTypes(): Promise<{ type: string; count: number }[]> {
try {
const sql = `
SELECT
type,
COUNT(*) as count
FROM apps
WHERE is_active = TRUE
GROUP BY type
ORDER BY count DESC, type ASC
`;
const types = await this.query(sql);
return types.map((type: any) => ({
type: type.type,
count: parseInt(type.count)
}));
} catch (error) {
console.error('獲取應用類型列表錯誤:', error);
return [];
}
}
// 獲取應用分類列表
async getAppCategories(): Promise<{ category: string; count: number }[]> {
try {
const sql = `
SELECT
category,
COUNT(*) as count
FROM apps
WHERE is_active = TRUE
GROUP BY category
ORDER BY count DESC, category ASC
`;
const categories = await this.query(sql);
return categories.map((cat: any) => ({
category: cat.category,
count: parseInt(cat.count)
}));
} catch (error) {
console.error('獲取應用分類列表錯誤:', error);
return [];
}
}
} }
// ===================================================== // =====================================================

View File

@@ -1,73 +0,0 @@
const mysql = require('mysql2/promise');
// 資料庫配置
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 addUserFields() {
console.log('🚀 開始為 users 表添加缺失的字段...');
try {
const connection = await mysql.createConnection(dbConfig);
console.log('✅ 資料庫連接成功');
// 檢查字段是否已存在
const [columns] = await connection.execute(`
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = 'users' AND COLUMN_NAME IN ('phone', 'location', 'bio')
`, [dbConfig.database]);
const existingColumns = columns.map(col => col.COLUMN_NAME);
console.log('現有字段:', existingColumns);
// 添加缺失的字段
const fieldsToAdd = [
{ name: 'phone', sql: 'ADD COLUMN `phone` VARCHAR(20) NULL' },
{ name: 'location', sql: 'ADD COLUMN `location` VARCHAR(100) NULL' },
{ name: 'bio', sql: 'ADD COLUMN `bio` TEXT NULL' }
];
for (const field of fieldsToAdd) {
if (!existingColumns.includes(field.name)) {
try {
await connection.execute(`ALTER TABLE users ${field.sql}`);
console.log(`✅ 添加字段: ${field.name}`);
} catch (error) {
console.log(`❌ 添加字段 ${field.name} 失敗:`, error.message);
}
} else {
console.log(`⚠️ 字段 ${field.name} 已存在,跳過`);
}
}
// 驗證字段添加結果
const [finalColumns] = await connection.execute(`
SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = 'users'
AND COLUMN_NAME IN ('phone', 'location', 'bio')
ORDER BY ORDINAL_POSITION
`, [dbConfig.database]);
console.log('\n📋 最終字段狀態:');
finalColumns.forEach(col => {
console.log(`- ${col.COLUMN_NAME}: ${col.DATA_TYPE} (${col.IS_NULLABLE === 'YES' ? '可為空' : '不可為空'})`);
});
await connection.end();
console.log('\n🎉 字段添加完成!');
} catch (error) {
console.error('❌ 添加字段時發生錯誤:', error);
}
}
addUserFields();

View File

@@ -1,84 +0,0 @@
async function debugLoadingIssue() {
console.log('🔍 調試管理員後台載入問題...\n');
try {
// 1. 檢查管理員頁面載入
console.log('1. 檢查管理員頁面載入...');
const response = await fetch('http://localhost:3000/admin');
if (response.ok) {
const pageContent = await response.text();
console.log('✅ 管理員頁面載入成功');
console.log('狀態碼:', response.status);
// 檢查頁面內容
if (pageContent.includes('載入中...')) {
console.log('❌ 頁面一直顯示載入中狀態');
// 檢查是否有調試信息
if (pageContent.includes('調試信息')) {
console.log('📋 發現調試信息');
const debugMatch = pageContent.match(/調試信息: 用戶=([^,]+), 角色=([^<]+)/);
if (debugMatch) {
console.log('調試信息:', {
用戶: debugMatch[1],
角色: debugMatch[2]
});
}
} else {
console.log('⚠️ 沒有調試信息,可能是載入狀態問題');
}
// 檢查頁面是否包含管理員內容
if (pageContent.includes('儀表板') || pageContent.includes('管理員')) {
console.log('✅ 頁面包含管理員內容,但被載入狀態覆蓋');
} else {
console.log('❌ 頁面不包含管理員內容');
}
} else if (pageContent.includes('存取被拒')) {
console.log('❌ 頁面顯示存取被拒');
} else if (pageContent.includes('儀表板') || pageContent.includes('管理員')) {
console.log('✅ 管理員頁面正常顯示');
} else {
console.log('⚠️ 頁面內容不確定');
}
} else {
console.log('❌ 管理員頁面載入失敗:', response.status);
}
// 2. 檢查登入狀態
console.log('\n2. 檢查登入狀態...');
const loginResponse = await fetch('http://localhost:3000/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'admin@ai-platform.com',
password: 'admin123456'
})
});
if (loginResponse.ok) {
const loginData = await loginResponse.json();
console.log('✅ 管理員登入 API 正常');
console.log('用戶角色:', loginData.user?.role);
} else {
console.log('❌ 管理員登入 API 失敗:', loginResponse.status);
}
console.log('\n🎉 載入問題調試完成!');
console.log('\n💡 可能的原因:');
console.log('1. isInitialized 狀態沒有正確設置');
console.log('2. 客戶端載入邏輯有問題');
console.log('3. localStorage 中的用戶資料有問題');
console.log('4. 載入條件邏輯有問題');
} catch (error) {
console.error('❌ 調試過程中發生錯誤:', error);
}
}
debugLoadingIssue();

View File

@@ -1,180 +0,0 @@
const mysql = require('mysql2/promise');
async function insertTestData() {
console.log('📊 在資料庫中插入真實測試數據...\n');
let connection;
try {
// 連接資料庫
connection = await mysql.createConnection({
host: 'localhost',
user: 'root',
password: '123456',
database: 'ai_showcase_platform'
});
console.log('✅ 資料庫連接成功');
// 1. 獲取現有的應用和用戶
const [apps] = await connection.execute('SELECT id FROM apps WHERE is_active = TRUE LIMIT 3');
const [users] = await connection.execute('SELECT id FROM users WHERE status = "active" LIMIT 10');
if (apps.length === 0) {
console.log('❌ 沒有找到現有應用,無法創建測試數據');
return;
}
console.log(`📱 找到 ${apps.length} 個應用`);
console.log(`👥 找到 ${users.length} 個用戶`);
const targetAppId = apps[0].id; // 使用第一個應用作為目標
console.log(`🎯 目標應用 ID: ${targetAppId}`);
// 2. 清空現有的測試數據(如果有的話)
console.log('\n🧹 清空現有測試數據...');
await connection.execute('DELETE FROM user_views WHERE app_id = ?', [targetAppId]);
await connection.execute('DELETE FROM user_likes WHERE app_id = ?', [targetAppId]);
await connection.execute('DELETE FROM user_ratings WHERE app_id = ?', [targetAppId]);
console.log('✅ 現有測試數據已清空');
// 3. 插入用戶瀏覽記錄
console.log('\n👀 插入用戶瀏覽記錄...');
const views = [];
for (let i = 0; i < 25; i++) {
const userId = users[Math.floor(Math.random() * users.length)].id;
const viewedAt = new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000); // 過去30天內
const ipAddress = `192.168.1.${Math.floor(Math.random() * 255)}`;
const userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36';
views.push([targetAppId, userId, viewedAt, ipAddress, userAgent]);
}
for (const view of views) {
await connection.execute(`
INSERT INTO user_views (id, app_id, user_id, viewed_at, ip_address, user_agent)
VALUES (UUID(), ?, ?, ?, ?, ?)
`, view);
}
console.log(`✅ 插入了 ${views.length} 個瀏覽記錄`);
// 4. 插入用戶按讚記錄
console.log('\n❤ 插入用戶按讚記錄...');
const likes = [];
for (let i = 0; i < 8; i++) {
const userId = users[Math.floor(Math.random() * users.length)].id;
const likedAt = new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000);
likes.push([targetAppId, userId, likedAt]);
}
for (const like of likes) {
await connection.execute(`
INSERT INTO user_likes (id, app_id, user_id, liked_at)
VALUES (UUID(), ?, ?, ?)
`, like);
}
console.log(`✅ 插入了 ${likes.length} 個按讚記錄`);
// 5. 插入用戶評分記錄
console.log('\n⭐ 插入用戶評分記錄...');
const ratings = [
{ rating: 5, review: '這個應用非常實用,界面設計得很棒!' },
{ rating: 4, review: '功能強大,處理速度很快,推薦使用。' },
{ rating: 5, review: '非常好用的工具,節省了很多時間。' },
{ rating: 3, review: '還不錯,但還有改進空間。' },
{ rating: 4, review: '界面簡潔,操作簡單,很滿意。' },
{ rating: 5, review: '功能齊全,使用體驗很好。' },
{ rating: 4, review: '整體不錯,希望能增加更多功能。' },
{ rating: 5, review: '非常推薦,已經推薦給同事了。' },
{ rating: 3, review: '還可以,但界面可以再優化。' },
{ rating: 4, review: '實用性很高,值得使用。' }
];
for (let i = 0; i < ratings.length; i++) {
const userId = users[Math.floor(Math.random() * users.length)].id;
const ratedAt = new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000);
await connection.execute(`
INSERT INTO user_ratings (id, app_id, user_id, rating, rated_at)
VALUES (UUID(), ?, ?, ?, ?)
`, [targetAppId, userId, ratings[i].rating, ratedAt]);
}
console.log(`✅ 插入了 ${ratings.length} 個評分記錄`);
// 6. 更新應用的統計數據
console.log('\n📈 更新應用統計數據...');
const [viewCount] = await connection.execute(
'SELECT COUNT(*) as count FROM user_views WHERE app_id = ?',
[targetAppId]
);
const [likeCount] = await connection.execute(
'SELECT COUNT(*) as count FROM user_likes WHERE app_id = ?',
[targetAppId]
);
await connection.execute(`
UPDATE apps
SET views_count = ?, likes_count = ?, updated_at = NOW()
WHERE id = ?
`, [viewCount[0].count, likeCount[0].count, targetAppId]);
console.log(` - 應用 ${targetAppId}: ${viewCount[0].count} 瀏覽, ${likeCount[0].count}`);
// 7. 顯示最終統計
console.log('\n📊 數據插入完成!最終統計:');
const [appStats] = await connection.execute(`
SELECT
views_count,
likes_count
FROM apps
WHERE id = ?
`, [targetAppId]);
const [ratingStats] = await connection.execute(`
SELECT
COUNT(*) as total_ratings,
AVG(rating) as avg_rating
FROM user_ratings
WHERE app_id = ?
`, [targetAppId]);
const [viewStats] = await connection.execute(`
SELECT COUNT(*) as total_views FROM user_views WHERE app_id = ?
`, [targetAppId]);
const [likeStats] = await connection.execute(`
SELECT COUNT(*) as total_likes FROM user_likes WHERE app_id = ?
`, [targetAppId]);
console.log(` - 應用瀏覽數: ${appStats[0].views_count}`);
console.log(` - 應用讚數: ${appStats[0].likes_count}`);
console.log(` - 總瀏覽數: ${viewStats[0].total_views}`);
console.log(` - 總讚數: ${likeStats[0].total_likes}`);
console.log(` - 評分總數: ${ratingStats[0].total_ratings}`);
console.log(` - 平均評分: ${ratingStats[0].avg_rating.toFixed(1)}`);
console.log('\n🎉 真實測試數據插入完成!');
console.log('\n💡 現在您可以重新載入應用管理頁面查看真實的資料庫數據');
} catch (error) {
console.error('❌ 插入測試數據時發生錯誤:', error);
if (error.code === 'ECONNREFUSED') {
console.log('\n💡 請確保 MySQL 服務正在運行,並且連接參數正確');
console.log(' - Host: localhost');
console.log(' - User: root');
console.log(' - Password: 123456');
console.log(' - Database: ai_showcase_platform');
}
} finally {
if (connection) {
await connection.end();
console.log('\n🔌 資料庫連接已關閉');
}
}
}
insertTestData();

View File

@@ -1,80 +0,0 @@
-- 插入真實測試數據到資料庫(多用戶版本)
-- 請在 MySQL 中執行此腳本
-- 1. 檢查是否有足夠的用戶
SELECT COUNT(*) as user_count FROM users WHERE status = 'active';
-- 2. 清空現有的測試數據(如果有的話)
DELETE FROM user_views WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7';
DELETE FROM user_likes WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7';
DELETE FROM user_ratings WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7';
-- 3. 獲取多個用戶的 ID
SET @user1_id = (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 0);
SET @user2_id = (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 1);
SET @user3_id = (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 2);
SET @user4_id = (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 3);
SET @user5_id = (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 4);
-- 4. 插入用戶瀏覽記錄 (25條) - 使用多個用戶
INSERT INTO user_views (id, app_id, user_id, viewed_at, ip_address, user_agent) VALUES
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user1_id, '2024-01-20 10:30:00', '192.168.1.100', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user2_id, '2024-01-19 14:20:00', '192.168.1.101', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user3_id, '2024-01-18 09:15:00', '192.168.1.102', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user4_id, '2024-01-17 16:45:00', '192.168.1.103', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user5_id, '2024-01-16 11:30:00', '192.168.1.104', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user1_id, '2024-01-15 08:20:00', '192.168.1.105', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user2_id, '2024-01-14 15:10:00', '192.168.1.106', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user3_id, '2024-01-13 12:45:00', '192.168.1.107', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user4_id, '2024-01-12 17:30:00', '192.168.1.108', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user5_id, '2024-01-11 09:15:00', '192.168.1.109', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user1_id, '2024-01-10 14:25:00', '192.168.1.110', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user2_id, '2024-01-09 11:40:00', '192.168.1.111', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user3_id, '2024-01-08 16:55:00', '192.168.1.112', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user4_id, '2024-01-07 13:20:00', '192.168.1.113', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user5_id, '2024-01-06 10:35:00', '192.168.1.114', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user1_id, '2024-01-05 15:50:00', '192.168.1.115', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user2_id, '2024-01-04 12:15:00', '192.168.1.116', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user3_id, '2024-01-03 09:30:00', '192.168.1.117', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user4_id, '2024-01-02 17:45:00', '192.168.1.118', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user5_id, '2024-01-01 14:20:00', '192.168.1.119', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user1_id, '2023-12-31 11:10:00', '192.168.1.120', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user2_id, '2023-12-30 16:25:00', '192.168.1.121', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user3_id, '2023-12-29 13:40:00', '192.168.1.122', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user4_id, '2023-12-28 10:55:00', '192.168.1.123', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user5_id, '2023-12-27 15:15:00', '192.168.1.124', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)');
-- 5. 插入用戶按讚記錄 (8條) - 使用多個用戶
INSERT INTO user_likes (id, app_id, user_id, liked_at) VALUES
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user1_id, '2024-01-20 10:35:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user2_id, '2024-01-19 14:25:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user3_id, '2024-01-18 09:20:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user4_id, '2024-01-17 16:50:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user5_id, '2024-01-16 11:35:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user1_id, '2024-01-15 08:25:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user2_id, '2024-01-14 15:15:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user3_id, '2024-01-13 12:50:00');
-- 6. 插入用戶評分記錄 (5條) - 使用不同用戶,避免重複約束
INSERT INTO user_ratings (id, app_id, user_id, rating, rated_at) VALUES
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user1_id, 5, '2024-01-20 10:40:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user2_id, 4, '2024-01-19 14:30:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user3_id, 5, '2024-01-18 09:25:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user4_id, 3, '2024-01-17 16:55:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @user5_id, 4, '2024-01-16 11:40:00');
-- 7. 更新應用的統計數據
UPDATE apps
SET
views_count = (SELECT COUNT(*) FROM user_views WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7'),
likes_count = (SELECT COUNT(*) FROM user_likes WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7'),
updated_at = NOW()
WHERE id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7';
-- 8. 顯示統計摘要
SELECT
'統計摘要' as ,
(SELECT COUNT(*) FROM user_views WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7') as ,
(SELECT COUNT(*) FROM user_likes WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7') as ,
(SELECT COUNT(*) FROM user_ratings WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7') as ,
(SELECT AVG(rating) FROM user_ratings WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7') as ;

View File

@@ -1,72 +0,0 @@
-- 插入真實測試數據到資料庫(簡化版本)
-- 請在 MySQL 中執行此腳本
-- 1. 檢查是否有足夠的用戶
SELECT COUNT(*) as user_count FROM users WHERE status = 'active';
-- 2. 清空現有的測試數據(如果有的話)
DELETE FROM user_views WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7';
DELETE FROM user_likes WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7';
DELETE FROM user_ratings WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7';
-- 3. 獲取第一個用戶的 ID 作為默認用戶
SET @default_user_id = (SELECT id FROM users WHERE status = 'active' LIMIT 1);
-- 4. 插入用戶瀏覽記錄 (25條) - 使用默認用戶
INSERT INTO user_views (id, app_id, user_id, viewed_at, ip_address, user_agent) VALUES
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-20 10:30:00', '192.168.1.100', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-19 14:20:00', '192.168.1.101', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-18 09:15:00', '192.168.1.102', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-17 16:45:00', '192.168.1.103', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-16 11:30:00', '192.168.1.104', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-15 08:20:00', '192.168.1.105', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-14 15:10:00', '192.168.1.106', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-13 12:45:00', '192.168.1.107', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-12 17:30:00', '192.168.1.108', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-11 09:15:00', '192.168.1.109', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-10 14:25:00', '192.168.1.110', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-09 11:40:00', '192.168.1.111', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-08 16:55:00', '192.168.1.112', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-07 13:20:00', '192.168.1.113', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-06 10:35:00', '192.168.1.114', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-05 15:50:00', '192.168.1.115', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-04 12:15:00', '192.168.1.116', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-03 09:30:00', '192.168.1.117', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-02 17:45:00', '192.168.1.118', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-01 14:20:00', '192.168.1.119', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2023-12-31 11:10:00', '192.168.1.120', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2023-12-30 16:25:00', '192.168.1.121', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2023-12-29 13:40:00', '192.168.1.122', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2023-12-28 10:55:00', '192.168.1.123', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2023-12-27 15:15:00', '192.168.1.124', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)');
-- 5. 插入用戶按讚記錄 (8條) - 使用默認用戶
INSERT INTO user_likes (id, app_id, user_id, liked_at) VALUES
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-20 10:35:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-19 14:25:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-18 09:20:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-17 16:50:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-16 11:35:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-15 08:25:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-14 15:15:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, '2024-01-13 12:50:00');
-- 6. 插入用戶評分記錄 (1條) - 使用默認用戶,避免重複約束
INSERT INTO user_ratings (id, app_id, user_id, rating, rated_at) VALUES
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', @default_user_id, 5, '2024-01-20 10:40:00');
-- 7. 更新應用的統計數據
UPDATE apps
SET
views_count = (SELECT COUNT(*) FROM user_views WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7'),
likes_count = (SELECT COUNT(*) FROM user_likes WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7'),
updated_at = NOW()
WHERE id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7';
-- 8. 顯示統計摘要
SELECT
'統計摘要' as ,
(SELECT COUNT(*) FROM user_views WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7') as ,
(SELECT COUNT(*) FROM user_likes WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7') as ,
(SELECT COUNT(*) FROM user_ratings WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7') as ,
(SELECT AVG(rating) FROM user_ratings WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7') as ;

View File

@@ -1,78 +0,0 @@
-- 插入真實測試數據到資料庫
-- 請在 MySQL 中執行此腳本
-- 1. 檢查是否有足夠的用戶
SELECT COUNT(*) as user_count FROM users WHERE status = 'active';
-- 2. 清空現有的測試數據(如果有的話)
DELETE FROM user_views WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7';
DELETE FROM user_likes WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7';
DELETE FROM user_ratings WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7';
-- 2. 插入用戶瀏覽記錄 (25條) - 使用不同的用戶避免重複約束
INSERT INTO user_views (id, app_id, user_id, viewed_at, ip_address, user_agent) VALUES
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 0), '2024-01-20 10:30:00', '192.168.1.100', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 1), '2024-01-19 14:20:00', '192.168.1.101', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 2), '2024-01-18 09:15:00', '192.168.1.102', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 3), '2024-01-17 16:45:00', '192.168.1.103', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 4), '2024-01-16 11:30:00', '192.168.1.104', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 5), '2024-01-15 08:20:00', '192.168.1.105', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 6), '2024-01-14 15:10:00', '192.168.1.106', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 7), '2024-01-13 12:45:00', '192.168.1.107', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 8), '2024-01-12 17:30:00', '192.168.1.108', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 9), '2024-01-11 09:15:00', '192.168.1.109', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 0), '2024-01-10 14:25:00', '192.168.1.110', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 1), '2024-01-09 11:40:00', '192.168.1.111', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 2), '2024-01-08 16:55:00', '192.168.1.112', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 3), '2024-01-07 13:20:00', '192.168.1.113', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 4), '2024-01-06 10:35:00', '192.168.1.114', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 5), '2024-01-05 15:50:00', '192.168.1.115', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 6), '2024-01-04 12:15:00', '192.168.1.116', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 7), '2024-01-03 09:30:00', '192.168.1.117', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 8), '2024-01-02 17:45:00', '192.168.1.118', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 9), '2024-01-01 14:20:00', '192.168.1.119', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 0), '2023-12-31 11:10:00', '192.168.1.120', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 1), '2023-12-30 16:25:00', '192.168.1.121', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 2), '2023-12-29 13:40:00', '192.168.1.122', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 3), '2023-12-28 10:55:00', '192.168.1.123', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 4), '2023-12-27 15:15:00', '192.168.1.124', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)');
-- 3. 插入用戶按讚記錄 (8條) - 使用不同的用戶避免重複約束
INSERT INTO user_likes (id, app_id, user_id, liked_at) VALUES
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 0), '2024-01-20 10:35:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 1), '2024-01-19 14:25:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 2), '2024-01-18 09:20:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 3), '2024-01-17 16:50:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 4), '2024-01-16 11:35:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 5), '2024-01-15 08:25:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 6), '2024-01-14 15:15:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 7), '2024-01-13 12:50:00');
-- 4. 插入用戶評分記錄 (10條) - 使用不同的用戶避免重複約束
INSERT INTO user_ratings (id, app_id, user_id, rating, rated_at) VALUES
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 0), 5, '2024-01-20 10:40:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 1), 4, '2024-01-19 14:30:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 2), 5, '2024-01-18 09:25:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 3), 3, '2024-01-17 16:55:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 4), 4, '2024-01-16 11:40:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 5), 5, '2024-01-15 08:30:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 6), 4, '2024-01-14 15:20:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 7), 5, '2024-01-13 12:55:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 8), 4, '2024-01-12 17:35:00'),
(UUID(), '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7', (SELECT id FROM users WHERE status = 'active' ORDER BY id LIMIT 1 OFFSET 9), 5, '2024-01-11 09:20:00');
-- 5. 更新應用的統計數據
UPDATE apps
SET
views_count = (SELECT COUNT(*) FROM user_views WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7'),
likes_count = (SELECT COUNT(*) FROM user_likes WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7'),
updated_at = NOW()
WHERE id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7';
-- 6. 顯示統計摘要
SELECT
'統計摘要' as ,
(SELECT COUNT(*) FROM user_views WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7') as ,
(SELECT COUNT(*) FROM user_likes WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7') as ,
(SELECT COUNT(*) FROM user_ratings WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7') as ,
(SELECT AVG(rating) FROM user_ratings WHERE app_id = '7f7395f4-ad9f-4d14-9e2c-84962ecbcfd7') as ;