flnxi-Wu/hooks/useDraftStorage.ts
WuYongJie 63ec6b7dfb fix: 修复排行榜按钮消失、tag排序和输入框浅色模式问题
1. 排行榜悬浮窗关闭后小按钮消失问题
   - 修复面板居中模式关闭时按钮位置错误
   - 保存并恢复按钮的居中状态

2. 首页tag按钮按素材数量排序
   - 添加按素材数量降序排序逻辑

3. 上传弹窗输入框浅色模式文字不可见
   - 添加 text-slate-900 确保浅色模式下文字可见
   - 添加 placeholder 样式确保占位符正确显示
2025-12-14 21:41:29 +08:00

189 lines
4.5 KiB
TypeScript

'use client'
import { useState, useEffect, useCallback, useRef } from 'react'
import {
saveDraft,
getDraft,
deleteDraft,
hasDraft,
checkConflict,
generateDraftId,
formatDraftTime,
DraftData,
DraftStorageConfig,
} from '@/lib/draft-storage'
interface UseDraftStorageOptions<T> extends DraftStorageConfig {
formType: string
entityId?: string
onRestore?: (data: T) => void
onConflict?: (otherDraft: DraftData<T>) => void
autoSave?: boolean
}
interface UseDraftStorageReturn<T> {
// 状态
draftId: string
hasDraft: boolean
lastSaved: string | null
isConflict: boolean
conflictDraft: DraftData<T> | null
// 操作
save: (data: T) => boolean
restore: () => T | null
clear: () => void
checkForConflict: () => boolean
// 格式化
formatLastSaved: () => string
}
/**
* 草稿自动保存 Hook
*/
export function useDraftStorage<T extends Record<string, unknown>>(
options: UseDraftStorageOptions<T>
): UseDraftStorageReturn<T> {
const {
formType,
entityId,
onRestore,
onConflict,
autoSave = true,
...config
} = options
// 生成草稿ID
const draftId = generateDraftId(formType, entityId)
// 状态
const [hasDraftState, setHasDraftState] = useState(false)
const [lastSaved, setLastSaved] = useState<string | null>(null)
const [isConflict, setIsConflict] = useState(false)
const [conflictDraft, setConflictDraft] = useState<DraftData<T> | null>(null)
// 自动保存定时器
const autoSaveTimerRef = useRef<NodeJS.Timeout | null>(null)
const pendingDataRef = useRef<T | null>(null)
// 检查是否存在草稿
useEffect(() => {
const exists = hasDraft(draftId, config)
setHasDraftState(exists)
if (exists) {
const draft = getDraft<T>(draftId, config)
if (draft) {
setLastSaved(draft.updatedAt)
// 检查冲突
const conflict = checkConflict<T>(draftId, config)
if (conflict.hasConflict && conflict.otherTabDraft) {
setIsConflict(true)
setConflictDraft(conflict.otherTabDraft)
onConflict?.(conflict.otherTabDraft)
}
}
}
}, [draftId])
// 保存草稿
const save = useCallback((data: T): boolean => {
const result = saveDraft(draftId, formType, data, config)
if (result.success) {
setHasDraftState(true)
setLastSaved(new Date().toISOString())
setIsConflict(false)
setConflictDraft(null)
}
return result.success
}, [draftId, formType, config])
// 恢复草稿
const restore = useCallback((): T | null => {
const draft = getDraft<T>(draftId, config)
if (draft) {
onRestore?.(draft.data)
return draft.data
}
return null
}, [draftId, config, onRestore])
// 清除草稿
const clear = useCallback(() => {
deleteDraft(draftId, config)
setHasDraftState(false)
setLastSaved(null)
setIsConflict(false)
setConflictDraft(null)
}, [draftId, config])
// 检查冲突
const checkForConflict = useCallback((): boolean => {
const conflict = checkConflict<T>(draftId, config)
setIsConflict(conflict.hasConflict)
if (conflict.hasConflict && conflict.otherTabDraft) {
setConflictDraft(conflict.otherTabDraft)
}
return conflict.hasConflict
}, [draftId, config])
// 格式化最后保存时间
const formatLastSaved = useCallback((): string => {
if (!lastSaved) return ''
return formatDraftTime(lastSaved)
}, [lastSaved])
// 自动保存逻辑
const scheduleAutoSave = useCallback((data: T) => {
if (!autoSave) return
pendingDataRef.current = data
if (autoSaveTimerRef.current) {
clearTimeout(autoSaveTimerRef.current)
}
autoSaveTimerRef.current = setTimeout(() => {
if (pendingDataRef.current) {
save(pendingDataRef.current)
pendingDataRef.current = null
}
}, config.autoSaveInterval || 3000)
}, [autoSave, save, config.autoSaveInterval])
// 清理定时器
useEffect(() => {
return () => {
if (autoSaveTimerRef.current) {
clearTimeout(autoSaveTimerRef.current)
}
}
}, [])
return {
draftId,
hasDraft: hasDraftState,
lastSaved,
isConflict,
conflictDraft,
save,
restore,
clear,
checkForConflict,
formatLastSaved,
}
}
/**
* 草稿恢复提示组件的 Props
*/
export interface DraftRecoveryPromptProps<T> {
hasDraft: boolean
lastSaved: string | null
onRestore: () => void
onDiscard: () => void
formatLastSaved: () => string
}