董国庆
2025-04-08 1d41f70b0d1c1756546dfa529786d810d7c8cccb
修改首页
1个文件已修改
2593 ■■■■ 已修改文件
src/view/home/index.vue 2593 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/view/home/index.vue
@@ -1,1270 +1,1325 @@
<template>
  <div class="flex homePage">
    <!-- 头部 -->
    <div class="mapTop">
      <!-- 车辆统计 -->
      <div class="carCount">
        <div class="title">车辆统计</div>
        <div class="fir">
          <div
            class="countCard"
            v-for="(item, index) in carCountData.slice(0, 3)"
            :key="item.id"
          >
            <img class="iconImg" :src="imgList[index]" />
            <div>
              <div class="name">{{ item.name || "" }}(辆)</div>
              <div class="num">{{ item.carNum || 0 }}</div>
            </div>
          </div>
        </div>
        <div class="sec">
          <div
            class="countCard"
            v-for="(item, index) in carCountData.slice(3, 7)"
            :key="item.id"
          >
            <img class="iconImg" :src="imgList[index + 3]" />
            <div>
              <div class="name">{{ item.name || "" }}(辆)</div>
              <div class="num">{{ item.carNum || 0 }}</div>
            </div>
          </div>
        </div>
      </div>
      <!-- 车辆状态 -->
      <div class="carStatus">
        <div class="title">车辆状态</div>
        <div class="statusFir">
          <div class="statusCard">
            <div class="statusLeft">
              <div class="name">在线</div>
              <div class="num">{{ carStatusData.online || 0 }}</div>
            </div>
            <el-progress
              type="circle"
              :width="20"
              :show-text="false"
              stroke-linecap="butt"
              :percentage="carStatusData.onlinePercent"
              color="rgba(91, 143, 249, 1)"
              define-back-color="rgba(91, 143, 249, 0.25)"
              class="progressCard"
            ></el-progress>
          </div>
          <div class="statusLine"></div>
          <div class="statusCard">
            <div class="statusLeft">
              <div class="name">离线</div>
              <div class="num">{{ carStatusData.offline || 0 }}</div>
            </div>
            <el-progress
              type="circle"
              :width="20"
              :show-text="false"
              stroke-linecap="butt"
              :percentage="carStatusData.offlinePercent"
              color="rgba(93, 112, 146, 1)"
              define-back-color="rgba(93, 112, 146, 0.25)"
              class="progressCard"
            ></el-progress>
          </div>
        </div>
        <div class="statusSec">
          <div class="statusCard">
            <div class="statusLeft">
              <div class="name">故障</div>
              <div class="num">{{ carStatusData.breakdown || 0 }}</div>
            </div>
            <el-progress
              type="circle"
              :width="20"
              :show-text="false"
              stroke-linecap="butt"
              :percentage="carStatusData.breakdownPercent"
              color="rgba(253, 83, 118, 1)"
              define-back-color="rgba(253, 83, 118, 0.25)"
              class="progressCard"
            ></el-progress>
          </div>
          <div class="statusLine"></div>
          <div class="statusCard">
            <div class="statusLeft">
              <div class="name">异常</div>
              <div class="num">{{ carStatusData.abnormal || 0 }}</div>
            </div>
            <el-progress
              type="circle"
              :width="20"
              :show-text="false"
              stroke-linecap="butt"
              :percentage="carStatusData.abnormalPercent"
              color="rgba(246, 189, 22, 1)"
              define-back-color="rgba(246, 189, 22, 0.25)"
              class="progressCard"
            ></el-progress>
          </div>
        </div>
      </div>
    </div>
    <!-- 左边 地图 -->
    <div class="leftMap">
      <div class="mapContainer" id="mapContainer"></div>
    </div>
    <!-- 右边 内容 -->
    <div class="right">
      <div class="firCard">
        <div class="companyCard">
          <div class="lineCard"></div>
          <div class="name">运营公司(家)</div>
          <div class="value">{{ carStatusData.enterprise || 0 }}</div>
        </div>
        <div class="companyCard">
          <div class="lineCard"></div>
          <div class="name">运营车辆(辆)</div>
          <div class="value">{{ carStatusData.car || 0 }}</div>
        </div>
        <div class="companyCard">
          <div class="lineCard"></div>
          <div class="name">驾驶员(人)</div>
          <div class="value">{{ carStatusData.driver || 0 }}</div>
        </div>
      </div>
      <!-- 今日预警 -->
      <div class="todayWarn">
        <div class="title">今日预警</div>
        <div class="warnList" v-if="warnList.length > 0">
          <div
            class="warnItem"
            v-for="(item, index) in warnList"
            :key="index"
            :class="
              item.warnLevel
                ? ['oneWarn', 'twoWarn', 'threeWarn', 'fourWarn'][
                    item.warnLevel - 1
                  ]
                : 'fiveWarn'
            "
          >
            <div class="grade">
              {{
                item.warnLevel
                  ? ["一级", "二级", "三级", "四级"][item.warnLevel - 1]
                  : "-"
              }}
            </div>
            <div class="info">
              {{ item.vehicleNumber }} {{ item.warnType }} {{ item.keepTime }}
              {{ item.startTime }}
            </div>
          </div>
        </div>
        <div class="noData" v-else>
          <el-empty description="暂无数据" :image-size="80"></el-empty>
        </div>
      </div>
      <!-- 预警情况统计 -->
      <div class="warnCount">
        <div class="title">预警情况统计</div>
        <div class="countChart" id="countChart"></div>
        <div class="noData" v-if="countList.length == 0">
          <el-empty description="暂无数据" :image-size="80"></el-empty>
        </div>
      </div>
      <!-- 预警排行榜(前10) -->
      <div class="warnRank">
        <div class="title">预警排行榜(前10)</div>
        <div class="rankChart" id="rankChart">
          <div class="rankItem" v-for="(item, index) in rankList" :key="index">
            <div class="left">{{ item.name }}</div>
            <div
              class="rankRight"
              :class="[0, 1, 2].includes(index) ? 'rankColor' : ''"
            >
              <div class="rank" :style="{ width: item.percentage + '%' }"></div>
            </div>
          </div>
        </div>
        <div class="noData" v-if="rankList.length == 0">
          <el-empty description="暂无数据" :image-size="80"></el-empty>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import * as echarts from "echarts";
