研发云代码量统计 (智能导航最终版)

【智能导航最终版】无论在哪一页点击,都会自动返回第一页开始统计,并集成所有计算规则。

// ==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;">&times;</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(); }
    })();
})();