您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
记录 Milky Way Idle 游戏的市场历史数据
当前为
// ==UserScript== // @name Market List History // @nname:zh-CN Market List History // @namespace http://tampermonkey.net/ // @version 0.1.1 // @description 记录 Milky Way Idle 游戏的市场历史数据 // @author deirc // @license MIT // @match *www.milkywayidle.com/game* // ==/UserScript== (function() { 'use strict'; const STORAGE_KEY = 'milkyway_market_history'; let lastReadTime = 0; // 记录最后一次读取时间 const READ_INTERVAL = 1000; // 读取间隔(1秒) // 检查是否可以读取数据 function canReadData() { const now = Date.now(); if (now - lastReadTime >= READ_INTERVAL) { lastReadTime = now; return true; } return false; } // 保存数据到 localStorage function saveData(data) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); return true; } catch (error) { console.error('保存数据失败:', error); return false; } } // 从 localStorage 读取数据 function loadData() { try { const data = localStorage.getItem(STORAGE_KEY); return data ? JSON.parse(data) : {}; } catch (error) { console.error('读取数据失败:', error); return {}; } } // 清理过期数据 function cleanupOldData(allHistory) { const retentionDays = parseInt(localStorage.getItem('market_history_retention') || '7'); const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - retentionDays); Object.keys(allHistory).forEach(item => { allHistory[item] = allHistory[item].filter(record => new Date(record.timestamp) > cutoffDate ); // 如果物品没有任何记录,删除该物品 if (allHistory[item].length === 0) { delete allHistory[item]; } }); return allHistory; } // 清理自动保存的数据 function cleanupAutoData(allHistory) { let hasChanges = false; Object.keys(allHistory).forEach(item => { const originalLength = allHistory[item].length; allHistory[item] = allHistory[item].filter(record => record.readType === 'manual'); // 如果物品没有任何记录,删除该物品 if (allHistory[item].length === 0) { delete allHistory[item]; } // 检查是否有数据被删除 if (originalLength !== allHistory[item]?.length) { hasChanges = true; } }); if (hasChanges) { saveData(allHistory); } return hasChanges; } // 删除指定天数前的手动保存数据 function cleanupManualDataBefore(days) { let allHistory = loadData(); const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - days); let hasChanges = false; Object.keys(allHistory).forEach(item => { const originalLength = allHistory[item].length; allHistory[item] = allHistory[item].filter(record => record.readType === 'auto' || new Date(record.timestamp) > cutoffDate ); // 如果物品没有任何记录,删除该物品 if (allHistory[item].length === 0) { delete allHistory[item]; } // 检查是否有数据被删除 if (originalLength !== allHistory[item]?.length) { hasChanges = true; } }); if (hasChanges) { saveData(allHistory); } return hasChanges; } // 等待页面加载完成 function waitForElement(selector) { return new Promise(resolve => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } const observer = new MutationObserver(mutations => { if (document.querySelector(selector)) { observer.disconnect(); resolve(document.querySelector(selector)); } }); observer.observe(document.body, { childList: true, subtree: true }); }); } // 获取物品名称 function getItemName() { const itemIconSelector = "#root > div > div > div.GamePage_gamePanel__3uNKN > div.GamePage_contentPanel__Zx4FH > div.GamePage_middlePanel__uDts7.GamePage_chatCollapsed__3pV19 > div.GamePage_mainPanel__2njyb > div > div:nth-child(1) > div > div.MarketplacePanel_tabsComponentContainer__3ctJH > div > div.TabsComponent_tabPanelsContainer__26mzo > div:nth-child(1) > div > div.MarketplacePanel_infoContainer__2mCnh > div > div.Item_itemContainer__x7kH1 > div > div > div.Item_iconContainer__5z7j4 > svg"; const itemIcon = document.querySelector(itemIconSelector); return itemIcon?.getAttribute('aria-label') || '未知物品'; } // 监视物品数量元素 function watchItemCount() { const itemCountSelector = "#root > div > div > div.GamePage_gamePanel__3uNKN > div.GamePage_contentPanel__Zx4FH > div.GamePage_middlePanel__uDts7 > div.GamePage_mainPanel__2njyb > div > div:nth-child(1) > div > div.MarketplacePanel_tabsComponentContainer__3ctJH > div > div.TabsComponent_tabPanelsContainer__26mzo > div:nth-child(1) > div > div.MarketplacePanel_marketNavButtonContainer__2QI9I"; const itemIconSelector = "#root > div > div > div.GamePage_gamePanel__3uNKN > div.GamePage_contentPanel__Zx4FH > div.GamePage_middlePanel__uDts7.GamePage_chatCollapsed__3pV19 > div.GamePage_mainPanel__2njyb > div > div:nth-child(1) > div > div.MarketplacePanel_tabsComponentContainer__3ctJH > div > div.TabsComponent_tabPanelsContainer__26mzo > div:nth-child(1) > div > div.MarketplacePanel_infoContainer__2mCnh > div > div.Item_itemContainer__x7kH1 > div > div > div.Item_iconContainer__5z7j4"; const marketPanelSelector = "#root > div > div > div.GamePage_gamePanel__3uNKN > div.GamePage_contentPanel__Zx4FH > div.GamePage_middlePanel__uDts7.GamePage_chatCollapsed__3pV19 > div.GamePage_mainPanel__2njyb > div > div:nth-child(1) > div > div.MarketplacePanel_tabsComponentContainer__3ctJH"; let isMarketOpen = false; let lastItemIcon = null; const observer = new MutationObserver((mutations, obs) => { const itemCount = document.querySelector(itemCountSelector); const marketPanel = document.querySelector(marketPanelSelector); const itemIcon = document.querySelector(itemIconSelector); const container = document.getElementById('market-history-container'); const historyPanel = document.getElementById('market-history-panel'); // 检查市场面板是否打开 const newMarketOpen = !!marketPanel; if (itemCount) { if (!container) { createHistoryUI(); } container?.style.setProperty('display', 'block'); } else { container?.style.setProperty('display', 'none'); // 关闭历史数据面板 historyPanel?.remove(); } // 当物品图标改变时执行自动读取 if (itemIcon && itemIcon !== lastItemIcon) { lastItemIcon = itemIcon; recordMarketData('auto'); } isMarketOpen = newMarketOpen; }); observer.observe(document.body, { childList: true, subtree: true }); // 初始检查 const itemCount = document.querySelector(itemCountSelector); const marketPanel = document.querySelector(marketPanelSelector); const itemIcon = document.querySelector(itemIconSelector); if (itemCount) { if (!document.getElementById('market-history-container')) { createHistoryUI(); } // 初始自动读取 if (marketPanel && itemIcon) { isMarketOpen = true; lastItemIcon = itemIcon; recordMarketData('auto'); } } } // 记录市场数据 function recordMarketData(readType = 'manual') { // 如果是自动读取且未达到间隔时间,则跳过 if (readType === 'auto' && !canReadData()) { return; } const currentData = getCurrentMarketData(); if (!currentData) { console.log('未找到市场数据'); return; } const currentTime = new Date().toISOString(); const itemName = getItemName(); // 获取所有历史数据 let allHistory = loadData(); // 如果该物品没有历史记录,创建新数组 if (!allHistory[itemName]) { allHistory[itemName] = []; } // 添加新记录 allHistory[itemName].push({ timestamp: currentTime, data: currentData, readType: readType // 添加读取类型标记 }); // 清理旧数据 allHistory = cleanupOldData(allHistory); // 保存数据 if (!saveData(allHistory)) { return; // 如果保存失败,直接返回 } // 更新状态显示 updateStatusDisplay(itemName, currentData, readType); return { itemName, timestamp: currentTime, data: currentData, readType: readType }; } // 获取当前市场数据 function getCurrentMarketData() { // 买单选择器 const buyOrderSelector = "#root > div > div > div.GamePage_gamePanel__3uNKN > div.GamePage_contentPanel__Zx4FH > div.GamePage_middlePanel__uDts7.GamePage_chatCollapsed__3pV19 > div.GamePage_mainPanel__2njyb > div > div:nth-child(1) > div > div.MarketplacePanel_tabsComponentContainer__3ctJH > div > div.TabsComponent_tabPanelsContainer__26mzo > div:nth-child(1) > div > div.MarketplacePanel_orderBook__326Yx > div.MarketplacePanel_orderBooksContainer__B4YE- > div:nth-child(2) > table > tbody"; // 卖单选择器 const sellOrderSelector = "#root > div > div > div.GamePage_gamePanel__3uNKN > div.GamePage_contentPanel__Zx4FH > div.GamePage_middlePanel__uDts7.GamePage_chatCollapsed__3pV19 > div.GamePage_mainPanel__2njyb > div > div:nth-child(1) > div > div.MarketplacePanel_tabsComponentContainer__3ctJH > div > div.TabsComponent_tabPanelsContainer__26mzo > div:nth-child(1) > div > div.MarketplacePanel_orderBook__326Yx > div.MarketplacePanel_orderBooksContainer__B4YE- > div:nth-child(1) > table > tbody"; const buyOrderRows = document.querySelector(buyOrderSelector)?.querySelectorAll('tr'); const sellOrderRows = document.querySelector(sellOrderSelector)?.querySelectorAll('tr'); if (!buyOrderRows && !sellOrderRows) { return null; } const marketData = { buyOrders: [], sellOrders: [] }; // 处理买单数据 if (buyOrderRows) { buyOrderRows.forEach(row => { const columns = row.querySelectorAll('td'); if (columns.length >= 2) { const itemData = { quantity: convertNumber(columns[0]?.textContent?.trim()), price: convertNumber(columns[1]?.textContent?.trim()), }; marketData.buyOrders.push(itemData); } }); } // 处理卖单数据 if (sellOrderRows) { sellOrderRows.forEach(row => { const columns = row.querySelectorAll('td'); if (columns.length >= 2) { const itemData = { quantity: convertNumber(columns[0]?.textContent?.trim()), price: convertNumber(columns[1]?.textContent?.trim()), }; marketData.sellOrders.push(itemData); } }); } return marketData; } // 转换数值(K和M转换为具体数字) function convertNumber(value) { if (typeof value !== 'string') { return value; } value = value.trim().toUpperCase(); // 删除"。"字符 value = value.replace(/。/g, ''); if (value.endsWith('K')) { return (parseFloat(value.slice(0, -1)) * 1000).toString(); } else if (value.endsWith('M')) { return (parseFloat(value.slice(0, -1)) * 1000000).toString(); } return value; } // 格式化数值显示 function formatNumber(value) { if (typeof value !== 'string') { return value; } const num = parseFloat(value); if (isNaN(num)) { return value; } // 直接返回数值,不添加千位分隔符 return num.toString(); } // 创建历史记录显示界面 function createHistoryUI() { const container = document.createElement('div'); container.id = 'market-history-container'; container.style.cssText = ` position: fixed; top: 20px; right: 20px; background: rgba(0, 0, 0, 0.8); padding: 12px; border-radius: 5px; color: white; z-index: 9999; font-family: Arial, sans-serif; display: none; cursor: move; width: 300px; `; // 创建按钮容器 const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = ` display: flex; gap: 8px; margin-bottom: 8px; `; // 读取当前数据按钮 const currentDataButton = document.createElement('button'); currentDataButton.textContent = '读取当前数据'; currentDataButton.style.cssText = ` padding: 6px 12px; border: none; border-radius: 3px; background: #4CAF50; color: white; cursor: pointer; flex: 1; white-space: nowrap; font-size: 13px; transition: background-color 0.2s; `; currentDataButton.onmouseover = () => { currentDataButton.style.backgroundColor = '#45a049'; }; currentDataButton.onmouseout = () => { currentDataButton.style.backgroundColor = '#4CAF50'; }; currentDataButton.onclick = showCurrentData; // 读取历史数据按钮 const historyButton = document.createElement('button'); historyButton.textContent = '读取历史数据'; historyButton.style.cssText = ` padding: 6px 12px; border: none; border-radius: 3px; background: #2196F3; color: white; cursor: pointer; flex: 1; white-space: nowrap; font-size: 13px; transition: background-color 0.2s; `; historyButton.onmouseover = () => { historyButton.style.backgroundColor = '#1e88e5'; }; historyButton.onmouseout = () => { historyButton.style.backgroundColor = '#2196F3'; }; historyButton.onclick = showHistory; // 添加按钮到容器 buttonContainer.appendChild(currentDataButton); buttonContainer.appendChild(historyButton); const status = document.createElement('span'); status.id = 'market-history-status'; status.style.cssText = ` display: block; margin-top: 8px; font-size: 12px; line-height: 1.4; color: #ccc; word-break: break-all; `; container.appendChild(buttonContainer); container.appendChild(status); document.body.appendChild(container); // 添加拖动功能 makeDraggable(container); } // 添加拖动功能 function makeDraggable(element) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; element.onmousedown = dragMouseDown; function dragMouseDown(e) { e = e || window.event; e.preventDefault(); // 获取鼠标光标的初始位置 pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; // 鼠标移动时调用函数 document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; e.preventDefault(); // 计算新的光标位置 pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; // 设置元素的新位置 element.style.top = (element.offsetTop - pos2) + "px"; element.style.left = (element.offsetLeft - pos1) + "px"; } function closeDragElement() { // 停止移动时清除事件 document.onmouseup = null; document.onmousemove = null; } } // 更新状态显示 function updateStatusDisplay(itemName, data, readType) { const status = document.getElementById('market-history-status'); if (status) { const now = new Date().toLocaleTimeString(); const buyCount = data.buyOrders?.length || 0; const sellCount = data.sellOrders?.length || 0; const readTypeText = readType === 'auto' ? '自动读取' : '手动读取'; status.textContent = `最后更新: ${now} | ${itemName} (买单: ${buyCount}条, 卖单: ${sellCount}条) [${readTypeText}]`; } } // 显示当前数据 function showCurrentData() { const result = recordMarketData('manual'); if (!result) { alert('未找到当前市场数据'); return; } showDataPopup(result.itemName, [{ timestamp: result.timestamp, data: result.data, readType: result.readType }]); // 如果历史数据面板已打开,更新显示 const historyPanel = document.getElementById('market-history-panel'); if (historyPanel) { showItemHistory(result.itemName, historyPanel); } } // 显示历史数据 function showHistory() { const allHistory = loadData(); const currentItemName = getItemName(); const historyDiv = document.createElement('div'); historyDiv.id = 'market-history-panel'; historyDiv.style.cssText = ` position: fixed; top: 50%; right: 20px; transform: translateY(-50%); background: rgba(0, 0, 0, 0.8); padding: 15px; border-radius: 5px; max-height: 80vh; overflow-y: auto; z-index: 10000; font-family: Arial, sans-serif; color: white; box-shadow: 0 0 10px rgba(0,0,0,0.5); min-width: 800px; display: flex; flex-direction: column; cursor: move; `; let html = '<h2 style="margin-top: 0; margin-bottom: 15px; font-size: 16px; cursor: move;">市场历史记录</h2>'; if (Object.keys(allHistory).length === 0) { html += '<p style="color: #ccc;">暂无历史数据</p>'; } else { // 创建顶部控制栏 html += `<div style="display: flex; gap: 10px; margin-bottom: 15px; align-items: center;"> <select id="item-selector" style=" padding: 5px 10px; border: none; border-radius: 3px; background: #4CAF50; color: white; cursor: pointer; flex-grow: 1; "> ${Object.keys(allHistory).map(item => `<option value="${item}" ${item === currentItemName ? 'selected' : ''}>${item}</option>` ).join('')} </select> <button id="compare-data" style=" padding: 5px 10px; border: none; border-radius: 3px; background: #2196F3; color: white; cursor: pointer; transition: background-color 0.2s; min-width: 60px; ">对比</button> <button id="cleanup-data" style=" padding: 5px 10px; border: none; border-radius: 3px; background: #f44336; color: white; cursor: pointer; transition: background-color 0.2s; ">清理自动保存数据</button> <button id="settings-button" style=" padding: 5px; width: 30px; height: 30px; border: none; border-radius: 3px; background: #2196F3; color: white; cursor: pointer; transition: background-color 0.2s; display: flex; align-items: center; justify-content: center; ">⚙</button> </div>`; // 创建历史记录列表和数据显示区域 html += `<div style="display: flex; gap: 15px; flex-grow: 1; min-height: 400px;"> <div id="history-list" style=" width: 200px; border-right: 1px solid rgba(255, 255, 255, 0.1); padding-right: 15px; overflow-y: auto; max-height: 60vh; "></div> <div id="history-detail" style=" flex-grow: 1; overflow-y: auto; max-height: 60vh; "> <p style="color: #ccc; text-align: center;">请选择左侧的历史记录查看详情</p> </div> </div>`; } historyDiv.innerHTML = html; // 如果有数据,设置事件监听 if (Object.keys(allHistory).length > 0) { // 设置清理按钮事件 const cleanupButton = historyDiv.querySelector('#cleanup-data'); cleanupButton.onmouseover = () => { cleanupButton.style.background = '#d32f2f'; }; cleanupButton.onmouseout = () => { cleanupButton.style.background = '#f44336'; }; cleanupButton.onclick = () => { createConfirmDialog( '确定要清理所有自动保存的数据吗?此操作不可恢复。', () => { const allHistory = loadData(); if (cleanupAutoData(allHistory)) { // 重新显示历史记录 showItemHistory(currentItemName, historyDiv); } } ); }; // 设置对比按钮事件 const compareButton = historyDiv.querySelector('#compare-data'); compareButton.onmouseover = () => { compareButton.style.background = '#1976D2'; }; compareButton.onmouseout = () => { compareButton.style.background = '#2196F3'; }; compareButton.onclick = () => { // 读取卖单数(第一个元素) const sellOrderElement = document.querySelector("#root > div > div > div.GamePage_gamePanel__3uNKN > div.GamePage_contentPanel__Zx4FH > div.GamePage_middlePanel__uDts7.GamePage_chatCollapsed__3pV19 > div.GamePage_mainPanel__2njyb > div > div:nth-child(1) > div > div.MarketplacePanel_tabsComponentContainer__3ctJH > div > div.TabsComponent_tabPanelsContainer__26mzo > div:nth-child(1) > div > div.MarketplacePanel_orderBook__326Yx > div.MarketplacePanel_orderBooksContainer__B4YE- > div:nth-child(1) > table > tbody > tr:nth-child(2) > td:nth-child(1)"); // 读取买单数(第二个元素) const buyOrderElement = document.querySelector("#root > div > div > div.GamePage_gamePanel__3uNKN > div.GamePage_contentPanel__Zx4FH > div.GamePage_middlePanel__uDts7.GamePage_chatCollapsed__3pV19 > div.GamePage_mainPanel__2njyb > div > div:nth-child(1) > div > div.MarketplacePanel_tabsComponentContainer__3ctJH > div > div.TabsComponent_tabPanelsContainer__26mzo > div:nth-child(1) > div > div.MarketplacePanel_orderBook__326Yx > div.MarketplacePanel_orderBooksContainer__B4YE- > div:nth-child(2) > table > tbody > tr:nth-child(2) > td:nth-child(1)"); const sellOrderValue = sellOrderElement?.textContent?.trim() || '未找到'; const buyOrderValue = buyOrderElement?.textContent?.trim() || '未找到'; // 对比并高亮历史记录面板中的数据 highlightMatchingValues(sellOrderValue, buyOrderValue); // 使用内部信息面板而非浏览器弹窗 showAnalysisPanel(`卖单数: ${sellOrderValue}\n买单数: ${buyOrderValue}\n已高亮显示匹配的数值`); }; // 设置按钮事件 const settingsButton = historyDiv.querySelector('#settings-button'); settingsButton.onmouseover = () => { settingsButton.style.background = '#1976D2'; }; settingsButton.onmouseout = () => { settingsButton.style.background = '#2196F3'; }; settingsButton.onclick = () => { showSettingsPanel(); }; // 设置物品选择事件 historyDiv.querySelector('#item-selector').onchange = function(e) { showItemHistory(e.target.value, historyDiv); }; // 显示当前物品的历史记录 showItemHistory(currentItemName, historyDiv); } document.body.appendChild(historyDiv); // 添加关闭按钮 const closeButton = document.createElement('button'); closeButton.textContent = '×'; closeButton.style.cssText = ` position: absolute; top: 10px; right: 10px; border: none; background: none; font-size: 20px; cursor: pointer; color: #ccc; padding: 0 5px; `; closeButton.onclick = () => historyDiv.remove(); historyDiv.appendChild(closeButton); // 添加拖动功能 makeDraggable(historyDiv); // 防止选择器和按钮的点击触发拖动 const preventDrag = (e) => e.stopPropagation(); historyDiv.querySelector('#item-selector')?.addEventListener('mousedown', preventDrag); historyDiv.querySelector('#cleanup-data')?.addEventListener('mousedown', preventDrag); historyDiv.querySelector('#settings-button')?.addEventListener('mousedown', preventDrag); // 添加设置按钮的拖动阻止 historyDiv.querySelectorAll('button').forEach(btn => btn.addEventListener('mousedown', preventDrag) ); } // 显示特定物品的历史记录 function showItemHistory(itemName, container) { const allHistory = loadData(); const itemHistory = allHistory[itemName] || []; const historyList = container.querySelector('#history-list'); const historyDetail = container.querySelector('#history-detail'); // 生成历史记录列表 let listHtml = ''; const sortedHistory = itemHistory.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)); sortedHistory.forEach((record, index) => { const date = new Date(record.timestamp); const readTypeText = record.readType === 'auto' ? '自动读取' : '手动读取'; const isManual = record.readType === 'manual'; listHtml += `<div class="history-item" data-index="${index}" style=" padding: 10px; border: 1px solid ${isManual ? 'rgba(76, 175, 80, 0.3)' : 'rgba(255, 255, 255, 0.1)'}; cursor: pointer; transition: all 0.2s; margin-bottom: 8px; border-radius: 4px; background: ${isManual ? 'rgba(76, 175, 80, 0.1)' : 'rgba(255, 255, 255, 0.05)'}; position: relative; "> <div style="font-weight: bold; color: #fff;">${date.toLocaleDateString()}</div> <div style="font-size: 12px; color: ${isManual ? '#4CAF50' : '#ccc'};"> ${date.toLocaleTimeString()} <span style=" margin-left: 5px; padding: 2px 6px; border-radius: 3px; background: ${isManual ? 'rgba(76, 175, 80, 0.2)' : 'rgba(255, 255, 255, 0.1)'}; ">[${readTypeText}]</span> </div> <button class="delete-record" data-timestamp="${record.timestamp}" style=" position: absolute; top: 5px; right: 5px; background: rgba(244, 67, 54, 0.2); border: none; color: #f44336; border-radius: 3px; width: 20px; height: 20px; line-height: 20px; text-align: center; cursor: pointer; font-size: 14px; padding: 0; opacity: 0; transition: opacity 0.2s; ">×</button> </div>`; }); if (listHtml === '') { historyList.innerHTML = '<p style="color: #ccc; text-align: center;">暂无历史数据</p>'; historyDetail.innerHTML = '<p style="color: #ccc; text-align: center;">暂无历史数据</p>'; return; } historyList.innerHTML = listHtml; // 添加历史记录项的点击事件和悬停效果 const historyItems = historyList.querySelectorAll('.history-item'); historyItems.forEach(item => { const index = parseInt(item.dataset.index); const record = sortedHistory[index]; const isManual = record.readType === 'manual'; const deleteBtn = item.querySelector('.delete-record'); // 添加悬停效果 item.onmouseover = () => { if (!item.classList.contains('selected')) { item.style.background = isManual ? 'rgba(76, 175, 80, 0.2)' : 'rgba(255, 255, 255, 0.1)'; } deleteBtn.style.opacity = '1'; }; item.onmouseout = () => { if (!item.classList.contains('selected')) { item.style.background = isManual ? 'rgba(76, 175, 80, 0.1)' : 'rgba(255, 255, 255, 0.05)'; } deleteBtn.style.opacity = '0'; }; // 添加删除按钮悬停效果 deleteBtn.onmouseover = (e) => { e.stopPropagation(); deleteBtn.style.background = 'rgba(244, 67, 54, 0.4)'; }; deleteBtn.onmouseout = (e) => { e.stopPropagation(); deleteBtn.style.background = 'rgba(244, 67, 54, 0.2)'; }; // 添加删除按钮点击事件 deleteBtn.onclick = (e) => { e.stopPropagation(); const timestamp = deleteBtn.dataset.timestamp; createConfirmDialog( '确定要删除这条记录吗?此操作不可恢复。', () => { if (deleteHistoryRecord(itemName, timestamp)) { // 重新显示历史记录 showItemHistory(itemName, container); } } ); }; // 添加点击事件 item.onclick = () => { // 移除其他项的选中状态 historyItems.forEach(i => { const idx = parseInt(i.dataset.index); const rec = sortedHistory[idx]; const isManualRec = rec.readType === 'manual'; i.classList.remove('selected'); i.style.background = isManualRec ? 'rgba(76, 175, 80, 0.1)' : 'rgba(255, 255, 255, 0.05)'; i.querySelector('.delete-record').style.opacity = '0'; }); // 设置当前项的选中状态 item.classList.add('selected'); item.style.background = isManual ? 'rgba(76, 175, 80, 0.3)' : 'rgba(255, 255, 255, 0.15)'; deleteBtn.style.opacity = '1'; // 显示详细数据 showHistoryDetail(record, historyDetail); }; }); // 默认显示最新的记录 if (historyItems.length > 0) { historyItems[0].click(); } } // 显示历史记录详情 function showHistoryDetail(record, container) { const isManual = record.readType === 'manual'; let html = `<div style=" display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; "> <h3 style=" margin: 0; color: #fff; font-size: 14px; padding: 8px; background: ${isManual ? 'rgba(76, 175, 80, 0.1)' : 'rgba(255, 255, 255, 0.05)'}; border-radius: 4px; border: 1px solid ${isManual ? 'rgba(76, 175, 80, 0.3)' : 'rgba(255, 255, 255, 0.1)'}; flex-grow: 1; margin-right: 10px; "> ${new Date(record.timestamp).toLocaleString()} <span style=" margin-left: 8px; padding: 2px 6px; border-radius: 3px; background: ${isManual ? 'rgba(76, 175, 80, 0.2)' : 'rgba(255, 255, 255, 0.1)'}; font-size: 12px; ">[${record.readType === 'auto' ? '自动读取' : '手动读取'}]</span> </h3> ${record.readType === 'auto' ? ` <button class="save-as-manual" data-timestamp="${record.timestamp}" style=" padding: 4px 12px; border: none; border-radius: 3px; background: #4CAF50; color: white; cursor: pointer; font-size: 12px; transition: background-color 0.2s; ">保存</button> ` : ''} </div>`; // 创建横向布局容器 html += '<div style="display: flex; gap: 20px; margin-top: 15px;">'; // 卖单数据(左侧) html += '<div style="flex: 1;">'; html += '<h4 style="color: #f44336; margin: 0 0 10px 0; font-size: 13px;">卖单</h4>'; if (record.data.sellOrders && record.data.sellOrders.length > 0) { html += '<table style="width: 100%; border-collapse: collapse; margin-bottom: 10px;">'; html += '<tr><th style="border: 1px solid rgba(255, 255, 255, 0.1); padding: 8px; background: rgba(255, 255, 255, 0.1); color: #fff;">数量</th><th style="border: 1px solid rgba(255, 255, 255, 0.1); padding: 8px; background: rgba(255, 255, 255, 0.1); color: #fff;">价格</th></tr>'; record.data.sellOrders.forEach((item, index) => { html += `<tr data-sell-index="${index}"> <td style="border: 1px solid rgba(255, 255, 255, 0.1); padding: 8px; color: #fff;">${formatNumber(item.quantity)}</td> <td style="border: 1px solid rgba(255, 255, 255, 0.1); padding: 8px; color: #fff;">${formatNumber(item.price)}</td> </tr>`; }); html += '</table>'; } else { html += '<p style="color: #ccc; margin: 0;">无卖单数据</p>'; } html += '</div>'; // 买单数据(右侧) html += '<div style="flex: 1;">'; html += '<h4 style="color: #4CAF50; margin: 0 0 10px 0; font-size: 13px;">买单</h4>'; if (record.data.buyOrders && record.data.buyOrders.length > 0) { html += '<table style="width: 100%; border-collapse: collapse; margin-bottom: 10px;">'; html += '<tr><th style="border: 1px solid rgba(255, 255, 255, 0.1); padding: 8px; background: rgba(255, 255, 255, 0.1); color: #fff;">数量</th><th style="border: 1px solid rgba(255, 255, 255, 0.1); padding: 8px; background: rgba(255, 255, 255, 0.1); color: #fff;">价格</th></tr>'; record.data.buyOrders.forEach((item, index) => { html += `<tr data-buy-index="${index}"> <td style="border: 1px solid rgba(255, 255, 255, 0.1); padding: 8px; color: #fff;">${formatNumber(item.quantity)}</td> <td style="border: 1px solid rgba(255, 255, 255, 0.1); padding: 8px; color: #fff;">${formatNumber(item.price)}</td> </tr>`; }); html += '</table>'; } else { html += '<p style="color: #ccc; margin: 0;">无买单数据</p>'; } html += '</div>'; // 关闭横向布局容器 html += '</div>'; container.innerHTML = html; // 添加保存按钮事件 const saveButton = container.querySelector('.save-as-manual'); if (saveButton) { saveButton.onmouseover = () => { saveButton.style.background = '#45a049'; }; saveButton.onmouseout = () => { saveButton.style.background = '#4CAF50'; }; saveButton.onclick = () => { const timestamp = saveButton.dataset.timestamp; const itemName = document.querySelector('#item-selector').value; if (updateRecordType(itemName, timestamp, 'manual')) { // 重新显示历史记录 showItemHistory(itemName, container.closest('#market-history-panel')); } }; } } // 显示数据弹窗 function showDataPopup(itemName, records) { const popupDiv = document.createElement('div'); popupDiv.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border-radius: 10px; max-height: 80vh; max-width: 80vw; overflow-y: auto; z-index: 10000; font-family: Arial, sans-serif; color: black; box-shadow: 0 0 10px rgba(0,0,0,0.5); `; let html = `<h2 style="margin-top: 0;">${itemName}</h2>`; records.forEach(record => { const readTypeText = record.readType === 'auto' ? '自动读取' : '手动读取'; html += `<h3>${new Date(record.timestamp).toLocaleString()} [${readTypeText}]</h3>`; // 显示卖单数据 if (record.data.sellOrders && record.data.sellOrders.length > 0) { html += '<h4 style="color: #f44336;">卖单</h4>'; html += '<table style="width: 100%; border-collapse: collapse; margin-bottom: 20px;">'; html += '<tr><th style="border: 1px solid #ddd; padding: 8px;">数量</th><th style="border: 1px solid #ddd; padding: 8px;">价格</th></tr>'; record.data.sellOrders.forEach(item => { html += `<tr> <td style="border: 1px solid #ddd; padding: 8px;">${item.quantity}</td> <td style="border: 1px solid #ddd; padding: 8px;">${item.price}</td> </tr>`; }); html += '</table>'; } // 显示买单数据 if (record.data.buyOrders && record.data.buyOrders.length > 0) { html += '<h4 style="color: #4CAF50;">买单</h4>'; html += '<table style="width: 100%; border-collapse: collapse; margin-bottom: 20px;">'; html += '<tr><th style="border: 1px solid #ddd; padding: 8px;">数量</th><th style="border: 1px solid #ddd; padding: 8px;">价格</th></tr>'; record.data.buyOrders.forEach(item => { html += `<tr> <td style="border: 1px solid #ddd; padding: 8px;">${item.quantity}</td> <td style="border: 1px solid #ddd; padding: 8px;">${item.price}</td> </tr>`; }); html += '</table>'; } }); popupDiv.innerHTML = html; // 添加关闭按钮 const closeButton = document.createElement('button'); closeButton.textContent = '×'; closeButton.style.cssText = ` position: absolute; top: 10px; right: 10px; border: none; background: none; font-size: 20px; cursor: pointer; color: #666; `; closeButton.onclick = () => popupDiv.remove(); popupDiv.appendChild(closeButton); document.body.appendChild(popupDiv); // 点击其他地方关闭 popupDiv.onclick = e => e.stopPropagation(); document.body.onclick = () => popupDiv.remove(); } // 删除单条历史记录 function deleteHistoryRecord(itemName, timestamp) { let allHistory = loadData(); if (allHistory[itemName]) { allHistory[itemName] = allHistory[itemName].filter(record => record.timestamp !== timestamp); // 如果物品没有任何记录,删除该物品 if (allHistory[itemName].length === 0) { delete allHistory[itemName]; } // 保存更新后的数据 saveData(allHistory); return true; } return false; } // 修改记录的读取类型 function updateRecordType(itemName, timestamp, newType) { let allHistory = loadData(); if (allHistory[itemName]) { const record = allHistory[itemName].find(record => record.timestamp === timestamp); if (record) { record.readType = newType; // 保存更新后的数据 saveData(allHistory); return true; } } return false; } // 创建设置面板 function showSettingsPanel() { const settingsDiv = document.createElement('div'); settingsDiv.id = 'market-history-settings'; settingsDiv.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.9); padding: 20px; border-radius: 5px; z-index: 10001; font-family: Arial, sans-serif; color: white; box-shadow: 0 0 10px rgba(0,0,0,0.5); min-width: 300px; `; let html = ` <h3 style=" margin: 0 0 20px 0; font-size: 16px; color: #fff; border-bottom: 1px solid rgba(255, 255, 255, 0.1); padding-bottom: 10px; ">设置</h3> <div style="margin-bottom: 15px;"> <label style=" display: flex; align-items: center; margin-bottom: 10px; cursor: pointer; "> <input type="checkbox" id="settings-auto-read" style="margin-right: 10px;"> <span>启用自动读取</span> </label> <div style=" font-size: 12px; color: #999; margin-left: 24px; ">切换物品时自动记录数据</div> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 5px;">自动保存数据保留时间</label> <select id="settings-retention" style=" width: 100%; padding: 8px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 3px; color: white; margin-top: 5px; cursor: pointer; font-size: 14px; appearance: none; -webkit-appearance: none; -moz-appearance: none; position: relative; transition: all 0.2s; "> <option value="1" style="background: rgba(0, 0, 0, 0.9);">1天</option> <option value="3" style="background: rgba(0, 0, 0, 0.9);">3天</option> <option value="7" style="background: rgba(0, 0, 0, 0.9);" selected>7天</option> <option value="14" style="background: rgba(0, 0, 0, 0.9);">14天</option> <option value="30" style="background: rgba(0, 0, 0, 0.9);">30天</option> </select> <div style=" position: absolute; right: 35px; margin-top: -28px; color: rgba(255, 255, 255, 0.5); pointer-events: none; font-size: 12px; ">▼</div> </div> <div style="margin-bottom: 15px;"> <label style="display: block; margin-bottom: 10px;">删除历史手动保存数据</label> <div style=" display: flex; gap: 10px; align-items: center; "> <select id="manual-cleanup-days" style=" padding: 8px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 3px; color: white; cursor: pointer; font-size: 14px; appearance: none; -webkit-appearance: none; -moz-appearance: none; position: relative; transition: all 0.2s; flex: 1; "> <option value="7" style="background: rgba(0, 0, 0, 0.9);">7天前</option> <option value="14" style="background: rgba(0, 0, 0, 0.9);">14天前</option> <option value="30" style="background: rgba(0, 0, 0, 0.9);">30天前</option> <option value="90" style="background: rgba(0, 0, 0, 0.9);">90天前</option> <option value="180" style="background: rgba(0, 0, 0, 0.9);">180天前</option> </select> <div style=" position: absolute; right: 140px; color: rgba(255, 255, 255, 0.5); pointer-events: none; font-size: 12px; ">▼</div> <button id="manual-cleanup-button" style=" padding: 8px 12px; border: none; border-radius: 3px; background: #f44336; color: white; cursor: pointer; transition: background-color 0.2s; font-size: 14px; min-width: 80px; ">删除</button> </div> <div style=" font-size: 12px; color: #999; margin-top: 5px; margin-left: 2px; ">删除指定天数前的手动保存数据,此操作不可恢复</div> </div> <div style=" display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; padding-top: 15px; border-top: 1px solid rgba(255, 255, 255, 0.1); "> <button id="settings-cancel" style=" padding: 6px 12px; border: none; border-radius: 3px; background: #757575; color: white; cursor: pointer; transition: background-color 0.2s; ">取消</button> <button id="settings-save" style=" padding: 6px 12px; border: none; border-radius: 3px; background: #4CAF50; color: white; cursor: pointer; transition: background-color 0.2s; ">保存</button> </div> `; settingsDiv.innerHTML = html; // 添加遮罩层 const overlay = document.createElement('div'); overlay.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 10000; `; // 添加按钮事件 const saveBtn = settingsDiv.querySelector('#settings-save'); const cancelBtn = settingsDiv.querySelector('#settings-cancel'); const autoReadCheckbox = settingsDiv.querySelector('#settings-auto-read'); const retentionSelect = settingsDiv.querySelector('#settings-retention'); const manualCleanupDaysSelect = settingsDiv.querySelector('#manual-cleanup-days'); const manualCleanupButton = settingsDiv.querySelector('#manual-cleanup-button'); // 加载当前设置 autoReadCheckbox.checked = localStorage.getItem('market_history_auto_read') !== 'false'; const currentRetention = localStorage.getItem('market_history_retention') || '7'; retentionSelect.value = currentRetention; // 下拉栏悬停和焦点效果 const addSelectEffects = (select) => { select.onmouseover = () => { select.style.background = 'rgba(255, 255, 255, 0.1)'; select.style.borderColor = 'rgba(255, 255, 255, 0.2)'; }; select.onmouseout = () => { if (document.activeElement !== select) { select.style.background = 'rgba(255, 255, 255, 0.05)'; select.style.borderColor = 'rgba(255, 255, 255, 0.1)'; } }; select.onfocus = () => { select.style.background = 'rgba(255, 255, 255, 0.1)'; select.style.borderColor = 'rgba(255, 255, 255, 0.2)'; select.style.outline = 'none'; }; select.onblur = () => { select.style.background = 'rgba(255, 255, 255, 0.05)'; select.style.borderColor = 'rgba(255, 255, 255, 0.1)'; }; }; // 为两个下拉框添加效果 addSelectEffects(retentionSelect); addSelectEffects(manualCleanupDaysSelect); // 保存按钮悬停效果 saveBtn.onmouseover = () => saveBtn.style.background = '#45a049'; saveBtn.onmouseout = () => saveBtn.style.background = '#4CAF50'; // 取消按钮悬停效果 cancelBtn.onmouseover = () => cancelBtn.style.background = '#616161'; cancelBtn.onmouseout = () => cancelBtn.style.background = '#757575'; // 保存设置 saveBtn.onclick = () => { localStorage.setItem('market_history_auto_read', autoReadCheckbox.checked); localStorage.setItem('market_history_retention', retentionSelect.value); overlay.remove(); settingsDiv.remove(); }; // 取消设置 cancelBtn.onclick = () => { overlay.remove(); settingsDiv.remove(); }; // 点击遮罩层关闭 overlay.onclick = () => { overlay.remove(); settingsDiv.remove(); }; // 添加手动清理按钮事件 manualCleanupButton.onmouseover = () => manualCleanupButton.style.background = '#d32f2f'; manualCleanupButton.onmouseout = () => manualCleanupButton.style.background = '#f44336'; manualCleanupButton.onclick = () => { const days = parseInt(manualCleanupDaysSelect.value); createConfirmDialog( `确定要删除 ${days} 天前手动保存的所有数据吗?此操作不可恢复。`, () => { if (cleanupManualDataBefore(days)) { showItemHistory(document.querySelector('#item-selector').value, settingsDiv.closest('#market-history-settings')); } }, () => {} // 取消时无操作 ); }; document.body.appendChild(overlay); document.body.appendChild(settingsDiv); } // 创建自定义确认对话框 function createConfirmDialog(message, onConfirm, onCancel) { const dialogDiv = document.createElement('div'); dialogDiv.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.9); padding: 20px; border-radius: 5px; z-index: 10001; font-family: Arial, sans-serif; color: white; box-shadow: 0 0 10px rgba(0,0,0,0.5); min-width: 300px; text-align: center; `; const messageDiv = document.createElement('div'); messageDiv.style.cssText = ` margin-bottom: 20px; font-size: 14px; `; messageDiv.textContent = message; const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = ` display: flex; gap: 10px; justify-content: center; `; const confirmButton = document.createElement('button'); confirmButton.textContent = '确认'; confirmButton.style.cssText = ` padding: 8px 16px; border: none; border-radius: 3px; background: #f44336; color: white; cursor: pointer; transition: background-color 0.2s; `; confirmButton.onmouseover = () => confirmButton.style.background = '#d32f2f'; confirmButton.onmouseout = () => confirmButton.style.background = '#f44336'; const cancelButton = document.createElement('button'); cancelButton.textContent = '取消'; cancelButton.style.cssText = ` padding: 8px 16px; border: none; border-radius: 3px; background: #757575; color: white; cursor: pointer; transition: background-color 0.2s; `; cancelButton.onmouseover = () => cancelButton.style.background = '#616161'; cancelButton.onmouseout = () => cancelButton.style.background = '#757575'; confirmButton.onclick = () => { dialogDiv.remove(); overlay.remove(); onConfirm(); }; cancelButton.onclick = () => { dialogDiv.remove(); overlay.remove(); if (onCancel) onCancel(); }; buttonContainer.appendChild(cancelButton); buttonContainer.appendChild(confirmButton); dialogDiv.appendChild(messageDiv); dialogDiv.appendChild(buttonContainer); // 添加遮罩层 const overlay = document.createElement('div'); overlay.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 10000; `; overlay.onclick = () => { overlay.remove(); dialogDiv.remove(); if (onCancel) onCancel(); }; document.body.appendChild(overlay); document.body.appendChild(dialogDiv); } // 显示对比面板 function showComparePanel(itemName, currentRecord) { const compareDiv = document.createElement('div'); compareDiv.id = 'market-history-compare'; compareDiv.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.9); padding: 20px; border-radius: 5px; z-index: 10001; font-family: Arial, sans-serif; color: white; box-shadow: 0 0 10px rgba(0,0,0,0.5); min-width: 800px; max-height: 80vh; overflow-y: auto; `; let html = ` <h3 style=" margin: 0 0 20px 0; font-size: 16px; color: #fff; border-bottom: 1px solid rgba(255, 255, 255, 0.1); padding-bottom: 10px; ">数据对比 - ${itemName}</h3> <div style=" display: flex; gap: 20px; "> <!-- 当前数据 --> <div style="flex: 1;"> <h4 style="margin: 0 0 10px 0; font-size: 14px;">当前数据</h4> <div style=" font-size: 12px; color: #ccc; margin-bottom: 10px; ">${new Date(currentRecord.timestamp).toLocaleString()}</div> <!-- 卖单数据 --> <div style="margin-bottom: 15px;"> <h5 style="color: #f44336; margin: 0 0 5px 0; font-size: 13px;">卖单</h5> ${currentRecord.data.sellOrders && currentRecord.data.sellOrders.length > 0 ? ` <table style="width: 100%; border-collapse: collapse;"> <tr> <th style="border: 1px solid rgba(255, 255, 255, 0.1); padding: 5px; background: rgba(255, 255, 255, 0.1); color: #fff;">数量</th> <th style="border: 1px solid rgba(255, 255, 255, 0.1); padding: 5px; background: rgba(255, 255, 255, 0.1); color: #fff;">价格</th> </tr> ${currentRecord.data.sellOrders.map(item => ` <tr> <td style="border: 1px solid rgba(255, 255, 255, 0.1); padding: 5px; color: #fff;">${formatNumber(item.quantity)}</td> <td style="border: 1px solid rgba(255, 255, 255, 0.1); padding: 5px; color: #fff;">${formatNumber(item.price)}</td> </tr> `).join('')} </table> ` : '<p style="color: #ccc; margin: 0;">无卖单数据</p>'} </div> <!-- 买单数据 --> <div> <h5 style="color: #4CAF50; margin: 0 0 5px 0; font-size: 13px;">买单</h5> ${currentRecord.data.buyOrders && currentRecord.data.buyOrders.length > 0 ? ` <table style="width: 100%; border-collapse: collapse;"> <tr> <th style="border: 1px solid rgba(255, 255, 255, 0.1); padding: 5px; background: rgba(255, 255, 255, 0.1); color: #fff;">数量</th> <th style="border: 1px solid rgba(255, 255, 255, 0.1); padding: 5px; background: rgba(255, 255, 255, 0.1); color: #fff;">价格</th> </tr> ${currentRecord.data.buyOrders.map(item => ` <tr> <td style="border: 1px solid rgba(255, 255, 255, 0.1); padding: 5px; color: #fff;">${formatNumber(item.quantity)}</td> <td style="border: 1px solid rgba(255, 255, 255, 0.1); padding: 5px; color: #fff;">${formatNumber(item.price)}</td> </tr> `).join('')} </table> ` : '<p style="color: #ccc; margin: 0;">无买单数据</p>'} </div> </div> <!-- 历史数据(待实现) --> <div style="flex: 1;"> <h4 style="margin: 0 0 10px 0; font-size: 14px;">历史数据</h4> <div style="color: #ccc;">请选择要对比的历史数据</div> </div> </div> `; compareDiv.innerHTML = html; // 添加关闭按钮 const closeButton = document.createElement('button'); closeButton.textContent = '×'; closeButton.style.cssText = ` position: absolute; top: 10px; right: 10px; border: none; background: none; color: #ccc; font-size: 20px; cursor: pointer; padding: 0 5px; `; closeButton.onmouseover = () => closeButton.style.color = '#fff'; closeButton.onmouseout = () => closeButton.style.color = '#ccc'; compareDiv.appendChild(closeButton); // 添加遮罩层 const overlay = document.createElement('div'); overlay.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 10000; `; // 关闭事件 const closePanel = () => { overlay.remove(); compareDiv.remove(); }; closeButton.onclick = closePanel; overlay.onclick = closePanel; document.body.appendChild(overlay); document.body.appendChild(compareDiv); } // 高亮匹配的数值 function highlightMatchingValues(sellOrderValue, buyOrderValue) { const historyDetail = document.querySelector('#history-detail'); if (!historyDetail) { return; } // 转换读取到的数值(处理K、M等单位) const convertedSellValue = convertNumber(sellOrderValue); const convertedBuyValue = convertNumber(buyOrderValue); // 查找卖单列表中的数量单元格 const sellQuantityCells = historyDetail.querySelectorAll('tr[data-sell-index] td:first-child'); sellQuantityCells.forEach(cell => { const cellValue = cell.textContent?.trim(); const convertedCellValue = convertNumber(cellValue); // 移除之前的高亮和按钮 cell.style.background = ''; cell.style.color = ''; cell.style.fontWeight = ''; // 移除之前添加的按钮 const existingButton = cell.querySelector('.highlight-button'); if (existingButton) { existingButton.remove(); } // 如果数值匹配,添加高亮和按钮 if (convertedCellValue === convertedSellValue) { cell.style.background = '#ffeb3b'; cell.style.color = '#000'; cell.style.fontWeight = 'bold'; // 创建按钮 const button = document.createElement('button'); button.textContent = '计算'; button.className = 'highlight-button'; button.style.cssText = ` margin-left: 5px; padding: 2px 6px; border: none; border-radius: 3px; background: #2196F3; color: white; cursor: pointer; font-size: 12px; font-weight: bold; transition: background-color 0.2s; `; // 添加按钮悬停效果 button.onmouseover = () => { button.style.background = '#1976D2'; }; button.onmouseout = () => { button.style.background = '#2196F3'; }; // 添加按钮点击事件 button.onclick = (e) => { e.stopPropagation(); readRemainingOrders(cell, cellValue); }; // 将按钮添加到单元格 cell.appendChild(button); } }); // 查找买单列表中的数量单元格 const buyQuantityCells = historyDetail.querySelectorAll('tr[data-buy-index] td:first-child'); buyQuantityCells.forEach(cell => { const cellValue = cell.textContent?.trim(); const convertedCellValue = convertNumber(cellValue); // 移除之前的高亮和按钮 cell.style.background = ''; cell.style.color = ''; cell.style.fontWeight = ''; // 移除之前添加的按钮 const existingButton = cell.querySelector('.highlight-button'); if (existingButton) { existingButton.remove(); } // 如果数值匹配,添加高亮和按钮 if (convertedCellValue === convertedBuyValue) { cell.style.background = '#ffeb3b'; cell.style.color = '#000'; cell.style.fontWeight = 'bold'; // 创建按钮 const button = document.createElement('button'); button.textContent = '计算'; button.className = 'highlight-button'; button.style.cssText = ` margin-left: 5px; padding: 2px 6px; border: none; border-radius: 3px; background: #2196F3; color: white; cursor: pointer; font-size: 12px; font-weight: bold; transition: background-color 0.2s; `; // 添加按钮悬停效果 button.onmouseover = () => { button.style.background = '#1976D2'; }; button.onmouseout = () => { button.style.background = '#2196F3'; }; // 添加按钮点击事件 button.onclick = (e) => { e.stopPropagation(); readRemainingOrders(cell, cellValue); }; // 将按钮添加到单元格 cell.appendChild(button); } }); } // 读取残余订单数 function readRemainingOrders(targetCell, currentValue) { // 判断当前按钮属于哪个列表 const parentRow = targetCell.parentElement; const isSellOrder = parentRow.hasAttribute('data-sell-index'); // 先计算累计数量之和 const sum = calculateSumForCell(targetCell); const formattedSum = formatNumber(sum.toString()); // 获取当前时间 const currentTime = new Date(); // 获取历史数据的时间(从记录中获取) const historyTime = getHistoryTimeFromRecord(); if (isSellOrder) { // 左侧按钮:只读取残余卖单数 const remainingSellElement = document.querySelector("#root > div > div > div.GamePage_gamePanel__3uNKN > div.GamePage_contentPanel__Zx4FH > div.GamePage_middlePanel__uDts7.GamePage_chatCollapsed__3pV19 > div.GamePage_mainPanel__2njyb > div > div:nth-child(1) > div > div.MarketplacePanel_tabsComponentContainer__3ctJH > div > div.TabsComponent_tabPanelsContainer__26mzo > div:nth-child(1) > div > div.MarketplacePanel_orderBook__326Yx > div.MarketplacePanel_orderBooksContainer__B4YE- > div:nth-child(1) > table > tbody > tr:nth-child(1) > td:nth-child(1)"); const remainingSellValue = remainingSellElement?.textContent?.trim() || '未找到'; // 计算差值 const remainingSellNum = parseFloat(convertNumber(remainingSellValue)) || 0; const difference = sum - remainingSellNum; const formattedDifference = formatNumber(difference.toString()); // 计算平均速度 const timeDiff = currentTime - historyTime; const hoursDiff = timeDiff / (1000 * 60 * 60); const daysDiff = hoursDiff / 24; const avgPerHour = hoursDiff > 0 ? (difference / hoursDiff).toFixed(2) : '0'; const avgPerDay = daysDiff > 0 ? (difference / daysDiff).toFixed(2) : '0'; showAnalysisPanel(`差值: ${formattedDifference}\n时间差: ${hoursDiff.toFixed(2)}小时 (${daysDiff.toFixed(2)}天)\n平均每小时卖出: ${avgPerHour}\n平均每天卖出: ${avgPerDay}`); } else { // 右侧按钮:只读取残余买单数 const remainingBuyElement = document.querySelector("#root > div > div > div.GamePage_gamePanel__3uNKN > div.GamePage_contentPanel__Zx4FH > div.GamePage_middlePanel__uDts7.GamePage_chatCollapsed__3pV19 > div.GamePage_mainPanel__2njyb > div > div:nth-child(1) > div > div.MarketplacePanel_tabsComponentContainer__3ctJH > div > div.TabsComponent_tabPanelsContainer__26mzo > div:nth-child(1) > div > div.MarketplacePanel_orderBook__326Yx > div.MarketplacePanel_orderBooksContainer__B4YE- > div:nth-child(2) > table > tbody > tr:nth-child(1) > td:nth-child(1)"); const remainingBuyValue = remainingBuyElement?.textContent?.trim() || '未找到'; // 计算差值 const remainingBuyNum = parseFloat(convertNumber(remainingBuyValue)) || 0; const difference = sum - remainingBuyNum; const formattedDifference = formatNumber(difference.toString()); // 计算平均速度 const timeDiff = currentTime - historyTime; const hoursDiff = timeDiff / (1000 * 60 * 60); const daysDiff = hoursDiff / 24; const avgPerHour = hoursDiff > 0 ? (difference / hoursDiff).toFixed(2) : '0'; const avgPerDay = daysDiff > 0 ? (difference / daysDiff).toFixed(2) : '0'; showAnalysisPanel(`差值: ${formattedDifference}\n时间差: ${hoursDiff.toFixed(2)}小时 (${daysDiff.toFixed(2)}天)\n平均每小时买入: ${avgPerHour}\n平均每天买入: ${avgPerDay}`); } } // 显示分析结果面板 function showAnalysisPanel(content) { // 移除已存在的分析面板 const existingPanel = document.getElementById('analysis-panel'); if (existingPanel) { existingPanel.remove(); } const analysisDiv = document.createElement('div'); analysisDiv.id = 'analysis-panel'; analysisDiv.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.9); padding: 20px; border-radius: 5px; z-index: 10001; font-family: Arial, sans-serif; color: white; box-shadow: 0 0 10px rgba(0,0,0,0.5); min-width: 320px; max-width: 480px; white-space: pre-line; line-height: 1.6; `; analysisDiv.innerHTML = content; // 添加关闭按钮 const closeButton = document.createElement('button'); closeButton.textContent = '×'; closeButton.style.cssText = ` position: absolute; top: 5px; right: 10px; border: none; background: none; color: #ccc; font-size: 18px; cursor: pointer; padding: 0 5px; `; closeButton.onmouseover = () => closeButton.style.color = '#fff'; closeButton.onmouseout = () => closeButton.style.color = '#ccc'; closeButton.onclick = () => analysisDiv.remove(); analysisDiv.appendChild(closeButton); document.body.appendChild(analysisDiv); } // 从当前显示的历史记录中获取时间 function getHistoryTimeFromRecord() { const historyDetail = document.querySelector('#history-detail'); if (!historyDetail) { return new Date(); } // 查找时间标题 const timeTitle = historyDetail.querySelector('h3'); if (timeTitle) { const timeText = timeTitle.textContent; // 提取时间部分(假设格式为 "日期 时间 [类型]") const timeMatch = timeText.match(/(\d{4}\/\d{1,2}\/\d{1,2}\s+\d{1,2}:\d{1,2}:\d{1,2})/); if (timeMatch) { return new Date(timeMatch[1]); } } // 如果无法解析,返回当前时间 return new Date(); } // 计算单元格的累计数量之和 function calculateSumForCell(targetCell) { const historyDetail = document.querySelector('#history-detail'); if (!historyDetail) { return 0; } // 判断当前单元格属于哪个列表 const parentRow = targetCell.parentElement; const isSellOrder = parentRow.hasAttribute('data-sell-index'); let sum = 0; let foundTarget = false; let startCounting = false; if (isSellOrder) { // 处理卖单列表 - 从第一个数量开始计算 const sellRows = historyDetail.querySelectorAll('tr[data-sell-index]'); for (const row of sellRows) { const cell = row.querySelector('td:first-child'); const cellValue = cell?.textContent?.trim(); if (cell === targetCell) { foundTarget = true; break; } // 从第一个数量开始计算 startCounting = true; if (startCounting && !foundTarget && cellValue) { const cleanValue = cellValue.replace(/[^\d.,]/g, ''); if (cleanValue) { const convertedValue = convertNumber(cellValue); const numValue = parseFloat(convertedValue); if (!isNaN(numValue)) { sum += numValue; } } } } } else { // 处理买单列表 - 从第一个数量开始计算 const buyRows = historyDetail.querySelectorAll('tr[data-buy-index]'); for (const row of buyRows) { const cell = row.querySelector('td:first-child'); const cellValue = cell?.textContent?.trim(); if (cell === targetCell) { foundTarget = true; break; } // 从第一个数量开始计算 startCounting = true; if (startCounting && !foundTarget && cellValue) { const cleanValue = cellValue.replace(/[^\d.,]/g, ''); if (cleanValue) { const convertedValue = convertNumber(cellValue); const numValue = parseFloat(convertedValue); if (!isNaN(numValue)) { sum += numValue; } } } } } return sum; } // 主函数 async function main() { // 等待市场界面加载 const marketSelector = "#root > div > div > div.GamePage_gamePanel__3uNKN"; await waitForElement(marketSelector); // 创建UI并开始监视物品数量元素 watchItemCount(); } // 启动脚本 main(); })();