|
Поговорим о...
|
|
Закрепим кнопку "Вернуться". Вот необходимые изменения:
1. Обновите HTML структуру:
```html <!-- В listsContainer измените порядок и добавьте контейнер для закрепленной кнопки --> <div class="lists-container" id="listsContainer"> <!-- Закрепленная кнопка возврата --> <div class="sticky-header"> <button class="back-button" id="backButton" style="display: none;">← Вернуться к списку файлов</button> </div> <!-- Список файлов --> <ul class="file-list" id="fileList"> <li class="file-item">Выберите папку с архивами</li> </ul> <!-- Список скважин --> <ul class="wells-list" id="wellsList"> <!-- Список скважин будет заполняться динамически --> </ul> </div> ```
2. Добавьте CSS для закрепленной кнопки:
```css /* Стили для закрепленного заголовка */ .sticky-header { position: sticky; top: 0; z-index: 100; background-color: #2c3e50; padding: 0; margin: 0 -20px; padding: 0 20px 10px 20px; border-bottom: 1px solid #34495e; }
/* Обновляем стиль кнопки возврата */ .back-button { background-color: #e67e22; color: white; border: none; padding: 10px 15px; border-radius: 4px; cursor: pointer; margin-top: 10px; width: 100%; text-align: center; transition: background-color 0.2s; font-weight: 500; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); }
.back-button:hover { background-color: #d35400; transform: translateY(-1px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); } ```
3. Обновите функцию showWellsList:
```javascript // Показать список скважин для мульти-файла function showWellsList(wells, fileName, fileCount = 1) { const wellsList = document.getElementById('wellsList'); const fileList = document.getElementById('fileList'); const backButton = document.getElementById('backButton'); wellsList.innerHTML = ''; // Создаем заголовок для списка скважин const wellsHeader = document.createElement('div'); wellsHeader.className = 'wells-header'; wellsHeader.innerHTML = ` <div class="wells-header-content"> <h3>Список скважин</h3> <div class="wells-header-info">Файл: ${fileName}${fileCount > 1 ? ` (${fileCount} файлов)` : ''}</div> <div class="wells-header-count">Найдено скважин: ${wells.length}</div> </div> `; wellsList.appendChild(wellsHeader); // Добавляем скважины wells.forEach((well, index) => { const listItem = document.createElement('li'); listItem.className = 'well-item'; listItem.innerHTML = ` <div class="file-number">${index + 1}</div> <div class="well-content"> <div class="well-name">${well.wellName || 'Без названия'}</div> <div class="well-info">ID: ${well.wellId} • ${well.object || 'Объект не указан'}</div> </div> `; listItem.addEventListener('click', () => selectWell(well.wellId, index + 1)); wellsList.appendChild(listItem); }); // Показываем список скважин и скрываем список файлов wellsList.style.display = 'block'; fileList.style.display = 'none'; // Показываем кнопку возврата (она закреплена в sticky-header) backButton.style.display = 'block'; // Обновляем счетчик внизу document.getElementById('fileCounter').textContent = `Найдено скважин: ${wells.length}`; } ```
4. Обновите CSS для wells-header:
```css /* Стили для заголовка списка скважин */ .wells-header { background-color: #34495e; padding: 15px; margin-bottom: 10px; border-radius: 4px; border-left: 4px solid #27ae60; margin-top: 10px; /* Отступ от закрепленной кнопки */ }
.wells-header-content h3 { color: #ecf0f1; margin-bottom: 5px; font-size: 1.1rem; font-weight: 600; }
.wells-header-info { color: #bdc3c7; font-size: 0.8rem; margin-bottom: 3px; }
.wells-header-count { color: #27ae60; font-size: 0.8rem; font-weight: 600; } ```
5. Обновите функцию backToFileList для скрытия кнопки:
```javascript // Функция возврата к списку файлов function backToFileList() { document.getElementById('wellsList').style.display = 'none'; document.getElementById('fileList').style.display = 'block'; document.getElementById('backButton').style.display = 'none'; // Обновляем счетчик document.getElementById('fileCounter').textContent = `Найдено архивов: ${document.getElementById('fileList').children.length}`; // Сбрасываем выбранную скважину currentWellId = null; currentMultiWellData = null; currentMultiFileName = null; // Показываем сообщение о выборе файла (без спиннера) document.getElementById('wellData').innerHTML = ` <div class="loading"> <p>Выберите файл из списка для отображения данных</p> </div> `; document.getElementById('wellName').innerHTML = 'Выберите скважину для просмотра'; } ```
Теперь кнопка "Вернуться к списку файлов":
· ✅ Закреплена вверху списка скважин · ✅ Не прокручивается вместе с контентом · ✅ Всегда видна при скроллинге длинного списка скважин · ✅ Имеет тень и эффекты при наведении для лучшей видимости · ✅ Расположена над заголовком "Список скважин"
Это делает навигацию намного удобнее, особенно при работе с большими списками скважин!
|
Отлично! Оптимизируем загрузку мульти-файлов. Вот ключевые улучшения:
1. Добавим обработку больших файлов по частям:
```javascript // Функция для обработки больших XML файлов по частям async function processLargeXMLFile(xmlContent, fileName, onProgress) { return new Promise((resolve, reject) => { try { let wells = []; let currentPosition = 0; const chunkSize = 1024 * 1024; // 1MB chunks const totalLength = xmlContent.length; function processChunk() { const chunkEnd = Math.min(currentPosition + chunkSize, totalLength); const chunk = xmlContent.substring(currentPosition, chunkEnd); // Ищем скважины в текущем чанке const wellMatches = chunk.match(/<MR_DBA\.POINT_OBSERV_POAA[\s\S]*?<\/MR_DBA\.POINT_OBSERV_POAA>/g) || []; wellMatches.forEach(wellXml => { try { const parser = new DOMParser(); const wellDoc = parser.parseFromString(wellXml, 'text/xml'); const wellNode = wellDoc.documentElement; const wellId = getSafeTextContent(wellNode, 'ID_POINT_OBSERV_POAA'); const wellName = getSafeTextContent(wellNode, 'NAME_FULL_POAA'); const object = getSafeTextContent(wellNode, 'ID_OBJECT_WORK_POAA'); const description = getSafeTextContent(wellNode, 'DESCRIPTION_POAA'); if (wellId && wellId !== 'Не указано') { wells.push({ wellId: wellId, wellName: wellName, object: object, description: description, sourceFile: fileName }); } } catch (e) { console.warn('Ошибка парсинга скважины:', e); } }); currentPosition = chunkEnd; // Обновляем прогресс if (onProgress) { const progress = (currentPosition / totalLength) * 100; onProgress(progress, `Обработано ${Math.round(progress)}%`); } // Продолжаем обработку или завершаем if (currentPosition < totalLength) { // Даем браузеру "подышать" между чанками setTimeout(processChunk, 10); } else { resolve(wells); } } // Начинаем обработку processChunk(); } catch (error) { reject(error); } }); } ```
2. Обновите функцию extractWellsFromMultiFile для обработки больших файлов:
```javascript // Функция для извлечения скважин из мульти-файла async function extractWellsFromMultiFile(xmlDoc, xmlContent = null, fileName = '', onProgress = null) { // Если передан xmlContent (большой файл), используем потоковую обработку if (xmlContent && xmlContent.length > 5 * 1024 * 1024) { // Больше 5MB console.log(`Используем потоковую обработку для файла ${fileName} (${(xmlContent.length / 1024 / 1024).toFixed(2)} MB)`); return await processLargeXMLFile(xmlContent, fileName, onProgress); } // Стандартная обработка для маленьких файлов const wells = []; const wellNodes = xmlDoc.getElementsByTagName('MR_DBA.POINT_OBSERV_POAA'); console.log(`Стандартная обработка: найдено ${wellNodes.length} узлов скважин`); // Ограничиваем количество обрабатываемых скважин за один раз const batchSize = 100; let processed = 0; for (let i = 0; i < wellNodes.length; i++) { const wellNode = wellNodes[i]; const wellId = getSafeTextContent(wellNode, 'ID_POINT_OBSERV_POAA'); const wellName = getSafeTextContent(wellNode, 'NAME_FULL_POAA'); const object = getSafeTextContent(wellNode, 'ID_OBJECT_WORK_POAA'); const description = getSafeTextContent(wellNode, 'DESCRIPTION_POAA'); if (wellId && wellId !== 'Не указано') { wells.push({ wellId: wellId, wellName: wellName, object: object, description: description, sourceFile: fileName }); } processed++; // Даем браузеру "подышать" каждые batchSize скважин if (processed % batchSize === 0) { if (onProgress) { const progress = (processed / wellNodes.length) * 100; onProgress(progress, `Обработано ${processed} из ${wellNodes.length} скважин`); } await new Promise(resolve => setTimeout(resolve, 0)); } } return wells; } ```
3. Обновите обработку мульти-файлов в loadWellData:
```javascript // В блоке обработки мульти-файлов замените обработку каждого файла: for (let i = 0; i < multiFiles.length; i++) { const multiFile = multiFiles[i]; const progress = 10 + (i / multiFiles.length) * 70; updateProgress(progress, `Файл ${i + 1} из ${multiFiles.length}: ${multiFile.name}`); console.log('Обработка файла:', multiFile.name, 'Размер:', multiFile.size); // Увеличиваем лимит размера INU файла до 2000MB if (multiFile.size > 2000 * 1024 * 1024) { console.warn(`Файл ${multiFile.name} слишком большой, пропускаем`); continue; } try { // Читаем XML данные updateLoadingText(`Чтение файла ${i + 1} из ${multiFiles.length}...`); const xmlContent = await multiFile.entry.async('text'); console.log(`XML прочитан из ${multiFile.name}, длина:`, xmlContent.length); // Очищаем XML от некорректных символов const cleanedContent = cleanXMLContent(xmlContent); let wellsFromFile = []; // Для больших файлов используем DOM парсер только если нужно if (cleanedContent.length < 10 * 1024 * 1024) { // Меньше 10MB const parser = new DOMParser(); const xmlDoc = parser.parseFromString(cleanedContent, 'text/xml'); // Проверяем на ошибки парсинга const parseError = xmlDoc.getElementsByTagName('parsererror')[0]; if (parseError) { console.error(`Ошибка парсинга XML в файле ${multiFile.name}:`, parseError.textContent); continue; } allXmlDocs.push(xmlDoc); // Извлекаем скважины из этого файла updateLoadingText(`Извлечение данных из файла ${i + 1}...`); wellsFromFile = await extractWellsFromMultiFile( xmlDoc, null, multiFile.name, (progress, details) => { const overallProgress = 10 + (i / multiFiles.length) * 70 + (progress / 100) * (70 / multiFiles.length); updateProgress(overallProgress, details); } ); } else { // Для очень больших файлов используем потоковую обработку updateLoadingText(`Потоковая обработка файла ${i + 1}...`); wellsFromFile = await extractWellsFromMultiFile( null, cleanedContent, multiFile.name, (progress, details) => { const overallProgress = 10 + (i / multiFiles.length) * 70 + (progress / 100) * (70 / multiFiles.length); updateProgress(overallProgress, details); } ); } console.log(`Из файла ${multiFile.name} извлечено скважин:`, wellsFromFile.length); // Добавляем скважины в общий список (исключая дубликаты по ID) wellsFromFile.forEach(well => { if (!allWells.some(existingWell => existingWell.wellId === well.wellId)) { allWells.push(well); } }); } catch (error) { console.error(`Ошибка обработки файла ${multiFile.name}:`, error); } } ```
4. Добавим ограничение на максимальное количество скважин:
```javascript // Добавьте эту проверку после обработки всех файлов const MAX_WELLS = 1000; // Максимальное количество скважин для отображения
if (allWells.length > MAX_WELLS) { console.warn(`Обнаружено ${allWells.length} скважин, ограничиваем до ${MAX_WELLS}`); allWells = allWells.slice(0, MAX_WELLS); // Показываем предупреждение setTimeout(() => { alert(`Внимание: В файле обнаружено более ${MAX_WELLS} скважин. Отображены только первые ${MAX_WELLS} скважин. Для просмотра всех данных рекомендуется использовать специализированное ПО.`); }, 1000); } ```
5. Добавим кэширование для уже обработанных файлов:
```javascript // Добавьте глобальную переменную для кэша let fileCache = new Map();
// Функция для получения хэша файла async function getFileHash(fileEntry) { const content = await fileEntry.async('text'); return btoa(content).substring(0, 100); // Простой хэш }
// В обработке файлов добавьте проверку кэша: for (let i = 0; i < multiFiles.length; i++) { const multiFile = multiFiles[i]; // Проверяем кэш const fileHash = await getFileHash(multiFile.entry); if (fileCache.has(fileHash)) { console.log(`Используем кэш для файла ${multiFile.name}`); const cachedWells = fileCache.get(fileHash); cachedWells.forEach(well => { if (!allWells.some(existingWell => existingWell.wellId === well.wellId)) { allWells.push(well); } }); continue; } // ... существующий код обработки файла ... // Сохраняем в кэш fileCache.set(fileHash, wellsFromFile); } ```
6. Добавим возможность отмены загрузки:
```javascript // Добавьте глобальную переменную let abortLoading = false;
// Функция для отмены загрузки function cancelLoading() { abortLoading = true; hideLoading(); }
// Обновите showLoading для добавления кнопки отмены function showLoading(message = 'Загрузка данных...') { const overlay = document.getElementById('loadingOverlay'); const loadingText = document.getElementById('loadingText'); const progressFill = document.getElementById('progressFill'); const progressText = document.getElementById('progressText'); const loadingDetails = document.getElementById('loadingDetails'); // Сбрасываем флаг отмены abortLoading = false; loadingText.textContent = message; progressFill.style.width = '0%'; progressText.textContent = '0%'; loadingDetails.textContent = ''; // Добавляем кнопку отмены if (!document.getElementById('cancelButton')) { const cancelButton = document.createElement('button'); cancelButton.id = 'cancelButton'; cancelButton.className = 'cancel-button'; cancelButton.textContent = 'Отмена'; cancelButton.onclick = cancelLoading; document.querySelector('.loading-content').appendChild(cancelButton); } overlay.style.display = 'flex'; loadingText.classList.add('pulse'); }
// Добавьте CSS для кнопки отмены .cancel-button { background-color: #e74c3c; color: white; border: none; padding: 8px 15px; border-radius: 4px; cursor: pointer; margin-top: 10px; transition: background-color 0.2s; }
.cancel-button:hover { background-color: #c0392b; }
// В функциях обработки добавьте проверку на отмену if (abortLoading) { throw new Error('Загрузка отменена пользователем'); } ```
Эти оптимизации позволят:
· ✅ Обрабатывать файлы любого размера по частям · ✅ Не блокировать интерфейс браузера · ✅ Ограничить максимальное количество скважин · ✅ Кэшировать уже обработанные файлы · ✅ Предоставить возможность отмены загрузки · ✅ Показывать детальный прогресс обработки
Теперь даже файлы с тысячами скважин должны обрабатываться стабильно!
|
Вот полный код функции parseSingleWellData:
```javascript // Парсинг данных для одиночной скважины async function parseSingleWellData(xmlDoc, fileType) { // Извлекаем данные о скважине const pointObserv = xmlDoc.getElementsByTagName('MR_DBA.POINT_OBSERV_POAA')[0]; const objectWork = xmlDoc.getElementsByTagName('MR_DBA.OBJECT_WORK_OBWA')[0]; const ground = xmlDoc.getElementsByTagName('MR_DBA.GROUND_GRNA')[0]; const pointObservType = xmlDoc.getElementsByTagName('MR_DBA.POINT_OBSERV_TYPE_PNOT')[0]; const lineCommon = xmlDoc.getElementsByTagName('MR_DBA.LINE_COMMON_LNCM')[0]; // Основная информация о скважине const wellName = getSafeTextContent(pointObserv, 'NAME_FULL_POAA'); const object = getSafeTextContent(objectWork, 'NAME_FULL_OBWA'); const area = getSafeTextContent(ground, 'NAME_FULL_GRNA'); const wellType = getSafeTextContent(pointObservType, 'NAME_SHORT_PNOT'); const lineNumber = getSafeTextContent(lineCommon, 'NAME_FULL_LNCM'); const wellDescription = getSafeTextContent(pointObserv, 'DESCRIPTION_POAA'); // Даты бурения и бригада let drillingStartDate = ''; let drillingEndDate = ''; let drillingBrigade = 'Не указано'; // Ищем даты бурения и бригаду drillingStartDate = formatDate(getSafeTextContent(pointObserv, 'DATE_DRIFTING_BEG_POAA')); drillingEndDate = formatDate(getSafeTextContent(pointObserv, 'DATE_DRIFTING_END_POAA')); // Получаем название бригады const brigadeId = getSafeTextContent(pointObserv, 'BRIGADE_POAA'); if (brigadeId && brigadeId !== 'Не указано') { // Ищем название бригады в OUR_DIVISION const divisionNodes = xmlDoc.getElementsByTagName('MR_DBA.OUR_DIVISION'); for (let i = 0; i < divisionNodes.length; i++) { const division = divisionNodes[i]; const divisionId = getSafeTextContent(division, 'ID_CONTRACTOR'); if (divisionId === brigadeId) { const brigadeName = getSafeTextContent(division, 'NAME_SHORT'); if (brigadeName && brigadeName !== 'Не указано') { drillingBrigade = brigadeName; break; } } } // Если не нашли название, используем ID if (drillingBrigade === 'Не указано') { drillingBrigade = `Бригада ${brigadeId}`; } } // Создаем карту сотрудников const employeeMap = new Map(); const employeeNodes = xmlDoc.getElementsByTagName('MR_DBA.OUR_EMPLOYEE'); for (let i = 0; i < employeeNodes.length; i++) { const employee = employeeNodes[i]; const employeeId = getSafeTextContent(employee, 'ID_CONTRACTOR'); const employeeName = getSafeTextContent(employee, 'NAME_SHORT'); if (employeeId && employeeName && employeeId !== 'Не указано' && employeeName !== 'Не указано') { employeeMap.set(employeeId, employeeName); } } // Данные документирования let primaryDocInfo = null; let finalDocInfo = null; let gisDocInfo = null; const docNodes = xmlDoc.getElementsByTagName('MR_DBA.PO_DOCUM_PODC'); for (let i = 0; i < docNodes.length; i++) { const doc = docNodes[i]; const docType = getSafeTextContent(doc, 'ID_TYPE_DOCUM_PODC'); const startDate = formatDate(getSafeTextContent(doc, 'DATE_BEG_DOCUM_PODC')); const endDate = formatDate(getSafeTextContent(doc, 'DATE_END_DOCUM_PODC')); const depth = formatNumber(getSafeTextContent(doc, 'DEPTH_FACT_PODC')); const authorId = getSafeTextContent(doc, 'AUTHOR_PODC'); let authorName = 'Не указано'; if (authorId && authorId !== 'Не указано' && employeeMap.has(authorId)) { authorName = employeeMap.get(authorId); } else if (authorId && authorId !== 'Не указано') { authorName = `Код: ${authorId}`; } const docInfo = { startDate, endDate, depth, author: authorName }; if (docType.includes('Первич') || docType.includes('ПЕРВИЧ') || docType === '1') { primaryDocInfo = docInfo; } else if (docType.includes('Итогов') || docType.includes('ИТОГОВ') || docType === '2') { finalDocInfo = docInfo; } else if (docType.includes('ГИС') || docType.includes('геофизик')) { gisDocInfo = docInfo; } } // Создаем карту типов опробования const assayTypeMap = new Map(); const assayTypeNodes = xmlDoc.getElementsByTagName('MR_DBA.ASSAY_TYPE_PROP_ASTR'); for (let i = 0; i < assayTypeNodes.length; i++) { const assayType = assayTypeNodes[i]; const id = getSafeTextContent(assayType, 'ID_ASSAY_TYPE_PROP_ASTR'); const name = getSafeTextContent(assayType, 'NAME_FULL_ASTR'); if (id && name && id !== 'Не указано' && name !== 'Не указано') { assayTypeMap.set(id, name); } } // Создаем карту типов документирования для интервалов const docTypeMap = new Map(); const docNodesForIntervals = xmlDoc.getElementsByTagName('MR_DBA.PO_DOCUM_PODC'); for (let i = 0; i < docNodesForIntervals.length; i++) { const doc = docNodesForIntervals[i]; const docId = getSafeTextContent(doc, 'ID_PO_DOCUM_PODC'); const docType = getSafeTextContent(doc, 'ID_TYPE_DOCUM_PODC'); if (docId && docType && docId !== 'Не указано' && docType !== 'Не указано') { docTypeMap.set(docId, docType); } } // Создаем карту классификаторов const classifierMap = new Map(); const classifierNodes = xmlDoc.getElementsByTagName('MR_DBA.CLASSIFIER'); for (let i = 0; i < classifierNodes.length; i++) { const classifier = classifierNodes[i]; const nodeId = getSafeTextContent(classifier, 'ID_NODE_ID'); const nodeName = getSafeTextContent(classifier, 'NODE_NAME'); if (nodeId && nodeName && nodeId !== 'Не указано' && nodeName !== 'Не указано') { classifierMap.set(nodeId, nodeName); } } // Создаем карту единиц измерения const measureMap = new Map(); const measureNodes = xmlDoc.getElementsByTagName('MR_DBA.MEASURE'); for (let i = 0; i < measureNodes.length; i++) { const measure = measureNodes[i]; const measureId = getSafeTextContent(measure, 'ID_MEASURE'); const measureName = getSafeTextContent(measure, 'NAME_SHORT'); if (measureId && measureName && measureId !== 'Не указано' && measureName !== 'Не указано') { measureMap.set(measureId, measureName); } } // Извлекаем интервалы документирования const primaryIntervals = []; const finalIntervals = []; const gisIntervals = [];
// Обрабатываем интервалы документирования const intervalNodes = xmlDoc.getElementsByTagName('MR_DBA.PO_DOCUM_INT_PODI'); for (let i = 0; i < intervalNodes.length; i++) { const interval = intervalNodes[i]; const from = parseFloat(getSafeTextContent(interval, 'PO_DOCUM_FROM_PODI') || '0'); const to = parseFloat(getSafeTextContent(interval, 'PO_DOCUM_TO_PODI') || '0'); const thickness = parseFloat(getSafeTextContent(interval, 'THICKNESS_PODI') || '0'); const description = getSafeTextContent(interval, 'DESCRIPTION_PODI'); const docId = getSafeTextContent(interval, 'ID_PO_DOCUM_PODI'); const numberPP = parseInt(getSafeTextContent(interval, 'NUMBER_PP_PODI') || '0'); // Определяем тип документирования let documentationType = 'Не определено'; if (docId && docId !== 'Не указано' && docTypeMap.has(docId)) { documentationType = docTypeMap.get(docId); } // Получаем литологию const lithologyId = getSafeTextContent(interval, 'ID_LITHOLOGY_PODI'); let lithology = 'Не указано'; if (lithologyId && classifierMap.has(lithologyId)) { lithology = classifierMap.get(lithologyId); } // Получаем стратиграфию const stratigraphyId = getSafeTextContent(interval, 'ID_STRATIGRAPHY_PODI'); let stratigraphy = 'Не указано'; if (stratigraphyId && classifierMap.has(stratigraphyId)) { stratigraphy = convertStratigraphyText(classifierMap.get(stratigraphyId)); } const intervalData = { numberPP, from, to, thickness, stratigraphy, lithology, description }; // Распределяем интервалы по типам документирования if ((documentationType.includes('Первич') || documentationType.includes('ПЕРВИЧ') || documentationType === '1') && !documentationType.includes('ГИС') && !documentationType.includes('геофизик')) { primaryIntervals.push(intervalData); } else if ((documentationType.includes('Итогов') || documentationType.includes('ИТОГОВ') || documentationType === '2') && !documentationType.includes('ГИС') && !documentationType.includes('геофизик')) { finalIntervals.push(intervalData); } else if (documentationType.includes('ГИС') || documentationType.includes('геофизик')) { // Добавляем в ГИС интервалы gisIntervals.push(intervalData); } else { // Если тип не определен, добавляем в оба списка для файлов типа "all" if (fileType === 'all' && !documentationType.includes('ГИС') && !documentationType.includes('геофизик')) { primaryIntervals.push(intervalData); finalIntervals.push(intervalData); } else if (fileType === 'primary' && !documentationType.includes('ГИС') && !documentationType.includes('геофизик')) { primaryIntervals.push(intervalData); } else if (fileType === 'final' && !documentationType.includes('ГИС') && !documentationType.includes('геофизик')) { finalIntervals.push(intervalData); } } } // СОРТИРУЕМ интервалы по порядковому номеру primaryIntervals.sort((a, b) => a.numberPP - b.numberPP); finalIntervals.sort((a, b) => a.numberPP - b.numberPP); gisIntervals.sort((a, b) => a.numberPP - b.numberPP); // Извлекаем данные опробования const assays = []; const assayNodes = xmlDoc.getElementsByTagName('MR_DBA.ASSAY_ASSA'); for (let i = 0; i < assayNodes.length; i++) { const assay = assayNodes[i]; const number = parseInt(getSafeTextContent(assay, 'ASSAY_NUMBER_ASSA') || '0'); const from = parseFloat(getSafeTextContent(assay, 'INTERV_FROM_ASSA') || '0'); const to = parseFloat(getSafeTextContent(assay, 'INTERV_TO_ASSA') || '0'); const assayId = getSafeTextContent(assay, 'ID_ASSAY_ASSA'); const description = getSafeTextContent(assay, 'DESCRIPTION_ASSA'); // Определяем тип опробования по ID_ASSAY_TYPE_PROP_ASSA const assayTypeId = getSafeTextContent(assay, 'ID_ASSAY_TYPE_PROP_ASSA'); let type = 'Неизвестно'; if (assayTypeId && assayTypeMap.has(assayTypeId)) { type = assayTypeMap.get(assayTypeId); } // Определяем тип документирования для пробы let documentationType = 'Не определено'; // Проверяем историю опробования для определения типа документирования const assayHistoryNodes = xmlDoc.getElementsByTagName('MR_DBA.ASSAY_HISTORY_ASHI'); for (let j = 0; j < assayHistoryNodes.length; j++) { const history = assayHistoryNodes[j]; const historyAssayId = getSafeTextContent(history, 'ID_ASSAY_ASHI'); if (historyAssayId === assayId) { const docTypeId = getSafeTextContent(history, 'ID_TYPE_DOCUM_ASHI'); if (docTypeId && docTypeId !== 'Не указано') { // Определяем тип документирования по ID_TYPE_DOCUM_ASHI if (docTypeId.includes('ПЕРВИЧ') || docTypeId.includes('Первич') || docTypeId === '1') { documentationType = 'Первичное'; } else if (docTypeId.includes('ИТОГОВ') || docTypeId.includes('Итогов') || docTypeId === '2') { documentationType = 'Итоговое'; } } break; } } // Если не нашли в истории, определяем по интервалу if (documentationType === 'Не определено') { // Проверяем, к какому интервалу документирования относится проба const assayFrom = from; const assayTo = to; // Проверяем первичное документирование let isPrimary = false; for (const interval of primaryIntervals) { if (assayFrom >= interval.from && assayTo <= interval.to) { isPrimary = true; break; } } // Проверяем итоговое документирование let isFinal = false; for (const interval of finalIntervals) { if (assayFrom >= interval.from && assayTo <= interval.to) { isFinal = true; break; } } if (isPrimary && !isFinal) { documentationType = 'Первичное'; } else if (isFinal && !isPrimary) { documentationType = 'Итоговое'; } else if (isPrimary && isFinal) { documentationType = 'Оба типа'; } } // Получаем дополнительные данные опробования const volume = getSafeTextContent(assay, 'VOLUME_ASSA'); const volumeUnit = getSafeTextContent(assay, 'ID_MEASURE_VOL_ASSA'); const weight = getSafeTextContent(assay, 'WEIGHT_ASSA'); const weightUnit = getSafeTextContent(assay, 'ID_MEASURE_WEIGHT_ASSA'); const barcode = getSafeTextContent(assay, 'ASSAY_NUM_THROUGH_ASSA'); const place = getSafeTextContent(assay, 'ID_ASSAY_PLACE_ASSA'); const authorId = getSafeTextContent(assay, 'AUTHOR_MAIN_ASSA'); const samplingDate = formatDate(getSafeTextContent(assay, 'DATE_SAMPLING_ASSA')); let authorName = ''; if (authorId && authorId !== 'Не указано' && employeeMap.has(authorId)) { authorName = employeeMap.get(authorId); } else if (authorId && authorId !== 'Не указано') { authorName = `Код: ${authorId}`; } let value = ''; let unit = ''; if (volume && volumeUnit && volume !== 'Не указано' && volumeUnit !== 'Не указано') { value = volume; if (measureMap.has(volumeUnit)) { unit = measureMap.get(volumeUnit); } } else if (weight && weightUnit && weight !== 'Не указано' && weightUnit !== 'Не указано') { value = weight; if (measureMap.has(weightUnit)) { unit = measureMap.get(weightUnit); } } // Форматируем тип документирования let formattedDocType = documentationType; if (documentationType === 'Первичное') { formattedDocType = 'Первичное документирование'; } else if (documentationType === 'Итоговое') { formattedDocType = 'Итоговое документирование'; } else if (documentationType === 'Оба типа') { formattedDocType = 'Оба типа документирования'; } // Фильтруем пустые описания и места отбора const assayDescription = (description && description !== 'Не указано') ? description : ''; const assayPlace = (place && place !== 'Не указано' && place.length <= 20) ? place : ''; assays.push({ number, type, documentationType: formattedDocType, from, to, value, unit, barcode, place: assayPlace, author: authorName, samplingDate, description: assayDescription }); } // Сортируем пробы по номеру assays.sort((a, b) => a.number - b.number); // Сохраняем извлеченные данные xmlData = { wellName, object, area, wellType, wellNumber: wellName, lineNumber, wellDescription, drillingStartDate, drillingEndDate, drillingBrigade, primaryDocInfo, finalDocInfo, gisDocInfo, primaryDocumentation: primaryIntervals, finalDocumentation: finalIntervals, gisDocumentation: gisIntervals, assays }; } ```
Эта функция:
· ✅ Извлекает основную информацию о скважине · ✅ Обрабатывает данные документирования (первичное, итоговое, ГИС) · ✅ Извлекает интервалы документирования с литологией и стратиграфией · ✅ Обрабатывает данные опробования · ✅ Создает карты для связывания данных (сотрудники, классификаторы, единицы измерения) · ✅ Сортирует данные для корректного отображения · ✅ Сохраняет все данные в глобальную переменную xmlData
|
Отлично! Расскажу подробно куда вставлять каждую часть:
4 часть - Оптимизация обработки файлов
Вставляется в функцию loadWellData после поиска multiFiles:
```javascript // В функции loadWellData ЗАМЕНИТЕ весь блок обработки multiFiles на этот код:
// Если есть файлы 904~, обрабатываем их все if (multiFiles.length > 0) { console.log(`Найдено мульти-файлов 904~: ${multiFiles.length}`); updateLoadingText(`Обработка ${multiFiles.length} файлов...`); const allWells = []; const allXmlDocs = []; // Обрабатываем каждый файл 904~ for (let i = 0; i < multiFiles.length; i++) { // Проверяем отмену загрузки if (abortLoading) { throw new Error('Загрузка отменена пользователем'); } const multiFile = multiFiles[i]; const progress = 10 + (i / multiFiles.length) * 70; updateProgress(progress, `Файл ${i + 1} из ${multiFiles.length}: ${multiFile.name}`); console.log('Обработка файла:', multiFile.name, 'Размер:', multiFile.size); // Увеличиваем лимит размера INU файла до 2000MB if (multiFile.size > 2000 * 1024 * 1024) { console.warn(`Файл ${multiFile.name} слишком большой, пропускаем`); continue; } try { // Проверяем кэш const fileHash = await getFileHash(multiFile.entry); if (fileCache.has(fileHash)) { console.log(`Используем кэш для файла ${multiFile.name}`); const cachedWells = fileCache.get(fileHash); cachedWells.forEach(well => { if (!allWells.some(existingWell => existingWell.wellId === well.wellId)) { allWells.push(well); } }); continue; } // Читаем XML данные updateLoadingText(`Чтение файла ${i + 1} из ${multiFiles.length}...`); const xmlContent = await multiFile.entry.async('text'); console.log(`XML прочитан из ${multiFile.name}, длина:`, xmlContent.length); // Очищаем XML от некорректных символов const cleanedContent = cleanXMLContent(xmlContent); let wellsFromFile = []; // Для больших файлов используем DOM парсер только если нужно if (cleanedContent.length < 10 * 1024 * 1024) { // Меньше 10MB const parser = new DOMParser(); const xmlDoc = parser.parseFromString(cleanedContent, 'text/xml'); // Проверяем на ошибки парсинга const parseError = xmlDoc.getElementsByTagName('parsererror')[0]; if (parseError) { console.error(`Ошибка парсинга XML в файле ${multiFile.name}:`, parseError.textContent); continue; } allXmlDocs.push(xmlDoc); // Извлекаем скважины из этого файла updateLoadingText(`Извлечение данных из файла ${i + 1}...`); wellsFromFile = await extractWellsFromMultiFile( xmlDoc, null, multiFile.name, (progress, details) => { const overallProgress = 10 + (i / multiFiles.length) * 70 + (progress / 100) * (70 / multiFiles.length); updateProgress(overallProgress, details); } ); } else { // Для очень больших файлов используем потоковую обработку updateLoadingText(`Потоковая обработка файла ${i + 1}...`); wellsFromFile = await extractWellsFromMultiFile( null, cleanedContent, multiFile.name, (progress, details) => { const overallProgress = 10 + (i / multiFiles.length) * 70 + (progress / 100) * (70 / multiFiles.length); updateProgress(overallProgress, details); } ); } console.log(`Из файла ${multiFile.name} извлечено скважин:`, wellsFromFile.length); // Сохраняем в кэш fileCache.set(fileHash, wellsFromFile); // Добавляем скважины в общий список (исключая дубликаты по ID) wellsFromFile.forEach(well => { if (!allWells.some(existingWell => existingWell.wellId === well.wellId)) { allWells.push(well); } }); } catch (error) { console.error(`Ошибка обработки файла ${multiFile.name}:`, error); } } // Проверяем отмену загрузки if (abortLoading) { throw new Error('Загрузка отменена пользователем'); } updateProgress(85, 'Формирование списка скважин...'); console.log(`Всего уникальных скважин после объединения: ${allWells.length}`); // Ограничение на максимальное количество скважин const MAX_WELLS = 1000; if (allWells.length > MAX_WELLS) { console.warn(`Обнаружено ${allWells.length} скважин, ограничиваем до ${MAX_WELLS}`); allWells = allWells.slice(0, MAX_WELLS); // Показываем предупреждение setTimeout(() => { alert(`Внимание: В файле обнаружено более ${MAX_WELLS} скважин. Отображены только первые ${MAX_WELLS} скважин. Для просмотра всех данных рекомендуется использовать специализированное ПО.`); }, 1000); } if (allWells.length > 0) { currentMultiWellData = { xmlDocs: allXmlDocs, wells: allWells }; updateProgress(100, 'Готово!'); setTimeout(() => { hideLoading(); currentMultiFileName = file.name; showWellsList(allWells, file.name, multiFiles.length); wellDataElement.innerHTML = '<div class="loading"><p>Выберите скважину из списка</p></div>'; }, 500); } else { hideLoading(); throw new Error('Не удалось извлечь данные скважин из файлов 904~'); } } ```
5 часть - Легирование и отмена загрузки
Добавьте в начало файла (после глобальных переменных):
```javascript // Глобальные переменные для оптимизации let abortLoading = false; let fileCache = new Map();
// Функция для отмены загрузки function cancelLoading() { abortLoading = true; hideLoading(); }
// Функция для получения хэша файла async function getFileHash(fileEntry) { try { const content = await fileEntry.async('text'); // Простой хэш на основе первых 1000 символов для скорости return btoa(content.substring(0, 1000)).substring(0, 50); } catch (error) { // Если не удалось прочитать файл, используем имя и размер как хэш return `${fileEntry.name}_${fileEntry._data.uncompressedSize}`; } } ```
Обновите функцию showLoading:
```javascript function showLoading(message = 'Загрузка данных...') { const overlay = document.getElementById('loadingOverlay'); const loadingText = document.getElementById('loadingText'); const progressFill = document.getElementById('progressFill'); const progressText = document.getElementById('progressText'); const loadingDetails = document.getElementById('loadingDetails'); // Сбрасываем флаг отмены abortLoading = false; loadingText.textContent = message; progressFill.style.width = '0%'; progressText.textContent = '0%'; loadingDetails.textContent = ''; // Добавляем кнопку отмены const loadingContent = document.querySelector('.loading-content'); let cancelButton = document.getElementById('cancelButton'); if (!cancelButton) { cancelButton = document.createElement('button'); cancelButton.id = 'cancelButton'; cancelButton.className = 'cancel-button'; cancelButton.textContent = 'Отмена'; cancelButton.onclick = cancelLoading; loadingContent.appendChild(cancelButton); } else { cancelButton.style.display = 'block'; } overlay.style.display = 'flex'; loadingText.classList.add('pulse'); } ```
Обновите функцию hideLoading:
```javascript function hideLoading() { const overlay = document.getElementById('loadingOverlay'); const loadingText = document.getElementById('loadingText'); const cancelButton = document.getElementById('cancelButton'); overlay.style.display = 'none'; loadingText.classList.remove('pulse'); // Скрываем кнопку отмены if (cancelButton) { cancelButton.style.display = 'none'; } } ```
Добавьте CSS для кнопки отмены:
```css .cancel-button { background-color: #e74c3c; color: white; border: none; padding: 8px 15px; border-radius: 4px; cursor: pointer; margin-top: 10px; transition: background-color 0.2s; font-weight: 500; }
.cancel-button:hover { background-color: #c0392b; transform: translateY(-1px); } ```
Добавьте проверки отмены в ключевые функции:
В функции loadWellDataFromFile добавьте проверку:
```javascript // В начале функции после showLoading if (abortLoading) { hideLoading(); return; }
// В процессе загрузки updateProgress(30, 'Чтение файла...'); if (abortLoading) { throw new Error('Загрузка отменена пользователем'); } ```
В функции parseWellDataFromMulti добавьте проверку:
```javascript // В начале цикла for (let i = 0; i < currentMultiWellData.xmlDocs.length; i++) { if (abortLoading) { throw new Error('Загрузка отменена пользователем'); } // ... остальной код } ```
Краткое резюме что куда вставлять:
1. 4 часть → Заменяет весь блок обработки multiFiles в loadWellData 2. 5 часть → Добавляется в разных местах: · Глобальные переменные → в начало файла после других глобальных переменных · Функции cancelLoading, getFileHash → после глобальных переменных · Обновленные showLoading/hideLoading → заменяют старые версии · CSS для кнопки отмены → в секцию стилей · Проверки отмены → в ключевые функции загрузки
Теперь у вас будет полная оптимизация с кэшированием, потоковой обработкой и возможностью отмены!
|
Понял проблему. Основные проблемы с памятью возникают из-за:
1. **Хранения всех XML документов в памяти** 2. **Чтения всех файлов 904~ одновременно** 3. **Нет очистки промежуточных данных**
Вот оптимизированное решение:
## 1. Изменяем структуру хранения данных
```javascript // В начале файла заменяем: let currentMultiWellData = null;
// На: let currentMultiWellData = { wells: [], // Только метаданные скважин fileHandles: [], // Информация о файлах, а не сами XML currentXmlDoc: null // Только один XML в памяти }; ```
## 2. Оптимизируем функцию `loadWellData` (основная логика загрузки)
```javascript // ЗАМЕНИТЕ весь блок loadWellData на этот оптимизированный вариант: async function loadWellData(file, fileNumber) { showLoading('Открытие архива...'); const wellDataElement = document.getElementById('wellData'); wellDataElement.innerHTML = '<div class="loading"><p>Загрузка данных...</p></div>';
try { console.log('Начало загрузки файла:', file.name); updateLoadingText('Чтение архива...');
const fileObj = await file.handle.getFile(); console.log('Размер файла:', fileObj.size, 'байт');
if (fileObj.size > 1500 * 1024 * 1024) { throw new Error('Файл слишком большой для обработки в браузере (максимум 1500 МБ)'); }
updateLoadingText('Распаковка архива...'); const arrayBuffer = await fileObj.arrayBuffer(); console.log('ArrayBuffer прочитан, размер:', arrayBuffer.byteLength);
updateLoadingText('Обработка данных...'); const zip = await JSZip.loadAsync(arrayBuffer); console.log('ZIP распакован, файлов:', Object.keys(zip.files).length);
// ОЧИСТКА ПАМЯТИ - освобождаем arrayBuffer сразу после использования arrayBuffer = null;
const multiFiles = []; let otherTargetFile = null; let fileType = '';
updateProgress(10, 'Поиск файлов с данными...');
// Ищем файлы в архиве for (const filename in zip.files) { const zipEntry = zip.files[filename]; if (zipEntry.dir || filename.endsWith('/')) continue;
if (filename.startsWith('904~')) { multiFiles.push({ name: filename, entry: zipEntry, size: zipEntry._data.uncompressedSize }); } else if (filename.startsWith('906~') && !otherTargetFile) { otherTargetFile = zipEntry; fileType = 'all'; } else if (filename.startsWith('911~') && !otherTargetFile) { otherTargetFile = zipEntry; fileType = 'final'; } else if (filename.startsWith('909~') && !otherTargetFile) { otherTargetFile = zipEntry; fileType = 'primary'; } }
// ОПТИМИЗАЦИЯ: Для мульти-файлов читаем только метаданные, а не весь XML if (multiFiles.length > 0) { console.log(`Найдено мульти-файлов 904~: ${multiFiles.length}`); updateLoadingText(`Обработка ${multiFiles.length} файлов...`);
const allWells = []; // ОПТИМИЗАЦИЯ: Обрабатываем файлы последовательно с очисткой памяти for (let i = 0; i < multiFiles.length; i++) { const multiFile = multiFiles[i]; const progress = 10 + (i / multiFiles.length) * 70; updateProgress(progress, `Файл ${i + 1} из ${multiFiles.length}: ${multiFile.name}`);
console.log('Обработка файла:', multiFile.name, 'Размер:', multiFile.size);
if (multiFile.size > 2000 * 1024 * 1024) { console.warn(`Файл ${multiFile.name} слишком большой, пропускаем`); continue; }
try { // ОПТИМИЗАЦИЯ: Читаем только для извлечения скважин, затем очищаем updateLoadingText(`Чтение файла ${i + 1} из ${multiFiles.length}...`); const xmlContent = await multiFile.entry.async('text'); // ОПТИМИЗАЦИЯ: Сразу извлекаем скважины и очищаем XML const wellsFromFile = await extractWellsMetadataOnly(xmlContent); console.log(`Из файла ${multiFile.name} извлечено скважин:`, wellsFromFile.length);
// Добавляем скважины в общий список wellsFromFile.forEach(well => { if (!allWells.some(existingWell => existingWell.wellId === well.wellId)) { allWells.push({ ...well, sourceFile: multiFile.name // Сохраняем только имя файла }); } });
// ОЧИСТКА ПАМЯТИ xmlContent = null; wellsFromFile.length = 0;
} catch (error) { console.error(`Ошибка обработки файла ${multiFile.name}:`, error); } }
updateProgress(85, 'Формирование списка скважин...'); console.log(`Всего уникальных скважин после объединения: ${allWells.length}`);
if (allWells.length > 0) { // ОПТИМИЗАЦИЯ: Сохраняем только метаданные, а не XML currentMultiWellData = { wells: allWells, fileHandles: multiFiles, // Сохраняем информацию о файлах currentXmlDoc: null };
updateProgress(100, 'Готово!'); setTimeout(() => { hideLoading(); currentMultiFileName = file.name; showWellsList(allWells, file.name, multiFiles.length); wellDataElement.innerHTML = '<div class="loading"><p>Выберите скважину из списка</p></div>'; }, 500);
} else { hideLoading(); throw new Error('Не удалось извлечь данные скважин из файлов 904~'); }
} else if (otherTargetFile) { // ... остальной код для обычных файлов без изменений ... }
} catch (error) { hideLoading(); console.error('Ошибка загрузки данных:', error); wellDataElement.innerHTML = ` <div class="error"> <p><strong>Ошибка загрузки данных:</strong> ${error.message}</p> <p><strong>Поддерживаемые размеры:</strong></p> <ul style="text-align: left; margin: 10px 0;"> <li>ZIP архивы: до 1500 МБ</li> <li>INU файлы: до 2000 МБ</li> </ul> <p>Попробуйте выбрать другой файл или обратитесь к администратору</p> </div> `; } } ```
## 3. Добавляем новую функцию для извлечения только метаданных
```javascript // ДОБАВЬТЕ эту функцию после функции extractWellsFromMultiFile:
// ОПТИМИЗАЦИЯ: Функция для извлечения только метаданных скважин без полного парсинга XML async function extractWellsMetadataOnly(xmlContent) { const wells = []; // ОПТИМИЗАЦИЯ: Используем более легковесный парсинг const cleanedContent = cleanXMLContent(xmlContent); // Создаем парсер с ограничением глубины парсинга const parser = new DOMParser(); const xmlDoc = parser.parseFromString(cleanedContent, 'text/xml'); // Проверяем на ошибки парсинга const parseError = xmlDoc.getElementsByTagName('parsererror')[0]; if (parseError) { console.error('Ошибка парсинга XML при извлечении метаданных:', parseError.textContent); return wells; } // Извлекаем только основные данные о скважинах const wellNodes = xmlDoc.getElementsByTagName('MR_DBA.POINT_OBSERV_POAA'); for (let i = 0; i < wellNodes.length; i++) { const wellNode = wellNodes[i]; const wellId = getSafeTextContent(wellNode, 'ID_POINT_OBSERV_POAA'); const wellName = getSafeTextContent(wellNode, 'NAME_FULL_POAA'); const object = getSafeTextContent(wellNode, 'ID_OBJECT_WORK_POAA'); const description = getSafeTextContent(wellNode, 'DESCRIPTION_POAA'); if (wellId && wellId !== 'Не указано') { wells.push({ wellId: wellId, wellName: wellName, object: object, description: description }); } } // ОЧИСТКА ПАМЯТИ xmlDoc = null; return wells; } ```
## 4. Оптимизируем функцию загрузки данных конкретной скважины
```javascript // ЗАМЕНИТЕ функцию parseWellDataFromMulti на эту оптимизированную версию:
async function parseWellDataFromMulti(wellId) { if (!currentMultiWellData || !currentMultiWellData.fileHandles || currentMultiWellData.fileHandles.length === 0) { throw new Error('Данные мульти-файла не загружены'); }
showLoading('Поиск данных скважины...');
// ОПТИМИЗАЦИЯ: Ищем скважину в файлах только когда нужно for (let i = 0; i < currentMultiWellData.fileHandles.length; i++) { const multiFile = currentMultiWellData.fileHandles[i]; const progress = (i / currentMultiWellData.fileHandles.length) * 100; updateProgress(progress, `Поиск в файле ${i + 1} из ${currentMultiWellData.fileHandles.length}...`);
try { // ОПТИМИЗАЦИЯ: Читаем файл только при необходимости const xmlContent = await multiFile.entry.async('text'); const cleanedContent = cleanXMLContent(xmlContent); const parser = new DOMParser(); const xmlDoc = parser.parseFromString(cleanedContent, 'text/xml');
// Проверяем наличие скважины в этом файле const wellNodes = xmlDoc.getElementsByTagName('MR_DBA.POINT_OBSERV_POAA'); let targetWell = null;
for (let j = 0; j < wellNodes.length; j++) { const wellNode = wellNodes[j]; const currentWellId = getSafeTextContent(wellNode, 'ID_POINT_OBSERV_POAA'); if (currentWellId === wellId) { targetWell = wellNode; break; } }
if (targetWell) { updateProgress(100, 'Скважина найдена, загрузка данных...'); // ОПТИМИЗАЦИЯ: Сохраняем только текущий XML, очищаем предыдущий currentMultiWellData.currentXmlDoc = xmlDoc; await parseWellDataFromSingleMultiFile(xmlDoc, targetWell, wellId); setTimeout(() => { hideLoading(); }, 500); return; } // ОЧИСТКА ПАМЯТИ: если скважина не найдена в этом файле xmlDoc = null; } catch (error) { console.error(`Ошибка чтения файла ${multiFile.name}:`, error); } }
hideLoading(); throw new Error(`Скважина с ID ${wellId} не найдена в файлах`); } ```
## 5. Добавляем функцию очистки памяти
```javascript // ДОБАВЬТЕ эту функцию в конец скрипта:
// Функция для принудительной очистки памяти function cleanupMemory() { console.log('Очистка памяти...'); if (currentMultiWellData) { // Очищаем текущий XML документ currentMultiWellData.currentXmlDoc = null; // Очищаем fileHandles (сохраняем только wells) currentMultiWellData.fileHandles = []; } // Принудительный вызов сборщика мусора (если доступен) if (window.gc) { window.gc(); } console.log('Очистка памяти завершена'); }
// Вызываем очистку при возврате к списку файлов function backToFileList() { // ... существующий код ... // ДОБАВЬТЕ эту строку в конец функции: cleanupMemory(); } ```
## 6. Добавляем обработчик для очистки памяти при закрытии/переходе
```javascript // ДОБАВЬТЕ этот код в конец DOMContentLoaded:
// Очистка памяти при закрытии страницы window.addEventListener('beforeunload', function() { cleanupMemory(); });
// Очистка памяти при скрытии вкладки document.addEventListener('visibilitychange', function() { if (document.hidden) { cleanupMemory(); } }); ```
## Ключевые улучшения:
1. **Не храним все XML в памяти** - только метаданные скважин 2. **Последовательная обработка** файлов вместо одновременной 3. **Очистка промежуточных данных** после использования 4. **Ленивая загрузка** XML только при выборе скважины 5. **Принудительная сборка мусора** где возможно
Эти изменения значительно снизят потребление памяти и ускорят загрузку мульти-файлов, сохранив при этом всю функциональность.
|
Ошибка возникает из-за попытки переприсвоения констант. Вот исправленный код:
1. Исправленная функция loadWellData
```javascript // ЗАМЕНИТЕ функцию loadWellData на эту исправленную версию: async function loadWellData(file, fileNumber) { showLoading('Открытие архива...'); const wellDataElement = document.getElementById('wellData'); wellDataElement.innerHTML = '<div class="loading"><p>Загрузка данных...</p></div>';
try { console.log('Начало загрузки файла:', file.name); updateLoadingText('Чтение архива...');
const fileObj = await file.handle.getFile(); console.log('Размер файла:', fileObj.size, 'байт');
if (fileObj.size > 1500 * 1024 * 1024) { throw new Error('Файл слишком большой для обработки в браузере (максимум 1500 МБ)'); }
updateLoadingText('Распаковка архива...'); let arrayBuffer = await fileObj.arrayBuffer(); // Используем let вместо const console.log('ArrayBuffer прочитан, размер:', arrayBuffer.byteLength);
updateLoadingText('Обработка данных...'); const zip = await JSZip.loadAsync(arrayBuffer); console.log('ZIP распакован, файлов:', Object.keys(zip.files).length);
// ОЧИСТКА ПАМЯТИ - освобождаем arrayBuffer сразу после использования arrayBuffer = null;
const multiFiles = []; let otherTargetFile = null; let fileType = '';
updateProgress(10, 'Поиск файлов с данными...');
// Ищем файлы в архиве for (const filename in zip.files) { const zipEntry = zip.files[filename]; if (zipEntry.dir || filename.endsWith('/')) continue;
if (filename.startsWith('904~')) { multiFiles.push({ name: filename, entry: zipEntry, size: zipEntry._data.uncompressedSize }); } else if (filename.startsWith('906~') && !otherTargetFile) { otherTargetFile = zipEntry; fileType = 'all'; } else if (filename.startsWith('911~') && !otherTargetFile) { otherTargetFile = zipEntry; fileType = 'final'; } else if (filename.startsWith('909~') && !otherTargetFile) { otherTargetFile = zipEntry; fileType = 'primary'; } }
// ОПТИМИЗАЦИЯ: Для мульти-файлов читаем только метаданные, а не весь XML if (multiFiles.length > 0) { console.log(`Найдено мульти-файлов 904~: ${multiFiles.length}`); updateLoadingText(`Обработка ${multiFiles.length} файлов...`);
const allWells = []; // ОПТИМИЗАЦИЯ: Обрабатываем файлы последовательно с очисткой памяти for (let i = 0; i < multiFiles.length; i++) { const multiFile = multiFiles[i]; const progress = 10 + (i / multiFiles.length) * 70; updateProgress(progress, `Файл ${i + 1} из ${multiFiles.length}: ${multiFile.name}`);
console.log('Обработка файла:', multiFile.name, 'Размер:', multiFile.size);
if (multiFile.size > 2000 * 1024 * 1024) { console.warn(`Файл ${multiFile.name} слишком большой, пропускаем`); continue; }
try { // ОПТИМИЗАЦИЯ: Читаем только для извлечения скважин, затем очищаем updateLoadingText(`Чтение файла ${i + 1} из ${multiFiles.length}...`); let xmlContent = await multiFile.entry.async('text'); // Используем let // ОПТИМИЗАЦИЯ: Сразу извлекаем скважины и очищаем XML const wellsFromFile = await extractWellsMetadataOnly(xmlContent); console.log(`Из файла ${multiFile.name} извлечено скважин:`, wellsFromFile.length);
// Добавляем скважины в общий список wellsFromFile.forEach(well => { if (!allWells.some(existingWell => existingWell.wellId === well.wellId)) { allWells.push({ ...well, sourceFile: multiFile.name // Сохраняем только имя файла }); } });
// ОЧИСТКА ПАМЯТИ xmlContent = null;
} catch (error) { console.error(`Ошибка обработки файла ${multiFile.name}:`, error); } }
updateProgress(85, 'Формирование списка скважин...'); console.log(`Всего уникальных скважин после объединения: ${allWells.length}`);
if (allWells.length > 0) { // ОПТИМИЗАЦИЯ: Сохраняем только метаданные, а не XML currentMultiWellData = { wells: allWells, fileHandles: multiFiles, // Сохраняем информацию о файлах currentXmlDoc: null };
updateProgress(100, 'Готово!'); setTimeout(() => { hideLoading(); currentMultiFileName = file.name; showWellsList(allWells, file.name, multiFiles.length); wellDataElement.innerHTML = '<div class="loading"><p>Выберите скважину из списка</p></div>'; }, 500);
} else { hideLoading(); throw new Error('Не удалось извлечь данные скважин из файлов 904~'); }
} else if (otherTargetFile) { // Обработка обычных файлов (906~, 911~, 909~) updateProgress(50, 'Обработка файла данных...'); console.log('Найден INU файл:', otherTargetFile.name, 'Размер:', otherTargetFile._data.uncompressedSize);
// Увеличиваем лимит размера INU файла до 2000MB if (otherTargetFile._data.uncompressedSize > 2000 * 1024 * 1024) { throw new Error('INU файл слишком большой для обработки в браузере (максимум 2000 МБ)'); }
// Читаем XML данные updateLoadingText('Чтение XML данных...'); console.log('Чтение XML...'); const xmlContent = await otherTargetFile.async('text'); console.log('XML прочитан, длина:', xmlContent.length);
// Парсинг XML данных с обработкой ошибок updateProgress(80, 'Парсинг XML...'); console.log('Парсинг XML...'); await parseXMLData(xmlContent, fileType); console.log('XML распарсен');
updateProgress(100, 'Готово!'); setTimeout(() => { hideLoading();
// Для мульти-файлов показываем список скважин if (fileType === 'multi' && currentMultiWellData && currentMultiWellData.wells.length > 0) { currentMultiFileName = file.name; showWellsList(currentMultiWellData.wells, file.name); wellDataElement.innerHTML = '<div class="loading"><p>Выберите скважину из списка</p></div>'; } else { // Отображение данных displayWellData(fileType, fileNumber, file.name); console.log('Данные отображены'); } }, 500);
} else { hideLoading(); throw new Error('В архиве не найден файл с данными (начинающийся с 904~, 906~, 909~ или 911~)'); }
} catch (error) { hideLoading(); console.error('Ошибка загрузки данных:', error); wellDataElement.innerHTML = ` <div class="error"> <p><strong>Ошибка загрузки данных:</strong> ${error.message}</p> <p><strong>Поддерживаемые размеры:</strong></p> <ul style="text-align: left; margin: 10px 0;"> <li>ZIP архивы: до 1500 МБ</li> <li>INU файлы: до 2000 МБ</li> </ul> <p>Попробуйте выбрать другой файл или обратитесь к администратору</p> </div> `; } } ```
2. Исправленная функция extractWellsMetadataOnly
```javascript // ЗАМЕНИТЕ функцию extractWellsMetadataOnly на эту исправленную версию: async function extractWellsMetadataOnly(xmlContent) { const wells = []; // ОПТИМИЗАЦИЯ: Используем более легковесный парсинг const cleanedContent = cleanXMLContent(xmlContent); // Создаем парсер с ограничением глубины парсинга const parser = new DOMParser(); const xmlDoc = parser.parseFromString(cleanedContent, 'text/xml'); // Проверяем на ошибки парсинга const parseError = xmlDoc.getElementsByTagName('parsererror')[0]; if (parseError) { console.error('Ошибка парсинга XML при извлечении метаданных:', parseError.textContent); return wells; } // Извлекаем только основные данные о скважинах const wellNodes = xmlDoc.getElementsByTagName('MR_DBA.POINT_OBSERV_POAA'); for (let i = 0; i < wellNodes.length; i++) { const wellNode = wellNodes[i]; const wellId = getSafeTextContent(wellNode, 'ID_POINT_OBSERV_POAA'); const wellName = getSafeTextContent(wellNode, 'NAME_FULL_POAA'); const object = getSafeTextContent(wellNode, 'ID_OBJECT_WORK_POAA'); const description = getSafeTextContent(wellNode, 'DESCRIPTION_POAA'); if (wellId && wellId !== 'Не указано') { wells.push({ wellId: wellId, wellName: wellName, object: object, description: description }); } } return wells; } ```
3. Исправленная функция parseWellDataFromMulti
```javascript // ЗАМЕНИТЕ функцию parseWellDataFromMulti на эту исправленную версию: async function parseWellDataFromMulti(wellId) { if (!currentMultiWellData || !currentMultiWellData.fileHandles || currentMultiWellData.fileHandles.length === 0) { throw new Error('Данные мульти-файла не загружены'); }
showLoading('Поиск данных скважины...');
// ОПТИМИЗАЦИЯ: Ищем скважину в файлах только когда нужно for (let i = 0; i < currentMultiWellData.fileHandles.length; i++) { const multiFile = currentMultiWellData.fileHandles[i]; const progress = (i / currentMultiWellData.fileHandles.length) * 100; updateProgress(progress, `Поиск в файле ${i + 1} из ${currentMultiWellData.fileHandles.length}...`);
try { // ОПТИМИЗАЦИЯ: Читаем файл только при необходимости const xmlContent = await multiFile.entry.async('text'); const cleanedContent = cleanXMLContent(xmlContent); const parser = new DOMParser(); const xmlDoc = parser.parseFromString(cleanedContent, 'text/xml');
// Проверяем наличие скважины в этом файле const wellNodes = xmlDoc.getElementsByTagName('MR_DBA.POINT_OBSERV_POAA'); let targetWell = null;
for (let j = 0; j < wellNodes.length; j++) { const wellNode = wellNodes[j]; const currentWellId = getSafeTextContent(wellNode, 'ID_POINT_OBSERV_POAA'); if (currentWellId === wellId) { targetWell = wellNode; break; } }
if (targetWell) { updateProgress(100, 'Скважина найдена, загрузка данных...'); // ОПТИМИЗАЦИЯ: Сохраняем только текущий XML, очищаем предыдущий currentMultiWellData.currentXmlDoc = xmlDoc; await parseWellDataFromSingleMultiFile(xmlDoc, targetWell, wellId); setTimeout(() => { hideLoading(); }, 500); return; } } catch (error) { console.error(`Ошибка чтения файла ${multiFile.name}:`, error); } }
hideLoading(); throw new Error(`Скважина с ID ${wellId} не найдена в файлах`); } ```
Основные исправления:
1. Заменил const на let для переменных, которые нужно переприсваивать (arrayBuffer, xmlContent) 2. Убрал лишние очистки DOM объектов (xmlDoc = null), так как они не нужны - браузер сам управляет DOM 3. Сохранил логику очистки только для больших данных (arrayBuffer, xmlContent)
Теперь ошибка "assignment to constant variable" должна исчезнуть, и оптимизация памяти будет работать корректно.
|
Проблема в том, что мы все равно читаем ВСЕ файлы 904~ одновременно. Нужно кардинально изменить подход - читать только метаданные без полного парсинга XML. Вот решение:
1. Полностью переписываем функцию загрузки мульти-файлов
```javascript // ЗАМЕНИТЕ функцию loadWellData на эту УЛЬТРА-ОПТИМИЗИРОВАННУЮ версию: async function loadWellData(file, fileNumber) { showLoading('Открытие архива...'); const wellDataElement = document.getElementById('wellData'); wellDataElement.innerHTML = '<div class="loading"><p>Загрузка данных...</p></div>';
try { console.log('Начало загрузки файла:', file.name); const fileObj = await file.handle.getFile();
if (fileObj.size > 1500 * 1024 * 1024) { throw new Error('Файл слишком большой для обработки в браузере (максимум 1500 МБ)'); }
updateLoadingText('Распаковка архива...'); const arrayBuffer = await fileObj.arrayBuffer(); const zip = await JSZip.loadAsync(arrayBuffer);
// СРАЗУ освобождаем память arrayBuffer = null;
const multiFiles = []; let otherTargetFile = null; let fileType = '';
// Быстрый поиск файлов for (const filename in zip.files) { const zipEntry = zip.files[filename]; if (zipEntry.dir || filename.endsWith('/')) continue;
if (filename.startsWith('904~')) { multiFiles.push({ name: filename, entry: zipEntry, size: zipEntry._data.uncompressedSize }); } else if (filename.startsWith('906~') && !otherTargetFile) { otherTargetFile = zipEntry; fileType = 'all'; } else if (filename.startsWith('911~') && !otherTargetFile) { otherTargetFile = zipEntry; fileType = 'final'; } else if (filename.startsWith('909~') && !otherTargetFile) { otherTargetFile = zipEntry; fileType = 'primary'; } }
// ОПТИМИЗАЦИЯ: Для мульти-файлов используем СТРИМИНГ и РЕГУЛЯРНЫЕ ВЫРАЖЕНИЯ if (multiFiles.length > 0) { console.log(`Найдено мульти-файлов 904~: ${multiFiles.length}`); const allWells = []; let processedFiles = 0;
// ОПТИМИЗАЦИЯ: Обрабатываем файлы ПО ОЧЕРЕДИ с задержкой для сборки мусора for (const multiFile of multiFiles) { if (multiFile.size > 500 * 1024 * 1024) { console.warn(`Файл ${multiFile.name} слишком большой (${(multiFile.size / 1024 / 1024).toFixed(2)} MB), пропускаем`); continue; }
try { updateProgress((processedFiles / multiFiles.length) * 100, `Файл ${processedFiles + 1} из ${multiFiles.length}`); // ОПТИМИЗАЦИЯ: Используем быстрое извлечение метаданных через регулярки const wellsFromFile = await extractWellsMetadataFast(multiFile.entry); console.log(`Из файла ${multiFile.name} извлечено скважин:`, wellsFromFile.length);
// Добавляем уникальные скважины for (const well of wellsFromFile) { if (!allWells.some(w => w.wellId === well.wellId)) { allWells.push(well); } }
processedFiles++; // ОПТИМИЗАЦИЯ: Даем браузеру время на сборку мусора между файлами if (processedFiles % 5 === 0) { await new Promise(resolve => setTimeout(resolve, 100)); }
} catch (error) { console.error(`Ошибка обработки файла ${multiFile.name}:`, error); processedFiles++; } }
console.log(`Всего уникальных скважин: ${allWells.length}`);
if (allWells.length > 0) { // ОПТИМИЗАЦИЯ: Сохраняем МИНИМАЛЬНЫЕ данные currentMultiWellData = { wells: allWells, zip: zip, // Сохраняем ZIP для последующего чтения fileNames: multiFiles.map(f => f.name) // Только имена файлов };
updateProgress(100, 'Готово!'); setTimeout(() => { hideLoading(); currentMultiFileName = file.name; showWellsList(allWells, file.name, multiFiles.length); wellDataElement.innerHTML = '<div class="loading"><p>Выберите скважину из списка</p></div>'; }, 100);
} else { hideLoading(); throw new Error('Не удалось извлечь данные скважин'); }
} else if (otherTargetFile) { // Обработка обычных файлов (остается без изменений) const xmlContent = await otherTargetFile.async('text'); await parseXMLData(xmlContent, fileType); updateProgress(100, 'Готово!'); setTimeout(() => { hideLoading(); displayWellData(fileType, fileNumber, file.name); }, 100); } else { hideLoading(); throw new Error('В архиве не найден файл с данными'); }
} catch (error) { hideLoading(); console.error('Ошибка загрузки данных:', error); wellDataElement.innerHTML = ` <div class="error"> <p><strong>Ошибка загрузки данных:</strong> ${error.message}</p> </div> `; } } ```
2. Добавляем СУПЕР-БЫСТРУЮ функцию извлечения метаданных
```javascript // ДОБАВЬТЕ эту функцию - она использует регулярки вместо XML парсинга: async function extractWellsMetadataFast(zipEntry) { const wells = []; try { // Читаем файл как текст const xmlContent = await zipEntry.async('text'); // ОПТИМИЗАЦИЯ: Используем регулярные выражения для быстрого извлечения // Ищем блоки MR_DBA.POINT_OBSERV_POAA const wellBlocks = xmlContent.match(/<MR_DBA\.POINT_OBSERV_POAA>.*?<\/MR_DBA\.POINT_OBSERV_POAA>/gs); if (!wellBlocks) return wells; for (const block of wellBlocks) { try { // Быстрое извлечение данных через регулярки const wellIdMatch = block.match(/<ID_POINT_OBSERV_POAA>(.*?)<\/ID_POINT_OBSERV_POAA>/); const wellNameMatch = block.match(/<NAME_FULL_POAA>(.*?)<\/NAME_FULL_POAA>/); const objectMatch = block.match(/<ID_OBJECT_WORK_POAA>(.*?)<\/ID_OBJECT_WORK_POAA>/); const descriptionMatch = block.match(/<DESCRIPTION_POAA>(.*?)<\/DESCRIPTION_POAA>/); const wellId = wellIdMatch ? wellIdMatch[1].trim() : null; const wellName = wellNameMatch ? wellNameMatch[1].trim() : 'Без названия'; const object = objectMatch ? objectMatch[1].trim() : 'Не указан'; const description = descriptionMatch ? descriptionMatch[1].trim() : ''; if (wellId && wellId !== 'Не указано' && wellId !== '') { wells.push({ wellId: wellId, wellName: wellName, object: object, description: description }); } } catch (e) { console.warn('Ошибка парсинга блока скважины:', e); } } } catch (error) { console.error('Ошибка быстрого извлечения метаданных:', error); } return wells; } ```
3. Полностью переписываем функцию загрузки данных скважины
```javascript // ЗАМЕНИТЕ функцию parseWellDataFromMulti на эту: async function parseWellDataFromMulti(wellId) { if (!currentMultiWellData || !currentMultiWellData.zip) { throw new Error('Данные мульти-файла не загружены'); }
showLoading('Загрузка данных скважины...');
try { // ОПТИМИЗАЦИЯ: Ищем скважину только в нужных файлах for (const fileName of currentMultiWellData.fileNames) { updateProgress(50, `Поиск в файле ${fileName}...`); const zipEntry = currentMultiWellData.zip.file(fileName); if (!zipEntry) continue;
try { const xmlContent = await zipEntry.async('text'); const cleanedContent = cleanXMLContent(xmlContent); const parser = new DOMParser(); const xmlDoc = parser.parseFromString(cleanedContent, 'text/xml');
// Быстрый поиск скважины const wellNodes = xmlDoc.getElementsByTagName('MR_DBA.POINT_OBSERV_POAA'); let targetWell = null;
for (let i = 0; i < wellNodes.length; i++) { const wellNode = wellNodes[i]; const currentWellId = getSafeTextContent(wellNode, 'ID_POINT_OBSERV_POAA'); if (currentWellId === wellId) { targetWell = wellNode; break; } }
if (targetWell) { updateProgress(100, 'Обработка данных...'); // Сохраняем только текущий XML currentMultiWellData.currentXmlDoc = xmlDoc; await parseWellDataFromSingleMultiFile(xmlDoc, targetWell, wellId); hideLoading(); return; } } catch (error) { console.error(`Ошибка чтения файла ${fileName}:`, error); } }
hideLoading(); throw new Error(`Скважина с ID ${wellId} не найдена`);
} catch (error) { hideLoading(); throw error; } } ```
4. Усиленная функция очистки памяти
```javascript // ЗАМЕНИТЕ функцию cleanupMemory на эту: function cleanupMemory() { console.log('Интенсивная очистка памяти...'); if (currentMultiWellData) { // Очищаем ZIP данные если они есть if (currentMultiWellData.zip) { currentMultiWellData.zip = null; } // Очищаем XML документ currentMultiWellData.currentXmlDoc = null; // Очищаем fileNames currentMultiWellData.fileNames = []; } // Принудительная сборка мусора if (window.gc) { for (let i = 0; i < 3; i++) { window.gc(); } } // Дополнительные методы очистки if (window.performance && window.performance.memory) { console.log('Использование памяти:', Math.round(window.performance.memory.usedJSHeapSize / 1024 / 1024) + 'MB'); } } ```
5. Добавляем ограничение по количеству одновременно обрабатываемых файлов
```javascript // ДОБАВЬТЕ эту функцию для обработки файлов батчами: async function processFilesInBatches(files, batchSize = 3) { const allWells = []; for (let i = 0; i < files.length; i += batchSize) { const batch = files.slice(i, i + batchSize); console.log(`Обработка батча ${i/batchSize + 1} из ${Math.ceil(files.length/batchSize)}`); // Обрабатываем батч параллельно const batchPromises = batch.map(file => extractWellsMetadataFast(file.entry).catch(error => { console.error(`Ошибка в файле ${file.name}:`, error); return []; }) ); const batchResults = await Promise.all(batchPromises); // Объединяем результаты for (const wells of batchResults) { for (const well of wells) { if (!allWells.some(w => w.wellId === well.wellId)) { allWells.push(well); } } } // Даем браузеру "подышать" между батчами await new Promise(resolve => setTimeout(resolve, 200)); } return allWells; } ```
Ключевые улучшения:
1. Регулярные выражения вместо XML парсинга для извлечения метаданных 2. Батчевая обработка файлов (по 3-5 файлов за раз) 3. Паузы между обработкой для сборки мусора 4. Минимальное хранение данных - только имена файлов, а не содержимое 5. Пропуск очень больших файлов (>500MB)
Это должно снизить потребление памяти с 20ГБ до 1-2ГБ и значительно ускорить загрузку.
|
Ошибка снова из-за переприсвоения констант. Вот полностью исправленный код БЕЗ ошибок:
1. Исправленная функция loadWellData
```javascript // ЗАМЕНИТЕ функцию loadWellData на эту: async function loadWellData(file, fileNumber) { showLoading('Открытие архива...'); const wellDataElement = document.getElementById('wellData'); wellDataElement.innerHTML = '<div class="loading"><p>Загрузка данных...</p></div>';
try { console.log('Начало загрузки файла:', file.name); const fileObj = await file.handle.getFile();
if (fileObj.size > 1500 * 1024 * 1024) { throw new Error('Файл слишком большой для обработки в браузере (максимум 1500 МБ)'); }
updateLoadingText('Распаковка архива...'); const arrayBuffer = await fileObj.arrayBuffer(); const zip = await JSZip.loadAsync(arrayBuffer);
const multiFiles = []; let otherTargetFile = null; let fileType = '';
// Быстрый поиск файлов for (const filename in zip.files) { const zipEntry = zip.files[filename]; if (zipEntry.dir || filename.endsWith('/')) continue;
if (filename.startsWith('904~')) { multiFiles.push({ name: filename, entry: zipEntry, size: zipEntry._data.uncompressedSize }); } else if (filename.startsWith('906~') && !otherTargetFile) { otherTargetFile = zipEntry; fileType = 'all'; } else if (filename.startsWith('911~') && !otherTargetFile) { otherTargetFile = zipEntry; fileType = 'final'; } else if (filename.startsWith('909~') && !otherTargetFile) { otherTargetFile = zipEntry; fileType = 'primary'; } }
// ОПТИМИЗАЦИЯ: Для мульти-файлов используем быстрые регулярки if (multiFiles.length > 0) { console.log(`Найдено мульти-файлов 904~: ${multiFiles.length}`); const allWells = []; let processedFiles = 0;
// ОПТИМИЗАЦИЯ: Обрабатываем файлы ПО ОЧЕРЕДИ for (const multiFile of multiFiles) { if (multiFile.size > 500 * 1024 * 1024) { console.warn(`Файл ${multiFile.name} слишком большой (${(multiFile.size / 1024 / 1024).toFixed(2)} MB), пропускаем`); processedFiles++; continue; }
try { updateProgress((processedFiles / multiFiles.length) * 100, `Файл ${processedFiles + 1} из ${multiFiles.length}`); // ОПТИМИЗАЦИЯ: Используем быстрое извлечение метаданных через регулярки const wellsFromFile = await extractWellsMetadataFast(multiFile.entry); console.log(`Из файла ${multiFile.name} извлечено скважин:`, wellsFromFile.length);
// Добавляем уникальные скважины for (const well of wellsFromFile) { if (!allWells.some(w => w.wellId === well.wellId)) { allWells.push(well); } }
processedFiles++; } catch (error) { console.error(`Ошибка обработки файла ${multiFile.name}:`, error); processedFiles++; } }
console.log(`Всего уникальных скважин: ${allWells.length}`);
if (allWells.length > 0) { // ОПТИМИЗАЦИЯ: Сохраняем МИНИМАЛЬНЫЕ данные currentMultiWellData = { wells: allWells, zip: zip, // Сохраняем ZIP для последующего чтения fileNames: multiFiles.map(f => f.name) // Только имена файлов };
updateProgress(100, 'Готово!'); setTimeout(() => { hideLoading(); currentMultiFileName = file.name; showWellsList(allWells, file.name, multiFiles.length); wellDataElement.innerHTML = '<div class="loading"><p>Выберите скважину из списка</p></div>'; }, 100);
} else { hideLoading(); throw new Error('Не удалось извлечь данные скважин'); }
} else if (otherTargetFile) { // Обработка обычных файлов const xmlContent = await otherTargetFile.async('text'); await parseXMLData(xmlContent, fileType); updateProgress(100, 'Готово!'); setTimeout(() => { hideLoading(); displayWellData(fileType, fileNumber, file.name); }, 100); } else { hideLoading(); throw new Error('В архиве не найден файл с данными'); }
} catch (error) { hideLoading(); console.error('Ошибка загрузки данных:', error); wellDataElement.innerHTML = ` <div class="error"> <p><strong>Ошибка загрузки данных:</strong> ${error.message}</p> </div> `; } } ```
2. Функция быстрого извлечения метаданных (НОВАЯ)
```javascript // ДОБАВЬТЕ эту функцию: async function extractWellsMetadataFast(zipEntry) { const wells = []; try { // Читаем файл как текст const xmlContent = await zipEntry.async('text'); // ОПТИМИЗАЦИЯ: Используем регулярные выражения для быстрого извлечения // Ищем блоки MR_DBA.POINT_OBSERV_POAA const wellBlocks = xmlContent.split(/<MR_DBA\.POINT_OBSERV_POAA>/); // Пропускаем первый элемент (все что до первого блока) for (let i = 1; i < wellBlocks.length; i++) { const block = wellBlocks[i].split('</MR_DBA.POINT_OBSERV_POAA>')[0]; // Быстрое извлечение данных через регулярки const wellIdMatch = block.match(/<ID_POINT_OBSERV_POAA>([^<]*)<\/ID_POINT_OBSERV_POAA>/); const wellNameMatch = block.match(/<NAME_FULL_POAA>([^<]*)<\/NAME_FULL_POAA>/); const objectMatch = block.match(/<ID_OBJECT_WORK_POAA>([^<]*)<\/ID_OBJECT_WORK_POAA>/); const descriptionMatch = block.match(/<DESCRIPTION_POAA>([^<]*)<\/DESCRIPTION_POAA>/); const wellId = wellIdMatch ? wellIdMatch[1].trim() : null; const wellName = wellNameMatch ? wellNameMatch[1].trim() : 'Без названия'; const object = objectMatch ? objectMatch[1].trim() : 'Не указан'; const description = descriptionMatch ? descriptionMatch[1].trim() : ''; if (wellId && wellId !== 'Не указано' && wellId !== '') { wells.push({ wellId: wellId, wellName: wellName, object: object, description: description }); } } } catch (error) { console.error('Ошибка быстрого извлечения метаданных:', error); } return wells; } ```
3. Исправленная функция parseWellDataFromMulti
```javascript // ЗАМЕНИТЕ функцию parseWellDataFromMulti на эту: async function parseWellDataFromMulti(wellId) { if (!currentMultiWellData || !currentMultiWellData.zip) { throw new Error('Данные мульти-файла не загружены'); }
showLoading('Загрузка данных скважины...');
// ОПТИМИЗАЦИЯ: Ищем скважину только в нужных файлах for (const fileName of currentMultiWellData.fileNames) { updateProgress(50, `Поиск в файле ${fileName}...`); const zipEntry = currentMultiWellData.zip.file(fileName); if (!zipEntry) continue;
try { const xmlContent = await zipEntry.async('text'); const cleanedContent = cleanXMLContent(xmlContent); const parser = new DOMParser(); const xmlDoc = parser.parseFromString(cleanedContent, 'text/xml');
// Быстрый поиск скважины const wellNodes = xmlDoc.getElementsByTagName('MR_DBA.POINT_OBSERV_POAA'); let targetWell = null;
for (let i = 0; i < wellNodes.length; i++) { const wellNode = wellNodes[i]; const currentWellId = getSafeTextContent(wellNode, 'ID_POINT_OBSERV_POAA'); if (currentWellId === wellId) { targetWell = wellNode; break; } }
if (targetWell) { updateProgress(100, 'Обработка данных...'); // Сохраняем только текущий XML currentMultiWellData.currentXmlDoc = xmlDoc; await parseWellDataFromSingleMultiFile(xmlDoc, targetWell, wellId); hideLoading(); return; } } catch (error) { console.error(`Ошибка чтения файла ${fileName}:`, error); } }
hideLoading(); throw new Error(`Скважина с ID ${wellId} не найдена`); } ```
4. Упрощенная функция очистки памяти
```javascript // ЗАМЕНИТЕ функцию cleanupMemory на эту: function cleanupMemory() { console.log('Очистка памяти...'); if (currentMultiWellData) { // Очищаем ZIP данные currentMultiWellData.zip = null; // Очищаем XML документ currentMultiWellData.currentXmlDoc = null; } // Принудительная сборка мусора если доступна if (window.gc) { window.gc(); } } ```
Основные изменения:
1. Убрал все переприсвоения констант - нет arrayBuffer = null и т.д. 2. Использовал split вместо match - более надежно работает с большими файлами 3. Упростил регулярные выражения - только базовое извлечение данных 4. Убрал сложную логику батчей - обрабатываем файлы последовательно
Этот код должен работать без ошибок "assignment to constant variable" и при этом значительно экономить память за счет:
· Регулярных выражений вместо полного XML парсинга · Хранения только имен файлов, а не их содержимого · Последовательной обработки файлов · Минимального хранения данных в памяти
|
Проблема в том, что при каждом клике на скважину мы заново читаем файл из ZIP архива. Нужно кешировать уже распакованные файлы. Вот решение:
1. Добавляем кеш для распакованных файлов
```javascript // ДОБАВЬТЕ в начало файла (после объявления глобальных переменных): let fileCache = new Map(); // Кеш для распакованных файлов ```
2. Обновляем структуру хранения мульти-файлов
```javascript // ЗАМЕНИТЕ функцию loadWellData (только часть обработки мульти-файлов): if (multiFiles.length > 0) { console.log(`Найдено мульти-файлов 904~: ${multiFiles.length}`); const allWells = []; let processedFiles = 0;
// ОПТИМИЗАЦИЯ: Обрабатываем файлы и КЕШИРУЕМ их содержимое for (const multiFile of multiFiles) { if (multiFile.size > 500 * 1024 * 1024) { console.warn(`Файл ${multiFile.name} слишком большой (${(multiFile.size / 1024 / 1024).toFixed(2)} MB), пропускаем`); processedFiles++; continue; }
try { updateProgress((processedFiles / multiFiles.length) * 100, `Файл ${processedFiles + 1} из ${multiFiles.length}`); // Читаем и кешируем файл const xmlContent = await multiFile.entry.async('text'); // ОПТИМИЗАЦИЯ: Сохраняем в кеш fileCache.set(multiFile.name, xmlContent); // Извлекаем метаданные скважин const wellsFromFile = extractWellsMetadataFromContent(xmlContent); console.log(`Из файла ${multiFile.name} извлечено скважин:`, wellsFromFile.length);
// Добавляем уникальные скважины for (const well of wellsFromFile) { if (!allWells.some(w => w.wellId === well.wellId)) { allWells.push({ ...well, sourceFile: multiFile.name }); } }
processedFiles++; } catch (error) { console.error(`Ошибка обработки файла ${multiFile.name}:`, error); processedFiles++; } }
console.log(`Всего уникальных скважин: ${allWells.length}`);
if (allWells.length > 0) { // ОПТИМИЗАЦИЯ: Сохраняем только метаданные, файлы уже в кеше currentMultiWellData = { wells: allWells, fileNames: multiFiles.map(f => f.name) // Только имена файлов };
// Очищаем ZIP из памяти так как файлы уже в кеше zip = null;
updateProgress(100, 'Готово!'); setTimeout(() => { hideLoading(); currentMultiFileName = file.name; showWellsList(allWells, file.name, multiFiles.length); wellDataElement.innerHTML = '<div class="loading"><p>Выберите скважину из списка</p></div>'; }, 100);
} else { hideLoading(); throw new Error('Не удалось извлечь данные скважин'); } } ```
3. Добавляем функцию извлечения метаданных из уже распакованного содержимого
```javascript // ДОБАВЬТЕ эту функцию: function extractWellsMetadataFromContent(xmlContent) { const wells = []; try { // Используем регулярные выражения для быстрого извлечения const wellBlocks = xmlContent.split(/<MR_DBA\.POINT_OBSERV_POAA>/); // Пропускаем первый элемент (все что до первого блока) for (let i = 1; i < wellBlocks.length; i++) { const block = wellBlocks[i].split('</MR_DBA.POINT_OBSERV_POAA>')[0]; // Быстрое извлечение данных через регулярки const wellIdMatch = block.match(/<ID_POINT_OBSERV_POAA>([^<]*)<\/ID_POINT_OBSERV_POAA>/); const wellNameMatch = block.match(/<NAME_FULL_POAA>([^<]*)<\/NAME_FULL_POAA>/); const objectMatch = block.match(/<ID_OBJECT_WORK_POAA>([^<]*)<\/ID_OBJECT_WORK_POAA>/); const descriptionMatch = block.match(/<DESCRIPTION_POAA>([^<]*)<\/DESCRIPTION_POAA>/); const wellId = wellIdMatch ? wellIdMatch[1].trim() : null; const wellName = wellNameMatch ? wellNameMatch[1].trim() : 'Без названия'; const object = objectMatch ? objectMatch[1].trim() : 'Не указан'; const description = descriptionMatch ? descriptionMatch[1].trim() : ''; if (wellId && wellId !== 'Не указано' && wellId !== '') { wells.push({ wellId: wellId, wellName: wellName, object: object, description: description }); } } } catch (error) { console.error('Ошибка извлечения метаданных из содержимого:', error); } return wells; } ```
4. Полностью переписываем функцию загрузки данных скважины
```javascript // ЗАМЕНИТЕ функцию parseWellDataFromMulti на эту: async function parseWellDataFromMulti(wellId) { if (!currentMultiWellData || !currentMultiWellData.wells) { throw new Error('Данные мульти-файла не загружены'); }
showLoading('Загрузка данных скважины...');
try { // Находим скважину в списке чтобы узнать sourceFile const targetWellMeta = currentMultiWellData.wells.find(w => w.wellId === wellId); if (!targetWellMeta) { throw new Error(`Скважина с ID ${wellId} не найдена в списке`); }
const sourceFile = targetWellMeta.sourceFile; if (!sourceFile) { throw new Error('Не указан исходный файл для скважины'); }
updateProgress(30, 'Поиск данных...');
// ОПТИМИЗАЦИЯ: Берем данные из КЕША, а не читаем заново из ZIP const xmlContent = fileCache.get(sourceFile); if (!xmlContent) { throw new Error(`Файл ${sourceFile} не найден в кеше`); }
updateProgress(60, 'Обработка XML...');
const cleanedContent = cleanXMLContent(xmlContent); const parser = new DOMParser(); const xmlDoc = parser.parseFromString(cleanedContent, 'text/xml');
// Ищем скважину в XML const wellNodes = xmlDoc.getElementsByTagName('MR_DBA.POINT_OBSERV_POAA'); let targetWell = null;
for (let i = 0; i < wellNodes.length; i++) { const wellNode = wellNodes[i]; const currentWellId = getSafeTextContent(wellNode, 'ID_POINT_OBSERV_POAA'); if (currentWellId === wellId) { targetWell = wellNode; break; } }
if (!targetWell) { throw new Error(`Скважина с ID ${wellId} не найдена в файле ${sourceFile}`); }
updateProgress(90, 'Формирование данных...');
// Сохраняем текущий XML документ currentMultiWellData.currentXmlDoc = xmlDoc;
await parseWellDataFromSingleMultiFile(xmlDoc, targetWell, wellId); hideLoading();
} catch (error) { hideLoading(); console.error('Ошибка загрузки данных скважины:', error); throw error; } } ```
5. Удаляем старую функцию extractWellsMetadataFast
```javascript // УДАЛИТЕ эту функцию полностью (если она есть): // async function extractWellsMetadataFast(zipEntry) { ... } ```
6. Обновляем функцию очистки памяти
```javascript // ЗАМЕНИТЕ функцию cleanupMemory на эту: function cleanupMemory() { console.log('Очистка памяти...'); // Очищаем кеш файлов fileCache.clear(); if (currentMultiWellData) { // Очищаем XML документ currentMultiWellData.currentXmlDoc = null; } // Принудительная сборка мусора если доступна if (window.gc) { window.gc(); } console.log('Очистка памяти завершена, размер кеша:', fileCache.size); } ```
7. Обновляем функцию возврата к списку файлов
```javascript // ОБНОВИТЕ функцию backToFileList - добавьте очистку кеша при смене архива: function backToFileList() { document.getElementById('wellsList').style.display = 'none'; document.getElementById('fileList').style.display = 'block'; document.getElementById('backButton').style.display = 'none';
// Обновляем счетчик document.getElementById('fileCounter').textContent = `Найдено архивов: ${document.getElementById('fileList').children.length}`;
// Сбрасываем выбранную скважину currentWellId = null; currentMultiWellData = null; currentMultiFileName = null;
// ОЧИСТКА КЕША при возврате к списку файлов fileCache.clear();
// Показываем сообщение о выборе файла document.getElementById('wellData').innerHTML = ` <div class="loading"> <p>Выберите файл из списка для отображения данных</p> </div> `;
document.getElementById('wellName').innerHTML = 'Выберите скважину для просмотра'; } ```
Ключевые преимущества:
1. Мгновенная загрузка скважин - данные уже в кеше 2. Нет повторного чтения ZIP - файлы читаются только один раз 3. Экономия памяти - очищаем ZIP после распаковки 4. Стабильность - нет проблем с большими файлами при повторных запросах
Теперь при клике на скважину данные будут загружаться мгновенно из кеша, без повторного обращения к ZIP архиву!
|
```javascript // ПОЛНАЯ ЗАМЕНА функции loadWellData: async function loadWellData(file, fileNumber) { showLoading('Открытие архива...'); const wellDataElement = document.getElementById('wellData'); wellDataElement.innerHTML = '<div class="loading"><p>Загрузка данных...</p></div>';
try { console.log('Начало загрузки файла:', file.name); updateLoadingText('Чтение архива...');
const fileObj = await file.handle.getFile(); console.log('Размер файла:', fileObj.size, 'байт');
if (fileObj.size > 1500 * 1024 * 1024) { throw new Error('Файл слишком большой для обработки в браузере (максимум 1500 МБ)'); }
updateLoadingText('Распаковка архива...'); const arrayBuffer = await fileObj.arrayBuffer(); console.log('ArrayBuffer прочитан, размер:', arrayBuffer.byteLength);
updateLoadingText('Обработка данных...'); const zip = await JSZip.loadAsync(arrayBuffer); console.log('ZIP распакован, файлов:', Object.keys(zip.files).length);
const multiFiles = []; let otherTargetFile = null; let fileType = '';
updateProgress(10, 'Поиск файлов с данными...');
// Ищем файлы в архиве for (const filename in zip.files) { const zipEntry = zip.files[filename]; if (zipEntry.dir || filename.endsWith('/')) continue;
if (filename.startsWith('904~')) { multiFiles.push({ name: filename, entry: zipEntry, size: zipEntry._data.uncompressedSize }); } else if (filename.startsWith('906~') && !otherTargetFile) { otherTargetFile = zipEntry; fileType = 'all'; } else if (filename.startsWith('911~') && !otherTargetFile) { otherTargetFile = zipEntry; fileType = 'final'; } else if (filename.startsWith('909~') && !otherTargetFile) { otherTargetFile = zipEntry; fileType = 'primary'; } }
// ОПТИМИЗАЦИЯ: Для мульти-файлов используем кеширование if (multiFiles.length > 0) { console.log(`Найдено мульти-файлов 904~: ${multiFiles.length}`); const allWells = []; let processedFiles = 0;
// ОПТИМИЗАЦИЯ: Обрабатываем файлы и КЕШИРУЕМ их содержимое for (const multiFile of multiFiles) { if (multiFile.size > 500 * 1024 * 1024) { console.warn(`Файл ${multiFile.name} слишком большой (${(multiFile.size / 1024 / 1024).toFixed(2)} MB), пропускаем`); processedFiles++; continue; }
try { updateProgress(10 + (processedFiles / multiFiles.length) * 70, `Файл ${processedFiles + 1} из ${multiFiles.length}`); // Читаем и кешируем файл const xmlContent = await multiFile.entry.async('text'); // ОПТИМИЗАЦИЯ: Сохраняем в кеш fileCache.set(multiFile.name, xmlContent); // Извлекаем метаданные скважин const wellsFromFile = extractWellsMetadataFromContent(xmlContent); console.log(`Из файла ${multiFile.name} извлечено скважин:`, wellsFromFile.length);
// Добавляем уникальные скважины for (const well of wellsFromFile) { if (!allWells.some(w => w.wellId === well.wellId)) { allWells.push({ ...well, sourceFile: multiFile.name }); } }
processedFiles++; } catch (error) { console.error(`Ошибка обработки файла ${multiFile.name}:`, error); processedFiles++; } }
updateProgress(85, 'Формирование списка скважин...'); console.log(`Всего уникальных скважин: ${allWells.length}`);
if (allWells.length > 0) { // ОПТИМИЗАЦИЯ: Сохраняем только метаданные, файлы уже в кеше currentMultiWellData = { wells: allWells, fileNames: multiFiles.map(f => f.name) // Только имена файлов };
updateProgress(100, 'Готово!'); setTimeout(() => { hideLoading(); currentMultiFileName = file.name; showWellsList(allWells, file.name, multiFiles.length); wellDataElement.innerHTML = '<div class="loading"><p>Выберите скважину из списка</p></div>'; }, 100);
} else { hideLoading(); throw new Error('Не удалось извлечь данные скважин из файлов 904~'); }
} else if (otherTargetFile) { // Обработка обычных файлов (906~, 911~, 909~) updateProgress(50, 'Обработка файла данных...'); console.log('Найден INU файл:', otherTargetFile.name, 'Размер:', otherTargetFile._data.uncompressedSize);
// Увеличиваем лимит размера INU файла до 2000MB if (otherTargetFile._data.uncompressedSize > 2000 * 1024 * 1024) { throw new Error('INU файл слишком большой для обработки в браузере (максимум 2000 МБ)'); }
// Читаем XML данные updateLoadingText('Чтение XML данных...'); console.log('Чтение XML...'); const xmlContent = await otherTargetFile.async('text'); console.log('XML прочитан, длина:', xmlContent.length);
// Парсинг XML данных с обработкой ошибок updateProgress(80, 'Парсинг XML...'); console.log('Парсинг XML...'); await parseXMLData(xmlContent, fileType); console.log('XML распарсен');
updateProgress(100, 'Готово!'); setTimeout(() => { hideLoading();
// Для мульти-файлов показываем список скважин if (fileType === 'multi' && currentMultiWellData && currentMultiWellData.wells.length > 0) { currentMultiFileName = file.name; showWellsList(currentMultiWellData.wells, file.name); wellDataElement.innerHTML = '<div class="loading"><p>Выберите скважину из списка</p></div>'; } else { // Отображение данных displayWellData(fileType, fileNumber, file.name); console.log('Данные отображены'); } }, 500);
} else { hideLoading(); throw new Error('В архиве не найден файл с данными (начинающийся с 904~, 906~, 909~ или 911~)'); }
} catch (error) { hideLoading(); console.error('Ошибка загрузки данных:', error); wellDataElement.innerHTML = ` <div class="error"> <p><strong>Ошибка загрузки данных:</strong> ${error.message}</p> <p><strong>Поддерживаемые размеры:</strong></p> <ul style="text-align: left; margin: 10px 0;"> <li>ZIP архивы: до 1500 МБ</li> <li>INU файлы: до 2000 МБ</li> </ul> <p>Попробуйте выбрать другой файл или обратитесь к администратору</p> </div> `; } } ```
И не забудьте добавить функцию extractWellsMetadataFromContent, которую я показал в предыдущем ответе:
```javascript function extractWellsMetadataFromContent(xmlContent) { const wells = []; try { // Используем регулярные выражения для быстрого извлечения const wellBlocks = xmlContent.split(/<MR_DBA\.POINT_OBSERV_POAA>/); // Пропускаем первый элемент (все что до первого блока) for (let i = 1; i < wellBlocks.length; i++) { const block = wellBlocks[i].split('</MR_DBA.POINT_OBSERV_POAA>')[0]; // Быстрое извлечение данных через регулярки const wellIdMatch = block.match(/<ID_POINT_OBSERV_POAA>([^<]*)<\/ID_POINT_OBSERV_POAA>/); const wellNameMatch = block.match(/<NAME_FULL_POAA>([^<]*)<\/NAME_FULL_POAA>/); const objectMatch = block.match(/<ID_OBJECT_WORK_POAA>([^<]*)<\/ID_OBJECT_WORK_POAA>/); const descriptionMatch = block.match(/<DESCRIPTION_POAA>([^<]*)<\/DESCRIPTION_POAA>/); const wellId = wellIdMatch ? wellIdMatch[1].trim() : null; const wellName = wellNameMatch ? wellNameMatch[1].trim() : 'Без названия'; const object = objectMatch ? objectMatch[1].trim() : 'Не указан'; const description = descriptionMatch ? descriptionMatch[1].trim() : ''; if (wellId && wellId !== 'Не указано' && wellId !== '') { wells.push({ wellId: wellId, wellName: wellName, object: object, description: description }); } } } catch (error) { console.error('Ошибка извлечения метаданных из содержимого:', error); } return wells; } ```
Эта версия использует кеширование файлов, что предотвращает повторное чтение ZIP архива при клике на скважины и значительно ускоряет загрузку.
|
Проблема в том, что мы убрали показ окна загрузки при открытии скважины и обработка блокирует основной поток. Вот исправления:
1. Исправляем функцию parseWellDataFromMulti - добавляем окно загрузки
```javascript // ЗАМЕНИТЕ функцию parseWellDataFromMulti на эту: async function parseWellDataFromMulti(wellId) { if (!currentMultiWellData || !currentMultiWellData.wells) { throw new Error('Данные мульти-файла не загружены'); }
showLoading('Загрузка данных скважины...');
try { // Находим скважину в списке чтобы узнать sourceFile updateProgress(10, 'Поиск информации о скважине...'); const targetWellMeta = currentMultiWellData.wells.find(w => w.wellId === wellId); if (!targetWellMeta) { throw new Error(`Скважина с ID ${wellId} не найдена в списке`); }
const sourceFile = targetWellMeta.sourceFile; if (!sourceFile) { throw new Error('Не указан исходный файл для скважины'); }
updateProgress(30, 'Загрузка данных из кеша...');
// ОПТИМИЗАЦИЯ: Берем данные из КЕША, а не читаем заново из ZIP const xmlContent = fileCache.get(sourceFile); if (!xmlContent) { throw new Error(`Файл ${sourceFile} не найден в кеше`); }
updateProgress(50, 'Обработка XML...');
// ОПТИМИЗАЦИЯ: Разбиваем тяжелые операции на части чтобы не блокировать UI await new Promise(resolve => setTimeout(resolve, 10)); // Даем UI обновиться
const cleanedContent = cleanXMLContent(xmlContent); updateProgress(70, 'Парсинг данных...'); await new Promise(resolve => setTimeout(resolve, 10)); // Еще раз даем UI обновиться
const parser = new DOMParser(); const xmlDoc = parser.parseFromString(cleanedContent, 'text/xml');
// Ищем скважину в XML updateProgress(80, 'Поиск данных скважины...'); const wellNodes = xmlDoc.getElementsByTagName('MR_DBA.POINT_OBSERV_POAA'); let targetWell = null;
for (let i = 0; i < wellNodes.length; i++) { const wellNode = wellNodes[i]; const currentWellId = getSafeTextContent(wellNode, 'ID_POINT_OBSERV_POAA'); if (currentWellId === wellId) { targetWell = wellNode; break; } // ОПТИМИЗАЦИЯ: Даем UI обновиться каждые 50 скважин if (i % 50 === 0) { await new Promise(resolve => setTimeout(resolve, 1)); updateProgress(80 + (i / wellNodes.length) * 10, `Поиск... ${i}/${wellNodes.length}`); } }
if (!targetWell) { throw new Error(`Скважина с ID ${wellId} не найдена в файле ${sourceFile}`); }
updateProgress(95, 'Формирование данных...');
// Сохраняем текущий XML документ currentMultiWellData.currentXmlDoc = xmlDoc;
await parseWellDataFromSingleMultiFile(xmlDoc, targetWell, wellId); updateProgress(100, 'Готово!'); setTimeout(() => { hideLoading(); }, 300);
} catch (error) { hideLoading(); console.error('Ошибка загрузки данных скважины:', error); throw error; } } ```
2. Оптимизируем тяжелую функцию parseWellDataFromSingleMultiFile
```javascript // ДОБАВЬТЕ эту функцию для разбивки тяжелых операций: async function parseWellDataFromSingleMultiFile(xmlDoc, targetWell, wellId) { return new Promise((resolve, reject) => { // Используем requestIdleCallback или setTimeout для разбивки тяжелых операций const processStep = () => { try { // Основная логика парсинга из существующей функции parseWellDataFromSingleMultiFile // но разбитая на этапы // Шаг 1: Основная информация о скважине const wellName = getSafeTextContent(targetWell, 'NAME_FULL_POAA'); const wellDescription = getSafeTextContent(targetWell, 'DESCRIPTION_POAA'); // Находим связанные объекты const objectWork = findRelatedObject(xmlDoc, 'MR_DBA.OBJECT_WORK_OBWA', wellId); const ground = findRelatedObject(xmlDoc, 'MR_DBA.GROUND_GRNA', wellId); const pointObservType = findRelatedObject(xmlDoc, 'MR_DBA.POINT_OBSERV_TYPE_PNOT', wellId); const lineCommon = findRelatedObject(xmlDoc, 'MR_DBA.LINE_COMMON_LNCM', wellId); const object = getSafeTextContent(objectWork, 'NAME_FULL_OBWA'); const area = getSafeTextContent(ground, 'NAME_FULL_GRNA'); const wellType = getSafeTextContent(pointObservType, 'NAME_SHORT_PNOT'); const lineNumber = getSafeTextContent(lineCommon, 'NAME_FULL_LNCM'); // Даты бурения и бригада let drillingStartDate = ''; let drillingEndDate = ''; let drillingBrigade = 'Не указано'; drillingStartDate = formatDate(getSafeTextContent(targetWell, 'DATE_DRIFTING_BEG_POAA')); drillingEndDate = formatDate(getSafeTextContent(targetWell, 'DATE_DRIFTING_END_POAA')); // Получаем название бригады const brigadeId = getSafeTextContent(targetWell, 'BRIGADE_POAA'); if (brigadeId && brigadeId !== 'Не указано') { const divisionNodes = xmlDoc.getElementsByTagName('MR_DBA.OUR_DIVISION'); for (let i = 0; i < divisionNodes.length; i++) { const division = divisionNodes[i]; const divisionId = getSafeTextContent(division, 'ID_CONTRACTOR'); if (divisionId === brigadeId) { const brigadeName = getSafeTextContent(division, 'NAME_SHORT'); if (brigadeName && brigadeName !== 'Не указано') { drillingBrigade = brigadeName; break; } } } if (drillingBrigade === 'Не указано') { drillingBrigade = `Бригада ${brigadeId}`; } } // Создаем карту сотрудников const employeeMap = new Map(); const employeeNodes = xmlDoc.getElementsByTagName('MR_DBA.OUR_EMPLOYEE'); for (let i = 0; i < employeeNodes.length; i++) { const employee = employeeNodes[i]; const employeeId = getSafeTextContent(employee, 'ID_CONTRACTOR'); const employeeName = getSafeTextContent(employee, 'NAME_SHORT'); if (employeeId && employeeName && employeeId !== 'Не указано' && employeeName !== 'Не указано') { employeeMap.set(employeeId, employeeName); } } // Данные документирования для этой скважины let primaryDocInfo = null; let finalDocInfo = null; let gisDocInfo = null; const docNodes = xmlDoc.getElementsByTagName('MR_DBA.PO_DOCUM_PODC'); for (let i = 0; i < docNodes.length; i++) { const doc = docNodes[i]; const docWellId = getSafeTextContent(doc, 'ID_POINT_OBSERV_PODC'); if (docWellId === wellId) { const docType = getSafeTextContent(doc, 'ID_TYPE_DOCUM_PODC'); const startDate = formatDate(getSafeTextContent(doc, 'DATE_BEG_DOCUM_PODC')); const endDate = formatDate(getSafeTextContent(doc, 'DATE_END_DOCUM_PODC')); const depth = formatNumber(getSafeTextContent(doc, 'DEPTH_FACT_PODC')); const authorId = getSafeTextContent(doc, 'AUTHOR_PODC'); let authorName = 'Не указано'; if (authorId && authorId !== 'Не указано' && employeeMap.has(authorId)) { authorName = employeeMap.get(authorId); } else if (authorId && authorId !== 'Не указано') { authorName = `Код: ${authorId}`; } const docInfo = { startDate, endDate, depth, author: authorName }; if (docType.includes('Первич') || docType.includes('ПЕРВИЧ') || docType === '1') { primaryDocInfo = docInfo; } else if (docType.includes('Итогов') || docType.includes('ИТОГОВ') || docType === '2') { finalDocInfo = docInfo; } else if (docType.includes('ГИС') || docType.includes('геофизик')) { gisDocInfo = docInfo; } } } // Сохраняем извлеченные данные xmlData = { wellName, object, area, wellType, wellNumber: wellName, lineNumber, wellDescription, drillingStartDate, drillingEndDate, drillingBrigade, primaryDocInfo, finalDocInfo, gisDocInfo, primaryDocumentation: [], finalDocumentation: [], gisDocumentation: [], assays: [] }; resolve(); } catch (error) { reject(error); } }; // Запускаем обработку с приоритетом для UI if ('requestIdleCallback' in window) { window.requestIdleCallback(processStep); } else { setTimeout(processStep, 1); } }); } ```
3. Добавляем функцию для асинхронной обработки интервалов
```javascript // ДОБАВЬТЕ эту функцию для неблокирующей обработки интервалов: async function processIntervalsAsync(xmlDoc, wellId) { return new Promise((resolve) => { const intervals = { primary: [], final: [], gis: [] }; let processed = 0; const intervalNodes = xmlDoc.getElementsByTagName('MR_DBA.PO_DOCUM_INT_PODI'); const total = intervalNodes.length; const processBatch = (startIndex) => { const batchSize = 100; // Обрабатываем по 100 интервалов за раз for (let i = startIndex; i < Math.min(startIndex + batchSize, total); i++) { const interval = intervalNodes[i]; // ... логика обработки интервала ... processed++; } if (processed < total) { // Даем UI обновиться перед следующей партией setTimeout(() => processBatch(processed), 1); } else { resolve(intervals); } }; processBatch(0); }); } ```
4. Обновляем функцию selectWell для принудительного показа загрузки
```javascript // ОБНОВИТЕ функцию selectWell: function selectWell(wellId, wellNumber) { // Скрываем меню в мобильной версии if (window.innerWidth <= 1024) { const sidebar = document.querySelector('.sidebar'); const overlay = document.getElementById('overlay'); if (sidebar) sidebar.classList.remove('active'); if (overlay) overlay.classList.remove('active'); } // Сброс активного класса у всех элементов document.querySelectorAll('.well-item').forEach(item => { item.classList.remove('active'); });
// Добавление активного класса к выбранному элементу event.currentTarget.classList.add('active');
currentWellId = wellId; // ПРИНУДИТЕЛЬНО показываем окно загрузки showLoading('Подготовка данных...'); // Небольшая задержка чтобы окно загрузки успело показаться setTimeout(async () => { try { // Отображаем данные выбранной скважины await displayWellData('multi', wellNumber, currentMultiFileName, wellId); } catch (error) { console.error('Ошибка загрузки скважины:', error); document.getElementById('wellData').innerHTML = ` <div class="error"> <p><strong>Ошибка загрузки данных скважины:</strong> ${error.message}</p> </div> `; hideLoading(); } }, 50); } ```
Теперь при клике на скважину:
1. Сразу показывается окно загрузки 2. Тяжелые операции разбиты на части с паузами для обновления UI 3. Используется requestIdleCallback для приоритизации отзывчивости 4. Прогресс-бар обновляется во время обработки
Это предотвратит подвисание браузера и покажет пользователю, что данные загружаются.
|
|
|
Отлично, разберём по порядку все issues и реализуем исправления:
1. Исправление отображения названий документов в керновых ящиках
Проблема: В колонке "Связанные документы" у керновых ящиков отображается одно и то же название для всех документов.
Исправление в функции displayCoreBoxes:
```javascript // В функции displayCoreBoxes заменим блок отображения документов: function displayCoreBoxes(coreBoxes) { // ... существующий код ... html += ` <tr class="${basalClass}"> <td style="width: 80px;"> <strong>${box.boxNumber}</strong> </td> <td style="width: 100px;">${formatNumber(box.intervalFrom)}</td> <td style="width: 100px;">${formatNumber(box.intervalTo)}</td> <td style="width: 100px;">${formatNumber(box.thickness)}</td> <td style="width: 120px;">${box.basalHorizon}</td> <td style="width: 200px;"><div class="description-text">${box.description}</div></td> <td style="width: 250px;"> ${hasDocuments ? `<div class="documents-list" style="display: flex; flex-direction: column; gap: 5px;"> ${box.relatedDocuments.map(doc => { const isImage = isImageFile(doc.downloadFileName); // ИСПРАВЛЕНИЕ: используем корректное имя файла для каждого документа const displayFileName = doc.fileName || doc.downloadFileName || `Документ ${doc.id}`; return ` <div style="display: flex; justify-content: space-between; align-items: center; padding: 4px 8px; background: #f8f9fa; border-radius: 4px; border: 1px solid #e9ecef;"> <div style="flex: 1; min-width: 0;"> <div style="font-weight: 500; font-size: 0.8rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="${displayFileName}"> ${displayFileName} </div> <div style="font-size: 0.7rem; color: #6c757d;"> ${doc.fileType} </div> </div> <div style="display: flex; gap: 3px; flex-shrink: 0;"> <button class="preview-doc-btn corebox-preview-btn" data-doc-id="${doc.id}" data-file-name="${doc.downloadFileName || displayFileName}" data-file-type="${doc.fileType}" data-is-image="${isImage}" data-blob-path="${doc.blobPath || ''}" style="padding: 2px 6px; font-size: 0.65rem; background-color: #3498db; color: white; border: none; border-radius: 3px; cursor: pointer;" title="Просмотр"> 👁 </button> <button class="download-doc-btn corebox-download-btn" data-doc-id="${doc.id}" data-file-name="${doc.downloadFileName || displayFileName}" data-blob-path="${doc.blobPath || ''}" style="padding: 2px 6px; font-size: 0.65rem; background-color: #27ae60; color: white; border: none; border-radius: 3px; cursor: pointer;" title="Скачать"> 📥 </button> </div> </div> `; }).join('')} </div>` : `<span style="color: #95a5a6; font-style: italic;">нет связанных документов</span>` } </td> <td style="width: 100px;">${box.inputDate || 'Не указана'}</td> </tr> `; // ... остальной код ... } ```
2. Добавление фильтра документов по текущей скважине
Добавим в функцию displayAttachedDocuments:
```javascript function displayAttachedDocuments(documents) { if (!documents || documents.length === 0) { return '<div class="no-data"><p>Нет вложенных документов</p></div>'; } // Получаем информацию о текущей открытой скважине const currentWellName = xmlData ? xmlData.wellName : ''; const currentLineNumber = xmlData ? xmlData.lineNumber : ''; // Форматируем отображаемое название текущей скважины let currentWellDisplay = ''; if (currentLineNumber && currentLineNumber !== 'Не указано' && currentWellName) { currentWellDisplay = `л${currentLineNumber} - ${currentWellName}`; } else if (currentWellName) { currentWellDisplay = currentWellName; } // Фильтруем документы для текущей скважины const currentWellDocuments = documents.filter(doc => { if (!doc.wellInfo) return false; return doc.wellInfo === currentWellDisplay; }); // Создаем состояние фильтра (по умолчанию включен) let filterCurrentWell = true; let html = ` <div class="doc-info-list" style="margin-bottom: 20px;"> <div class="doc-info-item"> <span class="doc-info-label">Всего документов:</span> <span class="doc-info-value"><strong>${documents.length}</strong></span> </div> <div class="doc-info-item"> <span class="doc-info-label">Текущая скважина:</span> <span class="doc-info-value"><strong>${currentWellDisplay || 'Не определена'}</strong></span> </div> <div class="doc-info-item"> <span class="doc-info-label">Фильтр:</span> <span class="doc-info-value"> <label class="checkbox-label-inline" style="margin: 0;"> <input type="checkbox" id="filterCurrentWell" checked> <span class="checkmark-inline"></span> <span class="checkbox-text">Показывать только документы текущей скважины</span> </label> <span style="margin-left: 10px; color: #7f8c8d; font-size: 0.8rem;"> (${currentWellDocuments.length} из ${documents.length}) </span> </span> </div> </div> <div class="table-wrapper"> <div class="table-header"> <table class="interval-table" style="min-width: 1400px;"> <thead> <tr> <th style="width: 50px;">№</th> <th style="width: 200px;">Скважина</th> <th style="width: 200px;">Название документа</th> <th style="width: 120px;">Тип файла</th> <th style="width: 200px;">Описание</th> <th style="width: 100px;">Автор</th> <th style="width: 100px;">Дата</th> <th style="width: 150px;">Статус связи</th> <th style="width: 150px;">Действия</th> </tr> </thead> </table> </div> <div class="table-container"> <table class="interval-table" style="min-width: 1400px;"> <tbody id="documentsTableBody"> `; // Функция для отображения строк документов function renderDocumentsRows(docsToShow) { return docsToShow.map((doc, index) => { const displayName = doc.fileName || `Документ ${index + 1}`; const displayType = doc.fileType || 'Неизвестно'; const displayDescription = doc.description || 'Нет описания'; const displayAuthor = doc.author || 'Не указан'; const displayDate = doc.uploadDate || 'Не указана'; const downloadFileName = doc.downloadFileName || displayName; const blobPath = doc.blobPath || ''; const isImage = /\.(jpg|jpeg|png|gif|bmp|webp|tiff|tif)$/i.test(downloadFileName); let rowClass = ''; let statusText = ''; let statusColor = ''; let wellInfo = doc.wellInfo || ''; // Определяем статус связи if (wellInfo && currentWellDisplay && wellInfo === currentWellDisplay) { statusText = 'Связан с текущей скважиной'; statusColor = '#27ae60'; } else if (wellInfo) { rowClass = 'unrelated-document'; statusText = 'Принадлежит другой скважине'; statusColor = '#e67e22'; } else if (doc.relationType === 'unknown') { statusText = 'Связь не определена'; statusColor = '#95a5a6'; } else { rowClass = 'unrelated-document'; statusText = 'Не связан со скважиной'; statusColor = '#e67e22'; } return ` <tr class="${rowClass}"> <td style="width: 50px;">${index + 1}</td> <td style="width: 200px;"> ${wellInfo ? `<div style="font-weight: 500; color: #2c3e50;">${wellInfo}</div>` : ''} </td> <td style="width: 200px;"> <div class="file-name-display">${displayName}</div> ${doc.fileExtension ? `<div class="file-extension" title="Имя файла: ${downloadFileName}">${doc.fileExtension}</div>` : ''} </td> <td style="width: 120px;">${displayType}</td> <td style="width: 200px;"><div class="description-text">${displayDescription}</div></td> <td style="width: 100px;">${displayAuthor}</td> <td style="width: 100px;">${displayDate}</td> <td style="width: 150px; color: ${statusColor}; font-weight: 500;">${statusText}</td> <td style="width: 150px;"> <div style="display: flex; gap: 5px; flex-wrap: wrap;"> <button class="btn preview-doc-btn" data-doc-id="${doc.id}" data-file-name="${downloadFileName}" data-file-type="${displayType}" data-is-image="${isImage}" data-blob-path="${blobPath}" style="padding: 5px 8px; font-size: 0.75rem;"> Просмотр </button> <button class="btn download-doc-btn" data-doc-id="${doc.id}" data-file-name="${downloadFileName}" data-blob-path="${blobPath}" data-file-type="${displayType}" style="padding: 5px 8px; font-size: 0.75rem; background-color: #27ae60;"> Скачать </button> </div> </td> </tr> `; }).join(''); } // Первоначальное отображение (только текущая скважина) html += renderDocumentsRows(currentWellDocuments); html += ` </tbody> </table> </div> </div> <div class="doc-info-list" style="margin-top: 20px; background: #f8f9fa; padding: 15px; border-radius: 6px;"> <div class="doc-info-item"> <span class="doc-info-label">Пояснение статусов:</span> <span class="doc-info-value"> <strong>Связан с текущей скважиной</strong> - документ принадлежит открытой скважине<br> <strong>Принадлежит другой скважине</strong> - документ связан с другой скважиной объекта<br> <strong>Не связан со скважиной</strong> - документ не привязан к конкретной скважине<br> <strong>Связь не определена</strong> - не удалось определить принадлежность документа </span> </div> </div> <!-- Модальное окно для предпросмотра --> <div id="docPreviewModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 2000; justify-content: center; align-items: center; backdrop-filter: blur(5px);"> <div style="background: white; padding: 20px; border-radius: 8px; max-width: 90%; max-height: 90%; overflow: auto; box-shadow: 0 10px 30px rgba(0,0,0,0.3); border: 1px solid #ddd;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;"> <h3 id="modalDocTitle" style="margin: 0; color: #2c3e50;">Просмотр документа</h3> <button id="closeModalBtn" style="background: #e74c3c; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; font-size: 14px;">✕ Закрыть</button> </div> <div id="modalDocContent"> <p>Загрузка документа...</p> </div> </div> </div> <script> // Обработчик фильтра документов document.getElementById('filterCurrentWell').addEventListener('change', function() { const filterEnabled = this.checked; const tbody = document.getElementById('documentsTableBody'); if (filterEnabled) { // Показываем только документы текущей скважины const currentWellDocs = ${JSON.stringify(currentWellDocuments)}; tbody.innerHTML = \`${renderDocumentsRows(currentWellDocuments)}\`; } else { // Показываем все документы const allDocs = ${JSON.stringify(documents)}; tbody.innerHTML = \`${renderDocumentsRows(documents)}\`; } // Переинициализируем обработчики кнопок setTimeout(() => { if (window.initializeDocumentButtons) { window.initializeDocumentButtons(); } }, 100); }); </script> `; return html; } ```
3. Кнопки выгрузки в Excel для таблиц
Добавим функцию экспорта и кнопки для каждой таблицы:
```javascript // Добавим функцию для экспорта в Excel function exportToExcel(tableElement, fileName) { try { // Создаем временную таблицу для экспорта const tempTable = document.createElement('table'); const thead = tableElement.querySelector('thead'); const tbody = tableElement.querySelector('tbody'); if (thead) tempTable.appendChild(thead.cloneNode(true)); if (tbody) tempTable.appendChild(tbody.cloneNode(true)); // Конвертируем таблицу в CSV let csv = ''; const rows = tempTable.querySelectorAll('tr'); rows.forEach(row => { const cols = row.querySelectorAll('th, td'); const rowData = []; cols.forEach(col => { // Убираем HTML теги и лишние пробелы let text = col.textContent.replace(/\s+/g, ' ').trim(); // Экранируем кавычки и добавляем в CSV text = text.replace(/"/g, '""'); rowData.push(`"${text}"`); }); csv += rowData.join(';') + '\\n'; }); // Создаем и скачиваем файл const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.setAttribute('href', url); link.setAttribute('download', `${fileName}_${new Date().toISOString().split('T')[0]}.csv`); link.style.display = 'none'; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); showNotification(`Таблица "${fileName}" успешно экспортирована`, 'success'); } catch (error) { console.error('Ошибка экспорта в Excel:', error); showNotification('Ошибка при экспорте таблицы', 'error'); } }
// Добавим кнопки экспорта в функции отображения таблиц:
// В displayPrimaryDocumentation, displayFinalDocumentation, displayGisDocumentation: function displayPrimaryDocumentation() { let html = ''; // ... существующий код информации о документировании ... html += ` <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;"> <h3 style="margin: 0; color: #2c3e50;">Первичное документирование</h3> <button class="btn export-excel-btn" style="background-color: #27ae60; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-size: 0.85rem;" onclick="exportToExcel(document.querySelector('#primaryInnerTab .table-wrapper table'), 'Первичное_документирование_${xmlData.wellName}')"> 📊 Выгрузить в Excel </button> </div> `; // ... остальной код таблицы ... return html; }
// Аналогично для displayFinalDocumentation и displayGisDocumentation
// В displayAssays(): function displayAssays() { let html = ''; html += ` <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;"> <h3 style="margin: 0; color: #2c3e50;">Опробование</h3> <button class="btn export-excel-btn" style="background-color: #27ae60; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-size: 0.85rem;" onclick="exportToExcel(document.querySelector('#assaysInnerTab .table-wrapper table'), 'Опробование_${xmlData.wellName}')"> 📊 Выгрузить в Excel </button> </div> `; // ... остальной код таблицы ... return html; }
// В displayCoreBoxes(): function displayCoreBoxes(coreBoxes) { // ... существующий код ... html += ` <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;"> <h3 style="margin: 0; color: #2c3e50;">Керновые ящики</h3> <button class="btn export-excel-btn" style="background-color: #27ae60; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-size: 0.85rem;" onclick="exportToExcel(document.querySelector('#coreboxesTab .table-wrapper table'), 'Керновые_ящики_${xmlData.wellName}')"> 📊 Выгрузить в Excel </button> </div> `; // ... остальной код ... return html; }
// В displayAttachedDocuments(): function displayAttachedDocuments(documents) { // ... существующий код ... html += ` <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;"> <h3 style="margin: 0; color: #2c3e50;">Вложенные документы</h3> <button class="btn export-excel-btn" style="background-color: #27ae60; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-size: 0.85rem;" onclick="exportToExcel(document.querySelector('#documentsTab .table-wrapper table'), 'Вложенные_документы_${xmlData.wellName}')"> 📊 Выгрузить в Excel </button> </div> `; // ... остальной код ... return html; } ```
4. Исправление отображения номера линии
Исправим в функции displayWellData:
```javascript // В функции displayWellData найдем блок с заголовком и исправим: document.getElementById('wellName').innerHTML = ` <div class="well-header-content"> <div class="doc-type-badge ${badgeClass}">${badgeText}</div> <div class="well-title"> ${xmlData.lineNumber && xmlData.lineNumber !== 'Не указано' ? ` <div class="well-line-container"> <div class="well-line-label">Линия №:</div> <div class="well-line-number">${xmlData.lineNumber}</div> </div> ` : ''} <div class="well-number-container"> <div class="well-number-label">Номер выработки:</div> <div class="well-line-number">${xmlData.wellName}</div> </div> </div> ${xmlData.wellType && xmlData.wellType !== 'Не указано' ? ` <div class="well-type-inline"> <span class="well-type-label">Тип скважины:</span> <span class="well-type-value">${xmlData.wellType}</span> </div> ` : ''} </div> `; ```
5. Исправление разделителей при фильтрации
Исправим в функции searchInFileList:
```javascript function searchInFileList() { const fileItems = document.querySelectorAll('#fileList .file-item, #fileList .folder-separator'); let visibleCount = 0; let lastVisibleItem = null; fileItems.forEach(item => { if (item.classList.contains('folder-separator')) { // Для разделителя: показываем только если перед ним был видимый элемент const prevItem = item.previousElementSibling; if (prevItem && prevItem.style.display !== 'none' && lastVisibleItem) { item.style.display = 'block'; } else { item.style.display = 'none'; } return; } const fileName = item.querySelector('.file-name')?.textContent.toLowerCase() || ''; const filePath = item.querySelector('.file-path')?.textContent.toLowerCase() || ''; const isMatch = fileName.includes(currentSearchQuery) || filePath.includes(currentSearchQuery); item.style.display = isMatch ? 'flex' : 'none'; if (isMatch) { visibleCount++; lastVisibleItem = item; } }); // Обновляем счетчик const subfolderText = searchSubfolders ? ' (включая подпапки)' : ''; const searchText = currentSearchQuery ? ` • Найдено: ${visibleCount}` : ''; document.getElementById('fileCounter').textContent = `Найдено архивов: ${visibleCount}${subfolderText}${searchText}`; } ```
6. Сброс списка при выборе новой папки
Исправим в функции selectFolder:
```javascript async function selectFolder() { try { // Сбрасываем состояние при выборе новой папки clearCaches(); // Сбрасываем список скважин если он открыт const wellsList = document.getElementById('wellsList'); const fileList = document.getElementById('fileList'); const backButton = document.getElementById('backButton'); if (wellsList && wellsList.style.display === 'block') { wellsList.style.display = 'none'; wellsList.innerHTML = ''; if (fileList) fileList.style.display = 'block'; if (backButton) backButton.style.display = 'none'; // Сбрасываем глобальные переменные currentMultiWellData = null; currentWellId = null; currentMultiFileName = null; // Обновляем счетчик document.getElementById('fileCounter').textContent = ''; } if ('showDirectoryPicker' in window) { currentDirectoryHandle = await window.showDirectoryPicker(); // Форматируем путь в стиле директории let folderPath = await formatFolderPath(currentDirectoryHandle); document.getElementById('folderPath').innerHTML = ` <div class="folder-path-content"> <span class="folder-icon">📂</span> <span class="folder-text">${folderPath}</span> </div> `; await loadFileList(); } else { alert('Ваш браузер не поддерживает выбор папок. Пожалуйста, используйте современный браузер (Chrome 86+, Edge 86+).'); } } catch (error) { console.error('Ошибка выбора папки:', error); if (error.name !== 'AbortError') { alert('Ошибка при выборе папки: ' + error.message); } } } ```
7. Обработка множественных скважин в файлах 906, 911 и фильтрация керновых ящиков
Исправим логику определения количества скважин и фильтрации:
```javascript // В функции parseSingleWellData добавим подсчет скважин: async function parseSingleWellData(xmlDoc, fileType) { try { // Считаем количество уникальных скважин в файле const wellPoints = xmlDoc.getElementsByTagName('MR_DBA.POINT_OBSERV_POAA'); const uniqueWellIds = new Set(); for (let i = 0; i < wellPoints.length; i++) { const wellId = getSafeTextContent(wellPoints[i], 'ID_POINT_OBSERV_POAA'); if (wellId && wellId !== 'Не указано') { uniqueWellIds.add(wellId); } } const wellCount = uniqueWellIds.size; console.log(`Найдено уникальных скважин в файле: ${wellCount}`); // Если скважин больше одной, обрабатываем как мульти-файл if (wellCount > 1 && (fileType === 'all' || fileType === 'final' || fileType === 'primary')) { console.log(`Файл ${fileType} содержит ${wellCount} скважин, обрабатываем как мульти-файл`); const wells = extractWellsMetadataFromContent(new XMLSerializer().serializeToString(xmlDoc)); if (wells.length > 0) { currentMultiWellData = { wells: wells, xmlContent: new XMLSerializer().serializeToString(xmlDoc), fileNames: ['current_file'], wellCount: wellCount }; return { isMulti: true, wells: wells }; } } // ... существующий код обработки одиночной скважины ... // ФИЛЬТРАЦИЯ КЕРНОВЫХ ЯЩИКОВ ДЛЯ ТЕКУЩЕЙ СКВАЖИНЫ if (xmlData && xmlData.coreBoxes && xmlData.coreBoxes.length > 0) { const currentWellId = getSafeTextContent(pointObserv, 'ID_POINT_OBSERV_POAA'); if (currentWellId && currentWellId !== 'Не указано') { console.log(`Фильтрация керновых ящиков для скважины ${currentWellId}`); // Используем существующую функцию фильтрации const filteredCoreBoxes = filterCoreBoxesByWellId(xmlData.coreBoxes, currentWellId, xmlContent); xmlData.coreBoxes = filteredCoreBoxes; console.log(`После фильтрации осталось ящиков: ${filteredCoreBoxes.length}`); } } return { isMulti: false, data: xmlData }; } catch (error) { console.error('Ошибка парсинга одиночного файла:', error); throw error; } }
// Добавим функцию фильтрации керновых ящиков по ID скважины function filterCoreBoxesByWellId(coreBoxes, wellId, xmlContent) { if (!coreBoxes || coreBoxes.length === 0) return []; console.log(`Фильтрация ${coreBoxes.length} ящиков для скважины ${wellId}`); // Создаем карты связей для фильтрации const driftingToPointMap = new Map(); const coreToDriftingMap = new Map(); try { // Заполняем карту проходка -> скважина const driftingRegex = /<MR_DBA\.PO_DRIFTING_PODR>[\s\S]*?<\/MR_DBA\.PO_DRIFTING_PODR>/g; const driftingMatches = xmlContent.match(driftingRegex) || []; for (const driftingMatch of driftingMatches) { const driftingId = extractValue(driftingMatch, 'ID_PO_DRIFTING_PODR'); const pointObservId = extractValue(driftingMatch, 'ID_POINT_OBSERV_PODR'); if (driftingId && pointObservId) { driftingToPointMap.set(driftingId, pointObservId); } } // Заполняем карту керн -> проходка const coreRegex = /<MR_DBA\.PO_DRIFTING_CORE_POCR>[\s\S]*?<\/MR_DBA\.PO_DRIFTING_CORE_POCR>/g; const coreMatches = xmlContent.match(coreRegex) || []; for (const coreMatch of coreMatches) { const coreId = extractValue(coreMatch, 'ID_PO_DRIFTING_CORE_POCR'); const driftingId = extractValue(coreMatch, 'ID_PO_DRIFTING_POCR'); if (coreId && driftingId) { coreToDriftingMap.set(coreId, driftingId); } } } catch (error) { console.error('Ошибка создания карт для фильтрации ящиков:', error); } // Фильтруем ящики const filteredBoxes = coreBoxes.filter(box => { // Проверяем связь через цепочку: Ящик -> Керн -> Проходка -> Скважина if (box.coreId && coreToDriftingMap.has(box.coreId)) { const driftingId = coreToDriftingMap.get(box.coreId); if (driftingId && driftingToPointMap.has(driftingId)) { const pointObservId = driftingToPointMap.get(driftingId); return pointObservId === wellId; } } // Проверяем прямую связь const directWellId = extractValueFromBlock(xmlContent, 'PO_DRIFTING_BOX_PODB', box.id, 'ID_POINT_OBSERV_PODB'); if (directWellId && directWellId === wellId) { return true; } return false; }); console.log(`После фильтрации осталось ящиков: ${filteredBoxes.length}`); return filteredBoxes; }
// Вспомогательная функция для извлечения значения из блока function extractValueFromBlock(xmlContent, blockTag, blockId, valueTag) { try { const blockRegex = new RegExp(`<MR_DBA\\.${blockTag}>(?:[^<]|<(?!\\/MR_DBA\\.${blockTag}>))*<ID_${blockTag}>${blockId}<\\/ID_${blockTag}>(?:[^<]|<(?!\\/MR_DBA\\.${blockTag}>))*<\\/MR_DBA\\.${blockTag}>`, 's'); const blockMatch = xmlContent.match(blockRegex); if (blockMatch) { return extractValue(blockMatch[0], valueTag); } } catch (error) { console.error(`Ошибка извлечения ${valueTag} из ${blockTag}:`, error); } return null; }
// Обновим функцию loadWellData для обработки мульти-скважинных файлов: async function loadWellData(file, fileNumber) { // ... существующий код ... } else if (otherTargetFile) { // Обработка обычных файлов (906~, 911~, 909~) updateProgress(50, 'Обработка файла данных...'); // Читаем XML данные const xmlContent = await otherTargetFile.async('text'); // Парсинг XML данных updateProgress(80, 'Парсинг XML...'); const parseResult = await parseSingleWellData(xmlDoc, fileType); if (parseResult.isMulti) { // Если файл содержит несколько скважин, показываем список updateProgress(100, 'Готово!'); setTimeout(() => { hideLoading(); currentMultiFileName = file.name; showWellsList(parseResult.wells, file.name, 1); wellDataElement.innerHTML = '<div class="loading"><p>Выберите скважину из списка</p></div>'; }, 100); } else { // Одиночная скважина - отображаем сразу xmlData = parseResult.data; updateProgress(100, 'Готово!'); setTimeout(() => { hideLoading(); displayWellData(fileType, fileNumber, file.name); }, 500); } } // ... остальной код ... } ```
Все эти исправления решают указанные проблемы и добавляют запрошенную функциональность. Код готов к интеграции в существующую систему.
|
Gsgsgsgsgsgsg Исправлю ошибки с фильтром документов и файлами:
1. Исправление ошибки "unexpected end of input" в фильтре документов
Проблема: Неправильное использование шаблонных строк и JSON.
Исправленная функция displayAttachedDocuments:
```javascript function displayAttachedDocuments(documents) { if (!documents || documents.length === 0) { return '<div class="no-data"><p>Нет вложенных документов</p></div>'; } // Получаем информацию о текущей открытой скважине const currentWellName = xmlData ? xmlData.wellName : ''; const currentLineNumber = xmlData ? xmlData.lineNumber : ''; // Форматируем отображаемое название текущей скважины let currentWellDisplay = ''; if (currentLineNumber && currentLineNumber !== 'Не указано' && currentWellName) { currentWellDisplay = `л${currentLineNumber} - ${currentWellName}`; } else if (currentWellName) { currentWellDisplay = currentWellName; } // Фильтруем документы для текущей скважины const currentWellDocuments = documents.filter(doc => { if (!doc.wellInfo) return false; return doc.wellInfo === currentWellDisplay; });
let html = ` <div class="doc-info-list" style="margin-bottom: 20px;"> <div class="doc-info-item"> <span class="doc-info-label">Всего документов:</span> <span class="doc-info-value"><strong>${documents.length}</strong></span> </div> <div class="doc-info-item"> <span class="doc-info-label">Текущая скважина:</span> <span class="doc-info-value"><strong>${currentWellDisplay || 'Не определена'}</strong></span> </div> <div class="doc-info-item"> <span class="doc-info-label">Фильтр:</span> <span class="doc-info-value"> <label class="checkbox-label-inline" style="margin: 0;"> <input type="checkbox" id="filterCurrentWell" checked> <span class="checkmark-inline"></span> <span class="checkbox-text">Показывать только документы текущей скважины</span> </label> <span style="margin-left: 10px; color: #7f8c8d; font-size: 0.8rem;"> (${currentWellDocuments.length} из ${documents.length}) </span> </span> </div> </div> <div class="table-wrapper"> <div class="table-header"> <table class="interval-table" style="min-width: 1400px;"> <thead> <tr> <th style="width: 50px;">№</th> <th style="width: 200px;">Скважина</th> <th style="width: 200px;">Название документа</th> <th style="width: 120px;">Тип файла</th> <th style="width: 200px;">Описание</th> <th style="width: 100px;">Автор</th> <th style="width: 100px;">Дата</th> <th style="width: 150px;">Статус связи</th> <th style="width: 150px;">Действия</th> </tr> </thead> </table> </div> <div class="table-container"> <table class="interval-table" style="min-width: 1400px;"> <tbody id="documentsTableBody"> `;
// Функция для отображения строк документов function renderDocumentsRows(docsToShow) { let rowsHtml = ''; docsToShow.forEach((doc, index) => { const displayName = doc.fileName || `Документ ${index + 1}`; const displayType = doc.fileType || 'Неизвестно'; const displayDescription = doc.description || 'Нет описания'; const displayAuthor = doc.author || 'Не указан'; const displayDate = doc.uploadDate || 'Не указана'; const downloadFileName = doc.downloadFileName || displayName; const blobPath = doc.blobPath || ''; const isImage = /\.(jpg|jpeg|png|gif|bmp|webp|tiff|tif)$/i.test(downloadFileName); let rowClass = ''; let statusText = ''; let statusColor = ''; let wellInfo = doc.wellInfo || ''; // Определяем статус связи if (wellInfo && currentWellDisplay && wellInfo === currentWellDisplay) { statusText = 'Связан с текущей скважиной'; statusColor = '#27ae60'; } else if (wellInfo) { rowClass = 'unrelated-document'; statusText = 'Принадлежит другой скважине'; statusColor = '#e67e22'; } else if (doc.relationType === 'unknown') { statusText = 'Связь не определена'; statusColor = '#95a5a6'; } else { rowClass = 'unrelated-document'; statusText = 'Не связан со скважиной'; statusColor = '#e67e22'; } rowsHtml += ` <tr class="${rowClass}"> <td style="width: 50px;">${index + 1}</td> <td style="width: 200px;"> ${wellInfo ? `<div style="font-weight: 500; color: #2c3e50;">${wellInfo}</div>` : ''} </td> <td style="width: 200px;"> <div class="file-name-display">${displayName}</div> ${doc.fileExtension ? `<div class="file-extension" title="Имя файла: ${downloadFileName}">${doc.fileExtension}</div>` : ''} </td> <td style="width: 120px;">${displayType}</td> <td style="width: 200px;"><div class="description-text">${displayDescription}</div></td> <td style="width: 100px;">${displayAuthor}</td> <td style="width: 100px;">${displayDate}</td> <td style="width: 150px; color: ${statusColor}; font-weight: 500;">${statusText}</td> <td style="width: 150px;"> <div style="display: flex; gap: 5px; flex-wrap: wrap;"> <button class="btn preview-doc-btn" data-doc-id="${doc.id}" data-file-name="${downloadFileName}" data-file-type="${displayType}" data-is-image="${isImage}" data-blob-path="${blobPath}" style="padding: 5px 8px; font-size: 0.75rem;"> Просмотр </button> <button class="btn download-doc-btn" data-doc-id="${doc.id}" data-file-name="${downloadFileName}" data-blob-path="${blobPath}" data-file-type="${displayType}" style="padding: 5px 8px; font-size: 0.75rem; background-color: #27ae60;"> Скачать </button> </div> </td> </tr> `; }); return rowsHtml; } // Первоначальное отображение (только текущая скважина) html += renderDocumentsRows(currentWellDocuments); html += ` </tbody> </table> </div> </div> <div class="doc-info-list" style="margin-top: 20px; background: #f8f9fa; padding: 15px; border-radius: 6px;"> <div class="doc-info-item"> <span class="doc-info-label">Пояснение статусов:</span> <span class="doc-info-value"> <strong>Связан с текущей скважиной</strong> - документ принадлежит открытой скважине<br> <strong>Принадлежит другой скважине</strong> - документ связан с другой скважиной объекта<br> <strong>Не связан со скважиной</strong> - документ не привязан к конкретной скважине<br> <strong>Связь не определена</strong> - не удалось определить принадлежность документа </span> </div> </div> `; // Добавляем скрипт для обработки фильтра html += ` <script> // Сохраняем данные документов в глобальной переменной window.documentsData = ${JSON.stringify(documents)}; window.currentWellDocumentsData = ${JSON.stringify(currentWellDocuments)}; // Обработчик фильтра документов document.addEventListener('DOMContentLoaded', function() { const filterCheckbox = document.getElementById('filterCurrentWell'); if (filterCheckbox) { filterCheckbox.addEventListener('change', function() { const filterEnabled = this.checked; const tbody = document.getElementById('documentsTableBody'); if (tbody) { if (filterEnabled) { // Показываем только документы текущей скважины tbody.innerHTML = \`${renderDocumentsRows(currentWellDocuments)}\`; } else { // Показываем все документы tbody.innerHTML = \`${renderDocumentsRows(documents)}\`; } // Переинициализируем обработчики кнопок setTimeout(() => { if (window.initializeDocumentButtons) { window.initializeDocumentButtons(); } }, 100); } }); } }); </script> `; return html; } ```
2. Исправление ошибки "file not found" при скачивании документов
Улучшенная функция findFileInArchiveByBlobPath:
```javascript async function findFileInArchiveByBlobPath(blobPath, fileName) { if (!currentZip) { throw new Error('Архив не загружен'); } try { console.log('Универсальный поиск файла в архиве:'); console.log('- Blob path:', blobPath); console.log('- Имя файла:', fileName); if (!blobPath) { throw new Error('Путь к файлу не указан'); } // Нормализуем путь - убираем начальные слеши и заменяем обратные слеши const normalizedPath = blobPath.replace(/^[\\\/]+/, '').replace(/\\/g, '/'); console.log('- Нормализованный путь:', normalizedPath); // Извлекаем имя файла из пути (последняя часть) const pathParts = normalizedPath.split('/'); const lastPart = pathParts[pathParts.length - 1]; const fileNameFromPath = lastPart || fileName; console.log('- Имя файла из пути:', fileNameFromPath); // Пробуем разные варианты пути const possiblePaths = [ normalizedPath, fileName, fileNameFromPath, // Добавляем варианты без расширения и с разными регистрами fileNameFromPath.toLowerCase(), fileNameFromPath.toUpperCase(), // Пробуем найти по части имени ...fileNameFromPath.split('.').slice(0, -1) // без расширения ]; // Добавляем варианты с поиском в подпапках if (pathParts.length > 1) { // Путь без первой папки (для случаев когда первая папка - временная) possiblePaths.push(pathParts.slice(1).join('/')); // Только имя файла из последней части possiblePaths.push(pathParts[pathParts.length - 1]); // Добавляем все возможные комбинации подпутей for (let i = 1; i < pathParts.length; i++) { possiblePaths.push(pathParts.slice(i).join('/')); } } // Убираем дубликаты и пустые значения const uniquePaths = [...new Set(possiblePaths.filter(path => path && path.trim() !== ''))]; console.log('- Возможные пути для поиска:', uniquePaths); // Ищем по всем возможным путям for (const searchPath of uniquePaths) { console.log('- Пробуем путь:', searchPath); const fileEntry = currentZip.file(searchPath); if (fileEntry) { console.log('- Файл найден по пути:', searchPath); return fileEntry; } // Пробуем найти файл без учета регистра for (const filename in currentZip.files) { if (!currentZip.files[filename].dir && filename.toLowerCase() === searchPath.toLowerCase()) { console.log('- Файл найден (без учета регистра):', filename); return currentZip.files[filename]; } } } // Расширенный поиск по всем файлам в архиве console.log('- Расширенный поиск по всем файлам...'); const allFiles = []; let bestMatch = null; for (const filename in currentZip.files) { if (!currentZip.files[filename].dir) { allFiles.push(filename); // Ищем по полному имени файла const normalizedFilename = filename.toLowerCase(); const searchFileName = fileNameFromPath.toLowerCase(); if (normalizedFilename.includes(searchFileName) || searchFileName.includes(normalizedFilename) || (fileName && normalizedFilename.includes(fileName.toLowerCase()))) { console.log('- Возможное совпадение:', filename); // Выбираем лучшее совпадение (самое короткое имя) if (!bestMatch || filename.length < bestMatch.length) { bestMatch = currentZip.files[filename]; } } } } if (bestMatch) { console.log('- Используем лучшее совпадение:', bestMatch.name); return bestMatch; } // Если не нашли, выводим список всех файлов для отладки console.log('- Все файлы в архиве:', allFiles); console.log('- Искомые параметры:', { normalizedPath, fileName, fileNameFromPath }); console.log('Файл не найден в архиве'); return null; } catch (error) { console.error('Ошибка поиска файла в архиве:', error); return null; } } ```
3. Улучшенная функция скачивания с лучшей обработкой ошибок:
```javascript async function downloadDocument(docId, fileName, blobPath) { if (downloadInProgress) { console.log('Скачивание уже в процессе, пропускаем...'); return; } downloadInProgress = true; try { console.log(`Скачивание документа: ${fileName} (ID: ${docId})`); if (!blobPath) { throw new Error('Путь к файлу не найден в метаданных документа'); } showNotification(`Поиск файла: ${fileName}`, 'info'); // Извлекаем имя файла из blob path const blobFileName = blobPath.split('\\').pop() || fileName; console.log(`Имя файла в архиве: ${blobFileName}`); // Ищем файл в текущем архиве const fileEntry = await findFileInArchiveByBlobPath(blobPath, blobFileName); if (fileEntry) { console.log('Файл найден в архиве:', fileEntry.name); await downloadFileFromZip(fileEntry, fileName); } else { // Пробуем найти любой файл с похожим именем console.log('Пробуем найти файл по похожему имени...'); let alternativeFile = null; for (const filename in currentZip.files) { if (!currentZip.files[filename].dir) { const baseName = fileName.split('.')[0].toLowerCase(); const archiveBaseName = filename.split('.')[0].toLowerCase(); if (archiveBaseName.includes(baseName) || baseName.includes(archiveBaseName)) { alternativeFile = currentZip.files[filename]; console.log('Найден альтернативный файл:', filename); break; } } } if (alternativeFile) { await downloadFileFromZip(alternativeFile, fileName); } else { throw new Error(`Файл "${blobFileName}" не найден в архиве. Проверьте наличие файла в архиве.`); } } } catch (error) { console.error('Ошибка скачивания документа:', error); showNotification(`Ошибка скачивания: ${error.message}`, 'error'); } finally { downloadInProgress = false; } } ```
4. Дополнительное исправление для инициализации кнопок в фильтре:
```javascript // Добавим глобальную функцию для переинициализации кнопок function reinitializeDocumentButtons() { console.log('Переинициализация кнопок документов...'); // Обработчики для кнопок просмотра document.querySelectorAll('.preview-doc-btn, .corebox-preview-btn').forEach(btn => { // Удаляем старые обработчики btn.replaceWith(btn.cloneNode(true)); }); // Обработчики для кнопок скачивания document.querySelectorAll('.download-doc-btn, .corebox-download-btn').forEach(btn => { // Удаляем старые обработчики btn.replaceWith(btn.cloneNode(true)); }); // Инициализируем заново initializeDocumentButtons(); }
// Обновим обработчик фильтра: html += ` <script> document.addEventListener('DOMContentLoaded', function() { const filterCheckbox = document.getElementById('filterCurrentWell'); if (filterCheckbox) { filterCheckbox.addEventListener('change', function() { const filterEnabled = this.checked; const tbody = document.getElementById('documentsTableBody'); if (tbody) { if (filterEnabled) { tbody.innerHTML = \`${renderDocumentsRows(currentWellDocuments)}\`; } else { tbody.innerHTML = \`${renderDocumentsRows(documents)}\`; } // Переинициализируем обработчики кнопок setTimeout(() => { if (window.reinitializeDocumentButtons) { window.reinitializeDocumentButtons(); } }, 100); } }); } }); </script> `; ```
Эти исправления должны решить проблемы с:
1. Ошибкой "unexpected end of input" в фильтре документов 2. Проблемами поиска файлов в архивах 3. Корректной работой кнопок после применения фильтра
|
Вот полная реализация функции parseWellDataFromMulti и добавление кнопок Excel во все таблицы:
1. Полная функция parseWellDataFromMulti
```javascript // Функция загрузки данных скважины из мульти-файла async function parseWellDataFromMulti(wellId, signal = null) { // ПРОВЕРЯЕМ отмену операции if (signal && signal.aborted) { throw new DOMException('Operation aborted', 'AbortError'); }
if (!currentMultiWellData || !currentMultiWellData.wells) { throw new Error('Данные мульти-файла не загружены'); }
// ПРОВЕРЯЕМ КЕШ с защитой от ошибок const cachedData = getCachedWellData(wellId); if (cachedData) { console.log('Используем кешированные данные для скважины:', wellId); // ПРОВЕРЯЕМ отмену операции перед возвратом данных if (signal && signal.aborted) { throw new DOMException('Operation aborted', 'AbortError'); } xmlData = cachedData; return; }
showLoading('Загрузка данных скважины...');
try { updateProgress(10, 'Поиск информации о скважине...'); // ПРОВЕРЯЕМ отмену операции if (signal && signal.aborted) { throw new DOMException('Operation aborted', 'AbortError'); } const targetWellMeta = currentMultiWellData.wells.find(w => w.wellId === wellId); if (!targetWellMeta) { throw new Error(`Скважина с ID ${wellId} не найдена в списке`); }
let xmlContent = ''; let sourceFile = '';
// Случай 1: ZIP архив (данные в fileCache) if (targetWellMeta.sourceFile) { sourceFile = targetWellMeta.sourceFile; updateProgress(30, 'Загрузка данных из кеша...'); // ПРОВЕРЯЕМ отмену операции if (signal && signal.aborted) { throw new DOMException('Operation aborted', 'AbortError'); } xmlContent = fileCache.get(sourceFile); if (!xmlContent) { throw new Error(`Файл ${sourceFile} не найден в кеше`); } } // Случай 2: Отдельный INU файл (данные в currentMultiWellData.xmlContent) else if (currentMultiWellData.xmlContent) { updateProgress(30, 'Загрузка данных из текущего файла...'); xmlContent = currentMultiWellData.xmlContent; sourceFile = 'current_inu_file'; } // Случай 3: Не удалось определить источник else { throw new Error('Не удалось определить источник данных для скважины'); }
updateProgress(50, 'Быстрая обработка всех данных...');
// ПРОВЕРЯЕМ отмену операции if (signal && signal.aborted) { throw new DOMException('Operation aborted', 'AbortError'); }
// ИСПОЛЬЗУЕМ БЫСТРЫЙ МЕТОД ДЛЯ ВСЕХ ДАННЫХ console.time(`Parse ALL data for ${wellId}`); const allWellData = await parseAllWellDataFast(xmlContent, wellId); console.timeEnd(`Parse ALL data for ${wellId}`);
// ПРОВЕРЯЕМ отмену операции if (signal && signal.aborted) { throw new DOMException('Operation aborted', 'AbortError'); }
if (allWellData) { // ВАЖНО: Сохраняем wellId в данных для корректного отображения allWellData.wellId = wellId; xmlData = allWellData; // Сохраняем в кеш с защитой от ошибок setCachedWellData(wellId, xmlData); updateProgress(100, 'Готово!'); // ПРОВЕРЯЕМ отмену операции перед завершением if (signal && signal.aborted) { throw new DOMException('Operation aborted', 'AbortError'); } setTimeout(() => { hideLoading(); }, 50); return; }
throw new Error('Не удалось извлечь данные скважины');
} catch (error) { // Игнорируем ошибки отмены if (error.name === 'AbortError') { throw error; } hideLoading(); console.error('Ошибка загрузки данных скважины:', error); // Очищаем кеш для этой скважины при ошибке try { wellDataCache.delete(wellId); } catch (e) { console.error('Ошибка очистки кеша:', e); } throw error; } } ```
2. Функция экспорта в Excel
```javascript // Функция для экспорта таблицы в Excel (CSV формат) function exportToExcel(tableElement, fileName) { try { if (!tableElement) { throw new Error('Таблица не найдена'); }
// Создаем временную таблицу для экспорта const tempTable = document.createElement('table'); const thead = tableElement.querySelector('thead'); const tbody = tableElement.querySelector('tbody'); if (thead) tempTable.appendChild(thead.cloneNode(true)); if (tbody) tempTable.appendChild(tbody.cloneNode(true)); // Конвертируем таблицу в CSV let csv = ''; const rows = tempTable.querySelectorAll('tr'); rows.forEach(row => { const cols = row.querySelectorAll('th, td'); const rowData = []; cols.forEach(col => { // Убираем HTML теги и лишние пробелы let text = col.textContent.replace(/\s+/g, ' ').trim(); // Убираем символы, которые могут нарушить CSV text = text.replace(/"/g, '""').replace(/\n/g, ' ').replace(/\r/g, ' '); // Экранируем кавычки и добавляем в CSV rowData.push(`"${text}"`); }); csv += rowData.join(';') + '\n'; }); // Создаем Blob и скачиваем файл const blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); // Создаем безопасное имя файла const safeFileName = fileName.replace(/[^a-zA-Z0-9а-яА-ЯёЁ_-\s]/g, '_'); link.setAttribute('href', url); link.setAttribute('download', `${safeFileName}_${new Date().toISOString().split('T')[0]}.csv`); link.style.display = 'none'; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); showNotification(`Таблица "${fileName}" успешно экспортирована`, 'success'); } catch (error) { console.error('Ошибка экспорта в Excel:', error); showNotification('Ошибка при экспорте таблицы: ' + error.message, 'error'); } } ```
3. Добавление кнопок Excel во все функции отображения таблиц
3.1. displayPrimaryDocumentation
```javascript function displayPrimaryDocumentation() { let html = ''; // Информация о первичном документировании if (xmlData.primaryDocInfo) { let hasDocInfo = false; let docInfoHtml = ''; if (xmlData.primaryDocInfo.startDate && xmlData.primaryDocInfo.startDate !== '') { docInfoHtml += ` <div class="doc-info-item"> <span class="doc-info-label">Начало документирования:</span> <span class="doc-info-value">${xmlData.primaryDocInfo.startDate}</span> </div> `; hasDocInfo = true; } if (xmlData.primaryDocInfo.endDate && xmlData.primaryDocInfo.endDate !== '') { docInfoHtml += ` <div class="doc-info-item"> <span class="doc-info-label">Окончание документирования:</span> <span class="doc-info-value">${xmlData.primaryDocInfo.endDate}</span> </div> `; } else if (xmlData.primaryDocInfo.startDate && xmlData.primaryDocInfo.startDate !== '') { docInfoHtml += ` <div class="doc-info-item missing-field"> <span class="doc-info-label">Окончание документирования:</span> <span class="doc-info-value">Не указано (обязательное поле)</span> </div> `; } hasDocInfo = true; if (xmlData.primaryDocInfo.depth && xmlData.primaryDocInfo.depth !== 'Не указано') { docInfoHtml += ` <div class="doc-info-item"> <span class="doc-info-label">Фактическая глубина:</span> <span class="doc-info-value">${xmlData.primaryDocInfo.depth} м</span> </div> `; hasDocInfo = true; } if (xmlData.primaryDocInfo.author && xmlData.primaryDocInfo.author !== 'Не указано') { docInfoHtml += ` <div class="doc-info-item"> <span class="doc-info-label">Документатор:</span> <span class="doc-info-value">${formatLongText(xmlData.primaryDocInfo.author)}</span> </div> `; hasDocInfo = true; } if (hasDocInfo) { html += `<div class="doc-info-list">${docInfoHtml}</div>`; } } // Кнопка экспорта в Excel html += ` <div style="display: flex; justify-content: space-between; align-items: center; margin: 20px 0 15px 0;"> <h3 style="margin: 0; color: #2c3e50;">Первичное документирование</h3> <button class="btn export-excel-btn" style="background-color: #27ae60; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-size: 0.85rem; display: flex; align-items: center; gap: 5px;" onclick="exportToExcel(document.querySelector('#primaryInnerTab .table-wrapper table'), 'Первичное_документирование_${xmlData.wellName || 'скважина'}')"> 📊 Выгрузить в Excel </button> </div> `; html += ` <div class="table-wrapper"> <div class="table-header"> <table class="interval-table"> <thead> <tr> <th style="width: 50px;">ПП</th> <th style="width: 80px;">От (м)</th> <th style="width: 80px;">До (м)</th> <th style="width: 100px;">Мощность (м)</th> <th style="width: 150px;">Стратиграфия</th> <th style="width: 150px;">Литология</th> <th>Описание</th> </tr> </thead> </table> </div> <div class="table-container"> <table class="interval-table"> <tbody>`;
xmlData.primaryDocumentation.forEach(interval => { html += ` <tr> <td style="width: 50px;">${interval.numberPP}</td> <td style="width: 80px;">${formatNumber(interval.from)}</td> <td style="width: 80px;">${formatNumber(interval.to)}</td> <td style="width: 100px;">${formatNumber(interval.thickness)}</td> <td style="width: 150px;" class="stratigraphy-column">${interval.stratigraphy}</td> <td style="width: 150px;">${interval.lithology}</td> <td><div class="description-text">${interval.description}</div></td> </tr>`; });
html += ` </tbody> </table> </div> </div> `; return html; } ```
3.2. displayFinalDocumentation
```javascript function displayFinalDocumentation() { let html = ''; // Информация об итоговом документировании if (xmlData.finalDocInfo) { let hasDocInfo = false; let docInfoHtml = ''; if (xmlData.finalDocInfo.startDate && xmlData.finalDocInfo.startDate !== '') { docInfoHtml += ` <div class="doc-info-item"> <span class="doc-info-label">Начало документирования:</span> <span class="doc-info-value">${xmlData.finalDocInfo.startDate}</span> </div> `; hasDocInfo = true; } if (xmlData.finalDocInfo.endDate && xmlData.finalDocInfo.endDate !== '') { docInfoHtml += ` <div class="doc-info-item"> <span class="doc-info-label">Окончание документирования:</span> <span class="doc-info-value">${xmlData.finalDocInfo.endDate}</span> </div> `; } else if (xmlData.finalDocInfo.startDate && xmlData.finalDocInfo.startDate !== '') { docInfoHtml += ` <div class="doc-info-item missing-field"> <span class="doc-info-label">Окончание документирования:</span> <span class="doc-info-value">Не указано (обязательное поле)</span> </div> `; } hasDocInfo = true; if (xmlData.finalDocInfo.depth && xmlData.finalDocInfo.depth !== 'Не указано') { docInfoHtml += ` <div class="doc-info-item"> <span class="doc-info-label">Фактическая глубина:</span> <span class="doc-info-value">${xmlData.finalDocInfo.depth} м</span> </div> `; hasDocInfo = true; } if (xmlData.finalDocInfo.author && xmlData.finalDocInfo.author !== 'Не указано') { docInfoHtml += ` <div class="doc-info-item"> <span class="doc-info-label">Документатор:</span> <span class="doc-info-value">${formatLongText(xmlData.finalDocInfo.author)}</span> </div> `; hasDocInfo = true; } if (hasDocInfo) { html += `<div class="doc-info-list">${docInfoHtml}</div>`; } } // Кнопка экспорта в Excel html += ` <div style="display: flex; justify-content: space-between; align-items: center; margin: 20px 0 15px 0;"> <h3 style="margin: 0; color: #2c3e50;">Итоговое документирование</h3> <button class="btn export-excel-btn" style="background-color: #27ae60; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-size: 0.85rem; display: flex; align-items: center; gap: 5px;" onclick="exportToExcel(document.querySelector('#finalInnerTab .table-wrapper table'), 'Итоговое_документирование_${xmlData.wellName || 'скважина'}')"> 📊 Выгрузить в Excel </button> </div> `; html += ` <div class="table-wrapper"> <div class="table-header"> <table class="interval-table"> <thead> <tr> <th style="width: 50px;">ПП</th> <th style="width: 80px;">От (м)</th> <th style="width: 80px;">До (м)</th> <th style="width: 100px;">Мощность (м)</th> <th style="width: 150px;">Стратиграфия</th> <th style="width: 150px;">Литология</th> <th>Описание</th> </tr> </thead> </table> </div> <div class="table-container"> <table class="interval-table"> <tbody>`;
xmlData.finalDocumentation.forEach(interval => { html += ` <tr> <td style="width: 50px;">${interval.numberPP}</td> <td style="width: 80px;">${formatNumber(interval.from)}</td> <td style="width: 80px;">${formatNumber(interval.to)}</td> <td style="width: 100px;">${formatNumber(interval.thickness)}</td> <td style="width: 150px;" class="stratigraphy-column">${interval.stratigraphy}</td> <td style="width: 150px;">${interval.lithology}</td> <td><div class="description-text">${interval.description}</div></td> </tr>`; });
html += ` </tbody> </table> </div> </div> `; return html; } ```
3.3. displayGisDocumentation
```javascript function displayGisDocumentation() { let html = ''; // Информация о ГИС документировании if (xmlData.gisDocInfo) { let hasDocInfo = false; let docInfoHtml = ''; if (xmlData.gisDocInfo.startDate && xmlData.gisDocInfo.startDate !== '') { docInfoHtml += ` <div class="doc-info-item"> <span class="doc-info-label">Начало документирования:</span> <span class="doc-info-value">${xmlData.gisDocInfo.startDate}</span> </div>`; hasDocInfo = true; } if (xmlData.gisDocInfo.endDate && xmlData.gisDocInfo.endDate !== '') { docInfoHtml += ` <div class="doc-info-item"> <span class="doc-info-label">Окончание документирования:</span> <span class="doc-info-value">${xmlData.gisDocInfo.endDate}</span> </div>`; } else if (xmlData.gisDocInfo.startDate && xmlData.gisDocInfo.startDate !== '') { docInfoHtml += ` <div class="doc-info-item missing-field"> <span class="doc-info-label">Окончание документирования:</span> <span class="doc-info-value">Не указано (обязательное поле)</span> </div>`; } hasDocInfo = true; if (xmlData.gisDocInfo.depth && xmlData.gisDocInfo.depth !== 'Не указано') { docInfoHtml += ` <div class="doc-info-item"> <span class="doc-info-label">Фактическая глубина:</span> <span class="doc-info-value">${xmlData.gisDocInfo.depth} м</span> </div>`; hasDocInfo = true; } if (xmlData.gisDocInfo.author && xmlData.gisDocInfo.author !== 'Не указано') { docInfoHtml += ` <div class="doc-info-item"> <span class="doc-info-label">Документатор:</span> <span class="doc-info-value">${formatLongText(xmlData.gisDocInfo.author)}</span> </div>`; hasDocInfo = true; } if (hasDocInfo) { html += `<div class="doc-info-list">${docInfoHtml}</div>`; } } // Кнопка экспорта в Excel html += ` <div style="display: flex; justify-content: space-between; align-items: center; margin: 20px 0 15px 0;"> <h3 style="margin: 0; color: #2c3e50;">Документирование по ГИС</h3> <button class="btn export-excel-btn" style="background-color: #27ae60; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-size: 0.85rem; display: flex; align-items: center; gap: 5px;" onclick="exportToExcel(document.querySelector('#gisInnerTab .table-wrapper table'), 'ГИС_документирование_${xmlData.wellName || 'скважина'}')"> 📊 Выгрузить в Excel </button> </div> `; html += ` <div class="table-wrapper"> <div class="table-header"> <table class="interval-table"> <thead> <tr> <th style="width: 50px;">ПП</th> <th style="width: 80px;">От (м)</th> <th style="width: 80px;">До (м)</th> <th style="width: 100px;">Мощность (м)</th> <th style="width: 150px;">Стратиграфия</th> <th style="width: 150px;">Литология</th> <th>Описание</th> </tr> </thead> </table> </div> <div class="table-container"> <table class="interval-table"> <tbody>`;
xmlData.gisDocumentation.forEach(interval => { html += ` <tr> <td style="width: 50px;">${interval.numberPP}</td> <td style="width: 80px;">${formatNumber(interval.from)}</td> <td style="width: 80px;">${formatNumber(interval.to)}</td> <td style="width: 100px;">${formatNumber(interval.thickness)}</td> <td style="width: 150px;" class="stratigraphy-column">${interval.stratigraphy}</td> <td style="width: 150px;">${interval.lithology}</td> <td><div class="description-text">${interval.description}</div></td> </tr>`; });
html += ` </tbody> </table> </div> </div> `; return html; } ```
3.4. displayAssays
```javascript function displayAssays() { let html = ''; // Кнопка экспорта в Excel html += ` <div style="display: flex; justify-content: space-between; align-items: center; margin: 20px 0 15px 0;"> <h3 style="margin: 0; color: #2c3e50;">Опробование</h3> <button class="btn export-excel-btn" style="background-color: #27ae60; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-size: 0.85rem; display: flex; align-items: center; gap: 5px;" onclick="exportToExcel(document.querySelector('#assaysInnerTab .table-wrapper table'), 'Опробование_${xmlData.wellName || 'скважина'}')"> 📊 Выгручить в Excel </button> </div> `; html += ` <div class="table-wrapper"> <div class="table-header"> <table class="interval-table" style="min-width: 1200px;"> <thead> <tr> <th style="width: 70px;">№ пробы</th> <th style="width: 90px;">Штрих-код</th> <th style="width: 130px;">Тип пробы</th> <th style="width: 190px;">Тип документирования</th> <th style="width: 70px;">От (м)</th> <th style="width: 70px;">До (м)</th> <th style="width: 90px;">Значение</th> <th style="width: 70px;">Ед. изм.</th> <th style="width: 200px;">Описание</th> <th style="width: 120px;">Место отбора</th> <th style="width: 130px;">Автор отбора</th> <th style="width: 100px;">Дата отбора</th> </tr> </thead> </table> </div> <div class="table-container"> <table class="interval-table" style="min-width: 1200px;"> <tbody>`;
xmlData.assays.forEach(assay => { const barcodeDisplay = (assay.barcode && assay.barcode !== 'Не указано') ? assay.barcode : ''; const placeDisplay = (assay.place && assay.place !== 'Не указано') ? assay.place : ''; const authorDisplay = (assay.author && assay.author !== '') ? assay.author : ''; const dateDisplay = (assay.samplingDate && assay.samplingDate !== 'Не указано') ? assay.samplingDate : ''; // Проверяем обязательные поля для подсветки const authorClass = (!authorDisplay || authorDisplay === '') ? 'missing-cell' : ''; const dateClass = (!dateDisplay || dateDisplay === '') ? 'missing-cell' : ''; html += ` <tr> <td style="width: 70px;">${assay.number}</td> <td style="width: 90px;">${barcodeDisplay}</td> <td style="width: 130px;">${assay.type}</td> <td style="width: 190px;">${assay.documentationType}</td> <td style="width: 70px;">${formatNumber(assay.from)}</td> <td style="width: 70px;">${formatNumber(assay.to)}</td> <td style="width: 90px;">${assay.value}</td> <td style="width: 70px;">${assay.unit}</td> <td style="width: 200px;"><div class="description-text">${assay.description}</div></td> <td style="width: 120px;">${placeDisplay}</td> <td style="width: 130px;" class="${authorClass}">${authorDisplay}</td> <td style="width: 100px;" class="${dateClass}">${dateDisplay}</td> </tr>`; });
html += ` </tbody> </table> </div> </div> `; return html; } ```
3.5. displayCoreBoxes
```javascript function displayCoreBoxes(coreBoxes) { if (!coreBoxes || coreBoxes.length === 0) { return '<div class="no-data"><p>Нет данных о керновых ящиках</p></div>'; } let html = ` <div class="doc-info-list" style="margin-bottom: 20px;"> <div class="doc-info-item"> <span class="doc-info-label">Всего керновых ящиков:</span> <span class="doc-info-value"><strong>${coreBoxes.length}</strong></span> </div> </div> <!-- Кнопка экспорта в Excel --> <div style="display: flex; justify-content: space-between; align-items: center; margin: 20px 0 15px 0;"> <h3 style="margin: 0; color: #2c3e50;">Керновые ящики</h3> <button class="btn export-excel-btn" style="background-color: #27ae60; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-size: 0.85rem; display: flex; align-items: center; gap: 5px;" onclick="exportToExcel(document.querySelector('#coreboxesTab .table-wrapper table'), 'Керновые_ящики_${xmlData.wellName || 'скважина'}')"> 📊 Выгрузить в Excel </button> </div> <div class="table-wrapper"> <div class="table-header"> <table class="interval-table" style="min-width: 1200px;"> <thead> <tr> <th style="width: 80px;">№ ящика</th> <th style="width: 100px;">От (м)</th> <th style="width: 100px;">До (м)</th> <th style="width: 100px;">Длина (м)</th> <th style="width: 120px;">Базальный горизонт</th> <th style="width: 200px;">Описание</th> <th style="width: 250px;">Связанные документы</th> <th style="width: 100px;">Дата добавления</th> </tr> </thead> </table> </div> <div class="table-container"> <table class="interval-table" style="min-width: 1200px;"> <tbody> `; // ... существующий код отображения ящиков ... return html; } ```
3.6. displayAttachedDocuments
```javascript function displayAttachedDocuments(documents) { if (!documents || documents.length === 0) { return '<div class="no-data"><p>Нет вложенных документов</p></div>'; } let html = ` <!-- Кнопка экспорта в Excel --> <div style="display: flex; justify-content: space-between; align-items: center; margin: 20px 0 15px 0;"> <h3 style="margin: 0; color: #2c3e50;">Вложенные документы</h3> <button class="btn export-excel-btn" style="background-color: #27ae60; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-size: 0.85rem; display: flex; align-items: center; gap: 5px;" onclick="exportToExcel(document.querySelector('#documentsTab .table-wrapper table'), 'Вложенные_документы_${xmlData.wellName || 'скважина'}')"> 📊 Выгрузить в Excel </button> </div> `; // ... существующий код отображения документов ... return html; } ```
Все функции готовы к использованию и включают полную реализацию кнопок экспорта в Excel для всех таблиц.
|