zhihu_salt_analysis

auto get salt data based CreatorRangePicker-PopoverContent

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         zhihu_salt_analysis
// @namespace    http://tampermonkey.net/
// @version      2025-09-28_6
// @description  auto get salt data based CreatorRangePicker-PopoverContent
// @author       Archimon@zhihu
// @match        https://www.zhihu.com/creator/knowledge-income
// @icon         https://picx.zhimg.com/v2-1de07498cdef102d69ed02e275c51ba9_xll.jpg?source=32738c0c&needBackground=1
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js
// @grant        none
// @license MIT
// ==/UserScript==

function formatDate() {
    const date = new Date();
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const yesterday = String(date.getDate()-1); // 去掉补零
    return `${yesterday}`;
}

// 等待元素加载的辅助函数
function waitForElement(selector, timeout = 5000) {
    return new Promise((resolve, reject) => {
        const element = document.querySelector(selector);
        if (element) {
            resolve(element);
            return;
        }

        const observer = new MutationObserver(() => {
            const element = document.querySelector(selector);
            if (element) {
                observer.disconnect();
                resolve(element);
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        setTimeout(() => {
            observer.disconnect();
            reject(new Error(`Element ${selector} not found within ${timeout}ms`));
        }, timeout);
    });
}

// 等待指定时间的辅助函数
function wait(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

// 检测元素消失的辅助函数
function waitForElementDisappear(selector, timeout = 10000) {
    return new Promise((resolve, reject) => {
        const element = document.querySelector(selector);
        if (!element) {
            resolve();
            return;
        }

        const observer = new MutationObserver(() => {
            const currentElement = document.querySelector(selector);
            if (!currentElement) {
                observer.disconnect();
                resolve();
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        setTimeout(() => {
            observer.disconnect();
            reject(new Error(`Element ${selector} still exists after ${timeout}ms`));
        }, timeout);
    });
}

// 监控日期选择器变化的函数(支持多次选择)
function monitorDatePickerChanges(callback) {
    console.log('开始监控日期选择器变化...');

    const datePickerSelector = 'div[class*="CreatorRangePicker-PopoverContent"]';
    let isMonitoring = true;

    const observer = new MutationObserver(() => {
        if (!isMonitoring) return;

        const datePicker = document.querySelector(datePickerSelector);

        if (datePicker) {
            console.log('日期选择器出现,开始监控消失...');

            // 监控日期选择器消失
            const disappearObserver = new MutationObserver(() => {
                const currentPicker = document.querySelector(datePickerSelector);
                if (!currentPicker) {
                    disappearObserver.disconnect();
                    console.log('日期选择器消失,执行回调...');

                    // 等待页面稳定后执行回调
                    setTimeout(async () => {
                        try {
                            await waitForPageLoad();
                            if (callback && typeof callback === 'function') {
                                await callback();
                            }
                        } catch (error) {
                            console.warn('回调执行失败:', error.message);
                        }
                    }, 500);
                }
            });

            disappearObserver.observe(document.body, {
                childList: true,
                subtree: true
            });

            // 设置超时,避免无限监控
            setTimeout(() => {
                disappearObserver.disconnect();
            }, 15000);
        }
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    // 返回停止监控的函数
    return () => {
        isMonitoring = false;
        observer.disconnect();
        console.log('停止监控日期选择器变化');
    };
}

// 检测页面是否加载完成的函数
function waitForPageLoad(timeout = 5000) {
    return new Promise((resolve, reject) => {
        // 检查页面是否已经加载完成
        if (document.readyState === 'complete') {
            resolve();
            return;
        }

        // 监听页面加载完成事件
        window.addEventListener('load', () => {
            resolve();
        });

        // 设置超时
        setTimeout(() => {
            reject(new Error('Page load timeout'));
        }, timeout);
    });
}

async function set_date(date, callback = null){
    let next_button = null;
    const buttons = document.querySelectorAll(`
            input[type="button"],
            button[type="button"]
        `);

    for (const button of buttons) {
        if (button.className.includes("CreatorRangePicker-Button")){
            button.click();
            await wait(100);

            // 等待日期选择器弹出
            const CreatorRangePicker = await waitForElement('div[class*="CreatorRangePicker-PopoverContent"]');

            // 检查今天是否是1号
            const today = new Date();
            const isFirstDayOfMonth = today.getDate() === 1;
            
            let days_all;
            let targetDate = date;
            
            if (isFirstDayOfMonth) {
                // 如果是1号,使用左边的日期选择器部分,并选择上个月的最后一天
                days_all = CreatorRangePicker.childNodes[0].childNodes[1].childNodes[0].childNodes[1].childNodes;
                          
                
                // 计算上个月的最后一天
                const lastMonth = new Date(today.getFullYear(), today.getMonth(), 0);
                targetDate = String(lastMonth.getDate()); // 去掉补零
                console.log(`今天是1号,自动选择上个月最后一天:${today} \n${lastMonth} ${targetDate}`);
            } else {
                // 如果不是1号,使用右边的日期选择器部分,选择昨天
                days_all = CreatorRangePicker.childNodes[1].childNodes[1].childNodes[0].childNodes[1].childNodes;
                console.log(`今天不是1号,选择昨天: ${targetDate}`);
            }
            console.log(CreatorRangePicker.childNodes[0].childNodes[1].tbody);
            let dateFound = false;
            for (const row of days_all) {
                const days_row = row.childNodes;
                for (const day of days_row) {
                    if (day.textContent.trim()  === targetDate){
                        // 双击选择日期
                        day.click();
                        await wait(120);
                        day.click();
                        
                        dateFound = true;

                        // 日期选择后,等待选择器消失
                        console.log('日期已选择,等待日期选择器消失...');
                        try {
                            await waitForElementDisappear('div[class*="CreatorRangePicker-PopoverContent"]');
                            console.log('日期选择器已消失,等待页面加载...');

                            // 等待页面加载完成
                            await waitForPageLoad();
                            console.log('页面加载完成');

                            // 如果有回调函数,执行回调
                            if (callback && typeof callback === 'function') {
                                console.log('执行回调函数...');
                                await callback();
                            }
                        } catch (error) {
                            console.warn('日期选择器消失检测超时:', error.message);
                        }
                        
                        break; // 找到日期后跳出循环
                    }
                }
                if (dateFound) break;
            }
            
            if (!dateFound) {
                console.warn(`未找到日期: ${targetDate},请手动选择`);
            }
        } else if (button.className.includes("CreatorPagination-nextButton")){
            next_button = button;
        }
    }

    const benqishouyi = document.querySelectorAll('th[class*=CreatorTable-tableHead--hasSorter]');
    for (const th of benqishouyi) {
        await wait(240);
        th.click();
    }

    return next_button;
}

function parseRow(row) {
    const cells = row.querySelectorAll('td, th');
    return Array.from(cells)
        .map(cell => {
            const text = cell.textContent.trim();

            // 获取cell元素中的链接a href
            let linkHref = null;
            const linkElement = cell.querySelector('a');
            if (linkElement && linkElement.href) {
                linkHref = linkElement.href;
            }

            return {
                text: text === '' ? null : text,
                linkHref: linkHref,
                rowspan: cell.rowSpan || 1,
                colspan: cell.colSpan || 1
            };
        })
        .filter(cell => cell.text !== null && cell.text !== ''); // 过滤掉text为null或空字符串的单元格
}

function prepareChartData(rows) {
    const fullData = [];
    rows.forEach((row, index) => {
        // 从链接中提取回答ID
        let ans_url = null;
        if (row[0].linkHref) {
            const match = row[0].linkHref.match(/answer\/([^\/\?]+)/);
            if (match) {
                ans_url = match[1];
            }
        }

        fullData.push({
            ans_names:row[0].text,
            ans_types:row[1].text,
            ans_times:row[2].text,
            ans_local_reads:parseInt(row[3].text.replace(/,/g, '')) || 0,
            ans_local_salts:parseInt(row[4].text.replace(/,/g, '')) || 0,
            ans_all_reads:parseInt(row[5].text.replace(/,/g, '')) || 0,
            ans_all_salts:parseInt(row[6].text.replace(/,/g, '')) || 0,
            ans_url:ans_url
        });
    });
    return fullData;
    }

// 解析回答时间字符串为Date对象
function parseAnswerTime(timeStr) {
    if (!timeStr) return null;
    
    // 处理各种时间格式
    if (timeStr.includes('今天')) {
        return new Date();
    } else if (timeStr.includes('昨天')) {
        const date = new Date();
        date.setDate(date.getDate() - 1);
        return date;
    } else if (timeStr.includes('前天')) {
        const date = new Date();
        date.setDate(date.getDate() - 2);
        return date;
    } else if (timeStr.includes('分钟前') || timeStr.includes('小时前')) {
        return new Date(); // 近似处理为今天
    } else {
        // 尝试解析标准日期格式
        const date = new Date(timeStr);
        return isNaN(date.getTime()) ? null : date;
    }
}

// 按时间分类准备饼图数据
function prepareTimeCategoryPieChartData(fullData) {
    const now = new Date();
    const oneDayMs = 24 * 60 * 60 * 1000;
    const twoDayMs = 2 * oneDayMs;
    const sevenDaysMs = 7 * oneDayMs;
    const oneMonthMs = 30 * oneDayMs; // 近似处理
    
    const categories = {
        '1天以内': { salt: 0, count: 0, reads: 0 },
        '1天-7天': { salt: 0, count: 0, reads: 0 },
        '7天-1个月': { salt: 0, count: 0, reads: 0 },
        '1个月以上': { salt: 0, count: 0, reads: 0 }
    };
    
    fullData.forEach(item => {
        const answerTime = parseAnswerTime(item.ans_times);
        if (!answerTime) return;
        
        const timeDiff = now - answerTime;
        // console.log(`回答时间: ${item.ans_times}, 解析后: ${answerTime}, 时间差: ${timeDiff}ms`);
        let category = '';
        
        if (timeDiff <= twoDayMs) {
            category = '1天以内';
        } else if (timeDiff <= sevenDaysMs) {
            category = '1天-7天';
        } else if (timeDiff <= oneMonthMs) {
            category = '7天-1个月';
        } else {
            category = '1个月以上';
        }
        
        categories[category].salt += item.ans_local_salts;
        categories[category].count += 1;
        categories[category].reads += item.ans_local_reads;
    });
    
    const totalSalt = Object.values(categories).reduce((sum, cat) => sum + cat.salt, 0);
    const totalCount = Object.values(categories).reduce((sum, cat) => sum + cat.count, 0);
    
    const pieData = Object.entries(categories)
        .filter(([_, cat]) => cat.salt > 0) // 只包含有盐粒的类别
        .map(([name, cat]) => ({
            name: `${name} (${cat.count}个回答)`,
            value: cat.salt,
            percentage: totalSalt > 0 ? ((cat.salt / totalSalt) * 100).toFixed(2) : '0.00',
            salt_read_ratio: cat.reads > 0 ? (cat.salt / cat.reads).toFixed(2) : '0.00',
            answer_count: cat.count,
            read_count: cat.reads
        }));
    
    // 添加总统计数据
    pieData.total_salt = totalSalt;
    pieData.total_count = totalCount;
    pieData.total_read = Object.values(categories).reduce((sum, cat) => sum + cat.reads, 0);
    pieData.total_salt_read_ratio = pieData.total_read > 0 ? (totalSalt / pieData.total_read).toFixed(2) : '0.00';
    
    return pieData;
}

// 创建饼图数据
function preparePieChartData(fullData) {
    const salt_all_yesterday = fullData.reduce((sum, item) => sum + item.ans_local_salts, 0);
    const read_all_yesterday = fullData.reduce((sum, item) => sum + item.ans_local_reads, 0);

    // 筛选盐粒占比超过1%的元素
    const significantData = fullData.filter(item => {
        const percentage = (item.ans_local_salts / salt_all_yesterday) * 100;
        return percentage >= 1;
    });

    // 取盐粒占比超过1%的元素,或前7个,取最大值
    const maxCount = Math.max(significantData.length, 7);
    const topData = fullData.slice(0, maxCount);
    const otherData = fullData.slice(maxCount);

    // 计算其他数据的盐粒和阅读数
    const otherSalt = otherData.reduce((sum, item) => sum + item.ans_local_salts, 0);
    const otherRead = otherData.reduce((sum, item) => sum + item.ans_local_reads, 0);
    const otherCount = otherData.length;

    const pieData = topData.map((item, index) => ({
        name: item.ans_names.length > 15 ? item.ans_names.substring(0, 15) + '...' : item.ans_names,
        value: item.ans_local_salts,
        percentage: ((item.ans_local_salts / salt_all_yesterday) * 100).toFixed(2),
        salt_read_ratio: (item.ans_local_salts / item.ans_local_reads).toFixed(2),
        read_count: item.ans_local_reads
    }));

    // 如果有其他数据,添加"其他"项
    if (otherSalt > 0) {
        pieData.push({
            name: `其他 (${otherCount}个回答)`,
            value: otherSalt,
            percentage: ((otherSalt / salt_all_yesterday) * 100).toFixed(2),
            salt_read_ratio: (otherSalt / otherRead).toFixed(2),
            answer_count: otherCount,
            read_count: otherRead
        });
    }

    // 添加总阅读量信息
    pieData.total_salt = salt_all_yesterday;
    pieData.total_read = read_all_yesterday;
    pieData.total_salt_read_ratio = (salt_all_yesterday / read_all_yesterday).toFixed(2);

    return pieData;
}

// 获取日期选择器的时间信息
function getDateRangeText() {
    // 查找日期选择器按钮,使用类名选择器,忽略动态生成的类名部分
    const datePickerButton = document.querySelector('button[class*="CreatorRangePicker-Button"]');
    if (datePickerButton) {
        // 获取按钮的文本内容,去除SVG图标等非文本内容
        const textContent = datePickerButton.textContent.trim();
        // 提取日期范围信息
        const dateMatch = textContent.match(/(\d{4}\/\d{2}\/\d{2})[^]*?(\d{4}\/\d{2}\/\d{2})/);
        if (dateMatch) {
            return `${dateMatch[1]} - ${dateMatch[2]}`;
        }
    }
    return '未知日期';
}

// 创建时间分类饼图
function createTimeCategoryPieChart(pieData, containerId = 'time-category-pie-chart') {
    // 创建容器
    const chartContainer = document.createElement('div');
    chartContainer.id = containerId;
    chartContainer.style.cssText = `
        width: 1016px;
        height: 400px;
        margin: 20px auto;
        border: 1px solid #e8e8e8;
        border-radius: 8px;
        padding: 20px;
        background: #fff;
        box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    `;

    // 初始化图表
    const chart = echarts.init(chartContainer);

    // 获取日期范围
    const dateRange = getDateRangeText();

    // 使用从prepareTimeCategoryPieChartData传递过来的统计数据
    const totalSalt = pieData.total_salt || pieData.reduce((sum, item) => sum + item.value, 0);
    const totalCount = pieData.total_count || pieData.reduce((sum, item) => sum + item.answer_count, 0);
    const totalRead = pieData.total_read || pieData.reduce((sum, item) => sum + (item.read_count || 0), 0);
    const totalSaltReadRatio = pieData.total_salt_read_ratio || (totalRead > 0 ? (totalSalt / totalRead).toFixed(2) : '0.00');

    // 配置项
    const option = {
        title: {
            text: `盐粒时间分布图\n${dateRange}\n共${totalCount}个回答 | 总盐粒: ${totalSalt} | 总盐粒阅读比: ${totalSaltReadRatio}`,
            left: 'center',
            textStyle: {
                fontSize: 16,
                fontWeight: 'bold'
            }
        },
        tooltip: {
            trigger: 'item',
            formatter: function(params) {
                return `${params.name}<br/>盐粒: ${params.value}<br/>占比: ${params.data.percentage}%<br/>盐粒阅读比: ${params.data.salt_read_ratio}<br/>回答数: ${params.data.answer_count}`;
            }
        },
        legend: {
            orient: 'vertical',
            right: 10,
            top: 'center',
            type: 'scroll',
            pageTextStyle: {
                color: '#666'
            }
        },
        series: [
            {
                name: '时间分布',
                type: 'pie',
                radius: ['40%', '70%'],
                center: ['40%', '50%'],
                avoidLabelOverlap: false,
                itemStyle: {
                    borderColor: '#fff',
                    borderWidth: 2
                },
                label: {
                    show: false,
                    position: 'center'
                },
                emphasis: {
                    label: {
                        show: true,
                        fontSize: 18,
                        fontWeight: 'bold',
                        formatter: '{b}\n{c}盐粒\n({d}%)'
                    }
                },
                labelLine: {
                    show: false
                },
                data: pieData.map(item => ({
                    name: item.name,
                    value: item.value,
                    percentage: item.percentage,
                    salt_read_ratio: item.salt_read_ratio,
                    answer_count: item.answer_count
                }))
            }
        ],
        color: [
            '#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4',
            '#feca57', '#ff9ff3', '#54a0ff', '#5f27cd'
        ]
    };

    // 设置配置项并渲染
    chart.setOption(option);

    // 响应窗口大小变化
    window.addEventListener('resize', function() {
        chart.resize();
    });

    return chartContainer;
}

// 创建ECharts饼图
function createPieChart(pieData, fullDataLength, containerId = 'salt-pie-chart') {
    // 创建容器
    const chartContainer = document.createElement('div');
    chartContainer.id = containerId;
    chartContainer.style.cssText = `
        width: 1016px;
        height: 400px;
        margin: 20px auto;
        border: 1px solid #e8e8e8;
        border-radius: 8px;
        padding: 20px;
        background: #fff;
        box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    `;

    // 初始化图表
    const chart = echarts.init(chartContainer);

    // 获取日期范围
    const dateRange = getDateRangeText();

    // 使用从preparePieChartData传递过来的总盐粒量和总盐粒阅读比
    const totalSalt = pieData.total_salt || pieData.reduce((sum, item) => sum + item.value, 0);
    const totalRead = pieData.total_read || pieData.reduce((sum, item) => sum + (item.read_count || 0), 0);
    const totalSaltReadRatio = pieData.total_salt_read_ratio || (totalRead > 0 ? (totalSalt / totalRead).toFixed(2) : '0.00');

    // 配置项
    const option = {
        title: {
            text: `盐粒来源分布图\n${dateRange}\n共${fullDataLength}个回答 | 总盐粒: ${totalSalt} | 总盐粒阅读比: ${totalSaltReadRatio}`,
            left: 'center',
            textStyle: {
                fontSize: 16,
                fontWeight: 'bold'
            }
        },
        tooltip: {
            trigger: 'item',
            formatter: function(params) {
                return `${params.name}<br/>盐粒: ${params.value}<br/>占比: ${params.data.percentage}%<br/>盐粒阅读比: ${params.data.salt_read_ratio}`;
            }
        },
        legend: {
            orient: 'vertical',
            right: 10,
            top: 'center',
            type: 'scroll',
            pageTextStyle: {
                color: '#666'
            }
        },
        series: [
            {
                name: '盐粒分布',
                type: 'pie',
                radius: ['40%', '70%'],
                center: ['40%', '50%'],
                avoidLabelOverlap: false,
                itemStyle: {
                    borderColor: '#fff',
                    borderWidth: 2
                },
                label: {
                    show: false,
                    position: 'center'
                },
                emphasis: {
                    label: {
                        show: true,
                        fontSize: 18,
                        fontWeight: 'bold',
                        formatter: '{b}\n{c}盐粒\n({d}%)'
                    }
                },
                labelLine: {
                    show: false
                },
                data: pieData.map(item => ({
                    name: item.name,
                    value: item.value,
                    percentage: item.percentage,
                    salt_read_ratio: item.salt_read_ratio
                }))
            }
        ],
        color: [
            '#5470c6', '#91cc75', '#fac858', '#ee6666',
            '#73c0de', '#3ba272', '#fc8452', '#9a60b4',
            '#ea7ccc'
        ]
    };

    // 设置配置项并渲染
    chart.setOption(option);

    // 响应窗口大小变化
    window.addEventListener('resize', function() {
        chart.resize();
    });

    return chartContainer;
}

function createDataTable(fullData) {
    let tableHtml = `<div style="position:relative;left:20px;top:30px">
    <table class="CreatorTable-table ToolsRecommendList-Table" cellspacing="0" cellpadding="0">
        <thead>
            <tr class="CreatorTable-tableRow">
                <th class="CreatorTable-tableHead css-0" width="140" data-tooltip-classname="CreatorTable-Tooltip" data-tooltip-position="bottom" style="text-align: center;">内容</th>
                <th class="CreatorTable-tableHead css-0" width="140" data-tooltip-classname="CreatorTable-Tooltip" data-tooltip-position="bottom" style="text-align: center;">本期盐粒</th>
                <th class="CreatorTable-tableHead css-0" width="140" data-tooltip-classname="CreatorTable-Tooltip" data-tooltip-position="bottom" style="text-align: center;">收益占比</th>
                <th class="CreatorTable-tableHead css-0" width="140" data-tooltip-classname="CreatorTable-Tooltip" data-tooltip-position="bottom" style="text-align: center;">盐粒阅读比</th>
            </tr>
        </thead>
    <tbody>
        `;
    const salt_all_range = fullData.reduce((sum, item) => sum + item.ans_local_salts, 0);
    const read_all_yesterday = fullData.reduce((sum, item) => sum + item.ans_local_reads, 0);
    tableHtml += `<tr>
                    <td class="CreatorTable-tableData css-0" style="text-align: center;">${fullData.length}个回答</td>
                    <td class="CreatorTable-tableData css-0" style="text-align: center;">${salt_all_range}</td>
                    <td class="CreatorTable-tableData css-0" style="text-align: center;">100%</td>
                    <td class="CreatorTable-tableData css-0" style="text-align: center;">${(salt_all_range/read_all_yesterday).toFixed(2)}</td>
                </tr>
            `;
    fullData.forEach((item,index) => {
        const percentage = ((item.ans_local_salts / salt_all_range) * 100).toFixed(2);
        tableHtml += `<tr>
                    <td class="CreatorTable-tableData css-0" style="text-align: center;"><div class="css-13zgqlo"><a href="https://www.zhihu.com/answer/${item.ans_url}" target="_blank" rel="noopener noreferrer" style="font-size: 14px; color: rgb(25, 27, 31); font-weight: 500; display: -webkit-box; overflow: hidden; text-overflow: ellipsis; -webkit-line-clamp: 3; -webkit-box-orient: vertical; white-space: pre-wrap;">${item.ans_names}</a></div></td>
                    <td class="CreatorTable-tableData css-0" style="text-align: center;">${item.ans_local_salts}</td>
                    <td class="CreatorTable-tableData css-0" style="text-align: center;">${percentage}%</td>
                    <td class="CreatorTable-tableData css-0" style="text-align: center;">${(item.ans_local_salts/item.ans_local_reads).toFixed(2)}</td>
                </tr>
            `;
        });

        tableHtml += `
                </tbody>
            </table></div>
        `;

        return tableHtml;
    }

async function get_data_draw(next_page_button){
    const table_data = {
        headers: [],
        rows: [],
        };

    const page_num = document.querySelector('div[class*="CreatorPagination-pageNumber"]');
    const end_page = parseInt(page_num.textContent.split('/')[1].trim());

    for (let i = 1; i <= end_page; i++) {
        // 等待页面稳定
        await wait(500);

        let table = document.querySelector('table[class*=ToolsRecommendList-Table]');
        if (!table) {
            console.warn(`第 ${i} 页表格未找到,等待重试...`);
            await wait(1000);
            table = document.querySelector('table[class*=ToolsRecommendList-Table]');
        }

        if (table) {
            let thead = table.childNodes[0];
            table_data.headers = parseRow(thead);
            let data_rows = table.childNodes[1].querySelectorAll('tr');
            let parsed_rows = Array.from(data_rows).map(row => parseRow(row));
            table_data.rows = Array.from(table_data.rows).concat(parsed_rows).filter(row => row.length !== 0);
            // console.log(table_data.rows);
        }

        if (i < end_page && next_page_button) {
            next_page_button.click();
            // 等待页面加载完成
            await waitForElement('table[class*=ToolsRecommendList-Table]');
        }
    }

    await wait(500);

    const all_data = prepareChartData(Array.from(table_data.rows));
    const salt_all_range = all_data.reduce((sum, item) => sum + item.ans_local_salts, 0);

    // 输出控制台信息
    all_data.sort((a,b) => b.ans_local_salts- a.ans_local_salts).forEach(item => {
        const percentage = ((item.ans_local_salts / salt_all_range) * 100).toFixed(2);
        console.log(`${String(item.ans_local_salts).padStart(8,' ')} ${String(percentage).padStart(6,' ')}% ${item.ans_names} `);
    });

    // 创建可视化界面
    await createVisualization(all_data);
}

// 创建可视化界面(饼图 + 表格)
async function createVisualization(all_data) {
    // 准备饼图数据
    const pieData = preparePieChartData(all_data);
    const timeCategoryData = prepareTimeCategoryPieChartData(all_data);

    // 创建主容器
    const container = document.createElement('div');
    container.style.cssText = `
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background: white;
        padding: 20px;
        border-radius: 8px;
        box-shadow: 0 4px 20px rgba(0,0,0,0.15);
        z-index: 10000;
        max-height: 90vh;
        overflow-y: auto;
        min-width: 800px;
    `;

    // 创建关闭按钮
    const closeButton = document.createElement('button');
    closeButton.textContent = '×';
    closeButton.style.cssText = `
        position: absolute;
        top: 10px;
        right: 10px;
        background: none;
        border: none;
        font-size: 24px;
        cursor: pointer;
        color: #666;
        width: 30px;
        height: 30px;
        border-radius: 50%;
        display: flex;
        align-items: center;
        justify-content: center;
    `;
    closeButton.onclick = () => container.remove();

    // 创建标题
    const title = document.createElement('h2');
    title.textContent = '知乎盐粒收益分析';
    title.style.cssText = `
        text-align: center;
        margin-bottom: 20px;
        color: #1890ff;
        font-size: 18px;
    `;

    // 创建盐粒来源饼图
    const pieChart = createPieChart(pieData, all_data.length);
    // 创建时间分类饼图
    const timeCategoryPieChart = createTimeCategoryPieChart(timeCategoryData);

    // 创建表格
    const tableHtml = createDataTable(all_data);
    const tableContainer = document.createElement('div');
    tableContainer.innerHTML = tableHtml;

    // 组装所有元素
    container.appendChild(closeButton);
    container.appendChild(title);
    container.appendChild(pieChart);
    container.appendChild(timeCategoryPieChart);
    container.appendChild(tableContainer);

    // 添加到页面
    document.body.appendChild(container);

    // 添加ESC键关闭功能
    const handleKeydown = (e) => {
        if (e.key === 'Escape') {
            container.remove();
            document.removeEventListener('keydown', handleKeydown);
        }
    };
    document.addEventListener('keydown', handleKeydown);

    // 点击背景关闭
    const overlay = document.createElement('div');
    overlay.style.cssText = `
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0,0,0,0.5);
        z-index: 9999;
    `;
    overlay.onclick = () => {
        container.remove();
        overlay.remove();
        document.removeEventListener('keydown', handleKeydown);
    };
    document.body.appendChild(overlay);
}

(async function() {
    'use strict';

    console.log(`🚀 [email protected] say hello!`);

    // 等待页面完全加载
    if (document.readyState === 'loading') {
        await new Promise(resolve => {
            document.addEventListener('DOMContentLoaded', resolve);
        });
    }

    // 添加样式
    // 获取昨天日期
    const yesterday_str = formatDate();

    // 点击进入『内容收益明细』
    const clickableDivs = document.querySelectorAll('div[class*="clickable"]');
    for (const div of clickableDivs) {
        const textContent = div.textContent.trim();
        if (textContent.includes('内容收益明细')) {
            div.click();
            break; // 只点击第一个匹配的元素
        }
    }

    // 等待页面跳转完成
    await wait(500);

    // 定义回调函数,在日期选择完成后执行
    const dataDrawCallback = async () => {
        console.log('回调函数执行:开始获取数据并绘制图表...');

        // 重新获取下一页按钮(因为页面可能已刷新)
        const next_button = document.querySelector('button[class*="CreatorPagination-nextButton"]');

        if (next_button) {
            await wait(720);
            await get_data_draw(next_button);
        } else {
            console.error('未找到下一页按钮,尝试直接获取数据...');
            await get_data_draw(null);
        }
    };

    // 初始设置日期
    await set_date(yesterday_str, dataDrawCallback);

    // 启动日期选择器变化监控(支持第二次及后续的时间选择)
    console.log('启动日期选择器变化监控...');
    const stopMonitoring = monitorDatePickerChanges(dataDrawCallback);

    // 页面卸载时停止监控
    window.addEventListener('beforeunload', () => {
        stopMonitoring();
    });

    // 添加停止监控的全局函数(用于调试)
    window.stopDatePickerMonitoring = stopMonitoring;
})();