Files
jiapuapp/docs/superpowers/plans/2026-07-19-g01-stretchable-switch-dialog.md
T
2026-07-20 06:52:26 +08:00

251 lines
13 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 可伸缩切换家谱弹层 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. The user explicitly forbids subagents and worktrees, so execution must remain inline in the current workspace. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 将 G01“切换当前家谱”改为紧凑、可读、带关闭图标且背景随内容九宫格伸缩的居中弹层。
**Architecture:** 继续使用现有完整 PNG `a01-scroll-dialog-v3.png`,由弹层根容器通过 `border-image` 九宫格渲染,固定装饰区并仅拉伸中间宣纸。标题和关闭入口固定在安全区,家谱列表按内容自然增高,到视口上限后独立滚动;不抽取全局组件。
**Tech Stack:** uni-app、Vue 3 `<script setup>`、SCSS、PowerShell 合同测试、Chrome DevTools Protocol 9222。
## Global Constraints
- 只修改 G01“切换当前家谱”弹层,不修改公共 `AppDialog``AppButton`、其他状态或其他页面。
- 唯一背景资产为 `static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png`,源尺寸 `1860×1560`;不得覆盖、删除、重绘或拆分资产。
- 九宫格源切片为 `300 260 360 260 fill`,显示边宽为 `110rpx 48rpx 132rpx 48rpx`
- 弹层宽度 `670rpx`、最小高度 `600rpx`、最大高度 `calc(100vh - 120rpx)`;内容区内边距为 `120rpx 58rpx 140rpx`
- 关闭入口复用 `g01-dialog-close.png`,热区 `80rpx × 80rpx`,定位 `top: 78rpx; right: 34rpx`,可访问名称为“关闭”。
- 复用 9222 上唯一的 `localhost:5173` 项目标签页,不新开浏览器或第二个项目标签页。
- 不使用多代理、worktree;不执行 `git add``commit``push``reset``checkout`;不删除或清理现有文件。
- H5 截图仅为内部候选;Android/HBuilderX 未验证;未经用户明确“通过”不得标记或冻结 G01。
---
### Task 1: 建立可伸缩切换弹层视觉合同
**Files:**
- Modify: `tests/g01-visual-contract.ps1`
- Test: `tests/g01-visual-contract.ps1`
**Interfaces:**
- Consumes: G01 源文件字符串 `$page` 和切换弹层现有类名。
- Produces: 九宫格、动态高度、安全区、滚动列表、关闭图标和返回键的单一合同。
- [ ] **Step 1: 添加失败合同**
在现有 switcher 断言附近加入:
```powershell
if ($page -match 'class="genealogy-switcher__skin"') {
throw 'G-01 switcher must not render the complete background as a fixed aspectFit image.'
}
if ($page -notmatch '(?s)\.genealogy-switcher\s*\{[^}]*min-height:\s*600rpx;[^}]*max-height:\s*calc\(100vh\s*-\s*120rpx\);[^}]*border-image-source:\s*url\("/static/assets/modules/auth/transparent/a01-scroll-dialog-v3\.png"\);[^}]*border-image-slice:\s*300\s+260\s+360\s+260\s+fill;[^}]*border-image-width:\s*110rpx\s+48rpx\s+132rpx\s+48rpx;') {
throw 'G-01 switcher does not use the approved stretchable complete background.'
}
if ($page -notmatch '(?s)\.genealogy-switcher__content\s*\{[^}]*min-height:\s*600rpx;[^}]*max-height:\s*calc\(100vh\s*-\s*120rpx\);[^}]*padding:\s*120rpx\s+58rpx\s+140rpx;') {
throw 'G-01 switcher content does not keep the approved decoration safe area.'
}
if ($page -notmatch 'class="genealogy-switcher__list"[^>]*scroll-y') {
throw 'G-01 switcher does not provide an independent scroll list.'
}
if ($page -notmatch '(?s)class="genealogy-switcher__close"[^>]*aria-label="关闭".*?g01-dialog-close\.png') {
throw 'G-01 switcher does not use the custom accessible close control.'
}
if ($page -match '<view class="dialog-close" @click="closeSwitcher">关闭</view>') {
throw 'G-01 switcher still exposes the obsolete bottom close copy.'
}
if ($page -notmatch '(?s)onBackPress\(\(\)\s*=>\s*\{\s*if\s*\(switcherVisible\.value\)\s*\{\s*closeSwitcher\(\);\s*return\s*true;\s*\}\s*if\s*\(addDialogVisible\.value\)') {
throw 'G-01 switcher does not consume Android back before the add dialog and page navigation.'
}
```
- [ ] **Step 2: 运行合同并确认 RED**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File tests/g01-visual-contract.ps1
```
Expected: FAIL,首先输出 `G-01 switcher must not render the complete background as a fixed aspectFit image.`
### Task 2: 最小实现动态背景、紧凑内容和关闭交互
**Files:**
- Modify: `pages/genealogy/g01-my-genealogies.vue:256-276`
- Modify: `pages/genealogy/g01-my-genealogies.vue:389-395`
- Modify: `pages/genealogy/g01-my-genealogies.vue:967-981`
- Test: `tests/g01-visual-contract.ps1`
**Interfaces:**
- Consumes: `availableGenealogies``selectedGenealogyId``selectGenealogy(item)``closeSwitcher()`
- Produces: `.genealogy-switcher__list` 独立滚动区和 `.genealogy-switcher__close` 自定义关闭控件。
- [ ] **Step 1: 替换切换弹层模板**
将当前弹层内部结构替换为:
```vue
<view v-if="switcherVisible" class="genealogy-switcher-layer" @click="closeSwitcher">
<view class="genealogy-switcher" role="dialog" aria-modal="true" aria-label="切换当前家谱" @click.stop>
<view class="genealogy-switcher__content">
<text class="dialog-title">切换当前家谱</text>
<view class="genealogy-switcher__close" role="button" aria-label="关闭" hover-class="action-hover" @click="closeSwitcher">
<image class="genealogy-switcher__close-icon" src="/static/assets/modules/genealogy/transparent/g01-dialog-close.png" mode="aspectFit" />
</view>
<scroll-view class="genealogy-switcher__list" scroll-y>
<view
v-for="item in availableGenealogies"
:key="item.id"
class="switcher-item"
:class="{ 'switcher-item--active': item.id === selectedGenealogyId }"
@click="selectGenealogy(item)"
>
<view>
<text class="switcher-item__name">{{ item.name }}</text>
<text class="switcher-item__meta">{{ item.location }} · {{ item.memberCount }} 位成员</text>
</view>
<text class="switcher-item__state">{{ item.id === selectedGenealogyId ? '当前' : '选择' }}</text>
</view>
</scroll-view>
</view>
</view>
</view>
```
- [ ] **Step 2: 调整 Android 返回键优先级**
将处理器改为:
```js
onBackPress(() => {
if (switcherVisible.value) {
closeSwitcher()
return true
}
if (addDialogVisible.value) {
closeAddDialog()
return true
}
return false
})
```
- [ ] **Step 3: 写入最小页面级样式**
使用以下结构替换旧 switcher 样式:
```scss
.genealogy-switcher-layer { position: fixed; z-index: 40; inset: 0; display: flex; align-items: center; justify-content: center; box-sizing: border-box; padding: 60rpx 40rpx; background: rgba(34, 20, 12, 0.58); }
.genealogy-switcher { position: relative; width: 670rpx; max-width: 100%; min-height: 600rpx; max-height: calc(100vh - 120rpx); box-sizing: border-box; border: 1px solid transparent; border-image-source: url("/static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png"); border-image-slice: 300 260 360 260 fill; border-image-width: 110rpx 48rpx 132rpx 48rpx; border-image-repeat: stretch; }
.genealogy-switcher__content { position: relative; z-index: 1; display: flex; min-height: 600rpx; max-height: calc(100vh - 120rpx); flex-direction: column; align-items: center; box-sizing: border-box; padding: 120rpx 58rpx 140rpx; }
.genealogy-switcher__close { position: absolute; z-index: 3; top: 78rpx; right: 34rpx; display: flex; width: 80rpx; height: 80rpx; align-items: center; justify-content: center; }
.genealogy-switcher__close-icon { width: 80rpx; height: 80rpx; }
.genealogy-switcher__list { width: 100%; min-height: 0; max-height: calc(100vh - 500rpx); margin-top: 24rpx; overflow-y: auto; }
.switcher-item { display: flex; width: 100%; min-height: 112rpx; align-items: center; justify-content: space-between; box-sizing: border-box; padding: 18rpx 16rpx; border: 1rpx solid transparent; border-bottom-color: rgba(181, 138, 75, 0.42); }
.switcher-item__name,
.switcher-item__meta { display: block; }
.switcher-item__name { color: $ink; font-family: "STKaiti", "KaiTi", serif; font-size: 32rpx; font-weight: 700; }
.switcher-item__meta { margin-top: 6rpx; color: #62584c; font-size: 24rpx; }
.switcher-item__state { color: $brand-red; font-size: 25rpx; font-weight: 600; }
.switcher-item--active { border-color: rgba(159, 23, 15, 0.22); background: rgba(159, 23, 15, 0.055); }
.switcher-item--active .switcher-item__name { color: $brand-red; }
```
不得修改公共组件或任何图片资产。
- [ ] **Step 4: 运行聚焦合同并确认 GREEN**
Run:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File tests/g01-visual-contract.ps1
```
Expected: `PASS G-01 visual contract`
- [ ] **Step 5: 运行相邻 G01 合同**
Run each command independently:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File tests/g01-empty-state-contract.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File tests/g01-loading-state-contract.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File tests/g01-error-state-contract.ps1
git diff --check
```
Expected: 三个状态合同均输出 `PASS``git diff --check` 退出码为 0,允许仅出现工作区既有 LF/CRLF 警告。
### Task 3: 在唯一 Chrome 标签页验证四档、伸缩和交互
**Files:**
- Modify: `design-qa.md`
- Create: `docs/design/screens/runtime/2026-07-19/g01-approval/06-switch-dialog-stretchable-412x915.png`
- Create: `docs/design/screens/runtime/2026-07-19/g01-approval/06-switch-dialog-stretchable-320x568.png`
- Create: `docs/design/screens/runtime/2026-07-19/g01-approval/06-switch-dialog-stretchable-six-items-412x915.png`
- Create: `docs/design/screens/runtime/2026-07-19/g01-approval/06-switch-dialog-before-vs-stretchable.png`
**Interfaces:**
- Consumes: 9222 上唯一的 `localhost:5173` 页面和当前 G01 列表态。
- Produces: 四档两项状态、六项自然增高、十二项封顶滚动及最终审批状态的运行证据。
- [ ] **Step 1: 确认唯一项目标签页并新鲜打开弹层**
读取 `http://127.0.0.1:9222/json/list`,严格确认只有一个 `type: page` 且 URL 以 `http://localhost:5173` 开头的项目页。刷新同一页面,点击 `.current-slip`,等待 `.genealogy-switcher` 可见。
Expected: 项目标签页数量为 1,切换弹层打开,没有添加弹层或临时克隆。
- [ ] **Step 2: 验证四档两项状态**
在同一标签页依次设置 `320×568``360×640``360×800``412×915`
Expected:
```text
dialog min-height = 600rpx 对应像素值
title 不与顶部边框或祥云重叠
close control 完整可见
itemCount = 2
列表不滚动
末项下方纯内容空白 <= 72rpx 对应像素值
horizontalOverflow = false
```
- [ ] **Step 3: 验证六项自然增高**
只在浏览器运行时向 `.genealogy-switcher__list` 临时克隆四项,并标记 `data-cdp-clone="1"`
Expected: 弹层高度大于两项状态、低于最大高度;背景中间宣纸伸长,顶部祥云和底部山水尺寸不变;列表无需滚动即可看全六项。
- [ ] **Step 4: 验证十二项封顶滚动**
清除六项克隆后临时补足十二项。
Expected: 弹层高度等于最大安全高度;标题和关闭图标固定;列表 `scrollHeight > clientHeight`、初始 `scrollTop = 0`,滚到底后末项可见;后方页面不滚动。
- [ ] **Step 5: 验证关闭和选择交互**
依次验证:弹层内部点击保持打开;关闭图标关闭;遮罩关闭;Android 返回处理器合同;点击第二项更新当前家谱并关闭。每次通过 `.current-slip` 在同一标签页重新打开。
Expected: 不出现原生 Toast、Loading、Modal 或 ActionSheet;控制台无异常,无 4xx/5xx 资源响应。
- [ ] **Step 6: 清理运行时克隆并恢复审批状态**
移除所有 `[data-cdp-clone]`,将视口恢复为 `412×915`,恢复两条真实数据并保持切换弹层打开。
Expected: 项目标签页仍为 1;添加弹层关闭;切换弹层打开;两项、无克隆、无横向溢出。
- [ ] **Step 7: 生成对比并更新设计质检**
把审查前截图 `audit-switch-dialog/01-switch-dialog-412x915.png` 与新 412×915 截图合成同尺寸横向对比;在 `design-qa.md` 记录标题安全区、空白量、九宫格装饰尺寸、文本可读性、关闭入口、四档、压力滚动、交互、控制台和证据限制。
Expected: 若 H5 内部候选不存在可执行 P0/P1/P2,则当前小节 `final result: passed`;同时明确用户尚未通过、Android/HBuilderX 未验证。
- [ ] **Step 8: 最终新鲜验证**
重新运行四个 G01 合同与 `git diff --check`,再读取浏览器最终状态。
Expected: 所有合同退出码为 0;同一项目标签页为 `412×915`、两项、切换弹层打开、无临时克隆、无横向溢出。