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>
This commit is contained in:
@@ -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
@@ -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,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
============================================================ */
|
||||
|
||||
@import './variables.css';
|
||||
@import './overrides/halo-core.css';
|
||||
@import './overrides/utilities.css';
|
||||
@import './overrides/layout.css';
|
||||
@import './overrides/components.css';
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user