Files
jiapuapp/docs/superpowers/plans/2026-07-21-page-header-safe-area-g03.md
T

12 KiB
Raw Blame History

公共页头安全区与 G03 复用实施计划

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 让所有普通二级页的公共页头自动避开 Android 状态栏,并让 G03 删除自建页头后复用同一组件。

Architecture: components/PageHeader.vue 继续作为根页面与二级页面页头的唯一实现;普通变体统一拥有“状态栏高度 + 104rpx 内容区”,根变体保持现状。G03 只提供动态标题和自定义返回处理,不再拥有页头结构、图标、背景或安全区样式。

Tech Stack: uni-app、Vue 3 Composition API、SCSS、PowerShell 静态契约、现有 Node.js CDP smoke、HBuilderX Android、MuMu、ADB。

Global Constraints

  • 普通二级页头总高度必须是 calc(104rpx + var(--status-bar-height, 0px)),状态栏补偿只能由 PageHeader.vue 持有。
  • 根页面 root 变体继续使用现有 calc(124rpx + var(--status-bar-height, 0px)),不得改变 G01 已放行布局。
  • customBack 默认值必须为 false;默认返回与深链 fallback 行为保持不变。
  • G03 第一、第二步都必须使用公共 PageHeader,且第二步返回第一步的原业务行为保持不变。
  • 不修改 G03 表单、弹层、按钮、背景和业务数据;原生 step=ancestor 路由参数问题另行修复。
  • 自动检查覆盖 320px、360px、412px CSS 视口;最终视觉结论只依据 MuMu 原生画面。

Task 1: 为公共 PageHeader 建立安全区与返回契约

Files:

  • Modify: tests/shared-component-document-flow-contract.ps1
  • Modify: tests/shared-interaction-accessibility-contract.ps1
  • Modify: components/PageHeader.vue

Interfaces:

  • Consumes: uni-app 提供的 var(--status-bar-height, 0px)getCurrentPages()uni.navigateBack()uni.reLaunch()

  • Produces: customBack: Boolean = false 属性和 back 事件;普通页头及占位槽均使用安全区总高度。

  • Step 1: 写入普通页头安全区的失败契约

tests/shared-component-document-flow-contract.ps1 的 PageHeader 断言中加入:

Assert-Match $header '(?s)\.page-header-slot\s*\{[^}]*height:\s*calc\(104rpx \+ var\(--status-bar-height, 0px\)\);' 'PageHeader secondary slot must reserve status bar plus 104rpx content height'
Assert-Match $header '(?s)\.page-header\s*\{[^}]*height:\s*calc\(104rpx \+ var\(--status-bar-height, 0px\)\);[^}]*padding-top:\s*var\(--status-bar-height, 0px\);' 'PageHeader secondary header must place its 104rpx content below the status bar'
Assert-Match $header '(?s)\.page-header-slot--root\s*\{[^}]*height:\s*calc\(124rpx \+ var\(--status-bar-height, 0px\)\);' 'PageHeader root slot height must remain unchanged'
Assert-Match $header '(?s)\.page-header--root\s*\{[^}]*height:\s*calc\(124rpx \+ var\(--status-bar-height, 0px\)\);' 'PageHeader root height must remain unchanged'
  • Step 2: 写入自定义返回的失败契约

tests/shared-interaction-accessibility-contract.ps1 的 PageHeader 检查中加入:

foreach ($token in @(
  'customBack: { type: Boolean, default: false }',
  'const emit = defineEmits(["brand", "notice", "action", "back"]);',
  'if (props.customBack) {',
  'emit("back");',
  'uni.navigateBack();',
  'uni.reLaunch({ url: props.fallbackUrl });'
)) {
  if (-not $header.Contains($token)) { throw "PageHeader back contract missing: $token" }
}
  • Step 3: 运行聚焦契约并确认 RED

Run:

& powershell -ExecutionPolicy Bypass -File tests/shared-component-document-flow-contract.ps1
& powershell -ExecutionPolicy Bypass -File tests/shared-interaction-accessibility-contract.ps1

Expected: 两项均 FAIL,分别指出普通页头仍为固定 104rpxcustomBack 合同不存在。

  • Step 4: 实现最小公共组件改动

components/PageHeader.vue 中补充属性和事件:

const props = defineProps({
  title: { type: String, required: true },
  action: { type: String, default: "" },
  root: { type: Boolean, default: false },
  unreadCount: { type: Number, default: 0 },
  fallbackUrl: { type: String, default: "/pages/genealogy/g01-my-genealogies" },
  customBack: { type: Boolean, default: false },
});

