index.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. import PageContent from '@/components/PageContent';
  2. import PageTitle from '@/components/PageTitle';
  3. import { queryWorkReport } from '@/services/smartReport';
  4. import { useNavigate, useParams, useRequest } from '@umijs/max';
  5. import { ConfigProvider, DatePicker, Select, Spin } from 'antd';
  6. import zhCN from 'antd/es/locale/zh_CN';
  7. import dayjs from 'dayjs';
  8. import * as echarts from 'echarts';
  9. import { useEffect, useRef, useState } from 'react';
  10. import styles from './index.less';
  11. const { RangePicker } = DatePicker;
  12. const SmartReport = () => {
  13. const navigate = useNavigate();
  14. const { projectId } = useParams();
  15. const FORMAT = 'YYYY-MM-DD';
  16. const [showRange, setShowRange] = useState(false);
  17. const [date, setDate] = useState({
  18. stime: dayjs().subtract(7, 'day').format(FORMAT),
  19. etime: dayjs().subtract(1, 'day').format(FORMAT),
  20. });
  21. const eqDomRef = useRef(null);
  22. const eqChartRef = useRef(null);
  23. const taskDomRef = useRef(null);
  24. const taskChartRef = useRef(null);
  25. const workDomRef = useRef(null);
  26. const workChartRef = useRef(null);
  27. const workScoreDomRef = useRef(null);
  28. const workScoreChartRef = useRef(null);
  29. const {
  30. data = {},
  31. run,
  32. loading,
  33. } = useRequest(
  34. (date) =>
  35. queryWorkReport({
  36. project_id: projectId,
  37. ...date,
  38. }),
  39. {
  40. defaultParams: [{ project_id: projectId, ...date }],
  41. formatResult: (res) => {
  42. const data = res.data;
  43. return {
  44. ...data,
  45. eqData: [
  46. { name: '安全隐患', value: data.patrol_safe },
  47. { name: '工艺异常', value: data.patrol_section },
  48. { name: '设备异常', value: data.push_complete_task },
  49. ],
  50. taskData: [
  51. { name: '工况任务', value: data.mandate_type_con },
  52. { name: '集团任务', value: data.mandate_type_group },
  53. { name: '系统自检', value: data.mandate_type_pat },
  54. { name: '生产任务', value: data.mandate_type_pro },
  55. ],
  56. workOrderData: [
  57. { name: '盘点', value: data.chart_check },
  58. { name: '备品备件', value: data.chart_part },
  59. { name: '工艺工单', value: data.chart_section },
  60. { name: '维修工单', value: data.repair_record },
  61. { name: '保养工单', value: data.maintain_record },
  62. { name: '巡检工单', value: data.patrol_mandate_record },
  63. { name: '加药工单', value: data.reagent_record },
  64. ],
  65. workScoreData: [
  66. { name: '较差', value: data.con_level_five },
  67. { name: '一般', value: data.con_level_four },
  68. { name: '优秀', value: data.con_level_one },
  69. { name: '较好', value: data.con_level_three },
  70. { name: '良好', value: data.con_level_two },
  71. ],
  72. };
  73. },
  74. },
  75. );
  76. const handleChange = (value) => {
  77. const date = { ...date };
  78. switch (value) {
  79. case '1':
  80. date.stime = dayjs().subtract(7, 'day').format(FORMAT);
  81. date.etime = dayjs().subtract(1, 'day').format(FORMAT);
  82. setDate(date);
  83. setShowRange(false);
  84. run(date);
  85. break;
  86. case '2':
  87. date.stime = dayjs().subtract(1, 'month').format(FORMAT);
  88. date.etime = dayjs().subtract(1, 'day').format(FORMAT);
  89. setDate(date);
  90. setShowRange(false);
  91. run(date);
  92. break;
  93. case '3':
  94. setShowRange(true);
  95. break;
  96. }
  97. };
  98. const {
  99. ele_65,
  100. ele_66,
  101. electricity,
  102. in_water,
  103. maintain_record,
  104. medicine,
  105. out_water,
  106. push_optimize_task,
  107. push_complete_task,
  108. push_complete_task_per,
  109. push_task,
  110. repair_record,
  111. self_inspection_abnormal_task,
  112. self_inspection_normal_task,
  113. self_inspection_task,
  114. water_electricity,
  115. water_medicine,
  116. work_order_complete_task,
  117. work_order_complete_task_per,
  118. work_order_task,
  119. } = data;
  120. useEffect(() => {
  121. initData();
  122. }, [data]);
  123. useEffect(() => {
  124. eqChartRef.current = echarts.init(eqDomRef.current);
  125. taskChartRef.current = echarts.init(taskDomRef.current);
  126. workChartRef.current = echarts.init(workDomRef.current);
  127. workScoreChartRef.current = echarts.init(workScoreDomRef.current);
  128. return () => {
  129. eqChartRef.current.dispose();
  130. taskChartRef.current.dispose();
  131. workChartRef.current.dispose();
  132. workScoreChartRef.current.dispose();
  133. };
  134. }, []);
  135. const initData = () => {
  136. const legend = {
  137. orient: 'vertical',
  138. left: 'left',
  139. top: 'center',
  140. };
  141. const eqOption = getPieOption(data.eqData, legend);
  142. if (eqChartRef.current) eqChartRef.current.setOption(eqOption);
  143. const taskOption = getPieOption(data.taskData);
  144. if (taskChartRef.current) taskChartRef.current.setOption(taskOption);
  145. const workOption = getPieOption(data.workOrderData);
  146. if (workChartRef.current) workChartRef.current.setOption(workOption);
  147. const workScoreOption = getPieOption(data.workScoreData, legend);
  148. if (workScoreChartRef.current)
  149. workScoreChartRef.current.setOption(workScoreOption);
  150. };
  151. const onChange = (value) => {
  152. if (!value) return;
  153. const date = { ...date };
  154. date.stime = value[0].format(FORMAT);
  155. date.etime = value[1].format(FORMAT);
  156. run(date);
  157. setDate(date);
  158. };
  159. const handleOnClick = () => {
  160. history.back();
  161. };
  162. const Box = ({ title, children }) => {
  163. return (
  164. <div className={styles.box}>
  165. <div className={styles.main_in}>
  166. <div className={styles.titleContent}>{title}</div>
  167. {children}
  168. </div>
  169. </div>
  170. );
  171. };
  172. const TwoBoxItem = ({ label, value, unit }) => {
  173. return (
  174. <div className={styles.longWhiteBox}>
  175. <div className={styles.longWhiteBoxIn}>
  176. {label}
  177. <span className={styles.value}>
  178. {value}
  179. {unit && <span className={styles.unit}>{unit}</span>}
  180. </span>
  181. </div>
  182. </div>
  183. );
  184. };
  185. const ThreeBoxContent = ({ data }) => {
  186. return (
  187. <div className={styles.threeContent}>
  188. {data?.map((item) => (
  189. <div className={styles.threeItem}>
  190. <div className={styles.value}>
  191. {item.value}
  192. <span className={styles.unit}>{item.unit}</span>
  193. </div>
  194. {item.label}
  195. </div>
  196. ))}
  197. </div>
  198. );
  199. };
  200. const CircleContent = ({ data }) => {
  201. return (
  202. <div className={styles.circleContent}>
  203. {data?.map((item) => (
  204. <div className={styles.circle}>
  205. <div className={styles.threeItem}>
  206. <div className={styles.value}>
  207. {item.value}
  208. <span className={styles.unit}>{item.unit}</span>
  209. </div>
  210. {item.label}
  211. </div>
  212. </div>
  213. ))}
  214. </div>
  215. );
  216. };
  217. const CircleThreeContent = ({ data }) => {
  218. return (
  219. <div className={styles.circleContent}>
  220. {data?.map((item) => (
  221. <div>
  222. <div className={styles.circleValue}>{item.value}</div>
  223. <div style={{ textAlign: 'center' }} className={styles.unit}>
  224. {item.label}
  225. </div>
  226. </div>
  227. ))}
  228. </div>
  229. );
  230. };
  231. return (
  232. <PageContent closeable={false}>
  233. <ConfigProvider locale={zhCN}>
  234. <PageTitle children="智慧运营报告" returnable />
  235. <div className={styles.head}>
  236. {/* <div className={styles.name}>智慧运营报告</div> */}
  237. <div className={styles.photo}>
  238. {dayjs(date.stime).format('MM月DD日')}-
  239. {dayjs(date.etime).format('MM月DD日')}
  240. </div>
  241. <div className={styles.headRight}>
  242. <div>
  243. 时间:
  244. <Select
  245. className={styles.headRightSelect}
  246. defaultValue="1"
  247. style={{ width: 180 }}
  248. onChange={handleChange}
  249. popupClassName={styles.headRightSelect}
  250. options={[
  251. {
  252. value: '1',
  253. label: '近7天',
  254. },
  255. {
  256. value: '2',
  257. label: '近30天',
  258. },
  259. {
  260. value: '3',
  261. label: '自定义时间',
  262. },
  263. ]}
  264. />
  265. </div>
  266. <div>
  267. {showRange && <RangePicker inputReadOnly onChange={onChange} />}
  268. </div>
  269. </div>
  270. </div>
  271. <div className={styles.title}>无锡锡山水厂</div>
  272. <Spin spinning={loading}>
  273. <Box title="概览">
  274. <div className={styles.content}>
  275. <TwoBoxItem label="累计进水:" value={in_water} />
  276. <TwoBoxItem label="累计出水:" value={out_water} />
  277. <ThreeBoxContent
  278. data={[
  279. { label: '吨水能耗', value: water_electricity, unit: 'kwh' },
  280. { label: '吨水药耗', value: water_medicine, unit: 'kg/m³' },
  281. {
  282. label: '系统自检次数',
  283. value: self_inspection_task,
  284. unit: '次',
  285. },
  286. ]}
  287. />
  288. <TwoBoxItem
  289. label="优化建议:"
  290. value={push_optimize_task}
  291. unit="条"
  292. />
  293. <TwoBoxItem
  294. label="任务完成:"
  295. value={push_complete_task}
  296. unit="个"
  297. />
  298. <ThreeBoxContent
  299. data={[
  300. {
  301. label: '工单完成',
  302. value: work_order_complete_task,
  303. unit: '个',
  304. },
  305. { label: '设备维修', value: repair_record, unit: '个' },
  306. {
  307. label: '设备保养',
  308. value: maintain_record,
  309. unit: '个',
  310. },
  311. ]}
  312. />
  313. </div>
  314. </Box>
  315. <Box title="水量">
  316. <CircleContent
  317. data={[
  318. { label: '累计进水', value: in_water },
  319. { label: '累计出水', value: out_water },
  320. ]}
  321. />
  322. </Box>
  323. <Box title="能耗">
  324. <CircleContent
  325. data={[
  326. { label: '吨水能耗', value: water_electricity, unit: '' },
  327. { label: '累计耗电量', value: electricity, unit: 'kwh' },
  328. ]}
  329. />
  330. </Box>
  331. <Box title="药耗">
  332. <CircleContent
  333. data={[
  334. { label: '吨水药耗', value: water_medicine, unit: 'kg/m³' },
  335. { label: '累计用药量', value: medicine },
  336. ]}
  337. />
  338. </Box>
  339. <div className={styles.box}>
  340. <div className={styles.main_in}>
  341. <div className={styles.titleContent}>系统自检</div>
  342. <CircleThreeContent
  343. data={[
  344. { label: '自检次数', value: self_inspection_task },
  345. { label: '异常次数', value: self_inspection_abnormal_task },
  346. { label: '正常次数', value: self_inspection_normal_task },
  347. ]}
  348. />
  349. <div className={styles.paddingContent}>
  350. <div className={styles.cutLine}></div>
  351. </div>
  352. <div style={{ padding: '0.43rem 0.4rem 0' }}>
  353. <div className={styles.unit}>异常类型统计:</div>
  354. <div
  355. ref={eqDomRef}
  356. style={{
  357. height: '4rem',
  358. width: 'calc(100% - 1.2rem)',
  359. margin: '0 0 0 0.8rem',
  360. }}
  361. />
  362. </div>
  363. </div>
  364. </div>
  365. <div className={styles.box}>
  366. <div className={styles.main_in}>
  367. <div className={styles.titleContent}>智慧运营</div>
  368. <CircleThreeContent
  369. data={[
  370. { label: '优化条数', value: push_optimize_task, unit: '条' },
  371. { label: '超滤能耗', value: ele_65 },
  372. { label: '反渗透能耗', value: ele_66 },
  373. ]}
  374. />
  375. <div className={styles.paddingContent}>
  376. <div className={styles.cutLine}></div>
  377. </div>
  378. <div style={{ padding: '0.43rem 0.4rem 0' }}>
  379. <div className={styles.unit}>工况评估统计:</div>
  380. <div
  381. ref={workScoreDomRef}
  382. style={{
  383. height: '4rem',
  384. width: 'calc(100% - 1.2rem)',
  385. margin: '0 0 0 0.8rem',
  386. }}
  387. />
  388. </div>
  389. {/* <div style={{ padding: '0.2rem 0.2rem 0 0.2rem' }}>
  390. <div className={styles.smartText}>
  391. 优化条数:{push_optimize_task}条
  392. </div>
  393. <div className={styles.smartText}>超滤能耗:{ele_65}</div>
  394. <div className={styles.smartText}>反渗透能耗:{ele_66}</div>
  395. </div> */}
  396. {/* <div
  397. ref={workScoreDomRef}
  398. style={{ height: '3.4rem', margin: '0.1rem 0 0.1rem 0' }}
  399. ></div> */}
  400. </div>
  401. </div>
  402. <div className={styles.box}>
  403. <div className={styles.main_in}>
  404. <div className={styles.titleContent}>任务工单</div>
  405. <CircleContent
  406. data={[
  407. { label: '任务数量', value: push_task },
  408. { label: '工单数量', value: work_order_task },
  409. ]}
  410. />
  411. <CircleContent
  412. data={[
  413. { label: '任务完成数量', value: push_complete_task },
  414. { label: '工单完成数量', value: work_order_complete_task },
  415. ]}
  416. />
  417. <CircleContent
  418. data={[
  419. {
  420. label: '任务完成率',
  421. value: push_complete_task_per,
  422. unit: '%',
  423. },
  424. {
  425. label: '工单完成率',
  426. value: work_order_complete_task_per,
  427. unit: '%',
  428. },
  429. ]}
  430. />
  431. <div style={{ display: 'flex', padding: '0 0.4rem' }}>
  432. <div className={styles.item}>
  433. <div className={styles.unit}>任务类型统计:</div>
  434. <div
  435. ref={taskDomRef}
  436. style={{
  437. height: '4rem',
  438. width: '100%',
  439. marginTop: '0.2rem',
  440. }}
  441. ></div>
  442. </div>
  443. <div className={styles.item}>
  444. <div className={styles.unit}>工单类型统计:</div>
  445. <div
  446. ref={workDomRef}
  447. style={{
  448. height: '4rem',
  449. width: '100%',
  450. marginTop: '0.2rem',
  451. }}
  452. ></div>
  453. </div>
  454. </div>
  455. </div>
  456. </div>
  457. <Box title="设备维修保养">
  458. <CircleContent
  459. data={[
  460. {
  461. label: '维修数量',
  462. value: repair_record,
  463. },
  464. {
  465. label: '保养数量',
  466. value: maintain_record,
  467. },
  468. ]}
  469. />
  470. </Box>
  471. </Spin>
  472. </ConfigProvider>
  473. </PageContent>
  474. );
  475. };
  476. export default SmartReport;
  477. const getPieOption = (chartData, legend = {}) => {
  478. const option = {
  479. color: [
  480. '#5470c6',
  481. '#91cc75',
  482. '#fac858',
  483. '#ee6666',
  484. '#73c0de',
  485. '#3ba272',
  486. '#fc8452',
  487. '#9a60b4',
  488. '#ea7ccc',
  489. ],
  490. tooltip: {
  491. trigger: 'item',
  492. },
  493. legend: {
  494. orient: 'horizontal',
  495. // left: 'left',
  496. textStyle: {
  497. color: '#000000',
  498. fontSize: 18,
  499. },
  500. ...legend,
  501. },
  502. series: [
  503. {
  504. type: 'pie',
  505. radius: '60%',
  506. data: chartData,
  507. emphasis: {
  508. itemStyle: {
  509. shadowBlur: 10,
  510. shadowOffsetX: 0,
  511. shadowColor: 'rgba(0, 0, 0, 0.5)',
  512. },
  513. },
  514. },
  515. ],
  516. };
  517. return option;
  518. };