pyt
2025-04-21 dc7262f5f6131c25e9ae0a70e473e68a960ad093
src/view/home/index.vue
@@ -10,6 +10,7 @@
            class="countCard"
            v-for="(item, index) in carCountData.slice(0, 3)"
            :key="item.id"
            @click="toCarManage(item.id)"
          >
            <img class="iconImg" :src="imgList[index]" />
            <div>
@@ -22,6 +23,7 @@
          <div
            class="countCard"
            v-for="(item, index) in carCountData.slice(3, 7)"
            @click="toCarManage(item.id)"
            :key="item.id"
          >
            <img class="iconImg" :src="imgList[index + 3]" />
@@ -154,8 +156,8 @@
              }}
            </div>
            <div class="info">
              {{ item.vehicleNumber  }} {{ item.warnType  }}
              {{ item.keepTime }} {{ item.startTime }}
              {{ item.vehicleNumber }} {{ item.warnType }} {{ item.keepTime }}
              {{ item.startTime }}
            </div>
          </div>
        </div>
@@ -205,12 +207,15 @@
  getCarWarnList,
  getWarnGroupCount,
  getWarnGroupCountTop10,
  getCarInfoById,
  getRealVideo,
  playDetection,
  closeRealVideo,
} from "./service";
export default {
  data() {
    return {
      flvPlayer: null,
      videoTimer: null,
      activeIndex: "1",
      activeIndex2: "1",
      timer: null,
@@ -233,6 +238,10 @@
      carCountData: [], //车辆统计数据
      carStatusData: {}, //车辆状态数据
      warnList: [], //预警列表数据
      serverIp: "", //监控ip
      serverPort: "", //监控端口
      carId: "", //监控车辆
    };
  },
  watch: {
@@ -251,6 +260,8 @@
      }
    },
  },
  filters: {},
  created() {
    window.toCarDetail = (record) => {
      this.toCarDetail(record);
@@ -286,6 +297,7 @@
    if (this.timer) {
      clearInterval(this.timer);
    }
    this.destroyPlayer();
    if (this.markers && this.markers.length > 0) {
      this.markers.forEach((marker) => {
        marker.setMap(null);
@@ -311,6 +323,12 @@
    next();
  },
  methods: {
    toCarManage(id) {
      this.$router.push({
        path: "/car-manage",
        query: { id },
      });
    },
    // 获取车辆统计数据
    async getCarCountData() {
      try {
@@ -436,16 +454,17 @@
    // 初始化地图
    initMap() {
      window._AMapSecurityConfig = {
        securityJsCode: "37ce61ae86efa5ad82b649a277f5097c",
        securityJsCode: this.$secretKey,
      };
      AMapLoader.load({
        key: "67968c82f27c7e2cb9f40c1a9aa3042b",
        key: this.$mapKey,
        version: "2.0",
        plugins: [
          "AMap.ToolBar",
          "AMap.AutoComplete",
          "AMap.Geocoder",
          "AMap.MarkerCluster",
          "AMap.Geocoder",
        ],
      })
        .then((AMap) => {
@@ -458,6 +477,11 @@
            offset: new AMap.Pixel(30, 30),
            autoMove: true,
            anchor: "top-center",
          });
          // 添加信息弹窗关闭事件监听
          this.infoWindow.on("close", () => {
            console.log("关闭信息弹窗1111111111111111111");
            this.destroyPlayer();
          });
          this.getMapCarData();
        })
@@ -546,9 +570,20 @@
            this.infoWindow.open(this.map, e.target.getPosition());
            try {
              // 并行请求车辆信息和视频地址
              const [carInfoRes, videoRes] = await Promise.all([
                this.getCarInfo(item.id),
              // 使用高德地图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) {
                      resolve(result.regeocode.formattedAddress);
                    } else {
                      resolve("未知地址");
                    }
                  });
                }),
                this.getVideoUrl(item.id),
              ]);
@@ -556,28 +591,12 @@
              this.infoWindow.setContent(
                this.listRender({
                  ...item,
                  ...carInfoRes.data,
                  videoUrl: videoRes.data.url,
                  drivingTime: this.formatterTime(item.drivingTime || 0),
                  location: addressResult,
                })
              );
              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();
              }
              this.initVideoPlayer();
            } catch (error) {
              this.infoWindow.setContent(
                '<div style="padding: 20px;text-align: center;color: red;">获取车辆信息失败</div>'
@@ -590,64 +609,122 @@
        });
      }
    },
    // 获取车辆信息
    async getCarInfo(carId) {
      try {
        const res = await getCarInfoById({ id: carId });
        if (res) {
          return {
            data: {
              ...res,
              id: carId,
            },
          };
        }
        return {
          data: {
            id: carId,
          },
        };
      } catch (error) {
        return {
          data: {
            licensePlate: "",
            driver: "",
            location: "",
            coordinates: "",
            speed: "0km/h",
            drivingTime: "0小时0分钟",
          },
        };
      }
    },
    // 获取视频地址
    async getVideoUrl(carId) {
      // TODO: 替换为实际的API调用
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve({
            data: {
              url: "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4",
      try {
        const res = await getRealVideo({ id: carId });
        // 将RTSP流转换为FLV流
        this.serverIp = res.serverIp;
        this.serverPort = res.serverPort;
        this.carId = carId;
      } catch (error) {
        console.error("获取视频地址失败", error);
        return {};
      }
            },
    // 初始化视频播放器
    initVideoPlayer(videoUrl) {
      console.log('11111',this.serverIp,'2222222222',this.serverPort)
      // 先销毁之前的播放器
      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 {
          playDetection(this.carId).then((res) => {
            this.flvPlayer = flvjs.createPlayer({
              type: "flv", //视频类型
              isLive: true, //是否为直播
              cors: true, //是否开启跨域
              hasAudio: false, //是否开启音频
              hasVideo: true, //是否开启视频
              url: `http://${this.serverIp}:${this.serverPort}/live?port=1935&app=flv&stream=${this.carId}`, // 后端拿到的视频路径
              enableWorker: true, //启用 Web Worker 进程来加速视频的解码和处理过程
              enableStashBuffer: false, // 启用数据缓存机制,提高视频的流畅度和稳定性。
              stashInitialSize: 1024, // 初始缓存大小。单位:字节。建议针对直播:调整为1024kb
              stashInitialTime: 0.2, // 缓存初始时间。单位:秒。建议针对直播:调整为200毫秒
              seekType: "range", // 建议将其设置为“range”模式,以便更快地加载视频数据,提高视频的实时性。
              lazyLoad: false, //关闭懒加载模式,从而提高视频的实时性。建议针对直播:调整为false
              lazyLoadMaxDuration: 0.2, // 懒加载的最大时长。单位:秒。建议针对直播:调整为200毫秒
              deferLoadAfterSourceOpen: false, // 不预先加载视频数据,在 MSE(Media Source Extensions)打开后立即加载数据,提高视频的实时性。建议针对直播:调整为false
          });
        }, 500);
            let video = document.getElementById("monitoringCard");
            this.flvPlayer.attachMediaElement(video); // video容器
            this.flvPlayer.load();
            this.flvPlayer
              .play()
              .then((res) => {
                this.videoTimer = setInterval(() => {
                  playDetection(this.carId);
                }, 5000);
              })
              .catch((err) => {
                this.destroyPlayer();
      });
            // 错误监听
            this.flvPlayer.on("error", (err) => {
              this.destroyPlayer();
            });
          });
        } catch (error) {
          console.error("创建播放器失败:", error);
        }
      } else {
        console.error("当前浏览器不支持flv.js");
      }
    },
    destroyPlayer() {
      // 销毁播放器释放资源
      if (this.flvPlayer) {
        if (this.videoTimer) clearInterval(this.videoTimer);
        closeRealVideo(this.carId).then((res) => {
          this.flvPlayer.pause();
          this.flvPlayer.unload();
          this.flvPlayer.detachMediaElement();
          this.flvPlayer.destroy();
          this.flvPlayer = null;
        });
      }
    },
    // 处理视频错误
    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"
              ref="video"
            style="width: 460px; height: 330px; border-radius: 9px" 
            id="monitoringCard"
               ref="monitoringCard"
            :controls="false"
            autoplay
            src="${record.videoUrl}"
                autoPlay
            width="620">
          </video>
          <canvas id="myCanvas" style="display:none"></canvas>
          <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()">
