he wei
2023-11-25 d043e9283165ac10757ab4bf536998bf42b98e9b
UA 部分UI完成
15个文件已修改
1个文件已删除
25个文件已添加
4686 ■■■■ 已修改文件
package.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pnpm-lock.yaml 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/images/bg.jpg 补丁 | 查看 | 原始文档 | blame | 历史
public/images/header-bg.png 补丁 | 查看 | 原始文档 | blame | 历史
public/images/panel-title.png 补丁 | 查看 | 原始文档 | blame | 历史
src/App.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/js/axios.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/js/getWebUrl.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Hamburger/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/alarmCard.vue 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/alarmLegend.vue 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/bar1.vue 371 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/echarts/BaseChart.vue 180 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/echarts/transparent.js 178 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/instrumentBoard.vue 258 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/listCard.vue 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/panel.vue 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Navbar.vue 150 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/SidebarItem.vue 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/TagsView/index.vue 251 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/index.vue 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/routes.js 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/details/index.vue 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/components/protectorBox.vue 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/components/svgLine.vue 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/components/switchBox.vue 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/components/test.vue 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/components/textBox.vue 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/images/bhq.png 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/images/hr.png 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/images/kgg.png 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/images/pdg.png 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/images/s-1p.png 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/images/s-2p.png 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/images/s-3p.png 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/index.vue 1142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/js/props.js 155 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login/index.vue 233 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/realTime/index.vue 751 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json
@@ -24,6 +24,7 @@
    "vue": "^2.7.5",
    "vue-layer": "^1.2.0",
    "vue-router": "3.0.2",
    "echarts": "^4.8.0",
    "vuex": "3.1.0"
  },
  "devDependencies": {
pnpm-lock.yaml
@@ -14,6 +14,9 @@
  core-js:
    specifier: ^3.8.3
    version: registry.npmmirror.com/core-js@3.33.3
  echarts:
    specifier: ^4.8.0
    version: registry.npmmirror.com/echarts@4.9.0
  element-ui:
    specifier: ^2.15.14
    version: registry.npmmirror.com/element-ui@2.15.14(vue@2.7.15)
@@ -5892,6 +5895,14 @@
    version: 1.0.1
    engines: {node: '>=6.0.0'}
    dev: true
  registry.npmmirror.com/echarts@4.9.0:
    resolution: {integrity: sha512-+ugizgtJ+KmsJyyDPxaw2Br5FqzuBnyOWwcxPKO6y0gc5caYcfnEUIlNStx02necw8jmKmTafmpHhGo4XDtEIA==, tarball: https://registry.npmmirror.com/echarts/-/echarts-4.9.0.tgz}
    name: echarts
    version: 4.9.0
    dependencies:
      zrender: registry.npmmirror.com/zrender@4.3.2
    dev: false
  registry.npmmirror.com/ee-first@1.0.3:
    resolution: {integrity: sha512-1q/3kz+ZwmrrWpJcCCrBZ3JnBzB1BMA5EVW9nxnIP1LxDZ16Cqs9VdolqLWlExet1vU+bar3WSkAa4/YrA9bIw==, tarball: https://registry.npmmirror.com/ee-first/-/ee-first-1.0.3.tgz}
@@ -13206,3 +13217,9 @@
      normalize-path: registry.npmmirror.com/normalize-path@1.0.0
      strip-indent: registry.npmmirror.com/strip-indent@2.0.0
    dev: true
  registry.npmmirror.com/zrender@4.3.2:
    resolution: {integrity: sha512-bIusJLS8c4DkIcdiK+s13HiQ/zjQQVgpNohtd8d94Y2DnJqgM1yjh/jpDb8DoL6hd7r8Awagw8e3qK/oLaWr3g==, tarball: https://registry.npmmirror.com/zrender/-/zrender-4.3.2.tgz}
    name: zrender
    version: 4.3.2
    dev: false
public/images/bg.jpg
public/images/header-bg.png
public/images/panel-title.png
src/App.vue
@@ -14,5 +14,6 @@
<style>
#app {
  height: 100vh;
  background: #011F39;
}
</style>
src/assets/js/axios.js
@@ -3,7 +3,7 @@
if (process.env.NODE_ENV == 'development') {
  // 跨域请求
  axios.defaults.baseURL = 'http://localhost:8094/fdk/';
  axios.defaults.baseURL = 'http://localhost:8095/ms/';
  axios.defaults.withCredentials = true;  // 保持请求头
}
src/assets/js/getWebUrl.js
@@ -1,9 +1,9 @@
function getWebUrl() {
  let url = '';
  if (process.env.NODE_ENV == 'development') {
    url = "http://localhost:8094/fdk/"
    url = "http://localhost:8095/ms/"
  } else {
    url = location.protocol + "//" + location.host + "/fdk/";
    url = location.protocol + "//" + location.host + "/ms/";
  }
  return url;
}
src/components/Hamburger/index.vue
@@ -1,5 +1,5 @@
<template>
  <div style="padding: 0 15px;" @click="toggleClick">
  <div style="padding: 6px 15px;" @click="toggleClick">
    <svg
      :class="{'is-active':isActive}"
      class="hamburger"
@@ -8,7 +8,7 @@
      width="64"
      height="64"
    >
      <path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
      <path fill="#fff" d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
    </svg>
  </div>
</template>
src/components/alarmCard.vue
New file
@@ -0,0 +1,67 @@
<template>
  <div class="alarm-card">
    <div class="name">{{ name }}</div>
    <div
      :class="[
        'state',
        { level1: 1 == level, level2: 2 == level, level3: 3 == level },
      ]"
    ></div>
  </div>
</template>
<script>
export default {
  name: "",
  props: {
    name: {
      type: String,
      required: true,
    },
    level: {
      type: Number,
      default: 0,
      validator(value) {
        return [0, 1, 2, 3].some((v) => v == value);
      },
    },
  },
  data() {
    return {};
  },
  components: {},
  methods: {},
  mounted() {},
};
</script>
<style scoped lang="less">
.alarm-card {
  background: #0C4D77;
  border: 1px solid #78eef8;
  border-radius: 4px;
  // width: 320px;
  width: 100%;
  height: 38px;
  display: flex;
  padding: 6px;
  justify-content: space-between;
  align-items: center;
  .state {
    width: 60px;
    background: #78eef8;
    border-radius: 4px;
    align-self: stretch;
    &.level1 {
      background: #4871E3;
    }
    &.level2 {
      background: #F69F40;
    }
    &.level3 {
      background: #FF3801;
    }
  }
}
</style>
src/components/alarmLegend.vue
New file
@@ -0,0 +1,73 @@
<template>
  <div class="alarm-legend">
    <div class="item" v-for="(item, idx) in list" :key="'legend_' + idx">
      <div
        :class="[
          'state',
          { level1: 1 == item.level, level2: 2 == item.level, level3: 3 == item.level },
        ]"
      ></div>
      <div class="name">{{ item.name }}</div>
    </div>
  </div>
</template>
<script>
export default {
  name: "",
  data() {
    return {
      list: [
        {
          name: "正常",
          level: 0,
        },
        {
          name: "一般",
          level: 1,
        },
        {
          name: "重大",
          level: 2,
        },
        {
          name: "紧急",
          level: 3,
        },
      ],
    };
  },
  components: {},
  methods: {},
  mounted() {},
};
</script>
<style scoped lang="less">
.alarm-legend {
  display: flex;
  padding: 6px;
  justify-content: space-between;
  align-items: center;
  .item {
    display: flex;
  }
  .state {
    margin-right: 4px;
    width: 20px;
    height: 20px;
    background: #78eef8;
    border-radius: 50%;
    &.level1 {
      background: #4871e3;
    }
    &.level2 {
      background: #f69f40;
    }
    &.level3 {
      background: #ff3801;
    }
  }
}
</style>
src/components/bar1.vue
New file
@@ -0,0 +1,371 @@
<script>
import ECharts from "echarts";
import BaseChart from "@/components/echarts/BaseChart";
export default {
  extends: BaseChart,
  props: {
    name: {
      type: String,
      default: "",
    },
    unit: {
      type: String,
      default: "",
    },
  },
  data() {
    return {};
  },
  methods: {
    fullScreen() {
      return false;
    },
    setData(data) {
      let option = this.getOption(data);
      this.setOption(option);
    },
    getOption(data) {
      let xLabel = data.xLabel;
      let sData = data.sData;
      // let sName = this.name;
      // let maxD = Math.max(...sData);
      // if (maxD <= 0) {
      //   maxD = 1;
      // } else {
      //   maxD = Math.round(maxD * 1.2 * 100) / 100;
      // }
      // let fillData = sData.map(() => maxD);
      // let min = 0;
      // let max =
      //   sData.length == 0
      //     ? 0
      //     : function (data) {
      //         let max = data.max;
      //         if (max == -Infinity) {
      //           max = 1;
      //         }
      //         // max = Math.max(Math.round((max + max * 0.2) * 100) / 100);
      //         return max;
      //       };
      // let data = [
      //   [30, 50, 120],
      //   ["A相", "B相", "C相"],
      // ];
      var getmydzd = [];
      let big = 0;
      sData.forEach((el) => {
        if (!(el === undefined || el === "")) {
          if (big < Number(el)) {
            big = Number(el);
          }
        }
      });
      for (let i = 0; i < sData.length; i++) {
        getmydzd.push(big * 4);
      }
      //计算最大值
      function calMax(arr) {
        let max = 0;
        arr.forEach((el) => {
          el.forEach((el1) => {
            if (!(el1 === undefined || el1 === "")) {
              if (max < Number(el1)) {
                max = Number(el1);
              }
            }
          });
        });
        let maxint = Math.ceil(max / 9.5);
        //不让最高的值超过最上面的刻度
        let maxval = maxint * 10;
        //让显示的刻度是整数
        return maxval;
      }
      var max = Math.ceil(calMax([sData]) / 10) * 10;
      return {
        grid: {
          left: "3%",
          right: "13%",
          bottom: "10%",
          top: "10%",
          containLabel: true,
        },
        tooltip: {
          formatter: (params) => {
            if (params.name !== "") {
              return params.name + " : " + sData[params.dataIndex];
            }
          },
          textStyle: {
            align: "left",
          },
        },
        xAxis: [
          {
            type: "value",
            axisLabel: {
              margin: 5,
              color: "#fff",
              formatter: function (val) {
                return val + "";
              },
              textStyle: {
                fontSize: "13",
              },
            },
            min: 0,
            max: max, // 计算最大值
            interval: max / 5, //  平均分为5份
            splitNumber: 5,
            splitLine: {
              show: false,
              lineStyle: {
                color: "#fff",
              },
            },
            axisLine: {
              show: false,
              lineStyle: {
                color: "#fff",
                width: 1,
                opacity: 0.3,
              },
            },
            axisTick: {
              show: false,
            },
            axisLabel: {
              show: false,
            },
          },
          {
            type: "value",
            axisLabel: {
              show: false,
            },
            min: 0,
            max: max, // 计算最大值
            interval: max / 10, //  平均分为5份
            splitNumber: 10,
            splitLine: {
              show: false,
              lineStyle: {
                type: "dashed",
                color: "#D8D8D8",
              },
            },
            axisLine: {
              show: false,
              lineStyle: {
                color: "#fff",
              },
            },
            axisTick: {
              show: false,
            },
          },
        ],
        yAxis: [
          {
            type: "category",
            inverse: true,
            //  boundaryGap:true,
            axisLabel: {
              formatter: (value, index) => {
                if (value.length >= 12) {
                  value = value.slice(0, 12) + `\n` + value.slice(12);
                }
                if (value.length >= 26) {
                  value = value.slice(0, 26) + `\n` + value.slice(26);
                }
                return value;
              },
              textStyle: {
                color: "rgba(255,255,255,0.8)",
                fontSize: "12",
                align: "right",
                lineHeight: 18,
              },
            },
            splitLine: {
              show: false,
            },
            axisTick: {
              show: false,
            },
            axisLine: {
              show: false,
              lineStyle: {
                color: "#fff",
                width: 1,
                opacity: 0.3,
              },
            },
            data: xLabel,
          },
          {
            type: "category",
            inverse: true,
            axisTick: "none",
            axisLine: "none",
            show: true,
            axisLabel: {
              textStyle: {
                color: "#ff0000",
                fontSize: "12",
              },
              formatter: function (value) {
                return "{a|" + value + "}";
              },
              rich: {
                a: {
                  backgroundColor: "#0C4D77",
                  fontSize: 14,
                  // lineHeight: 24,
                  verticalAlign: 'middle',
                  width: 50,
                  color: "#78EEF8",
                  // padding: [4, 0, 0, 1],
                  padding: [4, 4 ],
                  borderColor: "#78EEF8",
                  borderRadius: 3,
                  borderWidth: 1,
                },
              },
            },
            data: (function () {
              let arr = [];
              sData.forEach((item) => {
                let data = item + "V";
                arr.push(data);
              });
              return arr;
            })(),
          },
        ],
        dataZoom: [
          {
            type: "inside",
            show: true,
            height: 15,
            start: 1,
            end: 100,
            orient: "vertical",
            zlevel: 66,
          },
        ],
        series: [
          {
            name: "值",
            type: "bar",
            // zlevel: 1,
            xAxisIndex: 0,
            itemStyle: {
              normal: {
                // barBorderRadius: [0, 5, 5, 0],
                color: function (params) {
                  let colorList = ["#4AFD88", "#F69F40", "#FF3801"];
                  return colorList[params.dataIndex];
                },
              },
            },
            barWidth: 25,
            data: sData,
            z: 0,
          },
          {
            // 分隔
            type: "pictorialBar",
            itemStyle: {
              normal: {
                color: "#022539",
              },
            },
            symbolRepeat: "fixed",
            symbolMargin: 4,
            symbol: "rect",
            symbolClip: true,
            symbolSize: [2, 25],
            symbolPosition: "start",
            symbolOffset: [-1, 0],
            data: getmydzd,
            z: 66,
            animationEasing: "elasticOut",
          },
          {
            name: "背景",
            type: "bar",
            barWidth: 25,
            barGap: "-100%",
            xAxisIndex: 1,
            data: getmydzd,
            itemStyle: {
              normal: {
                // color: '#52768C'
                color: {
                  colorStops: [
                    {
                      offset: 0,
                      color: "rgba(24,144,255,0.3)", // 0% 处的颜色
                    },
                    {
                      offset: 1,
                      color: "rgba(99,180,255,0.3)", // 100% 处的颜色
                    },
                  ],
                },
              },
            },
            z: 0,
          },
          // {
          //   name: "背景",
          //   type: "bar",
          //   barMaxWidth: "40%",
          //   barGap: "-100%",
          //   label: {
          //     show: true,
          //     position: "right",
          //     formatter: function (params) {
          //       let value = params.value;
          //       return "{a|" + value + "V" + "}";
          //     },
          //     rich: {
          //       a: {
          //         backgroundColor: "#2c5875",
          //         fontSize: 14,
          //         color: "#fdf100",
          //         padding: 8,
          //         borderColor: "#00FEFF",
          //         borderWidth: 1,
          //       },
          //     },
          //   },
          //   tooltip: {
          //     show: false,
          //   },
          //   data: sData.map(() => max),
          // },
        ],
      };
    },
  },
  mounted() {
    this.setData({
      xLabel: ["A相", "B相", "C相"],
      sData: [30, 50, 120],
    });
  },
};
</script>
src/components/echarts/BaseChart.vue
New file
@@ -0,0 +1,180 @@
<template>
  <div
    class="e-chart-root"
    :class="{'full-screen': fullScreenFlag}"
    @click="handleClick"
    @dblclick="fullScreen">
    <div class="e-chart-container">
      <div class="e-chart" ref="chart"></div>
      <slot name="tools"></slot>
    </div>
    <div class="export-chart-wrapper">
      <div class="export-chart" ref="exportChart"></div>
    </div>
  </div>
</template>
<script>
// 引入 ECharts 主模块
import ECharts from "echarts";
// 引入自定义主题
import "./transparent";
export default {
  name: "BaseChart",
  chart: "",
  exportChart: "",
  data() {
    return {
      fullScreenFlag: false,
    };
  },
  methods: {
    /**
     * 设置echarts图表的配置项
     * @param option echarts标准的配置项
     */
    setOption(option) {
      let chart = this.$options.chart;
      if (chart) {
        chart.setOption(option);
      }
    },
    /**
     * 全屏
     */
    fullScreen() {
      this.fullScreenFlag = this.fullScreenFlag?false:true;
      this.$nextTick(()=>{
        this.resize();
      });
    },
    /**
     * 导出echarts600*400的图片
     * @returns {Base64} 图片的base64字符串
     */
    getDataURL() {
      let chart = this.$options.chart;
      let exportChart = this.$options.exportChart;
      let base64 = "";
      if(exportChart) {
        let option = chart.getOption();
        // x轴样式
        option.xAxis[0].axisLine.lineStyle = {
          color: "#000"
        };
        option.xAxis[0].axisLabel.textStyle.color = "#000";
        // y轴样式
        option.yAxis[0].axisLine.lineStyle = {
          color: "#000"
        };
        option.yAxis[0].axisLabel.textStyle.color = "#000";
        exportChart.setOption(option);
        base64 = exportChart.getDataURL({
          pixelRatio: 1,
          backgroundColor: '#fff'
        });
      }
      return base64;
    },
    /**
     * 对echarts进行缩放
     */
    resize() {
      let chart = this.$options.chart;
      // console.log("resize");
      if (chart) {
        chart.resize();
      }
    },
    /**
     * 销毁echarts实例释放内存
     * @return  {[type]}  [return description]
     */
    dispose() {
      // 销毁chart
      let chart = this.$options.chart;
      this.disposeChart(chart);
      // 销毁chart
      let exportChart = this.$options.exportChart;
      this.disposeChart(exportChart);
    },
    /**
     * 销毁echarts实例释放内存
     * @param chart echarts实例
     */
    disposeChart(chart) {
      if (chart) {
        chart.dispose(); // 销毁实例
        this.$options.chart = "";
      }
    },
    getChart() {
      return this.$options.chart;
    },
    handleClick() {
      this.$emit('click', true);
    }
  },
  mounted() {
    this.$options.chart = ECharts.init(this.$refs.chart, "transparent");
    this.$options.exportChart = ECharts.init(this.$refs.exportChart, "transparent");
    // 监听windows窗口的缩放,绑定resize事件
    window.addEventListener("resize", this.resize);
    this.$nextTick(() => {
      this.resize();
    });
  },
  beforeDestroy() {
    // 销毁resize事件
    window.removeEventListener("resize", this.resize);
    this.dispose();
  }
}
</script>
<style scoped>
.e-chart-root {
  position: relative;
}
/* chart wrapper css */
.e-chart-root,
.e-chart {
  height: 100%;
  box-sizing: border-box;
}
.e-chart-container {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  box-sizing: border-box;
}
.e-chart-root.full-screen .e-chart-container {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-size: 100% 100%;
  z-index: 9999;
}
/* export chart wrapper css */
.export-chart-wrapper {
  position: relative;
  width: 0;
  height: 0;
  overflow: hidden;
}
.export-chart {
  position: absolute;
  width: 600px;
  height: 400px;
}
</style>
src/components/echarts/transparent.js
New file
@@ -0,0 +1,178 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// 引入 ECharts 主模块
import ECharts from "echarts/lib/echarts";
var contrastColor = '#00feff';
var axisCommon = function () {
  return {
    axisLine: {
      lineStyle: {
        color: contrastColor
      }
    },
    axisTick: {
      show: false,
      lineStyle: {
        color: contrastColor
      }
    },
    axisLabel: {
      show: true,
      textStyle: {
        color: contrastColor
      }
    },
    splitLine: {
      show: false,
      lineStyle: {
        type: 'solid',
        color: '#0b388a'
      }
    },
    splitArea: {
      areaStyle: {
        color: contrastColor
      }
    }
  };
};
var colorPalette = ['#15E3F3', '#759aa0', '#e69d87', '#8dc1a9', '#ea7e53', '#eedd78', '#73a373', '#73b9bc', '#7289ab', '#91ca8c', '#f49f42'];
var theme = {
  color: colorPalette,
  backgroundColor: '',
  tooltip: {
    axisPointer: {
      lineStyle: {
        color: contrastColor
      },
      crossStyle: {
        color: contrastColor
      },
      label: {
        color: '#000'
      }
    }
  },
  legend: {
    textStyle: {
      color: contrastColor
    }
  },
  textStyle: {
    color: contrastColor
  },
  title: {
    textStyle: {
      color: contrastColor
    }
  },
  toolbox: {
    show: false,
    iconStyle: {
      normal: {
        borderColor: contrastColor
      }
    }
  },
  dataZoom: {
    textStyle: {
      color: contrastColor
    }
  },
  visualMap: {
    textStyle: {
      color: contrastColor
    }
  },
  timeline: {
    lineStyle: {
      color: contrastColor
    },
    itemStyle: {
      normal: {
        color: colorPalette[1]
      }
    },
    label: {
      normal: {
        textStyle: {
          color: contrastColor
        }
      }
    },
    controlStyle: {
      normal: {
        color: contrastColor,
        borderColor: contrastColor
      }
    }
  },
  timeAxis: axisCommon(),
  logAxis: axisCommon(),
  valueAxis: axisCommon(),
  categoryAxis: axisCommon(),
  line: {
    symbol: 'circle'
  },
  graph: {
    color: colorPalette
  },
  gauge: {
    title: {
      textStyle: {
        color: contrastColor
      }
    }
  },
  candlestick: {
    itemStyle: {
      normal: {
        color: '#FD1050',
        color0: '#0CF49B',
        borderColor: '#FD1050',
        borderColor0: '#0CF49B'
      }
    }
  }
};
ECharts.registerTheme('transparent', theme);
src/components/instrumentBoard.vue
New file
@@ -0,0 +1,258 @@
<script>
import ECharts from "echarts";
import BaseChart from "@/components/echarts/BaseChart";
var highlight = "#03b7c9";
var demoData = [
  {
    name: "电压",
    value: 220,
    unit: "V",
    pos: ["16.6%", "40%"],
    range: [0, 400],
  },
  { name: "电流", value: 32, unit: "A", pos: ["49.8%", "40%"], range: [0, 60] },
  {
    name: "功率因数",
    value: 0.9,
    pos: ["83%", "40%"],
    range: [0.1, 1.0],
    splitNum: 9,
  },
  // {
  //   name: "有功功率",
  //   value: 6.34,
  //   unit: "kW",
  //   pos: ["16.6%", "75%"],
  //   range: [0, 50],
  // },
  // {
  //   name: "有功电能",
  //   value: 6.28,
  //   unit: "kWh",
  //   pos: ["49.8%", "75%"],
  //   range: [0, 50],
  // },
  // {
  //   name: "电网频率",
  //   value: 50,
  //   unit: "Hz",
  //   pos: ["83%", "75%"],
  //   range: [0, 100],
  // },
];
export default {
  extends: BaseChart,
  props: {
    name: {
      type: String,
      default: "",
    },
    unit: {
      type: String,
      default: "",
    },
    r: {
      type: [String, Number],
      required: true,
    }
  },
  data() {
    return {
      // r: '33.3%',
    };
  },
  computed: {
  },
  watch: {
    r() {
      this.$nextTick(() => {
        this.resize();
      });
    }
  },
  methods: {
    fullScreen() {
      return false;
    },
    setData(data) {
      let option = this.getOption(data);
      this.setOption(option);
    },
    getOption(data) {
      let xLabel = data.xLabel;
      let sData = data.sData;
      let sName = this.name;
      let maxD = Math.max(...sData);
      if (maxD <= 0) {
        maxD = 1;
      } else {
        maxD = Math.round(maxD * 1.2 * 100) / 100;
      }
      let fillData = sData.map(() => maxD);
      let min = 0;
      let max =
        sData.length == 0
          ? 0
          : function (data) {
              let max = data.max;
              if (max == -Infinity) {
                max = 1;
              }
              // max = Math.max(Math.round((max + max * 0.2) * 100) / 100);
              return max;
            };
      let self = this;
      return {
        grid: {
          left: "1%",
          right: "1%",
          bottom: "2%",
          top: "2%",
          containLabel: true,
        },
        series: (function () {
          var result = [];
          demoData.forEach(function (item) {
            result.push(
              // 外围刻度
              {
                type: "gauge",
                center: item.pos,
                // radius: "33.33%", // 1行3个
                radius: self.r,
                splitNumber: item.splitNum || 10,
                min: item.range[0],
                max: item.range[1],
                startAngle: 225,
                endAngle: -45,
                axisLine: {
                  show: true,
                  lineStyle: {
                    width: 2,
                    shadowBlur: 0,
                    color: [[1, highlight]],
                  },
                },
                axisTick: {
                  show: true,
                  lineStyle: {
                    color: highlight,
                    width: 1,
                  },
                  // length: -5,
                  splitNumber: 10,
                },
                splitLine: {
                  // show: true,
                  show: false,
                  // length: -14,
                  lineStyle: {
                    color: highlight,
                  },
                },
                axisLabel: {
                  distance: -20,
                  // textStyle: {
                  //   color: highlight,
                  //   fontSize: "14",
                  //   fontWeight: "bold",
                  // },
                },
                pointer: {
                  show: 0,
                },
                detail: {
                  show: 0,
                },
              },
              // 内侧指针、数值显示
              {
                name: item.name,
                type: "gauge",
                center: item.pos,
                radius: "30.33%",
                startAngle: 225,
                endAngle: -45,
                min: item.range[0],
                max: item.range[1],
                axisLine: {
                  show: true,
                  lineStyle: {
                    width: 16,
                    color: [[1, "rgba(255,255,255,.1)"]],
                  },
                },
                axisTick: {
                  show: 0,
                },
                splitLine: {
                  show: 0,
                },
                axisLabel: {
                  show: 0,
                },
                pointer: {
                  show: true,
                  length: "105%",
                },
                detail: {
                  show: true,
                  offsetCenter: [0, "200%"],
                  textStyle: {
                    fontSize: 20,
                    color: "#fff",
                  },
                  formatter: [
                    "{value} " + (item.unit || ""),
                    "{name|" + item.name + "}",
                  ].join("\n"),
                  rich: {
                    name: {
                      fontSize: 14,
                      lineHeight: 30,
                      color: "#ddd",
                    },
                  },
                },
                itemStyle: {
                  normal: {
                    color: highlight,
                  },
                },
                data: [
                  {
                    value: item.value,
                  },
                ],
              }
            );
          });
          return result;
        })(),
      };
    },
  },
  mounted() {
    this.setData({
      xLabel: [],
      sData: [],
    });
  },
};
</script>
src/components/listCard.vue
New file
@@ -0,0 +1,115 @@
<template>
  <div class="list-card">
    <div class="column" v-for="(item, idx) in list" :key="'column_' + idx">
      <div
        class="list-item"
        v-for="(iitem, iidx) in item"
        :key="'item_' + iidx"
      >
        <template v-if="iitem">
          {{ iitem.label }}: {{ valueObj[iitem.key] || "" }}
        </template>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: "",
  props: {
    valueObj: {
      type: Object,
      default() {
        return {};
      },
    },
    datas: {
      type: Array,
      required: true,
    },
    emptyIdxs: {
      type: Array,
      default() {
        return [];
      },
    },
    rows: {
      type: Number,
      default: 3,
    },
    cols: {
      type: Number,
      default: 2,
    },
  },
  computed: {
    // 添加空行 并转为二维数组
    list() {
      let { datas: list, emptyIdxs } = this;
      emptyIdxs.sort((a, b) => a - b);
      for (let i = 0, j = emptyIdxs.length; i < j; i++) {
        list.splice(emptyIdxs[i], 0, null);
      }
      list = this.chunkArray(list, this.rows, this.cols);
      return list;
    },
  },
  data() {
    return {};
  },
  components: {},
  methods: {
    chunkArray(array, chunkSize, len) {
      let chunks = [];
      for (let i = 0; i < array.length; i += chunkSize) {
        chunks.push(array.slice(i, i + chunkSize));
      }
      // 只有最后一个子元素可能不够长度补null 或子元素个数不够 差的组 全部补null
      let realLen = chunks.length;
      let lastItem = chunks[realLen - 1];
      if (lastItem.length < chunkSize) {
        lastItem.push(...new Array(chunkSize - lastItem.length));
      }
      if (realLen < len) {
        let arr = new Array(chunkSize);
        for (let m = len - realLen; m > 0; m--) {
          chunks.push(arr);
        }
      }
      return chunks;
    },
  },
  mounted() {},
};
</script>
<style scoped lang="less">
.list-card {
  // background: #011f39;
  height: 100%;
  display: flex;
  flex-direction: row;
  .column {
    flex: 1;
    display: flex;
    flex-direction: column;
    & + .column {
      margin-left: 6px;
    }
    .list-item {
      flex: 1;
      display: flex;
      align-items: center;
      padding-left: 10px;
      // height: 20px;
      // line-height: 20px;
      background: #153952;
      &:nth-child(2n) {
        background: rgba(21, 57, 82, 0.6);
      }
    }
  }
}
</style>
src/components/panel.vue
New file
@@ -0,0 +1,176 @@
<template>
  <div class="panel">
    <div class="inner">
      <!-- 标题 -->
      <div class="title">{{ title }}</div>
      <!-- 内容 -->
      <div class="content">
        <slot></slot>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: "",
  props: {
    title: {
      type: String,
    },
  },
  data() {
    return {};
  },
  components: {},
  methods: {},
  mounted() {},
};
</script>
<style scoped lang="less">
.panel {
  padding: 4px;
  position: relative;
  &::after {
    pointer-events: none;
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    content: "";
    background: radial-gradient(
          circle farthest-side at 0% 100%,
          transparent 70%,
          rgba(0, 255, 255, 0.3) 70%,
          rgba(0, 255, 255, 3) 71%,
          rgba(0, 255, 255, 3) 95%,
          rgba(0, 255, 255, 0.3) 96%,
          transparent 96%,
          transparent
        )
        calc(100% - 2px) 2px,
      linear-gradient(
          0deg,
          transparent 50%,
          rgba(0, 255, 255, 0.3) 50%,
          rgba(100, 255, 255, 3) 51%,
          rgba(100, 255, 255, 3)
        )
        calc(100% - 16px) 3px,
      linear-gradient(
          90deg,
          transparent 50%,
          rgba(0, 255, 255, 0.3) 50%,
          rgba(100, 255, 255, 3) 51%,
          rgba(100, 255, 255, 3)
        )
        calc(100% - 3px) 16px,
      radial-gradient(
          circle farthest-side at 100% 0,
          transparent 70%,
          rgba(0, 255, 255, 0.3) 70%,
          rgba(0, 255, 255, 3) 71%,
          rgba(0, 255, 255, 3) 95%,
          rgba(0, 255, 255, 0.3) 96%,
          transparent 96%,
          transparent
        )
        2px calc(100% - 2px),
      linear-gradient(
          -90deg,
          transparent 50%,
          rgba(0, 255, 255, 0.3) 50%,
          rgba(100, 255, 255, 3) 51%,
          rgba(100, 255, 255, 3)
        )
        3px calc(100% - 16px),
      linear-gradient(
          180deg,
          transparent 50%,
          rgba(0, 255, 255, 0.3) 50%,
          rgba(100, 255, 255, 3) 51%,
          rgba(100, 255, 255, 3)
        )
        16px calc(100% - 3px),
      radial-gradient(
          circle farthest-side at 100% 100%,
          transparent 70%,
          rgba(0, 255, 255, 0.3) 70%,
          rgba(0, 255, 255, 3) 71%,
          rgba(0, 255, 255, 3) 95%,
          rgba(0, 255, 255, 0.3) 96%,
          transparent 96%,
          transparent
        )
        2px 2px,
      linear-gradient(
          0deg,
          transparent 50%,
          rgba(0, 255, 255, 0.3) 50%,
          rgba(100, 255, 255, 3) 51%,
          rgba(100, 255, 255, 3)
        )
        16px 3px,
      linear-gradient(
          -90deg,
          transparent 50%,
          rgba(0, 255, 255, 0.3) 50%,
          rgba(100, 255, 255, 3) 51%,
          rgba(100, 255, 255, 3)
        )
        3px 16px,
      radial-gradient(
          circle farthest-side at 0 0,
          transparent 70%,
          rgba(0, 255, 255, 0.3) 70%,
          rgba(0, 255, 255, 3) 71%,
          rgba(0, 255, 255, 3) 95%,
          rgba(0, 255, 255, 0.3) 96%,
          transparent 96%,
          transparent
        )
        calc(100% - 2px) calc(100% - 2px),
      linear-gradient(
          180deg,
          transparent 50%,
          rgba(0, 255, 255, 0.3) 50%,
          rgba(100, 255, 255, 3) 51%,
          rgba(100, 255, 255, 3)
        )
        calc(100% - 16px) calc(100% - 3px),
      linear-gradient(
          90deg,
          transparent 50%,
          rgba(0, 255, 255, 0.3) 50%,
          rgba(100, 255, 255, 3) 51%,
          rgba(100, 255, 255, 3)
        )
        calc(100% - 3px) calc(100% - 16px);
    background-size: 14px 14px, 6px 6px, 6px 6px;
    background-repeat: no-repeat;
  }
  // background: gray;
  .inner {
    height: 100%;
    border: 1px #0d8191 solid;
    border-radius: 10px;
    background: radial-gradient(rgba(01, 31, 57, 0.25) 63%, #77eef7 280%);
    display: flex;
    flex-direction: column;
    .title {
      height: 42px;
      text-align: center;
      color: #00f7f8;
      font-size: 20px;
      line-height: 42px;
      background: url("images/panel-title.png") 50% 50% / auto 100% no-repeat;
    }
    .content {
      flex: 1;
    }
  }
}
</style>
src/layout/components/Navbar.vue
@@ -1,24 +1,49 @@
<template>
  <div class="navbar">
    <hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
    <!-- <hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" /> -->
    <div class="title">通信电源监控主站测控系统</div>
    <div class="info">
      <!-- 左边 -->
      <div class="left">
        <div class="box">
          分闸开关
          <div class="num">12</div>
        </div>
        <div class="box">合闸开关<div class="num">22</div></div>
      </div>
      <!-- 右边 -->
      <div class="right">
        <div class="box">
          开关告警数
          <div class="num">4</div>
        </div>
        <div class="box">
          跳闸开关
          <div class="num">12</div>
        </div>
      </div>
    </div>
    <div class="right-menu">
      <el-dropdown class="avatar-container right-menu-item hover-effect" trigger="hover">
      <el-dropdown
        class="avatar-container right-menu-item hover-effect"
        trigger="hover"
      >
        <div class="avatar-wrapper">
          <span class="user-name">{{ name }}</span>
          <i class="el-icon-caret-bottom" />
        </div>
        <el-dropdown-menu slot="dropdown">
          <el-dropdown-item @click.native="changePwd">
            <span style="display:block;">修改密码</span>
            <span style="display: block">修改密码</span>
          </el-dropdown-item>
          <el-dropdown-item @click.native="logout">
            <span style="display:block;">退出登录</span>
            <span style="display: block">退出登录</span>
          </el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
    </div>
   <!-- 密码修改 -->
   <el-dialog
    <!-- 密码修改 -->
    <el-dialog
      title="密码修改"
      width="400px"
      :visible.sync="changePwdVisible"
@@ -27,66 +52,117 @@
      class="dialog-center"
      :modal-append-to-body="false"
    >
      <pwd-change v-if="changePwdVisible" :visible.sync="changePwdVisible"></pwd-change>
    </el-dialog>
      <pwd-change
        v-if="changePwdVisible"
        :visible.sync="changePwdVisible"
      ></pwd-change>
    </el-dialog>
  </div>
</template>
<script>
import { mapGetters } from 'vuex'
import Hamburger from '@/components/Hamburger'
import { mapGetters } from "vuex";
// import Hamburger from '@/components/Hamburger'
import PwdChange from "./PwdChange";
export default {
  components: {
    Hamburger,
    // Hamburger,
    PwdChange,
  },
  computed: {
    ...mapGetters([
      'sidebar',
      'avatar',
      'device',
      'name',
    ])
    ...mapGetters(["sidebar", "avatar", "device", "name"]),
  },
  data() {
    return {
      changePwdVisible: false,
    }
    };
  },
  methods: {
    changePwd() {
      this.changePwdVisible = true;
    },
    toggleSideBar() {
      this.$store.dispatch('app/toggleSideBar')
      this.$store.dispatch("app/toggleSideBar");
    },
    async logout() {
      await this.$store.dispatch('user/logout')
      this.$router.push(`/login?redirect=${this.$route.fullPath}`)
    }
  }
}
      await this.$store.dispatch("user/logout");
      this.$router.push(`/login?redirect=${this.$route.fullPath}`);
    },
  },
};
</script>
<style lang="less" scoped>
.navbar {
  height: 50px;
  height: 104px;
  overflow: hidden;
  position: relative;
  background: #fff;
  box-shadow: 0 1px 4px rgba(0,21,41,.08);
  color: #fff;
  background: #011f39 url("images/header-bg.png") 50% 50% / auto 100% no-repeat;
  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
  .title {
    font-size: 34px;
    font-weight: bold;
    text-align: center;
    line-height: 76px;
    letter-spacing: 6px;
  }
  .info {
    position: absolute;
    left: 0;
    right: 0;
    top: 30px;
    bottom: 0;
    display: flex;
    .left,
    .right {
      width: 50%;
      display: flex;
      justify-content: center;
    }
    .left {
      // padding-right: 380px;
      padding-right: 264px;
    }
    .right {
      padding-left: 264px;
    }
    .box {
      display: inline-block;
      width: 160px;
      height: 40px;
      line-height: 38px;
      font-size: 16px;
      border: 1px #5ec7d4 solid;
      border-radius: 10px;
      text-align: center;
      .num {
        display: inline-block;
        height: 36px;
        min-width: 36px;
        padding: 6px;
        background: #78eef8;
        margin-top: 1px;
        line-height: 24px;
        border-radius: 18px;
        color: #011f39;
        margin-left: 1.2em;
      }
      & + .box {
        margin-left: 10px;
      }
    }
  }
  .hamburger-container {
    line-height: 46px;
    height: 100%;
    float: left;
    cursor: pointer;
    transition: background .3s;
    -webkit-tap-highlight-color:transparent;
    transition: background 0.3s;
    -webkit-tap-highlight-color: transparent;
    &:hover {
      background: rgba(0, 0, 0, .025)
      background: rgba(0, 0, 0, 0.025);
    }
  }
@@ -100,10 +176,12 @@
  }
  .right-menu {
    float: right;
    position: absolute;
    right: 0;
    top: 0;
    height: 100%;
    line-height: 50px;
    padding-top: 40px;
    &:focus {
      outline: none;
    }
@@ -113,15 +191,15 @@
      padding: 0 8px;
      height: 100%;
      font-size: 18px;
      color: #5a5e66;
      color: #fff;
      vertical-align: text-bottom;
      &.hover-effect {
        cursor: pointer;
        transition: background .3s;
        transition: background 0.3s;
        &:hover {
          background: rgba(0, 0, 0, .025)
          background: rgba(0, 0, 0, 0.025);
        }
      }
    }
