<template>
  <div class="dust-box" data-testid="dust-monitoring-container">
    <div class="top-box" data-testid="dust-monitoring-filters">
      <el-form
        :model="form"
        label-width="auto"
        :inline="true"
        class="demo-form-inline"
      >
        <el-form-item label="除尘器名称">
          <el-select
            v-model="form.dusterNo"
            placeholder="请选择除尘器"
            style="width: 240px"
            filterable
            data-testid="dust-monitoring-duster-select"
          >
            <el-option
              v-for="item in dusterList"
              :key="item.dusterNo"
              :label="item.dusterName"
              :value="item.dusterNo"
            ></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="分析时间">
          <el-date-picker
            v-model="form.dateValue"
            type="datetimerange"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            style="width: 450px"
            popper-class="date-picker-popper"
            @calendar-change="calendarChange"
            @visible-change="visibleChange"
            :disabled-date="disabledFn"
            data-testid="dust-monitoring-date-picker"
          />
        </el-form-item>
      </el-form>
    </div>
    <div class="layout1" data-testid="dust-monitoring-main-content">
      <div class="left-box" data-testid="dust-monitoring-charts-section">
        <div class="part1 layout3" data-testid="dust-monitoring-charts-container">
          <div class="chart-box" v-for="(item, index) in chartData" :key="item" :data-testid="`dust-monitoring-chart-${index}`">
            <div :id="'chart' + index" class="chart-item"></div>
          </div>
        </div>
        <div class="warn-info" data-testid="dust-monitoring-warnings">
          <warnCom
            title="告警"
            :listInfo="warnInfoList"
            @jumpPage="toWarnDetail"
          ></warnCom>
        </div>
      </div>
      <div class="right-box" data-testid="dust-monitoring-info-section">
        <div class="part1" data-testid="dust-monitoring-health-section">
          <div class="battery" data-testid="dust-monitoring-health-indicator">
            <healthyCom :progress="healthPercent"></healthyCom>
          </div>
        </div>
        <div class="part2" data-testid="dust-monitoring-details-section">
          <div class="dust-title" data-testid="dust-monitoring-title">{{ dusterName }}</div>
          <div class="dust-info" data-testid="dust-monitoring-info-grid">
            <div class="info-item" v-for="item in dustInfo" :key="item.label">
              <span class="label">{{ item.label }}</span>
              <span class="value">
                <gsapNumber :value="item.value" :animationType="'count'" :animationInterval="10000" />
                <span class="unit">({{ item.unit }})</span>
              </span>
            </div>
            <div></div>
          </div>
          <div class="indicator-box" data-testid="dust-monitoring-gauges">
            <div class="indicator-item" id="indicatorOne"></div>
            <div class="indicator-item" id="indicatorTwo"></div>
          </div>
          <div class="position-info" data-testid="dust-monitoring-status-matrix">
            <div class="left" v-if="detailObj.compartHealthList.length > 0">
              <div class="part" v-for="(list, index) in detailObj.compartHealthList" :key="index">
                <div class="point" :class="{
                  'status-normal': item.healthStatus === 1,
                  'status-error': item.healthStatus === 2,
                  'status-warning': item.healthStatus === 3,
                }" v-for="item in detailObj.compartHealthList[index]" :key="item" @click="handleStatusDotClick()">
                </div>
              </div>
            </div>
            <!-- <div class="right">
              <div class="part">
                <div class="point warn" v-for="item in 6" :key="item"></div>
              </div>
              <div class="part">
                <div class="point warn" v-for="item in 6" :key="item"></div>
              </div>
            </div> -->
          </div>
          <div class="other-info" data-testid="dust-monitoring-compartment-info">
            <div class="other-info-item">
              <p class="label">反吹仓室：</p>
              <p>{{ detailObj.bachflushCompart }}</p>
            </div>
            <div class="other-info-item">
              <p class="label">泄露仓室：</p>
              <p>{{ detailObj.leakageCompart }}</p>
            </div>
            <div class="other-info-item">
              <p class="label">故障仓室：</p>
              <p>{{ detailObj.faultCompart }}</p>
            </div>
          </div>
        </div>
        <div class="warn-info" data-testid="dust-monitoring-closed-loops">
          <warnCom
            title="闭环"
            :listInfo="closedLoopInfoList"
            @jumpPage="toCircleDetail"
          ></warnCom>
        </div>
      </div>
    </div>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted, watch, computed } from "vue";
