Просмотр исходного кода

fix(s8): dashboard date filter takes effect + pending bucket aligned with list

Two latent inconsistencies between the S8 monitoring dashboard and the
exception list:

1. The "开始/结束日期" filter on the dashboard was decorative only —
   neither the page nor the controller passed beginTime/endTime through
   to GetOverviewAsync. Now the controller accepts both, the service
   applies WhereIF(CreatedAt >= begin / <= end), and the Vue page
   serializes the panel filter as local-time strings (same trick as the
   list page — toISOString shifts windows by 8h on +08 servers).

2. The "待处理" KPI counted NEW + ASSIGNED + IN_PROGRESS but the list's
   pending bucket also includes PENDING_VERIFICATION. Whenever a
   reviewer-pending exception existed the dashboard would silently
   under-count by one. Both sides now use the same four-status bucket.

loadDetail() (the panel detail list) shared the same toISOString bug;
fixed by routing through the same toLocalDateTimeString helper.
YY968XX 3 месяцев назад
Родитель
Сommit
4ae6e29437

+ 1 - 1
Web/src/views/aidop/s8/api/s8DashboardApi.ts

@@ -88,7 +88,7 @@ export interface S8CellDataQuery {
 }
 
 export const s8DashboardApi = {
-	overview: (params?: { tenantId?: number; factoryId?: number }) =>
+	overview: (params?: { tenantId?: number; factoryId?: number; beginTime?: string; endTime?: string }) =>
 		service.get<S8OverviewData>('/api/aidop/s8/dashboard/overview', { params }).then(unwrap),
 
 	trends: (params?: { tenantId?: number; factoryId?: number; days?: number }) =>

+ 17 - 3
Web/src/views/aidop/s8/dashboard/S8DashboardPage.vue

@@ -326,8 +326,20 @@ const visibleKpiCards = computed(() => {
 });
 
 // ─── 数据加载 ─────────────────────────────────────────────────
+// 把 Date 序列化为本地时间字符串 "YYYY-MM-DD HH:mm:ss"(避免 toISOString 因 +08 偏移导致后端窗口错位)。
+function toLocalDateTimeString(d: Date | null | undefined, endOfDay = false): string | undefined {
+	if (!d) return undefined;
+	const pad = (n: number) => String(n).padStart(2, '0');
+	const ymd = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
+	return endOfDay ? `${ymd} 23:59:59` : `${ymd} 00:00:00`;
+}
+
 async function loadOverview() {
-	const data = await s8DashboardApi.overview();
+	const filter = currentPanelFilter.value;
+	const data = await s8DashboardApi.overview({
+		beginTime: toLocalDateTimeString(filter.dateStart, false),
+		endTime: toLocalDateTimeString(filter.dateEnd, true),
+	});
 	Object.assign(overview, data);
 }
 
@@ -354,8 +366,10 @@ async function loadDetail() {
 	};
 	const filter = currentPanelFilter.value;
 	if (filter.severity) params.severity = filter.severity;
-	if (filter.dateStart) params.beginTime = filter.dateStart.toISOString();
-	if (filter.dateEnd) params.endTime = filter.dateEnd.toISOString();
+	const beginStr = toLocalDateTimeString(filter.dateStart, false);
+	const endStr = toLocalDateTimeString(filter.dateEnd, true);
+	if (beginStr) params.beginTime = beginStr;
+	if (endStr) params.endTime = endStr;
 	if (filter.extra) {
 		if (activePanel.value === 'process') params.processNodeCode = filter.extra;
 		if (activePanel.value === 'occurrence') params.deptId = filter.extra;

+ 6 - 2
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S8/AdoS8DashboardController.cs

@@ -23,8 +23,12 @@ public class AdoS8DashboardController : ControllerBase
     }
 
     [HttpGet("overview")]
-    public async Task<IActionResult> OverviewAsync([FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1) =>
-        Ok(await _svc.GetOverviewAsync(tenantId, factoryId));
+    public async Task<IActionResult> OverviewAsync(
+        [FromQuery] long tenantId = 1,
+        [FromQuery] long factoryId = 1,
+        [FromQuery] DateTime? beginTime = null,
+        [FromQuery] DateTime? endTime = null) =>
+        Ok(await _svc.GetOverviewAsync(tenantId, factoryId, beginTime, endTime));
 
     [HttpGet("trends")]
     public async Task<IActionResult> TrendsAsync([FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1,

+ 8 - 3
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/S8DashboardService.cs

@@ -20,11 +20,16 @@ public class S8DashboardService : ITransient
         _processNodeRep = processNodeRep;
     }
 
-    public async Task<object> GetOverviewAsync(long tenantId, long factoryId)
+    public async Task<object> GetOverviewAsync(long tenantId, long factoryId, DateTime? beginTime = null, DateTime? endTime = null)
     {
-        var q = _rep.AsQueryable().Where(x => x.TenantId == tenantId && x.FactoryId == factoryId && !x.IsDeleted);
+        // 看板顶部的"开始/结束日期"通过 beginTime/endTime 透传,与列表的 beginTime/endTime 同语义。
+        // pending 桶必须与列表 statusBucket="pending" 严格对齐:NEW/ASSIGNED/IN_PROGRESS/PENDING_VERIFICATION。
+        var q = _rep.AsQueryable()
+            .Where(x => x.TenantId == tenantId && x.FactoryId == factoryId && !x.IsDeleted)
+            .WhereIF(beginTime.HasValue, x => x.CreatedAt >= beginTime!.Value)
+            .WhereIF(endTime.HasValue, x => x.CreatedAt <= endTime!.Value);
         var total      = await q.CountAsync();
-        var pending    = await q.CountAsync(x => x.Status == "NEW" || x.Status == "ASSIGNED" || x.Status == "IN_PROGRESS");
+        var pending    = await q.CountAsync(x => x.Status == "NEW" || x.Status == "ASSIGNED" || x.Status == "IN_PROGRESS" || x.Status == "PENDING_VERIFICATION");
         var inProgress = await q.CountAsync(x => x.Status == "IN_PROGRESS");
         var timeout    = await q.CountAsync(x => x.TimeoutFlag);
         var closed     = await q.CountAsync(x => x.Status == "CLOSED");