提取认证页共享自适应骨架
This commit is contained in:
@@ -0,0 +1,72 @@
|
|||||||
|
<template>
|
||||||
|
<view class="auth-shell">
|
||||||
|
<view class="auth-shell__header">
|
||||||
|
<image
|
||||||
|
class="auth-shell__header-image"
|
||||||
|
src="/static/assets/modules/auth/opaque/a01-vnext-header-v1.png"
|
||||||
|
mode="widthFix"
|
||||||
|
/>
|
||||||
|
<image
|
||||||
|
class="auth-shell__seal"
|
||||||
|
src="/static/assets/foundation/transparent/brand-seal.png"
|
||||||
|
mode="aspectFit"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="auth-shell__paper">
|
||||||
|
<slot />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<slot name="overlay" />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.auth-shell {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
min-height: var(--app-viewport-height);
|
||||||
|
overflow-x: hidden;
|
||||||
|
background: #940400;
|
||||||
|
color: #493323;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-shell__header {
|
||||||
|
position: relative;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
height: calc(var(--app-safe-top) + min(36.5vw, 175px));
|
||||||
|
overflow: hidden;
|
||||||
|
padding-top: var(--app-safe-top);
|
||||||
|
background: #940400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-shell__header-image {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-shell__seal {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(var(--app-safe-top) + clamp(46px, 11.5vw, 58px));
|
||||||
|
left: 50%;
|
||||||
|
width: clamp(56px, 17vw, 78px);
|
||||||
|
height: clamp(67px, 20.4vw, 94px);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-shell__paper {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: var(--app-safe-bottom);
|
||||||
|
background: #f7f0e5 url("/static/assets/foundation/opaque/auth-page-paper.jpg") center top / 100% auto repeat-y;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
- Test: `tests/auth-page-shell-contract.ps1`
|
- Test: `tests/auth-page-shell-contract.ps1`
|
||||||
|
|
||||||
**Interfaces:**
|
**Interfaces:**
|
||||||
- Produces: `AuthPageShell` 的单一职责合同,以及 A01/A04/A05/A06 禁止旧画布的合同
|
- Produces: `AuthPageShell` 的单一职责合同,以及首个消费者 A01 禁止旧画布的合同;其余页面由各自任务的合同接管
|
||||||
|
|
||||||
- [ ] **Step 1: 写入失败合同**
|
- [ ] **Step 1: 写入失败合同**
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ foreach ($required in @(
|
|||||||
'min-height: var(--app-viewport-height);'
|
'min-height: var(--app-viewport-height);'
|
||||||
)) { Assert-Contains $shell $required "AuthPageShell is missing: $required" }
|
)) { Assert-Contains $shell $required "AuthPageShell is missing: $required" }
|
||||||
|
|
||||||
foreach ($pageName in @('a01-entry.vue','a04-register.vue','a05-reset-password.vue','a06-auth-status.vue')) {
|
foreach ($pageName in @('a01-entry.vue')) {
|
||||||
$page = Get-Content -LiteralPath (Join-Path $root "pages/auth/$pageName") -Raw -Encoding utf8
|
$page = Get-Content -LiteralPath (Join-Path $root "pages/auth/$pageName") -Raw -Encoding utf8
|
||||||
Assert-Contains $page '<AuthPageShell' "$pageName must consume AuthPageShell"
|
Assert-Contains $page '<AuthPageShell' "$pageName must consume AuthPageShell"
|
||||||
foreach ($forbidden in @('class="page-canvas"','class="page-backdrop"','mode="scaleToFill"','1665rpx')) {
|
foreach ($forbidden in @('class="page-canvas"','class="page-backdrop"','mode="scaleToFill"','1665rpx')) {
|
||||||
|
|||||||
+39
-99
@@ -1,20 +1,6 @@
|
|||||||
<!-- 页面编号:A-01;用途:APP 唯一登录入口,承载密码、验证码与微信登录入口。 -->
|
<!-- 页面编号:A-01;用途:APP 唯一登录入口,承载密码、验证码与微信登录入口。 -->
|
||||||
<template>
|
<template>
|
||||||
<view class="auth-page login-page">
|
<AuthPageShell class="auth-page login-page">
|
||||||
<view class="auth-header">
|
|
||||||
<image
|
|
||||||
class="auth-header__image"
|
|
||||||
src="/static/assets/modules/auth/opaque/a01-vnext-header-v1.png"
|
|
||||||
mode="widthFix"
|
|
||||||
/>
|
|
||||||
<image
|
|
||||||
class="brand-seal"
|
|
||||||
src="/static/assets/foundation/transparent/brand-seal.png"
|
|
||||||
mode="aspectFit"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="auth-paper">
|
|
||||||
<view class="login-content">
|
<view class="login-content">
|
||||||
<view class="login-heading">
|
<view class="login-heading">
|
||||||
<text class="login-title">登录家谱</text>
|
<text class="login-title">登录家谱</text>
|
||||||
@@ -217,53 +203,55 @@
|
|||||||
>
|
>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 当前只验收自定义容器外观,真实行为验证数据留到接口阶段接入。 -->
|
<template #overlay>
|
||||||
<view
|
<!-- 当前只验收自定义容器外观,真实行为验证数据留到接口阶段接入。 -->
|
||||||
v-if="verificationVisible"
|
<view
|
||||||
class="verification-layer"
|
v-if="verificationVisible"
|
||||||
@click="closeVerification"
|
class="verification-layer"
|
||||||
>
|
@click="closeVerification"
|
||||||
<view class="verification-dialog" @click.stop>
|
>
|
||||||
<image
|
<view class="verification-dialog" @click.stop>
|
||||||
class="verification-dialog__skin"
|
<image
|
||||||
src="/static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png"
|
class="verification-dialog__skin"
|
||||||
mode="aspectFit"
|
src="/static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png"
|
||||||
/>
|
mode="aspectFit"
|
||||||
<view class="verification-dialog__content">
|
/>
|
||||||
<text class="verification-title">安全验证</text>
|
<view class="verification-dialog__content">
|
||||||
<text class="verification-copy"
|
<text class="verification-title">安全验证</text>
|
||||||
>{{ verificationPurpose }}前需要完成行为验证</text
|
<text class="verification-copy"
|
||||||
>
|
>{{ verificationPurpose }}前需要完成行为验证</text
|
||||||
<text class="verification-note">验证数据将在接口接入后启用</text>
|
|
||||||
<view class="verification-actions">
|
|
||||||
<view
|
|
||||||
class="verification-action"
|
|
||||||
hover-class="tap-fade"
|
|
||||||
@click="closeVerification"
|
|
||||||
>取消</view
|
|
||||||
>
|
|
||||||
<view
|
|
||||||
class="verification-action verification-action--primary"
|
|
||||||
hover-class="tap-fade"
|
|
||||||
@click="confirmVerification"
|
|
||||||
>知道了</view
|
|
||||||
>
|
>
|
||||||
|
<text class="verification-note">验证数据将在接口接入后启用</text>
|
||||||
|
<view class="verification-actions">
|
||||||
|
<view
|
||||||
|
class="verification-action"
|
||||||
|
hover-class="tap-fade"
|
||||||
|
@click="closeVerification"
|
||||||
|
>取消</view
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="verification-action verification-action--primary"
|
||||||
|
hover-class="tap-fade"
|
||||||
|
@click="confirmVerification"
|
||||||
|
>知道了</view
|
||||||
|
>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-if="feedbackVisible" class="feedback-toast">
|
<view v-if="feedbackVisible" class="feedback-toast">
|
||||||
<text class="feedback-toast__copy">{{ feedbackMessage }}</text>
|
<text class="feedback-toast__copy">{{ feedbackMessage }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</template>
|
||||||
|
</AuthPageShell>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { onLoad, onUnload } from "@dcloudio/uni-app";
|
import { onLoad, onUnload } from "@dcloudio/uni-app";
|
||||||
|
import AuthPageShell from "@/components/AuthPageShell.vue";
|
||||||
|
|
||||||
const LOGIN_METHOD_STORAGE_KEY = "a01:last-login-method";
|
const LOGIN_METHOD_STORAGE_KEY = "a01:last-login-method";
|
||||||
const activeLoginMethod = ref("password");
|
const activeLoginMethod = ref("password");
|
||||||
@@ -375,54 +363,6 @@ const prepareAgreement = () => showFeedback("协议页面准备中");
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.auth-page {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
|
||||||
min-height: var(--app-viewport-height);
|
|
||||||
overflow-x: hidden;
|
|
||||||
background: #940400;
|
|
||||||
color: #493323;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-header {
|
|
||||||
position: relative;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
|
||||||
height: calc(var(--app-safe-top) + min(36.5vw, 175px));
|
|
||||||
overflow: hidden;
|
|
||||||
padding-top: var(--app-safe-top);
|
|
||||||
background: #940400;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-header__image {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-seal {
|
|
||||||
position: absolute;
|
|
||||||
top: calc(var(--app-safe-top) + clamp(46px, 11.5vw, 58px));
|
|
||||||
left: 50%;
|
|
||||||
width: clamp(56px, 17vw, 78px);
|
|
||||||
height: clamp(67px, 20.4vw, 94px);
|
|
||||||
transform: translateX(-50%);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-paper {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
|
||||||
padding-bottom: var(--app-safe-bottom);
|
|
||||||
background: #f7f0e5 url("/static/assets/foundation/opaque/auth-page-paper.jpg") center top / 100% auto repeat-y;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-content {
|
.login-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ function Assert-NotContains {
|
|||||||
|
|
||||||
$root = Split-Path -Parent $PSScriptRoot
|
$root = Split-Path -Parent $PSScriptRoot
|
||||||
$entryPath = Join-Path $root 'pages/auth/a01-entry.vue'
|
$entryPath = Join-Path $root 'pages/auth/a01-entry.vue'
|
||||||
|
$shellPath = Join-Path $root 'components/AuthPageShell.vue'
|
||||||
$legacyLoginPath = Join-Path $root 'pages/auth/a02-login.vue'
|
$legacyLoginPath = Join-Path $root 'pages/auth/a02-login.vue'
|
||||||
$pagesPath = Join-Path $root 'pages.json'
|
$pagesPath = Join-Path $root 'pages.json'
|
||||||
$globalStylesPath = Join-Path $root 'styles/global.scss'
|
$globalStylesPath = Join-Path $root 'styles/global.scss'
|
||||||
@@ -54,20 +55,22 @@ if ($pages.pages.Count -ne 52) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$entry = Get-Content -LiteralPath $entryPath -Raw -Encoding utf8
|
$entry = Get-Content -LiteralPath $entryPath -Raw -Encoding utf8
|
||||||
|
$shell = Get-Content -LiteralPath $shellPath -Raw -Encoding utf8
|
||||||
$globalStyles = Get-Content -LiteralPath $globalStylesPath -Raw -Encoding utf8
|
$globalStyles = Get-Content -LiteralPath $globalStylesPath -Raw -Encoding utf8
|
||||||
|
|
||||||
# A01 owns content in normal document flow. Decorative artwork must not become
|
# A01 owns content in normal document flow. Decorative artwork must not become
|
||||||
# a second full-page coordinate system.
|
# a second full-page coordinate system.
|
||||||
foreach ($required in @(
|
foreach ($required in @(
|
||||||
'class="auth-header"',
|
'class="auth-shell__header"',
|
||||||
'class="auth-header__image"',
|
'class="auth-shell__header-image"',
|
||||||
'a01-vnext-header-v1.png',
|
'a01-vnext-header-v1.png',
|
||||||
'mode="widthFix"',
|
'mode="widthFix"',
|
||||||
'class="auth-paper"',
|
'class="auth-shell__paper"',
|
||||||
'auth-page-paper.jpg'
|
'auth-page-paper.jpg'
|
||||||
)) {
|
)) {
|
||||||
Assert-Contains -Content $entry -Expected $required -Message "A01 adaptive structure is missing: $required"
|
Assert-Contains -Content $shell -Expected $required -Message "A01 adaptive shell is missing: $required"
|
||||||
}
|
}
|
||||||
|
Assert-Contains -Content $entry -Expected '<AuthPageShell' -Message 'A01 must consume the shared adaptive shell.'
|
||||||
foreach ($forbidden in @(
|
foreach ($forbidden in @(
|
||||||
'class="page-canvas"',
|
'class="page-canvas"',
|
||||||
'class="page-backdrop"',
|
'class="page-backdrop"',
|
||||||
@@ -77,11 +80,12 @@ foreach ($forbidden in @(
|
|||||||
'1665rpx'
|
'1665rpx'
|
||||||
)) {
|
)) {
|
||||||
Assert-NotContains -Content $entry -Unexpected $forbidden -Message "A01 still uses rejected page coordinates: $forbidden"
|
Assert-NotContains -Content $entry -Unexpected $forbidden -Message "A01 still uses rejected page coordinates: $forbidden"
|
||||||
|
Assert-NotContains -Content $shell -Unexpected $forbidden -Message "The adaptive shell uses rejected page coordinates: $forbidden"
|
||||||
}
|
}
|
||||||
|
|
||||||
$pageRule = [regex]::Match($entry, '(?ms)^\.auth-page\s*\{(?<Body>.*?)^\}')
|
$pageRule = [regex]::Match($shell, '(?ms)^\.auth-shell\s*\{(?<Body>.*?)^\}')
|
||||||
$headerRule = [regex]::Match($entry, '(?ms)^\.auth-header\s*\{(?<Body>.*?)^\}')
|
$headerRule = [regex]::Match($shell, '(?ms)^\.auth-shell__header\s*\{(?<Body>.*?)^\}')
|
||||||
$paperRule = [regex]::Match($entry, '(?ms)^\.auth-paper\s*\{(?<Body>.*?)^\}')
|
$paperRule = [regex]::Match($shell, '(?ms)^\.auth-shell__paper\s*\{(?<Body>.*?)^\}')
|
||||||
$contentRule = [regex]::Match($entry, '(?ms)^\.login-content\s*\{(?<Body>.*?)^\}')
|
$contentRule = [regex]::Match($entry, '(?ms)^\.login-content\s*\{(?<Body>.*?)^\}')
|
||||||
foreach ($rule in @($pageRule, $headerRule, $paperRule, $contentRule)) {
|
foreach ($rule in @($pageRule, $headerRule, $paperRule, $contentRule)) {
|
||||||
if (-not $rule.Success) { throw 'A01 adaptive layout rule is missing.' }
|
if (-not $rule.Success) { throw 'A01 adaptive layout rule is missing.' }
|
||||||
@@ -126,7 +130,7 @@ Assert-Contains -Content $dividerRule.Groups['Body'].Value -Expected 'width: 280
|
|||||||
Assert-Contains -Content $dividerRule.Groups['Body'].Value -Expected 'height: 46rpx;' -Message 'A01 compact title divider must retain a legible knot and line weight.'
|
Assert-Contains -Content $dividerRule.Groups['Body'].Value -Expected 'height: 46rpx;' -Message 'A01 compact 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.'
|
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>.*?)^\}')
|
$brandSealRule = [regex]::Match($shell, '(?ms)^\.auth-shell__seal\s*\{(?<Body>.*?)^\}')
|
||||||
if (-not $brandSealRule.Success) {
|
if (-not $brandSealRule.Success) {
|
||||||
throw 'A01 is missing the brand seal style rule.'
|
throw 'A01 is missing the brand seal style rule.'
|
||||||
}
|
}
|
||||||
@@ -153,7 +157,7 @@ foreach ($asset in @(
|
|||||||
'a02-agreement-unchecked.png',
|
'a02-agreement-unchecked.png',
|
||||||
'a02-agreement-checked.png'
|
'a02-agreement-checked.png'
|
||||||
)) {
|
)) {
|
||||||
Assert-Contains -Content $entry -Expected $asset -Message "A01 is missing asset reference: $asset"
|
Assert-Contains -Content ($entry + $shell) -Expected $asset -Message "A01 is missing asset reference: $asset"
|
||||||
}
|
}
|
||||||
foreach ($obsoleteVisualAsset in @(
|
foreach ($obsoleteVisualAsset in @(
|
||||||
'auth-header.png',
|
'auth-header.png',
|
||||||
|
|||||||
@@ -85,12 +85,12 @@ const run = async () => {
|
|||||||
await send('Page.reload')
|
await send('Page.reload')
|
||||||
await waitForFreshDocument(send, previousTimeOrigin)
|
await waitForFreshDocument(send, previousTimeOrigin)
|
||||||
await waitFor(send, "document.querySelectorAll('.login-tab').length === 2", `A01 did not render at ${size.width}x${size.height}`)
|
await waitFor(send, "document.querySelectorAll('.login-tab').length === 2", `A01 did not render at ${size.width}x${size.height}`)
|
||||||
await waitFor(send, "document.querySelector('.auth-header__image img')?.naturalWidth === 824", `A01 header did not load at ${size.width}x${size.height}`)
|
await waitFor(send, "document.querySelector('.auth-shell__header-image img')?.naturalWidth === 824", `A01 header did not load at ${size.width}x${size.height}`)
|
||||||
const metrics = await valueOf(send, `(() => {
|
const metrics = await valueOf(send, `(() => {
|
||||||
const root = document.querySelector('.auth-page')
|
const root = document.querySelector('.auth-page')
|
||||||
const header = document.querySelector('.auth-header')
|
const header = document.querySelector('.auth-shell__header')
|
||||||
const headerImage = document.querySelector('.auth-header__image img')
|
const headerImage = document.querySelector('.auth-shell__header-image img')
|
||||||
const paper = document.querySelector('.auth-paper')
|
const paper = document.querySelector('.auth-shell__paper')
|
||||||
const content = document.querySelector('.login-content')
|
const content = document.querySelector('.login-content')
|
||||||
const headerRect = header?.getBoundingClientRect()
|
const headerRect = header?.getBoundingClientRect()
|
||||||
const paperRect = paper?.getBoundingClientRect()
|
const paperRect = paper?.getBoundingClientRect()
|
||||||
|
|||||||
@@ -29,12 +29,10 @@ foreach ($required in @(
|
|||||||
Assert-Contains -Content $shell -Expected $required -Message "AuthPageShell is missing: $required"
|
Assert-Contains -Content $shell -Expected $required -Message "AuthPageShell is missing: $required"
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($pageName in @('a01-entry.vue', 'a04-register.vue', 'a05-reset-password.vue', 'a06-auth-status.vue')) {
|
$page = Get-Content -LiteralPath (Join-Path $root 'pages/auth/a01-entry.vue') -Raw -Encoding utf8
|
||||||
$page = Get-Content -LiteralPath (Join-Path $root "pages/auth/$pageName") -Raw -Encoding utf8
|
Assert-Contains -Content $page -Expected '<AuthPageShell' -Message 'A01 must consume AuthPageShell'
|
||||||
Assert-Contains -Content $page -Expected '<AuthPageShell' -Message "$pageName must consume AuthPageShell"
|
foreach ($forbidden in @('class="page-canvas"', 'class="page-backdrop"', 'mode="scaleToFill"', '1665rpx')) {
|
||||||
foreach ($forbidden in @('class="page-canvas"', 'class="page-backdrop"', 'mode="scaleToFill"', '1665rpx')) {
|
Assert-NotContains -Content $page -Unexpected $forbidden -Message "A01 retains rejected page coordinates: $forbidden"
|
||||||
Assert-NotContains -Content $page -Unexpected $forbidden -Message "$pageName retains rejected page coordinates: $forbidden"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Output 'AUTH-PAGE-SHELL-CONTRACT PASS'
|
Write-Output 'AUTH-PAGE-SHELL-CONTRACT PASS'
|
||||||
|
|||||||
Reference in New Issue
Block a user