|
| Форум » Флуд » Общение » Поговорим о... |
| Поговорим о... |
Вторник, 2026-03-10, 15:28
# 2
{{/* layouts/shortcodes/cardinfo.html */}}
{{ $link := .Get "link" }} {{ $title := .Get "title" }} {{ $desc := .Get "desc" | default "" }} {{ $date := .Get "date" }} {{ $audience := .Get "audience" }} {{ $tag := .Get "tag" }} {{ $tagtype := .Get "tagtype" | default "default" }} {{ $icon := .Get "icon" }} {{ $iconsize := .Get "iconsize" | default "24px" }} {{ $visible := .Get "visible" | default "true" }} {{ $style := .Get "style" | default "button" }} {{ if ne $visible "false" }} <div class="info-card info-card-style-{{ $style }}"> <a href="{{ $link }}" class="info-card-link" target="_blank" rel="noopener"> <div class="info-card-header"> <div class="info-card-left"> {{ if $icon }} <span class="info-card-title-icon"> {{ if .Site.Data.icons }} {{ $iconData := index .Site.Data.icons $icon }} {{ if $iconData }} {{ $iconSvg := replaceRE "<svg" (printf "<svg width='%s' height='%s'" $iconsize $iconsize) $iconData }} <span class="custom-icon">{{ $iconSvg | safeHTML }}</span> {{ else }} <span class="custom-icon"> <svg width="{{ $iconsize }}" height="{{ $iconsize }}" viewBox="0 0 24 24" fill="currentColor"> <path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/> </svg> </span> {{ end }} {{ else }} <span class="custom-icon"> <svg width="{{ $iconsize }}" height="{{ $iconsize }}" viewBox="0 0 24 24" fill="currentColor"> <path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/> </svg> </span> {{ end }} </span> {{ end }} <span class="info-card-title">{{ $title }}</span> </div> <div class="info-card-right-section"> {{ if $tag }} <span class="info-card-tag info-card-tag-{{ $tagtype }}"> {{ $tag }} </span> {{ end }} {{ if $desc }} <div class="info-card-tooltip-wrapper"> <span class="info-card-icon"> <svg class="info-icon" width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm0 13A6 6 0 1 1 8 2a6 6 0 0 1 0 12z"/> <path d="M7 11h2v2H7zm0-8h2v6H7z"/> </svg> </span> <div class="info-card-tooltip"> <div class="tooltip-description">{{ $desc | safeHTML }}</div> {{ if or $date $audience }} <div class="tooltip-details"> {{ if $date }} <div class="tooltip-row"> <svg class="tooltip-row-icon" width="14" height="14" viewBox="0 0 24 24" fill="currentColor"> <path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z"/> </svg> <span class="tooltip-row-text">Добавлено: {{ $date }}</span> </div> {{ end }} {{ if $audience }} <div class="tooltip-row"> <svg class="tooltip-row-icon" width="14" height="14" viewBox="0 0 24 24" fill="currentColor"> <path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/> </svg> <span class="tooltip-row-text">Для кого: {{ $audience }}</span> </div> {{ end }} </div> {{ end }} </div> </div> {{ end }} </div> </div> </a> </div> {{ end }} <style> /* ===== ОСНОВНЫЕ СТИЛИ ===== */ .info-card { transition: all 0.2s ease; position: relative; box-sizing: border-box; display: inline-block; vertical-align: top; margin: 0.75rem 0 0 0; padding: 0; width: 100%; } .info-card-link { display: block; text-decoration: none; color: #333333; width: 100%; height: 100%; box-sizing: border-box; } .info-card-link:hover { text-decoration: none; } .info-card-header { display: flex; justify-content: space-between; align-items: center; min-height: 24px; width: 100%; gap: 12px; /* Увеличил gap для лучшего разделения */ } .info-card-left { display: flex; align-items: center; flex: 1 1 auto; min-width: 0; /* Позволяет тексту сжиматься */ padding-right: 8px; gap: 10px; } .info-card-title-icon { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; } .info-card-title-icon .custom-icon { display: inline-flex; align-items: center; justify-content: center; line-height: 0; } .info-card-title-icon .custom-icon svg { width: 100%; height: 100%; fill: currentColor; color: #666666; vertical-align: middle; } .info-card-title { font-size: 1rem; font-weight: 600; color: #333333; line-height: 1.4; flex: 1 1 auto; min-width: 30px; word-wrap: break-word; overflow-wrap: break-word; white-space: normal; } .info-card-right-section { display: flex; align-items: center; gap: 8px; flex-shrink: 0; flex-wrap: nowrap; /* Запрещаем перенос внутри правой секции */ } /* ===== КОНТЕЙНЕР ДЛЯ СЕТКИ ===== */ .cards-grid { display: flex; flex-wrap: wrap; margin: 0; gap: 20px; } .cards-grid .info-card { flex: 0 0 auto; padding: 0; margin: 0.75rem 0 0 0; box-sizing: border-box; } /* 1 колонка */ .cards-grid.cols-1 .info-card { width: calc(100%); min-width: 350px; } /* 2 колонки */ .cards-grid.cols-2 .info-card { width: calc(50% - 10px); min-width: 350px; } /* 3 колонки */ .cards-grid.cols-3 .info-card { width: calc(33.333% - 13.333px); min-width: 350px; } /* 4 колонки */ .cards-grid.cols-4 .info-card { width: calc(25% - 15px); min-width: 350px; } /* Когда карточка переносится на новую строку из-за min-width */ @media (max-width: 750px) { .cards-grid.cols-2 .info-card, .cards-grid.cols-3 .info-card, .cards-grid.cols-4 .info-card { width: 100%; /* На всю ширину при переносе */ min-width: auto; } } /* Промежуточные состояния для 4 колонок */ @media (max-width: 1100px) and (min-width: 901px) { .cards-grid.cols-4 .info-card { width: calc(50% - 10px); /* 2 колонки */ } } /* Промежуточные состояния для 3 и 4 колонок */ @media (max-width: 900px) and (min-width: 751px) { .cards-grid.cols-3 .info-card, .cards-grid.cols-4 .info-card { width: calc(50% - 10px); /* 2 колонки */ } } /* ===== СТИЛЬ: BUTTON (по умолчанию) ===== */ .info-card-style-button { border: 1px solid #e0e0e0; border-radius: 8px; background: #ffffff; } .info-card-style-button:hover { border-color: #4a90e2; box-shadow: 0 2px 8px rgba(74, 144, 226, 0.15); } .info-card-style-button .info-card-link { padding: 1rem 1.25rem; } /* ===== СТИЛЬ: LINK (простая ссылка с отступами) ===== */ .info-card-style-link { border: none; background: transparent; border-radius: 0; } .info-card-style-link .info-card-link { padding: 0.5rem 0.25rem; border-bottom: 1px solid transparent; } .info-card-style-link:hover .info-card-link { border-bottom-color: #4a90e2; } .info-card-style-link .info-card-title { font-weight: 500; } .info-card-style-link .info-card-title-icon .custom-icon svg { color: #4a90e2; opacity: 0.8; } /* ===== СТИЛЬ: MINIMAL (минималистичный) ===== */ .info-card-style-minimal { border: none; background: transparent; } .info-card-style-minimal .info-card-link { padding: 0.3rem 0; } .info-card-style-minimal .info-card-title { font-weight: 400; font-size: 0.95rem; } .info-card-style-minimal .info-card-title-icon .custom-icon svg { color: #888; width: 18px; height: 18px; } .info-card-style-minimal:hover .info-card-title { color: #4a90e2; } /* ===== СТИЛЬ: CARD (карточка с тенью) ===== */ .info-card-style-card { border: none; border-radius: 12px; background: #ffffff; box-shadow: 0 4px 12px rgba(0,0,0,0.08); transition: all 0.3s ease; } .info-card-style-card:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0,0,0,0.12); } .info-card-style-card .info-card-link { padding: 1.25rem 1.5rem; } .info-card-style-card .info-card-title { font-size: 1.1rem; } /* ===== СТИЛЬ: PILL (в виде пилюли) ===== */ .info-card-style-pill { border: 1px solid #e0e0e0; border-radius: 50px; background: #f8f9fa; } .info-card-style-pill:hover { border-color: #4a90e2; background: #ffffff; } .info-card-style-pill .info-card-link { padding: 0.6rem 1.25rem; } .info-card-style-pill .info-card-title { font-weight: 500; font-size: 0.95rem; } /* ===== ТЕГИ ===== */ .info-card-tag { display: inline-block; font-size: 0.75rem; padding: 0.2rem 0.6rem; border-radius: 12px; font-weight: 500; white-space: nowrap; /* Запрещаем перенос текста в теге */ border: 1px solid transparent; line-height: 1.4; flex-shrink: 0; } .info-card-tag-default { background: #f0f0f0; color: #666666; border-color: #ddd; } .info-card-tag-info { background: #e3f2fd; color: #1565c0; border-color: #90caf9; } .info-card-tag-warning { background: #fff3e0; color: #e65100; border-color: #ffcc80; } .info-card-tag-success { background: #e8f5e9; color: #2e7d32; border-color: #a5d6a7; } .info-card-tag-error { background: #ffebee; color: #c62828; border-color: #ef9a9a; } /* ===== ТУЛТИПЫ ===== */ .info-card-tooltip-wrapper { position: relative; display: flex; align-items: center; flex-shrink: 0; } .info-card-icon { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; cursor: help; color: #666666; border-radius: 50%; transition: all 0.15s; flex-shrink: 0; } .info-card-icon:hover { background: #f0f0f0; color: #4a90e2; } .info-icon { width: 16px; height: 16px; } .info-card-tooltip { position: absolute; bottom: calc(100% + 10px); right: 50%; transform: translateX(50%); width: 420px; max-width: 90vw; padding: 18px; background: white; border-radius: 8px; box-shadow: 0 5px 25px rgba(0,0,0,0.15); border: 1px solid #e0e0e0; z-index: 1000; opacity: 0; visibility: hidden; transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); pointer-events: auto; } .info-card-tooltip.tooltip-bottom { bottom: auto; top: calc(100% + 10px); } .info-card-tooltip-wrapper:hover .info-card-tooltip { opacity: 1; visibility: visible; transform: translateX(50%) translateY(-5px); } .info-card-tooltip.tooltip-bottom:hover { transform: translateX(50%) translateY(5px); } .info-card-tooltip::before { content: ''; position: absolute; bottom: -7px; left: 50%; transform: translateX(-50%) rotate(45deg); width: 14px; height: 14px; background: white; border-right: 1px solid #e0e0e0; border-bottom: 1px solid #e0e0e0; z-index: -1; } .info-card-tooltip.tooltip-bottom::before { bottom: auto; top: -7px; transform: translateX(-50%) rotate(-135deg); } .tooltip-description { font-size: 0.92rem; line-height: 1.6; color: #444444; margin-bottom: 14px; max-height: 350px; overflow-y: auto; padding-right: 6px; } .tooltip-description::-webkit-scrollbar { width: 6px; } .tooltip-description::-webkit-scrollbar-track { background: #f5f5f5; border-radius: 3px; } .tooltip-description::-webkit-scrollbar-thumb { background: #b0b0b0; border-radius: 3px; } .tooltip-details { font-size: 0.85rem; color: #666666; border-top: 1px solid #f0f0f0; padding-top: 12px; } .tooltip-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; } .tooltip-row:last-child { margin-bottom: 0; } .tooltip-row-icon { flex-shrink: 0; width: 14px; height: 14px; } .tooltip-row-text { line-height: 1.4; } /* ===== ТЕМНАЯ ТЕМА ===== */ body.dark .info-card, html.dark .info-card { background: #2d3748; border-color: #4a5568; } body.dark .info-card:hover, html.dark .info-card:hover { border-color: #63b3ed; box-shadow: 0 2px 8px rgba(99, 179, 237, 0.2); } body.dark .info-card-link, html.dark .info-card-link { color: #e2e8f0; } body.dark .info-card-title, html.dark .info-card-title { color: #f7fafc; } body.dark .info-card-title-icon .custom-icon svg, html.dark .info-card-title-icon .custom-icon svg { color: #a0aec0; } body.dark .info-card-icon, html.dark .info-card-icon { color: #a0aec0; } body.dark .info-card-icon:hover, html.dark .info-card-icon:hover { background: #4a5568; color: #63b3ed; } body.dark .info-card-tooltip, html.dark .info-card-tooltip { background: #1a202c; border-color: #4a5568; box-shadow: 0 5px 25px rgba(0,0,0,0.5); } body.dark .info-card-tooltip::before, html.dark .info-card-tooltip::before { background: #1a202c; border-color: #4a5568; } body.dark .tooltip-description, html.dark .tooltip-description { color: #e2e8f0; } body.dark .tooltip-details, html.dark .tooltip-details { color: #a0aec0; border-top-color: #4a5568; } body.dark .info-card-tag-default, html.dark .info-card-tag-default { background: #4a5568; color: #cbd5e0; border-color: #718096; } body.dark .info-card-tag-info, html.dark .info-card-tag-info { background: #2c5282; color: #bee3f8; border-color: #3182ce; } body.dark .info-card-tag-warning, html.dark .info-card-tag-warning { background: #975a16; color: #feebc8; border-color: #d69e2e; } body.dark .info-card-tag-success, html.dark .info-card-tag-success { background: #276749; color: #c6f6d5; border-color: #38a169; } body.dark .info-card-tag-error, html.dark .info-card-tag-error { background: #9b2c2c; color: #fed7d7; border-color: #e53e3e; } body.dark .info-card-style-link, html.dark .info-card-style-link, body.dark .info-card-style-minimal, html.dark .info-card-style-minimal { background: transparent; } body.dark .info-card-style-card, html.dark .info-card-style-card { background: #2d3748; box-shadow: 0 4px 12px rgba(0,0,0,0.3); } body.dark .info-card-style-pill, html.dark .info-card-style-pill { background: #2d3748; border-color: #4a5568; } body.dark .info-card-style-pill:hover, html.dark .info-card-style-pill:hover { background: #374151; } /* ===== АДАПТИВНОСТЬ ===== */ @media (max-width: 768px) { .info-card-header { flex-direction: row; /* Оставляем в строку на мобильных */ flex-wrap: wrap; /* Разрешаем перенос если нужно */ gap: 8px; } .info-card-left { width: auto; /* Автоматическая ширина */ flex: 1 1 auto; padding-right: 0; } .info-card-right-section { width: auto; /* Автоматическая ширина */ flex-shrink: 0; } /* Если на мобильных совсем мало места - переносим правую секцию */ @media (max-width: 480px) { .info-card-header { flex-wrap: wrap; } .info-card-right-section { width: 100%; justify-content: flex-start; margin-top: 4px; } } .info-card-tooltip { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) !important; width: calc(100vw - 40px); max-width: 500px; max-height: 70vh; overflow-y: auto; bottom: auto !important; right: auto !important; } .info-card-tooltip::before { display: none; } .info-card { margin: 0.5rem 0 0 0; } .info-card-link { padding: 0.75rem 1rem; } .cards-grid { gap: 15px; } .cards-grid .info-card { margin: 0.5rem 0 0 0; } } @media (max-width: 480px) { .info-card-tooltip { width: calc(100vw - 20px); padding: 14px; } .info-card-left { gap: 8px; } .info-card { margin: 0.4rem 0 0 0; } .cards-grid { gap: 10px; } .cards-grid .info-card { margin: 0.4rem 0 0 0; } } @media (min-width: 1200px) { .info-card-tooltip { width: 450px; } } @media (min-width: 1400px) { .info-card-tooltip { width: 480px; } } </style> <script> document.addEventListener('DOMContentLoaded', function() { var wrappers = document.querySelectorAll('.info-card-tooltip-wrapper'); wrappers.forEach(function(wrapper) { var tooltip = wrapper.querySelector('.info-card-tooltip'); var icon = wrapper.querySelector('.info-card-icon'); if (!tooltip || !icon) return; function adjustTooltipPosition() { if (window.innerWidth <= 768) return; var wrapperRect = wrapper.getBoundingClientRect(); var tooltipRect = tooltip.getBoundingClientRect(); var viewportHeight = window.innerHeight; var spaceAbove = wrapperRect.top; var spaceBelow = viewportHeight - wrapperRect.bottom; var tooltipHeight = tooltipRect.height; if (spaceAbove < tooltipHeight + 20 && spaceBelow >= tooltipHeight + 20) { tooltip.classList.add('tooltip-bottom'); } else { tooltip.classList.remove('tooltip-bottom'); } } wrapper.addEventListener('mouseenter', function() { adjustTooltipPosition(); tooltip.style.opacity = '1'; tooltip.style.visibility = 'visible'; }); wrapper.addEventListener('mouseleave', function() { tooltip.style.opacity = '0'; tooltip.style.visibility = 'hidden'; }); wrapper.addEventListener('click', function(e) { if (window.innerWidth <= 768) { e.preventDefault(); e.stopPropagation(); if (tooltip.style.opacity === '1') { tooltip.style.opacity = '0'; tooltip.style.visibility = 'hidden'; } else { tooltip.style.opacity = '1'; tooltip.style.visibility = 'visible'; } } }); }); var scrollTimer; window.addEventListener('scroll', function() { clearTimeout(scrollTimer); scrollTimer = setTimeout(function() { document.querySelectorAll('.info-card-tooltip-wrapper').forEach(function(wrapper) { var tooltip = wrapper.querySelector('.info-card-tooltip'); if (tooltip && tooltip.style.opacity === '1') { tooltip.classList.remove('tooltip-bottom'); setTimeout(function() { var wrapperRect = wrapper.getBoundingClientRect(); var tooltipRect = tooltip.getBoundingClientRect(); var viewportHeight = window.innerHeight; var spaceAbove = wrapperRect.top; var spaceBelow = viewportHeight - wrapperRect.bottom; if (spaceAbove < tooltipRect.height + 20 && spaceBelow >= tooltipRect.height + 20) { tooltip.classList.add('tooltip-bottom'); } }, 10); } }); }, 50); }); window.addEventListener('resize', function() { document.querySelectorAll('.info-card-tooltip-wrapper').forEach(function(wrapper) { var tooltip = wrapper.querySelector('.info-card-tooltip'); if (tooltip && tooltip.style.opacity === '1') { tooltip.style.opacity = '0'; setTimeout(function() { tooltip.style.opacity = '1'; }, 10); } }); }); document.addEventListener('click', function(e) { if (!e.target.closest('.info-card-tooltip-wrapper')) { document.querySelectorAll('.info-card-tooltip').forEach(function(tooltip) { tooltip.style.opacity = '0'; tooltip.style.visibility = 'hidden'; }); } }); document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { document.querySelectorAll('.info-card-tooltip').forEach(function(tooltip) { tooltip.style.opacity = '0'; tooltip.style.visibility = 'hidden'; }); } }); }); </script> |
Вторник, 2026-03-10, 15:42
# 3
{{/* layouts/shortcodes/cardinfo.html */}}
{{ $link := .Get "link" }} {{ $title := .Get "title" }} {{ $desc := .Get "desc" | default "" }} {{ $date := .Get "date" }} {{ $audience := .Get "audience" }} {{ $tag := .Get "tag" }} {{ $tagtype := .Get "tagtype" | default "default" }} {{ $icon := .Get "icon" }} {{ $iconsize := .Get "iconsize" | default "24px" }} {{ $visible := .Get "visible" | default "true" }} {{ $style := .Get "style" | default "button" }} {{ if ne $visible "false" }} <div class="info-card info-card-style-{{ $style }}"> <a href="{{ $link }}" class="info-card-link" target="_blank" rel="noopener"> <div class="info-card-header"> <div class="info-card-left"> {{ if $icon }} <span class="info-card-title-icon"> {{ if .Site.Data.icons }} {{ $iconData := index .Site.Data.icons $icon }} {{ if $iconData }} {{ $iconSvg := replaceRE "<svg" (printf "<svg width='%s' height='%s'" $iconsize $iconsize) $iconData }} <span class="custom-icon">{{ $iconSvg | safeHTML }}</span> {{ else }} <span class="custom-icon"> <svg width="{{ $iconsize }}" height="{{ $iconsize }}" viewBox="0 0 24 24" fill="currentColor"> <path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/> </svg> </span> {{ end }} {{ else }} <span class="custom-icon"> <svg width="{{ $iconsize }}" height="{{ $iconsize }}" viewBox="0 0 24 24" fill="currentColor"> <path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/> </svg> </span> {{ end }} </span> {{ end }} <span class="info-card-title">{{ $title }}</span> </div> <div class="info-card-right-section"> {{ if $tag }} <span class="info-card-tag info-card-tag-{{ $tagtype }}"> {{ $tag }} </span> {{ end }} {{ if $desc }} <div class="info-card-tooltip-wrapper"> <span class="info-card-icon"> <svg class="info-icon" width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm0 13A6 6 0 1 1 8 2a6 6 0 0 1 0 12z"/> <path d="M7 11h2v2H7zm0-8h2v6H7z"/> </svg> </span> <div class="info-card-tooltip"> <div class="tooltip-description">{{ $desc | safeHTML }}</div> {{ if or $date $audience }} <div class="tooltip-details"> {{ if $date }} <div class="tooltip-row"> <svg class="tooltip-row-icon" width="14" height="14" viewBox="0 0 24 24" fill="currentColor"> <path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z"/> </svg> <span class="tooltip-row-text">Добавлено: {{ $date }}</span> </div> {{ end }} {{ if $audience }} <div class="tooltip-row"> <svg class="tooltip-row-icon" width="14" height="14" viewBox="0 0 24 24" fill="currentColor"> <path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/> </svg> <span class="tooltip-row-text">Для кого: {{ $audience }}</span> </div> {{ end }} </div> {{ end }} </div> </div> {{ end }} </div> </div> </a> </div> {{ end }} <style> /* ===== ОСНОВНЫЕ СТИЛИ ===== */ .info-card { transition: all 0.2s ease; position: relative; box-sizing: border-box; display: inline-block; vertical-align: top; margin: 0.75rem 0 0 0; padding: 0; width: 100%; } .info-card-link { display: block; text-decoration: none; color: #333333; width: 100%; height: 100%; box-sizing: border-box; } .info-card-link:hover { text-decoration: none; } .info-card-header { display: flex; justify-content: space-between; align-items: center; min-height: 24px; width: 100%; gap: 12px; } .info-card-left { display: flex; align-items: center; flex: 1 1 auto; min-width: 0; padding-right: 8px; gap: 10px; } .info-card-title-icon { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; } .info-card-title-icon .custom-icon { display: inline-flex; align-items: center; justify-content: center; line-height: 0; } .info-card-title-icon .custom-icon svg { width: 100%; height: 100%; fill: currentColor; color: #666666; vertical-align: middle; } .info-card-title { font-size: 1rem; font-weight: 600; color: #333333; line-height: 1.4; flex: 1 1 auto; min-width: 30px; word-wrap: break-word; overflow-wrap: break-word; white-space: normal; } .info-card-right-section { display: flex; align-items: center; gap: 8px; flex-shrink: 0; flex-wrap: nowrap; } /* ===== КОНТЕЙНЕР ДЛЯ СЕТКИ ===== */ .cards-grid { display: flex; flex-wrap: wrap; margin: 0; gap: 20px; } .cards-grid .info-card { flex: 0 0 auto; padding: 0; margin: 0.75rem 0 0 0; box-sizing: border-box; } /* 1 колонка - всегда на всю ширину */ .cards-grid.cols-1 .info-card { width: 100%; } /* 2 колонки */ .cards-grid.cols-2 .info-card { width: calc(50% - 10px); } /* 3 колонки */ .cards-grid.cols-3 .info-card { width: calc(33.333% - 13.333px); } /* 4 колонки */ .cards-grid.cols-4 .info-card { width: calc(25% - 15px); } /* Минимальная ширина карточки - принудительный перенос */ @media (max-width: 750px) { .cards-grid.cols-2 .info-card, .cards-grid.cols-3 .info-card, .cards-grid.cols-4 .info-card { width: 100%; /* На всю ширину на мобильных */ } } /* Промежуточные состояния */ @media (max-width: 1100px) and (min-width: 751px) { /* 4 колонки превращаются в 2 */ .cards-grid.cols-4 .info-card { width: calc(50% - 10px); } } @media (max-width: 900px) and (min-width: 751px) { /* 3 колонки превращаются в 2 */ .cards-grid.cols-3 .info-card { width: calc(50% - 10px); } } /* Отступы для первой карточки в ряду */ .cards-grid .info-card:nth-child(1), .cards-grid .info-card:nth-child(2), .cards-grid .info-card:nth-child(3), .cards-grid .info-card:nth-child(4) { margin-top: 0; /* Убираем отступ у первого ряда */ } /* ===== СТИЛЬ: BUTTON (по умолчанию) ===== */ .info-card-style-button { border: 1px solid #e0e0e0; border-radius: 8px; background: #ffffff; } .info-card-style-button:hover { border-color: #4a90e2; box-shadow: 0 2px 8px rgba(74, 144, 226, 0.15); } .info-card-style-button .info-card-link { padding: 1rem 1.25rem; } /* ===== СТИЛЬ: LINK (простая ссылка с отступами) ===== */ .info-card-style-link { border: none; background: transparent; border-radius: 0; } .info-card-style-link .info-card-link { padding: 0.5rem 0.25rem; border-bottom: 1px solid transparent; } .info-card-style-link:hover .info-card-link { border-bottom-color: #4a90e2; } .info-card-style-link .info-card-title { font-weight: 500; } .info-card-style-link .info-card-title-icon .custom-icon svg { color: #4a90e2; opacity: 0.8; } /* ===== СТИЛЬ: MINIMAL (минималистичный) ===== */ .info-card-style-minimal { border: none; background: transparent; } .info-card-style-minimal .info-card-link { padding: 0.3rem 0; } .info-card-style-minimal .info-card-title { font-weight: 400; font-size: 0.95rem; } .info-card-style-minimal .info-card-title-icon .custom-icon svg { color: #888; width: 18px; height: 18px; } .info-card-style-minimal:hover .info-card-title { color: #4a90e2; } /* ===== СТИЛЬ: CARD (карточка с тенью) ===== */ .info-card-style-card { border: none; border-radius: 12px; background: #ffffff; box-shadow: 0 4px 12px rgba(0,0,0,0.08); transition: all 0.3s ease; } .info-card-style-card:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0,0,0,0.12); } .info-card-style-card .info-card-link { padding: 1.25rem 1.5rem; } .info-card-style-card .info-card-title { font-size: 1.1rem; } /* ===== СТИЛЬ: PILL (в виде пилюли) ===== */ .info-card-style-pill { border: 1px solid #e0e0e0; border-radius: 50px; background: #f8f9fa; } .info-card-style-pill:hover { border-color: #4a90e2; background: #ffffff; } .info-card-style-pill .info-card-link { padding: 0.6rem 1.25rem; } .info-card-style-pill .info-card-title { font-weight: 500; font-size: 0.95rem; } /* ===== ТЕГИ ===== */ .info-card-tag { display: inline-block; font-size: 0.75rem; padding: 0.2rem 0.6rem; border-radius: 12px; font-weight: 500; white-space: nowrap; border: 1px solid transparent; line-height: 1.4; flex-shrink: 0; } .info-card-tag-default { background: #f0f0f0; color: #666666; border-color: #ddd; } .info-card-tag-info { background: #e3f2fd; color: #1565c0; border-color: #90caf9; } .info-card-tag-warning { background: #fff3e0; color: #e65100; border-color: #ffcc80; } .info-card-tag-success { background: #e8f5e9; color: #2e7d32; border-color: #a5d6a7; } .info-card-tag-error { background: #ffebee; color: #c62828; border-color: #ef9a9a; } /* ===== ТУЛТИПЫ ===== */ .info-card-tooltip-wrapper { position: relative; display: flex; align-items: center; flex-shrink: 0; } .info-card-icon { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; cursor: help; color: #666666; border-radius: 50%; transition: all 0.15s; flex-shrink: 0; } .info-card-icon:hover { background: #f0f0f0; color: #4a90e2; } .info-icon { width: 16px; height: 16px; } .info-card-tooltip { position: absolute; bottom: calc(100% + 10px); right: 50%; transform: translateX(50%); width: 420px; max-width: 90vw; padding: 18px; background: white; border-radius: 8px; box-shadow: 0 5px 25px rgba(0,0,0,0.15); border: 1px solid #e0e0e0; z-index: 1000; opacity: 0; visibility: hidden; transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); pointer-events: auto; } .info-card-tooltip.tooltip-bottom { bottom: auto; top: calc(100% + 10px); } .info-card-tooltip-wrapper:hover .info-card-tooltip { opacity: 1; visibility: visible; transform: translateX(50%) translateY(-5px); } .info-card-tooltip.tooltip-bottom:hover { transform: translateX(50%) translateY(5px); } .info-card-tooltip::before { content: ''; position: absolute; bottom: -7px; left: 50%; transform: translateX(-50%) rotate(45deg); width: 14px; height: 14px; background: white; border-right: 1px solid #e0e0e0; border-bottom: 1px solid #e0e0e0; z-index: -1; } .info-card-tooltip.tooltip-bottom::before { bottom: auto; top: -7px; transform: translateX(-50%) rotate(-135deg); } .tooltip-description { font-size: 0.92rem; line-height: 1.6; color: #444444; margin-bottom: 14px; max-height: 350px; overflow-y: auto; padding-right: 6px; } .tooltip-description::-webkit-scrollbar { width: 6px; } .tooltip-description::-webkit-scrollbar-track { background: #f5f5f5; border-radius: 3px; } .tooltip-description::-webkit-scrollbar-thumb { background: #b0b0b0; border-radius: 3px; } .tooltip-details { font-size: 0.85rem; color: #666666; border-top: 1px solid #f0f0f0; padding-top: 12px; } .tooltip-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; } .tooltip-row:last-child { margin-bottom: 0; } .tooltip-row-icon { flex-shrink: 0; width: 14px; height: 14px; } .tooltip-row-text { line-height: 1.4; } /* ===== ТЕМНАЯ ТЕМА ===== */ body.dark .info-card, html.dark .info-card { background: #2d3748; border-color: #4a5568; } body.dark .info-card:hover, html.dark .info-card:hover { border-color: #63b3ed; box-shadow: 0 2px 8px rgba(99, 179, 237, 0.2); } body.dark .info-card-link, html.dark .info-card-link { color: #e2e8f0; } body.dark .info-card-title, html.dark .info-card-title { color: #f7fafc; } body.dark .info-card-title-icon .custom-icon svg, html.dark .info-card-title-icon .custom-icon svg { color: #a0aec0; } body.dark .info-card-icon, html.dark .info-card-icon { color: #a0aec0; } body.dark .info-card-icon:hover, html.dark .info-card-icon:hover { background: #4a5568; color: #63b3ed; } body.dark .info-card-tooltip, html.dark .info-card-tooltip { background: #1a202c; border-color: #4a5568; box-shadow: 0 5px 25px rgba(0,0,0,0.5); } body.dark .info-card-tooltip::before, html.dark .info-card-tooltip::before { background: #1a202c; border-color: #4a5568; } body.dark .tooltip-description, html.dark .tooltip-description { color: #e2e8f0; } body.dark .tooltip-details, html.dark .tooltip-details { color: #a0aec0; border-top-color: #4a5568; } body.dark .info-card-tag-default, html.dark .info-card-tag-default { background: #4a5568; color: #cbd5e0; border-color: #718096; } body.dark .info-card-tag-info, html.dark .info-card-tag-info { background: #2c5282; color: #bee3f8; border-color: #3182ce; } body.dark .info-card-tag-warning, html.dark .info-card-tag-warning { background: #975a16; color: #feebc8; border-color: #d69e2e; } body.dark .info-card-tag-success, html.dark .info-card-tag-success { background: #276749; color: #c6f6d5; border-color: #38a169; } body.dark .info-card-tag-error, html.dark .info-card-tag-error { background: #9b2c2c; color: #fed7d7; border-color: #e53e3e; } body.dark .info-card-style-link, html.dark .info-card-style-link, body.dark .info-card-style-minimal, html.dark .info-card-style-minimal { background: transparent; } body.dark .info-card-style-card, html.dark .info-card-style-card { background: #2d3748; box-shadow: 0 4px 12px rgba(0,0,0,0.3); } body.dark .info-card-style-pill, html.dark .info-card-style-pill { background: #2d3748; border-color: #4a5568; } body.dark .info-card-style-pill:hover, html.dark .info-card-style-pill:hover { background: #374151; } /* ===== АДАПТИВНОСТЬ ===== */ @media (max-width: 768px) { .info-card-header { flex-direction: row; flex-wrap: wrap; gap: 8px; } .info-card-left { width: auto; flex: 1 1 auto; padding-right: 0; } .info-card-right-section { width: auto; flex-shrink: 0; } @media (max-width: 480px) { .info-card-header { flex-wrap: wrap; } .info-card-right-section { width: 100%; justify-content: flex-start; margin-top: 4px; } } .info-card-tooltip { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) !important; width: calc(100vw - 40px); max-width: 500px; max-height: 70vh; overflow-y: auto; bottom: auto !important; right: auto !important; } .info-card-tooltip::before { display: none; } .info-card { margin: 0.5rem 0 0 0; } .info-card-link { padding: 0.75rem 1rem; } .cards-grid { gap: 15px; } .cards-grid .info-card { margin: 0.5rem 0 0 0; } /* Убираем отступ у первого ряда на мобильных */ .cards-grid .info-card:nth-child(1) { margin-top: 0; } } @media (max-width: 480px) { .info-card-tooltip { width: calc(100vw - 20px); padding: 14px; } .info-card-left { gap: 8px; } .info-card { margin: 0.4rem 0 0 0; } .cards-grid { gap: 10px; } .cards-grid .info-card { margin: 0.4rem 0 0 0; } .cards-grid .info-card:nth-child(1) { margin-top: 0; } } @media (min-width: 1200px) { .info-card-tooltip { width: 450px; } } @media (min-width: 1400px) { .info-card-tooltip { width: 480px; } } </style> <script> document.addEventListener('DOMContentLoaded', function() { var wrappers = document.querySelectorAll('.info-card-tooltip-wrapper'); wrappers.forEach(function(wrapper) { var tooltip = wrapper.querySelector('.info-card-tooltip'); var icon = wrapper.querySelector('.info-card-icon'); if (!tooltip || !icon) return; function adjustTooltipPosition() { if (window.innerWidth <= 768) return; var wrapperRect = wrapper.getBoundingClientRect(); var tooltipRect = tooltip.getBoundingClientRect(); var viewportHeight = window.innerHeight; var spaceAbove = wrapperRect.top; var spaceBelow = viewportHeight - wrapperRect.bottom; var tooltipHeight = tooltipRect.height; if (spaceAbove < tooltipHeight + 20 && spaceBelow >= tooltipHeight + 20) { tooltip.classList.add('tooltip-bottom'); } else { tooltip.classList.remove('tooltip-bottom'); } } wrapper.addEventListener('mouseenter', function() { adjustTooltipPosition(); tooltip.style.opacity = '1'; tooltip.style.visibility = 'visible'; }); wrapper.addEventListener('mouseleave', function() { tooltip.style.opacity = '0'; tooltip.style.visibility = 'hidden'; }); wrapper.addEventListener('click', function(e) { if (window.innerWidth <= 768) { e.preventDefault(); e.stopPropagation(); if (tooltip.style.opacity === '1') { tooltip.style.opacity = '0'; tooltip.style.visibility = 'hidden'; } else { tooltip.style.opacity = '1'; tooltip.style.visibility = 'visible'; } } }); }); var scrollTimer; window.addEventListener('scroll', function() { clearTimeout(scrollTimer); scrollTimer = setTimeout(function() { document.querySelectorAll('.info-card-tooltip-wrapper').forEach(function(wrapper) { var tooltip = wrapper.querySelector('.info-card-tooltip'); if (tooltip && tooltip.style.opacity === '1') { tooltip.classList.remove('tooltip-bottom'); setTimeout(function() { var wrapperRect = wrapper.getBoundingClientRect(); var tooltipRect = tooltip.getBoundingClientRect(); var viewportHeight = window.innerHeight; var spaceAbove = wrapperRect.top; var spaceBelow = viewportHeight - wrapperRect.bottom; if (spaceAbove < tooltipRect.height + 20 && spaceBelow >= tooltipRect.height + 20) { tooltip.classList.add('tooltip-bottom'); } }, 10); } }); }, 50); }); window.addEventListener('resize', function() { document.querySelectorAll('.info-card-tooltip-wrapper').forEach(function(wrapper) { var tooltip = wrapper.querySelector('.info-card-tooltip'); if (tooltip && tooltip.style.opacity === '1') { tooltip.style.opacity = '0'; setTimeout(function() { tooltip.style.opacity = '1'; }, 10); } }); }); document.addEventListener('click', function(e) { if (!e.target.closest('.info-card-tooltip-wrapper')) { document.querySelectorAll('.info-card-tooltip').forEach(function(tooltip) { tooltip.style.opacity = '0'; tooltip.style.visibility = 'hidden'; }); } }); document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { document.querySelectorAll('.info-card-tooltip').forEach(function(tooltip) { tooltip.style.opacity = '0'; tooltip.style.visibility = 'hidden'; }); } }); }); </script> Добавлено (2026-03-10, 15:53) Добавлено (2026-03-10, 16:03) Добавлено (2026-03-10, 16:11) Добавлено (2026-03-10, 16:32) |
Среда, 2026-03-11, 07:35
# 4
/* ===== КОНТЕЙНЕР ДЛЯ СЕТКИ ===== */
.cards-grid { display: flex; flex-wrap: wrap; margin: 0; gap: 10px; } .cards-grid .info-card { flex: 0 0 auto; padding: 0; margin: 10px 0 0 0; /* Отступ сверху 10px для всех карточек в сетке */ box-sizing: border-box; } /* Убираем отступ у карточек первого ряда */ .cards-grid .info-card:nth-child(-n+4) { margin-top: 0; } /* Для 1 колонки */ .cards-grid.cols-1 .info-card:nth-child(1) { margin-top: 0; } /* Для 2 колонок - первый ряд это 2 карточки */ .cards-grid.cols-2 .info-card:nth-child(-n+2) { margin-top: 0; } /* Для 3 колонок - первый ряд это 3 карточки */ .cards-grid.cols-3 .info-card:nth-child(-n+3) { margin-top: 0; } /* Для 4 колонок - первый ряд это 4 карточки */ .cards-grid.cols-4 .info-card:nth-child(-n+4) { margin-top: 0; } Добавлено (2026-03-11, 07:39) Добавлено (2026-03-11, 07:44) Добавлено (2026-03-11, 07:48) Добавлено (2026-03-11, 09:22) Добавлено (2026-03-11, 09:29) Добавлено (2026-03-11, 09:37) Добавлено (2026-03-11, 09:44) |
Пятница, 2026-04-03, 01:08
# 5
654654564654
Прикрепления:
2933699.zip
(432.7 Kb)
|
Пятница, 2026-04-03, 01:12
# 6
|
Пятница, 2026-04-03, 10:04
# 7
Мпии
Прикрепления:
ddddd_1.noext
(33.2 Kb)
|
Пятница, 2026-04-03, 10:07
# 8
let database = {
tables: {}, tableStats: {}, descriptions: {}, // пользовательские описания searchResults: {}, // результаты поиска по данным {tableName: [{recordIndex, field, value}]} knownTables: { "MR_DBA.ASSAY_METHOD_ASMT": "Метод опробования", "MR_DBA.ASSAY_PLACE_ASPL": "Место отбора пробы", "MR_DBA.ASSAY_TYPE_PO_ASPO": "Тип пробы на ТН", "MR_DBA.ASSAY_TYPE_PROP_ASTR": "Тип пробы", "MR_DBA.COLOR_CLRA": "Возраст (полное название с цветом но без индексов)", "MR_DBA.COLOR_COM_CLRC": "цвет общий", "MR_DBA.CONDITION_DRILL_CNDR": "условие бурения", "MR_DBA.COORDINATE_DT_STDT": "названия эллипсоида системы координат", "MR_DBA.COORDINATE_GR_STGR": "таблица с названиями типов координат", "MR_DBA.COORDINATE_STMC": "таблица с названиями систем координат", "MR_DBA.COORDINATE_ZN_ORZN": "таблица координат с зонами", "MR_DBA.DIAM_BORE_DMBR": "Диаметр бурения/Коронки", "MR_DBA.DRIFTING_METHOD_DRMT": "вид бурения", "MR_DBA.DRIFTING_MODE_DRMD": "Способ проходки", "MR_DBA.DRIFTING_PROC_DRPR": "способ бурения", "MR_DBA.DRIFTING_WAY_DRWY": "способ выноса шлама", "MR_DBA.EMPLOYEE_FIO_EMFI": "также имена сотрудников (видимо для связей)", "MR_DBA.EMPLOYEE_LOGIN_EMLG": "логины", "MR_DBA.HOLE_DESTIN_HLAA": "назначение скважины", "MR_DBA.HOLE_INCLINE_HLIN": "вид скважины", "MR_DBA.METHOD_GWS_GWMT": "метод ГИС (геофизических исследований)", "MR_DBA.METHOD_TIE_ALTIT_MTTA": "метод высотной привязки (для координат)", "MR_DBA.METHOD_TIE_PLANE_MTTP": "метод плановой привязки (для координат)", "MR_DBA.MIN_WEALTH_MNWL": "полезное ископаемое", "MR_DBA.OBJECT_DESTIN_OBDA": "стадия работ на объекте (участке)", "MR_DBA.OUR_DIVISION": "Подразделения (бригады)", "MR_DBA.OUR_EMPLOYEE": "Сотрудники (авторы)", "MR_DBA.POINT_OBSERV_TYPE_PNOT": "Тип точки наблюдения", "MR_DBA.R_INSTALL_END_COL_INEC": "способ оборудования низа буровой колонны", "MR_DBA.R_PERFORATION_PRFR": "способ перфорации фильтра", "MR_DBA.R_TYPE_BORE_TPBR": "тип буровой коронки", "MR_DBA.R_TYPE_FILTER_TPFL": "тип фильтра", "MR_DBA.SPECK_SPCK": "Литология (геологическая порода)", "MR_DBA.SYSTEM_HEIGHT_STMH": "система высот для координат", "MR_DBA.S_EI_EIEI": "единица измерения для координат", "MR_DBA.TYPE_DOCUM_TNG_DCTT": "Тип документирования", "MR_DBA.PO_GWSURVEY_DEV_POGD": "Тип средства измерения", "MR_DBA.TYPE_ROCK_BORE_RBTP": "Категория пород по буримости", "MR_DBA.CLASSIFIER_LITHOLOGY": "Литология (породы) из классификатора", "MR_DBA.CLASSIFIER_AGE": "Возраст/Стратиграфия из классификатора", "MR_DBA.CLASSIFIER_GEOLOGY_BIND": "Геологическая привязка из классификатора", "MR_DBA.CLASSIFIER_PROPERTIES": "Геологические свойства пород из классификатора", "MR_DBA.PO_GWSURVEY_USE_POGU": "Назначение средств измерений", "MR_DBA.CLASSIFIER_OTHER": "Прочие данные классификатора" } }; |
Пятница, 2026-04-03, 10:15
# 9
<!DOCTYPE html>
<html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Геологическая БД - Документирование скважин</title> <style> * { box-sizing: border-box; font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; } body { background: #e9ecef; margin: 0; padding: 20px; } .app-container { max-width: 1400px; margin: 0 auto; } .json-panel { background: white; border-radius: 16px; padding: 15px 20px; margin-bottom: 20px; display: flex; flex-wrap: wrap; align-items: center; gap: 15px; box-shadow: 0 2px 6px rgba(0,0,0,0.1); } .json-panel label { font-weight: 600; background: #2c3e66; color: white; padding: 8px 16px; border-radius: 40px; cursor: pointer; font-size: 14px; } .json-status { font-size: 13px; padding: 5px 12px; border-radius: 40px; background: #e6f4ea; color: #2b7a3e; } .json-status.error { background: #fee2e2; color: #c62828; } .tabs { display: flex; gap: 8px; margin-bottom: 20px; flex-wrap: wrap; } .tab-btn { background: #dee2e6; border: none; padding: 10px 24px; border-radius: 40px; font-weight: 600; cursor: pointer; transition: 0.2s; } .tab-btn.active { background: #1e466e; color: white; } .tab-content { display: none; background: white; border-radius: 24px; padding: 24px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); margin-bottom: 20px; } .tab-content.active { display: block; } .form-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; } .field-group { display: flex; flex-direction: column; gap: 6px; } .field-group label { font-weight: 600; font-size: 13px; color: #1e466e; } input, select { padding: 10px 12px; border: 1px solid #ced4da; border-radius: 12px; font-size: 14px; } input:focus, select:focus { outline: none; border-color: #1e466e; } .combobox-container { position: relative; } .combobox-dropdown { position: absolute; top: 100%; left: 0; right: 0; background: white; border: 1px solid #ccc; border-radius: 12px; max-height: 220px; overflow-y: auto; z-index: 100; display: none; box-shadow: 0 6px 12px rgba(0,0,0,0.15); } .combobox-dropdown.show { display: block; } .combobox-item { padding: 8px 12px; cursor: pointer; font-size: 13px; border-bottom: 1px solid #f0f0f0; } .combobox-item:hover { background: #e9ecef; } .btn-primary { background: #1e466e; color: white; border: none; padding: 8px 18px; border-radius: 40px; font-weight: 600; cursor: pointer; } .card-list { display: flex; flex-direction: column; gap: 12px; margin-top: 12px; } .interval-item, .sample-item { background: #f8f9fc; border-radius: 20px; padding: 14px; border-left: 5px solid #2c7da0; cursor: pointer; transition: 0.1s; } .interval-header, .sample-header { display: flex; justify-content: space-between; font-weight: 700; margin-bottom: 8px; } .compact-data { display: flex; flex-wrap: wrap; gap: 16px; font-size: 13px; color: #2c3e50; } .well-card { background: white; border-radius: 20px; padding: 16px; margin-bottom: 12px; box-shadow: 0 2px 5px rgba(0,0,0,0.08); cursor: pointer; } .progress-bar-container { background: #e9ecef; border-radius: 20px; height: 30px; position: relative; margin: 20px 0 10px; overflow: hidden; } .progress-fill { background: #2c7da0; width: 0%; height: 100%; border-radius: 20px; transition: width 0.2s; } .section-header { display: flex; justify-content: space-between; align-items: center; margin: 24px 0 16px 0; } </style> </head> <body> <div class="app-container"> <div class="json-panel"> <label for="jsonFile">📂 Загрузить JSON базу данных</label> <input type="file" id="jsonFile" accept=".json" style="display:none"> <div class="json-status" id="jsonStatus">Ожидание загрузки JSON...</div> </div> <div class="tabs"> <button class="tab-btn active" data-tab="main">🏠 Мои скважины</button> <button class="tab-btn" data-tab="general">📋 Общая информация</button> <button class="tab-btn" data-tab="intervals">📊 Интервалы</button> <button class="tab-btn" data-tab="sampling">🧪 Опробование</button> </div> <div id="tab-main" class="tab-content active"> <div style="display: flex; justify-content: space-between; margin-bottom: 20px;"> <h2>Список скважин</h2> <button class="btn-primary" id="newWellBtn">➕ Новая скважина</button> </div> <div id="wellsList">Загрузка...</div> </div> <div id="tab-general" class="tab-content"> <h3>Общие сведения о скважине</h3> <div class="form-grid"> <div class="field-group"><label>Объект</label><input type="text" id="objectName"></div> <div class="field-group"><label>Участок</label><input type="text" id="areaName"></div> <div class="field-group"><label>Буровая линия</label><input type="text" id="lineNumber"></div> <div class="field-group"><label>Номер скважины</label><input type="text" id="holeNumber"></div> <div class="field-group"><label>Дата начала бурения</label><input type="date" id="drillStartDate"></div> <div class="field-group"><label>Дата окончания бурения</label><input type="date" id="drillEndDate"></div> <div class="field-group"><label>Буровая бригада</label><div class="combobox-container"><input type="text" id="crewInput" placeholder="Выберите бригаду"><div id="crewDropdown" class="combobox-dropdown"></div></div></div> <div class="field-group"><label>Фактическая глубина (м)</label><input type="number" step="0.1" id="actualDepth"></div> <div class="field-group"><label>Дата начала документирования</label><input type="date" id="docStartDate"></div> <div class="field-group"><label>Дата окончания документирования</label><input type="date" id="docEndDate"></div> <div class="field-group"><label>Документатор</label><div class="combobox-container"><input type="text" id="documentatorInput" placeholder="ФИО"><div id="documentatorDropdown" class="combobox-dropdown"></div></div></div> </div> <div class="progress-bar-container" id="progressContainer" style="display:none;"><div class="progress-fill" id="progressFill"></div></div> <div style="margin-top:20px; text-align:right"><button class="btn-primary" id="saveWellBtn">💾 Сохранить скважину</button></div> </div> <div id="tab-intervals" class="tab-content"> <h3>Геологические интервалы</h3> <div class="form-grid" style="margin-bottom: 16px;"> <div class="field-group"><label>От (м)</label><input type="number" step="0.1" id="intervalFrom"></div> <div class="field-group"><label>До (м)</label><input type="number" step="0.1" id="intervalTo"></div> <div class="field-group"><label>Возраст</label><div class="combobox-container"><input type="text" id="ageInput" placeholder="Возраст"><div id="ageDropdown" class="combobox-dropdown"></div></div></div> <div class="field-group"><label>Литология</label><div class="combobox-container"><input type="text" id="lithologyInput" placeholder="Литология"><div id="lithologyDropdown" class="combobox-dropdown"></div></div></div> <div class="field-group"><label>Категория пород</label><select id="catCategory"><option value="">Категория</option></select></div> <div class="field-group"><label>Описание</label><input type="text" id="intervalDesc"></div> </div> <button class="btn-primary" id="addIntervalBtn">➕ Добавить интервал</button> <div class="section-header"><h4>Список интервалов</h4></div> <div id="intervalsList" class="card-list"></div> </div> <div id="tab-sampling" class="tab-content"> <h3>Пробы (опробование)</h3> <div class="form-grid" style="margin-bottom:16px;"> <div class="field-group"><label>От (м)</label><input type="number" step="0.1" id="sampleFrom"></div> <div class="field-group"><label>До (м)</label><input type="number" step="0.1" id="sampleTo"></div> <div class="field-group"><label>Дата отбора</label><input type="date" id="sampleDate"></div> <div class="field-group"><label>Автор отбора</label><div class="combobox-container"><input type="text" id="sampleAuthorInput" placeholder="Автор"><div id="sampleAuthorDropdown" class="combobox-dropdown"></div></div></div> <div class="field-group"><label>Место отбора</label><div class="combobox-container"><input type="text" id="sampleLocationInput" placeholder="Место"><div id="sampleLocationDropdown" class="combobox-dropdown"></div></div></div> <div class="field-group"><label>Тип пробы</label><div class="combobox-container"><input type="text" id="assayTypeInput" placeholder="Тип пробы"><div id="assayTypeDropdown" class="combobox-dropdown"></div></div></div> <div class="field-group"><label>Документ</label><select id="sampleDocType"><option value="primary">Первичное</option><option value="final">Итоговое</option></select></div> <div class="field-group"><label>Объем</label><input type="text" id="sampleVolume"></div> <div class="field-group"><label>Ед. изм. объема</label><div class="combobox-container"><input type="text" id="volumeUnitInput" placeholder="Ед.изм"><div id="volumeUnitDropdown" class="combobox-dropdown"></div></div></div> <div class="field-group"><label>Вес (кг)</label><input type="number" step="0.01" id="sampleWeight"></div> </div> <button class="btn-primary" id="addSampleBtn">➕ Добавить пробу</button> <div class="section-header"><h4>Список проб</h4></div> <div id="samplesList" class="card-list"></div> </div> </div> <script> // ========== ГЛОБАЛЬНАЯ БАЗА ДАННЫХ ========== let db = { tables: {}, knownTables: { "MR_DBA.OUR_DIVISION": "Буровые бригады", "MR_DBA.OUR_EMPLOYEE": "Сотрудники", "MR_DBA.EMPLOYEE_FIO_EMFI": "ФИО сотрудников", "MR_DBA.CLASSIFIER_AGE": "Возраст (стратиграфия)", "MR_DBA.CLASSIFIER_LITHOLOGY": "Литология", "MR_DBA.SPECK_SPCK": "Литология (породы)", "MR_DBA.TYPE_ROCK_BORE_RBTP": "Категория пород", "MR_DBA.ASSAY_TYPE_PROP_ASTR": "Тип пробы", "MR_DBA.ASSAY_PLACE_ASPL": "Место отбора пробы", "MR_DBA.S_EI_EIEI": "Единицы измерения", "MR_DBA.COLOR_CLRA": "Возраст (цветной классификатор)" } }; let wells = []; let currentWellId = null; let intervals = []; let samples = []; // ========== ЗАГРУЗКА JSON И ПАРСИНГ ТАБЛИЦ ========== document.getElementById('jsonFile').addEventListener('change', function(e) { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(event) { try { const jsonData = JSON.parse(event.target.result); db.tables = jsonData; // Сохраняем все таблицы // Выводим информацию о загруженных таблицах const tableNames = Object.keys(jsonData); document.getElementById('jsonStatus').innerHTML = `✅ Загружено ${tableNames.length} таблиц: ${tableNames.slice(0, 5).join(', ')}${tableNames.length > 5 ? '...' : ''}`; document.getElementById('jsonStatus').classList.remove('error'); // Обновляем все выпадающие списки из загруженных данных loadDictionariesFromDB(); } catch (error) { document.getElementById('jsonStatus').innerHTML = `❌ Ошибка парсинга JSON: ${error.message}`; document.getElementById('jsonStatus').classList.add('error'); console.error(error); } }; reader.readAsText(file); }); // ========== ЗАГРУЗКА СПРАВОЧНИКОВ ИЗ БАЗЫ ========== function loadDictionariesFromDB() { // 1. Буровые бригады из MR_DBA.OUR_DIVISION (поле NAME_SHORT или NAME) if (db.tables["MR_DBA.OUR_DIVISION"]) { const crews = db.tables["MR_DBA.OUR_DIVISION"] .map(item => item.NAME_SHORT || item.NAME || item.NAME_FULL) .filter(v => v); setupCombobox('crew', crews); console.log(`✅ Загружено бригад: ${crews.length}`); } else { console.warn("⚠️ Таблица MR_DBA.OUR_DIVISION не найдена"); setupCombobox('crew', []); } // 2. Сотрудники (документаторы и авторы) из MR_DBA.OUR_EMPLOYEE или MR_DBA.EMPLOYEE_FIO_EMFI let employees = []; if (db.tables["MR_DBA.OUR_EMPLOYEE"]) { employees = db.tables["MR_DBA.OUR_EMPLOYEE"] .map(item => item.FAMILY_EMFI || item.FAMILY ? `${item.FAMILY_EMFI || item.FAMILY} ${item.NAME_EMFI || item.NAME} ${item.PATR_EMFI || ''}`.trim() : item.NAME_SHORT) .filter(v => v && v.length > 0); } if (db.tables["MR_DBA.EMPLOYEE_FIO_EMFI"] && employees.length === 0) { employees = db.tables["MR_DBA.EMPLOYEE_FIO_EMFI"] .map(item => `${item.FAMILY_EMFI || ''} ${item.NAME_EMFI || ''} ${item.PATR_EMFI || ''}`.trim()) .filter(v => v); } setupCombobox('documentator', employees); setupCombobox('sampleAuthor', employees); console.log(`✅ Загружено сотрудников: ${employees.length}`); // 3. Возраст из CLASSIFIER_AGE или COLOR_CLRA let ages = []; if (db.tables["MR_DBA.CLASSIFIER_AGE"]) { ages = db.tables["MR_DBA.CLASSIFIER_AGE"] .map(item => item.NODE_NAME || item.NAME || item.VALUE) .filter(v => v); } else if (db.tables["MR_DBA.COLOR_CLRA"]) { ages = db.tables["MR_DBA.COLOR_CLRA"] .map(item => item.NODE_NAME || item.NAME_FULL || item.NAME) .filter(v => v); } setupCombobox('age', ages); console.log(`✅ Загружено возрастов: ${ages.length}`); // 4. Литология из CLASSIFIER_LITHOLOGY или SPECK_SPCK let lithologies = []; if (db.tables["MR_DBA.CLASSIFIER_LITHOLOGY"]) { lithologies = db.tables["MR_DBA.CLASSIFIER_LITHOLOGY"] .map(item => item.NODE_NAME || item.NAME_SPCK || item.NAME) .filter(v => v); } else if (db.tables["MR_DBA.SPECK_SPCK"]) { lithologies = db.tables["MR_DBA.SPECK_SPCK"] .map(item => item.NAME_SPCK || item.NAME_FULL || item.NODE_NAME) .filter(v => v); } setupCombobox('lithology', lithologies); console.log(`✅ Загружено литологий: ${lithologies.length}`); // 5. Категория пород из TYPE_ROCK_BORE_RBTP if (db.tables["MR_DBA.TYPE_ROCK_BORE_RBTP"]) { const categories = db.tables["MR_DBA.TYPE_ROCK_BORE_RBTP"] .map(item => item.NAME_FULL_RBTP || item.NAME_SHORT || item.NAME) .filter(v => v); const catSelect = document.getElementById('catCategory'); catSelect.innerHTML = '<option value="">Категория пород</option>'; categories.forEach(cat => { catSelect.innerHTML += `<option value="${cat.replace(/"/g, '"')}">${cat}</option>`; }); console.log(`✅ Загружено категорий пород: ${categories.length}`); } // 6. Тип пробы из ASSAY_TYPE_PROP_ASTR let assayTypes = []; if (db.tables["MR_DBA.ASSAY_TYPE_PROP_ASTR"]) { assayTypes = db.tables["MR_DBA.ASSAY_TYPE_PROP_ASTR"] .map(item => { let code = item.NUMBER_PP_ASTR ? `[${item.NUMBER_PP_ASTR}] ` : ''; let name = item.NAME_FULL_ASTR || item.NAME_SHORT || item.NAME; return `${code}${name}`; }) .filter(v => v); } setupCombobox('assayType', assayTypes); console.log(`✅ Загружено типов проб: ${assayTypes.length}`); // 7. Место отбора из ASSAY_PLACE_ASPL let samplePlaces = []; if (db.tables["MR_DBA.ASSAY_PLACE_ASPL"]) { samplePlaces = db.tables["MR_DBA.ASSAY_PLACE_ASPL"] .map(item => item.NAME_SHORT_ASPL || item.NAME_FULL || item.NAME) .filter(v => v); } setupCombobox('sampleLocation', samplePlaces); console.log(`✅ Загружено мест отбора: ${samplePlaces.length}`); // 8. Единицы измерения из S_EI_EIEI let units = []; if (db.tables["MR_DBA.S_EI_EIEI"]) { units = db.tables["MR_DBA.S_EI_EIEI"] .map(item => item.EINAMES_EIEI || item.NAME_SHORT || item.NAME) .filter(v => v); } setupCombobox('volumeUnit', units); console.log(`✅ Загружено единиц измерения: ${units.length}`); } // ========== УНИВЕРСАЛЬНЫЙ КОМБОБОКС ========== function setupCombobox(fieldId, dataArray) { const input = document.getElementById(fieldId + 'Input'); const dropdown = document.getElementById(fieldId + 'Dropdown'); if (!input || !dropdown) return; // Удаляем старые обработчики, чтобы не дублировать const newInput = input.cloneNode(true); input.parentNode.replaceChild(newInput, input); const newDropdown = dropdown.cloneNode(true); dropdown.parentNode.replaceChild(newDropdown, dropdown); const finalInput = document.getElementById(fieldId + 'Input'); const finalDropdown = document.getElementById(fieldId + 'Dropdown'); function filterAndShow(searchText) { const filtered = dataArray.filter(item => item.toLowerCase().includes(searchText.toLowerCase()) ); finalDropdown.innerHTML = filtered.slice(0, 200).map(item => `<div class="combobox-item" onclick="selectComboboxItem('${fieldId}', '${item.replace(/'/g, "\\'").replace(/"/g, '"')}')">${escapeHtml(item)}</div>` ).join(''); if (filtered.length === 0) { finalDropdown.innerHTML = '<div class="combobox-item">Ничего не найдено</div>'; } } finalInput.addEventListener('focus', () => { filterAndShow(finalInput.value); finalDropdown.classList.add('show'); }); finalInput.addEventListener('input', (e) => { filterAndShow(e.target.value); finalDropdown.classList.add('show'); }); document.addEventListener('click', (e) => { if (!finalInput.contains(e.target) && !finalDropdown.contains(e.target)) { finalDropdown.classList.remove('show'); } }); } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } window.selectComboboxItem = function(fieldId, value) { document.getElementById(fieldId + 'Input').value = value; document.getElementById(fieldId + 'Dropdown').classList.remove('show'); }; // ========== РАБОТА СО СКВАЖИНАМИ ========== function loadWells() { const saved = localStorage.getItem('geology_wells_full'); if (saved) { wells = JSON.parse(saved); } else { wells = []; } displayWellsList(); } function saveWellsToLocal() { localStorage.setItem('geology_wells_full', JSON.stringify(wells)); } function displayWellsList() { const container = document.getElementById('wellsList'); if (wells.length === 0) { container.innerHTML = '<div style="padding:20px; text-align:center;">Нет сохранённых скважин. Создайте новую.</div>'; return; } const sorted = [...wells].sort((a, b) => new Date(b.lastModified) - new Date(a.lastModified)); container.innerHTML = sorted.map(well => { const line = well.info?.line || ''; const hole = well.info?.hole || ''; const object = well.info?.object || ''; const area = well.info?.area || ''; const depth = well.info?.actualDepth || '—'; const intervalsCount = well.intervals?.length || 0; const samplesCount = well.samples?.length || 0; return ` <div class="well-card" onclick="editWellById('${well.id}')"> <div class="interval-header"> <span><strong>${line ? line + ' / ' : ''}${hole || 'Без номера'}</strong></span> <span>${new Date(well.lastModified).toLocaleDateString()}</span> </div> <div style="font-size:13px; color:#666;">${object}${area ? ' / ' + area : ''}</div> <div class="compact-data" style="margin-top:8px;"> <span>📊 ${intervalsCount} интервалов</span> <span>🧪 ${samplesCount} проб</span> <span>📏 ${depth} м</span> </div> <div style="margin-top:10px;"> <button class="btn-outline" onclick="event.stopPropagation();editWellById('${well.id}')">✏️ Редактировать</button> <button class="btn-outline" onclick="event.stopPropagation();deleteWell('${well.id}')">🗑️ Удалить</button> </div> </div> `; }).join(''); } window.deleteWell = function(id) { if (confirm('Удалить скважину?')) { wells = wells.filter(w => w.id !== id); saveWellsToLocal(); if (currentWellId === id) clearForm(); displayWellsList(); } }; window.editWellById = function(id) { const well = wells.find(w => w.id === id); if (well) loadWellToForm(well); document.querySelector('.tab-btn[data-tab="general"]').click(); }; function clearForm() { currentWellId = null; intervals = []; samples = []; document.getElementById('objectName').value = ''; document.getElementById('areaName').value = ''; document.getElementById('lineNumber').value = ''; document.getElementById('holeNumber').value = ''; document.getElementById('crewInput').value = ''; document.getElementById('documentatorInput').value = ''; document.getElementById('actualDepth').value = ''; updateIntervalsList(); updateSamplesList(); } function loadWellToForm(well) { currentWellId = well.id; document.getElementById('objectName').value = well.info?.object || ''; document.getElementById('areaName').value = well.info?.area || ''; document.getElementById('lineNumber').value = well.info?.line || ''; document.getElementById('holeNumber').value = well.info?.hole || ''; document.getElementById('drillStartDate').value = well.info?.drillStart || ''; document.getElementById('drillEndDate').value = well.info?.drillEnd || ''; document.getElementById('crewInput').value = well.info?.crew || ''; document.getElementById('actualDepth').value = well.info?.actualDepth || ''; document.getElementById('docStartDate').value = well.info?.docStart || ''; document.getElementById('docEndDate').value = well.info?.docEnd || ''; document.getElementById('documentatorInput').value = well.info?.documentator || ''; intervals = well.intervals ? JSON.parse(JSON.stringify(well.intervals)) : []; samples = well.samples ? JSON.parse(JSON.stringify(well.samples)) : []; updateIntervalsList(); updateSamplesList(); updateProgressBar(); } function getCurrentWellInfo() { return { object: document.getElementById('objectName').value, area: document.getElementById('areaName').value, line: document.getElementById('lineNumber').value, hole: document.getElementById('holeNumber').value, drillStart: document.getElementById('drillStartDate').value, drillEnd: document.getElementById('drillEndDate').value, crew: document.getElementById('crewInput').value, actualDepth: parseFloat(document.getElementById('actualDepth').value) || 0, docStart: document.getElementById('docStartDate').value, docEnd: document.getElementById('docEndDate').value, documentator: document.getElementById('documentatorInput').value }; } function saveCurrentWell() { const info = getCurrentWellInfo(); if (!info.hole && !info.line) { alert('Укажите хотя бы номер скважины или буровую линию'); return; } const wellData = { id: currentWellId || Date.now().toString(), info: info, intervals: intervals, samples: samples, lastModified: new Date().toISOString() }; if (currentWellId) { const index = wells.findIndex(w => w.id === currentWellId); if (index !== -1) wells[index] = wellData; else wells.push(wellData); } else { wells.push(wellData); } saveWellsToLocal(); currentWellId = wellData.id; displayWellsList(); alert('Скважина сохранена'); updateProgressBar(); } // ========== ИНТЕРВАЛЫ ========== function addInterval() { const from = parseFloat(document.getElementById('intervalFrom').value); const to = parseFloat(document.getElementById('intervalTo').value); if (isNaN(from) || isNaN(to) || from >= to) { alert('Введите корректные значения от и до (от < до)'); return; } intervals.push({ from: from, to: to, age: document.getElementById('ageInput').value, lithology: document.getElementById('lithologyInput').value, category: document.getElementById('catCategory').value, desc: document.getElementById('intervalDesc').value }); intervals.sort((a, b) => a.from - b.from); updateIntervalsList(); document.getElementById('intervalFrom').value = to; document.getElementById('intervalTo').value = ''; document.getElementById('intervalDesc').value = ''; updateProgressBar(); } function updateIntervalsList() { const container = document.getElementById('intervalsList'); if (intervals.length === 0) { container.innerHTML = '<div style="padding:20px; text-align:center;">Нет добавленных интервалов</div>'; return; } container.innerHTML = intervals.map((int, idx) => ` <div class="interval-item" onclick="editInterval(${idx})"> <div class="interval-header"> <span>${int.from} - ${int.to} м (${(int.to - int.from).toFixed(1)} м)</span> <span> <span onclick="event.stopPropagation();editInterval(${idx})">✏️</span> <span onclick="event.stopPropagation();deleteInterval(${idx})">✕</span> </span> </div> <div class="compact-data"> <span>📅 ${int.age || '—'}</span> <span>⛰️ ${int.lithology || '—'}</span> <span>📊 ${int.category || '—'}</span> </div> ${int.desc ? `<div>📝 ${int.desc}</div>` : ''} </div> `).join(''); } window.deleteInterval = function(idx) { intervals.splice(idx, 1); updateIntervalsList(); updateProgressBar(); }; window.editInterval = function(idx) { const int = intervals[idx]; document.getElementById('intervalFrom').value = int.from; document.getElementById('intervalTo').value = int.to; document.getElementById('ageInput').value = int.age || ''; document.getElementById('lithologyInput').value = int.lithology || ''; document.getElementById('catCategory').value = int.category || ''; document.getElementById('intervalDesc').value = int.desc || ''; deleteInterval(idx); }; // ========== ПРОБЫ ========== function addSample() { const from = parseFloat(document.getElementById('sampleFrom').value); const to = parseFloat(document.getElementById('sampleTo').value); if (isNaN(from)) { alert('Введите глубину отбора'); return; } samples.push({ from: from, to: isNaN(to) ? from : to, date: document.getElementById('sampleDate').value, author: document.getElementById('sampleAuthorInput').value, location: document.getElementById('sampleLocationInput').value, type: document.getElementById('assayTypeInput').value, docType: document.getElementById('sampleDocType').value, volume: document.getElementById('sampleVolume').value, volumeUnit: document.getElementById('volumeUnitInput').value, weight: parseFloat(document.getElementById('sampleWeight').value) || null }); updateSamplesList(); document.getElementById('sampleFrom').value = ''; document.getElementById('sampleTo').value = ''; document.getElementById('sampleDate').value = ''; document.getElementById('sampleAuthorInput').value = ''; document.getElementById('sampleLocationInput').value = ''; document.getElementById('assayTypeInput').value = ''; document.getElementById('sampleVolume').value = ''; document.getElementById('volumeUnitInput').value = ''; document.getElementById('sampleWeight').value = ''; } function updateSamplesList() { const container = document.getElementById('samplesList'); if (samples.length === 0) { container.innerHTML = '<div style="padding:20px; text-align:center;">Нет добавленных проб</div>'; return; } container.innerHTML = samples.map((s, idx) => ` <div class="interval-item" onclick="editSample(${idx})"> <div class="interval-header"> <span>${s.from}${s.from !== s.to ? ' - ' + s.to + ' м' : ' м'}</span> <span> <span onclick="event.stopPropagation();editSample(${idx})">✏️</span> <span onclick="event.stopPropagation();deleteSample(${idx})">✕</span> </span> </div> <div class="compact-data"> <span>📅 ${s.date || '—'}</span> <span>👤 ${s.author || '—'}</span> <span>📍 ${s.location || '—'}</span> </div> <div>${s.type || '—'} (${s.docType === 'final' ? 'Итоговое' : 'Первичное'})</div> ${s.volume ? `<div>📊 Объем: ${s.volume} ${s.volumeUnit || ''}</div>` : ''} ${s.weight ? `<div>⚖️ Вес: ${s.weight} кг</div>` : ''} </div> `).join(''); } window.deleteSample = function(idx) { samples.splice(idx, 1); updateSamplesList(); }; window.editSample = function(idx) { const s = samples[idx]; document.getElementById('sampleFrom').value = s.from; document.getElementById('sampleTo').value = s.to; document.getElementById('sampleDate').value = s.date || ''; document.getElementById('sampleAuthorInput').value = s.author || ''; document.getElementById('sampleLocationInput').value = s.location || ''; document.getElementById('assayTypeInput').value = s.type || ''; document.getElementById('sampleDocType').value = s.docType || 'primary'; document.getElementById('sampleVolume').value = s.volume || ''; document.getElementById('volumeUnitInput').value = s.volumeUnit || ''; document.getElementById('sampleWeight').value = s.weight || ''; deleteSample(idx); }; function updateProgressBar() { const depth = parseFloat(document.getElementById('actualDepth').value); if (!depth || depth <= 0 || intervals.length === 0) { document.getElementById('progressContainer').style.display = 'none'; return; } const documentedDepth = intervals.reduce((sum, i) => sum + (i.to - i.from), 0); const percent = Math.min(100, Math.round((documentedDepth / depth) * 100)); document.getElementById('progressFill').style.width = percent + '%'; document.getElementById('progressContainer').style.display = 'block'; } // ========== ИНИЦИАЛИЗАЦИЯ ========== document.getElementById('saveWellBtn').addEventListener('click', saveCurrentWell); document.getElementById('addIntervalBtn').addEventListener('click', addInterval); document.getElementById('addSampleBtn').addEventListener('click', addSample); document.getElementById('newWellBtn').addEventListener('click', () => { currentWellId = null; intervals = []; samples = []; clearForm(); document.querySelector('.tab-btn[data-tab="general"]').click(); }); // Табы document.querySelectorAll('.tab-btn').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active')); document.getElementById(`tab-${btn.dataset.tab}`).classList.add('active'); document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); }); }); // Загружаем сохранённые скважины loadWells(); // Показываем подсказку о необходимости загрузить JSON document.getElementById('jsonStatus').innerHTML = '📁 Загрузите JSON файл с таблицами MR_DBA.*'; </script> </body> </html> |
Пятница, 2026-04-03, 12:41
# 11
<!DOCTYPE html>
<html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Геологическая БД - Документирование скважин</title> <style> * { box-sizing: border-box; font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; } body { background: #e9ecef; margin: 0; padding: 20px; } .app-container { max-width: 1400px; margin: 0 auto; } .json-panel { background: white; border-radius: 16px; padding: 15px 20px; margin-bottom: 20px; display: flex; flex-wrap: wrap; align-items: center; gap: 15px; box-shadow: 0 2px 6px rgba(0,0,0,0.1); } .json-panel label { font-weight: 600; background: #2c3e66; color: white; padding: 8px 16px; border-radius: 40px; cursor: pointer; font-size: 14px; } .json-status { font-size: 13px; padding: 5px 12px; border-radius: 40px; background: #e6f4ea; color: #2b7a3e; } .json-status.error { background: #fee2e2; color: #c62828; } .tabs { display: flex; gap: 8px; margin-bottom: 20px; flex-wrap: wrap; } .tab-btn { background: #dee2e6; border: none; padding: 10px 24px; border-radius: 40px; font-weight: 600; cursor: pointer; transition: 0.2s; } .tab-btn.active { background: #1e466e; color: white; } .tab-content { display: none; background: white; border-radius: 24px; padding: 24px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); margin-bottom: 20px; } .tab-content.active { display: block; } .form-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; } .field-group { display: flex; flex-direction: column; gap: 6px; } .field-group label { font-weight: 600; font-size: 13px; color: #1e466e; } input, select { padding: 10px 12px; border: 1px solid #ced4da; border-radius: 12px; font-size: 14px; } input:focus, select:focus { outline: none; border-color: #1e466e; } .combobox-container { position: relative; } .combobox-dropdown { position: absolute; top: 100%; left: 0; right: 0; background: white; border: 1px solid #ccc; border-radius: 12px; max-height: 220px; overflow-y: auto; z-index: 100; display: none; box-shadow: 0 6px 12px rgba(0,0,0,0.15); } .combobox-dropdown.show { display: block; } .combobox-item { padding: 8px 12px; cursor: pointer; font-size: 13px; border-bottom: 1px solid #f0f0f0; } .combobox-item:hover { background: #e9ecef; } .btn-primary { background: #1e466e; color: white; border: none; padding: 8px 18px; border-radius: 40px; font-weight: 600; cursor: pointer; } .card-list { display: flex; flex-direction: column; gap: 12px; margin-top: 12px; } .interval-item { background: #f8f9fc; border-radius: 20px; padding: 14px; border-left: 5px solid #2c7da0; cursor: pointer; transition: 0.1s; } .well-card { background: white; border-radius: 20px; padding: 16px; margin-bottom: 12px; box-shadow: 0 2px 5px rgba(0,0,0,0.08); cursor: pointer; } .progress-bar-container { background: #e9ecef; border-radius: 20px; height: 30px; position: relative; margin: 20px 0 10px; overflow: hidden; } .progress-fill { background: #2c7da0; width: 0%; height: 100%; border-radius: 20px; transition: width 0.2s; } .section-header { display: flex; justify-content: space-between; align-items: center; margin: 24px 0 16px 0; } .compact-data { display: flex; flex-wrap: wrap; gap: 16px; font-size: 13px; color: #2c3e50; } </style> </head> <body> <div class="app-container"> <div class="json-panel"> <label for="jsonFile">📂 Загрузить JSON базу данных</label> <input type="file" id="jsonFile" accept=".json" style="display:none"> <div class="json-status" id="jsonStatus">Ожидание загрузки JSON...</div> </div> <div class="tabs"> <button class="tab-btn active" data-tab="main">🏠 Мои скважины</button> <button class="tab-btn" data-tab="general">📋 Общая информация</button> <button class="tab-btn" data-tab="intervals">📊 Интервалы</button> <button class="tab-btn" data-tab="sampling">🧪 Опробование</button> </div> <div id="tab-main" class="tab-content active"> <div style="display: flex; justify-content: space-between; margin-bottom: 20px;"> <h2>Список скважин</h2> <button class="btn-primary" id="newWellBtn">➕ Новая скважина</button> </div> <div id="wellsList">Загрузка...</div> </div> <div id="tab-general" class="tab-content"> <h3>Общие сведения о скважине</h3> <div class="form-grid"> <div class="field-group"><label>Объект</label><input type="text" id="objectName"></div> <div class="field-group"><label>Участок</label><input type="text" id="areaName"></div> <div class="field-group"><label>Буровая линия</label><input type="text" id="lineNumber"></div> <div class="field-group"><label>Номер скважины</label><input type="text" id="holeNumber"></div> <div class="field-group"><label>Дата начала бурения</label><input type="date" id="drillStartDate"></div> <div class="field-group"><label>Дата окончания бурения</label><input type="date" id="drillEndDate"></div> <div class="field-group"><label>Буровая бригада</label><div class="combobox-container"><input type="text" id="crewInput" placeholder="Выберите бригаду"><div id="crewDropdown" class="combobox-dropdown"></div></div></div> <div class="field-group"><label>Фактическая глубина (м)</label><input type="number" step="0.1" id="actualDepth"></div> <div class="field-group"><label>Дата начала документирования</label><input type="date" id="docStartDate"></div> <div class="field-group"><label>Дата окончания документирования</label><input type="date" id="docEndDate"></div> <div class="field-group"><label>Документатор</label><div class="combobox-container"><input type="text" id="documentatorInput" placeholder="ФИО"><div id="documentatorDropdown" class="combobox-dropdown"></div></div></div> </div> <div class="progress-bar-container" id="progressContainer" style="display:none;"><div class="progress-fill" id="progressFill"></div></div> <div style="margin-top:20px; text-align:right"><button class="btn-primary" id="saveWellBtn">💾 Сохранить скважину</button></div> </div> <div id="tab-intervals" class="tab-content"> <h3>Геологические интервалы</h3> <div class="form-grid" style="margin-bottom: 16px;"> <div class="field-group"><label>От (м)</label><input type="number" step="0.1" id="intervalFrom"></div> <div class="field-group"><label>До (м)</label><input type="number" step="0.1" id="intervalTo"></div> <div class="field-group"><label>Возраст</label><div class="combobox-container"><input type="text" id="ageInput" placeholder="Возраст"><div id="ageDropdown" class="combobox-dropdown"></div></div></div> <div class="field-group"><label>Литология</label><div class="combobox-container"><input type="text" id="lithologyInput" placeholder="Литология"><div id="lithologyDropdown" class="combobox-dropdown"></div></div></div> <div class="field-group"><label>Категория пород</label><select id="catCategory"><option value="">Категория</option></select></div> <div class="field-group"><label>Описание</label><input type="text" id="intervalDesc"></div> </div> <button class="btn-primary" id="addIntervalBtn">➕ Добавить интервал</button> <div class="section-header"><h4>Список интервалов</h4></div> <div id="intervalsList" class="card-list"></div> </div> <div id="tab-sampling" class="tab-content"> <h3>Пробы (опробование)</h3> <div class="form-grid" style="margin-bottom:16px;"> <div class="field-group"><label>От (м)</label><input type="number" step="0.1" id="sampleFrom"></div> <div class="field-group"><label>До (м)</label><input type="number" step="0.1" id="sampleTo"></div> <div class="field-group"><label>Дата отбора</label><input type="date" id="sampleDate"></div> <div class="field-group"><label>Автор отбора</label><div class="combobox-container"><input type="text" id="sampleAuthorInput" placeholder="Автор"><div id="sampleAuthorDropdown" class="combobox-dropdown"></div></div></div> <div class="field-group"><label>Место отбора</label><div class="combobox-container"><input type="text" id="sampleLocationInput" placeholder="Место"><div id="sampleLocationDropdown" class="combobox-dropdown"></div></div></div> <div class="field-group"><label>Тип пробы</label><div class="combobox-container"><input type="text" id="assayTypeInput" placeholder="Тип пробы"><div id="assayTypeDropdown" class="combobox-dropdown"></div></div></div> <div class="field-group"><label>Документ</label><select id="sampleDocType"><option value="primary">Первичное</option><option value="final">Итоговое</option></select></div> <div class="field-group"><label>Объем</label><input type="text" id="sampleVolume"></div> <div class="field-group"><label>Ед. изм. объема</label><div class="combobox-container"><input type="text" id="volumeUnitInput" placeholder="Ед.изм"><div id="volumeUnitDropdown" class="combobox-dropdown"></div></div></div> <div class="field-group"><label>Вес (кг)</label><input type="number" step="0.01" id="sampleWeight"></div> </div> <button class="btn-primary" id="addSampleBtn">➕ Добавить пробу</button> <div class="section-header"><h4>Список проб</h4></div> <div id="samplesList" class="card-list"></div> </div> </div> <script> // ========== БАЗА ДАННЫХ ========== let db = { tables: {} }; // Справочные данные (заполняются из JSON) let dictionaries = { crews: [], // Буровые бригады documentators: [], // Документаторы ages: [], // Возраста lithologies: [], // Литологии assayTypes: [], // Типы проб samplePlaces: [], // Места отбора rockCategories: [], // Категории пород units: [] // Единицы измерения }; // Данные приложения let wells = []; let currentWellId = null; let intervals = []; let samples = []; // ========== ЗАГРУЗКА JSON ========== document.getElementById('jsonFile').addEventListener('change', function(e) { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(event) { try { const jsonData = JSON.parse(event.target.result); db.tables = jsonData; // Загружаем справочники из JSON loadDictionariesFromJSON(jsonData); document.getElementById('jsonStatus').innerHTML = `✅ Загружено ${Object.keys(jsonData).length} таблиц`; document.getElementById('jsonStatus').classList.remove('error'); } catch (error) { document.getElementById('jsonStatus').innerHTML = `❌ Ошибка: ${error.message}`; document.getElementById('jsonStatus').classList.add('error'); } }; reader.readAsText(file); }); // ========== ЗАГРУЗКА СПРАВОЧНИКОВ ИЗ JSON ========== function loadDictionariesFromJSON(jsonData) { // 1. БУРОВЫЕ БРИГАДЫ → поле "Буровая бригада" // Таблица: MR_DBA.OUR_DIVISION, поле: NAME_SHORT if (jsonData['MR_DBA.OUR_DIVISION']) { dictionaries.crews = jsonData['MR_DBA.OUR_DIVISION'] .map(item => item.NAME_SHORT || item.NAME) .filter(v => v); console.log(`✅ Буровых бригад: ${dictionaries.crews.length}`); } // 2. ДОКУМЕНТАТОРЫ и АВТОРЫ → поля "Документатор" и "Автор отбора" // Таблица: MR_DBA.EMPLOYEE_FIO_EMFI, поля: FAMILY_EMFI, NAME_EMFI, PATR_EMFI if (jsonData['MR_DBA.EMPLOYEE_FIO_EMFI']) { dictionaries.documentators = jsonData['MR_DBA.EMPLOYEE_FIO_EMFI'] .map(item => { const fam = item.FAMILY_EMFI || ''; const name = item.NAME_EMFI || ''; const patr = item.PATR_EMFI || ''; return `${fam} ${name} ${patr}`.trim(); }) .filter(v => v); console.log(`✅ Сотрудников: ${dictionaries.documentators.length}`); } // Альтернативная таблица: MR_DBA.OUR_EMPLOYEE, поле: NAME_FULL else if (jsonData['MR_DBA.OUR_EMPLOYEE']) { dictionaries.documentators = jsonData['MR_DBA.OUR_EMPLOYEE'] .map(item => item.NAME_FULL || item.NAME_SHORT) .filter(v => v); console.log(`✅ Сотрудников (OUR_EMPLOYEE): ${dictionaries.documentators.length}`); } // 3. ВОЗРАСТ → поле "Возраст" // Таблица: MR_DBA.CLASSIFIER, фильтр: NODE_ID начинается с 'A01010', поле: NODE_NAME if (jsonData['MR_DBA.CLASSIFIER']) { dictionaries.ages = jsonData['MR_DBA.CLASSIFIER'] .filter(item => item.NODE_ID && item.NODE_ID.startsWith('A01010')) .map(item => item.NODE_NAME) .filter(v => v); console.log(`✅ Возрастов: ${dictionaries.ages.length}`); } // Альтернатива: MR_DBA.COLOR_CLRA else if (jsonData['MR_DBA.COLOR_CLRA']) { dictionaries.ages = jsonData['MR_DBA.COLOR_CLRA'] .map(item => item.NODE_NAME || item.NAME_FULL) .filter(v => v); console.log(`✅ Возрастов (COLOR_CLRA): ${dictionaries.ages.length}`); } // 4. ЛИТОЛОГИЯ → поле "Литология" // Таблица: MR_DBA.SPECK_SPCK, поле: NAME_SPCK if (jsonData['MR_DBA.SPECK_SPCK']) { dictionaries.lithologies = jsonData['MR_DBA.SPECK_SPCK'] .map(item => item.NAME_SPCK) .filter(v => v); console.log(`✅ Литологий: ${dictionaries.lithologies.length}`); } // Альтернатива: MR_DBA.CLASSIFIER_LITHOLOGY else if (jsonData['MR_DBA.CLASSIFIER_LITHOLOGY']) { dictionaries.lithologies = jsonData['MR_DBA.CLASSIFIER_LITHOLOGY'] .map(item => item.NODE_NAME) .filter(v => v); console.log(`✅ Литологий (CLASSIFIER): ${dictionaries.lithologies.length}`); } // 5. ТИПЫ ПРОБ → поле "Тип пробы" // Таблица: MR_DBA.ASSAY_TYPE_PROP_ASTR, поля: NUMBER_PP_ASTR, NAME_FULL_ASTR, ASSAY_TYPE_PROP_ASTR if (jsonData['MR_DBA.ASSAY_TYPE_PROP_ASTR']) { dictionaries.assayTypes = jsonData['MR_DBA.ASSAY_TYPE_PROP_ASTR'] .map(item => { const number = item.NUMBER_PP_ASTR ? `[${item.NUMBER_PP_ASTR}] ` : ''; const name = item.NAME_FULL_ASTR || ''; const type = item.ASSAY_TYPE_PROP_ASTR ? ` (${item.ASSAY_TYPE_PROP_ASTR})` : ''; return number + name + type; }) .filter(v => v); console.log(`✅ Типов проб: ${dictionaries.assayTypes.length}`); } // 6. МЕСТО ОТБОРА → поле "Место отбора" // Таблица: MR_DBA.ASSAY_PLACE_ASPL, поле: NAME_SHORT_ASPL if (jsonData['MR_DBA.ASSAY_PLACE_ASPL']) { dictionaries.samplePlaces = jsonData['MR_DBA.ASSAY_PLACE_ASPL'] .map(item => item.NAME_SHORT_ASPL || item.NAME_FULL) .filter(v => v); console.log(`✅ Мест отбора: ${dictionaries.samplePlaces.length}`); } // 7. КАТЕГОРИЯ ПОРОД → select "Категория" // Таблица: MR_DBA.TYPE_ROCK_BORE_RBTP, поле: NAME_FULL_RBTP if (jsonData['MR_DBA.TYPE_ROCK_BORE_RBTP']) { dictionaries.rockCategories = jsonData['MR_DBA.TYPE_ROCK_BORE_RBTP'] .map(item => item.NAME_FULL_RBTP || item.NAME_SHORT) .filter(v => v); // Обновляем select const catSelect = document.getElementById('catCategory'); catSelect.innerHTML = '<option value="">Категория пород</option>'; dictionaries.rockCategories.forEach(cat => { catSelect.innerHTML += `<option value="${cat.replace(/"/g, '"')}">${cat}</option>`; }); console.log(`✅ Категорий пород: ${dictionaries.rockCategories.length}`); } // 8. ЕДИНИЦЫ ИЗМЕРЕНИЯ → поля "Ед. изм." // Таблица: MR_DBA.S_EI_EIEI, поле: EINAMES_EIEI if (jsonData['MR_DBA.S_EI_EIEI']) { dictionaries.units = jsonData['MR_DBA.S_EI_EIEI'] .map(item => item.EINAMES_EIEI) .filter(v => v); console.log(`✅ Единиц измерения: ${dictionaries.units.length}`); } // Обновляем все выпадающие списки updateAllDropdowns(); } // ========== НАСТРОЙКА КОМБОБОКСОВ ========== function setupCombobox(fieldId, dataArray) { const input = document.getElementById(fieldId + 'Input'); const dropdown = document.getElementById(fieldId + 'Dropdown'); if (!input || !dropdown) return; // Клонируем чтобы убрать старые обработчики const newInput = input.cloneNode(true); input.parentNode.replaceChild(newInput, input); const finalInput = document.getElementById(fieldId + 'Input'); const finalDropdown = document.getElementById(fieldId + 'Dropdown'); function filterAndShow() { const searchText = finalInput.value.toLowerCase(); const filtered = dataArray.filter(item => item && item.toLowerCase().includes(searchText) ).slice(0, 200); finalDropdown.innerHTML = filtered.map(item => `<div class="combobox-item" onclick="selectComboboxItem('${fieldId}', '${item.replace(/'/g, "\\'")}')">${escapeHtml(item)}</div>` ).join(''); if (filtered.length === 0) { finalDropdown.innerHTML = '<div class="combobox-item">Ничего не найдено</div>'; } } finalInput.addEventListener('focus', () => { filterAndShow(); finalDropdown.classList.add('show'); }); finalInput.addEventListener('input', filterAndShow); document.addEventListener('click', (e) => { if (!finalInput.contains(e.target) && !finalDropdown.contains(e.target)) { finalDropdown.classList.remove('show'); } }); } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } window.selectComboboxItem = function(fieldId, value) { document.getElementById(fieldId + 'Input').value = value; document.getElementById(fieldId + 'Dropdown').classList.remove('show'); markUnsavedChanges(); }; function updateAllDropdowns() { setupCombobox('crew', dictionaries.crews); setupCombobox('documentator', dictionaries.documentators); setupCombobox('sampleAuthor', dictionaries.documentators); setupCombobox('age', dictionaries.ages); setupCombobox('lithology', dictionaries.lithologies); setupCombobox('assayType', dictionaries.assayTypes); setupCombobox('sampleLocation', dictionaries.samplePlaces); setupCombobox('volumeUnit', dictionaries.units); } // ========== РАБОТА СО СКВАЖИНАМИ ========== function loadWells() { const saved = localStorage.getItem('geology_wells'); if (saved) { wells = JSON.parse(saved); } else { wells = []; } displayWellsList(); } function saveWellsToLocal() { localStorage.setItem('geology_wells', JSON.stringify(wells)); } function displayWellsList() { const container = document.getElementById('wellsList'); if (wells.length === 0) { container.innerHTML = '<div style="padding:20px; text-align:center;">Нет сохранённых скважин. Создайте новую.</div>'; return; } const sorted = [...wells].sort((a, b) => new Date(b.lastModified) - new Date(a.lastModified)); container.innerHTML = sorted.map(well => { const line = well.info?.line || ''; const hole = well.info?.hole || ''; const object = well.info?.object || ''; const area = well.info?.area || ''; const depth = well.info?.actualDepth || '—'; const intervalsCount = well.intervals?.length || 0; const samplesCount = well.samples?.length || 0; return ` <div class="well-card" onclick="editWellById('${well.id}')"> <div style="display: flex; justify-content: space-between;"> <strong>${line ? line + ' / ' : ''}${hole || 'Без номера'}</strong> <span style="font-size:12px; color:#666;">${new Date(well.lastModified).toLocaleDateString()}</span> </div> <div style="font-size:13px; color:#666;">${object}${area ? ' / ' + area : ''}</div> <div class="compact-data" style="margin-top:8px;"> <span>📊 ${intervalsCount} интервалов</span> <span>🧪 ${samplesCount} проб</span> <span>📏 ${depth} м</span> </div> <div style="margin-top:10px;"> <button class="btn-primary" style="padding:4px 12px; margin-right:8px;" onclick="event.stopPropagation();editWellById('${well.id}')">✏️ Редактировать</button> <button class="btn-primary" style="padding:4px 12px; background:#c62828;" onclick="event.stopPropagation();deleteWell('${well.id}')">🗑️ Удалить</button> </div> </div> `; }).join(''); } window.deleteWell = function(id) { if (confirm('Удалить скважину?')) { wells = wells.filter(w => w.id !== id); saveWellsToLocal(); if (currentWellId === id) clearForm(); displayWellsList(); } }; window.editWellById = function(id) { const well = wells.find(w => w.id === id); if (well) loadWellToForm(well); document.querySelector('.tab-btn[data-tab="general"]').click(); }; function clearForm() { currentWellId = null; intervals = []; samples = []; document.getElementById('objectName').value = ''; document.getElementById('areaName').value = ''; document.getElementById('lineNumber').value = ''; document.getElementById('holeNumber').value = ''; document.getElementById('crewInput').value = ''; document.getElementById('documentatorInput').value = ''; document.getElementById('actualDepth').value = ''; updateIntervalsList(); updateSamplesList(); } function loadWellToForm(well) { currentWellId = well.id; document.getElementById('objectName').value = well.info?.object || ''; document.getElementById('areaName').value = well.info?.area || ''; document.getElementById('lineNumber').value = well.info?.line || ''; document.getElementById('holeNumber').value = well.info?.hole || ''; document.getElementById('drillStartDate').value = well.info?.drillStart || ''; document.getElementById('drillEndDate').value = well.info?.drillEnd || ''; document.getElementById('crewInput').value = well.info?.crew || ''; document.getElementById('actualDepth').value = well.info?.actualDepth || ''; document.getElementById('docStartDate').value = well.info?.docStart || ''; document.getElementById('docEndDate').value = well.info?.docEnd || ''; document.getElementById('documentatorInput').value = well.info?.documentator || ''; intervals = well.intervals ? JSON.parse(JSON.stringify(well.intervals)) : []; samples = well.samples ? JSON.parse(JSON.stringify(well.samples)) : []; updateIntervalsList(); updateSamplesList(); updateProgressBar(); } function getCurrentWellInfo() { return { object: document.getElementById('objectName').value, area: document.getElementById('areaName').value, line: document.getElementById('lineNumber').value, hole: document.getElementById('holeNumber').value, drillStart: document.getElementById('drillStartDate').value, drillEnd: document.getElementById('drillEndDate').value, crew: document.getElementById('crewInput').value, actualDepth: parseFloat(document.getElementById('actualDepth').value) || 0, docStart: document.getElementById('docStartDate').value, docEnd: document.getElementById('docEndDate').value, documentator: document.getElementById('documentatorInput').value }; } function saveCurrentWell() { const info = getCurrentWellInfo(); if (!info.hole && !info.line) { alert('Укажите хотя бы номер скважины или буровую линию'); return; } const wellData = { id: currentWellId || Date.now().toString(), info: info, intervals: intervals, samples: samples, lastModified: new Date().toISOString() }; if (currentWellId) { const index = wells.findIndex(w => w.id === currentWellId); if (index !== -1) wells[index] = wellData; else wells.push(wellData); } else { wells.push(wellData); } saveWellsToLocal(); currentWellId = wellData.id; displayWellsList(); alert('Скважина сохранена'); updateProgressBar(); } // ========== ИНТЕРВАЛЫ ========== function addInterval() { const from = parseFloat(document.getElementById('intervalFrom').value); const to = parseFloat(document.getElementById('intervalTo').value); if (isNaN(from) || isNaN(to) || from >= to) { alert('Введите корректные значения от и до (от < до)'); return; } intervals.push({ from: from, to: to, age: document.getElementById('ageInput').value, lithology: document.getElementById('lithologyInput').value, category: document.getElementById('catCategory').value, desc: document.getElementById('intervalDesc').value }); intervals.sort((a, b) => a.from - b.from); updateIntervalsList(); document.getElementById('intervalFrom').value = to; document.getElementById('intervalTo').value = ''; document.getElementById('intervalDesc').value = ''; updateProgressBar(); } function updateIntervalsList() { const container = document.getElementById('intervalsList'); if (intervals.length === 0) { container.innerHTML = '<div style="padding:20px; text-align:center;">Нет добавленных интервалов</div>'; return; } container.innerHTML = intervals.map((int, idx) => ` <div class="interval-item" onclick="editInterval(${idx})"> <div style="display: flex; justify-content: space-between;"> <span><strong>${int.from} - ${int.to} м</strong> (${(int.to - int.from).toFixed(1)} м)</span> <span> <span style="color:#1e466e; cursor:pointer;" onclick="event.stopPropagation();editInterval(${idx})">✏️</span> <span style="color:#c62828; cursor:pointer;" onclick="event.stopPropagation();deleteInterval(${idx})">✕</span> </span> </div> <div class="compact-data"> <span>📅 ${int.age || '—'}</span> <span>⛰️ ${int.lithology || '—'}</span> <span>📊 ${int.category || '—'}</span> </div> ${int.desc ? `<div>📝 ${int.desc}</div>` : ''} </div> `).join(''); } window.deleteInterval = function(idx) { intervals.splice(idx, 1); updateIntervalsList(); updateProgressBar(); }; window.editInterval = function(idx) { const int = intervals[idx]; document.getElementById('intervalFrom').value = int.from; document.getElementById('intervalTo').value = int.to; document.getElementById('ageInput').value = int.age || ''; document.getElementById('lithologyInput').value = int.lithology || ''; document.getElementById('catCategory').value = int.category || ''; document.getElementById('intervalDesc').value = int.desc || ''; deleteInterval(idx); }; // ========== ПРОБЫ ========== function addSample() { const from = parseFloat(document.getElementById('sampleFrom').value); const to = parseFloat(document.getElementById('sampleTo').value); if (isNaN(from)) { alert('Введите глубину отбора'); return; } samples.push({ from: from, to: isNaN(to) ? from : to, date: document.getElementById('sampleDate').value, author: document.getElementById('sampleAuthorInput').value, location: document.getElementById('sampleLocationInput').value, type: document.getElementById('assayTypeInput').value, docType: document.getElementById('sampleDocType').value, volume: document.getElementById('sampleVolume').value, volumeUnit: document.getElementById('volumeUnitInput').value, weight: parseFloat(document.getElementById('sampleWeight').value) || null }); updateSamplesList(); document.getElementById('sampleFrom').value = ''; document.getElementById('sampleTo').value = ''; document.getElementById('sampleDate').value = ''; document.getElementById('sampleAuthorInput').value = ''; document.getElementById('sampleLocationInput').value = ''; document.getElementById('assayTypeInput').value = ''; document.getElementById('sampleVolume').value = ''; document.getElementById('volumeUnitInput').value = ''; document.getElementById('sampleWeight').value = ''; } function updateSamplesList() { const container = document.getElementById('samplesList'); if (samples.length === 0) { container.innerHTML = '<div style="padding:20px; text-align:center;">Нет добавленных проб</div>'; return; } container.innerHTML = samples.map((s, idx) => ` <div class="interval-item" onclick="editSample(${idx})" style="border-left-color: ${s.docType === 'final' ? '#c62828' : '#2c7da0'}"> <div style="display: flex; justify-content: space-between;"> <span><strong>${s.from}${s.from !== s.to ? ' - ' + s.to + ' м' : ' м'}</strong></span> <span> <span style="color:#1e466e; cursor:pointer;" onclick="event.stopPropagation();editSample(${idx})">✏️</span> <span style="color:#c62828; cursor:pointer;" onclick="event.stopPropagation();deleteSample(${idx})">✕</span> </span> </div> <div class="compact-data"> <span>📅 ${s.date || '—'}</span> <span>👤 ${s.author || '—'}</span> <span>📍 ${s.location || '—'}</span> </div> <div>${s.type || '—'} (${s.docType === 'final' ? 'Итоговое' : 'Первичное'})</div> ${s.volume ? `<div>📊 Объем: ${s.volume} ${s.volumeUnit || ''}</div>` : ''} ${s.weight ? `<div>⚖️ Вес: ${s.weight} кг</div>` : ''} </div> `).join(''); } window.deleteSample = function(idx) { samples.splice(idx, 1); updateSamplesList(); }; window.editSample = function(idx) { const s = samples[idx]; document.getElementById('sampleFrom').value = s.from; document.getElementById('sampleTo').value = s.to; document.getElementById('sampleDate').value = s.date || ''; document.getElementById('sampleAuthorInput').value = s.author || ''; document.getElementById('sampleLocationInput').value = s.location || ''; document.getElementById('assayTypeInput').value = s.type || ''; document.getElementById('sampleDocType').value = s.docType || 'primary'; document.getElementById('sampleVolume').value = s.volume || ''; document.getElementById('volumeUnitInput').value = s.volumeUnit || ''; document.getElementById('sampleWeight').value = s.weight || ''; deleteSample(idx); }; function updateProgressBar() { const depth = parseFloat(document.getElementById('actualDepth').value); if (!depth || depth <= 0 || intervals.length === 0) { document.getElementById('progressContainer').style.display = 'none'; return; } const documentedDepth = intervals.reduce((sum, i) => sum + (i.to - i.from), 0); const percent = Math.min(100, Math.round((documentedDepth / depth) * 100)); document.getElementById('progressFill').style.width = percent + '%'; document.getElementById('progressContainer').style.display = 'block'; } function markUnsavedChanges() {} // ========== ИНИЦИАЛИЗАЦИЯ ========== document.getElementById('saveWellBtn').addEventListener('click', saveCurrentWell); document.getElementById('addIntervalBtn').addEventListener('click', addInterval); document.getElementById('addSampleBtn').addEventListener('click', addSample); document.getElementById('newWellBtn').addEventListener('click', () => { currentWellId = null; intervals = []; samples = []; clearForm(); document.querySelector('.tab-btn[data-tab="general"]').click(); }); // Табы document.querySelectorAll('.tab-btn').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active')); document.getElementById(`tab-${btn.dataset.tab}`).classList.add('active'); document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); }); }); loadWells(); </script> </body> </html> |
Пятница, 2026-04-03, 12:56
# 12
Ррмитт
Прикрепления:
ddddd_2.noext
(41.6 Kb)
|
Пятница, 2026-04-03, 13:15
# 13
Ррпм
Прикрепления:
rrmmito.noext
(41.6 Kb)
|
| |||
| |||


