src/assets/header/more.pngsrc/assets/logo.pngsrc/assets/routerIcon/alarm.pngsrc/assets/routerIcon/car.pngsrc/assets/routerIcon/company.pngsrc/assets/routerIcon/complaint.pngsrc/assets/routerIcon/home.pngsrc/assets/routerIcon/order.pngsrc/assets/routerIcon/sys.pngsrc/assets/severBg.png
src/assets/title.png
src/components/PlayLive/index.vue
@@ -1,6 +1,13 @@ <template> <div style="height: 100%;"> <div style="height: 100%; position: relative;"> <video id="video" style="height: 100%;width: 100%;" muted controls></video> <div v-if="showError" class="error-box" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.8); display: flex; justify-content: center; align-items: center; z-index: 9999;"> <div class="error-content" style="text-align: center; color: #fff;"> <i class="el-icon-warning" style="font-size: 48px; color: #E6A23C; margin-bottom: 16px;"></i> <p style="margin: 8px 0; font-size: 16px;">视频播放失败</p> <p style="margin: 8px 0; font-size: 16px;">请稍后重试</p> </div> </div> </div> </template> @@ -11,7 +18,7 @@ props: { serverIp: { type: String, required: '' required: null }, serverPort: { type: Number, @@ -21,11 +28,16 @@ type: Number, required: null }, urlLink: { type: String, required: null, }, }, data() { return { flvPlayer: null, timer: null, showError: false, } }, mounted() { @@ -36,6 +48,7 @@ }, methods: { playDetection() { this.showError = false; if (flvjs.isSupported()) { playDetection(this.carId).then(res => { this.flvPlayer = flvjs.createPlayer({ @@ -44,12 +57,13 @@ cors: true,//是否开启跨域 hasAudio: false,//是否开启音频 hasVideo: true,//是否开启视频 url: `http://${this.serverIp}:${this.serverPort}/live?port=1935&app=flv&stream=${this.carId}`, // 后端拿到的视频路径 // url: `http://${this.serverIp}:${this.serverPort}/live?port=1935&app=flv&stream=${this.carId}`, // 后端拿到的视频路径 url: this.urlLink, // 后端拿到的视频路径 enableWorker: true, //启用 Web Worker 进程来加速视频的解码和处理过程 enableStashBuffer: false, // 启用数据缓存机制,提高视频的流畅度和稳定性。 stashInitialSize: 1024, // 初始缓存大小。单位:字节。建议针对直播:调整为1024kb stashInitialTime: 0.2, // 缓存初始时间。单位:秒。建议针对直播:调整为200毫秒 seekType: 'range', // 建议将其设置为“range”模式,以便更快地加载视频数据,提高视频的实时性。 seekType: 'range', // 建议将其设置为"range"模式,以便更快地加载视频数据,提高视频的实时性。 lazyLoad: false, //关闭懒加载模式,从而提高视频的实时性。建议针对直播:调整为false lazyLoadMaxDuration: 0.2, // 懒加载的最大时长。单位:秒。建议针对直播:调整为200毫秒 deferLoadAfterSourceOpen: false // 不预先加载视频数据,在 MSE(Media Source Extensions)打开后立即加载数据,提高视频的实时性。建议针对直播:调整为false @@ -62,10 +76,12 @@ playDetection(this.carId) }, 5000) }).catch(err => { this.showError = true; this.destroyPlayer(); }) // 错误监听 this.flvPlayer.on('error', (err) => { this.showError = true; this.destroyPlayer(); }); }) @@ -88,4 +104,6 @@ } </script> <style></style> <style scoped> /* 移除之前的样式,使用内联样式确保样式生效 */ </style> src/layouts/index.vue
@@ -1,9 +1,11 @@ <template> <div class="sticky top0 layout"> <div class="header relative"> <div class="header relative" style="align-items: flex-end;"> <div @click="$router.push('/home')" class="title"> <img src="@/assets/logo.png" alt=""> 射洪“两客一危”监管平台 <span> 射洪两客一危监管平台 </span> </div> <div></div> <div class="flex a-center pr--40"> @@ -23,40 +25,44 @@ </div> </div> <div class="menu w100 bgColor1"> <div v-for="(item, index) in routesList" :key="index" class="flex a-center h100"> <div v-if="!item.meta || !item.meta.title" class="h100"> <template v-for="(item, index) in routesList"> <template v-if="!item.meta || !item.meta.title"> <template v-for="(item2, index2) in item.children"> <div :key="index2" @click="pushPath(item2.path)" v-if="!item2.meta.hide" class="flex a-center j-center h100 w--160 menuItemHover pointer" <div v-if="!item2.meta.hide" :key="index2" @click="pushPath(item2.path)" class="flex a-center j-center h100 br--18 w--160 menuItemHover pointer" :class="item2.path == $route.path && 'bgColor2'"> <img v-if="item2.meta.icon" :src="require(`@/assets/routerIcon/${item2.meta.icon}.png`)" class="w--15 h--15 mr--12 shrink0" /> class="w--40 h--40 mr--12 shrink0" /> <div class="color1"> {{ item2.meta.title }} </div> </div> </template> </div> <div v-else :class="$route.path.includes('systemManage') && 'bgColor2'" class="h100 w--160 menuItemHover dropdown" @mouseenter="routerDropdown(true)" </template> <div v-else :key="index" :class="$route.path.includes('systemManage') && 'bgColor2'" class="h100 w--160 br--18 menuItemHover dropdown" @mouseenter="routerDropdown(true)" @mouseleave="routerDropdown(false)"> <div class="flex a-center j-center h100"> <img :src="require(`@/assets/routerIcon/${item.meta.icon}.png`)" class="w--15 h--15 mr--12 shrink0" /> class="w--40 h--40 mr--12 shrink0" /> <div class="color1"> {{ item.meta.title }} </div> </div> <div v-if="routerIsOpen" class="dropdown-menu positionTwo"> <template v-for="(item2, index2) in item.children"> <div v-if="!item2.meta.hide" :key="index2" @click="pushPath(item.path + '/' + item2.path)" <div v-if="!item2.meta.hide" :key="index2" @click="pushPath(item.path + '/' + item2.path)" class="dropdown-item flex a-center"> {{ item2.meta.title }} </div> </template> </div> </div> </div> </template> </div> <div class="main"> <router-view></router-view> @@ -141,11 +147,11 @@ } .bgColor1 { background-color: #0E6EFD; background-color: #3367ce; } .bgColor2 { background: #0D55B9; background: #2b5ab6; } .color1 { @@ -153,12 +159,15 @@ } .color2 { color: rgba(0, 0, 0, .6); // color: rgba(0, 0, 0, .6); color: #fff; } .header { height: 80px; background: #fff; background: #3367ce; background-image: url('../assets/title.png'); background-size: 100% 100%; display: flex; align-items: center; justify-content: space-between; @@ -167,18 +176,24 @@ display: flex; justify-content: center; font-weight: 600; font-size: 24px; font-size: 30px; align-items: center; position: absolute; top: 50%; left: 50%; top: 38%; left: 48.5%; transform: translate(-50%, -50%); color: rgba(0, 0, 0, .8); // color: rgba(0, 0, 0, .8); color: #fff; img { width: 40px; height: 40px; margin-right: 10px; width: 50px; height: 50px; margin-right: 25px; } span { letter-spacing: 4px; text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.3); // 新增字体阴影 } } } @@ -187,11 +202,24 @@ height: 60px; display: flex; align-items: center; justify-content: space-between; padding: 0 220px; } .menuItemHover { flex: 1; max-width: 160px; height: 100%; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.3s; } .menuItemHover:hover { background: #0D55B9; border-radius: 18px; background: #2b5ab6; } .dropdown { src/view/car-manage/components/detailOrderModal.vue
@@ -34,7 +34,7 @@ <!-- 行程监控 --> <div v-if="tabPosition == 'monitoring'"> <PlayLive :serverIp="monitoringData.serverIp" :serverPort="monitoringData.serverPort" :carId="orderData.carId" /> :urlLink="monitoringData.url" :carId="orderData.carId" /> </div> </el-dialog> </div> @@ -66,7 +66,7 @@ methods: { initData(orderData = {}, monitoringData = {}, travelData = []) { console.log('////////////////////////'); this.orderData = orderData this.monitoringData = monitoringData this.travelData = travelData src/view/car-manage/detail.vue
@@ -306,7 +306,8 @@ cors: true, //是否开启跨域 hasAudio: false, //是否开启音频 hasVideo: true, //是否开启视频 url: `http://${this.videoObj.serverIp}:${this.videoObj.serverPort}/live?port=1935&app=flv&stream=${this.$route.query.id}`, // 后端拿到的视频路径 // url: `http://${this.videoObj.serverIp}:${this.videoObj.serverPort}/live?port=1935&app=flv&stream=${this.$route.query.id}`, // 后端拿到的视频路径 url: this.videoObj.url, // 后端拿到的视频路径 enableWorker: true, //启用 Web Worker 进程来加速视频的解码和处理过程 enableStashBuffer: false, // 启用数据缓存机制,提高视频的流畅度和稳定性。 stashInitialSize: 1024, // 初始缓存大小。单位:字节。建议针对直播:调整为1024kb src/view/home/index.vue
@@ -6,12 +6,8 @@ <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.name)" > <div class="countCard" v-for="(item, index) in carCountData.slice(0, 3)" :key="item.id" @click="toCarManage(item.name)"> <img class="iconImg" :src="imgList[index]" /> <div> <div class="name">{{ item.name || "" }}(辆)</div> @@ -20,12 +16,8 @@ </div> </div> <div class="sec"> <div class="countCard" v-for="(item, index) in carCountData.slice(3, 7)" @click="toCarManage(item.name)" :key="item.id" > <div class="countCard" v-for="(item, index) in carCountData.slice(3, 7)" @click="toCarManage(item.name)" :key="item.id"> <img class="iconImg" :src="imgList[index + 3]" /> <div> <div class="name">{{ item.name || "" }}(辆)</div> @@ -43,16 +35,9 @@ <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> <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"> @@ -60,16 +45,9 @@ <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> <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"> @@ -78,16 +56,9 @@ <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> <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"> @@ -95,16 +66,9 @@ <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> <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> @@ -136,18 +100,12 @@ <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="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 @@ -179,10 +137,7 @@ <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="rankRight" :class="[0, 1, 2].includes(index) ? 'rankColor' : ''"> <div class="rank" :style="{ width: item.percentage + '%' }"></div> </div> </div> @@ -242,6 +197,7 @@ serverIp: "", //监控ip serverPort: "", //监控端口 carId: "", //监控车辆 urlLink: '',//视频地址 }; }, watch: { @@ -416,7 +372,7 @@ // 获取预警列表数据 async getWarnListData() { try { const res = await getCarWarnList({pageNum:1,pageSize:100000}); const res = await getCarWarnList({ pageNum: 1, pageSize: 100000 }); this.warnList = res.records; } catch (error) { this.$message.error("获取预警列表数据失败"); @@ -620,6 +576,7 @@ // 将RTSP流转换为FLV流 this.serverIp = res.serverIp; this.serverPort = res.serverPort; this.urlLink = res.url; } catch (error) { console.error("获取视频地址失败", error); return {}; @@ -628,7 +585,7 @@ // 初始化视频播放器 initVideoPlayer() { console.log('11111',this.serverIp,'2222222222',this.serverPort) console.log('11111', this.serverIp, '2222222222', this.serverPort) // 先销毁之前的播放器 if (this.flvPlayer) { this.flvPlayer.destroy(); @@ -652,7 +609,8 @@ cors: true, //是否开启跨域 hasAudio: false, //是否开启音频 hasVideo: true, //是否开启视频 url: `http://${this.serverIp}:${this.serverPort}/live?port=1935&app=flv&stream=${this.carId}`, // 后端拿到的视频路径 // url: `http://${this.serverIp}:${this.serverPort}/live?port=1935&app=flv&stream=${this.carId}`, // 后端拿到的视频路径 url: this.urlLink, // 后端拿到的视频路径 enableWorker: true, //启用 Web Worker 进程来加速视频的解码和处理过程 enableStashBuffer: false, // 启用数据缓存机制,提高视频的流畅度和稳定性。 stashInitialSize: 1024, // 初始缓存大小。单位:字节。建议针对直播:调整为1024kb @@ -675,7 +633,7 @@ if (emptyElement) { emptyElement.style.display = 'none'; } this.videoTimer = setInterval(() => { playDetection(this.carId); }, 5000); @@ -762,31 +720,24 @@ </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 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 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 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="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")}" /> @@ -848,7 +799,7 @@ overflow: 'truncate', // 截断模式 ellipsis: true, // 超出显示省略号 rotate: -40, // 可以根据需要调整角度 formatter: function(value) { formatter: function (value) { if (value.length > 4) { return value.substring(0, 4) + '...'; } @@ -972,6 +923,7 @@ height: 100%; } } .mapTop { z-index: 99; position: absolute; @@ -1029,11 +981,9 @@ .countCard { flex: 1; background: linear-gradient( 180deg, rgba(246, 246, 252, 0) 0%, #f3f4f8 100% ); 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; @@ -1180,7 +1130,8 @@ text-transform: none; margin-bottom: 10px; } .mt-0{ .mt-0 { margin-top: 0 !important; } @@ -1305,6 +1256,7 @@ color: rgba(82, 196, 26, 1); } } .fiveWarn { background: rgba(214, 219, 228, 0.3); src/view/login/index.vue
@@ -1,13 +1,11 @@ <template> <div class="bgImg"> <div class="login_box"> <div class="fs--40 fw-bold"> <h1> 射洪“两客一危”监管平台 </h1> <div class="logo"> <img src="@/assets/logo.png" alt=""> <div class="fs--25 fw-bold" style="letter-spacing: 4px;">射洪两客一危监管平台</div> </div> <div class="mt--20 txt-center py--20 px--20"> <div class="fs--20 fw-bold">登陆</div> <div class="txt-center pb--20 px--20"> <div class="mt--20"> <el-input class="w100" prefix-icon="el-icon-user" placeholder="账号" v-model="username" /> </div> @@ -118,7 +116,7 @@ }, // 校验验证码 matchCaseInsensitive(str, pattern) { return str.match(new RegExp(pattern, "i")); return str.toLowerCase() === pattern.toLowerCase(); }, resetCodeStr() { this.codeStr = generateVerificationCode(); @@ -137,6 +135,7 @@ } .login_box { width: 25%; position: absolute; top: 50%; left: 50%; @@ -145,6 +144,19 @@ box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2); border-radius: 10px; padding: 20px; .logo { display: flex; align-items: center; justify-content: center; img { width: 50px; height: 50px; margin-right: 15px; } } } .code_box { src/view/order/component/detailModal.vue
@@ -34,7 +34,7 @@ <!-- 行程监控 --> <div v-if="tabPosition == 'monitoring'"> <PlayLive :serverIp="monitoringData.serverIp" :serverPort="monitoringData.serverPort" :carId="orderData.carId" /> :urlLink="monitoringData.url" :carId="orderData.carId" /> </div> </el-dialog> </div> src/view/playback/index.vue
@@ -57,7 +57,8 @@ </el-col> <el-col :span="20"> <div class="grid-content" style=""> <PlayLive :serverPort="serverPort" :serverIp="serverIp" :carId="$route.query.id"/> <PlayLive :serverPort="serverPort" :serverIp="serverIp" :carId="$route.query.id" :urlLink="monitoringData.url" /> </div> </el-col> </el-row>