import { useRouter, useRoute } from "vue-router";
import * as echarts from "echarts";
import { getDataFun } from "@/request/method.js";
import { useUsersStore } from "@/pinia/user.js";
import moment from "moment";
import warnCom from "./components/warn.vue";
import healthyCom from "./components/healthyProgress.vue";
import { getLineOption2, getGaugeOption } from "@/utils/chart";
import gsapNumber from "./components/gsapNumber.vue";

const userStore = useUsersStore();
const form = reactive({
  dusterNo: "",
  dateValue: [
    moment(new Date(new Date().getTime() - 24 * 60 * 60 * 1000 * 10)).format(
      "YYYY-MM-DD"
    ) + " 00:00:00",
    moment(new Date()).format("YYYY-MM-DD") + " 23:59:59",
  ],
});
const startVal = ref(null);
const calendarChange = (e) => {
  if (e[0] && !e[1]) {
    startVal.value = e[0];
  }
};
const visibleChange = (e) => {
  if (e) {
    startVal.value = null;
  }
};
const disabledFn = (current) => {
  if (!startVal.value) return false;
  let range = [
    new Date(startVal.value).getTime() - 24 * 60 * 60 * 1000 * 30,
    new Date(startVal.value).getTime() + 24 * 60 * 60 * 1000 * 30,
  ];
  let cur = new Date(current).getTime();
  return cur < range[0] || cur > range[1];
};
const router = useRouter();
const route = useRoute();
const chartInstance = reactive({});
const chartData = reactive([{}, {}, {}]);
const initChart = (instance, option, domEl) => {
  // 检查是否已经存在该dom的实例，如果存在则清除
  if (chartInstance[domEl]) {
    chartInstance[domEl].dispose();
  }
  // 创建新的echarts实例
  const newInstance = echarts.init(document.getElementById(domEl));
  newInstance.setOption(option);
  chartInstance[domEl] = newInstance;
  chartLinkageFun();
};
let instanceList = reactive([]);
// 图表联动
const chartLinkageFun = (target) => {
  instanceList = [];
  Object.keys(chartInstance).forEach((key) => {
    instanceList.push(chartInstance[key]);
  });
  // if (instanceList.length < 3) return;
  instanceList.forEach((item) => {
    item.off("showTip");
    item.off("globalout");
    item.on("globalout", function () {
      instanceList.forEach((item_) => {
        item_.dispatchAction({
          type: "hideTip",
        });
      });
    });
    item.on("showTip", function (e) {
      bindToolTip(this, e);
    });
    // 获取canvas的非绘制图形区域
    item.getZr().on("mousemove", function (params) {
      if (
        item.containPixel("grid", [params.event.offsetX, params.event.offsetY])
      ) {
        // 只在图表区域内处理点击事件
        // console.log("Clicked inside the chart area");
      } else {
        // 处理点击空白区域的情况
        // console.log("Clicked outside the chart area");
        instanceList.forEach((item_, index) => {
          item_.dispatchAction({
            type: "hideTip",
          });
          item_.dispatchAction({
            type: 'updateAxisPointer',
            currTrigger: "leave"
          })
        });
      }
    });
  });
};
const bindToolTip = (that, e) => {
  const connectCharts = instanceList;
  if (window.echartTimer) {
    clearTimeout(window.echartTimer);
  }
  window.echartTimer = setTimeout(() => {
    if (!e.dataIndex && e.dataIndex !== 0) {
      connectCharts.forEach((item_) => {
        item_.dispatchAction({
          type: "hideTip",
        });
      });
      return;
    }
    connectCharts.forEach((item_) => {
      if (item_.id != that.id) {
        let option = item_.getOption();
        let xAxisList = option.xAxis[0].data;
        let tmpIndex = e.dataIndex;
        item_.off("showTip");
        item_.dispatchAction({
          type: "showTip",
          seriesIndex: 0,
          dataIndex: tmpIndex,
        });

        item_.on("showTip", function (e) {
          bindToolTip(this, e);
        });
      }
    });
  }, 50);
};
// 移出图表事件
const chartRemoveEvent = () => {
  Object.keys(chartInstance).forEach((key) => {
    chartInstance[key].off();
  });
};
const dustInfo = reactive([
  {
    label: "压差",
    value: "0.0",
    unit: "kPa",
    key: "pressureDiff",
    unitKey: "pressureDiffUnit",
  },
  {
    label: "排口浓度",
    value: "0.0",
    unit: "mg/m³",
    key: "dustConcentration",
    unitKey: "dustConcentrationUnit",
  },
  { label: "电磁阀数量", value: "0.0", unit: "个", key: "valveNum" },
  { label: "仓室数量", value: "0.0", unit: "个", key: "compartNum" },
  { label: "布袋检测仪数量", value: "0.0", unit: "个", key: "bagDetectorNum" },
  {
    label: "脉冲检测仪数量",
    value: "0.0",
    unit: "个",
    key: "pulseDetectorNum",
  },
]);
const indicatorOneInstance = ref();
const indicatorTwoInstance = ref();
const indicatorFun = (target, domId, option) => {
  if (target.value) {
    target.value.clear();
  }
  target.value = echarts.init(document.getElementById(domId));
  target.value.setOption(option);
};
const dusterList = ref([]);
// 获取除尘器名称枚举值
const getDusterNameEnum = () => {
  const url = "/bag/cycle/getDusterList";
  getDataFun(url, {})
    .then((res) => {
      dusterList.value = (res && res.data) || [];
      // 判断除尘器名称是否有值
      form.dusterNo = route.query.dusterNo || dusterList.value[0].dusterNo;
    })
    .catch(() => {
      dusterList.value = [];
    });
};
// 获取健康度指数
const getHealthIndex = () => {
  if (!form.dusterNo) {
    return;
  }
  const url = "/dusterStatusMonitor/healthIndexCurve";
  const params = {
    customerId: userStore.customerId,
    deviceNo: form.dusterNo,
    startTime: moment(form.dateValue[0]).format("YYYY-MM-DD") + " 00:00:00",
    endTime: moment(form.dateValue[1]).format("YYYY-MM-DD") + " 23:59:59",
  };
  getDataFun(url, params).then((res) => {
    if (res && res.data) {
      const xData = [];
      const seriesData = [];
      res.data.forEach((item) => {
        xData.push(item.time);
        seriesData.push(item.healthIndex);
      });
      const option = getLineOption2(xData, seriesData, "除尘器健康度指数", "%");
      initChart(chartData[0], option, "chart0");
    }
  });
};
// 获取压力差
const getPressure = () => {
  if (!form.dusterNo) {
    return;
  }
  const url = "/dusterStatusMonitor/pressureDiffCurve";
  const params = {
    customerId: userStore.customerId,
    deviceNo: form.dusterNo,
    startTime: moment(form.dateValue[0]).format("YYYY-MM-DD") + " 00:00:00",
    endTime: moment(form.dateValue[1]).format("YYYY-MM-DD") + " 23:59:59",
  };
  getDataFun(url, params).then((res) => {
    if (res && res.data) {
      const xData = [];
      const seriesData = [];
      res.data.forEach((item) => {
        xData.push(item.time);
        seriesData.push(item.pressureDiff);
      });
      const option = getLineOption2(xData, seriesData, "压力差", "kPa");
      initChart(chartData[1], option, "chart1");
    }
  });
};
// 获取能耗
const getEnergy = () => {
  if (!form.dusterNo) {
    return;
  }
  const url = "/dusterStatusMonitor/energyConsumptionCurve";
  const params = {
    customerId: userStore.customerId,
    deviceNo: form.dusterNo,
    startTime: moment(form.dateValue[0]).format("YYYY-MM-DD") + " 00:00:00",
    endTime: moment(form.dateValue[1]).format("YYYY-MM-DD") + " 23:59:59",
  };
  getDataFun(url, params).then((res) => {
    if (res && res.data) {
      const xData = [];
      const seriesData = [];
      res.data.forEach((item) => {
        xData.push(item.time);
        seriesData.push(item.energyConsumption);
      });
      const option = getLineOption2(xData, seriesData, "能耗", "kW·h");
      initChart(chartData[2], option, "chart2");
    }
  });
};
const detailObj = reactive({
  bachflushCompart: "",
  leakageCompart: "",
  faultCompart: "",
  compartHealthList: [],
});
const healthPercent = ref();
// 获取除尘器详情
const getDustDetail = () => {
  if (!form.dusterNo) {
    return;
  }
  const url = "/dusterStatusMonitor/dusterDetail";
  const params = {
    customerId: userStore.customerId,
    deviceNo: form.dusterNo,
    startTime: moment(form.dateValue[0]).format("YYYY-MM-DD") + " 00:00:00",
    endTime: moment(form.dateValue[1]).format("YYYY-MM-DD") + " 23:59:59",
  };

  dustInfo.value = [
  {
    label: "压差",
    value: "0.0",
    unit: "kPa",
    key: "pressureDiff",
    unitKey: "pressureDiffUnit",
  },
  {
    label: "排口浓度",
    value: "0.0",
    unit: "mg/m³",
    key: "dustConcentration",
    unitKey: "dustConcentrationUnit",
  },
  { label: "电磁阀数量", value: "0", unit: "个", key: "valveNum" },
  { label: "仓室数量", value: "0", unit: "个", key: "compartNum" },
  { label: "布袋检测仪数量", value: "0", unit: "个", key: "bagDetectorNum" },
  {
    label: "脉冲检测仪数量",
    value: "0",
    unit: "个",
    key: "pulseDetectorNum",
  },
];
  getDataFun(url, params)
    .then((res) => {
      if (res && res.data) {
        for (let i = 0; i < dustInfo.length; i++) {
          dustInfo[i].value = res.data[dustInfo[i].key] || 0;
          dustInfo[i].unit = "个";
          (dustInfo[i].unitKey &&
            (dustInfo[i].unit = res.data[dustInfo[i].unitKey])) ||
            "个";
        }
      } else {
        dustInfo.forEach((item) => {
          item.value = 0;
        });
      }
      detailObj.bachflushCompart =
        (res.data && res.data.bachflushCompart) || "";
      detailObj.leakageCompart = (res.data && res.data.leakageCompart) || "";
      detailObj.faultCompart = (res.data && res.data.faultCompart) || "";
      detailObj.compartHealthList = (res.data &&
        res.data.compartHealthList) || [
          [
            {
              compartNo: "1",
              healthStatus: 1,
              sort: 1,
            },
            {
              compartNo: "2",
              healthStatus: 1,
              sort: 2,
            },
            {
              compartNo: null,
              healthStatus: 1,
              sort: 3,
            },
            {
              compartNo: null,
              healthStatus: 1,
              sort: 4,
            },
            {
              compartNo: null,
              healthStatus: 1,
              sort: 5,
            },
            {
              compartNo: null,
              healthStatus: 1,
              sort: 6,
            },
            {
              compartNo: null,
              healthStatus: 1,
              sort: 7,
            },
            {
              compartNo: null,
              healthStatus: 1,
              sort: 8,
            },
            {
              compartNo: null,
              healthStatus: 1,
              sort: 9,
            },
            {
              compartNo: null,
              healthStatus: 1,
              sort: 10,
            },
            {
              compartNo: null,
              healthStatus: 1,
              sort: 11,
            },
            {
              compartNo: null,
              healthStatus: 1,
              sort: 12,
            },
          ],
        ];
      indicatorFun(
        indicatorOneInstance,
        "indicatorOne",
        getGaugeOption({
          value: dustInfo[0].value.toFixed(2),
          chartName: "压差" + '(' + dustInfo[0].unit + ')',
          min: parseInt(res.data.pressureDiffLow),
          max: parseInt(res.data.pressureDiffHigh),
          formatter:
            parseInt(res.data.pressureDiffHigh) > 10
              ? function (value) {
                return parseInt(value);
              }
              : "",
        })
      );
      indicatorFun(
        indicatorTwoInstance,
        "indicatorTwo",
        getGaugeOption({
          value: parseFloat(dustInfo[1].value),
          chartName: "排口浓度" + '(' + dustInfo[1].unit + ')',
          min: 0,
          max: parseInt(dustInfo[1].value) > 50 ? Math.ceil(parseFloat(dustInfo[1].value) / 10) * 10 : 10,
          formatter: function (value) {
            return parseInt(value);
          },
        })
      );
      healthPercent.value = parseFloat(res.data.healthPercent);
    })
    .catch(() => {
      dustInfo.forEach((item) => {
        item.value = 0;
      });
      healthPercent.value = 0;
    });
};
const closedLoopInfoList = ref([]);
const warnInfoList = ref([]);
const toWarnDetail = () => {
  router.push({
    path: "/alerts",
    query: {
      dusterNo: form.dusterNo,
      deviceName: dusterName.value,
      suspendStartTime: form.dateValue[0],
      suspendEndTime: form.dateValue[1],
    },
  });
};
const toCircleDetail = () => {
  router.push({
    path: "/my-loop/myAgency",
    query: {
      dusterNo: form.dusterNo,
      deviceName: dusterName.value,
      suspendStartTime: form.dateValue[0],
      suspendEndTime: form.dateValue[1],
    },
  });
};
// 获取告警信息
const getAlarmInfo = () => {
  if (!form.dusterNo) {
    return;
  }
  const url = "/dusterStatusMonitor/warnAndClosedLoopInfo";
  const params = {
    customerId: userStore.customerId,
    deviceNo: form.dusterNo,
    startTime: moment(form.dateValue[0]).format("YYYY-MM-DD") + " 00:00:00",
    endTime: moment(form.dateValue[1]).format("YYYY-MM-DD") + " 23:59:59",
  };
  getDataFun(url, params)
    .then((res) => {
      closedLoopInfoList.value =
        (res && res.data && res.data.closedLoopInfoList) || [];
      warnInfoList.value = (res && res.data && res.data.warnInfoList) || [];
    })
    .catch(() => {
      closedLoopInfoList.value = [];
      warnInfoList.value = [];
    });
};
const resizeFun = () => {
  indicatorOneInstance && indicatorOneInstance.value.resize();
  indicatorTwoInstance && indicatorTwoInstance.value.resize();
  Object.keys(chartInstance).forEach((key) => {
    chartInstance[key] && chartInstance[key].resize();
  });
};
onMounted(() => {
  getDusterNameEnum();
  window.addEventListener("resize", resizeFun);
  refeshFun();
  getAllMouseEvent();
  // 启动实时监测
  startRealTimeMonitoring();
});
onUnmounted(() => {
  window.removeEventListener("resize", resizeFun);
  cancelAllMouseEvent();
  chartRemoveEvent();
  // 停止实时监测
  stopRealTimeMonitoring();
});
watch(
  () => form.dusterNo,
  () => {
    startRealTimeMonitoring();
  }
);
watch(
  () => form.dateValue,
  () => {
    startRealTimeMonitoring();
  }
);
const dusterName = computed(() => {
  return dusterList.value.find((item) => item.dusterNo == form.dusterNo)
    ?.dusterName;
});
const handleStatusDotClick = () => {
  router.push({
    path: "/bag-monitor",
    query: {
      dusterNo: form.dusterNo,
      dusterName: dusterName.value,
    },
  });
};
// 定时刷新页面数据请求 30分钟一次。
const refreshData = () => {
  getHealthIndex();
  getPressure();
  getEnergy();
  getDustDetail();
  getAlarmInfo();
}
let refreshTimer = null;
const refeshFun = () => {
  if (refreshTimer) {
    clearInterval(refreshTimer);
  }
  refreshTimer = setTimeout(() => {
    refreshData();
    refeshFun()
  }, 1000 * 60 * 30);
}

