docs(admin-portal): 新增 nextstep-v2.md 记录下游核查结果
v1 声称完成的下游工作经核查实际未完成: - api-gateway: /api/admin/graphql 路由未注册,go vet 编译失败 - teacher-bff: resolver 已完成但 schema 未同步(命名空间 vs 扁平) - iam: proto 缺 BatchGetUsers rpc 声明 v2 记录详细核查证据和修复要求
This commit is contained in:
134
packages/ui-components/src/data-table.tsx
Normal file
134
packages/ui-components/src/data-table.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { cn } from "./utils/cn.js";
|
||||
import { Loading } from "./loading.js";
|
||||
import { Empty } from "./empty.js";
|
||||
|
||||
/**
|
||||
* DataTable - 通用数据表格
|
||||
*
|
||||
* 用途:列表数据的标准展示容器,内置加载态、空态、行交互。
|
||||
* 复用 Loading / Empty 组件保持一致的降级 UI。
|
||||
*
|
||||
* @example
|
||||
* const columns: Column<User>[] = [
|
||||
* { key: "name", header: "姓名" },
|
||||
* { key: "score", header: "分数", align: "right", render: (u) => u.score },
|
||||
* ];
|
||||
* <DataTable columns={columns} data={users} loading={isLoading} rowKey={(u) => u.id} />
|
||||
*/
|
||||
|
||||
/** 列对齐方式 */
|
||||
export type ColumnAlign = "left" | "center" | "right";
|
||||
|
||||
/** 列定义 */
|
||||
export interface Column<T> {
|
||||
/** 唯一标识(用于 key) */
|
||||
key: string;
|
||||
/** 表头内容 */
|
||||
header: ReactNode;
|
||||
/** 自定义单元格渲染;未提供时取 row[key] */
|
||||
render?: (row: T) => ReactNode;
|
||||
/** 自定义单元格类名 */
|
||||
className?: string;
|
||||
/** 对齐方式 */
|
||||
align?: ColumnAlign;
|
||||
/** 列宽(CSS 值,如 "200px" / "20%") */
|
||||
width?: string;
|
||||
}
|
||||
|
||||
export interface DataTableProps<T> {
|
||||
/** 列定义 */
|
||||
columns: Column<T>[];
|
||||
/** 数据数组 */
|
||||
data: T[];
|
||||
/** 加载态;为 true 时显示 Loading */
|
||||
loading?: boolean;
|
||||
/** 空态自定义内容;未提供时使用默认 Empty */
|
||||
empty?: ReactNode;
|
||||
/** 行点击回调 */
|
||||
onRowClick?: (row: T) => void;
|
||||
/** 行 key 生成函数;未提供时使用索引 */
|
||||
rowKey?: (row: T) => string;
|
||||
/** 自定义类名 */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const ALIGN_CLASS: Record<ColumnAlign, string> = {
|
||||
left: "text-left",
|
||||
center: "text-center",
|
||||
right: "text-right",
|
||||
};
|
||||
|
||||
export function DataTable<T>({
|
||||
columns,
|
||||
data,
|
||||
loading,
|
||||
empty,
|
||||
onRowClick,
|
||||
rowKey,
|
||||
className,
|
||||
}: DataTableProps<T>): ReactNode {
|
||||
if (loading) {
|
||||
return <Loading lines={Math.max(columns.length, 3)} />;
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return empty ?? <Empty title="暂无数据" description="调整筛选条件后重试" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"overflow-x-auto border border-rule rounded-card",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-subtle">
|
||||
<tr className="border-b border-rule">
|
||||
{columns.map((col) => (
|
||||
<th
|
||||
key={col.key}
|
||||
style={col.width ? { width: col.width } : undefined}
|
||||
className={cn(
|
||||
"py-2 px-3 text-tiny uppercase tracking-wide text-ink-muted font-medium",
|
||||
col.align ? ALIGN_CLASS[col.align] : "text-left",
|
||||
)}
|
||||
>
|
||||
{col.header}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.map((row, index) => {
|
||||
const key = rowKey ? rowKey(row) : String(index);
|
||||
return (
|
||||
<tr
|
||||
key={key}
|
||||
onClick={onRowClick ? () => onRowClick(row) : undefined}
|
||||
className={cn(
|
||||
"border-b border-rule",
|
||||
onRowClick && "cursor-pointer hover:bg-subtle",
|
||||
)}
|
||||
>
|
||||
{columns.map((col) => (
|
||||
<td
|
||||
key={col.key}
|
||||
className={cn(
|
||||
"py-2 px-3 text-ink",
|
||||
col.align ? ALIGN_CLASS[col.align] : "text-left",
|
||||
col.className,
|
||||
)}
|
||||
>
|
||||
{col.render ? col.render(row) : null}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user