609 lines
17 KiB
Markdown
609 lines
17 KiB
Markdown
# Personal Blog - Preline UI 迁移方案
|
||
|
||
> **迁移目标**: 逐步将管理后台从 Neobrutalism 迁移到 Preline UI
|
||
> **迁移策略**: 渐进式迁移,不影响现有功能
|
||
> **预计工作量**: 2-3 天(分阶段进行)
|
||
|
||
---
|
||
|
||
## 1. 迁移总览
|
||
|
||
### 1.1 迁移范围
|
||
|
||
| 区域 | 当前风格 | 目标风格 | 优先级 |
|
||
|------|----------|----------|--------|
|
||
| **访客端 (Home/PostDetail)** | Neobrutalism | **保持 Neobrutalism** | - |
|
||
| **登录页 (Login)** | Neobrutalism | Preline Modern | P1 |
|
||
| **后台首页 (Dashboard)** | Neobrutalism | Preline Admin | P1 |
|
||
| **文章管理 (Posts)** | Neobrutalism | Preline Admin | P2 |
|
||
| **文章编辑 (PostEdit)** | Neobrutalism | Preline Admin | P2 |
|
||
| **分类/标签/图片管理** | Neobrutalism | Preline Admin | P3 |
|
||
| **系统设置 (Settings)** | Neobrutalism | Preline Admin | P1 (试点) |
|
||
| **采集页面 (CrawlEdit)** | Neobrutalism | Preline Admin | P3 |
|
||
|
||
### 1.2 为什么这样分?
|
||
|
||
- **访客端保持原样**: 博客展示需要个性风格,Neobrutalism 很适合
|
||
- **后台用 Preline**: 管理后台需要专业、高效、统一
|
||
- **渐进式迁移**: 每次只改一个页面,确保稳定
|
||
|
||
---
|
||
|
||
## 2. 技术准备
|
||
|
||
### 2.1 已安装依赖
|
||
|
||
```bash
|
||
npm install preline @tailwindcss/forms
|
||
```
|
||
|
||
### 2.2 需要修改的配置文件
|
||
|
||
#### 步骤 1: 更新 CSS 入口文件
|
||
|
||
**文件**: `src/index.css`
|
||
|
||
```css
|
||
/* ========== Preline UI 迁移 - 双主题共存方案 ========== */
|
||
|
||
/* 基础 Tailwind */
|
||
@tailwind base;
|
||
@tailwind components;
|
||
@tailwind utilities;
|
||
|
||
/* Preline UI 支持 */
|
||
@source "./node_modules/preline/dist/*.js";
|
||
@import "./node_modules/preline/variants.css";
|
||
|
||
/* Preline 需要的基础样式 */
|
||
@plugin "@tailwindcss/forms";
|
||
|
||
/* ============================================ */
|
||
/* 主题 1: Neobrutalism (访客端 + 默认) */
|
||
/* ============================================ */
|
||
|
||
@layer base {
|
||
:root {
|
||
/* 色彩系统 */
|
||
--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);
|
||
}
|
||
|
||
body {
|
||
@apply bg-[var(--color-bg)] text-[var(--color-text)];
|
||
font-family: system-ui, -apple-system, sans-serif;
|
||
}
|
||
}
|
||
|
||
/* ============================================ */
|
||
/* 主题 2: Preline Admin (管理后台) */
|
||
/* ============================================ */
|
||
|
||
[data-theme="preline"] {
|
||
/* Preline 默认配色 */
|
||
--color-bg: #F8FAFC;
|
||
--color-card: #FFFFFF;
|
||
--color-primary: #10B981;
|
||
--color-text: #0F172A;
|
||
--color-text-secondary: #64748B;
|
||
--color-border: #E2E8F0;
|
||
--color-sidebar: #0F172A;
|
||
--color-sidebar-active: #1E293B;
|
||
|
||
/* 样式变量 */
|
||
--border-width: 1px;
|
||
--border-radius: 0.75rem;
|
||
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
/* ============================================ */
|
||
/* Neobrutalism 组件样式 (保留) */
|
||
/* ============================================ */
|
||
|
||
@layer components {
|
||
.btn-brutal {
|
||
@apply inline-flex items-center justify-center px-4 py-2
|
||
border-2 border-black font-bold text-sm
|
||
transition-all duration-150
|
||
shadow-brutal hover:shadow-none
|
||
hover:translate-x-[2px] hover:translate-y-[2px]
|
||
active:translate-x-[4px] active:translate-y-[4px];
|
||
}
|
||
|
||
.card-brutal {
|
||
@apply bg-white border-2 border-black shadow-brutal;
|
||
}
|
||
|
||
.input-brutal {
|
||
@apply w-full px-3 py-2 border-2 border-black bg-white
|
||
focus:outline-none focus:ring-2 focus:ring-brutal-yellow
|
||
placeholder:text-black/40;
|
||
}
|
||
}
|
||
|
||
/* ============================================ */
|
||
/* Preline Admin 组件样式 (新增) */
|
||
/* ============================================ */
|
||
|
||
@layer components {
|
||
/* Preline 风格按钮 */
|
||
.btn-preline {
|
||
@apply inline-flex items-center justify-center gap-2
|
||
px-4 py-2.5 rounded-lg font-medium text-sm
|
||
bg-[var(--color-primary)] text-white
|
||
hover:opacity-90 focus:outline-none focus:ring-2
|
||
focus:ring-[var(--color-primary)] focus:ring-offset-2
|
||
transition-all duration-200;
|
||
}
|
||
|
||
.btn-preline-secondary {
|
||
@apply inline-flex items-center justify-center gap-2
|
||
px-4 py-2.5 rounded-lg font-medium text-sm
|
||
bg-white text-[var(--color-text)]
|
||
border border-[var(--color-border)]
|
||
hover:bg-gray-50 focus:outline-none focus:ring-2
|
||
focus:ring-gray-200 transition-all duration-200;
|
||
}
|
||
|
||
/* Preline 风格卡片 */
|
||
.card-preline {
|
||
@apply bg-[var(--color-card)] rounded-xl
|
||
border border-[var(--color-border)]
|
||
shadow-sm hover:shadow-md transition-shadow duration-200;
|
||
}
|
||
|
||
/* Preline 风格输入框 */
|
||
.input-preline {
|
||
@apply w-full px-4 py-2.5 rounded-lg
|
||
border border-[var(--color-border)] bg-white
|
||
text-[var(--color-text)]
|
||
focus:outline-none focus:ring-2
|
||
focus:ring-[var(--color-primary)]/20
|
||
focus:border-[var(--color-primary)]
|
||
placeholder:text-gray-400
|
||
transition-all duration-200;
|
||
}
|
||
|
||
/* Preline 侧边栏 */
|
||
.sidebar-preline {
|
||
@apply w-64 bg-[var(--color-sidebar)] min-h-screen
|
||
fixed left-0 top-0 z-40;
|
||
}
|
||
|
||
.sidebar-nav-item {
|
||
@apply flex items-center gap-3 px-4 py-2.5 rounded-lg
|
||
text-gray-400 hover:bg-[var(--color-sidebar-active)]
|
||
hover:text-white transition-colors duration-200;
|
||
}
|
||
|
||
.sidebar-nav-item.active {
|
||
@apply bg-[var(--color-sidebar-active)] text-white font-medium;
|
||
}
|
||
}
|
||
|
||
/* ============================================ */
|
||
/* 工具类扩展 */
|
||
/* ============================================ */
|
||
|
||
@layer utilities {
|
||
.shadow-brutal {
|
||
box-shadow: 4px 4px 0px 0px rgba(0,0,0,1);
|
||
}
|
||
|
||
.shadow-brutal-sm {
|
||
box-shadow: 2px 2px 0px 0px rgba(0,0,0,1);
|
||
}
|
||
}
|
||
|
||
/* ============================================ */
|
||
/* Neobrutalism 滚动条 (仅访客端) */
|
||
/* ============================================ */
|
||
|
||
:not([data-theme="preline"]) ::-webkit-scrollbar {
|
||
width: 10px;
|
||
height: 10px;
|
||
}
|
||
|
||
:not([data-theme="preline"]) ::-webkit-scrollbar-track {
|
||
background: #FEF9E7;
|
||
border-left: 2px solid black;
|
||
}
|
||
|
||
:not([data-theme="preline"]) ::-webkit-scrollbar-thumb {
|
||
background: #000;
|
||
border: 2px solid #FEF9E7;
|
||
}
|
||
|
||
/* ============================================ */
|
||
/* Preline 滚动条 (管理后台) */
|
||
/* ============================================ */
|
||
|
||
[data-theme="preline"] ::-webkit-scrollbar {
|
||
width: 8px;
|
||
height: 8px;
|
||
}
|
||
|
||
[data-theme="preline"] ::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
|
||
[data-theme="preline"] ::-webkit-scrollbar-thumb {
|
||
background: #CBD5E1;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
[data-theme="preline"] ::-webkit-scrollbar-thumb:hover {
|
||
background: #94A3B8;
|
||
}
|
||
```
|
||
|
||
#### 步骤 2: 更新 tailwind.config.js
|
||
|
||
**文件**: `tailwind.config.js`
|
||
|
||
```javascript
|
||
/** @type {import('tailwindcss').Config} */
|
||
export default {
|
||
content: [
|
||
"./index.html",
|
||
"./src/**/*.{js,ts,jsx,tsx}",
|
||
// Preline UI 支持
|
||
"./node_modules/preline/dist/*.js",
|
||
],
|
||
theme: {
|
||
extend: {
|
||
// 保留 Neobrutalism 颜色
|
||
colors: {
|
||
brutal: {
|
||
cream: '#FEF9E7',
|
||
yellow: '#FFDE59',
|
||
pink: '#FF6B9D',
|
||
cyan: '#00D9FF',
|
||
lavender: '#C084FC',
|
||
green: '#4ADE80',
|
||
},
|
||
// Preline 配色
|
||
preline: {
|
||
primary: '#10B981',
|
||
'primary-hover': '#059669',
|
||
'primary-light': '#D1FAE5',
|
||
sidebar: '#0F172A',
|
||
'sidebar-active': '#1E293B',
|
||
bg: '#F8FAFC',
|
||
card: '#FFFFFF',
|
||
border: '#E2E8F0',
|
||
}
|
||
},
|
||
boxShadow: {
|
||
'brutal': '4px 4px 0px 0px rgba(0,0,0,1)',
|
||
'brutal-sm': '2px 2px 0px 0px rgba(0,0,0,1)',
|
||
// Preline 柔和阴影
|
||
'card': '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
|
||
'card-hover': '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
|
||
},
|
||
fontFamily: {
|
||
display: ['system-ui', '-apple-system', 'sans-serif'],
|
||
sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
|
||
},
|
||
},
|
||
},
|
||
plugins: [
|
||
// Preline 需要
|
||
require('@tailwindcss/forms'),
|
||
],
|
||
}
|
||
```
|
||
|
||
#### 步骤 3: 添加 Preline JS
|
||
|
||
**文件**: `src/main.jsx`
|
||
|
||
```javascript
|
||
import React from 'react'
|
||
import ReactDOM from 'react-dom/client'
|
||
import { BrowserRouter } from 'react-router-dom'
|
||
import App from './App.jsx'
|
||
import './index.css'
|
||
|
||
// Preline UI JavaScript
|
||
import 'preline'
|
||
|
||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||
<React.StrictMode>
|
||
<BrowserRouter>
|
||
<App />
|
||
</BrowserRouter>
|
||
</React.StrictMode>,
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 组件迁移计划
|
||
|
||
### 3.1 新建 Preline 组件目录
|
||
|
||
```
|
||
src/
|
||
├── components/ # 现有 Neobrutalism 组件
|
||
│ ├── Button.jsx
|
||
│ ├── Card.jsx
|
||
│ ├── Input.jsx
|
||
│ ├── Layout.jsx
|
||
│ └── Sidebar.jsx
|
||
├── components-preline/ # 新建 Preline 组件
|
||
│ ├── Button.jsx
|
||
│ ├── Card.jsx
|
||
│ ├── Input.jsx
|
||
│ ├── Layout.jsx # Preline 风格后台布局
|
||
│ ├── Sidebar.jsx # Preline 深色侧边栏
|
||
│ ├── StatsCard.jsx # 统计卡片
|
||
│ ├── DataTable.jsx # 数据表格
|
||
│ └── PageHeader.jsx # 页面头部
|
||
```
|
||
|
||
### 3.2 核心组件对照表
|
||
|
||
| 功能 | Neobrutalism | Preline |
|
||
|------|--------------|---------|
|
||
| 按钮 | `btn-brutal` | `hs-btn` + Tailwind 类 |
|
||
| 卡片 | `card-brutal` | `hs-card` |
|
||
| 输入框 | `input-brutal` | `hs-input` |
|
||
| 侧边栏 | 奶油色 + 硬边框 | 深色 + 柔和圆角 |
|
||
| 表格 | 自定义 | `hs-table` |
|
||
| 模态框 | 自定义 | `hs-overlay` |
|
||
| 下拉菜单 | 自定义 | `hs-dropdown` |
|
||
|
||
---
|
||
|
||
## 4. 分阶段实施计划
|
||
|
||
### Phase 1: 基础配置 (第 1 天)
|
||
|
||
**目标**: 让 Preline 跑起来,不影响现有功能
|
||
|
||
- [ ] 更新 `src/index.css` 添加双主题支持
|
||
- [ ] 更新 `tailwind.config.js`
|
||
- [ ] 更新 `main.jsx` 引入 Preline JS
|
||
- [ ] 创建 `components-preline/` 目录结构
|
||
- [ ] 测试:确认现有页面正常
|
||
|
||
**验证点**: 现有 Neobrutalism 风格页面不受影响
|
||
|
||
---
|
||
|
||
### Phase 2: Settings 页面试点 (第 1-2 天)
|
||
|
||
**目标**: 用 Settings 页面验证迁移方案
|
||
|
||
**为什么选 Settings?**
|
||
- 功能单一,改动风险小
|
||
- 表单元素多,能验证 Preline 表单组件
|
||
- 有保存/成功提示,能验证交互
|
||
|
||
**具体改动**:
|
||
- [ ] 新建 `components-preline/Layout.jsx` (Preline 风格后台布局)
|
||
- [ ] 新建 `components-preline/Sidebar.jsx` (深色侧边栏)
|
||
- [ ] 新建 `components-preline/Input.jsx` (Preline 输入框)
|
||
- [ ] 新建 `components-preline/Button.jsx` (Preline 按钮)
|
||
- [ ] 改写 `pages/admin/Settings.jsx`
|
||
- [ ] 添加主题切换开关
|
||
|
||
**Settings 页面新结构**:
|
||
```jsx
|
||
// pages/admin/Settings.jsx
|
||
import { AdminLayout } from '../../components-preline/Layout'
|
||
import { Card } from '../../components-preline/Card'
|
||
import { Input } from '../../components-preline/Input'
|
||
import { Button } from '../../components-preline/Button'
|
||
|
||
export default function Settings() {
|
||
return (
|
||
<AdminLayout>
|
||
<div className="p-6 max-w-4xl mx-auto">
|
||
<h1 className="text-2xl font-semibold mb-6">博客设置</h1>
|
||
|
||
<Card>
|
||
<Card.Header>
|
||
<h3 className="text-lg font-medium">基本信息</h3>
|
||
</Card.Header>
|
||
<Card.Body>
|
||
{/* Preline 风格表单 */}
|
||
</Card.Body>
|
||
</Card>
|
||
</div>
|
||
</AdminLayout>
|
||
)
|
||
}
|
||
```
|
||
|
||
**验证点**: Settings 页面显示正常,表单能正常提交
|
||
|
||
---
|
||
|
||
### Phase 3: Dashboard 首页 (第 2 天)
|
||
|
||
**目标**: 后台首页改用 Preline 风格
|
||
|
||
**改动内容**:
|
||
- [ ] 新建 `components-preline/StatsCard.jsx` (统计卡片)
|
||
- [ ] 改写 `pages/Dashboard.jsx`
|
||
- [ ] 添加图表支持 (可选,用 Recharts 或 Preline Charts)
|
||
|
||
**Dashboard 新布局**:
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ [Sidebar] │ Header: Dashboard [🔔][👤] │
|
||
│ (dark) ├───────────────────────────────────────────┤
|
||
│ │ │
|
||
│ Dashboard │ ┌──────────┬──────────┬──────────┐ │
|
||
│ Posts │ │ StatsCard│ StatsCard│ StatsCard│ │
|
||
│ Categories │ └──────────┴──────────┴──────────┘ │
|
||
│ Images │ │
|
||
│ Settings │ ┌───────────────────────────────┐ │
|
||
│ │ │ Recent Posts Table │ │
|
||
│ │ └───────────────────────────────┘ │
|
||
│ │ │
|
||
└─────────────┴───────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
### Phase 4: Posts 管理页面 (第 2-3 天)
|
||
|
||
**目标**: 文章列表改用 Preline 风格
|
||
|
||
**改动内容**:
|
||
- [ ] 新建 `components-preline/DataTable.jsx` (数据表格)
|
||
- [ ] 新建 `components-preline/Badge.jsx` (状态标签)
|
||
- [ ] 新建 `components-preline/Pagination.jsx` (分页)
|
||
- [ ] 改写 `pages/Posts.jsx`
|
||
|
||
---
|
||
|
||
### Phase 5: PostEdit 编辑页面 (第 3 天)
|
||
|
||
**目标**: 文章编辑改用 Preline 风格
|
||
|
||
**改动内容**:
|
||
- [ ] 新建 `components-preline/Textarea.jsx` (Markdown 编辑器容器)
|
||
- [ ] 改写 `pages/PostEdit.jsx`
|
||
|
||
---
|
||
|
||
### Phase 6: 剩余页面 (后续)
|
||
|
||
- [ ] Login 页面
|
||
- [ ] Categories 页面
|
||
- [ ] Images 页面
|
||
- [ ] CrawlEdit 页面
|
||
|
||
---
|
||
|
||
### Phase 7: 主题切换功能 (可选)
|
||
|
||
**目标**: 支持在 Settings 切换后台主题
|
||
|
||
- [ ] 后端 Setting 表添加 `admin_theme` 字段
|
||
- [ ] 主题切换逻辑
|
||
- [ ] 主题持久化
|
||
|
||
---
|
||
|
||
## 5. 文件清单
|
||
|
||
### 5.1 需要新建的组件
|
||
|
||
```
|
||
src/components-preline/
|
||
├── Layout.jsx # 后台布局 (深色侧边栏 + 顶部栏)
|
||
├── Sidebar.jsx # 侧边栏导航
|
||
├── Header.jsx # 顶部栏
|
||
├── Button.jsx # 按钮
|
||
├── Card.jsx # 卡片
|
||
├── Input.jsx # 输入框
|
||
├── Textarea.jsx # 文本域
|
||
├── Select.jsx # 下拉选择
|
||
├── Badge.jsx # 标签
|
||
├── DataTable.jsx # 数据表格
|
||
├── Pagination.jsx # 分页
|
||
├── StatsCard.jsx # 统计卡片
|
||
├── PageHeader.jsx # 页面标题
|
||
├── Modal.jsx # 模态框
|
||
├── Alert.jsx # 提示消息
|
||
└── index.js # 统一导出
|
||
```
|
||
|
||
### 5.2 需要改写的页面
|
||
|
||
```
|
||
src/pages/
|
||
├── Dashboard.jsx # Phase 3
|
||
├── Posts.jsx # Phase 4
|
||
├── PostEdit.jsx # Phase 5
|
||
├── Login.jsx # Phase 6
|
||
└── admin/
|
||
├── Settings.jsx # Phase 2 (试点)
|
||
├── Categories.jsx # Phase 6
|
||
└── Images.jsx # Phase 6
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 风险与回滚方案
|
||
|
||
### 6.1 潜在风险
|
||
|
||
| 风险 | 概率 | 影响 | 应对 |
|
||
|------|------|------|------|
|
||
| CSS 冲突 | 中 | 样式错乱 | 双主题 CSS 变量隔离 |
|
||
| JS 兼容 | 低 | Preline 组件失效 | 保留原有组件作为后备 |
|
||
| 构建失败 | 低 | 无法部署 | 每次改动后单独测试 |
|
||
| 功能回归 | 中 | 原有功能失效 | 逐个页面迁移,充分测试 |
|
||
|
||
### 6.2 回滚方案
|
||
|
||
如果某个页面迁移后出问题:
|
||
|
||
```bash
|
||
# 回滚单个页面
|
||
git checkout src/pages/Posts.jsx
|
||
|
||
# 或者使用原有组件
|
||
# 把 import { Button } from '../components-preline/Button'
|
||
# 改回 import { Button } from '../components/Button'
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 验证清单
|
||
|
||
每个 Phase 完成后检查:
|
||
|
||
- [ ] 页面能正常加载
|
||
- [ ] 样式显示正确
|
||
- [ ] 表单能正常提交
|
||
- [ ] 响应式正常 (移动端/平板/桌面)
|
||
- [ ] 访客端页面不受影响
|
||
- [ ] 构建无警告/错误
|
||
|
||
---
|
||
|
||
## 8. 参考资源
|
||
|
||
- [Preline Documentation](https://preline.co/docs/index.html)
|
||
- [Preline Components](https://preline.co/docs/buttons.html)
|
||
- [Preline Admin Templates](https://preline.co/templates.html)
|
||
- [Preline React Integration](https://preline.co/docs/frameworks-react.html)
|
||
|
||
---
|
||
|
||
## 9. 下一步行动
|
||
|
||
确认方案后,从 **Phase 1** 开始执行:
|
||
|
||
1. ✅ 先更新配置文件 (CSS + Tailwind + main.jsx)
|
||
2. ✅ 然后 Phase 2: Settings 页面试点
|
||
3. ✅ 验证通过后继续后续 Phase
|
||
|
||
---
|
||
|
||
**确认开始执行吗?** 先确认一下理解:
|
||
|
||
> 采用双主题共存方案,访客端保持 Neobrutalism 奶油色粗边框风格,管理后台逐步迁移到 Preline 深色侧边栏现代风格,从 Settings 页面开始试点迁移。
|
||
|
||
确认后我开始写 Phase 1 的代码。
|