/** * 股票列表表格骨架(自选/策略页共享)。 * * 职责:表头渲染(读列配置 label/align + 可排序三态指示器)、表体遍历、sticky 表头。 * 不内置任何业务逻辑:单元格内容(含 symbol 列交互、操作列、ext 列)由调用方通过 * renderCell / renderExtraCol 注入。这样两个页面的特有交互得以保留,同时表头能力一致。 */ import type { ReactNode } from 'react' import type { ColumnConfig } from '@/lib/list-columns' import { UNSORTABLE_KEYS } from '@/lib/stock-table' import type { SortState } from './useTableSort' export type { SortState } export interface StockDataTableProps { columns: ColumnConfig[] rows: any[] /** 单元格渲染回调。返回 null 时回退到内置纯数据列渲染。 */ renderCell: (r: any, col: ColumnConfig) => ReactNode /** 行 key(默认取 r.symbol) */ rowKey?: (r: any) => string | number /** 行 className(默认含 hover) */ rowClassName?: (r: any) => string /** 表头是否 sticky(自选页需要,策略页不需要) */ headerSticky?: boolean /** 最小表格宽度,默认按列数计算 */ minWidth?: number /** 排序:外部受控时传入(含当前 sort 与 toggle);不传则表头不可排序 */ sort?: SortState | null onSortToggle?: (colId: string) => void /** 追加在每行末尾的额外单元格(如自选页的操作列) */ renderExtraCol?: (r: any) => ReactNode /** 追加的表头单元格(对应 renderExtraCol) */ extraHeader?: ReactNode /** 自定义表头单元格内容覆盖(如日k眼睛按钮)。返回 undefined 则用 col.label */ renderHeaderContent?: (col: ColumnConfig) => ReactNode | undefined /** 外层容器 className */ className?: string } function alignThClass(align: ColumnConfig['align']): string { if (align === 'right') return 'px-3 py-2.5 font-medium text-right' if (align === 'center') return 'px-3 py-2.5 font-medium text-center' return 'px-3 py-2.5 font-medium' } export function StockDataTable({ columns, rows, renderCell, rowKey = (r: any) => r.symbol, rowClassName = () => 'border-t border-border hover:bg-elevated/50', headerSticky = false, minWidth, sort, onSortToggle, renderExtraCol, extraHeader, renderHeaderContent, className = 'rounded-card border border-border overflow-x-auto', }: StockDataTableProps) { const visibleColumns = columns.filter(c => c.visible) const computedMinWidth = minWidth ?? Math.max(900, visibleColumns.length * 110) const isColSortable = (col: ColumnConfig): boolean => { // 排序能力由调用方是否提供 onSortToggle 决定;sort 是否为 null 只影响当前指示器 if (!onSortToggle) return false if (col.source.type === 'builtin' && UNSORTABLE_KEYS.has(col.source.key)) return false return true } const theadClass = headerSticky ? 'sticky top-0 z-10 bg-surface after:absolute after:inset-x-0 after:bottom-0 after:h-px after:bg-border' : 'bg-elevated' return (
| onSortToggle!(col.id) : undefined} > {contentOverride !== undefined ? contentOverride : col.label} {sortable && ( {isSorted ? (dir === 'asc' ? '↑' : '↓') : '↕'} )} | ) })} {extraHeader && ({extraHeader} | )}
|---|---|