From 1d41f70b0d1c1756546dfa529786d810d7c8cccb Mon Sep 17 00:00:00 2001 From: 董国庆 <364620639@qq.com> Date: 星期二, 08 四月 2025 15:02:57 +0800 Subject: [PATCH] 修改首页 --- src/view/home/index.vue | 2593 ++++++++++++++++++++++++++++++---------------------------- 1 files changed, 1,324 insertions(+), 1,269 deletions(-) diff --git a/src/view/home/index.vue b/src/view/home/index.vue index f7ca11f..bcb3c75 100644 --- a/src/view/home/index.vue +++ b/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> \ No newline at end of file -- Gitblit v1.7.1