hejianhao
2025-04-08 601a63bc96b163f35be024b560a0151903b8786a
Merge branch 'master' of http://120.76.84.145:10101/gitblit/r/H5/shehong-vehicle-supervision
4个文件已修改
481 ■■■■ 已修改文件
src/view/car-manage/detail.vue 276 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/view/car-manage/service.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/view/home/index.vue 196 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/view/home/service.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/view/car-manage/detail.vue
@@ -36,19 +36,19 @@
                <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>
@@ -66,24 +66,29 @@
                <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">
@@ -100,8 +105,9 @@
                            <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>
@@ -118,15 +124,85 @@
                </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="mapContainer"></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 },
@@ -137,11 +213,18 @@
            searchForm: {
                pageCurr: 1,
                pageSize: 10,
                total: 0
                total: 0,
                date: undefined,
            },
            detail:{},
            activeName: 'first',
            routeList:[],
            videoObj: {},
            drawer: false,
            showWarnDetail: false,
            info: {},
            activeInfo: {},
            map: null
        }
    },
    mounted() {
@@ -150,10 +233,61 @@
                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("mapContainer", {
                            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'){
@@ -162,7 +296,12 @@
                    pageSize: 10,
                    total: 0,
                }
                this.getList(this.detail.vehicleNumber)
                //销毁地图
                if (this.map) {
                    this.map.destroy();
                }
            }else{
                getCarTrack({...this.searchForm,vehicleNumber:this.detail.vehicleNumber}).then(res => {
                  this.routeList = res;
@@ -173,9 +312,31 @@
            }
        },
        search() {
            if (this.searchForm.date != undefined) {
                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();
                    }
                })
            }
         },
        reset() {
            this.searchForm = {
               pageCurr: 1,
               pageSize: 10,
               total: 0,
               date: undefined,
            }
        },
        getList(vehicleNumber) { 
            if(this.activeName == 'first'){
@@ -213,23 +374,50 @@
                    });
                    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({
src/view/car-manage/service.js
@@ -30,3 +30,8 @@
export const getCarTrack = (data) => {
    return axios.get(`/system/car/getCarTravel`, {params:data}) 
}
//获取车辆详情实时视频
export const getCarVideo = (data) => {
    return axios.get(`/system/car/getRealVideo/${data.id}`)
}
src/view/home/index.vue
@@ -154,8 +154,8 @@
              }}
            </div>
            <div class="info">
              {{ item.vehicleNumber  }} {{ item.warnType  }}
              {{ item.keepTime }} {{ item.startTime }}
              {{ item.vehicleNumber }} {{ item.warnType }} {{ item.keepTime }}
              {{ item.startTime }}
            </div>
          </div>
        </div>
