Merge branch 'master' of http://120.76.84.145:10101/gitblit/r/H5/shehong-vehicle-supervision
New file |
| | |
| | | <template> |
| | | <div> |
| | | <video id="video" width="100%" height="100%" muted controls></video> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import { playDetection, closeRealVideo } from './service' |
| | | import flvjs from 'flv.js' |
| | | export default { |
| | | props: { |
| | | serverIp: { |
| | | type: String, |
| | | required: '' |
| | | }, |
| | | serverPort: { |
| | | type: Number, |
| | | required: null |
| | | }, |
| | | carId: { |
| | | type: Number, |
| | | required: null |
| | | }, |
| | | }, |
| | | data() { |
| | | return { |
| | | flvPlayer: null, |
| | | timer: null, |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.playDetection() |
| | | }, |
| | | beforeDestroy() { |
| | | this.destroyPlayer(); |
| | | }, |
| | | methods: { |
| | | playDetection() { |
| | | if (flvjs.isSupported()) { |
| | | 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('video'); |
| | | this.flvPlayer.attachMediaElement(video); // video容器 |
| | | this.flvPlayer.load(); |
| | | this.flvPlayer.play().then(res => { |
| | | this.timer = setInterval(() => { |
| | | playDetection(this.carId) |
| | | }, 5000) |
| | | }).catch(err => { |
| | | this.destroyPlayer(); |
| | | }) |
| | | // 错误监听 |
| | | this.flvPlayer.on('error', (err) => { |
| | | this.destroyPlayer(); |
| | | }); |
| | | }) |
| | | } |
| | | }, |
| | | destroyPlayer() { |
| | | // 销毁播放器释放资源 |
| | | if (this.flvPlayer) { |
| | | if (this.timer) clearInterval(this.timer) |
| | | closeRealVideo(this.carId).then(res => { |
| | | this.flvPlayer.pause(); |
| | | this.flvPlayer.unload(); |
| | | this.flvPlayer.detachMediaElement(); |
| | | this.flvPlayer.destroy(); |
| | | this.flvPlayer = null; |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style></style> |
New file |
| | |
| | | import axios from '@/utils/request'; |
| | | |
| | | // 通知后端开始获取视频流 |
| | | export const playDetection = (id) => { |
| | | return axios.get(`/system/car/playDetection/${id}`) |
| | | } |
| | | |
| | | // 通知后端开始关闭视频流 |
| | | export const closeRealVideo = (id) => { |
| | | return axios.get(`/system/car/closeRealVideo/${id}`) |
| | | } |
| | |
| | | <template>
|
| | | <div class="sticky top0 layout">
|
| | | <div class="header relative pointer">
|
| | | <div class="header relative">
|
| | | <div @click="$router.push('/home')" class="title">
|
| | | <img src="@/assets/logo.png" alt="">
|
| | | 射洪“两客一危”监管平台
|
| | |
| | | <div></div>
|
| | | <div class="flex a-center pr--40">
|
| | | <div class="flex a-center mr--72">
|
| | | <img src="@/assets/header/photo.png" class="w--32 h--32 shrink0 mr--10" />
|
| | | <div class="fs-- 18 lh--25 color2">admin</div>
|
| | | <img src="@/assets/header/photo.png" class="w--32 h--32 shrink0 mr--10"
|
| | | style="border-radius: 50%;" />
|
| | | <div class="fs-- 18 lh--25 color2">{{ $store.state.userInfo.nickName }}</div>
|
| | | </div>
|
| | | <div class="dropdown" @mouseenter="toggleDropdown(true)" @mouseleave="toggleDropdown(false)">
|
| | | <img src="@/assets/header/more.png" class="w--16 h--16" />
|
| | | <div v-if="isOpen" class="dropdown-menu">
|
| | | <div class="dropdown-item" v-for="item in menuItems" :key="item.text">
|
| | | <div @click="clickItem(item)" class="dropdown-item" v-for="item in menuItems" :key="item.text">
|
| | | <i :class="item.icon"></i> {{ item.text }}
|
| | | </div>
|
| | | </div>
|
| | |
| | | <div class="main">
|
| | | <router-view></router-view>
|
| | | </div>
|
| | | <ResetPassword v-if="passwordVisible" :row="row" :dialogVisible="passwordVisible"
|
| | | @close="passwordVisible = false, row = {}" @confirm="passwordConfirm" />
|
| | | </div>
|
| | | </template>
|
| | |
|
| | | <script>
|
| | | import routes from '@/router/router'
|
| | | import { mapMutations } from 'vuex';
|
| | | import ResetPassword from '@/view/systemManage/user/components/resetPassWord.vue'
|
| | | import { updatePwd } from '@/view/systemManage/user/service'
|
| | | export default {
|
| | | components: {
|
| | | ResetPassword,
|
| | | },
|
| | | data() {
|
| | | return {
|
| | | routesList: routes,
|
| | |
| | | menuItems: [
|
| | | { text: '密码设置' },
|
| | | { text: '退出登录' },
|
| | | ]
|
| | | ],
|
| | | passwordVisible: false,
|
| | | row: {}
|
| | | };
|
| | | },
|
| | | created() {
|
| | | },
|
| | | methods: {
|
| | | ...mapMutations(['clearToken']),
|
| | | passwordConfirm(form) {
|
| | | updatePwd(form).then(() => {
|
| | | this.row = {}
|
| | | this.passwordVisible = false
|
| | | this.msgsuccess('修改密码成功')
|
| | | })
|
| | | },
|
| | | clickItem(item) {
|
| | | switch (item.text) {
|
| | | case '密码设置':
|
| | | this.row = this.$store.state.userInfo
|
| | | this.passwordVisible = true
|
| | | break;
|
| | | case '退出登录':
|
| | | this.clearToken()
|
| | | window.location.replace(`/`);
|
| | | break;
|
| | | default:
|
| | | break;
|
| | | }
|
| | | },
|
| | | pushPath(path) {
|
| | | this.$router.push(path)
|
| | | if (this.routerIsOpen) this.routerIsOpen = false
|
| | |
| | | display: flex;
|
| | | flex-direction: column;
|
| | | height: 100%;
|
| | |
|
| | | .main {
|
| | | flex: 1;
|
| | | overflow: auto;
|
| | | }
|
| | | }
|
| | |
|
| | | .bgColor1 {
|
| | | background-color: #0E6EFD;
|
| | | }
|
| | |
| | | import {
|
| | | Message
|
| | | } from 'element-ui'
|
| | | import PlayLive from '@/components/PlayLive'
|
| | |
|
| | | Vue.use(ElementUI)
|
| | | Vue.prototype.$cookies = cookies;
|
| | | Vue.prototype.$baseURL = apiConfig.baseURL
|
| | | Vue.prototype.$mapKey = apiConfig.mapKey
|
| | | Vue.prototype.$secretKey = apiConfig.secretKey
|
| | | Vue.prototype.$store = store
|
| | | Vue.config.productionTip = false
|
| | | Vue.component('PlayLive', PlayLive)
|
| | | /* 全局TableHeight */
|
| | | Vue.prototype.$baseTableHeight = (formType) => {
|
| | | let height = window.innerHeight
|
| | |
| | | export default new Vuex.Store({
|
| | | state: {
|
| | | token: localStorage.getItem('token') || sessionStorage.getItem('token') || '',
|
| | | userInfo: localStorage.getItem('userInfo') || {}
|
| | | userInfo: JSON.parse(localStorage.getItem('userInfo')) || {}
|
| | | },
|
| | | mutations: {
|
| | | setToken(state, token) {
|
| | |
| | | const service = axios.create({
|
| | | baseURL: apiConfig.baseURL,
|
| | | withCredentials: false, // 当跨域请求时发送cookie
|
| | | timeout: 30000, // request timeout
|
| | | timeout: 60000, // request timeout
|
| | | })
|
| | | // 对 POST 请求参数进行排序
|
| | | const sortPostParams = (params) => {
|
| | |
| | | </div> |
| | | <div class="info-content-left ml--100"> |
| | | <el-descriptions :column="2"> |
| | | <el-descriptions-item label="公司名称">{{detail.enterpriseName}}</el-descriptions-item> |
| | | <el-descriptions-item label="车牌号码">{{detail.vehicleNumber}}</el-descriptions-item> |
| | | <el-descriptions-item label="车牌颜色">{{detail.licensePlateColor}}</el-descriptions-item> |
| | | <el-descriptions-item label="车辆营运类型">{{detail.operateType}}</el-descriptions-item> |
| | | <el-descriptions-item label="所属车主">{{detail.driverName}}</el-descriptions-item> |
| | | <el-descriptions-item label="联系电话">{{detail.driverPhone}}</el-descriptions-item> |
| | | <el-descriptions-item label="经营区域">{{detail.operatingArea}}</el-descriptions-item> |
| | | <el-descriptions-item label="车辆年度审验">{{detail.annualReviewStatus}}</el-descriptions-item> |
| | | <el-descriptions-item label="公司名称">{{ detail.enterpriseName }}</el-descriptions-item> |
| | | <el-descriptions-item label="车牌号码">{{ detail.vehicleNumber }}</el-descriptions-item> |
| | | <el-descriptions-item label="车牌颜色">{{ detail.licensePlateColor }}</el-descriptions-item> |
| | | <el-descriptions-item label="车辆营运类型">{{ detail.operateType }}</el-descriptions-item> |
| | | <el-descriptions-item label="所属车主">{{ detail.driverName }}</el-descriptions-item> |
| | | <el-descriptions-item label="联系电话">{{ detail.driverPhone }}</el-descriptions-item> |
| | | <el-descriptions-item label="经营区域">{{ detail.operatingArea }}</el-descriptions-item> |
| | | <el-descriptions-item label="车辆年度审验">{{ detail.annualReviewStatus }}</el-descriptions-item> |
| | | <el-descriptions-item label="驾驶证" v-if="detail.drivingLicense"> |
| | | <img class="img-size" :src="detail.drivingLicense"></img> |
| | | </el-descriptions-item> |
| | |
| | | <el-tab-pane label="订单记录" name="first"> |
| | | <div class="table-box mt--23"> |
| | | <el-table :data="tableData" border stripe style="width: 100%"> |
| | | <el-table-column prop="date" label="序号"></el-table-column> |
| | | <el-table-column prop="name" label="订单编号"></el-table-column> |
| | | <el-table-column prop="name" label="车牌号"></el-table-column> |
| | | <el-table-column prop="name" label="车辆颜色"></el-table-column> |
| | | <el-table-column prop="name" label="车辆所属公司"></el-table-column> |
| | | <el-table-column prop="name" label="上车地点"></el-table-column> |
| | | <el-table-column prop="name" label="下车地点"></el-table-column> |
| | | <el-table-column prop="name" label="载客里程"></el-table-column> |
| | | <el-table-column prop="name" label="驾驶员姓名"></el-table-column> |
| | | <el-table-column prop="name" label="驾驶员电话"></el-table-column> |
| | | <el-table-column prop="name" label="派单时间"></el-table-column> |
| | | <el-table-column prop="name" label="订单金额"></el-table-column> |
| | | <el-table-column prop="name" label="操作"> |
| | | <el-table-column prop="index" label="序号"></el-table-column> |
| | | <el-table-column prop="code" label="订单编号"></el-table-column> |
| | | <el-table-column prop="vehicleNumber" label="车牌号"></el-table-column> |
| | | <el-table-column prop="licensePlateColor" label="车牌颜色"></el-table-column> |
| | | <el-table-column prop="enterpriseName" label="车辆所属公司"></el-table-column> |
| | | <el-table-column prop="boardingPoint" label="上车地点"></el-table-column> |
| | | <el-table-column prop="dropOffPoint" label="下车地点"></el-table-column> |
| | | <el-table-column prop="passengerMileage" label="载客里程"></el-table-column> |
| | | <el-table-column prop="driverName" label="驾驶员姓名"></el-table-column> |
| | | <el-table-column prop="driverPhone" label="驾驶员电话"></el-table-column> |
| | | <el-table-column prop="orderDeliveryTime" label="派单时间"></el-table-column> |
| | | <el-table-column prop="orderAmount" label="订单金额"></el-table-column> |
| | | <el-table-column prop="option" label="操作"> |
| | | <template slot-scope="scope"> |
| | | <el-button type="text" @click="handle(scope.$index, scope.row)">详情</el-button> |
| | | </template> |
| | |
| | | <el-tab-pane label="预警记录" name="second"> |
| | | <div class="table-box mt--23"> |
| | | <el-table :data="tableData" border stripe style="width: 100%"> |
| | | <el-table-column prop="date" label="序号" fixed width="80"></el-table-column> |
| | | <el-table-column prop="name" label="车辆名称" width="120" fixed></el-table-column> |
| | | <el-table-column prop="name" label="车牌号码" width="120" fixed></el-table-column> |
| | | <el-table-column prop="name" label="持续报警" width="120"></el-table-column> |
| | | <el-table-column prop="name" label="近15分钟情况" width="120"></el-table-column> |
| | | <el-table-column prop="name" label="驾驶员名称" width="120"></el-table-column> |
| | | <el-table-column prop="name" label="所属公司" width="120"></el-table-column> |
| | | <el-table-column prop="name" label="终端编号" width="120"></el-table-column> |
| | | <el-table-column prop="name" label="开始报警时间" width="120"></el-table-column> |
| | | <el-table-column prop="name" label="结束报警时间" width="120"></el-table-column> |
| | | <el-table-column prop="name" label="持续时长" width="120"></el-table-column> |
| | | <el-table-column prop="name" label="持续里程数" width="120"></el-table-column> |
| | | <el-table-column prop="name" label="报警类型" width="120"></el-table-column> |
| | | <el-table-column prop="name" label="报警次数" width="120"></el-table-column> |
| | | <el-table-column prop="name" label="处理状态" width="120"></el-table-column> |
| | | <el-table-column prop="name" label="处理人" width="120"></el-table-column> |
| | | <el-table-column prop="name" label="处理时间" width="120"></el-table-column> |
| | | <el-table-column prop="name" label="处理描述" width="240"></el-table-column> |
| | | <el-table-column prop="index" label="序号" fixed width="80"></el-table-column> |
| | | <el-table-column prop="carName" label="车辆名称" width="120" fixed></el-table-column> |
| | | <el-table-column prop="vehicleNumber" label="车牌号码" width="120" fixed></el-table-column> |
| | | <el-table-column prop="keepWarn" label="持续报警" width="120"></el-table-column> |
| | | <el-table-column prop="name" label="近15分钟情况"> |
| | | <template #default="{ row }"> |
| | | <img src="@/assets/homeImg/eye-fill.png" alt="" @click="viewDetail(row)" |
| | | style="width: 30px;cursor: pointer;"> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="driverName" label="驾驶员名称" width="120"></el-table-column> |
| | | <el-table-column prop="enterpriseName" label="所属公司" width="120"></el-table-column> |
| | | <el-table-column prop="terminalNumber" label="终端编号" width="120"></el-table-column> |
| | | <el-table-column prop="startTime" label="开始报警时间" width="120"></el-table-column> |
| | | <el-table-column prop="endTime" label="结束报警时间" width="120"></el-table-column> |
| | | <el-table-column prop="keepTime" label="持续时长" width="120"></el-table-column> |
| | | <el-table-column prop="keepDistance" label="持续里程数" width="120"></el-table-column> |
| | | <el-table-column prop="warnType" label="报警类型" width="120"></el-table-column> |
| | | <el-table-column prop="warnNumber" label="报警次数" width="120"></el-table-column> |
| | | <el-table-column prop="treatmentState" label="处理状态" width="120"></el-table-column> |
| | | <el-table-column prop="treatmentUser" label="处理人" width="120"></el-table-column> |
| | | <el-table-column prop="treatmentTime" label="处理时间" width="120"></el-table-column> |
| | | <el-table-column prop="treatmentRemark" label="处理描述" width="240"></el-table-column> |
| | | </el-table> |
| | | |
| | | <div class="pagination-box relative mt--23 flex j-end"> |
| | |
| | | <el-form :inline="true" :model="searchForm" class="demo-form-inline"> |
| | | <el-form-item label="选择轨迹时间范围:" prop="level" class="unset_m" |
| | | style="margin-right: 15px;"> |
| | | <el-date-picker v-model="searchForm.date" type="datetimerange" range-separator="至" |
| | | start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd"> |
| | | <el-date-picker :value-format="'yyyy-MM-dd HH:mm'" v-model="searchForm.date" |
| | | type="datetimerange" range-separator="至" start-placeholder="开始日期" |
| | | end-placeholder="结束日期"> |
| | | </el-date-picker> |
| | | </el-form-item> |
| | | </el-form> |
| | |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | </div> |
| | | <DetailModal ref="detailModal" :detail="detail" |
| | | /> |
| | | <DetailModal ref="detailModal" :detail="detail" /> |
| | | <el-drawer :visible.sync="drawer" append-to-body :size="450" @close="closeDrawer"> |
| | | <div class="flex j-between a-center fs--20 pl--15 pr--15"> |
| | | <!-- 使用 Tailwind CSS 的内联十六进制颜色类 --> |
| | | <div>{{ info.vehicleNumber }}<span v-if="info.warnList && info.warnList.length > 0">({{ |
| | | info.warnList.length |
| | | }})</span></div><i @click="closeDrawer" class="el-icon-s-unfold color1 pointer"></i> |
| | | </div> |
| | | <hr class="mt--10" /> |
| | | <div class="pl--15 pr--15"> |
| | | <div class="flex mt--15"> |
| | | <div class="w--100 shrink0 color1">当前司机:</div> |
| | | <div class="color2">{{ info.driverName }}</div> |
| | | </div> |
| | | <div class="flex mt--15"> |
| | | <div class="w--100 shrink0 color1">当前车速:</div> |
| | | <div class="color2">{{ info.speed }}km/h</div> |
| | | </div> |
| | | <div class="flex mt--15"> |
| | | <div class="w--100 shrink0 color1">当前位置:</div> |
| | | <div class="color2">{{ info.nowAddress }}</div> |
| | | </div> |
| | | <div v-if="info.imageUrl" class="flex mt--15"> |
| | | <div class="w--100 shrink0 color1">抓拍照片:</div> |
| | | <el-image style="height: 100px" :src="info.imageUrl" :preview-src-list="[info.imageUrl]"> |
| | | </el-image> |
| | | </div> |
| | | </div> |
| | | <hr class="mt--10" /> |
| | | <div class="flex j-between a-center fs--15 pl--15 pr--15 mt--15 color2"> |
| | | 近15分钟报警 |
| | | </div> |
| | | <div class="block pl--15 pr--15 mt--15"> |
| | | <el-timeline> |
| | | <el-timeline-item v-for="(item, index) in info.warnList" :key="index" color='#0E6EFD' |
| | | :timestamp="item.warnTime" placement="top"> |
| | | <div @click="initMap1(item)"> |
| | | <el-card class="pointer"> |
| | | <h4>{{ item.warnType }}</h4> |
| | | <p class="color1">{{ item.speed }}km/h</p> |
| | | </el-card> |
| | | </div> |
| | | |
| | | </el-timeline-item> |
| | | </el-timeline> |
| | | </div> |
| | | <div v-if="showWarnDetail" class="fixed"> |
| | | <div class="card"> |
| | | <div class="title fs--18 color2">视频信号遮挡报警</div> |
| | | <div id="mapContainers"></div> |
| | | <div class=""> |
| | | <div class="flex mt--15"> |
| | | <div class="w--100 shrink0 color1">司机:</div> |
| | | <div class="color2">{{ info.driverName }}</div> |
| | | </div> |
| | | <div class="flex mt--15"> |
| | | <div class="w--100 shrink0 color1">车速:</div> |
| | | <div class="color2">{{ activeInfo.speed }}km/h</div> |
| | | </div> |
| | | <div class="flex mt--15"> |
| | | <div class="w--100 shrink0 color1">时间:</div> |
| | | <div class="color2">{{ activeInfo.warnTime }}</div> |
| | | </div> |
| | | <div class="flex mt--15"> |
| | | <div class="w--100 shrink0 color1">地点:</div> |
| | | <div class="color2">{{ activeInfo.address }}</div> |
| | | </div> |
| | | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-drawer> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import AMapLoader from "@amap/amap-jsapi-loader"; |
| | | import DetailModal from "./components/detailModal.vue"; |
| | | import { getCarDetail,getCarOrder,getCarWarning,getCarTrack } from './service' |
| | | import { getCarDetail, getCarOrder, getCarWarning, getCarTrack, getCarVideo,getDetail } from './service' |
| | | export default { |
| | | name: "detail", |
| | | components: { DetailModal }, |
| | |
| | | searchForm: { |
| | | pageCurr: 1, |
| | | pageSize: 10, |
| | | total: 0 |
| | | total: 0, |
| | | date: undefined, |
| | | }, |
| | | detail:{}, |
| | | detail: {}, |
| | | activeName: 'first', |
| | | routeList:[], |
| | | routeList: [], |
| | | videoObj: {}, |
| | | drawer: false, |
| | | showWarnDetail: false, |
| | | info: {}, |
| | | activeInfo: {}, |
| | | map: null |
| | | } |
| | | }, |
| | | mounted() { |
| | | if(this.$route.query.id){ |
| | | getCarDetail({id: this.$route.query.id}).then(res => { |
| | | if (this.$route.query.id) { |
| | | getCarDetail({ id: this.$route.query.id }).then(res => { |
| | | this.detail = res; |
| | | this.getList(res.vehicleNumber); |
| | | }) |
| | | getCarVideo({ id: this.$route.query.id }).then(res => { |
| | | this.videoObj = res; |
| | | }) |
| | | } |
| | | |
| | | |
| | | }, |
| | | methods: { |
| | | closeDrawer() { |
| | | this.drawer = false |
| | | this.showWarnDetail = false |
| | | |
| | | }, |
| | | // 查看详情 |
| | | viewDetail(row) { |
| | | this.drawer = true |
| | | getDetail({ vehicleNumber: row.vehicleNumber }).then(res => { |
| | | this.info = res |
| | | }) |
| | | }, |
| | | // 初始化地图 |
| | | initMap1(row) { |
| | | this.showWarnDetail = true |
| | | this.activeInfo = row |
| | | this.$nextTick(() => { |
| | | window._AMapSecurityConfig = { |
| | | securityJsCode: this.$secretKey, |
| | | }; |
| | | AMapLoader.load({ |
| | | key:this.$mapKey, |
| | | version: "2.0", |
| | | plugins: [ |
| | | "AMap.ToolBar", |
| | | "AMap.AutoComplete", |
| | | "AMap.Geocoder", |
| | | "AMap.MarkerCluster", |
| | | ], |
| | | }) |
| | | .then((AMap) => { |
| | | this.map = new AMap.Map("mapContainers", { |
| | | center: [row.lon, row.lat], |
| | | zoom: 15, |
| | | }); |
| | | |
| | | // 添加标记 |
| | | new AMap.Marker({ |
| | | position: [row.lon, row.lat], |
| | | map: this.map, |
| | | title: row.warnType |
| | | }); |
| | | }) |
| | | .catch((e) => { |
| | | console.log(e); |
| | | }); |
| | | }) |
| | | }, |
| | | handleClick(e) { |
| | | this.activeName = e.name |
| | | if(e.name != 'third'){ |
| | | if (e.name != 'third') { |
| | | this.searchForm = { |
| | | pageCurr: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | } |
| | | |
| | | this.getList(this.detail.vehicleNumber) |
| | | }else{ |
| | | getCarTrack({...this.searchForm,vehicleNumber:this.detail.vehicleNumber}).then(res => { |
| | | this.routeList = res; |
| | | if(res.length > 0){ |
| | | this.initMap(); |
| | | } |
| | | //销毁地图 |
| | | if (this.map) { |
| | | this.map.destroy(); |
| | | } |
| | | } else { |
| | | getCarTrack({ ...this.searchForm, vehicleNumber: this.detail.vehicleNumber }).then(res => { |
| | | this.routeList = res; |
| | | if (res.length > 0) { |
| | | this.initMap(); |
| | | } |
| | | }) |
| | | } |
| | | }, |
| | | search() { |
| | | if (this.searchForm.date != undefined) { |
| | | |
| | | }, |
| | | reset() { |
| | | this.searchForm = { |
| | | pageCurr: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | startTime: this.searchForm.date[0], |
| | | endTime: this.searchForm.date[1], |
| | | } |
| | | getCarTrack({ ...this.searchForm, vehicleNumber: this.detail.vehicleNumber }).then(res => { |
| | | this.routeList = res; |
| | | if (res.length > 0) { |
| | | this.initMap(); |
| | | } |
| | | }) |
| | | } |
| | | |
| | | }, |
| | | getList(vehicleNumber) { |
| | | if(this.activeName == 'first'){ |
| | | getCarOrder({...this.searchForm,vehicleNumber:vehicleNumber}).then(res => { |
| | | reset() { |
| | | this.searchForm = { |
| | | pageCurr: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | date: undefined, |
| | | } |
| | | }, |
| | | getList(vehicleNumber) { |
| | | if (this.activeName == 'first') { |
| | | getCarOrder({ ...this.searchForm, vehicleNumber: vehicleNumber }).then(res => { |
| | | this.tableData = res.records; |
| | | this.searchForm.total = res.total; |
| | | }) |
| | | }else{ |
| | | getCarWarning({...this.searchForm,vehicleNumber:vehicleNumber}).then(res => { |
| | | this.tableData = res.records; |
| | | }) |
| | | } else { |
| | | getCarWarning({ ...this.searchForm, vehicleNumber: vehicleNumber }).then(res => { |
| | | this.tableData = res.records; |
| | | this.searchForm.total = res.total; |
| | | }) |
| | | } |
| | | }, |
| | | |
| | | |
| | | |
| | | showDetail() { |
| | | this.$refs.detailModal.dialogVisible = true |
| | | }, |
| | |
| | | }); |
| | | this.map.addControl(new AMap.ToolBar()); |
| | | |
| | | let path = [ |
| | | // new AMap.LngLat(105.57, 30.51), |
| | | // new AMap.LngLat(116.382122, 39.901176), |
| | | // new AMap.LngLat(116.387271, 39.912501), |
| | | // new AMap.LngLat(116.398258, 39.9046), |
| | | ] |
| | | let path = [] |
| | | const iconMap = { |
| | | 出租车: { |
| | | icon: require("../../assets/homeImg/taxi.png"), |
| | | size: new AMap.Size(75, 37), |
| | | }, |
| | | 公交车: { |
| | | icon: require("../../assets/homeImg/bus.png"), |
| | | size: new AMap.Size(62, 34), |
| | | }, |
| | | 危险品: { |
| | | icon: require("../../assets/homeImg/risk.png"), |
| | | size: new AMap.Size(69, 32), |
| | | }, |
| | | 郊游: { |
| | | icon: require("../../assets/homeImg/outing.png"), |
| | | size: new AMap.Size(61, 31), |
| | | }, |
| | | 货运: { |
| | | icon: require("../../assets/homeImg/expressage.png"), |
| | | size: new AMap.Size(60, 31), |
| | | }, |
| | | 网约车: { |
| | | icon: require("../../assets/homeImg/online.png"), |
| | | size: new AMap.Size(75, 33), |
| | | }, |
| | | 客运: { |
| | | icon: require("../../assets/homeImg/passenger.png"), |
| | | size: new AMap.Size(69, 31), |
| | | }, |
| | | }; |
| | | |
| | | this.routeList.forEach(item => { |
| | | path.push(new AMap.LngLat(item.longitude, item.latitude)) |
| | | }) |
| | | |
| | | const content = `<div class="custom-content-marker"> |
| | | <img src="${require("@/assets/logo.png")}"> |
| | | <img src="${iconMap[this.detail.operateType].icon}"> |
| | | </div>`; |
| | | const marker = new AMap.Marker({ |
| | | content: content, //自定义点标记覆盖物内容 |
| | | position: [105.57, 30.51], //基点位置 |
| | | offset: new AMap.Pixel(-30, -15), //相对于基点的偏移位置 |
| | | size: iconMap[this.detail.operateType].size, |
| | | }); |
| | | this.map.add(marker); |
| | | let polyline = new AMap.Polyline({ |
| | |
| | | width: 100%; |
| | | height: 600px; |
| | | } |
| | | #mapContainers { |
| | | width: 100%; |
| | | height: 500px; |
| | | margin: 20px 0; |
| | | } |
| | | |
| | | ::v-deep .el-drawer__body { |
| | | position: relative; |
| | | |
| | | .fixed { |
| | | right: 470px; |
| | | top: 0; |
| | | bottom: 0; |
| | | margin: auto; |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | .card { |
| | | background: #fff; |
| | | width: 500px; |
| | | max-height: 1000px; |
| | | padding: 15px; |
| | | } |
| | | } |
| | | } |
| | | .color1 { |
| | | color: #0E6EFD; |
| | | } |
| | | |
| | | .color2 { |
| | | color: rgb(52, 52, 52); |
| | | } |
| | | |
| | | ::v-deep .pagination-popper { |
| | | position: fixed !important; |
| | | transform: |
| | | scale(calc(1 / var(--scale))) translate(calc(100px * (1 - 1 / var(--scale))), |
| | | calc(5px * (1 - 1 / var(--scale)))) !important; |
| | | transform-origin: right top !important; |
| | | right: calc(30px * (1 - 1 / var(--scale))) !important; |
| | | margin-top: 5px; |
| | | min-width: 100px !important; |
| | | } |
| | | </style> |
| | |
| | | size="small">重置</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table-box-btn mt--23 ml--30"> |
| | | <el-button class="search-button h--40 w--90 fs--14" icon="el-icon-top" type="primary" size="small" |
| | | @click="exportExcell">导出</el-button> |
| | | </div> |
| | | <div class="table-box ml--30 mt--23 mr--30"> |
| | | <el-table :data="tableData" border stripe style="width: 100%"> |
| | | <el-table-column type="index" label="序号" width="60"></el-table-column> |
| | |
| | | getCarType().then(res => { |
| | | this.options = res; |
| | | }); |
| | | // 判断URL参数并赋值 |
| | | const query = this.$route.query; |
| | | if (query && Object.keys(query).length > 0) { |
| | | if(query.id){ |
| | | this.searchForm.operationType = Number(query.id); |
| | | } |
| | | } |
| | | this.getList(); |
| | | }, |
| | | methods: { |
| | |
| | | //获取车辆详情行程轨迹 |
| | | export const getCarTrack = (data) => { |
| | | return axios.get(`/system/car/getCarTravel`, {params:data}) |
| | | } |
| | | |
| | | //获取车辆详情实时视频 |
| | | export const getCarVideo = (data) => { |
| | | return axios.get(`/system/car/getRealVideo/${data.id}`) |
| | | } |
| | | |
| | | // 获取车辆预警详情 |
| | | export const getDetail = (params) => { |
| | | console.log(params) |
| | | return axios.get('/system/warn/getCarWarnInfo', {params}) |
| | | } |
| | |
| | | <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,
|
| | | getCarInfoById,
|
| | | } 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);
|
| | | }
|
| | | },
|
| | | },
|
| | | 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",
|
| | | ],
|
| | | })
|
| | | .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 {
|
| | | // 并行请求车辆信息和视频地址
|
| | | const [carInfoRes, videoRes] = await Promise.all([
|
| | | this.getCarInfo(item.id),
|
| | | this.getVideoUrl(item.id),
|
| | | ]);
|
| | |
|
| | | // 更新弹窗内容
|
| | | this.infoWindow.setContent(
|
| | | this.listRender({
|
| | | ...item,
|
| | | ...carInfoRes.data,
|
| | | 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 getCarInfo(carId) {
|
| | | try {
|
| | | const res = await getCarInfoById({ id: carId });
|
| | | if (res) {
|
| | | return {
|
| | | data: {
|
| | | ...res,
|
| | | id: carId,
|
| | | },
|
| | | };
|
| | | }
|
| | | return {
|
| | | data: {
|
| | | id: carId,
|
| | | },
|
| | | };
|
| | | } catch (error) {
|
| | | return {
|
| | | data: {
|
| | | licensePlate: "",
|
| | | driver: "",
|
| | | location: "",
|
| | | coordinates: "",
|
| | | speed: "0km/h",
|
| | | drivingTime: "0小时0分钟",
|
| | | },
|
| | | };
|
| | | }
|
| | | },
|
| | |
|
| | | // 获取视频地址
|
| | | async getVideoUrl(carId) {
|
| | | // TODO: 替换为实际的API调用
|
| | | return new Promise((resolve) => {
|
| | | setTimeout(() => {
|
| | | resolve({
|
| | | data: {
|
| | | url: "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4",
|
| | | },
|
| | | });
|
| | | }, 500);
|
| | | });
|
| | | },
|
| | |
|
| | | 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
|
| | | src="${record.videoUrl}"
|
| | | 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;">位置:${
|
| | | 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
|
| | | }</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>`;
|
| | | },
|
| | | // 获取预警情况统计
|
| | | 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" |
| | | @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">预警排行榜(前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(videoUrl) { |
| | | console.log('11111',this.serverIp,'2222222222',this.serverPort) |
| | | // 先销毁之前的播放器 |
| | | if (this.flvPlayer) { |
| | | this.flvPlayer.destroy(); |
| | | this.flvPlayer = null; |
| | | } |
| | | |
| | | // 获取video元素 |
| | | const video = document.getElementById("monitoringCard"); |
| | | if (!video) { |
| | | console.error("Video element not found"); |
| | | return; |
| | | } |
| | | |
| | | // 检查flv.js是否支持 |
| | | if (flvjs.isSupported()) { |
| | | try { |
| | | playDetection(this.carId).then((res) => { |
| | | this.flvPlayer = flvjs.createPlayer({ |
| | | type: "flv", //视频类型 |
| | | isLive: true, //是否为直播 |
| | | cors: true, //是否开启跨域 |
| | | hasAudio: false, //是否开启音频 |
| | | hasVideo: true, //是否开启视频 |
| | | url: `http://${this.serverIp}:${this.serverPort}/live?port=1935&app=flv&stream=${this.carId}`, // 后端拿到的视频路径 |
| | | enableWorker: true, //启用 Web Worker 进程来加速视频的解码和处理过程 |
| | | enableStashBuffer: false, // 启用数据缓存机制,提高视频的流畅度和稳定性。 |
| | | stashInitialSize: 1024, // 初始缓存大小。单位:字节。建议针对直播:调整为1024kb |
| | | stashInitialTime: 0.2, // 缓存初始时间。单位:秒。建议针对直播:调整为200毫秒 |
| | | seekType: "range", // 建议将其设置为“range”模式,以便更快地加载视频数据,提高视频的实时性。 |
| | | lazyLoad: false, //关闭懒加载模式,从而提高视频的实时性。建议针对直播:调整为false |
| | | lazyLoadMaxDuration: 0.2, // 懒加载的最大时长。单位:秒。建议针对直播:调整为200毫秒 |
| | | deferLoadAfterSourceOpen: false, // 不预先加载视频数据,在 MSE(Media Source Extensions)打开后立即加载数据,提高视频的实时性。建议针对直播:调整为false |
| | | }); |
| | | let video = document.getElementById("monitoringCard"); |
| | | this.flvPlayer.attachMediaElement(video); // video容器 |
| | | this.flvPlayer.load(); |
| | | this.flvPlayer |
| | | .play() |
| | | .then((res) => { |
| | | this.videoTimer = setInterval(() => { |
| | | playDetection(this.carId); |
| | | }, 5000); |
| | | }) |
| | | .catch((err) => { |
| | | this.destroyPlayer(); |
| | | }); |
| | | // 错误监听 |
| | | this.flvPlayer.on("error", (err) => { |
| | | this.destroyPlayer(); |
| | | }); |
| | | }); |
| | | } catch (error) { |
| | | console.error("创建播放器失败:", error); |
| | | } |
| | | } else { |
| | | console.error("当前浏览器不支持flv.js"); |
| | | } |
| | | }, |
| | | |
| | | destroyPlayer() { |
| | | // 销毁播放器释放资源 |
| | | if (this.flvPlayer) { |
| | | if (this.videoTimer) clearInterval(this.videoTimer); |
| | | closeRealVideo(this.carId).then((res) => { |
| | | this.flvPlayer.pause(); |
| | | this.flvPlayer.unload(); |
| | | this.flvPlayer.detachMediaElement(); |
| | | this.flvPlayer.destroy(); |
| | | this.flvPlayer = null; |
| | | }); |
| | | } |
| | | }, |
| | | |
| | | // 处理视频错误 |
| | | handleVideoError(event) { |
| | | console.error("视频加载失败", event); |
| | | if (this.flvPlayer) { |
| | | this.flvPlayer.destroy(); |
| | | this.flvPlayer = null; |
| | | } |
| | | this.infoWindow.setContent( |
| | | '<div style="padding: 20px;text-align: center;color: red;">视频加载失败,请稍后重试</div>' |
| | | ); |
| | | }, |
| | | |
| | | listRender(record) { |
| | | return `<div style="background: #ffffff; padding: 24px 20px;z-index: 999"> |
| | | <div style="position: relative; width: 460px; height: 330px"> |
| | | |
| | | <video |
| | | ref="video" |
| | | style="width: 460px; height: 330px; border-radius: 9px" |
| | | id="monitoringCard" |
| | | ref="monitoringCard" |
| | | :controls="false" |
| | | autoPlay |
| | | width="620"> |
| | | </video> |
| | | <canvas id="myCanvas" style="display:none"></canvas> |
| | | <div style="position: absolute; right: 11px; top: 10px"> |
| | | <div style="display: flex;flex-direction: column;align-items: center;justify-content: center; |
| | | background: #ffffff; padding: 3px 10px; border-radius: 6px;margin-bottom: 10px;" onclick="fullScreen()"> |
| | | <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", |
| | | "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; |
| | | } |
| | | |
| | | .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; |
| | | 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> |
| | |
| | | // 获取预警排行统计前10 |
| | | export const getCarInfoById = (data) => { |
| | | return axios.get(`/system/car/getCarInfo/${data.id}`, data) |
| | | } |
| | | // 获取监控 |
| | | export const getRealVideo = (data) => { |
| | | return axios.get(`/system/car/getRealVideo/${data.id}`, data) |
| | | } |
| | | |
| | | // 通知后端开始获取视频流 |
| | | export const playDetection = (id) => { |
| | | return axios.get(`/system/car/playDetection/${id}`) |
| | | } |
| | | |
| | | // 通知后端开始关闭视频流 |
| | | export const closeRealVideo = (id) => { |
| | | return axios.get(`/system/car/closeRealVideo/${id}`) |
| | | } |
| | |
| | | <template> |
| | | <div> |
| | | <div class="bgImg"> |
| | | <div class="login_box"> |
| | | <div class="fs--40 fw-bold"> |
| | | <h1> |
| | | 射洪“两客一危”监管平台 |
| | | </h1> |
| | | </div> |
| | | <div class="mt--20 txt-center py--20 px--20 br--10 box-s1"> |
| | | <div class="mt--20 txt-center py--20 px--20"> |
| | | <div class="fs--20 fw-bold">登陆</div> |
| | | <div class="mt--20"> |
| | | <el-input class="w100" prefix-icon="el-icon-user" placeholder="账号" v-model="username" /> |
| | |
| | | document.removeEventListener("keydown", this.handleKeyDown); |
| | | }, |
| | | methods: { |
| | | ...mapMutations(['setToken', 'clearToken', 'setUserInfo']), |
| | | ...mapMutations(['setToken', 'setUserInfo']), |
| | | login() { |
| | | if (!this.rulesLogin()) return |
| | | this.loginLoading = true; |
| | | loginPwd({ |
| | | username: this.username, |
| | | password: CryptoJS.HmacMD5(this.password, 'password').toString( |
| | | CryptoJS.enc.Hex, |
| | | ) |
| | | password: CryptoJS.MD5(this.password).toString() |
| | | }).then(res => { |
| | | localStorage.setItem('client', generateRandomString(16)); |
| | | this.loginLoading = false; |
| | |
| | | }; |
| | | </script> |
| | | <style scoped lang="less"> |
| | | .bgImg { |
| | | background-image: url('../../assets/loginBG.png'); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | .login_box { |
| | | position: absolute; |
| | | top: 50%; |
| | | left: 50%; |
| | | transform: translate(-50%, -50%); |
| | | } |
| | | |
| | | .box-s1 { |
| | | background-color: #fff; |
| | | box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2); |
| | | border-radius: 10px; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .code_box { |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog title="订单详情" :visible.sync="dialogVisible" width="50%" :modal-append-to-body="false"> |
| | | <el-dialog title="订单详情" :visible.sync="dialogVisible" width="50%" :modal-append-to-body="false" |
| | | @close="closeClick"> |
| | | <el-radio-group v-model="tabPosition" style="margin-bottom: 30px;"> |
| | | <el-radio-button label="order">订单信息</el-radio-button> |
| | | <el-radio-button label="track">行程轨迹</el-radio-button> |
| | |
| | | </el-descriptions> |
| | | </div> |
| | | <!-- 行程轨迹 --> |
| | | <div v-show="tabPosition == 'track'"> |
| | | <div v-if="tabPosition == 'track'"> |
| | | <div class="mapContainer" id="mapContainer"></div> |
| | | </div> |
| | | <!-- 行程监控 --> |
| | | <div v-show="tabPosition == 'monitoring'"></div> |
| | | <div v-if="tabPosition == 'monitoring'"> |
| | | <PlayLive :serverIp="monitoringData.serverIp" :serverPort="monitoringData.serverPort" |
| | | :carId="orderData.carId" /> |
| | | </div> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | |
| | | tabPosition: 'order', |
| | | orderData: {}, |
| | | monitoringData: {}, |
| | | travelData: [] |
| | | travelData: [], |
| | | }; |
| | | }, |
| | | computed: {}, |
| | | watch: {}, |
| | | created() { }, |
| | | watch: { |
| | | tabPosition(val) { |
| | | if (val == 'track') { |
| | | this.$nextTick(() => { |
| | | this.initMap(); |
| | | }) |
| | | return |
| | | } |
| | | } |
| | | }, |
| | | methods: { |
| | | initData(orderData = {}, monitoringData = {}, travelData = {},) { |
| | | initData(orderData = {}, monitoringData = {}, travelData = []) { |
| | | this.orderData = orderData |
| | | this.monitoringData = monitoringData |
| | | this.travelData = travelData |
| | | this.dialogVisible = true |
| | | this.initMap(); |
| | | }, |
| | | initMap() { |
| | | window._AMapSecurityConfig = { |
| | |
| | | .then((AMap) => { |
| | | this.map = new AMap.Map("mapContainer", { |
| | | center: [this.travelData[this.travelData.length / 2].longitude, this.travelData[this.travelData.length / 2].latitude], |
| | | zoom: 15, |
| | | zoom: 12, |
| | | }); |
| | | this.map.addControl(new AMap.ToolBar()); |
| | | let path = this.travelData.map(item => { |
| | | return new AMap.LngLat(item.longitude, item.latitude) |
| | | }) |
| | | const content = `<div class="custom-content-marker"> |
| | | <img src="${require("@/assets/logo.png")}"> |
| | | 起点 |
| | | </div>`; |
| | | const marker = new AMap.Marker({ |
| | | content: content, //自定义点标记覆盖物内容 |
| | | position: [this.travelData[0].longitude, this.travelData[0].latitude], //基点位置 |
| | | offset: new AMap.Pixel(-30, -15), //相对于基点的偏移位置 |
| | | }); |
| | | |
| | | const contentTwo = `<div class="custom-content-marker-two"> |
| | | 终点 |
| | | </div>`; |
| | | const marker = [ |
| | | new AMap.Marker({ |
| | | content: content, //自定义点标记覆盖物内容 |
| | | position: [this.travelData[0].longitude, this.travelData[0].latitude], //基点位置 |
| | | offset: new AMap.Pixel(-35, -25), //相对于基点的偏移位置 |
| | | }), |
| | | new AMap.Marker({ |
| | | content: contentTwo, //自定义点标记覆盖物内容 |
| | | position: [this.travelData[this.travelData.length - 1].longitude, this.travelData[this.travelData.length - 1].latitude], //基点位置 |
| | | offset: new AMap.Pixel(-35, -25), //相对于基点的偏移位置 |
| | | }) |
| | | ] |
| | | this.map.add(marker); |
| | | let polyline = new AMap.Polyline({ |
| | | path: path, |
| | | strokeWeight: 2, //线条宽度 |
| | | strokeWeight: 3, //线条宽度 |
| | | strokeColor: "red", //线条颜色 |
| | | lineJoin: "round", //折线拐点连接处样式 |
| | | }); |
| | | this.map.add(polyline); |
| | | // 强制刷新地图 |
| | | this.$nextTick(() => { |
| | | this.map.resize(); |
| | | }); |
| | | }) |
| | | .catch((e) => { |
| | | }); |
| | | }, |
| | | closeClick() { |
| | | this.dialogVisible = false |
| | | } |
| | | this.tabPosition = 'order' |
| | | this.orderData = {} |
| | | this.monitoringData = {} |
| | | this.travelData = [] |
| | | }, |
| | | }, |
| | | }; |
| | | </script> |
| | | <style> |
| | | .custom-content-marker { |
| | | width: 30px; |
| | | height: 30px; |
| | | width: 50px; |
| | | height: 50px; |
| | | background-color: blue; |
| | | color: #fff; |
| | | border-radius: 50%; |
| | | line-height: 50px; |
| | | text-align: center; |
| | | } |
| | | |
| | | .custom-content-marker-two { |
| | | width: 50px; |
| | | height: 50px; |
| | | background-color: orange; |
| | | color: #fff; |
| | | border-radius: 50%; |
| | | line-height: 50px; |
| | | text-align: center; |
| | | } |
| | | |
| | | .custom-content-marker img { |
| | |
| | | |
| | | #mapContainer { |
| | | width: 100%; |
| | | height: 300px; |
| | | height: 500px; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div> |
| | | <div v-loading="loading" element-loading-text="加载中..."> |
| | | <div class="form flex a-center j-between mt--23"> |
| | | <div class="form-left ml--30"> |
| | | <el-form :inline="true" :model="searchForm"> |
| | |
| | | <script> |
| | | import DetailModal from "./component/detailModal" |
| | | import { exportExcell } from '@/utils/utils' |
| | | import { getOrderList, getOrderInfo, getOrderMonitoring, getOrderTravel } from './service' |
| | | import { getOrderList, getOrderInfo, getOrderMonitoring, getOrderTravel, } from './service' |
| | | import moment from "moment/moment"; |
| | | |
| | | export default { |
| | |
| | | pageSize: 10 |
| | | }, |
| | | tableData: [], |
| | | loading: false, |
| | | }; |
| | | }, |
| | | computed: { |
| | |
| | | obj.orderDeliveryTimeEnd = moment(obj.orderDeliveryTime[1]).format('YYYY-MM-DD HH-mm-ss') |
| | | delete obj.orderDeliveryTime |
| | | } |
| | | exportExcell('投诉记录导出', obj, '/system/order/exportOrderList') |
| | | exportExcell('订单记录导出', obj, '/system/order/exportOrderList') |
| | | }, |
| | | handleSizeChange(e) { |
| | | this.searchForm.pageSize = e |
| | |
| | | }) |
| | | }, |
| | | showDetail(row) { |
| | | // Promise.all([getOrderInfo(row.id), getOrderMonitoring({ id: row.id }), getOrderTravel({ id: row.id })]).then(res => { |
| | | Promise.all([getOrderInfo(row.id), getOrderTravel({ id: row.id })]).then(res => { |
| | | this.$refs.detailModal.initData(res[0], {}, res[2]) |
| | | this.loading = true |
| | | Promise.all([getOrderInfo(row.id), getOrderMonitoring({ id: row.id }), getOrderTravel({ id: row.id })]).then(res => { |
| | | // Promise.all([getOrderInfo(row.id), getOrderTravel({ id: row.id })]).then(res => { |
| | | this.$refs.detailModal.initData(res[0], res[1], res[2]) |
| | | this.loading = false |
| | | }).catch(err => { |
| | | this.loading = false |
| | | }) |
| | | }, |
| | | } |
| | |
| | | width: 100% !important; |
| | | } |
| | | } |
| | | |
| | | ::v-deep .el-loading-mask { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | |
| | | .el-loading-spinner { |
| | | width: unset; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog :visible.sync="dialogVisible" @close="$emit('close')" :title="row.deptId ? '编辑人员' : '添加人员'" |
| | | width="30%"> |
| | | width="30%" :modal-append-to-body="false"> |
| | | <el-form ref="form" :model="form" :rules="rules" label-width="80px"> |
| | | <el-form-item label="姓名" prop="nickName"> |
| | | <el-input v-model="form.nickName" placeholder="请输入" style="width: 50%;"></el-input> |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog :visible.sync="dialogVisible" @close="$emit('close')" title="禁用人员" width="30%"> |
| | | <el-dialog :visible.sync="dialogVisible" @close="$emit('close')" title="禁用人员" width="30%" :modal-append-to-body="false"> |
| | | <el-form ref="form" :model="form" label-width="80px"> |
| | | <el-form-item label="姓名" prop="nickName"> |
| | | {{ form.nickName }} |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog :visible.sync="dialogVisible" @close="$emit('close')" title="重置密码" |
| | | width="30%"> |
| | | <el-dialog :visible.sync="dialogVisible" @close="$emit('close')" title="重置密码" width="30%" |
| | | :modal-append-to-body="false"> |
| | | <el-form ref="form" :model="form" :rules="rules" label-width="80px"> |
| | | <el-form-item label="姓名" prop="nickName"> |
| | | <el-input :disabled="true" v-model="form.nickName" placeholder="请输入" style="width: 50%;"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="登陆账号" prop="account"> |
| | | <el-input :disabled="true" v-model="form.account" placeholder="请输入" style="width: 50%;"></el-input> |
| | | <el-input :disabled="true" v-model="form.userName" placeholder="请输入" style="width: 50%;"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="新密码" prop="password"> |
| | | <el-input v-model="form.password" placeholder="请输入" style="width: 50%;"></el-input> |
| | |
| | | </template> |
| | | |
| | | <script> |
| | | import CryptoJS from 'crypto-js'; |
| | | export default { |
| | | props: { |
| | | dialogVisible: { |
| | |
| | | form: { status: true }, |
| | | rules: { |
| | | password: [{ required: true, message: '请新密码', trigger: 'blur' }], |
| | | confirmPassword: [{ required: true, message: '请确认密码', trigger: 'blur' },{ |
| | | confirmPassword: [{ required: true, message: '请确认密码', trigger: 'blur' }, { |
| | | validator: (rule, value, callback) => { |
| | | if (value !== this.form.password) { |
| | | callback(new Error('两次输入密码不一致!')); |
| | | } else { |
| | | callback(); |
| | | } |
| | | }}], |
| | | } |
| | | }], |
| | | } |
| | | }; |
| | | }, |
| | |
| | | this.form = { |
| | | userId: this.row.userId, |
| | | nickName: this.row.nickName, |
| | | account: this.row.userName, |
| | | userName: this.row.userName, |
| | | } |
| | | } |
| | | }, |
| | |
| | | this.form.orderNum = 0 |
| | | this.form.ancestors = 0 |
| | | this.form.parentId = 100 |
| | | this.form.password = CryptoJS.MD5(this.form.password).toString() |
| | | this.$emit('confirm', this.form) |
| | | } |
| | | }) |