diff --git a/.ua/backlog.json b/.ua/backlog.json index 54a4f9a..04d5cec 100644 --- a/.ua/backlog.json +++ b/.ua/backlog.json @@ -152,6 +152,14 @@ "acceptance": "新增 tag.ts:label + variant 五档 + closable/onClose;closable 时渲染带 aria-label 的 × 按钮;只消费 --ua-* 令牌;导出 / manifest / 文档 / llms-full.txt / demo 同步;npm run check 全绿", "status": "done", "notes": "组件无状态:onClose 只通知,节点由调用方按信号重渲染移除(与 form/formItem 返回句柄、dataTable 委托内核的哲学一致);demo 用 signal + renderList 演示真实删除链路,另有四档语义色静态展示" + }, + { + "id": "d13", + "title": "dataTable 列定义支持对齐与单元格格式化", + "motivation": "皮肤层只能 String() 原始值,数字列无法右对齐、年龄/金额无法加单位;内核不关心展示,字段应留在皮肤层", + "acceptance": "新增皮肤层 TableColumn extends ColumnDef,追加 align?: left|center|right 与 format?: (value,row)=>string;DataTableProps.columns 改用它;表头与单元格共用对齐类;format 走 textContent 天然免注入;demo 表格演示年龄右对齐+单位、城市居中;manifest/文档/llms-full.txt 同步;npm run check 全绿", + "status": "done", + "notes": "严格分层:内核 ColumnDef 不动,展示字段只在 skin 的 TableColumn 上扩展(extends 而非改核心);对齐类同时作用于 th(取代原 class 函数,合并 ua-sortable);demo 首表展示 align:right + format(v=>`${v} 岁`)、城市居中" } ] } diff --git a/.ua/index.json b/.ua/index.json index dddd0dd..d1e2f29 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-23T08:55:03.791Z", + "generatedAt": "2026-10-23T08:55:54.330Z", "repoRoot": ".", "packages": [ { @@ -1166,7 +1166,7 @@ "n": "columns", "t": "ColumnDef[]", "r": 1, - "d": "列定义:{ key, header?, sortable? }" + "d": "列定义:内核字段 { key, header?, sortable? } + 皮肤字段 { align?: left|center|right, format?: (value, row) => string }" }, { "n": "filterKey", @@ -1200,6 +1200,7 @@ "steps": [ "准备行数据,确保每行有唯一 id(跨页选择与刷新不丢行靠它)", "定义 columns:sortable: true 的列表头可点击排序(升 → 降 → 取消循环),标记箭头 ↑↓↕", + "列展示细节用皮肤字段:align 控制表头与单元格对齐,format(value, row) 返回纯文本(走 textContent,不需要转义)", "selectable: true 开启行选择:表头全选框带 indeterminate 半选态,工具栏显示已选计数", "选中行用低视觉比重样式(淡色底 + 弱化文字),选择状态存 rowKey,跨页不丢失", "工具栏输入框即筛选,输入即时过滤并回第 1 页;无匹配时显示空状态行", diff --git a/.ua/iterations.log b/.ua/iterations.log index 2bdd1e3..834b3ce 100644 --- a/.ua/iterations.log +++ b/.ua/iterations.log @@ -17,3 +17,4 @@ {"time": "2026-10-23T16:53:44+08:00", "id": "d10", "result": "done", "summary": "toast 新增 closable(× 手动关闭)与同屏上限 4 条,退场统一走 dismiss() 双兜底;is-closable 才接收指针;demo/manifest/llms 同步,check 全绿"} {"time": "2026-10-23T16:54:36+08:00", "id": "d11", "result": "done", "summary": "modal 新增 size 三档(sm/md/lg)与 footer 操作区(右对齐 + 上分割线),缺省行为不变;demo/manifest/llms 同步,check 全绿"} {"time": "2026-10-23T16:55:10+08:00", "id": "d12", "result": "done", "summary": "新增可关闭标签 tag(五档语义色 + closable/onClose,无状态委托调用方重渲染);demo 用 renderList 演示真实删除;manifest/llms 同步,check 全绿"} +{"time": "2026-10-23T16:55:55+08:00", "id": "d13", "result": "done", "summary": "dataTable 列定义新增皮肤层 TableColumn(align + format),内核 ColumnDef 不变;表头/单元格共用对齐类,format 走 textContent;demo/manifest/llms 同步,check 全绿"} diff --git a/apps/demo/main.ts b/apps/demo/main.ts index 446e481..f691d9e 100644 --- a/apps/demo/main.ts +++ b/apps/demo/main.ts @@ -366,8 +366,8 @@ const content = h( loading: tableLoading, columns: [ { key: 'name', header: '姓名', sortable: true }, - { key: 'age', header: '年龄', sortable: true }, - { key: 'city', header: '城市' }, + { key: 'age', header: '年龄', sortable: true, align: 'right', format: (v) => `${v} 岁` }, + { key: 'city', header: '城市', align: 'center' }, ], pageSize: 5, }), h('div', { class: 'row' }, switchButton({ label: '加载态', checked: tableLoading }))), diff --git a/llms-full.txt b/llms-full.txt index 69c89b8..210f5cb 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-23T08:55:03.791Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-23T08:55:54.330Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -651,7 +651,7 @@ const name = signal(''); const f = form({ onSubmit: submit }, [formItem({ label: 参数: - data(必填):T[] —— 全量行数据,每行须含 id 字段作 rowKey -- columns(必填):ColumnDef[] —— 列定义:{ key, header?, sortable? } +- columns(必填):ColumnDef[] —— 列定义:内核字段 { key, header?, sortable? } + 皮肤字段 { align?: left|center|right, format?: (value, row) => string } - filterKey:string —— 工具栏筛选的列;缺省取第一列 - pageSize(默认 5):number —— 每页行数;缺省 5 - selectable(默认 false):boolean —— 启用行选择列:全选三态(含半选)、行 checkbox、已选计数;缺省 false @@ -668,12 +668,13 @@ dataTable({ data: users, selectable: true, columns: [{ key: 'name', header: '姓 1. 准备行数据,确保每行有唯一 id(跨页选择与刷新不丢行靠它) 2. 定义 columns:sortable: true 的列表头可点击排序(升 → 降 → 取消循环),标记箭头 ↑↓↕ -3. selectable: true 开启行选择:表头全选框带 indeterminate 半选态,工具栏显示已选计数 -4. 选中行用低视觉比重样式(淡色底 + 弱化文字),选择状态存 rowKey,跨页不丢失 -5. 工具栏输入框即筛选,输入即时过滤并回第 1 页;无匹配时显示空状态行 -6. 表头粘性固定(滚动容器 max-height 420px),斑马纹 + 悬停高亮由 CSS 提供 -7. 数据在途时传 loading(布尔或信号):表格体换成骨架行、筛选与翻页禁用,返回后置回 false 即恢复 -8. 需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用 +3. 列展示细节用皮肤字段:align 控制表头与单元格对齐,format(value, row) 返回纯文本(走 textContent,不需要转义) +4. selectable: true 开启行选择:表头全选框带 indeterminate 半选态,工具栏显示已选计数 +5. 选中行用低视觉比重样式(淡色底 + 弱化文字),选择状态存 rowKey,跨页不丢失 +6. 工具栏输入框即筛选,输入即时过滤并回第 1 页;无匹配时显示空状态行 +7. 表头粘性固定(滚动容器 max-height 420px),斑马纹 + 悬停高亮由 CSS 提供 +8. 数据在途时传 loading(布尔或信号):表格体换成骨架行、筛选与翻页禁用,返回后置回 false 即恢复 +9. 需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用 现场演示:apps/docs/demo.html#sec-table diff --git a/packages/skin/ui/src/dataTable.ts b/packages/skin/ui/src/dataTable.ts index 230bba5..638bce5 100644 --- a/packages/skin/ui/src/dataTable.ts +++ b/packages/skin/ui/src/dataTable.ts @@ -92,7 +92,7 @@ export function dataTable(props: DataTableProps): H h( 'th', { - class: () => (col.sortable ? 'ua-sortable' : ''), + class: `${col.sortable ? 'ua-sortable' : ''}${col.align ? ` ua-align-${col.align}` : ''}`.trim(), onClick: () => col.sortable && table.dispatch({ type: 'toggleSort', key: col.key }), }, col.header ?? col.key, @@ -117,7 +117,11 @@ export function dataTable(props: DataTableProps): H return rows.map((row, i) => { const key = visible().keys[i]; const selected = sel.includes(key); - const cells = props.columns.map((col) => h('td', null, String(table.getCellValue(row, col.key)))); + const cells = props.columns.map((col) => { + const raw = table.getCellValue(row, col.key); + const text = col.format ? col.format(raw, row) : String(raw); // format 只产出纯文本,天然免注入 + return h('td', col.align ? { class: `ua-align-${col.align}` } : null, text); + }); if (selectable) { const check = h('input', { type: 'checkbox', class: 'ua-checkbox' }) as HTMLInputElement; check.checked = selected; diff --git a/packages/skin/ui/src/types.ts b/packages/skin/ui/src/types.ts index a1c161b..864b91f 100644 --- a/packages/skin/ui/src/types.ts +++ b/packages/skin/ui/src/types.ts @@ -39,10 +39,16 @@ export interface CardProps { title?: string; // 标题;缺省不渲染标题行 } +/*** 皮肤层列定义:内核列定义 + 纯渲染字段(对齐 / 文案格式化),不参与排序筛选语义 ***/ +export interface TableColumn extends ColumnDef { + align?: 'left' | 'center' | 'right'; // 表头与单元格对齐;缺省 left + format?: (value: unknown, row: T) => string; // 单元格文案格式化(返回纯文本,走 textContent 渲染) +} + /*** 数据表格入参 ***/ export interface DataTableProps { - data: T[]; // 全量行数据,行须含 id 字段作 rowKey - columns: ColumnDef[]; // 列定义 + data: T[]; // 全量行数据,行须含 id 字段作 rowKey + columns: TableColumn[]; // 列定义:内核字段 + align / format filterKey?: string; // 工具栏筛选的列;缺省取第一列 pageSize?: number; // 每页行数;缺省 5 selectable?: boolean; // 是否启用行选择列(含全选三态与已选计数);缺省 false diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index 23f6cc1..7c45d0f 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -1037,7 +1037,7 @@ "n": "columns", "t": "ColumnDef[]", "r": 1, - "d": "列定义:{ key, header?, sortable? }" + "d": "列定义:内核字段 { key, header?, sortable? } + 皮肤字段 { align?: left|center|right, format?: (value, row) => string }" }, { "n": "filterKey", @@ -1071,6 +1071,7 @@ "steps": [ "准备行数据,确保每行有唯一 id(跨页选择与刷新不丢行靠它)", "定义 columns:sortable: true 的列表头可点击排序(升 → 降 → 取消循环),标记箭头 ↑↓↕", + "列展示细节用皮肤字段:align 控制表头与单元格对齐,format(value, row) 返回纯文本(走 textContent,不需要转义)", "selectable: true 开启行选择:表头全选框带 indeterminate 半选态,工具栏显示已选计数", "选中行用低视觉比重样式(淡色底 + 弱化文字),选择状态存 rowKey,跨页不丢失", "工具栏输入框即筛选,输入即时过滤并回第 1 页;无匹配时显示空状态行", diff --git a/packages/skin/ui/ui.css b/packages/skin/ui/ui.css index 52338fe..9274749 100644 --- a/packages/skin/ui/ui.css +++ b/packages/skin/ui/ui.css @@ -113,6 +113,13 @@ width: 36px; text-align: center !important; } +/* 列对齐:表头与单元格共用,由列定义的 align 决定 */ +.ua-align-center { + text-align: center; +} +.ua-align-right { + text-align: right; +} .ua-empty-row td { text-align: center; color: var(--ua-color-text-muted);