wlzboy
4 天以前 c098f1e3a3e052aa3d65584aae6dc003a70d75ad
app/pagesTask/create-emergency.vue
@@ -23,6 +23,7 @@
      </view>
        <view class="form-item">
        <OrganizationSelector 
          ref="organizationSelector"
          v-model="selectedOrganizationId"
          :required="true"
          :auto-select-user-dept="true"
@@ -59,34 +60,15 @@
          </view>
        </picker>
      </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
              v-if="index > 0"
              type="closeempty"
              size="20"
              color="#ff4d4f"
              @click="removeStaff(index)"
            ></uni-icons>
            <uni-icons
              v-else
              type="checkmarkempty"
              size="20"
              color="#007AFF"
            ></uni-icons>
          </view>
          <view class="add-staff" @click="showStaffSelector">
            <uni-icons type="plusempty" size="20" color="#007AFF"></uni-icons>
            <text>添加人员</text>
          </view>
        </view>
      </view>
      <StaffSelector
        v-model="selectedStaff"
        label="执行任务人员"
        :required="false"
        :auto-add-current-user="true"
        :current-user-removable="true"
        :branch-dept-ids="allOrganizationIds"
        @change="onStaffChange"
      />
      
    
      
@@ -162,28 +144,60 @@
        label="医院名称"
        address-label="转出地址"
        :required="true"
        :department-required="true"
        :show-department="false"
        v-model="taskForm.hospitalOut"
        :dept-id="selectedOrganizationId"
        :region="selectedRegion"
        :department-options="departmentOptions"
        @change="onHospitalOutChange"
        @address-selected="onHospitalOutAddressSelected"
      />
      <DepartmentSelector
        label="转出科室"
        :required="true"
        v-model="taskForm.hospitalOut.department"
        :department-id="taskForm.hospitalOut.departmentId"
        :is-home="taskForm.hospitalOut.name === '家中'"
        @change="onHospitalOutDepartmentChange"
      />
      <view class="form-item">
        <view class="form-label">床号</view>
        <input
          class="form-input"
          placeholder="请输入床号"
          v-model="taskForm.hospitalOut.bedNumber"
        />
      </view>
      
      <view class="form-section-title">转入医院信息</view>
      <HospitalSelector
        label="医院名称"
        address-label="转入地址"
        :required="true"
        :department-required="true"
        :show-department="false"
        v-model="taskForm.hospitalIn"
        :dept-id="selectedOrganizationId"
        :region="selectedRegion"
        :department-options="departmentOptions"
        @change="onHospitalInChange"
        @address-selected="onHospitalInAddressSelected"
      />
      <DepartmentSelector
        label="转入科室"
        :required="true"
        v-model="taskForm.hospitalIn.department"
        :department-id="taskForm.hospitalIn.departmentId"
        :is-home="taskForm.hospitalIn.name === '家中'"
        @change="onHospitalInDepartmentChange"
      />
      <view class="form-item">
        <view class="form-label">床号</view>
        <input
          class="form-input"
          placeholder="请输入床号"
          v-model="taskForm.hospitalIn.bedNumber"
        />
      </view>
      
      <view class="form-item">
        <view class="form-label required">转运公里数</view>
