大改动
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||
|
||||
@@ -41,20 +41,24 @@ const CatePage = () => {
|
||||
};
|
||||
|
||||
const editCateData = async (id: number) => {
|
||||
setIsMethod("edit")
|
||||
setLoading(true);
|
||||
setIsMethod("edit")
|
||||
setIsModelOpen(true);
|
||||
|
||||
const { data } = await getCateDataAPI(id);
|
||||
setIsCateShow(data.type === "cate" ? false : true)
|
||||
setCate(data);
|
||||
try {
|
||||
const { data } = await getCateDataAPI(id);
|
||||
setIsCateShow(data.type === "cate" ? false : true)
|
||||
setCate(data);
|
||||
|
||||
form.setFieldsValue(data);
|
||||
setLoading(false);
|
||||
form.setFieldsValue(data);
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const delCateData = async (id: number) => {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
await delCateDataAPI(id);
|
||||
message.success('🎉 删除分类成功');
|
||||
@@ -67,27 +71,29 @@ const CatePage = () => {
|
||||
const submit = async () => {
|
||||
setBtnLoading(true)
|
||||
|
||||
form.validateFields().then(async (values: Cate) => {
|
||||
if (values.type === "cate") values.url = '/'
|
||||
try {
|
||||
form.validateFields().then(async (values: Cate) => {
|
||||
if (values.type === "cate") values.url = '/'
|
||||
|
||||
if (isMethod === "edit") {
|
||||
await editCateDataAPI({ ...cate, ...values });
|
||||
message.success('🎉 修改分类成功');
|
||||
} else {
|
||||
await addCateDataAPI({ ...cate, ...values });
|
||||
message.success('🎉 新增分类成功');
|
||||
}
|
||||
if (isMethod === "edit") {
|
||||
await editCateDataAPI({ ...cate, ...values });
|
||||
message.success('🎉 修改分类成功');
|
||||
} else {
|
||||
await addCateDataAPI({ ...cate, ...values });
|
||||
message.success('🎉 新增分类成功');
|
||||
}
|
||||
|
||||
// 初始化表单状态
|
||||
form.resetFields();
|
||||
setCate({} as Cate);
|
||||
// 初始化表单状态
|
||||
form.resetFields();
|
||||
setCate({} as Cate);
|
||||
|
||||
setIsModelOpen(false);
|
||||
getCateList();
|
||||
setIsMethod("create")
|
||||
})
|
||||
|
||||
setBtnLoading(false)
|
||||
setIsModelOpen(false);
|
||||
getCateList();
|
||||
setIsMethod("create")
|
||||
})
|
||||
} catch (error) {
|
||||
setBtnLoading(false)
|
||||
}
|
||||
};
|
||||
|
||||
const closeModel = () => {
|
||||
|
||||
@@ -20,6 +20,8 @@ const CommentPage = () => {
|
||||
const user = useUserStore(state => state.user)
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [btnLoading, setBtnLoading] = useState(false);
|
||||
|
||||
const [comment, setComment] = useState<Comment>({} as Comment);
|
||||
const [list, setList] = useState<Comment[]>([]);
|
||||
|
||||
@@ -27,16 +29,19 @@ const CommentPage = () => {
|
||||
|
||||
const getCommentList = async () => {
|
||||
const { data } = await getCommentListAPI();
|
||||
|
||||
setList(data)
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
const delCommentData = async (id: number) => {
|
||||
setLoading(true)
|
||||
await delCommentDataAPI(id);
|
||||
getCommentList();
|
||||
message.success('🎉 删除评论成功');
|
||||
try {
|
||||
await delCommentDataAPI(id);
|
||||
getCommentList();
|
||||
message.success('🎉 删除评论成功');
|
||||
} catch (error) {
|
||||
setLoading(false)
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -115,38 +120,50 @@ const CommentPage = () => {
|
||||
const { RangePicker } = DatePicker;
|
||||
|
||||
const onSubmit = async (values: FilterForm) => {
|
||||
const query: FilterData = {
|
||||
key: values?.title,
|
||||
content: values?.content,
|
||||
startDate: values.createTime && values.createTime[0].valueOf() + '',
|
||||
endDate: values.createTime && values.createTime[1].valueOf() + ''
|
||||
}
|
||||
setLoading(true)
|
||||
|
||||
const { data } = await getCommentListAPI({ query });
|
||||
setList(data)
|
||||
try {
|
||||
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 [isReplyModalOpen, setIsReplyModalOpen] = useState(false);
|
||||
const handleReply = async () => {
|
||||
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(),
|
||||
})
|
||||
setBtnLoading(true)
|
||||
|
||||
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)
|
||||
setReplyInfo("")
|
||||
getCommentList()
|
||||
message.success('🎉 回复评论成功');
|
||||
|
||||
setIsReplyModalOpen(false)
|
||||
setReplyInfo("")
|
||||
getCommentList()
|
||||
} catch (error) {
|
||||
setBtnLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -198,7 +215,7 @@ const CommentPage = () => {
|
||||
<div><b>内容:</b> {comment?.content}</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 title="回复评论" open={isReplyModalOpen} footer={null} onCancel={() => setIsReplyModalOpen(false)}>
|
||||
@@ -211,7 +228,7 @@ const CommentPage = () => {
|
||||
|
||||
<div className="flex space-x-4">
|
||||
<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>
|
||||
</Modal>
|
||||
</>
|
||||
|
||||
@@ -25,22 +25,26 @@ const EditorMD = ({ value, onChange }: Props) => {
|
||||
const uploadImages = async (files: File[]) => {
|
||||
setLoading(true);
|
||||
|
||||
// 处理成后端需要的格式
|
||||
const formData = new FormData();
|
||||
formData.append("dir", "article");
|
||||
for (let i = 0; i < files.length; i++) formData.append('files', files[i])
|
||||
try {
|
||||
// 处理成后端需要的格式
|
||||
const formData = new FormData();
|
||||
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, {
|
||||
headers: {
|
||||
"Authorization": `Bearer ${store.token}`,
|
||||
"Content-Type": "multipart/form-data"
|
||||
}
|
||||
});
|
||||
const { data: { code, data } } = await axios.post(`${baseURL}/file`, formData, {
|
||||
headers: {
|
||||
"Authorization": `Bearer ${store.token}`,
|
||||
"Content-Type": "multipart/form-data"
|
||||
}
|
||||
});
|
||||
|
||||
// 返回图片信息数组
|
||||
return data.map((url: string) => ({ url }));
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
|
||||
// 返回图片信息数组
|
||||
return data.map((url: string) => ({ url }));
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -93,79 +93,82 @@ const PublishForm = ({ data, closeModel }: { data: Article, closeModel: () => vo
|
||||
const onSubmit = async (values: FieldType, isDraft?: boolean) => {
|
||||
setBtnLoading(true)
|
||||
|
||||
console.log(values);
|
||||
values.isEncrypt = values.isEncrypt ? 1 : 0
|
||||
try {
|
||||
values.isEncrypt = values.isEncrypt ? 1 : 0
|
||||
|
||||
// 如果是文章标签,则先判断是否存在,如果不存在则添加
|
||||
let tagIds: number[] = []
|
||||
for (const item of (values.tagIds ? values.tagIds : [])) {
|
||||
if (typeof item === "string") {
|
||||
// 如果已经有这个标签了,就没必要再创建一个了
|
||||
// 先转换为大写进行查找,否则会出现大小写不匹配问题
|
||||
const tag1 = tagList.find(t => t.name.toUpperCase() === item.toUpperCase())?.id;
|
||||
// 如果是文章标签,则先判断是否存在,如果不存在则添加
|
||||
let tagIds: number[] = []
|
||||
for (const item of (values.tagIds ? values.tagIds : [])) {
|
||||
if (typeof item === "string") {
|
||||
// 如果已经有这个标签了,就没必要再创建一个了
|
||||
// 先转换为大写进行查找,否则会出现大小写不匹配问题
|
||||
const tag1 = tagList.find(t => t.name.toUpperCase() === item.toUpperCase())?.id;
|
||||
|
||||
if (tag1) {
|
||||
tagIds.push(tag1)
|
||||
continue
|
||||
if (tag1) {
|
||||
tagIds.push(tag1)
|
||||
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.cateIds = [...new Set(values.cateIds?.flat())]
|
||||
values.createTime = values.createTime.valueOf()
|
||||
values.cateIds = [...new Set(values.cateIds?.flat())]
|
||||
|
||||
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 {
|
||||
// 修改草稿状态为发布文章
|
||||
if (id && !isDraftParams) {
|
||||
await editArticleDataAPI({
|
||||
id,
|
||||
...values,
|
||||
content: data.content,
|
||||
tagIds: tagIds.join(','),
|
||||
isDraft: isDraft ? 1 : 0,
|
||||
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({
|
||||
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")
|
||||
// 初始化表单
|
||||
form.resetFields()
|
||||
|
||||
setBtnLoading(false)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ const CreatePage = () => {
|
||||
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)
|
||||
@@ -29,13 +31,18 @@ const CreatePage = () => {
|
||||
|
||||
// 获取文章数据
|
||||
const getArticleData = async () => {
|
||||
const { data } = await getArticleDataAPI(id)
|
||||
setData(data)
|
||||
setContent(data.content)
|
||||
try {
|
||||
const { data } = await getArticleDataAPI(id)
|
||||
setData(data)
|
||||
setContent(data.content)
|
||||
} catch (error) {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 回显数据
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
setPublishOpen(false)
|
||||
|
||||
// 有Id就回显指定的数据
|
||||
@@ -176,7 +183,7 @@ const CreatePage = () => {
|
||||
</div>
|
||||
</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)} />
|
||||
|
||||
<Drawer
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
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 { addRecordDataAPI, editRecordDataAPI, getRecordDataAPI } from '@/api/Record'
|
||||
@@ -19,6 +19,8 @@ export default () => {
|
||||
const id = +params.get('id')!
|
||||
const navigate = useNavigate()
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const [content, setContent] = useState("")
|
||||
const [imageList, setImageList] = useState<string[]>([])
|
||||
|
||||
@@ -30,31 +32,42 @@ export default () => {
|
||||
}
|
||||
|
||||
const onSubmit = async () => {
|
||||
const data = {
|
||||
content,
|
||||
images: JSON.stringify(imageList),
|
||||
createTime: new Date().getTime().toString()
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
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) {
|
||||
message.error("请输入内容")
|
||||
return
|
||||
}
|
||||
|
||||
if (id) {
|
||||
await editRecordDataAPI({ id, content: data.content, images: data.images })
|
||||
} else {
|
||||
await addRecordDataAPI(data)
|
||||
}
|
||||
|
||||
navigate("/record")
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
const getRecordData = async () => {
|
||||
setLoading(true)
|
||||
|
||||
const { data } = await getRecordDataAPI(id)
|
||||
console.log(data, 222);
|
||||
setContent(data.content)
|
||||
setImageList(JSON.parse(data.images as string))
|
||||
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
// 回显数据
|
||||
@@ -106,7 +119,7 @@ export default () => {
|
||||
message.error('链接必须以 http:// 或 https:// 开头');
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
|
||||
setImageList([...imageList, inputUrl]);
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -120,49 +133,51 @@ export default () => {
|
||||
<>
|
||||
<Title value="闪念" />
|
||||
|
||||
<Card className={`${titleSty} min-h-[calc(100vh-180px)]`}>
|
||||
<div className="relative flex w-[90%] xl:w-[800px] mx-auto mt-[50px]">
|
||||
<TextArea
|
||||
rows={10}
|
||||
maxLength={500}
|
||||
placeholder="记录此刻!"
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
className="w-full p-4 border-2 border-[#eee] dark:border-strokedark text-base rounded-md"
|
||||
/>
|
||||
<Spin spinning={loading}>
|
||||
<Card className={`${titleSty} min-h-[calc(100vh-180px)]`}>
|
||||
<div className="relative flex w-[90%] xl:w-[800px] mx-auto mt-[50px]">
|
||||
<TextArea
|
||||
rows={10}
|
||||
maxLength={500}
|
||||
placeholder="记录此刻!"
|
||||
value={content}
|
||||
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="flex space-x-2 overflow-x-auto scrollbar-hide">
|
||||
{imageList.length > 0 && imageList.map((item, index) => (
|
||||
<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)}>
|
||||
<RiDeleteBinLine className="text-white" />
|
||||
<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">
|
||||
{imageList.length > 0 && imageList.map((item, index) => (
|
||||
<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)}>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
))}
|
||||
<Dropdown menu={dropdownItems} placement="top">
|
||||
<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" />
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
<Dropdown menu={dropdownItems} placement="top">
|
||||
<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" />
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
icon={<BiLogoTelegram className="text-xl" />}
|
||||
className="absolute bottom-4 right-4"
|
||||
onClick={onSubmit}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
icon={<BiLogoTelegram className="text-xl" />}
|
||||
className="absolute bottom-4 right-4"
|
||||
onClick={onSubmit}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</Spin>
|
||||
|
||||
<FileUpload
|
||||
dir="record"
|
||||
|
||||
@@ -5,17 +5,20 @@ import CardDataStats from "@/components/CardDataStats"
|
||||
import { AiOutlineEye, AiOutlineMeh, AiOutlineStock, AiOutlineFieldTime } from "react-icons/ai";
|
||||
import { useEffect, useState } from "react"
|
||||
import dayjs from 'dayjs';
|
||||
import { Spin } from "antd";
|
||||
|
||||
export default () => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const [stats, setStats] = useState({
|
||||
pv: 0,
|
||||
ip: 0,
|
||||
bounce: 0,
|
||||
avgTime: "",
|
||||
});
|
||||
|
||||
|
||||
const date = dayjs(new Date()).format("YYYY/MM/DD");
|
||||
|
||||
|
||||
const formatTime = (seconds: number) => {
|
||||
// 四舍五入到最接近的整数
|
||||
const roundedSeconds = Math.round(seconds);
|
||||
@@ -28,49 +31,58 @@ export default () => {
|
||||
|
||||
// 获取统计数据
|
||||
const getDataList = async () => {
|
||||
const siteId = import.meta.env.VITE_BAIDU_TONGJI_SITE_ID;
|
||||
const token = import.meta.env.VITE_BAIDU_TONGJI_ACCESS_TOKEN;
|
||||
setLoading(true)
|
||||
|
||||
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`);
|
||||
const data = await response.json();
|
||||
const { result } = data;
|
||||
try {
|
||||
const siteId = import.meta.env.VITE_BAIDU_TONGJI_SITE_ID;
|
||||
const token = import.meta.env.VITE_BAIDU_TONGJI_ACCESS_TOKEN;
|
||||
|
||||
let pv = 0;
|
||||
let ip = 0;
|
||||
let bounce = 0;
|
||||
let avgTime = 0;
|
||||
let count = 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`);
|
||||
const data = await response.json();
|
||||
const { result } = data;
|
||||
|
||||
result.items[1].forEach((item: number[]) => {
|
||||
if (!Number(item[0])) return;
|
||||
let pv = 0;
|
||||
let ip = 0;
|
||||
let bounce = 0;
|
||||
let avgTime = 0;
|
||||
let count = 0
|
||||
|
||||
// 检查并累加 pv
|
||||
if (!isNaN(Number(item[0]))) {
|
||||
pv += Number(item[0]);
|
||||
}
|
||||
result.items[1].forEach((item: number[]) => {
|
||||
if (!Number(item[0])) return;
|
||||
|
||||
// 检查并累加 ip
|
||||
if (!isNaN(Number(item[1]))) {
|
||||
ip += Number(item[1]);
|
||||
}
|
||||
// 检查并累加 pv
|
||||
if (!isNaN(Number(item[0]))) {
|
||||
pv += Number(item[0]);
|
||||
}
|
||||
|
||||
// 检查并累加 bounce
|
||||
if (!isNaN(Number(item[2]))) {
|
||||
bounce += Number(item[2]);
|
||||
}
|
||||
// 检查并累加 ip
|
||||
if (!isNaN(Number(item[1]))) {
|
||||
ip += Number(item[1]);
|
||||
}
|
||||
|
||||
// 检查并累加 avgTime
|
||||
if (!isNaN(Number(item[3]))) {
|
||||
avgTime += Number(item[3]);
|
||||
}
|
||||
// 检查并累加 bounce
|
||||
if (!isNaN(Number(item[2]))) {
|
||||
bounce += Number(item[2]);
|
||||
}
|
||||
|
||||
// 只有第三个和第四个数据都有值时才增加 count
|
||||
if (!isNaN(Number(item[2])) && !isNaN(Number(item[3]))) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
// 检查并累加 avgTime
|
||||
if (!isNaN(Number(item[3]))) {
|
||||
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(() => {
|
||||
@@ -78,7 +90,7 @@ export default () => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Spin spinning={loading}>
|
||||
{/* 基本数据 */}
|
||||
<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>
|
||||
@@ -104,6 +116,6 @@ export default () => {
|
||||
{/* <ChartTwo />
|
||||
<ChatCard /> */}
|
||||
</div>
|
||||
</>
|
||||
</Spin>
|
||||
)
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
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 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 type { Article } from '@/types/app/article';
|
||||
|
||||
import { useWebStore } from '@/stores';
|
||||
import dayjs from 'dayjs';
|
||||
@@ -23,33 +23,46 @@ export default () => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const getArticleList = async () => {
|
||||
setLoading(true);
|
||||
const { data } = await getArticleListAPI({ query: { isDel: 1 } });
|
||||
setArticleList(data as Article[]);
|
||||
try {
|
||||
const { data } = await getArticleListAPI({ query: { isDel: 1 } });
|
||||
setArticleList(data as Article[]);
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
getArticleList()
|
||||
}, []);
|
||||
|
||||
const delArticleData = async (id: number) => {
|
||||
setLoading(true);
|
||||
|
||||
// 严格删除:彻底从数据库删除,无法恢复
|
||||
await delArticleDataAPI(id);
|
||||
await getArticleList();
|
||||
form.resetFields()
|
||||
setCurrent(1)
|
||||
notification.success({ message: '🎉 删除文章成功' })
|
||||
|
||||
setLoading(false);
|
||||
try {
|
||||
// 严格删除:彻底从数据库删除,无法恢复
|
||||
await delArticleDataAPI(id);
|
||||
await getArticleList();
|
||||
form.resetFields()
|
||||
setCurrent(1)
|
||||
notification.success({ message: '🎉 删除文章成功' })
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const reductionArticleData = async (id: number) => {
|
||||
await reductionArticleDataAPI(id)
|
||||
navigate("/article")
|
||||
notification.success({ message: '🎉 还原文章成功' })
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
await reductionArticleDataAPI(id)
|
||||
navigate("/article")
|
||||
notification.success({ message: '🎉 还原文章成功' })
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 标签颜色
|
||||
|
||||
@@ -21,26 +21,32 @@ export default () => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const getArticleList = async () => {
|
||||
setLoading(true);
|
||||
const { data } = await getArticleListAPI({ query: { isDraft: 1 } });
|
||||
setArticleList(data as Article[]);
|
||||
setLoading(false);
|
||||
try {
|
||||
const { data } = await getArticleListAPI({ query: { isDraft: 1 } });
|
||||
setArticleList(data as Article[]);
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
}
|
||||
setLoading(false)
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
getArticleList()
|
||||
}, []);
|
||||
|
||||
const delArticleData = async (id: number) => {
|
||||
setLoading(true);
|
||||
|
||||
await delArticleDataAPI(id);
|
||||
await getArticleList();
|
||||
form.resetFields()
|
||||
setCurrent(1)
|
||||
notification.success({ message: '🎉 删除文章成功' })
|
||||
|
||||
setLoading(false);
|
||||
try {
|
||||
await delArticleDataAPI(id);
|
||||
await getArticleList();
|
||||
form.resetFields()
|
||||
setCurrent(1)
|
||||
notification.success({ message: '🎉 删除文章成功' })
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 标签颜色
|
||||
|
||||
@@ -34,9 +34,13 @@ export default () => {
|
||||
|
||||
// 获取目录列表
|
||||
const getDirList = async () => {
|
||||
setLoading(true)
|
||||
const { data } = await getDirListAPI()
|
||||
setDirList(data)
|
||||
try {
|
||||
const { data } = await getDirListAPI()
|
||||
setDirList(data)
|
||||
} catch (error) {
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
@@ -54,13 +58,16 @@ export default () => {
|
||||
const onDeleteImage = async (data: File) => {
|
||||
setLoading(true)
|
||||
|
||||
await delFileDataAPI(data.url)
|
||||
message.success("🎉 删除图片成功")
|
||||
getFileList(dirName)
|
||||
setFile({} as File)
|
||||
|
||||
setOpenFileInfoDrawer(false)
|
||||
setOpenFilePreviewDrawer(false)
|
||||
try {
|
||||
await delFileDataAPI(data.url)
|
||||
message.success("🎉 删除图片成功")
|
||||
getFileList(dirName)
|
||||
setFile({} as File)
|
||||
setOpenFileInfoDrawer(false)
|
||||
setOpenFilePreviewDrawer(false)
|
||||
} catch (error) {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 下载图片
|
||||
|
||||
@@ -12,6 +12,7 @@ import axios from 'axios';
|
||||
const FootprintPage = () => {
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [btnLoading, setBtnLoading] = useState(false)
|
||||
const [modalLoading, setModalLoading] = useState(false)
|
||||
|
||||
const [footprintList, setFootprintList] = useState<Footprint[]>([]);
|
||||
const [isModelOpen, setIsModelOpen] = useState(false);
|
||||
@@ -86,13 +87,18 @@ const FootprintPage = () => {
|
||||
const { RangePicker } = DatePicker;
|
||||
|
||||
const getFootprintList = async () => {
|
||||
setLoading(true);
|
||||
const { data } = await getFootprintListAPI();
|
||||
setFootprintList(data as Footprint[]);
|
||||
try {
|
||||
const { data } = await getFootprintListAPI();
|
||||
setFootprintList(data as Footprint[]);
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
getFootprintList();
|
||||
}, []);
|
||||
|
||||
@@ -105,10 +111,14 @@ const FootprintPage = () => {
|
||||
|
||||
const delFootprintData = async (id: number) => {
|
||||
setLoading(true);
|
||||
await delFootprintDataAPI(id);
|
||||
notification.success({ message: '🎉 删除足迹成功' });
|
||||
getFootprintList();
|
||||
setLoading(false);
|
||||
|
||||
try {
|
||||
await delFootprintDataAPI(id);
|
||||
notification.success({ message: '🎉 删除足迹成功' });
|
||||
getFootprintList();
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const addFootprintData = () => {
|
||||
@@ -119,60 +129,78 @@ const FootprintPage = () => {
|
||||
};
|
||||
|
||||
const editFootprintData = async (id: number) => {
|
||||
setIsMethod("edit");
|
||||
setLoading(true);
|
||||
setIsModelOpen(true);
|
||||
setModalLoading(true);
|
||||
|
||||
const { data } = await getFootprintDataAPI(id);
|
||||
try {
|
||||
setIsMethod("edit");
|
||||
setIsModelOpen(true);
|
||||
|
||||
data.images = (data.images as string[]).join("\n")
|
||||
data.createTime = dayjs(+data.createTime)
|
||||
const { data } = await getFootprintDataAPI(id);
|
||||
|
||||
setFootprint(data);
|
||||
form.setFieldsValue(data);
|
||||
setLoading(false);
|
||||
data.images = (data.images as string[]).join("\n")
|
||||
data.createTime = dayjs(+data.createTime)
|
||||
|
||||
setFootprint(data);
|
||||
form.setFieldsValue(data);
|
||||
} catch (error) {
|
||||
setModalLoading(false);
|
||||
}
|
||||
|
||||
setModalLoading(false);
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
setBtnLoading(true)
|
||||
|
||||
form.validateFields().then(async (values: Footprint) => {
|
||||
values.createTime = values.createTime.valueOf()
|
||||
values.images = values.images ? (values.images as string).split("\n") : []
|
||||
try {
|
||||
form.validateFields().then(async (values: Footprint) => {
|
||||
values.createTime = values.createTime.valueOf()
|
||||
values.images = values.images ? (values.images as string).split("\n") : []
|
||||
|
||||
if (isMethod === "edit") {
|
||||
await editFootprintDataAPI({ ...footprint, ...values });
|
||||
message.success('🎉 修改足迹成功');
|
||||
} else {
|
||||
await addFootprintDataAPI({ ...footprint, ...values });
|
||||
message.success('🎉 新增足迹成功');
|
||||
}
|
||||
if (isMethod === "edit") {
|
||||
await editFootprintDataAPI({ ...footprint, ...values });
|
||||
message.success('🎉 修改足迹成功');
|
||||
} else {
|
||||
await addFootprintDataAPI({ ...footprint, ...values });
|
||||
message.success('🎉 新增足迹成功');
|
||||
}
|
||||
|
||||
reset()
|
||||
getFootprintList();
|
||||
});
|
||||
|
||||
setBtnLoading(false)
|
||||
reset()
|
||||
getFootprintList();
|
||||
});
|
||||
} catch (error) {
|
||||
setBtnLoading(false)
|
||||
}
|
||||
};
|
||||
|
||||
const closeModel = () => reset();
|
||||
|
||||
const onFilterSubmit = async (values: FilterForm) => {
|
||||
const query: FilterData = {
|
||||
key: values.address,
|
||||
startDate: values.createTime && values.createTime[0].valueOf() + '',
|
||||
endDate: values.createTime && values.createTime[1].valueOf() + ''
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
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 });
|
||||
setFootprintList(data as Footprint[]);
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
// 通过详细地址获取纬度
|
||||
const getGeocode = async () => {
|
||||
const address = form.getFieldValue("address")
|
||||
|
||||
setModalLoading(true)
|
||||
|
||||
try {
|
||||
const address = form.getFieldValue("address")
|
||||
|
||||
const { data } = await axios.get('https://restapi.amap.com/v3/geocode/geo', {
|
||||
params: {
|
||||
address,
|
||||
@@ -187,16 +215,17 @@ const FootprintPage = () => {
|
||||
// 立即触发校验
|
||||
form.validateFields(['position']);
|
||||
|
||||
setModalLoading(false)
|
||||
|
||||
return data.geocodes[0].location;
|
||||
} else {
|
||||
message.warning('未找到该地址的经纬度');
|
||||
return '';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取地理编码时出错:', error);
|
||||
message.error('获取地理编码时出错');
|
||||
return '';
|
||||
setModalLoading(false)
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -237,7 +266,7 @@ const FootprintPage = () => {
|
||||
/>
|
||||
</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.Item label="标题" name="title" rules={[{ required: true, message: '标题不能为空' }]}>
|
||||
<Input placeholder="请输入标题" />
|
||||
|
||||
@@ -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 GitHubCalendar from 'react-github-calendar';
|
||||
import Title from '@/components/Title';
|
||||
@@ -12,6 +12,8 @@ interface Commit {
|
||||
}
|
||||
|
||||
const Home = () => {
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
|
||||
const [year, setYear] = useState<number>(new Date().getFullYear())
|
||||
const [yearList, setYearList] = useState<{ value: number, label: string }[]>([])
|
||||
|
||||
@@ -21,32 +23,40 @@ const Home = () => {
|
||||
|
||||
// 从github获取最近10次迭代记录
|
||||
const getCommitData = async (project: string) => {
|
||||
const res = await fetch(`https://api.github.com/repos/LiuYuYang01/${project}/commits?per_page=10`)
|
||||
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
|
||||
}
|
||||
))
|
||||
try {
|
||||
const res = await fetch(`https://api.github.com/repos/LiuYuYang01/${project}/commits?per_page=10`)
|
||||
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
|
||||
}
|
||||
))
|
||||
|
||||
switch (project) {
|
||||
case "ThriveX-Blog":
|
||||
sessionStorage.setItem('blog_project_iterative', JSON.stringify(result))
|
||||
setBlog_IterativeRecording(result)
|
||||
break;
|
||||
case "ThriveX-Admin":
|
||||
sessionStorage.setItem('admin_project_iterative', JSON.stringify(result))
|
||||
setAdmin_IterativeRecording(result)
|
||||
break;
|
||||
case "ThriveX-Server":
|
||||
sessionStorage.setItem('server_project_iterative', JSON.stringify(result))
|
||||
setServer_IterativeRecording(result)
|
||||
break;
|
||||
switch (project) {
|
||||
case "ThriveX-Blog":
|
||||
sessionStorage.setItem('blog_project_iterative', JSON.stringify(result))
|
||||
setBlog_IterativeRecording(result)
|
||||
break;
|
||||
case "ThriveX-Admin":
|
||||
sessionStorage.setItem('admin_project_iterative', JSON.stringify(result))
|
||||
setAdmin_IterativeRecording(result)
|
||||
break;
|
||||
case "ThriveX-Server":
|
||||
sessionStorage.setItem('server_project_iterative', JSON.stringify(result))
|
||||
setServer_IterativeRecording(result)
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
|
||||
// 获取当前年份
|
||||
const currentYear = dayjs().year();
|
||||
// 生成最近10年的年份数组
|
||||
@@ -62,48 +72,52 @@ const Home = () => {
|
||||
|
||||
const server_project_iterative = JSON.parse(sessionStorage.getItem('server_project_iterative') || '[]')
|
||||
server_project_iterative.length ? setServer_IterativeRecording(server_project_iterative) : getCommitData("ThriveX-Server")
|
||||
|
||||
setLoading(false)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title value='项目迭代记录'></Title>
|
||||
|
||||
<Card className='mt-2 min-h-[calc(100vh-180px)]'>
|
||||
<div className='flex flex-col items-center mt-2 mb-22'>
|
||||
<div className='ml-5 mb-6'>
|
||||
<span>年份切换:</span>
|
||||
<Spin spinning={loading}>
|
||||
<Card className='mt-2 min-h-[calc(100vh-180px)]'>
|
||||
<div className='flex flex-col items-center mt-2 mb-22'>
|
||||
<div className='ml-5 mb-6'>
|
||||
<span>年份切换:</span>
|
||||
|
||||
<Select
|
||||
size='small'
|
||||
defaultValue={year}
|
||||
options={yearList}
|
||||
onChange={setYear}
|
||||
className='w-20'
|
||||
/>
|
||||
<Select
|
||||
size='small'
|
||||
defaultValue={year}
|
||||
options={yearList}
|
||||
onChange={setYear}
|
||||
className='w-20'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<GitHubCalendar username="liuyuyang01" year={year} />
|
||||
</div>
|
||||
|
||||
<GitHubCalendar username="liuyuyang01" year={year} />
|
||||
</div>
|
||||
<div className='overflow-auto w-full'>
|
||||
<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='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='w-[400px] mx-[50px]'>
|
||||
<h3 className='text-xl text-center pb-6 font-bold text-gradient block'>ThriveX-Admin</h3>
|
||||
<Timeline mode="left" items={admin_iterativeRecording} />
|
||||
</div>
|
||||
|
||||
<div className='w-[400px] mx-[50px]'>
|
||||
<h3 className='text-xl text-center pb-6 font-bold text-gradient block'>ThriveX-Admin</h3>
|
||||
<Timeline mode="left" items={admin_iterativeRecording} />
|
||||
</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 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>
|
||||
</Card>
|
||||
</Card>
|
||||
</Spin>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,6 +7,8 @@ import { loginDataAPI } from '@/api/User';
|
||||
import { useUserStore } from '@/stores';
|
||||
|
||||
const LoginPage = () => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const [form] = useForm();
|
||||
const [isPassVisible, setIsPassVisible] = useState(false);
|
||||
const store = useUserStore();
|
||||
@@ -15,20 +17,28 @@ const LoginPage = () => {
|
||||
const returnUrl = new URLSearchParams(location.search).get('returnUrl') || '/';
|
||||
|
||||
const onSubmit = async () => {
|
||||
const values = await form.validateFields();
|
||||
const { data } = await loginDataAPI(values);
|
||||
setLoading(true)
|
||||
|
||||
// 将用户信息和token保存起来
|
||||
store.setToken(data.token);
|
||||
store.setUser(data.user);
|
||||
store.setRole(data.role)
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
const { data } = await loginDataAPI(values);
|
||||
|
||||
notification.success({
|
||||
message: '🎉 登录成功',
|
||||
description: `Hello ${data.user.name} 欢迎回来`,
|
||||
});
|
||||
// 将用户信息和token保存起来
|
||||
store.setToken(data.token);
|
||||
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 (
|
||||
@@ -70,7 +80,7 @@ const LoginPage = () => {
|
||||
</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>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,9 @@ import { titleSty } from '@/styles/sty';
|
||||
const StoragePage = () => {
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [btnLoading, setBtnLoading] = useState(false);
|
||||
const [modalLoading, setModalLoading] = useState(false)
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
const [oss, setOss] = useState<Oss>({} as Oss);
|
||||
const [ossList, setOssList] = useState<Oss[]>([]);
|
||||
const [platformList, setPlatformList] = useState<{ label: string, value: string, disabled: boolean }[]>([]);
|
||||
@@ -51,9 +53,9 @@ const StoragePage = () => {
|
||||
render: (_, record: Oss) => (
|
||||
<div className='space-x-2'>
|
||||
{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>
|
||||
@@ -83,44 +85,71 @@ const StoragePage = () => {
|
||||
};
|
||||
|
||||
const getOssList = async () => {
|
||||
setLoading(true);
|
||||
const { data } = await getOssListAPI();
|
||||
setOssList(data);
|
||||
try {
|
||||
const { data } = await getOssListAPI();
|
||||
setOssList(data);
|
||||
} catch (error) {
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
getOssList();
|
||||
getOssPlatformList()
|
||||
}, []);
|
||||
|
||||
const handleEnable = async (id: number) => {
|
||||
await enableOssDataAPI(id);
|
||||
message.success('启用成功');
|
||||
getOssList();
|
||||
const enableOssData = async (id: number) => {
|
||||
try {
|
||||
await enableOssDataAPI(id);
|
||||
await getOssList();
|
||||
message.success('启用成功');
|
||||
} catch (error) {
|
||||
setLoading(false)
|
||||
}
|
||||
};
|
||||
|
||||
const handleDisable = async (id: number) => {
|
||||
await disableOssDataAPI(id);
|
||||
message.success('禁用成功');
|
||||
getOssList();
|
||||
const disableOssData = async (id: number) => {
|
||||
try {
|
||||
await disableOssDataAPI(id);
|
||||
await getOssList();
|
||||
message.success('禁用成功');
|
||||
} catch (error) {
|
||||
setLoading(false)
|
||||
}
|
||||
};
|
||||
|
||||
const editOssData = async (record: Oss) => {
|
||||
setOss(record);
|
||||
const { data } = await getOssDataAPI(record.id)
|
||||
form.setFieldsValue(data);
|
||||
setIsModalOpen(true);
|
||||
setModalLoading(true)
|
||||
|
||||
try {
|
||||
setIsModalOpen(true);
|
||||
|
||||
const { data } = await getOssDataAPI(record.id)
|
||||
setOss(data);
|
||||
form.setFieldsValue(data);
|
||||
} catch (error) {
|
||||
setModalLoading(false)
|
||||
}
|
||||
|
||||
setModalLoading(false)
|
||||
};
|
||||
|
||||
const delOssData = async (id: number) => {
|
||||
setLoading(true);
|
||||
await delOssDataAPI(id);
|
||||
message.success('🎉 删除存储配置成功');
|
||||
getOssList();
|
||||
|
||||
try {
|
||||
await delOssDataAPI(id);
|
||||
await getOssList();
|
||||
message.success('🎉 删除存储配置成功');
|
||||
} catch (error) {
|
||||
setLoading(false)
|
||||
}
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
const addOssData = () => {
|
||||
setOss({} as Oss);
|
||||
form.resetFields();
|
||||
form.setFieldsValue({});
|
||||
@@ -151,20 +180,22 @@ const StoragePage = () => {
|
||||
form.resetFields();
|
||||
setBtnLoading(false);
|
||||
} catch (error) {
|
||||
console.error('表单验证失败:', error);
|
||||
setBtnLoading(false);
|
||||
}
|
||||
|
||||
setBtnLoading(false)
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title value="存储管理">
|
||||
<Button type="primary" size='large' onClick={handleAdd}>新增配置</Button>
|
||||
<Button type="primary" size='large' onClick={addOssData}>新增配置</Button>
|
||||
</Title>
|
||||
|
||||
<Card className={`${titleSty} min-h-[calc(100vh-180px)]`}>
|
||||
<Table
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
dataSource={ossList}
|
||||
columns={columns}
|
||||
scroll={{ x: 'max-content' }}
|
||||
@@ -172,11 +203,11 @@ const StoragePage = () => {
|
||||
position: ['bottomCenter'],
|
||||
pageSize: 8
|
||||
}}
|
||||
loading={loading}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Modal
|
||||
loading={modalLoading}
|
||||
title={oss.id ? "编辑存储配置" : "新增存储配置"}
|
||||
open={isModalOpen}
|
||||
onCancel={handleCancel}
|
||||
|
||||
Reference in New Issue
Block a user