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