const emit = defineEmits(["brand", "notice", "action", "back"]);

const goBack = () => {
  if (props.customBack) {
    emit("back");
    return;
  }
  const stack = getCurrentPages();
  if (stack.length > 1) {
    uni.navigateBack();
    return;
  }
  uni.reLaunch({ url: props.fallbackUrl });
};

把普通变体的尺寸改为:

.page-header-slot {
  height: calc(104rpx + var(--status-bar-height, 0px));
  flex: none;
}

.page-header {
  height: calc(104rpx + var(--status-bar-height, 0px));
  padding: 0 24rpx;
  padding-top: var(--status-bar-height, 0px);
}

保留 .page-header-slot--root.page-header--root 的现有 124rpx 规则不变。

  • Step 5: 运行聚焦契约并确认 GREEN

Run:

& powershell -ExecutionPolicy Bypass -File tests/shared-component-document-flow-contract.ps1
& powershell -ExecutionPolicy Bypass -File tests/shared-interaction-accessibility-contract.ps1
git diff --check

Expected: 两项输出 PASSgit diff --check 退出码为 0。

  • Step 6: 提交公共组件改动
git add components/PageHeader.vue tests/shared-component-document-flow-contract.ps1 tests/shared-interaction-accessibility-contract.ps1
git commit -m "fix: adapt shared page header to safe area"

Task 2: 用公共 PageHeader 替换 G03 自建页头

Files:

  • Modify: tests/g03-visual-states-contract.ps1
  • Modify: tests/g03-create-flow-contract.ps1
  • Modify: tests/g03-document-flow-contract.ps1
  • Modify: pages/genealogy/g03-create-genealogy.vue

Interfaces:

  • Consumes: Task 1 的 <PageHeader :title="String" custom-back @back="Function" />

  • Produces: G03 动态标题 isAncestorStep ? "录入首代人物" : "创建家谱",以及现有 goBack() 对第一、第二步的业务分流。

  • Step 1: 将 G03 契约改成公共页头要求

tests/g03-visual-states-contract.ps1 删除 .flow-header__back.flow-header__side.flow-header__back-icon 尺寸断言,改为:

Assert-Match -Content $g03 -Pattern '(?s)<PageHeader\s+:title="isAncestorStep \? .录入首代人物. : .创建家谱."\s+custom-back\s+@back="goBack"\s*/>' -Message 'G03 must use PageHeader with its dynamic title and custom back handler'
if ($g03 -match 'flow-header|flow-header__back|flow-header__title') { throw 'G03 must not retain a private header implementation' }

tests/g03-create-flow-contract.ps1 的 required 项中加入:

'import PageHeader from "@/components/PageHeader.vue";',
'<PageHeader',
'custom-back',
'@back="goBack"'

并从 required 项删除 'root-header-cinnabar.jpg';在 forbidden 项加入:

'class="flow-header"',
'flow-header__back',
'root-header-cinnabar.jpg'

tests/g03-document-flow-contract.ps1 删除页头背景的 required 项,并加入:

foreach ($forbidden in @('flow-header', 'root-header-cinnabar.jpg')) {
  if ($page -match [regex]::Escape($forbidden)) { throw "G03 must not retain private header token: $forbidden" }
}
  • Step 2: 运行 G03 聚焦契约并确认 RED

Run:

& powershell -ExecutionPolicy Bypass -File tests/g03-visual-states-contract.ps1
& powershell -ExecutionPolicy Bypass -File tests/g03-create-flow-contract.ps1
& powershell -ExecutionPolicy Bypass -File tests/g03-document-flow-contract.ps1

Expected: 契约因 G03 仍含私有 flow-header 且尚未导入 PageHeader 而 FAIL。

  • Step 3: 替换 G03 页头结构并删除私有样式

pages/genealogy/g03-create-genealogy.vue 中把整个 .flow-header 模板替换为:

<PageHeader
  :title="isAncestorStep ? '录入首代人物' : '创建家谱'"
  custom-back
  @back="goBack"
/>

<script setup> 中加入:

import PageHeader from "@/components/PageHeader.vue";

完整删除 .flow-header.flow-header__content.flow-header__back.flow-header__side.flow-header__back-icon.flow-header__title 六组样式。保留现有 goBack():第二步 redirectTo 第一页,第一步 navigateBack

  • Step 4: 运行 G03 聚焦契约并确认 GREEN

