export const DEFAULT_SIDEBAR_CONFIG = { collapsedByDefault: false, darkModeByDefault: false, entries: [ { key: 'product-monitoring', type: 'item', label: '商品监控', icon: 'appstore', routeKey: 'product-monitoring', visible: true, order: 12 }, { key: 'ops-b-incentive', type: 'item', label: '激励看板', icon: 'line-chart', routeKey: 'ops-b-incentive', visible: true, order: 15 }, { key: 'dashboard-preview', type: 'item', label: '仪表盘', icon: 'dashboard', routeKey: 'dashboard-preview', visible: true, order: 50 }, { key: 'marketing-management', type: 'group', label: '营销管理', icon: 'shopping', visible: true, order: 60, defaultExpanded: true, children: [ { key: 'marketing-events', label: '营销活动', icon: 'projection', routeKey: 'marketing-events', visible: true, order: 30 }, { key: 'promotion-module', label: '推广模块', icon: 'line-chart', routeKey: 'promotion-module', visible: true, order: 50 }, ], }, { key: 'product-management', type: 'group', label: '商品管理', icon: 'appstore', visible: true, order: 70, defaultExpanded: true, children: [ { key: 'product-analysis', label: '商品分析', icon: 'table', routeKey: 'product-analysis', visible: true, order: 20 }, ], }, { key: 'returns-inventory', type: 'group', label: '退货库存监控', icon: 'inbox', visible: true, order: 75, defaultExpanded: true, children: [ { key: 'returns-inventory-cuiqiao', label: '崔桥工厂仓客退数据监控', icon: 'table', routeKey: 'returns-inventory-cuiqiao', visible: true, order: 10 }, { key: 'returns-inventory-subei', label: '苏北客退数据监控', icon: 'table', routeKey: 'returns-inventory-subei', visible: true, order: 20 }, { key: 'returns-inventory-suining', label: '睢宁客退数据监控', icon: 'table', routeKey: 'returns-inventory-suining', visible: true, order: 30 }, { key: 'returns-inventory-shandong', label: '山东客退数据监控', icon: 'table', routeKey: 'returns-inventory-shandong', visible: true, order: 40 }, { key: 'returns-inventory-henan', label: '河南客退数据监控', icon: 'table', routeKey: 'returns-inventory-henan', visible: true, order: 50 }, ], }, { key: 'work-orders', type: 'item', label: '工单待办', icon: 'message', routeKey: 'work-orders', visible: true, order: 100 }, { key: 'modular-stock-monitor', type: 'group', label: '模块化库存监控', icon: 'appstore', visible: true, order: 105, defaultExpanded: true, children: [ { key: 'modular-stock-monitor-accessory', label: '配件库存监控', icon: 'table', routeKey: 'modular-stock-monitor-accessory', visible: true, order: 10 }, { key: 'mkh-bom-data', label: 'MKH-BOM资料', icon: 'table', routeKey: 'mkh-bom-data', visible: true, order: 20 }, ], }, { key: 'large-stock-monitor', type: 'group', label: '大货库存监控', icon: 'appstore', visible: true, order: 200, defaultExpanded: true, children: [ { key: 'daily-inventory', label: '每日进销存', icon: 'table', routeKey: 'daily-inventory', visible: true, order: 10 }, { key: 'stock-sales-analysis', label: '库存动销分析', icon: 'line-chart', routeKey: 'stock-sales-analysis', visible: true, order: 20 }, { key: 'picking-bin-sales-analysis', label: '拣选库位动销分析', icon: 'inbox', routeKey: 'picking-bin-sales-analysis', visible: true, order: 30 }, ], }, { key: 'manage', type: 'group', label: '系统管理', icon: 'setting', visible: true, order: 900, defaultExpanded: false, requiresAdmin: true, children: [ { key: 'settings', label: '系统设置', icon: 'setting', routeKey: 'settings', visible: true, order: 10, requiresAdmin: true }, { key: 'online-desktop-users', label: '在线客户端', icon: 'team', routeKey: 'online-desktop-users', visible: true, order: 30, requiresAdmin: true }, { key: 'desktop-execution-tasks', label: '执行任务', icon: 'projection', routeKey: 'desktop-execution-tasks', visible: true, order: 40, requiresAdmin: true }, ], }, ], }; const cloneDefaultConfig = () => JSON.parse(JSON.stringify(DEFAULT_SIDEBAR_CONFIG)); const flattenEntryKeys = (entries = []) => entries.flatMap((entry) => [ entry.key, ...flattenEntryKeys(entry.children || []), ]).filter(Boolean); const flattenRouteKeys = (entries = []) => entries.flatMap((entry) => [ entry.routeKey, ...flattenRouteKeys(entry.children || []), ]).filter(Boolean); const ALLOWED_ENTRY_KEYS = new Set(flattenEntryKeys(DEFAULT_SIDEBAR_CONFIG.entries)); const ALLOWED_ROUTE_KEYS = new Set(flattenRouteKeys(DEFAULT_SIDEBAR_CONFIG.entries)); // 激励看板原为「运营工具 → 运营中心B激励看板」,现升级为顶层入口;用于迁移存量侧边栏配置 const OPS_B_INCENTIVE_LEGACY_LABEL = '运营中心B激励看板'; const OPS_B_INCENTIVE_LEGACY_ORDER = 6; const PROMOTED_TOP_LEVEL_ENTRY_KEYS = new Set(['ops-b-incentive']); // 把存量配置里仍嵌在分组下的已升级条目摘出来,提升为顶层配置参与合并 const extractPromotedTopLevelEntries = (entries = []) => { const promoted = []; const nextEntries = entries.map((entry) => { if (!Array.isArray(entry?.children)) return entry; const children = []; entry.children.forEach((child) => { if (child?.key && PROMOTED_TOP_LEVEL_ENTRY_KEYS.has(child.key)) { promoted.push(child); } else { children.push(child); } }); return children.length === entry.children.length ? entry : { ...entry, children }; }); return { nextEntries, promoted }; }; const mergeSidebarEntries = (defaultEntries = [], configuredEntries = []) => { const defaultByKey = new Map(defaultEntries.map((entry) => [entry?.key, entry]).filter(([key]) => key)); const configuredKeys = new Set(); const mergeEntry = (defaultEntry = {}, configuredEntry = {}) => { const merged = { ...defaultEntry, ...configuredEntry }; if (defaultEntry.type) { merged.type = defaultEntry.type; } if (defaultEntry.routeKey) { merged.routeKey = defaultEntry.routeKey; } if (typeof defaultEntry.requiresAdmin !== 'undefined') { merged.requiresAdmin = defaultEntry.requiresAdmin; } if ( defaultEntry.key === 'ops-b-incentive' && (!configuredEntry.label || configuredEntry.label === OPS_B_INCENTIVE_LEGACY_LABEL) ) { merged.label = defaultEntry.label; } if ( defaultEntry.key === 'ops-b-incentive' && (configuredEntry.order == null || configuredEntry.order === OPS_B_INCENTIVE_LEGACY_ORDER) ) { merged.order = defaultEntry.order; } const hasDefaultChildren = Array.isArray(defaultEntry.children); const hasConfiguredChildren = Array.isArray(configuredEntry.children); if (hasDefaultChildren || hasConfiguredChildren) { if (hasDefaultChildren) { merged.type = defaultEntry.type; } else if (defaultEntry.routeKey) { merged.type = defaultEntry.type || 'item'; delete merged.children; delete merged.defaultExpanded; return merged; } merged.children = mergeSidebarEntries(defaultEntry.children || [], configuredEntry.children || []); } return merged; }; const configuredMerged = configuredEntries.map((entry) => { if (entry?.key) { configuredKeys.add(entry.key); } const defaultEntry = defaultByKey.get(entry?.key); return defaultEntry ? mergeEntry(defaultEntry, entry) : entry; }); const missingDefaults = defaultEntries .filter((entry) => entry?.key && !configuredKeys.has(entry.key)) .map((entry) => mergeEntry(entry, {})); return [...configuredMerged, ...missingDefaults]; }; const mergeSidebarConfigWithDefaults = (config) => { const defaults = cloneDefaultConfig(); const nestedEntries = (config?.entries || []); const { nextEntries, promoted } = extractPromotedTopLevelEntries(nestedEntries); const topLevelKeys = new Set(nextEntries.map((entry) => entry?.key).filter(Boolean)); const promotedNotDuplicated = promoted.some((entry) => topLevelKeys.has(entry.key)) ? [] : promoted; return { ...defaults, ...config, entries: mergeSidebarEntries(defaults.entries, [...nextEntries, ...promotedNotDuplicated]), }; }; const pruneSidebarEntry = (entry) => { if (!entry || !ALLOWED_ENTRY_KEYS.has(entry.key)) { return null; } const children = Array.isArray(entry.children) ? entry.children.map(pruneSidebarEntry).filter(Boolean) : undefined; const hasAllowedRoute = entry.routeKey ? ALLOWED_ROUTE_KEYS.has(entry.routeKey) : false; const hasAllowedChildren = Array.isArray(children) && children.length > 0; const keepEntry = hasAllowedRoute || hasAllowedChildren; if (!keepEntry) { return null; } return { ...entry, ...(Array.isArray(entry.children) ? { children } : {}), }; }; const removeRetiredEntries = (config) => ({ ...config, entries: (config.entries || []).map(pruneSidebarEntry).filter(Boolean), }); export const parseSidebarConfig = (value) => { if (!value) return cloneDefaultConfig(); try { const parsed = typeof value === 'string' ? JSON.parse(value) : value; if (!parsed || !Array.isArray(parsed.entries)) return cloneDefaultConfig(); return removeRetiredEntries(mergeSidebarConfigWithDefaults(parsed)); } catch { return cloneDefaultConfig(); } }; export const sortSidebarEntries = (entries = []) => ( [...entries] .filter((entry) => entry?.visible !== false) .sort((a, b) => Number(a.order || 0) - Number(b.order || 0)) );