编辑 | blame | 历史 | 原始文档

急救转运任务出发地目的地自动填充功能说明

功能概述

在创建急救转运任务时,自动将**转出医院地址**作为**出发地**,将**转入医院地址**作为**目的地**,写入主任务表(sys_task)的相应字段,便于任务调度、路径规划和统计分析。

业务背景

急救转运任务的核心就是将患者从一个医院转运到另一个医院:
- 转出医院 = 任务的起点(出发地)
- 转入医院 = 任务的终点(目的地)

将这些信息写入主任务表的好处:
1. 统一数据结构:所有类型任务(急救转运、福祉车、保养等)都有出发地和目的地
2. 便于路径规划:GPS导航可以直接使用出发地和目的地坐标
3. 统计分析:可以统计各区域的任务量、行驶距离等
4. 任务调度:调度系统可以根据地理位置分配最近的车辆和人员
5. 旧系统兼容:便于与旧系统的数据同步

实现位置

前端文件: app/pages/task/create-emergency.vue
后端实体: com.ruoyi.system.domain.SysTask

技术实现

1. 前端数据提交

buildSubmitData() 方法中,添加出发地和目的地字段:

const submitData = {
  taskType: 'EMERGENCY_TRANSFER',
  vehicleIds: this.selectedVehicleId ? [this.selectedVehicleId] : [],
  assigneeIds: this.selectedStaff.map(staff => staff.userId),
  transferTime: this.taskForm.transferTime,
  documentTypeId: this.selectedDocumentTypeId,
  taskTypeId: this.selectedEmergencyTaskTypeId,
  
  // 将转出医院地址作为出发地,转入医院地址作为目的地
  departureAddress: this.taskForm.hospitalOut.address || '',
  destinationAddress: this.taskForm.hospitalIn.address || '',
  
  patient: { ... },
  hospitalOut: this.taskForm.hospitalOut,
  hospitalIn: this.taskForm.hospitalIn,
  transferDistance: this.taskForm.transferDistance ? parseFloat(this.taskForm.transferDistance) : null,
  price: this.taskForm.price ? parseFloat(this.taskForm.price) : null
}

2. GPS坐标同步

如果有GPS坐标信息,同时写入出发地和目的地的经纬度:

if (this.addressCoordinates.hospitalOutAddress) {
  // 转出医院GPS坐标写入扩展表
  if (!submitData.hospitalOut) submitData.hospitalOut = {}
  submitData.hospitalOut.longitude = this.addressCoordinates.hospitalOutAddress.lng
  submitData.hospitalOut.latitude = this.addressCoordinates.hospitalOutAddress.lat
  
  // 同时写入主任务表的出发地经纬度
  submitData.departureLongitude = this.addressCoordinates.hospitalOutAddress.lng
  submitData.departureLatitude = this.addressCoordinates.hospitalOutAddress.lat
}

if (this.addressCoordinates.hospitalInAddress) {
  // 转入医院GPS坐标写入扩展表
  if (!submitData.hospitalIn) submitData.hospitalIn = {}
  submitData.hospitalIn.longitude = this.addressCoordinates.hospitalInAddress.lng
  submitData.hospitalIn.latitude = this.addressCoordinates.hospitalInAddress.lat
  
  // 同时写入主任务表的目的地经纬度
  submitData.destinationLongitude = this.addressCoordinates.hospitalInAddress.lng
  submitData.destinationLatitude = this.addressCoordinates.hospitalInAddress.lat
}

3. 后端数据接收

主任务表(sys_task)相关字段:

-- 出发地址
departure_address VARCHAR(500)

-- 目的地址
destination_address VARCHAR(500)

-- 出发地经度
departure_longitude DECIMAL(10,7)

-- 出发地纬度
departure_latitude DECIMAL(10,7)

-- 目的地经度
destination_longitude DECIMAL(10,7)

-- 目的地纬度
destination_latitude DECIMAL(10,7)

对应的Java实体字段:

public class SysTask extends BaseEntity {
    /** 出发地址 */
    private String departureAddress;
    
    /** 目的地址 */
    private String destinationAddress;
    
    /** 出发地经度 */
    private java.math.BigDecimal departureLongitude;
    
