KpiBoard.tsx
3.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// REQ-USR-003: KPI 合并网格(D5:CSS Grid gridRow span 复刻原型 kpi-body 合并)。
// 「导航类型」整列一格跨全部行;「角色」「子流程」按 roleSpan/subSpan 合并;空数据 → Empty。
import { Empty } from 'antd';
import type { KpiRow } from './dashboardData';
import { KPI_HEADERS } from './dashboardData';
import styles from './HomePage.module.css';
interface KpiBoardProps {
rows: KpiRow[];
/** KPI 待处理事项/描述为纯展示(蓝色链接样式),点击不跳转;保留占位以示意无导航。 */
onNavigate?: (target: string) => void;
}
export default function KpiBoard({ rows }: KpiBoardProps) {
if (!rows.length) {
return (
<div className={styles.kpiEmpty} data-testid="kpi-empty">
<Empty />
</div>
);
}
const total = rows.length;
// 表头行 = 第 1 行;数据行从第 2 行起
const cells: React.ReactNode[] = [];
// 表头 7 列
KPI_HEADERS.forEach((h, i) => {
cells.push(
<div key={`h-${i}`} className={styles.kpiHeadCell} style={{ gridColumn: i + 1, gridRow: 1 }}>
{h}
</div>,
);
});
// 导航类型:整列一格,跨全部数据行(复刻原型 navCell '按角色')
cells.push(
<div
key="navtype"
className={styles.kpiNavType}
style={{ gridColumn: 1, gridRow: `2 / span ${total}` }}
>
按角色
</div>,
);
rows.forEach((row, idx) => {
const curRow = idx + 2;
const alt = idx % 2 === 1 ? styles.kpiRowAlt : '';
// 角色(带 rowSpan)
if (row.role) {
const span = row.roleSpan ?? 1;
cells.push(
<div
key={`role-${idx}`}
className={`${styles.kpiCellCenter} ${alt}`}
style={{ gridColumn: 2, gridRow: `${curRow} / span ${span}` }}
>
{row.role}
</div>,
);
}
// KPI 待处理事项(蓝色链接样式,纯展示)
cells.push(
<div
key={`item-${idx}`}
className={`${styles.kpiLink} ${alt}`}
style={{ gridColumn: 3, gridRow: curRow }}
>
{row.item}
</div>,
);
// KPI 内容描述(蓝色链接样式,纯展示)
cells.push(
<div
key={`desc-${idx}`}
className={`${styles.kpiLink} ${alt}`}
style={{ gridColumn: 4, gridRow: curRow }}
>
{row.desc}
</div>,
);
// 今日未处理
cells.push(
<div
key={`today-${idx}`}
className={`${styles.kpiNum} ${row.red ? styles.kpiNumRed : ''} ${alt}`}
style={{ gridColumn: 5, gridRow: curRow }}
>
{row.today}
</div>,
);
// 未清总数
cells.push(
<div
key={`total-${idx}`}
className={`${styles.kpiNum} ${row.red ? styles.kpiNumRed : ''} ${alt}`}
style={{ gridColumn: 6, gridRow: curRow }}
>
{row.total}
</div>,
);
// 子流程(带 rowSpan)
if (row.sub && row.subSpan) {
cells.push(
<div
key={`sub-${idx}`}
className={styles.kpiSubProc}
style={{ gridColumn: 7, gridRow: `${curRow} / span ${row.subSpan}` }}
>
{row.sub}
</div>,
);
}
});
return (
<div
className={styles.kpiBoard}
style={{ gridTemplateRows: `38px repeat(${total}, minmax(38px, auto))` }}
>
{cells}
</div>
);
}