147 lines
5.5 KiB
React
147 lines
5.5 KiB
React
import React, { useCallback, useEffect, useState } from 'react';
|
|
import Paper from '@mui/material/Paper';
|
|
import Typography from '@mui/material/Typography';
|
|
import Button from '@mui/material/Button';
|
|
import Box from '@mui/material/Box';
|
|
import Table from '@mui/material/Table';
|
|
import TableBody from '@mui/material/TableBody';
|
|
import TableCell from '@mui/material/TableCell';
|
|
import TableContainer from '@mui/material/TableContainer';
|
|
import TableHead from '@mui/material/TableHead';
|
|
import TableRow from '@mui/material/TableRow';
|
|
import Chip from '@mui/material/Chip';
|
|
import Switch from '@mui/material/Switch';
|
|
import FormControlLabel from '@mui/material/FormControlLabel';
|
|
import IconButton from '@mui/material/IconButton';
|
|
import EditIcon from '@mui/icons-material/Edit';
|
|
import DeleteIcon from '@mui/icons-material/Delete';
|
|
import { listPosts, updatePost, deletePost } from '../../api/blog.js';
|
|
import { getSettings, saveSettings } from '../../api/settings.js';
|
|
import { showSnack } from '../snack.jsx';
|
|
import ConfirmDialog from '../ConfirmDialog.jsx';
|
|
|
|
/** 博客管理:博客侧栏开关 + 文章列表(发布开关/编辑跳前台 write.html?edit=id/删除) */
|
|
export default function BlogManage() {
|
|
const [posts, setPosts] = useState(null);
|
|
const [showSidebar, setShowSidebar] = useState(true);
|
|
const [confirm, setConfirm] = useState(null); // { id, title }
|
|
const [deleting, setDeleting] = useState(false);
|
|
|
|
const load = useCallback(() => {
|
|
listPosts(true)
|
|
.then((ps) => setPosts(ps || []))
|
|
.catch((e) => showSnack(e.message, 'error'));
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
load();
|
|
getSettings().then((s) => setShowSidebar(s.blog_show_sidebar !== '0')).catch(() => {});
|
|
}, [load]);
|
|
|
|
const saveSidebar = async () => {
|
|
try {
|
|
await saveSettings({ blog_show_sidebar: showSidebar ? '1' : '0' });
|
|
showSnack('已保存');
|
|
} catch (e) {
|
|
showSnack(e.message, 'error');
|
|
}
|
|
};
|
|
|
|
const togglePublish = async (p) => {
|
|
try {
|
|
await updatePost(p.id, {
|
|
title: p.title,
|
|
content: p.content,
|
|
excerpt: p.excerpt || '',
|
|
published: p.published ? 0 : 1,
|
|
use_markdown: p.use_markdown,
|
|
});
|
|
showSnack(p.published ? '已转为草稿' : '已发布');
|
|
load();
|
|
} catch (e) {
|
|
showSnack(e.message, 'error');
|
|
}
|
|
};
|
|
|
|
const doDelete = async () => {
|
|
if (!confirm) return;
|
|
setDeleting(true);
|
|
try {
|
|
await deletePost(confirm.id);
|
|
showSnack('已删除');
|
|
setConfirm(null);
|
|
load();
|
|
} catch (e) {
|
|
showSnack(e.message, 'error');
|
|
}
|
|
setDeleting(false);
|
|
};
|
|
|
|
return (
|
|
<Box>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 1, mb: 2 }}>
|
|
<Typography variant="h5">博客管理</Typography>
|
|
<Button variant="contained" component="a" href="/write.html" target="_blank" startIcon={<EditIcon />}>写文章</Button>
|
|
</Box>
|
|
|
|
<Paper sx={{ p: 2, mb: 2, maxWidth: 640 }}>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
|
<FormControlLabel
|
|
control={<Switch checked={showSidebar} onChange={(e) => setShowSidebar(e.target.checked)} />}
|
|
label="博客页面左侧显示头像和简介"
|
|
/>
|
|
<Button variant="outlined" size="small" onClick={saveSidebar}>保存</Button>
|
|
</Box>
|
|
</Paper>
|
|
|
|
<TableContainer component={Paper}>
|
|
<Table size="small">
|
|
<TableHead>
|
|
<TableRow>
|
|
<TableCell>标题</TableCell>
|
|
<TableCell>状态</TableCell>
|
|
<TableCell>格式</TableCell>
|
|
<TableCell>时间</TableCell>
|
|
<TableCell align="right">操作</TableCell>
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{posts === null ? (
|
|
<TableRow><TableCell colSpan={5}>加载中...</TableCell></TableRow>
|
|
) : posts.length === 0 ? (
|
|
<TableRow><TableCell colSpan={5}>暂无文章</TableCell></TableRow>
|
|
) : posts.map((p) => (
|
|
<TableRow key={p.id}>
|
|
<TableCell><Box sx={{ fontWeight: 600 }}>{p.title}</Box></TableCell>
|
|
<TableCell>
|
|
<Chip
|
|
size="small"
|
|
label={p.published ? '已发布' : '草稿'}
|
|
color={p.published ? 'primary' : 'default'}
|
|
onClick={() => togglePublish(p)}
|
|
title="点击切换发布状态"
|
|
/>
|
|
</TableCell>
|
|
<TableCell><Chip size="small" label={p.use_markdown ? 'Markdown' : '纯文本'} variant="outlined" /></TableCell>
|
|
<TableCell sx={{ color: 'text.secondary', fontSize: 13 }}>{p.created_at}</TableCell>
|
|
<TableCell align="right" sx={{ whiteSpace: 'nowrap' }}>
|
|
<IconButton size="small" component="a" href={`/write.html?edit=${p.id}`} target="_blank" title="编辑(前台)"><EditIcon fontSize="small" /></IconButton>
|
|
<IconButton size="small" color="error" onClick={() => setConfirm({ id: p.id, title: p.title })}><DeleteIcon fontSize="small" /></IconButton>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</TableContainer>
|
|
|
|
<ConfirmDialog
|
|
open={!!confirm}
|
|
message={`确定要删除 "${confirm ? confirm.title : ''}" 吗?`}
|
|
onClose={() => setConfirm(null)}
|
|
onConfirm={doDelete}
|
|
confirmText={deleting ? '删除中...' : '确认删除'}
|
|
/>
|
|
</Box>
|
|
);
|
|
}
|