Rapid Barcode Receiver (Resize Fix v2)

Uses !important CSS override to fix column resizing on pages with conflicting styles.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Rapid Barcode Receiver (Resize Fix v2)
// @namespace    http://tampermonkey.net/
// @version      6.1
// @description  Uses !important CSS override to fix column resizing on pages with conflicting styles.
// @author       Gemini & User
// @match        *://*/*
// @grant        GM_addStyle
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/colResizable-1.6.min.js
// @license      MIT
// ==/UserScript==

// --- Utility function to wait for dynamically loaded elements ---
function waitForKeyElements(selector, callback, bWaitOnce) {
    var targetNodes, bDone = false;
    targetNodes = $(selector);
    if (targetNodes && targetNodes.length > 0) {
        targetNodes.each(function() { callback($(this)); });
        bDone = bWaitOnce;
    }
    var obs = new MutationObserver(function(mutations) {
        if (bDone) { obs.disconnect(); return; }
        mutations.forEach(function(mutation) {
            var newNodes = mutation.addedNodes;
            if (newNodes && newNodes.length > 0) {
                for (var i = 0, len = newNodes.length; i < len; i++) {
                    var newNode = newNodes[i];
                    if (newNode.nodeType === 1) {
                        var $node = $(newNode);
                        if ($node.is(selector)) {
                            callback($node);
                            if (bWaitOnce) bDone = true;
                        } else if ($node.find(selector).length > 0) {
                            $node.find(selector).each(function() { callback($(this)); });
                            if (bWaitOnce) bDone = true;
                        }
                    }
                }
            }
        });
    });
    obs.observe(document.body, { childList: true, subtree: true });
}

