pages_device_instances.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. import React, { useState } from 'react';
  2. import {
  3. Button, Table, Space,
  4. Form, Input, Select, message, Modal,
  5. Card, Row, Col, Typography, Badge
  6. } from 'antd';
  7. import {
  8. useQuery,
  9. useMutation,
  10. useQueryClient
  11. } from '@tanstack/react-query';
  12. import axios from 'axios';
  13. import dayjs from 'dayjs';
  14. import 'dayjs/locale/zh-cn';
  15. import type {
  16. ZichanInfo, DeviceInstance, DeviceType,
  17. } from '../share/monitorTypes.ts';
  18. import {
  19. DeviceProtocolType,
  20. } from '../share/monitorTypes.ts';
  21. import {
  22. EnableStatus, DeleteStatus,
  23. } from '../share/types.ts';
  24. import { DeviceInstanceAPI , DeviceTypeAPI, ZichanAPI} from './api/index.ts';
  25. const { Title } = Typography;
  26. // 设备管理页面
  27. export const DeviceInstancesPage = () => {
  28. const [form] = Form.useForm();
  29. const [formMode, setFormMode] = useState<'create' | 'edit'>('create');
  30. const [modalVisible, setModalVisible] = useState(false);
  31. const [selectedId, setSelectedId] = useState<number | null>(null);
  32. const [pagination, setPagination] = useState({
  33. current: 1,
  34. pageSize: 10,
  35. total: 0
  36. });
  37. // 获取设备列表
  38. const {
  39. data,
  40. isLoading,
  41. refetch
  42. } = useQuery({
  43. queryKey: ['deviceInstances', pagination.current, pagination.pageSize],
  44. queryFn: async () => {
  45. try {
  46. const response = await DeviceInstanceAPI.getDeviceInstances({
  47. page: pagination.current,
  48. limit: pagination.pageSize
  49. });
  50. setPagination(prev => ({
  51. ...prev,
  52. total: response.pagination.total
  53. }));
  54. return response.data;
  55. } catch (error) {
  56. message.error('获取设备列表失败');
  57. return [];
  58. }
  59. }
  60. });
  61. // 获取设备类型
  62. const { data: deviceTypes = [] } = useQuery({
  63. queryKey: ['deviceTypesAll'],
  64. queryFn: async () => {
  65. try {
  66. const response = await DeviceTypeAPI.getDeviceTypes({ pageSize: 100 })
  67. return response.data;
  68. } catch (error) {
  69. message.error('获取设备类型失败');
  70. return [];
  71. }
  72. }
  73. });
  74. // 获取可用资产
  75. const { data: zichanList = [] } = useQuery({
  76. queryKey: ['zichanListAll'],
  77. queryFn: async () => {
  78. try {
  79. const response = await ZichanAPI.getZichanList({ limit: 500 })
  80. // 筛选出未分配给设备的资产
  81. const deviceInstances = data || [];
  82. const usedAssetIds = deviceInstances.map((d: DeviceInstance) => d.id);
  83. return response.data.filter(
  84. (z: ZichanInfo) => !usedAssetIds.includes(z.id)
  85. );
  86. } catch (error) {
  87. message.error('获取可用资产失败');
  88. return [];
  89. }
  90. }
  91. });
  92. const queryClient = useQueryClient();
  93. // 提交表单
  94. const { mutate: submitForm, isPending: isSubmitting } = useMutation({
  95. mutationFn: async (values: Partial<DeviceInstance>) => {
  96. const deviceData = {
  97. ...values,
  98. };
  99. if (formMode === 'create') {
  100. return DeviceInstanceAPI.createDeviceInstance(deviceData);
  101. } else {
  102. return DeviceInstanceAPI.updateDeviceInstance(selectedId!, deviceData);
  103. }
  104. },
  105. onSuccess: () => {
  106. message.success(formMode === 'create' ? '创建成功' : '更新成功');
  107. setModalVisible(false);
  108. refetch();
  109. form.resetFields();
  110. },
  111. onError: () => {
  112. message.error(formMode === 'create' ? '创建失败' : '更新失败');
  113. }
  114. });
  115. // 删除设备
  116. const { mutate: deleteDevice, isPending: isDeleting } = useMutation({
  117. mutationFn: async (id: number) => {
  118. return DeviceInstanceAPI.deleteDeviceInstance(id);
  119. },
  120. onSuccess: () => {
  121. message.success('删除成功');
  122. refetch();
  123. },
  124. onError: () => {
  125. message.error('删除失败');
  126. }
  127. });
  128. // 处理表单提交
  129. const handleSubmit = (values: Partial<DeviceInstance>) => {
  130. submitForm({
  131. ...values,
  132. is_enabled: values.is_enabled ?? EnableStatus.ENABLED,
  133. is_deleted: DeleteStatus.NOT_DELETED
  134. });
  135. };
  136. // 处理编辑
  137. const handleEdit = (record: DeviceInstance) => {
  138. setSelectedId(record.id);
  139. setFormMode('edit');
  140. form.setFieldsValue({
  141. ...record
  142. });
  143. setModalVisible(true);
  144. };
  145. // 处理删除
  146. const handleDelete = (id: number) => {
  147. Modal.confirm({
  148. title: '确认删除',
  149. content: '确定要删除这个设备吗?',
  150. okText: '确认',
  151. cancelText: '取消',
  152. onOk: () => deleteDevice(id)
  153. });
  154. };
  155. // 处理添加
  156. const handleAdd = () => {
  157. setFormMode('create');
  158. setSelectedId(null);
  159. form.resetFields();
  160. setModalVisible(true);
  161. };
  162. // 处理页码变化
  163. const handlePageChange = (page: number, pageSize?: number) => {
  164. setPagination({
  165. ...pagination,
  166. current: page,
  167. pageSize: pageSize || pagination.pageSize
  168. });
  169. };
  170. const columns = [
  171. {
  172. title: 'ID',
  173. dataIndex: 'id',
  174. key: 'id',
  175. width: 80
  176. },
  177. {
  178. title: '设备类型',
  179. dataIndex: 'type_id',
  180. key: 'type_id',
  181. render: (typeId: number) => {
  182. const deviceType = deviceTypes.find((t: DeviceType) => t.id === typeId);
  183. return deviceType ? deviceType.name : `未知类型(${typeId})`;
  184. }
  185. },
  186. {
  187. title: '通信协议',
  188. dataIndex: 'protocol',
  189. key: 'protocol'
  190. },
  191. {
  192. title: '通信地址',
  193. dataIndex: 'address',
  194. key: 'address'
  195. },
  196. {
  197. title: '采集间隔(秒)',
  198. dataIndex: 'collect_interval',
  199. key: 'collect_interval'
  200. },
  201. {
  202. title: '最后采集时间',
  203. dataIndex: 'last_collect_time',
  204. key: 'last_collect_time',
  205. render: (date: string) => date ? dayjs(date).format('YYYY-MM-DD HH:mm:ss') : '-'
  206. },
  207. {
  208. title: '状态地址',
  209. dataIndex: 'status_address',
  210. key: 'status_address'
  211. },
  212. {
  213. title: '连接配置',
  214. dataIndex: 'connection_config',
  215. key: 'connection_config'
  216. },
  217. {
  218. title: '状态',
  219. dataIndex: 'is_enabled',
  220. key: 'is_enabled',
  221. render: (status: number) => (
  222. <Badge
  223. status={status === EnableStatus.ENABLED ? 'success' : 'error'}
  224. text={status === EnableStatus.ENABLED ? '启用' : '禁用'}
  225. />
  226. )
  227. },
  228. {
  229. title: '操作',
  230. key: 'action',
  231. render: (_: any, record: DeviceInstance) => (
  232. <Space size="middle">
  233. <Button type="link" onClick={() => handleEdit(record)}>编辑</Button>
  234. <Button type="link" danger onClick={() => handleDelete(record.id)}>删除</Button>
  235. </Space>
  236. )
  237. }
  238. ];
  239. return (
  240. <div>
  241. <Title level={2}>设备管理</Title>
  242. <Card>
  243. <Row justify="end" style={{ marginBottom: 16 }}>
  244. <Button type="primary" onClick={handleAdd}>
  245. 添加设备
  246. </Button>
  247. </Row>
  248. <Table
  249. columns={columns}
  250. dataSource={data || []}
  251. loading={isLoading}
  252. rowKey="id"
  253. pagination={{
  254. current: pagination.current,
  255. pageSize: pagination.pageSize,
  256. total: pagination.total,
  257. onChange: handlePageChange,
  258. showSizeChanger: true
  259. }}
  260. />
  261. <Modal
  262. title={formMode === 'create' ? '添加设备' : '编辑设备'}
  263. open={modalVisible}
  264. onCancel={() => setModalVisible(false)}
  265. footer={null}
  266. width={700}
  267. >
  268. <Form
  269. form={form}
  270. layout="vertical"
  271. onFinish={handleSubmit}
  272. >
  273. <Row gutter={16}>
  274. {formMode === 'create' && (
  275. <Col span={24}>
  276. <Form.Item
  277. name="id"
  278. label="关联资产"
  279. rules={[{ required: true, message: '请选择关联资产' }]}
  280. >
  281. <Select placeholder="请选择关联资产">
  282. {zichanList.map((zichan: ZichanInfo) => (
  283. <Select.Option key={zichan.id} value={zichan.id}>
  284. {zichan.asset_name || `资产ID: ${zichan.id}`}
  285. </Select.Option>
  286. ))}
  287. </Select>
  288. </Form.Item>
  289. </Col>
  290. )}
  291. <Col span={12}>
  292. <Form.Item
  293. name="type_id"
  294. label="设备类型"
  295. rules={[{ required: true, message: '请选择设备类型' }]}
  296. >
  297. <Select placeholder="请选择设备类型">
  298. {deviceTypes.map((type: DeviceType) => (
  299. <Select.Option key={type.id} value={type.id}>
  300. {type.name}
  301. </Select.Option>
  302. ))}
  303. </Select>
  304. </Form.Item>
  305. </Col>
  306. <Col span={12}>
  307. <Form.Item
  308. name="protocol"
  309. label="通信协议"
  310. rules={[{ required: true, message: '请选择通信协议' }]}
  311. >
  312. <Select placeholder="请选择通信协议">
  313. {Object.entries(DeviceProtocolType).map(([key, value]) => (
  314. <Select.Option key={key} value={value}>
  315. {value} - {key === value ? '' : key}
  316. </Select.Option>
  317. ))}
  318. </Select>
  319. </Form.Item>
  320. </Col>
  321. </Row>
  322. <Row gutter={16}>
  323. <Col span={12}>
  324. <Form.Item
  325. name="address"
  326. label="通信地址"
  327. rules={[{ required: true, message: '请输入通信地址' }]}
  328. >
  329. <Input placeholder="请输入通信地址" />
  330. </Form.Item>
  331. </Col>
  332. <Col span={12}>
  333. <Form.Item
  334. name="collect_interval"
  335. label="采集间隔(秒)"
  336. >
  337. <Input type="number" placeholder="请输入采集间隔" />
  338. </Form.Item>
  339. </Col>
  340. </Row>
  341. <Row gutter={16}>
  342. <Col span={12}>
  343. <Form.Item
  344. name="status_address"
  345. label="状态地址"
  346. >
  347. <Input placeholder="请输入状态地址" />
  348. </Form.Item>
  349. </Col>
  350. <Col span={12}>
  351. <Form.Item
  352. name="connection_config"
  353. label="连接配置"
  354. >
  355. <Input.TextArea rows={1} placeholder="请输入连接配置" />
  356. </Form.Item>
  357. </Col>
  358. </Row>
  359. <Form.Item
  360. name="remark"
  361. label="备注"
  362. >
  363. <Input.TextArea rows={4} placeholder="请输入备注信息" />
  364. </Form.Item>
  365. <Form.Item
  366. name="is_enabled"
  367. label="状态"
  368. initialValue={EnableStatus.ENABLED}
  369. >
  370. <Select>
  371. <Select.Option value={EnableStatus.ENABLED}>启用</Select.Option>
  372. <Select.Option value={EnableStatus.DISABLED}>禁用</Select.Option>
  373. </Select>
  374. </Form.Item>
  375. <Form.Item>
  376. <Space>
  377. <Button type="primary" htmlType="submit" loading={isSubmitting}>
  378. {formMode === 'create' ? '创建' : '保存'}
  379. </Button>
  380. <Button onClick={() => setModalVisible(false)}>取消</Button>
  381. </Space>
  382. </Form.Item>
  383. </Form>
  384. </Modal>
  385. </Card>
  386. </div>
  387. );
  388. };