门店账号管理修改

This commit is contained in:
2026-07-07 00:01:16 +08:00
parent e85c4b9bcb
commit 7ca9fc8a35
25 changed files with 599 additions and 134 deletions
+162 -16
View File
@@ -1,27 +1,173 @@
export const apiBase = '/api/v1';
const CLIENT_APP = 'SHOP_H5';
export async function request<T>(clientApp: string, path: string, options: RequestInit = {}): Promise<T> {
const token = localStorage.getItem('accessToken');
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'X-Client-App': clientApp,
...(options.headers as Record<string, string>),
};
if (token) headers.Authorization = `Bearer ${token}`;
const res = await fetch(`${apiBase}${path}`, { ...options, headers });
const json = await res.json();
if (json.code !== 0) throw new Error(json.message || '请求失败');
return json.data as T;
export type StoreSessionStore = {
id: string;
storeId: string;
name: string;
phone: string;
storeName: string;
};
export type StoreProfile = {
id: string;
storeId: string;
name: string;
phone: string;
status?: string;
store?: { id: string; name: string };
};
export type ShopSessionPayload = {
accessToken: string;
refreshToken: string;
store?: StoreSessionStore;
};
const ACCESS_TOKEN = 'accessToken';
const REFRESH_TOKEN = 'refreshToken';
const LAST_PHONE = 'shopLastPhone';
const STORE_PROFILE = 'shopStoreProfile';
const AUTH_RECOVERY_EXEMPT_PATHS = ['/shop/auth/token/refresh', '/shop/auth/sms/send', '/shop/auth/login/sms'];
export function getLastPhone() {
return localStorage.getItem(LAST_PHONE) ?? '';
}
export function saveAuth(data: { accessToken: string }) {
localStorage.setItem('accessToken', data.accessToken);
export function getStoreProfile(): StoreSessionStore | null {
try {
const raw = localStorage.getItem(STORE_PROFILE);
return raw ? (JSON.parse(raw) as StoreSessionStore) : null;
} catch {
return null;
}
}
export function saveAuth(data: ShopSessionPayload) {
localStorage.setItem(ACCESS_TOKEN, data.accessToken);
if (data.refreshToken) localStorage.setItem(REFRESH_TOKEN, data.refreshToken);
if (data.store) {
localStorage.setItem(STORE_PROFILE, JSON.stringify(data.store));
localStorage.setItem(LAST_PHONE, data.store.phone);
}
}
export function clearAuth() {
localStorage.removeItem('accessToken');
localStorage.removeItem(ACCESS_TOKEN);
localStorage.removeItem(REFRESH_TOKEN);
localStorage.removeItem(STORE_PROFILE);
}
export function isLoggedIn() {
return !!localStorage.getItem('accessToken');
return !!localStorage.getItem(ACCESS_TOKEN);
}
function profileFromMe(me: StoreProfile): StoreSessionStore {
return {
id: me.id,
storeId: me.storeId,
name: me.name,
phone: me.phone,
storeName: me.store?.name ?? me.name,
};
}
async function rawRequest<T>(
path: string,
options: RequestInit = {},
token?: string | null,
): Promise<T> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'X-Client-App': CLIENT_APP,
...(options.headers as Record<string, string>),
};
const authToken = token ?? localStorage.getItem(ACCESS_TOKEN);
if (authToken) headers.Authorization = `Bearer ${authToken}`;
const res = await fetch(`${apiBase}${path}`, { ...options, headers });
const json = await res.json();
if (json.code !== 0) {
const err = new Error(json.message || '请求失败') as Error & { status?: number };
err.status = json.code;
throw err;
}
return json.data as T;
}
async function refreshSession(): Promise<ShopSessionPayload | null> {
const refreshToken = localStorage.getItem(REFRESH_TOKEN);
if (!refreshToken) return null;
try {
const data = await rawRequest<ShopSessionPayload>(
'/shop/auth/token/refresh',
{
method: 'POST',
body: JSON.stringify({ refreshToken }),
},
null,
);
saveAuth(data);
return data;
} catch {
return null;
}
}
async function requestWithAuthRetry<T>(
path: string,
options: RequestInit = {},
retried = false,
): Promise<T> {
try {
return await rawRequest<T>(path, options);
} catch (e) {
const err = e as Error & { status?: number };
const canRecover =
err.status === 401 &&
!retried &&
!AUTH_RECOVERY_EXEMPT_PATHS.some((p) => path.startsWith(p));
if (!canRecover) throw e;
const refreshed = await refreshSession();
if (!refreshed) {
clearAuth();
throw e;
}
return requestWithAuthRetry<T>(path, options, true);
}
}
export async function request<T>(clientApp: string, path: string, options: RequestInit = {}): Promise<T> {
void clientApp;
return requestWithAuthRetry<T>(path, options);
}
export async function ensureSession(): Promise<{ authenticated: boolean; store: StoreSessionStore | null }> {
if (!isLoggedIn()) {
return { authenticated: false, store: null };
}
try {
const me = await rawRequest<StoreProfile>('/shop/auth/me');
const store = profileFromMe(me);
saveAuth({
accessToken: localStorage.getItem(ACCESS_TOKEN) ?? '',
refreshToken: localStorage.getItem(REFRESH_TOKEN) ?? '',
store,
});
return { authenticated: true, store };
} catch (e) {
const err = e as Error & { status?: number };
if (err.status === 401) {
const refreshed = await refreshSession();
if (refreshed?.store) {
return { authenticated: true, store: refreshed.store };
}
clearAuth();
return { authenticated: false, store: null };
}
const cached = getStoreProfile();
if (cached) return { authenticated: true, store: cached };
throw e;
}
}