You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
547 lines
14 KiB
547 lines
14 KiB
const app = getApp<IAppOption>() |
|
|
|
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<string, PhotoItem>, |
|
|
|
// 顺序拍摄模式 |
|
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, |
|
|
|
hasBaseline: 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, |
|
today, |
|
}) |
|
}, |
|
|
|
onShow() { |
|
if (this.data.isCapturing) { |
|
this.setData({ isCapturing: false }) |
|
return |
|
} |
|
app.waitLogin({ type: [1] }).then(() => { |
|
this.getBaselineStatus() |
|
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<string, PhotoItem> = {} |
|
;(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, |
|
}) |
|
}, |
|
|
|
// 获取基准照状态 |
|
getBaselineStatus() { |
|
wx.ajax({ |
|
method: 'GET', |
|
url: '?r=xd/proptosis/baseline-status', |
|
data: {}, |
|
}).then((res: any) => { |
|
this.setData({ |
|
hasBaseline: res.hasBaseline, |
|
isBaseline: res.hasBaseline ? this.data.isBaseline : 1, |
|
}) |
|
}).catch(() => {}) |
|
}, |
|
|
|
// 是否设置为基准照 |
|
onBaselineChange(e: any) { |
|
const checked = e.detail.value.length > 0 |
|
if (checked && this.data.hasBaseline) { |
|
this.setData({ |
|
popupShow: true, |
|
popupType: 'popup18', |
|
}) |
|
return |
|
} |
|
this.setData({ |
|
isBaseline: checked ? 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<string, number> = { |
|
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, |
|
}) |
|
} |
|
}, |
|
|
|
// 关闭图片预览 |
|
handleImagePreviewClose() { |
|
this.setData({ |
|
imagePreview: false, |
|
}) |
|
}, |
|
|
|
// 重新拍摄 |
|
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, |
|
hasUnsavedData: true, |
|
}) |
|
}, |
|
|
|
// 保存记录 |
|
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() { |
|
const { popupType } = this.data |
|
this.setData({ |
|
popupShow: false, |
|
}) |
|
if (popupType === 'popup18') { |
|
this.setData({ isBaseline: 1 }) |
|
} |
|
else { |
|
this.handleSave() |
|
} |
|
}, |
|
|
|
handlePopupCancel() { |
|
const { popupType } = this.data |
|
this.setData({ |
|
popupShow: false, |
|
}) |
|
if (popupType === 'popup18') { |
|
this.setData({ isBaseline: 0 }) |
|
} |
|
else { |
|
wx.navigateBack() |
|
} |
|
}, |
|
|
|
handleBack() { |
|
if (this.data.hasUnsavedData) { |
|
this.setData({ |
|
popupShow: true, |
|
popupType: 'popup16', |
|
}) |
|
} |
|
else { |
|
wx.navigateBack() |
|
} |
|
}, |
|
}) |
|
|
|
export {}
|
|
|