132 lines
4.4 KiB
Swift
132 lines
4.4 KiB
Swift
import SwiftUI
|
|
|
|
/// 弹出菜单主容器
|
|
struct NetworkMenuPopup: View {
|
|
@Binding var isPresented: Bool
|
|
@State private var isNetworkEnabled = true
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
// 1. 顶部用户信息区 (不响应悬停)
|
|
HStack(spacing: 12) {
|
|
Image(systemName: "person.fill")
|
|
.font(.system(size: 14))
|
|
.frame(width: 34, height: 34)
|
|
.background(Color.gray.opacity(0.1))
|
|
.clipShape(Circle())
|
|
|
|
Text("test3")
|
|
.font(.system(size: 15, weight: .medium))
|
|
}
|
|
.padding(.horizontal, 16)
|
|
.padding(.vertical, 16)
|
|
|
|
Divider().opacity(0.3).padding(.horizontal, 16)
|
|
|
|
// 2. 菜单功能列表
|
|
VStack(spacing: 4) {
|
|
NetworkMenuRow(title: "管理平台")
|
|
.onTapGesture {
|
|
print("点击管理平台")
|
|
}
|
|
|
|
NetworkMenuRow(title: "我的网络", subtitle: "test的网络") {
|
|
Toggle("", isOn: $isNetworkEnabled)
|
|
.toggleStyle(.switch)
|
|
.scaleEffect(0.65)
|
|
.labelsHidden()
|
|
.tint(Color(red: 0.15, green: 0.2, blue: 0.3))
|
|
}
|
|
|
|
NetworkMenuRow(title: "出口节点", subtitle: "未选择")
|
|
|
|
NetworkMenuRow(title: "退出登录", showArrow: false)
|
|
.onTapGesture {
|
|
withAnimation(.easeOut(duration: 0.2)) {
|
|
isPresented = false
|
|
}
|
|
}
|
|
}
|
|
.padding(6) // 为悬停的高亮背景留出呼吸感
|
|
}
|
|
.frame(width: 240)
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 14)
|
|
.fill(Color(NSColor.windowBackgroundColor))
|
|
)
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 14)
|
|
.stroke(Color.gray.opacity(0.1), lineWidth: 1)
|
|
)
|
|
.shadow(color: .black.opacity(0.12), radius: 12, x: 0, y: 6)
|
|
}
|
|
}
|
|
|
|
/// 菜单行组件 (支持 Hover 效果)
|
|
struct NetworkMenuRow<RightContent: View>: View {
|
|
let title: String
|
|
var subtitle: String? = nil
|
|
var showArrow: Bool = true
|
|
var rightContent: RightContent?
|
|
|
|
@State private var isHovering = false // 内部维护悬停状态
|
|
|
|
init(title: String, subtitle: String? = nil, showArrow: Bool = true) where RightContent == EmptyView {
|
|
self.init(title: title, subtitle: subtitle, showArrow: showArrow) {
|
|
EmptyView()
|
|
}
|
|
}
|
|
|
|
init(title: String, subtitle: String? = nil, showArrow: Bool = true, @ViewBuilder rightContent: () -> RightContent? = { nil }) {
|
|
self.title = title
|
|
self.subtitle = subtitle
|
|
self.showArrow = showArrow
|
|
self.rightContent = rightContent()
|
|
}
|
|
|
|
var body: some View {
|
|
HStack(spacing: 0) {
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text(title)
|
|
.font(.system(size: 13.5))
|
|
.foregroundColor(.primary)
|
|
if let sub = subtitle {
|
|
Text(sub)
|
|
.font(.system(size: 11.5))
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
|
|
Spacer()
|
|
|
|
// 右侧内容
|
|
if let content = rightContent {
|
|
content
|
|
}
|
|
|
|
// 右侧箭头
|
|
if showArrow {
|
|
Image(systemName: "chevron.right")
|
|
.font(.system(size: 9, weight: .bold))
|
|
.foregroundColor(.secondary.opacity(0.4))
|
|
.padding(.leading, 6)
|
|
}
|
|
}
|
|
.padding(.horizontal, 12)
|
|
.padding(.vertical, 10)
|
|
// --- 悬停效果实现 ---
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 8)
|
|
.fill(isHovering ? Color.gray.opacity(0.12) : Color.clear)
|
|
)
|
|
.onHover { hovering in
|
|
withAnimation(.easeInOut(duration: 0.15)) {
|
|
isHovering = hovering
|
|
}
|
|
}
|
|
// ------------------
|
|
.contentShape(Rectangle())
|
|
}
|
|
|
|
}
|