From 83ed90ef67fbd7c5d948aecb50a67c04e564f80c Mon Sep 17 00:00:00 2001 From: jacy <18049821889@163.com> Date: Tue, 21 Jul 2026 10:41:17 +0800 Subject: [PATCH] feat(h5-auth): remember SMS sessions and simplify media access Hide WeChat login for shop and partner users, keep verified sessions for seven days, and allow JSSDK camera or album access without an OpenID binding. Co-authored-by: Cursor --- apps/h5-partner/src/components/AuthGate.tsx | 6 +- .../src/components/OssUploadField.tsx | 127 ++++------- apps/h5-partner/src/lib/api.ts | 13 +- apps/h5-partner/src/pages/LoginPage.tsx | 182 +--------------- apps/h5-shop/src/components/AuthGate.tsx | 5 - .../src/components/WeakNetFallbackPanel.tsx | 23 +- apps/h5-shop/src/lib/api.ts | 13 +- apps/h5-shop/src/pages/LoginPage.tsx | 198 ++---------------- 8 files changed, 97 insertions(+), 470 deletions(-) diff --git a/apps/h5-partner/src/components/AuthGate.tsx b/apps/h5-partner/src/components/AuthGate.tsx index 56229d5..eab698c 100644 --- a/apps/h5-partner/src/components/AuthGate.tsx +++ b/apps/h5-partner/src/components/AuthGate.tsx @@ -1,5 +1,5 @@ import { Navigate, useLocation } from 'react-router-dom'; -import { getPartnerProfile, hasPartnerWxSession } from '../lib/api'; +import { getPartnerProfile } from '../lib/api'; import { usePartnerSession } from '../contexts/PartnerSessionContext'; import { partnerHomePath } from '../lib/partnerAccess'; @@ -18,10 +18,6 @@ export default function AuthGate({ children }: { children: React.ReactNode }) { } if (!authenticated && !PUBLIC_PATHS.has(location.pathname)) { - const profile = getPartnerProfile(); - if (profile && hasPartnerWxSession() && profile.hasWechat) { - return ; - } return ; } diff --git a/apps/h5-partner/src/components/OssUploadField.tsx b/apps/h5-partner/src/components/OssUploadField.tsx index ac3e11a..aeb224a 100644 --- a/apps/h5-partner/src/components/OssUploadField.tsx +++ b/apps/h5-partner/src/components/OssUploadField.tsx @@ -1,17 +1,9 @@ import { useEffect, useRef, useState } from 'react'; import { uploadFileToOss, type OssMediaType } from '../lib/upload'; import { enqueueUpload } from '../lib/upload-lock'; -import { - authorizePartnerWechat, - fetchClientConfig, - fetchPartnerProfile, - needsWechatAuth, - type PartnerProfile, -} from '../lib/wechat-auth'; import { formatChooseImageFailMessage } from '@dukang/weixin-sdk'; import { isWechatEnv, weixinSdk } from '../lib/weixin'; import { toastError } from '../lib/toast'; -import type { ClientRuntimeConfig } from '@dukang/shared-types'; type OssUploadFieldProps = { value?: string; @@ -22,7 +14,7 @@ type OssUploadFieldProps = { wide?: boolean; compact?: boolean; label?: string; - /** 父级已确认微信授权时可跳过检查 */ + /** 兼容现有调用:选图不再依赖 openId 绑定 */ wechatReady?: boolean; onWechatReadyChange?: (ready: boolean) => void; }; @@ -52,15 +44,11 @@ export default function OssUploadField({ wide, compact, label, - wechatReady, - onWechatReadyChange, }: OssUploadFieldProps) { const inputRef = useRef(null); const [uploading, setUploading] = useState(false); - const [authorizing, setAuthorizing] = useState(false); const [error, setError] = useState(''); - const [profile, setProfile] = useState(null); - const [clientConfig, setClientConfig] = useState(null); + const [showAlbumFallback, setShowAlbumFallback] = useState(false); function showUploadError(text: string) { setError(text); @@ -72,32 +60,14 @@ export default function OssUploadField({ const inWechat = isWechatEnv(); const useWechatPicker = inWechat && (mediaType === 'IMAGE' || (mediaType === 'FILE' && acceptsImages(resolvedAccept))); - const needsAuth = useWechatPicker && needsWechatAuth(profile, clientConfig) && wechatReady !== true; - const onWechatReadyChangeRef = useRef(onWechatReadyChange); - onWechatReadyChangeRef.current = onWechatReadyChange; useEffect(() => { if (!useWechatPicker) return; - void fetchClientConfig() - .then(setClientConfig) - .catch(() => { - /* 未登录等场景由上传接口报错 */ - }); - if (wechatReady === true) return; - void fetchPartnerProfile() - .then(setProfile) - .catch(() => { - /* 未登录等场景由上传接口报错 */ - }); - }, [useWechatPicker, wechatReady]); - - useEffect(() => { - if (!useWechatPicker || needsAuth) return; weixinSdk.reset(); void weixinSdk.init().catch(() => { /* 点击上传时会再次初始化 */ }); - }, [useWechatPicker, needsAuth]); + }, [useWechatPicker]); async function persistUpload(file: File) { if (!file.size) { @@ -125,24 +95,6 @@ export default function OssUploadField({ } } - async function startWechatAuth() { - setAuthorizing(true); - setError(''); - try { - const result = await authorizePartnerWechat(); - if (result) { - const me = await fetchPartnerProfile(); - setProfile(me); - if (me.hasWechat) onWechatReadyChangeRef.current?.(true); - } - } catch (e) { - const text = e instanceof Error ? e.message : '微信授权失败'; - showUploadError(text); - } finally { - setAuthorizing(false); - } - } - async function pickWechatImage() { setUploading(true); setError(''); @@ -161,29 +113,26 @@ export default function OssUploadField({ } catch (e) { const msg = e instanceof Error ? e.message : '无法打开相册'; if (/cancel/i.test(msg)) return; - showUploadError(formatWechatUploadError(e)); + throw e; } finally { setUploading(false); } } async function pickFile() { - if (uploading || authorizing) return; + if (uploading) return; setError(''); - if (useWechatPicker && needsAuth) { - const text = '请先完成微信授权后再上传照片'; - showUploadError(text); - return; - } - if (useWechatPicker) { try { await pickWechatImage(); } catch (e) { const msg = e instanceof Error ? e.message : '无法打开相册'; if (/cancel/i.test(msg)) return; - showUploadError(formatWechatUploadError(e)); + const formatted = formatWechatUploadError(e); + setError(`${formatted},可改从系统相册选择`); + setShowAlbumFallback(true); + inputRef.current?.click(); } return; } @@ -193,44 +142,31 @@ export default function OssUploadField({ const isImage = mediaType === 'IMAGE' && value; const isFile = mediaType === 'FILE' && value; - const busy = uploading || authorizing; + const busy = uploading; const pickerLabel = label ?? (useWechatPicker ? '拍照 / 从相册选择' : '点击上传'); const triggerProps = { type: 'button' as const, - disabled: busy || needsAuth, + disabled: busy, onClick: () => void pickFile(), }; return (
- {needsAuth && ( -
-

上传照片需先完成微信授权绑定

- -
- )} - {!useWechatPicker && ( - { - const file = e.target.files?.[0]; - if (file) void uploadSelectedFile(file); - }} - /> - )} + { + const file = e.target.files?.[0]; + if (file) { + setShowAlbumFallback(false); + void uploadSelectedFile(file); + } + }} + /> {isImage ? ( )} + {showAlbumFallback && useWechatPicker && ( + + )} {error &&

{error}

}
); diff --git a/apps/h5-partner/src/lib/api.ts b/apps/h5-partner/src/lib/api.ts index 2c7c8cb..59ce2d2 100644 --- a/apps/h5-partner/src/lib/api.ts +++ b/apps/h5-partner/src/lib/api.ts @@ -12,7 +12,7 @@ const PARTNER_PROFILE = 'partnerProfile'; const SESSION_EXPIRES_AT = 'partnerSessionExpiresAt'; export const PARTNER_WX_BOUND = 'partnerWxBound'; -/** 微信验证通过后的免登录时长 */ +/** 手机号或微信验证通过后的免登录时长 */ export const PARTNER_SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1000; const AUTH_RECOVERY_EXEMPT_PATHS = [ @@ -77,7 +77,7 @@ export function isPartnerSessionExpired() { } export function touchPartnerSession() { - if (!hasPartnerWxSession()) return; + if (!localStorage.getItem(REFRESH_TOKEN)) return; localStorage.setItem(SESSION_EXPIRES_AT, String(Date.now() + PARTNER_SESSION_TTL_MS)); } @@ -90,11 +90,16 @@ export function saveAuth(data: PartnerSessionPayload) { } } +/** 手机号验证成功后写入 7 天免验证码会话 */ +export function saveRememberedSession(data: PartnerSessionPayload) { + saveAuth(data); + localStorage.setItem(SESSION_EXPIRES_AT, String(Date.now() + PARTNER_SESSION_TTL_MS)); +} + /** 微信登录/绑定成功后写入 7 天免登录 session */ export function saveWechatSession(data: PartnerSessionPayload) { - saveAuth(data); + saveRememberedSession(data); localStorage.setItem(PARTNER_WX_BOUND, '1'); - localStorage.setItem(SESSION_EXPIRES_AT, String(Date.now() + PARTNER_SESSION_TTL_MS)); } export function clearAuth(options?: { keepProfile?: boolean }) { diff --git a/apps/h5-partner/src/pages/LoginPage.tsx b/apps/h5-partner/src/pages/LoginPage.tsx index 6b32241..b7c2a1a 100644 --- a/apps/h5-partner/src/pages/LoginPage.tsx +++ b/apps/h5-partner/src/pages/LoginPage.tsx @@ -1,25 +1,14 @@ -import { useEffect, useRef, useState, type RefObject } from 'react'; -import { Link, useNavigate, useSearchParams } from 'react-router-dom'; -import { isWxAuthorizeEnabled } from '@dukang/shared-types'; -import { WECHAT_INAPP_REQUIRED_MSG } from '@dukang/weixin-sdk'; +import { useRef, useState, type RefObject } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; import { getLastPhone, getPartnerProfile, - hasPartnerWxSession, request, - saveAuth, + saveRememberedSession, type PartnerSessionPayload, } from '../lib/api'; import { usePartnerSession } from '../contexts/PartnerSessionContext'; import { partnerHomePath } from '../lib/partnerAccess'; -import { - bindPartnerWechatAfterSmsLogin, - canPartnerUseWechatLogin, - fetchClientConfig, - loginPartnerWithWechat, - PARTNER_WECHAT_LOGIN_HINT, -} from '../lib/wechat-auth'; -import { isWechatEnv } from '../lib/weixin'; const REMEMBER_PHONE_KEY = 'partner_remember_phone'; const REMEMBER_FLAG_KEY = 'partner_remember_account'; @@ -31,7 +20,7 @@ function AgreementCheckbox({ }: { agreed: boolean; onChange: (next: boolean) => void; - inputRef?: RefObject; + inputRef?: RefObject; }) { return (