<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"
|
@click="toCarManage(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)"
|
@click="toCarManage(item.id)"
|
: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 mt-0">预警排行榜(前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,
|
playDetection,
|
closeRealVideo,
|
} from "./service";
|
export default {
|
data() {
|
return {
|
flvPlayer: null,
|
videoTimer: 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: [], //预警列表数据
|
|
serverIp: "", //监控ip
|
serverPort: "", //监控端口
|
carId: "", //监控车辆
|
};
|
},
|
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);
|
}
|
this.destroyPlayer();
|
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: {
|
toCarManage(id) {
|
this.$router.push({
|
path: "/car-manage",
|
query: { id },
|
});
|
},
|
// 获取车辆统计数据
|
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.infoWindow.on("close", () => {
|
console.log("关闭信息弹窗1111111111111111111");
|
this.destroyPlayer();
|
});
|
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) {
|
resolve(result.regeocode.formattedAddress);
|
} else {
|
resolve("未知地址");
|
}
|
});
|
}),
|
this.getVideoUrl(item.id),
|
]);
|
|
// 更新弹窗内容
|
this.infoWindow.setContent(
|
this.listRender({
|
...item,
|
drivingTime: this.formatterTime(item.drivingTime || 0),
|
location: addressResult,
|
})
|
);
|
|
this.initVideoPlayer();
|
} 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流
|
this.serverIp = res.serverIp;
|
this.serverPort = res.serverPort;
|
this.carId = carId;
|
} catch (error) {
|
console.error("获取视频地址失败", error);
|
return {};
|
}
|
},
|
|
// 初始化视频播放器
|
initVideoPlayer() {
|
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
|
});
|
let video = document.getElementById("monitoringCard");
|
this.flvPlayer.attachMediaElement(video); // video容器
|
this.flvPlayer.load();
|
this.flvPlayer
|
.play()
|
.then((res) => {
|
// 显示视频元素
|
video.style.display = 'block';
|
// 隐藏空状态
|
const emptyElement = video.parentElement.querySelector('.el-empty');
|
if (emptyElement) {
|
emptyElement.style.display = 'none';
|
}
|
|
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;
|
|
// 恢复空状态的显示
|
const video = document.getElementById("monitoringCard");
|
if (video) {
|
video.style.display = 'none';
|
const emptyElement = video.parentElement.querySelector('.el-empty');
|
if (emptyElement) {
|
emptyElement.style.display = 'block';
|
}
|
}
|
});
|
}
|
},
|
|
// 处理视频错误
|
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">
|
<div style="width: 460px; height: 330px; border-radius: 9px; background: #f5f5f5; display: flex; justify-content: center; align-items: center; flex-direction: column">
|
<video
|
ref="video"
|
style="width: 460px; height: 330px; border-radius: 9px; display: none"
|
id="monitoringCard"
|
ref="monitoringCard"
|
:controls="false"
|
autoPlay
|
width="620">
|
</video>
|
<el-empty description="暂无视频信息" :image-size="80"></el-empty>
|
</div>
|
<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()">
|
<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)",
|
interval: 0, // 强制显示所有标签
|
width: 60, // 设置标签宽度
|
height: 20, // 设置标签高度
|
overflow: 'truncate', // 截断模式
|
ellipsis: true, // 超出显示省略号
|
rotate: -40, // 可以根据需要调整角度
|
formatter: function(value) {
|
if (value.length > 4) {
|
return value.substring(0, 4) + '...';
|
}
|
return value;
|
}
|
},
|
},
|
],
|
yAxis: [
|
{
|
type: "value",
|
},
|
],
|
series: [
|
{
|
type: "bar",
|
barWidth: "20px",
|
itemStyle: {
|
borderRadius: [20, 20, 20, 20],
|
color: (params) => {
|
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),
|
},
|
],
|
});
|
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();
|
}
|
},
|
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元素
|
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;
|
}
|
.mt-0{
|
margin-top: 0 !important;
|
}
|
|
.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: 180px;
|
}
|
|
.noData {
|
display: flex;
|
flex-direction: column;
|
align-content: center;
|
justify-content: center;
|
position: absolute;
|
bottom: 0;
|
left: 0;
|
width: 100%;
|
height: 180px;
|
}
|
}
|
|
.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: 13px;
|
color: rgba(0, 0, 0, 0.45);
|
line-height: 17px;
|
text-align: right;
|
overflow: hidden;
|
white-space: nowrap;
|
text-overflow: ellipsis;
|
}
|
|
.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>
|