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