Files
ulthon_admin/.agents/skills/ulthon-page-qa/SKILL.md
augushong 98dd244e61 docs(agents): 新增后台表格机制与页面验证技能
- ulthon-admin-table 技能:后台表格完整指南(核心机制/init/cols/内置渲染器/按钮系统/搜索系统/后端配套/常见模式与排错/实测代码引用表),含页面分类标准 A/B/C/D 与项目2 实战经验
- ulthon-page-qa 技能:AI 自测页面验证 6 步 checklist(HTML 冒烟→API→Page Data→浏览器→搜索→分辨率),零项目依赖原则,三模式验证策略
2026-07-19 12:00:21 +08:00

19 KiB
Raw Blame History

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 步「功能验证」的页面级执行细则

核心原则(不可协商)

  1. 零项目依赖:本技能是规则 checklist不向项目 package.json / composer.json 引入任何东西。前端验证用智能体自带的浏览器能力Playwright MCP / 内置 browser不要在项目里写浏览器自动化代码
  2. 后端先于前端:先用 php think tools:http:callCLI零浏览器验后端三模式后端对了再开浏览器验前端
  3. 特性名以代码为准:下文每个特性都注明了实测来源(文件:行)。若代码与本文档冲突,以代码为准并反馈修订本文件

一、框架页面特性清单(均经实测,附代码出处)

本框架的「标准后台列表页」由 ua.table.render({...}) 驱动(public/static/plugs/ulthon-admin/admin-table.js)。下列特性是验证时必须逐一对照的检查点。

完整机制(按钮/字段/搜索)详见 ulthon-admin-table。本技能聚焦"如何验证"。

1.1 cols 列定义

表格列在 *.jsua.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

这是框架「后端字典 → 前端下拉」的标准通道,验证时务必三处对齐:

  1. 后端 assign:控制器里 $this->assign('select_list_{名}', $字典数组, true);
    • 实测:extend/base/admin/controller/system/HostBase.phpassign 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:默认 trueadmin-table.js:307)——即列默认可搜索,要关掉才需显式 search: false
  • 搜索类型:
    • 默认(无 searchsearch:true):文本输入框
    • search: 'select':下拉,选项取自同列 selectList
    • search: 'number_limit':数值区间(生成 [field]min / [field]maxop 为 min/max
    • search: 'time_limit':时间区间
    • search: 'range':时间范围(实测 app/admin/view/system/timer_log/index.jsstart_time 字段)
  • d.searchOp:默认 '%*%'LIKE %val%admin-table.js:312)。可按列覆盖
  • d.defaultSearchValue / d.searchValue:默认选中值,会从 URL query 自动回填

提交时的参数名:搜索表单提交时把条件拼成两个对象 formatFilter / formatOpadmin-table.js:300-301),最终以 filter[字段]=值op[字段]=操作符 形式发给后端(如 filter[status]=2&op[status]=%*%)。验证时检查 network 里这两个参数是否正确即可。

完整 searchOp 操作符表见 ulthon-admin-table 第七章。

1.4 按钮显隐:data-auth-*(按权限节点)

权限按钮由两处配合控制:

  1. 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(...)} 是模板函数,返回当前登录账号对该权限节点的布尔判定
  2. JS 按 auth 匹配显隐cols/toolbar 里的按钮对象带 auth: '{动作}',框架读取对应 data-auth-{动作} 值决定渲染与否
    • 实测:system/admin/index.jstoolbar 默认含 add操作列含 password 自定义按钮 + edit + delete
    • 默认值:operat.auth = operat.auth || 'add'admin-table.js:1105)——未写 auth 的按钮按 add 节点判定

验证口诀:auth: 'X'JS必须能在表格容器找到对应的 data-auth-XHTML且该值=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/jsonisJson=true不带 get_page_data 参数 true index 走 ajax 分支返回分页 JSONPOST 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:callHTML 模式请求页面 URL不带 Accept:json),断言 HTTP 200、响应是 HTML、无 PHP Notice / 模板变量未定义错误
  • 重点看:Undefined variableTemplateNotFoundException、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打开页面需登录态逐项核对
    1. 表格表头/列按 cols 渲染(字段、标题、关联字段显示正确)
    2. 搜索区下拉选项 = Page Data 模式里 select_list_* 的内容(对照步骤 3 结果)
    3. 工具栏按钮:data-auth-X=1 的按钮出现、=0 的消失;auth: 'X'JSdata-auth-XHTML一一对应
    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 档390x844iPhone 12/13/14、393x852Pixel 7、375x667小屏 iPhone、360x800常见 Android
  • 平板 / 折叠屏768x1024iPad
  • 重点看:表格横向滚动、操作列 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
  • 登录态失效排查:若仍提示「请先登录」,多为命令行与服务端缓存不共享;确认走同一套 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:callAccept: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:15status 字段)
search: 'range' 时间范围 app/admin/view/system/timer_log/index.js:65start_time
search 默认 true / searchOp 默认 %*% admin-table.js:307, admin-table.js:312
formatFilter/formatOpfilter/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
操作列 authpassword 自定义按钮) 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-58admin-table.js:1113-1124
自定义 templet状态徽章 app/admin/view/system/timer_log/index.js:3-11statusTemplet
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 相关技能

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 支持函数与字符串两种形式;未声明时默认恒真(总是显示)