iter: d13 dataTable 列对齐与单元格格式化
This commit is contained in:
@@ -152,6 +152,14 @@
|
|||||||
"acceptance": "新增 tag.ts:label + variant 五档 + closable/onClose;closable 时渲染带 aria-label 的 × 按钮;只消费 --ua-* 令牌;导出 / manifest / 文档 / llms-full.txt / demo 同步;npm run check 全绿",
|
"acceptance": "新增 tag.ts:label + variant 五档 + closable/onClose;closable 时渲染带 aria-label 的 × 按钮;只消费 --ua-* 令牌;导出 / manifest / 文档 / llms-full.txt / demo 同步;npm run check 全绿",
|
||||||
"status": "done",
|
"status": "done",
|
||||||
"notes": "组件无状态:onClose 只通知,节点由调用方按信号重渲染移除(与 form/formItem 返回句柄、dataTable 委托内核的哲学一致);demo 用 signal<string[]> + renderList 演示真实删除链路,另有四档语义色静态展示"
|
"notes": "组件无状态:onClose 只通知,节点由调用方按信号重渲染移除(与 form/formItem 返回句柄、dataTable 委托内核的哲学一致);demo 用 signal<string[]> + renderList 演示真实删除链路,另有四档语义色静态展示"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d13",
|
||||||
|
"title": "dataTable 列定义支持对齐与单元格格式化",
|
||||||
|
"motivation": "皮肤层只能 String() 原始值,数字列无法右对齐、年龄/金额无法加单位;内核不关心展示,字段应留在皮肤层",
|
||||||
|
"acceptance": "新增皮肤层 TableColumn<T> extends ColumnDef<T>,追加 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} 岁`)、城市居中"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "ui-arch-registry-index/0.1",
|
"$schema": "ui-arch-registry-index/0.1",
|
||||||
"generatedAt": "2026-10-23T08:55:03.791Z",
|
"generatedAt": "2026-10-23T08:55:54.330Z",
|
||||||
"repoRoot": ".",
|
"repoRoot": ".",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
@@ -1166,7 +1166,7 @@
|
|||||||
"n": "columns",
|
"n": "columns",
|
||||||
"t": "ColumnDef<T>[]",
|
"t": "ColumnDef<T>[]",
|
||||||
"r": 1,
|
"r": 1,
|
||||||
"d": "列定义:{ key, header?, sortable? }"
|
"d": "列定义:内核字段 { key, header?, sortable? } + 皮肤字段 { align?: left|center|right, format?: (value, row) => string }"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"n": "filterKey",
|
"n": "filterKey",
|
||||||
@@ -1200,6 +1200,7 @@
|
|||||||
"steps": [
|
"steps": [
|
||||||
"准备行数据,确保每行有唯一 id(跨页选择与刷新不丢行靠它)",
|
"准备行数据,确保每行有唯一 id(跨页选择与刷新不丢行靠它)",
|
||||||
"定义 columns:sortable: true 的列表头可点击排序(升 → 降 → 取消循环),标记箭头 ↑↓↕",
|
"定义 columns:sortable: true 的列表头可点击排序(升 → 降 → 取消循环),标记箭头 ↑↓↕",
|
||||||
|
"列展示细节用皮肤字段:align 控制表头与单元格对齐,format(value, row) 返回纯文本(走 textContent,不需要转义)",
|
||||||
"selectable: true 开启行选择:表头全选框带 indeterminate 半选态,工具栏显示已选计数",
|
"selectable: true 开启行选择:表头全选框带 indeterminate 半选态,工具栏显示已选计数",
|
||||||
"选中行用低视觉比重样式(淡色底 + 弱化文字),选择状态存 rowKey,跨页不丢失",
|
"选中行用低视觉比重样式(淡色底 + 弱化文字),选择状态存 rowKey,跨页不丢失",
|
||||||
"工具栏输入框即筛选,输入即时过滤并回第 1 页;无匹配时显示空状态行",
|
"工具栏输入框即筛选,输入即时过滤并回第 1 页;无匹配时显示空状态行",
|
||||||
|
|||||||
@@ -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: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: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: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 全绿"}
|
||||||
|
|||||||
+2
-2
@@ -366,8 +366,8 @@ const content = h(
|
|||||||
loading: tableLoading,
|
loading: tableLoading,
|
||||||
columns: [
|
columns: [
|
||||||
{ key: 'name', header: '姓名', sortable: true },
|
{ key: 'name', header: '姓名', sortable: true },
|
||||||
{ key: 'age', header: '年龄', sortable: true },
|
{ key: 'age', header: '年龄', sortable: true, align: 'right', format: (v) => `${v} 岁` },
|
||||||
{ key: 'city', header: '城市' },
|
{ key: 'city', header: '城市', align: 'center' },
|
||||||
],
|
],
|
||||||
pageSize: 5,
|
pageSize: 5,
|
||||||
}), h('div', { class: 'row' }, switchButton({ label: '加载态', checked: tableLoading }))),
|
}), h('div', { class: 'row' }, switchButton({ label: '加载态', checked: tableLoading }))),
|
||||||
|
|||||||
+9
-8
@@ -1,6 +1,6 @@
|
|||||||
# @ui-arch 组件库 Agent 文档(无图形版)
|
# @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)。
|
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
||||||
|
|
||||||
## 快速上手
|
## 快速上手
|
||||||
@@ -651,7 +651,7 @@ const name = signal(''); const f = form({ onSubmit: submit }, [formItem({ label:
|
|||||||
参数:
|
参数:
|
||||||
|
|
||||||
- data(必填):T[] —— 全量行数据,每行须含 id 字段作 rowKey
|
- data(必填):T[] —— 全量行数据,每行须含 id 字段作 rowKey
|
||||||
- columns(必填):ColumnDef<T>[] —— 列定义:{ key, header?, sortable? }
|
- columns(必填):ColumnDef<T>[] —— 列定义:内核字段 { key, header?, sortable? } + 皮肤字段 { align?: left|center|right, format?: (value, row) => string }
|
||||||
- filterKey:string —— 工具栏筛选的列;缺省取第一列
|
- filterKey:string —— 工具栏筛选的列;缺省取第一列
|
||||||
- pageSize(默认 5):number —— 每页行数;缺省 5
|
- pageSize(默认 5):number —— 每页行数;缺省 5
|
||||||
- selectable(默认 false):boolean —— 启用行选择列:全选三态(含半选)、行 checkbox、已选计数;缺省 false
|
- selectable(默认 false):boolean —— 启用行选择列:全选三态(含半选)、行 checkbox、已选计数;缺省 false
|
||||||
@@ -668,12 +668,13 @@ dataTable({ data: users, selectable: true, columns: [{ key: 'name', header: '姓
|
|||||||
|
|
||||||
1. 准备行数据,确保每行有唯一 id(跨页选择与刷新不丢行靠它)
|
1. 准备行数据,确保每行有唯一 id(跨页选择与刷新不丢行靠它)
|
||||||
2. 定义 columns:sortable: true 的列表头可点击排序(升 → 降 → 取消循环),标记箭头 ↑↓↕
|
2. 定义 columns:sortable: true 的列表头可点击排序(升 → 降 → 取消循环),标记箭头 ↑↓↕
|
||||||
3. selectable: true 开启行选择:表头全选框带 indeterminate 半选态,工具栏显示已选计数
|
3. 列展示细节用皮肤字段:align 控制表头与单元格对齐,format(value, row) 返回纯文本(走 textContent,不需要转义)
|
||||||
4. 选中行用低视觉比重样式(淡色底 + 弱化文字),选择状态存 rowKey,跨页不丢失
|
4. selectable: true 开启行选择:表头全选框带 indeterminate 半选态,工具栏显示已选计数
|
||||||
5. 工具栏输入框即筛选,输入即时过滤并回第 1 页;无匹配时显示空状态行
|
5. 选中行用低视觉比重样式(淡色底 + 弱化文字),选择状态存 rowKey,跨页不丢失
|
||||||
6. 表头粘性固定(滚动容器 max-height 420px),斑马纹 + 悬停高亮由 CSS 提供
|
6. 工具栏输入框即筛选,输入即时过滤并回第 1 页;无匹配时显示空状态行
|
||||||
7. 数据在途时传 loading(布尔或信号):表格体换成骨架行、筛选与翻页禁用,返回后置回 false 即恢复
|
7. 表头粘性固定(滚动容器 max-height 420px),斑马纹 + 悬停高亮由 CSS 提供
|
||||||
8. 需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用
|
8. 数据在途时传 loading(布尔或信号):表格体换成骨架行、筛选与翻页禁用,返回后置回 false 即恢复
|
||||||
|
9. 需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用
|
||||||
|
|
||||||
现场演示:apps/docs/demo.html#sec-table
|
现场演示:apps/docs/demo.html#sec-table
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export function dataTable<T extends { id: RowKey }>(props: DataTableProps<T>): H
|
|||||||
h(
|
h(
|
||||||
'th',
|
'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 }),
|
onClick: () => col.sortable && table.dispatch({ type: 'toggleSort', key: col.key }),
|
||||||
},
|
},
|
||||||
col.header ?? col.key,
|
col.header ?? col.key,
|
||||||
@@ -117,7 +117,11 @@ export function dataTable<T extends { id: RowKey }>(props: DataTableProps<T>): H
|
|||||||
return rows.map((row, i) => {
|
return rows.map((row, i) => {
|
||||||
const key = visible().keys[i];
|
const key = visible().keys[i];
|
||||||
const selected = sel.includes(key);
|
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) {
|
if (selectable) {
|
||||||
const check = h('input', { type: 'checkbox', class: 'ua-checkbox' }) as HTMLInputElement;
|
const check = h('input', { type: 'checkbox', class: 'ua-checkbox' }) as HTMLInputElement;
|
||||||
check.checked = selected;
|
check.checked = selected;
|
||||||
|
|||||||
@@ -39,10 +39,16 @@ export interface CardProps {
|
|||||||
title?: string; // 标题;缺省不渲染标题行
|
title?: string; // 标题;缺省不渲染标题行
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*** 皮肤层列定义:内核列定义 + 纯渲染字段(对齐 / 文案格式化),不参与排序筛选语义 ***/
|
||||||
|
export interface TableColumn<T = unknown> extends ColumnDef<T> {
|
||||||
|
align?: 'left' | 'center' | 'right'; // 表头与单元格对齐;缺省 left
|
||||||
|
format?: (value: unknown, row: T) => string; // 单元格文案格式化(返回纯文本,走 textContent 渲染)
|
||||||
|
}
|
||||||
|
|
||||||
/*** 数据表格入参 ***/
|
/*** 数据表格入参 ***/
|
||||||
export interface DataTableProps<T> {
|
export interface DataTableProps<T> {
|
||||||
data: T[]; // 全量行数据,行须含 id 字段作 rowKey
|
data: T[]; // 全量行数据,行须含 id 字段作 rowKey
|
||||||
columns: ColumnDef<T>[]; // 列定义
|
columns: TableColumn<T>[]; // 列定义:内核字段 + align / format
|
||||||
filterKey?: string; // 工具栏筛选的列;缺省取第一列
|
filterKey?: string; // 工具栏筛选的列;缺省取第一列
|
||||||
pageSize?: number; // 每页行数;缺省 5
|
pageSize?: number; // 每页行数;缺省 5
|
||||||
selectable?: boolean; // 是否启用行选择列(含全选三态与已选计数);缺省 false
|
selectable?: boolean; // 是否启用行选择列(含全选三态与已选计数);缺省 false
|
||||||
|
|||||||
@@ -1037,7 +1037,7 @@
|
|||||||
"n": "columns",
|
"n": "columns",
|
||||||
"t": "ColumnDef<T>[]",
|
"t": "ColumnDef<T>[]",
|
||||||
"r": 1,
|
"r": 1,
|
||||||
"d": "列定义:{ key, header?, sortable? }"
|
"d": "列定义:内核字段 { key, header?, sortable? } + 皮肤字段 { align?: left|center|right, format?: (value, row) => string }"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"n": "filterKey",
|
"n": "filterKey",
|
||||||
@@ -1071,6 +1071,7 @@
|
|||||||
"steps": [
|
"steps": [
|
||||||
"准备行数据,确保每行有唯一 id(跨页选择与刷新不丢行靠它)",
|
"准备行数据,确保每行有唯一 id(跨页选择与刷新不丢行靠它)",
|
||||||
"定义 columns:sortable: true 的列表头可点击排序(升 → 降 → 取消循环),标记箭头 ↑↓↕",
|
"定义 columns:sortable: true 的列表头可点击排序(升 → 降 → 取消循环),标记箭头 ↑↓↕",
|
||||||
|
"列展示细节用皮肤字段:align 控制表头与单元格对齐,format(value, row) 返回纯文本(走 textContent,不需要转义)",
|
||||||
"selectable: true 开启行选择:表头全选框带 indeterminate 半选态,工具栏显示已选计数",
|
"selectable: true 开启行选择:表头全选框带 indeterminate 半选态,工具栏显示已选计数",
|
||||||
"选中行用低视觉比重样式(淡色底 + 弱化文字),选择状态存 rowKey,跨页不丢失",
|
"选中行用低视觉比重样式(淡色底 + 弱化文字),选择状态存 rowKey,跨页不丢失",
|
||||||
"工具栏输入框即筛选,输入即时过滤并回第 1 页;无匹配时显示空状态行",
|
"工具栏输入框即筛选,输入即时过滤并回第 1 页;无匹配时显示空状态行",
|
||||||
|
|||||||
@@ -113,6 +113,13 @@
|
|||||||
width: 36px;
|
width: 36px;
|
||||||
text-align: center !important;
|
text-align: center !important;
|
||||||
}
|
}
|
||||||
|
/* 列对齐:表头与单元格共用,由列定义的 align 决定 */
|
||||||
|
.ua-align-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.ua-align-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
.ua-empty-row td {
|
.ua-empty-row td {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--ua-color-text-muted);
|
color: var(--ua-color-text-muted);
|
||||||
|
|||||||
Reference in New Issue
Block a user