Files
ulthon_admin/.agents/skills/ulthon-admin-table/SKILL.md
augushong ad96755ff5 docs(permission): 统一校准权限节点措辞,消除更新节点误解
admin:permission:nodes 是查看型命令(实时扫描注解、不写库、改注解即生效),但文档/注释多处用一键更新/自动更新/生成等动作词描述,导致开发者系统性误以为它是更新权限节点命令。本次校准 8 个文件 10 处误导措辞,并在命令输出末尾追加机制提示。@NodeAnotation 拼写保持不变(框架故意少一个 t)。
2026-08-08 18:21:25 +08:00

661 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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/<module>/
├── index.html # 极简 HTML仅 table 元素 + data-auth-* 权限标记
├── _common.js # init 对象URL 配置等)
└── index.js # ua.table.render({init, cols, toolbar?})
```
**index.html**(典型示例,仅 11 行):
```html
<div class="layuimini-container">
<div class="layuimini-main">
<table id="currentTable" class="layui-table layui-hide"
data-auth-index="{:auth('system.timer_config/index')}"
data-auth-edit="{:auth('system.timer_config/edit')}"
data-auth-export="{:auth('system.timer_config/export')}"
data-auth-modify="{:auth('system.timer_config/modify')}"
lay-filter="currentTable">
</table>
</div>
</div>
```
**`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/<module>/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`(默认 `<br>` |
| `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}
// 状态开关(自动调 modifyUrldata 字段为 idvalue 为 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 '<span class="layui-badge layui-bg-green">已同步</span>';
}
return '<span class="layui-badge">未同步</span>';
}}
```
自定义 templet 接收行数据 `d`,返回 HTML 字符串。
## 五、按钮系统
后台表格有两类按钮:**顶部工具栏**toolbar和**行级操作按钮**operat
### 5.1 顶部工具栏toolbar
通过 `ua.table.render({ toolbar: [...] })` 配置。**不声明时默认 `['refresh', 'add', 'delete', 'export']`**——这意味着只读页面如果不覆盖,会显示无用的"新增/删除"按钮(见 9.2 常见错误)。
**字符串简写**(自动检查权限):
| 值 | 行为 | 权限节点 |
|----|------|---------|
| `'refresh'` | 刷新按钮(不带权限) | - |
| `'add'` | 新增按钮,打开 `init.addUrl` | `<module>/add` |
| `'delete'` | 批量删除(需配合 checkbox | `<module>/delete` |
| `'export'` | 导出按钮,请求 `init.exportUrl` | `<module>/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'`(标签页打开) | `<module>/edit` |
| `'delete'` | 删除按钮(带确认) | `'get'` | `<module>/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 classlayui-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
<table ... data-auth-bind="{:auth('app.code_bind/scan')}" ...>
```
**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渲染进隐藏元素 `<script id="data-brage" type="text/plain">{$data_brage|raw|default='[]'}</script>`(位于 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 常见错误模式
**错误 1operat 未声明 → CURD 默认 edit/delete**
```js
// 错误:依赖默认值
{ title: '操作', templet: ua.table.tool }
// tool 函数内自动填充 operat = ['edit', 'delete'],即使业务不需要
// 正确:显式声明,哪怕只保留详情
{ title: '操作', templet: ua.table.tool, operat: [[/* 详情 */]] }
```
**错误 2toolbar 未声明 → 默认含 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 == 可解绑状态值; } }
```
**错误 4CURD 关联残留列**
CURD 生成时会将关联模型的全部字段(如 `systemAdmin.*`)输出为列。**每个页面应清理掉不需要展示的关联残留列**,只保留业务需要的字段。
**错误 5select_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 模拟请求验证列表接口