This commit is contained in:
anlicheng 2026-03-19 18:43:42 +08:00
parent 177f8932fa
commit f144194734
3 changed files with 111 additions and 32 deletions

View File

@ -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

View File

@ -10,6 +10,7 @@ import Observation
//
struct Resource: Codable {
var uuid = UUID().uuidString
var id: Int
var name: String
var url: String

View File

@ -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)
}
}
}