index.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  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 eqOption = getPieOption(data.eqData, true);
  137. if (eqChartRef.current) eqChartRef.current.setOption(eqOption);
  138. const taskOption = getPieOption(data.taskData);
  139. if (taskChartRef.current) taskChartRef.current.setOption(taskOption);
  140. const workOption = getPieOption(data.workOrderData);
  141. if (workChartRef.current) workChartRef.current.setOption(workOption);
  142. const workScoreOption = getPieOption(data.workScoreData, true);
  143. if (workScoreChartRef.current)
  144. workScoreChartRef.current.setOption(workScoreOption);
  145. };
  146. const onChange = (value) => {
  147. if (!value) return;
  148. const date = { ...date };
  149. date.stime = value[0].format(FORMAT);
  150. date.etime = value[1].format(FORMAT);
  151. run(date);
  152. setDate(date);
  153. };
  154. const handleOnClick = () => {
  155. history.back();
  156. };
  157. const Box = ({ title, children }) => {
  158. return (
  159. <div className={styles.box}>
  160. <div className={styles.main_in}>
  161. <div className={styles.titleContent}>{title}</div>
  162. {children}
  163. </div>
  164. </div>
  165. );
  166. };
  167. const TwoBoxItem = ({ label, value, unit }) => {
  168. return (
  169. <div className={styles.longWhiteBox}>
  170. <div className={styles.longWhiteBoxIn}>
  171. {label}
  172. <span className={styles.value}>
  173. {value}
  174. {unit && <span className={styles.unit}>{unit}</span>}
  175. </span>
  176. </div>
  177. </div>
  178. );
  179. };
  180. const ThreeBoxContent = ({ data }) => {
  181. return (
  182. <div className={styles.threeContent}>
  183. {data?.map((item) => (
  184. <div className={styles.threeItem}>
  185. <div className={styles.value}>
  186. {item.value}
  187. <span className={styles.unit}>{item.unit}</span>
  188. </div>
  189. {item.label}
  190. </div>
  191. ))}
  192. </div>
  193. );
  194. };
  195. const CircleContent = ({ data }) => {
  196. return (
  197. <div className={styles.circleContent}>
  198. {data?.map((item) => (
  199. <div className={styles.circle}>
  200. <div className={styles.threeItem}>
  201. <div className={styles.value}>
  202. {item.value}
  203. <span className={styles.unit}>{item.unit}</span>
  204. </div>
  205. {item.label}
  206. </div>
  207. </div>
  208. ))}
  209. </div>
  210. );
  211. };
  212. const CircleThreeContent = ({ data }) => {
  213. return (
  214. <div className={styles.circleContent}>
  215. {data?.map((item) => (
  216. <div>
  217. <div
  218. className={`${styles.circleValue} ${
  219. Number(item.value) > 999 ? styles.circleValueSmall : ''
  220. }`}
  221. >
  222. {item.value}
  223. </div>
  224. <div style={{ textAlign: 'center' }} className={styles.unit}>
  225. {item.label}
  226. </div>
  227. </div>
  228. ))}
  229. </div>
  230. );
  231. };
  232. const ThreeContent = ({ data }) => {
  233. return (
  234. <div className={styles.circleContent}>
  235. {data?.map((item) => (
  236. <div>
  237. <div className={styles.ValueSmall}>{item.value}</div>
  238. <div style={{ textAlign: 'center' }} className={styles.unit}>
  239. {item.label}
  240. </div>
  241. </div>
  242. ))}
  243. </div>
  244. );
  245. };
  246. return (
  247. <PageContent closeable={false}>
  248. <ConfigProvider locale={zhCN}>
  249. <PageTitle children="智慧运营报告" returnable />
  250. <div className={styles.head}>
  251. {/* <div className={styles.name}>智慧运营报告</div> */}
  252. <div className={styles.photo}>
  253. {dayjs(date.stime).format('MM月DD日')}-
  254. {dayjs(date.etime).format('MM月DD日')}
  255. </div>
  256. <div className={styles.headRight}>
  257. <div>
  258. 时间:
  259. <Select
  260. className={styles.headRightSelect}
  261. defaultValue="1"
  262. style={{ width: '2rem' }}
  263. onChange={handleChange}
  264. popupClassName={styles.headRightSelect}
  265. options={[
  266. {
  267. value: '1',
  268. label: '近7天',
  269. },
  270. {
  271. value: '2',
  272. label: '近30天',
  273. },
  274. {
  275. value: '3',
  276. label: '自定义时间',
  277. },
  278. ]}
  279. />
  280. </div>
  281. <div>
  282. {showRange && <RangePicker inputReadOnly onChange={onChange} />}
  283. </div>
  284. </div>
  285. </div>
  286. <div className={styles.title}>无锡锡山水厂</div>
  287. <Spin spinning={loading}>
  288. <div className={styles.scrollContent}>
  289. <Box title="概览">
  290. <div className={styles.content}>
  291. <TwoBoxItem label="累计进水:" value={in_water} />
  292. <TwoBoxItem label="累计出水:" value={out_water} />
  293. <ThreeBoxContent
  294. data={[
  295. {
  296. label: '吨水能耗',
  297. value: water_electricity,
  298. unit: 'kwh',
  299. },
  300. { label: '吨水药耗', value: water_medicine, unit: 'kg/m³' },
  301. {
  302. label: '系统自检次数',
  303. value: self_inspection_task,
  304. unit: '次',
  305. },
  306. ]}
  307. />
  308. <TwoBoxItem
  309. label="优化建议:"
  310. value={push_optimize_task}
  311. unit="条"
  312. />
  313. <TwoBoxItem
  314. label="任务完成:"
  315. value={push_complete_task}
  316. unit="个"
  317. />
  318. <ThreeBoxContent
  319. data={[
  320. {
  321. label: '工单完成',
  322. value: work_order_complete_task,
  323. unit: '个',
  324. },
  325. { label: '设备维修', value: repair_record, unit: '个' },
  326. {
  327. label: '设备保养',
  328. value: maintain_record,
  329. unit: '个',
  330. },
  331. ]}
  332. />
  333. </div>
  334. </Box>
  335. <Box title="水量">
  336. <CircleContent
  337. data={[
  338. { label: '累计进水', value: in_water },
  339. { label: '累计出水', value: out_water },
  340. ]}
  341. />
  342. </Box>
  343. <Box title="能耗">
  344. <CircleContent
  345. data={[
  346. { label: '吨水能耗', value: water_electricity, unit: '' },
  347. { label: '累计耗电量', value: electricity, unit: 'kwh' },
  348. ]}
  349. />
  350. </Box>
  351. <Box title="药耗">
  352. <CircleContent
  353. data={[
  354. { label: '吨水药耗', value: water_medicine, unit: 'kg/m³' },
  355. { label: '累计用药量', value: medicine },
  356. ]}
  357. />
  358. </Box>
  359. <div className={styles.box}>
  360. <div className={styles.main_in}>
  361. <div className={styles.titleContent}>系统自检</div>
  362. <ThreeContent
  363. data={[
  364. { label: '自检次数', value: self_inspection_task },
  365. { label: '异常次数', value: self_inspection_abnormal_task },
  366. { label: '正常次数', value: self_inspection_normal_task },
  367. ]}
  368. />
  369. <div className={styles.paddingContent}>
  370. <div className={styles.cutLine}></div>
  371. </div>
  372. <div style={{ padding: '0.43rem 0.4rem 0' }}>
  373. <div className={styles.unit}>异常类型统计:</div>
  374. <div
  375. ref={eqDomRef}
  376. style={{
  377. height: '6rem',
  378. margin: '0 0 0 0.8rem',
  379. }}
  380. />
  381. </div>
  382. </div>
  383. </div>
  384. <div className={styles.box}>
  385. <div className={styles.main_in}>
  386. <div className={styles.titleContent}>智慧运营</div>
  387. <CircleThreeContent
  388. data={[
  389. {
  390. label: '优化条数',
  391. value: push_optimize_task,
  392. unit: '条',
  393. },
  394. { label: '超滤能耗(kwh)', value: ele_65 },
  395. { label: '反渗透能耗(kwh)', value: ele_66 },
  396. ]}
  397. />
  398. <div className={styles.paddingContent}>
  399. <div className={styles.cutLine}></div>
  400. </div>
  401. <div style={{ padding: '0.43rem 0.4rem 0' }}>
  402. <div className={styles.unit}>工况评估统计:</div>
  403. <div
  404. ref={workScoreDomRef}
  405. style={{
  406. height: '6rem',
  407. width: 'calc(100% - 1.2rem)',
  408. margin: '0 0 0 0.8rem',
  409. }}
  410. />
  411. </div>
  412. {/* <div style={{ padding: '0.2rem 0.2rem 0 0.2rem' }}>
  413. <div className={styles.smartText}>
  414. 优化条数:{push_optimize_task}条
  415. </div>
  416. <div className={styles.smartText}>超滤能耗:{ele_65}</div>
  417. <div className={styles.smartText}>反渗透能耗:{ele_66}</div>
  418. </div> */}
  419. {/* <div
  420. ref={workScoreDomRef}
  421. style={{ height: '3.4rem', margin: '0.1rem 0 0.1rem 0' }}
  422. ></div> */}
  423. </div>
  424. </div>
  425. <div className={styles.box}>
  426. <div className={styles.main_in}>
  427. <div className={styles.titleContent}>任务工单</div>
  428. <CircleContent
  429. data={[
  430. { label: '任务数量', value: push_task },
  431. { label: '工单数量', value: work_order_task },
  432. ]}
  433. />
  434. <CircleContent
  435. data={[
  436. { label: '任务完成数量', value: push_complete_task },
  437. { label: '工单完成数量', value: work_order_complete_task },
  438. ]}
  439. />
  440. <CircleContent
  441. data={[
  442. {
  443. label: '任务完成率',
  444. value: push_complete_task_per,
  445. unit: '%',
  446. },
  447. {
  448. label: '工单完成率',
  449. value: work_order_complete_task_per,
  450. unit: '%',
  451. },
  452. ]}
  453. />
  454. <div style={{ display: 'flex', padding: '0 0.4rem' }}>
  455. <div className={styles.item}>
  456. <div className={styles.unit}>任务类型统计:</div>
  457. <div
  458. ref={taskDomRef}
  459. style={{
  460. height: '6rem',
  461. width: '100%',
  462. marginTop: '0.2rem',
  463. }}
  464. ></div>
  465. </div>
  466. <div className={styles.item}>
  467. <div className={styles.unit}>工单类型统计:</div>
  468. <div
  469. ref={workDomRef}
  470. style={{
  471. height: '6rem',
  472. width: '100%',
  473. marginTop: '0.2rem',
  474. }}
  475. ></div>
  476. </div>
  477. </div>
  478. </div>
  479. </div>
  480. <Box title="设备维修保养">
  481. <CircleContent
  482. data={[
  483. {
  484. label: '维修数量',
  485. value: repair_record,
  486. },
  487. {
  488. label: '保养数量',
  489. value: maintain_record,
  490. },
  491. ]}
  492. />
  493. </Box>
  494. </div>
  495. </Spin>
  496. </ConfigProvider>
  497. </PageContent>
  498. );
  499. };
  500. export default SmartReport;
  501. const getPieOption = (chartData, isLeft = false) => {
  502. const option = {
  503. color: [
  504. '#5470c6',
  505. '#91cc75',
  506. '#fac858',
  507. '#ee6666',
  508. '#73c0de',
  509. '#3ba272',
  510. '#fc8452',
  511. '#9a60b4',
  512. '#ea7ccc',
  513. ],
  514. tooltip: {
  515. trigger: 'item',
  516. textStyle: {
  517. fontSize: '0.3rem',
  518. },
  519. },
  520. legend: isLeft
  521. ? {
  522. orient: 'vertical',
  523. left: 'left',
  524. top: 'center',
  525. textStyle: {
  526. color: '#000000',
  527. fontSize: '0.3rem',
  528. },
  529. }
  530. : {
  531. orient: 'horizontal',
  532. // itemWidth: 10,
  533. // itemHeight: 8,
  534. textStyle: {
  535. color: '#000000',
  536. fontSize: '0.3rem',
  537. },
  538. },
  539. series: [
  540. {
  541. type: 'pie',
  542. top: isLeft ? 0 : '20%',
  543. radius: isLeft ? '60%' : '40%',
  544. left: isLeft ? '20%' : 0,
  545. label: { fontSize: '0.3rem' },
  546. data: chartData,
  547. emphasis: {
  548. itemStyle: {
  549. shadowBlur: 10,
  550. shadowOffsetX: 0,
  551. shadowColor: 'rgba(0, 0, 0, 0.5)',
  552. },
  553. },
  554. },
  555. ],
  556. };
  557. return option;
  558. };