From 067ac7c092e2ad4f390fd7db926d6c1b1fbf996c Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Tue, 24 Mar 2026 15:10:58 +0800 Subject: [PATCH] fix View --- punchnet/Views/Login/LoginView.swift | 21 ------ punchnet/Views/Register/RegisterView.swift | 72 +++++++++---------- .../ResetPassword/ResetPasswordView.swift | 6 +- punchnet/Views/RootView.swift | 69 +++++++++++------- punchnet/punchnetApp.swift | 11 +-- 5 files changed, 83 insertions(+), 96 deletions(-) diff --git a/punchnet/Views/Login/LoginView.swift b/punchnet/Views/Login/LoginView.swift index ca6ff40..faf5dde 100644 --- a/punchnet/Views/Login/LoginView.swift +++ b/punchnet/Views/Login/LoginView.swift @@ -77,8 +77,6 @@ struct LoginView: View { .padding(.bottom, 20) } .frame(width: 380, height: 520) - // 关键:macOS 标准毛玻璃背景 - .background(VisualEffectView(material: .hudWindow, blendingMode: .behindWindow)) .ignoresSafeArea() } @@ -288,25 +286,6 @@ struct CustomSecureField: View { } } -// MARK: - 1. 基础 UI 组件 (已修正 Material 枚举) -struct VisualEffectView: NSViewRepresentable { - let material: NSVisualEffectView.Material - let blendingMode: NSVisualEffectView.BlendingMode - - func makeNSView(context: Context) -> NSVisualEffectView { - let view = NSVisualEffectView() - view.material = material - view.blendingMode = blendingMode - view.state = .active - return view - } - - func updateNSView(_ nsView: NSVisualEffectView, context: Context) { - nsView.material = material - nsView.blendingMode = blendingMode - } -} - #Preview { LoginView() .environment(AppContext(noticePort: 0)) diff --git a/punchnet/Views/Register/RegisterView.swift b/punchnet/Views/Register/RegisterView.swift index 2ea1a12..53637c5 100644 --- a/punchnet/Views/Register/RegisterView.swift +++ b/punchnet/Views/Register/RegisterView.swift @@ -14,11 +14,10 @@ struct RegisterRootView: View { var body: some View { ZStack { - // 背景毛玻璃 - VisualEffectView(material: .underWindowBackground, blendingMode: .behindWindow) + Color.clear .ignoresSafeArea() - Group { + ZStack(alignment: .center) { switch registerModel.stage { case .requestVerifyCode: RegisterRequestVerifyCodeView() @@ -64,7 +63,6 @@ struct RegisterRootView: View { .transition(.opacity) // 按钮出现的动画 } } - .frame(width: 500, height: 400) } } @@ -122,7 +120,9 @@ struct RegisterRequestVerifyCodeView: View { .frame(width: 280) Button(action: { - self.requestVerifyCode(username: model.username) + Task { @MainActor in + await self.requestVerifyCode(username: model.username) + } }) { Text("获取验证码") .fontWeight(.medium) @@ -141,40 +141,40 @@ struct RegisterRequestVerifyCodeView: View { } } - private func requestVerifyCode(username: String) { + private func requestVerifyCode(username: String) async { self.isProcessing = true - Task { @MainActor in - if username.isEmpty { - self.showAlert = true - self.errorMessage = "邮箱为空" - self.isProcessing = false - return - } - - switch SDLUtil.identifyContact(username) { - case .email: - do { - let registerSession = try await self.registerModel.requestVerifyCode(username: username) - withAnimation(.spring(duration: 0.6, bounce: 0.2)) { - self.registerModel.stage = .submitVerifyCode - self.registerModel.username = username - self.registerModel.sessionId = registerSession.sessionId - - self.registerModel.transitionEdge = .trailing - } - } catch let err as SDLAPIError { - self.showAlert = true - self.errorMessage = err.message - } catch let err { - self.showAlert = true - self.errorMessage = err.localizedDescription - } - default: - self.showAlert = true - self.errorMessage = "邮箱格式错误" - } + defer { self.isProcessing = false } + + if username.isEmpty { + self.showAlert = true + self.errorMessage = "邮箱为空" + return + } + + switch SDLUtil.identifyContact(username) { + case .email: + do { + let registerSession = try await self.registerModel.requestVerifyCode(username: username) + withAnimation(.spring(duration: 0.6, bounce: 0.2)) { + self.registerModel.stage = .submitVerifyCode + self.registerModel.username = username + self.registerModel.sessionId = registerSession.sessionId + + self.registerModel.transitionEdge = .trailing + } + } catch let err as SDLAPIError { + self.showAlert = true + self.errorMessage = err.message + } catch let err { + self.showAlert = true + self.errorMessage = err.localizedDescription + } + default: + self.showAlert = true + self.errorMessage = "邮箱格式错误" + } } } diff --git a/punchnet/Views/ResetPassword/ResetPasswordView.swift b/punchnet/Views/ResetPassword/ResetPasswordView.swift index e0e85d6..15aea34 100644 --- a/punchnet/Views/ResetPassword/ResetPasswordView.swift +++ b/punchnet/Views/ResetPassword/ResetPasswordView.swift @@ -14,11 +14,10 @@ struct ResetPasswordRootView: View { var body: some View { ZStack { - // 背景毛玻璃 (macOS 风格) - VisualEffectView(material: .underWindowBackground, blendingMode: .behindWindow) + Color.clear .ignoresSafeArea() - Group { + ZStack(alignment: .center) { switch resetPasswordModel.stage { case .requestVerifyCode: GetVerifyCodeView() @@ -60,7 +59,6 @@ struct ResetPasswordRootView: View { .transition(.opacity) // 按钮出现的动画 } } - .frame(width: 500, height: 400) } } diff --git a/punchnet/Views/RootView.swift b/punchnet/Views/RootView.swift index 352183e..a6f309a 100644 --- a/punchnet/Views/RootView.swift +++ b/punchnet/Views/RootView.swift @@ -9,60 +9,79 @@ import SwiftUI struct RootView: View { @Environment(AppContext.self) var appContext: AppContext - @State private var updateManager = AppUpdateManager.shared var body: some View { ZStack { - // 主要界面 - Group { + // 1. 主要界面容器 + // 使用 ZStack 代替 Group,因为它在处理内容对齐和转场重叠时更稳定 + ZStack(alignment: .center) { switch appContext.appScene { case .login(username: let username): LoginView(username: username) + .id("scene_login") // 显式 ID 确保转场触发 case .logined: NetworkView() + .id("scene_logined") case .register: RegisterRootView() + .id("scene_register") case .resetPassword: ResetPasswordRootView() + .id("scene_reset") } } + // 2. 关键:设置容器尺寸行为 + // 如果是全屏应用,用 maxWidth/Height .infinity + // 如果是固定大小窗口,可以在这里写死 minWidth/minHeight + .frame(maxWidth: .infinity, maxHeight: .infinity) + // 3. 限制裁剪,防止位移动画超出 RootView 的边界 + .clipped() .transition(.asymmetric( insertion: .move(edge: .trailing).combined(with: .opacity), - removal: .move(edge: .trailing).combined(with: .opacity) + removal: .move(edge: .leading).combined(with: .opacity) // 修改为 leading 更有“流转”感 )) - // 自动更新遮罩 + // 自动更新遮罩层级 if updateManager.showUpdateOverlay, let info = updateManager.updateInfo { - // 遮罩背景 - Color.black.opacity(0.4) - .ignoresSafeArea() - .onTapGesture { - if !info.forceUpdate { - updateManager.showUpdateOverlay = false - } - } - - // 弹窗卡片 - AppUpdateView(info: info) { - updateManager.showUpdateOverlay = false - } - .clipShape(RoundedRectangle(cornerRadius: 16)) - .shadow(color: .black.opacity(0.3), radius: 20) - .transition(.asymmetric( - insertion: .scale(scale: 0.9).combined(with: .opacity), - removal: .opacity - )) + updateOverlay(info: info) } } + // 4. 统一处理 Scene 切换的动画 + .animation(.spring(duration: 0.5), value: appContext.appScene) .animation(.spring(duration: 0.4), value: updateManager.showUpdateOverlay) + // 关键:macOS 标准毛玻璃背景 + .background(VisualEffectView(material: .hudWindow, blendingMode: .behindWindow)) .task { - // 启动时静默检查 let checkUpdateResult = await updateManager.checkUpdate(isManual: false) NSLog("[RootView] checkUpdateResult: \(checkUpdateResult)") } } + // 将遮罩抽离,保持 body 清爽 + @ViewBuilder + private func updateOverlay(info: SDLAPIClient.AppUpgradeInfo) -> some View { + ZStack { + Color.black.opacity(0.4) + .ignoresSafeArea() + .onTapGesture { + if !info.forceUpdate { + updateManager.showUpdateOverlay = false + } + } + + AppUpdateView(info: info) { + updateManager.showUpdateOverlay = false + } + .clipShape(RoundedRectangle(cornerRadius: 16)) + .shadow(color: .black.opacity(0.3), radius: 20) + } + .transition(.asymmetric( + insertion: .scale(scale: 0.9).combined(with: .opacity), + removal: .opacity + )) + .zIndex(100) // 确保更新遮罩永远在最上层 + } } #Preview { diff --git a/punchnet/punchnetApp.swift b/punchnet/punchnetApp.swift index 6309689..2ed4c66 100644 --- a/punchnet/punchnetApp.swift +++ b/punchnet/punchnetApp.swift @@ -55,16 +55,7 @@ struct punchnetApp: App { //.interactiveDismissDisabled() // 强制阅读 } } -// .commands { -// CommandGroup(replacing: .appInfo) { -// Button { -// openWindow(id: "abortPunchnet") -// } label: { -// Text("About Punchnet") -// } -// } -// } - //.windowResizability(.contentSize) + .windowResizability(.contentSize) .windowToolbarStyle(.unified) .defaultPosition(.center)