两步验证 · 场景文档

本页由 3 个真实组件拼成;提示词与源码都可直接复制带走。

整页提示词

复现「两步验证」整页场景(/showcase/demo/auth):
- 页面级规则按 /design#pages 的对应小节执行,不在此复述
- 本页由这些真实组件拼成(手写等价物不算):
- 玻璃提示条(Notification Banner):页面级状态:常驻、不自动消失,处理完或手动关闭才走|/showcase/component/banner/glass-apple
- 玻璃按钮(Liquid Glass Button):半透明底 + 1px 细边框 + 背景模糊,做出玻璃厚度|/showcase/component/button/glass-apple
- 玻璃验证码输入(One-Time Code Input):一个真输入框 + 画的格子:逐格 input 会打断 autocomplete=one-time-code 自动填充|/showcase/component/otp/glass-apple
- 骨架:视口高度固定,页头不滚,正文在容器内滚;分区页的页头与正文共用同一条内容列

本页用到的组件

清单是扫 src/pages/showcase/demo/auth.astro 的 import 闭包得出的, 手写等价物不会出现在这里。

一条链接装走整页

/r/scene--auth.json /llms/scenes/auth.md

auth.astro src/pages/showcase/demo/auth.astro

---
import BaseLayout from '../../../layouts/BaseLayout.astro';
import DemoChrome from '../../../components/DemoChrome.astro';
import PageHeader from '../../../components/PageHeader.astro';
import PageShell from '../../../components/PageShell.astro';
import SceneDocLink from '../../../components/SceneDocLink.astro';
import AuthVerify from './AuthVerify.tsx';
---
<BaseLayout title="UI Echo · 两步验证复现">
  <DemoChrome />
  <PageShell demo col colWidth="30rem">
    <PageHeader
      slot="header"
      title="两步验证"
      description="登录的第二步:验证码错了要看得懂后果,也要填得进去。"
      size="sm"
    >
      <SceneDocLink id="auth" slot="actions" />
    </PageHeader>

    <!-- 单任务页:整列在正文区垂直居中,主操作留在表单末尾而不是页头右上 -->
    <div class="my-auto">
      <AuthVerify client:visible />
    </div>
  </PageShell>
</BaseLayout>

AuthVerify.tsx src/pages/showcase/demo/AuthVerify.tsx

import { useState, type FormEvent } from 'react';
import { GlassAppleBanner } from '~/components/banner/glass-apple/Banner';
import { GlassAppleButton } from '~/components/button/glass-apple/Button';
import { GlassAppleOtp } from '~/components/otp/glass-apple/Otp';

type Status = 'idle' | 'error' | 'done';

const ACCOUNT = 'gqy@example.com';

/**
 * 两步验证:验证码错了怎么办——页面级 banner 常驻说清后果,
 * 字段级 errorMessage 说清该填什么,两层各管一件事(决策 17)。
 */
export default function AuthVerify() {
  const [code, setCode] = useState('');
  const [status, setStatus] = useState<Status>('idle');
  const [busy, setBusy] = useState(false);
  const [sentAt, setSentAt] = useState(1);

  const submit = (e: FormEvent) => {
    e.preventDefault();
    if (code.length < 6) {
      setStatus('error');
      return;
    }
    setBusy(true);
    window.setTimeout(() => {
      setBusy(false);
      setStatus('done');
      setCode('');
    }, 600);
  };

  return (
    <div className="my-auto w-full space-y-5">
      <p className="text-sm text-[var(--color-text-muted)]">
        我们把 6 位验证码发到了 <span className="text-[var(--color-text)]">{ACCOUNT}</span>。
      </p>

      {status === 'error' && (
        <GlassAppleBanner
          tone="warning"
          title="验证码不正确或已过期"
          description="验证码 5 分钟内有效。超过 5 次错误会暂时锁定登录。"
          action={
            <GlassAppleButton
              size="sm"
              onClick={() => {
                setSentAt((n) => n + 1);
                setCode('');
                setStatus('idle');
              }}
            >
              重新发送
            </GlassAppleButton>
          }
        />
      )}

      {status === 'done' && (
        <GlassAppleBanner tone="success" title="验证通过" description="这次登录已确认。" />
      )}

      <form onSubmit={submit} noValidate>
        <GlassAppleOtp
          label="验证码"
          hint={`6 位数字,5 分钟内有效(第 ${sentAt} 次发送)`}
          error={status === 'error'}
          errorMessage={status === 'error' ? '只填数字,不需要空格或短横线' : undefined}
          length={6}
          value={code}
          onChange={(v) => {
            setCode(v);
            if (status === 'error') setStatus('idle');
          }}
        />

        <div className="mt-6 flex items-center justify-between gap-4 flex-wrap">
          <a
            href="/showcase/demo/page-settings"
            className="text-sm text-[var(--color-text-muted)] hover:text-[var(--color-text)] underline underline-offset-4"
          >
            用备用验证方式
          </a>
          <GlassAppleButton type="submit" loading={busy}>
            {busy ? '验证中' : '验证并继续'}
          </GlassAppleButton>
        </div>
      </form>
    </div>
  );
}

页面级规则(页头、工具栏、表格、设置、详情、流程、状态)只写在 页面模式 一处,这里不复述——复述就会漂移。