liujiejie 10 месяцев назад
Родитель
Сommit
af53d56f8d

+ 6 - 1
package-lock.json

@@ -19,7 +19,7 @@
         "element-ui": "^2.15.14",
         "happypack": "^5.0.1",
         "ol": "^9.2.3",
-        "plotly.js": "^2.34.0",
+        "papaparse": "^5.4.1",
         "plotly.js-dist": "^2.34.0",
         "plotly.js-dist-min": "^2.34.0",
         "plotly.js-with-locales": "^1.31.2",
@@ -14681,6 +14681,11 @@
       "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
       "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
     },
+    "node_modules/papaparse": {
+      "version": "5.4.1",
+      "resolved": "https://registry.npmmirror.com/papaparse/-/papaparse-5.4.1.tgz",
+      "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw=="
+    },
     "node_modules/param-case": {
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",

+ 1 - 1
package.json

@@ -19,7 +19,7 @@
     "element-ui": "^2.15.14",
     "happypack": "^5.0.1",
     "ol": "^9.2.3",
-    "plotly.js": "^2.34.0",
+    "papaparse": "^5.4.1",
     "plotly.js-dist": "^2.34.0",
     "plotly.js-dist-min": "^2.34.0",
     "plotly.js-with-locales": "^1.31.2",

+ 30 - 3
src/api/performance.js

@@ -1,7 +1,7 @@
 /*
  * @Author: your name
  * @Date: 2024-06-03 09:29:50
- * @LastEditTime: 2024-08-02 10:59:48
+ * @LastEditTime: 2024-09-09 15:52:22
  * @LastEditors: bogon
  * @Description: In User Settings Edit
  * @FilePath: /performance-test/src/api/performance。.js
@@ -201,9 +201,7 @@ export function uploadFile(data) {
     data,
   });
 }
-
 //富文本编辑,性能分析编辑描述
-
 export function analysisCommentEdit(data) {
   return request({
     url: "/energy-manage-service/analysisComment/analysisCommentEdit",
@@ -211,3 +209,32 @@ export function analysisCommentEdit(data) {
     data,
   });
 }
+//全部下载文件查询接口
+//下载逻辑---先调用这个接口获取所有的分析类型,再循环这些类型下载(下载调用这个方法接口downloadFile)该类型下的所有分析文件
+export function queryDownloadFile(data) {
+  return request({
+    url: "/energy-manage-service/analysis/queryDownloadFile",
+    method: "get",
+    params: data,
+  });
+}
+//按分析类型对分析结果下载
+export function downloadFile(data, onProgress) {
+  return request({
+    url: "/energy-manage-service/analysis/downloadFile",
+    method: "post",
+    data,
+    headers: {
+      "Content-Type": "multipart/form-data",
+    },
+    responseType: "blob", // 关键在这里,确保 Axios 返回 Blob 数据
+    onDownloadProgress: (progressEvent) => {
+      if (onProgress) {
+        const percentCompleted = Math.round(
+          (progressEvent.loaded * 100) / progressEvent.total
+        );
+        onProgress(percentCompleted);
+      }
+    },
+  }).then((response) => response.data); // 返回数据中的 Blob;
+}

+ 29 - 2
src/assets/js/plotly-locale-zh-cn.js

@@ -8,7 +8,6 @@ export const customModeBarButtons = [
       Plotly.downloadImage(gd, { format: "png", filename: "plotly_chart" });
     },
   },
-
   {
     name: "套索选择",
     icon: Plotly.Icons.lasso,
@@ -46,7 +45,7 @@ export const customModeBarButtons = [
   //     "question": {},问号
   //     "disk": {},保存
   //     "drawopenpath": {},//划线
-  //     "drawclosedpath": {},
+  //     "drawclosedpath": {},//小房子home
   //     "lasso": {},//套索
   //     "selectbox":
   {
@@ -71,3 +70,31 @@ export const customModeBarButtons = [
     },
   },
 ];
+// 3d_rotate
+// autoscale
+// camera
+// camera-retro
+// disk
+// drawcircle//套索圆圈选择
+// drawclosedpath//套索自由选择
+// drawline //线条 绘制
+// drawopenpath//自由画笔
+// drawrect //矩形框选
+// eraseshape
+// home
+// lasso
+// movie
+// newplotlylogo
+// pan
+// pencil//编辑
+// plotlylogo
+// question
+// selectbox
+// spikeline
+// tooltip_basic
+// tooltip_compare
+// undo
+// z-axis
+// zoom_minus
+// zoom_plus
+// zoombox

+ 0 - 116
src/locale/zh-CN.js

@@ -1,116 +0,0 @@
-var locale = {
-  moduleType: "locale",
-  name: "zh-CN",
-  dictionary: {
-    Autoscale: "自动缩放",
-    "Box Select": "矩形框选",
-    "Click to enter Colorscale title": "点击输入色阶的标题",
-    "Click to enter Component A title": "点击输入组件A的标题",
-    "Click to enter Component B title": "点击输入组件B的标题",
-    "Click to enter Component C title": "点击输入组件C的标题",
-    "Click to enter Plot title": "点击输入图表的标题",
-    "Click to enter X axis title": "点击输入X轴的标题",
-    "Click to enter Y axis title": "点击输入Y轴的标题",
-    "Compare data on hover": "悬停时比较数据",
-    "Double-click on legend to isolate one trace": "双击图例来突出显示对应轨迹",
-    "Double-click to zoom back out": "双击返回缩小显示",
-    "Download plot as a png": "保存图片",
-    "Download plot": "下载图表",
-    "Edit in Chart Studio": "在Chart Studio中编辑",
-    "IE only supports svg.  Changing format to svg.":
-      "IE只支持SVG。转换格式为SVG。",
-    "Lasso Select": "套索选择",
-    "Orbital rotation": "轨道旋转",
-    Pan: "平移",
-    "Produced with Plotly.js": "由Plotly.js生成",
-    Reset: "重置",
-    "Reset axes": "重置轴",
-    "Reset camera to default": "重置镜头视角为默认状态",
-    "Reset camera to last save": "重置镜头视角为上次保存状态",
-    "Reset view": "重置视图",
-    "Reset views": "重置视图",
-    "Show closest data on hover": "悬停时显示最近的数据",
-    "Snapshot succeeded": "生成快照成功",
-    "Sorry, there was a problem downloading your snapshot!":
-      "抱歉,下载快照出现问题!",
-    "Taking snapshot - this may take a few seconds":
-      "正在生成快照 - 可能需要几秒钟",
-    Zoom: "缩放",
-    "Zoom in": "放大",
-    "Zoom out": "缩小",
-    "close:": "关闭:",
-    "trace:": "轨迹:",
-    "lat:": "纬度:",
-    "lon:": "经度:",
-    "q1:": "第一四分位数:",
-    "q3:": "第三四分位数:",
-    "source:": "源:",
-    "target:": "目标:",
-    "lower fence:": "内侧栏(lower fence):",
-    "upper fence": "外侧栏(upper fence):",
-    "max:": "最大值:",
-    "mean ± σ:": "平均值 ± 标准差:",
-    "mean:": "平均值:",
-    "median:": "中位数:",
-    "min:": "最小值:",
-    "Turntable rotation": "旋转转盘:",
-    "Toggle Spike Lines": "切换显示数据点辅助线(Spike Lines)",
-    "open:": "打开:",
-    "high:": "高:",
-    "low:": "低:",
-    "Toggle show closest data on hover": "切换悬停时显示最近的数据点",
-    "incoming flow count:": "流入数量:",
-    "outgoing flow count:": "流出数量:",
-    "kde:": "kde:",
-    "Click to enter radial axis title": "点击输入径向轴标题",
-    "new text": "新建文本",
-  },
-  format: {
-    days: [
-      "星期日",
-      "星期一",
-      "星期二",
-      "星期三",
-      "星期四",
-      "星期五",
-      "星期六",
-    ],
-    shortDays: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
-    months: [
-      "一月",
-      "二月",
-      "三月",
-      "四月",
-      "五月",
-      "六月",
-      "七月",
-      "八月",
-      "九月",
-      "十月",
-      "十一月",
-      "十二月",
-    ],
-    shortMonths: [
-      "一",
-      "二",
-      "三",
-      "四",
-      "五",
-      "六",
-      "七",
-      "八",
-      "九",
-      "十",
-      "十一",
-      "十二",
-    ],
-    date: "%Y-%m-%d",
-  },
-};
-
-if (typeof Plotly === "undefined") {
-  window.PlotlyLocales = window.PlotlyLocales || [];
-  window.PlotlyLocales.push(locale);
-} else {
-  Plotly.register(locale);
-}

+ 201 - 136
src/utils/common.js

@@ -1,46 +1,46 @@
 export const uuid = (len = 16, radix = 10) => {
   const chars =
-    '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('')
-  const uuid = []
-  let i
-  radix = radix || chars.length
+    "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split("");
+  const uuid = [];
+  let i;
+  radix = radix || chars.length;
   if (len) {
     // Compact form
-    for (i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() * radix)]
+    for (i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() * radix)];
   } else {
     // rfc4122, version 4 form
-    let r
+    let r;
 
     // rfc4122 requires these characters
-    uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'
-    uuid[14] = '4'
+    uuid[8] = uuid[13] = uuid[18] = uuid[23] = "-";
+    uuid[14] = "4";
 
     // Fill in random data.  At i==19 set the high bits of clock sequence as
     // per rfc4122, sec. 4.1.5
     for (i = 0; i < 36; i++) {
       if (!uuid[i]) {
-        r = 0 | (Math.random() * 16)
-        uuid[i] = chars[i === 19 ? (r & 0x3) | 0x8 : r]
+        r = 0 | (Math.random() * 16);
+        uuid[i] = chars[i === 19 ? (r & 0x3) | 0x8 : r];
       }
     }
   }
-  return uuid.join('')
-}
+  return uuid.join("");
+};
 
 export const copy = (data) => {
-  var copy = document.createElement('p')
-  copy.innerText = data
-  document.body.append(copy)
-  var range = document.createRange()
-  range.selectNode(copy)
-  var selection = window.getSelection()
+  var copy = document.createElement("p");
+  copy.innerText = data;
+  document.body.append(copy);
+  var range = document.createRange();
+  range.selectNode(copy);
+  var selection = window.getSelection();
   if (selection) {
-    if (selection.rangeCount > 0) selection.removeAllRanges()
-    selection.addRange(range)
+    if (selection.rangeCount > 0) selection.removeAllRanges();
+    selection.addRange(range);
   }
-  document.execCommand('copy')
-  document.body.removeChild(copy)
-}
+  document.execCommand("copy");
+  document.body.removeChild(copy);
+};
 
 /**
  *
@@ -52,22 +52,22 @@ export const copy = (data) => {
  * @description 根据圆心坐标、旋转角度计算旋转后的坐标
  */
 export const getRotatePosition = (positionData) => {
-  const radian = (Math.PI / 180) * positionData.rotate
+  const radian = (Math.PI / 180) * positionData.rotate;
   return {
     x:
       (positionData.originPosition.x - positionData.centerPosition.x) *
-      Math.cos(radian) -
+        Math.cos(radian) -
       (positionData.originPosition.y - positionData.centerPosition.y) *
-      Math.sin(radian) +
+        Math.sin(radian) +
       positionData.centerPosition.x,
     y:
       (positionData.originPosition.y - positionData.centerPosition.y) *
-      Math.cos(radian) +
+        Math.cos(radian) +
       (positionData.originPosition.x - positionData.centerPosition.x) *
-      Math.sin(radian) +
-      positionData.centerPosition.y
-  }
-}
+        Math.sin(radian) +
+      positionData.centerPosition.y,
+  };
+};
 
 /**
  * @description 防抖
@@ -76,15 +76,15 @@ export const getRotatePosition = (positionData) => {
  * @returns
  */
 export function _debounce(fn, wait = 500) {
-  let timer
+  let timer;
   return function () {
-    const context = this
-    const args = arguments
-    if (timer) clearTimeout(timer)
+    const context = this;
+    const args = arguments;
+    if (timer) clearTimeout(timer);
     timer = setTimeout(() => {
-      fn.apply(context, args)
-    }, wait)
-  }
+      fn.apply(context, args);
+    }, wait);
+  };
 }
 
 /**
@@ -94,20 +94,20 @@ export function _debounce(fn, wait = 500) {
  * @returns
  */
 export function _throttle(fn, wait = 500) {
-  let last, timer, now
+  let last, timer, now;
   return function () {
-    now = Date.now()
+    now = Date.now();
     if (last && now - last < wait) {
-      clearTimeout(timer)
+      clearTimeout(timer);
       timer = setTimeout(function () {
-        last = now
-        fn.call(this, ...arguments)
-      }, wait)
+        last = now;
+        fn.call(this, ...arguments);
+      }, wait);
     } else {
-      last = now
-      fn.call(this, ...arguments)
+      last = now;
+      fn.call(this, ...arguments);
     }
