index.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. import { PageContainer } from '@ant-design/pro-layout';
  2. import {
  3. Button,
  4. Card,
  5. Form,
  6. Input,
  7. InputNumber,
  8. message,
  9. Radio,
  10. Space,
  11. Switch,
  12. Tooltip,
  13. } from 'antd';
  14. import { useIntl, useLocation } from 'umi';
  15. import { useEffect, useRef, useState } from 'react';
  16. import { PermissionButton, TitleComponent } from '@/components';
  17. import ActionItems from './action/action';
  18. import { PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons';
  19. import { TimingTrigger, TriggerWay } from './components';
  20. import { TriggerWayType } from './components/TriggerWay';
  21. import TriggerTerm from '@/pages/rule-engine/Scene/TriggerTerm';
  22. import TriggerDevice from './trigger/device';
  23. import { service } from '../index';
  24. import './index.less';
  25. import { model } from '@formily/reactive';
  26. import type { FormModelType } from '@/pages/rule-engine/Scene/typings';
  27. type ShakeLimitType = {
  28. enabled: boolean;
  29. groupType?: string;
  30. time?: number;
  31. threshold?: number;
  32. alarmFirst?: boolean;
  33. };
  34. const DefaultShakeLimit = {
  35. enabled: false,
  36. alarmFirst: true,
  37. };
  38. export let FormModel = model<FormModelType>({});
  39. const CronRegEx = new RegExp(
  40. '(((^([0-9]|[0-5][0-9])(\\,|\\-|\\/){1}([0-9]|[0-5][0-9]) )|^([0-9]|[0-5][0-9]) |^(\\* ))((([0-9]|[0-5][0-9])(\\,|\\-|\\/){1}([0-9]|[0-5][0-9]) )|([0-9]|[0-5][0-9]) |(\\* ))((([0-9]|[01][0-9]|2[0-3])(\\,|\\-|\\/){1}([0-9]|[01][0-9]|2[0-3]) )|([0-9]|[01][0-9]|2[0-3]) |(\\* ))((([0-9]|[0-2][0-9]|3[01])(\\,|\\-|\\/){1}([0-9]|[0-2][0-9]|3[01]) )|(([0-9]|[0-2][0-9]|3[01]) )|(\\? )|(\\* )|(([1-9]|[0-2][0-9]|3[01])L )|([1-7]W )|(LW )|([1-7]\\#[1-4] ))((([1-9]|0[1-9]|1[0-2])(\\,|\\-|\\/){1}([1-9]|0[1-9]|1[0-2]) )|([1-9]|0[1-9]|1[0-2]) |(\\* ))(([1-7](\\,|\\-|\\/){1}[1-7])|([1-7])|(\\?)|(\\*)|(([1-7]L)|([1-7]\\#[1-4]))))|(((^([0-9]|[0-5][0-9])(\\,|\\-|\\/){1}([0-9]|[0-5][0-9]) )|^([0-9]|[0-5][0-9]) |^(\\* ))((([0-9]|[0-5][0-9])(\\,|\\-|\\/){1}([0-9]|[0-5][0-9]) )|([0-9]|[0-5][0-9]) |(\\* ))((([0-9]|[01][0-9]|2[0-3])(\\,|\\-|\\/){1}([0-9]|[01][0-9]|2[0-3]) )|([0-9]|[01][0-9]|2[0-3]) |(\\* ))((([0-9]|[0-2][0-9]|3[01])(\\,|\\-|\\/){1}([0-9]|[0-2][0-9]|3[01]) )|(([0-9]|[0-2][0-9]|3[01]) )|(\\? )|(\\* )|(([1-9]|[0-2][0-9]|3[01])L )|([1-7]W )|(LW )|([1-7]\\#[1-4] ))((([1-9]|0[1-9]|1[0-2])(\\,|\\-|\\/){1}([1-9]|0[1-9]|1[0-2]) )|([1-9]|0[1-9]|1[0-2]) |(\\* ))(([1-7](\\,|\\-|\\/){1}[1-7] )|([1-7] )|(\\? )|(\\* )|(([1-7]L )|([1-7]\\#[1-4]) ))((19[789][0-9]|20[0-9][0-9])\\-(19[789][0-9]|20[0-9][0-9])))',
  41. );
  42. export default () => {
  43. const location = useLocation();
  44. const [form] = Form.useForm();
  45. const intl = useIntl();
  46. const triggerRef = useRef<any>();
  47. const { getOtherPermission } = PermissionButton.usePermission('rule-engine/Scene');
  48. const [triggerType, setTriggerType] = useState('');
  49. // const [triggerValue, setTriggerValue] = useState<any>();
  50. const [loading, setLoading] = useState(false);
  51. const [parallel, setParallel] = useState(true); // 是否并行
  52. const [shakeLimit, setShakeLimit] = useState<ShakeLimitType>(DefaultShakeLimit);
  53. const [requestParams, setRequestParams] = useState<any>(undefined);
  54. const [actionsData, setActionsData] = useState<any[]>([]);
  55. const [isEdit, setIsEdit] = useState(false);
  56. const getDetail = async (id: string) => {
  57. const resp = await service.detail(id);
  58. if (resp.status === 200 && resp.result) {
  59. setIsEdit(true);
  60. const _data: any = resp.result;
  61. FormModel = _data;
  62. form.setFieldsValue(_data);
  63. setParallel(_data.parallel);
  64. setShakeLimit(_data.shakeLimit || DefaultShakeLimit);
  65. if (_data.trigger?.device?.selectorValues) {
  66. setRequestParams({ trigger: _data.trigger });
  67. }
  68. if (_data.actions) {
  69. setActionsData(_data.actions);
  70. }
  71. }
  72. };
  73. useEffect(() => {
  74. const params = new URLSearchParams(location.search);
  75. const id = params.get('id');
  76. if (id) {
  77. getDetail(id);
  78. }
  79. }, [location]);
  80. const saveData = async () => {
  81. const formData = await form.validateFields();
  82. let triggerData = undefined;
  83. // 获取触发条件数据
  84. if (triggerRef.current) {
  85. triggerData = await triggerRef.current.getTriggerForm();
  86. console.log(JSON.stringify(triggerData), 'trigger');
  87. if (!triggerData) {
  88. return;
  89. }
  90. formData.terms = triggerData.trigger;
  91. }
  92. console.log(formData);
  93. if (formData) {
  94. setLoading(true);
  95. const resp = formData.id ? await service.updateScene(formData) : await service.save(formData);
  96. setLoading(false);
  97. if (resp.status === 200) {
  98. message.success('操作成功');
  99. history.back();
  100. } else {
  101. message.error(resp.message);
  102. }
  103. }
  104. };
  105. const AntiShake = (
  106. <Space>
  107. <TitleComponent data={'触发条件'} style={{ margin: 0 }} />
  108. <Switch
  109. checked={shakeLimit.enabled}
  110. checkedChildren="开启防抖"
  111. unCheckedChildren="关闭防抖"
  112. onChange={(e) => {
  113. setShakeLimit({
  114. ...shakeLimit,
  115. enabled: e,
  116. });
  117. form.setFieldsValue({ shakeLimit });
  118. }}
  119. />
  120. {shakeLimit.enabled && (
  121. <>
  122. <InputNumber
  123. value={shakeLimit.time}
  124. onChange={(e: number) => {
  125. setShakeLimit({
  126. ...shakeLimit,
  127. time: e,
  128. });
  129. form.setFieldsValue({ shakeLimit });
  130. }}
  131. />
  132. <span> 秒内发生 </span>
  133. <InputNumber
  134. value={shakeLimit.threshold}
  135. onChange={(e: number) => {
  136. setShakeLimit({
  137. ...shakeLimit,
  138. threshold: e,
  139. });
  140. form.setFieldsValue({ shakeLimit });
  141. }}
  142. />
  143. <span>次及以上时,处理</span>
  144. <Radio.Group
  145. value={shakeLimit.alarmFirst}
  146. options={[
  147. { label: '第一次', value: true },
  148. { label: '最后一次', value: false },
  149. ]}
  150. optionType="button"
  151. onChange={(e) => {
  152. console.log(e);
  153. setShakeLimit({
  154. ...shakeLimit,
  155. alarmFirst: e.target.value,
  156. });
  157. form.setFieldsValue({ shakeLimit });
  158. }}
  159. ></Radio.Group>
  160. </>
  161. )}
  162. </Space>
  163. );
  164. return (
  165. <PageContainer>
  166. <Card>
  167. <Form
  168. form={form}
  169. colon={false}
  170. layout={'vertical'}
  171. preserve={false}
  172. className={'scene-save'}
  173. onValuesChange={(changeValue, allValues) => {
  174. if (changeValue.trigger) {
  175. setRequestParams({ trigger: allValues.trigger });
  176. }
  177. if (allValues.actions) {
  178. setActionsData(allValues.actions);
  179. }
  180. FormModel = { ...allValues };
  181. }}
  182. >
  183. <Form.Item
  184. name={'name'}
  185. label={<TitleComponent data={'名称'} style={{ margin: 0 }} />}
  186. rules={[
  187. { required: true, message: '请输入名称' },
  188. {
  189. max: 64,
  190. message: intl.formatMessage({
  191. id: 'pages.form.tip.max64',
  192. defaultMessage: '最多输入64个字符',
  193. }),
  194. },
  195. ]}
  196. required
  197. >
  198. <Input placeholder={'请输入名称'} />
  199. </Form.Item>
  200. <Form.Item label={<TitleComponent data={'触发方式'} style={{ margin: 0 }} />} required>
  201. <Form.Item
  202. name={['trigger', 'type']}
  203. rules={[{ required: true, message: '请选择触发方式' }]}
  204. >
  205. <TriggerWay onSelect={setTriggerType} disabled={isEdit} />
  206. </Form.Item>
  207. {triggerType === TriggerWayType.timing && (
  208. <Form.Item
  209. name={['trigger', 'timer']}
  210. rules={[
  211. {
  212. validator: async (_: any, value: any) => {
  213. if (value) {
  214. if (value.trigger === 'cron') {
  215. if (!value.cron) {
  216. return Promise.reject(new Error('请输入cron表达式'));
  217. } else if (value.cron.length > 64) {
  218. return Promise.reject(new Error('最多可输入64个字符'));
  219. } else if (!CronRegEx.test(value.cron)) {
  220. return Promise.reject(new Error('请输入正确的cron表达式'));
  221. }
  222. } else {
  223. if (!value.when.length) {
  224. return Promise.reject(new Error('请选择时间'));
  225. }
  226. if (value.period) {
  227. if (!value.period.from || !value.period.to) {
  228. return Promise.reject(new Error('请选择时间周期'));
  229. }
  230. if (!value.period.every) {
  231. return Promise.reject(new Error('请输入周期频率'));
  232. }
  233. } else if (value.once) {
  234. if (!value.once.time) {
  235. return Promise.reject(new Error('请选择时间周期'));
  236. }
  237. }
  238. }
  239. }
  240. return Promise.resolve();
  241. },
  242. },
  243. ]}
  244. initialValue={{
  245. trigger: 'week',
  246. mod: 'period',
  247. when: [],
  248. period: {
  249. unit: 'seconds',
  250. },
  251. }}
  252. >
  253. <TimingTrigger className={'trigger-type-content'} />
  254. </Form.Item>
  255. )}
  256. {triggerType === TriggerWayType.device && (
  257. <Form.Item name={['trigger', 'device']}>
  258. <TriggerDevice className={'trigger-type-content'} />
  259. </Form.Item>
  260. )}
  261. </Form.Item>
  262. {triggerType === TriggerWayType.device &&
  263. requestParams &&
  264. requestParams.trigger?.device?.productId ? (
  265. <Form.Item label={AntiShake}>
  266. <TriggerTerm
  267. ref={triggerRef}
  268. params={requestParams}
  269. // value={triggerValue}
  270. />
  271. </Form.Item>
  272. ) : null}
  273. <Form.Item hidden name={'parallel'} initialValue={true}>
  274. <Input />
  275. </Form.Item>
  276. <Form.Item
  277. label={
  278. <Space>
  279. <TitleComponent
  280. data={
  281. <>
  282. <span>执行动作</span>
  283. <span style={{ color: 'red', margin: '0 4px' }}>*</span>
  284. </>
  285. }
  286. style={{ margin: 0 }}
  287. />
  288. <Tooltip
  289. title={
  290. <div>
  291. <div>串行:满足所有执行条件才会触发执行动作</div>
  292. <div>并行:满足任意条件时会触发执行动作</div>
  293. </div>
  294. }
  295. >
  296. <QuestionCircleOutlined style={{ margin: '0 8px', fontSize: 14 }} />
  297. </Tooltip>
  298. <Radio.Group
  299. value={parallel}
  300. options={[
  301. { label: '串行', value: false },
  302. { label: '并行', value: true },
  303. ]}
  304. optionType="button"
  305. onChange={(e) => {
  306. setParallel(e.target.value);
  307. form.setFieldsValue({ parallel: e.target.value });
  308. }}
  309. ></Radio.Group>
  310. </Space>
  311. }
  312. >
  313. <Form.List
  314. name="actions"
  315. rules={[
  316. {
  317. validator: async (_: any, value: any) => {
  318. if (!value) {
  319. return Promise.reject(new Error('请添加执行动作'));
  320. }
  321. return Promise.resolve();
  322. },
  323. },
  324. ]}
  325. >
  326. {(fields, { add, remove }, { errors }) => (
  327. <>
  328. <div className={'scene-actions'}>
  329. {fields.map(({ key, name, ...restField }) => (
  330. <ActionItems
  331. key={key}
  332. form={form}
  333. restField={restField}
  334. name={name}
  335. triggerType={triggerType}
  336. onRemove={() => remove(name)}
  337. actionItemData={actionsData.length && actionsData[name]}
  338. />
  339. ))}
  340. <Form.Item noStyle>
  341. <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
  342. 新增
  343. </Button>
  344. </Form.Item>
  345. </div>
  346. <Form.ErrorList errors={errors} />
  347. </>
  348. )}
  349. </Form.List>
  350. </Form.Item>
  351. <Form.Item
  352. label={<TitleComponent data={'说明'} style={{ margin: 0 }} />}
  353. name={'description'}
  354. >
  355. <Input.TextArea showCount maxLength={200} placeholder={'请输入说明'} rows={4} />
  356. </Form.Item>
  357. {triggerType === TriggerWayType.device &&
  358. requestParams &&
  359. requestParams.trigger?.device?.productId ? (
  360. <Form.Item hidden name={'shakeLimit'} initialValue={DefaultShakeLimit}>
  361. <Input />
  362. </Form.Item>
  363. ) : null}
  364. <Form.Item hidden name={'id'}>
  365. <Input />
  366. </Form.Item>
  367. </Form>
  368. <PermissionButton
  369. isPermission={getOtherPermission(['add', 'update'])}
  370. onClick={saveData}
  371. type={'primary'}
  372. loading={loading}
  373. >
  374. 保存
  375. </PermissionButton>
  376. {/*<Button*/}
  377. {/* onClick={() => {*/}
  378. {/* setTriggerValue({*/}
  379. {/* trigger: [*/}
  380. {/* {*/}
  381. {/* terms: [*/}
  382. {/* {*/}
  383. {/* column: '_now',*/}
  384. {/* termType: 'eq',*/}
  385. {/* source: 'manual',*/}
  386. {/* value: '2022-04-21 14:26:04',*/}
  387. {/* },*/}
  388. {/* ],*/}
  389. {/* },*/}
  390. {/* {*/}
  391. {/* terms: [*/}
  392. {/* {*/}
  393. {/* column: 'properties.test-zhibioa.recent',*/}
  394. {/* termType: 'lte',*/}
  395. {/* source: 'metrics',*/}
  396. {/* value: '123',*/}
  397. {/* },*/}
  398. {/* ],*/}
  399. {/* },*/}
  400. {/* ],*/}
  401. {/* });*/}
  402. {/* }}*/}
  403. {/*>*/}
  404. {/* 设置*/}
  405. {/*</Button>*/}
  406. </Card>
  407. </PageContainer>
  408. );
  409. };