diff --git a/backend/app/core/config.py b/backend/app/core/config.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/main.py b/backend/main.py index cdd6725..9149eef 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,5 +1,5 @@ from fastapi import FastAPI - +from pydantic import BaseModel app = FastAPI() @app.get("/") def read_root(): diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 0000000..8ed17e0 --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1,2 @@ +# API 基础地址 +VITE_API_BASE_URL=http://localhost:8000/api/v1 diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..33895ab --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,5 @@ +# Vue 3 + TypeScript + Vite + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..2bcf4d4 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,34 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@vueuse/core": "^14.2.1", + "axios": "^1.13.6", + "naive-ui": "^2.44.1", + "pinia": "^3.0.4", + "vue": "^3.5.30", + "vue-router": "^5.0.3" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4.2.1", + "@tailwindcss/typography": "^0.5.19", + "@types/node": "^24.12.0", + "@vitejs/plugin-vue": "^6.0.5", + "@vue/tsconfig": "^0.9.0", + "autoprefixer": "^10.4.27", + "lightningcss": "^1.32.0", + "lightningcss-win32-x64-msvc": "^1.32.0", + "postcss": "^8.5.8", + "tailwindcss": "^4.2.1", + "typescript": "~5.9.3", + "vite": "^8.0.0", + "vue-tsc": "^3.2.5" + } +} diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..1c87846 --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + '@tailwindcss/postcss': {}, + autoprefixer: {}, + }, +} diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/icons.svg b/frontend/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/frontend/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..c7c8bcb --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,15 @@ + + + + + diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts new file mode 100644 index 0000000..06ac2f0 --- /dev/null +++ b/frontend/src/api/auth.ts @@ -0,0 +1,29 @@ +import { http } from './index' +import type { UserLoginRequest, UserRegisterRequest, TokenResponse, User } from '@/types' + +export const authApi = { + // 用户登录 + login(data: UserLoginRequest) { + return http.post('/auth/login', data) + }, + + // 用户注册 + register(data: UserRegisterRequest) { + return http.post('/auth/register', data) + }, + + // 获取当前用户信息 + getCurrentUser() { + return http.get('/auth/me') + }, + + // 刷新 Token + refreshToken(refreshToken: string) { + return http.post('/auth/refresh', { refresh_token: refreshToken }) + }, + + // 登出 + logout() { + return http.post('/auth/logout') + }, +} diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts new file mode 100644 index 0000000..3355cd2 --- /dev/null +++ b/frontend/src/api/index.ts @@ -0,0 +1,69 @@ +import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse } from 'axios' +import type { ApiResponse } from '@/types' + +// 创建 axios 实例 +const request: AxiosInstance = axios.create({ + baseURL: import.meta.env.VITE_API_BASE_URL || '/api/v1', + timeout: 10000, + headers: { + 'Content-Type': 'application/json', + }, +}) + +// 请求拦截器 +request.interceptors.request.use( + (config: any) => { + // 从 localStorage 获取 token + const token = localStorage.getItem('access_token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config + }, + (error: any) => { + return Promise.reject(error) + } +) + +// 响应拦截器 +request.interceptors.response.use( + (response: AxiosResponse) => { + return response + }, + (error: any) => { + if (error.response) { + const { status } = error.response + + // 401 未授权,跳转登录页 + if (status === 401) { + localStorage.removeItem('access_token') + window.location.href = '/login' + } + + return Promise.reject(error.response.data) + } + + return Promise.reject(error) + } +) + +// 封装请求方法 +export const http = { + get(url: string, config?: AxiosRequestConfig): Promise> { + return request.get(url, config) + }, + + post(url: string, data?: any, config?: AxiosRequestConfig): Promise> { + return request.post(url, data, config) + }, + + put(url: string, data?: any, config?: AxiosRequestConfig): Promise> { + return request.put(url, data, config) + }, + + delete(url: string, config?: AxiosRequestConfig): Promise> { + return request.delete(url, config) + }, +} + +export default request diff --git a/frontend/src/api/post.ts b/frontend/src/api/post.ts new file mode 100644 index 0000000..7a6a085 --- /dev/null +++ b/frontend/src/api/post.ts @@ -0,0 +1,34 @@ +import { http } from './index' +import type { Post, PostCreateRequest, PostUpdateRequest, PostListResponse, PageParams } from '@/types' + +export const postApi = { + // 获取文章列表 + getList(params?: PageParams & { category_id?: string; tag_id?: string }) { + return http.get('/posts', { params }) + }, + + // 获取单篇文章 + getDetail(id: string) { + return http.get(`/posts/${id}`) + }, + + // 创建文章 + create(data: PostCreateRequest) { + return http.post('/posts', data) + }, + + // 更新文章 + update(id: string, data: PostUpdateRequest) { + return http.put(`/posts/${id}`, data) + }, + + // 删除文章 + delete(id: string) { + return http.delete(`/posts/${id}`) + }, + + // 增加浏览量 + incrementView(id: string) { + return http.post(`/posts/${id}/view`) + }, +} diff --git a/frontend/src/assets/hero.png b/frontend/src/assets/hero.png new file mode 100644 index 0000000..cc51a3d Binary files /dev/null and b/frontend/src/assets/hero.png differ diff --git a/frontend/src/assets/vite.svg b/frontend/src/assets/vite.svg new file mode 100644 index 0000000..5101b67 --- /dev/null +++ b/frontend/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/frontend/src/assets/vue.svg b/frontend/src/assets/vue.svg new file mode 100644 index 0000000..770e9d3 --- /dev/null +++ b/frontend/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/components/Footer.vue b/frontend/src/components/Footer.vue new file mode 100644 index 0000000..de1e35b --- /dev/null +++ b/frontend/src/components/Footer.vue @@ -0,0 +1,227 @@ + + + + + diff --git a/frontend/src/components/Hero.vue b/frontend/src/components/Hero.vue new file mode 100644 index 0000000..f4c8290 --- /dev/null +++ b/frontend/src/components/Hero.vue @@ -0,0 +1,267 @@ + + + + + diff --git a/frontend/src/components/Navbar.vue b/frontend/src/components/Navbar.vue new file mode 100644 index 0000000..eb179f6 --- /dev/null +++ b/frontend/src/components/Navbar.vue @@ -0,0 +1,179 @@ + + + + + diff --git a/frontend/src/components/PostCard.vue b/frontend/src/components/PostCard.vue new file mode 100644 index 0000000..f9b3f5d --- /dev/null +++ b/frontend/src/components/PostCard.vue @@ -0,0 +1,251 @@ + + + + + diff --git a/frontend/src/components/Sidebar.vue b/frontend/src/components/Sidebar.vue new file mode 100644 index 0000000..7ebac98 --- /dev/null +++ b/frontend/src/components/Sidebar.vue @@ -0,0 +1,321 @@ + + + + + diff --git a/frontend/src/layouts/AdminLayout.vue b/frontend/src/layouts/AdminLayout.vue new file mode 100644 index 0000000..4f79b49 --- /dev/null +++ b/frontend/src/layouts/AdminLayout.vue @@ -0,0 +1,49 @@ + diff --git a/frontend/src/main.ts b/frontend/src/main.ts new file mode 100644 index 0000000..9279b89 --- /dev/null +++ b/frontend/src/main.ts @@ -0,0 +1,12 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import App from './App.vue' +import router from './router' +// import naive-ui (按需引入,后续按需配置) + +const app = createApp(App) + +app.use(createPinia()) +app.use(router) + +app.mount('#app') diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts new file mode 100644 index 0000000..7eb8025 --- /dev/null +++ b/frontend/src/router/index.ts @@ -0,0 +1,99 @@ +import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router' +import { useUserStore } from '@/store/user' + +const routes: RouteRecordRaw[] = [ + { + path: '/', + name: 'Home', + component: () => import('@/views/Home.vue'), + }, + { + path: '/post/:id', + name: 'PostDetail', + component: () => import('@/views/PostDetail.vue'), + }, + { + path: '/category/:id', + name: 'Category', + component: () => import('@/views/Category.vue'), + }, + { + path: '/about', + name: 'About', + component: () => import('@/views/About.vue'), + }, + { + path: '/login', + name: 'Login', + component: () => import('@/views/auth/Login.vue'), + }, + { + path: '/register', + name: 'Register', + component: () => import('@/views/auth/Register.vue'), + }, + { + path: '/profile', + name: 'Profile', + component: () => import('@/views/user/Profile.vue'), + meta: { requiresAuth: true }, + }, + { + path: '/admin', + name: 'Admin', + component: () => import('@/layouts/AdminLayout.vue'), + meta: { requiresAuth: true, requiresAdmin: true }, + children: [ + { + path: '', + redirect: '/admin/dashboard', + }, + { + path: 'dashboard', + name: 'AdminDashboard', + component: () => import('@/views/admin/Dashboard.vue'), + }, + { + path: 'posts', + name: 'AdminPosts', + component: () => import('@/views/admin/PostManage.vue'), + }, + { + path: 'categories', + name: 'AdminCategories', + component: () => import('@/views/admin/CategoryManage.vue'), + }, + { + path: 'tags', + name: 'AdminTags', + component: () => import('@/views/admin/TagManage.vue'), + }, + ], + }, + { + path: '/:pathMatch(.*)*', + name: 'NotFound', + component: () => import('@/views/NotFound.vue'), + }, +] + +const router = createRouter({ + history: createWebHistory(), + routes, +}) + +// 路由守卫 +router.beforeEach((to, _from, next) => { + const userStore = useUserStore() + const token = localStorage.getItem('access_token') + + if (to.meta.requiresAuth && !token) { + next({ name: 'Login' }) + } else if (to.meta.requiresAdmin && userStore.user?.is_active !== true) { + next({ name: 'Home' }) + } else { + next() + } +}) + +export default router diff --git a/frontend/src/store/index.ts b/frontend/src/store/index.ts new file mode 100644 index 0000000..88d657a --- /dev/null +++ b/frontend/src/store/index.ts @@ -0,0 +1,2 @@ +export { useUserStore } from './user' +export { useThemeStore } from './theme' diff --git a/frontend/src/store/theme.ts b/frontend/src/store/theme.ts new file mode 100644 index 0000000..acd1acd --- /dev/null +++ b/frontend/src/store/theme.ts @@ -0,0 +1,43 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' + +type Theme = 'light' | 'dark' | 'auto' + +export const useThemeStore = defineStore('theme', () => { + // 状态 + const theme = ref((localStorage.getItem('theme') as Theme) || 'light') + + // Actions + function setTheme(newTheme: Theme) { + theme.value = newTheme + localStorage.setItem('theme', newTheme) + applyTheme(newTheme) + } + + function applyTheme(t: Theme) { + const html = document.documentElement + + if (t === 'auto') { + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches + html.classList.toggle('dark', prefersDark) + } else { + html.classList.toggle('dark', t === 'dark') + } + } + + function toggleTheme() { + const themes: Theme[] = ['light', 'dark', 'auto'] + const currentIndex = themes.indexOf(theme.value) + const nextTheme = themes[(currentIndex + 1) % themes.length] + setTheme(nextTheme) + } + + // 初始化主题 + applyTheme(theme.value) + + return { + theme, + setTheme, + toggleTheme, + } +}) diff --git a/frontend/src/store/user.ts b/frontend/src/store/user.ts new file mode 100644 index 0000000..b7816d5 --- /dev/null +++ b/frontend/src/store/user.ts @@ -0,0 +1,64 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import type { User } from '@/types' +import { authApi } from '@/api/auth' + +export const useUserStore = defineStore('user', () => { + // 状态 + const user = ref(null) + const token = ref(localStorage.getItem('access_token')) + + // 计算属性 + const isLoggedIn = computed(() => !!token.value) + const isAdmin = computed(() => user.value?.is_active === true) + + // Actions + function setToken(newToken: string | null) { + token.value = newToken + if (newToken) { + localStorage.setItem('access_token', newToken) + } else { + localStorage.removeItem('access_token') + } + } + + function setUser(newUser: User | null) { + user.value = newUser + } + + async function fetchUser() { + if (!token.value) return + + try { + const response = await authApi.getCurrentUser() + setUser(response.data) + } catch (error) { + console.error('Failed to fetch user:', error) + setToken(null) + setUser(null) + } + } + + async function login(email: string, password: string) { + const response = await authApi.login({ email, password }) + setToken(response.data.access_token) + await fetchUser() + } + + function logout() { + setToken(null) + setUser(null) + } + + return { + user, + token, + isLoggedIn, + isAdmin, + setToken, + setUser, + fetchUser, + login, + logout, + } +}) diff --git a/frontend/src/style.css b/frontend/src/style.css new file mode 100644 index 0000000..be7f557 --- /dev/null +++ b/frontend/src/style.css @@ -0,0 +1,12 @@ +@import "tailwindcss"; + +/* 全局样式 */ +body { + margin: 0; + padding: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; +} + +* { + box-sizing: border-box; +} diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts new file mode 100644 index 0000000..41f8d5f --- /dev/null +++ b/frontend/src/types/index.ts @@ -0,0 +1,109 @@ +// 用户相关类型 +export interface User { + id: string + username: string + email: string + avatar?: string + is_active: boolean + created_at: string + updated_at: string +} + +export interface UserLoginRequest { + email: string + password: string +} + +export interface UserRegisterRequest { + username: string + email: string + password: string +} + +export interface TokenResponse { + access_token: string + token_type: string +} + +// 文章相关类型 +export interface Post { + id: string + title: string + slug: string + content: string + summary?: string + cover_image?: string + author: User + category?: Category + tags: Tag[] + view_count: number + status: 'draft' | 'published' | 'archived' + created_at: string + updated_at: string +} + +export interface PostCreateRequest { + title: string + content: string + summary?: string + cover_image?: string + category_id?: string + tags?: string[] + status?: 'draft' | 'published' +} + +export interface PostUpdateRequest { + title?: string + content?: string + summary?: string + cover_image?: string + category_id?: string + tags?: string[] + status?: 'draft' | 'published' | 'archived' +} + +export interface PostListResponse { + items: Post[] + total: number + page: number + page_size: number +} + +// 分类相关类型 +export interface Category { + id: string + name: string + slug: string + description?: string + created_at: string +} + +// 标签相关类型 +export interface Tag { + id: string + name: string + created_at: string +} + +// 评论相关类型 +export interface Comment { + id: string + content: string + author?: User + created_at: string + parent_id?: string + replies?: Comment[] +} + +// API 响应类型 +export interface ApiResponse { + code: number + message: string + data: T +} + +// 分页请求参数 +export interface PageParams { + page?: number + page_size?: number +} diff --git a/frontend/src/views/About.vue b/frontend/src/views/About.vue new file mode 100644 index 0000000..28d9704 --- /dev/null +++ b/frontend/src/views/About.vue @@ -0,0 +1,19 @@ + diff --git a/frontend/src/views/Category.vue b/frontend/src/views/Category.vue new file mode 100644 index 0000000..83892c5 --- /dev/null +++ b/frontend/src/views/Category.vue @@ -0,0 +1,25 @@ + + + diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue new file mode 100644 index 0000000..a7f3e23 --- /dev/null +++ b/frontend/src/views/Home.vue @@ -0,0 +1,243 @@ + + + + + diff --git a/frontend/src/views/NotFound.vue b/frontend/src/views/NotFound.vue new file mode 100644 index 0000000..51b0b0f --- /dev/null +++ b/frontend/src/views/NotFound.vue @@ -0,0 +1,13 @@ + + + diff --git a/frontend/src/views/PostDetail.vue b/frontend/src/views/PostDetail.vue new file mode 100644 index 0000000..b48e7e5 --- /dev/null +++ b/frontend/src/views/PostDetail.vue @@ -0,0 +1,25 @@ + + + diff --git a/frontend/src/views/admin/CategoryManage.vue b/frontend/src/views/admin/CategoryManage.vue new file mode 100644 index 0000000..2690663 --- /dev/null +++ b/frontend/src/views/admin/CategoryManage.vue @@ -0,0 +1,8 @@ + diff --git a/frontend/src/views/admin/Dashboard.vue b/frontend/src/views/admin/Dashboard.vue new file mode 100644 index 0000000..1b6365c --- /dev/null +++ b/frontend/src/views/admin/Dashboard.vue @@ -0,0 +1,19 @@ + diff --git a/frontend/src/views/admin/PostManage.vue b/frontend/src/views/admin/PostManage.vue new file mode 100644 index 0000000..3b03f6a --- /dev/null +++ b/frontend/src/views/admin/PostManage.vue @@ -0,0 +1,8 @@ + diff --git a/frontend/src/views/admin/TagManage.vue b/frontend/src/views/admin/TagManage.vue new file mode 100644 index 0000000..3f95e3a --- /dev/null +++ b/frontend/src/views/admin/TagManage.vue @@ -0,0 +1,8 @@ + diff --git a/frontend/src/views/auth/Login.vue b/frontend/src/views/auth/Login.vue new file mode 100644 index 0000000..cb9f09c --- /dev/null +++ b/frontend/src/views/auth/Login.vue @@ -0,0 +1,55 @@ + + + diff --git a/frontend/src/views/auth/Register.vue b/frontend/src/views/auth/Register.vue new file mode 100644 index 0000000..6e4a3fc --- /dev/null +++ b/frontend/src/views/auth/Register.vue @@ -0,0 +1,67 @@ + + + diff --git a/frontend/src/views/user/Profile.vue b/frontend/src/views/user/Profile.vue new file mode 100644 index 0000000..cd022e6 --- /dev/null +++ b/frontend/src/views/user/Profile.vue @@ -0,0 +1,28 @@ + + + diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js new file mode 100644 index 0000000..3bd3c70 --- /dev/null +++ b/frontend/tailwind.config.js @@ -0,0 +1,22 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{vue,js,ts,jsx,tsx}", + ], + theme: { + extend: { + colors: { + // 二次元风格配色 + 'acg-pink': '#FFB7C5', + 'acg-pink-light': '#FFE4E9', + 'acg-blue': '#A8D8EA', + 'acg-purple': '#D4B5E6', + 'acg-yellow': '#FFF4BD', + }, + }, + }, + plugins: [ + require('@tailwindcss/typography'), + ], +} diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json new file mode 100644 index 0000000..c2a267a --- /dev/null +++ b/frontend/tsconfig.app.json @@ -0,0 +1,22 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "types": ["vite/client"], + + /* Path Alias */ + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + }, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..8a67f62 --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..048f74c --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,22 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import path from 'path' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, + server: { + port: 5173, + proxy: { + '/api': { + target: 'http://localhost:8000', + changeOrigin: true, + }, + }, + }, +}) diff --git a/readme.md b/readme.md index ea20820..fbc4b5f 100644 --- a/readme.md +++ b/readme.md @@ -5,7 +5,7 @@ - **项目名称**:ACG Blog(二次元风格博客) - **项目简介**:基于 **FastAPI** 与 **Vue 3** 的前后端分离博客系统,主打二次元视觉体验与高性能响应。支持 Markdown 文章发布、动态看板娘、访问量统计、热搜排行、深色模式切换等特色功能。 - **技术栈概览**: - - 后端:Python + FastAPI + PostgreSQL + Tortoise‑ORM + Redis + JWT + - 后端:Python + FastAPI + SupaBase + Tortoise‑ORM + Redis + JWT - 前端:Vue 3 (Vite) + Pinia + Naive UI + Tailwind CSS + GSAP - **系统架构**:**B/S 架构**(Browser/Server,浏览器/服务器架构) - 前端:单页应用(SPA)运行于浏览器 @@ -28,7 +28,7 @@ | **模块** | **技术选型** | **说明** | | -------------- | --------------------- | ------------------------------------------------------------ | | **核心框架** | **FastAPI** | 高性能、原生异步(Async),满足二次元素材(图片/视频)的高并发加载需求。 | -| **数据库** | **PostgreSQL** | 关系型数据库首选,对 JSONB 的支持非常适合存储灵活的文章元数据。 | +| **数据库** | **SupaBace** | **内置 Auth**,支持第三方登录 (Github/Google) | | **ORM (异步)** | **Tortoise-ORM** | 语法类似 Django,且原生支持异步操作。 | | **缓存/任务** | **Redis** | 用于文章点击量统计、热搜排行以及 Session 存储。 | | **认证** | **JWT (python-jose)** | 无状态认证,方便前后端分离部署。 | @@ -99,7 +99,7 @@ uvicorn[standard]==0.27.1 tortoise-orm==0.20.0 aerich==0.7.2 # 数据库迁移工具 asyncpg==0.29.0 # PostgreSQL 驱动 -aioredis==2.0.1 # Redis 驱动 (用于缓存访问量) +aioredis==2.0.1 # Redis 驱动 # 日志系统 loguru==0.7.2 # 极简且强大的异步日志处理 @@ -113,8 +113,8 @@ passlib[bcrypt]==1.7.4 # 密码哈希 # 业务 python-multipart==0.0.9 # 处理表单与文件上传 mistune==3.0.2 # 快速 Markdown 解析 -pillow==10.2.0 # 处理图片 (ACG 博客需要自动缩略图) -httpx==0.27.0 # 异步 HTTP 请求 (爬取番剧信息等) +pillow==10.2.0 # 处理图片 +httpx==0.27.0 # 异步 HTTP 请求 # 开发与部署 python-dotenv==1.0.1 diff --git a/开发路径.md b/开发路径.md new file mode 100644 index 0000000..7e77143 --- /dev/null +++ b/开发路径.md @@ -0,0 +1,672 @@ +# ACG Blog 开发路径 + +> 二次元风格博客系统 - 分阶段开发计划 +> +> **最后更新**:2026-03-24 +> +> **当前状态**:前端基础搭建完成 ✅ | 后端开发未开始 ❌ + +--- + +## 系统架构设计 + +### 整体架构图 + +```mermaid +graph TB + subgraph Frontend["前端 (Vue 3 + Vite)"] + FE_Vue[Vue 3 应用] + FE_Router[Vue Router] + FE_Pinia[Pinia 状态管理] + FE_UI[Naive UI 组件库] + FE_CSS[Tailwind CSS] + FE_GSAP[GSAP 动画] + FE_Editor[V-MD-Editor] + end + + subgraph Backend["后端 (FastAPI)"] + BE_API[API 路由层] + BE_Service[Service 服务层] + BE_Crud[CRUD 数据操作层] + BE_Schema[Schema 数据校验层] + BE_Core[Core 核心配置层] + end + + subgraph BaaS["Supabase (BaaS)"] + SB_Auth[Auth 认证] + SB_DB[(PostgreSQL)] + SB_Storage[Storage 存储] + SB_Realtime[Realtime 实时] + end + + subgraph Cache["Redis 缓存"] + RC_Stats[访问统计] + RC_Hot[热搜排行] + RC_Session[会话存储] + end + + FE_Vue --> |HTTP/JSON| BE_API + FE_Router --> FE_Vue + FE_Pinia --> FE_Vue + FE_GSAP --> FE_Vue + FE_Editor --> FE_Vue + BE_API --> BE_Service + BE_Service --> BE_Crud + BE_Crud --> BE_Schema + BE_Schema --> BE_Core + BE_Core --> SB_Auth + BE_Core --> SB_DB + BE_Core --> SB_Storage + BE_Service --> RC_Stats + BE_Service --> RC_Hot + BE_Service --> RC_Session +``` + +### 前端模块架构 + +```mermaid +graph TB + subgraph Views["页面视图"] + Home[首页 Home.vue] + PostDetail[文章详情 PostDetail.vue] + Category[分类页 Category.vue] + Archive[归档页 Archive.vue] + About[关于页 About.vue] + end + + subgraph Auth["认证模块"] + Login[登录 Login.vue] + Register[注册 Register.vue] + Profile[个人资料 Profile.vue] + end + + subgraph Admin["管理后台"] + Dashboard[仪表盘 Dashboard.vue] + PostManage[文章管理 PostManage.vue] + CategoryManage[分类管理 CategoryManage.vue] + TagManage[标签管理 TagManage.vue] + end + + subgraph Components["公共组件"] + Navbar[导航栏 Navbar.vue] + Hero[Hero 区域 Hero.vue] + Footer[页脚 Footer.vue] + PostCard[文章卡片 PostCard.vue] + Sidebar[侧边栏 Sidebar.vue] + Kanban[看板娘 Kanban.vue] + end + + subgraph Store["状态管理"] + UserStore[user.ts 用户状态] + ThemeStore[theme.ts 主题状态] + KanbanStore[kanban.ts 看板娘状态] + end + + subgraph API["接口层"] + AuthAPI[auth.ts 认证接口] + PostAPI[post.ts 文章接口] + IndexAPI[index.ts 基础配置] + end + + Navbar --> Home + Hero --> Home + PostCard --> Home + Sidebar --> Home + Footer --> Views + Home --> PostDetail + Home --> Category + Login --> Auth + Register --> Auth + Profile --> Auth + Dashboard --> Admin + PostManage --> Admin + UserStore --> Store + ThemeStore --> Store + AuthAPI --> API + PostAPI --> API +``` + +### 后端模块架构 + +```mermaid +graph TB + subgraph API["API 路由层 api/endpoints/"] + AuthAPI[auth.py 认证接口] + UserAPI[users.py 用户接口] + PostAPI[posts.py 文章接口] + CategoryAPI[categories.py 分类接口] + TagAPI[tags.py 标签接口] + CommentAPI[comments.py 评论接口] + KanbanAPI[kanban.py 看板娘接口] + StatsAPI[stats.py 统计接口] + UploadAPI[upload.py 上传接口] + end + + subgraph Service["服务层 services/"] + AuthService[auth_service.py] + PostService[post_service.py] + KanbanService[kanban_service.py] + StatsService[stats_service.py] + UploadService[upload_service.py] + end + + subgraph Crud["CRUD 层 crud/"] + UserCrud[user.py] + PostCrud[post.py] + CategoryCrud[category.py] + TagCrud[tag.py] + CommentCrud[comment.py] + end + + subgraph Schema["Schema 层 schemas/"] + UserSchema[user.py] + PostSchema[post.py] + AuthSchema[auth.py] + TokenSchema[token.py] + end + + subgraph Core["核心层 core/"] + Config[config.py 配置] + Security[security.py JWT/密码] + Logger[logger.py 日志] + Supabase[supabase_client.py] + end + + AuthAPI --> AuthService + PostAPI --> PostService + KanbanAPI --> KanbanService + AuthService --> UserCrud + PostService --> PostCrud + UserCrud --> UserSchema + PostCrud --> PostSchema + UserSchema --> Supabase + PostSchema --> Supabase + UserCrud --> Supabase + PostCrud --> Supabase + AuthService --> Security + Core --> Config +``` + +### 数据库模型关系图 (Supabase PostgreSQL) + +```mermaid +erDiagram + USER ||--o{ POST : writes + USER ||--o{ COMMENT : writes + POST ||--o{ COMMENT : has + POST }o--o{ TAG : tagged_with + POST ||--|{ CATEGORY : belongs_to + USER { + uuid id PK + string username + string email + string avatar_url + boolean is_active + boolean is_superuser + datetime created_at + datetime updated_at + } + POST { + uuid id PK + string title + string slug + text content + text summary + string cover_url + uuid author_id FK + uuid category_id FK + integer view_count + string status + datetime created_at + datetime updated_at + } + CATEGORY { + uuid id PK + string name + string slug + text description + datetime created_at + } + TAG { + uuid id PK + string name + datetime created_at + } + COMMENT { + uuid id PK + text content + uuid author_id FK + uuid post_id FK + uuid parent_id + datetime created_at + } + POST_TAG { + uuid post_id FK + uuid tag_id FK + } +``` + +### 数据流架构图 + +```mermaid +sequenceDiagram + participant U as 用户 + participant FE as 前端 Vue + participant API as FastAPI 后端 + participant S as Service + participant SB as Supabase + participant R as Redis + + U->>FE: 访问页面 + FE->>API: HTTP Request + API->>S: 业务逻辑处理 + + Note over S,SB: 数据库操作 + S->>SB: Supabase SDK 调用 + SB-->>S: 查询结果 + + Note over S,R: 缓存操作 + S->>R: 检查/写入缓存 + R-->>S: 缓存数据 + + S-->>FE: JSON 响应 + FE-->>U: 渲染页面 +``` + +--- + +## 开发阶段 + +### 阶段一:后端基础架构搭建 + +**优先级:P0** | **状态:❌ 未开始** + +#### 1.1 核心配置层 +- [ ] `core/config.py` - 环境变量与全局配置(Pydantic Settings) +- [ ] `core/supabase.py` - Supabase 客户端初始化 +- [ ] `core/security.py` - JWT 生成、验证与密码哈希(python-jose + passlib) +- [ ] `core/logger.py` - Loguru 日志配置 +- [ ] `.env.example` - 环境变量模板 + +#### 1.2 数据库表结构 (Supabase PostgreSQL) +- [ ] 创建 profiles 表(用户扩展信息) +- [ ] 创建 posts 表(文章) +- [ ] 创建 categories 表(分类) +- [ ] 创建 tags 表(标签) +- [ ] 创建 comments 表(评论) +- [ ] 创建 post_tags 表(文章-标签关联) +- [ ] 配置 RLS (Row Level Security) 策略 +- [ ] 设计数据库索引 + +#### 1.3 Pydantic Schema 层 +- [ ] `schemas/user.py` - 用户数据验证 +- [ ] `schemas/post.py` - 文章数据验证 +- [ ] `schemas/auth.py` - 认证相关 Schema +- [ ] `schemas/token.py` - Token Schema + +#### 1.4 CRUD 操作层 +- [ ] `crud/user.py` - 用户 CRUD(通过 Supabase SDK) +- [ ] `crud/post.py` - 文章 CRUD +- [ ] `crud/category.py` - 分类 CRUD +- [ ] `crud/tag.py` - 标签 CRUD +- [ ] `crud/comment.py` - 评论 CRUD + +--- + +### 阶段二:认证与用户模块 + +**优先级:P0** | **状态:❌ 未开始** + +#### 2.1 认证接口 +- [ ] `api/endpoints/auth.py` + - [ ] POST `/api/v1/auth/register` - 用户注册 + - [ ] POST `/api/v1/auth/login` - 用户登录 + - [ ] POST `/api/v1/auth/refresh` - 刷新 Token + - [ ] POST `/api/v1/auth/logout` - 用户登出 + +#### 2.2 用户管理接口 +- [ ] `api/endpoints/users.py` + - [ ] GET `/api/v1/users/me` - 获取当前用户信息 + - [ ] PUT `/api/v1/users/me` - 更新当前用户信息 + - [ ] PUT `/api/v1/users/me/password` - 修改密码 + +#### 2.3 依赖注入与中间件 +- [ ] `api/deps.py` - 依赖注入(获取当前用户等) +- [ ] JWT 认证中间件 + +--- + +### 阶段三:文章管理模块 + +**优先级:P1** | **状态:❌ 未开始** + +#### 3.1 文章接口 +- [ ] `api/endpoints/posts.py` + - [ ] GET `/api/v1/posts/` - 文章列表(分页) + - [ ] GET `/api/v1/posts/{post_id}` - 获取单篇文章 + - [ ] POST `/api/v1/posts/` - 创建文章(需认证) + - [ ] PUT `/api/v1/posts/{post_id}` - 更新文章 + - [ ] DELETE `/api/v1/posts/{post_id}` - 删除文章 + - [ ] POST `/api/v1/posts/{post_id}/view` - 增加浏览量 + +#### 3.2 分类与标签接口 +- [ ] `api/endpoints/categories.py` + - [ ] GET `/api/v1/categories/` - 分类列表 + - [ ] POST `/api/v1/categories/` - 创建分类(管理员) +- [ ] `api/endpoints/tags.py` + - [ ] GET `/api/v1/tags/` - 标签列表 + - [ ] POST `/api/v1/tags/` - 创建标签(管理员) + +#### 3.3 评论接口 +- [ ] `api/endpoints/comments.py` + - [ ] GET `/api/v1/posts/{post_id}/comments` - 获取文章评论 + - [ ] POST `/api/v1/posts/{post_id}/comments` - 发表评论 + - [ ] DELETE `/api/v1/comments/{comment_id}` - 删除评论 + +--- + +### 阶段四:特色功能开发 + +**优先级:P3** | **状态:❌ 未开始** + +#### 4.1 看板娘系统 +- [ ] `services/kanban_service.py` - 看板娘互动逻辑 +- [ ] `api/endpoints/kanban.py` + - [ ] GET `/api/v1/kanban/greeting` - 随机问候语 + - [ ] POST `/api/v1/kanban/mood` - 切换看板娘心情 + - [ ] GET `/api/v1/kanban/stats` - 看板娘统计信息 + +#### 4.2 统计与热搜 +- [ ] Redis 缓存集成 +- [ ] `services/stats_service.py` - 访问统计逻辑 +- [ ] `api/endpoints/stats.py` + - [ ] GET `/api/v1/stats/hot-posts` - 热搜文章排行 + - [ ] GET `/api/v1/stats/visits` - 访问量统计 + - [ ] GET `/api/v1/stats/archive` - 归档统计 + +#### 4.3 文件上传 +- [ ] `services/upload_service.py` - 图片上传处理 +- [ ] `api/endpoints/upload.py` + - [ ] POST `/api/v1/upload/image` - 上传图片 + - [ ] 图片压缩与缩略图生成(Pillow) + +--- + +### 阶段五:前端项目初始化 + +**优先级:P1** | **状态:✅ 已完成** + +#### 5.1 基础搭建 ✅ +- [x] Vite + Vue 3 项目初始化 +- [x] 配置 Tailwind CSS v4 +- [x] 配置 Naive UI 主题(二次元配色) +- [x] 配置路由(Vue Router) + +#### 5.2 状态管理 ✅ +- [x] Pinia Store 结构设计 +- [x] `store/user.ts` - 用户状态 +- [x] `store/theme.ts` - 主题状态 +- [ ] `store/kanban.ts` - 看板娘状态 + +#### 5.3 API 封装 ✅ +- [x] Axios 基础配置 +- [x] 请求/响应拦截器(JWT 处理) +- [x] API 模块化封装 + +--- + +### 阶段六:前端核心页面 + +**优先级:P2** | **状态:⚠️ 部分完成** + +#### 6.1 布局组件 ✅ +- [x] `layouts/AdminLayout.vue` - 管理后台布局 +- [x] `components/Navbar.vue` - 导航栏 +- [x] `components/Footer.vue` - 页脚 +- [x] `components/Hero.vue` - Hero 区域 + +#### 6.2 公共组件 ⚠️ +- [ ] `components/Kanban.vue` - Live2D 看板娘 +- [x] `components/PostCard.vue` - 文章卡片 +- [ ] `components/MarkdownEditor.vue` - Markdown 编辑器 +- [x] `components/Sidebar.vue` - 侧边栏 + +#### 6.3 核心页面 ⚠️ +- [x] `views/Home.vue` - 首页(文章列表) +- [x] `views/PostDetail.vue` - 文章详情(占位) +- [x] `views/Category.vue` - 分类页(占位) +- [ ] `views/Archive.vue` - 归档页 +- [x] `views/About.vue` - 关于页(占位) + +--- + +### 阶段七:用户功能页面 + +**优先级:P2** | **状态:⚠️ 部分完成** + +#### 7.1 认证页面 ⚠️ +- [x] `views/auth/Login.vue` - 登录页(框架) +- [x] `views/auth/Register.vue` - 注册页(框架) + +#### 7.2 用户中心 ⚠️ +- [x] `views/user/Profile.vue` - 个人资料(框架) +- [ ] `views/user/Settings.vue` - 用户设置 +- [ ] `views/user/MyPosts.vue` - 我的文章 + +--- + +### 阶段八:管理后台 + +**优先级:P3** | **状态:⚠️ 部分完成** + +#### 8.1 后台布局 ✅ +- [x] `admin/Dashboard.vue` - 仪表盘(框架) +- [x] `admin/Sidebar.vue` - 侧边栏(在 AdminLayout 中) + +#### 8.2 后台功能 ⚠️ +- [x] `admin/PostManage.vue` - 文章管理(框架) +- [x] `admin/CategoryManage.vue` - 分类管理(框架) +- [x] `admin/TagManage.vue` - 标签管理(框架) +- [ ] `admin/CommentManage.vue` - 评论管理 +- [ ] `admin/UserManage.vue` - 用户管理 +- [ ] `admin/Statistics.vue` - 数据统计 + +--- + +### 阶段九:动效与优化 + +**优先级:P4** | **状态:⚠️ 部分完成** + +#### 9.1 动画效果 ⚠️ +- [ ] GSAP 页面转场动画 +- [x] 卡片悬浮效果 ✅ +- [ ] 看板娘互动动画 +- [x] 毛玻璃特效实现 ✅ + +#### 9.2 性能优化 +- [ ] 图片懒加载 +- [x] 路由懒加载 +- [ ] API 请求防抖节流 +- [ ] 前端缓存策略 + +--- + +### 阶段十:测试与部署 + +**优先级:P5** | **状态:❌ 未开始** + +#### 10.1 测试 +- [ ] 后端单元测试(pytest) +- [ ] API 集成测试 +- [ ] 前端组件测试(Vitest) + +#### 10.2 部署配置 +- [ ] `Dockerfile`(后端) +- [ ] `Dockerfile`(前端) +- [ ] `docker-compose.yml` - 完整编排 +- [ ] Nginx 配置 +- [ ] CI/CD 流程 + +--- + +## 项目结构(当前) + +``` +ACG-Blog/ +├── backend/ +│ ├── app/ +│ │ ├── api/ +│ │ │ └── endpoints/ # API 路由 +│ │ │ ├── auth.py # 认证接口 +│ │ │ ├── users.py # 用户接口 +│ │ │ ├── posts.py # 文章接口 +│ │ │ ├── categories.py # 分类接口 +│ │ │ ├── tags.py # 标签接口 +│ │ │ ├── comments.py # 评论接口 +│ │ │ ├── kanban.py # 看板娘接口 +│ │ │ ├── stats.py # 统计接口 +│ │ │ └── upload.py # 上传接口 +│ │ ├── core/ # 核心配置 +│ │ │ ├── config.py # 环境变量配置 +│ │ │ ├── supabase.py # Supabase 客户端 +│ │ │ ├── security.py # JWT/密码安全 +│ │ │ └── logger.py # 日志配置 +│ │ ├── crud/ # CRUD 操作 +│ │ │ ├── user.py +│ │ │ ├── post.py +│ │ │ ├── category.py +│ │ │ ├── tag.py +│ │ │ └── comment.py +│ │ ├── models/ # 数据模型(Pydantic) +│ │ │ ├── user.py +│ │ │ ├── post.py +│ │ │ ├── category.py +│ │ │ ├── tag.py +│ │ │ └── comment.py +│ │ ├── schemas/ # Pydantic Schema +│ │ │ ├── auth.py +│ │ │ ├── token.py +│ │ │ └── ... +│ │ └── services/ # 业务服务 +│ │ ├── kanban_service.py +│ │ ├── stats_service.py +│ │ └── upload_service.py +│ ├── main.py # FastAPI 入口 +│ ├── requirements.txt # Python 依赖 +│ └── venv/ # 虚拟环境 +│ +├── frontend/ +│ ├── public/ # 静态资源 +│ │ ├── favicon.svg +│ │ ├── icons.svg +│ │ └── live2d/ # Live2D 模型文件 +│ ├── src/ +│ │ ├── api/ # API 封装 ✅ +│ │ │ ├── index.ts # Axios 配置 +│ │ │ ├── auth.ts # 认证接口 +│ │ │ └── post.ts # 文章接口 +│ │ ├── assets/ # 静态资源 +│ │ ├── components/ # 公共组件 ✅ +│ │ │ ├── Footer.vue +│ │ │ ├── Hero.vue +│ │ │ ├── Kanban.vue # 看板娘组件 +│ │ │ ├── Navbar.vue +│ │ │ ├── PostCard.vue +│ │ │ └── Sidebar.vue +│ │ ├── layouts/ # 布局组件 +│ │ │ └── AdminLayout.vue +│ │ ├── router/ # 路由配置 +│ │ ├── store/ # 状态管理 ✅ +│ │ ├── views/ # 页面视图 +│ │ ├── App.vue +│ │ ├── main.ts +│ │ └── style.css +│ ├── package.json +│ ├── tsconfig.json +│ ├── vite.config.ts +│ ├── tailwind.config.js +│ └── postcss.config.js +│ +├── docker-compose.yml # Docker 编排 +├── README.md +└── 开发路径.md +``` + +--- + +## 技术栈汇总 + +### 后端技术 +| 技术 | 版本 | 用途 | +|------|------|------| +| Python | 3.11+ | 运行环境 | +| FastAPI | 0.110.0 | Web 框架 | +| Uvicorn | 0.27.1 | ASGI 服务器 | +| **Supabase** | - | BaaS 平台(内置 Auth + PostgreSQL + Storage) | +| Redis | - | 缓存/会话(热搜排行、访问统计) | +| Pydantic | 2.6.3 | 数据校验 | +| python-jose | 3.3.0 | JWT 认证 | +| Passlib | 1.7.4 | 密码哈希 | +| Loguru | 0.7.2 | 日志 | +| Pillow | 10.2.0 | 图片处理 | +| httpx | 0.27.0 | 异步 HTTP | +| python-multipart | 0.0.9 | 文件上传 | + +### 前端技术 +| 技术 | 版本 | 用途 | +|------|------|------| +| Vue | 3.5.30 | 框架 | +| Vite | 8.0.0 | 构建工具 | +| TypeScript | 5.9.3 | 类型系统 | +| Vue Router | 5.0.3 | 路由 | +| Pinia | 3.0.4 | 状态管理 | +| Naive UI | 2.44.1 | UI 组件库 | +| Tailwind CSS | 4.2.1 | CSS 框架 | +| Axios | 1.13.6 | HTTP 客户端 | +| @vueuse/core | 14.2.1 | Vue 组合式工具 | +| **GSAP** | - | 动画库(页面转场、交互动画) | +| **V-MD-Editor** | - | Markdown 编辑器 | + +--- + +## 优先级建议 + +| 优先级 | 阶段 | 说明 | 状态 | +|--------|------|------|------| +| P0 | 一、二 | 后端基础 + 认证系统(核心) | ❌ 未开始 | +| P1 | 三、五 | 文章管理 + 前端初始化 | ❌ / ⚠️ | +| P2 | 六、七 | 核心页面 + 用户功能 | ⚠️ 部分完成 | +| P3 | 四、八 | 特色功能 + 管理后台 | ❌ / ⚠️ | +| P4 | 九 | 动效优化 | ⚠️ 部分完成 | +| P5 | 十 | 测试与部署 | ❌ 未开始 | + +--- + +## 下一步开发计划 + +根据当前项目状态,建议按以下顺序继续开发: + +### 立即执行(Next Step) +1. **完成后端核心配置** (`core/config.py`, `core/security.py`) +2. **创建数据库模型** (`models/`) +3. **实现认证接口** (`api/endpoints/auth.py`) +4. **前后端 API 对接** + +### 看板娘集成(可选) +- 可使用 Live2D Cubism SDK 或 `vue-live2d` 等第三方库 + +--- + +## 注意事项 + +1. **Supabase**:使用 Supabase 作为 BaaS 平台,内置 Auth、PostgreSQL、Storage +2. **环境变量**:敏感信息(Supabase URL、anon key、JWT密钥)必须通过 `.env` 管理 +3. **前端主题**:使用二次元风格的配色(粉色 #FFB7C5、浅蓝 #A8D8EA、紫色 #D4B5E6) +4. **看板娘**:可使用 Live2D Cubism SDK 或第三方开源实现(如 vue-live2d) +5. **图片处理**:确保上传的图片有合适的压缩和尺寸限制(Pillow) +6. **Tailwind 4.x**:注意 `@apply` 指令使用限制,优先使用 CSS 原生语法 +7. **Redis**:用于缓存热搜排行、访问统计等高频访问数据 + +--- + +*文档版本:v2.0 | 更新日期:2026-03-24*