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 @@
+
+
+
+ 再生成一版
+
+ 将复用当前素材和模板重新生成,本次将扣除 {{ normalizedCost }}
+ 积分。开始后先冻结,生成成功后正式扣除,失败自动退回。
+
+
+
+ 当前余额 {{ normalizedBalance }} 积分
+
+
+
+
+ 取消
+
+
+ {{ loading ? "处理中..." : "确认生成" }}
+
+
+
+
+
+
+
+
+
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 @@