跳到主要内容

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信息
ApiOutlinedAPI
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:前端请求页面时的回调,需返回完整 HTML
  • parentId:父菜单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

遇到麻烦了?

有偿服务

我们提供有偿代安装服务,解决您的环境配置烦恼。

了解详情
Miku

少年,买服务器吗?

持证经营

专注高性价比游戏云VPS,铂金 / I7 / R9 / 物理机

💰 最低六元起、买不了吃亏买不了上当,快来看看吧~

快来看看