MilkywayIdle 市场价格助手(自定义商品+可缩放+删除+移动端优化)

市场价格采集与管理工具(可缩放窗口,表头固定,倒计时采集防刷,表格自适应,商品可自定义添加删除,移动端优化)

// ==UserScript==
// @name         MilkywayIdle 市场价格助手(自定义商品+可缩放+删除+移动端优化)
// @namespace    http://tampermonkey.net/
// @version      5.1
// @description  市场价格采集与管理工具(可缩放窗口,表头固定,倒计时采集防刷,表格自适应,商品可自定义添加删除,移动端优化)
// @author       AI
// @match        https://www.milkywayidle.com/*
// @grant        GM_setClipboard
// @grant        GM_addStyle
// @license MIT
// ==/UserScript==

(function() {
'use strict';

// 常量定义
const CATEGORY_OPTIONS = ["资源", "消耗品", "技能书", "钥匙", "装备", "工具"];
const DEFAULT_PRODUCTS = [
    {name: "糖", category: "资源"}
];
const STORAGE_KEY = 'mw_idle_market_prices';
const PRODUCTS_KEY = 'mw_idle_market_products';
const POSITION_KEY = 'mw_market_helper_position';
const SIZE_KEY = 'mw_market_helper_size';
const COL_VIS_KEY = 'mw_market_helper_col_vis'; // 显示列
const COL_COPY_KEY = 'mw_market_helper_col_copy'; // 复制列
const ALL_COLUMNS = [
    {key: 'name', label: '商品', always: true},
    {key: 'category', label: '分类'},
    {key: 'left', label: '左1'},
    {key: 'right', label: '右1'},
    {key: 'leftAvailable', label: '待出'},
    {key: 'sellOrderCount', label: '出售订单数'},
    {key: 'rightAvailable', label: '待收'},
    {key: 'buyOrderCount', label: '收购订单数'},
    {key: 'ratio', label: '出收比'},
    {key: 'suggestion', label: '建议'},
    {key: 'actions', label: '删除', isAction: true}
];


// 状态变量
let lastMarketPageStatus = false;
let clickLock = false;
let clickCountdown = 0;
let clickCountdownTimer = null;

// 工具函数
function isMobile() {
    return /android|iphone|ipad|ipod|mobile/i.test(navigator.userAgent);
}



function getColVis() {
    try {
        const arr = JSON.parse(localStorage.getItem(COL_VIS_KEY));
        if (Array.isArray(arr) && arr.length) return arr;
    } catch(e){}
    // 默认全部显示(除了actions)
    return ALL_COLUMNS.filter(c => c.key !== 'actions').map(c => c.key);
}

    function getCooldownTime() {
    let t = Number(localStorage.getItem('mw_market_helper_cooldown_time'));
    if (isNaN(t) || t < 1) t = 1;
    return t;
}

function setColVis(arr) {
    localStorage.setItem(COL_VIS_KEY, JSON.stringify(arr));
}
function getColCopy() {
    try {
        const arr = JSON.parse(localStorage.getItem(COL_COPY_KEY));
        if (Array.isArray(arr) && arr.length) return arr;
    } catch(e){}
    // 默认只复制左1和右1
    return ['left','right'];
}
function setColCopy(arr) {
    localStorage.setItem(COL_COPY_KEY, JSON.stringify(arr));
}



function saveSize(w, h) {
    localStorage.setItem(SIZE_KEY, JSON.stringify({w, h}));
}

function loadSize() {
    try {
        const sz = localStorage.getItem(SIZE_KEY);
        return sz ? JSON.parse(sz) : null;
    } catch(e) {
        return null;
    }
}

function getSavedProducts() {
    try {
        const data = localStorage.getItem(PRODUCTS_KEY);
        if (data) {
            const arr = JSON.parse(data);
            if (Array.isArray(arr) && arr.length > 0 && arr[0].name && arr[0].category) {
                return arr;
            }
        }
    } catch(e) {}
    return [...DEFAULT_PRODUCTS];
}

function saveProducts(arr) {
    localStorage.setItem(PRODUCTS_KEY, JSON.stringify(arr));
}

function getSavedData() {
    const PRODUCTS = getSavedProducts();
    let obj = {};

    try {
        const data = localStorage.getItem(STORAGE_KEY);
        if (data) {
            obj = JSON.parse(data);
        }
    } catch(e) {}

    // 确保所有产品都有数据条目
    PRODUCTS.forEach(p => {
        if (!(p.name in obj)) {
            obj[p.name] = {
                left: '',
                right: '',
                leftAvailable: '',
                rightAvailable: '',
                sellOrderCount: 0,
                buyOrderCount: 0
            };
        }
    });

    // 删除不在产品列表中的数据
    Object.keys(obj).forEach(name => {
        if (!PRODUCTS.some(p => p.name === name)) {
            delete obj[name];
        }
    });

    saveData(obj);
    return obj;
}

function saveData(data) {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
}

function clearData() {
    localStorage.removeItem(STORAGE_KEY);
}

// UI相关函数
function showToast(msg, ms=2000) {
    const box = document.createElement("div");
    box.innerText = msg;
    box.style.position = "fixed";
    box.style.left = "50%";
    box.style.top = "30%";
    box.style.transform = "translate(-50%, -50%)";
    box.style.background = "#222";
    box.style.color = "#fff";
    box.style.padding = "18px 36px";
    box.style.fontSize = isMobile() ? "22px" : "20px";
    box.style.borderRadius = "14px";
    box.style.zIndex = 999999;
    box.style.boxShadow = "0 2px 16px #0008";
    box.style.opacity = "0";
    box.style.transition = "opacity 0.3s";
    document.body.appendChild(box);

    setTimeout(() => { box.style.opacity = "1"; }, 10);
    setTimeout(() => {
        box.style.opacity = "0";
        setTimeout(() => box.remove(), 300);
    }, ms);
}

// 检测是否在市场页面
function isMarketPage() {
    const panel = Array.from(document.querySelectorAll('div'))
        .find(div =>
            Array.from(div.classList).some(cls => cls.startsWith('MarketplacePanel_marketplacePanel__')) &&
            div.offsetParent !== null
        );
    const title = Array.from(document.querySelectorAll('h1'))
        .find(h1 =>
            Array.from(h1.classList).some(cls => cls.startsWith('MarketplacePanel_title__')) &&
            h1.textContent.trim().includes('市场') &&
            h1.offsetParent !== null
        );
    return !!(panel && title);
}

// 获取商品分类
function getMarketCategory(itemName) {
    const PRODUCTS = getSavedProducts();
    const item = PRODUCTS.find(p => p.name === itemName);
    return item ? item.category : "资源";
}

// 跳转到市场商品
function jumpToMarketItemWithCategory(itemName, callback) {
    const category = getMarketCategory(itemName);
    const tabBtn = Array.from(document.querySelectorAll('.MuiTabs-flexContainer [role="tab"]'))
        .find(el => el.textContent.trim().startsWith(category));

    if (!tabBtn) {
        showToast('未找到市场分类按钮: ' + category, 2500);
        if (callback) callback(false);
        return;
    }

    if (tabBtn.getAttribute("aria-selected") !== "true") {
        tabBtn.click();
    }

    setTimeout(() => {
        const marketList = document.querySelector('[class^="MarketplacePanel_itemSelectionTabsContainer__"]');
        let svgs = [];

        if (marketList) {
            svgs = Array.from(marketList.querySelectorAll('svg[aria-label]'));
        }

        let svg = svgs.find(el => el.getAttribute('aria-label').trim() === itemName.trim());
        if (!svg) {
            svg = svgs.find(el => el.getAttribute('aria-label').includes(itemName));
        }

        if (svg) {
            const clickable = svg.closest('.Item_item__2De2O');
            if (clickable) {
                clickable.scrollIntoView({behavior: "smooth", block: "center"});
                clickable.click();
                if (callback) callback(true);
                return;
            }
        }

        showToast('未找到该商品,请确认名称和分类!', 2000);
        if (callback) callback(false);
    }, 500);
}

// 数字格式化函数
function parseAmount(str) {
    str = str.replace(/,/g, '').trim().toLowerCase();
    if (str.endsWith('m')) return Math.round(parseFloat(str) * 1e6);
    if (str.endsWith('k')) return Math.round(parseFloat(str) * 1e3);
    return Math.round(parseFloat(str));
}

function formatAmount(n) {
    if (n === undefined || n === null || n === '') return '';
    n = Number(n);
    if (isNaN(n)) return '';
    if (n >= 1e9) return (n / 1e9).toFixed(2).replace(/\.00$/, '') + 'B';
    if (n >= 1e6) return (n / 1e6).toFixed(2).replace(/\.00$/, '') + 'M';
    if (n >= 1e4) return (n / 1e3).toFixed(2).replace(/\.00$/, '') + 'K';
    return n.toString();
}


function calcRatio(out, inn) {
    if (!out || !inn) return '';
    if (inn === 0) return '';
    const ratio = out / inn;
    return ratio >= 1 ? ratio.toFixed(2) : (ratio * 100).toFixed(1) + '%';
}

// 获取订单统计
function getOrderStats() {
    const tables = document.querySelectorAll('.MarketplacePanel_orderBookTableContainer__hUu-X table');
    if (tables.length < 2) {
        return {
            sell: {sum: 0, count: 0},
            buy: {sum: 0, count: 0}
        };
    }

    function sumTable(table) {
        const rows = table.querySelectorAll('tbody tr');
        let sum = 0;
        let count = 0;

        for (let row of rows) {
            const numTd = row.querySelector('td');
            if (!numTd) continue;

            const text = numTd.textContent.replace(/[^\d.kKmM]/g, '').trim();
            let value = 0;

            if (/^[\d.]+[kK]$/.test(text)) {
                value = parseFloat(text) * 1000;
            } else if (/^[\d.]+[mM]$/.test(text)) {
                value = parseFloat(text) * 1000000;
            } else {
                value = parseInt(text, 10);
            }

            if (!isNaN(value)) {
                sum += value;
                count++;
            }
        }

        return {sum, count};
    }

    return {
        sell: sumTable(tables[0]),
        buy: sumTable(tables[1])
    };
}

// 添加样式
GM_addStyle(`

#mw_market_helper {
    display: flex;
    flex-direction: column;
    background: #fff;
    border: 1.5px solid #6ad3fc;
    border-radius: 12px;
    box-shadow: 0 4px 18px rgba(0,0,0,0.10), 0 1px 4px #6ad3fc33;
    font-size: 14px;
    color: #222;
    position: fixed;
    z-index: 99999;
    user-select: none;
    overflow: hidden;
    box-sizing: border-box;
    min-width: 320px !important;
    min-height: 180px !important;
    max-width: 98vw !important;
    max-height: 98vh !important;
    resize: none !important;
    transition: box-shadow 0.18s;
}
#mw_market_helper_header {
    background: linear-gradient(90deg,#e0f7fa 0%,#b2ebf2 100%);
    border-radius: 12px 12px 0 0;
    border-bottom: 1px solid #b2ebf2;
    padding: 4px 12px;
    display: flex;
    align-items: center;
    font-weight: bold;
    cursor: grab;
    user-select: none;
    justify-content: space-between;
    font-size: 15px;
    flex: none;
}
#mw_market_helper_title_wrap {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-shrink: 0;
}
#mw_market_helper_countdown {
    font-size: 14px;
    color: #e74c3c;
    font-weight: bold;
    margin-left: 6px;
    min-width: 40px;
    display: inline-block;
}
#mw_market_helper_close {
    cursor: pointer;
    font-size: 18px;
    margin-left: auto;
    color: #d33;
    padding: 0 8px;
    border-radius: 6px;
    transition: background 0.2s;
}
#mw_market_helper_close:hover {
    background: #fdd;
}
#mw_market_helper_table_wrapper {
    flex: 1 1 0;
    min-height: 0;
     max-height: 100%;
    overflow-y: auto;
    overflow-x: auto;
    margin: 0 4px;
    position: relative;
}
#mw_market_helper table {
    border-collapse: collapse;
    width: 100%;
    background: #fff;
    font-size: 13px;
    table-layout: auto;
}
#mw_market_helper th, #mw_market_helper td {
    border: 1px solid #e0e0e0;
    padding: 4px 6px;
    text-align: center;
    word-break: break-all;
}
#mw_market_helper th {
    background: #f4fbfe;
    font-size: 13px;
    position: sticky;
    top: 0;
    z-index: 2;
    font-weight: 600;
    white-space: nowrap;
}

#mw_market_helper td {
    font-size: 13px;
}
#mw_market_helper_actions {
    flex: none;
    margin: 4px 4px 6px 4px;
    user-select: none;
    text-align: left;
}
#mw_market_helper_actions button {
    margin-left: 8px;
    margin-top: 0;
    padding: 3px 13px;
    font-size: 13px;
    border-radius: 6px;
    border: none;
    cursor: pointer;
    background: #6ad3fc;
    color: #fff;
    opacity: 1;
    transition: background 0.2s;
    box-shadow: 0 1px 2px #8be9fd33;
    display:inline-block;
}
#mw_market_helper_actions button:hover {
    background: #1e90ff;
}
#mw_market_helper_actions button:disabled {
    background: #bbb;
    color: #eee;
    opacity: 0.7;
    cursor: not-allowed;
}
#mw_market_helper_ball {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: linear-gradient(135deg,#6ad3fc 30%,#1e90ff 100%);
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-size: 24px;
    font-weight: bold;
    cursor: pointer;
    position: fixed;
    z-index: 2147483647 !important;
    box-shadow: 0 2px 12px #6ad3fc33, 0 1.5px 6px #1e90ff22;
    user-select: none;
    transition: background 0.2s;
    pointer-events: auto !important;
}

.mw_market_helper_link {
    text-decoration: none;
    font-weight: bold;
    border-radius: 4px;
    padding: 1px 6px;
    display: inline-block;
    min-width: 52px;
    font-size: 13px;
    transition: background 0.2s, color 0.2s;
    word-break: break-all;
    white-space: normal;
    line-height: 1.2;
}
.mw_market_helper_link.market-disabled {
    background: #e0e0e0 !important;
    color: #aaa !important;
    cursor: not-allowed !important;
    pointer-events: none !important;
    text-decoration: none;
    border: 1px solid #ccc;
}
.mw_market_helper_link.market-enabled {
    background: #2c7 !important;
    color: #fff !important;
    cursor: pointer !important;
    border: 1.5px solid #2c7;
}
.market-helper-del-btn {
    color: #e74c3c;
    background: none;
    border: none;
    cursor: pointer;
    font-size: 16px;
    padding: 0 4px;
    margin-left: 1px;
    vertical-align: middle;
}
.mw_market_helper_resize_handle:after {
    content: "";
    display: block;
    width: 100%;
    height: 100%;
    background: linear-gradient(135deg,transparent 60%,#6ad3fc 100%);
    border-radius: 4px;
}
::-webkit-scrollbar {width: 7px; background: #f7f7f7;}
::-webkit-scrollbar-thumb {background: #b2ebf2;}
body > .market-helper-fix {display:none!important;}
`);


// 启用拖动功能
function enableDrag(dragElem, moveElem) {
    if (isMobile()) return; // 移动端不启用拖动

    let isDragging = false;
    let offsetX = 0, offsetY = 0;

    dragElem.addEventListener('mousedown', function onMouseDown(e) {
        if (e.button !== 0) return;
        isDragging = true;
        const rect = moveElem.getBoundingClientRect();
        offsetX = e.clientX - rect.left;
        offsetY = e.clientY - rect.top;
        document.body.style.userSelect = "none";

        function onMouseMove(e) {
            if (!isDragging) return;

            let x = e.clientX - offsetX;
            let y = e.clientY - offsetY;
            const maxX = window.innerWidth - moveElem.offsetWidth;
            const maxY = window.innerHeight - moveElem.offsetHeight;

            x = Math.max(0, Math.min(x, maxX));
            y = Math.max(0, Math.min(y, maxY));

            moveElem.style.left = x + "px";
            moveElem.style.top = y + "px";
            moveElem.style.right = "";
            moveElem.style.bottom = "";
        }

        function onMouseUp() {
            isDragging = false;
            document.body.style.userSelect = "";
            window.removeEventListener('mousemove', onMouseMove);
            window.removeEventListener('mouseup', onMouseUp);
            // 只在拖动box时保存右下角坐标
            if (moveElem.id === 'mw_market_helper') {
                const rect = moveElem.getBoundingClientRect();
                let x = rect.left + rect.width;
                let y = rect.top + rect.height;
                localStorage.setItem(POSITION_KEY, JSON.stringify({x, y}));
                saveSize(moveElem.offsetWidth, moveElem.offsetHeight); // 新增
            }
             if (moveElem.id === 'mw_market_helper_ball') {
        localStorage.setItem('mw_market_helper_ball_pos', JSON.stringify({
            left: moveElem.style.left,
            top: moveElem.style.top
        }));
    }
        }

        window.addEventListener('mousemove', onMouseMove);
        window.addEventListener('mouseup', onMouseUp);
    });

    // 防止按钮等元素触发拖动
    Array.from(dragElem.querySelectorAll('button, input, a')).forEach(ele => {
        ele.addEventListener('mousedown', e => e.stopPropagation());
    });
}


// 设置市场链接状态
function setMarketLinksEnabled(enabled) {
    document.querySelectorAll('.mw_market_helper_link').forEach(link => {
        if (enabled) {
            link.classList.add('market-enabled');
            link.classList.remove('market-disabled');
            link.onclick = function() {
                if (clickLock) {
                    showToast(`请等待${clickCountdown}s后再次采集`, 1200);
                    return;
                }
                const itemName = this.getAttribute("data-itemname");
                collectPriceManual(itemName);
            };
        } else {
            link.classList.remove('market-enabled');
            link.classList.add('market-disabled');
            link.onclick = function(e) {
                e.preventDefault();
                showToast("请先进入市场界面!", 1800);
            };
        }
    });
}

// 倒计时显示
function showCountdown(sec) {
    const ct = document.getElementById('mw_market_helper_countdown');
    if (!ct) return;

    if (sec > 0) {
        ct.style.display = '';
        ct.textContent = `请等待 ${sec} 秒`;
    } else {
        ct.style.display = 'none';
    }
}

function getCellHighlight(val, his, highlight, isOrder) {
    // highlight: 是否高亮(价格/订单)
    // isOrder: true=订单数量相关,false=价格
    let cellStyle = '';
    let colorNote = '';
    if (!highlight || !his || typeof val === 'undefined' || val === '') return {cellStyle, colorNote};

    let durationMs = Date.now() - (his.streakStartTime || 0);
    // 价格列和订单列都用streakDirection判断
    if (his.streakDirection === "up") {
        if (durationMs > 30000) {
            cellStyle = 'background:#b2e0ff;';
            let duration = formatDuration(durationMs);
            colorNote = `/<span style="color:#333;">${duration}</span>`;
        } else {
            cellStyle = 'background:#fbb;';
        }
    } else if (his.streakDirection === "down") {
        if (durationMs > 30000) {
            cellStyle = 'background:#b2e0ff;';
            let duration = formatDuration(durationMs);
            colorNote = `/<span style="color:#333;">${duration}</span>`;
        } else {
            cellStyle = 'background:#c8f7c5;';
        }
    } else {
        // 首次采集或无变化方向,如果超时也变蓝
        if (durationMs > 30000) {
            cellStyle = 'background:#b2e0ff;';
            let duration = formatDuration(durationMs);
            colorNote = `/<span style="color:#333;">${duration}</span>`;
        } else {
            cellStyle = '';
        }
    }
    return {cellStyle, colorNote};
}



// 渲染表格
function renderTable(data) {
    const table = document.getElementById('mw_market_helper_table');
    if (!table) return;

    const PRODUCTS = getSavedProducts();
    const colVis = getColVis();
    const highlightPrice = localStorage.getItem('mw_market_helper_highlight_price') === '1';
    const highlightOrder = localStorage.getItem('mw_market_helper_highlight_order') === '1';

    // 构造表头
    let html = `<thead><tr>`;
    ALL_COLUMNS.forEach(col=>{
        if (col.key === 'name' || colVis.includes(col.key)) {
            html += `<th>${col.label}</th>`;
        }
    });
    html += `</tr></thead><tbody>`;

    PRODUCTS.forEach((p, idx) => {
        const d = data[p.name] || {
            left: '',
            right: '',
            leftAvailable: '',
            rightAvailable: '',
            sellOrderCount: 0,
            buyOrderCount: 0
        };

        // 计算衍生字段
        const ratio = calcRatio(d.leftAvailable, d.rightAvailable);
        let ratioNum = Number(ratio.replace('%', ''));
        let ratioBg = '';
        if (ratio !== '') {
            if (ratio.includes('%')) {
                if (ratioNum < 100) ratioBg = 'background:#c8f7c5;';
            } else if (ratioNum > 20) {
                ratioBg = 'background:#fbb;';
            }
        }
        let suggestion = '';
        if (ratio !== '') {
            if (ratio.includes('%')) {
                ratioNum = Number(ratio.replace('%',''));
                if (ratioNum < 100) suggestion = '易出售';
            } else {
                ratioNum = Number(ratio);
                if (ratioNum > 20) suggestion = '易收购';
            }
        }
        const delBtn = `<button class="market-helper-del-btn" title="删除" data-index="${idx}">✖</button>`;

        html += `<tr id="mw_market_helper_row_${encodeURIComponent(p.name)}">`;

        ALL_COLUMNS.forEach(col=>{
            if (col.key === 'name' || colVis.includes(col.key)) {
                if (col.key === 'name') {
                    html += `<td>
                        <a href="javascript:void(0);"
                        class="mw_market_helper_link market-disabled"
                        data-itemname="${p.name}">${p.name}</a>
                    </td>`;
                } else if (col.key === 'category') {
                    html += `<td>${p.category}</td>`;
                } else if (col.key === 'left' || col.key === 'right') {
                    let val = d[col.key];
                    let showVal = formatAmount(val);
                    let his = d[col.key + 'History'];
                    let {cellStyle, colorNote} = getCellHighlight(val, his, highlightPrice, false);
                    html += `<td style="${cellStyle}">${showVal}${colorNote}</td>`;
                } else if (
                    col.key === 'leftAvailable' ||
                    col.key === 'rightAvailable' ||
                    col.key === 'sellOrderCount' ||
                    col.key === 'buyOrderCount'
                ) {
                    let val = d[col.key];
                    let showVal = formatAmount(val);
                    let his = d[col.key + 'History'];
                    let {cellStyle, colorNote} = getCellHighlight(val, his, highlightOrder, true);
                    html += `<td style="${cellStyle}">${showVal}${colorNote}</td>`;
                } else if (col.key === 'ratio') {
                    html += `<td style="${ratioBg}">${ratio}</td>`;
                } else if (col.key === 'suggestion') {
                    html += `<td>${suggestion}</td>`;
                } else if (col.key === 'actions') {
                    html += `<td>${delBtn}</td>`;
                }
            }
        });
        html += `</tr>`;
    });

    html += `</tbody>`;
    table.innerHTML = html;

    // 删除按钮事件
    table.querySelectorAll('.market-helper-del-btn').forEach(btn => {
        btn.onclick = function() {
            const idx = Number(this.getAttribute('data-index'));
            const PRODUCTS = getSavedProducts();
            const delName = PRODUCTS[idx].name;

            // 如果只剩一个产品,提示用户不能删除所有产品
            if (PRODUCTS.length <= 1) {
                showToast("至少需要保留一个监控物品!", 2000);
                return;
            }

            if (!confirm(`确定要删除【${delName}】吗?`)) return;

            PRODUCTS.splice(idx, 1);
            saveProducts(PRODUCTS);

            const data = getSavedData();
            if (data[delName]) delete data[delName];
            saveData(data);

            renderTable(getSavedData()); // 删除后刷新全表
        };
    });

    // 右键商品名清除高亮
    table.querySelectorAll('.mw_market_helper_link').forEach(link => {
        link.oncontextmenu = function(e) {
            e.preventDefault();
            const itemName = this.getAttribute('data-itemname');
            const data = getSavedData();
            if (data[itemName]) {
                // 清除所有高亮历史
                ['left','right','leftAvailable','rightAvailable','sellOrderCount','buyOrderCount'].forEach(key=>{
                    if (data[itemName][key+'History']) {
                        delete data[itemName][key+'History'];
                    }
                });
                saveData(data);
                updateTableRow(itemName);
            }
        };
    });
}


function updateTableRow(itemName) {
    const data = getSavedData();
    const PRODUCTS = getSavedProducts();
    const colVis = getColVis();
    const highlightPrice = localStorage.getItem('mw_market_helper_highlight_price') === '1';
    const highlightOrder = localStorage.getItem('mw_market_helper_highlight_order') === '1';
    const p = PRODUCTS.find(pp=>pp.name===itemName);
    if (!p) return;
    const d = data[p.name] || {
        left: '',
        right: '',
        leftAvailable: '',
        rightAvailable: '',
        sellOrderCount: 0,
        buyOrderCount: 0
    };
    let html = '';
    // 计算衍生字段
    const ratio = calcRatio(d.leftAvailable, d.rightAvailable);
    let ratioNum = Number(ratio && ratio.replace('%', ''));
    let ratioBg = '';
    if (ratio !== '') {
        if (ratio.includes('%')) {
            if (ratioNum < 100) ratioBg = 'background:#c8f7c5;';
        } else if (ratioNum > 20) {
            ratioBg = 'background:#fbb;';
        }
    }
    let suggestion = '';
    if (ratio !== '') {
        if (ratio.includes('%')) {
            ratioNum = Number(ratio.replace('%',''));
            if (ratioNum < 100) suggestion = '易出售';
        } else {
            ratioNum = Number(ratio);
            if (ratioNum > 20) suggestion = '易收购';
        }
    }
    const delBtn = `<button class="market-helper-del-btn" title="删除" data-index="${PRODUCTS.findIndex(pp=>pp.name===itemName)}">✖</button>`;

    ALL_COLUMNS.forEach(col=>{
        if (col.key === 'name' || colVis.includes(col.key)) {
            if (col.key === 'name') {
                html += `<td>
                    <a href="javascript:void(0);"
                    class="mw_market_helper_link market-disabled"
                    data-itemname="${p.name}">${p.name}</a>
                </td>`;
            } else if (col.key === 'category') {
                html += `<td>${p.category}</td>`;
            } else if (col.key === 'left' || col.key === 'right') {
                let val = d[col.key];
                let showVal = formatAmount(val);
                let his = d[col.key + 'History'];
                let {cellStyle, colorNote} = getCellHighlight(val, his, highlightPrice, false);
                html += `<td style="${cellStyle}">${showVal}${colorNote}</td>`;
            } else if (
                col.key === 'leftAvailable' ||
                col.key === 'rightAvailable' ||
                col.key === 'sellOrderCount' ||
                col.key === 'buyOrderCount'
            ) {
                let val = d[col.key];
                let showVal = formatAmount(val);
                let his = d[col.key + 'History'];
                let {cellStyle, colorNote} = getCellHighlight(val, his, highlightOrder, true);
                html += `<td style="${cellStyle}">${showVal}${colorNote}</td>`;
            } else if (col.key === 'ratio') {
                html += `<td style="${ratioBg}">${ratio}</td>`;
            } else if (col.key === 'suggestion') {
                html += `<td>${suggestion}</td>`;
            } else if (col.key === 'actions') {
                html += `<td>${delBtn}</td>`;
            }
        }
    });
    const tr = document.getElementById(`mw_market_helper_row_${encodeURIComponent(itemName)}`);
    if (tr) tr.innerHTML = html;

    // 重新绑定删除按钮和右键事件
    if (tr) {
        tr.querySelectorAll('.market-helper-del-btn').forEach(btn => {
            btn.onclick = function() {
                const idx = Number(this.getAttribute('data-index'));
                const PRODUCTS = getSavedProducts();
                const delName = PRODUCTS[idx].name;
                if (PRODUCTS.length <= 1) {
                    showToast("至少需要保留一个监控物品!", 2000);
                    return;
                }
                if (!confirm(`确定要删除【${delName}】吗?`)) return;
                PRODUCTS.splice(idx, 1);
                saveProducts(PRODUCTS);
                const data = getSavedData();
                if (data[delName]) delete data[delName];
                saveData(data);
                renderTable(getSavedData());
            };
        });
        tr.querySelectorAll('.mw_market_helper_link').forEach(link => {
            link.oncontextmenu = function(e) {
                e.preventDefault();
                const itemName = this.getAttribute('data-itemname');
                const data = getSavedData();
                if (data[itemName]) {
                    ['left','right','leftAvailable','rightAvailable','sellOrderCount','buyOrderCount'].forEach(key=>{
                        if (data[itemName][key+'History']) {
                            delete data[itemName][key+'History'];
                        }
                    });
                    saveData(data);
                    updateTableRow(itemName);
                }
            };
        });
    }
}




// 复制表格数据
function doCopy(data) {
    const PRODUCTS = getSavedProducts();
    const colCopy = getColCopy();
    // 动态生成表头
    const header = ALL_COLUMNS.filter(c => !c.isAction && (colCopy.includes(c.key) || c.key === 'name')).map(c => c.label).join('\t');
    const arr = [header];

    PRODUCTS.forEach(p => {
        const d = data[p.name] || {
            left: '',
            right: '',
            leftAvailable: '',
            rightAvailable: '',
            sellOrderCount: 0,
            buyOrderCount: 0
        };

        const ratio = calcRatio(d.leftAvailable, d.rightAvailable);

        // 建议
        let suggestion = '';
        if (ratio !== '') {
            if (ratio.includes('%')) {
                const ratioNum = Number(ratio.replace('%',''));
                if (ratioNum < 100) suggestion = '易出售';
            } else {
                const ratioNum = Number(ratio);
                if (ratioNum > 20) suggestion = '易收购';
            }
        }

        // 动态生成每行数据
        const row = ALL_COLUMNS.filter(c => !c.isAction && (colCopy.includes(c.key) || c.key === 'name')).map(col => {
            if (col.key === 'name') return p.name;
            if (col.key === 'category') return p.category;
            if (col.key === 'left') return d.left;
            if (col.key === 'right') return d.right;
            if (col.key === 'leftAvailable') return d.leftAvailable;
            if (col.key === 'sellOrderCount') return d.sellOrderCount || 0;
            if (col.key === 'rightAvailable') return d.rightAvailable;
            if (col.key === 'buyOrderCount') return d.buyOrderCount || 0;
            if (col.key === 'ratio') return ratio;
            if (col.key === 'suggestion') return suggestion;
            return '';
        }).join('\t');
        arr.push(row);
    });

    const str = arr.join('\n');

    if (typeof GM_setClipboard !== 'undefined') {
        GM_setClipboard(str);
    } else {
        navigator.clipboard.writeText(str);
    }

    showToast("已复制到剪贴板!");
}

// 清除数据
function doClear() {
    clearData();
    renderTable(getSavedData());
}
function formatDuration(ms) {
    if (typeof ms !== "number" || ms < 0) return '';
    let s = Math.floor(ms / 1000);
    let m = Math.floor(s / 60); s %= 60;
    let h = Math.floor(m / 60); m %= 60;
    let d = Math.floor(h / 24); h %= 24;
    let arr = [];
    if (d) arr.push(d + 'd');
    if (h) arr.push(h + 'h');
    if (m) arr.push(m + 'm');
    if (s || arr.length === 0) arr.push(s + 's');
    // 只显示最大单位和次大单位
    return arr.slice(0,2).join('');
}


// 手动收集价格
function collectPriceManual(itemName) {
    if (!isMarketPage()) {
        showToast("请先进入市场界面!", 2000);
        return;
    }

    // 设置冷却时间
    clickLock = true;
    clickCountdown = getCooldownTime();
    showCountdown(clickCountdown);

    if (clickCountdownTimer) clearInterval(clickCountdownTimer);

    clickCountdownTimer = setInterval(() => {
        clickCountdown--;
        showCountdown(clickCountdown);

        if (clickCountdown <= 0) {
            clearInterval(clickCountdownTimer);
            clickLock = false;
            showCountdown(0);
        }
    }, 1000);

    // 跳转到商品并收集数据
    jumpToMarketItemWithCategory(itemName, async (found) => {
        if (!found) {
            showToast("未找到该商品,请确认名称和分类!", 2500);
            return;
        }

        const start = Date.now();
        let data = getSavedData();

        function tryCollectLoop() {
            data = getSavedData();
            const ok = tryCollect(data, itemName);

            if (ok) {
                const stats = getOrderStats();
                const now = Date.now();
                // 保存历史数据
                if (!data[itemName].leftHistory) data[itemName].leftHistory = {};
                if (!data[itemName].rightHistory) data[itemName].rightHistory = {};
                if (!data[itemName].leftAvailableHistory) data[itemName].leftAvailableHistory = {};
                if (!data[itemName].rightAvailableHistory) data[itemName].rightAvailableHistory = {};
                if (!data[itemName].sellOrderCountHistory) data[itemName].sellOrderCountHistory = {};
                if (!data[itemName].buyOrderCountHistory) data[itemName].buyOrderCountHistory = {};

['left','right','leftAvailable','rightAvailable','sellOrderCount','buyOrderCount'].forEach((key) => {
    let val;
    if (key === 'left') val = Number(data[itemName].left);
    else if (key === 'right') val = Number(data[itemName].right);
    else if (key === 'leftAvailable') val = Number(stats.sell.sum);
    else if (key === 'rightAvailable') val = Number(stats.buy.sum);
    else if (key === 'sellOrderCount') val = Number(stats.sell.count);
    else if (key === 'buyOrderCount') val = Number(stats.buy.count);

    let his = data[itemName][key+'History'] || {};

    if (typeof his.value === 'undefined') {
        his.value = val;
        his.streakStartValue = val;
        his.streakStartTime = now;
        his.streakDirection = undefined;
    } else if (his.value !== val) {
        his.streakDirection = (val > his.value) ? "up" : "down";
        his.value = val;
        his.streakStartValue = val;
        his.streakStartTime = now;
    }
    // 相同则不变
    data[itemName][key+'History'] = his;
    data[itemName][key] = val;
    console.log('采集价格:', data[itemName].left, data[itemName].right);
    console.log(`[${itemName}] ${key}: val=${val}, his.value=${his.value}, streakStartTime=${his.streakStartTime}, streakDirection=${his.streakDirection}`);


});



saveData(data);
updateTableRow(itemName);
setMarketLinksEnabled(true);
showToast("价格和订单数采集成功!", 1500);
return;
            }

            if (Date.now() - start > 5000) {
                showToast("5秒内未采集到价格,请重试!", 2500);
                return;
            }

            setTimeout(tryCollectLoop, 400);
        }

        tryCollectLoop();
    });
}


// 尝试采集价格
function tryCollect(data, itemName) {
    const panel = document.querySelector('[class^="MarketplacePanel_currentItem__"]');
    if (!panel) return false;

    const svg = panel.querySelector('svg[aria-label]');
    if (!svg) return false;

    const name = svg.getAttribute('aria-label');
    if (!name || name !== itemName) return false;

    if (!(itemName in data)) return false;

    const tableContainers = document.querySelectorAll('div[class^="MarketplacePanel_orderBookTableContainer__"]');
    if (tableContainers.length < 2) return false;

    const leftSpan = tableContainers[0].querySelector('table tbody tr:nth-child(1) td:nth-child(2) div span');
    const rightSpan = tableContainers[1].querySelector('table tbody tr:nth-child(1) td:nth-child(2) div span');

    const left = leftSpan ? leftSpan.textContent.trim() : '';
    const right = rightSpan ? rightSpan.textContent.trim() : '';

    if (left && right && !isNaN(parseAmount(left)) && !isNaN(parseAmount(right))) {
        data[itemName] = data[itemName] || {};
        data[itemName].left = parseAmount(left);
        data[itemName].right = parseAmount(right);
        saveData(data);
        return true;
    }


    return false;
}


    function isValidProductName(name) {
    // 不允许为空或全空格,不允许特殊字符,仅允许中英文、数字、下划线
    return /^[\u4e00-\u9fa5\w\d]+$/.test(name.trim());
}

// 显示添加产品对话框
function showAddProductDialog() {
    const overlay = document.createElement("div");
    overlay.style.position = "fixed";
    overlay.style.left = "0";
    overlay.style.top = "0";
    overlay.style.width = "100vw";
    overlay.style.height = "100vh";
    overlay.style.background = "rgba(0,0,0,0.18)";
    overlay.style.zIndex = 999999;
    overlay.onclick = () => { overlay.remove(); };

    const dialog = document.createElement("div");
    dialog.style.position = "fixed";
    dialog.style.left = "50%";
    dialog.style.top = "50%";
    dialog.style.transform = "translate(-50%,-50%)";
    dialog.style.background = "#fff";
    dialog.style.padding = "26px 38px";
    dialog.style.borderRadius = "16px";
    dialog.style.boxShadow = "0 2px 20px #0007";
    dialog.style.zIndex = 1000000;
    dialog.style.minWidth = "340px";
    dialog.onclick = e => e.stopPropagation();

    const html = `<div style="font-size:18px;margin-bottom:10px;">添加监控物品</div>
    <table id="add_product_table" style="width:100%;font-size:15px;">
        <tr>
            <th>物品名称</th><th>分类</th><th></th>
        </tr>
        <tr>
            <td><input type="text" style="width:100px;" /></td>
            <td>
                <select>
                    ${CATEGORY_OPTIONS.map(c=>`<option value="${c}">${c}</option>`).join('')}
                </select>
            </td>
            <td><button class="add_row_btn">+</button></td>
        </tr>
    </table>
    <div style="margin-top:14px;text-align:right;">
        <button id="add_product_ok" style="padding:4px 18px;font-size:16px;border-radius:6px;background:#2c7;color:#fff;border:none;">添加</button>
        <button id="add_product_cancel" style="padding:4px 18px;font-size:16px;border-radius:6px;background:#aaa;color:#fff;border:none;margin-left:8px;">取消</button>
    </div>`;

    dialog.innerHTML = html;
    overlay.appendChild(dialog);
    document.body.appendChild(overlay);

    // 添加行按钮事件
    dialog.querySelectorAll(".add_row_btn").forEach(btn => {
        btn.onclick = function() {
            const tr = document.createElement("tr");
            tr.innerHTML = `<td><input type="text" style="width:100px;" /></td>
            <td>
                <select>
                    ${CATEGORY_OPTIONS.map(c=>`<option value="${c}">${c}</option>`).join('')}
                </select>
            </td>
            <td><button class="del_row_btn">-</button></td>`;

            dialog.querySelector("#add_product_table").appendChild(tr);

            tr.querySelector(".del_row_btn").onclick = function() {
                tr.remove();
            };
        };
    });

    // 取消按钮事件
    dialog.querySelector("#add_product_cancel").onclick = function() {
        overlay.remove();
    };

    // 确认添加按钮事件
dialog.querySelector("#add_product_ok").onclick = function() {
    const rows = Array.from(dialog.querySelectorAll("#add_product_table tr")).slice(1);
    let added = 0;
    const PRODUCTS = getSavedProducts();

    for (let tr of rows) {
        const name = tr.querySelector("input").value.trim();
        const cat = tr.querySelector("select").value;

        if (!isValidProductName(name)) {
            showToast("物品名称不能为空,且只能包含中英文、数字、下划线!", 1800);
            return;
        }

        if (PRODUCTS.some(p => p.name === name)) {
            showToast("有重复物品名称: " + name, 1800);
            return;
        }
    }

    rows.forEach(tr => {
        const name = tr.querySelector("input").value.trim();
        const cat = tr.querySelector("select").value;
        if (isValidProductName(name) && !PRODUCTS.some(p => p.name === name)) {
            PRODUCTS.push({name, category: cat});
            added++;
        }
    });

    if (added > 0) {
        saveProducts(PRODUCTS);
        renderTable(getSavedData());
        showToast(`成功添加${added}个物品!`, 1800);
    } else {
        showToast("无新增物品或有重复!", 1800);
    }

    overlay.remove();
};

}

    // 显示设置对话框
function showSettingsDialog() {
    // 获取当前设置
    const colVis = getColVis();
    const colCopy = getColCopy();
    const highlightPrice = localStorage.getItem('mw_market_helper_highlight_price') === '1';
    const highlightOrder = localStorage.getItem('mw_market_helper_highlight_order') === '1';
    const cooldownTime = Number(localStorage.getItem('mw_market_helper_cooldown_time')) || 1;

    const overlay = document.createElement("div");
    overlay.style.position = "fixed";
    overlay.style.left = "0";
    overlay.style.top = "0";
    overlay.style.width = "100vw";
    overlay.style.height = "100vh";
    overlay.style.background = "rgba(0,0,0,0.18)";
    overlay.style.zIndex = 999999;
    overlay.onclick = () => { overlay.remove(); };

    const dialog = document.createElement("div");
    dialog.style.position = "fixed";
    dialog.style.left = "50%";
    dialog.style.top = "50%";
    dialog.style.transform = "translate(-50%,-50%)";
    dialog.style.background = "#fff";
    dialog.style.padding = "26px 38px";
    dialog.style.borderRadius = "16px";
    dialog.style.boxShadow = "0 2px 20px #0007";
    dialog.style.zIndex = 1000000;
    dialog.style.minWidth = "340px";
    dialog.onclick = e => e.stopPropagation();

    // 显示列设置
    let html = `<div style="font-size:18px;margin-bottom:10px;">表格显示列设置</div>
<div style="margin-bottom:10px;">
${ALL_COLUMNS.map(c => {
    if (c.key === 'name') {
        return `<label style="margin-right:16px;"><input type="checkbox" checked disabled> ${c.label}</label>`;
    }
    // 让删除列也可选
    return `<label style="margin-right:16px;">
        <input type="checkbox" class="col-vis" value="${c.key}" ${colVis.includes(c.key)?'checked':''}> ${c.label}
    </label>`;
}).join('')}
</div>
    <div style="font-size:18px;margin:12px 0 8px 0;">复制表格包含列</div>
    <div>
    ${ALL_COLUMNS.filter(c=>!c.isAction).map(c => {
        return `<label style="margin-right:16px;">
            <input type="checkbox" class="col-copy" value="${c.key}" ${colCopy.includes(c.key)?'checked':''}> ${c.label}
        </label>`;
    }).join('')}
    </div>
    <div style="margin:14px 0 8px 0;">
        <label style="margin-right:26px;">
            <input type="checkbox" id="highlight_price" ${highlightPrice ? 'checked' : ''}> 强调价格
        </label>
        <label>
            <input type="checkbox" id="highlight_order" ${highlightOrder ? 'checked' : ''}> 强调订单
        </label>
    </div>
    <div style="margin:10px 0 8px 0;">
    <label>
        采集冷却时间(秒)
        <input type="number" id="cooldown_time" min="1" max="60" value="${cooldownTime}" style="width:60px;margin-left:8px;">
    </label>
</div>

    <div style="margin-top:18px;text-align:right;">
        <button id="mw_settings_export" style="padding:4px 14px;font-size:15px;border-radius:6px;background:#6ad3fc;color:#fff;border:none;margin-right:8px;">导出设置</button>
        <button id="mw_settings_import" style="padding:4px 14px;font-size:15px;border-radius:6px;background:#6ad3fc;color:#fff;border:none;margin-right:24px;">导入设置</button>
        <button id="mw_settings_ok" style="padding:4px 18px;font-size:16px;border-radius:6px;background:#2c7;color:#fff;border:none;">确定</button>
        <button id="mw_settings_cancel" style="padding:4px 18px;font-size:16px;border-radius:6px;background:#aaa;color:#fff;border:none;margin-left:8px;">取消</button>
    </div>`;


    dialog.innerHTML = html;
    overlay.appendChild(dialog);
    document.body.appendChild(overlay);

    // 取消
    dialog.querySelector("#mw_settings_cancel").onclick = function() {
        overlay.remove();
    };

    // 确定
    dialog.querySelector("#mw_settings_ok").onclick = function() {
        // 显示列
        const vis = [];
        dialog.querySelectorAll('.col-vis').forEach(chk => {
            if (chk.checked) vis.push(chk.value);
        });
        setColVis(vis);

        // 复制列
        const cop = [];
        dialog.querySelectorAll('.col-copy').forEach(chk => {
            if (chk.checked) cop.push(chk.value);
        });
        setColCopy(cop);

        // 新增保存强调设置
        localStorage.setItem('mw_market_helper_highlight_price', dialog.querySelector('#highlight_price').checked ? '1' : '0');
        localStorage.setItem('mw_market_helper_highlight_order', dialog.querySelector('#highlight_order').checked ? '1' : '0');

        showToast("设置已保存!");
        overlay.remove();
        renderTable(getSavedData());
        const cooldown = parseInt(dialog.querySelector('#cooldown_time').value, 10);
if (isNaN(cooldown) || cooldown < 1) {
    localStorage.setItem('mw_market_helper_cooldown_time', '1');
} else {
    localStorage.setItem('mw_market_helper_cooldown_time', cooldown.toString());
}

    };

    // 导出设置
    dialog.querySelector("#mw_settings_export").onclick = function() {
        const products = getSavedProducts();
        const json = JSON.stringify(products, null, 2);
        // 弹窗展示,方便复制
        const exportBox = document.createElement("div");
        exportBox.style.position = "fixed";
        exportBox.style.left = "50%";
        exportBox.style.top = "50%";
        exportBox.style.transform = "translate(-50%,-50%)";
        exportBox.style.background = "#fff";
        exportBox.style.padding = "22px 18px";
        exportBox.style.borderRadius = "12px";
        exportBox.style.boxShadow = "0 2px 20px #0007";
        exportBox.style.zIndex = 1000001;
        exportBox.innerHTML = `
            <div style="font-size:16px;margin-bottom:8px;">请复制以下内容:</div>
            <textarea readonly style="width:400px;height:180px;font-size:14px;">${json}</textarea>
            <div style="margin-top:12px;text-align:right;">
                <button id="export_close" style="padding:4px 16px;font-size:15px;border-radius:6px;background:#aaa;color:#fff;border:none;">关闭</button>
            </div>
        `;
        document.body.appendChild(exportBox);
        exportBox.querySelector("#export_close").onclick = function() {
            exportBox.remove();
        };
    };

    // 导入设置
    dialog.querySelector("#mw_settings_import").onclick = function() {
        // 弹窗输入框
        const importBox = document.createElement("div");
        importBox.style.position = "fixed";
        importBox.style.left = "50%";
        importBox.style.top = "50%";
        importBox.style.transform = "translate(-50%,-50%)";
        importBox.style.background = "#fff";
        importBox.style.padding = "22px 18px";
        importBox.style.borderRadius = "12px";
        importBox.style.boxShadow = "0 2px 20px #0007";
        importBox.style.zIndex = 1000001;
        importBox.innerHTML = `
            <div style="font-size:16px;margin-bottom:8px;">请粘贴导入的商品设置(JSON):</div>
            <textarea style="width:400px;height:180px;font-size:14px;"></textarea>
            <div style="margin-top:12px;text-align:right;">
                <button id="import_ok" style="padding:4px 16px;font-size:15px;border-radius:6px;background:#2c7;color:#fff;border:none;">导入</button>
                <button id="import_close" style="padding:4px 16px;font-size:15px;border-radius:6px;background:#aaa;color:#fff;border:none;margin-left:8px;">取消</button>
            </div>
        `;
        document.body.appendChild(importBox);
        importBox.querySelector("#import_close").onclick = function() {
            importBox.remove();
        };
        importBox.querySelector("#import_ok").onclick = function() {
            const val = importBox.querySelector("textarea").value;
            let arr;
            try {
                arr = JSON.parse(val);
            } catch(e) {
                showToast("JSON格式错误!", 2000);
                return;
            }
            if (!Array.isArray(arr)) {
                showToast("导入内容不是商品数组!", 2000);
                return;
            }
            let PRODUCTS = getSavedProducts();
            let added = 0;
            arr.forEach(item => {
                if (
                    item &&
                    typeof item.name === 'string' &&
                    typeof item.category === 'string' &&
                    !PRODUCTS.some(p => p.name === item.name)
                ) {
                    PRODUCTS.push({name: item.name, category: item.category});
                    added++;
                }
            });
            if (added > 0) {
                saveProducts(PRODUCTS);
                renderTable(getSavedData());
                showToast(`成功导入${added}个新商品!`, 2000);
            } else {
                showToast("没有新商品被导入(同名已存在)!", 2000);
            }
            importBox.remove();
        };
    };
}

// 创建UI
function createUI() {
    // 主窗口
    const box = document.createElement('div');
    box.id = 'mw_market_helper';
    box.style.position = 'fixed';
    box.style.display = 'flex';
    box.style.flexDirection = 'column';

    // 设置窗口大小(只在首次创建时设置)
    const sz = loadSize();
    if (sz) {
        box.style.width = sz.w + "px";
        box.style.height = sz.h + "px";
    } else {
        box.style.width = "800px";
        box.style.height = "540px";
    }

    // 悬浮球
    const ball = document.createElement('div');
    const ballPos = (() => {
        try {
            return JSON.parse(localStorage.getItem('mw_market_helper_ball_pos'));
        } catch(e) { return null; }
    })();
    if (ballPos && ballPos.left && ballPos.top) {
        ball.style.left = ballPos.left;
        ball.style.top = ballPos.top;
        ball.style.right = '';
        ball.style.bottom = '';
        ball.style.position = 'fixed';
    }
    ball.id = 'mw_market_helper_ball';
    ball.style.position = 'fixed';
    ball.style.display = 'flex';
    ball.title = '双击展开市场价格助手';
    ball.innerHTML = '¥';
    ball.style.display = 'none';
    document.body.appendChild(ball);

    // 设置位置
    const pos = loadBoxRightBottom();
    if (pos) {
        // 让box右下角对齐到pos.x, pos.y
        const boxW = box.offsetWidth, boxH = box.offsetHeight;
        box.style.left = (pos.x - boxW) + 'px';
        box.style.top = (pos.y - boxH) + 'px';
        box.style.right = '';
        box.style.bottom = '';
        // 圆球圆心
        ball.style.left = (pos.x - ball.offsetWidth/2) + 'px';
        ball.style.top = (pos.y - ball.offsetHeight/2) + 'px';
        ball.style.right = '';
        ball.style.bottom = '';
        ball.style.position = 'fixed';
    } else {
        // 默认右下
        box.style.right = '100px';
        box.style.bottom = '100px';
        box.style.left = '';
        box.style.top = '';
        ball.style.right = '100px';
        ball.style.bottom = '100px';
        ball.style.left = '';
        ball.style.top = '';
        ball.style.position = 'fixed';
    }

    // 标题栏
    const header = document.createElement('div');
    header.id = 'mw_market_helper_header';
    header.innerHTML = `<span id="mw_market_helper_title_wrap">市场价格助手
        <span id="mw_market_helper_countdown" style="display:none"></span>
    </span>
    <span id="mw_market_helper_close" title="关闭">✕</span>`;
    box.appendChild(header);

    // 表格容器
    const tableWrapper = document.createElement('div');
    tableWrapper.id = "mw_market_helper_table_wrapper";
    tableWrapper.style.flex = "1 1 0";
    tableWrapper.style.minHeight = "0";
    tableWrapper.style.overflowY = "auto";
    tableWrapper.style.overflowX = "auto";

const table = document.createElement('table');
table.id = "mw_market_helper_table";
table.style.tableLayout = "auto";
table.style.width = "100%";
tableWrapper.appendChild(table);

box.appendChild(tableWrapper);




    // 按钮区域
    const actions = document.createElement('div');
    actions.id = "mw_market_helper_actions";
    actions.innerHTML = `
    <button id="mw_market_helper_clear">清空历史</button>
    <button id="mw_market_helper_copy">复制表格</button>
    <button id="mw_market_helper_add">添加物品</button>
    <button id="mw_market_helper_settings">设置</button>
`;

    box.appendChild(actions);

    // 添加右下角resize角标
    const resizeHandle = document.createElement('div');
    resizeHandle.className = 'mw_market_helper_resize_handle';
    resizeHandle.style.position = 'absolute';
    resizeHandle.style.width = '22px';
    resizeHandle.style.height = '22px';
    resizeHandle.style.right = '2px';
    resizeHandle.style.bottom = '2px';
    resizeHandle.style.cursor = 'nwse-resize';
    resizeHandle.style.zIndex = 10;
    resizeHandle.style.background = 'rgba(106,211,252,0.4)';
    resizeHandle.style.borderRadius = '4px';
    resizeHandle.title = '拖动改变窗口大小';

    box.appendChild(resizeHandle);

    // 右下角拖动改变窗口大小
    resizeHandle.addEventListener('mousedown', function(e) {
        e.preventDefault();
        e.stopPropagation();
        let startX = e.clientX, startY = e.clientY;
        let startW = box.offsetWidth, startH = box.offsetHeight;

        function onMouseMove(e) {
            let newW = startW + (e.clientX - startX);
            let newH = startH + (e.clientY - startY);
            newW = Math.max(400, Math.min(newW, window.innerWidth - box.offsetLeft - 10));
            newH = Math.max(250, Math.min(newH, window.innerHeight - box.offsetTop - 10));
            box.style.width = newW + "px";
            box.style.height = newH + "px";
        }

        function onMouseUp() {
            // 保存右下角坐标
            const rect = box.getBoundingClientRect();
            let x = rect.left + rect.width;
            let y = rect.top + rect.height;
            saveBoxRightBottom(x, y);
            saveSize(box.offsetWidth, box.offsetHeight); // 新增
            document.removeEventListener('mousemove', onMouseMove);
            document.removeEventListener('mouseup', onMouseUp);
        }

        document.addEventListener('mousemove', onMouseMove);
        document.addEventListener('mouseup', onMouseUp);
    });

    document.body.appendChild(box);

    // 关闭按钮事件
    document.getElementById('mw_market_helper_close').onclick = function(e) {
        e.stopPropagation();
        // 先读取 box 的位置信息
        const rect = box.getBoundingClientRect();
        let x = rect.left + rect.width;
        let y = rect.top + rect.height;
        // 隐藏 box
        box.style.display = 'none';
        // 保证小球不会超出窗口
        x = Math.min(x, window.innerWidth - ball.offsetWidth/2);
        y = Math.min(y, window.innerHeight - ball.offsetHeight/2);
        x = Math.max(ball.offsetWidth/2, x);
        y = Math.max(ball.offsetHeight/2, y);
        // 定位圆球
        ball.style.left = x + "px";
        ball.style.top = y + "px";
        ball.style.right = '';
        ball.style.bottom = '';
        ball.style.position = 'fixed';
        ball.style.display = 'flex';
        // 存储圆球圆心(即box右下角)
        saveBoxRightBottom(x, y);
    };

    // 悬浮球双击事件
    ball.addEventListener('dblclick', function(e) {
        e.stopPropagation();
        if (box.style.display !== 'none' && box.style.display !== '') return;
        box.style.display = ''; // 先显示

        // 不再强制设置宽高,让其保持上次保存的宽高
        // 仅在首次无记录时设置默认宽高
        const sz = loadSize();
        if (!sz) {
            let boxW = Math.max(900, Math.floor(window.innerWidth * 0.8));
            let boxH = Math.max(400, Math.floor(window.innerHeight * 0.7));
            box.style.width = boxW + "px";
            box.style.height = boxH + "px";
        }

        // 让box右下角对齐到圆球圆心
        const bx = ball.offsetLeft;
        const by = ball.offsetTop;
        const rect = box.getBoundingClientRect();
        box.style.left = (bx - rect.width) + "px";
        box.style.top = (by - rect.height) + "px";
        box.style.right = '';
        box.style.bottom = '';
        ball.style.display = 'none';
    });

    // 启用拖动
    enableDrag(header, box); // 只允许header拖动box
    enableDrag(ball, ball); // 允许拖动小球本身

    // 监听窗口缩放
    let lastW = box.offsetWidth, lastH = box.offsetHeight;
    setInterval(() => {
        if (box.offsetWidth !== lastW || box.offsetHeight !== lastH) {
            lastW = box.offsetWidth;
            lastH = box.offsetHeight;
            // 保存右下角坐标
            const rect = box.getBoundingClientRect();
            let x = rect.left + rect.width;
            let y = rect.top + rect.height;
            saveBoxRightBottom(x, y);
            saveSize(box.offsetWidth, box.offsetHeight); // 新增:防止窗口被其他方式调整大小
        }
    }, 1000);

    // 初始显示状态
    box.style.display = 'none';
    ball.style.display = 'flex';
    // 保证初始位置小球在视窗内
    if (ball.offsetLeft < 0) ball.style.left = '10px';
    if (ball.offsetTop < 0) ball.style.top = '10px';
}


function saveBoxRightBottom(x, y) {
    localStorage.setItem(POSITION_KEY, JSON.stringify({x, y}));
}
function loadBoxRightBottom() {
    try {
        const pos = localStorage.getItem(POSITION_KEY);
        return pos ? JSON.parse(pos) : null;
    } catch(e) { return null; }
}

// 初始化
function init() {
    createUI();
    renderTable(getSavedData());

    // 按钮事件绑定
    document.getElementById('mw_market_helper_clear').onclick = doClear;
    document.getElementById('mw_market_helper_copy').onclick = function() {
        doCopy(getSavedData());
    };
    document.getElementById('mw_market_helper_add').onclick = showAddProductDialog;
    document.getElementById('mw_market_helper_settings').onclick = showSettingsDialog;

    // 定期检查市场状态
setInterval(() => {
    const nowMarket = isMarketPage();
    setMarketLinksEnabled(nowMarket);
    if (nowMarket !== lastMarketPageStatus) {
        lastMarketPageStatus = nowMarket;
    }
}, 1000);

const observer = new MutationObserver(() => {
    const nowMarket = isMarketPage();
    setMarketLinksEnabled(nowMarket);
    if (nowMarket !== lastMarketPageStatus) {
        lastMarketPageStatus = nowMarket;
    }
});

// 可以只监听大的 DOM 变化,减少频率
observer.observe(document.body, { childList: true, subtree: false });

}

// 启动脚本
init();

})();
长期地址
遇到问题?请前往 GitHub 提 Issues。