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
+15 -23
View File
@@ -3,6 +3,7 @@
import { ref, watch, onBeforeUnmount, nextTick } from 'vue'
import { NButton, NIcon } from 'naive-ui'
import { CloseOutline } from '@vicons/ionicons5'
import { readJson, writeJson } from '../utils/storage'
/** 浮窗位置与尺寸 */
interface PanelRect {
@@ -67,25 +68,20 @@ function clampRect() {
/** 读取持久化的位置与尺寸,非法值回退默认并居中 */
function loadRect() {
if (props.storageKey) {
try {
const raw = localStorage.getItem(props.storageKey)
const parsed = raw ? JSON.parse(raw) : null
const x = Number(parsed?.x)
const y = Number(parsed?.y)
const width = Number(parsed?.width)
const height = Number(parsed?.height)
if ([x, y, width, height].every((n) => Number.isFinite(n))) {
rect.value = {
x,
y,
width: Math.max(props.minWidth, width),
height: Math.max(props.minHeight, height),
}
clampRect()
return
const parsed = readJson<Partial<PanelRect> | null>(props.storageKey, null)
const x = Number(parsed?.x)
const y = Number(parsed?.y)
const width = Number(parsed?.width)
const height = Number(parsed?.height)
if ([x, y, width, height].every((n) => Number.isFinite(n))) {
rect.value = {
x,
y,
width: Math.max(props.minWidth, width),
height: Math.max(props.minHeight, height),
}
} catch {
// 忽略读取失败
clampRect()
return
}
}
rect.value = defaultRect()
@@ -94,11 +90,7 @@ function loadRect() {
/** 持久化位置与尺寸,忽略存储异常 */
function saveRect() {
if (!props.storageKey) return
try {
localStorage.setItem(props.storageKey, JSON.stringify(rect.value))
} catch {
// 忽略写入失败
}
writeJson(props.storageKey, rect.value)
}
// 拖拽状态
@@ -9,9 +9,11 @@ import type { TransactionGroup, TransactionRelationType, TransactionGroupPayload
import { getTransactionPage } from '../api/transaction'
import { getAiConfig, parseRelationByAi } from '../api/ai'
import { getChannelLabel } from '../utils/channel'
import { readString, writeString } from '../utils/storage'
import { STORAGE_KEYS } from '../utils/storageKeys'
/** 关系类型偏好存储键(界面偏好统一以 finance. 前缀命名) */
const RELATION_TYPE_KEY = 'finance.transaction.relationType'
const RELATION_TYPE_KEY = STORAGE_KEYS.transactionRelationType
const props = defineProps<{
/** 弹窗显示状态 */
@@ -79,21 +81,13 @@ onMounted(loadAiConfig)
/** 读取上次使用的关系类型偏好,非法值或异常回退 PARENT_CHILD */
function loadPreferredRelationType(): TransactionRelationType {
try {
const saved = localStorage.getItem(RELATION_TYPE_KEY)
return saved === 'PARENT_CHILD' || saved === 'SIBLING' ? saved : 'PARENT_CHILD'
} catch (e) {
return 'PARENT_CHILD'
}
const saved = readString(RELATION_TYPE_KEY)
return saved === 'PARENT_CHILD' || saved === 'SIBLING' ? saved : 'PARENT_CHILD'
}
/** 保存关系类型偏好,忽略存储异常 */
function savePreferredRelationType(type: TransactionRelationType) {
try {
localStorage.setItem(RELATION_TYPE_KEY, type)
} catch (e) {
// 忽略存储异常
}
writeString(RELATION_TYPE_KEY, type)
}
/** 加载关系名称历史(按当前关系类型过滤) */