refactor: 前端本地偏好统一为公共存储层

- 新增 frontend/src/utils/storageKeys.ts 集中维护存储键常量并登记旧键迁移
- 新增 frontend/src/utils/storage.ts 统一 localStorage/sessionStorage 读写与异常容错
- 新增 frontend/src/composables/usePreference.ts 提供 useStringPreference 响应式字符串偏好
- 新增 frontend/src/utils/transactionFilterPrefs.ts 抽出收支筛选偏好字段定义与共享读写
- 存储键统一以 finance. 前缀命名,locale、emailBill.tab、logs.tab 迁移到新键并自动迁移旧值
- 各页面与组件改用公共层,移除重复的 try/catch 与跨组件重复的序列化逻辑
- 认证凭据 token 属登录状态,保持原键名不纳入 finance. 前缀
This commit is contained in:
2026-09-23 09:41:55 +08:00
parent 66d6ab86b5
commit eb358d0ba1
16 changed files with 396 additions and 242 deletions
+7 -19
View File
@@ -4,6 +4,7 @@ import type { Ref } from 'vue'
import type { AutoCompleteOption } from 'naive-ui'
import { NIcon } from 'naive-ui'
import { CloseCircleOutline } from '@vicons/ionicons5'
import { readJson, removeKey, writeJson } from '../utils/storage'
/** 关键词历史配置 */
export interface KeywordHistoryConfig {
@@ -30,15 +31,10 @@ export function useKeywordHistory(config: KeywordHistoryConfig) {
/** 从 localStorage 读取历史,忽略损坏数据 */
function load() {
try {
const raw = localStorage.getItem(storageKey)
const parsed = raw ? JSON.parse(raw) : []
history.value = Array.isArray(parsed)
? parsed.filter((item) => typeof item === 'string' && item.trim()).slice(0, max)
: []
} catch {
history.value = []
}
const parsed = readJson<unknown>(storageKey, [])
history.value = Array.isArray(parsed)
? parsed.filter((item) => typeof item === 'string' && item.trim()).slice(0, max)
: []
}
/** 记录当前关键词:去重(忽略大小写)、最新在前、上限截断 */
@@ -47,21 +43,13 @@ export function useKeywordHistory(config: KeywordHistoryConfig) {
if (!value) return
const rest = history.value.filter((item) => item.toLowerCase() !== value.toLowerCase())
history.value = [value, ...rest].slice(0, max)
try {
localStorage.setItem(storageKey, JSON.stringify(history.value))
} catch {
// 忽略写入失败
}
writeJson(storageKey, history.value)
}
/** 清空历史 */
function clear() {
history.value = []
try {
localStorage.removeItem(storageKey)
} catch {
// 忽略删除失败
}
removeKey(storageKey)
}
// 下拉选项:按当前输入过滤,历史非空时末尾追加「清空历史」
+36
View File
@@ -0,0 +1,36 @@
// 偏好 composable:将字符串型界面偏好封装为响应式 ref,自动读取、校验回退与持久化
import { ref, watch } from 'vue'
import type { Ref } from 'vue'
import { migrateKey, readString, writeString } from '../utils/storage'
/** 字符串偏好配置 */
export interface StringPreferenceOptions {
/** 历史存储键,首次读取时迁移到当前键 */
legacyKeys?: string[]
/** 合法值校验,返回 false 时回退默认值 */
validate?: (value: string) => boolean
}
/**
* 管理字符串型界面偏好:初始化时迁移旧键并读取,值变化时自动写入。
*
* :param key: 当前存储键
* :param defaultValue: 默认值(键缺失或值非法时使用)
* :param options: 旧键迁移与合法值校验
* :returns: 与存储同步的字符串 ref
*/
export function useStringPreference(
key: string,
defaultValue: string,
options: StringPreferenceOptions = {},
): Ref<string> {
const { legacyKeys = [], validate } = options
// 首次读取前迁移历史键,保证老用户偏好不丢失
legacyKeys.forEach((legacy) => migrateKey(legacy, key))
const initial = readString(key, defaultValue)
const value = ref<string>(validate && !validate(initial) ? defaultValue : initial)
watch(value, (next) => writeString(key, next))
return value
}