UNPKG

wangbd-myui

Version:

MY前端基础框架核心组件库

358 lines (325 loc) 9.77 kB
/** * ajax请求模块, 底层调用axios发送请求,做了响应数据的适配,并支持 url path 传参 * @module $ui/utils/ajax * @author 陈华春 <chenhuachun@xdh.net.cn> * */ import pathToRegex from 'path-to-regexp' import isPlainObject from 'lodash/isPlainObject' import axios from './axios' import {getHost} from './url' import {get, save, LOCAL, SESSION} from './storage' import bus from './bus' import {guid} from './util' import globalConfig from '../config' // 编译过的url缓存 const pathToRegexCaches = {} // 内存缓存数据保存处 const Caches = {} /** * ajax 请求的默认参数 * @const * @type {{url: null, method: string, params: null, data: null}} * @property {string} url - 请求地址, 必要参数 * @property {string} method - 请求方法类型,默认:get * @property {object} params - path参数,如: /api/user/:id, 不是url查询参数, 默认:null * @property {object} data - 请求发送数据,get head delete请求是查询参数,其他类型是post数据, 默认:null, 支持 Object/URLSearchParams/FormData * @property {Object|boolean} cache 缓存配置 * @property {boolean} socket 是否启用webSocket通知 */ const defaultConfig = { url: null, method: 'get', params: null, // 这里不是查询参数,是path参数,如: /api/user/:id query: null, // url 查询参数 data: null, // get head delete请求是查询参数,其他类型是post数据 cache: null, // boolean/Object, Object: {local, session, key} local:数据是否保存到localStorage,session:数据是否保存到SessionStorage key:缓存的key,默认取url+query socket: false // boolean/Object, Object:{name, channel} 是否使用 websocket通知请求结果 } /** * 创建缓存key, 由请求url、类型、参数、发送数据构成的标识符 * @private * @param {string} url 请求url * @param {string} method 请求类型 * @param {object} query url参数对象 * @param {object} data 请求数据 * @return {string} */ function createCacheKey(url, method, query, data) { const keys = [ url, method, isPlainObject(query) ? JSON.stringify(query) : query?.toString(), isPlainObject(data) ? JSON.stringify(query) : data?.toString()] return encodeURIComponent(keys.join(',')) } export function parseOptions(opts = {}) { const config = {...defaultConfig, ...opts} // 从缓存中提取已经解析过的url // url 支持参数信息,如: /api/path/:id // 这种情况需要把url解析成一个正则表达式,然后再跟参数匹配组成一个真正要请求的url let compileCache = pathToRegexCaches[config.url] const host = getHost(config.url || '') if (!compileCache) { // 先排除host段,因为host段的端口号与参数写法有冲突 compileCache = pathToRegexCaches[config.url] = pathToRegex.compile(config.url.replace(host, '')) } // 出去传输过来的url参数,并补回host段 const url = host + compileCache(config.params) const method = (config.type || config.method || 'get').toLowerCase() const params = ['get', 'head', 'delete'].includes(method) ? Object.assign({}, config.data, config.query) : config.query const cache = config.cache ? { key: createCacheKey(url, method, params, config.data), ...config.cache } : null const socket = config.socket ? { channel: guid(), name: config.socket.name || '__async__' } : null clean(config, ['method', 'type', 'query', 'params', 'cache', 'socket']) return { config: { ...config, url, method, params }, cache, socket } } /** * 处理响应的数据 * @private * @param {Object} res 响应原始数据 * @param {object} cache 缓存配置 * @param {Storage} storage 缓存存储位置 * @param {function} resolve * @param {function} reject */ function processData(res, cache, storage, resolve, reject) { if (res.data) { const {keys, statusCode} = globalConfig const {code, data} = keys const config = res.config || {} // 非 json 格式不需要判断状态码 if (config.responseType !== 'json') { resolve(res.data) return } if (String(res.data[code]) === String(statusCode.success)) { (cache && cache.key) && saveCache(cache.key, res.data[data], storage) resolve(res.data[data]) } else { reject(res.data) } } else { reject(res) } } /** * * 构建通用适配处理ajax返回数据Promise,判断ajax响应的json对象code属性,如果与 AJAX_SUCCESS 的值不一致即 reject * @private * @returns {Promise} Promise 实例 */ function createPromise({config, cache, socket}) { let cacheData, storage = null if (cache && cache.key) { storage = cache.local ? LOCAL : (cache.session ? SESSION : null) cacheData = getCache(cache.key, storage) } return new Promise((resolve, reject) => { if (cacheData) { resolve(cacheData) return } // 开启用websocket接收响应数据,通过消息总线来传递数据 if (socket) { // 在请求数据中注入用户与websocket通信的消息名称标识 config.params = { ...config.params, // websocket约定的消息名称,这里生成一个唯一的标识 [socket.name]: socket.channel } // 用消息总线接收异步消息,需要在websocket接收到数据后,由事件总线触发 // 只接收一次,收到消息即销毁侦听句柄 bus.$once(socket.channel, res => { processData(res, cache, storage, resolve, reject) }) } axios(config).then(res => { // 如果开启用websocket接收响应数据,http的响应结果可以忽略 if (socket) return processData(res, cache, storage, resolve, reject) }).catch(e => reject(e)) }) } /** * 获取缓存 * @private * @param {string} key 缓存key * @param {Storage} storage 保存缓存方式,localStorage/sessionStorage/null * @return {String|Object|Array} */ function getCache(key, storage) { return storage ? get(key, storage) : Caches[key] } /** * 写入缓存 * @private * @param {string} key 缓存key * @param {Object|Array} data 写入数据 * @param {Storage} storage 保存缓存方式,localStorage/sessionStorage/null */ function saveCache(key, data, storage) { if (storage) { save(key, data, storage) } else { Caches[key] = data } } /** * 清除无用的属性 * @private * @param object * @param props */ function clean(object, props = []) { props.forEach(name => { delete object[name] }) } /** * ajax 函数 * @export * @param {object} options - ajax参数选项. [默认选项值]{@link module:utils/ajax~defaultConfig} * @returns {Promise} promise * * @example * * // 基础用法 * ajax({ * url: '/api/users' * }) * .then(res => { * // to do something... * }) * .catch(e => { * // to do something... * }) * * @example * * // url path传参 * ajax({ * method: 'get', * url: '/api/users/:id', * params: { * id: '123' * } * }) * * @example * * // 发送数据 * ajax({ * url: '/api/users', * method: 'post', * data: { * name: 'kenny', * password: '123456' * } * }) * * @example * // 设置请求头 * ajax({ * url: '/api/users/', * data: { * page: 1, * limit: 10 * }, * headers: { * 'Content-type': 'application/x-www-form-urlencoded' * } * }) * * @example * * // 启用缓存,缓存到内存,刷新页面将失效 * ajax({ * url: '/api/user', * cache: true * }) * * // 启用缓存,缓存到LocalStorage * ajax({ * url: '/api/user', * cache: { * local: true * } * }) * * // 启用缓存,缓存到SessionStorage * ajax({ * url: '/api/user', * cache: { * session: true * } * }) * * // 启用缓存,自定义缓存key * ajax({ * url: '/api/user', * cache: { * local: true, * key: 'cachekey' * } * }) * * @example http发送请求,websocket接收响应,需要与websocket配合 * ajax({ * url: '/api/user', * socket: true * }) * * // webscoket 接收转发 * ws.on('FwzxSyncCall', function (res) { * if (res && res.NotifyId) { * bus.$emit(res.NotifyId, responseData({data: res.Data})) * } * }) * */ export default function (options = {}) { // 处理默认参数,传参和默认参数合并 const opts = {...defaultConfig, ...options} // 必须要传入url if (!opts.url) { throw new Error('ajax url is required!') } const config = parseOptions(opts) return createPromise(config) } /** * axios 数据适配函数, 数据转换层对数据进行转换的函数句柄,代码生成器需要用到该函数 * @param {Function} transformer 自定义数据转换函数 * @param {string} method 方法名称 * @param {object} postData 请求的参数对象 * @param {object} options ajax请求的options * @param {object} params 请求的url参数对象 * @returns {Function} */ export function transformHandler(transformer, method, postData, options, params) { return function (data) { const json = typeof data === 'string' ? JSON.parse(data) : data return transformer(json, method, postData, options, params) } }