import { merge, each, isString, isNumber, isBoolean, pickBy, isNil, cloneDeep, isObject, } from "lodash-es"; import { axios, isCancel } from "./axios"; import { objToFormData, formDataToObj, _$Temp, setDefaultAxios, setDefaultErrMsg, setNetworkErrMsg, checkNetwork, } from "./utils"; import type { JSONObject, QueryParams, RequestData, ResponseData, ResData, AxiosConfig, MapFunction, RequestMapFunction, GetListResult, GetListFunc, HandleResponseFunc, ApiError, } from "./type"; /** * DataModel 构造函数参数接口 */ export interface DataModelOptions { /** 请求 url 替换的额外参数 */ ctx?: Record; /** GET 请求的参数 */ query?: QueryParams; /** POST 接口地址 */ createApi?: string; /** POST 接口提交前的数据处理回调 */ createMap?: MapFunction; /** GET 详情 接口地址 */ getApi?: string; /** GET 详情 接口返回后的数据处理回调 */ getMap?: MapFunction; /** GET 列表 接口地址 */ getListApi?: string; /** GET 列表 接口返回后的数据处理回调 */ getListMap?: MapFunction; /** GET 列表 接口回调,用于自定义列表请求接口 */ getListFunc?: GetListFunc; /** PUT 接口地址 */ updateApi?: string; /** PUT 接口提交前的数据处理回调 */ updateMap?: MapFunction; /** PATCH 接口地址 */ patchApi?: string; /** PATCH 接口提交前的数据处理回调 */ patchMap?: MapFunction; /** DELETE 接口地址 */ deleteApi?: string; /** DELETE 批量删除 接口地址 */ multipleDeleteApi?: string; /** GET 列表 接口请求前数据处理回调 */ getListReqMap?: RequestMapFunction; /** GET 列表 接口请求结果数据处理回调 */ getListResMap?: MapFunction; /** GET 详情 接口请求前数据处理回调 */ getReqMap?: RequestMapFunction; /** GET 详情 接口请求结果数据处理回调 */ getResMap?: MapFunction; /** POST 接口请求前数据处理回调 */ createReqMap?: RequestMapFunction; /** POST 接口请求结果数据处理回调 */ createResMap?: MapFunction; /** PUT 接口请求前数据处理回调 */ updateReqMap?: RequestMapFunction; /** PUT 接口请求结果数据处理回调 */ updateResMap?: MapFunction; /** PATCH 接口请求前数据处理回调 */ patchReqMap?: RequestMapFunction; /** PATCH 接口请求结果数据处理回调 */ patchResMap?: MapFunction; /** DELETE 接口请求前数据处理回调 */ deleteReqMap?: RequestMapFunction; /** DELETE 接口请求结果数据处理回调 */ deleteResMap?: MapFunction; /** DELETE 批量删除 接口请求前数据处理回调 */ multipleDeleteReqMap?: RequestMapFunction; /** DELETE 批量删除 接口请求结果数据处理回调 */ multipleDeleteResMap?: MapFunction; /** 统一 response 回调,用于处理定制 response 格式 */ handleResponse?: HandleResponseFunc; /** 请求的 axios 实例 */ axios?: typeof axios; /** axios 配置 */ axiosConf?: AxiosConfig; /** GET 列表接口 axios 配置 */ getListAxiosConf?: AxiosConfig; /** GET 详情接口 axios 配置 */ getAxiosConf?: AxiosConfig; /** POST 接口 axios 配置 */ createAxiosConf?: AxiosConfig; /** PUT 接口 axios 配置 */ updateAxiosConf?: AxiosConfig; /** PATCH 接口 axios 配置 */ patchAxiosConf?: AxiosConfig; /** DELETE 接口 axios 配置 */ deleteAxiosConf?: AxiosConfig; /** DELETE 批量删除接口 axios 配置 */ multipleDeleteAxiosConf?: AxiosConfig; /** 是否返回完整的接口数据(response.data) */ isResponseData?: boolean; /** 是否直接返回 response */ isResponse?: boolean; } /** * 删除请求配置接口 */ export interface DeleteConfig { /** axios delete url 参数 */ params?: JSONObject; /** axios delete data 参数 */ data?: JSONObject; /** 其他 axios delete 配置 */ [key: string]: any; } /** * 获取 API 地址的选项接口 */ export interface GetApiUrlOptions { /** 请求来源标识 */ from?: string; [key: string]: any; } /** * 错误处理选项接口 */ export interface HandleMsgOptions { /** 是否使用 response.statusText 作为兜底 msg */ useStatusText?: boolean; } /** * DataModel 类 * 用于封装常用的 CRUD 请求方法 */ class DataModel> { /** 是否返回完整的接口数据(response.data) */ isResponseData: boolean; /** 是否直接返回 response */ isResponse: boolean; /** 统一 response 回调,用于处理定制 response 格式 */ handleResponse?: HandleResponseFunc; /** 请求 url 替换的额外参数 */ ctx: Record; /** GET 请求的参数 */ query: QueryParams; /** POST 接口地址 */ createApi?: string; /** POST 接口提交前的数据处理回调 */ createMap?: MapFunction; /** GET 详情 接口地址 */ getApi?: string; /** GET 详情 接口返回后的数据处理回调 */ getMap?: MapFunction; /** GET 列表 接口地址 */ getListApi?: string; /** GET 列表 接口返回后的数据处理回调 */ getListMap?: MapFunction; /** GET 列表 接口回调,用于自定义列表请求接口 */ getListFunc?: GetListFunc; /** PUT 接口地址 */ updateApi?: string; /** PUT 接口提交前的数据处理回调 */ updateMap?: MapFunction; /** PATCH 接口地址 */ patchApi?: string; /** PATCH 接口提交前的数据处理回调 */ patchMap?: MapFunction; /** DELETE 接口地址 */ deleteApi?: string; /** DELETE 批量删除 接口地址 */ multipleDeleteApi?: string; /** GET 列表 接口请求前数据处理回调 */ getListReqMap?: RequestMapFunction; /** GET 列表 接口请求结果数据处理回调 */ getListResMap?: MapFunction; /** GET 详情 接口请求前数据处理回调 */ getReqMap?: RequestMapFunction; /** GET 详情 接口请求结果数据处理回调 */ getResMap?: MapFunction; /** POST 接口请求前数据处理回调 */ createReqMap?: RequestMapFunction; /** POST 接口请求结果数据处理回调 */ createResMap?: MapFunction; /** PUT 接口请求前数据处理回调 */ updateReqMap?: RequestMapFunction; /** PUT 接口请求结果数据处理回调 */ updateResMap?: MapFunction; /** PATCH 接口请求前数据处理回调 */ patchReqMap?: RequestMapFunction; /** PATCH 接口请求结果数据处理回调 */ patchResMap?: MapFunction; /** DELETE 接口请求前数据处理回调 */ deleteReqMap?: RequestMapFunction; /** DELETE 接口请求结果数据处理回调 */ deleteResMap?: MapFunction; /** DELETE 批量删除 接口请求前数据处理回调 */ multipleDeleteReqMap?: RequestMapFunction; /** DELETE 批量删除 接口请求结果数据处理回调 */ multipleDeleteResMap?: MapFunction; /** 请求的 axios 实例 */ axios: typeof axios; /** axios 配置 */ axiosConf: AxiosConfig; /** GET 列表接口 axios 配置 */ getListAxiosConf?: AxiosConfig; /** GET 详情接口 axios 配置 */ getAxiosConf?: AxiosConfig; /** POST 接口 axios 配置 */ createAxiosConf?: AxiosConfig; /** PUT 接口 axios 配置 */ updateAxiosConf?: AxiosConfig; /** PATCH 接口 axios 配置 */ patchAxiosConf?: AxiosConfig; /** DELETE 接口 axios 配置 */ deleteAxiosConf?: AxiosConfig; /** DELETE 批量删除接口 axios 配置 */ multipleDeleteAxiosConf?: AxiosConfig; constructor(params: DataModelOptions) { const { ctx, query, createApi, createMap, getApi, getMap, getListApi, getListMap, handleResponse, getListFunc, updateApi, updateMap, patchApi, patchMap, deleteApi, multipleDeleteApi, // 是否返回完整的接口数据(response.data) isResponseData, // 是否直接返回 response isResponse, } = params; /** 是否返回完整的接口数据(response.data) */ this.isResponseData = !!isResponseData; /** 是否直接返回 response */ this.isResponse = !!isResponse; /** 统一 response 回调,用于处理定制 response 格式 */ this.handleResponse = handleResponse; /** 请求 url 替换的额外参数 */ this.ctx = ctx || {}; /** GET 请求的参数 */ this.query = query || {}; /** POST 接口地址 */ this.createApi = createApi; /** POST 接口提交前的数据处理回调 */ this.createMap = createMap; /** GET 详情 接口地址 */ this.getApi = getApi; /** GET 详情 接口返回后的数据处理回调 */ this.getMap = getMap; /** GET 列表 接口地址 */ this.getListApi = getListApi; /** GET 列表 接口返回后的数据处理回调 */ this.getListMap = getListMap; /** GET 列表 接口回调,用于自定义列表请求接口。query => ({list: [], pagination: { total: 0 }}) 优先级高于 getListApi */ this.getListFunc = getListFunc; /** PUT 接口地址 */ this.updateApi = updateApi; /** PUT 接口提交前的数据处理回调 */ this.updateMap = updateMap; /** PATCH 接口地址 */ this.patchApi = patchApi; /** PATCH 接口提交前的数据处理回调 */ this.patchMap = patchMap; /** DELETE 接口地址 */ this.deleteApi = deleteApi; /** DELETE 批量删除 接口地址 */ this.multipleDeleteApi = multipleDeleteApi; const { getListReqMap, getListResMap, getReqMap, getResMap, createReqMap, createResMap, updateReqMap, updateResMap, patchReqMap, patchResMap, deleteReqMap, deleteResMap, multipleDeleteReqMap, multipleDeleteResMap, } = params || {}; /** GET 列表 接口请求前数据处理回调 */ this.getListReqMap = getListReqMap; /** GET 列表 接口请求结果数据处理回调 */ this.getListResMap = getListResMap; /** GET 详情 接口请求前数据处理回调 */ this.getReqMap = getReqMap; /** GET 详情 接口请求结果数据处理回调 */ this.getResMap = getResMap; /** POST 接口请求前数据处理回调 */ this.createReqMap = createReqMap; /** POST 接口请求结果数据处理回调 */ this.createResMap = createResMap; /** PUT 接口请求前数据处理回调 */ this.updateReqMap = updateReqMap; /** PUT 接口请求结果数据处理回调 */ this.updateResMap = updateResMap; /** PATCH 接口请求前数据处理回调 */ this.patchReqMap = patchReqMap; /** PATCH 接口请求结果数据处理回调 */ this.patchResMap = patchResMap; /** DELETE 接口请求前数据处理回调 */ this.deleteReqMap = deleteReqMap; /** DELETE 接口请求结果数据处理回调 */ this.deleteResMap = deleteResMap; /** DELETE 批量删除 接口请求前数据处理回调 */ this.multipleDeleteReqMap = multipleDeleteReqMap; /** DELETE 批量删除 接口请求结果数据处理回调 */ this.multipleDeleteResMap = multipleDeleteResMap; // axios 相关配置 const { axios: as, axiosConf, getListAxiosConf, getAxiosConf, createAxiosConf, updateAxiosConf, patchAxiosConf, deleteAxiosConf, multipleDeleteAxiosConf, } = params || {}; /** 请求的 axios 实例 */ this.axios = as || _$Temp.axios || axios; /** axios 配置 */ this.axiosConf = axiosConf || {}; /** GET 列表接口 axios 配置 */ this.getListAxiosConf = getListAxiosConf; /** GET 详情接口 axios 配置 */ this.getAxiosConf = getAxiosConf; /** POST 接口 axios 配置 */ this.createAxiosConf = createAxiosConf; /** PUT 接口 axios 配置 */ this.updateAxiosConf = updateAxiosConf; /** PATCH 接口 axios 配置 */ this.patchAxiosConf = patchAxiosConf; /** DELETE 接口 axios 配置 */ this.deleteAxiosConf = deleteAxiosConf; /** DELETE 批量删除接口 axios 配置 */ this.multipleDeleteAxiosConf = multipleDeleteAxiosConf; } /** * 获取请求 url 地址 * @param api 接口地址 https://a.com/test/:oId/:uId * @param record 替换参数 { uId: 81 } * @param ctx url 替换的额外参数 * @param opt 选项 * @returns 处理后的 api 地址 */ getApiUrl( api: string | undefined, record: JSONObject = {}, ctx: JSONObject = this.ctx, opt?: GetApiUrlOptions, ): string { const { from } = opt ?? {}; if (!api) { const errMsg = `${from ?? ""} api 不能为空。`; console.error(`Error ${errMsg} 具体入参:api、record、ctx、opt:`, api, record, ctx, opt); throw new Error(errMsg); } let apiUrl = api; const params = merge({}, record, ctx); each(params, (value, key) => { if (!isString(value) || !isNumber(value) || isBoolean(value)) { apiUrl = apiUrl.replace(new RegExp(`:${key}$|:${key}(?=/)`), value); } }); return apiUrl; } /** * GET 详情请求 * @param q query 参数 * @param ctx url 替换的额外参数 * @param axiosConf axios 配置 * @returns Promise */ async get(q: QueryParams = {}, ctx: JSONObject = {}, axiosConf?: AxiosConfig): Promise { let query = merge({}, this.query, q); query = pickBy(query, (val) => !isNil(val) && val !== ""); if (this.getReqMap) { query = await this.getReqMap(query); } return new Promise((resolve, reject) => { const apiUrl = this.getApiUrl(this.getApi, query, ctx, { from: "get" }); this.axios .get(apiUrl, { ...this.axiosConf, ...this.getAxiosConf, ...axiosConf, params: query, }) .then((response: ResponseData) => { this.handleRes( response, (res: ResData) => { if (this.getMap) { res = this.getMap(res); } if (this.getResMap) { res = this.getResMap(res); } resolve(res); }, reject, ); }) .catch((err) => this.errorHandler(err, reject)); }); } /** * GET 列表请求 * @param q query 参数 * @param ctx url 替换的额外参数 * @param axiosConf axios 配置 * @returns Promise> */ async getList( q: QueryParams = {}, ctx: JSONObject = {}, axiosConf?: AxiosConfig, ): Promise> { let query = merge({}, this.query, q); query = pickBy(query, (val) => !isNil(val) && val !== ""); if (this.getListReqMap) { query = await this.getListReqMap(query); } let resultList: GetListResult | null = null; if (this.getListFunc) { resultList = await this.getListFunc(query); } else { const getPro = new Promise>((resolve, reject) => { const apiUrl = this.getApiUrl(this.getListApi, q, ctx, { from: "getList" }); this.axios .get(apiUrl, { ...this.axiosConf, ...this.getListAxiosConf, ...axiosConf, params: query, }) .then((response) => { this.handleRes( response, (res: GetListResult) => { // data: { list: [], pagination: { current: 0, total: 1 } } resolve(res); }, reject, ); }) .catch((err) => this.errorHandler(err, reject)); }); resultList = await getPro; } if (this.getListMap) { resultList.list = resultList.list?.map((record) => this.getListMap(record)); } if (this.getListResMap) { resultList = this.getListResMap(resultList); } return resultList; } /** * POST 请求 * @param params 参数 * @param ctx url 替换的额外参数 * @param axiosConf axios 配置 * @returns Promise */ create(params?: RequestData | FormData, ctx?: JSONObject, axiosConf?: AxiosConfig): Promise { return new Promise(async (resolve, reject) => { const opt: AxiosConfig = { ...this.axiosConf, ...this.createAxiosConf, ...axiosConf, }; let _params = cloneDeep(formDataToObj(params)); if (this.createReqMap) { _params = await this.createReqMap(_params, params); } const apiUrl = this.getApiUrl(this.createApi, _params, ctx, { from: "create" }); if (params instanceof FormData) { opt.headers = { ...(opt.headers ?? {}), "Content-Type": "multipart/form-data" }; _params = objToFormData(_params); } this.axios .post(apiUrl, _params, opt) .then((response: ResponseData) => { this.handleRes( response, (res) => { if (this.createResMap) { res = this.createResMap(res); } resolve(res); }, reject, ); }) .catch((err) => this.errorHandler(err, reject)); }); } /** * PUT 请求 * @param params 参数 * @param ctx url 替换的额外参数 * @param axiosConf axios 配置 * @returns Promise */ update(params?: RequestData | FormData, ctx?: JSONObject, axiosConf?: AxiosConfig): Promise { return new Promise(async (resolve, reject) => { const opt: AxiosConfig = { ...this.axiosConf, ...this.updateAxiosConf, ...axiosConf }; let _params = cloneDeep(formDataToObj(params)); if (this.updateReqMap) { _params = await this.updateReqMap(_params, params); } const apiUrl = this.getApiUrl(this.updateApi, _params, ctx, { from: "update" }); if (params instanceof FormData) { opt.headers = { ...(opt.headers ?? {}), "Content-Type": "multipart/form-data" }; _params = objToFormData(_params); } this.axios .put(apiUrl, _params, opt) .then((response) => { this.handleRes( response, (res) => { if (this.updateResMap) { res = this.updateResMap(res); } resolve(res); }, reject, ); }) .catch((err) => this.errorHandler(err, reject)); }); } /** * PATCH 请求 * @param params 参数 * @param ctx url 替换的额外参数 * @param axiosConf axios 配置 * @returns Promise */ patch(params?: RequestData | FormData, ctx?: JSONObject, axiosConf?: AxiosConfig): Promise { return new Promise(async (resolve, reject) => { const opt: AxiosConfig = { ...this.axiosConf, ...this.patchAxiosConf, ...axiosConf }; let _params = cloneDeep(formDataToObj(params)); if (this.patchReqMap) { _params = await this.patchReqMap(_params, params); } const apiUrl = this.getApiUrl(this.patchApi, _params, ctx, { from: "patch" }); if (params instanceof FormData) { opt.headers = { ...(opt.headers ?? {}), "Content-Type": "multipart/form-data" }; _params = objToFormData(_params); } this.axios .patch(apiUrl, _params, opt) .then((response) => { this.handleRes( response, (res) => { if (this.patchResMap) { res = this.patchResMap(res); } resolve(res); }, reject, ); }) .catch((err) => this.errorHandler(err, reject)); }); } /** * 删除接口 * @param config axios.delete config 参数 * @param config.params axios.delete url 参数 * @param config.data axios.delete data 参数 * @param ctx url 替换的额外参数 * @returns Promise */ async delete(config?: DeleteConfig, ctx?: JSONObject): Promise { let _config = cloneDeep(config) || {}; if (this.deleteReqMap) { _config = await this.deleteReqMap(_config); } return new Promise((resolve, reject) => { const apiUrl = this.getApiUrl( this.deleteApi, Object.assign(cloneDeep(_config), _config.params, _config.data), ctx, { from: "delete" }, ); this.axios .delete(apiUrl, { ...this.axiosConf, ...this.deleteAxiosConf, ..._config }) .then((response) => { this.handleRes( response, (res) => { if (this.deleteResMap) { res = this.deleteResMap(res); } resolve(res); }, reject, ); }) .catch((err) => this.errorHandler(err, reject)); }); } /** * 批量删除接口 * @param config axios.delete config 参数 * @param config.params axios.delete url 参数 * @param config.data axios.delete data 参数 * @param ctx url 替换的额外参数 * @returns Promise */ async multipleDelete(config?: DeleteConfig, ctx?: JSONObject): Promise { let _config = cloneDeep(config) || {}; if (this.multipleDeleteReqMap) { _config = await this.multipleDeleteReqMap(_config); } return new Promise((resolve, reject) => { const apiUrl = this.getApiUrl( this.multipleDeleteApi, Object.assign(cloneDeep(_config), _config.params, _config.data), ctx, { from: "multipleDelete" }, ); this.axios .delete(apiUrl, { ...this.axiosConf, ...this.multipleDeleteAxiosConf, ..._config }) .then((response) => { this.handleRes( response, (res: ResponseData) => { if (this.multipleDeleteResMap) { res = this.multipleDeleteResMap(res); } resolve(res); }, reject, ); }) .catch((err) => this.errorHandler(err, reject)); }); } /** * 处理响应结果 * @param response axios 响应对象 * @param resolve 成功回调 * @param reject 失败回调 */ handleRes(response: ResponseData, resolve: (res: any) => void, reject: (err: ApiError) => void) { if (isCancel(response)) { console.info("errorHandler isCancel", response); return; } if (this.isResponse) { resolve(response); return; } if (this.isResponseData) { resolve(response?.data ?? response); return; } if (typeof response !== "object") { reject(new Error("response not object") as ApiError); return; } let _res: ResponseData | ResData; _res = this.handleResponse ? this.handleResponse(response) : response; const isAxiosResponse = (obj: any): obj is ResponseData => { return obj && "status" in obj && "headers" in obj && "request" in obj; }; if (isAxiosResponse(_res)) { _res = _res.data; } const { code, data } = _res as ResData; const message = this.handleMsg(response); if (code == 200) { const _data = data ?? {}; if (isObject(_data)) { if (Array.isArray(_data.content) && _data.pageNumber) { _data.list = _data.content; _data.pagination = { current: _data.pageNumber, total: _data.total }; } // 前缀 _ 避免与 data 里已有的 message 冲突 _data._msg = message; _data._message = message; } resolve(_data); } else { const error = new Error(message) as ApiError; error.code = code; error.response = response; error._msg = message; error._message = message; reject(error); } } /** * 错误处理器 * @param err 错误对象 * @param reject Promise reject 回调 */ errorHandler(err: any = {}, reject: (err: ApiError) => void) { if (isCancel(err)) { console.info("errorHandler isCancel", err); return; } if (this.isResponse) { reject(err); return; } if (this.isResponseData) { reject(err?.data ?? err); return; } const response = err?.response || err; if (response) { const message = this.handleMsg(response, { useStatusText: true }); const error = new Error(message) as ApiError; error.code = response.status; error.response = response; if (message) { // 前缀 _ 避免与 data 里已有的 message 冲突 error._message = message; error._msg = message; } return reject(error); } return reject(err ?? { _msg: _$Temp.networkErrMsg }); } /** * 处理错误消息 * @param response 响应对象 * @param opt 选项 * @returns 错误消息 */ handleMsg( response: any, opt: HandleMsgOptions = { // 是否使用 response.statusText 作为兜底 msg useStatusText: false, }, ): string { const { useStatusText } = opt || {}; let message = (response.data && (response.data.message || response.data.msg)) || response.msg || response.message || this.getNetworkErrMsg(response); if (!message && useStatusText) { message = response.statusText; } return message || _$Temp.defaultErrMsg; } /** * 获取网络错误消息 * @param httpCode HTTP 状态码 * @returns 错误消息 */ getNetworkErrMsg(httpCode: string): string { const { networkErrMsg } = _$Temp; if (httpCode === "ERR_NETWORK") { return networkErrMsg; } return checkNetwork() ? "" : networkErrMsg; } } export { axios, checkNetwork, DataModel, formDataToObj, objToFormData, setDefaultAxios, setDefaultErrMsg, setNetworkErrMsg, }; export default DataModel;