src/layout/components/Sidebar/SidebarItem.vue
@@ -1,7 +1,7 @@
<template>
  <div v-if="!item.hidden">
    <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)" @click.native="closeMenu">
        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
          <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
        </el-menu-item>
@@ -57,6 +57,9 @@
    return {}
  },
  methods: {
    closeMenu() {
      this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
    },
    hasOneShowingChild(children = [], parent) {
      const showingChildren = children.filter(item => {
        if (item.hidden) {
src/layout/components/Sidebar/index.vue
@@ -1,6 +1,6 @@
<template>
  <div class="side-menu-wrapper">
    <logo :collapse="isCollapse" />
    <!-- <logo :collapse="isCollapse" /> -->
    <div class="side-menu-content">
      <el-menu
        :default-active="activeMenu"
src/layout/components/TagsView/index.vue
@@ -1,23 +1,46 @@
<template>
  <div id="tags-view-container" class="tags-view-container">
      <scroll-pane ref="scrollPane" class="tags-view-wrapper" @scroll="handleScroll">
          <router-link
              v-for="tag in visitedViews"
              ref="tag"
              :key="tag.path"
              :class="isActive(tag)?'active':''"
              :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
              tag="span"
              class="tags-view-item"
              @click.middle.native="!isAffix(tag)?closeSelectedTag(tag):''"
              @contextmenu.prevent.native="openMenu(tag,$event)">
              {{ tag.title }}
              <span v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
          </router-link>
      </scroll-pane>
    <ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
    <hamburger
      id="hamburger-container"
      :is-active="sidebar.opened"
      class="hamburger-container"
      @toggleClick="toggleSideBar"
    />
    <div class="tags-inner">
      <scroll-pane
        ref="scrollPane"
        class="tags-view-wrapper"
        @scroll="handleScroll"
      >
        <router-link
          v-for="tag in visitedViews"
          ref="tag"
          :key="tag.path"
          :class="isActive(tag) ? 'active' : ''"
          :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
          tag="span"
          class="tags-view-item"
          @click.middle.native="!isAffix(tag) ? closeSelectedTag(tag) : ''"
          @contextmenu.prevent.native="openMenu(tag, $event)"
        >
          {{ tag.title }}
          <span
            v-if="!isAffix(tag)"
            class="el-icon-close"
            @click.prevent.stop="closeSelectedTag(tag)"
          />
        </router-link>
      </scroll-pane>
    </div>
    <ul
      v-show="visible"
      :style="{ left: left + 'px', top: top + 'px' }"
      class="contextmenu"
    >
      <li @click="refreshSelectedTag(selectedTag)">Refresh</li>
      <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">Close</li>
      <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
        Close
      </li>
      <li @click="closeOthersTags">Close Others</li>
      <li @click="closeAllTags(selectedTag)">Close All</li>
    </ul>
@@ -25,13 +48,16 @@
</template>
<script>
import path from "path-browserify"
import path from "path-browserify";
import scrollPane from "@/layout/components/TagsView/ScrollPane.vue";
import Hamburger from "@/components/Hamburger";
import routes from "@/router/routes";
import { mapGetters } from 'vuex';
export default {
    components: {
        scrollPane
    },
  components: {
    scrollPane,
    Hamburger,
  },
  data() {
    return {
      visible: false,
@@ -39,170 +65,183 @@
      left: 0,
      selectedTag: {},
      affixTags: [],
        routes: routes,
    }
      routes: routes,
    };
  },
  computed: {
    ...mapGetters([
      'sidebar',
    ]),
    visitedViews() {
      return this.$store.state.tagsView.visitedViews
    }
      return this.$store.state.tagsView.visitedViews;
    },
  },
  watch: {
    $route() {
      this.addTags()
      this.moveToCurrentTag()
      this.addTags();
      this.moveToCurrentTag();
    },
    visible(value) {
      if (value) {
        document.body.addEventListener('click', this.closeMenu)
        document.body.addEventListener("click", this.closeMenu);
      } else {
        document.body.removeEventListener('click', this.closeMenu)
        document.body.removeEventListener("click", this.closeMenu);
      }
    }
    },
  },
  mounted() {
    this.initTags()
    this.addTags()
    this.initTags();
    this.addTags();
  },
  methods: {
    isActive(route) {
      return route.path === this.$route.path
      return route.path === this.$route.path;
    },
    isAffix(tag) {
      return tag.meta && tag.meta.affix
      return tag.meta && tag.meta.affix;
    },
    filterAffixTags(routes, basePath = '/') {
      let tags = []
      routes.forEach(route => {
    filterAffixTags(routes, basePath = "/") {
      let tags = [];
      routes.forEach((route) => {
        if (route.meta && route.meta.affix) {
          const tagPath = path.resolve(basePath, route.path)
          const tagPath = path.resolve(basePath, route.path);
          tags.push({
            fullPath: tagPath,
            path: tagPath,
            name: route.name,
            meta: { ...route.meta }
          })
            meta: { ...route.meta },
          });
        }
        if (route.children) {
          const tempTags = this.filterAffixTags(route.children, route.path)
          const tempTags = this.filterAffixTags(route.children, route.path);
          if (tempTags.length >= 1) {
            tags = [...tags, ...tempTags]
            tags = [...tags, ...tempTags];
          }
        }
      })
      return tags
      });
      return tags;
    },
    initTags() {
      const affixTags = this.affixTags = this.filterAffixTags(this.routes)
      const affixTags = (this.affixTags = this.filterAffixTags(this.routes));
      for (const tag of affixTags) {
        // Must have tag name
        if (tag.name) {
          this.$store.dispatch('tagsView/addVisitedView', tag)
          this.$store.dispatch("tagsView/addVisitedView", tag);
        }
      }
    },
    addTags() {
      const { name } = this.$route
      const { name } = this.$route;
      if (name) {
        this.$store.dispatch('tagsView/addView', this.$route)
        this.$store.dispatch("tagsView/addView", this.$route);
      }
      return false
      return false;
    },
    moveToCurrentTag() {
      const tags = this.$refs.tag
      const tags = this.$refs.tag;
      this.$nextTick(() => {
        for (const tag of tags) {
          if (tag.to.path === this.$route.path) {
            this.$refs.scrollPane.moveToTarget(tag)
            this.$refs.scrollPane.moveToTarget(tag);
            // when query is different then update
            if (tag.to.fullPath !== this.$route.fullPath) {
              this.$store.dispatch('tagsView/updateVisitedView', this.$route)
              this.$store.dispatch("tagsView/updateVisitedView", this.$route);
            }
            break
            break;
          }
        }
      })
      });
    },
    refreshSelectedTag(view) {
      this.$store.dispatch('tagsView/delCachedView', view).then(() => {
        const { fullPath } = view
      this.$store.dispatch("tagsView/delCachedView", view).then(() => {
        const { fullPath } = view;
        this.$nextTick(() => {
          this.$router.replace({
            path: '/redirect' + fullPath
          })
        })
      })
            path: "/redirect" + fullPath,
          });
        });
      });
    },
    closeSelectedTag(view) {
      this.$store.dispatch('tagsView/delView', view).then(({ visitedViews }) => {
        if (this.isActive(view)) {
          this.toLastView(visitedViews, view)
        }
      })
      this.$store
        .dispatch("tagsView/delView", view)
        .then(({ visitedViews }) => {
          if (this.isActive(view)) {
            this.toLastView(visitedViews, view);
          }
        });
    },
    closeOthersTags() {
      this.$router.push(this.selectedTag)
      this.$store.dispatch('tagsView/delOthersViews', this.selectedTag).then(() => {
        this.moveToCurrentTag()
      })
      this.$router.push(this.selectedTag);
      this.$store
        .dispatch("tagsView/delOthersViews", this.selectedTag)
        .then(() => {
          this.moveToCurrentTag();
        });
    },
    closeAllTags(view) {
      this.$store.dispatch('tagsView/delAllViews').then(({ visitedViews }) => {
        if (this.affixTags.some(tag => tag.path === view.path)) {
          return
      this.$store.dispatch("tagsView/delAllViews").then(({ visitedViews }) => {
        if (this.affixTags.some((tag) => tag.path === view.path)) {
          return;
        }
        this.toLastView(visitedViews, view)
      })
        this.toLastView(visitedViews, view);
      });
    },
    toLastView(visitedViews, view) {
      const latestView = visitedViews.slice(-1)[0]
      const latestView = visitedViews.slice(-1)[0];
      if (latestView) {
        this.$router.push(latestView.fullPath)
        this.$router.push(latestView.fullPath);
      } else {
        // now the default is to redirect to the home page if there is no tags-view,
        // you can adjust it according to your needs.
        if (view.name === 'Dashboard') {
        if (view.name === "Dashboard") {
          // to reload home page
          this.$router.replace({ path: '/redirect' + view.fullPath })
          this.$router.replace({ path: "/redirect" + view.fullPath });
        } else {
          this.$router.push('/')
          this.$router.push("/");
        }
      }
    },
    openMenu(tag, e) {
      const menuMinWidth = 105
      const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
      const offsetWidth = this.$el.offsetWidth // container width
      const maxLeft = offsetWidth - menuMinWidth // left boundary
      const left = e.clientX - offsetLeft + 15 // 15: margin right
      const menuMinWidth = 105;
      const offsetLeft = this.$el.getBoundingClientRect().left; // container margin left
      const offsetWidth = this.$el.offsetWidth; // container width
      const maxLeft = offsetWidth - menuMinWidth; // left boundary
      const left = e.clientX - offsetLeft + 15; // 15: margin right
      if (left > maxLeft) {
        this.left = maxLeft
        this.left = maxLeft;
      } else {
        this.left = left
        this.left = left;
      }
      this.top = e.clientY
      this.visible = true
      this.selectedTag = tag
      this.top = e.clientY;
      this.visible = true;
      this.selectedTag = tag;
    },
    closeMenu() {
      this.visible = false
      this.visible = false;
    },
    handleScroll() {
      this.closeMenu()
    }
  }
}
      this.closeMenu();
    },
    toggleSideBar() {
      this.$store.dispatch('app/toggleSideBar')
    },
  },
};
</script>
<style lang="less" scoped>
.tags-view-container {
  height: 34px;
  width: 100%;
  background: #fff;
  border-bottom: 1px solid #d8dce5;
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
  // width: 100%;
  margin: 0 10px;
  background: #0F8090;
  display: flex;
  .tags-inner {
    flex: 1;
  }
  .tags-view-wrapper {
    .tags-view-item {
      display: inline-block;
@@ -224,11 +263,12 @@
        margin-right: 15px;
      }
      &.active {
        background-color: #42b983;
        color: #fff;
        border-color: #42b983;
        // background-color: #42b983;
        background-color: #78EEF8;
        color: #000;
        border-color: #78EEF8;
        &::before {
          content: '';
          content: "";
          background: #fff;
          display: inline-block;
          width: 8px;
@@ -251,7 +291,7 @@
    font-size: 12px;
    font-weight: 400;
    color: #333;
    box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
    box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
    li {
      margin: 0;
      padding: 7px 16px;
@@ -274,10 +314,11 @@
      vertical-align: 2px;
      border-radius: 50%;
      text-align: center;
      transition: all .3s cubic-bezier(.645, .045, .355, 1);
      transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
      transform-origin: 100% 50%;
      &:before {
        transform: scale(.6);
        // transform: scale(0.6);
        transform: scale(1.6);
        display: inline-block;
        vertical-align: -3px;
      }
src/layout/index.vue
@@ -1,17 +1,18 @@
<template>
  <div :class="classObj" class="app-wrapper">
    <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
    <!-- <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" /> -->
    <div v-if="sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
    <sidebar class="sidebar-container" />
    <div class="main-container">
        <div class="inner">
      <div class="inner">
        <div class="main-container-wrapper">
                <div class="main-container-header">
                  <navbar />
                  <tags-view />
                </div>
                <div class="main-container-content">
                  <app-main />
                </div>
          <div class="main-container-header">
            <navbar />
            <tags-view />
          </div>
          <div class="main-container-content">
            <app-main />
          </div>
        </div>
      </div>
    </div>
@@ -19,66 +20,86 @@
</template>
<script>
import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components'
import ResizeMixin from './mixin/ResizeHandler'
import { mapState } from 'vuex'
import { AppMain, Navbar, Settings, Sidebar, TagsView } from "./components";
import ResizeMixin from "./mixin/ResizeHandler";
import { mapState } from "vuex";
import createWs from '@/assets/js/websocket/plus';
const WSMixin = createWs('loginCheck');
import createWs from "@/assets/js/websocket/plus";
const WSMixin = createWs("loginCheck");
export default {
  name: 'Layout',
  name: "Layout",
  components: {
    AppMain,
    Navbar,
    Settings,
    Sidebar,
    TagsView
    TagsView,
  },
  mixins: [ResizeMixin, WSMixin],
  mixins: [ResizeMixin
  // , WSMixin
],
  computed: {
    ...mapState({
      sidebar: state => state.app.sidebar,
      device: state => state.app.device,
      sidebar: (state) => state.app.sidebar,
      device: (state) => state.app.device,
    }),
      classObj() {
            return {
                hideSidebar: !this.sidebar.opened,
                openSidebar: this.sidebar.opened,
                withoutAnimation: this.sidebar.withoutAnimation,
                mobile: this.device === 'mobile'
            }
      }
    classObj() {
      return {
        hideSidebar: !this.sidebar.opened,
        openSidebar: this.sidebar.opened,
        withoutAnimation: this.sidebar.withoutAnimation,
        mobile: this.device === "mobile",
      };
    },
  },
  methods: {
    handleClickOutside() {
      this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
      this.$store.dispatch("app/closeSideBar", { withoutAnimation: false });
    },
    onWSMessage1: function(res) {
    onWSMessage1: function (res) {
      //console.log(res)
      let resData = JSON.parse(res.data);
      if (!resData.data.checkLogin.data) {
        this.$layer.msg(resData.data.checkLogin.msg);
        this.$store.dispatch('user/logout');
        this.$store.dispatch("user/logout");
        setTimeout(() => {
          this.$router.push("/login");
          location.reload();
        }, 2000);
      }
    },
    onWSOpen1: function() {
    onWSOpen1: function () {
      console.log("通讯成功");
    },
  }
}
  },
};
</script>
<style lang="less" scoped>
.app-wrapper {
    display: flex;
    height: 100%;
    .main-container {
        flex: 1;
  display: flex;
  height: 100%;
  .drawer-bg {
    position: fixed;
    top: 0;
    bottom: 0;
    right: 0;
    left: 0;
    z-index: 999998;
  }
  &.hideSidebar .sidebar-container {
    transform: translate(-100%, 0);
  }
  .sidebar-container {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    z-index: 999999;
  }
  .main-container {
    flex: 1;
    position: relative;
    .inner {
      position: absolute;
@@ -87,16 +108,18 @@
      right: 0;
      bottom: 0;
    }
    }
  }
}
.main-container-wrapper {
    height: 100%;
    display: flex;
    flex-direction: column;
    .main-container-content {
        flex: 1;
        overflow-y: auto;
    }
  height: 100%;
  display: flex;
  flex-direction: column;
  .main-container-content {
    flex: 1;
    overflow-y: auto;
    margin: 0 10px;
    background: #122C43;
  }
}
</style>
src/router/routes.js
@@ -30,6 +30,20 @@
    ]
  },
  {
    path: "/realtime",
    component: Layout,
    meta: { title: "实时监控", icon: 'dashboard'},
    name: "realtime",
    children: [
      {
        path: '',
        component: () => import('@/views/realTime'),
        name: 'realtime',
        meta: {title: '实时监控', icon: 'dashboard'}
      },
    ]
  },
  {
    path: "/user",
    component: Layout,
    redirect: '/user/list',
src/views/details/index.vue
File was deleted
src/views/home/components/protectorBox.vue
New file
@@ -0,0 +1,78 @@
<template>
  <g :transform="'translate(' + offset.join(',') + ')'">
    <defs>
      <linearGradient id="color" x1="0%" y1="0%" x2="0%" y2="100%">
        <stop offset="0%" style="stop-color: #377add; stop-opacity: 1" />
        <stop offset="100%" style="stop-color: #4ec1fb; stop-opacity: 1" />
      </linearGradient>
    </defs>
    <!-- 圆角矩形 -->
    <path
      :d="createRoundRectPath(92, 80, 10)"
      stroke="none"
      fill="url(#color)"
    />
    <!-- 绘制标题 -->
    <text x="46" y="18" text-anchor="middle" fill="#fff" font-size="16">
      防雷保护器
    </text>
    <!-- 绘制图片 -->
    <image x="13" y="22" width="66" height="48" :xlink:href="img" />
  </g>
</template>
<script>
import img from "../images/bhq.png";
export default {
  name: "",
  props: {
    offset: {
      type: Array,
      default() {
        return [0, 0];
      },
    },
  },
  computed: {},
  data() {
    return {
      img,
    };
  },
  methods: {
    createRoundRectPath(w, h, r = 5, x = 0, y = 0) {
      let p0 = [x, y];
      let p1 = [x + w, y];
      let p2 = [x + w, y + h];
      let p3 = [x, y + h];
      let cp0 = [x + r, y];
      let cp1 = [x + w - r, y];
      let cp2 = [x + w, y + r];
      let cp3 = [x + w, y + h - r];
      let cp4 = [x + w - r, y + h];
      let cp5 = [x + r, y + h];
      let cp6 = [x, y + h - r];
      let cp7 = [x, y + r];
      return `M${cp0[0]},${cp0[1]}
        L ${cp1[0]} ${cp1[1]}
        C ${cp1[0]} ${cp1[1]}, ${p1[0]} ${p1[1]}, ${cp2[0]} ${cp2[1]}
        L ${cp3[0]} ${cp3[1]}
        C ${cp3[0]} ${cp3[1]}, ${p2[0]} ${p2[1]}, ${cp4[0]} ${cp4[1]}
        L ${cp5[0]} ${cp5[1]}
        C ${cp5[0]} ${cp5[1]}, ${p3[0]} ${p3[1]}, ${cp6[0]} ${cp6[1]}
        L ${cp7[0]} ${cp7[1]}
        C ${cp7[0]} ${cp7[1]}, ${p0[0]} ${p0[1]}, ${cp0[0]} ${cp0[1]}
        Z`;
    },
  },
  mounted() {},
};
</script>
<style scoped>
</style>
src/views/home/components/svgLine.vue
New file
@@ -0,0 +1,79 @@
<template>
  <g :transform="'translate(' + offset.join(',') + ')'">
    <!-- 圆角矩形 -->
    <path :d="createLinePath()" stroke="#fff" fill="none" stroke-width="2" />
  </g>
</template>
<script>
export default {
  name: "",
  props: {
    offset: {
      type: Array,
      default() {
        return [0, 0];
      },
    },
    radius: {
      type: Number,
      default: 0,
    },
    points: {
      type: Array,
      required: true,
      default() {
        return [
          [0, 0],
          [10, 10],
        ];
      },
    },
  },
  computed: {},
  data() {
    return {};
  },
  methods: {
    // 获取线段上离终点指定距离的点的坐标
    getPointForLine(p0, p1, dis) {
      let dir = [p1[0] - p0[0], p1[1] - p0[1]];
      let dirlen = Math.sqrt(dir[0] * dir[0] + dir[1] * dir[1]);
      let dir_normal = [dir[0] / dirlen, dir[1] / dirlen];
      let dis_v = [dir_normal[0] * dis, dir_normal[1] * dis];
      return [p1[0] - dis_v[0], p1[1] - dis_v[1]];
    },
    createLinePath() {
      let { radius, points } = this;
      let path = "";
      let len = points.length;
      points.forEach((v, i) => {
        let isCentral = i > 0 && i < len - 1;
        let p_last = i > 0 ? points[i - 1] : [];
        let p_next = i < len - 1 ? points[i + 1] : [];
        if (0 == i) {
          path += `M${v[0]},${v[1]}`;
        } else if (i == len - 1) {
          path += ` L ${v[0]} ${v[1]}`;
        } else {
          if (radius > 0) {
            let c_last = this.getPointForLine(p_last, v, radius);
            let c_next = this.getPointForLine(p_next, v, radius);
            p_last;
            path += ` L ${c_last[0]} ${c_last[1]} C ${c_last[0]} ${c_last[1]}, ${v[0]} ${v[1]}, ${c_next[0]} ${c_next[1]}`;
          } else {
            path += ` L ${v[0]} ${v[1]}`;
          }
        }
      });
      return path;
    },
  },
  mounted() {},
};
</script>
<style scoped>
</style>
src/views/home/components/switchBox.vue
New file
@@ -0,0 +1,126 @@
<template>
  <g class="pointer" :transform="'translate(' + offset.join(',') + ')'">
    <!-- 圆角矩形 -->
    <path
      :d="createRoundRectPath(small ? 64 : 84, 68, 10)"
      stroke="#07D0C8"
      :fill="alarm ? '#FF3801' : '#0C4D77'"
    />
    <!-- 绘制标题 -->
    <text
      x="16"
      y="44"
      text-anchor="middle"
      fill="rgb(200,200,200)"
      font-size="20"
    >
      {{ type + "P" }}
    </text>
    <!-- 绘制图片 -->
    <image :x="small ? 36 : 40" y="6" :width="imgW" height="60" :xlink:href="url" />
  </g>
</template>
<script>
import SP1 from "../images/s-1p.png";
import SP2 from "../images/s-2p.png";
import SP3 from "../images/s-3p.png";
export default {
  name: "",
  props: {
    type: {
      type: Number,
      default: 1,
    },
    small: {
      type: Boolean,
      default: false,
    },
    alarm: {
      type: Boolean,
      default: false,
    },
    offset: {
      type: Array,
      default() {
        return [0, 0];
      },
    },
  },
  computed: {
    url() {
      let res = "";
      switch (this.type) {
        case 1:
          res = SP1;
          break;
        case 2:
          res = SP2;
          break;
        case 3:
          res = SP3;
          break;
      }
      return res;
    },
    imgW() {
      let res = "";
      switch (this.type) {
        case 1:
          res = 20;
          break;
        case 2:
          res = 26;
          break;
        case 3:
          res = 32;
          break;
      }
      return res;
    },
  },
  data() {
    return {};
  },
  methods: {
    createRoundRectPath(w, h, r = 5, x = 0, y = 0) {
      let p0 = [x, y];
      let p1 = [x + w, y];
      let p2 = [x + w, y + h];
      let p3 = [x, y + h];
      let cp0 = [x + r, y];
      let cp1 = [x + w - r, y];
      let cp2 = [x + w, y + r];
      let cp3 = [x + w, y + h - r];
      let cp4 = [x + w - r, y + h];
      let cp5 = [x + r, y + h];
      let cp6 = [x, y + h - r];
      let cp7 = [x, y + r];
      return `M${cp0[0]},${cp0[1]}
        L ${cp1[0]} ${cp1[1]}
        C ${cp1[0]} ${cp1[1]}, ${p1[0]} ${p1[1]}, ${cp2[0]} ${cp2[1]}
        L ${cp3[0]} ${cp3[1]}
        C ${cp3[0]} ${cp3[1]}, ${p2[0]} ${p2[1]}, ${cp4[0]} ${cp4[1]}
        L ${cp5[0]} ${cp5[1]}
        C ${cp5[0]} ${cp5[1]}, ${p3[0]} ${p3[1]}, ${cp6[0]} ${cp6[1]}
        L ${cp7[0]} ${cp7[1]}
        C ${cp7[0]} ${cp7[1]}, ${p0[0]} ${p0[1]}, ${cp0[0]} ${cp0[1]}
        Z`;
    },
  },
  mounted() {},
};
</script>
<style scoped>
.pointer {
  cursor: pointer;
}
</style>
src/views/home/components/test.vue
New file
@@ -0,0 +1,36 @@
<template>
  <div>
    <svg width="100%" height="100%" viewBox="0 0 800 600">
      <switch-box :type="3" :offset="[60, 40]"></switch-box>
      <switch-box :type="3" :offset="[180, 40]"></switch-box>
      <switch-box :type="3" :offset="[300, 40]"></switch-box>
      <switch-box :type="1" :offset="[20, 180]"></switch-box>
      <switch-box :type="1" :offset="[120, 180]"></switch-box>
      <switch-box :type="1" :offset="[220, 180]"></switch-box>
      <switch-box :type="1" :offset="[320, 180]"></switch-box>
    </svg>
  </div>
</template>
<script>
import img from "../images/hr.png";
import SwitchBox from './switchBox';
export default {
  name: "",
  data() {
    return {
      img,
    };
  },
  components: {
    SwitchBox,
  },
  methods: {},
  mounted() {},
};
</script>
<style scoped>
</style>
src/views/home/components/textBox.vue
New file
@@ -0,0 +1,67 @@
<template>
  <g :transform="'translate(' + offset.join(',') + ')'">
    <!-- 圆角矩形 -->
    <path
      :d="createRoundRectPath(84, 30, 6)"
      stroke="none"
      fill="#F69F40"
    />
    <!-- 绘制标题 -->
    <text x="42" y="20" text-anchor="middle" fill="#011F39" font-size="18">
      电操开关
    </text>
  </g>
</template>
<script>
export default {
  name: "",
  props: {
    offset: {
      type: Array,
      default() {
        return [0, 0];
      },
    },
  },
  computed: {},
  data() {
    return {
    };
  },
  methods: {
    createRoundRectPath(w, h, r = 5, x = 0, y = 0) {
      let p0 = [x, y];
      let p1 = [x + w, y];
      let p2 = [x + w, y + h];
      let p3 = [x, y + h];
      let cp0 = [x + r, y];
      let cp1 = [x + w - r, y];
      let cp2 = [x + w, y + r];
      let cp3 = [x + w, y + h - r];
      let cp4 = [x + w - r, y + h];
      let cp5 = [x + r, y + h];
      let cp6 = [x, y + h - r];
      let cp7 = [x, y + r];
      return `M${cp0[0]},${cp0[1]}
        L ${cp1[0]} ${cp1[1]}
        C ${cp1[0]} ${cp1[1]}, ${p1[0]} ${p1[1]}, ${cp2[0]} ${cp2[1]}
        L ${cp3[0]} ${cp3[1]}
        C ${cp3[0]} ${cp3[1]}, ${p2[0]} ${p2[1]}, ${cp4[0]} ${cp4[1]}
        L ${cp5[0]} ${cp5[1]}
        C ${cp5[0]} ${cp5[1]}, ${p3[0]} ${p3[1]}, ${cp6[0]} ${cp6[1]}
        L ${cp7[0]} ${cp7[1]}
        C ${cp7[0]} ${cp7[1]}, ${p0[0]} ${p0[1]}, ${cp0[0]} ${cp0[1]}
        Z`;
    },
  },
  mounted() {},
};
</script>
<style scoped>
</style>
src/views/home/images/bhq.png
src/views/home/images/hr.png
src/views/home/images/kgg.png
src/views/home/images/pdg.png
src/views/home/images/s-1p.png
src/views/home/images/s-2p.png
src/views/home/images/s-3p.png
src/views/home/index.vue
@@ -1,317 +1,54 @@
<script>
import { mapState } from "vuex";
import { updateDfu, readFileList, getDevFileName, stopDfu } from "./api";
import propConfig from "./js/props";
import SwitchBox from "./components/switchBox";
import ProtectorBox from "./components/protectorBox";
import TextBox from "./components/textBox";
import SvgLine from "./components/svgline";
import FileProcess from "./fileProcess";
import getWebUrl from "@/assets/js/getWebUrl";
import Panel from "@/components/panel.vue";
import pdgImg from "./images/pdg.png";
import kggImg from "./images/kgg.png";
import hrImg from "./images/hr.png";
import createWs from "@/assets/js/websocket/plus";
const WSMixin = createWs("dev", "dfu");
const WORKSTATE = ["通信故障", "通信正常", "远程升级中", "文件下载中"];
const { PDG, KGG, HR } = propConfig;
export default {
  name: "home",
  mixins: [WSMixin],
  // mixins: [WSMixin],
  components: {
    FileProcess,
    Panel,
    SwitchBox,
    ProtectorBox,
    TextBox,
    SvgLine,
  },
  data() {
    const baseURL = getWebUrl();
    return {
      updatePercent: 0,
      baseURL,
      fileListVisible: false,
      updateVisible: false,
      battListVisible: false,
      tableData: [],
      headers: [
        {
          prop: "devIp",
          label: "设备Ip",
          width: "180",
        },
        {
          prop: "devVersion",
          label: "设备版本号",
          width: "240",
        },
        {
          prop: "devId",
          label: "设备ID",
          width: "180",
        },
        {
          prop: "recordDatetime",
          label: "更新时间",
          width: "180",
        },
        {
          prop: "devEachgroupBattsum",
          label: "电池组数",
          width: "120",
        },
        {
          prop: "devState",
          label: "设备工作状态",
          width: "120",
        },
        {
          prop: "devCommcount",
          label: "设备通信计数",
          width: "120",
        },
        {
          prop: "devErrcommcount",
          label: "设备通信错误计数",
          width: "150",
        },
      ],
      tableBattsData: [],
      battsHeaders: [
        {
          prop: "battName",
          label: "电池组名称",
          width: "240",
        },
      ],
      fileList: [],
      currDevId: 0,
      transferFiles: {},
      pcFileListVisible: false,
      pcFileList: [],
      multipleSelection: [],
      PDG,
      KGG,
      HR,
      pdgImg,
      kggImg,
      hrImg,
    };
  },
  methods: {
    onWSMessage1(res) {
      res = JSON.parse(res.data);
      let sys_time = new Date(res.data3).getTime();
      let data = res.data2.map((v) => {
        v.isTimeout =
          Math.abs(new Date(v.recordDatetime).getTime() - sys_time) > 1000 * 60;
        v.devState = v.isTimeout ? "连接超时" : WORKSTATE[v.devWorkstate];
        v.batts = v.battnamelist.split(",");
        return v;
      });
      // console.log(data, "=====data", sys_time);
      this.tableData = data;
    },
    onWSMessage2(res) {
      res = JSON.parse(res.data);
      let { dfuDataBlocklen, dfuDataBlocknum } = res.data2;
      if (!dfuDataBlocklen) {
        this.updatePercent = 0;
        return false;
      }
      this.updatePercent =
        Math.round((dfuDataBlocknum / dfuDataBlocklen) * 10000) / 100;
    },
    sendMessage2() {
      if (!this.isWSOpen2) {
        setTimeout(this.sendMessage2, 500);
        return false;
      }
      this.SOCKET2.send(JSON.stringify(this.currDevId));
    },
    canUpdate(record) {
      let { isTimeout, devWorkstate } = record;
      return !isTimeout && (devWorkstate == 1 || devWorkstate == 2);
    },
    canRead(record) {
      let { isTimeout, devWorkstate } = record;
      return !isTimeout && (devWorkstate == 1 || devWorkstate == 3);
    },
    showBatts(record) {
      this.currDevId = record.devId;
      this.tableBattsData = record.batts.map((v, i) => ({
        battName: v,
        idx: i,
      }));
      this.battListVisible = true;
    },
    update(record) {
      this.currDevId = record.devId;
      this.fileList = [];
      this.updateVisible = true;
      this.sendMessage2();
    },
    fileChange(file, fileList) {
      // console.log(file, fileList, "change");
      this.fileList = [file];
    },
    fileRemove(file, fileList) {
      this.fileList = fileList;
    },
    stopUpdate() {
      let loading = this.$layer.loading();
      stopDfu(this.currDevId)
        .then((res) => {
          let { code, data } = res.data;
          if (code && data) {
            this.$message.success("操作成功");
          } else {
            this.$message.error("操作失败");
          }
          this.$layer.close(loading);
        })
        .catch((err) => {
          this.$layer.close(loading);
          console.log(err);
        });
    },
    upload() {
      let formData = new FormData();
      formData.append("file", this.fileList[0].raw);
      formData.append("devId", this.currDevId);
      let loading = this.$layer.loading();
      updateDfu(formData)
        .then((res) => {
          let { code, data, msg } = res.data;
          if (code && data) {
            this.$message.success(msg);
          } else {
            this.$message.error(msg);
          }
          this.$layer.close(loading);
        })
        .catch((err) => {
          this.$layer.close(loading);
          console.log(err);
        });
    },
    showFiles(record) {
      // 如果是下载中 则不请求数据 进组件 查websocket
      if (this.isTransfering) {
        this.fileListVisible = true;
        this.transferFiles = {};
        return;
      }
      let battIndex = record.idx;
      let devId = this.currDevId;
      let fileIndex = 0;
      let loading = this.$layer.loading();
      readFileList(battIndex, devId, fileIndex)
        .then((res) => {
          let { code, data, data2, msg } = res.data;
          if (code && data) {
            this.$message.success(msg);
            this.fileListVisible = true;
            this.transferFiles = data2;
          } else {
            this.$message.error(msg);
            this.transferFiles = {};
          }
          this.$layer.close(loading);
        })
        .catch((err) => {
          this.$layer.close(loading);
          console.log(err);
        });
    },
    showPCFiles(record) {
      let battName = record.battName;
      let devId = this.currDevId;
      let loading = this.$layer.loading();
      getDevFileName(battName, devId)
        .then((res) => {
          let { code, data, data2 } = res.data;
          let list = [];
          if (code && data) {
            // console.log(data);
            list = data2.map((v) => {
              let url = v;
              let fileName = url.split("\\").pop();
              return {
                url,
                fileName,
              };
            });
          }
          this.$layer.close(loading);
          this.pcFileList = list;
          this.pcFileListVisible = true;
        })
        .catch((err) => {
          this.$layer.close(loading);
          console.log(err);
        });
    },
    handleSelectionChange(val) {
      // console.log(val, "selection");
      this.multipleSelection = val;
    },
    downloadFile(url) {
      // const fileName = url.split("\\").pop();
      // let link = document.createElement("a");
      // link.style.display = "none";
      // link.href = this.baseURL + url;
      // link.download = fileName;
      // document.body.appendChild(link);
      // link.click();
      // document.body.removeChild(link);
      const iframe = document.createElement("iframe");
      iframe.style.display = "none";
      iframe.src = this.baseURL + url;
      document.body.appendChild(iframe);
      setTimeout(() => {
        iframe.remove();
      }, 10 * 1000);
    },
    downloadFiles() {
      // console.log(this.multipleSelection, "??z");
      this.multipleSelection.forEach((v, i) => {
        this.downloadFile(v.url);
      });
    },
    hover() {
      console.log('hhhh');
    }
  },
  computed: {
    ...mapState({
      cachedViews: (state) => state.tagsView.cachedViews,
    }),
    isTransfering() {
      if (!this.currDevId) {
        return false;
      }
      let obj = this.tableData.filter((v) => v.devId == this.currDevId)[0];
      let { isTimeout, devWorkstate } = obj;
      // console.log("是否下载中", !isTimeout && devWorkstate == 3);
      return !isTimeout && devWorkstate == 3;
    },
    isUpdateing() {
      if (!this.currDevId) {
        return false;
      }
      let obj = this.tableData.filter((v) => v.devId == this.currDevId)[0];
      let { isTimeout, devWorkstate } = obj;
      return !isTimeout && devWorkstate == 2;
    },
    readingBattName() {
      if (!this.currDevId) {
        return false;
      }
      let obj = this.tableData.filter((v) => v.devId == this.currDevId)[0];
      return obj.nowDownloadBatt;
    },
    progressColor() {
      if (this.updatePercent < 30) {
        return "#ff0000";
      }
      if (this.updatePercent < 60) {
        return "#AA40F0";
      }
      if (this.updatePercent == 100) {
        return "#009900";
      } else {
        return "#4840F0";
      }
    },
    progressTextColor() {
      if (this.updatePercent < 8) {
        return "#00f7f9";
      }
      return "#ffffff";
    },
  },
  mounted() {},
};
@@ -319,210 +56,433 @@
<template>
  <div class="p-container" ref="root">
    <div class="p-title">设备列表</div>
    <el-table
      ref="table"
      :data="tableData"
      border
      height="100%"
      style="width: 100%"
      tooltip-effect="light"
    >
      <el-table-column type="index" label="序号" width="80"></el-table-column>
      <el-table-column
        v-for="header in headers"
        :key="header.prop"
        :prop="header.prop"
        :label="header.label"
        :min-width="header.width"
        align="center"
        show-overflow-tooltip
      ></el-table-column>
      <el-table-column label="操作" fixed="right" width="180" align="center">
        <template slot-scope="scope">
          <el-button
            type="primary"
            size="mini"
            :disabled="!canRead(scope.row)"
            @click="showBatts(scope.row)"
            >电池组列表</el-button
          >
          <el-button
            type="danger"
            :disabled="!canUpdate(scope.row)"
            size="mini"
            @click="update(scope.row)"
            >升级</el-button
          >
        </template>
      </el-table-column>
    </el-table>
    <!-- 升级弹窗 -->
    <el-dialog
      title="电池组列表"
      :visible.sync="updateVisible"
      top="0"
      :close-on-click-modal="false"
      class="dialog-center"
      width="700px"
      center
    >
      <el-upload
        v-if="!isUpdateing"
        class="upload-demo"
        ref="upload"
        action=""
        :multiple="false"
        :on-remove="fileRemove"
        :on-change="fileChange"
        :file-list="fileList"
        accept=".sm5"
        :auto-upload="false"
      >
        <el-button slot="trigger" size="small" type="primary"
          >选取文件</el-button
        >
      </el-upload>
      <template v-else>
        <div class="s-title">升级中</div>
        <el-progress
          :text-inside="true"
          :stroke-width="24"
          :percentage="updatePercent"
          :color="progressColor"
          :text-color="progressTextColor"
        ></el-progress>
      </template>
      <span slot="footer" class="dialog-footer">
        <el-button @click="updateVisible = false">关闭</el-button>
        <el-button
          v-if="!isUpdateing"
          type="primary"
          :disabled="!fileList.length"
          @click="upload"
          >上传并升级</el-button
        >
        <el-button v-else type="primary" @click="stopUpdate"
          >终止升级</el-button
        >
      </span>
    </el-dialog>
    <!-- 弹窗 -->
    <el-dialog
      title="电池组列表"
      :visible.sync="battListVisible"
      top="0"
      :close-on-click-modal="false"
      class="dialog-center"
      width="700px"
      center
    >
      <el-table
        ref="table"
        :data="tableBattsData"
        border
        max-height="300"
        style="width: 100%"
        tooltip-effect="light"
      >
        <el-table-column type="index" label="序号" width="80"></el-table-column>
        <el-table-column
          v-for="header in battsHeaders"
          :key="header.prop"
          :prop="header.prop"
          :label="header.label"
          :min-width="header.width"
          align="center"
          show-overflow-tooltip
        ></el-table-column>
        <el-table-column label="操作" fixed="right" width="260" align="center">
          <template slot-scope="scope">
            <el-button
              type="primary"
              size="mini"
              :disabled="
                isTransfering &&
                !!readingBattName &&
                readingBattName != scope.row.battName
              "
              @click="showFiles(scope.row)"
              >读取文件列表</el-button
            >
            <el-button
              type="primary"
              size="mini"
              @click="showPCFiles(scope.row)"
              >已导入文件列表</el-button
            >
          </template>
        </el-table-column>
      </el-table>
    </el-dialog>
    <!-- 服务器文件列表 -->
    <el-dialog
      title="已导入文件列表"
      :visible.sync="pcFileListVisible"
      top="0"
      :close-on-click-modal="false"
      class="dialog-center"
      width="700px"
      center
    >
      <div class="btn-grp">
        <el-button
          type="primary"
          size="mini"
          @click="downloadFiles"
          :disabled="!multipleSelection.length"
          >下载选中项</el-button
        >
    <panel class="panel left" title="通信电源柜、核容主机状态">
      <div class="content">
        <div class="info info1">
          <div class="side">
            <div class="info-title">交直流配电柜</div>
            <div class="img">
              <el-image :src="pdgImg" fit="fill"></el-image>
            </div>
            <div class="state">
              <div class="s-row">
                运行:
                <div class="i"></div>
              </div>
              <div class="s-row">
                通信:
                <div class="i danger"></div>
              </div>
            </div>
          </div>
          <div class="main">
            <div class="list">
              <div class="row-item" v-for="idx in 9" :key="'pdg_' + idx">
                {{ idx - 1 }}{{ PDG[idx - 1].label }}:
              </div>
            </div>
            <div class="list">
              <div class="row-item" v-for="idx of 9" :key="'pdg2_' + idx">
                <template v-if="PDG[idx + 8]"
                  >{{ idx + 8 }}{{ PDG[idx + 8].label }}:</template
                >
              </div>
            </div>
          </div>
        </div>
        <div class="info info2">
          <div class="side">
            <div class="info-title">高频开关柜</div>
            <div class="img">
              <el-image :src="kggImg" fit="fill"></el-image>
            </div>
            <div class="state">
              <div class="s-row">
                运行:
                <div class="i"></div>
              </div>
              <div class="s-row">
                通信:
                <div class="i danger"></div>
              </div>
            </div>
          </div>
          <div class="main">
            <div class="list">
              <div class="row-item" v-for="idx in 6" :key="'kgg_' + idx">
                {{ idx - 1 }}{{ KGG[idx - 1].label }}:
              </div>
            </div>
            <div class="list">
              <div class="row-item" v-for="idx of 6" :key="'kgg2_' + idx">
                <template v-if="KGG[idx + 5]"
                  >{{ idx + 5 }}{{ KGG[idx + 5].label }}:</template
                >
              </div>
            </div>
          </div>
        </div>
        <div class="info info3">
          <div class="side">
            <div class="info-title">核容装置</div>
            <div class="img img3">
              <el-image :src="hrImg" fit="fill"></el-image>
            </div>
            <div class="state">
              <div class="s-row">
                运行:
                <div class="i"></div>
              </div>
              <div class="s-row">
                通信:
                <div class="i danger"></div>
              </div>
            </div>
          </div>
          <div class="main">
            <div class="list">
              <div class="row-item" v-for="idx in 4" :key="'hr_' + idx">
                <template v-if="idx < 4"
                  >{{ idx - 1 }}{{ HR[idx - 1].label }}:</template
                >
              </div>
            </div>
            <div class="list">
              <div class="row-item" v-for="idx of 4" :key="'hr2_' + idx">
                <template v-if="idx < 4 && HR[idx + 2]"
                  >{{ idx + 2 }}{{ HR[idx + 2].label }}:</template
                >
              </div>
            </div>
          </div>
        </div>
      </div>
      <el-table
        ref="table"
        :data="pcFileList"
        border
        max-height="300"
        style="width: 100%"
        @selection-change="handleSelectionChange"
        tooltip-effect="light"
      >
        <el-table-column type="selection" width="55"> </el-table-column>
        <el-table-column type="index" label="序号" width="80"></el-table-column>
        <el-table-column
          prop="fileName"
          label="文件名称"
          align="center"
          show-overflow-tooltip
        ></el-table-column>
        <el-table-column label="操作" fixed="right" width="160" align="center">
          <template slot-scope="scope">
            <el-button
              type="primary"
              size="mini"
              @click="downloadFile(scope.row.url)"
              >下载</el-button
            >
          </template>
        </el-table-column>
      </el-table>
    </el-dialog>
    <!-- 传输进度弹窗 -->
    <el-dialog
      title="电池组文件列表"
      :visible.sync="fileListVisible"
      top="0"
      :close-on-click-modal="false"
      class="dialog-center"
      width="700px"
      center
    >
      <file-process
        v-if="fileListVisible"
        :dev-id="currDevId"
        :transfer="isTransfering"
        :infos="transferFiles"
      ></file-process>
    </el-dialog>
    </panel>
    <panel class="panel right" title="交流/直流微断路器状态">
      <div class="content">
        <!--  -->
        <div class="yc-row row1">
          <div class="yc-panel">
            <div class="yc-title">交流进线1</div>
            <div class="yc-content">
              <div class="svg-contain">
                <div class="pos-full">
                  <svg width="100%" height="100%" viewBox="0 0 500 340">
                    <text-box :offset="[220, 10]"></text-box>
                    <protector-box :offset="[36, 10]"></protector-box>
                    <switch-box :type="3" :offset="[36, 134]"></switch-box>
                    <switch-box :type="3" :offset="[220, 134]"></switch-box>
                    <switch-box :type="3" :offset="[390, 134]"></switch-box>
                    <switch-box :type="1" :offset="[36, 252]"></switch-box>
                    <switch-box :type="1" :offset="[150, 252]"></switch-box>
                    <switch-box :type="1" :offset="[274, 252]"></switch-box>
                    <switch-box :type="1" :offset="[390, 252]"></switch-box>
                    <svg-line
                      :points="[
                        [262, 40],
                        [262, 134],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [128, 70],
                        [262, 70],
                      ]"
                    ></svg-line>
                    <svg-line
                      :radius="10"
                      :points="[
                        [78, 134],
                        [78, 110],
                        [432, 110],
                        [432, 134],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [98, 202],
                        [98, 228],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [262, 202],
                        [262, 228],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [412, 202],
                        [412, 228],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [192, 228],
                        [192, 252],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [316, 228],
                        [316, 252],
                      ]"
                    ></svg-line>
                    <svg-line
                      :radius="10"
                      :points="[
                        [78, 252],
                        [78, 228],
                        [432, 228],
                        [432, 252],
                      ]"
                    ></svg-line>
                  </svg>
                </div>
              </div>
              <div class="yc-panel-footer">
                <div class="state">防雷保护器空开跳闸</div>
              </div>
            </div>
          </div>
          <div class="yc-panel">
            <div class="yc-title">交流进线2</div>
            <div class="yc-content">
              <div class="svg-contain">
                <div class="pos-full">
                  <svg width="100%" height="100%" viewBox="0 0 500 340">
                    <text-box :offset="[220, 10]"></text-box>
                    <protector-box :offset="[390, 10]"></protector-box>
                    <switch-box :type="3" :offset="[36, 134]"></switch-box>
                    <switch-box :type="3" :offset="[220, 134]"></switch-box>
                    <switch-box :type="3" :offset="[390, 134]"></switch-box>
                    <switch-box :type="1" :offset="[36, 252]"></switch-box>
                    <switch-box :type="1" :offset="[150, 252]"></switch-box>
                    <switch-box :type="1" :offset="[274, 252]"></switch-box>
                    <switch-box :type="1" :offset="[390, 252]"></switch-box>
                    <svg-line
                      :points="[
                        [262, 40],
                        [262, 134],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [390, 70],
                        [262, 70],
                      ]"
                    ></svg-line>
                    <svg-line
                      :radius="10"
                      :points="[
                        [78, 134],
                        [78, 110],
                        [432, 110],
                        [432, 134],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [98, 202],
                        [98, 228],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [262, 202],
                        [262, 228],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [412, 202],
                        [412, 228],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [192, 228],
                        [192, 252],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [316, 228],
                        [316, 252],
                      ]"
                    ></svg-line>
                    <svg-line
                      :radius="10"
                      :points="[
                        [78, 252],
                        [78, 228],
                        [432, 228],
                        [432, 252],
                      ]"
                    ></svg-line>
                  </svg>
                </div>
              </div>
              <div class="yc-panel-footer">
                <div class="state"></div>
              </div>
            </div>
          </div>
        </div>
        <div class="yc-row">
          <div class="yc-panel">
            <div class="yc-title">直流进线1</div>
            <div class="yc-content">
              <div class="svg-contain">
                <div class="pos-full">
                  <svg width="100%" height="100%" viewBox="0 0 622 240">
                    <switch-box @click.native="hover" :type="2" :offset="[269, 14]"></switch-box>
                    <switch-box @click.native="hover" small :offset="[20, 160]"></switch-box>
                    <switch-box small :offset="[94, 160]"></switch-box>
                    <switch-box small :offset="[168, 160]"></switch-box>
                    <switch-box alarm small :offset="[242, 160]"></switch-box>
                    <switch-box small :offset="[316, 160]"></switch-box>
                    <switch-box small :offset="[390, 160]"></switch-box>
                    <switch-box small :offset="[464, 160]"></switch-box>
                    <switch-box small :offset="[538, 160]"></switch-box>
                    <svg-line
                      :points="[
                        [311, 82],
                        [311, 116],
                      ]"
                    ></svg-line>
                    <svg-line
                      :radius="10"
                      :points="[
                        [52, 160],
                        [52, 116],
                        [570, 116],
                        [570, 160],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [126, 160],
                        [126, 116],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [200, 160],
                        [200, 116],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [274, 160],
                        [274, 116],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [348, 160],
                        [348, 116],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [422, 160],
                        [422, 116],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [496, 160],
                        [496, 116],
                      ]"
                    ></svg-line>
                  </svg>
                </div>
              </div>
              <div class="yc-panel-footer">
                <div class="state"></div>
              </div>
            </div>
          </div>
          <div class="yc-panel">
            <div class="yc-title">直流进线2</div>
            <div class="yc-content">
              <div class="svg-contain">
                <div class="pos-full">
                  <svg width="100%" height="100%" viewBox="0 0 622 240">
                    <switch-box :type="2" :offset="[269, 14]"></switch-box>
                    <switch-box small :offset="[20, 160]"></switch-box>
                    <switch-box small :offset="[94, 160]"></switch-box>
                    <switch-box small :offset="[168, 160]"></switch-box>
                    <switch-box small :offset="[242, 160]"></switch-box>
                    <switch-box small :offset="[316, 160]"></switch-box>
                    <switch-box small :offset="[390, 160]"></switch-box>
                    <switch-box small :offset="[464, 160]"></switch-box>
                    <switch-box small :offset="[538, 160]"></switch-box>
                    <svg-line
                      :points="[
                        [311, 82],
                        [311, 116],
                      ]"
                    ></svg-line>
                    <svg-line
                      :radius="10"
                      :points="[
                        [52, 160],
                        [52, 116],
                        [570, 116],
                        [570, 160],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [126, 160],
                        [126, 116],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [200, 160],
                        [200, 116],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [274, 160],
                        [274, 116],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [348, 160],
                        [348, 116],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [422, 160],
                        [422, 116],
                      ]"
                    ></svg-line>
                    <svg-line
                      :points="[
                        [496, 160],
                        [496, 116],
                      ]"
                    ></svg-line>
                  </svg>
                </div>
              </div>
              <div class="yc-panel-footer">
                <div class="state"></div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </panel>
  </div>