-  }
+  };
 }
 
 /**
@@ -118,34 +118,34 @@ export function _throttle(fn, wait = 500) {
 export const hasPermission = (permissionList, routerName) => {
   return permissionList.some((permission) => {
     if (permission.code === routerName) {
-      return true
+      return true;
     } else {
       return (
         permission.children &&
         permission.children.length &&
         permission.children.find((child) => {
           if (child.code === routerName) {
-            return true
+            return true;
           }
         })
-      )
+      );
     }
-  })
-}
+  });
+};
 
 // 后端返回二进制流下载文件
-export const downloadBlob = (blob, fileName = 'download') => {
-  const reader = new FileReader()
-  reader.readAsDataURL(blob)
+export const downloadBlob = (blob, fileName = "download") => {
+  const reader = new FileReader();
+  reader.readAsDataURL(blob);
   reader.onload = (e) => {
-    const a = document.createElement('a')
-    a.download = fileName
-    a.href = e.target.result
-    document.body.appendChild(a)
-    a.click()
-    document.body.removeChild(a)
-  }
-}
+    const a = document.createElement("a");
+    a.download = fileName;
+    a.href = e.target.result;
+    document.body.appendChild(a);
+    a.click();
+    document.body.removeChild(a);
+  };
+};
 
 /**
  * @description base64下载word
@@ -155,92 +155,92 @@ export const downloadWord = (base64Data, filename) => {
   // 创建Blob对象
   var blob = base64ToBlob(
     base64Data,
-    'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
-  )
+    "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
+  );
 
   // 创建下载链接
-  var url = URL.createObjectURL(blob)
-  var downloadLink = document.createElement('a')
+  var url = URL.createObjectURL(blob);
+  var downloadLink = document.createElement("a");
 
   // 设置下载链接属性
-  downloadLink.href = url
-  downloadLink.download = filename
+  downloadLink.href = url;
+  downloadLink.download = filename;
 
   // 触发下载
-  document.body.appendChild(downloadLink)
-  downloadLink.click()
-  document.body.removeChild(downloadLink)
-  URL.revokeObjectURL(url) // 清理
-}
+  document.body.appendChild(downloadLink);
+  downloadLink.click();
+  document.body.removeChild(downloadLink);
+  URL.revokeObjectURL(url); // 清理
+};
 
 export const base64ToBlob = (base64, mimeType) => {
   // 解码Base64字符串
-  var byteCharacters = atob(base64.replace(/^data:\w+\/\w+;base64,/, ''))
+  var byteCharacters = atob(base64.replace(/^data:\w+\/\w+;base64,/, ""));
 
   // 将解码的字符串转换为类型化数组
-  var byteNumbers = new Array(byteCharacters.length)
+  var byteNumbers = new Array(byteCharacters.length);
   for (var i = 0; i < byteCharacters.length; i++) {
-    byteNumbers[i] = byteCharacters.charCodeAt(i)
+    byteNumbers[i] = byteCharacters.charCodeAt(i);
   }
 
   // 创建Blob对象
-  var byteArray = new Uint8Array(byteNumbers)
-  return new Blob([byteArray], { type: mimeType })
-}
+  var byteArray = new Uint8Array(byteNumbers);
+  return new Blob([byteArray], { type: mimeType });
+};
 
 export const downloadExcel = (base64Data, filename, type) => {
   const blobs = type
-    ? 'application/vnd.ms-excel'
-    : 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+    ? "application/vnd.ms-excel"
+    : "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
   // 创建Blob对象
-  var blob = base64ToBlob(base64Data, blobs)
+  var blob = base64ToBlob(base64Data, blobs);
 
   // 创建下载链接
-  var url = URL.createObjectURL(blob)
-  var downloadLink = document.createElement('a')
+  var url = URL.createObjectURL(blob);
+  var downloadLink = document.createElement("a");
 
   // 设置下载链接属性
-  downloadLink.href = url
-  downloadLink.download = filename
+  downloadLink.href = url;
+  downloadLink.download = filename;
 
   // 触发下载
-  document.body.appendChild(downloadLink)
-  downloadLink.click()
-  document.body.removeChild(downloadLink)
-  URL.revokeObjectURL(url) // 清理
-}
+  document.body.appendChild(downloadLink);
+  downloadLink.click();
+  document.body.removeChild(downloadLink);
+  URL.revokeObjectURL(url); // 清理
+};
 // 时间戳转化为时间
 export function convertTimestamp(timestamp) {
-  const date = new Date(timestamp)
-  const year = date.getFullYear()
-  const month = String(date.getMonth() + 1).padStart(2, '0')
-  const day = String(date.getDate()).padStart(2, '0')
-  const hours = String(date.getHours()).padStart(2, '0')
-  const minutes = String(date.getMinutes()).padStart(2, '0')
-  const seconds = String(date.getSeconds()).padStart(2, '0')
-  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
+  const date = new Date(timestamp);
+  const year = date.getFullYear();
+  const month = String(date.getMonth() + 1).padStart(2, "0");
+  const day = String(date.getDate()).padStart(2, "0");
+  const hours = String(date.getHours()).padStart(2, "0");
+  const minutes = String(date.getMinutes()).padStart(2, "0");
+  const seconds = String(date.getSeconds()).padStart(2, "0");
+  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
 }
 // 格式化时间
 export function formatDate(timestamp, formate) {
-  const date = new Date()
-  date.setTime(parseInt(timestamp))
+  const date = new Date();
+  date.setTime(parseInt(timestamp));
   //  传入formate形式 yyyy-MM-dd yyyy-MM-dd hh:mm
-  formate = formate != null ? formate : 'yyyy-MM-dd'
+  formate = formate != null ? formate : "yyyy-MM-dd";
   const Format = function (fmt) {
     var o = {
-      'M+': date.getMonth() + 1, // 月
-      'd+': date.getDate(), // 日
-      'h+': date.getHours(), // 小时
-      'm+': date.getMinutes(), // 分
-      's+': date.getSeconds(), // 秒
-      'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
-      S: date.getMilliseconds() // 毫秒
-    }
+      "M+": date.getMonth() + 1, // 月
+      "d+": date.getDate(), // 日
+      "h+": date.getHours(), // 小时
+      "m+": date.getMinutes(), // 分
+      "s+": date.getSeconds(), // 秒
+      "q+": Math.floor((date.getMonth() + 3) / 3), // 季度
+      S: date.getMilliseconds(), // 毫秒
+    };
     if (/(y+)/.test(fmt)) {
       fmt = fmt.replace(
         RegExp.$1,
-        (date.getFullYear() + '').substr(4 - RegExp.$1.length)
-      )
+        (date.getFullYear() + "").substr(4 - RegExp.$1.length)
+      );
     }
     // if (/(y+)/.test(fmt))
     //   fmt = fmt.replace(
@@ -248,48 +248,111 @@ export function formatDate(timestamp, formate) {
     //     (date.getFullYear() + '').substr(4 - RegExp.$1.length)
     //   )
     for (var k in o) {
-      if (new RegExp('(' + k + ')').test(fmt)) {
+      if (new RegExp("(" + k + ")").test(fmt)) {
         fmt = fmt.replace(
           RegExp.$1,
           RegExp.$1.length === 1
             ? o[k]
-            : ('00' + o[k]).substr(('' + o[k]).length)
-        )
+            : ("00" + o[k]).substr(("" + o[k]).length)
+        );
       }
     }
-    return fmt
-  }
-  return Format(formate)
+    return fmt;
+  };
+  return Format(formate);
 }
 export const jsonToFormData = (json) => {
-  const formData = new FormData()
+  const formData = new FormData();
   for (const key in json) {
     if (Object.prototype.hasOwnProperty.call(json, key)) {
-      const value = json[key]
+      const value = json[key];
       if (Array.isArray(value)) {
         for (let i = 0; i < value.length; i++) {
-          let item
-          if (typeof value[i] === 'string') {
-            item = value[i]
-            formData.append(`${key}[${i}]`, item)
+          let item;
+          if (typeof value[i] === "string") {
+            item = value[i];
+            formData.append(`${key}[${i}]`, item);
           } else if (value[i] instanceof File) {
-            item = value[i]
-            formData.append(`${key}`, item)
+            item = value[i];
+            formData.append(`${key}`, item);
           } else {
-            item = JSON.stringify(value[i])
-            formData.append(`${key}[${i}]`, item)
+            item = JSON.stringify(value[i]);
+            formData.append(`${key}[${i}]`, item);
           }
         }
       } else if (value instanceof File) {
-        formData.append(key, value)
+        formData.append(key, value);
       } else {
-        const item = typeof value === 'string' ? value : JSON.stringify(value)
-        formData.append(key, item)
+        const item = typeof value === "string" ? value : JSON.stringify(value);
+        formData.append(key, item);
       }
     }
   }
-  return formData
-}
+  return formData;
+};
+//下载图表json 文件
+export const downLoadChartsJsonFile = (selectJson) => {
+  // 创建Blob对象,将selectJson数据转化为JSON字符串
+  const jsonStr = JSON.stringify(selectJson, null, 2); // 格式化JSON
+  const blob = new Blob([jsonStr], { type: "application/json" });
+  // 创建a元素,用于下载Blob内容
+  const link = document.createElement("a");
+  link.href = URL.createObjectURL(blob);
+  link.download = selectJson.layout.title + "_selected_data.json"; // 设置下载的文件名
+  // 触发下载
+  link.click();
+  // 释放URL对象
+  URL.revokeObjectURL(link.href);
+};
+
+//生成新的图表数据
+export const creatNewChartsJson = (selectedPoints, fullLayout) => {
+  const groupedData = {};
+  const addedPoints = {}; // 用于跟踪已添加的 (x, y) 组合
+  selectedPoints.forEach((item) => {
+    const { name } = item.data;
+    if (!groupedData[name]) {
+      groupedData[name] = {
+        x: [],
+        y: [],
+        mode: item.data.mode,
+        color: item.fullData.marker.color || item.fullData.line.color, // 获取颜色信息
+      };
+    }
+    // 使用 (x, y) 作为键来跟踪是否已添加
+    const pointKey = `${item.x}-${item.y}`;
+    if (!addedPoints[pointKey]) {
+      groupedData[name].x.push(item.x);
+      groupedData[name].y.push(item.y);
+      addedPoints[pointKey] = true;
+    }
+  });
+
+  const newPlotlyData = Object.keys(groupedData).map((name) => ({
+    x: groupedData[name].x,
+    y: groupedData[name].y,
+    mode: groupedData[name].mode,
+    marker: { color: groupedData[name].color, size: 10 }, // 使用原始颜色
+    line: { color: groupedData[name].color }, // 使用原始颜色
+    name: name,
+  }));
+
+  // 配置新的图表布局
+  const layout = {
+    title: fullLayout.title.text,
+    xaxis: { title: fullLayout.xaxis.title.text },
+    yaxis: { title: fullLayout.yaxis.title.text },
+    legend: {
+      orientation: "h",
+      y: -0.2,
+      x: 0.5,
+      xanchor: "center",
+    },
+  };
+
+  return { newPlotlyData, layout };
+};
+
 export default {
   uuid,
   copy,
@@ -303,5 +366,7 @@ export default {
   base64ToBlob,
   downloadExcel,
   convertTimestamp,
-  formatDate
-}
+  formatDate,
+  downLoadChartsJsonFile,
+  creatNewChartsJson,
+};

+ 19 - 0
src/utils/worker.worker.js

@@ -0,0 +1,19 @@
+/*
+ * @Author: your name
+ * @Date: 2024-08-16 09:30:14
+ * @LastEditTime: 2024-08-16 14:15:14
+ * @LastEditors: bogon
+ * @Description: In User Settings Edit
+ * @FilePath: /performance-test/src/utils/worker.js
+ */
+self.onmessage = function (event) {
+  const data = event.data;
+  const processedData = data.map((engine) => {
+    return {
+      xData: [...engine.xdata], // 假设需要将数据转换为数字
+      yData: [...engine.ydata],
+    };
+  });
+
+  self.postMessage(processedData);
+};

