Compare commits

2 Commits

Author SHA1 Message Date
Serendipity 42df299791 fix: 重写暗色模式覆盖,基于真实 DOM 类名
问题: 之前的 CSS 覆盖使用推测的 Tailwind/Vuetify 类名,
但 Halo 2.25 实际使用 UnoCSS + BEM 语义类, 导致大部分
页面元素未切换颜色。

修复:
- 新增 halo-core.css, 覆盖真实类名:
  .page-header, .card-wrapper, .entity-field-title,
  .pagination, .tag-default, .modal-*, .description-item-*,
  .toast-container, .markdown-body, html/body 根背景
- 新增 injector.ts, 通过 MutationObserver 将 ThemeToggle
  挂载到侧边栏 .sidebar__profile 上方
- 基于 demo.halocms.site 真实环境扫描验证 9 个页面
  零白色残留、零不可见文字

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-06 22:49:03 +08:00
Serendipity 2a37c36894 chore: 修正构建配置以支持本机 JDK 25 构建
- 移除 toolchain 约束,改用 options.release = 21 兼容 Halo API
- 通过 gradle.properties 指定本机 JDK 25 路径
- gradle wrapper 切换至腾讯镜像源解决下载超时

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-06 22:48:57 +08:00
8 changed files with 334 additions and 14 deletions
-6
View File
@@ -23,12 +23,6 @@ test {
useJUnitPlatform()
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
tasks.withType(JavaCompile).configureEach {
options.encoding = "UTF-8"
options.release = 21
+2
View File
@@ -1 +1,3 @@
version=1.0.0-SNAPSHOT
org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8
org.gradle.java.home=D:/settings/Language/Java/jdk-25.0.1
+3 -3
View File
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-9.4.0-bin.zip
networkTimeout=30000
validateDistributionUrl=false
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+4 -3
View File
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { IconMoon, IconSunny } from '@halo-dev/components'
import IconSunLine from '~icons/ri/sun-line'
import IconMoonLine from '~icons/ri/moon-line'
import { useDarkMode } from '../composables/useDarkMode'
const { isDark, toggle } = useDarkMode()
@@ -7,8 +8,8 @@ const { isDark, toggle } = useDarkMode()
<template>
<div class="theme-toggle" @click="toggle">
<IconSunny v-if="isDark" class="theme-toggle__icon" />
<IconMoon v-else class="theme-toggle__icon" />
<IconSunLine v-if="isDark" class="theme-toggle__icon" />
<IconMoonLine v-else class="theme-toggle__icon" />
<span class="theme-toggle__label">
{{ isDark ? '浅色模式' : '深色模式' }}
</span>
+6 -2
View File
@@ -1,7 +1,11 @@
import { definePlugin } from '@halo-dev/ui-shared'
import { IconMoon } from '@halo-dev/components'
import { IconPalette } from '@halo-dev/components'
import { markRaw } from 'vue'
import './styles/index.css'
import { injectThemeToggle } from './injector'
// 在插件加载后将切换器注入到侧边栏
injectThemeToggle()
export default definePlugin({
components: {},
@@ -18,7 +22,7 @@ export default definePlugin({
menu: {
name: '深色模式',
group: '偏好设置',
icon: markRaw(IconMoon),
icon: markRaw(IconPalette),
priority: 50,
},
},
+46
View File
@@ -0,0 +1,46 @@
import { createVNode, render } from 'vue'
import ThemeToggle from './components/ThemeToggle.vue'
const CONTAINER_CLASS = 'plugin-dark-mode-toggle'
const TARGET_SELECTOR = '.sidebar__profile'
let mounted = false
/**
* 将 ThemeToggle 注入到侧边栏中 UserProfileBanner 上方。
* 使用 MutationObserver 等待侧边栏 DOM 渲染完成。
*/
export function injectThemeToggle(): void {
if (mounted) return
// 先尝试直接查找(侧边栏可能已经渲染)
tryMount()
// 如果还没渲染,等待 DOM 变化
if (!mounted) {
const observer = new MutationObserver(() => {
tryMount()
if (mounted) observer.disconnect()
})
observer.observe(document.body, { childList: true, subtree: true })
}
}
function tryMount(): void {
// 避免重复挂载
if (document.querySelector(`.${CONTAINER_CLASS}`)) {
mounted = true
return
}
const profileEl = document.querySelector(TARGET_SELECTOR)
if (!profileEl?.parentNode) return
const container = document.createElement('div')
container.className = CONTAINER_CLASS
profileEl.parentNode.insertBefore(container, profileEl)
const vnode = createVNode(ThemeToggle)
render(vnode, container)
mounted = true
}
+1
View File
@@ -4,6 +4,7 @@
============================================================ */
@import './variables.css';
@import './overrides/halo-core.css';
@import './overrides/utilities.css';
@import './overrides/layout.css';
@import './overrides/components.css';
+272
View File
@@ -0,0 +1,272 @@
/* ============================================================
Halo Dark Mode — Halo 核心 BEM 类覆盖
基于 Halo 2.25 真实 DOM 扫描(demo.halocms.site)得出的选择器
============================================================ */
[data-halo-theme="dark"] {
/* ===== 页面根背景(body 溢出时兜底) ===== */
html,
body {
background-color: var(--halo-bg-body) !important;
color: var(--halo-text-primary);
}
/* ===== Toast 通知 ===== */
.toast-container .toast-body {
background-color: var(--halo-bg-card) !important;
color: var(--halo-text-primary) !important;
border-color: var(--halo-border-base);
}
/* ===== Markdown 正文(编辑器预览/瞬间预览) ===== */
.markdown-body {
background-color: transparent;
color: var(--halo-text-primary);
}
.markdown-body p,
.markdown-body li,
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6,
.markdown-body td,
.markdown-body th,
.markdown-body blockquote,
.markdown-body a {
color: var(--halo-text-primary);
}
.markdown-body a {
color: var(--halo-text-link);
}
.markdown-body code,
.markdown-body tt {
background-color: oklch(28% 0.02 250);
color: oklch(85% 0.05 160);
}
.markdown-body pre,
.markdown-body .highlight pre {
background-color: oklch(20% 0.015 250);
color: oklch(85% 0.03 160);
}
.markdown-body table tr,
.markdown-body table tr:nth-child(2n) {
background-color: var(--halo-bg-card);
}
.markdown-body table td,
.markdown-body table th {
border-color: var(--halo-border-base);
}
.markdown-body hr {
background-color: var(--halo-border-base);
}
/* ===== 页面顶栏 ===== */
.page-header {
background-color: var(--halo-bg-card);
}
.page-header__title-text {
color: var(--halo-text-primary);
}
.page-header__actions .btn,
.page-header__actions button {
background-color: var(--halo-bg-card);
color: var(--halo-text-primary);
border-color: var(--halo-border-base);
}
.page-header__actions .btn:hover,
.page-header__actions button:hover {
background-color: var(--halo-bg-hover);
}
.page-header__actions .btn-content {
color: var(--halo-text-primary);
}
/* ===== 卡片容器(列表页、详情页主容器) ===== */
.card-wrapper {
background-color: var(--halo-bg-card);
border-color: var(--halo-border-base);
color: var(--halo-text-primary);
}
/* ===== 实体字段列表(文章/页面/评论列表项) ===== */
.entity-field-title {
color: var(--halo-text-primary);
}
.entity-field-title-body {
color: var(--halo-text-primary);
}
.entity-field-description {
color: var(--halo-text-secondary);
}
.entity-field-title a,
.entity-field-title-body a {
color: var(--halo-text-primary);
}
.entity-field-title a:hover,
.entity-field-title-body a:hover {
color: var(--halo-text-link);
}
/* ===== 分页 ===== */
.pagination {
background-color: var(--halo-bg-card);
color: var(--halo-text-secondary);
}
.pagination__btn {
background-color: var(--halo-bg-hover);
color: var(--halo-text-secondary);
}
.pagination__btn:hover {
background-color: var(--halo-bg-active);
color: var(--halo-text-primary);
}
.pagination__select {
background-color: var(--halo-bg-input);
color: var(--halo-text-primary);
border-color: var(--halo-border-input);
}
/* ===== 标签/徽章(BEM ===== */
.tag-wrapper,
.tag-default,
.tag-pill,
[class*="tag-"] {
background-color: var(--halo-bg-hover);
color: var(--halo-text-secondary);
}
.tag-content,
.tag-left-icon,
.tag-right-icon {
color: var(--halo-text-secondary);
}
/* ===== 头像 ===== */
.avatar-wrapper,
.avatar-circle {
background-color: var(--halo-bg-hover);
}
/* ===== 按钮(默认态) ===== */
.btn-default,
.btn-secondary,
.btn-sm,
.btn-md,
.btn-lg,
.btn[class*="btn"]:not(.btn-primary):not(.btn-danger) {
background-color: var(--halo-bg-card);
color: var(--halo-text-primary);
border-color: var(--halo-border-base);
}
.btn-default:hover,
.btn-secondary:hover,
.btn-sm:hover,
.btn-md:hover,
.btn-lg:hover {
background-color: var(--halo-bg-hover);
}
.btn-content {
color: var(--halo-text-primary);
}
/* ===== 表单容器(FormKit ===== */
.formkit-inner {
background-color: var(--halo-bg-input);
}
.formkit-inner input,
.formkit-inner textarea,
.formkit-inner select {
background-color: transparent;
color: var(--halo-text-primary);
}
.formkit-inner .border,
.formkit-inner [class*="border-gray"] {
border-color: var(--halo-border-input);
}
/* ===== 下拉菜单 ===== */
.entity-dropdown {
background-color: var(--halo-bg-dropdown);
color: var(--halo-text-primary);
border-color: var(--halo-border-base);
}
.entity-dropdown-trigger {
background-color: var(--halo-bg-hover);
color: var(--halo-text-secondary);
}
.entity-dropdown-trigger:hover {
background-color: var(--halo-bg-active);
color: var(--halo-text-primary);
}
/* ===== 描述项(详情页列表,如用户详情) ===== */
.description-item-wrapper {
background-color: var(--halo-bg-card);
border-color: var(--halo-border-light);
}
.description-item__label {
color: var(--halo-text-secondary);
}
.description-item__content {
color: var(--halo-text-primary);
}
/* ===== 已选指示器 ===== */
.entity-selected-indicator {
color: var(--halo-accent-primary);
}
/* ===== 表格 ===== */
.table,
[class*="table-"] td,
[class*="table-"] th {
border-color: var(--halo-table-border);
color: var(--halo-text-primary);
}
/* ===== 通用文字(UnoCSS hash 类包含 Tailwind 语义) ===== */
.hover\:text-gray-600:hover,
.hover\:text-gray-700:hover,
.hover\:text-gray-500:hover,
.text-gray-600,
.text-gray-700,
.text-gray-500 {
color: var(--halo-text-secondary);
}
.text-gray-900,
.text-gray-800 {
color: var(--halo-text-primary);
}
.text-gray-400,
.text-gray-300 {
color: var(--halo-text-tertiary);
}
/* ===== 通用背景 ===== */
.bg-gray-50,
.bg-gray-100,
.bg-white {
background-color: var(--halo-bg-card);
}
.bg-gray-200 {
background-color: var(--halo-bg-hover);
}
.hover\:bg-gray-50:hover,
.hover\:bg-gray-100:hover,
.hover\:bg-gray-200:hover {
background-color: var(--halo-bg-hover);
}
/* ===== 边框 ===== */
.border,
.border-gray-200,
.border-gray-300 {
border-color: var(--halo-border-base);
}
/* ===== 通用链接 ===== */
a:not([class]) {
color: var(--halo-text-link);
}
}