wanglizhong
2025-05-10 34044707d97b6396325c7fc4cac8d889fae96f85
fix:修复map加载显示问题
3个文件已添加
7个文件已修改
1 文件已复制
1 文件已重命名
1052 ■■■■■ 已修改文件
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleGpsController.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application-dev.yml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application-prod.yml 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application-test.yml 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application.yml 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/GpsSyncTask.java 88 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/VehicleSyncTask.java 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-quartz/src/main/resources/sql/vehicle_gps_job.sql 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleGps.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/VehicleGpsMapper.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/system/gps/map.vue 217 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/system/gps/mapNeed.vue 386 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleGpsController.java
@@ -38,6 +38,9 @@
    @GetMapping("/list")
    public TableDataInfo list(VehicleGps vehicleGps) {
        startPage();
        // 设置按时间倒序排序
        vehicleGps.setOrderByColumn("collect_time");
        vehicleGps.setIsAsc("desc");
        List<VehicleGps> list = vehicleGpsService.selectVehicleGpsList(vehicleGps);
        return getDataTable(list);
    }
@@ -45,6 +48,9 @@
    @GetMapping("/anonymousList")
    public TableDataInfo anonymousList(VehicleGps vehicleGps) {
        startPage();
        // 设置按时间倒序排序
        vehicleGps.setOrderByColumn("collect_time");
        vehicleGps.setIsAsc("desc");
        List<VehicleGps> list = vehicleGpsService.selectVehicleGpsList(vehicleGps);
        return getDataTable(list);
    }
