功能:完成文章草稿箱功能

This commit is contained in:
宇阳
2024-11-25 14:39:18 +08:00
parent 37b6891f86
commit 5f28411fc4
8 changed files with 195 additions and 29 deletions

View File

@@ -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,

View File

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

View File

@@ -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",

View File

@@ -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() + ''
}

View File

@@ -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>
</>
);

View File

@@ -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
View 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>
</>
);
};

View File

@@ -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
}