punchnet-macos/punchnet/Features/Network/Views/NetworkConnectedView.swift
2026-04-17 15:56:56 +08:00

191 lines
6.5 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// NetworkConnectedView.swift
// punchnet
//
// Created by on 2026/4/17.
//
import SwiftUI
struct NetworkConnectedView: View {
var model: NetworkModel
var body: some View {
if self.model.showMode == .resource {
//
ScrollView {
LazyVGrid(columns: [
GridItem(.flexible(), spacing: 8),
GridItem(.flexible(), spacing: 8),
GridItem(.flexible(), spacing: 8)
], spacing: 10) {
ForEach(self.model.resourceList, id: \.uuid) { res in
ResourceItemCard(resource: res)
}
}
.padding(20)
}
.transition(.opacity)
.frame(maxWidth: .infinity)
} else {
//
NetworkDeviceGroupView(model: self.model)
.transition(.asymmetric(insertion: .move(edge: .trailing), removal: .opacity))
}
}
}
// MARK: - (NavigationSplitView)
struct NetworkDeviceGroupView: View {
var model: NetworkModel
//
private let sidebarWidth: CGFloat = 240
var body: some View {
let selectedIdBinding = Binding(
get: { self.model.selectedNodeId },
set: { newValue in
self.model.selectNode(id: newValue)
}
)
HStack(spacing: 0) {
// --- 1. (Sidebar) ---
VStack(alignment: .leading, spacing: 0) {
// macOS 绿
// WindowStyle .hiddenTitleBar Padding
Color.clear.frame(height: 28)
List(self.model.nodeList, id: \.id, selection: selectedIdBinding) { node in
NetworkNodeHeadView(node: node)
// HStack tag List selection
.tag(node.id)
.listRowSeparator(.hidden)
}
.listStyle(.inset) // 使 inset
.scrollContentBackground(.hidden) //
}
.frame(width: sidebarWidth)
Divider() // 线
// --- 2. (Detail) ---
ZStack {
if let selectedNode = self.model.selectedNode {
NetworkNodeDetailView(model: self.model, node: selectedNode)
.transition(.opacity.animation(.easeInOut(duration: 0.2)))
} else {
ContentUnavailableView(
"选择成员设备",
systemImage: "macbook.and.iphone",
description: Text("查看详细网络信息和服务")
)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(nsColor: .windowBackgroundColor)) // 使
}
.ignoresSafeArea() //
}
}
// MARK: -
struct NetworkNodeHeadView: View {
var node: NetworkContext.Node
var body: some View {
HStack(spacing: 10) {
Circle()
.fill(node.connectionStatus == "在线" ? Color.green : Color.secondary.opacity(0.4))
.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)
}
}
.padding(.vertical, 4)
}
}
struct NetworkNodeDetailView: View {
var model: NetworkModel
var node: NetworkContext.Node
var body: some View {
List {
Section("节点信息") {
LabeledContent("连接状态", value: node.connectionStatus)
LabeledContent("虚拟IPv4", value: node.ip)
LabeledContent("系统环境", value: node.system ?? "未知")
}
Section("提供的服务") {
if self.model.isLoadingResources(for: node.id) {
ProgressView()
.controlSize(.small)
} else if self.model.resources(for: node.id).isEmpty {
Text("该节点暂未发布资源")
.foregroundColor(.secondary)
.font(.callout)
} else {
ForEach(self.model.resources(for: node.id), id: \.id) { res in
VStack(alignment: .leading) {
Text(res.name)
.font(.body)
Text(res.url)
.font(.caption)
.foregroundColor(.secondary)
}
}
}
}
}
.task(id: self.node.id) {
await self.model.loadResourcesIfNeeded(for: self.node.id)
}
}
}
struct ResourceItemCard: View {
let resource: NetworkContext.Resource
@State private var isHovered = false
var body: some View {
VStack(alignment: .leading, spacing: 8) {
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(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
}
}
}