This commit is contained in:
2026-06-30 10:33:56 +08:00
commit 6e047dc0a5
607 changed files with 65966 additions and 0 deletions
+239
View File
@@ -0,0 +1,239 @@
import { useState } from 'react';
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import AppImage from '@dukang/shared-ui/AppImage';
import { request, saveAuth } from '../lib/api';
function maskPhone(phone: string) {
if (phone.length < 7) return phone;
return `${phone.slice(0, 3)} **** ${phone.slice(-4)}`;
}
export default function LoginPage() {
const navigate = useNavigate();
const [params] = useSearchParams();
const quick = params.get('quick') === '1';
const [phone, setPhone] = useState('13900000001');
const [code, setCode] = useState('123456');
const [agreed, setAgreed] = useState(false);
const [loading, setLoading] = useState(false);
const [msg, setMsg] = useState('');
const [codeCooldown, setCodeCooldown] = useState(0);
function ensureAgreed() {
if (!agreed) {
setMsg('请先阅读并同意用户协议');
return false;
}
return true;
}
async function sendCode() {
if (!ensureAgreed()) return;
setMsg('');
try {
await request('SHOP_H5', '/shop/auth/sms/send', {
method: 'POST',
body: JSON.stringify({ phone, scene: 'STORE_LOGIN' }),
});
setMsg('验证码已发送(Mock: 123456');
setCodeCooldown(60);
const timer = setInterval(() => {
setCodeCooldown((c) => {
if (c <= 1) {
clearInterval(timer);
return 0;
}
return c - 1;
});
}, 1000);
} catch (e) {
setMsg(e instanceof Error ? e.message : '发送失败');
}
}
async function login() {
if (!ensureAgreed()) return;
setLoading(true);
setMsg('');
try {
await request('SHOP_H5', '/shop/auth/sms/send', {
method: 'POST',
body: JSON.stringify({ phone, scene: 'STORE_LOGIN' }),
});
const data = await request<{ accessToken: string }>('SHOP_H5', '/shop/auth/login/sms', {
method: 'POST',
body: JSON.stringify({ phone, code }),
});
saveAuth(data);
navigate('/');
} catch (e) {
setMsg(e instanceof Error ? e.message : '登录失败');
} finally {
setLoading(false);
}
}
function wechatLogin() {
if (!ensureAgreed()) return;
setMsg('preV1:微信一键授权暂未开放,请使用手机验证码登录');
}
if (quick) {
return (
<div className="shop-quick-login-page">
<header className="shop-quick-header">
<AppImage src="/logo.png" alt="杜康好客" wrapperClassName="shop-quick-logo" fit="contain" />
<h1 className="shop-quick-welcome"></h1>
<div className="shop-quick-welcome-line" />
</header>
<section className="shop-quick-store-card">
<div className="shop-quick-store-inner">
<div className="shop-quick-store-icon">
<span className="material-symbols-outlined shop-fill-icon">storefront</span>
</div>
<div>
<h2 className="shop-quick-store-name"></h2>
<p className="shop-quick-store-phone">{maskPhone(phone)}</p>
</div>
<span className="shop-quick-verified">
<span className="material-symbols-outlined shop-fill-icon" style={{ fontSize: 14 }}>verified_user</span>
</span>
<div className="shop-quick-switch">
<Link to="/login">
<span className="material-symbols-outlined" style={{ fontSize: 18 }}>sync</span>
</Link>
</div>
</div>
</section>
<div className="shop-quick-actions">
{msg && <p className="shop-login-msg">{msg}</p>}
<button
type="button"
className="shop-quick-login-btn"
disabled={loading}
onClick={login}
>
<span>{loading ? '登录中...' : '一键登录'}</span>
{!loading && <span className="material-symbols-outlined">arrow_forward</span>}
</button>
<div className="shop-quick-secure">
<span className="material-symbols-outlined" style={{ fontSize: 16 }}>lock</span>
<span></span>
</div>
</div>
<footer className="shop-quick-footer">
<p className="shop-login-footer-brand">SECURED BY DUKANG HERITAGE</p>
<p style={{ fontSize: 10, fontFamily: 'var(--font-label)' }}>© 2024 </p>
</footer>
</div>
);
}
return (
<div className="shop-login-page">
<header className="shop-login-hero">
<div className="shop-login-logo-wrap">
<AppImage src="/logo.png" alt="杜康好客" wrapperClassName="app-image--fill" fit="contain" />
</div>
<h1 className="shop-login-brand"></h1>
<p className="shop-login-tagline"></p>
</header>
<main className="shop-login-main">
<div className="shop-login-card">
<div className="shop-login-field">
<label htmlFor="phone"></label>
<div className="shop-login-input-wrap">
<span className="material-symbols-outlined">phone_iphone</span>
<input
id="phone"
type="tel"
maxLength={11}
placeholder="请输入您的手机号"
value={phone}
onChange={(e) => setPhone(e.target.value)}
/>
</div>
</div>
<div className="shop-login-field">
<label htmlFor="code"></label>
<div className="shop-login-code-row">
<div className="shop-login-input-wrap">
<span className="material-symbols-outlined">shield</span>
<input
id="code"
type="text"
maxLength={6}
placeholder="验证码"
value={code}
onChange={(e) => setCode(e.target.value)}
/>
</div>
<button
type="button"
className="shop-login-code-btn"
disabled={codeCooldown > 0}
onClick={sendCode}
>
{codeCooldown > 0 ? `${codeCooldown}s` : '获取验证码'}
</button>
</div>
</div>
{msg && <p className="shop-login-msg">{msg}</p>}
<button
type="button"
className="shop-login-submit"
disabled={loading}
onClick={login}
>
<span>{loading ? '登录中...' : '登录'}</span>
{!loading && <span className="material-symbols-outlined">arrow_forward</span>}
</button>
<div className="shop-login-divider">
<span className="shop-login-divider-line" />
<span className="shop-login-divider-text"></span>
<span className="shop-login-divider-line" />
</div>
<button type="button" className="shop-login-wechat" onClick={wechatLogin}>
<span className="material-symbols-outlined">chat</span>
<span></span>
</button>
</div>
<label className="shop-login-agreement">
<input
type="checkbox"
checked={agreed}
onChange={(e) => setAgreed(e.target.checked)}
/>
<span>
<a href="#user-agreement"></a>
<a href="#privacy"></a>
</span>
</label>
</main>
<footer className="shop-login-footer">
<p className="shop-login-footer-brand">Secured by DUKANG HERITAGE</p>
<span className="material-symbols-outlined shop-fill-icon" style={{ opacity: 0.3, fontSize: 20, color: 'var(--color-outline)' }}>
security
</span>
<p style={{ marginTop: 16, textAlign: 'center' }}>
<Link to="/login?quick=1" className="text-primary body-md"></Link>
</p>
</footer>
</div>
);
}