mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-08-30 12:45:32 +08:00
- ulthon-admin-table 技能:后台表格完整指南(核心机制/init/cols/内置渲染器/按钮系统/搜索系统/后端配套/常见模式与排错/实测代码引用表),含页面分类标准 A/B/C/D 与项目2 实战经验 - ulthon-page-qa 技能:AI 自测页面验证 6 步 checklist(HTML 冒烟→API→Page Data→浏览器→搜索→分辨率),零项目依赖原则,三模式验证策略
320 lines
19 KiB
Markdown
320 lines
19 KiB
Markdown
---
|
||
name: "ulthon-page-qa"
|
||
description: "页面验证(AI 自测)技能。改完/新建一个后台页面后,用规则 checklist 验证页面能开、数据对、按钮显隐对、搜索对、分辨率不崩。零项目依赖(不在项目里装 Playwright/npm/node)。需要验证 CURD 生成的页面或排查页面渲染问题时调用。"
|
||
---
|
||
|
||
# 页面验证(AI 自测)— ulthon-page-qa
|
||
|
||
> 框架级技能(`ulthon-` 前缀)。一份规则驱动的 checklist,让 AI 智能体用**自己的**浏览器/HTTP 能力验证一个后台页面是否正常——**不在项目里安装任何新依赖**(不装 Playwright / npm / node)。
|
||
|
||
## 何时调用
|
||
|
||
- 改完/新建一个后台页面(控制器 + 视图 `*.html` + 同名 `*.js`)后,需要快速验证「页面能开、数据对、按钮显隐对、搜索对、分辨率不崩」
|
||
- 排查「页面白屏 / 列不渲染 / 下拉空 / 按钮该出现没出现 / 搜索提交后查不到」等问题时
|
||
- 作为 [ulthon-scheme-curd-workflow](./ulthon-scheme-curd-workflow/SKILL.md) 流程第 6~7 步「功能验证」的页面级执行细则
|
||
|
||
## 核心原则(不可协商)
|
||
|
||
1. **零项目依赖**:本技能是规则 checklist,不向项目 `package.json` / `composer.json` 引入任何东西。前端验证用**智能体自带的浏览器能力**(Playwright MCP / 内置 browser),不要在项目里写浏览器自动化代码
|
||
2. **后端先于前端**:先用 `php think tools:http:call`(CLI,零浏览器)验后端三模式,后端对了再开浏览器验前端
|
||
3. **特性名以代码为准**:下文每个特性都注明了实测来源(`文件:行`)。若代码与本文档冲突,以代码为准并反馈修订本文件
|
||
|
||
---
|
||
|
||
## 一、框架页面特性清单(均经实测,附代码出处)
|
||
|
||
本框架的「标准后台列表页」由 `ua.table.render({...})` 驱动(`public/static/plugs/ulthon-admin/admin-table.js`)。下列特性是验证时必须逐一对照的检查点。
|
||
|
||
完整机制(按钮/字段/搜索)详见 [ulthon-admin-table](./ulthon-admin-table/SKILL.md)。本技能聚焦"如何验证"。
|
||
|
||
### 1.1 cols 列定义
|
||
|
||
表格列在 `*.js` 的 `ua.table.render({ cols: [[ ... ]] })` 中声明。
|
||
|
||
实测示例(`extend/base/admin/view/system/admin/index.js:3-36`):
|
||
|
||
```js
|
||
cols: [[
|
||
{ type: "checkbox" },
|
||
{ field: 'id', width: 80, title: 'ID' },
|
||
{ field: 'username', minWidth: 80, title: '登录账户' },
|
||
{ field: 'head_img', minWidth: 80, title: '头像', search: false, templet: ua.table.image },
|
||
{ field: 'status', title: '状态', width: 85, search: 'select',
|
||
selectList: { 0: '禁用', 1: '启用' }, templet: ua.table.switch },
|
||
{ field: 'create_time', minWidth: 80, title: '创建时间', search: 'range' },
|
||
{
|
||
width: 250, title: '操作', fixed: 'right', templet: ua.table.tool,
|
||
operat: ['edit', [{ /* 设置密码 */ }], 'delete']
|
||
}
|
||
]]
|
||
```
|
||
|
||
### 1.2 下拉数据:`select_list_*` + `ua.getDataBrage` + `#data-brage`
|
||
|
||
这是框架「后端字典 → 前端下拉」的标准通道,验证时务必三处对齐:
|
||
|
||
1. **后端 assign**:控制器里 `$this->assign('select_list_{名}', $字典数组, true);`
|
||
- 实测:`extend/base/admin/controller/system/HostBase.php`(assign `select_list_status` / `select_list_is_master`)
|
||
- 约定:键名以 `select_list_` 开头,第三个参数 `true` 表示「同时压入 data_brage 通道」
|
||
2. **布局序列化**:所有 assign 变量被打包成 JSON,渲染进隐藏元素
|
||
- 实测:`extend/base/admin/view/layout/default.html` → `<script style="display: none;" id="data-brage" type="text/plain">{$data_brage|raw|default='[]'}</script>`
|
||
- 来源:`AdminControllerBase::fetch()` 内 `$this->assign('data_brage', json_encode($this->dataBrage))`(`extend/base/common/controller/AdminControllerBase.php:273`)
|
||
3. **前端读取**:`ua.getDataBrage('select_list_{名}')` 从 `#data-brage` 解析取值
|
||
- 实测:`public/static/plugs/ulthon-admin/admin-utils.js:63-74`
|
||
|
||
> 验证口诀:**控制器 assign 的 key** 必须等于 **cols 里 `ua.getDataBrage('xxx')` 的参数**,否则下拉为空。
|
||
|
||
### 1.3 搜索表单:`search` / `searchOp` / `filter` / `op`
|
||
|
||
搜索表单由 cols 自动生成(无需手写 HTML),核心参数(实测 `admin-table.js:300-369`):
|
||
|
||
- `d.search`:默认 **`true`**(`admin-table.js:307`)——即列默认可搜索,要关掉才需显式 `search: false`
|
||
- 搜索类型:
|
||
- 默认(无 `search` 或 `search:true`):文本输入框
|
||
- `search: 'select'`:下拉,选项取自同列 `selectList`
|
||
- `search: 'number_limit'`:数值区间(生成 `[field]min` / `[field]max`,op 为 `min`/`max`)
|
||
- `search: 'time_limit'`:时间区间
|
||
- `search: 'range'`:时间范围(实测 `app/admin/view/system/timer_log/index.js` 的 `start_time` 字段)
|
||
- `d.searchOp`:默认 **`'%*%'`**(LIKE `%val%`,`admin-table.js:312`)。可按列覆盖
|
||
- `d.defaultSearchValue` / `d.searchValue`:默认选中值,会从 URL query 自动回填
|
||
|
||
**提交时的参数名**:搜索表单提交时把条件拼成两个对象 `formatFilter` / `formatOp`(`admin-table.js:300-301`),最终以 `filter[字段]=值` 和 `op[字段]=操作符` 形式发给后端(如 `filter[status]=2&op[status]=%*%`)。验证时检查 network 里这两个参数是否正确即可。
|
||
|
||
完整 searchOp 操作符表见 [ulthon-admin-table](./ulthon-admin-table/SKILL.md) 第七章。
|
||
|
||
### 1.4 按钮显隐:`data-auth-*`(按权限节点)
|
||
|
||
权限按钮由两处配合控制:
|
||
|
||
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)
|
||
- 默认值:`operat.auth = operat.auth || 'add'`(`admin-table.js:1105`)——未写 `auth` 的按钮按 `add` 节点判定
|
||
|
||
> 验证口诀:**`auth: 'X'`**(JS)必须能在表格容器找到对应的 **`data-auth-X`**(HTML),且该值=1,按钮才会出现。节点路径必须和控制器 `@NodeAnnotation` 注解一致。
|
||
|
||
### 1.5 按钮显隐:`_if`(按数据状态)
|
||
|
||
操作列按钮可按当前行数据动态显隐,属性名为 **`_if`**(下划线前缀,小写)。实测 `admin-table.js:1113-1124`:
|
||
|
||
```js
|
||
operat._if = operat._if || function () { return true; };
|
||
if (typeof operat._if == 'function') {
|
||
if (operat._if(data, operat) !== true) { return ''; } // 不渲染该按钮
|
||
} else if (typeof operat._if == 'string') {
|
||
var ifValue = ua.table.returnColumnValue(data, operat._if, false);
|
||
if (!ifValue) { return ''; }
|
||
}
|
||
```
|
||
|
||
- **函数形式**:实测 `app/admin/view/system/timer_config/index.js:56-58`——仅当 `run_type==='manual'` 时显示「触发」按钮(通过 CSS 控制显隐的另一种写法)
|
||
- **字符串形式**:传字段名,该字段为真才显示
|
||
- 未写 `_if` 时默认恒真(总是显示)
|
||
|
||
> 验证口诀:`_if` 返回的判定必须与业务约束一致;典型场景是「只有已绑定(status=X)才允许解绑」这类状态门禁。
|
||
|
||
### 1.6 页面接口同体——三模式触发条件(实测)
|
||
|
||
同一路由通过请求特征返回 HTML / API JSON / Page Data JSON 三种输出。**触发条件以 `AdminControllerBase::fetch()` + `checkParseApi()` + `RequestBase::isAjax()` 实测为准**:
|
||
|
||
前置开关:`config('app.auto_parse_api')` 必须为 `true`(默认开启)。整个机制在该开关闭合时不生效。
|
||
|
||
| 模式 | 触发条件(实测) | `isAjax()` 返回 | 控制器最终输出 |
|
||
|------|----------------|----------------|--------------|
|
||
| **HTML 模式** | 请求头**不含** `Accept: application/json`(非 isJson) | 走常规 ajax 探测(一般 false) | `fetch()` 渲染并返回 HTML 页面 |
|
||
| **API 模式**(分页/提交) | `Accept: application/json`(isJson=true)且**不带** `get_page_data` 参数 | `true` | index 走 ajax 分支返回分页 JSON;POST success/error 返回 `json_message` |
|
||
| **Page Data 模式**(取 assign 字典) | `Accept: application/json` **且** 带 `get_page_data` 参数(值任意,存在即可) | `false` | `fetch()`→`checkParseApi()` 返回 true → 返回 `json_message(assign 全部变量)` |
|
||
| **错误提示** | 带 `get_page_data` 但**不带** `Accept: application/json` | — | `fetch()` 直接返回 `json_message([], 400, '使用 get_page_data 获取页面数据时,请设置请求头 Accept: application/json')`(`AdminControllerBase.php:269-271`) |
|
||
|
||
详细机制见 [ulthon-page-api-dual-mode](./ulthon-page-api-dual-mode/SKILL.md)。
|
||
|
||
---
|
||
|
||
## 二、AI 验证 checklist(改完页面后的标准 6 步)
|
||
|
||
每次完成一个后台页面开发/修改,按下列顺序执行。前 3 步纯 CLI(零浏览器、零项目依赖),后 3 步用智能体自带浏览器能力。
|
||
|
||
### 步骤 1 — 页面可打开(HTML 冒烟)
|
||
|
||
- 用 `tools:http:call` 以 **HTML 模式**请求页面 URL(不带 `Accept:json`),断言 HTTP 200、响应是 HTML、无 PHP Notice / 模板变量未定义错误
|
||
- 重点看:`Undefined variable`、`TemplateNotFoundException`、SQL 报错
|
||
|
||
```bash
|
||
php think tools:http:call --url="/admin/system.admin/index" --method=GET
|
||
```
|
||
|
||
### 步骤 2 — 后端接口验证(API 模式)
|
||
|
||
- 以 **API 模式**请求 `index`(带 `Accept:json`、不带 `get_page_data`),断言返回 `json_message` 结构、分页 `data.list|data.data` 非空、字段与 cols 对应
|
||
|
||
```bash
|
||
php think tools:http:call --url="/admin/system.admin/index" --method=GET --headers="{\"Accept\":\"application/json\"}"
|
||
```
|
||
|
||
- 对增/改/状态流转接口:用 `--method=POST --data='{...}'` 验提交返回 success,再用 API 模式回查确认数据变化
|
||
|
||
### 步骤 3 — 页面数据验证(Page Data 模式)
|
||
|
||
- 以 **Page Data 模式**请求页面(`Accept:json` + `get_page_data=1`),断言返回的 assign 字典里 `select_list_*` 齐全、`init.*Url` 等地址存在
|
||
|
||
```bash
|
||
php think tools:http:call --url="/admin/system.admin/index?get_page_data=1" --method=GET --headers="{\"Accept\":\"application/json\"}"
|
||
```
|
||
|
||
- 或用专用参数:`tools:http:call --app=admin --controller=system.admin --action=index --page-data`
|
||
|
||
### 步骤 4 — 前端渲染验证(浏览器)
|
||
|
||
- 用智能体自带浏览器能力(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)一一对应
|
||
4. 操作列按钮:`_if` 条件成立才出现
|
||
|
||
### 步骤 5 — 搜索提交验证(网络抓包)
|
||
|
||
- 在浏览器里实际提交一次搜索,抓 network 请求,核对:
|
||
- 请求头含 `Accept: application/json`、走 API 模式
|
||
- 参数形如 `filter[字段]=值` + `op[字段]=操作符`(默认 `%*%` 即 LIKE)
|
||
- `search:'select'` 的列确实以下拉提交、`number_limit`/`time_limit` 提交 `[field]min/[field]max`
|
||
- 返回结果集与筛选条件一致
|
||
|
||
### 步骤 6 — 分辨率覆盖(布局回归)
|
||
|
||
按 `AGENTS.md` 规定的档位截图检查布局不崩:
|
||
|
||
- **桌面端 4 档**:1920x1080、1536x864、1366x768、1280x720
|
||
- **移动端 4 档**:390x844(iPhone 12/13/14)、393x852(Pixel 7)、375x667(小屏 iPhone)、360x800(常见 Android)
|
||
- **平板 / 折叠屏**:768x1024(iPad)
|
||
- 重点看:表格横向滚动、操作列 `fixed:'right'` 是否遮挡、搜索区是否溢出
|
||
|
||
---
|
||
|
||
## 三、AI 工具使用指南
|
||
|
||
### 工具链顺序:CLI 后端 → 浏览器前端 → 视觉对比
|
||
|
||
```
|
||
tools:http:call(三模式,零浏览器)
|
||
│
|
||
▼ 后端三模式全绿
|
||
智能体自带浏览器(Playwright MCP / 内置 browser)
|
||
│
|
||
▼ 前端 DOM + 交互 + 截图全绿
|
||
visual-qa 技能(视觉对比 / 回归断言)
|
||
```
|
||
|
||
### 3.1 `php think tools:http:call`(后端,主力)
|
||
|
||
详见 [ulthon-tools-http-call](./ulthon-tools-http-call/SKILL.md)。要点:
|
||
|
||
- **默认 super token**:命令自动生成 token 写入登录缓存并带 `Authorization: Bearer <token>`,等价已登录。指定账号用 `--user-id=N`;回到未登录用 `--super-token=false`
|
||
- **三模式映射**:
|
||
- HTML 冒烟:不加 `Accept:json` 头
|
||
- API 模式:`--headers='{"Accept":"application/json"}'`(不加 `get_page_data`)
|
||
- Page Data 模式:`--page-data`(等价追加 `get_page_data=1` + Accept:json)
|
||
- **登录态失效排查**:若仍提示「请先登录」,多为命令行与服务端缓存不共享;确认走同一套 `Cache::store('login')`
|
||
- **无权限排查**:已登录但返回「无权限」,用 `--user-id` 换超管或检查 `admin:permission:nodes`
|
||
|
||
### 3.2 浏览器验证(前端,用智能体自带能力)
|
||
|
||
- **使用 Playwright MCP 或智能体内置 browser 工具**。加载 `playwright` 技能(`/playwright`)获取用法
|
||
- **严禁**在项目里 `npm install playwright`、新建 e2e 测试目录、写 `.spec.js`——这是规则 checklist,不是项目测试工程
|
||
- 需要登录态时:用 super token 思路,或直接在浏览器里走后台登录页登录一次后保持会话
|
||
- 做的只有三件事:打开页面(DOM 检查)、触发交互(搜索/点按钮)、按分辨率截图
|
||
|
||
### 3.3 `visual-qa` 技能(视觉对比)
|
||
|
||
- 对步骤 4/6 的截图,调用 `/visual-qa` 做视觉回归断言(设计系统一致性 + 功能完整性 + CJK 文字裁切)
|
||
- 适合在重构/换肤后确认「没改坏」
|
||
|
||
### 3.4 辅助:`tools:log` / `tools:db`
|
||
|
||
- 前端报错但后端返回正常时:`php think tools:log:show` 看运行时异常
|
||
- 数据对不上时:`php think tools:db:query "SELECT ..."` 直接查表(仅调试,不改表结构)
|
||
|
||
---
|
||
|
||
## 四、页面接口同体——三模式验证策略(各模式验什么)
|
||
|
||
明确每个模式该验的内容,避免重复或遗漏:
|
||
|
||
### 4.1 API 模式(`Accept:json`,无 `get_page_data`)——验业务正确性
|
||
|
||
- **验什么**:查询逻辑(筛选/排序/分页/关联)、写入逻辑(增删改)、状态流转
|
||
- **怎么验**:`tools:http:call` 带 `Accept:json`,断言 `code/msg/data` 结构、分页总数与 SQL 实际一致、状态流转前后数据变化符合预期
|
||
- **不验**:模板渲染、下拉选项(那是 Page Data 模式的事)
|
||
|
||
### 4.2 Page Data 模式(`Accept:json` + `get_page_data=1`)——验 assign 字典完整性
|
||
|
||
- **验什么**:页面渲染所需的全部静态数据——`select_list_*` 下拉选项、`init.*Url` 地址、默认值、其它 assign 变量
|
||
- **怎么验**:`tools:http:call --page-data`,断言每个 cols 里 `ua.getDataBrage('xxx')` 引用的 key 都在返回字典里且非空
|
||
- **不验**:业务查询结果集(那是 API 模式的事)
|
||
|
||
### 4.3 HTML 模式(无 `Accept:json`)——验模板渲染正确性
|
||
|
||
- **验什么**:模板能编译、`{$变量}` 都有值、`{:auth(...)}` 等模板函数正常、无 PHP Notice、DOM 结构(表格容器、`#data-brage` 隐藏元素)存在
|
||
- **怎么验**:`tools:http:call` 不带头,断言响应是 HTML、包含 `<table id="currentTable"` 与 `<script id="data-brage"`、无错误字符串
|
||
- **不验**:JS 执行后的最终 DOM(那是步骤 4 浏览器的事)
|
||
|
||
---
|
||
|
||
## 五、附录
|
||
|
||
### 5.1 实测代码引用表
|
||
|
||
| 特性 | 实测来源 |
|
||
|------|---------|
|
||
| `ua.table.render` 入口 | `public/static/plugs/ulthon-admin/admin-table.js:14` |
|
||
| cols 定义(checkbox/field/title/关联字段) | `extend/base/admin/view/system/admin/index.js:3-36` |
|
||
| `search:'select'` + `selectList` | `extend/base/admin/view/system/admin/index.js:15`(status 字段) |
|
||
| `search: 'range'` 时间范围 | `app/admin/view/system/timer_log/index.js:65`(start_time) |
|
||
| search 默认 true / `searchOp` 默认 `%*%` | `admin-table.js:307`, `admin-table.js:312` |
|
||
| `formatFilter`/`formatOp`(filter/op 提交) | `admin-table.js:300-301` |
|
||
| search 类型 select/number_limit/time_limit | `admin-table.js:348-369` |
|
||
| `ua.getDataBrage` 实现 | `public/static/plugs/ulthon-admin/admin-utils.js:63-74` |
|
||
| `#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` |
|
||
| 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` |
|
||
| `_if`(函数/字符串判定) | `app/admin/view/system/timer_config/index.js:56-58`;`admin-table.js:1113-1124` |
|
||
| 自定义 templet(状态徽章) | `app/admin/view/system/timer_log/index.js:3-11`(statusTemplet) |
|
||
| `fetch()` 三模式分流 | `extend/base/common/controller/AdminControllerBase.php:267-291` |
|
||
| `checkParseApi()` Page Data 判定 | `AdminControllerBase.php:293-302` |
|
||
| `get_page_data` 误用错误提示 | `AdminControllerBase.php:269-271` |
|
||
| 开关 `app.auto_parse_api` | `AdminControllerBase.php:295` |
|
||
|
||
### 5.2 相关技能
|
||
|
||
- [ulthon-admin-table](./ulthon-admin-table/SKILL.md):表格机制完整说明(本技能验证的对象)
|
||
- [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` 注解节点对不上时,用它核对节点
|
||
- `playwright` / `visual-qa`(内置技能):前端浏览器验证与视觉对比
|
||
|
||
### 5.3 计划假设修正记录
|
||
|
||
> 编写本技能前已逐条核对真实代码,结论:**计划假设全部准确,无需修正**。补充的实测细节如下:
|
||
|
||
- `get_page_data` 的**值**无关紧要(框架用 `has('get_page_data')` 判存在性),惯例仍写 `=1`
|
||
- 搜索列 `search` **默认为 true**(非 false)——即列默认参与搜索,要关闭需显式 `search:false`
|
||
- 默认搜索操作符 `searchOp` = `'%*%'`(两端 LIKE),提交时表现为 `op[字段]=%*%`
|
||
- `ua.getDataBrage` 拼写为 **`Brage`**(非 Bridge),对应隐藏元素 `#data-brage` 与 assign 键 `data_brage`,三者命名一致
|
||
- `_if` 支持函数与字符串两种形式;未声明时默认恒真(总是显示)
|