功能:完成文章回收站功能
This commit is contained in:
@@ -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) =>
|
||||||
|
|||||||
@@ -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]);
|
||||||
|
|
||||||
|
|||||||
@@ -99,18 +99,29 @@ const Sidebar = ({ sidebarOpen, setSidebarOpen }: SidebarProps) => {
|
|||||||
icon: <BiHomeSmile className='text-[22px]' />,
|
icon: <BiHomeSmile className='text-[22px]' />,
|
||||||
name: "仪表盘"
|
name: "仪表盘"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
to: "#",
|
||||||
|
path: "write",
|
||||||
|
icon: <BiEditAlt className='text-[22px]' />,
|
||||||
|
name: "创作",
|
||||||
|
subMenu: [
|
||||||
{
|
{
|
||||||
to: "/create",
|
to: "/create",
|
||||||
path: "create",
|
path: "create",
|
||||||
icon: <BiEditAlt className='text-[22px]' />,
|
name: "谱写"
|
||||||
name: "创作"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: "/draft",
|
to: "/draft",
|
||||||
path: "draft",
|
path: "draft",
|
||||||
icon: <RiDraftLine className='text-[22px]' />,
|
|
||||||
name: "草稿箱"
|
name: "草稿箱"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
to: "/recycle",
|
||||||
|
path: "recycle",
|
||||||
|
name: "回收站"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
to: "#",
|
to: "#",
|
||||||
path: "manage",
|
path: "manage",
|
||||||
|
|||||||
@@ -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() + ''
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
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 { 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';
|
||||||
|
|||||||
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,
|
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
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user