Files
openless/Sources/OpenLessApp/Settings/Tabs/HelpTab.swift
T
baiqing d94be8080d refactor(settings): split 1697-line SettingsView.swift into single-responsibility files
按用户的"800 行红线、单一职责"约束拆分。零行为变化,只是物理文件结构。

- SettingsView.swift  87 (root: 枚举 + 导航模型 + 主分发)
- SettingsComponents.swift  218 (SettingsPage / GlassSection / GlassPanel /
  MetricTile / Notification 名 / polishModeHint / isFilled)
- Sidebar.swift  270 (FixedSidebar + 状态卡 + stats + WindowCanvasBackground)
- Tabs/HomeTab.swift  161
- Tabs/HistoryTab.swift  50
- Tabs/DictionaryTab.swift  349 (含 Chip + ChipFlow + EditorSheet)
- Tabs/StyleTab.swift  123 (含 StyleCard)
- Tabs/HelpTab.swift  100
- Tabs/SettingsHubTab.swift  184
- Tabs/LegacyTabs.swift  172 (Overview/Hotkey/Modes/Privacy 旧版孤儿,原样搬)

private 改成默认 internal 让跨文件可见,封装边界仍止于 OpenLessApp 模块内部。
项目最长文件从 1697 行 → 566 行 (DictationCoordinator),全部在 800 红线下。
2026-04-28 13:13:37 +08:00

101 lines
4.7 KiB
Swift

import SwiftUI
import OpenLessCore
import OpenLessPersistence
struct HelpTab: View {
var body: some View {
SettingsPage(
title: "帮助中心",
subtitle: "快速上手 OpenLess、查阅快捷键、检查授权状态、跳转到文档与反馈渠道。"
) {
GlassSection(title: "快速上手", symbol: "play.circle") {
helpStep(num: 1, title: "配置火山 ASR", body: "在「火山 ASR」页面填入 APP ID 和 Access Token;没有 ASR 凭据时只能走演示模式。")
DividerLine()
helpStep(num: 2, title: "(可选)配置润色", body: "「润色模式」页面填入 Ark API Key 后,识别结果会按所选模式润色;不填也能用,会直接插入原文。")
DividerLine()
helpStep(num: 3, title: "授权辅助功能 + 麦克风", body: "首次启动会请求权限。授权后必须完全退出 OpenLess 再重新打开,全局快捷键才会生效。")
DividerLine()
helpStep(num: 4, title: "开始说话", body: "默认按右 Option 开始/停止录音;说完后文字自动插入到当前光标位置。Esc 取消本次。")
}
GlassSection(title: "快捷键速查", symbol: "keyboard") {
helpKey("开始 / 停止录音", value: UserPreferences.shared.hotkeyTrigger.displayName)
DividerLine()
helpKey("取消本次录音", value: "Esc")
DividerLine()
helpKey("胶囊确认插入", value: "点击右侧 ✓")
}
GlassSection(title: "常见问题", symbol: "questionmark.bubble") {
helpFAQ(q: "全局快捷键没反应?", a: "确认「系统设置 → 隐私与安全 → 辅助功能」里 OpenLess 已勾选;首次授权之后必须完全退出再重启 App。")
DividerLine()
helpFAQ(q: "胶囊一直显示「演示」?", a: "缺少火山 ASR 凭据。到「火山 ASR」页面填入 APP ID + Access Token 即可。")
DividerLine()
helpFAQ(q: "插入失败 / 只复制到剪贴板?", a: "目标 App 不支持 AX 写入或粘贴模拟。OpenLess 会自动降级为复制到剪贴板,按 ⌘V 粘贴即可。")
}
GlassSection(title: "更多", symbol: "link") {
helpLink(title: "GitHub 仓库", url: "https://github.com/baiqing/openless")
DividerLine()
helpLink(title: "提交问题或建议", url: "https://github.com/baiqing/openless/issues")
}
}
}
private func helpStep(num: Int, title: String, body: String) -> some View {
HStack(alignment: .top, spacing: 12) {
Text("\(num)")
.font(.system(size: 13, weight: .bold, design: .rounded))
.foregroundStyle(.white)
.frame(width: 22, height: 22)
.background(Color.accentColor, in: Circle())
VStack(alignment: .leading, spacing: 4) {
Text(title).font(.system(size: 14, weight: .semibold))
Text(body).font(.callout).foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
Spacer(minLength: 0)
}
.padding(.vertical, 9)
}
private func helpKey(_ title: String, value: String) -> some View {
HStack {
Text(title)
Spacer()
Text(value)
.font(.system(size: 13, weight: .medium, design: .monospaced))
.padding(.horizontal, 9)
.padding(.vertical, 3)
.background(Color.primary.opacity(0.06), in: RoundedRectangle(cornerRadius: 6, style: .continuous))
}
.padding(.vertical, 9)
}
private func helpFAQ(q: String, a: String) -> some View {
VStack(alignment: .leading, spacing: 4) {
Text(q).font(.system(size: 14, weight: .semibold))
Text(a).font(.callout).foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
.padding(.vertical, 9)
}
private func helpLink(title: String, url: String) -> some View {
HStack {
Text(title)
Spacer()
if let parsed = URL(string: url) {
Link(destination: parsed) {
Label(url, systemImage: "arrow.up.right.square")
.font(.callout)
.foregroundStyle(.blue)
.lineLimit(1)
.truncationMode(.middle)
}
}
}
.padding(.vertical, 9)
}
}