$ErrorActionPreference = 'Stop' function ConvertFrom-Utf8Base64 { param([string]$Value) return [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Value)) } function Assert-Contains { param( [string]$Content, [string]$Expected, [string]$Message ) if ($Content -notmatch [regex]::Escape($Expected)) { throw $Message } } function Assert-NotContains { param( [string]$Content, [string]$Unexpected, [string]$Message ) if ($Content -match [regex]::Escape($Unexpected)) { throw $Message } } $root = Split-Path -Parent $PSScriptRoot $entryPath = Join-Path $root 'pages/auth/a01-entry.vue' $loginPath = Join-Path $root 'pages/auth/a02-login.vue' $pagesPath = Join-Path $root 'pages.json' if (-not (Test-Path -LiteralPath $entryPath)) { throw 'Missing A-01 entry page: pages/auth/a01-entry.vue' } $pages = Get-Content -LiteralPath $pagesPath -Raw -Encoding utf8 | ConvertFrom-Json if ($pages.pages[0].path -ne 'pages/auth/a01-entry') { throw 'A-01 entry page must be the first route in pages.json' } $entry = Get-Content -LiteralPath $entryPath -Raw -Encoding utf8 $login = Get-Content -LiteralPath $loginPath -Raw -Encoding utf8 $accountPasswordLogin = ConvertFrom-Utf8Base64 '6LSm5Y+35a+G56CB55m75b2V' $wechatLogin = ConvertFrom-Utf8Base64 '5b6u5L+h55m75b2V' $registerPrompt = ConvertFrom-Utf8Base64 '6L+Y5rKh5pyJ6LSm5Y+377yf' $registerEntry = ConvertFrom-Utf8Base64 '5rOo5YaM6LSm5Y+3' $loginEntry = ConvertFrom-Utf8Base64 '55m75b2V' $dividerGlyph = ConvertFrom-Utf8Base64 '4oyY' $agreementRequired = ConvertFrom-Utf8Base64 '6K+35YWI6ZiF6K+75bm25ZCM5oSP55u45YWz5Y2P6K6u' $wechatPending = ConvertFrom-Utf8Base64 '5b6u5L+h55m75b2V5Yqf6IO95YeG5aSH5Lit' $registerPending = ConvertFrom-Utf8Base64 '5rOo5YaM6aG16Z2i5YeG5aSH5Lit' $agreementPending = ConvertFrom-Utf8Base64 '5Y2P6K6u6aG16Z2i5YeG5aSH5Lit' $smsLogin = ConvertFrom-Utf8Base64 '5omL5py66aqM6K+B56CB55m75b2V' $forgotPassword = ConvertFrom-Utf8Base64 '5b+Y6K6w5a+G56CB' $getCode = ConvertFrom-Utf8Base64 '6I635Y+W6aqM6K+B56CB' $loginPending = ConvertFrom-Utf8Base64 '55m75b2V5o6l5Y+j5b6F5o6l5YWl' $returnToEntry = ConvertFrom-Utf8Base64 '6K+35YWI6L+U5Zue55m75b2V5byV5a+856Gu6K6k5Y2P6K6u' $forgotPasswordPending = ConvertFrom-Utf8Base64 '5b+Y6K6w5a+G56CB6aG16Z2i5YeG5aSH5Lit' $getCodePending = ConvertFrom-Utf8Base64 '6I635Y+W6aqM6K+B56CB5Yqf6IO95b6F5o6l5YWl' $implicitAgreement = ConvertFrom-Utf8Base64 '55m75b2V5Y2z6KGo56S65ZCM5oSP' foreach ($requiredEntryCopy in @($wechatLogin, $registerPrompt, $registerEntry, $agreementRequired, $wechatPending, $registerPending, $agreementPending)) { Assert-Contains -Content $entry -Expected $requiredEntryCopy -Message "Missing A-01 copy: $requiredEntryCopy" } Assert-Contains -Content $entry -Expected "url: '/pages/auth/a02-login?agreed=1'" -Message 'A-01 account login must navigate with agreed=1' Assert-Contains -Content $entry -Expected "$loginEntry" -Message 'A-01 primary entry copy must be 登录' foreach ($requiredClass in @('auth-header', 'brand-intro', 'entry-actions', 'agreement-row')) { Assert-Contains -Content $entry -Expected $requiredClass -Message "Missing A-01 page structure: $requiredClass" } foreach ($requiredVisualAnchor in @( 'brand-divider--top', 'brand-divider--bottom', 'entry-button--primary', 'entry-button--secondary', 'height: 253rpx', 'padding: 150rpx 56rpx 0', 'width: 426rpx', 'margin: 95rpx 110rpx 0', 'height: 118rpx', 'margin-top: 51rpx', 'margin-top: 98rpx', 'margin-top: 141rpx', 'top: 70rpx', 'width: 64rpx', 'entry-button__content', 'translateX(-20rpx)', 'auth-divider-knot.png', 'auth-title-cloud.png', 'a01-primary-button.png', 'a01-secondary-button.png', 'entry-button__skin', 'align-items: center', 'white-space: nowrap', 'font-size: 20rpx', 'width: 30rpx', 'height: 30rpx' )) { 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"', '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')) { Assert-NotContains -Content $entry -Unexpected $forbiddenClass -Message "A-01 must not render the whole design image with $forbiddenClass" } 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, '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' } if ($entry -notmatch '(?s)\.agreement-copy\s*\{[^}]*white-space:\s*nowrap') { throw 'A-01 agreement copy must remain on a single line' } 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, $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' } foreach ($forbidden in @('appApi', 'calcMD5', 'loginWithPassword', 'uni.reLaunch', $implicitAgreement)) { Assert-NotContains -Content $entry -Unexpected $forbidden -Message "A-01 must not contain legacy auth behavior: $forbidden" Assert-NotContains -Content $login -Unexpected $forbidden -Message "A-02 must not contain legacy auth behavior: $forbidden" } Write-Output 'A01-A02-UI-CONTRACT PASS'