功能:完成文章草稿箱功能
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import Request from "@/utils/request";
|
||||
import { Article } from "@/types/app/article";
|
||||
import { Article, FilterArticle } from "@/types/app/article";
|
||||
|
||||
// 新增文章
|
||||
export const addArticleDataAPI = (data: Article) =>
|
||||
@@ -17,7 +17,7 @@ export const editArticleDataAPI = (data: Article) =>
|
||||
export const getArticleDataAPI = (id?: number) => Request<Article>("GET", `/article/${id}`)
|
||||
|
||||
// 获取文章列表
|
||||
export const getArticleListAPI = (data?: QueryData) => Request<Article[]>("POST", `/article/list`, {
|
||||
export const getArticleListAPI = (data?: QueryData<FilterArticle>) => Request<Article[]>("POST", `/article/list`, {
|
||||
data: { ...data?.query },
|
||||
params: {
|
||||
sort: data?.sort,
|
||||
|
||||
@@ -21,6 +21,7 @@ import Page from '@/pages/Route';
|
||||
import Role from '@/pages/Role';
|
||||
import Login from "@/pages/Login";
|
||||
import Work from "@/pages/Work";
|
||||
import Draft from "@/pages/Draft";
|
||||
|
||||
import PageTitle from "../PageTitle";
|
||||
|
||||
@@ -55,6 +56,7 @@ 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);
|
||||
|
||||
@@ -2,9 +2,10 @@ import React, { useEffect, useRef, useState } from 'react';
|
||||
import { NavLink, useLocation } from 'react-router-dom';
|
||||
import SidebarLinkGroup from './SidebarLinkGroup';
|
||||
|
||||
import { BiEditAlt, BiFolderOpen, BiHomeSmile, BiSliderAlt, BiShieldQuarter, BiLineChart, BiCategoryAlt, BiBug } from "react-icons/bi";
|
||||
import { BiEditAlt, BiFolderOpen, BiHomeSmile, BiSliderAlt, BiShieldQuarter, BiCategoryAlt, BiBug } from "react-icons/bi";
|
||||
import { LiaRssSolid } from "react-icons/lia";
|
||||
import { TbBrandAirtable } from "react-icons/tb";
|
||||
import { RiDraftLine } from "react-icons/ri";
|
||||
|
||||
import { useUserStore } from '@/stores';
|
||||
import { getRouteListAPI } from '@/api/Role'
|
||||
@@ -104,6 +105,12 @@ const Sidebar = ({ sidebarOpen, setSidebarOpen }: SidebarProps) => {
|
||||
icon: <BiEditAlt className='text-[22px]' />,
|
||||
name: "创作"
|
||||
},
|
||||
{
|
||||
to: "/draft",
|
||||
path: "draft",
|
||||
icon: <RiDraftLine className='text-[22px]' />,
|
||||
name: "草稿箱"
|
||||
},
|
||||
{
|
||||
to: "#",
|
||||
path: "manage",
|
||||
|
||||
@@ -132,9 +132,9 @@ const ArticlePage = () => {
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
render: (text: string, record: Article) => (
|
||||
<div className='flex space-x-2'>
|
||||
<div className='flex justify-center space-x-2'>
|
||||
<Link to={`/create?id=${record.id}`}>
|
||||
<Button>修改</Button>
|
||||
<Button>编辑</Button>
|
||||
</Link>
|
||||
|
||||
<Popconfirm title="警告" description="你确定要删除吗" okText="确定" cancelText="取消" onConfirm={() => delArticleData(record.id!)}>
|
||||
@@ -150,6 +150,7 @@ const ArticlePage = () => {
|
||||
key: values.title,
|
||||
cateIds: values.cateIds,
|
||||
tagId: values.tagId,
|
||||
isDraft: 0,
|
||||
startDate: values.createTime && values.createTime[0].valueOf() + '',
|
||||
endDate: values.createTime && values.createTime[1].valueOf() + ''
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ interface FieldType {
|
||||
const PublishForm = ({ data, closeModel }: { data: Article, closeModel: () => void }) => {
|
||||
const [params] = useSearchParams()
|
||||
const id = +params.get('id')!
|
||||
const isDraftParams = Boolean(params.get('draft'))
|
||||
|
||||
const [btnLoading, setBtnLoading] = useState(false)
|
||||
|
||||
@@ -51,7 +52,7 @@ const PublishForm = ({ data, closeModel }: { data: Article, closeModel: () => vo
|
||||
}
|
||||
});
|
||||
|
||||
const tagIds = data.tagList.map(item => item.id)
|
||||
const tagIds = data.tagList!.map(item => item.id)
|
||||
|
||||
form.setFieldsValue({
|
||||
...data,
|
||||
@@ -83,7 +84,7 @@ const PublishForm = ({ data, closeModel }: { data: Article, closeModel: () => vo
|
||||
return !value || /^(https?:\/\/)/.test(value) ? Promise.resolve() : Promise.reject(new Error('请输入有效的封面链接'));
|
||||
};
|
||||
|
||||
const onSubmit: FormProps<FieldType>['onFinish'] = async (values) => {
|
||||
const onSubmit = async (values: FieldType, isDraft?: boolean) => {
|
||||
setBtnLoading(true)
|
||||
|
||||
// 如果是文章标签,则先判断是否存在,如果不存在则添加
|
||||
@@ -112,7 +113,7 @@ const PublishForm = ({ data, closeModel }: { data: Article, closeModel: () => vo
|
||||
values.createTime = values.createTime.valueOf()
|
||||
values.cateIds = [...new Set(values.cateIds?.flat())]
|
||||
|
||||
if (id) {
|
||||
if (id && !isDraftParams) {
|
||||
await editArticleDataAPI({
|
||||
id,
|
||||
...values,
|
||||
@@ -120,32 +121,48 @@ const PublishForm = ({ data, closeModel }: { data: Article, closeModel: () => vo
|
||||
tagIds: tagIds.join(','),
|
||||
config: {
|
||||
status: values.status,
|
||||
top: values.top ? 1 : 0,
|
||||
password: values.password
|
||||
}
|
||||
} as any)
|
||||
message.success("🎉 编辑成功")
|
||||
} else {
|
||||
await addArticleDataAPI({
|
||||
id,
|
||||
...values,
|
||||
content: data.content,
|
||||
tagIds: tagIds.join(','),
|
||||
config: {
|
||||
status: values.status,
|
||||
top: values.top ? 1 : 0,
|
||||
password: values.password
|
||||
}
|
||||
} as any)
|
||||
message.success("🎉 发布成功")
|
||||
if (!isDraftParams) {
|
||||
await addArticleDataAPI({
|
||||
id,
|
||||
...values,
|
||||
content: data.content,
|
||||
tagIds: tagIds.join(','),
|
||||
isDraft: isDraft ? 1 : 0,
|
||||
config: {
|
||||
status: values.status,
|
||||
password: values.password
|
||||
},
|
||||
createTime: values.createTime.toString()
|
||||
})
|
||||
|
||||
isDraft ? message.success("🎉 保存为草稿成功") : message.success("🎉 发布成功")
|
||||
} else {
|
||||
// 修改草稿状态为发布文章
|
||||
await editArticleDataAPI({
|
||||
id,
|
||||
...values,
|
||||
content: data.content,
|
||||
tagIds: tagIds.join(','),
|
||||
isDraft: 0,
|
||||
config: {
|
||||
status: values.status,
|
||||
password: values.password
|
||||
}
|
||||
} as any)
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭弹框
|
||||
closeModel()
|
||||
// 清除本地持久化的数据
|
||||
localStorage.removeItem('article_content')
|
||||
// 跳转到文章页
|
||||
navigate("/article")
|
||||
// 如果是草稿就跳转到草稿页,否则文章页
|
||||
isDraft ? navigate("/draft") : navigate("/article")
|
||||
// 初始化表单
|
||||
form.resetFields()
|
||||
|
||||
@@ -226,9 +243,16 @@ const PublishForm = ({ data, closeModel }: { data: Article, closeModel: () => vo
|
||||
<Input.Password placeholder="请输入访问密码" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit" loading={btnLoading} className="w-full">{id ? "编辑文章" : "发布文章"}</Button>
|
||||
<Form.Item className="!mb-0">
|
||||
<Button type="primary" htmlType="submit" loading={btnLoading} className="w-full">{(id && !isDraftParams) ? "编辑文章" : "发布文章"}</Button>
|
||||
</Form.Item>
|
||||
|
||||
{/* 草稿和编辑状态下不再显示保存草稿按钮 */}
|
||||
{(!isDraftParams && !id) && (
|
||||
<Form.Item className="!mt-2 !mb-0">
|
||||
<Button className="w-full" onClick={() => form.validateFields().then(values => onSubmit(values, true))}>保存为草稿</Button>
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -15,6 +15,7 @@ import { AiOutlineEdit, AiOutlineSend } from 'react-icons/ai';
|
||||
const CreatePage = () => {
|
||||
const [params] = useSearchParams()
|
||||
const id = +params.get('id')!
|
||||
const isDraftParams = Boolean(params.get('draft'))
|
||||
|
||||
const [data, setData] = useState<Article>({} as Article)
|
||||
const [content, setContent] = useState('');
|
||||
@@ -178,7 +179,7 @@ const CreatePage = () => {
|
||||
<Editor value={content} onChange={(value) => setContent(value)} />
|
||||
|
||||
<Drawer
|
||||
title={id ? "编辑文章" : "发布文章"}
|
||||
title={(id && !isDraftParams) ? "编辑文章" : "发布文章"}
|
||||
placement="right"
|
||||
size='large'
|
||||
onClose={() => setPublishOpen(false)}
|
||||
|
||||
129
src/pages/Draft/index.tsx
Normal file
129
src/pages/Draft/index.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Table, Button, Tag, notification, Card, Popconfirm, Form, Input, Cascader, Select, DatePicker } from 'antd';
|
||||
import { titleSty } from '@/styles/sty'
|
||||
import Title from '@/components/Title';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { delArticleDataAPI, getArticleListAPI } from '@/api/Article';
|
||||
import type { Tag as ArticleTag } from '@/types/app/tag';
|
||||
import type { Cate } from '@/types/app/cate';
|
||||
import type { Article } from '@/types/app/article';
|
||||
|
||||
import { useWebStore } from '@/stores';
|
||||
|
||||
export default () => {
|
||||
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: { isDraft: 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 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: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
render: (text: string, record: Article) => (
|
||||
<div className='flex justify-center space-x-2'>
|
||||
<Link to={`/create?id=${record.id}&draft=true`}>
|
||||
<Button>编辑</Button>
|
||||
</Link>
|
||||
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
8
src/types/app/article.d.ts
vendored
8
src/types/app/article.d.ts
vendored
@@ -7,7 +7,7 @@ export interface Config {
|
||||
id?: number,
|
||||
articleId?: number,
|
||||
status: Status,
|
||||
password:string
|
||||
password: string
|
||||
}
|
||||
|
||||
export interface Article {
|
||||
@@ -17,12 +17,13 @@ export interface Article {
|
||||
content: string,
|
||||
cover: string,
|
||||
cateIds: number[],
|
||||
cateList: Cate[]
|
||||
cateList?: Cate[]
|
||||
tagIds: string,
|
||||
tagList: Tag[]
|
||||
tagList?: Tag[]
|
||||
view?: number
|
||||
comment?: number,
|
||||
config: Config,
|
||||
isDraft: number,
|
||||
createTime?: string,
|
||||
}
|
||||
|
||||
@@ -36,4 +37,5 @@ export interface FilterForm {
|
||||
export interface FilterArticle extends FilterData {
|
||||
cateIds?: number[],
|
||||
tagId?: number,
|
||||
isDraft: number
|
||||
}
|
||||
Reference in New Issue
Block a user