feat: 完成70%全局样式与可读性优化
This commit is contained in:
@@ -105,14 +105,14 @@ foreach ($selector in @('page-title', 'page-subtitle', 'register-divider')) {
|
||||
}
|
||||
|
||||
foreach ($ruleContract in @(
|
||||
@{ Selector = 'register-submit__content'; Expected = 'font-size: 36rpx;' },
|
||||
@{ Selector = 'agreement-row'; Expected = 'font-size: 24rpx;' },
|
||||
@{ Selector = 'register-submit__content'; Expected = 'font-size: clamp(19px, 36rpx, 24px);' },
|
||||
@{ Selector = 'agreement-row'; Expected = 'font-size: clamp(15px, 24rpx, 18px);' },
|
||||
@{ Selector = 'agreement-row'; Expected = 'color: #493323;' },
|
||||
@{ Selector = 'input-label'; Expected = 'font-size: 30rpx;' },
|
||||
@{ Selector = 'auth-input'; Expected = 'font-size: 30rpx;' },
|
||||
@{ Selector = 'input-label'; Expected = 'font-size: clamp(17px, 30rpx, 22px);' },
|
||||
@{ Selector = 'auth-input'; Expected = 'font-size: clamp(17px, 30rpx, 22px);' },
|
||||
@{ Selector = 'auth-input'; Expected = 'color: #493323;' },
|
||||
@{ Selector = 'placeholder'; Expected = 'color: #9f968d;' },
|
||||
@{ Selector = 'login-entry'; Expected = 'font-size: 28rpx;' },
|
||||
@{ Selector = 'login-entry'; Expected = 'font-size: clamp(16px, 28rpx, 20px);' },
|
||||
@{ Selector = 'login-entry'; Expected = 'color: #493323;' },
|
||||
@{ Selector = 'login-entry'; Expected = 'margin-top: auto;' }
|
||||
)) {
|
||||
|
||||
@@ -6,8 +6,8 @@ foreach ($token in @(
|
||||
'compact: { type: Boolean, default: false }',
|
||||
"'app-button--compact': compact",
|
||||
'@use "../styles/adaptive-frame-profiles.scss" as adaptive;',
|
||||
'@include adaptive.adaptive-scroll-button(primary);',
|
||||
'@include adaptive.adaptive-scroll-button(secondary);',
|
||||
'@include adaptive-scroll-button(primary);',
|
||||
'@include adaptive-scroll-button(secondary);',
|
||||
'a01-scroll-primary-v3.png',
|
||||
'a01-scroll-secondary-v3.png'
|
||||
)) {
|
||||
@@ -21,7 +21,7 @@ if ($button.Contains('border-image-slice: 56 300 fill;')) {
|
||||
if ($button -notmatch '(?s)\.app-button--compact \.app-button__skin\s*\{[^}]*display:\s*none;') {
|
||||
throw 'AppButton compact mode must hide the aspectFit image skin'
|
||||
}
|
||||
if ($button -notmatch '(?s)\.app-button--compact \.app-button__label\s*\{[^}]*padding:\s*0;[^}]*font-size:\s*23rpx;[^}]*letter-spacing:\s*0;[^}]*white-space:\s*nowrap;') {
|
||||
if ($button -notmatch '(?s)\.app-button--compact \.app-button__label\s*\{[^}]*padding:\s*0;[^}]*font-size:\s*clamp\(14px, 23rpx, 17px\);[^}]*letter-spacing:\s*0;[^}]*white-space:\s*nowrap;') {
|
||||
throw 'AppButton compact label must preserve four-character action capacity'
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ foreach ($token in @(
|
||||
'class="app-dialog__copy"',
|
||||
':compact="compactActions && showCancel"',
|
||||
'@use "../styles/adaptive-frame-profiles.scss" as adaptive;',
|
||||
'@include adaptive.adaptive-auth-dialog;',
|
||||
'@include adaptive-auth-dialog;',
|
||||
'padding: calc(40rpx + env(safe-area-inset-top)) 40rpx calc(40rpx + env(safe-area-inset-bottom));',
|
||||
'max-height: calc(var(--app-viewport-height, 100vh) - 80rpx - env(safe-area-inset-top) - env(safe-area-inset-bottom));',
|
||||
'min-height: 0;',
|
||||
|
||||
@@ -17,12 +17,12 @@ Assert-Match -Content $component -Pattern 'class="app-loading__seal"\s+src="/sta
|
||||
Assert-Match -Content $component -Pattern 'class="app-loading__knot"\s+src="/static/assets/foundation/transparent/auth-divider-knot\.png"' -Message 'AppLoading must render the approved real gold knot asset'
|
||||
Assert-Match -Content $component -Pattern '(?s)\.app-loading--page\s*\{[^}]*min-height:\s*320rpx;' -Message 'AppLoading page variant must use the approved minimum height'
|
||||
Assert-Match -Content $component -Pattern '(?s)\.app-loading--page\s+\.app-loading__seal\s*\{[^}]*width:\s*132rpx;[^}]*height:\s*136rpx;' -Message 'AppLoading page seal must use the approved size'
|
||||
Assert-Match -Content $component -Pattern '(?s)\.app-loading--page\s+\.app-loading__copy\s*\{[^}]*font-size:\s*30rpx;' -Message 'AppLoading page copy must use the approved size'
|
||||
Assert-Match -Content $component -Pattern '(?s)\.app-loading--page\s+\.app-loading__description\s*\{[^}]*font-size:\s*24rpx;' -Message 'AppLoading page description must use the approved size'
|
||||
Assert-Match -Content $component -Pattern '(?s)\.app-loading--page\s+\.app-loading__copy\s*\{[^}]*font-size:\s*clamp\(17px, 30rpx, 22px\);' -Message 'AppLoading page copy must use the approved size'
|
||||
Assert-Match -Content $component -Pattern '(?s)\.app-loading--page\s+\.app-loading__description\s*\{[^}]*font-size:\s*clamp\(15px, 24rpx, 18px\);' -Message 'AppLoading page description must use the approved size'
|
||||
Assert-Match -Content $component -Pattern '(?s)\.app-loading--section\s*\{[^}]*min-height:\s*180rpx;' -Message 'AppLoading section variant must use the approved minimum height'
|
||||
Assert-Match -Content $component -Pattern '(?s)\.app-loading--section\s+\.app-loading__seal\s*\{[^}]*width:\s*88rpx;[^}]*height:\s*90rpx;' -Message 'AppLoading section seal must use the approved size'
|
||||
Assert-Match -Content $component -Pattern '(?s)\.app-loading--section\s+\.app-loading__copy\s*\{[^}]*font-size:\s*24rpx;' -Message 'AppLoading section copy must use the approved size'
|
||||
Assert-Match -Content $component -Pattern '(?s)\.app-loading--section\s+\.app-loading__description\s*\{[^}]*font-size:\s*22rpx;' -Message 'AppLoading section description must use the approved size'
|
||||
Assert-Match -Content $component -Pattern '(?s)\.app-loading--section\s+\.app-loading__copy\s*\{[^}]*font-size:\s*clamp\(15px, 24rpx, 18px\);' -Message 'AppLoading section copy must use the approved size'
|
||||
Assert-Match -Content $component -Pattern '(?s)\.app-loading--section\s+\.app-loading__description\s*\{[^}]*font-size:\s*clamp\(14px, 22rpx, 17px\);' -Message 'AppLoading section description must use the approved size'
|
||||
Assert-Match -Content $component -Pattern 'app-loading-seal-breathe\s+1\.6s' -Message 'AppLoading seal must use the approved restrained breathing rhythm'
|
||||
Assert-Match -Content $component -Pattern '@media\s*\(prefers-reduced-motion:\s*reduce\)' -Message 'AppLoading must respect reduced-motion preferences'
|
||||
Assert-Match -Content $component -Pattern '(?s)@media\s*\(prefers-reduced-motion:\s*reduce\).*?\.app-loading__seal\s*\{[^}]*animation:\s*none;[^}]*transform:\s*none;' -Message 'Reduced-motion seal must stop animation and transforms'
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$avatar = Get-Content -LiteralPath (Join-Path $root 'utils/avatar.js') -Raw -Encoding UTF8
|
||||
$component = Get-Content -LiteralPath (Join-Path $root 'components/AppAvatar.vue') -Raw -Encoding UTF8
|
||||
|
||||
foreach ($required in @(
|
||||
'DEFAULT_MALE_AVATAR',
|
||||
'mjpc0703_A_cartoon_illustration_of_a_boy_wearing_a_red_Chinese__0@2x.png',
|
||||
'DEFAULT_FEMALE_AVATAR',
|
||||
'loyel003_Ancient_Beauty_Wearing_Tang_Dynasty_ClothingLooking_to_07cb98a9-ec37-4620-a032-ebe511fa93b5@2x.png',
|
||||
'String(sex ?? "").trim() === "1"'
|
||||
)) {
|
||||
if (-not $avatar.Contains($required)) {
|
||||
throw "Default avatar contract missing: $required"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $component.Contains('getDefaultAvatar(props.sex)')) {
|
||||
throw 'AppAvatar must own the fallback-avatar selection'
|
||||
}
|
||||
|
||||
foreach ($page in @(
|
||||
'pages/tree/t01-tree-overview.vue',
|
||||
'pages/tree/t03-member-profile.vue',
|
||||
'pages/profile/m01-profile-home.vue'
|
||||
)) {
|
||||
$source = Get-Content -LiteralPath (Join-Path $root $page) -Raw -Encoding UTF8
|
||||
if (-not $source.Contains('import AppAvatar from "@/components/AppAvatar.vue"')) {
|
||||
throw "Shared avatar component is not wired in: $page"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Output 'DEFAULT-AVATAR-CONTRACT PASS'
|
||||
@@ -7,10 +7,10 @@ function Assert-Match([string]$Content, [string]$Pattern, [string]$Message) { if
|
||||
$f01 = Read-Utf8 'pages/family/f01-family-feed.vue'
|
||||
$f02 = Read-Utf8 'pages/family/f02-publish-feed.vue'
|
||||
|
||||
foreach ($expected in @('24rpx','23rpx','22rpx')) {
|
||||
foreach ($expected in @('clamp\(15px, 24rpx, 18px\)','clamp\(14px, 23rpx, 17px\)','clamp\(14px, 22rpx, 17px\)')) {
|
||||
Assert-Match $f01 "font-size:\s*$expected" "F01 readability token missing: $expected"
|
||||
}
|
||||
foreach ($expected in @('24rpx','23rpx')) {
|
||||
foreach ($expected in @('clamp\(15px, 24rpx, 18px\)','clamp\(14px, 23rpx, 17px\)')) {
|
||||
Assert-Match $f02 "font-size:\s*$expected" "F02 readability token missing: $expected"
|
||||
}
|
||||
Write-Output 'F01-F02 readability visual contract passed.'
|
||||
|
||||
@@ -7,9 +7,9 @@ foreach ($expected in @(
|
||||
'box-sizing: border-box',
|
||||
'width: 514rpx',
|
||||
'@use "../../styles/adaptive-frame-profiles.scss" as adaptive;',
|
||||
'@include adaptive.adaptive-scroll-button(secondary);',
|
||||
'@include adaptive.adaptive-family-letter;',
|
||||
'@include adaptive.adaptive-scroll-button(primary);'
|
||||
'@include adaptive-scroll-button(secondary);',
|
||||
'@include adaptive-family-letter;',
|
||||
'@include adaptive-scroll-button(primary);'
|
||||
)) {
|
||||
if (-not $page.Contains($expected)) { throw "F01 document-flow contract missing: $expected" }
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ $root = Split-Path $PSScriptRoot -Parent
|
||||
$page = [System.IO.File]::ReadAllText((Join-Path $root 'pages/family/f02-publish-feed.vue'), [System.Text.Encoding]::UTF8)
|
||||
|
||||
foreach ($expected in @(
|
||||
'@include adaptive.adaptive-family-content;',
|
||||
'@include adaptive.adaptive-family-field;'
|
||||
'@include adaptive-family-content;',
|
||||
'@include adaptive-family-field;'
|
||||
)) {
|
||||
if (-not $page.Contains($expected)) { throw "F02 document-flow contract missing: $expected" }
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ foreach ($expected in @(
|
||||
'ModulePageBackground',
|
||||
'AppDialog',
|
||||
'AppButton',
|
||||
'@include adaptive.adaptive-family-panel;',
|
||||
'@include adaptive.adaptive-family-field;'
|
||||
'@include adaptive-family-panel;',
|
||||
'@include adaptive-family-field;'
|
||||
)) {
|
||||
Assert-Contains $expected "F06 article editor contract missing: $expected"
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ foreach ($expected in @(
|
||||
'PageHeader',
|
||||
'AppButton',
|
||||
'@use "../../styles/adaptive-frame-profiles.scss" as adaptive;',
|
||||
'@include adaptive.adaptive-family-panel;'
|
||||
'@include adaptive-family-panel;'
|
||||
)) {
|
||||
Assert-Contains $page $expected "F10 contract missing: $expected"
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ $editor = [System.IO.File]::ReadAllText((Join-Path $root 'pages/family/f06-artic
|
||||
|
||||
if ($publish -match 'position\s*:') { throw 'F02 must keep its editor in document flow' }
|
||||
if ($editor -match 'position\s*:') { throw 'F06 must keep its editor in document flow' }
|
||||
if (-not $publish.Contains('@include adaptive.adaptive-family-field;')) { throw 'F02 field frame must come from the adaptive profile owner' }
|
||||
if (-not $editor.Contains('@include adaptive.adaptive-family-field;')) { throw 'F06 field frame must come from the adaptive profile owner' }
|
||||
if (-not $publish.Contains('@include adaptive-family-field;')) { throw 'F02 field frame must come from the adaptive profile owner' }
|
||||
if (-not $editor.Contains('@include adaptive-family-field;')) { throw 'F06 field frame must come from the adaptive profile owner' }
|
||||
if ($publish -notmatch '(?s)<textarea[^>]*\sauto-height(?:\s|>)') { throw 'F02 long-form textarea must grow with its content' }
|
||||
if ($editor -notmatch '(?s)<textarea[^>]*\sauto-height(?:\s|>)') { throw 'F06 long-form textarea must grow with its content' }
|
||||
if ($publish -match '100%\s+100%\s+no-repeat' -or $editor -match '100%\s+100%\s+no-repeat') { throw 'Family editors must not stretch decorative backgrounds' }
|
||||
|
||||
@@ -3,9 +3,9 @@ $root = Split-Path $PSScriptRoot -Parent
|
||||
$page = [System.IO.File]::ReadAllText((Join-Path $root 'pages/genealogy/g01-my-genealogies.vue'), [System.Text.Encoding]::UTF8)
|
||||
|
||||
foreach ($expected in @(
|
||||
'@include adaptive.adaptive-genealogy-current-slip',
|
||||
'@include adaptive.adaptive-genealogy-list-card',
|
||||
'@include adaptive.adaptive-genealogy-state-panel',
|
||||
'@include adaptive-genealogy-current-slip',
|
||||
'@include adaptive-genealogy-list-card',
|
||||
'@include adaptive-genealogy-state-panel',
|
||||
'background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png") center / contain no-repeat',
|
||||
'grid-template-columns: minmax(0, 1fr) 96rpx'
|
||||
)) {
|
||||
|
||||
@@ -36,7 +36,7 @@ foreach ($required in @(
|
||||
'empty-search-action',
|
||||
'empty-invite-action',
|
||||
'empty-create-note',
|
||||
'@include adaptive.adaptive-genealogy-state-panel;',
|
||||
'@include adaptive-genealogy-state-panel;',
|
||||
'const createGenealogy = () =>',
|
||||
'const hasGenealogies = computed('
|
||||
)) {
|
||||
@@ -63,16 +63,16 @@ Assert-Match -Content $g01 -Pattern '(?s)\.genealogy-empty-state\s*\{[^}]*min-he
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.empty-panel\s*\{[^}]*min-height:\s*1120rpx;' -Message 'G01 empty panel does not retain the approved minimum height'
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.empty-panel__content\s*\{[^}]*justify-content:\s*center;[^}]*padding:\s*50rpx 28rpx 46rpx;' -Message 'G01 empty panel content is not centered with the approved responsive padding'
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.empty-seal\s*\{[^}]*width:\s*120rpx;[^}]*height:\s*184rpx;' -Message 'G01 empty seal does not use the approved prominent size'
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.empty-title\s*\{[^}]*margin-top:\s*30rpx;[^}]*font-size:\s*48rpx;' -Message 'G01 empty title does not use the approved prominent size and spacing'
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.empty-copy\s*\{[^}]*min-height:\s*84rpx;[^}]*margin-top:\s*18rpx;[^}]*font-size:\s*28rpx;' -Message 'G01 empty guidance does not use the approved prominent size and spacing'
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.empty-title\s*\{[^}]*margin-top:\s*30rpx;[^}]*font-size:\s*clamp\(24px, 48rpx, 30px\);' -Message 'G01 empty title does not use the approved prominent size and spacing'
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.empty-copy\s*\{[^}]*min-height:\s*84rpx;[^}]*margin-top:\s*18rpx;[^}]*font-size:\s*clamp\(16px, 28rpx, 20px\);' -Message 'G01 empty guidance does not use the approved prominent size and spacing'
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.empty-search-action,\s*\.empty-invite-action\s*\{[^}]*width:\s*560rpx;[^}]*min-height:\s*124rpx;' -Message 'G01 empty actions do not retain the approved prominent minimum size'
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.empty-search-action__copy,\s*\.empty-invite-action__copy\s*\{[^}]*font-size:\s*34rpx;' -Message 'G01 empty action labels do not use the approved prominent size'
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.empty-search-action__copy,\s*\.empty-invite-action__copy\s*\{[^}]*font-size:\s*clamp\(19px, 34rpx, 24px\);' -Message 'G01 empty action labels do not use the approved prominent size'
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.empty-create-action\s*\{[^}]*min-height:\s*76rpx;[^}]*margin-top:\s*14rpx;' -Message 'G01 empty create action does not use the approved prominent hit area and spacing'
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.empty-create-action__copy\s*\{[^}]*font-size:\s*31rpx;' -Message 'G01 empty create action does not use the approved prominent size'
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.empty-create-action__copy\s*\{[^}]*font-size:\s*clamp\(17px, 31rpx, 22px\);' -Message 'G01 empty create action does not use the approved prominent size'
|
||||
if ($g01 -match '(?s)\.empty-create-action__copy\s*\{[^}]*text-decoration:\s*underline') {
|
||||
throw 'G01 empty create action must not render a text underline'
|
||||
}
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.empty-create-note\s*\{[^}]*font-size:\s*26rpx;[^}]*line-height:\s*38rpx;' -Message 'G01 empty-state create note does not use the approved readable size'
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.empty-create-note\s*\{[^}]*font-size:\s*clamp\(16px, 26rpx, 20px\);[^}]*line-height:\s*max\(1.35em, clamp\(20px, 38rpx, 26px\)\);' -Message 'G01 empty-state create note does not use the approved readable size'
|
||||
if ($g01 -match '(?s)<view v-else class="genealogy-empty-state">.*?<image[^>]+g01-empty-panel\.png') { throw 'G01 empty state must not retain the opaque panel background' }
|
||||
|
||||
foreach ($className in @('empty-panel', 'empty-create-action', 'empty-search-action')) {
|
||||
|
||||
@@ -17,8 +17,8 @@ Assert-Match -Content $g01 -Pattern '(?s)\.state-panel--error\s*\{[^}]*@include\
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.state-retry\s*\{[^}]*background:\s*url\("/static/assets/foundation/transparent/a01-scroll-primary-v3\.png"\)\s*center\s*/\s*contain\s+no-repeat;' -Message 'G01 retry must use the approved scroll skin as a container background'
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.state-panel--error\s*\{[^}]*min-height:\s*1120rpx;' -Message 'G01 error state must retain the approved minimum frame height'
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.error-panel__seal\s*\{[^}]*width:\s*132rpx;[^}]*height:\s*136rpx;' -Message 'G01 error state seal must use the approved prominent size'
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.state-panel--error\s+\.state-title\s*\{[^}]*font-size:\s*42rpx;' -Message 'G01 error title must use the approved hierarchy'
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.state-panel--error\s+\.state-copy\s*\{[^}]*font-size:\s*27rpx;' -Message 'G01 error guidance must use the approved readable size'
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.state-panel--error\s+\.state-title\s*\{[^}]*font-size:\s*clamp\(22px, 42rpx, 28px\);' -Message 'G01 error title must use the approved hierarchy'
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.state-panel--error\s+\.state-copy\s*\{[^}]*font-size:\s*clamp\(16px, 27rpx, 20px\);' -Message 'G01 error guidance must use the approved readable size'
|
||||
Assert-Match -Content $g01 -Pattern '(?s)\.state-panel--error\s+\.state-retry\s*\{[^}]*width:\s*560rpx;[^}]*height:\s*124rpx;' -Message 'G01 retry action must match the approved primary action size'
|
||||
|
||||
if ($errorBlock -match 'modules/genealogy/opaque/g01-empty-panel\.png') { throw 'G01 error state must not retain the legacy opaque panel' }
|
||||
|
||||
@@ -51,7 +51,7 @@ if ($header -notmatch 'height:\s*calc\(124rpx\s*\+\s*var\(--status-bar-height,\s
|
||||
if ($header -notmatch 'padding-top:\s*var\(--status-bar-height,\s*0px\)') { throw 'Header does not reserve the real system status-bar height.' }
|
||||
if ($tabbar -match '/static/icons/tab-') { throw 'Tabbar still references rejected legacy tab assets.' }
|
||||
if ($tabbar -notmatch 'safe-area-inset-bottom') { throw 'Tabbar has no Android safe-area padding.' }
|
||||
if ($tabbar -notmatch 'font-size:\s*30rpx') { throw 'Tabbar label size does not match the approved visual weight.' }
|
||||
if ($tabbar -notmatch 'font-size:\s*clamp\(17px, 30rpx, 22px\)') { throw 'Tabbar label size does not match the approved visual weight.' }
|
||||
if ($tabbar -notmatch 'width:\s*64rpx') { throw 'Tabbar icon size does not match the approved visual weight.' }
|
||||
|
||||
$page = Get-Content -Raw -Encoding UTF8 (Join-Path $root 'pages/genealogy/g01-my-genealogies.vue')
|
||||
@@ -124,7 +124,7 @@ if ($page -match '(?s)\.add-dialog__heading\s*\{[^}]*top\s*:') {
|
||||
if ($page -notmatch '(?s)\.add-dialog__close\s*\{[^}]*grid-column:\s*2;[^}]*grid-row:\s*1\s*/\s*span\s+2;[^}]*margin-right:\s*-22rpx;') {
|
||||
throw 'G-01 add sheet close control does not follow the heading grid.'
|
||||
}
|
||||
if ($page -notmatch 'adaptive\.adaptive-g01-add-sheet' -or $profiles -notmatch [regex]::Escape('/static/assets/modules/genealogy/transparent/g01-add-sheet-background-v3.png')) {
|
||||
if ($page -notmatch '@include\s+adaptive-g01-add-sheet' -or $profiles -notmatch [regex]::Escape('/static/assets/modules/genealogy/transparent/g01-add-sheet-background-v3.png')) {
|
||||
throw 'G-01 add sheet does not use the approved complete background asset.'
|
||||
}
|
||||
if ($profiles -notmatch '(?s)@mixin\s+adaptive-g01-add-sheet\s*\{[^}]*border-image-source:\s*url\("/static/assets/modules/genealogy/transparent/g01-add-sheet-background-v3\.png"\);[^}]*border-image-slice:\s*220\s+0\s+1\s+0\s+fill;[^}]*border-image-width:\s*118rpx\s+0\s+1rpx;') {
|
||||
@@ -152,7 +152,7 @@ if ($page -notmatch '(?s)\.genealogy-switcher-layer\s*\{[^}]*align-items:\s*cent
|
||||
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*\{[^}]*@include\s+adaptive\.adaptive-g01-switcher;[^}]*min-height:\s*600rpx;[^}]*max-height:\s*calc\(100vh\s*-\s*120rpx\);' -or $profiles -notmatch '(?s)@mixin\s+adaptive-g01-switcher\s*\{[^}]*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;') {
|
||||
if ($page -notmatch '(?s)\.genealogy-switcher\s*\{[^}]*@include\s+adaptive-g01-switcher;[^}]*min-height:\s*600rpx;[^}]*max-height:\s*calc\(100vh\s*-\s*120rpx\);' -or $profiles -notmatch '(?s)@mixin\s+adaptive-g01-switcher\s*\{[^}]*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;') {
|
||||
@@ -176,11 +176,11 @@ foreach ($nativeUi in @('uni.showToast', 'uni.showModal', 'uni.showLoading', 'un
|
||||
foreach ($asset in @('shortcut-tree.png', 'shortcut-members.png', 'shortcut-generation-poem.png', 'shortcut-application.png', 'add.png')) {
|
||||
if ($page -notmatch [regex]::Escape($asset)) { throw "G-01 does not consume $asset" }
|
||||
}
|
||||
if ($page -notmatch 'adaptive\.adaptive-genealogy-current-slip' -or $profiles -notmatch 'current-slip-frame\.png') { throw 'G-01 does not consume the approved current genealogy frame profile.' }
|
||||
if ($page -notmatch '@include\s+adaptive-genealogy-current-slip' -or $profiles -notmatch 'current-slip-frame\.png') { throw 'G-01 does not consume the approved current genealogy frame profile.' }
|
||||
foreach ($token in @('current-summary', 'current-meta-item', 'current-seal-frame', 'meta-location.png', 'meta-member.png', 'meta-admin.png', 'create-cloud.png')) {
|
||||
if ($page -notmatch $token) { throw "G-01 current panel is missing $token" }
|
||||
}
|
||||
if ($page -notmatch 'width:\s*82rpx') { throw 'G-01 shortcut icons remain below the reference visual size.' }
|
||||
if ($page -notmatch '(?s)\.shortcut-icon\s*\{[^}]*width:\s*76rpx;[^}]*height:\s*76rpx;') { throw 'G-01 shortcut icons do not keep the compact readable size.' }
|
||||
if ($page -notmatch '<GenealogyPageBackground\s*/>') { throw 'G-01 does not consume the shared genealogy background component.' }
|
||||
if ($page -notmatch 'section-divider\.png') { throw 'G-01 does not consume the approved section divider asset.' }
|
||||
$pageWithoutAddMarkup = $page.Replace($addMarkup, '')
|
||||
@@ -223,26 +223,26 @@ if ($card -match 'calendar-v1\.png') { throw 'G-01 list shows a calendar icon th
|
||||
if ($card -match 'genealogy\.surname') { throw 'G-01 list seal still shows a surname instead of the fixed 家谱 seal.' }
|
||||
if ($card -match 'card-copy') { throw 'G-01 list still retains the superseded split content column.' }
|
||||
if ($card -notmatch '(?s)\.card-title-row\s*\{.*?justify-content:\s*space-between') { throw 'G-01 list title, role, and chevron do not share the required first row.' }
|
||||
if ($page -notmatch '(?s)\.current-switch-copy\s*\{[^}]*font-size:\s*28rpx') { throw 'G-01 switch copy does not use the approved 28rpx size.' }
|
||||
if ($page -notmatch '(?s)\.current-meta\s*\{[^}]*font-size:\s*27rpx') { throw 'G-01 current genealogy metadata must retain its original 27rpx size.' }
|
||||
if ($page -notmatch '(?s)\.current-meta-icon\s*\{[^}]*width:\s*36rpx;[^}]*height:\s*36rpx;[^}]*opacity:\s*1;') { throw 'G-01 current genealogy metadata icons do not use the approved 36rpx size and full opacity.' }
|
||||
if ($page -notmatch '(?s)\.current-switch-copy\s*\{[^}]*border-radius:\s*999rpx;[^}]*font-size:\s*clamp\(15px, 25rpx, 18px\)') { throw 'G-01 switch copy is not a compact visual action.' }
|
||||
if ($page -notmatch '(?s)\.current-meta\s*\{[^}]*display:\s*grid;[^}]*grid-template-columns:\s*minmax\(0, 1fr\)\s+auto;[^}]*font-size:\s*clamp\(15px, 25rpx, 18px\)') { throw 'G-01 current metadata is not split into readable rows.' }
|
||||
if ($page -notmatch '(?s)\.current-meta-item--location\s*\{[^}]*grid-column:\s*1\s*/\s*-1;') { throw 'G-01 location does not have its own metadata row.' }
|
||||
if ($page -notmatch '(?s)\.current-meta-icon\s*\{[^}]*width:\s*34rpx;[^}]*height:\s*34rpx;[^}]*opacity:\s*1;') { throw 'G-01 current genealogy metadata icons do not use the compact readable size.' }
|
||||
if ($page -notmatch '(?s)@media screen and \(max-width:\s*340px\)[^{]*\{.*?\.current-meta-icon\s*\{[^}]*width:\s*32rpx;[^}]*height:\s*32rpx;') { throw 'G-01 compact viewport metadata icons do not preserve the approved 32rpx size.' }
|
||||
if ($card -notmatch '(?s)\.card-meta-icon\s*\{[^}]*width:\s*46rpx;[^}]*height:\s*46rpx;[^}]*flex:\s*0 0 auto;[^}]*opacity:\s*1;[^}]*filter:\s*saturate\(1\.35\) brightness\(0\.82\) contrast\(1\.15\);') { throw 'G-01 list location and member icons do not use the approved balanced size and contrast.' }
|
||||
if ($card -notmatch '(?s)\.card-meta-item\s*\{[^}]*margin-right:\s*0;') { throw 'G-01 list metadata groups still retain the superseded trailing margin.' }
|
||||
if ($card -notmatch '(?s)\.card-metas\s*\{[^}]*flex-wrap:\s*wrap;[^}]*gap:\s*4rpx 18rpx;') { throw 'G-01 list metadata groups must preserve the approved separation while allowing data-driven wrapping.' }
|
||||
if ($card -notmatch '(?s)\.genealogy-card\s*\{[^}]*min-height:\s*178rpx;') { throw 'G-01 list cards do not reserve the approved height for the update-date row.' }
|
||||
if ($card -notmatch '(?s)\.card-detail-row\s*\{[^}]*flex-wrap:\s*wrap;') { throw 'G-01 list detail row does not allow the update date to occupy its own row.' }
|
||||
if ($card -notmatch '(?s)<view class="card-detail-row">\s*<view class="card-updated">.*?</view>\s*<view class="card-metas">') { throw 'G-01 update date must precede metadata in the approved visual and DOM order.' }
|
||||
if ($card -notmatch '(?s)\.card-updated\s*\{[^}]*width:\s*100%;[^}]*justify-content:\s*flex-end;[^}]*margin-top:\s*0;[^}]*margin-left:\s*0;') { throw 'G-01 update date does not use the approved right-aligned second row.' }
|
||||
if ($card -notmatch '(?s)\.card-metas\s*\{[^}]*width:\s*100%;[^}]*margin-top:\s*2rpx;') { throw 'G-01 list metadata does not use the approved third row.' }
|
||||
if ($card -notmatch '(?s)@media screen and \(max-width:\s*340px\)[^{]*\{.*?\.genealogy-card\s*\{[^}]*min-height:\s*148rpx;') { throw 'G-01 compact list cards must retain their original 148rpx height.' }
|
||||
if ($card -notmatch '(?s)\.card-meta\s*\{[^}]*font-size:\s*26rpx') { throw 'G-01 list metadata must retain its original 26rpx size.' }
|
||||
if ($card -notmatch '(?s)\.card-updated\s*\{[^}]*font-size:\s*24rpx') { throw 'G-01 list update date must retain its original 24rpx size.' }
|
||||
if ($card -notmatch '(?s)\.card-role\s*\{[^}]*font-size:\s*26rpx') { throw 'G-01 list role must retain its original 26rpx size.' }
|
||||
if ($card -notmatch '(?s)\.card-meta-icon\s*\{[^}]*width:\s*38rpx;[^}]*height:\s*38rpx;[^}]*flex:\s*0 0 auto;[^}]*opacity:\s*1;[^}]*filter:\s*saturate\(1\.35\) brightness\(0\.82\) contrast\(1\.15\);') { throw 'G-01 list location and member icons do not use the balanced compact size and contrast.' }
|
||||
if ($card -notmatch '(?s)\.genealogy-card\s*\{[^}]*grid-template-columns:\s*68rpx\s+minmax\(0, 1fr\);[^}]*min-height:\s*204rpx;') { throw 'G-01 list cards do not reserve the readable three-row layout.' }
|
||||
if ($card -notmatch '(?s)\.card-detail-row\s*\{[^}]*display:\s*grid;[^}]*grid-template-columns:\s*minmax\(0, 1fr\)\s+auto;') { throw 'G-01 list details are not arranged as a two-column grid.' }
|
||||
if ($card -notmatch '(?s)<view class="card-detail-row">\s*<view class="card-meta-item card-meta-item--location">.*?</view>\s*<view class="card-meta-item card-meta-item--members">.*?</view>\s*<view class="card-trailing">') { throw 'G-01 list card must show location, member count, then role in the readable order.' }
|
||||
if ($card -notmatch '(?s)\.card-meta-item--location\s*\{[^}]*grid-column:\s*1\s*/\s*-1;') { throw 'G-01 list location does not have its own full row.' }
|
||||
if ($card -notmatch '(?s)\.card-trailing\s*\{[^}]*grid-column:\s*2;[^}]*flex-direction:\s*column;') { throw 'G-01 role and update date are not anchored to the member-count row.' }
|
||||
if ($card -notmatch 'v-if="genealogy\.updatedAt"') { throw 'G-01 must not show an empty update-date label.' }
|
||||
if ($card -notmatch '(?s)@media screen and \(max-width:\s*340px\)[^{]*\{.*?\.genealogy-card\s*\{[^}]*min-height:\s*176rpx;') { throw 'G-01 compact list cards do not reserve the readable layout.' }
|
||||
if ($card -notmatch '(?s)\.card-meta\s*\{[^}]*font-size:\s*clamp\(15px, 24rpx, 18px\)') { throw 'G-01 list metadata does not use the approved readable size.' }
|
||||
if ($card -notmatch '(?s)\.card-updated\s*\{[^}]*font-size:\s*clamp\(14px, 22rpx, 17px\)') { throw 'G-01 list update date does not use the secondary readable size.' }
|
||||
if ($card -notmatch '(?s)\.card-role\s*\{[^}]*border-radius:\s*999rpx;[^}]*font-size:\s*clamp\(14px, 23rpx, 17px\)') { throw 'G-01 list role is not presented as a compact status tag.' }
|
||||
if ($page -match '(?s)\.create-action\s*>\s*text\s*\{[^}]*white-space:\s*nowrap') { throw 'G-01 create action text must not use a fixed single-line capacity.' }
|
||||
if ($page -notmatch '(?s)\.application-record\s*\+\s*\.application-record\s*\{[^}]*margin-top:\s*12rpx') { throw 'G-01 adjacent application cards must keep the approved 12rpx separation.' }
|
||||
if ($page -notmatch '(?s)\.application-record__copy\s*\{[^}]*color:\s*#62584c;[^}]*font-size:\s*28rpx;[^}]*font-weight:\s*500;[^}]*line-height:\s*1\.4;') { throw 'G-01 application descriptions do not use the approved readable style.' }
|
||||
if ($page -notmatch '(?s)\.application-record__status\s*\{[^}]*color:\s*#7f4f16;[^}]*font-size:\s*27rpx;[^}]*font-weight:\s*600;') { throw 'G-01 application statuses do not use the approved readable base style.' }
|
||||
if ($page -notmatch '(?s)\.application-record__copy\s*\{[^}]*color:\s*#62584c;[^}]*font-size:\s*clamp\(16px, 28rpx, 20px\);[^}]*font-weight:\s*500;[^}]*line-height:\s*1\.4;') { throw 'G-01 application descriptions do not use the approved readable style.' }
|
||||
if ($page -notmatch '(?s)\.application-record__status\s*\{[^}]*color:\s*#7f4f16;[^}]*font-size:\s*clamp\(16px, 27rpx, 20px\);[^}]*font-weight:\s*600;') { throw 'G-01 application statuses do not use the approved readable base style.' }
|
||||
if ($page -notmatch '(?s)\.application-record__status--rejected\s*\{[^}]*color:\s*#a7160c;') { throw 'G-01 rejected status must preserve the approved semantic red.' }
|
||||
if ($page -notmatch '(?s)\.application-record__status--muted\s*\{[^}]*color:\s*#62584c;') { throw 'G-01 muted status does not use the approved readable color.' }
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ $page = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g03-create-ge
|
||||
$style = [regex]::Match($page, '(?s)<style[^>]*>(.*?)</style>').Groups[1].Value
|
||||
|
||||
foreach ($required in @(
|
||||
'@include adaptive.adaptive-genealogy-state-panel;',
|
||||
'@include adaptive-genealogy-state-panel;',
|
||||
'.create-card',
|
||||
'.field-row',
|
||||
'.region-sheet',
|
||||
|
||||
@@ -11,9 +11,9 @@ function Assert-Match {
|
||||
if ($g03 -match '<text>返回</text>') { throw 'G03 header must use the shared PageHeader back control' }
|
||||
Assert-Match -Content $g03 -Pattern '(?s)<PageHeader\s+title="[^"]+"\s+custom-back\s+@back="backToGenealogies"\s*/>' -Message 'G03 must use PageHeader with its guarded 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)\.create-card__note\s*\{[^}]*font-size:\s*25rpx;' -Message 'G03 guidance copy must use the approved readable size'
|
||||
Assert-Match -Content $g03 -Pattern '(?s)\.access-rule__option\s*\{[^}]*font-size:\s*25rpx;' -Message 'G03 visibility options must use the approved readable size'
|
||||
Assert-Match -Content $g03 -Pattern '(?s)\.field-error,\s*\.submit-error\s*\{[^}]*font-size:\s*24rpx;[^}]*line-height:\s*1\.5;' -Message 'G03 validation errors must use the approved readable size'
|
||||
Assert-Match -Content $g03 -Pattern '(?s)\.create-card__note\s*\{[^}]*font-size:\s*clamp\(15px, 25rpx, 18px\);' -Message 'G03 guidance copy must use the approved readable size'
|
||||
Assert-Match -Content $g03 -Pattern '(?s)\.access-rule__option\s*\{[^}]*font-size:\s*clamp\(15px, 25rpx, 18px\);' -Message 'G03 visibility options must use the approved readable size'
|
||||
Assert-Match -Content $g03 -Pattern '(?s)\.field-error,\s*\.submit-error\s*\{[^}]*font-size:\s*clamp\(15px, 24rpx, 18px\);[^}]*line-height:\s*1\.5;' -Message 'G03 validation errors must use the approved readable size'
|
||||
Assert-Match -Content $g03 -Pattern '(?s)\.create-card\s*\.app-button\s*\{[^}]*margin-top:\s*32rpx;' -Message 'G03 primary action spacing must remain stable'
|
||||
|
||||
Write-Output 'G03-VISUAL-STATES-CONTRACT PASS'
|
||||
|
||||
@@ -13,8 +13,8 @@ if ($positions.Count -ne 0) {
|
||||
}
|
||||
|
||||
foreach ($required in @(
|
||||
'@include adaptive.adaptive-g05-overview-surface;',
|
||||
'@include adaptive.adaptive-genealogy-state-panel;',
|
||||
'@include adaptive-g05-overview-surface;',
|
||||
'@include adaptive-genealogy-state-panel;',
|
||||
'background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png")'
|
||||
)) {
|
||||
if ($page -notmatch [regex]::Escape($required)) {
|
||||
|
||||
@@ -13,9 +13,9 @@ if ($positions.Count -ne 0) {
|
||||
}
|
||||
|
||||
foreach ($required in @(
|
||||
'@include adaptive.adaptive-g06-search-field;',
|
||||
'@include adaptive.adaptive-g06-search-action;',
|
||||
'@include adaptive.adaptive-genealogy-list-card;',
|
||||
'@include adaptive-g06-search-field;',
|
||||
'@include adaptive-g06-search-action;',
|
||||
'@include adaptive-genealogy-list-card;',
|
||||
'background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png")'
|
||||
)) {
|
||||
if ($styleWithoutComments -notmatch [regex]::Escape($required)) {
|
||||
|
||||
@@ -6,8 +6,8 @@ $positions = [regex]::Matches([regex]::Replace($style, '(?s)/\*.*?\*/', ''), '(?
|
||||
if ($positions.Count -ne 0) { throw "G08 join flow must use document flow; found $($positions.Count) position declarations" }
|
||||
foreach ($required in @(
|
||||
'<GenealogyPageBackground />',
|
||||
'@include adaptive.adaptive-genealogy-state-panel;',
|
||||
'@include adaptive.adaptive-genealogy-form-field;',
|
||||
'@include adaptive-genealogy-state-panel;',
|
||||
'@include adaptive-genealogy-form-field;',
|
||||
'appApi.applyToJoin',
|
||||
'createRequestController',
|
||||
'auto-height'
|
||||
|
||||
@@ -3,16 +3,16 @@ $root = Split-Path -Parent $PSScriptRoot
|
||||
$page = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g09-my-applications.vue') -Raw -Encoding utf8
|
||||
|
||||
$rules = @(
|
||||
'(?s)\.application-intro text:last-child\s*\{[^}]*font-size:\s*24rpx;',
|
||||
'(?s)\.application-card__time\s*\{[^}]*font-size:\s*23rpx;',
|
||||
'(?s)\.application-card__relation\s*\{[^}]*font-size:\s*24rpx;',
|
||||
'(?s)\.application-card__status\s*\{[^}]*font-size:\s*25rpx;',
|
||||
'(?s)\.application-card__hint\s*\{[^}]*font-size:\s*23rpx;',
|
||||
'(?s)\.application-card__action\s*\{[^}]*font-size:\s*23rpx;',
|
||||
'(?s)\.application-intro text:last-child\s*\{[^}]*font-size:\s*clamp\(15px, 24rpx, 18px\);',
|
||||
'(?s)\.application-card__time\s*\{[^}]*font-size:\s*clamp\(14px, 23rpx, 17px\);',
|
||||
'(?s)\.application-card__relation\s*\{[^}]*font-size:\s*clamp\(15px, 24rpx, 18px\);',
|
||||
'(?s)\.application-card__status\s*\{[^}]*font-size:\s*clamp\(15px, 25rpx, 18px\);',
|
||||
'(?s)\.application-card__hint\s*\{[^}]*font-size:\s*clamp\(14px, 23rpx, 17px\);',
|
||||
'(?s)\.application-card__action\s*\{[^}]*font-size:\s*clamp\(14px, 23rpx, 17px\);',
|
||||
'(?s)\.application-card__action\s*\{[^}]*white-space:\s*nowrap;',
|
||||
'(?s)\.application-card\s*\{[^}]*@include\s+adaptive\.adaptive-genealogy-list-card;[^}]*min-height:\s*210rpx;[^}]*margin-bottom:\s*12rpx;',
|
||||
'(?s)\.application-card__body\s*\{[^}]*min-height:\s*174rpx;[^}]*gap:\s*8rpx 14rpx;[^}]*padding:\s*26rpx 28rpx 22rpx 40rpx;',
|
||||
'(?s)\.application-state-card__copy text:last-child\s*\{[^}]*font-size:\s*24rpx;'
|
||||
'(?s)\.application-state-card__copy text:last-child\s*\{[^}]*font-size:\s*clamp\(15px, 24rpx, 18px\);'
|
||||
)
|
||||
if ($page -match '(?s)\.application-card\s*\{[^}]*background:\s*url\("/static/assets/modules/genealogy/transparent/list-slip-frame\.png"\)[^}]*100%\s+100%') {
|
||||
throw 'G09 application card must not stretch the complete frame asset to 100% 100%'
|
||||
|
||||
@@ -5,7 +5,7 @@ $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 0) { throw "G09 application list must use document flow; found $($positions.Count) position declarations" }
|
||||
foreach ($required in @(
|
||||
'@include adaptive.adaptive-genealogy-list-card;',
|
||||
'@include adaptive-genealogy-list-card;',
|
||||
'background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png")'
|
||||
)) { if ($page -notmatch [regex]::Escape($required)) { throw "G09 is missing real asset background: $required" } }
|
||||
if ($page -match 'class="application-card__skin"') { throw 'G09 must not retain decorative card image layers' }
|
||||
|
||||
@@ -19,16 +19,16 @@ foreach ($required in @(
|
||||
}
|
||||
|
||||
$rules = @(
|
||||
'(?s)\.review-intro text:nth-child\(2\)\s*\{[^}]*font-size:\s*24rpx;',
|
||||
'(?s)\.application-card__phone\s*\{[^}]*font-size:\s*23rpx;',
|
||||
'(?s)\.application-card__time\s*\{[^}]*font-size:\s*23rpx;',
|
||||
'(?s)\.application-card__relation\s*\{[^}]*font-size:\s*24rpx;',
|
||||
'(?s)\.review-intro text:nth-child\(2\)\s*\{[^}]*font-size:\s*clamp\(15px, 24rpx, 18px\);',
|
||||
'(?s)\.application-card__phone\s*\{[^}]*font-size:\s*clamp\(14px, 23rpx, 17px\);',
|
||||
'(?s)\.application-card__time\s*\{[^}]*font-size:\s*clamp\(14px, 23rpx, 17px\);',
|
||||
'(?s)\.application-card__relation\s*\{[^}]*font-size:\s*clamp\(15px, 24rpx, 18px\);',
|
||||
'(?s)\.application-card\s*\{[^}]*@include\s+adaptive\.adaptive-genealogy-list-card;[^}]*min-height:\s*218rpx;[^}]*margin-bottom:\s*12rpx;',
|
||||
'(?s)\.application-card__body\s*\{[^}]*min-height:\s*182rpx;[^}]*gap:\s*8rpx 0;[^}]*padding:\s*28rpx 28rpx 22rpx 40rpx;',
|
||||
'(?s)\.review-actions \.app-button\s*\{[^}]*width:\s*148rpx;[^}]*min-height:\s*68rpx;',
|
||||
'(?s)\.review-result\s*\{[^}]*@include\s+adaptive\.adaptive-scroll-button\(primary\);[^}]*width:\s*308rpx;[^}]*min-height:\s*68rpx;',
|
||||
'(?s)\.review-state-card\s*\{[^}]*@include\s+adaptive\.adaptive-genealogy-list-card;',
|
||||
'(?s)\.review-state-card__copy text:last-child\s*\{[^}]*font-size:\s*24rpx;'
|
||||
'(?s)\.review-state-card__copy text:last-child\s*\{[^}]*font-size:\s*clamp\(15px, 24rpx, 18px\);'
|
||||
)
|
||||
if ($page -match 'a01-(?:primary|secondary)-button-v2\.png') {
|
||||
throw 'G10 must not render the ultra-wide v2 assets through aspectFit inside narrow action buttons'
|
||||
|
||||
@@ -3,15 +3,15 @@ $root = Split-Path $PSScriptRoot -Parent
|
||||
$page = [System.IO.File]::ReadAllText((Join-Path $root 'pages/genealogy/g10-application-review.vue'), [System.Text.Encoding]::UTF8)
|
||||
|
||||
foreach ($expected in @(
|
||||
'@include adaptive.adaptive-genealogy-list-card',
|
||||
'@include adaptive.adaptive-feedback-toast',
|
||||
'@include adaptive-genealogy-list-card',
|
||||
'@include adaptive-feedback-toast',
|
||||
'background: url("/static/assets/modules/genealogy/transparent/section-divider.png") bottom center / 100% 14rpx no-repeat',
|
||||
'grid-template-columns: auto minmax(0, 1fr) auto',
|
||||
'padding: 28rpx 28rpx 22rpx 40rpx',
|
||||
'<AppButton',
|
||||
'compact-actions',
|
||||
'z-index: 1',
|
||||
'@include adaptive.adaptive-genealogy-form-field'
|
||||
'@include adaptive-genealogy-form-field'
|
||||
)) {
|
||||
if (-not $page.Contains($expected)) { throw "G10 document-flow contract missing: $expected" }
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ if ($positions.Count -ne 1 -or $positions[0].Groups[1].Value.Trim() -ne 'fixed')
|
||||
throw "G11 must retain only its fixed feedback layer; found $($positions.Count) position declarations"
|
||||
}
|
||||
foreach ($required in @(
|
||||
'@include adaptive.adaptive-genealogy-state-panel;',
|
||||
'@include adaptive.adaptive-genealogy-form-field;',
|
||||
'@include adaptive-genealogy-state-panel;',
|
||||
'@include adaptive-genealogy-form-field;',
|
||||
'background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png")',
|
||||
'background: url("/static/assets/foundation/transparent/a01-scroll-secondary-v3.png")'
|
||||
)) { if ($page -notmatch [regex]::Escape($required)) { throw "G11 is missing adaptive asset surface: $required" } }
|
||||
|
||||
@@ -4,21 +4,21 @@ $g11 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g11-genealogy-
|
||||
$g12 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g12-generation-poems.vue') -Raw -Encoding utf8
|
||||
|
||||
foreach ($rule in @(
|
||||
'(?s)\.settings-form__copy,\s*\.settings-result__copy\s*\{[^}]*font-size:\s*24rpx;',
|
||||
'(?s)\.settings-field-error\s*\{[^}]*font-size:\s*24rpx;[^}]*line-height:\s*34rpx;',
|
||||
'(?s)\.visibility-block__hint\s*\{[^}]*font-size:\s*22rpx;',
|
||||
'(?s)\.visibility-option text\s*\{[^}]*font-size:\s*24rpx;'
|
||||
'(?s)\.settings-form__copy,\s*\.settings-result__copy\s*\{[^}]*font-size:\s*clamp\(15px, 24rpx, 18px\);',
|
||||
'(?s)\.settings-field-error\s*\{[^}]*font-size:\s*clamp\(15px, 24rpx, 18px\);[^}]*line-height:\s*max\(1.35em, clamp\(19px, 34rpx, 24px\)\);',
|
||||
'(?s)\.visibility-block__hint\s*\{[^}]*font-size:\s*clamp\(14px, 22rpx, 17px\);',
|
||||
'(?s)\.visibility-option text\s*\{[^}]*font-size:\s*clamp\(15px, 24rpx, 18px\);'
|
||||
)) {
|
||||
if ($g11 -notmatch $rule) { throw "G11 readable visual rule missing: $rule" }
|
||||
}
|
||||
|
||||
foreach ($rule in @(
|
||||
'(?s)\.poem-list__copy\s*\{[^}]*font-size:\s*24rpx;',
|
||||
'(?s)\.poem-row__number\s*\{[^}]*font-size:\s*23rpx;',
|
||||
'(?s)\.poem-row__status\s*\{[^}]*font-size:\s*22rpx;',
|
||||
'(?s)\.poem-field-error\s*\{[^}]*font-size:\s*24rpx;[^}]*line-height:\s*34rpx;',
|
||||
'(?s)\.poem-policy > text\s*\{[^}]*font-size:\s*24rpx;',
|
||||
'(?s)\.poem-preview\s*\{[^}]*font-size:\s*23rpx;'
|
||||
'(?s)\.poem-list__copy\s*\{[^}]*font-size:\s*clamp\(15px, 24rpx, 18px\);',
|
||||
'(?s)\.poem-row__number\s*\{[^}]*font-size:\s*clamp\(14px, 23rpx, 17px\);',
|
||||
'(?s)\.poem-row__status\s*\{[^}]*font-size:\s*clamp\(14px, 22rpx, 17px\);',
|
||||
'(?s)\.poem-field-error\s*\{[^}]*font-size:\s*clamp\(15px, 24rpx, 18px\);[^}]*line-height:\s*max\(1.35em, clamp\(19px, 34rpx, 24px\)\);',
|
||||
'(?s)\.poem-policy > text\s*\{[^}]*font-size:\s*clamp\(15px, 24rpx, 18px\);',
|
||||
'(?s)\.poem-preview\s*\{[^}]*font-size:\s*clamp\(14px, 23rpx, 17px\);'
|
||||
)) {
|
||||
if ($g12 -notmatch $rule) { throw "G12 readable visual rule missing: $rule" }
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ if ($positions.Count -ne 1 -or $positions[0].Groups[1].Value.Trim() -ne 'fixed')
|
||||
throw "G12 must retain only its fixed feedback layer; found $($positions.Count) position declarations"
|
||||
}
|
||||
foreach ($required in @(
|
||||
'@include adaptive.adaptive-genealogy-state-panel;',
|
||||
'@include adaptive.adaptive-genealogy-form-field;',
|
||||
'@include adaptive-genealogy-state-panel;',
|
||||
'@include adaptive-genealogy-form-field;',
|
||||
'background: url("/static/assets/foundation/transparent/a01-scroll-secondary-v3.png")'
|
||||
)) { if ($page -notmatch [regex]::Escape($required)) { throw "G12 is missing adaptive asset surface: $required" } }
|
||||
if ($page -notmatch '(?s)<textarea[^>]*auto-height') { throw 'G12 generation poem field must grow from its text data' }
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
|
||||
function Read-Utf8([string]$relativePath) {
|
||||
return [System.IO.File]::ReadAllText(
|
||||
(Join-Path $root $relativePath),
|
||||
[System.Text.Encoding]::UTF8
|
||||
)
|
||||
}
|
||||
|
||||
$pages = Get-Content -LiteralPath (Join-Path $root 'pages.json') -Raw -Encoding UTF8 |
|
||||
ConvertFrom-Json
|
||||
$responsiveCoverage = Get-Content -LiteralPath (Join-Path $PSScriptRoot 'responsive-layout-coverage.json') -Raw -Encoding UTF8 |
|
||||
ConvertFrom-Json
|
||||
foreach ($page in $pages.pages) {
|
||||
$relativeSource = "$($page.path).vue"
|
||||
$source = Join-Path $root $relativeSource
|
||||
if (-not (Test-Path -LiteralPath $source)) {
|
||||
throw "Registered page has no source file: $($page.path)"
|
||||
}
|
||||
if ($responsiveCoverage.covered -notcontains $relativeSource) {
|
||||
throw "Registered page is missing responsive coverage: $relativeSource"
|
||||
}
|
||||
}
|
||||
|
||||
$global = Read-Utf8 'styles/global.scss'
|
||||
foreach ($rule in @(
|
||||
'(?s)\.app-single-line\s*\{[^}]*overflow:\s*hidden;[^}]*text-overflow:\s*ellipsis;[^}]*white-space:\s*nowrap;',
|
||||
'(?s)\.app-long-value\s*\{[^}]*overflow-wrap:\s*anywhere;'
|
||||
)) {
|
||||
if ($global -notmatch $rule) { throw "Global text-flow rule missing: $rule" }
|
||||
}
|
||||
|
||||
foreach ($component in @(
|
||||
'components/PageHeader.vue',
|
||||
'components/AppButton.vue',
|
||||
'components/AppTabbar.vue',
|
||||
'components/GenealogyCard.vue'
|
||||
)) {
|
||||
if ((Read-Utf8 $component) -notmatch 'app-single-line') {
|
||||
throw "Shared compact-text owner is not using the global rule: $component"
|
||||
}
|
||||
}
|
||||
|
||||
$profile = Read-Utf8 'pages/profile/m01-profile-home.vue'
|
||||
if ($profile -notmatch 'app-long-value') {
|
||||
throw 'Profile email must use the global long-value rule'
|
||||
}
|
||||
|
||||
foreach ($relativePath in @(
|
||||
'pages/genealogy/g03-create-genealogy.vue',
|
||||
'pages/genealogy/g11-genealogy-settings.vue'
|
||||
)) {
|
||||
$source = Read-Utf8 $relativePath
|
||||
if ($source -notmatch '(?s)\.field-row__label\s*\{[^}]*width:\s*184rpx;[^}]*white-space:\s*nowrap;') {
|
||||
throw "Genealogy form labels must retain a four-character single-line column: $relativePath"
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($relativePath in @(
|
||||
'pages/profile/m04-change-password.vue',
|
||||
'pages/profile/m05-change-phone.vue'
|
||||
)) {
|
||||
$source = Read-Utf8 $relativePath
|
||||
if ($source -notmatch '(?s)\.form-row\s*>\s*text\s*\{[^}]*white-space:\s*nowrap;') {
|
||||
throw "Profile form labels must remain single-line: $relativePath"
|
||||
}
|
||||
}
|
||||
|
||||
$phone = Read-Utf8 'pages/profile/m05-change-phone.vue'
|
||||
if ($phone -notmatch 'class="code-action"') {
|
||||
throw 'Phone verification action must own a readable secondary treatment'
|
||||
}
|
||||
if ($phone -notmatch '(?s)\.code-action\s*\{[^}]*background:\s*rgba\(255, 250, 238, 0\.96\);[^}]*white-space:\s*nowrap;') {
|
||||
throw 'Phone verification action must not use the cramped scroll frame'
|
||||
}
|
||||
|
||||
Write-Output 'GLOBAL-TEXT-FLOW-CONTRACT PASS'
|
||||
@@ -63,7 +63,6 @@ foreach ($required in @(
|
||||
}
|
||||
|
||||
foreach ($required in @(
|
||||
'originalMember.personNo ||',
|
||||
'v-model="editForm.generation"',
|
||||
'v-model="editForm.sortOrder"',
|
||||
'sex: editForm.sex',
|
||||
@@ -88,6 +87,7 @@ foreach ($required in @(
|
||||
}
|
||||
|
||||
foreach ($forbidden in @(
|
||||
'<text>人物编号</text>',
|
||||
'v-model="addForm.personNo"',
|
||||
'personNo: addForm.personNo',
|
||||
'v-model="addForm.fatherId"',
|
||||
|
||||
@@ -4,9 +4,9 @@ $m01 = Get-Content -LiteralPath (Join-Path $root 'pages/profile/m01-profile-home
|
||||
|
||||
foreach ($required in @(
|
||||
'/static/assets/foundation/transparent/brand-seal.png',
|
||||
'font-size:\s*25rpx',
|
||||
'font-size:\s*23rpx',
|
||||
'font-size:\s*21rpx'
|
||||
'font-size:\s*clamp\(15px, 25rpx, 18px\)',
|
||||
'font-size:\s*clamp\(14px, 23rpx, 17px\)',
|
||||
'font-size:\s*clamp\(13px, 21rpx, 16px\)'
|
||||
)) {
|
||||
if ($m01 -notmatch $required) { throw "M01 visual contract missing: $required" }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
|
||||
foreach ($pageName in @('t04-add-relative.vue', 't05-edit-member.vue')) {
|
||||
$page = Get-Content -LiteralPath (Join-Path $root "pages/tree/$pageName") -Raw -Encoding UTF8
|
||||
if ($page.Contains('<text>业务用户</text>') -or $page.Contains('appUserBindingHint')) {
|
||||
throw "$pageName must not expose the internal business-user field"
|
||||
}
|
||||
if (-not $page.Contains('bindingMode')) {
|
||||
throw "$pageName must retain the member binding payload contract"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Output 'MEMBER-APP-USER-VISIBILITY-CONTRACT PASS'
|
||||
@@ -0,0 +1,15 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$page = Get-Content -LiteralPath (Join-Path $root 'pages/tree/t05-edit-member.vue') -Raw -Encoding UTF8
|
||||
|
||||
foreach ($forbidden in @('<text>人物编号</text>', '<text>业务用户</text>')) {
|
||||
if ($page.Contains($forbidden)) {
|
||||
throw "Edit-member form must not expose the internal identifier: $forbidden"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $page.Contains('appApi.updatePerson(')) {
|
||||
throw 'Edit-member save contract must remain intact'
|
||||
}
|
||||
|
||||
Write-Output 'MEMBER-INTERNAL-IDENTIFIERS-VISIBILITY-CONTRACT PASS'
|
||||
@@ -14,11 +14,11 @@ const device = process.env.MUMU_DEVICE || "emulator-5554";
|
||||
const outputDir = process.env.MUMU_AUDIT_DIR || join(process.cwd(), "tmp", "mumu-visual-audit");
|
||||
const genealogyId = process.env.MUMU_GENEALOGY_ID || "2081191846772518914";
|
||||
const start = Number(process.env.MUMU_AUDIT_START || 0);
|
||||
const end = Number(process.env.MUMU_AUDIT_END || 52);
|
||||
const end = Number(process.env.MUMU_AUDIT_END || 0);
|
||||
const routes = [
|
||||
["a01", "/pages/auth/a01-entry"], ["a04", "/pages/auth/a04-register"], ["a05", "/pages/auth/a05-reset-password"],
|
||||
["g01", "/pages/genealogy/g01-my-genealogies"], ["g03", "/pages/genealogy/g03-create-genealogy"], ["g05", `/pages/genealogy/g05-genealogy-overview?genealogyId=${genealogyId}`], ["g06", "/pages/genealogy/g06-search-genealogies"], ["g08", `/pages/genealogy/g08-join-application?genealogyId=${genealogyId}`], ["g09", "/pages/genealogy/g09-my-applications"], ["g10", `/pages/genealogy/g10-application-review?genealogyId=${genealogyId}`], ["g11", `/pages/genealogy/g11-genealogy-settings?genealogyId=${genealogyId}`], ["g12", `/pages/genealogy/g12-generation-poems?genealogyId=${genealogyId}`],
|
||||
["t01", `/pages/tree/t01-tree-overview?genealogyId=${genealogyId}`], ["t03", `/pages/tree/t03-member-profile?genealogyId=${genealogyId}`], ["t04", `/pages/tree/t04-add-relative?genealogyId=${genealogyId}`], ["t05", `/pages/tree/t05-edit-member?genealogyId=${genealogyId}`], ["t06", `/pages/tree/t06-edit-relationship?genealogyId=${genealogyId}`], ["t07", `/pages/tree/t07-member-directory?genealogyId=${genealogyId}`], ["t08", `/pages/tree/t08-member-states?genealogyId=${genealogyId}`],
|
||||
["t01", `/pages/tree/t01-tree-overview?genealogyId=${genealogyId}`], ["t02", `/pages/tree/t02-pedigree-overview?genealogyId=${genealogyId}`], ["t03", `/pages/tree/t03-member-profile?genealogyId=${genealogyId}`], ["t04", `/pages/tree/t04-add-relative?genealogyId=${genealogyId}`], ["t05", `/pages/tree/t05-edit-member?genealogyId=${genealogyId}`], ["t06", `/pages/tree/t06-edit-relationship?genealogyId=${genealogyId}`], ["t07", `/pages/tree/t07-member-directory?genealogyId=${genealogyId}`], ["t08", `/pages/tree/t08-member-states?genealogyId=${genealogyId}`],
|
||||
["f01", `/pages/family/f01-family-feed?genealogyId=${genealogyId}`], ["f02", `/pages/family/f02-publish-feed?genealogyId=${genealogyId}`], ["f03", `/pages/family/f03-feed-detail?genealogyId=${genealogyId}&feedId=2081197770778128385`], ["f04", `/pages/family/f04-article-list?genealogyId=${genealogyId}`], ["f05", `/pages/family/f05-article-detail?genealogyId=${genealogyId}&articleId=2081200381052891138`], ["f06", `/pages/family/f06-article-editor?genealogyId=${genealogyId}`], ["f07", `/pages/family/f07-album-list?genealogyId=${genealogyId}`], ["f08", `/pages/family/f08-album-detail?genealogyId=${genealogyId}&albumId=2081202236751392769`], ["f09", `/pages/family/f09-media-upload?genealogyId=${genealogyId}&albumId=2081202236751392769`], ["f10", `/pages/family/f10-video-list?genealogyId=${genealogyId}`],
|
||||
["r01", `/pages/records/r01-people-list?genealogyId=${genealogyId}`], ["r02", `/pages/records/r02-person-detail?genealogyId=${genealogyId}`], ["r03", `/pages/records/r03-gift-list?genealogyId=${genealogyId}`], ["r04", `/pages/records/r04-gift-editor?genealogyId=${genealogyId}`], ["r05", `/pages/records/r05-ritual-list?genealogyId=${genealogyId}`], ["r06", `/pages/records/r06-ritual-detail?genealogyId=${genealogyId}`], ["r07", `/pages/records/r07-ritual-editor?genealogyId=${genealogyId}`], ["r08", `/pages/records/r08-growth-journal?genealogyId=${genealogyId}`], ["r09", `/pages/records/r09-life-events?genealogyId=${genealogyId}`], ["r10", `/pages/records/r10-memo-list?genealogyId=${genealogyId}`], ["r11", `/pages/records/r11-merit-records?genealogyId=${genealogyId}`],
|
||||
["n01", "/pages/notification/n01-message-center"], ["n02", "/pages/notification/n02-message-detail"],
|
||||
@@ -27,6 +27,12 @@ const routes = [
|
||||
|
||||
const run = (...args) => execFileSync(adb, ["-s", device, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
||||
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
const discoverDevtoolsSocket = () => {
|
||||
const unixSockets = run("shell", "cat", "/proc/net/unix");
|
||||
const match = unixSockets.match(/@?(webview_devtools_remote_\d+)/);
|
||||
if (!match) throw new Error("MuMu 中没有可用的 WebView 调试端口");
|
||||
return match[1];
|
||||
};
|
||||
|
||||
async function currentTarget() {
|
||||
const targets = await (await fetch("http://127.0.0.1:9223/json/list")).json();
|
||||
@@ -52,9 +58,9 @@ async function evaluate(expression) {
|
||||
|
||||
async function main() {
|
||||
mkdirSync(outputDir, { recursive: true });
|
||||
run("forward", "tcp:9223", "localabstract:webview_devtools_remote_2728");
|
||||
run("forward", "tcp:9223", `localabstract:${discoverDevtoolsSocket()}`);
|
||||
try {
|
||||
for (const [name, route] of routes.slice(start, end)) {
|
||||
for (const [name, route] of routes.slice(start, end || routes.length)) {
|
||||
await evaluate(`uni.reLaunch({ url: ${JSON.stringify(route)} }); true`);
|
||||
await delay(1800);
|
||||
run("shell", "screencap", "-p", "/sdcard/jiapu-visual-audit.png");
|
||||
|
||||
@@ -7,7 +7,7 @@ $read = [string]([char]0x5DF2) + [char]0x8BFB
|
||||
$markAllRead = [string]([char]0x5168) + [char]0x90E8 + $read
|
||||
$goToReview = [string]([char]0x524D) + [char]0x5F80 + [char]0x5165 + [char]0x8C31 + [char]0x5BA1 + [char]0x6838
|
||||
$required = @(
|
||||
'@include adaptive.adaptive-notification-content;',
|
||||
'@include adaptive-notification-content;',
|
||||
'AppLoading',
|
||||
'AppToast',
|
||||
'notice-state--loading',
|
||||
@@ -45,8 +45,8 @@ if ($source -match '100%\s+100%\s+no-repeat|border-image-slice\s*:') {
|
||||
|
||||
# N01 的可读性与块级操作由本页面基线合同持有,不再依赖浏览器截图状态助手。
|
||||
foreach ($ruleContract in @(
|
||||
@{ Selector = 'notice-card__status'; Expected = 'font-size:\s*21rpx;' }
|
||||
@{ Selector = 'notice-card__summary'; Expected = 'font-size:\s*23rpx;' }
|
||||
@{ Selector = 'notice-card__status'; Expected = 'font-size:\s*clamp\(13px, 21rpx, 16px\);' }
|
||||
@{ Selector = 'notice-card__summary'; Expected = 'font-size:\s*clamp\(14px, 23rpx, 17px\);' }
|
||||
)) {
|
||||
$selector = [regex]::Escape($ruleContract.Selector)
|
||||
if ($source -notmatch "(?s)\.$selector\s*\{[^}]*$($ruleContract.Expected)") {
|
||||
|
||||
@@ -6,8 +6,8 @@ $profiles = [System.IO.File]::ReadAllText((Join-Path $root 'styles/adaptive-fram
|
||||
foreach ($expected in @(
|
||||
'grid-template-columns: minmax(0, 1fr) 126rpx',
|
||||
'class="people-state-card__copy"',
|
||||
'@include adaptive.adaptive-records-field;',
|
||||
'@include adaptive.adaptive-records-person;',
|
||||
'@include adaptive-records-field;',
|
||||
'@include adaptive-records-person;',
|
||||
'min-height: clamp(92px, 190rpx, 108px)',
|
||||
'import { onLoad } from "@dcloudio/uni-app";',
|
||||
'onLoad((query) => {'
|
||||
|
||||
@@ -7,7 +7,7 @@ foreach ($expected in @(
|
||||
'class="person-detail-page"','person-detail-state--','class="person-identity-card"',
|
||||
'class="person-edit-action"','class="person-save-action"','class="person-cancel-action"',
|
||||
'shortFields','longFields','person-field__label','person-long-field__label','const enterEdit = () =>','const savePerson = () =>',
|
||||
'const returnToPeople = () =>','@include adaptive.adaptive-records-person;','@include adaptive.adaptive-records-content;','@include adaptive.adaptive-records-field;',
|
||||
'const returnToPeople = () =>','@include adaptive-records-person;','@include adaptive-records-content;','@include adaptive-records-field;',
|
||||
'ModulePageBackground','PageHeader','AppButton','AppLoading','AppToast'
|
||||
)) { Assert-Contains $page $expected }
|
||||
foreach ($forbidden in @('<ModulePage page-id="r02"','import ModulePage from')) { if ($page.Contains($forbidden)) { throw "R02 retains forbidden dependency: $forbidden" } }
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$sourceFiles = @(
|
||||
(Get-ChildItem -LiteralPath (Join-Path $root 'pages') -Recurse -File -Include *.vue),
|
||||
(Get-ChildItem -LiteralPath (Join-Path $root 'components') -Recurse -File -Include *.vue),
|
||||
(Get-ChildItem -LiteralPath (Join-Path $root 'styles') -Recurse -File -Include *.scss),
|
||||
(Get-Item -LiteralPath (Join-Path $root 'App.vue'))
|
||||
)
|
||||
|
||||
foreach ($file in $sourceFiles) {
|
||||
$content = Get-Content -LiteralPath $file.FullName -Raw -Encoding UTF8
|
||||
if ($content -match 'font-size\s*:\s*\d+rpx') {
|
||||
throw "Fixed rpx font size remains: $($file.FullName)"
|
||||
}
|
||||
if ($content -match 'readable-font|readable-line-height') {
|
||||
throw "Sass typography helper remains: $($file.FullName)"
|
||||
}
|
||||
}
|
||||
|
||||
$allSource = ($sourceFiles | ForEach-Object {
|
||||
Get-Content -LiteralPath $_.FullName -Raw -Encoding UTF8
|
||||
}) -join "`n"
|
||||
if ($allSource -notmatch 'clamp\(\d+px,\s*\d+rpx,\s*\d+px\)') {
|
||||
throw 'Responsive clamp typography rule missing from app source'
|
||||
}
|
||||
|
||||
Write-Output 'READABLE-FONT-CONTRACT PASS'
|
||||
@@ -80,6 +80,7 @@
|
||||
{ "path": "pages/tree/t01-tree-overview.vue", "selector": ".member-node", "property": "fixed-height", "reason": "Fixed-ratio interactive tree node." },
|
||||
{ "path": "pages/tree/t01-tree-overview.vue", "selector": ".member-node--selected", "property": "fixed-height", "reason": "Selected state preserves the fixed-ratio tree node." },
|
||||
{ "path": "pages/tree/t01-tree-overview.vue", "selector": ".member-node__avatar", "property": "fixed-height", "reason": "Fixed-ratio member avatar icon." },
|
||||
{ "path": "pages/tree/t02-pedigree-overview.vue", "selector": ".generation-band__arrow", "property": "fixed-height", "reason": "Fixed-ratio generation navigation icon." },
|
||||
{ "path": "pages/tree/t03-member-profile.vue", "selector": ".member-heading__seal", "property": "fixed-height", "reason": "Fixed-ratio member seal." }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
"pages/records/r10-memo-list.vue",
|
||||
"pages/records/r11-merit-records.vue",
|
||||
"pages/tree/t01-tree-overview.vue",
|
||||
"pages/tree/t02-pedigree-overview.vue",
|
||||
"pages/tree/t03-member-profile.vue",
|
||||
"pages/tree/t04-add-relative.vue",
|
||||
"pages/tree/t05-edit-member.vue",
|
||||
|
||||
@@ -10,12 +10,12 @@ Assert-Contains $g01 'GenealogyPageBackground' 'G01 must use its genealogy backg
|
||||
if($g01 -notmatch '(?s)<PageHeader\s+root\s+notice\s+title="我的家谱"[^>]+@notice="toNotifications"') { throw 'G01 root header must expose the notice action without a back control' }
|
||||
if($pages['pages/family/f01-family-feed.vue'] -notmatch '(?s)<PageHeader\s+root\s+title="家族动态"\s+:action="hasValidContext \? ''发布'' : ''''"\s+@action="toPublish"') { throw 'F01 root header must expose publish only for a valid family context and without a back control' }
|
||||
if($pages['pages/profile/m01-profile-home.vue'] -notmatch '(?s)<PageHeader\s+root\s+title="我的"[^>]+@action="toProfile"') { throw 'M01 root header must expose profile editing without a back control' }
|
||||
foreach($required in @('feed-state--list','feed-state--empty','feed-state--error','@include adaptive.adaptive-family-letter;','openPage("F02"')){Assert-Contains $pages['pages/family/f01-family-feed.vue'] $required "Missing F01 contract: $required"}
|
||||
foreach($required in @('publish-state--form','publish-state--preview','publish-state--error','publish-state--invalid','@include adaptive.adaptive-family-content;','@include adaptive.adaptive-family-field;','AppButton','AppDialog','AppToast')){Assert-Contains $pages['pages/family/f02-publish-feed.vue'] $required "Missing F02 contract: $required"}
|
||||
foreach($required in @('notice-state--loading','notice-state--list','notice-state--empty','notice-state--error','@include adaptive.adaptive-notification-content;','AppButton','AppLoading','AppToast')){Assert-Contains $pages['pages/notification/n01-message-center.vue'] $required "Missing N01 contract: $required"}
|
||||
foreach($required in @('feed-state--list','feed-state--empty','feed-state--error','@include adaptive-family-letter;','openPage("F02"')){Assert-Contains $pages['pages/family/f01-family-feed.vue'] $required "Missing F01 contract: $required"}
|
||||
foreach($required in @('publish-state--form','publish-state--preview','publish-state--error','publish-state--invalid','@include adaptive-family-content;','@include adaptive-family-field;','AppButton','AppDialog','AppToast')){Assert-Contains $pages['pages/family/f02-publish-feed.vue'] $required "Missing F02 contract: $required"}
|
||||
foreach($required in @('notice-state--loading','notice-state--list','notice-state--empty','notice-state--error','@include adaptive-notification-content;','AppButton','AppLoading','AppToast')){Assert-Contains $pages['pages/notification/n01-message-center.vue'] $required "Missing N01 contract: $required"}
|
||||
if($pages['pages/notification/n01-message-center.vue'] -notmatch '(?s)openPage\(\s*"G10",\s*\{ genealogyId: genealogyId\.value \},\s*"N01",?\s*\)'){throw 'N01 review entry must use the registered G10 route with N01 as source'}
|
||||
if($pages['pages/notification/n01-message-center.vue']-match'application-status-card\.png'){throw 'N01 must not reuse the genealogy application card'}
|
||||
foreach($required in @('profile-state--ready','profile-state--error','@include adaptive.adaptive-profile-summary;','brand-seal.png','auth-divider-knot.png','chevron-right.png','AppButton','openPage("M02", {}, "M01")')){Assert-Contains $pages['pages/profile/m01-profile-home.vue'] $required "Missing M01 contract: $required"}
|
||||
foreach($required in @('profile-state--ready','profile-state--error','@include adaptive-profile-summary;','brand-seal.png','auth-divider-knot.png','chevron-right.png','AppButton','openPage("M02", {}, "M01")')){Assert-Contains $pages['pages/profile/m01-profile-home.vue'] $required "Missing M01 contract: $required"}
|
||||
foreach($entry in @($pages['pages/notification/n01-message-center.vue'],$pages['pages/profile/m01-profile-home.vue'])){if($entry-match'/pages/'){throw 'N01 and M01 must not embed raw business page paths'}}
|
||||
foreach($forbidden in @('m01-profile-notice-card.png','m01-profile-menu-frame.png','g03-create-flow-panel.png','g06-search-input-wide.png','application-status-card.png')){if($pages['pages/profile/m01-profile-home.vue']-match[regex]::Escape($forbidden)){throw "M01 must not reuse repeated or opaque business asset: $forbidden"}}
|
||||
Write-Output 'ROOT-PAGES-VISUAL-CONTRACT PASS'
|
||||
|
||||
@@ -34,7 +34,7 @@ foreach ($rule in @(
|
||||
'(?s)\.pedigree-swiper\s*\{[^}]*width:\s*100%;[^}]*height:\s*100%;',
|
||||
'(?s)\.pedigree-sheet\s*\{[^}]*display:\s*grid;[^}]*width:\s*100%;[^}]*grid-template-columns:\s*repeat\(6, minmax\(0, 1fr\)\);',
|
||||
'(?s)\.member-node\s*\{[^}]*grid-template-rows:\s*144rpx 274rpx minmax\(0, 1fr\);',
|
||||
'(?s)\.member-node__name\s*\{[^}]*font-size:\s*34rpx;',
|
||||
'(?s)\.member-node__name\s*\{[^}]*font-size:\s*clamp\(19px, 34rpx, 24px\);',
|
||||
'(?s)\.member-node__copy\s*\{[^}]*writing-mode:\s*vertical-rl;',
|
||||
'(?s)\.generation-band__copy\s*\{[^}]*writing-mode:\s*vertical-rl;'
|
||||
)) {
|
||||
|
||||
@@ -36,7 +36,6 @@ foreach ($asset in @(
|
||||
'static/assets/modules/tree/transparent/t01-action-arrow-v2.png',
|
||||
'static/assets/modules/tree/transparent/t01-action-close-v2.png',
|
||||
'static/assets/modules/tree/transparent/t01-management-marker-v2.png',
|
||||
'static/assets/modules/tree/transparent/t01-member-medallion-v2.png',
|
||||
'static/assets/modules/tree/transparent/t01-relation-marker-v2.png',
|
||||
'static/assets/modules/tree/transparent/t01-relation-button-frame-v4.png',
|
||||
'static/assets/modules/tree/transparent/t01-relation-map-v5.png'
|
||||
@@ -49,6 +48,10 @@ foreach ($asset in @(
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $tree.Contains('import AppAvatar from "@/components/AppAvatar.vue"')) {
|
||||
throw 'T01 member avatars must use the shared AppAvatar component'
|
||||
}
|
||||
|
||||
foreach ($required in @(
|
||||
'const openMemberProfile = (member) =>',
|
||||
'const openMemberDetail = async (member) =>',
|
||||
|
||||
@@ -47,6 +47,8 @@ foreach ($required in @(
|
||||
'v-for="row in generationRows"',
|
||||
':style="generationBandStyle(row)"',
|
||||
'const treeMetrics = computed(',
|
||||
'const NODE_HALF_HEIGHT = 112;',
|
||||
'const GENERATION_GAP = 296;',
|
||||
':style="treeState === ''tree'' ? treeMetricsStyle : undefined"',
|
||||
'const recenterSelectedMember = async () =>',
|
||||
'class="tree-recenter"',
|
||||
@@ -87,17 +89,43 @@ if ($invalidatedGuardIndex -lt 0 -or $contextWriteIndex -le $invalidatedGuardInd
|
||||
if ($page -match '<scroll-view\s+class="tree-stage"') {
|
||||
throw 'T01 outer vertical tree owner must preserve the stage grid instead of wrapping its grid children'
|
||||
}
|
||||
if ($page -notmatch '(?s)\.tree-stage--lineage\s*\{[^}]*display:\s*grid;[^}]*grid-template-columns:\s*144rpx minmax\(0, 1fr\);') {
|
||||
throw 'T01 generation rail must stay beside the tree canvas'
|
||||
if ($page -notmatch '(?s)\.tree-stage--lineage\s*\{[^}]*display:\s*grid;[^}]*grid-template-columns:\s*72rpx minmax\(0, 1fr\);') {
|
||||
throw 'T01 generation rail must use the compact rail beside the tree canvas'
|
||||
}
|
||||
if ($page -notmatch '(?s)\.generation-band__copy text\s*\{[^}]*writing-mode:\s*vertical-rl;') {
|
||||
throw 'T01 generation labels must use vertical text in the compact rail'
|
||||
}
|
||||
if ($page -notmatch '(?s)\.generation-band__copy text\s*\{[^}]*text-orientation:\s*upright;') {
|
||||
throw 'T01 generation numerals must stay upright in the vertical labels'
|
||||
}
|
||||
if ($page -notmatch 'class="generation-band__line"') {
|
||||
throw 'T01 generation divider must use the vertical orientation'
|
||||
}
|
||||
if ($page -notmatch '(?s)\.generation-band__line\s*\{[^}]*border-left:\s*1rpx solid') {
|
||||
throw 'T01 generation divider must be a vertical line between the labels'
|
||||
}
|
||||
if ($page -notmatch '(?s)\.generation-band\s*\{[^}]*height:\s*104rpx;') {
|
||||
throw 'T01 generation labels must have enough height for the readable text size'
|
||||
}
|
||||
if ($page -notmatch 'row\.y - NODE_HALF_HEIGHT.*span 21') {
|
||||
throw 'T01 generation labels must align to the readable member-node height'
|
||||
}
|
||||
if ($page -notmatch '(?s)\.member-node\s*\{[^}]*position:\s*relative;') {
|
||||
throw 'T01 graph node must retain its positioned tree layout'
|
||||
}
|
||||
if ($page -notmatch '(?s)\.member-node\s*\{[^}]*height:\s*224rpx;') {
|
||||
throw 'T01 graph nodes must have room for the readable text size'
|
||||
}
|
||||
if ($pedigree -match 'scroll-x|treeScrollLeft|changeGeneration =') {
|
||||
throw 'T02 must leave horizontal gestures to the native swiper, not an outer scroll or click pager'
|
||||
}
|
||||
if ($page -notmatch 'openPage\(\s*"T02"') {
|
||||
throw 'T01 must expose the pedigree page from the tree header'
|
||||
}
|
||||
if ($page -notmatch 'member\.treeYears\s*\|\|\s*member\.years' -or
|
||||
$pedigree -notmatch 'member\.treeYears\s*\|\|\s*member\.years' -or
|
||||
$pedigree -notmatch 'text-combine-upright:\s*all') {
|
||||
throw 'Tree readers must use compact year ranges instead of clipping full dates in narrow columns'
|
||||
}
|
||||
|
||||
Write-Output 'T01-TREE-STATE-CONTRACT PASS'
|
||||
|
||||
@@ -4,10 +4,10 @@ $page = Get-Content -LiteralPath (Join-Path $root 'pages/tree/t03-member-profile
|
||||
$style = [regex]::Match($page, '(?s)<style[^>]*>(.*?)</style>').Groups[1].Value
|
||||
if ([regex]::IsMatch($style, '(?m)\bposition\s*:')) { throw 'T03 member profile must use document flow throughout' }
|
||||
foreach ($required in @(
|
||||
'@include adaptive.adaptive-tree-panel;',
|
||||
'@include adaptive.adaptive-tree-field;',
|
||||
'@include adaptive.adaptive-genealogy-list-card;',
|
||||
'@include adaptive.adaptive-scroll-button(primary);',
|
||||
'@include adaptive-tree-panel;',
|
||||
'@include adaptive-tree-field;',
|
||||
'@include adaptive-genealogy-list-card;',
|
||||
'@include adaptive-scroll-button(primary);',
|
||||
'background: url("/static/assets/modules/genealogy/transparent/current-seal-frame.png")'
|
||||
)) { if ($page -notmatch [regex]::Escape($required)) { throw "T03 is missing adaptive asset surface: $required" } }
|
||||
$memberPanelStyle = [regex]::Match($style, '(?ms)^\s*\.member-panel\s*\{(?<body>.*?)\}').Groups['body'].Value
|
||||
|
||||
@@ -8,16 +8,16 @@ $pages = @{
|
||||
}
|
||||
|
||||
foreach ($rule in @(
|
||||
'(?s)\.member-heading > view:last-child text:last-child\s*\{[^}]*font-size:\s*23rpx;',
|
||||
'(?s)\.member-section-title\s*\{[^}]*font-size:\s*24rpx;',
|
||||
'(?s)\.member-info-row text\s*\{[^}]*font-size:\s*23rpx;'
|
||||
'(?s)\.member-heading > view:last-child text:last-child\s*\{[^}]*font-size:\s*clamp\(14px, 23rpx, 17px\);',
|
||||
'(?s)\.member-section-title\s*\{[^}]*font-size:\s*clamp\(15px, 24rpx, 18px\);',
|
||||
'(?s)\.member-info-row text\s*\{[^}]*font-size:\s*clamp\(14px, 23rpx, 17px\);'
|
||||
)) {
|
||||
if ($pages.T03 -notmatch $rule) { throw "T03 readable visual rule missing: $rule" }
|
||||
}
|
||||
|
||||
foreach ($key in @('T04', 'T05', 'T06')) {
|
||||
$content = $pages[$key]
|
||||
foreach ($token in @('@include adaptive.adaptive-tree-panel;', '@include adaptive.adaptive-tree-field;', 'grid-template-columns: auto minmax(0, 1fr)')) {
|
||||
foreach ($token in @('@include adaptive-tree-panel;', '@include adaptive-tree-field;', 'grid-template-columns: auto minmax(0, 1fr)')) {
|
||||
if (-not $content.Contains($token)) { throw "$key active form layout token missing: $token" }
|
||||
}
|
||||
if ($content -match '(?m)^\s*\.rank-form\s*\{[^}]*\bposition\s*:|(?m)^\s*\.add-relative-form\s*\{[^}]*\bposition\s*:|(?m)^\s*\.edit-member-form\s*\{[^}]*\bposition\s*:') {
|
||||
@@ -25,6 +25,6 @@ foreach ($key in @('T04', 'T05', 'T06')) {
|
||||
}
|
||||
}
|
||||
|
||||
if ($pages.T06 -notmatch '(?s)\.form-copy\s*\{[^}]*font-size:\s*24rpx;') { throw 'T06 rank copy must remain readable' }
|
||||
if ($pages.T06 -notmatch '(?s)\.form-copy\s*\{[^}]*font-size:\s*clamp\(15px, 24rpx, 18px\);') { throw 'T06 rank copy must remain readable' }
|
||||
|
||||
Write-Output 'T03-T06-ALL-STATES-VISUAL-CONTRACT PASS'
|
||||
|
||||
@@ -4,21 +4,21 @@ $t07 = Get-Content -LiteralPath (Join-Path $root 'pages/tree/t07-member-director
|
||||
$t08 = Get-Content -LiteralPath (Join-Path $root 'pages/tree/t08-member-states.vue') -Raw -Encoding utf8
|
||||
|
||||
foreach ($rule in @(
|
||||
'(?s)\.directory-search input\s*\{[^}]*font-size:\s*26rpx;',
|
||||
'(?s)\.directory-summary text:last-child\s*\{[^}]*font-size:\s*23rpx;',
|
||||
'(?s)\.directory-card__meta\s*\{[^}]*font-size:\s*24rpx;',
|
||||
'(?s)\.directory-card__status\s*\{[^}]*font-size:\s*22rpx;',
|
||||
'(?s)\.directory-state-card text:last-child\s*\{[^}]*font-size:\s*24rpx;'
|
||||
'(?s)\.directory-search input\s*\{[^}]*font-size:\s*clamp\(16px, 26rpx, 20px\);',
|
||||
'(?s)\.directory-summary text:last-child\s*\{[^}]*font-size:\s*clamp\(14px, 23rpx, 17px\);',
|
||||
'(?s)\.directory-card__meta\s*\{[^}]*font-size:\s*clamp\(15px, 24rpx, 18px\);',
|
||||
'(?s)\.directory-card__status\s*\{[^}]*font-size:\s*clamp\(14px, 22rpx, 17px\);',
|
||||
'(?s)\.directory-state-card text:last-child\s*\{[^}]*font-size:\s*clamp\(15px, 24rpx, 18px\);'
|
||||
)) {
|
||||
if ($t07 -notmatch $rule) { throw "T07 readable visual rule missing: $rule" }
|
||||
}
|
||||
|
||||
foreach ($rule in @(
|
||||
'(?s)\.member-status-context text\s*\{[^}]*font-size:\s*23rpx;',
|
||||
'(?s)\.status-card__copy text:first-child\s*\{[^}]*font-size:\s*22rpx;',
|
||||
'(?s)\.status-card__copy text:nth-child\(2\)\s*\{[^}]*font-size:\s*32rpx;',
|
||||
'(?s)\.status-card__copy text:last-child\s*\{[^}]*font-size:\s*24rpx;',
|
||||
'(?s)\.status-guidance text\s*\{[^}]*font-size:\s*24rpx;'
|
||||
'(?s)\.member-status-context text\s*\{[^}]*font-size:\s*clamp\(14px, 23rpx, 17px\);',
|
||||
'(?s)\.status-card__copy text:first-child\s*\{[^}]*font-size:\s*clamp\(14px, 22rpx, 17px\);',
|
||||
'(?s)\.status-card__copy text:nth-child\(2\)\s*\{[^}]*font-size:\s*clamp\(17px, 32rpx, 22px\);',
|
||||
'(?s)\.status-card__copy text:last-child\s*\{[^}]*font-size:\s*clamp\(15px, 24rpx, 18px\);',
|
||||
'(?s)\.status-guidance text\s*\{[^}]*font-size:\s*clamp\(15px, 24rpx, 18px\);'
|
||||
)) {
|
||||
if ($t08 -notmatch $rule) { throw "T08 readable visual rule missing: $rule" }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user