From de5886cb5206b47d7f93000f5286ec2387361ad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chen=20Peng=20=28=E5=BD=AD=29?= Date: Tue, 8 Sep 2026 21:49:08 +0800 Subject: [PATCH] =?UTF-8?q?Update=20plugin=20=E7=A7=91=E7=A0=94=E5=B0=8F?= =?UTF-8?q?=E7=9B=92=20v1.3.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 CCF 第七版(2026.3)目录,与 JCR 2026 合并为同一检索入口 - 期刊与会议统一检索,卡片同时展示 IF/分区 与 CCF A/B/C 等级/领域 - 支持 CCF 缩写检索(TPAMI/CVPR)与中文领域检索(人工智能) - 新增「全部/期刊/会议」筛选条与 CCF 官方主页按钮 - plugin.json 升至 1.3.1;pack.sh 改为内置 ASAR 构造 --- plugins/micro-tools/README.md | 78 +++++---- plugins/micro-tools/ccf.json.gz | Bin 0 -> 19885 bytes plugins/micro-tools/index.css | 63 ++++++- plugins/micro-tools/index.html | 15 +- plugins/micro-tools/index.js | 199 +++++++++++++++------- plugins/micro-tools/pack.sh | 78 +++++++++ plugins/micro-tools/plugin.json | 18 +- plugins/micro-tools/preload.js | 283 +++++++++++++++++++++----------- 8 files changed, 544 insertions(+), 190 deletions(-) create mode 100644 plugins/micro-tools/ccf.json.gz create mode 100755 plugins/micro-tools/pack.sh diff --git a/plugins/micro-tools/README.md b/plugins/micro-tools/README.md index 75d08ddd9..75691ad39 100644 --- a/plugins/micro-tools/README.md +++ b/plugins/micro-tools/README.md @@ -6,14 +6,15 @@ ``` micro-tools/ -├── plugin.json # 插件入口 + feature 触发配置 -├── preload.js # CommonJS:读 xlsx,暴露 window.journalApi -├── index.html # 前端界面(多功能 SPA) -├── index.css # 样式 -├── index.js # 前端逻辑 -├── 2026年度JCR期刊名单(完整版).xlsx # 期刊数据源(22,643 条,与插件一起发布) -├── vendor/xlsx.full.min.js # SheetJS 单文件发行版 -├── logo.png # 插件图标 +├── plugin.json # 插件入口 + feature 触发配置 +├── preload.js # CommonJS:读两个数据集,合并后暴露 window.journalApi +├── index.html # 前端界面(多功能 SPA) +├── index.css # 样式 +├── index.js # 前端逻辑 +├── journals.json.gz # JCR 2026 期刊数据(22,643 条,gzip -9) +├── ccf.json.gz # CCF 第七版目录(677 条,gzip -9) +├── pack.sh # 打包为 .zpx(ASAR + brotli) +├── logo.png / logo.svg # 插件图标 └── README.md ``` @@ -21,44 +22,63 @@ micro-tools/ | 功能 | 状态 | 说明 | | --- | --- | --- | -| **期刊查询** | ✅ 可用 | 基于 JCR 2026 名单查询影响因子(JIF)、分区、JCI、被引等 | -| **数据库查询** | 🔧 体验版 | NCBI Genome / Gene / Nucleotide、PubMed、UniProt、PDB、Ensembl、Google Scholar 一键检索 | +| **期刊 · 会议查询** | ✅ 可用 | 同一入口检索 JCR 2026 期刊与 CCF 第七版目录 | +| **数据库查询** | 🔧 体验版 | NCBI / PubMed / UniProt / PDB / Ensembl 等一键检索 | | 文献速读 | 🚧 即将推出 | PDF 摘要提炼 | | 单位换算 | 🚧 即将推出 | 科研常用单位 | -### 期刊查询 +### 期刊 · 会议查询 -- 按 **期刊名 / JCR 缩写 / ISSN** 模糊检索(220ms 防抖),结果按 **JIF 高→低排序**。 -- 卡片展示:**2025 JIF 徽标、JCR 分区 Q1–Q4 配色、JCI、总被引、金色 OA%、各学科分区详情、出版商、学科、ISSN**。 -- 「复制信息」经 `ztools.copyText()` + `showNotification()`。 +一个入口同时检索两套体系,卡片上**有就显示、没有就不显示**: + +| 数据 | 来源 | 卡片显示 | +| --- | --- | --- | +| 影响因子 / 分区 / JCI / 被引 | JCR 2026(22,643 条) | `IF 20.4`、`Q1`、四项统计 | +| CCF 等级 / 领域 / 变动 | CCF 第七版(677 条) | `CCF A`、`人工智能`、`晋级` | + +- **三种情况都覆盖**:既有 IF 又有 CCF 等级(如 TPAMI:IF 20.4 + CCF A);只有 CCF 等级(会议,如 CVPR;以及已被 JCR 除名的期刊,如 MTA);只有 IF(非计算机领域期刊,如 Nature)。 +- 检索字段:期刊/会议全称、JCR 缩写、**CCF 缩写**、ISSN、CCF 中文领域(可直接搜「人工智能」)。 +- 顶部筛选条:**全部 / 期刊 / 会议**。 +- 动作按钮:复制信息(纯文本多行)、JCR 官网、官方主页/DBLP。 +- 相关性排序:精确 > 前缀 > 包含,同分按影响因子降序。 ### 数据库查询 -- 输入检索词 → 选择数据库卡片 → 在系统浏览器中打开对应检索结果页(通过 `ztools.shellOpenExternal` 或回退到 `window.open`)。 -- 支持数据库:NCBI Genome / NCBI Gene / NCBI Nucleotide / PubMed / UniProt / PDB / Ensembl / Google Scholar。 +输入检索词 → 选择数据库 → 系统浏览器打开检索结果(`ztools.shellOpenExternal`)。 +支持:NCBI Genome / Gene / Nucleotide、PubMed、UniProt、PDB、Ensembl、Google Scholar。 -## 为什么是“纯 Preload”格式 +## 数据合并机制 -- `preload.js` 用 Node `fs` 读同目录 xlsx,经 SheetJS 解析后暴露给前端;前端只渲染 UI。 -- 不依赖 Vite/Webpack 等构建工具,所有资源用相对路径引用(`index.css`/`index.js`/`logo.png`),因此 ZTools 用 `file://` 加载 `index.html` 时所有资源正常解析。 -- 这规避了 Vite 构建产物用绝对 `/assets/...` 路径在插件环境下加载失败的问题。 +`preload.js` 加载时把 CCF 目录按 ISSN 关联到 JCR 期刊: + +- 关联成功(277/291 条 CCF 期刊,95.2%)→ CCF 字段挂到该 JCR 记录上,并把 CCF 缩写并入检索词表,**一张卡同时显示两套指标**。 +- 未关联(386 条会议 + 14 条未被 JCR 收录的期刊)→ 作为独立记录并入统一检索数组,只显示 CCF 等级。 + +CCF 中跨领域重复收录的刊物(如 TOMM 同时挂「网络」和「图形学」)会合并为一条,领域显示为 `网络 · 图形学`。 + +## 数据源 + +- **JCR**:`2026年度JCR期刊名单(完整版).xlsx` → 转换压缩为 `journals.json.gz`(短键名 + gzip -9,1.6 MB)。22,643 条,含 2025 JIF / 分区 / JCI 等字段。 +- **CCF**:《中国计算机学会推荐国际学术会议和期刊目录》第七版(2026 年 3 月发布)→ `ccf.json.gz`(19.9 KB)。677 条 = 291 期刊 + 386 会议,分 A/B/C 三级、10 个领域方向。 +- 两个数据集均为静态快照,更新时替换对应 `.gz` 文件即可(`pack.sh` 会自动打包)。 ## 在 ZTools 中使用 -1. ZTools → 插件管理 → 添加本地插件,选择 `micro-tools/` 目录。 +1. 插件管理 → 添加本地插件,选择 `micro-tools/` 目录(或导入 `micro-tools-vX.Y.Z.zpx`)。 2. 搜索框输入: - - `科研小盒` / `kydh` — 打开工具箱首页 - - `期刊 Nature` / `影响因子 JACS` / `journal nat rev mol cell bio` — 直接进入期刊查询 - - `数据库 BRCA1` / `ncbi genome TP53` — 进入数据库查询并带入关键词 -3. 或直接打开插件,从首页点卡片进入各功能。 + - `科研小盒` / `kyxh` — 打开工具箱首页 + - `期刊 Nature` / `影响因子 JACS` — 进入期刊·会议查询 + - `ccf CVPR` / `会议 NeurIPS` — 直接进入并切到「会议」筛选 + - `数据库 BRCA1` — 进入数据库查询 +3. 划词后通过 `type: over` 触发「查询期刊/会议」,自动带入选中文本。 -## 数据源 +## 打包 -- 影响因子来自 `2026年度JCR期刊名单(完整版).xlsx`(2025 JIF / JCR 分区 / JCI 等 33 个字段,22,643 条记录)。 -- 更新数据只需替换同目录下的 xlsx 文件,列名保持一致即可自动适配(见 preload.js 中 COLUMN_MAP)。 +```bash +bash pack.sh # 产出 ../micro-tools-.zpx(ASAR + brotli,自动排除 .git/.DS_Store) +``` ## 开发者 - **开发者**:Asa12138 - **邮箱**:bfzede@gmail.com - diff --git a/plugins/micro-tools/ccf.json.gz b/plugins/micro-tools/ccf.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..d8c527d19e8b4435af9465cbcaf7e9d0839d86d2 GIT binary patch literal 19885 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 + + + +
正在加载期刊与 CCF 数据…
@@ -69,7 +74,7 @@
- 提示:ZTools 搜索框输入 期刊 Nature影响因子 JACS数据库 BRCA1 可直接触发。 + 提示:ZTools 搜索框输入 期刊 Natureccf CVPR数据库 BRCA1 可直接触发。
diff --git a/plugins/micro-tools/index.js b/plugins/micro-tools/index.js index d3c8e56da..e735a8274 100644 --- a/plugins/micro-tools/index.js +++ b/plugins/micro-tools/index.js @@ -1,6 +1,7 @@ // index.js —— 科研小盒 前端逻辑(多功能 SPA) -// 视图:home(启动器)/ journal(期刊查询)/ database(数据库查询) -// 数据检索由 preload 的 window.journalApi 提供;ztools API 做剪贴板/通知/打开外链。 +// 视图:home(启动器)/ journal(期刊·会议查询)/ database(数据库查询) +// 数据检索由 preload 的 window.journalApi 提供(JCR 期刊 + CCF 目录已合并); +// ztools API 做剪贴板/通知/打开外链。 const $ = (sel) => document.querySelector(sel) @@ -14,13 +15,15 @@ const els = { jSearch: $('#jSearch'), jHint: $('#jHint'), jResults: $('#jResults'), + jFilter: $('#jFilter'), dSearch: $('#dSearch'), dbGrid: $('#dbGrid') } -const VIEW_TITLE = { home: '科研小盒', journal: '期刊查询', database: '数据库查询' } +const VIEW_TITLE = { home: '科研小盒', journal: '期刊 · 会议查询', database: '数据库查询' } let currentView = 'home' let journalInited = false +let jFilter = 'all' // all | journal | conf /* ================= 视图路由 ================= */ function showView(view) { @@ -65,50 +68,102 @@ function openExternal(url) { } } -/* ================= ① 期刊查询 ================= */ +/* ================= ① 期刊 · 会议查询 ================= */ const QCLASS = { Q1: 'q1', Q2: 'q2', Q3: 'q3', Q4: 'q4' } +const CCFCLASS = { A: 'ccf-a', B: 'ccf-b', C: 'ccf-c' } -function journalCardHTML(j) { - const q = j.quartile || '' +// CCF 领域全称 → 短标签(卡片上显示,避免过长) +const CCF_CAT_SHORT = { + '计算机体系结构/并行与分布计算/存储系统': '体系结构', + '计算机网络': '网络', + '网络与信息安全': '安全', + '软件工程/系统软件/程序设计语言': '软件工程', + '数据库/数据挖掘/内容检索': '数据库', + '计算机科学理论': '理论', + '计算机图形学与多媒体': '图形学', + '人工智能': '人工智能', + '人机交互与普适计算': '人机交互', + '交叉/综合/新兴': '交叉' +} + +function shortCats(cat) { + if (!cat) return '' + return String(cat).split('·').map((s) => CCF_CAT_SHORT[s.trim()] || s.trim()).join(' · ') +} + +// CCF 变动备注 → 徽章文案 +const NOTE_LABEL = { '新增': '新增', '晋级': '晋级', '降级': '降级', '名称更新': '更名' } + +function noteBadge(note) { + if (!note) return '' + const key = Object.keys(NOTE_LABEL).find((k) => note.indexOf(k) !== -1) + if (!key) return '' + return `${esc(NOTE_LABEL[key])}` +} + +function journalCardHTML(r, i) { + const q = r.quartile || '' const qc = QCLASS[q] || 'qna' - const jif = j.jif === '' || j.jif == null ? '—' : j.jif + const hasIf = r.jif !== '' && r.jif != null + const jif = hasIf ? r.jif : '—' + const rank = r.ccfRank || '' + const cc = rank ? (CCFCLASS[rank] || 'ccf-c') : '' + const isConf = !!r._conf + + const metaBits = [] + if (r.issn) metaBits.push(`ISSN ${esc(r.issn)}`) + if (r.category && !isConf) metaBits.push(esc(r.category)) + if (isConf) metaBits.push('会议') + if (!metaBits.length) metaBits.push('—') + + const hasStats = hasIf || r.citations || r.jci || r.jifRank + return ` -
+
-

