Merge branch 'remove-aigc' of https://git.nianxx.cn/zoujing/YGChatCS into home3.0

This commit is contained in:
2026-07-20 14:53:10 +08:00
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 () => {