From 46185797fea97e30476394aeb02280b4e2183e86 Mon Sep 17 00:00:00 2001 From: Qiu Yuzhou Date: Sun, 4 Oct 2026 15:41:10 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E8=BE=B9=E6=A0=8F=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E6=94=B9=E3=80=8C=E5=AF=BC=E5=85=A5=E3=80=8D=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=AE=A2=E9=98=85=E7=A7=BB=E5=9B=9E=E8=AE=A2?= =?UTF-8?q?=E9=98=85=E9=A1=B5=E5=B7=A5=E5=85=B7=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 边栏「添加」菜单改名「导入」,图标换 square.and.arrow.down,与 分享按钮 square.and.arrow.up 成对;菜单收敛为三项导入动作 - 「添加订阅」移回订阅分区自挂工具栏,表单由订阅页自持;窗口壳删 .addSubscription workspace sheet 与 destination 映射 - 空态文案同步:订阅页指向本页按钮,服务器页改称「导入」菜单并删 去从未存在的「创建分组」表述 --- ShadowsocksX-NG2/App/MainWindowView.swift | 28 ++++++-------------- ShadowsocksX-NG2/App/ServersView.swift | 2 +- ShadowsocksX-NG2/App/SubscriptionsView.swift | 19 +++++++++---- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/ShadowsocksX-NG2/App/MainWindowView.swift b/ShadowsocksX-NG2/App/MainWindowView.swift index 683db3fe..5f3575c1 100644 --- a/ShadowsocksX-NG2/App/MainWindowView.swift +++ b/ShadowsocksX-NG2/App/MainWindowView.swift @@ -1,21 +1,13 @@ import SwiftUI +/// 服务器导入表单清单:由边栏「导入」菜单打开,全部属服务器域,窗口壳统一 +/// 持有呈现;添加订阅表单由订阅分区自持(SubscriptionsView)。 private enum WorkspaceSheet: String, Identifiable { case importURL case qrImport case legacyImport - case addSubscription var id: String { rawValue } - - var destination: WorkspaceDestination { - switch self { - case .importURL, .qrImport, .legacyImport: - .servers - case .addSubscription: - .subscriptions - } - } } /// 主窗口外壳(地图 #52,票 #53):NavigationSplitView 侧栏承载五项导航与 @@ -53,11 +45,11 @@ struct MainWindowView: View { /// selection(选择再同步,且可能携带缓存的旧值),回写必须落在 SwiftUI 自管 /// 的 @State 上,再经 onChange 单向驱动 route;直接 publish 到 route 会触发 /// view-update 期间发布警告,且缓存的旧值会把外部导航弹回。route 的外部 - /// 导航(添加菜单、规则编辑器返回等)经 sidebar 上的 onChange 回填选中项。 + /// 导航(导入菜单、规则编辑器返回等)经 sidebar 上的 onChange 回填选中项。 /// 初值须与 WorkspaceRoute 的初始 destination 对齐,否则首帧侧栏无高亮行。 @State private var sidebarSelection: WorkspaceDestination? = WorkspaceRoute.initialDestination @State private var selection: NodeID? - /// 全局添加菜单打开的表单由窗口壳持有。 + /// 全局导入菜单打开的表单由窗口壳持有。 @State private var presentedWorkspaceSheet: WorkspaceSheet? @StateObject private var shellActionErrors = ErrorAlertPresenter() @@ -88,14 +80,12 @@ struct MainWindowView: View { QRImportSheet(workflow: workflow, errors: shellActionErrors, selection: $selection) case .legacyImport: LegacyImportSheet(workflow: workflow) - case .addSubscription: - AddSubscriptionSheet(workflow: workflow, errors: shellActionErrors) } } .presentingErrors(shellActionErrors) } - // MARK: - 全局添加菜单 + // MARK: - 全局导入菜单 private var addMenu: some View { Menu { @@ -104,13 +94,11 @@ struct MainWindowView: View { if workflow.legacyImportState.snapshotFound { Button(legacyImportActionTitle) { presentWorkspaceSheet(.legacyImport) } } - Divider() - Button("添加订阅…") { presentWorkspaceSheet(.addSubscription) } } label: { - Label("添加", systemImage: "plus") + Label("导入", systemImage: "square.and.arrow.down") } .labelStyle(.iconOnly) - .help("添加") + .help("导入") } private var legacyImportActionTitle: String { @@ -118,7 +106,7 @@ struct MainWindowView: View { } private func presentWorkspaceSheet(_ sheet: WorkspaceSheet) { - route.navigate(to: sheet.destination) + route.navigate(to: .servers) presentedWorkspaceSheet = sheet } diff --git a/ShadowsocksX-NG2/App/ServersView.swift b/ShadowsocksX-NG2/App/ServersView.swift index 5079375f..626c9cda 100644 --- a/ShadowsocksX-NG2/App/ServersView.swift +++ b/ShadowsocksX-NG2/App/ServersView.swift @@ -108,7 +108,7 @@ struct ServersView: View { if workflow.tree.isEmpty { ContentUnavailableView( "暂无服务器", systemImage: "server.rack", - description: Text("用工具栏的「新建服务器」按钮手动录入,或用「添加」菜单导入 ss:// 链接、创建分组") + description: Text("用工具栏的「新建服务器」按钮手动录入,或用「导入」菜单导入 ss:// 链接") ) .allowsHitTesting(false) } diff --git a/ShadowsocksX-NG2/App/SubscriptionsView.swift b/ShadowsocksX-NG2/App/SubscriptionsView.swift index f526c40c..13f86837 100644 --- a/ShadowsocksX-NG2/App/SubscriptionsView.swift +++ b/ShadowsocksX-NG2/App/SubscriptionsView.swift @@ -2,10 +2,9 @@ import SwiftUI /// 订阅分区(spec #21 D11,issue #35/#41,地图 #52 票 #56):每订阅一张卡片 /// (图标 + 名称 + 「HTTPS 订阅地址已隐藏」+ 状态徽标 + 更新/重试 + 「…」菜单 + -/// 三列元数据 + 脱敏说明行),失败卡警告色描边。添加订阅通过窗口壳侧栏的 -/// 「添加」菜单进入;「更新全部」由本页自挂工具栏(呈现于窗口工具栏右端, -/// 与服务器分区同法)。卡片数据来自订阅 projection(非敏感);结构化刷新 -/// 状态在呈现层本地化(story 42)。 +/// 三列元数据 + 脱敏说明行),失败卡警告色描边。「添加订阅」与「更新全部」 +/// 均由本页自挂工具栏(呈现于窗口工具栏右端,与服务器分区同法)。卡片数据 +/// 来自订阅 projection(非敏感);结构化刷新状态在呈现层本地化(story 42)。 struct SubscriptionsView: View { @ObservedObject var workflow: CatalogWorkflow /// 共享错误弹窗呈现(UI 持有;typed error → 本地化文案的呈现边缘)。 @@ -15,6 +14,8 @@ struct SubscriptionsView: View { @State private var editTarget: SubscriptionSummary? @State private var deleteTarget: SubscriptionSummary? + /// 添加订阅表单:本页自持(2026-10-04 自窗口壳「添加」菜单移回本页工具栏)。 + @State private var isPresentingAddSubscription = false var body: some View { ScrollView { @@ -40,7 +41,7 @@ struct SubscriptionsView: View { if workflow.subscriptions.isEmpty { ContentUnavailableView( "暂无订阅", systemImage: "arrow.triangle.2.circlepath", - description: Text("用窗口左上方的「添加」菜单添加 HTTPS 订阅地址(SIP-008 JSON)") + description: Text("用工具栏的「添加订阅」按钮添加 HTTPS 订阅地址(SIP-008 JSON)") ) .allowsHitTesting(false) } @@ -48,6 +49,9 @@ struct SubscriptionsView: View { .sheet(item: $editTarget) { summary in EditSubscriptionURLSheet(workflow: workflow, errors: errors, summary: summary) } + .sheet(isPresented: $isPresentingAddSubscription) { + AddSubscriptionSheet(workflow: workflow, errors: errors) + } .confirmationDialog( "删除订阅「\(deleteTarget?.name ?? "")」及其整棵子树?", isPresented: Binding( @@ -64,6 +68,11 @@ struct SubscriptionsView: View { } .presentingErrors(errors) .toolbar { + ToolbarItem(placement: .primaryAction) { + Button("添加订阅", systemImage: "plus") { + isPresentingAddSubscription = true + } + } ToolbarItem(placement: .primaryAction) { HStack(spacing: 8) { if !workflow.refreshingSubscriptionIDs.isEmpty {