窗口插件开发详解

窗口插件是最常用的一类。这一节把它拆开讲:入口是怎么被加载的、生命周期有哪几个、窗口能力怎么控制、以及怎么做到跟主题和语言一致。

入口是怎么被加载的

窗口插件在 manifest.json 里用 main 对象描述入口。三个字段撑起基本形态:

{
    "main": {
        "url": "index.html",
        "width": 640,
        "height": 480
    }
}

url 指向入口页面,Eagle 会把它载入一个独立的浏览器窗口。加了 minWidth/minHeight/maxWidth/maxHeight 之后,用户就拖不出你预设的范围。

一个配置完整的主窗口示例:

{
    "id": "LBCZE8V6LPCKD",
    "version": "1.0.0",
    "name": "窗口插件",
    "logo": "/logo.png",
    "keywords": [],
    "devTools": false,
    "main": {
        "url": "index.html",
        "width": 640,
        "height": 480,
        "minWidth": 400,
        "minHeight": 300,
        "maxWidth": 1200,
        "maxHeight": 900,
        "alwaysOnTop": false,
        "frame": true,
        "fullscreenable": true,
        "maximizable": true,
        "minimizable": true,
        "resizable": true,
        "backgroundColor": "#ffffff",
        "childWindow": false,
        "followCursor": false,
        "multiple": false,
        "runAfterInstall": false
    }
}

frame、resizable、minimizable、maximizable、fullscreenable 这几个布尔值共同决定了用户能对窗口做什么。全部设成 false 再配上 frame: false,你就得到了一个完全受控的「面板」——这是很多设计类插件的选择,因为工具型界面本来就不需要被最大化和拖动。

生命周期:五个必须知道的回调

窗口插件有一组完整的事件回调,都挂在 eagle 上:

// 窗口建立时触发一次,适合做初始化
eagle.onPluginCreate((plugin) => {
    console.log(plugin.manifest.name);
    console.log(plugin.path);
});

// 用户在插件面板点击插件时触发
eagle.onPluginRun(() => {
    console.log('用户触发了插件');
});

// 窗口显示时触发(从隐藏恢复也算)
eagle.onPluginShow(() => {
    console.log('窗口显示');
});

// 窗口隐藏时触发
eagle.onPluginHide(() => {
    console.log('窗口隐藏');
});

// 窗口关闭前触发
eagle.onPluginBeforeExit(() => {
    console.log('插件将退出');
});

注意 onPluginCreate 和 onPluginRun 的区别:前者是窗口创建,后者是用户点击。multiple: false 时窗口是复用的,第二次点击插件可能只触发 onPluginRun 而不重建窗口。如果你的初始化逻辑写在 onPluginCreate 里靠第二次点击刷新,那是行不通的——写在 onPluginRun 里才对。

阻止窗口关闭也可以,但要换一种写法:

eagle.onPluginBeforeExit(() => {
    console.log('插件将退出');
});

// 想拦截关闭时,注册 onbeforeunload 并返回
window.onbeforeunload = (event) => {
    return event.returnValue = false;
};

用 window API 控制窗口

eagle.window 提供了一整套窗口控制方法,覆盖日常需求:

// 显示与焦点
await eagle.window.show();            // 显示并聚焦
await eagle.window.showInactive();     // 显示但不抢焦点
await eagle.window.hide();
await eagle.window.focus();

// 最小化与最大化
await eagle.window.minimize();
await eagle.window.restore();
await eagle.window.maximize();
await eagle.window.unmaximize();
let isMax = await eagle.window.isMaximized();

// 全屏
await eagle.window.setFullScreen(true);
await eagle.window.setFullScreen(false);

// 尺寸与位置
await eagle.window.setSize(720, 480);
let [w, h] = await eagle.window.getSize();
await eagle.window.setBounds({ x: 440, y: 225, width: 800, height: 600 });
await eagle.window.setPosition(100, 200);
let [x, y] = await eagle.window.getPosition();

// 其他
await eagle.window.setAlwaysOnTop(true);      // 置顶
await eagle.window.setOpacity(0.8);           // 透明度 0~1
await eagle.window.setResizable(false);
await eagle.window.setBackgroundColor("#FFFFFF");
await eagle.window.flashFrame(true);          // 窗口边框闪烁,吸引注意

其中几个特别值得注意的设计意图:

  • setOpacity(0.5) 配合 setIgnoreMouseEvents(true) 能做出「悬浮在最上层、鼠标穿透」的桌面挂件。这在色彩取样器、标尺类工具里很常见。
  • setBackgroundColor 主要是防闪烁的——窗口弹出的那一瞬间 HTML 还没加载完,用户看到的是纯白色块。设一个接近你最终界面底色的颜色,视觉上就顺滑很多。
  • capturePage(rect) 能截取窗口画面并返回 NativeImage,可以拿来做「导出当前预览图」这类功能。

跟随主题变化

Eagle 有六套主题配色:LIGHT、LIGHTGRAY、GRAY、BLUE、PURPLE、DARK。用户切换时插件必须跟着变,否则白底插件套在深色主界面旁边会很突兀。

eagle.onPluginCreate(async () => {
    // 读取当前主题
    const theme = await eagle.app.theme;
    document.body.setAttribute('theme', theme);
});

// 监听主题变化
eagle.onThemeChanged((theme) => {
    document.body.setAttribute('theme', theme);
});

推荐把主题挂在 body 的属性上,再用 CSS 选择器统一切换,而不是在 JS 里逐个元素改颜色:

body { background: #ffffff; color: #000000; }
body[theme="DARK"], body[theme="GRAY"], body[theme="BLUE"], body[theme="PURPLE"] {
    background: #2c2c2c; color: #ffffff;
}
body[theme="LIGHTGRAY"] {
    background: #f0f0f0; color: #000000;
}

典型开发流程

  1. 在 manifest.json 里定好 main 的尺寸约束、backgroundColor、frame 等字段。
  2. 在 index.html 里搭结构,引入 js/plugin.js。
  3. 在 plugin.js 的 onPluginCreate 里读一次 eagle.app.theme 设置初始主题。
  4. 在 onPluginRun 里做每次点击都要执行的动作(刷新数据、重置状态)。
  5. 注册 onThemeChanged 应对主题切换。
  6. 按 F12 逐个尺寸档位测试布局,尤其是 minWidth 那个极端值。
  7. 打包前把 devTools 改成 false。

常见坑

multiple: false 时把刷新逻辑写死在 onPluginCreate。 窗口复用后第二次点击不会重新触发创建回调,用户会觉得「插件没反应」。刷新类逻辑应该放 onPluginRun。

忘记处理窗口最小尺寸。 你的界面在 640 宽下好看,用户把窗口拖到 300 宽就全乱了。用 minWidth/minHeight 兜住,并在测试时手动缩到最小验证一遍。

延伸注意: * 在窗口里 setInterval 之后不清理。 窗口复用时定时器不会被销毁,会越积越多导致内存上涨。清理放在 onPluginHide 或 onPluginBeforeExit 里。

整理人:WorkBuddy