chore: remove recharge and switch to test mode

Remove all recharge-related pages, components, and API endpoints.
Update developVersion flag to enable test environment with 400 free credits for all users.
Revise PointInsufficientDialog to reflect closed recharge channel, update button text and actions.
Remove recharge actions from all top bars and generate confirmation panels.
Add new user agreement page for service terms, update related API calls.
Restyle AigcConsentDialog to use sectioned content layout.
Fix visibility timing logic in GiftPointsToast component.
This commit is contained in:
duanshuwen
2026-07-20 14:50:14 +08:00
parent 2407a9d941
commit bfebed44b0
28 changed files with 260 additions and 963 deletions

View File

@@ -0,0 +1,59 @@
<template>
<view class="aigc-agreement-page">
<TopNavBar
title="智念AI用户规则"
background="#ffffff"
title-color="#172033"
back-icon-color="#172033"
:align-with-menu-button="true"
:z-index="3"
/>
<scroll-view class="aigc-agreement-scroll" scroll-y>
<view v-if="agreement" class="aigc-agreement-document">
<zero-markdown-view :markdown="agreement" />
</view>
<view v-else class="aigc-agreement-state">
<text>{{ loading ? "协议加载中..." : "协议内容加载失败" }}</text>
<button v-if="!loading" class="aigc-agreement-retry" @tap="fetchAgreement">
重新加载
</button>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import TopNavBar from "@/components/TopNavBar/index.vue";
import { getUserAgreement } from "@/request/api/AigcApi.js";
const agreement = ref("");
const loading = ref(false);
const fetchAgreement = async () => {
if (loading.value) return;
loading.value = true;
try {
const res = await getUserAgreement();
agreement.value =
res?.code === 0 && typeof res.data === "string" ? res.data.trim() : "";
} catch (error) {
agreement.value = "";
console.error("获取智念AI用户规则失败", error);
} finally {
loading.value = false;
}
};
onLoad(() => {
fetchAgreement();
});
</script>
<style scoped lang="scss">
@import "./styles/index.scss";
</style>

View File

@@ -0,0 +1,54 @@
.aigc-agreement-page {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
background: #ffffff;
}
.aigc-agreement-scroll {
height: 0;
flex: 1 1 auto;
min-height: 0;
background: #ffffff;
}
.aigc-agreement-document {
min-height: 100%;
padding: 20px 16px calc(24px + env(safe-area-inset-bottom));
box-sizing: border-box;
}
.aigc-agreement-state {
min-height: 240px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 24px;
color: #7b8798;
font-size: 14px;
line-height: 22px;
box-sizing: border-box;
}
.aigc-agreement-retry {
width: 120px;
height: 42px;
display: flex;
align-items: center;
justify-content: center;
margin: 16px 0 0;
padding: 0;
border-radius: 999px;
background: #102942;
color: #ffffff;
font-size: 14px;
line-height: 20px;
font-weight: 800;
}
.aigc-agreement-retry::after {
border: 0;
}

View File

