Skip to main content
浏览文档目录
第 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(),就不会再转发。