實作前台 APP 呈現、APP 詳細頁面
This commit is contained in:
@@ -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月
|
||||
**負責人**: 前端開發團隊
|
@@ -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
71
app/api/apps/route.ts
Normal 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 }
|
||||
)
|
||||
}
|
||||
}
|
135
app/page.tsx
135
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<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
|
||||
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<any>(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 */}
|
||||
<div className="mt-4 text-sm text-gray-600">
|
||||
找到 {filteredApps.length} 個應用,第 {currentPage} 頁,共 {totalPages} 頁
|
||||
找到 {totalApps} 個應用,第 {currentPage} 頁,共 {totalPages} 頁
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* All Apps with Pagination */}
|
||||
<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">
|
||||
{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 && (
|
||||
<Pagination currentPage={currentPage} totalPages={totalPages} onPageChange={setCurrentPage} />
|
||||
<Pagination currentPage={currentPage} totalPages={totalPages} onPageChange={handlePageChange} />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
|
@@ -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()
|
||||
}
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -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<T = any>(sql: string, params?: any[]): Promise<T[]> {
|
||||
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<T = any>(sql: string, params?: any[]): Promise<T | null> {
|
||||
const results = await this.query<T>(sql, params);
|
||||
return results.length > 0 ? results[0] : null;
|
||||
try {
|
||||
const results = await this.query<T>(sql, params);
|
||||
return results.length > 0 ? results[0] : null;
|
||||
} catch (error) {
|
||||
console.error('資料庫單一查詢錯誤:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 執行插入
|
||||
|
@@ -239,6 +239,7 @@ export interface UserRating {
|
||||
user_id: string;
|
||||
app_id: string;
|
||||
rating: number;
|
||||
comment?: string;
|
||||
rated_at: string;
|
||||
}
|
||||
|
||||
|
@@ -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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
|
@@ -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();
|
@@ -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();
|
@@ -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();
|
@@ -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 平均評分;
|
@@ -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 平均評分;
|
@@ -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 平均評分;
|
Reference in New Issue
Block a user