|
|
@@ -8,19 +8,19 @@
|
|
|
</div>
|
|
|
<el-tabs v-model="activeTab" class="s8-dept-severity__tabs">
|
|
|
<el-tab-pane label="严重" name="serious">
|
|
|
- <DeptTable
|
|
|
+ <DeptList
|
|
|
:loading="loading"
|
|
|
:rows="seriousRows"
|
|
|
- count-label="严重异常数"
|
|
|
+ count-label="严重异常"
|
|
|
empty-text="暂无严重异常"
|
|
|
:count-key="'seriousCount'"
|
|
|
/>
|
|
|
</el-tab-pane>
|
|
|
<el-tab-pane label="关注" name="follow">
|
|
|
- <DeptTable
|
|
|
+ <DeptList
|
|
|
:loading="loading"
|
|
|
:rows="followRows"
|
|
|
- count-label="关注异常数"
|
|
|
+ count-label="关注异常"
|
|
|
empty-text="暂无关注异常"
|
|
|
:count-key="'followCount'"
|
|
|
/>
|
|
|
@@ -42,7 +42,7 @@ const props = defineProps<{
|
|
|
type Tab = 'serious' | 'follow';
|
|
|
const activeTab = shallowRef<Tab>('serious');
|
|
|
|
|
|
-// TASK-013-P2-AVG-CLOSE-FOLLOW-1:严重 / 关注两个视角,分别按 seriousCount / followCount > 0 过滤并降序。
|
|
|
+// S8-DEPT-CARD-LIST-LAYOUT-FIX-1:严重 / 关注两个视角,分别按 seriousCount / followCount > 0 过滤并降序。
|
|
|
const seriousRows = computed(() =>
|
|
|
[...(props.items ?? [])]
|
|
|
.filter((x) => x && x.seriousCount > 0)
|
|
|
@@ -59,8 +59,8 @@ const minHeightStyle = computed<CSSProperties>(() => {
|
|
|
return { minHeight: `${props.minHeight}px` };
|
|
|
});
|
|
|
|
|
|
-// TASK-013-P2-AVG-CLOSE-FOLLOW-1:avgProcessHours 无样本(null/undefined/<=0)→ '--';
|
|
|
-// closeRate 缺字段 → '--',0 值显示 '0%'(数据真实闭环为 0,不当作空态)。
|
|
|
+// S8-DEPT-CARD-LIST-LAYOUT-FIX-1:avgProcessHours 无样本(null/undefined/<=0)→ '--';
|
|
|
+// closeRate 缺字段 → '--',0 显示 '0%'(数据真实闭环为 0,不当作空态)。
|
|
|
function formatHoursOrDash(v: number | null | undefined): string {
|
|
|
if (v === null || v === undefined || !Number.isFinite(v) || v <= 0) return '--';
|
|
|
return v >= 24 ? `${Math.round(v)}h+` : `${v.toFixed(1)}h`;
|
|
|
@@ -71,7 +71,11 @@ function formatPercentOrDash(v: number | null | undefined): string {
|
|
|
return `${clamped.toFixed(clamped % 1 === 0 ? 0 : 1)}%`;
|
|
|
}
|
|
|
|
|
|
-const DeptTable = (rawProps: {
|
|
|
+// S8-DEPT-CARD-LIST-LAYOUT-FIX-1:窄屏列表方案 —— 每部门一行 card,2 行结构:
|
|
|
+// 第一行:部门名 + 异常数 (右对齐徽标)
|
|
|
+// 第二行:均时 X · 关闭率 Y (副信息行)
|
|
|
+// 与之前 4 列 grid 相比,列宽不再受卡片总宽限制,文字不会挤压。
|
|
|
+const DeptList = (rawProps: {
|
|
|
loading?: boolean;
|
|
|
rows: S8DeptBacklogItem[];
|
|
|
countLabel: string;
|
|
|
@@ -80,48 +84,49 @@ const DeptTable = (rawProps: {
|
|
|
}) => {
|
|
|
if (rawProps.loading) return h('div', { class: 's8-dept-severity__empty' }, '加载中…');
|
|
|
if (!rawProps.rows.length) return h('div', { class: 's8-dept-severity__empty' }, rawProps.emptyText);
|
|
|
- return h('div', { class: 's8-dept-severity__table', role: 'table' }, [
|
|
|
- h('div', { class: 's8-dept-severity__thead', role: 'row' }, [
|
|
|
- h('span', { class: 's8-dept-severity__th s8-dept-severity__th--name', role: 'columnheader' }, '部门'),
|
|
|
- h('span', { class: 's8-dept-severity__th s8-dept-severity__th--count', role: 'columnheader' }, rawProps.countLabel),
|
|
|
- h('span', { class: 's8-dept-severity__th s8-dept-severity__th--metric', role: 'columnheader' }, '平均处理时间'),
|
|
|
- h('span', { class: 's8-dept-severity__th s8-dept-severity__th--metric', role: 'columnheader' }, '关闭率'),
|
|
|
- ]),
|
|
|
- h(
|
|
|
- 'div',
|
|
|
- { class: 's8-dept-severity__tbody' },
|
|
|
- rawProps.rows.map((row) => {
|
|
|
- const count = row[rawProps.countKey];
|
|
|
- const hot = count > 0;
|
|
|
- return h(
|
|
|
- 'div',
|
|
|
- {
|
|
|
- key: row.deptId,
|
|
|
- class: 's8-dept-severity__row',
|
|
|
- role: 'row',
|
|
|
- },
|
|
|
- [
|
|
|
- h('span', { class: 's8-dept-severity__cell s8-dept-severity__cell--name', title: row.deptName, role: 'cell' }, row.deptName),
|
|
|
- h(
|
|
|
- 'span',
|
|
|
- {
|
|
|
- class: [
|
|
|
- 's8-dept-severity__cell s8-dept-severity__cell--count',
|
|
|
- hot ? 's8-dept-severity__cell--hot' : 's8-dept-severity__cell--zero',
|
|
|
- ],
|
|
|
- role: 'cell',
|
|
|
- },
|
|
|
- String(count),
|
|
|
- ),
|
|
|
- h('span', { class: 's8-dept-severity__cell s8-dept-severity__cell--metric', role: 'cell' }, formatHoursOrDash(row.avgProcessHours)),
|
|
|
- h('span', { class: 's8-dept-severity__cell s8-dept-severity__cell--metric', role: 'cell' }, formatPercentOrDash(row.closeRate)),
|
|
|
- ],
|
|
|
- );
|
|
|
- }),
|
|
|
- ),
|
|
|
- ]);
|
|
|
+ return h(
|
|
|
+ 'div',
|
|
|
+ { class: 's8-dept-severity__list' },
|
|
|
+ rawProps.rows.map((row) => {
|
|
|
+ const count = row[rawProps.countKey];
|
|
|
+ const hot = count > 0;
|
|
|
+ return h(
|
|
|
+ 'div',
|
|
|
+ { key: row.deptId, class: 's8-dept-severity__item' },
|
|
|
+ [
|
|
|
+ h('div', { class: 's8-dept-severity__row-primary' }, [
|
|
|
+ h('span', { class: 's8-dept-severity__dept-name', title: row.deptName }, row.deptName),
|
|
|
+ h('span', { class: 's8-dept-severity__count-wrap' }, [
|
|
|
+ h('span', { class: 's8-dept-severity__count-label' }, rawProps.countLabel),
|
|
|
+ h(
|
|
|
+ 'span',
|
|
|
+ {
|
|
|
+ class: [
|
|
|
+ 's8-dept-severity__count-value',
|
|
|
+ hot ? 's8-dept-severity__count-value--hot' : 's8-dept-severity__count-value--zero',
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ String(count),
|
|
|
+ ),
|
|
|
+ ]),
|
|
|
+ ]),
|
|
|
+ h('div', { class: 's8-dept-severity__row-secondary' }, [
|
|
|
+ h('span', { class: 's8-dept-severity__metric' }, [
|
|
|
+ h('span', { class: 's8-dept-severity__metric-label' }, '均时 '),
|
|
|
+ h('span', { class: 's8-dept-severity__metric-value' }, formatHoursOrDash(row.avgProcessHours)),
|
|
|
+ ]),
|
|
|
+ h('span', { class: 's8-dept-severity__metric-sep' }, '·'),
|
|
|
+ h('span', { class: 's8-dept-severity__metric' }, [
|
|
|
+ h('span', { class: 's8-dept-severity__metric-label' }, '关闭率 '),
|
|
|
+ h('span', { class: 's8-dept-severity__metric-value' }, formatPercentOrDash(row.closeRate)),
|
|
|
+ ]),
|
|
|
+ ]),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }),
|
|
|
+ );
|
|
|
};
|
|
|
-DeptTable.props = {
|
|
|
+DeptList.props = {
|
|
|
loading: { type: Boolean as PropType<boolean>, default: false },
|
|
|
rows: { type: Array as PropType<S8DeptBacklogItem[]>, required: true },
|
|
|
countLabel: { type: String, required: true },
|
|
|
@@ -209,86 +214,97 @@ DeptTable.props = {
|
|
|
overflow: auto;
|
|
|
}
|
|
|
|
|
|
-.s8-dept-severity__table {
|
|
|
+/* S8-DEPT-CARD-LIST-LAYOUT-FIX-1:列表方案 */
|
|
|
+.s8-dept-severity__list {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 4px;
|
|
|
}
|
|
|
|
|
|
-.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 {
|
|
|
+.s8-dept-severity__item {
|
|
|
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;
|
|
|
- font-size: 12px;
|
|
|
- padding: 4px 6px;
|
|
|
+ padding: 6px 8px;
|
|
|
border-radius: 6px;
|
|
|
background: rgba(255, 255, 255, 0.02);
|
|
|
}
|
|
|
|
|
|
-.s8-dept-severity__cell {
|
|
|
+.s8-dept-severity__row-primary {
|
|
|
+ display: flex;
|
|
|
+ align-items: baseline;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 8px;
|
|
|
min-width: 0;
|
|
|
}
|
|
|
|
|
|
-.s8-dept-severity__cell--name {
|
|
|
- color: #cbd5e1;
|
|
|
+.s8-dept-severity__dept-name {
|
|
|
+ color: #e2e8f0;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
white-space: nowrap;
|
|
|
overflow: hidden;
|
|
|
text-overflow: ellipsis;
|
|
|
+ min-width: 0;
|
|
|
+ flex: 1;
|
|
|
}
|
|
|
|
|
|
-.s8-dept-severity__cell--count,
|
|
|
-.s8-dept-severity__cell--metric {
|
|
|
- text-align: right;
|
|
|
- font-family: 'Roboto Mono', monospace;
|
|
|
+.s8-dept-severity__count-wrap {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: baseline;
|
|
|
+ gap: 4px;
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
-.s8-dept-severity__cell--count {
|
|
|
- font-size: 14px;
|
|
|
- font-weight: 700;
|
|
|
+.s8-dept-severity__count-label {
|
|
|
+ font-size: 11px;
|
|
|
+ color: #94a3b8;
|
|
|
}
|
|
|
|
|
|
-.s8-dept-severity__cell--metric {
|
|
|
- font-size: 12px;
|
|
|
+.s8-dept-severity__count-value {
|
|
|
+ font-family: 'Roboto Mono', monospace;
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 700;
|
|
|
}
|
|
|
|
|
|
-.s8-dept-severity__cell--hot {
|
|
|
+.s8-dept-severity__count-value--hot {
|
|
|
color: #ef4444;
|
|
|
}
|
|
|
|
|
|
-.s8-dept-severity__cell--zero {
|
|
|
+.s8-dept-severity__count-value--zero {
|
|
|
+ color: #94a3b8;
|
|
|
+}
|
|
|
+
|
|
|
+.s8-dept-severity__row-secondary {
|
|
|
+ display: flex;
|
|
|
+ align-items: baseline;
|
|
|
+ gap: 6px;
|
|
|
+ font-size: 11px;
|
|
|
+ color: #94a3b8;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.s8-dept-severity__metric {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: baseline;
|
|
|
+ gap: 2px;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.s8-dept-severity__metric-label {
|
|
|
color: #94a3b8;
|
|
|
}
|
|
|
|
|
|
+.s8-dept-severity__metric-value {
|
|
|
+ font-family: 'Roboto Mono', monospace;
|
|
|
+ color: #cbd5e1;
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.s8-dept-severity__metric-sep {
|
|
|
+ color: rgba(148, 163, 184, 0.5);
|
|
|
+}
|
|
|
+
|
|
|
.s8-dept-severity__empty {
|
|
|
color: #64748b;
|
|
|
font-size: 12px;
|