import { ref, reactive, computed, onMounted, onUnmounted, nextTick, readonly } from 'vue' import { useWindowSize } from '@vueuse/core' import { useTableColumns } from './useTableColumns' import { TableCache, CacheInvalidationStrategy } from '../../utils/table/tableCache' import { defaultResponseAdapter, extractTableData, updatePaginationFromResponse, createSmartDebounce, createErrorHandler } from '../../utils/table/tableUtils' import { tableConfig } from '../../utils/table/tableConfig' function useTable(config) { return useTableImpl(config) } function useTableImpl(config) { const { core: { apiFn, apiParams = {}, excludeParams = [], immediate = true, columnsFactory, paginationKey }, transform: { dataTransformer, responseAdapter = defaultResponseAdapter } = {}, performance: { enableCache = false, cacheTime = 5 * 60 * 1e3, debounceTime = 300, maxCacheSize = 50 } = {}, hooks: { onSuccess, onError, onCacheHit, resetFormCallback } = {}, debug: { enableLog = false } = {} } = config const pageKey = paginationKey?.current || tableConfig.paginationKey.current const sizeKey = paginationKey?.size || tableConfig.paginationKey.size const cacheUpdateTrigger = ref(0) const logger = { log: (message, ...args) => { if (enableLog) { console.log(`[useTable] ${message}`, ...args) } }, warn: (message, ...args) => { if (enableLog) { console.warn(`[useTable] ${message}`, ...args) } }, error: (message, ...args) => { if (enableLog) { console.error(`[useTable] ${message}`, ...args) } } } const cache = enableCache ? new TableCache(cacheTime, maxCacheSize, enableLog) : null const loadingState = ref('idle') const loading = computed(() => loadingState.value === 'loading') const error = ref(null) const data = ref([]) let abortController = null let cacheCleanupTimer = null const searchParams = reactive( Object.assign( { [pageKey]: 1, [sizeKey]: 10 }, apiParams || {} ) ) const pagination = reactive({ current: searchParams[pageKey] || 1, size: searchParams[sizeKey] || 10, total: 0 }) const { width } = useWindowSize() const mobilePagination = computed(() => ({ ...pagination, small: width.value < 768 })) const columnConfig = columnsFactory ? useTableColumns(columnsFactory) : null const columns = columnConfig?.columns const columnChecks = columnConfig?.columnChecks const hasData = computed(() => data.value.length > 0) const cacheInfo = computed(() => { void cacheUpdateTrigger.value if (!cache) return { total: 0, size: '0KB', hitRate: '0 avg hits' } return cache.getStats() }) const handleError = createErrorHandler(onError, enableLog) const clearCache = (strategy, context) => { if (!cache) return let clearedCount = 0 switch (strategy) { case CacheInvalidationStrategy.CLEAR_ALL: cache.clear() logger.log(`清空所有缓存 - ${context || ''}`) break case CacheInvalidationStrategy.CLEAR_CURRENT: clearedCount = cache.clearCurrentSearch(searchParams) logger.log(`清空当前搜索缓存 ${clearedCount} 条 - ${context || ''}`) break case CacheInvalidationStrategy.CLEAR_PAGINATION: clearedCount = cache.clearPagination() logger.log(`清空分页缓存 ${clearedCount} 条 - ${context || ''}`) break case CacheInvalidationStrategy.KEEP_ALL: default: logger.log(`保持缓存不变 - ${context || ''}`) break } cacheUpdateTrigger.value++ } const fetchData = async (params, useCache = enableCache) => { if (abortController) { abortController.abort() } const currentController = new AbortController() abortController = currentController loadingState.value = 'loading' error.value = null try { let requestParams = Object.assign( {}, searchParams, { [pageKey]: pagination.current, [sizeKey]: pagination.size }, params || {} ) if (excludeParams.length > 0) { const filteredParams = { ...requestParams } excludeParams.forEach((key) => { delete filteredParams[key] }) requestParams = filteredParams } if (useCache && cache) { const cachedItem = cache.get(requestParams) if (cachedItem) { data.value = cachedItem.data updatePaginationFromResponse(pagination, cachedItem.response) const paramsRecord2 = searchParams if (paramsRecord2[pageKey] !== pagination.current) { paramsRecord2[pageKey] = pagination.current } if (paramsRecord2[sizeKey] !== pagination.size) { paramsRecord2[sizeKey] = pagination.size } loadingState.value = 'success' if (onCacheHit) { onCacheHit(cachedItem.data, cachedItem.response) } logger.log(`缓存命中`) return cachedItem.response } } const response = await apiFn(requestParams) if (currentController.signal.aborted) { throw new Error('请求已取消') } const standardResponse = responseAdapter(response) let tableData = extractTableData(standardResponse) if (dataTransformer) { tableData = dataTransformer(tableData) } data.value = tableData updatePaginationFromResponse(pagination, standardResponse) const paramsRecord = searchParams if (paramsRecord[pageKey] !== pagination.current) { paramsRecord[pageKey] = pagination.current } if (paramsRecord[sizeKey] !== pagination.size) { paramsRecord[sizeKey] = pagination.size } if (useCache && cache) { cache.set(requestParams, tableData, standardResponse) cacheUpdateTrigger.value++ logger.log(`数据已缓存`) } loadingState.value = 'success' if (onSuccess) { onSuccess(tableData, standardResponse) } return standardResponse } catch (err) { if (err instanceof Error && err.message === '请求已取消') { loadingState.value = 'idle' return { records: [], total: 0, current: 1, size: 10 } } loadingState.value = 'error' data.value = [] const tableError = handleError(err, '获取表格数据失败') throw tableError } finally { if (abortController === currentController) { abortController = null } } } const getData = async (params) => { try { return await fetchData(params) } catch { return Promise.resolve() } } const getDataByPage = async (params) => { pagination.current = 1 searchParams[pageKey] = 1 clearCache(CacheInvalidationStrategy.CLEAR_CURRENT, '搜索数据') try { return await fetchData(params, false) } catch { return Promise.resolve() } } const debouncedGetDataByPage = createSmartDebounce(getDataByPage, debounceTime) const resetSearchParams = async () => { debouncedGetDataByPage.cancel() const paramsRecord = searchParams const defaultPagination = { [pageKey]: 1, [sizeKey]: paramsRecord[sizeKey] || 10 } Object.keys(searchParams).forEach((key) => { delete paramsRecord[key] }) Object.assign(searchParams, apiParams || {}, defaultPagination) pagination.current = 1 pagination.size = defaultPagination[sizeKey] error.value = null clearCache(CacheInvalidationStrategy.CLEAR_ALL, '重置搜索') await getData() if (resetFormCallback) { await nextTick() resetFormCallback() } } const replaceSearchParams = (params) => { const paramsRecord = searchParams const currentSize = pagination.size || (paramsRecord[sizeKey] ?? 10) Object.keys(searchParams).forEach((key) => { if (key !== pageKey && key !== sizeKey) { delete paramsRecord[key] } }) Object.assign( searchParams, { [pageKey]: 1, [sizeKey]: currentSize }, params || {} ) pagination.current = 1 pagination.size = currentSize } let isCurrentChanging = false const handleSizeChange = async (newSize) => { if (newSize <= 0) return debouncedGetDataByPage.cancel() const paramsRecord = searchParams pagination.size = newSize pagination.current = 1 paramsRecord[sizeKey] = newSize paramsRecord[pageKey] = 1 clearCache(CacheInvalidationStrategy.CLEAR_CURRENT, '分页大小变化') await getData() } const handleCurrentChange = async (newCurrent) => { if (newCurrent <= 0) return if (isCurrentChanging) { return } if (pagination.current === newCurrent) { logger.log('分页页码未变化,跳过请求') return } try { isCurrentChanging = true const paramsRecord = searchParams pagination.current = newCurrent if (paramsRecord[pageKey] !== newCurrent) { paramsRecord[pageKey] = newCurrent } await getData() } finally { isCurrentChanging = false } } const refreshCreate = async () => { debouncedGetDataByPage.cancel() pagination.current = 1 searchParams[pageKey] = 1 clearCache(CacheInvalidationStrategy.CLEAR_PAGINATION, '新增数据') await getData() } const refreshUpdate = async () => { clearCache(CacheInvalidationStrategy.CLEAR_CURRENT, '编辑数据') await getData() } const refreshRemove = async () => { const { current } = pagination clearCache(CacheInvalidationStrategy.CLEAR_CURRENT, '删除数据') await getData() if (data.value.length === 0 && current > 1) { pagination.current = current - 1 searchParams[pageKey] = current - 1 await getData() } } const refreshData = async () => { debouncedGetDataByPage.cancel() clearCache(CacheInvalidationStrategy.CLEAR_ALL, '手动刷新') await getData() } const refreshSoft = async () => { clearCache(CacheInvalidationStrategy.CLEAR_CURRENT, '软刷新') await getData() } const cancelRequest = () => { if (abortController) { abortController.abort() } debouncedGetDataByPage.cancel() } const clearData = () => { data.value = [] error.value = null clearCache(CacheInvalidationStrategy.CLEAR_ALL, '清空数据') } const clearExpiredCache = () => { if (!cache) return 0 const cleanedCount = cache.cleanupExpired() if (cleanedCount > 0) { cacheUpdateTrigger.value++ } return cleanedCount } if (enableCache && cache) { cacheCleanupTimer = setInterval(() => { const cleanedCount = cache.cleanupExpired() if (cleanedCount > 0) { logger.log(`自动清理 ${cleanedCount} 条过期缓存`) cacheUpdateTrigger.value++ } }, cacheTime / 2) } if (immediate) { onMounted(async () => { await getData() }) } onUnmounted(() => { cancelRequest() if (cache) { cache.clear() } if (cacheCleanupTimer) { clearInterval(cacheCleanupTimer) } }) return { // 数据相关 /** 表格数据 */ data, /** 数据加载状态 */ loading: readonly(loading), /** 错误状态 */ error: readonly(error), /** 数据是否为空 */ isEmpty: computed(() => data.value.length === 0), /** 是否有数据 */ hasData, // 分页相关 /** 分页状态信息 */ pagination: readonly(pagination), /** 移动端分页配置 */ paginationMobile: mobilePagination, /** 页面大小变化处理 */ handleSizeChange, /** 当前页变化处理 */ handleCurrentChange, // 搜索相关 - 统一前缀 /** 搜索参数 */ searchParams, /** 替换搜索参数(适用于表单查询,避免旧字段残留) */ replaceSearchParams, /** 重置搜索参数 */ resetSearchParams, // 数据操作 - 更明确的操作意图 /** 加载数据 */ fetchData: getData, /** 获取数据 */ getData: getDataByPage, /** 获取数据(防抖) */ getDataDebounced: debouncedGetDataByPage, /** 清空数据 */ clearData, // 刷新策略 /** 全量刷新:清空所有缓存,重新获取数据(适用于手动刷新按钮) */ refreshData, /** 轻量刷新:仅清空当前搜索条件的缓存,保持分页状态(适用于定时刷新) */ refreshSoft, /** 新增后刷新:回到第一页并清空分页缓存(适用于新增数据后) */ refreshCreate, /** 更新后刷新:保持当前页,仅清空当前搜索缓存(适用于更新数据后) */ refreshUpdate, /** 删除后刷新:智能处理页码,避免空页面(适用于删除数据后) */ refreshRemove, // 缓存控制 /** 缓存统计信息 */ cacheInfo, /** 清除缓存,根据不同的业务场景选择性地清理缓存: */ clearCache, // 支持4种清理策略 // clearCache(CacheInvalidationStrategy.CLEAR_ALL, '手动刷新') // 清空所有缓存 // clearCache(CacheInvalidationStrategy.CLEAR_CURRENT, '搜索数据') // 只清空当前搜索条件的缓存 // clearCache(CacheInvalidationStrategy.CLEAR_PAGINATION, '新增数据') // 清空分页相关缓存 // clearCache(CacheInvalidationStrategy.KEEP_ALL, '保持缓存') // 不清理任何缓存 /** 清理已过期的缓存条目,释放内存空间 */ clearExpiredCache, // 请求控制 /** 取消当前请求 */ cancelRequest, // 列配置 (如果提供了 columnsFactory) ...(columnConfig && { /** 表格列配置 */ columns, /** 列显示控制 */ columnChecks, /** 新增列 */ addColumn: columnConfig.addColumn, /** 删除列 */ removeColumn: columnConfig.removeColumn, /** 切换列显示状态 */ toggleColumn: columnConfig.toggleColumn, /** 更新列配置 */ updateColumn: columnConfig.updateColumn, /** 批量更新列配置 */ batchUpdateColumns: columnConfig.batchUpdateColumns, /** 重新排序列 */ reorderColumns: columnConfig.reorderColumns, /** 获取指定列配置 */ getColumnConfig: columnConfig.getColumnConfig, /** 获取所有列配置 */ getAllColumns: columnConfig.getAllColumns, /** 重置所有列配置到默认状态 */ resetColumns: columnConfig.resetColumns }) } } import { CacheInvalidationStrategy as CacheInvalidationStrategy2 } from '../../utils/table/tableCache' export { CacheInvalidationStrategy2 as CacheInvalidationStrategy, useTable }