停车场项目web, 互联网仓库, 开发完成后, 需要将代码回传云桌面.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

736 lines
30 KiB

  1. import React, { useState, useEffect } from "react";
  2. import { ResultFlowResult } from "@/components";
  3. import { Select, Input, Button, Table, message, Pagination, DatePicker, Modal, Cascader, Tooltip } from "antd";
  4. import { Icon } from "@/components"
  5. import { useSessionStorageState } from "ahooks";
  6. import { lineChartOption, ringChartOption } from "../echarts.config";
  7. import moment from "moment";
  8. import ReactEcharts from "echarts-for-react";
  9. import "./index.scss";
  10. import ajax from "@/services";
  11. //停车时段分析
  12. function ParkingAlyPeriod() {
  13. // session缓存
  14. const [defaultParams, setDefaultParams] = useSessionStorageState(
  15. "formData_parkingAlyPeriod",
  16. { defaultValue: null }
  17. );
  18. //区域的下拉数据
  19. const [areaList, setAreaList] = useState([]);
  20. // 默认数据
  21. const defaultData = {
  22. start_time: moment().subtract('days').startOf('day').format("YYYY-MM-DD"),
  23. end_time: moment().endOf("day").format("YYYY-MM-DD"),
  24. date_type: '1',
  25. };
  26. // 分页数据
  27. const [pageInfo, setPageInfo] = useState({
  28. pn: defaultParams ? defaultParams?.pn : 1,
  29. page_size: defaultParams ? defaultParams?.page_size : 15,
  30. });
  31. // 表单数据
  32. const [formData, setFormData] = useState({
  33. ...defaultData,
  34. ...defaultParams,
  35. });
  36. // 搜索提交数据-存储
  37. const [holdData, setHoldData] = useState(formData);
  38. // 访问接口,isAjax改变时执行
  39. const [isAjax, setIsAjax] = useState(false);
  40. // 检索按钮加载状态
  41. const [loading, setLoading] = useState(false);
  42. // 表格加载状态
  43. const [tabLoading, setTabLoading] = useState(false);
  44. // 表格返回数据
  45. const [resultData, setResultData] = useState({
  46. total: 0,
  47. list: [],
  48. });
  49. //出入场车流量分析
  50. const [revenueData, setRevenueData] = useState({});
  51. //停车饱和度趋势分析
  52. const [parkData, setParkData] = useState({});
  53. const [searchSelectList, setSearchSelectList] = useState([]); //搜索下拉数据
  54. const [sessionTabList, setSessionTabList] = useSessionStorageState('parkingAlyPeriod', {
  55. value: {
  56. }
  57. })
  58. useEffect(() => {
  59. if (sessionTabList && Object.values(sessionTabList).length > 0) {
  60. setFormData({
  61. ...formData, ...sessionTabList
  62. })
  63. getCheck({
  64. ...sessionTabList
  65. })
  66. } else {
  67. getCheck()
  68. }
  69. }, [isAjax])
  70. useEffect(() => {
  71. setSessionTabList({
  72. ...formData
  73. })
  74. }, [formData])
  75. useEffect(() => {
  76. getSelectList();
  77. }, []);
  78. // 访问接口,获取表格
  79. // useEffect(() => {
  80. // getData();
  81. // }, [isAjax]);
  82. //时间状态切换
  83. const TimeChange = () => {
  84. let e = formData.date_type;
  85. let str = "day";
  86. let mat = "YYYY-MM-DD";
  87. if (e == 4) {
  88. str = "year";
  89. mat = "YYYY";
  90. } else if (e == 3) {
  91. str = "month";
  92. mat = "YYYY-MM";
  93. } else if (e == 2) {
  94. str = "week";
  95. mat = "YYYY-MM-DD";
  96. }
  97. return { str, mat };
  98. };
  99. //切换时间变化
  100. const SetTimeNow = (e) => {
  101. let start = "";
  102. let end = "";
  103. if (e == 4) {
  104. start = moment().format("YYYY");
  105. end = moment().format("YYYY");
  106. } else if (e == 3) {
  107. start = moment().startOf('month').format("YYYY-MM-DD");
  108. end = moment().endOf("month").format("YYYY-MM-DD");
  109. } else if (e == 2) {
  110. start = moment().day(1).format("YYYY-MM-DD");
  111. end = moment().day(7).format("YYYY-MM-DD");
  112. } else {
  113. start = moment().startOf("day").format("YYYY-MM-DD");
  114. end = moment().endOf("day").format("YYYY-MM-DD");
  115. }
  116. setFormData({
  117. ...formData,
  118. date_type: e,
  119. start_time: start,
  120. end_time: end,
  121. });
  122. };
  123. //出入场车流量分析 折线图
  124. const getRevenueOption = (data) => {
  125. const areaNames = data[0].name ? [...new Set(data.map((item) => item.name))] : ['入场车次', '出场车次'];
  126. // 获取所有横坐标
  127. const dates = [...new Set(data.map((item) => item.hour))].sort(
  128. (a, b) => a.hour - b.hour
  129. );
  130. // 构建数据对象
  131. const seriesData = areaNames.map((areaName, index) => {
  132. // 获取数据
  133. const areaData = data[0].name ? data.filter((item) => item.name === areaName) : data
  134. // 构建数据对象
  135. return {
  136. name: areaNames.length > 1 ? areaName : '',
  137. type: "line",
  138. itemStyle: {
  139. label: {
  140. show: true, //开启显示
  141. position: 'top', //在上方显示
  142. color: 'white',//字体颜色
  143. fontSize: 10//字体大小
  144. },
  145. },
  146. data: dates.map((item) => {
  147. for (const { hour, total_in_records, total_out_records } of areaData) {
  148. if (hour === item) return index == 1 ? total_in_records : total_out_records;
  149. }
  150. return 0;
  151. }),
  152. };
  153. });
  154. // 构建X轴数据
  155. const xAxisData = dates.map((date) => {
  156. return {
  157. value: date,
  158. align: "center",
  159. lineStyle: {
  160. color: "skyblue", // 设置线的颜色为天蓝色
  161. shadowBlur: 6,
  162. },
  163. };
  164. });
  165. setRevenueData(lineChartOption(areaNames, xAxisData, "车次数(个)", seriesData));
  166. };
  167. //停车饱和趋势分析分析 折线图
  168. const getParkOption = (data) => {
  169. // data = [
  170. // {
  171. // "hour": 0,
  172. // "occupancy_rate": "0.01%",
  173. // "charge_type": "2",
  174. // "charge_type_name": "二类区"
  175. // },
  176. // {
  177. // "hour": 1,
  178. // "occupancy_rate": 0,
  179. // "charge_type": "2",
  180. // "charge_type_name": "二类区"
  181. // },
  182. // {
  183. // "hour": 2,
  184. // "occupancy_rate": 0,
  185. // "charge_type": "2",
  186. // "charge_type_name": "二类区"
  187. // },
  188. // {
  189. // "hour": 3,
  190. // "occupancy_rate": 0,
  191. // "charge_type": "2",
  192. // "charge_type_name": "二类区"
  193. // },
  194. // {
  195. // "hour": 4,
  196. // "occupancy_rate": 0,
  197. // "charge_type": "2",
  198. // "charge_type_name": "二类区"
  199. // },
  200. // {
  201. // "hour": 5,
  202. // "occupancy_rate": 0,
  203. // "charge_type": "2",
  204. // "charge_type_name": "二类区"
  205. // },
  206. // {
  207. // "hour": 6,
  208. // "occupancy_rate": 0,
  209. // "charge_type": "2",
  210. // "charge_type_name": "二类区"
  211. // },
  212. // {
  213. // "hour": 7,
  214. // "occupancy_rate": 0,
  215. // "charge_type": "2",
  216. // "charge_type_name": "二类区"
  217. // },
  218. // {
  219. // "hour": 8,
  220. // "occupancy_rate": 0,
  221. // "charge_type": "2",
  222. // "charge_type_name": "二类区"
  223. // },
  224. // {
  225. // "hour": 9,
  226. // "occupancy_rate": "0.1%",
  227. // "charge_type": "2",
  228. // "charge_type_name": "二类区"
  229. // },
  230. // {
  231. // "hour": 10,
  232. // "occupancy_rate": "0.01%",
  233. // "charge_type": "2",
  234. // "charge_type_name": "二类区"
  235. // },
  236. // {
  237. // "hour": 11,
  238. // "occupancy_rate": "0.04%",
  239. // "charge_type": "2",
  240. // "charge_type_name": "二类区"
  241. // },
  242. // {
  243. // "hour": 12,
  244. // "occupancy_rate": "0.06%",
  245. // "charge_type": "2",
  246. // "charge_type_name": "二类区"
  247. // },
  248. // {
  249. // "hour": 13,
  250. // "occupancy_rate": "0.04%",
  251. // "charge_type": "2",
  252. // "charge_type_name": "二类区"
  253. // },
  254. // {
  255. // "hour": 14,
  256. // "occupancy_rate": "0.06%",
  257. // "charge_type": "2",
  258. // "charge_type_name": "二类区"
  259. // },
  260. // {
  261. // "hour": 15,
  262. // "occupancy_rate": "0.01%",
  263. // "charge_type": "2",
  264. // "charge_type_name": "二类区"
  265. // },
  266. // {
  267. // "hour": 16,
  268. // "occupancy_rate": "0.04%",
  269. // "charge_type": "2",
  270. // "charge_type_name": "二类区"
  271. // },
  272. // {
  273. // "hour": 17,
  274. // "occupancy_rate": "0.04%",
  275. // "charge_type": "2",
  276. // "charge_type_name": "二类区"
  277. // },
  278. // {
  279. // "hour": 18,
  280. // "occupancy_rate": "0.07%",
  281. // "charge_type": "2",
  282. // "charge_type_name": "二类区"
  283. // },
  284. // {
  285. // "hour": 19,
  286. // "occupancy_rate": 0,
  287. // "charge_type": "2",
  288. // "charge_type_name": "二类区"
  289. // },
  290. // {
  291. // "hour": 20,
  292. // "occupancy_rate": 0,
  293. // "charge_type": "2",
  294. // "charge_type_name": "二类区"
  295. // },
  296. // {
  297. // "hour": 21,
  298. // "occupancy_rate": "0.01%",
  299. // "charge_type": "2",
  300. // "charge_type_name": "二类区"
  301. // },
  302. // {
  303. // "hour": 22,
  304. // "occupancy_rate": 0,
  305. // "charge_type": "2",
  306. // "charge_type_name": "二类区"
  307. // },
  308. // {
  309. // "hour": 23,
  310. // "occupancy_rate": 0,
  311. // "charge_type": "2",
  312. // "charge_type_name": "二类区"
  313. // }
  314. // ]
  315. const areaNames = data[0].charge_type_name ? [...new Set(data.map((item) => item.charge_type_name))] : [''];
  316. // 获取所有横坐标
  317. const dates = [...new Set(data.map((item) => item.hour))].sort(
  318. (a, b) => a.hour - b.hour
  319. );
  320. // 构建数据对象
  321. const seriesData = areaNames.map((areaName, index) => {
  322. // 获取数据
  323. const areaData = data[0].charge_type_name ? data.filter((item) => item.charge_type_name === areaName) : data
  324. // 构建数据对象
  325. return {
  326. name: areaName,
  327. type: "line",
  328. itemStyle: {
  329. label: {
  330. show: true, //开启显示
  331. position: 'top', //在上方显示
  332. color: 'white',//字体颜色
  333. fontSize: 10//字体大小
  334. },
  335. },
  336. data: dates.map((item) => {
  337. for (const { hour, occupancy_rate } of areaData) {
  338. if (hour === item) return parseFloat(occupancy_rate);
  339. }
  340. return 0;
  341. }),
  342. };
  343. });
  344. // 构建X轴数据
  345. const xAxisData = dates.map((date) => {
  346. return {
  347. value: date,
  348. align: "center",
  349. lineStyle: {
  350. color: "skyblue", // 设置线的颜色为天蓝色
  351. shadowBlur: 6,
  352. },
  353. };
  354. });
  355. setParkData(lineChartOption(areaNames, xAxisData, "饱和度", seriesData));
  356. };
  357. function getParkingIncome(data) {
  358. ajax
  359. .getParkingAlyPeriodLine(data)
  360. .then((res) => {
  361. if (res.status === 20000) {
  362. getRevenueOption(res.data.list);
  363. setResultData(res.data)
  364. }
  365. })
  366. .catch((err) => console.error(err));
  367. }
  368. function getParkingData(data) {
  369. ajax
  370. .getParkingAlyPeriodParkLine(data)
  371. .then((res) => {
  372. if (res.status === 20000) {
  373. getParkOption(res.data.list);
  374. }
  375. })
  376. .catch((err) => console.error(err));
  377. }
  378. // 获取下拉数据
  379. const getSelectList = () => {
  380. ajax.getOperator().then((e) => {
  381. setSearchSelectList([
  382. ...searchSelectList,
  383. ...e.data
  384. ])
  385. })
  386. };
  387. // 携带参数处理
  388. const getCheck = (v) => {
  389. let postData = { ...formData };
  390. if (!loading) {
  391. postData = { ...holdData };
  392. }
  393. setDefaultParams({ ...postData, ...pageInfo });
  394. if (moment(formData.end_time) - moment(formData.start_time) > 1000 * 31 * 24 * 3600) {
  395. message.error("时间范围限制为31天!")
  396. setLoading(false);
  397. setTabLoading(false);
  398. return
  399. }
  400. setLoading(false);
  401. setTabLoading(false);
  402. let params = { ...postData, ...v, ...pageInfo }
  403. //请求接口
  404. getParkingIncome(params)
  405. getParkingData(params)
  406. };
  407. // 检索数据
  408. const handleSearch = () => {
  409. setLoading(true);
  410. setPageInfo({ ...pageInfo, ...{ pn: 1 } });
  411. setHoldData(formData);
  412. setIsAjax(!isAjax);
  413. };
  414. // 导出
  415. const handleExport = () => {
  416. if (tableData.list.area_list?.length > 0) {
  417. let { pn, page_size, ...params } = defaultParams;
  418. ajax.getParkingAlyDurationParkingExp(defaultParams).then(
  419. (res) => {
  420. if (res) {
  421. window.open(res.data.export_url)
  422. } else {
  423. message.error(res?.message);
  424. }
  425. },
  426. (err) => {
  427. console.log(err);
  428. }
  429. );
  430. } else {
  431. message.error("暂无数据");
  432. }
  433. };
  434. // useEffect(() => {
  435. // getParkingIncome();
  436. // }, []);
  437. //区域下拉框数据
  438. useEffect(() => {
  439. ajax
  440. .getAreaTree()
  441. .then((res) => {
  442. if (res.status === 20000) {
  443. setAreaList(res.data);
  444. }
  445. })
  446. .catch((err) => {
  447. console.error(err);
  448. });
  449. }, []);
  450. return (
  451. <>
  452. <div className="edit-order-inquiry">
  453. <div className="paid-search">
  454. <div className="title">查询条件</div>
  455. <div className="form-Wrap">
  456. <div className="yisa-search">
  457. <label>区域</label>
  458. <Cascader
  459. className="form-con"
  460. popupClassName="start-exception-deal-cascader"
  461. options={areaList}
  462. placeholder="请选择区域"
  463. expandTrigger="hover"
  464. fieldNames={{
  465. label: "name",
  466. value: "id",
  467. children: "children",
  468. }}
  469. value={formData.area_id}
  470. onChange={(v, option) => {
  471. setFormData({ ...formData, area_id: v ? v : null });
  472. }}
  473. />
  474. </div>
  475. <div className="yisa-search">
  476. <label>运营商</label>
  477. <Select
  478. className="form-con"
  479. placeholder="请选择"
  480. options={searchSelectList || []}
  481. value={formData.operator_id}
  482. onChange={(v) =>
  483. setFormData({ ...formData, operator_id: v })
  484. }
  485. />
  486. </div>
  487. <div className="yisa-search">
  488. <label>车场类型</label>
  489. <Select
  490. className="form-con"
  491. placeholder="请选择车场类型"
  492. options={[
  493. {
  494. label: '全部',
  495. value: '0',
  496. },
  497. {
  498. label: '路内车场',
  499. value: '1',
  500. },
  501. {
  502. label: '路外车场',
  503. value: '2',
  504. },
  505. ]}
  506. value={formData.car_parking_type}
  507. onChange={(v) =>
  508. setFormData({ ...formData, car_parking_type: v })
  509. }
  510. />
  511. </div>
  512. <div className="yisa-search">
  513. <label>停车场</label>
  514. <Input
  515. className="form-con"
  516. placeholder="请输入"
  517. value={formData?.road_name}
  518. onChange={(e) =>
  519. setFormData({ ...formData, road_name: e.target.value })
  520. }
  521. />
  522. </div>
  523. <div className="yisa-search">
  524. <label>日期
  525. <div className="daf">
  526. <Select
  527. value={formData.date_type}
  528. // style={{
  529. // width: "100%",
  530. // }}
  531. placeholder="请选择"
  532. options={[
  533. {
  534. value: "1",
  535. label: "日",
  536. },
  537. {
  538. value: "2",
  539. label: "周",
  540. },
  541. {
  542. value: "3",
  543. label: "月",
  544. },
  545. ]}
  546. onChange={(e) => SetTimeNow(e)}
  547. />
  548. </div>
  549. </label>
  550. <DatePicker
  551. style={{ width: "100%" }}
  552. // showTime
  553. format={TimeChange().mat}
  554. picker={TimeChange().str}
  555. allowClear={false}
  556. value={formData.start_time ? moment(formData.start_time) : null}
  557. onChange={(date, dateString) => {
  558. if (TimeChange().str == "week") {
  559. setFormData({
  560. ...formData,
  561. start_time: date
  562. ? moment(date).day(1).format("YYYY-MM-DD")
  563. : null,
  564. });
  565. } else if (TimeChange().str == "day") {
  566. if (date > moment(formData.end_time)) {
  567. setFormData({
  568. ...formData,
  569. end_time: dateString,
  570. start_time: formData.end_time,
  571. });
  572. } else {
  573. setFormData({
  574. ...formData,
  575. start_time: dateString,
  576. });
  577. }
  578. } else if (TimeChange().str == "month") {
  579. setFormData({ ...formData, start_time: moment(date).format("YYYY-MM-DD"), end_time: moment(date).endOf("month").format("YYYY-MM-DD") });
  580. } else {
  581. setFormData({ ...formData, start_time: dateString });
  582. }
  583. }}
  584. disabledDate={(current) => current > moment(formData.end_time)}
  585. />
  586. </div>
  587. <div className="yisa-search">
  588. <label></label>
  589. <DatePicker
  590. style={{ width: "100%" }}
  591. // showTime
  592. format={TimeChange().mat}
  593. picker={TimeChange().str}
  594. allowClear={false}
  595. value={formData.end_time ? moment(formData.end_time) : null}
  596. onChange={(date, dateString) => {
  597. if (TimeChange().str == "week") {
  598. setFormData({
  599. ...formData,
  600. end_time: date
  601. ? moment(date).day(7).format("YYYY-MM-DD")
  602. : null,
  603. });
  604. } else if (TimeChange().str == "day") {
  605. if (date < moment(formData.start_time)) {
  606. setFormData({
  607. ...formData,
  608. start_time: dateString,
  609. end_time: formData.start_time,
  610. });
  611. } else {
  612. setFormData({
  613. ...formData,
  614. end_time: dateString,
  615. });
  616. }
  617. } else if (TimeChange().str == "month") {
  618. setFormData({ ...formData, start_time: moment(date).startOf('month').format("YYYY-MM-DD"), end_time: moment(date).format("YYYY-MM-DD") });
  619. } else {
  620. setFormData({ ...formData, end_time: dateString });
  621. }
  622. }}
  623. disabledDate={(current) =>
  624. current < moment(formData.start_time)
  625. }
  626. />
  627. </div>
  628. <div className="form-btn">
  629. <Button
  630. className="reset"
  631. onClick={() => setFormData(defaultData)}
  632. >
  633. 重置
  634. </Button>
  635. <Button
  636. className="submit"
  637. type="primary"
  638. onClick={handleSearch}
  639. loading={loading}
  640. >
  641. 查询
  642. </Button>
  643. </div>
  644. </div>
  645. </div>
  646. <div className="paid-result period-result">
  647. <div className="result">
  648. <div className="result-hd">
  649. <div className="result-header rea">
  650. <div className="result-icon"><Icon type="shijian" /></div>
  651. <div className="result-content">
  652. <div className="title">停车高峰时段</div>
  653. <div className="time">{resultData.peak_hours || "--"}</div>
  654. </div>
  655. </div>
  656. <div className="result-header reb">
  657. <div className="result-icon"><Icon type="shijian" /></div>
  658. <div className="result-content">
  659. <div className="title">入场压力时段</div>
  660. <div className="time">{resultData.entry_pressure_hours || "--"}</div>
  661. </div>
  662. </div>
  663. <div className="result-header rec">
  664. <div className="result-icon"><Icon type="shijian" /></div>
  665. <div className="result-content">
  666. <div className="title">出场压力时段</div>
  667. <div className="time">{resultData.exit_pressure_hours || "--"}</div>
  668. </div>
  669. </div>
  670. </div>
  671. <div className="result-box">
  672. <div className="result-box-title">出入场车流量分析</div>
  673. <Tooltip
  674. placement="topLeft"
  675. title={<span>展示所选日期内各时段的车辆出入场数量用来分析一天内入场车流量与出场车流量的变化情况</span>}
  676. >
  677. <i>?</i>
  678. </Tooltip>
  679. <ReactEcharts
  680. option={revenueData}
  681. style={{ height: "300px", width: "100%", overflow: "hidden" }}
  682. />
  683. </div>
  684. <div className="result-box">
  685. <div className="result-box-title">停车饱和度趋势分析</div>
  686. <Tooltip
  687. placement="topLeft"
  688. title={<span>展示所选日期内各个时间段的停车场饱和度变化情况用来分析停车高峰与低谷时段入场与出场压力较大的时段</span>}
  689. >
  690. <i>?</i>
  691. </Tooltip>
  692. <div className="bhd-select">
  693. <Select
  694. className="form-con"
  695. placeholder="请选择"
  696. defaultValue={'0'}
  697. options={[
  698. {
  699. label: '按时间对比',
  700. value: '0',
  701. },
  702. {
  703. label: '按计费类型对比',
  704. value: '1',
  705. },
  706. ]}
  707. value={formData.pay_me}
  708. onChange={(v) =>
  709. setFormData({ ...formData, pay_me: v })
  710. }
  711. />
  712. </div>
  713. <ReactEcharts
  714. option={parkData}
  715. style={{ height: "300px", width: "100%", overflow: "hidden" }}
  716. />
  717. </div>
  718. </div>
  719. </div>
  720. </div>
  721. </>
  722. );
  723. }
  724. export default ParkingAlyPeriod;