Rapid Barcode Receiver (Resize Fix v2)

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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();