3v324v23 commited on
Commit
fa813a0
·
0 Parent(s):

initial commit: enrich functionality and configure for Hugging Face

Browse files
.gitignore ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
25
+ .vite
Dockerfile ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 使用 Node.js 18 官方镜像
2
+ FROM node:18-alpine AS builder
3
+
4
+ # 设置工作目录
5
+ WORKDIR /app
6
+
7
+ # 复制 package.json 和 lock 文件
8
+ COPY package*.json ./
9
+
10
+ # 安装依赖
11
+ RUN npm install
12
+
13
+ # 复制所有源代码
14
+ COPY . .
15
+
16
+ # 构建前端应用
17
+ RUN npm run build
18
+
19
+ # 运行环境阶段
20
+ FROM node:18-alpine
21
+
22
+ WORKDIR /app
23
+
24
+ # 复制构建好的文件和必要文件
25
+ COPY --from=builder /app/dist ./dist
26
+ COPY --from=builder /app/package*.json ./
27
+ COPY --from=builder /app/node_modules ./node_modules
28
+
29
+ # 设置环境变量
30
+ ENV NODE_ENV=production
31
+ ENV PORT=7860
32
+
33
+ # 暴露端口
34
+ EXPOSE 7860
35
+
36
+ # 启动命令
37
+ CMD ["npm", "start"]
README.md ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: "Koa 现代化中文 Web 应用"
3
+ emoji: 🚀
4
+ colorFrom: blue
5
+ colorTo: indigo
6
+ sdk: docker
7
+ short_description: "Koa 现代化中文 Web 应用"
8
+ ---
9
+
10
+ # Koa Web 应用 🚀
11
+
12
+ 这是一个基于 Koa 和 React 构建的高性能、全中文 Web 服务框架。本项目旨在为开发者提供一个开箱即用的 Web 应用模板,包含完整的后端 API 和前端界面。
13
+
14
+ ## ✨ 特性
15
+
16
+ - 🌏 **全中文支持**:界面内容、代码注释及文档均采用中文。
17
+ - ⚡ **高性能**:利用 Koa 的轻量级异步中间件和 React 18 的高效渲染。
18
+ - 📱 **响应式设计**:完美适配手机、平板及桌面设备。
19
+ - 🐳 **容器化部署**:提供 Docker 和 docker-compose 配置文件。
20
+ - 🚀 **一键部署**:完美支持 Hugging Face Spaces 部署。
21
+
22
+ ## 🛠️ 技术栈
23
+
24
+ - **前端**: React 18, Tailwind CSS, Lucide Icons, Vite
25
+ - **后端**: Koa 2, Koa-Router, Koa-Bodyparser
26
+ - **工具**: TypeScript, Docker, Node.js 18+
27
+
28
+ ## 🚀 快速开始
29
+
30
+ ### 1. 本地开发
31
+
32
+ ```bash
33
+ # 安装依赖
34
+ npm install
35
+
36
+ # 启动开发环境 (前端: 5173, 后端: 3001)
37
+ npm run dev
38
+ ```
39
+
40
+ ### 2. 生产构建
41
+
42
+ ```bash
43
+ # 构建前端并准备生产环境
44
+ npm run build
45
+
46
+ # 启动生产服务
47
+ npm start
48
+ ```
49
+
50
+ ### 3. Docker 部署
51
+
52
+ ```bash
53
+ # 构建并运行
54
+ docker build -t koa-web-app .
55
+ docker run -p 3000:3000 koa-web-app
56
+ ```
57
+
58
+ ## 📂 项目结构
59
+
60
+ ```text
61
+ koa-web-app/
62
+ ├── api/ # Koa 后端服务
63
+ │ ├── routes/ # 路由定义
64
+ │ └── app.ts # Koa 应用核心
65
+ ├── src/ # React 前端应用
66
+ │ ├── components/ # 组件
67
+ │ └── pages/ # 页面
68
+ ├── Dockerfile # Docker 配置
69
+ └── README.md # 项目文档
70
+ ```
71
+
72
+ ## 📝 许可证
73
+
74
+ MIT License
api/app.ts ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Koa from 'koa'
2
+ import bodyParser from 'koa-bodyparser'
3
+ import logger from 'koa-logger'
4
+ import cors from '@koa/cors'
5
+ import serve from 'koa-static'
6
+ import path from 'path'
7
+ import { fileURLToPath } from 'url'
8
+ import systemRouter from './routes/system.js'
9
+ import dataRouter from './routes/data.js'
10
+
11
+ const __filename = fileURLToPath(import.meta.url)
12
+ const __dirname = path.dirname(__filename)
13
+
14
+ const app = new Koa()
15
+
16
+ // 中间件配置
17
+ app.use(cors())
18
+ app.use(logger())
19
+ app.use(bodyParser({
20
+ jsonLimit: '10mb',
21
+ formLimit: '10mb'
22
+ }))
23
+
24
+ // 错误处理
25
+ app.use(async (ctx, next) => {
26
+ try {
27
+ await next()
28
+ } catch (err: unknown) {
29
+ const error = err as { status?: number; message?: string }
30
+ ctx.status = error.status || 500
31
+ ctx.body = {
32
+ success: false,
33
+ error: error.message || '服务器内部错误'
34
+ }
35
+ ctx.app.emit('error', err, ctx)
36
+ }
37
+ })
38
+
39
+ // 路由注册
40
+ app.use(systemRouter.routes()).use(systemRouter.allowedMethods())
41
+ app.use(dataRouter.routes()).use(dataRouter.allowedMethods())
42
+
43
+ // 生产环境下托管静态文件
44
+ if (process.env.NODE_ENV === 'production') {
45
+ const distPath = path.resolve(__dirname, '../dist')
46
+ app.use(serve(distPath))
47
+
48
+ // 处理 SPA 路由
49
+ app.use(async (ctx, next) => {
50
+ if (ctx.status === 404 && !ctx.path.startsWith('/api')) {
51
+ ctx.type = 'html'
52
+ ctx.body = await import('fs').then(fs => fs.readFileSync(path.join(distPath, 'index.html')))
53
+ } else {
54
+ await next()
55
+ }
56
+ })
57
+ }
58
+
59
+ // 404 处理
60
+ app.use(async (ctx) => {
61
+ if (ctx.status === 404) {
62
+ ctx.body = {
63
+ success: false,
64
+ error: '接口未找到'
65
+ }
66
+ }
67
+ })
68
+
69
+ export default app
api/index.ts ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Vercel deploy entry handler, for serverless deployment
3
+ */
4
+ import type { VercelRequest, VercelResponse } from '@vercel/node';
5
+ import app from './app.js';
6
+
7
+ export default function handler(req: VercelRequest, res: VercelResponse) {
8
+ return app.callback()(req, res);
9
+ }
api/routes/data.ts ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Router from 'koa-router'
2
+
3
+ const router = new Router({ prefix: '/api/data' })
4
+
5
+ // Mock 数据
6
+ const mockTasks = [
7
+ { id: 1, title: '完成 Koa 后端开发', completed: true, category: '开发' },
8
+ { id: 2, title: '配置 Hugging Face 部署', completed: false, category: '部署' },
9
+ { id: 3, title: '编写项目中文文档', completed: true, category: '文档' },
10
+ { id: 4, title: '优化 React 前端 UI', completed: false, category: '设计' },
11
+ { id: 5, title: '集成 Docker 容器化', completed: true, category: '运维' },
12
+ ]
13
+
14
+ /**
15
+ * 获取任务列表
16
+ */
17
+ router.get('/tasks', async (ctx) => {
18
+ ctx.body = {
19
+ success: true,
20
+ data: mockTasks,
21
+ total: mockTasks.length
22
+ }
23
+ })
24
+
25
+ /**
26
+ * 获取系统状态
27
+ */
28
+ router.get('/stats', async (ctx) => {
29
+ ctx.body = {
30
+ success: true,
31
+ data: {
32
+ activeUsers: 128,
33
+ totalRequests: 5432,
34
+ uptime: process.uptime(),
35
+ version: '1.0.0'
36
+ }
37
+ }
38
+ })
39
+
40
+ export default router
api/routes/system.ts ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Router from 'koa-router'
2
+ import os from 'os'
3
+
4
+ const router = new Router({ prefix: '/api' })
5
+
6
+ /**
7
+ * 健康检查接口
8
+ */
9
+ router.get('/health', async (ctx) => {
10
+ ctx.body = {
11
+ status: 'ok',
12
+ timestamp: Date.now(),
13
+ }
14
+ })
15
+
16
+ /**
17
+ * 系统信息接口
18
+ */
19
+ router.get('/info', async (ctx) => {
20
+ const used = process.memoryUsage().heapUsed / 1024 / 1024
21
+ ctx.body = {
22
+ nodeVersion: process.version,
23
+ platform: process.platform,
24
+ memoryUsage: {
25
+ used: Math.round(used * 100) / 100,
26
+ total: Math.round(os.totalmem() / 1024 / 1024 * 100) / 100,
27
+ unit: 'MB',
28
+ },
29
+ }
30
+ })
31
+
32
+ /**
33
+ * 服务器时间接口
34
+ */
35
+ router.get('/time', async (ctx) => {
36
+ const now = new Date()
37
+ ctx.body = {
38
+ serverTime: now.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }),
39
+ timezone: 'Asia/Shanghai',
40
+ utc: now.toISOString(),
41
+ }
42
+ })
43
+
44
+ export default router
api/server.ts ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * local server entry file, for local development
3
+ */
4
+ import app from './app.js';
5
+
6
+ /**
7
+ * start server with port
8
+ */
9
+ const PORT = process.env.PORT || 7860;
10
+
11
+ const server = app.listen(PORT, () => {
12
+ console.log(`Server ready on port ${PORT}`);
13
+ });
14
+
15
+ /**
16
+ * close server
17
+ */
18
+ process.on('SIGTERM', () => {
19
+ console.log('SIGTERM signal received');
20
+ server.close(() => {
21
+ console.log('Server closed');
22
+ process.exit(0);
23
+ });
24
+ });
25
+
26
+ process.on('SIGINT', () => {
27
+ console.log('SIGINT signal received');
28
+ server.close(() => {
29
+ console.log('Server closed');
30
+ process.exit(0);
31
+ });
32
+ });
33
+
34
+ export default app;
docker-compose.yml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ koa-app:
5
+ build: .
6
+ ports:
7
+ - "3000:3000"
8
+ environment:
9
+ - NODE_ENV=production
10
+ - PORT=3000
11
+ restart: unless-stopped
eslint.config.js ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+ import reactRefresh from 'eslint-plugin-react-refresh'
5
+ import tseslint from 'typescript-eslint'
6
+
7
+ export default tseslint.config(
8
+ { ignores: ['dist'] },
9
+ {
10
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
11
+ files: ['**/*.{ts,tsx}'],
12
+ languageOptions: {
13
+ ecmaVersion: 2020,
14
+ globals: globals.browser,
15
+ },
16
+ plugins: {
17
+ 'react-hooks': reactHooks,
18
+ 'react-refresh': reactRefresh,
19
+ },
20
+ rules: {
21
+ ...reactHooks.configs.recommended.rules,
22
+ 'react-refresh/only-export-components': [
23
+ 'warn',
24
+ { allowConstantExport: true },
25
+ ],
26
+ },
27
+ },
28
+ )
index.html ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>My Trae Project</title>
8
+ <script type="module">
9
+ if (import.meta.hot?.on) {
10
+ import.meta.hot.on('vite:error', (error) => {
11
+ if (error.err) {
12
+ console.error(
13
+ [error.err.message, error.err.frame].filter(Boolean).join('\n'),
14
+ )
15
+ }
16
+ })
17
+ }
18
+ </script>
19
+ </head>
20
+ <body>
21
+ <div id="root"></div>
22
+ <script type="module" src="/src/main.tsx"></script>
23
+ </body>
24
+ </html>
nodemon.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "watch": ["api"],
3
+ "ext": "ts,mts,js,json",
4
+ "ignore": ["api/dist/*"],
5
+ "exec": "tsx api/server.ts",
6
+ "env": {
7
+ "NODE_ENV": "development"
8
+ },
9
+ "delay": 1000
10
+ }
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "koa-web-app",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "client:dev": "vite",
8
+ "build": "vite build",
9
+ "start": "NODE_ENV=production tsx api/server.ts",
10
+ "lint": "eslint .",
11
+ "preview": "vite preview",
12
+ "check": "tsc --noEmit",
13
+ "server:dev": "nodemon",
14
+ "dev": "concurrently \"npm run client:dev\" \"npm run server:dev\""
15
+ },
16
+ "dependencies": {
17
+ "@koa/cors": "^5.0.0",
18
+ "@types/koa": "^3.0.1",
19
+ "@types/koa-bodyparser": "^4.3.13",
20
+ "@types/koa-cors": "^0.0.6",
21
+ "@types/koa-logger": "^3.1.5",
22
+ "@types/koa-router": "^7.4.9",
23
+ "@types/koa-static": "^4.0.4",
24
+ "clsx": "^2.1.1",
25
+ "dotenv": "^17.2.1",
26
+ "koa": "^3.1.2",
27
+ "koa-bodyparser": "^4.4.1",
28
+ "koa-logger": "^4.0.0",
29
+ "koa-router": "^14.0.0",
30
+ "koa-static": "^5.0.0",
31
+ "lucide-react": "^0.511.0",
32
+ "react": "^18.3.1",
33
+ "react-dom": "^18.3.1",
34
+ "react-router-dom": "^7.3.0",
35
+ "tailwind-merge": "^3.0.2",
36
+ "zustand": "^5.0.3"
37
+ },
38
+ "devDependencies": {
39
+ "@eslint/js": "^9.25.0",
40
+ "@types/node": "^22.15.30",
41
+ "@types/react": "^18.3.12",
42
+ "@types/react-dom": "^18.3.1",
43
+ "@vercel/node": "^5.3.6",
44
+ "@vitejs/plugin-react": "^4.4.1",
45
+ "autoprefixer": "^10.4.21",
46
+ "babel-plugin-react-dev-locator": "^1.0.0",
47
+ "concurrently": "^9.2.0",
48
+ "eslint": "^9.25.0",
49
+ "eslint-plugin-react-hooks": "^5.2.0",
50
+ "eslint-plugin-react-refresh": "^0.4.19",
51
+ "globals": "^16.0.0",
52
+ "nodemon": "^3.1.10",
53
+ "postcss": "^8.5.3",
54
+ "tailwindcss": "^3.4.17",
55
+ "tsx": "^4.20.3",
56
+ "typescript": "~5.8.3",
57
+ "typescript-eslint": "^8.30.1",
58
+ "vite": "^6.3.5",
59
+ "vite-plugin-trae-solo-badge": "^1.0.0",
60
+ "vite-tsconfig-paths": "^5.1.4"
61
+ }
62
+ }
postcss.config.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ /** WARNING: DON'T EDIT THIS FILE */
2
+ /** WARNING: DON'T EDIT THIS FILE */
3
+ /** WARNING: DON'T EDIT THIS FILE */
4
+
5
+ export default {
6
+ plugins: {
7
+ tailwindcss: {},
8
+ autoprefixer: {},
9
+ },
10
+ };
public/favicon.svg ADDED
src/App.tsx ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
3
+ import Navbar from './components/Navbar';
4
+ import Home from './pages/Home';
5
+ import About from './pages/About';
6
+ import ApiDocs from './pages/ApiDocs';
7
+ import Tasks from './pages/Tasks';
8
+
9
+ const App: React.FC = () => {
10
+ return (
11
+ <Router>
12
+ <div className="min-h-screen bg-gray-50 flex flex-col">
13
+ <Navbar />
14
+ <main className="flex-grow container mx-auto px-4 py-8 sm:px-6 lg:px-8">
15
+ <Routes>
16
+ <Route path="/" element={<Home />} />
17
+ <Route path="/tasks" element={<Tasks />} />
18
+ <Route path="/about" element={<About />} />
19
+ <Route path="/api-docs" element={<ApiDocs />} />
20
+ </Routes>
21
+ </main>
22
+ <footer className="bg-white border-t border-gray-200 py-8">
23
+ <div className="container mx-auto px-4 text-center text-gray-500 text-sm">
24
+ <p>© {new Date().getFullYear()} Koa Web 应用 - 极致性能,全中文支持</p>
25
+ </div>
26
+ </footer>
27
+ </div>
28
+ </Router>
29
+ );
30
+ };
31
+
32
+ export default App;
src/assets/react.svg ADDED
src/components/Empty.tsx ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import { cn } from '@/lib/utils'
2
+
3
+ // Empty component
4
+ export default function Empty() {
5
+ return (
6
+ <div className={cn('flex h-full items-center justify-center')}>Empty</div>
7
+ )
8
+ }
src/components/Navbar.tsx ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import { Link, useLocation } from 'react-router-dom';
3
+ import { Menu, X, Activity } from 'lucide-react';
4
+
5
+ const Navbar: React.FC = () => {
6
+ const [isOpen, setIsOpen] = useState(false);
7
+ const location = useLocation();
8
+
9
+ const navItems = [
10
+ { name: '首页', path: '/' },
11
+ { name: '任务管理', path: '/tasks' },
12
+ { name: '关于', path: '/about' },
13
+ { name: 'API文档', path: '/api-docs' },
14
+ ];
15
+
16
+ return (
17
+ <nav className="bg-blue-900 text-white shadow-lg sticky top-0 z-50">
18
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
19
+ <div className="flex items-center justify-between h-16">
20
+ <div className="flex items-center">
21
+ <Link to="/" className="flex items-center space-x-2">
22
+ <Activity className="h-8 w-8 text-blue-400" />
23
+ <span className="font-bold text-xl">Koa Web 应用</span>
24
+ </Link>
25
+ </div>
26
+
27
+ <div className="hidden md:block">
28
+ <div className="ml-10 flex items-baseline space-x-4">
29
+ {navItems.map((item) => (
30
+ <Link
31
+ key={item.path}
32
+ to={item.path}
33
+ className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
34
+ location.pathname === item.path
35
+ ? 'bg-blue-800 text-white'
36
+ : 'text-blue-100 hover:bg-blue-800 hover:text-white'
37
+ }`}
38
+ >
39
+ {item.name}
40
+ </Link>
41
+ ))}
42
+ </div>
43
+ </div>
44
+
45
+ <div className="md:hidden">
46
+ <button
47
+ onClick={() => setIsOpen(!isOpen)}
48
+ className="inline-flex items-center justify-center p-2 rounded-md text-blue-100 hover:text-white hover:bg-blue-800 focus:outline-none"
49
+ >
50
+ {isOpen ? <X className="h-6 w-6" /> : <Menu className="h-6 w-6" />}
51
+ </button>
52
+ </div>
53
+ </div>
54
+ </div>
55
+
56
+ {/* Mobile menu */}
57
+ {isOpen && (
58
+ <div className="md:hidden bg-blue-900 border-t border-blue-800">
59
+ <div className="px-2 pt-2 pb-3 space-y-1 sm:px-3">
60
+ {navItems.map((item) => (
61
+ <Link
62
+ key={item.path}
63
+ to={item.path}
64
+ onClick={() => setIsOpen(false)}
65
+ className={`block px-3 py-2 rounded-md text-base font-medium ${
66
+ location.pathname === item.path
67
+ ? 'bg-blue-800 text-white'
68
+ : 'text-blue-100 hover:bg-blue-800 hover:text-white'
69
+ }`}
70
+ >
71
+ {item.name}
72
+ </Link>
73
+ ))}
74
+ </div>
75
+ </div>
76
+ )}
77
+ </nav>
78
+ );
79
+ };
80
+
81
+ export default Navbar;
src/hooks/useTheme.ts ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect } from 'react';
2
+
3
+ type Theme = 'light' | 'dark';
4
+
5
+ export function useTheme() {
6
+ const [theme, setTheme] = useState<Theme>(() => {
7
+ const savedTheme = localStorage.getItem('theme') as Theme;
8
+ if (savedTheme) {
9
+ return savedTheme;
10
+ }
11
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
12
+ });
13
+
14
+ useEffect(() => {
15
+ document.documentElement.classList.remove('light', 'dark');
16
+ document.documentElement.classList.add(theme);
17
+ localStorage.setItem('theme', theme);
18
+ }, [theme]);
19
+
20
+ const toggleTheme = () => {
21
+ setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
22
+ };
23
+
24
+ return {
25
+ theme,
26
+ toggleTheme,
27
+ isDark: theme === 'dark'
28
+ };
29
+ }
src/index.css ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ :root {
6
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
7
+ line-height: 1.5;
8
+ font-weight: 400;
9
+ }
10
+
11
+ body {
12
+ margin: 0;
13
+ min-width: 320px;
14
+ min-height: 100vh;
15
+ @apply bg-gray-50 text-gray-900;
16
+ }
17
+
18
+ @layer components {
19
+ .btn-primary {
20
+ @apply bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg font-medium transition-all active:scale-95;
21
+ }
22
+ }
23
+
24
+ /* 自定义动画 */
25
+ @keyframes fadeIn {
26
+ from { opacity: 0; transform: translateY(10px); }
27
+ to { opacity: 1; transform: translateY(0); }
28
+ }
29
+
30
+ .animate-in {
31
+ animation: fadeIn 0.5s ease-out forwards;
32
+ }
src/lib/utils.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ import { clsx, type ClassValue } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
src/main.tsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import App from './App'
4
+ import './index.css'
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
src/pages/About.tsx ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Shield, Zap, Globe, Layout, Package, Code } from 'lucide-react';
3
+
4
+ const About: React.FC = () => {
5
+ const features = [
6
+ {
7
+ icon: <Globe className="h-6 w-6 text-blue-500" />,
8
+ title: '完全汉化',
9
+ description: '所有界面元素、注释和文档均使用中文,对国内开发者友好。'
10
+ },
11
+ {
12
+ icon: <Layout className="h-6 w-6 text-purple-500" />,
13
+ title: '响应式布局',
14
+ description: '基于 Tailwind CSS 构建,完美适配手机、平板和桌面设备。'
15
+ },
16
+ {
17
+ icon: <Zap className="h-6 w-6 text-yellow-500" />,
18
+ title: '高性能',
19
+ description: '利用 Koa 的异步中间件机制和 React 的高效渲染能力。'
20
+ },
21
+ {
22
+ icon: <Shield className="h-6 w-6 text-green-500" />,
23
+ title: '生产就绪',
24
+ description: '提供 Docker 和 Hugging Face 部署配置,支持快速上线。'
25
+ }
26
+ ];
27
+
28
+ const techStack = [
29
+ { name: 'React', version: '18.x', type: '前端框架' },
30
+ { name: 'Koa', version: '2.x', type: '后端框架' },
31
+ { name: 'TypeScript', version: '5.x', type: '开发语言' },
32
+ { name: 'Tailwind CSS', version: '3.x', type: '样式框架' },
33
+ { name: 'Vite', version: '5.x', type: '构建工具' },
34
+ { name: 'Lucide React', version: '最新', type: '图标库' }
35
+ ];
36
+
37
+ return (
38
+ <div className="space-y-12 animate-in fade-in slide-in-from-bottom-4 duration-700">
39
+ <section className="text-center">
40
+ <h1 className="text-3xl font-bold text-blue-900 mb-4">关于项目</h1>
41
+ <p className="text-gray-600 max-w-3xl mx-auto">
42
+ Koa Web 应用是一个示例性质的完整 Web 项目,旨在展示如何结合现代前端技术与稳健的后端框架
43
+ 来构建可扩展、易维护的应用程序。
44
+ </p>
45
+ </section>
46
+
47
+ <section>
48
+ <h2 className="text-2xl font-bold text-blue-900 mb-8 text-center">核心特性</h2>
49
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
50
+ {features.map((feature, index) => (
51
+ <div key={index} className="flex space-x-4 bg-white p-6 rounded-2xl shadow-sm border border-gray-100">
52
+ <div className="flex-shrink-0">{feature.icon}</div>
53
+ <div>
54
+ <h3 className="text-lg font-bold text-gray-900 mb-1">{feature.title}</h3>
55
+ <p className="text-gray-600">{feature.description}</p>
56
+ </div>
57
+ </div>
58
+ ))}
59
+ </div>
60
+ </section>
61
+
62
+ <section>
63
+ <h2 className="text-2xl font-bold text-blue-900 mb-8 text-center">技术栈说明</h2>
64
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
65
+ {techStack.map((tech, index) => (
66
+ <div key={index} className="bg-blue-50 p-4 rounded-xl border border-blue-100 text-center">
67
+ <div className="text-blue-900 font-bold mb-1">{tech.name}</div>
68
+ <div className="text-blue-700 text-xs mb-1">{tech.version}</div>
69
+ <div className="text-gray-500 text-[10px] uppercase tracking-wider">{tech.type}</div>
70
+ </div>
71
+ ))}
72
+ </div>
73
+ </section>
74
+
75
+ <section className="bg-gray-900 text-white p-12 rounded-3xl text-center">
76
+ <Package className="h-12 w-12 text-blue-400 mx-auto mb-6" />
77
+ <h2 className="text-2xl font-bold mb-4">想要贡献代码?</h2>
78
+ <p className="text-gray-400 mb-8 max-w-2xl mx-auto">
79
+ 本项目是开源的,欢迎通过提交 Pull Request 或 Issue 来帮助我们改进。
80
+ 我们重视每一位开发者的建议。
81
+ </p>
82
+ <button className="bg-blue-600 hover:bg-blue-700 text-white px-8 py-3 rounded-full font-bold transition-colors">
83
+ 访问 GitHub 仓库
84
+ </button>
85
+ </section>
86
+ </div>
87
+ );
88
+ };
89
+
90
+ export default About;
src/pages/ApiDocs.tsx ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Copy, Terminal, ExternalLink } from 'lucide-react';
3
+
4
+ const ApiDocs: React.FC = () => {
5
+ const apis = [
6
+ {
7
+ method: 'GET',
8
+ path: '/api/health',
9
+ description: '健康检查接口,用于监控服务状态',
10
+ example: '{ "status": "ok", "timestamp": 1710123456789 }'
11
+ },
12
+ {
13
+ method: 'GET',
14
+ path: '/api/info',
15
+ description: '系统信息接口,返回运行平台、Node 版本和内存使用情况',
16
+ example: '{ "nodeVersion": "v18.17.0", "platform": "linux", "memoryUsage": { "used": 45.2, "total": 128, "unit": "MB" } }'
17
+ },
18
+ {
19
+ method: 'GET',
20
+ path: '/api/time',
21
+ description: '服务器时间接口,返回当前服务器的本地时间和 UTC 时间',
22
+ example: '{ "serverTime": "2024-03-11 10:00:00", "timezone": "Asia/Shanghai", "utc": "2024-03-11T02:00:00.000Z" }'
23
+ }
24
+ ];
25
+
26
+ return (
27
+ <div className="max-w-4xl mx-auto space-y-12 animate-in fade-in duration-500">
28
+ <section>
29
+ <h1 className="text-3xl font-bold text-blue-900 mb-4">API 文档</h1>
30
+ <p className="text-gray-600">
31
+ 后端服务提供以下 RESTful API 接口。所有接口均返回 JSON 格式数据。
32
+ 默认 API 前缀为 <code className="bg-gray-100 px-1 rounded">/api</code>。
33
+ </p>
34
+ </section>
35
+
36
+ <section className="space-y-8">
37
+ {apis.map((api, index) => (
38
+ <div key={index} className="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
39
+ <div className="p-6">
40
+ <div className="flex items-center justify-between mb-4">
41
+ <div className="flex items-center space-x-3">
42
+ <span className={`px-3 py-1 rounded-full text-xs font-bold ${
43
+ api.method === 'GET' ? 'bg-green-100 text-green-700' : 'bg-blue-100 text-blue-700'
44
+ }`}>
45
+ {api.method}
46
+ </span>
47
+ <code className="text-lg font-mono font-bold text-gray-900">{api.path}</code>
48
+ </div>
49
+ <button
50
+ onClick={() => navigator.clipboard.writeText(api.path)}
51
+ className="text-gray-400 hover:text-blue-600 transition-colors"
52
+ title="复制代码"
53
+ >
54
+ <Copy className="h-4 w-4" />
55
+ </button>
56
+ </div>
57
+ <p className="text-gray-600 mb-6">{api.description}</p>
58
+
59
+ <div className="space-y-3">
60
+ <div className="flex items-center space-x-2 text-sm font-medium text-gray-500">
61
+ <Terminal className="h-4 w-4" />
62
+ <span>响应示例</span>
63
+ </div>
64
+ <pre className="bg-gray-900 text-blue-400 p-4 rounded-xl overflow-x-auto font-mono text-sm leading-relaxed">
65
+ {JSON.stringify(JSON.parse(api.example), null, 2)}
66
+ </pre>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ ))}
71
+ </section>
72
+
73
+ <section className="bg-blue-900 text-white p-8 rounded-3xl">
74
+ <h2 className="text-xl font-bold mb-4 flex items-center">
75
+ <ExternalLink className="h-5 w-5 mr-2" />
76
+ 错误处理
77
+ </h2>
78
+ <p className="text-blue-200 mb-4">
79
+ 当发生错误时,API 会返回 4xx 或 5xx 状态码,并包含以下格式的错误信息:
80
+ </p>
81
+ <pre className="bg-blue-950 p-4 rounded-xl text-blue-300 font-mono text-sm">
82
+ {`{
83
+ "success": false,
84
+ "error": "错误描述信息"
85
+ }`}
86
+ </pre>
87
+ </section>
88
+ </div>
89
+ );
90
+ };
91
+
92
+ export default ApiDocs;
src/pages/Home.tsx ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useEffect, useState } from 'react';
2
+ import { Cpu, Server, Clock, CheckCircle } from 'lucide-react';
3
+
4
+ interface SystemInfo {
5
+ nodeVersion: string;
6
+ platform: string;
7
+ memoryUsage: {
8
+ used: number;
9
+ total: number;
10
+ unit: string;
11
+ };
12
+ }
13
+
14
+ interface ServerTime {
15
+ serverTime: string;
16
+ timezone: string;
17
+ }
18
+
19
+ const Home: React.FC = () => {
20
+ const [systemInfo, setSystemInfo] = useState<SystemInfo | null>(null);
21
+ const [serverTime, setServerTime] = useState<ServerTime | null>(null);
22
+ const [loading, setLoading] = useState(true);
23
+
24
+ const fetchData = async () => {
25
+ try {
26
+ const [infoRes, timeRes] = await Promise.all([
27
+ fetch('/api/info'),
28
+ fetch('/api/time')
29
+ ]);
30
+ const infoData = await infoRes.json();
31
+ const timeData = await timeRes.json();
32
+ setSystemInfo(infoData);
33
+ setServerTime(timeData);
34
+ } catch (error) {
35
+ console.error('获取数据失败:', error);
36
+ } finally {
37
+ setLoading(false);
38
+ }
39
+ };
40
+
41
+ useEffect(() => {
42
+ fetchData();
43
+ const interval = setInterval(fetchData, 5000);
44
+ return () => clearInterval(interval);
45
+ }, []);
46
+
47
+ return (
48
+ <div className="space-y-8 animate-in fade-in duration-500">
49
+ <section className="text-center py-12 bg-white rounded-2xl shadow-sm border border-gray-100">
50
+ <h1 className="text-4xl font-extrabold text-blue-900 mb-4">
51
+ 欢迎使用 Koa Web 应用
52
+ </h1>
53
+ <p className="text-lg text-gray-600 max-w-2xl mx-auto px-4">
54
+ 这是一个基于 Koa 和 React 构建的高性能 Web 服务框架。
55
+ 项目已完全汉化,支持移动端适配,并具备容器化部署能力。
56
+ </p>
57
+ </section>
58
+
59
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
60
+ {/* 系统状态卡片 */}
61
+ <StatusCard
62
+ icon={<CheckCircle className="h-6 w-6 text-green-500" />}
63
+ title="运行状态"
64
+ value="正常"
65
+ description="系统服务运行中"
66
+ />
67
+ <StatusCard
68
+ icon={<Cpu className="h-6 w-6 text-blue-500" />}
69
+ title="内存使用"
70
+ value={loading ? '加载中...' : `${systemInfo?.memoryUsage.used} / ${systemInfo?.memoryUsage.total} ${systemInfo?.memoryUsage.unit}`}
71
+ description="当前堆内存占用"
72
+ />
73
+ <StatusCard
74
+ icon={<Server className="h-6 w-6 text-purple-500" />}
75
+ title="运行平台"
76
+ value={loading ? '加载中...' : systemInfo?.platform || '未知'}
77
+ description={`Node.js ${systemInfo?.nodeVersion || ''}`}
78
+ />
79
+ <StatusCard
80
+ icon={<Clock className="h-6 w-6 text-orange-500" />}
81
+ title="服务器时间"
82
+ value={loading ? '加载中...' : serverTime?.serverTime.split(' ')[1] || ''}
83
+ description={serverTime?.serverTime.split(' ')[0] || ''}
84
+ />
85
+ </div>
86
+
87
+ <section className="bg-blue-50 p-8 rounded-2xl border border-blue-100">
88
+ <h2 className="text-2xl font-bold text-blue-900 mb-4">快速开始</h2>
89
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
90
+ <div className="flex flex-col space-y-2">
91
+ <span className="font-semibold text-blue-800">1. 安装依赖</span>
92
+ <code className="bg-blue-100 p-2 rounded text-sm text-blue-900">npm install</code>
93
+ </div>
94
+ <div className="flex flex-col space-y-2">
95
+ <span className="font-semibold text-blue-800">2. 启动开发环境</span>
96
+ <code className="bg-blue-100 p-2 rounded text-sm text-blue-900">npm run dev</code>
97
+ </div>
98
+ <div className="flex flex-col space-y-2">
99
+ <span className="font-semibold text-blue-800">3. 生产部署</span>
100
+ <code className="bg-blue-100 p-2 rounded text-sm text-blue-900">docker-compose up -d</code>
101
+ </div>
102
+ </div>
103
+ </section>
104
+ </div>
105
+ );
106
+ };
107
+
108
+ interface StatusCardProps {
109
+ icon: React.ReactNode;
110
+ title: string;
111
+ value: string;
112
+ description: string;
113
+ }
114
+
115
+ const StatusCard: React.FC<StatusCardProps> = ({ icon, title, value, description }) => (
116
+ <div className="bg-white p-6 rounded-2xl shadow-sm border border-gray-100 hover:shadow-md transition-shadow">
117
+ <div className="flex items-center space-x-3 mb-4">
118
+ {icon}
119
+ <h3 className="text-sm font-medium text-gray-500 uppercase tracking-wider">{title}</h3>
120
+ </div>
121
+ <div className="text-2xl font-bold text-gray-900 mb-1">{value}</div>
122
+ <div className="text-xs text-gray-500">{description}</div>
123
+ </div>
124
+ );
125
+
126
+ export default Home;
src/pages/Tasks.tsx ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useEffect, useState } from 'react';
2
+ import { CheckCircle2, Circle, ListTodo, Activity } from 'lucide-react';
3
+
4
+ interface Task {
5
+ id: number;
6
+ title: string;
7
+ completed: boolean;
8
+ category: string;
9
+ }
10
+
11
+ interface Stats {
12
+ activeUsers: number;
13
+ totalRequests: number;
14
+ uptime: number;
15
+ version: string;
16
+ }
17
+
18
+ const Tasks: React.FC = () => {
19
+ const [tasks, setTasks] = useState<Task[]>([]);
20
+ const [stats, setStats] = useState<Stats | null>(null);
21
+ const [loading, setLoading] = useState(true);
22
+ const [error, setError] = useState<string | null>(null);
23
+
24
+ useEffect(() => {
25
+ const fetchData = async () => {
26
+ try {
27
+ const [tasksRes, statsRes] = await Promise.all([
28
+ fetch('/api/data/tasks'),
29
+ fetch('/api/data/stats')
30
+ ]);
31
+
32
+ if (!tasksRes.ok || !statsRes.ok) {
33
+ throw new Error('获取数据失败');
34
+ }
35
+
36
+ const tasksData = await tasksRes.json();
37
+ const statsData = await statsRes.json();
38
+
39
+ if (tasksData.success) setTasks(tasksData.data);
40
+ if (statsData.success) setStats(statsData.data);
41
+ } catch (err) {
42
+ setError(err instanceof Error ? err.message : '未知错误');
43
+ } finally {
44
+ setLoading(false);
45
+ }
46
+ };
47
+
48
+ fetchData();
49
+ }, []);
50
+
51
+ if (loading) {
52
+ return (
53
+ <div className="flex items-center justify-center min-h-[400px]">
54
+ <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
55
+ </div>
56
+ );
57
+ }
58
+
59
+ if (error) {
60
+ return (
61
+ <div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg text-center my-8">
62
+ <p>出错了: {error}</p>
63
+ </div>
64
+ );
65
+ }
66
+
67
+ return (
68
+ <div className="max-w-4xl mx-auto space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
69
+ <div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
70
+ <div>
71
+ <h1 className="text-3xl font-bold text-gray-900 flex items-center gap-2">
72
+ <ListTodo className="h-8 w-8 text-blue-600" />
73
+ 任务管理
74
+ </h1>
75
+ <p className="mt-2 text-gray-600">展示从后端接口获取的实时数据示例</p>
76
+ </div>
77
+ </div>
78
+
79
+ {/* 统计卡片 */}
80
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
81
+ <div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm">
82
+ <div className="flex items-center gap-3 text-blue-600 mb-2">
83
+ <Activity className="h-5 w-5" />
84
+ <span className="text-sm font-medium">活跃用户</span>
85
+ </div>
86
+ <p className="text-2xl font-bold text-gray-900">{stats?.activeUsers}</p>
87
+ </div>
88
+ <div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm">
89
+ <div className="flex items-center gap-3 text-green-600 mb-2">
90
+ <CheckCircle2 className="h-5 w-5" />
91
+ <span className="text-sm font-medium">总请求数</span>
92
+ </div>
93
+ <p className="text-2xl font-bold text-gray-900">{stats?.totalRequests}</p>
94
+ </div>
95
+ <div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm">
96
+ <div className="flex items-center gap-3 text-purple-600 mb-2">
97
+ <Activity className="h-5 w-5" />
98
+ <span className="text-sm font-medium">运行时长</span>
99
+ </div>
100
+ <p className="text-2xl font-bold text-gray-900">{Math.floor(stats?.uptime || 0)}s</p>
101
+ </div>
102
+ <div className="bg-white p-6 rounded-xl border border-gray-200 shadow-sm">
103
+ <div className="flex items-center gap-3 text-orange-600 mb-2">
104
+ <CheckCircle2 className="h-5 w-5" />
105
+ <span className="text-sm font-medium">版本</span>
106
+ </div>
107
+ <p className="text-2xl font-bold text-gray-900">{stats?.version}</p>
108
+ </div>
109
+ </div>
110
+
111
+ {/* 任务列表 */}
112
+ <div className="bg-white border border-gray-200 rounded-xl shadow-sm overflow-hidden">
113
+ <div className="px-6 py-4 border-b border-gray-200 bg-gray-50/50">
114
+ <h2 className="font-semibold text-gray-900">项目进度</h2>
115
+ </div>
116
+ <div className="divide-y divide-gray-100">
117
+ {tasks.map((task) => (
118
+ <div key={task.id} className="px-6 py-4 flex items-center justify-between hover:bg-gray-50 transition-colors">
119
+ <div className="flex items-center gap-3">
120
+ {task.completed ? (
121
+ <CheckCircle2 className="h-5 w-5 text-green-500" />
122
+ ) : (
123
+ <Circle className="h-5 w-5 text-gray-300" />
124
+ )}
125
+ <div>
126
+ <p className={`font-medium ${task.completed ? 'text-gray-400 line-through' : 'text-gray-900'}`}>
127
+ {task.title}
128
+ </p>
129
+ <span className="text-xs text-gray-500 bg-gray-100 px-2 py-0.5 rounded-full">{task.category}</span>
130
+ </div>
131
+ </div>
132
+ <span className={`text-sm px-2.5 py-1 rounded-full ${
133
+ task.completed ? 'bg-green-100 text-green-700' : 'bg-blue-100 text-blue-700'
134
+ }`}>
135
+ {task.completed ? '已完成' : '进行中'}
136
+ </span>
137
+ </div>
138
+ ))}
139
+ </div>
140
+ </div>
141
+ </div>
142
+ );
143
+ };
144
+
145
+ export default Tasks;
src/vite-env.d.ts ADDED
@@ -0,0 +1 @@
 
 
1
+ /// <reference types="vite/client" />
tailwind.config.js ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+
3
+ export default {
4
+ darkMode: "class",
5
+ content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
6
+ theme: {
7
+ container: {
8
+ center: true,
9
+ },
10
+ extend: {},
11
+ },
12
+ plugins: [],
13
+ };
tsconfig.json ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "ES2020",
5
+ "useDefineForClassFields": true,
6
+ "lib": [
7
+ "ES2020",
8
+ "DOM",
9
+ "DOM.Iterable"
10
+ ],
11
+ "module": "ESNext",
12
+ "skipLibCheck": true,
13
+ "moduleResolution": "bundler",
14
+ "allowImportingTsExtensions": true,
15
+ "verbatimModuleSyntax": false,
16
+ "moduleDetection": "force",
17
+ "noEmit": true,
18
+ "jsx": "react-jsx",
19
+ "strict": false,
20
+ "noUnusedLocals": false,
21
+ "noUnusedParameters": false,
22
+ "noFallthroughCasesInSwitch": false,
23
+ "noUncheckedSideEffectImports": false,
24
+ "forceConsistentCasingInFileNames": false,
25
+ "baseUrl": "./",
26
+ "paths": {
27
+ "@/*": [
28
+ "./src/*"
29
+ ]
30
+ },
31
+ "types": [
32
+ "node",
33
+ "express"
34
+ ]
35
+ },
36
+ "include": [
37
+ "src",
38
+ "api"
39
+ ]
40
+ }
vercel.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "rewrites": [
3
+ {
4
+ "source": "/api/(.*)",
5
+ "destination": "/api/index"
6
+ },
7
+ {
8
+ "source": "/(.*)",
9
+ "destination": "/index.html"
10
+ }
11
+ ]
12
+ }
vite.config.ts ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+ import tsconfigPaths from "vite-tsconfig-paths";
4
+ import { traeBadgePlugin } from 'vite-plugin-trae-solo-badge';
5
+
6
+ // https://vite.dev/config/
7
+ export default defineConfig({
8
+ plugins: [
9
+ react({
10
+ babel: {
11
+ plugins: [
12
+ 'react-dev-locator',
13
+ ],
14
+ },
15
+ }),
16
+ traeBadgePlugin({
17
+ variant: 'dark',
18
+ position: 'bottom-right',
19
+ prodOnly: true,
20
+ clickable: true,
21
+ clickUrl: 'https://www.trae.ai/solo?showJoin=1',
22
+ autoTheme: true,
23
+ autoThemeTarget: '#root'
24
+ }),
25
+ tsconfigPaths(),
26
+ ],
27
+ server: {
28
+ proxy: {
29
+ '/api': {
30
+ target: 'http://localhost:3001',
31
+ changeOrigin: true,
32
+ secure: false,
33
+ configure: (proxy, _options) => {
34
+ proxy.on('error', (err, _req, _res) => {
35
+ console.log('proxy error', err);
36
+ });
37
+ proxy.on('proxyReq', (proxyReq, req, _res) => {
38
+ console.log('Sending Request to the Target:', req.method, req.url);
39
+ });
40
+ proxy.on('proxyRes', (proxyRes, req, _res) => {
41
+ console.log('Received Response from the Target:', proxyRes.statusCode, req.url);
42
+ });
43
+ },
44
+ }
45
+ }
46
+ }
47
+ })