From f144194734fa9d99d9bd581f8be5325c2809fcea Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Thu, 19 Mar 2026 18:43:42 +0800 Subject: [PATCH] fix view --- punchnet/Views/Login/LoginView.swift | 15 ++- punchnet/Views/Network/NetworkModel.swift | 1 + punchnet/Views/Network/NetworkView.swift | 127 +++++++++++++++++----- 3 files changed, 111 insertions(+), 32 deletions(-) diff --git a/punchnet/Views/Login/LoginView.swift b/punchnet/Views/Login/LoginView.swift index e16906c..6a5ea6b 100644 --- a/punchnet/Views/Login/LoginView.swift +++ b/punchnet/Views/Login/LoginView.swift @@ -152,13 +152,16 @@ struct LoginAccountView: View { struct LoginTokenView: View { @Environment(UserContext.self) var userContext: UserContext @State private var token = "" + @State private var isLoading = false var body: some View { VStack(spacing: 20) { CustomTextField(title: "请输入认证密钥 (Token)", text: $token, icon: "key.fill") .frame(width: 280) - Button(action: { /* Token login logic */ }) { + Button(action: { + self.login() + }) { Text("验证并连接") .fontWeight(.medium) .frame(maxWidth: .infinity) @@ -166,7 +169,7 @@ struct LoginTokenView: View { .buttonStyle(.borderedProminent) .controlSize(.large) .frame(width: 280) - .disabled(token.isEmpty) + .disabled(token.isEmpty || isLoading) } .onAppear { if let cacheToken = self.userContext.loadCacheToken() { @@ -175,6 +178,14 @@ struct LoginTokenView: View { } } + private func login() { + isLoading = true + Task { + try? await userContext.loginWithToken(token: token) + isLoading = false + } + } + } // MARK: - 辅助 UI 组件 diff --git a/punchnet/Views/Network/NetworkModel.swift b/punchnet/Views/Network/NetworkModel.swift index 0efca45..8afc547 100644 --- a/punchnet/Views/Network/NetworkModel.swift +++ b/punchnet/Views/Network/NetworkModel.swift @@ -10,6 +10,7 @@ import Observation // 资源列表 struct Resource: Codable { + var uuid = UUID().uuidString var id: Int var name: String var url: String diff --git a/punchnet/Views/Network/NetworkView.swift b/punchnet/Views/Network/NetworkView.swift index 9cd4cab..d036862 100644 --- a/punchnet/Views/Network/NetworkView.swift +++ b/punchnet/Views/Network/NetworkView.swift @@ -4,7 +4,7 @@ import SwiftUI import Observation -// MARK: - 基础模型协议 (确保代码可编译,请根据实际 Model 调整) +// MARK: - 基础模型协议 enum ConnectState { case waitAuth, connected, disconnected } @@ -53,7 +53,9 @@ struct NetworkView: View { syncState(vpnManager.vpnStatus) } .onChange(of: vpnManager.vpnStatus) { _, newStatus in - withAnimation(.snappy) { syncState(newStatus) } + withAnimation(.snappy) { + syncState(newStatus) + } } } } @@ -74,7 +76,9 @@ extension NetworkView { .font(.system(size: 11, design: .monospaced)) .foregroundColor(.secondary) } else { - Text("PunchNet 服务未就绪").font(.caption).foregroundColor(.secondary) + Text("PunchNet 服务未就绪") + .font(.caption) + .foregroundColor(.secondary) } } @@ -82,13 +86,17 @@ extension NetworkView { if connectState == .connected { Picker("", selection: $showMode) { - ForEach(ShowMode.allCases, id: \.self) { Text($0.rawValue).tag($0) } + ForEach(ShowMode.allCases, id: \.self) { + Text($0.rawValue).tag($0) + } } .pickerStyle(.segmented) .frame(width: 160) } - Button { openWindow(id: "settings") } label: { + Button { + openWindow(id: "settings") + } label: { Image(systemName: "slider.horizontal.3") .font(.system(size: 14)) .foregroundColor(.secondary) @@ -119,14 +127,19 @@ extension NetworkView { if showMode == .resource { // 资源视图:网格布局 ScrollView { - LazyVGrid(columns: [GridItem(.adaptive(minimum: 200), spacing: 16)], spacing: 16) { - ForEach(networkModel.networkContext.resourceList, id: \.id) { res in + LazyVGrid(columns: [ + GridItem(.flexible(), spacing: 8), + GridItem(.flexible(), spacing: 8), + GridItem(.flexible(), spacing: 8) + ], spacing: 10) { + ForEach(networkModel.networkContext.resourceList, id: \.uuid) { res in ResourceItemCard(resource: res) } } .padding(20) } .transition(.opacity) + .frame(maxWidth: .infinity) } else { // 设备视图:双栏布局 NetworkDeviceGroupView(networkModel: networkModel) @@ -142,13 +155,17 @@ extension NetworkView { .foregroundStyle(.tertiary) .symbolEffect(.pulse, options: .repeating) - Text("尚未接入网络").font(.headline) + Text("尚未接入网络") + .font(.headline) Button(action: { startConnection() }) { if isConnecting { - ProgressView().controlSize(.small).frame(width: 80) + ProgressView() + .controlSize(.small) + .frame(width: 80) } else { - Text("建立安全连接").frame(width: 80) + Text("建立安全连接") + .frame(width: 80) } } .buttonStyle(.borderedProminent) @@ -169,8 +186,12 @@ extension NetworkView { isConnecting = true Task { do { - guard let session = userContext.networkSession else { return } + guard let session = userContext.networkSession else { + return + } + try await networkModel.connect(networkSession: session) + let context = networkModel.networkContext if let options = SystemConfig.getOptions( networkId: UInt32(session.networkId), @@ -184,8 +205,13 @@ extension NetworkView { ) { try await vpnManager.enableVpn(options: options) } - } catch { print("Connection error: \(error)") } - await MainActor.run { isConnecting = false } + } catch { + print("Connection error: \(error)") + } + + await MainActor.run { + isConnecting = false + } } } } @@ -228,8 +254,12 @@ struct NetworkNodeHeadView: View { .frame(width: 8, height: 8) VStack(alignment: .leading, spacing: 2) { - Text(node.name).font(.system(size: 13, weight: .medium)) - Text(node.ip).font(.system(size: 11, design: .monospaced)).foregroundColor(.secondary) + Text(node.name) + .font(.system(size: 13, weight: .medium)) + + Text(node.ip) + .font(.system(size: 11, design: .monospaced)) + .foregroundColor(.secondary) } } .padding(.vertical, 4) @@ -252,32 +282,47 @@ struct NetworkNodeDetailView: View { Section("提供的服务") { if isLoading { - ProgressView().controlSize(.small) + ProgressView() + .controlSize(.small) } else if resources.isEmpty { - Text("该节点暂未发布资源").foregroundColor(.secondary).font(.callout) + Text("该节点暂未发布资源") + .foregroundColor(.secondary) + .font(.callout) } else { ForEach(resources, id: \.id) { res in VStack(alignment: .leading) { - Text(res.name).font(.body) - Text(res.url).font(.caption).foregroundColor(.secondary) + Text(res.name) + .font(.body) + + Text(res.url) + .font(.caption) + .foregroundColor(.secondary) } } } } } - .task(id: node.id) { await loadNodeResources(id: node.id) } + .task(id: node.id) { + await loadNodeResources(id: node.id) + } } private func loadNodeResources(id: Int) async { - guard let session = userContext.networkSession else { return } + guard let session = userContext.networkSession else { + return + } + isLoading = true - defer { isLoading = false } + defer { + isLoading = false + } let params: [String: Any] = [ "client_id": SystemConfig.getClientId(), "access_token": session.accessToken, "id": id ] + if let detail = try? await SDLAPIClient.doPost(path: "/get_node_resources", params: params, as: NodeDetail.self) { self.resources = detail.resourceList } @@ -290,24 +335,46 @@ struct ResourceItemCard: View { var body: some View { VStack(alignment: .leading, spacing: 8) { - Image(systemName: "safari.fill").foregroundColor(.accentColor).font(.title3) - Text(resource.name).font(.headline).lineLimit(1) - Text(resource.url).font(.caption2).foregroundColor(.secondary).lineLimit(1) + Image(systemName: "safari.fill") + .foregroundColor(.accentColor) + .font(.title3) + + Text(resource.name) + .font(.headline) + .lineLimit(1) + .truncationMode(.tail) + + Text(resource.url) + .font(.caption2) + .foregroundColor(.secondary) + .lineLimit(1) + .truncationMode(.middle) } .padding() - .frame(maxWidth: .infinity, alignment: .leading) - .background(Color(NSColor.controlBackgroundColor).opacity(isHovered ? 0.8 : 0.4)) - .cornerRadius(10) - .onHover { isHovered = $0 } + .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) + .overlay( + RoundedRectangle(cornerRadius: 10) + .stroke(Color.gray, lineWidth: 1) + ) + .background( + RoundedRectangle(cornerRadius: 10) + .fill(Color(isHovered ? NSColor.selectedControlColor : NSColor.controlBackgroundColor)) + ) + .onHover { + isHovered = $0 + } } } struct NetworkWaitAuthView: View { @Bindable var networkModel: NetworkModel + var body: some View { VStack(spacing: 16) { ProgressView() - Text("等待认证确认中...").foregroundColor(.secondary) + + Text("等待认证确认中...") + .foregroundColor(.secondary) } } }