功能:完成文章回收站功能
This commit is contained in:
@@ -6,8 +6,12 @@ export const addArticleDataAPI = (data: Article) =>
|
||||
Request<Article>("POST", "/article", { data });
|
||||
|
||||
// 删除文章
|
||||
export const delArticleDataAPI = (id: number) =>
|
||||
Request<Article>("DELETE", `/article/${id}`);
|
||||
export const delArticleDataAPI = (id: number, isDel?: boolean) =>
|
||||
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) =>
|
||||
|
||||
@@ -22,6 +22,7 @@ import Role from '@/pages/Role';
|
||||
import Login from "@/pages/Login";
|
||||
import Work from "@/pages/Work";
|
||||
import Draft from "@/pages/Draft";
|
||||
import Decycle from "@/pages/Decycle";
|
||||
|
||||
import PageTitle from "../PageTitle";
|
||||
|
||||
@@ -40,6 +41,8 @@ export default () => {
|
||||
const routesAll = [
|
||||
{ path: "/", title: "仪表盘", component: <Home /> },
|
||||
{ path: "/create", title: "发挥灵感", component: <Create /> },
|
||||
{ path: "/draft", title: "草稿箱", component: <Draft /> },
|
||||
{ path: "/recycle", title: "回收站", component: <Decycle /> },
|
||||
{ path: "/cate", title: "分类管理", component: <Cate /> },
|
||||
{ path: "/article", title: "文章管理", component: <Article /> },
|
||||
{ path: "/tag", title: "标签管理", component: <Tag /> },
|
||||
@@ -56,7 +59,6 @@ export default () => {
|
||||
{ path: "/file", title: "文件管理", component: <File /> },
|
||||
{ path: "/iter", title: "项目更新记录", component: <Iterative /> },
|
||||
{ path: "/work", title: "工作台", component: <Work /> },
|
||||
{ path: "/draft", title: "草稿箱", component: <Draft /> },
|
||||
];
|
||||
|
||||
const [routes, setRoutes] = useState<typeof routesAll | null>(null);
|
||||
@@ -70,7 +72,6 @@ export default () => {
|
||||
useEffect(() => {
|
||||
// 如果没有token就跳转到登录页
|
||||
if (!store.token) return navigate("/login")
|
||||
|
||||
if (store.role.id) getRouteList(store.role.id)
|
||||
}, [store]);
|
||||
|
||||
|
||||
@@ -99,18 +99,29 @@ const Sidebar = ({ sidebarOpen, setSidebarOpen }: SidebarProps) => {
|
||||
icon: <BiHomeSmile className='text-[22px]' />,
|
||||
name: "仪表盘"
|
||||
},
|
||||
{
|
||||
to: "#",
|
||||
path: "write",
|
||||
icon: <BiEditAlt className='text-[22px]' />,
|
||||
name: "创作",
|
||||
subMenu: [
|
||||
{
|
||||
to: "/create",
|
||||
path: "create",
|
||||
icon: <BiEditAlt className='text-[22px]' />,
|
||||
name: "创作"
|
||||
name: "谱写"
|
||||
},
|
||||
{
|
||||
to: "/draft",
|
||||
path: "draft",
|
||||
icon: <RiDraftLine className='text-[22px]' />,
|
||||
name: "草稿箱"
|
||||
},
|
||||
{
|
||||
to: "/recycle",
|
||||
path: "recycle",
|
||||
name: "回收站"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
to: "#",
|
||||
path: "manage",
|
||||
|
||||
@@ -39,7 +39,8 @@ const ArticlePage = () => {
|
||||
const delArticleData = async (id: number) => {
|
||||
setLoading(true);
|
||||
|
||||
await delArticleDataAPI(id);
|
||||
// 普通删除:可从回收站恢复
|
||||
await delArticleDataAPI(id, true);
|
||||
await getArticleList();
|
||||
form.resetFields()
|
||||
setCurrent(1)
|
||||
@@ -151,6 +152,7 @@ const ArticlePage = () => {
|
||||
cateIds: values.cateIds,
|
||||
tagId: values.tagId,
|
||||
isDraft: 0,
|
||||
isDel: 0,
|
||||
startDate: values.createTime && values.createTime[0].valueOf() + '',
|
||||
endDate: values.createTime && values.createTime[1].valueOf() + ''
|
||||
}
|
||||
|
||||
@@ -133,6 +133,7 @@ const PublishForm = ({ data, closeModel }: { data: Article, closeModel: () => vo
|
||||
content: data.content,
|
||||
tagIds: tagIds.join(','),
|
||||
isDraft: isDraft ? 1 : 0,
|
||||
isDel: 0,
|
||||
config: {
|
||||
status: values.status,
|
||||
password: values.password
|
||||
|
||||
168
src/pages/Decycle/index.tsx
Normal file
168
src/pages/Decycle/index.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
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 Title from '@/components/Title';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
4
src/types/app/article.d.ts
vendored
4
src/types/app/article.d.ts
vendored
@@ -24,6 +24,7 @@ export interface Article {
|
||||
comment?: number,
|
||||
config: Config,
|
||||
isDraft: number,
|
||||
isDel: number,
|
||||
createTime?: string,
|
||||
}
|
||||
|
||||
@@ -37,5 +38,6 @@ export interface FilterForm {
|
||||
export interface FilterArticle extends FilterData {
|
||||
cateIds?: number[],
|
||||
tagId?: number,
|
||||
isDraft: number
|
||||
isDraft?: number,
|
||||
isDel?: number
|
||||
}
|
||||
Reference in New Issue
Block a user