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"
name="viewport" />
<link href="/favicon.ico" rel="icon" />
<link href="/files/theme.css" rel="stylesheet" />
</head>
<body>

View File

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

View File

@@ -1,5 +1,6 @@
import { Card, CardBody } from '@heroui/card'
import { Tab, Tabs } from '@heroui/tabs'
import clsx from 'clsx'
import { useMediaQuery } from 'react-responsive'
import { useNavigate, useSearchParams } from 'react-router-dom'
@@ -11,13 +12,25 @@ import WebUIConfigCard from './webui'
export interface ConfigPageProps {
children?: React.ReactNode
size?: 'sm' | 'md' | 'lg'
}
const ConfingPageItem: React.FC<ConfigPageProps> = ({ children }) => {
const ConfingPageItem: React.FC<ConfigPageProps> = ({
children,
size = 'md'
}) => {
return (
<Card className="bg-opacity-50 backdrop-blur-sm">
<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>
</Card>
)
@@ -71,7 +84,7 @@ export default function ConfigPage() {
</Tab>
<Tab title="主题配置" key="theme">
<ConfingPageItem>
<ConfingPageItem size="lg">
<ThemeConfigCard />
</ConfingPageItem>
</Tab>

View File

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

View File

@@ -53,6 +53,10 @@ const ThemeConfigCard = () => {
}
}
useEffect(() => {
reset()
}, [data])
if (loading) return <PageLoading loading={true} />
if (error)
@@ -60,10 +64,115 @@ const ThemeConfigCard = () => {
<div className="py-24 text-danger-500 text-center">{error.message}</div>
)
// 将颜色 key 补全为 ThemeConfigItem 中定义的所有颜色相关属性
const colorKeys = [
'--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-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
return (
@@ -74,19 +183,24 @@ const ThemeConfigCard = () => {
<div key={mode}>
<h3>{mode === 'dark' ? '暗色主题' : '亮色主题'}</h3>
{colorKeys.map((key) => (
<div
key={key}
style={{ display: 'flex', alignItems: 'center', marginBottom: 8 }}
>
<label style={{ width: 150 }}>{key}</label>
<div key={key} className="grid grid-cols-2 items-center mb-2 gap-2">
<label className="text-right">{key}</label>
<Controller
control={control}
name={`theme.${mode}.${key}`}
render={({ field: { value, onChange } }) => {
console.log(value)
const hslArray = value?.split(' ') ?? [0, 0, 0]
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>

View File

@@ -34,7 +34,8 @@ export default defineConfig(({ mode }) => {
ws: true,
changeOrigin: true
},
'/api': backendDebugUrl
'/api': backendDebugUrl,
'/files': backendDebugUrl
}
},
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);
});
// ------------中间件结束------------
// ------------挂载路由------------