视觉审核完成30%

This commit is contained in:
rain
2026-07-16 18:12:59 +08:00
parent cb25317412
commit 23cfd365a1
89 changed files with 3740 additions and 403 deletions
+69 -5
View File
@@ -33,6 +33,7 @@ $root = Split-Path -Parent $PSScriptRoot
$entryPath = Join-Path $root 'pages/auth/a01-entry.vue'
$legacyLoginPath = Join-Path $root 'pages/auth/a02-login.vue'
$pagesPath = Join-Path $root 'pages.json'
$globalStylesPath = Join-Path $root 'styles/global.scss'
if (-not (Test-Path -LiteralPath $entryPath)) {
throw 'Missing the single A01 login page.'
@@ -48,19 +49,59 @@ if ($pages.pages[0].path -ne 'pages/auth/a01-entry') {
if ($pages.pages.path -contains 'pages/auth/a02-login') {
throw 'pages.json must not retain the legacy A02 route.'
}
if ($pages.pages.Count -ne 53) {
throw "Expected 53 final routes after the A02 merge, found $($pages.pages.Count)."
if ($pages.pages.Count -ne 52) {
throw "Expected 52 active routes after the A02 merge and A06 archive, found $($pages.pages.Count)."
}
$entry = Get-Content -LiteralPath $entryPath -Raw -Encoding utf8
$globalStyles = Get-Content -LiteralPath $globalStylesPath -Raw -Encoding utf8
# Typography has one owner, and the title decoration participates in normal flow.
Assert-Contains -Content $globalStyles -Expected 'font-family: "STKaiti", "KaiTi", serif' -Message 'Global styles must own the STKaiti/KaiTi font contract.'
Assert-NotContains -Content $entry -Unexpected 'font-family:' -Message 'A01 must inherit the global font contract instead of overriding it locally.'
$headingRule = [regex]::Match($entry, '(?ms)^\.login-heading\s*\{(?<Body>.*?)^\}')
if (-not $headingRule.Success) {
throw 'A01 is missing the login heading style rule.'
}
Assert-Contains -Content $headingRule.Groups['Body'].Value -Expected 'flex-direction: column;' -Message 'A01 login heading must lay out its title and divider vertically in normal flow.'
Assert-NotContains -Content $headingRule.Groups['Body'].Value -Unexpected 'position:' -Message 'A01 login heading must not use positioning for ordinary vertical layout.'
$titleRule = [regex]::Match($entry, '(?ms)^\.login-title\s*\{(?<Body>.*?)^\}')
if (-not $titleRule.Success) {
throw 'A01 is missing the login title style rule.'
}
Assert-NotContains -Content $titleRule.Groups['Body'].Value -Unexpected 'position:' -Message 'A01 login title must remain in normal flow.'
$dividerRule = [regex]::Match($entry, '(?ms)^\.title-divider\s*\{(?<Body>.*?)^\}')
if (-not $dividerRule.Success) {
throw 'A01 is missing the title divider style rule.'
}
foreach ($positioning in @('position: absolute', 'left: 50%', 'translateX(')) {
Assert-NotContains -Content $dividerRule.Groups['Body'].Value -Unexpected $positioning -Message "A01 title divider must not use manual positioning: $positioning"
}
Assert-Contains -Content $dividerRule.Groups['Body'].Value -Expected 'width: 360rpx;' -Message 'A01 title divider must be wide enough to remain visible on paper.'
Assert-Contains -Content $dividerRule.Groups['Body'].Value -Expected 'height: 60rpx;' -Message 'A01 title divider must retain a legible knot and line weight.'
Assert-Contains -Content $dividerRule.Groups['Body'].Value -Expected 'filter: brightness(0.68) saturate(1.5) contrast(1.2);' -Message 'A01 title divider must keep sufficient contrast against the paper background.'
$brandSealRule = [regex]::Match($entry, '(?ms)^\.brand-seal\s*\{(?<Body>.*?)^\}')
if (-not $brandSealRule.Success) {
throw 'A01 is missing the brand seal style rule.'
}
Assert-Contains -Content $brandSealRule.Groups['Body'].Value -Expected 'width: 184rpx;' -Message 'A01 brand seal must use the approved smaller width.'
Assert-Contains -Content $brandSealRule.Groups['Body'].Value -Expected 'height: 221rpx;' -Message 'A01 brand seal must preserve its aspect ratio at the approved smaller height.'
Assert-Contains -Content $brandSealRule.Groups['Body'].Value -Expected 'top: 64rpx;' -Message 'A01 brand seal must keep its approved vertical anchor.'
Assert-Contains -Content $brandSealRule.Groups['Body'].Value -Expected 'left: 50%;' -Message 'A01 brand seal must remain horizontally centered.'
# Final decorative surfaces must come from real bitmap assets.
foreach ($asset in @(
'a01-red-hall-ink-backdrop-v1.png',
'brand-seal.png',
'a01-title-divider-v2.png',
'a01-primary-button.png',
'a01-secondary-button.png',
'a01-vnext-divider-v1.png',
'a01-scroll-primary-v3.png',
'a01-scroll-secondary-v3.png',
'a01-scroll-toast-v3.png',
'a01-scroll-dialog-v3.png',
'auth-wechat.png',
'a01-icon-phone-v1.png',
'a01-icon-lock-v1.png',
@@ -80,10 +121,24 @@ foreach ($obsoleteVisualAsset in @(
'a01-login-scroll-frame-v1.png',
'a01-vnext-scroll-v1.png',
'auth-ink-scenery.png'
'a01-title-divider-v2.png',
'a01-primary-button-v2.png',
'a01-secondary-button-v2.png',
'a02-login-panel.png'
)) {
Assert-NotContains -Content $entry -Unexpected $obsoleteVisualAsset -Message "A01 must not retain the rejected header, scenery, or scroll asset: $obsoleteVisualAsset"
}
$buttonSkinTags = [regex]::Matches($entry, '<image class="button-skin"[^>]+>')
if ($buttonSkinTags.Count -ne 2) {
throw "A01 must have exactly two visible login button skins, found $($buttonSkinTags.Count)."
}
foreach ($buttonSkinTag in $buttonSkinTags) {
Assert-Contains -Content $buttonSkinTag.Value -Expected 'mode="aspectFit"' -Message 'A01 button skins must use uniform aspectFit rendering.'
Assert-NotContains -Content $buttonSkinTag.Value -Unexpected '/opaque/' -Message 'A01 visible button skins must use cleaned transparent v2 assets.'
Assert-NotContains -Content $buttonSkinTag.Value -Unexpected 'mode="scaleToFill"' -Message 'A01 button skins must not distort fixed artwork with scaleToFill.'
}
$requiredCopy = @(
'55m75b2V5a626LCx',
'5a+G56CB55m75b2V',
@@ -137,4 +192,13 @@ Assert-NotContains -Content $entry -Unexpected 'a01-icon-eye-closed-v1.png' -Mes
Assert-NotContains -Content $entry -Unexpected 'a01-icon-verification-v1.png' -Message 'A01 SMS field must use a message-code icon instead of a success-state checkmark.'
Assert-NotContains -Content $entry -Unexpected 'a01-icon-sms-code-v1.svg' -Message 'A01 SMS field must use the custom generated icon instead of the temporary third-party SVG.'
Assert-NotContains -Content $entry -Unexpected '@media (max-height:' -Message 'A01 must scroll naturally on short screens instead of compressing the selected design with height breakpoints.'
Assert-NotContains -Content $entry -Unexpected 'feedback-toast__skin' -Message 'A01 feedback Toast must use the dedicated nine-slice border asset instead of an absolutely stretched image.'
$feedbackToastRule = [regex]::Match($entry, '(?ms)^\.feedback-toast\s*\{(?<Body>.*?)^\}')
if (-not $feedbackToastRule.Success) {
throw 'A01 is missing the custom feedback Toast style rule.'
}
Assert-Contains -Content $feedbackToastRule.Groups['Body'].Value -Expected "border-image-source: url('/static/assets/foundation/transparent/a01-scroll-toast-v3.png');" -Message 'A01 feedback Toast must use the approved v3 nine-slice asset.'
Assert-Contains -Content $feedbackToastRule.Groups['Body'].Value -Expected 'border-image-slice:' -Message 'A01 feedback Toast must declare fixed decorative slices.'
Assert-Contains -Content $feedbackToastRule.Groups['Body'].Value -Expected 'fill' -Message 'A01 feedback Toast nine-slice must fill the paper center.'
Write-Output 'A01-LOGIN-MERGE-CONTRACT PASS'
+55 -1
View File
@@ -18,13 +18,67 @@ $buttons = @(
'static/assets/foundation/opaque/a01-primary-button.png',
'static/assets/foundation/opaque/a01-secondary-button.png'
)
$v2Buttons = @(
'static/assets/foundation/transparent/a01-primary-button-v2.png',
'static/assets/foundation/transparent/a01-secondary-button-v2.png'
)
$v3Skins = @(
'static/assets/foundation/transparent/a01-scroll-primary-v3.png',
'static/assets/foundation/transparent/a01-scroll-secondary-v3.png',
'static/assets/foundation/transparent/a01-scroll-toast-v3.png',
'static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png'
)
foreach ($asset in $backgrounds + $icons + $buttons) {
foreach ($asset in $backgrounds + $icons + $buttons + $v2Buttons + $v3Skins) {
if (-not (Test-Path -LiteralPath (Join-Path $root $asset))) {
throw "Missing A01 asset: $asset"
}
}
foreach ($asset in $v3Skins) {
$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
)
$bitmap.Dispose()
$expectedSize = switch ($asset) {
'static/assets/foundation/transparent/a01-scroll-primary-v3.png' { @(1866, 276) }
'static/assets/foundation/transparent/a01-scroll-secondary-v3.png' { @(1866, 300) }
'static/assets/foundation/transparent/a01-scroll-toast-v3.png' { @(1770, 246) }
'static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png' { @(1860, 1560) }
}
if ($width -ne $expectedSize[0] -or $height -ne $expectedSize[1]) { throw "$asset must be $($expectedSize[0]) x $($expectedSize[1])px." }
if (($corners | Where-Object { $_ -ne 0 }).Count -ne 0) { throw "$asset must have transparent outer corners." }
Write-Output "PASS $asset transparent corners and exact v3 slot ratio"
}
foreach ($asset in $v2Buttons) {
$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
)
$bitmap.Dispose()
$expectedSize = switch ($asset) {
'static/assets/foundation/transparent/a01-primary-button-v2.png' { @(1866, 276) }
'static/assets/foundation/transparent/a01-secondary-button-v2.png' { @(1866, 300) }
}
if ($width -ne $expectedSize[0] -or $height -ne $expectedSize[1]) { throw "$asset must be $($expectedSize[0]) x $($expectedSize[1])px." }
if (($corners | Where-Object { $_ -ne 0 }).Count -ne 0) { throw "$asset must have transparent outer corners." }
Write-Output "PASS $asset transparent corners and exact runtime ratio"
}
foreach ($asset in $icons) {
$bitmap = [System.Drawing.Bitmap]::FromFile((Join-Path $root $asset))
$width = [int]$bitmap.Width
+41
View File
@@ -56,6 +56,14 @@ const waitFor = async (send, expression, message) => {
throw new Error(message)
}
const waitForFreshDocument = async (send, previousTimeOrigin) => {
await waitFor(
send,
`performance.timeOrigin !== ${JSON.stringify(previousTimeOrigin)}`,
'A01 navigation did not create a fresh document'
)
}
const run = async () => {
const { socket, send, exceptions } = await connect()
try {
@@ -70,7 +78,10 @@ const run = async () => {
mobile: true
})
await send('Page.navigate', { url })
await waitFor(send, `location.href === ${JSON.stringify(url)}`, `A01 did not navigate at ${size.width}x${size.height}`)
const previousTimeOrigin = await valueOf(send, 'performance.timeOrigin')
await send('Page.reload')
await waitForFreshDocument(send, previousTimeOrigin)
await waitFor(send, "document.querySelectorAll('.login-tab').length === 2", `A01 did not render at ${size.width}x${size.height}`)
if (size.width === 320 && size.height === 568) {
await waitFor(send, "document.querySelector('.auth-page')?.scrollHeight > document.querySelector('.auth-page')?.clientHeight", 'A01 did not establish natural scrolling at 320x568')
@@ -86,11 +97,30 @@ const run = async () => {
})`)
assert.strictEqual(metrics.innerWidth, size.width, `A01 viewport width mismatch at ${size.width}x${size.height}`)
assert(metrics.scrollWidth <= size.width + 1, `A01 has horizontal overflow at ${size.width}x${size.height}: ${metrics.scrollWidth}`)
const buttonAssets = await valueOf(send, `Array.from(document.querySelectorAll('.button-skin img')).map((image) => ({
src: image.currentSrc || image.src,
naturalWidth: image.naturalWidth,
naturalHeight: image.naturalHeight
}))`)
assert.strictEqual(buttonAssets.length, 2, `A01 button skins did not render at ${size.width}x${size.height}`)
assert(buttonAssets[0].src.includes('a01-scroll-primary-v3.png'), `A01 primary v3 skin is missing at ${size.width}x${size.height}`)
assert(buttonAssets[1].src.includes('a01-scroll-secondary-v3.png'), `A01 secondary v3 skin is missing at ${size.width}x${size.height}`)
assert.deepStrictEqual(
buttonAssets.map(({ naturalWidth, naturalHeight }) => [naturalWidth, naturalHeight]),
[[1866, 276], [1866, 300]],
`A01 v3 button assets have unexpected runtime dimensions at ${size.width}x${size.height}`
)
if (size.width === 320 && size.height === 568) {
assert(metrics.pageScrollHeight > metrics.pageClientHeight, 'A01 must scroll naturally at 320x568')
}
}
await valueOf(send, "document.querySelector('.login-submit').click()")
await waitFor(send, "Boolean(document.querySelector('.feedback-toast'))", 'A01 invalid phone Toast did not render')
const toastBorderImage = await valueOf(send, "getComputedStyle(document.querySelector('.feedback-toast')).borderImageSource")
assert(toastBorderImage.includes('a01-scroll-toast-v3.png'), `A01 Toast did not render the v3 nine-slice asset: ${toastBorderImage}`)
await valueOf(send, "document.querySelectorAll('.login-tab')[1].click()")
await waitFor(send, "document.querySelectorAll('.login-tab')[1].classList.contains('active')", 'A01 SMS tab did not activate')
await waitFor(send, "Boolean(document.querySelector('.input-icon--sms img'))", 'A01 SMS field image did not render after tab activation')
@@ -117,6 +147,17 @@ const run = async () => {
await waitFor(send, "!document.querySelector('.agreement-error')", 'A01 agreement error did not clear after selection')
await valueOf(send, "document.querySelector('.login-submit').click()")
await waitFor(send, "Boolean(document.querySelector('.verification-layer'))", 'A01 did not open the custom verification layer after local validation')
await waitFor(send, "document.querySelector('.verification-dialog__skin img')?.naturalWidth === 1860", 'A01 verification dialog v3 asset did not finish loading')
const dialogAsset = await valueOf(send, `(() => {
const image = document.querySelector('.verification-dialog__skin img')
return image ? { src: image.currentSrc || image.src, naturalWidth: image.naturalWidth, naturalHeight: image.naturalHeight } : null
})()`)
assert(dialogAsset?.src.includes('a01-scroll-dialog-v3.png'), `A01 verification dialog did not load the v3 asset: ${dialogAsset?.src}`)
assert.deepStrictEqual(
[dialogAsset.naturalWidth, dialogAsset.naturalHeight],
[1860, 1560],
'A01 verification dialog loaded unexpected natural dimensions'
)
assert.deepStrictEqual(exceptions, [], `A01 raised browser exceptions: ${exceptions.join('; ')}`)
process.stdout.write('A01-RESPONSIVE-RUNTIME-SMOKE PASS\n')
+1 -1
View File
@@ -4,7 +4,7 @@ $root = Split-Path -Parent $PSScriptRoot
$pages = Get-Content -LiteralPath (Join-Path $root 'pages.json') -Raw -Encoding utf8 | ConvertFrom-Json
$legacyPage = Join-Path $root 'pages/auth/a02-login.vue'
if ($pages.pages.Count -ne 53) { throw "Expected 53 routes, found $($pages.pages.Count)." }
if ($pages.pages.Count -ne 52) { throw "Expected 52 active routes, found $($pages.pages.Count)." }
if ($pages.pages.path -contains 'pages/auth/a02-login') { throw 'Legacy A02 route still exists.' }
if (Test-Path -LiteralPath $legacyPage) { throw 'Legacy A02 page file still exists.' }
+1 -1
View File
@@ -8,6 +8,6 @@ $a03File = Join-Path $root 'pages/auth/a03-mobile-verify.vue'
if ($pages.pages.path -contains 'pages/auth/a03-mobile-verify') { throw 'A-03 must not remain in pages.json' }
if (Test-Path -LiteralPath $a03File) { throw 'A-03 page file must be deleted' }
if ($catalog -match '(?m)^\s*a03\s*:') { throw 'A-03 catalog entry must be deleted' }
if ($pages.pages.Count -ne 53) { throw "Expected 53 final routes after the approved page-state merges, found $($pages.pages.Count)." }
if ($pages.pages.Count -ne 52) { throw "Expected 52 active routes after the approved page-state merges and A06 archive, found $($pages.pages.Count)." }
Write-Output 'A03-ROUTE-REMOVAL-CONTRACT PASS'
+23 -6
View File
@@ -20,16 +20,19 @@ $registerPath = Join-Path $root 'pages/auth/a04-register.vue'
$pages = Get-Content -LiteralPath (Join-Path $root 'pages.json') -Raw -Encoding utf8 | ConvertFrom-Json
$catalog = Get-Content -LiteralPath (Join-Path $root 'data/page-catalog.js') -Raw -Encoding utf8
$register = Get-Content -LiteralPath $registerPath -Raw -Encoding utf8
$globalStyles = Get-Content -LiteralPath (Join-Path $root 'styles/global.scss') -Raw -Encoding utf8
$registerCopy = ConvertFrom-Utf8Base64 '5rOo5YaM6LSm5Y+3'
$loginCopy = ConvertFrom-Utf8Base64 '55m75b2V'
$sliderPendingCopy = ConvertFrom-Utf8Base64 '5ruR5Yqo6aqM6K+B5b6F5o6l5Y+j5o6l5YWl'
if (-not ($pages.pages.path -contains 'pages/auth/a04-register')) { throw 'A-04 route must remain declared in pages.json' }
Assert-NotContains -Content $register -Unexpected 'ModulePage' -Message 'A-04 must not remain a ModulePage shell'
foreach ($required in @(
'a01-red-hall-ink-backdrop-v1.png',
'brand-seal.png',
'a02-login-panel.png',
'a01-primary-button.png',
'a01-vnext-divider-v1.png',
'a01-scroll-primary-v3.png',
'a01-scroll-toast-v3.png',
'a02-agreement-unchecked.png',
'a02-agreement-checked.png',
'v-model.trim="phone"',
@@ -38,11 +41,10 @@ foreach ($required in @(
'const agreed = ref(false)',
'const agreementError = ref(false)',
'const fieldErrors = ref({',
'const verificationVisible = ref(false)',
'const toggleAgreement = () =>',
'const submitRegister = () =>',
'class="feedback-toast"',
'class="verification-layer"',
$sliderPendingCopy,
"const prepareLogin = () => uni.redirectTo({ url: '/pages/auth/a01-entry' })",
'if (!/^1\d{10}$/.test(phone.value))',
'if (!password.value)',
@@ -56,13 +58,28 @@ Assert-Contains -Content $register -Expected $loginCopy -Message 'A-04 must prov
Assert-NotContains -Content $catalog -Unexpected 'a04:' -Message 'A-04 must not remain in the temporary page catalog'
Assert-NotContains -Content $register -Unexpected 'register-intro' -Message 'A-04 must not retain the redundant registration intro copy'
Assert-NotContains -Content $register -Unexpected 'auth-design-scenery-v1.png' -Message 'A-04 must use the shared red-hall ink backdrop instead of the rejected scenery layer'
Assert-Contains -Content $globalStyles -Expected 'font-family: "STKaiti", "KaiTi", serif' -Message 'Global styles must own the A04 font contract.'
Assert-NotContains -Content $register -Unexpected 'font-family:' -Message 'A04 must inherit the global font contract instead of overriding it locally.'
foreach ($obsoleteVisual in @(
'a01-primary-button.png',
'a01-secondary-button.png',
'a02-login-panel.png',
'verificationVisible',
'verification-layer',
'feedback-toast__skin'
)) {
Assert-NotContains -Content $register -Unexpected $obsoleteVisual -Message "A04 must not retain obsolete visual or placeholder state: $obsoleteVisual"
}
foreach ($nativeUi in @('uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet')) {
Assert-NotContains -Content $register -Unexpected $nativeUi -Message "A-04 must not use native UniApp feedback: $nativeUi"
}
foreach ($required in @(
'auth-divider-knot.png',
'register-divider',
'width: 154rpx'
'mode="aspectFit"',
'width: 184rpx;',
'height: 221rpx;',
"border-image-source: url('/static/assets/foundation/transparent/a01-scroll-toast-v3.png');",
'border-image-slice: 58 280 fill;'
)) {
Assert-Contains -Content $register -Expected $required -Message "Missing A-04 visual balance contract: $required"
}
+6 -4
View File
@@ -68,7 +68,7 @@ const run = async () => {
await valueOf(send, "document.querySelector('.register-link').click()")
await waitFor(send, "location.href.includes('/pages/auth/a04-register')", 'A01 registration entry did not open A04')
for (const size of [{ width: 320, height: 568 }, { width: 360, height: 800 }, { width: 412, height: 915 }]) {
for (const size of [{ width: 320, height: 568 }, { width: 360, height: 640 }, { width: 360, height: 800 }, { width: 412, height: 915 }]) {
await send('Emulation.setDeviceMetricsOverride', { ...size, deviceScaleFactor: 1, mobile: true })
await navigate(send, a04Url, '.register-submit')
const metrics = await valueOf(send, `({
@@ -94,9 +94,11 @@ const run = async () => {
document.querySelector('.agreement-row').click()
document.querySelector('.register-submit').click()
})()`)
await waitFor(send, "Boolean(document.querySelector('.verification-layer'))", 'A04 valid submit did not open custom verification')
await valueOf(send, "document.querySelector('.verification-action--primary').click()")
await waitFor(send, "Boolean(document.querySelector('.feedback-toast'))", 'A04 verification result did not use custom feedback')
await waitFor(send, "Boolean(document.querySelector('.feedback-toast'))", 'A04 valid submit did not show slider integration feedback')
assert.strictEqual(await valueOf(send, "document.querySelector('.feedback-toast__copy')?.textContent"), '滑动验证待接口接入', 'A04 valid submit showed unexpected feedback')
assert.strictEqual(await valueOf(send, "Boolean(document.querySelector('.verification-layer'))"), false, 'A04 must not retain the non-final custom verification placeholder')
const toastBorderImage = await valueOf(send, "getComputedStyle(document.querySelector('.feedback-toast')).borderImageSource")
assert(toastBorderImage.includes('a01-scroll-toast-v3.png'), `A04 Toast did not render the v3 nine-slice asset: ${toastBorderImage}`)
await navigate(send, a04Url, '.login-entry__link')
await valueOf(send, "document.querySelector('.login-entry__link').click()")
+29 -6
View File
@@ -24,15 +24,17 @@ $resetCopy = ConvertFrom-Utf8Base64 '6YeN6K6+5a+G56CB'
$confirmPasswordCopy = ConvertFrom-Utf8Base64 '56Gu6K6k5paw5a+G56CB'
$getCodeCopy = ConvertFrom-Utf8Base64 '6I635Y+W6aqM6K+B56CB'
$loginCopy = ConvertFrom-Utf8Base64 '6L+U5Zue55m75b2V'
$sliderPendingCopy = ConvertFrom-Utf8Base64 '5ruR5Yqo6aqM6K+B5b6F5o6l5Y+j5o6l5YWl'
if (-not ($pages.pages.path -contains 'pages/auth/a05-reset-password')) { throw 'A-05 route must remain declared in pages.json' }
Assert-NotContains -Content $page -Unexpected 'ModulePage' -Message 'A-05 must not remain a ModulePage shell'
foreach ($required in @(
'a01-red-hall-ink-backdrop-v1.png',
'brand-seal.png',
'a02-login-panel.png',
'a01-primary-button.png',
'auth-divider-knot.png',
'a01-vnext-divider-v1.png',
'a01-scroll-primary-v3.png',
'a01-scroll-toast-v3.png',
'a01-scroll-dialog-v3.png',
'chevron-right.png',
'v-model.trim="phone"',
'v-model.trim="verificationCode"',
@@ -41,16 +43,18 @@ foreach ($required in @(
'const prepareGetCode = () =>',
'const submitReset = () =>',
'const fieldErrors = ref({',
'const verificationVisible = ref(false)',
'const successVisible = ref(false)',
'class="verification-layer"',
'class="feedback-toast"',
'class="success-layer"',
"const prepareLogin = () => uni.redirectTo({ url: '/pages/auth/a01-entry' })",
'if (!/^1\d{10}$/.test(phone.value))',
'if (!/^\d{6}$/.test(verificationCode.value))',
'if (!password.value)',
'if (password.value !== confirmPassword.value)'
'if (password.value !== confirmPassword.value)',
'width: 184rpx;',
'height: 221rpx;',
"border-image-source: url('/static/assets/foundation/transparent/a01-scroll-toast-v3.png');",
'border-image-slice: 58 280 fill;'
)) {
Assert-Contains -Content $page -Expected $required -Message "Missing A-05 reset-password contract: $required"
}
@@ -58,8 +62,27 @@ Assert-Contains -Content $page -Expected $resetCopy -Message 'A-05 must visibly
Assert-Contains -Content $page -Expected $confirmPasswordCopy -Message 'A-05 must require password confirmation'
Assert-Contains -Content $page -Expected $getCodeCopy -Message 'A-05 must expose the verification-code action'
Assert-Contains -Content $page -Expected $loginCopy -Message 'A-05 must provide the A01 return path'
Assert-Contains -Content $page -Expected $sliderPendingCopy -Message 'A-05 must defer the real slider to the interface stage'
Assert-NotContains -Content $catalog -Unexpected 'a05:' -Message 'A-05 must not remain in the temporary page catalog'
Assert-NotContains -Content $page -Unexpected 'auth-design-scenery-v1.png' -Message 'A-05 must use the selected red-hall ink backdrop'
foreach ($obsoleteVisual in @(
'a01-primary-button.png',
'a01-secondary-button.png',
'a02-login-panel.png',
'auth-divider-knot.png',
'feedback-toast__skin',
'codeRequested',
'verificationVisible',
'verification-layer',
'closeVerification',
'confirmVerification',
'font-family:'
)) {
Assert-NotContains -Content $page -Unexpected $obsoleteVisual -Message "A-05 must not retain obsolete visual or placeholder state: $obsoleteVisual"
}
$primarySkinCount = ([regex]::Matches($page, 'a01-scroll-primary-v3\.png')).Count
if ($primarySkinCount -ne 2) { throw "A-05 must use the approved primary skin exactly twice, found $primarySkinCount" }
foreach ($nativeUi in @('uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet')) {
Assert-NotContains -Content $page -Unexpected $nativeUi -Message "A-05 must not use native UniApp feedback: $nativeUi"
}
+9 -12
View File
@@ -69,11 +69,13 @@ const run = async () => {
await send('Page.enable')
await send('Runtime.enable')
await navigate(send, a01Url, '.forgot-password')
await navigate(send, a01Url, '.login-tab')
await valueOf(send, "document.querySelectorAll('.login-tab')[0].click()")
await waitFor(send, "Boolean(document.querySelector('.forgot-password'))", 'A01 password state did not expose forgot-password entry')
await valueOf(send, "document.querySelector('.forgot-password').click()")
await waitFor(send, "location.href.includes('/pages/auth/a05-reset-password')", 'A01 forgot-password entry did not open A05')
for (const size of [{ width: 320, height: 568 }, { width: 360, height: 800 }, { width: 412, height: 915 }]) {
for (const size of [{ width: 320, height: 568 }, { width: 360, height: 640 }, { width: 360, height: 800 }, { width: 412, height: 915 }]) {
await send('Emulation.setDeviceMetricsOverride', { ...size, deviceScaleFactor: 1, mobile: true })
await navigate(send, a05Url, '.reset-submit')
const metrics = await valueOf(send, `({
@@ -89,16 +91,11 @@ const run = async () => {
await waitFor(send, "Boolean(document.querySelector('.field-error'))", 'A05 invalid phone did not show inline error')
await valueOf(send, setInputs(['13800138000', '', '', '']))
await valueOf(send, "document.querySelector('.get-code').click()")
await waitFor(send, "Boolean(document.querySelector('.verification-layer'))", 'A05 code request did not open custom verification')
await valueOf(send, "document.querySelector('.verification-action').click()")
await waitFor(send, "!document.querySelector('.verification-layer')", 'A05 verification did not close')
assert.strictEqual(await valueOf(send, "document.querySelector('.auth-input input').value"), '13800138000', 'A05 verification close cleared the form')
await valueOf(send, "document.querySelector('.get-code').click()")
await waitFor(send, "Boolean(document.querySelector('.verification-layer'))", 'A05 verification did not reopen')
await valueOf(send, "document.querySelector('.verification-action--primary').click()")
await waitFor(send, "Boolean(document.querySelector('.feedback-toast'))", 'A05 verified code request did not use custom feedback')
assert.strictEqual(await valueOf(send, "document.querySelector('.get-code').textContent"), '重新获取', 'A05 code action did not enter requested state')
await waitFor(send, "Boolean(document.querySelector('.feedback-toast'))", 'A05 code request did not use custom pending feedback')
assert.strictEqual(await valueOf(send, "document.querySelector('.feedback-toast').textContent.trim()"), '滑动验证待接口接入', 'A05 code request showed the wrong pending feedback')
assert.strictEqual(await valueOf(send, "Boolean(document.querySelector('.verification-layer'))"), false, 'A05 must not retain the old verification panel')
assert.strictEqual(await valueOf(send, "document.querySelector('.auth-input input').value"), '13800138000', 'A05 pending slider feedback cleared the form')
assert.strictEqual(await valueOf(send, "document.querySelector('.get-code').textContent.trim()"), '获取验证码', 'A05 must not fake a requested-code state before the real slider and SMS interface')
await valueOf(send, "document.querySelector('.reset-submit').click()")
await waitFor(send, "document.querySelectorAll('.field-error').length === 3", 'A05 incomplete submit did not show three remaining field errors')
+22 -5
View File
@@ -14,9 +14,12 @@ $root = Split-Path -Parent $PSScriptRoot
$page = Get-Content -LiteralPath (Join-Path $root 'pages/auth/a06-auth-status.vue') -Raw -Encoding utf8
$pages = Get-Content -LiteralPath (Join-Path $root 'pages.json') -Raw -Encoding utf8 | ConvertFrom-Json
$catalog = Get-Content -LiteralPath (Join-Path $root 'data/page-catalog.js') -Raw -Encoding utf8
$runtime = Get-Content -LiteralPath (Join-Path $root 'tests/a06-auth-status-runtime-smoke.js') -Raw -Encoding utf8
if (-not ($pages.pages.path -contains 'pages/auth/a06-auth-status')) { throw 'A-06 route must remain declared in pages.json' }
if ($pages.pages.path -contains 'pages/auth/a06-auth-status') { throw 'A-06 is archived and must not remain declared in pages.json' }
if (-not (Test-Path -LiteralPath (Join-Path $root 'pages/auth/a06-auth-status.vue'))) { throw 'Archived A-06 source must be retained' }
if (-not ($pages.pages.path -contains 'pages/auth/a01-entry')) { throw 'A-06 must retain the A01 return target' }
Assert-Contains -Content $runtime -Expected 'A06-AUTH-STATUS-RUNTIME-SMOKE SKIP archived route' -Message 'A-06 runtime smoke must explicitly skip while the route is archived'
Assert-NotContains -Content $page -Unexpected 'ModulePage' -Message 'A-06 must not remain a ModulePage shell'
Assert-NotContains -Content $catalog -Unexpected 'a06:' -Message 'A-06 must not remain in the temporary page catalog'
@@ -37,12 +40,15 @@ foreach ($required in @(
'const goBack = () =>',
'a01-red-hall-ink-backdrop-v1.png',
'brand-seal.png',
'a02-login-panel.png',
'a01-primary-button.png',
'auth-divider-knot.png',
'a01-vnext-divider-v1.png',
'a01-scroll-primary-v3.png',
'a01-scroll-dialog-v3.png',
'chevron-right.png',
'auth-login-outline.png',
'class="recovery-layer"'
'class="recovery-layer"',
'width: 184rpx;',
'height: 221rpx;',
'mode="aspectFit"'
)) {
Assert-Contains -Content $page -Expected $required -Message "Missing A-06 auth-status contract: $required"
}
@@ -54,6 +60,17 @@ foreach ($nativeUi in @('uni.showToast', 'uni.showModal', 'uni.showLoading', 'un
Assert-NotContains -Content $page -Unexpected $nativeUi -Message "A-06 must not use native UniApp feedback: $nativeUi"
}
Assert-NotContains -Content $page -Unexpected 'auth-design-scenery-v1.png' -Message 'A-06 must use the selected red-hall ink backdrop'
foreach ($obsoleteVisual in @(
'a01-primary-button.png',
'a02-login-panel.png',
'auth-divider-knot.png',
'font-family:'
)) {
Assert-NotContains -Content $page -Unexpected $obsoleteVisual -Message "A-06 must not retain obsolete visual: $obsoleteVisual"
}
$primarySkinCount = ([regex]::Matches($page, 'a01-scroll-primary-v3\.png')).Count
if ($primarySkinCount -ne 2) { throw "A-06 must use the approved primary skin exactly twice, found $primarySkinCount" }
foreach ($forbidden in @(
'(?s)\.status-panel\s*\{[^}]*\bborder\s*:',
+10 -1
View File
@@ -1,4 +1,13 @@
const assert = require('assert')
const fs = require('fs')
const path = require('path')
const routeConfig = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'pages.json'), 'utf8'))
const isActiveRoute = routeConfig.pages.some((item) => item.path === 'pages/auth/a06-auth-status')
if (!isActiveRoute) {
process.stdout.write('A06-AUTH-STATUS-RUNTIME-SMOKE SKIP archived route\n')
process.exit(0)
}
const baseUrl = 'http://localhost:5173/#/pages/auth/a06-auth-status'
const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds))
@@ -60,7 +69,7 @@ const run = async () => {
await send('Page.enable')
await send('Runtime.enable')
for (const size of [{ width: 320, height: 568 }, { width: 360, height: 800 }, { width: 412, height: 915 }]) {
for (const size of [{ width: 320, height: 568 }, { width: 360, height: 640 }, { width: 360, height: 800 }, { width: 412, height: 915 }]) {
await send('Emulation.setDeviceMetricsOverride', { ...size, deviceScaleFactor: 1, mobile: true })
await navigate(send, 'risk', '账号存在安全风险')
const metrics = await valueOf(send, `({
+4 -4
View File
@@ -9,7 +9,7 @@ $root = Split-Path -Parent $PSScriptRoot
$pagesPath = Join-Path $root 'pages.json'
$pageHeader = ConvertFrom-Utf8Base64 '6aG16Z2i57yW5Y+377ya'
$expectedPages = @(
'pages/auth/a01-entry', 'pages/auth/a04-register', 'pages/auth/a05-reset-password', 'pages/auth/a06-auth-status',
'pages/auth/a01-entry', 'pages/auth/a04-register', 'pages/auth/a05-reset-password',
'pages/genealogy/g01-my-genealogies', 'pages/genealogy/g03-create-genealogy', 'pages/genealogy/g05-genealogy-overview', 'pages/genealogy/g06-search-genealogies', '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/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',
@@ -20,11 +20,11 @@ $expectedPages = @(
$pages = Get-Content -LiteralPath $pagesPath -Raw -Encoding utf8 | ConvertFrom-Json
$actualPaths = @($pages.pages.path)
if ($actualPaths.Count -ne 53) {
throw "Expected 53 visual routes after the approved page-state merges, found $($actualPaths.Count)."
if ($actualPaths.Count -ne 52) {
throw "Expected 52 active visual routes after the approved page-state merges and A06 archive, found $($actualPaths.Count)."
}
if (Compare-Object -ReferenceObject $expectedPages -DifferenceObject $actualPaths) {
throw 'pages.json routes do not match the 53-route visual delivery contract.'
throw 'pages.json routes do not match the 52-route active visual delivery contract.'
}
foreach ($page in $expectedPages) {
+1
View File
@@ -117,6 +117,7 @@ if ($card -notmatch '(?s)\.card-title-row\s*\{.*?justify-content:\s*space-betwee
if ($card -notmatch '(?s)\.card-meta\s*\{.*?font-size:\s*26rpx') { throw 'G-01 list metadata is below the approved readable size.' }
if ($card -notmatch '(?s)\.card-updated\s*\{.*?font-size:\s*22rpx') { throw 'G-01 list update date is below the approved readable size.' }
if ($page -notmatch '(?s)\.create-action\s*>\s*text\s*\{.*?white-space:\s*nowrap') { throw 'G-01 create action text can wrap onto multiple lines.' }
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.' }
$manifest = Get-Content -Raw -Encoding UTF8 (Join-Path $root 'manifest.json')
if ($manifest -notmatch '"statusbar"\s*:\s*\{\s*"immersed"\s*:\s*"supportedDevice"\s*,\s*"style"\s*:\s*"light"\s*,\s*"background"\s*:\s*"#B52E22"') { throw 'Android status bar is not configured for the cinnabar root header.' }