import { Accordion, AccordionItem } from '@heroui/accordion'; import { Card, CardBody, CardHeader } from '@heroui/card'; import { useRequest } from 'ahooks'; import clsx from 'clsx'; import { useEffect, useRef } from 'react'; import { Controller, useForm, useWatch } from 'react-hook-form'; import toast from 'react-hot-toast'; import { FaUserAstronaut } from 'react-icons/fa'; import { FaPaintbrush } from 'react-icons/fa6'; import { IoIosColorPalette } from 'react-icons/io'; import { MdDarkMode, MdLightMode } from 'react-icons/md'; import themes from '@/const/themes'; import ColorPicker from '@/components/ColorPicker'; import SaveButtons from '@/components/button/save_buttons'; import PageLoading from '@/components/page_loading'; import { colorKeys, generateTheme, loadTheme } from '@/utils/theme'; import WebUIManager from '@/controllers/webui_manager'; export type PreviewThemeCardProps = { theme: ThemeInfo onPreview: () => void }; const values = [ '', '-50', '-100', '-200', '-300', '-400', '-500', '-600', '-700', '-800', '-900', ]; const colors = [ 'primary', 'secondary', 'success', 'danger', 'warning', 'default', ]; function PreviewThemeCard ({ theme, onPreview }: PreviewThemeCardProps) { const style = document.createElement('style'); style.innerHTML = generateTheme(theme.theme, theme.name); const cardRef = useRef(null); useEffect(() => { document.head.appendChild(style); return () => { document.head.removeChild(style); }; }, []); return (
{theme.name}
{theme.author ?? '未知'}
{theme.description}
{colors.map((color) => (
{color[0].toUpperCase()}
{values.map((value) => (
))}
))}
); } const ThemeConfigCard = () => { const { data, loading, error, refreshAsync } = useRequest( WebUIManager.getThemeConfig ); const { control, handleSubmit: handleOnebotSubmit, formState: { isSubmitting }, setValue: setOnebotValue, } = useForm<{ theme: ThemeConfig }>({ defaultValues: { theme: { dark: {}, light: {}, }, }, }); // 使用 useRef 存储 style 标签引用 const styleTagRef = useRef(null); // 在组件挂载时创建 style 标签,并在卸载时清理 useEffect(() => { const styleTag = document.createElement('style'); document.head.appendChild(styleTag); styleTagRef.current = styleTag; return () => { if (styleTagRef.current) { document.head.removeChild(styleTagRef.current); } }; }, []); const theme = useWatch({ control, name: 'theme' }); const reset = () => { if (data) setOnebotValue('theme', data); }; const onSubmit = handleOnebotSubmit(async (data) => { try { await WebUIManager.setThemeConfig(data.theme); toast.success('保存成功'); loadTheme(); } catch (_error) { const msg = (error as Error).message; toast.error(`保存失败: ${msg}`); } }); const onRefresh = async () => { try { await refreshAsync(); toast.success('刷新成功'); } catch (_error) { const msg = (error as Error).message; toast.error(`刷新失败: ${msg}`); } }; useEffect(() => { reset(); }, [data]); useEffect(() => { if (theme && styleTagRef.current) { const css = generateTheme(theme); styleTagRef.current.innerHTML = css; } }, [theme]); if (loading) return ; if (error) { return (
{error.message}
); } return ( <> 主题配置 - NapCat WebUI
实时预览,记得保存!
} >
{themes.map((theme) => ( { setOnebotValue('theme', theme.theme); }} /> ))}
} >
{(['dark', 'light'] as const).map((mode) => (

{mode === 'dark' ? ( ) : ( )} {mode === 'dark' ? '夜间模式主题' : '白昼模式主题'}

{colorKeys.map((key) => (
{ const hslArray = value?.split(' ') ?? [0, 0, 0]; const color = `hsl(${hslArray[0]}, ${hslArray[1]}, ${hslArray[2]})`; return ( { onChange( `${result.hsl.h} ${result.hsl.s * 100}% ${result.hsl.l * 100}%` ); }} /> ); }} />
))}
))}
); }; export default ThemeConfigCard;