From b33096b92524e141ba4a553b8d86b521bed0ad76 Mon Sep 17 00:00:00 2001
From: “mitao” <“2763622819@qq.com”>
Date: 星期五, 24 四月 2026 20:17:20 +0800
Subject: [PATCH] bugfix

---
 traditionalApp/utils/utils.js |  212 +++++++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 180 insertions(+), 32 deletions(-)

diff --git a/traditionalApp/utils/utils.js b/traditionalApp/utils/utils.js
index 6bf4230..0e7c2af 100644
--- a/traditionalApp/utils/utils.js
+++ b/traditionalApp/utils/utils.js
@@ -1,75 +1,223 @@
+const SAVE_IMAGE_TIMEOUT_MS = 15000
+
+/**
+ * 保存图片到系统相册,APP 端优先走下载/本地文件链路,避免云打包后 getImageInfo 卡住。
+ */
 export const saveImage = (url) => {
+	const normalizedUrl = normalizeOptionalText(url)
+	if (!normalizedUrl) {
+		uni.showToast({
+			title: '图片地址为空',
+			icon: 'none'
+		})
+		return
+	}
 	uni.showLoading({
 		title: '正在保存图片...'
-	});
-	if (normalizeOptionalText(url).startsWith('data:image/')) {
-		uni.hideLoading();
+	})
+	// #ifdef APP-PLUS
+	saveImageInApp(normalizedUrl)
+	return
+	// #endif
+	saveImageWithAlbumAuth(normalizedUrl)
+}
+
+/**
+ * APP 环境保存图片,远程图片先下载,base64 图片先写入本地临时文件。
+ */
+const saveImageInApp = async (url) => {
+	try {
+		const filePath = await resolveAppImageFilePath(url)
+		await withTimeout(saveImageToAlbum(filePath), '保存超时')
+		showSaveImageSuccess()
+	} catch (error) {
+		showSaveImageFailure(error)
+	}
+}
+
+/**
+ * 非 APP 环境按原授权流程保存图片。
+ */
+const saveImageWithAlbumAuth = (url) => {
+	if (url.startsWith('data:image/')) {
+		uni.hideLoading()
 		uni.showToast({
-			title: "当前图片暂不支持直接保存,请稍后重试!",
+			title: '当前图片暂不支持直接保存,请稍后重试!',
 			icon: 'none'
-		});
+		})
 		return
 	}
 	uni.getSetting({
 		success: async (res) => {
-			if (!res.authSetting["scope.writePhotosAlbum"]) {
+			if (!res.authSetting['scope.writePhotosAlbum']) {
 				uni.authorize({
-					scope: "scope.writePhotosAlbum",
+					scope: 'scope.writePhotosAlbum',
 					success: async () => {
 						await saveImageFile(url)
 					},
 					fail: () => {
-						uni.hideLoading();
+						uni.hideLoading()
 						uni.showModal({
-							title: "权限未授权",
-							content: "请前往设置中开启相册权限",
+							title: '权限未授权',
+							content: '请前往设置中开启相册权限',
 							success: (res) => {
 								if (res.confirm) {
-									uni.openSetting();
+									uni.openSetting()
 								}
 							}
-						});
+						})
 					}
-				});
+				})
 			} else {
 				await saveImageFile(url)
 			}
 		},
 		fail: () => {
-			uni.hideLoading();
+			uni.hideLoading()
 			uni.showToast({
-				title: "获取权限失败!",
+				title: '获取权限失败!',
 				icon: 'error'
-			});
+			})
 		}
