// ==UserScript== // @name 倒运计划智能填表助手 // @namespace http://tampermonkey.net/ // @version 5.1 // @description 优化冒号与Tab键混合解析逻辑,新增T15格式过磅点精准提取功能 // @author XIAOTIAN // @match // @include * // @grant GM_setClipboard // @downloadURL https://update.greasyfork.icu/scripts/575824/%E5%80%92%E8%BF%90%E8%AE%A1%E5%88%92%E6%99%BA%E8%83%BD%E5%A1%AB%E8%A1%A8%E5%8A%A9%E6%89%8B.user.js // @updateURL https://update.greasyfork.icu/scripts/575824/%E5%80%92%E8%BF%90%E8%AE%A1%E5%88%92%E6%99%BA%E8%83%BD%E5%A1%AB%E8%A1%A8%E5%8A%A9%E6%89%8B.meta.js // ==/UserScript== (function() { 'use strict'; // ================= V5.0 全新渐变悬浮按钮 UI ================= let btn = document.createElement('button'); btn.innerHTML = '🤖
智能
填表'; btn.style.cssText = ` display: none; flex-direction: column; justify-content: center; align-items: center; position: fixed; bottom: 100px; right: 30px; z-index: 9999; width: 70px; height: 70px; background: linear-gradient(to bottom, #D9D6F2, #6873E0); color: #ffffff; border: 4px solid #ffffff; border-radius: 50%; cursor: pointer; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.25); text-shadow: 0px 1px 3px rgba(0, 0, 0, 0.25); font-size: 13px; font-weight: bold; line-height: 1.2; box-sizing: border-box; transition: transform 0.2s ease; user-select: none; `; const originalTransition = btn.style.transition; btn.onmouseover = () => btn.style.transform = 'scale(1.08)'; btn.onmouseout = () => btn.style.transform = 'scale(1)'; document.body.appendChild(btn); // ================= 弹窗显示/隐藏控制 ================= setInterval(() => { if (window.location.href.includes('#/dump-dumparchives')) { if (btn.style.display === 'none') btn.style.display = 'flex'; } else { if (btn.style.display !== 'none') btn.style.display = 'none'; } }, 500); // ================= 拖拽核心逻辑 ================= let isDragging = false; let isMoved = false; let startX, startY, initialLeft, initialTop; btn.addEventListener('mousedown', dragStart); document.addEventListener('mousemove', drag); document.addEventListener('mouseup', dragEnd); function dragStart(e) { if (e.button !== 0) return; isDragging = true; isMoved = false; startX = e.clientX; startY = e.clientY; const rect = btn.getBoundingClientRect(); initialLeft = rect.left; initialTop = rect.top; btn.style.right = 'auto'; btn.style.bottom = 'auto'; btn.style.left = initialLeft + 'px'; btn.style.top = initialTop + 'px'; btn.style.transition = 'none'; } function drag(e) { if (!isDragging) return; let dx = e.clientX - startX; let dy = e.clientY - startY; if (Math.abs(dx) > 5 || Math.abs(dy) > 5) { isMoved = true; } let newLeft = initialLeft + dx; let newTop = initialTop + dy; const maxX = window.innerWidth - btn.offsetWidth; const maxY = window.innerHeight - btn.offsetHeight; newLeft = Math.max(0, Math.min(newLeft, maxX)); newTop = Math.max(0, Math.min(newTop, maxY)); btn.style.left = newLeft + 'px'; btn.style.top = newTop + 'px'; } function dragEnd() { if (!isDragging) return; isDragging = false; btn.style.transition = originalTransition; } // ================= 精准获取当前肉眼可见的弹窗 ================= function getActiveDialog(targetAriaLabel) { let wrappers = Array.from(document.querySelectorAll('.el-dialog__wrapper')).filter(el => { let style = window.getComputedStyle(el); return style.display !== 'none' && el.getBoundingClientRect().width > 0; }); if (targetAriaLabel) { let targetWrapper = wrappers.find(w => { let dialog = w.querySelector('.el-dialog'); return dialog && dialog.getAttribute('aria-label') === targetAriaLabel; }); return targetWrapper || null; } return wrappers.length > 0 ? wrappers[wrappers.length - 1] : null; } // ================= 主执行逻辑 ================= btn.addEventListener('click', async (e) => { if (isMoved) { e.preventDefault(); e.stopPropagation(); return; } try { const rawText = await navigator.clipboard.readText(); if (!rawText) return alert("剪贴板为空!"); const dataObj = parseClipboardText(rawText); const cleanStr = (str) => (str || '').replace(/\s+/g, ''); const getVal = (keyword) => { let key = Object.keys(dataObj).find(k => k.includes(keyword)); return key ? dataObj[key] : null; }; let mainDialog = getActiveDialog('新增倒运计划'); if (!mainDialog) return alert("未找到可见的'新增倒运计划'主弹窗!"); // --- 第一阶段:基础信息 --- let tabs = Array.from(mainDialog.querySelectorAll('.el-tabs__item')); let tab1 = tabs.find(tab => tab.innerText.replace(/\s+/g, '').includes('倒运计划基础信息')); if (tab1 && !tab1.classList.contains('is-active')) { tab1.click(); await new Promise(r => setTimeout(r, 400)); } // 发货库房 let sendLocation = cleanStr(getVal('装车地点')); let sendUnit = cleanStr(getVal('发货单位')); if (sendLocation) { let isMatched = false; if (sendUnit) { isMatched = await fillSelectByLabel(mainDialog, '发货库房', [sendLocation, sendUnit]); } if (!isMatched) { await fillSelectByLabel(mainDialog, '发货库房', [sendLocation]); } } // 收货库房 let receiveLocation = cleanStr(getVal('卸车地点')); let receiveUnit = cleanStr(getVal('收货单位')); if (receiveLocation) { let isMatched = false; if (receiveUnit) { isMatched = await fillSelectByLabel(mainDialog, '收货库房', [receiveLocation, receiveUnit]); } if (!isMatched) { await fillSelectByLabel(mainDialog, '收货库房', [receiveLocation]); } } // 业务类型双重降级匹配 let businessType = getVal('转运类型') || getVal('业务类型') || getVal('运输类型') || getVal('业务类别'); if (businessType) { let cnMatch = businessType.match(/[\u4e00-\u9fa5]+/g); let targetBt = cnMatch ? cnMatch.join('') : cleanStr(businessType); let isMatched = await fillSelectByLabel(mainDialog, '业务类型', cleanStr(businessType)); if (!isMatched && cnMatch) { console.log(`⚠️ 精确业务类型未找到,降级为中文匹配: [${targetBt}]`); await fillSelectByLabel(mainDialog, '业务类型', targetBt); } } // 绝对提纯物料名称 let materialName = getVal('物资名称') || getVal('物料名称') || getVal('货物名称') || getVal('物品名称'); let cleanMaterial = cleanStr(materialName); let isNickelOre = cleanMaterial === '镍矿'; let wbNum = null; // 【V5.1 核心升级:T格式提取器】 let weightPlace = getVal('过磅地点'); if (weightPlace) { let rawPlace = cleanStr(weightPlace); // 先尝试找T后面的数字(如T15),找不到再找普通数字(如15) let numMatch = rawPlace.match(/T(\d+)/i) || rawPlace.match(/\d+/); if (numMatch) { // 如果是正则 /T(\d+)/ 匹配到的,数字在索引 1;如果是 /\d+/ 匹配到的,数字在 0 let extractedNum = numMatch[1] ? numMatch[1] : numMatch[0]; wbNum = parseInt(extractedNum, 10); await fillSelectByLabel(mainDialog, '磅号', extractedNum + '#磅'); } else { await fillSelectByLabel(mainDialog, '磅号', rawPlace); } } // 联动逻辑:控制开关 let turnOnWBN = false; let turnOnPlan = false; if (isNickelOre && wbNum !== null) { if ([6, 7, 8, 10, 11, 12, 13, 17].includes(wbNum)) { turnOnWBN = true; } else if ([14, 15, 16, 18, 19].includes(wbNum)) { turnOnPlan = true; } } await toggleSwitchByLabel(mainDialog, '产线WBN镍矿', turnOnWBN); await toggleSwitchByLabel(mainDialog, '地磅计划所属', turnOnPlan); let remarkValue = getVal('批次') || getVal('矿名') || getVal('矿号') || getVal('物资名称') || getVal('物料名称'); if (remarkValue) fillInputByLabel(mainDialog, '备注', remarkValue.trim()); let loadPoint = getVal('装点编号'); if (loadPoint) fillInputByLabel(mainDialog, '装点编号', cleanStr(loadPoint)); let contract = getVal('合同号'); if (contract) fillInputByLabel(mainDialog, '合同号', cleanStr(contract)); // 自动选择物料 if (cleanMaterial) { await handleMaterialSelection(mainDialog, cleanMaterial); } // --- 第二阶段:车辆录入与统计 --- let vehicleKeys = Object.keys(dataObj).filter(k => k.includes('车号') || k.includes('车牌') || k.includes('车辆') || k.includes('车量') || k.includes('后八轮') ); let vehicleRawStr = vehicleKeys.map(k => dataObj[k]).join(' '); let totalVehicles = 0; let successCount = 0; let failedVehicles = []; if (vehicleRawStr) { let vehicles = vehicleRawStr.split(/[,,\s、\-\/。.]+/) .filter(v => !/^\d+[台辆车部]?$/.test(v.trim())) .map(v => v.replace(/[^a-zA-Z0-9]/g, '').toUpperCase()) .filter(v => v !== ''); totalVehicles = vehicles.length; if (totalVehicles > 0) { let currentTabs = Array.from(mainDialog.querySelectorAll('.el-tabs__item')); let tab2 = currentTabs.find(tab => tab.innerText.replace(/\s+/g, '').includes('倒运车辆列表')); if (tab2) { tab2.click(); await new Promise(r => setTimeout(r, 600)); for (let vehicle of vehicles) { let isSuccess = await handleVehicleSelection(mainDialog, vehicle); if (isSuccess) { successCount++; } else { failedVehicles.push(vehicle); } } } } } // ================= 战报弹窗 UI ================= let msg = document.createElement('div'); let reportHtml = `
${failedVehicles.length > 0 ? '⚠️ 填表完成,但有异常' : '✅ 填表与录入完美完成'}
`; if (totalVehicles > 0) { reportHtml += `
`; reportHtml += `
🚗 剪贴板识别总计:${totalVehicles}
`; reportHtml += `
✅ 实际成功录入:${successCount}
`; if (failedVehicles.length > 0) { reportHtml += `
❌ 异常车号拦截:${failedVehicles.length}
`; reportHtml += `
未通过比对:${failedVehicles.join(', ')}
`; } } msg.innerHTML = reportHtml; if (failedVehicles.length > 0) { msg.style.cssText = 'position:fixed; top:30px; left:50%; transform:translateX(-50%); background:#fdf6ec; color:#e6a23c; padding:15px 25px; border-radius:8px; z-index:99999; border:1px solid #faecd8; box-shadow: 0 4px 15px rgba(0,0,0,0.15); line-height:1.4; font-size:14px; min-width: 280px;'; document.body.appendChild(msg); setTimeout(() => msg.remove(), 10000); } else { msg.style.cssText = 'position:fixed; top:30px; left:50%; transform:translateX(-50%); background:#f0f9eb; color:#67c23a; padding:15px 25px; border-radius:8px; z-index:99999; border:1px solid #e1f3d8; box-shadow: 0 4px 15px rgba(0,0,0,0.15); line-height:1.4; font-size:14px; min-width: 250px;'; document.body.appendChild(msg); setTimeout(() => msg.remove(), 4000); } } catch (err) { console.error(err); alert('执行出错,请按 F12 查看控制台!'); } }); // --- 【V5.1 满级修复:冒号绝对优先防误切】 --- function parseClipboardText(rawText) { let cleanText = rawText.replace(/(^["'\s]+)|(["'\s]+$)/g, ''); let data = {}; let lines = cleanText.split(/\r?\n/); let currentKey = null; for (let line of lines) { let splitIndex = -1; let matchLen = 0; // 第一步防线:优先寻找任何形式的冒号 let colonMatch = line.match(/[::∶]/); if (colonMatch && line.indexOf(colonMatch[0]) > 0) { splitIndex = line.indexOf(colonMatch[0]); matchLen = colonMatch[0].length; } else { // 如果实在没冒号,再退而求其次找 Tab 分割 let tabMatch = line.match(/\t+/); if (tabMatch && line.indexOf(tabMatch[0]) > 0) { splitIndex = line.indexOf(tabMatch[0]); matchLen = tabMatch[0].length; } } if (splitIndex > 0) { // 此时截取出的 key 虽然包含被废弃的 Tab,但紧接着的 replace(/\s+/g, '') 会把它当成空气抹去 let key = line.substring(0, splitIndex).replace(/\s+/g, '').trim(); let val = line.substring(splitIndex + matchLen).trim(); currentKey = key; data[currentKey] = val; } else if (currentKey && line.trim() !== '') { data[currentKey] += ' ' + line.trim(); } } return data; } function getInputElementByLabel(dialogScope, labelText) { if (!dialogScope) return null; const labels = Array.from(dialogScope.querySelectorAll('.el-form-item__label')); const targetLabel = labels.find(l => l.innerText.replace(/\s+/g, '').includes(labelText)); return targetLabel ? targetLabel.parentElement.querySelector('input') : null; } function fillInputByLabel(dialogScope, labelText, value) { let inputEle = getInputElementByLabel(dialogScope, labelText); if (inputEle) { inputEle.value = value; inputEle.dispatchEvent(new Event('input', { bubbles: true })); } } async function fillSelectByLabel(dialogScope, labelText, targetData) { let inputEle = getInputElementByLabel(dialogScope, labelText); if (!inputEle) return false; inputEle.click(); await new Promise(resolve => setTimeout(resolve, 400)); let dropdownItems = document.querySelectorAll('.el-select-dropdown:not([style*="display: none"]) .el-select-dropdown__item'); let found = false; let keywords = Array.isArray(targetData) ? targetData : [targetData]; for (let item of dropdownItems) { let itemText = item.innerText.replace(/\s+/g, ''); let isAllMatch = keywords.every(kw => itemText.includes(kw)); if (isAllMatch) { item.click(); found = true; break; } } if (!found) { inputEle.click(); await new Promise(resolve => setTimeout(resolve, 300)); return false; } await new Promise(resolve => setTimeout(resolve, 300)); return true; } // ================= 智能开关控制器 ================= async function toggleSwitchByLabel(dialogScope, labelText, turnOn) { if (!dialogScope) return; const labels = Array.from(dialogScope.querySelectorAll('.el-form-item__label')); const targetLabel = labels.find(l => l.innerText.replace(/\s+/g, '').includes(labelText)); if (targetLabel) { const switchContainer = targetLabel.parentElement.querySelector('.el-switch'); if (switchContainer) { const isCurrentlyOn = switchContainer.classList.contains('is-checked'); if ((turnOn && !isCurrentlyOn) || (!turnOn && isCurrentlyOn)) { switchContainer.click(); await new Promise(r => setTimeout(r, 200)); } } } } // ================= 物料选择专属弹窗 ================= async function handleMaterialSelection(mainDialogScope, targetMaterial) { if (!targetMaterial) return; let openBtns = Array.from(mainDialogScope.querySelectorAll('button')).filter(btn => btn.innerText.replace(/\s+/g, '') === '+选择物料' || btn.innerText.replace(/\s+/g, '') === '选择物料'); if (openBtns.length === 0) return; openBtns[openBtns.length - 1].click(); await new Promise(r => setTimeout(r, 800)); let dialog = getActiveDialog('选择物料'); if (!dialog) return; let searchInput = dialog.querySelector('input[placeholder="关键字查询"]'); if (searchInput) { searchInput.value = targetMaterial; searchInput.dispatchEvent(new Event('input', { bubbles: true })); } let buttons = Array.from(dialog.querySelectorAll('button')); let queryBtn = buttons.find(btn => btn.innerText.replace(/\s+/g, '') === '查询'); if (queryBtn) queryBtn.click(); await new Promise(r => setTimeout(r, 1200)); let rows = dialog.querySelectorAll('.el-table__body-wrapper .el-table__row'); let foundRow = null; for (let row of rows) { let cells = Array.from(row.querySelectorAll('.cell')); if (cells.some(cell => cell.innerText.trim() === targetMaterial)) { foundRow = row; break; } } if (!foundRow) { for (let row of rows) { if (row.innerText.replace(/\s+/g, '').includes(targetMaterial)) { foundRow = row; break; } } } if (!foundRow && rows.length > 0) foundRow = rows[0]; if (foundRow) { let radio = foundRow.querySelector('.el-radio'); if (radio) radio.click(); else foundRow.click(); } else { let closeBtn = dialog.querySelector('.el-dialog__headerbtn'); if(closeBtn) closeBtn.click(); return; } await new Promise(r => setTimeout(r, 400)); let footer = dialog.querySelector('.el-dialog__footer'); if (footer) { let confirmBtns = Array.from(footer.querySelectorAll('button')).filter(btn => btn.innerText.replace(/\s+/g, '') === '确定选择'); if (confirmBtns.length > 0) confirmBtns[0].click(); } await new Promise(r => setTimeout(r, 800)); } // ================= 绝对车牌核对引擎 ================= async function handleVehicleSelection(mainDialogScope, targetVehicle) { if (!targetVehicle) return false; let addBtns = Array.from(mainDialogScope.querySelectorAll('button')).filter(btn => btn.innerText.replace(/\s+/g, '') === '添加车辆' && btn.getBoundingClientRect().width > 0 ); if (addBtns.length === 0) return false; addBtns[0].click(); await new Promise(r => setTimeout(r, 800)); let dialog = getActiveDialog('选择车辆'); if (!dialog) return false; let searchInput = dialog.querySelector('input[placeholder="关键字查询"]'); if (searchInput) { searchInput.value = targetVehicle; searchInput.dispatchEvent(new Event('input', { bubbles: true })); } let queryBtn = Array.from(dialog.querySelectorAll('button')).find(btn => btn.innerText.replace(/\s+/g, '') === '查询'); if (queryBtn) queryBtn.click(); await new Promise(r => setTimeout(r, 1200)); let rows = dialog.querySelectorAll('.el-table__body-wrapper .el-table__row'); let foundRow = null; for (let row of rows) { let cells = Array.from(row.querySelectorAll('.cell')); let isExactMatch = cells.some(cell => cell.innerText.trim().toUpperCase() === targetVehicle.toUpperCase()); if (isExactMatch) { foundRow = row; break; } } if (foundRow) { let checkbox = foundRow.querySelector('.el-checkbox'); if (checkbox) checkbox.click(); else foundRow.click(); } else { console.warn(`[核对失败]:剪贴板车号为 ${targetVehicle},但系统查出的结果不一致,执行安全跳过。`); let closeBtn = dialog.querySelector('.el-dialog__headerbtn'); if(closeBtn) closeBtn.click(); await new Promise(r => setTimeout(r, 600)); return false; } await new Promise(r => setTimeout(r, 400)); let footer = dialog.querySelector('.el-dialog__footer'); if (footer) { let confirmBtns = Array.from(footer.querySelectorAll('button')).filter(btn => btn.innerText.replace(/\s+/g, '') === '确定选择'); if (confirmBtns.length > 0) confirmBtns[0].click(); } await new Promise(r => setTimeout(r, 1000)); return true; } })();