/** * @file 全局数据模型 model * 提供数据的读取、保存/缓存、删除、更新等操作。各模块 model 可继承该模型,以进行模块范围内的数据存取操作。 * @module adm * @author lzwy0820@qq.com * @since 2016-03-31 * * @example * import adm from 'ajax-data-model'; * const upsModel = $.extend(true, {}, adm, {aa: 'ccc', restapi: {task_type: '/rest/task/type'}}); * // 支持的方法:upsModel.get、upsModel.save、upsModel.delete、upsModel.clear * // 配置了 url,则都返回 Promise 对象,不管是否缓存 * upsModel.get({url: '/rest/xxx'}).done().fail().then(); * // 保存数据到 localStorage 中 * upsModel.save('appList', [{music: 'test'}], 'localStorage'); * // 通过名字获取,返回存储的数据或者 undefined * upsModel.get('appList'); * upsModel.get('appList', 'localStorage'); * * @example * // 获取 task_type 数据 * const data = {type: 10}; * adm.get({ * url: upsModel.restapi.task_type, * data: data, * cache: 'sessionStorage', // 缓存到 sessionStorage * fromCache: 'sessionStorage', // 获取时优先从 sessionStorage 读取 * cacheName: 'task_type_' + data.type, // 缓存、从缓存读取时使用的名称 * expires: 1000 * 60 * 5, // 数据有效时间为 5 分钟 * }).then((result) => { * let taskTypeList = result.value || []; * console.log(taskTypeList); * }, (err) { * console.log(err); * }); */ 'use strict'; // import $ from 'jquery'; import settings from './common/settings'; import { getCacheStor, deleteCacheDataByName, getCacheDataByName, saveTOCache, isString, getPromise } from './common/cache-helper'; /** * ajax 请求通用方法 * @param {Object} config - 请求参数配置 * @param {String} config.url - ajax url,必须存在,`config.ajaxParam` 中配置此参数无效 * @param {Object} config.ajaxParam - ajax 额外参数扩展,如涉及文件上传等 * @param {Object} config.data - ajax 请求的参数 * @param {Object} config.waiting - 用于传递给 settings.fnWaiting 方法使用的参数配置 * @param {Object} config.tipConfig[true] - ajax 出错时的提示配置。配置为 false 时,禁用全局的系统提示,包括 成功/出错/404/50x 等 * @param {Object} config.errAlert[true] - ajax error 时是否给出提示 * @param {Function} callback - ajax 请求成功时回调 * @param {Function} errCallback - ajax 请求失败或 code !== 200 时回调 * @param {Object} param - 传递给 ajax 请求的额外参数 * @param {Function} fnCB - 请求到数据之后的立即回调方法,用于请求成功后需要前置处理的情况 * @return {Promise} 用于自定义回调处理。 * 注意:ajax 请求的 done/fail 回调,与 callback/errCallback 可能有区别,具体取决于 fnAjaxDone 与 fnAjaxFail 回调的实现 */ function requestAjax(config, callback, errCallback, fnCB) { const $p = getPromise(settings.isJquery); if (!config.url || typeof config.url !== 'string') { console.trace('请求 URL API 不存在,或格式不对:', config.url); return $p.reject('请求 URL API 不存在,或格式不对:', config.url); } // data.btnWaiting 的兼容,应使用 config.waiting 参数 if (config.data && config.data.btnWaiting) { config.waiting = config.waiting || config.data.btnWaiting; delete config.data.btnWaiting; } // jsonp 兼容 let dataType = 'json'; if (/^https?:\/\//.test(config.url) && config.url.search(window.location.host) === -1) { dataType = 'jsonp'; } // 请求前回调,可以引用方式修改 config if (settings.fnBeforeAjax) { settings.fnBeforeAjax(config); } // 格式化 config.data let item; if ('object' === typeof config.data) { for (item in config.data) { if ('string' !== typeof config.data[item]) { config.data[item] = JSON.stringify(config.data[item]); } } } // ajax 请求前处理,与请求后处理呼应 settings.fnWaiting(config); const startTime = new Date(); return $.ajax($.extend(true, { type: 'GET', dataType }, config.ajaxParam, { url: config.url, data: config.data })).then((result) => { const success = settings.fnAjaxDone(result, (res) => { if (fnCB instanceof Function) { fnCB(result); } if (callback instanceof Function) { callback(res); } }, errCallback, config); // 为 false,设为失败回调 if (!success) { return $p.reject(result); } // 为 true if (true === success) { return $p.resolve(result); } // 为 Promise 风格回调 if ('function' === typeof success.then) { // $p = success; // return $p; return success; } // 为其它类型,返回 success 内容 return $p.resolve(success); }, (err) => { settings.fnAjaxFail(err, config); if (errCallback instanceof Function) { errCallback(err); } return $p.reject(err); }).always(() => { // ajax 完成后处理 settings.fnWaiting(config, new Date() - startTime); }); // return $p; } // 获取缓存数据的名称 key function getCacheName(config) { // 第一个参数为字符串,则为名称,直接返回 config 作为缓存名称 if (isString(config) || !config) { return config; } let cacheName = config.cacheName; const data = config.data; if (!cacheName) { cacheName = config.url; if (typeof data === 'object') { cacheName += JSON.stringify(data); } } return cacheName; } /** * 全局数据模型 model * @alias module:adm * @type {Object} */ export default { /** * 数据获取,可为远程url、缓存等 * @param {Object} config 为字符串时,从缓存中读取数据,否则为从远程获取数据,参数如下: * ```js * { * url: '', // API url 地址,可为空。为空时应存在 cacheName,此时为从缓存中读取数据 * data: {}, // url 请求参数 * cache: false, // 配置了 url 获取数据时,是否缓存数据。可取值:`false/true/sessionStorage/localStorage` * fromCache: false, // 配置了 url,是否首先尝试从缓存中读取数据。可取值:`false/true/sessionStorage/localStorage` * cacheName: '', // 配置了 url 并且 cache 为 true,配置缓存数据的名称,不配置则取值 url (/ 会替换为 . 作为深度路径) * expires: 0, // 如果 cache 为 true,设置缓存数据的有效期,可为 毫秒数,或 Date 类型日期 * tipConfig: {delay: 2000} // ajax 出错时的提示配置。配置为 false 时,禁用全局的系统提示,包括 成功/出错/404/50x 等 * errAlert: true // ajax error 时是否给出全局提示,优先级高于 settings.errAlert * waiting: {} // 按钮等待等配置,用于传递给 settings.fnWaiting 方法 * ajaxParam: null // ajax 额外参数扩展,如涉及文件上传等,需要修改部分参数。其中 url 参数无效,应当使用 config.url * } * ``` * @param {Object} callback 成功回调方法 * @param {Object} errCallback 从 url 获取时,失败后需要做一些处理的回调方法 * } */ get(config, callback, errCallback) { if (!config) { return undefined; } let cacheData; const $promise = getPromise(settings.isJquery); const cacheName = getCacheName(config); // 配置了 url,从 url 中获取 if (config.url) { cacheData = getCacheDataByName(cacheName, config.fromCache); // fromCache 为 true,尝试从缓存中获取数据 if (config.fromCache && cacheData) { if (typeof callback === 'function') { callback(cacheData); } $promise.resolve(cacheData); // return cacheData; // 返回数据 return $promise; // 这里改了后不兼容旧的调用,应该注意 bug 的出现! } config.ajaxParam = $.extend(config.ajaxParam, { type: 'GET' }); return requestAjax(config, callback, errCallback, (result) => { // cache 为 true,缓存数据 if (config.cache) { this.save(cacheName, result, config); } }); } else if (config.hasOwnProperty('url')) { // 配置了 url,但 url 值为空 console.trace('配置了 URL 参数,但值为空:', config); $promise.reject('配置了 URL 参数,但值为空', config); } else { // 未配置 url,则必须配置 config.cacheName,或者 config 为字符串(作为cacheName),此时为从缓存中取得数据 cacheData = getCacheDataByName(cacheName, config.fromCache || callback); if (callback instanceof Function) { callback(cacheData); } return cacheData; } return $promise; }, /** * 设置/存储数据 * @param {Object|String} config - 配置信息。也可以为字符串,则为需存储的数据名称。与 {@link module:adm~get} 的 config 参数相同 * @param {Function|Object} callback - 存储成功后回调方法。当 config 为字符串时,为需存储的数据,或方法执行后返回要存储的数据 * @param {Function|String} errCallback - 从 url 获取时,失败后需要做一些处理的回调方法。config 为字符串时,为配置信息,如 {cacheType, expires} * @example * // 存储数据到 localStorage,名称为 testdataName * adm.save('testdataName', {test: 1}, 'localStorage'); * @example * // 存储数据到远程,同时存储到 sessionStorage * adm.save({url: '/rest/dd', data: {test: 1}, cache: 'sessionStorage'}); */ save(config, callback, errCallback) { if (!config) { return ''; } let cacheData; const $promise = getPromise(settings.isJquery); const cacheName = getCacheName(config); if (isString(config)) { // config 为字符串,则作为cacheName if (callback instanceof Function) { // 可以存储为回调方法执行后的结果 saveTOCache(cacheName, callback(), errCallback); } else { saveTOCache(cacheName, callback, errCallback); } $promise.resolve(cacheName); } else if (config.url) { // 配置了 url,将数据存储到远程 cacheData = getCacheDataByName(cacheName, config.fromCache); // fromCache 为 true,尝试从缓存中获取数据 if (config.fromCache && cacheData) { if (callback instanceof Function) { callback(cacheData); } $promise.resolve(cacheData); // return cacheData; // 返回数据 return $promise; // 这里改了后不兼容旧的调用,应该注意 bug 的出现! } config.ajaxParam = $.extend({ type: 'POST' }, config.ajaxParam); return requestAjax(config, callback, errCallback, (result) => { if (config.cache) { // 远程存储成功了,本地也需缓存数据时 saveTOCache(cacheName, result, config); } }); } else if (config.hasOwnProperty('url')) { // 配置了url,但 url 值为空 console.trace('配置了 URL 参数,但值为空:', config); $promise.reject('配置了 URL 参数,但值为空', config); } else if (cacheName) { // 没有设置 url,但设置了 config.cacheName(此时 cacheName=config.cachename),则保存数据到本地 saveTOCache(cacheName, config.data, config); if (callback instanceof Function) { callback(cacheData); } $promise.resolve(config.data); } return $promise; }, /** * 删除一个数据 * @param {Object} config - 为字符串时,作为 cacheName 尝试从缓存中删除数据。否则格式如下: * ```js * { * url: '', // 配置了 url,从远程删除数据,否则从缓存中删除 * cache: false, // 配置了 url,是否还尝试从缓存中删除数据。可取值:false/true/sessionStorage/localStorage * cacheName: '' // 从缓存中删除数据时,提供其名称。 * } * ``` */ delete(config, callback, errCallback) { if (!config) { return ''; } const $promise = getPromise(settings.isJquery); const cacheName = getCacheName(config); if (isString(config) || config instanceof RegExp) { // 第一个参数为字符串或正则,callback 就是 cacheType deleteCacheDataByName(config, callback); // 删除完成都返回执行成功 $promise.resolve(); } else if (config.url) { // 配置了 url,从远程删除数据 return requestAjax(config, callback, errCallback, { type: 'DELETE' }, () => { if (config.cache) { // 远程删除成功了,本地也需清空时 deleteCacheDataByName(cacheName, config.cache); } }); } else if (config.hasOwnProperty('url')) { // 配置了url,但 url 值为空 console.trace('配置了 URL 参数,但值为空:', config); $promise.reject('配置了 URL 参数,但值为空', config); } else if (cacheName) { deleteCacheDataByName(cacheName, config.cache); $promise.resolve(); } return $promise; }, /** * 返回所有存储中的所有数据 * @param {String} cacheType 存储的类型:sessionStorage、localStorage 或 memory * @return {Object} */ getAll(cacheType) { const cacheStor = getCacheStor(cacheType); const _cache = {}; const len = cacheStor.length; let i; let item, key; for (i = 0; i < len; i++) { item = cacheStor.key(i); if (!item || 0 !== item.indexOf(settings.cachePrefix)) { continue; } key = item.replace(settings.cachePrefix, ''); try { _cache[key] = JSON.parse(cacheStor.getItem(item)); } catch (e) { _cache[key] = cacheStor.getItem(item); } } return _cache; }, /** * {@link module:dataModel.get} 的 ajax 快捷方法 * @see module:dataModel.get * @param {String} url url 地址 * @param {Object} data 要传递的参数,可省略 * @param {Function} callback 成功回调 * @param {Function} errCallback 失败回调 * @returns {Promise} */ getJSON(url, data = {}, callback, errCallback) { // data 参数可以省略 if (data instanceof Function) { errCallback = callback; callback = data; data = void 0; } return this.get({ url, data }, callback, errCallback); }, /** * {@link module:dataModel.save} 的 ajax 快捷方法 * @see module:dataModel.save * @param {String} url url 地址 * @param {Object} data 要传递的参数 * @param {Function} callback 成功回调 * @param {Function} errCallback 失败回调 * @returns {Promise} */ post(url, data, callback, errCallback) { return this.save({ url, data }, callback, errCallback); }, /** * 根据存储类型清空存储的所有数据 * @param {String} cacheType * @return {scope} this */ clear(cacheType) { deleteCacheDataByName(new RegExp('.*'), cacheType); return this; }, /** * 修改缓存数据的前缀 * @param {String} prefix 以下划线开头,由字母、数字、或下划线组成 * @param {Boolean} clear[=true] 修改前缀前,是否移除已有的数据 */ setCachePrefix(prefix, clear = true) { if (!/^_[_a-zA-Z]*_$/.test(prefix)) { console.warn('以下划线开头和结尾,由字母、数字、或下划线组成'); return this; } if (clear) { this.clear('sessionStorage'); this.clear('localStorage'); this.clear(); } settings.cachePrefix = prefix; return this; }, /** * 设置配置项 * @param {Object} setting */ setSettings(setting) { let item; for (item in setting) { if ('cachePrefix' === item) { this.setCachePrefix(setting[item], false); } else if (settings.hasOwnProperty(item)) { settings[item] = setting[item]; } } return settings; } };