diff --git a/wake/Features/BlindBox/Components/BlindBoxActionButton.swift b/wake/Features/BlindBox/Components/BlindBoxActionButton.swift index 655306f..ac5e1e3 100644 --- a/wake/Features/BlindBox/Components/BlindBoxActionButton.swift +++ b/wake/Features/BlindBox/Components/BlindBoxActionButton.swift @@ -37,6 +37,15 @@ struct BlindBoxActionButton: View { .background(Color.themePrimary) .foregroundColor(Color.themeTextMessageMain) .cornerRadius(32) + case .opening: + Text("Ready") + .font(Typography.font(for: .body)) + .fontWeight(.bold) + .frame(maxWidth: .infinity) + .padding() + .background(Color.themePrimary) + .foregroundColor(Color.themeTextMessageMain) + .cornerRadius(32) default: Text("Go to Buy") .font(Typography.font(for: .body)) diff --git a/wake/Features/BlindBox/View/BlindBoxView.swift b/wake/Features/BlindBox/View/BlindBoxView.swift index 145ce50..78fc987 100644 --- a/wake/Features/BlindBox/View/BlindBoxView.swift +++ b/wake/Features/BlindBox/View/BlindBoxView.swift @@ -22,6 +22,8 @@ struct BlindBoxView: View { @State private var showLogin = false // 倒计时由 ViewModel 管理(countdownText) @State private var animationPhase: BlindBoxAnimationPhase = .none + // 防止开箱二次点击 + @State private var isOpening: Bool = false // 查询数据 - 简单查询 @Query private var login: [Login] @@ -55,6 +57,8 @@ struct BlindBoxView: View { viewModel.player?.pause() viewModel.player?.replaceCurrentItem(with: nil) viewModel.player = nil + // 重置防连点状态 + isOpening = false NotificationCenter.default.removeObserver( self, @@ -153,6 +157,8 @@ struct BlindBoxView: View { maxWidth: .infinity, maxHeight: UIScreen.main.bounds.height * 0.65 ) + // 确保开启动画层级更高 + .zIndex(animationPhase == .opening ? 1 : 0) // 打开 TODO 引导时,也要有按钮 @@ -161,12 +167,23 @@ struct BlindBoxView: View { phase: animationPhase, countdownText: viewModel.countdownText, onOpen: { - openBlindBoxAndUpdateState(navigateAfterOpen: true) + // 防连点:若已在处理则忽略 + guard !isOpening else { return } + isOpening = true + // 先播放开箱动画,动画结束后再在 onOpeningCompleted 内导航 + openBlindBoxAndUpdateState(navigateAfterOpen: false) }, onGoToBuy: { Router.shared.navigate(to: .mediaUpload) } ) + .disabled(isOpening) + // 开启动画时隐藏按钮,避免覆盖在动画之上 + .opacity(animationPhase == .opening ? 0 : 1) + // 可见性切换时进行轻微淡入淡出 + .animation(.easeInOut(duration: 0.2), value: animationPhase) + // 开启动画时完全屏蔽交互 + .allowsHitTesting(animationPhase != .opening) .padding(.horizontal) } } @@ -259,6 +276,8 @@ struct BlindBoxView: View { } } catch { print("❌ 开启盲盒失败: \(error)") + // 失败时允许再次点击 + isOpening = false } } } @@ -289,6 +308,8 @@ struct BlindBoxView: View { goToFeedback: false ) ) + // 导航后立即重置状态,确保返回时按钮可用 + isOpening = false return } } else if mediaType == .image { @@ -310,11 +331,15 @@ struct BlindBoxView: View { goToFeedback: true ) ) + // 导航后立即重置状态,确保返回时按钮可用 + isOpening = false return } } // 若仍未获取到媒体,记录日志以便排查 print("⚠️ navigateToOutcome: 媒体尚未准备好,videoURL=\(viewModel.videoURL), image=\(String(describing: viewModel.displayImage))") + // 如果因为媒体未就绪而导航失败,也应解锁按钮 + isOpening = false } } } diff --git a/wake/Features/BlindBox/View/BlindOutCome.swift b/wake/Features/BlindBox/View/BlindOutCome.swift index 6586f47..16d4b8d 100644 --- a/wake/Features/BlindBox/View/BlindOutCome.swift +++ b/wake/Features/BlindBox/View/BlindOutCome.swift @@ -115,13 +115,17 @@ struct BlindOutcomeView: View { .background(Color.themePrimary) .cornerRadius(26) } + // 弹窗显示时,按钮淡出且不可交互 + .opacity(showIPListModal ? 0 : 1) + .animation(.easeInOut(duration: 0.2), value: showIPListModal) + .allowsHitTesting(!showIPListModal) .padding(.horizontal) } .padding(.bottom, 20) } } - // .navigationBarHidden(true) - // .navigationBarBackButtonHidden(true) + .navigationBarHidden(true) + .navigationBarBackButtonHidden(true) .overlay( JoinModal(isPresented: $showIPListModal, onClose: { onContinue() }) )