+ 207 - 69
src/views/performance/assetssDetail.vue

@@ -1,13 +1,15 @@
 <!--
  * @Author: your name
  * @Date: 2024-05-27 09:25:45
- * @LastEditTime: 2024-08-08 13:53:25
+ * @LastEditTime: 2024-09-12 17:21:52
  * @LastEditors: bogon
  * @Description: In User Settings Edit
  * @FilePath: /performance-test/src/views/performance/assetssDetail.vue
 -->
 <template>
   <div class="global-variable" v-loading="loading">
+    <!-- 通过json文件绘制新的图表 -->
+    <!-- <CreateNewChart></CreateNewChart> -->
     <el-row type="flex" justify="space-between">
       <el-col :span="8">
         <el-button
@@ -30,7 +32,6 @@
         </div>
       </el-col>
     </el-row>
-
     <el-card class="box-card analysisType">
       <el-form
         :model="formInfo"
@@ -76,79 +77,107 @@
               <el-button @click="reset('ruleForm')" size="small">
                 重置
               </el-button>
-              <!-- <el-button type="primary" @click="" size="small">
+              <el-button
+                type="primary"
+                @click="downLoadAllTypeFile"
+                size="small"
+              >
                 一键下载分析结果图
-              </el-button> -->
+              </el-button>
             </el-form-item>
           </el-col>
         </el-row>
       </el-form>
     </el-card>
