Files
YGChatCS/src/pages-aigc/detail/detail.vue
duanshuwen dcf67dedfc feat(aigc/detail): add share preview popup
create reusable SharePreviewPopup component for share preview
update ResultActions to emit share event instead of direct native share
add share preview flow in aigc detail page
update native share config with correct image and fallback title
2026-07-16 11:25:39 +08:00

308 lines
7.8 KiB
Vue

<template>
<view class="aigc-detail-page">
<AigcTopBar :points="pointBalance" @back="handleBack" @history="handleOpenRecords" @recharge="handleRecharge" />
<view class="aigc-detail-content">
<template v-if="taskDetail">
<ResultPreview v-if="result.cover" :result="result" />
<ResultMeta :items="metaItems" />
<ResultActions :saving="isSaving" @save="handleSave" @share="handleOpenSharePreview" />
<view class="aigc-detail-text-actions">
<view class="aigc-detail-text-action" @tap="handleRegenerate">再生成一版</view>
<view class="aigc-detail-text-action" @tap="handleChangeTemplate">换个模板</view>
<view class="aigc-detail-text-action" @tap="handleOpenRecords">历史记录</view>
</view>
</template>
</view>
<SharePreviewPopup ref="sharePreviewPopup" :app-name="currentClientName" :title="result.title"
:variant-label="result.variantLabel" :image-url="sharePreviewImage" />
</view>
</template>
<script setup>
import { computed, ref } from "vue";
import { onLoad, onShareAppMessage, onShow } from "@dcloudio/uni-app";
import { getCurrentConfig } from "@/constant/base.js";
import AigcTopBar from "@/pages-aigc/components/AigcTopBar/index.vue";
import { useCurrentCredit } from "@/pages-aigc/hooks/useCurrentCredit.js";
import { getAigcGeneratorTaskDetail } from "@/request/api/AigcApi.js";
import ResultActions from "./components/ResultActions/index.vue";
import ResultMeta from "./components/ResultMeta/index.vue";
import ResultPreview from "./components/ResultPreview/index.vue";
import SharePreviewPopup from "./components/SharePreviewPopup/index.vue";
const { pointBalance, fetchCurrentCredit } = useCurrentCredit();
const taskId = ref("");
const taskDetail = ref(null);
const isSaving = ref(false);
const sharePreviewPopup = ref(null);
const currentClientName = String(getCurrentConfig()?.name || "").trim() || "微信小程序";
const GENERATOR_TYPE_TEXT = {
0: "图片版",
1: "视频版",
};
const TASK_STATUS_TEXT = {
0: "排队中",
1: "生成中",
2: "已完成",
3: "生成失败",
4: "视频生成中",
};
const getMappedText = (mapping, value, prefix) => {
if (Object.prototype.hasOwnProperty.call(mapping, value)) {
return mapping[value];
}
return value === undefined || value === null || value === "" ? "-" : `${prefix}${value}`;
};
const result = computed(() => {
const record = taskDetail.value || {};
const imageUrl = record.imageResultUrl || "";
const videoUrl = record.videoResultUrl || "";
const isVideo = Boolean(videoUrl) && (record.generatorType === 1 || !imageUrl);
const typeText = getMappedText(GENERATOR_TYPE_TEXT, record.generatorType, "类型");
const statusText = getMappedText(TASK_STATUS_TEXT, record.taskStatus, "状态");
const consumedText =
record.generatorCost === undefined || record.generatorCost === null || record.generatorCost === ""
? "-"
: `${record.generatorCost}积分`;
return {
id: record.taskId,
title: record.itemTitle || record.taskId || "生成结果",
cover: isVideo ? videoUrl : imageUrl || videoUrl,
mediaType: isVideo ? "video" : "image",
accent: "#0a4d46",
variantLabel: typeText,
resultLabel: statusText,
consumedText,
desc: record.itemSubTitle || statusText,
};
});
const metaItems = computed(() => {
const record = taskDetail.value;
if (!record) return [];
const completeTime =
record.videoGeneratorCompleteTime || record.imageGeneratorCompleteTime || record.createTime || "-";
return [
{
label: "状态",
value: result.value.resultLabel,
},
{
label: "已消耗",
value: result.value.consumedText,
},
{
label: "完成时间",
value: completeTime,
},
];
});
const sharePreviewImage = computed(() => taskDetail.value?.imageResultUrl || "");
const showPlaceholderToast = (title) => {
uni.showToast({
title,
icon: "none",
});
};
const fetchTaskDetail = async () => {
uni.showLoading({
title: "加载中",
mask: true,
});
try {
const res = await getAigcGeneratorTaskDetail({ taskId: taskId.value });
if (res?.code === 0 && res.data && typeof res.data === "object" && !Array.isArray(res.data)) {
taskDetail.value = res.data;
return;
}
taskDetail.value = null;
showPlaceholderToast("获取任务详情失败");
} catch (error) {
console.error("获取AIGC生成任务详情失败", error);
taskDetail.value = null;
showPlaceholderToast("获取任务详情失败");
} finally {
uni.hideLoading();
}
};
onLoad((query = {}) => {
taskId.value = String(query.taskId || "").trim();
if (!taskId.value) {
showPlaceholderToast("缺少任务ID");
return;
}
fetchTaskDetail();
});
onShow(() => {
fetchCurrentCredit();
});
onShareAppMessage(() => {
const shareInfo = {
title: taskDetail.value?.itemTitle || "分享旅行作品",
path: taskId.value
? `/pages-aigc/detail/detail?taskId=${encodeURIComponent(taskId.value)}`
: "/pages-aigc/home/home",
};
const imageUrl = sharePreviewImage.value;
if (imageUrl) {
shareInfo.imageUrl = imageUrl;
}
return shareInfo;
});
const handleOpenSharePreview = () => {
sharePreviewPopup.value?.open();
};
const handleBack = () => {
const pages = getCurrentPages();
if (pages.length > 1) {
uni.navigateBack();
}
};
const handleRecharge = () => {
showPlaceholderToast("积分充值");
};
const downloadImage = (url) =>
new Promise((resolve, reject) => {
if (!/^https?:\/\//i.test(url)) {
resolve(url);
return;
}
uni.downloadFile({
url,
success: (res) => {
if (res.statusCode === 200 && res.tempFilePath) {
resolve(res.tempFilePath);
return;
}
reject(res);
},
fail: reject,
});
});
const saveImageToAlbum = (filePath) =>
new Promise((resolve, reject) => {
uni.saveImageToPhotosAlbum({
filePath,
success: resolve,
fail: reject,
});
});
const isAlbumPermissionDenied = (error) => {
const errorMessage = String(error?.errMsg || "").toLowerCase();
return (
errorMessage.includes("auth deny") ||
errorMessage.includes("authorize:no response") ||
errorMessage.includes("permission denied")
);
};
const promptAlbumPermission = () => {
uni.showModal({
title: "需要相册权限",
content: "请在设置中允许保存图片到相册。",
confirmText: "去设置",
success: ({ confirm }) => {
if (confirm) {
uni.openSetting();
}
},
});
};
const handleSave = async () => {
if (isSaving.value) return;
if (result.value.mediaType !== "image") {
showPlaceholderToast("当前结果不是图片");
return;
}
if (!result.value.cover) {
showPlaceholderToast("暂无可保存的图片");
return;
}
isSaving.value = true;
uni.showLoading({
title: "保存中",
mask: true,
});
let saveError = null;
try {
const filePath = await downloadImage(result.value.cover);
await saveImageToAlbum(filePath);
} catch (error) {
saveError = error;
} finally {
uni.hideLoading();
isSaving.value = false;
}
if (!saveError) {
uni.showToast({
title: "已保存到相册",
icon: "success",
});
return;
}
if (isAlbumPermissionDenied(saveError)) {
promptAlbumPermission();
} else {
showPlaceholderToast("保存图片失败");
}
};
const handleRegenerate = () => {
showPlaceholderToast("再生成一版");
};
const handleChangeTemplate = () => {
uni.navigateTo({
url: "/pages-aigc/home/home",
fail: () => showPlaceholderToast("换个模板"),
});
};
const handleOpenRecords = () => {
uni.navigateTo({
url: "/pages-aigc/record/record",
fail: () => showPlaceholderToast("历史记录"),
});
};
</script>
<style scoped lang="scss">
@import "./styles/index.scss";
</style>