完成 IM Web Android 端产品页面开发
This commit is contained in:
@@ -0,0 +1,431 @@
|
||||
import React from 'react';
|
||||
|
||||
import HashNavigation from '../lib/navigation.js';
|
||||
|
||||
const USERNAME_RE = /^[a-z0-9]{1,10}$/;
|
||||
const PHONE_RE = /^\d{11}$/;
|
||||
const INVITE_RE = /^[A-Za-z]{5}$/;
|
||||
const CREDIT_CODE_RE = /^[0-9A-Z]{18}$/;
|
||||
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
const navigate = view => HashNavigation.navigateTo(view);
|
||||
|
||||
function Icon({children, className = ''}) {
|
||||
return <i className={'material-icons ' + className} aria-hidden="true">{children}</i>;
|
||||
}
|
||||
|
||||
function AppMark({tenantName}) {
|
||||
return <div className="auth-brand">
|
||||
<div className="auth-logo"><span>J</span></div>
|
||||
<h1>{tenantName || '即时通讯'}</h1>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function Header({title, back = 'tenant'}) {
|
||||
return <header className="auth-header">
|
||||
<button type="button" className="auth-icon-button" onClick={() => navigate(back)} aria-label="返回">
|
||||
<Icon>arrow_back_ios_new</Icon>
|
||||
</button>
|
||||
<h1>{title}</h1>
|
||||
<span className="auth-header-spacer" />
|
||||
</header>;
|
||||
}
|
||||
|
||||
function Field({icon, action, error, className = '', ...props}) {
|
||||
return <div className={'auth-field-wrap ' + className}>
|
||||
<div className={'auth-field' + (error ? ' invalid' : '')}>
|
||||
{icon ? <Icon className="auth-field-icon">{icon}</Icon> : null}
|
||||
<input {...props} />
|
||||
{action}
|
||||
</div>
|
||||
{error ? <div className="auth-field-error">{error}</div> : null}
|
||||
</div>;
|
||||
}
|
||||
|
||||
function PasswordField(props) {
|
||||
const [visible, setVisible] = React.useState(false);
|
||||
return <Field {...props} type={visible ? 'text' : 'password'}
|
||||
action={<button type="button" className="auth-inline-icon" onClick={() => setVisible(!visible)}
|
||||
aria-label={visible ? '隐藏密码' : '显示密码'}><Icon>{visible ? 'visibility' : 'visibility_off'}</Icon></button>} />;
|
||||
}
|
||||
|
||||
function Agreement({checked, onChange, prefix = '同意捷信密使'}) {
|
||||
return <label className="auth-agreement">
|
||||
<input type="checkbox" checked={checked} onChange={e => onChange(e.target.checked)} />
|
||||
<span>{prefix} <a href="#agreement" onClick={e => e.preventDefault()}>《用户协议》</a> 和 <a href="#privacy" onClick={e => e.preventDefault()}>《隐私政策》</a></span>
|
||||
</label>;
|
||||
}
|
||||
|
||||
function Toast({message, onDone}) {
|
||||
React.useEffect(() => {
|
||||
if (!message) return undefined;
|
||||
const timer = window.setTimeout(onDone, 2800);
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [message, onDone]);
|
||||
return message ? <div className="auth-toast" role="status"><Icon>check_circle</Icon>{message}</div> : null;
|
||||
}
|
||||
|
||||
function TenantEntry({onTenantSelected}) {
|
||||
const [code, setCode] = React.useState(sessionStorage.getItem('im-tenant-code') || '');
|
||||
const [error, setError] = React.useState('');
|
||||
|
||||
const submit = e => {
|
||||
e.preventDefault();
|
||||
const value = code.trim();
|
||||
if (!value || value.length > 64) {
|
||||
setError(value ? '企业码不能超过 64 个字符' : '请输入企业码');
|
||||
return;
|
||||
}
|
||||
sessionStorage.setItem('im-tenant-code', value);
|
||||
sessionStorage.setItem('im-tenant-name', value === 'acme' ? '示例企业' : value);
|
||||
onTenantSelected(value);
|
||||
navigate('login');
|
||||
};
|
||||
|
||||
return <div className="auth-landing">
|
||||
<button className="auth-top-link" type="button" onClick={() => navigate('enterprise-register')}>企业注册</button>
|
||||
<form className="auth-landing-form" onSubmit={submit}>
|
||||
<AppMark />
|
||||
<Field value={code} onChange={e => { setCode(e.target.value); setError(''); }}
|
||||
placeholder="请输入企业码" autoComplete="organization" maxLength="64" error={error} autoFocus />
|
||||
<button className="auth-primary" type="submit" disabled={!code.trim()}>进入企业</button>
|
||||
</form>
|
||||
<button className="auth-bottom-link" type="button" onClick={() => navigate('enterprise-feedback')}>遇到问题?</button>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function Login({tenantName}) {
|
||||
const [account, setAccount] = React.useState('');
|
||||
const [password, setPassword] = React.useState('');
|
||||
const [agreed, setAgreed] = React.useState(false);
|
||||
const [error, setError] = React.useState('');
|
||||
const [toast, setToast] = React.useState('');
|
||||
|
||||
const submit = e => {
|
||||
e.preventDefault();
|
||||
if (!account.trim() || !password || !agreed) return;
|
||||
if (password === 'error') {
|
||||
setPassword('');
|
||||
setError('账号或密码错误');
|
||||
} else {
|
||||
setToast('登录成功');
|
||||
window.setTimeout(() => navigate('messages-home'), 500);
|
||||
}
|
||||
};
|
||||
|
||||
return <div className="auth-landing auth-login-page">
|
||||
<button type="button" className="auth-back-link" onClick={() => navigate('tenant')}><Icon>arrow_back_ios_new</Icon>切换企业</button>
|
||||
<form className="auth-landing-form" onSubmit={submit}>
|
||||
<AppMark tenantName={tenantName} />
|
||||
{error ? <div className="auth-form-alert"><Icon>error_outline</Icon>{error}</div> : null}
|
||||
<Field value={account} onChange={e => { setAccount(e.target.value); setError(''); }}
|
||||
placeholder="请输入用户名/手机号" autoComplete="username" />
|
||||
<PasswordField value={password} onChange={e => { setPassword(e.target.value); setError(''); }}
|
||||
placeholder="请输入密码" autoComplete="current-password" />
|
||||
<div className="auth-row auth-row-end"><button type="button" className="auth-text-button" onClick={() => navigate('reset')}>忘记密码</button></div>
|
||||
<button className="auth-primary" type="submit" disabled={!account.trim() || !password || !agreed}>登录</button>
|
||||
<p className="auth-switch">没有账户? <button type="button" onClick={() => navigate('register')}>马上注册</button></p>
|
||||
</form>
|
||||
<Agreement checked={agreed} onChange={setAgreed} />
|
||||
<Toast message={toast} onDone={() => setToast('')} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
function EnterpriseRegister() {
|
||||
const [creditCode, setCreditCode] = React.useState('');
|
||||
const [email, setEmail] = React.useState('');
|
||||
const [file, setFile] = React.useState(null);
|
||||
const [errors, setErrors] = React.useState({});
|
||||
const [submitted, setSubmitted] = React.useState(sessionStorage.getItem('im-enterprise-application-status') === 'received');
|
||||
const [toast, setToast] = React.useState('');
|
||||
|
||||
React.useEffect(() => () => file && URL.revokeObjectURL(file.url), [file]);
|
||||
|
||||
const chooseFile = e => {
|
||||
const selected = e.target.files[0];
|
||||
if (!selected) return;
|
||||
if (selected.size > 10 * 1024 * 1024 || !['image/jpeg', 'image/png', 'image/webp', 'application/pdf'].includes(selected.type)) {
|
||||
setErrors({...errors, file: '支持 JPG、PNG、WebP、PDF,文件不超过 10MB'});
|
||||
return;
|
||||
}
|
||||
if (file) URL.revokeObjectURL(file.url);
|
||||
setFile({name: selected.name, type: selected.type, url: URL.createObjectURL(selected)});
|
||||
setErrors({...errors, file: ''});
|
||||
};
|
||||
|
||||
const submit = e => {
|
||||
e.preventDefault();
|
||||
const next = {};
|
||||
if (!CREDIT_CODE_RE.test(creditCode)) next.creditCode = '请输入 18 位统一社会信用代码';
|
||||
if (email && !EMAIL_RE.test(email)) next.email = '请输入有效的企业邮箱';
|
||||
setErrors(next);
|
||||
if (Object.keys(next).length) return;
|
||||
setSubmitted(true);
|
||||
sessionStorage.setItem('im-enterprise-application-status', 'received');
|
||||
setToast('提交成功,我们会尽快联系你');
|
||||
};
|
||||
|
||||
return <div className="auth-page">
|
||||
<Header title="企业注册" />
|
||||
<main className="auth-content auth-content-narrow">
|
||||
<ol className="auth-steps">
|
||||
<li className="active"><span>1</span><b>填写企业信息</b></li>
|
||||
<li><span>2</span><b>确定信息</b></li>
|
||||
<li><span>3</span><b>提交审核</b></li>
|
||||
</ol>
|
||||
<form className="auth-form" onSubmit={submit}>
|
||||
<label className="auth-label">统一社会信用代码</label>
|
||||
<Field value={creditCode} onChange={e => setCreditCode(e.target.value.trim().toUpperCase().replace(/[^0-9A-Z]/g, ''))}
|
||||
placeholder="请输入18位统一社会信用代码" maxLength="18" icon="qr_code_scanner" error={errors.creditCode} />
|
||||
<p className="auth-help">请填写营业执照上的统一社会信用代码</p>
|
||||
|
||||
<label className="auth-label">上传营业执照 <span>选填</span></label>
|
||||
<label className={'auth-upload' + (errors.file ? ' invalid' : '')}>
|
||||
<input type="file" accept="image/jpeg,image/png,image/webp,application/pdf" onChange={chooseFile} />
|
||||
{file && file.type !== 'application/pdf' ? <img src={file.url} alt="营业执照预览" /> : <Icon>add_photo_alternate</Icon>}
|
||||
<strong>{file ? file.name : '点击上传营业执照'}</strong>
|
||||
<small>支持 JPG、PNG、WebP、PDF,不超过 10MB</small>
|
||||
</label>
|
||||
{file ? <button type="button" className="auth-remove-file" onClick={() => { URL.revokeObjectURL(file.url); setFile(null); }}>移除文件</button> : null}
|
||||
{errors.file ? <div className="auth-field-error">{errors.file}</div> : null}
|
||||
|
||||
<label className="auth-label">企业邮箱 <span>选填</span></label>
|
||||
<Field icon="mail_outline" value={email} onChange={e => setEmail(e.target.value)}
|
||||
placeholder="请输入企业邮箱" type="email" error={errors.email} />
|
||||
<p className="auth-help">用于接收审核结果及企业通知</p>
|
||||
<button className="auth-primary" type="submit" disabled={submitted || creditCode.length !== 18}>{submitted ? '已提交' : '提交资料'}</button>
|
||||
<p className="auth-review-time"><Icon>schedule</Icon>资料提交后,预计 1 个工作日完成审核</p>
|
||||
</form>
|
||||
</main>
|
||||
<Toast message={toast} onDone={() => setToast('')} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
function EnterpriseFeedback() {
|
||||
const [description, setDescription] = React.useState('');
|
||||
const [contact, setContact] = React.useState('');
|
||||
const [files, setFiles] = React.useState([]);
|
||||
const [error, setError] = React.useState('');
|
||||
const [toast, setToast] = React.useState('');
|
||||
|
||||
const addFiles = e => {
|
||||
const selected = Array.from(e.target.files || []).slice(0, 9 - files.length);
|
||||
const valid = selected.filter(file => file.size <= 10 * 1024 * 1024 && ['image/jpeg', 'image/png', 'image/webp'].includes(file.type));
|
||||
setFiles(files.concat(valid.map(file => ({name: file.name, url: URL.createObjectURL(file)}))));
|
||||
};
|
||||
const submit = e => {
|
||||
e.preventDefault();
|
||||
const length = description.trim().length;
|
||||
if (length < 10 || length > 1000) {
|
||||
setError('问题描述需为 10 至 1000 个字符');
|
||||
return;
|
||||
}
|
||||
setToast('已收到反馈,我们会尽快处理');
|
||||
files.forEach(item => URL.revokeObjectURL(item.url));
|
||||
setDescription(''); setContact(''); setFiles([]);
|
||||
};
|
||||
|
||||
return <div className="auth-page auth-soft-bg">
|
||||
<Header title="问题反馈" />
|
||||
<main className="auth-content auth-content-narrow">
|
||||
<form className="auth-form" onSubmit={submit}>
|
||||
<label className="auth-label">问题描述</label>
|
||||
<textarea value={description} maxLength="1000" onChange={e => { setDescription(e.target.value); setError(''); }}
|
||||
placeholder="请详细描述遇到的问题,至少输入10个字" className={error ? 'invalid' : ''} />
|
||||
<div className="auth-counter">{description.length}/1000</div>
|
||||
{error ? <div className="auth-field-error">{error}</div> : null}
|
||||
<label className="auth-label">上传截图 <span>最多 9 张</span></label>
|
||||
<div className="auth-screenshot-grid">
|
||||
{files.map((file, index) => <div className="auth-screenshot" key={file.url}>
|
||||
<img src={file.url} alt={file.name} />
|
||||
<button type="button" onClick={() => { URL.revokeObjectURL(file.url); setFiles(files.filter((_, i) => i !== index)); }} aria-label="删除截图"><Icon>close</Icon></button>
|
||||
</div>)}
|
||||
{files.length < 9 ? <label className="auth-add-screenshot"><input type="file" multiple accept="image/jpeg,image/png,image/webp" onChange={addFiles} /><Icon>add</Icon></label> : null}
|
||||
</div>
|
||||
<label className="auth-label">联系方式 <span>选填</span></label>
|
||||
<Field value={contact} onChange={e => setContact(e.target.value)} placeholder="手机号或邮箱,便于我们联系您" />
|
||||
<button className="auth-primary" type="submit" disabled={description.trim().length < 10}>提交反馈</button>
|
||||
</form>
|
||||
</main>
|
||||
<Toast message={toast} onDone={() => setToast('')} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
function VerificationButton({phone, scene = 'register'}) {
|
||||
const [seconds, setSeconds] = React.useState(0);
|
||||
React.useEffect(() => {
|
||||
if (!seconds) return undefined;
|
||||
const timer = window.setTimeout(() => setSeconds(seconds - 1), 1000);
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [seconds]);
|
||||
return <button type="button" className="auth-code-button" disabled={seconds > 0 || !PHONE_RE.test(phone)}
|
||||
onClick={() => { sessionStorage.setItem('im-verification-scene', scene); setSeconds(60); }}>
|
||||
{seconds > 0 ? `${seconds}s` : '获取验证码'}
|
||||
</button>;
|
||||
}
|
||||
|
||||
function Register() {
|
||||
const [form, setForm] = React.useState({username: '', phone: '', code: '', password: '', invite: ''});
|
||||
const [agreed, setAgreed] = React.useState(false);
|
||||
const [errors, setErrors] = React.useState({});
|
||||
const [toast, setToast] = React.useState('');
|
||||
const change = key => e => setForm({...form, [key]: e.target.value});
|
||||
const submit = e => {
|
||||
e.preventDefault();
|
||||
const next = {};
|
||||
if (!form.username && !form.phone) next.username = '用户名和手机号至少填写一项';
|
||||
if (form.username && !USERNAME_RE.test(form.username)) next.username = '用户名仅支持 1-10 位小写字母或数字';
|
||||
if (form.phone && !PHONE_RE.test(form.phone)) next.phone = '请输入 11 位手机号';
|
||||
if (!form.phone && !INVITE_RE.test(form.invite)) next.invite = '用户名注册需填写 5 位字母邀请码';
|
||||
if (form.invite && !INVITE_RE.test(form.invite)) next.invite = '邀请码为 5 位大小写字母';
|
||||
if (form.password.length < 8) next.password = '密码至少 8 位';
|
||||
setErrors(next);
|
||||
if (Object.keys(next).length) return;
|
||||
setToast(form.phone ? '注册信息已准备,接口将在联调阶段启用' : '未绑定手机号,忘记密码需联系企业管理员');
|
||||
};
|
||||
|
||||
return <div className="auth-landing auth-register-page">
|
||||
<button type="button" className="auth-back-link" onClick={() => navigate('login')}><Icon>arrow_back_ios_new</Icon>返回登录</button>
|
||||
<form className="auth-landing-form auth-register-form" onSubmit={submit}>
|
||||
<AppMark tenantName={sessionStorage.getItem('im-tenant-name') || '讯工捷迅'} />
|
||||
<Field value={form.username} onChange={e => setForm({...form, username: e.target.value.replace(/[^a-z0-9]/g, '').slice(0, 10)})}
|
||||
placeholder="请输入用户名" error={errors.username} autoComplete="username" />
|
||||
<Field value={form.phone} onChange={e => setForm({...form, phone: e.target.value.replace(/\D/g, '').slice(0, 11)})}
|
||||
placeholder="请输入手机号" error={errors.phone} inputMode="numeric" />
|
||||
<Field value={form.code} onChange={change('code')} placeholder="请输入短信验证码" inputMode="numeric"
|
||||
action={<VerificationButton phone={form.phone} />} />
|
||||
<PasswordField value={form.password} onChange={change('password')} placeholder="请输入密码" autoComplete="new-password" error={errors.password} />
|
||||
<Field value={form.invite} onChange={e => setForm({...form, invite: e.target.value.replace(/[^A-Za-z]/g, '').slice(0, 5)})}
|
||||
placeholder="请输入邀请码(非必填)" error={errors.invite} />
|
||||
<button className="auth-primary" type="submit" disabled={(!form.username && !form.phone) || !form.password || !agreed}>注册</button>
|
||||
<p className="auth-switch">已有讯工捷迅账号? <button type="button" onClick={() => navigate('login')}>登录</button></p>
|
||||
</form>
|
||||
<Agreement checked={agreed} onChange={setAgreed} prefix="注册即认同捷讯" />
|
||||
<Toast message={toast} onDone={() => setToast('')} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
function PasswordReset() {
|
||||
const [form, setForm] = React.useState({phone: '', code: '', password: '', confirm: ''});
|
||||
const [error, setError] = React.useState('');
|
||||
const [toast, setToast] = React.useState('');
|
||||
const change = key => e => setForm({...form, [key]: e.target.value});
|
||||
const submit = e => {
|
||||
e.preventDefault();
|
||||
if (!PHONE_RE.test(form.phone)) return setError('请输入 11 位手机号');
|
||||
if (form.password.length < 8 || form.password.length > 20) return setError('密码需为 8-20 位字母或数字组合');
|
||||
if (form.password !== form.confirm) return setError('两次输入的密码不一致');
|
||||
setError(''); setToast('密码修改成功,请重新登录');
|
||||
window.setTimeout(() => navigate('login'), 1200);
|
||||
};
|
||||
return <div className="auth-page auth-soft-bg">
|
||||
<Header title="忘记密码" back="login" />
|
||||
<main className="auth-content auth-content-narrow auth-reset-content">
|
||||
<form className="auth-list-form" onSubmit={submit}>
|
||||
<label><span>手机号</span><input value={form.phone} onChange={e => setForm({...form, phone: e.target.value.replace(/\D/g, '').slice(0, 11)})} placeholder="请输入手机号" inputMode="numeric" /></label>
|
||||
<label><span>验证码</span><input value={form.code} onChange={change('code')} placeholder="请输入验证码" inputMode="numeric" /><VerificationButton phone={form.phone} scene="reset_password" /></label>
|
||||
<div className="auth-list-gap" />
|
||||
<label><span>设置密码</span><input type="password" value={form.password} onChange={change('password')} placeholder="8-20位字母或数字组合" /></label>
|
||||
<label><span>再次输入</span><input type="password" value={form.confirm} onChange={change('confirm')} placeholder="请再次确定密码" /></label>
|
||||
{error ? <div className="auth-form-alert"><Icon>error_outline</Icon>{error}</div> : null}
|
||||
<button className="auth-primary auth-sticky-action" type="submit" disabled={!form.phone || !form.code || !form.password || !form.confirm}>修改密码</button>
|
||||
</form>
|
||||
</main>
|
||||
<Toast message={toast} onDone={() => setToast('')} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
function IdentityCheck({authenticated}) {
|
||||
const [phone] = React.useState('138****8899');
|
||||
const [code, setCode] = React.useState('');
|
||||
const [toast, setToast] = React.useState('');
|
||||
return <div className="auth-page auth-soft-bg">
|
||||
<Header title="身份验证" back={authenticated ? 'security' : 'login'} />
|
||||
<main className="auth-content auth-content-narrow auth-reset-content">
|
||||
<form className="auth-list-form" onSubmit={e => { e.preventDefault(); setToast('身份验证成功'); }}>
|
||||
<label><span>手机号</span><input value={phone} readOnly /></label>
|
||||
<label><span>验证码</span><input value={code} onChange={e => setCode(e.target.value.replace(/\D/g, '').slice(0, 6))} placeholder="请输入验证码" inputMode="numeric" /><VerificationButton phone="13800008899" scene="security" /></label>
|
||||
<button className="auth-primary auth-sticky-action" type="submit" disabled={code.length !== 6}>确定</button>
|
||||
</form>
|
||||
</main>
|
||||
<Toast message={toast} onDone={() => setToast('')} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
function Realname({verified = false, authenticated = false}) {
|
||||
const [name, setName] = React.useState(verified ? '蒋欣' : '');
|
||||
const [idNumber, setIdNumber] = React.useState(verified ? '43993838581999355454' : '');
|
||||
const [status, setStatus] = React.useState(verified ? 'verified' : 'unverified');
|
||||
const [error, setError] = React.useState('');
|
||||
const submit = e => {
|
||||
e.preventDefault();
|
||||
if (name.trim().length < 2 || !/^\d{17}[\dXx]$/.test(idNumber)) {
|
||||
setError('请输入有效姓名和 18 位身份证号码'); return;
|
||||
}
|
||||
setStatus('pending'); setError('');
|
||||
};
|
||||
return <div className="auth-page auth-realname-page">
|
||||
<Header title="实名认证" back={authenticated ? 'security' : 'login'} />
|
||||
<div className="auth-realname-hero"><div><strong>实名认证</strong><span>更加真实,更受欢迎</span></div><Icon>verified_user</Icon></div>
|
||||
<main className="auth-realname-content">
|
||||
<h2>{status === 'verified' ? '认证信息' : '请您使用有效身份证信息认证'}</h2>
|
||||
<form onSubmit={submit}>
|
||||
<label><span>姓名</span><input value={status === 'verified' ? name[0] + '*' : name} onChange={e => setName(e.target.value)} readOnly={status !== 'unverified'} placeholder="请输入真实姓名" /></label>
|
||||
<label><span>证件号码</span><input value={status === 'verified' ? idNumber.slice(0, 3) + '***********' + idNumber.slice(-4) : idNumber} onChange={e => setIdNumber(e.target.value)} readOnly={status !== 'unverified'} placeholder="请输入证件号码" /></label>
|
||||
{error ? <div className="auth-field-error">{error}</div> : null}
|
||||
<button className="auth-primary" type="submit" disabled={status !== 'unverified'}>{status === 'verified' ? '已实名' : status === 'pending' ? '审核中' : '提交'}</button>
|
||||
</form>
|
||||
<p className="auth-privacy-note"><Icon>info</Icon>我们将获取您的姓名、证件号用于认证,信息会被严格保护</p>
|
||||
</main>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function RealnameReminder() {
|
||||
return <div className="auth-page auth-reminder-demo">
|
||||
<div className="auth-demo-chat"><h1>消息</h1><div className="auth-demo-search"><Icon>search</Icon>搜索</div>{['系统消息', '文件助手', '项目协作群', '陈经理'].map((item, i) => <div className="auth-demo-row" key={item}><span>{item[0]}</span><div><b>{item}</b><small>{i ? '最近一条消息内容' : '安全通知与账号动态'}</small></div></div>)}</div>
|
||||
<div className="auth-modal-backdrop">
|
||||
<div className="auth-modal"><h2>实名认证提醒</h2><p>根据国家法律法规要求,您需先进行实名认证才能使用该应用</p><button className="auth-primary" onClick={() => navigate('realname')}>去实名认证</button><button className="auth-text-button" onClick={() => navigate('tenant')}>稍后处理</button></div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function IndependentPassword({authenticated}) {
|
||||
const [pin, setPin] = React.useState('');
|
||||
const [error, setError] = React.useState('');
|
||||
const [toast, setToast] = React.useState('');
|
||||
const invalid = ['0000','1111','2222','3333','4444','5555','6666','7777','8888','9999','0123','1234','2345','3456','4567','5678','6789','9876','8765','7654','6543','5432','4321','3210'];
|
||||
const submit = e => {
|
||||
e.preventDefault();
|
||||
if (invalid.includes(pin)) { setError('请勿使用连续或重复数字'); return; }
|
||||
setError(''); setToast('独立密码设置成功'); setPin('');
|
||||
};
|
||||
return <div className="auth-page">
|
||||
<Header title="" back={authenticated ? 'security' : 'login'} />
|
||||
<main className="auth-pin-content">
|
||||
<h1>设置独立密码</h1>
|
||||
<div className="auth-pin-boxes" aria-hidden="true">{[0,1,2,3].map(i => <span key={i}>{pin[i] ? '●' : ''}</span>)}</div>
|
||||
<input className="auth-pin-input" type="text" value={pin} onChange={e => setPin(e.target.value.replace(/\D/g, '').slice(0, 4))} inputMode="numeric" autoComplete="off" aria-label="四位独立密码" autoFocus />
|
||||
{error ? <div className="auth-field-error">{error}</div> : null}
|
||||
<button className="auth-primary" disabled={pin.length !== 4} onClick={submit}>确定</button>
|
||||
</main>
|
||||
<Toast message={toast} onDone={() => setToast('')} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
export default function AuthOnboardingView({view, onTenantSelected, authenticated = false}) {
|
||||
const tenantName = sessionStorage.getItem('im-tenant-name') || '';
|
||||
switch (view) {
|
||||
case 'tenant': return <TenantEntry onTenantSelected={onTenantSelected || (() => {})} />;
|
||||
case 'login': return <Login tenantName={tenantName} />;
|
||||
case 'enterprise-register': return <EnterpriseRegister />;
|
||||
case 'enterprise-feedback': return <EnterpriseFeedback />;
|
||||
case 'register': return <Register />;
|
||||
case 'reset': return <PasswordReset />;
|
||||
case 'identity-check': return <IdentityCheck authenticated={authenticated} />;
|
||||
case 'realname': return <Realname authenticated={authenticated} />;
|
||||
case 'realname-verified': return <Realname verified authenticated={authenticated} />;
|
||||
case 'realname-reminder': return <RealnameReminder />;
|
||||
case 'independent-password': return <IndependentPassword authenticated={authenticated} />;
|
||||
default: return <TenantEntry onTenantSelected={onTenantSelected || (() => {})} />;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user