// frontend/utils/handlers/guideHandler.js // 引导记录 handler(分组型,含保留规则) // spec §3.4.2: 保留当前 uid 的 + 全局 key;其他用户组仅在登录态可清 import { getCurrentUid } from '@/utils/draftStorage' // 复用 import { listGuideKeys, extractUidFromGuideKey } from './guideKeys' const GLOBAL_PROTECTED_KEYS = new Set(['guide_debug_mode', 'guide_first_show']) function isGlobalKey(key) { // guide_shown_xxx 是无 uid 段(但有 configKey) if (GLOBAL_PROTECTED_KEYS.has(key)) return true if (key.startsWith('guide_shown_')) return true return false } function bytesOf(keys) { let total = 0 for (const k of keys) { try { const v = uni.getStorageSync(k); if (v != null) total += JSON.stringify(v).length } catch (e) {} } return total } export default { id: 'guide', label: '引导记录', description: '新手引导完成标记', warning: false, async computeSize() { const keys = listGuideKeys() return { sizeBytes: bytesOf(keys), keyCount: keys.length } }, async computeBreakdown() { const currentUid = getCurrentUid() const allKeys = listGuideKeys() const groups = [] const isLoggedIn = !!currentUid // 1. 我的(currentUid)— 保留不可清 if (currentUid) { const myKeys = allKeys.filter((k) => extractUidFromGuideKey(k) === currentUid) groups.push({ uid: currentUid, displayUid: currentUid, isCurrent: true, sizeBytes: bytesOf(myKeys), keyCount: myKeys.length, canClean: false, disabledReason: 'guide-current', }) } // 2. 其他用户(按 uid 聚合)— 仅登录态可清;未登录态保留展示但按钮置灰 const otherUidKeys = new Map() for (const k of allKeys) { const uid = extractUidFromGuideKey(k) if (!uid || uid === currentUid) continue if (!otherUidKeys.has(uid)) otherUidKeys.set(uid, []) otherUidKeys.get(uid).push(k) } for (const [uid, keys] of otherUidKeys) { groups.push({ uid, displayUid: uid, isCurrent: false, sizeBytes: bytesOf(keys), keyCount: keys.length, canClean: isLoggedIn && keys.length > 0, disabledReason: isLoggedIn ? (keys.length === 0 ? 'empty' : null) : 'logged-out', }) } // 3. 全局 guide_*(无 uid 段)— 永远不可清 const globalKeys = allKeys.filter(isGlobalKey) if (globalKeys.length > 0) { groups.push({ uid: '__global__', displayUid: '全局标记', isCurrent: false, sizeBytes: bytesOf(globalKeys), keyCount: globalKeys.length, global: true, canClean: false, disabledReason: 'guide-global', }) } // 按 sizeBytes 降序(spec §4.3.2 要求) return groups.sort((a, b) => b.sizeBytes - a.sizeBytes) }, async cleanGroup({ uid }) { const currentUid = getCurrentUid() const allKeys = listGuideKeys() // 保留:当前 uid 的 + 全局 key const reserved = new Set() if (currentUid) { for (const k of allKeys) if (extractUidFromGuideKey(k) === currentUid) reserved.add(k) } for (const k of allKeys) if (isGlobalKey(k)) reserved.add(k) let targetKeys = [] if (uid === 'self') { // 不允许清自己的(保留规则) return { freedBytes: 0, keyCount: 0 } } else if (uid === null) { // 其他用户聚合(不含全局) targetKeys = allKeys.filter((k) => { if (reserved.has(k)) return false const u = extractUidFromGuideKey(k) return u && u !== currentUid }) } else if (uid === '__global__') { // 全局 key 也不允许清 return { freedBytes: 0, keyCount: 0 } } else { // 具体其他 uid targetKeys = allKeys.filter((k) => { if (reserved.has(k)) return false // defensive return extractUidFromGuideKey(k) === uid }) } // 逐 key:先量后删(per-key 精确值,避免 approximation) let freed = 0 let count = 0 for (const k of targetKeys) { let valueBytes = 0 try { const v = uni.getStorageSync(k) if (v != null) valueBytes = JSON.stringify(v).length } catch (e) { console.warn('[guideHandler] read failed:', k, e.message) continue } try { uni.removeStorageSync(k) freed += valueBytes count++ } catch (e) { console.warn('[guideHandler] remove failed:', k, e.message) } } return { freedBytes: freed, keyCount: count } }, }