跳转到内容

下载 Termii · v0.4.5

三分钟,装进你的 Dock

原生构建,包体仅数 MB。应用内自动更新,始终新鲜。

安装说明

macOS:若提示「无法打开」,在终端执行 xattr -dr com.apple.quarantine /Applications/Termii.app。Windows:首次运行可能触发 SmartScreen,选择「仍要运行」即可。

界面反馈与文案

插件与用户交互的三件套:ctx.ui.toast(通知)、ctx.ui.modal(弹窗)、 ctx.ui.navigate(导航),以及贯穿其中的 ctx.i18n(文案)。

ctx.ui.toast.success({ title: "完成", description?: "可选描述" }): string;
ctx.ui.toast.error(opts): string;
ctx.ui.toast.info(opts): string;

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) });
}
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: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 组件可经 ModalFooter portal 把按钮 / 状态行渲染进底部(官方 Docker 插件的对话框模式;宿主不提供 prompt 等原始对话框)。
  • setCancelable(false):长任务提交中禁止 Esc / 遮罩点击关闭,结束后恢复 true(取消守卫只对 form 类型弹窗生效)。
ctx.ui.modal.openForm({
title: "创建规则",
body: <RuleForm ctx={ctx} />,
footer: null, // 按钮经 ModalFooter portal 渲染到底部
});

切换主区视图(核心视图 id 或插件视图 id):

ctx.ui.navigate(viewId: string): void;
ctx.ui.navigate("my-plugin.panel");
ctx.i18n.addBundle(lang: string, ns: string, resources: Record<string, unknown>): void;
  • ns 会被强制改写为 plugin-<pluginId>,避免覆盖宿主的 common / views / settings 命名空间——传什么都会被忽略,直接写 "ignored" 即可。
  • 深合并(deep merge),同键会覆盖之前注册的内容。
ctx.i18n.getLanguage(): string; // "en-US" / "zh-CN"
ctx.i18n.onLanguageChanged(cb: (lng: string) => void): Disposer;

插件用它把自己的 i18n 实例与宿主设置同步(官方插件 termii-docker 的 src/i18n.ts 即此模式)。

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 与打包脚手架)。