edbf78f236
- Halo Plugin 后端(Java/Gradle),含 DarkModePlugin 主类和测试 - Vue 3 + TypeScript 前端 UI,包含主题切换组件和设置页面 - 暗色模式 CSS 变量和覆盖样式(布局/编辑器/表单/滚动条等) - 设计文档和调查文档 - Halo 插件/主题开发 Agent Skills Co-Authored-By: Claude <noreply@anthropic.com>
557 lines
20 KiB
Markdown
557 lines
20 KiB
Markdown
# Halo 黑暗模式插件 — 调查文档
|
||
|
||
> 调查日期:2026-08-06
|
||
> 调查范围:Halo 核心仓库、plugin-starter、create-halo-plugin、dev-skills、theme-vite-starter、插件生态系统
|
||
> 目的:为开发 Halo 后台管理面板黑暗模式插件提供技术基础
|
||
|
||
---
|
||
|
||
## 0. 补充调查:create-halo-plugin / dev-skills / theme-vite-starter
|
||
|
||
> 本节为第二轮调查补充(2026-08-06),基于对三个额外仓库的分析。**本节内容优先于第 1-6 节中过时的信息。**
|
||
|
||
### 0.1 create-halo-plugin — 官方推荐的脚手架工具
|
||
|
||
**仓库**:https://github.com/halo-dev/create-halo-plugin
|
||
|
||
这是 Halo 官方推荐的插件创建方式,取代了旧的 `plugin-starter`:
|
||
|
||
```bash
|
||
pnpm create halo-plugin
|
||
pnpm create halo-plugin my-plugin
|
||
```
|
||
|
||
**交互式 CLI 特性**:
|
||
- 插件名称(遵循 `[a-z0-9][a-z0-9.-]*[a-z0-9]` 规则)
|
||
- 域名 → 自动生成 Java 包名
|
||
- 作者姓名
|
||
- 是否包含 UI 项目
|
||
- **UI 构建工具选择:Vite 或 Rsbuild**
|
||
|
||
**与 plugin-starter 的关键差异**:
|
||
|
||
| 项目 | plugin-starter (旧) | create-halo-plugin (新) |
|
||
|------|---------------------|------------------------|
|
||
| `definePlugin` 导入源 | `@halo-dev/console-shared` | `@halo-dev/ui-shared` |
|
||
| UI 构建产物目录 | `resources/main/console/` | `resources/main/ui/` |
|
||
| BOM 版本 | `2.23.0-SNAPSHOT` | `2.25.0` |
|
||
| DevTools 版本 | `0.6.2` | `0.8.0` |
|
||
| `halo.version` | `2.23.0-beta.2` | `2.25` |
|
||
| Maven 仓库 | 含 `Central Portal Snapshots` | 仅 `mavenCentral()` |
|
||
| 路由导入 | 静态 import | 懒加载 `() => import(...)` |
|
||
| 代码格式化 | Prettier | ESLint |
|
||
| SNAPSHOT 依赖 | 是 | **否(仅稳定版)** |
|
||
|
||
### 0.2 dev-skills — AI Agent 开发技能库
|
||
|
||
**仓库**:https://github.com/halo-dev/dev-skills
|
||
|
||
这是 Halo 官方的 Agent Skills 集合,为 AI Agent 提供结构化的领域知识。
|
||
|
||
**`halo-plugin-dev` 技能** — 涵盖 20+ 参考文档:
|
||
|
||
| 参考文档 | 用途 |
|
||
|----------|------|
|
||
| `plugin-structure.md` | 目录结构、前后端布局 |
|
||
| `plugin-manifest.md` | plugin.yaml 字段、版本约束、依赖 |
|
||
| `server-lifecycle.md` | BasePlugin start/stop/delete 生命周期 |
|
||
| `server-extension.md` | 自定义 GVK 扩展、CRUD API |
|
||
| `server-api.md` | CustomEndpoint、Controller |
|
||
| `server-shared-beans.md` | 注入 Halo 核心服务 |
|
||
| `server-security.md` | RBAC 角色模板 |
|
||
| `ui-entry.md` | definePlugin、routes、菜单配置 |
|
||
| `ui-build.md` | bundler-kit (Vite/Rsbuild)、产物目录 |
|
||
| `ui-shared.md` | stores (currentUser, globalInfo)、utils |
|
||
| `ui-extension-points.md` | Console UI 扩展点(编辑器、附件选择器等) |
|
||
| `ui-components.md` | @halo-dev/components 组件库 |
|
||
| `ui-forms.md` | FormKit schema、自定义输入组件 |
|
||
| `ui-api-request.md` | API client 调用、OpenAPI 客户端生成 |
|
||
| `ui-tooling.md` | unplugin-icons、UnoCSS 工具链 |
|
||
| `devtools.md` | haloServer、reload、watch、调试 |
|
||
| `api-changelog.md` | 各版本 API 变更记录 |
|
||
| `theme-head-processor.md` | 注入脚本/样式到主题 `<head>` |
|
||
| `theme-content-handler.md` | 修改渲染后的 HTML |
|
||
| `theme-integration.md` | 主题 Finder API、反向代理 |
|
||
|
||
> **实施时**:开发过程中应加载 `halo-plugin-dev` 技能以获取精确的 API 参考。
|
||
|
||
### 0.3 theme-vite-starter — 主题开发参考
|
||
|
||
**仓库**:https://github.com/halo-dev/theme-vite-starter
|
||
|
||
这是 Halo 主题脚手架,与插件开发不直接相关,但展示了 Halo 生态的以下约定:
|
||
- 使用 `vite-plus`(Vite 超集,集成 format/lint)
|
||
- 使用 `@halo-dev/vite-plugin-halo-theme` 构建主题
|
||
- `theme.yaml` manifest 格式(`apiVersion: theme.halo.run/v1alpha1`)
|
||
- `settings.yaml` 用于控制台表单配置
|
||
- Agent Skills 嵌入在 `.agents/skills/` 目录
|
||
|
||
### 0.4 对设计文档的影响
|
||
|
||
基于以上新发现,设计文档需要做以下修正:
|
||
|
||
1. **脚手架工具**:使用 `pnpm create halo-plugin` 而非手动克隆 plugin-starter
|
||
2. **`definePlugin` 导入**:从 `@halo-dev/ui-shared` 导入(非 `@halo-dev/console-shared`)
|
||
3. **版本目标**:BOM `2.25.0`,DevTools `0.8.0`,Halo `2.25`
|
||
4. **UI 产物路径**:`resources/main/ui/`(非 `resources/main/console/`)
|
||
5. **路由懒加载**:使用 `() => import(...)` 动态导入
|
||
6. **不需要 SNAPSHOT 仓库**:仅 `mavenCentral()` 即可
|
||
7. **开发时参考**:善用 `halo-plugin-dev` skill 的参考文档
|
||
|
||
---
|
||
|
||
## 1. Halo 项目概览
|
||
|
||
### 1.1 基本信息
|
||
|
||
| 属性 | 值 |
|
||
|------|-----|
|
||
| 仓库 | https://github.com/halo-dev/halo |
|
||
| 许可 | GPL-3.0 |
|
||
| 类型 | 全栈 monorepo |
|
||
| 最新稳定版 | 2.25 |
|
||
| 当前开发版 | 2.23.0-beta.2(plugin-starter 依赖版本) |
|
||
|
||
### 1.2 技术栈总览
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ HALO 架构图 │
|
||
├─────────────────────────────────────────────────────┤
|
||
│ 前端 (ui/) │
|
||
│ ├─ console-src/ → 后台管理面板 (Vue 3 + TS) │
|
||
│ ├─ uc-src/ → 用户中心 (Vue 3 + TS) │
|
||
│ ├─ src/ → 共享代码 (locales/styles/setup) │
|
||
│ └─ packages/ → 共享工作空间包 │
|
||
│ ├─ api-client → 后端 API 客户端 (生成) │
|
||
│ ├─ components → UI 组件库 (Storybook) │
|
||
│ ├─ console-shared → definePlugin API │
|
||
│ ├─ editor → 富文本编辑器 │
|
||
│ ├─ shared → 共享工具 │
|
||
│ └─ ui-plugin-bundler-kit → 插件打包工具链 │
|
||
├─────────────────────────────────────────────────────┤
|
||
│ 后端 │
|
||
│ ├─ api/ → 扩展模型/合约/安全 API │
|
||
│ ├─ application/ → 服务/路由/数据迁移 │
|
||
│ └─ platform/ → BOM (依赖版本约束) │
|
||
│ ├─ application/ → 应用 BOM │
|
||
│ └─ plugin/ → 插件 BOM (插件可用的依赖) │
|
||
└─────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**核心依赖版本:**
|
||
|
||
| 层 | 技术 | 版本 |
|
||
|----|------|------|
|
||
| 后端 | Java | 21 |
|
||
| 后端 | Spring Boot (WebFlux) | 3.x |
|
||
| 后端 | Gradle | 8.x |
|
||
| 前端 | Vue | 3.x |
|
||
| 前端 | TypeScript | 5.x |
|
||
| 前端 | TailwindCSS | 3.x (含 tailwindcss-themer) |
|
||
| 前端 | Vite | 6.x |
|
||
| 前端 | pnpm | 9.x |
|
||
| 前端 | Pinia | 2.x (状态管理) |
|
||
| 前端 | Vue Router | 4.x |
|
||
| 前端 | FormKit | 1.x (表单) |
|
||
|
||
---
|
||
|
||
## 2. 插件系统深度分析
|
||
|
||
### 2.1 插件生命周期
|
||
|
||
```java
|
||
// 每个插件只有一个主类继承 BasePlugin
|
||
@Component
|
||
public class PluginMain extends BasePlugin {
|
||
|
||
public PluginMain(PluginContext pluginContext) {
|
||
super(pluginContext);
|
||
}
|
||
|
||
@Override
|
||
public void start() {
|
||
// 插件启动 → 注册扩展点、创建资源
|
||
}
|
||
|
||
@Override
|
||
public void stop() {
|
||
// 插件停止 → 清理资源、取消注册
|
||
}
|
||
}
|
||
```
|
||
|
||
**关键点:**
|
||
- 插件以 Spring `@Component` 的形式存在
|
||
- 通过 `PluginContext` 可以访问 Halo 运行时上下文
|
||
- `start()` / `stop()` 是主要的生命周期钩子
|
||
- 只允许一个类继承 `BasePlugin`
|
||
|
||
### 2.2 plugin.yaml 清单
|
||
|
||
```yaml
|
||
apiVersion: plugin.halo.run/v1alpha1
|
||
kind: Plugin
|
||
metadata:
|
||
name: PluginName # 唯一标识符,后续无法修改
|
||
spec:
|
||
enabled: true
|
||
requires: ">=2.23.0" # Halo 版本要求
|
||
author:
|
||
name: 作者名
|
||
website: https://example.com
|
||
logo: logo.png
|
||
homepage: https://github.com/...
|
||
repo: https://github.com/...
|
||
issues: https://github.com/.../issues
|
||
displayName: "插件显示名称"
|
||
description: "插件描述"
|
||
license:
|
||
- name: "GPL-3.0"
|
||
```
|
||
|
||
**关键约束:**
|
||
- `metadata.name` 设置后不能修改(作为标识符绑定到 API 路由和资源路径)
|
||
- `spec.requires` 定义最低 Halo 版本
|
||
- 版本约束由 `platform/plugin/` BOM 管理
|
||
|
||
### 2.3 插件 Gradle 构建系统
|
||
|
||
```groovy
|
||
plugins {
|
||
id 'java'
|
||
id "io.freefair.lombok" version "9.2.0"
|
||
id "run.halo.plugin.devtools" version "0.6.2" // 热重载开发工具
|
||
}
|
||
|
||
dependencies {
|
||
implementation platform('run.halo.tools.platform:plugin:2.23.0-SNAPSHOT')
|
||
compileOnly 'run.halo.app:api' // 编译时 API
|
||
testImplementation 'run.halo.app:api' // 测试时可用
|
||
}
|
||
|
||
// 前端资源复制到后端 classpath
|
||
tasks.register('processUiResources', Copy) {
|
||
from project(':ui').layout.buildDirectory.dir('dist')
|
||
into layout.buildDirectory.dir('resources/main/console')
|
||
dependsOn project(':ui').tasks.named('assemble')
|
||
}
|
||
```
|
||
|
||
**关键点:**
|
||
- `run.halo.app:api` 是 `compileOnly` — 运行时由 Halo 提供
|
||
- 前端构建产物(JS/CSS)被复制到 `resources/main/console/`
|
||
- DevTools 插件支持 Docker 热重载开发
|
||
|
||
### 2.4 插件前端入口系统
|
||
|
||
```typescript
|
||
// ui/src/index.ts — 插件前端唯一入口
|
||
import { definePlugin } from '@halo-dev/console-shared'
|
||
import HomeView from './views/HomeView.vue'
|
||
import { IconPlug } from '@halo-dev/components'
|
||
import { markRaw } from 'vue'
|
||
|
||
export default definePlugin({
|
||
components: {}, // { [name: string]: Component } — 全局注册组件
|
||
routes: [...], // 路由定义(可追加到父路由)
|
||
extensionPoints: {}, // 扩展点(当前生态尚不成熟)
|
||
ucRoutes: [...], // 用户中心路由(可选)
|
||
})
|
||
```
|
||
|
||
**`definePlugin` 的本质:** 源码显示它是一个**身份函数**(identity function),即 `(x) => x`。这意味着插件配置对象直接传递给 Halo 运行时,没有额外的转换层。这简化了调试但也意味着没有类型强制。
|
||
|
||
### 2.5 插件资源加载机制(核心发现)
|
||
|
||
```
|
||
浏览器加载流程:
|
||
1. Halo 后端生成 bundle URL:
|
||
/apis/api.console.halo.run/v1alpha1/ui-plugins/-/bundle.js?t=<timestamp>
|
||
/apis/api.console.halo.run/v1alpha1/ui-plugins/-/bundle.css?t=<timestamp>
|
||
|
||
2. setupModules.ts 执行流程:
|
||
├─ loadEnabledUiPluginModules()
|
||
│ ├─ loadUiPluginBundle() → 加载 JS bundle
|
||
│ └─ 读取 window["enabledUiPlugins"] → 获取已启用插件列表
|
||
├─ setupComponents() → 注册 FormKit 输入组件
|
||
├─ setupPluginModules() → 注册路由 + 全局组件
|
||
└─ setupPluginStyles() → 加载插件 CSS bundle
|
||
└─ loadStyle(url) → 动态创建 <link> 标签
|
||
```
|
||
|
||
**关键发现:**
|
||
- **插件 CSS 会自动加载**:`setupPluginStyles()` 函数通过 `loadStyle()` 动态注入 `<link>` 标签到 `<head>`
|
||
- **CSS 在所有插件 JS 初始化完成后加载**:这确保了样式加载有正确的优先级
|
||
- **无需 Java 端干预 CSS 注入**:只要插件包含前端资源,Halo 会自动处理
|
||
- `window["enabledUiPlugins"]` 由后端填充,包含所有已启用插件的元数据
|
||
|
||
---
|
||
|
||
## 3. 当前 UI 主题系统分析
|
||
|
||
### 3.1 TailwindCSS 配置
|
||
|
||
```typescript
|
||
// ui/tailwind.config.ts
|
||
plugins: [
|
||
themer({
|
||
defaultTheme: {
|
||
extend: {
|
||
colors: {
|
||
primary: "#4CCBA0", // 绿色主色调
|
||
secondary: "#0E1731", // 深蓝色辅助色
|
||
danger: "#D71D1D", // 红色危险色
|
||
},
|
||
borderRadius: {
|
||
base: "4px",
|
||
},
|
||
},
|
||
},
|
||
// 注意:只有 defaultTheme,没有 dark 主题
|
||
}),
|
||
]
|
||
```
|
||
|
||
**核心问题:**
|
||
- `tailwindcss-themer` 支持多主题但只配置了一个 `defaultTheme`
|
||
- **没有定义 dark 主题变量** — 这是黑暗模式缺失的根本原因
|
||
- 所有颜色值硬编码为浅色模式值
|
||
|
||
### 3.2 BasicLayout 颜色扫描
|
||
|
||
通过分析 `BasicLayout.vue`(后台主布局),发现以下颜色使用模式:
|
||
|
||
| 元素 | 当前颜色 | 来源 |
|
||
|------|---------|------|
|
||
| 侧边栏背景 | `white` | `theme("colors.white")` |
|
||
| 搜索框背景 | `gray.100` | `theme("colors.gray.100")` |
|
||
| 搜索框图标色 | `gray.400` | `theme("colors.gray.400")` |
|
||
| 页脚文字 | `gray.600` | `theme("colors.gray.600")` |
|
||
| 悬停链接色 | `gray.600` | `theme("colors.gray.600")` |
|
||
| 搜索框悬停文字 | `gray.900` | `theme("colors.gray.900")` |
|
||
|
||
这些全部是 Tailwind 默认的浅色模式 gray 色调。
|
||
|
||
### 3.3 主题 Store 分析
|
||
|
||
```typescript
|
||
// ui/console-src/stores/theme.ts
|
||
export const useThemeStore = defineStore("theme", () => {
|
||
const activatedTheme = ref<Theme>();
|
||
|
||
async function fetchActivatedTheme() {
|
||
// 注意:这里获取的是站点前台主题(博客主题),不是 UI 主题
|
||
const { data } = await consoleApiClient.theme.theme.fetchActivatedTheme(...);
|
||
activatedTheme.value = data;
|
||
}
|
||
|
||
return { activatedTheme, fetchActivatedTheme };
|
||
});
|
||
```
|
||
|
||
**关键发现:**
|
||
- `useThemeStore` 管理的是**站点前台主题**(用于访客页面渲染)
|
||
- **Halo 后台完全没有 UI 主题/黑暗模式的概念**
|
||
- 不存在 `uiTheme`、`darkMode` 相关的 store、API 或配置项
|
||
|
||
### 3.4 样式加载架构
|
||
|
||
```
|
||
setupStyles.ts 加载顺序:
|
||
1. @halo-dev/richtext-editor/dist/style.css → 编辑器样式
|
||
2. @halo-dev/components/dist/style.css → 组件库样式
|
||
3. @/styles/tailwind.css → Tailwind 基础 + 组件样式
|
||
4. @/styles/index.css → 全局样式覆盖
|
||
5. overlayscrollbars/overlayscrollbars.css → 滚动条样式
|
||
6. 插件 CSS (最后加载) → setupPluginStyles()
|
||
```
|
||
|
||
**重要发现:插件 CSS 最后加载**,这意味着插件可以通过更高特异性的选择器覆盖前面的样式。这为黑暗模式插件的 CSS 覆盖策略提供了天然的优先级优势。
|
||
|
||
---
|
||
|
||
## 4. 黑暗模式实现可行性分析
|
||
|
||
### 4.1 Halo 现有的"主题"相关 API
|
||
|
||
| API / Store | 用途 | 是否可用于 UI 暗色模式 |
|
||
|-------------|------|----------------------|
|
||
| `useThemeStore` | 站点前台主题管理 | ❌ 不相关 |
|
||
| `tailwindcss-themer` | Tailwind 多主题支持 | ✅ 基础设施存在但未使用 |
|
||
| 插件 CSS bundle | 自动加载插件样式 | ✅ 天然支持 |
|
||
| 插件 JS bundle | 自动加载插件 JS | ✅ 天然支持 |
|
||
|
||
### 4.2 实现黑暗模式的可行路径
|
||
|
||
#### 方案 A:纯 CSS 覆盖(推荐 — 最简可行方案)
|
||
|
||
**原理:** 利用插件 CSS 最后加载的优先级,用高特异性 CSS 规则覆盖所有浅色模式样式。
|
||
|
||
**优点:**
|
||
- 无需修改 Halo 核心代码
|
||
- 开发成本最低
|
||
- 不依赖不成熟的 extensionPoints API
|
||
- 兼容所有 Halo 2.x 版本
|
||
|
||
**缺点:**
|
||
- 需要精准定位所有颜色元素
|
||
- Halo 版本升级可能导致选择器失效
|
||
- 无法利用 Tailwind 的 `dark:` 前缀体系
|
||
|
||
**技术要点:**
|
||
```css
|
||
/* 通过属性选择器 + CSS 变量方案 */
|
||
[data-color-scheme="dark"] {
|
||
--color-bg: #1a1a2e;
|
||
--color-text: #e0e0e0;
|
||
/* ... 覆盖所有相关的 CSS 变量/类 */
|
||
}
|
||
```
|
||
|
||
#### 方案 B:注入 Tailwind dark 主题(中等复杂度)
|
||
|
||
**原理:** 通过插件在运行时修改 `tailwindcss-themer` 的配置,注入 dark 主题。
|
||
|
||
**优点:**
|
||
- 可以复用 Tailwind 的 `dark:` 类名前缀
|
||
- 更系统化的颜色管理
|
||
|
||
**缺点:**
|
||
- 需要访问 Tailwind 运行时配置(不确定是否可行)
|
||
- `tailwindcss-themer` 的主题在构建时确定,运行时修改不标准
|
||
- 需要修改 Halo 前端构建流程,超出插件范畴
|
||
|
||
**可行性评估:不可行** — Tailwind 主题是编译时确定的,插件无法在运行时修改。
|
||
|
||
#### 方案 C:自定义 CSS Properties 体系(推荐 — 稳健方案)
|
||
|
||
**原理:** 在插件 CSS 中定义一套完整的 CSS 自定义属性(CSS Variables),覆盖 Tailwind 和 Halo 组件的颜色。
|
||
|
||
**优点:**
|
||
- CSS Variables 是标准 Web 技术,浏览器原生支持
|
||
- 切换时只需修改 `:root` 或 `[data-theme]` 属性
|
||
- 渐进式,可以分批覆盖
|
||
- 易于维护和扩展
|
||
- 可以使用 `prefers-color-scheme` 媒体查询自动跟随系统
|
||
|
||
**缺点:**
|
||
- 需要全面覆盖 Tailwind 颜色类
|
||
- 需要为 Halo 自定义组件定义变量名
|
||
|
||
**技术要点:**
|
||
```css
|
||
:root {
|
||
--halo-bg-primary: #ffffff;
|
||
--halo-bg-secondary: #f3f4f6;
|
||
--halo-text-primary: #111827;
|
||
--halo-text-secondary: #6b7280;
|
||
--halo-border: #e5e7eb;
|
||
/* ... */
|
||
}
|
||
|
||
[data-halo-theme="dark"] {
|
||
--halo-bg-primary: #1a1a2e;
|
||
--halo-bg-secondary: #16213e;
|
||
--halo-text-primary: #e0e0e0;
|
||
--halo-text-secondary: #a0a0a0;
|
||
--halo-border: #2a2a4a;
|
||
/* ... */
|
||
}
|
||
```
|
||
|
||
### 4.3 推荐方案:方案 C(CSS Variables)+ 方案 A 作为补充
|
||
|
||
采用 **CSS 自定义属性** 作为主体策略,对少量无法通过变量覆盖的硬编码颜色采用 **选择器覆盖** 作为补充。
|
||
|
||
---
|
||
|
||
## 5. 插件架构设计关键决策点
|
||
|
||
### 5.1 需要改动的内容
|
||
|
||
| 层级 | 内容 | 必要性 |
|
||
|------|------|--------|
|
||
| 后端 Java | 插件入口类(基本骨架) | ✅ 必须 |
|
||
| 后端 Java | 可选的 Setting API 端点 | ⬜ 可选(存储用户偏好) |
|
||
| plugin.yaml | 插件元数据 | ✅ 必须 |
|
||
| 前端 JS | 开关按钮 UI 组件 | ✅ 必须 |
|
||
| 前端 JS | 路由注册(设置页) | ✅ 必须 |
|
||
| 前端 JS | 主题切换逻辑 | ✅ 必须 |
|
||
| 前端 CSS | 暗色变量定义 (~200-500 变量) | ✅ 必须 |
|
||
| 前端 CSS | 组件覆盖样式 | ✅ 必须 |
|
||
|
||
### 5.2 不需要改动的内容
|
||
|
||
- ❌ Halo 核心代码 — 插件机制完全支持
|
||
- ❌ Tailwind 配置 — 通过 CSS 变量覆盖
|
||
- ❌ Halo 组件库源码 — 通过 CSS 变量穿透
|
||
- ❌ 后端路由 — 使用现有 API
|
||
|
||
### 5.3 开关按钮放置位置
|
||
|
||
分析 `BasicLayout.vue` 后发现可选位置:
|
||
|
||
1. **侧边栏底部(推荐)** — `sidebar__profile` 区域,与用户头像并列
|
||
2. **顶部导航栏** — 但目前 Halo 没有顶部导航栏
|
||
3. **插件自有设置页面** — 可添加一个设置页面
|
||
|
||
### 5.4 偏好持久化策略
|
||
|
||
| 方案 | 复杂度 | 跨设备 | 持久性 |
|
||
|------|--------|--------|--------|
|
||
| `localStorage` | 极低 | ❌ | 浏览器级 |
|
||
| 后端 Plugin Setting API | 中 | ✅ | 服务器级 |
|
||
| Cookie | 低 | ❌ | 浏览器级 |
|
||
|
||
**推荐:`localStorage` + `prefers-color-scheme` 作为初始版本,后续可选升级到后端持久化。**
|
||
|
||
---
|
||
|
||
## 6. 现有参考资源
|
||
|
||
### 6.1 Halo 生态相关仓库
|
||
|
||
| 仓库 | 用途 | 关键信息 |
|
||
|------|------|---------|
|
||
| halo-dev/halo | 核心仓库 | 前端 Vue 3 + Tailwind,后端 Spring Boot |
|
||
| halo-dev/plugin-starter | 插件模板 | 完整 Gradle + Vue 3 插件骨架 |
|
||
| halo-dev/create-halo-plugin | 交互式创建工具 | 推荐替代 plugin-starter |
|
||
|
||
### 6.2 关键发现总结
|
||
|
||
1. **Halo 后台完全没有黑暗模式支持** — 这既是挑战也是机会,意味着插件是第一无二的解决方案
|
||
2. **插件 CSS 最后加载** — 为样式覆盖提供了天然优势
|
||
3. **`definePlugin` 是纯透传函数** — 简单但缺少类型安全,需要自行保证配置正确性
|
||
4. **`tailwindcss-themer` 基础设施存在** — 但只配置了 light 主题且运行时无法修改
|
||
5. **`extensionPoints` 机制尚不成熟** — 不推荐作为主要实现手段
|
||
6. **Halo 使用 `pnpm` workspace** — 插件前端开发需匹配此工具链
|
||
|
||
### 6.3 技术风险
|
||
|
||
| 风险 | 严重程度 | 缓解措施 |
|
||
|------|---------|---------|
|
||
| Halo 升级导致选择器失效 | 中 | 使用语义化 CSS 变量名;定期跟进 Halo changelog |
|
||
| `@halo-dev/components` 组件内部样式无法覆盖 | 中 | 利用 CSS 变量穿透(该组件库可能使用 CSS 变量或 Tailwind 类) |
|
||
| 第三方插件不受暗色主题影响 | 低 | 在设计文档中说明插件的暗色化范围 |
|
||
| FormKit/编辑器样式复杂 | 中 | 优先保证核心布局暗色化,表单和编辑器作为第二阶段 |
|
||
|
||
---
|
||
|
||
## 7. 下一步建议
|
||
|
||
1. **审阅此调查文档**,确认技术方向
|
||
2. **编写设计文档**,详细定义:
|
||
- CSS 变量命名体系
|
||
- 组件覆盖策略
|
||
- 开关 UI 设计
|
||
- 路由/菜单结构
|
||
- 版本兼容性承诺
|
||
3. **使用 `create-halo-plugin` 创建插件骨架**
|
||
4. **TDD 开发流程**(先写测试,后实现)
|
||
|
||
---
|
||
|
||
*本文档基于 2026-08-06 时 Halo v2.25 的代码分析。*
|