// ==UserScript==
// @name MWI Market List History
// @name:zh-CN [银河奶牛]市场挂单记录
// @namespace http://tampermonkey.net/
// @version 0.1.10
// @description 记录 Milky Way Idle 游戏的市场历史数据
// @description:zh-CN 记录 Milky Way Idle 游戏的市场历史数据
// @author deirc
// @license MIT
// @match *www.milkywayidle.com/game*
// @downloadURL none
// ==/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 > 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 > 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 > div.GamePage_mainPanel__2njyb > div > div:nth-child(1) > div > div.MarketplacePanel_tabsComponentContainer__3ctJH";
let isMarketOpen = false;
let lastItemIcon = null;
// 新增:为市场导航第二个按钮添加点击事件
function addMarketNavButtonListener() {
const navContainer = document.querySelector(itemCountSelector);
if (navContainer) {
const btn = navContainer.querySelector('button:nth-child(2)');
if (btn && !btn._mwi_market_listener) {
btn.addEventListener('click', () => {
recordMarketData('manual');
});
btn._mwi_market_listener = true;
}
}
}
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');
// 新增:每次出现时都尝试绑定一次
addMarketNavButtonListener();
} 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');
}
// 新增:初始时也尝试绑定一次
addMarketNavButtonListener();
}
}
// 记录市场数据
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] = [];
}
// 检查上一条手动保存的时间
if (readType === 'auto') {
const historyArr = allHistory[itemName];
// 倒序查找最近的手动保存
let lastManual = null;
for (let i = historyArr.length - 1; i >= 0; i--) {
if (historyArr[i].readType === 'manual') {
lastManual = historyArr[i];
break;
}
}
// 如果没有任何手动保存数据,也标记为'manual'
if (!lastManual) {
readType = 'manual';
} else {
const lastManualTime = new Date(lastManual.timestamp).getTime();
const nowTime = new Date(currentTime).getTime();
if ((nowTime - lastManualTime) > 15 * 60 * 1000) {
readType = 'manual';
}
}
}
// 添加新记录
allHistory[itemName].push({
timestamp: currentTime,
data: currentData,
readType: readType // 添加读取类型标记
});
// 清理旧数据
allHistory = cleanupOldData(allHistory);
// 保存数据
if (!saveData(allHistory)) {
return; // 如果保存失败,直接返回
}
// 更新状态显示
updateStatusDisplay(itemName, currentData, readType);
// 如果是手动保存并且历史面板存在,则刷新历史面板
if (readType === 'manual') {
const historyPanel = document.getElementById('market-history-panel');
if (historyPanel) {
showItemHistory(itemName, historyPanel);
}
}
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 > 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 > 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 rawQuantity = columns[0]?.textContent?.trim() || '';
const rawPrice = columns[1]?.textContent?.trim() || '';
const cleanQuantity = rawQuantity.replace(/[^\da-zA-Z]/g, '');
const cleanPrice = rawPrice.replace(/[^\da-zA-Z]/g, '');
const itemData = {
quantity: convertNumber(cleanQuantity),
price: convertNumber(cleanPrice),
};
marketData.buyOrders.push(itemData);
}
});
}
// 处理卖单数据
if (sellOrderRows) {
sellOrderRows.forEach(row => {
const columns = row.querySelectorAll('td');
if (columns.length >= 2) {
// 剔除非数字和字母
const rawQuantity = columns[0]?.textContent?.trim() || '';
const rawPrice = columns[1]?.textContent?.trim() || '';
const cleanQuantity = rawQuantity.replace(/[^\da-zA-Z]/g, '');
const cleanPrice = rawPrice.replace(/[^\da-zA-Z]/g, '');
const itemData = {
quantity: convertNumber(cleanQuantity),
price: convertNumber(cleanPrice),
};
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 = text('readCurrent');
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 = text('readHistory');
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 = `${text('lastUpdate')}: ${now} | ${itemName} (${buyCount}${text('buy')}, ${sellCount}${text('sell')}) [${readTypeText}]`;
}
}
// 显示当前数据
function showCurrentData() {
const result = recordMarketData('manual');
if (!result) {
alert(text('noData'));
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 oldPanel = document.getElementById('market-history-panel');
if (oldPanel) oldPanel.remove();
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 = `
${text('marketHistory')}
`;
if (Object.keys(allHistory).length === 0) {
html += `${text('noHistory')}
`;
} else {
// 创建顶部控制栏
html += `
`;
// 创建历史记录列表和数据显示区域
html += ``;
}
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(
text('confirmCleanup'),
() => {
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 > 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 > 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(`${text('sell2')}: ${sellOrderValue}\n${text('buy2')}: ${buyOrderValue}\n${text('highlight')}`, true);
};
// 设置按钮事件
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' ? text('auto') : text('manual');
const isManual = record.readType === 'manual';
listHtml += `
${date.toLocaleDateString()}
${date.toLocaleTimeString()}
[${readTypeText}]
`;
});
if (listHtml === '') {
historyList.innerHTML = `${text('noHistory')}
`;
historyDetail.innerHTML = `${text('noHistory')}
`;
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(
text('confirmDelete'),
() => {
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 = `
${new Date(record.timestamp).toLocaleString()}
[${record.readType === 'auto' ? text('auto') : text('manual')}]
${record.readType === 'auto' ? `
` : ''}
`;
// 创建横向布局容器
html += '';
// 卖单数据(左侧)
html += '
';
html += `
${text('sell')}
`;
if (record.data.sellOrders && record.data.sellOrders.length > 0) {
html += '
';
html += `${text('quantity')} | ${text('price')} |
`;
record.data.sellOrders.forEach((item, index) => {
html += `
${formatNumber(item.quantity)} |
${formatNumber(item.price)} |
`;
});
html += '
';
} else {
html += `
${text('noData')}
`;
}
html += '
';
// 买单数据(右侧)
html += '
';
html += `
${text('buy')}
`;
if (record.data.buyOrders && record.data.buyOrders.length > 0) {
html += '
';
html += `${text('quantity')} | ${text('price')} |
`;
record.data.buyOrders.forEach((item, index) => {
html += `
${formatNumber(item.quantity)} |
${formatNumber(item.price)} |
`;
});
html += '
';
} else {
html += `
${text('noData')}
`;
}
html += '
';
// 关闭横向布局容器
html += '
';
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 = `${itemName}
`;
records.forEach(record => {
const readTypeText = record.readType === 'auto' ? '自动读取' : '手动读取';
html += `${new Date(record.timestamp).toLocaleString()} [${readTypeText}]
`;
// 显示卖单数据
if (record.data.sellOrders && record.data.sellOrders.length > 0) {
html += '卖单
';
html += '';
html += '数量 | 价格 |
';
record.data.sellOrders.forEach(item => {
html += `
${item.quantity} |
${item.price} |
`;
});
html += '
';
}
// 显示买单数据
if (record.data.buyOrders && record.data.buyOrders.length > 0) {
html += '买单
';
html += '';
html += '数量 | 价格 |
';
record.data.buyOrders.forEach(item => {
html += `
${item.quantity} |
${item.price} |
`;
});
html += '
';
}
});
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 = `
${text('settingsTitle')}
▼
▼
${text('manualCleanupDesc')}
`;
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(
text('confirmManualCleanup', 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 = text('confirm');
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 = text('cancel');
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 = `
数据对比 - ${itemName}
当前数据
${new Date(currentRecord.timestamp).toLocaleString()}
卖单
${currentRecord.data.sellOrders && currentRecord.data.sellOrders.length > 0 ? `
数量 |
价格 |
${currentRecord.data.sellOrders.map(item => `
${formatNumber(item.quantity)} |
${formatNumber(item.price)} |
`).join('')}
` : `
${text('noData')}
`}
买单
${currentRecord.data.buyOrders && currentRecord.data.buyOrders.length > 0 ? `
数量 |
价格 |
${currentRecord.data.buyOrders.map(item => `
${formatNumber(item.quantity)} |
${formatNumber(item.price)} |
`).join('')}
` : `
${text('noData')}
`}
`;
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);
// 不再移除已有高亮和按钮
// 如果数值匹配,添加高亮和按钮
if (convertedCellValue === convertedSellValue) {
cell.style.background = '#ffeb3b';
cell.style.color = '#000';
cell.style.fontWeight = 'bold';
// 检查是否已存在按钮,避免重复添加
if (!cell.querySelector('.highlight-button')) {
// 创建按钮
const button = document.createElement('button');
button.textContent = text('calc');
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);
// 不再移除已有高亮和按钮
// 如果数值匹配,添加高亮和按钮
if (convertedCellValue === convertedBuyValue) {
cell.style.background = '#ffeb3b';
cell.style.color = '#000';
cell.style.fontWeight = 'bold';
// 检查是否已存在按钮,避免重复添加
if (!cell.querySelector('.highlight-button')) {
// 创建按钮
const button = document.createElement('button');
button.textContent = text('calc');
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 > 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 = difference.toLocaleString('zh-CN');
// 计算平均速度
const timeDiff = currentTime - historyTime;
const hoursDiff = timeDiff / (1000 * 60 * 60);
const daysDiff = hoursDiff / 24;
// 新增:千分符且无小数
const avgPerHour = hoursDiff > 0 ? Math.floor(difference / hoursDiff) : 0;
const avgPerDay = daysDiff > 0 ? Math.floor(difference / daysDiff) : 0;
const avgPerHourStr = avgPerHour.toLocaleString('zh-CN');
const avgPerDayStr = avgPerDay.toLocaleString('zh-CN');
showAnalysisPanel(`${text('diff')}: ${formattedDifference}\n${text('timeDiff')}: ${hoursDiff.toFixed(2)}h (${daysDiff.toFixed(2)}d)\n${text('avgHourSell')}: ${avgPerHourStr}\n${text('avgDaySell')}: ${avgPerDayStr}`, false);
} else {
// 右侧按钮:只读取残余买单数
const remainingBuyElement = document.querySelector("#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_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 = difference.toLocaleString('zh-CN');
// 计算平均速度
const timeDiff = currentTime - historyTime;
const hoursDiff = timeDiff / (1000 * 60 * 60);
const daysDiff = hoursDiff / 24;
// 新增:千分符且无小数
const avgPerHour = hoursDiff > 0 ? Math.floor(difference / hoursDiff) : 0;
const avgPerDay = daysDiff > 0 ? Math.floor(difference / daysDiff) : 0;
const avgPerHourStr = avgPerHour.toLocaleString('zh-CN');
const avgPerDayStr = avgPerDay.toLocaleString('zh-CN');
showAnalysisPanel(`${text('diff')}: ${formattedDifference}\n${text('timeDiff')}: ${hoursDiff.toFixed(2)}h (${daysDiff.toFixed(2)}d)\n${text('avgHourBuy')}: ${avgPerHourStr}\n${text('avgDayBuy')}: ${avgPerDayStr}`, false);
}
}
// 显示分析结果面板
function showAnalysisPanel(content, autoClose = false) {
// 移除已存在的分析面板
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: 15%; /* 向上移动 */
left: 50%;
transform: translate(-50%, 0); /* 只水平居中,不再垂直居中 */
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);
// 仅对 autoClose 为 true 时自动关闭
if (autoClose) {
setTimeout(() => {
analysisDiv.remove();
}, 5000);
}
}
// 从当前显示的历史记录中获取时间
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();
// 多语言支持
function getLang() {
// 优先根据内容判断是否为中文
const titleEl = document.querySelector('head > title');
if (titleEl && /[\u4e00-\u9fa5]/.test(titleEl.textContent)) {
return 'zh';
}
let lang = '';
const htmlEl = document.querySelector('html');
if (htmlEl && htmlEl.lang) {
lang = htmlEl.lang.toLowerCase();
} else {
lang = (navigator.language || navigator.userLanguage || '').toLowerCase();
}
if (lang.startsWith('zh')) return 'zh';
return 'en';
}
const LANG = getLang();
const TEXT = {
zh: {
readCurrent: '读取当前数据',
readHistory: '读取历史数据',
compare: '对比',
cleanup: '清理自动保存数据',
settings: '⚙',
lastUpdate: '最后更新',
manual: '手动读取',
auto: '自动读取',
noHistory: '暂无历史数据',
marketHistory: '市场历史记录',
confirmCleanup: '确定要清理所有自动保存的数据吗?此操作不可恢复。',
confirmDelete: '确定要删除这条记录吗?此操作不可恢复。',
confirmManualCleanup: days => `确定要删除 ${days} 天前手动保存的所有数据吗?此操作不可恢复。`,
diff: '差值',
timeDiff: '时间差',
avgHourSell: '平均每小时卖出',
avgDaySell: '平均每天卖出',
avgHourBuy: '平均每小时买入',
avgDayBuy: '平均每天买入',
sell2: '第二个卖单数',
buy2: '第二个买单数',
highlight: '已高亮显示匹配的数值',
close: '关闭',
cancel: '取消',
confirm: '确认',
save: '保存',
delete: '删除',
settingsTitle: '设置',
autoRead: '启用自动读取',
autoReadDesc: '切换物品时自动记录数据',
retention: '自动保存数据保留时间',
manualCleanup: '删除历史手动保存数据',
manualCleanupDesc: '删除指定天数前的手动保存数据,此操作不可恢复',
select: '请选择左侧的历史记录查看详情',
noData: '无数据',
sell: '卖单',
buy: '买单',
quantity: '数量',
price: '价格',
daysAgoN: n => `${n}天前`,
calc: '计算',
},
en: {
readCurrent: 'Read Current Data',
readHistory: 'Read History',
compare: 'Compare',
cleanup: 'Cleanup Auto Data',
settings: '⚙',
lastUpdate: 'Last Update',
manual: 'Manual',
auto: 'Auto',
noHistory: 'No history data',
marketHistory: 'Market History',
confirmCleanup: 'Are you sure to cleanup all auto-saved data? This action cannot be undone.',
confirmDelete: 'Are you sure to delete this record? This action cannot be undone.',
confirmManualCleanup: days => `Are you sure to delete all manual data before ${days} days ago? This action cannot be undone.`,
diff: 'Diff',
timeDiff: 'Time Diff',
avgHourSell: 'Avg Sell/hour',
avgDaySell: 'Avg Sell/day',
avgHourBuy: 'Avg Buy/hour',
avgDayBuy: 'Avg Buy/day',
sell2: '2nd Sell Qty',
buy2: '2nd Buy Qty',
highlight: 'Highlighted matching values',
close: 'Close',
cancel: 'Cancel',
confirm: 'Confirm',
save: 'Save',
delete: 'Delete',
settingsTitle: 'Settings',
autoRead: 'Enable Auto Read',
autoReadDesc: 'Automatically record data when switching items',
retention: 'Auto Data Retention',
manualCleanup: 'Delete Manual Data',
manualCleanupDesc: 'Delete all manual data before specified days, cannot be undone',
select: 'Please select a record on the left to view details',
noData: 'No data',
sell: 'Sell',
buy: 'Buy',
quantity: 'Quantity',
price: 'Price',
daysAgoN: n => `${n} days ago`,
calc: 'Calc',
}
};
// 在TEXT对象定义后添加:
function text(key, lang) {
lang = lang || LANG;
const val = TEXT[lang][key];
if (typeof val === 'function') {
return (...args) => TEXT[lang][key](...args);
}
return val !== undefined ? val : key;
}
})();