pages_inspections.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. import React, { useState, useEffect } from 'react';
  2. import {
  3. Button, Table, Form, Input, Select, message, Card, Badge,
  4. Space, Modal, DatePicker, InputNumber, Switch, Tag, Checkbox, Divider, Progress
  5. } from 'antd';
  6. import { DeviceTypeAPI } from './api/device_type.ts';
  7. import { UserAPI } from './api/users.ts';
  8. const { RangePicker } = DatePicker;
  9. const { TextArea } = Input;
  10. import dayjs from 'dayjs';
  11. import 'dayjs/locale/zh-cn';
  12. import { InspectionsAPI } from './api/inspections.ts';
  13. import type {
  14. InspectionTask,
  15. InspectionTemplate,
  16. ListResponse
  17. } from './api/inspections.ts';
  18. import { getEnumOptions } from './utils.ts';
  19. const PriorityOptions = [
  20. { label: '低', value: 'low' },
  21. { label: '中', value: 'medium' },
  22. { label: '高', value: 'high' },
  23. { label: '紧急', value: 'urgent' }
  24. ];
  25. const StatusOptions = [
  26. { label: '待处理', value: 'pending' },
  27. { label: '进行中', value: 'in_progress' },
  28. { label: '已完成', value: 'completed' },
  29. { label: '已失败', value: 'failed' }
  30. ];
  31. const getStatusBadge = (status: InspectionTask['status']) => {
  32. switch (status) {
  33. case 'pending':
  34. return <Badge status="default" text="待处理" />;
  35. case 'in_progress':
  36. return <Badge status="processing" text="进行中" />;
  37. case 'completed':
  38. return <Badge status="success" text="已完成" />;
  39. case 'failed':
  40. return <Badge status="error" text="已失败" />;
  41. default:
  42. return <Badge status="default" text="未知" />;
  43. }
  44. };
  45. const getPriorityTag = (priority: string) => {
  46. switch (priority) {
  47. case 'low':
  48. return <Tag color="blue">低</Tag>;
  49. case 'medium':
  50. return <Tag color="orange">中</Tag>;
  51. case 'high':
  52. return <Tag color="red">高</Tag>;
  53. case 'urgent':
  54. return <Tag color="magenta">紧急</Tag>;
  55. default:
  56. return <Tag>未知</Tag>;
  57. }
  58. };
  59. export const InspectionsPage = () => {
  60. const [loading, setLoading] = useState(false);
  61. const [data, setData] = useState<InspectionTask[]>([]);
  62. const [deviceTypes, setDeviceTypes] = useState<{label: string, value: string}[]>([]);
  63. const [autoInspectionLoading, setAutoInspectionLoading] = useState(false);
  64. const [manualInspectionVisible, setManualInspectionVisible] = useState(false);
  65. const [progress, setProgress] = useState(0);
  66. const [autoForm] = Form.useForm();
  67. const [manualForm] = Form.useForm();
  68. const [pagination, setPagination] = useState({
  69. current: 1,
  70. pageSize: 10,
  71. total: 0,
  72. });
  73. const [formRef] = Form.useForm();
  74. const [modalVisible, setModalVisible] = useState(false);
  75. const [editingId, setEditingId] = useState<number | null>(null);
  76. useEffect(() => {
  77. fetchData();
  78. fetchDeviceTypes();
  79. }, [pagination.current, pagination.pageSize]);
  80. const fetchDeviceTypes = async () => {
  81. try {
  82. const response = await DeviceTypeAPI.getDeviceTypes({pageSize: 100});
  83. setDeviceTypes(response.data.map((item: any) => ({
  84. label: item.name,
  85. value: item.code
  86. })));
  87. } catch (error) {
  88. console.error('获取设备类型失败:', error);
  89. message.error('获取设备类型失败');
  90. }
  91. };
  92. const fetchData = async () => {
  93. setLoading(true);
  94. try {
  95. const values = formRef.getFieldsValue();
  96. const { title, priority, status, timeRange } = formRef.getFieldsValue();
  97. const params: Record<string, any> = {
  98. page: pagination.current,
  99. limit: pagination.pageSize,
  100. status,
  101. templateId: priority ? parseInt(priority) : undefined
  102. };
  103. if (timeRange) {
  104. params.startTime = timeRange[0].format('YYYY-MM-DD HH:mm:ss');
  105. params.endTime = timeRange[1].format('YYYY-MM-DD HH:mm:ss');
  106. }
  107. const response = await InspectionsAPI.getTasks(params);
  108. setData(response.data);
  109. setPagination({
  110. ...pagination,
  111. total: response.total,
  112. });
  113. } catch (error) {
  114. console.error('获取巡检任务失败:', error);
  115. message.error('获取巡检任务失败');
  116. } finally {
  117. setLoading(false);
  118. }
  119. };
  120. const handleSearch = (values: any) => {
  121. setPagination({
  122. ...pagination,
  123. current: 1,
  124. });
  125. fetchData();
  126. };
  127. const handleTableChange = (newPagination: any) => {
  128. setPagination({
  129. ...pagination,
  130. current: newPagination.current,
  131. pageSize: newPagination.pageSize,
  132. });
  133. };
  134. const handleEditAutoTask = (record: InspectionTask) => {
  135. setEditingId(record.id);
  136. formRef.setFieldsValue({
  137. ...record,
  138. cronExpression: record.cronExpression || `0 0 */${record.intervalDays} * *`
  139. });
  140. setModalVisible(true);
  141. };
  142. const handleDeleteAutoTask = async (id: number) => {
  143. Modal.confirm({
  144. title: '确认删除自动巡检任务?',
  145. content: '此操作不可撤销',
  146. onOk: async () => {
  147. try {
  148. await InspectionsAPI.deleteTemplate(id);
  149. message.success('删除成功');
  150. fetchData();
  151. } catch (error) {
  152. console.error('删除失败:', error);
  153. message.error('删除失败');
  154. }
  155. },
  156. });
  157. };
  158. const handleStatusChange = async (id: number, status: InspectionTask['status']) => {
  159. try {
  160. await InspectionsAPI.updateTemplate(id, { status } as Partial<InspectionTemplate>);
  161. message.success('状态更新成功');
  162. fetchData();
  163. } catch (error) {
  164. console.error('状态更新失败:', error);
  165. message.error('状态更新失败');
  166. }
  167. };
  168. const handleAutoInspection = async () => {
  169. try {
  170. const values = await formRef.validateFields();
  171. const { intervalDays, deviceTypes } = values;
  172. // 自动生成任务编号
  173. const taskNo = `INSP-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
  174. const data = {
  175. taskNo,
  176. intervalDays: intervalDays || 7, // 默认7天
  177. deviceTypes,
  178. reportReceivers: ['admin'] // 默认通知admin
  179. };
  180. if (editingId) {
  181. // 更新模板使用模板相关参数
  182. await InspectionsAPI.updateTemplate(editingId, {
  183. name: values.name,
  184. description: values.description
  185. });
  186. message.success('巡检模板更新成功');
  187. } else {
  188. // 创建任务使用任务相关参数
  189. await InspectionsAPI.createAutoInspectionTask(data);
  190. message.success('自动巡检任务创建成功');
  191. }
  192. setModalVisible(false);
  193. fetchData();
  194. } catch (error) {
  195. console.error('操作失败:', error);
  196. message.error('操作失败: ' + (error as Error).message);
  197. }
  198. };
  199. const columns = [
  200. {
  201. title: '任务标题',
  202. dataIndex: 'title',
  203. key: 'title',
  204. },
  205. {
  206. title: '优先级',
  207. dataIndex: 'priority',
  208. key: 'priority',
  209. render: (priority: string) => getPriorityTag(priority),
  210. },
  211. {
  212. title: '状态',
  213. dataIndex: 'status',
  214. key: 'status',
  215. render: (status: InspectionTask['status']) => getStatusBadge(status),
  216. },
  217. {
  218. title: '计划开始时间',
  219. dataIndex: 'startTime',
  220. key: 'startTime',
  221. render: (text?: string) => text ? dayjs(text).format('YYYY-MM-DD HH:mm') : '-',
  222. },
  223. {
  224. title: '计划结束时间',
  225. dataIndex: 'endTime',
  226. key: 'endTime',
  227. render: (text?: string) => text ? dayjs(text).format('YYYY-MM-DD HH:mm') : '-',
  228. },
  229. {
  230. title: '操作',
  231. key: 'action',
  232. render: (_: any, record: InspectionTask) => (
  233. <Space size="middle">
  234. {record.schedule_type === 'scheduled' && (
  235. <>
  236. <Button type="link" onClick={() => handleEditAutoTask(record)}>编辑</Button>
  237. <Button type="link" danger onClick={() => handleDeleteAutoTask(record.id)}>删除</Button>
  238. </>
  239. )}
  240. <Select
  241. defaultValue={record.status}
  242. style={{ width: 120 }}
  243. onChange={(value) => handleStatusChange(record.id, value)}
  244. options={StatusOptions}
  245. />
  246. </Space>
  247. ),
  248. },
  249. ];
  250. const handleManualInspection = async () => {
  251. try {
  252. const values = await manualForm.validateFields();
  253. setProgress(0);
  254. // 模拟巡检进度
  255. const interval = setInterval(() => {
  256. setProgress(prev => {
  257. const newProgress = prev + 10;
  258. if (newProgress >= 100) {
  259. clearInterval(interval);
  260. message.success('手动巡检完成');
  261. setManualInspectionVisible(false);
  262. // 实际调用API执行巡检
  263. InspectionsAPI.runManualTask({
  264. deviceTypes: values.deviceTypes
  265. });
  266. }
  267. return newProgress;
  268. });
  269. }, 500);
  270. } catch (error) {
  271. console.error('手动巡检失败:', error);
  272. message.error('手动巡检失败');
  273. }
  274. };
  275. return (
  276. <div className="inspections-page">
  277. <Card title="巡检任务配置" style={{ marginBottom: 16 }}>
  278. <Space>
  279. <Button
  280. type="primary"
  281. onClick={() => setManualInspectionVisible(true)}
  282. >
  283. 手动巡检
  284. </Button>
  285. <Button
  286. type="primary"
  287. onClick={() => {
  288. setEditingId(null);
  289. setModalVisible(true);
  290. formRef.setFieldsValue({
  291. name: `自动巡检-${new Date().toLocaleDateString()}`,
  292. intervalDays: 7,
  293. notifyEmails: ['admin'],
  294. cronExpression: '0 0 * * *'
  295. });
  296. }}
  297. >
  298. 自动巡检配置
  299. </Button>
  300. </Space>
  301. </Card>
  302. <Card title="巡检任务管理" style={{ marginBottom: 16 }}>
  303. <Form
  304. form={formRef}
  305. layout="inline"
  306. onFinish={handleSearch}
  307. style={{ marginBottom: 16 }}
  308. >
  309. <Form.Item name="title" label="任务标题">
  310. <Input placeholder="输入任务标题" style={{ width: 200 }} />
  311. </Form.Item>
  312. <Form.Item name="priority" label="优先级">
  313. <Select
  314. placeholder="选择优先级"
  315. style={{ width: 120 }}
  316. allowClear
  317. options={PriorityOptions}
  318. />
  319. </Form.Item>
  320. <Form.Item name="status" label="状态">
  321. <Select
  322. placeholder="选择状态"
  323. style={{ width: 120 }}
  324. allowClear
  325. options={StatusOptions}
  326. />
  327. </Form.Item>
  328. <Form.Item name="timeRange" label="计划时间">
  329. <RangePicker showTime format="YYYY-MM-DD HH:mm" />
  330. </Form.Item>
  331. <Form.Item>
  332. <Button type="primary" htmlType="submit">
  333. 查询
  334. </Button>
  335. </Form.Item>
  336. </Form>
  337. <Table
  338. columns={columns}
  339. dataSource={data}
  340. rowKey="id"
  341. pagination={pagination}
  342. loading={loading}
  343. onChange={handleTableChange}
  344. />
  345. </Card>
  346. <Modal
  347. title={editingId ? '编辑自动巡检任务' : '新建自动巡检任务'}
  348. visible={modalVisible}
  349. onOk={handleAutoInspection}
  350. onCancel={() => setModalVisible(false)}
  351. width={800}
  352. style={{ textAlign: 'center' }}
  353. >
  354. <Form form={formRef} layout="vertical">
  355. <Form.Item
  356. name="name"
  357. label="任务名称"
  358. rules={[{ required: true, message: '请输入任务名称' }]}
  359. >
  360. <Input placeholder="例如: 每周自动巡检" />
  361. </Form.Item>
  362. <Form.Item
  363. name="taskNo"
  364. label="任务编号"
  365. initialValue={`INSP-${Date.now()}`}
  366. >
  367. <Input disabled placeholder="自动生成" />
  368. </Form.Item>
  369. <Form.Item
  370. name="intervalDays"
  371. label="间隔天数"
  372. tooltip="如果未设置cron表达式,则使用间隔天数"
  373. >
  374. <InputNumber
  375. min={1}
  376. placeholder="例如: 7 (每周执行)"
  377. style={{ textAlign: 'left' }}
  378. />
  379. </Form.Item>
  380. <Form.Item
  381. name="deviceTypes"
  382. label="设备类型"
  383. tooltip="不选择则巡检所有设备"
  384. >
  385. <Select
  386. mode="multiple"
  387. style={{ width: '100%' }}
  388. placeholder="选择设备类型"
  389. options={deviceTypes}
  390. />
  391. </Form.Item>
  392. <Form.Item
  393. name="notifyEmails"
  394. label="通知"
  395. tooltip="巡检完成后发送通知"
  396. >
  397. <Select
  398. mode="tags"
  399. style={{ width: '100%' }}
  400. placeholder="输入邮箱后按回车添加"
  401. tokenSeparators={[',', ' ']}
  402. />
  403. </Form.Item>
  404. </Form>
  405. </Modal>
  406. {/* 手动巡检弹窗 */}
  407. <Modal
  408. title={<div style={{ textAlign: 'center', fontSize: '18px', fontWeight: 'bold' }}>手动巡检</div>}
  409. visible={manualInspectionVisible}
  410. onOk={handleManualInspection}
  411. onCancel={() => setManualInspectionVisible(false)}
  412. width={600}
  413. centered
  414. >
  415. <Form form={manualForm} layout="vertical">
  416. <Form.Item label="巡检时间">
  417. {dayjs().format('YYYY-MM-DD HH:mm:ss')}
  418. </Form.Item>
  419. <Form.Item
  420. name="taskNo"
  421. label="巡检任务编号"
  422. initialValue={`INSP-${Date.now()}`}
  423. >
  424. <Input disabled />
  425. </Form.Item>
  426. <Form.Item
  427. name="deviceTypes"
  428. label="巡检设备类型"
  429. >
  430. <Select
  431. mode="multiple"
  432. style={{ width: '100%' }}
  433. placeholder="选择设备类型(不选则为全部设备)"
  434. options={deviceTypes}
  435. />
  436. </Form.Item>
  437. <Form.Item label="巡检进度">
  438. <Progress percent={progress} status={progress < 100 ? 'active' : 'success'} />
  439. </Form.Item>
  440. </Form>
  441. </Modal>
  442. </div>
  443. );
  444. };