// 实时监测数据变化 - 每秒轮询 getDustDetail
let realTimeTimer = null;
const startRealTimeMonitoring = () => {
  if (realTimeTimer) {
    clearInterval(realTimeTimer);
    cancelAllMouseEvent();
    chartRemoveEvent();
  }
  // 默认先触发一次
  getDustDetail();
  getHealthIndex();
  getPressure();
  getEnergy();
  getDustDetail();
  getAlarmInfo();
  realTimeTimer = setInterval(() => {
    getDustDetail();
    getHealthIndex();
    getPressure();
    getEnergy();
    getDustDetail();
    getAlarmInfo();
  }, 10000);
}

const stopRealTimeMonitoring = () => {
  if (realTimeTimer) {
    clearInterval(realTimeTimer);
    realTimeTimer = null;
  }
}
const dustBox = ref(null);
// 获取鼠标所有事件
const getAllMouseEvent = () => {
  if (!dustBox.value) {
    return;
  }
  dustBox.value.addEventListener("mousemove", cancelRefreshEvent)
  dustBox.value.addEventListener("mousedown", cancelRefreshEvent)
}
const cancelRefreshEvent = () => {
  clearTimeout(refreshTimer);
  refeshFun();
}
// 取消鼠标事件
const cancelAllMouseEvent = () => {
  clearTimeout(refreshTimer);
  if (dustBox.value) {
    dustBox.value.removeEventListener("mousemove", cancelRefreshEvent)
    dustBox.value.removeEventListener("mousedown", cancelRefreshEvent)
  }
}
</script>
<style lang="scss" scoped>
$borderColor: #3a3f45;

