WebKit - 自定义页面与菜单
WebKit 模块允许插件在 EasyBot 的 Web 管理界面中注册自定义页面和菜单项。通过此 API,插件可以在侧边栏中添加菜单,并响应前端页面的交互请求。
版本要求
WebKit API 在 SDK v0.3.2 中引入。需要 EasyBot 2.1.0-dev.6 或更高版本。
核心概念
菜单系统架构
EasyBot 的菜单系统采用树形结构:
- 分组 (Group):纯目录节点,用于收纳子菜单
- 页面 (Page):叶子节点,关联一个前端页面路径
- 插件菜单:由插件通过
pluginMenu注册,自动关联到当前插件
ID 自动拼接
注册菜单时填写的 id 会自动加上当前插件 ID 前缀,格式为 插件ID:注册ID。例如:
- 插件 ID:
my_plugin - 注册时填写 ID:
my_page - 最终完整 ID:
my_plugin:my_page
核心类型
IconKey
系统内置图标名称类型,支持以下图标:
| 图标常量 | 含义 |
|---|---|
HomeOutlined | 首页 |
DashboardOutlined | 仪表盘 |
ToolOutlined | 工具 |
SettingOutlined | 设置 |
UserOutlined | 用户 |
InfoCircleOutlined | 信息 |
ApiOutlined | API |
PictureOutlined | 图片 |
ChromeOutlined | 浏览器 |
CloudServerOutlined | 云服务器 |
DeploymentUnitOutlined | 部署 |
LinkOutlined | 链接 |
ConsoleSqlOutlined | 数据库查询 |
DatabaseOutlined | 数据库 |
ContactsOutlined | 联系人 |
TeamOutlined | 团队 |
IdcardOutlined | 身份证 |
SearchOutlined | 搜索 |
CodeOutlined | 代码 |
SafetyCertificateOutlined | 安全认证 |
RobotOutlined | 机器人 |
SwapOutlined | 交换 |
EditOutlined | 编辑 |
LogoutOutlined | 登出 |
SecurityScanOutlined | 安全扫描 |
CheckCircleOutlined | 验证通过 |
StopOutlined | 停止 |
ControlOutlined | 控制 |
ShopOutlined | 商店 |
GiftOutlined | 礼物 |
FileTextOutlined | 文件文本 |
FolderViewOutlined | 文件夹视图 |
InsertRowBelowOutlined | 插入行 |
ImportOutlined | 导入 |
ClearOutlined | 清除 |
FireOutlined | 热门 |
UnlockOutlined | 解锁 |
NodeIndexOutlined | 节点索引 |
GoldOutlined | 金币 |
SkinOutlined | 皮肤 |
MenuOutlined | 菜单 |
TypeOutline | 文字/字体 |
也可以传入以 http 开头的 URL 作为自定义图标。
RemoteRoute
菜单路由节点,描述菜单树中的一个节点。
declare class RemoteRoute {
readonly id: string; // 路由唯一标识
readonly path?: string | null; // 页面路径(仅插件页面非null)
readonly label: string; // 菜单标题
readonly icon: IconKey | string; // 菜单图标
readonly component?: string | null; // 前端组件名(插件页面为null)
readonly isPremium: boolean; // 是否仅限高级版
readonly isLimitedFree: boolean; // 是否限时免费
readonly defaultVisible: boolean; // 是否默认可见
readonly defaultParentId: string | null;// 默认父菜单ID
readonly defaultOrder: number; // 排序权重
readonly isLocked: boolean; // 是否锁定
readonly pluginMeta?: PluginMenuMeta | null; // 插件元数据
readonly children?: RemoteRoute[] | null; // 子菜单列表
}
WebAction
前端触发的方法调用对象。
declare class WebAction {
readonly sessionId: string; // 前端随机ID,区分触发来源
readonly method: string; // 调用的方法名
readonly parameters: any; // 传递的参数
}
PluginMenuRegister
插件菜单注册器,每个插件拥有独立的实例。
declare class PluginMenuRegister {
register(
id: string,
path: string,
label: string,
icon: IconKey | string,
onAction: (action: WebAction) => Promise<any> | any,
onGet: (sessionId: string) => Promise<string> | string,
parentId?: string | null,
visible?: boolean | null,
order?: number | null,
isLocked?: boolean,
description?: string,
): RemoteRoute;
registerGroup(
id: string,
label: string,
icon: IconKey | string,
parentId?: string | null,
visible?: boolean | null,
order?: number | null,
isLocked?: boolean,
description?: string,
): RemoteRoute;
getMenus(): RemoteRoute[];
}
注册菜单
监听 menu_ready 事件
菜单注册必须在 menu_ready 事件中进行,该事件在菜单系统初始化完成后触发:
bus.on("menu_ready", (pluginMenu) => {
// 在这里注册菜单
});
register() - 注册页面菜单
注册一个带页面的菜单项,点击后会在前端打开对应路径的页面。
参数:
id:菜单标识符(仅需在插件内唯一)path:页面路径label:显示标题icon:图标(内置名称或URL)onAction:前端调用后端方法时的回调onGet:前端请求页面时的回调,需返回完整 HTMLparentId:父菜单ID,null为根级visible:是否默认可见order:排序权重,越小越前isLocked:是否锁定description:描述信息
示例:
bus.on("menu_ready", (pluginMenu) => {
pluginMenu.register(
"my_page", // ID
"/pages/mypage", // 路径
"我的页面", // 标题
"HomeOutlined", // 图标
(action) => { // 处理前端调用
if (action.method === "getData") {
return { result: "hello", time: Date.now() };
}
if (action.method === "saveData") {
// 处理保存逻辑
db.Set("mydata", JSON.stringify(action.parameters));
return { success: true };
}
},
(sessionId) => { // 返回页面HTML
return `
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>我的页面</title></head>
<body>
<h1>你好,这是我的自定义页面</h1>
<p>Session: ${sessionId}</p>
</body>
</html>`;
},
null, // 父菜单ID(null=根级)
true, // 默认可见
10, // 排序
false // 不锁定
);
});
registerGroup() - 注册菜单分组
创建纯目录节点,用于组织子菜单。
示例:
bus.on("menu_ready", (pluginMenu) => {
// 先创建分组
const group = pluginMenu.registerGroup(
"tools", // ID
"工具箱", // 标题
"ToolOutlined", // 图标
null, // 父菜单(null=根级)
true, // 默认可见
5, // 排序
false // 不锁定
);
// 在分组下注册页面
pluginMenu.register(
"tool_page",
"/pages/tool",
"小工具",
"SwapOutlined",
onAction,
onGet,
"tools", // 使用短ID引用同插件内的父菜单
true,
null,
false
);
});
getMenus() - 获取已注册菜单
bus.on("menu_ready", (pluginMenu) => {
const menus = pluginMenu.getMenus();
logger.info(`当前插件已注册 ${menus.length} 个菜单`);
});
完整示例
以下是一个完整的插件示例,注册了一个带交互功能的自定义页面:
/// <reference path="easybot-sdk/easybot.d.ts" />
bus.on("enable", () => {
logger.info("WebKit 示例插件已启用");
});
bus.on("menu_ready", (pluginMenu) => {
// 注册一个工具分组
pluginMenu.registerGroup("demo_group", "演示工具", "ToolOutlined", null, true, 10, false);
// 注册一个计数器页面
pluginMenu.register(
"counter",
"/pages/counter",
"计数器",
"DashboardOutlined",
(action) => {
switch (action.method) {
case "getCount": {
const count = db.GetString("counter_value") || "0";
return { count: parseInt(count) };
}
case "increment": {
const count = (parseInt(db.GetString("counter_value") || "0")) + 1;
db.Set("counter_value", count.toString());
return { count };
}
case "reset": {
db.Set("counter_value", "0");
return { count: 0 };
}
}
},
(sessionId) => {
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>计数器</title>
<style>
body { font-family: sans-serif; padding: 20px; text-align: center; }
button { font-size: 18px; margin: 5px; padding: 8px 16px; }
#count { font-size: 48px; margin: 20px; }
</style>
</head>
<body>
<h1>计数器</h1>
<div id="count">0</div>
<button onclick="callAction('increment')">+1</button>
<button onclick="callAction('reset')">重置</button>
<script>
async function callAction(method, params = {}) {
const countEl = document.getElementById('count');
try {
const result = await window.webuikit.action(method, params);
countEl.textContent = result.count;
} catch(e) {
countEl.textContent = '❌ Error: ' + e.message;
}
}
// 初始加载
callAction('getCount');
</script>
</body>
</html>`;
},
null, true, 20, false, "一个简单的计数器演示"
);
});
bus.on("disable", () => {
logger.info("WebKit 示例插件已禁用");
});
注意事项
- 菜单注册必须在
menu_ready事件中进行 - 引用同插件内的父菜单时,使用短ID(注册时填写的原始 ID,不含插件前缀)。系统会自动拼接前缀
onGet回调需返回完整的 HTML 字符串用于渲染页面- 前端调用后端方法使用
window.webuikit.action(method, params),无需手动传递 sessionId - 图标可传入
IconKey内 置名称或 http/https 开头的自定义 URL
