问题分类取字典

This commit is contained in:
linxd
2025-08-11 10:51:55 +08:00
parent 5740fbdef1
commit c2e62dfc62
12 changed files with 318 additions and 234 deletions

View File

@ -9,6 +9,7 @@ import {
} from '@ant-design/icons';
import { getDownloadList, updateDownloadStatus, batchDeleteDownload } from '@/servers/api/download';
import DownloadManageForm from './DownloadManageForm';
import uiconfig from '@/uiconfig';
import {
DownloadStatus,
DownloadIsTop,
@ -499,7 +500,7 @@ const DownloadManage: React.FC = () => {
}
open={modalVisible}
onCancel={handleModalCancel}
width={600}
width={uiconfig.Modal.width}
maskClosable={false}
destroyOnClose
footer={null}

View File

@ -5,6 +5,8 @@ import WangEditor from '@/components/WangEidtor/WangEidtor';
import { TopStatus, EnglishSetting, categoryOptions } from '@/dicts/helpManageDict';
import { addHelp, updateHelp, getHelpDetail } from '@/servers/api/help';
import styles from './helpManage.less';
import { getDictList } from '@/servers/api/dicts';
import type { DictItem } from '@/servers/api/dicts';
const { TabPane } = Tabs;
const { Option } = Select;
@ -20,9 +22,15 @@ const HelpManageForm: React.FC<HelpManageFormProps> = ({ id, isEdit, onSuccess }
const [activeTabKey, setActiveTabKey] = useState<string>('zh');
const [loading, setLoading] = useState<boolean>(false);
const intl = useIntl();
const [typeList, setTypeList] = useState<DictItem[]>([]);
// 获取详情数据
useEffect(() => {
getDictList('help_center').then((res) => {
if (res.code === 200 && res.success) {
setTypeList(res.data);
}
});
const fetchHelpDetail = async (helpId: string) => {
setLoading(true);
try {
@ -40,7 +48,9 @@ const HelpManageForm: React.FC<HelpManageFormProps> = ({ id, isEdit, onSuccess }
answerContentEn: detail.answerContentNe,
});
} else {
message.error(response.message || intl.formatMessage({ id: 'helpManage.fetchDetailFailed' }));
message.error(
response.message || intl.formatMessage({ id: 'helpManage.fetchDetailFailed' }),
);
}
} catch (error) {
console.error('获取帮助详情失败:', error);
@ -76,9 +86,17 @@ const HelpManageForm: React.FC<HelpManageFormProps> = ({ id, isEdit, onSuccess }
const fieldName = firstError.name[0];
// 根据字段名判断应该切换到哪个Tab
if (fieldName === 'titleZh' || fieldName === 'contentZh' || fieldName === 'answerContentZh') {
if (
fieldName === 'titleZh' ||
fieldName === 'contentZh' ||
fieldName === 'answerContentZh'
) {
setActiveTabKey('zh');
} else if (fieldName === 'titleEn' || fieldName === 'contentEn' || fieldName === 'answerContentEn') {
} else if (
fieldName === 'titleEn' ||
fieldName === 'contentEn' ||
fieldName === 'answerContentEn'
) {
setActiveTabKey('en');
}
}
@ -110,14 +128,19 @@ const HelpManageForm: React.FC<HelpManageFormProps> = ({ id, isEdit, onSuccess }
}
if (response && response.success) {
message.success(isEdit
? intl.formatMessage({ id: 'helpManage.updateSuccess' })
: intl.formatMessage({ id: 'helpManage.addSuccess' }));
message.success(
isEdit
? intl.formatMessage({ id: 'helpManage.updateSuccess' })
: intl.formatMessage({ id: 'helpManage.addSuccess' }),
);
onSuccess(); // 回调父组件
} else {
message.error(response.message || (isEdit
? intl.formatMessage({ id: 'helpManage.updateFailed' })
: intl.formatMessage({ id: 'helpManage.addFailed' })));
message.error(
response.message ||
(isEdit
? intl.formatMessage({ id: 'helpManage.updateFailed' })
: intl.formatMessage({ id: 'helpManage.addFailed' })),
);
}
} catch (error: any) {
console.error('表单验证或提交失败:', error);
@ -130,38 +153,63 @@ const HelpManageForm: React.FC<HelpManageFormProps> = ({ id, isEdit, onSuccess }
return (
<div className={styles.helpForm}>
<Form form={form} layout="vertical" name="helpForm" preserve={false}>
<Form.Item name="isTop" label={intl.formatMessage({ id: 'helpManage.form.isTop' })} valuePropName="checked">
<Form.Item
name="isTop"
label={intl.formatMessage({ id: 'helpManage.form.isTop' })}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
name="type"
label={intl.formatMessage({ id: 'helpManage.form.category' })}
rules={[{ required: true, message: intl.formatMessage({ id: 'helpManage.form.categoryRequired' }) }]}
rules={[
{
required: true,
message: intl.formatMessage({ id: 'helpManage.form.categoryRequired' }),
},
]}
>
<Select placeholder={intl.formatMessage({ id: 'helpManage.form.categoryPlaceholder' })}>
{categoryOptions.map(option => (
<Option key={option.value} value={option.value}>
{typeof option.label === 'function' ? option.label() : option.label}
{typeList.map((option) => (
<Option key={option.code} value={option.code}>
{option.dicName}
</Option>
))}
</Select>
</Form.Item>
<Tabs activeKey={activeTabKey} onChange={handleTabChange}>
<TabPane tab={intl.formatMessage({ id: 'helpManage.form.chineseTab' })} key="zh" forceRender={true}>
<TabPane
tab={intl.formatMessage({ id: 'helpManage.form.chineseTab' })}
key="zh"
forceRender={true}
>
<Form.Item
name="titleZh"
label={intl.formatMessage({ id: 'helpManage.form.titleZh' })}
rules={[{ required: true, message: intl.formatMessage({ id: 'helpManage.form.titleZhRequired' }) }]}
rules={[
{
required: true,
message: intl.formatMessage({ id: 'helpManage.form.titleZhRequired' }),
},
]}
>
<Input placeholder={intl.formatMessage({ id: 'helpManage.form.titleZhPlaceholder' })} />
<Input
placeholder={intl.formatMessage({ id: 'helpManage.form.titleZhPlaceholder' })}
/>
</Form.Item>
<Form.Item
name="contentZh"
label={intl.formatMessage({ id: 'helpManage.form.contentZh' })}
rules={[{ required: true, message: intl.formatMessage({ id: 'helpManage.form.contentZhRequired' }) }]}
rules={[
{
required: true,
message: intl.formatMessage({ id: 'helpManage.form.contentZhRequired' }),
},
]}
>
<WangEditor
language="zh-CN"
@ -177,23 +225,41 @@ const HelpManageForm: React.FC<HelpManageFormProps> = ({ id, isEdit, onSuccess }
<WangEditor
language="zh-CN"
height="200px"
placeholder={intl.formatMessage({ id: 'helpManage.form.answerContentZhPlaceholder' })}
placeholder={intl.formatMessage({
id: 'helpManage.form.answerContentZhPlaceholder',
})}
/>
</Form.Item>
</TabPane>
<TabPane tab={intl.formatMessage({ id: 'helpManage.form.englishTab' })} key="en" forceRender={true}>
<TabPane
tab={intl.formatMessage({ id: 'helpManage.form.englishTab' })}
key="en"
forceRender={true}
>
<Form.Item
name="titleEn"
label={intl.formatMessage({ id: 'helpManage.form.titleEn' })}
rules={[{ required: true, message: intl.formatMessage({ id: 'helpManage.form.titleEnRequired' }) }]}
rules={[
{
required: true,
message: intl.formatMessage({ id: 'helpManage.form.titleEnRequired' }),
},
]}
>
<Input placeholder={intl.formatMessage({ id: 'helpManage.form.titleEnPlaceholder' })} />
<Input
placeholder={intl.formatMessage({ id: 'helpManage.form.titleEnPlaceholder' })}
/>
</Form.Item>
<Form.Item
name="contentEn"
label={intl.formatMessage({ id: 'helpManage.form.contentEn' })}
rules={[{ required: true, message: intl.formatMessage({ id: 'helpManage.form.contentEnRequired' }) }]}
rules={[
{
required: true,
message: intl.formatMessage({ id: 'helpManage.form.contentEnRequired' }),
},
]}
>
<WangEditor
language="en"
@ -209,7 +275,9 @@ const HelpManageForm: React.FC<HelpManageFormProps> = ({ id, isEdit, onSuccess }
<WangEditor
language="en"
height="200px"
placeholder={intl.formatMessage({ id: 'helpManage.form.answerContentEnPlaceholder' })}
placeholder={intl.formatMessage({
id: 'helpManage.form.answerContentEnPlaceholder',
})}
/>
</Form.Item>
</TabPane>
@ -219,11 +287,7 @@ const HelpManageForm: React.FC<HelpManageFormProps> = ({ id, isEdit, onSuccess }
<Button onClick={() => onSuccess()}>
{intl.formatMessage({ id: 'helpManage.cancel' })}
</Button>
<Button
type="primary"
loading={loading}
onClick={handleSubmit}
>
<Button type="primary" loading={loading} onClick={handleSubmit}>
{intl.formatMessage({ id: 'helpManage.submit' })}
</Button>
</div>

View File

@ -1,22 +1,27 @@
import React from 'react';
import React, { useState } from 'react';
import { Descriptions, Divider, Card, Tag } from 'antd';
import { useIntl } from 'umi';
import { HelpStatus, HelpStatusText, HelpStatusColor } from '@/dicts/helpManageDict';
import styles from './helpManage.less';
import { getDictList } from '@/servers/api/dicts';
import type { DictItem } from '@/servers/api/dicts';
interface HelpManageInfoProps {
data: API.HelpRecord | null;
typeList: DictItem[];
}
const HelpManageInfo: React.FC<HelpManageInfoProps> = ({ data }) => {
const HelpManageInfo: React.FC<HelpManageInfoProps> = ({ data, typeList }) => {
const intl = useIntl();
// 获取状态标签
const getStatusTag = (status: string) => {
const statusKey = status as keyof typeof HelpStatusText;
const color = HelpStatusColor[statusKey] || 'default';
const getText = HelpStatusText[statusKey];
const text = typeof getText === 'function' ? getText() : intl.formatMessage({ id: 'helpManage.status.unknown' });
const text =
typeof getText === 'function'
? getText()
: intl.formatMessage({ id: 'helpManage.status.unknown' });
return <Tag color={color}>{text}</Tag>;
};
@ -29,12 +34,15 @@ const HelpManageInfo: React.FC<HelpManageInfoProps> = ({ data }) => {
{data.title}
</Descriptions.Item>
{data.titleEn && (
<Descriptions.Item label={intl.formatMessage({ id: 'helpManage.detail.titleEn' })} span={2}>
<Descriptions.Item
label={intl.formatMessage({ id: 'helpManage.detail.titleEn' })}
span={2}
>
{data.titleEn}
</Descriptions.Item>
)}
<Descriptions.Item label={intl.formatMessage({ id: 'helpManage.detail.category' })}>
{data.type}
{typeList.find((item) => item.code == data.type) ? typeList.find((item) => item.code == data.type).dicName : '-'}
</Descriptions.Item>
<Descriptions.Item label={intl.formatMessage({ id: 'helpManage.detail.isTop' })}>
{data.isTop === '1'
@ -51,40 +59,60 @@ const HelpManageInfo: React.FC<HelpManageInfoProps> = ({ data }) => {
{data.createTime}
</Descriptions.Item>
{data.updateTime && (
<Descriptions.Item label={intl.formatMessage({ id: 'helpManage.detail.updateTime' })} span={2}>
<Descriptions.Item
label={intl.formatMessage({ id: 'helpManage.detail.updateTime' })}
span={2}
>
{data.updateTime}
</Descriptions.Item>
)}
</Descriptions>
<Divider orientation="left">{intl.formatMessage({ id: 'helpManage.detail.contentZh' })}</Divider>
<Divider orientation="left">
{intl.formatMessage({ id: 'helpManage.detail.contentZh' })}
</Divider>
<Card size="small" bordered={false} className={styles.contentCard}>
<div className={styles.helpContent} dangerouslySetInnerHTML={{ __html: data.content }} />
</Card>
{data.contentEn && (
<>
<Divider orientation="left">{intl.formatMessage({ id: 'helpManage.detail.contentEn' })}</Divider>
<Divider orientation="left">
{intl.formatMessage({ id: 'helpManage.detail.contentEn' })}
</Divider>
<Card size="small" bordered={false} className={styles.contentCard}>
<div className={styles.helpContent} dangerouslySetInnerHTML={{ __html: data.contentEn }} />
<div
className={styles.helpContent}
dangerouslySetInnerHTML={{ __html: data.contentEn }}
/>
</Card>
</>
)}
{data.answerContent && (
<>
<Divider orientation="left">{intl.formatMessage({ id: 'helpManage.detail.answerContentZh' })}</Divider>
<Divider orientation="left">
{intl.formatMessage({ id: 'helpManage.detail.answerContentZh' })}
</Divider>
<Card size="small" bordered={false} className={styles.contentCard}>
<div className={styles.helpContent} dangerouslySetInnerHTML={{ __html: data.answerContent }} />
<div
className={styles.helpContent}
dangerouslySetInnerHTML={{ __html: data.answerContent }}
/>
</Card>
</>
)}
{data.answerContentNe && (
<>
<Divider orientation="left">{intl.formatMessage({ id: 'helpManage.detail.answerContentEn' })}</Divider>
<Divider orientation="left">
{intl.formatMessage({ id: 'helpManage.detail.answerContentEn' })}
</Divider>
<Card size="small" bordered={false} className={styles.contentCard}>
<div className={styles.helpContent} dangerouslySetInnerHTML={{ __html: data.answerContentNe }} />
<div
className={styles.helpContent}
dangerouslySetInnerHTML={{ __html: data.answerContentNe }}
/>
</Card>
</>
)}

View File

@ -63,7 +63,7 @@ const HelpManage: React.FC = () => {
showTotal: (total: number) => `${total} 条记录`,
});
const [searchParams, setSearchParams] = useState<SearchParams>({});
const [typeList, setTypeList] = useState<DictItem[]>([]);
const [typeList, setTypeList] = useState<DictItem[]>([]);
// 获取帮助中心列表数据
const fetchHelpList = (
@ -111,11 +111,11 @@ const HelpManage: React.FC = () => {
// 首次加载时获取数据
useEffect(() => {
getDictList('download_section').then((res) => {
if (res.code === 200 && res.success) {
setTypeList(res.data);
}
});
getDictList('help_center').then((res) => {
if (res.code === 200 && res.success) {
setTypeList(res.data);
}
});
fetchHelpList(1, pagination.pageSize, {});
}, []);
@ -358,6 +358,10 @@ const HelpManage: React.FC = () => {
dataIndex: 'type',
key: 'type',
align: 'center' as const,
render: (type: string) => {
const dictItem = typeList.find((item) => item.code === type);
return dictItem ? dictItem.dicName : '-';
},
},
{
title: intl.formatMessage({ id: 'helpManage.date' }),
@ -434,9 +438,9 @@ const HelpManage: React.FC = () => {
allowClear
style={{ width: 150 }}
>
{categoryOptions.map((option) => (
<Option key={option.value} value={option.value}>
{typeof option.label === 'function' ? option.label() : option.label}
{typeList.map((option) => (
<Option key={option.code} value={option.code}>
{option.dicName}
</Option>
))}
</Select>
@ -526,7 +530,7 @@ const HelpManage: React.FC = () => {
maskClosable={false}
>
{isViewMode ? (
<HelpManageInfo data={viewData} />
<HelpManageInfo data={viewData} typeList={typeList} />
) : (
<HelpManageForm id={currentId} isEdit={isEdit} onSuccess={handleModalCancel} />
)}

View File

@ -18,7 +18,7 @@ import {
updateNoticeTopStatus,
getNoticeDetail,
} from '@/servers/api/notice';
import styles from './noticeManage.less';
import uiconfig from '@/uiconfig';
import {
NoticeStatus,
NoticeStatusText,
@ -587,7 +587,7 @@ const NoticeManage: React.FC = () => {
}
open={modalVisible}
onCancel={handleModalCancel}
width={900}
width={uiconfig.Modal.width}
maskClosable={false}
destroyOnClose
footer={

View File

@ -142,6 +142,7 @@ const PolicyManageForm: React.FC<PolicyManageFormProps> = ({ id, isEdit, onSucce
<Form.Item
name="contentZh"
valuePropName="value"
label={intl.formatMessage({ id: 'policyManage.form.contentZh' })}
rules={[{ required: true, message: intl.formatMessage({ id: 'policyManage.form.contentZhRequired' }) }]}
>
@ -163,6 +164,7 @@ const PolicyManageForm: React.FC<PolicyManageFormProps> = ({ id, isEdit, onSucce
<Form.Item
name="contentEn"
valuePropName="value"
label={intl.formatMessage({ id: 'policyManage.form.contentEn' })}
rules={[{ required: true, message: intl.formatMessage({ id: 'policyManage.form.contentEnRequired' }) }]}
>

View File

@ -14,6 +14,7 @@ import {
TablePaginationConfig,
Tabs,
} from 'antd';
import uiconfig from '@/uiconfig';
import {
PlusOutlined,
DeleteOutlined,
@ -476,7 +477,7 @@ const PolicyManage: React.FC = () => {
: intl.formatMessage({ id: 'policyManage.form.add' })}
open={modalVisible}
onCancel={handleModalCancel}
width={900}
width={uiconfig.Modal.width}
maskClosable={false}
destroyOnClose
footer={

View File

@ -16,26 +16,29 @@ import {
TopStatusText,
TopStatusColor
} from '@/dicts/userQuestionDict';
import type { DictItem } from '@/servers/api/dicts';
const { TextArea } = Input;
interface QuestionModalProps {
visible: boolean;
open: boolean;
onCancel: () => void;
question: any;
mode: 'view' | 'answer' | 'edit';
onSuccess: () => void;
typeList: DictItem[];
}
/**
* 问题模态框组件 - 整合了查看详情、回答问题和编辑回答功能
*/
const QuestionModal: React.FC<QuestionModalProps> = ({
visible,
open,
onCancel,
question,
mode,
onSuccess
onSuccess,
typeList
}) => {
const intl = useIntl();
const [form] = Form.useForm();
@ -170,7 +173,7 @@ const QuestionModal: React.FC<QuestionModalProps> = ({
{question.title}
</Descriptions.Item>
<Descriptions.Item label={intl.formatMessage({ id: 'questionDetail.category' })}>
{question.type}
{typeList.find((item) => item.code === question.type)?.dicName}
</Descriptions.Item>
<Descriptions.Item label={intl.formatMessage({ id: 'questionDetail.askTime' })}>
{question.askTime}
@ -252,7 +255,7 @@ const QuestionModal: React.FC<QuestionModalProps> = ({
{question.title}
</Descriptions.Item>
<Descriptions.Item label={intl.formatMessage({ id: 'questionDetail.category' })}>
{question.type}
{typeList.find((item) => item.code === question.type)?.dicName}
</Descriptions.Item>
<Descriptions.Item label={intl.formatMessage({ id: 'questionDetail.askTime' })}>
{question.askTime}
@ -357,7 +360,7 @@ const QuestionModal: React.FC<QuestionModalProps> = ({
return (
<Modal
title={getModalTitle()}
open={visible}
open={open}
onCancel={onCancel}
width={800}
footer={renderFooter()}

View File

@ -1,108 +0,0 @@
import React from 'react';
import { Descriptions, Divider, Card, Tag } from 'antd';
import { useIntl, FormattedMessage } from 'umi';
import styles from './userQuestionManage.less';
import {
PublishStatus,
PublishStatusText,
PublishStatusColor,
TopStatus,
TopStatusText,
TopStatusColor
} from '@/dicts/userQuestionDict';
interface QuestionDetailProps {
question: any; // 可以改为具体的类型
}
/**
* 问题详情查看组件
*/
const QuestionViewDetail: React.FC<QuestionDetailProps> = ({ question }) => {
const intl = useIntl();
if (!question) return null;
// 获取发布状态标签
const getPublishTag = (isPublished: number) => {
return isPublished === 1 ?
<Tag color="green">{PublishStatusText[PublishStatus.YES]()}</Tag> :
<Tag color="orange">{PublishStatusText[PublishStatus.NO]()}</Tag>;
};
// 获取置顶状态标签
const getTopTag = (isTop: number) => {
return isTop === 1 ?
<Tag color="red">{TopStatusText[TopStatus.YES]()}</Tag> :
<Tag color="default">{TopStatusText[TopStatus.NO]()}</Tag>;
};
return (
<div className={styles.questionDetailView}>
<Descriptions bordered column={2} size="small">
<Descriptions.Item label={intl.formatMessage({ id: 'questionDetail.title' })} span={2}>
{question.title}
</Descriptions.Item>
<Descriptions.Item label={intl.formatMessage({ id: 'questionDetail.category' })}>
{question.type}
</Descriptions.Item>
<Descriptions.Item label={intl.formatMessage({ id: 'questionDetail.askTime' })}>
{question.askTime}
</Descriptions.Item>
<Descriptions.Item label={intl.formatMessage({ id: 'questionDetail.asker' })}>
{question.userName}
</Descriptions.Item>
<Descriptions.Item label={intl.formatMessage({ id: 'questionDetail.company' })}>
{question.companyName}
</Descriptions.Item>
<Descriptions.Item label={intl.formatMessage({ id: 'questionDetail.account' })}>
{question.fullName}
</Descriptions.Item>
<Descriptions.Item label={intl.formatMessage({ id: 'questionDetail.contact' })}>
{question.contactDetails}
</Descriptions.Item>
<Descriptions.Item label={intl.formatMessage({ id: 'questionDetail.email' })}>
{question.email}
</Descriptions.Item>
{/* 只有已回答的问题才会显示以下字段 */}
{question.isAnswer === '1' && (
<>
<Descriptions.Item label={intl.formatMessage({ id: 'questionDetail.publishStatus' })}>
{getPublishTag(question.isPublished)}
</Descriptions.Item>
<Descriptions.Item label={intl.formatMessage({ id: 'questionDetail.topStatus' })}>
{getTopTag(question.isTop)}
</Descriptions.Item>
<Descriptions.Item label={intl.formatMessage({ id: 'questionDetail.answerTime' })}>
{question.answerTime}
</Descriptions.Item>
<Descriptions.Item label={intl.formatMessage({ id: 'questionDetail.respondent' })}>
{question.answerBy || '-'}
</Descriptions.Item>
</>
)}
</Descriptions>
<Divider orientation="left">{intl.formatMessage({ id: 'questionDetail.questionContent' })}</Divider>
<Card size="small" bordered={false} className={styles.contentCard}>
<div className={styles.questionContent}>
{question.content}
</div>
</Card>
{question.answerContent && (
<>
<Divider orientation="left">{intl.formatMessage({ id: 'questionDetail.answerContent' })}</Divider>
<Card size="small" bordered={false} className={styles.contentCard}>
<div className={styles.answerContent}>
{question.answerContent}
</div>
</Card>
</>
)}
</div>
);
};
export default QuestionViewDetail;

View File

@ -1,12 +1,23 @@
import React, { useState, useEffect } from 'react';
import { useIntl, FormattedMessage } from 'umi';
import { Button, Table, Modal, message, Input, Form, Tooltip, Tag, Space, Switch, TablePaginationConfig, Select } from 'antd';
import {
DeleteOutlined,
ExclamationCircleOutlined,
SearchOutlined
} from '@ant-design/icons';
Button,
Table,
Modal,
message,
Input,
Form,
Tooltip,
Tag,
Space,
Switch,
TablePaginationConfig,
Select,
} from 'antd';
import { DeleteOutlined, ExclamationCircleOutlined, SearchOutlined } from '@ant-design/icons';
import styles from './userQuestionManage.less';
import { getDictList } from '@/servers/api/dicts';
import type { DictItem } from '@/servers/api/dicts';
// 引入封装的组件
import QuestionModal from './QuestionModal';
// 引入API
@ -14,15 +25,16 @@ import {
getQuestionPage,
getQuestionDetail,
deleteQuestion,
batchDeleteQuestion
batchDeleteQuestion,
} from '@/servers/api/userQuestion';
// 引入字典
import {
QuestionCategoryOptions,
AnswerStatus,
AnswerStatusText,
AnswerStatusColor
AnswerStatusColor,
} from '@/dicts/userQuestionDict';
import { render } from 'react-dom';
const { confirm } = Modal;
const { Option } = Select;
@ -49,6 +61,7 @@ const ReadQuestionManage: React.FC = () => {
const [modalVisible, setModalVisible] = useState<boolean>(false);
const [currentQuestion, setCurrentQuestion] = useState<QuestionItemType | null>(null);
const [modalMode, setModalMode] = useState<'view' | 'edit'>('view');
const [typeList, setTypeList] = useState<DictItem[]>([]);
const [pagination, setPagination] = useState<TablePaginationConfig>({
current: 1,
pageSize: 10,
@ -58,7 +71,7 @@ const ReadQuestionManage: React.FC = () => {
showTotal: (total: number) => `${total} 条记录`,
});
const [searchParams, setSearchParams] = useState<SearchParams>({
isAnswer: 1 // 默认已回答
isAnswer: 1, // 默认已回答
});
// 获取已回答的用户提问列表数据
@ -115,6 +128,11 @@ const ReadQuestionManage: React.FC = () => {
// 首次加载时获取数据
useEffect(() => {
fetchQuestionList(pagination.current, pagination.pageSize);
getDictList('help_center').then((res) => {
if (res.code === 200 && res.success) {
setTypeList(res.data);
}
});
}, []);
// 处理搜索
@ -123,7 +141,7 @@ const ReadQuestionManage: React.FC = () => {
...values,
isAnswer: 1,
// 如果设置了answerStatus为已回答则需要特殊处理
...values.answerStatus === AnswerStatus.ANSWERED ? { hasAnswerContent: 1 } : {}
...(values.answerStatus === AnswerStatus.ANSWERED ? { hasAnswerContent: 1 } : {}),
};
fetchQuestionList(1, pagination.pageSize, params);
};
@ -136,7 +154,7 @@ const ReadQuestionManage: React.FC = () => {
const detailData = response.data;
const formattedDetail = {
...detailData,
key: detailData.id
key: detailData.id,
};
setCurrentQuestion(formattedDetail);
@ -178,7 +196,9 @@ const ReadQuestionManage: React.FC = () => {
message.success(intl.formatMessage({ id: 'readQuestion.deleteSuccess' }));
fetchQuestionList(pagination.current, pagination.pageSize, searchParams); // 重新加载数据
} else {
message.error(response.message || intl.formatMessage({ id: 'readQuestion.deleteFailed' }));
message.error(
response.message || intl.formatMessage({ id: 'readQuestion.deleteFailed' }),
);
}
} catch (error) {
console.error('删除问题失败:', error);
@ -216,7 +236,9 @@ const ReadQuestionManage: React.FC = () => {
message.success(intl.formatMessage({ id: 'readQuestion.batchDeleteSuccess' }));
fetchQuestionList(pagination.current, pagination.pageSize, searchParams);
} else {
message.error(response.message || intl.formatMessage({ id: 'readQuestion.batchDeleteFailed' }));
message.error(
response.message || intl.formatMessage({ id: 'readQuestion.batchDeleteFailed' }),
);
}
} catch (error) {
console.error('批量删除失败:', error);
@ -241,17 +263,25 @@ const ReadQuestionManage: React.FC = () => {
// 获取回答状态标签
const getAnswerTag = (record: QuestionItemType) => {
if (record.answerContent && record.answerContent.trim() !== '') {
return <Tag color={AnswerStatusColor[AnswerStatus.ANSWERED]}>{AnswerStatusText[AnswerStatus.ANSWERED]()}</Tag>;
return (
<Tag color={AnswerStatusColor[AnswerStatus.ANSWERED]}>
{AnswerStatusText[AnswerStatus.ANSWERED]()}
</Tag>
);
} else {
return <Tag color={AnswerStatusColor[AnswerStatus.READ]}>{AnswerStatusText[AnswerStatus.READ]()}</Tag>;
return (
<Tag color={AnswerStatusColor[AnswerStatus.READ]}>
{AnswerStatusText[AnswerStatus.READ]()}
</Tag>
);
}
};
// 获取置顶状态标签
const getTopTag = (isTop: number) => {
return isTop === 1 ?
<Tag color="red">{intl.formatMessage({ id: 'userQuestion.top' })}</Tag> :
null;
return isTop === 1 ? (
<Tag color="red">{intl.formatMessage({ id: 'userQuestion.top' })}</Tag>
) : null;
};
// 表格列定义
@ -283,6 +313,10 @@ const ReadQuestionManage: React.FC = () => {
dataIndex: 'type',
key: 'type',
align: 'center' as const,
render: (type: string) => {
const dictItem = typeList.find((item) => item.code === type);
return dictItem ? dictItem.dicName : '-';
},
},
{
title: intl.formatMessage({ id: 'readQuestion.asker' }),
@ -353,7 +387,9 @@ const ReadQuestionManage: React.FC = () => {
render: (_: unknown, record: QuestionItemType) => (
<Space>
{getAnswerTag(record)}
{record.isPublished === 1 && <Tag color="green">{intl.formatMessage({ id: 'userQuestion.published' })}</Tag>}
{record.isPublished === 1 && (
<Tag color="green">{intl.formatMessage({ id: 'userQuestion.published' })}</Tag>
)}
</Space>
),
},
@ -380,9 +416,9 @@ const ReadQuestionManage: React.FC = () => {
// 获取分类选项,处理为下拉框选项
const getCategoryOptions = () => {
return QuestionCategoryOptions.map(option => (
<Option key={option.value} value={option.value}>
{typeof option.label === 'function' ? option.label() : option.label}
return typeList.map((option) => (
<Option key={option.code} value={option.code}>
{option.dicName}
</Option>
));
};
@ -398,17 +434,33 @@ const ReadQuestionManage: React.FC = () => {
className="filter-form"
>
<Form.Item name="title" label={intl.formatMessage({ id: 'readQuestion.questionTitle' })}>
<Input placeholder={intl.formatMessage({ id: 'readQuestion.questionPlaceholder' })} allowClear />
<Input
placeholder={intl.formatMessage({ id: 'readQuestion.questionPlaceholder' })}
allowClear
/>
</Form.Item>
<Form.Item name="type" label={intl.formatMessage({ id: 'readQuestion.category' })}>
<Select placeholder={intl.formatMessage({ id: 'readQuestion.selectCategory' })} allowClear style={{ width: 150 }}>
<Select
placeholder={intl.formatMessage({ id: 'readQuestion.selectCategory' })}
allowClear
style={{ width: 150 }}
>
{getCategoryOptions()}
</Select>
</Form.Item>
<Form.Item name="answerStatus" label={intl.formatMessage({ id: 'readQuestion.answerStatus' })}>
<Select placeholder={intl.formatMessage({ id: 'readQuestion.selectAnswerStatus' })} allowClear style={{ width: 120 }}>
<Form.Item
name="answerStatus"
label={intl.formatMessage({ id: 'readQuestion.answerStatus' })}
>
<Select
placeholder={intl.formatMessage({ id: 'readQuestion.selectAnswerStatus' })}
allowClear
style={{ width: 120 }}
>
<Option value={AnswerStatus.READ}>{AnswerStatusText[AnswerStatus.READ]()}</Option>
<Option value={AnswerStatus.ANSWERED}>{AnswerStatusText[AnswerStatus.ANSWERED]()}</Option>
<Option value={AnswerStatus.ANSWERED}>
{AnswerStatusText[AnswerStatus.ANSWERED]()}
</Option>
</Select>
</Form.Item>
<Form.Item className="filter-btns">
@ -430,17 +482,15 @@ const ReadQuestionManage: React.FC = () => {
</Form>
<div className="right-buttons">
<Button
danger
onClick={handleBatchDelete}
disabled={!hasSelected}
loading={loading}
>
<Button danger onClick={handleBatchDelete} disabled={!hasSelected} loading={loading}>
{intl.formatMessage({ id: 'readQuestion.batchDelete' })}
</Button>
{hasSelected && (
<span className="selected-count">
{intl.formatMessage({ id: 'readQuestion.selectedCount' }, { count: selectedRowKeys.length })}
{intl.formatMessage(
{ id: 'readQuestion.selectedCount' },
{ count: selectedRowKeys.length },
)}
</span>
)}
</div>
@ -464,6 +514,7 @@ const ReadQuestionManage: React.FC = () => {
onCancel={() => setModalVisible(false)}
question={currentQuestion}
mode={modalMode}
typeList={typeList}
onSuccess={() => fetchQuestionList(pagination.current, pagination.pageSize, searchParams)}
/>
</div>

View File

@ -1,14 +1,23 @@
import React, { useState, useEffect } from 'react';
import { useIntl, FormattedMessage } from 'umi';
import { Button, Table, Modal, message, Input, Form, Tooltip, Tag, Space, TablePaginationConfig } from 'antd';
import {
DeleteOutlined,
ExclamationCircleOutlined,
SearchOutlined
} from '@ant-design/icons';
Button,
Table,
Modal,
message,
Input,
Form,
Tooltip,
Tag,
Space,
TablePaginationConfig,
} from 'antd';
import { DeleteOutlined, ExclamationCircleOutlined, SearchOutlined } from '@ant-design/icons';
import styles from './userQuestionManage.less';
// 引入封装的组件
import QuestionModal from './QuestionModal';
import { getDictList } from '@/servers/api/dicts';
import type { DictItem } from '@/servers/api/dicts';
// 引入API
import {
getQuestionPage,
@ -16,7 +25,7 @@ import {
markQuestionSeen,
markQuestionSeeEdit,
deleteQuestion,
batchDeleteQuestion
batchDeleteQuestion,
} from '@/servers/api/userQuestion';
// 引入字典
import { QuestionCategoryOptions } from '@/dicts/userQuestionDict';
@ -43,6 +52,7 @@ const UnreadQuestionManage: React.FC = () => {
const [form] = Form.useForm();
const [answerModalVisible, setAnswerModalVisible] = useState<boolean>(false);
const [currentQuestion, setCurrentQuestion] = useState<QuestionItemType | null>(null);
const [typeList, setTypeList] = useState<DictItem[]>([]);
const [pagination, setPagination] = useState<TablePaginationConfig>({
current: 1,
pageSize: 10,
@ -52,7 +62,7 @@ const UnreadQuestionManage: React.FC = () => {
showTotal: (total: number) => `${total} 条记录`,
});
const [searchParams, setSearchParams] = useState<SearchParams>({
isAnswer: 0 // 默认未回答
isAnswer: 0, // 默认未回答
});
// 获取未回答的用户提问列表数据
@ -89,8 +99,8 @@ const UnreadQuestionManage: React.FC = () => {
}));
setQuestionData(formattedData);
setPagination({
...pagination,
setPagination({
...pagination,
current: currentPage,
pageSize: size,
total,
@ -102,12 +112,17 @@ const UnreadQuestionManage: React.FC = () => {
console.error('获取问题列表失败:', error);
message.error(intl.formatMessage({ id: 'unreadQuestion.fetchFailed' }));
} finally {
setLoading(false);
setLoading(false);
}
};
// 首次加载时获取数据
useEffect(() => {
getDictList('help_center').then((res) => {
if (res.code === 200 && res.success) {
setTypeList(res.data);
}
});
fetchQuestionList(pagination.current, pagination.pageSize);
}, []);
@ -124,8 +139,8 @@ const UnreadQuestionManage: React.FC = () => {
const response = await markQuestionSeeEdit(record.id);
if (!response || !response.success) {
message.error(response?.message || intl.formatMessage({ id: 'userQuestion.replyFailed' }));
return;
}
return;
}
// 获取问题详情
const detailResponse = await getQuestionDetail(record.id);
@ -133,13 +148,15 @@ const UnreadQuestionManage: React.FC = () => {
const detailData = detailResponse.data;
const formattedDetail = {
...detailData,
key: detailData.id
key: detailData.id,
};
setCurrentQuestion(formattedDetail);
setAnswerModalVisible(true);
} else {
message.error(detailResponse.message || intl.formatMessage({ id: 'userQuestion.replyFailed' }));
message.error(
detailResponse.message || intl.formatMessage({ id: 'userQuestion.replyFailed' }),
);
}
} catch (error) {
console.error('处理问题失败:', error);
@ -164,7 +181,9 @@ const UnreadQuestionManage: React.FC = () => {
message.success(intl.formatMessage({ id: 'userQuestion.deleteSuccess' }));
fetchQuestionList(pagination.current, pagination.pageSize, searchParams); // 重新加载数据
} else {
message.error(response.message || intl.formatMessage({ id: 'userQuestion.deleteFailed' }));
message.error(
response.message || intl.formatMessage({ id: 'userQuestion.deleteFailed' }),
);
}
} catch (error) {
console.error('删除问题失败:', error);
@ -202,7 +221,9 @@ const UnreadQuestionManage: React.FC = () => {
message.success(intl.formatMessage({ id: 'unreadQuestion.batchDeleteSuccess' }));
fetchQuestionList(pagination.current, pagination.pageSize, searchParams);
} else {
message.error(response.message || intl.formatMessage({ id: 'unreadQuestion.batchDeleteFailed' }));
message.error(
response.message || intl.formatMessage({ id: 'unreadQuestion.batchDeleteFailed' }),
);
}
} catch (error) {
console.error('批量删除失败:', error);
@ -226,9 +247,9 @@ const UnreadQuestionManage: React.FC = () => {
// 获取分类选项,处理为下拉框选项
const getCategoryOptions = () => {
return QuestionCategoryOptions.map(option => (
<option key={option.value} value={option.value}>
{typeof option.label === 'function' ? option.label() : option.label}
return typeList.map((option) => (
<option key={option.code} value={option.code}>
{option.dicName}
</option>
));
};
@ -260,6 +281,10 @@ const UnreadQuestionManage: React.FC = () => {
key: 'type',
width: 120,
align: 'center' as const,
render: (type: string) => {
const dictItem = typeList.find((item) => item.code === type);
return dictItem ? dictItem.dicName : '-';
},
},
{
title: intl.formatMessage({ id: 'unreadQuestion.asker' }),
@ -327,7 +352,7 @@ const UnreadQuestionManage: React.FC = () => {
<Space>
<Button type="link" onClick={() => handleAnswer(record)}>
{intl.formatMessage({ id: 'unreadQuestion.reply' })}
</Button>
</Button>
<Button type="link" onClick={() => showDeleteConfirm(record)}>
{intl.formatMessage({ id: 'unreadQuestion.delete' })}
</Button>
@ -346,12 +371,20 @@ const UnreadQuestionManage: React.FC = () => {
layout="inline"
className="filter-form"
>
<Form.Item name="title" label={intl.formatMessage({ id: 'unreadQuestion.questionTitle' })}>
<Input placeholder={intl.formatMessage({ id: 'unreadQuestion.questionPlaceholder' })} allowClear />
<Form.Item
name="title"
label={intl.formatMessage({ id: 'unreadQuestion.questionTitle' })}
>
<Input
placeholder={intl.formatMessage({ id: 'unreadQuestion.questionPlaceholder' })}
allowClear
/>
</Form.Item>
<Form.Item name="type" label={intl.formatMessage({ id: 'unreadQuestion.category' })}>
<select className="ant-select" style={{ width: 150, height: 32, borderRadius: 2 }}>
<option value="">{intl.formatMessage({ id: 'unreadQuestion.selectCategory' })}</option>
<option value="">
{intl.formatMessage({ id: 'unreadQuestion.selectCategory' })}
</option>
{getCategoryOptions()}
</select>
</Form.Item>
@ -374,17 +407,15 @@ const UnreadQuestionManage: React.FC = () => {
</Form>
<div className="right-buttons">
<Button
danger
onClick={handleBatchDelete}
disabled={!hasSelected}
loading={loading}
>
<Button danger onClick={handleBatchDelete} disabled={!hasSelected} loading={loading}>
{intl.formatMessage({ id: 'unreadQuestion.batchDelete' })}
</Button>
{hasSelected && (
<span className="selected-count">
{intl.formatMessage({ id: 'unreadQuestion.selectedCount' }, { count: selectedRowKeys.length })}
{intl.formatMessage(
{ id: 'unreadQuestion.selectedCount' },
{ count: selectedRowKeys.length },
)}
</span>
)}
</div>
@ -408,6 +439,7 @@ const UnreadQuestionManage: React.FC = () => {
open={answerModalVisible}
onCancel={() => setAnswerModalVisible(false)}
question={currentQuestion}
typeList={typeList}
mode="answer"
onSuccess={() => fetchQuestionList(pagination.current, pagination.pageSize, searchParams)}
/>

6
src/uiconfig.ts Normal file
View File

@ -0,0 +1,6 @@
// 配置ui
export default {
Modal: {
width: 1000
}
}