material_manage_system/frontend/src/api/index.js

140 lines
5.1 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 { ElMessage } from 'element-plus'
import router from '../router'
import { hasChinese, responseErrorText } from '../utils/errorText'
import {
captureOrganizationContext,
currentReadSignal,
getOrganizationId,
isCurrentOrganizationContext,
OrganizationContextSupersededError
} from '../utils/organizationContext'
const request = axios.create({
baseURL: '/api',
timeout: 30000,
headers: {
'Content-Type': 'application/json'
}
})
// 不需要公司上下文的接口白名单:认证、用户/组织/角色管理。
// 其余业务接口一律带 X-Organization-ID;websocket-ticket 属于组织级,不在白名单内。
const ORGANIZATION_EXEMPT_PATHS = [
'/auth/login',
'/auth/logout',
'/auth/me',
'/auth/organizations',
'/auth/organization-context',
'/organizations',
'/roles',
'/permissions',
'/users'
]
function isOrganizationExempt(url) {
const path = (url || '').split('?')[0]
return ORGANIZATION_EXEMPT_PATHS.some(exempt => path === exempt || path.startsWith(`${exempt}/`))
}
// 单次请求可通过 config.silent 关闭全局错误提示。取票等后台辅助请求失败时,
// 不应让用户看到与自己操作无关的报错;默认不静默(未显式置 true 即照旧提示)。
// 注意:silent 只抑制“业务/连接类”提示,不影响 401 的登录态全局处理。
function isSilent(config) {
return config?.silent === true
}
// Request interceptor - add auth token
request.interceptors.request.use(
(config) => {
const token = localStorage.getItem('wmms_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
if (!isOrganizationExempt(config.url)) {
const organizationId = getOrganizationId()
if (organizationId) {
config.headers['X-Organization-ID'] = String(organizationId)
}
// 记下发起时的上下文快照,响应回来时用它判断是否已被切换作废。
config.__organizationContext = captureOrganizationContext()
// 读请求挂上共享信号:切换公司时统一 abort,尚未返回的旧读请求被取消。
const method = (config.method || 'get').toLowerCase()
if (!config.signal && method === 'get') {
config.signal = currentReadSignal()
}
}
console.log('API请求:', config.method?.toUpperCase(), config.url)
return config
},
(error) => Promise.reject(error)
)
// Response interceptor - handle errors and 401
request.interceptors.response.use(
(response) => {
// 响应属于已被切换掉的旧公司:丢弃且不提示,页面只应关心新公司的数据。
const context = response.config?.__organizationContext
if (context && !isCurrentOrganizationContext(context)) {
return Promise.reject(new OrganizationContextSupersededError())
}
const res = response.data
if (res.code === 0) {
return res
}
if (!isSilent(response.config)) {
ElMessage.error(hasChinese(res.message) ? res.message : '请求失败')
}
return Promise.reject(new Error(res.message))
},
(error) => {
// 主动取消(切换公司)不是失败,静默丢弃,不要弹提示。
if (axios.isCancel(error)) {
return Promise.reject(error)
}
// 旧公司的延迟错误同样不得影响新页面,也不能弹提示。
const context = error.config?.__organizationContext
if (context && !isCurrentOrganizationContext(context)) {
return Promise.reject(new OrganizationContextSupersededError())
}
const silent = isSilent(error.config)
if (error.response?.status === 401) {
// 登录态失效的全局处理保持原样:清令牌并跳登录页。这与请求是否为 silent 无关,
// silent 只抑制“业务/连接类”错误提示。
localStorage.removeItem('wmms_token')
localStorage.removeItem('wmms_user')
// 登录页自己会把失败原因写在表单里,再弹一句"登录已过期"只会让人困惑
if (router.currentRoute.value.path !== '/login') {
router.push('/login')
ElMessage.error('登录已过期,请重新登录')
}
} else if (error.response?.status === 422) {
// Validation errors - parse and show friendly message
const data = error.response?.data
let msg = data?.message || ''
if (data?.error && typeof data.error === 'string') {
// Go validator errors format: "Key: 'Field' Error:Field validation ..."
const matches = data.error.match(/Error: (Field validation.*?)(\n|$)/gi)
if (matches) {
const fields = matches.map(m => {
const fieldMatch = m.match(/Key: '([^']+)'/)
return fieldMatch ? fieldMatch[1] : m
})
const labels = { Handler: '经手人', Supplier: '供应商', Receiver: '接收方' }
msg = fields.map(f => labels[f] || f).join('、') + '不能为空'
}
}
if (!silent) {
ElMessage.error(hasChinese(msg) ? msg : '请检查必填字段')
}
} else {
if (!silent) {
ElMessage.error(responseErrorText(error))
}
}
return Promise.reject(error)
}
)
export default request