优化机器人任务界面,增加根据区域下发任务,增加规则配置界面
This commit is contained in:
200
.trae/documents/add-rule-config-tab.md
Normal file
200
.trae/documents/add-rule-config-tab.md
Normal file
@@ -0,0 +1,200 @@
|
||||
# 在 systemSetting/systemMaintenance 下新增「规则配置」Tab
|
||||
|
||||
## Context
|
||||
|
||||
当前「系统维护」页面已有「故障码管理」「告警设置」「工单设置」三个 Tab。用户希望在 `systemSetting/systemMaintenance` 页面下再新增一个「规则配置」Tab,用于管理场站的自动规则(autoRule)。该功能复用 `src/api/rule.tsx` 中的接口:
|
||||
|
||||
- `selectRuleBySiteId`:`GET /system/autoRule/selectBySiteId`
|
||||
- `updateTriggerRule`:`POST /system/autoRule/save`(新增/编辑共用,按 `id` 区分)
|
||||
- `deleteRule`:`POST /system/autoRule/delete`
|
||||
|
||||
后端实体结构(用户提供)包含 `ruleName`、`ruleType`、`deviceScope`、`conditions`(`TriggerConditions` JSON)、`actionConfig`(`ActionConfig` JSON)、`enabled`、`ForceExecute`。
|
||||
|
||||
## 目标
|
||||
|
||||
在 `SystemMaintenancePage` 增加「规则配置」Tab,点击后展示 `RuleConfigManagement` 组件:支持按场站查询规则列表、新增/编辑/查看/删除规则。
|
||||
|
||||
## 方案
|
||||
|
||||
### 1. 组件拆分
|
||||
|
||||
新增文件:`src/components/SystemSetting/RuleConfigManagement.tsx`
|
||||
|
||||
复用 `ErrorManagement.tsx` 的成熟模式:
|
||||
- `Form` 搜索区(规则名称、规则类型、启用状态)
|
||||
- `Table` 列表展示
|
||||
- `Modal` + `Form` 新增/编辑/查看详情
|
||||
|
||||
### 2. 修改 SystemMaintain.tsx
|
||||
|
||||
在 `src/components/SystemSetting/SystemMaintain.tsx` 中:
|
||||
- 在 `activeSubTab` 初始化 `useEffect` 的白名单里新增 `'rule'`。
|
||||
- 在 `Tabs` 中新增一个 `TabPane`,key 为 `'rule'`,tab 文案使用 `t('systemSetting.ruleConfig')`。
|
||||
- 条件渲染 `{activeSubTab === 'rule' && <RuleConfigManagement />}`。
|
||||
- 引入新组件 `RuleConfigManagement`。
|
||||
|
||||
### 3. 接口调用
|
||||
|
||||
在 `RuleConfigManagement.tsx` 中引入:
|
||||
|
||||
```tsx
|
||||
import {
|
||||
updateTriggerRule,
|
||||
selectRuleBySiteId,
|
||||
deleteRule,
|
||||
} from '@/api/rule';
|
||||
```
|
||||
|
||||
- 列表:`selectRuleBySiteId({ siteId: stationId })`,兼容 `res.data` / `res.rows`。
|
||||
- 保存:`updateTriggerRule(submitData)`,包含 `id`(编辑时)、`ruleName`、`ruleType`、`deviceScope`、`conditions`、`actionConfig`、`enabled`、`ForceExecute`、`orgId`、`siteId`。
|
||||
- 删除:`deleteRule({ ids: [record.id] })`。
|
||||
|
||||
### 4. 前端枚举/选项
|
||||
|
||||
由于用户只给出 Java 实体,前端需要定义以下选项(放在组件内或 `src/lib/utils.ts`):
|
||||
|
||||
- `RuleTypeOptions`:
|
||||
- `LOW_BATTERY_RETURN`:低电量返航
|
||||
- 预留更多 key,方便后续扩展。
|
||||
- `DeviceScopeOptions`:
|
||||
- `ALL`:所有设备
|
||||
- `GROUP`:设备组
|
||||
- `SOME`:特定的设备
|
||||
- `ActionTypeOptions`:
|
||||
- `AUTO_TASK`:路径规划任务
|
||||
- `SEND_COMMAND`:下发指令
|
||||
- `ALARM`:告警
|
||||
- `WORK_ORDER`:工单下发
|
||||
- `TriggerKeyOptions`:
|
||||
- `battery`:电量
|
||||
- `temperature`:温度
|
||||
- `offline_duration`:离线时长
|
||||
- `voltage`:电压
|
||||
- 比较运算符:直接复用现有 `CompareEnumOptions`(EQ/NEQ/GT/LT/GTE/LTE/BETWEEN/NOT_BETWEEN)。
|
||||
|
||||
> 考虑到项目现有 `CompareEnumOptions` 使用 `i18n.t('constants.xxx')` 作为 label,规则相关枚举 label 也统一使用 `t('systemSetting.xxx')`,并在 `zh/index.ts` 与 `en/index.ts` 的 `systemSetting` 命名空间下补充 key。
|
||||
|
||||
### 5. 表单设计
|
||||
|
||||
#### 列表搜索区
|
||||
- 规则名称(Input)
|
||||
- 规则类型(Select)
|
||||
- 启用状态(Select:启用/禁用)
|
||||
|
||||
#### Modal 表单字段
|
||||
- `ruleName`:Input,必填
|
||||
- `ruleType`:Select(`RuleTypeOptions`),必填,默认 `LOW_BATTERY_RETURN`
|
||||
- `deviceScope`:Select(`DeviceScopeOptions`),必填
|
||||
- `conditions.key`:Select(`TriggerKeyOptions`)
|
||||
- `conditions.operator`:Select(`CompareEnumOptions`)
|
||||
- `conditions.value`:InputNumber / Input
|
||||
- 若 operator 为区间(`BETWEEN` / `NOT_BETWEEN`),拆分为 `min` / `max`,保存时拼接为 `value`(如 `"20,80"`)。
|
||||
- `actionConfig.actionType`:Select(`ActionTypeOptions`)
|
||||
- `actionConfig.args`:动态表单(根据 `actionType` 显示不同字段)。
|
||||
- 第一阶段先提供通用 `Map<String, Object>` 文本域或一组 key-value 输入。
|
||||
- 若时间允许,可针对 `AUTO_TASK` 提供「选择路径」下拉。
|
||||
- `enabled`:Switch(1/0 或 true/false,按后端实际类型转换)
|
||||
- `ForceExecute`:Switch
|
||||
|
||||
### 6. 数据结构
|
||||
|
||||
提交时组装为:
|
||||
|
||||
```tsx
|
||||
const submitData = {
|
||||
id: modalType === 'edit' ? currentRecord.id : undefined,
|
||||
ruleName: values.ruleName,
|
||||
ruleType: values.ruleType,
|
||||
deviceScope: values.deviceScope,
|
||||
conditions: {
|
||||
key: values.key,
|
||||
operator: values.operator,
|
||||
value: values.operator === 'BETWEEN' || values.operator === 'NOT_BETWEEN'
|
||||
? `${values.min},${values.max}`
|
||||
: values.value,
|
||||
},
|
||||
actionConfig: {
|
||||
actionType: values.actionType,
|
||||
args: values.args || {},
|
||||
},
|
||||
enabled: values.enabled ? 1 : 0, // 或 true/false,按后端 Boolean
|
||||
ForceExecute: values.ForceExecute ? 1 : 0,
|
||||
orgId: currentStation?.orgId || '',
|
||||
siteId: stationId || '',
|
||||
};
|
||||
```
|
||||
|
||||
> 后端 `enabled` / `ForceExecute` 为 `Boolean`,若后端接受 JSON boolean 可直接传 `true/false`;若接受 0/1 则按实际接口调整。
|
||||
|
||||
### 7. i18n 补充
|
||||
|
||||
在 `src/locales/zh/index.ts` 与 `src/locales/en/index.ts` 的 `systemSetting` 命名空间下新增:
|
||||
|
||||
```ts
|
||||
ruleConfig: '规则配置',
|
||||
ruleName: '规则名称',
|
||||
ruleType: '规则类型',
|
||||
ruleTypeLowBatteryReturn: '低电量返航',
|
||||
deviceScope: '适用范围',
|
||||
deviceScopeAll: '所有设备',
|
||||
deviceScopeGroup: '设备组',
|
||||
deviceScopeSome: '特定设备',
|
||||
triggerKey: '触发字段',
|
||||
triggerKeyBattery: '电量',
|
||||
triggerKeyTemperature: '温度',
|
||||
triggerKeyOfflineDuration: '离线时长',
|
||||
triggerKeyVoltage: '电压',
|
||||
operator: '比较运算符',
|
||||
triggerValue: '阈值',
|
||||
actionType: '执行动作',
|
||||
actionTypeAutoTask: '路径规划任务',
|
||||
actionTypeSendCommand: '下发指令',
|
||||
actionTypeAlarm: '告警',
|
||||
actionTypeWorkOrder: '工单下发',
|
||||
actionArgs: '动作参数',
|
||||
enabled: '是否启用',
|
||||
forceExecute: '强制执行',
|
||||
pleaseInputRuleName: '请输入规则名称',
|
||||
pleaseSelectRuleType: '请选择规则类型',
|
||||
pleaseSelectDeviceScope: '请选择适用范围',
|
||||
pleaseSelectTriggerKey: '请选择触发字段',
|
||||
pleaseSelectOperator: '请选择比较运算符',
|
||||
pleaseInputTriggerValue: '请输入阈值',
|
||||
pleaseSelectActionType: '请选择执行动作',
|
||||
ruleList: '规则列表',
|
||||
addRule: '新增规则',
|
||||
editRule: '编辑规则',
|
||||
viewRule: '查看规则',
|
||||
```
|
||||
|
||||
### 8. 表格列
|
||||
|
||||
| 列名 | 字段 | 说明 |
|
||||
|---|---|---|
|
||||
| 规则名称 | ruleName | - |
|
||||
| 规则类型 | ruleType | 使用 `RuleTypeOptions` 渲染 label |
|
||||
| 适用范围 | deviceScope | 使用 `DeviceScopeOptions` 渲染 |
|
||||
| 触发条件 | conditions | 渲染为 `key operator value` 文本 |
|
||||
| 执行动作 | actionConfig.actionType | 使用 `ActionTypeOptions` 渲染 |
|
||||
| 启用 | enabled | Switch 或 Tag |
|
||||
| 强制 | ForceExecute | Tag |
|
||||
| 操作 | action | 查看/编辑/删除 |
|
||||
|
||||
## 关键文件
|
||||
|
||||
- `src/components/SystemSetting/SystemMaintain.tsx`:新增 Tab 入口
|
||||
- `src/components/SystemSetting/RuleConfigManagement.tsx`:新增规则配置组件
|
||||
- `src/api/rule.tsx`:复用已有接口
|
||||
- `src/locales/zh/index.ts`:中文 i18n
|
||||
- `src/locales/en/index.ts`:英文 i18n
|
||||
|
||||
## 验证
|
||||
|
||||
1. 启动项目,进入「系统设置」→「系统维护」。
|
||||
2. 确认出现「规则配置」Tab。
|
||||
3. 列表为空时显示空状态;若已有规则则正确展示。
|
||||
4. 点击「新增」打开弹窗,填写后保存,调用 `/system/autoRule/save`。
|
||||
5. 编辑已有规则,确认表单回填正确(区间值拆分为 min/max)。
|
||||
6. 删除规则,确认调用 `/system/autoRule/delete`。
|
||||
7. 切换站点后,列表按 `siteId` 重新加载。
|
||||
8. 运行 `npx esbuild src/components/SystemSetting/RuleConfigManagement.tsx --outfile=_tmp_rule.js --log-level=warning` 校验语法。
|
||||
@@ -489,6 +489,18 @@ export function downloadDeviceTemplate(params) {
|
||||
}
|
||||
|
||||
|
||||
///iot/deviceTask/createRegionDefaultTask 下发区域默认任务
|
||||
|
||||
export function createRegionDefaultTask(params) {
|
||||
return request({
|
||||
url: '/iot/deviceTask/createRegionDefaultTask',
|
||||
method: 'get',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
34
src/api/rule.tsx
Normal file
34
src/api/rule.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import request from './request';
|
||||
|
||||
// ==================== 机器人类型 ====================
|
||||
|
||||
// 修改触发规则
|
||||
export function updateTriggerRule(data) {
|
||||
return request({
|
||||
url: '/system/autoRule/save',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
//获取场站规则/system/autoRule/selectBySiteId
|
||||
|
||||
export function selectRuleBySiteId(data) {
|
||||
return request({
|
||||
url: '/system/autoRule/selectBySiteId',
|
||||
method: 'get',
|
||||
params: data
|
||||
});
|
||||
}
|
||||
|
||||
//删除规则/system/autoRule/delete
|
||||
|
||||
export function deleteRule(data) {
|
||||
return request({
|
||||
url: '/system/autoRule/delete',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1280,6 +1280,9 @@ const CesiumMap = React.forwardRef(({ ifClear, ifClearPlanLine, ifClearNavLine,
|
||||
if (viewerRef.current) return;
|
||||
|
||||
const viewer = new window.Cesium.Viewer(containerRef.current, {
|
||||
useBrowserRecommendedResolution: false,
|
||||
|
||||
|
||||
animation: false,
|
||||
timeline: false,
|
||||
baseLayerPicker: true,
|
||||
@@ -1294,6 +1297,8 @@ const CesiumMap = React.forwardRef(({ ifClear, ifClearPlanLine, ifClearNavLine,
|
||||
fileExtension: "jpg"
|
||||
})
|
||||
});
|
||||
viewer.resolutionScale = 2.0;
|
||||
|
||||
viewerRef.current = viewer;
|
||||
viewer.scene.globe.baseColor = window.Cesium?.Color.BLACK;
|
||||
viewer.scene.globe.depthTestAgainstTerrain = false;
|
||||
@@ -1485,6 +1490,20 @@ const CesiumMap = React.forwardRef(({ ifClear, ifClearPlanLine, ifClearNavLine,
|
||||
),
|
||||
duration: 0.8,
|
||||
});
|
||||
} else if (focusOverlay.type === 'route') {
|
||||
// 飞到路线的边界框
|
||||
const pts = Array.isArray(focusOverlay.points) ? focusOverlay.points : [];
|
||||
const lons = pts.map(p => Number(p?.lng ?? p?.lon)).filter(n => isFinite(n) && n !== 0);
|
||||
const lats = pts.map(p => Number(p?.lat)).filter(n => isFinite(n) && n !== 0);
|
||||
if (!lons.length || !lats.length) return;
|
||||
setLegendHighlight('');
|
||||
setHighlightReturnPointId('');
|
||||
viewer.camera.flyTo({
|
||||
destination: Cesium.Rectangle.fromDegrees(
|
||||
Math.min(...lons), Math.min(...lats), Math.max(...lons), Math.max(...lats)
|
||||
),
|
||||
duration: 0.8,
|
||||
});
|
||||
}
|
||||
}, [focusOverlay, stationOverlay]);
|
||||
|
||||
|
||||
507
src/components/SystemSetting/RuleConfigManagement.tsx
Normal file
507
src/components/SystemSetting/RuleConfigManagement.tsx
Normal file
@@ -0,0 +1,507 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { Card, Tabs, Typography } from 'antd';
|
||||
import { ExclamationCircleOutlined, AlertOutlined, FormOutlined } from '@ant-design/icons';
|
||||
import { ExclamationCircleOutlined, AlertOutlined, FormOutlined, SafetyCertificateOutlined } from '@ant-design/icons';
|
||||
import ErrorManagement from './ErrorManagement';
|
||||
import AlarmSettingsManagement from './AlarmSettingsManagement';
|
||||
import WorkOrderSettingsManagement from './WorkOrderSettingsManagement';
|
||||
import RuleConfigManagement from './RuleConfigManagement';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
@@ -17,7 +18,7 @@ export const SystemMaintenancePage = () => {
|
||||
// 初始化:从url读取tab
|
||||
useEffect(() => {
|
||||
const tab = searchParams.get('subTab');
|
||||
if (tab && ['error', 'alarm', 'workOrder'].includes(tab)) {
|
||||
if (tab && ['error', 'alarm', 'workOrder', 'rule'].includes(tab)) {
|
||||
setActiveSubTab(tab);
|
||||
}
|
||||
}, [searchParams]);
|
||||
@@ -38,12 +39,14 @@ export const SystemMaintenancePage = () => {
|
||||
<TabPane tab={<span><ExclamationCircleOutlined />{t('systemSetting.fixed2Error')}</span>} key="error" />
|
||||
<TabPane tab={<span><AlertOutlined />{t('systemSetting.fixed2Alert')}</span>} key="alarm" />
|
||||
<TabPane tab={<span><FormOutlined />{t('systemSetting.s2')}</span>} key="workOrder" />
|
||||
<TabPane tab={<span><SafetyCertificateOutlined />{t('systemSetting.ruleConfig')}</span>} key="rule" />
|
||||
</Tabs>
|
||||
</Card>
|
||||
|
||||
{activeSubTab === 'error' && <ErrorManagement />}
|
||||
{activeSubTab === 'alarm' && <AlarmSettingsManagement />}
|
||||
{activeSubTab === 'workOrder' && <WorkOrderSettingsManagement />}
|
||||
{activeSubTab === 'rule' && <RuleConfigManagement />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -853,7 +853,7 @@ export default function MapEditorPage() {
|
||||
>
|
||||
<MapPinned size={18} color="#13C2C2" style={{ alignSelf: 'center' }} />
|
||||
<div style={{ fontSize: 13, marginTop: 4 }}>分区</div>
|
||||
<div style={{ fontSize: 11, color: '#86909C' }}>机器人任务中的休息位置</div>
|
||||
<div style={{ fontSize: 11, color: '#86909C' }}>场站中发分区,绑定规划路线</div>
|
||||
</div>
|
||||
<div
|
||||
onClick={() => setActiveType('routeGen')}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
161
src/components/taskRadio/TaskTypeRadio.css
Normal file
161
src/components/taskRadio/TaskTypeRadio.css
Normal file
@@ -0,0 +1,161 @@
|
||||
.task-type-radio {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.task-type-radio__title {
|
||||
margin-bottom: 12px;
|
||||
color: #000;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.task-type-radio__group {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.task-type-radio__option.ant-radio-wrapper {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-inline-end: 0;
|
||||
align-items: stretch;
|
||||
color: inherit;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.task-type-radio__option > .ant-radio {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.task-type-radio__option > span:last-child {
|
||||
display: block;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.task-type-radio__card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 62px;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
background: #fff;
|
||||
border: 1px solid #dce5f2;
|
||||
border-radius: 6px;
|
||||
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
background-color 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.task-type-radio__card:hover {
|
||||
border-color: #91caff;
|
||||
}
|
||||
|
||||
.task-type-radio__card--checked {
|
||||
border: 1px solid #4096ff;
|
||||
background: #f0f7ff;
|
||||
box-shadow: 0 0 0 1px rgb(22 119 255 / 5%);
|
||||
}
|
||||
|
||||
.task-type-radio__icon {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
color: #1677ff;
|
||||
background: #e6f4ff;
|
||||
border-radius: 5px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.task-type-radio__content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.task-type-radio__name {
|
||||
margin-bottom: 4px;
|
||||
overflow: hidden;
|
||||
|
||||
color: #262626;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 18px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-type-radio__description {
|
||||
overflow: hidden;
|
||||
|
||||
color: #8c9bb0;
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
line-height: 15px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-type-radio__status {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
color: #1677ff;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.task-type-radio__empty {
|
||||
display: block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.task-type-radio__option:focus-visible
|
||||
.task-type-radio__card {
|
||||
outline: 2px solid #91caff;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.task-type-radio__group {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.task-type-radio__card {
|
||||
padding: 8px;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.task-type-radio__icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.task-type-radio__description {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
100
src/components/taskRadio/taskRadio.tsx
Normal file
100
src/components/taskRadio/taskRadio.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import React from 'react';
|
||||
import { Radio } from 'antd';
|
||||
import {
|
||||
LoginOutlined,
|
||||
ClockCircleOutlined,
|
||||
CheckCircleFilled,
|
||||
} from '@ant-design/icons';
|
||||
import './TaskTypeRadio.css'
|
||||
type DispatchMode = 'immediate' | 'scheduled';
|
||||
|
||||
interface TaskTypeRadioProps {
|
||||
value: DispatchMode;
|
||||
onChange: (value: DispatchMode) => void;
|
||||
}
|
||||
|
||||
const TaskTypeRadio: React.FC<TaskTypeRadioProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
}) => {
|
||||
const options: {
|
||||
value: DispatchMode;
|
||||
title: string;
|
||||
description: string;
|
||||
icon: React.ReactNode;
|
||||
}[] = [
|
||||
{
|
||||
value: 'immediate',
|
||||
title: '立即执行任务',
|
||||
description: '下发后机器人立即开始执行',
|
||||
icon: <LoginOutlined />,
|
||||
},
|
||||
{
|
||||
value: 'scheduled',
|
||||
title: '定时任务',
|
||||
description: '设置开始时间和重复周期',
|
||||
icon: <ClockCircleOutlined />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="task-type-radio">
|
||||
<div className="task-type-radio__title">
|
||||
选择任务类型
|
||||
</div>
|
||||
|
||||
<Radio.Group
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
className="task-type-radio__group"
|
||||
>
|
||||
{options.map((option) => {
|
||||
const checked = value === option.value;
|
||||
|
||||
return (
|
||||
<Radio
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
className="task-type-radio__option"
|
||||
>
|
||||
<div
|
||||
className={[
|
||||
'task-type-radio__card',
|
||||
checked
|
||||
? 'task-type-radio__card--checked'
|
||||
: '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
<div className="task-type-radio__icon">
|
||||
{option.icon}
|
||||
</div>
|
||||
|
||||
<div className="task-type-radio__content">
|
||||
<div className="task-type-radio__name">
|
||||
{option.title}
|
||||
</div>
|
||||
|
||||
<div className="task-type-radio__description">
|
||||
{option.description}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="task-type-radio__status">
|
||||
{checked ? (
|
||||
<CheckCircleFilled />
|
||||
) : (
|
||||
<span className="task-type-radio__empty" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Radio>
|
||||
);
|
||||
})}
|
||||
</Radio.Group>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskTypeRadio;
|
||||
@@ -960,6 +960,28 @@
|
||||
repeatNone: 'Repeat None',
|
||||
repeatTypeLabel: 'Repeat Type Label',
|
||||
repeatWeekly: 'Repeat Weekly',
|
||||
taskDispatch: 'Task Dispatch',
|
||||
dispatchSource: 'Dispatch Source',
|
||||
dispatchByRoute: 'By Route',
|
||||
dispatchByRegion: 'By Region',
|
||||
noRouteInRegion: 'No routes bound to this region',
|
||||
taskSelectRegion: 'Select Task Region',
|
||||
taskDispatchMode: 'Dispatch Mode',
|
||||
taskModeImmediate: 'Immediate',
|
||||
taskModeScheduled: 'Scheduled',
|
||||
taskModeImmediateTip: 'Execute immediately after dispatch',
|
||||
taskModeScheduledTip: 'Execute automatically by the configured schedule',
|
||||
taskConfig: 'Task Configuration',
|
||||
taskSelectRoute: 'Select Routes',
|
||||
taskSelectRegionTip: 'Please select task regions first — bound routes will be linked automatically',
|
||||
taskSelectRobot: 'Execute Robot',
|
||||
taskScheduleSetting: 'Scheduled Task Settings',
|
||||
taskExecuteNow: 'Dispatch',
|
||||
taskSaveSchedule: 'Save Schedule',
|
||||
taskSelectRegionFirst: 'Please select task region first',
|
||||
taskSelectRouteFirst: 'The selected region has no bound routes',
|
||||
taskSelectRobotFirst: 'Please select an execute robot',
|
||||
taskDispatchResult: 'Dispatched. Success {success}, Failed {fail}',
|
||||
requestControlPermission: 'Request Control Permission',
|
||||
controlRequesting: 'Requesting...',
|
||||
controlGranted: 'Control Granted',
|
||||
@@ -2582,6 +2604,42 @@
|
||||
workOrderCompletionRate: 'Work Order Completion Rate',
|
||||
},
|
||||
systemSetting: {
|
||||
ruleConfig: 'Rule Config',
|
||||
ruleName: 'Rule Name',
|
||||
ruleType: 'Rule Type',
|
||||
ruleTypeLowBatteryReturn: 'Low Battery Return',
|
||||
deviceScope: 'Device Scope',
|
||||
deviceScopeAll: 'All Devices',
|
||||
deviceScopeGroup: 'Device Group',
|
||||
deviceScopeSome: 'Specific Devices',
|
||||
triggerKey: 'Trigger Field',
|
||||
triggerKeyBattery: 'Battery',
|
||||
triggerKeyTemperature: 'Temperature',
|
||||
triggerKeyOfflineDuration: 'Offline Duration',
|
||||
triggerKeyVoltage: 'Voltage',
|
||||
triggerValue: 'Threshold',
|
||||
actionType: 'Action Type',
|
||||
actionTypeAutoTask: 'Auto Task',
|
||||
actionTypeSendCommand: 'Send Command',
|
||||
actionTypeAlarm: 'Alarm',
|
||||
actionTypeWorkOrder: 'Work Order',
|
||||
actionArgs: 'Action Args',
|
||||
enabledLabel: 'Enabled',
|
||||
forceExecute: 'Force Execute',
|
||||
pleaseInputRuleName: 'Please input rule name',
|
||||
pleaseSelectRuleType: 'Please select rule type',
|
||||
pleaseSelectDeviceScope: 'Please select device scope',
|
||||
pleaseSelectTriggerKey: 'Please select trigger field',
|
||||
pleaseSelectOperator: 'Please select operator',
|
||||
pleaseInputTriggerValue: 'Please input threshold',
|
||||
pleaseSelectActionType: 'Please select action type',
|
||||
ruleList: 'Rule List',
|
||||
addRule: 'Add Rule',
|
||||
editRule: 'Edit Rule',
|
||||
viewRule: 'View Rule',
|
||||
operator: 'Operator',
|
||||
invalidJson: 'Invalid action args JSON',
|
||||
confirmDeleteRule: 'Are you sure to delete rule "{{name}}"?',
|
||||
flvPlaybackError: 'FLV playback error',
|
||||
flvNetworkError: 'Network error while loading the stream',
|
||||
mediaDecodeError: 'Media decode error',
|
||||
|
||||
@@ -902,6 +902,28 @@ const zh = {
|
||||
repeatNone: '重复无',
|
||||
repeatTypeLabel: '重复类型标签',
|
||||
repeatWeekly: '重复每周',
|
||||
taskDispatch: '任务下发',
|
||||
dispatchSource: '下发方式',
|
||||
dispatchByRoute: '按路线下发',
|
||||
dispatchByRegion: '按区域下发',
|
||||
noRouteInRegion: '该区域暂无绑定路径',
|
||||
taskSelectRegion: '选择任务区域',
|
||||
taskDispatchMode: '选择任务方式',
|
||||
taskModeImmediate: '立即执行',
|
||||
taskModeScheduled: '定时任务',
|
||||
taskModeImmediateTip: '下发后立即开始执行',
|
||||
taskModeScheduledTip: '按照设定的时间周期自动执行',
|
||||
taskConfig: '任务配置',
|
||||
taskSelectRoute: '选择路径',
|
||||
taskSelectRegionTip: '请先选择任务区域,系统会自动关联已绑定的路径',
|
||||
taskSelectRobot: '执行机器人',
|
||||
taskScheduleSetting: '定时任务设置',
|
||||
taskExecuteNow: '下发任务',
|
||||
taskSaveSchedule: '保存定时任务',
|
||||
taskSelectRegionFirst: '请先选择任务区域',
|
||||
taskSelectRouteFirst: '所选区域未关联任何路径',
|
||||
taskSelectRobotFirst: '请先选择执行机器人',
|
||||
taskDispatchResult: '下发完成。成功 {success} 条,失败 {fail} 条',
|
||||
requestControlPermission: '请求控制权限',
|
||||
controlRequesting: '申请中...',
|
||||
controlGranted: '已获得控制',
|
||||
@@ -2644,6 +2666,42 @@ const zh = {
|
||||
workOrderCompletionRate: '工作工单完成率',
|
||||
},
|
||||
systemSetting: {
|
||||
ruleConfig: '规则配置',
|
||||
ruleName: '规则名称',
|
||||
ruleType: '规则类型',
|
||||
ruleTypeLowBatteryReturn: '低电量返航',
|
||||
deviceScope: '适用范围',
|
||||
deviceScopeAll: '所有设备',
|
||||
deviceScopeGroup: '设备组',
|
||||
deviceScopeSome: '特定设备',
|
||||
triggerKey: '触发字段',
|
||||
triggerKeyBattery: '电量',
|
||||
triggerKeyTemperature: '温度',
|
||||
triggerKeyOfflineDuration: '离线时长',
|
||||
triggerKeyVoltage: '电压',
|
||||
triggerValue: '阈值',
|
||||
actionType: '执行动作',
|
||||
actionTypeAutoTask: '路径规划任务',
|
||||
actionTypeSendCommand: '下发指令',
|
||||
actionTypeAlarm: '告警',
|
||||
actionTypeWorkOrder: '工单下发',
|
||||
actionArgs: '动作参数',
|
||||
enabledLabel: '是否启用',
|
||||
forceExecute: '强制执行',
|
||||
pleaseInputRuleName: '请输入规则名称',
|
||||
pleaseSelectRuleType: '请选择规则类型',
|
||||
pleaseSelectDeviceScope: '请选择适用范围',
|
||||
pleaseSelectTriggerKey: '请选择触发字段',
|
||||
pleaseSelectOperator: '请选择比较运算符',
|
||||
pleaseInputTriggerValue: '请输入阈值',
|
||||
pleaseSelectActionType: '请选择执行动作',
|
||||
ruleList: '规则列表',
|
||||
addRule: '新增规则',
|
||||
editRule: '编辑规则',
|
||||
viewRule: '查看规则',
|
||||
operator: '比较运算符',
|
||||
invalidJson: '动作参数 JSON 格式不正确',
|
||||
confirmDeleteRule: '确定删除规则「{{name}}」吗?',
|
||||
flvPlaybackError: 'FLV 播放出错',
|
||||
flvNetworkError: '网络错误,加载失败',
|
||||
mediaDecodeError: '媒体解码失败',
|
||||
|
||||
Reference in New Issue
Block a user