| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355 |
- /**
- * uni-app 网络请求封装
- * 特性:请求/响应拦截、统一错误处理、超时控制、请求取消、Token 自动携带、环境切换
- * 适配端:小程序、H5、App
- */
- // ======================== 基础配置 ========================
- // 环境配置(根据 NODE_ENV 切换 baseURL)
- const env = process.env.NODE_ENV || 'development';
- const baseURLMap = {
- development: 'http://192.168.110.235:1860', // 开发环境
- production: '', // 生产环境
- test: 'http://192.168.110.235:1860' // 测试环境
- };
- // 默认基础请求地址
- const DEFAULT_BASE_URL = baseURLMap[env];
- const DEFAULT_BASE_URL_IMG = 'http://47.107.107.47:9000/zksy-file/';
- // 默认请求配置
- const DEFAULT_CONFIG = {
- baseURL: DEFAULT_BASE_URL,
- timeout: 10000, // 全局默认超时时间(10秒)
- header: {
- 'Content-Type': 'application/json;charset=UTF-8' // 默认请求头
- },
- showLoading: true, // 是否显示加载中提示(全局默认开启)
- loadingText: '加载中...', // 加载提示文字
- withToken: true, // 是否自动携带 Token(全局默认开启)
- cancelRepeat: true // 是否取消重复请求(全局默认开启)
- };
- // 存储请求取消令牌(key: 请求唯一标识,value: 取消函数)
- const cancelTokenMap = new Map();
- // ======================== 工具函数 ========================
- /**
- * 生成请求唯一标识
- * @param {String} method - 请求方法(GET/POST 等)
- * @param {String} url - 请求地址
- * @param {Object} data - 请求参数
- * @returns {String} 请求唯一标识
- */
- function generateRequestKey(method, url, data) {
- return `${method.toUpperCase()}-${url}-${JSON.stringify(data)}`;
- }
- /**
- * 统一提示方法(兼容不同端的提示样式)
- * @param {String} msg - 提示文字
- * @param {String} type - 提示类型(none/success/error)
- */
- function showToast(msg, type = 'none') {
- uni.showToast({
- title: msg,
- icon: type,
- duration: 2000,
- mask: true // 显示蒙层,防止穿透点击
- });
- }
- // ======================== 核心拦截逻辑 ========================
- /**
- * 请求拦截器:处理请求前的统一逻辑
- * @param {Object} header - 原始请求头
- * @param {Boolean} withToken - 是否携带 Token
- * @returns {Object} 处理后的请求头
- */
- function requestInterceptor(header, withToken) {
- const finalHeader = { ...header };
- // 自动携带 Token(从本地缓存读取)
- if (withToken) {
- const token = uni.getStorageSync('token');
- if (token) {
- finalHeader.Authorization = `Bearer ${token}`; // 适配 JWT Token 格式
- }
- }
- return finalHeader;
- }
- /**
- * 响应拦截器:处理响应后的统一逻辑
- * @param {Object} response - 响应对象
- * @returns {Promise} 处理后的结果
- */
- function responseInterceptor(response) {
- const { statusCode, data } = response;
- // 1. 处理 HTTP 状态码错误
- if (statusCode !== 200) {
- let errMsg = '';
- switch (statusCode) {
- case 401:
- errMsg = '登录失效,请重新登录';
- // 清除 Token 并跳转到登录页(排除登录页本身)
- uni.removeStorageSync('token');
- const pages = getCurrentPages();
- const currentPage = pages[pages.length - 1];
- if (currentPage.route !== 'pages/login/login') {
- uni.redirectTo({ url: '/pages/login/login' });
- }
- break;
- case 403:
- errMsg = '权限不足,无法访问';
- break;
- case 404:
- errMsg = '请求地址不存在';
- break;
- case 500:
- errMsg = '服务器内部错误,请稍后重试';
- break;
- default:
- errMsg = `请求失败(状态码:${statusCode})`;
- }
- showToast(errMsg, 'none');
- return Promise.reject({
- code: statusCode,
- msg: errMsg,
- raw: response
- });
- }
- // 2. 处理业务状态码(根据后端约定调整)
- // 假设后端返回格式:{ code: 0, msg: '成功', data: {} }
- // const { code, msg, data: resData } = data;
- const { code, msg, token } = data; // 直接解析 token
- if (code === 200) {
- // 业务请求成功,返回核心数据
- return Promise.resolve({ token, ...data }); // 确保返回有值
- } else {
- // 业务请求失败,提示错误信息
- showToast(msg || '操作失败', 'none');
- return Promise.reject({
- code,
- msg: msg || '操作失败',
- raw: data
- });
- }
- }
- /**
- * 网络错误处理
- * @param {Object} error - 错误对象
- * @returns {Promise} 拒绝的 Promise
- */
- function networkErrorHandler(error) {
- let errMsg = '网络请求失败';
- switch (error.errMsg) {
- case 'request:fail timeout':
- errMsg = '请求超时,请检查网络';
- break;
- case 'request:fail abort':
- errMsg = '请求已取消';
- break;
- case 'request:fail network error':
- errMsg = '网络异常,请检查网络连接';
- break;
- default:
- errMsg = `请求错误:${error.errMsg}`;
- }
- showToast(errMsg, 'none');
- return Promise.reject({
- code: -1,
- msg: errMsg,
- raw: error
- });
- }
- // ======================== 核心请求方法 ========================
- /**
- * 创建请求实例
- * @param {Object} customConfig - 自定义全局配置(覆盖默认配置)
- * @returns {Function} 请求方法
- */
- function createRequest(customConfig = {}) {
- // 合并全局配置
- const globalConfig = { ...DEFAULT_CONFIG, ...customConfig };
- /**
- * 核心请求函数
- * @param {Object} options - 单个请求配置
- * @returns {Promise} 请求结果
- */
- const request = async (options) => {
- const {
- url,
- method = 'GET',
- data = {},
- header = {},
- showLoading = globalConfig.showLoading,
- loadingText = globalConfig.loadingText,
- timeout = globalConfig.timeout,
- withToken = globalConfig.withToken,
- cancelRepeat = globalConfig.cancelRepeat
- } = options;
- // 1. 验证必填参数
- if (!url) {
- showToast('请求地址不能为空', 'none');
- return Promise.reject({ code: -2, msg: '请求地址不能为空' });
- }
- // 2. 显示加载中提示
- let loadingTimer = null;
- if (showLoading) {
- // 延迟显示(避免请求过快导致提示闪烁)
- loadingTimer = setTimeout(() => {
- uni.showLoading({ title: loadingText, mask: true });
- }, 200);
- }
- // 3. 处理重复请求
- const requestKey = generateRequestKey(method, url, data);
- if (cancelRepeat && cancelTokenMap.has(requestKey)) {
- // 取消已存在的重复请求
- cancelTokenMap.get(requestKey)('取消重复请求');
- cancelTokenMap.delete(requestKey);
- }
- // 4. 创建取消令牌(用于取消请求)
- let cancelRequest;
- const cancelToken = new Promise((resolve) => {
- cancelRequest = resolve;
- cancelTokenMap.set(requestKey, resolve);
- });
- try {
- // 5. 合并请求头(全局头 + 单个请求头 + 拦截器处理)
- const finalHeader = requestInterceptor({ ...globalConfig.header, ...header }, withToken);
- // 6. 发起请求(Promise 化 uni.request)
- const requestPromise = new Promise((resolve, reject) => {
- uni.request({
- url: globalConfig.baseURL + url, // 拼接完整请求地址
- method,
- data,
- header: finalHeader,
- timeout,
- success: resolve,
- fail: reject
- });
- });
- // 7. 同时监听请求和取消令牌(race: 谁先完成就执行谁)
- const result = await Promise.race([requestPromise, cancelToken]);
- // 8. 移除当前请求的取消令牌
- cancelTokenMap.delete(requestKey);
- // 9. 关闭加载中提示
- clearTimeout(loadingTimer);
- if (showLoading) uni.hideLoading();
- // 10. 处理响应结果
- return responseInterceptor(result);
- } catch (error) {
- // 11. 异常处理
- cancelTokenMap.delete(requestKey);
- clearTimeout(loadingTimer);
- if (showLoading) uni.hideLoading();
- // 区分是网络错误还是取消请求
- if (typeof error === 'string') {
- // 取消请求的错误(主动取消)
- return Promise.reject({ code: -3, msg: error });
- } else {
- // 网络错误(超时、断网等)
- return networkErrorHandler(error);
- }
- }
- };
- // ======================== 快捷请求方法 ========================
- /**
- * GET 请求快捷方法
- * @param {String} url - 请求地址
- * @param {Object} data - 请求参数
- * @param {Object} options - 自定义配置
- * @returns {Promise} 请求结果
- */
- request.get = (url, data = {}, options = {}) => {
- return request({ url, method: 'GET', data, ...options });
- };
- /**
- * POST 请求快捷方法
- * @param {String} url - 请求地址
- * @param {Object} data - 请求参数
- * @param {Object} options - 自定义配置
- * @returns {Promise} 请求结果
- */
- request.post = (url, data = {}, options = {}) => {
- return request({ url, method: 'POST', data, ...options });
- };
- /**
- * PUT 请求快捷方法
- * @param {String} url - 请求地址
- * @param {Object} data - 请求参数
- * @param {Object} options - 自定义配置
- * @returns {Promise} 请求结果
- */
- request.put = (url, data = {}, options = {}) => {
- return request({ url, method: 'PUT', data, ...options });
- };
- /**
- * DELETE 请求快捷方法
- * @param {String} url - 请求地址
- * @param {Object} data - 请求参数
- * @param {Object} options - 自定义配置
- * @returns {Promise} 请求结果
- */
- request.delete = (url, data = {}, options = {}) => {
- return request({ url, method: 'DELETE', data, ...options });
- };
- // ======================== 辅助方法 ========================
- /**
- * 取消所有未完成的请求
- * @param {String} reason - 取消原因
- */
- request.cancelAll = (reason = '取消所有请求') => {
- cancelTokenMap.forEach((cancel) => cancel(reason));
- cancelTokenMap.clear();
- };
- /**
- * 获取当前全局配置
- * @returns {Object} 全局配置
- */
- request.getConfig = () => {
- return { ...globalConfig };
- };
- /**
- * 更新全局配置
- * @param {Object} newConfig - 新的全局配置
- */
- request.updateConfig = (newConfig) => {
- Object.assign(globalConfig, newConfig);
- };
- return request;
- }
- // 创建默认请求实例(全局使用)
- const request = createRequest();
- export const baseURL = DEFAULT_BASE_URL;
- export const imgBaseURL = DEFAULT_BASE_URL_IMG;
- export { baseURL as DEFAULT_BASE_URL };
- export { imgBaseURL as DEFAULT_BASE_URL_IMG };
- // 导出请求实例和创建方法
- export default request;
- export { createRequest };
|