// --- Main script logic ---
function initializeScript() {
    const closeButtonSelector = "#closebtn-smplrecieve, #btnclose-smplcollection";

    waitForKeyElements(closeButtonSelector, (closeButton) => {
        if (closeButton.parent().find('#rapidReceiveBtn').length > 0) return;

        let rapidReceiveBtn = $('<button type="button" class="btn btn-color-1" id="rapidReceiveBtn">Rapid Receiver</button>');
        rapidReceiveBtn.css('margin-right', '5px');
        closeButton.before(rapidReceiveBtn);

        if ($('#rapidReceiveModal').length === 0) {
            let modalHTML = `
                <div id="rapidReceiveModal" class="rr-modal-backdrop">
                    <div class="rr-modal-content">
                        <div class="rr-modal-header">
                            <h2>Rapid Barcode Processor</h2>
                            <span class="rr-close-button">&times;</span>
                        </div>
                        <div class="rr-modal-body">
                            <input type="text" id="newBarcodeEntry" placeholder="Scan or paste barcodes here and press Enter">
                            <div id="rr-table-container">
                                <table id="barcodeTable">
                                    <thead>
                                        <tr>
                                            <th class="rr-th-no">No.</th>
                                            <th class="rr-th-barcode">Barcode</th>
                                            <th class="rr-th-location">Location</th>
                                            <th class="rr-th-status">Status</th>
                                        </tr>
                                    </thead>
                                    <tbody id="barcodeListBody"></tbody>
                                </table>
                            </div>
                        </div>
                        <div class="rr-modal-footer">
                            <span id="rr-counter" class="rr-counter-style">0 Barcodes Entered</span>
                            <div>
                                <button id="clearBarcodesBtn" class="btn btn-danger">Clear</button>
                                <button id="processBarcodesBtn" class="btn btn-success">Process</button>
                            </div>
                        </div>
                    </div>
                </div>
            `;
            $('body').append(modalHTML);

            GM_addStyle(`
                .rr-modal-backdrop { display: none; position: fixed; z-index: 9999; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.6); }
                .rr-modal-content { display: flex; flex-direction: column; background-color: #f8f9fa; margin: 5% auto; padding: 25px; border: none; width: 90%; max-width: 900px; border-radius: 8px; box-shadow: 0 5px 15px rgba(0,0,0,0.3); height: 80vh; }
                .rr-modal-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #dee2e6; padding-bottom: 15px; margin-bottom: 15px; }
                .rr-modal-header h2 { margin: 0; font-size: 1.5rem; color: #343a40; }
                .rr-modal-body { flex-grow: 1; display: flex; flex-direction: column; overflow: hidden; }
                .rr-modal-footer { display: flex; justify-content: space-between; align-items: center; border-top: 1px solid #dee2e6; padding-top: 15px; margin-top: 15px; }
                .rr-close-button { color: #aaa; font-size: 32px; font-weight: bold; cursor: pointer; line-height: 1; }
                .rr-close-button:hover { color: black; }
                #newBarcodeEntry { width: 100%; padding: 10px; font-size: 16px; margin-bottom: 15px; border: 1px solid #ced4da; border-radius: 4px; box-sizing: border-box; }
                #newBarcodeEntry:focus { border-color: #80bdff; outline: 0; box-shadow: 0 0 0 0.2rem rgba(0,123,255,.25); }
                #rr-table-container { flex-grow: 1; overflow-y: auto; border: 1px solid #dee2e6; border-radius: 4px; background-color: #fff; }
                #barcodeTable { width: 100%; border-collapse: collapse; table-layout: fixed; }
                #barcodeTable th, #barcodeTable td { padding: 4px 15px; text-align: left; border-bottom: 1px solid #e9ecef; vertical-align: middle; }
                #barcodeTable th { background-color: #e9ecef; color: #495057; position: sticky; top: 0; }
                /* MODIFIED: Added !important to force vertical lines to appear */
                #barcodeTable th:not(:last-child), #barcodeTable td:not(:last-child) { border-right: 1px solid #dee2e6 !important; }
                #barcodeTable tbody tr:nth-child(even) { background-color: #f8f9fa; }
                .rr-th-no { width: 5%; }
                .rr-th-barcode { width: 35%; }
                .rr-th-location { width: 15%; }
                .rr-th-status { width: 45%; }
                td.status-cell { font-weight: bold; text-align: left; }
                td.status-cell.success { text-align: center; color: green; font-size: 1.2rem; }
                .rr-counter-style { font-size: 14px; font-weight: bold; color: #555; }
                #clearBarcodesBtn { margin-right: 10px; }
                .rr-error-text { color: #D8000C; font-weight: bold; font-size: 12px; }
                /* MODIFIED: Forceful grip style to ensure it is visible and clickable */
                .JCLRgrip {
                    background-color: #007bff !important;
                    width: 3px !important;
                    z-index: 99999 !important;
                }
            `);

            // Delay initialization to prevent conflicts
            setTimeout(() => {
                $('#barcodeTable').colResizable({
                    liveDrag: true,
                    gripInnerHtml: "<div class='JCLRgrip'></div>",
                    minWidth: 30 // Prevent columns from becoming too small
                });
            }, 100);

            // --- Event Listeners ---
            $('.rr-close-button').on('click', () => $('#rapidReceiveModal').hide());
            $(window).on('click', (event) => {
                if ($(event.target).is('#rapidReceiveModal')) $('#rapidReceiveModal').hide();
            });
            $('#processBarcodesBtn').on('click', processBarcodes);
            $('#clearBarcodesBtn').on('click', clearAllBarcodes);
            $('#newBarcodeEntry').on('keydown', handleBarcodeEntry);
            $('#newBarcodeEntry').on('paste', handleBarcodePaste);
        }

        $('#rapidReceiveBtn').on('click', openRapidReceiver);

    }, false);
}

function openRapidReceiver() {
    $('#rapidReceiveModal').show();
    clearAllBarcodes();
    $('#newBarcodeEntry').focus();
    $('#rr-counter').css('color', '');
    $('#processBarcodesBtn, #clearBarcodesBtn').prop('disabled', false);
    $('#processBarcodesBtn').text('Process');
}

function clearAllBarcodes() {
    $('#barcodeListBody').empty();
    $('#newBarcodeEntry').val('').prop('disabled', false);
    updateBarcodeCount();
}

function updateBarcodeCount() {
    const count = $('#barcodeListBody tr').length;
    $('#rr-counter').text(`${count} Barcodes Entered`);
}

