wlzboy
2026-01-24 2f09efc660bf2cc94cbc5291ad25ca06fc9bdadf
app/pagesTask/create-emergency.vue
@@ -8,8 +8,149 @@
      <view class="smart-parse-btn" @click="showSmartParsePopup">
        <uni-icons type="compose" size="20" color="#007AFF"></uni-icons>
        <text>智能识别</text>
      </view>
      <view class="multi-photo-ocr-btn" @click="showMultiPhotoOCRPopup">
        <uni-icons type="images" size="20" color="#FF6B00"></uni-icons>
        <text>拍照识别</text>
      </view>
    </view>
    <!-- 多图片拍照识别弹窗 -->
    <uni-popup ref="multiPhotoOCRPopup" type="bottom" :safe-area="true">
      <view class="photo-ocr-popup">
        <view class="popup-header">
          <view class="popup-title">
            <uni-icons type="images" size="22" color="#FF6B00"></uni-icons>
            <text>拍照识别 - 知情同意书</text>
          </view>
          <view class="popup-close" @click="closeMultiPhotoOCRPopup">
            <uni-icons type="closeempty" size="24" color="#333"></uni-icons>
          </view>
        </view>
        <view class="ocr-content">
          <view class="ocr-tip">
            <uni-icons type="info" size="18" color="#FF6B00"></uni-icons>
            <text>请分别上传知情同意书的第一页和第二页,系统将自动识别并填充到表单中</text>
          </view>
          <!-- 知情同意书第一页 -->
          <view class="page-upload-section first-page">
            <view class="page-header">
              <view class="page-title">
                <view class="page-badge">
                  <uni-icons type="compose" size="18" color="#52c41a"></uni-icons>
                  <text class="page-number">1</text>
                </view>
                <view class="page-info">
                  <text class="page-main-title">知情同意书第一页</text>
                  <text class="page-sub-title">患者基本信息页</text>
                </view>
              </view>
              <!-- 只有未上传时显示+号 -->
              <view class="upload-btn green" @click="selectPage1Images" v-if="!page1Image">
                <uni-icons type="plusempty" size="30" color="#52c41a"></uni-icons>
              </view>
            </view>
            <!-- 识别字段提示 -->
            <view class="field-hint" v-if="!page1Image">
              <text>识别字段:患者姓名、性别、年龄、身份证号、诊断、转运费用、行程、开始时间、结束时间、家属签名</text>
            </view>
            <!-- 单图预览 -->
            <view class="single-image-container" v-if="page1Image">
              <image :src="page1Image" mode="aspectFit" class="preview-image"></image>
              <view class="delete-btn" @click="deletePage1Image">
                <uni-icons type="closeempty" size="20" color="#fff"></uni-icons>
              </view>
              <view class="image-status">
                <uni-icons type="checkmarkempty" size="16" color="#fff"></uni-icons>
                <text>已上传</text>
              </view>
            </view>
            <!-- 第一页识别结果 -->
            <view class="recognition-result" v-if="Object.keys(page1Fields).length > 0">
              <view class="result-title">
                <uni-icons type="checkmarkempty" size="18" color="#52c41a"></uni-icons>
                <text>识别结果</text>
                <text class="result-count">(共{{ Object.keys(page1Fields).length }}个字段)</text>
              </view>
              <view class="field-list">
                <view class="field-item" v-for="(value, key) in page1Fields" :key="key">
                  <text class="field-name">{{ key }}</text>
                  <text class="field-value">{{ value || '未识别' }}</text>
                </view>
              </view>
            </view>
          </view>
          <!-- 知情同意书第二页 -->
          <view class="page-upload-section second-page">
            <view class="page-header">
              <view class="page-title">
                <view class="page-badge">
                  <uni-icons type="compose" size="18" color="#FF6B00"></uni-icons>
                  <text class="page-number">2</text>
                </view>
                <view class="page-info">
                  <text class="page-main-title">知情同意书第二页</text>
                  <text class="page-sub-title">签名与联系信息页</text>
                </view>
              </view>
              <!-- 只有未上传时显示+号 -->
              <view class="upload-btn orange" @click="selectPage2Images" v-if="!page2Image">
                <uni-icons type="plusempty" size="30" color="#FF6B00"></uni-icons>
              </view>
            </view>
            <!-- 识别字段提示 -->
            <view class="field-hint" v-if="!page2Image">
              <text>识别字段:患者签名、签字人身份证号、签字日期、联系电话、签字人关系</text>
            </view>
            <!-- 单图预览 -->
            <view class="single-image-container" v-if="page2Image">
              <image :src="page2Image" mode="aspectFit" class="preview-image"></image>
              <view class="delete-btn" @click="deletePage2Image">
                <uni-icons type="closeempty" size="20" color="#fff"></uni-icons>
              </view>
              <view class="image-status">
                <uni-icons type="checkmarkempty" size="16" color="#fff"></uni-icons>
                <text>已上传</text>
              </view>
            </view>
            <!-- 第二页识别结果 -->
            <view class="recognition-result" v-if="Object.keys(page2Fields).length > 0">
              <view class="result-title">
                <uni-icons type="checkmarkempty" size="18" color="#FF6B00"></uni-icons>
                <text>识别结果</text>
                <text class="result-count">(共{{ Object.keys(page2Fields).length }}个字段)</text>
              </view>
              <view class="field-list">
                <view class="field-item" v-for="(value, key) in page2Fields" :key="key">
                  <text class="field-name">{{ key }}</text>
                  <text class="field-value">{{ value || '未识别' }}</text>
                </view>
              </view>
            </view>
          </view>
        </view>
        <view class="popup-footer">
          <button class="cancel-btn" @click="closeMultiPhotoOCRPopup">取消</button>
          <button
            class="confirm-btn"
            @click="applyOcrResult"
            :disabled="Object.keys(page1Fields).length === 0 && Object.keys(page2Fields).length === 0"
          >
            应用到表单
          </button>
        </view>
      </view>
    </uni-popup>
    
    <view class="form-section">
      <view class="form-item">
