refactor: 前端登录/权限/界面状态迁移至 zustand
This commit is contained in:
67
apps/admin/src/store/app/appStore.ts
Normal file
67
apps/admin/src/store/app/appStore.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { create } from 'zustand';
|
||||
import { devtools, persist } from 'zustand/middleware';
|
||||
import { appUiPersistStorage, APP_UI_STORAGE_NAME } from '../middleware/persist';
|
||||
import type { AppPersistedState, AppStore } from './appTypes';
|
||||
|
||||
/**
|
||||
* 应用级 UI Store:布局、抽屉、路由页签等跨组件共享状态。
|
||||
* 侧边栏折叠与路由页签持久化;其余为会话内状态。
|
||||
*/
|
||||
export const useAppStore = create<AppStore>()(
|
||||
devtools(
|
||||
persist(
|
||||
(set) => ({
|
||||
sidebarCollapsed: false,
|
||||
mobileDrawerOpen: false,
|
||||
menuOpenKeys: [],
|
||||
aiChatOpen: false,
|
||||
aiWorking: false,
|
||||
routeDockTabs: [],
|
||||
toggleSidebar: () => {
|
||||
set((state) => ({ sidebarCollapsed: !state.sidebarCollapsed }), false, 'app/toggleSidebar');
|
||||
},
|
||||
setSidebarCollapsed: (collapsed) => {
|
||||
set({ sidebarCollapsed: collapsed }, false, 'app/setSidebarCollapsed');
|
||||
},
|
||||
setMobileDrawerOpen: (open) => {
|
||||
set({ mobileDrawerOpen: open }, false, 'app/setMobileDrawerOpen');
|
||||
},
|
||||
setMenuOpenKeys: (keys) => {
|
||||
set(
|
||||
(state) => ({
|
||||
menuOpenKeys: typeof keys === 'function' ? keys(state.menuOpenKeys) : keys,
|
||||
}),
|
||||
false,
|
||||
'app/setMenuOpenKeys',
|
||||
);
|
||||
},
|
||||
setAiChatOpen: (open) => {
|
||||
set({ aiChatOpen: open }, false, 'app/setAiChatOpen');
|
||||
},
|
||||
setAiWorking: (working) => {
|
||||
set({ aiWorking: working }, false, 'app/setAiWorking');
|
||||
},
|
||||
setRouteDockTabs: (updater) => {
|
||||
set(
|
||||
(state) => ({
|
||||
routeDockTabs:
|
||||
typeof updater === 'function' ? updater(state.routeDockTabs) : updater,
|
||||
}),
|
||||
false,
|
||||
'app/setRouteDockTabs',
|
||||
);
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: APP_UI_STORAGE_NAME,
|
||||
storage: appUiPersistStorage,
|
||||
partialize: (state): AppPersistedState => ({
|
||||
sidebarCollapsed: state.sidebarCollapsed,
|
||||
routeDockTabs: state.routeDockTabs,
|
||||
}),
|
||||
version: 1,
|
||||
},
|
||||
),
|
||||
{ name: 'app-store', enabled: import.meta.env.DEV },
|
||||
),
|
||||
);
|
||||
39
apps/admin/src/store/app/appTypes.ts
Normal file
39
apps/admin/src/store/app/appTypes.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
export interface DockTab {
|
||||
key: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface AppState {
|
||||
/** 桌面端侧边栏折叠 */
|
||||
sidebarCollapsed: boolean;
|
||||
/** 移动端导航抽屉 */
|
||||
mobileDrawerOpen: boolean;
|
||||
/** 菜单展开的分组 key */
|
||||
menuOpenKeys: string[];
|
||||
/** AI 助理抽屉 */
|
||||
aiChatOpen: boolean;
|
||||
/** AI 请求处理中 */
|
||||
aiWorking: boolean;
|
||||
/** 路由页签(RouteDock) */
|
||||
routeDockTabs: DockTab[];
|
||||
}
|
||||
|
||||
export interface AppActions {
|
||||
toggleSidebar: () => void;
|
||||
setSidebarCollapsed: (collapsed: boolean) => void;
|
||||
setMobileDrawerOpen: (open: boolean) => void;
|
||||
/** 函数式更新,与 setState 语义一致 */
|
||||
setMenuOpenKeys: (keys: string[] | ((current: string[]) => string[])) => void;
|
||||
setAiChatOpen: (open: boolean) => void;
|
||||
setAiWorking: (working: boolean) => void;
|
||||
/** 函数式更新,与 setState 语义一致 */
|
||||
setRouteDockTabs: (updater: DockTab[] | ((current: DockTab[]) => DockTab[])) => void;
|
||||
}
|
||||
|
||||
export type AppStore = AppState & AppActions;
|
||||
|
||||
/** 持久化子集 */
|
||||
export interface AppPersistedState {
|
||||
sidebarCollapsed: boolean;
|
||||
routeDockTabs: DockTab[];
|
||||
}
|
||||
26
apps/admin/src/store/index.ts
Normal file
26
apps/admin/src/store/index.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 全局状态统一出口。
|
||||
* 业务代码从这里或对应 Store 文件导入,避免散落的局部状态管理。
|
||||
*/
|
||||
export { useAppStore } from './app/appStore';
|
||||
export { usePermissionStore } from './permission/permissionStore';
|
||||
export { useSettingsStore } from './settings/settingsStore';
|
||||
export { useUserStore } from './user/userStore';
|
||||
|
||||
export type { AppActions, AppPersistedState, AppState, AppStore, DockTab } from './app/appTypes';
|
||||
export type {
|
||||
PermissionActions,
|
||||
PermissionPersistedState,
|
||||
PermissionState,
|
||||
PermissionStatus,
|
||||
PermissionStore,
|
||||
} from './permission/permissionTypes';
|
||||
export type { AiChatSettings, SettingsActions, SettingsState, SettingsStore } from './settings/settingsTypes';
|
||||
export type { StoreStatus } from './types';
|
||||
export type {
|
||||
UserActions,
|
||||
UserInfo,
|
||||
UserPersistedState,
|
||||
UserState,
|
||||
UserStore,
|
||||
} from './user/userTypes';
|
||||
175
apps/admin/src/store/middleware/persist.ts
Normal file
175
apps/admin/src/store/middleware/persist.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* 持久化中间件基础设施。
|
||||
*
|
||||
* 为了平滑迁移,这里把旧实现直接读写 localStorage 的 key
|
||||
* (token / user / permissions / gongxue-route-dock)包装成 zustand
|
||||
* persist 的 StateStorage,保证迁移前后数据格式兼容。
|
||||
*/
|
||||
import { createJSONStorage, type StateStorage } from 'zustand/middleware';
|
||||
import type { AppPersistedState, DockTab } from '../app/appTypes';
|
||||
import type { PermissionPersistedState } from '../permission/permissionTypes';
|
||||
import type { UserInfo, UserPersistedState } from '../user/userTypes';
|
||||
|
||||
export const AUTH_STORAGE_NAME = 'gongxue-auth';
|
||||
export const LEGACY_TOKEN_KEY = 'token';
|
||||
export const LEGACY_USER_KEY = 'user';
|
||||
|
||||
export const PERMISSION_STORAGE_NAME = 'permissions';
|
||||
|
||||
export const APP_UI_STORAGE_NAME = 'gongxue-app-ui';
|
||||
export const LEGACY_DOCK_STORAGE_KEY = 'gongxue-route-dock';
|
||||
|
||||
export const SETTINGS_STORAGE_NAME = 'gongxue-settings';
|
||||
|
||||
function isString(value: unknown): value is string {
|
||||
return typeof value === 'string';
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null;
|
||||
}
|
||||
|
||||
function isDockTab(value: unknown): value is DockTab {
|
||||
return (
|
||||
isRecord(value) &&
|
||||
typeof value.key === 'string' &&
|
||||
value.key.startsWith('/') &&
|
||||
typeof value.label === 'string'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户会话持久化:继续使用旧的 `token` / `user` 两个 key,
|
||||
* 保持与后端、既有代码及浏览器缓存格式一致。
|
||||
*/
|
||||
const legacyAuthStorage: StateStorage = {
|
||||
getItem: () => {
|
||||
const token = localStorage.getItem(LEGACY_TOKEN_KEY);
|
||||
const rawUser = localStorage.getItem(LEGACY_USER_KEY);
|
||||
if (token === null && rawUser === null) return null;
|
||||
let user: UserInfo | null = null;
|
||||
if (rawUser !== null) {
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(rawUser);
|
||||
user = isRecord(parsed) ? (parsed as UserInfo) : null;
|
||||
} catch {
|
||||
user = null;
|
||||
}
|
||||
}
|
||||
return JSON.stringify({ state: { token, user }, version: 1 });
|
||||
},
|
||||
setItem: (_name, value) => {
|
||||
try {
|
||||
const persisted = JSON.parse(value) as { state?: UserPersistedState };
|
||||
const { token, user } = persisted.state ?? {};
|
||||
if (token) {
|
||||
localStorage.setItem(LEGACY_TOKEN_KEY, token);
|
||||
} else {
|
||||
localStorage.removeItem(LEGACY_TOKEN_KEY);
|
||||
}
|
||||
if (user) {
|
||||
localStorage.setItem(LEGACY_USER_KEY, JSON.stringify(user));
|
||||
} else {
|
||||
localStorage.removeItem(LEGACY_USER_KEY);
|
||||
}
|
||||
} catch {
|
||||
// 持久化写入失败不应影响应用运行
|
||||
}
|
||||
},
|
||||
removeItem: () => {
|
||||
localStorage.removeItem(LEGACY_TOKEN_KEY);
|
||||
localStorage.removeItem(LEGACY_USER_KEY);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 权限持久化:兼容旧格式(原始 JSON 数组)与 zustand persist 格式。
|
||||
* 无论磁盘上是什么状态,恢复后一律为 `unknown`,保持 fail-closed,
|
||||
* 直到 `/auth/profile` 校验成功。
|
||||
*/
|
||||
const legacyPermissionStorage: StateStorage = {
|
||||
getItem: () => {
|
||||
const raw = localStorage.getItem(PERMISSION_STORAGE_NAME);
|
||||
if (!raw) return null;
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(raw);
|
||||
if (Array.isArray(parsed)) {
|
||||
return JSON.stringify({
|
||||
state: { permissions: parsed.filter(isString), status: 'unknown' },
|
||||
version: 1,
|
||||
});
|
||||
}
|
||||
if (isRecord(parsed) && isRecord(parsed.state)) {
|
||||
const permissions = Array.isArray(parsed.state.permissions)
|
||||
? parsed.state.permissions.filter(isString)
|
||||
: [];
|
||||
return JSON.stringify({
|
||||
state: { permissions, status: 'unknown' },
|
||||
version: 1,
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// 损坏的缓存按无权限处理
|
||||
}
|
||||
return null;
|
||||
},
|
||||
setItem: (_name, value) => {
|
||||
try {
|
||||
const persisted = JSON.parse(value) as { state?: PermissionPersistedState };
|
||||
const permissions = Array.isArray(persisted.state?.permissions)
|
||||
? persisted.state.permissions.filter(isString)
|
||||
: [];
|
||||
localStorage.setItem(PERMISSION_STORAGE_NAME, JSON.stringify(permissions));
|
||||
} catch {
|
||||
// 忽略损坏数据
|
||||
}
|
||||
},
|
||||
removeItem: () => {
|
||||
localStorage.removeItem(PERMISSION_STORAGE_NAME);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 应用 UI 状态持久化:新 key `gongxue-app-ui`,
|
||||
* 首次读取时自动迁移旧 key `gongxue-route-dock` 中已打开的页签。
|
||||
*/
|
||||
const appUiStorage: StateStorage = {
|
||||
getItem: (name) => {
|
||||
const current = localStorage.getItem(name);
|
||||
if (current) return current;
|
||||
const legacy = localStorage.getItem(LEGACY_DOCK_STORAGE_KEY);
|
||||
if (!legacy) return null;
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(legacy);
|
||||
const routeDockTabs = Array.isArray(parsed) ? parsed.filter(isDockTab) : [];
|
||||
return JSON.stringify({ state: { routeDockTabs, sidebarCollapsed: false }, version: 1 });
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
setItem: (name, value) => {
|
||||
try {
|
||||
localStorage.setItem(name, value);
|
||||
} catch {
|
||||
// 持久化失败不应影响应用运行
|
||||
}
|
||||
},
|
||||
removeItem: (name) => {
|
||||
try {
|
||||
localStorage.removeItem(name);
|
||||
} catch {
|
||||
// 持久化失败不应影响应用运行
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/** 会话 Store 使用的 persist storage(兼容旧 token/user key) */
|
||||
export const authPersistStorage = createJSONStorage(() => legacyAuthStorage);
|
||||
|
||||
/** 权限 Store 使用的 persist storage(兼容旧 permissions key) */
|
||||
export const permissionPersistStorage = createJSONStorage(() => legacyPermissionStorage);
|
||||
|
||||
/** 应用 UI Store 使用的 persist storage(含旧 RouteDock key 迁移) */
|
||||
export const appUiPersistStorage = createJSONStorage(() => appUiStorage);
|
||||
|
||||
export type { AppPersistedState, PermissionPersistedState, UserPersistedState };
|
||||
48
apps/admin/src/store/permission/permissionStore.ts
Normal file
48
apps/admin/src/store/permission/permissionStore.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { create } from 'zustand';
|
||||
import { devtools, persist } from 'zustand/middleware';
|
||||
import {
|
||||
permissionPersistStorage,
|
||||
PERMISSION_STORAGE_NAME,
|
||||
} from '../middleware/persist';
|
||||
import type { PermissionPersistedState, PermissionStore } from './permissionTypes';
|
||||
|
||||
/**
|
||||
* 权限 Store。
|
||||
*
|
||||
* 迁移自旧的模块单例 + window 事件方案:
|
||||
* - 组件通过 usePermissionStore 订阅,不再依赖自定义事件;
|
||||
* - 持久化的权限只作为缓存,rehydrate 后 status 仍为 `unknown`,
|
||||
* 必须等待 /auth/profile 校验成功后才视为可用(保持 fail-closed)。
|
||||
*/
|
||||
export const usePermissionStore = create<PermissionStore>()(
|
||||
devtools(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
permissions: [],
|
||||
status: 'unknown',
|
||||
beginPermissionVerification: () => {
|
||||
// 后台刷新时保留已就绪的权限,避免界面闪加载
|
||||
if (get().status === 'ready') return;
|
||||
set({ permissions: [], status: 'loading' }, false, 'permission/beginVerification');
|
||||
},
|
||||
writePermissions: (permissions) => {
|
||||
set(
|
||||
{ permissions: [...new Set(permissions)], status: 'ready' },
|
||||
false,
|
||||
'permission/write',
|
||||
);
|
||||
},
|
||||
clearPermissions: (status = 'unknown') => {
|
||||
set({ permissions: [], status }, false, 'permission/clear');
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: PERMISSION_STORAGE_NAME,
|
||||
storage: permissionPersistStorage,
|
||||
partialize: (state): PermissionPersistedState => ({ permissions: state.permissions }),
|
||||
version: 1,
|
||||
},
|
||||
),
|
||||
{ name: 'permission-store', enabled: import.meta.env.DEV },
|
||||
),
|
||||
);
|
||||
25
apps/admin/src/store/permission/permissionTypes.ts
Normal file
25
apps/admin/src/store/permission/permissionTypes.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { StoreStatus } from '../types';
|
||||
|
||||
export type PermissionStatus = StoreStatus;
|
||||
|
||||
export interface PermissionState {
|
||||
/** 已校验通过的权限码 */
|
||||
permissions: string[];
|
||||
status: PermissionStatus;
|
||||
}
|
||||
|
||||
export interface PermissionActions {
|
||||
/** 开始后台校验:非 ready 状态时清空权限并进入 loading(fail-closed) */
|
||||
beginPermissionVerification: () => void;
|
||||
/** 写入已校验的权限 */
|
||||
writePermissions: (permissions: string[]) => void;
|
||||
/** 清空权限(退出登录 / 401) */
|
||||
clearPermissions: (status?: PermissionStatus) => void;
|
||||
}
|
||||
|
||||
export type PermissionStore = PermissionState & PermissionActions;
|
||||
|
||||
/** 持久化子集:只保存权限码,状态恢复后一律为 unknown */
|
||||
export interface PermissionPersistedState {
|
||||
permissions: string[];
|
||||
}
|
||||
45
apps/admin/src/store/settings/settingsStore.ts
Normal file
45
apps/admin/src/store/settings/settingsStore.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { create } from 'zustand';
|
||||
import { devtools, persist } from 'zustand/middleware';
|
||||
import { createJSONStorage } from 'zustand/middleware';
|
||||
import { SETTINGS_STORAGE_NAME } from '../middleware/persist';
|
||||
import type { SettingsState, SettingsStore } from './settingsTypes';
|
||||
|
||||
const defaultSettings: SettingsState = {
|
||||
aiChat: {
|
||||
deepThinking: false,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 用户偏好 Store:AI 聊天等非业务、可持久化的设置。
|
||||
*/
|
||||
export const useSettingsStore = create<SettingsStore>()(
|
||||
devtools(
|
||||
persist(
|
||||
(set) => ({
|
||||
...defaultSettings,
|
||||
setAiChatDeepThinking: (enabled) => {
|
||||
set(
|
||||
(state) => ({ aiChat: { ...state.aiChat, deepThinking: enabled } }),
|
||||
false,
|
||||
'settings/setAiChatDeepThinking',
|
||||
);
|
||||
},
|
||||
toggleAiChatDeepThinking: () => {
|
||||
set(
|
||||
(state) => ({ aiChat: { ...state.aiChat, deepThinking: !state.aiChat.deepThinking } }),
|
||||
false,
|
||||
'settings/toggleAiChatDeepThinking',
|
||||
);
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: SETTINGS_STORAGE_NAME,
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
partialize: (state): SettingsState => ({ aiChat: state.aiChat }),
|
||||
version: 1,
|
||||
},
|
||||
),
|
||||
{ name: 'settings-store', enabled: import.meta.env.DEV },
|
||||
),
|
||||
);
|
||||
15
apps/admin/src/store/settings/settingsTypes.ts
Normal file
15
apps/admin/src/store/settings/settingsTypes.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export interface AiChatSettings {
|
||||
/** 深度思考(reasoningEffort: high)偏好 */
|
||||
deepThinking: boolean;
|
||||
}
|
||||
|
||||
export interface SettingsState {
|
||||
aiChat: AiChatSettings;
|
||||
}
|
||||
|
||||
export interface SettingsActions {
|
||||
setAiChatDeepThinking: (enabled: boolean) => void;
|
||||
toggleAiChatDeepThinking: () => void;
|
||||
}
|
||||
|
||||
export type SettingsStore = SettingsState & SettingsActions;
|
||||
14
apps/admin/src/store/types.ts
Normal file
14
apps/admin/src/store/types.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 全局状态管理通用类型。
|
||||
*
|
||||
* 各领域 Store 采用“模块化切片”设计:
|
||||
* - 一个领域一个目录,state/actions 分别定义;
|
||||
* - 所有 Store 由 zustand `create` + `devtools` + `persist` 组合创建;
|
||||
* - 组件只通过 `src/store/index.ts` 或具体 Store 文件访问,保持单向数据流。
|
||||
*/
|
||||
|
||||
/** 权限校验状态:未知(fail-closed)→ 校验中 → 已就绪 */
|
||||
export type StoreStatus = 'unknown' | 'loading' | 'ready';
|
||||
|
||||
/** 持久化时从 Store 中挑选出的字段 */
|
||||
export type Partialize<T> = (state: T) => Partial<T>;
|
||||
33
apps/admin/src/store/user/userActions.ts
Normal file
33
apps/admin/src/store/user/userActions.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { StateCreator } from 'zustand';
|
||||
import type { UserActions, UserStore } from './userTypes';
|
||||
|
||||
/** user Store 应用到的中间件,用于让 createUserActions 获得完整 set 类型 */
|
||||
export type UserStoreMutators = [
|
||||
['zustand/devtools', never],
|
||||
['zustand/persist', unknown],
|
||||
];
|
||||
|
||||
/**
|
||||
* 用户会话 actions,与 state 分离,避免 Store 无限膨胀。
|
||||
* 由 userStore.ts 组合进 create()。
|
||||
*/
|
||||
export const createUserActions: StateCreator<UserStore, UserStoreMutators, [], UserActions> = (
|
||||
set,
|
||||
) => ({
|
||||
setSession: (token, user) => {
|
||||
set({ token, user }, false, 'user/setSession');
|
||||
},
|
||||
setUser: (user) => {
|
||||
set({ user }, false, 'user/setUser');
|
||||
},
|
||||
updateUser: (patch) => {
|
||||
set(
|
||||
(state) => (state.user ? { user: { ...state.user, ...patch } } : {}),
|
||||
false,
|
||||
'user/updateUser',
|
||||
);
|
||||
},
|
||||
logout: () => {
|
||||
set({ token: null, user: null }, false, 'user/logout');
|
||||
},
|
||||
});
|
||||
29
apps/admin/src/store/user/userStore.ts
Normal file
29
apps/admin/src/store/user/userStore.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { create } from 'zustand';
|
||||
import { devtools, persist } from 'zustand/middleware';
|
||||
import { authPersistStorage, AUTH_STORAGE_NAME } from '../middleware/persist';
|
||||
import { createUserActions } from './userActions';
|
||||
import type { UserPersistedState, UserStore } from './userTypes';
|
||||
|
||||
/**
|
||||
* 用户会话 Store(token + 用户资料)。
|
||||
* 使用 zustand 官方推荐写法:create()(devtools(persist(...)))。
|
||||
* 持久化沿用旧 `token` / `user` localStorage key。
|
||||
*/
|
||||
export const useUserStore = create<UserStore>()(
|
||||
devtools(
|
||||
persist(
|
||||
(set, get, api) => ({
|
||||
token: null,
|
||||
user: null,
|
||||
...createUserActions(set, get, api),
|
||||
}),
|
||||
{
|
||||
name: AUTH_STORAGE_NAME,
|
||||
storage: authPersistStorage,
|
||||
partialize: (state): UserPersistedState => ({ token: state.token, user: state.user }),
|
||||
version: 1,
|
||||
},
|
||||
),
|
||||
{ name: 'user-store', enabled: import.meta.env.DEV },
|
||||
),
|
||||
);
|
||||
38
apps/admin/src/store/user/userTypes.ts
Normal file
38
apps/admin/src/store/user/userTypes.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { StoreStatus } from '../types';
|
||||
|
||||
/** 登录用户信息(与后端 /auth/login、/auth/profile 返回结构对齐) */
|
||||
export interface UserInfo {
|
||||
id: number;
|
||||
username: string;
|
||||
name?: string;
|
||||
roles?: string[];
|
||||
permissions?: string[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface UserState {
|
||||
token: string | null;
|
||||
user: UserInfo | null;
|
||||
}
|
||||
|
||||
export interface UserActions {
|
||||
/** 登录成功后写入完整会话 */
|
||||
setSession: (token: string, user: UserInfo) => void;
|
||||
/** 整体替换用户信息 */
|
||||
setUser: (user: UserInfo | null) => void;
|
||||
/** 局部更新用户信息(如 profile 校验返回的最新数据) */
|
||||
updateUser: (patch: Partial<UserInfo>) => void;
|
||||
/** 退出登录:清空会话 */
|
||||
logout: () => void;
|
||||
}
|
||||
|
||||
export type UserStore = UserState & UserActions;
|
||||
|
||||
/** 持久化子集 */
|
||||
export interface UserPersistedState {
|
||||
token: string | null;
|
||||
user: UserInfo | null;
|
||||
}
|
||||
|
||||
/** 兼容:用户状态中的权限由 permission Store 统一管理 */
|
||||
export type { StoreStatus };
|
||||
Reference in New Issue
Block a user