本地环境不验证手机号和微信登录授权
This commit is contained in:
@@ -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(() => {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)} />
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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('请在微信内打开以完成授权');
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user