1. 页面概览

1.1 是什么

可视化仪表盘页面(路由 /foundry/dashboards,对应 TAD-08 可视化仪表盘)是 LightFoundry 把"数据洞察"组装成面板的操作台。它围绕三层实体运转:本体对象类型(数据从哪来)、图表(数据怎么展示)、仪表盘(图表怎么摆放与分享)。用户先在页面里创建绑定到本体对象类型的图表(选择图表类型、绑定对象、映射 X/Y 轴与分组属性),再创建仪表盘并把图表以栅格布局添加到盘中,最后可以把整个仪表盘分享给指定用户或角色。

本页的取数与传统 BI 有本质差异:图表直接绑定到本体对象object_type_id),数据加载复用 Foundry 的语义对象查询(semantic.QueryRequest),即"字段白名单 + RLS/CLS 只读注入 + 参数化执行"。因此仪表盘上每一张图的数据都天然受到本体权限体系的约束——用户看到的行/列就是语义查询按当前身份裁剪后的结果,不存在绕过权限的裸 SQL。

当前版本(MVP)图表数据以表格形式渲染(列 + 行),柱状图/折线图/饼图等类型的 ECharts 视觉化渲染由共享图表构建器 action/web/src/utils/chartBuilders.js 与按需注册 action/web/src/utils/echartsRegistry.js 支撑,被 Notebook、报表等页面的通用渲染组件(ChartRender.vue)复用;本页在数据层已经按"列 + 行"标准结构返回,后续可直接切换视觉渲染层。

1.2 核心价值

价值点说明
图表即语义图表绑定本体对象类型,字段选择来自对象属性白名单,数据口径与本体建模一致
权限内生图表取数经语义查询执行,RLS/CLS 只读注入按当前用户生效,分享他人也看到授权范围
仪表盘组装新建/打开/删除仪表盘,向盘中添加/移除图表,栅格宽度自适应(12 列栅格)
分享协作分享给指定用户(user_id)或角色(role),被分享者列表可见
类型可扩展图表类型枚举(bar/line/pie/table 等)与 ECharts option 映射集中管理,便于扩展视觉渲染

1.3 一句话总结

可视化仪表盘页把"本体对象 → 图表 → 仪表盘 → 分享"串成一条低门槛的可视化链路,让语义查询结果以可分享的面板形态交付给业务用户。

2. 访问入口

2.1 路由与菜单

项目
路由 path/foundry/dashboards
路由 nameFoundryDashboards
路由 title可视化仪表盘
requiresAuthtrue
菜单位置Foundry 左侧边栏(FoundryLayout.vue)菜单项「可视化仪表盘」
前端源码action/web/src/views/DashboardPage.vue
路由注册action/web/src/router/index.js

侧边栏菜单注册项为 { path: '/foundry/dashboards', label: '可视化仪表盘' };FoundryLayout 的全局搜索支持 dashboard://foundry/<id> 直达本页。

2.2 认证与权限

2.3 端口与 API 前缀

Foundry 后端端口 18081,API 前缀 /api(请求路径带 /v1)。开发环境 Vite 代理转发;跨产品共用前端时须按产品前缀分流(/api → 18081)。

3. 界面布局

