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

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 {
authorizePartnerWechat,
fetchClientConfig,
fetchPartnerProfile,
needsWechatAuth,
type PartnerProfile,
} from '../lib/wechat-auth';
import { isWechatEnv, weixinSdk } from '../lib/weixin';
import type { ClientRuntimeConfig } from '@dukang/shared-types';
type OssUploadFieldProps = {
value?: string;
@@ -42,17 +44,19 @@ export default function OssUploadField({
const [authorizing, setAuthorizing] = useState(false);
const [error, setError] = useState('');
const [profile, setProfile] = useState<PartnerProfile | null>(null);
const [clientConfig, setClientConfig] = useState<ClientRuntimeConfig | null>(null);
const resolvedAccept =
accept ?? (mediaType === 'VIDEO' ? 'video/*' : mediaType === 'FILE' ? 'image/*,.pdf' : 'image/*');
const useWechatPicker = isWechatEnv() && mediaType !== 'VIDEO';
const needsAuth = useWechatPicker && needsWechatAuth(profile) && wechatReady !== true;
const needsAuth = useWechatPicker && needsWechatAuth(profile, clientConfig) && wechatReady !== true;
useEffect(() => {
if (!useWechatPicker) return;
void fetchPartnerProfile()
.then((me) => {
void Promise.all([fetchPartnerProfile(), fetchClientConfig()])
.then(([me, config]) => {
setProfile(me);
setClientConfig(config);
onWechatReadyChange?.(!!me.hasWechat);
})
.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 { isWechatEnv, weixinSdk } from './weixin';
import { request, saveAuth } from './api';
@@ -11,15 +12,28 @@ export type PartnerProfile = {
hasWechat?: boolean;
};
export async function fetchClientConfig(): Promise<ClientRuntimeConfig> {
return request<ClientRuntimeConfig>('PARTNER_H5', '/common/client-config');
}
export async function fetchPartnerProfile(): Promise<PartnerProfile> {
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;
}
export async function checkNeedsWechatAuth(profile: PartnerProfile | null): Promise<boolean> {
const config = await fetchClientConfig();
return needsWechatAuth(profile, config);
}
/** 处理微信登录/绑定结果,返回是否已拿到 token 可进入首页 */
export function handlePartnerWechatLoginResult(result: WechatLoginResult): boolean {
if (!result.accessToken) return false;
@@ -29,6 +43,8 @@ export function handlePartnerWechatLoginResult(result: WechatLoginResult): boole
export async function handlePartnerWechatCallback(): Promise<WechatLoginResult | null> {
if (!isWechatEnv()) return null;
const config = await fetchClientConfig();
if (!isWxAuthorizeEnabled(config)) return null;
return weixinSdk.handleOAuthCallback();
}
@@ -37,6 +53,8 @@ export async function handlePartnerWechatCallback(): Promise<WechatLoginResult |
* 返回 true = 已登录;void = 已跳转授权页等待回调。
*/
export async function loginPartnerWithWechat(): Promise<boolean | void> {
const config = await fetchClientConfig();
if (!isWxAuthorizeEnabled(config)) return false;
if (!isWechatEnv()) {
throw new Error(WECHAT_INAPP_REQUIRED_MSG);
}
@@ -48,11 +66,15 @@ export async function loginPartnerWithWechat(): Promise<boolean | void> {
* 短信登录成功后于微信内自动发起 OAuth,将 openId 绑定到当前合伙人账号(便于同一微信后续免登)。
*/
export async function bindPartnerWechatAfterSmsLogin(): Promise<void> {
const config = await fetchClientConfig();
if (!isWxAuthorizeEnabled(config)) return;
if (!isWechatEnv()) return;
await weixinSdk.login();
}
export async function authorizePartnerWechat(): Promise<WechatLoginResult | void> {
const config = await fetchClientConfig();
if (!isWxAuthorizeEnabled(config)) return;
if (!isWechatEnv()) {
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 {
bindPartnerWechatAfterSmsLogin,
fetchClientConfig,
handlePartnerWechatCallback,
handlePartnerWechatLoginResult,
loginPartnerWithWechat,
} from '../lib/wechat-auth';
import { isWechatEnv } from '../lib/weixin';
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
import { WECHAT_INAPP_REQUIRED_MSG } from '@dukang/weixin-sdk';
const REMEMBER_PHONE_KEY = 'partner_remember_phone';
@@ -47,16 +49,23 @@ export default function LoginPage() {
const [wxLoading, setWxLoading] = useState(false);
const [msg, setMsg] = useState('');
const [codeCooldown, setCodeCooldown] = useState(0);
const [wxAuthorize, setWxAuthorize] = useState(false);
useEffect(() => {
if (!isWechatEnv()) return;
fetchClientConfig()
.then((config) => setWxAuthorize(isWxAuthorizeEnabled(config)))
.catch(() => setWxAuthorize(false));
}, []);
useEffect(() => {
if (!isWechatEnv() || !wxAuthorize) return;
void handlePartnerWechatCallback()
.then((result) => {
if (!result) return;
if (handlePartnerWechatLoginResult(result)) navigate('/');
})
.catch((e) => setMsg(formatWechatError(e)));
}, [navigate]);
}, [navigate, wxAuthorize]);
function formatWechatError(e: unknown): string {
const text = e instanceof Error ? e.message : '微信登录失败';
@@ -129,7 +138,7 @@ export default function LoginPage() {
});
saveAuth(data);
persistRememberAccount(phone);
if (isWechatEnv()) {
if (isWechatEnv() && wxAuthorize) {
setMsg('登录成功,正在关联微信…');
await bindPartnerWechatAfterSmsLogin();
return;
@@ -264,6 +273,8 @@ export default function LoginPage() {
{!loading && <span className="material-symbols-outlined">arrow_forward</span>}
</button>
{wxAuthorize && (
<>
<div className="partner-auth-divider"><span></span></div>
<button type="button" className="partner-btn-wechat" onClick={wechatLogin} disabled={wxLoading}>
<svg viewBox="0 0 24 24" fill="#07C160" aria-hidden>
@@ -271,6 +282,8 @@ export default function LoginPage() {
</svg>
<span>{wxLoading ? '登录中...' : '微信一键授权'}</span>
</button>
</>
)}
<label className="partner-checkbox-row">
<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 { request, saveAuth, type ShopSessionPayload } from './api';
@@ -11,14 +12,27 @@ export type ShopAccountProfile = {
store?: { name: string };
};
export async function fetchClientConfig(): Promise<ClientRuntimeConfig> {
return request<ClientRuntimeConfig>('SHOP_H5', '/common/client-config');
}
export async function fetchShopAccount(): Promise<ShopAccountProfile> {
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;
}
export async function checkNeedsWechatAuth(profile: ShopAccountProfile | null): Promise<boolean> {
const config = await fetchClientConfig();
return needsWechatAuth(profile, config);
}
export function sessionFromWechatLogin(result: WechatLoginResult): ShopSessionPayload | null {
if (!result.accessToken || !result.refreshToken) return null;
const store = result.store;
@@ -45,6 +59,8 @@ export function saveShopWechatAuth(result: WechatLoginResult): boolean {
}
export async function authorizeShopWechat(): Promise<WechatLoginResult | void> {
const config = await fetchClientConfig();
if (!isWxAuthorizeEnabled(config)) return;
if (!isWechatEnv()) {
throw new Error('请在微信内打开以完成授权');
}
@@ -53,5 +69,7 @@ export async function authorizeShopWechat(): Promise<WechatLoginResult | void> {
export async function handleShopWechatCallback(): Promise<WechatLoginResult | null> {
if (!isWechatEnv()) return null;
const config = await fetchClientConfig();
if (!isWxAuthorizeEnabled(config)) return null;
return weixinSdk.handleOAuthCallback();
}
+2 -2
View File
@@ -5,9 +5,9 @@ import { request } from '../lib/api';
import { parseRedeemTokenFromScan } from '../lib/redeem-scan';
import {
authorizeShopWechat,
checkNeedsWechatAuth,
fetchShopAccount,
handleShopWechatCallback,
needsWechatAuth,
saveShopWechatAuth,
sessionFromWechatLogin,
} from '../lib/wechat-auth';
@@ -105,7 +105,7 @@ export default function HomePage() {
}
try {
const profile = await fetchShopAccount();
if (needsWechatAuth(profile)) {
if (await checkNeedsWechatAuth(profile)) {
setAuthModalOpen(true);
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 AppImage from '@dukang/shared-ui/AppImage';
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
import { useStoreSession } from '../contexts/StoreSessionContext';
import { getLastPhone, getStoreProfile, request, type ShopSessionPayload } from '../lib/api';
import { fetchClientConfig } from '../lib/wechat-auth';
function maskPhone(phone: string) {
if (phone.length < 7) return phone;
@@ -21,6 +23,13 @@ export default function LoginPage() {
const [loading, setLoading] = useState(false);
const [msg, setMsg] = useState('');
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 quickPhone = savedProfile?.phone || phone;
@@ -83,6 +92,7 @@ export default function LoginPage() {
function wechatLogin() {
if (!ensureAgreed()) return;
if (!wxAuthorize) return;
setMsg('preV1:微信一键授权暂未开放,请使用手机验证码登录');
}
@@ -206,6 +216,8 @@ export default function LoginPage() {
{!loading && <span className="material-symbols-outlined">arrow_forward</span>}
</button>
{wxAuthorize && (
<>
<div className="shop-login-divider">
<span className="shop-login-divider-line" />
<span className="shop-login-divider-text"></span>
@@ -216,6 +228,8 @@ export default function LoginPage() {
<span className="material-symbols-outlined">chat</span>
<span></span>
</button>
</>
)}
</div>
<label className="shop-login-agreement">
+4
View File
@@ -1,4 +1,5 @@
import type { ClientRuntimeConfig, WechatLoginResult } from '@dukang/shared-types';
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
import { isWechatEnv, weixinSdk } from './weixin';
import { request, saveSession, type UserProfile } from './api';
@@ -21,6 +22,7 @@ export function needsWechatAuthForPay(
config: ClientRuntimeConfig,
profile: UserProfile | null,
): boolean {
if (!isWxAuthorizeEnabled(config)) return false;
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> {
const config = await fetchClientConfig();
if (!isWxAuthorizeEnabled(config)) return;
if (!isWechatEnv()) {
throw new Error('请在微信内打开以完成授权');
}
+12
View File
@@ -1,4 +1,5 @@
import type { WechatLoginResult } from '@dukang/shared-types';
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
import { isWechatEnv, weixinSdk } from './weixin';
import {
authorizeWechatForPay,
@@ -38,9 +39,20 @@ export async function ensureWechatAuthForPay(): Promise<WechatAuthEnsureResult>
export async function handleWechatAuthCallback(): Promise<WechatLoginResult | null> {
if (!isWechatEnv()) return null;
const config = await fetchClientConfig();
if (!isWxAuthorizeEnabled(config)) return null;
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 {
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 { SmsScene } from '@dukang/shared-types';
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 { 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 { touchPromoIfNeeded } from '../lib/promo';
@@ -27,19 +30,25 @@ export default function LoginPage() {
const [msg, setMsg] = useState('');
const [wxSessionKey, setWxSessionKey] = useState<string | null>(null);
const [bindMode, setBindMode] = useState(false);
const [wxAuthorize, setWxAuthorize] = useState(false);
const { sendCode, sending, codeCooldown, sentHint, error: smsError, setError: setSmsError, clearMessages } =
useSmsCode();
useEffect(() => {
if (!isWechatEnv()) return;
weixinSdk
.handleOAuthCallback()
fetchClientConfig()
.then((config) => setWxAuthorize(isWxAuthorizeEnabled(config)))
.catch(() => setWxAuthorize(false));
}, []);
useEffect(() => {
if (!isWechatEnv() || !wxAuthorize) return;
handleWechatOAuthCallback()
.then((result) => {
if (!result) return;
handleWechatLoginResult(result);
})
.catch((e) => setMsg(e instanceof Error ? e.message : '微信登录失败'));
}, []);
}, [wxAuthorize]);
function handleWechatLoginResult(result: WechatLoginResult) {
if (result.needBindPhone && result.wxSessionKey) {
@@ -116,11 +125,7 @@ export default function LoginPage() {
if (!ensureAgreed()) return;
setMsg('');
try {
if (!isWechatEnv()) {
setMsg('请在微信内打开以使用微信一键授权');
return;
}
const result = await weixinSdk.login();
const result = await loginWithWechatSdk();
if (result) handleWechatLoginResult(result);
} catch (e) {
setMsg(e instanceof Error ? e.message : '微信登录失败');
@@ -199,7 +204,7 @@ export default function LoginPage() {
</button>
</div>
{!bindMode && (
{!bindMode && wxAuthorize && (
<>
<div className="login-divider">
<span className="login-divider-line" />