Files
YGChatCS/src/pages-aigc/useTemplate/useTemplate.vue
duanshuwen b5952961fe feat: add mini-program privacy auth, update oss urls and refine photo guide
update oss static resource urls to oss.nianxx.cn
refactor privacy component to be reusable, add wechat mini-program privacy authorization check before image picking
add image urls to photo guide examples, refine photo guide ui styles and remove unused avatarSrc prop
2026-07-13 20:14:49 +08:00

409 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="aigc-use-template-page">
<AigcTopBar :points="pointBalance" recharge-label="充值" @back="handleBack" @history="handleHistory"
@recharge="handleRecharge" />
<view class="aigc-use-template-body">
<view class="aigc-use-template-content">
<TemplateVersionHero :template="currentTemplate" />
<GenerateConfirmPanel v-if="currentStep === 'generateConfirm'" :cost="currentCost" :balance="pointBalance"
@generate="handleGenerate" @recharge="handleRecharge" />
<GeneratingProgressPanel v-else-if="currentStep === 'generating'" :cost="currentCost" :progress="8"
@complete="handleGenerateComplete" />
<template v-else>
<VersionOptionList :options="templateItems" :selected-value="selectedTemplateItemId"
@select="handleSelectTemplateItem" />
<text class="aigc-use-template-note">
动效视频版会先生图再升级为 5 秒内动效视频
</text>
</template>
</view>
</view>
<view v-if="currentStep === 'uploadGuide'" class="aigc-use-template-popup-host">
<PhotoGuidePanel :positive-examples="positiveExamples" :negative-examples="negativeExamples"
@close="handleCloseGuide" @confirm="handleConfirmGuide" />
</view>
<view v-if="currentStep === 'photoPick'" class="aigc-use-template-popup-host">
<PhotoPickDrawer :loading="isUploading" @close="handleCloseGuide" @camera="handlePickCamera"
@album="handlePickAlbum" />
</view>
<view v-if="currentStep === 'photoConfirm'" class="aigc-use-template-popup-host">
<PhotoConfirmDrawer :avatar-src="selectedImageLocalPath || guideAvatar" @close="handleClosePhotoConfirm"
@confirm="handleConfirmPhoto" />
</view>
<PointInsufficientDialog v-if="pointDialogVisible" :cost="currentCost" :balance="pointBalance"
@cancel="handleClosePointDialog" @recharge="handlePointRecharge" />
<Privacy :visible="privacyVisible" :contract-name="privacyContractName" @agree="handlePrivacyAgree"
@disagree="handlePrivacyDisagree" />
</view>
</template>
<script setup>
import { computed, ref } from "vue";
import { onLoad, 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 {
createAigcGeneratorTask,
getAigcTemplateItemList,
getAigcTemplateList,
} from "@/request/api/AigcApi.js";
import { updateImageFile } from "@/request/api/UpdateFile.js";
import guideAvatar from "./assets/xiaoqi-avatar.png";
import GenerateConfirmPanel from "./components/GenerateConfirmPanel/index.vue";
import GeneratingProgressPanel from "./components/GeneratingProgressPanel/index.vue";
import PhotoConfirmDrawer from "./components/PhotoConfirmDrawer/index.vue";
import PhotoGuidePanel from "./components/PhotoGuidePanel/index.vue";
import PhotoPickDrawer from "./components/PhotoPickDrawer/index.vue";
import PointInsufficientDialog from "./components/PointInsufficientDialog/index.vue";
import TemplateVersionHero from "./components/TemplateVersionHero/index.vue";
import VersionOptionList from "./components/VersionOptionList/index.vue";
import { negativeExamples, positiveExamples } from "./data/photoGuideExamples.js";
const { pointBalance, fetchCurrentCredit } = useCurrentCredit();
const templateId = ref("");
const templates = ref([]);
const templateItems = ref([]);
const selectedTemplateItemId = ref("");
const selectedImageLocalPath = ref("");
const uploadedImageUrl = ref("");
const createdTaskId = ref("");
const currentStep = ref("version");
const pointDialogVisible = ref(false);
const isUploading = ref(false);
const isCreatingTask = ref(false);
const privacyVisible = ref(false);
const privacyContractName = ref("隐私保护指引");
const pendingImageSourceType = ref("");
const currentTemplate = computed(() => {
return templates.value.find((item) => item.templateId === templateId.value) || templates.value[0] || {};
});
const currentTemplateItem = computed(() => {
return (
templateItems.value.find((item) => item.templateItemId === selectedTemplateItemId.value) ||
templateItems.value[0] ||
{}
);
});
const currentCost = computed(() => {
return Number(currentTemplateItem.value.generatorCost) || 0;
});
const showPlaceholderToast = (title) => {
uni.showToast({
title,
icon: "none",
});
};
const resetSelectedPhoto = () => {
selectedImageLocalPath.value = "";
uploadedImageUrl.value = "";
};
const fetchTemplateList = async () => {
try {
const res = await getAigcTemplateList();
if (res?.code === 0 && Array.isArray(res.data)) {
templates.value = res.data;
if (!templateId.value && res.data[0]?.templateId) {
templateId.value = res.data[0].templateId;
}
}
} catch (error) {
console.warn("获取AIGC模板列表失败", error);
}
};
const fetchTemplateItemList = async () => {
if (!templateId.value) {
templateItems.value = [];
selectedTemplateItemId.value = "";
return;
}
try {
const res = await getAigcTemplateItemList({ templateId: templateId.value });
if (res?.code === 0 && Array.isArray(res.data)) {
templateItems.value = res.data;
selectedTemplateItemId.value = res.data[0]?.templateItemId || "";
}
} catch (error) {
console.warn("获取AIGC模板生成项失败", error);
}
};
const fetchPageData = async () => {
await fetchTemplateList();
await fetchTemplateItemList();
};
onLoad((query = {}) => {
templateId.value = query.templateId || "";
fetchPageData();
});
onShow(() => {
fetchCurrentCredit();
});
const handleBack = () => {
const pages = getCurrentPages();
if (pages.length > 1) {
uni.navigateBack();
}
};
const handleHistory = () => {
uni.navigateTo({
url: "/pages-aigc/record/record",
fail: () => showPlaceholderToast("最近任务"),
});
};
const handleRecharge = () => {
uni.navigateTo({
url: "/pages-aigc/recharge/recharge",
fail: () => showPlaceholderToast("积分充值"),
});
};
const handleSelectTemplateItem = (option) => {
if (!option?.templateItemId) return;
selectedTemplateItemId.value = option.templateItemId;
resetSelectedPhoto();
currentStep.value = "uploadGuide";
};
const handleCloseGuide = () => {
currentStep.value = "version";
};
const handleConfirmGuide = () => {
currentStep.value = "photoPick";
};
const getChosenFilePath = (res) => {
return res?.tempFiles?.[0]?.tempFilePath || res?.tempFilePaths?.[0] || res?.tempFiles?.[0]?.path || "";
};
const isChooseCanceled = (error) => {
const message = String(error?.errMsg || error?.message || "");
return message.includes("cancel") || message.includes("取消");
};
const handleChooseImageSuccess = async (res) => {
const filePath = getChosenFilePath(res);
if (!filePath) {
showPlaceholderToast("请选择图片");
return;
}
selectedImageLocalPath.value = filePath;
uploadedImageUrl.value = "";
isUploading.value = true;
uni.showLoading({
title: "上传中",
mask: true,
});
try {
const uploadRes = await updateImageFile(filePath);
if (uploadRes?.code === 0 && uploadRes.data) {
uploadedImageUrl.value = uploadRes.data;
currentStep.value = "photoConfirm";
} else {
resetSelectedPhoto();
showPlaceholderToast(uploadRes?.msg || "图片上传失败");
}
} catch (error) {
resetSelectedPhoto();
console.warn("上传AIGC生成图片失败", error);
showPlaceholderToast("图片上传失败");
} finally {
isUploading.value = false;
uni.hideLoading();
}
};
const handleChooseImageFail = (error) => {
if (!isChooseCanceled(error)) {
console.warn("选择AIGC生成图片失败", error);
showPlaceholderToast("选择图片失败");
}
};
const chooseImageWithSource = (sourceType) => {
// #ifdef MP-WEIXIN
uni.chooseMedia({
count: 1,
mediaType: ["image"],
sourceType: [sourceType],
sizeType: ["compressed"],
success: handleChooseImageSuccess,
fail: handleChooseImageFail,
});
// #endif
// #ifndef MP-WEIXIN
uni.chooseImage({
count: 1,
sizeType: ["compressed"],
sourceType: [sourceType],
success: handleChooseImageSuccess,
fail: handleChooseImageFail,
});
// #endif
};
const chooseAndUploadImage = (sourceType) => {
if (isUploading.value || pendingImageSourceType.value) return;
// #ifdef MP-WEIXIN
pendingImageSourceType.value = sourceType;
wx.getPrivacySetting({
success: (res) => {
if (res?.needAuthorization) {
privacyContractName.value = res.privacyContractName || "隐私保护指引";
privacyVisible.value = true;
return;
}
pendingImageSourceType.value = "";
chooseImageWithSource(sourceType);
},
fail: (error) => {
pendingImageSourceType.value = "";
console.warn("检查微信隐私授权失败", error);
showPlaceholderToast("隐私授权检查失败");
},
});
// #endif
// #ifndef MP-WEIXIN
chooseImageWithSource(sourceType);
// #endif
};
const handlePrivacyAgree = () => {
const sourceType = pendingImageSourceType.value;
privacyVisible.value = false;
pendingImageSourceType.value = "";
if (sourceType) {
chooseImageWithSource(sourceType);
}
};
const handlePrivacyDisagree = () => {
privacyVisible.value = false;
pendingImageSourceType.value = "";
};
const handlePickCamera = () => {
chooseAndUploadImage("camera");
};
const handlePickAlbum = () => {
chooseAndUploadImage("album");
};
const handleClosePhotoConfirm = () => {
currentStep.value = "photoPick";
};
const handleConfirmPhoto = () => {
if (!uploadedImageUrl.value) {
showPlaceholderToast("请先上传图片");
currentStep.value = "photoPick";
return;
}
currentStep.value = "generateConfirm";
};
const handleGenerate = async () => {
if (isCreatingTask.value) return;
const templateItemId = currentTemplateItem.value.templateItemId;
if (!templateItemId) {
showPlaceholderToast("请选择生成类型");
return;
}
if (!uploadedImageUrl.value) {
showPlaceholderToast("请先上传图片");
currentStep.value = "photoPick";
return;
}
const cost = currentCost.value;
const balance = Number(pointBalance.value) || 0;
if (balance < cost) {
pointDialogVisible.value = true;
return;
}
isCreatingTask.value = true;
uni.showLoading({
title: "创建中",
mask: true,
});
try {
const res = await createAigcGeneratorTask({
templateItemId,
imageUrlList: [uploadedImageUrl.value],
});
if (res?.code === 0 && res.data) {
createdTaskId.value = res.data;
currentStep.value = "generating";
fetchCurrentCredit();
} else {
showPlaceholderToast(res?.msg || "创建任务失败");
}
} catch (error) {
console.warn("创建AIGC生成任务失败", error);
showPlaceholderToast("创建任务失败");
} finally {
isCreatingTask.value = false;
uni.hideLoading();
}
};
const handleGenerateComplete = () => {
if (!createdTaskId.value) {
showPlaceholderToast("任务创建失败");
return;
}
uni.navigateTo({
url: `/pages-aigc/detail/detail?taskId=${encodeURIComponent(createdTaskId.value)}`,
});
};
const handleClosePointDialog = () => {
pointDialogVisible.value = false;
};
const handlePointRecharge = () => {
pointDialogVisible.value = false;
handleRecharge();
};
</script>
<style scoped lang="scss">
@import "./styles/index.scss";
</style>