| | |
| | | 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>
|
| | |
| | | <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]" />
|
| | |
| | | }}
|
| | | </div>
|
| | | <div class="info">
|
| | | {{ item.vehicleNumber }} {{ item.warnType }}
|
| | | {{ item.keepTime }} {{ item.startTime }}
|
| | | {{ item.vehicleNumber }} {{ item.warnType }} {{ item.keepTime }} |
| | | {{ item.startTime }} |
| | | </div>
|
| | | </div>
|
| | | </div>
|
| | |
| | | getCarWarnList,
|
| | | getWarnGroupCount,
|
| | | getWarnGroupCountTop10,
|
| | | getCarInfoById,
|
| | | getRealVideo, |
| | | playDetection, |
| | | closeRealVideo, |
| | | } from "./service";
|
| | | export default {
|
| | | data() {
|
| | | return {
|
| | | flvPlayer: null,
|
| | | videoTimer: null, |
| | | activeIndex: "1",
|
| | | activeIndex2: "1",
|
| | | timer: null,
|
| | |
| | | carCountData: [], //车辆统计数据
|
| | | carStatusData: {}, //车辆状态数据
|
| | | warnList: [], //预警列表数据
|
| | | |
| | | serverIp: "", //监控ip |
| | | serverPort: "", //监控端口 |
| | | carId: "", //监控车辆 |
| | | };
|
| | | },
|
| | | watch: {
|
| | |
| | | }
|
| | | },
|
| | | },
|
| | | filters: {}, |
| | | |
| | | created() {
|
| | | window.toCarDetail = (record) => {
|
| | | this.toCarDetail(record);
|
| | |
| | | if (this.timer) {
|
| | | clearInterval(this.timer);
|
| | | }
|
| | | this.destroyPlayer(); |
| | | if (this.markers && this.markers.length > 0) {
|
| | | this.markers.forEach((marker) => {
|
| | | marker.setMap(null);
|
| | |
| | | next();
|
| | | },
|
| | | methods: {
|
| | | toCarManage(id) { |
| | | this.$router.push({ |
| | | path: "/car-manage", |
| | | query: { id }, |
| | | }); |
| | | }, |
| | | // 获取车辆统计数据
|
| | | async getCarCountData() {
|
| | | try {
|
| | |
| | | // 初始化地图
|
| | | 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) => {
|
| | |
| | | offset: new AMap.Pixel(30, 30),
|
| | | autoMove: true,
|
| | | anchor: "top-center",
|
| | | }); |
| | | // 添加信息弹窗关闭事件监听 |
| | | this.infoWindow.on("close", () => { |
| | | console.log("关闭信息弹窗1111111111111111111"); |
| | | this.destroyPlayer(); |
| | | });
|
| | | this.getMapCarData();
|
| | | })
|
| | |
| | | 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),
|
| | | ]);
|
| | |
|
| | |
| | | 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>'
|
| | |
| | | });
|
| | | }
|
| | | },
|
| | |
|
| | | // 获取车辆信息
|
| | | 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()">
|
| | |
| | | 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>
|
| | |
| | | <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() {
|
| | |
| | | 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),
|
| | |
| | | // 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元素
|
| | |
| | | color: rgba(0, 0, 0, 0.45);
|
| | | line-height: 17px;
|
| | | text-align: right;
|
| | | overflow: hidden; |
| | | white-space: nowrap; |
| | | text-overflow: ellipsis; |
| | | }
|
| | |
|
| | | .rankRight {
|