在页面右下角添加一个悬浮面板,提供按年份和引用数排序的功能,两者均可切换升/降序。
目前為
// ==UserScript==
// @name 📚谷歌学术排序工具箱 (按年份、引用数)
// @namespace http://tampermonkey.net/
// @version 2025.07.10.005
// @description 在页面右下角添加一个悬浮面板,提供按年份和引用数排序的功能,两者均可切换升/降序。
// @author heyue
// @match https://scholar.google.com/scholar?*
// @match https://sc.panda985.com/scholar?*
// @icon https://www.google.com/s2/favicons?sz=64&domain=google.com
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// --- 状态变量,记录当前应用的排序规则 ---
let currentSortState = {
key: null, // 'year' or 'cite'
descending: null // true or false
};
// --- 数据提取辅助函数 ---
function getYear(node) {
const authorLine = node.querySelector('.gs_a');
if (!authorLine) return 0;
const text = authorLine.textContent;
const yearMatch = text.match(/\b(19|20)\d{2}\b/);
return yearMatch ? parseInt(yearMatch[0], 10) : 0;
}
function getCiteCount(node) {
const citeLink = node.querySelector('a[href*="/scholar?cites"]');
if (!citeLink) return 0;
const citeMatch = citeLink.textContent.match(/\d+/);
return citeMatch ? parseInt(citeMatch[0], 10) : 0;
}
// --- 核心排序逻辑 ---
/**
* 对当前页面的搜索结果进行排序。
* @param {string} sortKey - 'year' 或 'cite'
* @param {boolean} isDescending - true为降序,false为升序
*/
function sortElements(sortKey, isDescending) {
const gsResCclMid = document.getElementById('gs_res_ccl_mid');
if (!gsResCclMid) {
console.error('错误:未找到结果容器 #gs_res_ccl_mid');
return;
}
// 1. 获取所有结果,并为每项提取年份和引用数
const elementsWithData = [...gsResCclMid.querySelectorAll('.gs_or')]
.map(node => ({
node: node,
year: getYear(node),
cite: getCiteCount(node)
}));
// 2. 执行排序
elementsWithData.sort((a, b) => {
if (sortKey === 'year') {
// 按年份排序
const yearDiff = isDescending ? b.year - a.year : a.year - b.year;
if (yearDiff !== 0) {
return yearDiff;
}
// 【复合排序】如果年份相同,则按引用数从高到低排序
return b.cite - a.cite;
} else {
// 按引用数排序
return isDescending ? b.cite - a.cite : a.cite - b.cite;
}
});
// 3. 重新渲染结果
gsResCclMid.innerHTML = '';
gsResCclMid.append(...elementsWithData.map(item => item.node));
// 4. 更新全局排序状态
currentSortState = { key: sortKey, descending: isDescending };
console.log(`当前页已按【${sortKey === 'year' ? '年份' : '引用数'}】进行【${isDescending ? '降序' : '升序'}】排序。`);
}
// --- UI 创建与交互 ---
/**
* 在页面上创建并添加排序工具面板。
*/
function createSortPanel() {
// --- 样式定义 ---
const normalStyle = { backgroundColor: '#f8f9fa', color: '#202124', border: '1px solid #dadce0' };
const activeStyle = { backgroundColor: '#4285F4', color: 'white', border: '1px solid #4285F4' };
// 1. 创建面板容器
const panel = document.createElement('div');
Object.assign(panel.style, {
position: 'fixed', bottom: '20px', right: '20px', zIndex: '9999',
backgroundColor: 'white', border: '1px solid #dadce0', borderRadius: '8px',
padding: '12px 16px', boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
display: 'flex', flexDirection: 'column', gap: '12px'
});
// 2. 创建一个排序行的辅助函数 (减少代码重复)
const createSortRow = (labelText, key, descText, ascText) => {
const row = document.createElement('div');
Object.assign(row.style, { display: 'flex', alignItems: 'center', gap: '8px' });
const label = document.createElement('span');
Object.assign(label.style, { fontSize: '14px', color: '#5f6368', minWidth: '85px' });
label.textContent = labelText;
const buttonStyle = {
fontSize: '13px', padding: '6px 10px', cursor: 'pointer',
borderRadius: '4px', fontWeight: '500', transition: 'all 0.2s'
};
const descButton = document.createElement('button');
descButton.textContent = descText;
Object.assign(descButton.style, buttonStyle);
const ascButton = document.createElement('button');
ascButton.textContent = ascText;
Object.assign(ascButton.style, buttonStyle);
row.append(label, descButton, ascButton);
return { row, descButton, ascButton };
};
// 3. 创建两行排序控件
const yearRow = createSortRow('按年份排序:', 'year', '新 → 旧', '旧 → 新');
const citeRow = createSortRow('按引用数排序:', 'cite', '高 → 低', '低 → 高');
// 4. 定义更新高亮状态的函数
const updateHighlights = () => {
const allButtons = [
{ btn: yearRow.descButton, key: 'year', desc: true },
{ btn: yearRow.ascButton, key: 'year', desc: false },
{ btn: citeRow.descButton, key: 'cite', desc: true },
{ btn: citeRow.ascButton, key: 'cite', desc: false },
];
allButtons.forEach(item => {
const isActive = (currentSortState.key === item.key && currentSortState.descending === item.desc);
Object.assign(item.btn.style, isActive ? activeStyle : normalStyle);
});
};
// 5. 为按钮绑定事件
yearRow.descButton.addEventListener('click', () => { sortElements('year', true); updateHighlights(); });
yearRow.ascButton.addEventListener('click', () => { sortElements('year', false); updateHighlights(); });
citeRow.descButton.addEventListener('click', () => { sortElements('cite', true); updateHighlights(); });
citeRow.ascButton.addEventListener('click', () => { sortElements('cite', false); updateHighlights(); });
// 6. 组装并添加到页面
panel.append(yearRow.row, citeRow.row);
document.body.appendChild(panel);
// 7. 初始化按钮状态
updateHighlights();
}
// 等待页面完全加载后再执行
if (document.readyState === 'complete') {
createSortPanel();
} else {
window.addEventListener('load', createSortPanel);
}
})();