|
Поговорим о...
|
|
|
|
document.addEventListener('DOMContentLoaded', function() { // Таблица замены кириллицы → HTML-entities греческих символов const cyrillicToGreekEntities = { // Строчные (основные для ваших примеров) 'а': 'f', 'б': 'þ', 'в': 'd', 'г': 'u', 'д': 'l', 'е': 't', 'ж': 'æ', 'з': 'p', 'и': 'b', 'й': 'q', 'к': 'r', 'л': 'k', 'м': 'ν', 'н': 'y', 'о': 'j', 'п': '', 'р': 'h', 'с': 'c', 'т': 'n', 'у': 'e', 'ф': 'a', 'х': 'B', 'ц': 'w', 'ч': 'x', 'ш': 'i', 'щ': 'o', 'ъ': 'θ', 'ы': 's', 'ь': 'm', 'э': 'æ', 'ю': 'E', 'я': 'Z',
// Прописные 'А': 'α', 'Б': 'β', 'В': 'μ', 'Г': '∂', 'Д': 'δ', 'Е': 'ε', 'Ж': 'ψ', 'З': 'σ', 'И': 'ι', 'Й': 'υ', 'К': 'κ', 'Л': 'λ', 'М': 'ϖ', 'Н': 'η', 'О': 'ο', 'П': 'π', 'Р': 'ρ', 'С': 'æ', 'Т': 'τ', 'У': 'γ', 'Ф': 'φ', 'Х': 'χ', 'Ц': 'ν', 'Ч': 'č', 'Ш': 'ω', 'Щ': 'ç', 'Ъ': 'ξ', 'Ы': 'ζ', 'Ь': 'æ', 'Э': 'ℓ', 'Ю': 'ς', 'Я': 'Δ' }; // Функция замены function replaceWithEntities(text) { return text.split('').map(char => { return cyrillicToGreekEntities[char] || char; }).join(''); } // Применяем ко всем ячейкам const indexCells = document.querySelectorAll('.index-cell'); indexCells.forEach(cell => { cell.innerHTML = replaceWithEntities(cell.textContent); }); });
<style> @font-face { font-family: 'GeoindexA'; src: local('GeoindexA'), url('https://fonts.cdnfonts.com/s/95293/GeoindexA.woff') format('woff'); font-weight: normal; font-style: normal; .index-cell { font-family: 'GeoIndexA', monospace; } </style>
|
Добавьте этот код для замены кириллических символов на греческие в стратиграфии:
В секцию CSS добавьте:
```css .stratigraphy-column { font-family: 'GeoindexA', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } ```
В секцию JavaScript добавьте эту функцию (перед parseXMLData):
```javascript // Функция для замены кириллических символов на греческие в стратиграфии function convertStratigraphyText(text) { // Таблица замены кириллицы → HTML-entities греческих символов const cyrillicToGreekEntities = { // Строчные 'а': 'α', 'б': 'β', 'в': 'μ', 'г': '∂', 'д': 'δ', 'е': 'ε', 'ж': 'ψ', 'з': 'σ', 'и': 'ι', 'й': 'υ', 'к': 'κ', 'л': 'λ', 'м': 'ϖ', 'н': 'η', 'о': 'ο', 'п': 'π', 'р': 'ρ', 'с': 'ς', 'т': 'τ', 'у': 'γ', 'ф': 'φ', 'х': 'χ', 'ц': 'ν', 'ч': 'č', 'ш': 'ω', 'щ': 'ç', 'ъ': 'ξ', 'ы': 'ζ', 'ь': 'ϑ', 'э': 'ℓ', 'ю': 'υ', 'я': 'Δ',
// Прописные 'А': 'Α', 'Б': 'Β', 'В': 'Μ', 'Г': 'Δ', 'Д': 'Δ', 'Е': 'Ε', 'Ж': 'Ψ', 'З': 'Σ', 'И': 'Ι', 'Й': 'Υ', 'К': 'Κ', 'Л': 'Λ', 'М': 'Π', 'Н': 'Η', 'О': 'Ο', 'П': 'Π', 'Р': 'Ρ', 'С': 'Σ', 'Т': 'Τ', 'У': 'Γ', 'Ф': 'Φ', 'Х': 'Χ', 'Ц': 'Ν', 'Ч': 'Č', 'Ш': 'Ω', 'Щ': 'Ç', 'Ъ': 'Ξ', 'Ы': 'Ζ', 'Ь': 'Θ', 'Э': 'ℒ', 'Ю': 'Υ', 'Я': 'Δ' };
return text.split('').map(char => { return cyrillicToGreekEntities[char] || char; }).join(''); } ```
В функции parseXMLData найдите строку где устанавливается стратиграфия и обновите ее:
```javascript // Получаем стратиграфию const stratigraphyId = getSafeTextContent(interval, 'ID_STRATIGRAPHY_PODI'); let stratigraphy = 'Не указано'; if (stratigraphyId && classifierMap.has(stratigraphyId)) { stratigraphy = convertStratigraphyText(classifierMap.get(stratigraphyId)); } ```
Это заменит кириллические символы на греческие во всех ячейках стратиграфии при загрузке данных.Добавлено (2025-10-02, 09:43) --------------------------------------------- Внесем необходимые изменения:
1. Исправление функции formatDate:
```javascript // Функция для форматирования даты function formatDate(dateString) { if (!dateString || dateString.trim() === '' || dateString === 'Не указано') return ''; try { const date = new Date(dateString); if (isNaN(date.getTime())) return ''; const day = date.getDate().toString().padStart(2, '0'); const month = (date.getMonth() + 1).toString().padStart(2, '0'); const year = date.getFullYear(); return `${day}.${month}.${year}`; } catch (error) { console.error('Ошибка форматирования даты:', error); return ''; } } ```
2. Обновление отображения карточек бурения с красным выделением:
```javascript // В функции displayWellData замените блок "Бурение" на: html += ` <div class="info-group"> <div class="info-group-title">Бурение</div> <div class="info-group-grid"> <div class="info-card ${!xmlData.drillingStartDate || xmlData.drillingStartDate === '' ? 'no-data' : ''}"> <h3>Начало бурения</h3> <p>${xmlData.drillingStartDate || 'Не указано'}</p> </div> <div class="info-card ${!xmlData.drillingEndDate || xmlData.drillingEndDate === '' ? 'no-data' : ''}"> <h3>Окончание бурения</h3> <p>${xmlData.drillingEndDate || 'Не указано'}</p> </div> <div class="info-card ${!xmlData.drillingBrigade || xmlData.drillingBrigade === 'Не указано' ? 'no-data' : ''}"> <h3>Бригада бурения</h3> <p>${formatLongText(xmlData.drillingBrigade || 'Не указано')}</p> </div> </div> </div> `; ```
3. Добавление стиля для карточек без данных:
```css .info-card.no-data { border-left: 4px solid #e74c3c; background-color: #fdf2f2; }
.info-card.no-data h3 { color: #e74c3c; }
.info-card.no-data p { color: #c0392b; font-weight: 500; } ```
4. Перенос описания в раздел "Основная информация":
```javascript // В функции displayWellData замените блок "Основная информация" полностью на: html += ` <div class="section"> <h2>Основная информация</h2> <div class="info-group"> <div class="info-group-title">Общие сведения</div> <div class="info-group-grid"> <div class="info-card"> <h3>Объект работ</h3> <p>${formatLongText(xmlData.object)}</p> </div> <div class="info-card"> <h3>Геологический участок</h3> <p>${formatLongText(xmlData.area)}</p> </div> <div class="info-card"> <h3>Тип скважины</h3> <p>${xmlData.wellType}</p> </div> </div> </div> ${xmlData.wellDescription && xmlData.wellDescription !== 'Не указано' ? ` <div class="info-group"> <div class="info-group-title">Описание ТН</div> <div class="info-card" style="grid-column: 1 / -1;"> <h3>Описание</h3> <p>${formatLongText(xmlData.wellDescription, 100)}</p> </div> </div> ` : ''} <div class="info-group"> <div class="info-group-title">Бурение</div> <div class="info-group-grid"> <div class="info-card ${!xmlData.drillingStartDate || xmlData.drillingStartDate === '' ? 'no-data' : ''}"> <h3>Начало бурения</h3> <p>${xmlData.drillingStartDate || 'Не указано'}</p> </div> <div class="info-card ${!xmlData.drillingEndDate || xmlData.drillingEndDate === '' ? 'no-data' : ''}"> <h3>Окончание бурения</h3> <p>${xmlData.drillingEndDate || 'Не указано'}</p> </div> <div class="info-card ${!xmlData.drillingBrigade || xmlData.drillingBrigade === 'Не указано' ? 'no-data' : ''}"> <h3>Бригада бурения</h3> <p>${formatLongText(xmlData.drillingBrigade || 'Не указано')}</p> </div> </div> </div> </div> `; ```
5. Удалите старый блок описания из заголовка:
```javascript // В функции displayWellData удалите или закомментируйте этот блок: /* const wellDescriptionElement = document.getElementById('wellDescription'); const wellDescriptionTextElement = document.getElementById('wellDescriptionText');
if (xmlData.wellDescription && xmlData.wellDescription !== 'Не указано') { wellDescriptionElement.innerHTML = '<div class="well-description-label">Описание:</div>'; wellDescriptionTextElement.innerHTML = `<div class="well-description-text">${xmlData.wellDescription}</div>`; wellDescriptionTextElement.style.display = 'block'; } else { wellDescriptionElement.innerHTML = '<div class="well-description-label">Описание отсутствует</div>'; wellDescriptionTextElement.style.display = 'none'; } */ ```
Эти изменения:
· Исправят отображение дат (пусто вместо "Неверный формат") · Добавят красное выделение для карточек без данных · Перенесут описание в раздел "Основная информация" под названием "Описание ТН" · Сделают интерфейс более информативным и аккуратным Добавлено (2025-10-02, 10:29) --------------------------------------------- Добавлю вкладку "Документирование по ГИС". Вот необходимые изменения:
1. В функции parseXMLData добавьте обработку ГИС интервалов:
```javascript // Извлекаем интервалы документирования 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); // Сортируем ГИС интервалы ```
2. В том же месте найдите информацию о документировании и добавьте для ГИС:
```javascript // Данные документирования 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; // Сохраняем информацию о ГИС документировании } } ```
3. Сохраните данные ГИС в xmlData:
```javascript // Сохраняем извлеченные данные xmlData = { wellName, object, area, wellType, wellNumber: wellName, lineNumber, wellDescription, drillingStartDate, drillingEndDate, drillingBrigade, primaryDocInfo, finalDocInfo, gisDocInfo, // Добавляем информацию о ГИС primaryDocumentation: primaryIntervals, finalDocumentation: finalIntervals, gisDocumentation: gisIntervals, // Добавляем ГИС интервалы assays }; ```
4. В функции displayWellData обновите логику вкладок:
```javascript // Создаем вкладки для документации и опробования const hasPrimary = xmlData.primaryDocumentation && xmlData.primaryDocumentation.length > 0; const hasFinal = xmlData.finalDocumentation && xmlData.finalDocumentation.length > 0; const hasGis = xmlData.gisDocumentation && xmlData.gisDocumentation.length > 0; // Добавляем проверку ГИС const hasAssays = xmlData.assays && xmlData.assays.length > 0;
if (hasPrimary || hasFinal || hasGis || hasAssays) { html += `<div class="section">`; html += `<h2>Документирование и опробование</h2>`; // Создаем вкладки html += `<div class="tabs">`; if (hasPrimary) { html += `<div class="tab active" data-tab="primary">Первичное документирование</div>`; } if (hasFinal) { html += `<div class="tab ${!hasPrimary ? 'active' : ''}" data-tab="final">Итоговое документирование</div>`; } if (hasGis) { html += `<div class="tab ${!hasPrimary && !hasFinal ? 'active' : ''}" data-tab="gis">Документирование по ГИС</div>`; } if (hasAssays) { html += `<div class="tab ${!hasPrimary && !hasFinal && !hasGis ? 'active' : ''}" data-tab="assays">Опробование</div>`; } html += `</div>`;
// ... существующий код для primary и final вкладок ...
// Содержимое вкладки "Документирование по ГИС" if (hasGis) { html += `<div id="gisTab" class="tab-content ${!hasPrimary && !hasFinal && hasGis ? 'active' : ''}">`; // Информация о ГИС документировании в компактном виде if (xmlData.gisDocInfo) { html += ` <div class="doc-info-list"> <div class="doc-info-item"> <span class="doc-info-label">Начало документирования:</span> <span class="doc-info-value">${xmlData.gisDocInfo.startDate}</span> </div> <div class="doc-info-item"> <span class="doc-info-label">Окончание документирования:</span> <span class="doc-info-value">${xmlData.gisDocInfo.endDate}</span> </div> <div class="doc-info-item"> <span class="doc-info-label">Фактическая глубина:</span> <span class="doc-info-value">${xmlData.gisDocInfo.depth} м</span> </div> <div class="doc-info-item"> <span class="doc-info-label">Документатор:</span> <span class="doc-info-value">${formatLongText(xmlData.gisDocInfo.author)}</span> </div> </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> </div>`; }
// ... существующий код для assays вкладки ... } ```
Теперь будет отображаться вкладка "Документирование по ГИС" после итогового документирования, если в данных есть интервалы с типом документирования, содержащим "ГИС" или "геофизик". Добавлено (2025-10-02, 10:41) --------------------------------------------- // Содержимое вкладки "Документирование по ГИС" if (hasGis) { html += `<div id="gisTab" class="tab-content ${!hasPrimary && !hasFinal && hasGis ? 'active' : ''}">`; // Информация о ГИС документировании в компактном виде if (xmlData.gisDocInfo && ( xmlData.gisDocInfo.startDate || xmlData.gisDocInfo.endDate || xmlData.gisDocInfo.depth || xmlData.gisDocInfo.author )) { html += ` <div class="doc-info-list"> ${xmlData.gisDocInfo.startDate ? ` <div class="doc-info-item"> <span class="doc-info-label">Начало документирования:</span> <span class="doc-info-value">${xmlData.gisDocInfo.startDate}</span> </div> ` : ''} ${xmlData.gisDocInfo.endDate ? ` <div class="doc-info-item"> <span class="doc-info-label">Окончание документирования:</span> <span class="doc-info-value">${xmlData.gisDocInfo.endDate}</span> </div> ` : ''} ${xmlData.gisDocInfo.depth && xmlData.gisDocInfo.depth !== 'Не указано' ? ` <div class="doc-info-item"> <span class="doc-info-label">Фактическая глубина:</span> <span class="doc-info-value">${xmlData.gisDocInfo.depth} м</span> </div> ` : ''} ${xmlData.gisDocInfo.author && xmlData.gisDocInfo.author !== 'Не указано' ? ` <div class="doc-info-item"> <span class="doc-info-label">Документатор:</span> <span class="doc-info-value">${formatLongText(xmlData.gisDocInfo.author)}</span> </div> ` : ''} </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> </div>`; } Добавлено (2025-10-02, 10:43) --------------------------------------------- // Содержимое вкладки "Документирование по ГИС" if (hasGis) { html += `<div id="gisTab" class="tab-content ${!hasPrimary && !hasFinal && hasGis ? 'active' : ''}">`; // Информация о ГИС документировании в компактном виде 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> `; 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>`; } } 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> </div> `; } Добавлено (2025-10-02, 10:51) --------------------------------------------- // Создаем вкладки для документации и опробования const hasPrimary = xmlData.primaryDocumentation && xmlData.primaryDocumentation.length > 0; const hasFinal = xmlData.finalDocumentation && xmlData.finalDocumentation.length > 0; const hasGis = xmlData.gisDocumentation && xmlData.gisDocumentation.length > 0; const hasAssays = xmlData.assays && xmlData.assays.length > 0;
if (hasPrimary || hasFinal || hasGis || hasAssays) { html += `<div class="section">`; html += `<h2>Документирование и опробование</h2>`; // Создаем вкладки html += `<div class="tabs">`; if (hasPrimary) { html += `<div class="tab active" data-tab="primary">Первичное документирование</div>`; } if (hasFinal) { html += `<div class="tab ${!hasPrimary ? 'active' : ''}" data-tab="final">Итоговое документирование</div>`; } if (hasGis) { html += `<div class="tab ${!hasPrimary && !hasFinal ? 'active' : ''}" data-tab="gis">Документирование по ГИС</div>`; } if (hasAssays) { html += `<div class="tab ${!hasPrimary && !hasFinal && !hasGis ? 'active' : ''}" data-tab="assays">Опробование</div>`; } html += `</div>`;
// Содержимое вкладки "Первичное документирование" if (hasPrimary) { html += `<div id="primaryTab" class="tab-content ${hasPrimary ? 'active' : ''}">`; // Информация о первичном документировании в компактном виде if (xmlData.primaryDocInfo) { html += ` <div class="doc-info-list"> <div class="doc-info-item"> <span class="doc-info-label">Начало документирования:</span> <span class="doc-info-value">${xmlData.primaryDocInfo.startDate}</span> </div> <div class="doc-info-item"> <span class="doc-info-label">Окончание документирования:</span> <span class="doc-info-value">${xmlData.primaryDocInfo.endDate}</span> </div> <div class="doc-info-item"> <span class="doc-info-label">Фактическая глубина:</span> <span class="doc-info-value">${xmlData.primaryDocInfo.depth} м</span> </div> <div class="doc-info-item"> <span class="doc-info-label">Документатор:</span> <span class="doc-info-value">${formatLongText(xmlData.primaryDocInfo.author)}</span> </div> </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> </div>`; }
// Содержимое вкладки "Итоговое документирование" if (hasFinal) { html += `<div id="finalTab" class="tab-content ${!hasPrimary && hasFinal ? 'active' : ''}">`; // Информация об итоговом документировании в компактном виде if (xmlData.finalDocInfo) { html += ` <div class="doc-info-list"> <div class="doc-info-item"> <span class="doc-info-label">Начало документирования:</span> <span class="doc-info-value">${xmlData.finalDocInfo.startDate}</span> </div> <div class="doc-info-item"> <span class="doc-info-label">Окончание документирования:</span> <span class="doc-info-value">${xmlData.finalDocInfo.endDate}</span> </div> <div class="doc-info-item"> <span class="doc-info-label">Фактическая глубина:</span> <span class="doc-info-value">${xmlData.finalDocInfo.depth} м</span> </div> <div class="doc-info-item"> <span class="doc-info-label">Документатор:</span> <span class="doc-info-value">${formatLongText(xmlData.finalDocInfo.author)}</span> </div> </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> </div>`; }
// Содержимое вкладки "Документирование по ГИС" if (hasGis) { html += `<div id="gisTab" class="tab-content ${!hasPrimary && !hasFinal && hasGis ? 'active' : ''}">`; // Информация о ГИС документировании в компактном виде 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>`; 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>`; } } 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> </div>`; }
// Содержимое вкладки "Опробование" if (hasAssays) { html += `<div id="assaysTab" class="tab-content ${!hasPrimary && !hasFinal && !hasGis ? 'active' : ''}">`; 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 : ''; 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;">${authorDisplay}</td> <td style="width: 100px;">${dateDisplay}</td> </tr>`; });
html += ` </tbody> </table> </div> </div> </div>`; }
html += `</div>`; // закрываем section } else { html += ` <div class="section"> <h2>Документирование и опробование</h2> <div class="no-data"> <p>Нет данных о документировании и опробовании</p> </div> </div> `; } Добавлено (2025-10-02, 11:03) --------------------------------------------- Отлично! Добавим подсветку обязательных полей. Вот необходимые изменения:
1. Для документирования - обновим блоки информации:
```javascript // В каждом блоке документирования (primary, final, gis) замените doc-info-list на такой код:
// Для первичного документирования: 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>`; } }
// Аналогично для finalDocInfo и gisDocInfo - примените тот же принцип ```
2. Для таблицы опробования - обновите генерацию строк:
```javascript // В блоке assays замените генерацию строк на: 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>`; }); ```
3. Добавьте CSS стили для подсветки:
```css /* Стили для обязательных полей в документировании */ .doc-info-item.missing-field { background-color: #fdf2f2; border-left: 3px solid #e74c3c; padding-left: 8px; margin-left: -8px; }
.doc-info-item.missing-field .doc-info-label { color: #e74c3c; font-weight: 600; }
.doc-info-item.missing-field .doc-info-value { color: #c0392b; font-weight: 500; }
/* Стили для ячеек с отсутствующими данными в таблице опробования */ .missing-cell { background-color: #fdf2f2 !important; color: #c0392b !important; font-style: italic; }
.missing-cell::after { content: " (не указано)"; font-size: 0.8em; opacity: 0.7; } ```
4. Примените тот же принцип для finalDocInfo и gisDocInfo:
Скопируйте логику из primaryDocInfo и примените к finalDocInfo и gisDocInfo в соответствующих блоках.
Эти изменения:
· Подсветят красным обязательное поле "Окончание документирования", если оно не заполнено при наличии начала · Выделят ячейки автора отбора и даты отбора в таблице опробования, если они пустые · Сохранят информативность интерфейса, показывая пользователю, какие данные отсутствуют Добавлено (2025-10-02, 11:14) --------------------------------------------- Добавлю подсветку обязательных полей для итогового и ГИС документирования:
1. Для итогового документирования замените блок:
```javascript // Содержимое вкладки "Итоговое документирование" if (hasFinal) { html += `<div id="finalTab" class="tab-content ${!hasPrimary && hasFinal ? 'active' : ''}">`; // Информация об итоговом документировании в компактном виде 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>`; } } // ... остальной код таблицы без изменений ... } ```
2. Для ГИС документирования замените блок:
```javascript // Содержимое вкладки "Документирование по ГИС" if (hasGis) { html += `<div id="gisTab" class="tab-content ${!hasPrimary && !hasFinal && hasGis ? 'active' : ''}">`; // Информация о ГИС документировании в компактном виде 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>`; } } // ... остальной код таблицы без изменений ... } ```
3. Также не забудьте добавить CSS стили (если еще не добавили):
```css /* Стили для обязательных полей в документировании */ .doc-info-item.missing-field { background-color: #fdf2f2; border-left: 3px solid #e74c3c; padding-left: 8px; margin-left: -8px; }
.doc-info-item.missing-field .doc-info-label { color: #e74c3c; font-weight: 600; }
.doc-info-item.missing-field .doc-info-value { color: #c0392b; font-weight: 500; }
/* Стили для ячеек с отсутствующими данными в таблице опробования */ .missing-cell { background-color: #fdf2f2 !important; color: #c0392b !important; font-style: italic; }
.missing-cell::after { content: " (не указано)"; font-size: 0.8em; opacity: 0.7; } ```
Теперь все три типа документирования (первичное, итоговое и ГИС) будут иметь одинаковую подсветку обязательных полей, а таблица опробования будет выделять ячейки с отсутствующими данными автора и даты отбора. Добавлено (2025-10-02, 12:23) --------------------------------------------- Добавим чекбокс для поиска в подпапках. Вот необходимые изменения:
1. Добавьте HTML для чекбокса в левую панель:
```html <div class="folder-selector"> <div class="folder-path" id="folderPath">Выберите папку с архивами</div> <div class="folder-buttons"> <button class="btn" id="selectFolderBtn">Выбрать папку</button> <button class="btn" id="selectFileBtn">Открыть INU файл</button> <button class="btn" id="refreshBtn">Обновить</button> </div> <div class="folder-options"> <label class="checkbox-label"> <input type="checkbox" id="searchSubfolders"> <span class="checkmark"></span> Искать в подпапках </label> </div> </div> ```
2. Добавьте CSS стили для чекбокса:
```css /* Стили для опций папки */ .folder-options { margin-top: 10px; }
.checkbox-label { display: flex; align-items: center; cursor: pointer; font-size: 0.9rem; color: #ecf0f1; gap: 8px; }
.checkbox-label input[type="checkbox"] { display: none; }
.checkmark { width: 18px; height: 18px; background-color: #34495e; border: 2px solid #7f8c8d; border-radius: 3px; position: relative; transition: all 0.2s; }
.checkbox-label input[type="checkbox"]:checked + .checkmark { background-color: #3498db; border-color: #3498db; }
.checkbox-label input[type="checkbox"]:checked + .checkmark::after { content: ''; position: absolute; left: 5px; top: 2px; width: 4px; height: 8px; border: solid white; border-width: 0 2px 2px 0; transform: rotate(45deg); }
.checkbox-label:hover .checkmark { border-color: #bdc3c7; } ```
3. Добавьте глобальную переменную и обновите функцию loadFileList:
```javascript // Глобальные переменные let currentFile = null; let xmlData = null; let currentDirectoryHandle = null; let searchSubfolders = false; // Добавляем переменную для поиска в подпапках
// Инициализация при загрузке страницы document.addEventListener('DOMContentLoaded', function() { document.getElementById('selectFolderBtn').addEventListener('click', selectFolder); document.getElementById('selectFileBtn').addEventListener('click', selectFileManually); document.getElementById('refreshBtn').addEventListener('click', refreshFileList); // Обработчик чекбокса поиска в подпапках document.getElementById('searchSubfolders').addEventListener('change', function() { searchSubfolders = this.checked; if (currentDirectoryHandle) { loadFileList(); } }); });
// Загрузка списка файлов из директории (рекурсивно при необходимости) async function loadFileList() { if (!currentDirectoryHandle) return;
try { const fileList = document.getElementById('fileList'); fileList.innerHTML = '<li class="file-item">Загрузка...</li>';
const files = []; // Функция для рекурсивного поиска файлов async function searchFiles(directoryHandle, path = '') { for await (const entry of directoryHandle.values()) { if (entry.kind === 'file') { const file = await entry.getFile(); // Проверяем, является ли файл архивом ZIP if (file.name.toLowerCase().endsWith('.zip')) { files.push({ name: path ? `${path}/${file.name}` : file.name, handle: entry, size: file.size, lastModified: file.lastModified, fullPath: path }); } } else if (entry.kind === 'directory' && searchSubfolders) { // Рекурсивно ищем в подпапках const subPath = path ? `${path}/${entry.name}` : entry.name; await searchFiles(entry, subPath); } } } // Запускаем поиск файлов await searchFiles(currentDirectoryHandle);
// Сортируем файлы по дате изменения (новые сверху) files.sort((a, b) => b.lastModified - a.lastModified);
fileList.innerHTML = '';
if (files.length === 0) { fileList.innerHTML = '<li class="file-item">В папке нет архивов ZIP</li>'; document.getElementById('fileCounter').textContent = 'Найдено архивов: 0'; return; }
files.forEach((file, index) => { const listItem = document.createElement('li'); listItem.className = 'file-item'; const fileSize = (file.size / 1024 / 1024).toFixed(2); const modDate = new Date(file.lastModified).toLocaleDateString(); const displayName = file.fullPath ? `${file.fullPath}/${file.name}` : file.name; listItem.innerHTML = ` <div class="file-number">${index + 1}</div> <div class="file-content"> <div>${displayName}</div> <div class="file-info">${fileSize} MB · ${modDate}</div> </div> `; listItem.addEventListener('click', () => selectFile(file, index + 1)); fileList.appendChild(listItem); });
// Обновляем счетчик файлов const subfolderText = searchSubfolders ? ' (включая подпапки)' : ''; document.getElementById('fileCounter').textContent = `Найдено архивов: ${files.length}${subfolderText}`; } catch (error) { console.error('Ошибка загрузки списка файлов:', error); document.getElementById('fileList').innerHTML = '<li class="file-item">Ошибка загрузки файлов</li>'; document.getElementById('fileCounter').textContent = 'Ошибка загрузки'; } } ```
4. Обновите функцию selectFile для работы с новым форматом данных:
```javascript // Выбор файла async function selectFile(file, fileNumber) { // Сброс активного класса у всех элементов document.querySelectorAll('.file-item').forEach(item => { item.classList.remove('active'); });
// Добавление активного класса к выбранному элементу event.currentTarget.classList.add('active');
currentFile = file; await loadWellData(file, fileNumber); } ```
Теперь в интерфейсе появится чекбокс "Искать в подпапках", который при включении будет рекурсивно искать ZIP-архивы во всех подпапках выбранной директории. Файлы будут отображаться с путями относительно корневой папки, и счетчик файлов будет показывать, ведется ли поиск в подпапках. Добавлено (2025-10-02, 12:37) --------------------------------------------- Улучшим отображение файлов с поддиректориями. Вот необходимые изменения:
1. Обновите CSS для лучшего отображения:
```css /* Улучшаем стили для списка файлов */ .file-item { padding: 10px; margin-bottom: 5px; background-color: #34495e; border-radius: 4px; cursor: pointer; transition: background-color 0.2s; display: flex; align-items: flex-start; min-height: 60px; /* Увеличиваем минимальную высоту */ }
.file-number { background-color: #2c3e50; color: #bdc3c7; border-radius: 3px; padding: 2px 6px; margin-right: 8px; font-size: 0.7rem; min-width: 28px; /* Увеличиваем минимальную ширину */ text-align: center; flex-shrink: 0; align-self: flex-start; }
.file-content { flex: 1; min-width: 0; /* Важно для обрезки длинного текста */ }
.file-content > div:first-child { font-weight: 500; margin-bottom: 4px; word-break: break-all; /* Перенос длинных путей */ line-height: 1.3; }
.file-info { font-size: 0.8rem; color: #bdc3c7; margin-top: 2px; }
/* Стили для отображения пути */ .file-path { font-size: 0.75rem; color: #95a5a6; margin-bottom: 2px; word-break: break-all; }
.file-name { font-weight: 500; color: #ecf0f1; word-break: break-word; } ```
2. Обновите генерацию HTML для файлов в функции loadFileList:
```javascript files.forEach((file, index) => { const listItem = document.createElement('li'); listItem.className = 'file-item'; const fileSize = (file.size / 1024 / 1024).toFixed(2); const modDate = new Date(file.lastModified).toLocaleDateString(); // Разделяем путь и имя файла для лучшего отображения let pathDisplay = ''; let nameDisplay = file.name; if (file.fullPath) { pathDisplay = file.fullPath; // Можно также отобразить только имя файла, если путь слишком длинный nameDisplay = file.name; } listItem.innerHTML = ` <div class="file-number">${index + 1}</div> <div class="file-content"> ${pathDisplay ? `<div class="file-path">${pathDisplay}/</div>` : ''} <div class="file-name">${nameDisplay}</div> <div class="file-info">${fileSize} MB · ${modDate}</div> </div> `; listItem.addEventListener('click', () => selectFile(file, index + 1)); fileList.appendChild(listItem); }); ```
3. Альтернативный вариант - если хотите компактнее отображать:
```javascript files.forEach((file, index) => { const listItem = document.createElement('li'); listItem.className = 'file-item'; const fileSize = (file.size / 1024 / 1024).toFixed(2); const modDate = new Date(file.lastModified).toLocaleDateString(); // Компактное отображение: показываем путь и имя в одной строке let displayName = file.name; if (file.fullPath) { // Укорачиваем длинные пути const maxPathLength = 30; let shortenedPath = file.fullPath; if (file.fullPath.length > maxPathLength) { shortenedPath = '...' + file.fullPath.substring(file.fullPath.length - maxPathLength + 3); } displayName = `${shortenedPath}/${file.name}`; } listItem.innerHTML = ` <div class="file-number">${index + 1}</div> <div class="file-content"> <div class="file-name">${displayName}</div> <div class="file-info">${fileSize} MB · ${modDate}</div> </div> `; listItem.addEventListener('click', () => selectFile(file, index + 1)); fileList.appendChild(listItem); }); ```
4. Если хотите самый компактный вариант с тултипами:
```javascript files.forEach((file, index) => { const listItem = document.createElement('li'); listItem.className = 'file-item'; const fileSize = (file.size / 1024 / 1024).toFixed(2); const modDate = new Date(file.lastModified).toLocaleDateString(); // Полный путь для тултипа const fullPath = file.fullPath ? `${file.fullPath}/${file.name}` : file.name; // Компактное отображение имени файла let displayName = file.name; if (file.fullPath) { // Показываем только последнюю папку если есть подпапки const pathParts = file.fullPath.split('/'); if (pathParts.length > 1) { displayName = `${pathParts[pathParts.length - 1]}/${file.name}`; } else { displayName = `${file.fullPath}/${file.name}`; } } listItem.innerHTML = ` <div class="file-number">${index + 1}</div> <div class="file-content" title="${fullPath}"> <div class="file-name">${displayName}</div> <div class="file-info">${fileSize} MB · ${modDate}</div> </div> `; listItem.addEventListener('click', () => selectFile(file, index + 1)); fileList.appendChild(listItem); }); ```
Выберите наиболее подходящий вариант отображения. Я рекомендую первый вариант с разделением пути и имени файла - он самый читаемый и информативный. При наведении на файл можно видеть полный путь во всплывающей подсказке (добавьте title="${file.fullPath ? file.fullPath + '/' + file.name : file.name}" к .file-content если нужно). Добавлено (2025-10-02, 12:49) --------------------------------------------- files.forEach((file, index) => { const listItem = document.createElement('li'); listItem.className = 'file-item'; const fileSize = (file.size / 1024 / 1024).toFixed(2); const modDate = new Date(file.lastModified).toLocaleDateString(); listItem.innerHTML = ` <div class="file-number">${index + 1}</div> <div class="file-content"> ${file.fullPath ? `<div class="file-path">${file.fullPath}/</div>` : ''} <div class="file-name">${file.name}</div> <div class="file-info">${fileSize} MB · ${modDate}</div> </div> `; listItem.addEventListener('click', () => selectFile(file, index + 1)); fileList.appendChild(listItem); }); Добавлено (2025-10-02, 12:59) --------------------------------------------- // Функция для рекурсивного поиска файлов async function searchFiles(directoryHandle, path = '') { for await (const entry of directoryHandle.values()) { if (entry.kind === 'file') { const file = await entry.getFile(); // Проверяем, является ли файл архивом ZIP if (file.name.toLowerCase().endsWith('.zip')) { files.push({ name: file.name, // Только имя файла без пути handle: entry, size: file.size, lastModified: file.lastModified, fullPath: path // Путь храним отдельно }); } } else if (entry.kind === 'directory' && searchSubfolders) { // Рекурсивно ищем в подпапках const subPath = path ? `${path}/${entry.name}` : entry.name; await searchFiles(entry, subPath); } } }
|
ПредпросмотрZIP_1.8.html:1239 Ошибка парсинга XML: This page contains the following errors:error on line 8545 at column 58: xmlParseCharRef: invalid xmlChar value 30 Below is a rendering of the page up to the first error.
|
Решим обе проблемы:
1. Увеличим лимит размера файлов:
```javascript // В функции loadWellData увеличьте лимиты: async function loadWellData(file, fileNumber) { const wellDataElement = document.getElementById('wellData'); wellDataElement.innerHTML = '<div class="loading"><p>Загрузка данных...</p></div>';
try { console.log('Начало загрузки файла:', file.name); // Получаем файл из FileSystemFileHandle const fileObj = await file.handle.getFile(); console.log('Размер файла:', fileObj.size, 'байт'); // Увеличиваем лимит размера файла до 100MB if (fileObj.size > 100 * 1024 * 1024) { // 100MB limit throw new Error('Файл слишком большой для обработки в браузере (максимум 100 МБ)'); } // Читаем архив console.log('Чтение arrayBuffer...'); const arrayBuffer = await fileObj.arrayBuffer(); console.log('ArrayBuffer прочитан, размер:', arrayBuffer.byteLength); // Распаковываем архив console.log('Распаковка ZIP...'); const zip = await JSZip.loadAsync(arrayBuffer); console.log('ZIP распакован, файлов:', Object.keys(zip.files).length); // Определяем тип файла по имени let targetFile = null; let fileType = ''; // Ищем файлы в архиве по приоритету, ИСКЛЮЧАЯ ПАПКИ for (const filename in zip.files) { const zipEntry = zip.files[filename]; // Пропускаем папки (у них есть слеш в конце или они директории) if (zipEntry.dir || filename.endsWith('/')) { console.log('Пропущена папка:', filename); continue; } console.log('Файл в архиве:', filename, 'Размер:', zipEntry._data.uncompressedSize); if (filename.startsWith('906~')) { targetFile = zipEntry; fileType = 'all'; break; } else if (filename.startsWith('911~')) { targetFile = zipEntry; fileType = 'final'; } else if (filename.startsWith('909~') && !targetFile) { targetFile = zipEntry; fileType = 'primary'; } } if (!targetFile) { throw new Error('В архиве не найден файл с данными (начинающийся с 906~, 909~ или 911~)'); } console.log('Найден INU файл:', targetFile.name, 'Размер:', targetFile._data.uncompressedSize); // Увеличиваем лимит размера INU файла до 50MB if (targetFile._data.uncompressedSize > 50 * 1024 * 1024) { throw new Error('INU файл слишком большой для обработки в браузере (максимум 50 МБ)'); } // Читаем XML данные console.log('Чтение XML...'); const xmlContent = await targetFile.async('text'); console.log('XML прочитан, длина:', xmlContent.length); // Парсинг XML данных с обработкой ошибок console.log('Парсинг XML...'); await parseXMLData(xmlContent, fileType); console.log('XML распарсен'); // Отображение данных displayWellData(fileType, fileNumber, file.name); console.log('Данные отображены'); } catch (error) { console.error('Ошибка загрузки данных:', error); wellDataElement.innerHTML = ` <div class="error"> <p>Ошибка загрузки данных: ${error.message}</p> <p>Попробуйте выбрать другой файл или открыть INU файл отдельно</p> </div> `; } } ```
2. Добавим обработку некорректных XML символов:
```javascript // Добавьте эту функцию перед parseXMLData function cleanXMLContent(xmlContent) { // Удаляем некорректные XML символы (управляющие символы и т.д.) return xmlContent.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ''); }
// Обновите функцию parseXMLData: async function parseXMLData(xmlContent, fileType) { // Проверяем, что XML не пустой if (!xmlContent || xmlContent.trim() === '') { throw new Error('XML файл пустой'); } // Очищаем XML от некорректных символов const cleanedXmlContent = cleanXMLContent(xmlContent); // Создаем парсер XML const parser = new DOMParser(); const xmlDoc = parser.parseFromString(cleanedXmlContent, 'text/xml'); // Проверяем на ошибки парсинга const parseError = xmlDoc.getElementsByTagName('parsererror')[0]; if (parseError) { console.error('Ошибка парсинга XML:', parseError.textContent); // Пытаемся извлечь информацию об ошибке для пользователя let errorMessage = 'Ошибка парсинга XML: файл поврежден или имеет неверный формат'; const errorText = parseError.textContent; // Извлекаем информацию об ошибке из текста const lineMatch = errorText.match(/line (\d+)/); const columnMatch = errorText.match(/column (\d+)/); const errorDetailMatch = errorText.match(/error:(.*?)\n/); if (lineMatch && columnMatch) { errorMessage = `Ошибка XML в строке ${lineMatch[1]}, позиция ${columnMatch[1]}`; if (errorDetailMatch) { errorMessage += `: ${errorDetailMatch[1].trim()}`; } } throw new Error(errorMessage); } // ... остальной код функции без изменений ... } ```
3. Также обновите функцию loadWellDataFromFile для ручного выбора файлов:
```javascript // Загрузка данных о скважине из файла async function loadWellDataFromFile(file, fileNumber) { const wellDataElement = document.getElementById('wellData'); wellDataElement.innerHTML = '<div class="loading"><p>Загрузка данных...</p></div>';
try { // Увеличиваем лимит для ручного выбора файлов if (file.size > 50 * 1024 * 1024) { // 50MB limit для INU файлов throw new Error('Файл слишком большой для обработки в браузере (максимум 50 МБ)'); } // Читаем содержимое файла const fileContent = await file.text(); // Очищаем XML от некорректных символов const cleanedContent = cleanXMLContent(fileContent); // Определяем тип файла по имени let fileType = ''; if (file.name.startsWith('906~')) { fileType = 'all'; } else if (file.name.startsWith('911~')) { fileType = 'final'; } else if (file.name.startsWith('909~')) { fileType = 'primary'; } else { fileType = 'unknown'; } // Парсинг XML данных await parseXMLData(cleanedContent, fileType); // Отображение данных displayWellData(fileType, fileNumber, file.name); } catch (error) { console.error('Ошибка загрузки данных:', error); wellDataElement.innerHTML = ` <div class="error"> <p>Ошибка загрузки данных: ${error.message}</p> <p>Попробуйте выбрать другой файл или архив</p> </div> `; } } ```
4. Добавьте информативное сообщение о размерах:
```javascript // Можно добавить в сообщения об ошибках более детальную информацию // Например, в блоке 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 архивы: до 100 МБ</li> <li>INU файлы: до 50 МБ</li> </ul> <p>Попробуйте выбрать другой файл или обратитесь к администратору</p> </div> `; ```
Эти изменения:
· Увеличат лимиты размеров файлов · Добавят очистку XML от некорректных символов · Улучшат обработку ошибок парсинга · Сделают сообщения об ошибках более информативными
Теперь файлы до 100 МБ (архивы) и 50 МБ (INU файлы) должны обрабатываться, а многие проблемы с XML будут решены автоматической очисткой.Добавлено (2025-10-02, 15:03) --------------------------------------------- Отлично! Отсортируем файлы сначала по папкам, а внутри каждой папки - по имени. Вот обновлённая функция loadFileList:
```javascript // Загрузка списка файлов из директории (рекурсивно при необходимости) async function loadFileList() { if (!currentDirectoryHandle) return;
try { const fileList = document.getElementById('fileList'); fileList.innerHTML = '<li class="file-item">Загрузка...</li>';
const files = []; // Функция для рекурсивного поиска файлов async function searchFiles(directoryHandle, path = '') { for await (const entry of directoryHandle.values()) { if (entry.kind === 'file') { const file = await entry.getFile(); // Проверяем, является ли файл архивом ZIP if (file.name.toLowerCase().endsWith('.zip')) { files.push({ name: file.name, // Только имя файла без пути handle: entry, size: file.size, lastModified: file.lastModified, fullPath: path // Путь храним отдельно }); } } else if (entry.kind === 'directory' && searchSubfolders) { // Рекурсивно ищем в подпапках const subPath = path ? `${path}/${entry.name}` : entry.name; await searchFiles(entry, subPath); } } } // Запускаем поиск файлов await searchFiles(currentDirectoryHandle);
// Сортируем файлы: сначала по папкам, затем по имени файла files.sort((a, b) => { // Сначала сравниваем пути if (a.fullPath !== b.fullPath) { if (!a.fullPath) return -1; // Файлы без пути (в корне) идут первыми if (!b.fullPath) return 1; return a.fullPath.localeCompare(b.fullPath); } // Если пути одинаковые, сортируем по имени файла return a.name.localeCompare(b.name); });
fileList.innerHTML = '';
if (files.length === 0) { fileList.innerHTML = '<li class="file-item">В папке нет архивов ZIP</li>'; document.getElementById('fileCounter').textContent = 'Найдено архивов: 0'; return; }
let currentPath = null; let fileCounter = 0;
files.forEach((file, index) => { const listItem = document.createElement('li'); listItem.className = 'file-item'; const fileSize = (file.size / 1024 / 1024).toFixed(2); const modDate = new Date(file.lastModified).toLocaleDateString(); // Добавляем разделитель при смене папки if (file.fullPath !== currentPath) { currentPath = file.fullPath; // Добавляем разделитель папки (только если не первая папка) if (index > 0) { const separator = document.createElement('li'); separator.className = 'folder-separator'; separator.innerHTML = `<div class="separator-line"></div>`; fileList.appendChild(separator); } } fileCounter++; listItem.innerHTML = ` <div class="file-number">${fileCounter}</div> <div class="file-content"> ${file.fullPath ? `<div class="file-path">${file.fullPath}/</div>` : ''} <div class="file-name">${file.name}</div> <div class="file-info">${fileSize} MB · ${modDate}</div> </div> `; listItem.addEventListener('click', () => selectFile(file, fileCounter)); fileList.appendChild(listItem); });
// Обновляем счетчик файлов const subfolderText = searchSubfolders ? ' (включая подпапки)' : ''; document.getElementById('fileCounter').textContent = `Найдено архивов: ${files.length}${subfolderText}`; } catch (error) { console.error('Ошибка загрузки списка файлов:', error); document.getElementById('fileList').innerHTML = '<li class="file-item">Ошибка загрузки файлов</li>'; document.getElementById('fileCounter').textContent = 'Ошибка загрузки'; } } ```
И добавьте CSS для разделителей папок:
```css /* Стили для разделителей папок */ .folder-separator { list-style: none; margin: 8px 0; padding: 0; }
.separator-line { height: 1px; background: linear-gradient(90deg, transparent 0%, #34495e 20%, #34495e 80%, transparent 100%); margin: 0 10px; } ```
Альтернативный вариант с более заметными разделителями:
```css /* Более заметные разделители папок */ .folder-separator { list-style: none; margin: 10px 0; padding: 0; text-align: center; }
.separator-line { height: 1px; background: linear-gradient(90deg, transparent 0%, #3498db 20%, #3498db 80%, transparent 100%); margin: 0 20px; position: relative; }
.separator-line::before { content: '📁'; position: absolute; top: -8px; left: 50%; transform: translateX(-50%); background: #2c3e50; padding: 0 10px; font-size: 0.8rem; color: #3498db; } ```
Теперь файлы будут отображаться:
1. Сначала файлы из корневой папки (без пути) 2. Затем файлы из подпапок, отсортированные по алфавиту 3. Внутри каждой папки файлы отсортированы по имени 4. Между разными папками добавлены визуальные разделители
Это делает список файлов более структурированным и удобным для навигации!
|
|
|
|
|
|
|
Отлично! Добавим прогресс-бар и анимацию загрузки. Вот необходимые изменения:
1. Добавьте HTML для прогресс-бара в основную панель:
```html <!-- Добавьте этот код в <div class="main-content"> после program-header --> <div class="loading-overlay" id="loadingOverlay" style="display: none;"> <div class="loading-content"> <div class="loading-spinner"></div> <div class="loading-text" id="loadingText">Загрузка данных...</div> <div class="progress-container"> <div class="progress-bar"> <div class="progress-fill" id="progressFill"></div> </div> <div class="progress-text" id="progressText">0%</div> </div> <div class="loading-details" id="loadingDetails"></div> </div> </div> ```
2. Добавьте CSS стили для прогресс-бара и анимации:
```css /* Стили для прогресс-бара загрузки */ .loading-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(255, 255, 255, 0.95); display: flex; justify-content: center; align-items: center; z-index: 1000; backdrop-filter: blur(5px); }
.loading-content { text-align: center; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 5px 25px rgba(0, 0, 0, 0.2); min-width: 300px; border: 1px solid #e0e0e0; }
.loading-spinner { width: 50px; height: 50px; border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto 20px; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.loading-text { font-size: 1.2rem; color: #2c3e50; margin-bottom: 15px; font-weight: 600; }
.progress-container { margin: 20px 0; }
.progress-bar { width: 100%; height: 8px; background-color: #ecf0f1; border-radius: 4px; overflow: hidden; margin-bottom: 8px; }
.progress-fill { height: 100%; background: linear-gradient(90deg, #3498db, #2980b9); border-radius: 4px; width: 0%; transition: width 0.3s ease; }
.progress-text { font-size: 0.9rem; color: #7f8c8d; text-align: center; }
.loading-details { font-size: 0.8rem; color: #95a5a6; margin-top: 10px; min-height: 20px; }
/* Анимация пульсации для текста */ .pulse { animation: pulse 1.5s ease-in-out infinite; }
@keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.7; } 100% { opacity: 1; } }
/* Улучшенный стиль для обычного loading */ .loading { text-align: center; padding: 40px 20px; color: #7f8c8d; }
.loading::before { content: ''; display: block; width: 40px; height: 40px; border: 3px solid #f3f3f3; border-top: 3px solid #3498db; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto 20px; } ```
3. Добавьте функции управления прогресс-баром в JavaScript:
```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'); loadingText.textContent = message; progressFill.style.width = '0%'; progressText.textContent = '0%'; loadingDetails.textContent = ''; overlay.style.display = 'flex'; loadingText.classList.add('pulse'); }
function hideLoading() { const overlay = document.getElementById('loadingOverlay'); const loadingText = document.getElementById('loadingText'); overlay.style.display = 'none'; loadingText.classList.remove('pulse'); }
function updateProgress(percent, details = '') { const progressFill = document.getElementById('progressFill'); const progressText = document.getElementById('progressText'); const loadingDetails = document.getElementById('loadingDetails'); progressFill.style.width = percent + '%'; progressText.textContent = Math.round(percent) + '%'; if (details) { loadingDetails.textContent = details; } }
function updateLoadingText(message) { const loadingText = document.getElementById('loadingText'); loadingText.textContent = message; } ```
4. Обновите функцию loadWellData для использования прогресс-бара:
```javascript // Загрузка данных о скважине 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('Чтение архива...'); // Получаем файл из FileSystemFileHandle const fileObj = await file.handle.getFile(); console.log('Размер файла:', fileObj.size, 'байт'); // Увеличиваем лимит размера файла до 100MB if (fileObj.size > 100 * 1024 * 1024) { // 100MB limit throw new Error('Файл слишком большой для обработки в браузере (максимум 100 МБ)'); } // Читаем архив updateLoadingText('Распаковка архива...'); console.log('Чтение arrayBuffer...'); const arrayBuffer = await fileObj.arrayBuffer(); console.log('ArrayBuffer прочитан, размер:', arrayBuffer.byteLength); // Распаковываем архив updateLoadingText('Обработка данных...'); console.log('Распаковка ZIP...'); const zip = await JSZip.loadAsync(arrayBuffer); console.log('ZIP распакован, файлов:', Object.keys(zip.files).length); // Собираем все файлы 904~ для объединения 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'; } } // Если есть файлы 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++) { 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 файла до 50MB if (multiFile.size > 50 * 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); // Создаем парсер XML 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}...`); const wellsFromFile = extractWellsFromMultiFile(xmlDoc); 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); } } updateProgress(85, 'Формирование списка скважин...'); console.log(`Всего уникальных скважин после объединения: ${allWells.length}`); if (allWells.length > 0) { currentMultiWellData = { xmlDocs: allXmlDocs, // Сохраняем все XML документы 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~'); } } else if (otherTargetFile) { // Обработка обычных файлов (906~, 911~, 909~) updateProgress(50, 'Обработка файла данных...'); console.log('Найден INU файл:', otherTargetFile.name, 'Размер:', otherTargetFile._data.uncompressedSize); // Увеличиваем лимит размера INU файла до 50MB if (otherTargetFile._data.uncompressedSize > 50 * 1024 * 1024) { throw new Error('INU файл слишком большой для обработки в браузере (максимум 50 МБ)'); } // Читаем 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 архивы: до 100 МБ</li> <li>INU файлы: до 50 МБ</li> </ul> <p>Попробуйте выбрать другой файл или обратитесь к администратору</p> </div> `; } } ```
5. Обновите функцию parseWellDataFromMulti для отображения прогресса:
```javascript // Парсинг данных для конкретной скважины из мульти-файла async function parseWellDataFromMulti(wellId) { if (!currentMultiWellData || !currentMultiWellData.xmlDocs || currentMultiWellData.xmlDocs.length === 0) { throw new Error('Данные мульти-файла не загружены'); }
showLoading('Поиск данных скважины...');
// Ищем скважину во всех XML документах for (let i = 0; i < currentMultiWellData.xmlDocs.length; i++) { const xmlDoc = currentMultiWellData.xmlDocs[i]; const progress = (i / currentMultiWellData.xmlDocs.length) * 100; updateProgress(progress, `Поиск в файле ${i + 1} из ${currentMultiWellData.xmlDocs.length}...`); // Находим данные конкретной скважины 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 await parseWellDataFromSingleMultiFile(xmlDoc, targetWell, wellId); setTimeout(() => { hideLoading(); }, 500); return; } } hideLoading(); throw new Error(`Скважина с ID ${wellId} не найдена в файлах`); } ```
Теперь при загрузке данных будет отображаться красивый прогресс-бар с:
· Анимированным спиннером · Текстом текущей операции · Прогресс-баром с процентами · Детальной информацией о процессе · Размытым фоном для фокусировки на процессе загрузки
Особенно полезно для мульти-файлов, где показывается прогресс обработки каждого файла!
|
Исправлю лимиты размеров файлов. Вот необходимые изменения:
1. Обновите функцию loadWellData:
```javascript // Загрузка данных о скважине 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('Чтение архива...'); // Получаем файл из FileSystemFileHandle const fileObj = await file.handle.getFile(); console.log('Размер файла:', fileObj.size, 'байт'); // Увеличиваем лимит размера файла до 1500MB if (fileObj.size > 1500 * 1024 * 1024) { // 1500MB limit throw new Error('Файл слишком большой для обработки в браузере (максимум 1500 МБ)'); } // Читаем архив updateLoadingText('Распаковка архива...'); console.log('Чтение arrayBuffer...'); const arrayBuffer = await fileObj.arrayBuffer(); console.log('ArrayBuffer прочитан, размер:', arrayBuffer.byteLength); // Распаковываем архив updateLoadingText('Обработка данных...'); console.log('Распаковка ZIP...'); const zip = await JSZip.loadAsync(arrayBuffer); console.log('ZIP распакован, файлов:', Object.keys(zip.files).length); // Собираем все файлы 904~ для объединения 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'; } } // Если есть файлы 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++) { 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); // Создаем парсер XML 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}...`); const wellsFromFile = extractWellsFromMultiFile(xmlDoc); 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); } } updateProgress(85, 'Формирование списка скважин...'); console.log(`Всего уникальных скважин после объединения: ${allWells.length}`); if (allWells.length > 0) { currentMultiWellData = { xmlDocs: allXmlDocs, // Сохраняем все XML документы 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~'); } } 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. Обновите функцию loadWellDataFromFile:
```javascript // Загрузка данных о скважине из файла async function loadWellDataFromFile(file, fileNumber) { showLoading('Открытие файла...'); const wellDataElement = document.getElementById('wellData'); wellDataElement.innerHTML = '<div class="loading"><p>Загрузка данных...</p></div>';
try { // Увеличиваем лимит для ручного выбора файлов до 2000MB if (file.size > 2000 * 1024 * 1024) { // 2000MB limit для INU файлов throw new Error('Файл слишком большой для обработки в браузере (максимум 2000 МБ)'); } updateProgress(30, 'Чтение файла...'); // Читаем содержимое файла const fileContent = await file.text(); updateProgress(70, 'Обработка XML...'); // Очищаем XML от некорректных символов const cleanedContent = cleanXMLContent(fileContent); // Определяем тип файла по имени let fileType = ''; if (file.name.startsWith('906~')) { fileType = 'all'; } else if (file.name.startsWith('911~')) { fileType = 'final'; } else if (file.name.startsWith('909~')) { fileType = 'primary'; } else if (file.name.startsWith('904~')) { fileType = 'multi'; } else { fileType = 'unknown'; } // Парсинг XML данных updateProgress(90, 'Парсинг данных...'); await parseXMLData(cleanedContent, fileType); 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); } }, 500); } 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. Обновите сообщения об ошибках в других местах (если есть):
Замените все упоминания старых лимитов (100 МБ и 50 МБ) на новые (1500 МБ и 2000 МБ) в сообщениях об ошибках.
Теперь установлены лимиты:
· ZIP архивы: до 1500 МБ (1.5 ГБ) · INU файлы: до 2000 МБ (2 ГБ)
Это должно покрыть большинство реальных случаев использования!
|
Исправлю отображение анимации и перенесу кнопку. Вот необходимые изменения:
1. Уберите автоматический спиннер из обычного loading состояния:
```css /* Убираем спиннер из обычного loading */ .loading::before { display: none; /* Убираем автоматический спиннер */ } ```
2. Обновите начальное состояние wellData:
```javascript // В функции displayWellData или при инициализации убедитесь, что нет спиннера document.getElementById('wellData').innerHTML = ` <div class="loading"> <p>Выберите файл из списка для отображения данных</p> </div> `; ```
3. Обновите функцию showWellsList для нового расположения кнопки:
```javascript // Показать список скважин для мульти-файла function showWellsList(wells, fileName, fileCount = 1) { const wellsList = document.getElementById('wellsList'); const fileList = document.getElementById('fileList'); const backButton = document.getElementById('backButton'); const listsContainer = document.getElementById('listsContainer'); 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> `; 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'; // Обновляем счетчик document.getElementById('fileCounter').textContent = `Найдено скважин: ${wells.length}`; // Показываем кнопку возврата вверху backButton.style.display = 'block'; } ```
4. Добавьте CSS для нового заголовка списка скважин:
```css /* Стили для заголовка списка скважин */ .wells-header { background-color: #34495e; padding: 15px; margin-bottom: 10px; border-radius: 4px; border-left: 4px solid #27ae60; }
.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; }
/* Обновляем стиль кнопки возврата */ .back-button { background-color: #e67e22; color: white; border: none; padding: 10px 15px; border-radius: 4px; cursor: pointer; margin-bottom: 15px; width: 100%; text-align: center; transition: background-color 0.2s; font-weight: 500; }
.back-button:hover { background-color: #d35400; } ```
5. Обновите HTML для нового расположения кнопки:
```html <!-- В listsContainer измените порядок элементов --> <div class="lists-container" id="listsContainer"> <!-- Кнопка возврата ПЕРЕД списком скважин --> <button class="back-button" id="backButton" style="display: none;">← Вернуться к списку файлов</button> <!-- Список файлов --> <ul class="file-list" id="fileList"> <li class="file-item">Выберите папку с архивами</li> </ul> <!-- Список скважин --> <ul class="wells-list" id="wellsList"> <!-- Список скважин будет заполняться динамически --> </ul> </div> ```
6. Убедитесь, что анимация загрузки показывается только при реальной загрузке:
В функциях loadWellData и loadWellDataFromFile анимация уже правильно вызывается через showLoading() только когда начинается реальная загрузка данных.
7. Обновите функцию 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 = 'Выберите скважину для просмотра'; } ```
Теперь:
· ✅ Анимация загрузки показывается только при реальной загрузке данных · ✅ В обычном состоянии "Выберите файл" нет крутящегося спиннера · ✅ Кнопка "Вернуться к списку файлов" находится над списком скважин · ✅ Добавлен красивый заголовок для списка скважин с информацией о файле · ✅ Интерфейс стал более интуитивным и чистым
|
Понял! Проблема в том, что кнопка осталась в старом месте в HTML. Давайте полностью переработаем структуру. Вот исправления:
1. Обновите HTML структуру левой панели:
```html <!-- Левая панель с файлами --> <div class="sidebar"> <div class="sidebar-header"> <h2>Список архивов</h2> <div class="folder-selector"> <div class="folder-path" id="folderPath">Выберите папку с архивами</div> <div class="folder-buttons"> <button class="btn" id="selectFolderBtn">Выбрать папку</button> <button class="btn" id="selectFileBtn">Открыть INU файл</button> <button class="btn" id="refreshBtn">Обновить</button> </div> <div class="folder-options"> <label class="checkbox-label"> <input type="checkbox" id="searchSubfolders"> <span class="checkmark"></span> Искать в подпапках </label> </div> </div> </div> <div class="file-list-container"> <!-- Кнопка возврата ПЕРЕД списками --> <button class="back-button" id="backButton" style="display: none;">← Вернуться к списку файлов</button> <div class="lists-container" id="listsContainer"> <!-- Список файлов --> <ul class="file-list" id="fileList"> <li class="file-item">Выберите папку с архивами</li> </ul> <!-- Список скважин --> <ul class="wells-list" id="wellsList"> <!-- Список скважин будет заполняться динамически --> </ul> </div> <div class="file-counter" id="fileCounter"></div> </div> </div> ```
2. Обновите CSS для нового расположения:
```css /* Обновляем контейнер списков */ .file-list-container { flex: 1; display: flex; flex-direction: column; overflow: hidden; padding: 0 20px; }
/* Кнопка возврата - теперь в основном потоке */ .back-button { background-color: #e67e22; color: white; border: none; padding: 12px 15px; border-radius: 4px; cursor: pointer; margin-bottom: 15px; width: 100%; text-align: center; transition: background-color 0.2s; font-weight: 500; font-size: 0.9rem; flex-shrink: 0; }
.back-button:hover { background-color: #d35400; transform: translateY(-1px); box-shadow: 0 2px 5px rgba(0,0,0,0.2); }
.lists-container { flex: 1; overflow-y: auto; } ```
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 = ''; // Добавляем скважины 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'; // Показываем кнопку возврата backButton.style.display = 'block'; // Обновляем счетчик с информацией о файле const fileInfo = fileCount > 1 ? ` (${fileCount} файлов)` : ''; document.getElementById('fileCounter').textContent = `Файл: ${fileName}${fileInfo} • Найдено скважин: ${wells.length}`; } ```
4. Обновите функцию backToFileList:
```javascript // Функция возврата к списку файлов function backToFileList() { const wellsList = document.getElementById('wellsList'); const fileList = document.getElementById('fileList'); const backButton = document.getElementById('backButton'); wellsList.style.display = 'none'; fileList.style.display = 'block'; backButton.style.display = 'none'; // Сбрасываем выбранную скважину currentWellId = null; currentMultiWellData = null; currentMultiFileName = null; // Обновляем счетчик const fileCount = document.querySelectorAll('#fileList .file-item').length; document.getElementById('fileCounter').textContent = `Найдено архивов: ${fileCount}`; // Показываем сообщение о выборе файла document.getElementById('wellData').innerHTML = ` <div class="loading"> <p>Выберите файл из списка для отображения данных</p> </div> `; document.getElementById('wellName').innerHTML = 'Выберите скважину для просмотра'; } ```
Теперь кнопка "Вернуться к списку файлов" будет:
· ✅ Находиться ПЕРЕД списками файлов/скважин · ✅ Всегда видна когда активен список скважин · ✅ Исчезать когда возвращаемся к списку файлов · ✅ Иметь хорошее визуальное оформление
Структура теперь:
``` sidebar-header ├── Заголовок и кнопки └── back-button (только при показе скважин) file-list-container ├── lists-container │ ├── file-list (список архивов) │ └── wells-list (список скважин) └── file-counter (счетчик внизу) ```
|
Вероятно, я случайно удалил код для мобильного меню. Давайте восстановим его! Добавьте этот код:
1. Добавьте кнопку мобильного меню в HTML:
```html <!-- Добавьте эту кнопку в начало body --> <button class="mobile-menu-btn" id="mobileMenuBtn">☰ Меню</button>
<!-- Обновите container чтобы добавить класс для мобильной версии --> <div class="container" id="mainContainer"> <!-- существующий код левой панели и основной панели --> </div> ```
2. Добавьте CSS для мобильного меню:
```css /* Стили для мобильного меню */ .mobile-menu-btn { display: none; position: fixed; top: 10px; left: 10px; z-index: 1001; background-color: #3498db; color: white; border: none; padding: 10px 15px; border-radius: 4px; cursor: pointer; font-size: 1rem; box-shadow: 0 2px 5px rgba(0,0,0,0.2); }
.mobile-menu-btn:hover { background-color: #2980b9; }
/* Мобильная версия */ @media (max-width: 768px) { .mobile-menu-btn { display: block; } .container { flex-direction: column; } .sidebar { width: 100%; height: 300px; position: fixed; top: 0; left: 0; z-index: 1000; transform: translateY(-100%); transition: transform 0.3s ease; } .sidebar.mobile-open { transform: translateY(0); } .main-content { height: 100vh; margin-top: 0; padding-top: 60px; /* Место для кнопки меню */ } /* Когда меню открыто, затемняем основной контент */ .container.menu-open .main-content { opacity: 0.3; pointer-events: none; } /* Увеличиваем отступы для мобильных */ .sidebar-header { padding: 15px; } .file-list-container { padding: 0 15px; } /* Улучшаем видимость кнопки возврата на мобильных */ .back-button { padding: 15px; font-size: 1rem; margin-bottom: 10px; } } ```
3. Добавьте JavaScript для управления мобильным меню:
```javascript // Добавьте в конец скрипта, перед закрывающим тегом </script>
// Управление мобильным меню document.addEventListener('DOMContentLoaded', function() { const mobileMenuBtn = document.getElementById('mobileMenuBtn'); const sidebar = document.querySelector('.sidebar'); const mainContainer = document.getElementById('mainContainer'); if (mobileMenuBtn && sidebar) { mobileMenuBtn.addEventListener('click', function() { sidebar.classList.toggle('mobile-open'); mainContainer.classList.toggle('menu-open'); // Обновляем текст кнопки if (sidebar.classList.contains('mobile-open')) { mobileMenuBtn.textContent = '✕ Закрыть'; } else { mobileMenuBtn.textContent = '☰ Меню'; } }); // Закрываем меню при клике на основной контент document.querySelector('.main-content').addEventListener('click', function() { if (sidebar.classList.contains('mobile-open')) { sidebar.classList.remove('mobile-open'); mainContainer.classList.remove('menu-open'); mobileMenuBtn.textContent = '☰ Меню'; } }); } });
// Также обновите функцию selectFile чтобы закрывать меню на мобильных async function selectFile(file, fileNumber) { // Существующий код... // Закрываем мобильное меню после выбора файла closeMobileMenu(); }
// И функцию selectWell function selectWell(wellId, wellNumber) { // Существующий код... // Закрываем мобильное меню после выбора скважины closeMobileMenu(); }
// Функция для закрытия мобильного меню function closeMobileMenu() { const sidebar = document.querySelector('.sidebar'); const mainContainer = document.getElementById('mainContainer'); const mobileMenuBtn = document.getElementById('mobileMenuBtn'); if (sidebar && sidebar.classList.contains('mobile-open')) { sidebar.classList.remove('mobile-open'); mainContainer.classList.remove('menu-open'); if (mobileMenuBtn) { mobileMenuBtn.textContent = '☰ Меню'; } } } ```
4. Обновите HTML, добавив необходимые элементы:
```html <!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ГеоСкан ПС - Анализ данных скважин из полевой системы ИСИХОГИ</title> <!-- остальные мета-теги и стили --> </head> <body> <!-- Кнопка мобильного меню --> <button class="mobile-menu-btn" id="mobileMenuBtn">☰ Меню</button> <div class="container" id="mainContainer"> <!-- Левая панель --> <div class="sidebar"> <!-- существующий код sidebar --> </div> <!-- Основная панель --> <div class="main-content"> <!-- существующий код main-content --> </div> </div> <script> // существующий JavaScript код </script> </body> </html> ```
Теперь мобильное меню должно работать правильно:
· ✅ Кнопка "☰ Меню" появляется на мобильных устройствах · ✅ При нажатии меню выезжает сверху · ✅ Основной контент затемняется когда меню открыто · ✅ Меню автоматически закрывается при выборе файла или скважины · ✅ Можно закрыть меню кликом на основной контент
|
<button class="mobile-menu-toggle" id="mobileMenuToggle">☰ Меню</button>
|
Вероятно, я случайно удалил код мобильного меню. Давайте восстановим его:
1. Добавьте кнопку мобильного меню в HTML:
```html <!-- Добавьте эту кнопку в начало body --> <button class="mobile-menu-toggle" id="mobileMenuToggle">☰ Меню</button> ```
2. Добавьте CSS для мобильного меню:
```css /* Стили для мобильного меню */ .mobile-menu-toggle { display: none; position: fixed; top: 10px; left: 10px; z-index: 1001; background-color: #3498db; color: white; border: none; padding: 10px 15px; border-radius: 4px; cursor: pointer; font-size: 1rem; box-shadow: 0 2px 5px rgba(0,0,0,0.2); }
.mobile-menu-toggle:hover { background-color: #2980b9; }
/* Адаптивность для мобильных устройств */ @media (max-width: 768px) { .mobile-menu-toggle { display: block; } .container { flex-direction: column; } .sidebar { width: 100%; height: auto; max-height: 0; overflow: hidden; transition: max-height 0.3s ease; position: fixed; top: 0; left: 0; z-index: 1000; background-color: #2c3e50; } .sidebar.mobile-open { max-height: 100vh; height: 100vh; } .main-content { width: 100%; height: 100vh; margin-top: 0; padding-top: 60px; /* Отступ для кнопки меню */ } /* Убираем фиксированные элементы в мобильной версии */ .sidebar-header { padding-top: 50px; /* Место для кнопки закрытия */ } /* Кнопка закрытия мобильного меню */ .close-menu-btn { position: absolute; top: 10px; right: 15px; background: none; border: none; color: white; font-size: 1.5rem; cursor: pointer; z-index: 1002; } } ```
3. Добавьте JavaScript для управления мобильным меню:
```javascript // Добавьте этот код в инициализацию DOMContentLoaded document.addEventListener('DOMContentLoaded', function() { // ... существующий код ... // Инициализация мобильного меню const mobileMenuToggle = document.getElementById('mobileMenuToggle'); const sidebar = document.querySelector('.sidebar'); const mainContent = document.querySelector('.main-content'); if (mobileMenuToggle) { mobileMenuToggle.addEventListener('click', function() { sidebar.classList.toggle('mobile-open'); // Добавляем кнопку закрытия при открытии меню if (sidebar.classList.contains('mobile-open') && !document.querySelector('.close-menu-btn')) { const closeBtn = document.createElement('button'); closeBtn.className = 'close-menu-btn'; closeBtn.innerHTML = '×'; closeBtn.addEventListener('click', function() { sidebar.classList.remove('mobile-open'); }); sidebar.appendChild(closeBtn); } }); } // Закрытие меню при клике на основную область if (mainContent) { mainContent.addEventListener('click', function() { if (sidebar.classList.contains('mobile-open')) { sidebar.classList.remove('mobile-open'); } }); } // Закрытие меню при изменении размера окна на десктопный window.addEventListener('resize', function() { if (window.innerWidth > 768) { sidebar.classList.remove('mobile-open'); } }); }); ```
4. Обновите адаптивность в существующем CSS:
```css /* Адаптивность */ @media (max-width: 768px) { .container { flex-direction: column; } .sidebar { width: 100%; height: 300px; position: relative; /* Убираем фиксированное позиционирование для дефолтного состояния */ } .sidebar.mobile-open { position: fixed; /* Фиксируем только при открытии */ height: 100vh; z-index: 1000; } .main-content { height: calc(100vh - 300px); } /* Скрываем обычную кнопку меню в десктопном режиме */ .mobile-menu-toggle { display: none; } }
@media (max-width: 480px) { .mobile-menu-toggle { display: block; /* Показываем на маленьких экранах */ } .sidebar { height: auto; max-height: 0; overflow: hidden; transition: max-height 0.3s ease; } .sidebar.mobile-open { max-height: 100vh; height: 100vh; } .main-content { height: 100vh; padding-top: 50px; } } ```
Теперь мобильное меню должно работать правильно:
· ✅ Кнопка "☰ Меню" появляется на мобильных устройствах · ✅ При клике открывается/закрывается боковая панель · ✅ Добавлена кнопка закрытия "×" внутри меню · ✅ Меню закрывается при клике на основную область · ✅ Автоматически закрывается при переходе на десктопный размер
|