diff --git a/CHATBOT_ANALYSIS.md b/CHATBOT_ANALYSIS.md deleted file mode 100644 index 468ada2..0000000 --- a/CHATBOT_ANALYSIS.md +++ /dev/null @@ -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() // 訊息列表 -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 => { - // 實現細節... -} -``` - -## 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 - quickQuestions: Record - errorMessages: Record -} -``` - -### 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月 -**負責人**: 前端開發團隊 \ No newline at end of file diff --git a/SOFTWARE_SPECIFICATION.md b/SOFTWARE_SPECIFICATION.md deleted file mode 100644 index f635771..0000000 --- a/SOFTWARE_SPECIFICATION.md +++ /dev/null @@ -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月 -**負責人**: 敏捷小組 - 佩庭 -**審核人**: 強茂集團 \ No newline at end of file diff --git a/app/api/apps/route.ts b/app/api/apps/route.ts new file mode 100644 index 0000000..f591b61 --- /dev/null +++ b/app/api/apps/route.ts @@ -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 } + ) + } +} diff --git a/app/page.tsx b/app/page.tsx index a01cc47..9ed34b5 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,6 +1,6 @@ "use client" -import { useState } from "react" +import { useState, useEffect } from "react" import { useAuth } from "@/contexts/auth-context" import { useCompetition } from "@/contexts/competition-context" import { @@ -47,8 +47,7 @@ import { AwardDetailDialog } from "@/components/competition/award-detail-dialog" import { LikeButton } from "@/components/like-button" -// AI applications data - empty for production -const aiApps: any[] = [] +// AI applications data - will be loaded from API // Pagination component interface PaginationProps { @@ -156,6 +155,15 @@ export default function AIShowcasePlatform() { const [showCompetition, setShowCompetition] = useState(false) const appsPerPage = 6 + // 新增狀態管理 + const [aiApps, setAiApps] = useState([]) + 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 const [selectedCompetitionTypeFilter, setSelectedCompetitionTypeFilter] = useState("all") const [selectedMonthFilter, setSelectedMonthFilter] = useState("all") @@ -170,21 +178,61 @@ export default function AIShowcasePlatform() { const [showAwardDetail, setShowAwardDetail] = useState(false) const [selectedAward, setSelectedAward] = useState(null) - const filteredApps = aiApps.filter((app) => { - const matchesSearch = - app.name.toLowerCase().includes(searchTerm.toLowerCase()) || - app.description.toLowerCase().includes(searchTerm.toLowerCase()) - const matchesDepartment = selectedDepartment === "all" || app.department === selectedDepartment - const matchesType = selectedType === "all" || app.type === selectedType + // 載入應用數據 + const loadApps = async () => { + try { + setIsLoadingApps(true) + const params = new URLSearchParams({ + page: currentPage.toString(), + limit: appsPerPage.toString(), + search: searchTerm, + department: selectedDepartment, + type: selectedType + }) - return matchesSearch && matchesDepartment && matchesType - }) + const response = await fetch(`/api/apps?${params}`) + const data = await response.json() - // Pagination logic - const totalPages = Math.ceil(filteredApps.length / appsPerPage) - const startIndex = (currentPage - 1) * appsPerPage - const endIndex = startIndex + appsPerPage - const currentApps = filteredApps.slice(startIndex, endIndex) + if (data.success) { + 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 const handleFilterChange = (filterType: string, value: string) => { @@ -201,6 +249,40 @@ export default function AIShowcasePlatform() { 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 colors = { 文字處理: "bg-blue-100 text-blue-800 border-blue-200", @@ -866,19 +948,30 @@ export default function AIShowcasePlatform() { {/* Results summary */}
- 找到 {filteredApps.length} 個應用,第 {currentPage} 頁,共 {totalPages} 頁 + 找到 {totalApps} 個應用,第 {currentPage} 頁,共 {totalPages} 頁
{/* All Apps with Pagination */}
-

所有應用 ({filteredApps.length})

+

+ 所有應用 ({totalApps}) + {isLoadingApps && 載入中...} +