-	});
-};
+	})
+}
+
+/**
+ * 根据图片类型解析 APP 端可保存的本地文件路径。
+ */
+const resolveAppImageFilePath = async (url) => {
+	if (url.startsWith('data:image/')) {
+		return writeBase64ImageToAppFile(url)
+	}
+	if (/^https?:\/\//i.test(url)) {
+		return downloadImageToTempFile(url)
+	}
+	return url
+}
+
+/**
+ * 下载远程图片到临时文件,避免 APP 端 getImageInfo 不回调导致 loading 卡住。
+ */
+const downloadImageToTempFile = (url) => {
+	return withTimeout(new Promise((resolve, reject) => {
+		uni.downloadFile({
+			url,
+			success: (result) => {
+				if (result.statusCode !== 200 || !result.tempFilePath) {
+					reject(new Error('二维码下载失败'))
+					return
+				}
+				resolve(result.tempFilePath)
+			},
+			fail: () => reject(new Error('二维码下载失败'))
+		})
+	}), '二维码下载超时')
+}
+
+/**
+ * 将 dataURL 图片写入 APP 私有临时文件。
+ */
+const writeBase64ImageToAppFile = (dataUrl) => {
+	return withTimeout(new Promise((resolve, reject) => {
+		const matchedData = dataUrl.match(/^data:image\/(png|jpeg|jpg);base64,(.+)$/i)
+		if (!matchedData) {
+			reject(new Error('二维码图片格式不正确'))
+			return
+		}
+		// #ifdef APP-PLUS
+		const extension = matchedData[1].toLowerCase() === 'jpg' ? 'jpg' : matchedData[1].toLowerCase()
+		const fileName = `share-qrcode-${Date.now()}.${extension}`
+		const bitmap = new plus.nativeObj.Bitmap(fileName)
+		bitmap.loadBase64Data(dataUrl, () => {
+			const filePath = `_doc/${fileName}`
+			bitmap.save(filePath, {}, () => {
+				bitmap.clear()
+				resolve(filePath)
+			}, () => {
+				bitmap.clear()
+				reject(new Error('二维码图片写入失败'))
+			})
+		}, () => {
+			bitmap.clear()
+			reject(new Error('二维码图片写入失败'))
+		})
+		// #endif
+		// #ifndef APP-PLUS
+		reject(new Error('当前环境暂不支持保存该图片'))
+		// #endif
+	}), '二维码图片写入超时')
+}
+
+/**
+ * 保存本地图片文件到系统相册。
+ */
+const saveImageToAlbum = (filePath) => {
+	return new Promise((resolve, reject) => {
+		uni.saveImageToPhotosAlbum({
+			filePath,
+			success: resolve,
+			fail: () => reject(new Error('保存失败,请检查相册权限'))
+		})
+	})
+}
 
 /**
  * 下载图片并写入系统相册。
  */
 const saveImageFile = async (url) => {
 	try {
-		const imageInfo = await uni.getImageInfo({
+		const imageInfo = await withTimeout(uni.getImageInfo({
 			src: url
-		});
-		await uni.saveImageToPhotosAlbum({
-			filePath: imageInfo.path,
-		});
-		uni.hideLoading();
-		uni.showToast({
-			title: "保存成功!"
-		});
+		}), '图片加载超时')
+		await withTimeout(saveImageToAlbum(imageInfo.path), '保存超时')
+		showSaveImageSuccess()
 	} catch (error) {
-		uni.hideLoading();
-		uni.showToast({
-			title: "保存失败,请重试!",
-			icon: 'error'
-		});
+		showSaveImageFailure(error)
 	}
 }
 
 /**
+ * 为异步保存步骤增加超时保护,避免云打包后回调丢失导致 loading 常驻。
+ */
+const withTimeout = (promise, message) => {
+	return new Promise((resolve, reject) => {
+		const timer = setTimeout(() => {
+			reject(new Error(message || '操作超时'))
+		}, SAVE_IMAGE_TIMEOUT_MS)
+		Promise.resolve(promise)
+			.then((value) => {
+				clearTimeout(timer)
+				resolve(value)
+			})
+			.catch((error) => {
+				clearTimeout(timer)
+				reject(error)
+			})
+	})
+}
+
+/**
+ * 展示保存成功状态。
+ */
+const showSaveImageSuccess = () => {
+	uni.hideLoading()
+	uni.showToast({
+		title: '保存成功!'
+	})
+}
+
+/**
+ * 展示保存失败状态,确保任何失败路径都会关闭 loading。
+ */
+const showSaveImageFailure = (error) => {
+	uni.hideLoading()
+	uni.showToast({
+		title: normalizeOptionalText(error && error.message) || '保存失败,请重试!',
+		icon: 'none'
+	})
+}
+
+/**
  * 将可选文本归一化为空字符串。
  */
 export const normalizeOptionalText = (value) => {

--
Gitblit v1.7.1