sandianpredict.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. <template>
  2. <!-- littlegreen - 月度预测数据 - 补充form-label-->
  3. <div :class="className" :style="{ height: height, width: width }">
  4. <div class="select-container">
  5. <el-form
  6. style="
  7. display: grid;
  8. grid-template-columns: 1fr 1fr 1fr;
  9. align-items: center;
  10. grid-gap: 20px;
  11. "
  12. label-width="120px"
  13. >
  14. <el-form-item label="企业" style="margin-bottom: 0">
  15. <el-select v-model="selectedEnterprise" style="width: 100%">
  16. <el-option
  17. v-for="item in uniqueEnterprises"
  18. :key="item.enterpriseName"
  19. :value="item.enterpriseName"
  20. >
  21. {{ item.enterpriseName }}
  22. </el-option>
  23. </el-select>
  24. </el-form-item>
  25. <el-form-item label="评估指标" style="margin-bottom: 0">
  26. <el-select v-model="selectedDataKey" style="width: 100%">
  27. <el-option
  28. v-for="key in dataKeys"
  29. :key="key"
  30. :value="key"
  31. :label="keyToChinese[key]"
  32. >
  33. {{ keyToChinese[key] }}
  34. </el-option>
  35. </el-select>
  36. </el-form-item>
  37. <el-form-item label="年度" style="margin-bottom: 0">
  38. <el-select v-model="selectedYear" style="width: 100%">
  39. <el-option v-for="year in availableYears" :key="year" :value="year">
  40. {{ year }}
  41. </el-option>
  42. </el-select>
  43. </el-form-item>
  44. <el-form-item label="报警阈值" style="margin-bottom: 0">
  45. <el-input
  46. v-model="errorVal"
  47. placeholder="报警阈值"
  48. clearable
  49. style="width: 100%"
  50. />
  51. </el-form-item>
  52. <el-form-item label="预测模型" style="margin-bottom: 0">
  53. <el-select v-model="selectedModel" style="width: 100%">
  54. <el-option
  55. v-for="model in availableModels"
  56. :key="model"
  57. :value="model"
  58. >
  59. {{ model }}
  60. </el-option>
  61. </el-select>
  62. </el-form-item>
  63. <el-form-item label="采用预测模型" style="margin-bottom: 0">
  64. <div style="display: inline; padding-left: 10px">
  65. {{ selectedModel }}
  66. </div>
  67. </el-form-item>
  68. <el-form-item style="margin-bottom: 0">
  69. <el-button
  70. type="primary"
  71. @click="updateChart"
  72. size="mini"
  73. >计算</el-button
  74. >
  75. </el-form-item>
  76. </el-form>
  77. </div>
  78. <div ref="chart" :style="{ height: height, width: width }"></div>
  79. </div>
  80. </template>
  81. <script>
  82. import * as echarts from "echarts";
  83. import { listPredict } from "@/api/predict/predict";
  84. import { listBase_data_month } from "@/api/base_data_month/base_data_month";
  85. require("echarts/theme/macarons");
  86. import resize from "./mixins/resize";
  87. import { Decimal } from "decimal.js";
  88. export default {
  89. mixins: [resize],
  90. props: {
  91. className: {
  92. type: String,
  93. default: "chart",
  94. },
  95. width: {
  96. type: String,
  97. default: "100%",
  98. },
  99. height: {
  100. type: String,
  101. default: "400px",
  102. },
  103. },
  104. data() {
  105. return {
  106. errorVal: null,
  107. chart: null,
  108. chartData: [],
  109. base_data_monthData: [],
  110. selectedDataKey: "totalIndustrialValuePredict", // 默认第一个
  111. dataKeys: [
  112. "totalIndustrialValuePredict",
  113. // 'taxableIncomePredict',
  114. // 'paidTaxPredict',
  115. // 'energyConsumePredict',
  116. "powerConsumePredict",
  117. ],
  118. keyToChinese: {
  119. totalIndustrialValuePredict: "工业总产值预测",
  120. // 'taxableIncomePredict': '应税收入预测',
  121. // 'paidTaxPredict': '实缴税金预测',
  122. // 'energyConsumePredict': '能源消费量预测'
  123. powerConsumePredict: "电力消费预测",
  124. },
  125. keyToChineseTruth: {
  126. totalIndustrialValuePredict: "工业总产值实际数据",
  127. taxableIncomePredict: "应税收入实际数据",
  128. paidTaxPredict: "实缴税金实际数据",
  129. energyConsumePredict: "能源消费量实际数据",
  130. powerConsumePredict: "电力消费实际数据",
  131. },
  132. selectedYear: null,
  133. selectedEnterprise: null,
  134. selectedModel: null,
  135. queryParams: {
  136. pageNum: 1,
  137. pageSize: 2000000,
  138. enterpriseName: this.selectedEnterprise,
  139. location: null,
  140. code: null,
  141. mainBusiness: null,
  142. landArea: null,
  143. totalIndustrialValue: null,
  144. gdp: null,
  145. taxableIncome: null,
  146. paidTax: null,
  147. mainBusinessIncome: null,
  148. employeeNumber: null,
  149. profit: null,
  150. ownerEquity: null,
  151. funding: null,
  152. energyConsume: null,
  153. year: this.selectedYear,
  154. month: null,
  155. model: null,
  156. },
  157. availableYears: [],
  158. uniqueEnterprises: [],
  159. availableModels: [],
  160. };
  161. },
  162. mounted() {
  163. this.$nextTick(() => {
  164. this.fetchData();
  165. });
  166. },
  167. beforeDestroy() {
  168. if (this.chart) {
  169. this.chart.dispose();
  170. }
  171. },
  172. methods: {
  173. fetchData() {
  174. listPredict(this.queryParams)
  175. .then((response) => {
  176. this.chartData = response.rows;
  177. // console.log(this.chartData);
  178. this.availableYears = [
  179. ...new Set(this.chartData.map((item) => item.year)),
  180. ];
  181. this.selectedYear = this.availableYears[0] || null;
  182. this.uniqueEnterprises = [
  183. ...new Set(
  184. this.chartData.map((item) => ({
  185. enterpriseName: item.enterpriseName,
  186. }))
  187. ),
  188. ];
  189. this.uniqueEnterprises = Array.from(
  190. new Set(this.chartData.map((item) => item.enterpriseName))
  191. ).map((name) => ({ enterpriseName: name }));
  192. this.selectedEnterprise =
  193. this.uniqueEnterprises.length > 0
  194. ? this.uniqueEnterprises[0].enterpriseName
  195. : null;
  196. this.availableModels = [
  197. ...new Set(this.chartData.map((item) => item.model)),
  198. ];
  199. this.selectedModel = this.availableModels[0] || null;
  200. // 获取实际数据
  201. listBase_data_month(this.queryParams).then((response) => {
  202. this.base_data_monthData = response.rows;
  203. // console.log("vvv" + this.base_data_monthData.length); // 10
  204. this.initChart();
  205. });
  206. })
  207. .catch((error) => {
  208. console.error("Error fetching data:", error);
  209. });
  210. },
  211. initChart() {
  212. if (this.chart) {
  213. this.chart.dispose();
  214. }
  215. this.chart = echarts.init(this.$refs.chart, "macarons");
  216. this.updateChart();
  217. },
  218. // littlegreen 检验是否正确填写预警值
  219. checkErrorVal(number) {
  220. let val = parseFloat(number);
  221. let flag;
  222. if (!number) {
  223. return (flag = 0);
  224. }
  225. if (isNaN(val)) {
  226. this.$message({
  227. message: "请输入有效的数字。",
  228. type: "error",
  229. });
  230. return (flag = 2);
  231. }
  232. return (flag = 1);
  233. },
  234. updateChart() {
  235. const that = this;
  236. let flag = that.checkErrorVal(that.errorVal);
  237. if (flag == 2) {
  238. return;
  239. }
  240. if (!this.chart) {
  241. return;
  242. }
  243. let filteredData = this.chartData.filter(
  244. (item) =>
  245. item.year === this.selectedYear &&
  246. item.enterpriseName === this.selectedEnterprise &&
  247. item.model === this.selectedModel
  248. );
  249. // console.log(filteredData,"filteredData")
  250. //const xValues = filteredData.map(item => item.month + '月');
  251. const xValues = [
  252. "1月",
  253. "2月",
  254. "3月",
  255. "4月",
  256. "5月",
  257. "6月",
  258. "7月",
  259. "8月",
  260. "9月",
  261. "10月",
  262. "11月",
  263. "12月",
  264. ];
  265. let yValues;
  266. // 根据选择的不同预测数据 key 获取对应的值
  267. switch (this.selectedDataKey) {
  268. case "totalIndustrialValuePredict":
  269. yValues = filteredData.map((item) => [
  270. item.totalIndustrialValuePredict,
  271. item.month,
  272. ]);
  273. break;
  274. case "taxableIncomePredict":
  275. yValues = filteredData.map((item) => [
  276. item.taxableIncomePredict,
  277. item.month,
  278. ]);
  279. break;
  280. case "paidTaxPredict":
  281. yValues = filteredData.map((item) => [
  282. item.paidTaxPredict,
  283. item.month,
  284. ]);
  285. break;
  286. case "energyConsumePredict":
  287. yValues = filteredData.map((item) => [
  288. item.energyConsumePredict,
  289. item.month,
  290. ]);
  291. break;
  292. case "powerConsumePredict":
  293. yValues = filteredData.map((item) => [
  294. item.powerConsumePredict,
  295. item.month,
  296. ]);
  297. break;
  298. case "fundingConsumePredict":
  299. yValues = filteredData.map((item) => [
  300. item.fundingConsumePredict,
  301. item.month,
  302. ]);
  303. break;
  304. }
  305. // console.log(yValues, "yValues"); // 预测数据
  306. // // 处理实际数据
  307. // console.log(this.base_data_monthData, "初始过往数据");
  308. // console.log(filteredData, "过滤好的预测数据");
  309. const base_data_monthFiltered = this.base_data_monthData.filter(
  310. (item) =>
  311. item.year === this.selectedYear &&
  312. item.enterpriseName === this.selectedEnterprise
  313. );
  314. // console.log(base_data_monthFiltered, "base_data_monthFiltered"); //往期数据
  315. const base_data_monthXValues = base_data_monthFiltered.map(
  316. (item) => item.month + "月"
  317. );
  318. let base_data_monthYValues;
  319. switch (this.selectedDataKey) {
  320. case "totalIndustrialValuePredict":
  321. base_data_monthYValues = base_data_monthFiltered.map((item) => [
  322. item.totalIndustrialValue,
  323. item.month,
  324. ]);
  325. break;
  326. case "taxableIncomePredict":
  327. base_data_monthYValues = base_data_monthFiltered.map((item) => [
  328. item.taxableIncome,
  329. item.month,
  330. ]);
  331. break;
  332. case "paidTaxPredict":
  333. base_data_monthYValues = base_data_monthFiltered.map((item) => [
  334. item.paidTax,
  335. item.month,
  336. ]);
  337. break;
  338. case "powerConsumePredict":
  339. base_data_monthYValues = base_data_monthFiltered.map((item) => [
  340. item.powerConsume,
  341. item.month,
  342. ]);
  343. break;
  344. case "energyConsumePredict":
  345. base_data_monthYValues = base_data_monthFiltered.map((item) => [
  346. item.energyConsume,
  347. item.month,
  348. ]);
  349. break;
  350. }
  351. // littlegreen - 获取预警的markArea
  352. function getErrorArray(flag, yValues, monthYValues, xValues) {
  353. if (flag == 0) {
  354. return [];
  355. }
  356. const errorArray = [];
  357. const error = new Decimal(that.errorVal);
  358. yValues.forEach((yValue) => {
  359. const yVal = new Decimal(yValue[0]);
  360. const month = yValue[1];
  361. // 在 base_data_monthYValues 中找到相应的月
  362. const baseData = monthYValues.find((base) => base[1] === month);
  363. if (baseData) {
  364. const baseVal = new Decimal(baseData[0]);
  365. // 计算差值
  366. const difference = yVal.minus(baseVal);
  367. // 如果差值大于10,添加该月到新数组
  368. if (difference.gt(error)) {
  369. // 10-90 80分成xValues.length份
  370. errorArray.push([
  371. {
  372. x:
  373. 10 +
  374. (80 / xValues.length) * xValues.indexOf(month + "月") +
  375. "%",
  376. },
  377. {
  378. x:
  379. 10 +
  380. (80 / xValues.length) *
  381. (xValues.indexOf(month + "月") + 1) +
  382. "%",
  383. },
  384. ]);
  385. }
  386. }
  387. });
  388. return errorArray;
  389. }
  390. // console.log(yValues, base_data_monthYValues, xValues);
  391. yValues.sort((a, b) => {
  392. let aValue = new Decimal(a[1]);
  393. let bValue = new Decimal(b[1]);
  394. return aValue.cmp(bValue); // cmp方法用于比较两个Decimal对象
  395. });
  396. base_data_monthYValues.sort((a, b) => {
  397. let aValue = new Decimal(a[1]);
  398. let bValue = new Decimal(b[1]);
  399. return aValue.cmp(bValue); // cmp方法用于比较两个Decimal对象
  400. });
  401. this.chart.setOption(
  402. {
  403. legend: {
  404. data: [
  405. this.selectedEnterprise +
  406. " " +
  407. this.selectedYear +
  408. "年" +
  409. this.keyToChineseTruth[this.selectedDataKey],
  410. this.selectedEnterprise +
  411. " " +
  412. this.selectedYear +
  413. "年" +
  414. this.keyToChinese[this.selectedDataKey],
  415. ],
  416. },
  417. tooltip: {
  418. trigger: "axis",
  419. },
  420. toolbox: {
  421. show: false,
  422. feature: {
  423. saveAsImage: {},
  424. },
  425. },
  426. xAxis: {
  427. type: "category",
  428. data: xValues,
  429. },
  430. yAxis: {
  431. type: "value",
  432. },
  433. series: [
  434. {
  435. name:
  436. this.selectedEnterprise +
  437. " " +
  438. this.selectedYear +
  439. "年" +
  440. this.keyToChineseTruth[this.selectedDataKey],
  441. // + this.keyToChinese[this.selectedDataKey],
  442. type: "line",
  443. smooth: true,
  444. data: base_data_monthYValues.map((value) => [
  445. xValues[value[1] - 1],
  446. value[0],
  447. ]),
  448. color: "#4CAF50",
  449. markArea: {
  450. itemStyle: {
  451. color: "rgba(255, 173, 177, 0.4)",
  452. },
  453. data: getErrorArray(
  454. flag,
  455. base_data_monthYValues,
  456. yValues,
  457. xValues
  458. ),
  459. },
  460. },
  461. {
  462. name:
  463. this.selectedEnterprise +
  464. " " +
  465. this.selectedYear +
  466. "年" +
  467. this.keyToChinese[this.selectedDataKey],
  468. type: "line",
  469. smooth: true,
  470. color: "#FF5722",
  471. data: yValues.map((value) => [
  472. xValues[Number(value[1]) - 1],
  473. value[0],
  474. ]),
  475. },
  476. ],
  477. }
  478. // {
  479. // tooltip: {
  480. // trigger: "axis",
  481. // axisPointer: {
  482. // type: "shadow",
  483. // },
  484. // },
  485. // legend: {
  486. // data: [
  487. // this.selectedEnterprise +
  488. // " " +
  489. // this.selectedYear +
  490. // "年" +
  491. // this.keyToChinese[this.selectedDataKey],
  492. // this.selectedEnterprise +
  493. // " " +
  494. // this.selectedYear +
  495. // "年" +
  496. // this.keyToChineseTruth[this.selectedDataKey],
  497. // ],
  498. // },
  499. // grid: {
  500. // left: "3%",
  501. // right: "4%",
  502. // bottom: "3%",
  503. // containLabel: true,
  504. // },
  505. // xAxis: {
  506. // type: "category",
  507. // data: xValues,
  508. // boundaryGap: true,
  509. // axisLabel: {
  510. // fontSize: 12,
  511. // },
  512. // },
  513. // yAxis: {
  514. // type: "value",
  515. // },
  516. // series: [
  517. // {
  518. // name:
  519. // this.selectedEnterprise +
  520. // " " +
  521. // this.selectedYear +
  522. // "年" +
  523. // this.keyToChinese[this.selectedDataKey],
  524. // type: "line",
  525. // data: yValues.map((value) => [
  526. // xValues[Number(value[1]) - 1],
  527. // value[0],
  528. // ]), // 根据多少月去获取对应的数据
  529. // // color: "#4CAF50",
  530. // itemStyle: {
  531. // normal: {
  532. // color: function (params) {
  533. // const value = params.data[1];
  534. // if (flag == 1) {
  535. // if (value >= that.errorVal)
  536. // //预警值有值
  537. // return "blue";
  538. // }
  539. // return "#4CAF50";
  540. // },
  541. // },
  542. // },
  543. // },
  544. // {
  545. // name:
  546. // this.selectedEnterprise +
  547. // " " +
  548. // this.selectedYear +
  549. // "年" +
  550. // this.keyToChineseTruth[this.selectedDataKey],
  551. // // + this.keyToChinese[this.selectedDataKey],
  552. // type: "line",
  553. // // data: base_data_monthYValues.map((value, index) => [base_data_monthXValues[index], value]),
  554. // data: base_data_monthYValues.map((value) => [
  555. // xValues[value[1] - 1],
  556. // value[0],
  557. // ]),
  558. // color: "#FF5722",
  559. // markArea: {
  560. // itemStyle: {
  561. // color: "rgba(255, 173, 177, 0.4)",
  562. // },
  563. // data: [
  564. // [
  565. // {
  566. // name: "Morning Peak",
  567. // x: "10%",
  568. // },
  569. // {
  570. // x: 10 + (80 / 12) * 5 + "%",
  571. // },
  572. // ],
  573. // ],
  574. // },
  575. // },
  576. // ],
  577. // }
  578. );
  579. },
  580. },
  581. watch: {
  582. // selectedDataKey() {
  583. // this.updateChart();
  584. // },
  585. // selectedYear() {
  586. // this.updateChart();
  587. // },
  588. // selectedEnterprise() {
  589. // this.updateChart();
  590. // },
  591. },
  592. };
  593. </script>
  594. <style scoped>
  595. .select-container {
  596. margin: 10px 5px;
  597. font-size: 16px;
  598. }
  599. .select-container select {
  600. padding: 10px 20px;
  601. margin-right: 10px;
  602. border: 1px solid #121315;
  603. border-radius: 4px;
  604. background-color: white;
  605. color: #121315;
  606. font-size: 16px;
  607. cursor: pointer;
  608. outline: none;
  609. }
  610. .select-container select:hover {
  611. border-color: #1a7cc8;
  612. }
  613. .select-container select:focus {
  614. border-color: #121315;
  615. box-shadow: 0 0 0 2px rgba(64, 159, 255, 0.2);
  616. }
  617. </style>