wlzboy
2025-11-27 668e570bd1db6bd00e4293b6977e6d3d051053ce
app/pages/task/edit-emergency.vue
@@ -37,6 +37,28 @@
        />
      </view>
      
      <view class="form-item">
        <view class="form-label">执行任务人员</view>
        <view class="staff-list">
          <view class="staff-item" v-for="(staff, index) in selectedStaff" :key="staff.userId">
            <view class="staff-info">
              <text class="staff-name">{{ staff.nickName }}</text>
              <text class="staff-role">({{ getUserTypeName(staff.type) || '未知职位' }})</text>
            </view>
            <uni-icons
              type="closeempty"
              size="20"
              color="#ff4d4f"
              @click="removeStaff(index)"
            ></uni-icons>
          </view>
          <view class="add-staff" @click="showStaffSelector">
            <uni-icons type="plusempty" size="20" color="#007AFF"></uni-icons>
            <text>添加人员</text>
          </view>
        </view>
      </view>
      <view class="form-section-title">患者信息</view>
      <view class="form-item">
        <view class="form-label required">联系人</view>
@@ -166,6 +188,80 @@
        ></map-selector>
      </view>
    </uni-popup>
    <!-- 人员选择器弹窗 -->
    <uni-popup ref="staffPopup" type="bottom" :mask-click="false">
      <view class="staff-popup-container">
        <view class="popup-header">
          <view class="popup-title">选择执行人员</view>
          <view class="close-btn" @click="closeStaffSelector">
            <uni-icons type="closeempty" size="20" color="#999"></uni-icons>
          </view>
        </view>
        <view class="search-bar">
          <input
            class="search-input"
            placeholder="搜索姓名或电话"
            v-model="staffSearchKeyword"
            @input="onStaffSearch"
          />
        </view>
        <view class="filter-tabs">
          <view
            class="filter-tab"
            :class="{ active: staffFilterType === 'driver' }"
            @click="filterStaff('driver')"
          >
            司机
          </view>
          <view
            class="filter-tab"
            :class="{ active: staffFilterType === 'doctor' }"
            @click="filterStaff('doctor')"
          >
            医生
          </view>
          <view
            class="filter-tab"
            :class="{ active: staffFilterType === 'nurse' }"
            @click="filterStaff('nurse')"
          >
            护士
          </view>
        </view>
        <scroll-view class="staff-list-scroll" scroll-y="true">
          <view
            class="staff-list-item"
            v-for="staff in filteredStaffList"
            :key="staff.userId"
            @click="toggleStaffSelection(staff)"
          >
            <view class="staff-item-info">
              <text class="staff-item-name">{{ staff.nickName }}</text>
              <text class="staff-item-dept">{{ staff.deptName }}</text>
            </view>
            <view class="staff-item-check">
              <uni-icons
                v-if="isStaffSelected(staff.userId)"
                type="checkmarkempty"
                size="24"
                color="#007AFF"
              ></uni-icons>
            </view>
          </view>
          <view v-if="filteredStaffList.length === 0" class="empty-tip">
            暂无人员数据
          </view>
        </scroll-view>
        <view class="popup-footer">
          <button class="confirm-btn" @click="confirmStaffSelection">确定(已选{{ selectedStaff.length }})</button>
        </view>
      </view>
    </uni-popup>
  </scroll-view>
