您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Combines verification buttons (F7/F8), dynamic alerts (>, NO RESULT, X-NORESULT, CL/CH) with modals that persist until dismissed, checkbox automation, toggle back-nav.
当前为
// ==UserScript== // @name KAAUH Lab Enhancement Suite 1 (Buttons, Alerts, Auto-Actions) // @namespace Violentmonkey Scripts // @version 5.6.3 // @description Combines verification buttons (F7/F8), dynamic alerts (>, NO RESULT, X-NORESULT, CL/CH) with modals that persist until dismissed, checkbox automation, toggle back-nav. // @match *://his.kaauh.org/lab/* // @grant none // @author Hamad AlShegifi // @license MIT // ==/UserScript== (function () { 'use strict'; // --- Configuration --- const CONFIG = { // General & Debugging DEBUG_MODE: true, PERSISTENT_MODALS: true, // Modals require manual dismissal NO_RESULT_MESSAGE: "NO-RESULT DETECTED!!", // Standardized message // Script 1: Buttons & Verification TARGET_EDIT_PAGE_URL_PREFIX: 'https://his.kaauh.org/lab/#/lab-orders/edit-lab-order/', EXCLUDE_WORDS: ['culture', "gram's stain", 'stain', 'bacterial', 'fungal', 'culture', 'pcr', 'Meningitis', 'MRSA', 'Mid', 'stream', 'Cryptococcus'], VERIFY1_BUTTON_SELECTOR: '#custom-script-buttons button.btn-success', VERIFY2_BUTTON_SELECTOR: '#custom-script-buttons button.btn-primary', COMPLETE_TECH_VERIFY_SELECTOR: 'button.dropdown-item[translateid="test-results.CompleteTechnicalVerification"]', COMPLETE_MED_VERIFY_SELECTOR: 'button.dropdown-item[translateid="test-results.CompleteMedicalVerification"]', FINAL_VERIFY_BUTTON_SELECTOR: 'button.btn-success.btn-sm.min-width[translateid="test-verification.Verify"]', NEXT_BUTTON_SELECTOR: 'button#btnNext', TEST_DESC_SELECTOR: 'div[col-id="TestDesc"]', UNCHECKED_BOX_SELECTOR: 'span.ag-icon-checkbox-unchecked[unselectable="on"]', CHECKBOX_PARENT_ROW_SELECTOR: '.ag-row', // Script 2: Alerts & Scanning SCAN_INTERVAL: 150, FLASH_COLOR: "pink", FLASH_INTERVAL: 500, MODAL_TIMEOUT: 10000, RESULT_CELL_SELECTOR: 'div[role="gridcell"][col-id="TestResult"] app-result-value-render div', CRITICAL_FLAG_SELECTOR: 'div[role="gridcell"][col-id="LTFlag"] app-ref-high-low div span.critial-alret-indication', // Script 3 & General Toast Handling UNDEFINED_URL_CHECK_INTERVAL: 200, TOAST_CONTAINER_SELECTOR: '#toast-container', TOAST_CLOSE_BUTTON_SELECTOR: 'button.toast-close-button', SUCCESS_TOAST_SELECTOR: '.toast-success', }; // --- State Variables --- let verify1Toggle = localStorage.getItem('verify1Toggle') === 'true'; let verify2Toggle = localStorage.getItem('verify2Toggle') === 'true'; let verify1Clicked = false; let verify2Clicked = false; let issueScanIntervalId = null; let isScanningActive = false; const activeModals = new Set(); // --- Utility Functions --- function logDebug(message) { if (CONFIG.DEBUG_MODE) console.log(`[Lab Suite v5.6.3] ${message}`); } function loadFontAwesome() { const existingLink = document.querySelector('link[href*="font-awesome"]'); if (!existingLink) { const link = document.createElement('link'); link.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css'; link.rel = 'stylesheet'; document.head.appendChild(link); logDebug('Font Awesome loaded'); } } function isVisible(element) { return !!(element && (element.offsetWidth || element.offsetHeight || element.getClientRects().length)); } // --- Modal Functions --- function showModal(message) { // Remove duplicate modals with same message document.querySelectorAll('.lab-suite-modal').forEach(modal => { if (modal.textContent.includes(message)) { const overlay = document.querySelector('.lab-suite-modal-overlay'); if (overlay) overlay.remove(); modal.remove(); } }); logDebug(`Showing modal: ${message}`); const overlay = document.createElement("div"); overlay.className = 'lab-suite-modal-overlay'; overlay.style.position = "fixed"; overlay.style.top = "0"; overlay.style.left = "0"; overlay.style.width = "100%"; overlay.style.height = "100%"; overlay.style.backgroundColor = "rgba(0, 0, 0, 0.5)"; overlay.style.zIndex = "2000"; overlay.style.opacity = "0"; overlay.style.transition = "opacity 0.3s ease"; document.body.appendChild(overlay); const modal = document.createElement("div"); modal.className = 'lab-suite-modal'; modal.style.position = "fixed"; modal.style.top = "50%"; modal.style.left = "50%"; modal.style.transform = "translate(-50%, -50%) scale(0.5)"; modal.style.backgroundColor = "#f4f4f9"; modal.style.padding = "30px"; modal.style.boxShadow = "0px 10px 30px rgba(0, 0, 0, 0.15)"; modal.style.zIndex = "2001"; modal.style.borderRadius = "15px"; modal.style.textAlign = "center"; modal.style.transition = "transform 0.3s ease, opacity 0.3s ease"; modal.style.opacity = "0"; modal.style.maxWidth = "450px"; modal.setAttribute('role', 'alert'); modal.setAttribute('aria-live', 'assertive'); const heading = document.createElement("h2"); heading.textContent = "Attention!"; heading.style.fontFamily = "'Arial', sans-serif"; heading.style.color = "#333"; heading.style.marginBottom = "10px"; heading.style.fontSize = "24px"; modal.appendChild(heading); const content = document.createElement("p"); // Standardize NO RESULT messages if (message.includes("NO RESULT") || message.includes("X-NORESULT")) { message = CONFIG.NO_RESULT_MESSAGE; } content.textContent = message; content.style.fontFamily = "'Arial', sans-serif"; content.style.color = "#555"; content.style.marginBottom = "20px"; content.style.fontSize = "16px"; content.style.lineHeight = "1.5"; modal.appendChild(content); const okButton = createModalButton("OK", "#ff4081", () => { logDebug("Modal manually dismissed."); closeModal(modal, overlay); }); modal.appendChild(okButton); document.body.appendChild(modal); // Animate appearance setTimeout(() => { overlay.style.opacity = "1"; modal.style.transform = "translate(-50%, -50%) scale(1)"; modal.style.opacity = "1"; }, 10); activeModals.add(modal); } function createModalButton(text, backgroundColor, onClick) { const button = document.createElement("button"); button.textContent = text; button.style.padding = "10px 20px"; button.style.border = "none"; button.style.backgroundColor = backgroundColor; button.style.color = "white"; button.style.borderRadius = "30px"; button.style.cursor = "pointer"; button.style.fontSize = "16px"; button.style.transition = "background-color 0.3s ease, transform 0.2s ease"; button.style.minWidth = '80px'; button.style.margin = '0 5px'; button.addEventListener("mouseenter", () => { button.style.backgroundColor = darkenColor(backgroundColor, 20); button.style.transform = "scale(1.05)"; }); button.addEventListener("mouseleave", () => { button.style.backgroundColor = backgroundColor; button.style.transform = "scale(1)"; }); button.addEventListener("click", onClick); return button; } function closeModal(modal, overlay) { if (!modal || !overlay || !document.body.contains(modal)) return; logDebug("Closing modal."); modal.style.transform = "translate(-50%, -50%) scale(0.5)"; modal.style.opacity = "0"; overlay.style.opacity = "0"; setTimeout(() => { if (document.body.contains(modal)) document.body.removeChild(modal); if (document.body.contains(overlay)) document.body.removeChild(overlay); activeModals.delete(modal); }, 300); } function darkenColor(color, percent) { try { let num = parseInt(color.slice(1), 16); let amt = Math.round(2.55 * percent); let R = (num >> 16) - amt; let G = ((num >> 8) & 0x00ff) - amt; let B = (num & 0x0000ff) - amt; R = R < 0 ? 0 : R; G = G < 0 ? 0 : G; B = B < 0 ? 0 : B; return `#${(0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1)}`; } catch (e) { logDebug(`Error darkening color ${color}: ${e}`); return color; } } // --- Script 1 Functions --- function isCorrectPage() { return window.location.href.startsWith(CONFIG.TARGET_EDIT_PAGE_URL_PREFIX); } function createVerifyButton(label, className, onClick) { let button = document.createElement('button'); button.type = 'button'; button.innerHTML = label; button.className = className; let buttonColors = { 'btn btn-success btn-sm': '#28a745', 'btn btn-primary btn-sm': '#2594d9' }; button.style.cssText = ` font-family: 'Arial', sans-serif !important; font-size: 14px !important; font-weight: normal !important; color: #ffffff !important; background-color: ${buttonColors[className] || '#6c757d'} !important; padding: 8px 16px !important; border: none !important; border-radius: 5px !important; text-shadow: none !important; cursor: pointer !important; margin-right: 5px !important; line-height: 1.5 !important; `; button.onclick = onClick; return button; } function createToggleIcon(id, isActive, onClick) { let icon = document.createElement('span'); icon.id = id; icon.innerHTML = `<i class="fa fa-arrow-circle-left" aria-hidden="true" style="color: ${isActive ? '#008000' : '#d1cfcf'}; font-size: 1.3em; vertical-align: middle;"></i>`; icon.style.cursor = 'pointer'; icon.style.marginRight = '10px'; icon.style.marginLeft = '-1px'; icon.onclick = onClick; icon.title = "Go back after this verification"; return icon; } function handleVerify1IconToggle() { verify1Toggle = !verify1Toggle; localStorage.setItem('verify1Toggle', verify1Toggle); const iconElement = document.querySelector('#verify1Icon i'); if (iconElement) iconElement.style.color = verify1Toggle ? '#008000' : '#d1cfcf'; logDebug(`Verify1 Toggle set to: ${verify1Toggle}`); } function handleVerify2IconToggle() { verify2Toggle = !verify2Toggle; localStorage.setItem('verify2Toggle', verify2Toggle); const iconElement = document.querySelector('#verify2Icon i'); if (iconElement) iconElement.style.color = verify2Toggle ? '#008000' : '#d1cfcf'; logDebug(`Verify2 Toggle set to: ${verify2Toggle}`); } function addButtons() { if (document.getElementById('custom-script-buttons') || !isCorrectPage()) return; const nextButton = document.querySelector(CONFIG.NEXT_BUTTON_SELECTOR); if (nextButton && nextButton.parentNode) { logDebug("Adding custom VERIFY buttons."); let buttonDiv = document.createElement('div'); buttonDiv.id = 'custom-script-buttons'; buttonDiv.style.display = 'inline-block'; buttonDiv.style.marginLeft = '10px'; buttonDiv.style.verticalAlign = 'middle'; // VERIFY1 Button & Toggle let verify1Button = createVerifyButton('VERIFY1 (F7)', 'btn btn-success btn-sm', () => { logDebug("VERIFY1 button clicked."); verify1Clicked = true; verify2Clicked = false; checkAllVisibleBoxesWithoutDuplicates(); setTimeout(() => { clickCompleteTechnicalVerificationButton(); }, 500); }); let verify1Icon = createToggleIcon('verify1Icon', verify1Toggle, handleVerify1IconToggle); // VERIFY2 Button & Toggle let verify2Button = createVerifyButton('VERIFY2 (F8)', 'btn btn-primary btn-sm', () => { logDebug("VERIFY2 button clicked."); verify2Clicked = true; verify1Clicked = false; checkAllVisibleBoxesWithoutDuplicates(); setTimeout(() => { clickCompleteMedicalVerificationButton(); }, 500); }); let verify2Icon = createToggleIcon('verify2Icon', verify2Toggle, handleVerify2IconToggle); // Author Credit let modedByText = document.createElement('span'); modedByText.textContent = "Moded by: Hamad AlShegifi"; modedByText.style.fontSize = '12px'; modedByText.style.fontWeight = 'bold'; modedByText.style.color = '#ff0000'; modedByText.style.position = 'relative'; modedByText.style.left = '0px'; modedByText.style.top = '0px'; modedByText.style.border = '1.5px solid #ff0000'; modedByText.style.borderRadius = '8px'; modedByText.style.padding = '4px'; modedByText.style.backgroundColor = 'white'; buttonDiv.appendChild(verify1Button); buttonDiv.appendChild(verify1Icon); buttonDiv.appendChild(verify2Button); buttonDiv.appendChild(verify2Icon); buttonDiv.appendChild(modedByText); nextButton.parentNode.insertBefore(buttonDiv, nextButton.nextSibling); } else { logDebug("Could not find Next button to anchor custom buttons."); } } function checkAllVisibleBoxesWithoutDuplicates() { logDebug("Checking checkboxes..."); const testDivs = document.querySelectorAll(CONFIG.TEST_DESC_SELECTOR); let seenTests = new Set(); let boxesChecked = 0; const excludeWordsLower = CONFIG.EXCLUDE_WORDS.map(word => word.toLowerCase()); testDivs.forEach(testDiv => { const testName = testDiv.textContent?.trim().toLowerCase() || ''; if (!testName) return; if (excludeWordsLower.some(word => testName.includes(word))) { return; } if (seenTests.has(testName)) { return; } seenTests.add(testName); const parentRow = testDiv.closest(CONFIG.CHECKBOX_PARENT_ROW_SELECTOR); if (parentRow) { const checkbox = parentRow.querySelector(CONFIG.UNCHECKED_BOX_SELECTOR); if (checkbox && isVisible(checkbox)) { logDebug(`Clicking checkbox for test: ${testName}`); const event = new MouseEvent('click', { bubbles: true, cancelable: true, view: window }); checkbox.dispatchEvent(event); boxesChecked++; } } }); logDebug(`${boxesChecked} unique, non-excluded checkboxes were checked.`); } function clickCompleteTechnicalVerificationButton() { const button = document.querySelector(CONFIG.COMPLETE_TECH_VERIFY_SELECTOR); if (button) { button.click(); logDebug("Complete Technical Verification button clicked!"); setTimeout(() => { clickFinalVerifyButton(); }, 700); } else { logDebug("Complete Technical Verification button not found!"); showModal("Error: Unable to find the 'Complete Technical Verification' button."); } } function clickCompleteMedicalVerificationButton() { const button = document.querySelector(CONFIG.COMPLETE_MED_VERIFY_SELECTOR); if (button) { button.click(); logDebug("Complete Medical Verification button clicked!"); setTimeout(() => { clickFinalVerifyButton(); }, 700); } else { logDebug("Complete Medical Verification button not found!"); showModal("Error: Unable to find the 'Complete Medical Verification' button."); } } function clickFinalVerifyButton() { const verifyButton = document.querySelector(CONFIG.FINAL_VERIFY_BUTTON_SELECTOR); if (verifyButton) { verifyButton.click(); logDebug("Final Verify button clicked!"); } else { logDebug("Final Verify button not found!"); showModal("Error: Unable to find the final 'Verify' button."); } } // --- Script 2 Functions --- function applyFlashingEffect(rows) { rows.forEach(row => { if (row.dataset.flashing) return; row.dataset.flashing = 'true'; logDebug("Applying flashing effect to a row."); row.style.transition = "background-color 0.5s ease"; let isPink = false; const originalBg = row.style.backgroundColor || 'transparent'; const intervalId = setInterval(() => { if (!document.contains(row)) { clearInterval(intervalId); delete row.dataset.flashing; return; } isPink = !isPink; row.style.backgroundColor = isPink ? CONFIG.FLASH_COLOR : originalBg; }, CONFIG.FLASH_INTERVAL); row.dataset.flashIntervalId = intervalId; }); } function getNotificationSessionKey(type, identifier = 'general') { return `labSuiteNotified_${window.location.pathname}_${type}_${identifier}`; } function hasAlreadyNotified(type, identifier = 'general') { const key = getNotificationSessionKey(type, identifier); return sessionStorage.getItem(key) === 'true'; } function setNotificationFlag(type, identifier = 'general') { const key = getNotificationSessionKey(type, identifier); logDebug(`Setting notification flag for key: ${key}`); sessionStorage.setItem(key, 'true'); } function checkForIssues() { const resultDivs = document.querySelectorAll(CONFIG.RESULT_CELL_SELECTOR); const criticalDivs = document.querySelectorAll(CONFIG.CRITICAL_FLAG_SELECTOR); function scrollToRow(element) { const row = element.closest(CONFIG.CHECKBOX_PARENT_ROW_SELECTOR); if (row) { const rowId = row.getAttribute('row-id'); logDebug(`Attempting to scroll to row with ID: ${rowId || 'ID not found'}`); row.scrollIntoView({ behavior: 'smooth', block: 'start' }); return { scrolled: true, rowId: rowId }; } return { scrolled: false }; } let alertShownThisCycle = false; // 1. Check for Critical Flags (CL/CH) if (!alertShownThisCycle && !hasAlreadyNotified('critical')) { criticalDivs.forEach(div => { if (alertShownThisCycle) return; const text = div.textContent?.trim() || ''; if (text === "CL" || text === "CH") { const scrollResult = scrollToRow(div); if (scrollResult.scrolled) { const specificRowKey = scrollResult.rowId || text; if (!hasAlreadyNotified('critical', specificRowKey)) { logDebug(`Found ${text} for row ${specificRowKey}`); const message = text === "CL" ? "CRITICAL LOW RESULT DETECTED !!" : "CRITICAL HIGH RESULT DETECTED !!"; showModal(message); setNotificationFlag('critical', specificRowKey); setNotificationFlag('critical'); alertShownThisCycle = true; } } } }); } // 2. Check for "NO RESULT" / "X-NORESULT" if (!alertShownThisCycle && !hasAlreadyNotified('noresult')) { resultDivs.forEach(div => { if (alertShownThisCycle) return; const text = div.textContent?.trim().toLowerCase() || ''; const isNoResultType = (text === "no result" || text === "no-xresult" || text === "x-noresult"); if (isNoResultType) { const scrollResult = scrollToRow(div); if (scrollResult.scrolled) { const specificRowKey = scrollResult.rowId || text.replace(/[\s-]+/g, '_'); if (!hasAlreadyNotified('noresult', specificRowKey)) { logDebug(`Found ${text} for row ${specificRowKey}`); showModal(CONFIG.NO_RESULT_MESSAGE); setNotificationFlag('noresult', specificRowKey); setNotificationFlag('noresult'); alertShownThisCycle = true; } } } }); } // 3. Check for ">" (Dilution Required) if (!alertShownThisCycle) { resultDivs.forEach(div => { const text = div.textContent?.trim().toLowerCase() || ''; if (text.includes(">")) { const rowElement = div.closest(CONFIG.CHECKBOX_PARENT_ROW_SELECTOR); const rowId = rowElement?.getAttribute('row-id') || text; const eventKey = `greaterThan_${rowId}`; if (!hasAlreadyNotified('greaterThan', rowId)) { if (rowElement) { logDebug(`Found '>' for row ${rowId}`); applyFlashingEffect([rowElement]); showModal("Dilution is required for this sample (> detected)!"); setNotificationFlag('greaterThan', rowId); scrollToRow(div); } } } }); } return alertShownThisCycle; } function startContinuousScanning() { if (isScanningActive) return; logDebug("Starting continuous issue scanning..."); isScanningActive = true; if (issueScanIntervalId) clearInterval(issueScanIntervalId); issueScanIntervalId = setInterval(() => { const resultsGridExists = document.querySelector(CONFIG.RESULT_CELL_SELECTOR) || document.querySelector(CONFIG.CRITICAL_FLAG_SELECTOR); if (!resultsGridExists) { logDebug("Monitored elements disappeared, stopping issue scan."); stopContinuousScanning(); return; } checkForIssues(); }, CONFIG.SCAN_INTERVAL); } function stopContinuousScanning() { if (issueScanIntervalId) { clearInterval(issueScanIntervalId); issueScanIntervalId = null; logDebug("Stopped continuous issue scanning."); } isScanningActive = false; } // --- Script 3 Function --- function checkUrlAndTriggerClickForUndefined() { const currentUrl = window.location.href; if (currentUrl.endsWith('/undefined')) { const toastContainer = document.querySelector(CONFIG.TOAST_CONTAINER_SELECTOR); if (toastContainer) { const closeButton = toastContainer.querySelector(CONFIG.TOAST_CLOSE_BUTTON_SELECTOR); if (closeButton && isVisible(closeButton)) { logDebug('URL ends with /undefined. Found visible toast close button. Clicking...'); closeButton.click(); } } } } // --- Event Listeners & Observers --- document.addEventListener('keydown', function (event) { if (event.key === 'F7') { event.preventDefault(); logDebug("F7 pressed: Triggering VERIFY1 button click"); const verify1Button = document.querySelector(CONFIG.VERIFY1_BUTTON_SELECTOR); if (verify1Button) verify1Button.click(); else logDebug("VERIFY1 button not found for F7."); } else if (event.key === 'F8') { event.preventDefault(); logDebug("F8 pressed: Triggering VERIFY2 button click"); const verify2Button = document.querySelector(CONFIG.VERIFY2_BUTTON_SELECTOR); if (verify2Button) verify2Button.click(); else logDebug("VERIFY2 button not found for F8."); } }); const toastObserver = new MutationObserver((mutations) => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE && node.matches && node.matches(CONFIG.SUCCESS_TOAST_SELECTOR)) { logDebug('Success toast added. Adding click listener for back navigation.'); node.addEventListener('click', () => { logDebug('Success toast clicked.'); if (verify1Clicked && verify1Toggle) { logDebug('Verify1 was clicked and toggle is on. Going back.'); window.history.back(); } else if (verify2Clicked && verify2Toggle) { logDebug('Verify2 was clicked and toggle is on. Going back.'); window.history.back(); } verify1Clicked = false; verify2Clicked = false; }); } }); }); }); const mainObserver = new MutationObserver(() => { if (isCorrectPage()) { addButtons(); } else { const buttonDiv = document.getElementById('custom-script-buttons'); if (buttonDiv) { logDebug("Leaving edit page, removing custom buttons."); buttonDiv.remove(); } } const resultsGridExists = document.querySelector(CONFIG.RESULT_CELL_SELECTOR) || document.querySelector(CONFIG.CRITICAL_FLAG_SELECTOR); if (resultsGridExists) { if (!isScanningActive) { startContinuousScanning(); } } else { if (isScanningActive) { stopContinuousScanning(); } } }); // --- Initialization --- try { logDebug("KAAUH Lab Enhancement Suite Initializing (v5.6.3)..."); loadFontAwesome(); setInterval(checkUrlAndTriggerClickForUndefined, CONFIG.UNDEFINED_URL_CHECK_INTERVAL); logDebug(`Started URL check interval (${CONFIG.UNDEFINED_URL_CHECK_INTERVAL}ms) for /undefined toasts.`); mainObserver.observe(document.body, { childList: true, subtree: true }); logDebug("Started main MutationObserver."); toastObserver.observe(document.body, { childList: true, subtree: true }); logDebug("Started toast MutationObserver for back-navigation."); window.addEventListener('load', () => { logDebug("Page fully loaded. Performing initial checks."); if (isCorrectPage()) { addButtons(); } const resultsGridExists = document.querySelector(CONFIG.RESULT_CELL_SELECTOR) || document.querySelector(CONFIG.CRITICAL_FLAG_SELECTOR); if (resultsGridExists && !isScanningActive) { startContinuousScanning(); } }); logDebug("Initialization complete."); } catch (error) { console.error("[Lab Suite] Critical error during initialization:", error); showModal("A critical error occurred in the Lab Enhancement Suite. Please check the browser console (F12) for details."); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址