1. 页面概览
1.1 是什么
对象查询页面(ObjectQueryPage.vue)让用户以「选择对象 → 勾选字段 → 配置过滤/排序 → 执行」的方式对本体对象做结构化查询,结果以二维表格(行 × 列)渲染。它走的是 POST /objects/:id/query 语义查询接口:
- 对象驱动:先从对象类型下拉选择目标对象(如订单),页面自动加载该对象的属性清单、链接与动作信息;
- 字段多选:查询列从对象属性中勾选,不选 = 全部列;
- 过滤动态行:任意添加「字段 + 算子 + 值」过滤条件行,支持 eq/ne/gt/gte/lt/lte/like/contains/in/is_null 十种算子;
- 页码式分页:可指定排序字段、降序开关、每页行数(
page_size,上限 500)并翻页(page,从 1 起);后端响应不含总数,下一页按「满页」启发式判断; - CSV 导出:一键导出当前页结果为 CSV(纯前端导出,UTF-8 BOM 防 Excel 中文乱码,文件名含对象类型与页码);
- 安全兜底:查询由后端翻译(白名单校验防注入)+ RLS/CLS 只读注入 + 真实数据源执行,前端不可绕过;
- 编辑态可见:若对象存在「编辑态叠加」(草稿修改未合入),被覆盖的属性列会带「已编辑」标记,结果叠加最新编辑值。
本页与「SQL 工作台」形成互补:SQL 工作台用 FROM 对象名 写自由 SQL,本页则是表单化的 OOL 查询构造器,把对象查询语法(字段/过滤算子/排序/page/page_size/params)显式暴露给用户操作。
1.2 核心价值
| 维度 | 说明 |
|---|---|
| 表单化 OOL | 无需写 SQL,下拉+勾选+动态行即可构造对象查询 |
| 十种过滤算子 | eq/ne/gt/gte/lt/lte/like/contains/in/is_null 覆盖常规条件 |
| 页码分页 + CSV 导出 | 支持 page/page_size 翻页(下一页按满页启发式);「导出 CSV(当前页)」纯前端下载,UTF-8 BOM |
| 安全闸门 | 后端白名单校验 + RLS/CLS 只读注入 + 参数绑定,防注入防越权 |
| 编辑态可见 | 未合入的草稿修改会叠加到结果并用「已编辑」标记说明 |
| 二维结果 | 行 × 列表格直接呈现,与后端 columns/rows 结构一一对应 |
1.3 一句话总结
2. 访问入口
2.1 路由与菜单
- 路由:
/foundry/query - 路由名称:
FoundryQuery - 菜单位置:Foundry 左侧边栏「对象查询」(
FoundryLayout.vue菜单第 14 项,位于「语义检索」之后、「审计管理」之前) - 源码文件:
action/web/src/views/ObjectQueryPage.vue(369 行)
2.2 认证与权限
- 需要登录:是(路由
meta.requiresAuth = true) - 登录方式:AIP 统一登录,
localStorage.aip_token作为 Bearer Token 自动附带 - 页面本身无角色限制,但查询结果受三层约束:对象级
ontology:<对象名>:read(无读权限直接 403)、行级 RLS 过滤、列级 CLS 隐藏(无读权限列置 null);后端查询入口还会把当前用户注入params.user_id供注入判定 - 404 排错:若访问
/foundry/query出现 404,先确认 Foundry 后端(端口 18081)已启动、前端路由已注册(router/index.js第 257~261 行)
2.3 端口与 API 前缀
- 后端端口:18081(Foundry)
- 前端 API 前缀:
/api/v1(client.js,超时 30 秒) - 注意:执行查询的路径是
/objects/:id/query(不带/ontology前缀);加载对象列表与详情则用/ontology/objects与/ontology/objects/:id
3. 界面布局
页面纵向分为 5 个板块(从上到下):
┌──────────────────────────────────────────────────────────────┐ │ ① 页头:标题「对象语义查询」 │ │ ② 操作结果提示条(alert,失败红,可关闭) │ │ ③ 选择对象卡片 │ │ ├─ 对象类型 * 下拉(显示名(name · #id)) │ │ └─ 对象信息(display_name · 表 xxx · 数据源 #N) │ │ ④ 查询配置卡片(选中对象后出现) │ │ ├─ 查询字段 fields 多选(不选=全部列,计数标签) │ │ ├─ 过滤条件 filters(+ 过滤条件 动态行) │ │ └─ 排序字段 order_by / 每页行数 page_size / 降序 order_desc │ │ └─ [查询] 按钮 │ │ ⑤ 查询结果卡片(本页 N 行 × M 列 + 已编辑列标记 │ │ + [导出 CSV(当前页)] + 翻页条[上一页/下一页]) │ └──────────────────────────────────────────────────────────────┘
各板块职责:
- ① 页头:标识本页能力。
- ② 提示条:通用 alert,加载失败等场景提示。
- ③ 选择对象:唯一入口,选中对象即触发
loadDetail拉取属性清单。 - ④ 查询配置:字段多选、过滤动态行、排序与页码分页(
page/page_size);仅在对象详情加载成功后显示。 - ⑤ 查询结果:二维表格 + 「已编辑」列标记 + 「导出 CSV(当前页)」按钮 + 翻页条;查询出错时单独红条显示
queryError。
4. 交互元素详解
4.1 对象类型下拉
| 属性 | 值 |
|---|---|
| 控件 | <select v-model.number=selectedObjectId> |
| 数据来源 | GET /api/v1/ontology/objects(onMounted(fetchObjects) 拉取,data.data 数组) |
| 占位项 | 「选择对象类型」(value=null,disabled) |
| 选项格式 | display_name || name(name · #id),value 为 object_type.id |
| 交互效果 | @change="loadDetail" 选中即加载详情;切换对象时清空上次结果与错误 |
4.2 对象信息展示
选中对象后 loadDetail 调 GET /ontology/objects/:id,data.data 存为 detail,展示「display_name || name · 表 base_table || '-' · 数据源 #data_source_id」;未选中时显示灰色「选择对象后自动加载属性清单...」。loadDetail 还会把 queryForm.page 重置为 1、并把 queryForm.page_size 兜底为 100(page_size || 100),同时清空上次结果与错误。
4.3 查询字段 fields 多选
| 属性 | 值 |
|---|---|
| 控件 | 每个属性一个 checkbox(.row-check),标签为 p.name + 灰色 p.data_type |
| 数据来源 | detail.properties(对象详情属性数组) |
| 计数 | 标题右侧蓝色计数 {{ queryForm.fields.length }} |
| 语义 | 不选 = 全部列(请求体传空数组 fields: []);选中的属性名进入 fields |
| 空态 | 对象无属性时显示「该对象暂无属性。」 |
4.4 过滤条件 filters 动态行
| 属性 | 值 |
|---|---|
| 添加按钮 | 「+ 过滤条件」(.sub-title 右侧小按钮),push 一条 {field:'', op:'eq', value:''} |
| 行结构 | 字段下拉 + 算子下拉 + 值输入框 + 「删除」按钮 |
| 字段下拉 | 选项为 detail.properties[].name,占位「字段」(disabled 空值) |
| 算子下拉 | filterOps 十种:eq, ne, gt, gte, lt, lte, like, contains, in, is_null |
| 值输入框 | placeholder「值(in 用逗号分隔)」;算子为 is_null 时输入框禁用(无需值) |
| 删除按钮 | 「删除」调 removeRow(filters, i) 从数组移除该行 |
| 空态 | 无过滤条件时显示「暂无过滤条件。」 |
| 提交处理 | 只提交 f.field 非空的行;op !== 'is_null' 时带 value,is_null 省略 value |
4.5 排序与分页
| 字段 | 控件 | 说明 |
|---|---|---|
| 排序字段 order_by | <select> | 选项为属性名,空值「不排序」;建议指定以保证翻页稳定 |
| 每页行数 page_size | <select> | 选项 20/50/100/200/500,默认 100;上限 500(后端钳制,前端 clampPageSize 同步兜底:非正回退 20、超限钳 500);切换即回第 1 页并自动重查 |
| 降序 order_desc | checkbox | 勾选后请求体 order_desc: true |
配置区下方有提示文案,说明「页码式分页:page 从 1 起,page_size 上限 500;后端不返回总数,下一页按满页启发式判断;建议指定排序字段」。
4.6 「查询」按钮
| 属性 | 值 |
|---|---|
| 按钮文字 | 「查询」(查询中变「查询中...」并禁用) |
| 前置校验 | 未选对象时提示「请先选择对象类型」 |
| 请求体 | {fields, filters, order_by, order_desc, page, page_size, params: {}}(不再传 limit/offset) |
| 失败 | queryError 红条显示「查询失败:<error>」,结果清空 |
| 成功 | queryResult = {columns, rows, editedFields}、exec = {page, page_size}、queried = true |
| 空页回退 | 若返回 0 行且当前页 > 1,提示「第 N 页无数据(可能已翻过头),已回退到第 N-1 页」并自动回退重查(仅回退一次) |
4.7 查询结果表格
| 元素 | 说明 |
|---|---|
| 标题 | 「查询结果」+「本页 N 行 × M 列 · 第 P 页」 |
| 「导出 CSV(当前页)」按钮 | 结果卡片右上角;纯前端导出当前页(UTF-8 BOM 防 Excel 中文乱码),文件名 <对象名>_page<P>.csv;无数据时禁用 |
| 表头 | queryResult.columns 逐列渲染;列在 editedFields 中时追加「已编辑」黄色小标签(title「该列叠加了编辑态未合并的值」) |
| 单元格 | formatCell(cell):null 显示空串,对象/数组转 JSON,其余 String() |
| 空行 | rows.length === 0 时渲染跨列灰字「无查询结果」 |
| 翻页条 | 「上一页」「第 P 页」「下一页」;exec.page > 1 才可上一页,下一页要求「本页行数 == 请求 page_size」(满页启发式);旁边常驻提示「后端不返回总数,下一页按满页判断(本页 N / page_size 行)」 |
| 未查询态 | !queryResult && queried 时显示「请先选择对象并配置查询条件,然后点击"查询"。」 |
5. 后端关联
5.1 API 客户端
client.js:baseURL: /api/v1,超时 30 秒;请求拦截器附Authorization: Bearer <aip_token>;响应拦截器 401 清理并跳/login。- 页面直接使用
apiClient.get('/ontology/objects')、apiClient.get('/ontology/objects/:id')、apiClient.post('/objects/:id/query', body),无独立 API 文件。
5.2 端点表
| 方法 | 路径 | 请求体 | 说明 |
|---|---|---|---|
| GET | /ontology/objects | — | 对象类型列表(data.data 数组,含 object_type) |
| GET | /ontology/objects/:id | — | 对象详情(data.data,含 object_type/properties) |
| POST | /objects/:id/query | OOL 查询请求体(支持 page/page_size) | 执行对象语义查询(路径不带 /ontology);响应 data 无 total |
| POST | /ontology/objects/:id/query | 同左 | 语义等价别名路径 |
查询请求体(后端 objectQueryRequest):
{
"fields": ["amount", "status"],
"filters": [
{ "field": "amount", "op": "gt", "value": 1000 },
{ "field": "status", "op": "in", "value": ["paid", "shipped"] },
{ "field": "deleted_at", "op": "is_null" }
],
"order_by": "created_at",
"order_desc": true,
"page": 1,
"page_size": 100,
"traverse": [],
"params": {}
}
分页字段口径:后端 objectQueryRequest 同时支持 limit/offset(既有契约)与 page/page_size(页码式便捷参数,D-B5-01);两者同给时 page/page_size 优先。page 从 1 起(≤0 回退第 1 页),page_size 上限 500(MaxPageSize,超出钳制为 500),非法/缺省回退默认 20;页码式会映射为 LIMIT page_size OFFSET (page-1)*page_size(semantic/query.go applyPaging)。本页已从 limit/offset 切换为 page/page_size。 响应体为 domain.QueryResult{columns, rows, edited_fields},不含 total/总数,故前端无法按总数计算总页数,只能用「满页」启发式决定下一页是否可用。
5.3 响应结构
统一包装 {code: 0, data: ...};data 即 domain.QueryResult:
{
"code": 0,
"data": {
"columns": ["id", "amount", "status", "created_at"],
"rows": [
[1, 2500.0, "paid", "2026-08-29T10:00:00Z"],
[2, 300.0, "shipped", "2026-08-28T09:00:00Z"]
],
"edited_fields": ["status"]
}
}
columns:查询结果列名数组(属性 api_name 或linkName.propName关联列);rows:二维数组,每行与columns一一对应;edited_fields:编辑态叠加(第 1 层)发生、被覆盖的属性 api_name 列表,叠加未发生时为空/缺省。
5.4 关联模块表
| 后端包 | 职责 |
|---|---|
foundry/semantic | SemanticQueryService.Execute:OOL 翻译(白名单校验 + RLS/CLS 注入)→ 连接器真实执行 → 编辑态叠加 |
foundry/server/semantic_handlers.go | handleObjectQuery:解析对象 id、注入 params.user_id、调用 Execute |
foundry/ontology | 对象类型/属性/链接元数据、GetLatestEditValues 编辑态读取 |
platform/connector | 数据源连接器执行物理查询 |
platform/auth | RLS/CLS 注入与对象级 ontology:<name>:read 授权 |
5.5 关键机制
OOL 过滤算子:后端语义包定义算子全集为 eq / ne / not_eq(ne 别名)/ gt / gte / lt / lte / like / contains / starts_with / in / is_null / and / or / not;前端页面暴露其中十种(eq/ne/gt/gte/lt/lte/like/contains/in/is_null)。in 的值可为标量/切片,页面提示「in 用逗号分隔」。值一律参数化绑定,不拼接进 SQL。
安全注入:handleObjectQuery 把 req.Params["user_id"] = currentUserID(c);语义服务 Translate 阶段做属性白名单校验(字段必须是本体属性集合成员,含 linkName.propName 关联属性),再经 ApplyRLS/ApplyCLS 注入只读安全口径。
对象级可见性:查询入口经 authorizeObjectRead 校验 ontology:<对象名>:read,无读权限返回 403 明确错误(不静默放行);内部调度路径或 apps 组件加载(APP-06 已知路径)可跳过。
编辑态叠加:applyEditOverlay 按主对象行 id 批量取 LoadEditValues 最新值覆盖源表列(仅主对象 t0 属性,traverse/link 关联对象列为后续层);被覆盖的属性写入 edited_fields 并剥离注入的主键列,保证输出列契约稳定。
Traverse 遍历式扩展:请求体 traverse 支持沿 link 进入相邻对象再过滤(then_filters/select 限相邻对象属性,禁点号引用,可嵌套 ≤3 层)。本页已在「查询配置」中提供「遍历 traverse」编辑区(扁平步骤 + 父步骤表达嵌套,含 link 选择、select 属性勾选、then_filters 过滤行;3 层上限硬约束),提交时组装为后端嵌套结构;API 调用方亦可直接构造该字段。
6. 核心流程详解
6.1 主流程:执行一次对象查询
- 页面加载
onMounted(fetchObjects)→GET /ontology/objects填充对象下拉; - 选择对象 →
loadDetail→GET /ontology/objects/:id→ 渲染属性多选/过滤字段/排序字段,page置 1、page_size兜底 100; - 勾选查询字段(不选=全部列),按需点「+ 过滤条件」添加过滤行并选算子填值;
- 选择排序字段、勾选降序、选择每页行数(page_size);
- 点「查询」→
POST /objects/:id/query(body 含page/page_size,见 5.2)→ 后端翻译+注入+执行; - 结果渲染二维表格,
edited_fields中的列显示「已编辑」标签;点翻页条「上一页 / 下一页」按page±1重查;点「导出 CSV(当前页)」下载当前页。
6.1.1 分页与「满页」启发式
后端查询响应体是 domain.QueryResult{columns, rows, edited_fields},不含 total,前端无法据此算总页数。因此「下一页」采用满页启发式:请求 page_size 条,返回行数 == page_size 时认为「可能还有下一页」并允许翻页,否则禁用下一页。若某页返回 0 行且页码 > 1,回退上一页并提示(仅回退一次,避免死循环)。翻页时 order_by 建议显式指定,否则无序结果的跨页边界可能不稳定。
6.2 分支流程:is_null 过滤
算子选 is_null 时值输入框禁用且提交时省略 value(后端 is_null 不需要值)。例:过滤已删除行用 {field: "deleted_at", op: "is_null"}。
6.3 分支流程:in 多值过滤
算子选 in 时值按逗号分隔输入,如 paid, shipped。注意:前端把值作为字符串提交(out.value = f.value),若需数值列表由后端语义解析处理;逗号分隔是页面提示约定。
6.4 结果导出(导出 CSV)
本页已提供内置「导出 CSV(当前页)」按钮(结果卡片右上角),导出当前页结果为 CSV:
- 范围:仅当前页
queryResult.columns×queryResult.rows(纯前端导出,不新增后端接口)。需要更多行时先调大page_size(上限 500)或翻页分别导出; - 编码:UTF-8 带 BOM(
\ufeff前缀),避免 Excel 打开中文乱码;单元格内如含逗号/引号/换行用双引号包裹并转义; - 文件名:
<对象 api_name>_page<P>.csv(如order_page2.csv,含对象类型与页码); - 错误处理:当前页 0 行时按钮禁用并提示「当前页无数据可导出」。
其它替代途径仍可选用:手动复制表格内容、SQL 工作台「导出 CSV」(写 SELECT ... FROM 对象名 ... 导出全量)、API 直接调用 POST /objects/:id/query 自行序列化(可配合 page/page_size 循环拉全量)。
6.5 编辑态叠加语义
当对象存在未合入的草稿修改(如 Action 测试/应用写入的编辑态),查询结果会叠加最新编辑值:被覆盖的属性列带「已编辑」标签;未 SELECT 该属性的列不受影响;聚合场景下编辑态叠加会回退为行级+内存聚合(超 10000 行标 approximate),保证行级与聚合一致。
7. 权限与安全
- 认证:axios 拦截器附带 Bearer Token;401 统一清理并跳登录。
- 对象级可见性:无
ontology:<对象名>:read权限时查询直接 403(不静默放行);admin 通配自动通过。 - RLS/CLS 只读注入:翻译后的物理 SQL 注入行级(RLS)与列级(CLS)安全;无读权限属性列置 null/隐藏,不整行拒绝。
- 防注入:字段/算子白名单校验(非本体属性成员直接拒绝)、值参数化绑定(
?占位符)、标识符合法性校验,前端无法构造任意 SQL。 - 写防护:本页为纯只读查询;写操作必须走 Action 唯一写路径。
8. 常见问题与排错
8.1 点「查询」提示「查询失败:...」
- 现象:结果区上方红条提示查询失败 + 后端 error 文案。
- 原因:请求被后端拒绝。常见:对象 id 非法(路径参数解析失败)、对象不存在(404
object type not found)、字段不在属性白名单、无读权限(403ontology:<name>:read)、数据源不可达。 - 排查步骤:① Network 面板看
POST /objects/:id/query状态码与{error};② 403 检查当前用户对对象/属性的读权限(换 admin 对比);③ 400/422 检查 fields 是否误填了非属性名(含大小写、含点号引用);④ 5xx 检查后端日志与数据源连通性。
8.2 「已编辑」列标记出现了但值「不新」
- 现象:某列带「已编辑」标签,但看到的值仍是旧值。
- 原因:编辑态叠加只覆盖「主对象 t0 属性」,且仅当该属性被 SELECT 且主键列参与查询时生效;关联对象(traverse/link)列为后续层不叠加。
- 排查步骤:① 确认字段列表包含该属性且包含主键属性;② 确认该对象确实存在未合入的草稿(可在「Action 测试」/应用写入侧验证);③ 查看请求体是否意外带了
traverse;④ 聚合场景确认是否命中 approximate 标注。
8.3 查询结果只有一行「无查询结果」
- 现象:表格只显示「无查询结果」跨列灰字。
- 原因:
rows为空——过滤条件过严或数据源该对象确实无数据。若带过滤条件,通常是is_null/in写法与数据不匹配。 - 排查步骤:① 逐步删除过滤行重查,定位是哪个条件筛空;②
in检查逗号分隔与数值类型(数值字段用gt/eq而非 in 的字符串值);③like/contains确认算子语义(like用%通配还是子串由后端定义);④ 确认为空数据而非权限问题(换 admin 对比)。
8.4 字段下拉/过滤字段为空
- 现象:选中对象后「查询配置」卡片未出现,或字段列表为空。
- 原因:
GET /ontology/objects/:id失败(详情未加载),或对象确实无属性。 - 排查步骤:① 顶部 alert 是否提示「加载对象详情失败」;② Network 检查详情请求状态码;③ 查看对象是否有
properties(可在「本体工作台」确认);④ 切换其他对象验证是否通用问题。
8.5 翻页「下一页」灰掉 / 看不到总页数
- 现象:结果有数据但「下一页」按钮禁用;或页面上只有「第 P 页」「本页 N 行」而看不到「共 X 页」。
- 原因:后端查询响应体
domain.QueryResult{columns, rows, edited_fields}不含 total,前端无法算总页数,改用满页启发式:本页行数 < 请求page_size即认为已到末页并禁用「下一页」。界面不显示总页数是如实体现该口径,不是缺陷。 - 排查步骤:① 看本页行数是否恰好等于
page_size(如 page_size=100 而返回 37 行 → 判为末页、禁用下一页);② 若确认还有数据,检查是否被过滤条件 / RLS 限制;③ 需要更大单页可调大page_size(上限 500),或用 API 按page递增循环拉取。
8.6 page/page_size 被钳制或回退
- 现象:填写的
page_size与实际返回行数上限不符,或某页无数据。 - 原因:后端对
page_size有上限 500(超出钳制)、非正回退 20;page<=0回退第 1 页。前端下拉已限制为 20/50/100/200/500,并做同样兜底(clampPageSize)。 - 排查步骤:① 确认选择的是下拉内的合法值;② 若翻到超出数据的页码,页面会自动回退上一页并提示「第 N 页无数据(可能已翻过头)」;③ 极端情况下(数据在两次翻页之间被删除)可能短暂落到空页,直接回上一页重查即可。
9. 已知缺陷与边界
| 项 | 说明 |
|---|---|
| 分页无总数(满页启发式) | 后端响应不含 total,前端「下一页」按「本页行数 == page_size」判断、无法显示总页数与精确页码跳转(如实边界,非缺陷) |
| 导出仅当前页 | 「导出 CSV」为纯前端、仅导出当前页结果;导出全量需调大 page_size(≤500)或翻页多次,或走 SQL 工作台 / API(可配合 page 循环拉取) |
| traverse 已提供编辑 UI(≤3 层) | 请求体透传 traverse(空数组时后端视为无 traverse)。页面新增「遍历 traverse」编辑区:以「扁平步骤 + 父步骤 id」表达嵌套;每步从当前层对象的链接定义下拉选择 link(含目标对象 id 与基数),可勾选相邻对象属性作为 select(输出列别名 linkName.propName),可添加 then_filters(字段限相邻对象属性、禁止点号引用)。最多 3 层(后端 maxTraverseDepth=3,semantic/traverse.go:22/46-49);到 3 层后「+ 子遍历」按钮禁用并提示「已达 3 层上限」;父 link 变更会清空其子级步骤。提交时组装为后端嵌套 TraverseQuery(semantic/query.go:116-121,请求体透传见 server/semantic_handlers.go:62/136)。覆盖范围:then_filters 复用页面过滤算子下拉(eq/ne/not_eq/gt/gte/lt/lte/like/contains/starts_with/in/is_null),不含 and/or/not 布尔组合算子(需嵌套 Filter 结构,表单未表达)。边界不变:关联列 linkName.propName 显示与「编辑态仅主对象 t0 属性叠加」(traverse/link 关联对象列不叠加)维持既有语义 |
| 过滤值统一字符串 | 前端把过滤 value 作为字符串提交,数值语义由后端按列类型解析;例外:in 算子(顶层 filters 与 traverse then_filters)的值由前端按逗号分隔或 JSON 数组转为数组后提交(后端 toAnySlice 拒绝字符串,semantic/query.go:955-964) |
| 编辑态仅主对象叠加 | traverse/link 关联对象列的编辑态为后续层,不叠加 |
| 关联列显示 | linkName.propName 关联字段若勾选会出现在 columns,单元格对象/数组转 JSON 文本 |
10. 2026-09-10 安全与行为修订
- Nexus 深链接入:页面消费
?name=query 参数定位本体对象,顶部全局搜索(Nexus)命中对象可直接深链跳转至本页并预选该对象。
11. 2026-09-13 分页与导出补全
- 分页契约切换:请求体由写死的
limit/offset改为page(从 1 起)/page_size(上限 500),字段名与后端foundry/server/semantic_handlers.go的objectQueryRequest、foundry/semantic/query.go的QueryRequest一致;页码式映射为LIMIT/OFFSET(applyPaging)。 - 翻页控件:结果卡片底部加「上一页 / 第 P 页 / 下一页」;因后端响应无 total,下一页按「满页」启发式(本页行数 == page_size)判断,界面常驻如实提示。
- CSV 导出:新增「导出 CSV(当前页)」,纯前端导出当前页、UTF-8 BOM、文件名
<对象名>_page<P>.csv;未新增/修改后端接口。 - 空页回退:某页返回 0 行且页码 > 1 时自动回退上一页并提示(仅回退一次)。
后端文件
action/products/foundry/semantic/query.go(OOL 翻译、Filter/QueryRequest/TraverseQuery、Execute、applyEditOverlay)action/products/foundry/server/semantic_handlers.go(handleObjectQuery/executeObjectQuery)action/products/foundry/server/server.go(对象查询路由与安全注入组装)action/platform/domain/query_result.go(QueryResult{columns, rows, edited_fields})
项目文档
action/wiki/upgrade-v5/dev-story/stage-1.md(B1-5 编辑态叠加)action/wiki/changelog/2026-08-29-2-v5-stage1.md(Stage 1 交付说明)action/wiki/frontend-intro-v5/markdown/foundry/index.md(Foundry 页面清单)