做一个面板
面板是最常见的插件形态:一块 iframe,挂在宿主声明好的位置。本篇从快速开始的静态面板往前走三步:挂载位置、宿主上下文、连接后端。
先分清两个”视图”:manifest 的
views声明的是UI 挂载位置(抽屉/活动栏/设置页里的面板,与具体某张表无关)——就是本篇。产品里表格顶部那排”视图” tab(网格/看板/甘特…)是表的投影,那个位置也能自定义,但走的是另一个贡献点tableViews→ 见自定义表格视图。
视图声明的 location 决定它出现在哪:
"contributes": { "views": [ { "id": "panel", "title": "工具", "icon": "clock", "location": "right-drawer", "entry": "ui/index.html" }, { "id": "workbench", "title": "工作台", "icon": "bot", "location": "activity-bar", "entry": "ui/index.html#/workbench" } ]}| location | 出现在 | 适合 |
|---|---|---|
right-drawer | 右侧工具栏图标 → 抽屉面板 | 常驻小工具,与表格并排用 |
activity-bar | 左侧活动栏图标 → 占满编辑区的全屏页 | 工作台式独立界面(Agent 工作台就是它) |
settings | 偏好设置页的一个分区 | 复杂配置 UI(简单配置用 settings 贡献的自动表单更省) |
fullscreen / floating / bottom-panel | 全屏 / 浮窗 / 底部面板 | 特定形态需要时 |
entry 支持 hash 路由(ui/index.html#/workbench)—— 一份 UI 构建产物服务多个视图是内置插件的通行做法。
活动栏全屏页天然有路由深链:打开后 URL 是 /projects/<项目>/page/<插件id>/<视图id>,刷新、分享、浏览器前进后退都由宿主承载,插件不用做任何事。
拿到宿主上下文
Section titled “拿到宿主上下文”面板 iframe 启动时并不知道自己在哪个项目里。宿主会通过 postMessage 把上下文推进来,SDK 封装好了:
<div id="app"></div><script type="module"> import { installContextBridge, getContext, onContextChange } from '@xinghan/plugin-sdk/ui'
installContextBridge() // 幂等,装一次消息桥
function render() { const ctx = getContext() // ctx.projectId 当前项目(打开面板必然有项目) // ctx.activeNodeId 编辑器当前激活的节点(用户正在看哪张表/哪个文件) // ctx.activeFileType / ctx.activeNodeName / ctx.locale document.getElementById('app').textContent = ctx.activeNodeName ? `正在看:${ctx.activeNodeName}(${ctx.activeFileType})` : '还没有打开任何表或文件' }
onContextChange(render) // 用户切换表/文件、切语言时宿主会推 update render()</script>不同挂载形态下上下文字段有别(表视图会带 tableNodeId/viewId,文件渲染器会带 nodeId),完整字段见 UI 桥参考。
连接自己的后端
Section titled “连接自己的后端”需要读写数据、调 LLM、存配置时,把插件升级成 node 运行时,UI 与后端之间就是普通 HTTP:
{ "runtime": "node", "entry": "dist/server.js", "devEntry": "src/server.ts", "capabilities": ["dataEngine", "storage"]}后端是一个普通 HTTP 服务(监听宿主经环境变量 PORT 指定的端口),宿主把 /plugins/<你的id>/* 反代过来;UI 里用相对路径请求,天然免配置:
// ui 侧:请求会被宿主反代到你的后端进程const res = await fetch('../api/summary', { method: 'POST' })// server 侧(任意 node HTTP 框架;内置插件多用 koa)import { CapabilityClient } from '@xinghan/plugin-sdk/server'const cap = new CapabilityClient({ baseUrl: process.env.CAPABILITY_BASE_URL, token: process.env.CAPABILITY_TOKEN,})
router.post('/api/summary', async (ctx) => { const { projectId, tableNodeId } = ctx.request.body const actor = ctx.headers['x-actor'] as string // 宿主注入的用户身份 const fields = await cap.dataEngine.getFields({ tableNodeId }, { actor, projectId }) ctx.body = { ok: true, data: fields }})数据读写的完整讲解(查询、批量写入、治理与撤销)见读写表格数据。
- UI 热更新:视图声明加
devUiUrl: "http://localhost:3001"指向你的 vite dev server,宿主会反代 iframe 到它; - 后端免构建:
devEntry指向 TS 源码,宿主 dev 模式用 tsx 直接跑。
- 最小静态面板:
plugins/marketplace/pomodoro/(一个right-drawer视图,capabilities: []) - 带后端 + 活动栏工作台:
plugins/builtin/agent-anthropic/(views 三种 location 全用上了)