index.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  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 className={styles.circleValue}>{item.value}</div>
  218. <div style={{ textAlign: 'center' }} className={styles.unit}>
  219. {item.label}
  220. </div>
  221. </div>
  222. ))}
  223. </div>
  224. );
  225. };
  226. return (
  227. <PageContent closeable={false}>
  228. <ConfigProvider locale={zhCN}>
  229. <PageTitle children="智慧运营报告" returnable />
  230. <div className={styles.head}>
  231. {/* <div className={styles.name}>智慧运营报告</div> */}
  232. <div className={styles.photo}>
  233. {dayjs(date.stime).format('MM月DD日')}-
  234. {dayjs(date.etime).format('MM月DD日')}
  235. </div>
  236. <div className={styles.headRight}>
  237. <div>
  238. 时间:
  239. <Select
  240. className={styles.headRightSelect}
  241. defaultValue="1"
  242. style={{ width: 180 }}
  243. onChange={handleChange}
  244. popupClassName={styles.headRightSelect}
  245. options={[
  246. {
  247. value: '1',
  248. label: '近7天',
  249. },
  250. {
  251. value: '2',
  252. label: '近30天',
  253. },
  254. {
  255. value: '3',
  256. label: '自定义时间',
  257. },
  258. ]}
  259. />
  260. </div>
  261. <div>
  262. {showRange && <RangePicker inputReadOnly onChange={onChange} />}
  263. </div>
  264. </div>
  265. </div>
  266. <div className={styles.title}>无锡锡山水厂</div>
  267. <Spin spinning={loading}>
  268. <Box title="概览">
  269. <div className={styles.content}>
  270. <TwoBoxItem label="累计进水:" value={in_water} />
  271. <TwoBoxItem label="累计出水:" value={out_water} />
  272. <ThreeBoxContent
  273. data={[
  274. { label: '吨水能耗', value: water_electricity, unit: 'kwh' },
  275. { label: '吨水药耗', value: water_medicine, unit: 'kg/m³' },
  276. {
  277. label: '系统自检次数',
  278. value: self_inspection_task,
  279. unit: '次',
  280. },
  281. ]}
  282. />
  283. <TwoBoxItem
  284. label="优化建议:"
  285. value={push_optimize_task}
  286. unit="条"
  287. />
  288. <TwoBoxItem
  289. label="任务完成:"
  290. value={push_complete_task}
  291. unit="个"
  292. />
  293. <ThreeBoxContent
  294. data={[
  295. {
  296. label: '工单完成',
  297. value: work_order_complete_task,
  298. unit: '个',
  299. },
  300. { label: '设备维修', value: repair_record, unit: '个' },
  301. {
  302. label: '设备保养',
  303. value: maintain_record,
  304. unit: '个',
  305. },
  306. ]}
  307. />
  308. </div>
  309. </Box>
  310. <Box title="水量">
  311. <CircleContent
  312. data={[
  313. { label: '累计进水', value: in_water },
  314. { label: '累计出水', value: out_water },
  315. ]}
  316. />
  317. </Box>
  318. <Box title="能耗">
  319. <CircleContent
  320. data={[
  321. { label: '吨水能耗', value: water_electricity, unit: '' },
  322. { label: '累计耗电量', value: electricity, unit: 'kwh' },
  323. ]}
  324. />
  325. </Box>
  326. <Box title="药耗">
  327. <CircleContent
  328. data={[
  329. { label: '吨水药耗', value: water_medicine, unit: 'kg/m³' },
  330. { label: '累计用药量', value: medicine },
  331. ]}
  332. />
  333. </Box>
  334. <div className={styles.box}>
  335. <div className={styles.main_in}>
  336. <div className={styles.titleContent}>系统自检</div>
  337. <CircleThreeContent
  338. data={[
  339. { label: '自检次数', value: self_inspection_task },
  340. { label: '异常次数', value: self_inspection_abnormal_task },
  341. { label: '正常次数', value: self_inspection_normal_task },
  342. ]}
  343. />
  344. <div className={styles.paddingContent}>
  345. <div className={styles.cutLine}></div>
  346. </div>
  347. <div style={{ padding: '0.43rem 0.4rem 0' }}>
  348. <div className={styles.unit}>异常类型统计:</div>
  349. <div
  350. ref={eqDomRef}
  351. style={{
  352. height: '6rem',
  353. width: 'calc(100% - 1.2rem)',
  354. margin: '0 0 0 0.8rem',
  355. }}
  356. />
  357. </div>
  358. </div>
  359. </div>
  360. <div className={styles.box}>
  361. <div className={styles.main_in}>
  362. <div className={styles.titleContent}>智慧运营</div>
  363. <CircleThreeContent
  364. data={[
  365. { label: '优化条数', value: push_optimize_task, unit: '条' },
  366. { label: '超滤能耗', value: ele_65 },
  367. { label: '反渗透能耗', value: ele_66 },
  368. ]}
  369. />
  370. <div className={styles.paddingContent}>
  371. <div className={styles.cutLine}></div>
  372. </div>
  373. <div style={{ padding: '0.43rem 0.4rem 0' }}>
  374. <div className={styles.unit}>工况评估统计:</div>
  375. <div
  376. ref={workScoreDomRef}
  377. style={{
  378. height: '6rem',
  379. width: 'calc(100% - 1.2rem)',
  380. margin: '0 0 0 0.8rem',
  381. }}
  382. />
  383. </div>
  384. {/* <div style={{ padding: '0.2rem 0.2rem 0 0.2rem' }}>
  385. <div className={styles.smartText}>
  386. 优化条数:{push_optimize_task}条
  387. </div>
  388. <div className={styles.smartText}>超滤能耗:{ele_65}</div>
  389. <div className={styles.smartText}>反渗透能耗:{ele_66}</div>
  390. </div> */}
  391. {/* <div
  392. ref={workScoreDomRef}
  393. style={{ height: '3.4rem', margin: '0.1rem 0 0.1rem 0' }}
  394. ></div> */}
  395. </div>
  396. </div>
  397. <div className={styles.box}>
  398. <div className={styles.main_in}>
  399. <div className={styles.titleContent}>任务工单</div>
  400. <CircleContent
  401. data={[
  402. { label: '任务数量', value: push_task },
  403. { label: '工单数量', value: work_order_task },
  404. ]}
  405. />
  406. <CircleContent
  407. data={[
  408. { label: '任务完成数量', value: push_complete_task },
  409. { label: '工单完成数量', value: work_order_complete_task },
  410. ]}
  411. />
  412. <CircleContent
  413. data={[
  414. {
  415. label: '任务完成率',
  416. value: push_complete_task_per,
  417. unit: '%',
  418. },
  419. {
  420. label: '工单完成率',
  421. value: work_order_complete_task_per,
  422. unit: '%',
  423. },
  424. ]}
  425. />
  426. <div style={{ display: 'flex', padding: '0 0.4rem' }}>
  427. <div className={styles.item}>
  428. <div className={styles.unit}>任务类型统计:</div>
  429. <div
  430. ref={taskDomRef}
  431. style={{
  432. height: '6rem',
  433. width: '100%',
  434. marginTop: '0.2rem',
  435. }}
  436. ></div>
  437. </div>
  438. <div className={styles.item}>
  439. <div className={styles.unit}>工单类型统计:</div>
  440. <div
  441. ref={workDomRef}
  442. style={{
  443. height: '6rem',
  444. width: '100%',
  445. marginTop: '0.2rem',
  446. }}
  447. ></div>
  448. </div>
  449. </div>
  450. </div>
  451. </div>
  452. <Box title="设备维修保养">
  453. <CircleContent
  454. data={[
  455. {
  456. label: '维修数量',
  457. value: repair_record,
  458. },
  459. {
  460. label: '保养数量',
  461. value: maintain_record,
  462. },
  463. ]}
  464. />
  465. </Box>
  466. </Spin>
  467. </ConfigProvider>
  468. </PageContent>
  469. );
  470. };
  471. export default SmartReport;
  472. const getPieOption = (chartData, isLeft = false) => {
  473. const option = {
  474. color: [
  475. '#5470c6',
  476. '#91cc75',
  477. '#fac858',
  478. '#ee6666',
  479. '#73c0de',
  480. '#3ba272',
  481. '#fc8452',
  482. '#9a60b4',
  483. '#ea7ccc',
  484. ],
  485. tooltip: {
  486. trigger: 'item',
  487. },
  488. legend: isLeft
  489. ? {
  490. orient: 'vertical',
  491. left: 'left',
  492. top: 'center',
  493. textStyle: {
  494. color: '#000000',
  495. fontSize: 12,
  496. },
  497. }
  498. : {
  499. orient: 'horizontal',
  500. itemWidth: 10,
  501. itemHeight: 8,
  502. textStyle: {
  503. color: '#000000',
  504. fontSize: 10,
  505. },
  506. },
  507. series: [
  508. {
  509. type: 'pie',
  510. top: isLeft ? 0 : '20%',
  511. radius: isLeft ? '60%' : '40%',
  512. label: isLeft ? {} : { fontSize: 10 },
  513. data: chartData,
  514. emphasis: {
  515. itemStyle: {
  516. shadowBlur: 10,
  517. shadowOffsetX: 0,
  518. shadowColor: 'rgba(0, 0, 0, 0.5)',
  519. },
  520. },
  521. },
  522. ],
  523. };
  524. return option;
  525. };