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

fix(s8): complete department serious card metrics layout

YY968XX 3 недель назад
Родитель
Сommit
c6d7bb670e

+ 1 - 1
Web/package.json

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

+ 153 - 23
Web/src/views/aidop/s8/monitoring/components/S8DeptSeverityTabsCard.vue

@@ -7,21 +7,60 @@
 			</div>
 			<span class="s8-dept-severity__subtitle">按责任部门聚合</span>
 		</div>
+		<!-- TASK-013-DEPT-CARD-UI-COMPLETE-1:顶部汇总条 -->
+		<div class="s8-dept-severity__summary">
+			<div class="s8-dept-severity__summary-item">
+				<span class="s8-dept-severity__summary-label">严重异常总数</span>
+				<span class="s8-dept-severity__summary-value s8-dept-severity__summary-value--hot">{{ totalSerious }}</span>
+			</div>
+			<div class="s8-dept-severity__summary-item">
+				<span class="s8-dept-severity__summary-label">涉及部门</span>
+				<span class="s8-dept-severity__summary-value">{{ involvedDepts }}</span>
+			</div>
+			<div class="s8-dept-severity__summary-item">
+				<span class="s8-dept-severity__summary-label">平均处理时间</span>
+				<span class="s8-dept-severity__summary-value s8-dept-severity__summary-value--placeholder">{{ DISPLAY_PLACEHOLDER }}</span>
+			</div>
+			<div class="s8-dept-severity__summary-item">
+				<span class="s8-dept-severity__summary-label">关闭率</span>
+				<span class="s8-dept-severity__summary-value s8-dept-severity__summary-value--placeholder">{{ DISPLAY_PLACEHOLDER }}</span>
+			</div>
+		</div>
 		<el-tabs v-model="activeTab" class="s8-dept-severity__tabs">
 			<el-tab-pane label="严重" name="serious">
 				<div v-if="loading" class="s8-dept-severity__empty">加载中…</div>
 				<div v-else-if="!sortedItems.length" class="s8-dept-severity__empty">暂无严重异常</div>
-				<ul v-else class="s8-dept-severity__list">
-					<li v-for="row in sortedItems" :key="row.deptId" class="s8-dept-severity__row">
-						<span class="s8-dept-severity__name" :title="row.deptName">{{ row.deptName }}</span>
-						<span
-							class="s8-dept-severity__count"
-							:class="row.seriousCount > 0 ? 's8-dept-severity__count--hot' : 's8-dept-severity__count--zero'"
+				<div v-else class="s8-dept-severity__table" role="table">
+					<div class="s8-dept-severity__thead" role="row">
+						<span class="s8-dept-severity__th s8-dept-severity__th--name" role="columnheader">部门</span>
+						<span class="s8-dept-severity__th s8-dept-severity__th--count" role="columnheader">严重异常数</span>
+						<span class="s8-dept-severity__th s8-dept-severity__th--metric" role="columnheader">平均处理时间</span>
+						<span class="s8-dept-severity__th s8-dept-severity__th--metric" role="columnheader">关闭率</span>
+					</div>
+					<div class="s8-dept-severity__tbody">
+						<div
+							v-for="row in sortedItems"
+							:key="row.deptId"
+							class="s8-dept-severity__row"
+							role="row"
 						>
-							{{ row.seriousCount }}
-						</span>
-					</li>
-				</ul>
+							<span class="s8-dept-severity__cell s8-dept-severity__cell--name" :title="row.deptName" role="cell">{{ row.deptName }}</span>
+							<span
+								class="s8-dept-severity__cell s8-dept-severity__cell--count"
+								:class="row.seriousCount > 0 ? 's8-dept-severity__cell--hot' : 's8-dept-severity__cell--zero'"
+								role="cell"
+							>
+								{{ row.seriousCount }}
+							</span>
+							<span class="s8-dept-severity__cell s8-dept-severity__cell--metric s8-dept-severity__cell--placeholder" role="cell">
+								{{ DISPLAY_PLACEHOLDER }}
+							</span>
+							<span class="s8-dept-severity__cell s8-dept-severity__cell--metric s8-dept-severity__cell--placeholder" role="cell">
+								{{ DISPLAY_PLACEHOLDER }}
+							</span>
+						</div>
+					</div>
+				</div>
 			</el-tab-pane>
 		</el-tabs>
 	</div>
