Files
his/healthlink-his-ui/src/utils/request.js

297 lines
12 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import axios from 'axios'
import {ElLoading, ElMessage, ElMessageBox, ElNotification} from 'element-plus'
import {getToken} from '@/utils/auth'
import errorCode, {errorCodeEn, errorCodeVi} from '@/utils/errorCode'
import {blobValidate, tansParams} from '@/utils/his'
import cache from '@/plugins/cache'
import {saveAs} from 'file-saver'
import useUserStore from '@/store/modules/user'
import JSONBig from 'json-bigint'
import {autoTranslate} from '@/i18n/autoTranslate'
import Cookies from 'js-cookie'
// 初始化json-bigint配置大数字转字符串关键storeAsString: true
const jsonBig = JSONBig({ storeAsString: true })
// 🔧 Bug Fix #281: 转换所有ID字段为字符串防止BigInt精度丢失
const convertIdsToString = (obj) => {
if (obj === null || obj === undefined) return obj
if (typeof obj === 'number' && obj > 9007199254740991) {
// 如果是超过安全范围的数字,转为字符串
return String(obj)
}
if (typeof obj === 'object') {
if (Array.isArray(obj)) {
return obj.map(item => convertIdsToString(item))
} else {
const newObj = {}
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const value = obj[key]
// 如果key以Id结尾或者是id且值是数字转为字符串
if ((key === 'id' || key.endsWith('Id') || key.endsWith('ID')) && typeof value === 'number') {
newObj[key] = String(value)
} else {
newObj[key] = convertIdsToString(value)
}
}
}
return newObj
}
}
return obj
}
let downloadLoadingInstance;
// 是否显示重新登录
export let isRelogin = { show: false };
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 从环境变量读取租户ID如果没有则使用默认值'1'
axios.defaults.headers['X-Tenant-ID'] = import.meta.env.VITE_APP_TENANT_ID || '1'
axios.defaults.headers['Request-Method-Name'] = 'login'
// 创建axios实例
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 120000, // 增加到120秒配合后端超时设置
// 新增:重写响应解析逻辑,大数字自动转字符串(移到这里!)
transformResponse: [
function (data) {
if (!data) return {}
// 如果是 Blob 或 ArrayBuffer直接返回不进行 JSON 解析
if (data instanceof Blob || data instanceof ArrayBuffer) {
return data
}
try {
return jsonBig.parse(data)
} catch (err) {
return JSON.parse(data)
}
}
],
// 可选请求体序列化使用json-bigint处理大数字
transformRequest: [
function (data) {
if (!data) return data
// 🔧 Bug Fix #281: 使用json-bigint序列化保留大数字精度
return jsonBig.stringify(data)
}
]
})
// request拦截器
service.interceptors.request.use(config => {
// 🔧 Bug Fix #281: 转换请求数据中的ID字段为字符串
if (config.data && typeof config.data === 'object') {
config.data = convertIdsToString(config.data)
}
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false
// 是否需要防止数据重复提交
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
if (getToken() && !isToken) {
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
// get请求映射params参数
if (config.method === 'get' && config.params) {
let url = config.url + '?' + tansParams(config.params);
url = url.slice(0, -1);
config.params = {};
config.url = url;
}
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
const requestObj = {
url: config.url,
data: typeof config.data === 'object' ? jsonBig.stringify(config.data) : config.data,
time: new Date().getTime()
}
// 🔧 Bug Fix #281: 使用json-bigint计算大小
const requestSize = Object.keys(jsonBig.stringify(requestObj)).length; // 请求数据大小
const limitSize = 5 * 1024 * 1024; // 限制存放数据5M
if (requestSize >= limitSize) {
console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制无法进行防重复提交验证。')
return config;
}
const sessionObj = cache.session.getJSON('sessionObj')
if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
cache.session.setJSON('sessionObj', requestObj)
} else {
const s_url = sessionObj.url; // 请求地址
const s_data = sessionObj.data; // 请求数据
const s_time = sessionObj.time; // 请求时间
const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
const message = '数据正在处理,请勿重复提交';
console.warn(`[${s_url}]: ` + message)
return Promise.reject(new Error(message))
} else {
cache.session.setJSON('sessionObj', requestObj)
}
}
}
return config
}, error => {
console.log(error)
Promise.reject(error)
})
// 响应拦截器
service.interceptors.response.use(res => {
// 未设置状态码则默认成功状态
const code = res.data.code || 200;
// 获取错误信息(支持多语言)
const lang = Cookies.get('lang') || localStorage.getItem('lang') || 'zh-CN'
const errorDict = lang === 'en' ? errorCodeEn : lang === 'vi' ? errorCodeVi : errorCode
let msg = errorDict[code] || res.data.msg || errorDict['default']
// 后端返回的中文错误消息自动翻译
if (lang !== 'zh-CN' && /[\u4e00-\u9fff]/.test(msg)) {
msg = autoTranslate(msg)
}
// 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
return res.data
}
if (code === 401) {
if (!isRelogin.show) {
isRelogin.show = true;
// 判断是否在登录页面
const isLoginPage = window.location.pathname === '/login' || window.location.pathname === '/';
if (isLoginPage) {
// 登录页面直接清理token不弹窗
useUserStore().logOut().then(() => {
isRelogin.show = false;
}).catch(() => {
isRelogin.show = false;
});
return Promise.reject(lang === 'zh-CN' ? '登录已过期,请重新登录。' : lang === 'en' ? 'Session expired, please log in again.' : 'Phiên hết hạn, vui lòng đăng nhập lại.')
}
// 其他页面:显示提示后自动跳转
const expiredMsg = lang === 'zh-CN' ? '登录已过期,正在跳转到登录页面...' : lang === 'en' ? 'Session expired, redirecting to login...' : 'Phiên hết hạn, đang chuyển đến trang đăng nhập...'
ElMessage.warning(expiredMsg);
useUserStore().logOut().then(() => {
isRelogin.show = false;
// 跳转到登录页保留当前路径作为redirect参数
const currentPath = window.location.pathname;
const redirectUrl = currentPath !== '/login' ? `/login?redirect=${encodeURIComponent(currentPath)}` : '/login';
location.href = redirectUrl;
}).catch(() => {
isRelogin.show = false;
location.href = '/login';
});
}
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
} else if (code === 500) {
// 检查是否需要跳过错误提示(静默请求:返回响应让.then()处理)
if (res.config?.skipErrorMsg) {
return Promise.resolve(res.data)
}
ElMessage({ message: msg, type: 'error' })
return Promise.reject(new Error(msg))
} else if (code === 601) {
// 检查是否需要跳过错误提示(静默请求:返回响应让.then()处理)
if (res.config?.skipErrorMsg) {
return Promise.resolve(res.data)
}
ElMessage({ message: msg, type: 'warning' })
return Promise.reject(new Error(msg))
} else if (code !== 200) {
// 检查是否需要跳过错误提示(静默请求:返回响应让.then()处理)
if (res.config?.skipErrorMsg) {
return Promise.resolve(res.data)
}
ElNotification.error({ title: msg })
return Promise.reject('error')
} else {
return Promise.resolve(res.data)
}
},
error => {
console.log('err' + error)
const lang = Cookies.get('lang') || localStorage.getItem('lang') || 'zh-CN'
let { message } = error;
if (message == "Network Error") {
message = lang === 'zh-CN' ? '后端接口连接异常' : lang === 'en' ? 'Backend API connection error' : 'Lỗi kết nối API backend';
} else if (message.includes("timeout")) {
message = lang === 'zh-CN' ? '系统接口请求超时' : lang === 'en' ? 'API request timeout' : 'Yêu cầu API hết thời gian';
} else if (message.includes("Request failed with status code")) {
const code = message.substr(message.length - 3)
message = lang === 'zh-CN' ? '系统接口' + code + '异常' : lang === 'en' ? 'API error ' + code : 'Lỗi API ' + code;
}
// 检查是否需要跳过错误提示
if (!error.config?.skipErrorMsg) {
ElMessage({ message: message, type: 'error', duration: 5 * 1000 })
}
return Promise.reject(error)
})
// 通用下载方法
export function download(url, params, filename, config) {
const lang = Cookies.get('lang') || localStorage.getItem('lang') || 'zh-CN'
const dlMsg = lang === 'zh-CN' ? '正在下载数据,请稍候' : lang === 'en' ? 'Downloading data, please wait' : 'Đang tải dữ liệu, vui lòng đợi'
downloadLoadingInstance = ElLoading.service({ text: dlMsg, background: "rgba(0, 0, 0, 0.7)", })
return service.post(url, params, {
transformRequest: [(params) => { return tansParams(params) }],
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
responseType: 'blob',
...config
}).then(async (data) => {
const isBlob = blobValidate(data);
if (isBlob) {
const blob = new Blob([data])
saveAs(blob, filename)
} else {
const resText = await data.text();
const rspObj = JSON.parse(resText);
const errorDict = lang === 'en' ? errorCodeEn : lang === 'vi' ? errorCodeVi : errorCode
let errMsg = errorDict[rspObj.code] || rspObj.msg || errorDict['default']
if (lang !== 'zh-CN' && /[\u4e00-\u9fff]/.test(errMsg)) errMsg = autoTranslate(errMsg)
ElMessage.error(errMsg);
}
downloadLoadingInstance.close();
}).catch((r) => {
console.error(r)
const dlErr = lang === 'zh-CN' ? '下载文件出现错误,请联系管理员!' : lang === 'en' ? 'Download error, please contact administrator!' : 'Lỗi tải xuống, vui lòng liên hệ quản trị viên!'
ElMessage.error(dlErr)
downloadLoadingInstance.close();
})
}
// 添加GET方式下载方法
export function downloadGet(url, params, filename, config) {
const lang2 = Cookies.get('lang') || localStorage.getItem('lang') || 'zh-CN'
const dlMsg2 = lang2 === 'zh-CN' ? '正在下载数据,请稍候' : lang2 === 'en' ? 'Downloading data, please wait' : 'Đang tải dữ liệu, vui lòng đợi'
downloadLoadingInstance = ElLoading.service({ text: dlMsg2, background: "rgba(0, 0, 0, 0.7)", })
return service.get(url, {
params: params,
responseType: 'blob',
...config
}).then(async (data) => {
const isBlob = blobValidate(data);
if (isBlob) {
const blob = new Blob([data])
saveAs(blob, filename)
} else {
const resText = await data.text();
const rspObj = JSON.parse(resText);
const errorDict2 = lang2 === 'en' ? errorCodeEn : lang2 === 'vi' ? errorCodeVi : errorCode
let errMsg = errorDict2[rspObj.code] || rspObj.msg || errorDict2['default']
if (lang2 !== 'zh-CN' && /[\u4e00-\u9fff]/.test(errMsg)) errMsg = autoTranslate(errMsg)
ElMessage.error(errMsg);
}
downloadLoadingInstance.close();
}).catch((r) => {
console.error(r)
const dlErr2 = lang2 === 'zh-CN' ? '下载文件出现错误,请联系管理员!' : lang2 === 'en' ? 'Download error, please contact administrator!' : 'Lỗi tải xuống, vui lòng liên hệ quản trị viên!'
ElMessage.error(dlErr2)
downloadLoadingInstance.close();
})
}
export default service