Browse Source

开展招商辅助页面开发

littleblue55 1 month ago
parent
commit
ea6a9da6cf
1 changed files with 535 additions and 37 deletions
  1. 535 37
      src/views/investment/index.vue

+ 535 - 37
src/views/investment/index.vue

@@ -94,8 +94,8 @@
             新增企业近三年亩均产值
             <el-tooltip class="item" effect="light" placement="bottom">
               <span slot="content">
-                ①亩均产值=产值/用地面积<br/>
-                ②近三年:去年往前推三年,例如2025年时,计算年份为2024年、2023年、2022年<br/>
+                ①亩均产值=产值/用地面积<br />
+                ②近三年:去年往前推三年,例如2025年时,计算年份为2024年、2023年、2022年<br />
                 ③近三年平均亩均产值=(2022年亩均产值+2023年+2024年)/3
               </span>
               <i class="el-icon-question" /><!--小问号提示-->
@@ -118,8 +118,8 @@
             新增企业近三年亩均税收
             <el-tooltip class="item" effect="light" placement="bottom">
               <span slot="content">
-                ①亩均税收=实缴税金/用地面积<br/>
-                ②近三年:去年往前推三年,例如2025年时,计算年份为2024年、2023年、2022年<br/>
+                ①亩均税收=实缴税金/用地面积<br />
+                ②近三年:去年往前推三年,例如2025年时,计算年份为2024年、2023年、2022年<br />
                 ③近三年平均亩均税收=(2022年亩均税收+2023年+2024年)/3
               </span>
               <i class="el-icon-question" /><!--小问号提示-->
@@ -134,11 +134,7 @@
           </div>
         </el-form-item>
         <el-form-item label="行业代码" label-width="200px" prop="code">
-          <el-select
-            v-model="form.code"
-            style="width: 100%"
-            filterable
-          >
+          <el-select v-model="form.code" style="width: 100%" filterable>
             <el-option
               v-for="item in industryData"
               :key="item.key"
@@ -156,16 +152,8 @@
             >
             </el-option> </el-select> -->
         </el-form-item>
-        <el-form-item
-          label="企业分类"
-          label-width="200px"
-          prop="typeNum"
-        >
-          <el-select
-            v-model="form.typeNum"
-            style="width: 100%"
-            clearable
-          >
+        <el-form-item label="企业分类" label-width="200px" prop="typeNum">
+          <el-select v-model="form.typeNum" style="width: 100%" clearable>
             <el-option
               v-for="item in enterTypeList"
               :key="item.number"
@@ -197,7 +185,7 @@
               clearable
             />
           </div>
-        <!-- <el-select
+          <!-- <el-select
             v-model="form.year"
             style="width: 100%"
             filterable
@@ -212,12 +200,96 @@
           </el-select> -->
         </el-form-item>
         <el-form-item label-width="100px">
-          <el-button type="primary" size="mini" @click="submitForm">计算</el-button>
+          <el-button type="primary" size="mini" @click="submitForm"
+            >计算</el-button
+          >
           <el-button icon="el-icon-refresh" size="mini" @click="resetForm"
-            >重置</el-button>
+            >重置</el-button
+          >
         </el-form-item>
       </el-form>
     </div>
+    <div ref="chart" :style="{ height: height, width: width }"></div>
+    <el-table :data="tableData" border style="width: 100%">
+      <el-table-column prop="id" label="企业ID"></el-table-column>
+      <el-table-column prop="enterpriseName" label="企业名称"></el-table-column>
+      <el-table-column
+        prop="landUsedInRecentThreeYears"
+        label="近三年用地/亩"
+        width="200"
+      >
+      </el-table-column>
+      <el-table-column
+        prop="averageAnnualOutputValue"
+        label="年均产值/万元"
+        width="200"
+      ></el-table-column>
+      <el-table-column prop="output" label="年均亩均产值" width="200"
+        ><template slot="header">
+          <el-tooltip
+            class="item"
+            effect="dark"
+            content="近三年年均产值/近三年用地"
+            placement="top"
+          >
+            <span>年均亩均产值</span>
+          </el-tooltip>
+        </template>
+      </el-table-column>
+      <el-table-column prop="outputScore" label="亩均产值得分" width="200">
+        <template slot="header">
+          <el-tooltip
+            class="item"
+            effect="dark"
+            content="年均亩均产值/行业基准值A*40"
+            placement="top"
+          >
+            <span>亩均产值得分</span>
+          </el-tooltip>
+        </template></el-table-column
+      >
+      <el-table-column
+        prop="averageAnnualTaxPaid"
+        label="年均实缴税金/万元"
+        width="200"
+      ></el-table-column>
+      <el-table-column prop="tax" label="年均亩均税收" width="200"
+        ><template slot="header">
+          <el-tooltip
+            class="item"
+            effect="dark"
+            content="近三年年均实缴税金/近三年用地"
+            placement="top"
+          >
+            <span>年均亩均税收</span>
+          </el-tooltip>
+        </template>
+      </el-table-column>
+      <el-table-column prop="taxpaidScore" label="亩均税收得分" width="200">
+        <template slot="header">
+          <el-tooltip
+            class="item"
+            effect="dark"
+            content="年均亩均税收/行业基准值B*60"
+            placement="top"
+          >
+            <span>亩均税收得分</span>
+          </el-tooltip>
+        </template></el-table-column
+      >
+      <el-table-column prop="scoreTotal" label="企业综合得分" width="200"
+        ><template slot="header">
+          <el-tooltip
+            class="item"
+            effect="dark"
+            content="亩均产值得分+亩均税收得分"
+            placement="top"
+          >
+            <span>企业综合得分</span>
+          </el-tooltip>
+        </template>
+      </el-table-column>
+    </el-table>
   </div>
 </template>
 <script>
