优化
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import {
|
||||
preferenceStorageKey,
|
||||
readLayoutPreference,
|
||||
reconcileOrder,
|
||||
removeLayoutPreference,
|
||||
writeLayoutPreference,
|
||||
} from './uiLayoutPreference'
|
||||
|
||||
describe('UI layout preferences', () => {
|
||||
beforeEach(() => localStorage.clear())
|
||||
|
||||
it('reconciles stored order with the current stable keys', () => {
|
||||
expect(reconcileOrder(['actions', 'name', 'status', 'createdAt'], ['status', 'removed', 'name', 'status']))
|
||||
.toEqual(['status', 'name', 'actions', 'createdAt'])
|
||||
})
|
||||
|
||||
it('falls back safely when cached JSON is invalid', () => {
|
||||
localStorage.setItem(preferenceStorageKey('table', 'user-1', 'customers'), '{broken')
|
||||
expect(readLayoutPreference('table', 'user-1', 'customers', ['actions', 'name'], ['name']))
|
||||
.toEqual({ version: 1, order: ['actions', 'name'], hidden: ['name'] })
|
||||
})
|
||||
|
||||
it('keeps preferences isolated by user and removes them on reset', () => {
|
||||
writeLayoutPreference('query', 'user-1', 'orders', { version: 1, order: ['status', 'keyword'], hidden: [] })
|
||||
expect(readLayoutPreference('query', 'user-1', 'orders', ['keyword', 'status']).order).toEqual(['status', 'keyword'])
|
||||
expect(readLayoutPreference('query', 'user-2', 'orders', ['keyword', 'status']).order).toEqual(['keyword', 'status'])
|
||||
|
||||
removeLayoutPreference('query', 'user-1', 'orders')
|
||||
expect(readLayoutPreference('query', 'user-1', 'orders', ['keyword', 'status']).order).toEqual(['keyword', 'status'])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import type { ComputedRef, InjectionKey } from 'vue'
|
||||
|
||||
export type LayoutPreferenceKind = 'query' | 'table'
|
||||
|
||||
export interface LayoutPreferenceSnapshot {
|
||||
version: 1
|
||||
order: string[]
|
||||
hidden: string[]
|
||||
}
|
||||
|
||||
export const UI_PREFERENCE_SCOPE: InjectionKey<ComputedRef<string>> = Symbol('ui-preference-scope')
|
||||
|
||||
function uniqueKeys(values: readonly unknown[]) {
|
||||
const seen = new Set<string>()
|
||||
return values.flatMap((value) => {
|
||||
if (typeof value !== 'string' || !value || seen.has(value)) return []
|
||||
seen.add(value)
|
||||
return [value]
|
||||
})
|
||||
}
|
||||
|
||||
export function preferenceStorageKey(kind: LayoutPreferenceKind, scope: string, preferenceKey: string) {
|
||||
return `cc-admin-${kind}-layout:v1:${scope}:${preferenceKey}`
|
||||
}
|
||||
|
||||
export function reconcileOrder(availableKeys: readonly string[], storedOrder: readonly string[]) {
|
||||
const available = uniqueKeys(availableKeys)
|
||||
const availableSet = new Set(available)
|
||||
const retained = uniqueKeys(storedOrder).filter((key) => availableSet.has(key))
|
||||
const retainedSet = new Set(retained)
|
||||
return [...retained, ...available.filter((key) => !retainedSet.has(key))]
|
||||
}
|
||||
|
||||
export function readLayoutPreference(
|
||||
kind: LayoutPreferenceKind,
|
||||
scope: string,
|
||||
preferenceKey: string,
|
||||
availableKeys: readonly string[],
|
||||
defaultHidden: readonly string[] = [],
|
||||
): LayoutPreferenceSnapshot {
|
||||
const fallback: LayoutPreferenceSnapshot = {
|
||||
version: 1,
|
||||
order: reconcileOrder(availableKeys, []),
|
||||
hidden: uniqueKeys(defaultHidden).filter((key) => availableKeys.includes(key)),
|
||||
}
|
||||
if (!scope || !preferenceKey) return fallback
|
||||
try {
|
||||
const parsed = JSON.parse(localStorage.getItem(preferenceStorageKey(kind, scope, preferenceKey)) || 'null') as Partial<LayoutPreferenceSnapshot> | null
|
||||
if (!parsed || parsed.version !== 1 || !Array.isArray(parsed.order) || !Array.isArray(parsed.hidden)) return fallback
|
||||
return {
|
||||
version: 1,
|
||||
order: reconcileOrder(availableKeys, parsed.order),
|
||||
hidden: uniqueKeys(parsed.hidden).filter((key) => availableKeys.includes(key)),
|
||||
}
|
||||
} catch {
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
|
||||
export function writeLayoutPreference(
|
||||
kind: LayoutPreferenceKind,
|
||||
scope: string,
|
||||
preferenceKey: string,
|
||||
snapshot: LayoutPreferenceSnapshot,
|
||||
) {
|
||||
if (!scope || !preferenceKey) return
|
||||
try {
|
||||
localStorage.setItem(preferenceStorageKey(kind, scope, preferenceKey), JSON.stringify(snapshot))
|
||||
} catch {
|
||||
// Layout preferences are optional and must never block business workflows.
|
||||
}
|
||||
}
|
||||
|
||||
export function removeLayoutPreference(kind: LayoutPreferenceKind, scope: string, preferenceKey: string) {
|
||||
if (!scope || !preferenceKey) return
|
||||
try {
|
||||
localStorage.removeItem(preferenceStorageKey(kind, scope, preferenceKey))
|
||||
} catch {
|
||||
// Ignore unavailable browser storage and keep the in-memory default layout.
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user