
在桌宠开发中把 Live2D、BongoCat、文本呈现框、五笔输入法和 RPA 组合到一起听起来像一个大杂烩但实际上是一条非常实用的桌面自动化产品化路径。很多开发者都想要一个能聊天、能响应按键、能执行系统指令的“电子老婆”或“办公助理”但拆解下来它其实就是“形象渲染 输入交互 自动化执行”三层结构的组合。本文会把这五个组件一一拆开讲清楚每一层解决什么问题然后给出一套基于 Electron 的桌宠原型搭建方案最后讨论常见坑点和工程化建议。无论你是想做一个个人玩具还是想给团队做一个桌面机器人入口这篇文章的思路都可以直接复用。1. 背景与核心概念桌宠并不是一个新概念。从早期的“瑞星小狮子”到现在的 ASUS 天选姬、DeepSeek 桌宠插件用户对“桌面上有一个能互动的虚拟角色”这件事一直有需求。早期桌宠大多是纯展示或简单待机动画但现在的桌宠已经开始承担更实际的任务接收用户的中文指令调用自动化脚本执行系统操作把结果通过气泡文本反馈给用户。这就让桌宠从“玩具”变成了“桌面生产力入口”。本文要搭建的桌宠正是这种形态。它的核心组成可以拆成五个部分Live2D负责角色的身体、表情和动作让桌宠有“形象”。BongoCat负责猫爪动画模拟用户敲击键盘时桌宠跟随按键做出“打字”动作让桌宠更生动。文本呈现框负责显示对话气泡、状态提示、RPA 执行结果让桌宠“会说话”。五笔输入法是用户输入中文指令的通道。因为五笔输入法在电竞或办公场景中仍有大量用户如果桌宠要支持这些用户就不能假设大家都用拼音。RPA负责执行用户指令比如打开软件、操作网页、整理文件、批量处理 Excel 等。桌宠只负责发指令RPA 负责干重活。把这五个部分放在一起桌宠就从一个“只会卖萌的窗口”变成了“形象化的人机交互入口”。用户在桌宠上敲一行五笔打出的中文指令桌宠把文字解析成结构化命令再交给 RPA 去执行最后把结果用气泡形式展示出来。这套链路听起来很复杂但拆开看每一步都很清晰而且每一步都有成熟的开源或商业方案可以选用。阅读这篇文章的读者建议至少了解 HTML/CSS/JavaScript 基础知道 Electron 的基本用法理解“进程与主进程通信”的概念。如果你完全没接触过 Electron也不用担心我会在实战部分把每一步都写清楚。至于 Live2D 模型、BongoCat 源码和 RPA 组件我们会尽量使用通用接口避免绑定某个特定产品。2. 环境准备与版本说明因为最终要搭建一个能运行在 Windows 桌面上的桌宠我选择 Electron 作为应用外壳。Electron 的优点是可以直接用 Web 技术渲染 Live2D同时又能调用 Node.js 能力还可以嵌入任意 HTML 页面非常契合“透明窗口 气泡 动画”的需求。下面是我在本文实战环节所使用的环境你可以根据自己的实际情况调整操作系统Windows 10/11macOS / Linux 的透明窗口配置略有差异但整体思路一致Node.js 16 及以上版本npm 或 yarn 包管理器Electron 22 及以上版本建议 24后续示例代码默认使用较新的语法Live2D Cubism Web SDK 或 pixi-live2d-display 插件BongoCat 开源项目GitHub 上可以搜索到不建议直接复制最新 release应按你的 Electron 版本适配任意一款支持五笔的输入法比如搜狗五笔、QQ 五笔。需要注意 QQ 五笔在某些版本下会输入多余空格需要在输入法设置里关闭“五笔兼容”相关选项。影刀 RPA或其同类产品作为可选自动化执行层。如果你不想安装商业软件也可以使用 Python pyautogui 代替但就无法体验真正意义上的 RPA 流程编排。需要说明的是这些组件之间并没有强依赖关系。Live2D 只是渲染层BongoCat 只是动画层RPA 是独立服务。所以如果你在某个环节卡住了可以先跳过它不影响其他模块的调试。比如你先不想接 RPA可以先用一个本地 mock 函数接收指令等后面再接入真正的自动化执行。这也是我在实战部分推荐的开发顺序。3. 核心语法、配置或原理拆解这一节会围绕五个核心模块分别展开。每个模块我都会先说明它解决什么问题再给出最小实现思路最后补充关键参数和常见误区。这样在后面做完整桌宠时你不会觉得代码是拼凑出来的。3.1 Live2D 模型加载与渲染Live2D 不是传统 3D 模型而是一套基于纹理网格绑定的 2D 动画技术。它的文件结构一般包含三类内容纹理图片通常以.png形式存在、模型定义文件.model3.json、以及让角色动起来的状态机或表情文件.motions和.expressions。在 Web 环境下最常用的加载方式有两种一种是使用官方 Cubism Web SDK另一种是使用开源社区封装的pixi-live2d-display。推荐优先尝试pixi-live2d-display。它基于 PixiJS 开发你可以把 Live2D 模型当作一个 PixiJS 显示对象加入舞台然后使用原生 PixiJS 的坐标、缩放、旋转来控制模型。下面是一个最简单的加载示例import { Live2DModel } from pixi-live2d-display; import * as PIXI from pixi.js; const app new PIXI.Application({ view: document.getElementById(canvas), autoStart: true, width: 300, height: 300, transparent: true }); const model await Live2DModel.from(/models/your-model.model3.json); app.stage.addChild(model); model.scale.set(0.25); model.anchor.set(0.5, 0.5);这里的关键是Live2DModel.from()返回一个 Promise所以要用await等待模型加载完成。scale控制模型大小anchor控制原点位置。如果你在设置模型位置时发现模型不在预期区域优先检查anchor和scale。另外需要注意模型文件不能放在项目根目录下直接引用必须通过 devServer 或打包后的资源路径访问。常见误区是模型加载后不显示或报 CORS 错误。这通常是因为本地加载.model3.json时路径配置不正确或者 Web 服务器没有正确处理.moc3和纹理文件的 MIME 类型。在 Electron 中使用file://协议加载本地文件时会遇到安全限制最简单的方式是把模型文件放到public目录然后用相对路径加载。3.2 BongoCat 的猫爪跟随逻辑BongoCat 是一个很有趣的开源项目它会在桌面显示一个猫咪和猫爪猫爪会随着键盘按键做按压动画。这个项目本质上是一个“键盘事件 动画状态机”监听keydown和keyup根据按键编码如KeyA、KeyB切换对应的猫爪贴图或骨架位置。在桌宠里集成 BongoCat 时不需要把整个桌面应用都搬过来。如果你想做得轻量可以直接把 BongoCat 的源码作为一个子窗口加载或者把它的动画逻辑抽象成一个组件放到主窗口里。我这里给出一个通用实现思路假设猫爪由左右两个“爪子”组成每个爪子有“按下”和“抬起”两个状态通过 CSS 类切换动画。const pressedKeys new Set(); window.addEventListener(keydown, (e) { pressedKeys.add(e.code); updateCatPaws(); }); window.addEventListener(keyup, (e) { pressedKeys.delete(e.code); updateCatPaws(); }); function updateCatPaws() { const leftHasKey pressedKeys.has(KeyA) || pressedKeys.has(KeyW) || pressedKeys.has(KeyS); const rightHasKey pressedKeys.has(KeyJ) || pressedKeys.has(KeyK) || pressedKeys.has(KeyL); document.querySelector(.left-paw).classList.toggle(pressed, leftHasKey); document.querySelector(.right-paw).classList.toggle(pressed, rightHasKey); }这里只是演示了按键状态到 UI 状态的映射。真实 BongoCat 项目还包含猫爪悬停、按压力度、3D 视角倾斜等效果但核心逻辑不变通过e.code判断按键位置再控制对应猫爪的动画状态。如果你需要更自然的猫爪动画建议直接 fork BongoCat 源码把它内部的keyDown/keyUp事件与你的桌宠窗口共享。要注意的是Electron 的主窗口如果设置了focusable: false或transparent: true键盘事件可能无法正常捕获。所以必须让桌宠的可交互区域保持可聚焦或者使用globalShortcut来监听全局按键。globalShortcut的好处是即使桌宠窗口不聚焦也能感知用户敲键盘但缺点是如果用户的鼠标正在其他窗口输入文字你的桌宠也会跟着“敲爪”这可能会造成打扰。因此最好加一个开关只在用户激活桌宠对话时开启猫爪同步。3.3 文本呈现框的设计文本呈现框就是桌宠的“嘴”。它要解决的问题是把一段文字比如欢迎语、指令回执、RPA 执行结果用气泡形式显示在角色旁边。在 Electron 中最直接的做法是用一个透明的BrowserWindow里面放一个div通过 CSS 实现圆角气泡、尾巴、阴影和淡入淡出效果。气泡的样式需要和桌宠风格统一。我的建议是不要做得太复杂白色半透明背景、圆角 12px、内边距 12px、最大宽度 240px、底部一个小箭头指向角色。这样的气泡在大多数 Live2D 角色旁边都很协调。为了支持多段文本排队显示我们需要维护一个消息队列每次只显示一条等当前消息显示完或超时后显示下一条。class BubbleQueue { constructor() { this.queue []; this.isShowing false; } push(text) { this.queue.push(text); this.showNext(); } async showNext() { if (this.isShowing || this.queue.length 0) return; this.isShowing true; const text this.queue.shift(); document.getElementById(bubble).textContent text; document.getElementById(bubble).classList.add(visible); await wait(3000); document.getElementById(bubble).classList.remove(visible); await wait(300); this.isShowing false; this.showNext(); } }这里的wait是对setTimeout封装的 Promise。使用队列可以避免消息相互覆盖比如用户连续输入两条指令时桌宠会依次展示结果而不是突然跳到最后一条。除了对话气泡文本呈现框也可以承担状态提示的功能。例如 RPA 正在执行时显示“正在打开计算器…”执行完成显示“已完成”。在视觉上可以用不同颜色区分“成功”“失败”“等待”。我通常会准备三个 CSS 类.bubble-success、.bubble-error、.bubble-waiting通过切换类名来表达状态。3.4 五笔输入法与指令解析很多桌宠教程默认使用拼音输入法但实际工作中还有大量五笔用户。要让桌宠支持五笔输入法并不是要你去解析五笔码表而是要让桌宠的文本输入框兼容 Windows 的输入法框架。在 Electron 中只要你在页面里放了一个input或textarea用户使用任意输入法都可以正常输入。你需要处理的关键事件是compositionstart、compositionupdate和compositionend它们标记了输入法组合输入的开始、更新和结束。例如用户用五笔输入法打出“打开计算器”这几个字会在组合输入过程中逐步进入input的 value。如果你监听keydown事件只会拿到类似KeyD、KeyK的物理按键码无法拿到最终汉字。所以必须在compositionend事件里读取event.data或者读取input.value来获得完整的汉语句子。const commandInput document.getElementById(command-input); commandInput.addEventListener(compositionend, (e) { const text e.data || commandInput.value; if (text.trim()) { handleCommand(text.trim()); commandInput.value ; } }); commandInput.addEventListener(keydown, (e) { if (e.key Enter !e.isComposing) { const text commandInput.value.trim(); if (text) { handleCommand(text); commandInput.value ; } } });这里有两点特别容易踩坑。第一五笔输入法在打单字时可能会触发keydown的 Enter 键所以必须判断e.isComposing否则会在候选词未确认时误提交指令。第二QQ 五笔输入法在某些版本中会把“空格上屏”和普通空格混在一起如果用户打完字后按空格选字input.value里可能多出一个空格。所以拿到文本后要用.trim()去掉首尾空格。如果仍然出现多余空格建议在输入法配置中关闭“允许空格选字”或“五笔拼音混输”相关选项。通过这种方式五笔输入法和拼音输入法在桌宠里就完全一致了。用户想要让桌宠执行自动化命令只需要在输入框里用任意输入法打出中文句子即可。3.5 RPA 动作执行原理RPARobotic Process Automation是机器人流程自动化的简称。它的目标是模拟人类在电脑上的操作比如点击按钮、填写表单、处理文件、读取网页等。一个完整的 RPA 产品通常包含“流程编辑器”和“机器人执行器”。开发者在编辑器里编排流程然后发布给机器人执行。在桌宠场景里RPA 充当的是“手脚”。桌宠需要把用户的中文指令转换成 RPA 能理解的“流程触发信号”。最稳妥的方式是让 RPA 服务监听本地 HTTP 端口桌宠通过 HTTP 请求触发流程。比如我们把一个 RPA 流程命名为“打开计算器”ID 为open_calc那么桌宠就可以在指令解析后发送async function triggerRpa(processId, params {}) { const response await fetch(http://127.0.0.1:8080/rpa/run, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ processId, params, source: desktop-pet }) }); if (!response.ok) throw new Error(RPA 调用失败${response.status}); return await response.json(); }不同 RPA 产品的 HTTP 接口格式不同但思路相似。如果你使用影刀 RPA可以查看官方文档中“外部调用流程”或“API 集成”的说明按要求配置鉴权参数。如果不想依赖商业 RPA也可以直接在 Node.js 进程里调用child_process.execFile执行 Python 脚本或命令行工具。这样同样能让桌宠完成“打开计算器”“查看天气”“抓取网页数据”等操作。需要注意的是RPA 本质上是自动化操控操作系统权限很高。如果桌宠的指令解析做得太宽松用户输入任意文字都可能触发危险操作所以一定要在指令解析层增加白名单或确认机制。比如只有包含“计算器”“记事本”“截图”等预置命令时才触发 RPA对于删除、格式化等危险操作必须二次确认。4. 完整实战搭建一个桌宠原型下面我们开始动手把前面拆解的五个部分组合成一个最小可运行的原型。这个原型包含一个透明窗口、一个 Live2D 角色、一组猫爪动画、一个对话气泡、一个支持五笔输入的指令输入框以及一个 mock 的 RPA 调用模块。4.1 初始化项目结构与依赖首先创建一个新目录并初始化 npm 项目mkdir desktop-pet cd desktop-pet npm init -y安装 Electron、PixiJS 和 pixi-live2d-displaynpm install electron pixi.js pixi-live2d-display如果你的网络环境下载依赖较慢可以配置国内镜像。接下来创建一个简单的项目目录我会把所有的代码放在根目录便于快速理解desktop-pet/ ├── main.js ├── preload.js ├── index.html ├── renderer.js ├── models/ │ └── your-model/ │ ├── your-model.model3.json │ ├── your-model.moc3 │ └── textures/ │ └── texture_00.png └── package.jsonmodels/your-model/目录需要放入你自己的 Live2D 模型文件。如果你暂时没有模型可以先去 Live2D 官网或开源社区下载免费模型导出为 v3 格式这里只做演示不限制具体模型。4.2 创建 Electron 主进程main.js负责创建桌宠窗口。为了实现透明背景、无边框、置顶显示需要设置一些窗口参数。同时通过ipcMain.handle暴露一个rpa:run接口给渲染进程调用。// main.js const { app, BrowserWindow, ipcMain } require(electron); const path require(path); function createWindow() { const win new BrowserWindow({ width: 400, height: 500, transparent: true, frame: false, alwaysOnTop: true, resizable: false, hasShadow: false, webPreferences: { preload: path.join(__dirname, preload.js), contextIsolation: true, nodeIntegration: false } }); win.setAlwaysOnTop(true, screen-saver); win.loadFile(index.html); // 防止透明窗口在鼠标穿透时无法交互 win.setIgnoreMouseEvents(false); } app.whenReady().then(() { createWindow(); app.on(activate, function () { if (BrowserWindow.getAllWindows().length 0) createWindow(); }); }); app.on(window-all-closed, function () { if (process.platform ! darwin) app.quit(); }); // 模拟 RPA 执行接口 ipcMain.handle(rpa:run, async (event, payload) { console.log(收到 RPA 指令, payload); const { processId } payload; // 这里应该是真正调用 RPA 的代码 if (processId open_calc) { return { success: true, message: 已在模拟环境中打开计算器 }; } return { success: false, message: 未识别的流程 ID }; });preload.js用来安全地暴露渲染进程可调用方法。因为我们在webPreferences中关闭了nodeIntegration所以通过 contextBridge 暴露// preload.js const { contextBridge, ipcRenderer } require(electron); contextBridge.exposeInMainWorld(petAPI, { runRpa: (payload) ipcRenderer.invoke(rpa:run, payload) });4.3 编写渲染页面index.html包含画布、输入框和气泡容器。为了让透明窗口不显示默认背景我们整个页面使用透明背景。!DOCTYPE html html langzh-CN head meta charsetUTF-8 title桌宠/title style html, body { margin: 0; padding: 0; width: 100%; height: 100%; background: transparent; overflow: hidden; font-family: Microsoft YaHei, sans-serif; } #stage { position: absolute; left: 50px; top: 30px; width: 300px; height: 300px; } canvas { display: block; } .paw { position: absolute; width: 40px; height: 40px; background-image: url(paw.png); background-size: contain; transition: transform 0.05s linear; } #left-paw { left: 80px; bottom: 20px; } #right-paw { right: 80px; bottom: 20px; } .paw.pressed { transform: translateY(4px) scale(0.95); } #bubble { position: absolute; left: 140px; top: 10px; max-width: 220px; background: rgba(255, 255, 255, 0.9); border-radius: 12px; padding: 10px 14px; font-size: 14px; color: #333; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15); opacity: 0; transition: opacity 0.3s; pointer-events: none; } #bubble.visible { opacity: 1; } #command-input { position: absolute; left: 20px; bottom: 70px; width: 240px; height: 28px; border: 1px solid #ccc; border-radius: 8px; padding: 4px 10px; font-size: 13px; background: rgba(255,255,255,0.85); outline: none; color: #333; } /style /head body div idstage/div div idleft-paw classpaw/div div idright-paw classpaw/div div idbubble/div input idcommand-input placeholder输入指令支持五笔输入 / script srcrenderer.js typemodule/script /body /html这里的#stage用于挂载 PixiJS 画布猫爪图片paw.png需要你自行准备一张猫爪素材。如果暂时没有可以用一个圆形色块替代重点看交互逻辑。4.4 渲染 Live2D 模型在renderer.js中加载 Live2D 模型并挂到 PixiJS 舞台上。由于 PixiJS 和pixi-live2d-display的版本兼容性比较敏感下面代码以当前常见 API 为例// renderer.js import * as PIXI from pixi.js; import { Live2DModel } from pixi-live2d-display; const app new PIXI.Application({ view: document.getElementById(stage), autoStart: true, resizeTo: document.getElementById(stage), backgroundAlpha: 0 }); async function loadModel() { try { const model await Live2DModel.from(/models/your-model/your-model.model3.json); app.stage.addChild(model); model.scale.set(0.2); model.position.set(150, 250); model.anchor.set(0.5, 0.5); } catch (err) { console.error(Live2D 模型加载失败, err); document.getElementById(bubble).textContent 模型加载失败请检查路径; document.getElementById(bubble).classList.add(visible); } } loadModel();backgroundAlpha: 0是为了让 PixiJS 画布背景透明这样才能和 Electron 透明窗口融合。scale和position需要根据你实际模型大小调整。如果你发现模型太大或太小可以直接改这两个值。4.5 集成猫爪动画在同一个renderer.js中我们监听全局键盘事件触发猫爪按压状态。const leftPaw document.getElementById(left-paw); const rightPaw document.getElementById(right-paw); const pressedKeys new Set(); const leftKeys [KeyA, KeyS, KeyD, KeyW]; const rightKeys [KeyJ, KeyK, KeyL, KeyI]; window.addEventListener(keydown, (e) { pressedKeys.add(e.code); updatePaws(); }); window.addEventListener(keyup, (e) { pressedKeys.delete(e.code); updatePaws(); }); function updatePaws() { const leftPressed leftKeys.some((key) pressedKeys.has(key)); const rightPressed rightKeys.some((key) pressedKeys.has(key)); leftPaw.classList.toggle(pressed, leftPressed); rightPaw.classList.toggle(pressed, rightPressed); }注意这里window.addEventListener监听的是渲染进程内的键盘事件。如果输入框当前有焦点键盘事件会先被输入框处理但事件仍然会冒泡到window所以猫爪依然能响应。不过当用户激活其他应用时桌宠窗口失焦window将收不到事件。如果需要全系统捕捉按键应使用globalShortcut并在主进程中通过 IPC 通知渲染进程。4.6 添加文本呈现框逻辑消息队列已经在原理部分讲过现在把它写成一个可复用的模块。function wait(ms) { return new Promise((resolve) setTimeout(resolve, ms)); } class BubbleQueue { constructor() { this.queue []; this.isShowing false; this.bubbleElement document.getElementById(bubble); } push(text, type normal) { this.queue.push({ text, type }); this.showNext(); } async showNext() { if (this.isShowing || this.queue.length 0) return; this.isShowing true; const { text, type } this.queue.shift(); this.bubbleElement.textContent text; this.bubbleElement.className visible type; await wait(2500); this.bubbleElement.className ; await wait(300); this.isShowing false; this.showNext(); } } const bubble new BubbleQueue();使用类型控制颜色可以再补充三个样式类.bubble.success { border-left: 4px solid #52c41a; } .bubble.error { border-left: 4px solid #ff4d4f; } .bubble.waiting { border-left: 4px solid #faad14; }4.7 五笔输入框与指令解析我们继续在renderer.js里绑定输入框的compositionend事件。当用户用五笔输入法打出完整的一句话后这句话会被交给handleCommand。const commandInput document.getElementById(command-input); commandInput.addEventListener(compositionend, (e) { const text e.data || commandInput.value; if (text.trim()) { handleCommand(text.trim()); commandInput.value ; } }); commandInput.addEventListener(keydown, (e) { if (e.key Enter !e.isComposing) { const text commandInput.value.trim(); if (text) { handleCommand(text); commandInput.value ; } } }); async function handleCommand(text) { bubble.push(你输入了${text}, waiting); // 非常简单的指令映射实际项目建议用 NLP 或正则 const ruleMap [ { pattern: /^(打开|启动)(计算器)$/i, processId: open_calc }, { pattern: /^(打开|启动)(记事本)$/i, processId: open_notepad } ]; for (const rule of ruleMap) { const match text.match(rule.pattern); if (match) { try { const result await window.petAPI.runRpa({ processId: rule.processId, rawText: text }); if (result.success) { bubble.push(result.message, success); } else { bubble.push(执行失败${result.message}, error); } } catch (err) { bubble.push(调用 RPA 出错${err.message}, error); } return; } } bubble.push(抱歉我不知道该怎么做这个操作。, normal); }这里我使用正则来做简单的指令匹配。match结果只在需要保留分组时使用示例里只判断是否匹配。为了演示RPA 调用走了preload.js暴露的window.petAPI.runRpa返回结果后通过气泡反馈。4.8 运行与验证在package.json中配置启动命令{ scripts: { start: electron . } }然后在项目根目录执行npm start如果一切正常你会看到一个透明背景的桌宠窗口Live2D 角色显示在窗口左侧猫爪图片显示在底部。点击输入框用任意输入法包括五笔输入“打开计算器”回车后气泡会显示“正在执行…”随后收到模拟的 success 消息。如果你的 RPA 服务已经真实启动可以把ipcMain.handle里的模拟逻辑替换为真实的 HTTP 请求调用这样桌宠就真正连上了 RPA。到此五个组件已经完成闭环。5. 常见问题与排查思路在实际搭建过程中下面的问题出现频率最高我把现象、原因和解决办法整理成一张表方便你快速定位。问题现象常见原因解决思路Live2D 模型加载后不显示.model3.json路径错误或scale设置过小在浏览器控制台查看网络请求确认模型文件是否 200调整scale到 0.3~0.5 再观察猫爪动画长时间无反应窗口失焦导致键盘事件未进入渲染进程改用globalShortcut监听全局键盘通过 IPC 通知渲染进程更新猫爪输入框用五笔输入后触发异常未处理compositionend在组字过程中读取了keydown增加isComposing判断在compositionend后再读取文本QQ 五笔输入后结果带空格输入法“空格选字/上屏”与常规空格冲突在输入法设置中关闭“空格上屏”或代码中对文本trim()并替换\u00A0透明窗口无法点击setIgnoreMouseEvents(true)被误设置检查主进程中是否调用了setIgnoreMouseEvents(false)RPA 流程触发后无反应流程 ID 错误或 RPA 服务未启动先手动调用 HTTP 接口测试确认使用正确的鉴权 token文字气泡遮挡过多画面气泡显示时间过长或优先级过高缩短wait时间气泡最大宽度改小增加手动关闭按钮除了表格里的问题还有两个非常隐蔽的坑。第一Electron 透明窗口开启后如果页面里的元素有抗锯齿透明渐变可能会出现黑色或白色边缘建议给容器设置background: transparent而不是rgba(0,0,0,0)同时不要在html上强制设置背景色。第二Live2D 模型依赖 WebGL 上下文如果桌面启用了某些远程桌面会禁用 WebGL模型会加载失败这种情况只能在本地主机上运行或者切换到 Canvas2D 渲染模式但效果会打折。6. 最佳实践与工程建议当原型跑通后下一步就是把它工程化。这里分享我在桌宠项目里沉淀下来的几条经验。6.1 把指令解析从桌宠逻辑中抽离上面示例把指令映射直接写在renderer.js里适合 demo但不利于维护。更合理的做法是把指令解析单独放到一个模块里使用 DSL 或 JSON 规则描述“说什么做什么”。比如command-rules.json[ { name: 打开计算器, keywords: [打开计算器, 启动计算器], processId: open_calc }, { name: 打开记事本, keywords: [打开记事本, 记事本], processId: open_notepad } ]然后在代码里遍历规则匹配到任意关键词就执行对应processId。这样新增一个指令只需要改 JSON不用动代码。6.2 使用 IPC 做清晰的消息边界桌宠的渲染进程不应该直接调用 Node.js 的child_process否则一旦页面被 XSS 攻击攻击者就能拿到系统控制权。所以必须通过contextBridge暴露最小 API让渲染进程只能调runRpa不能执行任意 shell 命令。主进程的ipcMain.handle里还应该做参数校验比如只允许特定字段长度和类型。对于危险 RPA 流程应当加二次确认提示。6.3 注意 RPA 权限与防误触RPA 流程本质上是在操作系统上模拟鼠标键盘如果指令映射写得太宽用户无意中一句话就可能触发删除文件等操作。建议把 RPA 流程分为三类低危打开应用、显示系统信息、中危读取文件、网页操作、高危删除文件、执行脚本。低危流程可以直接执行中危需要弹气泡确认高危流程必须输入二次确认口令并且记录操作日志。6.4 使用配置中心管理模型与流程桌宠模型路径、RPA 端点地址、气泡样式这些信息不要硬编码在项目里。可以把它们写到一个config.json文件在主进程启动时读取。不同电脑上可以通过环境变量覆盖。例如{ modelPath: /models/your-model/your-model.model3.json, rpaEndpoint: http://127.0.0.1:8080/rpa/run, rpaToken: , bubbleTimeout: 2500 }这样在没有 RPA 服务的开发环境中也可以把rpaEndpoint指向一个 mock server方便前端独立调试。6.5 性能优化减少不必要的重绘Live2D 模型每帧都在重绘猫爪动画也依赖 CSS 切换。如果桌宠窗口一直置顶且不处理失焦状态会持续消耗 CPU 和 GPU。建议在桌宠失焦时暂停模型的 idle 动画只在用户触发输入或 RPA 回调时才唤醒。也可以监控document.hidden状态在窗口不可见时降低帧率。6.6 打包分发最后桌宠应用如果只是自己玩npm start就够了。但如果想分发给其他用户需要使用electron-builder或electron-packager打包。打包时要注意 Live2D 模型文件可能很大需要把models目录一起打包。对于 Windows 平台建议使用 NSIS 安装包模式并勾选“开机自启”选项。这样桌宠才能像一个真正的桌面应用一样常驻。7. 总结与下一步学习路线本文从桌宠的整体架构出发拆解了 Live2D、BongoCat、文本呈现框、五笔输入法和 RPA 五个组件的职责与实现方式并给出了一套基于 Electron 的桌宠原型。通过这套代码你可以快速拥有一个会动、会打字、会说话、能接 RPA 的桌面角色。接下来可以继续深入的方向有三个第一把指令解析换成真正的意图识别服务比如接入本地大模型或开源 NLP 工具让桌宠能听懂更自然的语言第二把 Live2D 模型和 BongoCat 动画结合得更紧密让猫爪“长”在角色身上而不是悬浮在角色旁边第三把 RPA 流程做成可视化任务库让用户不在代码里新增指令而是通过界面配置就能扩展能力。桌宠这条路并不难难的是把每个模块的边界理清楚。如果你按本文的顺序从窗口搭建开始逐步添加动画、输入、自动化你会发现自己对 Electron、输入法事件和 RPA 的理解都变得清晰了很多。动手跑一遍上面的 demo再改成你自己的模型和指令一个真正属于你的桌面宠物就算落地了。希望这篇文章能帮你少踩一些坑。