feat: 1.0.3 引入 Dark Reader 通用暗色引擎

This commit is contained in:
2026-08-08 17:21:22 +08:00
parent b36ad30d7a
commit 202ab537d1
29 changed files with 19555 additions and 207 deletions
+28 -12
View File
@@ -54,6 +54,7 @@ halo-dark-mode-plugin/
├── 前端 (Vue 3 / TypeScript) ← 核心实现
│ ├── index.ts ← definePlugin() 入口,注册路由+组件
│ ├── darkreader-engine.ts ← Dark Reader 通用暗色引擎
│ ├── injector.ts ← ThemeToggle 侧边栏注入器(MutationObserver 方案)
│ ├── composables/
│ │ ├── useDarkMode.ts ← 单例状态管理(light/dark/auto + localStorage 持久化)
@@ -80,33 +81,39 @@ halo-dark-mode-plugin/
### 主题切换机制
- **触发方式**`document.documentElement` 上设置/移除 `data-halo-theme="dark"` 属性
- **主引擎**Dark Reader(本地 vendored 于 `ui/vendor/darkreader`API 包由
`npm run api` 构建),监听 `useDarkMode().isDark`,深色时 `enable()`,浅色时
`disable()`,可自动分析 Halo 核心与第三方插件的动态 DOM
- **CSS 变量体系**:所有颜色通过 `--halo-*` 前缀的 CSS 自定义属性控制,一个语义变量对应一个视觉属性
- **颜色空间**:全部使用 OKLCH(感知均匀,暗色模式天然适配)
- **暗色配色策略**:用亮度层次区分背景(越"高"的层越亮),中性色含微量蓝色调,强调色略降饱和
- **CSS 覆盖定位**:现有 `overrides/` 仅作为 Dark Reader 的兼容层与兜底,
不再逐页新增手工转换规则;新页面残留统一交给 Dark Reader 处理
### ⚠️ 最关键的教训:Halo 2.25 用 UnoCSS,不是 Tailwind
Halo 2.25 的 Console 实际使用 **UnoCSS**hash 类如 `uno-*`)加 **BEM 语义类**。**不要写 `.bg-white``.text-gray-900``.v-card` 这类 Tailwind/Vuetify 原子选择器**——它们在真实 DOM 中不存在,覆盖会静默失效。
Halo 2.25 的 Console 实际使用 **UnoCSS**hash 类如 `uno-*`)加 **BEM 语义aliyun "curl -sI --max-time 5 http://localhost/ 2>&1 |**。**不要写 `.bg-white``.text-gray-900``.v-card` 这类 Tailwind/Vuetify 原子选择器**——它们在真实 DOM 中不存在,覆盖会静默失效。
真实 DOM 里的容器类名(已被 `halo-core.css` 覆盖):
| 语义 | 真实类名 |
|------|---------|
| 页面顶栏 | `.page-header` / `.page-header__title-text` |
| 列表卡片容器 | `.card-wrapper` |
| 文章/用户列表项标题 | `.entity-field-title` / `.entity-field-title-body` |
| 分页 | `.pagination` / `.pagination__btn` |
| 标签 | `.tag-default` / `.tag-content` |
| 模态框 | `.modal-content` / `.modal-header` / `.modal-body` / `.modal-footer` |
| 详情页描述项 | `.description-item-wrapper` / `.description-item__label` / `.description-item__content` |
| Toast | `.toast-container .toast-body` |
| 用户头像 | `.avatar-wrapper` / `.avatar-circle` |
| 语义 | 真实类名 |
| ------------------- | --------------------------------------------------------------------------------------------- |
| 页面顶栏 | `.page-header` / `.page-header__title-text` |
| 列表卡片容器 | `.card-wrapper` |
| 文章/用户列表项标题 | `.entity-field-title` / `.entity-field-title-body` |
| 分页 | `.pagination` / `.pagination__btn` |
| 标签 | `.tag-default` / `.tag-content` |
| 模态框 | `.modal-content` / `.modal-header` / `.modal-body` / `.modal-footer` |
| 详情页描述项 | `.description-item-wrapper` / `.description-item__label` / `.description-item__content` |
| Toast | `.toast-container .toast-body` |
| 用户头像 | `.avatar-wrapper` / `.avatar-circle` |
新增覆盖时:**先到真实环境确认类名,不要凭经验写**。
### 覆盖策略(重写后)
`halo-core.css` 是主要覆盖文件,按语义类精准覆盖。它包含三类规则:
1. **BEM 语义类**(如 `.card-wrapper`)— 直接 `[data-halo-theme="dark"] .card-wrapper { background-color: var(--halo-bg-card) }`
2. **通用 UnoCSS 工具类**(如 `.bg-gray-50``.hover:text-gray-600`)— 批量覆盖文字/背景/边框
3. **根背景兜底**`html, body { background-color: var(--halo-bg-body) !important }`body 不设暗色会在溢出时露白)
@@ -134,6 +141,15 @@ Halo 扩展点系统**没有侧边栏插槽**。`injector.ts` 用 `MutationObser
后端极简 — `DarkModePlugin extends BasePlugin` 仅含 `start()`/`stop()` 生命周期钩子。所有核心逻辑在前端。插件不依赖后端 Setting API。
## 打包约定(必须)
每次执行 `./gradlew build` 打 JAR 前,**必须先递增版本标签**:
1. 修改 `gradle.properties` 中的 `version`(如 `1.0.2``1.0.3`
2. 同步更新 `src/main/java/run/halo/darkmode/DarkModePlugin.java``@since`
3. 构建完成后核对 `build/libs/plugin-dark-mode-<新版本>.jar` 存在,且 JAR 内的
`ui/style.css``ui/main.js` 包含本次改动(用关键字检查,例如新增类名)
4. 向用户交付时明确给出新 JAR 路径和版本号,避免线上仍加载旧 bundle 的混淆
## 验证工作流(必须)
对 CSS 覆盖的任何改动,都要在**真实 Halo 环境**验证,不能只靠 `vite build` 通过: