您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Filtra AND|OR|NOT e ordena client-side os produtos já carregados, sem infinite scroll. SPA-aware. Limpa filtros e usa preço lado a lado.
// ==UserScript== // @name Shopee – Filtros e Ordenação Local // @namespace http://tampermonkey.net/ // @version 1.6 // @description Filtra AND|OR|NOT e ordena client-side os produtos já carregados, sem infinite scroll. SPA-aware. Limpa filtros e usa preço lado a lado. // @author Você // @match https://shopee.com.br/* // @match https://shopee.com/* // @grant none // ==/UserScript== (function(){ 'use strict'; const containerSelector = 'ul.shopee-search-item-result__items'; // --- Painel de filtros e ordenação --- const panel = document.createElement('div'); panel.id = 'shopee-filter-panel'; panel.innerHTML = ` <div id="shopee-filter-header"> <span>Filtros Shopee</span> <button id="shopee-filter-clear" title="Limpar filtros">✕</button> </div> <div id="shopee-filter-content"> <div class="shopee-filter-row"> <label>Palavras (AND implícito, "|"=OR, "!"=NOT):</label> <input type="text" id="shopee-filter-keywords" placeholder="ex: papel kit|folhas !glossy"> </div> <div class="shopee-filter-row price-row"> <label>Preço de (R$):</label> <input type="number" id="shopee-filter-minprice" placeholder="0" min="0"> <label>até:</label> <input type="number" id="shopee-filter-maxprice" placeholder="0" min="0"> </div> <div class="shopee-filter-row"> <label>Ordenar:</label> <select id="shopee-filter-sort"> <option value="">Relevância</option> <option value="sales,desc">Mais Vendidos</option> <option value="price,asc">Menor Preço</option> <option value="price,desc">Maior Preço</option> </select> </div> </div>`; document.head.insertAdjacentHTML('beforeend',` <style> #shopee-filter-panel { position: fixed; bottom: 20px; right: 20px; width: 300px; background: #fff; border: 1px solid #ccc; box-shadow: 0 2px 8px rgba(0,0,0,.15); z-index: 9999; font-family: Arial,sans-serif; } #shopee-filter-header { display: flex; justify-content: space-between; align-items: center; padding: 8px; background: #f5f5f5; font-weight: bold; } #shopee-filter-clear { background: none; border: none; font-size: 16px; cursor: pointer; } #shopee-filter-content { padding: 8px; } .shopee-filter-row { margin-bottom: 8px; } .shopee-filter-row label { display: block; font-size: 12px; margin-bottom: 4px; } .shopee-filter-row input, .shopee-filter-row select { width: 100%; padding: 4px; font-size: 12px; box-sizing: border-box; } .price-row { display: flex; align-items: center; gap: 4px; } .price-row label { margin-bottom: 0; white-space: nowrap; font-size: 12px; } .price-row input { flex: 1; } .shopee-filter-hidden { display: none !important; } </style>`); document.body.appendChild(panel); // --- Função de filtragem AND|OR|NOT + preço --- function filterProducts() { const raw = document.getElementById('shopee-filter-keywords').value .toLowerCase().trim(); const min = parseFloat(document.getElementById('shopee-filter-minprice').value) || null; const max = parseFloat(document.getElementById('shopee-filter-maxprice').value) || null; const tokens = raw.split(/\s+/).filter(Boolean); const req = [], forbid = [], groups = []; tokens.forEach(tok => { if (tok.startsWith('!')) forbid.push(tok.slice(1)); else if (tok.includes('|')) groups.push(tok.split('|')); else req.push(tok); }); document.querySelectorAll(`${containerSelector} .shopee-search-item-result__item`) .forEach(li => { const title = (li.querySelector('.line-clamp-2')?.innerText || '') .toLowerCase(); let price = null; const base = li.querySelector('.flex.items-baseline'); if (base && base.querySelectorAll('span')[1]) { price = parseFloat( base.querySelectorAll('span')[1].innerText .replace(/\./g,'').replace(',','.') ); } let hide = false; forbid.forEach(k => { if (title.includes(k)) hide = true; }); req.forEach(k => { if (!title.includes(k)) hide = true; }); groups.forEach(arr => { if (!arr.some(k => title.includes(k))) hide = true; }); if (price !== null) { if (min !== null && price < min) hide = true; if (max !== null && max > 0 && price > max) hide = true; } li.classList[hide ? 'add' : 'remove']('shopee-filter-hidden'); }); } // --- Ordenação client-side --- function sortProducts() { const sel = document.getElementById('shopee-filter-sort').value; const cont = document.querySelector(containerSelector); if (!sel || !cont) return; const [key, dir] = sel.split(','); const items = Array.from(cont.querySelectorAll('.shopee-search-item-result__item')) .filter(li => !li.classList.contains('shopee-filter-hidden')); function getVal(li) { if (key === 'price') { const b = li.querySelector('.flex.items-baseline'); return b && b.querySelectorAll('span')[1] ? parseFloat(b.querySelectorAll('span')[1].innerText .replace(/\./g,'').replace(',','.')) : 0; } if (key === 'sales') { const sold = li.querySelector('.flex.items-center .truncate:nth-child(3)')?.innerText || '0'; let n = sold.replace(/\./g,'').replace(/mil/i,'000').replace(/\D+/g,''); return parseInt(n) || 0; } return 0; } items.sort((a, b) => { const va = getVal(a), vb = getVal(b); return dir === 'asc' ? va - vb : vb - va; }); items.forEach(li => cont.appendChild(li)); } // --- Botão limpar filtros --- document.getElementById('shopee-filter-clear').addEventListener('click', () => { ['keywords','minprice','maxprice'].forEach(id => { document.getElementById('shopee-filter-' + id).value = ''; }); filterProducts(); sortProducts(); }); // --- Eventos de input/change para filtros e ordenação --- ['keywords','minprice','maxprice'].forEach(id => { ['input','change'].forEach(evt => { document.getElementById('shopee-filter-' + id) .addEventListener(evt, () => { filterProducts(); sortProducts(); }); }); }); document.getElementById('shopee-filter-sort') .addEventListener('change', sortProducts); // --- SPA-aware: re-aplica ao mudar de página (pushState/popState) --- function onLocationChange(){ filterProducts(); sortProducts(); } ['pushState','replaceState'].forEach(fn => { const orig = history[fn]; history[fn] = function(){ const rv = orig.apply(this, arguments); window.dispatchEvent(new Event('locationchange')); return rv; }; }); window.addEventListener('popstate', () => window.dispatchEvent(new Event('locationchange'))); window.addEventListener('locationchange', onLocationChange); // --- Inicialização --- onLocationChange(); })();