    /** 出发地纬度 */
    private java.math.BigDecimal departureLatitude;
    
    /** 目的地经度 */
    private java.math.BigDecimal destinationLongitude;
    
    /** 目的地纬度 */
    private java.math.BigDecimal destinationLatitude;
    
    // ... getters and setters
}

数据流程

数据流转图

用户选择转出医院
    ↓
选择医院后,自动填充 taskForm.hospitalOut.address
(例如:"广东省广州市天河区中山大道123号")
    ↓
用户选择转入医院
    ↓
选择医院后,自动填充 taskForm.hospitalIn.address
(例如:"广东省广州市越秀区解放路456号")
    ↓
用户点击"保存"
    ↓
调用 buildSubmitData()
    ↓
设置 departureAddress = taskForm.hospitalOut.address
设置 destinationAddress = taskForm.hospitalIn.address
    ↓
如果有GPS坐标,同时设置经纬度字段
    ↓
提交到后端
    ↓
保存到 sys_task 表

数据结构

前端表单数据

taskForm: {
  hospitalOut: {
    id: 123,                    // 医院ID
    name: "广州市第一人民医院",  // 医院名称
    department: "急诊科",         // 科室
    departmentId: 10,            // 科室ID
    bedNumber: "A101",           // 床号
    address: "广东省广州市天河区中山大道123号",  // 完整地址
    longitude: 113.3245,         // 经度(如果有GPS)
    latitude: 23.1291            // 纬度(如果有GPS)
  },
  hospitalIn: {
    id: 456,
    name: "广州市中医医院",
    department: "骨科",
    departmentId: 15,
    bedNumber: "B205",
    address: "广东省广州市越秀区解放路456号",
    longitude: 113.2654,
    latitude: 23.1389
  }
}

提交数据结构

{
  taskType: "EMERGENCY_TRANSFER",
  vehicleIds: [101],
  assigneeIds: [1, 2, 3],
  
  // 主任务表字段
  departureAddress: "广东省广州市天河区中山大道123号",
  destinationAddress: "广东省广州市越秀区解放路456号",
  departureLongitude: 113.3245,
  departureLatitude: 23.1291,
  destinationLongitude: 113.2654,
  destinationLatitude: 23.1389,
  
  // 扩展信息
  hospitalOut: {
    id: 123,
    name: "广州市第一人民医院",
    department: "急诊科",
    departmentId: 10,
    bedNumber: "A101",
    address: "广东省广州市天河区中山大道123号",
    longitude: 113.3245,
    latitude: 23.1291
  },
  hospitalIn: {
    id: 456,
    name: "广州市中医医院",
    department: "骨科",
    departmentId: 15,
    bedNumber: "B205",
    address: "广东省广州市越秀区解放路456号",
    longitude: 113.2654,
    latitude: 23.1389
  },
  
  patient: { ... },
  transferDistance: 15.5,
  price: 300.00
}

数据库存储

主任务表(sys_task)

字段 说明
task_id 10001 任务ID
task_type EMERGENCY_TRANSFER 任务类型
departure_address 广东省广州市天河区中山大道123号 出发地址(转出医院)
destination_address 广东省广州市越秀区解放路456号 目的地址(转入医院)
departure_longitude 113.3245 出发地经度
departure_latitude 23.1291 出发地纬度
destination_longitude 113.2654 目的地经度
destination_latitude 23.1389 目的地纬度

扩展信息表(sys_task_emergency)

字段 说明
emergency_id 20001 扩展信息ID
task_id 10001 关联任务ID
hospital_out_id 123 转出医院ID
hospital_out_name 广州市第一人民医院 转出医院名称
hospital_out_department_id 10 转出科室ID
hospital_out_department 急诊科 转出科室
hospital_out_address 广东省广州市天河区中山大道123号 转出医院地址
hospital_in_id 456 转入医院ID
hospital_in_name 广州市中医医院 转入医院名称
hospital_in_department_id 15 转入科室ID
hospital_in_department 骨科 转入科室
hospital_in_address 广东省广州市越秀区解放路456号 转入医院地址

应用场景

1. 任务调度

