MP Manager - Выделение товаров по артикулам

Автоматическое выделение товаров по списку артикулов с поддержкой ленивой загрузки

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})();
MP Manager - Выделение товаров по артикулам | Robomonkey