mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
fix
This commit is contained in:
@@ -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>
|
||||||
|
@@ -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>
|
||||||
)
|
)
|
||||||
|
@@ -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>
|
||||||
|
@@ -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 (
|
||||||
<>
|
<>
|
||||||
|
@@ -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>
|
||||||
|
@@ -34,7 +34,8 @@ export default defineConfig(({ mode }) => {
|
|||||||
ws: true,
|
ws: true,
|
||||||
changeOrigin: true
|
changeOrigin: true
|
||||||
},
|
},
|
||||||
'/api': backendDebugUrl
|
'/api': backendDebugUrl,
|
||||||
|
'/files': backendDebugUrl
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
@@ -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);
|
||||||
|
});
|
||||||
|
|
||||||
// ------------中间件结束------------
|
// ------------中间件结束------------
|
||||||
|
|
||||||
// ------------挂载路由------------
|
// ------------挂载路由------------
|
||||||
|
Reference in New Issue
Block a user