-
     <el-empty :image-size="200" v-if="flage"></el-empty>
     <div v-else>
-      <!-- <DetailCharts></DetailCharts> -->
-      <el-card class="box-card analysisType" v-if="generalFiles.length > 0">
-        <div slot="header" class="clearfix">
-          <span style="font-weight: 700; font-size: 16px">
-            分析总图{{ fileCheckResult1 }}
-          </span>
-        </div>
-        <el-row class="assetssConent">
-          <!-- :span="getSpan(index, 'generalFiles')" -->
-          <!-- v-loading="
+      <!-- <DetailCharts
+        v-if="this.formInfo.analysisTypeCode === 'power_curve'"
+        :jsonData=""
+      ></DetailCharts> -->
+      <el-table
+        v-if="
+          this.formInfo.analysisTypeCode === 'yaw_error' &&
+          this.csvData.length > 0
+        "
+        :data="csvData"
+        border
+        style="width: 100%"
+        align="center"
+      >
+        <el-table-column prop="engine_name" label="风机名称"> </el-table-column>
+        <el-table-column prop="yaw_error1" label="误差值"> </el-table-column>
+      </el-table>
+      <!-- <JsonMarkerCharts></JsonMarkerCharts> -->
+      <!-- <MarkersCharts></MarkersCharts> -->
+      <div v-else>
+        <el-card class="box-card analysisType" v-if="generalFiles.length > 0">
+          <div slot="header" class="clearfix">
+            <span style="font-weight: 700; font-size: 16px">
+              分析总图{{ fileCheckResult1 }}
+            </span>
+          </div>
+          <el-row class="assetssConent">
+            <!-- :span="getSpan(index, 'generalFiles')" -->
+            <!-- v-loading="
               loadings[index + generalFiles.length] &&
               getFileType(file.fileAddr) === 'html'
             " -->
-          <el-col
-            v-for="(file, index) in generalFiles"
-            :key="index"
-            :span="24"
-            class="col_content"
-            :style="{
-              display: getFileType(file.fileAddr) === 'html' ? 'block' : 'none',
-            }"
-          >
-            <iframe
-              v-if="getFileType(file.fileAddr) === 'html'"
-              :src="file.fileAddr"
-              :ref="'iframe' + index"
-              frameborder="0"
-              @load="iframeLoad(index + generalFiles.length)"
-              width="100%"
-              height="100%"
-            ></iframe>
-          </el-col>
-        </el-row>
-      </el-card>
-      <el-card class="box-card analysisType" v-if="diagramRelations.length > 0">
-        <div slot="header" class="clearfix">
-          <span style="font-weight: 700; font-size: 16px">
-            分析分图 {{ fileCheckResult1 }}
-          </span>
-        </div>
-        <el-row class="assetssConent">
-          <el-col
-            v-for="(file, index) in diagramRelations"
-            :key="index"
-            :span="24"
-            :style="{
-              display: getFileType(file.fileAddr) === 'html' ? 'block' : 'none',
-            }"
-            class="col_content"
-          >
-            <iframe
-              v-if="getFileType(file.fileAddr) === 'html'"
-              :src="file.fileAddr"
-              :ref="'iframe' + index + diagramRelations.length"
-              frameborder="0"
-              width="100%"
-              height="100%"
-              @load="iframeLoad(index + diagramRelations.length)"
-            ></iframe>
-          </el-col>
-        </el-row>
-      </el-card>
+            <el-col
+              v-for="(file, index) in generalFiles"
+              :key="index"
+              :span="24"
+              class="col_content"
+              :style="{
+                display:
+                  getFileType(file.fileAddr) === 'html' ? 'block' : 'none',
+              }"
+            >
+              <iframe
+                v-if="getFileType(file.fileAddr) === 'html'"
+                :src="file.fileAddr"
+                :ref="'iframe' + index"
+                frameborder="0"
+                @load="iframeLoad(index + generalFiles.length)"
+                width="100%"
+                height="100%"
+              ></iframe>
+            </el-col>
+          </el-row>
+        </el-card>
+        <el-card
+          class="box-card analysisType"
+          v-if="diagramRelations.length > 0"
+        >
+          <div slot="header" class="clearfix">
+            <span style="font-weight: 700; font-size: 16px">
+              分析分图 {{ fileCheckResult1 }}
+            </span>
+          </div>
+          <el-row class="assetssConent">
+            <el-col
+              v-for="(file, index) in diagramRelations"
+              :key="index"
+              :span="24"
+              :style="{
+                display:
+                  getFileType(file.fileAddr) === 'html' ? 'block' : 'none',
+              }"
+              class="col_content"
+            >
+              <iframe
+                v-if="getFileType(file.fileAddr) === 'html'"
+                :src="file.fileAddr"
+                :ref="'iframe' + index + diagramRelations.length"
+                frameborder="0"
+                width="100%"
+                height="100%"
+                @load="iframeLoad(index + diagramRelations.length)"
+              ></iframe>
+            </el-col>
+          </el-row>
+        </el-card>
+      </div>
       <el-card
         class="box-card"
         v-for="o in commentDescriptionVos"
@@ -172,10 +201,18 @@ import {
   queryAnalysisedType,
   queryAnalysisedEngine,
   queryAnalysisTypeConfig,
+  queryDownloadFile,
+  downloadFile,
 } from "@/api/performance";
+import CreateNewChart from "./createNewChart.vue";
 import DetailCharts from "./components/DetailCharts.vue";
