feat: 新增每日进销存(大货库存监控)
This commit is contained in:
parent
9fd0bcaf4b
commit
2bd5dce3d7
@ -1,195 +1,201 @@
|
||||
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: '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: '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))
|
||||
);
|
||||
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: '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: '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))
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user