@@ -213,82 +227,6 @@
      </view>
    </view>
    
    <!-- 人员选择弹窗 -->
    <uni-popup ref="staffPopup" type="bottom" :safe-area="true">
      <view class="staff-selector-popup">
        <view class="popup-header">
          <view class="popup-title">选择执行人员</view>
          <view class="popup-close" @click="closeStaffSelector">
            <uni-icons type="closeempty" size="24" color="#333"></uni-icons>
          </view>
        </view>
        <view class="search-box">
          <uni-icons type="search" size="18" color="#999"></uni-icons>
          <input
            class="search-input"
            placeholder="搜索姓名、手机号"
            v-model="staffSearchKeyword"
            @input="onStaffSearch"
          />
        </view>
        <view class="staff-filter">
          <view
            class="filter-item"
            :class="{ active: staffFilterType === 'driver' }"
            @click="filterStaff('driver')"
          >司机</view>
          <view
            class="filter-item"
            :class="{ active: staffFilterType === 'doctor' }"
            @click="filterStaff('doctor')"
          >医生</view>
          <view
            class="filter-item"
            :class="{ active: staffFilterType === 'nurse' }"
            @click="filterStaff('nurse')"
          >护士</view>
        </view>
        <scroll-view class="staff-list-popup" scroll-y="true">
          <view
            class="staff-item-popup"
            v-for="staff in filteredStaffList"
            :key="staff.userId"
            @click="toggleStaffSelection(staff)"
          >
            <view class="staff-info">
              <view class="staff-name-row">
                <text class="staff-name">{{ staff.nickName }}</text>
                <text class="staff-phone">{{ staff.phonenumber }}</text>
              </view>
              <view class="staff-detail-row">
                <text class="staff-dept">{{ staff.deptName }}</text>
                <text class="staff-post">{{ staff.postName || staff.roleName  || '未知职位' }}</text>
              </view>
            </view>
            <uni-icons
              v-if="isStaffSelected(staff.userId)"
              type="checkmarkempty"
              size="24"
              color="#007AFF"
            ></uni-icons>
            <view v-else class="checkbox-empty"></view>
          </view>
          <view class="no-data" v-if="filteredStaffList.length === 0">
            <uni-icons type="info" size="40" color="#ccc"></uni-icons>
            <text>暂无人员数据</text>
          </view>
        </scroll-view>
        <view class="popup-footer">
          <button class="cancel-btn" @click="closeStaffSelector">取消</button>
          <button class="confirm-btn" @click="confirmStaffSelection">确定(已选{{ selectedStaff.length }})</button>
        </view>
      </view>
    </uni-popup>
    
   <!-- 智能识别弹窗 -->
    <uni-popup ref="smartParsePopup" type="bottom" :safe-area="true">