@@ -56,6 +62,9 @@
    @Log(title = "车辆GPS坐标", businessType = BusinessType.EXPORT)
    @GetMapping("/export")
    public AjaxResult export(VehicleGps vehicleGps) {
        // 设置按时间倒序排序
        vehicleGps.setOrderByColumn("collect_time");
        vehicleGps.setIsAsc("desc");
        List<VehicleGps> list = vehicleGpsService.selectVehicleGpsList(vehicleGps);
        ExcelUtil<VehicleGps> util = new ExcelUtil<VehicleGps>(VehicleGps.class);
        return util.exportExcel(list, "车辆GPS坐标数据");
ruoyi-admin/src/main/resources/application-dev.yml
copy from ruoyi-admin/src/main/resources/application-druid.yml copy to ruoyi-admin/src/main/resources/application-dev.yml
File was copied from ruoyi-admin/src/main/resources/application-druid.yml
@@ -66,4 +66,24 @@
                    merge-sql: true
                wall:
                    config:
                        multi-statement-allow: true
                        multi-statement-allow: true
    # 定时任务配置
    quartz:
      # 是否启用定时任务
      enabled: false
      # 定时任务线程池配置
      properties:
        org:
          quartz:
            threadPool:
              threadCount: 5
              threadPriority: 5
              threadsInheritContextClassLoaderOfInitializingThread: true
            jobStore:
              class: org.quartz.simpl.RAMJobStore
            scheduler:
              instanceName: clusteredScheduler
              instanceId: AUTO
# 民航接口地址
min:
  apiUrl: http://120.25.98.119:8084/v1/   #测试环境:localhost:8011
ruoyi-admin/src/main/resources/application-prod.yml
New file
@@ -0,0 +1,163 @@
# 生产环境配置
server:
  # 服务器的HTTP端口
  port: 8080
  servlet:
    # 应用的访问路径
    context-path: /
  tomcat:
    # tomcat的URI编码
    uri-encoding: UTF-8
    # 连接数满后的排队数,默认值100
    accept-count: 1000
    threads:
      # tomcat最大线程数,默认为200
      max: 800
      # Tomcat启动初始化的线程数,默认值25
      min-spare: 30
# Spring配置
spring:
  # 数据源配置
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    druid:
      # 主库数据源
      master:
        url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: password
      # 从库数据源
      slave:
        # 从数据源开关/默认关闭
        enabled: false
        url:
        username:
        password:
      # 初始连接数
      initialSize: 5
      # 最小连接池数量
      minIdle: 10
      # 最大连接池数量
      maxActive: 20
      # 配置获取连接等待超时的时间
      maxWait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      minEvictableIdleTimeMillis: 300000
      # 配置一个连接在池中最大生存的时间,单位是毫秒
      maxEvictableIdleTimeMillis: 900000
      # 配置检测连接是否有效
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      webStatFilter:
        # 开启stat拦截
        enabled: true
      statViewServlet:
        # 开启druid监控
        enabled: true
        # 访问路径为/druid/*
        url-pattern: /druid/*
        # 是否允许清空统计数据
        reset-enable: false
        # 设置访问的用户名
        login-username: admin
        # 设置访问的密码
        login-password: 123456
      filter:
        stat:
          # 开启慢sql记录
          slow-sql-enabled: true
          # 慢sql时间
          log-slow-sql: true
          # 合并sql
          merge-sql: true
        wall:
          config:
            # 不允许删除表
            drop-table-allow: false
            # 不允许删除数据
            delete-allow: false
            # 不允许删除数据库
            delete-allow: false
            # 不允许删除数据库
            drop-table-allow: false
  # 文件上传 配置
  servlet:
    multipart:
      # 单个文件大小
      max-file-size: 10MB
      # 设置总上传的文件大小
      max-request-size: 20MB
  # 服务模块
  devtools:
    restart:
      # 热部署开关
      enabled: false
# 定时任务配置
quartz:
  # 是否启用定时任务
  enabled: true
  # 定时任务线程池配置
  properties:
    org:
      quartz:
        threadPool:
          threadCount: 5
          threadPriority: 5
          threadsInheritContextClassLoaderOfInitializingThread: true
        jobStore:
          class: org.quartz.simpl.RAMJobStore
        scheduler:
          instanceName: clusteredScheduler
          instanceId: AUTO
# MyBatis Plus配置
mybatis-plus:
  # 搜索指定包别名
  typeAliasesPackage: com.ruoyi.**.domain
  # 配置mapper的扫描,找到所有的mapper.xml映射文件
  mapperLocations: classpath*:mapper/**/*Mapper.xml
  # 加载全局的配置文件
  configLocation: classpath:mybatis/mybatis-config.xml
# PageHelper分页插件
pagehelper:
  pagehelper:
    # 分页参数合理化
    reasonable: true
    # 支持通过Mapper接口参数来传递分页参数
    supportMethodsArguments: true
    # 分页插件会自动检测当前的数据库链接
    helperDialect: mysql
# Swagger配置
swagger:
  # 是否开启swagger
  enabled: false
  # 请求前缀
  pathMapping: /
# 防止XSS攻击
xss:
  # 过滤开关
  enabled: true
  # 排除链接(多个用逗号分隔)
  excludes: /system/notice
  # 匹配链接
  urlPatterns: /system/*,/monitor/*,/tool/*
# 日志配置
logging:
  level:
    com.ruoyi: info
    org.springframework: warn
# 第三方接口配置
min:
  apiUrl: http://localhost:8080
ruoyi-admin/src/main/resources/application-test.yml
File was renamed from ruoyi-admin/src/main/resources/application-druid.yml
@@ -6,13 +6,13 @@
        druid:
            # 主库数据源
            master:
                url: jdbc:mysql://120.25.98.119:3307/ruoyi?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                url: jdbc:mysql://localhost:3307/ruoyi?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                username: root
                password: abcd1234
            # 从库数据源
            # SQL Server数据源
            sqlserver:
                url: jdbc:sqlserver://120.25.98.119:1432;databaseName=came
                url: jdbc:sqlserver://127.0.0.1:1432;databaseName=came
                username: camesa
                password: camesa
                driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
@@ -66,4 +66,10 @@
                    merge-sql: true
                wall:
                    config:
                        multi-statement-allow: true
                        multi-statement-allow: true
    # 定时任务配置
    quartz:
      enabled: false
# 民航接口地址
min:
  apiUrl: http://120.25.98.119:8084/v1/   #测试环境:localhost:8011
ruoyi-admin/src/main/resources/application.yml
@@ -3,9 +3,11 @@
  # 名称
  name: RuoYi
  # 版本
  version: 3.8.9
  version: ${revision}
  # 版权年份
  copyrightYear: 2025
  # 实例演示开关
  demoEnabled: true
  # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
  profile: D:/ruoyi/uploadPath
  # 获取ip地址开关
@@ -53,7 +55,8 @@
    # 国际化资源文件路径
    basename: i18n/messages
  profiles:
    active: druid
    # 环境 dev|test|prod
    active: dev
  # 文件上传
  servlet:
    multipart:
@@ -88,6 +91,7 @@
        max-active: 8
        # #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms
# token配置
token:
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/GpsSyncTask.java
@@ -4,15 +4,11 @@
import java.util.Date;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.ruoyi.system.domain.GpsDevice;
import com.ruoyi.system.domain.GpsDeviceListResponse;
import com.ruoyi.system.domain.GpsGroup;
import com.ruoyi.system.domain.GpsLastPosition;
import com.ruoyi.system.domain.GpsLastPositionRequest;
import com.ruoyi.system.domain.GpsLastPositionResponse;
@@ -40,21 +36,16 @@
    private IVehicleGpsService vehicleGpsService;
    /**
     * 同步设备列表和GPS位置
     * 同步GPS位置
     */
    public void syncGpsData() {
        try {
            log.info("开始同步GPS数据...");
            // 1. 获取设备列表,这会自动更新车辆信息中的设备ID
            GpsDeviceListResponse response = gpsCollectService.getDeviceList();
            // 更新车辆设备ID
            updateVehicleDeviceIds(response);
            // 2. 获取所有车辆信息
            // 1. 获取所有车辆信息
            List<VehicleInfo> vehicleList = vehicleInfoService.selectVehicleInfoList(new VehicleInfo());
            //在这里获得所有车辆的GPS最后位置
            // 2. 获取所有车辆的GPS最后位置
            GpsLastPositionResponse gpsLastPositionResponse = gpsCollectService.getLastPosition(new GpsLastPositionRequest());
            // 3. 遍历车辆列表,获取每个车辆的GPS位置
@@ -62,11 +53,11 @@
                if (vehicle.getDeviceId() != null && !vehicle.getDeviceId().isEmpty()) {
                    try {
                        // 获取车辆的最后位置
                        gpsLastPositionResponse.getRecords().stream().filter(e->e.getDeviceid().equals(vehicle.getDeviceId())).forEach(record -> {
                        gpsLastPositionResponse.getRecords().stream()
                            .filter(e -> e.getDeviceid().equals(vehicle.getDeviceId()))
                            .forEach(record -> {
                                updateVehicleGpsPositions(vehicle, record);
                        });
                            });
                    } catch (Exception e) {
                        log.error("获取车辆[{}]GPS位置失败: {}", vehicle.getVehicleNo(), e.getMessage());
                    }
@@ -77,57 +68,6 @@
        } catch (Exception e) {
            log.error("GPS数据同步失败: {}", e.getMessage());
        }
    }
    /**
     * 更新车辆设备ID
     */
    private void updateVehicleDeviceIds(GpsDeviceListResponse response) {
        if (response.getStatus() != 0 || response.getGroups() == null) {
            return;
        }
        for (GpsGroup group : response.getGroups()) {
            for (GpsDevice device : group.getDevices()) {
                String deviceName = device.getDevicename();
                String remark = device.getRemark();
                String deviceId = device.getDeviceid();
                if (StringUtils.isNotEmpty(deviceName) || StringUtils.isNotEmpty(remark)) {
                    String plateNumber = extractPlateNumber(deviceName, remark);
                    if (StringUtils.isNotEmpty(plateNumber)) {
                        VehicleInfo vehicleInfo = vehicleInfoService.selectVehicleInfoByPlateNumber(plateNumber);
                        if (vehicleInfo != null) {
                            vehicleInfo.setDeviceId(deviceId);
                            //获得数据字典中的平台编码
                            vehicleInfo.setPlatformCode("GPS51");
                            vehicleInfoService.updateVehicleInfo(vehicleInfo);
                        } else {
                            VehicleInfo newVehicle = new VehicleInfo();
                            newVehicle.setVehicleNo(plateNumber);
                            newVehicle.setDeviceId(deviceId);
                            newVehicle.setStatus("0");
                            newVehicle.setPlatformCode("GPS51");
                            vehicleInfoService.insertVehicleInfo(newVehicle);
                        }
                    }
                }
            }
        }
    }
    /**
     * 从设备名称和备注中提取车牌号
     */
    private String extractPlateNumber(String deviceName, String remark) {
        if (StringUtils.isNotEmpty(deviceName)) {
            return deviceName;
        }
        if (StringUtils.isNotEmpty(remark)) {
            return remark;
        }
        return null;
    }
    /**
@@ -144,19 +84,15 @@
            gps.setSpeed(position.getSpeed());
            gps.setDirection(Double.valueOf(position.getCourse()));
            
            //devicetime 这个是一个linux时间戳,要转换成北京时间,再转成yyyy-MM-dd HH:mm:ss格式
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            //getArrivedtime 这个是一个linux时间戳,要转换成北京时间,再转成yyyy-MM-dd HH:mm:ss格式
            // 处理到达时间
            long arrivedTime = position.getArrivedtime();
            Date arrivedDate;
            // 检查时间戳是否有效(大于0)
            if (arrivedTime > 0) {
                arrivedDate = new Date(arrivedTime);
                // 减去8小时
                arrivedDate.setTime(arrivedDate.getTime() - 8 * 60 * 60 * 1000);
            } else {
                // 时间戳无效,使用当前时间
                arrivedDate = new Date();
            }
            gps.setPlatformProcessTime(sdf.format(arrivedDate));
@@ -164,21 +100,16 @@
            // 设备上报时间
            long deviceTime = position.getDevicetime();
            Date date;
            // 检查时间戳是否有效(大于0)
            if (deviceTime > 0) {
                date = new Date(deviceTime);
                // 减去8小时
                date.setTime(date.getTime() - 8 * 60 * 60 * 1000);
            } else {
                // 时间戳无效,使用当前时间
                date = arrivedDate;
            }
            gps.setDeviceReportTime(sdf.format(date));
            
            // 采集时间(使用设备上报时间)
            gps.setCollectTime(sdf.format(new Date( )));
            gps.setCollectTime(sdf.format(new Date()));
            // 保存GPS位置信息
            vehicleGpsService.insertVehicleGps(gps);
@@ -190,5 +121,4 @@
            log.error("更新车辆[{}]GPS位置失败: {}", vehicle.getVehicleNo(), e.getMessage());
        }
    }
}
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/VehicleSyncTask.java
New file
@@ -0,0 +1,96 @@
package com.ruoyi.quartz.task;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.ruoyi.system.domain.GpsDevice;
import com.ruoyi.system.domain.GpsDeviceListResponse;
import com.ruoyi.system.domain.GpsGroup;
import com.ruoyi.system.domain.VehicleInfo;
import com.ruoyi.system.service.IGpsCollectService;
import com.ruoyi.system.service.IVehicleInfoService;
/**
 * 车辆同步定时任务
 */
@Component("vehicleSyncTask")
public class VehicleSyncTask {
    private static final Logger log = LoggerFactory.getLogger(VehicleSyncTask.class);
    @Autowired
    private IGpsCollectService gpsCollectService;
    @Autowired
    private IVehicleInfoService vehicleInfoService;
    /**
     * 同步车辆信息
     */
    public void syncVehicleInfo() {
        try {
            log.info("开始同步车辆信息...");
            // 获取设备列表,这会自动更新车辆信息中的设备ID
            GpsDeviceListResponse response = gpsCollectService.getDeviceList();
            // 更新车辆设备ID
            updateVehicleDeviceIds(response);
            log.info("车辆信息同步完成");
        } catch (Exception e) {
            log.error("车辆信息同步失败: {}", e.getMessage());
        }
    }
    /**
     * 更新车辆设备ID
     */
    private void updateVehicleDeviceIds(GpsDeviceListResponse response) {
        if (response.getStatus() != 0 || response.getGroups() == null) {
            return;
        }
        for (GpsGroup group : response.getGroups()) {
            for (GpsDevice device : group.getDevices()) {
                String deviceName = device.getDevicename();
                String remark = device.getRemark();
                String deviceId = device.getDeviceid();
                if (StringUtils.isNotEmpty(deviceName) || StringUtils.isNotEmpty(remark)) {
                    String plateNumber = extractPlateNumber(deviceName, remark);
                    if (StringUtils.isNotEmpty(plateNumber)) {
                        VehicleInfo vehicleInfo = vehicleInfoService.selectVehicleInfoByPlateNumber(plateNumber);
                        if (vehicleInfo != null) {
                            vehicleInfo.setDeviceId(deviceId);
                            vehicleInfo.setPlatformCode("GPS51");
                            vehicleInfoService.updateVehicleInfo(vehicleInfo);
                        } else {
                            VehicleInfo newVehicle = new VehicleInfo();
                            newVehicle.setVehicleNo(plateNumber);
                            newVehicle.setDeviceId(deviceId);
                            newVehicle.setStatus("0");
                            newVehicle.setPlatformCode("GPS51");
                            vehicleInfoService.insertVehicleInfo(newVehicle);
                        }
                    }
                }
            }
        }
    }
    /**
     * 从设备名称和备注中提取车牌号
     */
    private String extractPlateNumber(String deviceName, String remark) {
        if (StringUtils.isNotEmpty(deviceName)) {
            return deviceName;
        }
        if (StringUtils.isNotEmpty(remark)) {
            return remark;
        }
        return null;
    }
}
ruoyi-quartz/src/main/resources/sql/vehicle_gps_job.sql
New file
@@ -0,0 +1,7 @@
-- 车辆同步定时任务
INSERT INTO sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, update_by, update_time, remark)
VALUES ('车辆同步任务', 'DEFAULT', 'vehicleSyncTask.syncVehicleInfo()', '0 0 */1 * * ?', '3', '1', '1', 'admin', sysdate(), 'admin', sysdate(), '每小时同步一次车辆信息');
-- GPS同步定时任务
INSERT INTO sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, update_by, update_time, remark)
VALUES ('GPS同步任务', 'DEFAULT', 'gpsSyncTask.syncGpsData()', '0 */5 * * * ?', '3', '1', '1', 'admin', sysdate(), 'admin', sysdate(), '每5分钟同步一次GPS位置信息');
ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleGps.java
@@ -64,6 +64,12 @@
    /** 结束时间 */
    private String endTime;
    /** 排序列 */
    private String orderByColumn;
    /** 排序的方向desc或者asc */
    private String isAsc;
    public void setGpsId(Long gpsId) {
        this.gpsId = gpsId;
    }
