From 7382e855988049f96a2355fc47e71932449355be Mon Sep 17 00:00:00 2001 From: "zhaowei.huang" <228127304@qq.com> Date: Wed, 3 Jun 2026 09:37:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(platform):=20=E9=83=A8=E7=BD=B2=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=90=91=E5=AF=BC=20+=20=E5=9C=B0=E5=9B=BE=E7=AE=A1?= =?UTF-8?q?=E7=90=86=EF=BC=8C=E5=9C=B0=E5=9B=BE=E7=BC=96=E8=BE=91=E5=99=A8?= =?UTF-8?q?=E6=8E=A5=E5=85=A5=E7=BB=9F=E4=B8=80=E5=AD=98=E5=8F=96=E4=B8=8E?= =?UTF-8?q?=20AI=20=E5=8A=A9=E6=89=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 配置向导:登录按 deployment 画像引导平台选型(导航方式/模块/场景),未完成则路由守卫强制进入 /wizard;选型驱动菜单按需裁剪,并联动 SimpleLite 写 plugins/active-scenes.json + 透传 --scenes 选择性加载导航场景插件 - 地图管理页:服务器地图列表/使用/重命名/删除、地图合并、多地图连接管理 - 地图编辑器:项目存取改为存入地图管理统一目录(同名替换确认),支持 ?map=/?new= 进入,新增右侧可停靠 AI 助手面板 - 集成 PTL 拣选模块;新增车队分配面板(运维总览/筛选联动) - SimpleLiteBuildSync 同步运行时依赖 DLL;重新构建前端静态资源 Co-authored-by: Cursor --- MiGu.Server/Auth/PageCatalog.cs | 1 + MiGu.Server/Configs/ConfigStore.cs | 46 +- MiGu.Server/Configs/DeploymentCatalog.cs | 74 ++++ MiGu.Server/Configs/DeploymentProfile.cs | 65 +++ MiGu.Server/Configs/ExternalIntegrations.cs | 6 +- MiGu.Server/Controllers/AuthController.cs | 25 +- MiGu.Server/Controllers/WizardController.cs | 143 ++++++ MiGu.Server/Launcher/SimpleLiteBuildSync.cs | 26 ++ MiGu.Server/Launcher/SimpleLiteLauncher.cs | 84 +++- .../wwwroot/assets/AnnotationView-DCFcPZNR.js | 1 - .../wwwroot/assets/AnnotationView-W73nG5X1.js | 1 + .../wwwroot/assets/AppShell-BqTC0xJK.js | 1 + ...ell-kSXr3TmZ.css => AppShell-D28lCBtn.css} | 2 +- .../wwwroot/assets/AppShell-DSc4T3s8.js | 1 - ...w-CbH2OR6X.js => AuthRoleView-94zjdzbD.js} | 2 +- ...w-PMCxnAq3.js => CarPanelView-DpfkXBoo.js} | 2 +- ...a18gVS.js => ChargePolicyView-BMq754im.js} | 2 +- ...B4fOVsSK.js => ConfigPageBase-CT6BdvCe.js} | 2 +- .../assets/CustomWidgetView-BKa7U3FY.js | 1 + .../assets/CustomWidgetView-D54ImLde.js | 1 - ...-CrqZ1yEu.js => DashboardView-fmEPf2c4.js} | 30 +- ...-COkUK8gp.js => DeviceHubView-6kHSU9g1.js} | 2 +- .../ExternalIntegrationView-CRYMen0m.js | 1 + .../ExternalIntegrationView-CpHHAMpz.js | 1 - .../FRLD-logo-white-no_title-BE78iS6n.js | 1 + ...w-DLR_9_m5.js => LocationView-BH1yvcsy.js} | 2 +- .../wwwroot/assets/LoginView-Bg_Hrmmj.js | 1 + .../wwwroot/assets/LoginView-BoWUwdFm.js | 1 - .../wwwroot/assets/LoginView-CoJzk9R3.css | 1 - .../wwwroot/assets/LoginView-DKfiSb8w.css | 1 + .../wwwroot/assets/MapEditorView-Blj6CGw0.js | 1 - .../wwwroot/assets/MapEditorView-Bw6vCiWq.css | 1 + .../wwwroot/assets/MapEditorView-Cnbi0NJ-.css | 1 - .../wwwroot/assets/MapEditorView-DET9bUZJ.js | 1 + .../assets/MapManagementView-AlLVMANn.css | 1 + .../assets/MapManagementView-D5EJh6kt.js | 4 + ...09.js => MapMonitorConfigView-16D4TNib.js} | 2 +- .../wwwroot/assets/MapMonitorView-DEfDktpd.js | 2 + ...YIzKiz.css => MapMonitorView-Du9_ePeE.css} | 2 +- .../wwwroot/assets/MapMonitorView-oh-hd20C.js | 2 - ...YWhls.js => MissionEditorView-Cq1tHD-Z.js} | 2 +- ...Up.js => MonitorDashboardView-9UzVNNbL.js} | 2 +- .../wwwroot/assets/MonitorMapView-Chf_3bcP.js | 1 - .../wwwroot/assets/MonitorMapView-h38_wi8I.js | 1 + ...2vXw.js => OpsActionPanelView-DP5WSM1Z.js} | 4 +- ...-CyPTl7WY.js => OpsConfigView-Cx4yfu4M.js} | 2 +- ...xbOGfWF.js => PermissionGuard-DUg2FNrM.js} | 2 +- ...w-B1Y9yN9o.js => PlaybackView-CSBKNt4K.js} | 2 +- ...yHjFKr.js => ProcessPanelView-BaRn0PJa.js} | 2 +- ...f.js => ProjectPropertiesView-4-x-FH30.js} | 4 +- ....js => ReflectionManagerPanel-CKQWtKhJ.js} | 6 +- .../assets/RoutingPolicyView-CIGTEJMB.js | 1 - .../assets/RoutingPolicyView-EphbUSUM.js | 1 + .../assets/ScenarioTemplateView-2tn-jb3L.js | 1 + .../assets/ScenarioTemplateView-BfXftOcX.js | 1 - ...2wxU0Us.js => ScriptPanelView-BNpYhbAt.js} | 2 +- ...p7yHN.js => ServiceStatusView-COa3vbpj.js} | 2 +- ...XZ6aVu.js => SystemConfigView-4Mxf2af4.js} | 4 +- .../assets/TaskAllocationView-B6Eq3RnB.js | 1 - .../assets/TaskAllocationView-Dm9F_jCO.js | 1 + ...xOZBg8GQ.js => TrackTableView-BEdpBnup.js} | 2 +- ...vpgC5iS.js => TrafficRuleView-zSMLh9vH.js} | 2 +- .../assets/VehicleHubView-BySAdLFB.css | 1 + .../wwwroot/assets/VehicleHubView-CG4ybiip.js | 1 + .../assets/VehicleHubView-D_m8HS87.css | 1 - .../wwwroot/assets/VehicleHubView-cMWfwwrB.js | 1 - .../wwwroot/assets/WizardView-D_EJbvb2.css | 1 + .../wwwroot/assets/WizardView-DniZ4Y5l.js | 1 + ...3D-CE3B9LLc.js => Workspace3D-B2WrMPji.js} | 2 +- .../{index-CC_hCGHZ.js => index-368apsQG.js} | 40 +- ...{index-B461CCj6.css => index-CYGH8MrE.css} | 2 +- .../wwwroot/assets/mapEdit-BiAzHHno.js | 1 + .../wwwroot/assets/mapEdit-Z_uHqZwy.js | 1 - ...XjjB.js => monitorConfigCache-BtW5W6GI.js} | 2 +- .../{ops-DQIK15vn.js => ops-DA63t9Ed.js} | 2 +- ...ion-D6ulMg5U.js => projection-nWPnmoX1.js} | 2 +- ...ion-CTDOoTn5.js => reflection-C0K7EJ_N.js} | 2 +- ...GhVPpt.js => useMapEditStream-CoKaGT-a.js} | 2 +- ...vmm.js => useProjectionStream-C94Y5UgX.js} | 2 +- MiGu.Server/wwwroot/index.html | 4 +- .../apps/simple-platform-vue/components.d.ts | 5 + .../src/api/mapConnection.ts | 127 ++++++ .../simple-platform-vue/src/api/mapEdit.ts | 133 ++++++ .../simple-platform-vue/src/api/wizard.ts | 78 ++++ .../src/api/workspaceToolbar.ts | 12 + .../components/fleet/FleetAllocationPanel.vue | 281 ++++++++++++ .../map-editor/AiAssistantPanel.vue | 419 ++++++++++++++++++ .../map-editor/AiGenerateDialog.vue | 16 + .../src/components/map-editor/EditTopBar.vue | 32 +- .../map-manage/MapConnectionPanel.vue | 302 +++++++++++++ .../components/map-manage/MapMergePanel.vue | 223 ++++++++++ .../src/composables/useFleetGroups.ts | 39 ++ .../src/layouts/AppShell.vue | 4 + .../src/mock/data/configs.ts | 3 +- .../apps/simple-platform-vue/src/mock/rbac.ts | 1 + .../simple-platform-vue/src/router/index.ts | 14 + .../simple-platform-vue/src/stores/auth.ts | 14 +- .../simple-platform-vue/src/styles/theme.css | 18 + .../simple-platform-vue/src/types/auth.ts | 8 + .../simple-platform-vue/src/types/config.ts | 2 + .../simple-platform-vue/src/types/wizard.ts | 72 +++ .../src/views/LoginView.vue | 400 ++++++++++------- .../src/views/WizardView.vue | 364 +++++++++++++++ .../src/views/admin/MapEditorView.vue | 240 +++++++--- .../src/views/admin/MapManagementView.vue | 279 ++++++++++++ .../src/views/admin/MapMonitorView.vue | 53 ++- .../admin/config/ExternalIntegrationView.vue | 12 +- .../views/admin/config/FleetLifecycleView.vue | 22 +- .../src/views/shared/VehicleHubView.vue | 48 +- 109 files changed, 3481 insertions(+), 398 deletions(-) create mode 100644 MiGu.Server/Configs/DeploymentCatalog.cs create mode 100644 MiGu.Server/Configs/DeploymentProfile.cs create mode 100644 MiGu.Server/Controllers/WizardController.cs delete mode 100644 MiGu.Server/wwwroot/assets/AnnotationView-DCFcPZNR.js create mode 100644 MiGu.Server/wwwroot/assets/AnnotationView-W73nG5X1.js create mode 100644 MiGu.Server/wwwroot/assets/AppShell-BqTC0xJK.js rename MiGu.Server/wwwroot/assets/{AppShell-kSXr3TmZ.css => AppShell-D28lCBtn.css} (84%) delete mode 100644 MiGu.Server/wwwroot/assets/AppShell-DSc4T3s8.js rename MiGu.Server/wwwroot/assets/{AuthRoleView-CbH2OR6X.js => AuthRoleView-94zjdzbD.js} (72%) rename MiGu.Server/wwwroot/assets/{CarPanelView-PMCxnAq3.js => CarPanelView-DpfkXBoo.js} (75%) rename MiGu.Server/wwwroot/assets/{ChargePolicyView-BJa18gVS.js => ChargePolicyView-BMq754im.js} (76%) rename MiGu.Server/wwwroot/assets/{ConfigPageBase-B4fOVsSK.js => ConfigPageBase-CT6BdvCe.js} (75%) create mode 100644 MiGu.Server/wwwroot/assets/CustomWidgetView-BKa7U3FY.js delete mode 100644 MiGu.Server/wwwroot/assets/CustomWidgetView-D54ImLde.js rename MiGu.Server/wwwroot/assets/{DashboardView-CrqZ1yEu.js => DashboardView-fmEPf2c4.js} (97%) rename MiGu.Server/wwwroot/assets/{DeviceHubView-COkUK8gp.js => DeviceHubView-6kHSU9g1.js} (80%) create mode 100644 MiGu.Server/wwwroot/assets/ExternalIntegrationView-CRYMen0m.js delete mode 100644 MiGu.Server/wwwroot/assets/ExternalIntegrationView-CpHHAMpz.js create mode 100644 MiGu.Server/wwwroot/assets/FRLD-logo-white-no_title-BE78iS6n.js rename MiGu.Server/wwwroot/assets/{LocationView-DLR_9_m5.js => LocationView-BH1yvcsy.js} (60%) create mode 100644 MiGu.Server/wwwroot/assets/LoginView-Bg_Hrmmj.js delete mode 100644 MiGu.Server/wwwroot/assets/LoginView-BoWUwdFm.js delete mode 100644 MiGu.Server/wwwroot/assets/LoginView-CoJzk9R3.css create mode 100644 MiGu.Server/wwwroot/assets/LoginView-DKfiSb8w.css delete mode 100644 MiGu.Server/wwwroot/assets/MapEditorView-Blj6CGw0.js create mode 100644 MiGu.Server/wwwroot/assets/MapEditorView-Bw6vCiWq.css delete mode 100644 MiGu.Server/wwwroot/assets/MapEditorView-Cnbi0NJ-.css create mode 100644 MiGu.Server/wwwroot/assets/MapEditorView-DET9bUZJ.js create mode 100644 MiGu.Server/wwwroot/assets/MapManagementView-AlLVMANn.css create mode 100644 MiGu.Server/wwwroot/assets/MapManagementView-D5EJh6kt.js rename MiGu.Server/wwwroot/assets/{MapMonitorConfigView-CRLVKr09.js => MapMonitorConfigView-16D4TNib.js} (53%) create mode 100644 MiGu.Server/wwwroot/assets/MapMonitorView-DEfDktpd.js rename MiGu.Server/wwwroot/assets/{MapMonitorView-_OYIzKiz.css => MapMonitorView-Du9_ePeE.css} (91%) delete mode 100644 MiGu.Server/wwwroot/assets/MapMonitorView-oh-hd20C.js rename MiGu.Server/wwwroot/assets/{MissionEditorView-1UuYWhls.js => MissionEditorView-Cq1tHD-Z.js} (74%) rename MiGu.Server/wwwroot/assets/{MonitorDashboardView-C_9mDYUp.js => MonitorDashboardView-9UzVNNbL.js} (60%) delete mode 100644 MiGu.Server/wwwroot/assets/MonitorMapView-Chf_3bcP.js create mode 100644 MiGu.Server/wwwroot/assets/MonitorMapView-h38_wi8I.js rename MiGu.Server/wwwroot/assets/{OpsActionPanelView-iA-v2vXw.js => OpsActionPanelView-DP5WSM1Z.js} (62%) rename MiGu.Server/wwwroot/assets/{OpsConfigView-CyPTl7WY.js => OpsConfigView-Cx4yfu4M.js} (64%) rename MiGu.Server/wwwroot/assets/{PermissionGuard-DxbOGfWF.js => PermissionGuard-DUg2FNrM.js} (53%) rename MiGu.Server/wwwroot/assets/{PlaybackView-B1Y9yN9o.js => PlaybackView-CSBKNt4K.js} (61%) rename MiGu.Server/wwwroot/assets/{ProcessPanelView-BIyHjFKr.js => ProcessPanelView-BaRn0PJa.js} (71%) rename MiGu.Server/wwwroot/assets/{ProjectPropertiesView-0BoQ0Htf.js => ProjectPropertiesView-4-x-FH30.js} (70%) rename MiGu.Server/wwwroot/assets/{ReflectionManagerPanel-CpDwI_uG.js => ReflectionManagerPanel-CKQWtKhJ.js} (75%) delete mode 100644 MiGu.Server/wwwroot/assets/RoutingPolicyView-CIGTEJMB.js create mode 100644 MiGu.Server/wwwroot/assets/RoutingPolicyView-EphbUSUM.js create mode 100644 MiGu.Server/wwwroot/assets/ScenarioTemplateView-2tn-jb3L.js delete mode 100644 MiGu.Server/wwwroot/assets/ScenarioTemplateView-BfXftOcX.js rename MiGu.Server/wwwroot/assets/{ScriptPanelView-B2wxU0Us.js => ScriptPanelView-BNpYhbAt.js} (80%) rename MiGu.Server/wwwroot/assets/{ServiceStatusView-BAEp7yHN.js => ServiceStatusView-COa3vbpj.js} (77%) rename MiGu.Server/wwwroot/assets/{SystemConfigView-DFXZ6aVu.js => SystemConfigView-4Mxf2af4.js} (72%) delete mode 100644 MiGu.Server/wwwroot/assets/TaskAllocationView-B6Eq3RnB.js create mode 100644 MiGu.Server/wwwroot/assets/TaskAllocationView-Dm9F_jCO.js rename MiGu.Server/wwwroot/assets/{TrackTableView-xOZBg8GQ.js => TrackTableView-BEdpBnup.js} (75%) rename MiGu.Server/wwwroot/assets/{TrafficRuleView-uvpgC5iS.js => TrafficRuleView-zSMLh9vH.js} (66%) create mode 100644 MiGu.Server/wwwroot/assets/VehicleHubView-BySAdLFB.css create mode 100644 MiGu.Server/wwwroot/assets/VehicleHubView-CG4ybiip.js delete mode 100644 MiGu.Server/wwwroot/assets/VehicleHubView-D_m8HS87.css delete mode 100644 MiGu.Server/wwwroot/assets/VehicleHubView-cMWfwwrB.js create mode 100644 MiGu.Server/wwwroot/assets/WizardView-D_EJbvb2.css create mode 100644 MiGu.Server/wwwroot/assets/WizardView-DniZ4Y5l.js rename MiGu.Server/wwwroot/assets/{Workspace3D-CE3B9LLc.js => Workspace3D-B2WrMPji.js} (52%) rename MiGu.Server/wwwroot/assets/{index-CC_hCGHZ.js => index-368apsQG.js} (81%) rename MiGu.Server/wwwroot/assets/{index-B461CCj6.css => index-CYGH8MrE.css} (87%) create mode 100644 MiGu.Server/wwwroot/assets/mapEdit-BiAzHHno.js delete mode 100644 MiGu.Server/wwwroot/assets/mapEdit-Z_uHqZwy.js rename MiGu.Server/wwwroot/assets/{monitorConfigCache-B5CZXjjB.js => monitorConfigCache-BtW5W6GI.js} (72%) rename MiGu.Server/wwwroot/assets/{ops-DQIK15vn.js => ops-DA63t9Ed.js} (69%) rename MiGu.Server/wwwroot/assets/{projection-D6ulMg5U.js => projection-nWPnmoX1.js} (92%) rename MiGu.Server/wwwroot/assets/{reflection-CTDOoTn5.js => reflection-C0K7EJ_N.js} (98%) rename MiGu.Server/wwwroot/assets/{useMapEditStream-BAGhVPpt.js => useMapEditStream-CoKaGT-a.js} (83%) rename MiGu.Server/wwwroot/assets/{useProjectionStream-C_klWvmm.js => useProjectionStream-C94Y5UgX.js} (60%) create mode 100644 frontends/apps/simple-platform-vue/src/api/mapConnection.ts create mode 100644 frontends/apps/simple-platform-vue/src/api/wizard.ts create mode 100644 frontends/apps/simple-platform-vue/src/components/fleet/FleetAllocationPanel.vue create mode 100644 frontends/apps/simple-platform-vue/src/components/map-editor/AiAssistantPanel.vue create mode 100644 frontends/apps/simple-platform-vue/src/components/map-manage/MapConnectionPanel.vue create mode 100644 frontends/apps/simple-platform-vue/src/components/map-manage/MapMergePanel.vue create mode 100644 frontends/apps/simple-platform-vue/src/composables/useFleetGroups.ts create mode 100644 frontends/apps/simple-platform-vue/src/types/wizard.ts create mode 100644 frontends/apps/simple-platform-vue/src/views/WizardView.vue create mode 100644 frontends/apps/simple-platform-vue/src/views/admin/MapManagementView.vue diff --git a/MiGu.Server/Auth/PageCatalog.cs b/MiGu.Server/Auth/PageCatalog.cs index bc807ab..df82dc2 100644 --- a/MiGu.Server/Auth/PageCatalog.cs +++ b/MiGu.Server/Auth/PageCatalog.cs @@ -33,6 +33,7 @@ public static class PageCatalog new("admin-playback", "调度回放", "概览", ScopePlatform), // ── 管理端 / Platform:设计与编排 ── + new("admin-maps", "地图管理", "设计与编排", ScopePlatform), new("admin-map-editor", "地图编辑", "设计与编排", ScopePlatform), new("admin-project-properties", "项目属性", "设计与编排", ScopePlatform), new("admin-tracks", "场景管理", "设计与编排", ScopePlatform), diff --git a/MiGu.Server/Configs/ConfigStore.cs b/MiGu.Server/Configs/ConfigStore.cs index 8eb8523..1fcb379 100644 --- a/MiGu.Server/Configs/ConfigStore.cs +++ b/MiGu.Server/Configs/ConfigStore.cs @@ -5,7 +5,7 @@ namespace MiGu.Server.Configs; /// /// 配置中心存储(内存 + JSON 文件持久化占位)。 -/// 14 个 section 对应 ARCHITECTURE.md §9 的 13+1 维度。 +/// 前 14 个 section 对应 ARCHITECTURE.md §9 的 13+1 维度;外加 deployment —— 登录后「配置向导」的部署画像。 /// 真实落地时由 SimpleShared.Persistence 接入 EF Core,并配合 YARP 下发至 SimpleLite。 /// public sealed class ConfigStore @@ -13,7 +13,8 @@ public sealed class ConfigStore public static readonly string[] AllSections = { "system", "integrations", "routing", "vehicle", "charge", "task", - "traffic", "auth", "device", "fleet", "scenario", "location", "ops", "widget" + "traffic", "auth", "device", "fleet", "scenario", "location", "ops", "widget", + "deployment" }; public sealed record Envelope(string Section, int Version, DateTimeOffset UpdatedAt, object Payload); @@ -67,6 +68,46 @@ public sealed class ConfigStore public IEnumerable List() => AllSections.Select(Get); + /// + /// 强类型读取部署画像(deployment section)。兼容 Payload 为 (默认值场景) + /// 或 (已持久化场景)两种形态,并把 null 列表/空字符串规整为安全默认值, + /// 供 AuthController(NeedsWizard)/ WizardController / Launcher 直接使用。 + /// + public DeploymentProfile GetDeployment() + { + var env = Get("deployment"); + var dp = env.Payload switch + { + DeploymentProfile d => d, + JsonElement el => SafeDeserializeDeployment(el), + _ => DeploymentProfile.Default() + }; + return new DeploymentProfile( + dp.Configured, + string.IsNullOrWhiteSpace(dp.PlatformType) ? "standard" : dp.PlatformType, + dp.Modules ?? new List(), + dp.NavigationKinds ?? new List(), + dp.Scenarios ?? new List(), + dp.UpdatedBy ?? ""); + } + + /// 以强类型保存部署画像(统一经 走版本/持久化/camelCase 序列化)。 + public Envelope PutDeployment(DeploymentProfile profile) + { + var el = JsonSerializer.SerializeToElement(profile, _jsonOpts); + return Put("deployment", el); + } + + private DeploymentProfile SafeDeserializeDeployment(JsonElement el) + { + try { return el.Deserialize(_jsonOpts) ?? DeploymentProfile.Default(); } + catch (Exception ex) + { + _logger.LogWarning(ex, "反序列化 deployment 失败,回退默认值"); + return DeploymentProfile.Default(); + } + } + private static object JsonElementToObject(JsonElement el) { // 简化:直接保留 JsonElement,让 STJ 在响应时再原样写回 @@ -126,6 +167,7 @@ public sealed class ConfigStore "location" => LocationManagement.Default(), "ops" => OpsConfig.Default(), "widget" => CustomWidgetConfig.Default(), + "deployment" => DeploymentProfile.Default(), _ => new { } }; diff --git a/MiGu.Server/Configs/DeploymentCatalog.cs b/MiGu.Server/Configs/DeploymentCatalog.cs new file mode 100644 index 0000000..ed31c68 --- /dev/null +++ b/MiGu.Server/Configs/DeploymentCatalog.cs @@ -0,0 +1,74 @@ +namespace MiGu.Server.Configs; + +/// +/// 配置向导「可选项目录」与「选型 → 平台能力」映射的单一事实来源。 +/// +/// 前端向导从 GET /api/wizard/options 拿到这些可选项渲染勾选框;后端 +/// 在「按选型裁剪菜单」时把模块 +/// 映射为 PageCatalog 的可见页 id。集中定义,避免前后端各写一份导致漂移。 +/// +public static class DeploymentCatalog +{ + /// 一个可勾选项。 是稳定机器标识, 用于前端分组展示。 + public record Option(string Id, string Name, string Group, string Description); + + /// 导航方式(多选,一等维度)。id 与 的 key 对齐。 + public static readonly IReadOnlyList