Browse Source

添加柱状图

liujiejie 11 months ago
parent
commit
0ab2937c10

+ 8 - 5
src/views/overview/components/analysis_information/index.vue

@@ -120,8 +120,9 @@ export default {
   },
   watch: {
     initBatchCode(newVal, oldVal) {
-      console.log(newVal, oldVal, "分析信息内容更新");
-      this.getFieldInfoMess();
+      if (newVal) {
+        this.getFieldInfoMess();
+      }
     },
   },
   mounted() {
@@ -133,7 +134,9 @@ export default {
     }
   },
   created() {
-    this.getFieldInfoMess();
+    if (this.initBatchCode !== "") {
+      this.getFieldInfoMess();
+    }
   },
   methods: {
     async getWindEngList() {
@@ -263,8 +266,8 @@ export default {
   width: 100%;
   height: 100%;
   overflow-y: scroll;
-   /* 滚动条整体样式 */
-   &::-webkit-scrollbar {
+  /* 滚动条整体样式 */
+  &::-webkit-scrollbar {
     width: 6px; /* 滚动条宽度 */
   }
 

+ 2 - 1
src/views/overview/components/fault_all/index.vue

@@ -1,7 +1,7 @@
 <!--
  * @Author: your name
  * @Date: 2025-01-13 13:56:55
- * @LastEditTime: 2025-01-17 09:23:56
+ * @LastEditTime: 2025-01-17 15:09:56
  * @LastEditors: bogon
  * @Description: In User Settings Edit
  * @FilePath: /performance-test/src/views/overview/components/fault_all/index.vue
@@ -52,6 +52,7 @@
         ></FaultAll>
         <template v-for="(itemCsv, indCsv) in zongFaultCsvData">
           <el-table
+            max-height="500"
             :key="indCsv + 'indCsv'"
             :data="filteredData(itemCsv)"
             border

+ 2 - 1
src/views/overview/components/fault_unit/index.vue

@@ -1,7 +1,7 @@
 <!--
  * @Author: your name
  * @Date: 2025-01-13 13:56:12
- * @LastEditTime: 2025-01-17 09:24:03
+ * @LastEditTime: 2025-01-17 15:09:32
  * @LastEditors: bogon
  * @Description: In User Settings Edit
  * @FilePath: /performance-test/src/views/overview/components/fault_unit/index.vue
@@ -51,6 +51,7 @@
         ></FaultUnit>
         <template v-for="itemCsv in fenFaultCsvData">
           <el-table
+            max-height="500"
             :data="filteredFenData(itemCsv)"
             border
             style="width: 100%"

+ 1 - 1
src/views/overview/components/power_curve/index.vue

@@ -1,7 +1,7 @@
 <!--
  * @Author: your name
  * @Date: 2025-01-09 18:10:08
- * @LastEditTime: 2025-01-17 09:24:38
+ * @LastEditTime: 2025-01-17 15:11:04
  * @LastEditors: bogon
  * @Description: In User Settings Edit
  * @FilePath: /performance-test/src/views/overview/components/power_curve/index.vue

+ 45 - 16
src/views/overview/components/power_scatter_2D/index.vue

@@ -1,7 +1,7 @@
 <!--
  * @Author: your name
  * @Date: 2025-01-09 18:10:35
- * @LastEditTime: 2025-01-17 09:25:08
+ * @LastEditTime: 2025-01-17 11:32:49
  * @LastEditors: bogon
  * @Description: In User Settings Edit
  * @FilePath: /performance-test/src/views/overview/components/power_scatter_2D/index.vue
@@ -36,18 +36,21 @@ export default {
           </div>
         </template>
         <div style="font-size: 12px; margin-top: 10px">
-          风向玫瑰图是一种表示某地不同风向出现频率分布的图表。
+          在风力发电机组中,功率曲线散点图是一种展示在不同风速下风机产生的功率的工具。这种图表通常以风速为横坐标,以发电机的功率输出为纵坐标。在理想情况下,这些散点应当形成一条清晰的曲线,反映出风速与发电机功率之间的关系。功率曲线是风力发电机性能的关键指标,可以用来评估风机在不同风速条件下的表现
         </div>
       </el-alert>
       <div class="titleCharts">分析分图 :</div>
       <div class="charts" v-if="diagramRelationsDatas">
         <template v-for="(itemChart, indChart) in diagramRelationsDatas">
-          <WindRoseChart
+          <powerMarkers2DCharts
             :key="itemChart.fieldEngineCode + indChart"
-            :inds="indChart"
+            :index="indChart"
+            @getResult="getResult"
+            @changeRequestNum="changeRequestNum"
+            :result="requestResult"
             :ref="itemChart.fieldEngineCode"
             :fileAddr="itemChart.fileAddr"
-          ></WindRoseChart>
+          ></powerMarkers2DCharts>
         </template>
       </div>
       <el-empty description="暂无分析记录" v-else></el-empty>
@@ -83,7 +86,7 @@ export default {
 </template>
 
 <script>
-import WindRoseChart from "@/views/performance/components/chartsCom/WindRoseChart.vue";
+import powerMarkers2DCharts from "@/views/performance/components/chartsCom/powerMarkers2DCharts.vue";
 import DicCard from "@/views/overview/components/dicCard/index.vue";
 import FilterChart from "@/views/overview/components/filterChart/index.vue";
 import TinymceEditor from "@/components/Tinymce.vue";
@@ -95,7 +98,7 @@ export default {
     DicCard,
     FilterChart,
     TinymceEditor,
-    WindRoseChart,
+    powerMarkers2DCharts,
   },
   props: {
     initBatchCode: {
@@ -124,6 +127,8 @@ export default {
       diagramRelationsDatas: [], //分图
       commentDescriptionVos: [], //评论列表
       editableTabs: [],
+      requestResult: [], // 请求结果
+      requestRecord: [],
     };
   },
   watch: {
@@ -144,6 +149,39 @@ export default {
     }
   },
   methods: {
+    getResult({ index, result }) {
+      console.log(index, result);
+      this.$set(this.requestResult, index, result);
+      // this.requestResult[index] = result
+      this.requestRecord[index] = result;
+    },
+    // 请求风机列表
+    async getWindEnfineList(batchCode, analysisTypeCode) {
+      // console.log("请求风机列表 分钟级");
+      const resEngineList = await queryAnalysisedEngine({
+        batchCode: batchCode,
+        analysisTypeCode,
+      });
+      this.windEngineGroupList = resEngineList.data;
+    },
+    changeRequestNum(index) {
+      if (index <= 1) {
+        this.$set(this.requestRecord, index, "start");
+        return;
+      }
+      if (index > 1) {
+        if (
+          this.requestRecord.every((item) =>
+            ["success", "error"].includes(item)
+          )
+        ) {
+          this.$set(this.requestRecord, index, "start");
+          this.$set(this.requestResult, index, "start");
+        } else {
+          this.$set(this.requestRecord, index, "start");
+        }
+      }
+    },
     async handleComment() {
       try {
         await analysisCommentEdit({
@@ -227,15 +265,6 @@ export default {
       }
     },
 
-    // 请求风机列表
-    async getWindEnfineList(batchCode, analysisTypeCode) {
-      // console.log("请求风机列表 分钟级");
-      const resEngineList = await queryAnalysisedEngine({
-        batchCode: batchCode,
-        analysisTypeCode,
-      });
-      this.windEngineGroupList = resEngineList.data;
-    },
     handleEditorInput(index, newVal) {
       // 更新对应的 comment 值
       // 如果该功能没有实现,可以删除这个方法

+ 386 - 3
src/views/overview/components/production_indicator_all/index.vue

@@ -1,17 +1,400 @@
 <!--
  * @Author: your name
  * @Date: 2025-01-13 13:45:50
- * @LastEditTime: 2025-01-13 13:46:07
+ * @LastEditTime: 2025-01-17 15:42:52
  * @LastEditors: bogon
  * @Description: In User Settings Edit
  * @FilePath: /performance-test/src/views/overview/components/production_indicator_all/index.vue
 -->
+
 <template>
-  <div>全场指标</div>
+  <div class="type-variable">
+    <!-- 全场指标 -->
+    <div class="left">
+      <FilterChart
+        :windList="windEngineGroupList"
+        @getEnfineList="getEnfineList"
+        @handlePrevious="handlePrevious"
+        @handleNext="handleNext"
+      ></FilterChart>
+      <el-alert type="info" :closable="false">
+        <template v-slot:title>
+          <div style="display: flex; align-items: center">
+            <i
+              class="el-icon-info"
+              style="font-size: 20px; margin-right: 5px"
+            ></i>
+            <h3>分析说明:</h3>
+          </div>
+        </template>
+        <div style="font-size: 12px; margin-top: 10px">
+          依据公司生产运行指标评价管理办法规定计算对应运行指标。
+        </div>
+      </el-alert>
+      <div v-if="productionIndicatorCsvData.length > 0">
+        <template v-for="(itemCsv, itemind) in productionIndicatorCsvData">
+          <!-- <Rader
+            :chartData="chantItem"
+            v-for="(chantItem, chartInd) in itemCsv.data"
+            :key="chartInd + 'chantItem'"
+          ></Rader> -->
+          <el-table
+            v-if="Object.keys(itemCsv.data[0]).length > 4"
+            :data="itemCsv.data"
+            :key="itemind + 'itemCsv'"
+            border
+            style="width: 100%"
+            max-height="500"
+            align="center"
+          >
+            <el-table-column prop="EPActualTotal" label="实发电量">
+            </el-table-column>
+            <el-table-column prop="TurbinePowerRate" label="风机能量利用率">
+            </el-table-column>
+            <el-table-column prop="EPLostStopPercent" label="停机损失百分比">
+            </el-table-column>
+            <el-table-column prop="EPLostBadPercent" label="欠发损失百分比">
+            </el-table-column>
+            <el-table-column
+              prop="EPLostPerformPercent"
+              label="功率曲线未达标损失百分比"
+            >
+            </el-table-column>
+            <el-table-column prop="EPLostLimitPercent" label="限电损失百分比">
+            </el-table-column>
+            <el-table-column prop="TurbineRunRate" label="风机可利用率">
+            </el-table-column>
+            <el-table-column prop="mean_width" label="功率水平平均宽度">
+            </el-table-column>
+            <el-table-column prop="variance_width" label="功率水平方差">
+            </el-table-column>
+            <el-table-column prop="WindSpeedAvr" label="平均风速">
+            </el-table-column>
+            <el-table-column prop="Thi" label="利用小时"> </el-table-column>
+            <el-table-column prop="Ws" label="功率曲线一致性系数">
+            </el-table-column>
+          </el-table>
+          <el-table
+            v-else
+            :data="itemCsv.data"
+            border
+            style="width: 100%"
+            align="center"
+          >
+            <el-table-column prop="Qp" label="风场总发电量"> </el-table-column>
+            <el-table-column prop="Thc" label="风场等效利用小时">
+            </el-table-column>
+            <el-table-column prop="Rdr" label="风场弃风率"> </el-table-column>
+            <el-table-column prop="Qdr" label="风场弃风电量"> </el-table-column>
+          </el-table>
+        </template>
+      </div>
+      <el-empty description="暂无分析记录" v-else></el-empty>
+      <el-tabs value="first">
+        <el-tab-pane label="意见描述" name="first">
+          <TinymceEditor
+            ref="editor"
+            v-model="comment"
+            @input="handleEditorInput($event)"
+            @onClick="onClick"
+          >
+          </TinymceEditor>
+        </el-tab-pane>
+      </el-tabs>
+      <el-row type="flex" class="row-bg" justify="end">
+        <el-col :span="2" style="margin: 20px">
+          <el-button type="primary" size="small" @click="handleComment"
+            >提交评论</el-button
+          >
+        </el-col>
+      </el-row>
+    </div>
+    <div class="right">
+      <DicCard
+        :batchCode="initBatchCode"
+        :analysisTypeCode="'production_indicator'"
+        :commentDescriptionVos="commentDescriptionVos"
+      >
+      </DicCard>
+    </div>
+  </div>
 </template>
 <script>
+import DicCard from "@/views/overview/components/dicCard/index.vue";
+import FilterChart from "@/views/overview/components/filterChart/index.vue";
+import Rader from "@/views/performance/components/chartsCom/Radar.vue";
+import TinymceEditor from "@/components/Tinymce.vue";
+import { analysisDetail, queryAnalysisedEngine } from "@/api/performance";
+import Papa from "papaparse";
+import axios from "axios";
 export default {
   name: "production_indicator_all",
+  components: {
+    DicCard,
+    FilterChart,
+    Rader,
+    TinymceEditor,
+  },
+  props: {
+    initBatchCode: {
+      default: "",
+      type: String,
+    },
+    analysisTypeCode: {
+      default: "",
+      type: String,
+    },
+    batchCodeList: {
+      default: "",
+      type: Array,
+    },
+  },
+  data() {
+    return {
+      searchFen: "",
+      form: {
+        value2: "",
+      },
+      commentDescriptionVos: [], //评论列表
+      windEngineGroupList: [], //批次风机列表
+      fieldEngineCodes: [], //选中风机
+      comment: "",
+      options: [],
+      zongFaultCsvHeader: [],
+      zongFaultCsvData: [],
+      productionIndicatorCsvData: [],
+      productionIndicatorCsvHeader: [],
+      editableTabs: [],
+    };
+  },
+  computed: {
+    // 根据搜索关键字过滤数据
+    filteredData() {
+      return (itemCsv) => {
+        // 如果有搜索关键词,则过滤数据
+        if (this.search) {
+          return itemCsv.data.filter((item) => {
+            return item.Qp.toLowerCase().includes(this.search.toLowerCase());
+          });
+        }
+        // 没有搜索关键词时返回所有数据
+        return itemCsv.data;
+      };
+    },
+    filteredFenData() {
+      return (itemCsv) => {
+        // 如果有搜索关键词,则过滤数据
+        if (this.searchFen) {
+          return itemCsv.data.filter((item) => {
+            return item.Qp.toLowerCase().includes(this.searchFen.toLowerCase());
+          });
+        }
+        // 没有搜索关键词时返回所有数据
+        return itemCsv.data;
+      };
+    },
+  },
+  watch: {
+    initBatchCode(newVal) {
+      if (newVal) {
+        this.fetchData(); // 调用合并后的函数
+      }
+    },
+    analysisTypeCode(newVal) {
+      if (newVal) {
+        this.fetchData(); // 调用合并后的函数
+      }
+    },
+  },
+  mounted() {
+    if (this.initBatchCode && this.analysisTypeCode) {
+      this.fetchData(); // 调用合并后的函数
+    }
+  },
+  methods: {
+    onSubmit() {
+      console.log("submit!");
+    },
+    async handleComment() {
+      try {
+        await analysisCommentEdit({
+          batchCode: this.initBatchCode,
+          analysisTypeCode: this.analysisTypeCode,
+          commentList: this.editableTabs.map((item) => {
+            return {
+              commentTypeCode: item.commentTypeCode,
+              comment: item.commentTypeName === "分析评论" ? this.comment : "",
+            };
+          }),
+        });
+        this.$message({
+          type: "success",
+          message: "保存成功",
+        });
+        this.comment = "";
+        this.getAnalysisDetail();
+      } catch (e) {
+        console.error(e);
+        this.loading = false;
+      }
+    },
+    // 封装的获取 CSV 数据方法
+    fetchCsvData(analysisType, url) {
+      axios
+        .get(url, { responseType: "blob" }) // 确保数据以 blob 格式返回
+        .then((response) => {
+          const reader = new FileReader();
+          reader.onload = (e) => {
+            const csvText = e.target.result;
+            Papa.parse(csvText, {
+              header: true, // 使用 CSV 第一行作为键
+              complete: (result) => {
+                // 根据分析类型设置不同的数据
+                if (analysisType === "production_indicator") {
+                  this.productionIndicatorCsvHeader.push(
+                    Object.keys(result.data[0])
+                  );
+                  console.log(result.data, "csvvvvv");
+                  this.productionIndicatorCsvData.push({
+                    data: result.data
+                      .filter((row) => Object.keys(row).length)
+                      .slice(0, result.data.length - 1),
+                  }); // 过滤空行
+                  console.log(this.productionIndicatorCsvData, "csvvvvv");
+                }
+              },
+              error: (error) => {
+                console.error("CSV 解析错误:", error);
+              },
+            });
+          };
+          reader.readAsText(response.data); // 读取 blob 数据
+        })
+        .catch((error) => {
+          console.error("无法获取 CSV 文件:", error);
+        });
+    },
+    // 合并后的函数,处理数据请求
+    async fetchData() {
+      try {
+        // 获取分析详情
+        await this.getAnalysisDetail();
+
+        // 获取风机列表
+        await this.getWindEnfineList(this.initBatchCode, this.analysisTypeCode);
+      } catch (err) {
+        console.error("Failed to fetch data:", err);
+      }
+    },
+    // 获取分析详情接口
+    async getAnalysisDetail() {
+      try {
+        const result = await analysisDetail({
+          batchCode: this.initBatchCode,
+          analysisTypeCode: "production_indicator",
+          fieldEngineCodes:
+            this.fieldEngineCodes.length === 0
+              ? undefined
+              : this.fieldEngineCodes.join(","),
+        });
+        if (
+          result.data &&
+          result.data[0] &&
+          result.data[0].commentTypeRelations
+        ) {
+          this.editableTabs = result.data[0].commentTypeRelations;
+        }
+        //当前评论展示获取
+        if (
+          result.data &&
+          result.data[0] &&
+          result.data[0].commentDescriptionVos
+        ) {
+          this.commentDescriptionVos = result.data[0].commentDescriptionVos;
+        }
+        const resFilter =
+          result.data &&
+          result.data.filter((items) => items.analysisTypeName === "全场指标");
+        console.log(resFilter, "resFilter");
+        if (resFilter && resFilter[0] && resFilter[0].generalFiles) {
+          resFilter[0].generalFiles.map((item) => {
+            if (item.fileAddr) {
+              this.productionIndicatorCsvHeader = [];
+              this.productionIndicatorCsvData = [];
+              this.fetchCsvData("production_indicator", item.fileAddr);
+            }
+          });
+        } else {
+          this.productionIndicatorCsvHeader = [];
+          this.productionIndicatorCsvData = [];
+        }
+      } catch (err) {
+        console.error("Failed to fetch analysis details:", err);
+      }
+    },
+    // 请求风机列表
+    async getWindEnfineList(batchCode, analysisTypeCode) {
+      // console.log("请求风机列表 分钟级");
+      const resEngineList = await queryAnalysisedEngine({
+        batchCode: batchCode,
+        analysisTypeCode,
+      });
+      this.windEngineGroupList = resEngineList?.data;
+    },
+    handleEditorInput(index, newVal) {
+      // 更新对应的 comment 值
+      // 如果该功能没有实现,可以删除这个方法
+    },
+    //获取选中风机list
+    getEnfineList(data) {
+      this.fieldEngineCodes = data;
+      this.getAnalysisDetail();
+    },
+    //下一条
+    handleNext() {
+      const index = this.batchCodeList.findIndex(
+        (item) => item === this.initBatchCode
+      );
+      if (index === this.batchCodeList.length - 1) {
+        this.$message.warning("已经是最后一个分析结果了");
+        return;
+      }
+      this.$emit("setInitBathCode", this.batchCodeList[index + 1]);
+    },
+    //上一条
+    handlePrevious() {
+      const index = this.batchCodeList.findIndex(
+        (item) => item === this.initBatchCode
+      );
+      if (index === 0) {
+        this.$message.warning("没有上一条了");
+        return;
+      }
+      this.$emit("setInitBathCode", this.batchCodeList[index - 1]);
+    },
+    onClick() {},
+  },
 };
 </script>
-<style scoped lang="scss"></style>
+
+<style scoped lang="scss">
+.type-variable {
+  display: flex;
+  height: 90%;
+  overflow: hidden;
+
+  .left {
+    width: 30%;
+    height: 100%;
+    overflow: auto;
+    padding: 20px;
+    flex: 1;
+  }
+
+  .right {
+    width: 250px;
+    height: 100%;
+    overflow: hidden;
+  }
+}
+</style>

+ 383 - 3
src/views/overview/components/production_indicator_unit/index.vue

@@ -1,17 +1,397 @@
 <!--
  * @Author: your name
  * @Date: 2025-01-13 13:44:56
- * @LastEditTime: 2025-01-13 13:45:23
+ * @LastEditTime: 2025-01-17 15:46:34
  * @LastEditors: bogon
  * @Description: In User Settings Edit
  * @FilePath: /performance-test/src/views/overview/components/production_indicator_unit/index.vue
 -->
 <template>
-  <div>机组指标</div>
+  <div class="type-variable">
+    <!-- 机组指标 -->
+    <div class="left">
+      <FilterChart
+        :windList="windEngineGroupList"
+        @getEnfineList="getEnfineList"
+        @handlePrevious="handlePrevious"
+        @handleNext="handleNext"
+      ></FilterChart>
+      <el-alert type="info" :closable="false">
+        <template v-slot:title>
+          <div style="display: flex; align-items: center">
+            <i
+              class="el-icon-info"
+              style="font-size: 20px; margin-right: 5px"
+            ></i>
+            <h3>分析说明:</h3>
+          </div>
+        </template>
+        <div style="font-size: 12px; margin-top: 10px">
+          对风场及机组各项运行指标进行统计,包括应发电量、实发电量、风机能量利用率,有效风速小时数,利用小时等指标。
+        </div>
+      </el-alert>
+      <div v-if="productionIndicatorCsvData.length > 0">
+        <template v-for="(itemCsv, itemind) in productionIndicatorCsvData">
+          <Rader
+            :chartData="chantItem"
+            v-for="(chantItem, chartInd) in itemCsv.data"
+            :key="chartInd + 'chantItem'"
+          ></Rader>
+          <el-table
+            max-height="500"
+            v-if="Object.keys(itemCsv.data[0]).length > 4"
+            :data="itemCsv.data"
+            border
+            style="width: 100%"
+            align="center"
+          >
+            <el-table-column prop="EPActualTotal" label="实发电量">
+            </el-table-column>
+            <el-table-column prop="TurbinePowerRate" label="风机能量利用率">
+            </el-table-column>
+            <el-table-column prop="EPLostStopPercent" label="停机损失百分比">
+            </el-table-column>
+            <el-table-column prop="EPLostBadPercent" label="欠发损失百分比">
+            </el-table-column>
+            <el-table-column
+              prop="EPLostPerformPercent"
+              label="功率曲线未达标损失百分比"
+            >
+            </el-table-column>
+            <el-table-column prop="EPLostLimitPercent" label="限电损失百分比">
+            </el-table-column>
+            <el-table-column prop="TurbineRunRate" label="风机可利用率">
+            </el-table-column>
+            <el-table-column prop="mean_width" label="功率水平平均宽度">
+            </el-table-column>
+            <el-table-column prop="variance_width" label="功率水平方差">
+            </el-table-column>
+            <el-table-column prop="WindSpeedAvr" label="平均风速">
+            </el-table-column>
+            <el-table-column prop="Thi" label="利用小时"> </el-table-column>
+            <el-table-column prop="Ws" label="功率曲线一致性系数">
+            </el-table-column>
+          </el-table>
+          <el-table
+            v-else
+            :data="itemCsv.data"
+            border
+            style="width: 100%"
+            align="center"
+          >
+            <el-table-column prop="Qp" label="风场总发电量"> </el-table-column>
+            <el-table-column prop="Thc" label="风场等效利用小时">
+            </el-table-column>
+            <el-table-column prop="Rdr" label="风场弃风率"> </el-table-column>
+            <el-table-column prop="Qdr" label="风场弃风电量"> </el-table-column>
+          </el-table>
+        </template>
+      </div>
+      <el-empty description="暂无分析记录" v-else></el-empty>
+      <el-tabs value="first">
+        <el-tab-pane label="意见描述" name="first">
+          <TinymceEditor
+            ref="editor"
+            v-model="comment"
+            @input="handleEditorInput($event)"
+            @onClick="onClick"
+          >
+          </TinymceEditor>
+        </el-tab-pane>
+      </el-tabs>
+      <el-row type="flex" class="row-bg" justify="end">
+        <el-col :span="2" style="margin: 20px">
+          <el-button type="primary" size="small" @click="handleComment"
+            >提交评论</el-button
+          >
+        </el-col>
+      </el-row>
+    </div>
+    <div class="right">
+      <DicCard
+        :batchCode="initBatchCode"
+        :analysisTypeCode="'production_indicator'"
+        :commentDescriptionVos="commentDescriptionVos"
+      >
+      </DicCard>
+    </div>
+  </div>
 </template>
 <script>
+import DicCard from "@/views/overview/components/dicCard/index.vue";
+import FilterChart from "@/views/overview/components/filterChart/index.vue";
+import TinymceEditor from "@/components/Tinymce.vue";
+import Rader from "@/views/performance/components/chartsCom/Radar.vue";
+import { analysisDetail, queryAnalysisedEngine } from "@/api/performance";
+import Papa from "papaparse";
+import axios from "axios";
 export default {
   name: "production_indicator_unit",
+  components: {
+    DicCard,
+    FilterChart,
+    Rader,
+    TinymceEditor,
+  },
+  props: {
+    initBatchCode: {
+      default: "",
+      type: String,
+    },
+    analysisTypeCode: {
+      default: "",
+      type: String,
+    },
+    batchCodeList: {
+      default: "",
+      type: Array,
+    },
+  },
+  data() {
+    return {
+      searchFen: "",
+      form: {
+        value2: "",
+      },
+      commentDescriptionVos: [], //评论列表
+      windEngineGroupList: [], //批次风机列表
+      fieldEngineCodes: [], //选中风机
+      comment: "",
+      options: [],
+      zongFaultCsvHeader: [],
+      zongFaultCsvData: [],
+      productionIndicatorCsvData: [],
+      productionIndicatorCsvHeader: [],
+      editableTabs: [],
+    };
+  },
+  computed: {
+    // 根据搜索关键字过滤数据
+    filteredData() {
+      return (itemCsv) => {
+        // 如果有搜索关键词,则过滤数据
+        if (this.search) {
+          return itemCsv.data.filter((item) => {
+            return item.wind_turbine_name
+              .toLowerCase()
+              .includes(this.search.toLowerCase());
+          });
+        }
+        // 没有搜索关键词时返回所有数据
+        return itemCsv.data;
+      };
+    },
+    filteredFenData() {
+      return (itemCsv) => {
+        // 如果有搜索关键词,则过滤数据
+        if (this.searchFen) {
+          return itemCsv.data.filter((item) => {
+            return item.wind_turbine_name
+              .toLowerCase()
+              .includes(this.searchFen.toLowerCase());
+          });
+        }
+        // 没有搜索关键词时返回所有数据
+        return itemCsv.data;
+      };
+    },
+  },
+  watch: {
+    initBatchCode(newVal) {
+      if (newVal) {
+        this.fetchData(); // 调用合并后的函数
+      }
+    },
+    analysisTypeCode(newVal) {
+      if (newVal) {
+        this.fetchData(); // 调用合并后的函数
+      }
+    },
+  },
+  mounted() {
+    if (this.initBatchCode && this.analysisTypeCode) {
+      this.fetchData(); // 调用合并后的函数
+    }
+  },
+  methods: {
+    onSubmit() {
+      console.log("submit!");
+    },
+    async handleComment() {
+      try {
+        await analysisCommentEdit({
+          batchCode: this.initBatchCode,
+          analysisTypeCode: this.analysisTypeCode,
+          commentList: this.editableTabs.map((item) => {
+            return {
+              commentTypeCode: item.commentTypeCode,
+              comment: item.commentTypeName === "分析评论" ? this.comment : "",
+            };
+          }),
+        });
+        this.$message({
+          type: "success",
+          message: "保存成功",
+        });
+        this.comment = "";
+        this.getAnalysisDetail();
+      } catch (e) {
+        console.error(e);
+        this.loading = false;
+      }
+    },
+    // 封装的获取 CSV 数据方法
+    fetchCsvData(analysisType, url) {
+      axios
+        .get(url, { responseType: "blob" }) // 确保数据以 blob 格式返回
+        .then((response) => {
+          const reader = new FileReader();
+          reader.onload = (e) => {
+            const csvText = e.target.result;
+            Papa.parse(csvText, {
+              header: true, // 使用 CSV 第一行作为键
+              complete: (result) => {
+                // 根据分析类型设置不同的数据
+                this.productionIndicatorCsvHeader.push(
+                  Object.keys(result.data[0])
+                );
+                this.productionIndicatorCsvData.push({
+                  data: result.data
+                    .filter((row) => Object.keys(row).length)
+                    .slice(0, result.data.length - 1),
+                }); // 过滤空行
+              },
+              error: (error) => {
+                console.error("CSV 解析错误:", error);
+              },
+            });
+          };
+          reader.readAsText(response.data); // 读取 blob 数据
+        })
+        .catch((error) => {
+          console.error("无法获取 CSV 文件:", error);
+        });
+    },
+    // 合并后的函数,处理数据请求
+    async fetchData() {
+      try {
+        // 获取分析详情
+        await this.getAnalysisDetail();
+
+        // 获取风机列表
+        await this.getWindEnfineList(this.initBatchCode, this.analysisTypeCode);
+      } catch (err) {
+        console.error("Failed to fetch data:", err);
+      }
+    },
+    // 获取分析详情接口
+    async getAnalysisDetail() {
+      try {
+        const result = await analysisDetail({
+          batchCode: this.initBatchCode,
+          analysisTypeCode: "production_indicator",
+          fieldEngineCodes:
+            this.fieldEngineCodes.length === 0
+              ? undefined
+              : this.fieldEngineCodes.join(","),
+        });
+        if (
+          result.data &&
+          result.data[0] &&
+          result.data[0].commentTypeRelations
+        ) {
+          this.editableTabs = result.data[0].commentTypeRelations;
+        }
+        //当前评论展示获取
+        if (
+          result.data &&
+          result.data[0] &&
+          result.data[0].commentDescriptionVos
+        ) {
+          this.commentDescriptionVos = result.data[0].commentDescriptionVos;
+        }
+        const resFilter =
+          result.data &&
+          result.data.filter((items) => items.analysisTypeName === "机组指标");
+        if (resFilter && resFilter[0] && resFilter[0].diagramRelations) {
+          resFilter[0].diagramRelations.map((item) => {
+            if (item.fileAddr) {
+              this.productionIndicatorCsvHeader = [];
+              this.productionIndicatorCsvData = [];
+              this.fetchCsvData("production_indicator", item.fileAddr);
+            }
+          });
+        } else {
+          this.productionIndicatorCsvHeader = [];
+          this.productionIndicatorCsvData = [];
+        }
+      } catch (err) {
+        console.error("Failed to fetch analysis details:", err);
+      }
+    },
+    // 请求风机列表
+    async getWindEnfineList(batchCode, analysisTypeCode) {
+      // console.log("请求风机列表 分钟级");
+      const resEngineList = await queryAnalysisedEngine({
+        batchCode: batchCode,
+        analysisTypeCode,
+      });
+      this.windEngineGroupList = resEngineList.data;
+    },
+    handleEditorInput(index, newVal) {
+      // 更新对应的 comment 值
+      // 如果该功能没有实现,可以删除这个方法
+    },
+    //获取选中风机list
+    getEnfineList(data) {
+      this.fieldEngineCodes = data;
+      this.getAnalysisDetail();
+    },
+    //下一条
+    handleNext() {
+      const index = this.batchCodeList.findIndex(
+        (item) => item === this.initBatchCode
+      );
+      if (index === this.batchCodeList.length - 1) {
+        this.$message.warning("已经是最后一个分析结果了");
+        return;
+      }
+      this.$emit("setInitBathCode", this.batchCodeList[index + 1]);
+    },
+    //上一条
+    handlePrevious() {
+      const index = this.batchCodeList.findIndex(
+        (item) => item === this.initBatchCode
+      );
+      if (index === 0) {
+        this.$message.warning("没有上一条了");
+        return;
+      }
+      this.$emit("setInitBathCode", this.batchCodeList[index - 1]);
+    },
+    onClick() {},
+  },
 };
 </script>
-<style scoped lang="scss"></style>
+
+<style scoped lang="scss">
+.type-variable {
+  display: flex;
+  height: 90%;
+  overflow: hidden;
+
+  .left {
+    width: 30%;
+    height: 100%;
+    overflow: auto;
+    padding: 20px;
+    flex: 1;
+  }
+
+  .right {
+    width: 250px;
+    height: 100%;
+    overflow: hidden;
+  }
+}
+</style>

+ 1 - 2
src/views/overview/components/wind_direction_frequency/index.vue

@@ -1,7 +1,7 @@
 <!--
  * @Author: your name
  * @Date: 2025-01-10 09:11:12
- * @LastEditTime: 2025-01-17 09:25:34
+ * @LastEditTime: 2025-01-17 16:21:27
  * @LastEditors: bogon
  * @Description: In User Settings Edit
  * @FilePath: /performance-test/src/views/overview/components/wind_direction_frequency/index.vue
@@ -212,7 +212,6 @@ export default {
           result.data[0].diagramRelations.filter((item) =>
             item.fileAddr.endsWith(".json")
           );
-        console.log(this.diagramRelationsDatas, "this.diagramRelationsDatas");
       } catch (err) {
         console.error("Failed to fetch analysis details:", err);
       }

+ 267 - 3
src/views/overview/components/wind_speed/index.vue

@@ -1,17 +1,281 @@
 <!--
  * @Author: your name
  * @Date: 2025-01-10 09:11:23
- * @LastEditTime: 2025-01-10 09:13:17
+ * @LastEditTime: 2025-01-17 16:57:43
  * @LastEditors: bogon
  * @Description: In User Settings Edit
  * @FilePath: /performance-test/src/views/overview/components/wind_spee/index.vue
 -->
+
 <template>
-  <div>风速均值分析</div>
+  <div class="type-variable">
+    <!-- 风速均值分析 -->
+    <div class="left">
+      <FilterChart
+        :windList="windEngineGroupList"
+        @getEnfineList="getEnfineList"
+        @handlePrevious="handlePrevious"
+        @handleNext="handleNext"
+      ></FilterChart>
+      <el-alert type="info" :closable="false">
+        <template v-slot:title>
+          <div style="display: flex; align-items: center">
+            <i
+              class="el-icon-info"
+              style="font-size: 20px; margin-right: 5px"
+            ></i>
+            <h3>分析说明:</h3>
+          </div>
+        </template>
+        <div style="font-size: 12px; margin-top: 10px">
+          风向玫瑰图是一种表示某地不同风向出现频率分布的图表。
+        </div>
+      </el-alert>
+
+      <div class="titleCharts">分析总图 :</div>
+      <div
+        class="charts"
+        v-if="generalFilesDatas && generalFilesDatas.length > 0"
+      >
+        <template v-for="(itemChart, indChart) in generalFilesDatas">
+          <BarChart
+            :key="itemChart.batchCode + indChart"
+            :inds="indChart"
+            :ref="itemChart.batchCode"
+            :fileAddr="itemChart.fileAddr"
+          ></BarChart>
+        </template>
+      </div>
+      <el-empty description="暂无分析记录" v-else></el-empty>
+
+      <el-tabs value="first">
+        <el-tab-pane label="意见描述" name="first">
+          <TinymceEditor
+            ref="editor"
+            v-model="comment"
+            @input="handleEditorInput($event)"
+            @onClick="onClick"
+          >
+          </TinymceEditor>
+        </el-tab-pane>
+      </el-tabs>
+      <el-row type="flex" class="row-bg" justify="end">
+        <el-col :span="2" style="margin: 20px">
+          <el-button type="primary" size="small" @click="handleComment"
+            >提交评论</el-button
+          >
+        </el-col>
+      </el-row>
+    </div>
+    <div class="right">
+      <DicCard
+        :batchCode="initBatchCode"
+        :analysisTypeCode="analysisTypeCode"
+        :commentDescriptionVos="commentDescriptionVos"
+      >
+      </DicCard>
+    </div>
+  </div>
 </template>
 <script>
+import DicCard from "@/views/overview/components/dicCard/index.vue";
+import FilterChart from "@/views/overview/components/filterChart/index.vue";
+import BarChart from "@/views/performance/components/chartsCom/BarChart.vue";
+import TinymceEditor from "@/components/Tinymce.vue";
+import { analysisDetail, queryAnalysisedEngine } from "@/api/performance";
+
 export default {
   name: "windSpee",
+  components: {
+    DicCard,
+    FilterChart,
+    BarChart,
+    TinymceEditor,
+  },
+  props: {
+    initBatchCode: {
+      default: "",
+      type: String,
+    },
+    analysisTypeCode: {
+      default: "",
+      type: String,
+    },
+    batchCodeList: {
+      default: "",
+      type: Array,
+    },
+  },
+  data() {
+    return {
+      form: {
+        value2: "",
+      },
+      comment: "",
+      options: [],
+      windEngineGroupList: [], //批次风机列表
+      fieldEngineCodes: [], //选中风机
+      generalFilesDatas: [], //总图
+      diagramRelationsDatas: [], //分图
+      commentDescriptionVos: [], //评论列表
+      editableTabs: [],
+    };
+  },
+  watch: {
+    initBatchCode(newVal) {
+      if (newVal) {
+        this.fetchData(); // 调用合并后的函数
+      }
+    },
+    analysisTypeCode(newVal) {
+      if (newVal) {
+        this.fetchData(); // 调用合并后的函数
+      }
+    },
+  },
+  mounted() {
+    if (this.initBatchCode && this.analysisTypeCode) {
+      this.fetchData(); // 调用合并后的函数
+    }
+  },
+  methods: {
+    async handleComment() {
+      try {
+        await analysisCommentEdit({
+          batchCode: this.initBatchCode,
+          analysisTypeCode: this.analysisTypeCode,
+          commentList: this.editableTabs.map((item) => {
+            return {
+              commentTypeCode: item.commentTypeCode,
+              comment: item.commentTypeName === "分析评论" ? this.comment : "",
+            };
+          }),
+        });
+        this.$message({
+          type: "success",
+          message: "保存成功",
+        });
+        this.comment = "";
+        this.getAnalysisDetail();
+      } catch (e) {
+        console.error(e);
+        this.loading = false;
+      }
+    },
+    onSubmit() {
+      console.log("submit!");
+    },
+    // 合并后的函数,处理数据请求
+    async fetchData() {
+      try {
+        // 获取分析详情
+        await this.getAnalysisDetail();
+        // 获取风机列表
+        await this.getWindEnfineList(this.initBatchCode, this.analysisTypeCode);
+      } catch (err) {
+        console.error("Failed to fetch data:", err);
+      }
+    },
+    // 获取分析详情接口
+    async getAnalysisDetail() {
+      try {
+        const result = await analysisDetail({
+          batchCode: this.initBatchCode,
+          analysisTypeCode: this.analysisTypeCode,
+          fieldEngineCodes:
+            this.fieldEngineCodes.length === 0
+              ? undefined
+              : this.fieldEngineCodes.join(","),
+        });
+        if (
+          result.data &&
+          result.data[0] &&
+          result.data[0].commentTypeRelations
+        ) {
+          this.editableTabs = result.data[0].commentTypeRelations;
+        }
+        //当前评论展示获取
+        if (
+          result.data &&
+          result.data[0] &&
+          result.data[0].commentDescriptionVos
+        ) {
+          this.commentDescriptionVos = result.data[0].commentDescriptionVos;
+        }
+        this.generalFilesDatas =
+          result.data && result.data[0] && result.data[0].generalFiles; //总图数据
+        this.diagramRelationsDatas =
+          result.data && result.data[0] && result.data[0].diagramRelations;
+      } catch (err) {
+        console.error("Failed to fetch analysis details:", err);
+      }
+    },
+
+    // 请求风机列表
+    async getWindEnfineList(batchCode, analysisTypeCode) {
+      // console.log("请求风机列表 分钟级");
+      const resEngineList = await queryAnalysisedEngine({
+        batchCode: batchCode,
+        analysisTypeCode,
+      });
+      this.windEngineGroupList = resEngineList.data;
+    },
+    handleEditorInput(index, newVal) {
+      // 更新对应的 comment 值
+      // 如果该功能没有实现,可以删除这个方法
+    },
+    //获取选中风机list
+    getEnfineList(data) {
+      this.fieldEngineCodes = data;
+      this.getAnalysisDetail();
+    },
+    //下一条
+    handleNext() {
+      const index = this.batchCodeList.findIndex(
+        (item) => item === this.initBatchCode
+      );
+      if (index === this.batchCodeList.length - 1) {
+        this.$message.warning("已经是最后一个分析结果了");
+        return;
+      }
+      this.$emit("setInitBathCode", this.batchCodeList[index + 1]);
+    },
+    //上一条
+    handlePrevious() {
+      const index = this.batchCodeList.findIndex(
+        (item) => item === this.initBatchCode
+      );
+      if (index === 0) {
+        this.$message.warning("没有上一条了");
+        return;
+      }
+      this.$emit("setInitBathCode", this.batchCodeList[index - 1]);
+    },
+    onClick() {},
+  },
 };
 </script>
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+.type-variable {
+  display: flex;
+  height: 90%;
+  overflow: hidden;
+  .left {
+    width: 30%;
+    height: 100%;
+    overflow: auto;
+    padding: 20px;
+    flex: 1;
+  }
+  .right {
+    width: 250px;
+    height: 100%;
+    overflow: hidden;
+  }
+}
+.titleCharts {
+  font-size: 16px;
+  font-weight: 500;
+  margin-top: 20px;
+}
+</style>

+ 60 - 9
src/views/overview/components/wind_speed_frequency/index.vue

@@ -1,7 +1,7 @@
 <!--
  * @Author: your name
  * @Date: 2025-01-10 09:11:34
- * @LastEditTime: 2025-01-17 09:26:04
+ * @LastEditTime: 2025-01-17 16:57:19
  * @LastEditors: bogon
  * @Description: In User Settings Edit
  * @FilePath: /performance-test/src/views/overview/components/wind_speed_frequency/index.vue
@@ -16,7 +16,37 @@
         @handlePrevious="handlePrevious"
         @handleNext="handleNext"
       ></FilterChart>
-      <BarChart></BarChart>
+      <el-alert type="info" :closable="false">
+        <template v-slot:title>
+          <div style="display: flex; align-items: center">
+            <i
+              class="el-icon-info"
+              style="font-size: 20px; margin-right: 5px"
+            ></i>
+            <h3>分析说明:</h3>
+          </div>
+        </template>
+        <div style="font-size: 12px; margin-top: 10px">
+          风向玫瑰图是一种表示某地不同风向出现频率分布的图表。
+        </div>
+      </el-alert>
+
+      <div class="titleCharts">分析分图 :</div>
+      <div
+        class="charts"
+        v-if="diagramRelationsDatas && diagramRelationsDatas.length > 0"
+      >
+        <template v-for="(itemChart, indChart) in diagramRelationsDatas">
+          <BarChart
+            :key="itemChart.fieldEngineCode + indChart"
+            :inds="indChart"
+            :ref="itemChart.fieldEngineCode"
+            :fileAddr="itemChart.fileAddr"
+          ></BarChart>
+        </template>
+      </div>
+      <el-empty description="暂无分析记录" v-else></el-empty>
+
       <el-tabs value="first">
         <el-tab-pane label="意见描述" name="first">
           <TinymceEditor
@@ -37,7 +67,12 @@
       </el-row>
     </div>
     <div class="right">
-      <DicCard></DicCard>
+      <DicCard
+        :batchCode="initBatchCode"
+        :analysisTypeCode="analysisTypeCode"
+        :commentDescriptionVos="commentDescriptionVos"
+      >
+      </DicCard>
     </div>
   </div>
 </template>
@@ -81,6 +116,8 @@ export default {
       fieldEngineCodes: [], //选中风机
       generalFilesDatas: [], //总图
       diagramRelationsDatas: [], //分图
+      commentDescriptionVos: [], //评论列表
+      editableTabs: [],
     };
   },
   watch: {
@@ -130,14 +167,8 @@ export default {
     // 合并后的函数,处理数据请求
     async fetchData() {
       try {
-        console.log(
-          this.initBatchCode,
-          this.analysisTypeCode,
-          "请求详情 分钟级"
-        );
         // 获取分析详情
         await this.getAnalysisDetail();
-
         // 获取风机列表
         await this.getWindEnfineList(this.initBatchCode, this.analysisTypeCode);
       } catch (err) {
@@ -155,6 +186,21 @@ export default {
               ? undefined
               : this.fieldEngineCodes.join(","),
         });
+        if (
+          result.data &&
+          result.data[0] &&
+          result.data[0].commentTypeRelations
+        ) {
+          this.editableTabs = result.data[0].commentTypeRelations;
+        }
+        //当前评论展示获取
+        if (
+          result.data &&
+          result.data[0] &&
+          result.data[0].commentDescriptionVos
+        ) {
+          this.commentDescriptionVos = result.data[0].commentDescriptionVos;
+        }
         this.generalFilesDatas =
           result.data && result.data[0] && result.data[0].generalFiles; //总图数据
         this.diagramRelationsDatas =
@@ -226,4 +272,9 @@ export default {
     overflow: hidden;
   }
 }
+.titleCharts {
+  font-size: 16px;
+  font-weight: 500;
+  margin-top: 20px;
+}
 </style>

+ 31 - 6
src/views/overview/components/yaw_error/index.vue

@@ -1,7 +1,7 @@
 <!--
  * @Author: your name
  * @Date: 2025-01-10 09:24:14
- * @LastEditTime: 2025-01-17 10:02:10
+ * @LastEditTime: 2025-01-17 16:58:03
  * @LastEditors: bogon
  * @Description: In User Settings Edit
  * @FilePath: /performance-test/src/views/overview/components/yaw_error/index.vue
@@ -31,12 +31,13 @@
           静态偏航误差过大,导致风轮不能精准对风,会造成发电损失,例如10°的静态偏航误差导致2.6%的发电损失,且风速较低的风场往往出现更多的发电损失;此外静态偏航误差过大,不仅会影响机组的功率输出,还会引起叶片气动弹性波动,增加机组的额外载荷。因此,定期检测机组的静态偏航误差,并及时校正,对于改善机组发电性能和机组运行安全都有很重要的意义。
         </div>
       </el-alert>
+      <div class="titleCharts">分析分图 :</div>
       <div v-if="zongFaultCsvData.length > 0">
         <FaultAll
           :faultTypes="
             zongFaultCsvData &&
             zongFaultCsvData[0] &&
-            zongFaultCsvData[0].data.map((item) => item.fault_detail)
+            zongFaultCsvData[0].data.map((item) => item.engine_name)
           "
           :faultCounts="
             zongFaultCsvData &&
@@ -50,12 +51,18 @@
           "
           :zongFaultCsvData="zongFaultCsvData"
         ></FaultAll>
+      </div>
+      <el-empty description="暂无分析记录" v-else></el-empty>
+
+      <div class="titleCharts">分析总图 :</div>
+      <div v-if="zongFaultCsvData.length > 0">
         <template v-for="(itemCsv, indCsv) in zongFaultCsvData">
           <el-table
             :key="indCsv + 'indCsv'"
             :data="filteredData(itemCsv)"
             border
             style="width: 100%"
+            max-height="500"
             align="center"
           >
             <el-table-column prop="engine_name" label="风机名称">
@@ -67,7 +74,7 @@
                 <el-input
                   v-model="search"
                   size="mini"
-                  placeholder="输入故障类型关键字搜索"
+                  placeholder="输入风机名称关键字搜索"
                 />
               </template>
             </el-table-column>
@@ -75,6 +82,7 @@
         </template>
       </div>
       <el-empty description="暂无分析记录" v-else></el-empty>
+
       <el-tabs value="first">
         <el-tab-pane label="意见描述" name="first">
           <TinymceEditor
@@ -159,7 +167,7 @@ export default {
         // 如果有搜索关键词,则过滤数据
         if (this.search) {
           return itemCsv.data.filter((item) => {
-            return item.fault_detail
+            return item.engine_name
               .toLowerCase()
               .includes(this.search.toLowerCase());
           });
@@ -241,7 +249,7 @@ export default {
                 // 根据分析类型设置不同的数据
                 if (analysisType === "yaw_error") {
                   if (Object.keys(result.data[0]).includes("engine_name")) {
-                    //总图故障统计展示
+                    //总图
                     this.zongFaultCsvHeader.push(Object.keys(result.data[0]));
                     this.zongFaultCsvData.push({
                       data: result.data
@@ -249,7 +257,7 @@ export default {
                         .slice(0, result.data.length - 1),
                     });
                   } else {
-                    //分机型故障统计处理
+                    //分
                     this.fenFaultCsvHeader.push(Object.keys(result.data[0]));
                     this.fenFaultCsvData.push({
                       data: result.data
@@ -322,6 +330,18 @@ export default {
           this.zongFaultCsvHeader = [];
           this.zongFaultCsvData = [];
         }
+        if (result.data && result.data[0] && result.data[0].diagramRelations) {
+          result.data[0].diagramRelations.map((item) => {
+            if (item.fileAddr) {
+              this.fenFaultCsvHeader = [];
+              this.fenFaultCsvData = [];
+              this.fetchCsvData("yaw_error", item.fileAddr);
+            }
+          });
+        } else {
+          this.fenFaultCsvHeader = [];
+          this.fenFaultCsvData = [];
+        }
       } catch (err) {
         console.error("Failed to fetch analysis details:", err);
       }
@@ -390,4 +410,9 @@ export default {
     overflow: hidden;
   }
 }
+.titleCharts {
+  font-size: 16px;
+  font-weight: 500;
+  margin-top: 20px;
+}
 </style>

+ 34 - 31
src/views/overview/components/yaw_error_density/index.vue

@@ -1,7 +1,7 @@
 <!--
  * @Author: your name
  * @Date: 2025-01-10 09:26:12
- * @LastEditTime: 2025-01-17 10:43:42
+ * @LastEditTime: 2025-01-17 10:51:47
  * @LastEditors: bogon
  * @Description: In User Settings Edit
  * @FilePath: /performance-test/src/views/overview/components/yaw_error_density/index.vue
@@ -27,24 +27,22 @@
           </div>
         </template>
         <div style="font-size: 12px; margin-top: 10px">
-          风能利用系数可以表示为变桨角度和叶尖速比的函数。每一个确定的桨距角,都存在一个最大风力利用系数,随着桨距角的变大,最大风力利用系数降低,其对应的叶尖速比也降低。此分析可以看出机组的发电情况表现
+          动态偏航误差分析是对机组偏航对风角度的分布情况进行统计及可视化展示,对风角度分布对称性较差的机组需要关注其偏航系统对风异常原因
         </div>
       </el-alert>
       <div class="charts" v-if="diagramRelationsDatas">
-        <RecycleScroller
-          class="scroller"
-          :items="diagramRelationsDatas"
-          :item-size="452"
-          key-field="fieldEngineCode"
-          v-slot="{ item: itemChart }"
-        >
+        <template v-for="(itemChart, indChart) in diagramRelationsDatas">
           <TwoDMarkersChart
             :key="itemChart.fieldEngineCode"
+            @getResult="getResult"
+            @changeRequestNum="changeRequestNum"
+            :result="requestResult"
+            :index="indChart"
             :ref="itemChart.fieldEngineCode"
             :fileAddr="itemChart.fileAddr"
           >
           </TwoDMarkersChart>
-        </RecycleScroller>
+        </template>
       </div>
       <el-empty description="暂无分析记录" v-else></el-empty>
       <el-tabs value="first">
@@ -163,6 +161,30 @@ export default {
         this.loading = false;
       }
     },
+    getResult({ index, result }) {
+      console.log(index, result);
+      this.$set(this.requestResult, index, result);
+      // this.requestResult[index] = result
+      this.requestRecord[index] = result;
+    },
+    changeRequestNum(index) {
+      if (index <= 1) {
+        this.$set(this.requestRecord, index, "start");
+        return;
+      }
+      if (index > 1) {
+        if (
+          this.requestRecord.every((item) =>
+            ["success", "error"].includes(item)
+          )
+        ) {
+          this.$set(this.requestRecord, index, "start");
+          this.$set(this.requestResult, index, "start");
+        } else {
+          this.$set(this.requestRecord, index, "start");
+        }
+      }
+    },
     onSubmit() {
       console.log("submit!");
     },
@@ -192,7 +214,7 @@ export default {
           fieldEngineCodes:
             this.fieldEngineCodes.length === 0
               ? undefined
-              : this.fieldEngineCodes,
+              : this.fieldEngineCodes.join(","),
         });
         if (
           result.data &&
@@ -234,7 +256,7 @@ export default {
     //获取选中风机list
     getEnfineList(data) {
       this.fieldEngineCodes = data;
-      console.log(this.fieldEngineCodes, "this.fieldEngineCodes");
+      this.getAnalysisDetail();
     },
     //下一条
     handleNext() {
@@ -281,24 +303,5 @@ export default {
     height: 100%;
     overflow: hidden;
   }
-
-  .charts {
-    // height: calc(100% - 150px);
-    height: 80vh;
-  }
-
-  .scroller {
-    height: 100%;
-
-    /* 隐藏垂直滚动条 */
-    &::-webkit-scrollbar {
-      width: 0;
-    }
-
-    /* 隐藏水平滚动条 */
-    &::-webkit-scrollbar {
-      height: 0;
-    }
-  }
 }
 </style>

+ 14 - 12
src/views/overview/index.vue

@@ -71,8 +71,8 @@
 
 <script>
 import { assetssType } from "./js/assetssType";
-import { queryAllAnalysisType, queryAnalysisedEngine } from "@/api/performance";
-import { getAnalysisCodeInfo, getFieldInfo } from "@/api/overview";
+import { queryAllAnalysisType } from "@/api/performance";
+import { getAnalysisCodeInfo } from "@/api/overview";
 export default {
   data() {
     return {
@@ -127,17 +127,19 @@ export default {
     // 获取树形结构数据
     getTreeData() {
       this.loading = true;
-      getAnalysisCodeInfo().then((res) => {
-        if (res.code === 200) {
-          this.loading = false;
-          this.batchCodeList = [];
-          const formattedData = this.formatData(res.data);
-          this.data = formattedData;
+      getAnalysisCodeInfo()
+        .then((res) => {
+          if (res.code === 200) {
+            this.loading = false;
+            this.batchCodeList = [];
+            const formattedData = this.formatData(res.data);
+            this.data = formattedData;
 
-          // 确保数据加载完成后才查询其他分析类型
-          this.queryAllAnalysisType();
-        }
-      });
+            // 确保数据加载完成后才查询其他分析类型
+            this.queryAllAnalysisType();
+          }
+        })
+        .catch((err) => {});
     },
     //数据格式化树形结构
     formatData(data) {

+ 80 - 49
src/views/performance/components/chartsCom/BarChart.vue

@@ -1,7 +1,7 @@
 <!--
  * @Author: your name
  * @Date: 2024-09-11 14:30:17
- * @LastEditTime: 2025-01-14 16:31:27
+ * @LastEditTime: 2025-01-17 16:56:02
  * @LastEditors: bogon
  * @Description: In User Settings Edit
  * @FilePath: /performance-test/src/views/performance/components/chartsCom/BarChart.vue
@@ -9,13 +9,7 @@
 
 <template>
   <div>
-    <!-- BarChart -->
-    <!-- <h1>风速频率分析</h1>
-    <h1>风速均值分析</h1>
-    <h1>额定风速分析</h1>
-    <h1>环境温度传感器分析2个总图</h1> -->
-    <!-- 颜色选择器 -->
-    <!-- 颜色选择器 -->
+    <!-- 图表控制面板 -->
     <div style="display: flex; align-items: center">
       <div style="margin-right: 20px; display: flex; align-items: center">
         <el-color-picker
@@ -26,8 +20,6 @@
         ></el-color-picker>
         <span style="margin-left: 10px">自定义颜色</span>
       </div>
-
-      <!-- 图表类型切换按钮 -->
       <div>
         <el-button size="small" @click="toggleChartType">
           切换为{{ chartType === "bar" ? "折线图" : "柱状图" }}
@@ -36,90 +28,129 @@
     </div>
 
     <!-- 图表容器 -->
-    <div id="bar-chart" style="width: 100%; height: 500px"></div>
+    <div :id="`bar-chart${inds}`" style="width: 100%; height: 500px"></div>
   </div>
 </template>
 
 <script>
+import { nextTick } from "vue"; // 导入 nextTick
 import Plotly from "plotly.js-dist";
+import axios from "axios";
+
 export default {
+  props: {
+    fileAddr: {
+      default: "",
+      type: String,
+    },
+    inds: {
+      type: Number,
+      default() {
+        return 0;
+      },
+    },
+  },
   data() {
     return {
       chartData: {
-        x: ["January", "February", "March", "April", "May"], // 横坐标标签
-        y: [10, 15, 8, 20, 18], // 每个柱或折线点的高度(值)
+        analysisTypeCode: "",
+        engineCode: "",
+        engineTypeName: "",
+        xaixs: "",
+        yaixs: "",
+        data: [],
       },
-      chartType: "bar", // 当前图表类型 ('bar' 或 'scatter'(折线图))
-      color1: "#406DAB", // 默认柱状图或折线颜色
-      normalRangeMin: 5, // 最低范围
-      normalRangeMax: 18, // 最高范围
+      chartType: "bar", // 当前图表类型 ('bar' 或 'scatter')
+      color1: "#406DAB", // 默认颜色
+      // normalRangeMin: 5, // 最低范围
+      // normalRangeMax: 18, // 最高范围
     };
   },
   mounted() {
-    this.drawChart();
+    this.getData();
   },
   methods: {
+    async getData() {
+      if (this.fileAddr !== "") {
+        try {
+          const resultChartsData = await axios.get(this.fileAddr);
+          this.chartData = resultChartsData.data;
+          // 使用 nextTick 来确保 DOM 渲染完成后绘制图表
+          nextTick(() => {
+            this.drawChart();
+          });
+          // this.$emit("getResult", { index: this.index, result: "success" });
+        } catch (error) {
+          console.error("Error loading data:", error);
+          // this.$emit("getResult", { index: this.index, result: "error" });
+        }
+      }
+    },
     // 绘制图表
     drawChart() {
+      if (!this.chartData.data.length) return;
+
+      const chartDataset = this.chartData.data[0];
       const trace = {
-        x: this.chartData.x, // 横坐标数据
-        y: this.chartData.y, // 纵坐标数据
+        x: chartDataset.xData, // 横坐标数据
+        y: chartDataset.yData, // 纵坐标数据
         type: this.chartType, // 当前图表类型 ('bar' 或 'scatter')
         marker: {
-          color: this.color1, // 默认颜色
+          color: this.color1, // 柱状图颜色
         },
         line: {
           color: this.color1, // 折线图颜色
         },
+        name: chartDataset.title || "数据", // 图例名称
       };
 
-      // Normal Range Line trace (LCL/UCL)
+      // Normal Range Lines
       const normalRangeLine = {
-        x: this.chartData.x, // 横坐标数据
-        y: Array(this.chartData.x.length).fill(this.normalRangeMin), // 设置正常范围的最低值
-        mode: "lines", // 仅显示为线
-        name: "LCL", // 图例名称(LCL)
-        showlegend: true, // 显示图例
+        x: chartDataset.xData, // 横坐标数据
+        y: Array(chartDataset.xData.length).fill(this.normalRangeMin), // 最低值线
+        mode: "lines",
+        name: "LCL", // 图例名称(最低范围)
         line: {
-          color: "red", // 线的颜色为红色
-          width: 2, // 线宽
-          dash: "dash", // 虚线样式
+          color: "red",
+          width: 2,
+          dash: "dash", // 虚线
         },
       };
 
       const normalRangeMaxLine = {
-        x: this.chartData.x, // 横坐标数据
-        y: Array(this.chartData.x.length).fill(this.normalRangeMax), // 设置正常范围的最大值
-        mode: "lines", // 仅显示为线
-        name: "UCL", // 图例名称(UCL)
-        showlegend: true, // 显示图例
+        x: chartDataset.xData, // 横坐标数据
+        y: Array(chartDataset.xData.length).fill(this.normalRangeMax), // 最高值线
+        mode: "lines",
+        name: "UCL", // 图例名称(最高范围)
         line: {
-          color: "red", // 线的颜色为红色
-          width: 2, // 线宽
-          dash: "dash", // 虚线样式
+          color: "red",
+          width: 2,
+          dash: "dash", // 虚线
         },
       };
 
       const layout = {
-        title: "Monthly Data", // 图表标题
+        title: this.chartData.analysisTypeCode || "图表", // 图表标题
         xaxis: {
-          title: "Month", // 横坐标标题
+          title: this.chartData.xaixs || "X轴", // 横坐标标题
         },
         yaxis: {
-          title: "Value", // 纵坐标标题
+          title: this.chartData.yaixs || "Y轴", // 纵坐标标题
         },
         margin: {
-          l: 50, // 左侧边距
-          r: 50, // 右侧边距
-          t: 50, // 上方边距
-          b: 50, // 下方边距
+          l: 50,
+          r: 50,
+          t: 50,
+          b: 50,
         },
       };
 
       // 渲染图表
       Plotly.newPlot(
-        "bar-chart",
-        [trace, normalRangeLine, normalRangeMaxLine],
+        `bar-chart${this.inds}`,
+
+        // [trace, normalRangeLine, normalRangeMaxLine],
+        [trace],
         layout,
         {
           responsive: true,
@@ -129,11 +160,11 @@ export default {
     // 切换图表类型
     toggleChartType() {
       this.chartType = this.chartType === "bar" ? "scatter" : "bar";
-      this.drawChart(); // 重新绘制图表
+      this.drawChart();
     },
     // 更新图表颜色
     updateChartColor() {
-      this.drawChart(); // 重新绘制图表
+      this.drawChart();
     },
   },
 };

+ 81 - 0
src/views/performance/components/chartsCom/Radar.vue

@@ -0,0 +1,81 @@
+<template>
+  <div>
+    <!-- Chart container -->
+    <div ref="radarChart" style="width: 100%; height: 400px"></div>
+    <!-- Button to trigger dynamic chart update -->
+  </div>
+</template>
+
+<script>
+import Plotly from "plotly.js-dist-min";
+
+export default {
+  props: {
+    chartData: {
+      type: Object,
+      default: {},
+    },
+  },
+  data() {
+    return {
+      //   chartData: {
+      //     Qp: "87955465.05833334",
+      //     Thc: "2094.177739484127",
+      //     Rdr: "12",
+      //     Qdr: "-87955465.05833334",
+      //     fs: "12333",
+      //   },
+    };
+  },
+  mounted() {
+    if (this.chartData) {
+      this.drawRadarChart();
+    }
+  },
+  methods: {
+    drawRadarChart() {
+      // 获取动态数据的 keys 和 values
+      const keys = Object.keys(this.chartData);
+      const values = Object.values(this.chartData).map((val) =>
+        val === "" ? 0 : parseFloat(val)
+      );
+
+      // 构造 Plotly 雷达图数据
+      const trace = {
+        type: "scatterpolar",
+        r: [...values, values[0]], // 闭合多边形,起点与终点一致
+        theta: [...keys, keys[0]], // 闭合多边形,起点与终点一致
+        fill: "toself", // 填充多边形区域
+        name: "多边形雷达图",
+        marker: {
+          color: "#636efc",
+        },
+        line: {
+          color: "#636efc", // 多边形边框颜色
+          width: 2, // 边框宽度
+        },
+      };
+
+      // 雷达图布局
+      const layout = {
+        polar: {
+          radialaxis: {
+            visible: true,
+            range: [Math.min(...values) * 1.1, Math.max(...values) * 1.1], // 设置范围
+          },
+          angularaxis: {
+            showline: false, // 隐藏 `angularaxis` 线
+            showticklabels: true, // 隐藏角度标签
+            ticks: "", // 隐藏刻度
+          },
+        },
+        showlegend: false, // 隐藏图例
+        title: "多边形雷达图",
+      };
+
+      // 渲染图表
+      Plotly.react(this.$refs.radarChart, [trace], layout);
+    },
+  },
+};
+</script>

+ 48 - 57
src/views/performance/components/chartsCom/TwoDMarkersChart.vue

@@ -1,8 +1,9 @@
 <template>
-  <div style="height: 452px">
+  <div style="min-height: 300px">
+    <div v-inview="handleDomInView"></div>
     <!-- 2D散点图 -->
-    <template>
-      <div style="display: flex; align-items: center; padding-top: 20px">
+    <template v-if="isShow === true">
+      <div style="display: flex; align-items: center; margin-top: 20px">
         <div style="margin-right: 20px; display: flex; align-items: center">
           <el-color-picker
             size="small"
@@ -20,16 +21,15 @@
           <el-button size="small" @click="setChartType('line')"
             >折线图</el-button
           >
-          <!-- <el-button size="small" @click="setChartType('bar')"
-            >柱状图</el-button
-          > -->
         </div>
       </div>
-      <!-- <div v-loading="$parent.requestRecord[index] === 'start'"> -->
-      <div v-loading="loading" ref="plotlyChart" style="height: 400px">
-        <el-empty v-if="isError" description="请求失败"></el-empty>
+      <div v-loading="$parent.requestRecord[index] === 'start'">
+        <el-empty
+          v-if="result[index] === 'error'"
+          description="请求失败"
+        ></el-empty>
+        <div v-else ref="plotlyChart" style="height: 400px"></div>
       </div>
-      <!-- </div> -->
     </template>
   </div>
 </template>
@@ -43,75 +43,66 @@ export default {
       default: "",
       type: String,
     },
+    index: {
+      type: Number,
+    },
+    result: {
+      type: Array,
+      default: [],
+    },
   },
   data() {
     return {
-      chartData: {
-        // analysisTypeCode: "动态偏航误差",
-        // engineCode: "WEM00026",
-        // engineTypeName: "",
-        // xaixs: "对风角度(度)",
-        // yaixs: "风速(m/s)",
-        // data: [
-        //   {
-        //     engineName: "#01",
-        //     engineCode: "WOG00935",
-        //     title: "动态偏航误差分析-#01",
-        //     xData: [
-        //       -15.4, -9.2, 4, 7, 9, -6.7, -5.2, -3.9, 0.9, 5.4, 6.4, 12.5, -8.1,
-        //       3.0, -4.5,
-        //     ],
-        //     yData: [
-        //       3.3, 3.7, 1290.0, 10, 7, 8, 858.0, 987.0, 1159.0, 1091.0, 979.0,
-        //       1023.5, 1210.1, 1105.4, 970.2,
-        //     ],
-        //     colorbartitle: "密度",
-        //   },
-        // ],
-      },
+      chartData: {},
       chartType: "scatter", // 初始化为散点图 (scatter)
       color1: "", // 默认颜色
       selectedPoints: [],
       originalColors: [],
       originalSizes: [],
-      loading: false,
-      isError: false,
-      cancelToken: null,
+      isShow: undefined,
     };
   },
-  async mounted() {
-    this.getData();
+  watch: {
+    result: {
+      deep: true,
+      handler(v) {
+        console.log("-----------------------", v);
+        const startIndex = this.$parent.requestRecord.findIndex(
+          (item) => item === "start"
+        );
+        if (startIndex > -1) {
+          if (startIndex === this.index) {
+            this.getData();
+          }
+        }
+      },
+    },
   },
-  beforeDestroy() {
-    this.cancelRequest();
+  async mounted() {
+    if (this.index === 0) {
+      this.getData();
+    }
   },
   methods: {
+    handleDomInView(isInView) {
+      if (isInView && this.isShow === undefined) {
+        this.isShow = isInView;
+        this.$emit("changeRequestNum", this.index);
+      }
+      console.log(this.isShow);
+    },
     async getData() {
       if (this.fileAddr !== "") {
         try {
-          this.loading = true;
-          this.cancelToken = axios.CancelToken.source();
-          console.log(this.cancelToken);
-          const resultChartsData = await axios.get(this.fileAddr, {
-            cancelToken: this.cancelToken.token,
-          });
-          console.log(resultChartsData);
+          const resultChartsData = await axios.get(this.fileAddr);
           this.chartData = resultChartsData.data;
           this.drawChart();
-          this.isError = false;
-          this.loading = false;
+          this.$emit("getResult", { index: this.index, result: "success" });
         } catch (error) {
-          // this.$emit("getResult", { index: this.index, result: "error" });
-          this.isError = true;
-          this.loading = false;
+          this.$emit("getResult", { index: this.index, result: "error" });
         }
       }
     },
-    cancelRequest() {
-      if (this.cancelToken) {
-        this.cancelToken.cancel("Request was cancelled");
-      }
-    },
     drawChart() {
       const data = this.chartData.data && this.chartData.data[0];
       let trace = {};

+ 356 - 10
src/views/performance/components/chartsCom/powerMarkers2DCharts.vue

@@ -1,19 +1,19 @@
 <!--
  * @Author: your name
  * @Date: 2024-09-11 14:32:12
- * @LastEditTime: 2024-10-09 09:08:19
+ * @LastEditTime: 2025-01-17 12:31:47
  * @LastEditors: bogon
  * @Description: In User Settings Edit
  * @FilePath: /performance-test/src/views/performance/components/chartsCom/powerMarkers2DCharts.vue
 -->
-<template>
-  <div>
-    powerMarkers2DCharts
-    <h1>逐月有功功率散点2D分析</h1>
-    <!-- <h1>偏航控制策略异常检测 2D</h1>  目前没找到这个分析类型-->
-    <div :id="`chart-${inds}`" style="width: 100%; height: 550px"></div>
-  </div>
-</template>
+<!-- <template>
+  <div> -->
+<!-- powerMarkers2DCharts
+    <h1>逐月有功功率散点2D分析</h1> -->
+<!-- <h1>偏航控制策略异常检测 2D</h1>  目前没找到这个分析类型-->
+<!-- <div :id="`chart-${inds}`" style="width: 100%; height: 550px"></div>
+  </div> -->
+<!-- </template>
 <script>
 import { allTypesDatas } from "@/utils/allTypesOfAnalysisData.js";
 import Plotly from "plotly.js-dist";
@@ -138,6 +138,352 @@ export default {
     },
   },
 };
+</script> -->
+
+<!-- <style scoped></style> -->
+<template>
+  <div style="min-height: 300px">
+    <div v-inview="handleDomInView"></div>
+    <!-- 2D散点图 -->
+    <template v-if="isShow === true">
+      <div style="display: flex; align-items: center; margin-top: 20px">
+        <div style="margin-right: 20px; display: flex; align-items: center">
+          <el-color-picker
+            size="small"
+            v-model="color1"
+            show-alpha
+            @change="updateChartColor"
+          ></el-color-picker>
+          <span style="margin-left: 10px">自定义颜色</span>
+        </div>
+      </div>
+      <div v-loading="$parent.requestRecord[index] === 'start'">
+        <el-empty
+          v-if="result[index] === 'error'"
+          description="请求失败"
+        ></el-empty>
+        <div v-else ref="plotlyChart" style="height: 400px"></div>
+      </div>
+    </template>
+  </div>
+</template>
+
+<script>
+import Plotly from "plotly.js-dist";
+import axios from "axios";
+
+export default {
+  props: {
+    fileAddr: {
+      default: "",
+      type: String,
+    },
+    index: {
+      type: Number,
+    },
+    result: {
+      type: Array,
+      default: [],
+    },
+  },
+  data() {
+    return {
+      chartData: {},
+      chartType: "scatter", // 默认显示散点图
+      color1: "", // 默认颜色
+      selectedPoints: [],
+      originalColors: [],
+      originalSizes: [],
+      isShow: undefined,
+    };
+  },
+  watch: {
+    result: {
+      deep: true,
+      handler(v) {
+        const startIndex = this.$parent.requestRecord.findIndex(
+          (item) => item === "start"
+        );
+        if (startIndex > -1 && startIndex === this.index) {
+          this.getData();
+        }
+      },
+    },
+  },
+  async mounted() {
+    if (this.index === 0) {
+      this.getData();
+    }
+  },
+  methods: {
+    handleDomInView(isInView) {
+      if (isInView && this.isShow === undefined) {
+        this.isShow = isInView;
+        this.$emit("changeRequestNum", this.index);
+      }
+      console.log(this.isShow);
+    },
+    async getData() {
+      if (this.fileAddr !== "") {
+        // try {
+        const resultChartsData = await axios.get(this.fileAddr);
+        this.chartData = resultChartsData.data;
+        console.log(this.chartData, "this.chartData");
+        this.drawChart();
+        this.$emit("getResult", { index: this.index, result: "success" });
+        // } catch (error) {
+        //   this.$emit("getResult", { index: this.index, result: "error" });
+        // }
+      }
+    },
+    drawChart() {
+      const data =
+        this.chartData.data &&
+        this.chartData.data.filter(
+          (item) => item.enginName !== "合同功率曲线"
+        )[0]; // 点数据
+      const lineData =
+        this.chartData.data &&
+        this.chartData.data.filter(
+          (item) => item.enginName === "合同功率曲线"
+        )[0]; // 线数据
+
+      let scatterTrace = {}; // 用于存放散点图的 trace
+      let lineTrace = {}; // 用于存放折线图的 trace
+
+      // 保存原始颜色和大小
+      this.originalColors = [...data.yData];
+      this.originalSizes = new Array(data.xData.length).fill(6); // 初始点大小
+
+      if (this.chartType === "scatter") {
+        // 散点图
+        scatterTrace = {
+          x: data.xData,
+          y: data.yData,
+          mode: "markers", // 散点
+          type: "scattergl", // 使用散点图
+          text: data.engineName, // 提示文本
+          marker: {
+            color: data.yData, // 根据 yData 设置颜色
+            colorscale: this.color1
+              ? [
+                  [0, "#F9FDD2"], // 颜色从 this.color1 开始
+                  [1, this.color1], // 结束颜色为其他颜色
+                ]
+              : [
+                  [0, "#F9FDD2"],
+                  [0.15, "#E9F6BD"],
+                  [0.3, "#C2E3B9"],
+                  [0.45, "#8AC8BE"],
+                  [0.6, "#5CA8BF"],
+                  [0.75, "#407DB3"],
+                  [0.9, "#2E4C9A"],
+                  [1, "#1B2973"],
+                ],
+            colorbar: {
+              title: data.colorbartitle, // 色标标题
+            },
+            size: new Array(data.xData.length).fill(6), // 初始点大小
+          },
+        };
+      }
+
+      // 折线图
+      lineTrace = {
+        x: lineData.xData, // 线数据的 xData
+        y: lineData.yData, // 线数据的 yData
+        mode: "lines+markers", // 线和点同时显示
+        type: "scattergl", // 使用 scattergl 类型
+        text: lineData.engineName, // 提示文本
+        line: {
+          // color: this.color1 || "red", // 使用自定义颜色
+          color: "red",
+        },
+      };
+
+      // 图表布局
+      const layout = {
+        title: data.title,
+        xaxis: {
+          title: this.chartData.xaixs,
+        },
+        yaxis: {
+          title: this.chartData.yaixs,
+        },
+        showlegend: false,
+      };
+
+      const config = {
+        modeBarButtonsToAdd: [
+          {
+            name: "选择",
+            icon: Plotly.Icons.pencil,
+            click: (gd) => this.handleSelectClick(gd),
+          },
+          {
+            name: "清除选中",
+            icon: Plotly.Icons.undo,
+            click: (gd) => this.handleClearSelect(gd),
+          },
+          {
+            name: "下载CSV文件",
+            icon: Plotly.Icons.disk,
+            click: (gd) => this.handleDownloadCSV(gd),
+          },
+        ],
+        modeBarButtonsToRemove: [
+          // 移除不需要的工具按钮
+          "lasso2d",
+        ],
+        displaylogo: false,
+        editable: true,
+        scrollZoom: false,
+      };
+
+      // 合并 scatter 和 line 数据
+      const traces = [];
+      if (scatterTrace) traces.push(scatterTrace); // 如果有散点数据
+      if (lineTrace) traces.push(lineTrace); // 如果有线图数据
+
+      // 使用 Plotly 绘制图表
+      Plotly.react(this.$refs.plotlyChart, traces, layout, config).then(() => {
+        // 确保在图表加载完成后设置工具栏按钮
+        const plotElement = this.$refs.plotlyChart;
+        Plotly.relayout(plotElement, layout); // 使用 relayout 来确保自定义按钮应用
+      });
+    },
+
+    handleSelectClick(gd) {
+      // 绑定 plotly_click 事件
+      gd.on("plotly_click", (data) => {
+        const pointIndex = data.points[0].pointIndex;
+        const xClick = data.points[0].x;
+        const yClick = data.points[0].y;
+
+        // 将点击的点添加到选中的点数组
+        this.selectedPoints.push({
+          x: xClick, // 点击点的 x 坐标
+          y: yClick, // 点击点的 y 坐标
+          index: pointIndex, // 点击点的索引
+          time: data.points[0].text, // 点击点的时间信息
+        });
+
+        // 初始化颜色和大小数组
+        let newColors = [...this.originalColors];
+        let newSize = [...this.originalSizes];
+
+        // 如果选中的点数大于等于3,进行多边形选择区域的处理
+        if (this.selectedPoints.length >= 3) {
+          const xv = this.selectedPoints.map((p) => p.x);
+          const yv = this.selectedPoints.map((p) => p.y);
+
+          // 判断点是否在多边形内
+          function inPolygon(x, y, xv, yv) {
+            let inside = false;
+            for (let i = 0, j = xv.length - 1; i < xv.length; j = i++) {
+              const intersect =
+                yv[i] > y !== yv[j] > y &&
+                x < ((xv[j] - xv[i]) * (y - yv[i])) / (yv[j] - yv[i]) + xv[i];
+              if (intersect) inside = !inside;
+            }
+            return inside;
+          }
+
+          // 用于跟踪已添加的 (x, y) 组合
+          const addedPoints = {};
+
+          // 遍历图表数据中的所有点,检查是否在多边形内
+          gd.data[0].x.forEach((xVal, i) => {
+            const yVal = gd.data[0].y[i];
+            if (inPolygon(xVal, yVal, xv, yv)) {
+              const pointKey = `${xVal}-${yVal}`;
+              if (!addedPoints[pointKey]) {
+                this.selectedPoints.push({
+                  x: gd.data[0].x[i],
+                  y: gd.data[0].y[i],
+                  time: gd.data[0].text[i],
+                });
+
+                newColors[i] = "red"; // 高亮选择的点
+                newSize[i] = 10; // 设置点的大小
+                addedPoints[pointKey] = true;
+              }
+            }
+          });
+        }
+
+        // 更新选中点的颜色和大小
+        this.selectedPoints.forEach((point) => {
+          newColors[point.index] = "red";
+          newSize[point.index] = 10;
+        });
+
+        // 使用 Plotly.restyle 更新颜色和大小
+        Plotly.restyle(gd, {
+          "marker.color": [newColors],
+          "marker.size": [newSize],
+        });
+
+        // 处理选中的数据
+        this.getSelectData(this.selectedPoints, gd.layout);
+      });
+    },
+
+    handleClearSelect(gd) {
+      this.selectedPoints = [];
+      Plotly.restyle(gd, {
+        "marker.color": [this.originalColors],
+        "marker.size": [this.originalSizes],
+      });
+    },
+    getSelectData(selectedPoints, layout) {
+      // 在这里处理选中的数据,您可以将其展示或导出等
+      console.log("选中的点数据:", selectedPoints);
+      console.log("布局信息:", layout);
+    },
+    handleDownloadCSV(gd) {
+      if (this.selectedPoints.length === 0) {
+        alert("没有选中的数据");
+        return;
+      }
+      this.downloadCSV();
+    },
+
+    downloadCSV() {
+      const headers = [this.chartData.xaixs, this.chartData.yaixs];
+      const csvRows = [headers]; // 保存标头
+      // 使用 Set 或 Map 去重
+      const uniquePoints = [];
+      this.selectedPoints.forEach((point) => {
+        if (!uniquePoints.some((p) => p.x === point.x && p.y === point.y)) {
+          uniquePoints.push(point);
+        }
+      });
+
+      // 将去重后的点加入 CSV 数据
+      uniquePoints.forEach((point) => {
+        csvRows.push(`${point.x},${point.y}`);
+      });
+
+      const csvString = csvRows.join("\n");
+      const blob = new Blob([csvString], { type: "text/csv; charset=utf-8" });
+      const url = URL.createObjectURL(blob);
+      const a = document.createElement("a");
+      a.href = url;
+      a.download = "selected_data.csv";
+      a.click();
+      URL.revokeObjectURL(url);
+    },
+    updateChartColor(color) {
+      // 更新图表颜色
+      this.color1 = color;
+      console.log(this.color1, "this.color1");
+      this.drawChart();
+    },
+  },
+};
 </script>
 
-<style scoped></style>
+<style scoped>
+/* 自定义样式 */
+</style>

+ 3 - 1
vue.config.js

@@ -64,8 +64,10 @@ module.exports = {
     proxy: {
       "/api": {
         // target: "http://192.168.5.4:16200", // 石月
-        target: "http://192.168.50.235:16200", // 内网
+        // target: "http://192.168.50.235:16200", // 内网
         // target: "http://192.168.5.15:16200",
+        target: "http://192.168.50.235:16500", //演示环境
+        // target: "http://106.120.102.238:", //外网演示环境
         // target: "http://106.120.102.238:16700", // 外网16700  生产16600
         // target: "http://10.96.137.5",
         changeOrigin: true,