大改动

This commit is contained in:
宇阳
2025-01-11 22:10:58 +08:00
parent 9df0370ab6
commit c0946a7f40
15 changed files with 564 additions and 391 deletions

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" /> <link rel="icon" type="image/svg+xml" href="/favicon.ico" />

View File

@@ -41,20 +41,24 @@ const CatePage = () => {
}; };
const editCateData = async (id: number) => { const editCateData = async (id: number) => {
setIsMethod("edit")
setLoading(true); setLoading(true);
setIsMethod("edit")
setIsModelOpen(true); setIsModelOpen(true);
const { data } = await getCateDataAPI(id); try {
setIsCateShow(data.type === "cate" ? false : true) const { data } = await getCateDataAPI(id);
setCate(data); setIsCateShow(data.type === "cate" ? false : true)
setCate(data);
form.setFieldsValue(data); form.setFieldsValue(data);
setLoading(false); } catch (error) {
setLoading(false);
}
}; };
const delCateData = async (id: number) => { const delCateData = async (id: number) => {
setLoading(true); setLoading(true);
try { try {
await delCateDataAPI(id); await delCateDataAPI(id);
message.success('🎉 删除分类成功'); message.success('🎉 删除分类成功');
@@ -67,27 +71,29 @@ const CatePage = () => {
const submit = async () => { const submit = async () => {
setBtnLoading(true) setBtnLoading(true)
form.validateFields().then(async (values: Cate) => { try {
if (values.type === "cate") values.url = '/' form.validateFields().then(async (values: Cate) => {
if (values.type === "cate") values.url = '/'
if (isMethod === "edit") { if (isMethod === "edit") {
await editCateDataAPI({ ...cate, ...values }); await editCateDataAPI({ ...cate, ...values });
message.success('🎉 修改分类成功'); message.success('🎉 修改分类成功');
} else { } else {
await addCateDataAPI({ ...cate, ...values }); await addCateDataAPI({ ...cate, ...values });
message.success('🎉 新增分类成功'); message.success('🎉 新增分类成功');
} }
// 初始化表单状态 // 初始化表单状态
form.resetFields(); form.resetFields();
setCate({} as Cate); setCate({} as Cate);
setIsModelOpen(false); setIsModelOpen(false);
getCateList(); getCateList();
setIsMethod("create") setIsMethod("create")
}) })
} catch (error) {
setBtnLoading(false) setBtnLoading(false)
}
}; };
const closeModel = () => { const closeModel = () => {

View File

@@ -20,6 +20,8 @@ const CommentPage = () => {
const user = useUserStore(state => state.user) const user = useUserStore(state => state.user)
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [btnLoading, setBtnLoading] = useState(false);
const [comment, setComment] = useState<Comment>({} as Comment); const [comment, setComment] = useState<Comment>({} as Comment);
const [list, setList] = useState<Comment[]>([]); const [list, setList] = useState<Comment[]>([]);
@@ -27,16 +29,19 @@ const CommentPage = () => {
const getCommentList = async () => { const getCommentList = async () => {
const { data } = await getCommentListAPI(); const { data } = await getCommentListAPI();
setList(data) setList(data)
setLoading(false) setLoading(false)
} }
const delCommentData = async (id: number) => { const delCommentData = async (id: number) => {
setLoading(true) setLoading(true)
await delCommentDataAPI(id); try {
getCommentList(); await delCommentDataAPI(id);
message.success('🎉 删除评论成功'); getCommentList();
message.success('🎉 删除评论成功');
} catch (error) {
setLoading(false)
}
}; };
useEffect(() => { useEffect(() => {
@@ -115,38 +120,50 @@ const CommentPage = () => {
const { RangePicker } = DatePicker; const { RangePicker } = DatePicker;
const onSubmit = async (values: FilterForm) => { const onSubmit = async (values: FilterForm) => {
const query: FilterData = { setLoading(true)
key: values?.title,
content: values?.content,
startDate: values.createTime && values.createTime[0].valueOf() + '',
endDate: values.createTime && values.createTime[1].valueOf() + ''
}
const { data } = await getCommentListAPI({ query }); try {
setList(data) const query: FilterData = {
key: values?.title,
content: values?.content,
startDate: values.createTime && values.createTime[0].valueOf() + '',
endDate: values.createTime && values.createTime[1].valueOf() + ''
}
const { data } = await getCommentListAPI({ query });
setList(data)
} catch (error) {
setLoading(false)
}
} }
// 回复内容 // 回复内容
const [replyInfo, setReplyInfo] = useState("") const [replyInfo, setReplyInfo] = useState("")
const [isReplyModalOpen, setIsReplyModalOpen] = useState(false); const [isReplyModalOpen, setIsReplyModalOpen] = useState(false);
const handleReply = async () => { const handleReply = async () => {
await addCommentDataAPI({ setBtnLoading(true)
avatar: user.avatar,
url: web.url,
content: replyInfo,
commentId: comment?.id!,
auditStatus: 1,
email: user.email,
name: user.name,
articleId: comment?.articleId!,
createTime: new Date().getTime().toString(),
})
message.success('🎉 回复评论成功'); try {
await addCommentDataAPI({
avatar: user.avatar,
url: web.url,
content: replyInfo,
commentId: comment?.id!,
auditStatus: 1,
email: user.email,
name: user.name,
articleId: comment?.articleId!,
createTime: new Date().getTime().toString(),
})
setIsReplyModalOpen(false) message.success('🎉 回复评论成功');
setReplyInfo("")
getCommentList() setIsReplyModalOpen(false)
setReplyInfo("")
getCommentList()
} catch (error) {
setBtnLoading(false)
}
} }
return ( return (
@@ -198,7 +215,7 @@ const CommentPage = () => {
<div><b></b> {comment?.content}</div> <div><b></b> {comment?.content}</div>
</div> </div>
<Button type='primary' onClick={() => setIsReplyModalOpen(true)} className='w-full mt-4'></Button> <Button type='primary' loading={btnLoading} onClick={() => setIsReplyModalOpen(true)} className='w-full mt-4'></Button>
</Modal> </Modal>
<Modal title="回复评论" open={isReplyModalOpen} footer={null} onCancel={() => setIsReplyModalOpen(false)}> <Modal title="回复评论" open={isReplyModalOpen} footer={null} onCancel={() => setIsReplyModalOpen(false)}>
@@ -211,7 +228,7 @@ const CommentPage = () => {
<div className="flex space-x-4"> <div className="flex space-x-4">
<Button className="w-full mt-2" onClick={() => setIsReplyModalOpen(false)}></Button> <Button className="w-full mt-2" onClick={() => setIsReplyModalOpen(false)}></Button>
<Button type="primary" className="w-full mt-2" onClick={handleReply}></Button> <Button type="primary" loading={btnLoading} onClick={handleReply} className="w-full mt-2"></Button>
</div> </div>
</Modal> </Modal>
</> </>

View File

@@ -25,22 +25,26 @@ const EditorMD = ({ value, onChange }: Props) => {
const uploadImages = async (files: File[]) => { const uploadImages = async (files: File[]) => {
setLoading(true); setLoading(true);
// 处理成后端需要的格式 try {
const formData = new FormData(); // 处理成后端需要的格式
formData.append("dir", "article"); const formData = new FormData();
for (let i = 0; i < files.length; i++) formData.append('files', files[i]) formData.append("dir", "article");
for (let i = 0; i < files.length; i++) formData.append('files', files[i])
const { data: { code, data } } = await axios.post(`${baseURL}/file`, formData, { const { data: { code, data } } = await axios.post(`${baseURL}/file`, formData, {
headers: { headers: {
"Authorization": `Bearer ${store.token}`, "Authorization": `Bearer ${store.token}`,
"Content-Type": "multipart/form-data" "Content-Type": "multipart/form-data"
} }
}); });
// 返回图片信息数组
return data.map((url: string) => ({ url }));
} catch (error) {
setLoading(false);
}
setLoading(false); setLoading(false);
// 返回图片信息数组
return data.map((url: string) => ({ url }));
} }
return ( return (

View File

@@ -93,79 +93,82 @@ const PublishForm = ({ data, closeModel }: { data: Article, closeModel: () => vo
const onSubmit = async (values: FieldType, isDraft?: boolean) => { const onSubmit = async (values: FieldType, isDraft?: boolean) => {
setBtnLoading(true) setBtnLoading(true)
console.log(values); try {
values.isEncrypt = values.isEncrypt ? 1 : 0 values.isEncrypt = values.isEncrypt ? 1 : 0
// 如果是文章标签,则先判断是否存在,如果不存在则添加 // 如果是文章标签,则先判断是否存在,如果不存在则添加
let tagIds: number[] = [] let tagIds: number[] = []
for (const item of (values.tagIds ? values.tagIds : [])) { for (const item of (values.tagIds ? values.tagIds : [])) {
if (typeof item === "string") { if (typeof item === "string") {
// 如果已经有这个标签了,就没必要再创建一个了 // 如果已经有这个标签了,就没必要再创建一个了
// 先转换为大写进行查找,否则会出现大小写不匹配问题 // 先转换为大写进行查找,否则会出现大小写不匹配问题
const tag1 = tagList.find(t => t.name.toUpperCase() === item.toUpperCase())?.id; const tag1 = tagList.find(t => t.name.toUpperCase() === item.toUpperCase())?.id;
if (tag1) { if (tag1) {
tagIds.push(tag1) tagIds.push(tag1)
continue continue
}
await addTagDataAPI({ name: item });
const { data: list } = await getTagListAPI();
// 添加成功后查找对应的标签id
const tag2 = list.find(t => t.name === item)?.id;
if (tag2) tagIds.push(tag2);
} else {
tagIds.push(item);
} }
await addTagDataAPI({ name: item });
const { data: list } = await getTagListAPI();
// 添加成功后查找对应的标签id
const tag2 = list.find(t => t.name === item)?.id;
if (tag2) tagIds.push(tag2);
} else {
tagIds.push(item);
} }
}
values.createTime = values.createTime.valueOf() values.createTime = values.createTime.valueOf()
values.cateIds = [...new Set(values.cateIds?.flat())] values.cateIds = [...new Set(values.cateIds?.flat())]
if (id && !isDraftParams) { if (id && !isDraftParams) {
await editArticleDataAPI({
id,
...values,
content: data.content,
tagIds: tagIds.join(','),
config: {
status: values.status,
password: values.password
}
} as any)
message.success("🎉 编辑成功")
} else {
if (!isDraftParams) {
await addArticleDataAPI({
id,
...values,
content: data.content,
tagIds: tagIds.join(','),
isDraft: isDraft ? 1 : 0,
isDel: 0,
isEncrypt: 0,
config: {
status: values.status,
password: values.password
},
createTime: values.createTime.toString()
})
isDraft ? message.success("🎉 保存为草稿成功") : message.success("🎉 发布成功")
} else {
// 修改草稿状态为发布文章
await editArticleDataAPI({ await editArticleDataAPI({
id, id,
...values, ...values,
content: data.content, content: data.content,
tagIds: tagIds.join(','), tagIds: tagIds.join(','),
isDraft: isDraft ? 1 : 0,
config: { config: {
status: values.status, status: values.status,
password: values.password password: values.password
} }
} as any) } as any)
message.success("🎉 编辑成功")
} else {
if (!isDraftParams) {
await addArticleDataAPI({
id,
...values,
content: data.content,
tagIds: tagIds.join(','),
isDraft: isDraft ? 1 : 0,
isDel: 0,
isEncrypt: 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: isDraft ? 1 : 0,
config: {
status: values.status,
password: values.password
}
} as any)
}
} }
} catch (error) {
setBtnLoading(false)
} }
// 关闭弹框 // 关闭弹框
@@ -176,7 +179,6 @@ const PublishForm = ({ data, closeModel }: { data: Article, closeModel: () => vo
isDraft ? navigate("/draft") : navigate("/article") isDraft ? navigate("/draft") : navigate("/article")
// 初始化表单 // 初始化表单
form.resetFields() form.resetFields()
setBtnLoading(false) setBtnLoading(false)
} }

View File

@@ -18,6 +18,8 @@ const CreatePage = () => {
const id = +params.get('id')! const id = +params.get('id')!
const isDraftParams = Boolean(params.get('draft')) const isDraftParams = Boolean(params.get('draft'))
const [loading, setLoading] = useState(false)
const [data, setData] = useState<Article>({} as Article) const [data, setData] = useState<Article>({} as Article)
const [content, setContent] = useState(''); const [content, setContent] = useState('');
const [publishOpen, setPublishOpen] = useState(false) const [publishOpen, setPublishOpen] = useState(false)
@@ -29,13 +31,18 @@ const CreatePage = () => {
// 获取文章数据 // 获取文章数据
const getArticleData = async () => { const getArticleData = async () => {
const { data } = await getArticleDataAPI(id) try {
setData(data) const { data } = await getArticleDataAPI(id)
setContent(data.content) setData(data)
setContent(data.content)
} catch (error) {
setLoading(false)
}
} }
// 回显数据 // 回显数据
useEffect(() => { useEffect(() => {
setLoading(true)
setPublishOpen(false) setPublishOpen(false)
// 有Id就回显指定的数据 // 有Id就回显指定的数据
@@ -176,7 +183,7 @@ const CreatePage = () => {
</div> </div>
</Title> </Title>
<Card className={`${titleSty} overflow-hidden rounded-xl min-h-[calc(100vh-180px)]`}> <Card loading={loading} className={`${titleSty} overflow-hidden rounded-xl min-h-[calc(100vh-180px)]`}>
<Editor value={content} onChange={(value) => setContent(value)} /> <Editor value={content} onChange={(value) => setContent(value)} />
<Drawer <Drawer

View File

@@ -1,7 +1,7 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom"; import { useNavigate, useSearchParams } from "react-router-dom";
import { Button, Card, Dropdown, Image, Input, message, Modal } from "antd" import { Button, Card, Dropdown, Image, Input, message, Modal, Spin } from "antd"
import TextArea from "antd/es/input/TextArea" import TextArea from "antd/es/input/TextArea"
import { addRecordDataAPI, editRecordDataAPI, getRecordDataAPI } from '@/api/Record' import { addRecordDataAPI, editRecordDataAPI, getRecordDataAPI } from '@/api/Record'
@@ -19,6 +19,8 @@ export default () => {
const id = +params.get('id')! const id = +params.get('id')!
const navigate = useNavigate() const navigate = useNavigate()
const [loading, setLoading] = useState(false)
const [content, setContent] = useState("") const [content, setContent] = useState("")
const [imageList, setImageList] = useState<string[]>([]) const [imageList, setImageList] = useState<string[]>([])
@@ -30,31 +32,42 @@ export default () => {
} }
const onSubmit = async () => { const onSubmit = async () => {
const data = { setLoading(true)
content,
images: JSON.stringify(imageList), try {
createTime: new Date().getTime().toString() const data = {
content,
images: JSON.stringify(imageList),
createTime: new Date().getTime().toString()
}
if (!content.trim().length) {
message.error("请输入内容")
return
}
if (id) {
await editRecordDataAPI({ id, content: data.content, images: data.images })
} else {
await addRecordDataAPI(data)
}
navigate("/record")
} catch (error) {
setLoading(false)
} }
if (!content.trim().length) { setLoading(false)
message.error("请输入内容")
return
}
if (id) {
await editRecordDataAPI({ id, content: data.content, images: data.images })
} else {
await addRecordDataAPI(data)
}
navigate("/record")
} }
const getRecordData = async () => { const getRecordData = async () => {
setLoading(true)
const { data } = await getRecordDataAPI(id) const { data } = await getRecordDataAPI(id)
console.log(data, 222);
setContent(data.content) setContent(data.content)
setImageList(JSON.parse(data.images as string)) setImageList(JSON.parse(data.images as string))
setLoading(false)
} }
// 回显数据 // 回显数据
@@ -120,49 +133,51 @@ export default () => {
<> <>
<Title value="闪念" /> <Title value="闪念" />
<Card className={`${titleSty} min-h-[calc(100vh-180px)]`}> <Spin spinning={loading}>
<div className="relative flex w-[90%] xl:w-[800px] mx-auto mt-[50px]"> <Card className={`${titleSty} min-h-[calc(100vh-180px)]`}>
<TextArea <div className="relative flex w-[90%] xl:w-[800px] mx-auto mt-[50px]">
rows={10} <TextArea
maxLength={500} rows={10}
placeholder="记录此刻!" maxLength={500}
value={content} placeholder="记录此刻!"
onChange={(e) => setContent(e.target.value)} value={content}
className="w-full p-4 border-2 border-[#eee] dark:border-strokedark text-base rounded-md" onChange={(e) => setContent(e.target.value)}
/> className="w-full p-4 border-2 border-[#eee] dark:border-strokedark text-base rounded-md"
/>
<div className="absolute bottom-4 left-4 flex items-end space-x-3 max-w-[calc(100%-80px)]"> <div className="absolute bottom-4 left-4 flex items-end space-x-3 max-w-[calc(100%-80px)]">
<div className="flex space-x-2 overflow-x-auto scrollbar-hide"> <div className="flex space-x-2 overflow-x-auto scrollbar-hide">
{imageList.length > 0 && imageList.map((item, index) => ( {imageList.length > 0 && imageList.map((item, index) => (
<div key={index} className="group overflow-hidden relative shrink-0"> <div key={index} className="group overflow-hidden relative shrink-0">
<div className="absolute top-0 -right-6 group-hover:right-0 z-10 bg-slate-600 rounded-full cursor-pointer p-1" onClick={() => handleDelImage(item)}> <div className="absolute top-0 -right-6 group-hover:right-0 z-10 bg-slate-600 rounded-full cursor-pointer p-1" onClick={() => handleDelImage(item)}>
<RiDeleteBinLine className="text-white" /> <RiDeleteBinLine className="text-white" />
</div>
<Image
key={index}
src={item}
preview={false}
className='rounded-lg md:!w-[100px] md:!h-[100px] xs:!w-20 xs:!h-20 !w-15 !h-15 object-cover'
/>
</div> </div>
))}
</div>
<Image <Dropdown menu={dropdownItems} placement="top">
key={index} <LuImagePlus className="mb-1 text-3xl md:text-4xl text-slate-700 dark:text-white hover:text-primary dark:hover:text-primary cursor-pointer shrink-0" />
src={item} </Dropdown>
preview={false}
className='rounded-lg md:!w-[100px] md:!h-[100px] xs:!w-20 xs:!h-20 !w-15 !h-15 object-cover'
/>
</div>
))}
</div> </div>
<Dropdown menu={dropdownItems} placement="top"> <Button
<LuImagePlus className="mb-1 text-3xl md:text-4xl text-slate-700 dark:text-white hover:text-primary dark:hover:text-primary cursor-pointer shrink-0" /> type="primary"
</Dropdown> size="large"
icon={<BiLogoTelegram className="text-xl" />}
className="absolute bottom-4 right-4"
onClick={onSubmit}
/>
</div> </div>
</Card>
<Button </Spin>
type="primary"
size="large"
icon={<BiLogoTelegram className="text-xl" />}
className="absolute bottom-4 right-4"
onClick={onSubmit}
/>
</div>
</Card>
<FileUpload <FileUpload
dir="record" dir="record"

View File

@@ -5,8 +5,11 @@ import CardDataStats from "@/components/CardDataStats"
import { AiOutlineEye, AiOutlineMeh, AiOutlineStock, AiOutlineFieldTime } from "react-icons/ai"; import { AiOutlineEye, AiOutlineMeh, AiOutlineStock, AiOutlineFieldTime } from "react-icons/ai";
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { Spin } from "antd";
export default () => { export default () => {
const [loading, setLoading] = useState(false)
const [stats, setStats] = useState({ const [stats, setStats] = useState({
pv: 0, pv: 0,
ip: 0, ip: 0,
@@ -28,49 +31,58 @@ export default () => {
// 获取统计数据 // 获取统计数据
const getDataList = async () => { const getDataList = async () => {
const siteId = import.meta.env.VITE_BAIDU_TONGJI_SITE_ID; setLoading(true)
const token = import.meta.env.VITE_BAIDU_TONGJI_ACCESS_TOKEN;
const response = await fetch(`/baidu/rest/2.0/tongji/report/getData?access_token=${token}&site_id=${siteId}&start_date=${date}&end_date=${date}&metrics=pv_count%2Cip_count%2Cbounce_ratio%2Cavg_visit_time&method=overview%2FgetTimeTrendRpt`); try {
const data = await response.json(); const siteId = import.meta.env.VITE_BAIDU_TONGJI_SITE_ID;
const { result } = data; const token = import.meta.env.VITE_BAIDU_TONGJI_ACCESS_TOKEN;
let pv = 0; const response = await fetch(`/baidu/rest/2.0/tongji/report/getData?access_token=${token}&site_id=${siteId}&start_date=${date}&end_date=${date}&metrics=pv_count%2Cip_count%2Cbounce_ratio%2Cavg_visit_time&method=overview%2FgetTimeTrendRpt`);
let ip = 0; const data = await response.json();
let bounce = 0; const { result } = data;
let avgTime = 0;
let count = 0
result.items[1].forEach((item: number[]) => { let pv = 0;
if (!Number(item[0])) return; let ip = 0;
let bounce = 0;
let avgTime = 0;
let count = 0
// 检查并累加 pv result.items[1].forEach((item: number[]) => {
if (!isNaN(Number(item[0]))) { if (!Number(item[0])) return;
pv += Number(item[0]);
}
// 检查并累加 ip // 检查并累加 pv
if (!isNaN(Number(item[1]))) { if (!isNaN(Number(item[0]))) {
ip += Number(item[1]); pv += Number(item[0]);
} }
// 检查并累加 bounce // 检查并累加 ip
if (!isNaN(Number(item[2]))) { if (!isNaN(Number(item[1]))) {
bounce += Number(item[2]); ip += Number(item[1]);
} }
// 检查并累加 avgTime // 检查并累加 bounce
if (!isNaN(Number(item[3]))) { if (!isNaN(Number(item[2]))) {
avgTime += Number(item[3]); bounce += Number(item[2]);
} }
// 只有第三个和第四个数据都有值时才增加 count // 检查并累加 avgTime
if (!isNaN(Number(item[2])) && !isNaN(Number(item[3]))) { if (!isNaN(Number(item[3]))) {
count++; avgTime += Number(item[3]);
} }
});
setStats({ pv, ip, bounce: (bounce / count) || 0, avgTime: formatTime(avgTime / count) || "00:00:00" }) // 只有第三个和第四个数据都有值时才增加 count
if (!isNaN(Number(item[2])) && !isNaN(Number(item[3]))) {
count++;
}
});
setStats({ pv, ip, bounce: (bounce / count) || 0, avgTime: formatTime(avgTime / count) || "00:00:00" })
} catch (error) {
setLoading(false)
}
setLoading(false)
}; };
useEffect(() => { useEffect(() => {
@@ -78,7 +90,7 @@ export default () => {
}, []); }, []);
return ( return (
<> <Spin spinning={loading}>
{/* 基本数据 */} {/* 基本数据 */}
<div className="mt-2 grid grid-cols-1 gap-2 md:grid-cols-2 xl:grid-cols-4"> <div className="mt-2 grid grid-cols-1 gap-2 md:grid-cols-2 xl:grid-cols-4">
<CardDataStats title="今日访客" total={stats.pv + ''} rate="0.43%" levelUp> <CardDataStats title="今日访客" total={stats.pv + ''} rate="0.43%" levelUp>
@@ -104,6 +116,6 @@ export default () => {
{/* <ChartTwo /> {/* <ChartTwo />
<ChatCard /> */} <ChatCard /> */}
</div> </div>
</> </Spin>
) )
} }

View File

@@ -1,13 +1,13 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Table, Button, Tag, notification, Card, Popconfirm, Form } from 'antd'; import { Table, Button, Tag, notification, Card, Popconfirm, Form, Spin } from 'antd';
import { titleSty } from '@/styles/sty' import { titleSty } from '@/styles/sty'
import Title from '@/components/Title'; import Title from '@/components/Title';
import { delArticleDataAPI, getArticleListAPI, reductionArticleDataAPI } from '@/api/Article'; import { delArticleDataAPI, getArticleListAPI, reductionArticleDataAPI } from '@/api/Article';
import type { Tag as ArticleTag } from '@/types/app/tag'; import type { Tag as ArticleTag } from '@/types/app/tag';
import type { Cate } from '@/types/app/cate'; import type { Cate } from '@/types/app/cate';
import type { Article, Config } from '@/types/app/article'; import type { Article } from '@/types/app/article';
import { useWebStore } from '@/stores'; import { useWebStore } from '@/stores';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -23,33 +23,46 @@ export default () => {
const [form] = Form.useForm(); const [form] = Form.useForm();
const getArticleList = async () => { const getArticleList = async () => {
setLoading(true); try {
const { data } = await getArticleListAPI({ query: { isDel: 1 } }); const { data } = await getArticleListAPI({ query: { isDel: 1 } });
setArticleList(data as Article[]); setArticleList(data as Article[]);
} catch (error) {
setLoading(false);
}
setLoading(false); setLoading(false);
}; };
useEffect(() => { useEffect(() => {
setLoading(true);
getArticleList() getArticleList()
}, []); }, []);
const delArticleData = async (id: number) => { const delArticleData = async (id: number) => {
setLoading(true); setLoading(true);
// 严格删除:彻底从数据库删除,无法恢复 try {
await delArticleDataAPI(id); // 严格删除:彻底从数据库删除,无法恢复
await getArticleList(); await delArticleDataAPI(id);
form.resetFields() await getArticleList();
setCurrent(1) form.resetFields()
notification.success({ message: '🎉 删除文章成功' }) setCurrent(1)
notification.success({ message: '🎉 删除文章成功' })
setLoading(false); } catch (error) {
setLoading(false);
}
}; };
const reductionArticleData = async (id: number) => { const reductionArticleData = async (id: number) => {
await reductionArticleDataAPI(id) setLoading(true);
navigate("/article")
notification.success({ message: '🎉 还原文章成功' }) try {
await reductionArticleDataAPI(id)
navigate("/article")
notification.success({ message: '🎉 还原文章成功' })
} catch (error) {
setLoading(false);
}
} }
// 标签颜色 // 标签颜色

View File

@@ -21,26 +21,32 @@ export default () => {
const [form] = Form.useForm(); const [form] = Form.useForm();
const getArticleList = async () => { const getArticleList = async () => {
setLoading(true); try {
const { data } = await getArticleListAPI({ query: { isDraft: 1 } }); const { data } = await getArticleListAPI({ query: { isDraft: 1 } });
setArticleList(data as Article[]); setArticleList(data as Article[]);
setLoading(false); } catch (error) {
setLoading(false);
}
setLoading(false)
}; };
useEffect(() => { useEffect(() => {
setLoading(true)
getArticleList() getArticleList()
}, []); }, []);
const delArticleData = async (id: number) => { const delArticleData = async (id: number) => {
setLoading(true); setLoading(true);
await delArticleDataAPI(id); try {
await getArticleList(); await delArticleDataAPI(id);
form.resetFields() await getArticleList();
setCurrent(1) form.resetFields()
notification.success({ message: '🎉 删除文章成功' }) setCurrent(1)
notification.success({ message: '🎉 删除文章成功' })
setLoading(false); } catch (error) {
setLoading(false);
}
}; };
// 标签颜色 // 标签颜色

View File

@@ -34,9 +34,13 @@ export default () => {
// 获取目录列表 // 获取目录列表
const getDirList = async () => { const getDirList = async () => {
setLoading(true) try {
const { data } = await getDirListAPI() const { data } = await getDirListAPI()
setDirList(data) setDirList(data)
} catch (error) {
setLoading(false)
}
setLoading(false) setLoading(false)
} }
@@ -54,13 +58,16 @@ export default () => {
const onDeleteImage = async (data: File) => { const onDeleteImage = async (data: File) => {
setLoading(true) setLoading(true)
await delFileDataAPI(data.url) try {
message.success("🎉 删除图片成功") await delFileDataAPI(data.url)
getFileList(dirName) message.success("🎉 删除图片成功")
setFile({} as File) getFileList(dirName)
setFile({} as File)
setOpenFileInfoDrawer(false) setOpenFileInfoDrawer(false)
setOpenFilePreviewDrawer(false) setOpenFilePreviewDrawer(false)
} catch (error) {
setLoading(false)
}
} }
// 下载图片 // 下载图片

View File

@@ -12,6 +12,7 @@ import axios from 'axios';
const FootprintPage = () => { const FootprintPage = () => {
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const [btnLoading, setBtnLoading] = useState(false) const [btnLoading, setBtnLoading] = useState(false)
const [modalLoading, setModalLoading] = useState(false)
const [footprintList, setFootprintList] = useState<Footprint[]>([]); const [footprintList, setFootprintList] = useState<Footprint[]>([]);
const [isModelOpen, setIsModelOpen] = useState(false); const [isModelOpen, setIsModelOpen] = useState(false);
@@ -86,13 +87,18 @@ const FootprintPage = () => {
const { RangePicker } = DatePicker; const { RangePicker } = DatePicker;
const getFootprintList = async () => { const getFootprintList = async () => {
setLoading(true); try {
const { data } = await getFootprintListAPI(); const { data } = await getFootprintListAPI();
setFootprintList(data as Footprint[]); setFootprintList(data as Footprint[]);
} catch (error) {
setLoading(false);
}
setLoading(false); setLoading(false);
}; };
useEffect(() => { useEffect(() => {
setLoading(true);
getFootprintList(); getFootprintList();
}, []); }, []);
@@ -105,10 +111,14 @@ const FootprintPage = () => {
const delFootprintData = async (id: number) => { const delFootprintData = async (id: number) => {
setLoading(true); setLoading(true);
await delFootprintDataAPI(id);
notification.success({ message: '🎉 删除足迹成功' }); try {
getFootprintList(); await delFootprintDataAPI(id);
setLoading(false); notification.success({ message: '🎉 删除足迹成功' });
getFootprintList();
} catch (error) {
setLoading(false);
}
}; };
const addFootprintData = () => { const addFootprintData = () => {
@@ -119,60 +129,78 @@ const FootprintPage = () => {
}; };
const editFootprintData = async (id: number) => { const editFootprintData = async (id: number) => {
setIsMethod("edit"); setModalLoading(true);
setLoading(true);
setIsModelOpen(true);
const { data } = await getFootprintDataAPI(id); try {
setIsMethod("edit");
setIsModelOpen(true);
data.images = (data.images as string[]).join("\n") const { data } = await getFootprintDataAPI(id);
data.createTime = dayjs(+data.createTime)
setFootprint(data); data.images = (data.images as string[]).join("\n")
form.setFieldsValue(data); data.createTime = dayjs(+data.createTime)
setLoading(false);
setFootprint(data);
form.setFieldsValue(data);
} catch (error) {
setModalLoading(false);
}
setModalLoading(false);
}; };
const onSubmit = async () => { const onSubmit = async () => {
setBtnLoading(true) setBtnLoading(true)
form.validateFields().then(async (values: Footprint) => { try {
values.createTime = values.createTime.valueOf() form.validateFields().then(async (values: Footprint) => {
values.images = values.images ? (values.images as string).split("\n") : [] values.createTime = values.createTime.valueOf()
values.images = values.images ? (values.images as string).split("\n") : []
if (isMethod === "edit") { if (isMethod === "edit") {
await editFootprintDataAPI({ ...footprint, ...values }); await editFootprintDataAPI({ ...footprint, ...values });
message.success('🎉 修改足迹成功'); message.success('🎉 修改足迹成功');
} else { } else {
await addFootprintDataAPI({ ...footprint, ...values }); await addFootprintDataAPI({ ...footprint, ...values });
message.success('🎉 新增足迹成功'); message.success('🎉 新增足迹成功');
} }
reset() reset()
getFootprintList(); getFootprintList();
}); });
} catch (error) {
setBtnLoading(false) setBtnLoading(false)
}
}; };
const closeModel = () => reset(); const closeModel = () => reset();
const onFilterSubmit = async (values: FilterForm) => { const onFilterSubmit = async (values: FilterForm) => {
const query: FilterData = { setLoading(true)
key: values.address,
startDate: values.createTime && values.createTime[0].valueOf() + '', try {
endDate: values.createTime && values.createTime[1].valueOf() + '' const query: FilterData = {
key: values.address,
startDate: values.createTime && values.createTime[0].valueOf() + '',
endDate: values.createTime && values.createTime[1].valueOf() + ''
}
const { data } = await getFootprintListAPI({ query });
setFootprintList(data);
} catch (error) {
setLoading(false)
} }
const { data } = await getFootprintListAPI({ query }); setLoading(false)
setFootprintList(data as Footprint[]);
} }
// 通过详细地址获取纬度 // 通过详细地址获取纬度
const getGeocode = async () => { const getGeocode = async () => {
const address = form.getFieldValue("address") setModalLoading(true)
try { try {
const address = form.getFieldValue("address")
const { data } = await axios.get('https://restapi.amap.com/v3/geocode/geo', { const { data } = await axios.get('https://restapi.amap.com/v3/geocode/geo', {
params: { params: {
address, address,
@@ -187,16 +215,17 @@ const FootprintPage = () => {
// 立即触发校验 // 立即触发校验
form.validateFields(['position']); form.validateFields(['position']);
setModalLoading(false)
return data.geocodes[0].location; return data.geocodes[0].location;
} else { } else {
message.warning('未找到该地址的经纬度'); message.warning('未找到该地址的经纬度');
return '';
} }
} catch (error) { } catch (error) {
console.error('获取地理编码时出错:', error); setModalLoading(false)
message.error('获取地理编码时出错');
return '';
} }
}; };
return ( return (
@@ -237,7 +266,7 @@ const FootprintPage = () => {
/> />
</Card> </Card>
<Modal title={isMethod === "edit" ? "编辑足迹" : "新增足迹"} open={isModelOpen} onCancel={closeModel} destroyOnClose footer={null}> <Modal loading={modalLoading} title={isMethod === "edit" ? "编辑足迹" : "新增足迹"} open={isModelOpen} onCancel={closeModel} destroyOnClose footer={null}>
<Form form={form} layout="vertical" initialValues={footprint} size='large' preserve={false} className='mt-6'> <Form form={form} layout="vertical" initialValues={footprint} size='large' preserve={false} className='mt-6'>
<Form.Item label="标题" name="title" rules={[{ required: true, message: '标题不能为空' }]}> <Form.Item label="标题" name="title" rules={[{ required: true, message: '标题不能为空' }]}>
<Input placeholder="请输入标题" /> <Input placeholder="请输入标题" />

View File

@@ -1,4 +1,4 @@
import { Card, Select, Timeline, TimelineItemProps } from 'antd'; import { Card, Select, Spin, Timeline, TimelineItemProps } from 'antd';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import GitHubCalendar from 'react-github-calendar'; import GitHubCalendar from 'react-github-calendar';
import Title from '@/components/Title'; import Title from '@/components/Title';
@@ -12,6 +12,8 @@ interface Commit {
} }
const Home = () => { const Home = () => {
const [loading, setLoading] = useState<boolean>(false)
const [year, setYear] = useState<number>(new Date().getFullYear()) const [year, setYear] = useState<number>(new Date().getFullYear())
const [yearList, setYearList] = useState<{ value: number, label: string }[]>([]) const [yearList, setYearList] = useState<{ value: number, label: string }[]>([])
@@ -21,32 +23,40 @@ const Home = () => {
// 从github获取最近10次迭代记录 // 从github获取最近10次迭代记录
const getCommitData = async (project: string) => { const getCommitData = async (project: string) => {
const res = await fetch(`https://api.github.com/repos/LiuYuYang01/${project}/commits?per_page=10`) try {
const data = await res.json() const res = await fetch(`https://api.github.com/repos/LiuYuYang01/${project}/commits?per_page=10`)
const result = data?.map((item: Commit) => ( const data = await res.json()
{ const result = data?.map((item: Commit) => (
label: dayjs(item.commit.author.date).format("YYYY-MM-DD HH:mm:ss"), {
children: item.commit.message label: dayjs(item.commit.author.date).format("YYYY-MM-DD HH:mm:ss"),
} children: item.commit.message
)) }
))
switch (project) { switch (project) {
case "ThriveX-Blog": case "ThriveX-Blog":
sessionStorage.setItem('blog_project_iterative', JSON.stringify(result)) sessionStorage.setItem('blog_project_iterative', JSON.stringify(result))
setBlog_IterativeRecording(result) setBlog_IterativeRecording(result)
break; break;
case "ThriveX-Admin": case "ThriveX-Admin":
sessionStorage.setItem('admin_project_iterative', JSON.stringify(result)) sessionStorage.setItem('admin_project_iterative', JSON.stringify(result))
setAdmin_IterativeRecording(result) setAdmin_IterativeRecording(result)
break; break;
case "ThriveX-Server": case "ThriveX-Server":
sessionStorage.setItem('server_project_iterative', JSON.stringify(result)) sessionStorage.setItem('server_project_iterative', JSON.stringify(result))
setServer_IterativeRecording(result) setServer_IterativeRecording(result)
break; break;
}
} catch (error) {
setLoading(false)
} }
setLoading(false)
} }
useEffect(() => { useEffect(() => {
setLoading(true)
// 获取当前年份 // 获取当前年份
const currentYear = dayjs().year(); const currentYear = dayjs().year();
// 生成最近10年的年份数组 // 生成最近10年的年份数组
@@ -62,48 +72,52 @@ const Home = () => {
const server_project_iterative = JSON.parse(sessionStorage.getItem('server_project_iterative') || '[]') const server_project_iterative = JSON.parse(sessionStorage.getItem('server_project_iterative') || '[]')
server_project_iterative.length ? setServer_IterativeRecording(server_project_iterative) : getCommitData("ThriveX-Server") server_project_iterative.length ? setServer_IterativeRecording(server_project_iterative) : getCommitData("ThriveX-Server")
setLoading(false)
}, []) }, [])
return ( return (
<> <>
<Title value='项目迭代记录'></Title> <Title value='项目迭代记录'></Title>
<Card className='mt-2 min-h-[calc(100vh-180px)]'> <Spin spinning={loading}>
<div className='flex flex-col items-center mt-2 mb-22'> <Card className='mt-2 min-h-[calc(100vh-180px)]'>
<div className='ml-5 mb-6'> <div className='flex flex-col items-center mt-2 mb-22'>
<span></span> <div className='ml-5 mb-6'>
<span></span>
<Select <Select
size='small' size='small'
defaultValue={year} defaultValue={year}
options={yearList} options={yearList}
onChange={setYear} onChange={setYear}
className='w-20' className='w-20'
/> />
</div>
<GitHubCalendar username="liuyuyang01" year={year} />
</div> </div>
<GitHubCalendar username="liuyuyang01" year={year} /> <div className='overflow-auto w-full'>
</div> <div className='flex w-[1350px] mx-auto'>
<div className='w-[400px]'>
<h3 className='text-xl text-center pb-6 font-bold text-gradient block'>ThriveX-Blog</h3>
<Timeline mode="left" items={blog_iterativeRecording} />
</div>
<div className='overflow-auto w-full'> <div className='w-[400px] mx-[50px]'>
<div className='flex w-[1350px] mx-auto'> <h3 className='text-xl text-center pb-6 font-bold text-gradient block'>ThriveX-Admin</h3>
<div className='w-[400px]'> <Timeline mode="left" items={admin_iterativeRecording} />
<h3 className='text-xl text-center pb-6 font-bold text-gradient block'>ThriveX-Blog</h3> </div>
<Timeline mode="left" items={blog_iterativeRecording} />
</div>
<div className='w-[400px] mx-[50px]'> <div className='w-[400px]'>
<h3 className='text-xl text-center pb-6 font-bold text-gradient block'>ThriveX-Admin</h3> <h3 className='text-xl text-center pb-6 font-bold text-gradient block'>ThriveX-Server</h3>
<Timeline mode="left" items={admin_iterativeRecording} /> <Timeline mode="left" items={server_iterativeRecording} />
</div> </div>
<div className='w-[400px]'>
<h3 className='text-xl text-center pb-6 font-bold text-gradient block'>ThriveX-Server</h3>
<Timeline mode="left" items={server_iterativeRecording} />
</div> </div>
</div> </div>
</div> </Card>
</Card> </Spin>
</> </>
); );
}; };

View File

@@ -7,6 +7,8 @@ import { loginDataAPI } from '@/api/User';
import { useUserStore } from '@/stores'; import { useUserStore } from '@/stores';
const LoginPage = () => { const LoginPage = () => {
const [loading, setLoading] = useState(false)
const [form] = useForm(); const [form] = useForm();
const [isPassVisible, setIsPassVisible] = useState(false); const [isPassVisible, setIsPassVisible] = useState(false);
const store = useUserStore(); const store = useUserStore();
@@ -15,20 +17,28 @@ const LoginPage = () => {
const returnUrl = new URLSearchParams(location.search).get('returnUrl') || '/'; const returnUrl = new URLSearchParams(location.search).get('returnUrl') || '/';
const onSubmit = async () => { const onSubmit = async () => {
const values = await form.validateFields(); setLoading(true)
const { data } = await loginDataAPI(values);
// 将用户信息和token保存起来 try {
store.setToken(data.token); const values = await form.validateFields();
store.setUser(data.user); const { data } = await loginDataAPI(values);
store.setRole(data.role)
notification.success({ // 将用户信息和token保存起来
message: '🎉 登录成功', store.setToken(data.token);
description: `Hello ${data.user.name} 欢迎回来`, store.setUser(data.user);
}); store.setRole(data.role)
navigate(returnUrl); notification.success({
message: '🎉 登录成功',
description: `Hello ${data.user.name} 欢迎回来`,
});
navigate(returnUrl);
} catch (error) {
setLoading(false)
}
setLoading(false)
}; };
return ( return (
@@ -70,7 +80,7 @@ const LoginPage = () => {
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Button type="primary" htmlType="submit" className="w-full" block></Button> <Button type="primary" htmlType="submit" loading={loading} className="w-full" block></Button>
</Form.Item> </Form.Item>
</Form> </Form>
</div> </div>

View File

@@ -9,7 +9,9 @@ import { titleSty } from '@/styles/sty';
const StoragePage = () => { const StoragePage = () => {
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const [btnLoading, setBtnLoading] = useState(false); const [btnLoading, setBtnLoading] = useState(false);
const [modalLoading, setModalLoading] = useState(false)
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const [oss, setOss] = useState<Oss>({} as Oss); const [oss, setOss] = useState<Oss>({} as Oss);
const [ossList, setOssList] = useState<Oss[]>([]); const [ossList, setOssList] = useState<Oss[]>([]);
const [platformList, setPlatformList] = useState<{ label: string, value: string, disabled: boolean }[]>([]); const [platformList, setPlatformList] = useState<{ label: string, value: string, disabled: boolean }[]>([]);
@@ -51,9 +53,9 @@ const StoragePage = () => {
render: (_, record: Oss) => ( render: (_, record: Oss) => (
<div className='space-x-2'> <div className='space-x-2'>
{record.isEnable ? ( {record.isEnable ? (
<Button type="primary" danger onClick={() => handleDisable(record.id!)}></Button> <Button type="primary" danger onClick={() => disableOssData(record.id!)}></Button>
) : ( ) : (
<Button type="primary" onClick={() => handleEnable(record.id!)}></Button> <Button type="primary" onClick={() => enableOssData(record.id!)}></Button>
)} )}
<Button onClick={() => editOssData(record)}></Button> <Button onClick={() => editOssData(record)}></Button>
@@ -83,44 +85,71 @@ const StoragePage = () => {
}; };
const getOssList = async () => { const getOssList = async () => {
setLoading(true); try {
const { data } = await getOssListAPI(); const { data } = await getOssListAPI();
setOssList(data); setOssList(data);
} catch (error) {
setLoading(false)
}
setLoading(false); setLoading(false);
}; };
useEffect(() => { useEffect(() => {
setLoading(true);
getOssList(); getOssList();
getOssPlatformList() getOssPlatformList()
}, []); }, []);
const handleEnable = async (id: number) => { const enableOssData = async (id: number) => {
await enableOssDataAPI(id); try {
message.success('启用成功'); await enableOssDataAPI(id);
getOssList(); await getOssList();
message.success('启用成功');
} catch (error) {
setLoading(false)
}
}; };
const handleDisable = async (id: number) => { const disableOssData = async (id: number) => {
await disableOssDataAPI(id); try {
message.success('禁用成功'); await disableOssDataAPI(id);
getOssList(); await getOssList();
message.success('禁用成功');
} catch (error) {
setLoading(false)
}
}; };
const editOssData = async (record: Oss) => { const editOssData = async (record: Oss) => {
setOss(record); setModalLoading(true)
const { data } = await getOssDataAPI(record.id)
form.setFieldsValue(data); try {
setIsModalOpen(true); setIsModalOpen(true);
const { data } = await getOssDataAPI(record.id)
setOss(data);
form.setFieldsValue(data);
} catch (error) {
setModalLoading(false)
}
setModalLoading(false)
}; };
const delOssData = async (id: number) => { const delOssData = async (id: number) => {
setLoading(true); setLoading(true);
await delOssDataAPI(id);
message.success('🎉 删除存储配置成功'); try {
getOssList(); await delOssDataAPI(id);
await getOssList();
message.success('🎉 删除存储配置成功');
} catch (error) {
setLoading(false)
}
}; };
const handleAdd = () => { const addOssData = () => {
setOss({} as Oss); setOss({} as Oss);
form.resetFields(); form.resetFields();
form.setFieldsValue({}); form.setFieldsValue({});
@@ -151,20 +180,22 @@ const StoragePage = () => {
form.resetFields(); form.resetFields();
setBtnLoading(false); setBtnLoading(false);
} catch (error) { } catch (error) {
console.error('表单验证失败:', error);
setBtnLoading(false); setBtnLoading(false);
} }
setBtnLoading(false)
}; };
return ( return (
<> <>
<Title value="存储管理"> <Title value="存储管理">
<Button type="primary" size='large' onClick={handleAdd}></Button> <Button type="primary" size='large' onClick={addOssData}></Button>
</Title> </Title>
<Card className={`${titleSty} min-h-[calc(100vh-180px)]`}> <Card className={`${titleSty} min-h-[calc(100vh-180px)]`}>
<Table <Table
rowKey="id" rowKey="id"
loading={loading}
dataSource={ossList} dataSource={ossList}
columns={columns} columns={columns}
scroll={{ x: 'max-content' }} scroll={{ x: 'max-content' }}
@@ -172,11 +203,11 @@ const StoragePage = () => {
position: ['bottomCenter'], position: ['bottomCenter'],
pageSize: 8 pageSize: 8
}} }}
loading={loading}
/> />
</Card> </Card>
<Modal <Modal
loading={modalLoading}
title={oss.id ? "编辑存储配置" : "新增存储配置"} title={oss.id ? "编辑存储配置" : "新增存储配置"}
open={isModalOpen} open={isModalOpen}
onCancel={handleCancel} onCancel={handleCancel}