From c6ec2126e062ed3d289c50c5368e057aa299ebc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=8B=E7=93=9C=E4=B8=80=E5=8D=81=E9=9B=AA?= Date: Sun, 4 Jan 2026 18:48:16 +0800 Subject: [PATCH] Refactor theme font handling and preview logic Moved font configuration to be managed via theme.css, eliminating the need for separate font initialization and caching. Updated backend to generate @font-face rules and font variables in theme.css. Frontend now uses a dedicated style tag for real-time font preview in the theme config page, and removes legacy font cache logic for improved consistency. --- packages/napcat-webui-backend/index.ts | 52 +++++++++++++++-- .../src/pages/dashboard/config/theme.tsx | 12 ++-- .../napcat-webui-frontend/src/utils/theme.ts | 57 +++++++------------ 3 files changed, 72 insertions(+), 49 deletions(-) diff --git a/packages/napcat-webui-backend/index.ts b/packages/napcat-webui-backend/index.ts index 4da8e706..eb1e71db 100644 --- a/packages/napcat-webui-backend/index.ts +++ b/packages/napcat-webui-backend/index.ts @@ -182,16 +182,56 @@ export async function InitWebUi (logger: ILogWrapper, pathWrapper: NapCatPathWra // 如果是自定义色彩,构建一个css文件 app.use('/files/theme.css', async (_req, res) => { - const colors = await WebUiConfig.GetTheme(); + const theme = await WebUiConfig.GetTheme(); + const fontMode = theme.fontMode || 'system'; - let css = ':root, .light, [data-theme="light"] {'; - for (const key in colors.light) { - css += `${key}: ${colors.light[key]};`; + let css = ''; + + // 生成字体 @font-face + if (fontMode === 'aacute') { + css += ` +@font-face { + font-family: 'Aa偷吃可爱长大的'; + src: url('/webui/fonts/AaCute.woff') format('woff'); + font-display: swap; +} +`; + } else if (fontMode === 'custom') { + css += ` +@font-face { + font-family: 'CustomFont'; + src: url('/webui/fonts/CustomFont.woff') format('woff'); + font-display: swap; +} +`; + } + + // 生成颜色主题和字体变量 + css += ':root, .light, [data-theme="light"] {'; + for (const key in theme.light) { + css += `${key}: ${theme.light[key]};`; + } + // 添加字体变量 + if (fontMode === 'aacute') { + css += "--font-family-base: 'Aa偷吃可爱长大的', var(--font-family-fallbacks) !important;"; + } else if (fontMode === 'custom') { + css += "--font-family-base: 'CustomFont', var(--font-family-fallbacks) !important;"; + } else { + css += '--font-family-base: var(--font-family-fallbacks) !important;'; } css += '}'; + css += '.dark, [data-theme="dark"] {'; - for (const key in colors.dark) { - css += `${key}: ${colors.dark[key]};`; + for (const key in theme.dark) { + css += `${key}: ${theme.dark[key]};`; + } + // 添加字体变量 + if (fontMode === 'aacute') { + css += "--font-family-base: 'Aa偷吃可爱长大的', var(--font-family-fallbacks) !important;"; + } else if (fontMode === 'custom') { + css += "--font-family-base: 'CustomFont', var(--font-family-fallbacks) !important;"; + } else { + css += '--font-family-base: var(--font-family-fallbacks) !important;'; } css += '}'; diff --git a/packages/napcat-webui-frontend/src/pages/dashboard/config/theme.tsx b/packages/napcat-webui-frontend/src/pages/dashboard/config/theme.tsx index 1f6bb1eb..42d40208 100644 --- a/packages/napcat-webui-frontend/src/pages/dashboard/config/theme.tsx +++ b/packages/napcat-webui-frontend/src/pages/dashboard/config/theme.tsx @@ -268,15 +268,17 @@ const ThemeConfigCard = () => { // 找到已保存的主题名称 const savedThemeName = useMemo(() => { - if (!originalDataRef.current) return null; - return themes.find(t => isThemeColorsEqual(t.theme, originalDataRef.current!))?.name || '自定义'; - }, [dataLoaded, hasUnsavedChanges]); + const savedData = originalDataRef.current || data; + if (!savedData) return null; + return themes.find(t => isThemeColorsEqual(t.theme, savedData))?.name || '自定义'; + }, [data, dataLoaded, hasUnsavedChanges]); // 已保存的字体模式显示名称 const savedFontModeDisplayName = useMemo(() => { - const mode = originalDataRef.current?.fontMode || 'aacute'; + const savedData = originalDataRef.current || data; + const mode = savedData?.fontMode || 'aacute'; return fontModeNames[mode] || mode; - }, [dataLoaded, hasUnsavedChanges]); + }, [data, dataLoaded, hasUnsavedChanges]); if (loading) return ; diff --git a/packages/napcat-webui-frontend/src/utils/theme.ts b/packages/napcat-webui-frontend/src/utils/theme.ts index 8f02b31d..cb63b098 100644 --- a/packages/napcat-webui-frontend/src/utils/theme.ts +++ b/packages/napcat-webui-frontend/src/utils/theme.ts @@ -3,24 +3,27 @@ import { request } from './request'; const style = document.createElement('style'); document.head.appendChild(style); -// 字体样式标签 -const fontStyle = document.createElement('style'); -fontStyle.id = 'dynamic-font-style'; -document.head.appendChild(fontStyle); +// 用于主题配置页面实时预览字体的临时样式标签 +const fontPreviewStyle = document.createElement('style'); +fontPreviewStyle.id = 'font-preview-style'; +document.head.appendChild(fontPreviewStyle); export function loadTheme () { request('/files/theme.css?_t=' + Date.now()) .then((res) => res.data) .then((css) => { style.innerHTML = css; + // 清除预览样式,使用 theme.css 中的正式配置 + fontPreviewStyle.innerHTML = ''; + document.documentElement.style.removeProperty('--font-family-base'); }) .catch(() => { console.error('Failed to load theme.css'); }); } -// 动态加载字体 CSS -const loadFontCSS = (mode: string) => { +// 动态加载字体 CSS(用于预览) +const loadFontCSSForPreview = (mode: string) => { let css = ''; if (mode === 'aacute') { @@ -39,7 +42,7 @@ const loadFontCSS = (mode: string) => { }`; } - fontStyle.innerHTML = css; + fontPreviewStyle.innerHTML = css; }; export const colorKeys = [ @@ -168,11 +171,12 @@ export const generateTheme = (theme: ThemeConfig, validField?: string) => { return css; }; +// 用于主题配置页面实时预览字体 export const applyFont = (mode: string) => { const root = document.documentElement; - // 先加载字体 CSS - loadFontCSS(mode); + // 加载字体 CSS 用于预览 + loadFontCSSForPreview(mode); if (mode === 'aacute') { root.style.setProperty('--font-family-base', "'Aa偷吃可爱长大的', var(--font-family-fallbacks)", 'important'); @@ -184,36 +188,13 @@ export const applyFont = (mode: string) => { } }; -const FONT_MODE_CACHE_KEY = 'webui-font-mode-cache'; - +// 字体配置已通过 theme.css 加载,此函数仅用于兼容性保留 export const initFont = () => { - // 先从缓存读取,立即应用 - const cached = localStorage.getItem(FONT_MODE_CACHE_KEY); - if (cached) { - applyFont(cached); - } else { - // 默认使用系统字体 - applyFont('system'); - } - - // 后台拉取最新配置并更新缓存 - request('/api/base/Theme') - .then((res) => { - const data = res.data as { data: ThemeConfig; }; - const fontMode = data?.data?.fontMode || 'system'; - // 更新缓存 - localStorage.setItem(FONT_MODE_CACHE_KEY, fontMode); - // 如果与当前不同,则应用新字体 - if (fontMode !== cached) { - applyFont(fontMode); - } - }) - .catch((e) => { - console.error('Failed to fetch font config', e); - }); + // 字体现在由 theme.css 统一管理,无需单独初始化 }; -// 保存时更新缓存 -export const updateFontCache = (fontMode: string) => { - localStorage.setItem(FONT_MODE_CACHE_KEY, fontMode); +// 保存主题后调用 loadTheme 会使用 theme.css 中的正式配置 +// 此函数保留用于兼容性 +export const updateFontCache = (_fontMode: string) => { + // 不再需要缓存,字体配置已在 theme.css 中 };