feat: polish user auth entrance
This commit is contained in:
@@ -16,6 +16,16 @@ export default function UserRegister({ onLogin }: { onLogin?: () => void }) {
|
||||
const [captchaLoading, setCaptchaLoading] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const passwordScore = [
|
||||
password.length >= 6,
|
||||
/[A-Za-z]/.test(password) && /\d/.test(password),
|
||||
password.length >= 10,
|
||||
].filter(Boolean).length;
|
||||
const passwordTone = passwordScore >= 3 ? 'strong' : passwordScore >= 2 ? 'ok' : 'weak';
|
||||
const passwordLabel = password
|
||||
? (passwordScore >= 3 ? '强' : passwordScore >= 2 ? '可用' : '偏弱')
|
||||
: '等待输入';
|
||||
const passwordsMatch = Boolean(confirmPassword) && password === confirmPassword;
|
||||
|
||||
const loadCaptcha = useCallback(async () => {
|
||||
setCaptchaLoading(true);
|
||||
@@ -97,7 +107,7 @@ export default function UserRegister({ onLogin }: { onLogin?: () => void }) {
|
||||
variant="user"
|
||||
eyebrow="用户注册"
|
||||
title="创建 BossAI 账号"
|
||||
description="完成验证后会自动进入个人工作台,并生成你的默认 API Key。"
|
||||
description="注册后自动进入个人控制台,并创建默认 API Key。"
|
||||
error={error}
|
||||
footer={(
|
||||
<div className="login-auth-footer-links">
|
||||
@@ -153,6 +163,12 @@ export default function UserRegister({ onLogin }: { onLogin?: () => void }) {
|
||||
autoComplete="new-password"
|
||||
required
|
||||
/>
|
||||
<div className="login-password-meter" data-tone={passwordTone}>
|
||||
<span />
|
||||
<span />
|
||||
<span />
|
||||
<strong>{passwordLabel}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div className="login-field-group">
|
||||
<label className="login-auth-label" htmlFor="reg-confirm-password-input">确认密码</label>
|
||||
@@ -166,6 +182,11 @@ export default function UserRegister({ onLogin }: { onLogin?: () => void }) {
|
||||
autoComplete="new-password"
|
||||
required
|
||||
/>
|
||||
{confirmPassword ? (
|
||||
<div className={`login-field-hint ${passwordsMatch ? 'is-ok' : 'is-error'}`}>
|
||||
{passwordsMatch ? '两次密码一致' : '两次密码不一致'}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="login-captcha-panel">
|
||||
@@ -204,7 +225,8 @@ export default function UserRegister({ onLogin }: { onLogin?: () => void }) {
|
||||
</button>
|
||||
</div>
|
||||
<div className="login-register-hint">
|
||||
注册会受到验证码和频率限制保护,避免批量注册影响服务稳定性。
|
||||
<strong>注册保护已启用</strong>
|
||||
<span>图像验证码和频率限制会拦截批量注册,默认 API Key 会在账号创建后自动生成。</span>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
|
||||
@@ -28,6 +28,18 @@ const LOGIN_METRICS: Array<{ valueKey: string; labelKey: string }> = [
|
||||
{ valueKey: '/v1', labelKey: '统一兼容入口' },
|
||||
];
|
||||
|
||||
const USER_METRICS: Array<{ valueKey: string; labelKey: string }> = [
|
||||
{ valueKey: 'Key', labelKey: '注册后默认创建' },
|
||||
{ valueKey: '日志', labelKey: '调用内容与费用可查' },
|
||||
{ valueKey: '空间', labelKey: '产出自动归档' },
|
||||
];
|
||||
|
||||
const USER_CAPABILITY_KEYS: Array<{ titleKey: string; descKey: string }> = [
|
||||
{ titleKey: '模型创作入口', descKey: '对话、生图、视频助手与数字人任务统一进入个人控制台' },
|
||||
{ titleKey: 'API Key 与计费', descKey: '默认 Key 可直接调用兼容接口,每次请求都有日志和费用记录' },
|
||||
{ titleKey: '工作空间归档', descKey: '只保存最终产出的图片、视频和音频,素材上传不会混进资产库' },
|
||||
];
|
||||
|
||||
export default function LoginShell({
|
||||
t,
|
||||
eyebrow,
|
||||
@@ -38,66 +50,60 @@ export default function LoginShell({
|
||||
footer,
|
||||
children,
|
||||
}: LoginShellProps) {
|
||||
const metrics = variant === 'user' ? USER_METRICS : LOGIN_METRICS;
|
||||
const capabilities = variant === 'user' ? USER_CAPABILITY_KEYS : ADMIN_CAPABILITY_KEYS;
|
||||
|
||||
return (
|
||||
<div className="login-shell" data-variant={variant}>
|
||||
<MotionDiv className="login-surface animate-scale-in" motionVariant="login" motionKey={variant}>
|
||||
<section
|
||||
className="login-brand-panel login-brand-panel-light"
|
||||
style={variant === 'user' ? { padding: '28px 24px' } : undefined}
|
||||
>
|
||||
<div
|
||||
className="login-brand-header"
|
||||
style={variant === 'user' ? { justifyContent: 'center' } : undefined}
|
||||
>
|
||||
<section className="login-brand-panel login-brand-panel-light">
|
||||
<div className="login-brand-header">
|
||||
<div className="brand-mark-frame brand-mark-frame-hero">
|
||||
<div className="brand-mark-canvas">
|
||||
<img src="/logo.png" alt="BossAI" className="login-brand-logo" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="login-brand-summary"
|
||||
style={variant === 'user' ? { textAlign: 'center' } : undefined}
|
||||
>
|
||||
<div className="login-brand-summary">
|
||||
<div className="login-brand-name">BossAI</div>
|
||||
<div className="login-brand-kicker">
|
||||
{variant === 'user' ? t('创作者工作台') : t('多模型多模态 AI 应用平台')}
|
||||
{variant === 'user' ? t('用户创作控制台') : t('多模型多模态 AI 应用平台')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{variant === 'admin' && (
|
||||
<>
|
||||
<div className="login-brand-copy-block">
|
||||
<p className="login-brand-copy">
|
||||
{t('聚合对话、生图、视频、数字人与工作流编排,把 API 中转、模型路由和内容生产放进一套可运营的平台。')}
|
||||
</p>
|
||||
<div className="login-brand-copy-block">
|
||||
<p className="login-brand-copy">
|
||||
{variant === 'user'
|
||||
? t('登录后即可使用模型广场、视频助手、生图助手、工作流和工作空间,把创意任务沉淀成可追踪的产出。')
|
||||
: t('聚合对话、生图、视频、数字人与工作流编排,把 API 中转、模型路由和内容生产放进一套可运营的平台。')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="login-compat-line">
|
||||
{variant === 'user'
|
||||
? t('支持 OpenAI / Claude Messages / 图像 / 视频 / Comfy Cloud 工作流')
|
||||
: t('兼容 OpenAI / Claude Messages / Comfy Cloud / RunningHub / 火山方舟等多类上游')}
|
||||
</div>
|
||||
<div className="login-metric-grid" aria-label={t(variant === 'user' ? '用户能力概览' : '平台能力指标')}>
|
||||
{metrics.map((metric) => (
|
||||
<div key={metric.labelKey} className="login-metric-card">
|
||||
<strong>{t(metric.valueKey)}</strong>
|
||||
<span>{t(metric.labelKey)}</span>
|
||||
</div>
|
||||
<div className="login-compat-line">
|
||||
{t('兼容 OpenAI / Claude Messages / Comfy Cloud / RunningHub / 火山方舟等多类上游')}
|
||||
))}
|
||||
</div>
|
||||
<div className="login-capability-list">
|
||||
{capabilities.map((feature, index) => (
|
||||
<div key={feature.titleKey} className="login-capability-row">
|
||||
<div className="login-capability-index">
|
||||
{String(index + 1).padStart(2, '0')}
|
||||
</div>
|
||||
<div className="login-capability-content">
|
||||
<div className="login-capability-title">{t(feature.titleKey)}</div>
|
||||
<p className="login-capability-desc">{t(feature.descKey)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="login-metric-grid" aria-label={t('平台能力指标')}>
|
||||
{LOGIN_METRICS.map((metric) => (
|
||||
<div key={metric.labelKey} className="login-metric-card">
|
||||
<strong>{t(metric.valueKey)}</strong>
|
||||
<span>{t(metric.labelKey)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="login-capability-list">
|
||||
{ADMIN_CAPABILITY_KEYS.map((feature, index) => (
|
||||
<div key={feature.titleKey} className="login-capability-row">
|
||||
<div className="login-capability-index">
|
||||
{String(index + 1).padStart(2, '0')}
|
||||
</div>
|
||||
<div className="login-capability-content">
|
||||
<div className="login-capability-title">{t(feature.titleKey)}</div>
|
||||
<p className="login-capability-desc">{t(feature.descKey)}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
<section className="login-auth-stage">
|
||||
<div className="login-auth-panel">
|
||||
|
||||
@@ -95,22 +95,28 @@ export default function UserLoginPage({ t, onLogin }: UserLoginPageProps) {
|
||||
};
|
||||
|
||||
const description = mode === 'account'
|
||||
? t('使用邮箱和密码登录你的账号。')
|
||||
: t('使用账户中已签发的 API Key 直接登录。');
|
||||
? t('进入个人控制台,继续使用模型、工作流和工作空间。')
|
||||
: t('适合开发者和自动化脚本,使用账户中已签发的 Key 进入。');
|
||||
const modeContext = mode === 'account'
|
||||
? {
|
||||
title: t('推荐方式'),
|
||||
desc: t('邮箱密码会保留完整的个人中心、工作空间和使用日志权限。'),
|
||||
}
|
||||
: {
|
||||
title: t('开发者入口'),
|
||||
desc: t('仅识别已绑定到当前账号的 API Key,登录后仍会进入同一个用户控制台。'),
|
||||
};
|
||||
const footer = (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'center' }}>
|
||||
<div className="login-auth-footer-links">
|
||||
<span>
|
||||
{t('还没有账号?')}
|
||||
<Link
|
||||
to="/user/register"
|
||||
style={{ color: 'var(--color-primary)', textDecoration: 'none', marginLeft: 4 }}
|
||||
>
|
||||
<Link to="/user/register">
|
||||
{t('注册')}
|
||||
</Link>
|
||||
</span>
|
||||
<span style={{ fontSize: 12, color: 'var(--color-text-muted)' }}>
|
||||
<Link to="/boos" style={{ color: 'var(--color-text-muted)' }}>
|
||||
← {t('管理员登录')}
|
||||
<span>
|
||||
<Link to="/boos">
|
||||
{t('管理员登录')}
|
||||
</Link>
|
||||
</span>
|
||||
</div>
|
||||
@@ -152,6 +158,10 @@ export default function UserLoginPage({ t, onLogin }: UserLoginPageProps) {
|
||||
{t('API Key')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="login-mode-context">
|
||||
<span>{modeContext.title}</span>
|
||||
<p>{modeContext.desc}</p>
|
||||
</div>
|
||||
{mode === 'account' ? (
|
||||
<form onSubmit={handleAccountSubmit}>
|
||||
<label className="login-auth-label" htmlFor="user-email-input">
|
||||
|
||||
Reference in New Issue
Block a user