From a62d531493a4ffd1bed7d08df581c98ad12afa96 Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Thu, 19 Mar 2026 22:44:50 +0800 Subject: [PATCH] fix settings --- .../Views/Settings/SettingsAccountView.swift | 229 ++++++++++++------ punchnet/Views/Settings/SettingsView.swift | 129 ++++++++-- punchnet/Views/UserContext.swift | 6 + punchnet/punchnetApp.swift | 2 +- 4 files changed, 263 insertions(+), 103 deletions(-) diff --git a/punchnet/Views/Settings/SettingsAccountView.swift b/punchnet/Views/Settings/SettingsAccountView.swift index d6e5643..491407b 100644 --- a/punchnet/Views/Settings/SettingsAccountView.swift +++ b/punchnet/Views/Settings/SettingsAccountView.swift @@ -4,34 +4,58 @@ // // Created by 安礼成 on 2026/1/16. // - import SwiftUI struct SettingsAccountView: View { @State var state: SettingsState = SettingsState() @Environment(UserContext.self) var userContext: UserContext + @Environment(\.openWindow) var openWindow var body: some View { - ScrollView(.vertical, showsIndicators: false) { - VStack(alignment: .leading) { - Text("账户") + VStack(alignment: .leading, spacing: 24) { + // MARK: - 账户部分 + sectionHeader(title: "账户安全", icon: "shield.lefthalf.filled") - if let loginCredit = userContext.loginCredit { - switch loginCredit { - case .token(let token): - TokenCreditView(token: token) - case .accountAndPasword(let account, let password): - AccountCreditView(username: account) + VStack(spacing: 0) { + if userContext.isLogined, let loginCredit = userContext.loginCredit { + switch loginCredit { + case .token(let token): + TokenCreditView(token: token) + case .accountAndPasword(let account, _): + AccountCreditView(username: account) + } + } else { + // 蓝色主按钮 + Button { + self.openMainWindow(id: "main") + } label: { + Text("登录") + .fontWeight(.medium) + } + .buttonStyle(.borderedProminent) } } + .background(Color.primary.opacity(0.03)) + .cornerRadius(12) + .overlay(RoundedRectangle(cornerRadius: 12).stroke(Color.primary.opacity(0.05), lineWidth: 1)) + + // MARK: - 网络部分 + sectionHeader(title: "网络配置", icon: "network") - Text("网络") - - HStack(alignment: .top) { - Text("默认网络") - - VStack(alignment: .leading) { + VStack(spacing: 16) { + HStack { + VStack(alignment: .leading, spacing: 4) { + Text("默认网络") + .font(.subheadline) + .foregroundColor(.secondary) + + Text(state.selectedNetwork.name) + .font(.headline) + } + + Spacer() + Menu { ForEach(state.networks, id: \.id) { network in Button(network.name) { @@ -39,104 +63,151 @@ struct SettingsAccountView: View { } } } label: { - Text(state.selectedNetwork.name) - .padding() - .background(Color.gray.opacity(0.2)) - .cornerRadius(5) + Text("切换网络") + .font(.subheadline) + .padding(.horizontal, 12) + .padding(.vertical, 6) + .background(Capsule().fill(Color.blue.opacity(0.1))) } - + .buttonStyle(.plain) + } + + Divider() + + HStack { Button { } label: { - Text("进入管理平台") + Label("进入管理平台", systemImage: "arrow.up.right.square") } + .buttonStyle(.plain) + .foregroundColor(.blue) - } - - Button { + Spacer() - } label: { - Text("详情") + Button("查看详情") { + + } + .buttonStyle(.bordered) + .controlSize(.small) } } - + .padding(16) + .background(Color.primary.opacity(0.03)) + .cornerRadius(12) } + .frame(maxWidth: 600) // 限制宽度防止在大屏幕上显得太散 } - .padding() - } + // 打开窗口 + private func openMainWindow(id: String) { + let window = NSApp.windows.first { win in + if let idStr = win.identifier?.rawValue { + return idStr.starts(with: id) + } + return false + } + + if let window { + window.makeKeyAndOrderFront(nil) + NSApp.activate(ignoringOtherApps: true) + } else { + openWindow(id: id) + } + } + + // 辅助头部组件 + private func sectionHeader(title: String, icon: String) -> some View { + HStack { + Image(systemName: icon) + .foregroundColor(.blue) + + Text(title) + .font(.system(size: 16, weight: .bold)) + } + .padding(.leading, 4) + } } +// MARK: - 内部视图扩展 extension SettingsAccountView { - // 基于账号密码的登陆 - struct AccountCreditView: View { - @Environment(UserContext.self) var userContext: UserContext - - let username: String + // 统一的条目容器样式 + struct AccountRow: View { + let icon: String + let title: String + let subtitle: String + var actions: AnyView var body: some View { - HStack { - Image("logo") - .resizable() - .frame(width: 20, height: 20) + HStack(spacing: 16) { + // 模拟 Logo + Circle() + .fill(Color.blue.gradient) + .frame(width: 32, height: 32) + .overlay( + Image(systemName: icon) + .foregroundColor(.white) + .font(.system(size: 14)) + ) - Text(username) + VStack(alignment: .leading, spacing: 2) { + Text(title) + .font(.subheadline) + .foregroundColor(.secondary) + + Text(subtitle) + .font(.system(size: 15, weight: .medium, design: .monospaced)) + .lineLimit(1) + } Spacer() - Button { - - } label: { - Text("修改密码") - } - - Button { - Task { @MainActor in - try await VPNManager.shared.disableVpn() - self.userContext.isLogined = false - } - } label: { - Text("退出登陆") - } + actions } - .frame(width: 400) + .padding(16) + } + } + + struct AccountCreditView: View { + @Environment(UserContext.self) var userContext: UserContext + let username: String + + var body: some View { + AccountRow(icon: "person.fill", title: "当前登录账号", subtitle: username, actions: AnyView( + HStack(spacing: 12) { + Button("修改密码") { + + } + .buttonStyle(.link) + + Button("退出登录") { + Task { @MainActor in + try await userContext.logout() + } + } + .buttonStyle(.bordered) + .foregroundColor(.red) + } + )) } - } - // 基于Token的登陆 struct TokenCreditView: View { @Environment(UserContext.self) var userContext: UserContext let token: String var body: some View { - HStack { - Image("logo") - .resizable() - .frame(width: 20, height: 20) - - Text(token) - - Spacer() - - Button { + AccountRow(icon: "key.horizontal.fill", title: "Token 登录", subtitle: token, actions: AnyView( + Button("退出登录") { Task { @MainActor in - try await VPNManager.shared.disableVpn() - self.userContext.isLogined = false + try await userContext.logout() } - } label: { - Text("退出登陆") } - - } - .frame(width: 400) + .buttonStyle(.bordered) + .foregroundColor(.red) + )) } - } } - -#Preview { - SettingsAccountView() -} diff --git a/punchnet/Views/Settings/SettingsView.swift b/punchnet/Views/Settings/SettingsView.swift index b2f1790..9806d2a 100644 --- a/punchnet/Views/Settings/SettingsView.swift +++ b/punchnet/Views/Settings/SettingsView.swift @@ -4,13 +4,11 @@ // // Created by 安礼成 on 2026/1/16. // - import SwiftUI struct SettingsView: View { @State private var columnVisibility: NavigationSplitViewVisibility = .all @State private var state = SettingsState() - @State private var hovering = false @State private var selectedMenu: MenuItem = .accout enum MenuItem: String, CaseIterable { @@ -19,42 +17,127 @@ struct SettingsView: View { case device = "设备" case system = "软件" case about = "关于" + + // 图标信息 + var icon: String { + switch self { + case .accout: + return "person.crop.circle.fill" + case .network: + return "network" + case .device: + return "laptopcomputer" + case .system: + return "gearshape.fill" + case .about: + return "info.circle.fill" + } + } } var body: some View { NavigationSplitView(columnVisibility: $columnVisibility) { - List(MenuItem.allCases, id: \.self, selection: $selectedMenu) { menu in - HStack(alignment: .center) { - Text(menu.rawValue) + // MARK: - 自定义侧边栏 + VStack(alignment: .leading, spacing: 20) { + Text("设置") + .font(.system(size: 24, weight: .bold)) + .padding(.horizontal, 16) + .padding(.top, 24) + + VStack(spacing: 4) { + ForEach(MenuItem.allCases, id: \.self) { menu in + SidebarItem( + icon: menu.icon, + title: menu.rawValue, + isSelected: selectedMenu == menu + ) + .onTapGesture { + // 使用精调的 spring 动画切换 + withAnimation(.spring(response: 0.35, dampingFraction: 0.8)) { + self.selectedMenu = menu + } + } + } Spacer() } + .padding(.horizontal, 12) } - .listStyle(.sidebar) - .frame(minWidth: 180, idealWidth: 200, maxWidth: 250) + .background(VisualEffectView(material: .sidebar, blendingMode: .behindWindow)) + .navigationSplitViewColumnWidth(min: 180, ideal: 200, max: 250) } detail: { - VStack(alignment: .leading, spacing: 0) { - Group { - switch self.selectedMenu { - case .accout: - SettingsAccountView(state: self.state) - case .network: - SettingsNetworkView(state: self.state) - case .device: - SettingsDeviceView() - case .system: - SettingsSystemView() - case .about: - SettingsAboutView() + // MARK: - 详情页转场 + ZStack(alignment: .topLeading) { + // 背景保持统一 + VisualEffectView(material: .hudWindow, blendingMode: .behindWindow) + .ignoresSafeArea() + + VStack(alignment: .leading, spacing: 0) { + Group { + // 使用 ID 辅助 SwiftUI 识别视图切换,触发 transition + switch self.selectedMenu { + case .accout: + SettingsAccountView(state: self.state) + case .network: + SettingsNetworkView(state: self.state) + case .device: + SettingsDeviceView() + case .system: + SettingsSystemView() + case .about: + SettingsAboutView() + } } + .id(selectedMenu) // 关键:确保切换时触发转场 + .transition(.asymmetric( + insertion: .move(edge: .bottom).combined(with: .opacity), + removal: .opacity + )) + + Spacer() } - Spacer() + .padding(32) // 加大留白,显得更高级 + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } - .padding() - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } } +} + +// MARK: - 子组件:侧边栏按钮样式 +struct SidebarItem: View { + let icon: String + let title: String + let isSelected: Bool + @State private var isHovering = false + var body: some View { + HStack(spacing: 12) { + Image(systemName: icon) + .font(.system(size: 14, weight: .medium)) + .frame(width: 20) + + Text(title) + .font(.system(size: 14, weight: .medium)) + + Spacer() + } + .padding(.horizontal, 12) + .padding(.vertical, 8) + .foregroundColor(isSelected ? .white : .primary.opacity(0.8)) + .background { + if isSelected { + RoundedRectangle(cornerRadius: 8, style: .continuous) + .fill(Color.blue.gradient) // 保持一致的蓝色渐变 + } else if isHovering { + RoundedRectangle(cornerRadius: 8, style: .continuous) + .fill(Color.primary.opacity(0.05)) + } + } + .onHover { + isHovering = $0 + } + .contentShape(Rectangle()) + } } #Preview { diff --git a/punchnet/Views/UserContext.swift b/punchnet/Views/UserContext.swift index 358314c..910329d 100644 --- a/punchnet/Views/UserContext.swift +++ b/punchnet/Views/UserContext.swift @@ -143,4 +143,10 @@ class UserContext { return nil } + // 退出登陆 + func logout() async throws { + try await VPNManager.shared.disableVpn() + self.isLogined = false + } + } diff --git a/punchnet/punchnetApp.swift b/punchnet/punchnetApp.swift index c6527dc..fcd50e9 100644 --- a/punchnet/punchnetApp.swift +++ b/punchnet/punchnetApp.swift @@ -41,7 +41,7 @@ struct punchnetApp: App { } var body: some Scene { - WindowGroup(id: "mainWindow") { + WindowGroup(id: "main") { // RootView() SettingsView() .navigationTitle("")