// ==UserScript== // @name Boss QCOS 超级老板 高级商品搜索辅助 // @namespace http://tampermonkey.net/ // @version 26.4.13 // @description 修复打不开面板Bug;恢复空格交叉搜索、大小写自动转换;修复焦点抢夺无法打字问题;支持一键添加商品。 // @author lswlc33 // @match https://boss.qcos.cn/* // @grant GM_addStyle // @grant unsafeWindow // @run-at document-end // ==/UserScript== (function() { 'use strict'; // ================= 配置项 ================= const CONFIG = { ROWS_PER_PAGE: 50, MAX_PAGES: 15, // 最大翻页深度 UI: { Z_INDEX: 999999 } }; // ================= 工具函数 ================= const escapeHTML = (str) => (str || '').toString().replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); const getUid = (item) => item.stockid || item.spxxno; const getStock = (item) => parseInt(item.kcnum || '0', 10); // ================= CSS 样式注入 ================= GM_addStyle(` #tm-search-ball { position: fixed; top: 25px; right: 25px; width: 48px; height: 48px; background: linear-gradient(135deg, #ff3b30, #ff2d55, #af52de); border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 16px rgba(255, 45, 85, 0.4); cursor: pointer; z-index: ${CONFIG.UI.Z_INDEX}; transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); user-select: none; color: white; } #tm-search-panel { position: fixed; top: 85px; right: 25px; width: 500px; background: #ffffff; box-shadow: 0 10px 40px -10px rgba(0,0,0,0.2), 0 0 1px rgba(0,0,0,0.1); border-radius: 12px; z-index: ${CONFIG.UI.Z_INDEX - 1}; display: none; flex-direction: column; overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; opacity: 0; transform: translateY(-10px); transition: opacity 0.3s ease, transform 0.3s ease; } #tm-search-header { padding: 16px 20px; background: #fafafa; border-bottom: 1px solid #f0f0f0; display: flex; justify-content: space-between; align-items: center; cursor: move; user-select: none; } .tm-header-title { font-weight: 600; font-size: 16px; color: #1f1f1f; display: flex; align-items: center; gap: 8px;} .tm-header-title::before { content: ''; display: block; width: 4px; height: 16px; background: linear-gradient(to bottom, #ff2d55, #af52de); border-radius: 2px;} #tm-search-close { cursor: pointer; color: #8c8c8c; font-size: 20px; padding: 4px; } #tm-search-body { padding: 20px; display: flex; flex-direction: column; gap: 16px; background: #fff; } .tm-input-wrapper { display: flex; background: #f5f5f5; border-radius: 24px; padding: 4px 4px 4px 16px; border: 1px solid transparent; align-items: center; } .tm-input-wrapper:focus-within { background: #fff; border-color: #ff2d55; box-shadow: 0 0 0 2px rgba(255, 45, 85, 0.2); } #tm-input-keyword { flex: 1; border: none; background: transparent; outline: none; font-size: 14px; color: #333; } .tm-btn { padding: 8px 20px; background: linear-gradient(135deg, #ff2d55, #af52de); color: #fff; border: none; border-radius: 20px; cursor: pointer; font-weight: 500; font-size: 14px;} .tm-btn-add { width: 24px; height: 24px; border-radius: 6px; border: none; background: linear-gradient(135deg, #ff2d55, #af52de); color: white; cursor: pointer; font-weight: bold; font-size: 16px;} .tm-tools-row { display: flex; align-items: center; gap: 12px; font-size: 13px; color: #595959; } .tm-checkbox-label { display: flex; align-items: center; gap: 6px; cursor: pointer; } #tm-loading-wrapper { display: none; flex-direction: column; align-items: center; padding: 20px 0; color: #af52de; gap: 10px;} .tm-spinner { width: 24px; height: 24px; border: 3px solid rgba(175, 82, 222, 0.2); border-top-color: #af52de; border-radius: 50%; animation: tm-spin 0.8s linear infinite; } @keyframes tm-spin { to { transform: rotate(360deg); } } #tm-results-container { max-height: 400px; overflow-y: auto; border-top: 1px solid #f0f0f0; } #tm-results-table { width: 100%; border-collapse: collapse; font-size: 13px; } #tm-results-table th, #tm-results-table td { padding: 12px 10px; border-bottom: 1px solid #f0f0f0; text-align: left;} #tm-results-table th { background: #fafafa; position: sticky; top: 0; z-index: 10; color: #8c8c8c; } .tm-badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-weight: bold; font-size: 12px; } .tm-stock-in { background: #f6ffed; color: #52c41a; border: 1px solid #b7eb8f; } .tm-stock-out { background: #fff2f0; color: #ff4d4f; border: 1px solid #ffccc7; } .tm-dragging-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: ${CONFIG.UI.Z_INDEX - 2}; display: none; cursor: move; } `); // ================= UI 结构 ================= const ball = document.createElement('div'); ball.id = 'tm-search-ball'; ball.innerHTML = ``; document.body.appendChild(ball); const panel = document.createElement('div'); panel.id = 'tm-search-panel'; panel.innerHTML = `
高级库存搜索 ×
总 0, 显示 0
深度合并检索中...
商品名称库存
`; document.body.appendChild(panel); const dragOverlay = document.createElement('div'); dragOverlay.className = 'tm-dragging-overlay'; document.body.appendChild(dragOverlay); const dom = { ball: document.getElementById('tm-search-ball'), panel: document.getElementById('tm-search-panel'), header: document.getElementById('tm-search-header'), // <-- 就是这里刚刚漏掉了! close: document.getElementById('tm-search-close'), input: document.getElementById('tm-input-keyword'), btnSearch: document.getElementById('tm-btn-search'), filter: document.getElementById('tm-filter-stock'), summary: document.getElementById('tm-summary-text'), sort: document.getElementById('tm-sort-select'), tbody: document.getElementById('tm-results-tbody'), loading: document.getElementById('tm-loading-wrapper'), tableContainer: document.getElementById('tm-results-container') }; // ================= 焦点锁定修复 ================= const togglePanel = () => { if (dom.panel.style.display === 'flex') { dom.panel.style.display = 'none'; if(unsafeWindow.salesBilling) unsafeWindow.isNeedToNextFoces = true; } else { dom.panel.style.display = 'flex'; dom.panel.style.opacity = '1'; if(unsafeWindow.salesBilling) unsafeWindow.isNeedToNextFoces = false; setTimeout(() => dom.input.focus(), 150); } }; dom.input.addEventListener('keydown', (e) => { e.stopPropagation(); if (e.key === 'Enter') handleSearch(); }, true); dom.input.addEventListener('mousedown', (e) => { e.stopPropagation(); dom.input.focus(); }, true); // ================= 高级检索逻辑 (交叉搜索 + 大小写) ================= async function fetchPageData(keyword) { let allData = [], page = 1; while (page <= CONFIG.MAX_PAGES) { try { const data = await new Promise((resolve) => { unsafeWindow.$.ajax({ url: '/b2cSaleorder/queryCommodityInfoList', data: { spxxname: keyword, page: page, rows: CONFIG.ROWS_PER_PAGE }, success: (res) => resolve(typeof res === 'string' ? JSON.parse(res) : res) }); }); const rows = data.rows || []; allData = allData.concat(rows); if (rows.length === 0 || allData.length >= (data.total || 0)) break; page++; } catch (e) { break; } } return allData; } async function executeAdvancedSearch(inputText) { const terms = inputText.trim().split(/\s+/).filter(t => t); if (terms.length === 0) return []; let allTermResults = []; for (let term of terms) { const upper = term.toUpperCase(); const lower = term.toLowerCase(); // 并发请求大小写 const tasks = [fetchPageData(upper)]; if (upper !== lower) tasks.push(fetchPageData(lower)); const results = await Promise.all(tasks); const uniqueMap = new Map(); results.flat().forEach(item => uniqueMap.set(getUid(item), item)); allTermResults.push(Array.from(uniqueMap.values())); } // 求交集:必须包含所有关键词 let finalIntersection = allTermResults[0]; for (let i = 1; i < allTermResults.length; i++) { const currentIds = new Set(allTermResults[i].map(getUid)); finalIntersection = finalIntersection.filter(item => currentIds.has(getUid(item))); } return finalIntersection; } // ================= 系统对接逻辑 ================= const addToSalesList = (item) => { const jq = unsafeWindow.$; const salesBilling = unsafeWindow.salesBilling; if (unsafeWindow.checkSpxxIsExist(item.spxxno)) { salesBilling.addSpxx(item.barCode || " "); return; } let targetTr = null; jq('.sales-tr').each(function() { if (!jq(this).find('input[name=spxxno]').val()) { targetTr = jq(this); return false; } }); if (!targetTr) { jq('.add-goods').click(); targetTr = jq('.sales-tr:last'); } const rownum = targetTr.find('input[type=hidden]:first').attr('rownum'); jq('#totalprice' + rownum).html(item.lsprice); jq("#spxxname" + rownum).val(item.spxxname); jq("#spxxno" + rownum).val(item.spxxno); jq('#spmodel' + rownum).val(item.spmodel || ""); if (item.lsprice != 0) jq("#spprice" + rownum).val(item.lsprice); jq("#barCode" + rownum).val(item.barCode || " ").attr("disabled", true); jq("#categoryTotalPrice" + rownum).text(item.lsprice); jq("#spnum" + rownum).val("1"); salesBilling.getTotal(); jq("#pay_money, #ysmoney").val(jq("#totalprice").val()); const scrollDiv = document.getElementById('scrolldIV'); if(scrollDiv) scrollDiv.scrollTop = scrollDiv.scrollHeight; }; // ================= 主控流 ================= let rawCache = []; async function handleSearch() { if (!dom.input.value.trim()) return; dom.btnSearch.disabled = true; dom.loading.style.display = 'flex'; dom.tableContainer.style.display = 'none'; try { rawCache = await executeAdvancedSearch(dom.input.value); renderTable(); } finally { dom.btnSearch.disabled = false; dom.loading.style.display = 'none'; dom.tableContainer.style.display = 'block'; } } function renderTable() { dom.tbody.innerHTML = ''; let list = dom.filter.checked ? rawCache.filter(i => getStock(i) > 0) : [...rawCache]; const sortMode = dom.sort.value; if (sortMode === 'stockDesc') list.sort((a, b) => getStock(b) - getStock(a)); else if (sortMode === 'stockAsc') list.sort((a, b) => getStock(a) - getStock(b)); else if (sortMode === 'nameAsc') list.sort((a, b) => (a.spxxname || '').localeCompare(b.spxxname || '')); dom.summary.innerText = `总 ${rawCache.length}, 显示 ${list.length}`; list.forEach(item => { const tr = document.createElement('tr'); const stock = getStock(item); tr.innerHTML = ` ${stock > 0 ? '' : '-'} ${escapeHTML(item.spxxname)} ${stock} `; const btn = tr.querySelector('.tm-btn-add'); if(btn) btn.onclick = () => addToSalesList(item); dom.tbody.appendChild(tr); }); } // ================= 拖拽逻辑 ================= let isDragging = false, dragX, dragY, initL, initT; dom.header.onmousedown = (e) => { if(e.target.id === 'tm-search-close') return; isDragging = true; dragX = e.clientX; dragY = e.clientY; const rect = dom.panel.getBoundingClientRect(); dom.panel.style.right = 'auto'; dom.panel.style.bottom = 'auto'; initL = rect.left; initT = rect.top; dragOverlay.style.display = 'block'; e.preventDefault(); }; document.onmousemove = (e) => { if (!isDragging) return; dom.panel.style.left = (initL + (e.clientX - dragX)) + 'px'; dom.panel.style.top = (initT + (e.clientY - dragY)) + 'px'; }; document.onmouseup = () => { isDragging = false; dragOverlay.style.display = 'none'; }; // ================= 事件绑定 ================= dom.ball.onclick = togglePanel; dom.close.onclick = togglePanel; dom.btnSearch.onclick = handleSearch; dom.filter.onchange = renderTable; dom.sort.onchange = renderTable; })();