508 lines
16 KiB
TypeScript
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>
|
|
);
|
|
}
|