From 20290f67a4077ba8f3ef57847b786b535a2c7ff5 Mon Sep 17 00:00:00 2001 From: HIsheR Date: Tue, 27 Oct 2026 02:03:09 +0800 Subject: [PATCH] =?UTF-8?q?iter(g330):=20=E8=A1=A8=E6=A0=BC=E5=88=97?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=87=AA=E5=AE=9A=E4=B9=89=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=A0=BC=E6=B8=B2=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ua/backlog.json | 9 +++++++++ .ua/index.json | 5 +++-- apps/demo/main.ts | 2 +- llms-full.txt | 7 ++++--- packages/skin/ui/src/dataTable.ts | 4 ++-- packages/skin/ui/src/types.ts | 1 + packages/skin/ui/ua.manifest.json | 3 ++- 7 files changed, 22 insertions(+), 9 deletions(-) diff --git a/.ua/backlog.json b/.ua/backlog.json index 6db25bc..c6240da 100644 --- a/.ua/backlog.json +++ b/.ua/backlog.json @@ -3356,6 +3356,15 @@ "motivation": "库里有 textField / textarea,却没有「就地改一段已有文字」的基础件:看板标题改名、表格单元格、详情页字段这类场景只能把输入框常驻在页面上(视觉噪音大),或者自己写一套「点击变输入框 + Enter 提交 + Esc 取消」——每个业务都重写一遍,行为还不一致(多数忘了 Esc 不落值、忘了失焦提交、忘了校验失败要留在编辑态)。", "acceptance": "① 新增 editable 组件:静止态是可点文字按钮(hover 才露虚线暗示),点击 / 回车进编辑态,Enter 提交、Esc 取消(绝不写回)、失焦提交,多行模式 Ctrl/Cmd+Enter 提交、Enter 换行;② validate 返回文案时保持编辑态、控件 aria-invalid + 下方 role=alert 文案(与 form 同口径);③ disabled 支持布尔与信号双口径;外部改值只同步静止态,不打断正在编辑的草稿;④ 样式只消费 --ua-* 令牌(禁新增硬编码阴影 / 时长 / 字号),audit:visual 计数不涨;⑤ 十个落点齐全(实现 / 类型 / 导出 / 样式 / 清单 exports / docs.components 五步 / demo 段 / README 清单与分组 / 计数同步),组件数 89 → 90,npm run check 全绿。", "notes": "" + }, + { + "id": "g330", + "domain": "tune", + "status": "open", + "title": "表格单元格支持自定义渲染:标签 / 头像 / 行内编辑塞得进去", + "motivation": "dataTable 的单元格只有 format 一条路(纯文本),状态列只能是「已通过」四个字而不是 tag,头像列只能是 URL,editable 刚做出来却放不进表格——写实业务里表格是主战场,这一格塞不进组件,整套组件库在详情页能用、在列表页就退化成纯文本。", + "acceptance": "① TableColumn 增可选 render: (row) => unknown:传了即取代 format 决定这一格画什么,排序 / 筛选 / 导出仍按原值走;② 自定义单元格拦下点击冒泡,点格里的按钮不会顺带触发 onRowClick(整行可点时尤其重要);③ 不传 render 的列行为零变化(format 走 textContent,天然免注入);④ manifest 的列定义与步骤同步说明,demo 城市列改成标签渲染并在段落里点明格内点击不触发整行;⑤ npm run check 全绿。", + "notes": "" } ] } diff --git a/.ua/index.json b/.ua/index.json index 015f378..b46128a 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-26T17:59:51.593Z", + "generatedAt": "2026-10-26T18:02:35.274Z", "repoRoot": ".", "packages": [ { @@ -2067,7 +2067,7 @@ "n": "columns", "t": "ColumnDef[]", "r": 1, - "d": "列定义:内核字段 { key, header?, sortable?, width? } + 皮肤字段 { align?, format? };width 数字为固定像素,'flex' 自适应" + "d": "列定义:内核字段 { key, header?, sortable?, width? } + 皮肤字段 { align?, format?, render? };width 数字为固定像素,'flex' 自适应" }, { "n": "filterKey", @@ -4821,6 +4821,7 @@ "点文字进编辑态:Enter 提交、Esc 取消、点别处(失焦)提交;多行模式要 Ctrl / Cmd + Enter 才提交,Enter 留给换行——否则多行里连换行都写不了", "validate 返回文案时保留编辑态并标红:控件带 aria-invalid、下方 role=alert 出文案,与 form 的出错口径一致,用户改对再提交即可", "disabled 接受布尔或信号:禁用时静止态是灰字、点了不进编辑,适合权限只读的场景(要完全不给「可改」暗示就直接渲染文字)", + "单元格里要塞组件(editable 就地编辑、tag 状态标记、avatar 头像、progress 完成度)时给列传 render: (row) => 节点:它直接取代 format 决定这一格画什么,排序与筛选仍按 dataKey 的原值走;格内的点击会被拦下,不会顺带触发 onRowClick(点单元格里的按钮不该等于点整行)", "什么时候别用:长表单(用 form + textField,editable 没有 label 排版与必填标记)、需要撤销栈的富文本,以及值会被频繁外部改写的地方(编辑中不跟随外部更新,是有意的取舍)" ] } diff --git a/apps/demo/main.ts b/apps/demo/main.ts index 1f35cf1..3b72bff 100644 --- a/apps/demo/main.ts +++ b/apps/demo/main.ts @@ -1498,7 +1498,7 @@ const content = h( columns: [ { key: 'name', header: '姓名', sortable: true, width: 160 }, { key: 'age', header: '年龄', sortable: true, align: 'right', width: 100, format: (v) => `${v} 岁` }, - { key: 'city', header: '城市', align: 'center', width: 'flex' }, + { key: 'city', header: '城市', align: 'center', width: 'flex', render: (row) => tag({ label: row.city, variant: row.city === '上海' ? 'info' : 'default' }) }, // 自定义单元格:本列画标签而非纯文本,格内点击不触发整行回调 ], pageSize: 5, selected: picked, diff --git a/llms-full.txt b/llms-full.txt index 4927d50..90c6baf 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-26T17:59:51.593Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-26T18:02:35.274Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -1025,7 +1025,7 @@ const name = signal(''); const f = form({ onSubmit: submit }, [formItem({ label: 参数: - data(必填):T[] | Signal —— 全量行数据(行须含 id 作 rowKey);传信号或 computed 时数据变化自动换源,排序 / 筛选 / 页码保持用户原意 -- columns(必填):ColumnDef[] —— 列定义:内核字段 { key, header?, sortable?, width? } + 皮肤字段 { align?, format? };width 数字为固定像素,'flex' 自适应 +- columns(必填):ColumnDef[] —— 列定义:内核字段 { key, header?, sortable?, width? } + 皮肤字段 { align?, format?, render? };width 数字为固定像素,'flex' 自适应 - filterKey:string —— 工具栏筛选的列;缺省取第一列 - pageSize(默认 5):number —— 每页行数;缺省 5 - pageSizes(默认 [5, 10, 20]):number[] —— 分页条「每页条数」候选档;缺省 [5, 10, 20],当前 pageSize 不在候选里会自动补进去 @@ -2587,7 +2587,8 @@ const title = signal('季度目标'); editable({ value: title, label: '标题', 2. 点文字进编辑态:Enter 提交、Esc 取消、点别处(失焦)提交;多行模式要 Ctrl / Cmd + Enter 才提交,Enter 留给换行——否则多行里连换行都写不了 3. validate 返回文案时保留编辑态并标红:控件带 aria-invalid、下方 role=alert 出文案,与 form 的出错口径一致,用户改对再提交即可 4. disabled 接受布尔或信号:禁用时静止态是灰字、点了不进编辑,适合权限只读的场景(要完全不给「可改」暗示就直接渲染文字) -5. 什么时候别用:长表单(用 form + textField,editable 没有 label 排版与必填标记)、需要撤销栈的富文本,以及值会被频繁外部改写的地方(编辑中不跟随外部更新,是有意的取舍) +5. 单元格里要塞组件(editable 就地编辑、tag 状态标记、avatar 头像、progress 完成度)时给列传 render: (row) => 节点:它直接取代 format 决定这一格画什么,排序与筛选仍按 dataKey 的原值走;格内的点击会被拦下,不会顺带触发 onRowClick(点单元格里的按钮不该等于点整行) +6. 什么时候别用:长表单(用 form + textField,editable 没有 label 排版与必填标记)、需要撤销栈的富文本,以及值会被频繁外部改写的地方(编辑中不跟随外部更新,是有意的取舍) 现场演示:apps/docs/demo.html#sec-editable diff --git a/packages/skin/ui/src/dataTable.ts b/packages/skin/ui/src/dataTable.ts index 8f3e4c9..7f3c545 100644 --- a/packages/skin/ui/src/dataTable.ts +++ b/packages/skin/ui/src/dataTable.ts @@ -133,8 +133,8 @@ export function dataTable(props: DataTableProps): H 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 只产出纯文本,天然免注入 - return h('td', col.align ? { class: `ua-align-${col.align}` } : null, text); + const custom = col.render?.(row); // 自定义单元格:调用方直接给节点(editable / tag / avatar),排序与筛选仍走原值 + return h('td', { class: col.align ? `ua-align-${col.align}` : null, onclick: custom ? (e: MouseEvent) => e.stopPropagation() : null }, custom ?? (col.format ? col.format(raw, row) : String(raw))); // 无 render 退回纯文本(免注入);格内点击不触发整行回调 }); if (selectable) { const check = h('input', { type: 'checkbox', class: 'ua-checkbox' }) as HTMLInputElement; // 受控:选中态由内核 selection 决定 diff --git a/packages/skin/ui/src/types.ts b/packages/skin/ui/src/types.ts index 6d63b87..c52a825 100644 --- a/packages/skin/ui/src/types.ts +++ b/packages/skin/ui/src/types.ts @@ -57,6 +57,7 @@ export interface CardProps { export interface TableColumn extends ColumnDef { align?: 'left' | 'center' | 'right'; // 表头与单元格对齐;缺省 left format?: (value: unknown, row: T) => string; // 单元格文案格式化(返回纯文本,走 textContent 渲染) + render?: (row: T) => unknown; // 自定义单元格内容(DOM / 字符串):传了则 format 不生效,排序筛选仍按原值,格内点击不再触发整行回调 } /*** 数据表格入参 ***/ diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index 72fce73..544838f 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -1937,7 +1937,7 @@ "n": "columns", "t": "ColumnDef[]", "r": 1, - "d": "列定义:内核字段 { key, header?, sortable?, width? } + 皮肤字段 { align?, format? };width 数字为固定像素,'flex' 自适应" + "d": "列定义:内核字段 { key, header?, sortable?, width? } + 皮肤字段 { align?, format?, render? };width 数字为固定像素,'flex' 自适应" }, { "n": "filterKey", @@ -4658,6 +4658,7 @@ "点文字进编辑态:Enter 提交、Esc 取消、点别处(失焦)提交;多行模式要 Ctrl / Cmd + Enter 才提交,Enter 留给换行——否则多行里连换行都写不了", "validate 返回文案时保留编辑态并标红:控件带 aria-invalid、下方 role=alert 出文案,与 form 的出错口径一致,用户改对再提交即可", "disabled 接受布尔或信号:禁用时静止态是灰字、点了不进编辑,适合权限只读的场景(要完全不给「可改」暗示就直接渲染文字)", + "单元格里要塞组件(editable 就地编辑、tag 状态标记、avatar 头像、progress 完成度)时给列传 render: (row) => 节点:它直接取代 format 决定这一格画什么,排序与筛选仍按 dataKey 的原值走;格内的点击会被拦下,不会顺带触发 onRowClick(点单元格里的按钮不该等于点整行)", "什么时候别用:长表单(用 form + textField,editable 没有 label 排版与必填标记)、需要撤销栈的富文本,以及值会被频繁外部改写的地方(编辑中不跟随外部更新,是有意的取舍)" ] }