Files
his/openhis-ui-vue3/src/utils/request.js
zhaoyun fe2a79773f fix: 修复Bug#440用户管理修改提交报错hasOwnProperty
Vue 3 reactive proxy对象不支持直接调用hasOwnProperty方法
使用Object.prototype.hasOwnProperty.call替代,解决'hasOwnProperty is not a function'报错
2026-04-24 15:00:38 +08:00

273 lines
9.9 KiB
JavaScript
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 from '@/utils/errorCode'
import {blobValidate, tansParams} from '@/utils/openhis'
import cache from '@/plugins/cache'
import {saveAs} from 'file-saver'
import useUserStore from '@/store/modules/user'
import JSONBig from 'json-bigint'
// 初始化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 msg = errorCode[code] || res.data.msg || errorCode['default']
// 二进制数据则直接返回
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('登录已过期,请重新登录。')
}
// 其他页面:显示提示后自动跳转
ElMessage.warning('登录已过期,正在跳转到登录页面...');
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) {
// 检查是否需要跳过错误提示
if (!res.config?.skipErrorMsg) {
ElMessage({ message: msg, type: 'error' })
}
return Promise.reject(new Error(msg))
} else if (code === 601) {
// 检查是否需要跳过错误提示
if (!res.config?.skipErrorMsg) {
ElMessage({ message: msg, type: 'warning' })
}
return Promise.reject(new Error(msg))
} else if (code !== 200) {
// 检查是否需要跳过错误提示
if (!res.config?.skipErrorMsg) {
ElNotification.error({ title: msg })
}
return Promise.reject('error')
} else {
return Promise.resolve(res.data)
}
},
error => {
console.log('err' + error)
let { message } = error;
if (message == "Network Error") {
message = "后端接口连接异常";
} else if (message.includes("timeout")) {
message = "系统接口请求超时";
} else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
// 检查是否需要跳过错误提示
if (!error.config?.skipErrorMsg) {
ElMessage({ message: message, type: 'error', duration: 5 * 1000 })
}
return Promise.reject(error)
})
// 通用下载方法
export function download(url, params, filename, config) {
downloadLoadingInstance = ElLoading.service({ text: "正在下载数据,请稍候", 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 errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
ElMessage.error(errMsg);
}
downloadLoadingInstance.close();
}).catch((r) => {
console.error(r)
ElMessage.error('下载文件出现错误,请联系管理员!')
downloadLoadingInstance.close();
})
}
// 添加GET方式下载方法
export function downloadGet(url, params, filename, config) {
downloadLoadingInstance = ElLoading.service({ text: "正在下载数据,请稍候", 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 errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
ElMessage.error(errMsg);
}
downloadLoadingInstance.close();
}).catch((r) => {
console.error(r)
ElMessage.error('下载文件出现错误,请联系管理员!')
downloadLoadingInstance.close();
})
}
export default service