完成50%
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$attributePattern = '(?:"[^"]*"|''[^'']*''|[^>"'']+)*'
|
||||
|
||||
function Read-ProjectFile {
|
||||
param([string]$Path)
|
||||
return Get-Content -LiteralPath (Join-Path $root $Path) -Raw -Encoding UTF8
|
||||
}
|
||||
|
||||
function Require-Match {
|
||||
param([string]$Content, [string]$Pattern, [string]$Label)
|
||||
if ($Content -notmatch $Pattern) { throw "$Label 未满足" }
|
||||
}
|
||||
|
||||
function Require-ButtonClass {
|
||||
param([string]$Content, [string]$Class, [string]$Label)
|
||||
$button = [regex]::Matches($Content, "(?s)<button\b$script:attributePattern>") |
|
||||
Where-Object { $_.Value -match "class=`"[^`"]*\b$([regex]::Escape($Class))\b" } |
|
||||
Select-Object -First 1
|
||||
if ($null -eq $button) { throw "$Label 必须由原生 button 承载:$Class" }
|
||||
$view = [regex]::Matches($Content, "(?s)<view\b$script:attributePattern>") |
|
||||
Where-Object { $_.Value -match "class=`"[^`"]*\b$([regex]::Escape($Class))\b" -and $_.Value.Contains('@click') } |
|
||||
Select-Object -First 1
|
||||
if ($null -ne $view) {
|
||||
throw "$Label 不得继续用 view 模拟按钮:$Class"
|
||||
}
|
||||
}
|
||||
|
||||
function Get-ButtonByClass {
|
||||
param([string]$Content, [string]$Class)
|
||||
return [regex]::Matches($Content, "(?s)<button\b$script:attributePattern>") |
|
||||
Where-Object { $_.Value -match "class=`"[^`"]*\b$([regex]::Escape($Class))\b" } |
|
||||
Select-Object -First 1
|
||||
}
|
||||
|
||||
function Require-FieldRelation {
|
||||
param([string]$Content, [string]$Prefix, [string]$IdSuffix, [string]$ErrorKey, [string]$Label)
|
||||
$inputId = "$Prefix-$IdSuffix"
|
||||
$errorId = "$inputId-error"
|
||||
Require-Match $Content "(?s)<label\b[^>]*for=`"$([regex]::Escape($inputId))`"[^>]*>" "$Label label/for"
|
||||
$input = [regex]::Matches($Content, "(?s)<input\b$script:attributePattern>") |
|
||||
Where-Object { $_.Value.Contains("id=`"$inputId`"") } |
|
||||
Select-Object -First 1
|
||||
if ($null -eq $input) { throw "$Label 缺少对应 input:$inputId" }
|
||||
foreach ($pattern in @(
|
||||
":aria-invalid=`"Boolean\(fieldErrors\.$([regex]::Escape($ErrorKey))\)`"",
|
||||
":aria-describedby=`"fieldErrors\.$([regex]::Escape($ErrorKey)) \? '$([regex]::Escape($errorId))' : undefined`""
|
||||
)) {
|
||||
if ($input.Value -notmatch $pattern) { throw "$Label 输入错误关联缺失:$inputId" }
|
||||
}
|
||||
Require-Match $Content "(?s)<text\b(?=[^>]*id=`"$([regex]::Escape($errorId))`")(?=[^>]*role=`"alert`")[^>]*>" "$Label 错误节点"
|
||||
}
|
||||
|
||||
$a01 = Read-ProjectFile 'pages/auth/a01-entry.vue'
|
||||
$a04 = Read-ProjectFile 'pages/auth/a04-register.vue'
|
||||
$a05 = Read-ProjectFile 'pages/auth/a05-reset-password.vue'
|
||||
$globalStyles = Read-ProjectFile 'styles/global.scss'
|
||||
|
||||
foreach ($page in @($a01, $a04, $a05)) {
|
||||
Require-Match $page 'import\s+AppToast\s+from\s+["'']@/components/AppToast\.vue["'']' '认证状态播报 import'
|
||||
Require-Match $page '(?s)<AppToast\b(?=[^>]*:visible="feedbackVisible")(?=[^>]*:message="feedbackMessage")[^>]*/>' '认证状态播报实例'
|
||||
if ($page.Contains('class="feedback-toast"')) { throw '认证页不得重复实现缺少稳定 live region 的 Toast' }
|
||||
}
|
||||
|
||||
Require-Match $a01 '<view\s+class="login-tabs"\s+role="tablist"' 'A01 登录方式容器'
|
||||
$a01Buttons = [regex]::Matches($a01, "(?s)<button\b$attributePattern>")
|
||||
$tabButtons = @($a01Buttons | Where-Object { $_.Value.Contains('role="tab"') })
|
||||
if ($tabButtons.Count -ne 2) { throw 'A01 两个登录方式必须都是原生 tab 按钮' }
|
||||
if (@($tabButtons | Where-Object { $_.Value.Contains(':aria-selected=') }).Count -ne 2) { throw 'A01 两个 tab 都必须声明选中状态' }
|
||||
foreach ($class in @(
|
||||
'login-tab', 'password-toggle', 'get-code', 'forgot-password', 'login-submit',
|
||||
'wechat-login', 'register-link', 'agreement-toggle', 'agreement-link'
|
||||
)) { Require-ButtonClass $a01 $class 'A01' }
|
||||
if (@($a01Buttons | Where-Object { $_.Value -match 'class="[^"]*\bagreement-link\b' }).Count -ne 2) { throw 'A01 两份协议必须各自可聚焦' }
|
||||
foreach ($name in @('手机号', '登录密码', '短信验证码')) {
|
||||
$namedInput = [regex]::Matches($a01, "(?s)<input\b$attributePattern>") |
|
||||
Where-Object { $_.Value.Contains("aria-label=`"$name`"") } |
|
||||
Select-Object -First 1
|
||||
if ($null -eq $namedInput) { throw "A01 输入缺少名称:$name" }
|
||||
}
|
||||
$passwordToggle = Get-ButtonByClass $a01 'password-toggle'
|
||||
if ($passwordToggle.Value -notmatch ':aria-label="passwordVisible \? ''隐藏密码'' : ''显示密码''"' -or -not $passwordToggle.Value.Contains(':aria-pressed="passwordVisible"')) { throw 'A01 密码显隐状态未关联' }
|
||||
$a01GetCode = Get-ButtonByClass $a01 'get-code'
|
||||
if (-not $a01GetCode.Value.Contains(':disabled="sendingCode || submitting || cooldownSeconds > 0"')) { throw 'A01 短信按钮禁用态未关联' }
|
||||
$a01Submit = Get-ButtonByClass $a01 'login-submit'
|
||||
if (-not $a01Submit.Value.Contains(':disabled="submitting || sendingCode || tacVisible"') -or -not $a01Submit.Value.Contains(':aria-busy="submitting"')) { throw 'A01 提交忙碌态未关联' }
|
||||
$a01Agreement = Get-ButtonByClass $a01 'agreement-toggle'
|
||||
if (-not $a01Agreement.Value.Contains('role="checkbox"') -or -not $a01Agreement.Value.Contains(':aria-checked="agreed"')) { throw 'A01 协议复选语义未关联' }
|
||||
Require-Match $a01 '(?s)<text\b[^>]*class="agreement-error"[^>]*role="alert"' 'A01 协议错误播报'
|
||||
|
||||
foreach ($class in @('back-button', 'get-code', 'register-submit', 'agreement-toggle', 'agreement-link', 'login-entry__link')) {
|
||||
Require-ButtonClass $a04 $class 'A04'
|
||||
}
|
||||
$a04Buttons = [regex]::Matches($a04, "(?s)<button\b$attributePattern>")
|
||||
if (@($a04Buttons | Where-Object { $_.Value -match 'class="[^"]*\bagreement-link\b' }).Count -ne 2) { throw 'A04 两份协议必须各自可聚焦' }
|
||||
foreach ($class in @('back-button', 'get-code', 'reset-submit', 'login-entry__link')) {
|
||||
Require-ButtonClass $a05 $class 'A05'
|
||||
}
|
||||
|
||||
foreach ($entry in @(
|
||||
@{ Key = 'A04'; Content = $a04; Prefix = 'a04'; SubmitClass = 'register-submit' },
|
||||
@{ Key = 'A05'; Content = $a05; Prefix = 'a05'; SubmitClass = 'reset-submit' }
|
||||
)) {
|
||||
Require-FieldRelation $entry.Content $entry.Prefix 'phone' 'phone' "$($entry.Key) 手机号"
|
||||
Require-FieldRelation $entry.Content $entry.Prefix 'verification-code' 'verificationCode' "$($entry.Key) 验证码"
|
||||
Require-FieldRelation $entry.Content $entry.Prefix 'password' 'password' "$($entry.Key) 密码"
|
||||
Require-FieldRelation $entry.Content $entry.Prefix 'confirm-password' 'confirmPassword' "$($entry.Key) 确认密码"
|
||||
$getCode = Get-ButtonByClass $entry.Content 'get-code'
|
||||
if (-not $getCode.Value.Contains(':disabled="sendingCode || submitting || cooldownSeconds > 0"')) { throw "$($entry.Key) 短信按钮禁用态未关联" }
|
||||
$submit = Get-ButtonByClass $entry.Content $entry.SubmitClass
|
||||
if (-not $submit.Value.Contains(':disabled="submitting || sendingCode || tacVisible"') -or -not $submit.Value.Contains(':aria-busy="submitting"')) { throw "$($entry.Key) 主提交忙碌态未关联" }
|
||||
}
|
||||
|
||||
$a04Agreement = Get-ButtonByClass $a04 'agreement-toggle'
|
||||
if (-not $a04Agreement.Value.Contains('role="checkbox"') -or -not $a04Agreement.Value.Contains(':aria-checked="agreed"')) { throw 'A04 协议复选语义未关联' }
|
||||
Require-Match $a05 '(?s)<AppDialog\b(?=[^>]*:visible="successVisible")(?=[^>]*title="密码已重设")[^>]*>' 'A05 成功终态对话框'
|
||||
if ($a05.Contains('class="success-layer"')) { throw 'A05 成功终态必须复用唯一 AppDialog owner' }
|
||||
|
||||
foreach ($token in @(
|
||||
'.auth-plain-button {', 'margin: 0;', 'padding: 0;', 'border: 0;',
|
||||
'background: transparent;', 'line-height: normal;', '.auth-plain-button::after {',
|
||||
'.auth-plain-button:focus-visible {', '.auth-page.auth-page .auth-plain-button {',
|
||||
'min-width: 48px;', 'min-height: 48px;'
|
||||
)) {
|
||||
if (-not $globalStyles.Contains($token)) { throw "认证原生按钮重置 owner 缺少:$token" }
|
||||
}
|
||||
|
||||
Write-Output 'AUTH-ACCESSIBILITY-STATIC-CONTRACT PASS'
|
||||
Reference in New Issue
Block a user