调度系统可以根据出发地坐标,计算距离最近的可用车辆:

// 查询离出发地最近的车辆
SELECT v.*, 
  SQRT(POW(v.current_longitude - task.departure_longitude, 2) + 
       POW(v.current_latitude - task.departure_latitude, 2)) AS distance
FROM tb_vehicle_info v
JOIN sys_task task ON task.task_id = ?
WHERE v.status = 'AVAILABLE'
ORDER BY distance
LIMIT 5

2. 路径规划

GPS导航可以直接使用出发地和目的地坐标:

// 调用地图API规划路线
const route = await mapAPI.planRoute({
  origin: {
    longitude: task.departureLongitude,
    latitude: task.departureLatitude
  },
  destination: {
    longitude: task.destinationLongitude,
    latitude: task.destinationLatitude
  }
})

// 显示预计时间和距离
console.log('预计行驶时间:', route.duration, '分钟')
console.log('预计行驶距离:', route.distance, '公里')

3. 统计分析

按区域统计任务量:

-- 统计各区域的任务数量
SELECT 
  SUBSTRING(departure_address, 1, CHARINDEX('市', departure_address)) AS region,
  COUNT(*) AS task_count,
  AVG(estimated_distance) AS avg_distance
FROM sys_task
WHERE task_type = 'EMERGENCY_TRANSFER'
  AND create_time >= '2025-01-01'
GROUP BY SUBSTRING(departure_address, 1, CHARINDEX('市', departure_address))
ORDER BY task_count DESC

4. 任务列表展示

在任务列表中显示出发地和目的地:

<view class="task-item">
  <view class="task-info">
    <text class="task-code">{{ task.taskCode }}</text>
    <text class="task-type">急救转运</text>
  </view>
  <view class="task-location">
    <view class="location-item">
      <uni-icons type="location" size="16" color="#007AFF"></uni-icons>
      <text class="departure">{{ task.departureAddress }}</text>
    </view>
    <view class="arrow">→</view>
    <view class="location-item">
      <uni-icons type="location-filled" size="16" color="#ff4d4f"></uni-icons>
      <text class="destination">{{ task.destinationAddress }}</text>
    </view>
  </view>
</view>

异常处理

1. 医院地址为空

场景: 用户选择了医院但地址未能获取

处理:
javascript departureAddress: this.taskForm.hospitalOut.address || '', destinationAddress: this.taskForm.hospitalIn.address || '',

结果: 使用空字符串,不影响任务创建

2. GPS坐标缺失

场景: 医院数据中没有GPS坐标信息

