feat(ui): 边栏菜单改「导入」,添加订阅移回订阅页工具栏

- 边栏「添加」菜单改名「导入」,图标换 square.and.arrow.down,与
  分享按钮 square.and.arrow.up 成对;菜单收敛为三项导入动作
- 「添加订阅」移回订阅分区自挂工具栏,表单由订阅页自持;窗口壳删
  .addSubscription workspace sheet 与 destination 映射
- 空态文案同步:订阅页指向本页按钮,服务器页改称「导入」菜单并删
  去从未存在的「创建分组」表述
This commit is contained in:
Qiu Yuzhou
2026-10-04 15:41:10 +08:00
parent a00a93d5c0
commit 46185797fe
3 changed files with 23 additions and 26 deletions
+8 -20
View File
@@ -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
}
+1 -1
View File
@@ -108,7 +108,7 @@ struct ServersView: View {
if workflow.tree.isEmpty {
ContentUnavailableView(
"暂无服务器", systemImage: "server.rack",
description: Text("用工具栏的「新建服务器」按钮手动录入,或用「添加」菜单导入 ss:// 链接、创建分组")
description: Text("用工具栏的「新建服务器」按钮手动录入,或用「导入」菜单导入 ss:// 链接")
)
.allowsHitTesting(false)
}
+14 -5
View File
@@ -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 {