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