Веб-приложение диагностики по видео (ФПГ GigaDoc)
- Загрузка/сканирование видео, извлечение кадров, анализ через SDK ФПГ - Понятный диагноз с сохранением в SQLite и share-ссылками - Экспорт PDF/DOCX, скачивание записанного видео - Камерное сканирование на мобильных (canvas + MediaRecorder) - Секреты вынесены в переменные окружения
This commit is contained in:
@@ -0,0 +1,345 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<!-- SEO -->
|
||||
<title>{% if result %}Результат диагностики #{{ result.share_id }} | med.my-dpr.ru{% else %}Результат не найден | med.my-dpr.ru{% endif %}</title>
|
||||
<meta name="description" content="{% if result %}{{ result.summary }}{% else %}Результат анализа не найден.{% endif %}">
|
||||
<meta name="robots" content="index, follow">
|
||||
<meta name="theme-color" content="#0014FC">
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:site_name" content="Диагностика по видео">
|
||||
<meta property="og:title" content="{% if result %}Результат диагностики #{{ result.share_id }} — оценка здоровья по лицу{% else %}Результат не найден{% endif %}">
|
||||
<meta property="og:description" content="{% if result %}{{ result.summary }}{% else %}Результат анализа не найден.{% endif %}">
|
||||
<meta property="og:url" content="https://med.my-dpr.ru/r/{{ result.share_id if result else '' }}">
|
||||
<meta property="og:image" content="https://med.my-dpr.ru/static/og-image.png">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<meta property="og:image:alt" content="Результат диагностики по видео">
|
||||
<meta property="og:locale" content="ru_RU">
|
||||
|
||||
<!-- Twitter Card -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="{% if result %}Результат диагностики #{{ result.share_id }}{% else %}Результат не найден{% endif %}">
|
||||
<meta name="twitter:description" content="{% if result %}{{ result.summary }}{% else %}Результат анализа не найден.{% endif %}">
|
||||
<meta name="twitter:image" content="https://med.my-dpr.ru/static/og-image.png">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🩺</text></svg>">
|
||||
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: linear-gradient(135deg, #061A40 0%, #0B4F9C 50%, #2D9CDB 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
color: #333;
|
||||
}
|
||||
.container { max-width: 900px; margin: 0 auto; }
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
h1 { color: #fff; text-align: center; margin-bottom: 20px; font-size: 28px; }
|
||||
h2 { color: #333; margin-bottom: 15px; font-size: 20px; }
|
||||
.subtitle { color: rgba(255,255,255,0.9); text-align: center; margin-bottom: 25px; font-size: 15px; }
|
||||
|
||||
.result-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
.result-id {
|
||||
background: linear-gradient(135deg, #061A40, #0B4F9C, #2D9CDB);
|
||||
color: #fff;
|
||||
padding: 6px 14px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.result-date { color: #888; font-size: 13px; }
|
||||
|
||||
.download-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.btn-download {
|
||||
flex: 1;
|
||||
min-width: 140px;
|
||||
text-align: center;
|
||||
background: linear-gradient(135deg, #061A40, #0B4F9C, #2D9CDB);
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
.btn-download:hover { filter: brightness(1.15); }
|
||||
.btn-download:active { transform: scale(0.98); }
|
||||
|
||||
.summary-box {
|
||||
background: #f0f4ff;
|
||||
border-left: 4px solid #0014FC;
|
||||
padding: 15px 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.metrics-table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
|
||||
.metrics-table th, .metrics-table td { padding: 10px 15px; text-align: left; border-bottom: 1px solid #eee; }
|
||||
.metrics-table th { background: #f8f9fa; font-weight: 600; color: #555; }
|
||||
.metrics-table tr:hover { background: #fafafa; }
|
||||
.status-badge { display: inline-block; padding: 3px 10px; border-radius: 12px; font-size: 12px; font-weight: 600; }
|
||||
.status-ok { background: #d4edda; color: #155724; }
|
||||
.status-warning { background: #fff3cd; color: #856404; }
|
||||
.status-danger { background: #f8d7da; color: #721c24; }
|
||||
.status-info { background: #d1ecf1; color: #0c5460; }
|
||||
|
||||
.warnings-box {
|
||||
background: #fff3cd;
|
||||
border-left: 4px solid #ffc107;
|
||||
padding: 15px 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.warnings-box h3 { color: #856404; margin-bottom: 10px; font-size: 16px; }
|
||||
.warnings-box ul { padding-left: 20px; }
|
||||
.warnings-box li { margin: 5px 0; color: #856404; }
|
||||
|
||||
.advice-box {
|
||||
background: #d4edda;
|
||||
border-left: 4px solid #28a745;
|
||||
padding: 15px 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.advice-box h3 { color: #155724; margin-bottom: 10px; font-size: 16px; }
|
||||
.advice-box ul { padding-left: 20px; }
|
||||
.advice-box li { margin: 5px 0; color: #155724; }
|
||||
|
||||
.disclaimer {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 12px 15px;
|
||||
font-size: 13px;
|
||||
color: #888;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.error-box {
|
||||
background: #f8d7da;
|
||||
border-left: 4px solid #dc3545;
|
||||
padding: 15px 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
color: #721c24;
|
||||
}
|
||||
.btn {
|
||||
background: linear-gradient(135deg, #061A40, #0B4F9C, #2D9CDB);
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 12px 30px;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
.btn:hover { filter: brightness(1.15); }
|
||||
.btn-secondary {
|
||||
background: #eee;
|
||||
color: #333;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
.btn-secondary:hover { background: #ddd; }
|
||||
.center { text-align: center; }
|
||||
.share-box {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.share-box input {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
}
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(100px);
|
||||
background: #333;
|
||||
color: #fff;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
opacity: 0;
|
||||
transition: all 0.3s;
|
||||
z-index: 1000;
|
||||
}
|
||||
.toast.show { transform: translateX(-50%) translateY(0); opacity: 1; }
|
||||
|
||||
/* ===== Мобильная версия ===== */
|
||||
@media (max-width: 600px) {
|
||||
body { padding: 12px; }
|
||||
h1 { font-size: 22px; margin-bottom: 12px; }
|
||||
.subtitle { font-size: 14px; margin-bottom: 18px; }
|
||||
.card { padding: 18px; border-radius: 14px; margin-bottom: 14px; }
|
||||
h2 { font-size: 18px; }
|
||||
.result-header { flex-direction: column; align-items: flex-start; gap: 8px; }
|
||||
.metrics-table thead { display: none; }
|
||||
.metrics-table, .metrics-table tbody, .metrics-table tr, .metrics-table td { display: block; width: 100%; }
|
||||
.metrics-table tr { border: 1px solid #eee; border-radius: 10px; margin-bottom: 10px; padding: 10px 12px; }
|
||||
.metrics-table td { border: none; padding: 5px 0; }
|
||||
.metrics-table td:first-child { font-weight: 600; color: #555; }
|
||||
.summary-box, .warnings-box, .advice-box { padding: 12px 14px; font-size: 15px; }
|
||||
.btn, .btn-secondary { width: 100%; padding: 14px 20px; font-size: 16px; min-height: 48px; }
|
||||
.share-box { flex-direction: column; align-items: stretch; }
|
||||
.share-box input { width: 100%; min-width: 0; }
|
||||
.download-buttons { flex-direction: column; }
|
||||
.btn-download { width: 100%; min-width: 0; padding: 14px 20px; font-size: 16px; min-height: 48px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🩺 Диагностика по видео</h1>
|
||||
<p class="subtitle">Оценка состояния здоровья по лицу</p>
|
||||
|
||||
{% if error %}
|
||||
<div class="card">
|
||||
<div class="error-box">
|
||||
<strong>Ошибка:</strong> {{ error }}
|
||||
</div>
|
||||
<div class="center">
|
||||
<a href="/" class="btn">🔍 Пройти диагностику</a>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="card">
|
||||
<div class="result-header">
|
||||
<span class="result-id">Результат #{{ result.share_id }}</span>
|
||||
<span class="result-date">📅 {{ result.created_at[:10] }} {{ result.created_at[11:16] }}</span>
|
||||
</div>
|
||||
|
||||
<div class="download-buttons">
|
||||
<a class="btn-download" href="/download/{{ result.share_id }}/pdf">📄 Скачать PDF</a>
|
||||
<a class="btn-download" href="/download/{{ result.share_id }}/docx">📝 Скачать DOCX</a>
|
||||
{% if video_exists %}
|
||||
<a class="btn-download" href="/download/{{ result.share_id }}/video">🎬 Скачать видео</a>
|
||||
{% endif %}
|
||||
<a class="btn-download" href="https://max.ru/miac_dnr_bot" target="_blank" rel="noopener">🏥 Запись на диспанцеризацию</a>
|
||||
</div>
|
||||
|
||||
<div class="summary-box">{{ result.summary }}</div>
|
||||
|
||||
{% if result.warnings %}
|
||||
<div class="warnings-box">
|
||||
<h3>⚠️ Требует внимания</h3>
|
||||
<ul>
|
||||
{% for w in result.warnings %}
|
||||
<li>{{ w }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<h2>Показатели</h2>
|
||||
<table class="metrics-table">
|
||||
<thead>
|
||||
<tr><th>Показатель</th><th>Значение</th><th>Оценка</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for m in result.metrics %}
|
||||
<tr>
|
||||
<td>{{ m.name }}</td>
|
||||
<td>
|
||||
<strong>{{ m.value }}</strong>
|
||||
{% if m.comment %}<br><small style="color:#888">{{ m.comment }}</small>{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<span class="status-badge status-{{ m.status }}">
|
||||
{% if m.status == 'ok' %}В норме
|
||||
{% elif m.status == 'warning' %}Обратить внимание
|
||||
{% elif m.status == 'danger' %}Отклонение
|
||||
{% else %}Информация{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% if result.advice %}
|
||||
<div class="advice-box">
|
||||
<h3>💡 Рекомендации</h3>
|
||||
<ul>
|
||||
{% for a in result.advice %}
|
||||
<li>{{ a }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="share-box">
|
||||
<input type="text" id="shareLink" value="https://med.my-dpr.ru/r/{{ result.share_id }}" readonly>
|
||||
<button class="btn-secondary" onclick="copyLink()">📋 Копировать ссылку</button>
|
||||
</div>
|
||||
|
||||
<div class="disclaimer">⚠️ Результат получен автоматически по анализу видео и не является медицинским диагнозом. Для точной оценки обратитесь к врачу.</div>
|
||||
|
||||
<div class="center" style="margin-top:20px">
|
||||
<a href="/" class="btn">🔍 Пройти диагностику</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast">Ссылка скопирована!</div>
|
||||
|
||||
<script>
|
||||
function copyLink() {
|
||||
const input = document.getElementById('shareLink');
|
||||
input.select();
|
||||
input.setSelectionRange(0, 99999);
|
||||
navigator.clipboard.writeText(input.value).then(() => {
|
||||
const toast = document.getElementById('toast');
|
||||
toast.classList.add('show');
|
||||
setTimeout(() => toast.classList.remove('show'), 2000);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user