
鸿蒙 PC Markdown 编辑器标签栏对齐消除单标签留白而不破坏水平滚动用户截图中Untitled.md左边出现了一大段空白看起来像编辑器布局没有填满。问题并不在窗口最上方的系统标题栏也不在文件侧栏宽度而在应用内部标签滚动容器的内容对齐只有一个短标签时横向 Scroll 的子 Row 位于剩余轨道中部标签增多后又不容易察觉。PC 编辑器的首个标签应紧贴编辑工作区左边界空白只能出现在所有标签之后。修复已进入公开仓库 https://gitcode.com/VON-/codex_md_oh实现提交为358eb3f窄侧栏联动修复后的验证基线为0d8d38b。本文从布局树、默认对齐、滚动约束、固定标签尺寸、系统标题栏边界、焦点和设备验证解释为什么一行.align(Alignment.Start)能修复现象但完整交付仍需要多标签、窄窗口、侧栏调整和自动化回归。先把三种空白区域分清截图里至少有三种“看起来为空”的区域。窗口最上方、应用名称右侧是 HarmonyOS 原生标题栏的拖动空间用户在这里拖动窗口不能由应用标签填满左侧上下文面板为空时是文件树内容区承担打开文件夹的空状态应用标签栏中首标签左侧的空白没有任务价值才是需要修复的部分。如果误把系统标题栏当成 bug应用可能尝试覆盖系统拖动区域破坏窗口移动、最大化和系统按钮如果误把标签空白归因于固定侧栏拖动侧栏只能改变空白长度不能让标签回到起点。定位布局问题的第一步是用组件边界和层级确认哪一块由谁拥有。最终测试报告明确保留原生标题栏修复下方应用标签轨道。设备截图中Untitled.md紧贴 Web 工作区左边界系统顶部依然有合理拖动空间两者不冲突。标签栏的真实布局树WorkspaceShell.tabBar外层是 Row。左侧横向 Scroll 使用layoutWeight(1)占据除右侧工具按钮外的剩余宽度Scroll 内部是一个 Row依次放文档标签和新建图标。右侧是打开、保存、源码/分栏/预览等固定工具。BuilderprivatetabBar(){Row(){Scroll(){Row(){ForEach(this.documentSessions,(session:DocumentSession){this.documentTab(session)})this.iconButton($r(sys.symbol.plus),$r(app.string.new_document),(){this.requestEditorCommand(new);})}}.height(100%).layoutWeight(1).scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off)Button($r(app.string.open_action))Button($r(app.string.save_action))}}Scroll 的 viewport 很宽而单个标签和加号只占较小内容宽度。当内容短于 viewport 时“内容应该放在轨道哪里”由 Scroll 对齐决定当内容超过 viewport 时才进入水平滚动。之前代码没有明确声明对齐依赖平台默认值。这类问题常在响应式布局中出现开发者关注溢出后的滚动却忘记非溢出状态也有对齐语义。默认值即使在某版本或某容器中看似合理也不应替代产品明确要求。默认居中为何只在单标签暴露当前 ArkUI 环境下通用内容对齐表现为居中。假设滚动 viewport 可用宽度 700 vp单标签 156 vp加号 32 vp内容合计约 188 vp剩余 512 vp 会分布在内容两侧首标签左边就出现约 256 vp 空白。用户看到的正是这种量级。当打开四五个标签内容宽度逐渐接近 viewport居中空白缩小超过 viewport 后滚动内容本身比视口更宽起点问题也不再以大块空白呈现。因此依赖多文档日常测试很容易漏掉而新建应用的默认单标签首屏最明显。侧栏宽度也会改变 viewport。侧栏越窄编辑工作区越宽单标签剩余空间越大错误空白反而更明显把侧栏拖宽后空白缩短可能让人误以为它与侧栏相关。真正稳定的修复必须直接指定 Scroll 内容起点。用 Alignment.Start 声明产品意图修复只在 Scroll 上增加.align(Alignment.Start)Scroll(){Row(){ForEach(this.documentSessions,(session:DocumentSession){this.documentTab(session)},(session:DocumentSession):string${session.id}:${session.name}:${session.dirty?dirty:saved})this.iconButton($r(sys.symbol.plus),$r(app.string.new_document),(){this.requestEditorCommand(new);})}}.height(100%).layoutWeight(1).align(Alignment.Start).scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off)Start 表达的是逻辑起点而非硬编码像素偏移。当前中英文界面都采用从左到右布局首标签从左边界开始。未来若产品支持 RTL需要验证 ArkUI 的逻辑方向映射而不是把这里改成绝对 Left。这行代码没有给内部 Row 设置width(100%)。强行让内容 Row 填满视口后还要再决定 Row 内部排列可能影响内容溢出测量直接让 Scroll 对齐实际内容更符合组件职责。水平滚动能力必须继续保留修复目标是单标签左对齐不是取消 Scroll。OhMarkdown 支持多文档会话每个标签固定 156 vp打开多个文件后总宽度会超过可用轨道。Scroll 仍设置 Horizontal滚动条隐藏以保持紧凑桌面外观。.align(Alignment.Start)只影响内容小于 viewport 时的位置不会把溢出内容压缩到一行或禁用滚动。多标签顺序仍由documentSessions决定点击激活、关闭和脏状态没有变化。加号跟在最后一个标签之后单标签时位于其右侧多标签时随内容滚动。右侧打开、保存和模式按钮不在 Scroll 内所以标签再多也不会把关键文件操作推走。layoutWeight(1)让滚动轨道吸收剩余空间固定工具区保持可访问。这是 PC 工具栏比简单Row ForEach更可靠的结构。固定标签尺寸避免动态内容改轨道每个文档标签宽度 156、高度占满标签栏内部由文档图标、文件名、脏标记和关闭按钮组成。文件名maxLines(1)、layoutWeight(1)并使用 Ellipsis脏标记预留 8 vp即使保存状态变化也不会让关闭按钮横向跳动。Row({space:7}){SymbolGlyph($r(sys.symbol.doc_plaintext))Text(session.name).maxLines(1).layoutWeight(1).textOverflow({overflow:TextOverflow.Ellipsis})Text(session.dirty?*:).width(8)Button(){SymbolGlyph($r(sys.symbol.xmark))}.width(22).height(22)}.width(156).height(100%)稳定尺寸让对齐和滚动行为可预测。若标签宽度随完整文件名变化一个超长中文名称可能占满整条轨道其他标签位置和滚动量大幅跳动。PC 编辑器更适合固定轨道加省略完整路径可在文件树或后续 tooltip 中查看。激活态使用背景和底部 2 vp 强调线未激活态使用细右边框。状态改变不会改变标签外尺寸因此.align(Alignment.Start)的起点不会因保存或激活发生位移。标签身份与渲染键ForEach 的键包含 session id、名称和 dirty 状态。session.id保证文档会话身份名称和 dirty 变化触发对应标签视图更新。关闭操作按 id 路由不依赖显示名称两个同名文件也不会关闭错误会话。ForEach(this.documentSessions,(session:DocumentSession){this.documentTab(session)},(session:DocumentSession):string${session.id}:${session.name}:${session.dirty?dirty:saved})对齐修复没有改变数据模型或 key降低回归面。它不应顺便重构多文档会话因为用户反馈是明确的小范围布局问题。保持提交聚焦也使设备截图和测试结果更容易归因。当前键包含可变字段意味着名称或脏状态变化时组件可能被重建焦点是否受影响值得后续评估更稳定的策略可以只用 session id并依赖状态更新重绘。但该问题与首标签留白无直接关系当前提交没有扩大修改范围。系统标题栏为何必须保留空白HarmonyOS PC 窗口最上方由系统提供应用图标、名称、窗口控制和可拖动标题区域。应用截图中 OhMarkdown 名称右侧到最小化按钮之间的空白承担移动窗口任务。桌面用户依赖这一区域拖动、双击最大化或使用系统窗口行为。应用内部标签栏位于其下。它应从编辑工作区左边界开始但不应侵入系统标题栏。两者视觉上都横向延伸容易被红框一起圈中从组件边界看系统栏不在 WorkspaceShell 的 build Row 中应用无法用Alignment.Start改变它。修复报告明确说明“原生标题栏拖动空间按系统交互规范保留”。好的问题处理不仅消除真正 bug也要说明为何相邻空白不能消除避免后续再次以填满为目标破坏平台能力。与可调侧栏的组合关系WorkspaceShell 根 Row 依次是活动栏、侧栏、8 vp 调整柄和编辑工作区。标签栏属于editorWorkspace所以其逻辑起点随侧栏拖动一起移动在自己的坐标系中首标签始终从 x0 开始。Row(){this.activityRail()if(this.shouldShowSidebar()){this.contextualPanel()this.sidebarResizeHandle()}this.editorWorkspace()}默认侧栏约 264 vp 时标签紧邻编辑区边界拖到 392 vp 后整个编辑区向右移动标签仍紧邻新边界。旧实现会在每个新 viewport 中重新居中所以侧栏变化后空白长度也变化。Start 对齐使二者解耦侧栏决定编辑区在哪里标签栏决定内容从编辑区哪里开始。窗口宽度变化同理。右侧工具区占用固定空间Scroll viewport 伸缩内容不足时仍左对齐内容过多时滚动。布局规则不再依赖具体窗口或标签数量。真实模拟器截图下面截图来自 HarmonyOS MateBook Pro 2in1 模拟器。Untitled.md标签从编辑工作区左侧开始加号紧随其后旧截图中标签左侧的大段空白已经消失。左侧文件面板仍保持默认宽度顶部原生标题栏拖动区仍存在。设备记录显示默认布局中编辑器 Web 区域从横坐标 1125 开始。拖动侧栏到 392 vp 后 Web 起点变为 1374标签继续贴合起点文档状态没有重载。这个组合验证比单一静态截图更能说明对齐规则正确。视觉检查还确认标签文字、关闭按钮、加号和右侧打开/保存/模式按钮没有重叠。截图为真实应用内部画面不是设计稿也没有用裁切掩盖系统标题栏。窄窗口与侧栏折叠根布局最小约束为 640 × 480窗口小于 900 vp 时侧栏自动收起。侧栏消失后 editorWorkspace 向活动栏靠近标签仍从其新左边界开始。Start 对齐不需要为有无侧栏写分支。右侧工具按钮会减少 Scroll 可用空间但横向滚动保护标签不应通过缩小每个标签字体或动态缩放解决。显示文本必须保持专业可读长名称省略完整文档仍可通过文件树定位。当前测试重点是常用 PC 窗口和可调侧栏没有宣称所有极端最小窗口的工具栏按钮都已经完成折叠策略。若后续发现 640 vp 下右侧操作区过宽应独立设计菜单收纳而不是重新允许标签居中或覆盖按钮。焦点、点击与无障碍标签 Row 点击激活会话关闭按钮有资源化 accessibilityText“关闭文档”加号图标按钮使用“新建文档”。对齐变化不改变焦点顺序滚动内容中的标签和加号仍先于右侧工具按钮。激活文档由底部强调线、颜色和背景共同表达不只靠位置。脏状态使用星号并预留宽度当前还应进一步补充更明确的可访问描述例如把“已修改”加入标签 accessibilityText这属于后续无障碍增强不是本次已完成能力。鼠标点击关闭时事件是否冒泡到标签激活路径由 ArkUI Button 处理。修复未修改该交互。回归测试需要确认关闭非活动标签不会因对齐变更错误激活其他会话。自动化与构建验证布局提交后执行 Playwright30/30证明 CodeMirror、多文档、命令、搜索和预览未受原生轨道变化影响。ArkTSUnitTestBuild、Debug HAP 与 ohosTest HAP 构建通过MateBook Pro 2in1 模拟器 ohosTest7/7中英文资源键一致git diff --check通过。最终 Debug HAP 大小 1,520,352 字节SHA-256367ab8650479aa1fa8fe73bd1ebadd9a53f46659c850c2e388fc799d5cb88e5bohosTest HAP 大小 2,360,824 字节SHA-256b7230037b51044fe16168d2c835fb891e1c70f675941a1046165bc895217592c。产物均未签名只用于本轮可追溯测试。标签对齐主要依赖设备视觉与布局边界不适合只靠纯函数单元测试。未来可增加 ArkUI UI 自动化读取首个标签与 Webview 左边界并断言差值在预期范围减少截图人工判断。回归矩阵与错误方案至少要覆盖一个未命名标签、多个短标签、一个超长中文文件名、同名不同 URI、多标签溢出、脏状态切换、关闭活动与非活动标签、侧栏 220/392/480、侧栏折叠、中英文、亮暗主题。每种状态都要确认首标签起点、右侧工具稳定和水平滚动可用。不推荐通过给首标签负 margin 抵消空白因为空白随 viewport 和内容宽度变化不推荐给内部 Rowposition({ x: 0 })绝对定位会破坏滚动测量不推荐删除 Scroll多标签会溢出也不应把标签栏移动到系统标题栏窗口拖动和系统控制会变复杂。明确Alignment.Start是最小且语义正确的方案。它利用组件提供的布局能力修复原因而非截图结果同时保持现有滚动和会话架构。性能与稳定性对齐属性只影响布局计算不增加状态、监听、定时器或 Bridge 调用。内容短于 viewport 时位置从居中变为起点内容溢出时滚动机制不变。它不会读取文档、重绘预览或复制 CodeMirror 状态。固定标签宽度和隐藏滚动条保持轨道稳定。侧栏拖动期间 viewport 连续变化布局引擎重新计算标签位置但标签数量上限在现有工作流中较小没有手工逐标签测量。大文件模式与标签对齐无关。小改动仍需全套验证因为 WorkspaceShell 是共享外壳。提交保持单一目的让失败可以快速定位。后续若优化标签虚拟化或可拖动排序应保留“第一个可见标签从逻辑起点开始”的不变量。已知限制与后续方向当前标签固定 156 vp不支持用户调整、拖动排序、固定标签、分组或溢出下拉列表。水平滚动条隐藏后可发现性主要依赖触控板/滚轮大量标签场景可以增加左右滚动按钮或最近标签菜单。文件名省略尚无统一 tooltip用户可能需要回到文件树查看完整路径。脏状态星号的屏幕阅读器语义可以增强。RTL 语言尚未支持Alignment.Start在未来语言方向下需要设备验证。这些限制不影响本次问题结论单标签不应居中多标签不应失去滚动系统标题栏不应被应用填充。后续功能都应在这三个不变量上演进。结论OhMarkdown 标签留白的根因是横向 Scroll 未声明内容起点在单标签且 viewport 较宽时表现为居中。358eb3f使用.align(Alignment.Start)修复真实原因同时保留固定标签尺寸、水平滚动、右侧工具区和多文档会话。模拟器截图证明Untitled.md已贴合编辑工作区左边界可调侧栏改变工作区位置时对齐仍稳定。这次修改也建立了重要的鸿蒙 PC 判断方法先区分系统标题栏、应用标签栏和侧栏各自的所有权再用组件语义修复布局不用像素补丁追截图。小小一段空白背后连接的是窗口管理、滚动容器、多标签状态和桌面交互的一致性。