// ==UserScript== // @name 期刊评级查询 // @version 1.2 // @description 还行 // @author ZhaoXi // @match *://*/* // @include * // @grant GM.xmlHttpRequest // @connect wechat6d.fenqubiao.com // @tag test // ==/UserScript== // bug: // 设置2秒自动隐藏,设置固定大小 // 完善自动调整位置 // 需要适配手机版 /* 代码规范: 确保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 = 200000 // 按钮自动隐藏时间,默认2000ms // 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操作 let floatButton = null; let isQueryActive = false; // 标记是否正在查询或已出结果,避免按钮无法移除 let autoHideTimer = null; // 自动隐藏计时器 // 划词事件监听器 (兼容 PC 和 移动端) function handleSelectionEnd(event) { // 对于 touchend 事件,阻止默认的长按弹窗(可选) // if (event.type === 'touchend') { event.preventDefault(); } setTimeout(() => { if (isQueryActive) return; const selection = window.getSelection(); const text = selection.toString().trim(); if (!text || selection.rangeCount === 0) return; // 清除旧按钮 removeFloatButton(); const range = selection.getRangeAt(0); const rect = range.getBoundingClientRect(); // 过滤大面积选择(防误触) if (rect.height < 60 && rect.width > 0) { createFloatButton(rect); } }, 50); // 延迟确保 selection 稳定 } // 同时监听鼠标释放和触摸结束事件 document.addEventListener('mouseup', handleSelectionEnd); document.addEventListener('touchend', handleSelectionEnd, { passive: true }); function createFloatButton(rect) { if (floatButton) return; // 防重复 floatButton = document.createElement('button'); floatButton.id = 'journal-float-button' floatButton.textContent = '查评级'; // 在创建按钮的style中添加: 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; `; floatButton.addEventListener('click', async () => { // 清除自动隐藏计时器 clearTimeout(autoHideTimer); const text = window.getSelection().toString().trim(); if (text) { isQueryActive = true console.log('查询中:', text) let m_box = await Api.main(text) showResultPanel(m_box) } removeFloatButton(); }); document.body.appendChild(floatButton); positionButton(rect); // 设置自动隐藏计时器(2秒后自动隐藏) resetAutoHideTimer(); } function positionButton(rect) { floatButton.style.top = (rect.top + window.scrollY + 33) + 'px'; floatButton.style.left = (rect.left + window.scrollX + rect.width / 2 - 30) + 'px'; } function removeFloatButton() { // 清除计时器 if (autoHideTimer) { clearTimeout(autoHideTimer); autoHideTimer = null; } if (floatButton) { floatButton.remove(); floatButton = null; } } function resetAutoHideTimer() { // 清除现有计时器 if (autoHideTimer) { clearTimeout(autoHideTimer); } // 设置新计时器 autoHideTimer = setTimeout(() => { if (floatButton && !isQueryActive) { removeFloatButton(); } }, hidden_time); // 2秒后自动隐藏 } // 点击页面其他地方,关闭按钮 document.addEventListener('click', (e) => { if (floatButton && e.target !== floatButton) { removeFloatButton(); } }); // 鼠标悬停在按钮上时重置计时器,避免误隐藏 document.addEventListener('mouseover', (e) => { if (e.target === floatButton) { resetAutoHideTimer(); } }); function 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 = `
期刊名称:${title.toLowerCase()}
ISSN:${ISSN}
大类及分区: ${zky['name']} ${zky['class']}区
小类及分区:
${jcr.map(item => `${item['name'].toLowerCase()} ${item['class']}区` ).join('
')}
`; } else { let msg = m_box.msg let variable = m_box.variable panel.innerHTML = `
${msg} ${variable}
` } // 添加到页面 document.body.appendChild(panel); // 定位:放在选区正下方 let default_height = 150 let default_width = 250 const rect = window.getSelection().getRangeAt(0).getBoundingClientRect() let top = rect.bottom + window.scrollY + 5; let left = rect.left + window.scrollX - 15; // 防止面板超出右侧屏幕 if (left + default_width > window.innerWidth + window.scrollX) { left = window.innerWidth + window.scrollX - default_width - 10; } if (top + default_height > window.innerHeight + window.scrollY) { top = rect.top + window.scrollY - default_height - 5 } panel.style.top = top + 'px'; panel.style.left = left + 'px'; // === 修改:同时支持鼠标和触摸点击关闭 === setTimeout(() => { function closePanel(e) { if (!panel.contains(e.target)) { panel.remove(); document.removeEventListener('click', closePanel); document.removeEventListener('touchstart', closePanel); isQueryActive = false; } } document.addEventListener('click', closePanel); document.addEventListener('touchstart', closePanel); }, 100); } })();