From c7785e55d7ada1391ae72895a80eb8e96d08d384 Mon Sep 17 00:00:00 2001 From: duanshuwen Date: Wed, 15 Jul 2026 11:24:28 +0800 Subject: [PATCH] feat(aigc): add standalone AIGC task progress page - Add dedicated progress page for viewing AIGC generation task status - Extract reusable GeneratingProgressPanel component - Update task record cards to link to progress page for pending tasks - Rework useTemplate flow to redirect to progress page post task creation - Remove unused inline generating code and styles from useTemplate - Adjust quick access menu item order --- .../GeneratingProgressPanel/index.vue | 62 ++++----- .../GeneratingProgressPanel/styles/index.scss | 0 src/pages-aigc/progress/progress.vue | 124 ++++++++++++++++++ src/pages-aigc/progress/styles/index.scss | 28 ++++ .../record/components/RecordCard/index.vue | 11 +- src/pages-aigc/record/record.vue | 11 +- src/pages-aigc/useTemplate/styles/index.scss | 5 - src/pages-aigc/useTemplate/useTemplate.vue | 77 +++-------- src/pages.json | 6 + src/pages/ChatMain/ChatQuickAccess/index.vue | 19 ++- 10 files changed, 229 insertions(+), 114 deletions(-) rename src/pages-aigc/{useTemplate => progress}/components/GeneratingProgressPanel/index.vue (76%) rename src/pages-aigc/{useTemplate => progress}/components/GeneratingProgressPanel/styles/index.scss (100%) create mode 100644 src/pages-aigc/progress/progress.vue create mode 100644 src/pages-aigc/progress/styles/index.scss diff --git a/src/pages-aigc/useTemplate/components/GeneratingProgressPanel/index.vue b/src/pages-aigc/progress/components/GeneratingProgressPanel/index.vue similarity index 76% rename from src/pages-aigc/useTemplate/components/GeneratingProgressPanel/index.vue rename to src/pages-aigc/progress/components/GeneratingProgressPanel/index.vue index a237e00..00113a3 100644 --- a/src/pages-aigc/useTemplate/components/GeneratingProgressPanel/index.vue +++ b/src/pages-aigc/progress/components/GeneratingProgressPanel/index.vue @@ -48,10 +48,6 @@ import { computed, onUnmounted, ref, watch } from "vue"; const props = defineProps({ - progress: { - type: [Number, String], - default: 8, - }, cost: { type: [Number, String], default: 100, @@ -76,47 +72,50 @@ const props = defineProps({ const emit = defineEmits(["continue", "view"]); +const TASK_STATUS_PROGRESS = { + 0: 23, + 1: 56, + 2: 100, + 4: 74, +}; + const normalizeProgress = (progress) => { const value = Number(progress); if (!Number.isFinite(value)) return 0; - return Math.max(0, Math.min(99, Math.round(value))); + return Math.max(0, Math.min(100, Math.round(value))); }; -const displayProgress = ref(normalizeProgress(props.progress)); +const displayProgress = ref(0); let progressTimer = null; const normalizedProgress = computed(() => normalizeProgress(displayProgress.value)); const progressWidth = computed(() => `${normalizedProgress.value}%`); - -const isAnimatingStatus = (status) => { - if (status === null || status === undefined || status === "") return false; - return [0, 1, 4].includes(Number(status)); -}; +const targetProgress = computed(() => TASK_STATUS_PROGRESS[Number(props.taskStatus)] ?? 0); const clearProgressTimer = () => { - if (progressTimer) { + if (progressTimer !== null) { clearInterval(progressTimer); progressTimer = null; } }; -const getNextProgress = (progress) => { - if (progress < 35) return Math.min(progress + 7, 35); - if (progress < 70) return Math.min(progress + 5, 70); - if (progress < 92) return Math.min(progress + 3, 92); - return Math.min(progress + 1, 99); -}; - const startProgressAnimation = () => { clearProgressTimer(); - if (!isAnimatingStatus(props.taskStatus) || normalizedProgress.value >= 99) return; + const target = targetProgress.value; + if (normalizedProgress.value === target) return; progressTimer = setInterval(() => { - displayProgress.value = getNextProgress(normalizedProgress.value); - if (normalizedProgress.value >= 99) { + const current = normalizedProgress.value; + const direction = current < target ? 1 : -1; + const distance = Math.abs(target - current); + const step = distance > 36 ? 4 : distance > 16 ? 2 : 1; + const nextProgress = current + direction * Math.min(step, distance); + + displayProgress.value = nextProgress; + if (nextProgress === target) { clearProgressTimer(); } - }, 320); + }, 60); }; const generatorTypeText = computed(() => { @@ -144,23 +143,10 @@ const displaySteps = computed(() => { })); }); -watch( - () => props.progress, - (progress) => { - const initialProgress = normalizeProgress(progress); - if (!isAnimatingStatus(props.taskStatus) || displayProgress.value < initialProgress) { - displayProgress.value = initialProgress; - } - } -); - watch( () => props.taskStatus, - (status) => { - clearProgressTimer(); - if (isAnimatingStatus(status)) { - startProgressAnimation(); - } + () => { + startProgressAnimation(); }, { immediate: true } ); diff --git a/src/pages-aigc/useTemplate/components/GeneratingProgressPanel/styles/index.scss b/src/pages-aigc/progress/components/GeneratingProgressPanel/styles/index.scss similarity index 100% rename from src/pages-aigc/useTemplate/components/GeneratingProgressPanel/styles/index.scss rename to src/pages-aigc/progress/components/GeneratingProgressPanel/styles/index.scss diff --git a/src/pages-aigc/progress/progress.vue b/src/pages-aigc/progress/progress.vue new file mode 100644 index 0000000..13b7718 --- /dev/null +++ b/src/pages-aigc/progress/progress.vue @@ -0,0 +1,124 @@ + + + + + diff --git a/src/pages-aigc/progress/styles/index.scss b/src/pages-aigc/progress/styles/index.scss new file mode 100644 index 0000000..07c1d92 --- /dev/null +++ b/src/pages-aigc/progress/styles/index.scss @@ -0,0 +1,28 @@ +.aigc-progress-page { + display: flex; + flex-direction: column; + width: 100vw; + height: 100vh; + overflow: hidden; + color: #172033; + background: + radial-gradient( + 130% 78% at 0% 0%, + rgba(255, 249, 226, 0.76), + rgba(255, 249, 226, 0) 42% + ), + linear-gradient(180deg, #effaf5 0%, #f4fbf7 58%, #eef8ff 100%); + box-sizing: border-box; +} + +.aigc-progress-body { + flex: 1 1 0; + min-height: 0; + overflow: hidden; +} + +.aigc-progress-content { + min-height: 100%; + padding: 12px 12px 16px; + box-sizing: border-box; +} diff --git a/src/pages-aigc/record/components/RecordCard/index.vue b/src/pages-aigc/record/components/RecordCard/index.vue index a650acb..69deb35 100644 --- a/src/pages-aigc/record/components/RecordCard/index.vue +++ b/src/pages-aigc/record/components/RecordCard/index.vue @@ -13,8 +13,8 @@ {{ statusText }} - - 查看 + + {{ actionText }} @@ -48,7 +48,12 @@ const title = computed(() => props.record.itemTitle || props.record.taskId || "" const normalizedTaskStatus = computed(() => Number(props.record.taskStatus)); const isCompleted = computed(() => normalizedTaskStatus.value === 2); -const isGenerating = computed(() => [0, 1].includes(normalizedTaskStatus.value)); +const isGenerating = computed(() => [0, 1, 4].includes(normalizedTaskStatus.value)); +const actionText = computed(() => { + if (isCompleted.value) return "查看"; + if (isGenerating.value) return "进度"; + return ""; +}); const mediaUrl = computed(() => { if (!isCompleted.value) return ""; diff --git a/src/pages-aigc/record/record.vue b/src/pages-aigc/record/record.vue index 13305b3..ef0f710 100644 --- a/src/pages-aigc/record/record.vue +++ b/src/pages-aigc/record/record.vue @@ -49,12 +49,21 @@ const handleBack = () => { }; const handleViewRecord = (record) => { - if (record?.taskStatus === 2 && record.taskId) { + if (!record?.taskId) return; + + const taskStatus = Number(record.taskStatus); + if (taskStatus === 2) { uni.navigateTo({ url: `/pages-aigc/detail/detail?taskId=${encodeURIComponent(record.taskId)}`, }); return; } + + if ([0, 1, 4].includes(taskStatus)) { + uni.navigateTo({ + url: `/pages-aigc/progress/progress?taskId=${encodeURIComponent(record.taskId)}`, + }); + } }; diff --git a/src/pages-aigc/useTemplate/styles/index.scss b/src/pages-aigc/useTemplate/styles/index.scss index d7256b8..0f36d42 100644 --- a/src/pages-aigc/useTemplate/styles/index.scss +++ b/src/pages-aigc/useTemplate/styles/index.scss @@ -31,11 +31,6 @@ box-sizing: border-box; } -.aigc-use-template-content.is-generating { - overflow-y: auto; - padding: 12px 12px 16px; -} - .aigc-use-template-popup-host { position: fixed; z-index: 30; diff --git a/src/pages-aigc/useTemplate/useTemplate.vue b/src/pages-aigc/useTemplate/useTemplate.vue index 07eaf21..b11fffd 100644 --- a/src/pages-aigc/useTemplate/useTemplate.vue +++ b/src/pages-aigc/useTemplate/useTemplate.vue @@ -4,26 +4,19 @@ @recharge="handleRecharge" /> - - + + + + @@ -55,14 +48,12 @@ import AigcTopBar from "@/pages-aigc/components/AigcTopBar/index.vue"; import { useCurrentCredit } from "@/pages-aigc/hooks/useCurrentCredit.js"; import { createAigcGeneratorTask, - getAigcGeneratorTaskDetail, 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"; @@ -78,8 +69,6 @@ const templateItems = ref([]); const selectedTemplateItemId = ref(""); const selectedImageLocalPath = ref(""); const uploadedImageUrl = ref(""); -const createdTaskId = ref(""); -const taskStatus = ref(null); const currentStep = ref("version"); const pointDialogVisible = ref(false); const isUploading = ref(false); @@ -338,19 +327,6 @@ const handleConfirmPhoto = () => { currentStep.value = "generateConfirm"; }; -const fetchCreatedTaskStatus = async () => { - if (!createdTaskId.value) return; - - try { - const res = await getAigcGeneratorTaskDetail({ taskId: createdTaskId.value }); - if (res?.code === 0 && res.data) { - taskStatus.value = res.data.taskStatus; - } - } catch (error) { - console.warn("获取AIGC生成任务状态失败", error); - } -}; - const handleGenerate = async () => { if (isCreatingTask.value) return; @@ -375,9 +351,10 @@ const handleGenerate = async () => { } isCreatingTask.value = true; - createdTaskId.value = ""; - taskStatus.value = null; - currentStep.value = "generating"; + uni.showLoading({ + title: "创建中", + mask: true, + }); try { const res = await createAigcGeneratorTask({ @@ -385,37 +362,25 @@ const handleGenerate = async () => { imageUrlList: [uploadedImageUrl.value], }); - if (res?.code === 0 && res.data) { - createdTaskId.value = res.data; + const taskId = String(res?.data || "").trim(); + if (res?.code === 0 && taskId) { fetchCurrentCredit(); - await fetchCreatedTaskStatus(); + uni.redirectTo({ + url: `/pages-aigc/progress/progress?taskId=${encodeURIComponent(taskId)}`, + fail: () => showPlaceholderToast("打开任务进度失败"), + }); } else { - currentStep.value = "generateConfirm"; showPlaceholderToast(res?.msg || "创建任务失败"); } } catch (error) { console.warn("创建AIGC生成任务失败", error); - currentStep.value = "generateConfirm"; showPlaceholderToast("创建任务失败"); } finally { + uni.hideLoading(); isCreatingTask.value = false; } }; -const handleContinueTemplates = () => { - const pages = getCurrentPages(); - const previousPage = pages[pages.length - 2]; - if (previousPage?.route === "pages-aigc/home/home") { - uni.navigateBack(); - return; - } - - uni.redirectTo({ - url: "/pages-aigc/home/home", - fail: () => showPlaceholderToast("继续生成其他模板"), - }); -}; - const handleClosePointDialog = () => { pointDialogVisible.value = false; }; diff --git a/src/pages.json b/src/pages.json index bc9bce8..ee05b64 100644 --- a/src/pages.json +++ b/src/pages.json @@ -170,6 +170,12 @@ "navigationStyle": "custom" } }, + { + "path": "progress/progress", + "style": { + "navigationStyle": "custom" + } + }, { "path": "detail/detail", "style": { diff --git a/src/pages/ChatMain/ChatQuickAccess/index.vue b/src/pages/ChatMain/ChatQuickAccess/index.vue index 0d50325..47417f4 100644 --- a/src/pages/ChatMain/ChatQuickAccess/index.vue +++ b/src/pages/ChatMain/ChatQuickAccess/index.vue @@ -12,10 +12,7 @@ - + @@ -36,16 +33,16 @@ const itemList = ref([ title: "快速预定", type: Command.quickBooking, }, - // { - // icon: "", - // title: "旅行记录", - // type: Command.travelAIGC, - // }, { icon: "", - title: "我的订单", - type: Command.myOrder, + title: "旅行记录", + type: Command.travelAIGC, }, + // { + // icon: "", + // title: "我的订单", + // type: Command.myOrder, + // }, { icon: "", title: "呼叫服务",