mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-04 06:31:13 +00:00
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.
This commit is contained in:
parent
1239f622d2
commit
90e3936204
@ -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 += '}';
|
||||
|
||||
|
||||
@ -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, '检查字体文件失败');
|
||||
}
|
||||
};
|
||||
|
||||
@ -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 };
|
||||
|
||||
@ -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}`);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -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<ColorPickerProps> = ({ color, onChange }) => {
|
||||
@ -22,7 +22,8 @@ const ColorPicker: React.FC<ColorPickerProps> = ({ color, onChange }) => {
|
||||
style={{ background: color }}
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
{/* 移除 PopoverContent 默认的事件阻止,允许鼠标拖动到外部 */}
|
||||
<PopoverContent className='pointer-events-auto'>
|
||||
<SketchPicker
|
||||
color={color}
|
||||
onChange={handleChange}
|
||||
|
||||
@ -218,4 +218,11 @@ export default class FileManager {
|
||||
);
|
||||
return data.data;
|
||||
}
|
||||
|
||||
public static async checkWebUIFontExists () {
|
||||
const { data } = await serverRequest.get<ServerResponse<boolean>>(
|
||||
'/File/font/exists/webui'
|
||||
);
|
||||
return data.data;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<HTMLStyleElement | null>(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 = () => {
|
||||
</div>
|
||||
|
||||
<div className='p-4'>
|
||||
<Accordion variant='splitted' defaultExpandedKeys={['font', 'select']}>
|
||||
<Accordion
|
||||
variant='splitted'
|
||||
defaultExpandedKeys={['font']}
|
||||
selectionMode='single'
|
||||
>
|
||||
<AccordionItem
|
||||
key='font'
|
||||
aria-label='Font Settings'
|
||||
@ -381,38 +390,55 @@ const ThemeConfigCard = () => {
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
<div className='p-3 rounded-lg bg-default-100 dark:bg-default-50/30'>
|
||||
<div className='text-sm text-default-500 mb-2'>
|
||||
上传自定义字体(仅在选择"自定义字体"时生效)
|
||||
{theme.fontMode === 'custom' && (
|
||||
<div className='p-3 rounded-lg bg-default-100 dark:bg-default-50/30'>
|
||||
<div className='text-sm text-default-500 mb-2'>
|
||||
上传自定义字体(仅在选择"自定义字体"时生效)
|
||||
</div>
|
||||
{customFontExists && (
|
||||
<div className='mb-2 flex items-center gap-2 text-sm text-primary'>
|
||||
<FaCheck /> 已上传自定义字体
|
||||
</div>
|
||||
)}
|
||||
<FileInput
|
||||
label='上传字体文件'
|
||||
placeholder='选择字体文件 (.woff/.woff2/.ttf/.otf)'
|
||||
accept='.ttf,.otf,.woff,.woff2'
|
||||
onChange={async (file) => {
|
||||
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);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<FileInput
|
||||
label='上传字体文件'
|
||||
placeholder='选择字体文件 (.woff/.woff2/.ttf/.otf)'
|
||||
accept='.ttf,.otf,.woff,.woff2'
|
||||
onChange={async (file) => {
|
||||
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);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</AccordionItem>
|
||||
|
||||
|
||||
@ -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');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user