@@ -339,12 +277,14 @@
import { checkVehicleActiveTasks } from "@/api/task"
import { getDicts } from "@/api/dict"
import { getServiceOrdAreaTypes, getServiceOrderTypes, getHospitalDepartments } from "@/api/dictionary"
import { getServiceOrdAreaTypes, getServiceOrderTypes } from "@/api/dictionary"
import { listBranchCompany, getDept } from "@/api/system/dept"
import MapSelector from './components/map-selector.vue'
import OrganizationSelector from './components/OrganizationSelector.vue'
import HospitalSelector from './components/HospitalSelector.vue'
import DiseaseSelector from './components/DiseaseSelector.vue'
import StaffSelector from './components/StaffSelector.vue'
import DepartmentSelector from './components/DepartmentSelector.vue'
export default {
  components: {
@@ -354,13 +294,16 @@
    OrganizationSelector,
    HospitalSelector,
    DiseaseSelector,
    DepartureSelector
    DepartureSelector,
    StaffSelector,
    DepartmentSelector
  },
  data() {
    return {
      selectedVehicle: '',
      selectedVehicleId: null,
      selectedOrganizationId: null, // 归属机构ID(部门ID)
      selectedOrganizationId: null, // 当前选中的归属机构ID
      allOrganizationIds: [], // 所有可选机构ID数组
      selectedOrganizationServiceOrderClass: '', // 归属机构的服务单编码
      selectedRegion: '', // 从归属机构中提取的地域信息(如:广州、深圳等)
      departureAddress: '', // 出发地地址
@@ -373,10 +316,6 @@
      mapSelectorType: '',
      // 人员选择相关
      selectedStaff: [], // 已选择的人员列表
      allStaffList: [], // 所有人员列表
      filteredStaffList: [], // 过滤后的人员列表
      staffSearchKeyword: '', // 人员搜索关键词
      staffFilterType: 'driver', // 人员筛选类型:driver/doctor/nurse,默认选中司机
      // 病情选择相关
      selectedDiseases: [], // 已选择的病情列表
      taskForm: {
@@ -415,7 +354,6 @@
      emergencyTaskTypeOptions: [], // 任务类型选项(用于picker显示)
      documentTypes: [], // 单据类型列表
      documentTypeOptions: [], // 单据类型选项(用于picker显示)
      departmentOptions: [], // 科室字典数据
      loading: false,
      // 智能识别相关
      rawText: '',
@@ -452,14 +390,12 @@
    this.getAvailableVehicles().then(() => {
      this.getUserBoundVehicleInfo()
    })
    this.initSelectedStaff()
    this.loadDeptStaff()
    // 加载科室字典数据
    this.loadDepartments()
    // 加载任务类型数据
    this.loadEmergencyTaskTypes()
    // 加载单据类型数据
    this.loadDocumentTypes()
    // 加载所有机构ID
    this.loadAllOrganizationIds()
  },
  methods: {
    // 获取用户绑定的车辆信息
@@ -546,20 +482,21 @@
      return region.replace(/(分公司|总公司|总部)$/g, '').trim();
   },
    
    // 加载科室数据(从 SQL Server 动态加载)
    loadDepartments() {
      getHospitalDepartments().then(response => {
        const list = response.data || [];
        this.departmentOptions = list.map(item => ({
          id: item.vID,
          text: item.vtext,
          dictValue: item.vtext  // 为了保持兼容性,保留dictValue字段
        }));
        // console.log('科室数据加载成功:', this.departmentOptions);
      }).catch(error => {
        console.error('加载科室数据失败:', error)
        this.departmentOptions = []
      })
    // 加载所有机构ID
    loadAllOrganizationIds() {
      // 通过 OrganizationSelector 组件获取所有机构
      const orgSelector = this.$refs.organizationSelector
      if (orgSelector) {
        orgSelector.reload().then(organizations => {
          this.allOrganizationIds = organizations.map(org => org.deptId)
          console.log('所有机构ID:', this.allOrganizationIds)
        })
      } else {
        // 如果组件还未挂载,稍后重试
        setTimeout(() => {
          this.loadAllOrganizationIds()
        }, 100)
      }
    },
    
    // 加载任务类型数据(从 SQL Server)
@@ -592,18 +529,7 @@
      this.selectedEmergencyTaskType = selected.text
      this.selectedEmergencyTaskTypeId = selected.id
    },
    getUserTypeName(staffType){
      switch(staffType){
        case "nurse":
          return "护士";
        case "doctor":
          return "医生";
        case "driver":
          return "司机";
        default:
          return "司机";
      }
    },
    
    // 加载单据类型数据
    loadDocumentTypes() {
@@ -641,6 +567,12 @@
      console.log('转出医院变化:', hospitalData)
      // 组件已经通过 v-model 更新了 taskForm.hospitalOut
      
      // 如果选择的是"家中",自动设置科室为"其它"
      if (hospitalData.name === '家中') {
        this.taskForm.hospitalOut.department = '其它'
        this.taskForm.hospitalOut.departmentId = null
      }
      // 如果转入地址已填写,自动计算距离
      if (this.taskForm.hospitalIn.address) {
        // 如果两个都不是"家中",使用医院距离计算
@@ -665,6 +597,12 @@
      console.log('转入医院变化:', hospitalData)
      // 组件已经通过 v-model 更新了 taskForm.hospitalIn
      
      // 如果选择的是"家中",自动设置科室为"其它"
      if (hospitalData.name === '家中') {
        this.taskForm.hospitalIn.department = '其它'
        this.taskForm.hospitalIn.departmentId = null
      }
      // 如果转出地址已填写,自动计算距离
      if (this.taskForm.hospitalOut.address) {
        // 如果两个都不是"家中",使用医院距离计算
@@ -684,182 +622,32 @@
      }
    },
    
    // 初始化选中的人员(默认包含当前用户)
    initSelectedStaff() {
      // 构建当前用户对象,包含完整的角色信息
      const currentUserStaff = {
        userId: this.currentUser.userId,
        nickName: this.currentUser.nickName,
        phonenumber: this.currentUser.phonenumber,
        postName: this.currentUser.position,
        deptId: this.currentUser.deptId,
        posts: this.currentUser.posts || [],
        roles: this.currentUser.roles || [],
        dept: this.currentUser.dept || null
      }
      // 为当前用户设置角色类型
      currentUserStaff.type = this.getUserType(currentUserStaff)
      this.selectedStaff = [currentUserStaff]
    },
    // 加载当前用户所在分公司的所有人员
    loadDeptStaff() {
      console.log('开始加载人员列表')
      // 调用新接口,自动根据当前用户的oaOrderClass获取分公司下的用户
      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 || ''
      // console.log("获取用户类型:", postName, roleName,user)
      // 判断是否为司机
      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'
    },
    // 显示人员选择弹窗
    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
      // console.log('最终过滤结果:', this.filteredStaffList)
    },
    // 切换人员选中状态
    toggleStaffSelection(staff) {
      const index = this.selectedStaff.findIndex(s => s.userId === staff.userId)
      if (index > -1) {
        // 如果是第一个(当前用户),不允许移除
        if (index === 0) {
          this.$modal.showToast('当前用户不能移除')
          return
        }
        // 已选中,移除
        this.selectedStaff.splice(index, 1)
    // 转出科室变化
    onHospitalOutDepartmentChange(data) {
      if (data && typeof data === 'object') {
        this.taskForm.hospitalOut.department = data.department
        this.taskForm.hospitalOut.departmentId = data.departmentId
      } else {
        // 未选中,添加
        this.selectedStaff.push(staff)
        this.taskForm.hospitalOut.department = data
        this.taskForm.hospitalOut.departmentId = null
      }
    },
    
    // 判断人员是否已选中
    isStaffSelected(userId) {
      return this.selectedStaff.some(staff => staff.userId === userId)
    },
    // 确认人员选择
    confirmStaffSelection() {
      if (this.selectedStaff.length === 0) {
        this.$modal.showToast('请至少选择一名人员')
        return
    // 转入科室变化
    onHospitalInDepartmentChange(data) {
      if (data && typeof data === 'object') {
        this.taskForm.hospitalIn.department = data.department
        this.taskForm.hospitalIn.departmentId = data.departmentId
      } else {
        this.taskForm.hospitalIn.department = data
        this.taskForm.hospitalIn.departmentId = null
      }
      this.closeStaffSelector()
    },
    
    // 移除人员
    removeStaff(index) {
      if (index === 0) {
        this.$modal.showToast('当前用户不能移除')
        return
      }
      this.selectedStaff.splice(index, 1)
    },
    addStaff() {
      this.showStaffSelector()
    // 人员变化事件
    onStaffChange(staff) {
      console.log('选中人员变化:', staff)
      // 组件已经通过 v-model 更新了 selectedStaff
    },
    calculateDistanceByManualAddress() {
      const fromAddress = this.taskForm.hospitalOut.address
@@ -1263,20 +1051,14 @@
      if (result.phone) this.taskForm.patient.phone = result.phone
      if (result.price) this.taskForm.price = result.price
      
      // 应用科室信息(匹配 departmentOptions 中的数据)
      // 应用科室信息(通过 DepartmentSelector 组件处理)
      if (result.departmentOut) {
        const deptOut = this.matchDepartment(result.departmentOut)
        if (deptOut) {
          this.taskForm.hospitalOut.department = deptOut.text
          this.taskForm.hospitalOut.departmentId = deptOut.id
        }
        this.taskForm.hospitalOut.department = result.departmentOut
        // 科室ID会在 DepartmentSelector 组件中自动匹配
      }
      if (result.departmentIn) {
        const deptIn = this.matchDepartment(result.departmentIn)
        if (deptIn) {
          this.taskForm.hospitalIn.department = deptIn.text
          this.taskForm.hospitalIn.departmentId = deptIn.id
        }
        this.taskForm.hospitalIn.department = result.departmentIn
        // 科室ID会在 DepartmentSelector 组件中自动匹配
      }
      // 处理医院名称 → 精确匹配医院并补全地址与ID(不限制分公司区域)
@@ -1422,40 +1204,6 @@
      return ''
    },
    
    // 匹配科室(优先使用 departmentOptions 中的数据)
    matchDepartment(deptName) {
      if (!deptName || !this.departmentOptions || this.departmentOptions.length === 0) {
        return null
      }
      const normalized = deptName.trim().toUpperCase()
      // 1. 精确匹配(不区分大小写)
      let matched = this.departmentOptions.find(d =>
        d.text.toUpperCase() === normalized
      )
      if (matched) return matched
      // 2. 包含匹配(科室名包含识别到的关键词)
      matched = this.departmentOptions.find(d =>
        d.text.toUpperCase().includes(normalized) ||
        normalized.includes(d.text.toUpperCase())
      )
      if (matched) return matched
      // 3. 模糊匹配(去除"科"、"室"等后缀再匹配)
      const cleanedInput = normalized.replace(/[科室部]/g, '')
      matched = this.departmentOptions.find(d => {
        const cleanedDept = d.text.toUpperCase().replace(/[科室部]/g, '')
        return cleanedDept === cleanedInput ||
               cleanedDept.includes(cleanedInput) ||
               cleanedInput.includes(cleanedDept)
      })
      if (matched) return matched
      return null
    },
    // 提取科室信息
    extractDepartment(text, type) {
      // 常见科室关键词(作为兜底方案)
@@ -1471,24 +1219,7 @@
        '检验科', '病理科', '药剂科', '营养科'
      ]
      
      // 优先尝试从 departmentOptions 中匹配
      if (this.departmentOptions && this.departmentOptions.length > 0) {
        // 构建 departmentOptions 的匹配模式(按长度倒序)
        const optionTexts = this.departmentOptions.map(d => d.text).sort((a, b) => b.length - a.length)
        const optionPattern = optionTexts.map(t => t.replace(/[()()]/g, '\\$&')).join('|')
        if (optionPattern) {
          const regex = new RegExp(`(${optionPattern})`, 'gi')
          const matches = text.match(regex)
          if (matches && matches.length > 0) {
            // 如果是转出,取第一个科室;如果是转入,取最后一个科室
            return type === 'out' ? matches[0] : matches[matches.length - 1]
          }
        }
      }
      // 兜底:使用默认科室列表匹配
      // 使用默认科室列表匹配
      const sortedDepts = departments.sort((a, b) => b.length - a.length)
      const deptPattern = sortedDepts.join('|')
      
@@ -1969,172 +1700,6 @@
          color: #999;
        }
      }
    }
  }
}
// 人员选择弹窗样式
.staff-selector-popup {
  background-color: white;
  border-radius: 20rpx 20rpx 0 0;
  max-height: 80vh;
  display: flex;
  flex-direction: column;
  .popup-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 30rpx;
    border-bottom: 1rpx solid #f0f0f0;
    flex-shrink: 0;
    .popup-title {
      font-size: 32rpx;
      font-weight: bold;
      color: #333;
    }
    .popup-close {
      padding: 10rpx;
    }
  }
  .search-box {
    display: flex;
    align-items: center;
    margin: 20rpx 30rpx;
    padding: 15rpx 20rpx;
    background-color: #f5f5f5;
    border-radius: 10rpx;
    flex-shrink: 0;
    .search-input {
      flex: 1;
      margin-left: 10rpx;
      font-size: 28rpx;
    }
  }
  .staff-filter {
    display: flex;
    padding: 0 30rpx 20rpx;
    gap: 20rpx;
    flex-shrink: 0;
    .filter-item {
      flex: 1;
      text-align: center;
      padding: 15rpx 0;
      background-color: #f5f5f5;
      border-radius: 10rpx;
      font-size: 28rpx;
      color: #666;
      &.active {
        background-color: #007AFF;
        color: white;
      }
    }
  }
  .staff-list-popup {
    flex: 1;
    overflow-y: auto;
    padding: 0 30rpx;
    .staff-item-popup {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 25rpx 20rpx;
      border-bottom: 1rpx solid #f0f0f0;
      &:active {
        background-color: #f5f5f5;
      }
      .staff-info {
        flex: 1;
        .staff-name-row {
          display: flex;
          align-items: center;
          margin-bottom: 10rpx;
          .staff-name {
            font-size: 30rpx;
            font-weight: bold;
            color: #333;
            margin-right: 20rpx;
          }
          .staff-phone {
            font-size: 24rpx;
            color: #999;
          }
        }
        .staff-detail-row {
          display: flex;
          align-items: center;
          .staff-dept {
            font-size: 24rpx;
            color: #666;
            margin-right: 20rpx;
          }
          .staff-post {
            font-size: 24rpx;
            color: #007AFF;
          }
        }
      }
      .checkbox-empty {
        width: 40rpx;
        height: 40rpx;
        border: 2rpx solid #ddd;
        border-radius: 50%;
      }
    }
    .no-data {
      text-align: center;
      padding: 100rpx 0;
      color: #999;
      text {
        display: block;
        margin-top: 20rpx;
        font-size: 28rpx;
      }
    }
  }
  .popup-footer {
    display: flex;
    padding: 20rpx 30rpx;
    border-top: 1rpx solid #f0f0f0;
    gap: 20rpx;
    flex-shrink: 0;
    button {
      flex: 1;
      height: 80rpx;
      border-radius: 10rpx;
      font-size: 30rpx;
    }
    .cancel-btn {
      background-color: #f5f5f5;
      color: #666;
    }
    .confirm-btn {
      background-color: #007AFF;
      color: white;
    }
  }
}