设计完成30%

This commit is contained in:
2026-07-14 07:01:32 +08:00
parent 355f47976a
commit 1015e60ff7
95 changed files with 1991 additions and 349 deletions
+44 -7
View File
@@ -89,13 +89,13 @@ foreach ($requiredVisualAnchor in @(
'margin-top: 141rpx',
'top: 70rpx',
'width: 64rpx',
'inset: 6rpx',
'entry-button__content',
'translateX(-20rpx)',
'auth-button-corner.png',
'auth-divider-knot.png',
'auth-title-cloud.png',
'button-corner-asset',
'a01-primary-button.png',
'a01-secondary-button.png',
'entry-button__skin',
'align-items: center',
'white-space: nowrap',
'font-size: 20rpx',
@@ -104,7 +104,7 @@ foreach ($requiredVisualAnchor in @(
)) {
Assert-Contains -Content $entry -Expected $requiredVisualAnchor -Message "Missing A-01 calibrated visual anchor: $requiredVisualAnchor"
}
foreach ($rejectedVisualConstruction in @('link-line', 'button-corner--legacy', 'class="auth-button primary-button"')) {
foreach ($rejectedVisualConstruction in @('link-line', 'button-corner--legacy', 'class="auth-button primary-button"', 'auth-button-corner.png', 'button-corner-asset', '.entry-button::after')) {
Assert-NotContains -Content $entry -Unexpected $rejectedVisualConstruction -Message "A-01 retains rejected visual construction: $rejectedVisualConstruction"
}
foreach ($forbiddenClass in @('entryGuideVisual', 'guide-art', 'entry-hit', 'width: 750rpx', 'min-height: 1625rpx')) {
@@ -113,12 +113,18 @@ foreach ($forbiddenClass in @('entryGuideVisual', 'guide-art', 'entry-hit', 'wid
Assert-NotContains -Content $entry -Unexpected '/static/assets/icons/action/create-cloud-v2.png' -Message 'A-01 must not reuse the mismatched create-cloud asset for its title or divider ornaments'
Assert-Contains -Content $entry -Expected 'filter: brightness(0) invert(1)' -Message 'A-01 primary login icon must render in white'
Assert-NotContains -Content $entry -Unexpected $dividerGlyph -Message 'A-01 divider must not use a text glyph as ornament'
if (([regex]::Matches($entry, 'button-corner-asset button-corner-asset--')).Count -ne 8) {
throw 'A-01 must render the generated ornamental corner asset in all four corners of both entry buttons'
if (([regex]::Matches($entry, 'class="entry-button__skin"')).Count -ne 2) {
throw 'A-01 must render one complete opaque skin image for each entry button'
}
if (([regex]::Matches($entry, 'auth-divider-knot\.png')).Count -ne 2) {
throw 'A-01 must render the dedicated divider-knot asset above and below the subtitle'
}
if ($entry -match '(?s)\.entry-button\s*\{[^}]*\bborder\s*:') {
throw 'A-01 entry buttons must not draw their final border with CSS'
}
if ($entry -match '(?s)\.entry-button--(?:primary|secondary)\s*\{[^}]*\bbackground\s*:') {
throw 'A-01 entry buttons must not draw their final surface with CSS'
}
if ($entry -notmatch '(?s)\.agreement-row\s*\{[^}]*align-items:\s*center') {
throw 'A-01 agreement checkbox and copy must align on one horizontal center line'
}
@@ -129,9 +135,40 @@ if ($entry -match '(?s)\.auth-page\s*\{[^}]*overflow\s*:\s*hidden') {
throw 'A-01 must allow vertical scrolling on small screens'
}
foreach ($requiredLoginCopy in @($accountPasswordLogin, $smsLogin, $forgotPassword, $getCode, $loginPending, $returnToEntry, $forgotPasswordPending, $getCodePending)) {
foreach ($requiredLoginCopy in @($accountPasswordLogin, $smsLogin, $forgotPassword, $getCode, $loginPending, $returnToEntry, $getCodePending)) {
Assert-Contains -Content $login -Expected $requiredLoginCopy -Message "Missing A-02 copy: $requiredLoginCopy"
}
Assert-Contains -Content $login -Expected "url: '/pages/auth/a05-reset-password'" -Message 'A-02 forgot-password entry must navigate to A-05'
foreach ($requiredLoginVisualAnchor in @(
'auth-title-cloud.png',
'login-title-cloud',
'a02-login-panel.png',
'login-panel__skin',
'a01-primary-button.png',
'login-submit__skin',
'login-submit__content'
)) {
Assert-Contains -Content $login -Expected $requiredLoginVisualAnchor -Message "Missing A-02 calibrated visual anchor: $requiredLoginVisualAnchor"
}
if (([regex]::Matches($login, 'class="login-submit__skin"')).Count -ne 1) {
throw 'A-02 must render one complete opaque skin image for its login button'
}
if (([regex]::Matches($login, 'class="login-panel__skin"')).Count -ne 1) {
throw 'A-02 must render one complete opaque skin image for its login panel'
}
if ($login -match '(?s)\.login-panel\s*\{[^}]*\bborder\s*:') {
throw 'A-02 login panel must not draw its final frame with CSS'
}
if ($login -match '(?s)\.login-panel\s*\{[^}]*\bbackground\s*:') {
throw 'A-02 login panel must not draw its final paper surface with CSS'
}
if ($login -match '(?s)\.login-submit\s*\{[^}]*\bborder\s*:') {
throw 'A-02 login button must not draw its final border with CSS'
}
if ($login -match '(?s)\.login-submit\s*\{[^}]*\bbackground\s*:') {
throw 'A-02 login button must not draw its final surface with CSS'
}
if ($login -match '(?s)\.auth-page\s*\{[^}]*overflow\s*:\s*hidden') {
throw 'A-02 must allow vertical scrolling on small screens'
+31 -3
View File
@@ -11,12 +11,15 @@ $backgrounds = @(
$icons = @(
'static/assets/foundation/transparent/auth-login-outline.png',
'static/assets/foundation/transparent/auth-wechat.png',
'static/assets/foundation/transparent/auth-button-corner.png',
'static/assets/foundation/transparent/auth-divider-knot.png',
'static/assets/foundation/transparent/auth-title-cloud.png'
)
$buttons = @(
'static/assets/foundation/opaque/a01-primary-button.png',
'static/assets/foundation/opaque/a01-secondary-button.png'
)
foreach ($asset in $backgrounds + $icons) {
foreach ($asset in $backgrounds + $icons + $buttons) {
if (-not (Test-Path -LiteralPath (Join-Path $root $asset))) {
throw "Missing A01 asset: $asset"
}
@@ -41,7 +44,6 @@ foreach ($asset in $icons) {
$bitmap.Dispose()
$expectedSize = switch ($asset) {
'static/assets/foundation/transparent/auth-button-corner.png' { @(160, 160) }
'static/assets/foundation/transparent/auth-divider-knot.png' { @(160, 96) }
'static/assets/foundation/transparent/auth-title-cloud.png' { @(200, 120) }
default { @(96, 96) }
@@ -52,4 +54,30 @@ foreach ($asset in $icons) {
Write-Output "PASS $asset transparent corners and visible artwork"
}
foreach ($asset in $buttons) {
$bitmap = [System.Drawing.Bitmap]::FromFile((Join-Path $root $asset))
$width = [int]$bitmap.Width
$height = [int]$bitmap.Height
$corners = @(
$bitmap.GetPixel(0, 0).A,
$bitmap.GetPixel(($width - 1), 0).A,
$bitmap.GetPixel(0, ($height - 1)).A,
$bitmap.GetPixel(($width - 1), ($height - 1)).A
)
$chromaPixels = 0
for ($y = 0; $y -lt $height; $y++) {
for ($x = 0; $x -lt $width; $x++) {
$pixel = $bitmap.GetPixel($x, $y)
if ($pixel.G - [Math]::Max($pixel.R, $pixel.B) -gt 80 -and $pixel.G -gt 120) { $chromaPixels++ }
}
}
$bitmap.Dispose()
$ratio = [double]$width / $height
if ([Math]::Abs($ratio - 4.492) -gt 0.01) { throw "$asset must keep the approved 4.492:1 button ratio." }
if (($corners | Where-Object { $_ -ne 255 }).Count -ne 0) { throw "$asset must have opaque outer corners." }
if ($chromaPixels -ne 0) { throw "$asset retains chroma-key green pixels." }
Write-Output "PASS $asset opaque corners and approved button ratio"
}
Write-Output 'A01-ASSET-ALPHA-AUDIT PASS'
+27
View File
@@ -0,0 +1,27 @@
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$asset = Join-Path $root 'static/assets/modules/auth/opaque/a02-login-panel.png'
if (-not (Test-Path -LiteralPath $asset)) {
throw 'Missing A02 login-panel asset'
}
Add-Type -AssemblyName System.Drawing
$bitmap = [System.Drawing.Bitmap]::FromFile($asset)
$width = [int]$bitmap.Width
$height = [int]$bitmap.Height
$corners = @(
$bitmap.GetPixel(0, 0).A,
$bitmap.GetPixel(($width - 1), 0).A,
$bitmap.GetPixel(0, ($height - 1)).A,
$bitmap.GetPixel(($width - 1), ($height - 1)).A
)
$bitmap.Dispose()
$ratio = [double]$width / $height
if ($width -ne 940 -or $height -ne 1672) { throw 'A02 login-panel asset must remain 940 x 1672px.' }
if ([Math]::Abs($ratio - 0.5622) -gt 0.001) { throw 'A02 login-panel asset must retain its approved panel ratio.' }
if (($corners | Where-Object { $_ -ne 255 }).Count -ne 0) { throw 'A02 login-panel asset must have opaque outer corners.' }
Write-Output 'A02-ASSET-ALPHA-AUDIT PASS'
+3 -3
View File
@@ -1,7 +1,7 @@
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$requiredFiles = @('utils/config.js', 'utils/session.js', 'utils/genealogy-context.js', 'utils/api.js', 'pages/genealogy/g04-first-ancestor.vue', 'pages/skeleton/content-list-skeleton.vue', 'pages/family/f02-publish-feed.vue')
$requiredFiles = @('utils/config.js', 'utils/session.js', 'utils/genealogy-context.js', 'utils/api.js', 'pages/genealogy/g04-first-ancestor.vue', 'pages/family/f04-article-list.vue', 'pages/family/f02-publish-feed.vue')
foreach ($file in $requiredFiles) {
if (-not (Test-Path (Join-Path $root $file))) {
throw "Missing core-flow file: $file"
@@ -49,7 +49,7 @@ $pages = Get-Content -Raw -Encoding UTF8 (Join-Path $root 'pages.json')
if ($pages -notmatch 'pages/genealogy/g04-first-ancestor') {
throw 'Missing first-person page route'
}
foreach ($route in @('pages/skeleton/content-list-skeleton', 'pages/family/f02-publish-feed')) {
foreach ($route in @('pages/family/f04-article-list', 'pages/family/f02-publish-feed')) {
if ($pages -notmatch [regex]::Escape($route)) {
throw "Missing content page route: $route"
}
@@ -91,7 +91,7 @@ foreach ($method in @('markNotificationRead', 'markAllNotificationsRead')) {
}
$family = Get-Content -Raw -Encoding UTF8 (Join-Path $root 'pages/family/f01-family-feed.vue')
foreach ($token in @('genealogyContext', 'openSection', 'pages/skeleton/content-list-skeleton', 'pages/family/f02-publish-feed')) {
foreach ($token in @('genealogyContext', 'openSection', 'pages/family/f04-article-list', 'pages/family/f07-album-list', 'pages/records/r05-ritual-list', 'pages/records/r10-memo-list', 'pages/family/f02-publish-feed')) {
if ($family -notmatch [regex]::Escape($token)) {
throw "Missing family content flow: $token"
}
-1
View File
@@ -49,7 +49,6 @@ Test-ExpectedPage -ExpectedPath 'pages/tree/t01-tree-overview' -PageId 'T-01'
Test-ExpectedPage -ExpectedPath 'pages/tree/t03-member-profile' -PageId 'T-03'
Test-ExpectedPage -ExpectedPath 'pages/family/f01-family-feed' -PageId 'F-01'
Test-ExpectedPage -ExpectedPath 'pages/family/f02-publish-feed' -PageId 'F-02'
Test-ExpectedPage -ExpectedPath 'pages/skeleton/content-list-skeleton' -PageId 'S-01'
Test-ExpectedPage -ExpectedPath 'pages/notification/n01-message-center' -PageId 'N-01'
Test-ExpectedPage -ExpectedPath 'pages/profile/m01-profile-home' -PageId 'M-01'
+41
View File
@@ -0,0 +1,41 @@
$ErrorActionPreference = 'Stop'
function ConvertFrom-Utf8Base64 {
param([string]$Value)
return [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Value))
}
$root = Split-Path -Parent $PSScriptRoot
$pagesPath = Join-Path $root 'pages.json'
$pageHeader = ConvertFrom-Utf8Base64 '6aG16Z2i57yW5Y+377ya'
$expectedPages = @(
'pages/auth/a01-entry', 'pages/auth/a02-login', 'pages/auth/a03-mobile-verify', 'pages/auth/a04-register', 'pages/auth/a05-reset-password', 'pages/auth/a06-auth-status',
'pages/genealogy/g01-my-genealogies', 'pages/genealogy/g02-empty-genealogies', 'pages/genealogy/g03-create-genealogy', 'pages/genealogy/g04-first-ancestor', 'pages/genealogy/g05-genealogy-overview', 'pages/genealogy/g06-search-genealogies', 'pages/genealogy/g07-search-result', 'pages/genealogy/g08-join-application', 'pages/genealogy/g09-my-applications', 'pages/genealogy/g10-application-review', 'pages/genealogy/g11-genealogy-settings', 'pages/genealogy/g12-generation-poems',
'pages/tree/t01-tree-overview', 'pages/tree/t02-tree-states', 'pages/tree/t03-member-profile', 'pages/tree/t04-add-relative', 'pages/tree/t05-edit-member', 'pages/tree/t06-edit-relationship', 'pages/tree/t07-member-directory', 'pages/tree/t08-member-states',
'pages/family/f01-family-feed', 'pages/family/f02-publish-feed', 'pages/family/f03-feed-detail', 'pages/family/f04-article-list', 'pages/family/f05-article-detail', 'pages/family/f06-article-editor', 'pages/family/f07-album-list', 'pages/family/f08-album-detail', 'pages/family/f09-media-upload', 'pages/family/f10-video-list',
'pages/records/r01-people-list', 'pages/records/r02-person-detail', 'pages/records/r03-gift-list', 'pages/records/r04-gift-editor', 'pages/records/r05-ritual-list', 'pages/records/r06-ritual-detail', 'pages/records/r07-ritual-editor', 'pages/records/r08-growth-journal', 'pages/records/r09-life-events', 'pages/records/r10-memo-list', 'pages/records/r11-merit-records',
'pages/notification/n01-message-center', 'pages/notification/n02-message-detail',
'pages/profile/m01-profile-home', 'pages/profile/m02-edit-profile', 'pages/profile/m03-security-settings', 'pages/profile/m04-change-password', 'pages/profile/m05-change-phone', 'pages/profile/m06-help-center', 'pages/profile/m07-feedback', 'pages/profile/m08-promotion', 'pages/profile/m09-vip-orders', 'pages/profile/m10-about-settings'
)
$pages = Get-Content -LiteralPath $pagesPath -Raw -Encoding utf8 | ConvertFrom-Json
$actualPaths = @($pages.pages.path)
if ($actualPaths.Count -ne 59) {
throw "Expected 59 final visual routes, found $($actualPaths.Count)."
}
if (Compare-Object -ReferenceObject $expectedPages -DifferenceObject $actualPaths) {
throw 'pages.json routes do not match the 59-page visual delivery contract.'
}
foreach ($page in $expectedPages) {
$pageFile = Join-Path $root ($page + '.vue')
if (-not (Test-Path -LiteralPath $pageFile)) {
throw "Missing planned page file: $page.vue"
}
$content = Get-Content -LiteralPath $pageFile -Raw -Encoding utf8
if ($content -notmatch [regex]::Escape($pageHeader)) {
throw "Missing Chinese page header: $page.vue"
}
}
Write-Output 'FULL-PAGE-VISUAL-CONTRACT PASS'
+31
View File
@@ -0,0 +1,31 @@
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$componentPath = Join-Path $root 'components/ModulePage.vue'
if (-not (Test-Path -LiteralPath $componentPath)) {
throw 'Missing shared module page component'
}
$component = Get-Content -LiteralPath $componentPath -Raw -Encoding utf8
foreach ($anchor in @(
'a01-primary-button.png',
'background-image:',
'background-size: 100% 100%',
'.primary-action--compact'
)) {
if ($component -notmatch [regex]::Escape($anchor)) {
throw "ModulePage is missing primary-action visual anchor: $anchor"
}
}
if ($component -match '(?s)\.primary-action\s*\{[^}]*\bborder\s*:') {
throw 'ModulePage primary actions must not redraw the final button border with CSS'
}
if ($component -match '(?s)\.primary-action\s*\{[^}]*\bbackground\s*:\s*\$brand-red') {
throw 'ModulePage primary actions must not redraw the final button surface with a plain CSS color'
}
Write-Output 'MODULE-PAGE-VISUAL-CONTRACT PASS'
-2
View File
@@ -25,7 +25,6 @@ $requiredFiles = @(
'pages/tree/t03-member-profile.vue',
'pages/family/f01-family-feed.vue',
'pages/family/f02-publish-feed.vue',
'pages/skeleton/content-list-skeleton.vue',
'pages/profile/m01-profile-home.vue',
'pages/notification/n01-message-center.vue',
'static/icons/tab-genealogy.png',
@@ -53,7 +52,6 @@ $requiredRoutes = @(
'pages/tree/t03-member-profile',
'pages/family/f01-family-feed',
'pages/family/f02-publish-feed',
'pages/skeleton/content-list-skeleton',
'pages/profile/m01-profile-home',
'pages/notification/n01-message-center'
)