| | |
| | | <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"> |
| | |
| | | </view> |
| | | <view class="form-item"> |
| | | <OrganizationSelector |
| | | ref="organizationSelector" |
| | | v-model="selectedOrganizationId" |
| | | :required="true" |
| | | :auto-select-user-dept="true" |
| | |
| | | </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" |
| | | /> |
| | | |
| | | |
| | | |
| | |
| | | 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> |
| | |
| | | </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"> |
| | |
| | | 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 { 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: { |
| | |
| | | OrganizationSelector, |
| | | HospitalSelector, |
| | | DiseaseSelector, |
| | | DepartureSelector |
| | | DepartureSelector, |
| | | StaffSelector, |
| | | DepartmentSelector |
| | | }, |
| | | data() { |
| | | return { |
| | | selectedVehicle: '', |
| | | selectedVehicleId: null, |
| | | selectedOrganizationId: null, // 归属机构ID(部门ID) |
| | | selectedOrganizationId: null, // 当前选中的归属机构ID |
| | | allOrganizationIds: [], // 所有可选机构ID数组 |
| | | selectedOrganizationServiceOrderClass: '', // 归属机构的服务单编码 |
| | | selectedRegion: '', // 从归属机构中提取的地域信息(如:广州、深圳等) |
| | | departureAddress: '', // 出发地地址 |
| | |
| | | mapSelectorType: '', |
| | | // 人员选择相关 |
| | | selectedStaff: [], // 已选择的人员列表 |
| | | allStaffList: [], // 所有人员列表 |
| | | filteredStaffList: [], // 过滤后的人员列表 |
| | | staffSearchKeyword: '', // 人员搜索关键词 |
| | | staffFilterType: 'driver', // 人员筛选类型:driver/doctor/nurse,默认选中司机 |
| | | // 病情选择相关 |
| | | selectedDiseases: [], // 已选择的病情列表 |
| | | taskForm: { |
| | |
| | | emergencyTaskTypeOptions: [], // 任务类型选项(用于picker显示) |
| | | documentTypes: [], // 单据类型列表 |
| | | documentTypeOptions: [], // 单据类型选项(用于picker显示) |
| | | departmentOptions: [], // 科室字典数据 |
| | | loading: false, |
| | | // 智能识别相关 |
| | | rawText: '', |
| | | parseLoading: false |
| | | parseLoading: false, |
| | | // 多图片拍照识别相关 |
| | | multiOcrImages: [], |
| | | multiOcrLoading: false, |
| | | // 分页OCR识别相关 |
| | | currentOcrPage: 1, // 当前上传的页码:1=第一页,2=第二页 |
| | | page1Image: '', // 第一页图片(单图) |
| | | page2Image: '', // 第二页图片(单图) |
| | | page1Fields: {}, // 第一页识别结果 |
| | | page2Fields: {}, // 第二页识别结果 |
| | | // 附件临时存储(OCR图片) |
| | | pendingAttachments: [] // 待上传的附件列表 [{ filePath: '', category: '1' }] |
| | | } |
| | | }, |
| | | computed: { |
| | |
| | | this.getAvailableVehicles().then(() => { |
| | | this.getUserBoundVehicleInfo() |
| | | }) |
| | | this.initSelectedStaff() |
| | | this.loadDeptStaff() |
| | | // 加载科室字典数据 |
| | | this.loadDepartments() |
| | | // 加载任务类型数据 |
| | | this.loadEmergencyTaskTypes() |
| | | // 加载单据类型数据 |
| | | this.loadDocumentTypes() |
| | | // 加载所有机构ID |
| | | this.loadAllOrganizationIds() |
| | | }, |
| | | methods: { |
| | | // 获取用户绑定的车辆信息 |
| | |
| | | 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) |
| | |
| | | this.selectedEmergencyTaskType = selected.text |
| | | this.selectedEmergencyTaskTypeId = selected.id |
| | | }, |
| | | getUserTypeName(staffType){ |
| | | switch(staffType){ |
| | | case "nurse": |
| | | return "护士"; |
| | | case "doctor": |
| | | return "医生"; |
| | | case "driver": |
| | | return "司机"; |
| | | default: |
| | | return "司机"; |
| | | } |
| | | }, |
| | | |
| | | |
| | | // 加载单据类型数据 |
| | | loadDocumentTypes() { |
| | |
| | | console.log('转出医院变化:', hospitalData) |
| | | // 组件已经通过 v-model 更新了 taskForm.hospitalOut |
| | | |
| | | // 科室的设置由 DepartmentSelector 组件自动处理(通过 isHome 属性) |
| | | |
| | | // 如果转入地址已填写,自动计算距离 |
| | | if (this.taskForm.hospitalIn.address) { |
| | | // 如果两个都不是"家中",使用医院距离计算 |
| | |
| | | console.log('转入医院变化:', hospitalData) |
| | | // 组件已经通过 v-model 更新了 taskForm.hospitalIn |
| | | |
| | | // 科室的设置由 DepartmentSelector 组件自动处理(通过 isHome 属性) |
| | | |
| | | // 如果转出地址已填写,自动计算距离 |
| | | if (this.taskForm.hospitalOut.address) { |
| | | // 如果两个都不是"家中",使用医院距离计算 |
| | |
| | | } |
| | | }, |
| | | |
| | | // 初始化选中的人员(默认包含当前用户) |
| | | 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 |
| | |
| | | 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 |
| | |
| | | conditionText = this.taskForm.patient.condition |
| | | } |
| | | } |
| | | |
| | | // 调试日志:检查转入医院地址 |
| | | console.log('构建提交数据 - 转入医院:', this.taskForm.hospitalIn.name) |
| | | console.log('构建提交数据 - 转入医院地址:', this.taskForm.hospitalIn.address) |
| | | |
| | | const submitData = { |
| | | taskType: 'EMERGENCY_TRANSFER', |
| | |
| | | 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() { |
| | |
| | | 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(不限制分公司区域) |
| | |
| | | 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) { |
| | | // 常见科室关键词(作为兜底方案) |
| | |
| | | '检验科', '病理科', '药剂科', '营养科' |
| | | ] |
| | | |
| | | // 优先尝试从 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('|') |
| | | |
| | |
| | | findHospitalByName(name, type, restrictRegion = true) { |
| | | if (!name) return Promise.resolve(null) |
| | | const normalized = name.trim() |
| | | |
| | | |
| | | // 特殊处理"家中" |
| | | if (normalized === '家中') { |
| | | // 查询医院库中的"家中"记录 |
| | |
| | | const queryPromise = restrictRegion && deptId |
| | | ? searchHospitalsByDeptRegion('家中', deptId, 50) |
| | | : searchHospitals('家中', null, 50) |
| | | |
| | | |
| | | return queryPromise.then(res => { |
| | | const list = res.data || [] |
| | | // 查找名称为"家中"的医院记录 |
| | |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // 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 |
| | | }) |
| | | }, |
| | | |
| | |
| | | console.error('距离计算失败:', error) |
| | | this.$modal.showToast('距离计算失败,请手动输入') |
| | | }) |
| | | }, |
| | | |
| | | // ==================== 拍照识别相关方法 ==================== |
| | | |
| | | // 显示多图拍照识别弹窗 |
| | | 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('选择图片失败') |
| | | } |
| | | }) |
| | | }, |
| | | |
| | | // 应用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) |
| | | } |
| | | } |
| | | |
| | | // 提取诊断(病情)(限制10个字符以内,避免误识别) |
| | | if (fields['诊断']) { |
| | | const diagnosis = fields['诊断'].trim() |
| | | // 只有当10个字符以内的诊断信息才填入表单 |
| | | if (diagnosis.length <= 10) { |
| | | this.taskForm.patient.condition = diagnosis |
| | | } else { |
| | | console.log('诊断信息过长,可能为误识别,已忽略:', diagnosis) |
| | | } |
| | | } |
| | | |
| | | // 提取行程(转出医院和转入医院) |
| | | if (fields['行程']) { |
| | | const route = fields['行程'].trim() |
| | | console.log('【行程】OCR识别的行程信息:', route) |
| | | |
| | | // 按多种分隔符分割行程:-、—、---、-->、→、空格等 |
| | | const hospitals = route.split(/[-—]{1,3}|-->|→|\s{2,}/).map(h => h.trim()).filter(h => h.length > 0) |
| | | console.log('【行程】分割后的医院列表:', hospitals) |
| | | |
| | | if (hospitals.length >= 2) { |
| | | // 第一个是转出医院 |
| | | const outHospital = hospitals[0] |
| | | this.taskForm.hospitalOut.name = outHospital |
| | | |
| | | // 如枟转出医院是“家中”,提取后面的详细地址 |
| | | if (outHospital === '家中' && hospitals.length >= 3) { |
| | | // 将剩余部分作为详细地址 |
| | | const detailAddress = hospitals.slice(1, hospitals.length - 1).join(' ') |
| | | if (detailAddress) { |
| | | this.taskForm.hospitalOut.address = detailAddress |
| | | console.log('【行程】转出家中地址:', detailAddress) |
| | | } |
| | | // 最后一个是转入医院 |
| | | this.taskForm.hospitalIn.name = hospitals[hospitals.length - 1] |
| | | console.log('【行程】转入医院:', hospitals[hospitals.length - 1]) |
| | | } else { |
| | | // 第二个是转入医院 |
| | | const inHospital = hospitals[1] |
| | | this.taskForm.hospitalIn.name = inHospital |
| | | |
| | | // 如果转入医院是“家中”,提取后面的详细地址 |
| | | if (inHospital === '家中' && hospitals.length >= 3) { |
| | | // 将剩余部分作为详细地址 |
| | | const detailAddress = hospitals.slice(2).join(' ') |
| | | if (detailAddress) { |
| | | this.taskForm.hospitalIn.address = detailAddress |
| | | console.log('【行程】转入家中地址:', detailAddress) |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 尝试从医院库中匹配并补全地址(只在非“家中”时) |
| | | const outHospitalName = this.taskForm.hospitalOut.name |
| | | const inHospitalName = this.taskForm.hospitalIn.name |
| | | |
| | | Promise.all([ |
| | | outHospitalName !== '家中' ? this.findHospitalByName(outHospitalName, 'out', false) : Promise.resolve(null), |
| | | inHospitalName !== '家中' ? this.findHospitalByName(inHospitalName, 'in', false) : Promise.resolve(null) |
| | | ]).then(([outHosp, inHosp]) => { |
| | | // 处理转出医院 |
| | | if (outHospitalName !== '家中') { |
| | | if (outHosp) { |
| | | // 找到医院,使用医院库中的信息 |
| | | this.taskForm.hospitalOut.id = outHosp.hospId |
| | | this.taskForm.hospitalOut.name = outHosp.hospName |
| | | this.taskForm.hospitalOut.address = this.buildFullAddress(outHosp) |
| | | this.taskForm.hospitalOut.city = outHosp.hopsCity || '' |
| | | console.log('【行程】转出医院匹配成功:', outHosp.hospName) |
| | | } else { |
| | | // 找不到医院,默认设置为"家中",原医院名称作为地址 |
| | | console.log('【行程】转出医院未找到,默认设置为家中,地址为:', outHospitalName) |
| | | this.taskForm.hospitalOut.name = '家中' |
| | | this.taskForm.hospitalOut.address = outHospitalName |
| | | this.taskForm.hospitalOut.id = null |
| | | this.taskForm.hospitalOut.city = '' |
| | | } |
| | | } |
| | | |
| | | // 处理转入医院 |
| | | if (inHospitalName !== '家中') { |
| | | if (inHosp) { |
| | | // 找到医院,使用医院库中的信息 |
| | | this.taskForm.hospitalIn.id = inHosp.hospId |
| | | this.taskForm.hospitalIn.name = inHosp.hospName |
| | | this.taskForm.hospitalIn.address = this.buildFullAddress(inHosp) |
| | | this.taskForm.hospitalIn.city = inHosp.hopsCity || '' |
| | | console.log('【行程】转入医院匹配成功:', inHosp.hospName) |
| | | } else { |
| | | // 找不到医院,默认设置为"家中",原医院名称作为地址 |
| | | console.log('【行程】转入医院未找到,默认设置为家中,地址为:', inHospitalName) |
| | | this.taskForm.hospitalIn.name = '家中' |
| | | this.taskForm.hospitalIn.address = inHospitalName |
| | | this.taskForm.hospitalIn.id = null |
| | | this.taskForm.hospitalIn.city = '' |
| | | } |
| | | } |
| | | |
| | | // 如果两个医院地址都有,自动计算距离 |
| | | if (this.taskForm.hospitalOut.address && this.taskForm.hospitalIn.address && |
| | | this.taskForm.hospitalOut.name !== '家中' && this.taskForm.hospitalIn.name !== '家中') { |
| | | this.calculateHospitalDistance() |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | |
| | | console.log('多图OCR结果处理完成,表单数据更新') |
| | | }, |
| | | |
| | | // 格式化日期字符串(返回 yyyy-MM-dd HH:mm:ss 格式) |
| | | formatDateString(dateStr) { |
| | | // 尝试不同的日期格式 |
| | | // 支持格式: |
| | | // - yyyy年MM月dd日HH时mm分 |
| | | // - yyyy年MM月dd日 |
| | | // - YYYYMMDD |
| | | // - yyyy-MM-dd HH:mm:ss |
| | | console.log('尝试格式化日期字符串:', dateStr) |
| | | let cleaned = dateStr |
| | | .replace(/[年月]/g, '-') |
| | | .replace(/[日号]/g, ' ') // 日/号 → 空格,保留日期和时间的分隔 |
| | | .replace(/时/g, ':') |
| | | .replace(/分/g, ':') |
| | | .replace(/秒/g, '') |
| | | .replace(/\s+/g, ' ') // 多个空格合并为一个 |
| | | .trim() |
| | | console.log('清理后的日期字符串:', cleaned) |
| | | 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, '-') |
| | | } |
| | | // 如果包含时分但缺少秒(yyyy-MM-dd HH:mm:) |
| | | else if (cleaned.match(/^\d{4}[-/]\d{1,2}[-/]\d{1,2}\s+\d{1,2}:\d{1,2}:$/)) { |
| | | // 去掉末尾的冒号,补上秒数00 |
| | | return cleaned.replace(/[//]/g, '-').replace(/:$/, '') + ':00' |
| | | } |
| | | // 如果只包含时分(yyyy-MM-dd HH:mm) |
| | | else if (cleaned.match(/^\d{4}[-/]\d{1,2}[-/]\d{1,2}\s+\d{1,2}:\d{1,2}$/)) { |
| | | return cleaned.replace(/[//]/g, '-') + ':00' |
| | | } |
| | | 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 |
| | | } |
| | | } |
| | | } |
| | |
| | | color: #333; |
| | | } |
| | | |
| | | .smart-parse-btn { |
| | | .smart-parse-btn, |
| | | .ocr-page-btn { |
| | | position: relative; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | |
| | | |
| | | 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; |
| | | } |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | // 人员选择弹窗样式 |
| | | .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; |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 拍照识别弹窗样式 |
| | | .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> |