Compare commits
7 Commits
aigc
...
987e3cfefc
| Author | SHA1 | Date | |
|---|---|---|---|
| 987e3cfefc | |||
| f0843e0f12 | |||
|
|
54a83bad5d | ||
|
|
c7062e0b51 | ||
| c399bba109 | |||
|
|
bfebed44b0 | ||
| 54e6d423ae |
59
src/pages-aigc/agreement/agreement.vue
Normal file
59
src/pages-aigc/agreement/agreement.vue
Normal 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>
|
||||
54
src/pages-aigc/agreement/styles/index.scss
Normal file
54
src/pages-aigc/agreement/styles/index.scss
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
safe-area-inset-bottom @query="queryList">
|
||||
<template #top>
|
||||
<TopNavBar title="积分明细" background="transparent" title-color="#172033" back-icon-color="#172033"
|
||||
:align-with-menu-button="true" :z-index="3" @back="handleBack" />
|
||||
custom-back :align-with-menu-button="true" :z-index="3" @back="handleBack" />
|
||||
</template>
|
||||
|
||||
<view class="points-details-content">
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<template #top>
|
||||
<view class="aigc-record-nav">
|
||||
<TopNavBar title="最近任务" background="transparent" title-color="#162235" back-icon-color="#162235"
|
||||
:align-with-menu-button="true" :z-index="1" @back="handleBack" />
|
||||
custom-back :align-with-menu-button="true" :z-index="1" @back="handleBack" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<view class="shared-work-page">
|
||||
<view class="shared-work-nav">
|
||||
<TopNavBar :title="shareDetail.templateName || '旅行作品'" background="#fff" title-color="#173a5f"
|
||||
back-icon-color="#173a5f" :align-with-menu-button="true" :z-index="3" @back="handleBack" />
|
||||
back-icon-color="#173a5f" custom-back :align-with-menu-button="true" :z-index="3" @back="handleBack" />
|
||||
</view>
|
||||
|
||||
<view class="shared-work-scroll">
|
||||
@@ -106,13 +106,10 @@ const openAigcHome = () => {
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length > 1) {
|
||||
uni.navigateBack();
|
||||
return;
|
||||
}
|
||||
|
||||
openAigcHome();
|
||||
uni.reLaunch({
|
||||
url: "/pages/index/index",
|
||||
fail: () => showToast("返回首页失败"),
|
||||
});
|
||||
};
|
||||
|
||||
const handleCreateOwn = async () => {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -210,8 +210,8 @@ const validateUserForms = () => {
|
||||
return true;
|
||||
};
|
||||
|
||||
// 处理支付点击事件
|
||||
const handlePayClick = ThrottleUtils.createThrottle(async (goodsData) => {
|
||||
// 处理支付点击事件 - 使用执行锁防止重复支付
|
||||
const handlePayClick = ThrottleUtils.createExecutionLock(async (goodsData) => {
|
||||
console.log("处理支付点击事件", userFormList.value);
|
||||
// 预约日期,酒店类型不需要
|
||||
if (orderData.value.reservationEnabled) {
|
||||
@@ -267,72 +267,82 @@ const handlePayClick = ThrottleUtils.createThrottle(async (goodsData) => {
|
||||
|
||||
// 点击后立即展示 loading
|
||||
uni.showLoading({ title: "正在提交订单..." });
|
||||
const res = await orderPay(params);
|
||||
console.log("确认订单---2:", res);
|
||||
uni.hideLoading();
|
||||
|
||||
try {
|
||||
const res = await orderPay(params);
|
||||
console.log("确认订单---2:", res);
|
||||
|
||||
// 检查接口返回数据
|
||||
if (!res || !res.data) {
|
||||
// 检查接口返回数据
|
||||
if (!res || !res.data) {
|
||||
setTimeout(() => {
|
||||
uni.showToast({
|
||||
title: res.msg || "订单创建失败,请重试",
|
||||
icon: "none",
|
||||
});
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
const { data } = res;
|
||||
const { nonceStr, packageVal, paySign, signType, timeStamp } = data;
|
||||
|
||||
// 验证支付参数是否完整
|
||||
if (!nonceStr || !packageVal || !paySign || !signType || !timeStamp) {
|
||||
setTimeout(() => {
|
||||
uni.showToast({ title: "支付参数错误,请重试", icon: "none" });
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
// 关闭 loading
|
||||
uni.hideLoading();
|
||||
setTimeout(() => {
|
||||
uni.showToast({
|
||||
title: res.msg || "订单创建失败,请重试",
|
||||
icon: "none",
|
||||
});
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
const { data } = res;
|
||||
const { nonceStr, packageVal, paySign, signType, timeStamp } = data;
|
||||
|
||||
// 验证支付参数是否完整
|
||||
if (!nonceStr || !packageVal || !paySign || !signType || !timeStamp) {
|
||||
uni.hideLoading();
|
||||
setTimeout(() => {
|
||||
uni.showToast({ title: "支付参数错误,请重试", icon: "none" });
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
// 在发起微信支付前关闭 loading(避免与原生支付 UI 冲突)
|
||||
uni.hideLoading();
|
||||
|
||||
// #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",
|
||||
// #ifdef MP-WEIXIN
|
||||
// 调用微信支付
|
||||
await new Promise((resolve) => {
|
||||
uni.requestPayment({
|
||||
provider: "wxpay",
|
||||
timeStamp: String(timeStamp), // 确保为字符串类型
|
||||
nonceStr: String(nonceStr),
|
||||
package: String(packageVal), // 确保为字符串类型
|
||||
signType: String(signType),
|
||||
paySign: String(paySign),
|
||||
success: () => {
|
||||
uni.navigateTo({
|
||||
url: "/pages-order/order/list",
|
||||
uni.showToast({
|
||||
title: "支付成功",
|
||||
icon: "success",
|
||||
success: () => {
|
||||
uni.navigateTo({
|
||||
url: "/pages-order/order/list",
|
||||
});
|
||||
},
|
||||
});
|
||||
resolve();
|
||||
},
|
||||
fail: (e) => {
|
||||
console.error("支付失败:", e);
|
||||
uni.showToast({ title: "支付失败,请重试", icon: "none" });
|
||||
resolve();
|
||||
},
|
||||
});
|
||||
},
|
||||
fail: (e) => {
|
||||
console.error("支付失败:", e);
|
||||
uni.showToast({ title: "支付失败,请重试", icon: "none" });
|
||||
},
|
||||
});
|
||||
// #endif
|
||||
});
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
uni.showModal({
|
||||
title: "提示",
|
||||
content: "支付功能开发中",
|
||||
showCancel: false,
|
||||
});
|
||||
// #endif
|
||||
}, 1000);
|
||||
// #ifdef APP-PLUS
|
||||
uni.showModal({
|
||||
title: "提示",
|
||||
content: "支付功能开发中",
|
||||
showCancel: false,
|
||||
});
|
||||
// #endif
|
||||
} catch (error) {
|
||||
console.error("支付流程出错:", error);
|
||||
uni.showToast({ title: "支付失败,请重试", icon: "none" });
|
||||
} finally {
|
||||
// 确保 loading 被关闭
|
||||
uni.hideLoading();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<script setup>
|
||||
import { defineProps, defineEmits, computed } from "vue";
|
||||
import { orderPayNow } from "@/request/api/OrderApi";
|
||||
import { DebounceUtils } from "@/utils";
|
||||
import { ThrottleUtils } from "@/utils";
|
||||
|
||||
const props = defineProps({
|
||||
orderData: {
|
||||
@@ -64,8 +64,8 @@ const buttonText = computed(() => {
|
||||
// 定义事件发射器
|
||||
const emit = defineEmits(["refund", "refresh"]);
|
||||
|
||||
// 处理按钮点击事件
|
||||
const handleButtonClick = DebounceUtils.createDebounce(async (orderData) => {
|
||||
// 处理按钮点击事件 - 使用执行锁防止重复支付
|
||||
const handleButtonClick = ThrottleUtils.createExecutionLock(async (orderData) => {
|
||||
try {
|
||||
// 再次预定跳转商品详情
|
||||
if (["1", "2", "3", "4", "5", "6"].includes(statusCode.value)) {
|
||||
@@ -89,7 +89,6 @@ const handleButtonClick = DebounceUtils.createDebounce(async (orderData) => {
|
||||
|
||||
// 检查接口返回数据
|
||||
if (!res || !res.data) {
|
||||
uni.hideLoading();
|
||||
setTimeout(() => {
|
||||
uni.showToast({ title: res.msg || "订单创建失败,请重试", icon: "none" });
|
||||
}, 100);
|
||||
@@ -101,38 +100,41 @@ const handleButtonClick = DebounceUtils.createDebounce(async (orderData) => {
|
||||
|
||||
// 验证支付参数是否完整
|
||||
if (!nonceStr || !packageVal || !paySign || !signType || !timeStamp) {
|
||||
uni.hideLoading();
|
||||
setTimeout(() => {
|
||||
uni.showToast({ title: "支付参数错误,请重试", icon: "none" });
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
// 在发起微信支付前关闭 loading(避免与原生支付 UI 冲突)
|
||||
// 关闭 loading
|
||||
uni.hideLoading();
|
||||
|
||||
// #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",
|
||||
success: () => {
|
||||
console.log("支付成功,刷新订单详情");
|
||||
emit("refresh", { orderId: orderId });
|
||||
},
|
||||
});
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ title: "支付失败,请重试", icon: "none" });
|
||||
},
|
||||
// 调用微信支付 - 包装成 Promise 以便锁住执行状态
|
||||
await new Promise((resolve) => {
|
||||
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",
|
||||
success: () => {
|
||||
console.log("支付成功,刷新订单详情");
|
||||
emit("refresh", { orderId: orderId });
|
||||
},
|
||||
});
|
||||
resolve();
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ title: "支付失败,请重试", icon: "none" });
|
||||
resolve();
|
||||
},
|
||||
});
|
||||
});
|
||||
// #endif
|
||||
|
||||
@@ -146,9 +148,12 @@ const handleButtonClick = DebounceUtils.createDebounce(async (orderData) => {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("操作失败:", error);
|
||||
uni.showToast({ title: "操作失败,请重试", icon: "none" });
|
||||
} finally {
|
||||
// 确保 loading 被关闭
|
||||
uni.hideLoading();
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -28,8 +28,8 @@ 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) {
|
||||
@@ -48,7 +48,7 @@ export {
|
||||
createAigcGeneratorTask,
|
||||
getCurrentCredit,
|
||||
getCreditLedgerPage,
|
||||
getPointsTopUpAgreement,
|
||||
getUserAgreement,
|
||||
createAigcGeneratorTaskShare,
|
||||
getAigcGeneratorTaskShareDetail,
|
||||
};
|
||||
|
||||
@@ -280,7 +280,7 @@ export class DebounceUtils {
|
||||
*/
|
||||
export class ThrottleUtils {
|
||||
/**
|
||||
* 创建节流函数
|
||||
* 创建节流函数(传统时间戳方式)
|
||||
* @param {Function} func - 要节流的函数
|
||||
* @param {number} delay - 节流延迟时间
|
||||
* @returns {Function} 节流后的函数
|
||||
@@ -297,6 +297,81 @@ export class ThrottleUtils {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建异步安全的节流函数(带执行状态锁)
|
||||
* 适用于支付等需要等待完成的异步操作
|
||||
* @param {Function} func - 要节流的异步函数
|
||||
* @param {number} delay - 节流延迟时间(毫秒)
|
||||
* @returns {Function} 节流后的函数
|
||||
*/
|
||||
static createAsyncThrottle(func, delay = 1000) {
|
||||
let isExecuting = false; // 执行状态锁
|
||||
let lastExecTime = 0; // 上次执行时间
|
||||
|
||||
return async function (...args) {
|
||||
const now = Date.now();
|
||||
|
||||
// 如果正在执行中,直接返回
|
||||
if (isExecuting) {
|
||||
console.warn('函数正在执行中,跳过此次调用');
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果还在节流时间内,直接返回
|
||||
if (now - lastExecTime < delay) {
|
||||
console.warn('节流时间内,跳过此次调用');
|
||||
return;
|
||||
}
|
||||
|
||||
// 加锁
|
||||
isExecuting = true;
|
||||
lastExecTime = now;
|
||||
|
||||
try {
|
||||
// 执行异步函数
|
||||
return await func.apply(this, args);
|
||||
} catch (error) {
|
||||
console.error('异步节流函数执行出错:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
// 无论成功失败都解锁
|
||||
isExecuting = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建执行锁函数(最简单的防重复方式,只锁执行状态)
|
||||
* 适用于支付等场景,上一次没完成前绝对不执行第二次
|
||||
* @param {Function} func - 要加锁的函数
|
||||
* @returns {Function} 加锁后的函数
|
||||
*/
|
||||
static createExecutionLock(func) {
|
||||
let isExecuting = false;
|
||||
|
||||
return async function (...args) {
|
||||
// 如果正在执行中,直接返回
|
||||
if (isExecuting) {
|
||||
console.warn('函数正在执行中,跳过此次调用');
|
||||
return;
|
||||
}
|
||||
|
||||
// 加锁
|
||||
isExecuting = true;
|
||||
|
||||
try {
|
||||
// 执行函数
|
||||
return await func.apply(this, args);
|
||||
} catch (error) {
|
||||
console.error('执行锁函数执行出错:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
// 无论成功失败都解锁
|
||||
isExecuting = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user