function addBarcodeToTable(barcode) {
    barcode = barcode.trim();
    if (barcode === '') return;

    const currentBarcodes = new Set();
    $('#barcodeListBody td:nth-child(2)').each(function() {
        currentBarcodes.add($(this).text());
    });
    if (currentBarcodes.has(barcode)) return;

    const rowIndex = $('#barcodeListBody tr').length;
    const rowNum = rowIndex + 1;

    const letter = String.fromCharCode(65 + Math.floor(rowIndex / 10));
    const number = (rowIndex % 10) + 1;
    const location = `${letter}${number}`;

    const newRow = `
        <tr>
            <td>${rowNum}</td>
            <td>${barcode}</td>
            <td>${location}</td>
            <td class="status-cell"></td>
        </tr>
    `;
    $('#barcodeListBody').append(newRow);
    updateBarcodeCount();
}

function handleBarcodeEntry(event) {
    if (event.key === 'Enter') {
        event.preventDefault();
        const barcodeInput = $(this);
        const barcode = barcodeInput.val();
        addBarcodeToTable(barcode);
        barcodeInput.val('');
    }
}

function handleBarcodePaste(event) {
    event.preventDefault();
    const pastedText = (event.originalEvent || event).clipboardData.getData('text/plain');
    const barcodes = pastedText.split(/\r?\n/).filter(line => line.trim() !== '');
    barcodes.forEach(barcode => addBarcodeToTable(barcode));
    $(this).val('');
}


// --- Barcode processing logic ---
async function processBarcodes() {
    const barcodeInput = $('#barcodecollection');
    const processButton = $('#processBarcodesBtn');
    const clearButton = $('#clearBarcodesBtn');
    const entryInput = $('#newBarcodeEntry');
    const counterElement = $('#rr-counter');

    const rowsToProcess = $('#barcodeListBody tr').filter(function() {
        const status = $(this).find('td.status-cell').text().trim();
        return status !== '✔️';
    });

    if (rowsToProcess.length === 0) return alert('No new or failed barcodes to process.');
    if (barcodeInput.length === 0) return alert('Error: Main barcode input field "#barcodecollection" not found.');

    processButton.prop('disabled', true).text('Processing...');
    clearButton.prop('disabled', true);
    entryInput.prop('disabled', true);

    const INTER_BARCODE_DELAY = 1200;
    const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
    const dispatchEvent = (element, eventType) => element.dispatchEvent(new Event(eventType, { bubbles: true }));

    const simulateEnter = async (element) => {
        const commonEventProps = { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true, cancelable: true };
        element.dispatchEvent(new KeyboardEvent('keydown', commonEventProps));
        await sleep(50);
        element.dispatchEvent(new KeyboardEvent('keyup', commonEventProps));
    };

    let processedCount = 0;
    for (const row of rowsToProcess) {
        processedCount++;
        counterElement.text(`Processing: ${processedCount} / ${rowsToProcess.length}`);

        const $row = $(row);
        const barcode = $row.find('td:nth-child(2)').text();
        const statusCell = $row.find('td.status-cell');
        statusCell.removeClass('success').html('...');

        const inputElement = barcodeInput[0];
        inputElement.value = barcode;
        dispatchEvent(inputElement, 'input');
        dispatchEvent(inputElement, 'change');
        await sleep(100);
        inputElement.focus();
        await simulateEnter(inputElement);

        await sleep(600);

        const $errorAlert = $("div.alert.alert-danger:visible");
        if ($errorAlert.length > 0) {
            const errorMessage = $errorAlert.find('strong').text().trim() || 'Unknown Error';
            statusCell.html(`<span class="rr-error-text">${errorMessage}</span>`);
        } else {
            statusCell.addClass('success').html('✔️');
        }

        $('.alert-dismissable .close').click();
        await sleep(INTER_BARCODE_DELAY - 600);
    }

    processButton.prop('disabled', false).text('Process');
    clearButton.prop('disabled', false);
    entryInput.prop('disabled', false).focus();
    counterElement.text('✅ Complete!').css('color', 'green');
}

// --- Logic to handle dynamic page loads ---
let lastUrl = location.href;
setInterval(() => {
    const currentUrl = location.href;
    if (currentUrl !== lastUrl) {
        lastUrl = currentUrl;
        initializeScript();
    }
}, 500);

// Initial run
initializeScript();