feat(platform): 部署配置向导 + 地图管理,地图编辑器接入统一存取与 AI 助手
- 配置向导:登录按 deployment 画像引导平台选型(导航方式/模块/场景),未完成则路由守卫强制进入 /wizard;选型驱动菜单按需裁剪,并联动 SimpleLite 写 plugins/active-scenes.json + 透传 --scenes 选择性加载导航场景插件 - 地图管理页:服务器地图列表/使用/重命名/删除、地图合并、多地图连接管理 - 地图编辑器:项目存取改为存入地图管理统一目录(同名替换确认),支持 ?map=/?new= 进入,新增右侧可停靠 AI 助手面板 - 集成 PTL 拣选模块;新增车队分配面板(运维总览/筛选联动) - SimpleLiteBuildSync 同步运行时依赖 DLL;重新构建前端静态资源 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace MiGu.Server.Configs;
|
||||
|
||||
/// <summary>
|
||||
/// 配置中心存储(内存 + JSON 文件持久化占位)。
|
||||
/// 14 个 section 对应 ARCHITECTURE.md §9 的 13+1 维度。
|
||||
/// 前 14 个 section 对应 ARCHITECTURE.md §9 的 13+1 维度;外加 deployment —— 登录后「配置向导」的部署画像。
|
||||
/// 真实落地时由 SimpleShared.Persistence 接入 EF Core,并配合 YARP 下发至 SimpleLite。
|
||||
/// </summary>
|
||||
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<Envelope> List() => AllSections.Select(Get);
|
||||
|
||||
/// <summary>
|
||||
/// 强类型读取部署画像(<c>deployment</c> section)。兼容 Payload 为 <see cref="DeploymentProfile"/>(默认值场景)
|
||||
/// 或 <see cref="JsonElement"/>(已持久化场景)两种形态,并把 null 列表/空字符串规整为安全默认值,
|
||||
/// 供 AuthController(NeedsWizard)/ WizardController / Launcher 直接使用。
|
||||
/// </summary>
|
||||
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<string>(),
|
||||
dp.NavigationKinds ?? new List<string>(),
|
||||
dp.Scenarios ?? new List<string>(),
|
||||
dp.UpdatedBy ?? "");
|
||||
}
|
||||
|
||||
/// <summary>以强类型保存部署画像(统一经 <see cref="Put"/> 走版本/持久化/camelCase 序列化)。</summary>
|
||||
public Envelope PutDeployment(DeploymentProfile profile)
|
||||
{
|
||||
var el = JsonSerializer.SerializeToElement(profile, _jsonOpts);
|
||||
return Put("deployment", el);
|
||||
}
|
||||
|
||||
private DeploymentProfile SafeDeserializeDeployment(JsonElement el)
|
||||
{
|
||||
try { return el.Deserialize<DeploymentProfile>(_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 { }
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
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 暂无专属配置页,未纳入映射(选中不影响菜单)。</summary>
|
||||
public static readonly IReadOnlyDictionary<string, string[]> ModuleToPages =
|
||||
new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["wms"] = new[] { "admin-config-location" },
|
||||
};
|
||||
|
||||
/// <summary>所有「可被选型控制」的页面 Key(Module → 页 映射值的并集)。</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);
|
||||
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);
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
namespace MiGu.Server.Configs;
|
||||
|
||||
/// <summary>
|
||||
/// 部署画像 —— 登录后「配置向导」的结果,对应 ConfigStore 的 <c>deployment</c> section
|
||||
/// (持久化于 <c>data/config-deployment.json</c>)。
|
||||
///
|
||||
/// <para>它是「按选型裁剪」的单一事实来源:</para>
|
||||
/// <list type="number">
|
||||
/// <item><see cref="Configured"/>=false 时,登录返回 <c>NeedsWizard=true</c>,前端跳转配置向导;</item>
|
||||
/// <item><see cref="NavigationKinds"/> 经 <see cref="ToActiveSceneIds"/> 映射为 SimpleLite 场景 id,
|
||||
/// 由 Launcher 写入 <c>plugins/active-scenes.json</c> / 透传 <c>--scenes</c>,驱动内核「选择性加载」导航场景插件;</item>
|
||||
/// <item><see cref="Modules"/> 驱动平台菜单与能力裁剪(PageCatalog 可见页)。</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
/// <param name="Configured">向导是否已完成。false = 登录后强制进入配置向导。</param>
|
||||
/// <param name="PlatformType">平台主定位(仅用于 UI 标题/默认值),如 <c>standard</c> / <c>wcs</c> / <c>wms-wcs</c> / <c>custom</c>。</param>
|
||||
/// <param name="Modules">启用的功能模块(多选),暂定 <c>wms</c> / <c>ptl</c>。</param>
|
||||
/// <param name="NavigationKinds">导航方式(多选,一等维度),取值 <c>magnetic</c> / <c>qrcode</c> / <c>laser</c>,与 SimpleCore.NavKind 对齐。</param>
|
||||
/// <param name="Scenarios">选用的业务场景模板 id(来自 ScenarioTemplateConfig),如 <c>tpl-sps</c> / <c>tpl-pack</c>。</param>
|
||||
/// <param name="UpdatedBy">最近一次保存向导的用户名(审计用)。</param>
|
||||
public record DeploymentProfile(
|
||||
bool Configured,
|
||||
string PlatformType,
|
||||
List<string> Modules,
|
||||
List<string> NavigationKinds,
|
||||
List<string> Scenarios,
|
||||
string UpdatedBy)
|
||||
{
|
||||
public static DeploymentProfile Default() => new(
|
||||
Configured: false,
|
||||
PlatformType: "standard",
|
||||
Modules: new List<string>(),
|
||||
NavigationKinds: new List<string>(),
|
||||
Scenarios: new List<string>(),
|
||||
UpdatedBy: "");
|
||||
|
||||
/// <summary>
|
||||
/// 导航方式 → SimpleLite 场景 id 映射。与 <c>scene.json.id</c>、<c>SimpleCore.Navigation.NavKind</c>
|
||||
/// 以及内核 <c>active-scenes.json.activeScenes</c> 一致;这是平台与内核之间「导航选型」的契约约定。
|
||||
/// </summary>
|
||||
public static readonly IReadOnlyDictionary<string, string> NavKindToSceneId =
|
||||
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["magnetic"] = "scene.magnetic",
|
||||
["qrcode"] = "scene.qrcode",
|
||||
["laser"] = "scene.laser",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 把已选 <see cref="NavigationKinds"/> 映射为 SimpleLite 激活场景 id 列表(去重、忽略未知项、保持选择顺序)。
|
||||
/// 供 Launcher 写 <c>active-scenes.json</c> / 拼 <c>--scenes</c> 参数使用。
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> ToActiveSceneIds()
|
||||
{
|
||||
var result = new List<string>();
|
||||
if (NavigationKinds == null) return result;
|
||||
foreach (var k in NavigationKinds)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(k)) continue;
|
||||
if (NavKindToSceneId.TryGetValue(k.Trim(), out var sceneId) && !result.Contains(sceneId))
|
||||
result.Add(sceneId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,12 @@ public record EndpointDescriptor(string Id, string Name, string Url, bool Enable
|
||||
public record ExternalIntegrations(
|
||||
List<EndpointDescriptor> Mes,
|
||||
List<EndpointDescriptor> Wms,
|
||||
List<EndpointDescriptor> Rcs)
|
||||
List<EndpointDescriptor> Rcs,
|
||||
List<EndpointDescriptor> Ptl)
|
||||
{
|
||||
public static ExternalIntegrations Default() => new(
|
||||
Mes: new List<EndpointDescriptor> { new("mes-1", "MES 主线", "http://mes.lan/api", true) },
|
||||
Wms: new List<EndpointDescriptor> { new("wms-1", "WMS 仓储", "http://wms.lan/api", true) },
|
||||
Rcs: new List<EndpointDescriptor>());
|
||||
Rcs: new List<EndpointDescriptor>(),
|
||||
Ptl: new List<EndpointDescriptor> { new("ptl-1", "PTL 拣选", "http://ptl.lan/api", true) });
|
||||
}
|
||||
|
||||
@@ -35,7 +35,8 @@ public class AuthController : ControllerBase
|
||||
string RunMode,
|
||||
EffectivePermissions EffectivePermissions,
|
||||
string? LaunchStatus = null,
|
||||
string? LaunchWarning = null);
|
||||
string? LaunchWarning = null,
|
||||
bool NeedsWizard = false);
|
||||
|
||||
public record AuthUserDto(string Id, string Username, string DisplayName, List<string> Roles);
|
||||
|
||||
@@ -49,23 +50,29 @@ public class AuthController : ControllerBase
|
||||
AuthUserDto User,
|
||||
string Scope,
|
||||
string RunMode,
|
||||
EffectivePermissions EffectivePermissions);
|
||||
EffectivePermissions EffectivePermissions,
|
||||
bool NeedsWizard = false);
|
||||
|
||||
private const string CookieName = "simple.auth.token";
|
||||
|
||||
private readonly RbacStore _rbac;
|
||||
private readonly JwtIssuer _jwt;
|
||||
private readonly SimpleLiteLauncher _launcher;
|
||||
private readonly ConfigStore _config;
|
||||
private readonly ILogger<AuthController> _log;
|
||||
|
||||
public AuthController(RbacStore rbac, JwtIssuer jwt, SimpleLiteLauncher launcher, ILogger<AuthController> log)
|
||||
public AuthController(RbacStore rbac, JwtIssuer jwt, SimpleLiteLauncher launcher, ConfigStore config, ILogger<AuthController> log)
|
||||
{
|
||||
_rbac = rbac;
|
||||
_jwt = jwt;
|
||||
_launcher = launcher;
|
||||
_config = config;
|
||||
_log = log;
|
||||
}
|
||||
|
||||
/// <summary>是否需要进入配置向导(部署画像尚未完成)。登录 / me / switchScope 三处一致回填。</summary>
|
||||
private bool NeedsWizard() => !_config.GetDeployment().Configured;
|
||||
|
||||
[HttpPost("login")]
|
||||
[AllowAnonymous]
|
||||
public async Task<ActionResult<LoginResponse>> Login([FromBody] LoginRequest req)
|
||||
@@ -106,7 +113,8 @@ public class AuthController : ControllerBase
|
||||
var dto = new AuthUserDto(user.Id, user.Username, user.DisplayName, roleNames);
|
||||
return Ok(new LoginResponse(token, dto, req.Scope, runMode, perm,
|
||||
LaunchStatus: launchResult?.Status,
|
||||
LaunchWarning: launchResult?.Warning));
|
||||
LaunchWarning: launchResult?.Warning,
|
||||
NeedsWizard: NeedsWizard()));
|
||||
}
|
||||
|
||||
[HttpPost("logout")]
|
||||
@@ -140,7 +148,7 @@ public class AuthController : ControllerBase
|
||||
|
||||
var (perm, roleNames, _) = BuildSession(user, scope);
|
||||
var dto = new AuthUserDto(user.Id, user.Username, user.DisplayName, roleNames);
|
||||
return Ok(new MeResponse(dto, scope, InferRunMode(), perm));
|
||||
return Ok(new MeResponse(dto, scope, InferRunMode(), perm, NeedsWizard()));
|
||||
}
|
||||
|
||||
/// <summary>用同一身份切换 scope 并重发 token + perms。</summary>
|
||||
@@ -166,7 +174,8 @@ public class AuthController : ControllerBase
|
||||
SetAuthCookie(token);
|
||||
|
||||
var dto = new AuthUserDto(user.Id, user.Username, user.DisplayName, roleNames);
|
||||
return Ok(new LoginResponse(token, dto, req.Scope, InferRunMode(), perm));
|
||||
return Ok(new LoginResponse(token, dto, req.Scope, InferRunMode(), perm,
|
||||
NeedsWizard: NeedsWizard()));
|
||||
}
|
||||
|
||||
public record SwitchScopeRequest(string Scope);
|
||||
@@ -180,7 +189,9 @@ public class AuthController : ControllerBase
|
||||
private (EffectivePermissions perm, List<string> roleNames, string token) BuildSession(RbacUser user, string scope)
|
||||
{
|
||||
var eff = _rbac.ComputeEffective(user, scope);
|
||||
var perm = new EffectivePermissions(user.Id, 1, eff.Ops, eff.Widgets, eff.Pages);
|
||||
// 按部署画像裁剪可见页:未启用的功能/模块对应的配置页从菜单隐藏(向导未完成则不裁剪)。
|
||||
var pages = DeploymentCatalog.FilterPagesByDeployment(eff.Pages, _config.GetDeployment());
|
||||
var perm = new EffectivePermissions(user.Id, 1, eff.Ops, eff.Widgets, pages);
|
||||
var roleNames = _rbac.RoleNamesOf(user);
|
||||
// ops claim 写入有效操作码(含可能的 "*"),供 RbacAdmin policy 判定管理权限。
|
||||
var token = _jwt.Issue(user.Id, user.Username, scope, roleNames, eff.Ops);
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MiGu.Server.Configs;
|
||||
using MiGu.Server.Launcher;
|
||||
|
||||
namespace MiGu.Server.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 配置向导 API。登录后若 <c>deployment.Configured=false</c>(见 <c>LoginResponse.NeedsWizard</c>),
|
||||
/// 前端进入向导:
|
||||
/// <list type="bullet">
|
||||
/// <item><c>GET /api/wizard/options</c>:可选项目录(导航方式 / 模块 / 业务场景模板)。</item>
|
||||
/// <item><c>GET /api/wizard/profile</c>:回显当前部署画像(含由导航选型推导的激活场景 id)。</item>
|
||||
/// <item><c>PUT /api/wizard/profile</c>:保存并置 <c>Configured=true</c>。</item>
|
||||
/// <item><c>POST /api/wizard/reset</c>:把 <c>Configured</c> 置回 false 以重新引导(保留草稿)。</item>
|
||||
/// </list>
|
||||
/// 说明:保存时即把选型固化为单一事实来源 <c>deployment</c> section,并同步联动 Launcher ——
|
||||
/// <see cref="SaveProfile"/> 调 <c>WriteActiveScenes</c> 写 <c>plugins/active-scenes.json</c> / 透传 <c>--scenes</c>,
|
||||
/// 驱动 SimpleLite 下次启动选择性加载选定的导航场景插件。
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
[Route("api/wizard")]
|
||||
public class WizardController : ControllerBase
|
||||
{
|
||||
private readonly ConfigStore _store;
|
||||
private readonly SimpleLiteLauncher _launcher;
|
||||
private readonly ILogger<WizardController> _log;
|
||||
|
||||
public WizardController(ConfigStore store, SimpleLiteLauncher launcher, ILogger<WizardController> log)
|
||||
{
|
||||
_store = store;
|
||||
_launcher = launcher;
|
||||
_log = log;
|
||||
}
|
||||
|
||||
[HttpGet("options")]
|
||||
public IActionResult Options()
|
||||
{
|
||||
return Ok(new
|
||||
{
|
||||
navigationKinds = DeploymentCatalog.NavigationKinds,
|
||||
modules = DeploymentCatalog.Modules,
|
||||
scenarios = _store.Get("scenario").Payload
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("profile")]
|
||||
public IActionResult GetProfile() => Ok(Project(_store.GetDeployment()));
|
||||
|
||||
/// <summary>当前部署画像对菜单的裁剪结果(供前端做面板/能力级裁剪与排查)。</summary>
|
||||
[HttpGet("effective-pages")]
|
||||
public IActionResult EffectivePages()
|
||||
{
|
||||
var dp = _store.GetDeployment();
|
||||
return Ok(new
|
||||
{
|
||||
configured = dp.Configured,
|
||||
tailorablePages = DeploymentCatalog.TailorablePages(),
|
||||
enabledPages = DeploymentCatalog.EnabledPages(dp),
|
||||
hiddenPages = DeploymentCatalog.HiddenPages(dp)
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPut("profile")]
|
||||
public IActionResult SaveProfile([FromBody] SaveWizardRequest req)
|
||||
{
|
||||
if (req == null)
|
||||
return BadRequest(new { message = "请求体不能为空" });
|
||||
|
||||
var user = User.Identity?.Name ?? User.FindFirstValue(ClaimTypes.NameIdentifier) ?? "";
|
||||
var profile = new DeploymentProfile(
|
||||
Configured: true,
|
||||
PlatformType: string.IsNullOrWhiteSpace(req.PlatformType) ? "standard" : req.PlatformType.Trim(),
|
||||
Modules: Clean(req.Modules),
|
||||
NavigationKinds: Clean(req.NavigationKinds),
|
||||
Scenarios: Clean(req.Scenarios),
|
||||
UpdatedBy: user);
|
||||
|
||||
_store.PutDeployment(profile);
|
||||
|
||||
// 平台 → 内核联动:把导航选型写入 SimpleLite 的 plugins/active-scenes.json(下次启动选择性加载;
|
||||
// 已运行实例可由前端再调 POST /api/sl/projection/scenes/apply 触发增量 reload)。
|
||||
var sceneIds = profile.ToActiveSceneIds();
|
||||
var write = _launcher.WriteActiveScenes(sceneIds, alwaysLoad: null, source: "deployment-profile");
|
||||
|
||||
_log.LogInformation("部署向导已保存 by={User} nav=[{Nav}] scenes=[{Scenes}] activeScenesWritten={Ok}",
|
||||
user, string.Join(",", profile.NavigationKinds), string.Join(",", sceneIds), write.Ok);
|
||||
|
||||
return Ok(Project(profile, write));
|
||||
}
|
||||
|
||||
[HttpPost("reset")]
|
||||
public IActionResult Reset()
|
||||
{
|
||||
var reset = _store.GetDeployment() with { Configured = false };
|
||||
_store.PutDeployment(reset);
|
||||
return Ok(Project(reset));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统一对外投影:camelCase 字段 + 推导出的 activeSceneIds(导航选型 → 内核场景 id)+
|
||||
/// hiddenPages(被裁剪的菜单页)+ 可选的 activeScenesWrite(保存时写 active-scenes.json 的结果)。
|
||||
/// </summary>
|
||||
private static object Project(DeploymentProfile dp, SimpleLiteLauncher.ActiveScenesWriteResult? write = null) => new
|
||||
{
|
||||
configured = dp.Configured,
|
||||
platformType = dp.PlatformType,
|
||||
modules = dp.Modules,
|
||||
navigationKinds = dp.NavigationKinds,
|
||||
scenarios = dp.Scenarios,
|
||||
updatedBy = dp.UpdatedBy,
|
||||
activeSceneIds = dp.ToActiveSceneIds(),
|
||||
hiddenPages = DeploymentCatalog.HiddenPages(dp),
|
||||
activeScenesWrite = write == null ? null : new
|
||||
{
|
||||
ok = write.Value.Ok,
|
||||
path = write.Value.Path,
|
||||
error = write.Value.Error
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>去空白、去重(大小写不敏感)、保持顺序。</summary>
|
||||
private static List<string> Clean(List<string>? items)
|
||||
{
|
||||
var result = new List<string>();
|
||||
if (items == null) return result;
|
||||
foreach (var s in items)
|
||||
{
|
||||
var t = s?.Trim();
|
||||
if (!string.IsNullOrEmpty(t) && !result.Contains(t, StringComparer.OrdinalIgnoreCase))
|
||||
result.Add(t!);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public record SaveWizardRequest(
|
||||
string? PlatformType,
|
||||
List<string>? Modules,
|
||||
List<string>? NavigationKinds,
|
||||
List<string>? Scenarios);
|
||||
}
|
||||
@@ -45,6 +45,8 @@ public static class SimpleLiteBuildSync
|
||||
log?.LogInformation("[SimpleLiteBuildSync] 已同步 {Src} → {Dst}", objExe, binExe);
|
||||
}
|
||||
|
||||
SyncRuntimeDeps(objDll, binDir, log);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -67,6 +69,30 @@ public static class SimpleLiteBuildSync
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>同步 obj 输出目录中的运行时依赖(Costura 未嵌入或需独立存在的 DLL)。</summary>
|
||||
private static void SyncRuntimeDeps(string objDll, string binDir, ILogger? log)
|
||||
{
|
||||
var objDir = Path.GetDirectoryName(objDll);
|
||||
if (string.IsNullOrEmpty(objDir)) return;
|
||||
|
||||
var names = new[] { "LessokajiWeaverUtilities.dll" };
|
||||
foreach (var name in names)
|
||||
{
|
||||
var src = Path.Combine(objDir, name);
|
||||
if (!File.Exists(src))
|
||||
{
|
||||
var deps = Path.Combine(objDir, "..", "..", "tools", "deps", name);
|
||||
deps = Path.GetFullPath(deps);
|
||||
if (File.Exists(deps)) src = deps;
|
||||
else continue;
|
||||
}
|
||||
|
||||
var dst = Path.Combine(binDir, name);
|
||||
File.Copy(src, dst, true);
|
||||
log?.LogInformation("[SimpleLiteBuildSync] 已同步依赖 {Name}", name);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryResolvePaths(string contentRoot, out string objDll, out string objExe, out string binDir)
|
||||
{
|
||||
objDll = objExe = "";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Diagnostics;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace MiGu.Server.Launcher;
|
||||
@@ -246,13 +247,94 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
};
|
||||
}
|
||||
|
||||
private static string BuildArguments(string displayMode, string extra)
|
||||
private string BuildArguments(string displayMode, string extra)
|
||||
{
|
||||
var args = $"--display-mode={displayMode}";
|
||||
// 选择性加载:把平台写入的 plugins/active-scenes.json 同步透传为 --scenes(命令行优先级最高,与文件一致,双保险)。
|
||||
var sceneArg = ReadActiveScenesArg();
|
||||
if (!string.IsNullOrEmpty(sceneArg)) args += " " + sceneArg;
|
||||
if (!string.IsNullOrWhiteSpace(extra)) args += " " + extra.Trim();
|
||||
return args;
|
||||
}
|
||||
|
||||
/// <summary>解析 SimpleLite 工作目录(与拉起时一致):优先显式 <see cref="SimpleLiteOptions.WorkingDirectory"/>,
|
||||
/// 否则取解析到的 exe 所在目录。两者都拿不到返回 null。</summary>
|
||||
public string? ResolveWorkingDirectory()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_opts.WorkingDirectory))
|
||||
return Path.GetFullPath(_opts.WorkingDirectory);
|
||||
var resolved = ResolveExecutable(_opts.ExecutablePath);
|
||||
return resolved == null ? null : (Path.GetDirectoryName(resolved) ?? Environment.CurrentDirectory);
|
||||
}
|
||||
|
||||
/// <summary>SimpleLite 的 plugins 目录(工作目录/plugins);定位不到工作目录时返回 null。</summary>
|
||||
public string? ResolvePluginsDir()
|
||||
{
|
||||
var wd = ResolveWorkingDirectory();
|
||||
return wd == null ? null : Path.Combine(wd, "plugins");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 把「配置向导选定的导航场景」写入 SimpleLite 的 <c>plugins/active-scenes.json</c> ——
|
||||
/// 这是「平台 → 内核」选择性加载的主通道。下次 SimpleLite 启动即据此只加载选定导航场景插件;
|
||||
/// 已在运行的实例需重启或调 <c>POST /projection/scenes/apply</c> 才生效。字段名与 SimpleLite 端
|
||||
/// <c>ActiveScenesConfig</c> 对齐(activeScenes / alwaysLoad / source / updatedAt)。
|
||||
/// </summary>
|
||||
public ActiveScenesWriteResult WriteActiveScenes(IEnumerable<string> activeScenes, IEnumerable<string>? alwaysLoad, string source)
|
||||
{
|
||||
var pluginsDir = ResolvePluginsDir();
|
||||
if (pluginsDir == null)
|
||||
return new ActiveScenesWriteResult(false, null, "未找到 SimpleLite 工作目录/可执行文件,无法定位 plugins 目录");
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(pluginsDir);
|
||||
var path = Path.Combine(pluginsDir, "active-scenes.json");
|
||||
var payload = new
|
||||
{
|
||||
activeScenes = (activeScenes ?? Enumerable.Empty<string>())
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s)).Distinct().ToList(),
|
||||
alwaysLoad = (alwaysLoad ?? Enumerable.Empty<string>())
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s)).Distinct().ToList(),
|
||||
source = string.IsNullOrWhiteSpace(source) ? "deployment-profile" : source,
|
||||
updatedAt = DateTime.UtcNow
|
||||
};
|
||||
File.WriteAllText(path, JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true }));
|
||||
_log.LogInformation("[SimpleLite] active-scenes.json 写入 {Path}: active=[{Scenes}]",
|
||||
path, string.Join(",", payload.activeScenes));
|
||||
return new ActiveScenesWriteResult(true, path, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.LogWarning(ex, "[SimpleLite] 写 active-scenes.json 失败");
|
||||
return new ActiveScenesWriteResult(false, null, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>从已写入的 active-scenes.json 读取激活场景,拼成 <c>--scenes=a,b</c>(拉起时透传);无内容返回 null。</summary>
|
||||
private string? ReadActiveScenesArg()
|
||||
{
|
||||
try
|
||||
{
|
||||
var pluginsDir = ResolvePluginsDir();
|
||||
if (pluginsDir == null) return null;
|
||||
var path = Path.Combine(pluginsDir, "active-scenes.json");
|
||||
if (!File.Exists(path)) return null;
|
||||
using var doc = JsonDocument.Parse(File.ReadAllText(path));
|
||||
if (!doc.RootElement.TryGetProperty("activeScenes", out var arr) || arr.ValueKind != JsonValueKind.Array)
|
||||
return null;
|
||||
var ids = arr.EnumerateArray()
|
||||
.Where(e => e.ValueKind == JsonValueKind.String)
|
||||
.Select(e => e.GetString())
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||||
.ToList();
|
||||
return ids.Count == 0 ? null : "--scenes=" + string.Join(",", ids);
|
||||
}
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
/// <summary>写 active-scenes.json 的结果(供向导保存接口回显)。</summary>
|
||||
public readonly record struct ActiveScenesWriteResult(bool Ok, string? Path, string? Error);
|
||||
|
||||
/// <summary>
|
||||
/// 关闭所有 SimpleLite 进程、同步 obj→bin 最新 DLL,再按 launchMode 重新拉起。
|
||||
/// </summary>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
import{aG as S,bt as N,aA as f,bH as a,t as z,b7 as p,aF as e,N as A,bK as B,O,R as T,a3 as F,aC as M,ah as U,bh as D,Z as G,r as H,br as b,aE as d,H as I,a9 as L,aa as K,ac as R,bo as X,az as c,be as Z,bf as $,X as g}from"./index-CC_hCGHZ.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */const j={style:{display:"flex","align-items":"center",gap:"8px"}},pe=S({__name:"AnnotationView",setup(q){const s=N(),l=Z({title:"",target:"",level:"info",content:""}),m=$([{id:"N001",ts:"2026-05-19 10:25",author:"ops",target:"C05",level:"warn",title:"AGV-005 故障观察",content:"出库点附近频繁停顿,怀疑激光被遮挡"}]);function w(r){return r==="error"?"danger":r==="warn"?"warning":"info"}function v(){if(!l.title){g.warning("请填写标题");return}const r=`N${String(m.value.length+1).padStart(3,"0")}`;m.value.unshift({id:r,ts:new Date().toLocaleString("zh-CN"),author:s.user?.username??"mock",target:l.target,level:l.level,title:l.title,content:l.content}),g.success("已记录(Mock,未写入数据库)"),l.title="",l.content="",l.target=""}return(r,t)=>{const u=R,_=T,i=O,V=G,y=F,E=H,h=A,x=I,n=K,k=L,C=z;return p(),f(C,{shadow:"never"},{header:a(()=>[c("div",j,[t[7]||(t[7]=c("span",null,"运营备注(monitor.note.write · 写 platform.db.Annotations)",-1)),b(s).hasOp("monitor.note.write")?(p(),f(u,{key:0,size:"small",type:"success"},{default:a(()=>[...t[5]||(t[5]=[d("可写",-1)])]),_:1})):(p(),f(u,{key:1,size:"small",type:"danger"},{default:a(()=>[...t[6]||(t[6]=[d("只读",-1)])]),_:1}))])]),default:a(()=>[e(h,{inline:"",onSubmit:t[3]||(t[3]=B(()=>{},["prevent"]))},{default:a(()=>[e(i,{label:"标题"},{default:a(()=>[e(_,{modelValue:l.title,"onUpdate:modelValue":t[0]||(t[0]=o=>l.title=o),style:{width:"240px"}},null,8,["modelValue"])]),_:1}),e(i,{label:"关联目标"},{default:a(()=>[e(_,{modelValue:l.target,"onUpdate:modelValue":t[1]||(t[1]=o=>l.target=o),placeholder:"例如 C01 / M02 / S006",style:{width:"200px"}},null,8,["modelValue"])]),_:1}),e(i,{label:"层级"},{default:a(()=>[e(y,{modelValue:l.level,"onUpdate:modelValue":t[2]||(t[2]=o=>l.level=o),style:{width:"120px"}},{default:a(()=>[(p(),M(U,null,D(["info","warn","error"],o=>e(V,{key:o,label:o,value:o},null,8,["label","value"])),64))]),_:1},8,["modelValue"])]),_:1}),e(i,null,{default:a(()=>[e(E,{type:"primary",disabled:!b(s).hasOp("monitor.note.write"),onClick:v},{default:a(()=>[...t[8]||(t[8]=[d("提交",-1)])]),_:1},8,["disabled"])]),_:1})]),_:1}),e(_,{modelValue:l.content,"onUpdate:modelValue":t[4]||(t[4]=o=>l.content=o),type:"textarea",rows:4,placeholder:"描述备注内容...",disabled:!b(s).hasOp("monitor.note.write")},null,8,["modelValue","disabled"]),e(x),e(k,{data:m.value,size:"small","max-height":"400"},{default:a(()=>[e(n,{prop:"ts",label:"时间",width:"180"}),e(n,{prop:"author",label:"作者",width:"100"}),e(n,{prop:"target",label:"目标",width:"100"}),e(n,{prop:"level",label:"层级",width:"80"},{default:a(o=>[e(u,{size:"small",type:w(o.row.level)},{default:a(()=>[d(X(o.row.level),1)]),_:2},1032,["type"])]),_:1}),e(n,{prop:"title",label:"标题"}),e(n,{prop:"content",label:"内容"})]),_:1},8,["data"])]),_:1})}}});export{pe as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{aI as S,bv as N,aC as f,bJ as a,t as B,b9 as p,aH as e,N as z,bL as O,O as T,R as A,a4 as M,aE as U,ai as D,bj as F,Z as I,r as L,bt as b,aG as d,H as G,aa as H,ab as j,ad as q,bq as J,aB as c,bg as R,bh as X,X as g}from"./index-368apsQG.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */const Z={style:{display:"flex","align-items":"center",gap:"8px"}},pe=S({__name:"AnnotationView",setup($){const s=N(),l=R({title:"",target:"",level:"info",content:""}),m=X([{id:"N001",ts:"2026-05-19 10:25",author:"ops",target:"C05",level:"warn",title:"AGV-005 故障观察",content:"出库点附近频繁停顿,怀疑激光被遮挡"}]);function w(r){return r==="error"?"danger":r==="warn"?"warning":"info"}function v(){if(!l.title){g.warning("请填写标题");return}const r=`N${String(m.value.length+1).padStart(3,"0")}`;m.value.unshift({id:r,ts:new Date().toLocaleString("zh-CN"),author:s.user?.username??"mock",target:l.target,level:l.level,title:l.title,content:l.content}),g.success("已记录(Mock,未写入数据库)"),l.title="",l.content="",l.target=""}return(r,t)=>{const u=q,_=A,i=T,V=I,y=M,E=L,h=z,x=G,n=j,k=H,C=B;return p(),f(C,{shadow:"never"},{header:a(()=>[c("div",Z,[t[7]||(t[7]=c("span",null,"运营备注(monitor.note.write · 写 platform.db.Annotations)",-1)),b(s).hasOp("monitor.note.write")?(p(),f(u,{key:0,size:"small",type:"success"},{default:a(()=>[...t[5]||(t[5]=[d("可写",-1)])]),_:1})):(p(),f(u,{key:1,size:"small",type:"danger"},{default:a(()=>[...t[6]||(t[6]=[d("只读",-1)])]),_:1}))])]),default:a(()=>[e(h,{inline:"",onSubmit:t[3]||(t[3]=O(()=>{},["prevent"]))},{default:a(()=>[e(i,{label:"标题"},{default:a(()=>[e(_,{modelValue:l.title,"onUpdate:modelValue":t[0]||(t[0]=o=>l.title=o),style:{width:"240px"}},null,8,["modelValue"])]),_:1}),e(i,{label:"关联目标"},{default:a(()=>[e(_,{modelValue:l.target,"onUpdate:modelValue":t[1]||(t[1]=o=>l.target=o),placeholder:"例如 C01 / M02 / S006",style:{width:"200px"}},null,8,["modelValue"])]),_:1}),e(i,{label:"层级"},{default:a(()=>[e(y,{modelValue:l.level,"onUpdate:modelValue":t[2]||(t[2]=o=>l.level=o),style:{width:"120px"}},{default:a(()=>[(p(),U(D,null,F(["info","warn","error"],o=>e(V,{key:o,label:o,value:o},null,8,["label","value"])),64))]),_:1},8,["modelValue"])]),_:1}),e(i,null,{default:a(()=>[e(E,{type:"primary",disabled:!b(s).hasOp("monitor.note.write"),onClick:v},{default:a(()=>[...t[8]||(t[8]=[d("提交",-1)])]),_:1},8,["disabled"])]),_:1})]),_:1}),e(_,{modelValue:l.content,"onUpdate:modelValue":t[4]||(t[4]=o=>l.content=o),type:"textarea",rows:4,placeholder:"描述备注内容...",disabled:!b(s).hasOp("monitor.note.write")},null,8,["modelValue","disabled"]),e(x),e(k,{data:m.value,size:"small","max-height":"400"},{default:a(()=>[e(n,{prop:"ts",label:"时间",width:"180"}),e(n,{prop:"author",label:"作者",width:"100"}),e(n,{prop:"target",label:"目标",width:"100"}),e(n,{prop:"level",label:"层级",width:"80"},{default:a(o=>[e(u,{size:"small",type:w(o.row.level)},{default:a(()=>[d(J(o.row.level),1)]),_:2},1032,["type"])]),_:1}),e(n,{prop:"title",label:"标题"}),e(n,{prop:"content",label:"内容"})]),_:1},8,["data"])]),_:1})}}});export{pe as default};
|
||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{aG as s,aA as c,bH as t,br as d,b7 as u,aF as e,N as f,O as b,a7 as h,S as C,a9 as g,aa as w,D as E}from"./index-CC_hCGHZ.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{C as V}from"./ConfigPageBase-B4fOVsSK.js";/* empty css *//* empty css *//* empty css */import"./PermissionGuard-DxbOGfWF.js";/* empty css */import"./reflection-CTDOoTn5.js";import"./monitorConfigCache-B5CZXjjB.js";const K=s({__name:"ChargePolicyView",setup(x){return(A,T)=>{const i=h,l=b,n=C,a=w,p=g,_=f;return u(),c(V,{section:"charge",title:"充电逻辑",description:"充电优先级、空闲充电、任务中断充电(ChargePolicy)",defaults:d(E)},{default:t(({payload:o,update:r})=>[e(_,{"label-width":"180px",model:o},{default:t(()=>[e(l,{label:"允许任务中断充电"},{default:t(()=>[e(i,{"model-value":o.allowMidTaskCharge,"onUpdate:modelValue":m=>r({...o,allowMidTaskCharge:!!m})},null,8,["model-value","onUpdate:modelValue"])]),_:2},1024),e(l,{label:"空闲多少秒后充电"},{default:t(()=>[e(n,{"model-value":o.idleChargeAfterSec,min:0,max:86400,"onUpdate:modelValue":m=>r({...o,idleChargeAfterSec:m??0})},null,8,["model-value","onUpdate:modelValue"])]),_:2},1024),e(l,{label:"充电优先级规则"},{default:t(()=>[e(p,{data:o.priority,size:"small"},{default:t(()=>[e(a,{label:"ID",prop:"id",width:"100"}),e(a,{label:"条件",prop:"condition"}),e(a,{label:"权重",prop:"weight",width:"100"})]),_:1},8,["data"])]),_:2},1024)]),_:2},1032,["model"])]),_:1},8,["defaults"])}}});export{K as default};
|
||||
import{aI as s,aC as c,bJ as t,bt as d,b9 as u,aH as e,N as f,O as b,a8 as h,S as C,aa as g,ab as w,D as E}from"./index-368apsQG.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{C as V}from"./ConfigPageBase-CT6BdvCe.js";/* empty css *//* empty css *//* empty css */import"./PermissionGuard-DUg2FNrM.js";/* empty css */import"./reflection-C0K7EJ_N.js";import"./monitorConfigCache-BtW5W6GI.js";const K=s({__name:"ChargePolicyView",setup(x){return(T,U)=>{const i=h,l=b,n=C,a=w,p=g,_=f;return u(),c(V,{section:"charge",title:"充电逻辑",description:"充电优先级、空闲充电、任务中断充电(ChargePolicy)",defaults:d(E)},{default:t(({payload:o,update:r})=>[e(_,{"label-width":"180px",model:o},{default:t(()=>[e(l,{label:"允许任务中断充电"},{default:t(()=>[e(i,{"model-value":o.allowMidTaskCharge,"onUpdate:modelValue":m=>r({...o,allowMidTaskCharge:!!m})},null,8,["model-value","onUpdate:modelValue"])]),_:2},1024),e(l,{label:"空闲多少秒后充电"},{default:t(()=>[e(n,{"model-value":o.idleChargeAfterSec,min:0,max:86400,"onUpdate:modelValue":m=>r({...o,idleChargeAfterSec:m??0})},null,8,["model-value","onUpdate:modelValue"])]),_:2},1024),e(l,{label:"充电优先级规则"},{default:t(()=>[e(p,{data:o.priority,size:"small"},{default:t(()=>[e(a,{label:"ID",prop:"id",width:"100"}),e(a,{label:"条件",prop:"condition"}),e(a,{label:"权重",prop:"weight",width:"100"})]),_:1},8,["data"])]),_:2},1024)]),_:2},1032,["model"])]),_:1},8,["defaults"])}}});export{K as default};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
import{aI as _,aC as i,bJ as e,bt as d,b9 as s,aH as t,aa as u,ab as f,aB as n,bq as r,aE as b,ai as g,bj as h,ad as w,aG as C,l as E,ak as T}from"./index-368apsQG.js";/* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css */import{C as y}from"./ConfigPageBase-CT6BdvCe.js";/* empty css *//* empty css *//* empty css */import"./PermissionGuard-DUg2FNrM.js";/* empty css */import"./reflection-C0K7EJ_N.js";import"./monitorConfigCache-BtW5W6GI.js";const B={class:"js-mini"},k={class:"js-mini"},x=_({__name:"CustomWidgetView",setup(J){return(S,V)=>{const a=f,p=w,m=u;return s(),i(y,{section:"widget",title:"自定义控件",description:"呼叫/展示界面可自由定义(platform-vue + rcsmonitor-vue 双渲染器)",defaults:d(E)},{default:e(({payload:c})=>[t(m,{data:c.items,size:"small",border:""},{default:e(()=>[t(a,{label:"ID",prop:"id",width:"180"}),t(a,{label:"名称",prop:"name",width:"140"}),t(a,{label:"Schema",prop:"schemaJson"},{default:e(o=>[n("pre",B,r(o.row.schemaJson),1)]),_:1}),t(a,{label:"Layout",prop:"layoutJson"},{default:e(o=>[n("pre",k,r(o.row.layoutJson),1)]),_:1}),t(a,{label:"绑定 Scope"},{default:e(o=>[(s(!0),b(g,null,h(o.row.bindToScopes,l=>(s(),i(p,{key:l,size:"small",effect:"plain",style:{"margin-right":"4px"}},{default:e(()=>[C(r(l),1)]),_:2},1024))),128))]),_:1})]),_:1},8,["data"])]),_:1},8,["defaults"])}}}),M=T(x,[["__scopeId","data-v-96b90481"]]);export{M as default};
|
||||
@@ -1 +0,0 @@
|
||||
import{aG as _,aA as i,bH as e,br as d,b7 as s,aF as t,a9 as u,aa as f,az as n,bo as r,aC as b,ah as h,bh as g,ac as w,aE as C,l as E,aj as T}from"./index-CC_hCGHZ.js";/* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css */import{C as y}from"./ConfigPageBase-B4fOVsSK.js";/* empty css *//* empty css *//* empty css */import"./PermissionGuard-DxbOGfWF.js";/* empty css */import"./reflection-CTDOoTn5.js";import"./monitorConfigCache-B5CZXjjB.js";const x={class:"js-mini"},B={class:"js-mini"},S=_({__name:"CustomWidgetView",setup(V){return(k,D)=>{const a=f,p=w,m=u;return s(),i(y,{section:"widget",title:"自定义控件",description:"呼叫/展示界面可自由定义(platform-vue + rcsmonitor-vue 双渲染器)",defaults:d(E)},{default:e(({payload:c})=>[t(m,{data:c.items,size:"small",border:""},{default:e(()=>[t(a,{label:"ID",prop:"id",width:"180"}),t(a,{label:"名称",prop:"name",width:"140"}),t(a,{label:"Schema",prop:"schemaJson"},{default:e(o=>[n("pre",x,r(o.row.schemaJson),1)]),_:1}),t(a,{label:"Layout",prop:"layoutJson"},{default:e(o=>[n("pre",B,r(o.row.layoutJson),1)]),_:1}),t(a,{label:"绑定 Scope"},{default:e(o=>[(s(!0),b(h,null,g(o.row.bindToScopes,l=>(s(),i(p,{key:l,size:"small",effect:"plain",style:{"margin-right":"4px"}},{default:e(()=>[C(r(l),1)]),_:2},1024))),128))]),_:1})]),_:1},8,["data"])]),_:1},8,["defaults"])}}}),M=T(S,[["__scopeId","data-v-96b90481"]]);export{M as default};
|
||||
+15
-15
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{aG as b,aA as u,bH as a,br as f,b7 as h,aF as e,ab as w,a8 as v,a9 as E,aa as C,a7 as D,C as P,F as T,aE as i,bo as r,a as V}from"./index-CC_hCGHZ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css */import{C as g}from"./ConfigPageBase-B4fOVsSK.js";/* empty css *//* empty css *//* empty css */import"./PermissionGuard-DxbOGfWF.js";/* empty css */import"./reflection-CTDOoTn5.js";import"./monitorConfigCache-B5CZXjjB.js";const W=b({__name:"DeviceHubView",setup(x){return(I,S)=>{const l=C,s=E,n=v,d=D,o=T,m=P,c=w;return h(),u(g,{section:"device",title:"第三方设备统一接入",description:"电梯/卷帘门/安全门/充电桩/AP/交换机/摄像头/读码器/PLC(DeviceManagementConfig)",defaults:f(V)},{default:a(({payload:t})=>[e(c,{"model-value":"drivers"},{default:a(()=>[e(n,{name:"drivers",label:"驱动绑定"},{default:a(()=>[e(s,{data:t.drivers,size:"small",border:""},{default:a(()=>[e(l,{label:"ID",prop:"id",width:"120"}),e(l,{label:"设备类型",prop:"deviceType",width:"120"}),e(l,{label:"驱动名",prop:"driverName"}),e(l,{label:"版本",prop:"version",width:"100"})]),_:1},8,["data"])]),_:2},1024),e(n,{name:"devices",label:"设备实例"},{default:a(()=>[e(s,{data:t.devices,size:"small",border:""},{default:a(()=>[e(l,{label:"ID",prop:"id",width:"120"}),e(l,{label:"名称",prop:"name",width:"140"}),e(l,{label:"类型",prop:"deviceType",width:"100"}),e(l,{label:"协议",prop:"protocol",width:"120"}),e(l,{label:"地址",prop:"address"}),e(l,{label:"启用",width:"80"},{default:a(p=>[e(d,{modelValue:p.row.enabled,"onUpdate:modelValue":_=>p.row.enabled=_},null,8,["modelValue","onUpdate:modelValue"])]),_:1})]),_:1},8,["data"])]),_:2},1024),e(n,{name:"policy",label:"健康 / 告警"},{default:a(()=>[e(m,{column:2,border:""},{default:a(()=>[e(o,{label:"心跳周期 (s)"},{default:a(()=>[i(r(t.healthPolicy.heartbeatSec),1)]),_:2},1024),e(o,{label:"离线判定 (s)"},{default:a(()=>[i(r(t.healthPolicy.offlineSec),1)]),_:2},1024),e(o,{label:"告警启用"},{default:a(()=>[i(r(t.alarmPolicy.enabled?"是":"否"),1)]),_:2},1024),e(o,{label:"规则数"},{default:a(()=>[i(r(t.alarmPolicy.rules.length),1)]),_:2},1024)]),_:2},1024)]),_:2},1024)]),_:2},1024)]),_:1},8,["defaults"])}}});export{W as default};
|
||||
import{aI as b,aC as u,bJ as a,bt as f,b9 as h,aH as e,ac as w,a9 as v,aa as C,ab as E,a8 as D,C as P,F as T,aG as i,bq as r,a as V}from"./index-368apsQG.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css */import{C as g}from"./ConfigPageBase-CT6BdvCe.js";/* empty css *//* empty css *//* empty css */import"./PermissionGuard-DUg2FNrM.js";/* empty css */import"./reflection-C0K7EJ_N.js";import"./monitorConfigCache-BtW5W6GI.js";const W=b({__name:"DeviceHubView",setup(I){return(x,S)=>{const l=E,s=C,n=v,d=D,o=T,m=P,c=w;return h(),u(g,{section:"device",title:"第三方设备统一接入",description:"电梯/卷帘门/安全门/充电桩/AP/交换机/摄像头/读码器/PLC(DeviceManagementConfig)",defaults:f(V)},{default:a(({payload:t})=>[e(c,{"model-value":"drivers"},{default:a(()=>[e(n,{name:"drivers",label:"驱动绑定"},{default:a(()=>[e(s,{data:t.drivers,size:"small",border:""},{default:a(()=>[e(l,{label:"ID",prop:"id",width:"120"}),e(l,{label:"设备类型",prop:"deviceType",width:"120"}),e(l,{label:"驱动名",prop:"driverName"}),e(l,{label:"版本",prop:"version",width:"100"})]),_:1},8,["data"])]),_:2},1024),e(n,{name:"devices",label:"设备实例"},{default:a(()=>[e(s,{data:t.devices,size:"small",border:""},{default:a(()=>[e(l,{label:"ID",prop:"id",width:"120"}),e(l,{label:"名称",prop:"name",width:"140"}),e(l,{label:"类型",prop:"deviceType",width:"100"}),e(l,{label:"协议",prop:"protocol",width:"120"}),e(l,{label:"地址",prop:"address"}),e(l,{label:"启用",width:"80"},{default:a(p=>[e(d,{modelValue:p.row.enabled,"onUpdate:modelValue":_=>p.row.enabled=_},null,8,["modelValue","onUpdate:modelValue"])]),_:1})]),_:1},8,["data"])]),_:2},1024),e(n,{name:"policy",label:"健康 / 告警"},{default:a(()=>[e(m,{column:2,border:""},{default:a(()=>[e(o,{label:"心跳周期 (s)"},{default:a(()=>[i(r(t.healthPolicy.heartbeatSec),1)]),_:2},1024),e(o,{label:"离线判定 (s)"},{default:a(()=>[i(r(t.healthPolicy.offlineSec),1)]),_:2},1024),e(o,{label:"告警启用"},{default:a(()=>[i(r(t.alarmPolicy.enabled?"是":"否"),1)]),_:2},1024),e(o,{label:"规则数"},{default:a(()=>[i(r(t.alarmPolicy.rules.length),1)]),_:2},1024)]),_:2},1024)]),_:2},1024)]),_:2},1024)]),_:1},8,["defaults"])}}});export{W as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{aI as U,aC as x,bJ as a,bt as c,b9 as b,aH as t,ac as g,aE as T,ai as h,bj as I,a9 as S,aa as B,ab as $,R as v,a8 as D,r as L,aG as f,bc as N,bq as R,c as z}from"./index-368apsQG.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{C as P}from"./ConfigPageBase-CT6BdvCe.js";/* empty css *//* empty css */import"./PermissionGuard-DUg2FNrM.js";/* empty css */import"./reflection-C0K7EJ_N.js";import"./monitorConfigCache-BtW5W6GI.js";const le=U({__name:"ExternalIntegrationView",setup(A){function w(r,s,l){const o=`${l}-${Date.now()}`,i={...r,[l]:[...r[l]??[],{id:o,name:"新端点",url:"http://",enabled:!1}]};s(i)}function V(r,s,l,o){const i={...r,[l]:(r[l]??[]).filter((u,p)=>p!==o)};s(i)}return(r,s)=>{const l=v,o=$,i=D,u=L,p=B,E=S,C=g;return b(),x(P,{section:"integrations",title:"外部系统对接",description:"MES / WMS / RCS / PTL 等标准接口配置(ExternalIntegrations)",defaults:c(z)},{default:a(({payload:d,update:_})=>[t(C,{"model-value":"mes"},{default:a(()=>[(b(),T(h,null,I(["mes","wms","rcs","ptl"],m=>t(E,{key:m,name:m,label:m.toUpperCase()},{default:a(()=>[t(p,{data:d[m]??[],size:"small",border:""},{default:a(()=>[t(o,{label:"ID",width:"120"},{default:a(e=>[t(l,{modelValue:e.row.id,"onUpdate:modelValue":n=>e.row.id=n},null,8,["modelValue","onUpdate:modelValue"])]),_:1}),t(o,{label:"名称","min-width":"160"},{default:a(e=>[t(l,{modelValue:e.row.name,"onUpdate:modelValue":n=>e.row.name=n},null,8,["modelValue","onUpdate:modelValue"])]),_:1}),t(o,{label:"URL","min-width":"260"},{default:a(e=>[t(l,{modelValue:e.row.url,"onUpdate:modelValue":n=>e.row.url=n},null,8,["modelValue","onUpdate:modelValue"])]),_:1}),t(o,{label:"启用",width:"80"},{default:a(e=>[t(i,{modelValue:e.row.enabled,"onUpdate:modelValue":n=>e.row.enabled=n},null,8,["modelValue","onUpdate:modelValue"])]),_:1}),t(o,{label:"操作",width:"80"},{default:a(e=>[t(u,{text:"",type:"danger",size:"small",onClick:n=>V(d,_,m,e.$index)},{default:a(()=>[...s[0]||(s[0]=[f("删除",-1)])]),_:1},8,["onClick"])]),_:2},1024)]),_:2},1032,["data"]),t(u,{size:"small",icon:c(N),style:{"margin-top":"8px"},onClick:e=>w(d,_,m)},{default:a(()=>[f("新增 "+R(m.toUpperCase())+" 端点",1)]),_:2},1032,["icon","onClick"])]),_:2},1032,["name","label"])),64))]),_:2},1024)]),_:1},8,["defaults"])}}});export{le as default};
|
||||
@@ -1 +0,0 @@
|
||||
import{aG as U,aA as x,bH as a,br as c,b7 as b,aF as t,ab as h,aC as g,ah as T,bh as I,a8 as S,a9 as B,aa as $,R as v,a7 as D,r as N,aE as f,ba as R,bo as z,c as A}from"./index-CC_hCGHZ.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{C as F}from"./ConfigPageBase-B4fOVsSK.js";/* empty css *//* empty css */import"./PermissionGuard-DxbOGfWF.js";/* empty css */import"./reflection-CTDOoTn5.js";import"./monitorConfigCache-B5CZXjjB.js";const le=U({__name:"ExternalIntegrationView",setup(L){function w(r,s,l){const o=`${l}-${Date.now()}`,i={...r,[l]:[...r[l],{id:o,name:"新端点",url:"http://",enabled:!1}]};s(i)}function V(r,s,l,o){const i={...r,[l]:r[l].filter((u,d)=>d!==o)};s(i)}return(r,s)=>{const l=v,o=$,i=D,u=N,d=B,E=S,C=h;return b(),x(F,{section:"integrations",title:"外部系统对接",description:"MES / WMS / RCS 等标准接口配置(ExternalIntegrations)",defaults:c(A)},{default:a(({payload:p,update:_})=>[t(C,{"model-value":"mes"},{default:a(()=>[(b(),g(T,null,I(["mes","wms","rcs"],m=>t(E,{key:m,name:m,label:m.toUpperCase()},{default:a(()=>[t(d,{data:p[m],size:"small",border:""},{default:a(()=>[t(o,{label:"ID",width:"120"},{default:a(e=>[t(l,{modelValue:e.row.id,"onUpdate:modelValue":n=>e.row.id=n},null,8,["modelValue","onUpdate:modelValue"])]),_:1}),t(o,{label:"名称","min-width":"160"},{default:a(e=>[t(l,{modelValue:e.row.name,"onUpdate:modelValue":n=>e.row.name=n},null,8,["modelValue","onUpdate:modelValue"])]),_:1}),t(o,{label:"URL","min-width":"260"},{default:a(e=>[t(l,{modelValue:e.row.url,"onUpdate:modelValue":n=>e.row.url=n},null,8,["modelValue","onUpdate:modelValue"])]),_:1}),t(o,{label:"启用",width:"80"},{default:a(e=>[t(i,{modelValue:e.row.enabled,"onUpdate:modelValue":n=>e.row.enabled=n},null,8,["modelValue","onUpdate:modelValue"])]),_:1}),t(o,{label:"操作",width:"80"},{default:a(e=>[t(u,{text:"",type:"danger",size:"small",onClick:n=>V(p,_,m,e.$index)},{default:a(()=>[...s[0]||(s[0]=[f("删除",-1)])]),_:1},8,["onClick"])]),_:2},1024)]),_:2},1032,["data"]),t(u,{size:"small",icon:c(R),style:{"margin-top":"8px"},onClick:e=>w(p,_,m)},{default:a(()=>[f("新增 "+z(m.toUpperCase())+" 端点",1)]),_:2},1032,["icon","onClick"])]),_:2},1032,["name","label"])),64))]),_:2},1024)]),_:1},8,["defaults"])}}});export{le as default};
|
||||
@@ -0,0 +1 @@
|
||||
const o="/FRLD-logo-white-no_title.png";export{o as _};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{aG as s,aA as m,bH as t,br as c,b7 as d,aF as e,ab as _,a8 as b,a9 as u,aa as f,_ as w,d as h}from"./index-CC_hCGHZ.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css */import{C as g}from"./ConfigPageBase-B4fOVsSK.js";/* empty css *//* empty css *//* empty css */import"./PermissionGuard-DxbOGfWF.js";/* empty css */import"./reflection-CTDOoTn5.js";import"./monitorConfigCache-B5CZXjjB.js";const H=s({__name:"LocationView",setup(T){return(C,E)=>{const a=f,i=w,o=u,l=b,n=_;return d(),m(g,{section:"location",title:"库位管理",description:"出入库、库存、库位可视化(LocationManagement)",defaults:c(h)},{default:t(({payload:r})=>[e(n,{"model-value":"locs"},{default:t(()=>[e(l,{name:"locs",label:"库位"},{default:t(()=>[e(o,{data:r.locations,size:"small",border:""},{default:t(()=>[e(a,{label:"ID",prop:"id",width:"100"}),e(a,{label:"编码",prop:"code",width:"120"}),e(a,{label:"名称",prop:"name"}),e(a,{label:"站点",prop:"siteId",width:"100"}),e(a,{label:"容量",prop:"capacity",width:"100"}),e(a,{label:"占用"},{default:t(p=>[e(i,{percentage:Math.round(p.row.occupied/p.row.capacity*100),"stroke-width":10},null,8,["percentage"])]),_:1})]),_:1},8,["data"])]),_:2},1024),e(l,{name:"rules",label:"库存规则"},{default:t(()=>[e(o,{data:r.inventoryRules,size:"small",border:""},{default:t(()=>[e(a,{label:"ID",prop:"id",width:"100"}),e(a,{label:"物料类型",prop:"itemType"}),e(a,{label:"下限",prop:"minQty",width:"100"}),e(a,{label:"上限",prop:"maxQty",width:"100"})]),_:1},8,["data"])]),_:2},1024)]),_:2},1024)]),_:1},8,["defaults"])}}});export{H as default};
|
||||
import{aI as s,aC as m,bJ as t,bt as c,b9 as d,aH as e,ac as _,a9 as b,aa as u,ab as f,$ as w,d as h}from"./index-368apsQG.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css */import{C as g}from"./ConfigPageBase-CT6BdvCe.js";/* empty css *//* empty css *//* empty css */import"./PermissionGuard-DUg2FNrM.js";/* empty css */import"./reflection-C0K7EJ_N.js";import"./monitorConfigCache-BtW5W6GI.js";const J=s({__name:"LocationView",setup(C){return(T,E)=>{const a=f,i=w,o=u,l=b,n=_;return d(),m(g,{section:"location",title:"库位管理",description:"出入库、库存、库位可视化(LocationManagement)",defaults:c(h)},{default:t(({payload:p})=>[e(n,{"model-value":"locs"},{default:t(()=>[e(l,{name:"locs",label:"库位"},{default:t(()=>[e(o,{data:p.locations,size:"small",border:""},{default:t(()=>[e(a,{label:"ID",prop:"id",width:"100"}),e(a,{label:"编码",prop:"code",width:"120"}),e(a,{label:"名称",prop:"name"}),e(a,{label:"站点",prop:"siteId",width:"100"}),e(a,{label:"容量",prop:"capacity",width:"100"}),e(a,{label:"占用"},{default:t(r=>[e(i,{percentage:Math.round(r.row.occupied/r.row.capacity*100),"stroke-width":10},null,8,["percentage"])]),_:1})]),_:1},8,["data"])]),_:2},1024),e(l,{name:"rules",label:"库存规则"},{default:t(()=>[e(o,{data:p.inventoryRules,size:"small",border:""},{default:t(()=>[e(a,{label:"ID",prop:"id",width:"100"}),e(a,{label:"物料类型",prop:"itemType"}),e(a,{label:"下限",prop:"minQty",width:"100"}),e(a,{label:"上限",prop:"maxQty",width:"100"})]),_:1},8,["data"])]),_:2},1024)]),_:2},1024)]),_:1},8,["defaults"])}}});export{J as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{R as o}from"./ReflectionManagerPanel-CpDwI_uG.js";import{aG as i,aC as t,aF as r,b7 as p,aj as e}from"./index-CC_hCGHZ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./reflection-CTDOoTn5.js";import"./useMapEditStream-BAGhVPpt.js";import"./useProjectionStream-C_klWvmm.js";const m={class:"mission-page"},s=i({__name:"MissionEditorView",setup(a){return(n,c)=>(p(),t("div",m,[r(o,{kind:"process","kind-label":"任务",title:"任务编排(Mission / 进程,含插件 MissionType 实例化)","empty-text":"当前没有任务;点击右上角「新建任务」从已加载的 MissionType 中选一个实例化。"})]))}}),q=e(s,[["__scopeId","data-v-40f885f1"]]);export{q as default};
|
||||
import{R as o}from"./ReflectionManagerPanel-CKQWtKhJ.js";import{aI as i,aE as t,aH as r,b9 as p,ak as e}from"./index-368apsQG.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./reflection-C0K7EJ_N.js";import"./useMapEditStream-CoKaGT-a.js";import"./useProjectionStream-C94Y5UgX.js";const m={class:"mission-page"},s=i({__name:"MissionEditorView",setup(a){return(n,c)=>(p(),t("div",m,[r(o,{kind:"process","kind-label":"任务",title:"任务编排(Mission / 进程,含插件 MissionType 实例化)","empty-text":"当前没有任务;点击右上角「新建任务」从已加载的 MissionType 中选一个实例化。"})]))}}),z=e(s,[["__scopeId","data-v-40f885f1"]]);export{z as default};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{aG as k,b5 as z,aC as M,aF as t,bH as e,bf as p,a1 as T,b7 as V,w as B,a5 as D,az as d,bo as c,t as N,a9 as S,aa as I,ac as P,aE as h,_ as j,av as _,aj as A}from"./index-CC_hCGHZ.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{l as F,a as G}from"./projection-D6ulMg5U.js";import{l as H}from"./ops-DQIK15vn.js";import"./reflection-CTDOoTn5.js";const R={class:"muted"},q=k({__name:"MonitorDashboardView",setup(J){const n=p([]),m=p([]),i=p([]),b=_(()=>n.value.filter(l=>l.state!=="offline").length),C=_(()=>m.value.filter(l=>l.status==="running").length),E=_(()=>n.value.filter(l=>l.state==="fault").length);function x(l){switch(l){case"running":return"success";case"idle":return"info";case"charging":return"warning";case"paused":return"warning";case"fault":return"danger";case"offline":return"info";default:return"info"}}return z(async()=>{[n.value,m.value,i.value]=await Promise.all([F(),G(),H()])}),(l,r)=>{const u=D,o=B,f=T,a=I,g=P,y=j,v=S,w=N;return V(),M("div",null,[t(f,{gutter:14,class:"kpi-row"},{default:e(()=>[t(o,{span:6},{default:e(()=>[t(u,{title:"车辆在线",value:b.value},{suffix:e(()=>[d("span",R,"/ "+c(n.value.length),1)]),_:1},8,["value"])]),_:1}),t(o,{span:6},{default:e(()=>[t(u,{title:"任务进行中",value:C.value},null,8,["value"])]),_:1}),t(o,{span:6},{default:e(()=>[t(u,{title:"故障车辆",value:E.value},null,8,["value"])]),_:1}),t(o,{span:6},{default:e(()=>[t(u,{title:"今日运维动作",value:i.value.length},null,8,["value"])]),_:1})]),_:1}),t(f,{gutter:14,style:{"margin-top":"14px"}},{default:e(()=>[t(o,{span:14},{default:e(()=>[t(w,{shadow:"never"},{header:e(()=>[...r[0]||(r[0]=[d("span",null,"实时车辆",-1)])]),default:e(()=>[t(v,{data:n.value,size:"small","max-height":"360",stripe:""},{default:e(()=>[t(a,{prop:"name",label:"名称",width:"100"}),t(a,{prop:"state",label:"状态",width:"90"},{default:e(s=>[t(g,{size:"small",type:x(s.row.state)},{default:e(()=>[h(c(s.row.state),1)]),_:2},1032,["type"])]),_:1}),t(a,{prop:"group",label:"分组",width:"100"}),t(a,{label:"电量",width:"160"},{default:e(s=>[t(y,{percentage:Math.round(s.row.batterySoc*100),"stroke-width":8},null,8,["percentage"])]),_:1}),t(a,{prop:"missionId",label:"当前任务"})]),_:1},8,["data"])]),_:1})]),_:1}),t(o,{span:10},{default:e(()=>[t(w,{shadow:"never"},{header:e(()=>[...r[1]||(r[1]=[d("span",null,"最近运维动作",-1)])]),default:e(()=>[t(v,{data:i.value,size:"small","max-height":"360",stripe:""},{default:e(()=>[t(a,{prop:"ts",label:"时间",width:"180"}),t(a,{prop:"opCode",label:"动作"}),t(a,{prop:"target",label:"目标",width:"100"}),t(a,{prop:"result",label:"结果",width:"80"},{default:e(s=>[t(g,{size:"small",type:s.row.result==="ok"?"success":"danger"},{default:e(()=>[h(c(s.row.result),1)]),_:2},1032,["type"])]),_:1})]),_:1},8,["data"])]),_:1})]),_:1})]),_:1})])}}}),ot=A(q,[["__scopeId","data-v-53088435"]]);export{ot as default};
|
||||
import{aI as k,b7 as M,aE as T,aH as t,bJ as e,bh as p,a2 as V,b9 as z,w as B,a6 as D,aB as d,bq as c,t as I,aa as N,ab as S,ad as P,aG as v,$ as q,ax as _,ak as A}from"./index-368apsQG.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{l as G,a as H}from"./projection-nWPnmoX1.js";import{l as J}from"./ops-DA63t9Ed.js";import"./reflection-C0K7EJ_N.js";const R={class:"muted"},$=k({__name:"MonitorDashboardView",setup(j){const n=p([]),m=p([]),i=p([]),b=_(()=>n.value.filter(l=>l.state!=="offline").length),x=_(()=>m.value.filter(l=>l.status==="running").length),C=_(()=>n.value.filter(l=>l.state==="fault").length);function E(l){switch(l){case"running":return"success";case"idle":return"info";case"charging":return"warning";case"paused":return"warning";case"fault":return"danger";case"offline":return"info";default:return"info"}}return M(async()=>{[n.value,m.value,i.value]=await Promise.all([G(),H(),J()])}),(l,r)=>{const u=D,o=B,f=V,a=S,g=P,y=q,h=N,w=I;return z(),T("div",null,[t(f,{gutter:14,class:"kpi-row"},{default:e(()=>[t(o,{span:6},{default:e(()=>[t(u,{title:"车辆在线",value:b.value},{suffix:e(()=>[d("span",R,"/ "+c(n.value.length),1)]),_:1},8,["value"])]),_:1}),t(o,{span:6},{default:e(()=>[t(u,{title:"任务进行中",value:x.value},null,8,["value"])]),_:1}),t(o,{span:6},{default:e(()=>[t(u,{title:"故障车辆",value:C.value},null,8,["value"])]),_:1}),t(o,{span:6},{default:e(()=>[t(u,{title:"今日运维动作",value:i.value.length},null,8,["value"])]),_:1})]),_:1}),t(f,{gutter:14,style:{"margin-top":"14px"}},{default:e(()=>[t(o,{span:14},{default:e(()=>[t(w,{shadow:"never"},{header:e(()=>[...r[0]||(r[0]=[d("span",null,"实时车辆",-1)])]),default:e(()=>[t(h,{data:n.value,size:"small","max-height":"360",stripe:""},{default:e(()=>[t(a,{prop:"name",label:"名称",width:"100"}),t(a,{prop:"state",label:"状态",width:"90"},{default:e(s=>[t(g,{size:"small",type:E(s.row.state)},{default:e(()=>[v(c(s.row.state),1)]),_:2},1032,["type"])]),_:1}),t(a,{prop:"group",label:"分组",width:"100"}),t(a,{label:"电量",width:"160"},{default:e(s=>[t(y,{percentage:Math.round(s.row.batterySoc*100),"stroke-width":8},null,8,["percentage"])]),_:1}),t(a,{prop:"missionId",label:"当前任务"})]),_:1},8,["data"])]),_:1})]),_:1}),t(o,{span:10},{default:e(()=>[t(w,{shadow:"never"},{header:e(()=>[...r[1]||(r[1]=[d("span",null,"最近运维动作",-1)])]),default:e(()=>[t(h,{data:i.value,size:"small","max-height":"360",stripe:""},{default:e(()=>[t(a,{prop:"ts",label:"时间",width:"180"}),t(a,{prop:"opCode",label:"动作"}),t(a,{prop:"target",label:"目标",width:"100"}),t(a,{prop:"result",label:"结果",width:"80"},{default:e(s=>[t(g,{size:"small",type:s.row.result==="ok"?"success":"danger"},{default:e(()=>[v(c(s.row.result),1)]),_:2},1032,["type"])]),_:1})]),_:1},8,["data"])]),_:1})]),_:1})]),_:1})])}}}),ot=A($,[["__scopeId","data-v-53088435"]]);export{ot as default};
|
||||
@@ -1 +0,0 @@
|
||||
import o from"./MapMonitorView-oh-hd20C.js";import{aG as r,aA as t,b7 as p}from"./index-CC_hCGHZ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./Workspace3D-CE3B9LLc.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css */import"./reflection-CTDOoTn5.js";import"./mapEdit-Z_uHqZwy.js";import"./ops-BCeFEEjY.js";import"./ops-DQIK15vn.js";import"./monitorConfigCache-B5CZXjjB.js";/* empty css *//* empty css *//* empty css */import"./projection-D6ulMg5U.js";import"./useProjectionStream-C_klWvmm.js";const H=r({__name:"MonitorMapView",setup(i){return(m,e)=>(p(),t(o,{"read-only":""}))}});export{H as default};
|
||||
@@ -0,0 +1 @@
|
||||
import o from"./MapMonitorView-DEfDktpd.js";import{aI as r,aC as t,b9 as p}from"./index-368apsQG.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./Workspace3D-B2WrMPji.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css */import"./reflection-C0K7EJ_N.js";import"./mapEdit-BiAzHHno.js";import"./ops-BCeFEEjY.js";import"./ops-DA63t9Ed.js";import"./monitorConfigCache-BtW5W6GI.js";/* empty css *//* empty css *//* empty css */import"./projection-nWPnmoX1.js";import"./useProjectionStream-C94Y5UgX.js";const G=r({__name:"MonitorMapView",setup(i){return(m,e)=>(p(),t(o,{"read-only":""}))}});export{G as default};
|
||||
+2
-2
@@ -1,2 +1,2 @@
|
||||
import{aG as O,bt as T,b5 as V,aA as m,bH as a,bf as B,b7 as d,aF as o,t as S,a9 as A,br as _,aa as M,az as p,bo as r,ac as P,aE as i,R as $,r as D,ad as N,aC as F,ah as G,bh as H,ae as L,be as R,av as U,X as f,Y as W}from"./index-CC_hCGHZ.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{P as X}from"./PermissionGuard-DxbOGfWF.js";import{O as g}from"./ops-BCeFEEjY.js";import{l as C,e as Y}from"./ops-DQIK15vn.js";/* empty css */const j={style:{display:"flex","align-items":"center",gap:"8px"}},me=O({__name:"OpsActionPanelView",setup(q){const w=T(),u=R({}),c=B([]),E=U(()=>g.filter(n=>w.hasOp(n.code)));async function h(n){const e=u[n.code];if(!e&&n.target!=="note"){f.warning("请填写目标 ID");return}if(n.needConfirm)try{await W.confirm(`确认执行 [${n.label}]?
|
||||
目标:${e}`,"二次确认",{type:"warning"})}catch{return}try{const l=await Y({opCode:n.code,targetId:e});f.success(`成功,auditId=${l.auditId}`),c.value=await C()}catch(l){f.error(`失败:${l instanceof Error?l.message:String(l)}`)}}return V(async()=>{c.value=await C()}),(n,e)=>{const l=P,s=M,x=$,k=D,v=A,b=S,I=L,z=N;return d(),m(X,{"widget-id":"OpsActionPanel"},{default:a(()=>[o(b,{shadow:"never"},{header:a(()=>[p("div",j,[e[1]||(e[1]=p("span",null,"运维操作(架构 §5.1 白名单)",-1)),o(l,{size:"small",type:"info"},{default:a(()=>[...e[0]||(e[0]=[i("scope=RCSMonitor",-1)])]),_:1}),o(l,{size:"small",type:"success"},{default:a(()=>[i(r(E.value.length)+" / "+r(_(g).length)+" 可用",1)]),_:1})])]),default:a(()=>[o(v,{data:_(g),size:"small",border:""},{default:a(()=>[o(s,{prop:"code",label:"权限码",width:"200"},{default:a(t=>[p("code",null,r(t.row.code),1)]),_:1}),o(s,{prop:"label",label:"操作",width:"140"}),o(s,{prop:"target",label:"目标",width:"80"}),o(s,{prop:"needConfirm",label:"二次确认",width:"100"},{default:a(t=>[t.row.needConfirm?(d(),m(l,{key:0,size:"small",type:"warning"},{default:a(()=>[...e[2]||(e[2]=[i("是",-1)])]),_:1})):(d(),m(l,{key:1,size:"small",effect:"plain"},{default:a(()=>[...e[3]||(e[3]=[i("否",-1)])]),_:1}))]),_:1}),o(s,{prop:"description",label:"说明"}),o(s,{label:"操作",width:"200"},{default:a(t=>[o(x,{modelValue:u[t.row.code],"onUpdate:modelValue":y=>u[t.row.code]=y,placeholder:"目标 ID",size:"small",style:{width:"100px","margin-right":"6px"}},null,8,["modelValue","onUpdate:modelValue"]),o(k,{size:"small",type:t.row.needConfirm?"warning":"primary",disabled:!_(w).hasOp(t.row.code),onClick:y=>h(t.row)},{default:a(()=>[...e[4]||(e[4]=[i("执行",-1)])]),_:1},8,["type","disabled","onClick"])]),_:1})]),_:1},8,["data"])]),_:1}),o(b,{shadow:"never",style:{"margin-top":"12px"}},{header:a(()=>[...e[5]||(e[5]=[p("span",null,"本地操作记录(占位 Mock)",-1)])]),default:a(()=>[o(z,null,{default:a(()=>[(d(!0),F(G,null,H(c.value,t=>(d(),m(I,{key:t.id,timestamp:t.ts,type:t.result==="ok"?"success":"danger"},{default:a(()=>[p("strong",null,r(t.opCode),1),i(" → "+r(t.target)+"("+r(t.user)+") ",1)]),_:2},1032,["timestamp","type"]))),128))]),_:1})]),_:1})]),_:1})}}});export{me as default};
|
||||
import{aI as T,bv as V,b7 as z,aC as m,bJ as a,bh as B,b9 as d,aH as o,t as S,aa as M,bt as _,ab as P,aB as p,bq as r,ad as $,aG as i,R as A,r as D,ae as N,aE as G,ai as H,bj as L,af as R,bg as U,ax as j,X as f,Y as q}from"./index-368apsQG.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{P as F}from"./PermissionGuard-DUg2FNrM.js";import{O as g}from"./ops-BCeFEEjY.js";import{l as C,e as J}from"./ops-DA63t9Ed.js";/* empty css */const W={style:{display:"flex","align-items":"center",gap:"8px"}},me=T({__name:"OpsActionPanelView",setup(X){const w=V(),u=U({}),c=B([]),E=j(()=>g.filter(n=>w.hasOp(n.code)));async function x(n){const e=u[n.code];if(!e&&n.target!=="note"){f.warning("请填写目标 ID");return}if(n.needConfirm)try{await q.confirm(`确认执行 [${n.label}]?
|
||||
目标:${e}`,"二次确认",{type:"warning"})}catch{return}try{const l=await J({opCode:n.code,targetId:e});f.success(`成功,auditId=${l.auditId}`),c.value=await C()}catch(l){f.error(`失败:${l instanceof Error?l.message:String(l)}`)}}return z(async()=>{c.value=await C()}),(n,e)=>{const l=$,s=P,h=A,k=D,v=M,b=S,I=R,O=N;return d(),m(F,{"widget-id":"OpsActionPanel"},{default:a(()=>[o(b,{shadow:"never"},{header:a(()=>[p("div",W,[e[1]||(e[1]=p("span",null,"运维操作(架构 §5.1 白名单)",-1)),o(l,{size:"small",type:"info"},{default:a(()=>[...e[0]||(e[0]=[i("scope=RCSMonitor",-1)])]),_:1}),o(l,{size:"small",type:"success"},{default:a(()=>[i(r(E.value.length)+" / "+r(_(g).length)+" 可用",1)]),_:1})])]),default:a(()=>[o(v,{data:_(g),size:"small",border:""},{default:a(()=>[o(s,{prop:"code",label:"权限码",width:"200"},{default:a(t=>[p("code",null,r(t.row.code),1)]),_:1}),o(s,{prop:"label",label:"操作",width:"140"}),o(s,{prop:"target",label:"目标",width:"80"}),o(s,{prop:"needConfirm",label:"二次确认",width:"100"},{default:a(t=>[t.row.needConfirm?(d(),m(l,{key:0,size:"small",type:"warning"},{default:a(()=>[...e[2]||(e[2]=[i("是",-1)])]),_:1})):(d(),m(l,{key:1,size:"small",effect:"plain"},{default:a(()=>[...e[3]||(e[3]=[i("否",-1)])]),_:1}))]),_:1}),o(s,{prop:"description",label:"说明"}),o(s,{label:"操作",width:"200"},{default:a(t=>[o(h,{modelValue:u[t.row.code],"onUpdate:modelValue":y=>u[t.row.code]=y,placeholder:"目标 ID",size:"small",style:{width:"100px","margin-right":"6px"}},null,8,["modelValue","onUpdate:modelValue"]),o(k,{size:"small",type:t.row.needConfirm?"warning":"primary",disabled:!_(w).hasOp(t.row.code),onClick:y=>x(t.row)},{default:a(()=>[...e[4]||(e[4]=[i("执行",-1)])]),_:1},8,["type","disabled","onClick"])]),_:1})]),_:1},8,["data"])]),_:1}),o(b,{shadow:"never",style:{"margin-top":"12px"}},{header:a(()=>[...e[5]||(e[5]=[p("span",null,"本地操作记录(占位 Mock)",-1)])]),default:a(()=>[o(O,null,{default:a(()=>[(d(!0),G(H,null,L(c.value,t=>(d(),m(I,{key:t.id,timestamp:t.ts,type:t.result==="ok"?"success":"danger"},{default:a(()=>[p("strong",null,r(t.opCode),1),i(" → "+r(t.target)+"("+r(t.user)+") ",1)]),_:2},1032,["timestamp","type"]))),128))]),_:1})]),_:1})]),_:1})}}});export{me as default};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{aG as p,bt as c,bi as i,aC as u,az as s,aA as m,L as g,bH as v,av as y,b7 as d,bo as _,aj as f}from"./index-CC_hCGHZ.js";/* empty css *//* empty css */const k=["title"],b={class:"pg-hidden-hint"},h=p({__name:"PermissionGuard",props:{widgetId:{}},setup(e){const n=e,l=c(),a=y(()=>l.widgetOf(n.widgetId));return(o,t)=>{const r=g;return a.value==="interactive"?i(o.$slots,"default",{key:0},void 0,!0):a.value==="readonly"?(d(),u("div",{key:1,class:"pg-readonly",title:`${e.widgetId} 当前为只读`},[i(o.$slots,"default",{},void 0,!0),t[0]||(t[0]=s("div",{class:"pg-mask"},null,-1))],8,k)):(d(),m(r,{key:2,class:"pg-hidden"},{description:v(()=>[t[1]||(t[1]=s("p",{class:"pg-hidden-title"},"当前账号无权查看此功能",-1)),s("p",b,"控件:"+_(e.widgetId)+"。若应为管理员可见,请尝试顶部切换「管理员/运营」或刷新页面。",1)]),_:1}))}}}),C=f(h,[["__scopeId","data-v-4766a149"]]);export{C as P};
|
||||
import{aI as p,bv as c,bk as i,aE as u,aB as s,aC as m,L as g,bJ as v,ax as k,b9 as d,bq as y,ak as _}from"./index-368apsQG.js";/* empty css *//* empty css */const f=["title"],b={class:"pg-hidden-hint"},h=p({__name:"PermissionGuard",props:{widgetId:{}},setup(e){const n=e,l=c(),a=k(()=>l.widgetOf(n.widgetId));return(o,t)=>{const r=g;return a.value==="interactive"?i(o.$slots,"default",{key:0},void 0,!0):a.value==="readonly"?(d(),u("div",{key:1,class:"pg-readonly",title:`${e.widgetId} 当前为只读`},[i(o.$slots,"default",{},void 0,!0),t[0]||(t[0]=s("div",{class:"pg-mask"},null,-1))],8,f)):(d(),m(r,{key:2,class:"pg-hidden"},{description:v(()=>[t[1]||(t[1]=s("p",{class:"pg-hidden-title"},"当前账号无权查看此功能",-1)),s("p",b,"控件:"+y(e.widgetId)+"。若应为管理员可见,请尝试顶部切换「管理员/运营」或刷新页面。",1)]),_:1}))}}}),E=_(h,[["__scopeId","data-v-4766a149"]]);export{E as P};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{aG as c,aA as y,bH as a,t as w,b7 as E,aF as e,a1 as V,w as k,R as B,B as g,r as x,aE as n,br as z,bD as N,H as P,a9 as v,aa as D,az as A,bo as C,be as S,bf as K}from"./index-CC_hCGHZ.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */const T=30,X=c({__name:"PlaybackView",setup(G){const r=S({kw:"",range:null}),i=K([{id:"SNAP-001",ts:"2026-05-19 10:21:33",type:"调度异常",summary:"AGV-005 上线超时 → 故障",size:"128 KB"},{id:"SNAP-002",ts:"2026-05-19 14:05:17",type:"路口死锁",summary:"路口-N 等待链 3 节点 30s",size:"64 KB"},{id:"SNAP-003",ts:"2026-05-20 09:11:02",type:"手动快照",summary:"用户 admin 触发 SnapshotExport",size:"420 KB"}]);return(H,t)=>{const d=B,s=k,m=g,o=x,u=V,_=P,l=D,b=v,f=w;return E(),y(f,{shadow:"never"},{header:a(()=>[A("span",null,"调度回放(PlaybackPolicy.retentionDays="+C(T)+" 天)")]),default:a(()=>[e(u,{gutter:12},{default:a(()=>[e(s,{span:6},{default:a(()=>[e(d,{modelValue:r.kw,"onUpdate:modelValue":t[0]||(t[0]=p=>r.kw=p),placeholder:"按任务 / 车辆检索",clearable:""},null,8,["modelValue"])]),_:1}),e(s,{span:8},{default:a(()=>[e(m,{modelValue:r.range,"onUpdate:modelValue":t[1]||(t[1]=p=>r.range=p),type:"datetimerange","range-separator":"→","start-placeholder":"开始","end-placeholder":"结束",style:{width:"100%"}},null,8,["modelValue"])]),_:1}),e(s,{span:10},{default:a(()=>[e(o,{type:"primary"},{default:a(()=>[...t[2]||(t[2]=[n("检索快照",-1)])]),_:1}),e(o,null,{default:a(()=>[...t[3]||(t[3]=[n("下载日志",-1)])]),_:1}),e(o,{icon:z(N),plain:""},{default:a(()=>[...t[4]||(t[4]=[n("回放选中",-1)])]),_:1},8,["icon"])]),_:1})]),_:1}),e(_),e(b,{data:i.value,stripe:""},{default:a(()=>[e(l,{prop:"id",label:"ID",width:"100"}),e(l,{prop:"ts",label:"时间",width:"180"}),e(l,{prop:"type",label:"类型",width:"140"}),e(l,{prop:"summary",label:"概要"}),e(l,{prop:"size",label:"大小",width:"100"}),e(l,{label:"操作",width:"160"},{default:a(()=>[e(o,{text:"",size:"small"},{default:a(()=>[...t[5]||(t[5]=[n("回放",-1)])]),_:1}),e(o,{text:"",size:"small"},{default:a(()=>[...t[6]||(t[6]=[n("下载",-1)])]),_:1})]),_:1})]),_:1},8,["data"])]),_:1})}}});export{X as default};
|
||||
import{aI as c,aC as y,bJ as a,t as w,b9 as V,aH as e,a2 as E,w as k,R as B,B as g,r as x,aG as n,bt as N,bF as P,H as v,aa as z,ab as C,aB as D,bq as S,bg as A,bh as I}from"./index-368apsQG.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */const K=30,X=c({__name:"PlaybackView",setup(T){const s=A({kw:"",range:null}),i=I([{id:"SNAP-001",ts:"2026-05-19 10:21:33",type:"调度异常",summary:"AGV-005 上线超时 → 故障",size:"128 KB"},{id:"SNAP-002",ts:"2026-05-19 14:05:17",type:"路口死锁",summary:"路口-N 等待链 3 节点 30s",size:"64 KB"},{id:"SNAP-003",ts:"2026-05-20 09:11:02",type:"手动快照",summary:"用户 admin 触发 SnapshotExport",size:"420 KB"}]);return(G,t)=>{const d=B,r=k,m=g,o=x,u=E,_=v,l=C,b=z,f=w;return V(),y(f,{shadow:"never"},{header:a(()=>[D("span",null,"调度回放(PlaybackPolicy.retentionDays="+S(K)+" 天)")]),default:a(()=>[e(u,{gutter:12},{default:a(()=>[e(r,{span:6},{default:a(()=>[e(d,{modelValue:s.kw,"onUpdate:modelValue":t[0]||(t[0]=p=>s.kw=p),placeholder:"按任务 / 车辆检索",clearable:""},null,8,["modelValue"])]),_:1}),e(r,{span:8},{default:a(()=>[e(m,{modelValue:s.range,"onUpdate:modelValue":t[1]||(t[1]=p=>s.range=p),type:"datetimerange","range-separator":"→","start-placeholder":"开始","end-placeholder":"结束",style:{width:"100%"}},null,8,["modelValue"])]),_:1}),e(r,{span:10},{default:a(()=>[e(o,{type:"primary"},{default:a(()=>[...t[2]||(t[2]=[n("检索快照",-1)])]),_:1}),e(o,null,{default:a(()=>[...t[3]||(t[3]=[n("下载日志",-1)])]),_:1}),e(o,{icon:N(P),plain:""},{default:a(()=>[...t[4]||(t[4]=[n("回放选中",-1)])]),_:1},8,["icon"])]),_:1})]),_:1}),e(_),e(b,{data:i.value,stripe:""},{default:a(()=>[e(l,{prop:"id",label:"ID",width:"100"}),e(l,{prop:"ts",label:"时间",width:"180"}),e(l,{prop:"type",label:"类型",width:"140"}),e(l,{prop:"summary",label:"概要"}),e(l,{prop:"size",label:"大小",width:"100"}),e(l,{label:"操作",width:"160"},{default:a(()=>[e(o,{text:"",size:"small"},{default:a(()=>[...t[5]||(t[5]=[n("回放",-1)])]),_:1}),e(o,{text:"",size:"small"},{default:a(()=>[...t[6]||(t[6]=[n("下载",-1)])]),_:1})]),_:1})]),_:1},8,["data"])]),_:1})}}});export{X as default};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{R as o}from"./ReflectionManagerPanel-CpDwI_uG.js";import{aG as t,aC as r,aF as e,b7 as i,aj as p}from"./index-CC_hCGHZ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./reflection-CTDOoTn5.js";import"./useMapEditStream-BAGhVPpt.js";import"./useProjectionStream-C_klWvmm.js";const m={class:"process-page"},s=t({__name:"ProcessPanelView",setup(a){return(n,c)=>(i(),r("div",m,[e(o,{kind:"process","kind-label":"进程",title:"进程管理(Mission / Process)","empty-text":"当前没有进程;点击右上角「新建进程」即可基于已加载的 MissionType 实例化一个。"})]))}}),q=p(s,[["__scopeId","data-v-6a85813e"]]);export{q as default};
|
||||
import{R as o}from"./ReflectionManagerPanel-CKQWtKhJ.js";import{aI as t,aE as r,aH as e,b9 as i,ak as p}from"./index-368apsQG.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./reflection-C0K7EJ_N.js";import"./useMapEditStream-CoKaGT-a.js";import"./useProjectionStream-C94Y5UgX.js";const m={class:"process-page"},s=t({__name:"ProcessPanelView",setup(a){return(n,c)=>(i(),r("div",m,[e(o,{kind:"process","kind-label":"进程",title:"进程管理(Mission / Process)","empty-text":"当前没有进程;点击右上角「新建进程」即可基于已加载的 MissionType 实例化一个。"})]))}}),z=p(s,[["__scopeId","data-v-6a85813e"]]);export{z as default};
|
||||
+2
-2
File diff suppressed because one or more lines are too long
+3
-3
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
import{aG as f,aA as w,bH as t,br as h,b7 as g,aF as e,N as v,O as E,a3 as I,Z as k,a9 as x,aa as z,S as U,f as V}from"./index-CC_hCGHZ.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{C}from"./ConfigPageBase-B4fOVsSK.js";/* empty css *//* empty css *//* empty css */import"./PermissionGuard-DxbOGfWF.js";/* empty css */import"./reflection-CTDOoTn5.js";import"./monitorConfigCache-B5CZXjjB.js";const X=f({__name:"RoutingPolicyView",setup(D){function _(r){return Object.entries(r).map(([p,a])=>({k:p,v:a}))}return(r,p)=>{const a=k,u=I,m=E,o=z,d=U,n=x,c=v;return g(),w(C,{section:"routing",title:"路径规划策略",description:"算法选择、权重、避障规则、区域限速(RoutingPolicy)",defaults:h(V)},{default:t(({payload:l,update:s})=>[e(c,{"label-width":"140px",model:l},{default:t(()=>[e(m,{label:"算法"},{default:t(()=>[e(u,{"model-value":l.algorithm,"onUpdate:modelValue":i=>s({...l,algorithm:i})},{default:t(()=>[e(a,{label:"Dijkstra",value:"dijkstra"}),e(a,{label:"A*",value:"astar"}),e(a,{label:"自定义",value:"custom"})]),_:1},8,["model-value","onUpdate:modelValue"])]),_:2},1024),e(m,{label:"权重"},{default:t(()=>[e(n,{data:_(l.weights),size:"small"},{default:t(()=>[e(o,{label:"维度",prop:"k",width:"140"}),e(o,{label:"权重"},{default:t(i=>[e(d,{"model-value":i.row.v,min:0,max:10,step:.1,"onUpdate:modelValue":b=>s({...l,weights:{...l.weights,[i.row.k]:b??0}})},null,8,["model-value","onUpdate:modelValue"])]),_:2},1024)]),_:2},1032,["data"])]),_:2},1024),e(m,{label:"避障规则"},{default:t(()=>[e(n,{data:l.avoidance,size:"small"},{default:t(()=>[e(o,{label:"ID",prop:"id",width:"100"}),e(o,{label:"区域 ID",prop:"zoneId",width:"140"}),e(o,{label:"规则",prop:"rule"})]),_:1},8,["data"])]),_:2},1024),e(m,{label:"区域限速"},{default:t(()=>[e(n,{data:l.zoneSpeedLimits,size:"small"},{default:t(()=>[e(o,{label:"区域",prop:"zoneId",width:"140"}),e(o,{label:"最大速度 (m/s)",prop:"maxSpeedMps"})]),_:1},8,["data"])]),_:2},1024)]),_:2},1032,["model"])]),_:1},8,["defaults"])}}});export{X as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{aI as f,aC as w,bJ as t,bt as h,b9 as g,aH as e,N as v,O as E,a4 as I,Z as k,aa as x,ab as z,S as C,f as U}from"./index-368apsQG.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{C as V}from"./ConfigPageBase-CT6BdvCe.js";/* empty css *//* empty css *//* empty css */import"./PermissionGuard-DUg2FNrM.js";/* empty css */import"./reflection-C0K7EJ_N.js";import"./monitorConfigCache-BtW5W6GI.js";const X=f({__name:"RoutingPolicyView",setup(D){function _(r){return Object.entries(r).map(([p,a])=>({k:p,v:a}))}return(r,p)=>{const a=k,u=I,m=E,o=z,d=C,n=x,c=v;return g(),w(V,{section:"routing",title:"路径规划策略",description:"算法选择、权重、避障规则、区域限速(RoutingPolicy)",defaults:h(U)},{default:t(({payload:l,update:s})=>[e(c,{"label-width":"140px",model:l},{default:t(()=>[e(m,{label:"算法"},{default:t(()=>[e(u,{"model-value":l.algorithm,"onUpdate:modelValue":i=>s({...l,algorithm:i})},{default:t(()=>[e(a,{label:"Dijkstra",value:"dijkstra"}),e(a,{label:"A*",value:"astar"}),e(a,{label:"自定义",value:"custom"})]),_:1},8,["model-value","onUpdate:modelValue"])]),_:2},1024),e(m,{label:"权重"},{default:t(()=>[e(n,{data:_(l.weights),size:"small"},{default:t(()=>[e(o,{label:"维度",prop:"k",width:"140"}),e(o,{label:"权重"},{default:t(i=>[e(d,{"model-value":i.row.v,min:0,max:10,step:.1,"onUpdate:modelValue":b=>s({...l,weights:{...l.weights,[i.row.k]:b??0}})},null,8,["model-value","onUpdate:modelValue"])]),_:2},1024)]),_:2},1032,["data"])]),_:2},1024),e(m,{label:"避障规则"},{default:t(()=>[e(n,{data:l.avoidance,size:"small"},{default:t(()=>[e(o,{label:"ID",prop:"id",width:"100"}),e(o,{label:"区域 ID",prop:"zoneId",width:"140"}),e(o,{label:"规则",prop:"rule"})]),_:1},8,["data"])]),_:2},1024),e(m,{label:"区域限速"},{default:t(()=>[e(n,{data:l.zoneSpeedLimits,size:"small"},{default:t(()=>[e(o,{label:"区域",prop:"zoneId",width:"140"}),e(o,{label:"最大速度 (m/s)",prop:"maxSpeedMps"})]),_:1},8,["data"])]),_:2},1024)]),_:2},1032,["model"])]),_:1},8,["defaults"])}}});export{X as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{aI as b,aC as _,bJ as e,bt as C,b9 as n,aH as t,a2 as E,aE as g,ai as v,bj as w,w as S,t as h,aB as r,bq as a,ad as k,aG as o,H as D,C as P,F as V,g as B,ak as T}from"./index-368apsQG.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{C as x}from"./ConfigPageBase-CT6BdvCe.js";/* empty css */import"./PermissionGuard-DUg2FNrM.js";/* empty css */import"./reflection-C0K7EJ_N.js";import"./monitorConfigCache-BtW5W6GI.js";const I={class:"tpl-title"},L={class:"tpl-meta"},N={class:"tpl-baseline"},F=b({__name:"ScenarioTemplateView",setup(R){return(y,z)=>{const c=k,d=h,m=S,p=E,f=D,l=V,u=P;return n(),_(x,{section:"scenario",title:"业务场景模板化",description:"SPS / Pack / 环线 / 点对点;DSL/低代码扩展(ScenarioTemplateConfig)",defaults:C(B)},{default:e(({payload:s})=>[t(p,{gutter:12},{default:e(()=>[(n(!0),g(v,null,w(s.templates,i=>(n(),_(m,{key:i.id,span:6},{default:e(()=>[t(d,{shadow:"hover",class:"tpl-card"},{default:e(()=>[r("div",I,a(i.name),1),r("div",L,[t(c,{size:"small"},{default:e(()=>[o(a(i.category),1)]),_:2},1024),t(c,{size:"small",type:"info"},{default:e(()=>[o("v"+a(i.version),1)]),_:2},1024)]),r("pre",N,a(i.baselineJson||"{}"),1)]),_:2},1024)]),_:2},1024))),128))]),_:2},1024),t(f),t(u,{column:2,border:""},{default:e(()=>[t(l,{label:"DSL 启用"},{default:e(()=>[o(a(s.dslPolicy.enabled?"是":"否"),1)]),_:2},1024),t(l,{label:"Schema 版本"},{default:e(()=>[o(a(s.dslPolicy.schemaVersion),1)]),_:2},1024),t(l,{label:"低代码"},{default:e(()=>[o(a(s.lowCode.enabled?"启用":"关闭"),1)]),_:2},1024),t(l,{label:"编辑器"},{default:e(()=>[o(a(s.lowCode.editor),1)]),_:2},1024),t(l,{label:"保留版本"},{default:e(()=>[o(a(s.versionPolicy.keepVersions),1)]),_:2},1024),t(l,{label:"允许回滚"},{default:e(()=>[o(a(s.versionPolicy.allowRollback?"是":"否"),1)]),_:2},1024)]),_:2},1024)]),_:1},8,["defaults"])}}}),Y=T(F,[["__scopeId","data-v-4faf66a8"]]);export{Y as default};
|
||||
@@ -1 +0,0 @@
|
||||
import{aG as b,aA as _,bH as e,br as C,b7 as i,aF as t,a1 as h,aC as E,ah as g,bh as v,w,t as S,az as r,bo as a,ac as k,aE as o,H as D,C as P,F as V,g as T,aj as x}from"./index-CC_hCGHZ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{C as B}from"./ConfigPageBase-B4fOVsSK.js";/* empty css */import"./PermissionGuard-DxbOGfWF.js";/* empty css */import"./reflection-CTDOoTn5.js";import"./monitorConfigCache-B5CZXjjB.js";const F={class:"tpl-title"},L={class:"tpl-meta"},N={class:"tpl-baseline"},z=b({__name:"ScenarioTemplateView",setup(A){return(I,R)=>{const c=k,d=S,m=w,p=h,f=D,l=V,u=P;return i(),_(B,{section:"scenario",title:"业务场景模板化",description:"SPS / Pack / 环线 / 点对点;DSL/低代码扩展(ScenarioTemplateConfig)",defaults:C(T)},{default:e(({payload:s})=>[t(p,{gutter:12},{default:e(()=>[(i(!0),E(g,null,v(s.templates,n=>(i(),_(m,{key:n.id,span:6},{default:e(()=>[t(d,{shadow:"hover",class:"tpl-card"},{default:e(()=>[r("div",F,a(n.name),1),r("div",L,[t(c,{size:"small"},{default:e(()=>[o(a(n.category),1)]),_:2},1024),t(c,{size:"small",type:"info"},{default:e(()=>[o("v"+a(n.version),1)]),_:2},1024)]),r("pre",N,a(n.baselineJson||"{}"),1)]),_:2},1024)]),_:2},1024))),128))]),_:2},1024),t(f),t(u,{column:2,border:""},{default:e(()=>[t(l,{label:"DSL 启用"},{default:e(()=>[o(a(s.dslPolicy.enabled?"是":"否"),1)]),_:2},1024),t(l,{label:"Schema 版本"},{default:e(()=>[o(a(s.dslPolicy.schemaVersion),1)]),_:2},1024),t(l,{label:"低代码"},{default:e(()=>[o(a(s.lowCode.enabled?"启用":"关闭"),1)]),_:2},1024),t(l,{label:"编辑器"},{default:e(()=>[o(a(s.lowCode.editor),1)]),_:2},1024),t(l,{label:"保留版本"},{default:e(()=>[o(a(s.versionPolicy.keepVersions),1)]),_:2},1024),t(l,{label:"允许回滚"},{default:e(()=>[o(a(s.versionPolicy.allowRollback?"是":"否"),1)]),_:2},1024)]),_:2},1024)]),_:1},8,["defaults"])}}}),Y=x(z,[["__scopeId","data-v-4faf66a8"]]);export{Y as default};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{R as t}from"./ReflectionManagerPanel-CpDwI_uG.js";import{aG as r,aC as o,aF as i,b7 as e,aj as a}from"./index-CC_hCGHZ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./reflection-CTDOoTn5.js";import"./useMapEditStream-BAGhVPpt.js";import"./useProjectionStream-C_klWvmm.js";const m={class:"script-page"},p=r({__name:"ScriptPanelView",setup(s){return(c,n)=>(e(),o("div",m,[i(t,{kind:"script","kind-label":"脚本",title:"脚本管理(CarProgram 运行实例 · 与 SimpleLite 工作台「脚本」页对齐)","empty-text":"当前无 CarProgram 实例。CarProgram 是 Mission 运行时编译用户脚本后产生的对象,要创建脚本请到「任务编排」页新建 Mission。","disable-create":"","disable-delete":"","show-summary-column":"","summary-label":"车辆","show-status-column":"","status-label":"状态","show-script-actions":""})]))}}),N=a(p,[["__scopeId","data-v-40d1e5dd"]]);export{N as default};
|
||||
import{R as t}from"./ReflectionManagerPanel-CKQWtKhJ.js";import{aI as r,aE as o,aH as i,b9 as e,ak as a}from"./index-368apsQG.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./reflection-C0K7EJ_N.js";import"./useMapEditStream-CoKaGT-a.js";import"./useProjectionStream-C94Y5UgX.js";const m={class:"script-page"},p=r({__name:"ScriptPanelView",setup(s){return(c,n)=>(e(),o("div",m,[i(t,{kind:"script","kind-label":"脚本",title:"脚本管理(CarProgram 运行实例 · 与 SimpleLite 工作台「脚本」页对齐)","empty-text":"当前无 CarProgram 实例。CarProgram 是 Mission 运行时编译用户脚本后产生的对象,要创建脚本请到「任务编排」页新建 Mission。","disable-create":"","disable-delete":"","show-summary-column":"","summary-label":"车辆","show-status-column":"","status-label":"状态","show-script-actions":""})]))}}),q=a(p,[["__scopeId","data-v-40d1e5dd"]]);export{q as default};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{aG as w,b5 as E,b6 as k,aC as y,aF as s,bH as e,t as C,bf as V,bv as x,b7 as z,C as D,F as I,aE as a,bo as f,br as M,ac as W,az as m,r as B,aj as N}from"./index-CC_hCGHZ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css */const O={class:"status-page mg-content"},K={class:"status-actions"},R=w({__name:"ServiceStatusView",setup(T){const b=x(),_=new Date().toLocaleString("zh-CN"),i=V("00:00:00");let r;const S=Date.now();function u(){const p=Date.now()-S,t=Math.floor(p/1e3),o=String(Math.floor(t/3600)).padStart(2,"0"),l=String(Math.floor(t%3600/60)).padStart(2,"0"),d=String(t%60).padStart(2,"0");i.value=`${o}:${l}:${d}`}E(()=>{u(),r=window.setInterval(u,1e3)}),k(()=>{r&&clearInterval(r)});function v(){b.back()}return(p,t)=>{const o=W,l=I,d=D,n=B,g=C;return z(),y("div",O,[s(g,{shadow:"never",class:"status-card"},{header:e(()=>[t[1]||(t[1]=m("span",null,"SimpleLite Service Status",-1)),s(o,{type:"success",effect:"dark",style:{"margin-left":"8px"}},{default:e(()=>[...t[0]||(t[0]=[a("Web-Enabled (Mock)",-1)])]),_:1})]),default:e(()=>[s(d,{column:2,border:""},{default:e(()=>[s(l,{label:"模式"},{default:e(()=>[...t[2]||(t[2]=[a("Web-Enabled",-1)])]),_:1}),s(l,{label:"启动时间"},{default:e(()=>[a(f(M(_)),1)]),_:1}),s(l,{label:"运行时长"},{default:e(()=>[a(f(i.value),1)]),_:1}),s(l,{label:"节点角色"},{default:e(()=>[s(o,{type:"success"},{default:e(()=>[...t[3]||(t[3]=[a("Active (ROSE)",-1)])]),_:1})]),_:1}),s(l,{label:"WebAPI"},{default:e(()=>[t[5]||(t[5]=a("http://0.0.0.0:7001 ",-1)),s(o,{size:"small"},{default:e(()=>[...t[4]||(t[4]=[a("OK",-1)])]),_:1})]),_:1}),s(l,{label:"WebSocket"},{default:e(()=>[t[7]||(t[7]=a("ws://0.0.0.0:7002 ",-1)),s(o,{size:"small"},{default:e(()=>[...t[6]||(t[6]=[a("OK",-1)])]),_:1})]),_:1}),s(l,{label:"webVRender"},{default:e(()=>[t[9]||(t[9]=a("http://0.0.0.0:8223 ",-1)),s(o,{size:"small",type:"success"},{default:e(()=>[...t[8]||(t[8]=[a("OK",-1)])]),_:1})]),_:1}),s(l,{label:"Platform.Server"},{default:e(()=>[...t[10]||(t[10]=[a(":8080 (pid=12345)",-1)])]),_:1}),s(l,{label:"在线 Vue 客户端"},{default:e(()=>[...t[11]||(t[11]=[a("admin=3, monitor=4",-1)])]),_:1}),s(l,{label:"调度循环 / 任务"},{default:e(()=>[...t[12]||(t[12]=[a("50 Hz · 14 / 32",-1)])]),_:1})]),_:1}),m("div",K,[s(n,null,{default:e(()=>[...t[13]||(t[13]=[a("查看日志",-1)])]),_:1}),s(n,{type:"warning",plain:""},{default:e(()=>[...t[14]||(t[14]=[a("重启 Web",-1)])]),_:1}),s(n,{type:"danger",plain:""},{default:e(()=>[...t[15]||(t[15]=[a("关闭服务",-1)])]),_:1}),s(n,{onClick:v},{default:e(()=>[...t[16]||(t[16]=[a("返回",-1)])]),_:1})])]),_:1})])}}}),P=N(R,[["__scopeId","data-v-ddc086d3"]]);export{P as default};
|
||||
import{aI as w,b7 as k,b8 as E,aE as x,aH as s,bJ as e,t as y,bh as V,bx as C,b9 as D,C as I,F as z,aG as a,bq as f,bt as B,ad as M,aB as m,r as W,ak as N}from"./index-368apsQG.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css */const O={class:"status-page mg-content"},K={class:"status-actions"},R=w({__name:"ServiceStatusView",setup(T){const b=C(),_=new Date().toLocaleString("zh-CN"),i=V("00:00:00");let r;const S=Date.now();function u(){const p=Date.now()-S,t=Math.floor(p/1e3),o=String(Math.floor(t/3600)).padStart(2,"0"),l=String(Math.floor(t%3600/60)).padStart(2,"0"),d=String(t%60).padStart(2,"0");i.value=`${o}:${l}:${d}`}k(()=>{u(),r=window.setInterval(u,1e3)}),E(()=>{r&&clearInterval(r)});function v(){b.back()}return(p,t)=>{const o=M,l=z,d=I,n=W,g=y;return D(),x("div",O,[s(g,{shadow:"never",class:"status-card"},{header:e(()=>[t[1]||(t[1]=m("span",null,"SimpleLite Service Status",-1)),s(o,{type:"success",effect:"dark",style:{"margin-left":"8px"}},{default:e(()=>[...t[0]||(t[0]=[a("Web-Enabled (Mock)",-1)])]),_:1})]),default:e(()=>[s(d,{column:2,border:""},{default:e(()=>[s(l,{label:"模式"},{default:e(()=>[...t[2]||(t[2]=[a("Web-Enabled",-1)])]),_:1}),s(l,{label:"启动时间"},{default:e(()=>[a(f(B(_)),1)]),_:1}),s(l,{label:"运行时长"},{default:e(()=>[a(f(i.value),1)]),_:1}),s(l,{label:"节点角色"},{default:e(()=>[s(o,{type:"success"},{default:e(()=>[...t[3]||(t[3]=[a("Active (ROSE)",-1)])]),_:1})]),_:1}),s(l,{label:"WebAPI"},{default:e(()=>[t[5]||(t[5]=a("http://0.0.0.0:7001 ",-1)),s(o,{size:"small"},{default:e(()=>[...t[4]||(t[4]=[a("OK",-1)])]),_:1})]),_:1}),s(l,{label:"WebSocket"},{default:e(()=>[t[7]||(t[7]=a("ws://0.0.0.0:7002 ",-1)),s(o,{size:"small"},{default:e(()=>[...t[6]||(t[6]=[a("OK",-1)])]),_:1})]),_:1}),s(l,{label:"webVRender"},{default:e(()=>[t[9]||(t[9]=a("http://0.0.0.0:8223 ",-1)),s(o,{size:"small",type:"success"},{default:e(()=>[...t[8]||(t[8]=[a("OK",-1)])]),_:1})]),_:1}),s(l,{label:"Platform.Server"},{default:e(()=>[...t[10]||(t[10]=[a(":8080 (pid=12345)",-1)])]),_:1}),s(l,{label:"在线 Vue 客户端"},{default:e(()=>[...t[11]||(t[11]=[a("admin=3, monitor=4",-1)])]),_:1}),s(l,{label:"调度循环 / 任务"},{default:e(()=>[...t[12]||(t[12]=[a("50 Hz · 14 / 32",-1)])]),_:1})]),_:1}),m("div",K,[s(n,null,{default:e(()=>[...t[13]||(t[13]=[a("查看日志",-1)])]),_:1}),s(n,{type:"warning",plain:""},{default:e(()=>[...t[14]||(t[14]=[a("重启 Web",-1)])]),_:1}),s(n,{type:"danger",plain:""},{default:e(()=>[...t[15]||(t[15]=[a("关闭服务",-1)])]),_:1}),s(n,{onClick:v},{default:e(()=>[...t[16]||(t[16]=[a("返回",-1)])]),_:1})])]),_:1})])}}}),q=N(R,[["__scopeId","data-v-ddc086d3"]]);export{q as default};
|
||||
+2
-2
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
import{aG as p,aA as f,bH as o,br as c,b7 as b,aF as e,N as v,O as V,a0 as x,$ as E,aE as n,a7 as B,S as U,i as C}from"./index-CC_hCGHZ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{C as k}from"./ConfigPageBase-B4fOVsSK.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./PermissionGuard-DxbOGfWF.js";/* empty css */import"./reflection-CTDOoTn5.js";import"./monitorConfigCache-B5CZXjjB.js";const j=p({__name:"TaskAllocationView",setup(w){return(A,t)=>{const m=E,d=x,u=V,i=B,s=U,_=v;return b(),f(k,{section:"task",title:"任务分配机制",description:"负载均衡、就近分配、优先级调度(TaskAllocationPolicy)",defaults:c(C)},{default:o(({payload:l,update:r})=>[e(_,{"label-width":"160px",model:l},{default:o(()=>[e(u,{label:"分配模式"},{default:o(()=>[e(d,{"model-value":l.mode,"onUpdate:modelValue":a=>r({...l,mode:a})},{default:o(()=>[e(m,{value:"roundRobin"},{default:o(()=>[...t[0]||(t[0]=[n("轮询",-1)])]),_:1}),e(m,{value:"nearest"},{default:o(()=>[...t[1]||(t[1]=[n("就近",-1)])]),_:1}),e(m,{value:"leastLoad"},{default:o(()=>[...t[2]||(t[2]=[n("最少负载",-1)])]),_:1}),e(m,{value:"custom"},{default:o(()=>[...t[3]||(t[3]=[n("自定义",-1)])]),_:1})]),_:1},8,["model-value","onUpdate:modelValue"])]),_:2},1024),e(u,{label:"启用负载均衡"},{default:o(()=>[e(i,{"model-value":l.loadBalance,"onUpdate:modelValue":a=>r({...l,loadBalance:!!a})},null,8,["model-value","onUpdate:modelValue"])]),_:2},1024),e(u,{label:"单车队列上限"},{default:o(()=>[e(s,{"model-value":l.maxQueuePerCar,min:1,max:100,"onUpdate:modelValue":a=>r({...l,maxQueuePerCar:a??0})},null,8,["model-value","onUpdate:modelValue"])]),_:2},1024)]),_:2},1032,["model"])]),_:1},8,["defaults"])}}});export{j as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{aI as p,aC as f,bJ as o,bt as c,b9 as b,aH as e,N as v,O as V,a1 as x,a0 as B,aG as n,a8 as C,S as E,i as U}from"./index-368apsQG.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{C as k}from"./ConfigPageBase-CT6BdvCe.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./PermissionGuard-DUg2FNrM.js";/* empty css */import"./reflection-C0K7EJ_N.js";import"./monitorConfigCache-BtW5W6GI.js";const j=p({__name:"TaskAllocationView",setup(w){return(T,t)=>{const m=B,d=x,u=V,i=C,s=E,_=v;return b(),f(k,{section:"task",title:"任务分配机制",description:"负载均衡、就近分配、优先级调度(TaskAllocationPolicy)",defaults:c(U)},{default:o(({payload:l,update:r})=>[e(_,{"label-width":"160px",model:l},{default:o(()=>[e(u,{label:"分配模式"},{default:o(()=>[e(d,{"model-value":l.mode,"onUpdate:modelValue":a=>r({...l,mode:a})},{default:o(()=>[e(m,{value:"roundRobin"},{default:o(()=>[...t[0]||(t[0]=[n("轮询",-1)])]),_:1}),e(m,{value:"nearest"},{default:o(()=>[...t[1]||(t[1]=[n("就近",-1)])]),_:1}),e(m,{value:"leastLoad"},{default:o(()=>[...t[2]||(t[2]=[n("最少负载",-1)])]),_:1}),e(m,{value:"custom"},{default:o(()=>[...t[3]||(t[3]=[n("自定义",-1)])]),_:1})]),_:1},8,["model-value","onUpdate:modelValue"])]),_:2},1024),e(u,{label:"启用负载均衡"},{default:o(()=>[e(i,{"model-value":l.loadBalance,"onUpdate:modelValue":a=>r({...l,loadBalance:!!a})},null,8,["model-value","onUpdate:modelValue"])]),_:2},1024),e(u,{label:"单车队列上限"},{default:o(()=>[e(s,{"model-value":l.maxQueuePerCar,min:1,max:100,"onUpdate:modelValue":a=>r({...l,maxQueuePerCar:a??0})},null,8,["model-value","onUpdate:modelValue"])]),_:2},1024)]),_:2},1032,["model"])]),_:1},8,["defaults"])}}});export{j as default};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{aG as m,b7 as _,aC as k,aF as e,bH as a,a8 as x,ab as g,bf as l,aj as T,aA as v}from"./index-CC_hCGHZ.js";/* empty css *//* empty css */import{R as s}from"./ReflectionManagerPanel-CpDwI_uG.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./reflection-CTDOoTn5.js";import"./useMapEditStream-BAGhVPpt.js";import"./useProjectionStream-C_klWvmm.js";const y={class:"scene-mgr-page"},P=m({__name:"SceneManagerView",setup(f){const r=l("site"),o=l(null),p=l(null),c=l(null);function u(t){(t==="site"?o.value:t==="track"?p.value:t==="special"?c.value:null)?.refresh?.()}return(t,n)=>{const i=x,d=g;return _(),k("div",y,[e(d,{modelValue:r.value,"onUpdate:modelValue":n[0]||(n[0]=b=>r.value=b),type:"border-card",class:"scene-tabs admin-tabs",onTabChange:u},{default:a(()=>[e(i,{label:"站点",name:"site"},{default:a(()=>[e(s,{ref_key:"sitePanelRef",ref:o,kind:"site","kind-label":"站点",title:"站点管理(Site / UISite,含禁用/启用、必空点等动作)","empty-text":"当前没有站点;点击右上角「新建站点」按 x/y 坐标添加。"},null,512)]),_:1}),e(i,{label:"路径",name:"track"},{default:a(()=>[e(s,{ref_key:"trackPanelRef",ref:p,kind:"track","kind-label":"路径",title:"路径管理(Track / UITrack,含方向、冲突、投影、二分等动作)","empty-text":"当前没有路径;点击右上角「新建路径」选起止站点添加。"},null,512)]),_:1}),e(i,{label:"装饰物",name:"special"},{default:a(()=>[e(s,{ref_key:"specialPanelRef",ref:c,kind:"special","kind-label":"装饰物",title:"装饰物管理(UI_Image / UI_Text / UI_Model)","empty-text":"当前没有装饰物;新建图片/文本/模型可用底部按钮,或先在地图编辑里上传资产。"},null,512)]),_:1})]),_:1},8,["modelValue"])])}}}),R=T(P,[["__scopeId","data-v-ce41515a"]]),Y=m({__name:"TrackTableView",setup(f){return(r,o)=>(_(),v(R))}});export{Y as default};
|
||||
import{aI as m,b9 as _,aE as b,aH as e,bJ as a,a9 as x,ac as g,bh as l,ak as T,aC as v}from"./index-368apsQG.js";/* empty css *//* empty css */import{R as s}from"./ReflectionManagerPanel-CKQWtKhJ.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./reflection-C0K7EJ_N.js";import"./useMapEditStream-CoKaGT-a.js";import"./useProjectionStream-C94Y5UgX.js";const y={class:"scene-mgr-page"},I=m({__name:"SceneManagerView",setup(f){const r=l("site"),o=l(null),p=l(null),c=l(null);function u(t){(t==="site"?o.value:t==="track"?p.value:t==="special"?c.value:null)?.refresh?.()}return(t,n)=>{const i=x,d=g;return _(),b("div",y,[e(d,{modelValue:r.value,"onUpdate:modelValue":n[0]||(n[0]=k=>r.value=k),type:"border-card",class:"scene-tabs admin-tabs",onTabChange:u},{default:a(()=>[e(i,{label:"站点",name:"site"},{default:a(()=>[e(s,{ref_key:"sitePanelRef",ref:o,kind:"site","kind-label":"站点",title:"站点管理(Site / UISite,含禁用/启用、必空点等动作)","empty-text":"当前没有站点;点击右上角「新建站点」按 x/y 坐标添加。"},null,512)]),_:1}),e(i,{label:"路径",name:"track"},{default:a(()=>[e(s,{ref_key:"trackPanelRef",ref:p,kind:"track","kind-label":"路径",title:"路径管理(Track / UITrack,含方向、冲突、投影、二分等动作)","empty-text":"当前没有路径;点击右上角「新建路径」选起止站点添加。"},null,512)]),_:1}),e(i,{label:"装饰物",name:"special"},{default:a(()=>[e(s,{ref_key:"specialPanelRef",ref:c,kind:"special","kind-label":"装饰物",title:"装饰物管理(UI_Image / UI_Text / UI_Model)","empty-text":"当前没有装饰物;新建图片/文本/模型可用底部按钮,或先在地图编辑里上传资产。"},null,512)]),_:1})]),_:1},8,["modelValue"])])}}}),P=T(I,[["__scopeId","data-v-ce41515a"]]),Y=m({__name:"TrackTableView",setup(f){return(r,o)=>(_(),v(P))}});export{Y as default};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{aG as m,aA as d,bH as e,br as _,b7 as c,aF as a,ab as f,a8 as b,a9 as u,aa as w,aE as s,bo as p,j as I}from"./index-CC_hCGHZ.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css */import{C as T}from"./ConfigPageBase-B4fOVsSK.js";/* empty css *//* empty css *//* empty css */import"./PermissionGuard-DxbOGfWF.js";/* empty css */import"./reflection-CTDOoTn5.js";import"./monitorConfigCache-B5CZXjjB.js";const L=m({__name:"TrafficRuleView",setup(h){return(x,D)=>{const t=w,o=u,l=b,n=f;return c(),d(T,{section:"traffic",title:"交通管制规则",description:"路口策略、区域互斥、动态让行(TrafficRule)",defaults:_(I)},{default:e(({payload:i})=>[a(n,{"model-value":"ix"},{default:e(()=>[a(l,{name:"ix",label:"路口策略"},{default:e(()=>[a(o,{data:i.intersections,size:"small"},{default:e(()=>[a(t,{label:"ID",prop:"id",width:"100"}),a(t,{label:"站点 IDs",prop:"siteIds"},{default:e(r=>[s(p(r.row.siteIds.join(", ")),1)]),_:1}),a(t,{label:"模式",prop:"mode",width:"100"})]),_:1},8,["data"])]),_:2},1024),a(l,{name:"mz",label:"区域互斥"},{default:e(()=>[a(o,{data:i.mutex,size:"small"},{default:e(()=>[a(t,{label:"ID",prop:"id",width:"100"}),a(t,{label:"区域 IDs",prop:"zoneIds"},{default:e(r=>[s(p(r.row.zoneIds.join(", ")),1)]),_:1})]),_:1},8,["data"])]),_:2},1024),a(l,{name:"yd",label:"动态让行"},{default:e(()=>[a(o,{data:i.yields,size:"small"},{default:e(()=>[a(t,{label:"ID",prop:"id",width:"100"}),a(t,{label:"From",prop:"from",width:"140"}),a(t,{label:"To",prop:"to",width:"140"}),a(t,{label:"条件",prop:"condition"})]),_:1},8,["data"])]),_:2},1024)]),_:2},1024)]),_:1},8,["defaults"])}}});export{L as default};
|
||||
import{aI as m,aC as d,bJ as e,bt as _,b9 as c,aH as a,ac as f,a9 as b,aa as u,ab as I,aG as s,bq as p,j as w}from"./index-368apsQG.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css */import{C as T}from"./ConfigPageBase-CT6BdvCe.js";/* empty css *//* empty css *//* empty css */import"./PermissionGuard-DUg2FNrM.js";/* empty css */import"./reflection-C0K7EJ_N.js";import"./monitorConfigCache-BtW5W6GI.js";const H=m({__name:"TrafficRuleView",setup(h){return(x,C)=>{const t=I,o=u,l=b,n=f;return c(),d(T,{section:"traffic",title:"交通管制规则",description:"路口策略、区域互斥、动态让行(TrafficRule)",defaults:_(w)},{default:e(({payload:i})=>[a(n,{"model-value":"ix"},{default:e(()=>[a(l,{name:"ix",label:"路口策略"},{default:e(()=>[a(o,{data:i.intersections,size:"small"},{default:e(()=>[a(t,{label:"ID",prop:"id",width:"100"}),a(t,{label:"站点 IDs",prop:"siteIds"},{default:e(r=>[s(p(r.row.siteIds.join(", ")),1)]),_:1}),a(t,{label:"模式",prop:"mode",width:"100"})]),_:1},8,["data"])]),_:2},1024),a(l,{name:"mz",label:"区域互斥"},{default:e(()=>[a(o,{data:i.mutex,size:"small"},{default:e(()=>[a(t,{label:"ID",prop:"id",width:"100"}),a(t,{label:"区域 IDs",prop:"zoneIds"},{default:e(r=>[s(p(r.row.zoneIds.join(", ")),1)]),_:1})]),_:1},8,["data"])]),_:2},1024),a(l,{name:"yd",label:"动态让行"},{default:e(()=>[a(o,{data:i.yields,size:"small"},{default:e(()=>[a(t,{label:"ID",prop:"id",width:"100"}),a(t,{label:"From",prop:"from",width:"140"}),a(t,{label:"To",prop:"to",width:"140"}),a(t,{label:"条件",prop:"condition"})]),_:1},8,["data"])]),_:2},1024)]),_:2},1024)]),_:1},8,["defaults"])}}});export{H as default};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{aG as Q,b5 as X,b4 as J,aC as b,b1 as K,aF as u,ac as Y,bH as c,az as f,bo as s,aA as U,aB as p,r as Z,br as S,Q as ee,bf as l,av as V,b7 as i,aE as m,bg as ae,aQ as te,aX as ne,aj as oe}from"./index-CC_hCGHZ.js";/* empty css *//* empty css *//* empty css */const se={key:0,class:"workspace-3d-toolbar"},le={class:"hint"},re=["src"],ce={key:1,class:"workspace-3d-mask"},ie=1800,de=4,ue=18e3,fe=Q({__name:"Workspace3D",props:{host:{default:void 0},scope:{default:"Platform"},token:{default:""},readOnly:{type:Boolean,default:!1},embedUi:{type:Boolean,default:!1},canvasOnly:{type:Boolean,default:!1},hideToolbar:{type:Boolean,default:!1}},emits:["pick","select","ready"],setup(o,{expose:z,emit:F}){const n=o,h=F,_=l(),O=l(),y=l(!1),v=l("正在连接 webVRender ..."),w=l(null),g=l([]),r=l(""),d=V(()=>n.host??"localhost:8223"??"localhost:8223"),M=V(()=>{const a=new URLSearchParams;return a.set("scope",n.scope??"Platform"),n.token&&a.set("token",n.token),a.set("ro",n.readOnly?"1":"0"),n.canvasOnly?a.set("ui","canvas-only"):n.embedUi&&a.set("ui","embed"),`http://${d.value}/?${a.toString()}`});let k=null,E=0;function R(){return`http://${d.value}`}function B(a){return new Promise(e=>setTimeout(e,a))}function P(){return n.canvasOnly||n.embedUi}function H(){return n.canvasOnly?"/declareCanvasOnly":n.embedUi?"/declareEmbedUi":null}async function L(a){try{return(await fetch(`${R()}${a}`,{method:"GET",cache:"no-store",credentials:"omit",signal:AbortSignal.timeout(ie)})).ok}catch{return!1}}async function G(){const a=H();if(a){for(let e=0;e<de;e++){if(await L(a))return;await B(80*(e+1))}console.warn(`[Workspace3D] ${a} 未成功,将依赖 SimpleLite 默认 canvas-only 模式`)}}async function I(a=1e4){const e=Date.now()+a;let t=60;for(;Date.now()<e;){if(await L("/vrenderReady"))return!0;await B(t),t=Math.min(t+40,280)}return!1}function T(){k!=null&&(clearTimeout(k),k=null)}function N(a){T(),k=setTimeout(()=>{a!==E||y.value||(v.value=`连接超时 (http://${d.value}),请确认 SimpleLite 已启动`)},ue)}function C(a){const e=M.value;if(!a)return e;const t=e.includes("?")?"&":"?";return`${e}${t}_=${Date.now()}`}async function D(a=!1){const e=++E;if(y.value=!1,v.value=`正在连接 webVRender (http://${d.value}) ...`,N(e),P())G(),r.value=C(a||!!r.value);else{if(v.value="正在等待 SimpleLite webVRender 就绪 ...",await I(),e!==E)return;r.value=C(a||!!r.value)}}function j(){y.value=!0,T(),h("ready")}function W(a){if(!_.value||a.source!==_.value.contentWindow)return;const e=a.data;if(!(!e||typeof e!="object")){if(e.type==="workspace.pick"&&e.payload&&typeof e.payload.x=="number")w.value=e.payload,h("pick",e.payload);else if(e.type==="workspace.select"&&Array.isArray(e.payload))g.value=e.payload,h("select",e.payload);else if(e.type==="workspace.shortcut"&&e.payload&&typeof e.payload=="object"){const t=e.payload.action;(t==="undo"||t==="redo"||t==="delete")&&window.dispatchEvent(new CustomEvent("workspace-shortcut",{detail:{action:t}}))}}}function x(){D(!0)}function $(){const a=O.value;a&&(document.fullscreenElement?document.exitFullscreen():a.requestFullscreen())}return X(()=>{window.addEventListener("message",W),D(!1)}),J(()=>{window.removeEventListener("message",W),T()}),z({reload:x,enterFullscreen:$}),(a,e)=>{const t=Y,A=Z,q=ee;return i(),b("div",{class:K(["workspace-3d-wrap",{"workspace-3d--canvas-only":o.canvasOnly||o.hideToolbar}])},[o.canvasOnly||o.hideToolbar?p("",!0):(i(),b("div",se,[u(t,{type:o.readOnly?"info":"success",effect:"dark",size:"small"},{default:c(()=>[m(s(o.readOnly?"只读":"可交互"),1)]),_:1},8,["type"]),f("span",le,"webVRender · http://"+s(d.value)+" · scope="+s(o.scope),1),e[2]||(e[2]=f("div",{class:"spacer"},null,-1)),w.value?(i(),U(t,{key:0,type:"warning",size:"small"},{default:c(()=>[m(" Pick ("+s(w.value.x.toFixed(0))+", "+s(w.value.y.toFixed(0))+") ",1)]),_:1})):p("",!0),g.value.length?(i(),U(t,{key:1,type:"primary",size:"small"},{default:c(()=>[m(" Selected: "+s(g.value.length),1)]),_:1})):p("",!0),u(A,{size:"small",icon:S(ae),onClick:x},{default:c(()=>[...e[0]||(e[0]=[m("重载",-1)])]),_:1},8,["icon"]),u(A,{size:"small",icon:S(te),onClick:$},{default:c(()=>[...e[1]||(e[1]=[m("全屏",-1)])]),_:1},8,["icon"])])),f("div",{ref_key:"frameWrap",ref:O,class:"workspace-3d-frame"},[r.value?(i(),b("iframe",{key:0,ref_key:"frame",ref:_,src:r.value,class:"workspace-3d-iframe",allow:"fullscreen",onLoad:j},null,40,re)):p("",!0),y.value?p("",!0):(i(),b("div",ce,[u(q,{class:"is-loading",size:"32"},{default:c(()=>[u(S(ne))]),_:1}),f("span",null,s(v.value),1),e[3]||(e[3]=f("span",{class:"muted"},"如长时间未加载,请确认 SimpleLite 已以 Web-Enabled 模式启动且 8223 端口可达。",-1))]))],512)],2)}}}),we=oe(fe,[["__scopeId","data-v-17efa15d"]]);export{we as W};
|
||||
import{aI as J,b7 as Q,b6 as X,aE as b,b3 as Z,aH as u,ad as K,bJ as c,aB as f,bq as s,aC as V,aD as p,r as Y,bt as T,Q as ee,bh as l,ax as A,b9 as i,aG as m,bi as ae,aS as te,aZ as ne,ak as oe}from"./index-368apsQG.js";/* empty css *//* empty css *//* empty css */const se={key:0,class:"workspace-3d-toolbar"},le={class:"hint"},re=["src"],ce={key:1,class:"workspace-3d-mask"},ie=1800,de=4,ue=18e3,fe=J({__name:"Workspace3D",props:{host:{default:void 0},scope:{default:"Platform"},token:{default:""},readOnly:{type:Boolean,default:!1},embedUi:{type:Boolean,default:!1},canvasOnly:{type:Boolean,default:!1},hideToolbar:{type:Boolean,default:!1}},emits:["pick","select","ready"],setup(o,{expose:M,emit:R}){const n=o,h=R,_=l(),O=l(),y=l(!1),v=l("正在连接 webVRender ..."),w=l(null),g=l([]),r=l(""),d=A(()=>n.host??"localhost:8223"??"localhost:8223"),z=A(()=>{const a=new URLSearchParams;return a.set("scope",n.scope??"Platform"),n.token&&a.set("token",n.token),a.set("ro",n.readOnly?"1":"0"),n.canvasOnly?a.set("ui","canvas-only"):n.embedUi&&a.set("ui","embed"),`http://${d.value}/?${a.toString()}`});let k=null,E=0;function F(){return`http://${d.value}`}function B(a){return new Promise(e=>setTimeout(e,a))}function P(){return n.canvasOnly||n.embedUi}function H(){return n.canvasOnly?"/declareCanvasOnly":n.embedUi?"/declareEmbedUi":null}async function D(a){try{return(await fetch(`${F()}${a}`,{method:"GET",cache:"no-store",credentials:"omit",signal:AbortSignal.timeout(ie)})).ok}catch{return!1}}async function I(){const a=H();if(a){for(let e=0;e<de;e++){if(await D(a))return;await B(80*(e+1))}console.warn(`[Workspace3D] ${a} 未成功,将依赖 SimpleLite 默认 canvas-only 模式`)}}async function G(a=1e4){const e=Date.now()+a;let t=60;for(;Date.now()<e;){if(await D("/vrenderReady"))return!0;await B(t),t=Math.min(t+40,280)}return!1}function S(){k!=null&&(clearTimeout(k),k=null)}function N(a){S(),k=setTimeout(()=>{a!==E||y.value||(v.value=`连接超时 (http://${d.value}),请确认 SimpleLite 已启动`)},ue)}function L(a){const e=z.value;if(!a)return e;const t=e.includes("?")?"&":"?";return`${e}${t}_=${Date.now()}`}async function x(a=!1){const e=++E;if(y.value=!1,v.value=`正在连接 webVRender (http://${d.value}) ...`,N(e),P())I(),r.value=L(a||!!r.value);else{if(v.value="正在等待 SimpleLite webVRender 就绪 ...",await G(),e!==E)return;r.value=L(a||!!r.value)}}function q(){y.value=!0,S(),h("ready")}function C(a){if(!_.value||a.source!==_.value.contentWindow)return;const e=a.data;if(!(!e||typeof e!="object")){if(e.type==="workspace.pick"&&e.payload&&typeof e.payload.x=="number")w.value=e.payload,h("pick",e.payload);else if(e.type==="workspace.select"&&Array.isArray(e.payload))g.value=e.payload,h("select",e.payload);else if(e.type==="workspace.shortcut"&&e.payload&&typeof e.payload=="object"){const t=e.payload.action;(t==="undo"||t==="redo"||t==="delete")&&window.dispatchEvent(new CustomEvent("workspace-shortcut",{detail:{action:t}}))}}}function W(){x(!0)}function $(){const a=O.value;a&&(document.fullscreenElement?document.exitFullscreen():a.requestFullscreen())}return Q(()=>{window.addEventListener("message",C),x(!1)}),X(()=>{window.removeEventListener("message",C),S()}),M({reload:W,enterFullscreen:$}),(a,e)=>{const t=K,U=Y,j=ee;return i(),b("div",{class:Z(["workspace-3d-wrap",{"workspace-3d--canvas-only":o.canvasOnly||o.hideToolbar}])},[o.canvasOnly||o.hideToolbar?p("",!0):(i(),b("div",se,[u(t,{type:o.readOnly?"info":"success",effect:"dark",size:"small"},{default:c(()=>[m(s(o.readOnly?"只读":"可交互"),1)]),_:1},8,["type"]),f("span",le,"webVRender · http://"+s(d.value)+" · scope="+s(o.scope),1),e[2]||(e[2]=f("div",{class:"spacer"},null,-1)),w.value?(i(),V(t,{key:0,type:"warning",size:"small"},{default:c(()=>[m(" Pick ("+s(w.value.x.toFixed(0))+", "+s(w.value.y.toFixed(0))+") ",1)]),_:1})):p("",!0),g.value.length?(i(),V(t,{key:1,type:"primary",size:"small"},{default:c(()=>[m(" Selected: "+s(g.value.length),1)]),_:1})):p("",!0),u(U,{size:"small",icon:T(ae),onClick:W},{default:c(()=>[...e[0]||(e[0]=[m("重载",-1)])]),_:1},8,["icon"]),u(U,{size:"small",icon:T(te),onClick:$},{default:c(()=>[...e[1]||(e[1]=[m("全屏",-1)])]),_:1},8,["icon"])])),f("div",{ref_key:"frameWrap",ref:O,class:"workspace-3d-frame"},[r.value?(i(),b("iframe",{key:0,ref_key:"frame",ref:_,src:r.value,class:"workspace-3d-iframe",allow:"fullscreen",onLoad:q},null,40,re)):p("",!0),y.value?p("",!0):(i(),b("div",ce,[u(j,{class:"is-loading",size:"32"},{default:c(()=>[u(T(ne))]),_:1}),f("span",null,s(v.value),1),e[3]||(e[3]=f("span",{class:"muted"},"如长时间未加载,请确认 SimpleLite 已以 Web-Enabled 模式启动且 8223 端口可达。",-1))]))],512)],2)}}}),we=oe(fe,[["__scopeId","data-v-17efa15d"]]);export{we as W};
|
||||
+20
-20
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
import{aU as t}from"./index-368apsQG.js";const s="/sl/projection/map-edit",r="/sl/projection/ai-config";async function a(e){const{data:o}=await e;if(!o?.success)throw new Error(o?.message??"request failed");return o.data}const m={createSite:e=>a(t.post(`${s}/objects/site`,e)),createTrack:(e,o)=>a(t.post(`${s}/objects/${e}`,o)),createImage:e=>a(t.post(`${s}/objects/image`,e)),createText:e=>a(t.post(`${s}/objects/text`,e)),createModel:e=>a(t.post(`${s}/objects/model`,e)),createCar:e=>a(t.post(`${s}/objects/car`,e)),deleteObject:(e,o)=>a(t.delete(`${s}/objects/${e}/${o}`)),batch:e=>a(t.post(`${s}/objects/batch`,{ops:e})),copyFieldsTo:(e,o,c,p)=>a(t.post(`${s}/objects/${e}/${o}/fields/copy-to`,{fieldNames:c,targets:p})),pick:()=>a(t.post(`${s}/pick`)),dashboardSummary:()=>a(t.get(`${s}/dashboard/summary`)),uploadAsset:(e,o)=>a(t.post(`${s}/assets/upload`,{filename:e,data:o},{headers:{"Content-Type":"multipart/form-data"}})),aiMapGenerate:e=>a(t.post(`${s}/ai/map-generate`,e)),projectSave:e=>a(t.post(`${s}/project/save`,{path:e??""})),projectLoad:e=>a(t.post(`${s}/project/load`,{path:e})),projectCurrent:()=>a(t.get(`${s}/project/current`)),projectBrowse:e=>a(t.get(`${s}/project/browse`,{params:e?{dir:e}:{}})),projectNativePickOpen:e=>a(t.post(`${s}/project/native-pick-open`,{initialDir:e??""})),projectNativePickSave:(e,o)=>a(t.post(`${s}/project/native-pick-save`,{initialDir:e??"",suggestedFileName:o??"project.current.json"})),sampleSitesAlongTrack:(e,o)=>a(t.post(`${s}/objects/track/${e}/sample-sites`,o)),getViewFilter:()=>a(t.get(`${s}/view-filter`)),setViewFilter:e=>a(t.post(`${s}/view-filter`,e))},$={get:()=>a(t.get(`${r}/`)),save:e=>a(t.post(`${r}/`,e))},d={list:()=>a(t.get(`${s}/maps`)),sceneTaskStatus:()=>a(t.get(`${s}/maps/scene-task-status`)),async save(e,o=!1){const{data:c}=await t.post(`${s}/maps/save`,{name:e,overwrite:o});return c?.success?{ok:!0,data:c.data}:{ok:!1,conflict:c?.code===409,message:c?.message??"保存失败"}},open:e=>a(t.post(`${s}/maps/open`,{name:e})),use:e=>a(t.post(`${s}/maps/use`,{name:e})),delete:e=>a(t.delete(`${s}/maps/${encodeURIComponent(e)}`)),rename:(e,o)=>a(t.post(`${s}/maps/rename`,{from:e,to:o})),async merge(e,o,c=!1){const{data:p}=await t.post(`${s}/maps/merge`,{sources:e,target:o,overwrite:c});return p?.success?{ok:!0,data:p.data}:{ok:!1,conflict:p?.code===409&&(p?.message??"").includes("已存在"),message:p?.message??"合并失败"}}};export{$ as a,d as b,m};
|
||||
@@ -1 +0,0 @@
|
||||
import{aS as t}from"./index-CC_hCGHZ.js";const a="/sl/projection/map-edit",r="/sl/projection/ai-config";async function o(e){const{data:s}=await e;if(!s?.success)throw new Error(s?.message??"request failed");return s.data}const n={createSite:e=>o(t.post(`${a}/objects/site`,e)),createTrack:(e,s)=>o(t.post(`${a}/objects/${e}`,s)),createImage:e=>o(t.post(`${a}/objects/image`,e)),createText:e=>o(t.post(`${a}/objects/text`,e)),createModel:e=>o(t.post(`${a}/objects/model`,e)),createCar:e=>o(t.post(`${a}/objects/car`,e)),deleteObject:(e,s)=>o(t.delete(`${a}/objects/${e}/${s}`)),batch:e=>o(t.post(`${a}/objects/batch`,{ops:e})),copyFieldsTo:(e,s,p,c)=>o(t.post(`${a}/objects/${e}/${s}/fields/copy-to`,{fieldNames:p,targets:c})),pick:()=>o(t.post(`${a}/pick`)),dashboardSummary:()=>o(t.get(`${a}/dashboard/summary`)),uploadAsset:(e,s)=>o(t.post(`${a}/assets/upload`,{filename:e,data:s},{headers:{"Content-Type":"multipart/form-data"}})),aiMapGenerate:e=>o(t.post(`${a}/ai/map-generate`,e)),projectSave:e=>o(t.post(`${a}/project/save`,{path:e??""})),projectLoad:e=>o(t.post(`${a}/project/load`,{path:e})),projectCurrent:()=>o(t.get(`${a}/project/current`)),projectBrowse:e=>o(t.get(`${a}/project/browse`,{params:e?{dir:e}:{}})),projectNativePickOpen:e=>o(t.post(`${a}/project/native-pick-open`,{initialDir:e??""})),projectNativePickSave:(e,s)=>o(t.post(`${a}/project/native-pick-save`,{initialDir:e??"",suggestedFileName:s??"project.current.json"})),sampleSitesAlongTrack:(e,s)=>o(t.post(`${a}/objects/track/${e}/sample-sites`,s)),getViewFilter:()=>o(t.get(`${a}/view-filter`)),setViewFilter:e=>o(t.post(`${a}/view-filter`,e))},$={get:()=>o(t.get(`${r}/`)),save:e=>o(t.post(`${r}/`,e))};export{$ as a,n as m};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{r as e}from"./reflection-CTDOoTn5.js";let i=null,n=null;function o(){i=null,n=null}async function f(l=!1){return!l&&i?i:(!l&&n||(n=e.getMonitorConfig().then(t=>(i=t,n=null,t)).catch(t=>{throw n=null,t})),n)}export{f,o as i};
|
||||
import{r as e}from"./reflection-C0K7EJ_N.js";let i=null,n=null;function o(){i=null,n=null}async function f(l=!1){return!l&&i?i:(!l&&n||(n=e.getMonitorConfig().then(t=>(i=t,n=null,t)).catch(t=>{throw n=null,t})),n)}export{f,o as i};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{aS as a}from"./index-CC_hCGHZ.js";async function n(t){const{data:s}=await a.post("/sl/ops/execute",t);return s}async function o(){const{data:t}=await a.get("/sl/ops/audits");return t}export{n as e,o as l};
|
||||
import{aU as a}from"./index-368apsQG.js";async function n(t){const{data:s}=await a.post("/sl/ops/execute",t);return s}async function o(){const{data:t}=await a.get("/sl/ops/audits");return t}export{n as e,o as l};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{aS as e}from"./index-CC_hCGHZ.js";import{r as s}from"./reflection-CTDOoTn5.js";async function p(){const{data:t}=await e.get("/sl/projection/sites");return t}async function d(){const{data:t}=await e.get("/sl/projection/tracks");return t}function i(t){if(!t)return"idle";const r=t.toLowerCase();return/fault|error|failed|故障|异常|失联|超时|检修/.test(r)?"fault":/charg|充电/.test(r)?"charging":/pause|暂停|挂起/.test(r)?"paused":/offline|离线/.test(r)?"offline":/run|busy|working|运行|工作|执行|忙/.test(r)?"running":"idle"}function o(t){if(!t)return"queued";const r=t.toLowerCase();return/run|运行|执行/.test(r)?"running":/pause|暂停|挂起/.test(r)?"paused":/complete|done|完成|成功/.test(r)?"completed":/cancel|取消|中止/.test(r)?"cancelled":/fail|error|fault|失败|异常/.test(r)?"failed":/assign|分配/.test(r)?"assigned":"queued"}function u(t){return{id:`C${String(t.id).padStart(2,"0")}`,rawId:t.id,name:t.name,typeName:t.typeName,x:0,y:0,theta:0,batterySoc:.8,state:i(t.status),lastUpdate:new Date().toISOString(),group:t.layer??void 0}}function c(t){return{id:`M${String(t.id).padStart(2,"0")}`,name:t.name,typeName:t.typeName,priority:50,status:o(t.status),steps:[],createdAt:new Date().toISOString()}}async function n(){return(await s.listObjects("car")).map(u)}async function a(){return(await s.listObjects("mission")).map(c)}async function m(){try{const{data:t}=await e.get("/sl/projection/cars");if(Array.isArray(t)&&t.length>0)return t;const r=await n();return r.length>0?r:Array.isArray(t)?t:[]}catch{return n()}}async function y(){try{const{data:t}=await e.get("/sl/projection/missions");if(Array.isArray(t)&&t.length>0)return t;const r=await a();return r.length>0?r:Array.isArray(t)?t:[]}catch{return a()}}export{y as a,p as b,d as c,m as l};
|
||||
import{aU as e}from"./index-368apsQG.js";import{r as s}from"./reflection-C0K7EJ_N.js";async function p(){const{data:t}=await e.get("/sl/projection/sites");return t}async function d(){const{data:t}=await e.get("/sl/projection/tracks");return t}function i(t){if(!t)return"idle";const r=t.toLowerCase();return/fault|error|failed|故障|异常|失联|超时|检修/.test(r)?"fault":/charg|充电/.test(r)?"charging":/pause|暂停|挂起/.test(r)?"paused":/offline|离线/.test(r)?"offline":/run|busy|working|运行|工作|执行|忙/.test(r)?"running":"idle"}function o(t){if(!t)return"queued";const r=t.toLowerCase();return/run|运行|执行/.test(r)?"running":/pause|暂停|挂起/.test(r)?"paused":/complete|done|完成|成功/.test(r)?"completed":/cancel|取消|中止/.test(r)?"cancelled":/fail|error|fault|失败|异常/.test(r)?"failed":/assign|分配/.test(r)?"assigned":"queued"}function u(t){return{id:`C${String(t.id).padStart(2,"0")}`,rawId:t.id,name:t.name,typeName:t.typeName,x:0,y:0,theta:0,batterySoc:.8,state:i(t.status),lastUpdate:new Date().toISOString(),group:t.layer??void 0}}function c(t){return{id:`M${String(t.id).padStart(2,"0")}`,name:t.name,typeName:t.typeName,priority:50,status:o(t.status),steps:[],createdAt:new Date().toISOString()}}async function n(){return(await s.listObjects("car")).map(u)}async function a(){return(await s.listObjects("mission")).map(c)}async function m(){try{const{data:t}=await e.get("/sl/projection/cars");if(Array.isArray(t)&&t.length>0)return t;const r=await n();return r.length>0?r:Array.isArray(t)?t:[]}catch{return n()}}async function y(){try{const{data:t}=await e.get("/sl/projection/missions");if(Array.isArray(t)&&t.length>0)return t;const r=await a();return r.length>0?r:Array.isArray(t)?t:[]}catch{return a()}}export{y as a,p as b,d as c,m as l};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{aS as l}from"./index-CC_hCGHZ.js";const d="/sl/projection/reflection";function p(){return{fields:[],status:[],methods:[]}}function f(){return{car:p(),site:p(),track:p()}}const y=f;async function s(e){const{data:t}=await l.get(`${d}${e}`);if(!t?.success)throw new Error(t?.message??`reflection ${e} failed`);return t.data}async function n(e,t){const{data:o}=await l.post(`${d}${e}`,null,{params:t});if(!o?.success)throw new Error(o?.message??`reflection ${e} failed`);return o.data}async function g(e){const{data:t}=await l.delete(`${d}${e}`);if(!t?.success)throw new Error(t?.message??`reflection ${e} failed`);return t.data}async function u(e,t){const{data:o}=await l.patch(`${d}${e}`,t);if(!o?.success)throw new Error(o?.message??`reflection PATCH ${e} failed`);return o.data}const A={listKinds:()=>s("/kinds"),listAssemblies:()=>s("/assemblies"),listObjects:e=>s(`/objects/${e}`),listCreatableTypes:e=>s(`/types/${e}`),createObject:(e,t,o)=>{const r={};if(t&&(r.typeName=t),o)for(const[c,a]of Object.entries(o))a==null||a===""||(r[c]=a);return n(`/objects/${e}`,r)},deleteObject:(e,t)=>g(`/objects/${e}/${t}`),reloadPlugins:()=>n("/plugins/reload"),listPlugins:()=>s("/plugins"),unloadPlugin:e=>n(`/plugins/${encodeURIComponent(e)}/unload`),listMethods:(e,t)=>s(`/methods/${e}/${t}`),listMethodsByType:e=>s(`/methods-by-type/${e}`),getStatus:(e,t)=>s(`/status/${e}/${t}`),getFields:(e,t)=>s(`/fields/${e}/${t}`),setField:(e,t,o,r)=>n(`/fields/${e}/${t}/${encodeURIComponent(o)}`,{value:r}),deleteField:(e,t,o)=>g(`/fields/${e}/${t}/${encodeURIComponent(o)}`),getBundle:(e,t)=>s(`/bundle/${e}/${t}`),execute:(e,t,o,r)=>n(`/execute/${e}/${t}/${encodeURIComponent(o)}`,r),gotoCarSite:(e,t)=>n(`/car/${e}/goto-site`,{siteId:t}),getScriptSource:e=>s(`/scripts/${e}/source`),getScriptExceptionStatus:e=>s(`/scripts/${e}/exception-status`),getMonitorConfig:()=>s("/monitor-config"),saveMonitorConfig:async e=>{const{data:t}=await l.post(`${d}/monitor-config`,e);if(!t?.success)throw new Error(t?.message??"saveMonitorConfig failed");return t.data},getSelection:()=>s("/selection"),setSelection:(e,t)=>n("/selection",{kind:e,id:t}),clearSelection:()=>n("/selection/clear"),getProjectFields:()=>s("/project/fields"),setProjectField:(e,t)=>n(`/project/fields/${encodeURIComponent(e)}`,{value:t}),saveProject:e=>n("/project/save",e?{path:e}:void 0),getAppConfigFields:()=>s("/app-config/fields"),setAppConfigField:(e,t)=>n(`/app-config/fields/${encodeURIComponent(e)}`,{value:t}),saveAppConfig:()=>n("/app-config/save"),getViewportStyle:()=>s("/viewport-style"),patchViewportStyle:e=>u("/viewport-style",b(e)),getCarStyleTypes:()=>s("/car-style/types"),getCarStyle:e=>s(`/car-style/${encodeURIComponent(e)}`),putCarStyle:async(e,t)=>{const{data:o}=await l.post(`${d}/car-style/${encodeURIComponent(e)}`,t);if(!o?.success)throw new Error(o?.message??"putCarStyle failed");return o.data},deleteCarStyle:e=>g(`/car-style/${encodeURIComponent(e)}`),getAlarmColors:()=>s("/car-style/alarm-colors"),putAlarmColors:async e=>{const{data:t}=await l.post(`${d}/car-style/alarm-colors`,e);if(!t?.success)throw new Error(t?.message??"putAlarmColors failed");return t.data},saveCarStyle:()=>n("/car-style/save")};function i(e){return Number.isFinite(e)?e>>>0:0}function C(e){const t=i(e),o=t>>>24&255,r=t>>>16&255,c=t>>>8&255,a=t&255;return o>=255?`#${r.toString(16).padStart(2,"0")}${c.toString(16).padStart(2,"0")}${a.toString(16).padStart(2,"0")}`:`#${o.toString(16).padStart(2,"0")}${r.toString(16).padStart(2,"0")}${c.toString(16).padStart(2,"0")}${a.toString(16).padStart(2,"0")}`}function w(e){const t=e.replace("#","").trim();if(t.length===6){const o=parseInt(t.slice(0,2),16),r=parseInt(t.slice(2,4),16),c=parseInt(t.slice(4,6),16);return i(255<<24|o<<16|r<<8|c)}if(t.length===8){const o=parseInt(t.slice(0,2),16),r=parseInt(t.slice(2,4),16),c=parseInt(t.slice(4,6),16),a=parseInt(t.slice(6,8),16);return i(o<<24|r<<16|c<<8|a)}return 4294967295}const $=["colorNormalArgb","colorSelectedArgb","labelColorNormalArgb","labelColorSelectedArgb"],m=["colorNormalArgb","colorSelectedArgb"];function b(e){const t={};if(e.site){const o={...e.site};for(const r of $)o[r]!=null&&(o[r]=i(o[r]));t.site=o}if(e.track){const o={...e.track};for(const r of m)o[r]!=null&&(o[r]=i(o[r]));t.track=o}return t}function I(e){return{site:{...e.site,colorNormalArgb:i(e.site.colorNormalArgb),colorSelectedArgb:i(e.site.colorSelectedArgb),labelColorNormalArgb:i(e.site.labelColorNormalArgb),labelColorSelectedArgb:i(e.site.labelColorSelectedArgb)},track:{...e.track,colorNormalArgb:i(e.track.colorNormalArgb),colorSelectedArgb:i(e.track.colorSelectedArgb)}}}export{C as a,f as b,y as e,w as h,I as n,A as r};
|
||||
import{aU as l}from"./index-368apsQG.js";const d="/sl/projection/reflection";function p(){return{fields:[],status:[],methods:[]}}function f(){return{car:p(),site:p(),track:p()}}const y=f;async function s(e){const{data:t}=await l.get(`${d}${e}`);if(!t?.success)throw new Error(t?.message??`reflection ${e} failed`);return t.data}async function n(e,t){const{data:o}=await l.post(`${d}${e}`,null,{params:t});if(!o?.success)throw new Error(o?.message??`reflection ${e} failed`);return o.data}async function g(e){const{data:t}=await l.delete(`${d}${e}`);if(!t?.success)throw new Error(t?.message??`reflection ${e} failed`);return t.data}async function u(e,t){const{data:o}=await l.patch(`${d}${e}`,t);if(!o?.success)throw new Error(o?.message??`reflection PATCH ${e} failed`);return o.data}const A={listKinds:()=>s("/kinds"),listAssemblies:()=>s("/assemblies"),listObjects:e=>s(`/objects/${e}`),listCreatableTypes:e=>s(`/types/${e}`),createObject:(e,t,o)=>{const r={};if(t&&(r.typeName=t),o)for(const[c,a]of Object.entries(o))a==null||a===""||(r[c]=a);return n(`/objects/${e}`,r)},deleteObject:(e,t)=>g(`/objects/${e}/${t}`),reloadPlugins:()=>n("/plugins/reload"),listPlugins:()=>s("/plugins"),unloadPlugin:e=>n(`/plugins/${encodeURIComponent(e)}/unload`),listMethods:(e,t)=>s(`/methods/${e}/${t}`),listMethodsByType:e=>s(`/methods-by-type/${e}`),getStatus:(e,t)=>s(`/status/${e}/${t}`),getFields:(e,t)=>s(`/fields/${e}/${t}`),setField:(e,t,o,r)=>n(`/fields/${e}/${t}/${encodeURIComponent(o)}`,{value:r}),deleteField:(e,t,o)=>g(`/fields/${e}/${t}/${encodeURIComponent(o)}`),getBundle:(e,t)=>s(`/bundle/${e}/${t}`),execute:(e,t,o,r)=>n(`/execute/${e}/${t}/${encodeURIComponent(o)}`,r),gotoCarSite:(e,t)=>n(`/car/${e}/goto-site`,{siteId:t}),getScriptSource:e=>s(`/scripts/${e}/source`),getScriptExceptionStatus:e=>s(`/scripts/${e}/exception-status`),getMonitorConfig:()=>s("/monitor-config"),saveMonitorConfig:async e=>{const{data:t}=await l.post(`${d}/monitor-config`,e);if(!t?.success)throw new Error(t?.message??"saveMonitorConfig failed");return t.data},getSelection:()=>s("/selection"),setSelection:(e,t)=>n("/selection",{kind:e,id:t}),clearSelection:()=>n("/selection/clear"),getProjectFields:()=>s("/project/fields"),setProjectField:(e,t)=>n(`/project/fields/${encodeURIComponent(e)}`,{value:t}),saveProject:e=>n("/project/save",e?{path:e}:void 0),getAppConfigFields:()=>s("/app-config/fields"),setAppConfigField:(e,t)=>n(`/app-config/fields/${encodeURIComponent(e)}`,{value:t}),saveAppConfig:()=>n("/app-config/save"),getViewportStyle:()=>s("/viewport-style"),patchViewportStyle:e=>u("/viewport-style",b(e)),getCarStyleTypes:()=>s("/car-style/types"),getCarStyle:e=>s(`/car-style/${encodeURIComponent(e)}`),putCarStyle:async(e,t)=>{const{data:o}=await l.post(`${d}/car-style/${encodeURIComponent(e)}`,t);if(!o?.success)throw new Error(o?.message??"putCarStyle failed");return o.data},deleteCarStyle:e=>g(`/car-style/${encodeURIComponent(e)}`),getAlarmColors:()=>s("/car-style/alarm-colors"),putAlarmColors:async e=>{const{data:t}=await l.post(`${d}/car-style/alarm-colors`,e);if(!t?.success)throw new Error(t?.message??"putAlarmColors failed");return t.data},saveCarStyle:()=>n("/car-style/save")};function i(e){return Number.isFinite(e)?e>>>0:0}function C(e){const t=i(e),o=t>>>24&255,r=t>>>16&255,c=t>>>8&255,a=t&255;return o>=255?`#${r.toString(16).padStart(2,"0")}${c.toString(16).padStart(2,"0")}${a.toString(16).padStart(2,"0")}`:`#${o.toString(16).padStart(2,"0")}${r.toString(16).padStart(2,"0")}${c.toString(16).padStart(2,"0")}${a.toString(16).padStart(2,"0")}`}function w(e){const t=e.replace("#","").trim();if(t.length===6){const o=parseInt(t.slice(0,2),16),r=parseInt(t.slice(2,4),16),c=parseInt(t.slice(4,6),16);return i(255<<24|o<<16|r<<8|c)}if(t.length===8){const o=parseInt(t.slice(0,2),16),r=parseInt(t.slice(2,4),16),c=parseInt(t.slice(4,6),16),a=parseInt(t.slice(6,8),16);return i(o<<24|r<<16|c<<8|a)}return 4294967295}const $=["colorNormalArgb","colorSelectedArgb","labelColorNormalArgb","labelColorSelectedArgb"],m=["colorNormalArgb","colorSelectedArgb"];function b(e){const t={};if(e.site){const o={...e.site};for(const r of $)o[r]!=null&&(o[r]=i(o[r]));t.site=o}if(e.track){const o={...e.track};for(const r of m)o[r]!=null&&(o[r]=i(o[r]));t.track=o}return t}function I(e){return{site:{...e.site,colorNormalArgb:i(e.site.colorNormalArgb),colorSelectedArgb:i(e.site.colorSelectedArgb),labelColorNormalArgb:i(e.site.labelColorNormalArgb),labelColorSelectedArgb:i(e.site.labelColorSelectedArgb)},track:{...e.track,colorNormalArgb:i(e.track.colorNormalArgb),colorSelectedArgb:i(e.track.colorSelectedArgb)}}}export{C as a,f as b,y as e,w as h,I as n,A as r};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{u as n}from"./useProjectionStream-C_klWvmm.js";import{b5 as r,b4 as s}from"./index-CC_hCGHZ.js";function u(e){const c=n({autoConnect:!1});function o(a){const t=a?.payload;if(t)switch(a.kind){case"alarm":e.onAlarm?.(t);break;case"object-created":e.onObjectCreated?.(t);break;case"object-deleted":e.onObjectDeleted?.(t);break;case"object-patched":e.onObjectPatched?.(t);break;case"object-batch-changed":e.onObjectBatchChanged?.(t);break;case"pick-result":e.onPickResult?.(t);break;case"selection-detail":e.onSelectionDetail?.(t);break;case"workspace-shortcut":e.onWorkspaceShortcut?.(t);break;case"viewport-style-updated":e.onViewportStyleUpdated?.();break}}return r(()=>{c.on(o),c.connect()}),s(()=>{c.off(o),c.disconnect()}),{connected:c.connected}}export{u};
|
||||
import{u as n}from"./useProjectionStream-C94Y5UgX.js";import{b7 as r,b6 as s}from"./index-368apsQG.js";function u(e){const c=n({autoConnect:!1});function o(a){const t=a?.payload;if(t)switch(a.kind){case"alarm":e.onAlarm?.(t);break;case"object-created":e.onObjectCreated?.(t);break;case"object-deleted":e.onObjectDeleted?.(t);break;case"object-patched":e.onObjectPatched?.(t);break;case"object-batch-changed":e.onObjectBatchChanged?.(t);break;case"pick-result":e.onPickResult?.(t);break;case"selection-detail":e.onSelectionDetail?.(t);break;case"workspace-shortcut":e.onWorkspaceShortcut?.(t);break;case"viewport-style-updated":e.onViewportStyleUpdated?.();break}}return r(()=>{c.on(o),c.connect()}),s(()=>{c.off(o),c.disconnect()}),{connected:c.connected}}export{u};
|
||||
+1
-1
@@ -1 +1 @@
|
||||
import{b6 as S,bf as h}from"./index-CC_hCGHZ.js";const g=["snapshot-tick","selection-detail","alarm","object-created","object-deleted","object-patched","object-batch-changed","pick-result","project-saved","project-loaded","car-state","mission-status","monitor-config-updated","workspace-shortcut","viewport-style-updated"];function w(a={}){const y=a.url??"/api/sl/projection/stream",j=a.reconnectDelayMs??3e3,r=h(!1),u=h(null),s=new Set;let e=null,c=null,l=!1;function i(){if(l||e)return;try{e=new EventSource(y)}catch{p();return}e.onopen=()=>{r.value=!0},e.onerror=()=>{r.value=!1,f(),p()},e.onmessage=t=>{d(void 0,t.data)};const n=new Set([...g,...a.extraEventKinds??[]]);for(const t of n)e.addEventListener(t,o=>{d(t,o.data)})}function d(n,t){if(!(typeof t!="string"||!t))try{const o=JSON.parse(t),v=typeof o=="object"&&o!==null&&"kind"in o?o:{kind:n??"message",payload:o};u.value=v,s.forEach(k=>{try{k(v)}catch{}})}catch{}}function f(){if(e){try{e.close()}catch{}e=null}}function p(){l||c||(c=setTimeout(()=>{c=null,i()},j))}function m(){l=!0,c&&(clearTimeout(c),c=null),f(),r.value=!1}function b(n){s.add(n)}function E(n){s.delete(n)}return a.autoConnect!==!1&&i(),S(()=>{m(),s.clear()}),{connected:r,lastEvent:u,connect:i,disconnect:m,on:b,off:E}}export{w as u};
|
||||
import{b8 as S,bh as v}from"./index-368apsQG.js";const g=["snapshot-tick","selection-detail","alarm","object-created","object-deleted","object-patched","object-batch-changed","pick-result","project-saved","project-loaded","car-state","mission-status","monitor-config-updated","workspace-shortcut","viewport-style-updated"];function w(a={}){const y=a.url??"/api/sl/projection/stream",j=a.reconnectDelayMs??3e3,r=v(!1),u=v(null),s=new Set;let e=null,c=null,l=!1;function i(){if(l||e)return;try{e=new EventSource(y)}catch{p();return}e.onopen=()=>{r.value=!0},e.onerror=()=>{r.value=!1,f(),p()},e.onmessage=t=>{d(void 0,t.data)};const n=new Set([...g,...a.extraEventKinds??[]]);for(const t of n)e.addEventListener(t,o=>{d(t,o.data)})}function d(n,t){if(!(typeof t!="string"||!t))try{const o=JSON.parse(t),m=typeof o=="object"&&o!==null&&"kind"in o?o:{kind:n??"message",payload:o};u.value=m,s.forEach(k=>{try{k(m)}catch{}})}catch{}}function f(){if(e){try{e.close()}catch{}e=null}}function p(){l||c||(c=setTimeout(()=>{c=null,i()},j))}function h(){l=!0,c&&(clearTimeout(c),c=null),f(),r.value=!1}function b(n){s.add(n)}function E(n){s.delete(n)}return a.autoConnect!==!1&&i(),S(()=>{h(),s.clear()}),{connected:r,lastEvent:u,connect:i,disconnect:h,on:b,off:E}}export{w as u};
|
||||
@@ -6,8 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>迷毂 · 智能调度平台</title>
|
||||
<meta name="theme-color" content="#2e3f8a" />
|
||||
<script type="module" crossorigin src="/assets/index-CC_hCGHZ.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-B461CCj6.css">
|
||||
<script type="module" crossorigin src="/assets/index-368apsQG.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CYGH8MrE.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -7,6 +7,7 @@ export {}
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AiAssistantPanel: typeof import('./src/components/map-editor/AiAssistantPanel.vue')['default']
|
||||
AiGenerateDialog: typeof import('./src/components/map-editor/AiGenerateDialog.vue')['default']
|
||||
ConfigPageBase: typeof import('./src/components/ConfigPageBase.vue')['default']
|
||||
DataTablePro: typeof import('./src/components/DataTablePro.vue')['default']
|
||||
@@ -51,6 +52,7 @@ declare module 'vue' {
|
||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
@@ -71,8 +73,11 @@ declare module 'vue' {
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||
FieldMemberEditor: typeof import('./src/components/orchestration/FieldMemberEditor.vue')['default']
|
||||
FleetAllocationPanel: typeof import('./src/components/fleet/FleetAllocationPanel.vue')['default']
|
||||
FloatingAlarmCard: typeof import('./src/components/map-monitor/FloatingAlarmCard.vue')['default']
|
||||
FloatingAlarmStack: typeof import('./src/components/map-monitor/FloatingAlarmStack.vue')['default']
|
||||
MapConnectionPanel: typeof import('./src/components/map-manage/MapConnectionPanel.vue')['default']
|
||||
MapMergePanel: typeof import('./src/components/map-manage/MapMergePanel.vue')['default']
|
||||
MapMonitorConfigGroup: typeof import('./src/components/config/MapMonitorConfigGroup.vue')['default']
|
||||
MissionListPanel: typeof import('./src/components/workbench/MissionListPanel.vue')['default']
|
||||
MonitorSelectionPanel: typeof import('./src/components/workbench/MonitorSelectionPanel.vue')['default']
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* 多地图连接(跨楼层 / 多地图拼接)数据层。
|
||||
*
|
||||
* 业务背景:一个站点可作为「切换点站点」与另一张地图的某个切换点站点相连,
|
||||
* 配上转移代价(cm),即可在多张楼层地图之间做跨图路径规划与地图拼接。
|
||||
*
|
||||
* 持久化:后端暂无对应表,先用 localStorage 落地,保证前端功能完整、可演示;
|
||||
* 待后端补上 `/sl/projection/map-edit/connections` 系列接口后,仅需替换本文件实现,
|
||||
* 组件层(MapConnectionPanel)无需改动(接口已按异步 Promise 设计)。
|
||||
*/
|
||||
|
||||
export interface MapConnection {
|
||||
id: number
|
||||
/** 起始地图名称(与 mapsApi.list 的 name 对齐) */
|
||||
sourceMap: string
|
||||
/** 起始地图 ID(地图以名称为主键,这里按名称分配稳定数字 ID,呼应参考图的「地图ID」列) */
|
||||
sourceMapId: number
|
||||
/** 起始切换点站点 */
|
||||
sourceStation: string
|
||||
/** 目的地图名称 */
|
||||
targetMap: string
|
||||
/** 目的地图 ID */
|
||||
targetMapId: number
|
||||
/** 目的切换点站点 */
|
||||
targetStation: string
|
||||
/** 转移代价(cm) */
|
||||
cost: number
|
||||
}
|
||||
|
||||
export type MapConnectionInput = Omit<MapConnection, 'id' | 'sourceMapId' | 'targetMapId'>
|
||||
|
||||
const LS_KEY = 'mapEditor.mapConnections.v1'
|
||||
const LS_IDREG = 'mapEditor.mapIdRegistry.v1'
|
||||
|
||||
function readAll(): MapConnection[] {
|
||||
try {
|
||||
const raw = localStorage.getItem(LS_KEY)
|
||||
if (!raw) return []
|
||||
const arr = JSON.parse(raw)
|
||||
return Array.isArray(arr) ? (arr as MapConnection[]) : []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function writeAll(list: MapConnection[]): void {
|
||||
localStorage.setItem(LS_KEY, JSON.stringify(list))
|
||||
}
|
||||
|
||||
function readReg(): Record<string, number> {
|
||||
try {
|
||||
return (JSON.parse(localStorage.getItem(LS_IDREG) ?? '{}') as Record<string, number>) || {}
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function writeReg(reg: Record<string, number>): void {
|
||||
localStorage.setItem(LS_IDREG, JSON.stringify(reg))
|
||||
}
|
||||
|
||||
/** 名称 → 稳定数字 ID:首次遇到某地图名时分配一个递增 ID,并持久化,保证后续一致。 */
|
||||
export function mapIdOf(name: string): number {
|
||||
if (!name) return 0
|
||||
const reg = readReg()
|
||||
if (reg[name] != null) return reg[name]
|
||||
const next = Object.values(reg).reduce((m, v) => Math.max(m, v), 0) + 1
|
||||
reg[name] = next
|
||||
writeReg(reg)
|
||||
return next
|
||||
}
|
||||
|
||||
// 模拟一点点网络延迟,让 loading 态自然,也方便日后替换为真实 http 调用。
|
||||
function later<T>(value: T): Promise<T> {
|
||||
return new Promise((resolve) => setTimeout(() => resolve(value), 60))
|
||||
}
|
||||
|
||||
export const mapConnectionApi = {
|
||||
/** 列出全部连接(按 id 倒序,新建的在前)。 */
|
||||
list(): Promise<MapConnection[]> {
|
||||
return later(readAll().slice().sort((a, b) => b.id - a.id))
|
||||
},
|
||||
|
||||
create(input: MapConnectionInput): Promise<MapConnection> {
|
||||
const list = readAll()
|
||||
const id = list.reduce((m, c) => Math.max(m, c.id), 0) + 1
|
||||
const rec: MapConnection = {
|
||||
id,
|
||||
...input,
|
||||
sourceMapId: mapIdOf(input.sourceMap),
|
||||
targetMapId: mapIdOf(input.targetMap)
|
||||
}
|
||||
list.push(rec)
|
||||
writeAll(list)
|
||||
return later(rec)
|
||||
},
|
||||
|
||||
update(id: number, input: MapConnectionInput): Promise<MapConnection> {
|
||||
const list = readAll()
|
||||
const idx = list.findIndex((c) => c.id === id)
|
||||
if (idx < 0) return Promise.reject(new Error('连接不存在或已被删除'))
|
||||
const rec: MapConnection = {
|
||||
id,
|
||||
...input,
|
||||
sourceMapId: mapIdOf(input.sourceMap),
|
||||
targetMapId: mapIdOf(input.targetMap)
|
||||
}
|
||||
list[idx] = rec
|
||||
writeAll(list)
|
||||
return later(rec)
|
||||
},
|
||||
|
||||
remove(id: number): Promise<{ id: number; deleted: boolean }> {
|
||||
writeAll(readAll().filter((c) => c.id !== id))
|
||||
return later({ id, deleted: true })
|
||||
},
|
||||
|
||||
/** 历史用过的切换点站点名,给「切换点站点」下拉做候选(可继续手动输入新名)。 */
|
||||
stationSuggestions(): string[] {
|
||||
const set = new Set<string>()
|
||||
for (const c of readAll()) {
|
||||
if (c.sourceStation) set.add(c.sourceStation)
|
||||
if (c.targetStation) set.add(c.targetStation)
|
||||
}
|
||||
return Array.from(set)
|
||||
}
|
||||
}
|
||||
@@ -280,3 +280,136 @@ export const aiConfigApi = {
|
||||
get: () => unwrap<AiConfig>(http.get(`${AI_BASE}/`)),
|
||||
save: (cfg: AiConfig) => unwrap<{ saved: boolean }>(http.post(`${AI_BASE}/`, cfg))
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
// 地图管理(固定文件夹 + 按名称列表 / 保存 / 删除 / 使用 / 编辑打开)
|
||||
// 对应 SimpleLite MapEditApiController 的 /map-edit/maps* 接口。
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface MapListItem {
|
||||
/** 地图名称(文件名去掉 .json 后缀),列表展示与各操作都以它为标识。 */
|
||||
name: string
|
||||
fileName: string
|
||||
sizeBytes: number
|
||||
modified: string
|
||||
/** 是否为当前项目默认使用的地图(列表高亮)。 */
|
||||
isCurrent: boolean
|
||||
}
|
||||
|
||||
export interface MapListResult {
|
||||
directory: string
|
||||
currentFileName: string | null
|
||||
maps: MapListItem[]
|
||||
}
|
||||
|
||||
export interface MapLoadSummary {
|
||||
name: string
|
||||
fileName: string
|
||||
path: string
|
||||
sites: number
|
||||
tracks: number
|
||||
specials: number
|
||||
missions: number
|
||||
}
|
||||
|
||||
export interface SceneTaskBusyCar {
|
||||
id: number
|
||||
name: string
|
||||
reasons: string[]
|
||||
}
|
||||
|
||||
export interface SceneTaskStatus {
|
||||
hasTask: boolean
|
||||
busyCount: number
|
||||
busyCars: SceneTaskBusyCar[]
|
||||
}
|
||||
|
||||
export interface MapSaveResult {
|
||||
name: string
|
||||
fileName: string
|
||||
path: string
|
||||
savedAt: string
|
||||
}
|
||||
|
||||
/** 保存结果:success 正常返回 data;conflict=true 表示同名地图已存在,调用方应弹「替换」确认。 */
|
||||
export type MapSaveOutcome =
|
||||
| { ok: true; data: MapSaveResult }
|
||||
| { ok: false; conflict: boolean; message: string }
|
||||
|
||||
export interface MapMergeResult {
|
||||
name: string
|
||||
fileName: string
|
||||
path: string
|
||||
/** 底图 = 合并时的「当前使用地图」名。 */
|
||||
baseMap: string
|
||||
/** 合并进来的源地图名(去重、且不含当前地图自身,按叠加顺序)。 */
|
||||
sources: string[]
|
||||
sourceCount: number
|
||||
/** target 与当前地图同名 → 本次合并覆盖了当前地图。 */
|
||||
overwroteCurrent: boolean
|
||||
/** 合并后统计(用于结果提示)。 */
|
||||
sites: number
|
||||
tracks: number
|
||||
specials: number
|
||||
missions: number
|
||||
savedAt: string
|
||||
}
|
||||
|
||||
/** 合并结果:conflict=true 仅表示目标地图同名已存在,调用方应弹「替换」确认后重试。 */
|
||||
export type MapMergeOutcome =
|
||||
| { ok: true; data: MapMergeResult }
|
||||
| { ok: false; conflict: boolean; message: string }
|
||||
|
||||
export const mapsApi = {
|
||||
list: () => unwrap<MapListResult>(http.get(`${BASE}/maps`)),
|
||||
|
||||
sceneTaskStatus: () => unwrap<SceneTaskStatus>(http.get(`${BASE}/maps/scene-task-status`)),
|
||||
|
||||
/**
|
||||
* 保存当前场景为固定文件夹内的地图。overwrite=false 且同名已存在时后端回 409,
|
||||
* 这里翻译为 { ok:false, conflict:true },让调用方弹「是否替换原地图」确认框。
|
||||
*/
|
||||
async save(name: string, overwrite = false): Promise<MapSaveOutcome> {
|
||||
const { data } = await http.post<MapEditEnvelope<MapSaveResult>>(`${BASE}/maps/save`, { name, overwrite })
|
||||
if (data?.success) return { ok: true, data: data.data as MapSaveResult }
|
||||
return { ok: false, conflict: data?.code === 409, message: data?.message ?? '保存失败' }
|
||||
},
|
||||
|
||||
/** 加载指定地图到场景以供编辑(不校验任务、不改当前使用地图)。 */
|
||||
open: (name: string) => unwrap<MapLoadSummary>(http.post(`${BASE}/maps/open`, { name })),
|
||||
|
||||
/**
|
||||
* 设为当前使用地图并加载。后端切换前会校验场景无车辆任务;存在任务回 409,
|
||||
* unwrap 会抛出携带后端友好文案的 Error,调用方 try/catch 提示即可。
|
||||
*/
|
||||
use: (name: string) => unwrap<MapLoadSummary>(http.post(`${BASE}/maps/use`, { name })),
|
||||
|
||||
delete: (name: string) =>
|
||||
unwrap<{ name: string; fileName: string; deleted: boolean }>(
|
||||
http.delete(`${BASE}/maps/${encodeURIComponent(name)}`)
|
||||
),
|
||||
|
||||
/** 重命名 maps 目录下的地图文件(from / to 均为不含扩展名的地图名)。 */
|
||||
rename: (from: string, to: string) =>
|
||||
unwrap<{ from: string; to: string; fileName: string; path: string }>(
|
||||
http.post(`${BASE}/maps/rename`, { from, to })
|
||||
),
|
||||
|
||||
/**
|
||||
* 把选中的地图合并进「当前使用地图」后另存为新地图(语义同桌面端「合并」= SimpleProject.ImportFile)。
|
||||
* sources 为要合并进来的源地图名数组(≥1,当前地图自身会被后端忽略),以「当前使用地图」为底图依次叠加。
|
||||
* target 与当前地图同名 + overwrite 即覆盖当前地图。
|
||||
* 目标同名且 overwrite=false 时后端回 409 → { ok:false, conflict:true },调用方弹「替换」确认;
|
||||
* 当前未设置使用地图(400)/ 场景内有车辆任务(409)时 message 不含「已存在」,conflict=false,调用方直接提示。
|
||||
*/
|
||||
async merge(sources: string[], target: string, overwrite = false): Promise<MapMergeOutcome> {
|
||||
const { data } = await http.post<MapEditEnvelope<MapMergeResult>>(`${BASE}/maps/merge`, {
|
||||
sources,
|
||||
target,
|
||||
overwrite
|
||||
})
|
||||
if (data?.success) return { ok: true, data: data.data as MapMergeResult }
|
||||
const conflict = data?.code === 409 && (data?.message ?? '').includes('已存在')
|
||||
return { ok: false, conflict, message: data?.message ?? '合并失败' }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import http from './http'
|
||||
import type {
|
||||
WizardOptions,
|
||||
DeploymentProfileDto,
|
||||
SaveWizardRequest,
|
||||
EffectivePagesDto
|
||||
} from '@/types/wizard'
|
||||
|
||||
// 与 api/auth.ts 一致:VITE_USE_MOCK==='true' 时走本地假数据,便于无后端联调。
|
||||
const MOCK = import.meta.env.VITE_USE_MOCK === 'true'
|
||||
|
||||
const MOCK_OPTIONS: WizardOptions = {
|
||||
navigationKinds: [
|
||||
{ id: 'magnetic', name: '磁导航', group: 'navigation', description: '磁条循迹 + 地标 / RFID 定位' },
|
||||
{ id: 'qrcode', name: '二维码导航', group: 'navigation', description: '二维码地标 + 码值地图' },
|
||||
{ id: 'laser', name: '激光导航', group: 'navigation', description: '反光板 / SLAM + 激光避障' }
|
||||
],
|
||||
modules: [
|
||||
{ id: 'wms', name: 'WMS 仓储管理', group: 'module', description: '库位 / 库存 / 出入库管理' },
|
||||
{ id: 'ptl', name: 'PTL 拣选系统', group: 'module', description: 'Pick-to-Light 亮灯拣选与播种' }
|
||||
],
|
||||
scenarios: {
|
||||
templates: [
|
||||
{ id: 'tpl-sps', name: 'SPS 物料配送', category: 'SPS' },
|
||||
{ id: 'tpl-pack', name: '电池 Pack 自动化产线', category: 'BatteryPack' },
|
||||
{ id: 'tpl-loop', name: '环线运行', category: 'Loop' },
|
||||
{ id: 'tpl-p2p', name: '点对点柔性搬运', category: 'P2P' }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
let mockProfile: DeploymentProfileDto = {
|
||||
configured: false,
|
||||
platformType: 'standard',
|
||||
modules: [],
|
||||
navigationKinds: [],
|
||||
scenarios: [],
|
||||
updatedBy: 'mock',
|
||||
activeSceneIds: [],
|
||||
hiddenPages: []
|
||||
}
|
||||
|
||||
export async function getWizardOptions(): Promise<WizardOptions> {
|
||||
if (MOCK) return MOCK_OPTIONS
|
||||
const { data } = await http.get<WizardOptions>('/wizard/options')
|
||||
return data
|
||||
}
|
||||
|
||||
export async function getWizardProfile(): Promise<DeploymentProfileDto> {
|
||||
if (MOCK) return mockProfile
|
||||
const { data } = await http.get<DeploymentProfileDto>('/wizard/profile')
|
||||
return data
|
||||
}
|
||||
|
||||
export async function saveWizardProfile(req: SaveWizardRequest): Promise<DeploymentProfileDto> {
|
||||
if (MOCK) {
|
||||
mockProfile = {
|
||||
...mockProfile,
|
||||
platformType: req.platformType ?? mockProfile.platformType,
|
||||
modules: req.modules ?? [],
|
||||
navigationKinds: req.navigationKinds ?? [],
|
||||
scenarios: req.scenarios ?? [],
|
||||
configured: true,
|
||||
activeSceneIds: (req.navigationKinds ?? []).map((k) => `scene.${k}`)
|
||||
}
|
||||
return mockProfile
|
||||
}
|
||||
const { data } = await http.put<DeploymentProfileDto>('/wizard/profile', req)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function getEffectivePages(): Promise<EffectivePagesDto> {
|
||||
if (MOCK) {
|
||||
return { configured: mockProfile.configured, tailorablePages: [], enabledPages: [], hiddenPages: [] }
|
||||
}
|
||||
const { data } = await http.get<EffectivePagesDto>('/wizard/effective-pages')
|
||||
return data
|
||||
}
|
||||
@@ -130,5 +130,17 @@ export const workspaceToolbarApi = {
|
||||
CarId: carId ?? undefined,
|
||||
Enabled: enabled
|
||||
})
|
||||
),
|
||||
|
||||
/**
|
||||
* 一次性把地图相机定位(居中 + 2D 俯视)到指定车辆,不开启持续跟随。
|
||||
* 对应 SimpleLite `WorkspaceToolbarApiController.LocateCamera`(与原生「双击车辆行 = 选中+定位」一致)。
|
||||
*/
|
||||
locateCamera: (carId: number) =>
|
||||
unwrap<ToolbarState>(
|
||||
http.post(`${BASE}/camera/locate`, {
|
||||
carId,
|
||||
CarId: carId
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
<template>
|
||||
<section class="fleet-alloc">
|
||||
<div class="fa-header">
|
||||
<span class="fa-title">车队分配</span>
|
||||
<el-tag size="small" type="info" effect="plain">区域管理</el-tag>
|
||||
<span class="fa-sub">将车辆分配到车队,并设定车队名称 / 区域 / 楼层</span>
|
||||
<div class="spacer" />
|
||||
<el-button size="small" :icon="Refresh" :loading="loading" @click="reload(true)">重载</el-button>
|
||||
<el-button size="small" :icon="Plus" :disabled="!canWrite" @click="addFleet">新建车队</el-button>
|
||||
<el-button size="small" type="primary" :icon="Check" :loading="saving" :disabled="!canWrite || !dirty" @click="save">
|
||||
保存
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div v-loading="loading" class="fa-body">
|
||||
<el-empty v-if="!fleets.length" description="暂无车队,点击「新建车队」开始分配" />
|
||||
|
||||
<div v-for="(fleet, idx) in fleets" :key="fleet.id" class="fleet-card">
|
||||
<div class="fc-row">
|
||||
<el-input
|
||||
v-model="fleet.name"
|
||||
size="small"
|
||||
class="fc-name"
|
||||
placeholder="车队名称"
|
||||
:disabled="!canWrite"
|
||||
@input="markDirty">
|
||||
<template #prepend>名称</template>
|
||||
</el-input>
|
||||
<el-input
|
||||
v-model="fleet.region"
|
||||
size="small"
|
||||
class="fc-region"
|
||||
placeholder="区域"
|
||||
:disabled="!canWrite"
|
||||
@input="markDirty">
|
||||
<template #prepend>区域</template>
|
||||
</el-input>
|
||||
<el-input
|
||||
v-model="fleet.floor"
|
||||
size="small"
|
||||
class="fc-floor"
|
||||
placeholder="楼层"
|
||||
:disabled="!canWrite"
|
||||
@input="markDirty">
|
||||
<template #prepend>楼层</template>
|
||||
</el-input>
|
||||
<el-tag size="small" effect="plain">{{ fleet.carIds.length }} 辆</el-tag>
|
||||
<el-button
|
||||
size="small"
|
||||
type="danger"
|
||||
text
|
||||
:icon="Delete"
|
||||
:disabled="!canWrite"
|
||||
@click="removeFleet(idx)">
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-select
|
||||
v-model="fleet.carIds"
|
||||
size="small"
|
||||
multiple
|
||||
filterable
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
class="fc-cars"
|
||||
placeholder="选择要分配到该车队的车辆"
|
||||
:disabled="!canWrite"
|
||||
@change="markDirty">
|
||||
<el-option
|
||||
v-for="c in optionsForFleet(fleet)"
|
||||
:key="c.id"
|
||||
:label="c.label"
|
||||
:value="c.id"
|
||||
:disabled="c.takenByOther" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="unknownCarIds.length" class="fa-note">
|
||||
提示:以下已分配的车辆 ID 不在当前在册车辆中:{{ unknownCarIds.join('、') }}
|
||||
</p>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { Refresh, Check, Plus, Delete } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useConfigStore } from '@/stores/config'
|
||||
import { useFleetGroups } from '@/composables/useFleetGroups'
|
||||
import { DEFAULT_FLEET } from '@/mock/data/configs'
|
||||
import type { FleetGroup, FleetLifecycleConfig } from '@/types/config'
|
||||
|
||||
const props = defineProps<{
|
||||
cars: { id: string; name?: string }[]
|
||||
canWrite: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{ saved: [] }>()
|
||||
|
||||
const store = useConfigStore()
|
||||
const { reload: reloadShared } = useFleetGroups()
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const dirty = ref(false)
|
||||
|
||||
// 保留 fleet 配置中除 groups 以外的字段(OTA / 批量 / 诊断),保存时原样回写。
|
||||
const rest = ref<Omit<FleetLifecycleConfig, 'groups'>>({
|
||||
ota: DEFAULT_FLEET.ota,
|
||||
batchOps: DEFAULT_FLEET.batchOps,
|
||||
networkDiag: DEFAULT_FLEET.networkDiag
|
||||
})
|
||||
const fleets = ref<FleetGroup[]>([])
|
||||
|
||||
function markDirty() {
|
||||
dirty.value = true
|
||||
}
|
||||
|
||||
const carIndex = computed(() => {
|
||||
const m = new Map<string, string>()
|
||||
for (const c of props.cars) m.set(c.id, c.name ?? c.id)
|
||||
return m
|
||||
})
|
||||
|
||||
function optionsForFleet(fleet: FleetGroup) {
|
||||
const assignedElsewhere = new Set<string>()
|
||||
for (const f of fleets.value) {
|
||||
if (f === fleet) continue
|
||||
for (const id of f.carIds) assignedElsewhere.add(id)
|
||||
}
|
||||
return props.cars.map((c) => ({
|
||||
id: c.id,
|
||||
label: c.name && c.name !== c.id ? `${c.id} · ${c.name}` : c.id,
|
||||
takenByOther: assignedElsewhere.has(c.id)
|
||||
}))
|
||||
}
|
||||
|
||||
const unknownCarIds = computed(() => {
|
||||
const known = carIndex.value
|
||||
const out: string[] = []
|
||||
for (const f of fleets.value) {
|
||||
for (const id of f.carIds) {
|
||||
if (!known.has(id) && !out.includes(id)) out.push(id)
|
||||
}
|
||||
}
|
||||
return out
|
||||
})
|
||||
|
||||
function newFleetId(): string {
|
||||
const used = new Set(fleets.value.map((f) => f.id))
|
||||
let i = fleets.value.length + 1
|
||||
let id = `G-${i}`
|
||||
while (used.has(id)) {
|
||||
i += 1
|
||||
id = `G-${i}`
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
function addFleet() {
|
||||
fleets.value.push({ id: newFleetId(), name: '新车队', floor: '', region: '', carIds: [] })
|
||||
markDirty()
|
||||
}
|
||||
|
||||
function removeFleet(idx: number) {
|
||||
fleets.value.splice(idx, 1)
|
||||
markDirty()
|
||||
}
|
||||
|
||||
async function reload(force = false) {
|
||||
loading.value = true
|
||||
try {
|
||||
const env = await store.load<FleetLifecycleConfig>('fleet', force)
|
||||
const payload = env.payload ?? DEFAULT_FLEET
|
||||
rest.value = {
|
||||
ota: payload.ota ?? DEFAULT_FLEET.ota,
|
||||
batchOps: payload.batchOps ?? DEFAULT_FLEET.batchOps,
|
||||
networkDiag: payload.networkDiag ?? DEFAULT_FLEET.networkDiag
|
||||
}
|
||||
fleets.value = JSON.parse(JSON.stringify(payload.groups ?? [])) as FleetGroup[]
|
||||
dirty.value = false
|
||||
if (force) ElMessage.success('已重载车队配置')
|
||||
} catch (e) {
|
||||
ElMessage.error(`加载车队配置失败:${e instanceof Error ? e.message : String(e)}`)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function save() {
|
||||
saving.value = true
|
||||
try {
|
||||
const body: FleetLifecycleConfig = {
|
||||
...rest.value,
|
||||
groups: JSON.parse(JSON.stringify(fleets.value)) as FleetGroup[]
|
||||
}
|
||||
const env = await store.save<FleetLifecycleConfig>('fleet', body)
|
||||
dirty.value = false
|
||||
await reloadShared(true)
|
||||
emit('saved')
|
||||
ElMessage.success(`车队分配已保存 v${env.version}`)
|
||||
} catch (e) {
|
||||
ElMessage.error(`保存失败:${e instanceof Error ? e.message : String(e)}`)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => reload())
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fleet-alloc {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
background: var(--mg-veil-2, rgba(255, 255, 255, 0.03));
|
||||
}
|
||||
|
||||
.fa-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.fa-title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: var(--mg-text-light, #fff);
|
||||
}
|
||||
|
||||
.fa-sub {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.fa-header .spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.fa-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
max-height: 280px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.fleet-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.fc-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.fc-name { width: 220px; }
|
||||
.fc-region { width: 160px; }
|
||||
.fc-floor { width: 150px; }
|
||||
.fc-cars { width: 100%; }
|
||||
|
||||
.fa-note {
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
color: var(--el-color-warning);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,419 @@
|
||||
<template>
|
||||
<div
|
||||
class="ai-assistant-panel"
|
||||
:class="{ 'is-open': open }"
|
||||
:style="{ width: panelWidth + 'px' }"
|
||||
role="complementary"
|
||||
aria-label="AI 助手"
|
||||
>
|
||||
<div
|
||||
class="aap-resizer"
|
||||
title="拖拽调整宽度"
|
||||
@pointerdown="onResizeStart"
|
||||
@pointermove="onResizeMove"
|
||||
@pointerup="onResizeEnd"
|
||||
@pointercancel="onResizeEnd"
|
||||
></div>
|
||||
|
||||
<div class="aap-header">
|
||||
<div class="aap-title">
|
||||
<span class="aap-glyph">✦</span>
|
||||
<div class="aap-title-text">
|
||||
<div class="aap-title-main">AI 助手</div>
|
||||
<div class="aap-title-sub">用自然语言描述地图需求,自动生成站点 / 路径</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="aap-close" type="button" title="收起" @click="close">✕</button>
|
||||
</div>
|
||||
|
||||
<el-alert v-if="!configured" type="warning" :closable="false" class="aap-alert">
|
||||
尚未配置 AI 服务(apiKey / endpoint)。请先到
|
||||
<el-link type="primary" @click="goConfig">系统级配置 → AI 服务</el-link>
|
||||
完成配置。
|
||||
</el-alert>
|
||||
|
||||
<div ref="listRef" class="aap-messages">
|
||||
<div v-if="messages.length === 0" class="aap-empty">
|
||||
<div class="aap-empty-title">试着这样说:</div>
|
||||
<button
|
||||
v-for="(ex, i) in examples"
|
||||
:key="i"
|
||||
type="button"
|
||||
class="aap-example"
|
||||
@click="useExample(ex)"
|
||||
>{{ ex }}</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="(m, i) in messages"
|
||||
:key="i"
|
||||
class="aap-msg"
|
||||
:class="`aap-msg--${m.role}`"
|
||||
>
|
||||
<div class="aap-bubble">
|
||||
<div class="aap-bubble-text">{{ m.text }}</div>
|
||||
<div v-if="m.role === 'assistant' && m.meta" class="aap-meta">
|
||||
落地对象 <b>{{ m.meta.created }}</b> · 工具调用 <b>{{ m.meta.usedTools }}</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="busy" class="aap-msg aap-msg--assistant">
|
||||
<div class="aap-bubble aap-bubble--loading">
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
<span>AI 正在生成…</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="aap-toolbar">
|
||||
<span class="aap-toolbar-label">生成模式</span>
|
||||
<el-radio-group v-model="mode" size="small">
|
||||
<el-radio-button value="sites">站点</el-radio-button>
|
||||
<el-radio-button value="tracks">路径</el-radio-button>
|
||||
<el-radio-button value="sites+tracks">站点+路径</el-radio-button>
|
||||
<el-radio-button value="full">完整</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="aap-input">
|
||||
<el-input
|
||||
v-model="draft"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
resize="none"
|
||||
:disabled="!configured || busy"
|
||||
placeholder="例如:一条 U 型生产线,含 5 个工站,间距 2m,单向通行…(Enter 发送,Shift+Enter 换行)"
|
||||
@keydown="onKeydown"
|
||||
/>
|
||||
<el-button
|
||||
type="primary"
|
||||
class="aap-send"
|
||||
:loading="busy"
|
||||
:disabled="!configured || !draft.trim()"
|
||||
@click="send"
|
||||
>发送</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
|
||||
import { Loading } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { mapEditApi, type AiMapGenerateRequest, type AiMapGenerateResult } from '@/api/mapEdit'
|
||||
|
||||
const props = defineProps<{
|
||||
/** 面板是否展开(停靠在右侧)。 */
|
||||
open: boolean
|
||||
/** AI 服务是否已配置 apiKey / endpoint。 */
|
||||
configured: boolean
|
||||
/** 面板宽度(px),由父级持久化;可拖拽左沿调整。 */
|
||||
width?: number
|
||||
/** 生成范围默认值 x1,y1,x2,y2(mm),沿用编辑器默认。 */
|
||||
defaultBounds?: [number, number, number, number]
|
||||
/** 生成对象默认落点图层。 */
|
||||
defaultLayer?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:open', v: boolean): void
|
||||
(e: 'update:width', v: number): void
|
||||
(e: 'generated', r: AiMapGenerateResult): void
|
||||
}>()
|
||||
|
||||
/** 宽度约束:保证内容(按钮 / 单选组)不被压垮,也不至于把画布挤没。 */
|
||||
const MIN_WIDTH = 300
|
||||
const MAX_WIDTH = 720
|
||||
const panelWidth = computed(() => Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, props.width ?? 360)))
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
interface ChatMessage {
|
||||
role: 'user' | 'assistant'
|
||||
text: string
|
||||
meta?: { created: number; usedTools: number }
|
||||
}
|
||||
|
||||
const messages = ref<ChatMessage[]>([])
|
||||
const draft = ref('')
|
||||
const busy = ref(false)
|
||||
const listRef = ref<HTMLElement | null>(null)
|
||||
|
||||
// 记忆上次使用的生成模式:下次打开沿用,免去每次重选。
|
||||
type GenMode = NonNullable<AiMapGenerateRequest['mode']>
|
||||
const MODE_KEY = 'mapEditor.aiAssistant.mode'
|
||||
function loadMode(): GenMode {
|
||||
const v = localStorage.getItem(MODE_KEY)
|
||||
if (v === 'sites' || v === 'tracks' || v === 'sites+tracks' || v === 'full') return v
|
||||
return 'sites+tracks'
|
||||
}
|
||||
const mode = ref<GenMode>(loadMode())
|
||||
watch(mode, (v) => {
|
||||
try { localStorage.setItem(MODE_KEY, v) } catch { /* localStorage 不可用则忽略 */ }
|
||||
})
|
||||
|
||||
// ── 拖拽调整宽度 ──
|
||||
// 面板停靠右侧,左沿手柄向左拖 → 变宽。用 setPointerCapture 把后续 pointermove 锁定到
|
||||
// 手柄元素上,避免指针移到中间的 webVRender iframe 上方时事件被 iframe 吞掉、拖拽中断。
|
||||
let resizing = false
|
||||
let resizeStartX = 0
|
||||
let resizeStartW = 0
|
||||
function onResizeStart(e: PointerEvent) {
|
||||
resizing = true
|
||||
resizeStartX = e.clientX
|
||||
resizeStartW = panelWidth.value
|
||||
;(e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId)
|
||||
e.preventDefault()
|
||||
}
|
||||
function onResizeMove(e: PointerEvent) {
|
||||
if (!resizing) return
|
||||
const next = Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, resizeStartW + (resizeStartX - e.clientX)))
|
||||
emit('update:width', next)
|
||||
}
|
||||
function onResizeEnd(e: PointerEvent) {
|
||||
if (!resizing) return
|
||||
resizing = false
|
||||
;(e.currentTarget as HTMLElement).releasePointerCapture?.(e.pointerId)
|
||||
}
|
||||
onBeforeUnmount(() => { resizing = false })
|
||||
|
||||
const examples = [
|
||||
'生成一条横向直线,5 个站点,间距 2000mm',
|
||||
'画一个 3×3 的站点矩阵,间距 2500mm',
|
||||
'一条 U 型产线,含 5 个工站,单向通行'
|
||||
]
|
||||
|
||||
function close() {
|
||||
emit('update:open', false)
|
||||
}
|
||||
|
||||
function goConfig() {
|
||||
close()
|
||||
router.push('/admin/config/system')
|
||||
}
|
||||
|
||||
function useExample(ex: string) {
|
||||
draft.value = ex
|
||||
}
|
||||
|
||||
function onKeydown(e: Event | KeyboardEvent) {
|
||||
// el-input 的 keydown 事件签名是 Event | KeyboardEvent,这里收窄到键盘事件。
|
||||
// Enter 发送、Shift+Enter 换行;中文输入法组合期间(isComposing)不触发发送。
|
||||
if (!(e instanceof KeyboardEvent)) return
|
||||
if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) {
|
||||
e.preventDefault()
|
||||
void send()
|
||||
}
|
||||
}
|
||||
|
||||
async function scrollToBottom() {
|
||||
await nextTick()
|
||||
const el = listRef.value
|
||||
if (el) el.scrollTop = el.scrollHeight
|
||||
}
|
||||
|
||||
async function send() {
|
||||
const text = draft.value.trim()
|
||||
if (!text || busy.value || !props.configured) return
|
||||
|
||||
messages.value.push({ role: 'user', text })
|
||||
draft.value = ''
|
||||
void scrollToBottom()
|
||||
|
||||
busy.value = true
|
||||
try {
|
||||
const req: AiMapGenerateRequest = {
|
||||
prompt: text,
|
||||
mode: mode.value,
|
||||
bounds: props.defaultBounds,
|
||||
layer: props.defaultLayer
|
||||
}
|
||||
const r = await mapEditApi.aiMapGenerate(req)
|
||||
const created = r.created?.length ?? 0
|
||||
messages.value.push({
|
||||
role: 'assistant',
|
||||
text: r.assistantText?.trim() || `已根据你的描述生成并落地 ${created} 个对象。`,
|
||||
meta: { created, usedTools: r.usedTools ?? 0 }
|
||||
})
|
||||
emit('generated', r)
|
||||
} catch (err) {
|
||||
const msg = (err as Error).message
|
||||
messages.value.push({ role: 'assistant', text: `生成失败:${msg}` })
|
||||
ElMessage.error(`AI 助手生成失败:${msg}`)
|
||||
} finally {
|
||||
busy.value = false
|
||||
void scrollToBottom()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ai-assistant-panel {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 30;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* 实底(高不透明),解决「太透明看不清」 */
|
||||
background: linear-gradient(180deg, rgba(28, 12, 56, 0.98) 0%, rgba(16, 6, 34, 0.99) 100%);
|
||||
border-left: 1px solid rgba(190, 140, 240, 0.28);
|
||||
box-shadow: -10px 0 30px rgba(8, 2, 16, 0.55);
|
||||
color: rgba(236, 224, 250, 0.95);
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: transform 0.26s cubic-bezier(0.25, 0.8, 0.25, 1), opacity 0.26s ease, visibility 0.26s;
|
||||
}
|
||||
.ai-assistant-panel.is-open {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* 左沿拖拽手柄:覆盖在 border-left 上方,hover 高亮提示可拖拽。 */
|
||||
.aap-resizer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 6px;
|
||||
cursor: ew-resize;
|
||||
z-index: 5;
|
||||
background: transparent;
|
||||
transition: background 0.15s ease;
|
||||
touch-action: none;
|
||||
}
|
||||
.aap-resizer:hover { background: rgba(190, 140, 240, 0.45); }
|
||||
|
||||
.aap-header {
|
||||
flex: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 12px 14px;
|
||||
background: linear-gradient(135deg, rgba(120, 70, 220, 0.5) 0%, rgba(255, 90, 200, 0.4) 100%);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
.aap-title { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
||||
.aap-glyph {
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
text-shadow: 0 0 10px rgba(255, 200, 250, 0.7);
|
||||
flex: none;
|
||||
}
|
||||
.aap-title-text { min-width: 0; }
|
||||
.aap-title-main { font-size: 15px; font-weight: 700; color: #fff; line-height: 1.2; }
|
||||
.aap-title-sub { font-size: 11px; color: rgba(240, 222, 255, 0.78); margin-top: 2px; }
|
||||
.aap-close {
|
||||
appearance: none;
|
||||
border: 0;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
color: #fff;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
flex: none;
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
.aap-close:hover { background: rgba(255, 255, 255, 0.24); }
|
||||
|
||||
.aap-alert { margin: 10px 12px 0; }
|
||||
|
||||
.aap-messages {
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.aap-empty { padding: 8px 2px; display: flex; flex-direction: column; gap: 8px; }
|
||||
.aap-empty-title { font-size: 12px; color: rgba(210, 188, 240, 0.7); }
|
||||
.aap-example {
|
||||
appearance: none;
|
||||
text-align: left;
|
||||
border: 1px dashed rgba(190, 140, 240, 0.4);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
color: rgba(232, 215, 245, 0.9);
|
||||
border-radius: 8px;
|
||||
padding: 8px 10px;
|
||||
font-size: 12.5px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.aap-example:hover {
|
||||
background: rgba(150, 90, 230, 0.22);
|
||||
border-color: rgba(190, 140, 240, 0.7);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.aap-msg { display: flex; }
|
||||
.aap-msg--user { justify-content: flex-end; }
|
||||
.aap-msg--assistant { justify-content: flex-start; }
|
||||
.aap-bubble {
|
||||
max-width: 86%;
|
||||
padding: 8px 11px;
|
||||
border-radius: 12px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.aap-msg--user .aap-bubble {
|
||||
background: linear-gradient(135deg, rgba(150, 90, 240, 0.95) 0%, rgba(120, 70, 220, 0.95) 100%);
|
||||
color: #fff;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
.aap-msg--assistant .aap-bubble {
|
||||
background: rgba(255, 255, 255, 0.07);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
color: rgba(236, 224, 250, 0.95);
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
.aap-bubble-text { white-space: pre-wrap; word-break: break-word; }
|
||||
.aap-meta {
|
||||
margin-top: 6px;
|
||||
padding-top: 6px;
|
||||
border-top: 1px dashed rgba(255, 255, 255, 0.14);
|
||||
font-size: 11.5px;
|
||||
color: rgba(210, 188, 240, 0.8);
|
||||
}
|
||||
.aap-meta b { color: var(--mg-accent, #c4a4ff); }
|
||||
.aap-bubble--loading {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: rgba(210, 188, 240, 0.85);
|
||||
}
|
||||
|
||||
.aap-toolbar {
|
||||
flex: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.aap-toolbar-label { font-size: 11.5px; color: rgba(210, 188, 240, 0.7); flex: none; }
|
||||
.aap-toolbar :deep(.el-radio-button__inner) {
|
||||
padding: 5px 9px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.aap-input {
|
||||
flex: none;
|
||||
padding: 8px 12px 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.aap-send { align-self: flex-end; min-width: 84px; }
|
||||
</style>
|
||||
@@ -3,6 +3,7 @@
|
||||
:model-value="modelValue"
|
||||
title="AI 生图"
|
||||
width="640px"
|
||||
class="ai-generate-dialog"
|
||||
@update:model-value="(v: boolean) => emit('update:modelValue', v)"
|
||||
@close="onClose"
|
||||
>
|
||||
@@ -186,3 +187,18 @@ async function onGenerate() {
|
||||
}
|
||||
.ai-stat b { color: var(--mg-accent, #c4a4ff); }
|
||||
</style>
|
||||
|
||||
<!--
|
||||
非 scoped:el-dialog 会 teleport 到 body,scoped 的 data-v 不一定能命中对话框盒子。
|
||||
深色主题下全局 --el-bg-color 仅 0.55 不透明度,导致 AI 生图对话框「太透明、看不清」。
|
||||
这里按主题色把对话框背景设为实底(rgb 三元组无 alpha = 完全不透明),
|
||||
同时兼容 class 落在 .el-dialog 盒子或外层 overlay 两种情况。
|
||||
fame-lavender 浅色主题已有 `.el-dialog{background:#fff!important}` 且特异性更高,不受影响。
|
||||
-->
|
||||
<style>
|
||||
.ai-generate-dialog.el-dialog,
|
||||
.ai-generate-dialog .el-dialog {
|
||||
background-color: rgb(var(--mg-bg-card-rgb)) !important;
|
||||
box-shadow: 0 24px 60px rgba(8, 2, 16, 0.6) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
<template>
|
||||
<div class="edit-top-bar">
|
||||
<el-dropdown trigger="click" @command="onCmd">
|
||||
<el-button text class="topbar-btn">项目加载和保存</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="project.open">加载文件…</el-dropdown-item>
|
||||
<el-dropdown-item command="project.save">保存文件</el-dropdown-item>
|
||||
<el-dropdown-item command="project.saveAs">另存为…</el-dropdown-item>
|
||||
<el-dropdown-item command="project.props" divided>项目属性</el-dropdown-item>
|
||||
<el-dropdown-item command="project.close">关闭</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button
|
||||
type="primary"
|
||||
class="topbar-save-btn"
|
||||
:loading="saving"
|
||||
@click="onCmd('project.save')"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
|
||||
<el-dropdown trigger="click" @command="onCmd">
|
||||
<el-button text class="topbar-btn">导入</el-button>
|
||||
@@ -115,9 +111,6 @@
|
||||
<el-button size="small" :disabled="!canRedo" class="topbar-icon-btn" @click="onCmd('edit.redo')">↷</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tag v-if="saving" type="warning" effect="dark" class="topbar-saving-tag" size="small">
|
||||
保存中…
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -194,8 +187,13 @@ function mark(on: boolean): string {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.topbar-saving-tag {
|
||||
margin-left: 8px;
|
||||
.topbar-save-btn {
|
||||
font-weight: 600;
|
||||
font-size: 13.5px;
|
||||
padding: 6px 18px;
|
||||
height: auto;
|
||||
border-radius: 6px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.topbar-filter-menu :deep(.topbar-filter-header) {
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
<template>
|
||||
<div class="mc-panel">
|
||||
<div class="mc-toolbar">
|
||||
<el-input
|
||||
v-model="keyword"
|
||||
class="mc-search"
|
||||
placeholder="请输入地图名称搜索"
|
||||
clearable
|
||||
:prefix-icon="Search"
|
||||
/>
|
||||
<el-button type="primary" :icon="Plus" @click="openCreate">添加地图关系</el-button>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="pageRows"
|
||||
class="mc-table"
|
||||
border
|
||||
empty-text="还没有地图连接关系,点击右上角「添加地图关系」创建跨楼层 / 拼接连接。"
|
||||
>
|
||||
<el-table-column prop="id" label="ID" width="80" sortable />
|
||||
<el-table-column prop="sourceMap" label="起始地图名称" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="sourceMapId" label="起始地图ID" width="110" />
|
||||
<el-table-column prop="sourceStation" label="起始切换点站点" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="targetMap" label="目的地图名称" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="targetMapId" label="目的地图ID" width="110" />
|
||||
<el-table-column prop="targetStation" label="目的切换点站点" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="转移代价(cm)" width="130">
|
||||
<template #default="{ row }">
|
||||
<span class="mc-cost mg-mono">{{ row.cost }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="130" align="right" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button link type="danger" @click="onDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="mc-pager">
|
||||
<el-pagination
|
||||
v-model:current-page="page"
|
||||
v-model:page-size="pageSize"
|
||||
:total="filtered.length"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
background
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 添加 / 编辑 地图关系 -->
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="editingId === null ? '添加地图关系' : '编辑地图关系'"
|
||||
width="520px"
|
||||
class="map-conn-dialog"
|
||||
append-to-body
|
||||
@closed="onDialogClosed"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px" label-position="right">
|
||||
<el-form-item label="起始地图" prop="sourceMap">
|
||||
<el-select v-model="form.sourceMap" placeholder="请选择起始地图" filterable style="width: 100%">
|
||||
<el-option v-for="m in mapOptions" :key="m" :label="m" :value="m" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="起始切换点站点" prop="sourceStation">
|
||||
<el-select
|
||||
v-model="form.sourceStation"
|
||||
placeholder="请选择起始切换点站点"
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option v-for="s in stationOptions" :key="s" :label="s" :value="s" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="目的地图" prop="targetMap">
|
||||
<el-select v-model="form.targetMap" placeholder="请选择目的地图" filterable style="width: 100%">
|
||||
<el-option v-for="m in mapOptions" :key="m" :label="m" :value="m" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="目的切换点站点" prop="targetStation">
|
||||
<el-select
|
||||
v-model="form.targetStation"
|
||||
placeholder="请选择目的切换点站点"
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option v-for="s in stationOptions" :key="s" :label="s" :value="s" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="转移代价(cm)" prop="cost">
|
||||
<el-input-number v-model="form.cost" :min="0" :step="100" :controls="false" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="onSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { Search, Plus } from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
|
||||
import { mapsApi } from '@/api/mapEdit'
|
||||
import { mapConnectionApi, type MapConnection } from '@/api/mapConnection'
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const rows = ref<MapConnection[]>([])
|
||||
const mapOptions = ref<string[]>([])
|
||||
const stationOptions = ref<string[]>([])
|
||||
|
||||
const keyword = ref('')
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const editingId = ref<number | null>(null)
|
||||
const formRef = ref<FormInstance>()
|
||||
const form = reactive({
|
||||
sourceMap: '',
|
||||
sourceStation: '',
|
||||
targetMap: '',
|
||||
targetStation: '',
|
||||
cost: 1000
|
||||
})
|
||||
|
||||
const rules: FormRules = {
|
||||
sourceMap: [{ required: true, message: '请选择起始地图', trigger: 'change' }],
|
||||
sourceStation: [{ required: true, message: '请选择起始切换点站点', trigger: 'change' }],
|
||||
targetMap: [{ required: true, message: '请选择目的地图', trigger: 'change' }],
|
||||
targetStation: [{ required: true, message: '请选择目的切换点站点', trigger: 'change' }],
|
||||
cost: [{ required: true, message: '请输入转移代价', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const filtered = computed(() => {
|
||||
const kw = keyword.value.trim().toLowerCase()
|
||||
if (!kw) return rows.value
|
||||
return rows.value.filter(
|
||||
(r) => r.sourceMap.toLowerCase().includes(kw) || r.targetMap.toLowerCase().includes(kw)
|
||||
)
|
||||
})
|
||||
|
||||
const pageRows = computed(() => {
|
||||
const start = (page.value - 1) * pageSize.value
|
||||
return filtered.value.slice(start, start + pageSize.value)
|
||||
})
|
||||
|
||||
// 搜索 / 分页大小变化时回到第一页,避免停留在空白页。
|
||||
watch([keyword, pageSize], () => {
|
||||
page.value = 1
|
||||
})
|
||||
|
||||
async function refresh() {
|
||||
loading.value = true
|
||||
try {
|
||||
rows.value = await mapConnectionApi.list()
|
||||
stationOptions.value = mapConnectionApi.stationSuggestions()
|
||||
} catch (err) {
|
||||
ElMessage.error(`加载地图连接失败:${(err as Error).message}`)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMaps() {
|
||||
try {
|
||||
const r = await mapsApi.list()
|
||||
mapOptions.value = r.maps.map((m) => m.name)
|
||||
} catch {
|
||||
// 地图列表拉取失败不阻塞连接管理;下拉为空时仍可手动输入站点。
|
||||
mapOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
form.sourceMap = ''
|
||||
form.sourceStation = ''
|
||||
form.targetMap = ''
|
||||
form.targetStation = ''
|
||||
form.cost = 1000
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
editingId.value = null
|
||||
resetForm()
|
||||
stationOptions.value = mapConnectionApi.stationSuggestions()
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
function openEdit(row: MapConnection) {
|
||||
editingId.value = row.id
|
||||
form.sourceMap = row.sourceMap
|
||||
form.sourceStation = row.sourceStation
|
||||
form.targetMap = row.targetMap
|
||||
form.targetStation = row.targetStation
|
||||
form.cost = row.cost
|
||||
stationOptions.value = mapConnectionApi.stationSuggestions()
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
function onDialogClosed() {
|
||||
formRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
if (!formRef.value) return
|
||||
await formRef.value.validate(async (ok) => {
|
||||
if (!ok) return
|
||||
saving.value = true
|
||||
try {
|
||||
const payload = {
|
||||
sourceMap: form.sourceMap,
|
||||
sourceStation: form.sourceStation,
|
||||
targetMap: form.targetMap,
|
||||
targetStation: form.targetStation,
|
||||
cost: form.cost
|
||||
}
|
||||
if (editingId.value === null) {
|
||||
await mapConnectionApi.create(payload)
|
||||
ElMessage.success('已添加地图连接')
|
||||
} else {
|
||||
await mapConnectionApi.update(editingId.value, payload)
|
||||
ElMessage.success('已更新地图连接')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
await refresh()
|
||||
} catch (err) {
|
||||
ElMessage.error(`保存失败:${(err as Error).message}`)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function onDelete(row: MapConnection) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确认删除「${row.sourceMap} → ${row.targetMap}」这条地图连接?`,
|
||||
'删除地图连接',
|
||||
{ type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消', confirmButtonClass: 'el-button--danger' }
|
||||
)
|
||||
await mapConnectionApi.remove(row.id)
|
||||
ElMessage.success('已删除')
|
||||
// 删除后当前页可能空了,回退一页。
|
||||
if (pageRows.value.length === 1 && page.value > 1) page.value -= 1
|
||||
await refresh()
|
||||
} catch (err) {
|
||||
if (err === 'cancel' || err === 'close') return
|
||||
ElMessage.error(`删除失败:${(err as Error).message}`)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
refresh()
|
||||
loadMaps()
|
||||
})
|
||||
|
||||
defineExpose({ refresh })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mc-panel {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
min-height: 0;
|
||||
}
|
||||
.mc-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.mc-search {
|
||||
width: 300px;
|
||||
max-width: 60%;
|
||||
}
|
||||
.mc-table {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
.mc-cost {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.mc-pager {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 2px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,223 @@
|
||||
<template>
|
||||
<div class="mm-merge">
|
||||
<el-alert class="merge-tip" type="info" :closable="false" show-icon>
|
||||
<template #title>
|
||||
与桌面端「合并」一致:以<strong>当前使用地图</strong>为底图,把选中的地图依次合并进来(自动分配独立图层、
|
||||
站点 / 路径 ID 自动避让,互不冲突),再另存为目标地图。可用于多楼层汇总、多区域拼接。
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<el-alert
|
||||
v-if="!loadingMaps && !currentName"
|
||||
class="merge-tip"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="当前未设置「使用中」地图。请先到「服务器地图」页对某张地图点「使用」,再来执行合并。"
|
||||
/>
|
||||
|
||||
<div class="merge-body">
|
||||
<el-form label-width="120px" label-position="right" class="merge-form" @submit.prevent>
|
||||
<el-form-item label="底图(当前地图)">
|
||||
<el-tag v-if="currentName" type="success" effect="plain">{{ currentName }}</el-tag>
|
||||
<span v-else class="merge-hint">未设置</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="合并进来的地图" required>
|
||||
<el-select
|
||||
v-model="selected"
|
||||
multiple
|
||||
filterable
|
||||
:loading="loadingMaps"
|
||||
:disabled="!currentName"
|
||||
placeholder="选择 1 张及以上要合并进当前地图的地图"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option v-for="m in sourceOptions" :key="m" :label="m" :value="m" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="另存为" required>
|
||||
<el-input
|
||||
v-model="target"
|
||||
:disabled="!currentName"
|
||||
placeholder="目标地图名称(与当前地图同名则覆盖当前地图)"
|
||||
clearable
|
||||
maxlength="60"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="merging" :disabled="!canMerge" @click="onMerge">开始合并</el-button>
|
||||
<el-button text :disabled="!currentName" @click="reset">重置</el-button>
|
||||
<span class="merge-hint">已选 {{ selected.length }} 张</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div v-if="currentName" class="merge-order">
|
||||
<div class="order-title">叠加顺序</div>
|
||||
<ol class="order-list">
|
||||
<li class="order-item">
|
||||
<el-tag size="small" type="success" effect="plain">底图</el-tag>
|
||||
<span class="order-name">{{ currentName }}</span>
|
||||
</li>
|
||||
<li v-for="(m, i) in selected" :key="m" class="order-item">
|
||||
<el-tag size="small" type="info" effect="plain">叠加 {{ i + 1 }}</el-tag>
|
||||
<span class="order-name">{{ m }}</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { mapsApi } from '@/api/mapEdit'
|
||||
|
||||
const emit = defineEmits<{ (e: 'merged', name: string): void }>()
|
||||
|
||||
const loadingMaps = ref(false)
|
||||
const merging = ref(false)
|
||||
const allMaps = ref<string[]>([])
|
||||
const currentName = ref('')
|
||||
const selected = ref<string[]>([])
|
||||
const target = ref('')
|
||||
|
||||
// 可合并的源地图 = 全部地图去掉「当前地图」自身。
|
||||
const sourceOptions = computed(() => allMaps.value.filter((m) => m !== currentName.value))
|
||||
const canMerge = computed(
|
||||
() => !!currentName.value && selected.value.length >= 1 && target.value.trim().length > 0
|
||||
)
|
||||
|
||||
async function loadMaps() {
|
||||
loadingMaps.value = true
|
||||
try {
|
||||
const r = await mapsApi.list()
|
||||
allMaps.value = r.maps.map((m) => m.name)
|
||||
currentName.value = r.maps.find((m) => m.isCurrent)?.name ?? ''
|
||||
// 当前地图变化时,剔除已不可选的项。
|
||||
selected.value = selected.value.filter((m) => sourceOptions.value.includes(m))
|
||||
} catch (err) {
|
||||
ElMessage.error(`加载地图列表失败:${(err as Error).message}`)
|
||||
} finally {
|
||||
loadingMaps.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
selected.value = []
|
||||
target.value = ''
|
||||
}
|
||||
|
||||
async function doMerge(overwrite: boolean) {
|
||||
const sources = [...selected.value]
|
||||
const name = target.value.trim()
|
||||
const res = await mapsApi.merge(sources, name, overwrite)
|
||||
if (res.ok) {
|
||||
const into = res.data.overwroteCurrent ? '(已覆盖当前地图)' : ''
|
||||
ElMessage.success(
|
||||
`已把 ${res.data.sourceCount} 张地图合并进「${res.data.baseMap}」并另存为「${res.data.name}」${into}` +
|
||||
`(站点 ${res.data.sites} · 路径 ${res.data.tracks})`
|
||||
)
|
||||
reset()
|
||||
await loadMaps()
|
||||
emit('merged', name)
|
||||
return
|
||||
}
|
||||
if (res.conflict) {
|
||||
const tip =
|
||||
name === currentName.value
|
||||
? `目标与当前地图「${name}」同名,将用合并结果覆盖当前地图,是否继续?`
|
||||
: `地图「${name}」已存在,是否替换原地图?`
|
||||
try {
|
||||
await ElMessageBox.confirm(tip, '目标地图已存在', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '替换',
|
||||
cancelButtonText: '取消'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
await doMerge(true)
|
||||
return
|
||||
}
|
||||
ElMessage.error(res.message)
|
||||
}
|
||||
|
||||
async function onMerge() {
|
||||
if (!canMerge.value) return
|
||||
merging.value = true
|
||||
try {
|
||||
await doMerge(false)
|
||||
} finally {
|
||||
merging.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadMaps)
|
||||
|
||||
defineExpose({ refresh: loadMaps })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mm-merge {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
.merge-tip {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.merge-body {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 24px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.merge-form {
|
||||
flex: 1 1 440px;
|
||||
max-width: 580px;
|
||||
}
|
||||
.merge-hint {
|
||||
margin-left: 12px;
|
||||
font-size: 12.5px;
|
||||
color: var(--mg-text-muted);
|
||||
}
|
||||
.merge-order {
|
||||
flex: 0 1 280px;
|
||||
min-width: 220px;
|
||||
padding: 12px 16px;
|
||||
border-radius: var(--mg-radius);
|
||||
background: rgba(var(--mg-accent-rgb), 0.08);
|
||||
border: 1px solid var(--mg-veil-border);
|
||||
}
|
||||
.order-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--mg-text-light);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.order-list {
|
||||
margin: 0;
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.order-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.order-name {
|
||||
color: var(--mg-text-light);
|
||||
font-size: 13px;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,39 @@
|
||||
import { ref } from 'vue'
|
||||
import { useConfigStore } from '@/stores/config'
|
||||
import { DEFAULT_FLEET } from '@/mock/data/configs'
|
||||
import type { FleetGroup, FleetLifecycleConfig } from '@/types/config'
|
||||
|
||||
const groups = ref<FleetGroup[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
/** 共享车队分组配置(运维总览分配 + 筛选联动) */
|
||||
export function useFleetGroups() {
|
||||
const store = useConfigStore()
|
||||
|
||||
async function reload(force = false) {
|
||||
loading.value = true
|
||||
try {
|
||||
const env = await store.load<FleetLifecycleConfig>('fleet', force)
|
||||
const payload = env.payload ?? DEFAULT_FLEET
|
||||
groups.value = JSON.parse(JSON.stringify(payload.groups ?? [])) as FleetGroup[]
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function fleetNameForCarId(carId: string): string | undefined {
|
||||
for (const g of groups.value) {
|
||||
if (g.carIds.includes(carId)) return g.name || g.id
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function regionForCarId(carId: string): string | undefined {
|
||||
for (const g of groups.value) {
|
||||
if (g.carIds.includes(carId)) return g.region || undefined
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
return { groups, loading, reload, fleetNameForCarId, regionForCarId }
|
||||
}
|
||||
@@ -80,6 +80,7 @@
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-if="auth.scope === 'Platform'" command="wizard">配置向导</el-dropdown-item>
|
||||
<el-dropdown-item command="status">服务状态</el-dropdown-item>
|
||||
<el-dropdown-item command="logout" divided>退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
@@ -146,6 +147,7 @@ const ADMIN_MENU: MenuItem[] = [
|
||||
{
|
||||
path: '/admin/design', label: '设计与编排', icon: Tools,
|
||||
children: [
|
||||
{ path: '/admin/maps', label: '地图管理', key: 'admin-maps' },
|
||||
{ path: '/admin/map-editor', label: '地图编辑', key: 'admin-map-editor' },
|
||||
{ path: '/admin/project-properties', label: '项目属性', key: 'admin-project-properties' },
|
||||
{ path: '/admin/tracks', label: '场景管理', key: 'admin-tracks' },
|
||||
@@ -223,6 +225,8 @@ function onUserCommand(cmd: string) {
|
||||
router.push('/login')
|
||||
} else if (cmd === 'status') {
|
||||
router.push('/status')
|
||||
} else if (cmd === 'wizard') {
|
||||
router.push('/wizard')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -25,7 +25,8 @@ export const DEFAULT_SYSTEM: SystemConfig = {
|
||||
export const DEFAULT_INTEGRATIONS: ExternalIntegrations = {
|
||||
mes: [{ id: 'mes-1', name: 'MES 主线', url: 'http://mes.lan/api', enabled: true }],
|
||||
wms: [{ id: 'wms-1', name: 'WMS 仓储', url: 'http://wms.lan/api', enabled: true }],
|
||||
rcs: []
|
||||
rcs: [],
|
||||
ptl: [{ id: 'ptl-1', name: 'PTL 拣选', url: 'http://ptl.lan/api', enabled: true }]
|
||||
}
|
||||
|
||||
export const DEFAULT_ROUTING: RoutingPolicy = {
|
||||
|
||||
@@ -13,6 +13,7 @@ const PAGES: PageDef[] = [
|
||||
{ key: 'admin-dashboard', label: '总览', group: '概览', scope: 'Platform' },
|
||||
{ key: 'admin-map-monitor', label: '地图监控', group: '概览', scope: 'Platform' },
|
||||
{ key: 'admin-playback', label: '调度回放', group: '概览', scope: 'Platform' },
|
||||
{ key: 'admin-maps', label: '地图管理', group: '设计与编排', scope: 'Platform' },
|
||||
{ key: 'admin-map-editor', label: '地图编辑', group: '设计与编排', scope: 'Platform' },
|
||||
{ key: 'admin-project-properties', label: '项目属性', group: '设计与编排', scope: 'Platform' },
|
||||
{ key: 'admin-tracks', label: '场景管理', group: '设计与编排', scope: 'Platform' },
|
||||
|
||||
@@ -15,6 +15,13 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('@/views/ServiceStatusView.vue'),
|
||||
meta: { layout: 'blank', public: true, title: '服务状态' }
|
||||
},
|
||||
{
|
||||
// 部署配置向导:首次部署强制完成平台选型(导航方式 / 模块 / 功能)。需登录,但不属于 admin/monitor scope。
|
||||
path: '/wizard',
|
||||
name: 'wizard',
|
||||
component: () => import('@/views/WizardView.vue'),
|
||||
meta: { layout: 'blank', title: '配置向导' }
|
||||
},
|
||||
{
|
||||
path: '/admin',
|
||||
component: () => import('@/layouts/AppShell.vue'),
|
||||
@@ -23,6 +30,7 @@ const routes: RouteRecordRaw[] = [
|
||||
children: [
|
||||
{ path: 'dashboard', name: 'admin-dashboard', component: () => import('@/views/admin/DashboardView.vue'), meta: { title: '总览' } },
|
||||
{ path: 'map-monitor', name: 'admin-map-monitor', component: () => import('@/views/admin/MapMonitorView.vue'), meta: { title: '地图监控' } },
|
||||
{ path: 'maps', name: 'admin-maps', component: () => import('@/views/admin/MapManagementView.vue'), meta: { title: '地图管理' } },
|
||||
{ path: 'map-editor', name: 'admin-map-editor', component: () => import('@/views/admin/MapEditorView.vue'), meta: { title: '地图编辑' } },
|
||||
{ path: 'tracks', name: 'admin-tracks', component: () => import('@/views/admin/TrackTableView.vue'), meta: { title: '场景管理' } },
|
||||
{ path: 'cars', name: 'admin-cars', component: () => import('@/views/admin/CarPanelView.vue'), meta: { title: '车辆管理' } },
|
||||
@@ -109,6 +117,12 @@ router.beforeEach(async (to) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 部署配置向导:首次部署(deployment.Configured=false)时,强制先完成平台选型再进入业务页。
|
||||
// 已在 /wizard 则放行,避免自跳死循环;保存成功后 store.markWizardDone() 解除拦截。
|
||||
if (auth.needsWizard && to.name !== 'wizard') {
|
||||
return { name: 'wizard' }
|
||||
}
|
||||
|
||||
// 会话 45 AR-6:switchScope 改为后端发起 ——
|
||||
// 必须 await 完成后再放行,否则页面用旧 scope 的 perms 渲染一帧后才被纠正。
|
||||
// 失败(如 ops 账号尝试切 Platform 被 403)则维持原 scope,路由仍放行让用户看到 readonly UI。
|
||||
|
||||
@@ -19,6 +19,8 @@ interface AuthState {
|
||||
* 在用户离开期间重启(JWT secret 重生)的场景下能立刻被发现并跳登录。
|
||||
*/
|
||||
validated: boolean
|
||||
/** 部署配置向导是否待完成(来自登录/me 响应;仅内存态,刷新后由 validate 重新拉取)。 */
|
||||
needsWizard: boolean
|
||||
}
|
||||
|
||||
const TOKEN_KEY = 'simple.auth.token'
|
||||
@@ -49,7 +51,9 @@ function loadState(): AuthState {
|
||||
runMode: safeReadString(RUN_MODE_KEY) as RunMode | null,
|
||||
effectivePermissions: safeJsonParse<EffectivePermissions>(PERM_KEY),
|
||||
// 刷新页面后默认未校验:路由守卫会在受保护路由首次进入前 await validate()。
|
||||
validated: false
|
||||
validated: false,
|
||||
// 部署向导状态不持久化:刷新后默认 false,validate() 用后端最新值回填,避免误拦。
|
||||
needsWizard: false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +65,7 @@ function clearLocalAuth(target: AuthState) {
|
||||
target.runMode = null
|
||||
target.effectivePermissions = null
|
||||
target.validated = false
|
||||
target.needsWizard = false
|
||||
try {
|
||||
localStorage.removeItem(TOKEN_KEY)
|
||||
localStorage.removeItem(USER_KEY)
|
||||
@@ -105,6 +110,7 @@ export const useAuthStore = defineStore('auth', {
|
||||
this.scope = resp.scope
|
||||
this.runMode = resp.runMode
|
||||
this.effectivePermissions = resp.effectivePermissions
|
||||
this.needsWizard = resp.needsWizard ?? false
|
||||
// 登录响应本身就是后端的身份背书,等同于一次成功的 /me;省一次往返。
|
||||
this.validated = true
|
||||
localStorage.setItem(TOKEN_KEY, resp.token)
|
||||
@@ -136,6 +142,7 @@ export const useAuthStore = defineStore('auth', {
|
||||
this.scope = me.scope
|
||||
this.runMode = me.runMode
|
||||
this.effectivePermissions = me.effectivePermissions
|
||||
this.needsWizard = me.needsWizard ?? false
|
||||
this.validated = true
|
||||
localStorage.setItem(USER_KEY, JSON.stringify(me.user))
|
||||
localStorage.setItem(SCOPE_KEY, me.scope)
|
||||
@@ -161,6 +168,7 @@ export const useAuthStore = defineStore('auth', {
|
||||
this.scope = resp.scope
|
||||
this.runMode = resp.runMode
|
||||
this.effectivePermissions = resp.effectivePermissions
|
||||
this.needsWizard = resp.needsWizard ?? false
|
||||
// SwitchScope 后端重发了 token + perm,等同于一次成功的 /me,保持 validated 为 true。
|
||||
this.validated = true
|
||||
localStorage.setItem(TOKEN_KEY, resp.token)
|
||||
@@ -169,6 +177,10 @@ export const useAuthStore = defineStore('auth', {
|
||||
localStorage.setItem(RUN_MODE_KEY, resp.runMode)
|
||||
localStorage.setItem(PERM_KEY, JSON.stringify(resp.effectivePermissions))
|
||||
return resp
|
||||
},
|
||||
/** 向导保存成功后调用:清掉 needsWizard,避免守卫再次把用户导回 /wizard。 */
|
||||
markWizardDone() {
|
||||
this.needsWizard = false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -35,6 +35,24 @@
|
||||
--mg-font-sans: 'PingFang SC', 'Microsoft YaHei', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
--mg-font-mono: 'JetBrains Mono', 'Cascadia Mono', 'SF Mono', Menlo, Consolas, monospace;
|
||||
|
||||
/* ── 登录页 / 配置向导 固定品牌色(紫蓝混合,永不随主题切换变化) ──
|
||||
* 登录窗与「平台配置向导」共用同一套定值,保证两处观感统一、且不受用户切换主题影响。
|
||||
* 色相:靛紫 #7c5cff ↔ 蓝紫(periwinkle) #5e7cff 的「紫蓝」渐变,刻意避开纯蓝 / 青色。 */
|
||||
--lg-primary: #7d4dff;
|
||||
--lg-primary-rgb: 125, 77, 255;
|
||||
--lg-primary-hover: #9c79ff;
|
||||
--lg-primary-hover-rgb: 156, 121, 255;
|
||||
--lg-primary-deep: #5a2fc4;
|
||||
/* accent 与登录按钮同款紫(同色系、略亮),让强调色/光晕/激活态都呈现按钮那种紫 */
|
||||
--lg-accent: #8b5cff;
|
||||
--lg-accent-rgb: 139, 92, 255;
|
||||
--lg-deep-rgb: 14, 9, 28;
|
||||
--lg-aside-rgb: 30, 20, 58;
|
||||
--lg-card-rgb: 36, 23, 68;
|
||||
--lg-text: #eef0ff;
|
||||
--lg-text-soft: rgba(220, 220, 250, 0.82);
|
||||
--lg-text-dim: rgba(196, 198, 235, 0.58);
|
||||
|
||||
/* ── 品牌主色(默认 = 星云紫,可被 themes.ts 注入覆盖) ── */
|
||||
--mg-primary: #7c3aed;
|
||||
--mg-primary-rgb: 124, 58, 237;
|
||||
|
||||
@@ -73,6 +73,12 @@ export interface LoginResponse {
|
||||
* (比如「检测到 SimpleLite 已经在端口上运行,本次选择的启动模式未生效」)。
|
||||
*/
|
||||
launchWarning?: string
|
||||
/**
|
||||
* 部署配置向导是否待完成(后端 deployment.Configured=false)。
|
||||
* true 时路由守卫会把用户导向 /wizard 完成平台选型(导航方式 / 模块 / 功能)。
|
||||
* 字段缺失(旧后端 / mock)按 false 处理。
|
||||
*/
|
||||
needsWizard?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,4 +90,6 @@ export interface MeResponse {
|
||||
scope: Scope
|
||||
runMode: RunMode
|
||||
effectivePermissions: EffectivePermissions
|
||||
/** 部署配置向导是否待完成(与 LoginResponse.needsWizard 同义)。 */
|
||||
needsWizard?: boolean
|
||||
}
|
||||
|
||||
@@ -23,11 +23,13 @@ export interface SystemConfig {
|
||||
export interface MesEndpoint { id: string; name: string; url: string; enabled: boolean }
|
||||
export interface WmsEndpoint { id: string; name: string; url: string; enabled: boolean }
|
||||
export interface RcsEndpoint { id: string; name: string; url: string; enabled: boolean }
|
||||
export interface PtlEndpoint { id: string; name: string; url: string; enabled: boolean }
|
||||
|
||||
export interface ExternalIntegrations {
|
||||
mes: MesEndpoint[]
|
||||
wms: WmsEndpoint[]
|
||||
rcs: RcsEndpoint[]
|
||||
ptl: PtlEndpoint[]
|
||||
}
|
||||
|
||||
export interface AvoidanceRule { id: string; zoneId: string; rule: string }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user