Files
web-Iot/src/components/SystemSetting/RuleConfigManagement.tsx

508 lines
16 KiB
TypeScript

import React, { useState, useEffect, useMemo } from 'react';
import {
Table,
Input,
Button,
Form,
Select,
Space,
Tag,
Modal,
Card,
Typography,
Switch,
InputNumber,
Row,
Col,
App,
} from 'antd';
import {
SearchOutlined,
ReloadOutlined,
PlusOutlined,
EditOutlined,
DeleteOutlined,
EyeOutlined,
ExclamationCircleOutlined,
} from '@ant-design/icons';
import {
updateTriggerRule,
selectRuleBySiteId,
deleteRule,
} from '@/api/rule';
import utils, { CompareEnumOptions } from '@/lib/utils';
import { useSelector } from 'react-redux';
import { RootState } from '@/src/store';
import { useTranslation } from 'react-i18next';
const { TextArea } = Input;
const { Text } = Typography;
const { Option } = Select;
const AntdCard = Card as any;
// 规则类型
const RuleTypeOptions = [
{ value: 'LOW_BATTERY_RETURN', labelKey: 'ruleTypeLowBatteryReturn' },
];
// 适用范围
const DeviceScopeOptions = [
{ value: 'ALL', labelKey: 'deviceScopeAll' },
{ value: 'GROUP', labelKey: 'deviceScopeGroup' },
{ value: 'SOME', labelKey: 'deviceScopeSome' },
];
// 触发字段
const TriggerKeyOptions = [
{ value: 'battery', labelKey: 'triggerKeyBattery' },
{ value: 'temperature', labelKey: 'triggerKeyTemperature' },
{ value: 'offline_duration', labelKey: 'triggerKeyOfflineDuration' },
{ value: 'voltage', labelKey: 'triggerKeyVoltage' },
];
// 执行动作
const ActionTypeOptions = [
{ value: 'AUTO_TASK', labelKey: 'actionTypeAutoTask' },
{ value: 'SEND_COMMAND', labelKey: 'actionTypeSendCommand' },
{ value: 'ALARM', labelKey: 'actionTypeAlarm' },
{ value: 'WORK_ORDER', labelKey: 'actionTypeWorkOrder' },
];
const rangeOperators = ['BETWEEN', 'NOT_BETWEEN'];
export default function RuleConfigManagement() {
const { t } = useTranslation();
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 [ruleList, setRuleList] = useState<any[]>([]);
const [filteredList, setFilteredList] = useState<any[]>([]);
const [modalVisible, setModalVisible] = useState(false);
const [modalType, setModalType] = useState<'add' | 'edit' | 'view'>('add');
const [currentRecord, setCurrentRecord] = useState<any>(null);
const [submitting, setSubmitting] = useState(false);
// 获取规则列表
const fetchRuleList = async () => {
if (!stationId) return;
setLoading(true);
try {
const res = await selectRuleBySiteId({ siteId: stationId });
const list = res?.data || res?.rows || [];
setRuleList(list);
setFilteredList(list);
} catch (err) {
utils.message.error(t('systemSetting.operationFailed'));
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchRuleList();
}, [stationId]);
// 搜索过滤
const handleSearch = () => {
searchForm.validateFields().then(values => {
const next = ruleList.filter(item => {
const matchName = !values.ruleName || (item.ruleName || '').toLowerCase().includes(values.ruleName.toLowerCase());
const matchType = !values.ruleType || item.ruleType === values.ruleType;
const matchEnabled = values.enabled === undefined || values.enabled === '' || Number(item.enabled) === Number(values.enabled);
return matchName && matchType && matchEnabled;
});
setFilteredList(next);
});
};
const handleReset = () => {
searchForm.resetFields();
setFilteredList(ruleList);
};
// 打开弹窗
const openModal = (type: 'add' | 'edit' | 'view', record?: any) => {
setModalType(type);
setCurrentRecord(record || null);
setModalVisible(true);
form.resetFields();
if ((type === 'edit' || type === 'view') && record) {
const conditions = record.conditions || {};
const actionConfig = record.actionConfig || {};
const isRange = rangeOperators.includes(conditions.operator);
let min = '';
let max = '';
let value = conditions.value;
if (isRange && typeof conditions.value === 'string' && conditions.value.includes(',')) {
const [a, b] = conditions.value.split(',');
min = a;
max = b;
value = '';
}
let argsJson = '';
try {
argsJson = actionConfig.args ? JSON.stringify(actionConfig.args, null, 2) : '';
} catch (e) {
argsJson = '';
}
form.setFieldsValue({
ruleName: record.ruleName,
ruleType: record.ruleType,
deviceScope: record.deviceScope,
key: conditions.key,
operator: conditions.operator,
value,
min,
max,
actionType: actionConfig.actionType,
argsJson,
enabled: record.enabled === true || record.enabled === 1,
ForceExecute: record.ForceExecute === true || record.ForceExecute === 1,
});
} else {
form.setFieldsValue({
ruleType: 'LOW_BATTERY_RETURN',
deviceScope: 'ALL',
key: 'battery',
operator: 'LT',
actionType: 'AUTO_TASK',
enabled: true,
ForceExecute: false,
});
}
};
// 保存规则
const handleSave = async (values: any) => {
let argsObj: any = {};
if (values.argsJson) {
try {
argsObj = JSON.parse(values.argsJson);
} catch (e) {
utils.message.error(t('systemSetting.invalidJson'));
return;
}
}
const isRange = rangeOperators.includes(values.operator);
const value = isRange ? `${values.min},${values.max}` : values.value;
const submitData: any = {
id: modalType === 'edit' ? currentRecord?.id : undefined,
ruleName: values.ruleName,
ruleType: values.ruleType,
deviceScope: values.deviceScope,
conditions: {
key: values.key,
operator: values.operator,
value,
},
actionConfig: {
actionType: values.actionType,
args: argsObj,
},
enabled: values.enabled ? 1 : 0,
ForceExecute: values.ForceExecute ? 1 : 0,
orgId: currentStation?.orgId || '',
siteId: stationId || '',
};
setSubmitting(true);
try {
const res = await updateTriggerRule(submitData);
if (res?.code === 200) {
utils.message.success(modalType === 'edit' ? t('common.editSuccess') : t('common.addSuccess'));
setModalVisible(false);
fetchRuleList();
} else {
utils.message.error(res?.msg || t('systemSetting.operationFailed'));
}
} catch (err) {
utils.message.error(t('systemSetting.operationFailed'));
} finally {
setSubmitting(false);
}
};
// 删除规则
const handleDelete = (record: any) => {
modal.confirm({
title: t('common.confirmDelete'),
content: t('systemSetting.confirmDeleteRule', { name: record.ruleName }),
icon: <ExclamationCircleOutlined />,
okText: t('common.ok'),
cancelText: t('common.cancel'),
onOk: async () => {
try {
const res = await deleteRule({ ids: [record.id] });
if (res?.code === 200) {
utils.message.success(t('common.deleteSuccess'));
fetchRuleList();
} else {
utils.message.error(res?.msg || t('common.deleteFail'));
}
} catch (err) {
utils.message.error(t('common.deleteFail'));
}
},
});
};
const operatorWatch = Form.useWatch('operator', form);
const isRangeOperator = useMemo(() => rangeOperators.includes(operatorWatch), [operatorWatch]);
const getLabel = (options: any[], value: string, ns: string = 'systemSetting') => {
const item = options.find(o => o.value === value);
return item ? t(`${ns}.${item.labelKey}`) : value;
};
const renderConditions = (record: any) => {
const c = record.conditions || {};
const opItem = CompareEnumOptions.find(o => o.value === c.operator);
const opLabel = opItem ? t(`constants.${opItem.label}`) : c.operator;
return <Text type="secondary">{c.key} {opLabel} {c.value}</Text>;
};
const columns = [
{
title: t('systemSetting.ruleName'),
dataIndex: 'ruleName',
key: 'ruleName',
width: 180,
fixed: 'left' as const,
},
{
title: t('systemSetting.ruleType'),
dataIndex: 'ruleType',
key: 'ruleType',
width: 140,
render: (text: string) => getLabel(RuleTypeOptions, text),
},
{
title: t('systemSetting.deviceScope'),
dataIndex: 'deviceScope',
key: 'deviceScope',
width: 120,
render: (text: string) => getLabel(DeviceScopeOptions, text),
},
{
title: t('systemSetting.triggerKey'),
key: 'conditions',
width: 220,
render: (_: any, record: any) => renderConditions(record),
},
{
title: t('systemSetting.actionType'),
dataIndex: ['actionConfig', 'actionType'],
key: 'actionType',
width: 140,
render: (text: string) => getLabel(ActionTypeOptions, text),
},
{
title: t('systemSetting.enabledLabel'),
dataIndex: 'enabled',
key: 'enabled',
width: 100,
render: (val: any) => <Tag color={val === 1 || val === true ? 'green' : 'default'}>{val === 1 || val === true ? t('common.enable') : t('common.disable')}</Tag>,
},
{
title: t('systemSetting.forceExecute'),
dataIndex: 'ForceExecute',
key: 'ForceExecute',
width: 100,
render: (val: any) => <Tag color={val === 1 || val === true ? 'orange' : 'default'}>{val === 1 || val === true ? t('common.yes') : t('common.no')}</Tag>,
},
{
title: t('common.actions'),
key: 'action',
width: 220,
fixed: 'right' as const,
render: (_: any, record: any) => (
<Space>
<Button type="link" size="small" icon={<EyeOutlined />} onClick={() => openModal('view', record)}>{t('common.view')}</Button>
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => openModal('edit', record)}>{t('common.edit')}</Button>
<Button type="link" size="small" danger icon={<DeleteOutlined />} onClick={() => handleDelete(record)}>{t('common.delete')}</Button>
</Space>
),
},
];
const disabledForm = modalType === 'view';
return (
<div style={{ padding: '0px 0px', background: '#f5f7fa', minHeight: 'calc(100vh - 120px)' }}>
<AntdCard style={{ borderRadius: 8 }}>
<Form form={searchForm} layout="inline" style={{ marginBottom: 16 }}>
<Form.Item name="ruleName" label={t('systemSetting.ruleName')}>
<Input placeholder={t('systemSetting.pleaseInputRuleName')} style={{ width: 160 }} allowClear />
</Form.Item>
<Form.Item name="ruleType" label={t('systemSetting.ruleType')}>
<Select placeholder={t('systemSetting.pleaseSelectRuleType')} style={{ width: 150 }} allowClear>
{RuleTypeOptions.map(option => (
<Option key={option.value} value={option.value}>{t(`systemSetting.${option.labelKey}`)}</Option>
))}
</Select>
</Form.Item>
<Form.Item name="enabled" label={t('systemSetting.enabledLabel')}>
<Select placeholder={t('common.pleaseSelect')} style={{ width: 120 }} allowClear>
<Option value={1}>{t('common.enable')}</Option>
<Option value={0}>{t('common.disable')}</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('systemSetting.addRule')}</Button>
</Space>
</Form.Item>
</Form>
<Table
rowKey="id"
columns={columns}
dataSource={filteredList}
loading={loading}
scroll={{ x: 1100 }}
pagination={{ pageSize: 10, showSizeChanger: true, showTotal: total => t('common.total', { total }) }}
/>
</AntdCard>
<Modal
title={modalType === 'add' ? t('systemSetting.addRule') : modalType === 'edit' ? t('systemSetting.editRule') : t('systemSetting.viewRule')}
open={modalVisible}
width={720}
maskClosable={false}
destroyOnClose
onCancel={() => setModalVisible(false)}
footer={
disabledForm ? (
<Button onClick={() => setModalVisible(false)}>{t('common.close')}</Button>
) : (
<Space>
<Button onClick={() => setModalVisible(false)}>{t('common.cancel')}</Button>
<Button type="primary" loading={submitting} onClick={() => form.submit()}>{t('common.save')}</Button>
</Space>
)
}
>
<Form
form={form}
layout="vertical"
disabled={disabledForm}
onFinish={handleSave}
>
<Row gutter={16}>
<Col span={12}>
<Form.Item name="ruleName" label={t('systemSetting.ruleName')} rules={[{ required: true, message: t('systemSetting.pleaseInputRuleName') }]}>
<Input placeholder={t('systemSetting.pleaseInputRuleName')} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="ruleType" label={t('systemSetting.ruleType')} rules={[{ required: true, message: t('systemSetting.pleaseSelectRuleType') }]}>
<Select placeholder={t('systemSetting.pleaseSelectRuleType')}>
{RuleTypeOptions.map(option => (
<Option key={option.value} value={option.value}>{t(`systemSetting.${option.labelKey}`)}</Option>
))}
</Select>
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<Form.Item name="deviceScope" label={t('systemSetting.deviceScope')} rules={[{ required: true, message: t('systemSetting.pleaseSelectDeviceScope') }]}>
<Select placeholder={t('systemSetting.pleaseSelectDeviceScope')}>
{DeviceScopeOptions.map(option => (
<Option key={option.value} value={option.value}>{t(`systemSetting.${option.labelKey}`)}</Option>
))}
</Select>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="key" label={t('systemSetting.triggerKey')} rules={[{ required: true, message: t('systemSetting.pleaseSelectTriggerKey') }]}>
<Select placeholder={t('systemSetting.pleaseSelectTriggerKey')}>
{TriggerKeyOptions.map(option => (
<Option key={option.value} value={option.value}>{t(`systemSetting.${option.labelKey}`)}</Option>
))}
</Select>
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<Form.Item name="operator" label={t('systemSetting.operator')} rules={[{ required: true, message: t('systemSetting.pleaseSelectOperator') }]}>
<Select placeholder={t('systemSetting.pleaseSelectOperator')}>
{CompareEnumOptions.map(option => (
<Option key={option.value} value={option.value}>{t(`constants.${option.label}`)}</Option>
))}
</Select>
</Form.Item>
</Col>
<Col span={12}>
{isRangeOperator ? (
<Row gutter={8}>
<Col span={12}>
<Form.Item name="min" label={t('systemSetting.triggerValue')} rules={[{ required: true, message: t('systemSetting.pleaseInputTriggerValue') }]}>
<InputNumber style={{ width: '100%' }} placeholder={t('systemSetting.pleaseInputTriggerValue')} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="max" label=" " rules={[{ required: true, message: t('systemSetting.pleaseInputTriggerValue') }]}>
<InputNumber style={{ width: '100%' }} placeholder={t('systemSetting.pleaseInputTriggerValue')} />
</Form.Item>
</Col>
</Row>
) : (
<Form.Item name="value" label={t('systemSetting.triggerValue')} rules={[{ required: true, message: t('systemSetting.pleaseInputTriggerValue') }]}>
<InputNumber style={{ width: '100%' }} placeholder={t('systemSetting.pleaseInputTriggerValue')} />
</Form.Item>
)}
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<Form.Item name="actionType" label={t('systemSetting.actionType')} rules={[{ required: true, message: t('systemSetting.pleaseSelectActionType') }]}>
<Select placeholder={t('systemSetting.pleaseSelectActionType')}>
{ActionTypeOptions.map(option => (
<Option key={option.value} value={option.value}>{t(`systemSetting.${option.labelKey}`)}</Option>
))}
</Select>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="enabled" label={t('systemSetting.enabledLabel')} valuePropName="checked">
<Switch checkedChildren={t('common.enable')} unCheckedChildren={t('common.disable')} />
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={24}>
<Form.Item name="argsJson" label={t('systemSetting.actionArgs')}>
<TextArea rows={4} placeholder={`{\n "routeId": 123\n}`} />
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<Form.Item name="ForceExecute" label={t('systemSetting.forceExecute')} valuePropName="checked">
<Switch checkedChildren={t('common.yes')} unCheckedChildren={t('common.no')} />
</Form.Item>
</Col>
</Row>
</Form>
</Modal>
</div>
);
}