// // NetworkView.swift // punchnet // // Created by 安礼成 on 2026/1/16. // import SwiftUI struct NetworkView: View { @Environment(UserContext.self) var userContext: UserContext @State private var networkModel = NetworkModel() @State private var showMode: ShowMode = .resource // 展示状态 enum ShowMode { case resource case device } var body: some View { VStack { HStack { VStack { HStack(alignment: .center) { Text(userContext.networkSession?.networkName ?? "未知") Text(">") Spacer() } HStack { Toggle("", isOn: $networkModel.isOn) .toggleStyle(SwitchToggleStyle(tint: .green)) .disabled(true) Text("已连接") Spacer() } } .frame(width: 320) // 显示设备和资源选项 HStack { Button { self.showMode = .resource } label: { Text("资源") } Button { self.showMode = .device } label: { Text("设备") } } Spacer() } Group { switch self.networkModel.connectState { case .waitAuth: NetworkWaitAuthView(networkModel: self.networkModel) case .connected: Group { switch self.showMode { case .resource: NetworkResourceGroupView(networkModel: self.networkModel) case .device: NetworkDeviceGroupView(networkModel: self.networkModel) } } case .disconnected: NetworkDisconnctedView(networkModel: self.networkModel) } } Spacer() } .padding(.top, 10) .padding(.leading, 10) .toolbar { ToolbarItem(placement: .primaryAction) { Button { print("clicked") } label: { Image(systemName: "gearshape") } } } } } // 网络处于未连接状态 struct NetworkDisconnctedView: View { @Bindable var networkModel: NetworkModel @Environment(AppContext.self) var appContext: AppContext @Environment(UserContext.self) var userContext: UserContext @State private var showAlert = false @State private var errorMessage = "" var body: some View { ZStack { Color.clear VStack { Button { Task { @MainActor in do { try await self.connect() try await self.startVpn() } catch let err { self.showAlert = true self.errorMessage = err.localizedDescription } } } label: { Text("连接") .font(.system(size: 14, weight: .regular)) .padding([.top, .bottom], 8) .padding([.leading, .trailing], 30) .foregroundColor(.white) } .background(Color(red: 74/255, green: 207/255, blue: 154/255)) .cornerRadius(5) .frame(width: 120, height: 35) Button { Task { try await VPNManager.shared.disableVpn() } } label: { Text("关闭") .font(.system(size: 14, weight: .regular)) .padding([.top, .bottom], 8) .padding([.leading, .trailing], 30) .foregroundColor(.white) } .background(Color(red: 74/255, green: 207/255, blue: 154/255)) .cornerRadius(5) .frame(width: 120, height: 35) } } .alert(isPresented: $showAlert) { Alert(title: Text("提示"), message: Text(self.errorMessage)) } } private func connect() async throws { guard let networkSession = userContext.networkSession else { return } try await networkModel.connect(networkSession: networkSession) } // 执行登陆操作 private func startVpn() async throws { guard let networkSession = userContext.networkSession else { return } let networkContext = networkModel.networkContext let options = SystemConfig.getOptions(networkId: UInt32(networkSession.networkId), networkDomain: networkSession.networkDomain, ip: networkContext.ip, maskLen: networkContext.maskLen, accessToken: networkSession.accessToken, identityId: networkContext.identityId, hostname: networkContext.hostname, noticePort: self.appContext.noticePort) // token存在则优先使用token try await VPNManager.shared.enableVpn(options: options!) } } // 网络处于连接状态 // 显示资源信息 struct NetworkResourceGroupView: View { @Bindable var networkModel: NetworkModel var body: some View { LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 3), spacing: 8) { ForEach(self.networkModel.networkContext.resourceList, id: \.id) { resource in NetworkResourceView(resource: resource) } } } } struct NetworkResourceView: View { var resource: Resource var body: some View { VStack { HStack { Text(resource.connectionStatus) Text(resource.name) .font(.system(size: 14, weight: .regular)) } Text(resource.url) .font(.system(size: 14, weight: .regular)) .padding(.leading, 30) } } } // 显示设备信息 struct NetworkDeviceGroupView: View { @Bindable var networkModel: NetworkModel @State private var selectedId: Int? var body: some View { NavigationSplitView { List(self.networkModel.networkContext.nodeList, id: \.id, selection: $selectedId) { node in NetworkNodeHeadView(node: node) } .listStyle(.sidebar) .onChange(of: selectedId) { self.networkModel.changeSelectedNode(nodeId: selectedId) } .onAppear { if selectedId == nil { selectedId = self.networkModel.networkContext.nodeList.first?.id } } } detail: { NetworkNodeDetailView(node: networkModel.selectedNode) } } } struct NetworkNodeHeadView: View { var node: Node var body: some View { VStack { HStack { Text(node.connectionStatus) Text(node.name) .font(.system(size: 14, weight: .regular)) } Text(node.ip) .font(.system(size: 14, weight: .regular)) .padding(.leading, 30) } } } struct NetworkNodeDetailView: View { @Environment(UserContext.self) var userContext: UserContext var node: Node? @State private var resources: [Resource] = [] var body: some View { Group { if let node { List { Section { HStack { Text("连接状态") Text("\(node.connectionStatus)") Spacer() } HStack { Text("虚拟IPv4") Text("\(node.ip)") Spacer() } HStack { Text("操作系统") Text(node.system ?? "未知") Spacer() } } Section("服务列表") { ForEach(resources, id: \.id) { resource in HStack { Text("\(resource.name)") Text("\(resource.url)") } } } } .task(id: node.id) { await self.loadNodeResources(id: node.id) } } else { EmptyView() } } } private func loadNodeResources(id: Int) async { guard let networkSession = userContext.networkSession else { return } let params: [String: Any] = [ "client_id": SystemConfig.getClientId(), "access_token": networkSession.accessToken, "id": id ] if let nodeDetail = try? await SDLAPIClient.doPost(path: "/get_node_resources", params: params, as: NodeDetail.self) { self.resources = nodeDetail.resourceList } } } struct NetworkWaitAuthView: View { @Bindable var networkModel: NetworkModel var body: some View { Color.clear .overlay { Text("等待确认中") } } } #Preview { NetworkView() }