docs(skills): 更新表格技能文档覆盖受控模式与模板协议

This commit is contained in:
augushong
2026-09-11 13:09:09 +00:00
parent 0708afddf0
commit 86e114ccd5

View File

@@ -1,6 +1,6 @@
---
name: "ulthon-admin-table"
description: "后台列表页表格机制与定制完整指南:核心机制(ua 命名空间、三件套)、字段配置、内置渲染器、按钮系统(含页面分类标准 A/B/C/D)、搜索系统(前端配置 + 后端 buildTableParames)、后端配套、常见模式与排错。需要业务定制 CURD 生成的列表页(加按钮、改字段、调搜索)或排查按钮/搜索不生效时调用。"
description: "后台列表页表格机制与定制完整指南:核心机制(ua 命名空间、三件套)、字段配置、内置渲染器、按钮系统(含页面分类标准 A/B/C/D)、搜索系统(前端配置 + 后端 buildTableParames)、后端配套、常见模式与排错、多形态渲染(受控模式 controlled、layoutList 五级模板协议 tplFunction/tplLay/tplVue/tplLayId/tplVueId、卡片形态、渲染上下文)。需要业务定制 CURD 生成的列表页(加按钮、改字段、调搜索)、排查按钮/搜索不生效、或自定义卡片/模板形态时调用。"
---
# 后台表格机制与定制(ulthon-admin-table)
@@ -16,6 +16,7 @@ CURD 生成器生成列表页骨架后,所有业务定制(按钮、字段渲
- 自定义字段渲染(图片、状态、关联数据)
- 实现状态流转按钮(带 `_if` 显隐)
- 跨控制器跳转按钮
- 列表自定义卡片/模板形态(`controlled` + `layoutList`,见第十一章)
## 一、核心机制总览
@@ -25,9 +26,10 @@ CURD 生成器生成列表页骨架后,所有业务定制(按钮、字段渲
| 文件 | 作用 |
|------|------|
| `admin-table.js` (1650 行) | 表格核心:render / formatCols / renderOperat / 内置渲染器 / 搜索监听 |
| `admin-table.js` | 表格核心:render / formatCols / renderOperat / 内置渲染器 / 搜索监听 / 受控模式接线与多形态渲染(reloadData shim、五级模板协议、渲染上下文) |
| `admin-table-store.js` | 受控模式数据层(全局 `window.UA_TABLE_STORE`):dataProvider / pager / selection / reload 四模块 |
| `admin-core.js` | ua 全局组装、headers、url、parame 等工具 |
| `admin-utils.js` | `ua.getDataBrage` 等数据读取工具 |
| `admin-utils.js` | `ua.getDataBrage`、`ua.checkMobile`、`ua.getPageSetting` 等工具 |
| `admin-listen.js` | 事件监听(`ua.listen`) |
| `admin-api.js` | AJAX 请求封装(`ua.request`) |
| `ulthon-admin.js` | 入口(把 UA_ADMIN 挂到 window.ua) |
@@ -650,6 +652,163 @@ CURD 生成时会将关联模型的全部字段(如 `systemAdmin.*`)输出
| 实际示例:标准 CRUD | `extend/base/admin/view/system/admin/index.js` |
| 实际示例:`_if` 条件按钮 | `app/admin/view/system/timer_config/index.js` |
| 实际示例:自定义 templet | `app/admin/view/system/timer_log/index.js` |
| `controlled` 受控分支接线 | `admin-table.js:914`(注册 store、自建分页与模板容器、首屏取数) |
| reloadData shim(受控分流/透传) | `admin-table.js:21` |
| `ua.table.reloadData` / `ua.table.checkStatus` 分发入口 | `admin-table.js:373` / `:379` |
| store 五入口(register/has/reload/fetch/checkStatus) | `admin-table-store.js:531` |
| layoutList 规范化(字符串/对象条目) | `admin-table.js:69`(normalizeLayoutList) |
| 五级钩子优先级解析 | `admin-table.js:194`(resolveLayoutEngine) |
| 内置 card 模板(tplFunction) | `admin-table.js:112`(builtinCardTemplate) |
| 渲染上下文构建器 | `admin-table.js:394`(buildLayoutContext) |
| tplFunction/laytpl/Vue 调用约定注释 | `admin-table.js:466-508` |
| renderLayoutForm(模板管线入口) | `admin-table.js:511` |
| 统一行选中委托(data-table-id + data-id) | `admin-table.js:158` |
| getLayoutForm / setLayoutForm | `admin-table.js:670` / `:678` |
| 默认形态解析(layoutDefault/checkMobile/记忆) | `admin-table.js:943` |
| 视图切换按钮注入(条目 > 1) | `admin-table.js:732`,记忆写入 `:2025` |
| `ua.checkMobile` 判定 | `admin-utils.js:6`,实现 `public/static/common/js/app.js:63` |
| 实际示例:名片形态(Vue 模板) | `extend/base/admin/view/system/admin/index.html:19` + `index.js:10` |
## 十一、多形态渲染(受控模式与自定义模板)
列表除了引擎表格形态,还能以卡片等自定义模板形态渲染。模板形态运行在受控模式之上:数据由框架数据层接管,模板只负责"一行/一屏数据长什么样"。官方完整范例是用户管理的名片形态(`extend/base/admin/view/system/admin/index.html:19` 的 usercardTpl 节点 + `extend/base/admin/view/system/admin/index.js:10` 的 layoutList 配置)。
### 11.1 受控模式
`ua.table.render` 的 options 加 `controlled: true` 即开启。受控后页面数据由 `admin-table-store.js`(全局 `window.UA_TABLE_STORE`)接管:引擎不再自己发请求,首屏、搜索、排序、翻页、switch 回滚、删除刷新等所有数据重载统一走 reloadData 的 shim 分流进 store。
| 组件 | 说明 |
|------|------|
| shim(`table.reloadData` 包装) | 受控表(store 已注册该 id)→ `store.reload`;非受控表原样透传引擎(admin-table.js:21) |
| `ua.table.reloadData(id, opts)` | 页面代码刷新表格的推荐入口:按受控与否自动分流,受控/非受控页面通吃(admin-table.js:373) |
| `ua.table.checkStatus(id)` | 取选中行的推荐入口,同样自动分流(admin-table.js:379) |
旧页面里直呼 `layui.table.reloadData` / `layui.table.checkStatus` 的代码仍会被 shim 覆盖到,但新代码请统一用 `ua.table.reloadData` / `ua.table.checkStatus`。
store 内部四个模块(admin-table-store.js):
| 模块 | 职责 |
|------|------|
| `dataProvider` | 取数(fetch)与状态:where / sort / 当页数据 / count |
| `pager` | laypage 封装;受控表分页条由框架自建(`.ua-store-pager`),页面不用管 |
| `selection` | 选中集合:跨页保持勾选,与引擎勾选是同一事实源 |
| `reload` | 重载编排:合并 where/page 后 fetch,成功回调里按当前形态分发渲染 |
对外五个主入口 `register / has / reload / fetch / checkStatus`(admin-table-store.js:531)。业务页面一般不直接调 store,用上表两个 `ua.table.*` 分发入口就够了。
受控表在引擎视图与分页条之间有一个模板渲染容器 `#ua-layout-{tableId}`,模板形态的内容渲染在这里,与引擎数据区互斥显隐;工具栏与搜索区两形态共用(卡片形态下新增/批量删除/搜索照常可用)。
### 11.2 五级模板协议(layoutList)
`layoutList` 声明表格支持的形态清单,条目有两种写法:
```js
// 字符串条目:'table'/'card' 有内置实现(标题缺省「表格」/「卡片」),
// 其他字符串以自身为标题
layoutList: ['table', 'card']
// 对象条目:自定义形态,name 必填,title 供视图切换下拉显示,icon 可选
layoutList: ['table', {name: 'usercard', title: '名片', tplVueId: '#usercardTpl'}]
```
**模板钩子五级优先级**(按序取第一个命中的,提供一种即生效):
| 优先级 | 钩子 | 类型 | 引擎 |
|--------|------|------|------|
| 1 | `tplFunction` | function | 框架行循环,`(行上下文, 列表上下文)` 返回单行 HTML 字符串 |
| 2 | `tplLay` | string | laytpl 模板字符串,行级作用域 |
| 3 | `tplVue` | string | Vue 模板字符串,列表级单实例 |
| 4 | `tplLayId` | string | `<script type="text/html">` 节点选择器(laytpl) |
| 5 | `tplVueId` | string | `<script type="text/x-template">` 节点选择器(Vue) |
- 五种钩子全无且条目不是 `table`:console.warn(每条目仅一次)并回退表格形态
- 内置 `card` 本质是一个框架内置的 tplFunction 条目(header/body/footer 卡片骨架,样式类 `.ua-data-card` 族);条目自带任意钩子时完全覆盖内置实现
- 模板渲染失败(节点缺失、laytpl 错误、Vue 未引入或挂载失败)自动 console.error 并回退表格形态,不白屏;回退不写记忆,修复后可再切换
**默认形态与视图切换**:
- 默认 `table`。解析优先级:localStorage 按页记忆 > 显式 `layoutDefault` 或手机端自动 card > `table`(admin-table.js:943)
- `layoutList` 条目数 > 1 时,工具栏自动注入「视图」按钮;点击下拉条目 = 写记忆 + 切形态,切换用的是 store 当页数据,零网络请求
- 记忆键 `table-layout-default`(经 `ua.getPageSetting` 按页加前缀存储),运行期 API:`ua.table.getLayoutForm(id)` 读当前形态、`ua.table.setLayoutForm(id, name)` 切形态
**两条硬约束**:
1. 模板渲染容器恒带 `layui-form` 类与 lay-filter(框架自建容器已保证,页面无需关心)。表单控件的渲染美化与事件都要求 `.layui-form` 祖先,模板里写 switch/checkbox 依赖这条保证,缺失则全部失效。
2. laytpl/Vue 的模板节点(`<script type="text/html">` / `text/x-template`)**必须写在渲染容器之外**(页面根容器之后即可)。节点若写进容器内会被模板引擎编译时吞掉。这是使用者必须遵守的约束:框架不生成也不搬运模板节点。
### 11.3 渲染上下文
自定义模板拿到的数据由 `ua.table.buildLayoutContext(tableId)` 构建(调试时可直接在控制台调用查看)。
**行上下文**(tplFunction 第一参 / laytpl 的 `d` / Vue 的 `v-for` 项):
| 字段 | 说明 |
|------|------|
| `...row` | 行原始字段全部可用(username、status、create_time 等) |
| `_cells` | 对象,field → 该列 templet 渲染后的 HTML。只有配了渲染器的列才有内容;纯文本列恒为空串,勿引用,直接用原始字段 |
| `_operatHtml` | 操作列渲染产物(编辑/删除等按钮 HTML,已按 data-auth-* 裁剪权限) |
| `_checked` | 该行是否选中(store 选中集合) |
| `_disabled` | 该行是否禁选(行数据 LAY_DISABLED) |
**列表上下文**(tplFunction 第二参):`{tableId, rows, page, count, checkedAll}`。laytpl 形态挂在 `d._list` 上;Vue 形态叫 `list`,因为 Vue 实例不代理 `_`/`$` 前缀的数据键,裸写 `_list` 会 ReferenceError。
### 11.4 自定义形态示例
**laytpl 形态(tplLayId)**,简版:
```html
<!-- 页面模板区:渲染容器之外 -->
<script type="text/html" id="myCardTpl">
<div class="my-card">
<h3>{{- d._cells.title }}</h3>
<div>{{- d._operatHtml }}</div>
{{# if (d._checked) { }}<i class="layui-icon layui-icon-ok-circle"></i>{{# } }}
</div>
</script>
```
```js
layoutList: ['table', {name: 'mycard', title: '自定义卡', tplLayId: '#myCardTpl'}],
```
laytpl 语法实测(layui 2.10.1 内置 laytpl v2.0.0):`{{ d.x }}` 与 `{{= d.x }}` 都会 HTML 转义,`{{- d.x }}` 才是原样输出。`_cells`/`_operatHtml` 是渲染产物 HTML,必须用 `{{- }}`;纯文本用 `{{ }}` 转义更安全;`{{# }}` 写 JS 语句;列表上下文是 `d._list`。
**Vue 形态(tplVueId)**,简版:
```html
<!-- 页面模板区:渲染容器之外 -->
<script type="text/x-template" id="myVueTpl">
<div class="my-card" v-for="d in rows" :key="d.id"
:data-id="d.id" :data-table-id="list.tableId"
:class="{'ua-data-card-checked': d._checked}">
<h3 v-html="d._cells.title"></h3>
<div v-html="d._operatHtml"></div>
</div>
</script>
```
```js
layoutList: ['table', {name: 'mycard', title: '自定义卡', tplVueId: '#myVueTpl'}],
```
- `v-for="d in rows"` 承接行循环;`_cells`/`_operatHtml` 必须 `v-html` 输出,纯文本字段用 `{{ }}` 原生插值(自动转义);列表上下文是 `list`
- 行根元素带 `data-table-id` + `data-id` 两属性即获得统一行选中委托:点卡片切换勾选、选中态经 `:class` 绑 `ua-data-card-checked` 高亮回显;按钮/表单控件自动豁免不误触,零 JS
- 事件无需自己绑定:操作按钮(data-open/data-request 等属性)与 switch/checkbox 经页面级委托与 form 事件天然生效;`@click` 可用,但处理函数内不要修改模板数据(渲染上下文被冻结,数据更新一律整卡销毁重建)
**完整官方范例**:用户管理名片卡片,接入面只有两处,建议直接对照源码:
| 位置 | 内容 |
|------|------|
| `extend/base/admin/view/system/admin/index.html:19` | usercardTpl 模板节点 + 页面级网格样式 |
| `extend/base/admin/view/system/admin/index.js:10` | layoutList 一行接入(`controlled: true` 在第 5 行) |
该范例演示了三种典型取值通道:渲染器列(status)用 `_cells` + v-html 承接框架语义、纯文本列(username/create_time/phone)直接原始字段插值、头像(head_img)因名片需要圆形大图绕开渲染器直接绑原始字段。模板形态的网格布局由模板自带容器承担(Vue 形态不带内置卡片网格类)。
### 11.5 手机端默认行为
- 手机端判定 `ua.checkMobile()`(admin-utils.js:6,实现 public/static/common/js/app.js:63):UA 关键字(Android/iPhone/iPad 等)或 `window.screen` 宽 < 600 且高 < 800。判的是物理屏幕指标,**浏览器开发者工具仅改视口宽度不会触发**;设备仿真验证手机默认形态时,必须同时覆写 UA 或仿真 screen 尺寸,仅设 viewport 无效
- `layoutList` 含 `card` 条目时,手机端默认进入 card 形态;localStorage 记忆值优先级更高(记忆值不在 layoutList 内时忽略)
- 卡片形态能力边界:工具栏(刷新/新增/批量删除/导出)与搜索区两形态共用、照常可用;勾选语义由卡片勾位图标与选中委托承接(checkbox 列本身不进模板上下文);`_disabled` 行整元素不可勾选
## 相关技能