@@ -176,6 +182,22 @@
        this.endTime = endTime;
    }
    public String getOrderByColumn() {
        return orderByColumn;
    }
    public void setOrderByColumn(String orderByColumn) {
        this.orderByColumn = orderByColumn;
    }
    public String getIsAsc() {
        return isAsc;
    }
    public void setIsAsc(String isAsc) {
        this.isAsc = isAsc;
    }
    @Override
    public String toString() {
        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
ruoyi-system/src/main/resources/mapper/system/VehicleGpsMapper.xml
@@ -30,16 +30,20 @@
    <select id="selectVehicleGpsList" parameterType="VehicleGps" resultMap="VehicleGpsResult">
        <include refid="selectVehicleGpsVo"/>
        <where>  
            <if test="vehicleId != null "> and g.vehicle_id = #{vehicleId}</if>
            <if test="vehicleNo != null and vehicleNo != ''"> and v.vehicle_no like concat('%', #{vehicleNo}, '%')</if>
            <if test="longitude != null "> and g.longitude = #{longitude}</if>
            <if test="latitude != null "> and g.latitude = #{latitude}</if>
            <if test="altitude != null "> and g.altitude = #{altitude}</if>
            <if test="speed != null "> and g.speed = #{speed}</if>
            <if test="direction != null "> and g.direction = #{direction}</if>
            <if test="beginTime != null and beginTime != ''"> and g.collect_time &gt;= #{beginTime}</if>
            <if test="endTime != null and endTime != ''"> and g.collect_time &lt;= #{endTime}</if>
            <if test="vehicleNo != null  and vehicleNo != ''"> and vehicle_no = #{vehicleNo}</if>
            <if test="longitude != null "> and longitude = #{longitude}</if>
            <if test="latitude != null "> and latitude = #{latitude}</if>
            <if test="speed != null "> and speed = #{speed}</if>
            <if test="direction != null "> and direction = #{direction}</if>
            <if test="collectTime != null "> and collect_time = #{collectTime}</if>
            <if test="beginTime != null and beginTime != ''"><!-- 开始时间检索 -->
                AND date_format(collect_time,'%y%m%d') &gt;= date_format(#{beginTime},'%y%m%d')
            </if>
            <if test="endTime != null and endTime != ''"><!-- 结束时间检索 -->
                AND date_format(collect_time,'%y%m%d') &lt;= date_format(#{endTime},'%y%m%d')
            </if>
        </where>
        order by collect_time desc
    </select>
    
    <select id="selectVehicleGpsById" parameterType="Long" resultMap="VehicleGpsResult">
ruoyi-ui/src/views/system/gps/map.vue
@@ -26,6 +26,8 @@
          range-separator="-"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          :default-time="['00:00:00', '23:59:59']"
          @change="handleDateRangeChange"
        ></el-date-picker>
      </el-form-item>
      <el-form-item>
@@ -56,7 +58,9 @@
          >
            <el-table-column label="时间" align="center" prop="collectTime" />
            <el-table-column label="速度(km/h)" align="center" prop="speed" />
            <el-table-column label="经度" align="center" prop="longitude" />
            <el-table-column label="纬度" align="center" prop="latitude" />
            <el-table-column label="方向(°)" align="center" prop="direction" />
          </el-table>
        </el-card>
      </el-col>
@@ -127,11 +131,27 @@
      // 查询参数
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        pageSize: 1000,
        deviceId: null,
        appId: null,
        sign: null,
        timestamp: null
        timestamp: null,
        vehicleNo: null,
        beginTime: null,
        endTime: null
      },
      // 表单参数
      form: {
        vehicleNo: null
      },
      // 表单校验
      rules: {
        vehicleNo: [
          { required: true, message: "车牌号不能为空", trigger: "blur" }
        ],
        dateRange: [
          { required: true, message: "请选择时间范围", trigger: "change" }
        ]
      },
      // 地图对象
      map: null,
@@ -158,7 +178,25 @@
    const query = this.$route.query;
    if (query.vehicleNo) {
      this.queryParams.vehicleNo = query.vehicleNo;
    } else {
      this.$message.error('缺少车牌号参数');
      return;
    }
    // 检查时间参数
    if (query.beginTime && query.endTime) {
      // 格式化时间
      this.dateRange = [
        this.formatDateTime(query.beginTime),
        this.formatDateTime(query.endTime)
      ];
      this.queryParams.beginTime = this.dateRange[0];
      this.queryParams.endTime = this.dateRange[1];
    } else {
      this.$message.error('缺少时间范围参数');
      return;
    }
    // 设置认证参数
    if (query.appId) {
      this.queryParams.appId = query.appId;
@@ -170,26 +208,14 @@
      this.queryParams.timestamp = query.timestamp;
    }
    
    // 设置默认时间范围
    const today = new Date();
    const startTime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0);
    const endTime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 23, 59, 59);
    this.dateRange = [this.parseTime(startTime), this.parseTime(endTime)];
    // 如果URL中有时间参数,则使用URL中的时间
    if (query.beginTime && query.endTime) {
      this.dateRange = [query.beginTime, query.endTime];
    }
    this.getList();
  },
  mounted() {
    // 动态加载百度地图API
    this.loadBMapScript().then(() => {
      this.initMap();
      window.initMapFlag=true;
      if(window.loadGpsList){
        this.drawTrack()
    this.initMap().then(() => {
      window.initMapFlag = true;
      if (window.loadGpsList) {
        this.drawTrack();
      }
    });
  },
@@ -201,31 +227,21 @@
          resolve(window.BMap);
          return;
        }
        window.initBMap = () => {
          console.log("百度地图API加载成功");
          resolve(window.BMap);
        };
        const script = document.createElement("script");
        script.type = "text/javascript";
        script.src =
          "https://api.map.baidu.com/api?v=3.0&ak=n5z5pKfAnaP3fYMR4RJOAQsR1wQ2avAn&callback=initBMap";
        script.onerror = reject;
        document.head.appendChild(script);
        window.initBMap = () => {
          // 加载坐标转换库
          const convertorScript = document.createElement("script");
          convertorScript.type = "text/javascript";
          convertorScript.src =
            "https://api.map.baidu.com/getscript?v=3.0&ak=n5z5pKfAnaP3fYMR4RJOAQsR1wQ2avAn&services=&t=20230101100000";
          convertorScript.onload = () => {
            // 加载坐标转换工具
            const toolsScript = document.createElement("script");
            toolsScript.type = "text/javascript";
            toolsScript.src =
              "https://api.map.baidu.com/library/Convertor/1.4/src/Convertor_min.js";
            toolsScript.onload = () => {
              resolve(window.BMap);
            };
            document.head.appendChild(toolsScript);
          };
          document.head.appendChild(convertorScript);
        script.onerror = (error) => {
          console.error("百度地图API加载失败", error);
          reject(error);
        };
        document.head.appendChild(script);
      });
    },
    /** 格式化时间 */
