152 lines
7.5 KiB
React
152 lines
7.5 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 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>
|
||
);
|
||
}
|