@@ -666,21 +743,21 @@
            record.vehicleNumber || ""
          }</div>
          <div style="font-weight: 500; font-size: 18px;color: rgba(0, 0, 0, 0.85);line-height: 25px;">驾驶员:${
            record.driverName||''
            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;">位置:${
          <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
          }</div>
          }">位置:${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
            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
          }</div>
            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>
@@ -692,6 +769,16 @@
          <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() {
@@ -747,9 +834,19 @@
            itemStyle: {
              borderRadius: [20, 20, 20, 20],
              color: (params) => {
                return ["#5B8FF9", "#5AD8A6", "#F6BD16", "#6DC8EC", "#945FB9"][
                  params.dataIndex
                ];
                return [
                  "#5B8FF9",
                  "#5AD8A6",
                  "#F6BD16",
                  "#6DC8EC",
                  "#945FB9",
                  "rgba(248, 204, 65, 0.5)",
                  "rgba(2, 179, 118, 0.5)",
                  "rgba(254, 41, 94, 0.5)",
                  "rgba(255, 102, 39, 0.5)",
                  "rgba(169, 14, 253, 0.5)",
                  "rgba(109, 200, 236, 0.5)",
                ][params.dataIndex];
              },
            },
            data: this.countList.map((item) => item.num),
@@ -782,6 +879,14 @@
        // IE/Edge
        video.msRequestFullscreen();
      }
    },
    getRandomColor() {
      const letters = "0123456789ABCDEF";
      let color = "#";
      for (let i = 0; i < 6; i++) {
        color += letters[Math.floor(Math.random() * 16)];
      }
      return color;
    },
    shotScreen() {
      // 获取video和canvas元素
@@ -1229,6 +1334,9 @@
            color: rgba(0, 0, 0, 0.45);
            line-height: 17px;
            text-align: right;
            overflow: hidden;
            white-space: nowrap;
            text-overflow: ellipsis;
          }
          .rankRight {