</template>
@@ -530,7 +490,8 @@
.p-container {
  height: 100%;
  display: flex;
  flex-direction: column;
  flex-direction: row;
  padding: 10px;
}
.p-title {
  font-size: 20px;
@@ -545,4 +506,199 @@
  font-weight: bolder;
  padding-bottom: 10px;
}
.panel {
  color: #fff;
  // height: 100%;
  flex: 1;
  &.right {
    margin-left: 10px;
    flex: 1.44;
  }
  &.left .content {
    height: 100%;
    padding: 10px;
    display: flex;
    flex-direction: column;
    .info {
      border: 1px #02b4c0 solid;
      border-radius: 4px;
      display: flex;
      flex-direction: row;
      background: #011f39;
      overflow: hidden;
      .side {
        width: 160px;
        background: #0c4d77;
        padding: 0 4px 10px;
        border-right: 1px #02b4c0 solid;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: space-between;
      }
      .img {
        width: 80px;
        height: 120px;
      }
      .img3 {
        width: 120px;
        height: 50px;
      }
      .info-title {
        font-size: 18px;
        padding-top: 10px;
        letter-spacing: 2px;
        text-align: center;
      }
      .state {
        background: #f69f41;
        border-radius: 4px;
        font-weight: bold;
        align-self: stretch;
        color: #1b2d3a;
        .s-row {
          display: flex;
          justify-content: center;
          align-items: center;
        }
        .i {
          margin-left: 20px;
          display: inline-block;
          width: 20px;
          height: 20px;
          border-radius: 50%;
          background: radial-gradient(
            circle farthest-side at 50% 50%,
            rgba(1, 31, 57, 0.5) 50%,
            rgba(74, 253, 136, 0.5) 100%
          );
          text-align: center;
          position: relative;
          &::after {
            content: "";
            display: inline-block;
            position: absolute;
            border-radius: 50%;
            top: 4px;
            right: 4px;
            bottom: 4px;
            left: 4px;
            background: #4afd88;
          }
          &.danger {
            background: radial-gradient(
              circle farthest-side at 50% 50%,
              rgba(1, 31, 57, 0.5) 50%,
              rgba(255, 56, 1, 0.5) 100%
            );
            &::after {
              background: #ff3801;
            }
          }
        }
      }
      .main {
        flex: 1;
        display: flex;
        padding: 0 6px;
        .list {
          flex: 1;
          display: flex;
          flex-direction: column;
          padding: 10px 0;
          & + .list {
            margin-left: 6px;
          }
          .row-item {
            flex: 1;
            display: flex;
            align-items: center;
            padding-left: 10px;
            // height: 20px;
            // line-height: 20px;
            background: #153952;
            &:nth-child(2n) {
              background: rgba(21, 57, 82, 0.6);
            }
          }
        }
      }
      &.info1 {
        flex: 10;
      }
      &.info2 {
        flex: 7;
      }
      &.info3 {
        flex: 5;
      }
      & + .info {
        margin-top: 10px;
      }
    }
  }
  &.right .content {
    height: 100%;
    padding: 10px;
    display: flex;
    flex-direction: column;
  }
  .yc-row {
    flex: 3;
    display: flex;
    &.row1 {
      flex: 4;
    }
    & + .yc-row {
      margin-top: 10px;
    }
    .yc-panel {
      flex: 1;
      border: 1px solid #4da2b1;
      border-radius: 6px;
      overflow: hidden;
      display: flex;
      flex-direction: column;
      & + .yc-panel {
        margin-left: 10px;
      }
      .yc-title {
        text-align: center;
        font-size: 18px;
        height: 36px;
        line-height: 36px;
        background: #0c4d77;
      }
      .yc-content {
        flex: 1;
        display: flex;
        flex-direction: column;
        background: #011f39;
        .svg-contain {
          flex: 1;
          position: relative;
          .pos-full {
            position: absolute;
            left: 0;
            top: 0;
            right: 0;
            bottom: 0;
          }
        }
        .yc-panel-footer {
          padding: 0 20px 10px;
          .state {
            background: #78eef8;
            height: 36px;
            line-height: 36px;
            color: #011f39;
            font-weight: bold;
            border-radius: 4px;
            padding-left: 0.8em;
          }
        }
      }
    }
  }
}
</style>
src/views/home/js/props.js
New file
@@ -0,0 +1,155 @@
const PDG = [
  {
    label: '1路交流输入A相电压(V)',
    key: '',
  },
  {
    label: '1路交流输入B相电压(V)',
    key: '',
  },
  {
    label: '1路交流输入C相电压(V)',
    key: '',
  },
  {
    label: '1路交流输入A相电流(A)',
    key: '',
  },
  {
    label: '1路交流输入B相电流(A)',
    key: '',
  },
  {
    label: '1路交流输入C相电流(A)',
    key: '',
  },
  {
    label: '直流电压(V)',
    key: '',
  },
  {
    label: '电池组电压(V)',
    key: '',
  },
  {
    label: '机柜温度(℃)',
    key: '',
  },
  {
    label: '2路交流输入A相电压(V)',
    key: '',
  },
  {
    label: '2路交流输入B相电压(V)',
    key: '',
  },
  {
    label: '2路交流输入C相电压(V)',
    key: '',
  },
  {
    label: '2路交流输入A相电流(A)',
    key: '',
  },
  {
    label: '2路交流输入B相电流(A)',
    key: '',
  },
  {
    label: '2路交流输入C相电流(A)',
    key: '',
  },
  {
    label: '直流电流(A)',
    key: '',
  },
  {
    label: '电池组电流(A)',
    key: '',
  },
  {
    label: '故障报警',
    key: '',
  },
];
const KGG = [
  {
    label: '交流输入电压(V)',
    key: '',
  },
  {
    label: '交流输入电流(A)',
    key: '',
  },
  {
    label: '电池电压(V)',
    key: '',
  },
  {
    label: '负载电流(A)',
    key: '',
  },
  {
    label: '开关状态',
    key: '',
  },
  {
    label: '故障报警',
    key: '',
  },
  {
    label: '直流输出电压(V)',
    key: '',
  },
  {
    label: '直流输出电流(A)',
    key: '',
  },
  {
    label: '电池(充电)电流(A)',
    key: '',
  },
  {
    label: '直流熔丝状态',
    key: '',
  },
  {
    label: '机柜温度(℃)',
    key: '',
  },
];
const HR = [
  {
    label: '系统工作状态',
    key: '',
  },
  {
    label: '电池组电流',
    key: '',
  },
  {
    label: '最高单体电压',
    key: '',
  },
  {
    label: '电池组电压',
    key: '',
  },
  {
    label: '在线端电压',
    key: '',
  },
  {
    label: '最低单体电压',
    key: '',
  },
];
export default {
  PDG,
  KGG,
  HR
}
src/views/login/index.vue
@@ -1,7 +1,7 @@
<script>
import { validUsername } from "@/utils/validate";
import { login } from "./api";
import { mapMutations } from 'vuex';
import { mapMutations } from "vuex";
export default {
  name: "loginPage",
  data() {
@@ -13,7 +13,7 @@
      }
    };
    const validatePassword = (rule, value, callback) => {
      if (value.trim() == '') {
      if (value.trim() == "") {
        callback(new Error("密码不能为空"));
      } else {
        callback();
@@ -21,10 +21,10 @@
    };
    return {
      loginForm: {
        // username: "hw",
        // password: "123456",
        username: "",
        password: "",
        username: "hw",
        password: "123456",
        // username: "",
        // password: "",
      },
      loginRules: {
        username: [
@@ -43,7 +43,7 @@
    };
  },
  methods: {
    ...mapMutations('user', ['SETUSER', 'SETDOWNLOAD', 'SETUID']),
    ...mapMutations("user", ["SETUSER", "SETDOWNLOAD", "SETUID"]),
    checkCapslock(e) {
      const { key } = e;
      this.capsTooltip = key && key.length === 1 && key >= "A" && key <= "Z";
@@ -55,11 +55,11 @@
          let { code, data, data2 } = res.data;
          if (code && data) {
            // console.log(data);
            this.$message.success('登录成功');
            this.$message.success("登录成功");
            this.SETUSER(data2.uname);
            this.SETDOWNLOAD(data2.udownloadRole);
            this.SETUID(data2.uid);
            this.$router.push('/');
            this.$router.push("/");
          } else {
            this.$message.error(msg);
          }
@@ -87,71 +87,73 @@
<template>
  <div class="login-container">
    <el-form
      ref="loginForm"
      :model="loginForm"
      :rules="loginRules"
      class="login-form"
      autocomplete="on"
      label-position="left"
    >
      <div class="title-container">
        <h3 class="title">登录</h3>
      </div>
      <el-form-item prop="username">
        <span class="svg-container">
          <svg-icon icon-class="user" />
        </span>
        <el-input
          ref="username"
          v-model="loginForm.username"
          placeholder="Username"
          name="username"
          type="text"
          tabindex="1"
          autocomplete="on"
        />
      </el-form-item>
      <el-tooltip
        v-model="capsTooltip"
        content="输入了大写字母"
        placement="right"
        manual
    <div class="page-title">通信电源监控主站测控系统</div>
    <div class="login-wrap">
      <el-form
        ref="loginForm"
        :model="loginForm"
        :rules="loginRules"
        class="login-form"
        autocomplete="on"
        label-position="left"
      >
        <el-form-item prop="password">
        <div class="title-container">
          <h3 class="title">用户登录</h3>
        </div>
        <el-form-item prop="username">
          <span class="svg-container">
            <svg-icon icon-class="password" />
            <svg-icon icon-class="user" />
          </span>
          <el-input
            :key="passwordType"
            ref="password"
            v-model="loginForm.password"
            :type="passwordType"
            placeholder="Password"
            name="password"
            tabindex="2"
            ref="username"
            v-model="loginForm.username"
            placeholder="请输入用户名"
            name="username"
            type="text"
            tabindex="1"
            autocomplete="on"
            @keyup.native="checkCapslock"
            @blur="capsTooltip = false"
            @keyup.enter.native="handleLogin"
          />
          <span class="show-pwd" @click="showPwd">
            <svg-icon
              :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'"
            />
          </span>
        </el-form-item>
      </el-tooltip>
      <el-button
        :loading="loading"
        type="primary"
        style="width: 100%; margin-bottom: 30px"
        @click.native.prevent="handleLogin"
        >Login</el-button
      >
    </el-form>
        <el-tooltip
          v-model="capsTooltip"
          content="输入了大写字母"
          placement="right"
          manual
        >
          <el-form-item prop="password">
            <span class="svg-container">
              <svg-icon icon-class="password" />
            </span>
            <el-input
              :key="passwordType"
              ref="password"
              v-model="loginForm.password"
              :type="passwordType"
              placeholder="请输入密码"
              name="password"
              tabindex="2"
              autocomplete="on"
              @keyup.native="checkCapslock"
              @blur="capsTooltip = false"
              @keyup.enter.native="handleLogin"
            />
            <span class="show-pwd" @click="showPwd">
              <svg-icon
                :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'"
              />
            </span>
          </el-form-item>
        </el-tooltip>
        <div class="btn-grp">
          <el-button
            class="btn-login"
            :loading="loading"
            @click.native.prevent="handleLogin"
            >登录</el-button
          >
        </div>
      </el-form>
    </div>
  </div>
</template>
@@ -168,7 +170,7 @@
      -webkit-appearance: none;
      border-radius: 0;
      padding: 12px 5px 12px 15px;
      color: #ffffff;
      color: #192e41;
      height: 47px;
      caret-color: #283443;
@@ -180,8 +182,8 @@
  }
  .el-form-item {
    border: 1px solid rgba(255, 255, 255, 0.1);
    background: rgba(0, 0, 0, 0.1);
    border: 2px solid #e4e4e4;
    background: transparent;
    border-radius: 5px;
    color: #454545;
  }
@@ -192,16 +194,53 @@
.login-container {
  min-height: 100%;
  width: 100%;
  background-color: #2d3a4b;
  // background: url('images/bg.jpg') center center / 100% 100% no-repeat;
  background: url("images/bg.jpg") center center / cover no-repeat;
  // background-color: #2d3a4b;
  overflow: hidden;
  .login-form {
    position: relative;
    width: 520px;
    max-width: 100%;
    padding: 160px 35px 0;
    margin: 0 auto;
    overflow: hidden;
  .page-title {
    font-weight: bolder;
    font-size: 50px;
    color: #fff;
    padding-top: 80px;
    padding-left: 80px;
  }
  .login-wrap {
    position: absolute;
    top: 50%;
    right: 0;
    width: 460px;
    transform: translate(-44%, -50%);
    .login-form {
      background: #fff;
      // max-width: 100%;
      padding: 50px 35px 30px;
      border-radius: 12px;
      letter-spacing: 2px;
      // margin: 0 auto;
      // overflow: hidden;
      z-index: 0;
    }
    &::before,
    &::after {
      position: absolute;
      content: "";
      left: -20px;
      right: -20px;
      top: 36px;
      bottom: 36px;
      background: rgba(255, 255, 255, 0.2);
      border-radius: 10px;
      z-index: -1;
    }
    &::after {
      top: 72px;
      bottom: 72px;
      left: -40px;
      right: -40px;
    }
  }
  .tips {
@@ -218,18 +257,50 @@
  .svg-container {
    padding: 6px 5px 6px 15px;
    color: #889aa4;
    color: #505050;
    vertical-align: middle;
    width: 30px;
    width: 38px;
    display: inline-block;
    position: relative;
  }
  .svg-container::after {
    content: "";
    position: absolute;
    top: 50%;
    right: 0;
    width: 1px;
    height: 42%;
    transform: translate(5px, -50%);
    background: #e4e4e4;
  }
  .btn-grp {
    width: 100%;
    padding: 120px 20px 60px;
  }
  .btn-login {
    // display: block;
    width: 100%;
    letter-spacing: 6px;
    line-height: 28px;
    color: #f0f0f0;
    border: 0 none;
    border-radius: 6px;
    font-size: 16px;
    background: linear-gradient(to right, #059fab 0, #09335b 100%);
    box-shadow: 10px 10px 20px -10px #000;
  }
  // /deep/ .el-form-item__content {
  //   background: transparent;
  //   border: 1px solid #000;
  //   border-radius: 4px;
  // }
  .title-container {
    position: relative;
    .title {
      font-size: 26px;
      color: #eee;
      color: #1d3748;
      margin: 0 auto 40px auto;
      text-align: center;
      font-weight: bold;
src/views/realTime/index.vue
New file
@@ -0,0 +1,751 @@
<template>
  <div class="p-main">
    <el-tabs class="tab" type="border-card"  @tab-click="tabClick">
      <el-tab-pane label="交流/直流配电柜">
        <div class="tab-content content1 flex-r">
          <panel class="panel left" title="交流/直流配电柜遥测量">
            <div class="content">
              <div class="row row1">
                <div class="card has-title">
                  <div class="card-title">第1路交流三相输入电压(V)</div>
                  <div class="card-content">
                    <bar1 ref="bar1"></bar1>
                  </div>
                </div>
                <div class="card has-title">
                  <div class="card-title">第2路交流三相输入电压(V)</div>
                  <div class="card-content">
                    <bar1 ref="bar2"></bar1>
                  </div>
                </div>
              </div>
              <div class="card row2">
                <list-card :datas="props1" :rows="4"></list-card>
              </div>
              <div class="row row3">
                <div class="card has-title">
                  <div class="card-title">第1路交流三相输入电流(A)</div>
                  <div class="card-content">
                    <instrument-board
                      ref="board1"
                      :r="boardR"
                    ></instrument-board>
                  </div>
                </div>
                <div class="card has-title">
                  <div class="card-title">第2路交流三相输入电流(A)</div>
                </div>
              </div>
              <div class="card card2 row4">
                <list-card :datas="props2" :rows="3"></list-card>
              </div>
            </div>
          </panel>
          <panel class="panel right" title="交流/直流配电柜遥信量">
            <div class="content">
              <alarm-legend class="legend"></alarm-legend>
              <div class="state-row">
                <el-row :gutter="30">
                  <el-col
                    :span="8"
                    v-for="(item, idx) in alarmList1"
                    :key="'alarm1_' + idx"
                  >
                    <alarm-card
                      class="state-item"
                      :level="item.level"
                      :name="item.name"
                    ></alarm-card>
                  </el-col>
                </el-row>
              </div>
              <div class="state-row">
                <el-row :gutter="30">
                  <el-col
                    :span="8"
                    v-for="(item, idx) in alarmList2"
                    :key="'alarm2_' + idx"
                  >
                    <alarm-card
                      :level="item.level"
                      :name="item.name"
                    ></alarm-card>
                  </el-col>
                </el-row>
              </div>
            </div>
          </panel>
        </div>
      </el-tab-pane>
      <el-tab-pane label="高频开关整流柜">
        <div class="tab-content content1 flex-r">
          <panel class="panel left" title="高频开关整流柜遥测量">
            <div class="content">
              <div class="row row1">
                <div class="card has-title">
                  <div class="card-title">第1路交流三相输入电压(V)</div>
                  <div class="card-content">
                    <bar1 ref="bar3"></bar1>
                  </div>
                </div>
                <div class="card has-title">
                  <div class="card-title">三相交流输出电压(V)</div>
                  <div class="card-content">
                    <bar1 ref="bar4"></bar1>
                  </div>
                </div>
              </div>
              <div class="card row2">
                <list-card :datas="props3" :rows="4"></list-card>
              </div>
              <div class="row row3">
                <div class="card has-title">
                  <div class="card-title">第2路交流三相输入电压(V)</div>
                  <div class="card-content">
                    <instrument-board
                      ref="board2"
                      :r="boardR"
                    ></instrument-board>
                  </div>
                </div>
                <div class="card has-title">
                  <div class="card-title">三相交流输出电流(A)</div>
                </div>
              </div>
              <div class="card card2 row4">
                <list-card :datas="props4" :rows="3"></list-card>
              </div>
            </div>
          </panel>
          <panel class="panel right" title="高频开关整流柜遥信量">
            <div class="content">
              <alarm-legend class="legend"></alarm-legend>
              <div class="state-row">
                <el-row :gutter="30">
                  <el-col
                    :span="8"
                    v-for="(item, idx) in alarmList3"
                    :key="'alarm1_' + idx"
                  >
                    <alarm-card
                      class="state-item"
                      :level="item.level"
                      :name="item.name"
                    ></alarm-card>
                  </el-col>
                </el-row>
              </div>
            </div>
          </panel>
        </div>
      </el-tab-pane>
      <el-tab-pane label="核容装置"> </el-tab-pane>
    </el-tabs>
  </div>
</template>
  <script>
import { mapState } from "vuex";
import Panel from "@/components/panel.vue";
import ListCard from "@/components/listCard.vue";
import AlarmCard from "@/components/alarmCard.vue";
import AlarmLegend from "@/components/alarmLegend.vue";
import InstrumentBoard from "@/components/instrumentBoard";
import bar1 from "@/components/bar1";
export default {
  name: "Details",
  components: {
    Panel,
    ListCard,
    AlarmCard,
    AlarmLegend,
    InstrumentBoard,
    bar1,
  },
  data() {
    const props1 = [
      {
        label: "交流配电柜温度(℃)",
        key: "",
      },
      {
        label: "交流电压告警阀值下限(V)",
        key: "",
      },
      {
        label: "第1路直流输入电压(V)",
        key: "",
      },
      {
        label: "1段直流母线输出电压(V)",
        key: "",
      },
      {
        label: "2段直流母线输出电流(A)",
        key: "",
      },
      {
        label: "直流配电柜温度(℃)",
        key: "",
      },
      {
        label: "直流电压告警阀值下限(V)",
        key: "",
      },
    ];
    const props2 = [
      {
        label: "交流电压告警阀值上限(V)",
        key: "",
      },
      {
        label: "第二路直流输入电压(V)",
        key: "",
      },
      {
        label: "2段直流母线输出电压(V)",
        key: "",
      },
      {
        label: "2段直流母线输出电流(A)",
        key: "",
      },
      {
        label: "直流电压告警阀值上限(V)",
        key: "",
      },
    ];
    const props3 = [
      {
        label: "直流输出电压(V)",
        key: "",
      },
      {
        label: "蓄电池组充电电流(A)",
        key: "",
      },
      {
        label: "均充电压(V)",
        key: "",
      },
      {
        label: "电池N端电压(V)",
        key: "",
      },
      {
        label: "模块N输出电流(A)",
        key: "",
      },
      {
        label: "交流电压告警阀值下限(V)",
        key: "",
      },
      {
        label: "直流电压告警阀值下限(V)",
        key: "",
      },
    ];
    const props4 = [
      {
        label: "负载电流(A)",
        key: "",
      },
      {
        label: "电池充电限流值(A)",
        key: "",
      },
      {
        label: "浮充电压(V)",
        key: "",
      },
      {
        label: "高频开关电源柜温度(℃)",
        key: "",
      },
      {
        label: "交流电压告警阀值上限(V)",
        key: "",
      },
      {
        label: "直流电压告警阀值上限(V)",
        key: "",
      },
    ];
    const alarmList1 = [
      {
        name: "交流输入1停电",
        level: 0,
      },
      {
        name: "交流输入2停电",
        level: 1,
      },
      {
        name: "交流输入1防雷器故障",
        level: 2,
      },
      {
        name: "交流输入2防雷器故障",
        level: 0,
      },
      {
        name: "第1路交流输入开关跳闸",
        level: 3,
      },
      {
        name: "第2路交流输入开关跳闸",
        level: 0,
      },
      {
        name: "第1路交流A相过压",
        level: 0,
      },
      {
        name: "第1路交流A相欠压",
        level: 0,
      },
      {
        name: "第1路交流B相过压",
        level: 0,
      },
      {
        name: "第1路交流B相欠压",
        level: 0,
      },
      {
        name: "第1路交流C相过压",
        level: 0,
      },
      {
        name: "第1路交流C相欠压",
        level: 0,
      },
      {
        name: "第2路交流A相过压",
        level: 0,
      },
      {
        name: "第2路交流A相欠压",
        level: 0,
      },
      {
        name: "第2路交流B相过压",
        level: 0,
      },
      {
        name: "第2路交流B相欠压",
        level: 0,
      },
      {
        name: "第2路交流C相过压",
        level: 0,
      },
      {
        name: "第2路交流C相欠压",
        level: 0,
      },
      {
        name: "第1路交流A相缺相",
        level: 0,
      },
      {
        name: "第1路交流B相缺相",
        level: 0,
      },
      {
        name: "第1路交流C相缺相",
        level: 0,
      },
      {
        name: "第2路交流A相缺相",
        level: 0,
      },
      {
        name: "第2路交流B相缺相",
        level: 0,
      },
      {
        name: "第2路交流C相缺相",
        level: 0,
      },
      {
        name: "交流监控单元故障",
        level: 0,
      },
      {
        name: "1段交流母线输出开关1~N跳闸",
        level: 0,
      },
      {
        name: "2段交流母线输出开关1~N跳闸",
        level: 0,
      },
      {
        name: "交流配电柜总告警",
        level: 0,
      },
      {
        name: "交流配电柜温度告警",
        level: 0,
      },
    ];
    const alarmList2 = [
      {
        name: "第1路直流过压",
        level: 0,
      },
      {
        name: "第1路直流欠压",
        level: 0,
      },
      {
        name: "第2路直流过压",
        level: 0,
      },
      {
        name: "第2路直流欠压",
        level: 0,
      },
      {
        name: "第1路输入开关跳闸",
        level: 0,
      },
      {
        name: "第2路输入开关跳闸",
        level: 0,
      },
      {
        name: "1段直流第N路开关跳闸",
        level: 0,
      },
      {
        name: "2段直流第N路开关跳闸",
        level: 0,
      },
      {
        name: "温度告警",
        level: 0,
      },
      {
        name: "监控单元故障",
        level: 0,
      },
      {
        name: "直流配电柜总告警",
        level: 0,
      },
    ];
    const alarmList3 = [
      {
        name: "交流输入1停电",
        level: 0,
      },
      {
        name: "交流输入2停电",
        level: 0,
      },
      {
        name: "交流输入1防雷器故障",
        level: 0,
      },
      {
        name: "交流输入2防雷器故障",
        level: 0,
      },
      {
        name: "第1路交流输入开关跳闸",
        level: 0,
      },
      {
        name: "第2路交流输入开关跳闸",
        level: 0,
      },
      {
        name: "第1路交流A相过压",
        level: 0,
      },
      {
        name: "第1路交流A相欠压",
        level: 0,
      },
      {
        name: "第1路交流B相过压",
        level: 0,
      },
      {
        name: "第1路交流B相欠压",
        level: 0,
      },
      {
        name: "第1路交流C相过压",
        level: 0,
      },
      {
        name: "第1路交流C相欠压",
        level: 0,
      },
      {
        name: "第2路交流A相过压",
        level: 0,
      },
      {
        name: "第2路交流A相欠压",
        level: 0,
      },
      {
        name: "第2路交流B相过压",
        level: 0,
      },
      {
        name: "第2路交流B相欠压",
        level: 0,
      },
      {
        name: "第2路交流C相过压",
        level: 0,
      },
      {
        name: "第2路交流C相欠压",
        level: 0,
      },
      {
        name: "第1路交流A相缺相",
        level: 0,
      },
      {
        name: "第1路交流B相缺相",
        level: 0,
      },
      {
        name: "第1路交流C相缺相",
        level: 0,
      },
      {
        name: "第2路交流A相缺相",
        level: 0,
      },
      {
        name: "第2路交流B相缺相",
        level: 0,
      },
      {
        name: "第2路交流C相缺相",
        level: 0,
      },
      {
        name: "交流监控单元故障",
        level: 0,
      },
      {
        name: "模块N故障",
        level: 0,
      },
      {
        name: "高频开关电源柜总告警",
        level: 0,
      },
      {
        name: "直流输出过压",
        level: 0,
      },
      {
        name: "直流输出欠压",
        level: 0,
      },
      {
        name: "电池组下电保护告警",
        level: 0,
      },
      {
        name: "电池组N熔丝告警",
        level: 0,
      },
      {
        name: "负载熔丝状态",
        level: 0,
      },
      {
        name: "均浮充状态",
        level: 0,
      },
      {
        name: "在用交流输入路数",
        level: 0,
      },
    ];
    return {
      boardR: "33.3%",
      props1,
      props2,
      props3,
      props4,
      alarmList1,
      alarmList2,
      alarmList3,
    };
  },
  mounted() {
    // this.$nextTick(() => {
    //   this.getRadius();
    // });
    // window.addEventListener("resize", this.getRadius);
  },
  methods: {
    getRadius() {
      this.boardR = this.$refs.board.$el.clientWidth / 6;
      console.log(this.boardR, "?????");
      this.$refs.board1.setData({ xLabel: [], sData: [] });
    },
    tabClick() {
      this.$nextTick(() => {
        this.$refs.bar1.resize();
        this.$refs.bar2.resize();
        this.$refs.bar3.resize();
        this.$refs.bar4.resize();
        this.$refs.board1.resize();
        this.$refs.board2.resize();
      });
    },
  },
  beforeDestroy() {
    // 销毁resize事件
    // window.removeEventListener("resize", this.getRadius);
  },
  computed: {
    ...mapState({
      cachedViews: (state) => state.tagsView.cachedViews,
    }),
  },
};
</script>
<style scoped lang="less">
.p-main {
  height: 100%;
  .tab {
    height: 100%;
    background: transparent;
    border: 0 none;
    box-shadow: none;
    display: flex;
    flex-direction: column;
    /deep/ .el-tabs__header {
      border: 0 none;
      background-color: transparent;
    }
    /deep/ .el-tabs__content {
      flex: 1;
      padding: 4px;
      .el-tab-pane {
        height: 100%;
      }
    }
    .flex-c {
      display: flex;
      flex-direction: column;
    }
    .flex-r {
      display: flex;
      flex-direction: row;
    }
    .tab-content {
      height: 100%;
    }
    /deep/ .el-tabs__header .el-tabs__item {
      background: #78eef8;
      color: #000;
      border-radius: 4px;
      font-weight: bold;
      font-size: 18px;
      border: 0 none;
      & + .el-tabs__item {
        margin-left: 6px;
      }
      &.is-active {
        color: #fff;
        background-color: #0081ff;
      }
    }
  }
  .panel {
    color: #fff;
    // height: 100%;
    flex: 1;
    &.right {
      margin-left: 10px;
      flex: 1.44;
      .content {
        height: 100%;
        padding: 10px 30px;
        position: relative;
        display: flex;
        flex-direction: column;
        justify-content: space-between;
        .legend {
          position: absolute;
          bottom: 100%;
          right: 6px;
          width: 286px;
        }
        .state-row {
          /deep/ .el-col:nth-child(n + 4) {
            // /deep/ .el-col:nth-child(3) ~ .el-col {
            margin-top: 10px;
          }
          .state-item {
          }
        }
      }
    }
    &.left .content {
      height: 100%;
      padding: 10px;
      display: flex;
      flex-direction: column;
      & > div:not(:first-child) {
        margin-top: 10px;
      }
      .row {
        flex: 2.5;
        display: flex;
      }
      .row2 {
        flex: 1.66;
      }
      .row4 {
        flex: 1.3;
      }
    }
  }
  .card {
    border-radius: 6px;
    border: 1px #4392a2 solid;
    padding: 10px;
    background: #011f39;
    overflow: hidden;
    &.has-title {
      padding: 0;
      flex: 1;
      display: flex;
      flex-direction: column;
      & + .has-title {
        margin-left: 10px;
      }
      .card-title {
        background: #0c4d77;
        line-height: 38px;
        height: 38px;
        font-size: 20px;
        text-align: center;
      }
    }
  }
}
.card-content {
  flex: 1;
}
</style>
vite.config.js
@@ -37,6 +37,10 @@
      protocolImports: true,
    }),
  ],
  server: {
    host: '0.0.0.0',
    port: 8080
  },
  base: "./",
  resolve: {
    extensions: [".js", ".vue"],