挂刀网优化

优化界面,优化表格价格列展示信息,增加数据更新时间;支持配置首次查询参数

目前为 2024-10-10 提交的版本,查看 最新版本

// ==UserScript==
// @name         挂刀网优化
// @namespace    https://gf.qytechs.cn/users/1362311
// @version      1.0.0
// @description  优化界面,优化表格价格列展示信息,增加数据更新时间;支持配置首次查询参数
// @author       honguangli
// @license      MIT
// @match        https://www.hangknife.com/
// @icon         https://www.hangknife.com/static/imgs/logo_home_black.png
// @run-at       document-end
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    const routerHash = '#/Home'; // 需要开启优化的页面
    let state = true; // 是否默认开启优化

    const updateDurationMinutes = 30; // 期望更新时间间隔,单位为分钟,当前时间与更新时间差值小于此配置时会标黄色

    // csgo配置
    const csgoOption = {
        name: 'csgo',
        selector: '#pane-csgo .el-table', // 表格选择器
        priceColumnIndex: 15, // 价格列下标
        search: true, // 是否自定义查询
        searchParam: {
            orderBy: 'minPrice / steamSafeBuyerPrice,ascending', // 排序方式
            minPrice: '1', // 最低价
            maxPrice: '', // 最高价
            last24Volume: '', // 成交数
            platform: ["uupin"], // 交易平台
        },
        minWidth: 190, // 表格价格列最小宽度
        _isInit: false, // 是否已初始化
        _isSearch: false, // 是否已触发自定义查询
        _width: undefined, // 表格价格列宽度,缓存初始值,用于恢复默认样式
        _minWidth: undefined, // 表格价格列最小宽度,缓存初始值,用于恢复默认样式
    };

    // dota2配置
    const dota2Option = {
        name: 'dota2',
        selector: '#pane-dota2 .el-table', // 表格选择器
        priceColumnIndex: 9, // 价格列下标
        search: true, // 是否自定义查询,仅执行一次
        searchParam: {
            orderBy: 'minPrice / steamSafeBuyerPrice,ascending', // 排序方式
            minPrice: '1', // 最低价
            maxPrice: '', // 最高价
            last24Volume: '', // 成交数
            platform: ["buff"], // 交易平台
        },
        minWidth: 190, // 表格价格列最小宽度
        _isInit: false, // 是否已初始化
        _isSearch: false, // 是否已触发自定义查询
        _width: undefined, // 表格价格列宽度,缓存初始值,用于恢复默认样式
        _minWidth: undefined, // 表格价格列最小宽度,缓存初始值,用于恢复默认样式
    };

    // csgo 查询参数
    // 交易平台可选项
    // ['buff', 'dmarket', 'c5game', 'skinPort', 'igxe', 'uupin', 'v5Item', 'csMoney', 'waxpeer', 'eco', 'bitSkins', 'haloskins']
    // 排序方式可选项
    // {value: 'minPrice / steamSellerPrice,ascending', label: '最优寄售'}
    // {value: 'minPrice / steamBuyerPrice,ascending', label: '最优求购'}
    // {value: 'minPrice / steamSafeBuyerPrice,ascending', label: '稳定成交'}
    // {value: 'minPrice,ascending', label: i18n.$t('sortMinPrice')}
    // {value: 'minPrice / steamSellerPrice,descending', label: '第三方寄售价降序'}
    // {value: '(buffBuyOrderPrice - minPrice)/minPrice,descending', label: '低于Buff求购'}
    // {value: '(uupinBuyerPrice - minPrice)/minPrice,descending', label: '低于UU求购'}
    // {value: '(dmarketOrderPrice - minPrice)/minPrice,descending', label: '低于DMarket求购'}

    // 隐藏原生的折扣内容
    // 通过改变DOM属性实现
    GM_addStyle('tbody > tr > td:last-child > div.cell[data-optimize="true"] > div:not([data-type="optimize"]) { display: none; }');

    // 优化成交数量输入框尺寸
    GM_addStyle('.el-tabs .el-tab-pane > div > div > div > div:nth-child(2) > div:nth-child(2) > div:first-child > div:first-child { width: 185px !important; }');
    GM_addStyle('.el-tabs .el-tab-pane > div > div > div > div:nth-child(2) > div:nth-child(2) > div:first-child > div:first-child > div:first-child { padding: 0 12px; }');

    let btnOptimize; // 优化按钮

    // 监听页面变化
    // 匹配路由成功后插入优化按钮
    const observerApp = () => {
        // 获取app节点
        const domApp = document.getElementById('app');
        const app = domApp.__vue__;

        // 开启监听
        const observer = new MutationObserver(() => {
            // 等待DOM刷新
            app.$nextTick(() => {
                if (location.hash === routerHash) {
                    // 重置配置
                    csgoOption._isInit = false;
                    csgoOption._isSearch = false;
                    dota2Option._isInit = false;
                    dota2Option._isSearch = false;

                    // 重新监听标签页
                    observerTab();
                    // 重新插入按钮
                    insertOptimizeBtn();
                }
            });
        });
        observer.observe(domApp, { childList: true });
        return observer;
    };

    // 监听标签页变化
    // 触发一次优化功能
    const observerTab = () => {
        // 获取tab节点
        const domTab = document.getElementById('tab-csgo');
        const tab = domTab.__vue__;

        // 开启监听
        const observer = new MutationObserver(() => {
            // 重新触发优化
            changeState(state);
        });
        observer.observe(domTab, { attributes: true });
        return observer;
    };

    // 监听表格变化
    // 触发优化功能
    // @param {element} tableElement 表格节点
    // @param {object} table 表格vue组件
    // @param {function} callback 更新回调
    const observerTable = (tableElement, table, callback) => {
        // 开启监听,当表格属性变化时触发优化
        const observer = new MutationObserver(() => {
            // 优化已关闭
            if (!state) {
                //observer.disconnect();
                return;
            }
            // 等待DOM刷新后触发优化
            table.$nextTick(callback);
        });
        observer.observe(tableElement, { attributes: true });
    };

    // 插入优化按钮
    const insertOptimizeBtn = () => {
        // 获取右侧导航栏
        const domRightMenu = document.getElementsByClassName('avatarDiv')[0];

        // 添加优化按钮
        btnOptimize = document.createElement('button');
        btnOptimize.appendChild(document.createTextNode('自定义优化:关闭'));
        btnOptimize.classList.add('el-button', 'el-button--small');
        btnOptimize.style.setProperty('margin-right', '8px');
        btnOptimize.onclick = function () {
            state = !state;
            changeState(state);
        };
        domRightMenu.insertBefore(btnOptimize, domRightMenu.children[0]);

        // 是否默认开启优化
        if (state) {
            changeState(state);
        }
    };

    // 优化状态变更
    const changeState = (state) => {
        if (!state) {
            // 关闭
            btnOptimize.innerText = '自定义优化:关闭';
            btnOptimize.classList.remove('el-button--success');
            reset(csgoOption);
            reset(dota2Option);
        } else {
            // 开启
            btnOptimize.innerText = '自定义优化:开启';
            btnOptimize.classList.add('el-button--success');
            optimize(csgoOption);
            optimize(dota2Option);
        }
    };

    // 优化
    const optimize = (option) => {
        const tableElement = document.querySelector(option.selector);
        if (!tableElement) {
            return;
        }

        const table = tableElement.__vue__;
        if (!table) {
            return;
        }

        // 初始化
        if (!option._isInit) {
            option._isInit = true;

            // 缓存参数
            option._width = table.columns[option.priceColumnIndex].width;
            option._minWidth = table.columns[option.priceColumnIndex].minWidth;

            // 监听表格变化
            observerTable(tableElement, table, () => {
                // 仅执行一次自定义查询
                if (option.search && !option._isSearch) {
                    option._isSearch = true;
                    Object.entries(option.searchParam).forEach(item => {
                        table.$parent.initPage[item[0]] = item[1];
                    });
                    table.$parent.queryItemList();
                    return;
                }
                optimizeCell(table, option);
            });
        } else {
            // 执行优化
            optimizeCell(table, option);
        }
    };

    // 优化表格
    // @param {object} option 配置
    const optimizeCell = (table, option) => {
        // 开启表格纵向边框,开启后可以拖动改变列宽度
        table.border = true;
        // 设置折扣列宽度,需清除width并设置最小宽度,否则宽度占不满则会出现空白列
        table.columns[option.priceColumnIndex].width = undefined;
        table.columns[option.priceColumnIndex].minWidth = option.minWidth;
        // 修改UI后需重新渲染表格布局
        table.doLayout();

        // 移除优化生成的内容
        const domOptimizes = table.$el.querySelectorAll(`tbody > tr > td:nth-child(${ option.priceColumnIndex+1 }) > div.cell > div[data-type="optimize"]`);
        for (let i = 0; i < domOptimizes.length; i++) {
            domOptimizes[i].parentNode.removeAttribute('data-optimize');
            domOptimizes[i].remove();
        }

        // 找到每行的折扣内容
        const cells = table.$el.querySelectorAll(`tbody > tr > td:nth-child(${ option.priceColumnIndex+1 }) > div.cell`);

        const nowUnix = Math.round(new Date() / 1000);

        // 遍历每行生成优化内容
        const data = table.data;
        for (let i = 0; i < data.length; i++) {
            const h = `
            <div data-type="optimize">
                <div style="display: flex;">
                    <div style="min-width: 33px;">寄售:</div>
                    <div style="flex: 1">${ table.$parent.formatterPrice(data[i].steamSellerPrice) } &rarr; ${ table.$parent.formatterPrice(data[i].steamSellerPrice / 1.15) }</div>
                    <span class="el-tag el-tag--${ table.$parent.formatterTagType(data[i].lowestDisCount * 1.15) } el-tag--mini el-tag--plain">${ (data[i].lowestDisCount * 1.15).toFixed(3) }</span>
                </div>
                <div style="display: flex;">
                    <div style="min-width: 33px;">求购:</div>
                    <div style="flex: 1">${ table.$parent.formatterPrice(data[i].steamBuyerPrice) } &rarr; ${ table.$parent.formatterPrice(data[i].steamBuyerPrice / 1.15) }</div>
                    <span class="el-tag el-tag--${ table.$parent.formatterTagType(data[i].buyerPriceDisCount * 1.15) } el-tag--mini el-tag--plain">${ (data[i].buyerPriceDisCount * 1.15).toFixed(3) }</span>
                </div>
                <div style="display: flex;">
                    <div style="min-width: 33px;">稳定:</div>
                    <div style="flex: 1">${ table.$parent.formatterPrice(data[i].steamSafeBuyerPrice) } &rarr; ${ table.$parent.formatterPrice(data[i].steamSafeBuyerPrice / 1.15) }</div>
                    <span class="el-tag el-tag--${ table.$parent.formatterTagType(data[i].safeBuyerPriceDisCount * 1.15) } el-tag--mini el-tag--plain">${ (data[i].safeBuyerPriceDisCount * 1.15).toFixed(3) }</span>
                </div>
                <div style="display: flex;">
                    <div style="min-width: 33px;">更新:</div>
                    <div style="${ nowUnix - Math.round(new Date(data[i].updateTime)/1000) < updateDurationMinutes * 60 ? 'color: #e6a23c' : '' }">${ data[i].updateTime }</div>
                </div>
            </div>`;
            cells[i].insertAdjacentHTML('beforeend', h);
            cells[i].setAttribute('data-optimize', 'true');
        }
    };

    // 重置
    // @param {object} option 配置
    const reset = (option) => {
        const tableElement = document.querySelector(option.selector);
        if (!tableElement) {
            return;
        }

        const table = tableElement.__vue__;
        if (!table) {
            return;
        }

        // 关闭表格纵向边框
        table.border = false;
        // 重置列宽度
        table.columns[option.priceColumnIndex].width = option._width;
        table.columns[option.priceColumnIndex].minWidth = option._minWidth;
        // 修改UI后需重新渲染表格布局
        table.doLayout();

        // 移除优化生成的内容
        const domOptimizes = table.$el.querySelectorAll(`tbody > tr > td:nth-child(${ option.priceColumnIndex+1 }) > div.cell > div[data-type="optimize"]`);
        for (let i = 0; i < domOptimizes.length; i++) {
            domOptimizes[i].parentNode.removeAttribute('data-optimize');
            domOptimizes[i].remove();
        }
    };

    // 监听页面
    observerApp();

    // 监听标签页
    observerTab();

    // 创建按钮
    insertOptimizeBtn();
})();

QingJ © 2025

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