1. 完整练习
插件化手册
  • FH2 插件开发手册
    • 产品简介
    • 典型场景
    • 发版说明
    • 快速开始
      • 快速开始总览
      • 第一个插件
      • 后端插件
      • 全栈闭环
    • AI辅助开发
      • AI 开发 Skill
      • 安装与复验
      • 第一个工程
      • 真实需求示例:现场问题上报
      • 改造已有工程
      • 调试与交付
    • 开发指南
      • 开发指南
      • 基础概念
        • 基础概念总览
        • 什么是插件开发
        • 平台架构
        • 前端宿主、扩展点与能力
        • 后端运行时、入口与权限
        • 开发流程
        • 安全与隔离
        • 支持范围与问题反馈
      • 开发指南
        • 开发指南总览
        • 前端插件
          • 前端插件深度指南
          • 前端工程结构
          • 插件清单(fh2-plugin.json)
          • 入口与生命周期
          • 界面与状态
          • 多宿主插件
          • 跨宿主通信服务
          • HTTP、WebSocket 与事件
          • 存储、Blob 与定时器
          • 工具链与本地调试页面
        • 后端插件
          • 后端开发深度指南
          • 后端插件的结构
          • 后端源清单 fh2-plugin.json
          • HTTP 接口处理函数
          • 消息订阅与事件处理函数
          • ctx 与能力注入
          • 数据存储与缓存
          • 流程编排
          • 触发方式
          • 连接外部系统
          • 流程编排练习(DAG)
        • 前后端协作
          • 前后端协作总览
          • 前后端共享契约
          • 端到端幂等
          • 事件、通知与状态
          • 统一错误模型
          • 链路标识与诊断日志
        • 完整练习
          • 环境准备
          • 第一个前端插件
          • 第一个后端插件
          • 第一个全栈插件
      • 能力总览
        • 能力地图
        • 能力可用性矩阵
        • 前端能力
          • 平台通用能力
            • 平台通用能力总览
            • 基础运行时:HTTP、事件、WebSocket 与存储
            • 导航、位置与通知
            • 弹窗、音频与剪贴板
            • 地图能力
            • 项目设备能力
            • WPMZ 与航线预览
            • 受控对象上传
          • 项目宿主
            • 项目宿主总览
            • 项目上下文
            • 项目侧边栏入口
            • 项目插件路由
            • 项目工作区
          • 消息宿主
            • 消息宿主总览
            • 消息告警弹层
            • 消息导航与通知
          • 计划库宿主
            • 计划库宿主总览
            • 计划库表单上下文
            • 计划库表单扩展
            • 校验、提交与 `biz_ext`
          • 驾驶舱
            • 驾驶舱插件能力总览
            • 驾驶舱扩展点
            • 上下文与遥测
            • 相机、检测与 AI
            • 场景几何与直播指针
            • 飞控、云台与任务
            • 驾驶舱平台事件
        • 后端能力
          • 后端能力地图
          • 后端能力总览
          • 配置、受管出网与集成连接
          • 事件与流程
          • 飞行任务与违停巡逻
          • 数据存储
          • 媒体与前端通知
          • 资源租约
          • 作业、目标轨迹与视频处理
      • 调试与测试
        • 调试与测试总览
        • 快速入门排错
        • 本地开发
        • Web IDE:打开工程与保存改动
        • 工作流编辑器:节点、接线与交付
        • 知识与 MCP:先判断需要哪一层事实
        • 前端本地调试
        • 后端运行时与流程调试
        • 模拟数据与测试数据
        • 集成测试与 E2E
        • 日志与链路追踪
        • 常见问题排查
      • 完整示例
        • 示例与案例总览
        • 项目侧边栏与工作区
        • 消息告警卡片
        • 计划表单任务清单
        • 驾驶舱动作与左侧面板
        • 最小后端插件
        • 最小全栈插件
        • 多宿主插件实战
        • 事故快处
        • 交通抓拍
        • 违停巡逻完整案例
      • 构建发布与安全
        • 构建、发布与安全
        • 质量门禁
        • 行业包构建与打包
        • 能力版本发布
        • 安装、升级与回滚
        • 安全基线
      • API参考
        • API 参考总览
        • 前端
          • 前端 API 参考
          • 前端插件清单结构
          • 前端 SDK API
          • 前端扩展点目录
          • 前端能力目录
          • 前端平台事件目录
          • 跨宿主通信服务 API
          • 工具链命令行参考(`fh2-plugin`)
        • 后端
          • 后端 API 参考
          • 后端插件源清单
          • 后端 HTTP 接口参考
          • 后端事件消费参考
          • 后端脚本上下文(`ctx`)
          • 后端数据存储
          • 流程编排语言
          • 流程触发器参考
          • 后端能力目录
          • 后端 HTTP API
        • 共享契约
          • 共享契约参考
          • 类型与契约目录
          • 错误模型
          • 版本兼容性
      • 附录
        • 错误码
        • 术语表
        • 常见问题
  1. 完整练习

