feat:修改聚合分反馈动画

This commit is contained in:
zerosaturation 2026-06-24 20:07:42 +08:00
parent 0d6b7a9e95
commit 1939d35f9e

View File

@ -74,12 +74,12 @@
>点击赠送</view
>
<!-- 每个道具独立的反馈动画 -->
<!-- 每个道具独立的反馈动画实时累加每次点击 +1 -->
<view v-if="feedbackItem === item.type" class="feedback-layer">
<view
class="feedback-icon"
:style="{ color: itemFeedbackColor(item.type) }"
>+1</view
>{{ feedbackCount }}</view
>
</view>
</view>
@ -166,6 +166,11 @@ const items = computed(() => props.actionItems);
const safeAreaBottom = ref(0);
const feedbackItem = ref(null); // type
const feedbackCount = ref(0); //
let feedbackClearTimer = null; //
const clickCounts = new Map(); // type UI
const pendingContribution = ref(0); // toast
let contributionToastTimer = null; // toast
const selectedItem = ref(null); //
const quantity = ref(1); //
const isContributing = ref(false); //
@ -177,6 +182,53 @@ function itemFeedbackColor(type) {
const idx = items.value.findIndex((i) => i.type === type);
return FEEDBACK_COLORS[idx >= 0 ? idx % FEEDBACK_COLORS.length : 0];
}
/**
* 实时反馈动画触发器 API 返回前立即调用让连击反馈无延迟
* - 累加 clickCounts[type]feedbackCount 同步显示给 UI
* - 每次点击重置清除定时器800ms 内无新点击才清除让动画在连击期间持续可见
* - 切换道具时计数自动重置clickCounts type 隔离
*/
function triggerRealtimeFeedback(type) {
const nextCount = (clickCounts.get(type) || 0) + 1;
clickCounts.set(type, nextCount);
feedbackItem.value = type;
feedbackCount.value = nextCount;
// 800ms
if (feedbackClearTimer) {
clearTimeout(feedbackClearTimer);
}
feedbackClearTimer = setTimeout(() => {
feedbackItem.value = null;
feedbackCount.value = 0;
clickCounts.delete(type);
feedbackClearTimer = null;
}, 800);
}
/**
* 延迟弹"贡献值 +N"toast连击期间累加停止 800ms 后一次性弹出累计值
* - 每次 API 返回成功调用本函数累加 pendingContribution
* - 重置 800ms 延迟定时器连续点击会一直延后弹 toast
* - 定时器触发时弹出累计 toast 并清零 pendingContribution
* - 用于直接贡献连击场景批量/重试路径不走这里各自处理 toast
*/
function scheduleContributionToast(deltaContribution) {
pendingContribution.value += deltaContribution;
if (contributionToastTimer) {
clearTimeout(contributionToastTimer);
}
contributionToastTimer = setTimeout(() => {
showResultToast(
"✅",
`贡献值 +${pendingContribution.value}`,
);
pendingContribution.value = 0;
contributionToastTimer = null;
}, 800);
}
const pendingActions = ref([]);
const hasFailedActions = ref(false);
const isOnline = ref(true);
@ -306,6 +358,8 @@ onUnmounted(() => {
if (networkListener) uni.offNetworkStatusChange(networkListener);
if (syncDebounceTimer) clearTimeout(syncDebounceTimer);
if (resultToastTimer) clearTimeout(resultToastTimer);
if (feedbackClearTimer) clearTimeout(feedbackClearTimer);
if (contributionToastTimer) clearTimeout(contributionToastTimer);
uni.$off("balanceUpdated");
});
@ -374,30 +428,33 @@ function decreaseQuantity() {
}
// ""
//
// - UI N
// - API purchase N combo 1 record
// contribution N N
// - /
// ""/toast
// - contributeItem catch 线
async function handleDirectContribute(item) {
//
if (processingItems.has(item.type) || isContributing.value) return;
processingItems.add(item.type);
isContributing.value = true;
// 0. +
// purchase contributeItem purchaseItem
const hasBalance = await validateBalance(item.cost);
if (!hasBalance) {
showResultToast("", "余额不足");
return;
}
// 1. 西
triggerRealtimeFeedback(item.type);
// 2.
try {
//
const hasBalance = await validateBalance(item.cost);
if (!hasBalance) {
showResultToast("", "余额不足");
// API
return;
}
// API
const success = await contributeItem(item);
if (!success) {
// contributeItem
return;
}
} finally {
processingItems.delete(item.type);
isContributing.value = false;
// skipFeedback=trueUI 1 contributeItem
// / emit / toast / 线 contributeItem
await contributeItem(item, false, false, 1, true);
} catch (error) {
// contributeItem catch 线 +
console.error("贡献失败:", error);
}
}
@ -467,20 +524,22 @@ async function validateBalance(cost) {
}
//
async function contributeItem(item, isRetry = false, silent = false, qty = 1) {
async function contributeItem(item, isRetry = false, silent = false, qty = 1, skipFeedback = false) {
try {
// 使 activity-config.js purchaseItem
const result = await purchaseItem(props.activityId, item.type, qty);
console.log(
`[contributeItem] result:`,
result,
"isRetry:",
isRetry,
"silent:",
silent,
"qty:",
qty,
);
// console.log(
// `[contributeItem] result:`,
// result,
// "isRetry:",
// isRetry,
// "silent:",
// silent,
// "qty:",
// qty,
// "skipFeedback:",
// skipFeedback,
// );
//
if (!result.success) {
@ -489,8 +548,9 @@ async function contributeItem(item, isRetry = false, silent = false, qty = 1) {
return { success: false, message: msg };
}
//
if (!isRetry && !silent) {
// //
// skipFeedback handleDirectContribute
if (!isRetry && !silent && !skipFeedback) {
feedbackItem.value = item.type;
setTimeout(() => {
feedbackItem.value = null;
@ -507,7 +567,9 @@ async function contributeItem(item, isRetry = false, silent = false, qty = 1) {
// toast
if (!isRetry && !silent) {
showResultToast("✅", `贡献值 +${result.totalContribution}`);
// pendingContribution
// 800ms toast
scheduleContributionToast(result.totalContribution);
}
// syncPendingActions true
@ -991,10 +1053,13 @@ defineExpose({
}
.feedback-icon {
font-size: 60rpx;
font-size: 56rpx;
color: #ff6b9d;
font-weight: bold;
animation: feedback-pop 1s ease-out;
/* 让 X 和数字视觉分开X 略小斜体,数字更醒目 */
font-style: italic;
text-shadow: 0 3rpx 6rpx rgba(0, 0, 0, 0.4);
}
.retry-banner {