门店端微信号自动登录

This commit is contained in:
2026-07-09 22:07:35 +08:00
parent e20ee1ec7f
commit 12ba19ede4
7 changed files with 210 additions and 59 deletions
+48 -5
View File
@@ -28,8 +28,18 @@ const ACCESS_TOKEN = 'accessToken';
const REFRESH_TOKEN = 'refreshToken';
const LAST_PHONE = 'shopLastPhone';
const STORE_PROFILE = 'shopStoreProfile';
const SESSION_EXPIRES_AT = 'shopSessionExpiresAt';
export const SHOP_WX_BOUND = 'shopWxBound';
const AUTH_RECOVERY_EXEMPT_PATHS = ['/shop/auth/token/refresh', '/shop/auth/sms/send', '/shop/auth/login/sms'];
/** 微信验证通过后的免登录时长 */
export const SHOP_SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1000;
const AUTH_RECOVERY_EXEMPT_PATHS = [
'/shop/auth/token/refresh',
'/shop/auth/sms/send',
'/shop/auth/login/sms',
'/shop/auth/login/wechat',
];
export function getLastPhone() {
return localStorage.getItem(LAST_PHONE) ?? '';
@@ -44,6 +54,21 @@ export function getStoreProfile(): StoreSessionStore | null {
}
}
export function hasShopWxSession() {
return localStorage.getItem(SHOP_WX_BOUND) === '1';
}
export function isShopSessionExpired() {
const raw = localStorage.getItem(SESSION_EXPIRES_AT);
if (!raw) return false;
return Date.now() > Number(raw);
}
export function touchShopSession() {
if (!hasShopWxSession()) return;
localStorage.setItem(SESSION_EXPIRES_AT, String(Date.now() + SHOP_SESSION_TTL_MS));
}
export function saveAuth(data: ShopSessionPayload) {
localStorage.setItem(ACCESS_TOKEN, data.accessToken);
if (data.refreshToken) localStorage.setItem(REFRESH_TOKEN, data.refreshToken);
@@ -53,10 +78,22 @@ export function saveAuth(data: ShopSessionPayload) {
}
}
export function clearAuth() {
/** 微信登录/绑定成功后写入 7 天免登录 session */
export function saveWechatSession(data: ShopSessionPayload) {
saveAuth(data);
localStorage.setItem(SHOP_WX_BOUND, '1');
localStorage.setItem(SESSION_EXPIRES_AT, String(Date.now() + SHOP_SESSION_TTL_MS));
}
export function clearAuth(options?: { keepProfile?: boolean }) {
localStorage.removeItem(ACCESS_TOKEN);
localStorage.removeItem(REFRESH_TOKEN);
localStorage.removeItem(STORE_PROFILE);
localStorage.removeItem(SESSION_EXPIRES_AT);
localStorage.removeItem(SHOP_WX_BOUND);
if (!options?.keepProfile) {
localStorage.removeItem(STORE_PROFILE);
localStorage.removeItem(LAST_PHONE);
}
}
export function isLoggedIn() {
@@ -109,6 +146,7 @@ async function refreshSession(): Promise<ShopSessionPayload | null> {
null,
);
saveAuth(data);
touchShopSession();
return data;
} catch {
return null;
@@ -147,6 +185,10 @@ export async function ensureSession(): Promise<{ authenticated: boolean; store:
if (!isLoggedIn()) {
return { authenticated: false, store: null };
}
if (isShopSessionExpired()) {
clearAuth({ keepProfile: true });
return { authenticated: false, store: getStoreProfile() };
}
try {
const me = await rawRequest<StoreProfile>('/shop/auth/me');
const store = profileFromMe(me);
@@ -155,6 +197,7 @@ export async function ensureSession(): Promise<{ authenticated: boolean; store:
refreshToken: localStorage.getItem(REFRESH_TOKEN) ?? '',
store,
});
touchShopSession();
return { authenticated: true, store };
} catch (e) {
const err = e as Error & { status?: number };
@@ -163,8 +206,8 @@ export async function ensureSession(): Promise<{ authenticated: boolean; store:
if (refreshed?.store) {
return { authenticated: true, store: refreshed.store };
}
clearAuth();
return { authenticated: false, store: null };
clearAuth({ keepProfile: true });
return { authenticated: false, store: getStoreProfile() };
}
const cached = getStoreProfile();
if (cached) return { authenticated: true, store: cached };