小程序修改

This commit is contained in:
2026-07-12 19:44:36 +08:00
parent 3b4833b51a
commit e2dfb08de3
45 changed files with 2028 additions and 347 deletions
+159 -68
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { View, Text, Input, Button } from '@tarojs/components';
import { View, Text, Input, Button, Image } from '@tarojs/components';
import { useRouter } from '@tarojs/taro';
import {
SmsScene,
@@ -9,8 +9,12 @@ import {
} from '@dukang/shared-types';
import PageShell from '../../components/PageShell';
import WechatLoginButton from '../../components/WechatLoginButton';
import { BRAND_LOGO_WIDE_URL } from '@dukang/shared-types';
import { finishLoginNavigate } from '../../lib/auth-nav';
import { request, saveAuth, toast, type SessionPayload } from '../../lib/api';
import { bindWechatForUser } from '../../lib/wechat-auth';
import { fetchUserProfile } from '../../lib/pay-wechat';
import { saveUserPhone } from '../../lib/user-phone';
import { isLoggedIn, request, saveAuth, toast, type SessionPayload } from '../../lib/api';
import { loginWithWechat } from '../../lib/wechat-auth';
function normalizePhone(value: string) {
@@ -24,6 +28,10 @@ function isValidPhone(phone: string) {
export default function LoginPage() {
const router = useRouter();
const returnTo = router.params.return || '';
const needPhone = router.params.needPhone === '1';
const needWechat = router.params.needWechat === '1';
const initialBindMode = router.params.bindMode === '1';
const initialWxSessionKey = router.params.wxSessionKey || null;
const [phone, setPhone] = useState('');
const [code, setCode] = useState('');
@@ -34,9 +42,10 @@ export default function LoginPage() {
const [agreed, setAgreed] = useState(true);
const [msg, setMsg] = useState('');
const [sentHint, setSentHint] = useState('');
const [bindMode, setBindMode] = useState(false);
const [wxSessionKey, setWxSessionKey] = useState<string | null>(null);
const [bindMode, setBindMode] = useState(initialBindMode);
const [wxSessionKey, setWxSessionKey] = useState<string | null>(initialWxSessionKey);
const [wxAuthorize, setWxAuthorize] = useState(true);
const [completeMode, setCompleteMode] = useState<'phone' | 'wechat' | null>(null);
useEffect(() => {
request<ClientRuntimeConfig>('/common/client-config')
@@ -44,6 +53,30 @@ export default function LoginPage() {
.catch(() => setWxAuthorize(true));
}, []);
useEffect(() => {
if (!isLoggedIn()) {
setCompleteMode(null);
return;
}
fetchUserProfile()
.then((me) => {
if (needPhone && !me.phoneVerified) {
setCompleteMode('phone');
return;
}
if (needWechat && !me.hasWechat) {
setCompleteMode('wechat');
return;
}
if (needPhone || needWechat) {
finishLoginNavigate(returnTo);
return;
}
setCompleteMode(null);
})
.catch(() => setCompleteMode(null));
}, [needPhone, needWechat, returnTo]);
useEffect(() => {
if (cooldown <= 0) return;
const timer = setTimeout(() => setCooldown((c) => Math.max(0, c - 1)), 1000);
@@ -58,8 +91,9 @@ export default function LoginPage() {
return true;
}
function applySessionAndLeave(data: SessionPayload | WechatLoginResult) {
function applySessionAndLeave(data: SessionPayload | WechatLoginResult, phone?: string) {
if (!data.accessToken) return;
if (phone) saveUserPhone(phone);
saveAuth({
accessToken: data.accessToken,
refreshToken: data.refreshToken,
@@ -95,12 +129,11 @@ export default function LoginPage() {
setSentHint('');
setSending(true);
try {
const scene =
bindMode || completeMode === 'phone' ? SmsScene.BIND_PHONE : SmsScene.USER_LOGIN;
await request('/auth/sms/send', {
method: 'POST',
data: {
phone: normalized,
scene: bindMode ? SmsScene.BIND_PHONE : SmsScene.USER_LOGIN,
},
data: { phone: normalized, scene },
});
setCooldown(60);
setSentHint('验证码已发送');
@@ -132,13 +165,24 @@ export default function LoginPage() {
data: { wxSessionKey, phone: normalized, code: code.trim() },
});
handleWechatLoginResult(data);
saveUserPhone(normalized);
return;
}
if (completeMode === 'phone' && isLoggedIn()) {
await request('/auth/phone/bind', {
method: 'POST',
data: { phone: normalized, code: code.trim() },
});
saveUserPhone(normalized);
toast('手机号验证成功', 'success');
finishLoginNavigate(returnTo);
return;
}
const data = await request<SessionPayload>('/auth/login/sms', {
method: 'POST',
data: { phone: normalized, code: code.trim() },
});
applySessionAndLeave(data);
applySessionAndLeave(data, normalized);
} catch (e) {
setMsg(e instanceof Error ? e.message : '登录失败');
} finally {
@@ -152,6 +196,22 @@ export default function LoginPage() {
setSentHint('');
setWxLoading(true);
try {
if (completeMode === 'wechat' && isLoggedIn()) {
const result = await bindWechatForUser();
if (!result.ok && result.needBindPhone) {
setBindMode(true);
setWxSessionKey(result.wxSessionKey);
setCompleteMode('phone');
setMsg('请绑定手机号完成认证');
return;
}
if (result.ok) {
toast('微信授权成功', 'success');
finishLoginNavigate(returnTo);
return;
}
return;
}
const result = await loginWithWechat();
handleWechatLoginResult(result);
} catch (e) {
@@ -168,89 +228,120 @@ export default function LoginPage() {
const displayMsg = msg || sentHint;
const codeDisabled = cooldown > 0 || sending;
const showWechatLogin =
!bindMode && (process.env.TARO_ENV === 'weapp' || wxAuthorize);
(completeMode === 'wechat' || (!bindMode && !completeMode)) &&
(process.env.TARO_ENV === 'weapp' || wxAuthorize);
const showSmsForm = completeMode !== 'wechat';
const cardTitle =
completeMode === 'phone'
? '验证手机号'
: bindMode
? '绑定手机号'
: completeMode === 'wechat'
? '微信授权'
: '手机验证码登录';
return (
<PageShell variant="plain" className="login-page">
<View className="login-header">
<View className="login-logo-wrap">
<View className="login-logo">
<Text></Text>
<Image className="login-logo-img" src={BRAND_LOGO_WIDE_URL} mode="aspectFit" />
</View>
<Text className="login-logo-badge"></Text>
</View>
<View className="login-welcome">
<Text className="login-welcome-title"></Text>
<Text className="login-welcome-sub"></Text>
<Text className="login-welcome-title">
{completeMode === 'phone'
? '完成手机验证'
: completeMode === 'wechat'
? '完成微信授权'
: '欢迎来到杜康好客'}
</Text>
<Text className="login-welcome-sub">
{completeMode ? '完成后将返回继续支付' : '买美酒,享好礼'}
</Text>
</View>
</View>
<View className="login-main">
<View className="login-card">
<Text className="login-card-title">
{bindMode ? '绑定手机号' : '手机验证码登录'}
</Text>
<View className="login-field">
<Text className="login-field-prefix">+86</Text>
<Input
className="login-field-input"
type="number"
maxlength={11}
placeholder="请输入手机号"
value={phone}
onInput={(e) => {
setPhone(normalizePhone(e.detail.value));
setMsg('');
setSentHint('');
}}
/>
{completeMode === 'wechat' ? (
<View className="login-card">
<Text className="login-card-title"></Text>
<Text className="login-msg login-msg--hint" style={{ marginBottom: 16 }}>
使
</Text>
{showWechatLogin ? (
<WechatLoginButton loading={wxLoading} onClick={() => void wechatLogin()} />
) : null}
</View>
) : (
<View className="login-card">
<Text className="login-card-title">{cardTitle}</Text>
<View className="login-field">
<Input
className="login-field-input"
type="number"
maxlength={6}
placeholder="请输入验证码"
value={code}
onInput={(e) => setCode(e.detail.value.replace(/\D/g, '').slice(0, 6))}
/>
<Text
className={`login-get-code${codeDisabled ? ' login-get-code--disabled' : ''}`}
onClick={() => void onSendCode()}
<View className="login-field">
<Text className="login-field-prefix">+86</Text>
<Input
className="login-field-input"
type="number"
maxlength={11}
placeholder="请输入手机号"
value={phone}
onInput={(e) => {
setPhone(normalizePhone(e.detail.value));
setMsg('');
setSentHint('');
}}
/>
</View>
<View className="login-field">
<Input
className="login-field-input"
type="number"
maxlength={6}
placeholder="请输入验证码"
value={code}
onInput={(e) => setCode(e.detail.value.replace(/\D/g, '').slice(0, 6))}
/>
<Text
className={`login-get-code${codeDisabled ? ' login-get-code--disabled' : ''}`}
onClick={() => void onSendCode()}
>
{sending ? '发送中...' : cooldown > 0 ? `${cooldown}s 后重新获取` : '获取验证码'}
</Text>
</View>
{displayMsg ? (
<Text className={`login-msg${sentHint && !msg ? ' login-msg--hint' : ''}`}>
{displayMsg}
</Text>
) : null}
<Button
className="login-sms-btn"
loading={loading}
disabled={loading}
onClick={() => void login()}
>
{sending ? '发送中...' : cooldown > 0 ? `${cooldown}s 后重新获取` : '获取验证码'}
</Text>
{loading
? '处理中...'
: completeMode === 'phone'
? '完成验证'
: bindMode
? '绑定并登录'
: '登录'}
</Button>
</View>
)}
{displayMsg ? (
<Text className={`login-msg${sentHint && !msg ? ' login-msg--hint' : ''}`}>
{displayMsg}
</Text>
) : null}
<Button
className="login-sms-btn"
loading={loading}
disabled={loading}
onClick={() => void login()}
>
{loading ? '登录中...' : bindMode ? '绑定并登录' : '登录'}
</Button>
</View>
{showWechatLogin ? (
{showWechatLogin && completeMode !== 'wechat' ? (
<>
<View className="login-divider">
<View className="login-divider-line" />
<Text className="login-divider-text"></Text>
<View className="login-divider-line" />
</View>
<WechatLoginButton
loading={wxLoading}
onClick={() => void wechatLogin()}
/>
<WechatLoginButton loading={wxLoading} onClick={() => void wechatLogin()} />
</>
) : null}
</View>