Files
rainblogweb/frontend/src/admin/pages/ThemeSettings.jsx
T

157 lines
7.4 KiB
React

import React, { useEffect, useRef, useState } from 'react';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import TextField from '@mui/material/TextField';
import Button from '@mui/material/Button';
import Box from '@mui/material/Box';
import Slider from '@mui/material/Slider';
import ToggleButton from '@mui/material/ToggleButton';
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
import FormControlLabel from '@mui/material/FormControlLabel';
import Switch from '@mui/material/Switch';
import { getSettings, saveSettings } from '../../api/settings.js';
import { uploadWallpaper } from '../../api/upload.js';
import { showSnack } from '../snack.jsx';
/** 主题设置:主题色/壁纸(上传或URL)/导航卡片样式/玻璃模糊透明度/强制深色(迁移自 v1 主题卡片) */
export default function ThemeSettings() {
const [form, setForm] = useState({
primary_color: '#6750a4',
theme_wallpaper: '',
theme_wallpaper_scale: 'cover',
theme_wallpaper_enabled: true,
nav_style: 'default',
card_style: 'default',
glass_blur: '20',
glass_opacity: '0.6',
theme_force_dark: false,
});
const [saving, setSaving] = useState(false);
const fileRef = useRef(null);
useEffect(() => {
getSettings()
.then((s) => setForm({
primary_color: s.primary_color || '#6750a4',
theme_wallpaper: s.theme_wallpaper || '',
theme_wallpaper_scale: s.theme_wallpaper_scale || 'cover',
theme_wallpaper_enabled: s.theme_wallpaper_enabled !== '0',
nav_style: s.nav_style || 'default',
card_style: s.card_style || 'default',
glass_blur: s.glass_blur || '20',
glass_opacity: s.glass_opacity || '0.6',
theme_force_dark: s.theme_force_dark === '1',
}))
.catch((e) => showSnack(e.message, 'error'));
}, []);
const set = (k) => (e) => setForm((p) => ({ ...p, [k]: e.target.value }));
const handleWallpaperUpload = async (e) => {
const file = e.target.files && e.target.files[0];
e.target.value = '';
if (!file) return;
try {
const data = await uploadWallpaper(file);
setForm((p) => ({ ...p, theme_wallpaper: data.url }));
showSnack('壁纸已上传');
} catch (err) {
showSnack(err.message, 'error');
}
};
const save = async () => {
setSaving(true);
try {
await saveSettings({
primary_color: form.primary_color,
theme_wallpaper: form.theme_wallpaper.trim(),
theme_wallpaper_scale: form.theme_wallpaper_scale,
theme_wallpaper_enabled: form.theme_wallpaper_enabled ? '1' : '0',
theme_force_dark: form.theme_force_dark ? '1' : '0',
nav_style: form.nav_style,
card_style: form.card_style,
glass_blur: form.glass_blur,
glass_opacity: form.glass_opacity,
});
showSnack('主题设置已保存');
} catch (e) {
showSnack(e.message, 'error');
}
setSaving(false);
};
return (
<Box sx={{ maxWidth: 640 }}>
<Typography variant="h5" sx={{ mb: 2 }}>主题设置</Typography>
<Paper sx={{ p: 3, mb: 2 }}>
<Typography variant="subtitle1" sx={{ mb: 1 }}>主题色</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<input type="color" value={form.primary_color} onChange={set('primary_color')} style={{ width: 48, height: 48, border: 'none', background: 'none', cursor: 'pointer' }} />
<TextField label="主色调" value={form.primary_color} onChange={set('primary_color')} size="small" sx={{ width: 160 }} />
</Box>
</Paper>
<Paper sx={{ p: 3, mb: 2 }}>
<Typography variant="subtitle1" sx={{ mb: 1 }}>壁纸背景</Typography>
<FormControlLabel
control={<Switch checked={form.theme_wallpaper_enabled} onChange={(e) => setForm((p) => ({ ...p, theme_wallpaper_enabled: e.target.checked }))} />}
label="启用壁纸背景"
sx={{ mb: 1 }}
/>
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center', mb: 1 }}>
<Button variant="outlined" onClick={() => fileRef.current && fileRef.current.click()}>上传图片</Button>
<input ref={fileRef} type="file" accept="image/*" style={{ display: 'none' }} onChange={handleWallpaperUpload} />
<Typography variant="caption" color="text.secondary">上传后自动填入 URL</Typography>
</Box>
<TextField fullWidth label="图片 URL" type="url" value={form.theme_wallpaper} onChange={set('theme_wallpaper')} margin="normal" placeholder="https://example.com/wallpaper.jpg" />
{form.theme_wallpaper && (
<Box sx={{ mt: 1 }}>
<img src={form.theme_wallpaper} alt="wallpaper" style={{ width: '100%', maxHeight: 120, objectFit: 'cover', borderRadius: 8 }} />
</Box>
)}
<TextField fullWidth label="缩放方式" select value={form.theme_wallpaper_scale} onChange={set('theme_wallpaper_scale')} margin="normal" sx={{ maxWidth: 280 }}>
<option value="cover">cover - 覆盖填充</option>
<option value="contain">contain - 完整显示</option>
<option value="repeat">repeat - 平铺重复</option>
<option value="stretch">stretch - 拉伸填充</option>
</TextField>
</Paper>
<Paper sx={{ p: 3, mb: 2 }}>
<Typography variant="subtitle1" sx={{ mb: 1 }}>样式设置</Typography>
<Typography variant="body2" sx={{ mb: 0.5 }}>导航栏样式</Typography>
<ToggleButtonGroup exclusive value={form.nav_style} onChange={(e, v) => v && setForm((p) => ({ ...p, nav_style: v }))} size="small" sx={{ mb: 2 }}>
<ToggleButton value="default">默认</ToggleButton>
<ToggleButton value="glass">磨砂玻璃</ToggleButton>
<ToggleButton value="capsule">胶囊</ToggleButton>
</ToggleButtonGroup>
<Typography variant="body2" sx={{ mb: 0.5 }}>卡片样式</Typography>
<ToggleButtonGroup exclusive value={form.card_style} onChange={(e, v) => v && setForm((p) => ({ ...p, card_style: v }))} size="small">
<ToggleButton value="default">默认</ToggleButton>
<ToggleButton value="glass">磨砂玻璃</ToggleButton>
</ToggleButtonGroup>
</Paper>
<Paper sx={{ p: 3, mb: 2 }}>
<Typography variant="subtitle1" sx={{ mb: 1 }}>玻璃效果</Typography>
<Typography variant="body2" color="text.secondary">模糊强度: {form.glass_blur}px</Typography>
<Slider min={5} max={40} value={parseInt(form.glass_blur, 10) || 20} onChange={(e, v) => setForm((p) => ({ ...p, glass_blur: String(v) }))} sx={{ maxWidth: 400 }} />
<Typography variant="body2" color="text.secondary">透明度: {form.glass_opacity}</Typography>
<Slider min={0.1} max={0.95} step={0.05} value={parseFloat(form.glass_opacity) || 0.6} onChange={(e, v) => setForm((p) => ({ ...p, glass_opacity: String(v) }))} sx={{ maxWidth: 400 }} />
</Paper>
<Paper sx={{ p: 3, mb: 2 }}>
<Typography variant="subtitle1" sx={{ mb: 1 }}>深色模式</Typography>
<FormControlLabel
control={<Switch checked={form.theme_force_dark} onChange={(e) => setForm((p) => ({ ...p, theme_force_dark: e.target.checked }))} />}
label="强制深色模式(启用后用户无法切换为浅色)"
/>
</Paper>
<Button variant="contained" onClick={save} disabled={saving}>保存主题设置</Button>
</Box>
);
}