微信小程序审核不通过修改
CI / verify (pull_request) Has been cancelled

This commit is contained in:
2026-07-21 09:32:06 +08:00
parent 350a086a73
commit 935ab0d1d4
14 changed files with 466 additions and 242 deletions
+187 -125
View File
@@ -9,6 +9,7 @@ import {
} from '@dukang/shared-types';
import PageShell from '../../components/PageShell';
import WechatLoginButton from '../../components/WechatLoginButton';
import PhoneQuickLoginButton from '../../components/PhoneQuickLoginButton';
import { BRAND_LOGO_WIDE_URL } from '@dukang/shared-types';
import { finishLoginNavigate, forceReloadAfterAccountMerge } from '../../lib/auth-nav';
import {
@@ -24,6 +25,8 @@ import {
} from '../../lib/mini-wechat-profile';
import { isLoggedIn, request, saveAuth, toast, type SessionPayload } from '../../lib/api';
const IS_WEAPP = process.env.TARO_ENV === 'weapp';
function normalizePhone(value: string) {
return value.replace(/\D/g, '').slice(0, 11);
}
@@ -32,6 +35,44 @@ function isValidPhone(phone: string) {
return /^1[3-9]\d{9}$/.test(phone);
}
function AgreementRow({
agreed,
onToggle,
}: {
agreed: boolean;
onToggle: () => void;
}) {
return (
<View className="login-agreement" onClick={onToggle}>
<View className={`login-agreement-check${agreed ? ' login-agreement-check--on' : ''}`}>
{agreed ? <Text></Text> : null}
</View>
<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>
);
}
export default function LoginPage() {
const router = useRouter();
const returnTo = router.params.return || '';
@@ -44,8 +85,10 @@ export default function LoginPage() {
const [code, setCode] = useState('');
const [loading, setLoading] = useState(false);
const [wxLoading, setWxLoading] = useState(false);
const [phoneQuickLoading, setPhoneQuickLoading] = useState(false);
const [sending, setSending] = useState(false);
const [cooldown, setCooldown] = useState(0);
/** 须用户主动勾选,禁止默认同意 */
const [agreed, setAgreed] = useState(false);
const [msg, setMsg] = useState('');
const [sentHint, setSentHint] = useState('');
@@ -53,6 +96,7 @@ export default function LoginPage() {
const [wxSessionKey, setWxSessionKey] = useState<string | null>(initialWxSessionKey);
const [wxAuthorize, setWxAuthorize] = useState(true);
const [completeMode, setCompleteMode] = useState<'phone' | 'wechat' | null>(null);
const [showSmsForm, setShowSmsForm] = useState(!IS_WEAPP);
useEffect(() => {
request<ClientRuntimeConfig>('/common/client-config')
@@ -65,7 +109,6 @@ export default function LoginPage() {
setCompleteMode(null);
return;
}
// 完善资料场景才拉 profile;普通登录勿抢跑 /auth/me,避免旧 token 401 与短信登录竞态
if (!needPhone && !needWechat) {
setCompleteMode(null);
return;
@@ -76,6 +119,7 @@ export default function LoginPage() {
if (cancelled) return;
if (needPhone && !me.phoneVerified) {
setCompleteMode('phone');
setShowSmsForm(true);
return;
}
if (needWechat && !me.hasWechat) {
@@ -100,7 +144,7 @@ export default function LoginPage() {
function ensureAgreed() {
if (!agreed) {
setMsg('请先勾选同意用户协议');
setMsg('请先阅读并勾选同意用户服务协议》和《隐私政策》');
return false;
}
return true;
@@ -108,18 +152,18 @@ export default function LoginPage() {
function applySessionAndLeave(
data: SessionPayload | WechatLoginResult,
phone?: string,
phoneValue?: string,
wxInfo?: MiniWechatProfile | null,
successToast = '登录成功',
) {
if (!data.accessToken) return;
if (phone) saveUserPhone(phone);
if (phoneValue) saveUserPhone(phoneValue);
saveAuth({
accessToken: data.accessToken,
refreshToken: data.refreshToken,
});
void syncMiniWechatProfile(wxInfo ?? getCachedWxProfile());
if (!phone) {
if (!phoneValue) {
void fetchUserProfile()
.then((me) => resolveDefaultUserPhone(me))
.catch(() => {});
@@ -133,7 +177,6 @@ export default function LoginPage() {
}
function handleWechatLoginResult(result: WechatLoginResult, wxInfo?: MiniWechatProfile | null) {
// 微信授权成功即登录;手机号改为下单页可选绑定
if (result.accessToken) {
applySessionAndLeave(result, undefined, wxInfo);
return;
@@ -141,11 +184,49 @@ export default function LoginPage() {
if (result.needBindPhone && result.wxSessionKey) {
setBindMode(true);
setWxSessionKey(result.wxSessionKey);
setMsg('微信授权成功,可绑定手机号(也可跳过,稍后在下单时再绑定)');
setShowSmsForm(true);
setMsg('授权成功,可绑定手机号(也可稍后在下单时再绑定)');
setSentHint('');
return;
}
setMsg('微信登录未完成,请重试或使用手机号登录');
setMsg('登录未完成,请重试或使用手机号登录');
}
async function onPhoneQuickLogin(phoneCode: string) {
if (!ensureAgreed()) return;
setPhoneQuickLoading(true);
setMsg('');
setSentHint('');
try {
let loginCode: string | undefined;
try {
const loginRes = await Taro.login();
loginCode = loginRes.code || undefined;
} catch {
/* openId 绑定失败不阻断手机号登录 */
}
const data = await request<WechatLoginResult>('/auth/login/wechat-phone', {
method: 'POST',
data: {
phoneCode,
...(loginCode ? { loginCode } : {}),
platform: 'mini',
},
});
if (!data?.accessToken) {
setMsg('登录成功但未返回令牌,请重试');
return;
}
const profilePhone =
typeof data.user === 'object' && data.user && 'phone' in data.user
? String((data.user as { phone?: string }).phone || '')
: '';
applySessionAndLeave(data, profilePhone || undefined);
} catch (e) {
setMsg(e instanceof Error ? e.message : '手机号快捷登录失败');
} finally {
setPhoneQuickLoading(false);
}
}
async function onSendCode() {
@@ -200,7 +281,6 @@ export default function LoginPage() {
return;
}
if (completeMode === 'phone' && isLoggedIn()) {
// bind 返回新 session(合并账号后旧 guest JWT 立刻失效),必须落盘后再离开
const data = await request<SessionPayload>('/auth/phone/bind', {
method: 'POST',
data: { phone: normalized, code: code.trim() },
@@ -234,7 +314,6 @@ export default function LoginPage() {
setSentHint('');
setWxLoading(true);
try {
// 登录仅绑定 openId;资料展示走账号昵称/头像
const wxInfo = getCachedWxProfile();
if (completeMode === 'wechat' && isLoggedIn()) {
@@ -246,12 +325,13 @@ export default function LoginPage() {
setBindMode(true);
setWxSessionKey(result.wxSessionKey);
setCompleteMode('phone');
setShowSmsForm(true);
setMsg('请绑定手机号完成认证');
return;
}
if (result.ok) {
if (wxInfo) await syncMiniWechatProfile(wxInfo);
toast('微信授权成功', 'success');
toast('授权成功', 'success');
finishLoginNavigate(returnTo);
return;
}
@@ -260,11 +340,11 @@ export default function LoginPage() {
const result = await loginWithWechat();
if (result) handleWechatLoginResult(result, wxInfo);
} catch (e) {
const raw = e instanceof Error ? e.message : '微信登录失败';
const raw = e instanceof Error ? e.message : '授权登录失败';
const hint = /invalid code/i.test(raw)
? process.env.TARO_ENV === 'weapp'
? '微信 code 无效:请确认后端 WX_MINI_APP_ID 与小程序 appid 一致,或本地联调使用 MOCK_WECHAT'
: '微信授权失败:请确认公众号 WX_APP_ID / 网页授权域名配置正确'
? '授权失败:请确认后端小程序 AppID 配置正确'
: '授权失败:请确认公众号网页授权域名配置正确'
: raw;
setMsg(hint);
} finally {
@@ -274,18 +354,19 @@ export default function LoginPage() {
const displayMsg = msg || sentHint;
const codeDisabled = cooldown > 0 || sending;
const showWechatLogin =
(completeMode === 'wechat' || (!bindMode && !completeMode)) &&
(process.env.TARO_ENV === 'weapp' || wxAuthorize);
const showSmsForm = completeMode !== 'wechat';
const showAuthLogin =
(completeMode === 'wechat' || (!IS_WEAPP && !bindMode && !completeMode)) &&
(IS_WEAPP || wxAuthorize);
const showPhoneQuick =
IS_WEAPP && completeMode !== 'wechat' && !bindMode && completeMode !== 'phone';
const cardTitle =
completeMode === 'phone'
? '验证手机号'
: bindMode
? '绑定手机号'
: completeMode === 'wechat'
? '微信授权'
: '手机验证码登录';
? '授权登录'
: '手机号快捷登录';
return (
<PageShell variant="plain" className="login-page">
@@ -301,7 +382,7 @@ export default function LoginPage() {
{completeMode === 'phone'
? '建议绑定手机号'
: completeMode === 'wechat'
? '完成微信授权'
? '完成授权登录'
: '欢迎来到杜康好客'}
</Text>
<Text className="login-welcome-sub">
@@ -317,77 +398,100 @@ export default function LoginPage() {
<View className="login-main">
{completeMode === 'wechat' ? (
<View className="login-card">
<Text className="login-card-title"></Text>
<Text className="login-card-title"></Text>
<Text className="login-msg login-msg--hint" style={{ marginBottom: 16 }}>
使
使
</Text>
<View className="login-agreement" onClick={() => setAgreed((v) => !v)}>
<View className={`login-agreement-check${agreed ? ' login-agreement-check--on' : ''}`}>
{agreed ? <Text></Text> : null}
</View>
<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>
<AgreementRow agreed={agreed} onToggle={() => setAgreed((v) => !v)} />
{displayMsg ? (
<Text className={`login-msg${sentHint && !msg ? ' login-msg--hint' : ''}`}>
{displayMsg}
</Text>
</View>
{showWechatLogin ? (
<WechatLoginButton loading={wxLoading} onClick={() => void wechatLogin()} />
) : null}
{showAuthLogin ? (
<WechatLoginButton loading={wxLoading} label="授权登录" onClick={() => void wechatLogin()} />
) : null}
</View>
) : (
<View className="login-card">
<Text className="login-card-title">{cardTitle}</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('');
}}
/>
</View>
<AgreementRow agreed={agreed} onToggle={() => setAgreed((v) => !v)} />
<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))}
{showPhoneQuick ? (
<PhoneQuickLoginButton
loading={phoneQuickLoading}
agreed={agreed}
onRequireAgree={() => ensureAgreed()}
onGetPhoneNumber={(phoneCode) => void onPhoneQuickLogin(phoneCode)}
onFail={(message) => setMsg(message)}
/>
<Text
className={`login-get-code${codeDisabled ? ' login-get-code--disabled' : ''}`}
onClick={() => void onSendCode()}
>
{sending ? '发送中...' : cooldown > 0 ? `${cooldown}s 后重新获取` : '获取验证码'}
</Text>
</View>
) : null}
{showPhoneQuick ? (
<View className="login-divider" style={{ marginTop: 20 }}>
<View className="login-divider-line" />
<Text
className="login-divider-text"
onClick={() => setShowSmsForm((v) => !v)}
>
{showSmsForm ? '收起验证码登录' : '使用验证码登录'}
</Text>
<View className="login-divider-line" />
</View>
) : null}
{(showSmsForm || !showPhoneQuick) && (
<>
<View className="login-field" style={showPhoneQuick ? { marginTop: 8 } : undefined}>
<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>
<View
className={`login-sms-btn${loading ? ' login-sms-btn--disabled' : ''}`}
onClick={loading ? undefined : () => void login()}
>
<Text className="login-sms-btn__text">
{loading
? '处理中...'
: completeMode === 'phone'
? '完成验证'
: bindMode
? '绑定并登录'
: '验证码登录'}
</Text>
</View>
</>
)}
{displayMsg ? (
<Text className={`login-msg${sentHint && !msg ? ' login-msg--hint' : ''}`}>
@@ -395,48 +499,6 @@ export default function LoginPage() {
</Text>
) : null}
<View className="login-agreement" onClick={() => setAgreed((v) => !v)}>
<View className={`login-agreement-check${agreed ? ' login-agreement-check--on' : ''}`}>
{agreed ? <Text></Text> : null}
</View>
<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>
<View
className={`login-sms-btn${loading ? ' login-sms-btn--disabled' : ''}`}
onClick={loading ? undefined : () => void login()}
>
<Text className="login-sms-btn__text">
{loading
? '处理中...'
: completeMode === 'phone'
? '完成验证'
: bindMode
? '绑定并登录'
: '登录'}
</Text>
</View>
{completeMode === 'phone' ? (
<View
className="login-skip-bind"
@@ -451,14 +513,14 @@ export default function LoginPage() {
</View>
)}
{showWechatLogin && completeMode !== 'wechat' ? (
{showAuthLogin && 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} label="授权登录" onClick={() => void wechatLogin()} />
</>
) : null}
</View>