diff --git a/punchnet/Views/Settings/SettingsAccountView.swift b/punchnet/Views/Settings/SettingsAccountView.swift index 491407b..3d371b2 100644 --- a/punchnet/Views/Settings/SettingsAccountView.swift +++ b/punchnet/Views/Settings/SettingsAccountView.swift @@ -7,9 +7,9 @@ import SwiftUI struct SettingsAccountView: View { - @State var state: SettingsState = SettingsState() @Environment(UserContext.self) var userContext: UserContext @Environment(\.openWindow) var openWindow + @Environment(\.openURL) var openURL var body: some View { ScrollView(.vertical, showsIndicators: false) { @@ -43,63 +43,78 @@ struct SettingsAccountView: View { // MARK: - 网络部分 sectionHeader(title: "网络配置", icon: "network") - 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) { - self.state.selectedNetwork = network - } + if let networkSession = userContext.networkSession { + VStack(spacing: 16) { + HStack { + VStack(alignment: .leading, spacing: 4) { + Text("网络") + .font(.subheadline) + .foregroundColor(.secondary) + + Text(networkSession.networkName) + .font(.headline) } - } label: { - Text("切换网络") - .font(.subheadline) - .padding(.horizontal, 12) - .padding(.vertical, 6) - .background(Capsule().fill(Color.blue.opacity(0.1))) - } - .buttonStyle(.plain) - } - - Divider() - - HStack { - Button { - } label: { - Label("进入管理平台", systemImage: "arrow.up.right.square") + Spacer() +// +// Menu { +// ForEach(state.networks, id: \.id) { network in +// Button(network.name) { +// self.state.selectedNetwork = network +// } +// } +// } label: { +// Text("切换网络") +// .font(.subheadline) +// .padding(.horizontal, 12) +// .padding(.vertical, 6) +// .background(Capsule().fill(Color.blue.opacity(0.1))) +// } +// .buttonStyle(.plain) } - .buttonStyle(.plain) - .foregroundColor(.blue) - Spacer() + Divider() - Button("查看详情") { + HStack { + Button { + self.openNetworkUrl(url: networkSession.networkUrl) + } label: { + Label("进入管理平台", systemImage: "arrow.up.right.square") + } + .buttonStyle(.plain) + .foregroundColor(.blue) + Spacer() + + Button("查看详情") { + self.openNetworkUrl(url: networkSession.networkUrl) + } + .buttonStyle(.bordered) + .controlSize(.small) } - .buttonStyle(.bordered) - .controlSize(.small) } + .padding(16) + .background(Color.primary.opacity(0.03)) + .cornerRadius(12) } - .padding(16) - .background(Color.primary.opacity(0.03)) - .cornerRadius(12) + } .frame(maxWidth: 600) // 限制宽度防止在大屏幕上显得太散 } } + private func openNetworkUrl(url: String) { + if let url = URL(string: url) { + openURL(url) { accepted in + if accepted { + print("浏览器已成功打开") + } else { + print("打开失败(可能是 URL 格式错误)") + } + } + } + } + // 打开窗口 private func openMainWindow(id: String) { let window = NSApp.windows.first { win in diff --git a/punchnet/Views/Settings/SettingsNetworkView.swift b/punchnet/Views/Settings/SettingsNetworkView.swift index 5de6a96..8967e8f 100644 --- a/punchnet/Views/Settings/SettingsNetworkView.swift +++ b/punchnet/Views/Settings/SettingsNetworkView.swift @@ -1,83 +1,174 @@ // -// SettingsNetworkView.swift +// SettingsNetworkView 2.swift // punchnet // -// Created by 安礼成 on 2026/1/19. +// Created by 安礼成 on 2026/3/19. // - import SwiftUI struct SettingsNetworkView: View { - @State var state: SettingsState = SettingsState() + @Environment(UserContext.self) var userContext: UserContext + @State private var selectedExitNode: UserContext.NetworkSession.ExitNode? var body: some View { - VStack(alignment: .leading) { - Text("网络") - - HStack(alignment: .top) { - Text("默认网络") + ScrollView(.vertical, showsIndicators: false) { + VStack(alignment: .leading, spacing: 24) { - VStack(alignment: .leading) { - Menu { - ForEach(state.networks, id: \.id) { network in - Button(network.name) { - self.state.selectedNetwork = network + // MARK: - 网络连接配置 + networkSectionHeader(title: "连接设置", icon: "wifi.router.fill") + + if let networkSession = userContext.networkSession { + VStack(spacing: 0) { +// // 默认网络项 +// NetworkRow(title: "默认网络", value: state.selectedNetwork.name) { +// Menu { +// ForEach(state.networks, id: \.id) { network in +// Button(network.name) { +// self.state.selectedNetwork = network +// } +// } +// } label: { +// actionLabel(text: "切换") +// } +// .buttonStyle(.plain) +// } +// +// Divider().padding(.leading, 16) + + // 出口节点项 + NetworkRow(title: "出口节点", value: selectedExitNode?.nodeName ?? "") { + Menu { + ForEach(networkSession.exitNodes, id: \.uuid) { node in + Button { + self.selectedExitNode = node + } label: { + Text(node.nodeName) + } + } + } label: { + actionLabel(text: "更改") } + .buttonStyle(.plain) } - } label: { - Text(state.selectedNetwork.name) - .padding() - .background(Color.gray.opacity(0.2)) - .cornerRadius(5) + } + .background(Color.primary.opacity(0.03)) + .cornerRadius(12) + .overlay(RoundedRectangle(cornerRadius: 12).stroke(Color.primary.opacity(0.05), lineWidth: 1)) + } + + // MARK: - 授权与安全 + networkSectionHeader(title: "授权状态", icon: "checkmark.shield.fill") + + VStack(spacing: 0) { + StatusRow(title: "当前状态", value: "有效", valueColor: .green) + + Divider() + .padding(.leading, 16) + + StatusRow(title: "有效期", value: "临时 (至断开连接)", valueColor: .secondary) + } + .background(Color.primary.opacity(0.03)) + .cornerRadius(12) + + // MARK: - 外部操作 + HStack(spacing: 16) { + Button { + + } label: { + Label("进入管理平台", systemImage: "arrow.up.right.app") + .font(.subheadline.bold()) + } + .buttonStyle(.borderedProminent) + .controlSize(.large) Button { } label: { - Text("进入管理平台") + Text("查看诊断详情") } + .buttonStyle(.bordered) + .controlSize(.large) } - - Button { - - } label: { - Text("详情") - } - + .padding(.top, 8) } - - - HStack { - Text("出口节点") - - Menu { - ForEach(state.exitNodes, id: \.id) { node in - Button(node.name) { - self.state.selectedExitNode = node - } - } - - } label: { - Text(state.selectedExitNode.name) - } - } - - HStack { - Text("授权状态") - Text("有效") - } - - HStack { - Text("授权有效期") - Text("临时(至断开连接)") - } - + .padding(32) + .frame(maxWidth: 600, alignment: .leading) } - } + // MARK: - 辅助组件 + + private func networkSectionHeader(title: String, icon: String) -> some View { + HStack { + Image(systemName: icon) + .foregroundColor(.blue) + .font(.system(size: 14, weight: .semibold)) + + Text(title) + .font(.system(size: 15, weight: .bold)) + .foregroundColor(.secondary) + } + .padding(.leading, 4) + } + + private func actionLabel(text: String) -> some View { + Text(text) + .font(.subheadline) + .padding(.horizontal, 10) + .padding(.vertical, 4) + .background(Capsule().fill(Color.blue.opacity(0.1))) + .foregroundColor(.blue) + } } +// MARK: - 通用行组件 +struct NetworkRow: View { + let title: String + let value: String + let action: () -> Content + + init(title: String, value: String, @ViewBuilder action: @escaping () -> Content) { + self.title = title + self.value = value + self.action = action + } + + var body: some View { + HStack { + VStack(alignment: .leading, spacing: 4) { + Text(title).font(.caption).foregroundColor(.secondary) + Text(value).font(.system(size: 15, weight: .medium)) + } + Spacer() + action() + } + .padding(16) + } +} + +struct StatusRow: View { + let title: String + let value: String + let valueColor: Color + + var body: some View { + HStack { + Text(title) + .font(.system(size: 14)) + .foregroundColor(.primary.opacity(0.8)) + + Spacer() + Text(value) + .font(.system(size: 14, weight: .medium)) + .foregroundColor(valueColor) + } + .padding(16) + } +} + +// MARK: - 预览辅助 #Preview { SettingsNetworkView() + .environment(UserContext()) // 确保环境中存在 UserContext } diff --git a/punchnet/Views/Settings/SettingsState.swift b/punchnet/Views/Settings/SettingsState.swift deleted file mode 100644 index d53c3b7..0000000 --- a/punchnet/Views/Settings/SettingsState.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// SettingsState.swift -// punchnet -// -// Created by 安礼成 on 2026/1/16. -// - -import Foundation -import Observation - -@Observable -class SettingsState { - struct Network { - var id: Int - var name: String - } - - struct ExitNode { - var id: Int - var name: String - } - - var networks: [Network] - var selectedNetwork: Network - - var exitNodes: [ExitNode] - var selectedExitNode: ExitNode - - init() { - let networks: [Network] = [ - .init(id: 1, name: "测试网络12"), - .init(id: 2, name: "测试网络13"), - .init(id: 3, name: "测试网络14"), - .init(id: 4, name: "测试网络15"), - .init(id: 5, name: "xyz"), - ] - - self.selectedNetwork = networks[0] - self.networks = networks - - - let exitNodes: [ExitNode] = [ - .init(id: 1, name: "出口节点1"), - .init(id: 2, name: "出口节点12"), - .init(id: 3, name: "出口节点13"), - .init(id: 4, name: "出口节点14"), - .init(id: 5, name: "出口节点15"), - ] - self.selectedExitNode = exitNodes[0] - self.exitNodes = exitNodes - - } - -} diff --git a/punchnet/Views/Settings/SettingsView.swift b/punchnet/Views/Settings/SettingsView.swift index 9806d2a..f874c28 100644 --- a/punchnet/Views/Settings/SettingsView.swift +++ b/punchnet/Views/Settings/SettingsView.swift @@ -8,7 +8,6 @@ import SwiftUI struct SettingsView: View { @State private var columnVisibility: NavigationSplitViewVisibility = .all - @State private var state = SettingsState() @State private var selectedMenu: MenuItem = .accout enum MenuItem: String, CaseIterable { @@ -77,9 +76,9 @@ struct SettingsView: View { // 使用 ID 辅助 SwiftUI 识别视图切换,触发 transition switch self.selectedMenu { case .accout: - SettingsAccountView(state: self.state) + SettingsAccountView() case .network: - SettingsNetworkView(state: self.state) + SettingsNetworkView() case .device: SettingsDeviceView() case .system: diff --git a/punchnet/Views/UserContext.swift b/punchnet/Views/UserContext.swift index 910329d..6c9862e 100644 --- a/punchnet/Views/UserContext.swift +++ b/punchnet/Views/UserContext.swift @@ -22,6 +22,7 @@ class UserContext { // 登陆后的网络会话信息 struct NetworkSession: Codable { struct ExitNode: Codable { + let uuid = UUID().uuidString let nnid: Int let nodeName: String @@ -40,6 +41,10 @@ class UserContext { let networkDomain: String let exitNodes: [ExitNode] + var networkUrl: String { + return "https://www.test.cn/id=\(self.networkId)" + } + enum CodingKeys: String, CodingKey { case accessToken = "access_token" case username