useCheckinDraft.js
5.65 KB
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
/**
* 打卡草稿缓存逻辑封装
* @module useCheckinDraft
* @description
* 提供打卡草稿的增删改查能力,基于 localStorage。
*
* 核心功能:
* 1. 构建唯一的缓存 Key(用户+任务+日期+状态维度)。
* 2. 保存草稿:自动过滤未完成的附件,只存 meta_id/url 等元数据。
* 3. 读取草稿:支持过期检查(TTL 7天)。
* 4. 自动清理:支持惰性清理和全量扫描清理过期项。
*
* 使用场景:
* - CheckinDetailPage 自动保存与恢复。
*/
import { useRoute } from 'vue-router'
export function useCheckinDraft() {
const DRAFT_PREFIX = 'CHECKIN_DRAFT_V1'
const TTL_MS = 7 * 24 * 60 * 60 * 1000 // 7天过期
/**
* 判断功能是否开启
* @returns {boolean}
*/
const is_enabled = () => {
// 支持 URL 参数覆盖 (CheckinDetailPage 会用到 route,但此处为纯函数逻辑,依赖调用方传入或在组件内判断)
// 这里仅读取环境变量
// 实际使用时建议结合 route.query.enable_draft 判断
return import.meta.env.VITE_CHECKIN_DRAFT_CACHE === '1'
}
/**
* 构建缓存 Key
* @param {Object} context
* @param {string} context.user_id 用户ID
* @param {string} context.task_id 任务ID
* @param {string} context.date 日期
* @param {string} context.task_type 任务类型
* @param {string} context.status 状态 (create/edit)
* @returns {string} Key
*/
const build_key = ({ user_id, task_id, date, task_type, status }) => {
// 确保所有字段都存在,避免生成 undefined
const parts = [
user_id || 'guest',
task_id || 'default',
date || 'nodate',
task_type || 'default',
status || 'create'
]
return `${DRAFT_PREFIX}:${parts.join(':')}`
}
/**
* 保存草稿
* @param {string} key 缓存Key
* @param {Object} payload 业务数据
* @param {string} payload.message 文本
* @param {Array} payload.file_list 文件列表
* @param {string} payload.active_type 当前类型
* @param {string|number} payload.subtask_id 子任务ID
* @param {Array} payload.selected_task_value 选中的任务值
* @param {Object} payload.count 计数打卡数据
*/
const save_draft = (key, payload) => {
if (!key) return
try {
// 过滤附件:只保存已上传成功且有 meta_id 的
const validFiles = (payload.file_list || []).filter(item =>
item.status === 'done' && item.meta_id
).map(item => ({
meta_id: item.meta_id,
url: item.url,
name: item.name,
file_type: item.file_type
}))
const data = {
version: 1,
saved_at: Date.now(),
expires_at: Date.now() + TTL_MS,
payload: {
...payload,
file_list: validFiles
}
}
localStorage.setItem(key, JSON.stringify(data))
console.log(`[草稿缓存] 已保存: ${key}`, data.payload)
} catch (e) {
console.error('[草稿缓存] 保存失败:', e)
}
}
/**
* 读取草稿
* @param {string} key 缓存Key
* @returns {Object|null} 草稿数据或null
*/
const read_draft = (key) => {
if (!key) return null
try {
const raw = localStorage.getItem(key)
if (!raw) return null
const data = JSON.parse(raw)
// 检查版本
if (data.version !== 1) {
console.warn('[草稿缓存] 版本不匹配,丢弃旧数据')
clear_draft(key)
return null
}
// 检查过期
if (data.expires_at && Date.now() > data.expires_at) {
console.log('[草稿缓存] 数据已过期,自动清理')
clear_draft(key)
return null
}
return data
} catch (e) {
console.error('[草稿缓存] 读取失败:', e)
return null
}
}
/**
* 清除指定草稿
* @param {string} key
*/
const clear_draft = (key) => {
if (!key) return
localStorage.removeItem(key)
console.log(`[草稿缓存] 已清除: ${key}`)
}
/**
* 清理所有过期草稿
*/
const cleanup_expired = () => {
try {
const keysToRemove = []
const now = Date.now()
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i)
if (key && key.startsWith(DRAFT_PREFIX)) {
try {
const raw = localStorage.getItem(key)
const data = JSON.parse(raw)
if (data.expires_at && now > data.expires_at) {
keysToRemove.push(key)
}
} catch (e) {
// 解析失败的脏数据也清理掉
keysToRemove.push(key)
}
}
}
keysToRemove.forEach(key => {
localStorage.removeItem(key)
console.log(`[草稿缓存] 自动清理过期项: ${key}`)
})
} catch (e) {
console.error('[草稿缓存] 批量清理失败:', e)
}
}
return {
is_enabled,
build_key,
save_draft,
read_draft,
clear_draft,
cleanup_expired
}
}