1. 排行榜悬浮窗关闭后小按钮消失问题 - 修复面板居中模式关闭时按钮位置错误 - 保存并恢复按钮的居中状态 2. 首页tag按钮按素材数量排序 - 添加按素材数量降序排序逻辑 3. 上传弹窗输入框浅色模式文字不可见 - 添加 text-slate-900 确保浅色模式下文字可见 - 添加 placeholder 样式确保占位符正确显示
163 lines
3.7 KiB
TypeScript
163 lines
3.7 KiB
TypeScript
/**
|
||
* useHomeFilterFields Hook
|
||
*
|
||
* 首页筛选字段数据获取Hook,优化了以下功能:
|
||
* - 使用SWR进行数据缓存和自动重新验证
|
||
* - 100ms内UI更新(无闪烁)
|
||
* - 使用防抖技术避免重复请求
|
||
* - 支持手动刷新
|
||
* - 错误处理和重试
|
||
*
|
||
* @created 2024-12-13
|
||
*/
|
||
|
||
import { useCallback, useMemo } from 'react'
|
||
import useSWR from 'swr'
|
||
|
||
export interface FilterOption {
|
||
label: string
|
||
value: string
|
||
count?: number
|
||
}
|
||
|
||
export interface FilterField {
|
||
key: string
|
||
label: string
|
||
type?: string
|
||
options: FilterOption[]
|
||
}
|
||
|
||
interface HomeFilterFieldsResponse {
|
||
fields: FilterField[]
|
||
}
|
||
|
||
const CACHE_KEY = '/api/home-filter-fields'
|
||
|
||
// SWR fetcher
|
||
const fetcher = async (url: string): Promise<HomeFilterFieldsResponse> => {
|
||
const response = await fetch(url, { cache: 'no-store' })
|
||
if (!response.ok) {
|
||
throw new Error('Failed to fetch filter fields')
|
||
}
|
||
return response.json()
|
||
}
|
||
|
||
interface UseHomeFilterFieldsOptions {
|
||
/** 是否启用自动刷新 */
|
||
refreshInterval?: number
|
||
/** 是否在窗口聚焦时重新验证 */
|
||
revalidateOnFocus?: boolean
|
||
/** 是否在重新连接时重新验证 */
|
||
revalidateOnReconnect?: boolean
|
||
}
|
||
|
||
interface UseHomeFilterFieldsReturn {
|
||
/** 筛选字段列表 */
|
||
fields: FilterField[]
|
||
/** 字段映射(按key索引) */
|
||
fieldsMap: Record<string, FilterField>
|
||
/** 是否正在加载 */
|
||
isLoading: boolean
|
||
/** 是否正在验证(后台刷新) */
|
||
isValidating: boolean
|
||
/** 错误信息 */
|
||
error: Error | undefined
|
||
/** 手动刷新 */
|
||
refresh: () => Promise<void>
|
||
/** 数据是否已准备好 */
|
||
isReady: boolean
|
||
}
|
||
|
||
/**
|
||
* 首页筛选字段数据获取Hook
|
||
*/
|
||
export function useHomeFilterFields(
|
||
options: UseHomeFilterFieldsOptions = {}
|
||
): UseHomeFilterFieldsReturn {
|
||
const {
|
||
refreshInterval = 0,
|
||
revalidateOnFocus = false,
|
||
revalidateOnReconnect = true,
|
||
} = options
|
||
|
||
const { data, error, isLoading, isValidating, mutate } = useSWR<HomeFilterFieldsResponse>(
|
||
CACHE_KEY,
|
||
fetcher,
|
||
{
|
||
refreshInterval,
|
||
revalidateOnFocus,
|
||
revalidateOnReconnect,
|
||
// 保持之前的数据,避免闪烁
|
||
keepPreviousData: true,
|
||
// 错误重试
|
||
errorRetryCount: 3,
|
||
errorRetryInterval: 1000,
|
||
// 去重请求
|
||
dedupingInterval: 2000,
|
||
}
|
||
)
|
||
|
||
const fields = useMemo(() => {
|
||
if (!data?.fields) return []
|
||
// 规范化选项数据
|
||
return data.fields.map(field => ({
|
||
...field,
|
||
options: Array.isArray(field.options)
|
||
? field.options.map(opt => {
|
||
if (typeof opt === 'string') {
|
||
return { label: opt, value: opt }
|
||
}
|
||
return {
|
||
label: opt.label || opt.value,
|
||
value: opt.value,
|
||
count: typeof opt.count === 'number' ? opt.count : undefined,
|
||
}
|
||
}).filter(opt => opt.value)
|
||
: [],
|
||
}))
|
||
}, [data])
|
||
|
||
const fieldsMap = useMemo(() => {
|
||
const map: Record<string, FilterField> = {}
|
||
fields.forEach(field => {
|
||
map[field.key] = field
|
||
})
|
||
return map
|
||
}, [fields])
|
||
|
||
const refresh = useCallback(async () => {
|
||
await mutate()
|
||
}, [mutate])
|
||
|
||
return {
|
||
fields,
|
||
fieldsMap,
|
||
isLoading,
|
||
isValidating,
|
||
error,
|
||
refresh,
|
||
isReady: !isLoading && !error,
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取特定字段的选项
|
||
*/
|
||
export function useFilterFieldOptions(fieldKey: string): {
|
||
options: FilterOption[]
|
||
label: string
|
||
isLoading: boolean
|
||
} {
|
||
const { fieldsMap, isLoading } = useHomeFilterFields()
|
||
|
||
const field = fieldsMap[fieldKey]
|
||
|
||
return {
|
||
options: field?.options ?? [],
|
||
label: field?.label ?? fieldKey,
|
||
isLoading,
|
||
}
|
||
}
|
||
|
||
export default useHomeFilterFields
|