/** * 通用网络请求封装 - 使用 fetch */ const BASE_URL = import.meta.env.VITE_API_URL || 'http://127.0.0.1:3001'; // 默认超时时间 15 秒 const DEFAULT_TIMEOUT = 15000; // 错误码定义 const ERR_CODE = { SUCCESS: 200, UNAUTHORIZED: 401, FORBIDDEN: 403, NOT_FOUND: 404, SERVER_ERROR: 500, }; function getHeaders(contentType = 'application/json') { const headers = { 'Content-Type': contentType, }; const token = localStorage.getItem('member_token'); if (token) { headers['Authorization'] = `Bearer ${token}`; } return headers; } function handleResponse(response) { return response.text().then(text => { let data; try { data = JSON.parse(text); } catch (e) { // JSON 解析失败,返回原始文本 return Promise.reject({ status: response.status, message: '数据解析失败', data: null }); } // 检查 HTTP 状态码 if (!response.ok) { return Promise.reject({ status: response.status, message: data.message || '请求失败', data: null }); } // 检查业务状态码 if (data.status !== ERR_CODE.SUCCESS) { return Promise.reject(data); } return data; }); } /** * 处理 token 失效 */ function handleUnauthorized() { // 如果 localStorage 中本来就没有 token,说明是未登录状态,不做处理 if (!localStorage.getItem('member_token')) { return; } // 清除登录状态 localStorage.removeItem('member_token'); localStorage.removeItem('member_username'); // 跳转到登录页 if (window.location.hash !== '#/Login') { window.location.href = '#/Login'; } } /** * 统一错误处理 */ function handleError(err) { // token 失效 if (err.status === ERR_CODE.UNAUTHORIZED || err.status === ERR_CODE.FORBIDDEN) { handleUnauthorized(); } return Promise.reject(err); } /** * 带超时的 fetch */ function fetchWithTimeout(url, options, timeout = DEFAULT_TIMEOUT) { return new Promise((resolve, reject) => { const timer = setTimeout(() => { clearTimeout(timer); reject({ status: 0, message: '网络超时,请稍后重试', data: null }); }, timeout); fetch(url, options) .then(res => { clearTimeout(timer); resolve(res); }) .catch(err => { clearTimeout(timer); reject({ status: 0, message: '网络连接失败', data: null }); }); }); } /** * 通用请求 * @param {string} url - 请求路径 * @param {object} data - 请求数据 * @param {string} method - 请求方法,默认 POST * @param {number} timeout - 超时时间 ms * @returns {Promise} */ export function request(url, data, method = 'POST', timeout = DEFAULT_TIMEOUT) { return fetchWithTimeout(BASE_URL + url, { method, headers: getHeaders(), body: JSON.stringify(data), }, timeout).then(handleResponse).catch(handleError); } export const get = (url, params, timeout = DEFAULT_TIMEOUT) => { let query = ''; if (params) { query = '?' + new URLSearchParams(params).toString(); } return fetchWithTimeout(BASE_URL + url + query, { method: 'GET', headers: getHeaders(), }, timeout).then(handleResponse).catch(handleError); }; export const post = (url, data, timeout = DEFAULT_TIMEOUT) => request(url, data, 'POST', timeout); export const put = (url, data, timeout = DEFAULT_TIMEOUT) => request(url, data, 'PUT', timeout); export const del = (url, data, timeout = DEFAULT_TIMEOUT) => request(url, data, 'DELETE', timeout); /** * POST FormData 请求 * @param {string} url - 请求路径 * @param {FormData} formData - FormData 对象 * @param {number} timeout - 超时时间 ms * @returns {Promise} */ export function postForm(url, formData, timeout = DEFAULT_TIMEOUT) { const headers = {}; const token = localStorage.getItem('member_token'); if (token) { headers['Authorization'] = `Bearer ${token}`; } return fetchWithTimeout(BASE_URL + url, { method: 'POST', headers, body: formData, }, timeout).then(handleResponse).catch(handleError); } /** * 上传文件 * @param {string} url - 请求路径 * @param {File[]} files - 文件数组 * @param {number} timeout - 超时时间 ms(默认 30 秒上传较长) * @returns {Promise} */ export function upload(url, files, timeout = 30000) { const formData = new FormData(); files.forEach(file => { formData.append('file', file); }); const headers = {}; const token = localStorage.getItem('member_token'); if (token) { headers['Authorization'] = `Bearer ${token}`; } return fetchWithTimeout(BASE_URL + url, { method: 'POST', headers, body: formData, }, timeout) .then(res => res.text().then(text => { try { const data = JSON.parse(text); if (data.status !== ERR_CODE.SUCCESS) { return Promise.reject(data); } return data; } catch (e) { return Promise.reject({ status: 0, message: '数据解析失败', data: null }); } })) .catch(handleError); } export default { get, post, put, del, upload, postForm, request };