@@ -10,8 +10,7 @@
</view>
<view v-if="showActions" class="aigc-topbar-actions">
<view class="aigc-wallet-button" @tap="emit('recharge')">
<text class="aigc-wallet-label">{{ rechargeLabel }}</text>
<view class="aigc-points-display">
<text class="aigc-wallet-points">
<text class="aigc-wallet-number">{{ displayPoints }}</text>
积分
@@ -37,10 +36,6 @@ const props = defineProps({
type: [Number, String],
default: 0,
},
rechargeLabel: {
type: String,
default: "充值",
},
showActions: {
type: Boolean,
default: true,
@@ -52,7 +47,7 @@ const props = defineProps({
},
});
const emit = defineEmits(["back", "history", "recharge"]);
const emit = defineEmits(["back", "history"]);
const statusBarHeight = ref(0);
const navHeight = ref(52);

View File

@@ -18,6 +18,7 @@
}
.aigc-nav-row {
position: relative;
height: var(--aigc-nav-height);
display: flex;
align-items: center;
@@ -53,12 +54,15 @@
}
.aigc-topbar-actions {
flex: 1 1 auto;
position: absolute;
left: 50%;
transform: translateX(-50%);
flex: 0 0 auto;
width: auto;
min-width: 0;
height: 36px;
display: grid;
grid-template-columns: minmax(0, 1fr) 36px;
grid-template-columns: auto 36px;
align-items: center;
gap: 6px;
border-radius: 999px;
@@ -68,7 +72,7 @@
}
.aigc-history-button,
.aigc-wallet-button {
.aigc-points-display {
height: 36px;
min-width: 0;
display: flex;
@@ -94,29 +98,15 @@
justify-content: center;
}
.aigc-wallet-button {
justify-content: space-between;
gap: 6px;
padding: 0 8px;
.aigc-points-display {
min-width: 84px;
max-width: 112px;
justify-content: center;
padding: 0 12px;
background: #102942;
color: #ffffff;
}
.aigc-wallet-label {
height: 24px;
flex: 0 0 auto;
display: inline-flex;
align-items: center;
padding: 0 8px;
border-radius: 999px;
background: rgba(32, 207, 117, 0.18);
color: #50e093;
font-size: 10px;
line-height: 24px;
font-weight: 900;
white-space: nowrap;
}
.aigc-wallet-points {
min-width: 0;
display: inline-flex;

View File

@@ -1,6 +1,6 @@
<template>
<view class="aigc-detail-page">
<AigcTopBar :points="pointBalance" @back="handleBack" @history="handleOpenRecords" @recharge="handleRecharge" />
<AigcTopBar :points="pointBalance" @back="handleBack" @history="handleOpenRecords" />
<view class="aigc-detail-content">
<template v-if="taskDetail">
@@ -22,7 +22,7 @@
<RegenerateConfirmPopup ref="regeneratePopupRef" :cost="regenerateCost" :balance="pointBalance"
:loading="isRegenerating" @confirm="handleConfirmRegenerate" />
<PointInsufficientDialog v-if="pointDialogVisible" :cost="regenerateCost" :balance="pointBalance"
@cancel="handleClosePointDialog" @recharge="handlePointRecharge" />
@cancel="handleClosePointDialog" />
</view>
</template>
@@ -239,13 +239,6 @@ const handleBack = () => {
}
};
const handleRecharge = () => {
uni.navigateTo({
url: "/pages-aigc/recharge/recharge",
fail: () => showPlaceholderToast("打开充值页面失败"),
});
};
const getMediaFileExtension = (url, mediaType) => {
const pathname = String(url || "").split(/[?#]/)[0];
const matchedExtension = pathname.match(/\.([a-z0-9]{2,5})$/i)?.[1]?.toLowerCase();
@@ -572,11 +565,6 @@ const handleClosePointDialog = () => {
pointDialogVisible.value = false;
};
const handlePointRecharge = () => {
pointDialogVisible.value = false;
handleRecharge();
};
const handleChangeTemplate = () => {
uni.navigateTo({
url: "/pages-aigc/home/home",

View File

@@ -13,13 +13,24 @@
<text class="aigc-consent-title">温馨提示</text>
<view class="aigc-consent-content">
<text v-if="content">{{ content }}</text>
<text v-if="content" class="aigc-consent-paragraph">{{ content }}</text>
<template v-else>
<text>{{ defaultContentPrefix }}</text>
<text class="aigc-consent-rule" @tap.stop="handleRuleClick">
{{ ruleName }}
</text>
<text>{{ defaultContentSuffix }}</text>
<view class="aigc-consent-section">
<text class="aigc-consent-section-title">体验权益</text>
<text class="aigc-consent-paragraph">{{ experienceContent }}</text>
</view>
<view class="aigc-consent-section">
<text class="aigc-consent-section-title">服务说明</text>
<text class="aigc-consent-paragraph">{{ serviceContent }}</text>
<view class="aigc-consent-paragraph">
<text>请您在使用前仔细阅读</text>
<text class="aigc-consent-rule" @tap.stop="handleRuleClick">
{{ ruleName }}
</text>
<text>{{ ruleConsentSuffix }}</text>
</view>
</view>
</template>
</view>
@@ -66,16 +77,19 @@ const props = defineProps({
},
});
const emit = defineEmits(["update:visible", "cancel", "agree", "ruleClick"]);
const emit = defineEmits(["update:visible", "cancel", "agree"]);
const popupRef = ref(null);
const defaultContentPrefix = computed(
const experienceContent =
"当前体验版本赠送 400 积分,可生成 1 张高清旅行图片和 1 段 5 秒内动效视频。如需更多生成额度,敬请期待后续版本上线。";
const serviceContent = computed(
() =>
`欢迎使用【${props.companyName}】提供的AI内容升级服务。本服务通过人工智能技术AIGC您上传的照片或视频与景区场景模板进行创意优化,为您生成具有特定风格的旅行内容。请您仔细阅读`
`欢迎使用【${props.companyName}】提供的 AI 内容升级服务。本服务通过人工智能技术AIGC,对您上传的照片或视频进行处理,并结合景区场景模板,为您生成具有特定风格的旅行图片或视频内容。`
);
const defaultContentSuffix =
",您点击“我同意”等进行下一步操作的行为均视为您同意受本规则约束。如您不同意,您可以直接关闭页面。";
const ruleConsentSuffix =
"点击“我同意”并继续操作,即表示您已阅读、理解并同意遵守相关规则;如您不同意,可点击“取消”并退出本服务。";
const close = () => {
popupRef.value?.close();
@@ -93,7 +107,15 @@ const handleAgree = () => {
};
const handleRuleClick = () => {
emit("ruleClick");
uni.navigateTo({
url: "/pages-aigc/agreement/agreement",
fail: () => {
uni.showToast({
title: "打开用户规则失败",
icon: "none",
});
},
});
};
const syncPopupVisible = async (visible) => {

View File

@@ -31,6 +31,28 @@
text-align: justify;
}
.aigc-consent-section + .aigc-consent-section {
margin-top: 18px;
}
.aigc-consent-section-title,
.aigc-consent-paragraph {
display: block;
}
.aigc-consent-section-title {
margin-bottom: 6px;
color: #172033;
font-size: 16px;
line-height: 24px;
font-weight: 900;
text-align: left;
}
.aigc-consent-paragraph + .aigc-consent-paragraph {
margin-top: 10px;
}
.aigc-consent-rule {
color: #18bd78;
font-weight: 900;

View File

@@ -1,5 +1,5 @@
<template>
<view v-if="visible" class="gift-points-toast" role="status">
<view v-if="displayVisible" class="gift-points-toast" role="status">
<text class="gift-points-badge">{{ title }}</text>
<text class="gift-points-message">{{ message }}</text>
<view class="gift-points-close" @tap="handleClose">
@@ -9,14 +9,20 @@
</template>
<script setup>
defineProps({
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
const props = defineProps({
visible: {
type: Boolean,
default: true,
},
delay: {
type: Number,
default: 3000,
},
title: {
type: String,
default: "200积分已到账",
default: "400积分已到账",
},
message: {
type: String,
@@ -25,11 +31,50 @@ defineProps({
});
const emit = defineEmits(["close", "update:visible"]);
const displayVisible = ref(false);
const mounted = ref(false);
let showTimer = null;
const clearShowTimer = () => {
if (showTimer) {
clearTimeout(showTimer);
showTimer = null;
}
};
const syncVisible = (visible) => {
clearShowTimer();
displayVisible.value = false;
if (!visible || !mounted.value) return;
showTimer = setTimeout(() => {
showTimer = null;
displayVisible.value = props.visible;
}, Math.max(0, props.delay));
};
const handleClose = () => {
clearShowTimer();
displayVisible.value = false;
emit("update:visible", false);
emit("close");
};
watch(
() => props.visible,
(visible) => {
syncVisible(visible);
}
);
onMounted(() => {
mounted.value = true;
syncVisible(props.visible);
});
onBeforeUnmount(() => {
clearShowTimer();
});
</script>
<style scoped lang="scss">

View File

@@ -1,7 +1,7 @@
<template>
<view class="aigc-home-page">
<AigcTopBar :points="pointBalance" :left-action="isSharedWorkEntry ? 'home' : 'back'" @back="handleBack"
@history="handleHistory" @recharge="handleRecharge" />
@history="handleHistory" />
<view class="aigc-home-browser">
<GiftPointsToast :visible="giftToastVisible" @close="handleCloseGiftToast" />
@@ -86,12 +86,6 @@ const handleHistory = () => {
});
};
const handleRecharge = () => {
uni.navigateTo({
url: "/pages-aigc/recharge/recharge"
});
};
const handleCloseGiftToast = () => {
giftToastVisible.value = false;
setAigcGiftPointsToastClosed();

View File

@@ -1,7 +1,6 @@
<template>
<view class="aigc-progress-page">
<AigcTopBar :points="pointBalance" recharge-label="充值" @back="handleBack" @history="handleViewTasks"
@recharge="handleRecharge" />
<AigcTopBar :points="pointBalance" @back="handleBack" @history="handleViewTasks" />
<scroll-view class="aigc-progress-body" scroll-y>
<view class="aigc-progress-content">
@@ -174,13 +173,6 @@ const handleViewTasks = () => {
});
};
const handleRecharge = () => {
uni.navigateTo({
url: "/pages-aigc/recharge/recharge",
fail: () => showToast("打开积分充值失败"),
});
};
const handleProgressComplete = () => {
if (isNavigatingToDetail || !taskId.value || !isTaskCompleted()) return;

View File

@@ -1,25 +0,0 @@
<template>
<view class="balance-card">
<view class="balance-copy">
<text class="balance-label">当前可用积分</text>
<text class="balance-value">{{ points }}</text>
</view>
<view class="balance-ledger" @tap="emit('ledger')">积分明细</view>
</view>
</template>
<script setup>
defineProps({
points: {
type: [Number, String],
default: 0,
},
});
const emit = defineEmits(["ledger"]);
</script>
<style scoped lang="scss">
@import "./styles/index.scss";
</style>

View File

@@ -1,60 +0,0 @@
.balance-card {
min-height: 116px;
display: grid;
grid-template-columns: 1fr 78px;
align-items: center;
gap: 12px;
padding: 18px;
border-radius: 22px;
background: #172951;
color: #ffffff;
box-sizing: border-box;
}
.balance-copy {
min-width: 0;
}
.balance-label,
.balance-value,
.balance-rule {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.balance-label {
color: rgba(255, 255, 255, 0.66);
font-size: 11px;
line-height: 15px;
font-weight: 800;
}
.balance-value {
margin: 4px 0;
color: #ffffff;
font-size: 34px;
line-height: 39px;
font-weight: 900;
}
.balance-rule {
color: rgba(255, 255, 255, 0.66);
font-size: 11px;
line-height: 15px;
font-weight: 800;
}
.balance-ledger {
height: 34px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
color: #ffffff;
font-size: 12px;
line-height: 34px;
font-weight: 900;
}

View File

@@ -1,49 +0,0 @@
<template>
<view :class="['custom-card', { 'is-selected': selected }]" @tap="emit('select')">
<text class="custom-title">自选金额</text>
<label class="custom-input-wrap">
<text class="custom-currency">¥</text>
<input
class="custom-input"
type="number"
:maxlength="4"
:value="modelValue"
@focus="emit('select')"
@input="handleInput"
/>
</label>
<view class="custom-hint">
<text>请输入整数金额</text>
</view>
</view>
</template>
<script setup>
defineProps({
modelValue: {
type: String,
default: "",
},
selected: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:modelValue", "select"]);
const MAX_CUSTOM_AMOUNT = 9999;
const handleInput = (event) => {
const rawValue = event?.detail?.value ?? event?.target?.value ?? "";
const digits = String(rawValue).replace(/\D/g, "");
const value = digits ? String(Math.min(Number(digits), MAX_CUSTOM_AMOUNT)) : "";
emit("select");
emit("update:modelValue", value);
};
</script>
<style scoped lang="scss">
@import "./styles/index.scss";
</style>

View File

@@ -1,84 +0,0 @@
.custom-card {
min-height: 90px;
display: grid;
grid-template-columns: minmax(0, 1fr) 118px;
grid-template-rows: 28px auto;
column-gap: 14px;
row-gap: 2px;
align-items: center;
margin-top: 12px;
padding: 14px 16px;
border: 1.5px solid transparent;
border-radius: 18px;
background: rgba(255, 255, 255, 0.8);
box-sizing: border-box;
}
.custom-card.is-selected {
border-color: #20cf75;
}
.custom-title {
grid-column: 1;
grid-row: 1;
display: block;
overflow: hidden;
color: #172033;
font-size: 14px;
line-height: 28px;
font-weight: 900;
text-overflow: ellipsis;
white-space: nowrap;
}
.custom-input-wrap {
grid-column: 2;
grid-row: 1 / 3;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 0 0 0 18px;
border-left: 1px solid rgba(82, 96, 115, 0.12);
color: #172033;
font-size: 17px;
line-height: 56px;
font-weight: 900;
box-sizing: border-box;
}
.custom-currency {
flex: 0 0 auto;
}
.custom-input {
width: 52px;
height: 56px;
min-height: 56px;
border: 0;
background: transparent;
color: #172033;
font-size: 18px;
line-height: 56px;
font-weight: 900;
}
.custom-hint {
grid-column: 1;
grid-row: 2;
min-width: 0;
display: grid;
gap: 1px;
color: #7a8596;
font-size: 11px;
line-height: 15px;
font-weight: 800;
}
.custom-hint text {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

View File

@@ -1,77 +0,0 @@
<template>
<view class="recharge-footer">
<view class="recharge-agreement" :class="{ 'is-disabled': !agreementReady }">
<CheckBox :model-value="agreed" @update:model-value="handleAgreementChange">
<text class="recharge-agreement-text">我已阅读并同意</text>
<text class="recharge-agreement-link" @tap.stop="handleViewAgreement">积分充值协议</text>
</CheckBox>
</view>
<view class="recharge-payment-row">
<text class="recharge-gain">
支付金额 <text class="recharge-gain-value">¥{{ formattedPrice }}</text>
</text>
<view
class="recharge-pay-button"
:class="{ 'is-disabled': payDisabled }"
@tap="handlePay"
>
{{ loading ? "支付中..." : "立即支付" }}
</view>
</view>
</view>
</template>
<script setup>
import { computed } from "vue";
import CheckBox from "@/components/CheckBox/index.vue";
const props = defineProps({
price: {
type: Number,
default: 0,
},
loading: {
type: Boolean,
default: false,
},
agreed: {
type: Boolean,
default: false,
},
agreementReady: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["pay", "update:agreed", "view-agreement"]);
const formattedPrice = computed(() => Number(props.price).toLocaleString("zh-CN"));
const payDisabled = computed(() => {
return props.loading || props.price <= 0 || !props.agreed || !props.agreementReady;
});
const handleAgreementChange = (value) => {
if (!props.agreementReady) {
emit("view-agreement");
return;
}
emit("update:agreed", value);
};
const handleViewAgreement = () => {
emit("view-agreement");
};
const handlePay = () => {
if (props.loading || props.price <= 0) return;
emit("pay");
};
</script>
<style scoped lang="scss">
@import "./styles/index.scss";
</style>

View File

@@ -1,79 +0,0 @@
.recharge-footer {
position: sticky;
bottom: 12px;
display: flex;
flex-direction: column;
gap: 12px;
margin-top: 16px;
padding: 12px;
border-radius: 20px;
background: rgba(255, 255, 255, 0.9);
box-shadow: 0 10px 24px rgba(18, 48, 56, 0.1);
box-sizing: border-box;
}
.recharge-agreement {
display: flex;
align-items: center;
box-sizing: border-box;
}
.recharge-agreement.is-disabled {
opacity: 0.56;
}
.recharge-agreement-text,
.recharge-agreement-link {
font-size: 14px;
line-height: 24px;
}
.recharge-agreement-text {
color: #667386;
}
.recharge-agreement-link {
color: $theme-color-600;
font-weight: 700;
}
.recharge-payment-row {
display: grid;
grid-template-columns: minmax(0, 1fr) 142px;
align-items: center;
gap: 10px;
}
.recharge-gain {
min-width: 0;
overflow: hidden;
color: #667386;
font-size: 12px;
line-height: 18px;
font-weight: 800;
text-overflow: ellipsis;
white-space: nowrap;
}
.recharge-gain-value {
color: #172033;
font-weight: 900;
}
.recharge-pay-button {
height: 46px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 999px;
background: #000000;
color: #ffffff;
font-size: 14px;
line-height: 46px;
font-weight: 900;
white-space: nowrap;
}
.recharge-pay-button.is-disabled {
opacity: 0.48;
}

View File

@@ -1,44 +0,0 @@
<template>
<view class="package-section">
<text class="package-title">优惠充值</text>
<view class="package-list">
<view
v-for="item in packages"
:key="getPackageKey(item)"
:class="['package-card', { 'is-selected': getPackageKey(item) === selectedKey }]"
@tap="emit('select', item)"
>
<view class="package-copy">
<text class="package-points">{{ item.rechargePoints }}积分</text>
<text class="package-origin">充值金额</text>
</view>
<text class="package-price">¥{{ formatPrice(item.rechargeAmount) }}</text>
</view>
</view>
</view>
</template>
<script setup>
defineProps({
packages: {
type: Array,
default: () => [],
},
selectedKey: {
type: String,
default: "",
},
});
const emit = defineEmits(["select"]);
const formatPrice = (price) => Number(price).toLocaleString("zh-CN");
const getPackageKey = (item) => `${item?.rechargeAmount ?? ""}-${item?.rechargePoints ?? ""}`;
</script>
<style scoped lang="scss">
@import "./styles/index.scss";
</style>

View File

@@ -1,71 +0,0 @@
.package-section {
margin-top: 18px;
}
.package-title {
display: block;
margin-bottom: 10px;
color: #172033;
font-size: 15px;
line-height: 20px;
font-weight: 900;
}
.package-list {
display: grid;
gap: 10px;
}
.package-card {
height: 74px;
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
gap: 8px;
padding: 12px 14px;
border: 1.5px solid transparent;
border-radius: 18px;
background: rgba(255, 255, 255, 0.82);
text-align: left;
box-sizing: border-box;
}
.package-card.is-selected {
border-color: #20cf75;
background: rgba(255, 255, 255, 0.72);
}
.package-copy {
min-width: 0;
}
.package-points,
.package-origin,
.package-price {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.package-points {
color: #172033;
font-size: 16px;
line-height: 22px;
font-weight: 900;
}
.package-origin {
margin-top: 6px;
color: #7a8596;
font-size: 11px;
line-height: 15px;
font-weight: 800;
}
.package-price {
color: #12a765;
font-size: 16px;
line-height: 22px;
font-weight: 900;
}

View File

@@ -1,294 +0,0 @@
<template>
<view class="aigc-recharge-page">
<TopNavBar
title="积分充值"
title-align="left"
background="transparent"
title-color="#172033"
back-icon-color="#172033"
:align-with-menu-button="true"
:z-index="3"
@back="handleBack"
/>
<view class="aigc-recharge-content">
<BalanceCard
:points="balancePoints"
@ledger="handleOpenLedger"
/>
<RechargePackageList
:packages="rechargeOptions"
:selected-key="selectedRechargeKey"
@select="handleSelectRechargeOption"
/>
<CustomAmountCard
v-model="customAmount"
:selected="rechargeMode === 'custom'"
@select="handleSelectCustomAmount"
/>
<RechargeFooter
:price="payPrice"
:loading="paying"
:agreed="agreementAccepted"
:agreement-ready="agreementReady"
@update:agreed="agreementAccepted = $event"
@view-agreement="handleViewAgreement"
@pay="handlePay"
/>
</view>
<AgreePopup
:visible="agreementVisible"
title="积分充值协议"
:agreement="agreementContent"
@close="agreementVisible = false"
/>
</view>
</template>
<script setup>
import { computed, ref } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import TopNavBar from "@/components/TopNavBar/index.vue";
import { useCurrentCredit } from "@/pages-aigc/hooks/useCurrentCredit.js";
import {
getPointsTopUpAgreement,
getRechargeOptions,
rechargeCredit,
} from "@/request/api/AigcApi.js";
import AgreePopup from "@/pages/login/components/AgreePopup/index.vue";
import BalanceCard from "./components/BalanceCard/index.vue";
import CustomAmountCard from "./components/CustomAmountCard/index.vue";
import RechargeFooter from "./components/RechargeFooter/index.vue";
import RechargePackageList from "./components/RechargePackageList/index.vue";
const { pointBalance: balancePoints, fetchCurrentCredit } = useCurrentCredit();
const rechargeOptions = ref([]);
const selectedRechargeOption = ref(null);
const customAmount = ref("");
const rechargeMode = ref("package");
const paying = ref(false);
const agreementAccepted = ref(false);
const agreementContent = ref("");
const agreementLoading = ref(false);
const agreementVisible = ref(false);
const agreementReady = computed(() => Boolean(agreementContent.value.trim()));
const getRechargeOptionKey = (option) => {
if (!option) return "";
return `${option.rechargeAmount ?? ""}-${option.rechargePoints ?? ""}`;
};
const selectedRechargeKey = computed(() => {
if (rechargeMode.value !== "package") return "";
return getRechargeOptionKey(selectedRechargeOption.value);
});
const payPrice = computed(() => {
const amount =
rechargeMode.value === "custom"
? Number.parseInt(customAmount.value || "0", 10)
: Number(selectedRechargeOption.value?.rechargeAmount);
return Number.isFinite(amount) ? amount : 0;
});
const showToast = (title) => {
uni.showToast({
title,
icon: "none",
});
};
const fetchRechargeOptions = async () => {
try {
const res = await getRechargeOptions();
if (res?.code === 0 && Array.isArray(res.data)) {
rechargeOptions.value = res.data;
if (rechargeMode.value === "package") {
selectedRechargeOption.value = res.data[0] || null;
}
}
} catch (error) {
console.error("获取积分充值档位失败", error);
}
};
const fetchPointsTopUpAgreement = async (showError = false) => {
if (agreementLoading.value) return false;
agreementLoading.value = true;
try {
const res = await getPointsTopUpAgreement();
const content = typeof res?.data === "string" ? res.data.trim() : "";
if (res?.code === 0 && content) {
agreementContent.value = content;
return true;
}
agreementContent.value = "";
agreementAccepted.value = false;
if (showError) {
showToast(res?.msg || "协议加载失败,请重试");
}
} catch (error) {
console.error("获取积分充值协议失败", error);
agreementContent.value = "";
agreementAccepted.value = false;
if (showError) {
showToast("协议加载失败,请重试");
}
} finally {
agreementLoading.value = false;
}
return false;
};
onLoad(() => {
agreementAccepted.value = false;
fetchRechargeOptions();
fetchPointsTopUpAgreement();
});
onShow(() => {
fetchCurrentCredit();
});
const handleBack = () => {
const pages = getCurrentPages();
if (pages.length > 1) {
uni.navigateBack();
}
};
const handleOpenLedger = () => {
uni.navigateTo({
url: "/pages-aigc/pointsDetails/pointsDetails",
fail: () => showToast("积分明细"),
});
};
const handleSelectRechargeOption = (option) => {
rechargeMode.value = "package";
selectedRechargeOption.value = option;
};
const handleSelectCustomAmount = () => {
rechargeMode.value = "custom";
selectedRechargeOption.value = null;
};
const handleViewAgreement = async () => {
if (agreementReady.value) {
agreementVisible.value = true;
return;
}
if (agreementLoading.value) {
showToast("协议加载中,请稍候");
return;
}
const loaded = await fetchPointsTopUpAgreement(true);
if (loaded) {
agreementVisible.value = true;
}
};
const requestWechatPay = (payData) => {
const { nonceStr, packageVal, paySign, signType, timeStamp } = payData || {};
if (!nonceStr || !packageVal || !paySign || !signType || !timeStamp) {
showToast("支付参数错误,请重试");
return;
}
// #ifdef MP-WEIXIN
uni.requestPayment({
provider: "wxpay",
timeStamp: String(timeStamp),
nonceStr: String(nonceStr),
package: String(packageVal),
signType: String(signType),
paySign: String(paySign),
success: () => {
uni.showToast({
title: "支付成功",
icon: "success",
});
fetchCurrentCredit();
},
fail: () => {
showToast("支付失败,请重试");
},
});
// #endif
};
const handlePay = async () => {
if (paying.value) return;
if (rechargeMode.value === "package" && !selectedRechargeOption.value) {
showToast("请选择充值档位");
return;
}
const rechargeAmount = payPrice.value;
if (!Number.isInteger(rechargeAmount) || rechargeAmount <= 0) {
const message = rechargeMode.value === "custom" ? "请输入正确的充值金额" : "充值档位金额错误,请重试";
showToast(message);
return;
}
if (agreementLoading.value) {
showToast("协议加载中,请稍候");
return;
}
if (!agreementReady.value) {
showToast("协议加载失败,请重试");
return;
}
if (!agreementAccepted.value) {
showToast("请先阅读并同意积分充值协议");
return;
}
paying.value = true;
uni.showLoading({ title: "正在发起支付..." });
try {
const res = await rechargeCredit({
rechargeAmount,
payWay: "0",
paySource: "1",
});
uni.hideLoading();
if (res?.code === 0 && res.data) {
requestWechatPay(res.data);
return;
}
showToast(res?.msg || "充值下单失败,请重试");
} catch (error) {
uni.hideLoading();
console.error("充值下单失败", error);
showToast("充值下单失败,请重试");
} finally {
paying.value = false;
}
};
</script>
<style scoped lang="scss">
@import "./styles/index.scss";
</style>

View File

@@ -1,31 +0,0 @@
.aigc-recharge-page {
position: relative;
display: flex;
flex-direction: column;
width: 100vw;
height: 100vh;
min-height: 812px;
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-recharge-content {
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
padding: 24px 12px;
scrollbar-width: none;
box-sizing: border-box;
}
.aigc-recharge-content::-webkit-scrollbar {
display: none;
}

View File

@@ -26,13 +26,6 @@
>
开始生成
</view>
<view
class="generate-action is-secondary"
hover-class="is-pressed"
@tap="emit('recharge')"
>
充值积分
</view>
</view>
</view>
</template>
@@ -49,7 +42,7 @@ defineProps({
},
});
const emit = defineEmits(["generate", "recharge"]);
const emit = defineEmits(["generate"]);
</script>
<style scoped lang="scss">

View File

@@ -86,9 +86,7 @@
.generate-confirm-actions {
height: 48px;
display: grid;
grid-template-columns: minmax(0, 1.08fr) minmax(0, 0.92fr);
gap: 10px;
display: block;
margin-top: 18px;
}
@@ -114,11 +112,6 @@
box-shadow: 0 12px 24px rgba(32, 207, 117, 0.25);
}
.generate-action.is-secondary {
background: rgba(255, 255, 255, 0.82);
color: #3d5066;
}
.generate-action.is-pressed {
opacity: 0.86;
}

View File

@@ -7,11 +7,12 @@
:safe-area="false"
:is-mask-click="false"
>
<view class="point-dialog point-recharge-dialog">
<text class="point-dialog-badge">充值提醒</text>
<view class="point-dialog point-insufficient-dialog">
<text class="point-dialog-badge">积分提醒</text>
<text class="point-dialog-title">当前积分不足</text>
<text class="point-dialog-desc">
次生成需要先冻结对应积分充值后可继续当前素材生成
平台当前为每位用户赠送 400 积分支持生成 1 张高清旅行图片及 1 5
秒内动效视频充值通道暂未开放如需更多额度敬请关注后续版本
</text>
<view class="point-dialog-summary">
@@ -24,25 +25,18 @@
<text class="point-summary-value">{{ normalizedCost }}</text>
</view>
<view class="point-summary-item is-shortage">
<text class="point-summary-label">需补</text>
<text class="point-summary-label">积分不</text>
<text class="point-summary-value">{{ shortage }}</text>
</view>
</view>
<view class="point-dialog-actions">
<view
class="point-dialog-button is-cancel"
class="point-dialog-button is-primary"
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>
@@ -63,7 +57,7 @@ const props = defineProps({
},
});
const emit = defineEmits(["cancel", "recharge"]);
const emit = defineEmits(["cancel"]);
const popupRef = ref(null);
const normalizedCost = computed(() => Math.max(0, Number(props.cost) || 0));

View File

@@ -8,7 +8,7 @@
box-sizing: border-box;
}
.point-recharge-dialog {
.point-insufficient-dialog {
padding-top: 24px;
}
@@ -92,9 +92,7 @@
}
.point-dialog-actions {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 18px;
display: block;
margin-top: 28px;
}
@@ -114,12 +112,6 @@
box-sizing: border-box;
}
.point-dialog-button.is-cancel {
border: 1px solid #e2e6ea;
background: #ffffff;
color: #172033;
}
.point-dialog-button.is-primary {
background: #061e34;
color: #ffffff;

View File

@@ -1,14 +1,13 @@
<template>
<view class="aigc-use-template-page">
<AigcTopBar :points="pointBalance" recharge-label="充值" @back="handleBack" @history="handleHistory"
@recharge="handleRecharge" />
<AigcTopBar :points="pointBalance" @back="handleBack" @history="handleHistory" />
<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" />
:balance="pointBalance" @generate="handleGenerate" />
<template v-else>
<VersionOptionList :options="templateItems" :selected-value="selectedTemplateItemId"
@@ -34,7 +33,7 @@
@confirm="handleConfirmPhoto" />
</view>
<PointInsufficientDialog v-if="pointDialogVisible" :cost="currentCost" :balance="pointBalance"
@cancel="handleClosePointDialog" @recharge="handlePointRecharge" />
@cancel="handleClosePointDialog" />
<Privacy :visible="privacyVisible" :contract-name="privacyContractName" @agree="handlePrivacyAgree"
@disagree="handlePrivacyDisagree" />
</view>
@@ -165,13 +164,6 @@ const handleHistory = () => {
});
};
const handleRecharge = () => {
uni.navigateTo({
url: "/pages-aigc/recharge/recharge",
fail: () => showPlaceholderToast("积分充值"),
});
};
const handleSelectTemplateItem = (option) => {
if (!option?.templateItemId) return;
@@ -385,10 +377,6 @@ const handleClosePointDialog = () => {
pointDialogVisible.value = false;
};
const handlePointRecharge = () => {
pointDialogVisible.value = false;
handleRecharge();
};
</script>
<style scoped lang="scss">

View File

@@ -164,6 +164,12 @@
"navigationStyle": "custom"
}
},
{
"path": "agreement/agreement",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "useTemplate/useTemplate",
"style": {
@@ -188,12 +194,6 @@
"navigationStyle": "custom"
}
},
{
"path": "recharge/recharge",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pointsDetails/pointsDetails",
"style": {

View File

@@ -24,20 +24,12 @@ function getCurrentCredit() {
return request.get("/hotelBiz/credit/current", {});
}
function getRechargeOptions() {
return request.get("/hotelBiz/credit/recharge/options", {});
}
function rechargeCredit(args) {
return request.post("/hotelBiz/credit/recharge", args);
}
function getCreditLedgerPage(args) {
return request.post("/hotelBiz/credit/ledger/page", args);
}
function getPointsTopUpAgreement() {
return request.get("/hotelBiz/mainScene/pointsTopUpAgreement", {});
function getUserAgreement() {
return request.get("/hotelBiz/mainScene/userAgreement", {});
}
function createAigcGeneratorTaskShare(args) {
@@ -55,10 +47,8 @@ export {
getAigcGeneratorTaskDetail,
createAigcGeneratorTask,
getCurrentCredit,
getRechargeOptions,
rechargeCredit,
getCreditLedgerPage,
getPointsTopUpAgreement,
getUserAgreement,
createAigcGeneratorTaskShare,
getAigcGeneratorTaskShareDetail,
};

View File

@@ -7,7 +7,7 @@ import { getServiceUrl } from "../api/GetServiceUrlApi";
const versionValue = "1.1.3";
/// 是否是测试版本, 测试版本为true 发布版本为false
const developVersion = false;
const developVersion = true;
// 获取服务地址
const getEvnUrl = async () => {