Everyday Profit

记录每日净资产并计算增长 + 单位识别 + 历史图表弹窗,需要安装MWITools,数据存储在本地,保留30天记录

// ==UserScript==
// @name         Everyday Profit
// @namespace    http://tampermonkey.net/
// @version      2025.07.31
// @description  记录每日净资产并计算增长 + 单位识别 + 历史图表弹窗,需要安装MWITools,数据存储在本地,保留30天记录
// @author       VictoryWinWinWin
// @match        https://www.milkywayidle.com/*
// @grant        GM_addStyle
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    // 添加样式
    GM_addStyle(`
        #deltaNetworthChartModal {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 800px;
            max-width: 90vw;
            background: #1e1e1e;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0,0,0,0.6);
            z-index: 9999;
            display: none;
            flex-direction: column;
        }
        #deltaNetworthChartModal.dragging {
            cursor: grabbing;
        }
        #deltaNetworthChartHeader {
            padding: 10px 15px;
            background: #333;
            color: white;
            font-weight: bold;
            display: flex;
            justify-content: space-between;
            align-items: center;
            cursor: move;
            user-select: none;
            border-top-left-radius: 8px;
            border-top-right-radius: 8px;
        }
        #netWorthChartBody {
            padding: 15px;
        }
        #netWorthChart {
            width: 100%;
            height: 300px;
        }
        #showHistoryBtn {
            display: inline-block;
            padding: 6px 12px;
            margin: 10px 0;
            font-size: 16px;
            background: #444;
            color: white;
            border: none;
            cursor: pointer;
            border-radius: 4px;
        }
        #showHistoryBtn:hover {
            background: #666;
        }
    `);

    // 工具函数:将带单位的字符串(如 1.5M, 3.2K)转为数字
    function parseFormattedNumber(str) {
        const cleanStr = str.replace(/[^\d.,-]/g, '').replace(',', '.');
        const num = parseFloat(cleanStr);
        if (isNaN(num)) return 0;

        if (str.includes('B') || str.includes('b')) return num * 1e9;
        if (str.includes('M') || str.includes('m')) return num * 1e6;
        if (str.includes('K') || str.includes('k')) return num * 1e3;
        return num;
    }

    // 工具函数:将大数字格式化为带单位的字符串(如 2.5M)
    function formatLargeNumber(num) {
        const abs = Math.abs(num);
        let formatted;
        if (abs >= 1e9) {
            formatted = (num / 1e9).toFixed(2) + 'B';
        } else if (abs >= 1e6) {
            formatted = (num / 1e6).toFixed(2) + 'M';
        } else if (abs >= 1e3) {
            formatted = (num / 1e3).toFixed(2) + 'K';
        } else {
            formatted = num.toString();
        }
        return formatted;
    }

    window.kbd_calculateTotalNetworth = function kbd_calculateTotalNetworth(totalNetworth, dom) {
        class DailyDataStore {
            constructor(storageKey = 'kbd_calc_data', maxDays = 30, currentRole = 'default') {
                this.storageKey = storageKey;
                this.maxDays = maxDays;
                this.currentRole = currentRole; // 当前操作的角色
                this.data = this.loadFromStorage();
            }

            // ✅ 设置当前角色
            setRole(roleId) {
                this.currentRole = roleId;
            }

            // ✅ 获取当前角色的数据对象
            getRoleData() {
                if (!this.data[this.currentRole]) {
                    this.data[this.currentRole] = {};
                }
                return this.data[this.currentRole];
            }

            getTodayKey() {
                const now = new Date();
                const utcPlus8 = new Date(now.getTime() + 8 * 3600000);
                return utcPlus8.toISOString().split('T')[0];
            }

            getYesterdayKey() {
                const now = new Date();
                const yesterday = new Date(now.getTime() - 24 * 3600000);
                const utcPlus8 = new Date(yesterday.getTime() + 8 * 3600000);
                return utcPlus8.toISOString().split('T')[0];
            }

            loadFromStorage() {
                const raw = localStorage.getItem(this.storageKey);
                try {
                    return raw ? JSON.parse(raw) : {};
                } catch {
                    return {};
                }
            }

            saveToStorage() {
                localStorage.setItem(this.storageKey, JSON.stringify(this.data));
                console.log(`${this.storageKey} 数据已成功保存到本地存储。`);
            }

            setTodayValue(value) {
                const roleData = this.getRoleData();
                const today = this.getTodayKey();
                roleData[today] = value;
                this.cleanupOldData(); // 清理当前角色的旧数据
                this.saveToStorage();
            }

            cleanupOldData() {
                const roleData = this.getRoleData();
                const keys = Object.keys(roleData).sort();
                const today = this.getTodayKey();
                const indexToday = keys.indexOf(today);

                if (indexToday !== -1) {
                    const startIdx = Math.max(0, indexToday - this.maxDays + 1);
                    const newKeys = keys.slice(startIdx, indexToday + 1);
                    const newData = {};
                    newKeys.forEach(key => {
                        newData[key] = roleData[key];
                    });
                    this.data[this.currentRole] = newData;
                }
            }

            getTodayDelta() {
                const roleData = this.getRoleData();
                const todayKey = this.getTodayKey();
                const yesterdayKey = this.getYesterdayKey();

                const todayValue = roleData[todayKey] || 0;
                const yesterdayValue = roleData[yesterdayKey] || 0;

                return todayValue - yesterdayValue;
            }

            getHistoryData() {
                const roleData = this.getRoleData();
                const sorted = Object.entries(roleData).sort(([a], [b]) => new Date(a) - new Date(b));
                const labels = sorted.map(([date]) => date);
                const values = sorted.map(([, value]) => value);
                return { labels, values };
            }

            // ✅ 获取所有角色列表
            getAllRoles() {
                return Object.keys(this.data);
            }

            // ✅ 删除某个角色的数据
            removeRole(roleId) {
                delete this.data[roleId];
                this.saveToStorage();
            }
        }

        const injectDeltaScript = (isFirst = true) => {
            const store = new DailyDataStore();
            const divElement = document.querySelector('.CharacterName_name__1amXp');
            const username = divElement.querySelector('span').textContent;
            console.log(username);
            store.setRole(username)

            function filterHistoryData(days) {
                const sorted = Object.entries(store.data).sort(([a], [b]) => new Date(a) - new Date(b));
                const now = new Date();
                const cutoff = new Date();
                cutoff.setDate(now.getDate() - days);

                const filtered = sorted.filter(([date]) => new Date(date) >= cutoff);
                const labels = filtered.map(([date]) => date);
                const values = filtered.map(([, value]) => value);
                return { labels, values };
            }

            store.setTodayValue(totalNetworth);

            const delta = store.getTodayDelta();
            const formattedDelta = formatLargeNumber(delta);
            const color = delta > 0 ? 'green' : (delta < 0 ? 'red' : 'gray');

            if (isFirst) {
                dom.insertAdjacentHTML(
                    'afterend',
                    `
                    <div id="deltaNetworthDiv" style="text-align:left;color:#fff;font-size:20px;margin:10px 0;">
                        <span style="font-weight:bold;">💰今日盈亏: </span>
                        <span style="color:${color};font-weight:bold;">${formattedDelta}</span>
                        <span id="showHistoryIcon" style="cursor:pointer; margin-left:8px; font-size:18px;">📊</span>
                    </div>
                `

                );

                // 创建弹窗
                const modal = document.createElement('div');
                modal.id = 'deltaNetworthChartModal';

                modal.innerHTML = `
                    <div id="deltaNetworthChartHeader">
                        <span>净资产历史曲线</span>
                        <span id="deltaNetworthChartCloseBtn" style="cursor:pointer;">❌</span>
                    </div>
                    <div id="deltaNetworthChartControls" style="padding: 10px; text-align:center;">
                        <button id="btn7Days" style="margin: 5px; padding: 6px 12px; background: #444; color: white; border: none; border-radius: 4px;">7天</button>
                        <button id="btn30Days" style="margin: 5px; padding: 6px 12px; background: #444; color: white; border: none; border-radius: 4px;">30天</button>
                    </div>
                    <div id="netWorthChartBody">
                        <canvas id="netWorthChart"></canvas>
                    </div>
                `;

                document.body.appendChild(modal);

                // const showBtn = document.getElementById('showHistoryBtn');
                const modalDiv = document.getElementById('deltaNetworthChartModal');
                const closeBtn = document.getElementById('deltaNetworthChartCloseBtn');

                let chartLoaded = false;

                function showModal() {
                    modalDiv.style.display = 'flex';
                    if (!chartLoaded) {
                        const { labels, values } = store.getHistoryData();
                        const script = document.createElement('script');
                        script.src = 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js';
                        script.onload = () => {
                            new Chart(document.getElementById('netWorthChart'), {
                                type: 'line',
                                data: {
                                    labels,
                                    datasets: [{
                                        label: '净资产历史',
                                        data: values,
                                        borderColor: 'rgba(75, 192, 192, 1)',
                                        tension: 0.3,
                                        fill: false
                                    }]
                                },
                                options: {
                                    responsive: true,
                                    plugins: {
                                        legend: { display: true },
                                        tooltip: {
                                            callbacks: {
                                                label: (context) => formatLargeNumber(context.raw)
                                            }
                                        }
                                    },
                                    scales: {
                                        y: {
                                            ticks: {
                                                callback: (value) => formatLargeNumber(value)
                                            }
                                        }
                                    }
                                }
                            });
                            chartLoaded = true;
                            document.getElementById('btn7Days').onclick = () => {
                                const { labels, values } = filterHistoryData(7);
                                chart.data.labels = labels;
                                chart.data.datasets[0].data = values;
                                chart.update();
                            };

                            document.getElementById('btn30Days').onclick = () => {
                                const { labels, values } = filterHistoryData(30);
                                chart.data.labels = labels;
                                chart.data.datasets[0].data = values;
                                chart.update();
                            };
                        };
                        document.head.appendChild(script);
                    }
                }

                function hideModal() {
                    modalDiv.style.display = 'none';
                }

                // showBtn.addEventListener('click', () => {
                //     if (modalDiv.style.display === 'flex') {
                //         hideModal();
                //     } else {
                //         showModal();
                //     }
                // });

                document.getElementById('showHistoryIcon').addEventListener('click', (e) => {
                    e.stopPropagation(); // 防止事件冒泡(如有需要)
                    if (modalDiv.style.display === 'flex') {
                        hideModal();
                    } else {
                        showModal();
                    }
                });

                closeBtn.addEventListener('click', hideModal);

                // 拖动功能
                let isDragging = false, offsetX, offsetY;

                modalDiv.querySelector('#deltaNetworthChartHeader').addEventListener('mousedown', (e) => {
                    isDragging = true;
                    offsetX = e.clientX - modalDiv.offsetLeft;
                    offsetY = e.clientY - modalDiv.offsetTop;
                });

                document.addEventListener('mousemove', (e) => {
                    if (isDragging) {
                        modalDiv.style.left = `${e.clientX - offsetX}px`;
                        modalDiv.style.top = `${e.clientY - offsetY}px`;
                    }
                });

                document.addEventListener('mouseup', () => {
                    isDragging = false;
                });
            } else {
                store.setTodayValue(totalNetworth);
                const delta = store.getTodayDelta(totalNetworth);
                const deltaDom = document.getElementById('deltaNetworthDiv');
                if (deltaDom) {
                    const formattedDelta = formatLargeNumber(delta);
                    const color = delta > 0 ? 'green' : (delta < 0 ? 'red' : 'gray');
                    deltaDom.innerHTML = `
                        <span style="font-weight:bold;">💰今日增长: </span>
                        <span style="color:${color};font-weight:bold;">${formattedDelta}</span>
                        <span id="showHistoryIcon" style="cursor:pointer; margin-left:8px; font-size:18px;">📊</span>
                    `;
                }
            }
        };

        injectDeltaScript();
        setInterval(() => injectDeltaScript(false), 10 * 60 * 1000); // 每10分钟刷新
    };

    // 监听 Networth 的 DOM 元素是否出现
    const checkNetworthAndRun = () => {
        const networthDisplay = document.querySelector('#toggleNetWorth');
        if (networthDisplay) {
            const textContent = networthDisplay.textContent.trim();
            const totalNetworth = parseFormattedNumber(textContent);

            const insertDom = document.getElementById('netWorthDetails');
            if (insertDom && !document.getElementById('deltaNetworthDiv')) {
                window.kbd_calculateTotalNetworth?.(totalNetworth, insertDom);
            }
        }
    };

    // 初始检查
    checkNetworthAndRun();

    // 定时检查(页面可能动态加载)
    setInterval(checkNetworthAndRun, 5000);
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址