docs(skills): 技能文档更新为全局权限判定协议

This commit is contained in:
augushong
2026-10-04 01:44:19 +00:00
parent 35e4d03e27
commit f688eef43e
3 changed files with 48 additions and 51 deletions

View File

@@ -41,28 +41,26 @@ CURD 生成器生成列表页骨架后,所有业务定制(按钮、字段渲
```
app/admin/view/<module>/
├── index.html # 极简 HTML,仅 table 元素 + data-auth-* 权限标记
├── index.html # 纯结构零权限声明,权限由 layout 全局 UA_AUTH 注入
├── _common.js # init 对象(URL 配置等)
└── index.js # ua.table.render({init, cols, toolbar?})
```
**index.html**(典型示例,仅 11 行):
**index.html**(典型示例):
```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 才显示。详见第六章。
**权限判定协议**:index.html 不写任何权限声明。页面加载时 layout 注入全局载荷 `window.UA_AUTH = {all, controller, nodes}`(服务端一次性构建,超管 `all:true`),前端 `ua.auth(node)` 据此全局判定按钮显隐:短名(不含 `/`)自动补当前控制器前缀,跨控制器直接写全节点(如 `'app.code_bind/scan'`,与注册节点逐字一致,动作段大小写敏感)。详见 5.4 与 5.7。
> **升级注意**:本机制的判定实现位于 admin-core.js,其静态引用带框架版本号缓存键(下游部署为 site_version)。升级框架后若按钮显隐异常,先强制刷新浏览器缓存(Ctrl+F5)再排查,避免旧缓存 JS 与新页面叠加造成误判。
### 1.3 前后端链路
@@ -241,7 +239,7 @@ ua.table.render({
text: '导入',
url: 'demo.index/import',
method: 'open', // 见 5.3 method 表
auth: 'import', // 对应 data-auth-import
auth: 'import', // 短名=当前控制器动作(等价 'demo.index/import')
class: 'layui-btn layui-btn-warm layui-btn-sm',
icon: 'fa fa-upload',
title: '导入数据',
@@ -308,7 +306,7 @@ ua.table.render({
| `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`) |
| `auth` | string | `'add'` | 权限节点:短名=当前控制器动作,跨控制器写全节点(与注册节点逐字一致,详见 5.7) |
| `class` | string | `''` | HTML class(layui-btn 样式) |
| `icon` | string | `''` | 图标 class |
| `extend` | string | `''` | 额外 HTML 属性(如 `data-full="true"`) |
@@ -338,7 +336,7 @@ operat: [
text: '触发',
url: init.triggerUrl,
method: 'request',
auth: 'trigger',
auth: 'trigger', // 短名=当前控制器动作(等价 'system.timer_config/trigger')
class: 'layui-btn layui-btn-warm layui-btn-xs',
// 仅 run_type='manual' 时显示
_if: function(data) {
@@ -356,15 +354,7 @@ operat: [
### 5.7 跨控制器权限检查
按钮跳转到**其他控制器**的页面时(如 box_sn 页面的"绑定"按钮跳转 `code_bind/scan`),`auth` 的权限节点不在当前控制器下。需要在 HTML 用自定义 key 声明跨控制器的 `data-auth` 属性。
**HTML**:
```html
<table ... data-auth-bind="{:auth('app.code_bind/scan')}" ...>
```
**JS**:
按钮跳转到**其他控制器**的页面时(如 box_sn 页面的"绑定"按钮跳转 `code_bind/scan`),`auth` 直接写全节点即可,无需任何 HTML 中转声明:
```js
[{
@@ -372,13 +362,21 @@ operat: [
method: 'tab',
text: '绑定',
title: '扫码绑定',
auth: 'bind', // 对应 HTML 中的 data-auth-bind
auth: 'app.code_bind/scan', // 全节点,与注册节点逐字一致
url: 'app.code_bind/scan', // 跨控制器 URL
icon: 'fa fa-link'
}]
```
框架通过 `data-auth-{auth值}` 查找 HTML 属性做权限校验:有权限显示按钮,无权限自动隐藏。
`ua.auth` 按全局允许集(layout 注入的 `window.UA_AUTH`)判定:有权限显示按钮,无权限自动隐藏。
**全节点写法约定**:
- 与 `@NodeAnnotation` 注册的节点**逐字一致**:`ua.auth` 做字符串匹配,前端不做任何规范化转换
- 动作段**大小写敏感**:注册为 `setMaster` 就写 `'system.host/setMaster'`,禁止 snake 化写成 `set-master`(parseNodeStr 只规范控制器段,动作段原样保留)
- 短名(不含 `/`)自动按"当前控制器/短名"解析,只适用于本控制器动作
**未注册动作的行为差异**:权限载荷不含 default_pass,前端对"不在允许集"的按钮一律保守隐藏。在 `default_auth_check=true` 的部署中,未注册动作后端仍按真实规则放行,即前端隐藏而后端放行,此时应前后端核对并补注册节点。旧协议(HTML `data-auth` 属性中转)仅作为下游旧页面的回退通道保留,新页面不使用。
### 5.8 operat 数组结构
@@ -518,7 +516,7 @@ public function index()
| `$selectWhere` | array 属性,`selectList()` 方法的过滤条件 |
| `modify()` | 状态开关接口,`switch` 渲染器自动调用,接收 `id`/`field`/`value` |
| `selectList()` | 下拉数据源接口,接收 `selectFields` 参数 |
| `@NodeAnnotation` | 控制器方法注解,生成权限节点(与 `data-auth-X` 对应) |
| `@NodeAnnotation` | 控制器方法注解,生成权限节点(前端 `ua.auth` 全局判定按钮显隐的依据) |
**关联搜索示例**(`extend/base/admin/controller/mall/GoodsBase.php`):
@@ -594,9 +592,9 @@ CURD 生成时会将关联模型的全部字段(如 `systemAdmin.*`)输出
**错误 6:权限节点不匹配(按钮不显示)**
- HTML 的 `data-auth-X` 必须存在
- JS 里的 `auth: 'X'` 必须能找到对应的 `data-auth-X`
- `{:auth('controller/action')}` 的节点路径必须与控制器 `@NodeAnnotation` 一致
- JS 里的 auth 值解析出的节点必须与控制器 `@NodeAnnotation` 注册的节点一致:短名等于"当前控制器/动作",跨控制器直接写全节点
- 全节点与注册节点逐字比对,动作段大小写敏感(注册 `setMaster` 写成 `set-master` 即失配隐藏)
- 未注册动作前端保守隐藏;`default_auth_check=true` 部署下后端仍按真实规则放行,前后端核对后补注册节点
排查工具:`php think admin:permission:nodes`(见 [ulthon-permission-cli](./ulthon-permission-cli/SKILL.md))
@@ -604,10 +602,11 @@ CURD 生成时会将关联模型的全部字段(如 `systemAdmin.*`)输出
**"按钮不显示"排查步骤**:
1. 检查 HTML 是否声明了对应的 `data-auth-X`(用浏览器开发者工具看 table 元素)
2. 检查 `{:auth('xxx/yyy')}` 的返回值是否为 1(用超管账号测试排除权限问题)
3. 检查 JS 里 `auth: 'X'` 的 X 与 HTML `data-auth-X` 是否完全一致(大小写)
1. 浏览器控制台核对 `window.UA_AUTH`:确认按钮 auth 值解析出的节点在允许集内(短名自动补当前控制器前缀,全节点逐字匹配)
2. 检查 JS 里 auth 值与 `@NodeAnnotation` 注册节点是否逐字一致(动作段大小写敏感,禁止 snake 化动作段)
3. 用超管账号测试排除账号权限配置问题(超管 `all:true` 恒显)
4. 查看权限节点列表:`php think admin:permission:nodes`
5. 下游旧页面(data-auth 回退通道):检查 HTML 是否声明了对应的 `data-auth-X` 属性且值为 1。仅旧页面有此声明,新协议页面 index.html 无权限声明
**"搜索不生效"排查步骤**:
@@ -787,6 +786,6 @@ layoutDefault: true, // 旧协议"自动判断",视
- [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-permission-cli](./ulthon-permission-cli/SKILL.md):权限节点核对(按钮 auth 值与 `@NodeAnnotation` 注册节点对不上时)
- [ulthon-tools-http-call](./ulthon-tools-http-call/SKILL.md):CLI 模拟请求验证列表接口
- [ulthon-page-qa](./ulthon-page-qa/SKILL.md):页面验证(AI 自测)与浏览器断言要点

View File

@@ -51,7 +51,7 @@ Vue 形态的 `v-for="d in rows"` 里的 `d`:
|------|------|
| `...row` | 行原始字段全部可用(username、title、price、create_time 等) |
| `_cells` | 对象,field 到该列 templet 渲染产物 HTML 的映射。只有配了渲染器的列才有内容;纯文本列恒为空串,**勿引用,直接用原始字段**;隐藏列(col.hide)不产出对应键 |
| `_operatHtml` | 操作列渲染产物(编辑/删除等按钮 HTML,已按 `data-auth-*` 裁剪权限) |
| `_operatHtml` | 操作列渲染产物(编辑/删除等按钮 HTML,已按当前登录者权限裁剪) |
| `_checked` | 该行是否选中(store 选中集合) |
| `_disabled` | 该行是否禁选 |

View File

@@ -82,27 +82,25 @@ cols: [[
完整 searchOp 操作符表见 [ulthon-admin-table](./ulthon-admin-table/SKILL.md) 第七章。
### 1.4 按钮显隐:`data-auth-*`(按权限节点)
### 1.4 按钮显隐:UA_AUTH 全局权限判定
权限按钮由两处配合控制:
按钮显隐由全局权限载荷决定,index.html 纯结构零权限声明:
1. **HTML 写入权限标记**:表格容器上以 `data-auth-{动作}="{:auth('控制器/动作')}"` 形式声明每个动作的权限值(1 有权限 / 0 无)
- 实测:`extend/base/admin/view/system/admin/index.html:3`:
```html
<table id="currentTable" ...
data-auth-add="{:auth('system.admin/add')}"
data-auth-edit="{:auth('system.admin/edit')}"
data-auth-delete="{:auth('system.admin/delete')}"
data-auth-password="{:auth('system.admin/password')}"
data-auth-modify="{:auth('system.admin/modify')}"
lay-filter="currentTable">
```
- `{:auth(...)}` 是模板函数,返回当前登录账号对该权限节点的布尔判定
2. **JS 按 auth 匹配显隐**:cols/toolbar 里的按钮对象带 `auth: '{动作}'`,框架读取对应 `data-auth-{动作}` 值决定渲染与否
- 实测:`system/admin/index.js`(toolbar 默认含 add;操作列含 `password` 自定义按钮 + edit + delete)
1. **layout 注入全局载荷**:页面加载时服务端一次性注入 `window.UA_AUTH = {all, controller, nodes}`(`all`=超管标记;`controller`=当前控制器节点名;`nodes`=允许集 `{'控制器': ['动作', ...]}`,超管 `all:true` 时 nodes 为空数组)
- 实测:`extend/base/admin/view/layout/default.html`(内联 script,与 window.CONFIG 同级)
- 同体 JSON 模式(get_page_data)不携带该载荷,仅在页面 HTML 中存在
2. **ua.auth(node) 全局判定**:按钮对象带 `auth` 值,框架经 `ua.checkAuth` 依序判定:`'1'` 恒显 → `CONFIG.IS_SUPER_ADMIN` → `ua.auth(node)` → `data-auth-{node}` 属性回退(仅旧页面)
- 短名(不含 `/`)自动补 `UA_AUTH.controller` 前缀;全节点逐字匹配,动作段大小写敏感(如 `system.host/setMaster` 禁止写成 `set-master`)
- 默认值:`operat.auth = operat.auth || 'add'`(`admin-table.js:1105`)——未写 `auth` 的按钮按 `add` 节点判定
> 验证口诀:**`auth: 'X'`**(JS)必须能在表格容器找到对应的 **`data-auth-X`**(HTML),且该值=1,按钮才会出现。节点路径必须和控制器 `@NodeAnnotation` 注解一致。
**QA 校验口径**:
- 页面源码断言 `window.UA_AUTH` 存在且形状正确(`{all, controller, nodes}` 三键齐全)
- 按钮显隐对应用户允许集:允许集内的按钮出现,不在允许集的消失,超管全显
- 未注册动作按钮前端保守隐藏(载荷无 default_pass);`default_auth_check=true` 部署下后端仍按真实规则放行,属已知差异,以前后端核对为准
- `data-auth-*` HTML 属性是下游旧页面的回退通道,新协议页面无此声明
> 验证口诀:按钮节点在 `UA_AUTH.nodes` 允许集内则显示,不在则隐藏(超管恒显)。节点路径必须和控制器 `@NodeAnnotation` 注解逐字一致(动作段大小写敏感)。
### 1.5 按钮显隐:`_if`(按数据状态)
@@ -179,7 +177,7 @@ php think tools:http:call --url="/admin/system.admin/index?get_page_data=1" --me
- 用智能体自带浏览器能力(Playwright MCP / agent-browser)打开页面(需登录态),逐项核对:
1. 表格表头/列按 cols 渲染(字段、标题、关联字段显示正确)
2. 搜索区下拉选项 = Page Data 模式里 `select_list_*` 的内容(对照步骤 3 结果)
3. 工具栏按钮:`data-auth-X=1` 的按钮出现、`=0` 的消失;`auth: 'X'`(JS)与 `data-auth-X`(HTML)一一对应
3. 工具栏按钮:显隐与用户允许集一致(`window.UA_AUTH`),有权限的出现、无权限的消失;旧页面回退通道才存在 `data-auth` 属性
4. 操作列按钮:`_if` 条件成立才出现
### 步骤 5 — 搜索提交验证(网络抓包)
@@ -287,8 +285,8 @@ visual-qa 技能(视觉对比 / 回归断言)
| `#data-brage` 隐藏元素 | `extend/base/admin/view/layout/default.html` |
| `data_brage` assign | `extend/base/common/controller/AdminControllerBase.php:273` |
| 后端 assign `select_list_*` | `extend/base/admin/controller/system/HostBase.php` |
| `data-auth-{动作}` HTML 标记 | `extend/base/admin/view/system/admin/index.html:3` |
| `data-auth-set-master` 跨控制器权限示例 | `extend/base/admin/view/system/host/index.html:5` |
| `window.UA_AUTH` 载荷注入(layout 内联) | `extend/base/admin/view/layout/default.html` |
| set-master 按钮全节点写法(勘正:旧 `data-auth-set-master` 属性是"自定义键/驼峰动作段"样例而非跨控制器样例,auth() 参数与本页同控制器;属性已随新协议清理,JS 现写全节点) | `extend/base/admin/view/system/host/index.js:33` |
| toolbar/auth(默认 add) | `extend/base/admin/view/system/admin/index.js` |
| 操作列 auth(password 自定义按钮) | `extend/base/admin/view/system/admin/index.js:24-31` |
| `operat.auth` 默认 'add' | `admin-table.js:1105` |
@@ -305,7 +303,7 @@ visual-qa 技能(视觉对比 / 回归断言)
- [ulthon-page-api-dual-mode](./ulthon-page-api-dual-mode/SKILL.md):页面/接口同体的触发条件与机制(本技能 1.6 节的依据)
- [ulthon-tools-http-call](./ulthon-tools-http-call/SKILL.md):CLI HTTP 调用工具用法(本技能三模式的执行工具)
- [ulthon-scheme-curd-workflow](./ulthon-scheme-curd-workflow/SKILL.md):标准开发流程,本技能是其「功能验证」环节的页面级细则
- [ulthon-permission-cli](./ulthon-permission-cli/SKILL.md):`data-auth-*` / `auth` 注解节点对不上时,用它核对节点
- [ulthon-permission-cli](./ulthon-permission-cli/SKILL.md):按钮 auth 值与注解注册节点对不上时,用它核对节点
- `playwright` / `visual-qa`(内置技能):前端浏览器验证与视觉对比
### 5.3 计划假设修正记录