大量修改UI
This commit is contained in:
@@ -2,19 +2,24 @@ import { useEffect, useRef, useState } from "react";
|
||||
import type { FormEvent } from "react";
|
||||
import {
|
||||
ArrowRight,
|
||||
Boxes,
|
||||
Eye,
|
||||
EyeOff,
|
||||
Info,
|
||||
LoaderCircle,
|
||||
Lock,
|
||||
LockKeyhole,
|
||||
ScanLine,
|
||||
ShieldCheck,
|
||||
Sparkles,
|
||||
Ticket,
|
||||
UserRound,
|
||||
Users
|
||||
Users,
|
||||
Workflow
|
||||
} from "lucide-react";
|
||||
import { api, getRemember, setRemember as persistRemember } from "../api";
|
||||
import type { Team, User } from "../types";
|
||||
import type { AuthMode } from "./route-config";
|
||||
import "../login-page.css";
|
||||
|
||||
type LoginProgress = "checking" | "entering";
|
||||
|
||||
@@ -23,6 +28,10 @@ const LOGIN_PROGRESS_COPY: Record<LoginProgress, string> = {
|
||||
entering: "登录成功,正在进入工作台…"
|
||||
};
|
||||
|
||||
const LOGIN_PLUS_MARKS = Array.from({ length: 168 }, (_, index) => (
|
||||
<span key={index}>+</span>
|
||||
));
|
||||
|
||||
type FieldErrors = {
|
||||
username?: string;
|
||||
password?: string;
|
||||
@@ -252,48 +261,76 @@ export function AuthScreen({
|
||||
}
|
||||
|
||||
const brand = (
|
||||
<aside className="auth-brand">
|
||||
<div className="logo"><img className="logo-img" src="/assets/logo-dark.png" alt="Airshelf" /></div>
|
||||
<div className="tag">// SHORT-VIDEO COMMERCE PLATFORM</div>
|
||||
<div className="hero">
|
||||
{mode === "register" ? (
|
||||
<>
|
||||
<h1>开通团队,<br />拍出第一条 <span className="h">带货短剧</span></h1>
|
||||
<p>1-2 小时,从一张商品图,到一条能卖货的短剧。</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<h1>让每件商品<br />都有自己的<span className="h">带货短剧</span></h1>
|
||||
<p>商品图进,带货短剧出 —— 你只管挑成片。</p>
|
||||
</>
|
||||
)}
|
||||
<aside className="login-visual">
|
||||
<div className="login-brand-lockup">
|
||||
<img className="login-logo" src="/assets/yz/logo-on-dark.png" alt="影擎" />
|
||||
<div className="login-brand-copy"><span>YINGQING AIGC STUDIO</span></div>
|
||||
</div>
|
||||
<div className="login-hero">
|
||||
<p className="login-eyebrow">// AIGC COMMERCE CONTENT ENGINE</p>
|
||||
<div className="login-hero-copy">
|
||||
<h1>
|
||||
<span className="login-hero-line">让每一次商品表达,</span>
|
||||
<span className="login-hero-line login-hero-accent">更快成为内容。</span>
|
||||
</h1>
|
||||
<p>从商品资产、脚本、故事板到视频生成,以统一的创作工作流连接品牌内容生产的每一个环节。</p>
|
||||
</div>
|
||||
<div className="login-capabilities" aria-label="平台能力">
|
||||
<div className="login-capability">
|
||||
<Sparkles />
|
||||
<strong>智能创作</strong>
|
||||
<span>图像与视频生成</span>
|
||||
</div>
|
||||
<div className="login-capability">
|
||||
<Workflow />
|
||||
<strong>流程协同</strong>
|
||||
<span>五阶段项目管理</span>
|
||||
</div>
|
||||
<div className="login-capability">
|
||||
<Boxes />
|
||||
<strong>资产沉淀</strong>
|
||||
<span>商品、角色与场景</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="login-visual-footer">
|
||||
<span>© 2026 影擎智能内容平台</span>
|
||||
<span className="login-system-status">创作服务正常</span>
|
||||
</div>
|
||||
<div className="foot"><a href="#">关于</a><a href="#">定价</a><a href="#">联系</a><a href="#">隐私</a></div>
|
||||
</aside>
|
||||
);
|
||||
|
||||
const toastNode = toast ? (
|
||||
<div className="login-toast" role="status" aria-live="polite">
|
||||
{toast.title}
|
||||
<span>{toast.sub}</span>
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
if (mode === "register") {
|
||||
return (
|
||||
<main className="auth-exact-page auth-register-page">
|
||||
<div className="auth-wrap">
|
||||
<span className="corner-tr" aria-hidden="true"></span><span className="corner-bl" aria-hidden="true"></span>
|
||||
{brand}
|
||||
<section className="auth-form" aria-label="注册">
|
||||
<div className="h-row"><h2>注册</h2><span className="sub">// /auth/register</span></div>
|
||||
<p className="lead">
|
||||
{inviteState.status === "valid"
|
||||
? inviteState.kind === "create_team"
|
||||
? "邀请码有效 · 开新团队,你将成为团队超管。"
|
||||
: `邀请码有效 · 加入「${inviteState.teamName || "团队"}」。`
|
||||
: "输入邀请码开通账户 —— 没有码请联系团队管理员或平台。"}
|
||||
</p>
|
||||
<form id="register-form" autoComplete="off" onSubmit={submitRegister} noValidate>
|
||||
{/* 第一步:邀请码(必填)· 验过才展开后续表单 */}
|
||||
<div className={`field${inviteState.status === "invalid" ? " has-error" : ""}`}>
|
||||
<label className="field-label" htmlFor="reg-invite">邀请码 <span className="req">*</span></label>
|
||||
<div className="invite-verify-row">
|
||||
<div className="field-input-wrap">
|
||||
<Ticket className="ic-l" size={16} strokeWidth={1.5} aria-hidden="true" />
|
||||
<section className="login-page" aria-labelledby="loginTitle">
|
||||
{brand}
|
||||
<main className="login-form-panel">
|
||||
<div className="login-plus-pattern" aria-hidden="true">{LOGIN_PLUS_MARKS}</div>
|
||||
<div className="login-card is-register">
|
||||
<div className="login-card-heading">
|
||||
<span className="login-step"><ShieldCheck />安全访问</span>
|
||||
<h2 id="loginTitle">开通账户</h2>
|
||||
<p>
|
||||
{inviteState.status === "valid"
|
||||
? inviteState.kind === "create_team"
|
||||
? "邀请码有效 · 开新团队,你将成为团队超管。"
|
||||
: `邀请码有效 · 加入「${inviteState.teamName || "团队"}」。`
|
||||
: "输入邀请码开通账户 —— 没有码请联系团队管理员或平台。"}
|
||||
</p>
|
||||
</div>
|
||||
<form className="login-form" autoComplete="off" onSubmit={submitRegister} noValidate>
|
||||
<label className={`login-field${inviteState.status === "invalid" ? " has-error" : ""}`}>
|
||||
<span>邀请码 <em className="req">*</em></span>
|
||||
<div className="login-verify">
|
||||
<span className="login-input-wrap">
|
||||
<Ticket />
|
||||
<input
|
||||
id="reg-invite"
|
||||
type="text"
|
||||
@@ -304,33 +341,32 @@ export function AuthScreen({
|
||||
onChange={(event) => onInviteChange(event.target.value)}
|
||||
onKeyDown={(event) => { if (event.key === "Enter") { event.preventDefault(); void checkInvite(); } }}
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="btn invite-verify-btn"
|
||||
className="login-verify-btn"
|
||||
disabled={inviteState.status === "checking" || !regInvite.trim()}
|
||||
onClick={() => void checkInvite()}
|
||||
>
|
||||
{inviteState.status === "checking" ? "验证中…" : inviteState.status === "valid" ? "重新验证" : "验证"}
|
||||
</button>
|
||||
</div>
|
||||
{inviteState.status === "invalid" && <p className="field-note err">{inviteState.detail}</p>}
|
||||
{inviteState.status === "invalid" && <p className="login-note err">{inviteState.detail}</p>}
|
||||
{inviteState.status === "valid" && (
|
||||
<p className="field-note ok">
|
||||
<ShieldCheck size={13} strokeWidth={1.8} aria-hidden="true" />
|
||||
<p className="login-note ok">
|
||||
<ShieldCheck size={13} />
|
||||
{inviteState.kind === "create_team" ? "可开新团队" : `将加入「${inviteState.teamName || "团队"}」`}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
{/* 第二步:验过码后展开 —— 不暴露任何团队列表 */}
|
||||
{inviteState.status === "valid" && (
|
||||
<>
|
||||
{inviteState.kind === "create_team" ? (
|
||||
<div className={`field${regErrors.team ? " has-error" : ""}`}>
|
||||
<label className="field-label" htmlFor="reg-team">团队名 <span className="req">*</span></label>
|
||||
<div className="field-input-wrap">
|
||||
<Users className="ic-l" size={16} strokeWidth={1.5} aria-hidden="true" />
|
||||
<label className={`login-field${regErrors.team ? " has-error" : ""}`}>
|
||||
<span>团队名 <em className="req">*</em></span>
|
||||
<span className="login-input-wrap">
|
||||
<Users />
|
||||
<input
|
||||
id="reg-team"
|
||||
type="text"
|
||||
@@ -339,23 +375,23 @@ export function AuthScreen({
|
||||
aria-invalid={Boolean(regErrors.team)}
|
||||
onChange={(event) => setRegValue("team", event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</label>
|
||||
) : (
|
||||
<div className="field">
|
||||
<label className="field-label">团队</label>
|
||||
<div className="field-input-wrap is-readonly">
|
||||
<Users className="ic-l" size={16} strokeWidth={1.5} aria-hidden="true" />
|
||||
<label className="login-field">
|
||||
<span>团队</span>
|
||||
<span className="login-input-wrap">
|
||||
<Users />
|
||||
<input type="text" value={inviteState.teamName || "已绑定团队"} readOnly disabled />
|
||||
</div>
|
||||
<p className="field-note">邀请码已绑定该团队,你将作为成员加入。</p>
|
||||
</div>
|
||||
</span>
|
||||
<p className="login-note">邀请码已绑定该团队,你将作为成员加入。</p>
|
||||
</label>
|
||||
)}
|
||||
|
||||
<div className={`field${regErrors.username ? " has-error" : ""}`}>
|
||||
<label className="field-label" htmlFor="reg-username">用户名 <span className="req">*</span></label>
|
||||
<div className="field-input-wrap">
|
||||
<UserRound className="ic-l" size={16} strokeWidth={1.5} aria-hidden="true" />
|
||||
<label className={`login-field${regErrors.username ? " has-error" : ""}`}>
|
||||
<span>用户名 <em className="req">*</em></span>
|
||||
<span className="login-input-wrap">
|
||||
<UserRound />
|
||||
<input
|
||||
id="reg-username"
|
||||
type="text"
|
||||
@@ -365,14 +401,14 @@ export function AuthScreen({
|
||||
aria-invalid={Boolean(regErrors.username)}
|
||||
onChange={(event) => setRegValue("username", event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<div className="field-row">
|
||||
<div className={`field${regErrors.password ? " has-error" : ""}`}>
|
||||
<label className="field-label" htmlFor="reg-pwd">密码 <span className="req">*</span></label>
|
||||
<div className="field-input-wrap">
|
||||
<LockKeyhole className="ic-l" size={16} strokeWidth={1.5} aria-hidden="true" />
|
||||
<div className="login-pass-row">
|
||||
<label className={`login-field${regErrors.password ? " has-error" : ""}`}>
|
||||
<span>密码 <em className="req">*</em></span>
|
||||
<span className="login-input-wrap">
|
||||
<LockKeyhole />
|
||||
<input
|
||||
id="reg-pwd"
|
||||
type={regShowPwd ? "text" : "password"}
|
||||
@@ -384,20 +420,18 @@ export function AuthScreen({
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className={`toggle-pwd${regShowPwd ? " active" : ""}`}
|
||||
className="login-password-toggle"
|
||||
aria-label={regShowPwd ? "隐藏密码" : "显示密码"}
|
||||
aria-pressed={regShowPwd}
|
||||
title={regShowPwd ? "隐藏密码" : "显示密码"}
|
||||
onClick={() => setRegShowPwd((value) => !value)}
|
||||
>
|
||||
{regShowPwd ? <EyeOff size={18} strokeWidth={1.5} aria-hidden="true" /> : <Eye size={18} strokeWidth={1.5} aria-hidden="true" />}
|
||||
{regShowPwd ? <EyeOff /> : <Eye />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`field${regErrors.password2 ? " has-error" : ""}`}>
|
||||
<label className="field-label" htmlFor="reg-pwd2">确认密码 <span className="req">*</span></label>
|
||||
<div className="field-input-wrap">
|
||||
<LockKeyhole className="ic-l" size={16} strokeWidth={1.5} aria-hidden="true" />
|
||||
</span>
|
||||
</label>
|
||||
<label className={`login-field${regErrors.password2 ? " has-error" : ""}`}>
|
||||
<span>确认密码 <em className="req">*</em></span>
|
||||
<span className="login-input-wrap">
|
||||
<LockKeyhole />
|
||||
<input
|
||||
id="reg-pwd2"
|
||||
type={regShowPwd2 ? "text" : "password"}
|
||||
@@ -409,19 +443,17 @@ export function AuthScreen({
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className={`toggle-pwd${regShowPwd2 ? " active" : ""}`}
|
||||
className="login-password-toggle"
|
||||
aria-label={regShowPwd2 ? "隐藏密码" : "显示密码"}
|
||||
aria-pressed={regShowPwd2}
|
||||
title={regShowPwd2 ? "隐藏密码" : "显示密码"}
|
||||
onClick={() => setRegShowPwd2((value) => !value)}
|
||||
>
|
||||
{regShowPwd2 ? <EyeOff size={18} strokeWidth={1.5} aria-hidden="true" /> : <Eye size={18} strokeWidth={1.5} aria-hidden="true" />}
|
||||
{regShowPwd2 ? <EyeOff /> : <Eye />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label className={`agree${regErrors.agree ? " has-error" : ""}`}>
|
||||
<label className={`login-agree${regErrors.agree ? " has-error" : ""}`}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={regAgree}
|
||||
@@ -429,76 +461,72 @@ export function AuthScreen({
|
||||
/>
|
||||
<span>
|
||||
我已阅读并同意{" "}
|
||||
<a href="#" onClick={(event) => { event.preventDefault(); showToast("用户协议", "含数据处理 / 内容生成版权 / 计费规则三章 · 完整文本将在正式版接入"); }}>用户协议</a>{" "}
|
||||
与{" "}
|
||||
<a href="#" onClick={(event) => { event.preventDefault(); showToast("用户协议", "含数据处理 / 内容生成版权 / 计费规则三章 · 完整文本将在正式版接入"); }}>用户协议</a>
|
||||
{" "}与{" "}
|
||||
<a href="#" onClick={(event) => { event.preventDefault(); showToast("隐私政策", "遵循《个人信息保护法》· 团队数据存中国境内 · 默认不用于模型训练"); }}>隐私政策</a>。
|
||||
</span>
|
||||
</label>
|
||||
|
||||
{loginProgress && !error && (
|
||||
<div className="login-progress" role="status" aria-live="polite">
|
||||
<span className="login-progress-spinner" aria-hidden="true"></span>
|
||||
<span>{inviteState.kind === "create_team" ? "正在创建团队,进入工作台…" : "正在加入团队,进入工作台…"}</span>
|
||||
</div>
|
||||
)}
|
||||
{error && <div className="form-error" role="alert">{error}</div>}
|
||||
|
||||
<button className="btn-cta" type="submit" disabled={busy}>
|
||||
<p className={`login-error${error ? " show" : ""}`} role="alert">{error || "\u00a0"}</p>
|
||||
<button className="login-submit" type="submit" disabled={busy}>
|
||||
{busy ? <LoaderCircle /> : <ArrowRight />}
|
||||
{busy
|
||||
? "提交中"
|
||||
: inviteState.kind === "create_team"
|
||||
? <>创建团队 · 开始使用 <ArrowRight size={15} strokeWidth={2} aria-hidden="true" /></>
|
||||
: <>加入团队 · 开始使用 <ArrowRight size={15} strokeWidth={2} aria-hidden="true" /></>}
|
||||
? "创建团队 · 开始使用"
|
||||
: "加入团队 · 开始使用"}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{inviteState.status !== "valid" && error && <div className="form-error" role="alert">{error}</div>}
|
||||
|
||||
<div className="switch-row">
|
||||
已有账号? <a href="/login" onClick={(event) => { event.preventDefault(); switchMode("login"); }}>去登录 →</a>
|
||||
</div>
|
||||
{inviteState.status !== "valid" && (
|
||||
<p className={`login-error${error ? " show" : ""}`} role="alert">{error || "\u00a0"}</p>
|
||||
)}
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
{toast && (
|
||||
<div className="toast show" role="status" aria-live="polite">
|
||||
<div className="ic-t"><Info size={13} aria-hidden="true" /></div>
|
||||
<div className="txt">{toast.title}<span className="mono">// {toast.sub}</span></div>
|
||||
<p className="login-card-footer"><Lock />登录信息仅用于当前演示会话</p>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
<div className="login-extras">
|
||||
<p className="login-switch">
|
||||
已有账号? <a href="/login" onClick={(event) => { event.preventDefault(); switchMode("login"); }}>去登录 →</a>
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
{toastNode}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="auth-exact-page auth-login-page">
|
||||
<div className="auth-wrap">
|
||||
<span className="corner-tr" aria-hidden="true"></span><span className="corner-bl" aria-hidden="true"></span>
|
||||
{brand}
|
||||
<section className="auth-form" aria-label="登录">
|
||||
<div className="h-row"><h2>登录</h2><span className="sub">// /auth/login</span></div>
|
||||
<form id="login-form" autoComplete="off" onSubmit={submitLogin} noValidate>
|
||||
<p className="lead">用团队账号登录,进入你的工作台与生产管线。</p>
|
||||
<div className={`field${fieldErrors.username ? " has-error" : ""}`}>
|
||||
<label className="field-label" htmlFor="auth-username">用户名 <span className="req">*</span></label>
|
||||
<div className="field-input-wrap">
|
||||
<UserRound className="ic-l" size={16} strokeWidth={1.5} aria-hidden="true" />
|
||||
<section className="login-page" aria-labelledby="loginTitle">
|
||||
{brand}
|
||||
<main className="login-form-panel">
|
||||
<div className="login-plus-pattern" aria-hidden="true">{LOGIN_PLUS_MARKS}</div>
|
||||
<div className={`login-card${error ? " shake" : ""}`}>
|
||||
<div className="login-card-heading">
|
||||
<span className="login-step"><ShieldCheck />安全访问</span>
|
||||
<h2 id="loginTitle">欢迎回来</h2>
|
||||
<p>请输入影擎账号与密码,进入内容创作工作台。</p>
|
||||
</div>
|
||||
<form className="login-form" autoComplete="off" onSubmit={submitLogin} noValidate>
|
||||
<label className={`login-field${fieldErrors.username ? " has-error" : ""}`}>
|
||||
<span>账号</span>
|
||||
<span className="login-input-wrap">
|
||||
<UserRound />
|
||||
<input
|
||||
id="auth-username"
|
||||
type="text"
|
||||
placeholder="请输入用户名"
|
||||
placeholder="请输入账号"
|
||||
value={username}
|
||||
autoComplete="username"
|
||||
aria-invalid={Boolean(fieldErrors.username)}
|
||||
onChange={(event) => setFieldValue("username", event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`field${fieldErrors.password ? " has-error" : ""}`}>
|
||||
<label className="field-label" htmlFor="auth-pwd">密码 <span className="req">*</span></label>
|
||||
<div className="field-input-wrap">
|
||||
<LockKeyhole className="ic-l" size={16} strokeWidth={1.5} aria-hidden="true" />
|
||||
</span>
|
||||
</label>
|
||||
<label className={`login-field${fieldErrors.password ? " has-error" : ""}`}>
|
||||
<span>密码</span>
|
||||
<span className="login-input-wrap">
|
||||
<LockKeyhole />
|
||||
<input
|
||||
id="auth-pwd"
|
||||
type={showPwd ? "text" : "password"}
|
||||
@@ -510,53 +538,47 @@ export function AuthScreen({
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className={`toggle-pwd${showPwd ? " active" : ""}`}
|
||||
className="login-password-toggle"
|
||||
aria-label={showPwd ? "隐藏密码" : "显示密码"}
|
||||
aria-pressed={showPwd}
|
||||
title={showPwd ? "隐藏密码" : "显示密码"}
|
||||
onClick={() => setShowPwd((value) => !value)}
|
||||
>
|
||||
{showPwd ? <EyeOff size={18} strokeWidth={1.5} aria-hidden="true" /> : <Eye size={18} strokeWidth={1.5} aria-hidden="true" />}
|
||||
{showPwd ? <EyeOff /> : <Eye />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row-between">
|
||||
<label><input type="checkbox" checked={remember} onChange={(event) => setRemember(event.target.checked)} /> 记住我 7 天</label>
|
||||
<a href="#" onClick={(event) => { event.preventDefault(); showToast("重置密码", "请联系团队超管重置你的登录密码"); }}>忘记密码?</a>
|
||||
</div>
|
||||
<div className="auth-submit-zone">
|
||||
{loginProgress && !error && (
|
||||
<div className="login-progress" role="status" aria-live="polite">
|
||||
<span className="login-progress-spinner" aria-hidden="true"></span>
|
||||
<span>{LOGIN_PROGRESS_COPY[loginProgress]}</span>
|
||||
</div>
|
||||
)}
|
||||
{error && <div className="form-error" role="alert">{error}</div>}
|
||||
<button className="btn-cta" type="submit" disabled={busy}>
|
||||
{busy ? "登录中" : "登录"}
|
||||
</button>
|
||||
<div className="divider"><span className="line"></span><span className="txt">OR</span><span className="line"></span></div>
|
||||
<div className="sso-row">
|
||||
<button type="button" className="sso-btn" onClick={() => showToast("微信扫码", "对接中 · 当前请用用户名 + 密码登录")}>
|
||||
<ScanLine size={14} strokeWidth={1.6} aria-hidden="true" /> 微信扫码
|
||||
</span>
|
||||
</label>
|
||||
<p className={`login-error${error ? " show" : ""}`} role="alert">
|
||||
{error || "账号或密码不正确,请重新输入。"}
|
||||
</p>
|
||||
<button className="login-submit" type="submit" disabled={busy}>
|
||||
{busy ? (LOGIN_PROGRESS_COPY[loginProgress || "checking"] || "正在进入工作台") : "确认登录"}
|
||||
{busy ? <LoaderCircle /> : <ArrowRight />}
|
||||
</button>
|
||||
<button type="button" className="sso-btn" onClick={() => showToast("飞书 SSO", "对接中 · 当前请用用户名 + 密码登录")}>
|
||||
<ShieldCheck size={14} strokeWidth={1.6} aria-hidden="true" /> 飞书 SSO
|
||||
</button>
|
||||
</div>
|
||||
<div className="switch-row">
|
||||
还没账号? <a href="/register" onClick={(event) => { event.preventDefault(); switchMode("register"); }}>注册团队 →</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
{toast && (
|
||||
<div className="toast show" role="status" aria-live="polite">
|
||||
<div className="ic-t"><Info size={13} aria-hidden="true" /></div>
|
||||
<div className="txt">{toast.title}<span className="mono">// {toast.sub}</span></div>
|
||||
<p className="login-card-footer"><Lock />登录信息仅用于当前演示会话</p>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
<div className="login-extras">
|
||||
<div className="login-row">
|
||||
<label>
|
||||
<input type="checkbox" checked={remember} onChange={(event) => setRemember(event.target.checked)} />
|
||||
记住我 7 天
|
||||
</label>
|
||||
<a href="#" onClick={(event) => { event.preventDefault(); showToast("重置密码", "请联系团队超管重置你的登录密码"); }}>忘记密码?</a>
|
||||
</div>
|
||||
<div className="login-sso">
|
||||
<button type="button" onClick={() => showToast("微信扫码", "对接中 · 当前请用账号 + 密码登录")}>
|
||||
<ScanLine /> 微信扫码
|
||||
</button>
|
||||
<button type="button" onClick={() => showToast("飞书 SSO", "对接中 · 当前请用账号 + 密码登录")}>
|
||||
<ShieldCheck /> 飞书 SSO
|
||||
</button>
|
||||
</div>
|
||||
<p className="login-switch">
|
||||
还没账号? <a href="/register" onClick={(event) => { event.preventDefault(); switchMode("register"); }}>注册团队 →</a>
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
{toastNode}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user