import Taro from '@tarojs/taro' import { getToken, redirectToLogin } from './auth' import type { ResponseData } from '@/api/types' type RequestMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' interface RequestOptions { method: RequestMethod url: string data?: unknown params?: object showLoading?: boolean } interface UploadOptions { url: string filePath: string fileName?: string name?: string formData?: Record showLoading?: boolean } const baseURL = process.env.TARO_APP_API_BASE || 'http://localhost:3000' export async function request(options: RequestOptions) { if (options.showLoading !== false) { Taro.showLoading({ title: '加载中', mask: true }) } try { return await sendRequest(options) } finally { if (options.showLoading !== false) { Taro.hideLoading() } } } async function sendRequest(options: RequestOptions) { const response = await Taro.request>({ url: buildUrl(options.url, options.params), method: options.method, data: options.data, header: buildHeaders() }) return handleResponse(response.data) } export async function uploadFile(options: UploadOptions) { if (options.showLoading !== false) { Taro.showLoading({ title: '上传中', mask: true }) } try { return await sendUpload(options) } finally { if (options.showLoading !== false) { Taro.hideLoading() } } } async function sendUpload(options: UploadOptions) { const response = await Taro.uploadFile({ url: buildUrl(options.url), filePath: options.filePath, name: options.name || 'file', fileName: options.fileName, formData: options.formData, header: buildUploadHeaders() }) validateUploadStatus(response.statusCode, response.data) return handleResponse(parseUploadData(response.data)) } function validateUploadStatus(statusCode: number, data: string) { if (statusCode >= 200 && statusCode < 300) return const message = getUploadErrorMessage(data) || `上传失败(${statusCode})` throw new Error(message) } function buildUrl(url: string, params?: object) { const target = `${baseURL}${url}` const query = new URLSearchParams() Object.entries(params || {}).forEach(([key, value]) => appendQuery(query, key, value)) const queryString = query.toString() return queryString ? `${target}?${queryString}` : target } function appendQuery(query: URLSearchParams, key: string, value: unknown) { if (value === undefined || value === null || value === '') return query.append(key, String(value)) } function buildHeaders() { return { ...buildUploadHeaders(), 'Content-Type': 'application/json' } } function buildUploadHeaders() { const token = getToken() const headers: Record = { Accept: 'application/json, text/plain, */*' } if (token) { headers.Authorization = `Bearer ${token}` } return headers } function parseUploadData(data: string | object) { if (typeof data !== 'string') return data as ResponseData try { return JSON.parse(data) as ResponseData } catch { throw new Error(getUploadErrorMessage(data) || '服务器返回数据结构有误') } } function getUploadErrorMessage(data: string) { try { return (JSON.parse(data) as { msg?: string }).msg || '' } catch { return data.slice(0, 60) } } function handleResponse(data: ResponseData) { if (!data || data.code === undefined) { throw new Error('服务器返回数据结构有误') } if (data.code === 106) { redirectToLogin() throw new Error(data.msg || '登录状态已过期') } if (data.code !== 0) { Taro.showToast({ title: data.msg || '请求失败', icon: 'none' }) throw new Error(data.msg || '请求失败') } return data }