功能:完成文章回收站功能

This commit is contained in:
宇阳
2024-11-25 15:53:08 +08:00
parent 5f28411fc4
commit 7b191ef9a4
8 changed files with 205 additions and 16 deletions

View File

@@ -6,8 +6,12 @@ export const addArticleDataAPI = (data: Article) =>
Request<Article>("POST", "/article", { data }); Request<Article>("POST", "/article", { data });
// 删除文章 // 删除文章
export const delArticleDataAPI = (id: number) => export const delArticleDataAPI = (id: number, isDel?: boolean) =>
Request<Article>("DELETE", `/article/${id}`); Request<Article>("DELETE", isDel ? `/article/${id}/1` : `/article/${id}/0`);
// 还原被删除的文章
export const reductionArticleDataAPI = (id: number) =>
Request<Article>("PATCH", `/article/reduction/${id}`);
// 编辑文章 // 编辑文章
export const editArticleDataAPI = (data: Article) => export const editArticleDataAPI = (data: Article) =>

View File

@@ -22,6 +22,7 @@ import Role from '@/pages/Role';
import Login from "@/pages/Login"; import Login from "@/pages/Login";
import Work from "@/pages/Work"; import Work from "@/pages/Work";
import Draft from "@/pages/Draft"; import Draft from "@/pages/Draft";
import Decycle from "@/pages/Decycle";
import PageTitle from "../PageTitle"; import PageTitle from "../PageTitle";
@@ -40,6 +41,8 @@ export default () => {
const routesAll = [ const routesAll = [
{ path: "/", title: "仪表盘", component: <Home /> }, { path: "/", title: "仪表盘", component: <Home /> },
{ path: "/create", title: "发挥灵感", component: <Create /> }, { path: "/create", title: "发挥灵感", component: <Create /> },
{ path: "/draft", title: "草稿箱", component: <Draft /> },
{ path: "/recycle", title: "回收站", component: <Decycle /> },
{ path: "/cate", title: "分类管理", component: <Cate /> }, { path: "/cate", title: "分类管理", component: <Cate /> },
{ path: "/article", title: "文章管理", component: <Article /> }, { path: "/article", title: "文章管理", component: <Article /> },
{ path: "/tag", title: "标签管理", component: <Tag /> }, { path: "/tag", title: "标签管理", component: <Tag /> },
@@ -56,7 +59,6 @@ export default () => {
{ path: "/file", title: "文件管理", component: <File /> }, { path: "/file", title: "文件管理", component: <File /> },
{ path: "/iter", title: "项目更新记录", component: <Iterative /> }, { path: "/iter", title: "项目更新记录", component: <Iterative /> },
{ path: "/work", title: "工作台", component: <Work /> }, { path: "/work", title: "工作台", component: <Work /> },
{ path: "/draft", title: "草稿箱", component: <Draft /> },
]; ];
const [routes, setRoutes] = useState<typeof routesAll | null>(null); const [routes, setRoutes] = useState<typeof routesAll | null>(null);
@@ -70,7 +72,6 @@ export default () => {
useEffect(() => { useEffect(() => {
// 如果没有token就跳转到登录页 // 如果没有token就跳转到登录页
if (!store.token) return navigate("/login") if (!store.token) return navigate("/login")
if (store.role.id) getRouteList(store.role.id) if (store.role.id) getRouteList(store.role.id)
}, [store]); }, [store]);

View File

@@ -100,16 +100,27 @@ const Sidebar = ({ sidebarOpen, setSidebarOpen }: SidebarProps) => {
name: "仪表盘" name: "仪表盘"
}, },
{ {
to: "/create", to: "#",
path: "create", path: "write",
icon: <BiEditAlt className='text-[22px]' />, icon: <BiEditAlt className='text-[22px]' />,
name: "创作" name: "创作",
}, subMenu: [
{ {
to: "/draft", to: "/create",
path: "draft", path: "create",
icon: <RiDraftLine className='text-[22px]' />, name: "谱写"
name: "草稿箱" },
{
to: "/draft",
path: "draft",
name: "草稿箱"
},
{
to: "/recycle",
path: "recycle",
name: "回收站"
}
]
}, },
{ {
to: "#", to: "#",

View File

@@ -39,7 +39,8 @@ const ArticlePage = () => {
const delArticleData = async (id: number) => { const delArticleData = async (id: number) => {
setLoading(true); setLoading(true);
await delArticleDataAPI(id); // 普通删除:可从回收站恢复
await delArticleDataAPI(id, true);
await getArticleList(); await getArticleList();
form.resetFields() form.resetFields()
setCurrent(1) setCurrent(1)
@@ -151,6 +152,7 @@ const ArticlePage = () => {
cateIds: values.cateIds, cateIds: values.cateIds,
tagId: values.tagId, tagId: values.tagId,
isDraft: 0, isDraft: 0,
isDel: 0,
startDate: values.createTime && values.createTime[0].valueOf() + '', startDate: values.createTime && values.createTime[0].valueOf() + '',
endDate: values.createTime && values.createTime[1].valueOf() + '' endDate: values.createTime && values.createTime[1].valueOf() + ''
} }

View File

@@ -133,6 +133,7 @@ const PublishForm = ({ data, closeModel }: { data: Article, closeModel: () => vo
content: data.content, content: data.content,
tagIds: tagIds.join(','), tagIds: tagIds.join(','),
isDraft: isDraft ? 1 : 0, isDraft: isDraft ? 1 : 0,
isDel: 0,
config: { config: {
status: values.status, status: values.status,
password: values.password password: values.password

168
src/pages/Decycle/index.tsx Normal file
View File

@@ -0,0 +1,168 @@
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { Table, Button, Tag, notification, Card, Popconfirm, Form } from 'antd';
import { titleSty } from '@/styles/sty'
import Title from '@/components/Title';
import { delArticleDataAPI, getArticleListAPI, reductionArticleDataAPI } from '@/api/Article';
import type { Tag as ArticleTag } from '@/types/app/tag';
import type { Cate } from '@/types/app/cate';
import type { Article, Config } from '@/types/app/article';
import { useWebStore } from '@/stores';
import dayjs from 'dayjs';
export default () => {
const navigate = useNavigate()
const web = useWebStore(state => state.web)
const [current, setCurrent] = useState<number>(1);
const [loading, setLoading] = useState<boolean>(false);
const [articleList, setArticleList] = useState<Article[]>([]);
const [form] = Form.useForm();
const getArticleList = async () => {
setLoading(true);
const { data } = await getArticleListAPI({ query: { isDel: 1 } });
setArticleList(data as Article[]);
setLoading(false);
};
useEffect(() => {
getArticleList()
}, []);
const delArticleData = async (id: number) => {
setLoading(true);
// 严格删除:彻底从数据库删除,无法恢复
await delArticleDataAPI(id);
await getArticleList();
form.resetFields()
setCurrent(1)
notification.success({ message: '🎉 删除文章成功' })
setLoading(false);
};
const reductionArticleData = async (id: number) => {
await reductionArticleDataAPI(id)
navigate("/article")
notification.success({ message: '🎉 还原文章成功' })
}
// 标签颜色
const colors = ['', '#2db7f5', '#87d068', '#f50', '#108ee9'];
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
align: 'center',
width: 100,
},
{
title: '标题',
dataIndex: 'title',
key: 'title',
align: 'center',
width: 300,
render: (text: string, record: Article) => <a href={`${web.url}/article/${record.id}`} target='_blank' className='hover:text-primary line-clamp-1'>{text}</a>,
},
{
title: '摘要',
dataIndex: 'description',
key: 'description',
align: 'center',
width: 350,
render: (text: string) => <div className='line-clamp-2'>{text ? text : '该文章暂未设置文章摘要'}</div>,
},
{
title: '分类',
dataIndex: 'cateList',
key: 'cateList',
align: 'center',
render: (cates: Cate[]) => cates.map((item, index) => <Tag key={item.id} color={colors[index]}>{item.name}</Tag>)
},
{
title: '标签',
dataIndex: 'tagList',
key: 'tagList',
align: 'center',
render: (tags: ArticleTag[]) => tags.map((item, index) => <Tag key={item.id} color={colors[index]}>{item.name}</Tag>)
},
{
title: '浏览量',
dataIndex: 'view',
key: 'view',
align: 'center',
sorter: (a: Article, b: Article) => a.view! - b.view!
},
{
title: '评论数量',
dataIndex: 'comment',
key: 'comment',
align: 'center',
render: (data: string) => <span>{data}</span>,
sorter: (a: Article, b: Article) => a.comment! - b.comment!
},
{
title: '状态',
dataIndex: 'isDel',
key: 'isDel',
align: 'center',
render: (isDel: number) => isDel === 1 && <span></span>
},
{
title: '发布时间',
dataIndex: 'createTime',
key: 'createTime',
align: 'center',
width: 200,
render: (text: string) => dayjs(+text).format('YYYY-MM-DD HH:mm:ss'),
sorter: (a: Article, b: Article) => +a.createTime! - +b.createTime!,
showSorterTooltip: false
},
{
title: '操作',
key: 'action',
fixed: 'right',
align: 'center',
render: (text: string, record: Article) => (
<div className='flex justify-center space-x-2'>
<Button onClick={() => reductionArticleData(record.id!)}></Button>
<Popconfirm title="警告" description="此操作会彻底文章且无法恢复" okText="确定" cancelText="取消" onConfirm={() => delArticleData(record.id!)}>
<Button type="primary" danger></Button>
</Popconfirm>
</div>
),
},
];
return (
<>
<Title value="回收站" />
<Card className={`${titleSty} mt-2 min-h-[calc(100vh-250px)]`}>
<Table
rowKey="id"
dataSource={articleList}
columns={columns as any}
loading={loading}
scroll={{ x: 'max-content' }}
pagination={{
position: ['bottomCenter'],
current,
defaultPageSize: 8,
onChange(current) {
setCurrent(current)
}
}}
/>
</Card>
</>
);
};

View File

@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { Table, Button, Tag, notification, Card, Popconfirm, Form, Input, Cascader, Select, DatePicker } from 'antd'; import { Table, Button, Tag, notification, Card, Popconfirm, Form } from 'antd';
import { titleSty } from '@/styles/sty' import { titleSty } from '@/styles/sty'
import Title from '@/components/Title'; import Title from '@/components/Title';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';

View File

@@ -24,6 +24,7 @@ export interface Article {
comment?: number, comment?: number,
config: Config, config: Config,
isDraft: number, isDraft: number,
isDel: number,
createTime?: string, createTime?: string,
} }
@@ -37,5 +38,6 @@ export interface FilterForm {
export interface FilterArticle extends FilterData { export interface FilterArticle extends FilterData {
cateIds?: number[], cateIds?: number[],
tagId?: number, tagId?: number,
isDraft: number isDraft?: number,
isDel?: number
} }