File size: 4,187 Bytes
793e953
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/**
 * 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,
  }
}