Sfoglia il codice sorgente

fix(aidop): demo 导入自动补齐 LayoutItem/HomeModule + 小数值精度 3 位

- 后端 AdoSmartOpsKpiMasterController.ImportDemoData 结尾追加 LayoutItem / HomeModule
  "缺啥补啥"的幂等同步,解决新增 KPI 但布局行缺失导致九宫格少卡片(S9 仅 4/5 项)
- 前端 HomeModuleKpiCard.fmtValue/fmtTargetDisplay 对 |n|<1 的小数切到 3 位精度,
  避免 0.018 被 round 成 0.02
- chore: bump version 2.4.100 / 1.0.67

Made-with: Cursor
skygu 1 mese fa
parent
commit
7c1502a027

+ 1 - 1
Web/package.json

@@ -1,7 +1,7 @@
 {
 	"name": "admin.net",
 	"type": "module",
-	"version": "2.4.99",
+	"version": "2.4.100",
 	"packageManager": "pnpm@10.32.1",
 	"lastBuildTime": "2026.03.15",
 	"description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",

+ 4 - 0
Web/src/views/dashboard/components/HomeModuleKpiCard.vue

@@ -46,6 +46,8 @@ function fmtValue(v, unit) {
   if (unit === '%') return `${Math.round(n * 10) / 10}`
   if (unit === '天') return `${Math.round(n * 10) / 10}`
   if (Math.abs(n - Math.round(n)) < 1e-9) return `${Math.round(n)}`
+  // 小数值(|n|<1)保留 3 位,避免 0.018 被 round 成 0.02 失真
+  if (Math.abs(n) < 1) return `${Math.round(n * 1000) / 1000}`
   return `${Math.round(n * 100) / 100}`
 }
 
@@ -60,6 +62,8 @@ function fmtTargetDisplay(v, unit) {
       ? `${Math.round(n * 10) / 10}`
       : Math.abs(n - Math.round(n)) < 1e-9
       ? `${Math.round(n)}`
+      : Math.abs(n) < 1
+      ? `${Math.round(n * 1000) / 1000}`
       : `${Math.round(n * 100) / 100}`
   if (unit === '%') return `${num}%`
   if (unit === '天') return `${num} 天`

+ 3 - 3
server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj

@@ -11,9 +11,9 @@
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <Copyright>Admin.NET</Copyright>
     <Description>Admin.NET 通用权限开发平台</Description>
-    <AssemblyVersion>1.0.66</AssemblyVersion>
-    <FileVersion>1.0.66</FileVersion>
-    <Version>1.0.66</Version>
+    <AssemblyVersion>1.0.67</AssemblyVersion>
+    <FileVersion>1.0.67</FileVersion>
+    <Version>1.0.67</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 84 - 2
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/AdoSmartOpsKpiMasterController.cs

@@ -448,12 +448,94 @@ public class AdoSmartOpsKpiMasterController : ControllerBase
         await InsertLevel(l2, "ado_s9_kpi_value_l2_day");
         await InsertLevel(l3, "ado_s9_kpi_value_l3_day");
 
+        // ---- 同步 LayoutItem / HomeModule(只补不改,保护已有布局配置) ----
+        // 背景:
+        //   seed_layout_items_all_modules.py 的语义是"(tenant,module) 已有布局就 skip";
+        //   之后在 KpiMaster 里新增某个 L1/L2 指标时,LayoutItem 不会自动跟进,九宫格 / 详情会少条目。
+        //   这里按本次 Demo 导入涉及的指标做"缺啥补啥"的幂等同步,不覆盖任何已有行。
+        const long layoutFactoryId = 1;
+        var existingLayout = await _db.Queryable<AdoSmartOpsLayoutItem>()
+            .Where(x => x.TenantId == tenantId && x.FactoryId == layoutFactoryId)
+            .ToListAsync();
+        var layoutKey = new HashSet<string>(
+            existingLayout.Select(x => $"{x.ModuleCode}|{x.MetricCode}|{x.MetricLevel}"),
+            StringComparer.OrdinalIgnoreCase);
+
+        var now = DateTime.Now;
+        var toInsertLayout = new List<AdoSmartOpsLayoutItem>();
+        foreach (var p in parsed)
+        {
+            var m = p.Master;
+            var key = $"{m.ModuleCode}|{m.MetricCode}|{m.MetricLevel}";
+            if (layoutKey.Contains(key)) continue;
+            string? zone = null;
+            if (m.MetricLevel >= 2)
+            {
+                var nm = m.MetricName ?? "";
+                if (nm.Contains("周期") || nm.Contains("人效")) zone = "left";
+                else if (nm.Contains("满足率") || nm.Contains("周转")) zone = "right";
+                else zone = (m.SortNo % 2 == 1) ? "left" : "right";
+            }
+            toInsertLayout.Add(new AdoSmartOpsLayoutItem
+            {
+                TenantId = tenantId,
+                FactoryId = layoutFactoryId,
+                ModuleCode = m.ModuleCode,
+                RowId = $"{m.ModuleCode}-L{m.MetricLevel}-{m.MetricCode}",
+                MetricLevel = m.MetricLevel,
+                MetricCode = m.MetricCode,
+                DisplayName = null,
+                SortNo = m.SortNo,
+                ParentRowId = null,
+                FormulaText = null,
+                PanelZone = zone,
+                IsEnabled = 1,
+                UpdateTime = now,
+            });
+            layoutKey.Add(key);
+        }
+        if (toInsertLayout.Count > 0)
+            await _db.Insertable(toInsertLayout).ExecuteCommandAsync();
+
+        var moduleCodes = parsed.Select(p => p.Master.ModuleCode).Distinct().ToList();
+        var existingHm = (await _db.Queryable<AdoSmartOpsHomeModule>()
+            .Where(x => x.TenantId == tenantId && x.FactoryId == layoutFactoryId)
+            .ToListAsync())
+            .Select(x => x.ModuleCode)
+            .ToHashSet(StringComparer.OrdinalIgnoreCase);
+        var toInsertHm = new List<AdoSmartOpsHomeModule>();
+        foreach (var mc in moduleCodes)
+        {
+            if (string.IsNullOrWhiteSpace(mc) || existingHm.Contains(mc)) continue;
+            toInsertHm.Add(new AdoSmartOpsHomeModule
+            {
+                TenantId = tenantId,
+                FactoryId = layoutFactoryId,
+                ModuleCode = mc,
+                LayoutPattern = "card_grid",
+                UpdateTime = now,
+            });
+            existingHm.Add(mc);
+        }
+        if (toInsertHm.Count > 0)
+            await _db.Insertable(toInsertHm).ExecuteCommandAsync();
+
         var fromExcel = excelOverrides.Values.Count(v => v.HasTarget || v.HasActual);
         return Ok(new
         {
             ok = true,
-            message = $"导入成功:Excel 填入 {fromExcel} 条,自动补全 {parsed.Count - fromExcel} 条,共 {parsed.Count} 条指标 × {DAYS} 天",
-            detail = new { l1 = l1.Count, l2 = l2.Count, l3 = l3.Count, days = DAYS, fromExcel, autoGen = parsed.Count - fromExcel }
+            message = $"导入成功:Excel 填入 {fromExcel} 条,自动补全 {parsed.Count - fromExcel} 条,共 {parsed.Count} 条指标 × {DAYS} 天;补齐布局行 {toInsertLayout.Count} 条、模块模板 {toInsertHm.Count} 条",
+            detail = new
+            {
+                l1 = l1.Count,
+                l2 = l2.Count,
+                l3 = l3.Count,
+                days = DAYS,
+                fromExcel,
+                autoGen = parsed.Count - fromExcel,
+                layoutInserted = toInsertLayout.Count,
+                homeModuleInserted = toInsertHm.Count
+            }
         });
     }