.dust-box {
  width: 100%;
  height: calc(100% - 14px);
  box-sizing: border-box;
  padding: 0 1rem 1rem 1rem;
  overflow: hidden;
  overflow: auto;
  display: flex;
  flex-direction: column;
  // background-color: #181d21;
  color: #909399;

  &::-webkit-scrollbar {
    width: 2px;
    background-color: rgba(58, 63, 69, 0.5);
  }
}

.top-box {
  width: 100%;
  padding-top: 18px;
  padding-left: 10px;
  box-sizing: border-box;
  background: #181d21;
  border-radius: 6px;
  box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.2);
  margin-bottom: 1rem;
}

.left-box {
  width: 50%;

  .part1 {
    border-radius: 6px;
    border: 1px solid rgba(58, 63, 69, 0.5);
    box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.2);
    width: 100%;
    height: 63vh;
    background: #181d21;

    .chart-box {
      width: 100%;
      height: 31%;

      .chart-item {
        width: 100%;
        height: 100%;
      }
    }
  }
}

.value {
  color: #fff;
  font-size: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.unit {
  color: #909399;
  font-size: 12px;
}

.right-box {
  width: 50%;

  .part1 {
    width: 100%;
    height: 5vh;
    border-radius: 6px;
    border: 1px solid rgba(58, 63, 69, 0.5);
    box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.2);
    background: #181d21;
    padding: 1%;
    box-sizing: border-box;

    .battery {
      width: 100%;
      height: calc(100%);
    }
  }

  .part2 {
    margin-top: 10px;
    width: 100%;
    height: calc(58vh - 10px);
    border-radius: 6px;
    border: 1px solid rgba(58, 63, 69, 0.5);
    box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.2);
    overflow: hidden;
    overflow-y: auto;
    padding: 20px 20px 0 20px;
    box-sizing: border-box;
    background: #181d21;
    position: relative;

    .dust-title {
      text-align: center;
      font-size: 20px;
      font-weight: 600;
      padding-bottom: 20px;
      border-bottom: 1px solid $borderColor;
      width: 100%;
      color: #fff;
    }

    .dust-info {
      padding: 0.5rem 1rem;
      background: #232a31;
      border-radius: 8px;
      margin: 20px 0;
      display: flex;
      flex-wrap: wrap;
      gap: 0 10px;
    }

    .info-item {
      padding: 3px 0;
      width: calc(50% - 5px);
      box-sizing: border-box;
      margin: 0.5rem 0;
      padding: 0.4rem 0.6rem;
      background: #181d21;
      border-radius: 6px;
      height: 35px;
      font-size: 14px;
      display: flex;
      align-items: center;
      font-weight: 600;
      justify-content: space-between;

      .label {
        color: #909399;
      }
    }

    .indicator-box {
      display: flex;
      align-items: center;
      justify-content: space-between;

      .indicator-item {
        width: 50%;
        height: 180px;
      }
    }

    .position-info {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 10px 20px 20px 20px;

      .left,
      .right {
        padding: 5px;
        width: 100%;
        flex-wrap: wrap;
        box-shadow: rgba(0, 0, 0, 0.3) 0px 0px 20px 0px;
      }

      .left {
        background-color: #232a31;
        border-radius: 6px;
        padding: 10px;
        box-sizing: border-box;
      }

      .part {
        display: flex;
        align-items: center;
        justify-content: space-around;
      }

      .point {
        width: 20px;
        margin: 10px;
        border-radius: 50%;
        display: inline-block;
        cursor: pointer;
        transition: transform 0.3s ease-in-out;

        &:hover {
          transform: scale(1.2);
          box-shadow: 0 0 0px 0 rgba(255, 255, 255, 0.5);
        }
      }

      .point::after {
        content: "";
        display: block;
        padding-bottom: 100%;
        /* 9/16 * 100% = 56.25% */
      }

      .status-normal {
        background-color: rgb(103, 194, 58); // 绿色
        animation: pulse-glow1 2s ease-in-out infinite;
      }

      .status-warning {
        background-color: rgb(230, 162, 60); // 橙色
        animation: pulse-glow2 2s ease-in-out infinite;
      }

      .status-error {
        background-color: rgb(245, 108, 108); // 红色
        animation: pulse-glow3 2s ease-in-out infinite;
      }

      @keyframes pulse-glow1 {
        0% {
          box-shadow: 0 0 0px rgba(103, 194, 58, 0.3),
            0 0 10px rgba(103, 194, 58, 0.2),
            0 0 20px rgba(103, 194, 58, 0.1);
        }

        50% {
          box-shadow: 0 0 20px rgba(103, 194, 58, 0.8),
            0 0 10px rgba(103, 194, 58, 0.6),
            0 0 20px rgba(103, 194, 58, 0.4);
        }

        100% {
          box-shadow: 0 0 0px rgba(103, 194, 58, 0.3),
            0 0 10px rgba(103, 194, 58, 0.2),
            0 0 20px rgba(103, 194, 58, 0.1);
        }
      }

      @keyframes pulse-glow2 {
        0% {
          box-shadow: 0 0 0px rgba(230, 162, 60, 0.3),
            0 0 10px rgba(230, 162, 60, 0.2),
            0 0 20px rgba(230, 162, 60, 0.1);
        }

        50% {
          box-shadow: 0 0 0px rgba(230, 162, 60, 0.8),
            0 0 10px rgba(230, 162, 60, 0.6),
            0 0 20px rgba(230, 162, 60, 0.4);
        }

        100% {
          box-shadow: 0 0 0px rgba(230, 162, 60, 0.3),
            0 0 10px rgba(230, 162, 60, 0.2),
            0 0 20px rgba(230, 162, 60, 0.1);
        }
      }

      @keyframes pulse-glow3 {
        0% {
          box-shadow: 0 0 0px rgba(245, 108, 108, 0.3),
            0 0 10px rgba(245, 108, 108, 0.2),
            0 0 20px rgba(245, 108, 108, 0.1);
        }

        50% {
          box-shadow: 0 0 0px rgba(245, 108, 108, 0.8),
            0 0 10px rgba(245, 108, 108, 0.6),
            0 0 20px rgba(245, 108, 108, 0.4);
        }

        100% {
          box-shadow: 0 0 0px rgba(245, 108, 108, 0.3),
            0 0 10px rgba(245, 108, 108, 0.2),
            0 0 20px rgba(245, 108, 108, 0.1);
        }
      }
    }

    .other-info {
      padding: 20px;
      border-top: 1px solid $borderColor;
      display: flex;
      align-items: normal;
      justify-content: space-between;
      font-size: 14px;
      gap: 10px;

      &-item {
        padding: 10px;
        min-height: 70px;
        background: #232a31;
        border-radius: 6px;
        flex: 1;
        overflow-y: auto;

        p:nth-child(2) {
          font-size: 14px;
          font-weight: 600;
          color: #fff;
        }
      }

      &-item::-webkit-scrollbar {
        width: 0;
      }

      .label {
        color: #909399;
      }

    }
  }

  .part2::-webkit-scrollbar {
    width: 2px;
    background-color: rgba(58, 63, 69, 0.5);
  }
}

.warn-info {
  margin-top: 20px;
  height: 17vh;
  border-radius: 6px;
  box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.2);
  padding: 10px 30px;
  background: #181d21;
}

.layout1 {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  flex: 1;
}

.layout2 {
  display: flex;
  justify-content: space-between;
  flex-direction: column;
}

.layout3 {
  display: flex;
  align-items: center;
  justify-content: space-around;
  flex-direction: column;
}
</style>
