This commit is contained in:
bietiaop
2025-02-08 23:38:30 +08:00
parent 970a49e2a5
commit a6a11a7026
7 changed files with 171 additions and 35 deletions

View File

@@ -13,6 +13,7 @@
content="viewport-fit=cover, width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" content="viewport-fit=cover, width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
name="viewport" /> name="viewport" />
<link href="/favicon.ico" rel="icon" /> <link href="/favicon.ico" rel="icon" />
<link href="/files/theme.css" rel="stylesheet" />
</head> </head>
<body> <body>

View File

@@ -6,32 +6,28 @@ import { ColorResult, SketchPicker } from 'react-color'
interface ColorPickerProps { interface ColorPickerProps {
color: string color: string
onChange: (color: string) => void onChange: (color: ColorResult) => void
} }
const ColorPicker: React.FC<ColorPickerProps> = ({ color, onChange }) => { const ColorPicker: React.FC<ColorPickerProps> = ({ color, onChange }) => {
const handleChange = (colorResult: ColorResult) => { const handleChange = (colorResult: ColorResult) => {
const hsl = colorResult.hsl onChange(colorResult)
const color = `${hsl.h} ${hsl.s}% ${hsl.l}%`
onChange(color)
} }
return ( return (
<Popover> <Popover triggerScaleOnOpen={false}>
<PopoverTrigger> <PopoverTrigger>
<div <div
style={{ className="w-36 h-8 rounded-md cursor-pointer border border-content4"
background: color, style={{ background: color }}
width: 36,
height: 14,
borderRadius: 2,
cursor: 'pointer',
border: '1px solid #ddd'
}}
/> />
</PopoverTrigger> </PopoverTrigger>
<PopoverContent> <PopoverContent>
<SketchPicker color={color} onChange={handleChange} /> <SketchPicker
color={color}
onChange={handleChange}
className="!bg-transparent !shadow-none"
/>
</PopoverContent> </PopoverContent>
</Popover> </Popover>
) )

View File

@@ -1,5 +1,6 @@
import { Card, CardBody } from '@heroui/card' import { Card, CardBody } from '@heroui/card'
import { Tab, Tabs } from '@heroui/tabs' import { Tab, Tabs } from '@heroui/tabs'
import clsx from 'clsx'
import { useMediaQuery } from 'react-responsive' import { useMediaQuery } from 'react-responsive'
import { useNavigate, useSearchParams } from 'react-router-dom' import { useNavigate, useSearchParams } from 'react-router-dom'
@@ -11,13 +12,25 @@ import WebUIConfigCard from './webui'
export interface ConfigPageProps { export interface ConfigPageProps {
children?: React.ReactNode children?: React.ReactNode
size?: 'sm' | 'md' | 'lg'
} }
const ConfingPageItem: React.FC<ConfigPageProps> = ({ children }) => { const ConfingPageItem: React.FC<ConfigPageProps> = ({
children,
size = 'md'
}) => {
return ( return (
<Card className="bg-opacity-50 backdrop-blur-sm"> <Card className="bg-opacity-50 backdrop-blur-sm">
<CardBody className="items-center py-5"> <CardBody className="items-center py-5">
<div className="w-96 max-w-full flex flex-col gap-2">{children}</div> <div
className={clsx('max-w-full flex flex-col gap-2', {
'w-72': size === 'sm',
'w-96': size === 'md',
'w-[32rem]': size === 'lg'
})}
>
{children}
</div>
</CardBody> </CardBody>
</Card> </Card>
) )
@@ -71,7 +84,7 @@ export default function ConfigPage() {
</Tab> </Tab>
<Tab title="主题配置" key="theme"> <Tab title="主题配置" key="theme">
<ConfingPageItem> <ConfingPageItem size="lg">
<ThemeConfigCard /> <ThemeConfigCard />
</ConfingPageItem> </ConfingPageItem>
</Tab> </Tab>

View File

@@ -1,6 +1,6 @@
import { Input } from '@heroui/input' import { Input } from '@heroui/input'
import { useRequest } from 'ahooks' import { useRequest } from 'ahooks'
import { useEffect, useState } from 'react' import { useEffect } from 'react'
import { Controller, useForm } from 'react-hook-form' import { Controller, useForm } from 'react-hook-form'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
@@ -16,7 +16,6 @@ const LoginConfigCard = () => {
error: quickLoginError, error: quickLoginError,
refreshAsync: refreshQuickLogin refreshAsync: refreshQuickLogin
} = useRequest(QQManager.getQuickLoginQQ) } = useRequest(QQManager.getQuickLoginQQ)
const [loading, setLoading] = useState(false)
const { const {
control, control,
handleSubmit: handleOnebotSubmit, handleSubmit: handleOnebotSubmit,
@@ -36,27 +35,21 @@ const LoginConfigCard = () => {
const onSubmit = handleOnebotSubmit((data) => { const onSubmit = handleOnebotSubmit((data) => {
try { try {
setLoading(true)
QQManager.setQuickLoginQQ(data.quickLoginQQ) QQManager.setQuickLoginQQ(data.quickLoginQQ)
toast.success('保存成功') toast.success('保存成功')
} catch (error) { } catch (error) {
const msg = (error as Error).message const msg = (error as Error).message
toast.error(`保存失败: ${msg}`) toast.error(`保存失败: ${msg}`)
} finally {
setLoading(false)
} }
}) })
const onRefresh = async () => { const onRefresh = async () => {
try { try {
setLoading(true)
await refreshQuickLogin() await refreshQuickLogin()
toast.success('刷新成功') toast.success('刷新成功')
} catch (error) { } catch (error) {
const msg = (error as Error).message const msg = (error as Error).message
toast.error(`刷新失败: ${msg}`) toast.error(`刷新失败: ${msg}`)
} finally {
setLoading(false)
} }
} }
@@ -64,7 +57,7 @@ const LoginConfigCard = () => {
reset() reset()
}, [quickLoginData]) }, [quickLoginData])
if (loading) return <PageLoading loading={true} /> if (quickLoginLoading) return <PageLoading loading={true} />
return ( return (
<> <>

View File

@@ -53,6 +53,10 @@ const ThemeConfigCard = () => {
} }
} }
useEffect(() => {
reset()
}, [data])
if (loading) return <PageLoading loading={true} /> if (loading) return <PageLoading loading={true} />
if (error) if (error)
@@ -60,10 +64,115 @@ const ThemeConfigCard = () => {
<div className="py-24 text-danger-500 text-center">{error.message}</div> <div className="py-24 text-danger-500 text-center">{error.message}</div>
) )
// 将颜色 key 补全为 ThemeConfigItem 中定义的所有颜色相关属性
const colorKeys = [ const colorKeys = [
'--heroui-background', '--heroui-background',
'--heroui-foreground-50',
'--heroui-foreground-100',
'--heroui-foreground-200',
'--heroui-foreground-300',
'--heroui-foreground-400',
'--heroui-foreground-500',
'--heroui-foreground-600',
'--heroui-foreground-700',
'--heroui-foreground-800',
'--heroui-foreground-900',
'--heroui-foreground',
'--heroui-content1',
'--heroui-content1-foreground',
'--heroui-content2',
'--heroui-content2-foreground',
'--heroui-content3',
'--heroui-content3-foreground',
'--heroui-content4',
'--heroui-content4-foreground',
'--heroui-default-50',
'--heroui-default-100',
'--heroui-default-200',
'--heroui-default-300',
'--heroui-default-400',
'--heroui-default-500',
'--heroui-default-600',
'--heroui-default-700',
'--heroui-default-800',
'--heroui-default-900',
'--heroui-default-foreground',
'--heroui-default',
'--heroui-danger-50',
'--heroui-danger-100',
'--heroui-danger-200',
'--heroui-danger-300',
'--heroui-danger-400',
'--heroui-danger-500',
'--heroui-danger-600',
'--heroui-danger-700',
'--heroui-danger-800',
'--heroui-danger-900',
'--heroui-danger-foreground',
'--heroui-danger',
'--heroui-primary-50',
'--heroui-primary-100',
'--heroui-primary-200',
'--heroui-primary-300',
'--heroui-primary-400',
'--heroui-primary-500',
'--heroui-primary-600',
'--heroui-primary-700',
'--heroui-primary-800',
'--heroui-primary-900',
'--heroui-primary-foreground',
'--heroui-primary', '--heroui-primary',
'--heroui-danger'
'--heroui-secondary-50',
'--heroui-secondary-100',
'--heroui-secondary-200',
'--heroui-secondary-300',
'--heroui-secondary-400',
'--heroui-secondary-500',
'--heroui-secondary-600',
'--heroui-secondary-700',
'--heroui-secondary-800',
'--heroui-secondary-900',
'--heroui-secondary-foreground',
'--heroui-secondary',
'--heroui-success-50',
'--heroui-success-100',
'--heroui-success-200',
'--heroui-success-300',
'--heroui-success-400',
'--heroui-success-500',
'--heroui-success-600',
'--heroui-success-700',
'--heroui-success-800',
'--heroui-success-900',
'--heroui-success-foreground',
'--heroui-success',
'--heroui-warning-50',
'--heroui-warning-100',
'--heroui-warning-200',
'--heroui-warning-300',
'--heroui-warning-400',
'--heroui-warning-500',
'--heroui-warning-600',
'--heroui-warning-700',
'--heroui-warning-800',
'--heroui-warning-900',
'--heroui-warning-foreground',
'--heroui-warning',
'--heroui-focus',
'--heroui-overlay',
'--heroui-divider',
'--heroui-code-background',
'--heroui-strong',
'--heroui-code-mdx'
] as const ] as const
return ( return (
@@ -74,19 +183,24 @@ const ThemeConfigCard = () => {
<div key={mode}> <div key={mode}>
<h3>{mode === 'dark' ? '暗色主题' : '亮色主题'}</h3> <h3>{mode === 'dark' ? '暗色主题' : '亮色主题'}</h3>
{colorKeys.map((key) => ( {colorKeys.map((key) => (
<div <div key={key} className="grid grid-cols-2 items-center mb-2 gap-2">
key={key} <label className="text-right">{key}</label>
style={{ display: 'flex', alignItems: 'center', marginBottom: 8 }}
>
<label style={{ width: 150 }}>{key}</label>
<Controller <Controller
control={control} control={control}
name={`theme.${mode}.${key}`} name={`theme.${mode}.${key}`}
render={({ field: { value, onChange } }) => { render={({ field: { value, onChange } }) => {
console.log(value)
const hslArray = value?.split(' ') ?? [0, 0, 0] const hslArray = value?.split(' ') ?? [0, 0, 0]
const color = `hsl(${hslArray[0]}, ${hslArray[1]}, ${hslArray[2]})` const color = `hsl(${hslArray[0]}, ${hslArray[1]}, ${hslArray[2]})`
return <ColorPicker color={color} onChange={onChange} /> return (
<ColorPicker
color={color}
onChange={(result) => {
onChange(
`${result.hsl.h} ${result.hsl.s * 100}% ${result.hsl.l * 100}%`
)
}}
/>
)
}} }}
/> />
</div> </div>

View File

@@ -34,7 +34,8 @@ export default defineConfig(({ mode }) => {
ws: true, ws: true,
changeOrigin: true changeOrigin: true
}, },
'/api': backendDebugUrl '/api': backendDebugUrl,
'/files': backendDebugUrl
} }
}, },
build: { build: {

View File

@@ -81,6 +81,24 @@ export async function InitWebUi(logger: LogWrapper, pathWrapper: NapCatPathWrapp
} }
}); });
// 如果是自定义色彩构建一个css文件
app.use('/files/theme.css', async (_req, res) => {
const colors = await WebUiConfig.GetTheme();
// 生成css分为亮色和暗色靠class和[data-theme="light"]
let css = '.dark, [data-theme="dark"] {';
for (const key in colors.dark) {
css += `--${key}: ${colors.dark[key]};`;
}
css += '}';
css += '.light, [data-theme="light"] {';
for (const key in colors.light) {
css += `--${key}: ${colors.light[key]};`;
}
css += '}';
res.send(css);
});
// ------------中间件结束------------ // ------------中间件结束------------
// ------------挂载路由------------ // ------------挂载路由------------