diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 0000000..004eb8a --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,16 @@ +// Folder-specific settings +// +// For a full list of overridable settings, and general information on folder-specific settings, +// see the documentation: https://zed.dev/docs/configuring-zed#settings-files +{ + "lsp": { + "emmet-language-server": { + "initialization_options": { + "preferences": { + "css.intUnit": "rpx", + "css.floatUnitr": "rpx", + }, + }, + }, + }, +} diff --git a/src/components/image-merge/index.ts b/src/components/image-merge/index.ts index 8568833..e46c449 100644 --- a/src/components/image-merge/index.ts +++ b/src/components/image-merge/index.ts @@ -96,7 +96,7 @@ Component({ const canvas = res[0].node const ctx = canvas.getContext('2d') - Promise.all(imageList.map(item => this.getImageInfo(item.src))) + Promise.all(imageList.map((item) => this.getImageInfo(item.src))) .then((imageInfos) => { const targetWidth = 750 const pixelRatio = wx.getWindowInfo().pixelRatio @@ -118,15 +118,25 @@ Component({ let currentY = 0 let loadedCount = 0 + // 预计算每张图片的 Y 偏移,确保顺序正确 + const yPositions: number[] = [] + scaledHeights.forEach((h, i) => { + yPositions.push(currentY) + currentY += h + }) + + loadedCount = 0 + currentY = 0 + imageInfos.forEach((info, index) => { const img = canvas.createImage() img.src = info.path img.onload = () => { - ctx.drawImage(img, 0, currentY, canvasWidth, scaledHeights[index]) + ctx.drawImage(img, 0, yPositions[index], canvasWidth, scaledHeights[index]) const timeText = imageList[index].time || this.formatTime(new Date()) const padding = 20 - const textY = currentY + 40 + const textY = yPositions[index] + 40 ctx.fillStyle = '#ffffff' ctx.font = 'bold 28px sans-serif' @@ -143,14 +153,13 @@ Component({ ctx.shadowOffsetY = 0 if (index === imageInfos.length - 1) { - const lastImageBottom = currentY + scaledHeights[index] + const lastImageBottom = yPositions[index] + scaledHeights[index] ctx.fillStyle = 'rgba(255, 255, 255, 0.8)' ctx.font = '24px sans-serif' ctx.textAlign = 'right' ctx.fillText('由-TED关爱小助手-小程序生成', canvasWidth - 20, lastImageBottom - 20) } - currentY += scaledHeights[index] loadedCount++ if (loadedCount === imageInfos.length) { diff --git a/src/pages/d_noteDetail/index.ts b/src/pages/d_noteDetail/index.ts index 1f13165..f6d5bbb 100644 --- a/src/pages/d_noteDetail/index.ts +++ b/src/pages/d_noteDetail/index.ts @@ -30,16 +30,25 @@ Page({ recordDetail: {} as RecordDetail, photoMap: {} as Record, - // 有照片的角度分组 - frontendPhotos: [] as Photo[], - backendPhotos: [] as Photo[], - otherPhotos: [] as Photo[], + // 各组是否有照片 + hasFrontend: false, + hasBackend: false, + hasOther: false, // 角度分组 angleGroups: { frontend: ['front_open', 'front_closed', 'front_looking_up'], backend: ['side_left_90', 'side_right_90', 'side_left_45', 'side_right_45'], - other: ['eye_up_left', 'eye_up', 'eye_up_right', 'eye_left', 'eye_right', 'eye_down_left', 'eye_down', 'eye_down_right'], + other: [ + 'eye_up_left', + 'eye_up', + 'eye_up_right', + 'eye_left', + 'eye_right', + 'eye_down_left', + 'eye_down', + 'eye_down_right', + ], }, // 角度名称映射 @@ -87,34 +96,36 @@ Page({ data: { recordId: this.data.recordId, }, - }).then((res: any) => { - wx.hideLoading() - const photoMap: Record = {} - const photos = res.photos || [] - photos.forEach((p: Photo) => { - if (p.photoUrl) { - photoMap[p.photoAngle] = p - } + }) + .then((res: any) => { + wx.hideLoading() + const photoMap: Record = {} + const photos = res.photos || [] + photos.forEach((p: Photo) => { + if (p.photoUrl) { + photoMap[p.photoAngle] = p + } + }) + + // 判断各组是否有照片 + const { frontend, backend, other } = this.data.angleGroups + const hasFrontend = frontend.some((angle) => photoMap[angle]?.photoUrl) + const hasBackend = backend.some((angle) => photoMap[angle]?.photoUrl) + const hasOther = other.some((angle) => photoMap[angle]?.photoUrl) + + this.setData({ + recordDetail: res, + photoMap, + hasFrontend, + hasBackend, + hasOther, + }) }) - - // 按分组过滤有照片的角度 - const { frontend, backend, other } = this.data.angleGroups - const frontendPhotos = frontend.filter(angle => photoMap[angle]).map(angle => photoMap[angle]) - const backendPhotos = backend.filter(angle => photoMap[angle]).map(angle => photoMap[angle]) - const otherPhotos = other.filter(angle => photoMap[angle]).map(angle => photoMap[angle]) - - this.setData({ - recordDetail: res, - photoMap, - frontendPhotos, - backendPhotos, - otherPhotos, + .catch((err) => { + wx.hideLoading() + console.error('获取记录详情失败:', err) + wx.showToast({ title: '加载失败', icon: 'none' }) }) - }).catch((err) => { - wx.hideLoading() - console.error('获取记录详情失败:', err) - wx.showToast({ title: '加载失败', icon: 'none' }) - }) }, // 返回上一页 diff --git a/src/pages/d_noteDetail/index.wxml b/src/pages/d_noteDetail/index.wxml index c9815e7..296da3b 100644 --- a/src/pages/d_noteDetail/index.wxml +++ b/src/pages/d_noteDetail/index.wxml @@ -39,30 +39,30 @@ - + 正面 - - - {{item.photoAngleName}} + + + {{angleNameMap[item]}} - + 侧面 - - - {{item.photoAngleName}} + + + {{angleNameMap[item]}} - + 眼球运动八个方向 - - - {{item.photoAngleName}} + + + {{angleNameMap[item]}} diff --git a/src/pages/d_noteDiff/index.json b/src/pages/d_noteDiff/index.json index 9847532..2bd40f6 100644 --- a/src/pages/d_noteDiff/index.json +++ b/src/pages/d_noteDiff/index.json @@ -1,5 +1,5 @@ { - "navigationBarTitleText": "照片对比", + "navigationBarTitleText": "照片对比分析", "usingComponents": { "navbar": "/components/navbar/index", "van-icon": "@vant/weapp/icon/index" diff --git a/src/pages/d_noteDiff/index.scss b/src/pages/d_noteDiff/index.scss index 8c50427..8192933 100644 --- a/src/pages/d_noteDiff/index.scss +++ b/src/pages/d_noteDiff/index.scss @@ -3,7 +3,7 @@ page { } .page { - padding-bottom: 160rpx; + padding-bottom: calc(env(safe-area-inset-bottom) + 160rpx); .page-tip { padding: 24rpx 28rpx; @@ -288,7 +288,7 @@ page { bottom: 0; left: 0; right: 0; - padding: 24rpx 40rpx 48rpx; + padding: 24rpx 40rpx calc(env(safe-area-inset-bottom) + 24rpx); background: #fff; box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.05); diff --git a/src/pages/d_noteDiff/index.ts b/src/pages/d_noteDiff/index.ts index 9b6e2c0..ab12efa 100644 --- a/src/pages/d_noteDiff/index.ts +++ b/src/pages/d_noteDiff/index.ts @@ -1,4 +1,5 @@ const app = getApp() +const licia = require('miniprogram-licia') interface ComparePhoto { recordId: string @@ -14,11 +15,10 @@ interface ComparePhoto { interface CompareDate { recordId: string recordDate: string + isSelected?: boolean } -interface ListItem extends CompareDate { - isBaseline: number -} +type BaselineItem = CompareDate | null Page({ data: { @@ -31,17 +31,23 @@ Page({ angleMap: {} as Record, // 日期选择 - baseline: null as CompareDate | null, + baseline: null as unknown as BaselineItem, nonBaselineList: [] as CompareDate[], selectedDates: [] as string[], + baselineSelected: true, // 对比照片 comparePhotos: [] as ComparePhoto[], + + generateDate: '', }, onLoad(option: any) { + const now = new Date() + const generateDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}` this.setData({ patientId: option.patientId || '', + generateDate, }) app.waitLogin({ type: [2] }).then(() => { this.getCompareAngle() @@ -101,16 +107,17 @@ Page({ newRecordIds.push(newBaseline.recordId) } const selectedDates = this.data.selectedDates.filter((id: string) => newRecordIds.includes(id)) + const baselineSelected = this.data.baselineSelected const nonBaselineList = newList.map((item: CompareDate & { isSelected?: boolean }) => ({ ...item, isSelected: selectedDates.includes(item.recordId), })) this.setData({ - baseline: newBaseline, + baseline: newBaseline ? { ...newBaseline, isSelected: baselineSelected } : null, nonBaselineList, selectedDates, }) - if (selectedDates.length > 0 || newBaseline) { + if (selectedDates.length > 0 || (newBaseline && baselineSelected)) { this.getComparePhotos() } else { this.setData({ comparePhotos: [] }) @@ -129,11 +136,25 @@ Page({ this.setData({ photoAngle: angle.key, photoAngleName: angle.name, + selectedDates: [], + baselineSelected: true, comparePhotos: [], }) this.getCompareDates() }, + // 选择基准照日期 + onBaselineSelect() { + const baselineSelected = !this.data.baselineSelected + const baseline = this.data.baseline + this.setData({ + baselineSelected, + baseline: baseline ? { ...baseline, isSelected: baselineSelected } : null, + }) + // 获取对比照片 + this.getComparePhotos() + }, + // 选择日期 onDateSelect(e: any) { const { recordId } = e.currentTarget.dataset @@ -142,7 +163,7 @@ Page({ if (index > -1) { selectedDates.splice(index, 1) } else { - const maxSelect = this.data.baseline ? 5 : 6 + const maxSelect = this.data.baselineSelected ? 5 : 6 if (selectedDates.length >= maxSelect) { wx.showToast({ title: '最多选择6张对比图', icon: 'none' }) return @@ -160,13 +181,13 @@ Page({ }, // 获取对比照片 - getComparePhotos() { + getComparePhotos: licia.debounce(function (this: any) { const { photoAngle, selectedDates, baseline } = this.data if (!photoAngle) { this.setData({ comparePhotos: [] }) return } - const recordIds = baseline ? [baseline.recordId, ...selectedDates] : selectedDates + const recordIds = baseline && this.data.baselineSelected ? [baseline.recordId, ...selectedDates] : selectedDates if (recordIds.length === 0) { this.setData({ comparePhotos: [] }) return @@ -216,16 +237,18 @@ Page({ console.error('获取对比照片失败:', err) wx.showToast({ title: '获取对比照片失败', icon: 'none' }) }) - }, + }, 300), // 生成对比图 handleEdit() { - if (this.data.selectedDates.length === 0) { - wx.showToast({ title: '请选择对比日期', icon: 'none' }) + const { baseline, baselineSelected, selectedDates } = this.data + const hasBaseline = baseline && baselineSelected + const totalCount = selectedDates.length + (hasBaseline ? 1 : 0) + if (totalCount < 2) { + wx.showToast({ title: '请至少选择两个日期', icon: 'none' }) return } - const { baseline, selectedDates } = this.data - const recordIds = baseline ? [baseline.recordId, ...selectedDates] : selectedDates + const recordIds = hasBaseline ? [baseline.recordId, ...selectedDates] : selectedDates wx.navigateTo({ url: `/pages/d_noteDiffEdit/index?patientId=${this.data.patientId}&photoAngle=${this.data.photoAngle}&recordIds=${recordIds.join(',')}`, }) diff --git a/src/pages/d_noteDiff/index.wxml b/src/pages/d_noteDiff/index.wxml index 99b0b99..440c6f7 100644 --- a/src/pages/d_noteDiff/index.wxml +++ b/src/pages/d_noteDiff/index.wxml @@ -22,7 +22,7 @@ 选择对比日期(可多选) - + {{baseline.recordDate}} @@ -39,7 +39,7 @@ {{photoAngleName}}时间线对比 - 生成日期:{{comparePhotos[0].recordDate}} + 生成日期:{{generateDate}} diff --git a/src/pages/d_noteDiffData/index.ts b/src/pages/d_noteDiffData/index.ts index 0827781..efaa10f 100644 --- a/src/pages/d_noteDiffData/index.ts +++ b/src/pages/d_noteDiffData/index.ts @@ -13,6 +13,7 @@ interface CompareItem { Page({ data: { patientId: '', + patientName: '', dataList: [] as CompareItem[], loading: false, }, @@ -21,6 +22,7 @@ Page({ const patientName = option.patientName || '' this.setData({ patientId: option.patientId || '', + patientName, }) if (patientName) { wx.setNavigationBarTitle({ title: `${patientName}的眼突度对比` }) @@ -46,19 +48,19 @@ Page({ page: 1, pageSize: 1000, }, - }).then((res: any) => { - const list: CompareItem[] = (res.list || []) - .slice() - .sort((a, b) => String(a.recordDate).localeCompare(String(b.recordDate))) - this.setData({ - dataList: list, - loading: false, - }) - }).catch((err) => { - console.error('获取对比数据失败:', err) - this.setData({ loading: false }) - wx.showToast({ title: '获取对比数据失败', icon: 'none' }) }) + .then((res: any) => { + const list: CompareItem[] = (res.list || []).slice() + this.setData({ + dataList: list, + loading: false, + }) + }) + .catch((err) => { + console.error('获取对比数据失败:', err) + this.setData({ loading: false }) + wx.showToast({ title: '获取对比数据失败', icon: 'none' }) + }) }, handleBack() { diff --git a/src/pages/d_noteDiffData/index.wxml b/src/pages/d_noteDiffData/index.wxml index 3ecbe39..44cec1b 100644 --- a/src/pages/d_noteDiffData/index.wxml +++ b/src/pages/d_noteDiffData/index.wxml @@ -1,4 +1,4 @@ - + diff --git a/src/pages/d_noteDiffEdit/index.json b/src/pages/d_noteDiffEdit/index.json index 5b79b53..1143a0a 100644 --- a/src/pages/d_noteDiffEdit/index.json +++ b/src/pages/d_noteDiffEdit/index.json @@ -1,5 +1,5 @@ { - "navigationBarTitleText": "对比图编辑", + "navigationBarTitleText": "照片对比编辑", "usingComponents": { "navbar": "/components/navbar/index", "imageMerge": "/components/image-merge/index", diff --git a/src/pages/d_noteDiffEdit/index.scss b/src/pages/d_noteDiffEdit/index.scss index 52cdf60..c19d1f4 100644 --- a/src/pages/d_noteDiffEdit/index.scss +++ b/src/pages/d_noteDiffEdit/index.scss @@ -4,6 +4,25 @@ page { .page { padding-bottom: 160rpx; + .page-tip { + padding: 24rpx 28rpx; + background-color: #fff7e9; + display: flex; + + .icon { + margin-top: 6rpx; + flex-shrink: 0; + width: 32rpx; + height: 32rpx; + } + + .content { + margin-left: 16rpx; + font-size: 28rpx; + color: #ffa300; + line-height: 44rpx; + } + } .container { margin: 40rpx 40rpx 0; diff --git a/src/pages/d_noteDiffEdit/index.ts b/src/pages/d_noteDiffEdit/index.ts index def6938..f34d445 100644 --- a/src/pages/d_noteDiffEdit/index.ts +++ b/src/pages/d_noteDiffEdit/index.ts @@ -20,6 +20,7 @@ Page({ patientId: '', photoAngle: '', photoAngleName: '', + generateDate: '', recordIds: [] as string[], photos: [] as PhotoItem[], loading: false, @@ -27,10 +28,13 @@ Page({ }, onLoad(option: any) { + const now = new Date() + const generateDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}` this.setData({ patientId: option.patientId || '', photoAngle: option.photoAngle || '', recordIds: option.recordIds ? option.recordIds.split(',') : [], + generateDate, }) // 获取角度名称 this.getAngleName() @@ -137,7 +141,7 @@ Page({ success: (cropRes) => { photos[index].isCropped = true photos[index].croppedUrl = cropRes.tempFilePath - this.setData({ photos }) + this.setData({ photos, mergedImage: '' }) wx.showToast({ title: '裁剪成功', icon: 'success' }) }, fail: (err) => { @@ -163,7 +167,7 @@ Page({ const photos = this.data.photos photos[index].isCropped = false photos[index].croppedUrl = '' - this.setData({ photos }) + this.setData({ photos, mergedImage: '' }) }, // 生成对比图预览 @@ -176,7 +180,9 @@ Page({ const mergeComponent = this.selectComponent('#merge') if (mergeComponent) { - const imageList = photos.map((item) => { + // 确保基准照片排在第一位 + const sorted = [...photos].sort((a, b) => b.isBaseline - a.isBaseline) + const imageList = sorted.map((item) => { const label = item.isBaseline === 1 ? `基准照片 ${item.recordDate} 替妥尤单抗:${item.treatmentCount >= 9 ? '>8' : item.treatmentCount}次` @@ -194,12 +200,54 @@ Page({ handleSaveAlbum() { const { mergedImage } = this.data if (!mergedImage) { - this.handleMergePreview() + wx.showToast({ title: '请先生成对比图', icon: 'none' }) return } + this.saveImageToAlbum(mergedImage) + }, + + // 保存图片到相册(带权限引导) + saveImageToAlbum(filePath: string) { + wx.getSetting({ + success: (res) => { + if ( + res.authSetting['scope.writePhotosAlbum'] != undefined && + res.authSetting['scope.writePhotosAlbum'] == true + ) { + this.doSaveImage(filePath) + } else if (res.authSetting['scope.writePhotosAlbum'] == undefined) { + this.doSaveImage(filePath) + } else { + wx.showModal({ + title: '请求授权相册权限', + content: '需要保存对比图到相册,请确认授权', + confirmColor: '#8c75d0', + success: (res) => { + if (res.cancel) { + wx.showToast({ title: '拒绝授权', icon: 'none' }) + } else if (res.confirm) { + wx.openSetting({ + success: (res) => { + if (res.authSetting['scope.writePhotosAlbum'] == true) { + this.doSaveImage(filePath) + } else { + wx.showToast({ title: '授权失败', icon: 'none' }) + } + }, + }) + } + }, + }) + } + }, + }) + }, + + // 执行保存图片 + doSaveImage(filePath: string) { wx.saveImageToPhotosAlbum({ - filePath: mergedImage, + filePath, success: () => { wx.showToast({ title: '保存成功', icon: 'success' }) }, @@ -214,6 +262,11 @@ Page({ onMergeSave(e: any) { const { tempFilePath } = e.detail this.setData({ mergedImage: tempFilePath }) + wx.previewImage({ + urls: [tempFilePath], + current: tempFilePath, + showmenu: true, + }) }, // 合并失败回调 diff --git a/src/pages/d_noteDiffEdit/index.wxml b/src/pages/d_noteDiffEdit/index.wxml index 8a4dd61..2e70953 100644 --- a/src/pages/d_noteDiffEdit/index.wxml +++ b/src/pages/d_noteDiffEdit/index.wxml @@ -3,10 +3,16 @@ + + + + 本页面仅作为生成对比图的工具使用。裁剪后照片不会覆盖原图、也不会保存。 + + {{photoAngleName}}时间线对比 - 生成日期:{{photos[0].recordDate}} + 生成日期:{{generateDate}} diff --git a/src/pages/d_noteList/index.ts b/src/pages/d_noteList/index.ts index 01be786..e9b6129 100644 --- a/src/pages/d_noteList/index.ts +++ b/src/pages/d_noteList/index.ts @@ -45,10 +45,8 @@ Page({ // 获取患者突眼记录列表 getRecordList(reset = false) { - if (this.data.loading) - return - if (!reset && !this.data.hasMore) - return + if (this.data.loading) return + if (!reset && !this.data.hasMore) return const page = reset ? 1 : this.data.page this.setData({ loading: true }) @@ -61,27 +59,29 @@ Page({ page, pageSize: this.data.pageSize, }, - }).then((res: any) => { - const list = res.list || [] - const total = res.pagination?.total || 0 - const pages = Math.ceil(total / this.data.pageSize) || 1 + }) + .then((res: any) => { + const list = res.list || [] + const total = res.pagination?.total || 0 + const pages = Math.ceil(total / this.data.pageSize) || 1 - this.setData({ - recordList: reset ? list : [...this.data.recordList, ...list], - total, - page: page + 1, - hasMore: list.length >= this.data.pageSize, - loading: false, - pagination: { - count: total, - page, - pages, - }, + this.setData({ + recordList: reset ? list : [...this.data.recordList, ...list], + total, + page: page + 1, + hasMore: list.length >= this.data.pageSize, + loading: false, + pagination: { + count: total, + page, + pages, + }, + }) + }) + .catch((err) => { + console.error('获取记录列表失败:', err) + this.setData({ loading: false }) }) - }).catch((err) => { - console.error('获取记录列表失败:', err) - this.setData({ loading: false }) - }) }, // 加载更多 @@ -89,6 +89,10 @@ Page({ this.getRecordList() }, + onReachBottom() { + this.loadMore() + }, + // 查看记录详情 handleHistory(e: any) { const { recordId } = e.currentTarget.dataset diff --git a/src/patient/components/camera/index.ts b/src/patient/components/camera/index.ts index d01c08f..366aa03 100644 --- a/src/patient/components/camera/index.ts +++ b/src/patient/components/camera/index.ts @@ -37,11 +37,41 @@ Component({ type: 1, // 拍摄位置名称映射 typeNameMap: { - 1: { name: '正面睁眼', group: '正面', index: 1, total: 3, tip: '平视,目光看向镜头方向,自然睁眼,不眯眼、不瞪眼。' }, - 2: { name: '正面闭眼', group: '正面', index: 2, total: 3, tip: '正对镜头,面部居中,自然放松,双眼轻轻闭合,不皱眉、不挤眼。' }, - 3: { name: '正面仰头', group: '正面', index: 3, total: 3, tip: '拍摄时,正对镜头,面部居中,头部自然向上仰至约45°,双眼同步平视,保持自然睁眼、不眯眼。' }, - 4: { name: '左侧-90°', group: '侧面', index: 1, total: 4, tip: '身体与头部完全转向右侧,呈标准90°侧面,仅可见左侧眼睛。' }, - 5: { name: '右侧-90°', group: '侧面', index: 2, total: 4, tip: '身体与头部完全转向左侧,呈标准90°侧面,仅可见右侧眼睛。' }, + 1: { + name: '正面睁眼', + group: '正面', + index: 1, + total: 3, + tip: '平视,目光看向镜头方向,自然睁眼,不眯眼、不瞪眼。', + }, + 2: { + name: '正面闭眼', + group: '正面', + index: 2, + total: 3, + tip: '正对镜头,面部居中,自然放松,双眼轻轻闭合,不皱眉、不挤眼。', + }, + 3: { + name: '正面仰头', + group: '正面', + index: 3, + total: 3, + tip: '拍摄时,正对镜头,面部居中,头部自然向上仰至约45°,双眼同步平视,保持自然睁眼、不眯眼。', + }, + 4: { + name: '左侧-90°', + group: '侧面', + index: 1, + total: 4, + tip: '身体与头部完全转向右侧,呈标准90°侧面,仅可见左侧眼睛。', + }, + 5: { + name: '右侧-90°', + group: '侧面', + index: 2, + total: 4, + tip: '身体与头部完全转向左侧,呈标准90°侧面,仅可见右侧眼睛。', + }, 6: { name: '左侧-45°', group: '侧面', index: 3, total: 4, tip: '身体与头部转向右前方45°。' }, 7: { name: '右侧-45°', group: '侧面', index: 4, total: 4, tip: '身体与头部转向左前方45°' }, 8: { name: '左上', group: '眼球运动', index: 1, total: 8, tip: '正对镜头,双眼向左上方看。' }, @@ -133,11 +163,12 @@ Component({ }) // 如果设置了只使用相机,直接打开相机 if (this.properties.onlyCamera) { - this.setData({ - visible: true, + this.checkCameraPermission(() => { + this.setData({ + visible: true, + }) }) - } - else { + } else { this.setData({ selectShow: true, }) @@ -149,9 +180,46 @@ Component({ }) }, handleCamera() { - this.setData({ - selectShow: false, - visible: true, + // 先检查相机权限 + this.checkCameraPermission(() => { + this.setData({ + selectShow: false, + visible: true, + }) + }) + }, + + // 检查相机权限 + checkCameraPermission(callback: () => void) { + wx.getSetting({ + success: (res) => { + if (res.authSetting['scope.camera'] != undefined && res.authSetting['scope.camera'] == true) { + callback() + } else if (res.authSetting['scope.camera'] == undefined) { + callback() + } else { + wx.showModal({ + title: '请求授权相机权限', + content: '需要使用相机进行拍摄,请确认授权', + confirmColor: '#8c75d0', + success: (res) => { + if (res.cancel) { + wx.showToast({ title: '拒绝授权', icon: 'none' }) + } else if (res.confirm) { + wx.openSetting({ + success: (res) => { + if (res.authSetting['scope.camera'] == true) { + callback() + } else { + wx.showToast({ title: '授权失败', icon: 'none' }) + } + }, + }) + } + }, + }) + } + }, }) }, handleHideCamera() { @@ -160,8 +228,7 @@ Component({ this.setData({ visible: false, }) - } - else { + } else { this.setData({ visible: false, selectShow: true, @@ -170,29 +237,69 @@ Component({ }, handlePicture() { this.handleCancel() - wx.chooseMedia({ - count: 1, - mediaType: ['image'], - sourceType: ['album'], - success: (res) => { - if (res.tempFiles && res.tempFiles.length > 0) { - const tempFile = res.tempFiles[0] - const maxSize = 10 * 1024 * 1024 - if (tempFile.size > maxSize) { - wx.showToast({ - title: '图片大小不能超过10M', - icon: 'none', - }) - this.triggerEvent('uploaderror', { reason: 'file_too_large', size: tempFile.size, maxSize }) - return + // 检查相册权限 + this.checkAlbumPermission(() => { + wx.chooseMedia({ + count: 1, + mediaType: ['image'], + sourceType: ['album'], + success: (res) => { + if (res.tempFiles && res.tempFiles.length > 0) { + const tempFile = res.tempFiles[0] + const maxSize = 10 * 1024 * 1024 + if (tempFile.size > maxSize) { + wx.showToast({ + title: '图片大小不能超过10MB', + icon: 'none', + }) + this.triggerEvent('uploaderror', { reason: 'file_too_large', size: tempFile.size, maxSize }) + return + } + this.uploadImage(tempFile.tempFilePath) } - this.uploadImage(tempFile.tempFilePath) - } - }, - fail: (err) => { - console.error('选择图片失败:', err) - if (err.errMsg?.includes('cancel')) { - this.setData({ selectShow: true }) + }, + fail: (err) => { + console.error('选择图片失败:', err) + if (err.errMsg?.includes('cancel')) { + this.setData({ selectShow: true }) + } + }, + }) + }) + }, + + // 检查相册权限 + checkAlbumPermission(callback: () => void) { + wx.getSetting({ + success: (res) => { + if ( + res.authSetting['scope.writePhotosAlbum'] != undefined && + res.authSetting['scope.writePhotosAlbum'] == true + ) { + callback() + } else if (res.authSetting['scope.writePhotosAlbum'] == undefined) { + callback() + } else { + wx.showModal({ + title: '请求授权相册权限', + content: '需要访问相册选择照片,请确认授权', + confirmColor: '#8c75d0', + success: (res) => { + if (res.cancel) { + wx.showToast({ title: '拒绝授权', icon: 'none' }) + } else if (res.confirm) { + wx.openSetting({ + success: (res) => { + if (res.authSetting['scope.writePhotosAlbum'] == true) { + callback() + } else { + wx.showToast({ title: '授权失败', icon: 'none' }) + } + }, + }) + } + }, + }) } }, }) @@ -258,9 +365,27 @@ Component({ onCameraError(e: WechatMiniprogram.CustomEvent) { console.error('相机错误:', e.detail) - wx.showToast({ - title: '相机权限未开启', - icon: 'none', + // 相机出错时引导用户去设置页开启权限 + wx.showModal({ + title: '请求授权相机权限', + content: '需要使用相机进行拍摄,请确认授权', + confirmColor: '#8c75d0', + success: (res) => { + if (res.cancel) { + this.setData({ visible: false, selectShow: true }) + } else if (res.confirm) { + wx.openSetting({ + success: (res) => { + if (res.authSetting['scope.camera'] == true) { + wx.showToast({ title: '授权成功', icon: 'success' }) + } else { + this.setData({ visible: false, selectShow: true }) + wx.showToast({ title: '授权失败', icon: 'none' }) + } + }, + }) + } + }, }) this.triggerEvent('error', { reason: 'permission_denied' }) }, @@ -275,13 +400,12 @@ Component({ const fileSize = (stats as WechatMiniprogram.Stats).size if (fileSize > maxSize) { wx.showToast({ - title: '图片大小不能超过10M', + title: '图片大小不能超过10MB', icon: 'none', }) this.triggerEvent('uploaderror', { reason: 'file_too_large', size: fileSize, maxSize }) resolve(false) - } - else { + } else { resolve(true) } }, @@ -294,8 +418,7 @@ Component({ uploadImage(tempFilePath: string) { this.checkImageSize(tempFilePath).then((isValid) => { - if (!isValid) - return + if (!isValid) return wx.showLoading({ title: '正在上传', @@ -313,8 +436,7 @@ Component({ const photoUrl = data.data.Url // 第二步:调用 photo-upload 接口保存照片信息 this.savePhotoInfo(photoUrl) - } - else { + } else { wx.hideLoading() wx.showToast({ title: '上传失败', @@ -322,8 +444,7 @@ Component({ }) this.triggerEvent('uploaderror', { reason: 'upload_failed', data }) } - } - catch (e) { + } catch (e) { wx.hideLoading() wx.showToast({ title: '解析响应失败', @@ -355,17 +476,18 @@ Component({ method: 'GET', url: '?r=xd/proptosis/get-session-id', data: {}, - }).then((res: any) => { - if (res.sessionId) { - this.setData({ sessionIdCache: res.sessionId }) - resolve(res.sessionId) - } - else { - reject(new Error('sessionId not found')) - } - }).catch((err) => { - reject(err) }) + .then((res: any) => { + if (res.sessionId) { + this.setData({ sessionIdCache: res.sessionId }) + resolve(res.sessionId) + } else { + reject(new Error('sessionId not found')) + } + }) + .catch((err) => { + reject(err) + }) }) }, @@ -383,70 +505,73 @@ Component({ } // 先获取 sessionId,再上传照片 - this.getSessionId().then((sessionId) => { - const recordId = this.properties.recordId - wx.ajax({ - method: 'POST', - url: '?r=xd/proptosis/photo-checker', - data: { - ...(recordId ? { recordId } : {}), - sessionId, - photoAngle, - photoUrl, - }, - }).then((res: any) => { - wx.hideLoading() - const { photoId, checkStatus, isContinue, message } = res - - // 触发上传成功事件,返回完整的照片信息 - this.triggerEvent('uploadsuccess', { - photoId, - photoAngle, - photoUrl, - checkStatus, - isContinue, - message, + this.getSessionId() + .then((sessionId) => { + const recordId = this.properties.recordId + wx.ajax({ + method: 'POST', + url: '?r=xd/proptosis/photo-checker', + data: { + ...(recordId ? { recordId } : {}), + sessionId, + photoAngle, + photoUrl, + }, }) + .then((res: any) => { + wx.hideLoading() + const { photoId, checkStatus, isContinue, message } = res - // 关闭相机 - this.setData({ - visible: false, - selectShow: false, - }) + // 触发上传成功事件,返回完整的照片信息 + this.triggerEvent('uploadsuccess', { + photoId, + photoAngle, + photoUrl, + checkStatus, + isContinue, + message, + }) - // 如果机审不通过且不允许继续,提示用户 - if (checkStatus === 2 && !isContinue) { - wx.showModal({ - title: '提示', - content: message || '图片不合规,请重新上传', - showCancel: false, - confirmColor: '#8c75d0', - cancelColor: '#141515', + // 关闭相机 + this.setData({ + visible: false, + selectShow: false, + }) + + // 如果机审不通过且不允许继续,提示用户 + if (checkStatus === 2 && !isContinue) { + wx.showModal({ + title: '提示', + content: message || '图片不合规,请重新上传', + showCancel: false, + confirmColor: '#8c75d0', + cancelColor: '#141515', + }) + } else if (checkStatus === 2 && isContinue) { + // 机审不通过但允许继续,提示用户 + wx.showToast({ + title: message || '图片可能不合规', + icon: 'none', + }) + } }) - } - else if (checkStatus === 2 && isContinue) { - // 机审不通过但允许继续,提示用户 - wx.showToast({ - title: message || '图片可能不合规', - icon: 'none', + .catch((err) => { + wx.hideLoading() + wx.showToast({ + title: '保存照片信息失败', + icon: 'none', + }) + this.triggerEvent('uploaderror', { reason: 'save_photo_failed', error: err }) }) - } - }).catch((err) => { + }) + .catch((err) => { wx.hideLoading() wx.showToast({ - title: '保存照片信息失败', + title: '获取会话ID失败', icon: 'none', }) - this.triggerEvent('uploaderror', { reason: 'save_photo_failed', error: err }) - }) - }).catch((err) => { - wx.hideLoading() - wx.showToast({ - title: '获取会话ID失败', - icon: 'none', + this.triggerEvent('uploaderror', { reason: 'get_session_failed', error: err }) }) - this.triggerEvent('uploaderror', { reason: 'get_session_failed', error: err }) - }) }, handleExample() { this.setData({ diff --git a/src/patient/components/image-merge/index.ts b/src/patient/components/image-merge/index.ts index e6189b1..c554434 100644 --- a/src/patient/components/image-merge/index.ts +++ b/src/patient/components/image-merge/index.ts @@ -96,7 +96,7 @@ Component({ const canvas = res[0].node const ctx = canvas.getContext('2d') - Promise.all(imageList.map(item => this.getImageInfo(item.src))) + Promise.all(imageList.map((item) => this.getImageInfo(item.src))) .then((imageInfos) => { const targetWidth = 750 const pixelRatio = wx.getWindowInfo().pixelRatio @@ -118,16 +118,25 @@ Component({ let currentY = 0 let loadedCount = 0 + // 预计算每张图片的 Y 偏移,确保顺序正确 + const yPositions: number[] = [] + scaledHeights.forEach((h, i) => { + yPositions.push(currentY) + currentY += h + }) + + loadedCount = 0 + imageInfos.forEach((info, index) => { const img = canvas.createImage() img.src = info.path img.onload = () => { - ctx.drawImage(img, 0, currentY, canvasWidth, scaledHeights[index]) + ctx.drawImage(img, 0, yPositions[index], canvasWidth, scaledHeights[index]) // 在每张图片左上角绘制时间,白色字体 const timeText = imageList[index].time || this.formatTime(new Date()) const padding = 20 - const textY = currentY + 40 + const textY = yPositions[index] + 40 // 设置白色字体和阴影以增强可读性 ctx.fillStyle = '#ffffff' @@ -147,14 +156,13 @@ Component({ // 如果是最后一张图片,在其右下角绘制水印 if (index === imageInfos.length - 1) { - const lastImageBottom = currentY + scaledHeights[index] + const lastImageBottom = yPositions[index] + scaledHeights[index] ctx.fillStyle = 'rgba(255, 255, 255, 0.8)' ctx.font = '24px sans-serif' ctx.textAlign = 'right' ctx.fillText('由-TED关爱小助手-小程序生成', canvasWidth - 20, lastImageBottom - 20) } - currentY += scaledHeights[index] loadedCount++ if (loadedCount === imageInfos.length) { diff --git a/src/patient/pages/note/index.ts b/src/patient/pages/note/index.ts index f7c1d2e..2091cb4 100644 --- a/src/patient/pages/note/index.ts +++ b/src/patient/pages/note/index.ts @@ -46,22 +46,45 @@ Page({ method: 'GET', url: '?r=xd/proptosis/baseline-status', data: {}, - }).then((res: any) => { - this.setData({ - hasBaseline: res.hasBaseline, - baselineRecordId: res.baselineRecordId || '', + }) + .then((res: any) => { + this.setData({ + hasBaseline: res.hasBaseline, + baselineRecordId: res.baselineRecordId || '', + }) + // 有基准照时,通过 record-detail 获取基准照详情 + if (res.baselineRecordId) { + this.getBaselineDetail(res.baselineRecordId) + } + }) + .catch((err) => { + console.error('获取基准照状态失败:', err) }) - }).catch((err) => { - console.error('获取基准照状态失败:', err) + }, + + // 通过 record-detail 获取基准照详情 + getBaselineDetail(recordId: string) { + wx.ajax({ + method: 'GET', + url: '?r=xd/proptosis/record-detail', + data: { recordId }, }) + .then((res: any) => { + const firstPhoto = (res.photos || [])[0] + this.setData({ + baselineDate: res.recordDate || '', + baselinePhotoUrl: firstPhoto?.photoUrl || '', + }) + }) + .catch((err) => { + console.error('获取基准照详情失败:', err) + }) }, // 获取突眼记录列表 getRecordList(reset = false) { - if (this.data.loading) - return - if (!reset && !this.data.hasMore) - return + if (this.data.loading) return + if (!reset && !this.data.hasMore) return const page = reset ? 1 : this.data.page @@ -76,34 +99,28 @@ Page({ page, pageSize: this.data.pageSize, }, - }).then((res: any) => { - const list = res.list || [] - const total = res.pagination?.total || 0 + }) + .then((res: any) => { + const list = res.list || [] + const total = res.pagination?.total || 0 - const nextList = reset ? list : [...this.data.recordList, ...list] + const nextList = reset ? list : [...this.data.recordList, ...list] - // 从已加载数据中找到基准照信息(避免后续分页把基准照“翻走”) - const baselineItem = nextList.find((item: RecordItem) => item.isBaseline === 1) - if (baselineItem) { this.setData({ - baselineDate: baselineItem.recordDate, - baselinePhotoUrl: baselineItem.firstPhotoUrl, + recordList: nextList, + loading: false, + refreshing: false, + page: page + 1, + total, + hasMore: nextList.length < total && list.length >= this.data.pageSize, }) - } - this.setData({ - recordList: nextList, - loading: false, - refreshing: false, - page: page + 1, - total, - hasMore: nextList.length < total && list.length >= this.data.pageSize, + wx.stopPullDownRefresh() + }) + .catch((err) => { + console.error('获取记录列表失败:', err) + this.setData({ loading: false, refreshing: false }) + wx.stopPullDownRefresh() }) - wx.stopPullDownRefresh() - }).catch((err) => { - console.error('获取记录列表失败:', err) - this.setData({ loading: false, refreshing: false }) - wx.stopPullDownRefresh() - }) }, onPullDownRefresh() { diff --git a/src/patient/pages/noteAdd/index.ts b/src/patient/pages/noteAdd/index.ts index 090a31b..ed0db6a 100644 --- a/src/patient/pages/noteAdd/index.ts +++ b/src/patient/pages/noteAdd/index.ts @@ -56,7 +56,7 @@ Page({ // 顺序拍摄模式 sequentialShootMode: false, - sequentialShootList: [] as { name: string, type: string, angle: string }[], + sequentialShootList: [] as { name: string; type: string; angle: string }[], sequentialShootIndex: 0, // 替妥尤单抗使用次数选项 (0-9, 9表示大于8) @@ -125,35 +125,37 @@ Page({ 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: '', - } + }) + .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, + }) }) - - 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' }) }) - }).catch((err) => { - wx.hideLoading() - console.error('获取记录详情失败:', err) - wx.showToast({ title: '加载失败', icon: 'none' }) - }) }, // 日期选择 @@ -177,12 +179,14 @@ Page({ method: 'GET', url: '?r=xd/proptosis/baseline-status', data: {}, - }).then((res: any) => { - this.setData({ - hasBaseline: res.hasBaseline, - isBaseline: res.hasBaseline ? this.data.isBaseline : 1, + }) + .then((res: any) => { + this.setData({ + hasBaseline: res.hasBaseline, + isBaseline: res.hasBaseline ? this.data.isBaseline : 1, + }) }) - }).catch(() => {}) + .catch(() => {}) }, // 是否设置为基准照 @@ -203,8 +207,7 @@ Page({ // 左眼度数输入 onLeftEyeInput(e: any) { let val = e.detail.value - if (Number(val) > 999.9) - val = '999.9' + if (Number(val) > 999.9) val = '999.9' this.setData({ leftEye: val, }) @@ -212,8 +215,7 @@ Page({ onRightEyeInput(e: any) { let val = e.detail.value - if (Number(val) > 999.9) - val = '999.9' + if (Number(val) > 999.9) val = '999.9' this.setData({ rightEye: val, }) @@ -221,8 +223,7 @@ Page({ onInterorbitalDistanceInput(e: any) { let val = e.detail.value - if (Number(val) > 999.9) - val = '999.9' + if (Number(val) > 999.9) val = '999.9' this.setData({ interorbitalDistance: val, }) @@ -361,7 +362,7 @@ Page({ // camera 组件上传失败回调 onUploadError(e: any) { const { errMsg } = e.detail - wx.showToast({ title: errMsg || '上传失败', icon: 'none' }) + console.error('上传失败:', errMsg) }, // 关闭相机 @@ -412,7 +413,8 @@ Page({ // 保存记录 handleSave() { - const { recordId, recordDate, treatmentCount, isBaseline, leftEye, rightEye, interorbitalDistance, photoMap } = this.data + const { recordId, recordDate, treatmentCount, isBaseline, leftEye, rightEye, interorbitalDistance, photoMap } = + this.data // 表单验证 if (!recordDate) { @@ -428,13 +430,13 @@ Page({ } // 所有照片全不合规才不允许保存 - const hasCompliantPhoto = photos.some(photo => photo.checkStatus === 1) + 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 photoIds = photos.map((photo) => photo.photoId).join(',') const data: any = { ...(recordId ? { recordId } : {}), @@ -445,12 +447,9 @@ Page({ } // 可选字段 - if (leftEye) - data.leftEye = leftEye - if (rightEye) - data.rightEye = rightEye - if (interorbitalDistance) - data.interorbitalDistance = interorbitalDistance + if (leftEye) data.leftEye = leftEye + if (rightEye) data.rightEye = rightEye + if (interorbitalDistance) data.interorbitalDistance = interorbitalDistance wx.showLoading({ title: '保存中...' }) @@ -458,21 +457,23 @@ Page({ 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' }) }) + .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() { @@ -488,8 +489,7 @@ Page({ }) if (popupType === 'popup18') { this.setData({ isBaseline: 1 }) - } - else { + } else { this.handleSave() } }, @@ -501,8 +501,7 @@ Page({ }) if (popupType === 'popup18') { this.setData({ isBaseline: 0 }) - } - else { + } else { wx.navigateBack() } }, @@ -513,8 +512,7 @@ Page({ popupShow: true, popupType: 'popup16', }) - } - else { + } else { wx.navigateBack() } }, diff --git a/src/patient/pages/noteDiff/index.scss b/src/patient/pages/noteDiff/index.scss index a06a0ae..4bc9f6c 100644 --- a/src/patient/pages/noteDiff/index.scss +++ b/src/patient/pages/noteDiff/index.scss @@ -36,7 +36,7 @@ page { } } .page { - padding-bottom: 80rpx; + padding-bottom: calc(env(safe-area-inset-bottom) + 160rpx); .page-tip { padding: 24rpx 28rpx; background-color: #fff7e9; @@ -288,5 +288,26 @@ page { border-radius: 100rpx 100rpx 100rpx 100rpx; } } + + .footer-fixed { + position: fixed; + bottom: 0; + left: 0; + right: 0; + padding: 24rpx 40rpx calc(env(safe-area-inset-bottom) + 24rpx); + background: #fff; + box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.05); + + .btn1 { + height: 88rpx; + font-size: 36rpx; + color: #ffffff; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(0deg, #e98ff8 0%, #b073ff 100%); + border-radius: 100rpx; + } + } } } diff --git a/src/patient/pages/noteDiff/index.ts b/src/patient/pages/noteDiff/index.ts index b5d1e9c..494fdaa 100644 --- a/src/patient/pages/noteDiff/index.ts +++ b/src/patient/pages/noteDiff/index.ts @@ -1,4 +1,5 @@ const app = getApp() +const licia = require('miniprogram-licia') interface ComparePhoto { recordId: string @@ -17,6 +18,8 @@ interface CompareDate { isSelected?: boolean } +type BaselineItem = CompareDate | null + Page({ data: { // 对比角度 @@ -26,18 +29,24 @@ Page({ angleMap: {} as Record, // 日期选择 - baseline: null as CompareDate | null, + baseline: null as unknown as BaselineItem, nonBaselineList: [] as CompareDate[], selectedDates: [] as string[], + baselineSelected: true, // 对比照片 comparePhotos: [] as ComparePhoto[], // 空状态 hasBaseline: true, + + generateDate: '', }, onLoad() { + const now = new Date() + const generateDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}` + this.setData({ generateDate }) this.getCompareAngle() }, @@ -112,16 +121,17 @@ Page({ newRecordIds.push(newBaseline.recordId) } const selectedDates = this.data.selectedDates.filter((id: string) => newRecordIds.includes(id)) + const baselineSelected = this.data.baselineSelected const nonBaselineList = newList.map((item: CompareDate) => ({ ...item, isSelected: selectedDates.includes(item.recordId), })) this.setData({ - baseline: newBaseline, + baseline: newBaseline ? { ...newBaseline, isSelected: baselineSelected } : null, nonBaselineList, selectedDates, }) - if (selectedDates.length > 0 || newBaseline) { + if (selectedDates.length > 0 || (newBaseline && baselineSelected)) { this.getComparePhotos() } else { this.setData({ comparePhotos: [] }) @@ -139,11 +149,25 @@ Page({ this.setData({ photoAngle: angle.key, photoAngleName: angle.name, + selectedDates: [], + baselineSelected: true, comparePhotos: [], }) this.getCompareDates() }, + // 选择基准照日期 + onBaselineSelect() { + const baselineSelected = !this.data.baselineSelected + const baseline = this.data.baseline + this.setData({ + baselineSelected, + baseline: baseline ? { ...baseline, isSelected: baselineSelected } : null, + }) + // 获取对比照片 + this.getComparePhotos() + }, + // 选择日期 onDateSelect(e: any) { const { recordId } = e.currentTarget.dataset @@ -152,7 +176,7 @@ Page({ if (index > -1) { selectedDates.splice(index, 1) } else { - const maxSelect = this.data.baseline ? 5 : 6 + const maxSelect = this.data.baselineSelected ? 5 : 6 if (selectedDates.length >= maxSelect) { wx.showToast({ title: '最多选择6张对比图', icon: 'none' }) return @@ -170,13 +194,13 @@ Page({ }, // 获取对比照片 - getComparePhotos() { + getComparePhotos: licia.debounce(function (this: any) { const { photoAngle, selectedDates, baseline } = this.data if (!photoAngle) { this.setData({ comparePhotos: [] }) return } - const recordIds = baseline ? [baseline.recordId, ...selectedDates] : selectedDates + const recordIds = baseline && this.data.baselineSelected ? [baseline.recordId, ...selectedDates] : selectedDates if (recordIds.length === 0) { this.setData({ comparePhotos: [] }) return @@ -201,7 +225,7 @@ Page({ .catch((err) => { console.error('获取对比照片失败:', err) }) - }, + }, 300), // 去设置基准照 goSetBaseline() { @@ -212,13 +236,15 @@ Page({ // 生成对比图 handleEdit() { - if (this.data.selectedDates.length === 0) { - wx.showToast({ title: '请选择对比日期', icon: 'none' }) + const { baseline, baselineSelected, selectedDates } = this.data + const hasBaseline = baseline && baselineSelected + const totalCount = selectedDates.length + (hasBaseline ? 1 : 0) + if (totalCount < 2) { + wx.showToast({ title: '请至少选择两个日期', icon: 'none' }) return } - const { baseline, selectedDates } = this.data // 生成长图页也应包含基准照(与预览一致) - const recordIds = baseline ? [baseline.recordId, ...selectedDates] : selectedDates + const recordIds = hasBaseline ? [baseline.recordId, ...selectedDates] : selectedDates wx.navigateTo({ url: `/patient/pages/noteDiffEdit/index?photoAngle=${this.data.photoAngle}&recordIds=${recordIds.join(',')}`, }) diff --git a/src/patient/pages/noteDiff/index.wxml b/src/patient/pages/noteDiff/index.wxml index de74a22..bf810b3 100644 --- a/src/patient/pages/noteDiff/index.wxml +++ b/src/patient/pages/noteDiff/index.wxml @@ -32,7 +32,7 @@ 选择对比日期(可多选) - + {{baseline.recordDate}} @@ -51,7 +51,7 @@ {{photoAngleName}}时间线对比 - 生成日期:{{comparePhotos[0].recordDate}} + 生成日期:{{generateDate}} @@ -99,7 +99,7 @@ - + 生成对比图 diff --git a/src/patient/pages/noteDiffEdit/index.json b/src/patient/pages/noteDiffEdit/index.json index a4f88ee..e9727f8 100644 --- a/src/patient/pages/noteDiffEdit/index.json +++ b/src/patient/pages/noteDiffEdit/index.json @@ -5,5 +5,5 @@ "imageMerge": "/patient/components/image-merge/index", "popup": "/components/popup/index" }, - "navigationBarTitleText": "照片对比分析" + "navigationBarTitleText": "照片对比编辑" } diff --git a/src/patient/pages/noteDiffEdit/index.ts b/src/patient/pages/noteDiffEdit/index.ts index 60a4ba8..8a79163 100644 --- a/src/patient/pages/noteDiffEdit/index.ts +++ b/src/patient/pages/noteDiffEdit/index.ts @@ -21,6 +21,7 @@ Page({ photoAngle: '', photoAngleName: '', + generateDate: '', recordIds: [] as string[], photos: [] as PhotoItem[], loading: false, @@ -28,7 +29,10 @@ Page({ }, onLoad(option: any) { + const now = new Date() + const generateDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}` this.setData({ + generateDate, photoAngle: option.photoAngle || '', recordIds: option.recordIds ? option.recordIds.split(',') : [], }) @@ -121,7 +125,7 @@ Page({ success: (cropRes) => { photos[index].isCropped = true photos[index].croppedUrl = cropRes.tempFilePath - this.setData({ photos }) + this.setData({ photos, mergedImage: '' }) wx.showToast({ title: '裁剪成功', icon: 'success' }) }, fail: (err) => { @@ -147,7 +151,7 @@ Page({ const photos = this.data.photos photos[index].isCropped = false photos[index].croppedUrl = '' - this.setData({ photos }) + this.setData({ photos, mergedImage: '' }) }, // 生成对比图预览 @@ -160,7 +164,9 @@ Page({ const mergeComponent = this.selectComponent('#merge') if (mergeComponent) { - const imageList = photos.map((item) => { + // 确保基准照片排在第一位 + const sorted = [...photos].sort((a, b) => b.isBaseline - a.isBaseline) + const imageList = sorted.map((item) => { const label = item.isBaseline === 1 ? `基准照片 ${item.recordDate} 替妥尤单抗:${item.treatmentCount >= 9 ? '>8' : item.treatmentCount}次` @@ -182,8 +188,50 @@ Page({ return } + this.saveImageToAlbum(mergedImage) + }, + + // 保存图片到相册(带权限引导) + saveImageToAlbum(filePath: string) { + wx.getSetting({ + success: (res) => { + if ( + res.authSetting['scope.writePhotosAlbum'] != undefined && + res.authSetting['scope.writePhotosAlbum'] == true + ) { + this.doSaveImage(filePath) + } else if (res.authSetting['scope.writePhotosAlbum'] == undefined) { + this.doSaveImage(filePath) + } else { + wx.showModal({ + title: '请求授权相册权限', + content: '需要保存对比图到相册,请确认授权', + confirmColor: '#8c75d0', + success: (res) => { + if (res.cancel) { + wx.showToast({ title: '拒绝授权', icon: 'none' }) + } else if (res.confirm) { + wx.openSetting({ + success: (res) => { + if (res.authSetting['scope.writePhotosAlbum'] == true) { + this.doSaveImage(filePath) + } else { + wx.showToast({ title: '授权失败', icon: 'none' }) + } + }, + }) + } + }, + }) + } + }, + }) + }, + + // 执行保存图片 + doSaveImage(filePath: string) { wx.saveImageToPhotosAlbum({ - filePath: mergedImage, + filePath, success: () => { wx.showToast({ title: '保存成功', icon: 'success' }) }, diff --git a/src/patient/pages/noteDiffEdit/index.wxml b/src/patient/pages/noteDiffEdit/index.wxml index 52fcc0b..787fd70 100644 --- a/src/patient/pages/noteDiffEdit/index.wxml +++ b/src/patient/pages/noteDiffEdit/index.wxml @@ -6,7 +6,7 @@ {{photoAngleName}}时间线对比 - 生成日期:{{photos[0].recordDate}} + 生成日期:{{generateDate}}