- generic ContextMenu component (viewport flip, esc/outside close) - SchemaSidebar table right-click menu: preview data, generate SELECT/INSERT/UPDATE/DELETE templates with pk-aware WHERE, copy table name; double-click table runs preview SELECT; column right-click copy/insert; dialect-aware quoting; preview SELECT carries no LIMIT - SqlEditor selection-first execution (Mod-Enter runs selection else statement at cursor via quote/comment-aware splitter); pagination replays last executed statement - query history persisted server-side: drawer fetches query-histories with keyword/mine/favorites filters, star toggle (new PUT favorite + mine filter endpoints), insert and insert-and-run - custom templates in localStorage alongside built-ins - unit tests: genSql + statement splitter (28 tests green)
81 lines
2.2 KiB
TypeScript
81 lines
2.2 KiB
TypeScript
/**
|
||
* 统一的 localStorage 命名空间与迁移层。
|
||
*
|
||
* 所有持久化 key 一律从 STORAGE_KEYS 取值(`<app>.<域>.<名>` 命名空间),
|
||
* 禁止在业务代码里手写字符串 key。历史无命名空间 key 仅在此处做
|
||
* 「读旧写新」迁移,迁移期结束后删除 LEGACY_KEYS。
|
||
*/
|
||
|
||
const read = (key: string): string | null => {
|
||
try {
|
||
return localStorage.getItem(key);
|
||
} catch {
|
||
return null;
|
||
}
|
||
};
|
||
|
||
const write = (key: string, value: string): void => {
|
||
try {
|
||
localStorage.setItem(key, value);
|
||
} catch {
|
||
/* 存储不可用(隐私模式等)时静默降级为内存态 */
|
||
}
|
||
};
|
||
|
||
const remove = (key: string): void => {
|
||
try {
|
||
localStorage.removeItem(key);
|
||
} catch {
|
||
/* noop */
|
||
}
|
||
};
|
||
|
||
export const STORAGE_KEYS = {
|
||
token: 'opsxsql.auth.token',
|
||
refreshToken: 'opsxsql.auth.refresh',
|
||
theme: 'opsxsql.theme.mode',
|
||
loginStyle: 'opsxsql.ui.loginStyle',
|
||
queryFavorites: 'opsxsql.query.favorites',
|
||
queryCustomTemplates: 'opsxsql.query.customTemplates',
|
||
querySideWidth: 'opsxsql.query.sideWidth',
|
||
chunkReloaded: 'opsxsql.app.chunkReloaded',
|
||
} as const;
|
||
|
||
/** 历史无命名空间 key → 新 key(仅用于一次性迁移读取) */
|
||
const LEGACY_KEYS: Partial<Record<keyof typeof STORAGE_KEYS, string>> = {
|
||
token: 'token',
|
||
theme: 'dg-theme',
|
||
loginStyle: 'login-style',
|
||
queryFavorites: 'query_favorites',
|
||
querySideWidth: 'query-side-width',
|
||
};
|
||
|
||
/** 读取(新 key 优先,回退旧 key 并完成迁移写入) */
|
||
export function readStorage(key: keyof typeof STORAGE_KEYS): string | null {
|
||
const current = read(STORAGE_KEYS[key]);
|
||
if (current !== null) return current;
|
||
|
||
const legacyKey = LEGACY_KEYS[key];
|
||
if (legacyKey) {
|
||
const legacy = read(legacyKey);
|
||
if (legacy !== null) {
|
||
write(STORAGE_KEYS[key], legacy);
|
||
remove(legacyKey);
|
||
return legacy;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/** 写入(只写新 key) */
|
||
export function writeStorage(key: keyof typeof STORAGE_KEYS, value: string): void {
|
||
write(STORAGE_KEYS[key], value);
|
||
}
|
||
|
||
/** 删除(新旧 key 一起清理) */
|
||
export function removeStorage(key: keyof typeof STORAGE_KEYS): void {
|
||
remove(STORAGE_KEYS[key]);
|
||
const legacyKey = LEGACY_KEYS[key];
|
||
if (legacyKey) remove(legacyKey);
|
||
}
|