8f83624ffe
Avoid conflict with another local service on 3000; align Vite proxies and docs.
100 lines
2.5 KiB
TypeScript
100 lines
2.5 KiB
TypeScript
import Taro from '@tarojs/taro';
|
||
|
||
function resolveApiBase(): string {
|
||
const origin =
|
||
typeof TARO_APP_API_ORIGIN !== 'undefined' && TARO_APP_API_ORIGIN
|
||
? TARO_APP_API_ORIGIN
|
||
: process.env.TARO_ENV === 'h5'
|
||
? 'http://localhost:3010'
|
||
: '';
|
||
if (origin) {
|
||
return `${origin.replace(/\/$/, '')}/api/v1`;
|
||
}
|
||
return '/api/v1';
|
||
}
|
||
|
||
export const API_BASE = resolveApiBase();
|
||
const TOKEN_KEY = 'hq_access_token';
|
||
const CLIENT_APP = 'HQ_WEB';
|
||
|
||
export function getToken(): string {
|
||
try {
|
||
return Taro.getStorageSync(TOKEN_KEY) || '';
|
||
} catch {
|
||
return '';
|
||
}
|
||
}
|
||
|
||
export function saveToken(token: string) {
|
||
Taro.setStorageSync(TOKEN_KEY, token);
|
||
}
|
||
|
||
export function clearToken() {
|
||
Taro.removeStorageSync(TOKEN_KEY);
|
||
}
|
||
|
||
export function isLoggedIn(): boolean {
|
||
return !!getToken();
|
||
}
|
||
|
||
export function redirectToLogin() {
|
||
Taro.reLaunch({ url: '/pages/login/index' });
|
||
}
|
||
|
||
export function logout() {
|
||
clearToken();
|
||
redirectToLogin();
|
||
}
|
||
|
||
type ReqOptions = {
|
||
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
||
data?: Record<string, unknown> | unknown;
|
||
auth?: boolean;
|
||
};
|
||
|
||
function parseBody(data: unknown): { code?: number; message?: string } {
|
||
if (data && typeof data === 'object') {
|
||
return data as { code?: number; message?: string };
|
||
}
|
||
return {};
|
||
}
|
||
|
||
/** 统一请求:注入 X-Client-App + Bearer,解包 { code, message, data },401 自动回登录 */
|
||
export async function request<T = unknown>(path: string, options: ReqOptions = {}): Promise<T> {
|
||
const header: Record<string, string> = {
|
||
'Content-Type': 'application/json',
|
||
'X-Client-App': CLIENT_APP,
|
||
};
|
||
const token = getToken();
|
||
if (token) header.Authorization = `Bearer ${token}`;
|
||
|
||
const res = await Taro.request({
|
||
url: `${API_BASE}${path}`,
|
||
method: options.method ?? 'GET',
|
||
data: options.data as Record<string, unknown>,
|
||
header,
|
||
});
|
||
|
||
const status = res.statusCode;
|
||
const body = parseBody(res.data);
|
||
|
||
if (status === 401 || body?.code === 401) {
|
||
clearToken();
|
||
redirectToLogin();
|
||
throw new Error(body?.message || '登录已过期,请重新登录');
|
||
}
|
||
if (status === 404 && body.code === undefined) {
|
||
throw new Error('接口不可达,请确认 API 服务已启动');
|
||
}
|
||
if (status >= 400 || body.code !== 0) {
|
||
throw new Error(body?.message || `请求失败(${status})`);
|
||
}
|
||
return (res.data as { data: T }).data;
|
||
}
|
||
|
||
export type Paginated<T> = { items: T[]; total: number };
|
||
|
||
export function toast(title: string, icon: 'success' | 'error' | 'none' = 'none') {
|
||
Taro.showToast({ title, icon, duration: 1800 });
|
||
}
|