+---------------------------------------------------------------------+
| 可视化仪表盘                     [刷新][新建图表][新建仪表盘|收起]      |
+---------------------------------------------------------------------+
| [操作结果提示 alert(可关闭)]                                         |
+---------------------------------------------------------------------+
| [新建仪表盘表单(点「新建仪表盘」展开)] 名称*/布局类型/描述 + 创建      |
+---------------------------------------------------------------------+
| 左侧:我的仪表盘(card,宽 280px)                                     |
|   仪表盘项:名称 + 「布局 xx · 已发布|草稿」 + [打开][分享][删除]        |
+---------------------------------------------------------------------+
| 右侧:当前仪表盘(dash-main)                                          |
|   - 未打开:提示选择左侧仪表盘打开                                     |
|   - 已打开:仪表盘头部(名称/描述/图表数)+ [分享][添加图表]            |
|            图表网格(12 列栅格):每个图表卡片 = 标题/类型徽标/          |
|            [↑][↓][宽度▾][编辑][刷新][移除] + 元信息(对象 #id · X/Y 轴) |
|            + 数据表格                                                  |
+---------------------------------------------------------------------+
| [图表创建/编辑弹窗] 名称*/描述/图表类型/绑定对象*/X轴/Y轴/分组 + 取消/保存 |
| [添加图表弹窗] 可选图表列表 + 宽度/高度 + 取消/添加                     |
| [分享弹窗] 目标(user|role)+目标值 + 分享;已分享列表 + 取消分享         |
+---------------------------------------------------------------------+

各板块职责:

4. 交互元素详解

4.1 页头操作区

元素位置含义必填与默认值操作效果触发的后端调用
刷新按钮页头右侧重新加载三类主数据并行刷新仪表盘、图表、对象类型GET /dashboards + GET /charts + GET /ontology/objects
新建图表按钮页头右侧打开图表创建弹窗打开弹窗,表单为空白(编辑态则由图表项进入)无(保存时才请求)
新建仪表盘/收起按钮页头右侧展开/收起创建表单切换 showDashboardForm无(提交时请求)

4.2 新建仪表盘表单

元素含义必填与默认值操作效果触发的后端调用
名称输入框仪表盘名称必填,placeholder「如 销售大盘」决定仪表盘标识POST /dashboards
布局类型下拉grid(栅格)/ freeform(自由)默认 grid决定布局体系同上
描述输入框仪表盘用途说明选填展示于仪表盘头部同上
创建按钮提交表单busy 时禁用成功后提示"仪表盘创建成功(id=...)"并收起表单POST /dashboards,body {name, description, layout_type}

4.3 仪表盘列表项

元素含义操作效果触发的后端调用
名称/元信息区显示名称与「布局 xx · 已发布/草稿」点击打开仪表盘GET /dashboards/:id
打开按钮打开选中仪表盘打开后自动加载全部图表数据(每张图调一次取数)GET /dashboards/:id + 各图 POST /charts/:id/data
分享按钮打开分享弹窗加载该仪表盘分享列表GET /dashboards/:id/shares
删除按钮删除仪表盘confirm 二次确认"确认删除该仪表盘?(图表保留,仅删除仪表盘及布局/分享)"后删除DELETE /dashboards/:id

4.4 当前仪表盘区

元素含义操作效果触发的后端调用
分享按钮分享当前仪表盘打开分享弹窗GET /dashboards/:id/shares
添加图表按钮打开添加图表弹窗展示"可添加图表"列表(已有图表排除)无(提交时请求)
图表卡片刷新按钮重新加载单图数据该图 loading → 取数 → 表格渲染POST /charts/:id/data,body {filters: [], limit: 200}
图表卡片上移按钮(↑)把该图在盘中顺序前移一位moveChart(item, -1):数组内与前一位置交换后整体提交布局;已在首位时无操作;busy 期间禁用PUT /dashboards/:id/layout
图表卡片下移按钮(↓)把该图在盘中顺序后移一位moveChart(item, 1):同上;已在末位时无操作PUT /dashboards/:id/layout
图表卡片宽度下拉(宽度▾)调整该图栅格跨度选项 4/6/8/12 列(对应 1/3、1/2、2/3、整行);changeWidth(item, w)clampWidth 夹紧到 [4,12](与卡片 gridColumn 口径一致)后整体提交布局PUT /dashboards/:id/layout
图表卡片移除按钮从仪表盘移除图表confirm 二次确认"确认从仪表盘移除该图表?(图表定义保留)"后移除DELETE /dashboards/:id/charts/:chartId
图表卡片元信息对象 #id · X 轴/Y 轴只读展示

图表卡片渲染逻辑:item.loading 显示"数据加载中...";item.error 显示红色错误块;item.result 有数据时渲染列 + 行表格(对象/数组单元格 JSON 化),行数为 0 显示"无数据";否则显示"点击"刷新"加载数据"。卡片宽度按 gridColumn: span min(max(width||6,4),12) 占栅格列数(4~12 列)。

4.5 图表创建/编辑弹窗

元素含义必填与默认值操作效果触发的后端调用
名称输入框图表名称必填,placeholder「如 各区域销售额」图表面板标题POST /charts / PUT /charts/:id
描述输入框图表说明选填图表卡片副标题同上
图表类型下拉bar(柱状图)/ line(折线图)/ pie(饼图)/ table(表格)默认 table决定渲染形态与语义查询路径同上
绑定对象下拉本体对象类型(展示 display_name(name)必填选中后触发加载该对象属性列表GET /ontology/objects(选项);GET /ontology/objects/:id(属性)
X 轴属性下拉X 轴维度属性默认不选语义查询的维度列保存时写入 x_axis
Y 轴属性下拉Y 轴度量属性默认不选语义查询的度量列保存时写入 y_axis
分组属性下拉分组维度属性默认不选语义查询 GROUP BY保存时写入 group_by
保存按钮保存新建/更新图表名称与绑定对象必填新建调 POST,编辑调 PUT;成功后刷新图表列表POST /charts / PUT /charts/:id
取消按钮关闭弹窗丢弃当前表单

轴字段保存为 AxisSpec(JSON {property, label, aggregation}):前端把空属性置 null 不发送(后端允许轴缺省,回退全部属性),label 缺省取 property;编辑时后端返回的 JSON 字符串经 parseAxis 反序列化回表单。

4.6 添加图表到仪表盘弹窗

元素含义必填与默认值操作效果触发的后端调用
可选图表列表未在该仪表盘上的图表(含类型徽标与对象 #id)必须选中一个点击选中(高亮)无(提交时请求)
宽度输入框栅格列数默认 6,min 1 max 12决定卡片栅格跨度提交 body width
高度输入框栅格行数默认 4,min 1决定卡片高度提交 body height
添加按钮提交添加chart_id 必选成功后重新打开仪表盘并刷新图表列表POST /dashboards/:id/charts,body {chart_id, position_x: 0, position_y: 当前图表数, width, height}
取消按钮关闭弹窗丢弃

4.7 分享弹窗

元素含义必填与默认值操作效果触发的后端调用
目标类型下拉user(用户 user_id)/ role(角色 role)默认 user决定分享语义与 body 字段POST /dashboards/:id/share
目标输入框用户 ID(如 user_2)或角色名(如 analyst)必填决定分享对象同上
分享按钮提交分享目标非空成功后刷新分享列表POST /dashboards/:id/share,body {share_type, user_id}{share_type, role}
已分享列表每条:类型徽标 + user_id 或 role + 取消分享按钮取消分享有即时反馈DELETE /dashboards/:id/share/:shareId

5. 后端关联

5.1 API 客户端

同页共用一个 apiClientaction/web/src/api/client.js):baseURL /api/v1、timeout 30000、JSON 头、Bearer 注入、401 跳登录。

5.2 端点表

方法路径请求参数超时
GET/ontology/objects30s
GET/ontology/objects/:id无(返回对象属性列表)30s
GET/charts30s
POST/chartsbody {name, description, chart_type, object_type_id, x_axis, y_axis, group_by, filters: []}30s
PUT/charts/:idbody 同上(指针字段可部分更新)30s
POST/charts/:id/databody {filters: [], limit: 200}(filters 为仪表盘全局筛选器,与图表默认筛选合并)30s
POST/charts/previewbody {chart, data}(未保存图表预览,本页未直接使用)30s
GET/dashboards无(后端按当前用户 + 角色过滤)30s
POST/dashboardsbody {name, description, layout_type}30s
GET/dashboards/:id无(返回仪表盘 + 全部图表布局)30s
DELETE/dashboards/:id无(级联清理关联与分享)30s
POST/dashboards/:id/chartsbody {chart_id, position_x, position_y, width, height}30s
DELETE/dashboards/:id/charts/:chartId30s
PUT/dashboards/:id/layoutbody [{chart_id, position_x, position_y, width, height}](本页「↑/↓/宽度」布局编辑使用;position_x 归 0、position_y 取图表在盘中的行序)30s
POST/dashboards/:id/sharebody {share_type: user, user_id}{share_type: role, role}30s
GET/dashboards/:id/shares30s
DELETE/dashboards/:id/share/:shareId30s

5.3 响应结构

统一成功包裹 {"code": 0, "data": ...};错误 {"code", "error"}

图表数据(POST /charts/:id/data)响应 data

{
  "columns": ["region", "total_amount", "order_count"],
  "rows": [["华东", 12345.0, 320], ["华南", 9800.0, 210]],
  "chart": { "id": 1, "name": "各区域销售额", "chart_type": "bar", "object_type_id": 3 }
}

仪表盘详情(GET /dashboards/:id)响应 data

{
  "dashboard": { "id": 1, "name": "销售大盘", "description": "月度销售", "layout_type": "grid", "is_published": false, "owner_id": "alice" },
  "charts": [
    {
      "dashboard_chart_id": 11, "chart_id": 3, "position_x": 0, "position_y": 0,
      "width": 6, "height": 4,
      "chart": { "id": 3, "name": "各区域销售额", "chart_type": "bar", "object_type_id": 3 }
    }
  ]
}

分享列表(GET /dashboards/:id/shares)响应 data[{id, dashboard_id, share_type: "user", user_id: "user_2"}, ...]

5.4 关联模块表

后端包职责
action/products/foundry/server/dashboard_handlers.go图表/仪表盘/分享/取数全部 handler
action/products/foundry/dashboard/service.go业务服务:CRUD、布局、分享、GetChartData 语义取数
action/products/foundry/dashboard/models.goChart/Dashboard/DashboardChart/DashboardShare 模型与图表类型枚举
action/products/foundry/semantic/query.go语义对象查询:字段白名单、Filter、RLS/CLS 注入、Y 轴聚合
action/products/foundry/ontology对象类型/属性元数据(绑定对象与轴字段来源)
action/products/foundry/server/server.goprotected 组路由注册(1058-1077 行)与 dashboardSvc 组装

5.5 关键机制

6. 核心流程详解

6.1 页面加载

onMounted(fetchAll) 并行拉取仪表盘、图表、对象类型三类数据;任一失败各自 alert 提示(不影响其他请求)。

6.2 创建图表主流程

  1. 点「新建图表」→ 打开弹窗,chart_type 默认 table。
  2. 填写名称、选择图表类型、绑定对象类型;选对象后 @change 触发 loadObjectProperties 拉取属性填充 X/Y/分组下拉。
  3. 选择 X/Y/分组属性(可不选)。
  4. 点「保存」:名称与绑定对象必填校验(否则提示"图表名称与绑定对象必填");新建调 POST /charts、编辑调 PUT /charts/:id;成功后提示"图表创建成功(id=...)"/"图表已更新",关闭弹窗并刷新图表列表;若当前正打开某仪表盘,保存后同步重新打开该仪表盘以刷新盘中图表。
  5. 编辑入口:图表卡片提供「编辑」按钮,点击调 openChartForm(item.chart) 回填表单并置 editingChartId,提交走 PUT /charts/:id 更新。

6.3 组装仪表盘主流程

  1. 点「新建仪表盘」展开表单 → 填名称(必填)/布局类型/描述 → 「创建」→ 成功提示并收起表单。
  2. 左侧点「打开」或点击仪表盘名称 → GET /dashboards/:id → 填充 currentDashboard 与图表列表,并自动为每张图调 loadChartData(打开即出数)。
  3. 点「添加图表」→ 在弹窗选择未入盘图表,设置宽高 → 「添加」→ 重新打开仪表盘刷新出图。
  4. 图表卡片可单独「刷新」(重新取数)、「移除」(从盘中移除,图表定义保留),或用「↑ / ↓ / 宽度▾」调整盘内顺序与栅格跨度(布局编辑,成功提示「布局已保存」)。
  5. 发布切换:列表项提供「发布/转草稿」按钮;打开仪表盘后头部有状态徽标与「发布/转草稿」切换,均调 PUT /dashboards/:idis_published)落库。
  6. 自动刷新:打开仪表盘后头部「自动刷新」内联设置可填 refresh_interval_sec(秒)并「保存」调 PUT /dashboards/:id;设置后按该间隔定时重新取当前仪表盘各图表数据,onBeforeUnmount 清除定时器,无泄漏。
  7. 全局筛选:打开仪表盘后提供「全局筛选」卡片,按 field/op/value 增删筛选行,「应用筛选」后所有图表取数携带该条件。
  8. 分享:点「分享」→ 弹窗选 user/role 填目标 → 「分享」;已分享列表可逐条「取消分享」。

6.4 图表数据加载机制

loadChartData(item):置 item.loading=truePOST /charts/:chart_id/data,body {filters: <全局筛选数组>, limit: 200} → 成功存 columns/rows,失败记 item.error("数据加载失败:...")。filters 由「全局筛选」卡片构建(buildFilters:field/op/value,in 按逗号拆数组、is_null 不带 value);无筛选时为空数组。limit 200 是前端固定值;后端语义查询有单图表数据点上限(chartDataDefaultLimit = 1000)。

6.5 状态与终态语义

7. 权限与安全

8. 常见问题与排错

问题 1:仪表盘列表为空但后台有数据

问题 2:图表数据加载失败/列不对

问题 3:打开仪表盘后某些图表一直"数据加载中..."

问题 4:分享后对方看不到仪表盘

问题 5:图表类型选了柱状图但显示表格

9. 已知缺陷与边界

说明
MVP 表格渲染本页图表数据以表格呈现,bar/line/pie 的 ECharts 视觉渲染依赖共享图表组件,未在本页内置
图表编辑图表卡片提供「编辑」入口,回填表单后走 PUT /charts/:id;仅本页字段(名称/类型/绑定对象/轴/分组),无高级轴配置
发布切换列表与打开的头部均提供「发布/转草稿」,调 PUT /dashboards/:id;无「定时发布」等策略
布局编辑(已于 2026-09-13 补齐按钮式编辑)每张图表卡片提供「↑ 上移 / ↓ 下移」(moveChart)与「宽度▾」(changeWidth,4/6/8/12 列),整体提交 PUT /dashboards/:id/layoutposition_x=0position_y=数组行序),成功提示「布局已保存」。边界:仍无自由拖拽/像素级定位(不引入拖拽库,属有意取舍);position_x 恒提交 0、高度 height 沿用原值不在本页调整;该端点需仪表盘所有者或管理员权限(handleUpdateDashboardLayoutrequireDashboardAccess(c,id,true)dashboard_handlers.go:299-317),被分享的只读用户操作会收到 403
自动刷新打开仪表盘后可内联设置 refresh_interval_sec 并按间隔轮询取数;关闭页面/切换仪表盘即停止,无后台常驻
全局筛选提供 field/op/value 行式筛选并随取数下发;无跨图表联动、无筛选持久化到仪表盘定义
分享无权限细分分享仅控制可见性,写权限仍需所有者能力,无只读/可编辑细粒度