Автоматическое выделение товаров по списку артикулов с поддержкой ленивой загрузки
Size
16.4 KB
Version
1.1.19
Created
Mar 23, 2026
Updated
24 days ago
1// ==UserScript==
2// @name MP Manager - Выделение товаров по артикулам
3// @description Автоматическое выделение товаров по списку артикулов с поддержкой ленивой загрузки
4// @version 1.1.19
5// @match https://*.app.mpmgr.ru/*
6// @icon https://app.mpmgr.ru/favicon.ico
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('MP Manager - Расширение для выделения товаров запущено');
12
13 // Функция для создания UI
14 function createUI() {
15 // Проверяем, что UI еще не создан
16 if (document.getElementById('sku-selector-panel')) {
17 console.log('UI уже создан');
18 return;
19 }
20
21 // Создаем кнопку "Выделить товары"
22 const mainButton = document.createElement('button');
23 mainButton.id = 'sku-selector-button';
24 mainButton.textContent = 'Выделить товары';
25 mainButton.style.cssText = `
26 position: fixed;
27 top: 80px;
28 right: 20px;
29 z-index: 10000;
30 padding: 12px 24px;
31 background: #1976d2;
32 color: white;
33 border: none;
34 border-radius: 4px;
35 font-size: 14px;
36 font-weight: 500;
37 cursor: pointer;
38 box-shadow: 0 2px 8px rgba(0,0,0,0.2);
39 transition: background 0.3s;
40 `;
41 mainButton.onmouseover = () => mainButton.style.background = '#1565c0';
42 mainButton.onmouseout = () => mainButton.style.background = '#1976d2';
43
44 // Создаем панель с textarea
45 const panel = document.createElement('div');
46 panel.id = 'sku-selector-panel';
47 panel.style.cssText = `
48 position: fixed;
49 top: 130px;
50 right: 20px;
51 z-index: 10000;
52 background: white;
53 border-radius: 8px;
54 box-shadow: 0 4px 16px rgba(0,0,0,0.3);
55 padding: 20px;
56 width: 350px;
57 display: none;
58 `;
59
60 const title = document.createElement('div');
61 title.textContent = 'Список артикулов';
62 title.style.cssText = `
63 font-size: 16px;
64 font-weight: 600;
65 margin-bottom: 12px;
66 color: #333;
67 `;
68
69 const textarea = document.createElement('textarea');
70 textarea.id = 'sku-input';
71 textarea.placeholder = 'Введите артикулы (каждый с новой строки)';
72 textarea.style.cssText = `
73 width: 100%;
74 height: 200px;
75 padding: 10px;
76 border: 1px solid #ddd;
77 border-radius: 4px;
78 font-size: 14px;
79 resize: vertical;
80 box-sizing: border-box;
81 font-family: monospace;
82 `;
83
84 const buttonContainer = document.createElement('div');
85 buttonContainer.style.cssText = `
86 display: flex;
87 gap: 10px;
88 margin-top: 12px;
89 `;
90
91 const selectButton = document.createElement('button');
92 selectButton.textContent = 'Выделить';
93 selectButton.style.cssText = `
94 flex: 1;
95 padding: 10px;
96 background: #4caf50;
97 color: white;
98 border: none;
99 border-radius: 4px;
100 font-size: 14px;
101 font-weight: 500;
102 cursor: pointer;
103 transition: background 0.3s;
104 `;
105 selectButton.onmouseover = () => selectButton.style.background = '#45a049';
106 selectButton.onmouseout = () => selectButton.style.background = '#4caf50';
107
108 const cancelButton = document.createElement('button');
109 cancelButton.textContent = 'Отмена';
110 cancelButton.style.cssText = `
111 flex: 1;
112 padding: 10px;
113 background: #f44336;
114 color: white;
115 border: none;
116 border-radius: 4px;
117 font-size: 14px;
118 font-weight: 500;
119 cursor: pointer;
120 transition: background 0.3s;
121 `;
122 cancelButton.onmouseover = () => cancelButton.style.background = '#da190b';
123 cancelButton.onmouseout = () => cancelButton.style.background = '#f44336';
124
125 const statusDiv = document.createElement('div');
126 statusDiv.id = 'sku-status';
127 statusDiv.style.cssText = `
128 margin-top: 12px;
129 padding: 10px;
130 border-radius: 4px;
131 font-size: 13px;
132 display: none;
133 `;
134
135 buttonContainer.appendChild(selectButton);
136 buttonContainer.appendChild(cancelButton);
137 panel.appendChild(title);
138 panel.appendChild(textarea);
139 panel.appendChild(buttonContainer);
140 panel.appendChild(statusDiv);
141
142 document.body.appendChild(mainButton);
143 document.body.appendChild(panel);
144
145 // Обработчики событий
146 mainButton.addEventListener('click', () => {
147 const isVisible = panel.style.display === 'block';
148 panel.style.display = isVisible ? 'none' : 'block';
149 statusDiv.style.display = 'none';
150 });
151
152 cancelButton.addEventListener('click', () => {
153 panel.style.display = 'none';
154 statusDiv.style.display = 'none';
155 });
156
157 selectButton.addEventListener('click', async () => {
158 const skuList = textarea.value
159 .split('\n')
160 .map(sku => sku.trim())
161 .filter(sku => sku.length > 0);
162
163 if (skuList.length === 0) {
164 showStatus('Введите хотя бы один артикул', 'error');
165 return;
166 }
167
168 console.log('Начинаем выделение товаров по артикулам:', skuList);
169 selectButton.disabled = true;
170 selectButton.textContent = 'Обработка...';
171
172 await selectProductsBySKU(skuList);
173
174 selectButton.disabled = false;
175 selectButton.textContent = 'Выделить';
176 });
177
178 console.log('UI создан успешно');
179 }
180
181 // Функция для отображения статуса
182 function showStatus(message, type = 'info') {
183 const statusDiv = document.getElementById('sku-status');
184 if (!statusDiv) return;
185
186 statusDiv.textContent = message;
187 statusDiv.style.display = 'block';
188
189 if (type === 'error') {
190 statusDiv.style.background = '#ffebee';
191 statusDiv.style.color = '#c62828';
192 statusDiv.style.border = '1px solid #ef5350';
193 } else if (type === 'success') {
194 statusDiv.style.background = '#e8f5e9';
195 statusDiv.style.color = '#2e7d32';
196 statusDiv.style.border = '1px solid #66bb6a';
197 } else {
198 statusDiv.style.background = '#e3f2fd';
199 statusDiv.style.color = '#1565c0';
200 statusDiv.style.border = '1px solid #42a5f5';
201 }
202
203 console.log(`Статус: ${message}`);
204 }
205
206 // Функция для прокрутки контейнера с ленивой загрузкой
207 async function scrollToLoadAll() {
208 const scrollContainer = document.querySelector('div.container.css-9hf803');
209 if (!scrollContainer) {
210 console.error('Контейнер для прокрутки не найден');
211 return;
212 }
213
214 console.log('Начинаем прокрутку для загрузки всех товаров');
215
216 const scrollStep = 500; // Прокручиваем по 500px за раз
217 const maxScrollTop = scrollContainer.scrollHeight - scrollContainer.clientHeight;
218 let currentScroll = 0;
219
220 // Прокручиваем постепенно вниз
221 while (currentScroll < maxScrollTop) {
222 currentScroll += scrollStep;
223 scrollContainer.scrollTop = Math.min(currentScroll, maxScrollTop);
224
225 // Ждем загрузки новых элементов
226 await new Promise(resolve => setTimeout(resolve, 300));
227
228 const visibleRows = document.querySelectorAll('tbody tr').length;
229 showStatus(`Прокрутка... (видимых строк: ${visibleRows})`, 'info');
230 console.log(`Прокрутка: ${scrollContainer.scrollTop} / ${maxScrollTop}, строк: ${visibleRows}`);
231 }
232
233 console.log('Прокрутка завершена');
234
235 // Прокручиваем обратно наверх
236 scrollContainer.scrollTop = 0;
237 await new Promise(resolve => setTimeout(resolve, 500));
238
239 const totalRows = document.querySelectorAll('tbody tr').length;
240 console.log(`Видимых строк в таблице: ${totalRows}`);
241
242 return totalRows;
243 }
244
245 // Основная функция для выделения товаров по SKU
246 async function selectProductsBySKU(skuList) {
247 try {
248 const scrollContainer = document.querySelector('div.container.css-9hf803');
249 if (!scrollContainer) {
250 showStatus('Контейнер для прокрутки не найден', 'error');
251 return;
252 }
253
254 showStatus('Поиск и выделение товаров...', 'info');
255 console.log('Начинаем выделение товаров по артикулам:', skuList);
256
257 let selectedCount = 0;
258 let totalProductsCount = 0;
259 const skuSet = new Set(skuList.map(sku => sku.toLowerCase()));
260 const processedSkus = new Set(); // Чтобы не выделять один товар дважды
261 const allSeenSkus = new Set(); // Все уникальные товары на странице
262
263 // Прокручиваем в начало
264 scrollContainer.scrollTop = 0;
265 await new Promise(resolve => setTimeout(resolve, 500));
266
267 const scrollStep = 300; // Уменьшаем шаг скролла с 500 до 300
268 let currentScroll = 0;
269 let previousProductCount = 0;
270 let stableIterations = 0;
271
272 // Прокручиваем постепенно и выделяем товары на каждом шаге
273 while (stableIterations < 5) {
274 // Пересчитываем maxScrollTop на каждой итерации
275 const maxScrollTop = scrollContainer.scrollHeight - scrollContainer.clientHeight;
276
277 // Получаем видимые строки на текущей позиции
278 const rows = document.querySelectorAll('tbody tr');
279 console.log(`Позиция скролла: ${currentScroll}, maxScrollTop: ${maxScrollTop}, видимых строк: ${rows.length}`);
280
281 // Проверяем каждую видимую строку
282 for (const row of rows) {
283 const cells = row.querySelectorAll('td');
284
285 if (cells.length < 3) {
286 continue;
287 }
288
289 // Артикул товара находится в 3-й ячейке (индекс 2)
290 const skuCell = cells[2];
291 const sku = skuCell.textContent.trim().toLowerCase();
292
293 // Считаем уникальные товары
294 if (sku && !allSeenSkus.has(sku)) {
295 allSeenSkus.add(sku);
296 totalProductsCount++;
297 }
298
299 // Проверяем, есть ли артикул в списке и не обработан ли уже
300 if (skuSet.has(sku) && !processedSkus.has(sku)) {
301 console.log(`Найден товар с артикулом: ${sku}`);
302
303 // Находим чекбокс в первой ячейке
304 const checkboxCell = cells[0];
305 const checkboxSpan = checkboxCell.querySelector('span.MuiCheckbox-root');
306 const checkboxInput = checkboxCell.querySelector('input.PrivateSwitchBase-input[type="checkbox"]');
307
308 if (checkboxInput && !checkboxInput.checked && checkboxSpan) {
309 // Полная симуляция клика (mousedown + mouseup + click)
310 const mousedown = new MouseEvent('mousedown', { bubbles: true, cancelable: true });
311 const mouseup = new MouseEvent('mouseup', { bubbles: true, cancelable: true });
312 const click = new MouseEvent('click', { bubbles: true, cancelable: true });
313
314 checkboxSpan.dispatchEvent(mousedown);
315 checkboxSpan.dispatchEvent(mouseup);
316 checkboxSpan.dispatchEvent(click);
317
318 processedSkus.add(sku);
319 selectedCount++;
320 console.log(`Товар с артикулом ${sku} выделен`);
321
322 // Небольшая задержка между кликами
323 await new Promise(resolve => setTimeout(resolve, 150));
324 } else if (checkboxInput && checkboxInput.checked) {
325 console.log(`Товар с артикулом ${sku} уже выделен`);
326 processedSkus.add(sku);
327 } else {
328 console.log(`Чекбокс не найден для артикула ${sku}`);
329 }
330 }
331 }
332
333 // Проверяем, изменилось ли количество товаров
334 if (totalProductsCount === previousProductCount) {
335 stableIterations++;
336 console.log(`Количество товаров стабильно: ${stableIterations}/5`);
337 } else {
338 stableIterations = 0;
339 previousProductCount = totalProductsCount;
340 }
341
342 // Обновляем статус
343 showStatus(`Найдено: ${selectedCount} из ${skuList.length} (всего товаров: ${totalProductsCount})`, 'info');
344
345 // Прокручиваем дальше
346 currentScroll += scrollStep;
347 scrollContainer.scrollTop = Math.min(currentScroll, maxScrollTop);
348
349 // Ждем загрузки новых элементов (увеличена пауза до 800ms)
350 await new Promise(resolve => setTimeout(resolve, 800));
351 }
352
353 if (selectedCount > 0) {
354 showStatus(`Выделено: ${selectedCount} из ${skuList.length} (всего товаров на странице: ${totalProductsCount})`, 'success');
355 } else {
356 showStatus(`Товары не найдены (всего товаров на странице: ${totalProductsCount})`, 'error');
357 }
358
359 console.log(`Выделение завершено. Выделено: ${selectedCount} товаров из ${totalProductsCount} на странице`);
360
361 } catch (error) {
362 console.error('Ошибка при выделении товаров:', error);
363 showStatus('Произошла ошибка при выделении товаров', 'error');
364 }
365 }
366
367 // Инициализация
368 function init() {
369 console.log('Инициализация расширения');
370
371 // Ждем загрузки страницы
372 if (document.readyState === 'loading') {
373 document.addEventListener('DOMContentLoaded', createUI);
374 } else {
375 // Небольшая задержка для загрузки динамического контента
376 setTimeout(createUI, 2000);
377 }
378 }
379
380 init();
381})();