index.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. import React, { useState } from 'react';
  2. import { StatusColorEnum } from '@/components/BadgeStatus';
  3. import { Ellipsis, TableCard } from '@/components';
  4. import '@/style/common.less';
  5. import '../../index.less';
  6. import styles from './index.less';
  7. import type { SceneItem } from '@/pages/rule-engine/Scene/typings';
  8. import { CheckOutlined, DownOutlined, UpOutlined } from '@ant-design/icons';
  9. import classNames from 'classnames';
  10. import { ActionsType, BranchesThen } from '@/pages/rule-engine/Scene/typings';
  11. import MyTooltip from './MyTooltip';
  12. const imageMap = new Map();
  13. imageMap.set('timer', require('/public/images/scene/scene-timer.png'));
  14. imageMap.set('manual', require('/public/images/scene/scene-hand.png'));
  15. imageMap.set('device', require('/public/images/scene/scene-device.png'));
  16. const iconMap = new Map();
  17. iconMap.set('timer', require('/public/images/scene/trigger-type-icon/timing.png'));
  18. iconMap.set('manual', require('/public/images/scene/trigger-type-icon/manual.png'));
  19. iconMap.set('device', require('/public/images/scene/trigger-type-icon/device.png'));
  20. // @ts-ignore
  21. export interface SceneCardProps extends SceneItem {
  22. detail?: React.ReactNode;
  23. tools?: React.ReactNode[];
  24. avatarSize?: number;
  25. className?: string;
  26. onUnBind?: (e: any) => void;
  27. showBindBtn?: boolean;
  28. cardType?: 'bind' | 'unbind';
  29. showTool?: boolean;
  30. onClick?: () => void;
  31. }
  32. enum TriggerWayType {
  33. manual = '手动触发',
  34. timer = '定时触发',
  35. device = '设备触发',
  36. }
  37. enum UnitEnum {
  38. seconds = '秒',
  39. minutes = '分',
  40. hours = '小时',
  41. }
  42. // const selectorRender = (obj: any) => {
  43. // switch (obj?.selector) {
  44. // case 'all':
  45. // return (
  46. // <span>
  47. // 所有的<span className={styles['trigger-device']}>{obj?.productId}</span>
  48. // </span>
  49. // );
  50. // case 'fixed':
  51. // return (
  52. // <span>
  53. // 设备
  54. // <span className={styles['trigger-device']}>
  55. // {(obj?.selectorValues || '').map((item: any) => item?.name).join(',')}
  56. // </span>
  57. // </span>
  58. // );
  59. // case 'org':
  60. // return (
  61. // <span>
  62. // 部门
  63. // <span className={styles['trigger-device']}>
  64. // {(obj?.selectorValues || '').map((item: any) => item?.name).join(',')}
  65. // </span>
  66. // </span>
  67. // );
  68. // default:
  69. // return '';
  70. // }
  71. // };
  72. // const selectorContextRender = (obj: any) => {
  73. // switch (obj?.selector) {
  74. // case 'all':
  75. // return `所有的${obj?.productId}`;
  76. // case 'fixed':
  77. // return `设备${(obj?.selectorValues || '').map((item: any) => item?.name).join(',')}`;
  78. // case 'org':
  79. // return `部门${(obj?.selectorValues || '').map((item: any) => item?.name).join(',')}`;
  80. // default:
  81. // return '';
  82. // }
  83. // };
  84. // const timerRender = (timer: any) => {
  85. // if (timer?.trigger && timer?.mod) {
  86. // const trigger = timer?.trigger;
  87. // const mod = timer?.mod;
  88. // const str: string = trigger === 'week' ? '星期' : trigger === 'month' ? '月' : timer?.cron;
  89. // if (mod === 'once') {
  90. // return `,每${str}${timer.when.join('/')},${timer?.once.time}执行一次`;
  91. // } else {
  92. // return `每${str}${timer.when.join('/')},${timer?.period?.from}-${timer?.period.to},每${
  93. // timer?.period.every
  94. // }${UnitEnum[timer?.period?.unit]}执行一次`;
  95. // }
  96. // }
  97. // return '';
  98. // };
  99. // const operatorRender = (operation: any) => {
  100. // switch (operation?.operator) {
  101. // case 'online':
  102. // return '上线';
  103. // case 'offline':
  104. // return '离线';
  105. // case 'reportEvent':
  106. // return `上报事件${operation?.options?.eventName}`;
  107. // case 'reportProperty':
  108. // return `上报属性${(operation?.options?.propertiesName || []).join(',')}`;
  109. // case 'readProperty':
  110. // return `读取属性${(operation?.options?.propertiesName || []).join(',')}`;
  111. // case 'writeProperty':
  112. // return `修改属性${(operation?.options?.propertiesName || []).join(',')}`;
  113. // case 'invokeFunction':
  114. // return `调用功能${operation?.options?.functionName}`;
  115. // default:
  116. // return '';
  117. // }
  118. // };
  119. const notifyRender = (data: ActionsType | undefined) => {
  120. switch (data?.notify?.notifyType) {
  121. case 'dingTalk':
  122. return (
  123. <div className={styles['notify-img-highlight']}>
  124. 向<span>{data?.options?.notifierName || data?.notify?.notifierId}</span>
  125. 通过<span>钉钉</span>发送
  126. <span>{data?.options?.templateName || data?.notify?.templateId}</span>
  127. </div>
  128. );
  129. case 'weixin':
  130. return (
  131. <div className={styles['notify-img-highlight']}>
  132. 向<span>{data?.options?.sendTo || ''}</span>
  133. <span>{data?.options?.orgName || ''}</span>
  134. <span>{data?.options?.tagName || ''}</span>
  135. 通过<span>微信</span>发送
  136. <span>{data?.options?.templateName || data?.notify?.templateId}</span>
  137. </div>
  138. );
  139. case 'email':
  140. return (
  141. <div className={styles['notify-img-highlight']}>
  142. 向<span>{data?.options?.sendTo || ''}</span>
  143. 通过<span>邮件</span>发送
  144. <span>{data?.options?.templateName || data?.notify?.templateId}</span>
  145. </div>
  146. );
  147. case 'voice':
  148. return (
  149. <div className={styles['notify-img-highlight']}>
  150. 向<span>{data?.options?.calledNumber || ''}</span>
  151. 通过<span>语音</span>发送
  152. <span>{data?.options?.templateName || data?.notify?.templateId}</span>
  153. </div>
  154. );
  155. case 'sms':
  156. return (
  157. <div className={styles['notify-img-highlight']}>
  158. 向{data?.options?.sendTo || ''}通过短信发送
  159. {data?.options?.templateName || data?.notify?.templateId}
  160. </div>
  161. );
  162. case 'webhook':
  163. return (
  164. <div className={styles['notify-img-highlight']}>
  165. 通过<span>webhook</span>发送
  166. <span>{data?.options?.templateName || data?.notify?.templateId}</span>
  167. </div>
  168. );
  169. default:
  170. return null;
  171. }
  172. };
  173. const deviceRender = (data: ActionsType | undefined) => {
  174. switch (data?.device?.selector) {
  175. case 'fixed':
  176. return (
  177. <div className={styles['notify-text-highlight']}>
  178. {data?.options?.type}
  179. <span className={styles['trigger-device']}>{data?.options?.name}</span>
  180. {data?.options?.properties}
  181. </div>
  182. );
  183. case 'tag':
  184. return (
  185. <div className={styles['notify-text-highlight']}>
  186. {data?.options?.type}
  187. {data.options?.taglist.map((item: any) => (
  188. <span className={styles['trigger-device']}>
  189. {item.type}
  190. {item.name}
  191. {item.value}
  192. </span>
  193. ))}
  194. {data?.options?.productName}
  195. {data?.options?.properties}
  196. </div>
  197. );
  198. case 'relation':
  199. return (
  200. <div className={styles['notify-text-highlight']}>
  201. {data?.options?.type}与
  202. <span className={styles['trigger-device']}>{data?.options?.name}</span>具有相同
  203. {data?.options?.relationName}的{data?.options?.productName}设备的
  204. {data?.options?.properties}
  205. </div>
  206. );
  207. default:
  208. return null;
  209. }
  210. };
  211. const actionRender = (action: ActionsType, index: number) => {
  212. switch (action?.executor) {
  213. case 'notify':
  214. return (
  215. <div
  216. className={styles['card-item-content-action-item-right-item']}
  217. key={action?.key || index}
  218. >
  219. <div className={classNames(styles['trigger-contents'], 'ellipsis')}>
  220. {notifyRender(action)}
  221. </div>
  222. </div>
  223. );
  224. case 'delay':
  225. return (
  226. <div
  227. className={styles['card-item-content-action-item-right-item']}
  228. key={action?.key || index}
  229. >
  230. <div className={classNames(styles['trigger-contents'], 'ellipsis')}>
  231. <span style={{ fontWeight: 'bold' }}>
  232. {action?.delay?.time}
  233. {UnitEnum[action?.delay?.unit || '']}
  234. </span>
  235. 后执行后续动作
  236. </div>
  237. </div>
  238. );
  239. case 'device':
  240. return (
  241. <div
  242. className={styles['card-item-content-action-item-right-item']}
  243. key={action?.key || index}
  244. >
  245. <div className={classNames(styles['trigger-contents'], 'ellipsis')}>
  246. {deviceRender(action)}
  247. </div>
  248. </div>
  249. );
  250. case 'alarm':
  251. return (
  252. <div
  253. className={styles['card-item-content-action-item-right-item']}
  254. key={action?.key || index}
  255. >
  256. <div className={classNames(styles['trigger-contents'], 'ellipsis')}>
  257. 满足条件后将触发关联此场景的告警
  258. </div>
  259. </div>
  260. );
  261. default:
  262. return null;
  263. }
  264. };
  265. const conditionsRender = (when: any[], index: number) => {
  266. if (when.length) {
  267. return (when[index]?.terms || []).join('');
  268. }
  269. return '';
  270. };
  271. const branchesActionRender = (actions: any[]) => {
  272. if (actions && actions?.length) {
  273. const list: any[] = [];
  274. actions.map((item, index) => {
  275. const dt = actionRender(item, index);
  276. list.push(dt);
  277. });
  278. return list.map((item, index) => (
  279. <div className={styles['right-item-right-item-contents-item']}>
  280. <div style={{ margin: '0 10px' }}>{item}</div>
  281. <MyTooltip title={actions[index]?.options?.terms || ''}>
  282. {actions[index]?.options?.terms && (
  283. <div className={'ellipsis'} style={{ minWidth: 40 }}>
  284. 动作{index + 1}
  285. {actions[index]?.options?.terms}
  286. </div>
  287. )}
  288. </MyTooltip>
  289. </div>
  290. ));
  291. }
  292. return '';
  293. };
  294. const ContentRender = (data: SceneCardProps) => {
  295. const [visible, setVisible] = useState<boolean>(false);
  296. const type = data.triggerType;
  297. if (!!type && (data.branches || [])?.length) {
  298. const trigger = data?.options?.trigger;
  299. const text =
  300. (trigger?.name || '') +
  301. (trigger?.productName || '') +
  302. (trigger?.when || '') +
  303. (trigger?.time || '') +
  304. (trigger?.extraTime || '') +
  305. (trigger?.type || '');
  306. return (
  307. <div className={styles['card-item-content-box']}>
  308. <MyTooltip placement="topLeft" title={text}>
  309. <div className={classNames(styles['card-item-content-trigger'], 'ellipsis')}>
  310. {trigger?.name || ''}
  311. <span className={styles['trigger-device']}>{trigger?.productName || ''}</span>
  312. {(trigger?.when || '') +
  313. (trigger?.time || '') +
  314. (trigger?.extraTime || '') +
  315. (trigger?.type || '')}
  316. </div>
  317. </MyTooltip>
  318. <div className={styles['card-item-content-action']}>
  319. {(visible ? data.branches || [] : (data?.branches || []).slice(0, 2)).map(
  320. (item: any, index) => {
  321. return (
  322. <div className={styles['card-item-content-action-item']} key={item?.key || index}>
  323. <div className={styles['card-item-content-action-item-left']}>
  324. {type === 'device' ? (index === 0 ? '当' : '否则') : '执行'}
  325. </div>
  326. <div className={styles['card-item-content-action-item-right']}>
  327. <div className={styles['card-item-content-action-item-right-item']}>
  328. {type === 'device' && (
  329. <div className={styles['right-item-left']}>
  330. <MyTooltip title={conditionsRender(data.options?.terms || [], index)}>
  331. <div className={classNames(styles['trigger-conditions'], 'ellipsis')}>
  332. {conditionsRender(data.options?.terms || [], index)}
  333. </div>
  334. </MyTooltip>
  335. {item.shakeLimit?.enabled && (
  336. <MyTooltip
  337. title={`(${item.shakeLimit?.time}秒内发生${item.shakeLimit?.threshold}
  338. 次以上时执行一次)`}
  339. >
  340. <div className={classNames(styles['trigger-shake'], 'ellipsis')}>
  341. ({item.shakeLimit?.time}秒内发生{item.shakeLimit?.threshold}
  342. 次以上时执行一次)
  343. </div>
  344. </MyTooltip>
  345. )}
  346. </div>
  347. )}
  348. <div className={styles['right-item-right']}>
  349. {(item?.then || []).map((i: BranchesThen, _index: number) => (
  350. <div key={i?.key || _index} className={styles['right-item-right-item']}>
  351. <div className={styles['trigger-ways']}>
  352. {i.parallel ? '并行执行' : '串行执行'}
  353. </div>
  354. <div className={classNames(styles['right-item-right-item-contents'])}>
  355. {branchesActionRender(Array.isArray(i?.actions) ? i?.actions : [])}
  356. </div>
  357. </div>
  358. ))}
  359. </div>
  360. </div>
  361. </div>
  362. </div>
  363. );
  364. },
  365. )}
  366. {(data?.branches || []).length > 2 && (
  367. <div
  368. className={styles['trigger-actions-more']}
  369. onClick={(e) => {
  370. e.stopPropagation();
  371. setVisible(!visible);
  372. }}
  373. >
  374. 展开更多{!visible ? <DownOutlined /> : <UpOutlined />}
  375. </div>
  376. )}
  377. </div>
  378. </div>
  379. );
  380. } else {
  381. return <div className={styles['card-item-content-box-empty']}>未配置规则</div>;
  382. }
  383. };
  384. export const ExtraSceneCard = (props: SceneCardProps) => {
  385. return (
  386. <TableCard
  387. status={props.state.value}
  388. statusText={props.state.text}
  389. statusNames={{
  390. started: StatusColorEnum.success,
  391. disable: StatusColorEnum.error,
  392. notActive: StatusColorEnum.warning,
  393. }}
  394. showTool={props.showTool}
  395. showMask={false}
  396. actions={props.tools}
  397. onClick={props.onClick}
  398. className={props.className}
  399. contentClassName={styles['content-class']}
  400. >
  401. <div className={'pro-table-card-item context-access'}>
  402. <div className={'card-item-avatar'}>
  403. <img width={88} height={88} src={imageMap.get(props.triggerType)} alt={''} />
  404. </div>
  405. <div className={'card-item-body'}>
  406. <div className={'card-item-header'}>
  407. <div className={'card-item-header-item'} style={{ maxWidth: '50%' }}>
  408. <Ellipsis
  409. title={props.name}
  410. style={{ fontSize: 18, opacity: 0.85, color: '#000', fontWeight: 'bold' }}
  411. />
  412. </div>
  413. <div className={'card-item-header-item'} style={{ maxWidth: '50%' }}>
  414. <Ellipsis
  415. title={props.description}
  416. style={{ color: 'rgba(0, 0, 0, 0.65)', margin: '3px 0 0 10px' }}
  417. />
  418. </div>
  419. </div>
  420. <ContentRender {...props} />
  421. </div>
  422. </div>
  423. <div className={styles['card-item-trigger-type']}>
  424. <div className={styles['card-item-trigger-type-text']}>
  425. <img height={16} src={iconMap.get(props.triggerType)} style={{ marginRight: 8 }} />
  426. {TriggerWayType[props.triggerType]}
  427. </div>
  428. </div>
  429. <div className={'checked-icon'}>
  430. <div>
  431. <CheckOutlined />
  432. </div>
  433. </div>
  434. </TableCard>
  435. );
  436. };
  437. export default (props: SceneCardProps) => {
  438. return (
  439. <TableCard
  440. showMask={false}
  441. detail={props.detail}
  442. showTool={props.showTool}
  443. actions={props.tools}
  444. status={props.state.value}
  445. statusText={props.state.text}
  446. statusNames={{
  447. started: StatusColorEnum.success,
  448. disable: StatusColorEnum.error,
  449. notActive: StatusColorEnum.warning,
  450. }}
  451. contentClassName={styles['content-class']}
  452. >
  453. <div className={'pro-table-card-item'} onClick={props.onClick}>
  454. <div className={'card-item-avatar'}>
  455. <img width={88} height={88} src={imageMap.get(props.triggerType)} alt={''} />
  456. </div>
  457. <div className={'card-item-body'}>
  458. <div className={'card-item-header'}>
  459. <div className={'card-item-header-item'} style={{ maxWidth: '50%' }}>
  460. <Ellipsis
  461. title={props.name}
  462. style={{ fontSize: 18, opacity: 0.85, color: '#000', fontWeight: 'bold' }}
  463. />
  464. </div>
  465. <div className={'card-item-header-item'} style={{ maxWidth: '50%' }}>
  466. <Ellipsis
  467. title={props.description}
  468. style={{ color: 'rgba(0, 0, 0, 0.65)', margin: '3px 0 0 10px' }}
  469. />
  470. </div>
  471. </div>
  472. <ContentRender {...props} />
  473. </div>
  474. </div>
  475. <div className={styles['card-item-trigger-type']}>
  476. <div className={styles['card-item-trigger-type-text']}>
  477. <img height={16} src={iconMap.get(props.triggerType)} style={{ marginRight: 8 }} />
  478. {TriggerWayType[props.triggerType]}
  479. </div>
  480. </div>
  481. </TableCard>
  482. );
  483. };