const app = getApp() interface PhotoItem { photoId: string photoAngle: string photoUrl: string checkStatus: number isContinue: boolean message: string } interface RecordPhoto { photoId: string photoAngle: string photoAngleName: string photoUrl: string uploadTime: string } interface RecordDetail { recordId: string recordDate: string treatmentCount: number isBaseline: number leftEye: number rightEye: number interorbitalDistance: number uploadCompleted: number photos: RecordPhoto[] } Page({ data: { popupShow: false, popupType: 'popup16', popupParams: { close: false, position: 'center', } as any, imagePreview: false, previewImageSrc: '', currentPhotoAngle: '', recordId: '', // 表单数据 recordDate: '', treatmentCount: 0, isBaseline: 0, leftEye: '', rightEye: '', interorbitalDistance: '', // 照片数据 photoMap: {} as Record, // 顺序拍摄模式 sequentialShootMode: false, sequentialShootList: [] as { name: string, type: string, angle: string }[], sequentialShootIndex: 0, // 替妥尤单抗使用次数选项 (0-9, 9表示大于8) treatmentCountRange: ['0次', '1次', '2次', '3次', '4次', '5次', '6次', '7次', '8次', '大于8次'], hasUnsavedData: false, isCapturing: false, cameraList: { frontend: [ { name: '睁眼', type: 'front_open', angle: 'front_open' }, { name: '闭眼', type: 'front_closed', angle: 'front_closed' }, { name: '仰头', type: 'front_looking_up', angle: 'front_looking_up' }, ], backend: [ { name: '左侧-90°', type: 'side_left_90', angle: 'side_left_90' }, { name: '右侧-90°', type: 'side_right_90', angle: 'side_right_90' }, { name: '左侧-45°', type: 'side_left_45', angle: 'side_left_45' }, { name: '右侧-45°', type: 'side_right_45', angle: 'side_right_45' }, ], other: [ { name: '左上', type: 'eye_up_left', angle: 'eye_up_left' }, { name: '向上', type: 'eye_up', angle: 'eye_up' }, { name: '右上', type: 'eye_up_right', angle: 'eye_up_right' }, { name: '向左', type: 'eye_left', angle: 'eye_left' }, { name: '向右', type: 'eye_right', angle: 'eye_right' }, { name: '左下', type: 'eye_down_left', angle: 'eye_down_left' }, { name: '向下', type: 'eye_down', angle: 'eye_down' }, { name: '右下', type: 'eye_down_right', angle: 'eye_down_right' }, ], }, }, onLoad(option: any) { const recordId = option?.recordId || '' // 新增时默认日期为今天;编辑时以接口返回为准 const today = new Date().toISOString().split('T')[0] this.setData({ recordId, recordDate: recordId ? '' : today, }) }, onShow() { if (this.data.isCapturing) { this.setData({ isCapturing: false }) return } app.waitLogin({ type: [1] }).then(() => { if (this.data.recordId) { this.getRecordDetail() } }) }, // 获取记录详情(用于编辑/补拍回显) getRecordDetail() { wx.showLoading({ title: '加载中...' }) wx.ajax({ method: 'GET', url: '?r=xd/proptosis/record-detail', data: { recordId: this.data.recordId, }, }).then((res: RecordDetail) => { wx.hideLoading() const photoMap: Record = {} ;(res.photos || []).forEach((p) => { photoMap[p.photoAngle] = { photoId: p.photoId, photoAngle: p.photoAngle, photoUrl: p.photoUrl, // 已在后台入库的历史照片,默认视为可继续 checkStatus: 1, isContinue: true, message: '', } }) this.setData({ recordDate: res.recordDate || '', treatmentCount: typeof res.treatmentCount === 'number' ? res.treatmentCount : 0, isBaseline: res.isBaseline || 0, leftEye: res.leftEye != null ? String(res.leftEye) : '', rightEye: res.rightEye != null ? String(res.rightEye) : '', interorbitalDistance: res.interorbitalDistance != null ? String(res.interorbitalDistance) : '', photoMap, }) }).catch((err) => { wx.hideLoading() console.error('获取记录详情失败:', err) wx.showToast({ title: '加载失败', icon: 'none' }) }) }, // 日期选择 onDateChange(e: any) { this.setData({ recordDate: e.detail.value, }) }, // 替妥尤单抗使用次数选择 onTreatmentCountChange(e: any) { const index = e.detail.value this.setData({ treatmentCount: index, }) }, // 是否设置为基准照 onBaselineChange(e: any) { this.setData({ isBaseline: e.detail.value.length > 0 ? 1 : 0, }) }, // 左眼度数输入 onLeftEyeInput(e: any) { this.setData({ leftEye: e.detail.value, }) }, // 右眼度数输入 onRightEyeInput(e: any) { this.setData({ rightEye: e.detail.value, }) }, // 眶间距输入 onInterorbitalDistanceInput(e: any) { this.setData({ interorbitalDistance: e.detail.value, }) }, // 打开相机选择照片 handleCamera(e: any) { const { angle } = e.currentTarget.dataset this.setData({ currentPhotoAngle: angle, isCapturing: true, }) // 调用 camera 组件的 handleSelect 方法,传入类型 const cameraComponent = this.selectComponent('#camera-component') if (cameraComponent) { // 根据 angle 映射到对应的 type const type = this.getCameraType(angle) cameraComponent.handleSelect(type) } }, // 一键顺序拍摄 handleSequentialShoot() { // 定义拍摄顺序:正面 -> 侧面 -> 眼球运动 const shootOrder = [ ...this.data.cameraList.frontend, ...this.data.cameraList.backend, ...this.data.cameraList.other, ] this.setData({ sequentialShootMode: true, sequentialShootList: shootOrder, sequentialShootIndex: 0, isCapturing: true, }) // 开始第一个拍摄 this.startSequentialShoot() }, // 开始顺序拍摄 startSequentialShoot() { const { sequentialShootList, sequentialShootIndex } = this.data if (sequentialShootIndex >= sequentialShootList.length) { // 全部拍摄完成 this.setData({ sequentialShootMode: false, sequentialShootList: [], sequentialShootIndex: 0, }) wx.showToast({ title: '全部拍摄完成', icon: 'success', }) return } const currentItem = sequentialShootList[sequentialShootIndex] this.setData({ currentPhotoAngle: currentItem.angle, }) // 调用 camera 组件,设置 onlyCamera 为 true const cameraComponent = this.selectComponent('#camera-component') if (cameraComponent) { const type = this.getCameraType(currentItem.angle) cameraComponent.setData({ onlyCamera: true }) cameraComponent.handleSelect(type) } }, // 顺序拍摄模式下的上传成功回调 onSequentialUploadSuccess(e: any) { const { photoId, photoAngle, photoUrl, checkStatus, isContinue, message } = e.detail // 保存照片信息到页面数据 const photoMap = this.data.photoMap photoMap[photoAngle] = { photoId, photoAngle, photoUrl, checkStatus, isContinue, message, } // 检查是否是顺序拍摄模式 if (this.data.sequentialShootMode) { // 继续下一个拍摄 const nextIndex = this.data.sequentialShootIndex + 1 this.setData({ photoMap, sequentialShootIndex: nextIndex, }) // 延迟一下再打开下一个相机,给用户反馈时间 setTimeout(() => { this.startSequentialShoot() }, 500) } else { this.setData({ photoMap }) } }, // 将 angle 映射到 camera 组件的 type getCameraType(angle: string): number { const typeMap: Record = { front_open: 1, front_closed: 2, front_looking_up: 3, side_left_90: 4, side_right_90: 5, side_left_45: 6, side_right_45: 7, eye_up_left: 8, eye_up: 9, eye_up_right: 10, eye_left: 11, eye_right: 12, eye_down_left: 13, eye_down: 14, eye_down_right: 15, } return typeMap[angle] || 1 }, // camera 组件上传成功回调 onUploadSuccess(e: any) { const { photoId, photoAngle, photoUrl, checkStatus, isContinue, message } = e.detail // 不允许继续的情况:不写入 photoMap,提示重新上传;顺序拍摄不推进 index if (checkStatus === 2 && !isContinue) { wx.showToast({ title: message || '图片不合规,请重新上传', icon: 'none' }) if (this.data.sequentialShootMode) { setTimeout(() => { this.startSequentialShoot() }, 500) } return } // 保存照片信息到页面数据 const photoMap = this.data.photoMap photoMap[photoAngle] = { photoId, photoAngle, photoUrl, checkStatus, isContinue, message, } // 检查是否是顺序拍摄模式 if (this.data.sequentialShootMode) { // 继续下一个拍摄 const nextIndex = this.data.sequentialShootIndex + 1 this.setData({ photoMap, sequentialShootIndex: nextIndex, hasUnsavedData: true, }) // 延迟一下再打开下一个相机,给用户反馈时间 setTimeout(() => { this.startSequentialShoot() }, 500) } else { this.setData({ photoMap, hasUnsavedData: true }) } }, // camera 组件上传失败回调 onUploadError(e: any) { const { errMsg } = e.detail wx.showToast({ title: errMsg || '上传失败', icon: 'none' }) }, // 关闭相机 onCloseCamera() { // camera 组件内部处理关闭逻辑 }, // 预览图片 handlePreview(e: any) { const { angle } = e.currentTarget.dataset const photo = this.data.photoMap[angle] if (photo) { this.setData({ imagePreview: true, previewImageSrc: photo.photoUrl, currentPhotoAngle: angle, }) } }, // 重新拍摄 handleImageRetake() { this.setData({ imagePreview: false, }) this.handleCamera({ currentTarget: { dataset: { angle: this.data.currentPhotoAngle } } }) }, // 删除图片 handleImageDel() { const photoMap = this.data.photoMap delete photoMap[this.data.currentPhotoAngle] this.setData({ imagePreview: false, photoMap, }) }, // 保存记录 handleSave() { const { recordId, recordDate, treatmentCount, isBaseline, leftEye, rightEye, interorbitalDistance, photoMap } = this.data // 表单验证 if (!recordDate) { wx.showToast({ title: '请选择记录日期', icon: 'none' }) return } // 获取所有已上传的照片 const photos = Object.values(photoMap) if (photos.length === 0) { wx.showToast({ title: '请至少上传一张照片', icon: 'none' }) return } // 所有照片全不合规才不允许保存 const hasCompliantPhoto = photos.some(photo => photo.checkStatus === 1) if (!hasCompliantPhoto) { wx.showToast({ title: '所有照片不合规,请重新上传', icon: 'none' }) return } const photoIds = photos.map(photo => photo.photoId).join(',') const data: any = { ...(recordId ? { recordId } : {}), recordDate, treatmentCount, isBaseline, photoIds, } // 可选字段 if (leftEye) data.leftEye = leftEye if (rightEye) data.rightEye = rightEye if (interorbitalDistance) data.interorbitalDistance = interorbitalDistance wx.showLoading({ title: '保存中...' }) wx.ajax({ method: 'POST', url: '?r=xd/proptosis/record-save', data, }).then(() => { wx.hideLoading() this.setData({ hasUnsavedData: false }) wx.showToast({ title: '保存成功', icon: 'success', }) setTimeout(() => { wx.navigateBack() }, 1500) }).catch((err: any) => { wx.hideLoading() const msg = err?.data?.msg || '保存失败' wx.showToast({ title: msg, icon: 'none' }) }) }, handleDemo() { wx.navigateTo({ url: '/patient/pages/noteDemo/index', }) }, handlePopupOk() { this.setData({ popupShow: false, }) this.handleSave() }, handlePopupCancel() { this.setData({ popupShow: false, }) wx.navigateBack() }, handleBack() { if (this.data.hasUnsavedData) { this.setData({ popupShow: true, popupType: 'popup16', }) } else { wx.navigateBack() } }, }) export {}