feat(useTemplate): add point insufficient dialog and progress panel
Create PointInsufficientDialog component to notify users of insufficient points before generation. Add GeneratingProgressPanel to display generation progress, steps and cost freeze info. Integrate point balance validation into the generate workflow and update the main page to handle generating state.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<view class="generating-progress-panel">
|
||||
<view class="generating-progress-row">
|
||||
<view class="generating-progress-track">
|
||||
<view class="generating-progress-fill" :style="{ width: progressWidth }" />
|
||||
</view>
|
||||
<text class="generating-progress-text">{{ normalizedProgress }}%</text>
|
||||
</view>
|
||||
|
||||
<view class="generating-step-list">
|
||||
<view
|
||||
v-for="(step, index) in steps"
|
||||
:key="`${step}-${index}`"
|
||||
class="generating-step-item"
|
||||
>
|
||||
<view class="generating-step-dot" />
|
||||
<text class="generating-step-label">{{ step }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<text class="generating-freeze-text">
|
||||
已冻结 {{ cost }} 积分,生成成功后扣除,失败自动退回。
|
||||
</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
progress: {
|
||||
type: [Number, String],
|
||||
default: 8,
|
||||
},
|
||||
cost: {
|
||||
type: [Number, String],
|
||||
default: 100,
|
||||
},
|
||||
steps: {
|
||||
type: Array,
|
||||
default: () => ["素材质检", "图片优化", "套入模板", "导出图片"],
|
||||
},
|
||||
});
|
||||
|
||||
const normalizedProgress = computed(() => {
|
||||
const value = Number(props.progress);
|
||||
if (!Number.isFinite(value)) return 0;
|
||||
return Math.max(0, Math.min(100, Math.round(value)));
|
||||
});
|
||||
|
||||
const progressWidth = computed(() => `${normalizedProgress.value}%`);
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "./styles/index.scss";
|
||||
</style>
|
||||
@@ -0,0 +1,87 @@
|
||||
.generating-progress-panel {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.generating-progress-row {
|
||||
height: 18px;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 34px;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.generating-progress-track {
|
||||
width: 100%;
|
||||
height: 12px;
|
||||
overflow: hidden;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
}
|
||||
|
||||
.generating-progress-fill {
|
||||
height: 100%;
|
||||
min-width: 24px;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(90deg, #20cf75 0%, #f2d646 100%);
|
||||
}
|
||||
|
||||
.generating-progress-text {
|
||||
color: #061e34;
|
||||
font-size: 15px;
|
||||
line-height: 18px;
|
||||
font-weight: 900;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.generating-step-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.generating-step-item {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 0 12px;
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.generating-step-dot {
|
||||
flex: 0 0 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 2px solid #d6e8f4;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.generating-step-label {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: #6c7e96;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
font-weight: 900;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.generating-freeze-text {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
margin: 18px 14px 0;
|
||||
color: #7b8da4;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<uni-popup
|
||||
ref="popupRef"
|
||||
type="center"
|
||||
background-color="transparent"
|
||||
mask-background-color="rgba(0, 0, 0, 0.58)"
|
||||
:safe-area="false"
|
||||
:is-mask-click="false"
|
||||
>
|
||||
<view class="point-dialog">
|
||||
<text class="point-dialog-title">积分不足</text>
|
||||
<text class="point-dialog-desc">
|
||||
本次需 {{ normalizedCost }} 积分,当前仅 {{ normalizedBalance }} 积分,还差 {{ shortage }} 积分。
|
||||
</text>
|
||||
|
||||
<view class="point-dialog-actions">
|
||||
<view
|
||||
class="point-dialog-button is-cancel"
|
||||
hover-class="is-pressed"
|
||||
@tap="emit('cancel')"
|
||||
>
|
||||
稍后再说
|
||||
</view>
|
||||
<view
|
||||
class="point-dialog-button is-primary"
|
||||
hover-class="is-pressed"
|
||||
@tap="emit('recharge')"
|
||||
>
|
||||
去充值
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, nextTick, onMounted, ref } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
cost: {
|
||||
type: [Number, String],
|
||||
default: 100,
|
||||
},
|
||||
balance: {
|
||||
type: [Number, String],
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["cancel", "recharge"]);
|
||||
const popupRef = ref(null);
|
||||
|
||||
const normalizedCost = computed(() => Math.max(0, Number(props.cost) || 0));
|
||||
const normalizedBalance = computed(() => Math.max(0, Number(props.balance) || 0));
|
||||
const shortage = computed(() => Math.max(normalizedCost.value - normalizedBalance.value, 0));
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick();
|
||||
popupRef.value?.open();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "./styles/index.scss";
|
||||
</style>
|
||||
@@ -0,0 +1,70 @@
|
||||
.point-dialog {
|
||||
width: 318px;
|
||||
max-width: calc(100vw - 56px);
|
||||
min-height: 220px;
|
||||
padding: 29px 24px 22px;
|
||||
border: 1px solid rgba(20, 143, 255, 0.8);
|
||||
border-radius: 20px;
|
||||
background: #dceeff;
|
||||
box-shadow: 0 18px 38px rgba(8, 28, 44, 0.22);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.point-dialog-title {
|
||||
display: block;
|
||||
color: #102942;
|
||||
font-size: 22px;
|
||||
line-height: 29px;
|
||||
font-weight: 900;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.point-dialog-desc {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
margin: 15px auto 0;
|
||||
max-width: 228px;
|
||||
color: #506985;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.point-dialog-actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 18px;
|
||||
margin-top: 28px;
|
||||
}
|
||||
|
||||
.point-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;
|
||||
}
|
||||
|
||||
.point-dialog-button.is-cancel {
|
||||
border: 1px solid rgba(124, 168, 207, 0.28);
|
||||
background: rgba(255, 255, 255, 0.14);
|
||||
color: #102942;
|
||||
}
|
||||
|
||||
.point-dialog-button.is-primary {
|
||||
background: #061e34;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.point-dialog-button.is-pressed {
|
||||
opacity: 0.86;
|
||||
}
|
||||
@@ -19,6 +19,11 @@
|
||||
@generate="handleGenerate"
|
||||
@recharge="handleRecharge"
|
||||
/>
|
||||
<GeneratingProgressPanel
|
||||
v-else-if="currentStep === 'generating'"
|
||||
:cost="currentVersion.cost || 100"
|
||||
:progress="8"
|
||||
/>
|
||||
|
||||
<template v-else>
|
||||
<VersionOptionList
|
||||
@@ -57,6 +62,13 @@
|
||||
@confirm="handleConfirmPhoto"
|
||||
/>
|
||||
</view>
|
||||
<PointInsufficientDialog
|
||||
v-if="pointDialogVisible"
|
||||
:cost="currentVersion.cost || 100"
|
||||
:balance="pointBalance"
|
||||
@cancel="handleClosePointDialog"
|
||||
@recharge="handlePointRecharge"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -67,9 +79,11 @@ import AigcTopBar from "@/pages-aigc/components/AigcTopBar/index.vue";
|
||||
import { aigcTemplates } from "@/pages-aigc/home/data/templates.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";
|
||||
@@ -79,6 +93,7 @@ const pointBalance = ref(300);
|
||||
const templateId = ref("");
|
||||
const selectedVersion = ref("image");
|
||||
const currentStep = ref("version");
|
||||
const pointDialogVisible = ref(false);
|
||||
|
||||
const currentTemplate = computed(() => {
|
||||
return aigcTemplates.find((item) => item.id === templateId.value) || aigcTemplates[0] || {};
|
||||
@@ -150,7 +165,24 @@ const handleConfirmPhoto = () => {
|
||||
};
|
||||
|
||||
const handleGenerate = () => {
|
||||
showPlaceholderToast("开始生成");
|
||||
const cost = Number(currentVersion.value.cost) || 0;
|
||||
const balance = Number(pointBalance.value) || 0;
|
||||
|
||||
if (balance < cost) {
|
||||
pointDialogVisible.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
currentStep.value = "generating";
|
||||
};
|
||||
|
||||
const handleClosePointDialog = () => {
|
||||
pointDialogVisible.value = false;
|
||||
};
|
||||
|
||||
const handlePointRecharge = () => {
|
||||
pointDialogVisible.value = false;
|
||||
handleRecharge();
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user