iter(g315): 表格支持行展开详情
This commit is contained in:
@@ -12,6 +12,7 @@ import { exportButton } from './csv.js';
|
||||
import { skeleton } from './skeleton.js';
|
||||
import { textField } from './textField.js';
|
||||
import { emptyRow } from './tableEmpty.js';
|
||||
import { detailRow, expandCell } from './tableExpand.js';
|
||||
import { tablePager } from './tablePager.js';
|
||||
import { attachDataSource, readRows } from './tableDataSource.js';
|
||||
import { bridgePage, bridgeSelection } from './tableBridge.js';
|
||||
@@ -21,6 +22,7 @@ import type { DataTableProps, TableApi } from './types.js';
|
||||
/*** 数据表格:粘性表头 + 行选择 + 排序 + 筛选 + 分页 + 空状态;状态由 table-core 持有,信号只做桥接渲染 ***/
|
||||
export function dataTable<T extends { id: RowKey }>(props: DataTableProps<T>): HTMLElement {
|
||||
const selectable = props.selectable ?? false; // 状态集中声明:行选择开关
|
||||
const expanded = signal<RowKey[]>([]); // 已展开行的 rowKey:纯视图状态,不参与内核派生
|
||||
const keyword = signal(''); // 筛选框关键字:受控信号,空状态里的「清除筛选」靠它一键还原
|
||||
const table = createTable({
|
||||
data: readRows(props.data), // 信号数据源先取当前值,后续靠 attachDataSource 换
|
||||
@@ -56,7 +58,7 @@ export function dataTable<T extends { id: RowKey }>(props: DataTableProps<T>): H
|
||||
const filterKey = props.filterKey ?? props.columns[0].key;
|
||||
const hidden = props.hiddenColumns ?? signal<string[]>([]); // 隐藏列键集:外部没给就自己持有
|
||||
const shown = computed(() => props.columns.filter((c) => !hidden().includes(c.key))); // 可见列:表头 / 单元格 / 导出 / colspan 全从这一处派生
|
||||
const span = (): number => shown().length + (selectable ? 1 : 0); // 跨列数随可见列变化
|
||||
const span = (): number => shown().length + (selectable ? 1 : 0) + (props.renderDetail ? 1 : 0); // 跨列数随可见列与展开列变化
|
||||
|
||||
// —— 表头全选框:checked / indeterminate 三态跟随当页选中情况 ——
|
||||
const headCheck = h('input', { type: 'checkbox', class: 'ua-checkbox' }) as HTMLInputElement;
|
||||
@@ -125,9 +127,10 @@ export function dataTable<T extends { id: RowKey }>(props: DataTableProps<T>): H
|
||||
const sel = state().selection; // 读入依赖:选择变化即整表重绘(行数 = pageSize,代价可控)
|
||||
const rows = visible().rows;
|
||||
if (!rows.length) return [emptyRow(span(), keyword, () => table.dispatch({ type: 'setFilter', key: filterKey, value: '' }), props.emptyText, props.emptyAction)]; // 空状态:筛选词与内核筛选一起清;真没数据时给调用方的操作插槽
|
||||
return rows.map((row, i) => {
|
||||
return rows.flatMap((row, i) => {
|
||||
const key = visible().keys[i];
|
||||
const selected = sel.includes(key);
|
||||
const open = !!props.renderDetail && expanded().includes(key); // 展开态按 rowKey 记:翻页或筛选后同一行仍保持着
|
||||
const cells = shown().map((col) => {
|
||||
const raw = table.getCellValue(row, col.key);
|
||||
const text = col.format ? col.format(raw, row) : String(raw); // format 只产出纯文本,天然免注入
|
||||
@@ -139,10 +142,12 @@ export function dataTable<T extends { id: RowKey }>(props: DataTableProps<T>): H
|
||||
check.addEventListener('change', () => table.dispatch({ type: 'toggleSelect', key }));
|
||||
cells.unshift(h('td', { class: 'ua-cell-check' }, check));
|
||||
}
|
||||
if (props.renderDetail) cells.unshift(expandCell(open, `第 ${i + 1} 行`, () => expanded(open ? expanded().filter((k) => k !== key) : [...expanded(), key]))); // 展开列在最前:与选择列并列但不互斥
|
||||
const rowProps = props.onRowClick // 传了才把整行做成可聚焦按钮语义(Tab 进入、Enter / 空格触发)
|
||||
? { tabindex: '0', class: `ua-row-clickable${selected ? ' ua-row-selected' : ''}`, onclick: () => props.onRowClick?.(row), onkeydown: (e: KeyboardEvent) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); props.onRowClick?.(row); } } }
|
||||
: selected ? { class: 'ua-row-selected' } : null;
|
||||
return h('tr', rowProps, ...cells);
|
||||
const tr = h('tr', rowProps, ...cells);
|
||||
return props.renderDetail && open ? [tr, detailRow(span(), props.renderDetail(row))] : [tr];
|
||||
});
|
||||
});
|
||||
// —— 列宽:数字落成 <col> 固定宽度,'flex' 或未设置交给浏览器自适应;按列键建一次,显隐只切换是否挂进 colgroup ——
|
||||
@@ -161,9 +166,13 @@ export function dataTable<T extends { id: RowKey }>(props: DataTableProps<T>): H
|
||||
},
|
||||
});
|
||||
|
||||
// —— 表头:随可见列重绘(隐藏列既不占表头也不占单元格) ——
|
||||
// —— 表头:随可见列重绘(隐藏列既不占表头也不占单元格);选择与展开各占一个 th,顺序与行内单元格一致 ——
|
||||
const thead = h('thead');
|
||||
renderList(thead, () => [h('tr', null, ...(selectable ? [headCheck] : []), ...shown().map(th))]);
|
||||
const lead = (): unknown[] => [
|
||||
props.renderDetail ? h('th', { class: 'ua-cell-expand', scope: 'col' }, h('span', { class: 'ua-visually-hidden' }, '展开详情')) : null, // 展开列在最前
|
||||
selectable ? h('th', { class: 'ua-cell-check', scope: 'col' }, headCheck) : null, // 全选框借这次归位进 th(原先直接塞进 tr,属无效结构)
|
||||
];
|
||||
renderList(thead, () => [h('tr', null, ...lead(), ...shown().map(th))]);
|
||||
|
||||
return h(
|
||||
'section',
|
||||
|
||||
Reference in New Issue
Block a user