+import JsonMarkerCharts from "./components/JsonMarkerCharts.vue";
+import { saveAs } from "file-saver";
+import JSZip from "jszip";
+import Papa from "papaparse";
+import axios from "axios";
 export default {
-  components: { DetailCharts },
+  components: { DetailCharts, JsonMarkerCharts, CreateNewChart },
   data() {
     return {
       loadings: [],
@@ -184,8 +221,8 @@ export default {
       fileCheckResult1: null,
       analysisTypeList: [],
       windEngineGroupList: [],
-      generalFiles: [],
-      diagramRelations: [],
+      generalFiles: [], //总图数组
+      diagramRelations: [], //分图数组
       commentDescriptionVos: [],
       formInfo: {
         fieldEngineCode: null,
@@ -193,6 +230,8 @@ export default {
       },
       rules: {},
       flage: false,
+      csvData: [], // 解析后的数据
+      csvHeaders: [], // CSV 表头
     };
   },
   created() {
@@ -200,10 +239,77 @@ export default {
     this.getWindCodeList();
   },
   mounted() {
-    // this.initializeLoading();
-    this.chartsTypeConfig();
+    this.initializeLoading();
+    // this.chartsTypeConfig();
   },
   methods: {
+    //一键下载所有文件
+    async downLoadAllTypeFile() {
+      const result = await queryDownloadFile({
+        fieldCode: this.$route.query.fieldCode,
+        batchCode: this.$route.query.batchCode,
+      });
+      result.code === 200
+        ? this.downLoadSingleType(result.data)
+        : this.$message({ type: "error", message: result.msg });
+    },
+    // 下载单个类型的文件
+    async downLoadSingleType(data) {
+      const zip = new JSZip();
+      let totalFiles = data.length;
+      let completedFiles = 0;
+      if (!("Notification" in window)) {
+        alert("This browser does not support desktop notifications.");
+      } else if (Notification.permission !== "granted") {
+        Notification.requestPermission();
+      }
+      this.loading = true;
+      for (let i = 0; i < data.length; i++) {
+        try {
+          const formData = new FormData();
+          formData.append("batchCode", this.$route.query.batchCode);
+          formData.append("analysisTypeCode", data[i]);
+          formData.append("fieldCode", this.$route.query.fieldCode);
+          // 获取 Blob 对象并提供进度回调
+          await downloadFile(formData, (percentCompleted) => {
+            // 创建或更新通知
+            // if (Notification.permission === "granted") {
+            //   new Notification(`Downloading file ${data[i]}`, {
+            //     body: `Progress: ${percentCompleted}%`,
+            //   });
+            // }
+          }).then((blob) => {
+            if (blob instanceof Blob) {
+              zip.file(`file_${data[i]}.zip`, blob);
+              // console.log(`文件 ${data[i]} 加入到 ZIP 成功`);
+              completedFiles += 1;
+              this.loading = false;
+              this.$notify({
+                title: "提示",
+                message: `已完成 ${completedFiles}/${totalFiles} 文件`,
+                // duration: 0,
+              });
+              // console.log(`已完成 ${completedFiles}/${totalFiles} 文件`);
+            } else {
+              throw new Error(`返回的数据不是 Blob 对象`);
+            }
+          });
+        } catch (error) {
+          console.error(`下载文件 ${data[i]} 时发生错误:`, error);
+        }
+      }
+
+      // 生成 ZIP 文件并下载
+      zip
+        .generateAsync({ type: "blob" })
+        .then((content) => {
+          saveAs(content, "Downloaded_Files.zip");
+          console.log("所有文件已打包并下载成功");
+        })
+        .catch((error) => {
+          console.error("生成 ZIP 文件时发生错误:", error);
+        });
+    },
     //分析类型图表样式
     async chartsTypeConfig() {
       const result = await queryAnalysisTypeConfig();
@@ -222,6 +328,33 @@ export default {
     onSubmit() {
       this.getDetailInfo();
     },
+    getCsvData(url) {
+      // 使用 axios 获取 CSV 文件
+      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.csvHeaders = Object.keys(result.data[0]);
+                this.csvData = result.data.filter(
+                  (row) => Object.keys(row).length
+                ); // 过滤空行
+              },
+              error: (error) => {
+                console.error("CSV 解析错误:", error);
+              },
+            });
+          };
+          reader.readAsText(response.data); // 读取 blob 数据
+        })
+        .catch((error) => {
+          console.error("无法获取 CSV 文件:", error);
+        });
+    },
     // 重置
     reset(formName) {
       this.$refs[formName].resetFields();
@@ -229,6 +362,7 @@ export default {
       this.getWindCodeList();
     },
     async getDetailInfo() {
+      console.log(this.csvData, "csvDatacsvData");
       const formData = new FormData();
       formData.append("batchCode", this.$route.query.batchCode);
       formData.append("analysisTypeCode", this.formInfo.analysisTypeCode);
@@ -259,6 +393,10 @@ export default {
             response.data.length > 0 &&
             response.data[0].commentDescriptionVos) ||
           [];
+        if (this.formInfo.analysisTypeCode === "yaw_error") {
+          this.getCsvData(response.data[0].generalFiles[0].fileAddr);
+        }
+
         this.initializeLoading();
         this.loading = false;
       } catch (error) {

+ 2 - 2
src/views/performance/assetssMag.vue

@@ -296,8 +296,8 @@ export default {
           path: "/home/performance/assetssDetail",
           query: {
             batchCode: row.batchCode,
-            analysisTypeCode: row.analysisTypeCode,
-            fieldEngineCode: row.fieldEngineCode,
+            // analysisTypeCode: row.analysisTypeCode,
+            fieldCode: row.fieldCode,
           },
         });
       };

+ 255 - 21
src/views/performance/components/DetailCharts.vue

@@ -3,18 +3,19 @@
     <div id="mainChart"></div>
     <div
       v-for="(engine, index) in data.data"
-      :key="index"
+      :key="'chart-' + index"
       :id="'chart-' + index"
     ></div>
-    <div v-for="(item, ind) in imageDataUri">
-      <img :src="item" alt="" style="width: 80%; height: auto" />
-    </div>
   </div>
 </template>
 
 <script>
 import Plotly from "plotly.js-dist";
-
+import {
+  creatNewChartsJson,
+  downLoadChartsJsonFile,
+  _debounce,
+} from "@/utils/common";
 export default {
   name: "PlotlyChart",
   data() {
@@ -26,13 +27,47 @@ export default {
           {
             engineName: "#1",
             windSpeed: [
-              3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
-              27, 30,
+              "3",
+              "4",
+              "5",
+              "6",
+              "7",
+              "8",
+              "9",
+              "10",
+              "11",
+              "12",
+              "13",
+              "14",
+              "15",
+              "16",
+              "17",
+              "18",
+              " 19",
+              "20",
+              "27",
+              "30",
             ],
             power: [
-              6.06, 76.54, 184.1, 345.94, 574.4, 879.9, 1274.73, 1664.94,
-              1942.96, 2004.78, 2004.78, 2004.78, 2004.78, 2004.78, 2004.78,
-              2004.78, 2004.78, 2004.78, 2230,
+              "6.06",
+              "76.54",
+              "184.1",
+              "345.94",
+              "574.4",
+              "879.9",
+              "1274.73",
+              "1664.94",
+              "1942.96",
+              "2004.78",
+              "2004.78",
+              "2004.78",
+              "2004.78",
+              "2004.78",
+              "2004.78",
+              "2004.78",
+              "2004.78",
+              "2004.78",
+              "2230",
             ],
           },
           {
@@ -66,13 +101,62 @@ export default {
           },
         ],
       },
+      selectJson: {},
+      selectedPoints: [],
+      modeBarButtons: [
+        // 撤销按钮
+        {
+          name: "撤销圈选",
+          icon: Plotly.Icons.undo,
+          click: (gd) => {
+            this.deleteLastShapeAndPoints(gd);
+          },
+        },
+        // 自定义工具按钮配置
+        {
+          name: "套索选择",
+          icon: Plotly.Icons.lasso,
+          click: (gd) => {
+            // 处理套索选择功能
+            Plotly.relayout(gd, { dragmode: "lasso" });
+            // 创建一个用于保存所有选择点的数组
+            let allSelectedPoints = [];
+            // 监听 plotly_selected 事件
+            gd.on("plotly_selected", (eventData) => {
+              if (eventData && eventData.points) {
+                // 将每次选择的点累加到 allSelectedPoints 中
+                allSelectedPoints = allSelectedPoints.concat(eventData.points);
+                // 显示累加后的所有选中点
+                this.debouncedGetSelectData(allSelectedPoints, gd.layout);
+                // this.getSelectData(allSelectedPoints, gd.layout);
+              }
+            });
+          },
+        },
+        // 其他工具按钮配置
+        {
+          name: "下载标记数据",
+          icon: Plotly.Icons.disk,
+          click: (gd) => {
+            this.downLoadSelectJson();
+          },
+        },
+      ],
       imageDataUri: [],
       currentEngineIndex: 0,
+      actionHistory: [],
+      lastLasso: null,
+      undoStack: [], // 初始化为一个空数组
+      allSelectedPoints: [], // 所有选择的点
+      lastTraceIndex: null, // 上一次添加的 trace 索引
+      lastSelectedPoints: [], // 上一次选择的点
     };
   },
   mounted() {
     this.initializeMainChart();
     this.initializeEngineCharts();
+    // 初始化防抖函数
+    this.debouncedGetSelectData = _debounce(this.getSelectData, 500);
   },
   methods: {
     initializeMainChart() {
@@ -80,33 +164,45 @@ export default {
         title: "功率曲线图",
         xaxis: { title: "Wind Speed (m/s)" },
         yaxis: { title: "Power (kW)" },
+        // 布局配置
+        shapes: this.lassoShapes, // 初始化时加载已保存的套索
         legend: {
           orientation: "h",
           y: -0.2,
           x: 0.5,
           xanchor: "center",
         },
+        shapes: [],
+        _undoStack: [],
+        editable: true,
       };
-
       const plotlyData = this.data.data.map((engine) => ({
         x: engine.windSpeed,
         y: engine.power,
         mode: "lines+markers",
         name: engine.engineName,
       }));
-
       const config = {
+        modeBarButtonsToAdd: this.modeBarButtons,
+        modeBarButtonsToRemove: [
+          // 移除不需要的工具按钮
+          "lasso2d",
+        ],
         displaylogo: false,
+        editable: true,
+        scrollZoom: true,
       };
-
       Plotly.newPlot("mainChart", plotlyData, layout, config).then((gd) => {
-        //生成图片方法
+        // 初始化 _undoStack
+        if (!gd._fullLayout._undoStack) {
+          gd._fullLayout._undoStack = [];
+        }
         Plotly.toImage(gd, { format: "png" }).then((dataUrl) => {
-          // console.log(dataUrl, "dataUrl");
           this.imageDataUri.push(dataUrl);
         });
       });
     },
+    //初始化分图
     initializeEngineCharts() {
       this.data.data.forEach((engine, index) => {
         const layout = {
@@ -119,33 +215,171 @@ export default {
             x: 0.5,
             xanchor: "center",
           },
+          annotations: [
+            {
+              x: 5, // 示例坐标
+              y: 500,
+              xref: "x",
+              yref: "y",
+              text: "示例注释",
+              showarrow: true,
+              arrowhead: 2,
+              ax: -50,
+              ay: -50,
+              font: {
+                size: 12,
+                color: "black",
+              },
+              bgcolor: "white",
+              bordercolor: "black",
+            },
+          ],
+          shapes: [],
+          _undoStack: [], // 用于存储撤回操作的栈
+          editable: true,
         };
-
         const plotlyData = this.data.data.map((eng, i) => ({
           x: eng.windSpeed,
           y: eng.power,
           mode: "lines+markers",
           name: eng.engineName,
           line: {
-            color: index === i ? "blue" : "grey",
+            color: index === i ? "#1c77b3" : "#c1c1c1",
           },
         }));
-
         const config = {
+          modeBarButtonsToAdd: this.modeBarButtons,
+          modeBarButtonsToRemove: [
+            // "selectbox",
+            "lasso",
+            // "zoombox",
+            // "pan",
+            // "autoscale",
+            // "zoom2d",
+            // "pan2d",
+            // "select2d",
+          ],
           displaylogo: false,
+          scrollZoom: true,
+          editable: true,
         };
-
         Plotly.newPlot(`chart-${index}`, plotlyData, layout, config).then(
           (gd) => {
-            //生成图片方法
+            // 初始化 _undoStack
+            if (!gd._fullLayout._undoStack) {
+              gd._fullLayout._undoStack = [];
+            }
+            gd.on("plotly_relayout", (eventData) => {
+              if (eventData.shapes) {
+                gd._fullLayout._undoStack.push({
+                  undo: gd.layout.shapes.slice(),
+                });
+              }
+            });
+            // 生成图片方法
             Plotly.toImage(gd, { format: "png" }).then((dataUrl) => {
-              console.log(dataUrl, "dataUrl");
               this.imageDataUri.push(dataUrl);
             });
           }
         );
       });
     },
+    deleteLastShapeAndPoints(gd) {
+      if (this.undoStack.length > 0) {
+        const lastAction = this.undoStack.pop();
+        // 删除上一次添加的 trace
+        if (lastAction.traceIndex !== undefined) {
+          Plotly.deleteTraces(gd, lastAction.traceIndex);
+        }
+        // 从 allSelectedPoints 中删除上一次添加的点
+        this.allSelectedPoints = this.allSelectedPoints.filter(
+          (point) =>
+            !lastAction.points.some((p) => p.x === point.x && p.y === point.y)
+        );
+        // 删除上一次的 shape(即套索)
+        if (lastAction.shape) {
+          const newShapes = gd.layout.shapes.filter(
+            (shape) => shape !== lastAction.shape
+          );
+          Plotly.relayout(gd, { shapes: newShapes });
+        }
+        // 删除 selectionlayer 和 zoomlayer 的子元素
+        const selectionLayer = document.querySelector(".selectionlayer");
+        const zoomLayer = document.querySelector(".zoomlayer");
+        if (selectionLayer) {
+          while (selectionLayer.firstChild) {
+            selectionLayer.removeChild(selectionLayer.firstChild); // 清空 selectionlayer 的子元素
+          }
+        }
+        if (zoomLayer) {
+          while (zoomLayer.firstChild) {
+            zoomLayer.removeChild(zoomLayer.firstChild); // 清空 zoomlayer 的子元素
+          }
+        }
+
+        // 恢复为 pan 平移工具
+        Plotly.relayout(gd, { dragmode: "pan" });
+      }
+      // 清除所有选择点
+      this.allSelectedPoints = [];
+      this.selectJson = {};
+    },
+    getSelectData(selectedPoints, fullLayout) {
+      const { newPlotlyData, layout } = creatNewChartsJson(
+        selectedPoints,
+        fullLayout
+      );
+
+      this.selectJson = {
+        data: newPlotlyData,
+        layout: layout,
+      };
+
+      const update = {
+        x: selectedPoints.map((p) => p.x),
+        y: selectedPoints.map((p) => p.y),
+        mode: "markers",
+        marker: { color: "red", size: 10 },
+        showlegend: false,
+      };
+
+      Plotly.addTraces("mainChart", update).then((gd) => {
+        const traceIndex = gd.data.length - 1;
+        this.lastSelectedPoints = [...selectedPoints];
+        this.allSelectedPoints = [...this.allSelectedPoints, ...selectedPoints];
+
+        // 获取当前 shapes
+        const currentShape = gd.layout.shapes
+          ? gd.layout.shapes[gd.layout.shapes.length - 1]
+          : null;
+
+        // 将添加的 trace 索引、选择点、和 shape 压入栈中
+        this.undoStack.push({
+          traceIndex,
+          points: [...selectedPoints],
+          shape: currentShape, // 记录当前的 shape
+        });
+      });
+    },
+    downLoadSelectJson() {
+      // 处理选中的点数据
+      // 根据name对选中的点进行分组
+      if (this.selectJson.data && this.selectJson.data.length > 0) {
+        downLoadChartsJsonFile(this.selectJson);
+      } else {
+        this.$message({
+          message: "请圈选数据后进行下载操作",
+          type: "warning",
+        });
+      }
+    },
+  },
+  beforeDestroy() {
+    // 清理 Plotly 监听器
+    const gd = document.getElementById("mainChart");
+    if (gd) {
+      Plotly.purge(gd);
+    }
   },
 };
 </script>

