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

13 KiB
Raw Blame History

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“切换当前家谱”弹层,不修改公共 AppDialogAppButton、其他状态或其他页面。
  • 唯一背景资产为 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 addcommitpushresetcheckout;不删除或清理现有文件。
  • 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 断言附近加入:

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 -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: availableGenealogiesselectedGenealogyIdselectGenealogy(item)closeSwitcher()

  • Produces: .genealogy-switcher__list 独立滚动区和 .genealogy-switcher__close 自定义关闭控件。

  • Step 1: 替换切换弹层模板

将当前弹层内部结构替换为:

<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 返回键优先级

将处理器改为:

onBackPress(() => {
  if (switcherVisible.value) {
    closeSwitcher()
    return true
  }
  if (addDialogVisible.value) {
    closeAddDialog()
    return true
  }
  return false
})
  • Step 3: 写入最小页面级样式

使用以下结构替换旧 switcher 样式:

.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 -NoProfile -ExecutionPolicy Bypass -File tests/g01-visual-contract.ps1

Expected: PASS G-01 visual contract

  • Step 5: 运行相邻 G01 合同

Run each command independently:

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: 三个状态合同均输出 PASSgit 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×568360×640360×800412×915

Expected:

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、两项、切换弹层打开、无临时克隆、无横向溢出。