144 lines
4.6 KiB
TypeScript
144 lines
4.6 KiB
TypeScript
/*
|
|
* @Author: ZhaoYing
|
|
* @Date: 2026-02-02 16:33:34
|
|
* @Last Modified by: ZhaoYing
|
|
* @Last Modified time: 2026-02-04 10:31:14
|
|
*/
|
|
/**
|
|
* Menu Store
|
|
*
|
|
* Manages application menu and breadcrumb navigation with:
|
|
* - Menu loading from JSON configuration
|
|
* - Sidebar collapse state
|
|
* - Breadcrumb generation from menu paths
|
|
* - Custom breadcrumb support
|
|
* - Separate menu contexts (space/manage)
|
|
*
|
|
* @store
|
|
*/
|
|
|
|
import { create } from 'zustand'
|
|
import AllMenus from './menu.json'
|
|
|
|
/** Menu item interface */
|
|
export interface MenuItem {
|
|
id: number;
|
|
parent: number;
|
|
code: string | null;
|
|
label: string;
|
|
i18nKey: string | null;
|
|
path: string | null;
|
|
enable: boolean;
|
|
display: boolean;
|
|
level: number;
|
|
sort: number;
|
|
icon?: string | null;
|
|
iconActive?: string | null;
|
|
menuDesc?: string | null;
|
|
deleted?: string | null;
|
|
updateTime?: number;
|
|
new_?: string | null;
|
|
keepAlive?: boolean;
|
|
master?: string | null;
|
|
disposable?: boolean;
|
|
appSystem?: string | null;
|
|
type?: 'group' | string;
|
|
subs?: MenuItem[] | null;
|
|
onClick?: (e?: React.MouseEvent) => void | boolean;
|
|
}
|
|
|
|
/** Menu state interface */
|
|
interface MenuState {
|
|
/** Sidebar collapsed state */
|
|
collapsed: boolean;
|
|
/** Toggle sidebar collapse */
|
|
toggleSider: () => void;
|
|
/** All menus by context */
|
|
allMenus: Record<'space' | 'manage', MenuItem[]>;
|
|
/** All breadcrumbs by context */
|
|
allBreadcrumbs: Record<'space' | 'manage' | string, MenuItem[]>;
|
|
/** Load menus for specific context */
|
|
loadMenus: (source: 'space' | 'manage') => void;
|
|
/** Update breadcrumbs based on key path */
|
|
updateBreadcrumbs: (keyPath: string[], source: 'space' | 'manage') => void;
|
|
/** Set custom breadcrumbs */
|
|
setCustomBreadcrumbs: (breadcrumbs: MenuItem[], source: string) => void;
|
|
}
|
|
|
|
/** Initialize breadcrumbs from localStorage */
|
|
const initBreadcrumbs = localStorage.getItem('breadcrumbs') || '[]'
|
|
|
|
/** Menu store */
|
|
export const useMenu = create<MenuState>((set, get) => ({
|
|
collapsed: localStorage.getItem('collapsed') === 'true',
|
|
allMenus: {
|
|
manage: [],
|
|
space: []
|
|
},
|
|
allBreadcrumbs: JSON.parse(initBreadcrumbs),
|
|
loadMenus: async () => {
|
|
set({ allMenus: AllMenus })
|
|
},
|
|
toggleSider: () => {
|
|
set((state) => {
|
|
const newCollapsed = !state.collapsed
|
|
localStorage.setItem('collapsed', JSON.stringify(newCollapsed))
|
|
return { collapsed: newCollapsed }
|
|
})
|
|
},
|
|
updateBreadcrumbs: (paths, source) => {
|
|
const { allMenus } = get()
|
|
const menus = allMenus[source] || []
|
|
let result: MenuItem[] = []
|
|
|
|
/** Flatten group menus so top-level items are always real menu entries */
|
|
const flatMenus = menus.flatMap(m => m.type === 'group' ? (m.subs || []) : [m]);
|
|
|
|
const findById = (list: MenuItem[], id: string) => list.find(m => `${m.id}` === id);
|
|
/** Find menu by id in both original menus and flatMenus (handles group ids) */
|
|
const findMenuById = (id: string) => findById(menus, id) || findById(flatMenus, id);
|
|
const pathMatches = (pattern: string, path: string) => {
|
|
const n = pattern[0] !== '/' ? '/' + pattern : pattern;
|
|
if (n === path) return true;
|
|
if (n.includes(':')) return new RegExp('^' + n.replace(/:[\w-]+/g, '[^/]+') + '$').test(path);
|
|
return false;
|
|
};
|
|
|
|
if (paths.length === 3) {
|
|
/** Three-level: [subSubPath, subId, menuId] */
|
|
const matchedMenu = findMenuById(paths[2]);
|
|
if (matchedMenu?.subs) {
|
|
const matchedSub = findById(matchedMenu.subs, paths[1]);
|
|
if (matchedSub?.subs) {
|
|
const matchedSubSub = matchedSub.subs.find(s => s.path === paths[0] || pathMatches(s.path || '', paths[0]));
|
|
if (matchedSubSub) {
|
|
result = [
|
|
{ ...matchedMenu, subs: null },
|
|
{ ...matchedSub, subs: null },
|
|
{ ...matchedSubSub, subs: null }
|
|
];
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
const matchedMenu = flatMenus.find(m => m.path === paths[0] || `${m.id}` === paths[1]);
|
|
if (matchedMenu) {
|
|
let matchedSubMenu: MenuItem | undefined;
|
|
if (paths.length > 1 && matchedMenu.subs?.length) {
|
|
matchedSubMenu = matchedMenu.subs.find(m => m.path === paths[0]);
|
|
}
|
|
result = [{ ...matchedMenu, subs: null }, matchedSubMenu].filter(Boolean) as MenuItem[];
|
|
}
|
|
}
|
|
|
|
const allBreadcrumbs = { ...get().allBreadcrumbs, [source]: result }
|
|
set({ allBreadcrumbs })
|
|
localStorage.setItem('breadcrumbs', JSON.stringify(allBreadcrumbs))
|
|
},
|
|
setCustomBreadcrumbs: (breadcrumbs, source) => {
|
|
const allBreadcrumbs = { ...get().allBreadcrumbs, [source]: breadcrumbs }
|
|
set({ allBreadcrumbs })
|
|
localStorage.setItem('breadcrumbs', JSON.stringify(allBreadcrumbs))
|
|
},
|
|
}))
|