index.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. import { PageContainer } from '@ant-design/pro-layout';
  2. import type { ProColumns, ActionType } from '@jetlinks/pro-table';
  3. import type { DeviceInstance } from '@/pages/device/Instance/typings';
  4. import moment from 'moment';
  5. import { Badge, Button, Dropdown, Menu, message, Popconfirm, Tooltip } from 'antd';
  6. import { useRef, useState } from 'react';
  7. import { Link } from 'umi';
  8. import {
  9. CheckCircleOutlined,
  10. DeleteOutlined,
  11. ExportOutlined,
  12. ImportOutlined,
  13. PlusOutlined,
  14. SearchOutlined,
  15. StopOutlined,
  16. SyncOutlined,
  17. } from '@ant-design/icons';
  18. import { model } from '@formily/reactive';
  19. import Service from '@/pages/device/Instance/service';
  20. import type { MetadataItem } from '@/pages/device/Product/typings';
  21. import { useIntl } from '@@/plugin-locale/localeExports';
  22. import Save from './Save';
  23. import Export from './Export';
  24. import Import from './Import';
  25. import Process from './Process';
  26. import ProTable from '@jetlinks/pro-table';
  27. import encodeQuery from '@/utils/encodeQuery';
  28. import SearchComponent from '@/components/SearchComponent';
  29. import SystemConst from '@/utils/const';
  30. import Token from '@/utils/token';
  31. const statusMap = new Map();
  32. statusMap.set('在线', 'success');
  33. statusMap.set('离线', 'error');
  34. statusMap.set('未激活', 'processing');
  35. statusMap.set('online', 'success');
  36. statusMap.set('offline', 'error');
  37. statusMap.set('notActive', 'processing');
  38. export const InstanceModel = model<{
  39. current: DeviceInstance | undefined;
  40. detail: Partial<DeviceInstance>;
  41. config: any;
  42. metadataItem: MetadataItem;
  43. params: Set<string>; // 处理无限循环Card
  44. active?: string; // 当前编辑的Card
  45. }>({
  46. current: undefined,
  47. detail: {},
  48. config: {},
  49. metadataItem: {},
  50. params: new Set<string>(['test']),
  51. });
  52. export const service = new Service('device/instance');
  53. const Instance = () => {
  54. const actionRef = useRef<ActionType>();
  55. const [visible, setVisible] = useState<boolean>(false);
  56. const [exportVisible, setExportVisible] = useState<boolean>(false);
  57. const [importVisible, setImportVisible] = useState<boolean>(false);
  58. const [operationVisible, setOperationVisible] = useState<boolean>(false);
  59. const [type, setType] = useState<'active' | 'sync'>('active');
  60. const [api, setApi] = useState<string>('');
  61. const [current, setCurrent] = useState<DeviceInstance>();
  62. const [searchParams, setSearchParams] = useState<any>({});
  63. const [bindKeys, setBindKeys] = useState<any[]>([]);
  64. const intl = useIntl();
  65. const columns: ProColumns<DeviceInstance>[] = [
  66. {
  67. dataIndex: 'index',
  68. valueType: 'indexBorder',
  69. width: 48,
  70. },
  71. {
  72. title: 'ID',
  73. dataIndex: 'id',
  74. },
  75. {
  76. title: intl.formatMessage({
  77. id: 'pages.table.deviceName',
  78. defaultMessage: '设备名称',
  79. }),
  80. dataIndex: 'name',
  81. ellipsis: true,
  82. },
  83. {
  84. title: intl.formatMessage({
  85. id: 'pages.table.productName',
  86. defaultMessage: '产品名称',
  87. }),
  88. dataIndex: 'productName',
  89. ellipsis: true,
  90. },
  91. {
  92. title: intl.formatMessage({
  93. id: 'pages.device.instance.registrationTime',
  94. defaultMessage: '注册时间',
  95. }),
  96. dataIndex: 'registryTime',
  97. width: '200px',
  98. render: (text: any) => (text ? moment(text).format('YYYY-MM-DD HH:mm:ss') : '/'),
  99. sorter: true,
  100. },
  101. {
  102. title: intl.formatMessage({
  103. id: 'pages.searchTable.titleStatus',
  104. defaultMessage: '状态',
  105. }),
  106. dataIndex: 'state',
  107. width: '90px',
  108. renderText: (record) =>
  109. record ? <Badge status={statusMap.get(record.value)} text={record.text} /> : '',
  110. filters: [
  111. {
  112. text: intl.formatMessage({
  113. id: 'pages.device.instance.status.notActive',
  114. defaultMessage: '未启用',
  115. }),
  116. value: 'notActive',
  117. },
  118. {
  119. text: intl.formatMessage({
  120. id: 'pages.device.instance.status.offLine',
  121. defaultMessage: '离线',
  122. }),
  123. value: 'offline',
  124. },
  125. {
  126. text: intl.formatMessage({
  127. id: 'pages.device.instance.status.onLine',
  128. defaultMessage: '在线',
  129. }),
  130. value: 'online',
  131. },
  132. ],
  133. filterMultiple: false,
  134. },
  135. {
  136. title: intl.formatMessage({
  137. id: 'pages.table.description',
  138. defaultMessage: '说明',
  139. }),
  140. dataIndex: 'describe',
  141. width: '15%',
  142. ellipsis: true,
  143. },
  144. {
  145. title: intl.formatMessage({
  146. id: 'pages.data.option',
  147. defaultMessage: '操作',
  148. }),
  149. valueType: 'option',
  150. align: 'center',
  151. width: 200,
  152. render: (text, record) => [
  153. <Link
  154. onClick={() => {
  155. InstanceModel.current = record;
  156. }}
  157. to={`/device/instance/detail/${record.id}`}
  158. key="link"
  159. >
  160. <Tooltip
  161. title={intl.formatMessage({
  162. id: 'pages.data.option.detail',
  163. defaultMessage: '查看',
  164. })}
  165. key={'detail'}
  166. >
  167. <SearchOutlined />
  168. </Tooltip>
  169. </Link>,
  170. // <a key="editable" onClick={() => {
  171. // setVisible(true)
  172. // setCurrent(record)
  173. // }}>
  174. // <Tooltip
  175. // title={intl.formatMessage({
  176. // id: 'pages.data.option.edit',
  177. // defaultMessage: '编辑',
  178. // })}
  179. // >
  180. // <EditOutlined />
  181. // </Tooltip>
  182. // </a>,
  183. <a href={record.id} target="_blank" rel="noopener noreferrer" key="view">
  184. <Popconfirm
  185. title={intl.formatMessage({
  186. id: 'pages.data.option.disabled.tips',
  187. defaultMessage: '确认禁用?',
  188. })}
  189. onConfirm={async () => {
  190. if (record.state.value !== 'notActive') {
  191. await service.undeployDevice(record.id);
  192. } else {
  193. await service.deployDevice(record.id);
  194. }
  195. message.success(
  196. intl.formatMessage({
  197. id: 'pages.data.option.success',
  198. defaultMessage: '操作成功!',
  199. }),
  200. );
  201. actionRef.current?.reload();
  202. }}
  203. >
  204. <Tooltip
  205. title={intl.formatMessage({
  206. id: `pages.data.option.${
  207. record.state.value !== 'notActive' ? 'disabled' : 'enabled'
  208. }`,
  209. defaultMessage: record.state.value !== 'notActive' ? '禁用' : '启用',
  210. })}
  211. >
  212. {record.state.value !== 'notActive' ? <StopOutlined /> : <CheckCircleOutlined />}
  213. </Tooltip>
  214. </Popconfirm>
  215. </a>,
  216. <a key={'delete'}>
  217. <Popconfirm
  218. title="确认删除"
  219. onConfirm={async () => {
  220. await service.remove(record.id);
  221. message.success(
  222. intl.formatMessage({
  223. id: 'pages.data.option.success',
  224. defaultMessage: '操作成功!',
  225. }),
  226. );
  227. actionRef.current?.reload();
  228. }}
  229. >
  230. <Tooltip title={'删除'}>
  231. <DeleteOutlined />
  232. </Tooltip>
  233. </Popconfirm>
  234. </a>,
  235. ],
  236. },
  237. ];
  238. const menu = (
  239. <Menu>
  240. <Menu.Item key="1">
  241. <Button
  242. icon={<ExportOutlined />}
  243. type="default"
  244. onClick={() => {
  245. setExportVisible(true);
  246. }}
  247. >
  248. 批量导出设备
  249. </Button>
  250. </Menu.Item>
  251. <Menu.Item key="2">
  252. <Button
  253. icon={<ImportOutlined />}
  254. onClick={() => {
  255. setImportVisible(true);
  256. }}
  257. >
  258. 批量导入设备
  259. </Button>
  260. </Menu.Item>
  261. <Menu.Item key="4">
  262. <Popconfirm
  263. title={'确认激活全部设备?'}
  264. onConfirm={() => {
  265. setType('active');
  266. const activeAPI = `/${
  267. SystemConst.API_BASE
  268. }/device-instance/deploy?:X_Access_Token=${Token.get()}`;
  269. setApi(activeAPI);
  270. setOperationVisible(true);
  271. }}
  272. >
  273. <Button icon={<CheckCircleOutlined />} type="primary" ghost>
  274. 激活全部设备
  275. </Button>
  276. </Popconfirm>
  277. </Menu.Item>
  278. <Menu.Item key="5">
  279. <Button
  280. icon={<SyncOutlined />}
  281. type="primary"
  282. onClick={() => {
  283. setType('sync');
  284. const syncAPI = `/${
  285. SystemConst.API_BASE
  286. }/device-instance/state/_sync?:X_Access_Token=${Token.get()}`;
  287. setApi(syncAPI);
  288. setOperationVisible(true);
  289. }}
  290. >
  291. 同步设备状态
  292. </Button>
  293. </Menu.Item>
  294. {bindKeys.length > 0 && (
  295. <Menu.Item key="3">
  296. <Popconfirm
  297. title="确认删除选中设备?"
  298. onConfirm={() => {
  299. service.batchDeleteDevice(bindKeys).then((resp) => {
  300. if (resp.status === 200) {
  301. message.success('操作成功');
  302. actionRef.current?.reset?.();
  303. }
  304. });
  305. }}
  306. okText="确认"
  307. cancelText="取消"
  308. >
  309. <Button icon={<DeleteOutlined />} type="primary" danger>
  310. 删除选中设备
  311. </Button>
  312. </Popconfirm>
  313. </Menu.Item>
  314. )}
  315. {bindKeys.length > 0 && (
  316. <Menu.Item key="6">
  317. <Popconfirm
  318. title="确认禁用选中设备?"
  319. onConfirm={() => {
  320. service.batchUndeployDevice(bindKeys).then((resp) => {
  321. if (resp.status === 200) {
  322. message.success('操作成功');
  323. actionRef.current?.reset?.();
  324. }
  325. });
  326. }}
  327. okText="确认"
  328. cancelText="取消"
  329. >
  330. <Button icon={<StopOutlined />} type="primary" danger>
  331. 禁用选中设备
  332. </Button>
  333. </Popconfirm>
  334. </Menu.Item>
  335. )}
  336. </Menu>
  337. );
  338. return (
  339. <PageContainer>
  340. <SearchComponent<DeviceInstance>
  341. field={columns}
  342. target="device-instance"
  343. onSearch={(data) => {
  344. // 重置分页数据
  345. actionRef.current?.reset?.();
  346. setSearchParams(data);
  347. }}
  348. onReset={() => {
  349. // 重置分页及搜索参数
  350. actionRef.current?.reset?.();
  351. setSearchParams({});
  352. }}
  353. />
  354. <ProTable<DeviceInstance>
  355. columns={columns}
  356. actionRef={actionRef}
  357. params={searchParams}
  358. options={{ fullScreen: true }}
  359. request={(params) => service.query(encodeQuery({ ...params, sorts: { id: 'ascend' } }))}
  360. rowKey="id"
  361. search={false}
  362. pagination={{ pageSize: 10 }}
  363. rowSelection={{
  364. selectedRowKeys: bindKeys,
  365. onChange: (selectedRowKeys, selectedRows) => {
  366. setBindKeys(selectedRows.map((item) => item.id));
  367. },
  368. }}
  369. toolBarRender={() => [
  370. <Button
  371. onClick={() => {
  372. setVisible(true);
  373. setCurrent(undefined);
  374. }}
  375. key="button"
  376. icon={<PlusOutlined />}
  377. type="primary"
  378. >
  379. {intl.formatMessage({
  380. id: 'pages.data.option.add',
  381. defaultMessage: '新增',
  382. })}
  383. </Button>,
  384. <Dropdown key={'more'} overlay={menu} placement="bottom">
  385. <Button>批量操作</Button>
  386. </Dropdown>,
  387. ]}
  388. />
  389. <Save
  390. data={current}
  391. model={!current ? 'add' : 'edit'}
  392. close={() => {
  393. setVisible(false);
  394. }}
  395. reload={() => {
  396. actionRef.current?.reload();
  397. }}
  398. visible={visible}
  399. />
  400. <Export
  401. data={searchParams}
  402. close={() => {
  403. setExportVisible(false);
  404. actionRef.current?.reload();
  405. }}
  406. visible={exportVisible}
  407. />
  408. <Import
  409. data={current}
  410. close={() => {
  411. setImportVisible(false);
  412. actionRef.current?.reload();
  413. }}
  414. visible={importVisible}
  415. />
  416. {operationVisible && (
  417. <Process
  418. api={api}
  419. action={type}
  420. closeVisible={() => {
  421. setOperationVisible(false);
  422. actionRef.current?.reload();
  423. }}
  424. />
  425. )}
  426. </PageContainer>
  427. );
  428. };
  429. export default Instance;