mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-03-01 16:20:25 +00:00
feat: 优化webui界面和文件管理器 (#1472)
This commit is contained in:
@@ -10,9 +10,8 @@ import { Controller, useForm, useWatch } from 'react-hook-form';
|
||||
import toast from 'react-hot-toast';
|
||||
import { FaFont, FaUserAstronaut, FaCheck } from 'react-icons/fa';
|
||||
import { FaPaintbrush } from 'react-icons/fa6';
|
||||
import { IoIosColorPalette } from 'react-icons/io';
|
||||
import { IoIosColorPalette, IoMdRefresh } from 'react-icons/io';
|
||||
import { MdDarkMode, MdLightMode } from 'react-icons/md';
|
||||
import { IoMdRefresh } from 'react-icons/io';
|
||||
|
||||
import themes from '@/const/themes';
|
||||
|
||||
@@ -77,8 +76,8 @@ function PreviewThemeCard ({ theme, onPreview, isSelected }: PreviewThemeCardPro
|
||||
)}
|
||||
>
|
||||
{isSelected && (
|
||||
<div className="absolute top-1 right-1 z-10">
|
||||
<Chip size="sm" color="primary" variant="solid">
|
||||
<div className='absolute top-1 right-1 z-10'>
|
||||
<Chip size='sm' color='primary' variant='solid'>
|
||||
<FaCheck size={10} />
|
||||
</Chip>
|
||||
</div>
|
||||
@@ -91,20 +90,20 @@ function PreviewThemeCard ({ theme, onPreview, isSelected }: PreviewThemeCardPro
|
||||
<FaUserAstronaut />
|
||||
{theme.author ?? '未知'}
|
||||
</div>
|
||||
<div className='text-xs text-primary-200'>{theme.description}</div>
|
||||
<div className='text-xs text-primary-200 whitespace-nowrap overflow-hidden text-ellipsis w-full'>{theme.description}</div>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<div className='flex flex-col gap-1'>
|
||||
{colors.map((color) => (
|
||||
<div className='flex gap-1 items-center flex-wrap' key={color}>
|
||||
<div className='text-xs w-4 text-right'>
|
||||
<div className='flex gap-1 items-center flex-nowrap' key={color}>
|
||||
<div className='text-xs w-4 text-right flex-shrink-0'>
|
||||
{color[0].toUpperCase()}
|
||||
</div>
|
||||
{values.map((value) => (
|
||||
<div
|
||||
key={value}
|
||||
className={clsx(
|
||||
'w-2 h-2 rounded-full shadow-small',
|
||||
'w-2 h-2 rounded-full shadow-small flex-shrink-0',
|
||||
`bg-${color}${value}`
|
||||
)}
|
||||
/>
|
||||
@@ -135,9 +134,9 @@ const isThemeColorsEqual = (a: ThemeConfig, b: ThemeConfig): boolean => {
|
||||
|
||||
// 字体模式显示名称映射
|
||||
const fontModeNames: Record<string, string> = {
|
||||
'aacute': 'Aa 偷吃可爱长大的',
|
||||
'system': '系统默认',
|
||||
'custom': '自定义字体',
|
||||
aacute: 'Aa 偷吃可爱长大的',
|
||||
system: '系统默认',
|
||||
custom: '自定义字体',
|
||||
};
|
||||
|
||||
const ThemeConfigCard = () => {
|
||||
@@ -169,11 +168,16 @@ const ThemeConfigCard = () => {
|
||||
const originalDataRef = useRef<ThemeConfig | null>(null);
|
||||
|
||||
// 在组件挂载时创建 style 标签,并在卸载时清理
|
||||
// 同时在卸载时恢复字体到已保存的状态(避免"伪自动保存"问题)
|
||||
useEffect(() => {
|
||||
const styleTag = document.createElement('style');
|
||||
document.head.appendChild(styleTag);
|
||||
styleTagRef.current = styleTag;
|
||||
return () => {
|
||||
// 组件卸载时,恢复到已保存的字体设置
|
||||
if (originalDataRef.current?.fontMode) {
|
||||
applyFont(originalDataRef.current.fontMode);
|
||||
}
|
||||
if (styleTagRef.current) {
|
||||
document.head.removeChild(styleTagRef.current);
|
||||
}
|
||||
@@ -259,14 +263,12 @@ const ThemeConfigCard = () => {
|
||||
const savedThemeName = useMemo(() => {
|
||||
if (!originalDataRef.current) return null;
|
||||
return themes.find(t => isThemeColorsEqual(t.theme, originalDataRef.current!))?.name || '自定义';
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dataLoaded, hasUnsavedChanges]);
|
||||
|
||||
// 已保存的字体模式显示名称
|
||||
const savedFontModeDisplayName = useMemo(() => {
|
||||
const mode = originalDataRef.current?.fontMode || 'aacute';
|
||||
return fontModeNames[mode] || mode;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dataLoaded, hasUnsavedChanges]);
|
||||
|
||||
if (loading) return <PageLoading loading />;
|
||||
@@ -282,33 +284,33 @@ const ThemeConfigCard = () => {
|
||||
<title>主题配置 - NapCat WebUI</title>
|
||||
|
||||
{/* 顶部操作栏 */}
|
||||
<div className="sticky top-0 z-20 bg-background/80 backdrop-blur-md border-b border-divider">
|
||||
<div className="flex items-center justify-between p-4">
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<span className="text-default-400">当前主题:</span>
|
||||
<Chip size="sm" color="primary" variant="flat">
|
||||
<div className='sticky top-0 z-20 bg-background/80 backdrop-blur-md border-b border-divider'>
|
||||
<div className='flex items-center justify-between p-4'>
|
||||
<div className='flex items-center gap-3 flex-wrap'>
|
||||
<div className='flex items-center gap-2 text-sm'>
|
||||
<span className='text-default-400'>当前主题:</span>
|
||||
<Chip size='sm' color='primary' variant='flat'>
|
||||
{savedThemeName || '加载中...'}
|
||||
</Chip>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<span className="text-default-400">字体:</span>
|
||||
<Chip size="sm" color="secondary" variant="flat">
|
||||
<div className='flex items-center gap-2 text-sm'>
|
||||
<span className='text-default-400'>字体:</span>
|
||||
<Chip size='sm' color='secondary' variant='flat'>
|
||||
{savedFontModeDisplayName}
|
||||
</Chip>
|
||||
</div>
|
||||
{hasUnsavedChanges && (
|
||||
<Chip size="sm" color="warning" variant="solid">
|
||||
<Chip size='sm' color='warning' variant='solid'>
|
||||
有未保存的更改
|
||||
</Chip>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className='flex items-center gap-2'>
|
||||
<Button
|
||||
size="sm"
|
||||
radius="full"
|
||||
variant="flat"
|
||||
className="font-medium bg-default-100 text-default-600 dark:bg-default-50/50"
|
||||
size='sm'
|
||||
radius='full'
|
||||
variant='flat'
|
||||
className='font-medium bg-default-100 text-default-600 dark:bg-default-50/50'
|
||||
onPress={() => {
|
||||
reset();
|
||||
toast.success('已重置');
|
||||
@@ -318,10 +320,10 @@ const ThemeConfigCard = () => {
|
||||
取消更改
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
size='sm'
|
||||
color='primary'
|
||||
radius="full"
|
||||
className="font-medium shadow-md shadow-primary/20"
|
||||
radius='full'
|
||||
className='font-medium shadow-md shadow-primary/20'
|
||||
isLoading={isSubmitting}
|
||||
onPress={() => onSubmit()}
|
||||
isDisabled={!hasUnsavedChanges}
|
||||
@@ -329,11 +331,11 @@ const ThemeConfigCard = () => {
|
||||
保存
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
size='sm'
|
||||
isIconOnly
|
||||
radius='full'
|
||||
variant='flat'
|
||||
className="text-default-500 bg-default-100 dark:bg-default-50/50"
|
||||
className='text-default-500 bg-default-100 dark:bg-default-50/50'
|
||||
onPress={onRefresh}
|
||||
>
|
||||
<IoMdRefresh size={18} />
|
||||
@@ -342,7 +344,7 @@ const ThemeConfigCard = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4">
|
||||
<div className='p-4'>
|
||||
<Accordion variant='splitted' defaultExpandedKeys={['font', 'select']}>
|
||||
<AccordionItem
|
||||
key='font'
|
||||
@@ -355,18 +357,18 @@ const ThemeConfigCard = () => {
|
||||
<div className='flex flex-col gap-4'>
|
||||
<Controller
|
||||
control={control}
|
||||
name="theme.fontMode"
|
||||
name='theme.fontMode'
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
label="字体预设"
|
||||
label='字体预设'
|
||||
selectedKeys={field.value ? [field.value] : ['aacute']}
|
||||
onChange={(e) => field.onChange(e.target.value)}
|
||||
className="max-w-xs"
|
||||
className='max-w-xs'
|
||||
disallowEmptySelection
|
||||
>
|
||||
<SelectItem key="aacute">Aa 偷吃可爱长大的</SelectItem>
|
||||
<SelectItem key="system">系统默认</SelectItem>
|
||||
<SelectItem key="custom">自定义字体</SelectItem>
|
||||
<SelectItem key='aacute'>Aa 偷吃可爱长大的</SelectItem>
|
||||
<SelectItem key='system'>系统默认</SelectItem>
|
||||
<SelectItem key='custom'>自定义字体</SelectItem>
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -337,7 +337,7 @@ export default function FileManagerPage () {
|
||||
return (
|
||||
<div className='h-full flex flex-col relative gap-4 w-full p-2 md:p-4'>
|
||||
<div className={clsx(
|
||||
'mb-4 flex flex-col md:flex-row items-stretch md:items-center gap-4 sticky top-14 z-10 backdrop-blur-sm shadow-sm py-2 px-4 rounded-xl transition-colors',
|
||||
'mb-4 flex flex-col md:flex-row items-stretch md:items-center gap-4 sticky top-14 z-10 backdrop-blur-sm shadow-sm py-2 px-4 rounded-sm transition-colors',
|
||||
hasBackground
|
||||
? 'bg-white/20 dark:bg-black/10 border border-white/40 dark:border-white/10'
|
||||
: 'bg-white/60 dark:bg-black/40 border border-white/40 dark:border-white/10'
|
||||
@@ -345,6 +345,7 @@ export default function FileManagerPage () {
|
||||
>
|
||||
<div className='flex items-center gap-2 overflow-x-auto hide-scrollbar pb-1 md:pb-0'>
|
||||
<Button
|
||||
radius='sm'
|
||||
color='primary'
|
||||
size='sm'
|
||||
isIconOnly
|
||||
@@ -356,6 +357,7 @@ export default function FileManagerPage () {
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
radius='sm'
|
||||
color='primary'
|
||||
size='sm'
|
||||
isIconOnly
|
||||
@@ -367,6 +369,7 @@ export default function FileManagerPage () {
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
radius='sm'
|
||||
color='primary'
|
||||
isLoading={loading}
|
||||
size='sm'
|
||||
@@ -378,6 +381,7 @@ export default function FileManagerPage () {
|
||||
<MdRefresh />
|
||||
</Button>
|
||||
<Button
|
||||
radius='sm'
|
||||
color='primary'
|
||||
size='sm'
|
||||
isIconOnly
|
||||
@@ -392,6 +396,7 @@ export default function FileManagerPage () {
|
||||
selectedFiles === 'all') && (
|
||||
<>
|
||||
<Button
|
||||
radius='sm'
|
||||
color='primary'
|
||||
size='sm'
|
||||
variant='flat'
|
||||
@@ -404,6 +409,7 @@ export default function FileManagerPage () {
|
||||
)
|
||||
</Button>
|
||||
<Button
|
||||
radius='sm'
|
||||
color='primary'
|
||||
size='sm'
|
||||
variant='flat'
|
||||
@@ -419,6 +425,7 @@ export default function FileManagerPage () {
|
||||
)
|
||||
</Button>
|
||||
<Button
|
||||
radius='sm'
|
||||
color='primary'
|
||||
size='sm'
|
||||
variant='flat'
|
||||
@@ -435,7 +442,10 @@ export default function FileManagerPage () {
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col md:flex-row flex-1 gap-2 overflow-hidden items-stretch md:items-center'>
|
||||
<Breadcrumbs className='flex-1 bg-white/40 dark:bg-black/20 backdrop-blur-md shadow-sm border border-white/20 px-2 py-2 rounded-lg overflow-x-auto hide-scrollbar whitespace-nowrap'>
|
||||
<Breadcrumbs
|
||||
radius='sm'
|
||||
className='flex-1 bg-white/40 dark:bg-black/20 backdrop-blur-md shadow-sm border border-white/20 px-2 py-2 rounded-sm overflow-x-auto hide-scrollbar whitespace-nowrap'
|
||||
>
|
||||
{currentPath.split('/').map((part, index, parts) => (
|
||||
<BreadcrumbItem
|
||||
key={part}
|
||||
@@ -450,6 +460,7 @@ export default function FileManagerPage () {
|
||||
))}
|
||||
</Breadcrumbs>
|
||||
<Input
|
||||
radius='sm'
|
||||
type='text'
|
||||
placeholder='输入跳转路径'
|
||||
value={jumpPath}
|
||||
@@ -472,7 +483,7 @@ export default function FileManagerPage () {
|
||||
animate={{ height: showUpload ? 'auto' : 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className={clsx(
|
||||
'border-dashed rounded-lg text-center overflow-hidden',
|
||||
'border-dashed rounded-sm text-center overflow-hidden',
|
||||
isDragActive ? 'border-primary bg-primary/10' : 'border-default-300',
|
||||
showUpload ? 'mb-4 border-2' : 'border-none'
|
||||
)}
|
||||
@@ -486,7 +497,7 @@ export default function FileManagerPage () {
|
||||
<div className='flex flex-col items-center gap-2'>
|
||||
<FiUpload className='text-3xl text-primary' />
|
||||
<p className='text-default-600'>拖拽文件到此处上传</p>
|
||||
<Button color='primary' size='sm' variant='flat' onPress={open}>
|
||||
<Button radius='sm' color='primary' size='sm' variant='flat' onPress={open}>
|
||||
点击选择文件
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -102,7 +102,7 @@ const DashboardIndexPage: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<title>基础信息 - NapCat WebUI</title>
|
||||
<section className='w-full p-2 md:p-4 md:max-w-[1000px] mx-auto'>
|
||||
<section className='w-full p-2 md:p-4 md:max-w-[1000px] mx-auto overflow-hidden'>
|
||||
<div className='grid grid-cols-1 lg:grid-cols-3 gap-4 items-stretch'>
|
||||
<div className='flex flex-col gap-2'>
|
||||
<QQInfo />
|
||||
@@ -112,10 +112,11 @@ const DashboardIndexPage: React.FC = () => {
|
||||
</div>
|
||||
<Networks />
|
||||
<Card className={clsx(
|
||||
'backdrop-blur-sm border border-white/40 dark:border-white/10 shadow-sm transition-all',
|
||||
'backdrop-blur-sm border border-white/40 dark:border-white/10 shadow-sm transition-all overflow-hidden',
|
||||
hasBackground ? 'bg-white/10 dark:bg-black/10' : 'bg-white/60 dark:bg-black/40'
|
||||
)}>
|
||||
<CardBody>
|
||||
)}
|
||||
>
|
||||
<CardBody className='overflow-hidden'>
|
||||
<Hitokoto />
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
@@ -158,7 +158,7 @@ export default function TerminalPage () {
|
||||
variant='flat'
|
||||
onPress={createNewTerminal}
|
||||
startContent={<IoAdd />}
|
||||
className='text-xl'
|
||||
className='text-xl ml-auto'
|
||||
/>
|
||||
</div>
|
||||
<div className='flex-grow overflow-hidden'>
|
||||
|
||||
Reference in New Issue
Block a user