</template>
@@ -174,6 +270,7 @@
import uniDatetimePicker from '@/uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue'
import uniPopup from '@/uni_modules/uni-popup/components/uni-popup/uni-popup.vue'
import { getTask, updateTask } from "@/api/task"
import { listBranchUsers } from "@/api/system/user"
import { baiduDistanceByAddress } from "@/api/map"
import { calculateTransferPrice } from "@/api/price"
import MapSelector from '@/components/map-selector.vue'
@@ -196,18 +293,23 @@
  mixins: [distanceCalculator],
  data() {
    return {
      loading: false,
      taskId: null,
      taskDetail: null,
      selectedVehicleId: null,
      selectedOrganizationId: null,
      selectedRegion: '',
      mapSelectorType: '',
      // 地址坐标(用于手动输入地址时计算距离)
      addressCoordinates: {
        hospitalOutAddress: null,
        hospitalInAddress: null
      },
      // 出发地信息
      departureAddress: '',
      departureLongitude: null,
      departureLatitude: null,
      selectedDiseases: [], // 已选择的病情列表
      selectedStaff: [], // 已选择的人员列表
      allStaffList: [], // 所有人员列表
      filteredStaffList: [], // 过滤后的人员列表
      staffSearchKeyword: '', // 人员搜索关键词
      staffFilterType: 'driver', // 人员筛选类型
      taskForm: {
        transferTime: '',
        patient: {
@@ -237,8 +339,7 @@
        },
        transferDistance: '',
        price: ''
      },
      loading: false
      }
    }
  },
  computed: {
@@ -255,6 +356,7 @@
    if (options.id) {
      this.taskId = options.id
      this.loadTaskDetail()
      this.loadDeptStaff() // 加载人员列表
    } else {
      this.$modal.showToast('任务ID不能为空')
      setTimeout(() => {
@@ -311,12 +413,30 @@
          this.taskForm.hospitalOut.bedNumber = info.hospitalOutBedNumber || ''
          this.taskForm.hospitalOut.address = info.hospitalOutAddress || ''
          
          // 加载转出医院GPS坐标(不显示,但保存在数据中)
          if (info.hospitalOutLongitude && info.hospitalOutLatitude) {
            this.addressCoordinates.hospitalOutAddress = {
              lng: info.hospitalOutLongitude,
              lat: info.hospitalOutLatitude
            }
            console.log('加载转出医院GPS坐标:', info.hospitalOutLongitude, info.hospitalOutLatitude)
          }
          // 转入医院信息
          this.taskForm.hospitalIn.id = info.hospitalInId || null
          this.taskForm.hospitalIn.name = info.hospitalInName || ''
          this.taskForm.hospitalIn.department = info.hospitalInDepartment || ''
          this.taskForm.hospitalIn.bedNumber = info.hospitalInBedNumber || ''
          this.taskForm.hospitalIn.address = info.hospitalInAddress || ''
          // 加载转入医院GPS坐标(不显示,但保存在数据中)
          if (info.hospitalInLongitude && info.hospitalInLatitude) {
            this.addressCoordinates.hospitalInAddress = {
              lng: info.hospitalInLongitude,
              lat: info.hospitalInLatitude
            }
            console.log('加载转入医院GPS坐标:', info.hospitalInLongitude, info.hospitalInLatitude)
          }
          
          // 转运距离和价格
          this.taskForm.transferDistance = info.transferDistance ? String(info.transferDistance) : ''
@@ -346,20 +466,43 @@
          console.warn('未找到归属机构信息')
        }
        
        // 设置地址坐标(使用mixin中的方法)
        // 设置出发地信息(地址和坐标)
        if (this.taskDetail.departureAddress) {
          this.departureAddress = this.taskDetail.departureAddress
        }
        if (this.taskDetail.departureLongitude && this.taskDetail.departureLatitude) {
          this.setStartLocation({
            lng: this.taskDetail.departureLongitude,
            lat: this.taskDetail.departureLatitude
          })
          console.log('设置出发地坐标')
          this.departureLongitude = this.taskDetail.departureLongitude
          this.departureLatitude = this.taskDetail.departureLatitude
          console.log('设置出发地坐标:', this.departureLongitude, this.departureLatitude)
        }
        // 设置目标地信息(转入医院的地址和坐标)
        if (this.taskDetail.destinationAddress) {
          // 目标地地址已经在 taskForm.hospitalIn.address 中设置
          console.log('目标地地址:', this.taskDetail.destinationAddress)
        }
        if (this.taskDetail.destinationLongitude && this.taskDetail.destinationLatitude) {
          this.setEndLocation({
          this.addressCoordinates.hospitalInAddress = {
            lng: this.taskDetail.destinationLongitude,
            lat: this.taskDetail.destinationLatitude
          })
          console.log('设置目的地坐标')
          }
          console.log('设置目标地坐标:', this.taskDetail.destinationLongitude, this.taskDetail.destinationLatitude)
        }
        // 设置执行人员
        if (this.taskDetail.assignees && this.taskDetail.assignees.length > 0) {
          console.log('原始执行人员数据:', this.taskDetail.assignees)
          this.selectedStaff = this.taskDetail.assignees.map(assignee => ({
            userId: assignee.userId,
            nickName: assignee.userName,
            type: assignee.userType || 'driver',
            phonenumber: '',
            deptName: ''
          }))
          console.log('处理后的执行人员列表:', this.selectedStaff)
        } else {
          console.warn('任务没有分配执行人员或assignees为空')
          console.log('taskDetail.assignees:', this.taskDetail.assignees)
        }
        
        console.log('表单数据填充完成:', this.taskForm)
@@ -402,16 +545,10 @@
      }
    },
    
    // 转出医院地址选择(当选择"家中"时使用百度地图地址建议)
    // 转出医院地址选择事件(简化)
    onHospitalOutAddressSelected(data) {
      // data 包含:address, location
      if (data.location) {
        this.addressCoordinates.hospitalOutAddress = data.location
        // 如果转入地址也已填写,自动计算距离
        if (this.taskForm.hospitalIn.address) {
          this.calculateDistanceByManualAddress()
        }
      }
    },
    
@@ -432,16 +569,10 @@
      }
    },
    
    // 转入医院地址选择(当选择"家中"时使用百度地图地址建议)
    // 转入医院地址选择事件(简化)
    onHospitalInAddressSelected(data) {
      // data 包含:address, location
      if (data.location) {
        this.addressCoordinates.hospitalInAddress = data.location
        // 如果转出地址也已填写,自动计算距离
        if (this.taskForm.hospitalOut.address) {
          this.calculateDistanceByManualAddress()
        }
      }
    },
    
