// ==UserScript== // @name 期刊评级查询 // @version 1.3 // @description 网页期刊查询小工具,支持划词和快捷键调用了 // @author ZhaoXi // @match *://*/* // @include * // @grant GM.xmlHttpRequest // @grant GM_getValue // @connect wechat6d.fenqubiao.com // ==/UserScript== /* ==UserConfig== group: configA: title: 快捷键配置(选择是否开启快捷键) description: 通过快捷键唤起查询 type: select default: '0 关闭快捷键(默认)' values: ['0 关闭快捷键(默认)','1 仅使用快捷键','2 同时使用按钮和快捷键'] configB: title: 选择快捷键(需要先开启快捷键) description: 需要先开启快捷键 type: select default: 'Alt + J' values: ['Alt + J','Alt + L','Ctrl + Q','其他快捷键请按格式添加'] ==/UserConfig== */ /* 代码规范: Python移植方便 保证线性结构 分开网络请求和DOM操作 */ console.log('》》》 期刊评级程序已启用!😋 开始愉快的学习吧(doge)') // 封装原函数,实现请求顺序执行 function gmFetch(url, options = {}) { return new Promise((resolve, reject) => { // 解构并排除 onload/onerror,防止覆盖 const { onload, onerror, ...otherOptions } = options; GM.xmlHttpRequest({ url, method: options.method || 'GET', headers: options.headers || {}, data: options.body, onload: (res) => { resolve(res); }, onerror: (err) => { reject(err); }, ...otherOptions }); }); } (function () { 'use strict'; // 全局属性 let Year = '2025' let Hidden_time = 2000 // 按钮自动隐藏时间,默认2000ms // 获取配置信息 let ShortcutEnabled = Number(GM_getValue('group.configA').split(' ')[0]) let ShortcutKeys = null if (ShortcutEnabled > 0) { let shortcutStr = GM_getValue('group.configB') if (shortcutStr.includes('+')) { ShortcutKeys = parseShortcut(shortcutStr) console.log(ShortcutKeys) } else { ShortcutEnabled = 0 } } function parseShortcut(shortcutStr) { const parts = shortcutStr.split('+').map(part => part.trim()); let altKey = false; let ctrlKey = false; let key = ''; for (const part of parts) { if (part.toLowerCase() === 'alt') { altKey = true; } else if (part.toLowerCase() === 'ctrl') { ctrlKey = true; } else { // 假设最后一个非修饰键的部分是实际按键 key = part.toUpperCase(); // 转换为大写便于比较 } } return { altKey, ctrlKey, key }; } // API实现 const Api = { home_url: "https://wechat6d.fenqubiao.com", headers: { "Host": "wechat6d.fenqubiao.com", "Content-Type": "application/json", }, msg_codes: ['成功', '请求出错!请重试', '未检索到该期刊:', '未知错误'], msg_box: function (code, variable = null) { let msg = Api.msg_codes[code] return { code, msg, variable } }, async search(keyword) { const suffix = "/api/journal/search" let params = { "keyword": keyword, "year": Year, "top": "15", "code": "chenfuyou" } const url_params = new URLSearchParams(params) const url = `${Api.home_url}${suffix}?${url_params}` console.log('query_url=', url) const response = await gmFetch(url, { method: 'GET', headers: Api.headers }) if (response.status < 200 || response.status >= 300) { return Api.msg_box(1) } try { // 这里直接粗暴try判断是否拿到期刊id if (!response.responseText) return Api.msg_box(2, { keyword }) const journal_id = JSON.parse(response.responseText)[0]['id'] return Api.msg_box(0, journal_id) // get_detail(journal_id) } catch (e) { return Api.msg_box(2, keyword) } }, async get_detail(id1) { const suffix = '/api/journal/get' let params = { "year": Year, "id": id1, "code": "chenfuyou" } const url_params = new URLSearchParams(params) const url = `${Api.home_url}${suffix}?${url_params}` const response = await gmFetch(url, { method: 'GET', headers: Api.headers, }) if (response.status < 200 || response.status >= 300) { return Api.msg_box(1) } try { const result = JSON.parse(response.responseText); if (!result) { return Api.msg_box(3, id1) } else { return Api.msg_box(0, result) } } catch (e) { return Api.msg_box(2) } }, format_output(result) { const title = result['title'] const ISSN = result['issn'] const zky = result['zky'][0] const jcr = result['jcr'] console.log("期刊名称:" + title.toLowerCase()) console.log("ISSN:" + ISSN) console.log("大类及分区:" + zky['name'] + zky['class'] + '区') console.log("小类及分区:") for (let i in jcr) { console.log(`${jcr[i]['name'].toLowerCase()} ${jcr[i]['class']}区`) } return 0 }, async main(keyword) { let m_box = await Api.search(keyword) console.log('搜索结果', m_box) if (m_box.code !== 0) { return m_box } const id1 = m_box.variable m_box = await Api.get_detail(id1) console.log('查询结果', m_box) if (m_box.code === 0) { Api.format_output(m_box.variable) } return m_box } } // DOM操作 const UI = { floatButton: null, isQueryActive: false, // 标记是否正在查询或已出结果,避免按钮无法移除 autoHideTimer: null, // 自动隐藏计时器 createFloatButton(rect) { if (this.floatButton) return; // 防重复 this.floatButton = document.createElement('button'); this.floatButton.id = 'journal-float-button' this.floatButton.textContent = '查评级'; // 在创建按钮的style中添加: this.floatButton.style.cssText = ` position: absolute; width: 60px; /* 固定宽度 */ height: 30px; /* 固定高度 */ line-height: 30px; /* 文字垂直居中 */ text-align: center; /* 文字水平居中 */ background: #0078d4; color: white; border: none; padding: 0 10px; /* 去掉上下内边距,仅保留左右 */ font-size: 12px; border-radius: 4px; cursor: pointer; box-shadow: 0 2px 6px rgba(0,0,0,0.2); z-index: 9999; transform: translateY(-8px); /* 强制继承基础样式,避免页面干扰 */ font-family: sans-serif; margin: 0; `; this.floatButton.addEventListener('click', async () => { // 清除自动隐藏计时器 clearTimeout(this.autoHideTimer); const text = window.getSelection().toString().trim(); if (text) { this.isQueryActive = true console.log('查询中:', text) let m_box = await Api.main(text) this.showResultPanel(m_box) } this.removeFloatButton(); }); document.body.appendChild(this.floatButton); this.positionButton(rect); this.resetAutoHideTimer(); }, positionButton(rect) { this.floatButton.style.top = (rect.top + window.scrollY + 33) + 'px'; this.floatButton.style.left = (rect.left + window.scrollX + rect.width / 2 - 30) + 'px'; }, removeFloatButton() { // 清除计时器 if (this.autoHideTimer) { clearTimeout(this.autoHideTimer); this.autoHideTimer = null; } if (this.floatButton) { this.floatButton.remove(); this.floatButton = null; } }, resetAutoHideTimer() { // 清除现有计时器 if (this.autoHideTimer) { clearTimeout(this.autoHideTimer); } // 设置新计时器 this.autoHideTimer = setTimeout(() => { if (this.floatButton && !this.isQueryActive) { this.removeFloatButton(); } }, Hidden_time); }, showResultPanel(m_box) { // 如果已有面板,先移除 const existing = document.getElementById('journal-result-panel'); if (existing) existing.remove(); const panel = document.createElement('div'); panel.id = 'journal-result-panel'; panel.style.cssText = ` position: absolute; top: 40px; left: 0; background: white; color: #333; font-family: Consolas, monospace, sans-serif; font-size: 13px; line-height: 1.6; border: 1px solid #ccc; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); padding: 12px; width: auto; min-width: 150px; max-height: 400px; overflow-y: auto; z-index: 99999; cursor: default; `; // 消息盒子code!=0 时改为显示报错信息 if (m_box.code === 0) { let result = m_box.variable const title = result['title']; const ISSN = result['issn']; const zky = result['zky'][0]; const jcr = result['jcr']; // 构建内容 // 大类分区颜色映射(1区红色,2区橙色,3-4区灰色) const getClassColor = (classNum) => { if (classNum === 1) { return 'color: #f2c01d; font-weight: bold;'; // 金色加粗(1区) } else if (classNum === 2) { return 'color: #944aec;'; // 紫色(2区) } else { return 'color: #7c430b;'; // 铜色(3,4区) } }; panel.innerHTML = `