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

152 lines
7.5 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 FormControlLabel from '@mui/material/FormControlLabel';
import Switch from '@mui/material/Switch';
import IconButton from '@mui/material/IconButton';
import DeleteIcon from '@mui/icons-material/Delete';
import AddIcon from '@mui/icons-material/Add';
import { getSettings, saveSettings } from '../../api/settings.js';
import { uploadFile } from '../../api/upload.js';
import { showSnack } from '../snack.jsx';
/** 首页设置:个人信息(头像/简介/联系链接)/主页内容(Markdown)/音乐嵌入(迁移自 v1 首页卡片) */
export default function Homepage() {
const [form, setForm] = useState({
homepage_avatar: '',
homepage_bio: '',
homepage_content: '',
music_embed_enabled: false,
music_embed_code: '',
music_embed_position: 'right',
music_embed_autohide: false,
music_embed_idle_timeout: '10',
});
const [contacts, setContacts] = useState([{ icon: 'link', url: '', title: '' }]);
const [saving, setSaving] = useState(false);
const [uploadStatus, setUploadStatus] = useState('');
const fileRef = useRef(null);
useEffect(() => {
getSettings()
.then((s) => {
let parsed = [];
try { parsed = JSON.parse(s.homepage_contacts || '[]'); } catch { parsed = []; }
setForm({
homepage_avatar: s.homepage_avatar || '',
homepage_bio: s.homepage_bio || '',
homepage_content: s.homepage_content || '',
music_embed_enabled: s.music_embed_enabled === '1',
music_embed_code: s.music_embed_code || '',
music_embed_position: s.music_embed_position || 'right',
music_embed_autohide: s.music_embed_autohide === '1',
music_embed_idle_timeout: s.music_embed_idle_timeout || '10',
});
setContacts(parsed.length > 0 ? parsed : [{ icon: 'link', url: '', title: '' }]);
})
.catch((e) => showSnack(e.message, 'error'));
}, []);
const set = (k) => (e) => setForm((p) => ({ ...p, [k]: e.target.value }));
const setSwitch = (k) => (e) => setForm((p) => ({ ...p, [k]: e.target.checked }));
const updateContact = (i, k) => (e) => {
const next = [...contacts];
next[i] = { ...next[i], [k]: e.target.value };
setContacts(next);
};
const addContact = () => {
if (contacts.length >= 5) { showSnack('最多 5 个联系链接', 'error'); return; }
setContacts([...contacts, { icon: 'link', url: '', title: '' }]);
};
const removeContact = (i) => setContacts(contacts.filter((_, idx) => idx !== i));
const handleUpload = async (e) => {
const file = e.target.files && e.target.files[0];
e.target.value = '';
if (!file) return;
try {
const data = await uploadFile(file);
setForm((p) => ({ ...p, homepage_content: p.homepage_content + '\n' + data.tag + '\n' }));
setUploadStatus('已插入: ' + data.tag);
} catch (err) {
showSnack(err.message, 'error');
}
};
const save = async () => {
const validContacts = contacts.filter((c) => c.url.trim());
setSaving(true);
try {
await saveSettings({
homepage_avatar: form.homepage_avatar.trim(),
homepage_bio: form.homepage_bio,
homepage_content: form.homepage_content,
homepage_contacts: JSON.stringify(validContacts),
music_embed_enabled: form.music_embed_enabled ? '1' : '0',
music_embed_code: form.music_embed_code.trim(),
music_embed_position: form.music_embed_position,
music_embed_autohide: form.music_embed_autohide ? '1' : '0',
music_embed_idle_timeout: form.music_embed_idle_timeout,
});
showSnack('首页设置已保存');
} catch (e) {
showSnack(e.message, 'error');
}
setSaving(false);
};
return (
<Box sx={{ maxWidth: 720 }}>
<Typography variant="h5" sx={{ mb: 2 }}>首页设置</Typography>
<Paper sx={{ p: 3, mb: 2 }}>
<Typography variant="subtitle1" sx={{ mb: 1 }}>个人信息</Typography>
<TextField fullWidth label="头像 URL" type="url" value={form.homepage_avatar} onChange={set('homepage_avatar')} margin="normal" placeholder="https://example.com/avatar.jpg" />
<TextField fullWidth label="个人简介" multiline rows={2} value={form.homepage_bio} onChange={set('homepage_bio')} margin="normal" placeholder="一段简短的自我介绍" />
<Typography variant="body2" color="text.secondary" sx={{ mt: 1, mb: 1 }}>联系链接最多5个</Typography>
{contacts.map((c, i) => (
<Box key={i} sx={{ display: 'flex', gap: 1, alignItems: 'center', mb: 1 }}>
<TextField size="small" label="图标名" value={c.icon} onChange={updateContact(i, 'icon')} sx={{ width: 110 }} />
<TextField size="small" label="URL" value={c.url} onChange={updateContact(i, 'url')} sx={{ flex: 1 }} placeholder="https://..." />
<TextField size="small" label="标题(选填)" value={c.title} onChange={updateContact(i, 'title')} sx={{ width: 120 }} />
<IconButton size="small" color="error" onClick={() => removeContact(i)} title="删除"><DeleteIcon fontSize="small" /></IconButton>
</Box>
))}
<Button size="small" startIcon={<AddIcon />} onClick={addContact}>添加链接</Button>
</Paper>
<Paper sx={{ p: 3, mb: 2 }}>
<Typography variant="subtitle1" sx={{ mb: 1 }}>主页内容</Typography>
<TextField fullWidth label="正文 (Markdown)" multiline rows={10} value={form.homepage_content} onChange={set('homepage_content')} margin="normal" placeholder="支持 Markdown 语法和 [image:filename] 标签" />
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
<Button variant="outlined" size="small" onClick={() => fileRef.current && fileRef.current.click()}>
上传附件
</Button>
<input ref={fileRef} type="file" style={{ display: 'none' }} onChange={handleUpload} />
{uploadStatus && <Typography variant="caption" color="text.secondary">{uploadStatus}</Typography>}
</Box>
</Paper>
<Paper sx={{ p: 3, mb: 2 }}>
<Typography variant="subtitle1" sx={{ mb: 1 }}>音乐嵌入</Typography>
<FormControlLabel control={<Switch checked={form.music_embed_enabled} onChange={setSwitch('music_embed_enabled')} />} label="所有页面显示音乐播放器" />
<TextField fullWidth label="嵌入代码" multiline rows={3} value={form.music_embed_code} onChange={set('music_embed_code')} margin="normal" placeholder="粘贴网易云音乐 iframe 代码" />
<TextField select label="显示位置" value={form.music_embed_position} onChange={set('music_embed_position')} margin="normal" sx={{ maxWidth: 200 }}>
<option value="right">右下角</option>
<option value="left">左下角</option>
</TextField>
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
<FormControlLabel control={<Switch checked={form.music_embed_autohide} onChange={setSwitch('music_embed_autohide')} />} label="无操作后缩小为图标" />
<TextField size="small" label="空闲超时(秒)" type="number" inputProps={{ min: 3, max: 120 }} value={form.music_embed_idle_timeout} onChange={set('music_embed_idle_timeout')} sx={{ width: 140 }} />
</Box>
</Paper>
<Button variant="contained" onClick={save} disabled={saving}>保存首页设置</Button>
</Box>
);
}