diff --git a/src/pages-aigc/detail/components/RegenerateConfirmPopup/index.vue b/src/pages-aigc/detail/components/RegenerateConfirmPopup/index.vue new file mode 100644 index 0000000..e05ed22 --- /dev/null +++ b/src/pages-aigc/detail/components/RegenerateConfirmPopup/index.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/src/pages-aigc/detail/components/RegenerateConfirmPopup/styles/index.scss b/src/pages-aigc/detail/components/RegenerateConfirmPopup/styles/index.scss new file mode 100644 index 0000000..104e7d1 --- /dev/null +++ b/src/pages-aigc/detail/components/RegenerateConfirmPopup/styles/index.scss @@ -0,0 +1,87 @@ +.regenerate-dialog { + width: 318px; + max-width: calc(100vw - 56px); + padding: 30px 26px 24px; + border-radius: 24px; + background: #ffffff; + box-shadow: 0 18px 48px rgba(0, 0, 0, 0.26); + box-sizing: border-box; +} + +.regenerate-dialog-title { + display: block; + color: #111827; + font-size: 20px; + line-height: 28px; + font-weight: 900; + text-align: center; +} + +.regenerate-dialog-desc { + display: block; + margin-top: 14px; + color: #667386; + font-size: 14px; + line-height: 22px; + font-weight: 800; + text-align: center; +} + +.regenerate-dialog-balance { + min-height: 36px; + display: flex; + align-items: center; + justify-content: center; + margin-top: 14px; + padding: 8px 12px; + border-radius: 14px; + background: #effaf5; + color: #0fa567; + font-size: 12px; + line-height: 18px; + font-weight: 900; + text-align: center; + box-sizing: border-box; +} + +.regenerate-dialog-actions { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 18px; + margin-top: 28px; +} + +.regenerate-dialog-button { + min-width: 0; + height: 54px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + border-radius: 999px; + font-size: 16px; + line-height: 22px; + font-weight: 900; + text-overflow: ellipsis; + white-space: nowrap; + box-sizing: border-box; +} + +.regenerate-dialog-button.is-cancel { + border: 1px solid #e2e6ea; + background: #ffffff; + color: #172033; +} + +.regenerate-dialog-button.is-primary { + background: #061e34; + color: #ffffff; +} + +.regenerate-dialog-button.is-pressed { + opacity: 0.86; +} + +.regenerate-dialog-button.is-disabled { + opacity: 0.6; +} diff --git a/src/pages-aigc/detail/detail.vue b/src/pages-aigc/detail/detail.vue index e2e12fa..d32fa13 100644 --- a/src/pages-aigc/detail/detail.vue +++ b/src/pages-aigc/detail/detail.vue @@ -19,6 +19,10 @@ + + @@ -29,9 +33,12 @@ 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, createAigcGeneratorTaskShare, getAigcGeneratorTaskDetail, } from "@/request/api/AigcApi.js"; +import PointInsufficientDialog from "@/pages-aigc/useTemplate/components/PointInsufficientDialog/index.vue"; +import RegenerateConfirmPopup from "./components/RegenerateConfirmPopup/index.vue"; import ResultActions from "./components/ResultActions/index.vue"; import ResultMeta from "./components/ResultMeta/index.vue"; import ResultPreview from "./components/ResultPreview/index.vue"; @@ -45,6 +52,9 @@ const shareKey = ref(""); const privacyVisible = ref(false); const privacyContractName = ref("隐私保护指引"); const pendingSaveAfterPrivacy = ref(false); +const regeneratePopupRef = ref(null); +const isRegenerating = ref(false); +const pointDialogVisible = ref(false); const GENERATOR_TYPE_TEXT = { 0: "图片版", @@ -124,6 +134,7 @@ const metaItems = computed(() => { }); const sharePreviewImage = computed(() => taskDetail.value?.imageResultUrl || ""); +const regenerateCost = computed(() => Math.max(0, Number(taskDetail.value?.generatorCost) || 0)); const showPlaceholderToast = (title) => { uni.showToast({ @@ -425,8 +436,72 @@ const handlePrivacyDisagree = () => { pendingSaveAfterPrivacy.value = false; }; -const handleRegenerate = () => { - showPlaceholderToast("再生成一版"); +const handleRegenerate = async () => { + await fetchCurrentCredit(); + regeneratePopupRef.value?.open(); +}; + +const handleConfirmRegenerate = async () => { + if (isRegenerating.value) return; + + isRegenerating.value = true; + await fetchCurrentCredit(); + + const balance = Number(pointBalance.value) || 0; + if (balance < regenerateCost.value) { + isRegenerating.value = false; + regeneratePopupRef.value?.close(); + pointDialogVisible.value = true; + return; + } + + const templateItemId = String(taskDetail.value?.templateItemId || "").trim(); + const imageResultUrl = String(taskDetail.value?.imageResultUrl || "").trim(); + if (!templateItemId || !imageResultUrl) { + isRegenerating.value = false; + showPlaceholderToast(!templateItemId ? "缺少模板生成项" : "缺少可复用素材"); + return; + } + + uni.showLoading({ + title: "创建中", + mask: true, + }); + + try { + const res = await createAigcGeneratorTask({ + templateItemId, + imageUrlList: [imageResultUrl], + }); + const nextTaskId = String(res?.data || "").trim(); + + if (res?.code === 0 && nextTaskId) { + regeneratePopupRef.value?.close(); + fetchCurrentCredit(); + uni.redirectTo({ + url: `/pages-aigc/progress/progress?taskId=${encodeURIComponent(nextTaskId)}`, + fail: () => showPlaceholderToast("打开任务进度失败"), + }); + return; + } + + showPlaceholderToast(res?.msg || "创建任务失败"); + } catch (error) { + console.error("重新创建AIGC生成任务失败", error); + showPlaceholderToast("创建任务失败"); + } finally { + uni.hideLoading(); + isRegenerating.value = false; + } +}; + +const handleClosePointDialog = () => { + pointDialogVisible.value = false; +}; + +const handlePointRecharge = () => { + pointDialogVisible.value = false; + handleRecharge(); }; const handleChangeTemplate = () => { diff --git a/src/pages-aigc/progress/progress.vue b/src/pages-aigc/progress/progress.vue index 13b7718..6ed472b 100644 --- a/src/pages-aigc/progress/progress.vue +++ b/src/pages-aigc/progress/progress.vue @@ -15,7 +15,7 @@