@@ -39,16 +78,26 @@ const props = defineProps<{
 
 const activeTab = shallowRef<'serious'>('serious');
 
+// TASK-013-DEPT-CARD-UI-COMPLETE-1:平均处理时间 / 关闭率后端未返回,统一占位 "--",
+// 不伪造 0 或 fake 计算;后续 P2 任务 TASK-013-P2-AVG-CLOSE-FOLLOW-1 接通真实计算后替换。
+const DISPLAY_PLACEHOLDER = '--';
+
 // TASK-013-DEPT-SERIOUS-CARD-1:seriousCount desc, total desc 兜底;
 // 全员 seriousCount=0 视为空态(模板里 empty 判定)。
 const sortedItems = computed(() => {
-	const filtered = (props.items ?? []).filter((item) => item.seriousCount > 0);
+	const filtered = (props.items ?? []).filter((item) => item && item.seriousCount > 0);
 	return [...filtered].sort((a, b) => {
 		if (b.seriousCount !== a.seriousCount) return b.seriousCount - a.seriousCount;
 		return b.total - a.total;
 	});
 });
 
+const totalSerious = computed(() =>
+	(props.items ?? []).reduce((acc, item) => acc + (item?.seriousCount ?? 0), 0),
+);
+
+const involvedDepts = computed(() => sortedItems.value.length);
+
 const minHeightStyle = computed<CSSProperties>(() => {
 	if (!props.minHeight || props.minHeight <= 0) return {};
 	return { minHeight: `${props.minHeight}px` };
@@ -102,6 +151,46 @@ const minHeightStyle = computed<CSSProperties>(() => {
 	color: #64748b;
 }
 
+.s8-dept-severity__summary {
+	display: grid;
+	grid-template-columns: repeat(4, minmax(0, 1fr));
+	gap: 6px;
+	padding: 6px 4px;
+	border-radius: 6px;
+	background: rgba(239, 68, 68, 0.04);
+	border: 1px solid rgba(239, 68, 68, 0.12);
+}
+
+.s8-dept-severity__summary-item {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	min-width: 0;
+	line-height: 1.2;
+}
+
+.s8-dept-severity__summary-label {
+	font-size: 10px;
+	color: #64748b;
+	white-space: nowrap;
+}
+
+.s8-dept-severity__summary-value {
+	font-family: 'Roboto Mono', monospace;
+	font-size: 14px;
+	font-weight: 700;
+	color: #e2e8f0;
+}
+
+.s8-dept-severity__summary-value--hot {
+	color: #ef4444;
+}
+
+.s8-dept-severity__summary-value--placeholder {
+	color: #475569;
+	font-weight: 600;
+}
+
 .s8-dept-severity__tabs {
 	flex: 1;
 	min-height: 0;
@@ -139,49 +228,90 @@ const minHeightStyle = computed<CSSProperties>(() => {
 	overflow: auto;
 }
 
-.s8-dept-severity__list {
-	margin: 0;
-	padding: 0;
-	list-style: none;
+.s8-dept-severity__table {
 	display: flex;
 	flex-direction: column;
 	gap: 4px;
 }
 
-.s8-dept-severity__row {
+.s8-dept-severity__thead {
+	display: grid;
+	grid-template-columns: minmax(0, 1.4fr) minmax(0, 0.9fr) minmax(0, 1fr) minmax(0, 0.9fr);
+	gap: 6px;
+	padding: 4px 6px;
+	font-size: 11px;
+	color: #94a3b8;
+	border-bottom: 1px solid rgba(255, 255, 255, 0.08);
+}
+
+.s8-dept-severity__th {
+	white-space: nowrap;
+}
+
+.s8-dept-severity__th--name {
+	text-align: left;
+}
+
+.s8-dept-severity__th--count,
+.s8-dept-severity__th--metric {
+	text-align: right;
+}
+
+.s8-dept-severity__tbody {
 	display: flex;
+	flex-direction: column;
+	gap: 3px;
+}
+
+.s8-dept-severity__row {
+	display: grid;
+	grid-template-columns: minmax(0, 1.4fr) minmax(0, 0.9fr) minmax(0, 1fr) minmax(0, 0.9fr);
+	gap: 6px;
 	align-items: center;
-	gap: 8px;
 	font-size: 12px;
 	padding: 4px 6px;
 	border-radius: 6px;
 	background: rgba(255, 255, 255, 0.02);
 }
 
-.s8-dept-severity__name {
-	flex: 1;
+.s8-dept-severity__cell {
+	min-width: 0;
+}
+
+.s8-dept-severity__cell--name {
 	color: #cbd5e1;
 	white-space: nowrap;
 	overflow: hidden;
 	text-overflow: ellipsis;
 }
 
-.s8-dept-severity__count {
-	min-width: 32px;
+.s8-dept-severity__cell--count,
+.s8-dept-severity__cell--metric {
 	text-align: right;
 	font-family: 'Roboto Mono', monospace;
+}
+
+.s8-dept-severity__cell--count {
 	font-size: 14px;
 	font-weight: 700;
 }
 
-.s8-dept-severity__count--hot {
+.s8-dept-severity__cell--metric {
+	font-size: 12px;
+}
+
+.s8-dept-severity__cell--hot {
 	color: #ef4444;
 }
 
-.s8-dept-severity__count--zero {
+.s8-dept-severity__cell--zero {
 	color: #94a3b8;
 }
 
+.s8-dept-severity__cell--placeholder {
+	color: #475569;
+}
+
 .s8-dept-severity__empty {
 	color: #64748b;
 	font-size: 12px;