Download vetcopilot-app/src/hooks/useAutoSave.ts from WWsCa/VetNet-LCPS: direct link, hf CLI and curl.
- Browser
- Download file 4.19 kB
-
https://huggingface.co/WWsCa/VetNet-LCPS/resolve/main/vetcopilot-app/src/hooks/useAutoSave.ts
- Command line
-
hf download hf://WWsCa/VetNet-LCPS/vetcopilot-app/src/hooks/useAutoSave.ts
-
curl -L -o useAutoSave.ts https://huggingface.co/WWsCa/VetNet-LCPS/resolve/main/vetcopilot-app/src/hooks/useAutoSave.ts
4.19 kB
| /** | |
| * useAutoSave Hook | |
| * | |
| * 表单内容变化后自动保存草稿到 localStorage。 | |
| * 使用 debounce 避免频繁写入,提供保存状态指示器。 | |
| * | |
| * 用法: | |
| * const { isSaving, lastSaved, hasUnsavedChanges, saveNow } = useAutoSave({ | |
| * key: 'consultation_draft', | |
| * data: formData, | |
| * debounceMs: 3000, | |
| * onSave: async (data) => { await api.saveDraft(data) }, | |
| * }) | |
| * | |
| * // 显示保存状态 | |
| * {isSaving && '正在保存...'} | |
| * {lastSaved && !isSaving && `上次保存:${lastSaved}`} | |
| */ | |
| import { useState, useEffect, useCallback, useRef } from 'react' | |
| // ================================================================ | |
| // 类型定义 | |
| // ================================================================ | |
| interface UseAutoSaveOptions<T> { | |
| /** localStorage 存储键 */ | |
| key: string | |
| /** 需要自动保存的数据 */ | |
| data: T | |
| /** 防抖延迟(ms),默认 2000 */ | |
| debounceMs?: number | |
| /** 自定义保存函数(如保存到后端),返回 true 表示成功 */ | |
| onSave?: (data: T) => Promise<boolean> | |
| /** 是否启用,默认 true */ | |
| enabled?: boolean | |
| } | |
| interface UseAutoSaveReturn<T> { | |
| /** 是否正在保存 */ | |
| isSaving: boolean | |
| /** 上次成功保存的时间字符串 */ | |
| lastSaved: string | null | |
| /** 是否有未保存的更改(自上次保存后) */ | |
| hasUnsavedChanges: boolean | |
| /** 手动触发保存 */ | |
| saveNow: () => Promise<void> | |
| /** 清除已保存的草稿 */ | |
| clearDraft: () => void | |
| /** 从 localStorage 恢复草稿数据 */ | |
| restoreDraft: () => T | null | |
| } | |
| // ================================================================ | |
| // Hook | |
| // ================================================================ | |
| const DRAFT_PREFIX = 'vetcopilot_autosave_' | |
| export function useAutoSave<T>(options: UseAutoSaveOptions<T>): UseAutoSaveReturn<T> { | |
| const { key, data, debounceMs = 2000, onSave, enabled = true } = options | |
| const [isSaving, setIsSaving] = useState(false) | |
| const [lastSaved, setLastSaved] = useState<string | null>(null) | |
| const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false) | |
| const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null) | |
| const storageKey = `${DRAFT_PREFIX}${key}` | |
| const dataRef = useRef(data) | |
| dataRef.current = data | |
| const doSave = useCallback(async () => { | |
| setIsSaving(true) | |
| try { | |
| const currentData = dataRef.current | |
| // 保存到 localStorage | |
| localStorage.setItem( | |
| storageKey, | |
| JSON.stringify({ | |
| data: currentData, | |
| timestamp: Date.now(), | |
| }) | |
| ) | |
| // 调用自定义保存函数 | |
| if (onSave) { | |
| const success = await onSave(currentData) | |
| if (!success) throw new Error('Save failed') | |
| } | |
| const now = new Date().toLocaleTimeString() | |
| setLastSaved(now) | |
| setHasUnsavedChanges(false) | |
| } catch { | |
| // 保存失败时不清除 hasUnsavedChanges | |
| } finally { | |
| setIsSaving(false) | |
| } | |
| }, [storageKey, onSave]) | |
| // 数据变化时标记未保存,并启动防抖定时器 | |
| useEffect(() => { | |
| if (!enabled) return | |
| setHasUnsavedChanges(true) | |
| if (timerRef.current) { | |
| clearTimeout(timerRef.current) | |
| } | |
| timerRef.current = setTimeout(() => { | |
| doSave() | |
| }, debounceMs) | |
| return () => { | |
| if (timerRef.current) { | |
| clearTimeout(timerRef.current) | |
| } | |
| } | |
| }, [data, debounceMs, enabled, doSave]) | |
| const saveNow = useCallback(async () => { | |
| if (timerRef.current) { | |
| clearTimeout(timerRef.current) | |
| timerRef.current = null | |
| } | |
| await doSave() | |
| }, [doSave]) | |
| const clearDraft = useCallback(() => { | |
| try { | |
| localStorage.removeItem(storageKey) | |
| } catch { | |
| // ignore | |
| } | |
| setHasUnsavedChanges(false) | |
| }, [storageKey]) | |
| const restoreDraft = useCallback((): T | null => { | |
| try { | |
| const raw = localStorage.getItem(storageKey) | |
| if (!raw) return null | |
| const parsed = JSON.parse(raw) | |
| return parsed.data as T | |
| } catch { | |
| return null | |
| } | |
| }, [storageKey]) | |
| return { | |
| isSaving, | |
| lastSaved, | |
| hasUnsavedChanges, | |
| saveNow, | |
| clearDraft, | |
| restoreDraft, | |
| } | |
| } | |