From 025833b2aff213387817196868863d6635a569bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=92=96=E5=95=A1=E5=85=AB=E6=9D=AF?= <2062008467@qq.com> Date: Thu, 24 Sep 2026 20:32:58 +0800 Subject: [PATCH] 3.0.1 --- plugins/devbox/AGENTS.md | 6 ++- plugins/devbox/CHANGELOG.md | 9 +++++ plugins/devbox/README.md | 4 ++ plugins/devbox/package.json | 2 +- plugins/devbox/public/plugin.json | 8 +++- plugins/devbox/src/App.vue | 62 +++++++++++++---------------- plugins/devbox/src/toolbox/tools.ts | 17 -------- 7 files changed, 53 insertions(+), 55 deletions(-) diff --git a/plugins/devbox/AGENTS.md b/plugins/devbox/AGENTS.md index 5d64d9c3b..3ea6cf72f 100644 --- a/plugins/devbox/AGENTS.md +++ b/plugins/devbox/AGENTS.md @@ -55,14 +55,16 @@ public/ **首页与常用统计**:`HomePage.vue` 展示「常用」(`frequentTools`:按 `usageCounts` 降序 Top 8,次数相同按注册顺序)与全部工具分组网格,点击走 `openTab`。`usageCounts` 在 `openTab` 时自增,持久化到 ZTools dbStorage(key:`devbox.toolUsage`),无宿主环境时仅内存。 -**`setRoute(action)` 逻辑**:仅当 action 为**真实 feature 触发**时 `openTab(action.code)`(保留已开标签),`isFeatureTrigger` 三层判定:① `type !== 'text'`(划词/图片/文件)→ 触发;② 有搜索词 → payload 命中该 feature 的 cmds(精确或前缀,忽略大小写;cmds 维护在 `tools.ts` 的 `Tool.cmds`,需与 plugin.json 同步)→ 触发;③ 无搜索词 → `from` 非 `'main'` 或 **code 不是第一个 feature**(快捷键形态)→ 触发,否则(图标点击)保持现状(无已开标签则显示首页)。详见 skill pitfalls「onPluginEnter 图标进入」条目。 +**`setRoute(action)` 逻辑**:`action.code` 命中工具注册表(`toolMap`)即 `openTab(code)`(保留已开标签),否则保持现状(无已开标签则显示首页)。plugin.json 首位是一个专用的 `home` 入口 feature(cmds: `["devbox"]`)——宿主把图标点击伪装成第一个 feature 的 code 回传,`home` 承接了该伪装使判定极简:真实工具的进入(含拼音/模糊搜索词,如「suiji」)code 必然命中注册表,无需 payload 与 cmds 比对。详见 skill pitfalls「onPluginEnter 图标进入」条目。 + +**onPluginEnter 重注册(3.0.1 修复,勿删)**:宿主在插件隐藏时会重载开发插件视图(重跑 preload 并替换 `window.ztools`),已注册的 enter 回调失效,后续进入事件被宿主缓冲不再派发(表现为重进不切换标签)。App.vue 通过 800ms 定时重注册 + visibilitychange 补注册保证回调挂在最新接口对象上,宿主会回放缓冲的 enter 事件。 **新增工具时注意 KeepAlive**:若工具组件有全局副作用(document 级监听、定时器、matchMedia 监听等),必须同时适配 `onUnmounted`(tab 关闭)与 `onActivated`/`onDeactivated`(tab 切换,挂载/恢复、停用/清理),且挂载函数要幂等(首次挂载 mounted + activated 会连续触发)。参考 Qrcode / TimeConvert / JsonTool / HTMLPreview 的写法。 ### 添加新工具的步骤 1. 在 `src/tools//index.vue` 创建组件 -2. 在 `src/toolbox/tools.ts` 的 `categories` 数组中注册(导入组件,定义 code/explain/icon/cmds——**cmds 必须与 plugin.json 同步**,入口判定依赖它) +2. 在 `src/toolbox/tools.ts` 的 `categories` 数组中注册(导入组件,定义 code/explain/icon) 3. 在 `public/plugin.json` 的 features 数组中添加匹配条目(code + cmds) 4. 如需 Node.js 能力,在 `public/preload/services.js` 添加方法,通过 `window.services` 调用 5. 如有全局监听/定时器,按「路由系统」一节适配 KeepAlive 生命周期 diff --git a/plugins/devbox/CHANGELOG.md b/plugins/devbox/CHANGELOG.md index a2cb21b3f..022b89434 100644 --- a/plugins/devbox/CHANGELOG.md +++ b/plugins/devbox/CHANGELOG.md @@ -2,6 +2,15 @@ 本文件记录 ZTools 开发百宝箱插件的版本变更历史。 +## 3.0.1 - 2026-09-24 + +### 修复 + +- **修复再次进入插件时不切换到对应标签页的问题**(插件隐藏到后台后重进,只会停留在上次的标签页) + - 根因:宿主在插件隐藏时会重载开发插件视图(重跑 preload 并替换 `window.ztools`),已注册的 `onPluginEnter` 回调随之失效,后续进入事件被宿主缓冲、无法送达页面。现定时将 enter 回调重新注册到最新的宿主接口对象上,缓冲的进入事件会在重注册时回放,重进插件即可正确激活对应工具标签(多标签与首页状态保留逻辑不受影响) + - 同时修正指令触发判定:图标点击伪装只会回传第一个 feature 的 code,因此其余 feature 的 text 进入(含拼音/模糊搜索词,如输入「suiji」进随机数字)不再要求搜索词命中指令关键词,避免被误判为图标点击而保持原状 + - 配套调整:plugin.json 首位新增 `home` 入口 feature(搜索词 `devbox`)承接宿主图标点击伪装回传的 code,指令触发判定简化为「action.code 命中工具注册表即切换」,不再比对 payload 与 cmds;`tools.ts` 中各工具的 `cmds` 字段随之一并移除,触发指令以 `plugin.json` 为唯一事实来源。至此随机身份等所有工具的历史记录 / 最近使用 / 快捷键 / 拼音搜索进入均可正确切换标签 + ## 3.0.0 - 2026-09-22 ### 新增 diff --git a/plugins/devbox/README.md b/plugins/devbox/README.md index a984cfb6d..5a3240f0b 100644 --- a/plugins/devbox/README.md +++ b/plugins/devbox/README.md @@ -6,6 +6,10 @@ ## 最近更新 +### v3.0.1 (2026-09-24) + +- 修复再次进入插件时不切换到对应标签页的问题:插件隐藏后重进(含历史记录 / 最近使用 / 拼音搜索等所有进入方式),现在都会正确激活指令对应的工具标签;多标签与工具状态保留逻辑不变 + ### v3.0.0 (2026-09-22) - 新增 **多标签页**:右侧内容区支持同时打开多个工具页签,点击左侧菜单新开 / 激活标签(26px 紧凑标签条),多个工具配合使用,切换时各工具状态保留(输入、结果不丢);关键词触发或快捷键进入插件时直达对应工具标签,已开标签不再被重置,点击插件图标进入则停在当前状态(未开标签显示首页)。新增 **插件首页**:未开任何标签时展示首页,含按使用频率自动统计的「常用」区(Top 8,持久化)与按分类分组的全部工具网格,全部关完标签自动回到首页。修复重复进入插件可能黑屏的问题;插件隐藏后标签与工具状态完整保留。 diff --git a/plugins/devbox/package.json b/plugins/devbox/package.json index 7f048136b..f4da125f1 100644 --- a/plugins/devbox/package.json +++ b/plugins/devbox/package.json @@ -1,6 +1,6 @@ { "name": "myPlugins", - "version": "3.0.0", + "version": "3.0.1", "description": "开发百宝箱——只做实际开发中会用到的工具,每个工具都对应真实开发场景里的具体问题,做到真正提效", "type": "module", "scripts": { diff --git a/plugins/devbox/public/plugin.json b/plugins/devbox/public/plugin.json index 5083b8e92..a2c86f0e8 100644 --- a/plugins/devbox/public/plugin.json +++ b/plugins/devbox/public/plugin.json @@ -5,7 +5,7 @@ "categories": ["development"], "description": "开发百宝箱——只做实际开发中会用到的工具,不做凑数功能,每个工具都对应真实开发场景里的具体问题,做到真正提效。支持多标签页同时使用多个工具", "author": "咖啡八杯", - "version": "3.0.0", + "version": "3.0.1", "main": "index.html", "preload": "preload/services.js", "logo": "logo.png", @@ -13,6 +13,12 @@ "main": "http://localhost:5173" }, "features": [ + { + "code": "home", + "explain": "打开开发百宝箱首页", + "icon": "logo.png", + "cmds": ["devbox"] + }, { "code": "identity", "explain": "随机生成虚拟身份信息(姓名、身份证号、手机号、地址等),支持批量和导出", diff --git a/plugins/devbox/src/App.vue b/plugins/devbox/src/App.vue index 110544d10..64b656beb 100644 --- a/plugins/devbox/src/App.vue +++ b/plugins/devbox/src/App.vue @@ -2,7 +2,7 @@ import { onMounted, ref, computed, watch, nextTick } from 'vue' import ToolboxLayout from './toolbox/ToolboxLayout.vue' import HomePage from './toolbox/HomePage.vue' -import { toolMap, categories, type Tool } from './toolbox/tools' +import { toolMap, type Tool } from './toolbox/tools' const route = ref('') const isDev = ref(false) @@ -82,43 +82,24 @@ function closeTab(code: string) { setActive(next ?? '') } -// feature cmds 来自 tools.ts 注册表(新增工具时需与 public/plugin.json 的 cmds 同步维护)。 -// 用于区分「真实 feature 触发」与「点击插件图标进入」:后者宿主会伪装成第一个 feature 的 -// text 触发(code=第一个feature,payload=用户搜索词,实测搜 "dev" 点图标 → payload="dev"), -// 搜索词命中的是插件名而非 feature 的 cmds;真实关键词触发时 payload 与 cmds 精确或前缀匹配。 -const featureCmds = new Map( - [...toolMap.values()].map((t) => [t.code, t.cmds.map((c) => c.toLowerCase())]) -) - -// 宿主对「主界面点击插件图标」固定回传 plugin.json 第一个 feature 的 code; -// 快捷键进入则回传绑定的 feature 的 code(实测 payload 为空、from 不填充) -const FIRST_FEATURE_CODE = categories[0].tools[0].code +// 宿主把各入口都包装成 feature 触发传给 onPluginEnter,图标点击伪装固定回传 plugin.json +// 第一位的 home feature 的 code。因此判定逻辑可以极简:action.code 命中工具注册表即切换, +// 否则(home / 未知 code)保持现状——真实工具的进入不再依赖 payload 与 cmds 的比对, +// 拼音/模糊搜索词(如「suiji」)也能正确切换。 function setRoute(action?: any) { route.value = 'toolbox' - if (isFeatureTrigger(action)) { - // 关键词/划词/图片/文件等 feature 触发:打开/激活对应工具,保留已开的其他 tab - openTab(action.code) + // 只有携带真实工具 code 的进入才切换标签;图标点击伪装(回传首位的 home feature)、 + // 无指令进入等非工具 code 一律保持现状(无已开标签时显示首页) + const code = action?.code + if (code && toolMap.has(code)) { + openTab(code) } - // 点击插件图标进入或无指令进入:保持现状,无已开标签时显示首页 } -/** 是否为真实的 feature 触发(区别于点击插件图标进入) */ -function isFeatureTrigger(action?: any): boolean { - const code = action?.code - if (!code || !toolMap.has(code)) return false - if (action.type && action.type !== 'text') return true // 划词/图片/文件等,payload 即内容本身 - const payload = String(action.payload ?? '').trim().toLowerCase() - if (payload) { - // 有搜索词:命中该 feature 的 cmds(精确或前缀)才是关键词触发,否则是图标点击 - const cmds = featureCmds.get(code) - if (!cmds || !cmds.length) return true // cmds 缺失时退化为「有 payload 即触发」 - return cmds.some((cmd) => cmd === payload || cmd.startsWith(payload)) - } - // 无搜索词的 text 进入:快捷键(from 非 main,或 code 不是首个 feature)→ 真实触发; - // 主界面图标点击(宿主固定回传首个 feature)→ 保持现状/首页 - if (action.from && action.from !== 'main') return true - return code !== FIRST_FEATURE_CODE +/** enter 事件统一处理(注册可能因宿主重载 preload 而多次进行,处理逻辑必须稳定) */ +function handlePluginEnter(action: any) { + setRoute(action) } onMounted(async () => { @@ -134,8 +115,21 @@ onMounted(async () => { ztools.setExpendHeight(600) - ztools.onPluginEnter((action: any) => { - setRoute(action) + // 【修复】宿主在插件隐藏时会重载开发插件的视图(重跑 preload 并替换 window.ztools), + // 导致已注册的 enter 回调失效,后续 onPluginEnter 事件被宿主缓冲、不再派发 + // (表现为重进插件停留在上次的标签页)。定时重注册保证回调始终挂在最新的宿主接口 + // 对象上;注册动作本身会触发宿主回放缓冲的 enter 事件,重进即可正确激活对应标签。 + const registerEnter = () => { + // 每次都从 window 上重新取 ztools——重跑 preload 后旧引用不再是宿主当前使用的接口对象 + ;(window as any).ztools?.onPluginEnter?.(handlePluginEnter) + } + + registerEnter() + setInterval(registerEnter, 800) + + // 页面由不可见变为可见时(部分宿主版本通过可见性触发重载)也补一次注册 + document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'visible') registerEnter() }) ztools.onPluginOut(() => { diff --git a/plugins/devbox/src/toolbox/tools.ts b/plugins/devbox/src/toolbox/tools.ts index a93509195..0d5865d54 100644 --- a/plugins/devbox/src/toolbox/tools.ts +++ b/plugins/devbox/src/toolbox/tools.ts @@ -20,8 +20,6 @@ export interface Tool { explain: string icon: string component: Component - /** 触发指令,需与 public/plugin.json 中对应 feature 的 cmds 保持同步(用于区分真实触发与图标进入) */ - cmds: string[] } export interface Category { @@ -40,35 +38,30 @@ export const categories: Category[] = [ explain: '随机身份', icon: '', component: Identity, - cmds: ['identity', '身份证', '身份', '随机身份', '虚拟身份', '生成身份', '假身份'], }, { code: 'password', explain: '随机密码', icon: '', component: RandomPassword, - cmds: ['密码', '随机密码', 'password', '生成密码', '口令', 'pass'], }, { code: 'number', explain: '随机数字', icon: '', component: RandomNumber, - cmds: ['随机数字', '随机数', 'number', '生成数字', '范围数字', '随机整数'], }, { code: 'uuid', explain: 'UUID生成', icon: '', component: UUID, - cmds: ['uuid', 'UUID', '唯一标识', '生成uuid', 'guid'], }, { code: 'color', explain: '随机颜色', icon: '', component: RandomColor, - cmds: ['随机颜色', '颜色', 'color', '生成颜色', '调色', '配色', '色值'], }, ], }, @@ -81,42 +74,36 @@ export const categories: Category[] = [ explain: '加密签名', icon: '', component: Signature, - cmds: ['加密', '签名', 'token', 'signature', 'hash', '加密签名', '哈希', '签名生成', 'md5', 'sha', 'hmac'], }, { code: 'base64', explain: 'Base64 编解码', icon: '', component: Base64, - cmds: ['base64', 'Base64', 'B64', 'base64编码', 'base64解码', 'b64编码', 'b64解码', '编解码'], }, { code: 'urlcodec', explain: 'URL 编解码', icon: '', component: UrlCodec, - cmds: ['url', 'URL', 'url编码', 'url解码', 'URL编码', 'URL解码', 'urlencode', 'urldecode', 'urlcodec', '百分号编码'], }, { code: 'pinyin', explain: '中文转拼音', icon: '', component: Pinyin, - cmds: ['拼音', 'pinyin', '中文转拼音', '命名', '驼峰', '下划线命名', '首字母'], }, { code: 'qrcode', explain: '二维码', icon: '', component: Qrcode, - cmds: ['二维码', 'qrcode', 'qr', '生成二维码', '解码二维码', '扫码', 'QR码'], }, { code: 'timeconvert', explain: '时间转换', icon: '', component: TimeConvert, - cmds: ['时间', '时间转换', 'time', 'timestamp', '时间戳', '日期转换', '格式化时间', 'timeconvert'], }, ], }, @@ -129,28 +116,24 @@ export const categories: Category[] = [ explain: 'HTML预览', icon: '', component: HTMLPreview, - cmds: ['html', 'HTML', '预览', 'html预览', 'HTML预览', 'htmlpreview', '网页预览'], }, { code: 'textcompress', explain: '压缩文本', icon: '', component: TextCompress, - cmds: ['压缩文本', '文本压缩', 'compress', 'textcompress', '去换行', '压缩', 'sql压缩'], }, { code: 'texttransform', explain: '文本转换', icon: '', component: TextTransform, - cmds: ['文本转换', '多行转一行', '一行转多行', '合并行', '拆分行', 'texttransform', '加引号', '行转换'], }, { code: 'jsontool', explain: 'JSON工具', icon: '', component: JsonTool, - cmds: ['json', 'JSON', 'json工具', 'JSON工具', 'json格式化', 'json解析', '格式化json', 'json压缩', 'json转义', 'json校验'], }, ], },