大改动

This commit is contained in:
宇阳
2025-01-11 23:26:36 +08:00
parent 2a1391da72
commit 34198b212b
6 changed files with 512 additions and 387 deletions

View File

@@ -1,12 +1,15 @@
import { useState, useEffect } from 'react';
import { Table, Button, Form, Input, Popconfirm, message, Card } from 'antd';
import { getTagListAPI, addTagDataAPI, editTagDataAPI, delTagDataAPI } from '@/api/Tag';
import { getTagListAPI, addTagDataAPI, editTagDataAPI, delTagDataAPI, getTagDataAPI } from '@/api/Tag';
import { Tag } from '@/types/app/tag';
import Title from '@/components/Title';
import { ColumnsType } from 'antd/es/table';
const TagPage = () => {
const [loading, setLoading] = useState<boolean>(false);
const [btnLoading, setBtnLoading] = useState(false)
const [form] = Form.useForm();
const [tag, setTag] = useState<Tag>({} as Tag);
const [list, setList] = useState<Tag[]>([]);
@@ -28,49 +31,76 @@ const TagPage = () => {
];
const getTagList = async () => {
setLoading(true);
const { data } = await getTagListAPI();
setList(data as Tag[]);
try {
const { data } = await getTagListAPI();
setList(data as Tag[]);
} catch (error) {
setLoading(false);
}
setLoading(false);
};
useEffect(() => {
setLoading(true);
getTagList();
}, []);
const [form] = Form.useForm();
const editTagData = (record: Tag) => {
setTag(record);
form.setFieldsValue(record);
const editTagData = async (record: Tag) => {
setLoading(true);
try {
const { data } = await getTagDataAPI(record.id)
setTag(data);
form.setFieldsValue(data);
} catch (error) {
setLoading(false);
}
setLoading(false);
};
const delTagData = async (id: number) => {
setLoading(true);
await delTagDataAPI(id);
message.success('🎉 删除标签成功');
getTagList();
try {
await delTagDataAPI(id);
await getTagList();
message.success('🎉 删除标签成功');
} catch (error) {
setLoading(false);
}
};
const onSubmit = async () => {
setLoading(true);
form.validateFields().then(async (values: Tag) => {
if (tag.id) {
await editTagDataAPI({ ...tag, ...values });
message.success('🎉 编辑标签成功');
} else {
await addTagDataAPI(values);
message.success('🎉 新增标签成功');
}
setBtnLoading(true);
getTagList();
form.resetFields();
form.setFieldsValue({ name: '' })
setTag({} as Tag);
});
try {
form.validateFields().then(async (values: Tag) => {
if (tag.id) {
await editTagDataAPI({ ...tag, ...values });
message.success('🎉 编辑标签成功');
} else {
await addTagDataAPI(values);
message.success('🎉 新增标签成功');
}
await getTagList();
form.resetFields();
form.setFieldsValue({ name: '' })
setTag({} as Tag);
});
} catch (error) {
setLoading(false);
setBtnLoading(false);
}
setBtnLoading(false);
};
return (
<>
<div>
<Title value="标签管理" />
<div className='flex md:justify-between flex-col md:flex-row mx-auto mt-2'>
@@ -81,14 +111,13 @@ const TagPage = () => {
initialValues={tag}
onFinish={onSubmit}
size='large'
>
<Form.Item label="标签名称" name="name" rules={[{ required: true, message: '标签名称不能为空' }]}>
<Input placeholder="请输入标签名称" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" loading={loading} className="w-full">{tag.id ? '编辑标签' : '新增标签'}</Button>
<Button type="primary" htmlType="submit" loading={btnLoading} className="w-full">{tag.id ? '编辑标签' : '新增标签'}</Button>
</Form.Item>
</Form>
</Card>
@@ -107,7 +136,7 @@ const TagPage = () => {
/>
</Card>
</div>
</>
</div>
);
};

View File

@@ -17,7 +17,9 @@ import dayjs from 'dayjs';
const UserPage = () => {
const [loading, setLoading] = useState<boolean>(false);
const [btnLoading, setBtnLoading] = useState(false)
const [editLoading, setEditLoading] = useState(false)
const [form] = Form.useForm();
const store = useUserStore()
const [userList, setUserList] = useState<User[]>([]);
@@ -107,76 +109,105 @@ const UserPage = () => {
},
];
const [userForm] = Form.useForm();
const getUserList = async () => {
setLoading(true);
const { data } = await getUserListAPI();
setUserList(data as User[]);
try {
const { data } = await getUserListAPI();
setUserList(data as User[]);
setLoading(false);
} catch (error) {
setLoading(false);
}
setLoading(false);
};
const getRoleList = async () => {
const { data } = await getRoleListAPI();
setRoleList(data as Role[]);
console.log(data);
setRoleList(data);
};
useEffect(() => {
setLoading(true);
getUserList();
getRoleList()
}, []);
const delUserData = async (id: number) => {
setLoading(true);
await delUserDataAPI(id);
await getUserList();
notification.success({ message: '🎉 删除用户成功' });
setLoading(false);
try {
await delUserDataAPI(id);
await getUserList();
notification.success({ message: '🎉 删除用户成功' });
} catch (error) {
setLoading(false);
}
};
const editUserData = async (id: number) => {
const { data } = await getUserDataAPI(id)
setUser({ ...data, role: data.role.id });
setEditLoading(true);
userForm.setFieldsValue({ ...data, roleId: data.role.id });
setDrawerVisible(true);
try {
setDrawerVisible(true);
const { data } = await getUserDataAPI(id)
setUser(data);
form.setFieldsValue(data);
} catch (error) {
setEditLoading(false);
}
setEditLoading(false);
};
const reset = () => {
setUser({} as User)
userForm.resetFields()
form.resetFields()
}
const onSubmit = async () => {
setBtnLoading(true)
userForm.validateFields().then(async (values: User) => {
if (user.id) {
await editUserDataAPI({ ...user, ...values });
notification.success({ message: '🎉 编辑用户成功' });
} else {
await addUserDataAPI({ ...values, password: "123456", createTime: new Date().getTime().toString() });
notification.success({ message: '🎉 创建用户成功' });
}
setDrawerVisible(false);
getUserList();
})
try {
form.validateFields().then(async (values: User) => {
if (user.id) {
await editUserDataAPI({ ...user, ...values });
notification.success({ message: '🎉 编辑用户成功' });
} else {
await addUserDataAPI({ ...values, password: "123456", createTime: new Date().getTime().toString() });
notification.success({ message: '🎉 创建用户成功' });
}
await getUserList();
setDrawerVisible(false);
reset()
})
} catch (error) {
setBtnLoading(false)
}
setBtnLoading(false)
};
const [filterForm] = Form.useForm();
const onFilterSubmit = async (values: FilterForm) => {
const query: FilterUser = {
key: values.name,
roleId: values.role,
startDate: values.createTime && values.createTime[0].valueOf() + '',
endDate: values.createTime && values.createTime[1].valueOf() + ''
setLoading(true)
try {
const query: FilterUser = {
key: values.name,
roleId: values.role,
startDate: values.createTime && values.createTime[0].valueOf() + '',
endDate: values.createTime && values.createTime[1].valueOf() + ''
}
const { data } = await getUserListAPI({ query });
setUserList(data as User[]);
} catch (error) {
setLoading(false)
}
const { data } = await getUserListAPI({ query });
setUserList(data as User[]);
setLoading(false)
}
return (
@@ -186,7 +217,7 @@ const UserPage = () => {
</Title>
<Card className='my-2 overflow-scroll'>
<Form form={filterForm} layout="inline" onFinish={onFilterSubmit} autoComplete="off" className='flex-nowrap'>
<Form layout="inline" onFinish={onFilterSubmit} autoComplete="off" className='flex-nowrap'>
<Form.Item label="名称" name="name" className='min-w-[200px]'>
<Input placeholder='请输入名称' />
</Form.Item>
@@ -227,9 +258,10 @@ const UserPage = () => {
setDrawerVisible(false)
}}
open={drawerVisible}
loading={editLoading}
>
<Form
form={userForm}
form={form}
layout="vertical"
size='large'
onFinish={onSubmit}
@@ -278,7 +310,7 @@ const UserPage = () => {
label="角色"
rules={[{ required: true, message: '请选择角色' }]}
>
<Select options={roleList.map(item => ({ label: item.name, value: item.id }))} placeholder="选择用户角色" />
<Select options={roleList.map(item => ({ label: item.name, value: +item.id }))} placeholder="选择用户角色" />
</Form.Item>
<Form.Item>

View File

@@ -9,23 +9,35 @@ import dayjs from 'dayjs';
const WallPage = () => {
const [loading, setLoading] = useState(false);
const [wall, setWall] = useState<Wall>();
const [wall, setWall] = useState<Wall>({} as Wall);
const [list, setList] = useState<Wall[]>([]);
const [isModalOpen, setIsModalOpen] = useState(false);
const getWallList = async () => {
const { data } = await getWallListAPI();
try {
const { data } = await getWallListAPI();
setList(data)
} catch (error) {
setLoading(false)
}
setList(data)
setLoading(false)
}
const delWallData = async (id: number) => {
setLoading(true)
await delWallDataAPI(id);
getWallList();
message.success('🎉 删除留言成功');
try {
await delWallDataAPI(id);
await getWallList();
message.success('🎉 删除留言成功');
} catch (error) {
setLoading(false)
}
setLoading(false)
};
// 获取留言的分类列表
@@ -105,16 +117,24 @@ const WallPage = () => {
const { RangePicker } = DatePicker;
const onSubmit = async (values: FilterForm) => {
const query: FilterWall = {
key: values.content,
cateId: values.cateId,
startDate: values.createTime && values.createTime[0].valueOf() + '',
endDate: values.createTime && values.createTime[1].valueOf() + ''
const onFilterSubmit = async (values: FilterForm) => {
setLoading(true)
try {
const query: FilterWall = {
key: values.content,
cateId: values.cateId,
startDate: values.createTime && values.createTime[0].valueOf() + '',
endDate: values.createTime && values.createTime[1].valueOf() + ''
}
const { data } = await getWallListAPI({ query });
setList(data)
} catch (error) {
setLoading(false)
}
const { data } = await getWallListAPI({ query });
setList(data)
setLoading(false)
}
return (
@@ -122,7 +142,7 @@ const WallPage = () => {
<Title value='留言管理' />
<Card className='my-2 overflow-scroll'>
<Form layout="inline" onFinish={onSubmit} autoComplete="off" className='flex-nowrap'>
<Form layout="inline" onFinish={onFilterSubmit} autoComplete="off" className='flex-nowrap'>
<Form.Item label="内容" name="content" className='min-w-[200px]'>
<Input placeholder='请输入内容关键词' />
</Form.Item>

View File

@@ -1,15 +1,18 @@
import { useState, useEffect } from 'react';
import { Tabs, Input, Button, Form, Spin, Empty, Card, Popconfirm, Select, message } from 'antd';
import { SearchOutlined } from '@ant-design/icons';
import { getLinkListAPI, addLinkDataAPI, editLinkDataAPI, delLinkDataAPI, getWebTypeListAPI } from '@/api/Web';
import { getLinkListAPI, addLinkDataAPI, editLinkDataAPI, delLinkDataAPI, getWebTypeListAPI, getLinkDataAPI } from '@/api/Web';
import { WebType, Web } from '@/types/app/web';
import Title from '@/components/Title';
import { RuleObject } from 'antd/es/form';
import './index.scss';
const LinkPage = () => {
export default () => {
const [loading, setLoading] = useState(false);
const [btnLoading, setBtnLoading] = useState(false)
const [editLoading, setEditLoading] = useState(false)
const [form] = Form.useForm();
const [tab, setTab] = useState<string>('list');
const [list, setList] = useState<Web[]>([]);
@@ -23,13 +26,18 @@ const LinkPage = () => {
// 获取网站列表
const getLinkList = async () => {
const { data } = await getLinkListAPI();
data.sort((a, b) => a.order - b.order)
data.sort((a, b) => a.type.order - b.type.order)
try {
const { data } = await getLinkListAPI();
data.sort((a, b) => a.order - b.order)
data.sort((a, b) => a.type.order - b.type.order)
setList(data as Web[]);
setListTemp(data as Web[]);
setLoading(false);
setList(data);
setListTemp(data);
} catch (error) {
setLoading(false);
}
setLoading(false)
};
// 获取网站类型列表
@@ -50,18 +58,31 @@ const LinkPage = () => {
const deleteLinkData = async (id: number) => {
setLoading(true);
await delLinkDataAPI(id);
message.success('🎉 删除网站成功');
getLinkList();
try {
await delLinkDataAPI(id);
await getLinkList();
message.success('🎉 删除网站成功');
} catch (error) {
setLoading(false)
}
};
const [form] = Form.useForm();
const editLinkData = async (record: Web) => {
setEditLoading(true)
const editLinkData = (item: Web) => {
setTab('operate');
setIsMethod("edit");
setLink(item);
form.setFieldsValue(item); // 回显数据
try {
setTab('operate');
setIsMethod("edit");
const { data } = await getLinkDataAPI(record.id)
setLink(data);
form.setFieldsValue(data);
} catch (error) {
setEditLoading(false)
}
setEditLoading(false)
};
// 做一些初始化的事情
@@ -79,19 +100,23 @@ const LinkPage = () => {
const submit = async () => {
setBtnLoading(true)
form.validateFields().then(async (values: Web) => {
if (isMethod === "edit") {
await editLinkDataAPI({ ...link, ...values });
message.success('🎉 编辑网站成功');
} else {
await addLinkDataAPI({ ...values, createTime: new Date().getTime().toString() });
message.success('🎉 新增网站成功');
}
try {
form.validateFields().then(async (values: Web) => {
if (isMethod === "edit") {
await editLinkDataAPI({ ...link, ...values });
message.success('🎉 编辑网站成功');
} else {
await addLinkDataAPI({ ...values, createTime: new Date().getTime().toString() });
message.success('🎉 新增网站成功');
}
getLinkList();
setTab('list');
reset()
});
await getLinkList();
reset()
setTab('list');
});
} catch (error) {
setBtnLoading(false)
}
setBtnLoading(false)
};
@@ -164,47 +189,49 @@ const LinkPage = () => {
<>
<h2 className="text-xl pb-4 text-center">{isMethod === "edit" ? '编辑网站' : '新增网站'}</h2>
<div className='w-full md:w-[500px] mx-auto'>
<Form form={form} layout="vertical" size='large' initialValues={link} onFinish={submit}>
<Form.Item label="网站标题" name="title" rules={[{ required: true, message: '网站标题不能为空' }]}>
<Input placeholder="Thrive" />
</Form.Item>
<Spin spinning={editLoading}>
<div className='w-full md:w-[500px] mx-auto'>
<Form form={form} layout="vertical" size='large' initialValues={link} onFinish={submit}>
<Form.Item label="网站标题" name="title" rules={[{ required: true, message: '网站标题不能为空' }]}>
<Input placeholder="Thrive" />
</Form.Item>
<Form.Item label="网站描述" name="description" rules={[{ required: true, message: '网站描述不能为空' }]}>
<Input placeholder="记录前端、Python、Java点点滴滴" />
</Form.Item>
<Form.Item label="网站描述" name="description" rules={[{ required: true, message: '网站描述不能为空' }]}>
<Input placeholder="记录前端、Python、Java点点滴滴" />
</Form.Item>
<Form.Item label="站长邮箱" name="email">
<Input placeholder="3311118881@qq.com" />
</Form.Item>
<Form.Item label="站长邮箱" name="email">
<Input placeholder="3311118881@qq.com" />
</Form.Item>
<Form.Item label="网站图标" name="image" rules={[{ required: true, message: '网站图标不能为空' }]}>
<Input placeholder="https://liuyuyang.net/logo.png" />
</Form.Item>
<Form.Item label="网站图标" name="image" rules={[{ required: true, message: '网站图标不能为空' }]}>
<Input placeholder="https://liuyuyang.net/logo.png" />
</Form.Item>
<Form.Item label="网站链接" name="url" rules={[{ required: true, message: '网站链接不能为空' }, { validator: validateURL }]}>
<Input placeholder="https://liuyuyang.net/" />
</Form.Item>
<Form.Item label="网站链接" name="url" rules={[{ required: true, message: '网站链接不能为空' }, { validator: validateURL }]}>
<Input placeholder="https://liuyuyang.net/" />
</Form.Item>
<Form.Item label="订阅地址" name="rss" rules={[{ validator: validateURL }]}>
<Input placeholder="https://liuyuyang.net/api/rss" />
</Form.Item>
<Form.Item label="订阅地址" name="rss" rules={[{ validator: validateURL }]}>
<Input placeholder="https://liuyuyang.net/api/rss" />
</Form.Item>
<Form.Item name="typeId" label="网站类型" rules={[{ required: true, message: '网站类型不能为空' }]}>
<Select placeholder="请选择网站类型" allowClear>
{typeList.map(item => <Option key={item.id} value={item.id}>{item.name}</Option>)}
</Select>
</Form.Item>
<Form.Item name="typeId" label="网站类型" rules={[{ required: true, message: '网站类型不能为空' }]}>
<Select placeholder="请选择网站类型" allowClear>
{typeList.map(item => <Option key={item.id} value={item.id}>{item.name}</Option>)}
</Select>
</Form.Item>
<Form.Item label="顺序" name="order">
<Input placeholder="请输入网站顺序(值越小越靠前)" />
</Form.Item>
<Form.Item label="顺序" name="order">
<Input placeholder="请输入网站顺序(值越小越靠前)" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" loading={btnLoading} className='w-full'>{isMethod === "edit" ? '编辑网站' : '新增网站'}</Button>
</Form.Item>
</Form>
</div>
<Form.Item>
<Button type="primary" htmlType="submit" loading={btnLoading} className='w-full'>{isMethod === "edit" ? '编辑网站' : '新增网站'}</Button>
</Form.Item>
</Form>
</div>
</Spin>
</>
),
},
@@ -219,6 +246,4 @@ const LinkPage = () => {
</Card>
</>
);
};
export default LinkPage;
};

View File

@@ -0,0 +1,210 @@
import { useState } from "react";
import { Button, Dropdown, message, Modal } from "antd";
import { delLinkDataAPI, auditWebDataAPI } from '@/api/Web';
import { auditCommentDataAPI, delCommentDataAPI, addCommentDataAPI } from "@/api/Comment";
import { auditWallDataAPI, delWallDataAPI } from "@/api/Wall";
import dayjs from 'dayjs';
import RandomAvatar from "@/components/RandomAvatar";
import { useUserStore, useWebStore } from '@/stores';
import TextArea from "antd/es/input/TextArea";
import { sendDismissEmailAPI } from "@/api/Email";
type Menu = "comment" | "link" | "wall";
interface ListItemProps {
item: any;
type: Menu;
fetchData: (type: Menu) => void;
}
export default ({ item, type, fetchData }: ListItemProps) => {
const web = useWebStore(state => state.web)
const user = useUserStore(state => state.user)
const [btnType, setBtnType] = useState<"reply" | "dismiss" | string>("")
// 通过
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);
}
btnType != "reply" && message.success('🎉 审核成功');
fetchData(type);
};
// 回复
const [isModalOpen, setIsModalOpen] = useState(false);
const [replyInfo, setReplyInfo] = useState("")
const handleReply = async () => {
// 审核通过评论
await handleApproval()
// 发送回复内容
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(),
})
message.success('🎉 回复成功');
setIsModalOpen(false)
fetchData(type);
setReplyInfo("")
setBtnType("")
}
// 驳回
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);
}
// 有内容就发送驳回通知邮件,反之直接删除
if (dismissInfo.trim().length) await sendDismissEmail()
message.success('🎉 驳回成功');
setIsModalOpen(false)
fetchData(type);
setDismissInfo("")
setBtnType("")
};
// 发送驳回通知邮件
const sendDismissEmail = async () => {
// 类型名称
let email_info = {
name: "",
type: "",
url: ""
}
switch (type) {
case "link":
email_info = {
name: item.title,
type: "友链",
url: `${web.url}/friend`,
}
break;
case "comment":
email_info = {
name: item.name,
type: "评论",
url: `${web.url}/article/${item.articleId}`,
}
break;
case "wall":
email_info = {
name: item.name,
type: "留言",
url: `${web.url}/wall/all`,
}
break;
}
// 有邮箱才会邮件通知
item.email != null && await sendDismissEmailAPI({
to: item.email,
content: dismissInfo,
recipient: email_info.name,
subject: `${email_info.type}驳回通知`,
time: dayjs(Date.now()).format('YYYY年MM月DD日 HH:mm'),
type: email_info.type,
url: email_info.url
})
}
return (
<div key={item.id}>
<div className="text-center text-xs text-[#e0e0e0] mb-4">
{dayjs(+item.createTime!).format('YYYY-MM-DD HH:mm:ss')}
</div>
<div className="flex justify-between md:p-7 rounded-md transition-colors">
<div className="flex mr-10">
{type !== "wall" ? (
<img src={item.avatar || item.image} alt="" className="w-13 h-13 border border-[#eee] rounded-full" />
) : <RandomAvatar className="w-13 h-13 border border-[#eee] rounded-full" />}
<div className="flex flex-col justify-center ml-4 px-4 py-2 min-w-[210px] text-xs md:text-sm bg-[#F9F9FD] dark:bg-[#4e5969] rounded-md">
{type === "link" ? (
<>
<div>{item.title}</div>
<div>{item.description}</div>
<div>{item.type.name}</div>
<div>{item?.url ? <a href={item?.url} target='_blank' className="hover:text-primary font-bold">{item?.url}</a> : '无网站'}</div>
</>
) : type === "comment" ? (
<>
<div>{item.name}</div>
<div>{item.content}</div>
<div>{item?.url ? <a href={item?.url} target='_blank' className="hover:text-primary font-bold">{item?.url}</a> : '无网站'}</div>
<div><a href={`${web.url}/article/${item.articleId}`} target='_blank' className="hover:text-primary">{item.articleTitle || '暂无'}</a></div>
</>
) : (
<>
<div>{item.name}</div>
<div>{item.content}</div>
</>
)}
<div>{item.email || '暂无'}</div>
</div>
</div>
<div className="flex items-end">
<Dropdown menu={{
items: type === "comment"
? [
{ key: 'ok', label: "通过", onClick: handleApproval },
{ key: 'reply', label: "回复", onClick: () => [setIsModalOpen(true), setBtnType("reply")] },
{ key: 'dismiss', label: "驳回", onClick: () => [setIsModalOpen(true), , setBtnType("dismiss")] }
]
: [
{ key: 'ok', label: "通过", onClick: handleApproval },
{ key: 'dismiss', label: "驳回", onClick: () => [setIsModalOpen(true), , setBtnType("dismiss")] }
]
}}>
<div className="flex justify-evenly items-center bg-[#F9F9FD] dark:bg-[#4e5969] w-11 h-5 rounded-md cursor-pointer">
<span className="inline-block w-2 h-2 bg-[#b5c2d3] rounded-full"></span>
<span className="inline-block w-2 h-2 bg-[#b5c2d3] rounded-full"></span>
</div>
</Dropdown>
</div>
</div>
<Modal title={btnType === "reply" ? "回复内容" : "驳回原因"} open={isModalOpen} footer={null} onCancel={() => setIsModalOpen(false)} onClose={() => setIsModalOpen(false)}>
<TextArea
value={btnType === "reply" ? replyInfo : dismissInfo}
onChange={(e) => (btnType === "reply" ? setReplyInfo(e.target.value) : setDismissInfo(e.target.value))}
placeholder={btnType === "reply" ? "请输入回复内容" : "请输入驳回原因"}
autoSize={{ minRows: 3, maxRows: 5 }}
/>
<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>
</div>
</Modal>
</div>
)
}

View File

@@ -1,8 +1,8 @@
import { useEffect, useState } from "react";
import { Button, Card, Dropdown, message, Modal } from "antd";
import { getLinkListAPI, delLinkDataAPI, auditWebDataAPI } from '@/api/Web';
import { getCommentListAPI, auditCommentDataAPI, delCommentDataAPI, addCommentDataAPI } from "@/api/Comment";
import { getWallListAPI, auditWallDataAPI, delWallDataAPI } from "@/api/Wall";
import { Card, Spin } from "antd";
import { getLinkListAPI } from '@/api/Web';
import { getCommentListAPI } from "@/api/Comment";
import { getWallListAPI } from "@/api/Wall";
import Title from "@/components/Title";
@@ -10,213 +10,14 @@ import comment from './image/comment.svg';
import info from './image/message.svg';
import link from './image/link.svg';
import dayjs from 'dayjs';
import RandomAvatar from "@/components/RandomAvatar";
import Empty from "@/components/Empty";
import { useUserStore, useWebStore } from '@/stores';
import TextArea from "antd/es/input/TextArea";
import { sendDismissEmailAPI } from "@/api/Email";
import List from "./components/List";
type Menu = "comment" | "link" | "wall";
interface ListItemProps {
item: any;
type: Menu;
fetchData: (type: Menu) => void;
}
export default () => {
const [loading, setLoading] = useState(false)
const ListItem = ({ item, type, fetchData }: ListItemProps) => {
const web = useWebStore(state => state.web)
const user = useUserStore(state => state.user)
const [btnType, setBtnType] = useState<"reply" | "dismiss" | string>("")
// 通过
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);
}
btnType != "reply" && message.success('🎉 审核成功');
fetchData(type);
};
// 回复
const [isModalOpen, setIsModalOpen] = useState(false);
const [replyInfo, setReplyInfo] = useState("")
const handleReply = async () => {
// 审核通过评论
await handleApproval()
// 发送回复内容
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(),
})
message.success('🎉 回复成功');
setIsModalOpen(false)
fetchData(type);
setReplyInfo("")
setBtnType("")
}
// 驳回
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);
}
// 有内容就发送驳回通知邮件,反之直接删除
if (dismissInfo.trim().length) await sendDismissEmail()
message.success('🎉 驳回成功');
setIsModalOpen(false)
fetchData(type);
setDismissInfo("")
setBtnType("")
};
// 发送驳回通知邮件
const sendDismissEmail = async () => {
// 类型名称
let email_info = {
name: "",
type: "",
url: ""
}
switch (type) {
case "link":
email_info = {
name: item.title,
type: "友链",
url: `${web.url}/friend`,
}
break;
case "comment":
email_info = {
name: item.name,
type: "评论",
url: `${web.url}/article/${item.articleId}`,
}
break;
case "wall":
email_info = {
name: item.name,
type: "留言",
url: `${web.url}/wall/all`,
}
break;
}
// 有邮箱才会邮件通知
item.email != null && await sendDismissEmailAPI({
to: item.email,
content: dismissInfo,
recipient: email_info.name,
subject: `${email_info.type}驳回通知`,
time: dayjs(Date.now()).format('YYYY年MM月DD日 HH:mm'),
type: email_info.type,
url: email_info.url
})
}
return (
<div key={item.id}>
<div className="text-center text-xs text-[#e0e0e0] mb-4">
{dayjs(+item.createTime!).format('YYYY-MM-DD HH:mm:ss')}
</div>
<div className="flex justify-between md:p-7 rounded-md transition-colors">
<div className="flex mr-10">
{type !== "wall" ? (
<img src={item.avatar || item.image} alt="" className="w-13 h-13 border border-[#eee] rounded-full" />
) : <RandomAvatar className="w-13 h-13 border border-[#eee] rounded-full" />}
<div className="flex flex-col justify-center ml-4 px-4 py-2 min-w-[210px] text-xs md:text-sm bg-[#F9F9FD] dark:bg-[#4e5969] rounded-md">
{type === "link" ? (
<>
<div>{item.title}</div>
<div>{item.description}</div>
<div>{item.type.name}</div>
<div>{item?.url ? <a href={item?.url} target='_blank' className="hover:text-primary font-bold">{item?.url}</a> : '无网站'}</div>
</>
) : type === "comment" ? (
<>
<div>{item.name}</div>
<div>{item.content}</div>
<div>{item?.url ? <a href={item?.url} target='_blank' className="hover:text-primary font-bold">{item?.url}</a> : '无网站'}</div>
<div><a href={`${web.url}/article/${item.articleId}`} target='_blank' className="hover:text-primary">{item.articleTitle || '暂无'}</a></div>
</>
) : (
<>
<div>{item.name}</div>
<div>{item.content}</div>
</>
)}
<div>{item.email || '暂无'}</div>
</div>
</div>
<div className="flex items-end">
<Dropdown menu={{
items: type === "comment"
? [
{ key: 'ok', label: "通过", onClick: handleApproval },
{ key: 'reply', label: "回复", onClick: () => [setIsModalOpen(true), setBtnType("reply")] },
{ key: 'dismiss', label: "驳回", onClick: () => [setIsModalOpen(true), , setBtnType("dismiss")] }
]
: [
{ key: 'ok', label: "通过", onClick: handleApproval },
{ key: 'dismiss', label: "驳回", onClick: () => [setIsModalOpen(true), , setBtnType("dismiss")] }
]
}}>
<div className="flex justify-evenly items-center bg-[#F9F9FD] dark:bg-[#4e5969] w-11 h-5 rounded-md cursor-pointer">
<span className="inline-block w-2 h-2 bg-[#b5c2d3] rounded-full"></span>
<span className="inline-block w-2 h-2 bg-[#b5c2d3] rounded-full"></span>
</div>
</Dropdown>
</div>
</div>
<Modal title={btnType === "reply" ? "回复内容" : "驳回原因"} open={isModalOpen} footer={null} onCancel={() => setIsModalOpen(false)} onClose={() => setIsModalOpen(false)}>
<TextArea
value={btnType === "reply" ? replyInfo : dismissInfo}
onChange={(e) => (btnType === "reply" ? setReplyInfo(e.target.value) : setDismissInfo(e.target.value))}
placeholder={btnType === "reply" ? "请输入回复内容" : "请输入驳回原因"}
autoSize={{ minRows: 3, maxRows: 5 }}
/>
<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>
</div>
</Modal>
</div>
)
}
const WorkPage = () => {
const activeSty = "bg-[#f9f9ff] dark:bg-[#3c5370] text-primary";
const [active, setActive] = useState<Menu>("comment");
const [commentList, setCommentList] = useState<any[]>([]);
@@ -225,19 +26,26 @@ const WorkPage = () => {
// 重新获取最新数据
const fetchData = async (type: Menu) => {
if (type === "comment") {
const { data } = await getCommentListAPI({ query: { status: 0 }, pattern: "list" });
setCommentList(data);
} else if (type === "link") {
const { data } = await getLinkListAPI({ query: { status: 0 } });
setLinkList(data);
} else if (type === "wall") {
const { data } = await getWallListAPI({ query: { status: 0 } });
setWallList(data);
try {
if (type === "comment") {
const { data } = await getCommentListAPI({ query: { status: 0 }, pattern: "list" });
setCommentList(data);
} else if (type === "link") {
const { data } = await getLinkListAPI({ query: { status: 0 } });
setLinkList(data);
} else if (type === "wall") {
const { data } = await getWallListAPI({ query: { status: 0 } });
setWallList(data);
}
} catch (error) {
setLoading(false)
}
setLoading(false)
};
useEffect(() => {
setLoading(true)
fetchData(active);
}, [active]);
@@ -246,39 +54,40 @@ const WorkPage = () => {
return <Empty />;
}
return list.map(item => (
<ListItem key={item.id} item={item} type={type} fetchData={(type) => fetchData(type)} />
<List key={item.id} item={item} type={type} fetchData={(type) => fetchData(type)} />
));
};
return (
<>
<Title value="工作台" />
<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>
<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")}
<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>
<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>
</div>
</Card>
</Card>
</Spin>
</>
);
}
export default WorkPage;
}