diff --git a/.agents/skills/ulthon-admin-table/SKILL.md b/.agents/skills/ulthon-admin-table/SKILL.md index 19570a6..485a847 100644 --- a/.agents/skills/ulthon-admin-table/SKILL.md +++ b/.agents/skills/ulthon-admin-table/SKILL.md @@ -1,6 +1,6 @@ --- name: "ulthon-admin-table" -description: "后台列表页表格机制与定制完整指南:核心机制(ua 命名空间、三件套)、字段配置、内置渲染器、按钮系统(含页面分类标准 A/B/C/D)、搜索系统(前端配置 + 后端 buildTableParames)、后端配套、常见模式与排错、多形态渲染(受控默认数据流与 controlled:false 逃生口、layoutList 五级模板协议 tplFunction/tplLay/tplVue/tplLayId/tplVueId、双端自动注入、按端 layoutDefault、卡片形态、渲染上下文)。需要业务定制 CURD 生成的列表页(加按钮、改字段、调搜索)、排查按钮/搜索不生效、自定义卡片/模板形态、或判断页面是否需要 controlled:false 逃生口时调用。" +description: "后台列表页表格机制与定制完整指南:核心机制(ua 命名空间、三件套)、字段配置、内置渲染器、按钮系统(含页面分类标准 A/B/C/D)、搜索系统(前端配置 + 后端 buildTableParames)、后端配套、常见模式与排错、多形态渲染机制(受控默认数据流与 controlled:false 逃生口、layoutList 五级模板协议 tplFunction/tplLay/tplVue/tplLayId/tplVueId、双端自动注入、layoutDefault 按端默认与记忆链、卡片形态)。需要业务定制 CURD 生成的列表页(加按钮、改字段、调搜索)、排查按钮/搜索不生效、配置 layoutList/layoutDefault/视图切换,或判断页面是否需要 controlled:false 逃生口时调用;自定义卡片模板的创作(上下文契约、实战配方、设计模式、坑与验收)见 ulthon-card-template 技能。" --- # 后台表格机制与定制(ulthon-admin-table) @@ -16,7 +16,7 @@ CURD 生成器生成列表页骨架后,所有业务定制(按钮、字段渲 - 自定义字段渲染(图片、状态、关联数据) - 实现状态流转按钮(带 `_if` 显隐) - 跨控制器跳转按钮 -- 列表自定义卡片/模板形态与表格/卡片切换(`layoutList` + `layoutDefault`,见第十一章) +- 列表多形态机制与表格/卡片切换(`layoutList` + `layoutDefault`,见第十一章;自定义卡片模板的创作见 [ulthon-card-template](./ulthon-card-template/SKILL.md) 技能) - 判断页面走的是哪种数据流,或是否需要 `controlled: false` 逃生口(见第十一章) ## 一、核心机制总览 @@ -219,7 +219,7 @@ var init = { ### 5.1 顶部工具栏(toolbar) -通过 `ua.table.render({ toolbar: [...] })` 配置。**不声明时默认 `['refresh', 'add', 'delete', 'export']`**——这意味着只读页面如果不覆盖,会显示无用的"新增/删除"按钮(见 9.2 常见错误)。 +通过 `ua.table.render({ toolbar: [...] })` 配置。**不声明时默认 `['refresh', 'add', 'delete', 'export']`**,这意味着只读页面如果不覆盖,会显示无用的"新增/删除"按钮(见 9.2 常见错误)。 **字符串简写**(自动检查权限): @@ -256,7 +256,7 @@ ua.table.render({ ### 5.2 行级操作按钮(operat) -在 `cols` 最后一列配置 `templet: ua.table.tool` + `operat` 数组。**不声明 operat 时,tool 函数内自动填充 `['edit', 'delete']`**(admin-table.js:1055)。 +在 `cols` 最后一列配置 `templet: ua.table.tool` + `operat` 数组。**不声明 operat 时,tool 函数内自动填充 `['edit', 'delete']`**。 ```js { @@ -393,7 +393,7 @@ operat: [ ] ``` -每个 `[]` 内的对象是独立按钮,按顺序渲染。`renderOperat`(admin-table.js:769)做权限整列裁剪:只要任意一个按钮组内有按钮有权限,整列就保留。 +每个 `[]` 内的对象是独立按钮,按顺序渲染。`renderOperat`(定位见第十章速查表)做权限整列裁剪:只要任意一个按钮组内有按钮有权限,整列就保留。 ## 六、页面分类标准(A/B/C/D) @@ -619,37 +619,36 @@ CURD 生成时会将关联模型的全部字段(如 `systemAdmin.*`)输出 ## 十、实测代码引用表 -每个特性标注实测来源(多为文件:行;受控/多形态相关特性因行号随迭代漂移,改用 grep 关键词定位),若代码与本文档冲突,以代码为准。 +每个特性标注实测来源(文件 + grep 关键词定位;行号随迭代漂移,故不逐条标注行号),若代码与本文档冲突,以代码为准。 | 特性 | 实测来源 | |------|---------| -| `ua.table.render` 入口 | `public/static/plugs/ulthon-admin/admin-table.js:14` | -| `formatCols`(cols 加工) | `admin-table.js:921` | -| `renderOperat`(操作列整列裁剪) | `admin-table.js:769` | -| `renderToolbar`(顶部工具栏) | `admin-table.js:251` | -| `renderSearch`(搜索表单生成) | `admin-table.js:294` | -| `search` 默认 `true` | `admin-table.js:307`(`ua.parame(d.search, true)`) | -| `searchOp` 默认 `'%*%'` | `admin-table.js:312` | -| `search: 'number_limit'` / `'time_limit'` | `admin-table.js:348-369` | -| `listenTableSearch`(filter+op 构造) | `admin-table.js:1334` | -| `tool` 函数(自动填充默认 operat) | `admin-table.js:1053`,自动填充在 1055 | -| `tool` 内置 'edit' 处理 | `admin-table.js:1061` | -| `tool` 内置 'delete' 处理 | `admin-table.js:1082` | -| `operat._if` 显隐判定 | `admin-table.js:1113-1124` | -| `operat.auth` 默认 `'add'` | `admin-table.js:1105` | -| `list` 渲染器 | `admin-table.js:1169` | -| `filePreview` 渲染器 | `admin-table.js:1180` | -| `image` 渲染器(imageSplit/imageJoin) | `admin-table.js:1195-1213` | -| `url` 渲染器(urlNameField) | `admin-table.js:1215` | -| `switch` 渲染器(自动调 modify) | `admin-table.js:1245`,modify 调用在 `listenSwitch:1365` | -| `date` 渲染器 | `admin-table.js:1281` | -| `ua.getDataBrage` 实现 | `public/static/plugs/ulthon-admin/admin-utils.js:63-74` | +| `ua.table.render` 入口 | `public/static/plugs/ulthon-admin/admin-table.js`(`grep "render: function (options)"`) | +| `formatCols`(cols 加工) | `admin-table.js`(`grep "formatCols: function"`) | +| `renderOperat`(操作列整列裁剪) | `admin-table.js`(`grep "renderOperat(data, elem)"`) | +| `renderToolbar`(顶部工具栏) | `admin-table.js`(`grep "renderToolbar: function"`) | +| `renderSearch`(搜索表单生成) | `admin-table.js`(`grep "renderSearch: function"`) | +| `search` 默认 `true` | `admin-table.js`(`grep "d.search, true"`) | +| `searchOp` 默认 `'%*%'` | `admin-table.js`(`grep "d.searchOp"`) | +| `search: 'number_limit'` / `'time_limit'` | `admin-table.js`(`grep "number_limit"`) | +| `listenTableSearch`(filter+op 构造) | `admin-table.js`(`grep "listenTableSearch: function"`) | +| `tool` 函数(自动填充默认 operat) | `admin-table.js`(`grep "operat = option.operat"`,tool 函数内) | +| `tool` 内置 'edit' / 'delete' 处理 | 同上 tool 函数内 `case 'edit'` / `case 'delete'` 分支 | +| `operat._if` 显隐判定 | `admin-table.js`(`grep "operat._if"`) | +| `operat.auth` 默认 `'add'` | `admin-table.js`(`grep "operat.auth = operat.auth \|\|"`) | +| `list` 渲染器 | `admin-table.js`(`grep "list: function"`) | +| `filePreview` 渲染器 | `admin-table.js`(`grep "filePreview: function"`) | +| `image` 渲染器(imageSplit/imageJoin) | `admin-table.js`(`grep "image: function"`) | +| `url` 渲染器(urlNameField) | `admin-table.js`(`grep "url: function"`) | +| `switch` 渲染器(自动调 modify) | `admin-table.js`(`grep "switch: function"`),modify 调用在 `grep "listenSwitch: function"` | +| `date` 渲染器 | `admin-table.js`(`grep "date: function"`) | +| `ua.getDataBrage` 实现 | `admin-utils.js`(`grep "ua.getDataBrage = function"`) | | `#data-brage` 隐藏元素 | `extend/base/admin/view/layout/default.html`(layout 文件) | -| `data_brage` assign | `extend/base/common/controller/AdminControllerBase.php:273` | -| `buildTableParames`(filter+op 解析) | `extend/base/common/controller/AdminControllerBase.php:405` | -| `$relationSearch` 属性 | `AdminControllerBase.php:69`(默认 false) | -| `$selectWhere` 属性 | `AdminControllerBase.php:63` | -| `selectList()` 方法 | `AdminControllerBase.php:489` | +| `data_brage` assign | `AdminControllerBase.php`(`grep "assign('data_brage'"`) | +| `buildTableParames`(filter+op 解析) | `AdminControllerBase.php`(`grep "function buildTableParames"`) | +| `$relationSearch` 属性 | `AdminControllerBase.php`(`grep "protected $relationSearch"`,默认 false) | +| `$selectWhere` 属性 | `AdminControllerBase.php`(`grep "protected $selectWhere"`) | +| `selectList()` 方法 | `AdminControllerBase.php`(`grep "public function selectList"`) | | 实际示例:标准 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` | @@ -671,12 +670,13 @@ CURD 生成时会将关联模型的全部字段(如 `systemAdmin.*`)输出 | renderLayoutForm(模板管线入口) | `grep "renderLayoutForm: function" admin-table.js` | | 统一行选中委托(data-table-id + data-id) | `grep "统一行选中委托" admin-table.js` | | getLayoutForm / setLayoutForm | `grep "getLayoutForm" admin-table.js` | +| 内置卡片公共样式(`.ua-data-card` 族) | `grep "ua-data-card" public/static/admin/css/public.css` | | `ua.checkMobile` 判定 | `grep "checkMobile" public/static/common/js/app.js`(`admin-utils.js` 转发) | -| 实际示例:三形态 + 按端默认 | `extend/base/admin/view/system/admin/index.js`(render 配置块:layoutList 三形态 + layoutDefault 对象式)+ `index.html`(usercardTpl 模板节点) | +| 实际示例:三形态 + 按端默认 | `extend/base/admin/view/system/admin/index.js`(render 配置块:layoutList 三形态 + layoutDefault 对象式)+ `index.html`(usercardTpl 模板节点);第二官方范例 `extend/base/admin/view/mall/goods/`(goodscardTpl + `layoutDefault: 'goodscard'` 字符串写法) | -## 十一、多形态渲染(受控数据流与自定义模板) +## 十一、多形态渲染机制(受控数据流与形态协议) -`ua.table.render` 页面默认运行在受控数据流上,并默认获得表格/卡片两种形态,两者都是零配置即得。开发者只需要回答两个问题:想定制形态怎么配(`layoutList` + `layoutDefault`,见 11.2),以及自己的页面是否属于少数需要逃生口的场景(`controlled: false`,见 11.1)。官方完整范例是用户管理页:`extend/base/admin/view/system/admin/index.js` 顶部 render 配置里的 layoutList 三形态与 layoutDefault 对象式按端默认,建议直接对照源码。 +`ua.table.render` 页面默认运行在受控数据流上,并默认获得表格/卡片两种形态,两者都是零配置即得。本章只讲机制:数据流与逃生口(11.1)、形态清单协议与解析链(11.2)、双端默认行为(11.3)。要为自己的页面定制专属卡片模板(适用判断、上下文契约、实战配方、设计模式库、坑与验收),见 [ulthon-card-template](./ulthon-card-template/SKILL.md) 技能。 ### 11.1 受控数据流(默认)与逃生口 @@ -763,92 +763,30 @@ layoutDefault: true, // 旧协议"自动判断",视 - `layoutList` 条目数 > 1 时工具栏出现「视图」按钮;点击下拉条目即写记忆并切形态,切换用的是 store 当页数据,零网络请求 - 页面显式配置了 `defaultToolbar` 时,视图按钮会自动补投进最终工具栏(自动注入场景下去重追加),普通开发者无需关心 -- 运行期 API:`ua.table.getLayoutForm(id)` 读当前形态、`ua.table.setLayoutForm(id, name)` 切形态 +- 运行期 API:`ua.table.getLayoutForm(id)` 读当前形态、`ua.table.setLayoutForm(id, name)` 切形态。API 切换**不写记忆**,只有点视图下拉条目才写 `ua.setPageSetting`;API 切换后刷新会回到记忆形态,这是设计行为 - **CURD 页面缺省即得双形态**:CURD 生成的列表页不含任何受控/形态配置,自动获得受控数据流 + 双端「表格/卡片」+ 视图按钮,模板零改动;PC 端缺省表格、手机端缺省卡片 **两条硬约束**: 1. 模板渲染容器恒带 `layui-form` 类与 lay-filter(框架自建容器已保证,页面无需关心)。表单控件的渲染美化与事件都要求 `.layui-form` 祖先,模板里写 switch/checkbox 依赖这条保证,缺失则全部失效。 -2. laytpl/Vue 的模板节点(` -``` - -```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 - - -``` - -```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.js` | render 配置块:layoutList 三形态 + layoutDefault 对象式按端默认(`{pc: 'table', mobile: 'card'}`) | -| `extend/base/admin/view/system/admin/index.html` | usercardTpl 模板节点(` +``` + +```js +layoutList: ['table', {name: 'mycard', title: '自定义卡', tplLayId: '#myCardTpl'}], +``` ## 四、实战配方:以商品卡为蓝本走一遍 @@ -101,10 +118,10 @@ layoutList: ['table', {name: 'usercard', title: '名片', tplVueId: '#usercardTp ### 第 1 步:index.html 写模板节点与页面级样式 -在根容器 `` 之后追加 ` @@ -130,7 +147,7 @@ layoutList: ['table', {name: 'usercard', title: '名片', tplVueId: '#usercardTp 同文件追加页面级 `