feat;提交管理端和城市合伙人端
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
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 { 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(true);
|
||||
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('');
|
||||
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>
|
||||
|
||||
<Button className="hq-btn hq-btn--primary hq-btn--block login-submit" loading={loading} onClick={smsLogin}>
|
||||
登录
|
||||
</Button>
|
||||
|
||||
{msg ? <Text className="login-msg">{msg}</Text> : null}
|
||||
|
||||
<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 className="login-agreement" onClick={() => setAgreed((v) => !v)}>
|
||||
<View className={`login-checkbox${agreed ? ' is-checked' : ''}`} />
|
||||
<Text className="login-agreement-text">
|
||||
我已阅读并同意
|
||||
<Text className="login-agreement-link">《用户协议》</Text>
|
||||
与
|
||||
<Text className="login-agreement-link">《隐私政策》</Text>
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className="login-footer">
|
||||
<Text className="material-symbols-outlined" style="font-size:16px">verified_user</Text>
|
||||
<Text>杜康好客 · 传承千年</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user