@@ -238,6 +254,15 @@
      const seconds = String(time.getSeconds()).padStart(2, '0');
      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
    },
    /** 处理时间格式 */
    formatDateTime(dateTimeStr) {
      if (!dateTimeStr) return '';
      // 如果时间字符串不包含秒,添加秒
      if (dateTimeStr.length === 16) { // yyyy-MM-dd HH:mm
        return dateTimeStr + ':00';
      }
      return dateTimeStr;
    },
    /** 查询GPS列表 */
    getList() {
      this.loading = true;      
@@ -246,13 +271,17 @@
        ...this.queryParams
      };
      // 如果没有选择时间范围,则默认使用当天
      // 如果没有选择时间范围,则使用URL中的时间
      if (!this.dateRange || this.dateRange.length === 0) {
        const today = new Date();
        const startTime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0);
        const endTime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 23, 59, 59);
        params.beginTime = this.parseTime(startTime);
        params.endTime = this.parseTime(endTime);
        const query = this.$route.query;
        if (query.beginTime && query.endTime) {
          params.beginTime = query.beginTime;
          params.endTime = query.endTime;
        } else {
          this.$message.error('请选择时间范围');
          this.loading = false;
          return;
        }
      } else {
        params.beginTime = this.dateRange[0];
        params.endTime = this.dateRange[1];
@@ -268,21 +297,45 @@
        }
      });
    },
    async translatePoints(points) {
      // 将WGS84坐标转换为百度坐标
      var translatePoints = [];
      return new Promise((resolve, reject) => {
    /** 坐标转换方法 */
    translatePoint(point) {
      return new Promise((resolve) => {
        // 使用百度地图API内置的坐标转换
        const convertor = new BMap.Convertor();
        convertor.translate(points, 1, 5, (data) => {
        const pointArr = [];
        pointArr.push(point);
        convertor.translate(pointArr, 1, 5, (data) => {
          if (data.status === 0) {
            translatePoints = data.points;
            resolve(translatePoints);
            resolve(data.points[0]);
          } else {
            // 如果转换失败,返回原始坐标
            resolve(point);
          }
        });
      });
    },
    /** 批量坐标转换 */
    async translatePoints(points) {
      const translatePoints = [];
      for (const point of points) {
        const translatedPoint = await this.translatePoint(point);
        translatePoints.push(translatedPoint);
      }
      return translatePoints;
    },
    /** 搜索按钮操作 */
    handleQuery() {
      if (!this.queryParams.vehicleNo) {
        this.$message.error('请输入车牌号');
        return;
      }
      if (!this.dateRange || this.dateRange.length !== 2) {
        this.$message.error('请选择时间范围');
        return;
      }
      // 更新查询参数
      this.queryParams.beginTime = this.dateRange[0];
      this.queryParams.endTime = this.dateRange[1];
      this.getList();
    },
    /** 重置按钮操作 */
@@ -293,17 +346,28 @@
      const endTime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 23, 59, 59);
      this.dateRange = [this.parseTime(startTime), this.parseTime(endTime)];
      this.resetForm("queryForm");
      // 保留车牌号
      this.queryParams.vehicleNo = this.$route.query.vehicleNo;
      // 更新查询参数
      this.queryParams.beginTime = this.dateRange[0];
      this.queryParams.endTime = this.dateRange[1];
      this.handleQuery();
    },
    /** 初始化地图 */
    initMap() {
      // 创建地图实例
      this.map = new BMap.Map("mapContainer");
      // 设置地图中心点和缩放级别
      this.map.centerAndZoom(new BMap.Point(116.404, 39.915), 11);
      // 启用滚轮放大缩小
      this.map.enableScrollWheelZoom();
      console.log("initMap 初始化地图")
    async initMap() {
      try {
        await this.loadBMapScript();
        // 创建地图实例
        this.map = new BMap.Map("mapContainer");
        // 设置地图中心点和缩放级别
        this.map.centerAndZoom(new BMap.Point(116.404, 39.915), 11);
        // 启用滚轮放大缩小
        this.map.enableScrollWheelZoom();
        console.log("地图初始化成功");
      } catch (error) {
        console.error("地图初始化失败", error);
        this.$message.error("地图加载失败,请刷新页面重试");
      }
    },
     /** 计算两点之间的距离(米) */
     getDistance(point1, point2) {
@@ -346,7 +410,7 @@
      );
      const currentSegment = this.gpsList.slice(startIndex, endIndex);
      //先获得所有坐标数组
      // 获取所有坐标数组
      const originPoints = currentSegment.map(
        (item) => new BMap.Point(item.longitude, item.latitude)
      );
@@ -354,10 +418,11 @@
      this.gpsList.sort((a, b) => {
        return new Date(b.collectTime) - new Date(a.collectTime);
      }).forEach(item => {
       item.speed=item.speed/1000;
        item.speed = item.speed/1000;
      });
      //批量转换坐标
      var translatePoints = await this.translatePoints(originPoints);
      // 批量转换坐标
      const translatePoints = await this.translatePoints(originPoints);
      // 创建轨迹点数组
      const points = translatePoints;
@@ -601,6 +666,30 @@
      }
      this.isPlaying = false;
    },
    /** 处理时间范围变化 */
    handleDateRangeChange(val) {
      if (val && val.length === 2) {
        // 格式化时间
        this.dateRange = [
          this.formatDateTime(val[0]),
          this.formatDateTime(val[1])
        ];
        // 检查结束时间是否包含具体时间
        const endDate = new Date(this.dateRange[1]);
        const hasSpecificTime = endDate.getHours() !== 0 || endDate.getMinutes() !== 0;
        if (!hasSpecificTime) {
          // 如果没有具体时间,设置为23:59:59
          endDate.setHours(23, 59, 59);
          this.dateRange[1] = this.parseTime(endDate);
        }
        // 更新查询参数
        this.queryParams.beginTime = this.dateRange[0];
        this.queryParams.endTime = this.dateRange[1];
      }
    },
  },
  beforeDestroy() {
    this.stopPlayback();
ruoyi-ui/src/views/system/gps/mapNeed.vue
@@ -127,7 +127,7 @@
      // 查询参数
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        pageSize: 1000,
        deviceId: null,
        appId: null,
        sign: null,
@@ -175,12 +175,16 @@
  },
  mounted() {
    // 动态加载百度地图API
    this.loadBMapScript().then(() => {
      this.initMap();
      window.initMapFlag=true;
      if(window.loadGpsList){
        this.drawTrack()
    this.initMap().then((success) => {
      if (success) {
        window.initMapFlag = true;
        if (window.loadGpsList) {
          this.drawTrack();
        }
      }
    }).catch(error => {
      console.error("地图初始化失败", error);
      this.$message.error("地图加载失败,请刷新页面重试");
    });
  },
  methods: {
@@ -191,31 +195,28 @@
          resolve(window.BMap);
          return;
        }
        window.initBMap = () => {
          console.log("百度地图API加载成功");
          // 确保BMap对象完全加载
          setTimeout(() => {
            if (window.BMap && window.BMap.Map) {
              resolve(window.BMap);
            } else {
              reject(new Error("百度地图API加载不完整"));
            }
          }, 100);
        };
        const script = document.createElement("script");
        script.type = "text/javascript";
        script.src =
          "https://api.map.baidu.com/api?v=3.0&ak=n5z5pKfAnaP3fYMR4RJOAQsR1wQ2avAn&callback=initBMap";
        script.onerror = reject;
        document.head.appendChild(script);
        window.initBMap = () => {
          // 加载坐标转换库
          const convertorScript = document.createElement("script");
          convertorScript.type = "text/javascript";
          convertorScript.src =
            "https://api.map.baidu.com/getscript?v=3.0&ak=n5z5pKfAnaP3fYMR4RJOAQsR1wQ2avAn&services=&t=20230101100000";
          convertorScript.onload = () => {
            // 加载坐标转换工具
            const toolsScript = document.createElement("script");
            toolsScript.type = "text/javascript";
            toolsScript.src =
              "https://api.map.baidu.com/library/Convertor/1.4/src/Convertor_min.js";
            toolsScript.onload = () => {
              resolve(window.BMap);
            };
            document.head.appendChild(toolsScript);
          };
          document.head.appendChild(convertorScript);
        script.onerror = (error) => {
          console.error("百度地图API加载失败", error);
          reject(error);
        };
        document.head.appendChild(script);
      });
    },
    /** 查询GPS列表 */
@@ -258,18 +259,55 @@
      const seconds = String(time.getSeconds()).padStart(2, '0');
      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
    },
    async translatePoints(points) {
      // 将WGS84坐标转换为百度坐标
      var translatePoints = [];
      return new Promise((resolve, reject) => {
    /** 初始化地图 */
    async initMap() {
      try {
        await this.loadBMapScript();
        // 确保DOM元素已经准备好
        await this.$nextTick();
        // 确保BMap对象存在
        if (!window.BMap || !window.BMap.Map) {
          throw new Error("百度地图API未正确加载");
        }
        // 创建地图实例
        this.map = new BMap.Map("mapContainer");
        // 设置地图中心点和缩放级别
        this.map.centerAndZoom(new BMap.Point(116.404, 39.915), 11);
        // 启用滚轮放大缩小
        this.map.enableScrollWheelZoom();
        console.log("地图初始化成功");
        return true;
      } catch (error) {
        console.error("地图初始化失败", error);
        this.$message.error("地图加载失败,请刷新页面重试");
        return false;
      }
    },
    /** 坐标转换方法 */
    translatePoint(point) {
      return new Promise((resolve) => {
        // 使用百度地图API内置的坐标转换
        const convertor = new BMap.Convertor();
        convertor.translate(points, 1, 5, (data) => {
        const pointArr = [];
        pointArr.push(point);
        convertor.translate(pointArr, 1, 5, (data) => {
          if (data.status === 0) {
            translatePoints = data.points;
            resolve(translatePoints);
            resolve(data.points[0]);
          } else {
            // 如果转换失败,返回原始坐标
            resolve(point);
          }
        });
      });
    },
    /** 批量坐标转换 */
    async translatePoints(points) {
      const translatePoints = [];
      for (const point of points) {
        const translatedPoint = await this.translatePoint(point);
        translatePoints.push(translatedPoint);
      }
      return translatePoints;
    },
    /** 搜索按钮操作 */
    handleQuery() {
@@ -285,90 +323,117 @@
      this.resetForm("queryForm");
      this.handleQuery();
    },
    /** 初始化地图 */
    initMap() {
      // 创建地图实例
      this.map = new BMap.Map("mapContainer");
      // 设置地图中心点和缩放级别
      this.map.centerAndZoom(new BMap.Point(116.404, 39.915), 11);
      // 启用滚轮放大缩小
      this.map.enableScrollWheelZoom();
      console.log("initMap 初始化地图")
    /** 计算两点之间的距离(米) */
    getDistance(point1, point2) {
      return this.map.getDistance(point1, point2);
    },
     /** 计算两点之间的距离(米) */
     getDistance(point1, point2) {
        return this.map.getDistance(point1, point2);
      },
      /** 计算两点之间的角度 */
      getAngle(point1, point2) {
        const dx = point2.lng - point1.lng;
        const dy = point2.lat - point1.lat;
        return Math.atan2(dy, dx) * 180 / Math.PI;
      },
    /** 计算两点之间的角度 */
    getAngle(point1, point2) {
      const dx = point2.lng - point1.lng;
      const dy = point2.lat - point1.lat;
      return Math.atan2(dy, dx) * 180 / Math.PI;
    },
    /** 绘制轨迹 */
    async drawTrack() {
      // 清除之前的轨迹
      if (this.polyline) {
        this.map.removeOverlay(this.polyline);
      }
      this.markers.forEach((marker) => {
        this.map.removeOverlay(marker);
      });
      this.markers = [];
      if (this.gpsList.length === 0) {
      // 确保地图实例存在
      if (!this.map) {
        console.error("地图实例未初始化");
        return;
      }
      // 按时间排序
      this.gpsList.sort((a, b) => {
        return new Date(a.collectTime) - new Date(b.collectTime);
      });
      try {
        // 清除之前的轨迹
        if (this.polyline) {
          this.map.removeOverlay(this.polyline);
        }
        this.markers.forEach((marker) => {
          this.map.removeOverlay(marker);
        });
        this.markers = [];
      // 计算当前段落的起始和结束索引
      const startIndex = this.segmentIndex * this.segmentSize;
      const endIndex = Math.min(
        startIndex + this.segmentSize,
        this.gpsList.length
      );
      const currentSegment = this.gpsList.slice(startIndex, endIndex);
        if (this.gpsList.length === 0) {
          return;
        }
      //先获得所有坐标数组
      const originPoints = currentSegment.map(
        (item) => new BMap.Point(item.longitude, item.latitude)
      );
      this.gpsList.sort((a, b) => {
        return new Date(b.collectTime) - new Date(a.collectTime);
      }).forEach(item => {
       item.speed=item.speed/1000;
      });
      //批量转换坐标
      var translatePoints = await this.translatePoints(originPoints);
        // 按时间排序
        this.gpsList.sort((a, b) => {
          return new Date(a.collectTime) - new Date(b.collectTime);
        });
      // 创建轨迹点数组
      const points = translatePoints;
      translatePoints.forEach((item, index) => {
        const bdPoint = item;
        // 计算当前段落的起始和结束索引
        const startIndex = this.segmentIndex * this.segmentSize;
        const endIndex = Math.min(
          startIndex + this.segmentSize,
          this.gpsList.length
        );
        const currentSegment = this.gpsList.slice(startIndex, endIndex);
        // 判断起点和终点是否相同
        const isStartPoint = index === 0;
        const isEndPoint = index === translatePoints.length - 1;
        const isStartEndSame = isStartPoint && isEndPoint &&
          translatePoints[0].lng === translatePoints[translatePoints.length - 1].lng &&
          translatePoints[0].lat === translatePoints[translatePoints.length - 1].lat;
        // 获取所有坐标数组
        const originPoints = currentSegment.map(
          (item) => new BMap.Point(item.longitude, item.latitude)
        );
        this.gpsList.sort((a, b) => {
          return new Date(b.collectTime) - new Date(a.collectTime);
        }).forEach(item => {
          item.speed = item.speed/1000;
        });
        // 只在起点和终点创建标记,且起点和终点不同时才显示起点标记
        if ((isStartPoint && !isStartEndSame) || isEndPoint) {
          let marker;
          let direction=currentSegment[index].direction;
          if (isStartPoint && !isStartEndSame) {
            // 起点显示"起"字
            const label = new BMap.Label("起", {
              offset: new BMap.Size(0, 0),
        // 批量转换坐标
        const translatePoints = await this.translatePoints(originPoints);
        // 创建轨迹点数组
        const points = translatePoints;
        for (let index = 0; index < translatePoints.length; index++) {
          const bdPoint = translatePoints[index];
          // 判断起点和终点是否相同
          const isStartPoint = index === 0;
          const isEndPoint = index === translatePoints.length - 1;
          const isStartEndSame = isStartPoint && isEndPoint &&
            translatePoints[0].lng === translatePoints[translatePoints.length - 1].lng &&
            translatePoints[0].lat === translatePoints[translatePoints.length - 1].lat;
          // 只在起点和终点创建标记,且起点和终点不同时才显示起点标记
          if ((isStartPoint && !isStartEndSame) || isEndPoint) {
            let marker;
            let direction = currentSegment[index].direction;
            if (isStartPoint && !isStartEndSame) {
              // 起点显示"起"字
              const label = new BMap.Label("起", {
                offset: new BMap.Size(0, 0),
                position: bdPoint,
              });
              label.setStyle({
                color: "white",
                fontSize: "12px",
                backgroundColor: "#3388ff",
                border: "none",
                padding: "2px 6px",
                borderRadius: "3px",
              });
              marker = new BMap.Marker(bdPoint, {rotation: direction});
              marker.setLabel(label);
            } else {
              // 终点显示车辆图标
              const myIcon = new BMap.Icon(
                "/car_blue.png",
                new BMap.Size(20, 20),
                {
                  imageSize: new BMap.Size(20, 20),
                  anchor: new BMap.Size(10, 10),
                }
              );
              marker = new BMap.Marker(bdPoint, {
                icon: myIcon,
                rotation: direction,
              });
            }
            // 在车图标上显示车牌
            const label = new BMap.Label(currentSegment[index].vehicleNo, {
              offset: new BMap.Size(0, -25),
              position: bdPoint,
            });
            label.setStyle({
@@ -379,81 +444,56 @@
              padding: "2px 6px",
              borderRadius: "3px",
            });
            marker = new BMap.Marker(bdPoint,{rotation:direction});
            marker.setLabel(label);
          } else {
            // 终点显示车辆图标
            const myIcon = new BMap.Icon(
              "/car_blue.png",
              new BMap.Size(20, 20),
              {
                imageSize: new BMap.Size(20, 20),
                anchor: new BMap.Size(10, 10),
            // 获取地址信息
            const geoc = new BMap.Geocoder();
            geoc.getLocation(bdPoint, (rs) => {
              if (rs && rs.addressComponents) {
                const addComp = rs.addressComponents;
                const address =
                  addComp.province +
                  addComp.city +
                  addComp.district +
                  addComp.street +
                  addComp.streetNumber;
                // 添加点击事件监听器
                marker.addEventListener("click", () => {
                  // 创建信息窗口
                  const infoWindow = new BMap.InfoWindow(
                    `车牌号:${currentSegment[index].vehicleNo}<br/>时间:${currentSegment[index].collectTime}<br/>速度:${
                      currentSegment[index].speed
                    }km/h<br/>方向:${currentSegment[index].direction}°<br/>地址:${address}`
                  );
                  this.map.openInfoWindow(infoWindow, bdPoint);
                });
              }
            );
            marker = new BMap.Marker(bdPoint, {
              icon: myIcon,
              rotation: direction,
            });
            this.map.addOverlay(marker);
            this.markers.push(marker);
          }
          //在车图标上显示车牌
          const label = new BMap.Label(currentSegment[index].vehicleNo, {
            offset: new BMap.Size(0, -25), // 向上偏移25像素
            position: bdPoint,
          });
          label.setStyle({
            color: "white",
            fontSize: "12px",
            backgroundColor: "#3388ff",
            border: "none",
            padding: "2px 6px",
            borderRadius: "3px",
          });
          marker.setLabel(label);
          // 获取地址信息
          const geoc = new BMap.Geocoder();
          geoc.getLocation(bdPoint, (rs) => {
            const addComp = rs.addressComponents;
            const address =
              addComp.province +
              addComp.city +
              addComp.district +
              addComp.street +
              addComp.streetNumber;
            // 添加点击事件监听器
            marker.addEventListener("click", () => {
              // 创建信息窗口
              const infoWindow = new BMap.InfoWindow(
                `车牌号:${currentSegment[index].vehicleNo}<br/>时间:${currentSegment[index].collectTime}<br/>速度:${
                  currentSegment[index].speed
                }km/h<br/>方向:${currentSegment[index].direction}°<br/>地址:${address}`
              );
              this.map.openInfoWindow(infoWindow, bdPoint);
          // 如果是最后一个点,绘制轨迹线
          if (index === currentSegment.length - 1) {
            // 创建轨迹线
            this.polyline = new BMap.Polyline(points, {
              strokeColor: "#3388ff",
              strokeWeight: 5,
              strokeOpacity: 0.8,
              strokeStyle: "solid",
            });
          });
            this.map.addOverlay(this.polyline);
          this.map.addOverlay(marker);
          this.markers.push(marker);
            // 调整地图视野以显示完整轨迹
            this.map.setViewport(points);
          }
        }
        // 如果是最后一个点,绘制轨迹线
        if (index === currentSegment.length - 1) {
          // 创建轨迹线
          this.polyline = new BMap.Polyline(points, {
            strokeColor: "#3388ff",
            strokeWeight: 5,
            strokeOpacity: 0.8,
            strokeStyle: "solid",
          });
          this.map.addOverlay(this.polyline);
          // 调整地图视野以显示完整轨迹
          this.map.setViewport(points);
        }
      });
      } catch (error) {
        console.error("绘制轨迹失败", error);
        this.$message.error("绘制轨迹失败,请刷新页面重试");
      }
    },
    /** 点击表格行 */
    handleRowClick(row) {