@@ -23,6 +164,7 @@
      </view>
        <view class="form-item">
        <OrganizationSelector 
          ref="organizationSelector"
          v-model="selectedOrganizationId"
          :required="true"
          :auto-select-user-dept="true"
@@ -59,34 +201,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 +285,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 +368,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">
@@ -321,6 +400,41 @@
        </view>
      </view>
    </uni-popup>
    <!-- 拍照识别弹窗 -->
    <uni-popup ref="photoOCRPopup" type="bottom" :safe-area="true">
      <view class="photo-ocr-popup">
        <view class="popup-header">
          <view class="popup-title">拍照识别</view>
          <view class="popup-close" @click="closePhotoOCRPopup">
            <uni-icons type="closeempty" size="24" color="#333"></uni-icons>
          </view>
        </view>
        <view class="ocr-content">
          <view class="ocr-tip">
            <uni-icons type="info" size="18" color="#007AFF"></uni-icons>
            <text>拍照或选择图片,自动识别转运单信息</text>
          </view>
          <view class="image-preview" v-if="ocrImage">
            <image :src="ocrImage" mode="aspectFit" style="width: 100%; height: 300rpx; border-radius: 10rpx;"></image>
          </view>
          <view class="ocr-actions">
            <button class="select-btn" @click="selectImage">选择图片</button>
            <button class="capture-btn" @click="captureImage">拍照</button>
          </view>
        </view>
        <view class="popup-footer">
          <button class="cancel-btn" @click="closePhotoOCRPopup">取消</button>
          <button class="confirm-btn" @click="performOCR" :disabled="ocrLoading">
            {{ ocrLoading ? '识别中...' : '开始识别' }}
          </button>
        </view>
      </view>
    </uni-popup>
  </scroll-view>
