Skip to content

数据 接口&联调

开发环境

如果前端应用和后端接口服务器没有运行在同一个主机上,你需要在开发环境下将接口请求代理到接口服务器。

如果是同一个主机,可以直接请求具体的接口地址。

配置

开发环境时候,接口地址在项目根目录下

.env.development 文件配置

Terminal window
# vite 服务端接口服务主机地址
VITE_GLOB_BASE_URL=https://apigw-dev.trinablue.com/csde-nem-api/v1.0/test

生产环境

生产环境接口地址在项目根目录下 .env.production 文件配置。

生产环境接口地址值需要修改 VITE_GLOB_BASE_URL,如果出现跨域问题,可以使用 nginx 或者后台开启 cors 进行处理

接口请求

在 v3-admin-template 中:

  1. 页面交互操作;
  2. 调用统一管理的 api 请求函数;
  3. 使用封装的 axios.ts 发送请求;
  4. 获取服务端返回数据
  5. 更新 data;

接口统一存放于 src/http/ 下面管理

以登陆接口为例:

在 src/api/ 内新建模块文件

import $http from '@/utils/axios';
// 登录
export const postLoginSmsAPI = (data) => {
return $http.post('/login/nem', { data });
};

axios 配置

axios 请求封装存放于 src/utils/axios 文件夹内部

除 index.js 文件内容需要根据项目自行修改外,其余文件无需修改

├── Axios.ts // axios实例
├── axiosCancel.ts // axiosCancel实例,取消重复请求
├── checkStatus.js // 返回状态值校验
├── index.js // 接口返回统一处理

index.ts 配置说明

const axios = new VAxios({
// 认证方案,例如: Bearer
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes
authenticationScheme: '',
// 接口超时时间 单位毫秒
timeout: 10 * 1000,
headers: { 'Content-Type': ContentTypeEnum.JSON },
// 数据处理方式,见下方说明
transform: clone(transform),
// 配置项,下面的选项都可以在独立的接口请求中覆盖
requestOptions: {
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
isReturnRaw: false,
// post请求的时候添加参数到url
joinParamsToUrl: false,
// 格式化提交参数时间
formatDate: true,
// 消息提示类型
errorMessageMode: 'message',
// 接口地址
urlPrefix: import.meta.env.VITE_GLOB_API_PREFIX,
// 是否加入时间戳
joinTime: false,
// 忽略重复请求
ignoreCancelToken: true,
// 是否携带token
withToken: true,
// 返回信息是否采用当地语言
withLocale: true,
},
});

transform 数据处理说明

类型定义,见 axiosTransform.ts 文件

export abstract class AxiosTransform {
/**
* @description: 请求之前处理配置
*/
beforeRequestHook?: (config: AxiosRequestConfig, options: RequestOptions) => AxiosRequestConfig;
/**
* @description: 请求成功处理
*/
transformRequestData?: (res: AxiosResponse<Result>, options: RequestOptions) => any;
/**
* @description: 请求失败处理
*/
requestCatch?: (e: Error) => Promise<any>;
/**
* @description: 请求之前的拦截器
*/
requestInterceptors?: (config: AxiosRequestConfig) => AxiosRequestConfig;
/**
* @description: 请求之后的拦截器
*/
responseInterceptors?: (res: AxiosResponse<any>) => AxiosResponse<any>;
/**
* @description: 请求之前的拦截器错误处理
*/
requestInterceptorsCatch?: (error: Error) => void;
/**
* @description: 请求之后的拦截器错误处理
*/
responseInterceptorsCatch?: (error: Error) => void;
}

项目默认 transform 处理逻辑,可以根据各自项目进行处理。一般需要更改的部分为下方代码,见代码注释说明

