Files
jiapuapp/docs/superpowers/plans/2026-07-19-g01-add-genealogy-bottom-sheet.md
T
2026-07-20 06:52:26 +08:00

236 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# G01 添加家谱底部弹层实施计划
> **状态:已废止。** 用户否决了完整卷轴背景方向;不得继续执行本文。当前唯一实施计划为 `docs/superpowers/plans/2026-07-19-g01-add-genealogy-paper-sheet.md`。
> **执行要求:** 使用 `superpowers:executing-plans` 在当前会话内联执行;禁止多代理、worktree、`git add`、`commit`、`push`、`reset` 和 `checkout`。
**目标:** 将 G01 当前居中大卷轴“添加家谱”弹窗改成紧凑、可明确关闭的自定义底部弹层,并保持三个现有跳转行为不变。
**架构:** 只在 `g01-my-genealogies.vue` 内拆分添加弹层与切换弹层的模板选择器和样式责任;添加弹层底部对齐并使用自己的关闭入口,切换弹层继续沿用居中卷轴。先用现有 PowerShell 视觉契约锁定结构、文案、布局和返回键行为,再做最小实现。
**技术栈:** uni-app、Vue 3 `<script setup>`、SCSS、PowerShell 契约测试、Chrome DevTools Protocol 9222。
## 全局约束
- 只返工 G01“添加家谱”弹层,不修改切换家谱弹层、其他 G01 状态或其他页面。
- 不对接接口;三个入口继续调用 `applyToJoin``joinByInvite``createGenealogy`
- 继续使用项目自定义组件和现有真实 PNG 资产,不使用原生 UniApp 弹窗、Toast、Loading 或 ActionSheet。
- 只复用当前 9222 Chrome 窗口和唯一项目标签页,不打开第二个浏览器或第二个项目标签页。
- 不覆盖、删除或清理现有修改、未跟踪文件、测试、文档、截图、母版和候选资产。
- 不执行任何 Git 暂存、提交、推送、重置或检出命令。
- H5 截图只作为内部候选证据;Android/HBuilderX 仍标记为未验证。
- 没有用户明确“通过”,不得将 G01 标记为 `[x]` 或冻结。
---
### 任务 1:锁定底部弹层的结构、文案与视觉契约
**文件:**
- 修改:`tests/g01-visual-contract.ps1`
- 修改:`pages/genealogy/g01-my-genealogies.vue` 的添加弹层模板与弹层样式
**接口:**
- 消费:`addDialogVisible``closeAddDialog()``applyToJoin()``joinByInvite()``createGenealogy()`、现有 `AppButton`
- 产出:`.add-dialog-layer``.add-dialog``.add-dialog__heading``.add-dialog__close``.add-dialog__content`;切换弹层选择器和表现保持不变。
- [ ] **步骤 1:先加入会失败的结构与样式契约**
`tests/g01-visual-contract.ps1` 读取 `$page` 后增加:
```powershell
foreach ($token in @('class="add-dialog__heading"', 'class="add-dialog__close"', '>关闭</view>', 'label="继续创建家谱"')) {
if ($page -notmatch [regex]::Escape($token)) { throw "G-01 add sheet is missing $token" }
}
if ($page -match [regex]::Escape('label="确认没有现有家谱,继续创建"')) {
throw 'G-01 add sheet still puts guidance copy inside the create button.'
}
if ($page -notmatch '(?s)\.add-dialog-layer\s*\{[^}]*align-items:\s*flex-end;[^}]*background:\s*rgba\(34,\s*20,\s*12,\s*0\.68\);') {
throw 'G-01 add sheet is not a bottom-aligned layer with the approved mask.'
}
if ($page -notmatch '(?s)\.add-dialog\s*\{[^}]*max-width:\s*650rpx;[^}]*min-height:\s*545rpx;') {
throw 'G-01 add sheet does not use the compact approved proportions.'
}
if ($page -notmatch '(?s)\.add-dialog__close\s*\{[^}]*min-height:\s*72rpx;') {
throw 'G-01 add sheet close control does not reserve the approved hit area.'
}
if ($page -notmatch '(?s)\.genealogy-switcher-layer\s*\{[^}]*align-items:\s*center;[^}]*padding:\s*40rpx;') {
throw 'G-01 switcher must remain a centered dialog.'
}
```
- [ ] **步骤 2:运行契约并确认按预期失败**
运行:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File tests/g01-visual-contract.ps1
```
预期:`FAIL`,第一条失败信息为缺少 `class="add-dialog__heading"`;失败原因必须是新结构尚未实现,而不是脚本语法或文件编码错误。
- [ ] **步骤 3:最小修改添加弹层模板**
将添加弹层模板改为以下结构;切换家谱模板不动:
```vue
<view v-if="addDialogVisible" class="add-dialog-layer" @click="closeAddDialog">
<view class="add-dialog" @click.stop>
<image class="add-dialog__skin" src="/static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png" mode="aspectFit" />
<view class="add-dialog__content">
<view class="add-dialog__heading">
<text class="dialog-title">添加家谱</text>
<text class="dialog-copy">建议先搜索已有家谱避免重复创建</text>
</view>
<view class="add-dialog__close" hover-class="action-hover" @click="closeAddDialog">关闭</view>
<AppButton block label="搜索家谱" @click="applyToJoin" />
<AppButton block type="secondary" label="邀请码加入" @click="joinByInvite" />
<AppButton block type="secondary" label="继续创建家谱" @click="createGenealogy" />
</view>
</view>
</view>
```
- [ ] **步骤 4:拆分添加弹层和切换弹层样式**
用以下责任边界替换当前合并规则;保留现有 `.switcher-item` 及其后续规则:
```scss
.add-dialog-layer { position: fixed; z-index: 40; inset: 0; display: flex; align-items: flex-end; justify-content: center; box-sizing: border-box; padding: 0 20rpx; background: rgba(34, 20, 12, 0.68); }
.add-dialog { position: relative; width: 100%; max-width: 650rpx; min-height: 545rpx; max-height: calc(100vh - 24rpx); }
.add-dialog__skin { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.add-dialog__content { position: relative; z-index: 1; display: flex; min-height: 545rpx; max-height: calc(100vh - 24rpx); flex-direction: column; align-items: stretch; box-sizing: border-box; padding: 48rpx 58rpx calc(36rpx + env(safe-area-inset-bottom)); overflow-y: auto; }
.add-dialog__heading { padding-right: 104rpx; }
.add-dialog .dialog-title,
.add-dialog .dialog-copy { display: block; text-align: left; }
.add-dialog .dialog-title { padding-left: 54rpx; }
.add-dialog__close { position: absolute; z-index: 2; top: 38rpx; right: 120rpx; display: flex; min-width: 88rpx; min-height: 72rpx; align-items: center; justify-content: center; color: $ink-muted; font-size: 26rpx; }
.add-dialog .app-button { min-height: 92rpx; }
.add-dialog__content > .app-button:first-of-type { margin-top: 24rpx; }
.add-dialog__content > .app-button + .app-button { margin-top: 6rpx; }
.genealogy-switcher-layer { position: fixed; z-index: 40; inset: 0; display: flex; align-items: center; justify-content: center; box-sizing: border-box; padding: 40rpx; background: rgba(34, 20, 12, 0.58); }
.genealogy-switcher { position: relative; width: 100%; max-width: 670rpx; min-height: 720rpx; }
.genealogy-switcher__skin { position: absolute; inset: 0; width: 100%; height: 100%; }
.genealogy-switcher__content { position: relative; z-index: 1; display: flex; min-height: 720rpx; flex-direction: column; align-items: center; box-sizing: border-box; padding: 78rpx 58rpx 50rpx; }
.dialog-title { color: $brand-red; font-family: "STKaiti", "KaiTi", serif; font-size: 42rpx; font-weight: 700; letter-spacing: 4rpx; }
.dialog-copy { margin-top: 12rpx; color: $ink-muted; font-size: 25rpx; line-height: 1.5; }
.dialog-close { display: flex; min-height: 72rpx; align-items: center; justify-content: center; margin-top: auto; color: $ink-muted; font-size: 24rpx; }
```
- [ ] **步骤 5:运行契约并确认通过**
运行同一步骤 2。预期输出:
```text
PASS G-01 visual contract
```
---
### 任务 2:让 Android 返回键优先关闭添加弹层
**文件:**
- 修改:`tests/g01-visual-contract.ps1`
- 修改:`pages/genealogy/g01-my-genealogies.vue` 的 uni-app 生命周期导入和返回键处理
**接口:**
- 消费:`addDialogVisible: Ref<boolean>``closeAddDialog(): void`
- 产出:`onBackPress` 回调;弹层打开时返回 `true` 并关闭,未打开时不拦截页面返回。
- [ ] **步骤 1:先加入会失败的返回键契约**
`tests/g01-visual-contract.ps1` 增加:
```powershell
if ($page -notmatch 'import\s*\{[^}]*onBackPress[^}]*\}\s*from\s*"@dcloudio/uni-app"') {
throw 'G-01 add sheet does not import onBackPress.'
}
if ($page -notmatch '(?s)onBackPress\(\(\)\s*=>\s*\{\s*if\s*\(!addDialogVisible\.value\)\s*return\s*false;\s*closeAddDialog\(\);\s*return\s*true;\s*\}\);') {
throw 'G-01 add sheet does not consume Android back before page navigation.'
}
```
- [ ] **步骤 2:运行契约并确认按预期失败**
运行视觉契约。预期:`FAIL: G-01 add sheet does not import onBackPress.`
- [ ] **步骤 3:加入最小返回键处理**
将导入改为:
```js
import { onBackPress, onLoad, onShow } from "@dcloudio/uni-app";
```
`closeAddDialog` 定义之后加入:
```js
onBackPress(() => {
if (!addDialogVisible.value) return false;
closeAddDialog();
return true;
});
```
- [ ] **步骤 4:运行契约并确认通过**
预期输出:`PASS G-01 visual contract`
---
### 任务 3:聚焦验证并在唯一标签页恢复审批状态
**文件:**
- 验证:`pages/genealogy/g01-my-genealogies.vue`
- 读取:`tests/g01-empty-state-contract.ps1`
- 读取:`tests/g01-visual-contract.ps1`
- 生成候选截图:`docs/design/screens/runtime/2026-07-19/g01-approval/05-add-dialog-bottom-sheet-412x915.png`
**接口:**
- 消费:5173 H5 服务、9222 唯一 Chrome 项目标签页、当前 G01 默认列表态。
- 产出:412×915 当前添加弹层的候选证据;不改变验收规划勾选状态。
- [ ] **步骤 1:运行最小相关静态检查**
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File tests/g01-visual-contract.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File tests/g01-empty-state-contract.ps1
git diff --check -- pages/genealogy/g01-my-genealogies.vue tests/g01-visual-contract.ps1 docs/superpowers/specs/2026-07-19-g01-add-genealogy-bottom-sheet-design.md docs/superpowers/plans/2026-07-19-g01-add-genealogy-bottom-sheet.md
```
预期:两个测试均输出 `PASS``git diff --check` 无输出且退出码为 0。不修改测试阈值来换取通过。
- [ ] **步骤 2:确认服务、标签页和视口约束**
只读检查 5173、9222 和 `http://127.0.0.1:9222/json/list`。预期:5173 可访问;9222 只有一个 `localhost:5173` 项目页;不得启动第二个 Chrome。
- [ ] **步骤 3:在同一标签页刷新现有 G01 代码并打开添加弹层**
保持 G01 默认列表态,将视口设为 412×915;若热更新已生效则不导航,关闭旧弹层后点击现有 `.create-action` 重新打开。等待 `.add-dialog-layer``.add-dialog__close` 和三个 `.app-button` 同时出现。
- [ ] **步骤 4:检查运行时几何和交互**
通过当前 CDP 连接仅检查同一个“添加家谱”状态,先检查 320×568,再恢复并检查 412×915
- `.add-dialog` 底边与当前视口底边重合,允许 1px 取整误差;
- 弹层没有横向溢出,且高度小于视口高度的 50%;
- `.add-dialog__close` 完全位于弹层可视区域内;
- 三个按钮同时可见,第三个文本为“继续创建家谱”;
- 点击弹层内部不关闭;点击“关闭”会关闭;再次打开后点击遮罩会关闭;
- 关闭和重新打开不改变 G01 独立列表的滚动位置;
- 最后恢复 412×915,并让添加家谱弹层保持打开。
- [ ] **步骤 5:截取并检查候选证据**
使用当前标签页的 `Page.captureScreenshot` 保存指定 PNG,打开原图检查:没有错误页、加载中、裁切、横向溢出、标题压线、关闭入口消失或大块无效留白。若不满足,只返工当前弹层。
- [ ] **步骤 6:交回用户审批**
只报告当前添加弹层的变更和真实验证结果,明确 Android/HBuilderX 尚未验证;等待用户明确“通过”,不得切换到“切换家谱弹层”,不得更新 G01 为 `[x]`