521 lines
15 KiB
TypeScript
521 lines
15 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
||
import { useIntl } from 'umi';
|
||
import { Button, Table, Modal, message, Input, Select, Form, Tooltip, Tag, Tabs } from 'antd';
|
||
import {
|
||
PlusOutlined,
|
||
DeleteOutlined,
|
||
ExclamationCircleOutlined,
|
||
SearchOutlined,
|
||
EyeOutlined,
|
||
} from '@ant-design/icons';
|
||
import { getNoticeList, addNotice, updateNotice, deleteNotice, batchDeleteNotice, updateNoticeStatus, updateNoticeTopStatus, getNoticeDetail } from '@/servers/api/notice';
|
||
import styles from './noticeManage.less';
|
||
import {
|
||
NoticeStatus,
|
||
NoticeStatusText,
|
||
NoticeStatusColor,
|
||
TopStatus,
|
||
ModalType,
|
||
} from '@/dicts/noticeManageDict';
|
||
|
||
import NoticeManageForm from './NoticeManageForm';
|
||
import NoticeManageInfo from './NoticeManageInfo';
|
||
|
||
const { Option } = Select;
|
||
const { TabPane } = Tabs;
|
||
|
||
type NoticeType = API.NoticeRecord & {
|
||
key: string;
|
||
};
|
||
|
||
interface SearchParams {
|
||
title?: string;
|
||
status?: string;
|
||
}
|
||
|
||
const NoticeManage: React.FC = () => {
|
||
const intl = useIntl();
|
||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||
const [loading, setLoading] = useState<boolean>(false);
|
||
const [form] = Form.useForm();
|
||
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
||
const [isEdit, setIsEdit] = useState<boolean>(false);
|
||
const [isViewMode, setIsViewMode] = useState<boolean>(false);
|
||
const [currentId, setCurrentId] = useState<string>('');
|
||
|
||
// 查看详情数据
|
||
const [viewData, setViewData] = useState<API.NoticeRecord | null>(null);
|
||
const [noticeData, setNoticeData] = useState<NoticeType[]>([]);
|
||
const [pagination, setPagination] = useState({
|
||
current: 1,
|
||
pageSize: 10,
|
||
total: 0,
|
||
showSizeChanger: true,
|
||
showQuickJumper: true,
|
||
showTotal: (total: number) => `共 ${total} 条记录`,
|
||
});
|
||
const [searchParams, setSearchParams] = useState<SearchParams>({});
|
||
|
||
// 获取通知列表
|
||
const fetchNoticeList = (current: number = 1, pageSize: number = 10, params: SearchParams = searchParams) => {
|
||
// 更新搜索参数状态
|
||
if (params !== searchParams) {
|
||
setSearchParams(params);
|
||
}
|
||
|
||
setLoading(true);
|
||
// 实际项目中应调用API
|
||
getNoticeList({
|
||
...params,
|
||
pageNo: current,
|
||
pageSize: pageSize,
|
||
})
|
||
.then(res => {
|
||
if (res && res.success) {
|
||
const { records, total, current: currentPage, size } = res.data;
|
||
|
||
// 添加key属性
|
||
setNoticeData(
|
||
records.map((item: API.NoticeRecord) => ({
|
||
...item,
|
||
key: item.id,
|
||
}))
|
||
);
|
||
setPagination({
|
||
...pagination,
|
||
current: currentPage,
|
||
pageSize: size,
|
||
total: total,
|
||
});
|
||
} else {
|
||
message.error(res.message || '获取通知列表失败');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('获取通知列表失败:', error);
|
||
message.error('获取通知列表失败');
|
||
})
|
||
.finally(() => {
|
||
setLoading(false);
|
||
});
|
||
};
|
||
|
||
// 首次加载时获取数据
|
||
useEffect(() => {
|
||
fetchNoticeList(1, pagination.pageSize, {});
|
||
}, []);
|
||
|
||
// 处理查看
|
||
const handleView = async (record: NoticeType) => {
|
||
setCurrentId(record.id);
|
||
setIsViewMode(true);
|
||
|
||
try {
|
||
const response = await getNoticeDetail(record.id);
|
||
if (response && response.success) {
|
||
// 设置查看详情数据
|
||
setViewData(response.data);
|
||
setModalVisible(true);
|
||
} else {
|
||
message.error(response.message || '获取详情失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('获取通知详情失败:', error);
|
||
message.error('获取详情失败');
|
||
}
|
||
};
|
||
|
||
// 处理编辑
|
||
const handleEdit = (record: NoticeType) => {
|
||
// 检查是否为已发布状态
|
||
if (record.status === NoticeStatus.PUBLISHED) {
|
||
message.warning('已发布的通知不能编辑');
|
||
return;
|
||
}
|
||
|
||
setIsEdit(true);
|
||
setIsViewMode(false);
|
||
setCurrentId(record.id);
|
||
setModalVisible(true);
|
||
};
|
||
|
||
// 处理删除
|
||
const showDeleteConfirm = (record: NoticeType) => {
|
||
// 检查是否为已发布状态
|
||
if (record.status === NoticeStatus.PUBLISHED) {
|
||
message.warning('已发布的通知不能删除');
|
||
return;
|
||
}
|
||
|
||
Modal.confirm({
|
||
title: '确定要删除该通知吗?',
|
||
icon: <ExclamationCircleOutlined />,
|
||
content: `标题: ${record.title}`,
|
||
okText: '确定',
|
||
okType: 'danger',
|
||
cancelText: '取消',
|
||
maskClosable: false,
|
||
onOk: async () => {
|
||
try {
|
||
// 调用删除API
|
||
const response = await deleteNotice(record.id);
|
||
if (response && response.success) {
|
||
message.success('删除成功');
|
||
fetchNoticeList(pagination.current, pagination.pageSize, searchParams); // 重新加载数据
|
||
} else {
|
||
message.error(response.message || '删除失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('删除通知失败:', error);
|
||
message.error('删除失败');
|
||
}
|
||
},
|
||
});
|
||
};
|
||
|
||
// 处理发布/下架
|
||
const handlePublishStatus = async (record: NoticeType) => {
|
||
// 状态: 0-草稿,1-已发布
|
||
const isPublished = record.status === NoticeStatus.PUBLISHED;
|
||
const actionText = isPublished ? '下架' : '发布';
|
||
const newStatus = isPublished ? NoticeStatus.UNPUBLISHED : NoticeStatus.PUBLISHED;
|
||
|
||
try {
|
||
// 调用API更新状态
|
||
const response = await updateNoticeStatus(record.id, newStatus);
|
||
if (response && response.success) {
|
||
message.success(`${actionText}成功`);
|
||
fetchNoticeList(pagination.current, pagination.pageSize, searchParams); // 重新加载数据
|
||
} else {
|
||
message.error(response.message || `${actionText}失败`);
|
||
}
|
||
} catch (error) {
|
||
console.error(`${actionText}失败:`, error);
|
||
message.error(`${actionText}失败`);
|
||
}
|
||
};
|
||
|
||
// 处理置顶/取消置顶
|
||
const handleToggleTop = async (record: NoticeType) => {
|
||
const isTop = record.isTop === TopStatus.YES;
|
||
const actionText = isTop ? '取消置顶' : '置顶';
|
||
const newIsTop = isTop ? TopStatus.NO : TopStatus.YES;
|
||
|
||
try {
|
||
// 调用API更新置顶状态
|
||
const response = await updateNoticeTopStatus(record.id, newIsTop);
|
||
if (response && response.success) {
|
||
message.success(`${actionText}成功`);
|
||
fetchNoticeList(pagination.current, pagination.pageSize, searchParams); // 重新加载数据
|
||
} else {
|
||
message.error(response.message || `${actionText}失败`);
|
||
}
|
||
} catch (error) {
|
||
console.error(`${actionText}失败:`, error);
|
||
message.error(`${actionText}失败`);
|
||
}
|
||
};
|
||
|
||
// 获取状态标签
|
||
const getStatusTag = (status: string) => {
|
||
const statusKey = status as keyof typeof NoticeStatusText;
|
||
const color = NoticeStatusColor[statusKey] || 'default';
|
||
const text = NoticeStatusText[statusKey] || '未知';
|
||
return <Tag color={color}>{text}</Tag>;
|
||
};
|
||
|
||
// 处理表格分页变化
|
||
const handleTableChange = (newPagination: any) => {
|
||
fetchNoticeList(newPagination.current, newPagination.pageSize, searchParams);
|
||
};
|
||
|
||
const columns = [
|
||
{
|
||
title: '序号',
|
||
render: (text: string, record: NoticeType, index: number) => index + 1,
|
||
width: 80,
|
||
align: 'center' as const,
|
||
},
|
||
{
|
||
title: '标题',
|
||
dataIndex: 'title',
|
||
key: 'title',
|
||
ellipsis: {
|
||
showTitle: false,
|
||
},
|
||
render: (title: string, record: NoticeType) => (
|
||
<Tooltip placement="topLeft" title={title}>
|
||
<span>
|
||
{record.isTop === TopStatus.YES && <Tag color="red" style={{ marginRight: 8 }}>置顶</Tag>}
|
||
{title}
|
||
</span>
|
||
</Tooltip>
|
||
),
|
||
},
|
||
{
|
||
title: '日期',
|
||
dataIndex: 'createTime',
|
||
key: 'createTime',
|
||
align: 'center' as const,
|
||
},
|
||
{
|
||
title: '状态',
|
||
dataIndex: 'status',
|
||
key: 'status',
|
||
align: 'center' as const,
|
||
render: (status: string) => getStatusTag(status),
|
||
},
|
||
{
|
||
title: '发布人',
|
||
dataIndex: 'createBy',
|
||
key: 'createBy',
|
||
align: 'center' as const,
|
||
},
|
||
{
|
||
title: '操作',
|
||
key: 'action',
|
||
width: 300,
|
||
align: 'center' as const,
|
||
render: (_: unknown, record: NoticeType) => (
|
||
<>
|
||
<Button type="link" onClick={() => handleView(record)}>
|
||
查看
|
||
</Button>
|
||
{record.status === NoticeStatus.PUBLISHED ? (
|
||
<>
|
||
<Button type="link" onClick={() => handlePublishStatus(record)}>
|
||
下架
|
||
</Button>
|
||
<Button
|
||
type="link"
|
||
onClick={() => handleToggleTop(record)}
|
||
>
|
||
{record.isTop === TopStatus.YES ? '取消置顶' : '置顶'}
|
||
</Button>
|
||
</>
|
||
) : (
|
||
<>
|
||
<Button type="link" onClick={() => handleEdit(record)}>
|
||
编辑
|
||
</Button>
|
||
<Button
|
||
type="link"
|
||
onClick={() => handlePublishStatus(record)}
|
||
>
|
||
发布
|
||
</Button>
|
||
<Button
|
||
type="link"
|
||
onClick={() => showDeleteConfirm(record)}
|
||
>
|
||
删除
|
||
</Button>
|
||
<Button
|
||
type="link"
|
||
onClick={() => handleToggleTop(record)}
|
||
>
|
||
{record.isTop === TopStatus.YES ? '取消置顶' : '置顶'}
|
||
</Button>
|
||
</>
|
||
)}
|
||
</>
|
||
),
|
||
},
|
||
];
|
||
|
||
// 行选择限制
|
||
const checkSelectable = (record: NoticeType) => {
|
||
return record.status !== NoticeStatus.PUBLISHED; // 已发布的不能选择
|
||
};
|
||
|
||
const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
|
||
setSelectedRowKeys(newSelectedRowKeys);
|
||
};
|
||
|
||
const rowSelection = {
|
||
selectedRowKeys,
|
||
onChange: onSelectChange,
|
||
getCheckboxProps: (record: NoticeType) => ({
|
||
disabled: record.status === NoticeStatus.PUBLISHED, // 已发布的不能选择
|
||
}),
|
||
};
|
||
|
||
const hasSelected = selectedRowKeys.length > 0;
|
||
|
||
// 处理添加
|
||
const handleAdd = () => {
|
||
setIsEdit(false);
|
||
setIsViewMode(false);
|
||
setCurrentId('');
|
||
setViewData(null);
|
||
setModalVisible(true);
|
||
};
|
||
|
||
// 处理批量删除
|
||
const handleBatchDelete = () => {
|
||
Modal.confirm({
|
||
title: '确定要删除选中的通知吗?',
|
||
icon: <ExclamationCircleOutlined />,
|
||
content: '删除后无法恢复',
|
||
okText: '确定',
|
||
okType: 'danger',
|
||
cancelText: '取消',
|
||
maskClosable: false,
|
||
onOk: async () => {
|
||
setLoading(true);
|
||
try {
|
||
// 获取可删除的ID(非已发布状态)
|
||
const deleteIds = selectedRowKeys.filter(key => {
|
||
const record = noticeData.find(item => item.key === key);
|
||
return record && record.status !== NoticeStatus.PUBLISHED;
|
||
}) as string[];
|
||
|
||
if (deleteIds.length > 0) {
|
||
// 调用批量删除API
|
||
const response = await batchDeleteNotice(deleteIds);
|
||
if (response && response.success) {
|
||
setSelectedRowKeys([]);
|
||
message.success('删除成功');
|
||
fetchNoticeList(pagination.current, pagination.pageSize, searchParams); // 重新加载数据
|
||
} else {
|
||
message.error(response.message || '批量删除失败');
|
||
}
|
||
} else {
|
||
message.warning('没有可删除的通知');
|
||
}
|
||
} catch (error) {
|
||
console.error('批量删除失败:', error);
|
||
message.error('批量删除失败');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
},
|
||
});
|
||
};
|
||
|
||
// 处理搜索
|
||
const handleSearch = (values: SearchParams) => {
|
||
fetchNoticeList(1, pagination.pageSize, values);
|
||
};
|
||
|
||
// 处理模态框取消
|
||
const handleModalCancel = () => {
|
||
setModalVisible(false);
|
||
setIsViewMode(false);
|
||
setViewData(null);
|
||
};
|
||
|
||
return (
|
||
<div className="common-container">
|
||
<div className="filter-action-row">
|
||
<Form
|
||
form={form}
|
||
name="search"
|
||
onFinish={handleSearch}
|
||
layout="inline"
|
||
className="filter-form"
|
||
>
|
||
<Form.Item name="title" label="标题">
|
||
<Input placeholder="请输入标题关键词" allowClear />
|
||
</Form.Item>
|
||
<Form.Item name="status" label="状态">
|
||
<Select placeholder="请选择状态" allowClear>
|
||
<Option value={NoticeStatus.DRAFT}>草稿</Option>
|
||
<Option value={NoticeStatus.PUBLISHED}>已发布</Option>
|
||
<Option value={NoticeStatus.UNPUBLISHED}>已下架</Option>
|
||
</Select>
|
||
</Form.Item>
|
||
<Form.Item className="filter-btns">
|
||
<Button type="primary" htmlType="submit" icon={<SearchOutlined />}>
|
||
搜索
|
||
</Button>
|
||
<Button
|
||
type="primary"
|
||
danger
|
||
icon={<DeleteOutlined />}
|
||
onClick={() => {
|
||
form.resetFields();
|
||
fetchNoticeList(1, pagination.pageSize, {});
|
||
}}
|
||
>
|
||
重置
|
||
</Button>
|
||
</Form.Item>
|
||
</Form>
|
||
|
||
<div className="right-buttons">
|
||
<Button
|
||
type="primary"
|
||
ghost
|
||
icon={<PlusOutlined />}
|
||
onClick={handleAdd}
|
||
>
|
||
新增
|
||
</Button>
|
||
<Button
|
||
danger
|
||
icon={<DeleteOutlined />}
|
||
onClick={handleBatchDelete}
|
||
disabled={!hasSelected}
|
||
loading={loading}
|
||
>
|
||
删除
|
||
</Button>
|
||
{hasSelected && (
|
||
<span className="selected-count">
|
||
已选择 {selectedRowKeys.length} 项
|
||
</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="content-area">
|
||
<Table
|
||
rowSelection={rowSelection}
|
||
columns={columns}
|
||
dataSource={noticeData}
|
||
pagination={pagination}
|
||
loading={loading}
|
||
onChange={handleTableChange}
|
||
/>
|
||
</div>
|
||
|
||
{/* 新增/编辑/查看模态框 */}
|
||
<Modal
|
||
title={isViewMode ? "查看通知" : isEdit ? "编辑通知" : "新增通知"}
|
||
visible={modalVisible}
|
||
onCancel={handleModalCancel}
|
||
width={900}
|
||
maskClosable={false}
|
||
destroyOnClose
|
||
footer={
|
||
isViewMode
|
||
? [
|
||
<Button key="close" onClick={handleModalCancel}>
|
||
关闭
|
||
</Button>,
|
||
]
|
||
: null
|
||
}
|
||
>
|
||
{isViewMode && viewData ? (
|
||
// 查看详情模式
|
||
<NoticeManageInfo data={viewData} />
|
||
) : (
|
||
// 编辑/新增模式
|
||
<NoticeManageForm
|
||
id={currentId}
|
||
isEdit={isEdit}
|
||
onSuccess={() => {
|
||
setModalVisible(false);
|
||
fetchNoticeList(pagination.current, pagination.pageSize, searchParams);
|
||
}}
|
||
/>
|
||
)}
|
||
</Modal>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default NoticeManage;
|