1. 页面概览
1.1 是什么
智能报表页(报表引擎)是 LightFoundry 在 V5 Stage 3(B3-2)交付的分析协作闭环能力,对标 OpenFoundry report-service 的「模板 + 调度 + 分发」三段式模型。页面把「报表定义 → 立即/定时生成快照 → 按渠道分发 → 历史回放」串成一条完整链路:
- 报表定义:一张报表由名称、描述、调度 cron、分发渠道与收件人,以及一组报表块构成。报表块分三类——
chart(引用仪表盘图表)、text(markdown 正文)、table(取数表格,取数源为ref_chart_id/metric_name/object_query三选一)。 - 生成快照:点「立即生成」调后端
GenerateSnapshot逐块取数,渲染出 markdown + HTML 双形态快照并落fr_report_runs;点「生成并分发」在生成后按报表渠道(email/feishu/internal)派发。 - 快照预览:运行历史中每次运行可「预览」——右侧抽屉支持 Markdown / HTML 渲染 / 分发结果三个 Tab 切换查看。
- 调度:报表可配置 cron 表达式(如
0 9 * * *),保存后由平台 scheduler 定时自动生成(+ 可选分发),实现「运营日报每天早上 9 点自动产出并发送」。
数据落库三张表:fr_reports(报表定义)、fr_report_runs(运行快照)、fr_notifications(internal 渠道站内通知)。块引用 metric 时自动记录血缘(RecordMetricToReport),分发与审计形成闭环。
1.2 核心价值
| 维度 | 说明 |
|---|---|
| 三段式语义 | 模板(报表块)→ 生成(快照)→ 分发(email/internal/feishu),对标 OpenFoundry report-service |
| 三类块 | chart(图表引用)+ text(markdown 正文)+ table(取数表格,OOL/指标/图表三源) |
| 双形态快照 | 每次运行落 markdown + HTML 双渲染,供预览、归档与二次加工 |
| 调度自动化 | cron 定时生成(+分发),实现日报 / 周报自动产出 |
| 多渠道分发 | email(SMTP 邮件)、internal(站内通知落表 + 审计)、feishu(无引擎时降级标注) |
| 血缘与审计 | 块引用 metric 打血缘;internal 分发落 fr_notifications + 审计,全程可回放 |
1.3 一句话总结
2. 访问入口
2.1 路由与菜单
- 路由:
/foundry/reports - 路由名称:
FoundryReports - 路由标题:
智能报表 - 菜单位置:Foundry 左侧边栏「智能报表」(FoundryLayout 菜单项)
- 源码文件:
action/web/src/views/ReportPage.vue(478 行)
2.2 认证与权限
- 需要登录:是(路由
meta.requiresAuth = true,挂 FoundryLayout) - 登录方式:AIP 统一登录,
reportApi.js请求拦截器自动附加localStorage.aip_token为Authorization: Bearer <token> - 页面本身无角色限制;报表
owner由服务端currentUserID(c)无条件覆盖请求体中的 owner,防伪造归属 - 分发目标:收件人为「邮箱地址或 user_id」的逗号分隔列表;email 渠道需平台 SMTP 已配置,internal 渠道写入站内通知
- 404 排错:访问
/foundry/reports出现 404 时,先确认 Foundry 后端(18081)已启动、前端路由已注册、Vite 代理/api指向 18081
2.3 端口与 API 前缀
- 后端端口:18081(Foundry)
- 前端 API 前缀:
/api/v1(独立reportApi.jsaxios 实例,baseURL = '/api/v1') - 请求超时:默认 60000ms(60 秒);运行类请求(
runReport/dispatchReport)放宽到 120000ms(120 秒)
3. 界面布局
页面采用左右两栏布局(rp-layout,左侧 240px 固定,右侧自适应):
┌───────────────┬──────────────────────────────────────────────────┐ │ 左栏:报表列表 │ 右栏:当前报表 │ │ ① 新建输入+新建 │ ② 报表定义卡片(名称/描述/调度/渠道/收件人+操作) │ │ ③ 报表列表 │ ④ 报表块编辑器(chart/text/table 块 + 添加按钮) │ │ (on/off) │ ⑤ 运行历史卡片(表格 + 预览按钮) │ │ │ ⑥ 快照预览抽屉(Markdown/HTML/分发结果) │ └───────────────┴──────────────────────────────────────────────────┘
各板块职责:
- ① 左栏新建区:
newName输入框(placeholder「新报表名称」,回车即新建)+「新建」按钮。 - ③ 左栏列表:每项显示报表名称与
enabled徽标(on绿色status-active/off灰色status-archived),副标题为updated_at;点击切换当前报表(openReport);当前项高亮。 - ② 报表定义卡片:五个字段(名称 * / 描述 / 调度 cron / 渠道 / 收件人)+ 四个操作按钮(保存 / 停用·启用 / 立即生成 / 生成并分发)。
- ④ 块编辑器卡片:纵向排列报表块(每块左上角类型徽标 chart 橙 / text 紫 / table 蓝),支持类型切换、标题编辑、参数配置与移除;底部三个添加按钮(+ chart 块 / + text 块 / + table 块)。
- ⑤ 运行历史卡片:表格展示每次运行(时间 / 状态 / 块数 / 摘要 / 操作-预览),右上角「刷新」按钮。
- ⑥ 快照预览抽屉:点击「预览」从右侧滑出(
drawer-mask+snapshot-drawer),含 Markdown / HTML 渲染 / 分发结果三个 Tab;点击遮罩或「关闭」收起。
未选择报表时右栏显示占位文案「从左侧选择或新建一个报表开始。」
4. 交互元素详解
4.1 报表定义字段
| 字段 | 位置 | 含义 | 必填 / 默认值 | 操作效果 |
|---|---|---|---|---|
| 名称 * | 报表定义卡片 | 报表名称 | 必填 | 空名触发「立即生成 / 生成并分发」时前端提示「请先填写报表名称」 |
| 描述 | 名称右侧 | 报表说明 | 非必填 | 落 description(text 列) |
| 调度 | 第 2 行左 | cron 表达式(如 0 9 * * *) | 非必填,留空不定时 | 保存时后端按新 schedule 重注册调度任务 |
| 渠道 | 第 2 行右 | 逗号分隔:email / feishu / internal | 非必填 | 保存时按逗号拆分、去空格、去空项 |
| 收件人 | 第 3 行 | 逗号分隔:邮箱地址或 user_id | 非必填 | 同上拆分;email 用邮箱,internal 用 user_id |
提交到后端时这些字段名分别为:name、description、schedule、channels(数组)、recipients(数组)。
4.2 报表操作按钮
| 按钮 | 含义 | 操作效果与触发调用 |
|---|---|---|
| 保存 | 保存报表定义 | 调 updateReport(id, toApiPayload(form));提示「报表已保存(调度/渠道变更即时生效)」并刷新列表 |
| 停用 / 启用 | 切换 enabled | 调 updateReport(id, {enabled: !form.enabled});成功提示「报表已停用」/「报表已启用」,左栏徽标切换 on/off |
| 立即生成 | 仅生成快照,不分发 | 先保存编辑内容 → 调 runReport(id, false) → 自动加载快照抽屉 |
| 生成并分发 | 生成快照并按渠道分发 | 先保存编辑内容 → 调 runReport(id, true) → 后端 Dispatch → 加载快照抽屉查看分发结果 |
「立即生成」与「生成并分发」在生成前都会先调 updateReport 把当前编辑内容(调度 / 渠道 / 块配置)同步到后端,保证「以最新编辑为准」。
4.3 报表块编辑器
每块包含:类型徽标(bt-badge)、标题输入(block.title,占位「块标题」)、类型下拉(chart / text / table)、「移除」按钮(removeBlock(idx) 就地 splice)。
| 块类型 | 参数区 | 说明 |
|---|---|---|
| chart | ref_chart_id *(数字)+ metric_name(血缘打点,可选) | 绑定仪表盘图表,运行时经 ChartDataProvider 取数;metric_name 填写后快照生成时调 RecordMetricToReport 打血缘 |
| text | content textarea(占位「markdown 正文」) | markdown 正文直取,快照直接内嵌 |
| table | 取数源三选一:ref_chart_id / metric_name / object_query | object_query(OOL 对象查询)含 aggregate(sum/avg/count/min/max)、metric_field、object_type_id、dimensions(逗号分隔) |
object_query 仅在 block.object_query 已存在时展示(table 块新建时默认带 {aggregate:'sum', metric_field:'', object_type_id:0, dimensionsText:'', filters:[]})。提交时 normalizeBlock 会过滤空值:ref_chart_id / metric_name / content 非空才带,dimensions 由逗号拆分后非空才带,filters 非空数组才带。
4.4 添加块按钮
| 按钮 | 效果 |
|---|---|
| + chart 块 | addBlock('chart'):{type, title:'', ref_chart_id:null, metric_name:''} |
| + text 块 | addBlock('text'):{type, title:'', content:'本节正文'} |
| + table 块 | addBlock('table'):{type, title:'', ref_chart_id:null, metric_name:'', object_query:{...}} |
4.5 运行历史表格
| 列 | 含义 |
|---|---|
| 时间 | run.created_at(fmtTime 格式化) |
| 状态 | success(绿 status-active)/ failed / partial(红 status-error 徽标统一用 'status-' + (run.status === 'success' ? 'active' : 'error')) |
| 块数 | (run.snapshot && run.snapshot.blocks ? run.snapshot.blocks : []).length |
| 摘要 | run.error(失败/降级摘要,红字单行省略) |
| 操作 | 「预览」按钮 → previewRun(run) 调 getReportSnapshot(run.id) 打开快照抽屉 |
4.6 快照预览抽屉
| Tab | 内容 |
|---|---|
| Markdown | <pre class="snapshot-md">{{ snapshot.markdown || '(空)' }}</pre> |
| HTML 渲染 | <div class="snapshot-html" v-html="snapshot.html || '<p>(空)</p>'"></div> |
| 分发结果 | 无分发显示「尚未分发」;否则 plain-table 逐渠道展示(渠道 / 状态 / 说明) |
抽屉头部显示 run <id> 与快照 note(若有);「关闭」按钮或点击遮罩关闭。生成(立即生成 / 生成并分发)后会自动打开抽屉并定位到 Markdown Tab。
5. 后端关联
5.1 API 客户端
- 文件:
action/web/src/api/reportApi.js baseURL: '/api/v1',默认timeout: 60000,请求头Content-Type: application/json- 请求拦截器:附加
aip_tokenBearer;响应拦截器:401 清 token 并跳登录 - 导出函数:
listReports/createReport/getReport/updateReport/deleteReport、runReport(id, dispatch)(120s)、dispatchReport(id, payload)(120s)、listReportRuns(id, limit)、getReportSnapshot(runId)
5.2 端点表(前缀 /api/v1,均挂 protected)
| 方法 | 路径 | 请求体 / 参数 | 超时 | 用途 |
|---|---|---|---|---|
| GET | /reports | — | 60s | 报表列表 |
| POST | /reports | {name, description, blocks, schedule, channels, recipients} | 60s | 创建报表 |
| GET | /reports/:id | — | 60s | 报表详情 |
| PUT | /reports/:id | 指针语义(部分字段)+ 调度重注册 | 60s | 更新报表 |
| DELETE | /reports/:id | — | 60s | 删除报表(级联 runs/notifications + 调度反注册) |
| POST | /reports/:id/run | {dispatch?: bool} | 120s | 生成快照(可选分发) |
| POST | /reports/:id/dispatch | {run_id?, channels?, recipients?} | 120s | 对最近/指定 run 按渠道分发 |
| GET | /reports/:id/runs?limit= | 查询参数 limit | 60s | 运行历史(快照摘要) |
| GET | /report-runs/:id/snapshot | — | 60s | 单次运行快照(markdown/html/blocks) |
5.3 响应结构(JSON 示例)
报表详情 GET /reports/:id(data 即 Report):
{ "code": 0, "data": {
"id": "...", "rid": "report://foundry/...", "name": "运营日报",
"description": "每日销售概览", "enabled": true, "owner": "550e8400-...",
"schedule": "0 9 * * *",
"channels": ["email", "internal"], "recipients": ["u1@example.com", "u2"],
"blocks": [
{ "type": "text", "title": "摘要", "content": "本节正文" },
{ "type": "chart", "title": "销售趋势", "ref_chart_id": 12, "metric_name": "order_total" },
{ "type": "table", "title": "TOP 客户", "ref_chart_id": 13 }
],
"created_at": "...", "updated_at": "..."
} }
运行 POST /reports/:id/run(dispatch=true 时 data 含 run + dispatch):
{ "code": 0, "data": {
"run": { "id": "run-uuid", "report_id": "...", "status": "success",
"error": "", "created_at": "...", "finished_at": "..." },
"dispatch": [
{ "channel": "email", "status": "delivered", "note": "" },
{ "channel": "internal", "status": "delivered", "note": "" }
]
} }
快照 GET /report-runs/:id/snapshot:
{ "code": 0, "data": {
"run": { "id": "run-uuid", "report_id": "...", "status": "partial", "error": "feishu: 未配置渠道" },
"snapshot": {
"markdown": "# 运营日报\n\n## 摘要\n本节正文\n\n| 客户 | 金额 |\n|---|--:|\n| 张三 | 1200 |",
"html": "<h1>运营日报</h1>...",
"blocks": [ { "title": "摘要", "type": "text", "content": "本节正文" },
{ "title": "销售趋势", "type": "chart", "columns": ["date", "amount"], "rows": [["2026-08-29", 1200]] } ],
"dispatch": [ { "channel": "email", "status": "delivered", "note": "" } ]
}
} }
5.4 关联模块
| 后端包 / 表 | 职责 |
|---|---|
foundry/report/models.go | Report(fr_reports)、ReportRun(fr_report_runs)、ReportNotification(fr_notifications)、Snapshot、DispatchItem、OOLQuery |
foundry/report/rest.go | REST handler:reports CRUD + run / dispatch / runs / snapshot |
foundry/report/service.go | 业务服务:GenerateSnapshot、Dispatch、ListRuns、调度注册 |
foundry/dashboard | chart 块取数源 ChartDataProvider(dashboard.GetChartData) |
foundry/semantic | table 块 OOL 取数(semantic.TranslateOOL),经 reportOOLExecutor 适配器执行 |
foundry/metric | table 块指标取数 MetricProvider(metric.QueryMetric) |
platform/mailer | email 渠道 MailSender 接口;foundry/server 以 foundryMailer 注入 |
platform/scheduler | sched.Register("report:"+rid, schedule, ...) 定时生成 + 分发 |
platform/audit | internal 分发 + 血缘记录(SetAudit / SetLineage 注入) |
5.5 关键机制
- 运行状态机:
success(快照生成 + 全渠道分发成功)/failed(快照生成失败)/partial(快照生成成功但部分渠道降级或失败,如 feishu 未配、email 未配置)。 - 分发结果状态:
delivered(投递成功)/degraded(渠道降级,未配置 → 标注)/failed(投递失败,如 email 发送错误)/skipped(渠道跳过,如 recipients 为空)。 - 调度:
PUT /reports/:id保存时后端按新 schedule 重注册调度任务("report:"+rid);DELETE反注册;enabled=false停止定时触发。 - 快照结构:
Snapshot{markdown, html, blocks[], note, dispatch[]},blocks每项为BlockResult{title, type, content | columns+rows, note}。 - 血缘打点:块引用 metric(
metric_name)时,生成快照调RecordMetricToReport(注入的LineageRecorder接口,nil 跳过)。 - 分发重放:
POST /reports/:id/dispatch可对最近一次运行(或指定run_id)重新分发,channels/recipients 缺省用报表配置;无运行记录时返回 404(「该报表尚无运行记录,请先 POST /reports/:id/run 生成」)。 - Owner 防伪造:
handleCreateReport无条件以服务端currentUserID(c)覆盖请求体owner。
6. 核心流程详解
6.1 新建报表主流程
- 左栏输入新报表名称(或直接回车)→ 点「新建」。
handleCreate:构造emptyForm()(enabled=true、blocks=[]),blocks = [newBlock('text')]默认带一个 text 块(content「本节正文」),调createReport(toApiPayload(f))。- 成功提示「报表已创建」、清空输入、刷新列表,并
openReport(data.data)拉详情填充表单与运行历史。
6.2 立即生成快照流程
- 打开报表,确认名称非空,点「立即生成」(
handleGenerate(false))。 - 先调
updateReport(id, toApiPayload(form))保存最新编辑 → 调runReport(id, false)。 - 后端
GenerateSnapshot:逐块取数(chart 经 ChartDataProvider、table 经 OOL/指标、text 直取)→ 组装 markdown + HTML 快照 → 落fr_report_runs。 - 前端提示「快照已生成」,
loadRuns()刷新历史,随后getReportSnapshot(run.id)自动打开快照抽屉(Markdown Tab)。
6.3 生成并分发流程
- 点「生成并分发」(
handleGenerate(true))→ 先保存 → 调runReport(id, true)。 - 后端生成快照后,读取报表
channels/recipients调Dispatch:email 走 mailer、internal 落fr_notifications+ 审计、feishu 无引擎时降级标注。 - 返回
{run, dispatch[]};前端提示「快照已生成并分发」,加载快照抽屉,可在「分发结果」Tab 查看各渠道状态(delivered / degraded / failed / skipped)。
6.4 快照回放流程
- 运行历史中点「预览」(或生成后自动打开)→
getReportSnapshot(run.id)。 - 抽屉内三个 Tab 切换查看:Markdown(原始 markdown 文本)/ HTML 渲染(
v-html渲染)/ 分发结果(各渠道状态表)。 - 点「关闭」或点击遮罩收起;
snapshot置空。
6.5 调度定时流程
- 在「调度(cron 表达式,留空不定时)」填
0 9 * * *,点「保存」。 - 后端
sched.Register("report:"+rid, schedule, ...):到点自动GenerateSnapshot,并按报表渠道分发。 - 定时生成的运行同样落
fr_report_runs,可在运行历史回放;enabled=false或删除报表后调度停止/反注册。
7. 权限与安全
- 认证:全部端点要求
aip_token(protected 组);401 时reportApi.js响应拦截器清理 token 并跳登录。 - 归属隔离:报表
owner由服务端currentUserID(c)注入,创建时无条件覆盖请求体 owner(防跨用户伪造);列表按 owner 过滤。 - 数据级安全:chart/table 块取数继承当前用户的 RLS/CLS 与对象可见性;OOL 经
semantic.TranslateOOL翻译执行。 - 写操作防护:生成前自动保存当前编辑,保证以最新配置为准;生成中
busy置灰禁用「立即生成 / 生成并分发」;块「移除」与报表删除均有确认提示(本页删除报表走前端 confirm 前的业务逻辑,未提供前端删除按钮)。 - 分发安全:收件人列表由报表 owner 配置;email 未配置 SMTP 时渠道降级 / 失败并写入
note,不影响快照生成。
8. 常见问题与排错
8.1 生成快照报错 / 状态 failed
- 现象:「立即生成」后提示「生成失败:...」,运行历史该条状态
failed。 - 原因与排查:① 报表块取数源配置错误(如
ref_chart_id指向不存在的图表)——对照仪表盘页确认图表 ID;②object_query的object_type_id/metric_field有误——用对象查询页验证 OOL 可执行;③ 当前用户对取数对象无权限(RLS/CLS 拦截);④ 查看运行历史「摘要」列的run.error具体文案与后端日志。
8.2 分发结果显示 degraded / failed
- 现象:生成并分发后「分发结果」Tab 某渠道显示
degraded或failed。 - 原因:①
feishu渠道——Foundry 无飞书引擎,未配 FeishuSender 时降级标注(degraded);②email渠道——平台 SMTP 未配置或发送失败(failed);③skipped——recipients 为空。 - 排查:确认报表「渠道」与「收件人」已填写;对照
platform/mailer配置;feishu 渠道仅 AIP 注册了渠道,Foundry 运行时降级为标注。
8.3 快照预览空白
- 现象:打开抽屉后 Markdown / HTML 均显示「(空)」。
- 原因:该次运行未成功生成快照(状态
failed),或快照 JSON 为空。 - 处理:重新「立即生成」;仍为空检查
GET /report-runs/:id/snapshot响应中data.snapshot是否为空对象(历史数据容错返回空快照)。
8.4 保存后调度不生效
- 现象:填了 cron 并保存,到点没有自动生成。
- 原因与排查:① 报表
enabled为 off(左栏徽标灰色)——点「启用」;② cron 表达式格式非法——后端注册失败,检查调度表达式(如0 9 * * *);③ 后端进程未启动 scheduler——确认 Foundry 服务正常;④ 查看后端日志sched.Register("report:"+rid, ...)是否报错。
8.5 无法用「生成并分发」以外的方式单独分发
- 现象:想对历史某次运行重新分发,但页面只有「生成并分发」。
- 说明:前端
reportApi.js已导出dispatchReport,但ReportPage.vue未提供独立分发按钮(仅 run 时带 dispatch=true 触发);如需对最近一次运行重发,可调POST /api/v1/reports/:id/dispatch(body 可空)实现。此属前端能力缺口,见「已知缺陷」。
9. 已知缺陷与边界
| 项目 | 说明 |
|---|---|
| feishu 渠道降级 | Foundry 无飞书引擎,feishu 渠道未配 FeishuSender 时运行状态为 partial、分发结果为 degraded(仅标注);feishu 完整能力仅在 AIP |
| notify Hub 进程内 | 站内通知 Hub 为进程内实例,跨产品不共享 |
| text 块直取 | text 块 markdown 正文直取入快照,无模板变量 / 占位符替换能力 |
| 快照数据量 | 快照内嵌完整 blocks 结果(columns+rows),大报表会增大 fr_report_runs.snapshot 体积 |
| 调度精度 | cron 定时依赖 platform/scheduler 进程存活;重启后调度随服务启动重新注册 |
注:无独立分发入口已于 2026-09-06 修复(历史运行行新增「再分发」按钮,按 run_id 对该次运行重新分发)。
注:前端无删除报表入口已于 2026-09-06 修复(列表项新增「删除」按钮,confirm 确认后调 DELETE /reports/:id 并刷新列表)。