Run:

& powershell -ExecutionPolicy Bypass -File tests/g03-visual-states-contract.ps1
& powershell -ExecutionPolicy Bypass -File tests/g03-create-flow-contract.ps1
& powershell -ExecutionPolicy Bypass -File tests/g03-document-flow-contract.ps1
git diff --check

Expected: 三项输出 PASSgit diff --check 退出码为 0。

  • Step 5: 提交 G03 接入改动
git add pages/genealogy/g03-create-genealogy.vue tests/g03-visual-states-contract.ps1 tests/g03-create-flow-contract.ps1 tests/g03-document-flow-contract.ps1
git commit -m "refactor: reuse shared header on G03"

Task 3: 自动回归公共页头消费者与响应式边界

Files:

  • Verify: components/PageHeader.vue
  • Verify: pages/genealogy/g03-create-genealogy.vue
  • Verify: pages/genealogy/g06-search-genealogies.vue
  • Verify: pages/genealogy/g01-my-genealogies.vue

Interfaces:

  • Consumes: Task 1 与 Task 2 的公共页头合同。

  • Produces: 公共组件、G03、G06 与 G01 无静态合同回退的验证记录。

  • Step 1: 运行全部聚焦合同

Run:

$tests = @(
  'tests/shared-component-document-flow-contract.ps1',
  'tests/shared-interaction-accessibility-contract.ps1',
  'tests/g03-visual-states-contract.ps1',
  'tests/g03-create-flow-contract.ps1',
  'tests/g03-document-flow-contract.ps1',
  'tests/g06-search-flow-contract.ps1',
  'tests/g06-document-flow-contract.ps1',
  'tests/g01-visual-contract.ps1'
)
foreach ($test in $tests) { & powershell -ExecutionPolicy Bypass -File $test }

Expected: 八项均输出各自 PASSPowerShell 退出码为 0。

  • Step 2: 运行现有 H5 逻辑和宽度辅助检查

在本地 H5 服务已运行时执行:

node tests/g03-create-flow-runtime-smoke.js http://localhost:5173
node tests/g06-search-flow-runtime-smoke.js http://localhost:5173

Expected: 分别输出 G03-CREATE-FLOW-RUNTIME-SMOKE PASSG06-SEARCH-FLOW-RUNTIME-SMOKE PASS320、360、412px 视口无横向溢出。若本地服务端口不同,只替换 URL,不改变测试内容。

  • Step 3: 检查最终差异

Run:

git diff --check
git status --short

Expected: git diff --check 退出码为 0;工作区只允许存在本计划明确列出的验证证据或尚未提交的计划文件。

Task 4: MuMu 原生视觉验收门

Files:

  • Create: tmp/G03-shared-header-mumu.png
  • Create: tmp/G06-shared-header-mumu.png
  • Create: tmp/G01-root-header-regression-mumu.png

Interfaces:

  • Consumes: HBuilderX 已同步到 MuMu 的最新 Android 包与设备 emulator-5554

  • Produces: G03、G06、G01 三张原生截图及用户逐页结论。

  • Step 1: 确认设备尺寸与密度

Run:

$adb = 'C:\Users\Administrator\Desktop\HBuilderX\plugins\launcher-tools\tools\adbs\adb.exe'
& $adb devices
& $adb -s emulator-5554 shell wm size
& $adb -s emulator-5554 shell wm density

Expected: emulator-5554device,并记录当前 MuMu 的物理尺寸与密度。

  • Step 2: 捕获 G03、G06、G01 原生截图

每次人工导航到指定页面后执行同一组命令,仅替换文件名:

& $adb -s emulator-5554 shell screencap -p /sdcard/G03-shared-header-mumu.png
& $adb -s emulator-5554 pull /sdcard/G03-shared-header-mumu.png tmp/G03-shared-header-mumu.png

G06 使用 G06-shared-header-mumu.pngG01 使用 G01-root-header-regression-mumu.png

  • Step 3: 按页面验收并向用户展示

检查:

G03:返回按钮和“创建家谱”完整位于系统状态栏下方,正文紧随占位槽,无裁切或横向溢出。
G06:公共二级页头获得相同安全区,正文没有被推入页头或遮挡。
G01:根页头高度、logo、标题和操作区与已放行状态一致。

只有用户明确确认三页后,才能把本次公共页头改动报告为视觉通过。G03 原生第二步仍因既有路由参数问题单独记为未通过,不得用本次页头结论覆盖。