本地环境不验证手机号和微信登录授权

This commit is contained in:
2026-07-07 21:42:56 +08:00
parent ec748335bf
commit e32ccebc80
14 changed files with 132 additions and 24 deletions
@@ -2,11 +2,13 @@ import { useEffect, useId, useRef, useState } from 'react';
import { uploadFileToOss, type OssMediaType } from '../lib/upload'; import { uploadFileToOss, type OssMediaType } from '../lib/upload';
import { import {
authorizePartnerWechat, authorizePartnerWechat,
fetchClientConfig,
fetchPartnerProfile, fetchPartnerProfile,
needsWechatAuth, needsWechatAuth,
type PartnerProfile, type PartnerProfile,
} from '../lib/wechat-auth'; } from '../lib/wechat-auth';
import { isWechatEnv, weixinSdk } from '../lib/weixin'; import { isWechatEnv, weixinSdk } from '../lib/weixin';
import type { ClientRuntimeConfig } from '@dukang/shared-types';
type OssUploadFieldProps = { type OssUploadFieldProps = {
value?: string; value?: string;
@@ -42,17 +44,19 @@ export default function OssUploadField({
const [authorizing, setAuthorizing] = useState(false); const [authorizing, setAuthorizing] = useState(false);
const [error, setError] = useState(''); const [error, setError] = useState('');
const [profile, setProfile] = useState<PartnerProfile | null>(null); const [profile, setProfile] = useState<PartnerProfile | null>(null);
const [clientConfig, setClientConfig] = useState<ClientRuntimeConfig | null>(null);
const resolvedAccept = const resolvedAccept =
accept ?? (mediaType === 'VIDEO' ? 'video/*' : mediaType === 'FILE' ? 'image/*,.pdf' : 'image/*'); accept ?? (mediaType === 'VIDEO' ? 'video/*' : mediaType === 'FILE' ? 'image/*,.pdf' : 'image/*');
const useWechatPicker = isWechatEnv() && mediaType !== 'VIDEO'; const useWechatPicker = isWechatEnv() && mediaType !== 'VIDEO';
const needsAuth = useWechatPicker && needsWechatAuth(profile) && wechatReady !== true; const needsAuth = useWechatPicker && needsWechatAuth(profile, clientConfig) && wechatReady !== true;
useEffect(() => { useEffect(() => {
if (!useWechatPicker) return; if (!useWechatPicker) return;
void fetchPartnerProfile() void Promise.all([fetchPartnerProfile(), fetchClientConfig()])
.then((me) => { .then(([me, config]) => {
setProfile(me); setProfile(me);
setClientConfig(config);
onWechatReadyChange?.(!!me.hasWechat); onWechatReadyChange?.(!!me.hasWechat);
}) })
.catch(() => { .catch(() => {
+24 -2
View File
@@ -1,4 +1,5 @@
import type { WechatLoginResult } from '@dukang/shared-types'; import type { ClientRuntimeConfig, WechatLoginResult } from '@dukang/shared-types';
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
import { WECHAT_INAPP_REQUIRED_MSG } from '@dukang/weixin-sdk'; import { WECHAT_INAPP_REQUIRED_MSG } from '@dukang/weixin-sdk';
import { isWechatEnv, weixinSdk } from './weixin'; import { isWechatEnv, weixinSdk } from './weixin';
import { request, saveAuth } from './api'; import { request, saveAuth } from './api';
@@ -11,15 +12,28 @@ export type PartnerProfile = {
hasWechat?: boolean; hasWechat?: boolean;
}; };
export async function fetchClientConfig(): Promise<ClientRuntimeConfig> {
return request<ClientRuntimeConfig>('PARTNER_H5', '/common/client-config');
}
export async function fetchPartnerProfile(): Promise<PartnerProfile> { export async function fetchPartnerProfile(): Promise<PartnerProfile> {
return request<PartnerProfile>('PARTNER_H5', '/partner/me'); return request<PartnerProfile>('PARTNER_H5', '/partner/me');
} }
/** 微信内上传照片前需完成公众号授权绑定 */ /** 微信内上传照片前需完成公众号授权绑定 */
export function needsWechatAuth(profile: PartnerProfile | null): boolean { export function needsWechatAuth(
profile: PartnerProfile | null,
config?: Pick<ClientRuntimeConfig, 'wxAuthorize'> | null,
): boolean {
if (config && !isWxAuthorizeEnabled(config)) return false;
return isWechatEnv() && !!profile && !profile.hasWechat; return isWechatEnv() && !!profile && !profile.hasWechat;
} }
export async function checkNeedsWechatAuth(profile: PartnerProfile | null): Promise<boolean> {
const config = await fetchClientConfig();
return needsWechatAuth(profile, config);
}
/** 处理微信登录/绑定结果,返回是否已拿到 token 可进入首页 */ /** 处理微信登录/绑定结果,返回是否已拿到 token 可进入首页 */
export function handlePartnerWechatLoginResult(result: WechatLoginResult): boolean { export function handlePartnerWechatLoginResult(result: WechatLoginResult): boolean {
if (!result.accessToken) return false; if (!result.accessToken) return false;
@@ -29,6 +43,8 @@ export function handlePartnerWechatLoginResult(result: WechatLoginResult): boole
export async function handlePartnerWechatCallback(): Promise<WechatLoginResult | null> { export async function handlePartnerWechatCallback(): Promise<WechatLoginResult | null> {
if (!isWechatEnv()) return null; if (!isWechatEnv()) return null;
const config = await fetchClientConfig();
if (!isWxAuthorizeEnabled(config)) return null;
return weixinSdk.handleOAuthCallback(); return weixinSdk.handleOAuthCallback();
} }
@@ -37,6 +53,8 @@ export async function handlePartnerWechatCallback(): Promise<WechatLoginResult |
* 返回 true = 已登录;void = 已跳转授权页等待回调。 * 返回 true = 已登录;void = 已跳转授权页等待回调。
*/ */
export async function loginPartnerWithWechat(): Promise<boolean | void> { export async function loginPartnerWithWechat(): Promise<boolean | void> {
const config = await fetchClientConfig();
if (!isWxAuthorizeEnabled(config)) return false;
if (!isWechatEnv()) { if (!isWechatEnv()) {
throw new Error(WECHAT_INAPP_REQUIRED_MSG); throw new Error(WECHAT_INAPP_REQUIRED_MSG);
} }
@@ -48,11 +66,15 @@ export async function loginPartnerWithWechat(): Promise<boolean | void> {
* 短信登录成功后于微信内自动发起 OAuth,将 openId 绑定到当前合伙人账号(便于同一微信后续免登)。 * 短信登录成功后于微信内自动发起 OAuth,将 openId 绑定到当前合伙人账号(便于同一微信后续免登)。
*/ */
export async function bindPartnerWechatAfterSmsLogin(): Promise<void> { export async function bindPartnerWechatAfterSmsLogin(): Promise<void> {
const config = await fetchClientConfig();
if (!isWxAuthorizeEnabled(config)) return;
if (!isWechatEnv()) return; if (!isWechatEnv()) return;
await weixinSdk.login(); await weixinSdk.login();
} }
export async function authorizePartnerWechat(): Promise<WechatLoginResult | void> { export async function authorizePartnerWechat(): Promise<WechatLoginResult | void> {
const config = await fetchClientConfig();
if (!isWxAuthorizeEnabled(config)) return;
if (!isWechatEnv()) { if (!isWechatEnv()) {
throw new Error(WECHAT_INAPP_REQUIRED_MSG); throw new Error(WECHAT_INAPP_REQUIRED_MSG);
} }
+16 -3
View File
@@ -3,11 +3,13 @@ import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { request, saveAuth } from '../lib/api'; import { request, saveAuth } from '../lib/api';
import { import {
bindPartnerWechatAfterSmsLogin, bindPartnerWechatAfterSmsLogin,
fetchClientConfig,
handlePartnerWechatCallback, handlePartnerWechatCallback,
handlePartnerWechatLoginResult, handlePartnerWechatLoginResult,
loginPartnerWithWechat, loginPartnerWithWechat,
} from '../lib/wechat-auth'; } from '../lib/wechat-auth';
import { isWechatEnv } from '../lib/weixin'; import { isWechatEnv } from '../lib/weixin';
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
import { WECHAT_INAPP_REQUIRED_MSG } from '@dukang/weixin-sdk'; import { WECHAT_INAPP_REQUIRED_MSG } from '@dukang/weixin-sdk';
const REMEMBER_PHONE_KEY = 'partner_remember_phone'; const REMEMBER_PHONE_KEY = 'partner_remember_phone';
@@ -47,16 +49,23 @@ export default function LoginPage() {
const [wxLoading, setWxLoading] = useState(false); const [wxLoading, setWxLoading] = useState(false);
const [msg, setMsg] = useState(''); const [msg, setMsg] = useState('');
const [codeCooldown, setCodeCooldown] = useState(0); const [codeCooldown, setCodeCooldown] = useState(0);
const [wxAuthorize, setWxAuthorize] = useState(false);
useEffect(() => { useEffect(() => {
if (!isWechatEnv()) return; fetchClientConfig()
.then((config) => setWxAuthorize(isWxAuthorizeEnabled(config)))
.catch(() => setWxAuthorize(false));
}, []);
useEffect(() => {
if (!isWechatEnv() || !wxAuthorize) return;
void handlePartnerWechatCallback() void handlePartnerWechatCallback()
.then((result) => { .then((result) => {
if (!result) return; if (!result) return;
if (handlePartnerWechatLoginResult(result)) navigate('/'); if (handlePartnerWechatLoginResult(result)) navigate('/');
}) })
.catch((e) => setMsg(formatWechatError(e))); .catch((e) => setMsg(formatWechatError(e)));
}, [navigate]); }, [navigate, wxAuthorize]);
function formatWechatError(e: unknown): string { function formatWechatError(e: unknown): string {
const text = e instanceof Error ? e.message : '微信登录失败'; const text = e instanceof Error ? e.message : '微信登录失败';
@@ -129,7 +138,7 @@ export default function LoginPage() {
}); });
saveAuth(data); saveAuth(data);
persistRememberAccount(phone); persistRememberAccount(phone);
if (isWechatEnv()) { if (isWechatEnv() && wxAuthorize) {
setMsg('登录成功,正在关联微信…'); setMsg('登录成功,正在关联微信…');
await bindPartnerWechatAfterSmsLogin(); await bindPartnerWechatAfterSmsLogin();
return; return;
@@ -264,6 +273,8 @@ export default function LoginPage() {
{!loading && <span className="material-symbols-outlined">arrow_forward</span>} {!loading && <span className="material-symbols-outlined">arrow_forward</span>}
</button> </button>
{wxAuthorize && (
<>
<div className="partner-auth-divider"><span></span></div> <div className="partner-auth-divider"><span></span></div>
<button type="button" className="partner-btn-wechat" onClick={wechatLogin} disabled={wxLoading}> <button type="button" className="partner-btn-wechat" onClick={wechatLogin} disabled={wxLoading}>
<svg viewBox="0 0 24 24" fill="#07C160" aria-hidden> <svg viewBox="0 0 24 24" fill="#07C160" aria-hidden>
@@ -271,6 +282,8 @@ export default function LoginPage() {
</svg> </svg>
<span>{wxLoading ? '登录中...' : '微信一键授权'}</span> <span>{wxLoading ? '登录中...' : '微信一键授权'}</span>
</button> </button>
</>
)}
<label className="partner-checkbox-row"> <label className="partner-checkbox-row">
<input type="checkbox" checked={agreed} onChange={(e) => setAgreed(e.target.checked)} /> <input type="checkbox" checked={agreed} onChange={(e) => setAgreed(e.target.checked)} />
+20 -2
View File
@@ -1,4 +1,5 @@
import type { WechatLoginResult } from '@dukang/shared-types'; import type { ClientRuntimeConfig, WechatLoginResult } from '@dukang/shared-types';
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
import { isWechatEnv, weixinSdk } from './weixin'; import { isWechatEnv, weixinSdk } from './weixin';
import { request, saveAuth, type ShopSessionPayload } from './api'; import { request, saveAuth, type ShopSessionPayload } from './api';
@@ -11,14 +12,27 @@ export type ShopAccountProfile = {
store?: { name: string }; store?: { name: string };
}; };
export async function fetchClientConfig(): Promise<ClientRuntimeConfig> {
return request<ClientRuntimeConfig>('SHOP_H5', '/common/client-config');
}
export async function fetchShopAccount(): Promise<ShopAccountProfile> { export async function fetchShopAccount(): Promise<ShopAccountProfile> {
return request<ShopAccountProfile>('SHOP_H5', '/shop/auth/me'); return request<ShopAccountProfile>('SHOP_H5', '/shop/auth/me');
} }
export function needsWechatAuth(profile: ShopAccountProfile | null): boolean { export function needsWechatAuth(
profile: ShopAccountProfile | null,
config?: Pick<ClientRuntimeConfig, 'wxAuthorize'> | null,
): boolean {
if (config && !isWxAuthorizeEnabled(config)) return false;
return isWechatEnv() && !!profile && !profile.wxOpenId; return isWechatEnv() && !!profile && !profile.wxOpenId;
} }
export async function checkNeedsWechatAuth(profile: ShopAccountProfile | null): Promise<boolean> {
const config = await fetchClientConfig();
return needsWechatAuth(profile, config);
}
export function sessionFromWechatLogin(result: WechatLoginResult): ShopSessionPayload | null { export function sessionFromWechatLogin(result: WechatLoginResult): ShopSessionPayload | null {
if (!result.accessToken || !result.refreshToken) return null; if (!result.accessToken || !result.refreshToken) return null;
const store = result.store; const store = result.store;
@@ -45,6 +59,8 @@ export function saveShopWechatAuth(result: WechatLoginResult): boolean {
} }
export async function authorizeShopWechat(): Promise<WechatLoginResult | void> { export async function authorizeShopWechat(): Promise<WechatLoginResult | void> {
const config = await fetchClientConfig();
if (!isWxAuthorizeEnabled(config)) return;
if (!isWechatEnv()) { if (!isWechatEnv()) {
throw new Error('请在微信内打开以完成授权'); throw new Error('请在微信内打开以完成授权');
} }
@@ -53,5 +69,7 @@ export async function authorizeShopWechat(): Promise<WechatLoginResult | void> {
export async function handleShopWechatCallback(): Promise<WechatLoginResult | null> { export async function handleShopWechatCallback(): Promise<WechatLoginResult | null> {
if (!isWechatEnv()) return null; if (!isWechatEnv()) return null;
const config = await fetchClientConfig();
if (!isWxAuthorizeEnabled(config)) return null;
return weixinSdk.handleOAuthCallback(); return weixinSdk.handleOAuthCallback();
} }
+2 -2
View File
@@ -5,9 +5,9 @@ import { request } from '../lib/api';
import { parseRedeemTokenFromScan } from '../lib/redeem-scan'; import { parseRedeemTokenFromScan } from '../lib/redeem-scan';
import { import {
authorizeShopWechat, authorizeShopWechat,
checkNeedsWechatAuth,
fetchShopAccount, fetchShopAccount,
handleShopWechatCallback, handleShopWechatCallback,
needsWechatAuth,
saveShopWechatAuth, saveShopWechatAuth,
sessionFromWechatLogin, sessionFromWechatLogin,
} from '../lib/wechat-auth'; } from '../lib/wechat-auth';
@@ -105,7 +105,7 @@ export default function HomePage() {
} }
try { try {
const profile = await fetchShopAccount(); const profile = await fetchShopAccount();
if (needsWechatAuth(profile)) { if (await checkNeedsWechatAuth(profile)) {
setAuthModalOpen(true); setAuthModalOpen(true);
return; return;
} }
+15 -1
View File
@@ -1,8 +1,10 @@
import { useState } from 'react'; import { useEffect, useState } from 'react';
import { Link, useNavigate, useSearchParams } from 'react-router-dom'; import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import AppImage from '@dukang/shared-ui/AppImage'; import AppImage from '@dukang/shared-ui/AppImage';
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
import { useStoreSession } from '../contexts/StoreSessionContext'; import { useStoreSession } from '../contexts/StoreSessionContext';
import { getLastPhone, getStoreProfile, request, type ShopSessionPayload } from '../lib/api'; import { getLastPhone, getStoreProfile, request, type ShopSessionPayload } from '../lib/api';
import { fetchClientConfig } from '../lib/wechat-auth';
function maskPhone(phone: string) { function maskPhone(phone: string) {
if (phone.length < 7) return phone; if (phone.length < 7) return phone;
@@ -21,6 +23,13 @@ export default function LoginPage() {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [msg, setMsg] = useState(''); const [msg, setMsg] = useState('');
const [codeCooldown, setCodeCooldown] = useState(0); const [codeCooldown, setCodeCooldown] = useState(0);
const [wxAuthorize, setWxAuthorize] = useState(false);
useEffect(() => {
fetchClientConfig()
.then((config) => setWxAuthorize(isWxAuthorizeEnabled(config)))
.catch(() => setWxAuthorize(false));
}, []);
const quickStoreName = savedProfile?.storeName ?? '门店管理中心'; const quickStoreName = savedProfile?.storeName ?? '门店管理中心';
const quickPhone = savedProfile?.phone || phone; const quickPhone = savedProfile?.phone || phone;
@@ -83,6 +92,7 @@ export default function LoginPage() {
function wechatLogin() { function wechatLogin() {
if (!ensureAgreed()) return; if (!ensureAgreed()) return;
if (!wxAuthorize) return;
setMsg('preV1:微信一键授权暂未开放,请使用手机验证码登录'); setMsg('preV1:微信一键授权暂未开放,请使用手机验证码登录');
} }
@@ -206,6 +216,8 @@ export default function LoginPage() {
{!loading && <span className="material-symbols-outlined">arrow_forward</span>} {!loading && <span className="material-symbols-outlined">arrow_forward</span>}
</button> </button>
{wxAuthorize && (
<>
<div className="shop-login-divider"> <div className="shop-login-divider">
<span className="shop-login-divider-line" /> <span className="shop-login-divider-line" />
<span className="shop-login-divider-text"></span> <span className="shop-login-divider-text"></span>
@@ -216,6 +228,8 @@ export default function LoginPage() {
<span className="material-symbols-outlined">chat</span> <span className="material-symbols-outlined">chat</span>
<span></span> <span></span>
</button> </button>
</>
)}
</div> </div>
<label className="shop-login-agreement"> <label className="shop-login-agreement">
+4
View File
@@ -1,4 +1,5 @@
import type { ClientRuntimeConfig, WechatLoginResult } from '@dukang/shared-types'; import type { ClientRuntimeConfig, WechatLoginResult } from '@dukang/shared-types';
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
import { isWechatEnv, weixinSdk } from './weixin'; import { isWechatEnv, weixinSdk } from './weixin';
import { request, saveSession, type UserProfile } from './api'; import { request, saveSession, type UserProfile } from './api';
@@ -21,6 +22,7 @@ export function needsWechatAuthForPay(
config: ClientRuntimeConfig, config: ClientRuntimeConfig,
profile: UserProfile | null, profile: UserProfile | null,
): boolean { ): boolean {
if (!isWxAuthorizeEnabled(config)) return false;
return !config.mockPay && config.wechatPayEnabled && isWechatEnv() && !profile?.hasWechat; return !config.mockPay && config.wechatPayEnabled && isWechatEnv() && !profile?.hasWechat;
} }
@@ -37,6 +39,8 @@ export function saveWechatLoginResult(result: WechatLoginResult): boolean {
} }
export async function authorizeWechatForPay(): Promise<WechatLoginResult | void> { export async function authorizeWechatForPay(): Promise<WechatLoginResult | void> {
const config = await fetchClientConfig();
if (!isWxAuthorizeEnabled(config)) return;
if (!isWechatEnv()) { if (!isWechatEnv()) {
throw new Error('请在微信内打开以完成授权'); throw new Error('请在微信内打开以完成授权');
} }
+12
View File
@@ -1,4 +1,5 @@
import type { WechatLoginResult } from '@dukang/shared-types'; import type { WechatLoginResult } from '@dukang/shared-types';
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
import { isWechatEnv, weixinSdk } from './weixin'; import { isWechatEnv, weixinSdk } from './weixin';
import { import {
authorizeWechatForPay, authorizeWechatForPay,
@@ -38,9 +39,20 @@ export async function ensureWechatAuthForPay(): Promise<WechatAuthEnsureResult>
export async function handleWechatAuthCallback(): Promise<WechatLoginResult | null> { export async function handleWechatAuthCallback(): Promise<WechatLoginResult | null> {
if (!isWechatEnv()) return null; if (!isWechatEnv()) return null;
const config = await fetchClientConfig();
if (!isWxAuthorizeEnabled(config)) return null;
return weixinSdk.handleOAuthCallback(); return weixinSdk.handleOAuthCallback();
} }
export async function loginWithWechatSdk(): Promise<WechatLoginResult | void> {
const config = await fetchClientConfig();
if (!isWxAuthorizeEnabled(config)) return;
if (!isWechatEnv()) {
throw new Error('请在微信内打开以使用微信一键授权');
}
return weixinSdk.login();
}
export function applyWechatLoginResult(result: WechatLoginResult): boolean { export function applyWechatLoginResult(result: WechatLoginResult): boolean {
return saveWechatLoginResult(result); return saveWechatLoginResult(result);
} }
+16 -11
View File
@@ -4,9 +4,12 @@ import AppImage from '@dukang/shared-ui/AppImage';
import type { WechatLoginResult } from '@dukang/shared-types'; import type { WechatLoginResult } from '@dukang/shared-types';
import { SmsScene } from '@dukang/shared-types'; import { SmsScene } from '@dukang/shared-types';
import { request, type SessionPayload } from '../lib/api'; import { request, type SessionPayload } from '../lib/api';
import { fetchClientConfig } from '../lib/pay-wechat';
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
import { normalizePhoneInput, validateMobilePhone } from '../lib/phone'; import { normalizePhoneInput, validateMobilePhone } from '../lib/phone';
import { useSmsCode } from '../lib/use-sms-code'; import { useSmsCode } from '../lib/use-sms-code';
import { isWechatEnv, weixinSdk } from '../lib/weixin'; import { isWechatEnv } from '../lib/weixin';
import { loginWithWechatSdk, handleWechatAuthCallback as handleWechatOAuthCallback } from '../lib/wechat-auth';
import { useUserSession } from '../contexts/UserSessionContext'; import { useUserSession } from '../contexts/UserSessionContext';
import { touchPromoIfNeeded } from '../lib/promo'; import { touchPromoIfNeeded } from '../lib/promo';
@@ -27,19 +30,25 @@ export default function LoginPage() {
const [msg, setMsg] = useState(''); const [msg, setMsg] = useState('');
const [wxSessionKey, setWxSessionKey] = useState<string | null>(null); const [wxSessionKey, setWxSessionKey] = useState<string | null>(null);
const [bindMode, setBindMode] = useState(false); const [bindMode, setBindMode] = useState(false);
const [wxAuthorize, setWxAuthorize] = useState(false);
const { sendCode, sending, codeCooldown, sentHint, error: smsError, setError: setSmsError, clearMessages } = const { sendCode, sending, codeCooldown, sentHint, error: smsError, setError: setSmsError, clearMessages } =
useSmsCode(); useSmsCode();
useEffect(() => { useEffect(() => {
if (!isWechatEnv()) return; fetchClientConfig()
weixinSdk .then((config) => setWxAuthorize(isWxAuthorizeEnabled(config)))
.handleOAuthCallback() .catch(() => setWxAuthorize(false));
}, []);
useEffect(() => {
if (!isWechatEnv() || !wxAuthorize) return;
handleWechatOAuthCallback()
.then((result) => { .then((result) => {
if (!result) return; if (!result) return;
handleWechatLoginResult(result); handleWechatLoginResult(result);
}) })
.catch((e) => setMsg(e instanceof Error ? e.message : '微信登录失败')); .catch((e) => setMsg(e instanceof Error ? e.message : '微信登录失败'));
}, []); }, [wxAuthorize]);
function handleWechatLoginResult(result: WechatLoginResult) { function handleWechatLoginResult(result: WechatLoginResult) {
if (result.needBindPhone && result.wxSessionKey) { if (result.needBindPhone && result.wxSessionKey) {
@@ -116,11 +125,7 @@ export default function LoginPage() {
if (!ensureAgreed()) return; if (!ensureAgreed()) return;
setMsg(''); setMsg('');
try { try {
if (!isWechatEnv()) { const result = await loginWithWechatSdk();
setMsg('请在微信内打开以使用微信一键授权');
return;
}
const result = await weixinSdk.login();
if (result) handleWechatLoginResult(result); if (result) handleWechatLoginResult(result);
} catch (e) { } catch (e) {
setMsg(e instanceof Error ? e.message : '微信登录失败'); setMsg(e instanceof Error ? e.message : '微信登录失败');
@@ -199,7 +204,7 @@ export default function LoginPage() {
</button> </button>
</div> </div>
{!bindMode && ( {!bindMode && wxAuthorize && (
<> <>
<div className="login-divider"> <div className="login-divider">
<span className="login-divider-line" /> <span className="login-divider-line" />
+3
View File
@@ -6,6 +6,8 @@ export interface AppConfig {
autoApproveStore: boolean; autoApproveStore: boolean;
/** preV1 Mock 微信授权登录:点击授权按钮走 Mock 流程直接登录,不接真实微信 */ /** preV1 Mock 微信授权登录:点击授权按钮走 Mock 流程直接登录,不接真实微信 */
mockWechat: boolean; mockWechat: boolean;
/** 登录后是否走微信 SDK OAuth 授权(WX_AUTHORIZE=false 时三端跳过授权流程) */
wxAuthorize: boolean;
wechatAuthEnabled: boolean; wechatAuthEnabled: boolean;
wechatPayEnabled: boolean; wechatPayEnabled: boolean;
wxAppId: string; wxAppId: string;
@@ -35,6 +37,7 @@ export function loadAppConfig(env?: Record<string, string | undefined>): AppConf
mockDeliveryAuto: e.MOCK_DELIVERY_AUTO !== 'false', mockDeliveryAuto: e.MOCK_DELIVERY_AUTO !== 'false',
autoApproveStore: e.AUTO_APPROVE_STORE !== 'false', autoApproveStore: e.AUTO_APPROVE_STORE !== 'false',
mockWechat: e.MOCK_WECHAT === 'true', mockWechat: e.MOCK_WECHAT === 'true',
wxAuthorize: e.WX_AUTHORIZE !== 'false',
wechatAuthEnabled: e.WECHAT_AUTH_ENABLED === 'true', wechatAuthEnabled: e.WECHAT_AUTH_ENABLED === 'true',
wechatPayEnabled: e.WECHAT_PAY_ENABLED === 'true' || e.MOCK_PAY === 'false', wechatPayEnabled: e.WECHAT_PAY_ENABLED === 'true' || e.MOCK_PAY === 'false',
wxAppId: e.WX_APP_ID ?? '', wxAppId: e.WX_APP_ID ?? '',
+8
View File
@@ -28,8 +28,16 @@ export type ClientRuntimeConfig = {
mockPay: boolean; mockPay: boolean;
wechatPayEnabled: boolean; wechatPayEnabled: boolean;
mockSms: boolean; mockSms: boolean;
mockWechat?: boolean;
/** false 时三端跳过微信 SDK OAuth 授权 */
wxAuthorize?: boolean;
}; };
/** 是否启用登录后微信 SDK 授权(默认 true,仅 WX_AUTHORIZE=false 时关闭) */
export function isWxAuthorizeEnabled(config?: Pick<ClientRuntimeConfig, 'wxAuthorize'> | null): boolean {
return config?.wxAuthorize !== false;
}
export interface WechatLoginResult { export interface WechatLoginResult {
accessToken?: string; accessToken?: string;
refreshToken?: string; refreshToken?: string;
+2
View File
@@ -22,6 +22,8 @@ AUTO_APPROVE_STORE=true
# 微信 OAuth Mock(preV1 本地联调):须在微信内置浏览器内打开 H5,走 OAuth 回跳带 mock code # 微信 OAuth Mock(preV1 本地联调):须在微信内置浏览器内打开 H5,走 OAuth 回跳带 mock code
# 非微信浏览器不会发起 /login/wechat 请求。生产请 MOCK_WECHAT=false 且 WECHAT_AUTH_ENABLED=true。 # 非微信浏览器不会发起 /login/wechat 请求。生产请 MOCK_WECHAT=false 且 WECHAT_AUTH_ENABLED=true。
MOCK_WECHAT=true MOCK_WECHAT=true
# 登录后是否走微信 SDK OAuth 授权(本地 false 可仅用短信登录/核销,不影响支付 Mock)
WX_AUTHORIZE=false
# C 端 H5 落地页(推广码二维码链接前缀) # C 端 H5 落地页(推广码二维码链接前缀)
USER_H5_URL=http://localhost:5173 USER_H5_URL=http://localhost:5173
@@ -24,6 +24,8 @@ TRUST_PROXY=true
WECHAT_AUTH_ENABLED=true WECHAT_AUTH_ENABLED=true
WECHAT_PAY_ENABLED=true WECHAT_PAY_ENABLED=true
# 登录后走微信 SDK OAuth 授权(生产/预发建议 true)
WX_AUTHORIZE=true
WX_APP_ID= WX_APP_ID=
WX_APP_SECRET= WX_APP_SECRET=
WX_MCH_ID= WX_MCH_ID=
@@ -11,6 +11,7 @@ export class ClientConfigController {
wechatPayEnabled: cfg.wechatPayEnabled, wechatPayEnabled: cfg.wechatPayEnabled,
mockSms: cfg.mockSms, mockSms: cfg.mockSms,
mockWechat: cfg.mockWechat, mockWechat: cfg.mockWechat,
wxAuthorize: cfg.wxAuthorize,
}; };
} }
} }