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

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>
);
}