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:
SpecialX
2026-07-14 08:26:27 +08:00
parent 99580fa13a
commit 0b42302a64
42 changed files with 4804 additions and 637 deletions

View 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>
);
}