feat(aigc): add video save support and improve consent flow
- add support for saving both image and video AIGC generation results - refactor ResultActions component to accept mediaType prop and use dynamic save button text - remove AIGC consent dialog from ChatQuickAccess and implement it on AIGC home page - add WeChat mini-program privacy authorization check before saving media - create reusable AigcConsentDialog component with proper styling
This commit is contained in:
@@ -6,8 +6,8 @@
|
||||
<template v-if="taskDetail">
|
||||
<ResultPreview v-if="result.cover" :result="result" />
|
||||
<ResultMeta :items="metaItems" />
|
||||
<ResultActions :saving="isSaving" :share-ready="Boolean(shareKey)" :share-preparing="isPreparingShare"
|
||||
@save="handleSave" @prepare-share="handlePrepareShare" />
|
||||
<ResultActions :saving="isSaving" :media-type="result.mediaType" :share-ready="Boolean(shareKey)"
|
||||
:share-preparing="isPreparingShare" @save="handleSave" @prepare-share="handlePrepareShare" />
|
||||
|
||||
<view class="aigc-detail-text-actions">
|
||||
<view class="aigc-detail-text-action" @tap="handleRegenerate">再生成一版</view>
|
||||
@@ -17,12 +17,15 @@
|
||||
</template>
|
||||
</view>
|
||||
|
||||
<Privacy :visible="privacyVisible" :contract-name="privacyContractName" @agree="handlePrivacyAgree"
|
||||
@disagree="handlePrivacyDisagree" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from "vue";
|
||||
import { onLoad, onShareAppMessage, onShow } from "@dcloudio/uni-app";
|
||||
import Privacy from "@/components/Privacy/index.vue";
|
||||
import AigcTopBar from "@/pages-aigc/components/AigcTopBar/index.vue";
|
||||
import { useCurrentCredit } from "@/pages-aigc/hooks/useCurrentCredit.js";
|
||||
import {
|
||||
@@ -39,6 +42,9 @@ const taskDetail = ref(null);
|
||||
const isSaving = ref(false);
|
||||
const isPreparingShare = ref(false);
|
||||
const shareKey = ref("");
|
||||
const privacyVisible = ref(false);
|
||||
const privacyContractName = ref("隐私保护指引");
|
||||
const pendingSaveAfterPrivacy = ref(false);
|
||||
|
||||
const GENERATOR_TYPE_TEXT = {
|
||||
0: "图片版",
|
||||
@@ -220,7 +226,7 @@ const handleRecharge = () => {
|
||||
showPlaceholderToast("积分充值");
|
||||
};
|
||||
|
||||
const downloadImage = (url) =>
|
||||
const downloadMedia = (url) =>
|
||||
new Promise((resolve, reject) => {
|
||||
if (!/^https?:\/\//i.test(url)) {
|
||||
resolve(url);
|
||||
@@ -249,6 +255,46 @@ const saveImageToAlbum = (filePath) =>
|
||||
});
|
||||
});
|
||||
|
||||
const saveVideoToAlbum = (filePath) =>
|
||||
new Promise((resolve, reject) => {
|
||||
uni.saveVideoToPhotosAlbum({
|
||||
filePath,
|
||||
success: resolve,
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
|
||||
const ensureAlbumPermission = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.getSetting({
|
||||
success: ({ authSetting = {} }) => {
|
||||
const permission = authSetting["scope.writePhotosAlbum"];
|
||||
if (permission === true) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
if (permission === false) {
|
||||
reject({ errMsg: "scope.writePhotosAlbum permission denied" });
|
||||
return;
|
||||
}
|
||||
|
||||
uni.authorize({
|
||||
scope: "scope.writePhotosAlbum",
|
||||
success: resolve,
|
||||
fail: reject,
|
||||
});
|
||||
},
|
||||
fail: reject,
|
||||
});
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
resolve();
|
||||
// #endif
|
||||
});
|
||||
|
||||
const isAlbumPermissionDenied = (error) => {
|
||||
const errorMessage = String(error?.errMsg || "").toLowerCase();
|
||||
return (
|
||||
@@ -258,10 +304,11 @@ const isAlbumPermissionDenied = (error) => {
|
||||
);
|
||||
};
|
||||
|
||||
const promptAlbumPermission = () => {
|
||||
const promptAlbumPermission = (mediaType) => {
|
||||
const mediaName = mediaType === "video" ? "视频" : "图片";
|
||||
uni.showModal({
|
||||
title: "需要相册权限",
|
||||
content: "请在设置中允许保存图片到相册。",
|
||||
content: `请在设置中允许保存${mediaName}到相册。`,
|
||||
confirmText: "去设置",
|
||||
success: ({ confirm }) => {
|
||||
if (confirm) {
|
||||
@@ -271,51 +318,104 @@ const promptAlbumPermission = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
const saveResultMedia = async () => {
|
||||
if (isSaving.value) return;
|
||||
|
||||
if (result.value.mediaType !== "image") {
|
||||
showPlaceholderToast("当前结果不是图片");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.value.cover) {
|
||||
showPlaceholderToast("暂无可保存的图片");
|
||||
const mediaType = result.value.mediaType;
|
||||
const isVideo = mediaType === "video";
|
||||
const mediaName = isVideo ? "视频" : "图片";
|
||||
const mediaUrl = isVideo ? result.value.videoResultUrl : result.value.imageResultUrl;
|
||||
if (!mediaUrl) {
|
||||
showPlaceholderToast(`暂无可保存的${mediaName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
isSaving.value = true;
|
||||
uni.showLoading({
|
||||
title: "保存中",
|
||||
mask: true,
|
||||
});
|
||||
|
||||
let saveError = null;
|
||||
let loadingVisible = false;
|
||||
try {
|
||||
const filePath = await downloadImage(result.value.cover);
|
||||
await saveImageToAlbum(filePath);
|
||||
await ensureAlbumPermission();
|
||||
uni.showLoading({
|
||||
title: "保存中",
|
||||
mask: true,
|
||||
});
|
||||
loadingVisible = true;
|
||||
|
||||
const filePath = await downloadMedia(mediaUrl);
|
||||
if (isVideo) {
|
||||
await saveVideoToAlbum(filePath);
|
||||
} else {
|
||||
await saveImageToAlbum(filePath);
|
||||
}
|
||||
} catch (error) {
|
||||
saveError = error;
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
if (loadingVisible) {
|
||||
uni.hideLoading();
|
||||
}
|
||||
isSaving.value = false;
|
||||
}
|
||||
|
||||
if (!saveError) {
|
||||
uni.showToast({
|
||||
title: "已保存到相册",
|
||||
title: `${mediaName}已保存`,
|
||||
icon: "success",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAlbumPermissionDenied(saveError)) {
|
||||
promptAlbumPermission();
|
||||
promptAlbumPermission(mediaType);
|
||||
} else {
|
||||
showPlaceholderToast("保存图片失败");
|
||||
showPlaceholderToast(`保存${mediaName}失败`);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
if (isSaving.value || pendingSaveAfterPrivacy.value) return;
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
pendingSaveAfterPrivacy.value = true;
|
||||
wx.getPrivacySetting({
|
||||
success: (res) => {
|
||||
if (res?.needAuthorization) {
|
||||
privacyContractName.value = res.privacyContractName || "隐私保护指引";
|
||||
privacyVisible.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
pendingSaveAfterPrivacy.value = false;
|
||||
saveResultMedia();
|
||||
},
|
||||
fail: (error) => {
|
||||
pendingSaveAfterPrivacy.value = false;
|
||||
console.warn("检查微信隐私授权失败", error);
|
||||
showPlaceholderToast("隐私授权检查失败");
|
||||
},
|
||||
});
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
saveResultMedia();
|
||||
// #endif
|
||||
};
|
||||
|
||||
const handlePrivacyAgree = () => {
|
||||
const shouldSave = pendingSaveAfterPrivacy.value;
|
||||
privacyVisible.value = false;
|
||||
pendingSaveAfterPrivacy.value = false;
|
||||
|
||||
if (shouldSave) {
|
||||
saveResultMedia();
|
||||
}
|
||||
};
|
||||
|
||||
const handlePrivacyDisagree = () => {
|
||||
privacyVisible.value = false;
|
||||
pendingSaveAfterPrivacy.value = false;
|
||||
};
|
||||
|
||||
const handleRegenerate = () => {
|
||||
showPlaceholderToast("再生成一版");
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user