--- name: "ulthon-admin-table" description: "后台列表页表格机制与定制完整指南:核心机制(ua 命名空间、三件套)、字段配置、内置渲染器、按钮系统(含页面分类标准 A/B/C/D)、搜索系统(前端配置 + 后端 buildTableParames)、后端配套、常见模式与排错。需要业务定制 CURD 生成的列表页(加按钮、改字段、调搜索)或排查按钮/搜索不生效时调用。" --- # 后台表格机制与定制(ulthon-admin-table) 本技能是后台列表页(基于 layui table 的 `ua.table.render`)完整指南。涵盖核心机制、字段配置、按钮系统、搜索机制、后端配套,以及实战中的常见模式与排错。 CURD 生成器生成列表页骨架后,所有业务定制(按钮、字段渲染、搜索)都在本技能范畴内。生成流程本身见 [ulthon-scheme-curd-workflow](./ulthon-scheme-curd-workflow/SKILL.md)。 ## 何时调用 - 业务定制 CURD 生成的列表页(加/改字段、加按钮、调搜索) - 排查"按钮不显示"、"搜索不生效"、"字段渲染不对" - 自定义字段渲染(图片、状态、关联数据) - 实现状态流转按钮(带 `_if` 显隐) - 跨控制器跳转按钮 ## 一、核心机制总览 ### 1.1 `ua` 命名空间 `ua` = ulthon-admin 的 JS 全局对象,定义在 `public/static/plugs/ulthon-admin/`: | 文件 | 作用 | |------|------| | `admin-table.js` (1650 行) | 表格核心:render / formatCols / renderOperat / 内置渲染器 / 搜索监听 | | `admin-core.js` | ua 全局组装、headers、url、parame 等工具 | | `admin-utils.js` | `ua.getDataBrage` 等数据读取工具 | | `admin-listen.js` | 事件监听(`ua.listen`) | | `admin-api.js` | AJAX 请求封装(`ua.request`) | | `ulthon-admin.js` | 入口(把 UA_ADMIN 挂到 window.ua) | ### 1.2 表格三件套 每个 CURD 模块的列表页由三个文件配合: ``` app/admin/view// ├── index.html # 极简 HTML,仅 table 元素 + data-auth-* 权限标记 ├── _common.js # init 对象(URL 配置等) └── index.js # ua.table.render({init, cols, toolbar?}) ``` **index.html**(典型示例,仅 11 行): ```html
``` **`data-auth-X` 是按钮权限显隐的关键**:JS 里的 `auth: 'X'` 必须能在 HTML 找到对应的 `data-auth-X`,且值为 1 才显示。详见第六章。 ### 1.3 前后端链路 ``` [前端] index.js 的 cols 配置 │ ├─ search: 'select'/'range'/false ──► [前端] renderSearch 自动生成搜索表单 │ │ │ ▼ 用户提交 │ [前端] listenTableSearch 构造 {filter, op} JSON │ │ └─ templet: ua.table.xxx ──► [前端] tool/image/switch 渲染单元格 │ ▼ GET /admin//index?filter={...}&op={...} [后端] AdminControllerBase::buildTableParames() │ ├─ 解析 filter + op 为 WHERE 数组(按 op 操作符构造) ├─ relationSearch:关联表搜索时自动加 `table.field` 前缀 └─ 返回 [$page, $limit, $where, $excludes, $request_options, $group] │ ▼ [后端] 模型查询 → 分页数据 → JSON 返回 → 前端表格渲染 ``` ## 二、init 对象(`_common.js` 内容) `init` 是表格的"配置中心",在 `_common.js` 中定义,被 `index.js` 引用。 **完整字段表**: | 字段 | 类型 | 说明 | |------|------|------| | `tableElem` | string | 表格元素选择器,默认 `'#currentTable'` | | `tableRenderId` | string | 表格实例 ID,用于 reload,默认 `'currentTableRenderId'` | | `indexUrl` | string | 列表数据 URL(如 `'system.timer_config/index'`) | | `addUrl` | string \| '' | 新增页 URL,空字符串则不显示新增按钮 | | `editUrl` | string \| '' | 编辑页 URL | | `readUrl` | string \| '' | 详情页 URL | | `deleteUrl` | string \| '' | 删除接口 URL | | `exportUrl` | string \| '' | 导出接口 URL | | `modifyUrl` | string \| '' | 状态开关修改接口 URL(`switch` 渲染器自动调用) | | `formFullScreen` | 'true' \| 'false' | 表单是否全屏(字符串) | **典型示例**(`app/admin/view/system/timer_log/_common.js`): ```js var init = { tableElem: '#currentTable', tableRenderId: 'currentTableRenderId', indexUrl: 'system.timer_log/index', addUrl: '', editUrl: '', readUrl: 'system.timer_log/read', deleteUrl: '', exportUrl: '', modifyUrl: '', }; ``` 只读列表(B 类页面,详见第六章)的所有写按钮 URL 留空字符串即可。 ## 三、字段配置(cols 数组项) `ua.table.render({ cols: [[...]] })` 的 `cols` 是二维数组(layui 标准),每个列项支持以下参数。 ### 3.1 基础参数 | 参数 | 类型 | 说明 | |------|------|------| | `field` | string | 数据字段名;支持关联字段如 `appCodeBind.tube_code` | | `title` | string | 表头文字 | | `width` | number | 列宽(像素) | | `minWidth` | number | 最小列宽 | | `sort` | boolean | 是否允许排序 | | `type` | string | 特殊列:`'checkbox'`(多选)/ `'radio'`(单选) | | `fixed` | 'left' \| 'right' | 固定列 | | `edit` | 'text' | 行内编辑(更新到 `modifyUrl`) | | `hide` | boolean | 默认隐藏(用户可在列配置里开启) | ### 3.2 渲染参数 | 参数 | 类型 | 说明 | |------|------|------| | `templet` | function | 渲染函数,接收 `data` 返回 HTML。可用内置渲染器(见第五章)或自定义函数 | | `defaultValue` | string | 数据为空时的默认显示值 | ### 3.3 搜索参数(详见第七章) | 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| | `search` | boolean \| string | `true` | 搜索类型:`true`(文本)/ `false`(不搜索)/ `'select'`(下拉)/ `'number_limit'`(数值区间)/ `'time_limit'`(时间区间)/ `'range'`(范围) | | `searchOp` | string | `'%*%'` | 搜索操作符,对应后端 WHERE 构造(见 7.1) | | `selectList` | object | `{}` | 下拉选项;常用 `ua.getDataBrage('select_list_xxx')` 从页面字典读取 | | `searchUrl` | string | - | 远程下拉数据源(如 `'system.timer_log/index?selectFields=task_name'`) | | `searchValue` | string | - | 搜索框默认值 | | `defaultSearchValue` | string | - | 默认搜索值(可从 URL query 自动回填) | | `searchHide` | boolean | false | 是否默认隐藏该搜索项 | | `fieldAlias` | string | = `field` | 搜索参数别名(用于关联表搜索时指定 `table.field`) | | `searchTip` | string | `'请输入' + title` | 搜索框 placeholder | | `timeType` | string | `'datetime'` | 时间搜索组件类型 | > **注意**:`search` 默认是 `true`(每列默认参与搜索),要关闭必须显式 `search: false`。 ## 四、内置渲染器(templet 取值) 通过 `templet: ua.table.xxx` 使用框架内置渲染器。 ### 4.1 内置渲染器清单 | 渲染器 | 用途 | 关键参数 | |--------|------|---------| | `ua.table.image` | 单图/多图显示 | `imageWidth`(默认 200)/ `imageHeight`(默认 26)/ `imageSplit`(默认 `\|`)/ `imageJoin`(默认 `
`) | | `ua.table.switch` | 开关组件(自动调 modify 接口) | `filter`(默认 = field)/ `checked`(默认 1,等于该值时勾选) | | `ua.table.list` | 根据 selectList 映射显示文本 | `selectList` | | `ua.table.date` | 日期格式化 | (从字段值自动格式化) | | `ua.table.url` | 链接显示 | `urlNameField`(链接显示文字字段,可为 string 或 function) | | `ua.table.filePreview` | 文件预览(图片或图标) | (根据 mime_type 自动判断) | | `ua.table.tool` | **行操作按钮渲染器**(特殊,见第六章) | `operat`(按钮配置数组) | ### 4.2 使用示例 ```js // 图片字段(自动识别多图,按 | 分隔) {field: 'head_img', title: '头像', search: false, templet: ua.table.image} // 状态开关(自动调 modifyUrl,data 字段为 id,value 为 0/1) {field: 'status', title: '状态', search: 'select', selectList: {0: '禁用', 1: '启用'}, templet: ua.table.switch} // 状态徽章(用 list 把 0/1 映射成文字) {field: 'status', title: '状态', search: 'select', selectList: {0: '禁用', 1: '启用'}, templet: ua.table.list} // 时间字段 {field: 'create_time', title: '创建时间', search: 'range', templet: ua.table.date} ``` ### 4.3 自定义 templet 函数 ```js {field: 'is_synced', title: '同步状态', width: 100, templet: function(d) { if (d.is_synced == 1) { return '已同步'; } return '未同步'; }} ``` 自定义 templet 接收行数据 `d`,返回 HTML 字符串。 ## 五、按钮系统 后台表格有两类按钮:**顶部工具栏**(toolbar)和**行级操作按钮**(operat)。 ### 5.1 顶部工具栏(toolbar) 通过 `ua.table.render({ toolbar: [...] })` 配置。**不声明时默认 `['refresh', 'add', 'delete', 'export']`**——这意味着只读页面如果不覆盖,会显示无用的"新增/删除"按钮(见 9.2 常见错误)。 **字符串简写**(自动检查权限): | 值 | 行为 | 权限节点 | |----|------|---------| | `'refresh'` | 刷新按钮(不带权限) | - | | `'add'` | 新增按钮,打开 `init.addUrl` | `/add` | | `'delete'` | 批量删除(需配合 checkbox) | `/delete` | | `'export'` | 导出按钮,请求 `init.exportUrl` | `/export` | | `'selectConfirm'` | 选择器模式的"确定选择"按钮 | - | **自定义按钮对象**: ```js ua.table.render({ toolbar: [ 'add', // 简写可与自定义混用 [{ text: '导入', url: 'demo.index/import', method: 'open', // 见 5.3 method 表 auth: 'import', // 对应 data-auth-import class: 'layui-btn layui-btn-warm layui-btn-sm', icon: 'fa fa-upload', title: '导入数据', extend: 'data-full="true"' // 额外 HTML 属性 }] ], // ... }); ``` 自定义按钮对象参数与行按钮通用,详见 5.4。 ### 5.2 行级操作按钮(operat) 在 `cols` 最后一列配置 `templet: ua.table.tool` + `operat` 数组。**不声明 operat 时,tool 函数内自动填充 `['edit', 'delete']`**(admin-table.js:1055)。 ```js { field: '', title: '操作', width: 250, fixed: 'right', templet: ua.table.tool, operat: [ 'edit', // 字符串简写 [{ text: '设置密码', url: init.password_url, // 来自 _common.js method: 'open', auth: 'password', class: 'layui-btn layui-btn-normal layui-btn-xs', field: 'id', // URL 拼接的主键字段 titleField: 'username', // 弹窗标题用某字段值(如"设置密码 - admin") }], 'delete' ] } ``` **字符串简写**: | 值 | 行为 | 默认 method | 权限节点 | |----|------|------------|---------| | `'edit'` | 编辑按钮 | `'tab'`(标签页打开) | `/edit` | | `'delete'` | 删除按钮(带确认) | `'get'` | `/delete` | ### 5.3 method 方法表 | method | 行为 | 适用场景 | |--------|------|----------| | `'tab'` | 主内容区导航(layuimini-content-href) | 详情页、编辑页(全页面跳转) | | `'open'` | 弹窗打开(layer.open iframe) | 添加、导入等弹窗表单 | | `'request'` | AJAX GET 请求 + 自动 confirm | 危险/不可逆操作:解绑、撤销、封闭 | | `'blank'` | 新标签页打开 | 外部链接、文件预览 | | `'get'` | 直接 GET 请求(不带 confirm) | 内置 `'delete'` 用此方法 | | `'none'` | 不绑定行为,配合 extend 自定义监听 | 自定义 JS 交互 | ### 5.4 自定义按钮对象完整参数表 | 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| | `text` | string | - | 按钮文字 | | `title` | string | = `text` | 提示文字 / 弹窗标题 / `request` 方法的 confirm 文案 | | `url` | string \| function | `''` | 请求 URL;为 function 时接收 `(data, operat)` 动态构造 | | `method` | string | `'open'` | 见 5.3 method 表 | | `field` | string \| function | `'id'` | URL 拼接的主键字段名;为 function 时可动态构造 URL | | `auth` | string | `'add'` | 权限节点名(对应 HTML 的 `data-auth-X`) | | `class` | string | `''` | HTML class(layui-btn 样式) | | `icon` | string | `''` | 图标 class | | `extend` | string | `''` | 额外 HTML 属性(如 `data-full="true"`) | | `titleField` | string | - | 用数据中某字段值作为标题(如"编辑 - 张三") | | `extra` | string | - | 标题附加字段(自动拼到 title 前) | | `_if` | function \| string | 始终显示 | **显隐条件**:function 接收 `(data, operat)` 返回布尔;string 时按字段名取值 | | `callback` | function | - | 点击回调(覆盖默认行为) | | `checkbox` | boolean | false | 是否需要选中行 | | `data` | string[] | `['id']` | 绑定到 HTML 的数据字段,生成 `data-{key}="{value}"` | ### 5.5 按钮颜色语义约定 | 颜色 | class | 语义 | |------|-------|------| | 灰色 | `layui-btn-primary` | 查看 / 中性操作 | | 绿色 | `layui-btn-success` | 编辑 / 正向操作(内置 `'edit'` 默认) | | 蓝色 | `layui-btn-normal` | 恢复 / 新增入口 | | 橙色 | `layui-btn-warm` | 警告 / 可逆危险操作 | | 红色 | `layui-btn-danger` | 危险 / 不可逆操作(内置 `'delete'` 默认) | ### 5.6 `_if` 条件显隐(最常用的进阶特性) ```js operat: [ 'edit', [{ text: '触发', url: init.triggerUrl, method: 'request', auth: 'trigger', class: 'layui-btn layui-btn-warm layui-btn-xs', // 仅 run_type='manual' 时显示 _if: function(data) { return data.run_type === 'manual'; } }] ] ``` `_if` 支持两种形式: - **函数**:`function(data) { return boolean }`,data 是当前行数据 - **字符串**:传字段名,该字段为真才显示(如 `'_if': 'is_active'`) 未声明 `_if` 时默认恒真(总是显示)。**条件按钮缺少 `_if` 是常见错误**(见 9.2)。 ### 5.7 跨控制器权限检查 按钮跳转到**其他控制器**的页面时(如 box_sn 页面的"绑定"按钮跳转 `code_bind/scan`),`auth` 的权限节点不在当前控制器下。需要在 HTML 用自定义 key 声明跨控制器的 `data-auth` 属性。 **HTML**: ```html ``` **JS**: ```js [{ class: 'layui-btn layui-btn-normal layui-btn-sm', method: 'tab', text: '绑定', title: '扫码绑定', auth: 'bind', // 对应 HTML 中的 data-auth-bind url: 'app.code_bind/scan', // 跨控制器 URL icon: 'fa fa-link' }] ``` 框架通过 `data-auth-{auth值}` 查找 HTML 属性做权限校验:有权限显示按钮,无权限自动隐藏。 ### 5.8 operat 数组结构 `operat` 是一个数组,元素有两种形态,可混用: ```js operat: [ [/* 按钮组1:每个对象是一个按钮 */], [/* 按钮组2 */], 'edit', // 字符串简写 'delete' ] ``` 每个 `[]` 内的对象是独立按钮,按顺序渲染。`renderOperat`(admin-table.js:769)做权限整列裁剪:只要任意一个按钮组内有按钮有权限,整列就保留。 ## 六、页面分类标准(A/B/C/D) 根据数据的来源和维护方式,每个列表页必定属于以下某一类。**每个页面都应显式声明属于哪一类,并按对应标准配置 toolbar + operat。** ### A 类:完整 CRUD 数据由后台用户主动创建和维护,支持完整的增删改查。 ```js toolbar: [/* 自定义按钮(如有) */, 'add', 'delete', 'export'], operat: [[/* 详情 */], [/* 业务操作(如有) */], 'edit', 'delete'] ``` ### B 类:只读列表 数据由系统流程生成或外部同步,后台仅查看,不支持增删改。 ```js toolbar: [], defaultToolbar: ['filter'], operat: [[/* 仅详情 */]] ``` ### C 类:有限操作 数据由系统流程生成,不支持通用 add/edit/delete,但支持特定的业务操作(状态流转、撤销等)。 ```js toolbar: [[/* 特定入口按钮 */]/* , 'export'(如需) */], operat: [[/* 详情 */], [/* 业务按钮(带 _if) */]] // 不含 'edit' / 'delete',除非有明确的业务需求 ``` ### D 类:表单交互 非标准列表页,以表单/扫码交互为主,表格仅作为结果展示。 ```js toolbar: [], // 通常无操作列,或仅详情 ``` ## 七、搜索系统 ### 7.1 前端配置:searchOp 操作符表 `cols` 项的 `searchOp` 决定后端如何构造 WHERE: | searchOp | 后端 WHERE 构造 | 适用场景 | |----------|----------------|---------| | `'%*%'`(默认) | `LIKE '%val%'` | 通用文本模糊搜索 | | `'*%'` | `LIKE 'val%'` | 前缀匹配(如编号) | | `'%*'` | `LIKE '%val'` | 后缀匹配 | | `'='` | `= val` | 精确匹配(如状态) | | `'in'` | `IN (val)` | 多值匹配 | | `'min'` | `>= val` | 数值最小值 | | `'max'` | `<= val` | 数值最大值 | | `'min_date'` | `>= strtotime(val)` | 日期最小值(前端传字符串,后端转时间戳) | | `'max_date'` | `<= strtotime(val)` | 日期最大值 | | `'range'` | `>= strtotime(begin)` AND `<= strtotime(end)` | 时间范围(前端用 `search: 'range'` 自动生成起止输入) | | `'none'` | 不进 where,作为 `request_options` | 业务自定义参数(不查库) | > 默认操作符是 `%*%'`(LIKE 模糊匹配)。**搜索"不灵活"的错觉来源于不知道可以覆盖 `searchOp`**。 ### 7.2 搜索类型与 searchOp 的关系 | `search` 值 | UI 类型 | 默认提交参数 | 常配 searchOp | |------------|---------|-------------|---------------| | `true`(默认) | 文本输入框 | `filter[field]=val` | `%*%` 或自定义 | | `false` | 不显示搜索项 | - | - | | `'select'` | 下拉选择 | `filter[field]=val` | `=` | | `'number_limit'` | 数值区间(生成 `[field]min`/`[field]max`) | `filter[field]min=val&filter[field]max=val` | 自动用 `min`/`max` | | `'time_limit'` | 时间区间 | 同上但格式为日期 | 自动用 `min_date`/`max_date` | | `'range'` | 时间范围选择器 | `filter[field]=begin - end` | `range` | ### 7.3 select_list_* 字典通道(下拉数据来源) 下拉选项的标准通道,三处对齐: 1. **后端 assign**(控制器里):`$this->assign('select_list_{名}', $字典数组, true);` - 第三个参数 `true` 表示同时压入 `data_brage` 通道 - 例:`$this->assign('select_list_status', StatusService::SN_STATUS_OPTIONS, true);` 2. **页面渲染**:所有 assign 变量被打包成 JSON,渲染进隐藏元素 ``(位于 layout) 3. **前端读取**:`ua.getDataBrage('select_list_{名}')` 从 `#data-brage` 解析取值 ```js // cols 中引用: {field: 'status', search: 'select', selectList: ua.getDataBrage('select_list_status'), ...} ``` > **口诀**:控制器 `assign` 的 key 必须等于 cols 里 `ua.getDataBrage('xxx')` 的参数,否则下拉为空。 ### 7.4 后端处理:buildTableParames `AdminControllerBase::buildTableParames()` 自动解析前端传入的 `filter` + `op` 参数为 WHERE 数组(`extend/base/common/controller/AdminControllerBase.php:405`): ```php // 控制器 index 方法典型用法 public function index() { list($page, $limit, $where, $excludes, $request_options, $group) = $this->buildTableParames(); $list = $this->model ->where($where) ->page($page, $limit) ->select(); // ... } ``` 支持: - **`$excludeFields`**:`buildTableParames(['field_to_exclude'])` 排除某些字段不进 where - **`relationSearch`**:控制器属性 `$relationSearch = true` 时,自动给字段加 `table.field` 前缀 - **`$request_options`**:`op='none'` 的字段会进入此变量(业务自定义参数) ## 八、后端控制器配套 | 配套项 | 说明 | |--------|------| | `index()` | 使用 `buildTableParames()` 构造查询 | | `$relationSearch` | bool 属性,开启关联表搜索(默认 false) | | `$selectWhere` | array 属性,`selectList()` 方法的过滤条件 | | `modify()` | 状态开关接口,`switch` 渲染器自动调用,接收 `id`/`field`/`value` | | `selectList()` | 下拉数据源接口,接收 `selectFields` 参数 | | `@NodeAnnotation` | 控制器方法注解,生成权限节点(与 `data-auth-X` 对应) | **关联搜索示例**(`extend/base/admin/controller/mall/GoodsBase.php`): ```php class GoodsBase extends AdminController { protected $relationSearch = true; // 此时 cols 中 field: 'cate.title' 会自动转为 where: ['mall_goods.cate_id' => ...] // 注意:关联字段需在模型里定义好 with 关系 } ``` ## 九、常见模式与排错 ### 9.1 常见模式速查(参考示例) | 场景 | 参考文件 | |------|---------| | 标准列表(增删改查 + 状态开关) | `extend/base/admin/view/system/admin/index.js` | | 行内排序(edit: 'text') | `extend/base/admin/view/system/admin/index.js`(sort 字段) | | 多按钮(设置密码 + 编辑 + 删除) | `extend/base/admin/view/system/admin/index.js` | | `_if` 控制按钮显隐 | `app/admin/view/system/timer_config/index.js`(仅 manual 显示触发按钮) | | 详情按钮 + 状态徽章 | `app/admin/view/system/timer_config/index.js` | | 关联表数据展示 | `extend/base/admin/view/mall/goods/index.js`(含 cate 信息) | | 树形列表 | `extend/base/admin/view/system/menu/index.js` | | 文件管理(图标预览) | `extend/base/admin/view/system/uploadfile/index.js` | | 权限分配 UI(双表格) | `extend/base/admin/view/system/auth/assign_users.js` | | 时间范围搜索 | `app/admin/view/system/timer_log/index.js`(start_time 用 `search: 'range'`) | | 自定义 templet(多状态徽章) | `app/admin/view/system/timer_log/index.js`(status 字段) | ### 9.2 常见错误模式 **错误 1:operat 未声明 → CURD 默认 edit/delete** ```js // 错误:依赖默认值 { title: '操作', templet: ua.table.tool } // tool 函数内自动填充 operat = ['edit', 'delete'],即使业务不需要 // 正确:显式声明,哪怕只保留详情 { title: '操作', templet: ua.table.tool, operat: [[/* 详情 */]] } ``` **错误 2:toolbar 未声明 → 默认含 add/delete/export** ```js // 错误:只读页面(B 类)不覆盖 toolbar ua.table.render({ init: init, cols: [[...]] }); // 默认 toolbar = ['refresh', 'add', 'delete', 'export'],但 addUrl/deleteUrl 可能为空,按钮仍显示但点击报错 // 正确:只读页面显式清空 ua.table.render({ init: init, toolbar: [], defaultToolbar: ['filter'], cols: [[...]] }); ``` **错误 3:条件按钮缺少 _if** ```js // 错误:按钮始终显示,后端校验拒绝 { text: '解绑', method: 'request', url: init.unbindUrl } // 正确:前端条件过滤,减少无效点击 { text: '解绑', method: 'request', url: init.unbindUrl, _if: function (data) { return data.status == 可解绑状态值; } } ``` **错误 4:CURD 关联残留列** CURD 生成时会将关联模型的全部字段(如 `systemAdmin.*`)输出为列。**每个页面应清理掉不需要展示的关联残留列**,只保留业务需要的字段。 **错误 5:select_list_* 不对应(下拉为空)** 控制器 `assign('select_list_xxx', ...)` 的 key 必须与 cols 里 `ua.getDataBrage('select_list_xxx')` 的参数完全一致(大小写敏感)。 **错误 6:权限节点不匹配(按钮不显示)** - HTML 的 `data-auth-X` 必须存在 - JS 里的 `auth: 'X'` 必须能找到对应的 `data-auth-X` - `{:auth('controller/action')}` 的节点路径必须与控制器 `@NodeAnnotation` 一致 排查工具:`php think admin:permission:nodes`(见 [ulthon-permission-cli](./ulthon-permission-cli/SKILL.md)) ### 9.3 排错指南 **"按钮不显示"排查步骤**: 1. 检查 HTML 是否声明了对应的 `data-auth-X`(用浏览器开发者工具看 table 元素) 2. 检查 `{:auth('xxx/yyy')}` 的返回值是否为 1(用超管账号测试排除权限问题) 3. 检查 JS 里 `auth: 'X'` 的 X 与 HTML `data-auth-X` 是否完全一致(大小写) 4. 查看权限节点列表:`php think admin:permission:nodes` **"搜索不生效"排查步骤**: 1. 浏览器抓 network 看请求是否带 `filter` 和 `op` 参数 2. 检查 `filter` JSON 是否包含期望的字段 3. 检查对应字段的 `op` 值是否符合预期(默认 `%*%`) 4. 后端用 `php think tools:db:query "SELECT ... WHERE ..."` 直接查表确认数据存在 5. 检查控制器是否调用了 `buildTableParames()` 而不是手动 `where()` ## 十、实测代码引用表 每个特性都标注实测来源(文件:行),若代码与本文档冲突,以代码为准。 | 特性 | 实测来源 | |------|---------| | `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` | | `#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` | | 实际示例:标准 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` | ## 相关技能 - [ulthon-scheme-curd-workflow](./ulthon-scheme-curd-workflow/SKILL.md):CURD 代码生成流程(生成本技能所定制的列表页骨架) - [ulthon-scheme-definition](./ulthon-scheme-definition/SKILL.md):Scheme 定义(决定生成哪些字段、字段类型) - [ulthon-page-api-dual-mode](./ulthon-page-api-dual-mode/SKILL.md):页面/接口同体机制(列表接口的 JSON 返回模式) - [ulthon-permission-cli](./ulthon-permission-cli/SKILL.md):权限节点核对(`data-auth-X` 与 `@NodeAnnotation` 对不上时) - [ulthon-tools-http-call](./ulthon-tools-http-call/SKILL.md):CLI 模拟请求验证列表接口