From 90e3936204f9e88e4762757f5894aadb3b4d7cc0 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, 1 Feb 2026 14:00:27 +0800 Subject: [PATCH] Support custom WebUI fonts and UI additions Backend: add CheckWebUIFontExist API and route; set --font-family-mono CSS variable in InitWebUi for aacute/custom/default. Improve webui font uploader: force saved filename to CustomFont, robustly clean old webui/CustomFont files, and log failures. Frontend: add FileManager.checkWebUIFontExists; update theme settings to show upload UI only when 'custom' is selected, display uploaded status, attempt delete-before-upload, reload after actions, and adjust Accordion props. ColorPicker: enable pointer events on PopoverContent to allow dragging. applyFont now sets --font-family-mono for all modes. --- packages/napcat-webui-backend/index.ts | 6 ++ packages/napcat-webui-backend/src/api/File.ts | 10 +++ .../napcat-webui-backend/src/router/File.ts | 2 + .../src/uploader/webui_font.ts | 35 +++++--- .../src/components/ColorPicker.tsx | 7 +- .../src/controllers/file_manager.ts | 7 ++ .../src/pages/dashboard/config/theme.tsx | 90 ++++++++++++------- .../napcat-webui-frontend/src/utils/theme.ts | 3 + 8 files changed, 115 insertions(+), 45 deletions(-) diff --git a/packages/napcat-webui-backend/index.ts b/packages/napcat-webui-backend/index.ts index 57a01b8f..ea35561a 100644 --- a/packages/napcat-webui-backend/index.ts +++ b/packages/napcat-webui-backend/index.ts @@ -231,10 +231,13 @@ export async function InitWebUi (logger: ILogWrapper, pathWrapper: NapCatPathWra // 添加字体变量 if (fontMode === 'aacute') { css += "--font-family-base: 'Aa偷吃可爱长大的', var(--font-family-fallbacks) !important;"; + css += "--font-family-mono: 'Aa偷吃可爱长大的', var(--font-family-fallbacks) !important;"; } else if (fontMode === 'custom') { css += "--font-family-base: 'CustomFont', var(--font-family-fallbacks) !important;"; + css += "--font-family-mono: 'CustomFont', var(--font-family-fallbacks) !important;"; } else { css += '--font-family-base: var(--font-family-fallbacks) !important;'; + css += '--font-family-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important;'; } css += '}'; @@ -245,10 +248,13 @@ export async function InitWebUi (logger: ILogWrapper, pathWrapper: NapCatPathWra // 添加字体变量 if (fontMode === 'aacute') { css += "--font-family-base: 'Aa偷吃可爱长大的', var(--font-family-fallbacks) !important;"; + css += "--font-family-mono: 'Aa偷吃可爱长大的', var(--font-family-fallbacks) !important;"; } else if (fontMode === 'custom') { css += "--font-family-base: 'CustomFont', var(--font-family-fallbacks) !important;"; + css += "--font-family-mono: 'CustomFont', var(--font-family-fallbacks) !important;"; } else { css += '--font-family-base: var(--font-family-fallbacks) !important;'; + css += '--font-family-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important;'; } css += '}'; diff --git a/packages/napcat-webui-backend/src/api/File.ts b/packages/napcat-webui-backend/src/api/File.ts index 42af94ee..d820e0d0 100644 --- a/packages/napcat-webui-backend/src/api/File.ts +++ b/packages/napcat-webui-backend/src/api/File.ts @@ -653,3 +653,13 @@ export const DeleteWebUIFontHandler: RequestHandler = async (_req, res) => { return sendError(res, '删除字体文件失败'); } }; + +// 检查WebUI字体文件是否存在 +export const CheckWebUIFontExistHandler: RequestHandler = async (_req, res) => { + try { + const exists = await WebUiConfig.CheckWebUIFontExist(); + return sendSuccess(res, exists); + } catch (_error) { + return sendError(res, '检查字体文件失败'); + } +}; diff --git a/packages/napcat-webui-backend/src/router/File.ts b/packages/napcat-webui-backend/src/router/File.ts index 572f2fa4..0de664f0 100644 --- a/packages/napcat-webui-backend/src/router/File.ts +++ b/packages/napcat-webui-backend/src/router/File.ts @@ -16,6 +16,7 @@ import { UploadHandler, UploadWebUIFontHandler, DeleteWebUIFontHandler, // 添加上传处理器 + CheckWebUIFontExistHandler, // Add this } from '../api/File'; const router: Router = Router(); @@ -46,4 +47,5 @@ router.post('/upload', UploadHandler); router.post('/font/upload/webui', UploadWebUIFontHandler); router.post('/font/delete/webui', DeleteWebUIFontHandler); +router.get('/font/exists/webui', CheckWebUIFontExistHandler); // Add this export { router as FileRouter }; diff --git a/packages/napcat-webui-backend/src/uploader/webui_font.ts b/packages/napcat-webui-backend/src/uploader/webui_font.ts index 03e9abe8..85c2ee66 100644 --- a/packages/napcat-webui-backend/src/uploader/webui_font.ts +++ b/packages/napcat-webui-backend/src/uploader/webui_font.ts @@ -9,15 +9,30 @@ const SUPPORTED_FONT_EXTENSIONS = ['.woff', '.woff2', '.ttf', '.otf']; // 清理旧的字体文件 const cleanOldFontFiles = (fontsPath: string) => { - for (const ext of SUPPORTED_FONT_EXTENSIONS) { - const fontPath = path.join(fontsPath, `webui${ext}`); - try { - if (fs.existsSync(fontPath)) { - fs.unlinkSync(fontPath); - } - } catch { - // 忽略删除失败 + try { + // 确保字体目录存在 + if (!fs.existsSync(fontsPath)) { + return; } + + // 遍历目录下所有文件 + const files = fs.readdirSync(fontsPath); + + for (const file of files) { + // 检查文件名是否以 webui 或 CustomFont 开头,且是支持的字体扩展名 + const ext = path.extname(file).toLowerCase(); + const name = path.basename(file, ext); + + if (SUPPORTED_FONT_EXTENSIONS.includes(ext) && (name === 'webui' || name === 'CustomFont')) { + try { + fs.unlinkSync(path.join(fontsPath, file)); + } catch (e) { + console.error(`Failed to delete old font file ${file}:`, e); + } + } + } + } catch (err) { + console.error('Failed to clean old font files:', err); } }; @@ -36,9 +51,9 @@ export const webUIFontStorage = multer.diskStorage({ } }, filename: (_, file, cb) => { - // 保留原始扩展名,统一文件名为 webui + // 强制文件名为 CustomFont,保留原始扩展名 const ext = path.extname(file.originalname).toLowerCase(); - cb(null, `webui${ext}`); + cb(null, `CustomFont${ext}`); }, }); diff --git a/packages/napcat-webui-frontend/src/components/ColorPicker.tsx b/packages/napcat-webui-frontend/src/components/ColorPicker.tsx index 481157bc..40fbb1f0 100644 --- a/packages/napcat-webui-frontend/src/components/ColorPicker.tsx +++ b/packages/napcat-webui-frontend/src/components/ColorPicker.tsx @@ -5,8 +5,8 @@ import { ColorResult, SketchPicker } from 'react-color'; // 假定 heroui 提供的 Popover组件 interface ColorPickerProps { - color: string - onChange: (color: ColorResult) => void + color: string; + onChange: (color: ColorResult) => void; } const ColorPicker: React.FC = ({ color, onChange }) => { @@ -22,7 +22,8 @@ const ColorPicker: React.FC = ({ color, onChange }) => { style={{ background: color }} /> - + {/* 移除 PopoverContent 默认的事件阻止,允许鼠标拖动到外部 */} + >( + '/File/font/exists/webui' + ); + return data.data; + } } 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 42d40208..e15e182a 100644 --- a/packages/napcat-webui-frontend/src/pages/dashboard/config/theme.tsx +++ b/packages/napcat-webui-frontend/src/pages/dashboard/config/theme.tsx @@ -162,6 +162,7 @@ const ThemeConfigCard = () => { const [dataLoaded, setDataLoaded] = useState(false); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); + const [customFontExists, setCustomFontExists] = useState(false); // 使用 useRef 存储 style 标签引用和状态 const styleTagRef = useRef(null); @@ -213,6 +214,10 @@ const ThemeConfigCard = () => { } setDataLoaded(true); setHasUnsavedChanges(false); + // 检查自定义字体是否存在 + FileManager.checkWebUIFontExists().then(exists => { + setCustomFontExists(exists); + }).catch(err => console.error('Failed to check custom font:', err)); }, [data, setOnebotValue]); // 实时应用字体预设(预览) @@ -354,7 +359,11 @@ const ThemeConfigCard = () => {
- + { )} /> -
-
- 上传自定义字体(仅在选择"自定义字体"时生效) + {theme.fontMode === 'custom' && ( +
+
+ 上传自定义字体(仅在选择"自定义字体"时生效) +
+ {customFontExists && ( +
+ 已上传自定义字体 +
+ )} + { + try { + // 如果已存在自定义字体,先尝试删除 + if (customFontExists) { + try { + await FileManager.deleteWebUIFont(); + } catch (e) { + console.warn('Failed to delete existing font before upload:', e); + // 继续尝试上传,后端可能会覆盖或报错 + } + } + + await FileManager.uploadWebUIFont(file); + toast.success('上传成功,即将刷新页面'); + setTimeout(() => { + window.location.reload(); + }, 1000); + } catch (error) { + toast.error('上传失败: ' + (error as Error).message); + } + }} + onDelete={async () => { + try { + await FileManager.deleteWebUIFont(); + toast.success('删除成功,即将刷新页面'); + setTimeout(() => { + window.location.reload(); + }, 1000); + } catch (error) { + toast.error('删除失败: ' + (error as Error).message); + } + }} + />
- { - try { - await FileManager.uploadWebUIFont(file); - toast.success('上传成功,即将刷新页面'); - setTimeout(() => { - window.location.reload(); - }, 1000); - } catch (error) { - toast.error('上传失败: ' + (error as Error).message); - } - }} - onDelete={async () => { - try { - await FileManager.deleteWebUIFont(); - toast.success('删除成功,即将刷新页面'); - setTimeout(() => { - window.location.reload(); - }, 1000); - } catch (error) { - toast.error('删除失败: ' + (error as Error).message); - } - }} - /> -
+ )}
diff --git a/packages/napcat-webui-frontend/src/utils/theme.ts b/packages/napcat-webui-frontend/src/utils/theme.ts index cb63b098..ef62c12e 100644 --- a/packages/napcat-webui-frontend/src/utils/theme.ts +++ b/packages/napcat-webui-frontend/src/utils/theme.ts @@ -180,11 +180,14 @@ export const applyFont = (mode: string) => { if (mode === 'aacute') { root.style.setProperty('--font-family-base', "'Aa偷吃可爱长大的', var(--font-family-fallbacks)", 'important'); + root.style.setProperty('--font-family-mono', "'Aa偷吃可爱长大的', var(--font-family-fallbacks)", 'important'); } else if (mode === 'custom') { root.style.setProperty('--font-family-base', "'CustomFont', var(--font-family-fallbacks)", 'important'); + root.style.setProperty('--font-family-mono', "'CustomFont', var(--font-family-fallbacks)", 'important'); } else { // system or default - restore default root.style.setProperty('--font-family-base', 'var(--font-family-fallbacks)', 'important'); + root.style.setProperty('--font-family-mono', 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace', 'important'); } };