+ 81 - 7
src/views/performance/components/EditAnalysis.vue

@@ -1,7 +1,7 @@
 <!--
  * @Author: your name
  * @Date: 2024-05-29 09:14:23
- * @LastEditTime: 2024-08-08 11:03:42
+ * @LastEditTime: 2024-09-13 09:47:21
  * @LastEditors: bogon
  * @Description: In User Settings Edit
  * @FilePath: /performance-test/src/views/performance/components/EditAnalysis.vue
@@ -90,6 +90,20 @@
             </el-table>
           </div>
         </el-col>
+        <!-- 其他类型展示的 图表 ,只展示图表不必要展示表格 -->
+        <el-col
+          v-loading="htmlLoading"
+          v-if="
+            form.configAnalysis === 'power_curve'
+            // ||
+            // form.configAnalysis === 'cp'
+          "
+          :span="12"
+        >
+          <div class="right">
+            <PlotlyCharts :lineMarkerData="lineMarkerData"></PlotlyCharts>
+          </div>
+        </el-col>
         <el-col :span="24" v-if="form.configAnalysis === 'yaw_error'"
           ><div class="left">
             <el-table
@@ -104,6 +118,71 @@
           </div>
         </el-col>
         <el-col
+          :span="24"
+          v-if="form.configAnalysis === '发电效能评价数字化指标分析'"
+          ><div class="left">
+            <el-table
+              :data="tableData"
+              border
+              style="width: 100%"
+              align="center"
+            >
+              <el-table-column prop="date" label="风电机组可利用率">
+              </el-table-column>
+              <el-table-column prop="name" label="能量利用率">
+              </el-table-column>
+              <el-table-column prop="name" label="应发电量"> </el-table-column>
+              <el-table-column prop="name" label="实发电量"> </el-table-column>
+              <el-table-column prop="name" label="停机损失电量">
+              </el-table-column>
+              <el-table-column prop="name" label="停机损失电量百分比">
+              </el-table-column>
+              <el-table-column prop="name" label="欠发损失电量">
+              </el-table-column>
+              <el-table-column prop="name" label="欠发损失电量百分比">
+              </el-table-column>
+              <el-table-column prop="name" label="限电损失电量">
+              </el-table-column>
+              <el-table-column prop="name" label="限电损失电量百分比">
+              </el-table-column>
+              <el-table-column prop="name" label="功率曲线未达标损失电量">
+              </el-table-column>
+              <el-table-column prop="name" label="功率曲线未达标损失电量百分比">
+              </el-table-column>
+              <el-table-column
+                prop="name"
+                label="功率散点不同水平功率带分布宽度"
+              >
+              </el-table-column>
+              <el-table-column
+                prop="name"
+                label="功率散点水平平均功率带分布平均宽度"
+              >
+              </el-table-column>
+              <el-table-column prop="name" label="功率散点分布异常特征值">
+              </el-table-column>
+              <el-table-column prop="name" label="平均风速"> </el-table-column>
+            </el-table>
+          </div>
+        </el-col>
+        <el-col :span="24" v-if="form.configAnalysis === '故障统计'"
+          ><div class="left">
+            <el-table
+              :data="tableData"
+              border
+              style="width: 100%"
+              align="center"
+            >
+              <el-table-column prop="date" label="故障次数(次)">
+              </el-table-column>
+              <el-table-column prop="name" label="故障时长(秒)">
+              </el-table-column>
+              <el-table-column prop="name" label="损失电量(KW/h)">
+              </el-table-column>
+            </el-table>
+          </div>
+        </el-col>
+        <el-col
           v-loading="htmlLoading"
           v-if="form.configAnalysis === 'power_curve'"
           :span="12"