</template>
@@ -328,23 +442,28 @@
import { mapState } from 'vuex'
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 { addTask } from "@/api/task"
import { addTask, checkTaskDuplicate } from "@/api/task"
import { listAvailableVehicles, getUserBoundVehicle } from "@/api/vehicle"
import { searchHospitals, searchHospitalsByDeptRegion } from "@/api/hospital"
import { searchHospitals, searchHospitalsByDeptRegion, searchHospitalsByKeywords } from "@/api/hospital"
import DepartureSelector from './components/DepartureSelector.vue'
import { calculateDistance, baiduDistanceByAddress, baiduPlaceSuggestion } from "@/api/map"
import { calculateTianDiTuDistance } from "@/api/map"
import { listBranchUsers } from "@/api/system/user"
import { searchIcd10 } from "@/api/icd10"
import { calculateTransferPrice } from "@/api/price"
import { checkVehicleActiveTasks } from "@/api/task"
import { recognizeImage, batchRecognizeImages, DEFAULT_TRANSFER_ITEM_NAMES } from "@/api/ocr"
import config from '@/config'
import { getToken } from '@/utils/auth'
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 +473,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 +495,6 @@
      mapSelectorType: '',
      // 人员选择相关
      selectedStaff: [], // 已选择的人员列表
      allStaffList: [], // 所有人员列表
      filteredStaffList: [], // 过滤后的人员列表
      staffSearchKeyword: '', // 人员搜索关键词
      staffFilterType: 'driver', // 人员筛选类型:driver/doctor/nurse,默认选中司机
      // 病情选择相关
      selectedDiseases: [], // 已选择的病情列表
      taskForm: {
@@ -415,11 +533,24 @@
      emergencyTaskTypeOptions: [], // 任务类型选项(用于picker显示)
      documentTypes: [], // 单据类型列表
      documentTypeOptions: [], // 单据类型选项(用于picker显示)
      departmentOptions: [], // 科室字典数据
      loading: false,
      // 智能识别相关
      rawText: '',
      parseLoading: false
      parseLoading: false,
      // 拍照识别相关
      ocrImage: '',
      ocrLoading: false,
      // 多图片拍照识别相关
      multiOcrImages: [],
      multiOcrLoading: false,
      // 分页OCR识别相关
      currentOcrPage: 1, // 当前上传的页码:1=第一页,2=第二页
      page1Image: '', // 第一页图片(单图)
      page2Image: '', // 第二页图片(单图)
      page1Fields: {}, // 第一页识别结果
      page2Fields: {}, // 第二页识别结果
      // 附件临时存储(OCR图片)
      pendingAttachments: [] // 待上传的附件列表 [{ filePath: '', category: '1' }]
    }
  },
  computed: {
@@ -452,14 +583,12 @@
    this.getAvailableVehicles().then(() => {
      this.getUserBoundVehicleInfo()
    })
    this.initSelectedStaff()
    this.loadDeptStaff()
    // 加载科室字典数据
    this.loadDepartments()
    // 加载任务类型数据
    this.loadEmergencyTaskTypes()
    // 加载单据类型数据
    this.loadDocumentTypes()
    // 加载所有机构ID
    this.loadAllOrganizationIds()
  },
  methods: {
    // 获取用户绑定的车辆信息
@@ -546,20 +675,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 +722,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 +760,8 @@
      console.log('转出医院变化:', hospitalData)
      // 组件已经通过 v-model 更新了 taskForm.hospitalOut
      
      // 科室的设置由 DepartmentSelector 组件自动处理(通过 isHome 属性)
      // 如果转入地址已填写,自动计算距离
      if (this.taskForm.hospitalIn.address) {
        // 如果两个都不是"家中",使用医院距离计算
@@ -665,6 +786,8 @@
      console.log('转入医院变化:', hospitalData)
      // 组件已经通过 v-model 更新了 taskForm.hospitalIn
      
      // 科室的设置由 DepartmentSelector 组件自动处理(通过 isHome 属性)
      // 如果转出地址已填写,自动计算距离
      if (this.taskForm.hospitalOut.address) {
        // 如果两个都不是"家中",使用医院距离计算
@@ -684,182 +807,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
@@ -878,7 +851,7 @@
      
      // 调用百度地图API计算距离
      const region = this.selectedRegion || '广州'
      baiduDistanceByAddress(fromAddress, region, toAddress, region)
      calculateTianDiTuDistance(fromAddress,  toAddress)
        .then(response => {
          uni.hideLoading()
          
@@ -1025,6 +998,18 @@
        return false
      }
      
      // 如果转入医院是“家中”,必须填写地址
      if (this.taskForm.hospitalIn.name === '家中' && !this.taskForm.hospitalIn.address) {
        this.$modal.showToast('请输入转入地址(家中)')
        return false
      }
      // 如果转出医院是“家中”,必须填写地址
      if (this.taskForm.hospitalOut.name === '家中' && !this.taskForm.hospitalOut.address) {
        this.$modal.showToast('请输入转出地址(家中)')
        return false
      }
      if (!this.taskForm.transferDistance) {
        this.$modal.showToast('请输入转运公里数')
        return false
@@ -1058,6 +1043,10 @@
          conditionText = this.taskForm.patient.condition
        }
      }
      // 调试日志:检查转入医院地址
      console.log('构建提交数据 - 转入医院:', this.taskForm.hospitalIn.name)
      console.log('构建提交数据 - 转入医院地址:', this.taskForm.hospitalIn.address)
      
      const submitData = {
        taskType: 'EMERGENCY_TRANSFER',
@@ -1120,32 +1109,134 @@
      if (!this.validateForm()) {
        return
      }
      this.doSubmitTask();
    },
    checkTaskDp(){
   // 获取当前日期(格式YYYY-MM-DD)
      const today = new Date()
      const createDate = today.getFullYear() + '-' +
                        String(today.getMonth() + 1).padStart(2, '0') + '-' +
                        String(today.getDate()).padStart(2, '0')
      const phone = this.taskForm.patient.phone
      
      // 先检查是否重复
      uni.showLoading({
        title: '检查中...'
      })
      checkTaskDuplicate(phone, createDate).then(response => {
        uni.hideLoading()
        // 如果接口返回错误,说明存在重复
        if (response.code !== 200) {
          this.$modal.showToast(response.msg || '该联系电话在当天已有任务,不能重复提交')
          return
        }
        // 检查通过,继续提交
        this.doSubmitTask()
      }).catch(error => {
        uni.hideLoading()
        console.error('重复性检查失败:', error)
        this.$modal.showToast('检查失败,请重试')
      })
    },
    // 执行实际的提交操作
    doSubmitTask() {
      this.$modal.confirm('确定要保存任务吗?').then(() => {
        this.loading = true
        const submitData = this.buildSubmitData()
        
        addTask(submitData).then(response => {
          this.loading = false
          this.$modal.showToast('任务创建成功')
          
          // 延迟跳转,让用户看到成功提示
          setTimeout(() => {
            // 跳转到任务列表并触发刷新
            uni.switchTab({
              url: '/pages/task/index',
              success: () => {
                // 使用事件总线通知任务列表页面刷新
                uni.$emit('refreshTaskList')
              }
          // 获取创建的任务ID
          const taskId = response.taskId || (response.data && response.data.taskId) || null
          console.log('任务创建成功,taskId:', taskId)
          // 如果有待上传的附件(OCR图片),先上传附件
          if (taskId && this.pendingAttachments.length > 0) {
            this.uploadPendingAttachments(taskId).then(() => {
              this.$modal.showToast('任务创建成功,附件已上传')
              this.navigateToTaskList()
            }).catch(error => {
              console.error('附件上传失败:', error)
              this.$modal.showToast('任务创建成功,但附件上传失败')
              this.navigateToTaskList()
            })
          }, 1000)
          } else {
            this.$modal.showToast('任务创建成功')
            this.navigateToTaskList()
          }
        }).catch(error => {
          this.loading = false
          console.error('任务创建失败:', error)
          this.$modal.showToast('任务创建失败,请重试')
        })
      }).catch(() => {})
    },
    // 上传待上传的附件(OCR图片)
    uploadPendingAttachments(taskId) {
      console.log('开始上传附件,taskId:', taskId, '附件数量:', this.pendingAttachments.length)
      // 使用 Promise.all 并发上传所有附件
      const uploadPromises = this.pendingAttachments.map(attachment => {
        return this.uploadSingleAttachment(taskId, attachment)
      })
      return Promise.all(uploadPromises)
    },
    // 上传单个附件
    uploadSingleAttachment(taskId, attachment) {
      return new Promise((resolve, reject) => {
        uni.uploadFile({
          url: config.baseUrl + '/task/attachment/upload/' + taskId,
          filePath: attachment.filePath,
          name: 'file',
          formData: {
            'category': attachment.category
          },
          header: {
            'Authorization': 'Bearer ' + getToken()
          },
          success: function(uploadRes) {
            if (uploadRes.statusCode === 200) {
              const result = JSON.parse(uploadRes.data)
              if (result.code === 200) {
                console.log('附件上传成功:', attachment.description)
                resolve(result)
              } else {
                console.error('附件上传失败:', attachment.description, result.msg)
                reject(result)
              }
            } else {
              console.error('附件上传失败:', attachment.description, uploadRes)
              reject(uploadRes)
            }
          },
          fail: function(err) {
            console.error('附件上传失败:', attachment.description, err)
            reject(err)
          }
        })
      })
    },
    // 跳转到任务列表
    navigateToTaskList() {
      setTimeout(() => {
        // 跳转到任务列表并触发刷新
        uni.switchTab({
          url: '/pages/task/index',
          success: () => {
            // 使用事件总线通知任务列表页面刷新
            uni.$emit('refreshTaskList')
          }
        })
      }, 1000)
    },
    
   goBack() {
@@ -1160,7 +1251,7 @@
      
      // 使用uni-app的GPS定位功能
      uni.getLocation({
        type: 'gcj02', // 返回国测局坐标,适用于国内地图
        type: 'gcj02', // 返回国测局坐标,适用于国内地图 火星坐标
        success: (res) => {
          console.log('获取到GPS坐标:', res)
          const latitude = res.latitude
@@ -1263,20 +1354,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 +1507,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 +1522,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('|')
      
@@ -1507,7 +1541,7 @@
    findHospitalByName(name, type, restrictRegion = true) {
      if (!name) return Promise.resolve(null)
      const normalized = name.trim()
      // 特殊处理"家中"
      if (normalized === '家中') {
        // 查询医院库中的"家中"记录
@@ -1515,7 +1549,7 @@
        const queryPromise = restrictRegion && deptId
          ? searchHospitalsByDeptRegion('家中', deptId, 50)
          : searchHospitals('家中', null, 50)
        return queryPromise.then(res => {
          const list = res.data || []
          // 查找名称为"家中"的医院记录
@@ -1540,20 +1574,23 @@
          }
        })
      }
      // restrictRegion=false 时走全量查询;true 且有 deptId 时走区域接口
      // OCR识别后的医院名称,使用新的分词匹配接口
      const deptId = this.selectedOrganizationId || null
      const queryPromise = (restrictRegion && deptId)
        ? searchHospitalsByDeptRegion(normalized, deptId, 50)
        : searchHospitals(normalized, null, 50)
      return queryPromise.then(res => {
        const list = res.data || []
        if (!list.length) return null
        // 自动选择第一个非"家中"的区院,如果全是"家中"则选第一个
        const best = this.pickBestHospitalMatch(list, normalized)
        return best || null
      return searchHospitalsByKeywords(normalized, deptId, 50).then(res => {
        const rawData = res.data || []
        if (!rawData.length) return null
        // 提取 hospital 对象(接口返回格式:{hospital: {...}, matchScore: ...})
        const firstItem = rawData[0]
        const firstHospital = firstItem.hospital || firstItem
        console.log(`OCR识别医院"${normalized}",自动选中:${firstHospital.hospName}(匹配分数:${firstItem.matchScore || 'N/A'})`)
        return firstHospital
      }).catch(error => {
        console.error(`搜索医院"${normalized}"失败:`, error)
        return null
      })
    },
@@ -1626,7 +1663,7 @@
      })
      
      // 调用百度地图API计算距离
      baiduDistanceByAddress(fromAddress, fromCity, toAddress, toCity)
      calculateTianDiTuDistance(fromAddress, toAddress)
        .then(response => {
          uni.hideLoading()
          
@@ -1651,6 +1688,496 @@
          console.error('距离计算失败:', error)
          this.$modal.showToast('距离计算失败,请手动输入')
        })
    },
    // ==================== 拍照识别相关方法 ====================
    // 显示拍照识别弹窗
    showPhotoOCRPopup() {
      this.ocrImage = ''
      this.$refs.photoOCRPopup.open()
    },
    // 显示多图拍照识别弹窗
    showMultiPhotoOCRPopup() {
      this.page1Image = ''
      this.page2Image = ''
      this.page1Fields = {}
      this.page2Fields = {}
      this.$refs.multiPhotoOCRPopup.open()
    },
    // 关闭多图拍照识别弹窗
    closeMultiPhotoOCRPopup() {
      this.$refs.multiPhotoOCRPopup.close()
    },
    // 选择第一页图片
    selectPage1Images() {
      uni.showActionSheet({
        itemList: ['从相册选择', '拍照'],
        success: (res) => {
          if (res.tapIndex === 0) {
            this.chooseImageForPage(1, 'album')
          } else if (res.tapIndex === 1) {
            this.chooseImageForPage(1, 'camera')
          }
        }
      })
    },
    // 选择第二页图片
    selectPage2Images() {
      uni.showActionSheet({
        itemList: ['从相册选择', '拍照'],
        success: (res) => {
          if (res.tapIndex === 0) {
            this.chooseImageForPage(2, 'album')
          } else if (res.tapIndex === 1) {
            this.chooseImageForPage(2, 'camera')
          }
        }
      })
    },
    // 删除第一页图片
    deletePage1Image() {
      this.page1Image = ''
      this.page1Fields = {}
    },
    // 删除第二页图片
    deletePage2Image() {
      this.page2Image = ''
      this.page2Fields = {}
    },
    // 为指定页码选择图片(单图)
    chooseImageForPage(page, sourceType) {
      uni.chooseImage({
        count: 1, // 只选择一张图片
        sizeType: ['compressed'],
        sourceType: [sourceType],
        success: (res) => {
          const imagePath = res.tempFilePaths[0]
          if (page === 1) {
            this.page1Image = imagePath
          } else {
            this.page2Image = imagePath
          }
          // 选择完图片后,立即进行OCR识别
          this.$modal.showToast(`已选择图片,开始识别...`)
          this.recognizeSinglePage(page)
        },
        fail: (err) => {
          console.error('选择图片失败:', err)
          this.$modal.showToast('选择图片失败')
        }
      })
    },
    // 选择图片
    selectImage() {
      uni.chooseImage({
        count: 1,
        sizeType: ['compressed'],
        sourceType: ['album'],
        success: (res) => {
          this.ocrImage = res.tempFilePaths[0]
        },
        fail: (err) => {
          console.error('选择图片失败:', err)
          this.$modal.showToast('选择图片失败')
        }
      })
    },
    // 拍照
    captureImage() {
      uni.chooseImage({
        count: 1,
        sizeType: ['compressed'],
        sourceType: ['camera'],
        success: (res) => {
          this.ocrImage = res.tempFilePaths[0]
        },
        fail: (err) => {
          console.error('拍照失败:', err)
          this.$modal.showToast('拍照失败')
        }
      })
    },
    // 应用OCR识别结果到表单
    applyOcrResult() {
      // 合并两页的识别结果
      const mergedFields = { ...this.page1Fields, ...this.page2Fields }
      // 处理合并后的识别结果
      this.processMultiOCRResult(mergedFields)
      // 保存OCR图片到待上传附件列表(知情同意书,分类为'1')
      this.pendingAttachments = []
      if (this.page1Image) {
        this.pendingAttachments.push({
          filePath: this.page1Image,
          category: '1', // 知情同意书
          description: '知情同意书第一页'
        })
      }
      if (this.page2Image) {
        this.pendingAttachments.push({
          filePath: this.page2Image,
          category: '1', // 知情同意书
          description: '知情同意书第二页'
        })
      }
      console.log('保存待上传附件:', this.pendingAttachments)
      // 关闭弹窗
      this.closeMultiPhotoOCRPopup()
      // 清空图片和结果(但保留 pendingAttachments)
      this.page1Image = ''
      this.page2Image = ''
      this.page1Fields = {}
      this.page2Fields = {}
      this.$modal.showToast('已应用到表单,知情同意书将在任务创建后上传')
    },
    // 识别单个页码的图片(选择后立即识别)
    recognizeSinglePage(page) {
      const image = page === 1 ? this.page1Image : this.page2Image
      if (!image) {
        return
      }
      // 显示加载提示
      uni.showLoading({
        title: `识别第${page}页...`
      })
      // 第一页的itemNames
      const page1ItemNames = [
        "患者姓名", "性别", "年龄", "身份证号", "诊断",
        "需支付转运费用", "行程", "开始时间", "结束时间", "家属签名"
      ]
      // 第二页的itemNames
      const page2ItemNames = [
        "患者签名(手印)", "签字人身份证号码", "签字人身份证号", "日期",
        "联系电话", "本人", "签字人与患者关系"
      ]
      const itemNames = page === 1 ? page1ItemNames : page2ItemNames
      // 调用批量OCR API(传入单个图片)
      batchRecognizeImages([image], itemNames)
        .then(result => {
          uni.hideLoading()
          if (result.success && result.successCount > 0) {
            // 保存识别结果
            if (page === 1) {
              this.page1Fields = result.fields
            } else {
              this.page2Fields = result.fields
            }
            console.log(`第${page}页识别结果:`, result.fields)
            this.$modal.showToast(`第${page}页识别成功`)
          } else {
            this.$modal.showToast(`第${page}页识别失败`)
          }
        })
        .catch(error => {
          uni.hideLoading()
          console.error(`第${page}页OCR识别失败:`, error)
          this.$modal.showToast(error.msg || `第${page}页识别失败`)
        })
    },
    // 处理多图OCR识别结果
    processMultiOCRResult(fields) {
      console.log('多图OCR识别结果:', fields)
      // 提取患者姓名
      if (fields['患者姓名']) {
        this.taskForm.patient.name = fields['患者姓名'].trim()
      }
      // 提取联系人(优先患者签名,其次家属签名,最后本人)
      if (fields['患者签名(手印)']) {
        this.taskForm.patient.contact = fields['患者签名(手印)'].trim()
      } else if (fields['家属签名']) {
        this.taskForm.patient.contact = fields['家属签名'].trim()
      } else if (fields['本人']) {
        this.taskForm.patient.contact = fields['本人'].trim()
      }
      // 提取性别
      if (fields['性别']) {
        const gender = fields['性别'].trim()
        if (gender.includes('男')) {
          this.taskForm.patient.gender = 'male'
        } else if (gender.includes('女')) {
          this.taskForm.patient.gender = 'female'
        }
      }
      // 提取身份证号(优先身份证号,其次签字人身份证号码)
      const patientIdCard = fields['身份证号'] || fields['患者身份证号']
      const signerIdCard = fields['签字人身份证号码'] || fields['签字人身份证号']
      if (patientIdCard) {
        this.taskForm.patient.idCard = patientIdCard.trim()
      } else if (signerIdCard) {
        this.taskForm.patient.idCard = signerIdCard.trim()
      }
      // 提取日期(转运时间)
      if (fields['日期']) {
        const dateString = fields['日期'].trim()
        const dateFormatted = this.formatDateString(dateString)
        if (dateFormatted) {
          this.taskForm.transferTime = dateFormatted
        }
      }
      // 提取联系电话
      if (fields['联系电话']) {
        this.taskForm.patient.phone = fields['联系电话'].trim()
      }
      // 提取需支付转运费用(成交价)
      if (fields['需支付转运费用']) {
        const priceText = fields['需支付转运费用'].trim()
        const priceNumber = priceText.match(/\d+(?:\.\d{1,2})?/)
        if (priceNumber) {
          this.taskForm.price = parseFloat(priceNumber[0]).toFixed(2)
        }
      }
      // 提取诊断(病情)
      if (fields['诊断']) {
        this.taskForm.patient.condition = fields['诊断'].trim()
      }
      // 提取行程(转出医院和转入医院)
      if (fields['行程']) {
        const route = fields['行程'].trim()
        // 按"-"或"—"分割行程
        const hospitals = route.split(/[-—]/).map(h => h.trim())
        if (hospitals.length >= 2) {
          // 第一个是转出医院
          this.taskForm.hospitalOut.name = hospitals[0]
          // 第二个是转入医院
          this.taskForm.hospitalIn.name = hospitals[1]
          // 尝试从医院库中匹配并补全地址
          Promise.all([
            this.findHospitalByName(hospitals[0], 'out', false),
            this.findHospitalByName(hospitals[1], 'in', false)
          ]).then(([outHosp, inHosp]) => {
            if (outHosp) {
              this.taskForm.hospitalOut.id = outHosp.hospId
              this.taskForm.hospitalOut.name = outHosp.hospName
              if (outHosp.hospName !== '家中') {
                this.taskForm.hospitalOut.address = this.buildFullAddress(outHosp)
                this.taskForm.hospitalOut.city = outHosp.hopsCity || ''
              }
            }
            if (inHosp) {
              this.taskForm.hospitalIn.id = inHosp.hospId
              this.taskForm.hospitalIn.name = inHosp.hospName
              if (inHosp.hospName !== '家中') {
                this.taskForm.hospitalIn.address = this.buildFullAddress(inHosp)
                this.taskForm.hospitalIn.city = inHosp.hopsCity || ''
              }
            }
            // 如果两个医院地址都有,自动计算距离
            if (this.taskForm.hospitalOut.address && this.taskForm.hospitalIn.address &&
                this.taskForm.hospitalOut.name !== '家中' && this.taskForm.hospitalIn.name !== '家中') {
              this.calculateHospitalDistance()
            }
          })
        }
      }
      console.log('多图OCR结果处理完成,表单数据更新')
    },
    // 执行OCR识别
    performOCR() {
      if (!this.ocrImage) {
        this.$modal.showToast('请先选择或拍摄图片')
        return
      }
      this.ocrLoading = true
      // 使用OCR API进行识别
      recognizeImage(this.ocrImage, 'HandWriting', 'tencent', DEFAULT_TRANSFER_ITEM_NAMES)
        .then(response => {
          const ocrResult = response.data.ocrResult
          this.processOCRResult(ocrResult)
          this.$modal.showToast('OCR识别成功')
        })
        .catch(error => {
          console.error('OCR识别失败:', error)
          this.$modal.showToast(`OCR识别失败: ${error.msg || '未知错误'}`)
        })
        .finally(() => {
          this.ocrLoading = false
          this.closePhotoOCRPopup()
        })
    },
    // 处理OCR识别结果
    processOCRResult(ocrResult) {
      if (!ocrResult || !ocrResult.content) {
        console.log('OCR识别结果为空')
        return
      }
      const content = ocrResult.content
      console.log('OCR识别内容:', content)
      // 提取患者姓名
      const patientNameMatch = content.match(/患者姓名[::]?\s*([^\n,,。;;]+)/)
      if (patientNameMatch && patientNameMatch[1]) {
        this.taskForm.patient.name = patientNameMatch[1].trim()
      }
      // 提取性别
      const genderMatch = content.match(/性别[::]?\s*([^\n,,。;;]+)/)
      if (genderMatch && genderMatch[1]) {
        const gender = genderMatch[1].trim()
        if (gender.includes('男')) {
          this.taskForm.patient.gender = 'male'
        } else if (gender.includes('女')) {
          this.taskForm.patient.gender = 'female'
        }
      }
      // 提取身份证号
      const idCardMatch = content.match(/身份证号[::]?\s*([^\n,,。;;]+)/)
      const signerIdMatch = content.match(/签字人身份证号码[::]?\s*([^\n,,。;;]+)/)
      if (idCardMatch && idCardMatch[1]) {
        this.taskForm.patient.idCard = idCardMatch[1].trim()
      } else if (signerIdMatch && signerIdMatch[1]) {
        this.taskForm.patient.idCard = signerIdMatch[1].trim()
      }
      // 提取联系电话
      const phoneMatch = content.match(/联系电话[::]?\s*([^\n,,。;;]+)/)
      if (phoneMatch && phoneMatch[1]) {
        this.taskForm.patient.phone = phoneMatch[1].trim()
      }
      // 提取诊断信息
      const diagnosisMatch = content.match(/诊断[::]?\s*([^\n,,。;;]+)/)
      if (diagnosisMatch && diagnosisMatch[1]) {
        this.taskForm.patient.condition = diagnosisMatch[1].trim()
      }
      // 提取需支付转运费用(成交价)
      const priceMatch = content.match(/需支付转运费用[::]?\s*([^\n,,。;;]+)/)
      if (priceMatch && priceMatch[1]) {
        // 提取数字金额
        const priceNumber = priceMatch[1].match(/\d+(?:\.\d{1,2})?/)
        if (priceNumber) {
          this.taskForm.price = parseFloat(priceNumber[0]).toFixed(2)
        }
      }
      // 提取日期
      const dateMatch = content.match(/日期[::]?\s*([^\n,,。;;]+)/)
      if (dateMatch && dateMatch[1]) {
        const dateString = dateMatch[1].trim()
        // 尝试解析日期格式
        const dateFormatted = this.formatDateString(dateString)
        if (dateFormatted) {
          this.taskForm.transferTime = dateFormatted
        }
      }
      // 提取行程(转出医院和转入医院)
      const routeMatch = content.match(/行程[::]?\s*([^\n]+)/)
      if (routeMatch && routeMatch[1]) {
        const route = routeMatch[1].trim()
        // 按"-"分割行程,获取转出和转入医院
        const hospitals = route.split(/[-—]/).map(h => h.trim())
        if (hospitals.length >= 2) {
          // 第一个是转出医院
          this.taskForm.hospitalOut.name = hospitals[0]
          // 第二个是转入医院
          this.taskForm.hospitalIn.name = hospitals[1]
        }
      }
      // 提取家属签名或本人作为联系人
      const familyContactMatch = content.match(/(?:家属签名|本人)[::]?\s*([^\n,,。;;]+)/)
      if (familyContactMatch && familyContactMatch[1]) {
        this.taskForm.patient.contact = familyContactMatch[1].trim()
      }
      // 提取患者签名(手印)作为联系人
      const patientSignatureMatch = content.match(/患者签名(手印)[::]?\s*([^\n,,。;;]+)/)
      if (patientSignatureMatch && patientSignatureMatch[1]) {
        if (!this.taskForm.patient.contact) {
          this.taskForm.patient.contact = patientSignatureMatch[1].trim()
        }
      }
      console.log('OCR结果处理完成,表单数据更新')
    },
    // 格式化日期字符串(返回 yyyy-MM-dd HH:mm:ss 格式)
    formatDateString(dateStr) {
      // 尝试不同的日期格式
      let cleaned = dateStr.replace(/[年月]/g, '-').replace(/[日号]/g, '')
      let dateResult = ''
      // 如果是YYMMDD格式
      if (/^\d{6}$/.test(cleaned)) {
        const year = '20' + cleaned.substring(0, 2)
        const month = cleaned.substring(2, 4)
        const day = cleaned.substring(4, 6)
        dateResult = `${year}-${month}-${day}`
      }
      // 如果是YYYYMMDD格式
      else if (/^\d{8}$/.test(cleaned)) {
        const year = cleaned.substring(0, 4)
        const month = cleaned.substring(4, 6)
        const day = cleaned.substring(6, 8)
        dateResult = `${year}-${month}-${day}`
      }
      // 如果已经是合理格式,直接使用
      else if (cleaned.match(/^\d{4}[-/]\d{1,2}[-/]\d{1,2}$/)) {
        dateResult = cleaned.replace(/[//]/g, '-')
      }
      // 如果已经包含时分秒,直接返回
      else if (cleaned.match(/^\d{4}[-/]\d{1,2}[-/]\d{1,2}\s+\d{1,2}:\d{1,2}:\d{1,2}$/)) {
        return cleaned.replace(/[//]/g, '-')
      }
      else {
        dateResult = dateStr
      }
      // 如果日期格式正确,添加默认时分秒 00:00:00
      if (dateResult && dateResult.match(/^\d{4}-\d{1,2}-\d{1,2}$/)) {
        return dateResult + ' 00:00:00'
      }
      return dateResult
    }
  }
}
@@ -1686,7 +2213,9 @@
      color: #333;
    }
    
    .smart-parse-btn {
    .smart-parse-btn,
    .ocr-page-btn {
      position: relative;
      display: flex;
      flex-direction: column;
      align-items: center;
@@ -1695,7 +2224,53 @@
      
      text {
        font-size: 22rpx;
        margin-top: 4rpx;
      }
      .badge {
        position: absolute;
        top: 0;
        right: 10rpx;
        min-width: 32rpx;
        height: 32rpx;
        line-height: 32rpx;
        text-align: center;
        background-color: #ff4d4f;
        color: white;
        font-size: 20rpx;
        border-radius: 16rpx;
        padding: 0 8rpx;
      }
    }
    .smart-parse-btn {
      text {
        color: #007AFF;
      }
    }
    .ocr-page-btn:first-of-type {
      text {
        color: #52c41a;
      }
    }
    .ocr-page-btn:last-of-type {
      text {
        color: #FF6B00;
      }
    }
    .multi-photo-ocr-btn {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      padding: 10rpx 20rpx;
      text {
        font-size: 22rpx;
        color: #FF6B00;
        margin-top: 4rpx;
      }
    }
@@ -1973,172 +2548,6 @@
  }
}
// 人员选择弹窗样式
.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;
    }
  }
}
// 智能识别弹窗样式
.smart-parse-popup {
  background-color: white;
@@ -2229,4 +2638,493 @@
    }
  }
}
// 拍照识别弹窗样式
.photo-ocr-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 {
      display: flex;
      align-items: center;
      gap: 10rpx;
      font-size: 32rpx;
      font-weight: bold;
      color: #333;
      text {
        margin-left: 8rpx;
      }
    }
    .popup-close {
      padding: 10rpx;
    }
  }
  .ocr-content {
    flex: 1;
    padding: 30rpx;
    overflow-y: auto;
    .ocr-tip {
      display: flex;
      align-items: flex-start;
      padding: 20rpx;
      background-color: #f0f7ff;
      border-radius: 10rpx;
      margin-bottom: 20rpx;
      text {
        flex: 1;
        margin-left: 10rpx;
        font-size: 24rpx;
        color: #666;
        line-height: 1.6;
      }
    }
    .image-preview {
      margin-bottom: 20rpx;
      border: 1rpx solid #eee;
      border-radius: 10rpx;
      overflow: hidden;
    }
    .multi-image-preview {
      display: flex;
      flex-wrap: wrap;
      gap: 15rpx;
      margin-bottom: 20rpx;
      .image-item {
        position: relative;
        width: 200rpx;
        height: 200rpx;
        border: 1rpx solid #eee;
        border-radius: 10rpx;
        overflow: hidden;
        image {
          width: 100%;
          height: 100%;
        }
        .delete-btn {
          position: absolute;
          top: 5rpx;
          right: 5rpx;
          width: 40rpx;
          height: 40rpx;
          background-color: rgba(0, 0, 0, 0.6);
          border-radius: 50%;
          display: flex;
          align-items: center;
          justify-content: center;
        }
      }
    }
    .ocr-actions {
      display: flex;
      gap: 20rpx;
      button {
        flex: 1;
        height: 80rpx;
        border-radius: 10rpx;
        font-size: 28rpx;
      }
      .select-btn {
        background-color: #f5f5f5;
        color: #333;
      }
      .capture-btn {
        background-color: #007AFF;
        color: white;
      }
    }
    .image-count {
      text-align: center;
      margin-top: 20rpx;
      font-size: 26rpx;
      color: #FF6B00;
      font-weight: bold;
    }
    // 页面上传区域
    .page-upload-section {
      background: linear-gradient(135deg, #f9f9f9 0%, #ffffff 100%);
      border-radius: 15rpx;
      padding: 25rpx;
      margin-bottom: 25rpx;
      border: 2rpx solid #f0f0f0;
      transition: all 0.3s;
      &.first-page {
        border-left: 4rpx solid #52c41a;
        .page-badge {
          background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
          .page-number {
            color: #52c41a;
          }
        }
      }
      &.second-page {
        border-left: 4rpx solid #FF6B00;
        .page-badge {
          background: linear-gradient(135deg, #FF6B00 0%, #ff8c3a 100%);
          .page-number {
            color: #FF6B00;
          }
        }
      }
      &:last-child {
        margin-bottom: 0;
      }
      .page-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 20rpx;
        .page-title {
          display: flex;
          align-items: center;
          gap: 15rpx;
          flex: 1;
          .page-badge {
            display: flex;
            align-items: center;
            justify-content: center;
            width: 60rpx;
            height: 60rpx;
            background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
            border-radius: 50%;
            position: relative;
            .page-number {
              position: absolute;
              bottom: -2rpx;
              right: -2rpx;
              width: 24rpx;
              height: 24rpx;
              background-color: #fff;
              border-radius: 50%;
              font-size: 16rpx;
              font-weight: bold;
              color: #52c41a;
              display: flex;
              align-items: center;
              justify-content: center;
              box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
            }
          }
          .page-info {
            display: flex;
            flex-direction: column;
            gap: 4rpx;
            .page-main-title {
              font-size: 28rpx;
              font-weight: bold;
              color: #333;
            }
            .page-sub-title {
              font-size: 22rpx;
              color: #999;
            }
          }
        }
        .upload-btn {
          width: 60rpx;
          height: 60rpx;
          border-radius: 50%;
          display: flex;
          align-items: center;
          justify-content: center;
          transition: all 0.3s;
          box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
          &.green {
            border: 2rpx dashed #52c41a;
            background-color: rgba(82, 196, 26, 0.05);
          }
          &.orange {
            border: 2rpx dashed #FF6B00;
            background-color: rgba(255, 107, 0, 0.05);
          }
          &:active {
            transform: scale(0.95);
            opacity: 0.8;
          }
        }
      }
      // 识别字段提示
      .field-hint {
        background-color: #fffbe6;
        border: 1rpx solid #ffe58f;
        border-radius: 8rpx;
        padding: 15rpx;
        margin-bottom: 15rpx;
        text {
          font-size: 22rpx;
          color: #d48806;
          line-height: 1.6;
        }
      }
      .images-container {
        display: flex;
        flex-wrap: wrap;
        gap: 10rpx;
        margin-bottom: 15rpx;
        .image-item {
          position: relative;
          width: 150rpx;
          height: 150rpx;
          border: 1rpx solid #eee;
          border-radius: 10rpx;
          overflow: hidden;
          image {
            width: 100%;
            height: 100%;
          }
          .delete-btn {
            position: absolute;
            top: 5rpx;
            right: 5rpx;
            width: 35rpx;
            height: 35rpx;
            background-color: rgba(0, 0, 0, 0.6);
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
          }
        }
      }
      // 单图预览区域
      .single-image-container {
        position: relative;
        width: 100%;
        height: 400rpx;
        border: 1rpx solid #eee;
        border-radius: 10rpx;
        overflow: hidden;
        margin-bottom: 15rpx;
        background-color: #f5f5f5;
        .preview-image {
          width: 100%;
          height: 100%;
        }
        .delete-btn {
          position: absolute;
          top: 10rpx;
          right: 10rpx;
          width: 50rpx;
          height: 50rpx;
          background-color: rgba(0, 0, 0, 0.6);
          border-radius: 50%;
          display: flex;
          align-items: center;
          justify-content: center;
          z-index: 2;
          &:active {
            transform: scale(0.95);
            opacity: 0.9;
          }
        }
        // 图片状态标签
        .image-status {
          position: absolute;
          bottom: 10rpx;
          left: 10rpx;
          display: flex;
          align-items: center;
          gap: 5rpx;
          padding: 8rpx 15rpx;
          background-color: rgba(82, 196, 26, 0.9);
          border-radius: 20rpx;
          z-index: 2;
          text {
            font-size: 22rpx;
            color: #fff;
            font-weight: 500;
          }
        }
      }
      .recognition-result {
        background-color: white;
        border-radius: 10rpx;
        padding: 15rpx;
        border: 1rpx solid #e0e0e0;
        .result-title {
          display: flex;
          align-items: center;
          margin-bottom: 15rpx;
          padding-bottom: 10rpx;
          border-bottom: 1rpx solid #f0f0f0;
          text {
            margin-left: 8rpx;
            font-size: 26rpx;
            font-weight: bold;
            color: #333;
          }
          .result-count {
            margin-left: 8rpx;
            font-size: 22rpx;
            color: #999;
            font-weight: normal;
          }
        }
        .field-list {
          .field-item {
            display: flex;
            padding: 10rpx 0;
            border-bottom: 1rpx solid #f9f9f9;
            &:last-child {
              border-bottom: none;
            }
            .field-name {
              min-width: 150rpx;
              font-size: 24rpx;
              color: #666;
              font-weight: 500;
              &::after {
                content: ':';
                margin-left: 4rpx;
              }
            }
            .field-value {
              flex: 1;
              font-size: 24rpx;
              color: #333;
              word-break: break-all;
            }
          }
        }
      }
    }
    .page-indicator {
      display: flex;
      justify-content: center;
      gap: 30rpx;
      margin-top: 30rpx;
      .page-dot {
        padding: 15rpx 30rpx;
        border: 2rpx solid #ddd;
        border-radius: 30rpx;
        font-size: 26rpx;
        color: #999;
        background-color: #f5f5f5;
        transition: all 0.3s;
        &.active {
          border-color: #007AFF;
          background-color: #007AFF;
          color: white;
        }
        &.completed {
          border-color: #52c41a;
          color: #52c41a;
          &.active {
            background-color: #007AFF;
            border-color: #007AFF;
            color: white;
          }
        }
      }
    }
  }
  .popup-footer {
    display: flex;
    padding: 20rpx 30rpx;
    border-top: 1rpx solid #f0f0f0;
    gap: 20rpx;
    flex-shrink: 0;
    flex-wrap: wrap;
    button {
      flex: 1;
      min-width: 160rpx;
      height: 80rpx;
      border-radius: 10rpx;
      font-size: 30rpx;
    }
    .cancel-btn {
      background-color: #f5f5f5;
      color: #666;
    }
    .next-btn,
    .prev-btn {
      background-color: #52c41a;
      color: white;
    }
    .confirm-btn {
      background-color: #007AFF;
      color: white;
      &[disabled] {
        background-color: #ccc;
        color: #999;
      }
    }
  }
}
</style>