@@ -205,7 +205,7 @@
  getCarWarnList,
  getWarnGroupCount,
  getWarnGroupCountTop10,
  getCarInfoById,
  getRealVideo,
} from "./service";
export default {
  data() {
@@ -251,6 +251,10 @@
      }
    },
  },
  filters: {
  },
  created() {
    window.toCarDetail = (record) => {
      this.toCarDetail(record);
@@ -436,16 +440,17 @@
    // 初始化地图
    initMap() {
      window._AMapSecurityConfig = {
        securityJsCode: "37ce61ae86efa5ad82b649a277f5097c",
        securityJsCode: this.$secretKey,
      };
      AMapLoader.load({
        key: "67968c82f27c7e2cb9f40c1a9aa3042b",
        key: this.$mapKey,
        version: "2.0",
        plugins: [
          "AMap.ToolBar",
          "AMap.AutoComplete",
          "AMap.Geocoder",
          "AMap.MarkerCluster",
          "AMap.Geocoder",
        ],
      })
        .then((AMap) => {
@@ -546,9 +551,21 @@
            this.infoWindow.open(this.map, e.target.getPosition());
            try {
              // 并行请求车辆信息和视频地址
              const [carInfoRes, videoRes] = await Promise.all([
                this.getCarInfo(item.id),
              // 使用高德地图API获取地址信息
              const geocoder = new this.AMap.Geocoder();
              const location = [Number(item.longitude), Number(item.latitude)];
              const [addressResult, videoRes] = await Promise.all([
                new Promise((resolve) => {
                  geocoder.getAddress(location, (status, result) => {
                    if (status === "complete" && result.regeocode) {
                      console.log("result", result,'status',status);
                      resolve(result.regeocode.formattedAddress);
                    } else {
                      resolve("未知地址");
                    }
                  });
                }),
                this.getVideoUrl(item.id),
              ]);
@@ -556,28 +573,13 @@
              this.infoWindow.setContent(
                this.listRender({
                  ...item,
                  ...carInfoRes.data,
                  videoUrl: videoRes.data.url,
                  drivingTime:this.formatterTime(item.drivingTime || 0) ,
                  location: addressResult,
                  videoUrl: videoRes.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();
              }
              this.initVideoPlayer(videoRes.url);
            } catch (error) {
              this.infoWindow.setContent(
                '<div style="padding: 20px;text-align: center;color: red;">获取车辆信息失败</div>'
@@ -590,50 +592,87 @@
        });
      }
    },
    // 获取车辆信息
    async getCarInfo(carId) {
      try {
        const res = await getCarInfoById({ id: carId });
        if (res) {
          return {
            data: {
              ...res,
              id: carId,
            },
          };
        }
        return {
          data: {
            id: carId,
          },
        };
      } catch (error) {
        return {
          data: {
            licensePlate: "",
            driver: "",
            location: "",
            coordinates: "",
            speed: "0km/h",
            drivingTime: "0小时0分钟",
          },
        };
      }
    },
    // 获取视频地址
    async getVideoUrl(carId) {
      // TODO: 替换为实际的API调用
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve({
            data: {
              url: "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4",
      try {
        const res = await getRealVideo({ id: carId });
        // 将RTSP流转换为FLV流
        const flvUrl = this.convertRtspToFlv(res.url);
        return {
          url: flvUrl
        };
      } catch (error) {
        console.error("获取视频地址失败", error);
        return {
          url: ""
        };
      }
            },
    // RTSP转FLV
    convertRtspToFlv(rtspUrl) {
      // 这里需要根据实际的流媒体服务器地址进行修改
      // 假设流媒体服务器地址为 http://your-media-server:8080
      const mediaServer = "http://101.206.211.65:18042";
      // 从RTSP URL中提取流标识
      const streamId = rtspUrl.split("/").pop();
      // 返回FLV流地址
      return `${mediaServer}/live/${streamId}.flv`;
    },
    // 初始化视频播放器
    initVideoPlayer(videoUrl) {
      // 先销毁之前的播放器
      if (this.flvPlayer) {
        this.flvPlayer.destroy();
        this.flvPlayer = null;
      }
      // 获取video元素
      const video = document.getElementById("monitoringCard");
      if (!video) {
        console.error("Video element not found");
        return;
      }
      // 检查flv.js是否支持
      if (flvjs.isSupported()) {
        try {
          this.flvPlayer = flvjs.createPlayer({
            type: "flv",
            isLive: true,
            cors: true,
            hasAudio: true,
            hasVideo: true,
            url: videoUrl,
            enableWorker: true,
            enableStashBuffer: false,
            seekType: "range",
          });
        }, 500);
          this.flvPlayer.attachMediaElement(video);
          this.flvPlayer.load();
          this.flvPlayer.play().catch(error => {
            console.error("视频播放失败:", error);
      });
        } catch (error) {
          console.error("创建播放器失败:", error);
        }
      } else {
        console.error("当前浏览器不支持flv.js");
      }
    },
    // 处理视频错误
    handleVideoError(event) {
      console.error("视频加载失败", event);
      if (this.flvPlayer) {
        this.flvPlayer.destroy();
        this.flvPlayer = null;
      }
      this.infoWindow.setContent(
        '<div style="padding: 20px;text-align: center;color: red;">视频加载失败,请稍后重试</div>'
      );
    },
    listRender(record) {
@@ -643,9 +682,10 @@
            crossorigin="anonymous" 
            style="width: 460px; height: 330px; border-radius: 9px" 
            id="monitoringCard"
            :controls="false"
            autoplay
            src="${record.videoUrl}"
            playsinline
            preload="auto"
            @error="handleVideoError"
            width="620">
          </video>
          <div style="position: absolute; right: 11px; top: 10px">
@@ -666,21 +706,21 @@
            record.vehicleNumber || ""
          }</div>
          <div style="font-weight: 500; font-size: 18px;color: rgba(0, 0, 0, 0.85);line-height: 25px;">驾驶员:${
            record.driverName||''
            record.driverName || ""
          }</div>
        </div>
        <div style="display: flex; justify-content: space-between">
          <div style="font-weight: 500; font-size: 14px; color: rgba(0, 0, 0, 0.65);line-height: 26px;">位置:${
          <div style="font-weight: 500; font-size: 14px; color: rgba(0, 0, 0, 0.65);line-height: 26px;width: 200px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;" title="${record.location}">位置:${
            record.location
          }</div>
          <div style="font-weight: 500;font-size: 14px; color: rgba(0, 0, 0, 0.65);line-height: 26px;">经纬度:${
            record.longitude +',' + record.latitude
            record.longitude + "," + record.latitude
          }</div>
        </div>
        <div style="display: flex; justify-content: space-between">
          <div style="font-weight: 500;font-size: 14px; color: rgba(0, 0, 0, 0.65);line-height: 26px;">当前时速:${
            record.speed
          }</div>
            record.speed || ""
          }${record.speed && "km/h"}</div>
          <div style="font-weight: 500;font-size: 14px; color: rgba(0, 0, 0, 0.65);line-height: 26px;">驾驶时长:${
            record.drivingTime
          }</div>
@@ -693,6 +733,16 @@
        </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"));
src/view/home/service.js
@@ -27,3 +27,7 @@
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)
}