处理:
javascript if (this.addressCoordinates.hospitalOutAddress) { // 只在有坐标时才设置 submitData.departureLongitude = ... submitData.departureLatitude = ... }

结果: 经纬度字段为 NULL,不影响任务创建

3. 地址过长

场景: 完整地址超过数据库字段长度限制(500字符)

处理: 数据库字段定义为 VARCHAR(500),通常足够

备选方案: 如果确实超长,可以在前端截取:
javascript departureAddress: (this.taskForm.hospitalOut.address || '').substring(0, 500), destinationAddress: (this.taskForm.hospitalIn.address || '').substring(0, 500),

数据一致性

双重存储策略

出发地和目的地信息在两个地方存储:

  1. 主任务表(sys_task)
  • departure_address / destination_address
  • departure_longitude / departure_latitude
  • destination_longitude / destination_latitude
  1. 扩展信息表(sys_task_emergency)
  • hospital_out_address / hospital_in_address
  • hospital_out_longitude / hospital_out_latitude
  • hospital_in_longitude / hospital_in_latitude

为什么要双重存储?

  1. 主任务表: 供通用任务调度、统计使用,所有任务类型统一
  2. 扩展表: 保留完整的医院信息,包括医院ID、名称、科室等

数据同步保证

前端代码确保数据一致:
```javascript
// 地址一致
departureAddress: this.taskForm.hospitalOut.address
hospitalOut.address: this.taskForm.hospitalOut.address

// GPS坐标一致
departureLongitude: this.addressCoordinates.hospitalOutAddress.lng
hospitalOut.longitude: this.addressCoordinates.hospitalOutAddress.lng
```

相关规范

根据项目记忆:

  1. 急救转运扩展信息存储方案(memory: 5b8a95d1):
  • ✅ 急救转运任务的扩展信息采用独立的 sys_task_emergency 表
  • ✅ 与主任务表通过任务ID关联
  • ✅ 本次修改确保了主任务表和扩展表的数据同步
  1. 旧系统同步参数扩展规则(memory: cabfc07d):
  • ✅ 需要传递医院ID和科室ID
  • ✅ 地址信息便于旧系统展示和调度

测试建议

测试场景1:正常创建任务

操作:
1. 选择转出医院:"广州市第一人民医院"
2. 地址自动填充:"广东省广州市天河区中山大道123号"
3. 选择转入医院:"广州市中医医院"
4. 地址自动填充:"广东省广州市越秀区解放路456号"
5. 填写其他信息并保存

验证:
sql SELECT task_id, departure_address, destination_address, departure_longitude, departure_latitude, destination_longitude, destination_latitude FROM sys_task WHERE task_id = ?

预期结果:
- departure_address = "广东省广州市天河区中山大道123号"
- destination_address = "广东省广州市越秀区解放路456号"
- 如果有GPS坐标,经纬度字段有值
- 如果无GPS坐标,经纬度字段为 NULL

测试场景2:医院地址为空

操作:
1. 选择的医院数据中没有地址信息
2. 保存任务

预期结果:
- departure_address = ""(空字符串)
- destination_address = ""(空字符串)
- 任务创建成功,不报错

测试场景3:地址变更

操作:
1. 先选择医院A,地址自动填充
2. 又选择医院B,地址应该更新
3. 保存任务

预期结果:
- 保存的地址是最后选择的医院B的地址
- 不会残留医院A的地址

测试场景4:任务列表显示

操作:
1. 创建任务后
2. 在任务列表查看

预期结果:
- 任务列表正确显示出发地和目的地
- GPS地图能正确定位两个位置

优化建议

1. 地址简化显示

完整地址可能过长,可以提供简化版本:

// 提取关键信息:市 + 区 + 主要道路
function simplifyAddress(fullAddress) {
  // "广东省广州市天河区中山大道123号" → "天河区中山大道"
  const match = fullAddress.match(/(.+?市)?(.+?区)(.+?)(\d+号)?/)
  if (match) {
    return (match[2] || '') + (match[3] || '')
  }
  return fullAddress
}

// 在任务列表中使用简化地址
const displayAddress = simplifyAddress(task.departureAddress)

2. 地址验证

确保地址格式正确:

function validateAddress(address) {
  if (!address || address.length < 5) {
    return false
  }
  // 检查是否包含关键词
  const keywords = ['省', '市', '区', '县', '路', '街', '道', '号']
  return keywords.some(keyword => address.includes(keyword))
}

3. 自动计算距离

如果有GPS坐标,自动计算两点距离:

function calculateDistance(lat1, lng1, lat2, lng2) {
  const R = 6371 // 地球半径(公里)
  const dLat = (lat2 - lat1) * Math.PI / 180
  const dLng = (lng2 - lng1) * Math.PI / 180
  const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
            Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
            Math.sin(dLng/2) * Math.sin(dLng/2)
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))
  return R * c
}

// 自动填充转运距离
if (this.addressCoordinates.hospitalOutAddress && 
    this.addressCoordinates.hospitalInAddress) {
  const distance = calculateDistance(
    this.addressCoordinates.hospitalOutAddress.lat,
    this.addressCoordinates.hospitalOutAddress.lng,
    this.addressCoordinates.hospitalInAddress.lat,
    this.addressCoordinates.hospitalInAddress.lng
  )
  this.taskForm.transferDistance = distance.toFixed(2)
}

版本历史

  • 2025-01-25 v1: 初始版本
  • ✅ 将转出医院地址写入 departure_address
  • ✅ 将转入医院地址写入 destination_address
  • ✅ 同时写入GPS坐标到 departure/destination longitude/latitude
  • ✅ 确保主任务表和扩展表数据一致
  • ✅ 添加异常处理(地址为空的情况)