${esc(j.name)}

-
${esc(j.abbr || '—')} · ${esc(j.publisher || '—')}
+

${esc(r.name)}

+
${esc(r.abbr || r.ccfAbbr || '—')} · ${esc(r.publisher || '—')}
- IF ${esc(jif)} - ${q ? `${esc(q)}` : ''} +
+ ${hasIf ? `IF ${esc(jif)}` : ''} + ${q ? `${esc(q)}` : ''} +
+
+ ${rank ? `CCF ${esc(rank)}` : ''} + ${rank ? `${esc(shortCats(r.ccfCat))}` : ''} + ${noteBadge(r.ccfNotes)} +
- ISSN ${esc(j.issn || '—')} - · - ${esc(j.category || '—')} + ${metaBits.map((m, k) => (k ? `·${m}` : `${m}`)).join('')}
+ ${r.ccfCat ? `
CCF 领域:${esc(r.ccfCat)}
` : ''} + ${hasStats ? `
-
${esc(j.citations || '—')}总被引
-
${esc(j.jci || '—')}JCI
-
${esc(j.oa || '—')}${j.oa ? '%' : ''}金色OA
-
${esc(j.jifRank || '—')}JIF排名
-
- ${j.catDetail ? `
分区详情:${esc(j.catDetail)}
` : ''} +
${esc(r.citations || '—')}总被引
+
${esc(r.jci || '—')}JCI
+
${esc(r.oa || '—')}${r.oa ? '%' : ''}金色OA
+
${esc(r.jifRank || '—')}JIF排名
+ ` : ''} + ${r.catDetail ? `
分区详情:${esc(r.catDetail)}
` : ''}
- ${j.abbr ? `` : ''} + ${(!isConf && r.abbr) ? `` : ''} + ${r.ccfUrl ? `` : ''}
` } function renderJournal(list) { if (!list.length) { - els.jResults.innerHTML = '
未找到匹配的期刊,换个关键词试试。
' + const what = jFilter === 'conf' ? '会议' : jFilter === 'journal' ? '期刊' : '期刊或会议' + els.jResults.innerHTML = `
未找到匹配的${what},换个关键词或切换筛选试试。
` return } - els.jResults.innerHTML = list.map(journalCardHTML).join('') + els.jResults.innerHTML = list.map((r, i) => journalCardHTML(r, r._i == null ? i : r._i)).join('') } function doJournalSearch(q) { @@ -116,12 +171,14 @@ function doJournalSearch(q) { if (!api) return const query = (q || '').trim() if (query) { - const list = api.search(query) + const list = api.search(query, 60, jFilter) renderJournal(list) - els.jHint.innerHTML = `匹配到 ${list.length} 条结果(按影响因子排序,最多 60 条)` + els.jHint.innerHTML = `匹配到 ${list.length} 条${jFilter === 'conf' ? '会议' : jFilter === 'journal' ? '期刊' : '结果'}(按相关度、影响因子排序,最多 60 条)` } else { - renderJournal(api.top(20)) - els.jHint.innerHTML = `共收录 ${api.total} 条期刊数据(默认显示影响因子最高的 20 条)` + const list = api.top(20) + renderJournal(list) + const ccf = api.ccfTotal ? ` · CCF 目录 ${api.ccfTotal} 条独立条目` : '' + els.jHint.innerHTML = `共收录 ${api.total} 条 JCR 期刊${ccf}(默认显示影响因子最高的 20 条)` } } @@ -133,16 +190,18 @@ function initJournal() { els.jHint.textContent = 'preload 未加载,无法读取期刊数据。' return } - // 首次进入触发一次性加载(gzip 解压 + 解析约 <0.1 秒) - els.jHint.textContent = '正在加载期刊数据…' + els.jHint.textContent = '正在加载期刊与 CCF 数据…' setTimeout(() => { const info = api.ensureLoaded() if (!info.ok) { els.jHint.textContent = '数据加载失败:' + info.error - els.jResults.innerHTML = '
无法读取期刊数据,请确认 journals.json.gz 与插件在同一目录。
' + els.jResults.innerHTML = '
无法读取 journals.json.gz,请确认它与插件在同一目录。
' return } journalInited = true + if (info.ccfError) { + els.jHint.innerHTML = `JCR 加载正常,CCF 目录加载失败:${esc(info.ccfError)}` + } doJournalSearch(els.jSearch.value) }, 30) } @@ -153,40 +212,64 @@ els.jSearch.addEventListener('input', () => { jTimer = setTimeout(() => doJournalSearch(els.jSearch.value), 220) }) +els.jFilter.addEventListener('click', (e) => { + const chip = e.target.closest('.chip') + if (!chip) return + jFilter = chip.dataset.filter + els.jFilter.querySelectorAll('.chip').forEach((c) => c.classList.toggle('is-on', c === chip)) + doJournalSearch(els.jSearch.value) +}) + els.jResults.addEventListener('click', (e) => { - const btn = e.target.closest('[data-copy]') - if (btn) { - const name = btn.closest('.card').dataset.name - const j = window.journalApi && window.journalApi.search(name)[0] - if (!j) return - const text = - `期刊: ${j.name}\n缩写: ${j.abbr || '—'}\n出版商: ${j.publisher || '—'}\n` + - `ISSN: ${j.issn || '—'}\n学科: ${j.category || '—'}\n` + - `影响因子(JIF): ${j.jif || '—'} 分区: ${j.quartile || '—'}\n` + - `JCI: ${j.jci || '—'} 5年IF: ${j.fiveYearJif || '—'}\n` + - `总被引: ${j.citations || '—'} 金色OA: ${j.oa || '—'}%\n` + - `JIF排名: ${j.jifRank || '—'}\n分区详情: ${j.catDetail || '—'}\n数据年份: ${j.year || '—'}` - copyToClipboard(text, '已复制期刊信息') + const card = e.target.closest('.card') + if (!card) return + const rec = window.journalApi && window.journalApi.get(Number(card.dataset.idx)) + if (!rec) return + + if (e.target.closest('[data-copy]')) { + const lines = [ + `名称: ${rec.name}`, + `缩写: ${rec.abbr || rec.ccfAbbr || '—'}`, + `出版商/主办: ${rec.publisher || '—'}` + ] + if (rec.ccfRank) { + lines.push(`CCF: ${rec.ccfRank} 类(${rec.ccfType})`) + lines.push(`CCF 领域: ${rec.ccfCat || '—'}`) + if (rec.ccfNotes) lines.push(`CCF 变动: ${rec.ccfNotes}`) + } + if (rec.jif !== '' && rec.jif != null) { + lines.push( + `影响因子(JIF): ${rec.jif} 分区: ${rec.quartile || '—'}`, + `JCI: ${rec.jci || '—'} 5年IF: ${rec.fiveYearJif || '—'}`, + `总被引: ${rec.citations || '—'} 金色OA: ${rec.oa || '—'}%`, + `JIF排名: ${rec.jifRank || '—'}`, + `分区详情: ${rec.catDetail || '—'}`, + `数据年份: ${rec.year || '—'}` + ) + } else { + lines.push('影响因子: 未被 JCR 2026 收录') + } + if (rec.issn) lines.push(`ISSN: ${rec.issn}`) + copyToClipboard(lines.join('\n'), '已复制信息') return } - const jcrBtn = e.target.closest('[data-jcr]') - if (jcrBtn) { - const card = jcrBtn.closest('.card') - const name = card.dataset.name - const j = window.journalApi && window.journalApi.search(name)[0] - if (j && j.abbr) { + if (e.target.closest('[data-jcr]')) { + if (rec.abbr) { openExternal( 'https://jcr.clarivate.com/jcr-jp/journal-profile?journal=' + - encodeURIComponent(j.abbr) + '&year=2025&fromPage=%2Fjcr%2Fhome' + encodeURIComponent(rec.abbr) + '&year=2025&fromPage=%2Fjcr%2Fhome' ) } return } + + if (e.target.closest('[data-home]')) { + if (rec.ccfUrl) openExternal(rec.ccfUrl) + } }) /* ================= ② 数据库查询(体验版) ================= */ -// 通过检索词拼出各数据库的搜索链接,用系统浏览器打开。 const DATABASES = [ { name: 'NCBI Genome', desc: '基因组/物种参考序列', url: (q) => `https://www.ncbi.nlm.nih.gov/genome/?term=${q}` }, { name: 'NCBI Gene', desc: '基因功能与位点信息', url: (q) => `https://www.ncbi.nlm.nih.gov/gene/?term=${q}` }, @@ -220,7 +303,7 @@ els.dbGrid.addEventListener('click', (e) => { els.dSearch.addEventListener('keydown', (e) => { if (e.key === 'Enter') { const q = els.dSearch.value.trim() - if (q) openExternal(DATABASES[0].url(encodeURIComponent(q))) // 回车默认打开第一个(NCBI Genome) + if (q) openExternal(DATABASES[0].url(encodeURIComponent(q))) } }) @@ -236,14 +319,20 @@ if (window.ztools && window.ztools.onPluginEnter) { window.ztools.onPluginEnter((param) => { const code = param && param.code const payload = param && param.payload != null ? String(param.payload) : '' - if (code === 'journal') { + if (code === 'journal' || code === 'ccf') { showView('journal') + if (code === 'ccf' && !payload) { + jFilter = 'conf' + els.jFilter.querySelectorAll('.chip').forEach((c) => c.classList.toggle('is-on', c.dataset.filter === 'conf')) + } if (payload) { els.jSearch.value = payload - // 如果数据已加载完成,立即搜索;否则 initJournal 会在加载后自动触发 + // 数据已加载则立即搜索,否则 initJournal 会在加载完成后自动触发 if (journalInited && window.journalApi && window.journalApi.loaded) { doJournalSearch(payload) } + } else if (journalInited) { + doJournalSearch('') } } else if (code === 'database') { showView('database') diff --git a/plugins/micro-tools/pack.sh b/plugins/micro-tools/pack.sh new file mode 100755 index 000000000..7a06f3691 --- /dev/null +++ b/plugins/micro-tools/pack.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +# ============================================================ +# 打包 科研小盒 (micro-tools) 为 ZTools 可安装的 .zpx +# +# .zpx 格式 = brotli 压缩的 Electron ASAR(ZTools ≥ 2.3.1 采用; +# ZTools 安装时先解压,再按 ASAR 读取 plugin.json) +# +# 用法: bash pack.sh (在插件项目根目录执行) +# 产物: ../micro-tools-v<版本>.zpx (生成在插件目录上一级,方便直接分发) +# +# 关键:只打包「git 跟踪的运行文件」,绝不包含 .git / .DS_Store / +# node_modules / 本脚本 / 未跟踪的临时文件。 +# ============================================================ +set -euo pipefail +cd "$(dirname "$0")" + +[ -f plugin.json ] || { echo "❌ 未找到 plugin.json,请在插件项目根目录运行。" >&2; exit 1; } + +# 从 plugin.json 读取版本号,用于产物命名(版本统一以 plugin.json 为准) +VERSION="$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' plugin.json | head -1 | sed -E 's/.*"([^"]+)"$/\1/')" +[[ -n "$VERSION" ]] || { echo "❌ 无法从 plugin.json 读取 version。" >&2; exit 1; } +OUT="../micro-tools-v${VERSION}.zpx" + +# 收集要打包的文件:git 跟踪文件(天然排除 .git / node_modules / 未跟踪文件), +# 再排除开发类文件:.gitignore、本打包脚本 +if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + echo "❌ 不在 git 仓库内:请先 git init && git add . && git commit(依赖 git 跟踪列表排除 .git)。" >&2 + exit 1 +fi +git ls-files | grep -vE '^(\.gitignore|pack\.sh)$' > /tmp/mt_pack_files.txt +[ -s /tmp/mt_pack_files.txt ] || { echo "❌ 没有可打包的文件(git ls-files 为空),请先 git add 并提交。" >&2; exit 1; } + +# 把文件复制到临时干净目录(保持目录结构) +TMP_SRC="$(mktemp -d)" +TMP_ASAR="${TMP_SRC}/plugin.asar" +while IFS= read -r f; do + mkdir -p "${TMP_SRC}/$(dirname "$f")" + cp "$f" "${TMP_SRC}/$f" +done < /tmp/mt_pack_files.txt + +# 构造 ASAR 并 brotli 压缩为 .zpx(node 内置能力,无需安装 @electron/asar / 联网) +# +# ASAR 布局(与 @electron/asar 完全一致): +# [u32 4][u32 8+jsonLen][u32 4+jsonLen][u32 jsonLen][json][数据区] +# 文件元信息里 offset 为相对数据区起点的偏移(字符串形式) +node -e ' +const fs = require("fs"), path = require("path"), zlib = require("zlib"); +const SRC = process.argv[1], OUT = process.argv[2]; +const list = fs.readFileSync(process.argv[3], "utf8").trim().split("\n").filter(Boolean); +const chunks = []; let off = 0; const tree = {}; +for (const f of list) { + const buf = fs.readFileSync(path.join(SRC, f)); + tree[f] = { size: buf.length, offset: String(off) }; + off += buf.length; chunks.push(buf); +} +const json = Buffer.from(JSON.stringify({ files: tree }), "utf8"); +const head = Buffer.alloc(16); +head.writeUInt32LE(4, 0); +head.writeUInt32LE(8 + json.length, 4); +head.writeUInt32LE(4 + json.length, 8); +head.writeUInt32LE(json.length, 12); +const asar = Buffer.concat([head, json, ...chunks]); +fs.writeFileSync(OUT, zlib.brotliCompressSync(asar)); +console.log("✔ 已生成 " + OUT + " (" + fs.statSync(OUT).size + " B,包内 " + list.length + " 个文件)"); +' "$PWD" "$OUT" /tmp/mt_pack_files.txt + +# 校验:解回 ASAR 并列出包内文件(应只见运行文件,无 .git / .DS_Store) +echo "--- 包内文件 ---" +node -e ' +const fs = require("fs"), zlib = require("zlib"); +const b = zlib.brotliDecompressSync(fs.readFileSync(process.argv[1])); +const jl = b.readUInt32LE(12); +const h = JSON.parse(b.slice(16, 16 + jl).toString("utf8")); +for (const [n, m] of Object.entries(h.files)) console.log(" /" + n + " " + m.size + " B"); +' "$OUT" + +rm -rf "$TMP_SRC" /tmp/mt_pack_files.txt +echo "✅ 打包完成:${OUT}" diff --git a/plugins/micro-tools/plugin.json b/plugins/micro-tools/plugin.json index e6527425b..4e5ae220e 100644 --- a/plugins/micro-tools/plugin.json +++ b/plugins/micro-tools/plugin.json @@ -1,8 +1,8 @@ { "name": "micro-tools", "title": "科研小盒", - "description": "科研常用小工具集合。当前功能:① 期刊查询——基于 JCR 2026 名单查询影响因子(JIF)、分区、JCI、被引等;② 常用数据库查询——一键检索 NCBI Genome / PubMed / UniProt 等(占位,持续扩展)。纯 Preload 模式,无构建步骤,可直接被 ZTools 加载。", - "version": "1.3.0", + "description": "科研常用小工具集合。当前功能:① 期刊·会议查询——JCR 2026 影响因子/分区 + CCF 第七版(2026.3)A/B/C 等级,同一入口检索期刊与会议;② 常用数据库查询——一键检索 NCBI Genome / PubMed / UniProt 等(占位,持续扩展)。纯 Preload 模式,无构建步骤,可直接被 ZTools 加载。", + "version": "1.3.1", "author": "Asa12138", "email": "bfzede@gmail.com", "main": "index.html", @@ -16,7 +16,7 @@ }, { "code": "journal", - "explain": "查询期刊影响因子/分区等信息,例如 期刊 Nature / 影响因子 JACS", + "explain": "查询期刊/会议的影响因子、分区与 CCF 等级,例如 期刊 Nature / 影响因子 JACS", "cmds": [ "期刊", "影响因子", @@ -25,11 +25,21 @@ "jcr", { "type": "over", - "label": "查询期刊", + "label": "查询期刊/会议", "minLength": 1 } ] }, + { + "code": "ccf", + "explain": "查询 CCF 第七版推荐会议与期刊等级,例如 ccf CVPR / 会议 NeurIPS", + "cmds": [ + "ccf", + "会议", + "会议等级", + "conference" + ] + }, { "code": "database", "explain": "常用科研数据库查询,例如 数据库 BRCA1 / ncbi genome", diff --git a/plugins/micro-tools/preload.js b/plugins/micro-tools/preload.js index 13a7513c2..058dd609c 100644 --- a/plugins/micro-tools/preload.js +++ b/plugins/micro-tools/preload.js @@ -2,21 +2,27 @@ // 规则:遵循 CommonJS 规范,可 require Node.js / Electron 原生能力; // 本文件源码保持清晰可读,未压缩混淆,并与插件一起发布。 // -// 职责:读取并解压同目录下的 journals.json.gz(短键名压缩版), -// 映射回完整字段后供前端检索使用。 -// 数据源替换:重新生成 journals.json.gz 即可(字段映射见 KEY_MAP)。 +// 职责:读取并解压同目录下的两个数据集,映射回完整字段后供前端检索: +// 1. journals.json.gz —— JCR 2026 期刊(影响因子 / 分区 / JCI …) +// 2. ccf.json.gz —— CCF 第七版(2026.3)推荐国际学术会议与期刊目录 +// 两者在加载时按 ISSN 关联合并:同一本刊既显示影响因子,也显示 CCF 等级; +// 未被 JCR 收录的 CCF 条目(全部会议 + 少数期刊)作为独立记录并入检索。 const fs = require('fs') const path = require('path') const zlib = require('zlib') const DATA_FILE = 'journals.json.gz' +const CCF_FILE = 'ccf.json.gz' -let DATA = [] +let DATA = [] // JCR 期刊(合并后会挂上 CCF 字段) +let CCF = [] // 未关联 JCR 的独立 CCF 条目 +let ALL = [] // 统一检索数组 = DATA + CCF let LOADED = false let ERROR = null +let CCF_ERROR = null -// 短键名 → 完整字段名(与 Python 压缩脚本的 KEY_MAP 对应) +// 短键名 → 完整字段名(与生成脚本对应) const KEY_MAP = { n: 'name', a: 'abbr', i: 'issn', e: 'eissn', p: 'publisher', c: 'category', j: 'jif', q: 'quartile', @@ -30,8 +36,7 @@ function expand(rec) { for (var k in rec) { out[KEY_MAP[k] || k] = rec[k] } - // 补充计算字段 - var jifStr = String(out.jif || '') + var jifStr = String(out.jif == null ? '' : out.jif) var jifNum = -1 if (jifStr !== '') { var n = parseFloat(jifStr) @@ -41,86 +46,166 @@ function expand(rec) { return out } -// 解压 gzip → JSON → 展开键名 → 记录数组(懒加载) +/* ---------------- 检索辅助 ---------------- */ + +function isIssnLike(q) { + return /^\d{4}-?\d{3}[\dxX]$/.test(q.replace(/\s+/g, '')) +} + +// 归一化:转大写并去除所有非字母数字(消除空格/连字符/点/大小写差异) +function norm(s) { + return String(s == null ? '' : s).toUpperCase().replace(/[^A-Z0-9]/g, '') +} + +var STOP = { THE: 1, OF: 1, AND: 1, A: 1, AN: 1, VOL: 1, NO: 1 } +function tok(s) { + return String(s == null ? '' : s).toUpperCase().replace(/[^A-Z0-9 ]/g, ' ').split(/\s+/).filter(function (t) { return t && !STOP[t] }) +} + +/* ---------------- 加载 JCR ---------------- */ + +function loadJournals() { + var file = path.join(__dirname, DATA_FILE) + var raw = JSON.parse(zlib.gunzipSync(fs.readFileSync(file)).toString('utf-8')) + DATA = new Array(raw.length) + for (var i = 0; i < raw.length; i++) { + var j = expand(raw[i]) + j._tokensAll = tok((j.abbr || '') + ' ' + (j.name || '')) + DATA[i] = j + } +} + +/* ---------------- 加载 CCF 并合并 ---------------- */ + +function loadCcf() { + var file = path.join(__dirname, CCF_FILE) + var raw = JSON.parse(zlib.gunzipSync(fs.readFileSync(file)).toString('utf-8')) + + // ISSN/eISSN → JCR 记录(用于校验下标是否仍然有效) + var byIssn = {} + for (var i = 0; i < DATA.length; i++) { + var d = DATA[i] + if (d.issn) byIssn[d.issn] = d + if (d.eissn) byIssn[d.eissn] = d + } + + CCF = [] + var linked = 0 + + for (var k = 0; k < raw.length; k++) { + var o = raw[k] + var type = o.t === 1 ? '期刊' : '会议' + var info = { + ccfRank: o.r || '', + ccfCat: o.c || '', + ccfType: type, + ccfUrl: o.u || '', + ccfNotes: o.x || '', + ccfAbbr: o.a || '' + } + + var host = null + if (o.k != null) { + var cand = DATA[o.k] + if (cand && (!o.s || cand.issn === o.s || cand.eissn === o.s)) { + host = cand + } else if (o.s && byIssn[o.s]) { + host = byIssn[o.s] // 下标失效时退回 ISSN 查找 + } + } + + if (host) { + // 关联成功:把 CCF 信息挂到 JCR 期刊上,并把 CCF 缩写/中文领域并入检索 + host.ccfRank = info.ccfRank + host.ccfCat = info.ccfCat + host.ccfType = type + host.ccfUrl = info.ccfUrl + host.ccfNotes = info.ccfNotes + host.ccfAbbr = info.ccfAbbr + host._tokensAll = host._tokensAll.concat(tok((o.a || '') + ' ' + (o.n || '') + ' ' + (o.c || ''))) + linked++ + continue + } + + // 无 JCR 对应(会议,或已被 JCR 除名的期刊):独立成条 + var rec = { + name: o.n || '', + abbr: o.a || '', + publisher: o.p || '', + ccfRank: info.ccfRank, + ccfCat: info.ccfCat, + ccfType: type, + ccfUrl: info.ccfUrl, + ccfNotes: info.ccfNotes, + ccfAbbr: info.ccfAbbr, + // 与 JCR 记录保持同构,便于复用同一套评分/渲染 + issn: '', eissn: '', jif: '', jifNum: -1, quartile: '', + jci: '', citations: '', fiveYearJif: '', jifRank: '', oa: '', catDetail: '', + category: o.c || '', year: '', + _conf: type === '会议', + _tokensAll: tok((o.a || '') + ' ' + (o.n || '') + ' ' + (o.p || '') + ' ' + (o.c || '')) + } + CCF.push(rec) + } + + ALL = DATA.concat(CCF) + // 记录自身在统一数组中的下标,供渲染后按 data-idx 精确回查(避免按名字反查出错) + for (var z = 0; z < ALL.length; z++) ALL[z]._i = z + return { total: raw.length, linked: linked, standalone: CCF.length } +} + function ensureLoaded() { - if (LOADED) return { ok: !ERROR, total: DATA.length, error: ERROR } + if (LOADED) return { ok: !ERROR, total: DATA.length, error: ERROR, ccf: CCF.length, ccfError: CCF_ERROR } LOADED = true try { - var file = path.join(__dirname, DATA_FILE) - var gzBuf = fs.readFileSync(file) - var jsonBuf = zlib.gunzipSync(gzBuf) - var raw = JSON.parse(jsonBuf.toString('utf-8')) - - DATA = new Array(raw.length) - for (var i = 0; i < raw.length; i++) { - var j = expand(raw[i]) - // 构建检索用的小写拼接串(一次性) - j._hay = ((j.name || '') + ' ' + (j.abbr || '') + ' ' + - (j.issn || '') + ' ' + (j.eissn || '')).toLowerCase() - // 缩写前缀匹配用:同时覆盖“缩写 + 全称”的全部词(去停用词) - j._tokensAll = tok((j.abbr || '') + ' ' + (j.name || '')) - DATA[i] = j - } + loadJournals() ERROR = null } catch (e) { ERROR = String((e && e.message) || e) DATA = [] console.error('[科研小盒] 加载 journals.json.gz 失败:', ERROR) } - return { ok: !ERROR, total: DATA.length, error: ERROR } + try { + if (!ERROR) loadCcf() + } catch (e2) { + CCF_ERROR = String((e2 && e2.message) || e2) + CCF = [] + ALL = DATA + console.error('[科研小盒] 加载 ccf.json.gz 失败:', CCF_ERROR) + } + if (!ALL.length) ALL = DATA.concat(CCF) + return { ok: !ERROR, total: DATA.length, error: ERROR, ccf: CCF.length, ccfError: CCF_ERROR } } /* ---------------- 搜索相关性评分 ---------------- */ -function isIssnLike(q) { - return /^\d{4}-?\d{3}[\dxX]$/.test(q.replace(/\s+/g, '')) -} - /** - * 计算查询词与期刊的相关性得分(越高越相关) - * 3 = 精确匹配(ISSN / 期刊全名 / 缩写完全等于 query) + * 3 = 精确匹配(ISSN / 全称 / 缩写 / CCF 缩写 完全等于 query) * 2 = 词首/前缀匹配 - * 1 = 包含匹配 + * 1 = 包含匹配 / 全词命中 * 0 = 不匹配 */ -// 归一化:转大写并去除所有非字母数字(消除空格/连字符/点/大小写差异) -// 例: "Acta Derm Venereol" 与 "ACTA DERM-VENEREOL" 归一化后均为 "ACTADERMVENEREOL" -function norm(s) { - return String(s == null ? '' : s).toUpperCase().replace(/[^A-Z0-9]/g, '') -} - -// 分词 + 停用词(用于词级回退匹配) -var STOP = { THE: 1, OF: 1, AND: 1, A: 1, AN: 1, VOL: 1, NO: 1 } -function tok(s) { - return String(s == null ? '' : s).toUpperCase().replace(/[^A-Z0-9 ]/g, ' ').split(/\s+/).filter(function (t) { return t && !STOP[t] }) -} +function relevanceScore(r, q) { + // 中文查询(如"人工智能")归一化后会变成空串,需在此单独处理:只匹配 CCF 中文领域 + if (r.ccfCat && q && String(r.ccfCat).indexOf(q) !== -1) return 1 -function relevanceScore(journal, q) { var qn = norm(q) if (!qn) return 0 - // ISSN 精确匹配 - var issnNorm = norm(journal.issn || '') - var eissnNorm = norm(journal.eissn || '') - if (issnNorm === qn || eissnNorm === qn) return 3 + var issnNorm = norm(r.issn || '') + var eissnNorm = norm(r.eissn || '') + if ((issnNorm && issnNorm === qn) || (eissnNorm && eissnNorm === qn)) return 3 - var name = norm(journal.name || '') - var abbr = norm(journal.abbr || '') + var name = norm(r.name || '') + var abbr = norm(r.abbr || '') + var ccfA = norm(r.ccfAbbr || '') - // 精确全名/缩写(已归一化,自动容忍符号与大小写) - if (name === qn || abbr === qn) return 3 - - // 前缀匹配 - if (name.indexOf(qn) === 0 || abbr.indexOf(qn) === 0) return 2 - - // 子串包含 - if (name.indexOf(qn) !== -1 || abbr.indexOf(qn) !== -1) return 1 - - // 词级回退:查询的每个分词都能在刊名/缩写分词中找到,或作为某词前缀(如 N→NEW) - // 注意:必须用“原始带空格的 q”做分词,不能用归一化后的结果(会丢失词边界) + if (name === qn || abbr === qn || ccfA === qn) return 3 + if (name.indexOf(qn) === 0 || abbr.indexOf(qn) === 0 || ccfA.indexOf(qn) === 0) return 2 + if (name.indexOf(qn) !== -1 || abbr.indexOf(qn) !== -1 || ccfA.indexOf(qn) !== -1) return 1 var qt = tok(q) if (qt.length) { - var recTok = tok(journal.abbr || journal.name) + var recTok = tok((r.abbr || '') + ' ' + (r.name || '') + ' ' + (r.ccfAbbr || '')) if (recTok.length) { var hit = 0 for (var ti = 0; ti < qt.length; ti++) { @@ -134,17 +219,13 @@ function relevanceScore(journal, q) { } } - // 缩写前缀匹配:处理 PubMed/NLM 缩写与 JCR 全称/缩写差异。 - // query 每个词必须是候选某词的前缀或反之(双向),且全部词命中。 - // 例: "Comput Struct Biotechnol J" -> "Computational and Structural Biotechnology Journal" - // "Probiotics Antimicrob Proteins" -> "Probiotics and Antimicrobial Proteins" - // "Clinical, Cosmetic and Investigational Dermatology" -> "CLIN COSMET INV DERM" - if (qt.length >= 2 && journal._tokensAll && journal._tokensAll.length) { + // 缩写前缀双向匹配(处理 PubMed/NLM 缩写与全称差异) + if (qt.length >= 2 && r._tokensAll && r._tokensAll.length) { var allHit = true for (var ci = 0; ci < qt.length; ci++) { var qw = qt[ci], found = false - for (var di = 0; di < journal._tokensAll.length; di++) { - var cw = journal._tokensAll[di] + for (var di = 0; di < r._tokensAll.length; di++) { + var cw = r._tokensAll[di] if (cw.indexOf(qw) === 0 || qw.indexOf(cw) === 0) { found = true; break } } if (!found) { allHit = false; break } @@ -162,51 +243,61 @@ window.journalApi = { get loaded() { return LOADED && !ERROR }, get total() { return DATA.length }, get error() { return ERROR }, + get allTotal() { return ALL.length }, + get ccfTotal() { return CCF.length }, + get ccfError() { return CCF_ERROR }, + + /** 按下标取统一检索数组中的记录(渲染与操作复用,避免按名字反查出错) */ + get(i) { return ALL[i] }, /** - * 搜索期刊:按相关性得分排序(精确 > 前缀 > 包含),同分按 JIF 高→低 - * @param {string} query 查询词(期刊名 / 缩写 / ISSN) - * @param {number} limit 最大返回条数 + * 统一检索:JCR 期刊 + CCF 目录 + * @param {string} query + * @param {number} limit + * @param {string} filter 'all' | 'journal' | 'conf' */ - search(query, limit /* = 60 */) { + search(query, limit /* = 60 */, filter /* = 'all' */) { if (limit == null) limit = 60 + if (filter == null) filter = 'all' ensureLoaded() - var q = String(query == null ? '' : query).trim().toLowerCase() + var q = String(query == null ? '' : query).trim() if (!q) return [] + var out = [] + var wantConf = filter === 'conf' + var wantJour = filter === 'journal' + // ISSN 路径:精确匹配优先 if (isIssnLike(q)) { - var norm = q.replace(/\s+/g, '') - var results = [] - for (var i = 0; i < DATA.length; i++) { - var j = DATA[i] - if ((j.issn || '').replace(/\s+/g, '').toLowerCase() === norm || - (j.eissn || '').replace(/\s+/g, '').toLowerCase() === norm) { - results.push(j) - if (results.length >= limit) break - } + var nq = q.replace(/\s+/g, '').toLowerCase() + for (var i = 0; i < ALL.length && out.length < limit; i++) { + var r = ALL[i] + if (r._conf && wantJour) continue + if (!r._conf && wantConf) continue + if ((r.issn || '').replace(/\s+/g, '').toLowerCase() === nq || + (r.eissn || '').replace(/\s+/g, '').toLowerCase() === nq) out.push(r) } - return results + return out } - // 通用路径:评分 + 排序 + var ql = q.toLowerCase() var scored = [] - for (var k = 0; k < DATA.length; k++) { - var journal = DATA[k] - var s = relevanceScore(journal, q) - if (s > 0) scored.push({ j: journal, score: s }) + for (var k = 0; k < ALL.length; k++) { + var rec = ALL[k] + if (rec._conf && wantJour) continue + if (!rec._conf && wantConf) continue + var s = relevanceScore(rec, ql) + if (s > 0) scored.push({ r: rec, score: s, i: k }) } scored.sort(function (a, b) { if (b.score !== a.score) return b.score - a.score - return b.j.jifNum - a.j.jifNum + if (b.r.jifNum !== a.r.jifNum) return b.r.jifNum - a.r.jifNum + return a.i - b.i }) - var result = [] - for (var m = 0; m < scored.length && m < limit; m++) { - result.push(scored[m].j) - } - return result + for (var m = 0; m < scored.length && m < limit; m++) out.push(scored[m].r) + return out }, top(n /* = 20 */) {