/**
* @description: 数据处理,方便区分多种处理方式
*/
const transform = {
// 请求前处理配置
beforeRequestHook: (config, options) => {
const disposeGetMothod = (params, joinTime) => {
if (isString(params)) {
// 兼容restful风格传参
config.url = config.url + params + `${joinTimestamp(joinTime, true)}`;
config.params = undefined;
} else {
// 非restful风格传参
config.params = Object.assign(
params || {},
joinTimestamp(joinTime, false)
);
}
};
const disposeNonGetMothod = (
params,
data,
{ formatDate, joinParamsToUrl }
) => {
if (isString(params)) {
// 兼容restful风格传参
config.url = config.url + params;
config.params = undefined;
} else {
if (formatDate) {
data && !isString(data) && formatRequestDate(data);
}
if (
Reflect.has(config, 'data') &&
config.data &&
Object.keys(config.data).length > 0
) {
config.data = data;
config.params = params;
} else {
// 非GET请求如果没有提供data,则将params视为data
config.data = params;
config.params = undefined;
}
if (joinParamsToUrl) {
config.url = setObjToUrlParams(
config.url,
Object.assign({}, config.params, config.data)
);
}
}
};
const { urlPrefix, joinTime = true, formatDate, joinParamsToUrl } = options;
if (urlPrefix) {
config.url = `${urlPrefix}${config.url}`;
}
const params = config.params || {};
const data = config.data || false;
if (config.method?.toUpperCase() === RequestEnum.GET) {
disposeGetMothod(params, joinTime);
} else {
disposeNonGetMothod(params, data, { formatDate, joinParamsToUrl });
}
return config;
},
// 请求拦截器处理
requestInterceptors: (config, options) => {
// 请求之前处理config
const userStore = useUserStoreWithOut();
const localeStore = useLocaleStoreWithOut();
const token = userStore.getToken;
if (token && config?.requestOptions?.withToken !== false) {
if (/getResourceTree/.test(config.url)) {
config.headers.Authorization = `Bearer ${token}`;
} else {
// jwt token
config.headers.Authorization = options.authenticationScheme
? `${options.authenticationScheme} ${token}`
: token;
}
}
const locale = localeStore.getLocale;
if (config?.requestOptions?.withLocale && locale) {
config.headers.LocaleLang = locale;
}
return config;
},
// 响应拦截器处理
responseInterceptors: (res) => res,
// 响应拦截器错误处理
responseInterceptorsCatch: (error) => {
const { t } = useI18n();
const { response, code, message, config } = error || {};
const errorMessageMode = config?.requestOptions?.errorMessageMode || 'none';
const msg = response?.data?.error?.message ?? '';
const err = error?.toString?.() ?? '';
let errMessage = '';
try {
if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) {
errMessage = t('sys.api.apiTimeoutMessage');
}
if (err?.includes('Network Error')) {
errMessage = t('sys.api.networkExceptionMsg');
}
if (errMessage) {
if (errorMessageMode === 'modal') {
createErrorModal({
title: t('sys.api.errorTip'),
content: errMessage,
});
} else if (errorMessageMode === 'message') {
createMessage.error(errMessage);
}
return Promise.reject(error);
}
} catch (error) {
throw new Error(error);
}
checkStatus(error?.response?.status, msg, errorMessageMode);
return Promise.reject(error);
},
/**
* @param {*} res
* @param {*} options
* @returns
*/
transformRequestHook: (res, options) => {
const { t } = useI18n();
const { isReturnRaw } = options;
if (isReturnRaw) {
return res;
}
if (res.data.bizcode) {
const { data: axiosData } = res;
if (!axiosData) {
throw new Error(t('sys.api.apiRequestFailed'));
}
const { bizcode, data, msg, bizmsg, code, errMsg } = axiosData;
const bizSuccess =
Reflect.has(axiosData, 'bizcode') && bizcode === ResultEnum.SUCCESS;
if (bizSuccess) {
return data;
}
// bizcode非10000的情况
let errorMsg = '';
const userStore = useUserStoreWithOut();
switch (bizcode) {
case ResultEnum.UNAUTHORIZED:
errorMsg = t('sys.api.errMsg401');
// eslint-disable-next-line no-case-declarations
userStore.setToken(undefined);
userStore.logout(true);
break;
case ResultEnum.TOKENINVALID:
errorMsg = t('sys.api.errMsg402');
userStore.setToken(undefined);
userStore.logout(true);
break;
default:
if (msg) {
errorMsg = msg;
} else if (bizmsg) {
errorMsg = bizmsg;
} else if (errMsg) {
errorMsg = errMsg;
}
break;
}
// errorMessageMode=‘modal’的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误
// errorMessageMode='none' 一般是调用时明确表示不希望自动弹出错误提示
if (options.errorMessageMode === 'modal') {
createErrorModal({ title: t('sys.api.errorTip'), content: errorMsg });
} else if (options.errorMessageMode === 'message') {
createMessage.error(errorMsg);
}
throw new Error(errorMsg || t('sys.api.apiRequestFailed'));
} else {
return res.data;
}
},
};

更改参数格式

项目接口默认为 Json 参数格式,即 headers: { 'Content-Type': ContentTypeEnum.JSON },

如果需要更改为 form-data 格式,更改 headers 的 'Content-Type 为 ContentTypeEnum.FORM_URLENCODED 即可

多个接口地址

当项目中需要用到多个接口地址时, 可以在 src/utils/axios/index.js 导出多个 axios 实例

// 目前只导出一个默认实例,接口地址对应的是环境变量中的 VITE_GLOB_BASE_URL 接口地址
const $http = createAxios()
export default $http
// 需要有其他接口地址的可以在后面添加
// other api url
export const otherHttp = createAxios({
requestOptions: {
apiUrl: 'xxx',
},
});

或者在 src/api/ 模块文件内通过环境变量

import $http from '@/utils/axios';
const VITE_GLOB_SMS_SERVER = import.meta.env.VITE_GLOB_SMS_SERVER
export const getCompanies4TfsAPI = () => {
return $http.get(`${VITE_GLOB_SMS_SERVER}/open/getCompanies4Tfs`)
}

删除请求 URL 携带的时间戳参数

如果不需要 url 上面默认携带的时间戳参数 ?_t=xxx

const axios = new VAxios({
requestOptions: {
// 是否加入时间戳
joinTime: false,
},
});