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 自测)与浏览器断言要点