@@ -449,6 +580,156 @@
    onDiseaseChange(diseases) {
      console.log('病情变化:', diseases)
      // 组件已经通过 v-model 更新了 selectedDiseases
    },
    // 加载当前用户所在分公司的所有人员
    loadDeptStaff() {
      console.log('开始加载人员列表')
      listBranchUsers().then(response => {
        console.log('人员列表API响应:', response)
        const userList = response.data || []
        console.log('解析出的用户列表:', userList, '数量:', userList.length)
        this.allStaffList = userList.map(user => ({
          userId: user.userId,
          nickName: user.nickName,
          phonenumber: user.phonenumber,
          deptName: user.dept?.deptName || '',
          postName: user.posts && user.posts.length > 0 ? user.posts[0].postName : '',
          roleName: user.roles && user.roles.length > 0 ? user.roles[0].roleName : '',
          type: this.getUserType(user)
        }))
        console.log('处理后的人员列表:', this.allStaffList, '数量:', this.allStaffList.length)
        this.filterStaffList()
      }).catch(error => {
        console.error('加载人员列表失败:', error)
        this.$modal.showToast('加载人员列表失败')
      })
    },
    // 根据用户的岗位或角色判断类型
    getUserType(user) {
      const postName = user.posts && user.posts.length > 0 ? user.posts[0].postName : ''
      const roleName = user.roles && user.roles.length > 0 ? user.roles[0].roleName : ''
      const deptName = user.dept?.deptName || ''
      if (postName.includes('司机') || roleName.includes('司机') || deptName.includes('车队') || deptName.includes('司机')) {
        return 'driver'
      }
      if (postName.includes('护士') || roleName.includes('护士') || deptName.includes('护士')) {
        return 'nurse'
      }
      if (postName.includes('医生') || roleName.includes('医生') || deptName.includes('医生')) {
        return 'doctor'
      }
      if (deptName.includes('医护')) {
        return 'doctor'
      }
      return 'driver'
    },
    getUserTypeName(staffType) {
      switch(staffType) {
        case 'nurse':
          return '护士'
        case 'doctor':
          return '医生'
        case 'driver':
          return '司机'
        default:
          return '司机'
      }
    },
    // 显示人员选择弹窗
    showStaffSelector() {
      this.$refs.staffPopup.open()
      this.filterStaffList()
    },
    // 关闭人员选择弹窗
    closeStaffSelector() {
      this.$refs.staffPopup.close()
      this.staffSearchKeyword = ''
      this.staffFilterType = 'driver'
    },
    // 人员搜索
    onStaffSearch(e) {
      this.staffSearchKeyword = e.detail.value
      this.filterStaffList()
    },
    // 筛选人员类型
    filterStaff(type) {
      this.staffFilterType = type
      this.filterStaffList()
    },
    // 过滤人员列表
    filterStaffList() {
      console.log('开始过滤人员列表,原始数量:', this.allStaffList.length)
      let list = [...this.allStaffList]
      // 按类型过滤
      if (this.staffFilterType === 'driver') {
        list = list.filter(staff => staff.type === 'driver')
      } else if (this.staffFilterType === 'doctor') {
        list = list.filter(staff => staff.type === 'doctor')
      } else if (this.staffFilterType === 'nurse') {
        list = list.filter(staff => staff.type === 'nurse')
      }
      console.log('按类型过滤后:', this.staffFilterType, '数量:', list.length)
      // 按关键词搜索
      if (this.staffSearchKeyword && this.staffSearchKeyword.trim() !== '') {
        const keyword = this.staffSearchKeyword.trim().toLowerCase()
        list = list.filter(staff => {
          return staff.nickName.toLowerCase().includes(keyword) ||
                 (staff.phonenumber && staff.phonenumber.includes(keyword))
        })
      }
      console.log('按关键词过滤后,数量:', list.length)
      this.filteredStaffList = list
    },
    // 切换人员选中状态
    toggleStaffSelection(staff) {
      const index = this.selectedStaff.findIndex(s => s.userId === staff.userId)
      if (index > -1) {
        // 已选中,移除
        this.selectedStaff.splice(index, 1)
      } else {
        // 未选中,添加
        this.selectedStaff.push(staff)
      }
    },
    // 判断人员是否已选中
    isStaffSelected(userId) {
      return this.selectedStaff.some(staff => staff.userId === userId)
    },
    // 确认人员选择
    confirmStaffSelection() {
      if (this.selectedStaff.length === 0) {
        this.$modal.showToast('请至少选择一名人员')
        return
      }
      this.closeStaffSelector()
    },
    // 移除人员
    removeStaff(index) {
      this.selectedStaff.splice(index, 1)
    },
    
    // 解析病情信息(从字符串解析出ICD-10疾病列表)
