完善用户提问功能模块

This commit is contained in:
linxd
2025-06-18 14:37:42 +08:00
parent 763871a465
commit 5fff687782
28 changed files with 2702 additions and 1640 deletions

View File

@ -1,26 +1,38 @@
import React, { useState, useEffect } from 'react';
import { useIntl } from 'umi';
import { Button, Table, Modal, message, Input, Select, Form, Tooltip, Switch, Tag, Tabs } from 'antd';
import { Button, Table, Modal, message, Input, Select, Form, Tooltip, Tag, Tabs } from 'antd';
import {
PlusOutlined,
DeleteOutlined,
ExclamationCircleOutlined,
SearchOutlined,
VerticalAlignTopOutlined,
EyeOutlined,
} from '@ant-design/icons';
import { getNoticeList, addNotice, updateNotice, deleteNotice, batchDeleteNotice, updateNoticeStatus, updateNoticeTopStatus } from '@/servers/api/notice';
import { getNoticeList, addNotice, updateNotice, deleteNotice, batchDeleteNotice, updateNoticeStatus, updateNoticeTopStatus, getNoticeDetail } from '@/servers/api/notice';
import './noticeManage.less';
// 引入封装的WangEditor组件
import WangEditor from '@/components/WangEidtor/WangEidtor';
import {
NoticeStatus,
NoticeStatusText,
NoticeStatusColor,
TopStatus,
ModalType,
} from '@/dicts/noticeManageDict';
import NoticeManageForm from './NoticeManageForm';
import NoticeManageInfo from './NoticeManageInfo';
const { Option } = Select;
const { TextArea } = Input;
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[]>([]);
@ -28,121 +40,56 @@ const NoticeManage: React.FC = () => {
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 [modalForm] = Form.useForm();
// 富文本内容状态
const [htmlZh, setHtmlZh] = useState<string>('');
const [htmlEn, setHtmlEn] = useState<string>('');
const [noticeData, setNoticeData] = useState<NoticeType[]>([
{
key: '1',
id: '1',
title: '系统维护通知',
titleEn: 'System Maintenance Notice',
content: '系统将于2023年7月1日进行例行维护请提前做好准备。',
contentEn: 'The system will undergo routine maintenance on July 1, 2023. Please prepare in advance.',
createTime: '2023-06-25 10:30:00',
createBy: 'admin',
status: '1', // 已发布
isTop: '1', // 已置顶
settingEn: 1, // 设置英文
},
{
key: '2',
id: '2',
title: '新功能上线通知',
titleEn: 'New Feature Launch',
content: '系统新增了XXXX功能欢迎使用。',
contentEn: 'The system has added XXXX function, welcome to use.',
createTime: '2023-06-20 14:45:00',
createBy: 'admin',
status: '0', // 草稿
isTop: '0', // 未置顶
settingEn: 1,
},
{
key: '3',
id: '3',
title: '用户反馈调查',
titleEn: '',
content: '为了提升用户体验,我们正在收集用户反馈...',
contentEn: '',
createTime: '2023-06-18 09:15:00',
createBy: 'admin',
status: '1', // 已发布
isTop: '0', // 未置顶
settingEn: 0,
},
]);
// 查看详情数据
const [viewData, setViewData] = useState<API.NoticeRecord | null>(null);
const [noticeData, setNoticeData] = useState<NoticeType[]>([]);
const [pagination, setPagination] = useState({
current: 1,
pageSize: 10,
total: 3,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total: number) => `${total} 条记录`,
});
const [searchParams, setSearchParams] = useState<API.NoticeSearchParams>({});
const [activeTabKey, setActiveTabKey] = useState<string>('zh');
const [searchParams, setSearchParams] = useState<SearchParams>({});
// 获取通知列表
const fetchNoticeList = (current: number = 1, pageSize: number = 10, params: SearchParams = searchParams) => {
// 更新搜索参数状态
if (params !== searchParams) {
setSearchParams(params);
}
// 获取通知列表(模拟数据)
const fetchNoticeList = (current: number = 1, pageSize: number = 10) => {
setLoading(true);
// 实际项目中应调用API
getNoticeList({
...searchParams,
...params,
pageNo: current,
pageSize: pageSize,
})
.then(res => {
// 此处为模拟数据实际项目中应使用API返回的数据
setNoticeData([
{
key: '1',
id: '1',
title: '系统维护通知',
titleEn: 'System Maintenance Notice',
content: '系统将于2023年7月1日进行例行维护请提前做好准备。',
contentEn: 'The system will undergo routine maintenance on July 1, 2023. Please prepare in advance.',
createTime: '2023-06-25 10:30:00',
createBy: 'admin',
status: '1', // 已发布
isTop: '1', // 已置顶
settingEn: 1,
},
{
key: '2',
id: '2',
title: '新功能上线通知',
titleEn: 'New Feature Launch',
content: '系统新增了XXXX功能欢迎使用。',
contentEn: 'The system has added XXXX function, welcome to use.',
createTime: '2023-06-20 14:45:00',
createBy: 'admin',
status: '0', // 草稿
isTop: '0', // 未置顶
settingEn: 1,
},
{
key: '3',
id: '3',
title: '用户反馈调查',
titleEn: '',
content: '为了提升用户体验,我们正在收集用户反馈...',
contentEn: '',
createTime: '2023-06-18 09:15:00',
createBy: 'admin',
status: '1', // 已发布
isTop: '0', // 未置顶
settingEn: 0,
},
]);
setPagination({
...pagination,
current,
pageSize,
total: 3, // 应该是res.total
});
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);
@ -155,36 +102,47 @@ const NoticeManage: React.FC = () => {
// 首次加载时获取数据
useEffect(() => {
fetchNoticeList();
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 === '1') {
if (record.status === NoticeStatus.PUBLISHED) {
message.warning('已发布的通知不能编辑');
return;
}
setIsEdit(true);
setIsViewMode(false);
setCurrentId(record.id);
setModalVisible(true);
setActiveTabKey('zh');
// 填充表单数据
modalForm.setFieldsValue({
isTop: record.isTop === '1',
titleZh: record.title,
titleEn: record.titleEn,
contentZh: record.content,
contentEn: record.contentEn || '',
});
};
// 处理删除
const showDeleteConfirm = (record: NoticeType) => {
// 检查是否为已发布状态
if (record.status === '1') {
if (record.status === NoticeStatus.PUBLISHED) {
message.warning('已发布的通知不能删除');
return;
}
@ -200,11 +158,13 @@ const NoticeManage: React.FC = () => {
onOk: async () => {
try {
// 调用删除API
await deleteNotice(record.id);
// 更新本地数据
const newData = noticeData.filter(item => item.id !== record.id);
setNoticeData(newData);
message.success('删除成功');
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('删除失败');
@ -216,19 +176,19 @@ const NoticeManage: React.FC = () => {
// 处理发布/下架
const handlePublishStatus = async (record: NoticeType) => {
// 状态: 0-草稿1-已发布
const isPublished = record.status === '1';
const isPublished = record.status === NoticeStatus.PUBLISHED;
const actionText = isPublished ? '下架' : '发布';
const newStatus = isPublished ? '0' : '1';
const newStatus = isPublished ? NoticeStatus.UNPUBLISHED : NoticeStatus.PUBLISHED;
try {
// 调用API更新状态
await updateNoticeStatus(record.id, newStatus);
// 更新本地数据
const newData = noticeData.map(item =>
item.id === record.id ? { ...item, status: newStatus } : item
);
setNoticeData(newData);
message.success(`${actionText}成功`);
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}失败`);
@ -237,19 +197,19 @@ const NoticeManage: React.FC = () => {
// 处理置顶/取消置顶
const handleToggleTop = async (record: NoticeType) => {
const isTop = record.isTop === '1';
const isTop = record.isTop === TopStatus.YES;
const actionText = isTop ? '取消置顶' : '置顶';
const newIsTop = isTop ? '0' : '1';
const newIsTop = isTop ? TopStatus.NO : TopStatus.YES;
try {
// 调用API更新置顶状态
await updateNoticeTopStatus(record.id, newIsTop);
// 更新本地数据
const newData = noticeData.map(item =>
item.id === record.id ? { ...item, isTop: newIsTop } : item
);
setNoticeData(newData);
message.success(`${actionText}成功`);
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}失败`);
@ -258,30 +218,23 @@ const NoticeManage: React.FC = () => {
// 获取状态标签
const getStatusTag = (status: string) => {
switch (status) {
case '0':
return <Tag color="default">稿</Tag>;
case '1':
return <Tag color="green"></Tag>;
case '2':
return <Tag color="orange"></Tag>;
default:
return <Tag color="default"></Tag>;
}
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);
fetchNoticeList(newPagination.current, newPagination.pageSize, searchParams);
};
const columns = [
{
title: '序号',
dataIndex: 'id',
key: 'id',
align: 'center' as const,
render: (text: string, record: NoticeType, index: number) => index + 1,
width: 80,
align: 'center' as const,
},
{
title: '标题',
@ -293,7 +246,7 @@ const NoticeManage: React.FC = () => {
render: (title: string, record: NoticeType) => (
<Tooltip placement="topLeft" title={title}>
<span>
{record.isTop === '1' && <Tag color="red" style={{ marginRight: 8 }}></Tag>}
{record.isTop === TopStatus.YES && <Tag color="red" style={{ marginRight: 8 }}></Tag>}
{title}
</span>
</Tooltip>
@ -325,7 +278,10 @@ const NoticeManage: React.FC = () => {
align: 'center' as const,
render: (_: unknown, record: NoticeType) => (
<>
{record.status === '1' ? (
<Button type="link" onClick={() => handleView(record)}>
</Button>
{record.status === NoticeStatus.PUBLISHED ? (
<>
<Button type="link" onClick={() => handlePublishStatus(record)}>
@ -334,7 +290,7 @@ const NoticeManage: React.FC = () => {
type="link"
onClick={() => handleToggleTop(record)}
>
{record.isTop === '1' ? '取消置顶' : '置顶'}
{record.isTop === TopStatus.YES ? '取消置顶' : '置顶'}
</Button>
</>
) : (
@ -358,7 +314,7 @@ const NoticeManage: React.FC = () => {
type="link"
onClick={() => handleToggleTop(record)}
>
{record.isTop === '1' ? '取消置顶' : '置顶'}
{record.isTop === TopStatus.YES ? '取消置顶' : '置顶'}
</Button>
</>
)}
@ -369,7 +325,7 @@ const NoticeManage: React.FC = () => {
// 行选择限制
const checkSelectable = (record: NoticeType) => {
return record.status !== '1'; // 已发布的不能选择
return record.status !== NoticeStatus.PUBLISHED; // 已发布的不能选择
};
const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
@ -380,7 +336,7 @@ const NoticeManage: React.FC = () => {
selectedRowKeys,
onChange: onSelectChange,
getCheckboxProps: (record: NoticeType) => ({
disabled: record.status === '1', // 已发布的不能选择
disabled: record.status === NoticeStatus.PUBLISHED, // 已发布的不能选择
}),
};
@ -389,10 +345,10 @@ const NoticeManage: React.FC = () => {
// 处理添加
const handleAdd = () => {
setIsEdit(false);
setIsViewMode(false);
setCurrentId('');
modalForm.resetFields();
setViewData(null);
setModalVisible(true);
setActiveTabKey('zh');
};
// 处理批量删除
@ -411,17 +367,19 @@ const NoticeManage: React.FC = () => {
// 获取可删除的ID非已发布状态
const deleteIds = selectedRowKeys.filter(key => {
const record = noticeData.find(item => item.key === key);
return record && record.status !== '1';
return record && record.status !== NoticeStatus.PUBLISHED;
}) as string[];
if (deleteIds.length > 0) {
// 调用批量删除API
await batchDeleteNotice(deleteIds);
// 更新本地数据
const newData = noticeData.filter(item => !deleteIds.includes(item.key));
setNoticeData(newData);
setSelectedRowKeys([]);
message.success('删除成功');
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('没有可删除的通知');
}
@ -436,85 +394,15 @@ const NoticeManage: React.FC = () => {
};
// 处理搜索
const handleSearch = (values: API.NoticeSearchParams) => {
setSearchParams(values);
fetchNoticeList(1, pagination.pageSize);
};
// 处理模态框提交
const handleModalSubmit = () => {
// 先触发所有字段的验证
modalForm.validateFields()
.then(async values => {
try {
const noticeParams: API.NoticeRequest = {
title: values.titleZh,
titleEn: values.titleEn || '',
content: values.contentZh,
contentEn: values.contentEn || '',
isTop: values.isTop ? '1' : '0',
settingEn: values.titleEn && values.contentEn ? 1 : 0,
};
if (isEdit) {
// 编辑模式调用更新API
await updateNotice(currentId, noticeParams);
message.success('更新成功');
} else {
// 新增模式调用添加API
await addNotice(noticeParams);
message.success('添加成功');
}
setModalVisible(false);
modalForm.resetFields();
// 刷新列表
fetchNoticeList(pagination.current, pagination.pageSize);
} catch (error) {
console.error(isEdit ? '更新通知失败:' : '添加通知失败:', error);
message.error(isEdit ? '更新失败' : '添加失败');
}
})
.catch(errorInfo => {
// 获取所有字段的错误信息
const errorFields = errorInfo.errorFields || [];
// 检查是否有中文标题或内容的错误
const hasZhError = errorFields.some((field: any) => {
if (!field.name) return false;
const fieldName = Array.isArray(field.name) ? field.name.join('.') : String(field.name);
return fieldName.includes('titleZh') || fieldName.includes('contentZh');
});
// 检查是否有英文标题或内容的错误
const hasEnError = errorFields.some((field: any) => {
if (!field.name) return false;
const fieldName = Array.isArray(field.name) ? field.name.join('.') : String(field.name);
return fieldName.includes('titleEn') || fieldName.includes('contentEn');
});
// 如果有中文字段错误切换到中文Tab
if (hasZhError) {
setActiveTabKey('zh');
}
// 如果只有英文字段错误切换到英文Tab
else if (hasEnError && !hasZhError) {
setActiveTabKey('en');
}
console.log('表单验证失败:', errorInfo);
});
};
// 处理Tab切换
const handleTabChange = (key: string) => {
setActiveTabKey(key);
const handleSearch = (values: SearchParams) => {
fetchNoticeList(1, pagination.pageSize, values);
};
// 处理模态框取消
const handleModalCancel = () => {
setModalVisible(false);
modalForm.resetFields();
setIsViewMode(false);
setViewData(null);
};
return (
@ -532,9 +420,9 @@ const NoticeManage: React.FC = () => {
</Form.Item>
<Form.Item name="status" label="状态">
<Select placeholder="请选择状态" allowClear>
<Option value="0">稿</Option>
<Option value="1"></Option>
<Option value="2"></Option>
<Option value={NoticeStatus.DRAFT}>稿</Option>
<Option value={NoticeStatus.PUBLISHED}></Option>
<Option value={NoticeStatus.UNPUBLISHED}></Option>
</Select>
</Form.Item>
<Form.Item className="filter-btns">
@ -547,8 +435,7 @@ const NoticeManage: React.FC = () => {
icon={<DeleteOutlined />}
onClick={() => {
form.resetFields();
setSearchParams({});
handleSearch({});
fetchNoticeList(1, pagination.pageSize, {});
}}
>
@ -593,71 +480,38 @@ const NoticeManage: React.FC = () => {
/>
</div>
{/* 新增/编辑模态框 */}
{/* 新增/编辑/查看模态框 */}
<Modal
title={isEdit ? "编辑通知" : "新增通知"}
title={isViewMode ? "查看通知" : isEdit ? "编辑通知" : "新增通知"}
visible={modalVisible}
onOk={handleModalSubmit}
onCancel={handleModalCancel}
width={900}
maskClosable={false}
destroyOnClose
footer={
isViewMode
? [
<Button key="close" onClick={handleModalCancel}>
</Button>,
]
: null
}
>
<Form
form={modalForm}
layout="vertical"
name="noticeForm"
preserve={false}
>
<Form.Item name="isTop" label="是否置顶" valuePropName="checked">
<Switch />
</Form.Item>
<Tabs activeKey={activeTabKey} onChange={handleTabChange}>
<TabPane tab="中文版" key="zh">
<Form.Item
name="titleZh"
label="标题(中文)"
rules={[{ required: true, message: '请输入中文标题' }]}
>
<Input placeholder="请输入中文标题" />
</Form.Item>
<Form.Item
name="contentZh"
label="内容(中文)"
rules={[{ required: true, message: '请输入中文内容' }]}
>
<WangEditor
language="zh-CN"
height="300px"
placeholder="请输入通知内容..."
/>
</Form.Item>
</TabPane>
<TabPane tab="英文版" key="en">
<Form.Item
name="titleEn"
label="标题(英文)"
rules={[{ required: true, message: 'Please enter title in English' }]}
>
<Input placeholder="Please enter title in English" />
</Form.Item>
<Form.Item
name="contentEn"
label="内容(英文)"
rules={[{ required: true, message: 'Please enter content in English' }]}
>
<WangEditor
language="en"
height="300px"
placeholder="Please enter notice content..."
/>
</Form.Item>
</TabPane>
</Tabs>
</Form>
{isViewMode && viewData ? (
// 查看详情模式
<NoticeManageInfo data={viewData} />
) : (
// 编辑/新增模式
<NoticeManageForm
id={currentId}
isEdit={isEdit}
onSuccess={() => {
setModalVisible(false);
fetchNoticeList(pagination.current, pagination.pageSize, searchParams);
}}
/>
)}
</Modal>
</div>
);