第一个前端插件

这一页带你做出一个能跑的前端插件:在驾驶舱工具栏加一个"打开示例面板"按钮,点开一个带计数器的面板。

前置条件#

已完成环境准备,工具链可用。
目标目录 exp_first_frontend 不存在或为空。
端口 5175 空闲。
fh2-plugin-sdk 是插件开发 SDK;模块路径示意,实际导入保留生成工程配置。本页用于说明 API,不表示存在可直接安装的同名 npm 包;不要据此改依赖名或构建配置。

1. 创建工程#

在环境准备选定的工程父目录(示例为 $HOME/fh2-plugin-dev)执行,保持 FH2_TOOLS 指向已初始化的固定源码。不做全局注册时,把下面的 fh2-plugin 换成环境准备中的源码 bin 调用:
按向导填写:工程类型选 1(前端插件),插件 ID 填 exp_first_frontend,项目目录填
exp_first_frontend,名称填 第一个前端插件,包管理器选 pnpm,立即安装选 是。
括号里的默认值符合本教程时可以直接回车;不必手填全部命令参数。
创建结束后进入工程,先检查未修改的模板:
选择“立即安装”会自动安装依赖并复验类型;安装失败时工程保留,先在工程根重新执行
pnpm install 和 pnpm run generate,不要重复创建覆盖它。上面四条检查都通过后再继续。

2. 认识生成的工程#

frontend-basic 模板自带两组示例:hello(入门,本页主角)和 issue(进阶,先不管它)。目录长这样:
exp_first_frontend/
├── fh2-plugin.json               # 插件清单(你维护)
├── rsbuild.config.ts             # 构建配置(模板生成,不要改)
├── package.json                  # 依赖与脚本
├── public/icons/                 # plugin.svg 及 issue 示例图标
├── tests/                        # 契约测试
└── src/
    ├── plugin.ts                 # 入口:按宿主分发
    ├── hosts/cockpit/
    │   ├── activate.ts           # 驾驶舱激活:转发到两个示例
    │   ├── hello-example.ts      # 入门示例(本页主角)
    │   └── issue-example.ts      # 进阶示例(本页先不管)
    ├── views/
    │   ├── hello-panel/          # 入门示例的面板(契约 + 挂载 + Vue 组件)
    │   ├── issue-panel/          # 进阶示例的面板
    │   └── issue-dialog/         # 进阶示例的弹窗
    └── generated/plugin-types.ts # 清单生成的类型,不要手改
清单(fh2-plugin.json)声明了 4 个扩展点和 3 个界面——hello 一组、issue 一组:
{
  "id": "exp_first_frontend",
  "name": "第一个前端插件",
  "version": "0.1.0",
  "description": "驾驶舱入门面板与现场问题弹窗示例",
  "entry": "./plugin.js",
  "permissions": ["platform:dialog"],
  "extensions": [
    { "id": "hello-action", "point": "cockpit.ui.toolbar-actions" },
    { "id": "hello-panel", "point": "cockpit.ui.left-panel" },
    { "id": "issue-entry", "point": "cockpit.ui.toolbar-actions", "anchor": "footer-toolbar:before-ai" },
    { "id": "issue-panel", "point": "cockpit.ui.left-bottom-panels" }
  ],
  "views": {
    "hello-panel": { "entry": "./views/hello-panel.js" },
    "issue-panel": { "entry": "./views/issue-panel.js" },
    "issue-dialog": { "entry": "./views/issue-dialog.js" }
  },
  "contributes": {
    "icons": [
      { "id": "plugin", "src": "./icons/plugin.svg" },
      { "id": "issue", "src": "./icons/issue.svg" },
      { "id": "issue-active", "src": "./icons/issue-active.svg" }
    ]
  }
}
本页只动 hello-action(工具栏按钮)和 hello-panel(左侧面板)这两个声明。
hello 的 icon: 'plugin' 对应清单中的 plugin 与 public/icons/plugin.svg;保留图标声明及资源,构建时由工具链复制并转换路径,不手改 dist/。
入口代码 src/plugin.ts:宿主加载插件时调用它的 activate,按宿主分发:
import { definePlugin } from 'fh2-plugin-sdk'
import type { PluginContext } from 'fh2-plugin-sdk'
import type { AppCockpitContext } from './generated/plugin-types'

