界面反馈与文案
界面反馈与文案
Section titled “界面反馈与文案”插件与用户交互的三件套:ctx.ui.toast(通知)、ctx.ui.modal(弹窗)、
ctx.ui.navigate(导航),以及贯穿其中的 ctx.i18n(文案)。
toast(通知)
Section titled “toast(通知)”ctx.ui.toast.success({ title: "完成", description?: "可选描述" }): string;ctx.ui.toast.error(opts): string;ctx.ui.toast.info(opts): string;持续进度 toast(running / update)
Section titled “持续进度 toast(running / update)”running 创建一条不自动消失的持续 toast(如「启动中…」),返回
toast id;结束时用 update 切成 success / error 并设置自动消失时长。
需要展示进度时可带 progress(total 为 0 时宿主渲染 indeterminate
进度条):
ctx.ui.toast.running(opts: { title: string; description?: string; progress?: { total: number; completed: number; stage: string };}): string;
ctx.ui.toast.update(id, patch: { kind?: "success" | "error" | "info" | "running"; title?: string; description?: string; duration?: number; // 自动消失时长(ms) progress?: { total; completed; stage } | null; // null 显式清空进度条}): void;典型的「启动中 → 成功 / 失败」流程:
const id = ctx.ui.toast.running({ title: "正在启动隧道…" });try { await ctx.tunnels.start(hostId, spec); ctx.ui.toast.update(id, { kind: "success", title: "隧道已启动", duration: 3000 });} catch (e) { ctx.ui.toast.update(id, { kind: "error", title: "启动失败", description: String(e) });}modal(弹窗)
Section titled “modal(弹窗)”confirm / alert
Section titled “confirm / alert”ctx.ui.modal.confirm(opts: { title: string; body?: string; confirmText?: string; cancelText?: string; danger?: boolean; // 红色确认按钮(危险操作)}): Promise<boolean>;
ctx.ui.modal.alert(opts: { title: string; body?: string }): Promise<void>;openForm(表单弹窗)
Section titled “openForm(表单弹窗)”需要自定义表单时用 openForm:body 为任意 ReactNode,是弹窗栈的顶层
入口;表单内提交 / 取消后用 close() 关闭:
ctx.ui.modal.openForm(opts: { title: ReactNode; body: ReactNode; // 任意 ReactNode footer?: ReactNode; // 底部槽位;传 null 渲染空 .dlg-footer 容器}): void;ctx.ui.modal.close(): void;footer槽位:传null时宿主渲染一个空的.dlg-footer容器,body 组件可经ModalFooterportal 把按钮 / 状态行渲染进底部(官方 Docker 插件的对话框模式;宿主不提供prompt等原始对话框)。setCancelable(false):长任务提交中禁止 Esc / 遮罩点击关闭,结束后恢复true(取消守卫只对 form 类型弹窗生效)。
ctx.ui.modal.openForm({ title: "创建规则", body: <RuleForm ctx={ctx} />, footer: null, // 按钮经 ModalFooter portal 渲染到底部});navigate(导航)
Section titled “navigate(导航)”切换主区视图(核心视图 id 或插件视图 id):
ctx.ui.navigate(viewId: string): void;ctx.ui.navigate("my-plugin.panel");i18n 与文案
Section titled “i18n 与文案”ctx.i18n.addBundle(lang: string, ns: string, resources: Record<string, unknown>): void;ns会被强制改写为plugin-<pluginId>,避免覆盖宿主的common/views/settings命名空间——传什么都会被忽略,直接写"ignored"即可。- 深合并(deep merge),同键会覆盖之前注册的内容。
读取与监听界面语言
Section titled “读取与监听界面语言”ctx.i18n.getLanguage(): string; // "en-US" / "zh-CN"ctx.i18n.onLanguageChanged(cb: (lng: string) => void): Disposer;插件用它把自己的 i18n 实例与宿主设置同步(官方插件 termii-docker 的
src/i18n.ts 即此模式)。
视图 / 设置分区的文案
Section titled “视图 / 设置分区的文案”labelKey 默认在 views / settings 命名空间解析;插件应通过
ctx.i18n.addBundle 注册自己的文案并用 ns 指过去(ns 被强制改写为
plugin-<id>,因此指向 plugin-<id> 即可):
ctx.i18n.addBundle("zh-CN", "ignored", { panelTitle: "我的面板" });ctx.i18n.addBundle("en-US", "ignored", { panelTitle: "My Panel" });
ctx.ui.registerView({ id: "my-plugin.panel", icon: Package, labelKey: "panelTitle", ns: "plugin-my-plugin", // 命名空间会被强制改写为 plugin-my-plugin component: MyPanel,});注意:若使用 react-i18next 等库做组件内文案,i18next 不在宿主共享 范围内,需要插件自己
npm i i18next并打进 bundle(见 SDK 与打包脚手架)。