14b867a3a5
CI / verify (pull_request) Has been cancelled
Quick login required agreement without a checkbox; move the consent row above CTAs across partner/shop/user/mini login pages so it stays visible on short screens. Co-authored-by: Cursor <cursoragent@cursor.com>
267 lines
8.4 KiB
TypeScript
267 lines
8.4 KiB
TypeScript
import { useEffect, useState } from 'react';
|
||
import { View, Text, Input, Button } from '@tarojs/components';
|
||
import Taro from '@tarojs/taro';
|
||
import { request, saveToken, toast } from '../../lib/api';
|
||
import {
|
||
bindHqWechatAfterSmsLogin,
|
||
handleHqWechatCallback,
|
||
handleHqWechatLoginResult,
|
||
loginHqWithWechat,
|
||
} from '../../lib/wechat';
|
||
import { isWechatEnv } from '../../lib/weixin';
|
||
import { WECHAT_INAPP_REQUIRED_MSG } from '@dukang/weixin-sdk';
|
||
import { clearHqAccountCache } from '../../lib/session';
|
||
import './index.css';
|
||
|
||
const DEMO_PHONE = '13600000001';
|
||
const REMEMBER_PHONE_KEY = 'hq_remember_phone';
|
||
const REMEMBER_FLAG_KEY = 'hq_remember_account';
|
||
|
||
function loadRememberedPhone(): { phone: string; remember: boolean } {
|
||
try {
|
||
const remember = Taro.getStorageSync(REMEMBER_FLAG_KEY) === '1';
|
||
const phone = remember ? Taro.getStorageSync(REMEMBER_PHONE_KEY) || '' : '';
|
||
return { phone, remember };
|
||
} catch {
|
||
return { phone: '', remember: false };
|
||
}
|
||
}
|
||
|
||
function WechatIcon() {
|
||
if (process.env.TARO_ENV === 'h5') {
|
||
return (
|
||
<svg className="login-wechat-svg" viewBox="0 0 24 24" fill="#07C160" aria-hidden>
|
||
<path d="M8.25 4.5C4.52 4.5 1.5 7.04 1.5 10.17c0 1.78.98 3.37 2.5 4.48l-.63 1.88 2.19-1.09c.84.24 1.74.38 2.69.38.25 0 .5 0 .75-.03-.16-.53-.25-1.09-.25-1.66 0-3.13 3.02-5.67 6.75-5.67.57 0 1.13.06 1.66.17C15.17 6.13 12 4.5 8.25 4.5zm10.5 6.33c-3.11 0-5.62 2.12-5.62 4.73 0 2.61 2.51 4.73 5.62 4.73.79 0 1.54-.14 2.24-.38l1.83.91-.53-1.57c1.27-.92 2.08-2.25 2.08-3.73 0-2.61-2.51-4.73-5.62-4.73z" />
|
||
</svg>
|
||
);
|
||
}
|
||
return <Text className="login-wechat-fallback">微</Text>;
|
||
}
|
||
|
||
export default function LoginPage() {
|
||
const remembered = loadRememberedPhone();
|
||
const [phone, setPhone] = useState(remembered.phone || DEMO_PHONE);
|
||
const [code, setCode] = useState('123456');
|
||
const [rememberAccount, setRememberAccount] = useState(remembered.remember);
|
||
const [agreed, setAgreed] = useState(false);
|
||
const [cooldown, setCooldown] = useState(0);
|
||
const [loading, setLoading] = useState(false);
|
||
const [wxLoading, setWxLoading] = useState(false);
|
||
const [msg, setMsg] = useState('');
|
||
|
||
useEffect(() => {
|
||
if (!isWechatEnv()) return;
|
||
void handleHqWechatCallback()
|
||
.then((result) => {
|
||
if (!result) return;
|
||
if (handleHqWechatLoginResult(result)) enterApp();
|
||
})
|
||
.catch((e) => setMsg(formatWechatError(e)));
|
||
}, []);
|
||
|
||
function formatWechatError(e: unknown): string {
|
||
const text = e instanceof Error ? e.message : '微信登录失败';
|
||
if (text.includes('首次登录') || text.includes('手机验证码')) {
|
||
return '该微信尚未绑定管理员账号,请先使用手机验证码登录,登录后将自动关联微信';
|
||
}
|
||
return text;
|
||
}
|
||
|
||
function ensureAgreed(): boolean {
|
||
if (!agreed) {
|
||
setMsg('请先勾选并同意用户协议');
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
function persistRememberAccount(nextPhone: string) {
|
||
try {
|
||
if (rememberAccount) {
|
||
Taro.setStorageSync(REMEMBER_FLAG_KEY, '1');
|
||
Taro.setStorageSync(REMEMBER_PHONE_KEY, nextPhone);
|
||
} else {
|
||
Taro.removeStorageSync(REMEMBER_FLAG_KEY);
|
||
Taro.removeStorageSync(REMEMBER_PHONE_KEY);
|
||
}
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
}
|
||
|
||
function enterApp() {
|
||
clearHqAccountCache();
|
||
Taro.reLaunch({ url: '/pages/dashboard/index' });
|
||
}
|
||
|
||
async function sendCode() {
|
||
if (!ensureAgreed()) return;
|
||
if (cooldown > 0) return;
|
||
setMsg('');
|
||
try {
|
||
await request('/admin/auth/sms/send', {
|
||
method: 'POST',
|
||
data: { phone, scene: 'HQ_LOGIN' },
|
||
});
|
||
toast('验证码已发送(Mock:123456)', 'success');
|
||
setCooldown(60);
|
||
const t = setInterval(() => {
|
||
setCooldown((s) => {
|
||
if (s <= 1) {
|
||
clearInterval(t);
|
||
return 0;
|
||
}
|
||
return s - 1;
|
||
});
|
||
}, 1000);
|
||
} catch (e) {
|
||
setMsg(e instanceof Error ? e.message : '发送失败');
|
||
}
|
||
}
|
||
|
||
async function smsLogin() {
|
||
if (!ensureAgreed()) return;
|
||
setLoading(true);
|
||
setMsg('');
|
||
try {
|
||
const data = await request<{ accessToken: string }>('/admin/auth/login/sms', {
|
||
method: 'POST',
|
||
data: { phone, code },
|
||
});
|
||
saveToken(data.accessToken);
|
||
persistRememberAccount(phone);
|
||
if (isWechatEnv() || process.env.TARO_ENV === 'weapp') {
|
||
setMsg('登录成功,正在关联微信…');
|
||
await bindHqWechatAfterSmsLogin();
|
||
if (process.env.TARO_ENV === 'weapp') {
|
||
enterApp();
|
||
}
|
||
return;
|
||
}
|
||
enterApp();
|
||
} catch (e) {
|
||
setMsg(e instanceof Error ? e.message : '登录失败');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}
|
||
|
||
async function wechatLogin() {
|
||
if (!ensureAgreed()) return;
|
||
setMsg('');
|
||
if (!isWechatEnv()) {
|
||
setMsg(WECHAT_INAPP_REQUIRED_MSG);
|
||
return;
|
||
}
|
||
setWxLoading(true);
|
||
try {
|
||
const ok = await loginHqWithWechat();
|
||
if (ok) enterApp();
|
||
} catch (e) {
|
||
setMsg(formatWechatError(e));
|
||
} finally {
|
||
setWxLoading(false);
|
||
}
|
||
}
|
||
|
||
return (
|
||
<View className="login-page">
|
||
<View className="login-brand">
|
||
<View className="login-logo">
|
||
<Text className="material-symbols-outlined">local_bar</Text>
|
||
</View>
|
||
<Text className="login-title">杜康好客</Text>
|
||
<Text className="login-subtitle">总部管理中心</Text>
|
||
</View>
|
||
|
||
<View className="login-card">
|
||
<Text className="login-card-title">管理员登录</Text>
|
||
|
||
<View className="login-input-wrap">
|
||
<Text className="material-symbols-outlined login-input-icon">smartphone</Text>
|
||
<Input
|
||
className="login-input"
|
||
type="number"
|
||
placeholder="请输入手机号"
|
||
value={phone}
|
||
onInput={(e) => setPhone(e.detail.value)}
|
||
/>
|
||
</View>
|
||
|
||
<View className="login-input-row">
|
||
<View className="login-input-wrap">
|
||
<Text className="material-symbols-outlined login-input-icon">shield</Text>
|
||
<Input
|
||
className="login-input"
|
||
type="number"
|
||
placeholder="验证码"
|
||
value={code}
|
||
onInput={(e) => setCode(e.detail.value)}
|
||
/>
|
||
</View>
|
||
<View
|
||
className={`login-code-btn${cooldown > 0 ? ' is-disabled' : ''}`}
|
||
onClick={sendCode}
|
||
>
|
||
<Text>{cooldown > 0 ? `${cooldown}s 后重发` : '获取验证码'}</Text>
|
||
</View>
|
||
</View>
|
||
|
||
<View className="login-remember" onClick={() => setRememberAccount((v) => !v)}>
|
||
<View className={`login-checkbox${rememberAccount ? ' is-checked' : ''}`} />
|
||
<Text>记住账号</Text>
|
||
</View>
|
||
|
||
<View className="login-agreement" onClick={() => setAgreed((v) => !v)}>
|
||
<View className={`login-checkbox${agreed ? ' is-checked' : ''}`} />
|
||
<Text className="login-agreement-text">
|
||
我已阅读并同意
|
||
<Text
|
||
className="login-agreement-link"
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
Taro.navigateTo({ url: '/pages/user-agreement/index' });
|
||
}}
|
||
>
|
||
《用户协议》
|
||
</Text>
|
||
与
|
||
<Text
|
||
className="login-agreement-link"
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
Taro.navigateTo({ url: '/pages/privacy-policy/index' });
|
||
}}
|
||
>
|
||
《隐私政策》
|
||
</Text>
|
||
</Text>
|
||
</View>
|
||
|
||
{msg ? <Text className="login-msg">{msg}</Text> : null}
|
||
|
||
<Button className="hq-btn hq-btn--primary hq-btn--block login-submit" loading={loading} onClick={smsLogin}>
|
||
登录
|
||
</Button>
|
||
|
||
<View className="login-divider">
|
||
<Text>其他登录方式</Text>
|
||
</View>
|
||
|
||
<View
|
||
className={`login-wechat-btn${wxLoading ? ' is-disabled' : ''}`}
|
||
onClick={wxLoading ? undefined : wechatLogin}
|
||
>
|
||
<WechatIcon />
|
||
<Text>{wxLoading ? '登录中...' : '微信一键授权'}</Text>
|
||
</View>
|
||
</View>
|
||
|
||
<View className="login-footer">
|
||
<Text className="material-symbols-outlined" style="font-size:16px">verified_user</Text>
|
||
<Text>杜康好客 · 传承千年</Text>
|
||
</View>
|
||
</View>
|
||
);
|
||
}
|