Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions plugins/devbox/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<ToolName>/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 生命周期
Expand Down
9 changes: 9 additions & 0 deletions plugins/devbox/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

### 新增
Expand Down
4 changes: 4 additions & 0 deletions plugins/devbox/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

## 最近更新

### v3.0.1 (2026-09-24)

- 修复再次进入插件时不切换到对应标签页的问题:插件隐藏后重进(含历史记录 / 最近使用 / 拼音搜索等所有进入方式),现在都会正确激活指令对应的工具标签;多标签与工具状态保留逻辑不变

### v3.0.0 (2026-09-22)

- 新增 **多标签页**:右侧内容区支持同时打开多个工具页签,点击左侧菜单新开 / 激活标签(26px 紧凑标签条),多个工具配合使用,切换时各工具状态保留(输入、结果不丢);关键词触发或快捷键进入插件时直达对应工具标签,已开标签不再被重置,点击插件图标进入则停在当前状态(未开标签显示首页)。新增 **插件首页**:未开任何标签时展示首页,含按使用频率自动统计的「常用」区(Top 8,持久化)与按分类分组的全部工具网格,全部关完标签自动回到首页。修复重复进入插件可能黑屏的问题;插件隐藏后标签与工具状态完整保留。
Expand Down
2 changes: 1 addition & 1 deletion plugins/devbox/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "myPlugins",
"version": "3.0.0",
"version": "3.0.1",
"description": "开发百宝箱——只做实际开发中会用到的工具,每个工具都对应真实开发场景里的具体问题,做到真正提效",
"type": "module",
"scripts": {
Expand Down
8 changes: 7 additions & 1 deletion plugins/devbox/public/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@
"categories": ["development"],
"description": "开发百宝箱——只做实际开发中会用到的工具,不做凑数功能,每个工具都对应真实开发场景里的具体问题,做到真正提效。支持多标签页同时使用多个工具",
"author": "咖啡八杯",
"version": "3.0.0",
"version": "3.0.1",
"main": "index.html",
"preload": "preload/services.js",
"logo": "logo.png",
"development": {
"main": "http://localhost:5173"
},
"features": [
{
"code": "home",
"explain": "打开开发百宝箱首页",
"icon": "logo.png",
"cmds": ["devbox"]
},
{
"code": "identity",
"explain": "随机生成虚拟身份信息(姓名、身份证号、手机号、地址等),支持批量和导出",
Expand Down
62 changes: 28 additions & 34 deletions plugins/devbox/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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<string, string[]>(
[...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 () => {
Expand All @@ -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(() => {
Expand Down
17 changes: 0 additions & 17 deletions plugins/devbox/src/toolbox/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ export interface Tool {
explain: string
icon: string
component: Component
/** 触发指令,需与 public/plugin.json 中对应 feature 的 cmds 保持同步(用于区分真实触发与图标进入) */
cmds: string[]
}

export interface Category {
Expand All @@ -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', '生成颜色', '调色', '配色', '色值'],
},
],
},
Expand All @@ -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'],
},
],
},
Expand All @@ -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校验'],
},
],
},
Expand Down
Loading