fix: id兼容, 通知入口
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 5m0s

This commit is contained in:
luo
2026-07-28 15:19:29 +08:00
parent 0e8a11430a
commit 261f5f2853
21 changed files with 271 additions and 71 deletions

58
src/utils/business/id.ts Normal file
View File

@@ -0,0 +1,58 @@
/**
* 兼容后端实体 ID 字段的大小写差异。
* 后端历史接口可能返回 `ID`,新接口返回 `id`,前端统一优先使用小写 id。
*/
export type CompatibleId = number | string
export interface CompatibleIdFields {
id?: CompatibleId | null
ID?: CompatibleId | null
}
/** 从实体中读取兼容的 ID 字段。0 也是有效 ID,不能使用 ||。 */
export const getCompatibleId = (value: CompatibleIdFields | null | undefined) => {
const id = value?.id ?? value?.ID
return id === null || id === undefined || id === '' ? undefined : id
}
/** 读取并转换为数字 ID,供只接受 number 的接口参数使用。 */
export const getCompatibleNumericId = (
value: CompatibleIdFields | null | undefined
): number | undefined => {
const id = getCompatibleId(value)
if (id === undefined) return undefined
const numericId = Number(id)
return Number.isFinite(numericId) ? numericId : undefined
}
const isPlainObject = (value: unknown): value is Record<string, unknown> => {
if (value === null || typeof value !== 'object') return false
const prototype = Object.getPrototypeOf(value)
return prototype === Object.prototype || prototype === null
}
/** 递归补齐响应中的 id/ID 别名,不影响 Blob、FormData、Date 等特殊数据。 */
export const syncCompatibleIdFields = <T>(value: T): T => {
if (Array.isArray(value)) {
value.forEach((item) => syncCompatibleIdFields(item))
return value
}
if (!isPlainObject(value)) return value
const record = value as Record<string, unknown>
const hasId = Object.prototype.hasOwnProperty.call(record, 'id')
const hasUpperId = Object.prototype.hasOwnProperty.call(record, 'ID')
if (hasId || hasUpperId) {
const id = record.id ?? record.ID
if (id !== null && id !== undefined && id !== '') {
if (!hasId) record.id = id
if (!hasUpperId) record.ID = id
}
}
Object.values(record).forEach((item) => syncCompatibleIdFields(item))
return value
}

View File

@@ -9,3 +9,4 @@ export * from './voucher'
export * from './apiRateLimit'
export * from './approvalSummary'
export * from './expiryEstimate'
export * from './id'

View File

@@ -4,6 +4,7 @@ import { useUserStore } from '@/store/modules/user'
import { ApiStatus } from './status'
import type { RequestOptions, ErrorMessageMode } from '@/types/api'
import { normalizeApiError } from '@/utils/business/apiError'
import { syncCompatibleIdFields } from '@/utils/business/id'
const axiosInstance = axios.create({
timeout: 15000, // 请求超时时间(毫秒)
@@ -90,6 +91,9 @@ const processQueue = (error: any, token: string | null = null) => {
// 响应拦截器
axiosInstance.interceptors.response.use(
(response: AxiosResponse) => {
// 兼容历史接口返回的 ID 与新接口返回的 id。
syncCompatibleIdFields(response.data)
// 401 未授权 - 尝试刷新 token
if (response.data.code === ApiStatus.unauthorized) {
const userStore = useUserStore()