549 lines
16 KiB
TypeScript
549 lines
16 KiB
TypeScript
import React, { useState, useRef, useEffect } from 'react';
|
||
import {
|
||
Table,
|
||
Input,
|
||
Button,
|
||
Form,
|
||
Select,
|
||
Space,
|
||
Tag,
|
||
Modal,
|
||
Card,
|
||
Typography,
|
||
InputNumber,
|
||
Row,
|
||
Col,
|
||
App,
|
||
Divider,
|
||
} from 'antd';
|
||
import {
|
||
SearchOutlined,
|
||
ReloadOutlined,
|
||
PlusOutlined,
|
||
EditOutlined,
|
||
DeleteOutlined,
|
||
EyeOutlined,
|
||
ExclamationCircleOutlined,
|
||
} from '@ant-design/icons';
|
||
import {
|
||
getErrorListApi,
|
||
addErrorApi,
|
||
updateErrorApi,
|
||
deleteErrorApi,
|
||
getErrorDetailApi,
|
||
getErrorListAllApi
|
||
} from '@/api/systemSetting';
|
||
import utils from '@/lib/utils';
|
||
import { errorLevelOptions, errorSourceOptions, CompareEnumOptions } from '@/lib/utils';
|
||
import { useSelector } from 'react-redux';
|
||
import { RootState } from '@/src/store';
|
||
import { useTranslation } from 'react-i18next';
|
||
|
||
const { TextArea } = Input;
|
||
const { Text, Title } = Typography;
|
||
const { Option } = Select;
|
||
|
||
const AntdCard = Card as any;
|
||
|
||
|
||
export default function ErrorManagement() {
|
||
const { t } = useTranslation();
|
||
const { userInfo } = useSelector((state: RootState) => state.user);
|
||
const { stationId, currentStation } = useSelector((state: RootState) => state.station);
|
||
const { modal } = App.useApp();
|
||
const [form] = Form.useForm();
|
||
const [searchForm] = Form.useForm();
|
||
|
||
const [loading, setLoading] = useState(false);
|
||
const [errorList, setErrorList] = useState([]);
|
||
const [total, setTotal] = useState(0);
|
||
const [params, setParams] = useState({
|
||
errorCode: '',
|
||
errorName: '',
|
||
errorSource: '',
|
||
errorLevel: '',
|
||
orgId: currentStation?.orgId || '',
|
||
siteId: stationId || '',
|
||
});
|
||
|
||
|
||
const [modalVisible, setModalVisible] = useState(false);
|
||
const [modalType, setModalType] = useState<'add' | 'edit' | 'view'>('add');
|
||
const [currentRecord, setCurrentRecord] = useState<any>(null);
|
||
// 监听对比类型,判断是否是区间
|
||
const compareTypeWatch = Form.useWatch('compareType', form);
|
||
|
||
// 获取错误列表
|
||
const fetchErrorList = async () => {
|
||
setLoading(true);
|
||
try {
|
||
const requestParams = {
|
||
...params,
|
||
siteId: stationId || '',
|
||
};
|
||
const realParams = Object.fromEntries(
|
||
Object.entries(requestParams).filter(([_, val]) => val !== '')
|
||
);
|
||
|
||
const res = await getErrorListAllApi(realParams);
|
||
if (res.code === 200) {
|
||
setErrorList(res.rows || []);
|
||
setTotal(res.total || res.rows?.length || 0);
|
||
}
|
||
} catch (err) {
|
||
utils.message.error(t('systemSetting.getErrorListFailed'));
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
setParams(prev => ({
|
||
...prev,
|
||
siteId: stationId || ''
|
||
}));
|
||
}, [stationId]);
|
||
|
||
useEffect(() => {
|
||
fetchErrorList();
|
||
}, [params]);
|
||
|
||
// 打开弹窗
|
||
// 打开弹窗
|
||
const openModal = (type: 'add' | 'edit' | 'view', record?: any) => {
|
||
setModalType(type);
|
||
setCurrentRecord(record);
|
||
setModalVisible(true);
|
||
form.resetFields();
|
||
|
||
if (type === 'edit' || type === 'view') {
|
||
const fillData = { ...record };
|
||
// 区间规则拆分 compareValues 回填 min/max
|
||
const targetEnum = CompareEnumOptions.find(e => e.value === record.compareType);
|
||
if (targetEnum?.range && record.compareValues) {
|
||
const valArr = record.compareValues.split(',');
|
||
fillData.rangeMin = valArr[0];
|
||
fillData.rangeMax = valArr[1];
|
||
}
|
||
form.setFieldsValue(fillData);
|
||
}
|
||
};
|
||
|
||
// 保存错误(字段映射适配后端实体)
|
||
const handleSave = async (values: any) => {
|
||
console.log('提交表单数据', values);
|
||
//return;
|
||
// 组装后端需要的实体字段
|
||
const submitData = {
|
||
id: modalType == 'edit' ? currentRecord.id : undefined,
|
||
// 原有基础字段
|
||
errorCode: values.errorCode,
|
||
errorName: values.errorName,
|
||
errorSource: values.errorSource,
|
||
errorLevel: values.errorLevel,
|
||
errorDescription: values.errorDescription,
|
||
suggestion: values.suggestion,
|
||
// 新增校验规则字段
|
||
field: values.field,
|
||
compareType: values.compareType,
|
||
orgId: currentStation?.orgId || '',
|
||
siteId: stationId || '',
|
||
|
||
// 区间/普通值统一拼接逗号字符串传给 compareValues
|
||
compareValues: values.rangeMin && values.rangeMax
|
||
? `${values.rangeMin},${values.rangeMax}`
|
||
: values.compareValues || '',
|
||
};
|
||
console.log('提交表单数据', submitData);
|
||
|
||
|
||
try {
|
||
// 真实接口
|
||
const res = await addErrorApi(submitData);
|
||
if (res.code === 200) {
|
||
utils.message.success(modalType == 'edit' ? t('common.editSuccess') : t('common.addSuccess'));
|
||
setModalVisible(false);
|
||
fetchErrorList();
|
||
} else {
|
||
utils.message.error(res.msg || t('systemSetting.operationFailed'));
|
||
}
|
||
|
||
} catch (err) {
|
||
utils.message.error('操作失败');
|
||
}
|
||
};
|
||
|
||
// 删除错误
|
||
const handleDelete = (record: any) => {
|
||
modal.confirm({
|
||
title: t('common.confirmDelete'),
|
||
content: t('systemSetting.confirmDeleteError', { name: record.errorName }),
|
||
icon: <ExclamationCircleOutlined />,
|
||
okText: t('common.ok'),
|
||
cancelText: t('common.cancel'),
|
||
onOk: async () => {
|
||
try {
|
||
// 真实接口
|
||
const data = {
|
||
ids: [record.id]
|
||
}
|
||
console.log('删除请求参数', data);
|
||
const res = await deleteErrorApi(data.ids);
|
||
if (res.code == 200) {
|
||
utils.message.success(t('common.deleteSuccess'));
|
||
fetchErrorList();
|
||
} else {
|
||
utils.message.error(res.msg || t('common.deleteFail'));
|
||
}
|
||
|
||
|
||
} catch (err) {
|
||
utils.message.error(t('common.deleteFail'));
|
||
}
|
||
},
|
||
});
|
||
};
|
||
|
||
// 搜索
|
||
const handleSearch = () => {
|
||
searchForm.validateFields().then(values => {
|
||
setParams({
|
||
...params,
|
||
...values,
|
||
});
|
||
});
|
||
};
|
||
|
||
// 重置
|
||
const handleReset = () => {
|
||
searchForm.resetFields();
|
||
setParams({
|
||
|
||
errorCode: '',
|
||
errorName: '',
|
||
errorSource: '',
|
||
errorLevel: '',
|
||
});
|
||
};
|
||
|
||
// 标签颜色方法(原有不变)
|
||
const getTypeColor = (type: string) => {
|
||
const colorMap: Record<string, string> = {
|
||
ROBOT: 'blue',
|
||
DEVICE: 'green',
|
||
SYSTEM: 'purple',
|
||
COMMUNICATION: 'orange',
|
||
SENSOR: 'cyan',
|
||
OTHER: 'default',
|
||
};
|
||
return colorMap[type] || 'default';
|
||
};
|
||
const getSeverityColor = (severity: string) => {
|
||
const colorMap: Record<string, string> = {
|
||
MINOR: 'default',
|
||
NORMAL: 'blue',
|
||
SERIOUS: 'orange',
|
||
CRITICAL: 'red',
|
||
};
|
||
return colorMap[severity] || 'default';
|
||
};
|
||
const getStatusColor = (status: string) => {
|
||
const colorMap: Record<string, string> = {
|
||
PENDING: 'orange',
|
||
PROCESSING: 'blue',
|
||
RESOLVED: 'green',
|
||
IGNORED: 'default',
|
||
};
|
||
return colorMap[status] || 'default';
|
||
};
|
||
|
||
// 表格列(完全不变)
|
||
const columns = [
|
||
{
|
||
title: t('systemSetting.errorCode'),
|
||
dataIndex: 'errorCode',
|
||
key: 'errorCode',
|
||
width: 150,
|
||
fixed: 'left' as const,
|
||
},
|
||
{
|
||
title: t('systemSetting.errorName'),
|
||
dataIndex: 'errorName',
|
||
key: 'errorName',
|
||
width: 200,
|
||
},
|
||
{
|
||
title: t('systemSetting.errorSourceLabel'),
|
||
dataIndex: 'errorSource',
|
||
key: 'errorSource',
|
||
width: 120,
|
||
render: (text: string) => {
|
||
const typeObj = errorSourceOptions.find(t => t.value === text);
|
||
return <Tag color={getTypeColor(text)}>{typeObj?.label || text}</Tag>;
|
||
},
|
||
},
|
||
{
|
||
title: t('systemSetting.errorLevelLabel'),
|
||
dataIndex: 'errorLevel',
|
||
key: 'errorLevel',
|
||
width: 100,
|
||
render: (text: string) => {
|
||
const severityObj = errorLevelOptions.find(t => t.value === text);
|
||
return <Tag color={getSeverityColor(text)}>{severityObj?.label || text}</Tag>;
|
||
},
|
||
},
|
||
{
|
||
title: t('systemSetting.checkField'),
|
||
dataIndex: 'field',
|
||
key: 'field',
|
||
width: 130,
|
||
},
|
||
{
|
||
title: t('systemSetting.compareRule'),
|
||
dataIndex: 'compareType',
|
||
key: 'compareType',
|
||
width: 140,
|
||
render: (val) => {
|
||
const item = CompareEnumOptions.find(o => o.value === val);
|
||
return item?.label || val;
|
||
},
|
||
},
|
||
|
||
{
|
||
title: t('systemSetting.compareValueCol'),
|
||
dataIndex: 'compareValues',
|
||
key: 'compareValues',
|
||
width: 140,
|
||
|
||
},
|
||
{
|
||
title: t('common.description'),
|
||
dataIndex: 'errorDescription',
|
||
key: 'errorDescription',
|
||
ellipsis: true,
|
||
},
|
||
{
|
||
title: t('aiAnalysis.suggestion'),
|
||
dataIndex: 'suggestion',
|
||
key: 'suggestion',
|
||
width: 180,
|
||
},
|
||
{
|
||
title: t('roles.actions'),
|
||
key: 'action',
|
||
width: 300,
|
||
fixed: 'right' as const,
|
||
render: (_: any, record: any) => (
|
||
<Space>
|
||
<Button
|
||
type="link"
|
||
size="small"
|
||
icon={<EyeOutlined />}
|
||
onClick={() => openModal('view', record)}
|
||
>
|
||
查看
|
||
</Button>
|
||
<Button
|
||
type="link"
|
||
size="small"
|
||
icon={<EditOutlined />}
|
||
onClick={() => openModal('edit', record)}
|
||
>
|
||
编辑
|
||
</Button>
|
||
<Button
|
||
type="link"
|
||
size="small"
|
||
danger
|
||
icon={<DeleteOutlined />}
|
||
onClick={() => handleDelete(record)}
|
||
>
|
||
删除
|
||
</Button>
|
||
</Space>
|
||
),
|
||
},
|
||
];
|
||
|
||
return (
|
||
<div style={{ padding: '0px 0px', background: '#f5f7fa', minHeight: 'calc(100vh - 120px)' }}>
|
||
<AntdCard style={{ borderRadius: 8 }}>
|
||
{/* 搜索区域完全不变 */}
|
||
<Form form={searchForm} layout="inline" style={{ marginBottom: 0 }}>
|
||
<Form.Item name="errorCode" label={t("systemSetting.errorCode")}>
|
||
<Input placeholder={t("systemSetting.pleaseInputErrorCode")} style={{ width: 150 }} allowClear />
|
||
</Form.Item>
|
||
<Form.Item name="errorName" label={t("systemSetting.errorName")}>
|
||
<Input placeholder={t("systemSetting.pleaseInputErrorName")} style={{ width: 150 }} allowClear />
|
||
</Form.Item>
|
||
<Form.Item name="errorSource" label={t("systemSetting.errorSourceLabel")}>
|
||
<Select placeholder={t("systemSetting.pleaseSelectSource")} style={{ width: 120 }} allowClear >
|
||
{errorSourceOptions.map(option => (
|
||
<Option key={option.value} value={option.value}>{option.label}</Option>
|
||
))}
|
||
</Select>
|
||
</Form.Item>
|
||
<Form.Item name="errorLevel" label={t("systemSetting.errorLevelLabel")}>
|
||
<Select placeholder={t("systemSetting.pleaseSelectLevel")} style={{ width: 120 }} allowClear >
|
||
{errorLevelOptions.map(option => (
|
||
<Option key={option.value} value={option.value}>{option.label}</Option>
|
||
))}
|
||
</Select>
|
||
</Form.Item>
|
||
<Form.Item>
|
||
<Space>
|
||
<Button type="primary" icon={<SearchOutlined />} onClick={handleSearch}>{t('common.search2')}</Button>
|
||
<Button icon={<ReloadOutlined />} onClick={handleReset}>{t('common.reset')}</Button>
|
||
<Button type="primary" icon={<PlusOutlined />} onClick={() => openModal('add')}>{t('common.addNew')}</Button>
|
||
</Space>
|
||
</Form.Item>
|
||
</Form>
|
||
|
||
<Table
|
||
rowKey="id"
|
||
columns={columns}
|
||
dataSource={errorList}
|
||
loading={loading}
|
||
scroll={{ x: 1600, y: 1200 }}
|
||
pagination={{
|
||
current: params.pageNum,
|
||
pageSize: params.pageSize,
|
||
total: total,
|
||
showSizeChanger: true,
|
||
showQuickJumper: true,
|
||
showTotal: (total) => `${t('common.total')} ${total} ${t('common.items')}`,
|
||
onChange: (page, pageSize) => {
|
||
setParams({ ...params, pageNum: page, pageSize });
|
||
},
|
||
}}
|
||
/>
|
||
</AntdCard>
|
||
|
||
{/* 新增/编辑/查看弹窗(核心优化区域) */}
|
||
<Modal
|
||
title={
|
||
modalType === 'add'
|
||
? t('systemSetting.addErrorRule')
|
||
: modalType === 'edit'
|
||
? '编辑错误规则'
|
||
: t('systemSetting.viewErrorRuleDetail')
|
||
}
|
||
open={modalVisible}
|
||
onCancel={() => {
|
||
setModalVisible(false);
|
||
form.resetFields();
|
||
}}
|
||
width={780}
|
||
footer={
|
||
modalType === 'view'
|
||
? [
|
||
<Button key="close" onClick={() => setModalVisible(false)}>{t('common.close')}</Button>
|
||
]
|
||
: [
|
||
<div style={{ margin: '8px 0 24px 0' }}></div>,
|
||
<Button key="cancel" onClick={() => {
|
||
setModalVisible(false);
|
||
form.resetFields();
|
||
}}>{t('common.cancel')}</Button>,
|
||
<Button key="submit" type="primary" onClick={() => form.submit()}>{t('common.save')}</Button>
|
||
]
|
||
}
|
||
>
|
||
<Divider style={{ margin: '8px 0 16px 0' }} />
|
||
<Form
|
||
form={form}
|
||
layout="vertical"
|
||
onFinish={handleSave}
|
||
disabled={modalType === 'view'}
|
||
>
|
||
{/* 第一行:基础编码名称 */}
|
||
<Row gutter={16}>
|
||
<Col span={12}>
|
||
<Form.Item label={t("systemSetting.errorCode")} name="errorCode" rules={[{ required: true, message: t("systemSetting.pleaseInputErrorCode") }]}>
|
||
<Input placeholder={t("systemSetting.errorCodePlaceholder")} />
|
||
</Form.Item>
|
||
</Col>
|
||
<Col span={12}>
|
||
<Form.Item label={t("systemSetting.errorName")} name="errorName" rules={[{ required: true, message: t("systemSetting.pleaseInputErrorName") }]}>
|
||
<Input placeholder={t("systemSetting.pleaseInputErrorName")} />
|
||
</Form.Item>
|
||
</Col>
|
||
</Row>
|
||
|
||
{/* 第二行:来源、等级、校验字段 */}
|
||
<Row gutter={16}>
|
||
<Col span={8}>
|
||
<Form.Item label={t("systemSetting.errorSourceLabel")} name="errorSource" rules={[{ required: true, message: t("systemSetting.pleaseSelectErrorSource") }]}>
|
||
<Select placeholder={t('common.pleaseSelect')}>
|
||
{errorSourceOptions.map(option => (
|
||
<Option key={option.value} value={option.value}>{option.label}</Option>
|
||
))}
|
||
</Select>
|
||
</Form.Item>
|
||
</Col>
|
||
<Col span={8}>
|
||
<Form.Item label={t("systemSetting.errorLevelLabel")} name="errorLevel" rules={[{ required: true, message: t("systemSetting.pleaseSelectErrorLevel") }]}>
|
||
<Select placeholder={t('common.pleaseSelect')}>
|
||
{errorLevelOptions.map(option => (
|
||
<Option key={option.value} value={option.value}>{option.label}</Option>
|
||
))}
|
||
</Select>
|
||
</Form.Item>
|
||
</Col>
|
||
<Col span={8}>
|
||
<Form.Item label={t("systemSetting.checkField")} name="field" rules={[{ required: true, message: t("systemSetting.pleaseInputCheckField") }]}>
|
||
<Input placeholder={t("systemSetting.checkFieldPlaceholder")} />
|
||
</Form.Item>
|
||
</Col>
|
||
</Row>
|
||
|
||
{/* 第三行:对比类型 + 对比值(自动切换单输入/区间双输入) */}
|
||
<Row gutter={16}>
|
||
<Col span={8}>
|
||
<Form.Item label={t("systemSetting.compareRule")} name="compareType" rules={[{ required: true, message: t("systemSetting.pleaseSelectCompareRule") }]}>
|
||
<Select placeholder={t("systemSetting.pleaseSelectCompareRule")}>
|
||
{CompareEnumOptions.map(item => (
|
||
<Option key={item.value} value={item.value}>{item.label} ({item.symbol})</Option>
|
||
))}
|
||
</Select>
|
||
</Form.Item>
|
||
</Col>
|
||
{/* 区间类型:BETWEEN / NOT_BETWEEN 显示两个输入框 */}
|
||
{CompareEnumOptions.find(o => o.value === compareTypeWatch)?.range ? (
|
||
<>
|
||
<Col span={8}>
|
||
<Form.Item label={t("systemSetting.rangeMin")} name="rangeMin" rules={[{ required: true, message: t("systemSetting.pleaseInputMinValue") }]}>
|
||
<InputNumber style={{ width: '100%' }} placeholder={t("systemSetting.minValuePlaceholder")} />
|
||
</Form.Item>
|
||
</Col>
|
||
<Col span={8}>
|
||
<Form.Item label={t("systemSetting.rangeMax")} name="rangeMax" rules={[{ required: true, message: t("systemSetting.pleaseInputMaxValue") }]}>
|
||
<InputNumber style={{ width: '100%' }} placeholder={t("systemSetting.maxValuePlaceholder")} />
|
||
</Form.Item>
|
||
</Col>
|
||
</>
|
||
) : (
|
||
<Col span={16}>
|
||
<Form.Item label={t("systemSetting.compareValueLabel")} name="compareValues" rules={[{ required: true, message: t("systemSetting.pleaseInputCompareValue") }]}>
|
||
<Input placeholder={t("systemSetting.compareValuePlaceholder")} />
|
||
</Form.Item>
|
||
</Col>
|
||
)}
|
||
</Row>
|
||
|
||
{/* 错误描述 */}
|
||
<Form.Item label={t("systemSetting.errorDesc")} name="errorDescription">
|
||
<TextArea rows={3} placeholder={t("systemSetting.errorDescPlaceholder")} showCount maxLength={500} />
|
||
</Form.Item>
|
||
|
||
{/* 处理建议 */}
|
||
<Form.Item label={t('aiAnalysis.suggestion')} name="suggestion">
|
||
<TextArea rows={3} placeholder={t("systemSetting.suggestionPlaceholder")} showCount maxLength={500} />
|
||
</Form.Item>
|
||
</Form>
|
||
</Modal>
|
||
</div>
|
||
);
|
||
}
|
||
|