1
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# SDD Progress
|
||||
|
||||
Plan: docs/superpowers/plans/2026-07-21-page-header-safe-area-g03.md
|
||||
Branch: fix/page-header-safe-area
|
||||
Base: 779a442
|
||||
|
||||
Task 1: complete (commits 779a442..2f22df6, review clean)
|
||||
Task 2: complete (commits 2f22df6..a0d19f4, review clean after strict-regex fix)
|
||||
Task 3: complete (verification only at a0d19f4, review clean; 8 contracts + 2 H5 smokes pass)
|
||||
@@ -0,0 +1,143 @@
|
||||
# Review package: 779a442..2f22df6
|
||||
|
||||
## Commits
|
||||
2f22df6 fix: adapt shared page header to safe area
|
||||
|
||||
## Files changed
|
||||
components/PageHeader.vue | 12 ++++++++++--
|
||||
tests/shared-component-document-flow-contract.ps1 | 4 ++++
|
||||
tests/shared-interaction-accessibility-contract.ps1 | 10 ++++++++++
|
||||
3 files changed, 24 insertions(+), 2 deletions(-)
|
||||
|
||||
## Diff
|
||||
diff --git a/components/PageHeader.vue b/components/PageHeader.vue
|
||||
index 5ef28dd..17ab7ef 100644
|
||||
--- a/components/PageHeader.vue
|
||||
+++ b/components/PageHeader.vue
|
||||
@@ -67,52 +67,60 @@
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
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 });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-header-slot {
|
||||
- height: 104rpx;
|
||||
+ height: calc(104rpx + var(--status-bar-height, 0px));
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.page-header-slot--root {
|
||||
height: calc(124rpx + var(--status-bar-height, 0px));
|
||||
}
|
||||
|
||||
.page-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 30;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
- height: 104rpx;
|
||||
+ height: calc(104rpx + var(--status-bar-height, 0px));
|
||||
padding: 0 24rpx;
|
||||
+ padding-top: var(--status-bar-height, 0px);
|
||||
box-sizing: border-box;
|
||||
background: $brand-red;
|
||||
color: #fff9ed;
|
||||
}
|
||||
|
||||
.page-header--root {
|
||||
height: calc(124rpx + var(--status-bar-height, 0px));
|
||||
padding-top: var(--status-bar-height, 0px);
|
||||
background-color: #b52e22;
|
||||
overflow: hidden;
|
||||
diff --git a/tests/shared-component-document-flow-contract.ps1 b/tests/shared-component-document-flow-contract.ps1
|
||||
index e7b88ae..71e6b5f 100644
|
||||
--- a/tests/shared-component-document-flow-contract.ps1
|
||||
+++ b/tests/shared-component-document-flow-contract.ps1
|
||||
@@ -28,20 +28,24 @@ if ($header -match '(?s)\.page-header--root \.header-side,\s*\.page-header--root
|
||||
throw 'PageHeader root content must not use positioning'
|
||||
}
|
||||
Assert-Match $header '(?s)\.header-icon-button\s*\{[^}]*display:\s*grid;[^}]*place-items:\s*center;' 'PageHeader icon button must use a grid overlay'
|
||||
if ($header -match '(?s)\.header-icon-button\s*\{[^}]*position\s*:') {
|
||||
throw 'PageHeader icon button must not be a positioning context'
|
||||
}
|
||||
Assert-Match $header '(?s)\.header-logo,\s*\.header-notice-icon,\s*\.notice-dot\s*\{[^}]*grid-area:\s*1 / 1;' 'PageHeader icon and notice dot must share one grid cell'
|
||||
if ($header -match '(?s)\.notice-dot\s*\{[^}]*position\s*:') {
|
||||
throw 'PageHeader notice dot must use grid alignment instead of positioning'
|
||||
}
|
||||
+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'
|
||||
|
||||
if ($card -match '(?im)(?<![-\w])position\s*:') {
|
||||
throw 'GenealogyCard must use grid instead of positioning'
|
||||
}
|
||||
Assert-Match $card '(?s)\.genealogy-card\s*\{[^}]*display:\s*grid;[^}]*grid-template-columns:\s*72rpx minmax\(0, 1fr\);[^}]*column-gap:\s*22rpx;' 'GenealogyCard must own the two-column document-flow grid'
|
||||
Assert-Match $card '(?s)\.row-frame\s*\{[^}]*grid-area:\s*1 / 1 / 2 / -1;[^}]*z-index:\s*1;[^}]*width:\s*calc\(100% \+ var\(--card-padding-x\) \+ var\(--card-padding-x\)\);[^}]*height:\s*calc\(100% \+ var\(--card-padding-y\) \+ var\(--card-padding-y\)\);[^}]*margin:\s*calc\(-1 \* var\(--card-padding-y\)\) calc\(-1 \* var\(--card-padding-x\)\);' 'GenealogyCard row frame must span the padded card grid above the fixed page background'
|
||||
Assert-Match $card '(?s)\.surname-seal\s*\{[^}]*display:\s*grid;[^}]*grid-row:\s*1;[^}]*z-index:\s*2;[^}]*place-items:\s*center;' 'GenealogyCard surname seal must share the frame row and stay above it'
|
||||
Assert-Match $card '(?s)\.card-main\s*\{[^}]*grid-row:\s*1;[^}]*z-index:\s*2;' 'GenealogyCard content must share the frame row and stay above it'
|
||||
Assert-Match $card '(?s)\.surname-seal-frame,\s*\.surname-seal-copy\s*\{[^}]*grid-area:\s*1 / 1;' 'GenealogyCard seal frame and copy must share one grid cell'
|
||||
if ($card -match '(?s)\.card-name\s*\{[^}]*(?:overflow:\s*hidden|text-overflow:\s*ellipsis|white-space:\s*nowrap)') {
|
||||
diff --git a/tests/shared-interaction-accessibility-contract.ps1 b/tests/shared-interaction-accessibility-contract.ps1
|
||||
index 84ad70c..1059467 100644
|
||||
--- a/tests/shared-interaction-accessibility-contract.ps1
|
||||
+++ b/tests/shared-interaction-accessibility-contract.ps1
|
||||
@@ -10,20 +10,30 @@ if ($button -match '<view\s+[^>]*class="app-button"') { throw 'AppButton must us
|
||||
|
||||
$header = Read-Utf8 'components/PageHeader.vue'
|
||||
foreach ($pattern in @(
|
||||
'(?s)<button[^>]+class="header-back"[^>]+aria-label=',
|
||||
'(?s)<button[^>]+class="header-icon-button header-notice"[^>]+aria-label=',
|
||||
'(?s)<button[^>]+class="header-icon-button"[^>]+aria-label='
|
||||
)) {
|
||||
if ($header -notmatch $pattern) { throw "PageHeader accessibility contract missing: $pattern" }
|
||||
}
|
||||
if (-not $header.Contains('fallbackUrl')) { throw 'PageHeader must expose a deep-link fallback route' }
|
||||
+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" }
|
||||
+}
|
||||
|
||||
$dialog = Read-Utf8 'components/AppDialog.vue'
|
||||
foreach ($token in @('role="dialog"', 'aria-modal="true"', 'tabindex="-1"', '@keydown.esc.stop="cancel"', 'max-height: calc(100vh - 80rpx)', 'overflow-y: auto')) {
|
||||
if (-not $dialog.Contains($token)) { throw "AppDialog accessibility contract missing: $token" }
|
||||
}
|
||||
|
||||
$toast = Read-Utf8 'components/AppToast.vue'
|
||||
foreach ($token in @('v-show="visible"', 'role="status"', 'aria-live="polite"', 'aria-atomic="true"')) {
|
||||
if (-not $toast.Contains($token)) { throw "AppToast accessibility contract missing: $token" }
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
# Review package: 2f22df6..9362c47
|
||||
|
||||
## Commits
|
||||
9362c47 refactor: reuse shared header on G03
|
||||
|
||||
## Files changed
|
||||
pages/genealogy/g03-create-genealogy.vue | 62 ++++----------------------------
|
||||
tests/g03-create-flow-contract.ps1 | 9 +++--
|
||||
tests/g03-document-flow-contract.ps1 | 4 ++-
|
||||
tests/g03-visual-states-contract.ps1 | 4 +--
|
||||
4 files changed, 17 insertions(+), 62 deletions(-)
|
||||
|
||||
## Diff
|
||||
diff --git a/pages/genealogy/g03-create-genealogy.vue b/pages/genealogy/g03-create-genealogy.vue
|
||||
index 2bc757d..f37cadc 100644
|
||||
--- a/pages/genealogy/g03-create-genealogy.vue
|
||||
+++ b/pages/genealogy/g03-create-genealogy.vue
|
||||
@@ -1,30 +1,20 @@
|
||||
<!-- 页面编号:G-03;用途:创建家谱与录入首代人物的同路由两步流程。 -->
|
||||
<template>
|
||||
<view class="flow-page">
|
||||
<GenealogyPageBackground />
|
||||
|
||||
- <view class="flow-header">
|
||||
- <view class="flow-header__content">
|
||||
- <view class="flow-header__back" @click="goBack">
|
||||
- <image
|
||||
- class="flow-header__back-icon"
|
||||
- src="/static/assets/foundation/transparent/chevron-right.png"
|
||||
- mode="aspectFit"
|
||||
- />
|
||||
- </view>
|
||||
- <text class="flow-header__title">{{
|
||||
- isAncestorStep ? "录入首代人物" : "创建家谱"
|
||||
- }}</text>
|
||||
- <view class="flow-header__side" />
|
||||
- </view>
|
||||
- </view>
|
||||
+ <PageHeader
|
||||
+ :title="isAncestorStep ? '录入首代人物' : '创建家谱'"
|
||||
+ custom-back
|
||||
+ @back="goBack"
|
||||
+ />
|
||||
|
||||
<view class="flow-content">
|
||||
<view class="create-flow-panel">
|
||||
<view v-if="!isAncestorStep" class="create-flow-panel__content">
|
||||
<view class="flow-step-label"><text>第一步 · 立谱信息</text></view>
|
||||
<text class="flow-heading">为家族立一部可传承的谱</text>
|
||||
<text class="flow-note"
|
||||
>家谱建立后可继续完善,名称与访问规则由创建者维护。</text
|
||||
>
|
||||
|
||||
@@ -241,20 +231,21 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, onUnmounted, reactive, ref } from "vue";
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||||
import GenealogyPageBackground from "@/components/GenealogyPageBackground.vue";
|
||||
+import PageHeader from "@/components/PageHeader.vue";
|
||||
|
||||
const currentStep = ref("create");
|
||||
const genealogyId = ref("");
|
||||
const isSubmitting = ref(false);
|
||||
const createState = ref("form");
|
||||
const ancestorState = ref("form");
|
||||
const duplicateReminderVisible = ref(false);
|
||||
const fieldErrors = reactive({
|
||||
surname: "",
|
||||
name: "",
|
||||
@@ -416,61 +407,20 @@ const enterOverview = () =>
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.flow-page {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
background: #f9f6ef;
|
||||
}
|
||||
|
||||
-.flow-header {
|
||||
- z-index: 3;
|
||||
- height: 112rpx;
|
||||
- overflow: hidden;
|
||||
- background: url("/static/assets/foundation/opaque/root-header-cinnabar.jpg")
|
||||
- center / 100% 100% no-repeat;
|
||||
-}
|
||||
-
|
||||
-.flow-header__content {
|
||||
- z-index: 1;
|
||||
- display: flex;
|
||||
- height: 100%;
|
||||
- align-items: center;
|
||||
- justify-content: space-between;
|
||||
- padding: 0 26rpx;
|
||||
- box-sizing: border-box;
|
||||
-}
|
||||
-
|
||||
-.flow-header__back,
|
||||
-.flow-header__side {
|
||||
- display: flex;
|
||||
- width: 72rpx;
|
||||
- align-items: center;
|
||||
-}
|
||||
-
|
||||
-.flow-header__back-icon {
|
||||
- width: 34rpx;
|
||||
- height: 34rpx;
|
||||
- transform: rotate(180deg);
|
||||
-}
|
||||
-
|
||||
-.flow-header__title {
|
||||
- flex: 1;
|
||||
- color: #ffe4a7;
|
||||
- font-family: "STKaiti", "KaiTi", serif;
|
||||
- font-size: 39rpx;
|
||||
- font-weight: 700;
|
||||
- letter-spacing: 4rpx;
|
||||
- text-align: center;
|
||||
-}
|
||||
-
|
||||
.flow-content {
|
||||
z-index: 2;
|
||||
padding: 24rpx 32rpx 48rpx;
|
||||
}
|
||||
|
||||
.create-flow-panel {
|
||||
min-height: 1000rpx;
|
||||
background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png")
|
||||
center / 100% 100% no-repeat;
|
||||
}
|
||||
diff --git a/tests/g03-create-flow-contract.ps1 b/tests/g03-create-flow-contract.ps1
|
||||
index 4accfd6..89923c2 100644
|
||||
--- a/tests/g03-create-flow-contract.ps1
|
||||
+++ b/tests/g03-create-flow-contract.ps1
|
||||
@@ -30,29 +30,32 @@ foreach ($required in @(
|
||||
'window.addEventListener("hashchange", syncFlowFromRoute)',
|
||||
'step=ancestor&genealogyId=',
|
||||
'const createState = ref("form");',
|
||||
'const ancestorState = ref("form");',
|
||||
'const fieldErrors = reactive({',
|
||||
'const duplicateReminderVisible = ref(false);',
|
||||
'class="duplicate-reminder-layer"',
|
||||
'if (isSubmitting.value) return;',
|
||||
'/pages/genealogy/g05-genealogy-overview?genealogyId=',
|
||||
'g01-empty-panel-frame.png',
|
||||
- 'root-header-cinnabar.jpg',
|
||||
'a01-scroll-primary-v3.png',
|
||||
'create-flow-panel',
|
||||
- 'flow-primary-action'
|
||||
+ 'flow-primary-action',
|
||||
+ 'import PageHeader from "@/components/PageHeader.vue";',
|
||||
+ '<PageHeader',
|
||||
+ 'custom-back',
|
||||
+ '@back="goBack"'
|
||||
)) {
|
||||
Assert-Contains -Content $g03 -Expected $required -Message "Missing G03 flow contract: $required"
|
||||
}
|
||||
|
||||
-foreach ($forbidden in @("from '@/utils/api.js'", 'appApi.', 'uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet', '/pages/tree/t01-tree-overview?genealogyId=')) {
|
||||
+foreach ($forbidden in @("from '@/utils/api.js'", 'appApi.', 'uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet', '/pages/tree/t01-tree-overview?genealogyId=', 'class="flow-header"', 'flow-header__back', 'root-header-cinnabar.jpg')) {
|
||||
if ($g03 -match [regex]::Escape($forbidden)) { throw "G03 retains forbidden implementation: $forbidden" }
|
||||
}
|
||||
|
||||
foreach ($className in @('create-flow-panel', 'flow-primary-action')) {
|
||||
Assert-NoCssSurface -Content $g03 -ClassName $className
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $panelPath)) { throw 'G03 requires the accepted genealogy paper panel bitmap' }
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
$panel = [System.Drawing.Bitmap]::FromFile($panelPath)
|
||||
diff --git a/tests/g03-document-flow-contract.ps1 b/tests/g03-document-flow-contract.ps1
|
||||
index 0249541..654dfa7 100644
|
||||
--- a/tests/g03-document-flow-contract.ps1
|
||||
+++ b/tests/g03-document-flow-contract.ps1
|
||||
@@ -1,15 +1,17 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$page = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g03-create-genealogy.vue') -Raw -Encoding utf8
|
||||
$style = [regex]::Match($page, '(?s)<style[^>]*>(.*?)</style>').Groups[1].Value
|
||||
$positions = [regex]::Matches([regex]::Replace($style, '(?s)/\*.*?\*/', ''), '(?m)\bposition\s*:\s*([^;]+);')
|
||||
if ($positions.Count -ne 1 -or $positions[0].Groups[1].Value.Trim() -ne 'fixed') {
|
||||
throw "G03 must retain only its combined fixed dialog layer rule; found $($positions.Count) position declarations"
|
||||
}
|
||||
foreach ($required in @(
|
||||
- 'background: url("/static/assets/foundation/opaque/root-header-cinnabar.jpg")',
|
||||
'background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png")',
|
||||
'background: url("/static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png")',
|
||||
'background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png")'
|
||||
)) { if ($page -notmatch [regex]::Escape($required)) { throw "G03 is missing real asset background: $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" }
|
||||
+}
|
||||
Write-Output 'G03-DOCUMENT-FLOW-CONTRACT PASS'
|
||||
diff --git a/tests/g03-visual-states-contract.ps1 b/tests/g03-visual-states-contract.ps1
|
||||
index 5342acb..cfb7911 100644
|
||||
--- a/tests/g03-visual-states-contract.ps1
|
||||
+++ b/tests/g03-visual-states-contract.ps1
|
||||
@@ -2,22 +2,22 @@ $ErrorActionPreference = 'Stop'
|
||||
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$g03 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g03-create-genealogy.vue') -Raw -Encoding utf8
|
||||
|
||||
function Assert-Match {
|
||||
param([string]$Content, [string]$Pattern, [string]$Message)
|
||||
if ($Content -notmatch $Pattern) { throw $Message }
|
||||
}
|
||||
|
||||
if ($g03 -match '<text>返回</text>') { throw 'G03 header must use only the approved image back arrow' }
|
||||
-Assert-Match -Content $g03 -Pattern '(?s)\.flow-header__back,\s*\.flow-header__side\s*\{[^}]*width:\s*72rpx;' -Message 'G03 header side slots must preserve centered title after removing back text'
|
||||
-Assert-Match -Content $g03 -Pattern '(?s)\.flow-header__back-icon\s*\{[^}]*width:\s*34rpx;[^}]*height:\s*34rpx;' -Message 'G03 back arrow must use the approved visible size'
|
||||
+Assert-Match -Content $g03 -Pattern '(?s)<PageHeader\s+:title="isAncestorStep \? .\u5F55\u5165\u9996\u4EE3\u4EBA\u7269. : .\u521B\u5EFA\u5BB6\u8C31."\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' }
|
||||
Assert-Match -Content $g03 -Pattern '(?s)\.flow-note,\s*\.flow-rule__note\s*\{[^}]*font-size:\s*25rpx;' -Message 'G03 guidance copy must use the approved readable size'
|
||||
Assert-Match -Content $g03 -Pattern '(?s)\.flow-rule__option\s*\{[^}]*font-size:\s*23rpx;' -Message 'G03 visibility options must use the approved readable size'
|
||||
Assert-Match -Content $g03 -Pattern '(?s)\.field-error\s*\{[^}]*font-size:\s*24rpx;[^}]*line-height:\s*34rpx;' -Message 'G03 field errors must use the approved readable size'
|
||||
Assert-Match -Content $g03 -Pattern '(?s)\.flow-error\s*\{[^}]*font-size:\s*25rpx;[^}]*line-height:\s*36rpx;' -Message 'G03 submit errors must use the approved readable size'
|
||||
|
||||
foreach ($asset in @('a01-scroll-dialog-v3.png', 'a01-scroll-primary-v3.png', 'a01-scroll-secondary-v3.png')) {
|
||||
if ($g03 -notmatch [regex]::Escape($asset)) { throw "G03 must retain approved shared asset: $asset" }
|
||||
}
|
||||
|
||||
Write-Output 'G03-VISUAL-STATES-CONTRACT PASS'
|
||||
@@ -0,0 +1,230 @@
|
||||
# Review package: 2f22df6..a0d19f4
|
||||
|
||||
## Commits
|
||||
a0d19f4 test: tighten G03 shared header contract
|
||||
9362c47 refactor: reuse shared header on G03
|
||||
|
||||
## Files changed
|
||||
pages/genealogy/g03-create-genealogy.vue | 62 ++++----------------------------
|
||||
tests/g03-create-flow-contract.ps1 | 9 +++--
|
||||
tests/g03-document-flow-contract.ps1 | 4 ++-
|
||||
tests/g03-visual-states-contract.ps1 | 4 +--
|
||||
4 files changed, 17 insertions(+), 62 deletions(-)
|
||||
|
||||
## Diff
|
||||
diff --git a/pages/genealogy/g03-create-genealogy.vue b/pages/genealogy/g03-create-genealogy.vue
|
||||
index 2bc757d..f37cadc 100644
|
||||
--- a/pages/genealogy/g03-create-genealogy.vue
|
||||
+++ b/pages/genealogy/g03-create-genealogy.vue
|
||||
@@ -1,30 +1,20 @@
|
||||
<!-- 页面编号:G-03;用途:创建家谱与录入首代人物的同路由两步流程。 -->
|
||||
<template>
|
||||
<view class="flow-page">
|
||||
<GenealogyPageBackground />
|
||||
|
||||
- <view class="flow-header">
|
||||
- <view class="flow-header__content">
|
||||
- <view class="flow-header__back" @click="goBack">
|
||||
- <image
|
||||
- class="flow-header__back-icon"
|
||||
- src="/static/assets/foundation/transparent/chevron-right.png"
|
||||
- mode="aspectFit"
|
||||
- />
|
||||
- </view>
|
||||
- <text class="flow-header__title">{{
|
||||
- isAncestorStep ? "录入首代人物" : "创建家谱"
|
||||
- }}</text>
|
||||
- <view class="flow-header__side" />
|
||||
- </view>
|
||||
- </view>
|
||||
+ <PageHeader
|
||||
+ :title="isAncestorStep ? '录入首代人物' : '创建家谱'"
|
||||
+ custom-back
|
||||
+ @back="goBack"
|
||||
+ />
|
||||
|
||||
<view class="flow-content">
|
||||
<view class="create-flow-panel">
|
||||
<view v-if="!isAncestorStep" class="create-flow-panel__content">
|
||||
<view class="flow-step-label"><text>第一步 · 立谱信息</text></view>
|
||||
<text class="flow-heading">为家族立一部可传承的谱</text>
|
||||
<text class="flow-note"
|
||||
>家谱建立后可继续完善,名称与访问规则由创建者维护。</text
|
||||
>
|
||||
|
||||
@@ -241,20 +231,21 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, onUnmounted, reactive, ref } from "vue";
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||||
import GenealogyPageBackground from "@/components/GenealogyPageBackground.vue";
|
||||
+import PageHeader from "@/components/PageHeader.vue";
|
||||
|
||||
const currentStep = ref("create");
|
||||
const genealogyId = ref("");
|
||||
const isSubmitting = ref(false);
|
||||
const createState = ref("form");
|
||||
const ancestorState = ref("form");
|
||||
const duplicateReminderVisible = ref(false);
|
||||
const fieldErrors = reactive({
|
||||
surname: "",
|
||||
name: "",
|
||||
@@ -416,61 +407,20 @@ const enterOverview = () =>
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.flow-page {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
background: #f9f6ef;
|
||||
}
|
||||
|
||||
-.flow-header {
|
||||
- z-index: 3;
|
||||
- height: 112rpx;
|
||||
- overflow: hidden;
|
||||
- background: url("/static/assets/foundation/opaque/root-header-cinnabar.jpg")
|
||||
- center / 100% 100% no-repeat;
|
||||
-}
|
||||
-
|
||||
-.flow-header__content {
|
||||
- z-index: 1;
|
||||
- display: flex;
|
||||
- height: 100%;
|
||||
- align-items: center;
|
||||
- justify-content: space-between;
|
||||
- padding: 0 26rpx;
|
||||
- box-sizing: border-box;
|
||||
-}
|
||||
-
|
||||
-.flow-header__back,
|
||||
-.flow-header__side {
|
||||
- display: flex;
|
||||
- width: 72rpx;
|
||||
- align-items: center;
|
||||
-}
|
||||
-
|
||||
-.flow-header__back-icon {
|
||||
- width: 34rpx;
|
||||
- height: 34rpx;
|
||||
- transform: rotate(180deg);
|
||||
-}
|
||||
-
|
||||
-.flow-header__title {
|
||||
- flex: 1;
|
||||
- color: #ffe4a7;
|
||||
- font-family: "STKaiti", "KaiTi", serif;
|
||||
- font-size: 39rpx;
|
||||
- font-weight: 700;
|
||||
- letter-spacing: 4rpx;
|
||||
- text-align: center;
|
||||
-}
|
||||
-
|
||||
.flow-content {
|
||||
z-index: 2;
|
||||
padding: 24rpx 32rpx 48rpx;
|
||||
}
|
||||
|
||||
.create-flow-panel {
|
||||
min-height: 1000rpx;
|
||||
background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png")
|
||||
center / 100% 100% no-repeat;
|
||||
}
|
||||
diff --git a/tests/g03-create-flow-contract.ps1 b/tests/g03-create-flow-contract.ps1
|
||||
index 4accfd6..89923c2 100644
|
||||
--- a/tests/g03-create-flow-contract.ps1
|
||||
+++ b/tests/g03-create-flow-contract.ps1
|
||||
@@ -30,29 +30,32 @@ foreach ($required in @(
|
||||
'window.addEventListener("hashchange", syncFlowFromRoute)',
|
||||
'step=ancestor&genealogyId=',
|
||||
'const createState = ref("form");',
|
||||
'const ancestorState = ref("form");',
|
||||
'const fieldErrors = reactive({',
|
||||
'const duplicateReminderVisible = ref(false);',
|
||||
'class="duplicate-reminder-layer"',
|
||||
'if (isSubmitting.value) return;',
|
||||
'/pages/genealogy/g05-genealogy-overview?genealogyId=',
|
||||
'g01-empty-panel-frame.png',
|
||||
- 'root-header-cinnabar.jpg',
|
||||
'a01-scroll-primary-v3.png',
|
||||
'create-flow-panel',
|
||||
- 'flow-primary-action'
|
||||
+ 'flow-primary-action',
|
||||
+ 'import PageHeader from "@/components/PageHeader.vue";',
|
||||
+ '<PageHeader',
|
||||
+ 'custom-back',
|
||||
+ '@back="goBack"'
|
||||
)) {
|
||||
Assert-Contains -Content $g03 -Expected $required -Message "Missing G03 flow contract: $required"
|
||||
}
|
||||
|
||||
-foreach ($forbidden in @("from '@/utils/api.js'", 'appApi.', 'uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet', '/pages/tree/t01-tree-overview?genealogyId=')) {
|
||||
+foreach ($forbidden in @("from '@/utils/api.js'", 'appApi.', 'uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet', '/pages/tree/t01-tree-overview?genealogyId=', 'class="flow-header"', 'flow-header__back', 'root-header-cinnabar.jpg')) {
|
||||
if ($g03 -match [regex]::Escape($forbidden)) { throw "G03 retains forbidden implementation: $forbidden" }
|
||||
}
|
||||
|
||||
foreach ($className in @('create-flow-panel', 'flow-primary-action')) {
|
||||
Assert-NoCssSurface -Content $g03 -ClassName $className
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $panelPath)) { throw 'G03 requires the accepted genealogy paper panel bitmap' }
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
$panel = [System.Drawing.Bitmap]::FromFile($panelPath)
|
||||
diff --git a/tests/g03-document-flow-contract.ps1 b/tests/g03-document-flow-contract.ps1
|
||||
index 0249541..654dfa7 100644
|
||||
--- a/tests/g03-document-flow-contract.ps1
|
||||
+++ b/tests/g03-document-flow-contract.ps1
|
||||
@@ -1,15 +1,17 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$page = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g03-create-genealogy.vue') -Raw -Encoding utf8
|
||||
$style = [regex]::Match($page, '(?s)<style[^>]*>(.*?)</style>').Groups[1].Value
|
||||
$positions = [regex]::Matches([regex]::Replace($style, '(?s)/\*.*?\*/', ''), '(?m)\bposition\s*:\s*([^;]+);')
|
||||
if ($positions.Count -ne 1 -or $positions[0].Groups[1].Value.Trim() -ne 'fixed') {
|
||||
throw "G03 must retain only its combined fixed dialog layer rule; found $($positions.Count) position declarations"
|
||||
}
|
||||
foreach ($required in @(
|
||||
- 'background: url("/static/assets/foundation/opaque/root-header-cinnabar.jpg")',
|
||||
'background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png")',
|
||||
'background: url("/static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png")',
|
||||
'background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png")'
|
||||
)) { if ($page -notmatch [regex]::Escape($required)) { throw "G03 is missing real asset background: $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" }
|
||||
+}
|
||||
Write-Output 'G03-DOCUMENT-FLOW-CONTRACT PASS'
|
||||
diff --git a/tests/g03-visual-states-contract.ps1 b/tests/g03-visual-states-contract.ps1
|
||||
index 5342acb..d539bc5 100644
|
||||
--- a/tests/g03-visual-states-contract.ps1
|
||||
+++ b/tests/g03-visual-states-contract.ps1
|
||||
@@ -2,22 +2,22 @@ $ErrorActionPreference = 'Stop'
|
||||
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$g03 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g03-create-genealogy.vue') -Raw -Encoding utf8
|
||||
|
||||
function Assert-Match {
|
||||
param([string]$Content, [string]$Pattern, [string]$Message)
|
||||
if ($Content -notmatch $Pattern) { throw $Message }
|
||||
}
|
||||
|
||||
if ($g03 -match '<text>返回</text>') { throw 'G03 header must use only the approved image back arrow' }
|
||||
-Assert-Match -Content $g03 -Pattern '(?s)\.flow-header__back,\s*\.flow-header__side\s*\{[^}]*width:\s*72rpx;' -Message 'G03 header side slots must preserve centered title after removing back text'
|
||||
-Assert-Match -Content $g03 -Pattern '(?s)\.flow-header__back-icon\s*\{[^}]*width:\s*34rpx;[^}]*height:\s*34rpx;' -Message 'G03 back arrow must use the approved visible size'
|
||||
+Assert-Match -Content $g03 -Pattern '(?s)<PageHeader\s+:title="isAncestorStep \? ''\u5F55\u5165\u9996\u4EE3\u4EBA\u7269'' : ''\u521B\u5EFA\u5BB6\u8C31''"\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' }
|
||||
Assert-Match -Content $g03 -Pattern '(?s)\.flow-note,\s*\.flow-rule__note\s*\{[^}]*font-size:\s*25rpx;' -Message 'G03 guidance copy must use the approved readable size'
|
||||
Assert-Match -Content $g03 -Pattern '(?s)\.flow-rule__option\s*\{[^}]*font-size:\s*23rpx;' -Message 'G03 visibility options must use the approved readable size'
|
||||
Assert-Match -Content $g03 -Pattern '(?s)\.field-error\s*\{[^}]*font-size:\s*24rpx;[^}]*line-height:\s*34rpx;' -Message 'G03 field errors must use the approved readable size'
|
||||
Assert-Match -Content $g03 -Pattern '(?s)\.flow-error\s*\{[^}]*font-size:\s*25rpx;[^}]*line-height:\s*36rpx;' -Message 'G03 submit errors must use the approved readable size'
|
||||
|
||||
foreach ($asset in @('a01-scroll-dialog-v3.png', 'a01-scroll-primary-v3.png', 'a01-scroll-secondary-v3.png')) {
|
||||
if ($g03 -notmatch [regex]::Escape($asset)) { throw "G03 must retain approved shared asset: $asset" }
|
||||
}
|
||||
|
||||
Write-Output 'G03-VISUAL-STATES-CONTRACT PASS'
|
||||
@@ -0,0 +1,7 @@
|
||||
# Review package: a0d19f4..a0d19f4
|
||||
|
||||
## Commits
|
||||
|
||||
## Files changed
|
||||
|
||||
## Diff
|
||||
@@ -0,0 +1,116 @@
|
||||
### 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 断言中加入:
|
||||
|
||||
```powershell
|
||||
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 检查中加入:
|
||||
|
||||
```powershell
|
||||
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
|
||||
& powershell -ExecutionPolicy Bypass -File tests/shared-component-document-flow-contract.ps1
|
||||
& powershell -ExecutionPolicy Bypass -File tests/shared-interaction-accessibility-contract.ps1
|
||||
```
|
||||
|
||||
Expected: 两项均 FAIL,分别指出普通页头仍为固定 `104rpx`、`customBack` 合同不存在。
|
||||
|
||||
- [ ] **Step 4: 实现最小公共组件改动**
|
||||
|
||||
在 `components/PageHeader.vue` 中补充属性和事件:
|
||||
|
||||
```js
|
||||
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 });
|
||||
};
|
||||
```
|
||||
|
||||
把普通变体的尺寸改为:
|
||||
|
||||
```scss
|
||||
.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
|
||||
& 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: 两项输出 `PASS`,`git diff --check` 退出码为 0。
|
||||
|
||||
- [ ] **Step 6: 提交公共组件改动**
|
||||
|
||||
```powershell
|
||||
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"
|
||||
```
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# Task 1 Report
|
||||
|
||||
Status: DONE
|
||||
|
||||
## Changed files
|
||||
|
||||
- `components/PageHeader.vue`
|
||||
- `tests/shared-component-document-flow-contract.ps1`
|
||||
- `tests/shared-interaction-accessibility-contract.ps1`
|
||||
|
||||
## RED
|
||||
|
||||
Command:
|
||||
|
||||
```powershell
|
||||
& powershell -ExecutionPolicy Bypass -File tests/shared-component-document-flow-contract.ps1
|
||||
& powershell -ExecutionPolicy Bypass -File tests/shared-interaction-accessibility-contract.ps1
|
||||
```
|
||||
|
||||
Failure evidence:
|
||||
|
||||
- `PageHeader secondary slot must reserve status bar plus 104rpx content height`
|
||||
- `PageHeader back contract missing: customBack: { type: Boolean, default: false }`
|
||||
|
||||
Both contract scripts exited with code `1`, for the expected missing behavior.
|
||||
|
||||
## GREEN
|
||||
|
||||
Command:
|
||||
|
||||
```powershell
|
||||
& powershell -ExecutionPolicy Bypass -File tests/shared-component-document-flow-contract.ps1
|
||||
& powershell -ExecutionPolicy Bypass -File tests/shared-interaction-accessibility-contract.ps1
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Result summary:
|
||||
|
||||
- `SHARED-COMPONENT-DOCUMENT-FLOW-CONTRACT PASS`
|
||||
- `SHARED-INTERACTION-ACCESSIBILITY-CONTRACT PASS`
|
||||
- `git diff --check` exited `0`
|
||||
|
||||
## Commit
|
||||
|
||||
`2f22df6 fix: adapt shared page header to safe area`
|
||||
|
||||
## Self-review
|
||||
|
||||
- Normal PageHeader slot and header use `calc(104rpx + var(--status-bar-height, 0px))`; the header applies status-bar top padding.
|
||||
- Root slot and header retain their `calc(124rpx + var(--status-bar-height, 0px))` rules.
|
||||
- `customBack` emits `back` and returns before navigation; normal navigation retains stack-aware back and fallback relaunch behavior.
|
||||
- The added contract tests cover the required exact public interface and layout declarations.
|
||||
|
||||
## Concerns
|
||||
|
||||
None. Git emitted existing line-ending conversion warnings during checks and commit, but all required checks passed.
|
||||
@@ -0,0 +1,98 @@
|
||||
### 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` 尺寸断言,改为:
|
||||
|
||||
```powershell
|
||||
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 项中加入:
|
||||
|
||||
```powershell
|
||||
'import PageHeader from "@/components/PageHeader.vue";',
|
||||
'<PageHeader',
|
||||
'custom-back',
|
||||
'@back="goBack"'
|
||||
```
|
||||
|
||||
并从 required 项删除 `'root-header-cinnabar.jpg'`;在 forbidden 项加入:
|
||||
|
||||
```powershell
|
||||
'class="flow-header"',
|
||||
'flow-header__back',
|
||||
'root-header-cinnabar.jpg'
|
||||
```
|
||||
|
||||
在 `tests/g03-document-flow-contract.ps1` 删除页头背景的 required 项,并加入:
|
||||
|
||||
```powershell
|
||||
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
|
||||
& 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` 模板替换为:
|
||||
|
||||
```vue
|
||||
<PageHeader
|
||||
:title="isAncestorStep ? '录入首代人物' : '创建家谱'"
|
||||
custom-back
|
||||
@back="goBack"
|
||||
/>
|
||||
```
|
||||
|
||||
在 `<script setup>` 中加入:
|
||||
|
||||
```js
|
||||
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
|
||||
& 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: 三项输出 `PASS`,`git diff --check` 退出码为 0。
|
||||
|
||||
- [ ] **Step 5: 提交 G03 接入改动**
|
||||
|
||||
```powershell
|
||||
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"
|
||||
```
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
# Task 2 Report: Reuse shared PageHeader on G03
|
||||
|
||||
## Status
|
||||
|
||||
Complete. Commit: `9362c47` (`refactor: reuse shared header on G03`).
|
||||
|
||||
## Changed files
|
||||
|
||||
- `pages/genealogy/g03-create-genealogy.vue`
|
||||
- `tests/g03-visual-states-contract.ps1`
|
||||
- `tests/g03-create-flow-contract.ps1`
|
||||
- `tests/g03-document-flow-contract.ps1`
|
||||
|
||||
## RED
|
||||
|
||||
Command:
|
||||
|
||||
```powershell
|
||||
& 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
|
||||
```
|
||||
|
||||
Exit code: `1`.
|
||||
|
||||
Failure evidence:
|
||||
|
||||
- `G03 must use PageHeader with its dynamic title and custom back handler`
|
||||
- `Missing G03 flow contract: import PageHeader from "@/components/PageHeader.vue";`
|
||||
- `G03 must not retain private header token: flow-header`
|
||||
|
||||
## GREEN
|
||||
|
||||
Command:
|
||||
|
||||
```powershell
|
||||
& 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
|
||||
```
|
||||
|
||||
Result: exit code `0`; all three focused contracts emitted `PASS`, and `git diff --check` exited cleanly. Git emitted only LF-to-CRLF informational warnings for the four changed files.
|
||||
|
||||
## Self-review
|
||||
|
||||
- Replaced only the private G03 header with `PageHeader`, using the required dynamic title, `custom-back`, and existing `goBack` handler.
|
||||
- Added the single shared-header import.
|
||||
- Removed all six specified private header style groups and the obsolete header asset reference.
|
||||
- Preserved G03 forms, dialogs, actions, backgrounds, data, and the existing two-step `goBack()` branching unchanged.
|
||||
- Updated contracts to require the shared header and reject private header tokens.
|
||||
|
||||
## Concerns
|
||||
|
||||
- The known App-Plus `step=ancestor` route-parameter issue was deliberately not modified, as required.
|
||||
- No remaining task-specific concern.
|
||||
|
||||
## Follow-up: strict dynamic-title quotes
|
||||
|
||||
Fixed `tests/g03-visual-states-contract.ps1` so its `PageHeader` title regex requires the Vue expression's internal single quotes. In the PowerShell single-quoted pattern, each required single quote is written as `''`.
|
||||
|
||||
Regression commands:
|
||||
|
||||
```powershell
|
||||
# RED: old pattern incorrectly matched a candidate with double-quoted internal titles.
|
||||
$ErrorActionPreference = 'Stop'; $pattern = '(?s)<PageHeader\s+:title="isAncestorStep \? .\u5F55\u5165\u9996\u4EE3\u4EBA\u7269. : .\u521B\u5EFA\u5BB6\u8C31."\s+custom-back\s+@back="goBack"\s*/>'; $title1 = -join [char[]]@(0x5F55, 0x5165, 0x9996, 0x4EE3, 0x4EBA, 0x7269); $title2 = -join [char[]]@(0x521B, 0x5EFA, 0x5BB6, 0x8C31); $invalid = '<PageHeader :title="isAncestorStep ? "' + $title1 + '" : "' + $title2 + '"" custom-back @back="goBack" />'; if ($invalid -match $pattern) { throw 'Dynamic-title regex incorrectly matches double-quoted internal titles' }
|
||||
|
||||
# GREEN and focused verification
|
||||
& 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
|
||||
```
|
||||
|
||||
Output summary: the RED probe failed with `Dynamic-title regex incorrectly matches double-quoted internal titles`; after the fix, the strict-quote probe and all three G03 contracts emitted `PASS`, and `git diff --check` exited `0`.
|
||||
|
||||
Commit: `a0d19f4` (`test: tighten G03 shared header contract`).
|
||||
@@ -0,0 +1,54 @@
|
||||
### 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:
|
||||
|
||||
```powershell
|
||||
$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: 八项均输出各自 `PASS`,PowerShell 退出码为 0。
|
||||
|
||||
- [ ] **Step 2: 运行现有 H5 逻辑和宽度辅助检查**
|
||||
|
||||
在本地 H5 服务已运行时执行:
|
||||
|
||||
```powershell
|
||||
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 PASS`、`G06-SEARCH-FLOW-RUNTIME-SMOKE PASS`;320、360、412px 视口无横向溢出。若本地服务端口不同,只替换 URL,不改变测试内容。
|
||||
|
||||
- [ ] **Step 3: 检查最终差异**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
git diff --check
|
||||
git status --short
|
||||
```
|
||||
|
||||
Expected: `git diff --check` 退出码为 0;工作区只允许存在本计划明确列出的验证证据或尚未提交的计划文件。
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# Task 3 Verification Report
|
||||
|
||||
## Status
|
||||
|
||||
PASS. All eight focused static contracts and both available H5 runtime smoke checks passed. No source or test files were modified by this task.
|
||||
|
||||
## Focused contract checks
|
||||
|
||||
Command executed:
|
||||
|
||||
```powershell
|
||||
$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 }
|
||||
```
|
||||
|
||||
| Test | Exit code | Output summary |
|
||||
| --- | ---: | --- |
|
||||
| `tests/shared-component-document-flow-contract.ps1` | 0 | `SHARED-COMPONENT-DOCUMENT-FLOW-CONTRACT PASS` |
|
||||
| `tests/shared-interaction-accessibility-contract.ps1` | 0 | `SHARED-INTERACTION-ACCESSIBILITY-CONTRACT PASS` |
|
||||
| `tests/g03-visual-states-contract.ps1` | 0 | `G03-VISUAL-STATES-CONTRACT PASS` |
|
||||
| `tests/g03-create-flow-contract.ps1` | 0 | `G03-CREATE-FLOW-CONTRACT PASS` |
|
||||
| `tests/g03-document-flow-contract.ps1` | 0 | `G03-DOCUMENT-FLOW-CONTRACT PASS` |
|
||||
| `tests/g06-search-flow-contract.ps1` | 0 | `G06-SEARCH-FLOW-CONTRACT PASS` |
|
||||
| `tests/g06-document-flow-contract.ps1` | 0 | `G06-DOCUMENT-FLOW-CONTRACT PASS` |
|
||||
| `tests/g01-visual-contract.ps1` | 0 | `PASS G-01 visual contract` |
|
||||
|
||||
Aggregate command exit code: `0`.
|
||||
|
||||
## H5 runtime smoke checks
|
||||
|
||||
Availability probe command:
|
||||
|
||||
```powershell
|
||||
Invoke-WebRequest -UseBasicParsing -Uri 'http://localhost:5173' -TimeoutSec 5
|
||||
```
|
||||
|
||||
Result: HTTP `200`; therefore an existing local H5 service was available at `http://localhost:5173`. No service was started or installed. (`Get-NetTCPConnection -LocalPort 5173` did not list a listener, but the HTTP probe and both browser-driven smoke tests succeeded.)
|
||||
|
||||
| Command | Exit code | Output summary |
|
||||
| --- | ---: | --- |
|
||||
| `node tests/g03-create-flow-runtime-smoke.js http://localhost:5173` | 0 | `G03-CREATE-FLOW-RUNTIME-SMOKE PASS` |
|
||||
| `node tests/g06-search-flow-runtime-smoke.js http://localhost:5173` | 0 | `G06-SEARCH-FLOW-RUNTIME-SMOKE PASS` |
|
||||
|
||||
## Final worktree checks
|
||||
|
||||
| Command | Exit code | Output summary |
|
||||
| --- | ---: | --- |
|
||||
| `git diff --check` | 0 | No output; no whitespace errors. |
|
||||
| `git status --short` | 0 | `?? .superpowers/` only. |
|
||||
|
||||
`git status --short` also emitted warnings that `C:\\Users\\Administrator/.config/git/ignore` could not be accessed due to permission denial; this did not affect its exit code or the reported worktree status.
|
||||
|
||||
## Worktree classification and concerns
|
||||
|
||||
The only untracked tree is `.superpowers/`, containing task briefs, earlier task reports/review diffs, and this Task 3 report. It is verification scratch evidence rather than source or test changes. No source-code worktree changes were reported.
|
||||
|
||||
Concern: the port-listener probe did not enumerate `5173` despite a successful HTTP `200`; runtime results confirm the H5 target was nevertheless reachable and exercised successfully.
|
||||
@@ -0,0 +1,46 @@
|
||||
### 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:
|
||||
|
||||
```powershell
|
||||
$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-5554` 为 `device`,并记录当前 MuMu 的物理尺寸与密度。
|
||||
|
||||
- [ ] **Step 2: 捕获 G03、G06、G01 原生截图**
|
||||
|
||||
每次人工导航到指定页面后执行同一组命令,仅替换文件名:
|
||||
|
||||
```powershell
|
||||
& $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.png`,G01 使用 `G01-root-header-regression-mumu.png`。
|
||||
|
||||
- [ ] **Step 3: 按页面验收并向用户展示**
|
||||
|
||||
检查:
|
||||
|
||||
```text
|
||||
G03:返回按钮和“创建家谱”完整位于系统状态栏下方,正文紧随占位槽,无裁切或横向溢出。
|
||||
G06:公共二级页头获得相同安全区,正文没有被推入页头或遮挡。
|
||||
G01:根页头高度、logo、标题和操作区与已放行状态一致。
|
||||
```
|
||||
|
||||
只有用户明确确认三页后,才能把本次公共页头改动报告为视觉通过。G03 原生第二步仍因既有路由参数问题单独记为未通过,不得用本次页头结论覆盖。
|
||||
Reference in New Issue
Block a user