import html2canvas from "html2canvas";
import AMapLoader from "@amap/amap-jsapi-loader";
import flvjs from "flv.js";
import {
  getCarCount,
  getCarStatusCount,
  getMapCarList,
  getCarWarnList,
  getWarnGroupCount,
  getWarnGroupCountTop10,
  getRealVideo,
} from "./service";
export default {
  data() {
    return {
      flvPlayer: null,
      activeIndex: "1",
      activeIndex2: "1",
      timer: null,
      markers: [],
      map: null,
      AMap: null,
      infoWindow: null,
      imgList: [
        require("../../assets//homeImg/img1.png"),
        require("../../assets//homeImg/img2.png"),
        require("../../assets//homeImg/img3.png"),
        require("../../assets//homeImg/img4.png"),
        require("../../assets//homeImg/img5.png"),
        require("../../assets//homeImg/img6.png"),
        require("../../assets//homeImg/img7.png"),
      ],
      countList: [], //预警情况统计数据
      rankList: [], //预警排行榜数据
      carList: [], //车辆列表数据
      carCountData: [], //车辆统计数据
      carStatusData: {}, //车辆状态数据
      warnList: [], //预警列表数据
    };
  },
  watch: {
    map(val) {
      if (val) {
        HTMLCanvasElement.prototype.getContext = (function (origFn) {
          return function (type, attributes) {
            if (type.indexOf("webgl") > -1) {
              attributes = Object.assign({}, attributes, {
                preserveDrawingBuffer: true,
              });
            }
            return origFn.call(this, type, attributes);
          };
        })(HTMLCanvasElement.prototype.getContext);
      }
    },
  },
  filters: {
  },
  created() {
    window.toCarDetail = (record) => {
      this.toCarDetail(record);
    };
    window.fullScreen = () => {
      this.fullScreen();
    };
    window.shotScreen = () => {
      this.shotScreen();
    };
  },
  mounted() {
    // 调用所有接口
    this.getCarCountData();
    this.getCarStatusData();
    // this.getMapCarData(); // 移除这里的调用,因为 initMap 中会调用
    this.getWarnListData();
    this.getWarnGroupData();
    this.getWarnTop10Data();
    this.initMap();
    // 设置定时器,每分钟刷新一次数据
    this.timer = setInterval(() => {
      this.getCarCountData();
      this.getCarStatusData();
      this.getMapCarData(); // 保留定时器中的调用
      this.getWarnListData();
      this.getWarnGroupData();
      this.getWarnTop10Data();
    }, 60000);
  },
  beforeDestroy() {
    if (this.timer) {
      clearInterval(this.timer);
    }
    if (this.markers && this.markers.length > 0) {
      this.markers.forEach((marker) => {
        marker.setMap(null);
      });
      this.markers = [];
    }
    if (this.infoWindow) {
      this.infoWindow.close();
    }
    if (this.flvPlayer) {
      this.flvPlayer.destroy();
      this.flvPlayer = null;
    }
  },
  beforeRouteLeave(to, from, next) {
    if (this.infoWindow) {
      this.infoWindow.close();
    }
    if (this.flvPlayer) {
      this.flvPlayer.destroy();
      this.flvPlayer = null;
    }
    next();
  },
  methods: {
    // 获取车辆统计数据
    async getCarCountData() {
      try {
        const res = await getCarCount();
        this.carCountData = res;
      } catch (error) {
        this.$message.error("获取车辆统计数据失败");
      }
    },
    // 获取车辆状态数据
    async getCarStatusData() {
      try {
        const res = await getCarStatusCount();
        // 设置默认值为0,防止空值
        const online = Number(res.online) || 0;
        const offline = Number(res.offline) || 0;
        const breakdown = Number(res.breakdown) || 0;
        const abnormal = Number(res.abnormal) || 0;
        const enterprise = Number(res.enterprise) || 0;
        const car = Number(res.car) || 0;
        const driver = Number(res.driver) || 0;
        // 计算总数
        const total = online + offline + breakdown + abnormal;
        // 计算百分比,如果总数为0则百分比为0
        const onlinePercent =
          total > 0 ? Math.round((online / total) * 100) : 0;
        const offlinePercent =
          total > 0 ? Math.round((offline / total) * 100) : 0;
        const breakdownPercent =
          total > 0 ? Math.round((breakdown / total) * 100) : 0;
        const abnormalPercent =
          total > 0 ? Math.round((abnormal / total) * 100) : 0;
        // 更新数据
        this.carStatusData = {
          online,
          offline,
          breakdown,
          abnormal,
          enterprise,
          car,
          driver,
          total,
          onlinePercent,
          offlinePercent,
          breakdownPercent,
          abnormalPercent,
        };
      } catch (error) {
        this.$message.error("获取车辆状态数据失败");
        // 设置默认值
        this.carStatusData = {
          online: 0,
          offline: 0,
          breakdown: 0,
          abnormal: 0,
          enterprise: 0,
          car: 0,
          driver: 0,
          total: 0,
          onlinePercent: 0,
          offlinePercent: 0,
          breakdownPercent: 0,
          abnormalPercent: 0,
        };
      }
    },
    // 获取地图车辆数据
    async getMapCarData() {
      try {
        const res = await getMapCarList();
        this.carList = res;
        // 确保地图已初始化后再更新标记
        if (this.AMap && this.map) {
          this.updateMarkers(res);
        }
      } catch (error) {
        this.$message.error("获取地图车辆数据失败");
      }
    },
    // 获取预警列表数据
    async getWarnListData() {
      try {
        const res = await getCarWarnList();
        this.warnList = res.records;
      } catch (error) {
        this.$message.error("获取预警列表数据失败");
      }
    },
    // 获取预警统计情况数据
    async getWarnGroupData() {
      try {
        const res = await getWarnGroupCount();
        this.countList = res;
        this.getCountList();
      } catch (error) {
        this.$message.error("获取预警统计情况数据失败");
      }
    },
    // 获取预警排行数据
    async getWarnTop10Data() {
      try {
        const res = await getWarnGroupCountTop10();
        // 判断返回的数组是否为空
        if (!res || res.length === 0) {
          this.rankList = [];
          return;
        }
        // 计算所有num的总和
        const total = res.reduce((sum, item) => sum + (item.num || 0), 0);
        // 为每个数据项添加百分比属性
        this.rankList = res.map((item) => ({
          ...item,
          percentage:
            total > 0 ? (((item.num || 0) / total) * 100).toFixed(2) : 0,
        }));
      } catch (error) {
        this.$message.error("获取预警排行数据失败");
        this.rankList = [];
      }
    },
    // 初始化地图
    initMap() {
      window._AMapSecurityConfig = {
        securityJsCode: "37ce61ae86efa5ad82b649a277f5097c",
      };
      AMapLoader.load({
        key: "67968c82f27c7e2cb9f40c1a9aa3042b",
        version: "2.0",
        plugins: [
          "AMap.ToolBar",
          "AMap.AutoComplete",
          "AMap.Geocoder",
          "AMap.MarkerCluster",
          "AMap.Geocoder",
        ],
      })
        .then((AMap) => {
          this.AMap = AMap;
          this.map = new AMap.Map("mapContainer", {
            center: [105.574542, 30.5061493],
            zoom: 8,
          });
          this.infoWindow = new AMap.InfoWindow({
            offset: new AMap.Pixel(30, 30),
            autoMove: true,
            anchor: "top-center",
          });
          this.getMapCarData();
        })
        .catch((e) => {
          this.$message.error("地图加载失败");
        });
    },
    // 更新地图标记
    updateMarkers(arr) {
      if (!this.AMap) {
        return;
      }
      if (this.markers && this.markers.length > 0) {
        this.markers.forEach((marker) => {
          marker.setMap(null);
        });
        this.markers = [];
      }
      if (arr.length > 0) {
        const iconMap = {
          出租车: {
            icon: require("../../assets/homeImg/taxi.png"),
            size: new this.AMap.Size(75, 37),
          },
          公交车: {
            icon: require("../../assets/homeImg/bus.png"),
            size: new this.AMap.Size(62, 34),
          },
          危险品: {
            icon: require("../../assets/homeImg/risk.png"),
            size: new this.AMap.Size(69, 32),
          },
          郊游: {
            icon: require("../../assets/homeImg/outing.png"),
            size: new this.AMap.Size(61, 31),
          },
          货运: {
            icon: require("../../assets/homeImg/expressage.png"),
            size: new this.AMap.Size(60, 31),
          },
          网约车: {
            icon: require("../../assets/homeImg/online.png"),
            size: new this.AMap.Size(75, 33),
          },
          客运: {
            icon: require("../../assets/homeImg/passenger.png"),
            size: new this.AMap.Size(69, 31),
          },
        };
        arr.forEach((item, index) => {
          // 检查必要字段
          if (!item.operateType || !item.longitude || !item.latitude) {
            return;
          }
          const iconConfig = iconMap[item.operateType];
          if (!iconConfig) {
            return;
          }
          let marker = new this.AMap.Marker({
            position: [Number(item.longitude), Number(item.latitude)],
            map: this.map,
            icon: new this.AMap.Icon({
              size: iconConfig.size,
              image: iconConfig.icon,
              imageSize: iconConfig.size,
              imageOffset: new this.AMap.Pixel(0, 0),
            }),
          });
          // 添加点击事件
          marker.on("click", async (e) => {
            // 如果已经有视频在播放,先销毁
            if (this.flvPlayer) {
              this.flvPlayer.destroy();
              this.flvPlayer = null;
            }
            // 显示loading
            this.infoWindow.setContent(
              '<div style="padding: 20px;text-align: center;">加载中...</div>'
            );
            this.infoWindow.open(this.map, e.target.getPosition());
            try {
              // 使用高德地图API获取地址信息
              const geocoder = new this.AMap.Geocoder();
              const location = [Number(item.longitude), Number(item.latitude)];
              const [addressResult, videoRes] = await Promise.all([
                new Promise((resolve) => {
                  geocoder.getAddress(location, (status, result) => {
                    if (status === "complete" && result.regeocode) {
                      console.log("result", result,'status',status);
                      resolve(result.regeocode.formattedAddress);
                    } else {
                      resolve("未知地址");
                    }
                  });
                }),
                this.getVideoUrl(item.id),
              ]);
              // 更新弹窗内容
              this.infoWindow.setContent(
                this.listRender({
                  ...item,
                  drivingTime:this.formatterTime(item.drivingTime || 0) ,
                  location: addressResult,
                  videoUrl: videoRes.data.url,
                })
              );
              // if (flvjs.isSupported()) {
              //   this.flvPlayer = flvjs.createPlayer({
              //     type: "flv",
              //     isLive: true,
              //     cors: true,
              //     hasAudio: true,
              //     hasVideo: true,
              //     url: videoRes.data.url,
              //     enableWorker: true,
              //     enableStashBuffer: false,
              //     seekType: "range",
              //   });
              //   let video = document.getElementById("monitoringCard");
              //   this.flvPlayer.attachMediaElement(video);
              //   this.flvPlayer.load();
              //   this.flvPlayer.play();
              // }
            } catch (error) {
              this.infoWindow.setContent(
                '<div style="padding: 20px;text-align: center;color: red;">获取车辆信息失败</div>'
              );
            }
          });
          // 将marker添加到数组中
          this.markers.push(marker);
        });
      }
    },
    // 获取视频地址
    async getVideoUrl(carId) {
      try {
        const res = await getRealVideo({ id: carId });
        return res;
      } catch (error) {
        return {
          data: {
            url: "",
          },
        };
      }
    },
    listRender(record) {
      return `<div style="background: #ffffff; padding: 24px 20px;z-index: 999">
        <div style="position: relative; width: 460px; height: 330px">
          <video
            crossorigin="anonymous"
            style="width: 460px; height: 330px; border-radius: 9px"
            id="monitoringCard"
            :controls="false"
            autoplay
            width="620">
          </video>
          <div style="position: absolute; right: 11px; top: 10px">
            <div style="display: flex;flex-direction: column;align-items: center;justify-content: center;
              background: #ffffff; padding: 3px 10px; border-radius: 6px;margin-bottom: 10px;" onclick="fullScreen()">
              <img style="width: 20px; height: 20px" src="${require("../../assets//homeImg/full.png")}" />
              <div style="font-size: 12px;font-weight: 400; line-height: 17px; color: rgba(0, 0, 0, 0.88);">全屏</div>
            </div>
            <div style="display: flex;flex-direction: column;align-items: center; justify-content: center;
              background: #ffffff;padding: 3px 10px;border-radius: 6px;" onclick="shotScreen()">
              <img style="width: 20px; height: 20px" src="${require("../../assets//homeImg/slot.png")}" />
              <div style="font-size: 12px; font-weight: 400; line-height: 17px; color: rgba(0, 0, 0, 0.88);">截屏</div>
            </div>
          </div>
        </div>
        <div style="display: flex;justify-content: space-between;margin-top: 15px;margin-bottom: 12px;">
          <div style="font-weight: 500;font-size: 18px;color: rgba(0, 0, 0, 0.85);line-height: 25px;">车牌号:${
            record.vehicleNumber || ""
          }</div>
          <div style="font-weight: 500; font-size: 18px;color: rgba(0, 0, 0, 0.85);line-height: 25px;">驾驶员:${
            record.driverName || ""
          }</div>
        </div>
        <div style="display: flex; justify-content: space-between">
          <div style="font-weight: 500; font-size: 14px; color: rgba(0, 0, 0, 0.65);line-height: 26px;width: 200px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;" title="${record.location}">位置:${
            record.location
          }</div>
          <div style="font-weight: 500;font-size: 14px; color: rgba(0, 0, 0, 0.65);line-height: 26px;">经纬度:${
            record.longitude + "," + record.latitude
          }</div>
        </div>
        <div style="display: flex; justify-content: space-between">
          <div style="font-weight: 500;font-size: 14px; color: rgba(0, 0, 0, 0.65);line-height: 26px;">当前时速:${
            record.speed || ""
          }${record.speed && "km/h"}</div>
          <div style="font-weight: 500;font-size: 14px; color: rgba(0, 0, 0, 0.65);line-height: 26px;">驾驶时长:${
            record.drivingTime
          }</div>
        </div>
        <div style="margin-top: 14px;display: flex;justify-content: flex-end;align-items: center;cursor: pointer;" onclick="toCarDetail(${
          record.id
        })">
          <div style="font-weight: 400;font-size: 18px; color: rgba(22, 119, 255, 1);line-height: 25px;">车辆详情</div>
          <img style="width:18px;height: 18px;margin-left: 8px;" src="${require("../../assets//homeImg/right.png")}" />
        </div>
      </div>`;
    },
    formatterTime(value) {
      if (!value) return "";
      const hours = Math.floor(value / 60);
      const minutes = value % 60;
      if (hours > 0) {
        return `${hours}小时${minutes}分钟`;
      } else {
        return `${minutes}分钟`;
      }
    },
    // 获取预警情况统计
    getCountList() {
      echarts.dispose(document.getElementById("countChart"));
      if (this.countList.length > 0) {
        this.chartTnit();
      }
    },
    chartTnit() {
      // 基于准备好的dom,初始化echarts实例
      const myChart = echarts.init(document.getElementById("countChart"));
      // 绘制数量图表
      myChart.setOption({
        tooltip: {
          trigger: "axis",
          axisPointer: {
            type: "shadow",
          },
        },
        grid: {
          width: "auto",
          height: "auto",
          top: "5%",
          left: "3%",
          right: "4%",
          bottom: "0%",
          containLabel: true,
        },
        xAxis: [
          {
            type: "category",
            data: this.countList.map((item) => item.warnType),
            axisTick: {
              alignWithLabel: true,
              lineStyle: {
                color: "#777777",
              },
            },
            axisLabel: {
              color: "rgba(0, 0, 0, 0.45)",
            },
          },
        ],
        yAxis: [
          {
            type: "value",
          },
        ],
        series: [
          {
            type: "bar",
            barWidth: "20px",
            itemStyle: {
              borderRadius: [20, 20, 20, 20],
              color: (params) => {
                return ["#5B8FF9", "#5AD8A6", "#F6BD16", "#6DC8EC", "#945FB9"][
                  params.dataIndex
                ];
              },
            },
            data: this.countList.map((item) => item.num),
          },
        ],
      });
      myChart.resize();
    },
    // 跳转车辆详情
    toCarDetail(id) {
      this.$router.push({
        path: "/car-detail",
        query: {
          id: id,
        },
      });
    },
    fullScreen() {
      const video = document.getElementById("monitoringCard");
      if (video.requestFullscreen) {
        video.requestFullscreen();
      } else if (video.mozRequestFullScreen) {
        // Firefox
        video.mozRequestFullScreen();
      } else if (video.webkitRequestFullscreen) {
        // Chrome, Safari and Opera
        video.webkitRequestFullscreen();
      } else if (video.msRequestFullscreen) {
        // IE/Edge
        video.msRequestFullscreen();
      }
    },
    shotScreen() {
      // 获取video和canvas元素
      const video = document.getElementById("monitoringCard");
      const canvas = document.getElementById("myCanvas");
      // 设置canvas的宽度和高度与video相同
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
      // 获取canvas的2d绘图上下文
      const context = canvas.getContext("2d");
      // 将当前video帧绘制到canvas上
      context.drawImage(video, 0, 0, canvas.width, canvas.height);
      setTimeout(() => {
        // 将canvas内容转换为图片
        let dataURL = canvas.toDataURL("image/png");
        this.downloadImage(dataURL);
      }, 100);
    },
    downloadImage(base64) {
      const link = document.createElement("a");
      link.href = base64;
      link.download = "screenshot.png"; // 你希望下载的文件名
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
    },
  },
};
</script>
<style scoped lang="less">
.homePage {
  display: flex;
  height: 100vh;
  position: relative;
  .leftMap {
    // width: 100%;
    height: 100%;
    flex: 1;
    display: flex;
    position: relative;
    #mapContainer {
      flex: 1;
      width: 100%;
      height: 100%;
    }
  }
  .mapTop {
    z-index: 99;
    position: absolute;
    top: 20px;
    left: 20px;
    right: 513px;
    display: flex;
    justify-content: space-between;
    width: calc(100% - 570px);
    .title {
      font-weight: 600;
      font-size: 18px;
      color: #000000;
      line-height: 25px;
      text-transform: none;
      text-align: center;
    }
    .carCount {
      flex: 8;
      background: #ffffff;
      box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2);
      border-radius: 10px;
      padding: 14px 20px 20px 20px;
      margin-right: 20px;
      .fir {
        display: flex;
        justify-content: space-between;
        margin-bottom: 10px;
        margin-top: 11px;
        .countCard {
          margin-right: 20px;
        }
        .countCard:last-child {
          margin-right: none;
        }
      }
      .sec {
        display: flex;
        justify-content: space-between;
        .countCard {
          margin-right: 15px;
        }
        .countCard:last-child {
          margin-right: none;
        }
      }
      .countCard {
        flex: 1;
        background: linear-gradient(
          180deg,
          rgba(246, 246, 252, 0) 0%,
          #f3f4f8 100%
        );
        box-shadow: inset 0px -1px 4px 0px #ffffff;
        border-radius: 10px;
        border: 1px solid #f1f1f1;
        padding: 14px 20px 18px 14px;
        display: flex;
        justify-content: space-between;
        .iconImg {
          width: 30px;
          height: 30px;
          border-radius: 10px;
        }
        .iconImg:nth-child(1) {
          box-shadow: 0px 7px 14px 0px rgba(14, 110, 253, 0.4);
        }
        .iconImg:nth-child(2) {
          box-shadow: 0px 7px 14px 0px rgba(255, 102, 39, 0.5);
        }
        .iconImg:nth-child(3) {
          box-shadow: 0px 7px 14px 0px rgba(254, 41, 94, 0.5);
        }
        .iconImg:nth-child(4) {
          box-shadow: 0px 7px 14px 0px rgba(248, 204, 65, 0.5);
        }
        .iconImg:nth-child(5) {
          box-shadow: 0px 7px 14px 0px rgba(2, 179, 118, 0.5);
        }
        .iconImg:nth-child(6) {
          box-shadow: 0px 7px 14px 0px rgba(169, 14, 253, 0.4);
        }
        .iconImg:nth-child(7) {
          box-shadow: 0px 7px 14px 0px rgba(109, 200, 236, 0.5);
        }
        .name {
          font-weight: 500;
          font-size: 12px;
          color: rgba(0, 0, 0, 0.6);
          line-height: 17px;
          margin-top: 2px;
        }
        .num {
          font-weight: 900;
          font-size: 16px;
          color: rgba(0, 0, 0, 0.8);
          line-height: 19px;
          text-align: right;
          margin-top: 1px;
        }
      }
    }
    .carStatus {
      flex: 5;
      background: #ffffff;
      box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2);
      border-radius: 10px;
      padding: 14px 84px 20px 84px;
      .statusFir {
        margin-top: 23px;
        margin-bottom: 28px;
        display: flex;
        justify-content: space-between;
        align-content: center;
      }
      .statusSec {
        display: flex;
        justify-content: space-between;
        align-content: center;
      }
      .statusCard {
        display: flex;
        align-content: center;
        .statusLeft {
          margin-right: 9px;
          display: flex;
          flex-direction: column;
          justify-content: center;
          .name {
            font-weight: 500;
            font-size: 12px;
            color: rgba(0, 0, 0, 0.6);
            line-height: 17px;
          }
          .num {
            font-weight: 900;
            font-size: 16px;
            color: rgba(0, 0, 0, 0.8);
            line-height: 19px;
          }
        }
        .progressCard {
          width: 56px;
          height: 56px;
          ::v-deep .el-progress-circle {
            width: 56px !important;
            height: 56px !important;
          }
        }
      }
      .statusLine {
        width: 1px;
        height: 46px;
        border: 1px solid #dedede;
        box-sizing: border-box;
      }
    }
  }
  .right {
    width: 493px;
    height: calc(100% - 20px);
    margin: 20px 17px 0 20px;
    background: #ffffff;
    box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2);
    border-radius: 10px 10px 0px 0px;
    padding: 20px;
    .title {
      margin-top: 30px;
      font-weight: 600;
      font-size: 18px;
      color: rgba(0, 0, 0, 0.88);
      line-height: 25px;
      text-transform: none;
      margin-bottom: 10px;
    }
    .firCard {
      display: flex;
      justify-content: space-between;
      .companyCard {
        width: 140px;
        height: 90px;
        background: #f4f4ff;
        border-radius: 0px 10px 10px 0px;
        position: relative;
        .lineCard {
          position: absolute;
          left: 0;
          top: 0;
          width: 4px;
          height: 90px;
          background: #0e6efd;
          border-radius: 2px;
        }
        .name {
          margin: 18px 0 12px 19px;
          font-size: 12px;
          color: rgba(0, 0, 0, 0.6);
          line-height: 17px;
        }
        .value {
          margin-bottom: 22px;
          text-align: center;
          font-weight: 900;
          font-size: 18px;
          color: #0e6efd;
          line-height: 21px;
        }
      }
    }
    .todayWarn {
      .warnList {
        height: 134px;
        overflow-y: auto;
        .warnItem {
          height: 26px;
          background: rgba(39, 129, 255, 0.06);
          border-radius: 4px;
          padding: 2px;
          display: flex;
          align-content: center;
          margin-bottom: 10px;
          .grade {
            height: 22px;
            background: #e6f4ff;
            border-radius: 4px;
            border: 1px solid #bae0ff;
            margin-right: 11px;
            box-sizing: border-box;
            padding: 1px 8px;
            font-weight: 400;
            font-size: 12px;
            color: #1677ff;
            line-height: 20px;
          }
          .info {
            font-size: 12px;
            color: rgba(0, 0, 0, 0.8);
            line-height: 26px;
          }
        }
        .warnItem:last-child {
          margin-bottom: 0 !important;
        }
        .oneWarn {
          background: rgba(255, 77, 79, 0.06);
          .grade {
            background: #fff1f0;
            border: 1px solid #ffccc7;
            color: rgba(255, 77, 79, 1);
          }
        }
        .twoWarn {
          background: rgba(250, 173, 20, 0.06);
          .grade {
            background: #fffbe6;
            border-radius: 4px;
            border: 1px solid #fff1b8;
            color: rgba(250, 173, 20, 1);
          }
        }
        .threeWarn {
          background: rgba(39, 129, 255, 0.06);
          .grade {
            background: #e6f4ff;
            border: 1px solid #bae0ff;
            color: #1677ff;
          }
        }
        .fourWarn {
          background: rgba(82, 196, 26, 0.06);
          .grade {
            background: #f6ffed;
            border-radius: 4px;
            border: 1px solid #d9f7be;
            color: rgba(82, 196, 26, 1);
          }
        }
        .fiveWarn {
          background: rgba(214, 219, 228, 0.3);
          .grade {
            background: rgba(214, 219, 228, 0.3);
            border-radius: 4px;
            border: 1px solid rgba(214, 219, 228, 0.6);
            color: rgba(0, 0, 0, 0.8);
          }
        }
      }
      .warnList::-webkit-scrollbar {
        width: 8px;
        height: 8px;
        background-color: #f5f5f5;
      }
      .warnList::-webkit-scrollbar-thumb {
        background-color: #ccc;
        border-radius: 4px;
      }
      .warnList::-webkit-scrollbar-thumb:hover {
        background-color: #aaa;
      }
    }
    .warnCount {
      position: relative;
      #countChart {
        width: 453px;
        height: 150px;
      }
      .noData {
        display: flex;
        flex-direction: column;
        align-content: center;
        justify-content: center;
        position: absolute;
        bottom: 0;
        left: 0;
        width: 100%;
        height: 150px;
      }
    }
    .warnRank {
      position: relative;
      .rankChart {
        width: 453px;
        height: 300px;
        .rankItem {
          display: flex;
          align-content: center;
          margin-bottom: 14px;
          .left {
            flex: 2;
            padding-right: 25px;
            font-weight: 400;
            font-size: 12px;
            color: rgba(0, 0, 0, 0.45);
            line-height: 17px;
            text-align: right;
          }
          .rankRight {
            flex: 5;
            height: 15px;
            background: rgba(93, 112, 146, 0.2);
            border-radius: 8px;
            .rank {
              height: 15px;
              border-radius: 8px;
              background: rgba(93, 112, 146, 0.6);
            }
          }
          .rankColor {
            background: rgba(91, 143, 249, 0.2);
            .rank {
              background: rgba(91, 143, 249, 0.85);
            }
          }
        }
        .rankItem:last-child {
          margin-bottom: 0 !important;
        }
      }
      .noData {
        display: flex;
        flex-direction: column;
        align-content: center;
        justify-content: center;
        position: absolute;
        bottom: 0;
        left: 0;
        width: 100%;
        height: 300px;
      }
    }
  }
}
<template>
  <div class="flex homePage">
    <!-- 头部 -->
    <div class="mapTop">
      <!-- 车辆统计 -->
      <div class="carCount">
        <div class="title">车辆统计</div>
        <div class="fir">
          <div
            class="countCard"
            v-for="(item, index) in carCountData.slice(0, 3)"
            :key="item.id"
          >
            <img class="iconImg" :src="imgList[index]" />
            <div>
              <div class="name">{{ item.name || "" }}(辆)</div>
              <div class="num">{{ item.carNum || 0 }}</div>
            </div>
          </div>
        </div>
        <div class="sec">
          <div
            class="countCard"
            v-for="(item, index) in carCountData.slice(3, 7)"
            :key="item.id"
          >
            <img class="iconImg" :src="imgList[index + 3]" />
            <div>
              <div class="name">{{ item.name || "" }}(辆)</div>
              <div class="num">{{ item.carNum || 0 }}</div>
            </div>
          </div>
        </div>
      </div>
      <!-- 车辆状态 -->
      <div class="carStatus">
        <div class="title">车辆状态</div>
        <div class="statusFir">
          <div class="statusCard">
            <div class="statusLeft">
              <div class="name">在线</div>
              <div class="num">{{ carStatusData.online || 0 }}</div>
            </div>
            <el-progress
              type="circle"
              :width="20"
              :show-text="false"
              stroke-linecap="butt"
              :percentage="carStatusData.onlinePercent"
              color="rgba(91, 143, 249, 1)"
              define-back-color="rgba(91, 143, 249, 0.25)"
              class="progressCard"
            ></el-progress>
          </div>
          <div class="statusLine"></div>
          <div class="statusCard">
            <div class="statusLeft">
              <div class="name">离线</div>
              <div class="num">{{ carStatusData.offline || 0 }}</div>
            </div>
            <el-progress
              type="circle"
              :width="20"
              :show-text="false"
              stroke-linecap="butt"
              :percentage="carStatusData.offlinePercent"
              color="rgba(93, 112, 146, 1)"
              define-back-color="rgba(93, 112, 146, 0.25)"
              class="progressCard"
            ></el-progress>
          </div>
        </div>
        <div class="statusSec">
          <div class="statusCard">
            <div class="statusLeft">
              <div class="name">故障</div>
              <div class="num">{{ carStatusData.breakdown || 0 }}</div>
            </div>
            <el-progress
              type="circle"
              :width="20"
              :show-text="false"
              stroke-linecap="butt"
              :percentage="carStatusData.breakdownPercent"
              color="rgba(253, 83, 118, 1)"
              define-back-color="rgba(253, 83, 118, 0.25)"
              class="progressCard"
            ></el-progress>
          </div>
          <div class="statusLine"></div>
          <div class="statusCard">
            <div class="statusLeft">
              <div class="name">异常</div>
              <div class="num">{{ carStatusData.abnormal || 0 }}</div>
            </div>
            <el-progress
              type="circle"
              :width="20"
              :show-text="false"
              stroke-linecap="butt"
              :percentage="carStatusData.abnormalPercent"
              color="rgba(246, 189, 22, 1)"
              define-back-color="rgba(246, 189, 22, 0.25)"
              class="progressCard"
            ></el-progress>
          </div>
        </div>
      </div>
    </div>
    <!-- 左边 地图 -->
    <div class="leftMap">
      <div class="mapContainer" id="mapContainer"></div>
    </div>
    <!-- 右边 内容 -->
    <div class="right">
      <div class="firCard">
        <div class="companyCard">
          <div class="lineCard"></div>
          <div class="name">运营公司(家)</div>
          <div class="value">{{ carStatusData.enterprise || 0 }}</div>
        </div>
        <div class="companyCard">
          <div class="lineCard"></div>
          <div class="name">运营车辆(辆)</div>
          <div class="value">{{ carStatusData.car || 0 }}</div>
        </div>
        <div class="companyCard">
          <div class="lineCard"></div>
          <div class="name">驾驶员(人)</div>
          <div class="value">{{ carStatusData.driver || 0 }}</div>
        </div>
      </div>
      <!-- 今日预警 -->
      <div class="todayWarn">
        <div class="title">今日预警</div>
        <div class="warnList" v-if="warnList.length > 0">
          <div
            class="warnItem"
            v-for="(item, index) in warnList"
            :key="index"
            :class="
              item.warnLevel
                ? ['oneWarn', 'twoWarn', 'threeWarn', 'fourWarn'][
                    item.warnLevel - 1
                  ]
                : 'fiveWarn'
            "
          >
            <div class="grade">
              {{
                item.warnLevel
                  ? ["一级", "二级", "三级", "四级"][item.warnLevel - 1]
                  : "-"
              }}
            </div>
            <div class="info">
              {{ item.vehicleNumber }} {{ item.warnType }} {{ item.keepTime }}
              {{ item.startTime }}
            </div>
          </div>
        </div>
        <div class="noData" v-else>
          <el-empty description="暂无数据" :image-size="80"></el-empty>
        </div>
      </div>
      <!-- 预警情况统计 -->
      <div class="warnCount">
        <div class="title">预警情况统计</div>
        <div class="countChart" id="countChart"></div>
        <div class="noData" v-if="countList.length == 0">
          <el-empty description="暂无数据" :image-size="80"></el-empty>
        </div>
      </div>
      <!-- 预警排行榜(前10) -->
      <div class="warnRank">
        <div class="title">预警排行榜(前10)</div>
        <div class="rankChart" id="rankChart">
          <div class="rankItem" v-for="(item, index) in rankList" :key="index">
            <div class="left">{{ item.name }}</div>
            <div
              class="rankRight"
              :class="[0, 1, 2].includes(index) ? 'rankColor' : ''"
            >
              <div class="rank" :style="{ width: item.percentage + '%' }"></div>
            </div>
          </div>
        </div>
        <div class="noData" v-if="rankList.length == 0">
          <el-empty description="暂无数据" :image-size="80"></el-empty>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import * as echarts from "echarts";