export default definePlugin({
  async activate (ctx: PluginContext) {
    if (ctx.host.id === 'cockpit') {
      return (await import('./hosts/cockpit/activate')).activate(ctx as AppCockpitContext)
    }

    ctx.logger.warn(`当前示例未实现宿主:${ctx.host.id}`)
    return undefined
  },
})
驾驶舱激活逻辑 src/hosts/cockpit/hello-example.ts:创建面板状态 → 把界面交给 hello-panel 扩展点 → 把按钮交给 hello-action 扩展点,点击时展开面板:
import { createViewState } from 'fh2-plugin-sdk'
import type { AppCockpitContext } from '../../generated/plugin-types'
import { helloPanelView } from '../../views/hello-panel/contract'
import type { HelloPanelState } from '../../views/hello-panel/contract'

export async function registerHelloExample (ctx: AppCockpitContext): Promise<void> {
  const state = createViewState<HelloPanelState>({ count: 0 })

  await helloPanelView.handle(ctx, {
    state,
    methods: {
      increment: () => {
        state.update(current => ({ count: current.count + 1 }))
        return state.get()
      },
    },
  })

  const panel = await ctx.extensions.contribute('hello-panel', {
    visible: false,
    label: '示例面板',
    icon: 'plugin',
    render: () => helloPanelView.get(ctx),
  })

  await ctx.extensions.contribute('hello-action', {
    icon: 'plugin',
    label: '打开示例面板',
    active: false,
    disabled: false,
    async onTrigger () {
      await panel.reveal()
    },
  })
}
三个关键点:
状态在入口代码里。createViewState 创建计数状态,界面只显示它——这是"入口持有状态、界面只管呈现"的平台约定。
先声明再贡献。contribute('hello-action', ...) 的 ID 必须和清单 extensions[] 里的一致,否则运行时报 EXTENSION_DECLARATION_INVALID。
panel.reveal() 把左侧面板展开给用户(相当于用户手动点开页签)。
界面三件套(src/views/hello-panel/:契约 contract.ts、挂载 mount.ts、组件 HelloPanel.vue)本页先不展开,做第一个全栈插件时你会逐个改到它们。

3. 跑起来#

在插件工程根目录(exp_first_frontend/)执行:
打开终端输出的地址(默认 http://127.0.0.1:5175)。本地调试页面默认进入团队页,此时驾驶舱宿主尚未挂载,看不到驾驶舱插件按钮。按以下步骤进入:
1.
在团队页找到在线设备 MOCK-DRONE-001,点击设备卡的"打开设备小窗"。
2.
在设备小窗点击"虚拟驾驶舱",进入新页签。该入口带入设备参数,并打开驾驶舱页面。
3.
在直播左侧竖排工具栏的插件区悬停,找到“打开示例面板”按钮(hello)并点击,展开左侧“示例面板”。当前 Mock 的通用插件区在 AR 之前;AI 按钮前还有“现场问题”按钮(issue 进阶示例)。
4.
点击面板里的"点击次数:0",计数变为 1,再点继续递增。
也可以停止自己启动的 dev 后,仍在本页独立前端插件根(有 fh2-plugin.json 和 package.json,不是含 packages.json 的外层工作区)使用模板已有的驾驶舱脚本:
访问这条命令打印的地址(默认端口为 5176),以实际驾驶舱界面和按钮判断是否进入,不要求地址栏片段固定为某个值。build 只生成产物,浏览器调试期间需保持 dev 运行。

4. 改一点东西试试#

把按钮文案改掉:在 src/hosts/cockpit/hello-example.ts 里找到 label: '打开示例面板',改成 label: '我的面板',保存。本地调试页面会自动重建,刷新页面即可看到新文案。
参照上方代码修改逻辑时,保留生成文件中的真实导入语句,只调整函数体和类型声明;示意模块路径不是替换工程导入或安装依赖的指令。

预期结果与自查#

pnpm run validate 和 pnpm run build 通过。
dist/fh2-plugin.json 已生成(构建产物,里面的 manifestVersion 等字段是工具链写的,别复制回源清单)。
工具栏按钮可见,点击能打开面板。
计数由入口维护(状态在 createViewState),界面代码里没有 ctx。

常见问题#

现象原因与处理
代码里没有 AppCockpitContext 类型改过清单后没跑 pnpm run generate
本地调试页面看不到按钮进入驾驶舱,确认插件已启用,按钮位置见打开插件面板
按钮图标不对检查清单中的 plugin 图标声明和对应 SVG 文件
界面加载失败清单 views 的 entry 字段、界面文件夹的契约 ID、默认导出(defineView(...))三处要一致
构建报"入口包含 Vue"Vue 组件、样式只能放在 src/views/,插件入口代码不要导入它们

下一步#

后端怎么做:第一个后端插件。
前后端连起来:第一个全栈插件。
每个目录的职责、文件怎么分工:前端工程结构。
修改于 2026-10-25 04:04:44
上一页
环境准备
下一页
第一个后端插件
Built with