- ulthon-admin-table 技能:后台表格完整指南(核心机制/init/cols/内置渲染器/按钮系统/搜索系统/后端配套/常见模式与排错/实测代码引用表),含页面分类标准 A/B/C/D 与项目2 实战经验 - ulthon-page-qa 技能:AI 自测页面验证 6 步 checklist(HTML 冒烟→API→Page Data→浏览器→搜索→分辨率),零项目依赖原则,三模式验证策略
19 KiB
name, description
| name | description |
|---|---|
| ulthon-page-qa | 页面验证(AI 自测)技能。改完/新建一个后台页面后,用规则 checklist 验证页面能开、数据对、按钮显隐对、搜索对、分辨率不崩。零项目依赖(不在项目里装 Playwright/npm/node)。需要验证 CURD 生成的页面或排查页面渲染问题时调用。 |
页面验证(AI 自测)— ulthon-page-qa
框架级技能(
ulthon-前缀)。一份规则驱动的 checklist,让 AI 智能体用自己的浏览器/HTTP 能力验证一个后台页面是否正常——不在项目里安装任何新依赖(不装 Playwright / npm / node)。
何时调用
- 改完/新建一个后台页面(控制器 + 视图
*.html+ 同名*.js)后,需要快速验证「页面能开、数据对、按钮显隐对、搜索对、分辨率不崩」 - 排查「页面白屏 / 列不渲染 / 下拉空 / 按钮该出现没出现 / 搜索提交后查不到」等问题时
- 作为 ulthon-scheme-curd-workflow 流程第 6~7 步「功能验证」的页面级执行细则
核心原则(不可协商)
- 零项目依赖:本技能是规则 checklist,不向项目
package.json/composer.json引入任何东西。前端验证用智能体自带的浏览器能力(Playwright MCP / 内置 browser),不要在项目里写浏览器自动化代码 - 后端先于前端:先用
php think tools:http:call(CLI,零浏览器)验后端三模式,后端对了再开浏览器验前端 - 特性名以代码为准:下文每个特性都注明了实测来源(
文件:行)。若代码与本文档冲突,以代码为准并反馈修订本文件
一、框架页面特性清单(均经实测,附代码出处)
本框架的「标准后台列表页」由 ua.table.render({...}) 驱动(public/static/plugs/ulthon-admin/admin-table.js)。下列特性是验证时必须逐一对照的检查点。
完整机制(按钮/字段/搜索)详见 ulthon-admin-table。本技能聚焦"如何验证"。
1.1 cols 列定义
表格列在 *.js 的 ua.table.render({ cols: [[ ... ]] }) 中声明。
实测示例(extend/base/admin/view/system/admin/index.js:3-36):
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
这是框架「后端字典 → 前端下拉」的标准通道,验证时务必三处对齐:
- 后端 assign:控制器里
$this->assign('select_list_{名}', $字典数组, true);- 实测:
extend/base/admin/controller/system/HostBase.php(assignselect_list_status/select_list_is_master) - 约定:键名以
select_list_开头,第三个参数true表示「同时压入 data_brage 通道」
- 实测:
- 布局序列化:所有 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)
- 实测:
- 前端读取:
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':下拉,选项取自同列selectListsearch: '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 第七章。
1.4 按钮显隐:data-auth-*(按权限节点)
权限按钮由两处配合控制:
- HTML 写入权限标记:表格容器上以
data-auth-{动作}="{:auth('控制器/动作')}"形式声明每个动作的权限值(1 有权限 / 0 无)- 实测:
extend/base/admin/view/system/admin/index.html:3:<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(...)}是模板函数,返回当前登录账号对该权限节点的布尔判定
- 实测:
- 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:
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。
二、AI 验证 checklist(改完页面后的标准 6 步)
每次完成一个后台页面开发/修改,按下列顺序执行。前 3 步纯 CLI(零浏览器、零项目依赖),后 3 步用智能体自带浏览器能力。
步骤 1 — 页面可打开(HTML 冒烟)
- 用
tools:http:call以 HTML 模式请求页面 URL(不带Accept:json),断言 HTTP 200、响应是 HTML、无 PHP Notice / 模板变量未定义错误 - 重点看:
Undefined variable、TemplateNotFoundException、SQL 报错
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 对应
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等地址存在
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)打开页面(需登录态),逐项核对:
- 表格表头/列按 cols 渲染(字段、标题、关联字段显示正确)
- 搜索区下拉选项 = Page Data 模式里
select_list_*的内容(对照步骤 3 结果) - 工具栏按钮:
data-auth-X=1的按钮出现、=0的消失;auth: 'X'(JS)与data-auth-X(HTML)一一对应 - 操作列按钮:
_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。要点:
- 默认 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)
- HTML 冒烟:不加
- 登录态失效排查:若仍提示「请先登录」,多为命令行与服务端缓存不共享;确认走同一套
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-page-api-dual-mode:页面/接口同体的触发条件与机制(本技能 1.6 节的依据)
- ulthon-tools-http-call:CLI HTTP 调用工具用法(本技能三模式的执行工具)
- ulthon-scheme-curd-workflow:标准开发流程,本技能是其「功能验证」环节的页面级细则
- ulthon-permission-cli:
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支持函数与字符串两种形式;未声明时默认恒真(总是显示)