初始化

This commit is contained in:
2026-03-03 09:53:54 +08:00
commit 3f349a35a4
437 changed files with 65639 additions and 0 deletions

View File

@@ -0,0 +1,297 @@
/**
* 表格工具函数模块
*
* 提供表格数据处理和请求管理的核心工具函数
*
* ## 主要功能
*
* - 多格式 API 响应自动适配和标准化
* - 表格数据提取和转换
* - 分页信息自动更新和校验
* - 智能防抖函数(支持取消和立即执行)
* - 统一的错误处理机制
* - 嵌套数据结构解析
*
* ## 使用场景
*
* - useTable 组合式函数的底层工具
* - 适配各种后端接口响应格式
* - 表格数据的标准化处理
* - 请求防抖和性能优化
* - 错误统一处理和日志记录
*
* ## 支持的响应格式
*
* 1. 直接数组: [item1, item2, ...]
* 2. 标准对象: { records: [], total: 100 }
* 3. 嵌套data: { data: { list: [], total: 100 } }
* 4. 多种字段名: list/data/records/items/result/rows
*
* ## 核心功能
*
* - defaultResponseAdapter: 智能识别和转换响应格式
* - extractTableData: 提取表格数据数组
* - updatePaginationFromResponse: 更新分页信息
* - createSmartDebounce: 创建可控的防抖函数
* - createErrorHandler: 生成错误处理器
*
* @module utils/table/tableUtils
* @author Art Design Pro Team
*/
import type { ApiResponse } from './tableCache'
import { tableConfig } from './tableConfig'
// 请求参数基础接口,扩展分页参数
export interface BaseRequestParams extends Api.Common.PaginationParams {
[key: string]: unknown
}
// 错误处理接口
export interface TableError {
code: string
message: string
details?: unknown
}
// 辅助函数:从对象中提取记录数组
function extractRecords<T>(obj: Record<string, unknown>, fields: string[]): T[] {
for (const field of fields) {
if (field in obj && Array.isArray(obj[field])) {
return obj[field] as T[]
}
}
return []
}
// 辅助函数:从对象中提取总数
function extractTotal(obj: Record<string, unknown>, records: unknown[], fields: string[]): number {
for (const field of fields) {
if (field in obj && typeof obj[field] === 'number') {
return obj[field] as number
}
}
return records.length
}
// 辅助函数:提取分页参数
function extractPagination(
obj: Record<string, unknown>,
data?: Record<string, unknown>
): Pick<ApiResponse<unknown>, 'current' | 'size'> | undefined {
const result: Partial<Pick<ApiResponse<unknown>, 'current' | 'size'>> = {}
const sources = [obj, data ?? {}]
const currentFields = tableConfig.currentFields
for (const src of sources) {
for (const field of currentFields) {
if (field in src && typeof src[field] === 'number') {
result.current = src[field] as number
break
}
}
if (result.current !== undefined) break
}
const sizeFields = tableConfig.sizeFields
for (const src of sources) {
for (const field of sizeFields) {
if (field in src && typeof src[field] === 'number') {
result.size = src[field] as number
break
}
}
if (result.size !== undefined) break
}
if (result.current === undefined && result.size === undefined) return undefined
return result
}
/**
* 默认响应适配器 - 支持多种常见的API响应格式
*/
export const defaultResponseAdapter = <T>(response: unknown): ApiResponse<T> => {
// 定义支持的字段
const recordFields = tableConfig.recordFields
if (!response) {
return { records: [], total: 0 }
}
if (Array.isArray(response)) {
return { records: response, total: response.length }
}
if (typeof response !== 'object') {
console.warn(
'[tableUtils] 无法识别的响应格式,支持的格式包括: 数组、包含' +
recordFields.join('/') +
'字段的对象、嵌套data对象。当前格式:',
response
)
return { records: [], total: 0 }
}
const res = response as Record<string, unknown>
let records: T[] = []
let total = 0
let pagination: Pick<ApiResponse<unknown>, 'current' | 'size'> | undefined
// 处理标准格式或直接列表
records = extractRecords(res, recordFields)
total = extractTotal(res, records, tableConfig.totalFields)
pagination = extractPagination(res)
// 如果没有找到检查嵌套data
if (records.length === 0 && 'data' in res && typeof res.data === 'object') {
const data = res.data as Record<string, unknown>
records = extractRecords(data, ['list', 'records', 'items'])
total = extractTotal(data, records, tableConfig.totalFields)
pagination = extractPagination(res, data)
if (Array.isArray(res.data)) {
records = res.data as T[]
total = records.length
}
}
if (!recordFields.some((field) => field in res) && records.length === 0) {
console.warn('[tableUtils] 无法识别的响应格式')
console.warn('支持的字段包括: ' + recordFields.join('、'), response)
console.warn('扩展字段请到 utils/table/tableConfig 文件配置')
}
const result: ApiResponse<T> = { records, total }
if (pagination) {
Object.assign(result, pagination)
}
return result
}
/**
* 从标准化的API响应中提取表格数据
*/
export const extractTableData = <T>(response: ApiResponse<T>): T[] => {
const data = response.records || response.data || []
return Array.isArray(data) ? data : []
}
/**
* 根据API响应更新分页信息
*/
export const updatePaginationFromResponse = <T>(
pagination: Api.Common.PaginationParams,
response: ApiResponse<T>
): void => {
pagination.total = response.total ?? pagination.total ?? 0
if (response.current !== undefined) {
pagination.current = response.current
}
const maxPage = Math.max(1, Math.ceil(pagination.total / (pagination.size || 1)))
if (pagination.current > maxPage) {
pagination.current = maxPage
}
}
/**
* 创建智能防抖函数 - 支持取消和立即执行
*/
export const createSmartDebounce = <T extends (...args: any[]) => Promise<any>>(
fn: T,
delay: number
): T & { cancel: () => void; flush: () => Promise<any> } => {
let timeoutId: NodeJS.Timeout | null = null
let lastArgs: Parameters<T> | null = null
let lastResolve: ((value: any) => void) | null = null
let lastReject: ((reason: any) => void) | null = null
const debouncedFn = (...args: Parameters<T>): Promise<any> => {
return new Promise((resolve, reject) => {
if (timeoutId) clearTimeout(timeoutId)
lastArgs = args
lastResolve = resolve
lastReject = reject
timeoutId = setTimeout(async () => {
try {
const result = await fn(...args)
resolve(result)
} catch (error) {
reject(error)
} finally {
timeoutId = null
lastArgs = null
lastResolve = null
lastReject = null
}
}, delay)
})
}
debouncedFn.cancel = () => {
if (timeoutId) clearTimeout(timeoutId)
timeoutId = null
lastArgs = null
lastResolve = null
lastReject = null
}
debouncedFn.flush = async () => {
if (timeoutId && lastArgs && lastResolve && lastReject) {
clearTimeout(timeoutId)
timeoutId = null
const args = lastArgs
const resolve = lastResolve
const reject = lastReject
lastArgs = null
lastResolve = null
lastReject = null
try {
const result = await fn(...args)
resolve(result)
return result
} catch (error) {
reject(error)
throw error
}
}
return Promise.resolve()
}
return debouncedFn as any
}
/**
* 生成错误处理函数
*/
export const createErrorHandler = (
onError?: (error: TableError) => void,
enableLog: boolean = false
) => {
const logger = {
error: (message: string, ...args: any[]) => {
if (enableLog) console.error(`[useTable] ${message}`, ...args)
}
}
return (err: unknown, context: string): TableError => {
const tableError: TableError = {
code: 'UNKNOWN_ERROR',
message: '未知错误',
details: err
}
if (err instanceof Error) {
tableError.message = err.message
tableError.code = err.name
} else if (typeof err === 'string') {
tableError.message = err
}
logger.error(`${context}:`, err)
onError?.(tableError)
return tableError
}
}