Files
Migu2.0/MiGu.Server/Configs/DeploymentCatalog.cs
T

97 lines
4.9 KiB
C#

namespace MiGu.Server.Configs;
/// <summary>
/// 配置向导「可选项目录」与「选型 → 平台能力」映射的单一事实来源。
///
/// <para>前端向导从 <c>GET /api/wizard/options</c> 拿到这些可选项渲染勾选框;后端
/// <see cref="ModuleToPages"/> 在「按选型裁剪菜单」时把模块
/// 映射为 PageCatalog 的可见页 id。集中定义,避免前后端各写一份导致漂移。</para>
/// </summary>
public static class DeploymentCatalog
{
/// <summary>一个可勾选项。<see cref="Id"/> 是稳定机器标识,<see cref="Group"/> 用于前端分组展示。</summary>
public record Option(string Id, string Name, string Group, string Description);
/// <summary>导航方式(多选,一等维度)。id 与 <see cref="DeploymentProfile.NavKindToSceneId"/> 的 key 对齐。</summary>
public static readonly IReadOnlyList<Option> NavigationKinds = new[]
{
new Option("magnetic", "磁导航", "navigation", "磁条循迹 + 地标 / RFID 定位"),
new Option("qrcode", "二维码导航", "navigation", "二维码地标 + 码值地图"),
new Option("laser", "激光导航", "navigation", "反光板 / SLAM + 激光避障"),
};
/// <summary>功能模块(多选)。暂定保留 WMS / PTL 两项,后续按需扩展(参考 RIOT 的 WMS/WCS/MES/APS 分层)。</summary>
public static readonly IReadOnlyList<Option> Modules = new[]
{
new Option("wms", "WMS 仓储管理", "module", "库位 / 库存 / 出入库管理"),
new Option("ptl", "PTL 拣选系统", "module", "Pick-to-Light 亮灯拣选与播种"),
};
/// <summary>
/// 功能模块 → PageCatalog 页面 Key 列表的映射。PTL 暂无专属配置页,未纳入映射(选中不影响菜单)。
/// 注意:Key 必须是 PageCatalog 当前有效 Key(不能用 LegacyKeyAliases 里的旧名,否则裁剪悄然失效)。
/// </summary>
public static readonly IReadOnlyDictionary<string, string[]> ModuleToPages =
new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
{
["wms"] = new[] { "admin-config-facility", "admin-config-warehouse" },
};
/// <summary>
/// 场景插件 → 平台页面 Key。选 SPS / Pack 时 ToLauncherSceneIds 会并入
/// <see cref="DeploymentProfile.SignalSceneId"/>,从而点亮「数据中心」。
/// </summary>
public static readonly IReadOnlyDictionary<string, string[]> SceneToPages =
new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
{
[DeploymentProfile.SignalSceneId] = new[] { "admin-data-center" },
};
/// <summary>所有「可被选型控制」的页面 Key(Module / Scene 映射值的并集)。</summary>
public static IReadOnlyCollection<string> TailorablePages()
{
var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var v in ModuleToPages.Values) foreach (var p in v) set.Add(p);
foreach (var v in SceneToPages.Values) foreach (var p in v) set.Add(p);
return set;
}
/// <summary>当前部署画像下被「点亮」的可裁剪页(已启用 Module / 激活场景映射到的页)。</summary>
public static IReadOnlyCollection<string> EnabledPages(DeploymentProfile dp)
{
var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (dp == null) return set;
foreach (var m in dp.Modules ?? new List<string>())
if (ModuleToPages.TryGetValue(m, out var ps)) foreach (var p in ps) set.Add(p);
var activeScenes = dp.ToLauncherSceneIds();
foreach (var (sceneId, pages) in SceneToPages)
{
if (!activeScenes.Contains(sceneId, StringComparer.OrdinalIgnoreCase)) continue;
foreach (var p in pages) set.Add(p);
}
return set;
}
/// <summary>当前部署画像下应隐藏的页(可裁剪但未被点亮)。向导未完成(Configured=false)则不隐藏任何页。</summary>
public static IReadOnlyCollection<string> HiddenPages(DeploymentProfile dp)
{
if (dp == null || !dp.Configured) return Array.Empty<string>();
var enabled = EnabledPages(dp);
return TailorablePages().Where(p => !enabled.Contains(p)).ToList();
}
/// <summary>
/// 从 RBAC 计算出的可见页集合中移除「被部署画像隐藏」的页 —— 这是「按选型裁剪菜单」的核心。
/// 不在任何映射中的页不受影响;向导未完成时原样返回(避免首登空菜单)。
/// </summary>
public static List<string> FilterPagesByDeployment(IEnumerable<string> pages, DeploymentProfile dp)
{
var input = (pages ?? Enumerable.Empty<string>()).ToList();
if (dp == null || !dp.Configured) return input;
var hidden = new HashSet<string>(HiddenPages(dp), StringComparer.OrdinalIgnoreCase);
return input.Where(p => !hidden.Contains(p)).ToList();
}
}