From 7f01194c616fde746ab97ebb21ea2fa9d6abf15d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=87=E9=98=B3?= Date: Sun, 12 Jan 2025 00:00:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=A7=E6=94=B9=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Article/index.tsx | 67 ++++++----- src/pages/Cate/index.tsx | 32 ++--- src/pages/Comment/index.tsx | 49 ++++---- .../Create/components/PublishForm/index.tsx | 28 +++-- src/pages/Create/index.tsx | 102 ++++++++-------- src/pages/CreateRecord/index.tsx | 22 ++-- src/pages/Work/components/List/index.tsx | 113 +++++++++++------- src/pages/Work/index.tsx | 43 +++---- 8 files changed, 259 insertions(+), 197 deletions(-) diff --git a/src/pages/Article/index.tsx b/src/pages/Article/index.tsx index c5830b0..67b5c45 100644 --- a/src/pages/Article/index.tsx +++ b/src/pages/Article/index.tsx @@ -15,27 +15,29 @@ import { useWebStore } from '@/stores'; import dayjs from 'dayjs'; -const ArticlePage = () => { +export default () => { + const [loading, setLoading] = useState(false); + + const [form] = Form.useForm(); const web = useWebStore(state => state.web) const [current, setCurrent] = useState(1); - const [loading, setLoading] = useState(false); const [articleList, setArticleList] = useState([]); - const [form] = Form.useForm(); const { RangePicker } = DatePicker; const getArticleList = async () => { - setLoading(true); - const { data } = await getArticleListAPI(); - setArticleList(data as Article[]); + try { + const { data } = await getArticleListAPI(); + setArticleList(data); + setLoading(false); + } catch (error) { + setLoading(false); + } + setLoading(false); }; - useEffect(() => { - getArticleList() - }, []); - const delArticleData = async (id: number) => { setLoading(true); @@ -46,7 +48,6 @@ const ArticlePage = () => { form.resetFields() setCurrent(1) notification.success({ message: '🎉 删除文章成功' }) - setLoading(false); } catch (error) { setLoading(false); } @@ -149,19 +150,27 @@ const ArticlePage = () => { }, ]; - const onSubmit = async (values: FilterForm) => { - const query: FilterArticle = { - key: values.title, - cateIds: values.cateIds, - tagId: values.tagId, - isDraft: 0, - isDel: 0, - startDate: values.createTime && values.createTime[0].valueOf() + '', - endDate: values.createTime && values.createTime[1].valueOf() + '' + const onFilterSubmit = async (values: FilterForm) => { + setLoading(true) + + try { + const query: FilterArticle = { + key: values.title, + cateIds: values.cateIds, + tagId: values.tagId, + isDraft: 0, + isDel: 0, + startDate: values.createTime && values.createTime[0].valueOf() + '', + endDate: values.createTime && values.createTime[1].valueOf() + '' + } + + const { data } = await getArticleListAPI({ query }); + setArticleList(data); + } catch (error) { + setLoading(false) } - const { data } = await getArticleListAPI({ query }); - setArticleList(data as Article[]); + setLoading(false) } const [cateList, setCateList] = useState([]) @@ -178,16 +187,18 @@ const ArticlePage = () => { } useEffect(() => { + setLoading(true); + getArticleList() getCateList() getTagList() }, []) return ( - <> +
<Card className='my-2 overflow-scroll'> - <Form form={form} layout="inline" onFinish={onSubmit} autoComplete="off" className='flex-nowrap'> + <Form form={form} layout="inline" onFinish={onFilterSubmit} autoComplete="off" className='flex-nowrap'> <Form.Item label="标题" name="title" className='min-w-[200px]'> <Input placeholder='请输入关键词' /> </Form.Item> @@ -225,7 +236,6 @@ const ArticlePage = () => { rowKey="id" dataSource={articleList} columns={columns as any} - loading={loading} scroll={{ x: 'max-content' }} pagination={{ position: ['bottomCenter'], @@ -235,10 +245,9 @@ const ArticlePage = () => { setCurrent(current) } }} + loading={loading} /> </Card> - </> + </div> ); -}; - -export default ArticlePage; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/pages/Cate/index.tsx b/src/pages/Cate/index.tsx index 6cc4fc2..9b86a9f 100644 --- a/src/pages/Cate/index.tsx +++ b/src/pages/Cate/index.tsx @@ -6,9 +6,10 @@ import { Form, Input, Button, Tree, Modal, Spin, Dropdown, Card, MenuProps, Popc import Title from '@/components/Title'; import "./index.scss" -const CatePage = () => { +export default () => { const [loading, setLoading] = useState(false); const [btnLoading, setBtnLoading] = useState(false) + const [editLoading, setEditLoading] = useState(false) const [isModelOpen, setIsModelOpen] = useState(false); const [cate, setCate] = useState<Cate>({} as Cate); @@ -41,19 +42,22 @@ const CatePage = () => { }; const editCateData = async (id: number) => { - setLoading(true); - setIsMethod("edit") - setIsModelOpen(true); + setEditLoading(true); try { + setIsMethod("edit") + setIsModelOpen(true); const { data } = await getCateDataAPI(id); - setIsCateShow(data.type === "cate" ? false : true) setCate(data); - form.setFieldsValue(data); + + // 判断是分类还是导航 + setIsCateShow(data.type === "cate" ? false : true) } catch (error) { - setLoading(false); + setEditLoading(false); } + + setEditLoading(false); }; const delCateData = async (id: number) => { @@ -61,8 +65,8 @@ const CatePage = () => { try { await delCateDataAPI(id); + await getCateList(); message.success('🎉 删除分类成功'); - getCateList(); } catch (error) { setLoading(false); } @@ -83,17 +87,19 @@ const CatePage = () => { message.success('🎉 新增分类成功'); } + await getCateList(); + // 初始化表单状态 form.resetFields(); setCate({} as Cate); - setIsModelOpen(false); - getCateList(); setIsMethod("create") }) } catch (error) { setBtnLoading(false) } + + setBtnLoading(false) }; const closeModel = () => { @@ -153,7 +159,7 @@ const CatePage = () => { <Tree defaultExpandAll={true} treeData={treeData(list)} /> </Spin> - <Modal title={isMethod === "edit" ? "编辑分类" : "新增分类"} open={isModelOpen} onCancel={closeModel} destroyOnClose footer={null}> + <Modal loading={editLoading} title={isMethod === "edit" ? "编辑分类" : "新增分类"} open={isModelOpen} onCancel={closeModel} destroyOnClose footer={null}> <Form form={form} layout="vertical" initialValues={cate} size='large' preserve={false} className='mt-6'> <Form.Item label="名称" name="name" rules={[{ required: true, message: '分类名称不能为空' }]}> <Input placeholder="请输入分类名称" /> @@ -195,6 +201,4 @@ const CatePage = () => { </Card> </> ); -}; - -export default CatePage; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/pages/Comment/index.tsx b/src/pages/Comment/index.tsx index a8b597a..719cc99 100644 --- a/src/pages/Comment/index.tsx +++ b/src/pages/Comment/index.tsx @@ -15,11 +15,12 @@ import { useWebStore, useUserStore } from '@/stores' import dayjs from 'dayjs'; -const CommentPage = () => { +export default () => { + const [loading, setLoading] = useState(false); + const web = useWebStore(state => state.web) const user = useUserStore(state => state.user) - const [loading, setLoading] = useState(false); const [btnLoading, setBtnLoading] = useState(false); const [comment, setComment] = useState<Comment>({} as Comment); @@ -28,21 +29,15 @@ const CommentPage = () => { const [isCommentModalOpen, setIsCommentModalOpen] = useState(false); const getCommentList = async () => { - const { data } = await getCommentListAPI(); - setList(data) - setLoading(false) - } - - const delCommentData = async (id: number) => { - setLoading(true) try { - await delCommentDataAPI(id); - getCommentList(); - message.success('🎉 删除评论成功'); + const { data } = await getCommentListAPI(); + setList(data) } catch (error) { setLoading(false) } - }; + + setLoading(false) + } useEffect(() => { setLoading(true) @@ -119,6 +114,18 @@ const CommentPage = () => { const { RangePicker } = DatePicker; + const delCommentData = async (id: number) => { + setLoading(true) + + try { + await delCommentDataAPI(id); + await getCommentList(); + message.success('🎉 删除评论成功'); + } catch (error) { + setLoading(false) + } + }; + const onSubmit = async (values: FilterForm) => { setLoading(true) @@ -135,6 +142,8 @@ const CommentPage = () => { } catch (error) { setLoading(false) } + + setLoading(false) } // 回复内容 @@ -156,18 +165,17 @@ const CommentPage = () => { createTime: new Date().getTime().toString(), }) + await getCommentList() message.success('🎉 回复评论成功'); - setIsReplyModalOpen(false) setReplyInfo("") - getCommentList() } catch (error) { setBtnLoading(false) } } return ( - <> + <div> <Title value='评论管理' /> <Card className='my-2 overflow-scroll'> @@ -195,13 +203,13 @@ const CommentPage = () => { rowKey="id" dataSource={list} columns={columns} - loading={loading} expandable={{ defaultExpandAllRows: true }} scroll={{ x: 'max-content' }} pagination={{ position: ['bottomCenter'], defaultPageSize: 8, }} + loading={loading} /> </Card> @@ -231,9 +239,6 @@ const CommentPage = () => { <Button type="primary" loading={btnLoading} onClick={handleReply} className="w-full mt-2">确定</Button> </div> </Modal> - </> + </div> ); -}; - -export default CommentPage; - +}; \ No newline at end of file diff --git a/src/pages/Create/components/PublishForm/index.tsx b/src/pages/Create/components/PublishForm/index.tsx index 62d4857..2062a64 100644 --- a/src/pages/Create/components/PublishForm/index.tsx +++ b/src/pages/Create/components/PublishForm/index.tsx @@ -15,6 +15,11 @@ import { Article, Status } from "@/types/app/article"; import dayjs from 'dayjs'; +interface Props { + data: Article, + closeModel: () => void +} + interface FieldType { title: string, createTime: number; @@ -28,7 +33,7 @@ interface FieldType { password: string } -const PublishForm = ({ data, closeModel }: { data: Article, closeModel: () => void }) => { +const PublishForm = ({ data, closeModel }: Props) => { const [params] = useSearchParams() const id = +params.get('id')! const isDraftParams = Boolean(params.get('draft')) @@ -167,18 +172,19 @@ const PublishForm = ({ data, closeModel }: { data: Article, closeModel: () => vo } as any) } } + + // 关闭弹框 + closeModel() + // 清除本地持久化的数据 + localStorage.removeItem('article_content') + // 如果是草稿就跳转到草稿页,否则文章页 + isDraft ? navigate("/draft") : navigate("/article") + // 初始化表单 + form.resetFields() } catch (error) { setBtnLoading(false) } - // 关闭弹框 - closeModel() - // 清除本地持久化的数据 - localStorage.removeItem('article_content') - // 如果是草稿就跳转到草稿页,否则文章页 - isDraft ? navigate("/draft") : navigate("/article") - // 初始化表单 - form.resetFields() setBtnLoading(false) } @@ -191,7 +197,7 @@ const PublishForm = ({ data, closeModel }: { data: Article, closeModel: () => vo } return ( - <> + <div> <Form form={form} name="basic" @@ -277,7 +283,7 @@ const PublishForm = ({ data, closeModel }: { data: Article, closeModel: () => vo </Form.Item> )} </Form> - </> + </div> ); }; diff --git a/src/pages/Create/index.tsx b/src/pages/Create/index.tsx index 96de887..d642a71 100644 --- a/src/pages/Create/index.tsx +++ b/src/pages/Create/index.tsx @@ -13,13 +13,13 @@ import { BiSave } from "react-icons/bi"; import { AiOutlineEdit, AiOutlineSend } from 'react-icons/ai'; import { titleSty } from '@/styles/sty'; -const CreatePage = () => { +export default () => { + const [loading, setLoading] = useState(false) + const [params] = useSearchParams() const id = +params.get('id')! const isDraftParams = Boolean(params.get('draft')) - const [loading, setLoading] = useState(false) - const [data, setData] = useState<Article>({} as Article) const [content, setContent] = useState(''); const [publishOpen, setPublishOpen] = useState(false) @@ -38,6 +38,8 @@ const CreatePage = () => { } catch (error) { setLoading(false) } + + setLoading(false) } // 回显数据 @@ -91,53 +93,61 @@ const CreatePage = () => { // 解析接口数据 const parsingData = async (command: string) => { - const res = await fetch(`/ai/v1/chat/completions`, { - method: "POST", - headers: { - "Content-Type": "application/json", - "Authorization": `Bearer ${import.meta.env.VITE_AI_APIPassword}` - }, - body: JSON.stringify({ - model: import.meta.env.VITE_AI_MODEL, - messages: [{ - role: "user", - content: `${command}${content}` - }], - stream: true - }) - }); + setLoading(true) - const reader = res.body.getReader(); - const decoder = new TextDecoder("utf-8"); + try { + const res = await fetch(`/ai/v1/chat/completions`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${import.meta.env.VITE_AI_APIPassword}` + }, + body: JSON.stringify({ + model: import.meta.env.VITE_AI_MODEL, + messages: [{ + role: "user", + content: `${command}${content}` + }], + stream: true + }) + }); - let receivedText = ""; + const reader = res.body.getReader(); + const decoder = new TextDecoder("utf-8"); - while (true) { - const { done, value } = await reader.read(); - if (done) break; - receivedText += decoder.decode(value, { stream: true }); + let receivedText = ""; - // 处理每一块数据 - const lines = receivedText.split("\n"); - for (let i = 0; i < lines.length - 1; i++) { - const line = lines[i].trim(); - if (line.startsWith("data:")) { - const jsonString = line.substring(5).trim(); - if (jsonString !== "[DONE]") { - const data = JSON.parse(jsonString); - console.log("Received chunk:", data.choices[0].delta.content); - setContent((content) => content + data.choices[0].delta.content); - // 在这里处理每一块数据 - } else { - console.log("Stream finished."); - return; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + receivedText += decoder.decode(value, { stream: true }); + + // 处理每一块数据 + const lines = receivedText.split("\n"); + for (let i = 0; i < lines.length - 1; i++) { + const line = lines[i].trim(); + if (line.startsWith("data:")) { + const jsonString = line.substring(5).trim(); + if (jsonString !== "[DONE]") { + const data = JSON.parse(jsonString); + console.log("Received chunk:", data.choices[0].delta.content); + setContent((content) => content + data.choices[0].delta.content); + // 在这里处理每一块数据 + } else { + console.log("Stream finished."); + return; + } } } - } - // 保留最后一行未处理的数据 - receivedText = lines[lines.length - 1]; + // 保留最后一行未处理的数据 + receivedText = lines[lines.length - 1]; + } + } catch (error) { + setLoading(false) } + + setLoading(false) } // AI功能 @@ -166,7 +176,7 @@ const CreatePage = () => { ]; return ( - <> + <div> <Title value="创作"> <div className='flex items-center space-x-4 w-[360px]'> <Dropdown.Button menu={{ items }}> @@ -196,8 +206,6 @@ const CreatePage = () => { <PublishForm data={data} closeModel={() => setPublishOpen(false)} /> </Drawer> </Card > - </> + </div> ); -}; - -export default CreatePage; +}; \ No newline at end of file diff --git a/src/pages/CreateRecord/index.tsx b/src/pages/CreateRecord/index.tsx index bec6a06..151327c 100644 --- a/src/pages/CreateRecord/index.tsx +++ b/src/pages/CreateRecord/index.tsx @@ -15,12 +15,12 @@ import { LuImagePlus } from "react-icons/lu"; import { RiDeleteBinLine } from "react-icons/ri"; export default () => { + const [loading, setLoading] = useState(false) + const [params] = useSearchParams() const id = +params.get('id')! const navigate = useNavigate() - const [loading, setLoading] = useState(false) - const [content, setContent] = useState("") const [imageList, setImageList] = useState<string[]>([]) @@ -61,17 +61,20 @@ export default () => { } const getRecordData = async () => { - setLoading(true) - - const { data } = await getRecordDataAPI(id) - setContent(data.content) - setImageList(JSON.parse(data.images as string)) + try { + const { data } = await getRecordDataAPI(id) + setContent(data.content) + setImageList(JSON.parse(data.images as string)) + } catch (error) { + setLoading(false) + } setLoading(false) } // 回显数据 useEffect(() => { + setLoading(true) // 有Id就回显指定的数据 if (id) getRecordData() }, [id]) @@ -130,7 +133,7 @@ export default () => { }; return ( - <> + <div> <Title value="闪念" /> <Spin spinning={loading}> @@ -172,6 +175,7 @@ export default () => { type="primary" size="large" icon={<BiLogoTelegram className="text-xl" />} + loading={loading} className="absolute bottom-4 right-4" onClick={onSubmit} /> @@ -188,6 +192,6 @@ export default () => { }} onCancel={() => setIsModalOpen(false)} /> - </> + </div> ) } \ No newline at end of file diff --git a/src/pages/Work/components/List/index.tsx b/src/pages/Work/components/List/index.tsx index 726e873..8677443 100644 --- a/src/pages/Work/components/List/index.tsx +++ b/src/pages/Work/components/List/index.tsx @@ -17,9 +17,12 @@ interface ListItemProps { item: any; type: Menu; fetchData: (type: Menu) => void; + setLoading: (loading: boolean) => void; } -export default ({ item, type, fetchData }: ListItemProps) => { +export default ({ item, type, fetchData, setLoading }: ListItemProps) => { + const [btnLoading, setBtnLoading] = useState<boolean>(false) + const web = useWebStore(state => state.web) const user = useUserStore(state => state.user) @@ -27,64 +30,86 @@ export default ({ item, type, fetchData }: ListItemProps) => { // 通过 const handleApproval = async () => { - if (type === "link") { - await auditWebDataAPI(item.id); - } else if (type === "comment") { - await auditCommentDataAPI(item.id); - } else if (type === "wall") { - await auditWallDataAPI(item.id); - } + setLoading(true) - btnType != "reply" && message.success('🎉 审核成功'); - fetchData(type); + try { + if (type === "link") { + await auditWebDataAPI(item.id); + } else if (type === "comment") { + await auditCommentDataAPI(item.id); + } else if (type === "wall") { + await auditWallDataAPI(item.id); + } + + await fetchData(type); + btnType != "reply" && message.success('🎉 审核成功'); + } catch (error) { + setLoading(false) + } }; // 回复 const [isModalOpen, setIsModalOpen] = useState(false); const [replyInfo, setReplyInfo] = useState("") const handleReply = async () => { - // 审核通过评论 - await handleApproval() + setBtnLoading(true) - // 发送回复内容 - await addCommentDataAPI({ - avatar: user.avatar, - url: web.url, - content: replyInfo, - commentId: item?.id!, - auditStatus: 1, - email: user.email ? user.email : null, - name: user.name, - articleId: item?.articleId!, - createTime: new Date().getTime().toString(), - }) + try { + // 审核通过评论 + await handleApproval() - message.success('🎉 回复成功'); - setIsModalOpen(false) - fetchData(type); - setReplyInfo("") - setBtnType("") + // 发送回复内容 + await addCommentDataAPI({ + avatar: user.avatar, + url: web.url, + content: replyInfo, + commentId: item?.id!, + auditStatus: 1, + email: user.email ? user.email : null, + name: user.name, + articleId: item?.articleId!, + createTime: new Date().getTime().toString(), + }) + + await fetchData(type); + message.success('🎉 回复成功'); + setReplyInfo("") + setBtnType("") + setIsModalOpen(false) + } catch (error) { + setBtnLoading(false) + } + + setBtnLoading(false) } // 驳回 const [dismissInfo, setDismissInfo] = useState("") const handleDismiss = async () => { - if (type === "link") { - await delLinkDataAPI(item.id); - } else if (type === "comment") { - await delCommentDataAPI(item.id); - } else if (type === "wall") { - await delWallDataAPI(item.id); + setBtnLoading(true) + + try { + if (type === "link") { + await delLinkDataAPI(item.id); + } else if (type === "comment") { + await delCommentDataAPI(item.id); + } else if (type === "wall") { + await delWallDataAPI(item.id); + } + + // 有内容就发送驳回通知邮件,反之直接删除 + if (dismissInfo.trim().length) await sendDismissEmail() + + await fetchData(type); + message.success('🎉 驳回成功'); + setDismissInfo("") + setBtnType("") + setIsModalOpen(false) + } catch (error) { + setBtnLoading(false) } - // 有内容就发送驳回通知邮件,反之直接删除 - if (dismissInfo.trim().length) await sendDismissEmail() - - message.success('🎉 驳回成功'); - setIsModalOpen(false) - fetchData(type); - setDismissInfo("") - setBtnType("") + setBtnLoading(false) }; // 发送驳回通知邮件 @@ -202,7 +227,7 @@ export default ({ item, type, fetchData }: ListItemProps) => { <div className="flex space-x-4"> <Button className="w-full mt-2" onClick={() => setIsModalOpen(false)}>取消</Button> - <Button type="primary" className="w-full mt-2" onClick={btnType === "reply" ? handleReply : handleDismiss}>确定</Button> + <Button type="primary" onClick={btnType === "reply" ? handleReply : handleDismiss} loading={btnLoading} className="w-full mt-2">确定</Button> </div> </Modal> </div> diff --git a/src/pages/Work/index.tsx b/src/pages/Work/index.tsx index e11f3b0..9d491e7 100644 --- a/src/pages/Work/index.tsx +++ b/src/pages/Work/index.tsx @@ -54,7 +54,7 @@ export default () => { return <Empty />; } return list.map(item => ( - <List key={item.id} item={item} type={type} fetchData={(type) => fetchData(type)} /> + <List key={item.id} item={item} type={type} fetchData={(type) => fetchData(type)} setLoading={setLoading} /> )); }; @@ -62,32 +62,33 @@ export default () => { <> <Title value="工作台" /> - <Spin spinning={loading}> - <Card className="mt-2 min-h-[calc(100vh-180px)]"> - <div className="flex flex-col md:flex-row w-full"> - <div className="w-full min-w-[200px] md:w-2/12 md:min-h-96 mb-5 md:mb-0 pr-4 md:border-b-transparent md:border-r border-[#eee] dark:border-strokedark"> - <ul className="space-y-1"> - {(["comment", "link", "wall"] as Menu[]).map((menu) => ( - <li - key={menu} - className={`flex items-center w-full py-3 px-4 hover:bg-[#f9f9ff] dark:hover:bg-[#3c5370] hover:text-primary ${active === menu ? activeSty : ''} rounded-md text-base cursor-pointer transition-colors`} - onClick={() => setActive(menu)} - > - <img src={menu === "comment" ? comment : menu === "link" ? link : info} alt="" className="w-8 mr-4" /> - <span>{menu === "comment" ? "评论" : menu === "link" ? "友联" : "留言"}</span> - </li> - ))} - </ul> - </div> + <Card className="mt-2 min-h-[calc(100vh-180px)]"> + <div className="flex flex-col md:flex-row w-full"> + <div className="w-full min-w-[200px] md:w-2/12 md:min-h-96 mb-5 md:mb-0 pr-4 md:border-b-transparent md:border-r border-[#eee] dark:border-strokedark"> + <ul className="space-y-1"> + {(["comment", "link", "wall"] as Menu[]).map((menu) => ( + <li + key={menu} + className={`flex items-center w-full py-3 px-4 hover:bg-[#f9f9ff] dark:hover:bg-[#3c5370] hover:text-primary ${active === menu ? activeSty : ''} rounded-md text-base cursor-pointer transition-colors`} + onClick={() => setActive(menu)} + > + <img src={menu === "comment" ? comment : menu === "link" ? link : info} alt="" className="w-8 mr-4" /> + <span>{menu === "comment" ? "评论" : menu === "link" ? "友联" : "留言"}</span> + </li> + ))} + </ul> + </div> + <Spin spinning={loading}> <div className="w-full md:w-10/12 md:pl-6 py-4 space-y-10"> {active === "link" && renderList(linkList, "link")} {active === "comment" && renderList(commentList, "comment")} {active === "wall" && renderList(wallList, "wall")} </div> - </div> - </Card> - </Spin> + </Spin> + </div> + </Card> + </> ); } \ No newline at end of file