@@ -634,10 +915,18 @@
        deptId: this.selectedOrganizationId,
        vehicleIds: this.selectedVehicleId ? [this.selectedVehicleId] : [],
        plannedStartTime: this.taskForm.transferTime,
        departureAddress: this.taskForm.hospitalOut.address,
        // 出发地使用 departureAddress(如果有),否则使用转出医院地址
        departureAddress: this.departureAddress || this.taskForm.hospitalOut.address,
        // 目标地使用转入医院地址
        destinationAddress: this.taskForm.hospitalIn.address,
        // 病情ID列表(用于同步调度单的OrdICD_ID参数)
        diseaseIds: this.selectedDiseases.map(d => d.id).filter(id => id !== null),
        // 执行人员列表
        assignees: this.selectedStaff.map(staff => ({
          userId: staff.userId,
          userName: staff.nickName,
          userType: staff.type
        })),
        emergencyInfo: {
          patientContact: this.taskForm.patient.contact,
          patientPhone: this.taskForm.patient.phone,
@@ -649,10 +938,16 @@
          hospitalOutDepartment: this.taskForm.hospitalOut.department,
          hospitalOutBedNumber: this.taskForm.hospitalOut.bedNumber,
          hospitalOutAddress: this.taskForm.hospitalOut.address,
          // 转出医院GPS坐标(保存到扩展表)
          hospitalOutLongitude: this.addressCoordinates.hospitalOutAddress ? this.addressCoordinates.hospitalOutAddress.lng : null,
          hospitalOutLatitude: this.addressCoordinates.hospitalOutAddress ? this.addressCoordinates.hospitalOutAddress.lat : null,
          hospitalInName: this.taskForm.hospitalIn.name,
          hospitalInDepartment: this.taskForm.hospitalIn.department,
          hospitalInBedNumber: this.taskForm.hospitalIn.bedNumber,
          hospitalInAddress: this.taskForm.hospitalIn.address,
          // 转入医院GPS坐标(保存到扩展表)
          hospitalInLongitude: this.addressCoordinates.hospitalInAddress ? this.addressCoordinates.hospitalInAddress.lng : null,
          hospitalInLatitude: this.addressCoordinates.hospitalInAddress ? this.addressCoordinates.hospitalInAddress.lat : null,
          transferDistance: this.taskForm.transferDistance ? parseFloat(this.taskForm.transferDistance) : null,
          transferPrice: this.taskForm.price ? parseFloat(this.taskForm.price) : null,
          // 病情详细信息(过滤掉空的病情名称)
@@ -666,16 +961,13 @@
        }
      }
      
      // 添加GPS坐标
      if (this.addressCoordinates.start) {
        submitData.departureLongitude = this.addressCoordinates.start.lng
        submitData.departureLatitude = this.addressCoordinates.start.lat
      // 出发地GPS坐标
      if (this.departureLongitude && this.departureLatitude) {
        submitData.departureLongitude = this.departureLongitude
        submitData.departureLatitude = this.departureLatitude
      }
      
      if (this.addressCoordinates.end) {
        submitData.destinationLongitude = this.addressCoordinates.end.lng
        submitData.destinationLatitude = this.addressCoordinates.end.lat
      }
      // 目标地GPS坐标由后端自动获取
      
      return submitData
    },
