01 / GET STARTED一个文件夹,一个工具。
友签当前使用 Python + PySide6。工具是宿主内的一个 QWidget 页签,不是浏览器插件,也不是独立 EXE。将插件放到程序旁 tools 的一级子目录,激活扩展功能后加载。
下载可运行模板 ↘模板包含界面、纯逻辑、本地存档示例和说明。开发界面需适配约 240px 起的窄列;复杂内容可使用滚动容器或有父对象的对话框。
YouQian Plus.exe
tools/
my_tool/
plugin.json # 插件清单(必需)
plugin.py # QWidget 入口(必需)
core.py # 纯逻辑,建议不依赖 Qt
README.md # 用法、存档、依赖与边界
02 / MANIFEST清单与入口契约
目录内必须有 plugin.json 和 plugin.py。id 在本机工具中保持唯一,并建议与目录名相同。
{
"id": "my_tool",
"name": "我的工具",
"version": "1.0.0",
"api_version": 1,
"description": "一句话说明功能",
"enabled": true
}
| 字段 | 规则 |
|---|
| id | 匹配 ^[a-z][a-z0-9_]*$,不可重复。 |
| name | 非空字符串,用于页签标题;建议简短。 |
| api_version | 当前必须为数字 1。 |
| enabled | false 时不加载;可省略。 |
| version / description | 用于版本和功能说明,建议明确提供。 |
from PySide6.QtWidgets import QLabel
def create_widget(parent, plugin_dir):
# parent 为宿主页签容器;plugin_dir 为插件资源目录
return QLabel("你好,友签。", parent)
create_widget 必须返回 QWidget。不要创建第二个 QApplication,也不要调用 app.exec()。同级模块采用相对导入,例如 from .core import Counter;不要依赖当前工作目录寻找资源。
03 / DESIGN & DATA保持轻巧,照顾宿主。
界面与性能
- 为根控件设置唯一 objectName,样式选择器限定在自身子树,避免修改宿主的全局样式。
- 避免固定大宽度。文字换行、控件可缩放,提供清晰的空状态与错误提示。
- 耗时计算、网络请求和大文件操作放入工作线程,通过信号更新界面;不要阻塞主线程。
- 模块导入时不要启动线程、定时器或重型初始化。只在需要时读取资源。
存档与依赖
插件负责自己的数据,不直接修改宿主 notes.db 的内部表结构。建议把存档放到当前用户的本地应用数据目录 StickyNotes/tools/<id>,而不是安装目录或临时解包目录。保存时使用临时文件加原子替换,读取时校验格式、版本与取值范围;损坏文件保留备份。
宿主当前使用 PySide6-Essentials,不等于包含所有 Qt 模块。运行时不会自动安装依赖;额外 Python 模块需要随插件携带兼容版本,或纳入宿主构建后验证。不要假定开发环境中可导入的所有模块在 EXE 中都存在。
04 / LIFECYCLE加载、暂停与退出
- 未激活时不导入工具插件。扩展权限恢复后,宿主才加载插件页签。
- 取消本机激活、服务端明确拒绝或 7 天公网故障计时到期后,工具区隐藏,已有插件页签被关闭并移除。
- 窗口与 QTimer 应归属插件根控件或其子控件。关闭时保存状态、停止任务并释放资源。
- 宿主会停止控件树下的计时器并关闭子窗口;自行创建的无父窗口、线程与外部进程需要插件负责清理。
- 修改插件文件后应重启宿主,不依赖运行中热加载。权限恢复时可能重新创建控件,初始化与关闭应可重复执行。
插件在宿主进程内执行,具有本机程序权限。错误页签用于展示加载失败,并不构成安全沙箱。只分发和安装可信插件。
05 / SHIP IT交付前,走完这一遍。
- 验证独立逻辑。覆盖正常输入、边界输入、无效数据、存档读取与损坏处理。
- 验证宿主内行为。检查窄列排版、输入焦点、对话框归属、插件关闭与重新创建,确认不影响便签编辑和自动收起。
- 验证实际 EXE。在发布版里检查依赖、资源路径和文件读写,不能只在源码环境测试。
- 写清使用边界。附上说明、版本、依赖、存档位置、网络行为与第三方授权文件。
- 按完整目录分发。退出友签后,复制插件文件夹到 tools,再启动并激活扩展功能。升级前备份插件存档。
插件开发与兼容问题可以发送到 [email protected]。请附插件 id、版本、宿主版本及可复现的步骤。
回到工具箱展示 ↗