# Personal Blog - 前端设计文档 > React 18 + Vite + Tailwind CSS + 双主题架构 --- ## 1. 项目结构 ``` frontend/ ├── index.html # HTML 入口(Vite 注入 JS) ├── vite.config.js # Vite 配置(proxy、host、port) ├── tailwind.config.js # Tailwind CSS 配置 ├── package.json # 依赖管理 └── src/ ├── main.jsx # React 根渲染 ├── App.jsx # 根路由配置 ├── index.css # 全局样式(双主题 CSS 变量 + 组件类) ├── api/ # API 封装层 │ ├── config.js # Axios 实例 + 拦截器 │ ├── posts.js # 文章 API │ └── categories.js # 分类 API ├── pages/ # 页面组件(路由级) │ ├── Home.jsx # 访客端首页 │ ├── PostDetail.jsx # 访客端文章详情 │ ├── PostEdit.jsx # 文章编辑/新建 │ ├── Posts.jsx # 文章管理列表 │ ├── CrawlEdit.jsx # 通用网页采集 │ ├── WechatCrawlEdit.jsx # 公众号采集 │ ├── Categories.jsx # 分类管理 │ ├── Tags.jsx # 标签管理 │ ├── Images.jsx # 图片管理 │ ├── Settings.jsx # 系统设置 │ ├── Login.jsx # 登录页 │ └── About.jsx # 关于页面 ├── components/ # 访客端通用组件 │ ├── Layout.jsx # 访客端页面布局(含导航、页脚) │ └── PostCard.jsx # 文章列表卡片 └── components-preline/ # 管理后台组件库 ├── Sidebar.jsx # 侧边栏导航 ├── Card.jsx # 卡片容器 ├── Button.jsx # 按钮 └── Input.jsx # 输入框/文本域 ``` --- ## 2. 路由架构 (App.jsx) ``` React Router v7 (BrowserRouter) │ ├── 访客端路径 (Layout 包裹) │ ├── / → Home.jsx 首页文章列表 │ ├── /post/:slug → PostDetail.jsx 文章详情 │ ├── /category/:id → Home.jsx 分类筛选 │ ├── /tag/:name → Home.jsx 标签筛选 │ ├── /about → About.jsx 关于页面 │ └── /page/:slug → PageView.jsx 自定义页面 │ └── 管理后台路径 (AdminLayout 包裹,需登录) ├── /admin → Dashboard 仪表盘 ├── /admin/posts → Posts.jsx 文章列表 ├── /admin/posts/new → PostEdit.jsx 新建文章 ├── /admin/posts/:id/edit → PostEdit.jsx 编辑文章 ├── /admin/crawl → CrawlEdit.jsx 通用采集 ├── /admin/wechat-crawl → WechatCrawlEdit.jsx 公众号采集 ├── /admin/categories → Categories.jsx 分类管理 ├── /admin/tags → Tags.jsx 标签管理 ├── /admin/images → Images.jsx 图片管理 ├── /admin/settings → Settings.jsx 系统设置 └── /admin/login → Login.jsx 登录页 ``` ### 2.1 路由守卫 管理后台路由通过 `localStorage.getItem('admin_token')` 判断登录状态: ```jsx // App.jsx 中的路由守卫逻辑 const isAdminRoute = location.pathname.startsWith('/admin') const isLoginPage = location.pathname === '/admin/login' useEffect(() => { if (isAdminRoute && !isLoginPage && !localStorage.getItem('admin_token')) { navigate('/admin/login') } }, [location.pathname]) ``` - 未登录访问管理后台 → 强制跳转到登录页 - 登录页不检查 Token(避免循环跳转) --- ## 3. 双主题设计 项目同时存在两种视觉风格,通过 CSS 变量和独立组件类实现共存。 ### 3.1 主题对比 | 维度 | 访客端 (Neobrutalism) | 管理后台 (Preline Admin) | |------|----------------------|-------------------------| | **定位** | 面向读者,活泼有趣 | 面向管理员,专业高效 | | **色彩** | 奶油色底 + 高饱和撞色(粉/黄/青/紫) | 白色底 + 深蓝灰 | | **边框** | 粗黑实线(2px),直角 | 细灰实线(1px),圆角(rounded-lg) | | **阴影** | 硬阴影偏移(4px 4px 0) | 柔和阴影(shadow-sm/md) | | **按钮** | 块状,hover 位移凹陷 | 圆角,hover 背景加深 | | **滚动条** | 自定义粗黑风格 | 浏览器默认 | | **选中文字** | 黄色背景 | 默认 | ### 3.2 CSS 变量体系 ```css :root { /* Neobrutalism 默认配色 */ --brutal-cream: #FEF9E7; --brutal-yellow: #FFDE59; --brutal-pink: #FF6B9D; --brutal-cyan: #00D9FF; --brutal-lavender: #C084FC; --brutal-green: #4ADE80; /* 默认使用 Neobrutalism */ --color-bg: var(--brutal-cream); --color-card: #FFFFFF; --color-primary: var(--brutal-pink); --color-text: #000000; --border-width: 2px; --border-radius: 0px; --shadow: 4px 4px 0px 0px rgba(0,0,0,1); } ``` ### 3.3 组件类映射 访客端使用 `@layer components` 中定义的 `*-brutal` 类: - `.btn-brutal` / `.btn-brutal-sm` - `.card-brutal` / `.card-brutal-sm` - `.input-brutal` - `.nav-link` / `.nav-link-active` 管理后台使用 `*-preline` 类或直接写 Tailwind 工具类: - `.btn-preline` / `.btn-preline-secondary` / `.btn-preline-danger` - `.card-preline` / `.card-preline-header` / `.card-preline-body` - `.input-preline` / `.label-preline` - `.sidebar-preline` 等 ### 3.4 实现方式 两种主题不是通过切换 CSS 变量实现的,而是: - **访客端组件** 硬编码使用 `*-brutal` 类 - **管理后台组件** 硬编码使用 `*-preline` 类或 Tailwind 工具类 共存于同一个 `index.css` 文件中,按页面路由分别加载对应组件。 --- ## 4. API 层 (api/config.js) ### 4.1 Axios 实例配置 ```javascript const api = axios.create({ baseURL: 'http://localhost:8889/api', // 开发环境 timeout: 30000, }) ``` ### 4.2 请求拦截器 自动注入 `Authorization` 头部: ```javascript api.interceptors.request.use(config => { const token = localStorage.getItem('admin_token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) ``` ### 4.3 响应拦截器 Token 过期时自动跳转登录页: ```javascript api.interceptors.response.use( response => response, error => { if (error.response?.status === 401) { localStorage.removeItem('admin_token') window.location.href = '/admin/login' } return Promise.reject(error) } ) ``` --- ## 5. 管理后台布局 (AdminLayout) ``` ┌─────────────────────────────────────────────────────────────┐ │ ┌──────┐ Header │ │ │ Logo │ 面包屑 / 页面标题 管理员 / 退出 │ │ └──────┘────────────────────────────────────────────────── │ │ ┌────────┐ ┌────────────────────────────────────────────┐ │ │ │ 首页 │ │ │ │ │ │ 文章 │ │ 页面内容区域 │ │ │ │ 采集 │ │ │ │ │ │ 分类 │ │ │ │ │ │ 标签 │ │ │ │ │ │ 图片 │ │ │ │ │ │ 设置 │ │ │ │ │ └────────┘ └────────────────────────────────────────────┘ │ │ Sidebar Main Content │ └─────────────────────────────────────────────────────────────┘ ``` ### 5.1 侧边栏导航 (Sidebar.jsx) ```javascript const menuItems = [ { name: '首页', path: '/admin', icon: Home }, { name: '文章管理', path: '/admin/posts', icon: FileText }, { name: '文章采集', path: '/admin/crawl', icon: Globe }, { name: '公众号采集', path: '/admin/wechat-crawl', icon: MessageSquare }, { name: '分类管理', path: '/admin/categories', icon: Folder }, { name: '标签管理', path: '/admin/tags', icon: Tag }, { name: '图片管理', path: '/admin/images', icon: Image }, { name: '系统设置', path: '/admin/settings', icon: Settings }, ] ``` - 固定左侧,宽度 64 (折叠) / 256 (展开) px - 当前路由高亮 - 移动端自适应汉堡菜单 ### 5.2 退出登录 ```javascript const handleLogout = () => { localStorage.removeItem('admin_token') window.location.href = '/admin/login' } ``` --- ## 6. 访客端布局 (Layout.jsx) ``` ┌─────────────────────────────────────────────┐ │ Logo 首页 · 文章 · 分类 · 关于 · 管理后台 │ ├─────────────────────────────────────────────┤ │ │ │ 页面内容 (Outlet) │ │ │ ├─────────────────────────────────────────────┤ │ Footer (版权 / 备案) │ └─────────────────────────────────────────────┘ ``` ### 6.1 导航栏 - Neobrutalism 风格:粗黑边框、黄底高亮当前项 - 移动端:汉堡菜单 + 抽屉式导航 - 导航项从后端 `/api/settings` 动态获取(自定义页面) ### 6.2 页脚 - 左侧:站点名称 + 描述 + 版权 - 右侧:备案号链接 + 社交媒体图标(可选) - 返回顶部按钮 --- ## 7. 核心页面详解 ### 7.1 访客端首页 (Home.jsx) **功能**: - 展示文章卡片网格 - 支持按分类/标签筛选 - 搜索框 - 分页 **文章卡片 (PostCard.jsx)**: - 封面图(如有) - 标题 - 摘要(截断至 120 字符) - 分类标签 - 日期 + 阅读数 ### 7.2 访客端文章详情 (PostDetail.jsx) **功能**: - 文章标题、元信息(日期/分类/标签) - 封面图 - 正文渲染(见 `05-markdown-rendering.md`) - 分享按钮 - 上一篇/下一篇导航 ### 7.3 文章编辑 (PostEdit.jsx) **功能**: - 新建/编辑双模式 - 左右双栏布局: - 左栏:标题、摘要、正文(Markdown textarea)、操作按钮 - 右栏:分类、标签、封面图上传、文章状态 - Markdown 实时预览(react-markdown) - 保存草稿 / 发布 / 取消 ### 7.4 文章列表 (Posts.jsx) **功能**: - 表格展示所有文章 - 搜索、分类筛选、状态筛选 - 分页 - 批量操作(删除) - 快捷操作:编辑、删除、预览 --- ## 8. 状态管理 项目未使用 Redux/Zustand 等全局状态库,采用 **React 内置状态 + 服务端状态**: | 数据 | 管理方式 | |------|----------| | 登录 Token | `localStorage` | | 文章列表 | `useState` + `useEffect` 调用 API | | 文章详情 | `useState` + `useEffect` | | 分类/标签 | `useState` + `useEffect` | | 系统设置 | `useState` + `useEffect` | | 表单数据 | `useState` (受控组件) | ### 8.1 典型数据获取模式 ```jsx const [posts, setPosts] = useState([]) const [loading, setLoading] = useState(true) useEffect(() => { api.get('/posts', { params: { skip: 0, limit: 10 } }) .then(res => setPosts(res.data.items)) .catch(err => console.error(err)) .finally(() => setLoading(false)) }, []) ``` --- ## 9. 构建配置 (vite.config.js) ```javascript export default defineConfig({ plugins: [react()], server: { host: '0.0.0.0', port: 8888, proxy: { '/api': { target: 'http://localhost:8889', changeOrigin: true }, '/uploads': { target: 'http://localhost:8889', changeOrigin: true }, }, }, }) ``` ### 9.1 代理配置 开发环境下,前端请求 `/api/*` 和 `/uploads/*` 自动代理到后端(:8889),解决跨域问题。 ### 9.2 生产部署 ```bash npm run build # 输出到 dist/ 目录 ``` 生产环境使用 `serve` 或 Nginx 托管静态文件: ```bash npx serve -s dist -l 8888 --cors ``` --- ## 10. Tailwind 配置 ### 10.1 自定义颜色 ```javascript // tailwind.config.js theme: { extend: { colors: { 'brutal-cream': '#FEF9E7', 'brutal-yellow': '#FFDE59', 'brutal-pink': '#FF6B9D', 'brutal-cyan': '#00D9FF', 'brutal-lavender': '#C084FC', 'brutal-green': '#4ADE80', }, }, } ``` ### 10.2 插件 - `@tailwindcss/forms` — 表单元素基础样式 - `preline` — Preline UI JavaScript 组件交互 --- *本文档由 nanobot 根据项目源码自动生成。*