@@ -688,8 +980,6 @@
      this.$modal.confirm('确定要保存修改吗?').then(() => {
        this.loading = true
        const submitData = this.buildSubmitData()
        console.log('提交数据:', JSON.stringify(submitData, null, 2))
        
        updateTask(submitData).then(response => {
          this.loading = false
@@ -946,6 +1236,51 @@
          color: #333;
        }
      }
      .staff-list {
        .staff-item {
          display: flex;
          justify-content: space-between;
          align-items: center;
          padding: 20rpx;
          margin-bottom: 15rpx;
          background-color: #f8f8f8;
          border-radius: 10rpx;
          .staff-info {
            display: flex;
            align-items: center;
            gap: 10rpx;
            .staff-name {
              font-size: 28rpx;
              color: #333;
            }
            .staff-role {
              font-size: 24rpx;
              color: #999;
            }
          }
        }
        .add-staff {
          display: flex;
          align-items: center;
          justify-content: center;
          gap: 10rpx;
          padding: 20rpx;
          border: 2rpx dashed #007AFF;
          border-radius: 10rpx;
          color: #007AFF;
          font-size: 28rpx;
          cursor: pointer;
          &:active {
            background-color: #f0f8ff;
          }
        }
      }
    }
    
    .form-actions {
@@ -1011,5 +1346,132 @@
      }
    }
  }
  .staff-popup-container {
    height: 80vh;
    background-color: white;
    border-top-left-radius: 20rpx;
    border-top-right-radius: 20rpx;
    display: flex;
    flex-direction: column;
    .popup-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 20rpx 30rpx;
      border-bottom: 1rpx solid #f0f0f0;
      .popup-title {
        font-size: 32rpx;
        font-weight: bold;
        color: #333;
      }
      .close-btn {
        width: 50rpx;
        height: 50rpx;
        display: flex;
        align-items: center;
        justify-content: center;
      }
    }
    .search-bar {
      padding: 20rpx 30rpx;
      border-bottom: 1rpx solid #f0f0f0;
      .search-input {
        height: 70rpx;
        padding: 0 20rpx;
        background-color: #f5f5f5;
        border-radius: 10rpx;
        font-size: 28rpx;
      }
    }
    .filter-tabs {
      display: flex;
      padding: 20rpx 30rpx;
      gap: 20rpx;
      border-bottom: 1rpx solid #f0f0f0;
      .filter-tab {
        flex: 1;
        height: 60rpx;
        line-height: 60rpx;
        text-align: center;
        background-color: #f5f5f5;
        border-radius: 10rpx;
        font-size: 28rpx;
        color: #666;
        &.active {
          background-color: #007AFF;
          color: white;
        }
      }
    }
    .staff-list-scroll {
      flex: 1;
      padding: 20rpx 30rpx;
      .staff-list-item {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 20rpx;
        margin-bottom: 15rpx;
        background-color: #f8f8f8;
        border-radius: 10rpx;
        .staff-item-info {
          flex: 1;
          .staff-item-name {
            display: block;
            font-size: 30rpx;
            color: #333;
            margin-bottom: 8rpx;
          }
          .staff-item-dept {
            display: block;
            font-size: 24rpx;
            color: #999;
          }
        }
        .staff-item-check {
          width: 50rpx;
          display: flex;
          align-items: center;
          justify-content: center;
        }
      }
      .empty-tip {
        text-align: center;
        padding: 100rpx 0;
        color: #999;
        font-size: 28rpx;
      }
    }
    .popup-footer {
      padding: 20rpx 30rpx;
      border-top: 1rpx solid #f0f0f0;
      .confirm-btn {
        width: 100%;
        height: 80rpx;
        background-color: #007AFF;
        color: white;
        border-radius: 10rpx;
        font-size: 32rpx;
      }
    }
  }
}
</style>