您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
【智能导航最终版】无论在哪一页点击,都会自动返回第一页开始统计,并集成所有计算规则。
// ==UserScript== // @name 研发云代码量统计 (智能导航最终版) // @namespace http://tampermonkey.net/ // @version 3.4 // @description 【智能导航最终版】无论在哪一页点击,都会自动返回第一页开始统计,并集成所有计算规则。 // @author Gemini & yaya // @match https://10.1.6.39/index.php?m=my&f=mycommitlog* // @grant none // @icon https://www.google.com/s2/favicons?sz=64&domain=10.1.6.39 // ==/UserScript== (function() { 'use strict'; // --- ⚙️ 配置区域 --- const QUERY_BUTTON_SELECTOR = 'button#submit'; const TABLE_SELECTOR = 'table.table-list'; const PAGER_CONTAINER_SELECTOR = 'div.table-footer .pager'; const DATE_COLUMN_INDEX = 2, SUFFIX_COLUMN_INDEX = 4, ADDED_COLUMN_INDEX = 6, DELETED_COLUMN_INDEX = 7, IS_VALID_COLUMN_INDEX = 8; const CONVERSION_RATES = { 'java': 1, 'h': 1.3, 'm': 1, 'mm': 1, 'xib': 1, 'htm': 0.1, 'html': 0.1, 'tpl': 1, 'shtml': 0.1, 'css': 0.1, 'js': 0.7, 'less': 1, 'php': 1, 'jsp': 0.1, 'c': 1.3, 'cpp': 1.3, 'xml': 0.5, 'sql': 1, 'other': 0.1, 'bshrc': 0.3, 'jmx': 0.5, 'vue': 0.7, 'sh': 1, 'sol': 1.3, 'yml': 1, 'yaml': 1, 'go': 1.2, 'py': 1, 'json': 0.1, 'dart': 1, 'swift': 1.3, 'kt': 1, 'ts': 0.7, 'sass': 0.4, 'scala': 1, 'lua': 1, 'wpy': 1, 'cs': 1, 'ftl': 1, 'properties': 1, 'rb': 1, 'scss': 0.1, 'vm': 1, 'nvue': 0.8 }; // --- 配置区域结束 --- const LOG_PREFIX = '[代码统计脚本 v3.4]:'; const SESSION_STORAGE_KEY_DATA = 'codeStatsData'; const SESSION_STORAGE_KEY_FLAG = 'isCollectingCodeStats'; let isProcessing = false; /** * 【重要更新】当用户点击按钮时,智能判断并启动任务 */ async function startFullStats() { if (isProcessing) { alert('正在统计中,请稍候...'); return; } console.log(LOG_PREFIX, '==================== 用户点击,开始新的统计任务 ===================='); // 立刻设置标记,无论在哪一页,都表明一个新任务开始了 sessionStorage.setItem(SESSION_STORAGE_KEY_DATA, JSON.stringify({ byDate: {}, bySuffix: {} })); sessionStorage.setItem(SESSION_STORAGE_KEY_FLAG, 'true'); const { currentPage } = getPagerInfo(); const statsButton = document.getElementById('start-stats-button'); const modal = document.getElementById('stats-modal-container'); if (currentPage === 1) { // 如果已经在第一页,直接开始 console.log(LOG_PREFIX, '当前已在第 1 页,直接开始统计。'); isProcessing = true; if (modal) modal.style.display = 'flex'; if (statsButton) { statsButton.textContent = '统计中...'; statsButton.disabled = true; } await processCurrentPage(); } else { // 如果不在第一页,则导航过去,让页面重新加载后自动开始 console.log(LOG_PREFIX, `当前在第 ${currentPage} 页,正在导航至第 1 页...`); if (modal) modal.style.display = 'flex'; updateStatus('正在导航至第 1 页...'); if (statsButton) { statsButton.textContent = '导航中...'; statsButton.disabled = true; } const firstPageButton = findFirstPageButton(); if (firstPageButton) { firstPageButton.click(); // 点击后页面刷新,后续操作由主入口的自动检测逻辑接管 } else { alert('错误:无法找到“首页”按钮,请手动返回第一页后重试。'); console.error(LOG_PREFIX, '无法找到“首页”按钮。'); // 清理掉我们设置的标记,因为任务无法开始 sessionStorage.removeItem(SESSION_STORAGE_KEY_DATA); sessionStorage.removeItem(SESSION_STORAGE_KEY_FLAG); if (modal) modal.style.display = 'none'; if (statsButton) { statsButton.innerHTML = '📊 统计代码量'; statsButton.disabled = false; } } } } // --- 以下为无需修改的函数 --- function findFirstPageButton() { const pagerLinks = document.querySelectorAll(`${PAGER_CONTAINER_SELECTOR} a`); for (const link of pagerLinks) { if (link.querySelector('i.icon-first-page')) { return link; } } return null; } async function processCurrentPage() { const { currentPage, totalPages } = getPagerInfo(); console.log(LOG_PREFIX, `--- 开始处理第 ${currentPage} / ${totalPages} 页 ---`); updateStatus(`正在处理第 ${currentPage} / ${totalPages} 页...`); let { byDate, bySuffix } = JSON.parse(sessionStorage.getItem(SESSION_STORAGE_KEY_DATA) || '{}'); parseTableData(currentPage, byDate, bySuffix); sessionStorage.setItem(SESSION_STORAGE_KEY_DATA, JSON.stringify({ byDate, bySuffix })); console.log(LOG_PREFIX, `第 ${currentPage} 页数据已解析并存入SessionStorage。`); const nextPageButton = findNextPageButton(); if (currentPage < totalPages && nextPageButton) { console.log(LOG_PREFIX, `准备从第 ${currentPage} 页翻到第 ${currentPage + 1} 页...`); nextPageButton.click(); } else { console.log(LOG_PREFIX, '已到达最后一页或未找到下一页按钮,准备生成最终报告。'); displayFinalResults(); sessionStorage.removeItem(SESSION_STORAGE_KEY_DATA); sessionStorage.removeItem(SESSION_STORAGE_KEY_FLAG); console.log(LOG_PREFIX, '任务完成,已清理SessionStorage。'); isProcessing = false; const statsButton = document.getElementById('start-stats-button'); if(statsButton) { statsButton.innerHTML = '📊 统计代码量'; statsButton.disabled = false; } } } function parseTableData(currentPage, statsByDate, statsBySuffix) { const commitTable = document.querySelector(TABLE_SELECTOR); if (!commitTable) { console.error(LOG_PREFIX, `在第 ${currentPage} 页未找到数据表格!`); return; } const rows = commitTable.querySelectorAll('tbody > tr'); rows.forEach((row) => { const cells = row.querySelectorAll('td'); if (cells.length < IS_VALID_COLUMN_INDEX + 1 || cells[IS_VALID_COLUMN_INDEX].textContent.trim() !== '有效') return; const dateStr = cells[DATE_COLUMN_INDEX].textContent.trim().split(' ')[0]; const suffix = cells[SUFFIX_COLUMN_INDEX].textContent.trim().toLowerCase() || 'unknown'; const addedLines = parseInt(cells[ADDED_COLUMN_INDEX].textContent.trim(), 10) || 0; const deletedLines = parseInt(cells[DELETED_COLUMN_INDEX].textContent.trim(), 10) || 0; if (!dateStr) return; let addedRate = CONVERSION_RATES[suffix] || CONVERSION_RATES['other']; if (['shtml', 'html', 'htm', 'jsp'].includes(suffix)) addedRate = 0.1; const finalLineValue = (addedLines * addedRate) + (deletedLines * 0.1); if (!statsByDate[dateStr]) statsByDate[dateStr] = { rawAdded: 0, rawDeleted: 0, commitCount: 0, finalLines: 0 }; statsByDate[dateStr].rawAdded += addedLines; statsByDate[dateStr].rawDeleted += deletedLines; statsByDate[dateStr].commitCount++; statsByDate[dateStr].finalLines += finalLineValue; if (!statsBySuffix[suffix]) statsBySuffix[suffix] = { rawAdded: 0, rawDeleted: 0, finalLines: 0 }; statsBySuffix[suffix].rawAdded += addedLines; statsBySuffix[suffix].rawDeleted += deletedLines; statsBySuffix[suffix].finalLines += finalLineValue; }); } function displayFinalResults() { const { byDate: finalStatsByDate, bySuffix: finalStatsBySuffix } = JSON.parse(sessionStorage.getItem(SESSION_STORAGE_KEY_DATA) || '{}'); console.log(LOG_PREFIX, '📊 所有页面数据汇总完毕,最终数据对象为:', { finalStatsByDate, finalStatsBySuffix }); const modalBody = document.getElementById('stats-modal-body'); if (!modalBody) return; const dates = Object.keys(finalStatsByDate).sort((a, b) => new Date(b) - new Date(a)); if (dates.length === 0) { modalBody.innerHTML = '<p>在所有页面中未找到有效的提交记录。</p>'; return; } let totalRawAdded = 0, totalRawDeleted = 0, totalCommits = 0, totalFinalLines = 0; let byDateTableHTML = `<p>已汇总所有页面 (已按规则折算)</p><table class="table table-bordered table-striped" style="margin-top: 10px; width: 100%;"><thead><tr><th>日期</th><th>提交次数</th><th>原始新增</th><th>原始删除</th><th style="background-color: #e8f5e9;">最终代码行</th></tr></thead><tbody>`; dates.forEach(date => { const data = finalStatsByDate[date]; totalRawAdded += data.rawAdded; totalRawDeleted += data.rawDeleted; totalCommits += data.commitCount; totalFinalLines += data.finalLines; byDateTableHTML += `<tr><td>${date}</td><td>${data.commitCount}</td><td style="color: green;">+${data.rawAdded}</td><td style="color: red;">-${data.rawDeleted}</td><td style="font-weight: bold; background-color: #f1f8e9;">${data.finalLines.toFixed(2)}</td></tr>`; }); byDateTableHTML += `<tr style="font-weight: bold; border-top: 2px solid #007bff;"><td>总计</td><td>${totalCommits}</td><td style="color: green;">+${totalRawAdded}</td><td style="color: red;">-${totalRawDeleted}</td><td style="background-color: #dcedc8;">${totalFinalLines.toFixed(2)}</td></tr></tbody></table>`; const suffixes = Object.keys(finalStatsBySuffix).sort(); let bySuffixTableHTML = `<h4 style="margin-top: 25px; border-bottom: 1px solid #ccc; padding-bottom: 5px;">按文件类型汇总 (明细)</h4><table class="table table-bordered table-striped" style="margin-top: 10px; width: 100%;"><thead><tr><th>文件后缀</th><th>原始新增</th><th>原始删除</th><th style="background-color: #e8f5e9;">最终代码行</th></tr></thead><tbody>`; suffixes.forEach(suffix => { const data = finalStatsBySuffix[suffix]; bySuffixTableHTML += `<tr><td>${suffix}</td><td style="color: green;">+${data.rawAdded}</td><td style="color: red;">-${data.rawDeleted}</td><td style="font-weight: bold; background-color: #f1f8e9;">${data.finalLines.toFixed(2)}</td></tr>`; }); bySuffixTableHTML += `</tbody></table>`; modalBody.innerHTML = byDateTableHTML + bySuffixTableHTML; console.log(LOG_PREFIX, '✅ 统计完成!最终结果已显示在弹窗上。'); } function findNextPageButton() { const pagerLinks = document.querySelectorAll(`${PAGER_CONTAINER_SELECTOR} a`); for (const link of pagerLinks) { if (link.querySelector('i.icon-angle-right')) return link; } return null; } function createUI() { createModal(); const queryButton = document.querySelector(QUERY_BUTTON_SELECTOR); if (!queryButton) { console.error(LOG_PREFIX, '无法找到“查询”按钮。'); return; } const container = queryButton.closest('.row'); if (!container) { console.error(LOG_PREFIX, '无法找到按钮的父容器 .row'); return; } const buttonWrapper = document.createElement('div'); buttonWrapper.className = 'col-sm-4'; const statsButton = document.createElement('button'); statsButton.id = 'start-stats-button'; statsButton.innerHTML = '📊 统计代码量'; statsButton.title = '在新弹窗中统计所有页面的代码量'; statsButton.type = 'button'; statsButton.className = 'btn'; statsButton.style.cssText = `background-color: #3280FC; color: white; border: none; padding: 5px 12px; line-height: 1.5; transition: all 0.2s ease-in-out; box-shadow: 0 2px 4px rgba(0,0,0,0.1); width: 120px;`; statsButton.onmouseover = () => { statsButton.style.transform = 'translateY(-1px)'; statsButton.style.boxShadow = '0 4px 8px rgba(0,0,0,0.15)'; statsButton.style.filter = 'brightness(1.05)'; }; statsButton.onmouseout = () => { statsButton.style.transform = 'translateY(0)'; statsButton.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)'; statsButton.style.filter = 'brightness(1)'; }; statsButton.onclick = startFullStats; buttonWrapper.appendChild(statsButton); container.appendChild(buttonWrapper); console.log(LOG_PREFIX, 'UI创建成功!'); } function createModal() { const modalContainer = document.createElement('div'); modalContainer.id = 'stats-modal-container'; modalContainer.style.cssText = `display: none; position: fixed; z-index: 9999; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.6); align-items: center; justify-content: center;`; modalContainer.innerHTML = `<div id="stats-modal-content" style="background-color: #fefefe; margin: auto; padding: 20px; border: 1px solid #888; border-radius: 8px; width: 80%; max-width: 700px; box-shadow: 0 5px 15px rgba(0,0,0,0.3); animation: fadeIn 0.3s;"><div id="stats-modal-header" style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #eee; padding-bottom: 10px; margin-bottom: 15px;"><h2 style="margin: 0; font-size: 1.5em;">代码量统计结果</h2><span id="stats-modal-close" style="color: #aaa; font-size: 28px; font-weight: bold; cursor: pointer;">×</span></div><div id="stats-modal-body" style="max-height: 60vh; overflow-y: auto;"></div></div>`; document.body.appendChild(modalContainer); const styleSheet = document.createElement("style"); styleSheet.type = "text/css"; styleSheet.innerText = `@keyframes fadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }`; document.head.appendChild(styleSheet); document.getElementById('stats-modal-close').onclick = () => { modalContainer.style.display = 'none'; }; modalContainer.onclick = (event) => { if (event.target == modalContainer) { modalContainer.style.display = 'none'; } }; } function getPagerInfo() { const pagerEl = document.querySelector(PAGER_CONTAINER_SELECTOR); if (!pagerEl) return { currentPage: 1, totalPages: 1 }; const recTotal = parseInt(pagerEl.dataset.recTotal || '0', 10), recPerPage = parseInt(pagerEl.dataset.recPerPage || '100', 10), currentPage = parseInt(pagerEl.dataset.page || '1', 10); const totalPages = Math.ceil(recTotal / recPerPage); return { currentPage, totalPages: totalPages > 0 ? totalPages : 1 }; } function updateStatus(message) { const modalBody = document.getElementById('stats-modal-body'); if (modalBody) { modalBody.innerHTML = `<h3 style="color: #007bff; text-align: center;">${message}</h3>`; } } // --- 脚本主入口 --- (function() { const onReady = () => { createUI(); if (sessionStorage.getItem(SESSION_STORAGE_KEY_FLAG) === 'true') { console.log(LOG_PREFIX, '检测到正在进行的统计任务,自动继续...'); const modal = document.getElementById('stats-modal-container'); if (modal) modal.style.display = 'flex'; isProcessing = true; const statsButton = document.getElementById('start-stats-button'); if(statsButton) { statsButton.textContent = '统计中...'; statsButton.disabled = true; } processCurrentPage(); } }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', onReady); } else { onReady(); } })(); })();