Skip to main content
Browse the docs

The developer docs are currently available in Chinese only. The SDK, the CLI and every code sample work the same in any language.

Chapter 7

面板

面板 SDK、React、Tailwind 预设、主题、嵌入模式、沙箱里不能做什么

面板是包里的 panel.html,以标签页打开(ctx.ui.openPanel(),或用户在插件库 / ⌘K 里点开)。 它运行在一个沙箱 iframe 里:opaque origin、CSP 禁止一切网络请求与外部资源。面板只能做这些事:

  • run(action, args):执行本插件的命令(界面来源:写命令不弹确认,不进撤销栈);
  • openCourse(courseId)、openCalendarAt(isoDate):打开应用的课程页 / 日历某一天;
  • notify(message, kind):顶部通知(第三方插件的通知前面会被加上插件名);
  • askUniMind(prompt):带着一句话打开 UniMind 并发送(第三方插件会先显示「插件想让 UniMind 回答:…」由用户点发送)。

读写数据一律走 run():面板和 UniMind 调的是同一批命令,逻辑只写一份。

两种写法

不用框架

TypeScript
import '@uniflow/plugin-sdk/styles.css'           // 可选:基础样式 + uf-* 组件类
import { connectPanel, unwrap } from '@uniflow/plugin-sdk/panel'

async function main() {
  const panel = await connectPanel()
  const render = async () => {
    const stats = await unwrap(panel.run('get_stats', { days: 7 }))
    document.getElementById('root')!.textContent = panel.t('panel.today', { minutes: stats.todayMinutes })
  }
  panel.on('data', render)     // 本插件的数据变了(UniMind 写的、后台任务写的、别的设备同步来的)
  panel.on('locale', render)   // 用户切换了界面语言
  await render()
}
void main()

React

TSX
import { mountReactPanel, type PanelProps } from '@uniflow/plugin-sdk/react'

function Panel({ run, t, subscribe, courses, askUniMind }: PanelProps) { … }
void mountReactPanel(Panel)

语言、主题、参数、课程列表变化时组件自动重渲染;数据变化不自动重渲染,用 subscribe 自己决定怎么刷新 (一次批量写入会触发很多次 data,合并成一次刷新)。

PanelApi

字段 说明
pluginId、locale
params 打开面板时带的参数(ctx.ui.openPanel({ deckId }));再次打开会带新的参数(_open 递增)
run(action, args) 返回 ExecResult:{ ok: true, result } 或 { ok: false, code, error };unwrap() 把失败变成异常
t(key, opts) 本插件文案
subscribe(listener) / on('data' | 'locale' | 'theme' | 'params' | 'courses' | 'change', fn) 事件
courses 课程列表(只有声明了 context.read:courses 才有内容)
openCourse、openCalendarAt、notify、askUniMind? 宿主动作;askUniMind 为 undefined = 宿主没有提供这个动作(应用里的插件面板总会提供),隐藏相关入口
theme { scheme: 'light' | 'dark', vars }
embedded 面板嵌在设置页里(高度随内容,SDK 自动上报高度)

样式与主题

宿主把应用的设计令牌写进面板的 :root(--surface-raised、--text-primary、--accent、--danger …), 并在 <html> 上设置 data-theme="light|dark",用户切换主题时实时更新。

  • Tailwind:tailwind.config.cjs 里 presets: [require('@uniflow/plugin-sdk/tailwind-preset')], panel/styles.css 写 @tailwind base; @tailwind components; @tailwind utilities; —— 应用的类名(bg-surface-raised、 text-content-secondary、bg-brand、ring-line-subtle、animate-fade-in …)全部可用,深浅色自动正确。
  • 不用 Tailwind:import '@uniflow/plugin-sdk/styles.css',直接写 var(--accent),或用 uf-card、uf-btn uf-btn-primary、 uf-input、uf-badge、uf-empty 等组件类。

布局

  • 标签页里:面板占满标签页,自己负责滚动(根元素 h-screen overflow-y-auto,或 .uf-page)。左右留白 32px,顶部 24px。
  • 嵌入模式(embedded):去掉外层留白与固定高度,高度随内容。
  • 窄到约 560px 也要能用(用户会把窗口拉窄、分屏)。

沙箱里不能做的事

  • 任何网络请求(fetch、XHR、WebSocket、EventSource)、外部 <script> / <img src=https://…> / 字体 / iframe —— 都被 CSP 拒绝。 图片与字体用 data: / blob:(构建时 .svg/.png/.woff2 自动内联成 data URL)。
  • window.open、弹窗、alert/confirm/prompt、页面跳转 —— 都被沙箱拦截。用行内确认代替 confirm()。
  • <form> 的提交事件不会触发(沙箱没有 allow-forms):不要依赖 onSubmit / 回车提交表单,用按钮的 onClick 与输入框的 onKeyDown(Enter)自己处理。
  • 访问 parent、应用的 localStorage / cookie —— opaque origin,碰不到。面板自己的 localStorage 也不可用:状态放进命令与存储。
  • eval / new Function。

键盘

面板是独立文档:快捷键挂在 window 上即可,不会影响应用别处。带 ⌘ / Ctrl 的组合键 SDK 会转给应用(⌘K 照常可用); 你自己处理了的组合键请 preventDefault(),就不会再转发。