import Taro from '@tarojs/taro' import type { ApiResponse, RequestConfig } from '@/types/api' /** 存储 key(与 AuthContext 保持一致) */ const STORAGE_TOKEN_KEY = 'auth_token' /** 登录页路径 */ const LOGIN_PATH = '/pages/login/index' /** 默认请求超时(ms) */ const DEFAULT_TIMEOUT = 15000 /** 接口根地址(uploadFile 等原生请求同样使用) */ export const BASE_URL = "http://localhost:8000/index.php" /** * HTTP 状态码 → 错误提示映射 */ const HTTP_ERROR_MAP: Record = { 400: '参数不正确', 401: '登录已过期,请重新登录', 403: '您没有权限操作', 404: '请求的资源不存在', 408: '请求超时', 500: '服务器内部错误', 502: '网关错误', 503: '服务暂时不可用', 504: '网关超时', } /** 业务状态码常量 */ const BIZ_CODE = { SUCCESS: 0, } as const /** * 获取本地存储的 token */ export function getToken(): string | null { try { return Taro.getStorageSync(STORAGE_TOKEN_KEY) || null } catch { return null } } /** * 清除本地认证信息 */ function clearAuth(): void { try { Taro.removeStorageSync(STORAGE_TOKEN_KEY) Taro.removeStorageSync('auth_user') } catch { // noop } } /** * 处理 HTTP 状态码错误 * @param statusCode - HTTP 状态码 */ function handleHttpError(statusCode: number): void { // 401 → 清除登录态并跳转登录页 if (statusCode === 401) { clearAuth() Taro.showToast({ title: '登录已过期,请重新登录', icon: 'none' }) // 避免在登录页重复跳转 const pages = Taro.getCurrentPages() const currentPage = pages[pages.length - 1] if (currentPage?.route !== 'pages/login/index') { setTimeout(() => { Taro.navigateTo({ url: LOGIN_PATH }) }, 800) } return } const message = HTTP_ERROR_MAP[statusCode] || `请求失败 (状态码: ${statusCode})` Taro.showToast({ title: message, icon: 'none' }) } /** * 处理业务错误 * @param data - 接口返回数据 */ function handleBusinessError(data: ApiResponse): void { const { msg } = data if (msg) { Taro.showToast({ title: msg, icon: 'none' }) } } /** * 发起网络请求 * * @example * ```ts * // GET 请求 * const res = await request({ url: '/api/user/info' }) * * // POST 请求 * const res = await request({ url: '/api/order/create', method: 'POST', data: { id: 1 } }) * * // 跳过 token(如登录接口) * const res = await request({ url: '/api/auth/login', method: 'POST', skipToken: true }) * ``` */ export function request(config: RequestConfig): Promise> { const { skipToken, skipErrorToast, ...restConfig } = config // 构建请求头 const header: Record = { 'Content-Type': 'application/json', ...((restConfig.header as Record) || {}), } // 自动附加 token if (!skipToken) { const token = getToken() if (token) { header['Authorization'] = `Bearer ${token}` } } return new Promise((resolve, reject) => { Taro.request({ ...restConfig, url: BASE_URL + restConfig.url, header, timeout: restConfig.timeout || DEFAULT_TIMEOUT, success(res) { const { statusCode, data } = res // HTTP 状态码异常 if (statusCode < 200 || statusCode >= 300) { if (!skipErrorToast) { handleHttpError(statusCode) } reject(res) return } const responseData = data as ApiResponse // 业务成功 if (responseData.success) { resolve(responseData) return } // 业务失败 if (!skipErrorToast) { handleBusinessError(responseData) } reject(responseData) }, fail(err) { // 网络错误 / 超时 const errMsg = err.errMsg || '' if (errMsg.includes('timeout')) { Taro.showToast({ title: '请求超时,请稍后重试', icon: 'none' }) } else if (errMsg.includes('fail')) { Taro.showToast({ title: '网络连接失败,请检查网络', icon: 'none' }) } else { Taro.showToast({ title: '网络错误,请稍后重试', icon: 'none' }) } reject(err) }, }) }) } /** * GET 请求快捷方法 */ export function get(url: string, config?: Omit) { return request({ ...config, url, method: 'GET' }) } /** * POST 请求快捷方法 */ export function post(url: string, data?: any, config?: Omit) { return request({ ...config, url, method: 'POST', data }) } /** * PUT 请求快捷方法 */ export function put(url: string, data?: any, config?: Omit) { return request({ ...config, url, method: 'PUT', data }) } /** * DELETE 请求快捷方法 */ export function del(url: string, config?: Omit) { return request({ ...config, url, method: 'DELETE' }) }