- {currentApps.length > 0 ? ( + {isLoadingApps ? ( +
+
+ +
+

載入應用中...

+

請稍候

+
+ ) : currentApps.length > 0 ? ( <>
{currentApps.map((app) => { - const IconComponent = app.icon + const IconComponent = getIconComponent(app.icon || 'Bot') const likes = getAppLikes(app.id.toString()) const views = getViewCount(app.id.toString()) const rating = getAppRating(app.id.toString()) @@ -944,7 +1037,7 @@ export default function AIShowcasePlatform() { {/* Pagination */} {totalPages > 1 && ( - + )} ) : ( diff --git a/components/app-detail-dialog.tsx b/components/app-detail-dialog.tsx index d8610b0..f77d595 100644 --- a/components/app-detail-dialog.tsx +++ b/components/app-detail-dialog.tsx @@ -1,6 +1,6 @@ "use client" -import { useState } from "react" +import { useState, useEffect } from "react" import { useAuth } from "@/contexts/auth-context" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Button } from "@/components/ui/button" @@ -127,7 +127,7 @@ export function AppDetailDialog({ open, onOpenChange, app }: AppDetailDialogProp } // 當對話框打開時載入統計數據 - React.useEffect(() => { + useEffect(() => { if (open && app.id) { loadAppStats() } diff --git a/database-schema-simple.sql b/database-schema-simple.sql index 01375e0..99c77ea 100644 --- a/database-schema-simple.sql +++ b/database-schema-simple.sql @@ -154,6 +154,8 @@ CREATE TABLE `apps` ( `team_id` VARCHAR(36) NULL, `category` 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, `views_count` INT DEFAULT 0, `rating` DECIMAL(3,2) DEFAULT 0.00, @@ -353,6 +355,7 @@ CREATE TABLE `user_ratings` ( `user_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), + `comment` TEXT NULL, `rated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE, FOREIGN KEY (`app_id`) REFERENCES `apps`(`id`) ON DELETE CASCADE, diff --git a/database-schema.sql b/database-schema.sql index f1eb166..1a8c97d 100644 --- a/database-schema.sql +++ b/database-schema.sql @@ -176,6 +176,8 @@ CREATE TABLE `apps` ( `team_id` VARCHAR(36) NULL, `category` 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, `views_count` INT DEFAULT 0, `rating` DECIMAL(3,2) DEFAULT 0.00, @@ -408,6 +410,7 @@ CREATE TABLE `user_ratings` ( `user_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), + `comment` TEXT NULL, `rated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE, diff --git a/database-tables-only.sql b/database-tables-only.sql index 33c2bd5..de67956 100644 --- a/database-tables-only.sql +++ b/database-tables-only.sql @@ -353,6 +353,7 @@ CREATE TABLE `user_ratings` ( `user_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), + `comment` TEXT NULL, `rated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE, FOREIGN KEY (`app_id`) REFERENCES `apps`(`id`) ON DELETE CASCADE, diff --git a/lib/database.ts b/lib/database.ts index c4a72cc..f23ced8 100644 --- a/lib/database.ts +++ b/lib/database.ts @@ -18,6 +18,14 @@ const dbConfig = { reconnect: true, connectionLimit: 10, queueLimit: 0, + // 添加連接重試和錯誤處理配置 + retryDelay: 2000, + maxRetries: 3, + // 添加連接池配置 + idleTimeout: 300000, + maxIdle: 10, + // 添加 SSL 配置(如果需要) + ssl: false, }; // 創建連接池 @@ -46,19 +54,51 @@ export class Database { // 執行查詢 public async query(sql: string, params?: any[]): Promise { - const connection = await this.getConnection(); - try { - const [rows] = await connection.execute(sql, params); - return rows as T[]; - } finally { - connection.release(); + let connection; + let retries = 0; + const maxRetries = 3; + + while (retries < maxRetries) { + try { + connection = await this.getConnection(); + const [rows] = await connection.execute(sql, params); + return rows as T[]; + } catch (error: any) { + console.error(`資料庫查詢錯誤 (嘗試 ${retries + 1}/${maxRetries}):`, error.message); + + if (connection) { + 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(sql: string, params?: any[]): Promise { - const results = await this.query(sql, params); - return results.length > 0 ? results[0] : null; + try { + const results = await this.query(sql, params); + return results.length > 0 ? results[0] : null; + } catch (error) { + console.error('資料庫單一查詢錯誤:', error); + throw error; + } } // 執行插入 diff --git a/lib/models.ts b/lib/models.ts index a78eccb..4187003 100644 --- a/lib/models.ts +++ b/lib/models.ts @@ -239,6 +239,7 @@ export interface UserRating { user_id: string; app_id: string; rating: number; + comment?: string; rated_at: string; } diff --git a/lib/services/database-service.ts b/lib/services/database-service.ts index a148686..566ac6f 100644 --- a/lib/services/database-service.ts +++ b/lib/services/database-service.ts @@ -1396,6 +1396,7 @@ export class AppService { SELECT ur.id, ur.rating, + ur.comment, ur.rated_at, u.name as user_name, u.department as user_department, @@ -1411,7 +1412,7 @@ export class AppService { return reviews.map((review: any) => ({ id: review.id, rating: review.rating, - review: '用戶評價', // 暫時使用固定文字,因為資料庫中沒有 review 欄位 + review: review.comment || '用戶評價', // 使用 comment 欄位,如果為空則顯示預設文字 ratedAt: review.rated_at, userName: review.user_name, userDepartment: review.user_department, @@ -1464,6 +1465,79 @@ export class AppService { 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 []; + } + } } // ===================================================== diff --git a/scripts/add-user-fields.js b/scripts/add-user-fields.js deleted file mode 100644 index fcdd407..0000000 --- a/scripts/add-user-fields.js +++ /dev/null @@ -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(); diff --git a/scripts/debug-loading-issue.js b/scripts/debug-loading-issue.js deleted file mode 100644 index d43475f..0000000 --- a/scripts/debug-loading-issue.js +++ /dev/null @@ -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(); diff --git a/scripts/insert-real-test-data.js b/scripts/insert-real-test-data.js deleted file mode 100644 index 40ccb7e..0000000 --- a/scripts/insert-real-test-data.js +++ /dev/null @@ -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(); diff --git a/scripts/insert-test-data-multi-user.sql b/scripts/insert-test-data-multi-user.sql deleted file mode 100644 index 111b58c..0000000 --- a/scripts/insert-test-data-multi-user.sql +++ /dev/null @@ -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 平均評分; diff --git a/scripts/insert-test-data-simple.sql b/scripts/insert-test-data-simple.sql deleted file mode 100644 index b15e73d..0000000 --- a/scripts/insert-test-data-simple.sql +++ /dev/null @@ -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 平均評分; diff --git a/scripts/insert-test-data.sql b/scripts/insert-test-data.sql deleted file mode 100644 index 40a3c6d..0000000 --- a/scripts/insert-test-data.sql +++ /dev/null @@ -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 平均評分;