Refactor the shared wechat login code utility by moving it from the recharge service to a standalone module. Add the sensitive request flag to the createAigcGeneratorTask POST API call. Update the aigc detail and template use pages to fetch and pass the wechat login code when creating generation tasks. Remove the redundant login code implementation from the recharge payment service.
399 lines
11 KiB
Vue
399 lines
11 KiB
Vue
<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" />
|
||
|
||
<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" @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 { getWechatLoginCode } from "@/pages-aigc/services/wechatLogin.js";
|
||
import {
|
||
createAigcGeneratorTask,
|
||
getAigcTemplateItemList,
|
||
getAigcTemplateList,
|
||
} from "@/request/api/AigcApi.js";
|
||
import { updateImageFile } from "@/request/api/UpdateFile.js";
|
||
import GenerateConfirmPanel from "./components/GenerateConfirmPanel/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 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 wxLoginCode = await getWechatLoginCode();
|
||
const res = await createAigcGeneratorTask({
|
||
templateItemId,
|
||
imageUrlList: [uploadedImageUrl.value],
|
||
wxLoginCode,
|
||
});
|
||
|
||
const taskId = String(res?.data || "").trim();
|
||
if (res?.code === 0 && taskId) {
|
||
fetchCurrentCredit();
|
||
uni.redirectTo({
|
||
url: `/pages-aigc/progress/progress?taskId=${encodeURIComponent(taskId)}`,
|
||
fail: () => showPlaceholderToast("打开任务进度失败"),
|
||
});
|
||
} else {
|
||
showPlaceholderToast(res?.msg || "创建任务失败");
|
||
}
|
||
} catch (error) {
|
||
console.warn("创建AIGC生成任务失败", error);
|
||
showPlaceholderToast("创建任务失败");
|
||
} finally {
|
||
uni.hideLoading();
|
||
isCreatingTask.value = false;
|
||
}
|
||
};
|
||
|
||
const handleClosePointDialog = () => {
|
||
pointDialogVisible.value = false;
|
||
};
|
||
|
||
const handlePointRecharge = () => {
|
||
pointDialogVisible.value = false;
|
||
handleRecharge();
|
||
};
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
@import "./styles/index.scss";
|
||
</style>
|