import html2canvas from "html2canvas";
import AMapLoader from "@amap/amap-jsapi-loader";
import flvjs from "flv.js";
import {
  getCarCount,
  getCarStatusCount,
  getMapCarList,
  getCarWarnList,
  getWarnGroupCount,
  getWarnGroupCountTop10,
  getRealVideo,
} from "./service";
export default {
  data() {
    return {
      flvPlayer: null,
      activeIndex: "1",
      activeIndex2: "1",
      timer: null,
      markers: [],
      map: null,
      AMap: null,
      infoWindow: null,
      imgList: [
        require("../../assets//homeImg/img1.png"),
        require("../../assets//homeImg/img2.png"),
        require("../../assets//homeImg/img3.png"),
        require("../../assets//homeImg/img4.png"),
        require("../../assets//homeImg/img5.png"),
        require("../../assets//homeImg/img6.png"),
        require("../../assets//homeImg/img7.png"),
      ],
      countList: [], //预警情况统计数据
      rankList: [], //预警排行榜数据
      carList: [], //车辆列表数据
      carCountData: [], //车辆统计数据
      carStatusData: {}, //车辆状态数据
      warnList: [], //预警列表数据
    };
  },
  watch: {
    map(val) {
      if (val) {
        HTMLCanvasElement.prototype.getContext = (function (origFn) {
          return function (type, attributes) {
            if (type.indexOf("webgl") > -1) {
              attributes = Object.assign({}, attributes, {
                preserveDrawingBuffer: true,
              });
            }
            return origFn.call(this, type, attributes);
          };
        })(HTMLCanvasElement.prototype.getContext);
      }
    },
  },
  filters: {
  },
  created() {
    window.toCarDetail = (record) => {
      this.toCarDetail(record);
    };
    window.fullScreen = () => {
      this.fullScreen();
    };
    window.shotScreen = () => {
      this.shotScreen();
    };
  },
  mounted() {
    // 调用所有接口
    this.getCarCountData();
    this.getCarStatusData();
    // this.getMapCarData(); // 移除这里的调用,因为 initMap 中会调用
    this.getWarnListData();
    this.getWarnGroupData();
    this.getWarnTop10Data();
    this.initMap();
    // 设置定时器,每分钟刷新一次数据
    this.timer = setInterval(() => {
      this.getCarCountData();
      this.getCarStatusData();
      this.getMapCarData(); // 保留定时器中的调用
      this.getWarnListData();
      this.getWarnGroupData();
      this.getWarnTop10Data();
    }, 60000);
  },
  beforeDestroy() {
    if (this.timer) {
      clearInterval(this.timer);
    }
    if (this.markers && this.markers.length > 0) {
      this.markers.forEach((marker) => {
        marker.setMap(null);
      });
      this.markers = [];
    }
    if (this.infoWindow) {
      this.infoWindow.close();
    }
    if (this.flvPlayer) {
      this.flvPlayer.destroy();
      this.flvPlayer = null;
    }
  },
  beforeRouteLeave(to, from, next) {
    if (this.infoWindow) {
      this.infoWindow.close();
    }
    if (this.flvPlayer) {
      this.flvPlayer.destroy();
      this.flvPlayer = null;
    }
    next();
  },
  methods: {
    // 获取车辆统计数据
    async getCarCountData() {
      try {
        const res = await getCarCount();
        this.carCountData = res;
      } catch (error) {
        this.$message.error("获取车辆统计数据失败");
      }
    },
    // 获取车辆状态数据
    async getCarStatusData() {
      try {
        const res = await getCarStatusCount();
        // 设置默认值为0,防止空值
        const online = Number(res.online) || 0;
        const offline = Number(res.offline) || 0;
        const breakdown = Number(res.breakdown) || 0;
        const abnormal = Number(res.abnormal) || 0;
        const enterprise = Number(res.enterprise) || 0;
        const car = Number(res.car) || 0;
        const driver = Number(res.driver) || 0;
        // 计算总数
        const total = online + offline + breakdown + abnormal;
        // 计算百分比,如果总数为0则百分比为0
        const onlinePercent =
          total > 0 ? Math.round((online / total) * 100) : 0;
        const offlinePercent =
          total > 0 ? Math.round((offline / total) * 100) : 0;
        const breakdownPercent =
          total > 0 ? Math.round((breakdown / total) * 100) : 0;
        const abnormalPercent =
          total > 0 ? Math.round((abnormal / total) * 100) : 0;
        // 更新数据
        this.carStatusData = {
          online,
          offline,
          breakdown,
          abnormal,
          enterprise,
          car,
          driver,
          total,
          onlinePercent,
          offlinePercent,
          breakdownPercent,
          abnormalPercent,
        };
      } catch (error) {
        this.$message.error("获取车辆状态数据失败");
        // 设置默认值
        this.carStatusData = {
          online: 0,
          offline: 0,
          breakdown: 0,
          abnormal: 0,
          enterprise: 0,
          car: 0,
          driver: 0,
          total: 0,
          onlinePercent: 0,
          offlinePercent: 0,
          breakdownPercent: 0,
          abnormalPercent: 0,
        };
      }
    },
    // 获取地图车辆数据
    async getMapCarData() {
      try {
        const res = await getMapCarList();
        this.carList = res;
        // 确保地图已初始化后再更新标记
        if (this.AMap && this.map) {
          this.updateMarkers(res);
        }
      } catch (error) {
        this.$message.error("获取地图车辆数据失败");
      }
    },
    // 获取预警列表数据
    async getWarnListData() {
      try {
        const res = await getCarWarnList();
        this.warnList = res.records;
      } catch (error) {
        this.$message.error("获取预警列表数据失败");
      }
    },
    // 获取预警统计情况数据
    async getWarnGroupData() {
      try {
        const res = await getWarnGroupCount();
        this.countList = res;
        this.getCountList();
      } catch (error) {
        this.$message.error("获取预警统计情况数据失败");
      }
    },
    // 获取预警排行数据
    async getWarnTop10Data() {
      try {
        const res = await getWarnGroupCountTop10();
        // 判断返回的数组是否为空
        if (!res || res.length === 0) {
          this.rankList = [];
          return;
        }
        // 计算所有num的总和
        const total = res.reduce((sum, item) => sum + (item.num || 0), 0);
        // 为每个数据项添加百分比属性
        this.rankList = res.map((item) => ({
          ...item,
          percentage:
            total > 0 ? (((item.num || 0) / total) * 100).toFixed(2) : 0,
        }));
      } catch (error) {
        this.$message.error("获取预警排行数据失败");
        this.rankList = [];
      }
    },
    // 初始化地图
    initMap() {
      window._AMapSecurityConfig = {
        securityJsCode: this.$secretKey,
      };
      AMapLoader.load({
        key: this.$mapKey,
        version: "2.0",
        plugins: [
          "AMap.ToolBar",
          "AMap.AutoComplete",
          "AMap.Geocoder",
          "AMap.MarkerCluster",
          "AMap.Geocoder",
        ],
      })
        .then((AMap) => {
          this.AMap = AMap;
          this.map = new AMap.Map("mapContainer", {
            center: [105.574542, 30.5061493],
            zoom: 8,
          });
          this.infoWindow = new AMap.InfoWindow({
            offset: new AMap.Pixel(30, 30),
            autoMove: true,
            anchor: "top-center",
          });
          this.getMapCarData();
        })
        .catch((e) => {
          this.$message.error("地图加载失败");
        });
    },
    // 更新地图标记
    updateMarkers(arr) {
      if (!this.AMap) {
        return;
      }
      if (this.markers && this.markers.length > 0) {
        this.markers.forEach((marker) => {
          marker.setMap(null);
        });
        this.markers = [];
      }
      if (arr.length > 0) {
        const iconMap = {
          出租车: {
            icon: require("../../assets/homeImg/taxi.png"),
            size: new this.AMap.Size(75, 37),
          },
          公交车: {
            icon: require("../../assets/homeImg/bus.png"),
            size: new this.AMap.Size(62, 34),
          },
          危险品: {
            icon: require("../../assets/homeImg/risk.png"),
            size: new this.AMap.Size(69, 32),
          },
          郊游: {
            icon: require("../../assets/homeImg/outing.png"),
            size: new this.AMap.Size(61, 31),
          },
          货运: {
            icon: require("../../assets/homeImg/expressage.png"),
            size: new this.AMap.Size(60, 31),
          },
          网约车: {
            icon: require("../../assets/homeImg/online.png"),
            size: new this.AMap.Size(75, 33),
          },
          客运: {
            icon: require("../../assets/homeImg/passenger.png"),
            size: new this.AMap.Size(69, 31),
          },
        };
        arr.forEach((item, index) => {
          // 检查必要字段
          if (!item.operateType || !item.longitude || !item.latitude) {
            return;
          }
          const iconConfig = iconMap[item.operateType];
          if (!iconConfig) {
            return;
          }
          let marker = new this.AMap.Marker({
            position: [Number(item.longitude), Number(item.latitude)],
            map: this.map,
            icon: new this.AMap.Icon({
              size: iconConfig.size,
              image: iconConfig.icon,
              imageSize: iconConfig.size,
              imageOffset: new this.AMap.Pixel(0, 0),
            }),
          });
          // 添加点击事件
          marker.on("click", async (e) => {
            // 如果已经有视频在播放,先销毁
            if (this.flvPlayer) {
              this.flvPlayer.destroy();
              this.flvPlayer = null;
            }
            // 显示loading
            this.infoWindow.setContent(
              '<div style="padding: 20px;text-align: center;">加载中...</div>'
            );
            this.infoWindow.open(this.map, e.target.getPosition());
            try {
              // 使用高德地图API获取地址信息
              const geocoder = new this.AMap.Geocoder();
              const location = [Number(item.longitude), Number(item.latitude)];
              const [addressResult, videoRes] = await Promise.all([
                new Promise((resolve) => {
                  geocoder.getAddress(location, (status, result) => {
                    if (status === "complete" && result.regeocode) {
                      console.log("result", result,'status',status);
                      resolve(result.regeocode.formattedAddress);
                    } else {
                      resolve("未知地址");
                    }
                  });
                }),
                this.getVideoUrl(item.id),
              ]);
              // 更新弹窗内容
              this.infoWindow.setContent(
                this.listRender({
                  ...item,
                  drivingTime:this.formatterTime(item.drivingTime || 0) ,
                  location: addressResult,
                  videoUrl: videoRes.url,
                })
              );
              this.initVideoPlayer(videoRes.url);
            } catch (error) {
              this.infoWindow.setContent(
                '<div style="padding: 20px;text-align: center;color: red;">获取车辆信息失败</div>'
              );
            }
          });
          // 将marker添加到数组中
          this.markers.push(marker);
        });
      }
    },
    // 获取视频地址
    async getVideoUrl(carId) {
      try {
        const res = await getRealVideo({ id: carId });
        // 将RTSP流转换为FLV流
        const flvUrl = this.convertRtspToFlv(res.url);
        return {
          url: flvUrl
        };
      } catch (error) {
        console.error("获取视频地址失败", error);
        return {
          url: ""
        };
      }
    },
    // RTSP转FLV
    convertRtspToFlv(rtspUrl) {
      // 这里需要根据实际的流媒体服务器地址进行修改
      // 假设流媒体服务器地址为 http://your-media-server:8080
      const mediaServer = "http://101.206.211.65:18042";
      // 从RTSP URL中提取流标识
      const streamId = rtspUrl.split("/").pop();
      // 返回FLV流地址
      return `${mediaServer}/live/${streamId}.flv`;
    },
    // 初始化视频播放器
    initVideoPlayer(videoUrl) {
      // 先销毁之前的播放器
      if (this.flvPlayer) {
        this.flvPlayer.destroy();
        this.flvPlayer = null;
      }
      // 获取video元素
      const video = document.getElementById("monitoringCard");
      if (!video) {
        console.error("Video element not found");
        return;
      }
      // 检查flv.js是否支持
      if (flvjs.isSupported()) {
        try {
          this.flvPlayer = flvjs.createPlayer({
            type: "flv",
            isLive: true,
            cors: true,
            hasAudio: true,
            hasVideo: true,
            url: videoUrl,
            enableWorker: true,
            enableStashBuffer: false,
            seekType: "range",
          });
          this.flvPlayer.attachMediaElement(video);
          this.flvPlayer.load();
          this.flvPlayer.play().catch(error => {
            console.error("视频播放失败:", error);
          });
        } catch (error) {
          console.error("创建播放器失败:", error);
        }
      } else {
        console.error("当前浏览器不支持flv.js");
      }
    },
    // 处理视频错误
    handleVideoError(event) {
      console.error("视频加载失败", event);
      if (this.flvPlayer) {
        this.flvPlayer.destroy();
        this.flvPlayer = null;
      }
      this.infoWindow.setContent(
        '<div style="padding: 20px;text-align: center;color: red;">视频加载失败,请稍后重试</div>'
      );
    },
    listRender(record) {
      return `<div style="background: #ffffff; padding: 24px 20px;z-index: 999">
        <div style="position: relative; width: 460px; height: 330px">
          <video
            crossorigin="anonymous"
            style="width: 460px; height: 330px; border-radius: 9px"
            id="monitoringCard"
            autoplay
            playsinline
            preload="auto"
            @error="handleVideoError"
            width="620">
          </video>
          <div style="position: absolute; right: 11px; top: 10px">
            <div style="display: flex;flex-direction: column;align-items: center;justify-content: center;
              background: #ffffff; padding: 3px 10px; border-radius: 6px;margin-bottom: 10px;" onclick="fullScreen()">
              <img style="width: 20px; height: 20px" src="${require("../../assets//homeImg/full.png")}" />
              <div style="font-size: 12px;font-weight: 400; line-height: 17px; color: rgba(0, 0, 0, 0.88);">全屏</div>
            </div>
            <div style="display: flex;flex-direction: column;align-items: center; justify-content: center;
              background: #ffffff;padding: 3px 10px;border-radius: 6px;" onclick="shotScreen()">
              <img style="width: 20px; height: 20px" src="${require("../../assets//homeImg/slot.png")}" />
              <div style="font-size: 12px; font-weight: 400; line-height: 17px; color: rgba(0, 0, 0, 0.88);">截屏</div>
            </div>
          </div>
        </div>
        <div style="display: flex;justify-content: space-between;margin-top: 15px;margin-bottom: 12px;">
          <div style="font-weight: 500;font-size: 18px;color: rgba(0, 0, 0, 0.85);line-height: 25px;">车牌号:${
            record.vehicleNumber || ""
          }</div>
          <div style="font-weight: 500; font-size: 18px;color: rgba(0, 0, 0, 0.85);line-height: 25px;">驾驶员:${
            record.driverName || ""
          }</div>
        </div>
        <div style="display: flex; justify-content: space-between">
          <div style="font-weight: 500; font-size: 14px; color: rgba(0, 0, 0, 0.65);line-height: 26px;width: 200px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;" title="${record.location}">位置:${
            record.location
          }</div>
          <div style="font-weight: 500;font-size: 14px; color: rgba(0, 0, 0, 0.65);line-height: 26px;">经纬度:${
            record.longitude + "," + record.latitude
          }</div>
        </div>
        <div style="display: flex; justify-content: space-between">
          <div style="font-weight: 500;font-size: 14px; color: rgba(0, 0, 0, 0.65);line-height: 26px;">当前时速:${
            record.speed || ""
          }${record.speed && "km/h"}</div>
          <div style="font-weight: 500;font-size: 14px; color: rgba(0, 0, 0, 0.65);line-height: 26px;">驾驶时长:${
            record.drivingTime
          }</div>
        </div>
        <div style="margin-top: 14px;display: flex;justify-content: flex-end;align-items: center;cursor: pointer;" onclick="toCarDetail(${
          record.id
        })">
          <div style="font-weight: 400;font-size: 18px; color: rgba(22, 119, 255, 1);line-height: 25px;">车辆详情</div>
          <img style="width:18px;height: 18px;margin-left: 8px;" src="${require("../../assets//homeImg/right.png")}" />
        </div>
      </div>`;
    },
    formatterTime(value) {
      if (!value) return "";
      const hours = Math.floor(value / 60);
      const minutes = value % 60;
      if (hours > 0) {
        return `${hours}小时${minutes}分钟`;
      } else {
        return `${minutes}分钟`;
      }
    },
    // 获取预警情况统计
    getCountList() {
      echarts.dispose(document.getElementById("countChart"));
      if (this.countList.length > 0) {
        this.chartTnit();
      }
    },
    chartTnit() {
      // 基于准备好的dom,初始化echarts实例
      const myChart = echarts.init(document.getElementById("countChart"));
      // 绘制数量图表
      myChart.setOption({
        tooltip: {
          trigger: "axis",
          axisPointer: {
            type: "shadow",
          },
        },
        grid: {
          width: "auto",
          height: "auto",
          top: "5%",
          left: "3%",
          right: "4%",
          bottom: "0%",
          containLabel: true,
        },
        xAxis: [
          {
            type: "category",
            data: this.countList.map((item) => item.warnType),
            axisTick: {
              alignWithLabel: true,
              lineStyle: {
                color: "#777777",
              },
            },
            axisLabel: {
              color: "rgba(0, 0, 0, 0.45)",
            },
          },
        ],
        yAxis: [
          {
            type: "value",
          },
        ],
        series: [
          {
            type: "bar",
            barWidth: "20px",
            itemStyle: {
              borderRadius: [20, 20, 20, 20],
              color: (params) => {
                return ["#5B8FF9", "#5AD8A6", "#F6BD16", "#6DC8EC", "#945FB9"][
                  params.dataIndex
                ];
              },
            },
            data: this.countList.map((item) => item.num),
          },
        ],
      });
      myChart.resize();
    },
    // 跳转车辆详情
    toCarDetail(id) {
      this.$router.push({
        path: "/car-detail",
        query: {
          id: id,
        },
      });
    },
    fullScreen() {
      const video = document.getElementById("monitoringCard");
      if (video.requestFullscreen) {
        video.requestFullscreen();
      } else if (video.mozRequestFullScreen) {
        // Firefox
        video.mozRequestFullScreen();
      } else if (video.webkitRequestFullscreen) {
        // Chrome, Safari and Opera
        video.webkitRequestFullscreen();
      } else if (video.msRequestFullscreen) {
        // IE/Edge
        video.msRequestFullscreen();
      }
    },
    shotScreen() {
      // 获取video和canvas元素
      const video = document.getElementById("monitoringCard");
      const canvas = document.getElementById("myCanvas");
      // 设置canvas的宽度和高度与video相同
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
      // 获取canvas的2d绘图上下文
      const context = canvas.getContext("2d");
      // 将当前video帧绘制到canvas上
      context.drawImage(video, 0, 0, canvas.width, canvas.height);
      setTimeout(() => {
        // 将canvas内容转换为图片
        let dataURL = canvas.toDataURL("image/png");
        this.downloadImage(dataURL);
      }, 100);
    },
    downloadImage(base64) {
      const link = document.createElement("a");
      link.href = base64;
      link.download = "screenshot.png"; // 你希望下载的文件名
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
    },
  },
};
</script>
<style scoped lang="less">
.homePage {
  display: flex;
  height: 100vh;
  position: relative;
  .leftMap {
    // width: 100%;
    height: 100%;
    flex: 1;
    display: flex;
    position: relative;
    #mapContainer {
      flex: 1;
      width: 100%;
      height: 100%;
    }
  }
  .mapTop {
    z-index: 99;
    position: absolute;
    top: 20px;
    left: 20px;
    right: 513px;
    display: flex;
    justify-content: space-between;
    width: calc(100% - 570px);
    .title {
      font-weight: 600;
      font-size: 18px;
      color: #000000;
      line-height: 25px;
      text-transform: none;
      text-align: center;
    }
    .carCount {
      flex: 8;
      background: #ffffff;
      box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2);
      border-radius: 10px;
      padding: 14px 20px 20px 20px;
      margin-right: 20px;
      .fir {
        display: flex;
        justify-content: space-between;
        margin-bottom: 10px;
        margin-top: 11px;
        .countCard {
          margin-right: 20px;
        }
        .countCard:last-child {
          margin-right: none;
        }
      }
      .sec {
        display: flex;
        justify-content: space-between;
        .countCard {
          margin-right: 15px;
        }
        .countCard:last-child {
          margin-right: none;
        }
      }
      .countCard {
        flex: 1;
        background: linear-gradient(
          180deg,
          rgba(246, 246, 252, 0) 0%,
          #f3f4f8 100%
        );
        box-shadow: inset 0px -1px 4px 0px #ffffff;
        border-radius: 10px;
        border: 1px solid #f1f1f1;
        padding: 14px 20px 18px 14px;
        display: flex;
        justify-content: space-between;
        .iconImg {
          width: 30px;
          height: 30px;
          border-radius: 10px;
        }
        .iconImg:nth-child(1) {
          box-shadow: 0px 7px 14px 0px rgba(14, 110, 253, 0.4);
        }
        .iconImg:nth-child(2) {
          box-shadow: 0px 7px 14px 0px rgba(255, 102, 39, 0.5);
        }
        .iconImg:nth-child(3) {
          box-shadow: 0px 7px 14px 0px rgba(254, 41, 94, 0.5);
        }
        .iconImg:nth-child(4) {
          box-shadow: 0px 7px 14px 0px rgba(248, 204, 65, 0.5);
        }
        .iconImg:nth-child(5) {
          box-shadow: 0px 7px 14px 0px rgba(2, 179, 118, 0.5);
        }
        .iconImg:nth-child(6) {
          box-shadow: 0px 7px 14px 0px rgba(169, 14, 253, 0.4);
        }
        .iconImg:nth-child(7) {
          box-shadow: 0px 7px 14px 0px rgba(109, 200, 236, 0.5);
        }
        .name {
          font-weight: 500;
          font-size: 12px;
          color: rgba(0, 0, 0, 0.6);
          line-height: 17px;
          margin-top: 2px;
        }
        .num {
          font-weight: 900;
          font-size: 16px;
          color: rgba(0, 0, 0, 0.8);
          line-height: 19px;
          text-align: right;
          margin-top: 1px;
        }
      }
    }
    .carStatus {
      flex: 5;
      background: #ffffff;
      box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2);
      border-radius: 10px;
      padding: 14px 84px 20px 84px;
      .statusFir {
        margin-top: 23px;
        margin-bottom: 28px;
        display: flex;
        justify-content: space-between;
        align-content: center;
      }
      .statusSec {
        display: flex;
        justify-content: space-between;
        align-content: center;
      }
      .statusCard {
        display: flex;
        align-content: center;
        .statusLeft {
          margin-right: 9px;
          display: flex;
          flex-direction: column;
          justify-content: center;
          .name {
            font-weight: 500;
            font-size: 12px;
            color: rgba(0, 0, 0, 0.6);
            line-height: 17px;
          }
          .num {
            font-weight: 900;
            font-size: 16px;
            color: rgba(0, 0, 0, 0.8);
            line-height: 19px;
          }
        }
        .progressCard {
          width: 56px;
          height: 56px;
          ::v-deep .el-progress-circle {
            width: 56px !important;
            height: 56px !important;
          }
        }
      }
      .statusLine {
        width: 1px;
        height: 46px;
        border: 1px solid #dedede;
        box-sizing: border-box;
      }
    }
  }
  .right {
    width: 493px;
    height: calc(100% - 20px);
    margin: 20px 17px 0 20px;
    background: #ffffff;
    box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2);
    border-radius: 10px 10px 0px 0px;
    padding: 20px;
    .title {
      margin-top: 30px;
      font-weight: 600;
      font-size: 18px;
      color: rgba(0, 0, 0, 0.88);
      line-height: 25px;
      text-transform: none;
      margin-bottom: 10px;
    }
    .firCard {
      display: flex;
      justify-content: space-between;
      .companyCard {
        width: 140px;
        height: 90px;
        background: #f4f4ff;
        border-radius: 0px 10px 10px 0px;
        position: relative;
        .lineCard {
          position: absolute;
          left: 0;
          top: 0;
          width: 4px;
          height: 90px;
          background: #0e6efd;
          border-radius: 2px;
        }
        .name {
          margin: 18px 0 12px 19px;
          font-size: 12px;
          color: rgba(0, 0, 0, 0.6);
          line-height: 17px;
        }
        .value {
          margin-bottom: 22px;
          text-align: center;
          font-weight: 900;
          font-size: 18px;
          color: #0e6efd;
          line-height: 21px;
        }
      }
    }
    .todayWarn {
      .warnList {
        height: 134px;
        overflow-y: auto;
        .warnItem {
          height: 26px;
          background: rgba(39, 129, 255, 0.06);
          border-radius: 4px;
          padding: 2px;
          display: flex;
          align-content: center;
          margin-bottom: 10px;
          .grade {
            height: 22px;
            background: #e6f4ff;
            border-radius: 4px;
            border: 1px solid #bae0ff;
            margin-right: 11px;
            box-sizing: border-box;
            padding: 1px 8px;
            font-weight: 400;
            font-size: 12px;
            color: #1677ff;
            line-height: 20px;
          }
          .info {
            font-size: 12px;
            color: rgba(0, 0, 0, 0.8);
            line-height: 26px;
          }
        }
        .warnItem:last-child {
          margin-bottom: 0 !important;
        }
        .oneWarn {
          background: rgba(255, 77, 79, 0.06);
          .grade {
            background: #fff1f0;
            border: 1px solid #ffccc7;
            color: rgba(255, 77, 79, 1);
          }
        }
        .twoWarn {
          background: rgba(250, 173, 20, 0.06);
          .grade {
            background: #fffbe6;
            border-radius: 4px;
            border: 1px solid #fff1b8;
            color: rgba(250, 173, 20, 1);
          }
        }
        .threeWarn {
          background: rgba(39, 129, 255, 0.06);
          .grade {
            background: #e6f4ff;
            border: 1px solid #bae0ff;
            color: #1677ff;
          }
        }
        .fourWarn {
          background: rgba(82, 196, 26, 0.06);
          .grade {
            background: #f6ffed;
            border-radius: 4px;
            border: 1px solid #d9f7be;
            color: rgba(82, 196, 26, 1);
          }
        }
        .fiveWarn {
          background: rgba(214, 219, 228, 0.3);
          .grade {
            background: rgba(214, 219, 228, 0.3);
            border-radius: 4px;
            border: 1px solid rgba(214, 219, 228, 0.6);
            color: rgba(0, 0, 0, 0.8);
          }
        }
      }
      .warnList::-webkit-scrollbar {
        width: 8px;
        height: 8px;
        background-color: #f5f5f5;
      }
      .warnList::-webkit-scrollbar-thumb {
        background-color: #ccc;
        border-radius: 4px;
      }
      .warnList::-webkit-scrollbar-thumb:hover {
        background-color: #aaa;
      }
    }
    .warnCount {
      position: relative;
      #countChart {
        width: 453px;
        height: 150px;
      }
      .noData {
        display: flex;
        flex-direction: column;
        align-content: center;
        justify-content: center;
        position: absolute;
        bottom: 0;
        left: 0;
        width: 100%;
        height: 150px;
      }
    }
    .warnRank {
      position: relative;
      .rankChart {
        width: 453px;
        height: 300px;
        .rankItem {
          display: flex;
          align-content: center;
          margin-bottom: 14px;
          .left {
            flex: 2;
            padding-right: 25px;
            font-weight: 400;
            font-size: 12px;
            color: rgba(0, 0, 0, 0.45);
            line-height: 17px;
            text-align: right;
          }
          .rankRight {
            flex: 5;
            height: 15px;
            background: rgba(93, 112, 146, 0.2);
            border-radius: 8px;
            .rank {
              height: 15px;
              border-radius: 8px;
              background: rgba(93, 112, 146, 0.6);
            }
          }
          .rankColor {
            background: rgba(91, 143, 249, 0.2);
            .rank {
              background: rgba(91, 143, 249, 0.85);
            }
          }
        }
        .rankItem:last-child {
          margin-bottom: 0 !important;
        }
      }
      .noData {
        display: flex;
        flex-direction: column;
        align-content: center;
        justify-content: center;
        position: absolute;
        bottom: 0;
        left: 0;
        width: 100%;
        height: 300px;
      }
    }
  }
}
</style>