@@ -225,7 +297,14 @@ import { getYearData } from "@/api/home";
 import { listEtypeAll } from "@/api/etype/etype";
 import { listAllIndustry } from "@/api/industry/industry"; // 导入行业数据的接口
 import { analysisInvestment } from "@/api/analysis/analysis";
+
+import * as echarts from "echarts";
+require("echarts/theme/macarons"); // echarts theme
+import resize from "@/views/dashboard/mixins/resize";
+import { Decimal } from "decimal.js";
+
 export default {
+  mixins: [resize],
   props: {
     className: {
       type: String,
@@ -254,9 +333,16 @@ export default {
         averageTaxPerMuInRecentThreeYears: null, // 近三年亩均税收
       },
       rules: {},
-      industryData:[],
+      industryData: [],
       yearsOptions: [],
       enterTypeList: [],
+      BaseA: 0,
+      BaseB: 0,
+      BaseData: [],
+      chart: null,
+      chartData: [],
+      newScore: 0,
+      tableData: [],
     };
   },
   mounted() {
@@ -264,6 +350,11 @@ export default {
       this.fetchData();
     });
   },
+  beforeDestroy() {
+    if (this.chart) {
+      this.chart.dispose();
+    }
+  },
   methods: {
     fetchData() {
       Promise.all([
@@ -271,13 +362,13 @@ export default {
         this.getAllIndustry(),
         this.getEtypeAll(),
       ])
-      .then(() => {
-        console.log("All data fetched successfully");
-        // this.initChart();
-      })
-      .catch((error) => {
-        console.error("Error fetching data:", error);
-      });
+        .then(() => {
+          // console.log("All data fetched successfully");
+          this.initChart();
+        })
+        .catch((error) => {
+          console.error("Error fetching data:", error);
+        });
     },
     getYearData() {
       return getYearData().then((res) => {
@@ -305,16 +396,423 @@ export default {
       const item = this.enterTypeList.find((element) => element.number === num);
       return item ? item.name : null;
     },
-    submitForm(){
-      analysisInvestment(this.form).then(res=>{
-        console.log("提交",res)
-      })
-      // console.log("提交",this.form)
+    submitForm() {
+      // this.form
+      // {
+      //   annualOutputValue: null,
+      //   taxPaid: null,
+      //   landArea: null,
+      //   typeNum: null,
+      //   code: "3252",
+      //   year: 2025,
+      //   averageOutputValuePerMuInRecentThreeYears: 22,
+      //   averageTaxPerMuInRecentThreeYears: 22,
+      // }
+      analysisInvestment(this.form).then((res) => {
+        if (res && res?.code === 200 && res?.rows.length > 0) {
+          let data = res.rows[0];
+          this.BaseA = data.standardA;
+          this.BaseB = data.standardB;
+          this.BaseData = data.list;
+          // console.log(this.BaseData);
+          this.BaseData.push({
+            id: "新企业",
+            typeNum: this.form.typeNum,
+            enterpriseName: "新企业",
+            landUsedInRecentThreeYears: this.form.landArea,
+            averageAnnualOutputValue: this.form.annualOutputValue,
+            averageAnnualTaxPaid: this.form.taxPaid,
+          });
+          this.updateChart();
+        }
+      });
     },
     resetForm() {
       this.$refs.ruleForm.resetFields();
-      // this.calShow = "init";
     },
-  }
+    initChart() {
+      if (this.chart) {
+        this.chart.dispose();
+      }
+      this.chart = echarts.init(this.$refs.chart, "macarons");
+    },
+    updateChart() {
+      const that = this;
+      if (!that.chart) {
+        return;
+      }
+      // 更新图表数据
+      that.chartData = that.handleData(that.BaseData);
+      console.log(that.chartData);
+      let totalValues = that.chartData
+        .map((item) => item.scoreTotal)
+        .filter((value) => {
+          return Number.isFinite(value); // 只保留有限值
+        });
+      // console.log(totalValues);
+      let { min, max, avg, stdDev } = that.getBebeQ(totalValues);
+      const interval = Math.ceil((max - min) / 100);
+      const center = new Decimal(avg);
+
+      let minLimit = new Decimal(min); // 向下取整到最近的0.1
+      let maxLimit = new Decimal(max); // 向上取整到最近的0.1
+
+      // 确保 minLimit 和 maxLimit 关于 center 对称
+      if (!center.minus(minLimit).equals(maxLimit.minus(center))) {
+        const distanceToCenter = center
+          .minus(minLimit)
+          .gt(maxLimit.minus(center))
+          ? center.minus(minLimit)
+          : maxLimit.minus(center);
+
+        maxLimit = center.plus(distanceToCenter).ceil(); // 更新 maxLimit 保持对称
+        minLimit = center.minus(distanceToCenter).floor(); // 更新 minLimit 保持对称
+      }
+      const intervals = [];
+
+      // 创建区间
+      let current = minLimit;
+      let limit = maxLimit;
+      // console.log(maxLimit.toNumber(), that.newScore);
+      for (; current.lte(limit); current = current.plus(interval)) {
+        intervals.push(current.toFixed(2)); // 保留两位小数
+      }
+
+      // 确保包含最大值
+      if (!current.eq(maxLimit)) {
+        intervals.push(limit.toFixed(2)); // 将最大值添加到 interval 中
+      }
+      // console.log(intervals);
+      // 计算每个间隔的频次
+      const frequency = new Array(intervals.length - 1).fill(0); // 初始化频次数组
+
+      let intervalArr = intervals.slice(0, -1);
+      const intervalStrings = [];
+      for (let i = 0; i < intervalArr.length; i++) {
+        const start = new Decimal(intervalArr[i]).toFixed(0); // 保留一位小数
+        const end = new Decimal(intervalArr[i]).plus(interval).toFixed(0); // 保留一位小数
+        intervalStrings.push({
+          name: `${start}-${end}`,
+          max: end,
+          min: start,
+        }); // 构建区间字符串
+      }
+      // console.log(intervalStrings);
+      totalValues.forEach((value) => {
+        let v = new Decimal(value); // 将 value 转换为 Decimal
+        for (let j = 0; j < intervalStrings.length; j++) {
+          // 保证包含右边界
+          if (
+            v.gte(new Decimal(intervalStrings[j].min)) &&
+            v.lt(new Decimal(intervalStrings[j].max))
+          ) {
+            // if (value == that.newScore) {
+            //   console.log("newScore1", value);
+            // }
+            // console.log(v, intervalStrings[j].min, intervalStrings[j].max);
+            frequency[j]++;
+            break; // 找到对应区间后可以跳出循环
+          }
+        }
+      });
+
+      // 确保处理最大值等于 maxLimit 的情况
+      if (totalValues.some((value) => new Decimal(value).eq(maxLimit))) {
+        frequency[frequency.length - 1]++;
+      }
+
+      // console.log(intervals, intervalStrings, frequency);
+
+      const normalDistributionValues = intervals
+        .slice(0, -1)
+        .map((intervalStart, index) => {
+          const intervalEnd = intervals[index + 1];
+
+          // 将 intervalStart 和 intervalEnd 转换为 Decimal 实例
+          const midPointDecimal = new Decimal(intervalStart)
+            .plus(new Decimal(intervalEnd))
+            .dividedBy(2);
+
+          // 使用 Decimal 进行更高精度的计算
+          const avgDecimal = new Decimal(avg);
+          const stdDevDecimal = new Decimal(stdDev);
+
+          const exponent = midPointDecimal
+            .minus(avgDecimal)
+            .dividedBy(stdDevDecimal)
+            .pow(2)
+            .negated()
+            .dividedBy(new Decimal(2));
+
+          // 取自然指数
+          const exponentValue = new Decimal(Math.exp(exponent.toNumber()));
+
+          // 计算系数
+          const coefficient = new Decimal(1).dividedBy(
+            stdDevDecimal.times(new Decimal(Math.sqrt(2 * Math.PI)))
+          );
+
+          return coefficient.times(exponentValue).toNumber(); // 返回计算结果
+        });
+      const newScore = new Decimal(that.newScore);
+      // console.log(that.newScore, "newScore");
+      // console.log(intervals, intervalStrings, frequency);
+      const option = {
+        color: ["rgba(245,0,0,0.6)"],
+        dataZoom: [
+          {
+            type: "inside", // 内置于坐标系中
+            start: 0,
+            end: 100,
+            xAxisIndex: [0],
+          },
+        ],
+        tooltip: {
+          trigger: "axis",
+          axisPointer: {
+            type: "shadow",
+          },
+          formatter: (params) => {
+            // 使用箭头函数来保持 this 的上下文
+            const seriesName = params[0].name; // 系列名称
+            const dataIndex = params[0].dataIndex; // 数据索引
+            const value = params[0].data; // 实际的数据值
+            const xAxisLabel = params[0].axisValueLabel; // x轴的标签
+            const yAxisLabel = params[0].seriesName;
+            // 构建自定义的 tooltip 内容,包含年份
+            return `${xAxisLabel}</br> ${yAxisLabel}: ${value} `;
+          },
+        },
+        legend: {
+          data: [`企业数统计`, "正态分布"],
+        },
+        grid: {
+          left: "3%",
+          right: "4%",
+          bottom: "50px",
+          containLabel: true,
+        },
+        xAxis: {
+          type: "category",
+          name: "分布区间",
+          // data: histogramData.map((item) => item.name),
+          data: intervalStrings.map((item) => item.name),
+          axisLabel: {
+            fontSize: 12,
+            rotate: 0,
+            // padding: [0, 0, 0, -200]
+          },
+        },
+        yAxis: [
+          {
+            name: "企业数",
+            type: "value",
+            minInterval: 1,
+          },
+          {
+            name: "正态分布",
+            type: "value",
+          },
+        ],
+        series: [
+          {
+            name: `招商辅助`,
+            type: "bar",
+            barWidth: "30%",
+            itemStyle: {
+              // color: "rgba(245,0,0,0.6)",
+              normal: {
+                color: function (params) {
+                  const value = params.dataIndex; // 获取当前柱子在数据中的索引
+                  let min = new Decimal(intervalStrings[value].min);
+                  let max = new Decimal(intervalStrings[value].max);
+                  if (newScore.gte(min) && newScore.lt(max)) {
+                    return "#ffcc00";
+                  } else if (newScore.eq(maxLimit) && max.eq(maxLimit)) {
+                    return "#ffcc00";
+                  }
+                  return "rgba(245,0,0,0.6)";
+                  // 默认颜色
+                },
+              },
+            },
+            // data: histogramData.map((item) => item.value),
+            data: frequency,
+            label: {
+              show: false, // 将show属性设置为false,去掉柱子上的数字
+            },
+          },
+          {
+            name: "正态分布",
+            yAxisIndex: 1,
+            type: "line",
+            data: normalDistributionValues,
+            lineStyle: {
+              color: "#1a7cc8",
+            },
+            symbol: "none",
+          },
+        ],
+      };
+      this.chart.setOption(option);
+      this.chart.on("click", function (param) {
+        if (
+          typeof param.dataIndex === "number" &&
+          param.dataIndex >= 0 &&
+          param.dataIndex < intervalStrings.length
+        ) {
+          let dataIndexValue = intervalStrings[param.dataIndex];
+          const filteredArray = that.chartData.filter((item) => {
+            let score = new Decimal(item.scoreTotal);
+            let max = new Decimal(dataIndexValue.max);
+            let min = new Decimal(dataIndexValue.min);
+            let maxL = new Decimal(maxLimit);
+            const isMaxValEqual = max.equals(maxL);
+            const lowerBoundCheck = score.greaterThan(min) || score.equals(min);
+            const upperBoundCheck = isMaxValEqual
+              ? score.lessThan(max) || score.equals(max)
+              : score.lessThan(max);
+            return lowerBoundCheck && upperBoundCheck;
+          });
+          that.tableData = filteredArray.map((item) => {
+            return item;
+          });
+        }
+      });
+    },
+    // 计算亩均产值得分、亩均税收得分、企业综合得分
+    handleData(data) {
+      return data.map((item) => {
+        // 使用Decimal进行高精度计算
+        const averageAnnualOutputValue = new Decimal(
+          item.averageAnnualOutputValue || 0
+        );
+        const averageAnnualTaxPaid = new Decimal(
+          item.averageAnnualTaxPaid || 0
+        );
+        const landArea = new Decimal(item.landUsedInRecentThreeYears || 0);
+
+        // 计算亩均产值
+        let output = landArea.gt(0)
+          ? averageAnnualOutputValue.dividedBy(landArea)
+          : new Decimal(0);
+
+        // 计算亩均税收
+        let tax = landArea.gt(0)
+          ? averageAnnualTaxPaid.dividedBy(landArea)
+          : new Decimal(0);
+
+        if (item.id === "新企业") {
+          output = new Decimal(
+            this.form.averageOutputValuePerMuInRecentThreeYears
+          );
+          tax = new Decimal(this.form.averageTaxPerMuInRecentThreeYears);
+        }
+
+        // 计算得分
+        let outputScore = output.dividedBy(this.BaseA).times(40);
+        let taxpaidScore = tax.dividedBy(this.BaseB).times(60);
+
+        // 限制得分不超过最大值
+        outputScore = outputScore.gt(40) ? new Decimal(40) : outputScore;
+        taxpaidScore = taxpaidScore.gt(60) ? new Decimal(60) : taxpaidScore;
+
+        // 计算总分
+        const scoreTotal = outputScore.plus(taxpaidScore);
+
+        if (item.id === "新企业") {
+          this.newScore = scoreTotal.toNumber();
+        }
+
+        return {
+          ...item,
+          output: output.toNumber(),
+          tax: tax.toNumber(),
+          outputScore: outputScore.toNumber(),
+          taxpaidScore: taxpaidScore.toNumber(),
+          scoreTotal: scoreTotal.toNumber(),
+        };
+      });
+    },
+
+    // 获取基础数据:最大值,最小值,平均值,标准差
+    getBebeQ(numbers, digit = 2) {
+      let sum = numbers.reduce(
+        (acc, num) => acc.add(new Decimal(num)),
+        new Decimal(0)
+      );
+
+      let max = Math.max.apply(null, numbers);
+      let min = Math.min.apply(null, numbers);
+
+      // 平均值
+      let mean = sum.dividedBy(numbers.length);
+
+      // 计算每个数与均值的差的平方
+      const squaredDiffs = numbers.map((num) => {
+        const diff = new Decimal(num).minus(mean);
+        return diff.pow(2);
+      });
+
+      // 计算平方差的均值
+      const sumOfSquaredDiffs = squaredDiffs.reduce(
+        (acc, diff) => acc.add(diff),
+        new Decimal(0)
+      );
+
+      const variance = sumOfSquaredDiffs.dividedBy(numbers.length);
+
+      // 开平方得到标准差
+      const standardDeviation = variance.sqrt();
+
+      // 向上取整到最近的 0.1
+      const ceilingRoundedMax = Math.ceil(max * 10) / 10;
+
+      // 向下取整到最近的 0.1
+      const floorRoundedMin = Math.floor(min * 10) / 10;
+
+      return {
+        max: ceilingRoundedMax,
+        min: floorRoundedMin,
+        avg: parseFloat(mean.toNumber().toFixed(digit)) || 0,
+        stdDev: parseFloat(standardDeviation.toNumber().toFixed(digit)),
+      };
+    },
+    calculateMeanAndStdDev(data) {
+      const sum = data.reduce(
+        (acc, val) => acc.plus(new Decimal(val)),
+        new Decimal(0)
+      );
+      let mean = sum.dividedBy(data.length);
+
+      const variance = data
+        .reduce((acc, val) => {
+          const diff = new Decimal(val).minus(mean);
+          return acc.plus(diff.pow(2));
+        }, new Decimal(0))
+        .dividedBy(data.length);
+
+      let stdDev = variance.sqrt();
+      return { mean, stdDev };
+    },
+    calculateNormalDistribution(step, mean, stdDev) {
+      // const min = Math.min(...data);
+      // const max = Math.max(...data);
+      // const step = (max - min) / 100;
+      let arr = [];
+      for (let x = min; x <= max; x += step) {
+        const exponent = new Decimal(x)
+          .minus(mean)
+          .pow(2)
+          .dividedBy(new Decimal(2).times(stdDev.pow(2)))
+          .neg();
+        const pdf = new Decimal(1)
+          .dividedBy(stdDev.times(new Decimal(2).times(Math.PI).sqrt()))
+          .times(exponent.exp());
+        arr.push({ x, y: pdf.toNumber() });
+      }
+      return arr;
+    },
+  },
 };
 </script>