index.tsx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. import { Badge, Button, Card, Divider, Dropdown, Input, Menu, Modal } from 'antd';
  2. import { useDomFullHeight } from '@/hooks';
  3. import '../index.less';
  4. import SearchComponent from '@/components/SearchComponent';
  5. import ProTable, { ActionType, ProColumns } from '@jetlinks/pro-table';
  6. import PermissionButton from '@/components/PermissionButton';
  7. import {
  8. DeleteOutlined,
  9. EditOutlined,
  10. ExportOutlined,
  11. ImportOutlined,
  12. PlayCircleOutlined,
  13. PlusOutlined,
  14. SearchOutlined,
  15. StopOutlined,
  16. } from '@ant-design/icons';
  17. import { useEffect, useRef, useState } from 'react';
  18. import { useIntl } from 'umi';
  19. import ChannelCard from '../channelCard';
  20. import { PageContainer } from '@ant-design/pro-layout';
  21. import Service from './service';
  22. import SaveChannel from './saveChannel';
  23. import SavePoint from './savePoint';
  24. import Import from './import';
  25. import { onlyMessage } from '@/utils/util';
  26. import Export from './Export';
  27. import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
  28. import { map } from 'rxjs/operators';
  29. import Ellipsis from '@/components/Ellipsis';
  30. export const service = new Service('');
  31. const NewModbus = () => {
  32. const { minHeight } = useDomFullHeight(`.modbus`);
  33. const intl = useIntl();
  34. const actionRef = useRef<ActionType>();
  35. const { permission } = PermissionButton.usePermission('link/Channel/Modbus');
  36. const [param, setParam] = useState({});
  37. const [activeKey, setActiveKey] = useState<any>('');
  38. const [visible, setVisible] = useState<boolean>(false);
  39. const [visiblePoint, setVisiblePoint] = useState<boolean>(false);
  40. const [exportVisible, setExportVisible] = useState<boolean>(false);
  41. const [current, setCurrent] = useState<any>({});
  42. const [pointDetail, setPointDetail] = useState<any>({});
  43. const [importVisible, setImportVisible] = useState<boolean>(false);
  44. const [masterList, setMasterList] = useState<any>([]);
  45. const [filterList, setFilterList] = useState([]);
  46. const masterId = useRef<string>('');
  47. const [subscribeTopic] = useSendWebsocketMessage();
  48. const wsRef = useRef<any>();
  49. const [pointList, setPointList] = useState<any>([]);
  50. const [currentData, setCurrentData] = useState<any>({});
  51. const collectMap = new Map();
  52. collectMap.set('running', 'success');
  53. collectMap.set('error', 'error');
  54. collectMap.set('stopped', 'warning');
  55. const menu = (
  56. <Menu>
  57. <Menu.Item key="1">
  58. <PermissionButton
  59. isPermission={permission.export || true}
  60. icon={<ExportOutlined />}
  61. type="default"
  62. onClick={() => {
  63. setExportVisible(true);
  64. }}
  65. >
  66. 批量导出点位
  67. </PermissionButton>
  68. </Menu.Item>
  69. <Menu.Item key="2">
  70. <PermissionButton
  71. isPermission={permission.import || true}
  72. icon={<ImportOutlined />}
  73. onClick={() => {
  74. setImportVisible(true);
  75. }}
  76. >
  77. 批量导入点位
  78. </PermissionButton>
  79. </Menu.Item>
  80. </Menu>
  81. );
  82. const columns: ProColumns<any>[] = [
  83. {
  84. title: '名称',
  85. dataIndex: 'name',
  86. ellipsis: true,
  87. width: 200,
  88. fixed: 'left',
  89. },
  90. {
  91. title: '功能码',
  92. valueType: 'select',
  93. dataIndex: 'function',
  94. valueEnum: {
  95. Coils: { text: '线圈寄存器', status: 'Coils' },
  96. HoldingRegisters: { text: '保存寄存器', status: 'HoldingRegisters' },
  97. InputRegisters: { text: '输入寄存器', status: 'InputRegisters' },
  98. },
  99. render: (_, record: any) => <>{record.function?.text}</>,
  100. },
  101. {
  102. title: '从站ID',
  103. valueType: 'digit',
  104. dataIndex: 'unitId',
  105. },
  106. {
  107. title: '寄存器数量',
  108. search: false,
  109. render: (_, record: any) => <>{record.parameter?.quantity}</>,
  110. },
  111. {
  112. title: '地址',
  113. dataIndex: 'address',
  114. valueType: 'digit',
  115. },
  116. {
  117. title: '当前数据',
  118. search: false,
  119. // ellipsis: true,
  120. width: 150,
  121. render: (record: any) => (
  122. <a
  123. onClick={() => {
  124. if (currentData[record?.id]) {
  125. const arr = currentData[record?.id].match(/[\S]{1,4}/g);
  126. Modal.info({
  127. title: '当前数据',
  128. content: (
  129. <div style={{ display: 'flex', flexDirection: 'column' }}>
  130. {arr.map((item: any, index: number) => (
  131. <div>
  132. 寄存器{index + 1}: {item}
  133. </div>
  134. ))}
  135. </div>
  136. ),
  137. });
  138. }
  139. }}
  140. >
  141. <Ellipsis title={currentData[record?.id] || ''} />
  142. </a>
  143. ),
  144. },
  145. {
  146. title: '采集状态',
  147. dataIndex: 'collectState',
  148. valueType: 'select',
  149. valueEnum: {
  150. running: { text: '采集中', status: 'running' },
  151. error: { text: '失败', status: 'error' },
  152. stopped: { text: '已停止', status: 'stopped' },
  153. },
  154. render: (_, record: any) => (
  155. <>
  156. <Badge
  157. status={collectMap.get(record.collectState?.value)}
  158. text={record.collectState?.text}
  159. />
  160. {record.collectState?.value === 'error' && (
  161. <SearchOutlined
  162. style={{ color: '#1d39c4', marginLeft: 3 }}
  163. onClick={() => {
  164. Modal.error({
  165. title: '失败原因',
  166. content: <div>{record.errorReason}</div>,
  167. });
  168. }}
  169. />
  170. )}
  171. </>
  172. ),
  173. },
  174. {
  175. title: '状态',
  176. dataIndex: 'state',
  177. renderText: (state) => (
  178. <Badge text={state?.text} status={state?.value === 'disabled' ? 'error' : 'success'} />
  179. ),
  180. valueType: 'select',
  181. valueEnum: {
  182. disabled: {
  183. text: intl.formatMessage({
  184. id: 'pages.data.option.disabled',
  185. defaultMessage: '禁用',
  186. }),
  187. status: 'disabled',
  188. },
  189. enabled: {
  190. text: '正常',
  191. status: 'enabled',
  192. },
  193. },
  194. filterMultiple: false,
  195. },
  196. {
  197. title: '操作',
  198. valueType: 'option',
  199. align: 'center',
  200. width: 120,
  201. fixed: 'right',
  202. render: (text, record) => [
  203. <PermissionButton
  204. isPermission={permission.update}
  205. key="edit"
  206. onClick={() => {
  207. setPointDetail(record);
  208. setVisiblePoint(true);
  209. }}
  210. type={'link'}
  211. style={{ padding: 0 }}
  212. tooltip={{
  213. title: intl.formatMessage({
  214. id: 'pages.data.option.edit',
  215. defaultMessage: '编辑',
  216. }),
  217. }}
  218. >
  219. <EditOutlined />
  220. </PermissionButton>,
  221. <PermissionButton
  222. type="link"
  223. key={'action'}
  224. style={{ padding: 0 }}
  225. popConfirm={{
  226. title: intl.formatMessage({
  227. id: `pages.data.option.${
  228. record.state.value !== 'disabled' ? 'disabled' : 'enabled'
  229. }.tips`,
  230. defaultMessage: '确认禁用?',
  231. }),
  232. onConfirm: async () => {
  233. if (record.state.value === 'disabled') {
  234. await service.enablePoint([record.id]);
  235. } else {
  236. await service.disablePoint([record.id]);
  237. }
  238. onlyMessage(
  239. intl.formatMessage({
  240. id: 'pages.data.option.success',
  241. defaultMessage: '操作成功!',
  242. }),
  243. );
  244. actionRef.current?.reload();
  245. },
  246. }}
  247. isPermission={permission.action}
  248. tooltip={{
  249. title: intl.formatMessage({
  250. id: `pages.data.option.${record.state.value !== 'disabled' ? 'disabled' : 'enabled'}`,
  251. defaultMessage: record.state.value !== 'disabled' ? '禁用' : '启用',
  252. }),
  253. }}
  254. >
  255. {record.state.value !== 'disabled' ? <StopOutlined /> : <PlayCircleOutlined />}
  256. </PermissionButton>,
  257. <PermissionButton
  258. isPermission={permission.delete}
  259. style={{ padding: 0 }}
  260. disabled={record.state.value === 'enabled'}
  261. popConfirm={{
  262. title: '确认删除',
  263. disabled: record.state.value === 'enabled',
  264. onConfirm: async () => {
  265. const resp: any = await service.deletePoint(record.id);
  266. if (resp.status === 200) {
  267. onlyMessage(
  268. intl.formatMessage({
  269. id: 'pages.data.option.success',
  270. defaultMessage: '操作成功!',
  271. }),
  272. );
  273. actionRef.current?.reload();
  274. }
  275. },
  276. }}
  277. key="delete"
  278. type="link"
  279. >
  280. <DeleteOutlined />
  281. </PermissionButton>,
  282. ],
  283. },
  284. ];
  285. const getMaster = () => {
  286. service
  287. .queryMaster({
  288. paging: false,
  289. sorts: [
  290. {
  291. name: 'createTime',
  292. order: 'desc',
  293. },
  294. ],
  295. })
  296. .then((res: any) => {
  297. if (res.status === 200) {
  298. setMasterList(res.result);
  299. setFilterList(res.result);
  300. setActiveKey(res.result?.[0]?.id);
  301. masterId.current = res.result?.[0]?.id;
  302. }
  303. });
  304. };
  305. //启用禁用
  306. const actions = (id: string, data: any) => {
  307. service.editMaster(id, data).then((res) => {
  308. if (res.status === 200) {
  309. onlyMessage('操作成功');
  310. getMaster();
  311. }
  312. });
  313. };
  314. const deteleMaster = (id: string) => {
  315. service.deleteMaster(id).then((res) => {
  316. if (res.status === 200) {
  317. onlyMessage('删除成功');
  318. getMaster();
  319. }
  320. });
  321. };
  322. useEffect(() => {
  323. masterId.current = activeKey;
  324. actionRef.current?.reload();
  325. }, [activeKey]);
  326. useEffect(() => {
  327. getMaster();
  328. }, []);
  329. useEffect(() => {
  330. const id = `collector-data-modbus`;
  331. const topic = `/collector/MODBUS_TCP/${activeKey}/data`;
  332. wsRef.current = subscribeTopic?.(id, topic, {
  333. pointId: pointList.map((item: any) => item.id),
  334. })
  335. ?.pipe(map((res: any) => res.payload))
  336. .subscribe((payload: any) => {
  337. const { pointId, hex } = payload;
  338. current[pointId] = hex;
  339. setCurrentData({ ...current });
  340. });
  341. return () => wsRef.current && wsRef.current?.unsubscribe();
  342. }, [pointList]);
  343. return (
  344. <PageContainer>
  345. <Card className="modbus" style={{ minHeight }}>
  346. <div className="item">
  347. <div className="item-left">
  348. <div style={{ width: 220 }}>
  349. <Input.Search
  350. placeholder="请输入名称"
  351. allowClear
  352. onSearch={(value) => {
  353. const items = masterList.filter((item: any) => item.name.match(value));
  354. if (value) {
  355. setFilterList(items);
  356. setActiveKey(items?.[0].id);
  357. } else {
  358. setFilterList(masterList);
  359. setActiveKey(masterList?.[0].id);
  360. }
  361. }}
  362. />
  363. <PermissionButton
  364. onClick={() => {
  365. setVisible(true);
  366. setCurrent({});
  367. }}
  368. isPermission={permission.add}
  369. key="add"
  370. icon={<PlusOutlined />}
  371. type="primary"
  372. style={{ width: '100%', marginTop: 16, marginBottom: 16 }}
  373. >
  374. 新增
  375. </PermissionButton>
  376. <div className="item-left-list">
  377. {filterList.map((item: any) => (
  378. <ChannelCard
  379. active={activeKey === item.id}
  380. data={item}
  381. onClick={() => {
  382. setActiveKey(item.id);
  383. }}
  384. actions={
  385. <>
  386. <PermissionButton
  387. isPermission={permission.update}
  388. key="edit"
  389. onClick={() => {
  390. setVisible(true);
  391. setCurrent(item);
  392. }}
  393. type={'link'}
  394. style={{ padding: 0 }}
  395. >
  396. <EditOutlined />
  397. 编辑
  398. </PermissionButton>
  399. <Divider type="vertical" />
  400. <PermissionButton
  401. isPermission={permission.update}
  402. key="enbale"
  403. type={'link'}
  404. style={{ padding: 0 }}
  405. popConfirm={{
  406. title: intl.formatMessage({
  407. id: `pages.data.option.${
  408. item.state.value !== 'disabled' ? 'disabled' : 'enabled'
  409. }.tips`,
  410. defaultMessage: '确认禁用?',
  411. }),
  412. onConfirm: async () => {
  413. if (item.state.value === 'disabled') {
  414. actions(item.id, { state: 'enabled' });
  415. } else {
  416. actions(item.id, { state: 'disabled' });
  417. }
  418. },
  419. }}
  420. >
  421. {item.state.value === 'enabled' ? (
  422. <StopOutlined />
  423. ) : (
  424. <PlayCircleOutlined />
  425. )}
  426. {item.state.value === 'enabled' ? '禁用' : '启用'}
  427. </PermissionButton>
  428. <Divider type="vertical" />
  429. <PermissionButton
  430. isPermission={permission.delete}
  431. style={{ padding: 0 }}
  432. disabled={item.state.value === 'enabled'}
  433. tooltip={{
  434. title: item.state.value === 'enabled' ? '请先禁用该通道,再删除。' : '',
  435. }}
  436. popConfirm={{
  437. title: '确认删除',
  438. disabled: item.state.value === 'enabled',
  439. onConfirm: async () => {
  440. deteleMaster(item.id);
  441. },
  442. }}
  443. key="delete"
  444. type="link"
  445. >
  446. <DeleteOutlined />
  447. </PermissionButton>
  448. </>
  449. }
  450. />
  451. ))}
  452. </div>
  453. </div>
  454. </div>
  455. <div className="item-right">
  456. <div style={{ width: '100%' }}>
  457. <SearchComponent<any>
  458. field={columns}
  459. target="modbus"
  460. onSearch={(value) => {
  461. actionRef.current?.reset?.();
  462. setParam(value);
  463. }}
  464. />
  465. <ProTable
  466. actionRef={actionRef}
  467. params={param}
  468. columns={columns}
  469. rowKey="id"
  470. scroll={{ x: 1000 }}
  471. search={false}
  472. headerTitle={
  473. <>
  474. <PermissionButton
  475. onClick={() => {
  476. setPointDetail({});
  477. setVisiblePoint(true);
  478. }}
  479. isPermission={permission.add}
  480. key="add"
  481. icon={<PlusOutlined />}
  482. type="primary"
  483. style={{ marginRight: 10 }}
  484. >
  485. {intl.formatMessage({
  486. id: 'pages.data.option.add',
  487. defaultMessage: '新增',
  488. })}
  489. </PermissionButton>
  490. <Dropdown key={'more'} overlay={menu} placement="bottom">
  491. <Button>批量操作</Button>
  492. </Dropdown>
  493. </>
  494. }
  495. request={async (params) => {
  496. if (masterId.current) {
  497. const res = await service.queryPoint(masterId.current, {
  498. ...params,
  499. sorts: [{ name: 'createTime', order: 'desc' }],
  500. });
  501. setPointList(res.result.data);
  502. return {
  503. code: res.message,
  504. result: {
  505. data: res.result.data,
  506. pageIndex: res.result.pageIndex,
  507. pageSize: res.result.pageSize,
  508. total: res.result.total,
  509. },
  510. status: res.status,
  511. };
  512. } else {
  513. return {
  514. code: 200,
  515. result: {
  516. data: [],
  517. pageIndex: 0,
  518. pageSize: 0,
  519. total: 0,
  520. },
  521. status: 200,
  522. };
  523. }
  524. }}
  525. />
  526. </div>
  527. </div>
  528. </div>
  529. </Card>
  530. {visible && (
  531. <SaveChannel
  532. data={current}
  533. close={() => {
  534. setVisible(false);
  535. getMaster();
  536. // actionRef.current?.reload();
  537. }}
  538. />
  539. )}
  540. {visiblePoint && (
  541. <SavePoint
  542. data={pointDetail}
  543. masterId={activeKey}
  544. close={() => {
  545. setVisiblePoint(false);
  546. actionRef.current?.reload();
  547. }}
  548. />
  549. )}
  550. <Import
  551. masterId={activeKey}
  552. close={() => {
  553. setImportVisible(false);
  554. actionRef.current?.reload();
  555. }}
  556. visible={importVisible}
  557. />
  558. <Export
  559. data={masterList}
  560. close={() => {
  561. setExportVisible(false);
  562. actionRef.current?.reload();
  563. }}
  564. visible={exportVisible}
  565. />
  566. </PageContainer>
  567. );
  568. };
  569. export default NewModbus;