@@ -112,12 +191,6 @@
             <PlotlyCharts></PlotlyCharts>
           </div>
         </el-col>
-        <!-- 其他类型展示的 图表 ,只展示图表不必要展示表格 -->
-        <!-- <el-col v-loading="htmlLoading" v-else :span="24">
-          <div class="right">
-            <PlotlyCharts></PlotlyCharts>
-          </div>
-        </el-col> -->
         <el-col
           :span="12"
           v-if="
@@ -298,6 +371,7 @@ export default {
       engineCode: null, //台账机组编号
       windDetail: {},
       flage: false,
+      lineMarkerData: [],
       rules: {
         commentTypeName: {
           required: true,

+ 142 - 0
src/views/performance/components/JsonMarkerCharts.vue

@@ -0,0 +1,142 @@
+<!--
+ * @Author: your name
+ * @Date: 2024-08-16 16:19:19
+ * @LastEditTime: 2024-09-05 08:57:40
+ * @LastEditors: milo-MacBook-Pro.local
+ * @Description: In User Settings Edit
+ * @FilePath: /performance-test/src/views/performance/components/JsonMarkerCharts.vue
+-->
+<template>
+  <div>
+    <div ref="chart" style="width: 100%; height: 500px"></div>
+  </div>
+</template>
+<script>
+import Plotly from "plotly.js-dist";
+import axios from "axios";
+
+export default {
+  data() {
+    return {
+      dataArr: [], // 存储好点标识的数据
+      dataBrr: [], // 存储坏点标识的数据
+      dataCrr: [], // 存储限电点标识的数据
+    };
+  },
+  async mounted() {
+    try {
+      // 从服务器获取数据
+      const response = await axios.get(
+        // "http://localhost:3006/wof053600062/WOF053600062-WOB00021/power_curve/manual/demo.json"
+        "http://localhost:3006/getEchartsData"
+      );
+      this.processData(response.data); // 处理并分类数据
+      this.initChart(); // 初始化图表
+    } catch (error) {
+      console.error("Error fetching data:", error);
+    }
+  },
+  methods: {
+    processData(data) {
+      data.forEach((item) => {
+        switch (item.lab) {
+          case "1":
+            this.dataArr.push({ x: item.wind_velocity, y: item.active_power });
+            break;
+          case "5":
+            this.dataBrr.push({ x: item.wind_velocity, y: item.active_power });
+            break;
+          case "4":
+            this.dataCrr.push({ x: item.wind_velocity, y: item.active_power });
+            break;
+          default:
+            return;
+        }
+      });
+    },
+    initChart() {
+      const chartElement = this.$refs.chart;
+      const trace1 = {
+        x: this.dataArr.map((d) => d.x),
+        y: this.dataArr.map((d) => d.y),
+        mode: "markers",
+        marker: { color: "green" },
+        name: "好点",
+      };
+      const trace2 = {
+        x: this.dataBrr.map((d) => d.x),
+        y: this.dataBrr.map((d) => d.y),
+        mode: "markers",
+        marker: { color: "red" },
+        name: "坏点",
+      };
+      const trace3 = {
+        x: this.dataCrr.map((d) => d.x),
+        y: this.dataCrr.map((d) => d.y),
+        mode: "markers",
+        marker: { color: "blue" },
+        name: "限电点",
+      };
+
+      const data = [trace1, trace2, trace3];
+
+      const layout = {
+        title: "Scatter Plot",
+        xaxis: {
+          title: "Wind Velocity",
+          gridcolor: "rgb(255,255,255)",
+          range: [1, 10],
+          showgrid: true,
+          showline: false,
+          showticklabels: true,
+          tickcolor: "rgb(127,127,127)",
+          ticks: "outside",
+          zeroline: false,
+        },
+        yaxis: {
+          title: "Active Power",
+          gridcolor: "rgb(255,255,255)",
+          showgrid: true,
+          showline: false,
+          showticklabels: true,
+          tickcolor: "rgb(127,127,127)",
+          ticks: "outside",
+          zeroline: false,
+        },
+        showlegend: true,
+        hovermode: "closest",
+        hovermode: "closest",
+        paper_bgcolor: "rgb(255,255,255)",
+        // plot_bgcolor: "rgb(229,229,229)",
+        // paper_bgcolor: "lightgray", // 设置图表外部区域背景色
+        plot_bgcolor: "#e5ecf6", // 设置绘图区背景色
+      };
+
+      Plotly.newPlot(chartElement, data, layout);
+
+      // 响应窗口大小变化
+      window.addEventListener("resize", () => {
+        Plotly.Plots.resize(chartElement);
+      });
+
+      // 处理图表的选择事件
+      chartElement.on("plotly_selected", (eventData) => {
+        if (eventData) {
+          const selectedPoints = [];
+          eventData.points.forEach((pt) => {
+            selectedPoints.push({
+              x: pt.x,
+              y: pt.y,
+              curveNumber: pt.curveNumber,
+            });
+          });
+          console.log("Selected data:", selectedPoints);
+        }
+      });
+    },
+  },
+};
+</script>
+<style scoped>
+/* 自定义样式 */
+</style>

+ 9 - 3
src/views/performance/components/PlotlyCharts.vue

@@ -21,6 +21,12 @@ export default {
         title: "Power Curve",
         xaxis: { title: "Wind Speed (m/s)" },
         yaxis: { title: "Power (kW)" },
+        legend: {
+          orientation: "h",
+          y: -0.2,
+          x: 0.5,
+          xanchor: "center",
+        },
       };
       const data = {
         analysisTypeCode: "power_curve",
@@ -70,7 +76,6 @@ export default {
           },
         ],
       };
-
       const plotlyData = data.data.map((engine) => ({
         x: engine.windSpeed,
         y: engine.power,
@@ -163,11 +168,13 @@ export default {
           "hoverCompareCartesian",
         ],
         displaylogo: false, // 移除plotly logo
+        scrollZoom: true,
+        // editable: true,//编辑图表标题
       };
       Plotly.newPlot("myDiv", plotlyData, layout, config).then((gd) => {
         //生成图片方法
         Plotly.toImage(gd, { format: "png" }).then((dataUrl) => {
-          console.log(dataUrl, "dataUrl");
+          // console.log(dataUrl, "dataUrl");
           this.imageDataUri = dataUrl;
         });
         // 处理选择事件
@@ -181,7 +188,6 @@ export default {
             // 这里可以保存选中的数据进行后续处理
           }
         });
-
         // 处理套索结束事件
         gd.on("plotly_relayout", (eventData) => {
           if (eventData["dragmode"] === "lasso") {

+ 19 - 0
src/views/performance/components/chartsCom/BarChart.vue

@@ -0,0 +1,19 @@
+<!--
+ * @Author: your name
+ * @Date: 2024-09-11 14:30:17
+ * @LastEditTime: 2024-09-11 14:35:22
+ * @LastEditors: milo-MacBook-Pro.local
+ * @Description: In User Settings Edit
+ * @FilePath: /performance-test/src/views/performance/components/chartsCom/BarChart.vue
+-->
+<template>
+  <div>
+    BarChart
+    <h1>风速频率分析</h1>
+    <h1>风速均值分析</h1>
+    <h1>额定风速分析</h1>
+    <h1>环境温度传感器分析2个总图</h1>
+  </div>
+</template>
+<script></script>
+<style scoped></style>

+ 17 - 0
src/views/performance/components/chartsCom/BoxLineCharts.vue

@@ -0,0 +1,17 @@
+<!--
+ * @Author: your name
+ * @Date: 2024-09-11 14:32:51
+ * @LastEditTime: 2024-09-11 14:33:06
+ * @LastEditors: milo-MacBook-Pro.local
+ * @Description: In User Settings Edit
+ * @FilePath: /performance-test/src/views/performance/components/chartsCom/BoxLineCharts.vue
+-->
+<template>
+  <div>
+    boxLineCharts
+    <h1>额定功率和风速分析</h1>
+    <h1></h1>
+  </div>
+</template>
+<script></script>
+<style scoped></style>

+ 18 - 0
src/views/performance/components/chartsCom/BoxMarkersCharts.vue

@@ -0,0 +1,18 @@
+<!--
+ * @Author: your name
+ * @Date: 2024-09-11 14:36:31
+ * @LastEditTime: 2024-09-11 14:40:15
+ * @LastEditors: milo-MacBook-Pro.local
+ * @Description: In User Settings Edit
+ * @FilePath: /performance-test/src/views/performance/components/chartsCom/BoxMarkersCharts.vue
+-->
+<template>
+  <div>
+    BoxMarkersCharts
+    <h1>叶尖速比时序分析</h1>
+    <h1>风能利用系数时序分析</h1>
+    <h1></h1>
+  </div>
+</template>
+<script></script>
+<style scoped></style>

+ 17 - 0
src/views/performance/components/chartsCom/HeatmapCharts.vue

@@ -0,0 +1,17 @@
+<!--
+ * @Author: your name
+ * @Date: 2024-09-11 14:29:26
+ * @LastEditTime: 2024-09-11 14:41:29
+ * @LastEditors: milo-MacBook-Pro.local
+ * @Description: In User Settings Edit
+ * @FilePath: /performance-test/src/views/performance/components/chartsCom/HeatmapCharts.vue
+-->
+<template>
+  <div>
+    HeatmapCharts
+    <h1>分钟级SCADA数据记录完整度分析</h1>
+    <h1>秒级SCADA数据记录完整度分析</h1>
+  </div>
+</template>
+<script></script>
+<style scoped></style>

+ 23 - 0
src/views/performance/components/chartsCom/MarkersCharts.vue

@@ -0,0 +1,23 @@
+<!--
+ * @Author: your name
+ * @Date: 2024-09-11 14:24:26
+ * @LastEditTime: 2024-09-11 14:39:39
+ * @LastEditors: milo-MacBook-Pro.local
+ * @Description: In User Settings Edit
+ * @FilePath: /performance-test/src/views/performance/components/chartsCom/MarkersCharts.vue
+-->
+<template>
+  <div>
+    MarkersCharts
+    <h1>变桨和有功功率协调性分析 2D+3D</h1>
+    <h1>变桨和叶尖速比及风能利用系数分析 2D</h1>
+    <h1>逐月有功功率散点3D分析 3D</h1>
+    <h1>发电机转速和有功功率分析 3D+2D+3D</h1>
+    <h1>发电机转速和转矩分析3D+2D+3D</h1>
+    <h1>变桨和发电机转速协调性分析2D</h1>
+    <h1>最小桨距角分析2D</h1>
+    <h1>叶尖速比-Cp-功率散点分析2D</h1>
+  </div>
+</template>
+<script></script>
+<style scoped></style>

+ 16 - 0
src/views/performance/components/chartsCom/WindRoseChart.vue

@@ -0,0 +1,16 @@
+<!--
+ * @Author: your name
+ * @Date: 2024-09-11 14:31:05
+ * @LastEditTime: 2024-09-11 14:31:19
+ * @LastEditors: milo-MacBook-Pro.local
+ * @Description: In User Settings Edit
+ * @FilePath: /performance-test/src/views/performance/components/chartsCom/WindRoseChart.vue
+-->
+<template>
+  <div>
+    WindRoseChart
+    <h1>风向玫瑰分析</h1>
+  </div>
+</template>
+<script></script>
+<style scoped></style>

+ 21 - 0
src/views/performance/components/chartsCom/lineAndChildLine.vue

@@ -0,0 +1,21 @@
+<!--
+ * @Author: your name
+ * @Date: 2024-09-11 14:28:15
+ * @LastEditTime: 2024-09-11 14:40:37
+ * @LastEditors: milo-MacBook-Pro.local
+ * @Description: In User Settings Edit
+ * @FilePath: /performance-test/src/views/performance/components/chartsCom/lineAndChildLine.vue
+-->
+<template>
+  <div>
+    lineAndChildLine
+    <h1>有功功率曲线分析</h1>
+    <h1>叶尖速比和有功功率分析</h1>
+    <h1>叶尖速比-Cp-功率分析</h1>
+    <h1>风能利用系数和有功功率分析</h1>
+    <h1>风能利用系数和风速分析</h1>
+    <h1>叶尖速比和风速分析</h1>
+  </div>
+</template>
+<script></script>
+<style scoped></style>

+ 16 - 0
src/views/performance/components/chartsCom/lineChart.vue

@@ -0,0 +1,16 @@
+<!--
+ * @Author: your name
+ * @Date: 2024-09-11 14:23:08
+ * @LastEditTime: 2024-09-11 14:24:01
+ * @LastEditors: milo-MacBook-Pro.local
+ * @Description: In User Settings Edit
+ * @FilePath: /performance-test/src/views/performance/components/chartsCom/lineChart.vue
+-->
+<template>
+  <div>
+    lineChart
+    <h1>大部件温度传感器分析</h1>
+  </div>
+</template>
+<script></script>
+<style scoped></style>

+ 17 - 0
src/views/performance/components/chartsCom/powerMarkers2DCharts.vue

@@ -0,0 +1,17 @@
+<!--
+ * @Author: your name
+ * @Date: 2024-09-11 14:32:12
+ * @LastEditTime: 2024-09-11 16:21:49
+ * @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>
+</template>
+<script></script>
+<style scoped></style>

+ 109 - 0
src/views/performance/createNewChart.vue

@@ -0,0 +1,109 @@
+<template>
+  <div>
+    <el-card class="box-card">
+      <div slot="header" class="clearfix">
+        <span>上传json文件,生成新的图表</span>
+        <el-upload
+          style="float: right; padding: 3px 0"
+          class="upload-demo"
+          action=""
+          :before-upload="beforeUpload"
+          :http-request="customUpload"
+          multiple
+          :file-list="fileList"
+        >
+          <el-button size="small" type="text">点击上传</el-button>
+          <div slot="tip" class="el-upload__tip">只能上传json文件</div>
+        </el-upload>
+      </div>
+      <div id="creatNewChart"></div>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import Plotly from "plotly.js-dist";
+
+export default {
+  data() {
+    return {
+      fileList: [], // 保存上传的文件
+      mergedData: [], // 合并后的图表数据
+      mergedLayout: {}, // 合并后的布局数据
+    };
+  },
+  methods: {
+    // 文件类型检查
+    beforeUpload(file) {
+      const isJSON =
+        file.type === "application/json" || file.name.endsWith(".json");
+      if (!isJSON) {
+        this.$message.error("只能上传 JSON 文件");
+      }
+      return isJSON;
+    },
+    // 自定义上传方法
+    customUpload({ file }) {
+      const reader = new FileReader();
+      reader.onload = (e) => {
+        try {
+          const jsonData = JSON.parse(e.target.result);
+          this.mergeChartData(jsonData);
+          this.generateChart();
+        } catch (error) {
+          this.$message.error("上传的文件不是有效的 JSON 格式");
+        }
+      };
+      reader.readAsText(file); // 读取文件内容
+    },
+    // 合并图表数据
+    mergeChartData(jsonData) {
+      // 合并 data 部分
+      this.mergedData = this.mergedData.concat(jsonData.data);
+
+      // 合并 layout 部分,假设 layout 相同,否则需要根据需求处理
+      if (Object.keys(this.mergedLayout).length === 0) {
+        this.mergedLayout = jsonData.layout;
+      }
+    },
+    // 生成图表
+    generateChart() {
+      const config = {
+        displaylogo: false,
+        editable: true,
+        scrollZoom: true,
+      };
+
+      Plotly.newPlot(
+        "creatNewChart",
+        this.mergedData,
+        this.mergedLayout,
+        config
+      );
+    },
+  },
+};
+</script>
+
+<style scoped>
+.text {
+  font-size: 14px;
+}
+
+.item {
+  margin-bottom: 18px;
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: "";
+}
+.clearfix:after {
+  clear: both;
+}
+
+.box-card {
+  width: 100%;
+}
+</style>

+ 2 - 1
vue.config.js

@@ -65,9 +65,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://106.120.102.238:16600", //外网
+           target: "http://10.96.137.5",
         changeOrigin: true,
         pathRewrite: {
           "^/api": "", // 需要regit write重写的,