MassageRobot_Dobot/UI_next/templates/thermal_analysis.html
2025-05-27 15:46:31 +08:00

2398 lines
71 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, user-scalable=no"
/>
<title>星耀慧眼-智能报告</title>
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script> -->
<script src="{{ url_for('static', filename='js/socketio/socket.io.min.js') }}"></script>
<style>
* {
margin: 0;
padding: 0;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-webkit-tap-highlight-color: transparent;
}
:root {
--primary-gradient: linear-gradient(135deg, #d460f1 0%, #914ac5 100%);
--secondary-gradient: linear-gradient(
135deg,
rgba(212, 96, 241, 0.1) 0%,
rgba(145, 66, 197, 0.1) 100%
);
--text-primary: #333;
--text-secondary: #666;
--border-radius: 12px;
--box-shadow: 0 8px 16px rgba(145, 66, 197, 0.1);
}
body {
font-family: "Segoe UI", -apple-system, BlinkMacSystemFont, sans-serif;
margin: 0;
padding: 20px;
background-color: #f8f9fc;
color: var(--text-primary);
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 30px;
background: white;
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
}
h1 {
font-size: 2.2em;
margin-bottom: 30px;
color: #914ac5;
text-align: center;
font-weight: 600;
}
.button-group {
display: flex;
gap: 15px;
margin-bottom: 30px;
justify-content: center;
}
button {
padding: 12px 25px;
border: none;
border-radius: var(--border-radius);
background: var(--primary-gradient);
color: white;
cursor: pointer;
font-size: 16px;
font-weight: 500;
transition: all 0.3s ease;
box-shadow: 0 4px 8px rgba(145, 66, 197, 0.2);
}
button.cancel-btn {
background: linear-gradient(135deg, #ff6b6b 0%, #ee5253 100%);
box-shadow: 0 4px 8px rgba(238, 82, 83, 0.2);
}
button.cancel-btn:hover {
box-shadow: 0 6px 12px rgba(238, 82, 83, 0.3);
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(145, 66, 197, 0.3);
}
button:disabled {
background: #e0e0e0;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.status-box {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 25px;
border-radius: var(--border-radius);
background: white;
box-shadow: var(--box-shadow);
z-index: 1000;
display: flex;
flex-direction: column;
gap: 10px;
min-width: 250px;
max-width: 300px;
animation: slideIn 0.3s ease;
border-left: 4px solid #914ac5;
}
.status-header {
display: flex;
align-items: center;
gap: 15px;
}
.progress-container {
width: 100%;
height: 4px;
background: var(--secondary-gradient);
border-radius: 2px;
overflow: hidden;
}
.progress-bar {
width: 0%;
height: 100%;
background: var(--primary-gradient);
border-radius: 2px;
transition: width 0.3s ease;
}
.progress-text {
font-size: 12px;
color: var(--text-secondary);
text-align: right;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.spinner {
width: 24px;
height: 24px;
border: 2.5px solid rgba(145, 66, 197, 0.1);
border-top: 2.5px solid #d460f1;
border-right: 2.5px solid #914ac5;
border-radius: 50%;
animation: spin 0.8s cubic-bezier(0.5, 0, 0.5, 1) infinite;
filter: drop-shadow(0 0 2px rgba(145, 66, 197, 0.2));
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.image-container {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
margin: 30px 0;
}
.image-box {
background: white;
border-radius: var(--border-radius);
padding: 15px;
box-shadow: var(--box-shadow);
transition: all 0.3s ease;
cursor: pointer;
overflow: hidden;
position: relative;
}
.image-box:hover {
transform: translateY(-5px);
box-shadow: 0 12px 24px rgba(145, 66, 197, 0.15);
}
.image-box h3 {
margin: 0 0 15px 0;
color: #914ac5;
font-size: 16px;
font-weight: 600;
text-align: center;
}
.image-box img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 8px;
background: var(--secondary-gradient);
}
.retake-btn {
position: absolute;
bottom: 25px;
left: 50%;
transform: translateX(-50%);
background: linear-gradient(
135deg,
rgba(220, 110, 250, 0.95) 0%,
rgba(145, 74, 197, 0.95) 100%
);
color: white;
border: none;
border-radius: 24px;
padding: 10px 20px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 10;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(145, 74, 197, 0.3);
font-size: 14px;
font-weight: 500;
letter-spacing: 0.5px;
animation: float 2s ease-in-out infinite;
}
@keyframes float {
0% {
transform: translateX(-50%) translateY(0px);
}
50% {
transform: translateX(-50%) translateY(-5px);
}
100% {
transform: translateX(-50%) translateY(0px);
}
}
.retake-btn:hover {
background: linear-gradient(
135deg,
rgba(230, 130, 255, 1) 0%,
rgba(170, 90, 220, 1) 100%
);
transform: translateX(-50%) translateY(-2px);
box-shadow: 0 6px 16px rgba(145, 74, 197, 0.45);
}
.retake-btn svg {
margin-right: 8px;
}
.placeholder {
height: 220px;
background: var(--secondary-gradient);
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #914ac5;
}
.placeholder img {
width: 60px;
height: 60px;
margin-bottom: 15px;
opacity: 0.8;
background: transparent;
}
.placeholder-text {
font-size: 14px;
color: var(--text-secondary);
}
.summary-container {
background: white;
border-radius: var(--border-radius);
padding: 25px;
margin-top: 30px;
box-shadow: var(--box-shadow);
}
.summary-container h3 {
color: #914ac5;
margin: 0 0 20px 0;
font-size: 1.2em;
font-weight: 600;
}
#summary-content {
line-height: 1.6;
font-size: 16px;
}
#summary-content p {
margin: 0.8em 0;
}
#summary-content h1,
#summary-content h2,
#summary-content h3,
#summary-content h4 {
color: #914ac5;
margin: 1em 0 0.5em 0;
}
#summary-content ul,
#summary-content ol {
padding-left: 1.5em;
margin: 0.5em 0;
}
#summary-content li {
margin: 0.3em 0;
}
#summary-content code {
background: var(--secondary-gradient);
padding: 0.2em 0.4em;
border-radius: 4px;
font-family: "Courier New", Courier, monospace;
font-size: 0.9em;
}
#summary-content pre {
background: var(--secondary-gradient);
padding: 1em;
border-radius: 8px;
overflow-x: auto;
margin: 1em 0;
}
#summary-content pre code {
background: none;
padding: 0;
}
#summary-content blockquote {
border-left: 4px solid #914ac5;
margin: 1em 0;
padding-left: 1em;
color: var(--text-secondary);
}
#summary-content table {
border-collapse: collapse;
width: 100%;
margin: 1em 0;
}
#summary-content th,
#summary-content td {
border: 1px solid rgba(145, 74, 197, 0.2);
padding: 0.5em;
}
#summary-content th {
background: var(--secondary-gradient);
color: #914ac5;
}
.typing-cursor {
display: inline-block;
width: 4px; /* 增加宽度从2px到4px */
height: 1.2em;
background: #914ac5;
margin-left: 2px;
vertical-align: middle;
animation: blink 0.6s step-end infinite; /* 加快闪烁频率 */
}
@keyframes blink {
from,
to {
opacity: 0.2; /* 增加最低透明度,使光标在闪烁时也能看到 */
}
50% {
opacity: 1;
}
}
.typing {
border-right: 2px solid #914ac5;
animation: blink 0.75s step-end infinite;
padding-right: 4px;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.85);
z-index: 2000;
backdrop-filter: blur(5px);
touch-action: none; /* 防止在触摸屏上模态框手势影响整个页面 */
overflow: hidden;
}
.modal-content-wrapper {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
height: 80vh;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
touch-action: none; /* 防止触摸事件传递 */
}
.modal-content {
max-width: 100%;
max-height: 100%;
object-fit: contain;
transform-origin: center center;
transition: transform 0.1s ease;
touch-action: none; /* 使触摸事件只应用于图片 */
user-select: none; /* 防止选中图片 */
-webkit-user-drag: none; /* 防止拖拽操作自动拖拽图片 */
}
.modal-loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 40px;
height: 40px;
border: 3px solid rgba(255, 255, 255, 0.2);
border-top: 3px solid white;
border-radius: 50%;
animation: modal-spin 1s linear infinite;
display: none;
}
@keyframes modal-spin {
0% {
transform: translate(-50%, -50%) rotate(0deg);
}
100% {
transform: translate(-50%, -50%) rotate(360deg);
}
}
.zoom-indicator {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0, 0, 0, 0.6);
color: white;
padding: 5px 10px;
border-radius: 15px;
font-size: 14px;
opacity: 0;
transition: opacity 0.3s ease;
}
.modal-close {
position: absolute;
top: 20px;
right: 30px;
color: white;
font-size: 35px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
z-index: 2001;
}
.modal-close:hover {
color: #d460f1;
}
@media (max-width: 1200px) {
.image-container {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.image-container {
grid-template-columns: 1fr;
}
.container {
padding: 20px;
}
.button-group {
flex-direction: column;
}
.status-box {
left: 20px;
right: 20px;
max-width: none;
}
.retake-btn {
bottom: 15px;
padding: 8px 16px;
border-radius: 20px;
font-size: 13px;
}
}
#back-home-button {
position: fixed;
top: 40px;
left: 40px;
background: var(--primary-gradient);
color: #fff;
padding: 12px;
display: flex;
align-items: center;
justify-content: center;
}
#back-home-button img {
width: 24px;
height: 24px;
}
.reports-container {
background: white;
border-radius: var(--border-radius);
padding: 20px;
box-shadow: var(--box-shadow);
}
.reports-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.reports-header h2 {
color: #914ac5;
margin: 0;
font-size: 1.5em;
}
.reports-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
max-height: 396px;
overflow-y: auto;
padding-right: 10px;
}
.report-card {
background: var(--secondary-gradient);
border-radius: var(--border-radius);
padding: 15px;
cursor: pointer;
transition: all 0.3s ease;
border: 1px solid rgba(145, 74, 197, 0.1);
}
.report-card:hover {
transform: translateY(-2px);
box-shadow: var(--box-shadow);
border-color: rgba(145, 74, 197, 0.3);
}
.report-thumbnail {
width: 100%;
object-fit: cover;
border-radius: 8px;
margin-bottom: 10px;
}
.report-info {
color: var(--text-primary);
}
.report-timestamp {
font-size: 0.9em;
color: var(--text-secondary);
margin-bottom: 5px;
}
.report-summary {
font-size: 0.9em;
color: var(--text-secondary);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.empty-reports {
text-align: center;
padding: 40px;
color: var(--text-secondary);
}
.empty-reports i {
font-size: 48px;
margin-bottom: 15px;
color: #914ac5;
opacity: 0.5;
}
/* 修改滚动条样式 */
.reports-grid::-webkit-scrollbar {
width: 8px;
}
.reports-grid::-webkit-scrollbar-track {
background: var(--secondary-gradient);
border-radius: 4px;
}
.reports-grid::-webkit-scrollbar-thumb {
background: var(--primary-gradient);
border-radius: 4px;
}
.reports-grid::-webkit-scrollbar-thumb:hover {
background: linear-gradient(135deg, #c44de3 0%, #833ab4 100%);
}
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
}
.page-header .left-section {
display: flex;
align-items: center;
}
.back-button,
.analyze-button {
padding: 12px 25px;
border-radius: var(--border-radius);
cursor: pointer;
font-size: 16px;
font-weight: 500;
transition: all 0.3s ease;
}
.back-button {
background: white;
padding: 10px 25px;
border: 2px solid #914ac5;
color: #914ac5;
display: flex;
align-items: center;
box-shadow: none;
box-sizing: border-box;
}
.back-button:hover {
transform: translateY(-2px);
background: rgba(145, 74, 197, 0.05);
}
.back-button img {
width: 16px;
height: 16px;
margin-right: 8px;
}
.back-button.disabled {
pointer-events: none;
opacity: 0.5;
cursor: not-allowed;
}
/* 分析按钮样式 */
.analyze-button {
background: var(--primary-gradient);
border: none;
color: white;
box-shadow: 0 4px 8px rgba(145, 66, 197, 0.2);
}
.analyze-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(145, 66, 197, 0.3);
}
.analyze-button:disabled {
background: #e0e0e0;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.analyze-button.cancel {
background: linear-gradient(135deg, #ff6b6b 0%, #ee5253 100%);
box-shadow: 0 4px 8px rgba(238, 82, 83, 0.2);
margin-left: 10px;
}
.analyze-button.cancel:hover {
box-shadow: 0 6px 12px rgba(238, 82, 83, 0.3);
}
.loading-reports {
text-align: center;
padding: 40px;
color: var(--text-secondary);
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
}
.loading-reports .spinner {
width: 40px;
height: 40px;
}
/* 弹窗的背景遮罩 */
.popup-modal {
display: none;
/* 默认隐藏 */
position: fixed;
z-index: 1000;
/* 确保弹窗在最顶层 */
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
/* 背景透明 */
justify-content: center;
align-items: center;
}
/* 弹窗的内容 */
.popup-content {
background-color: rgba(255, 255, 255, 0.7);
/* 背景透明度 */
backdrop-filter: blur(5px);
/* 背景模糊效果 */
padding: 20px;
border-radius: 15px;
text-align: center;
width: 300px;
box-shadow: 0px 0px 24px rgba(255, 255, 255, 0.65);
/* 与其他元素一致的阴影效果 */
}
#popup-message {
padding: 20px 0;
}
/* 按钮样式 */
#popup-buttons button {
margin: 5px;
padding: 10px 25px;
border: none;
border-radius: 10px;
background: linear-gradient(
to right bottom,
rgb(212, 96, 241),
rgba(145, 66, 197, 0.5)
);
/* 紫色渐变 */
color: white;
font-size: 16px;
cursor: pointer;
transition: background 0.3s ease;
}
#popup-buttons button:active {
background: linear-gradient(
to right bottom,
rgba(145, 66, 197, 1),
rgb(212, 96, 241)
);
/* 悬停时加深渐变 */
}
</style>
<!-- 添加图标库 -->
<!-- <link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"
/> -->
<!-- <link
rel="stylesheet"
href="{{ url_for('static', filename='css/font-awesome.min.css') }}"
/> -->
<!-- 添加 Markdown 渲染库 -->
<script src="{{ url_for('static', filename='js/marked.min.js') }}"></script>
</head>
<body>
<div class="container">
<h1>星耀慧眼-智能报告</h1>
<button class="back-home-button" id="back-home-button">
<img
src="{{ url_for('static', filename='images/common/首页.svg') }}"
alt=""
/>
</button>
<!-- 报告列表页面 -->
<div class="reports-container" id="reports-page">
<div class="reports-header">
<h2>历史报告</h2>
<button id="new-report-btn">新建报告</button>
</div>
<div class="reports-grid" id="reports-grid">
<div class="loading-reports" id="reports-loading">
<div class="spinner"></div>
<p>正在加载历史报告...</p>
</div>
</div>
</div>
<!-- 新建报告页面 -->
<div id="new-report-page" style="display: none">
<div class="page-header">
<div class="left-section">
<button class="back-button" id="new-report-back-btn">
<img
src="{{ url_for('static', filename='images/thermal/back.png') }}"
alt=""
/>
返回列表
</button>
</div>
<div class="right-section">
<button id="analyze-btn" class="analyze-button" disabled>
开始分析
</button>
<button
id="cancel-btn"
class="analyze-button cancel"
style="display: none"
>
取消分析
</button>
</div>
</div>
<div class="image-container">
<div class="image-box" id="original-image-box">
<div class="placeholder" id="thermal-cam-placeholder">
<!-- <i class="fas fa-camera"></i> -->
<img
src="{{ url_for('static', filename='images/thermal/photo.svg') }}"
alt=""
/>
<div class="placeholder-text">点击拍照</div>
</div>
<button
class="retake-btn"
id="retake-btn"
style="display: none"
title="点击重新拍照"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M20 5H16.83L15 2H9L7.17 5H4C2.9 5 2 5.9 2 7V19C2 20.1 2.9 21 4 21H20C21.1 21 22 20.1 22 19V7C22 5.9 21.1 5 20 5Z"
stroke="white"
stroke-width="1.5"
fill="none"
/>
<circle
cx="12"
cy="13"
r="4"
stroke="white"
stroke-width="1.5"
fill="none"
/>
<path
d="M18 9L20 7M20 9L18 7"
stroke="white"
stroke-width="1.5"
stroke-linecap="round"
/>
</svg>
重新拍照
</button>
<img
id="original-image"
src=""
alt="原始热成像图"
style="display: none"
/>
</div>
<div class="image-box" id="heatmap-0-box">
<div class="placeholder">
<!-- <i class="fas fa-temperature-high"></i> -->
<img
src="{{ url_for('static', filename='images/thermal/hot.svg') }}"
alt=""
/>
<div class="placeholder-text">等待生成热图</div>
</div>
<img id="heatmap-0" src="" alt="基准热图" style="display: none" />
</div>
<div class="image-box" id="heatmap-1-box">
<div class="placeholder">
<img
src="{{ url_for('static', filename='images/thermal/hot2.svg') }}"
alt=""
/>
<div class="placeholder-text">等待生成热图</div>
</div>
<img id="heatmap-1" src="" alt="对比热图 1" style="display: none" />
</div>
<div class="image-box" id="heatmap-2-box">
<div class="placeholder">
<img
src="{{ url_for('static', filename='images/thermal/hot2.svg') }}"
alt=""
/>
<div class="placeholder-text">等待生成热图</div>
</div>
<img id="heatmap-2" src="" alt="对比热图 2" style="display: none" />
</div>
</div>
</div>
<!-- 查看报告页面 -->
<div id="view-report-page" style="display: none">
<div class="page-header">
<div class="left-section">
<button class="back-button" id="view-report-back-btn">
<img
src="{{ url_for('static', filename='images/thermal/back.png') }}"
alt=""
/>
返回列表
</button>
</div>
</div>
<div class="image-container">
<div class="image-box" id="view-original-image-box">
<div class="placeholder" id="view-thermal-cam-placeholder">
<img
src="{{ url_for('static', filename='images/thermal/photo.svg') }}"
alt=""
/>
<div class="placeholder-text">点击拍照</div>
</div>
<button
class="retake-btn"
id="view-retake-btn"
style="display: none"
title="点击重新拍照"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M20 5H16.83L15 2H9L7.17 5H4C2.9 5 2 5.9 2 7V19C2 20.1 2.9 21 4 21H20C21.1 21 22 20.1 22 19V7C22 5.9 21.1 5 20 5Z"
stroke="white"
stroke-width="1.5"
fill="none"
/>
<circle
cx="12"
cy="13"
r="4"
stroke="white"
stroke-width="1.5"
fill="none"
/>
<path
d="M18 9L20 7M20 9L18 7"
stroke="white"
stroke-width="1.5"
stroke-linecap="round"
/>
</svg>
重新拍照
</button>
<img
id="view-original-image"
src=""
alt="原始热成像图"
style="display: none"
/>
</div>
<div class="image-box" id="view-heatmap-0-box">
<div class="placeholder">
<img
src="{{ url_for('static', filename='images/thermal/hot.svg') }}"
alt=""
/>
<div class="placeholder-text">等待加载热图</div>
</div>
<img
id="view-heatmap-0"
src=""
alt="基准热图"
style="display: none"
/>
</div>
<div class="image-box" id="view-heatmap-1-box">
<div class="placeholder">
<img
src="{{ url_for('static', filename='images/thermal/hot2.svg') }}"
alt=""
/>
<div class="placeholder-text">等待加载热图</div>
</div>
<img
id="view-heatmap-1"
src=""
alt="对比热图 1"
style="display: none"
/>
</div>
<div class="image-box" id="view-heatmap-2-box">
<div class="placeholder">
<img
src="{{ url_for('static', filename='images/thermal/hot2.svg') }}"
alt=""
/>
<div class="placeholder-text">等待加载热图</div>
</div>
<img
id="view-heatmap-2"
src=""
alt="对比热图 2"
style="display: none"
/>
</div>
</div>
<div
class="summary-container"
id="view-summary-container"
style="display: none"
>
<h3>分析报告</h3>
<div id="view-summary-content"></div>
</div>
</div>
<div class="status-box" id="status-box" style="display: none">
<div class="status-header">
<div class="spinner"></div>
<p id="status-message"></p>
</div>
<div
class="progress-container"
id="progress-container"
style="display: none"
>
<div class="progress-bar" id="progress-bar"></div>
</div>
<div class="progress-text" id="progress-text"></div>
</div>
<div
class="summary-container"
id="summary-container"
style="display: none"
>
<h3>分析报告</h3>
<div id="summary-content"></div>
</div>
</div>
<!-- 模态框 -->
<div id="imageModal" class="modal">
<span class="modal-close" onclick="hideModal()">&times;</span>
<div class="modal-content-wrapper">
<img class="modal-content" id="modalImage" />
<div class="modal-loading" id="modalLoading"></div>
<div class="zoom-indicator">
<span id="zoom-indicator-text">1.00x</span>
</div>
</div>
</div>
<!-- Popup Modal -->
<div id="popup-modal" class="popup-modal">
<div class="popup-content">
<p id="popup-message">这里是消息内容</p>
<div id="popup-buttons">
<button
id="confirm-btn"
onclick="confirmAction()"
i18n="popup.confirm_btn"
>
确认
</button>
<button
id="cancel-btn"
onclick="cancelAction()"
i18n="popup.cancel_btn"
>
取消
</button>
</div>
</div>
</div>
<script>
// 启用调试信息
const DEBUG = true;
function debug(msg, data) {
if (DEBUG) {
console.log("DEBUG:", msg, data || "");
}
}
debug("页面加载完成");
const socket = io();
let isGeneratingSummary = false;
// 添加分析状态跟踪变量
let isAnalysisInProgress = false;
// 获取DOM元素
const analyzeBtn = document.getElementById("analyze-btn");
const cancelBtn = document.getElementById("cancel-btn");
const statusBox = document.getElementById("status-box");
const statusMessage = document.getElementById("status-message");
const summaryContainer = document.getElementById("summary-container");
const summaryContent = document.getElementById("summary-content");
const newReportBtn = document.getElementById("new-report-btn");
const reportsGrid = document.getElementById("reports-grid");
const buttonGroup = document.querySelector(".button-group");
const thermalCamPhoto = document.getElementById(
"thermal-cam-placeholder"
);
const viewThermalCamPhoto = document.getElementById(
"view-thermal-cam-placeholder"
);
const retakeBtn = document.getElementById("retake-btn");
const viewRetakeBtn = document.getElementById("view-retake-btn");
const backHomeBtn = document.getElementById("back-home-button");
let currentTaskId = null;
backHomeBtn.addEventListener("click", function () {
if (isAnalysisInProgress) {
// 如果正在分析,显示确认弹窗
showPopup(
"当前正在进行热成像分析,返回首页将取消分析。<br>确定要返回首页吗?"
).then(async (confirmed) => {
if (confirmed) {
// 用户确认返回,先取消分析
if (currentTaskId) {
try {
const response = await fetch(
`/cancel_analysis/${currentTaskId}`,
{
method: "POST",
}
);
const data = await response.json();
debug("取消分析响应", data);
} catch (error) {
debug("取消分析请求错误", error);
}
}
// 返回首页
window.location.href = "/home";
}
// 如果用户取消,什么都不做,继续留在当前页面
});
} else {
// 如果没有正在进行的分析,直接返回首页
window.location.href = "/home";
}
});
// 重置页面所有内容
function resetPageContent() {
debug("重置页面内容");
// 重置按钮状态
analyzeBtn.disabled = true;
analyzeBtn.style.display = "block"; // 确保开始分析按钮可见
cancelBtn.style.display = "none";
// 重置状态框
statusBox.style.display = "none";
statusMessage.textContent = "";
// 重置总结内容
summaryContainer.style.display = "none";
summaryContent.innerHTML = "";
summaryContent.removeAttribute("data-raw-text");
// 重置所有图片框
const imageBoxes = [
"original-image",
"heatmap-0",
"heatmap-1",
"heatmap-2",
];
imageBoxes.forEach((imgId) => {
const box = document.getElementById(`${imgId}-box`);
const img = document.getElementById(imgId);
const placeholder = box.querySelector(".placeholder");
if (img) {
img.src = "";
img.style.display = "none";
}
if (placeholder) {
placeholder.style.display = "flex";
}
// 移除点击事件
box.onclick = null;
});
// 隐藏重拍按钮
if (retakeBtn) retakeBtn.style.display = "none";
if (viewRetakeBtn) viewRetakeBtn.style.display = "none";
// 重置任务ID
currentTaskId = null;
isGeneratingSummary = false;
}
// 给热成像相机区域添加点击事件
thermalCamPhoto.addEventListener("click", function () {
openThermalCam();
});
viewThermalCamPhoto.addEventListener("click", function () {
openThermalCam();
});
// 给重拍按钮添加点击事件
retakeBtn.addEventListener("click", function (e) {
e.stopPropagation(); // 阻止事件冒泡,避免触发图片框的点击事件
openThermalCam();
});
viewRetakeBtn.addEventListener("click", function (e) {
e.stopPropagation(); // 阻止事件冒泡,避免触发图片框的点击事件
openThermalCam();
});
function openThermalCam() {
debug("点击拍照按钮");
// 重置页面内容
resetPageContent();
if (typeof AndroidInterface !== "undefined") {
AndroidInterface.sendMessageToAndroid("Open Thermal:hik");
debug("发送Android消息: Open Thermal");
} else {
debug("AndroidInterface未定义仅作测试");
}
}
// Socket.io 连接状态监听
socket.on("connect", function () {
debug("Socket.io 已连接");
});
socket.on("disconnect", function () {
debug("Socket.io 已断开连接");
});
socket.on("connect_error", function (error) {
debug("Socket.io 连接错误", error);
});
// 监听报告列表更新事件
socket.on("reports_update", function (data) {
debug("收到报告列表更新", data);
const reportsGrid = document.getElementById("reports-grid");
if (data.reports.length === 0) {
reportsGrid.innerHTML = `
<div class="empty-reports">
<img src="{{ url_for('static', filename='images/thermal/empty.svg') }}" alt="暂无报告">
<p>暂无历史报告</p>
</div>
`;
} else {
// 创建一个文档片段来存储所有报告卡片
const fragment = document.createDocumentFragment();
data.reports.forEach((report) => {
const reportCard = document.createElement("div");
reportCard.className = "report-card";
reportCard.onclick = () => loadReport(report.id);
reportCard.innerHTML = `
<img src="${report.thumbnail}" alt="报告缩略图" class="report-thumbnail">
<div class="report-info">
<div class="report-timestamp">${report.timestamp}</div>
</div>
`;
fragment.appendChild(reportCard);
});
reportsGrid.innerHTML = "";
reportsGrid.appendChild(fragment);
}
// 重置分页状态
currentPage = 2; // 因为我们已经加载了第一页
hasMoreReports = data.pagination.total_pages > 1;
});
// 显示图片并隐藏占位符
function showImage(imgId, src) {
debug(`显示图片: ${imgId}, 路径: ${src}`);
const box = document.getElementById(`${imgId}-box`);
const img = document.getElementById(imgId);
if (!box || !img) {
console.error(
`找不到元素: box=${!!box}, img=${!!img}, imgId=${imgId}`
);
return;
}
const placeholder = box.querySelector(".placeholder");
// 先预加载图片
const tempImg = new Image();
tempImg.onload = function () {
debug(`图片加载完成: ${imgId}`);
img.src = src;
img.style.display = "block";
if (placeholder) {
placeholder.style.display = "none";
}
// 只在新建报告页面显示重拍按钮,查看历史报告页面不显示
const isViewingReport =
document.getElementById("view-report-page").style.display ===
"block";
// 显示对应的重拍按钮 - 仅在非查看历史报告模式下
if (imgId === "original-image" && retakeBtn && !isViewingReport) {
retakeBtn.style.display = "flex";
} else if (
imgId === "view-original-image" &&
viewRetakeBtn &&
!isViewingReport
) {
viewRetakeBtn.style.display = "flex";
}
// 添加点击事件 - 点击图片区域时显示大图模态框
img.onclick = function (e) {
e.stopPropagation(); // 阻止事件冒泡
showModal(img);
};
// 确保整个box的点击事件不会干扰重拍按钮
box.onclick = function (e) {
// 只有当点击的不是重拍按钮时才显示模态框
if (imgId === "original-image") {
if (!e.target.closest("#retake-btn")) {
showModal(img);
}
} else if (imgId === "view-original-image" && viewRetakeBtn) {
if (!e.target.closest("#view-retake-btn")) {
showModal(img);
}
} else {
showModal(img);
}
};
};
tempImg.onerror = function (e) {
debug(`图片加载失败: ${src}`);
console.error(`图片加载失败: ${src}, 错误:`, e);
// 尝试获取图片状态
fetch(src)
.then((response) => {
debug(`图片请求状态: ${response.status} ${response.statusText}`);
if (!response.ok) {
return response.text().then((text) => {
throw new Error(`HTTP ${response.status}: ${text}`);
});
}
})
.catch((error) => {
debug(`图片请求错误: ${error.message}`);
});
if (placeholder) {
const errorText = placeholder.querySelector(".placeholder-text");
if (errorText) {
errorText.textContent = "加载失败";
}
placeholder.style.display = "block";
}
img.style.display = "none";
// 隐藏重拍按钮
if (imgId === "original-image" && retakeBtn) {
retakeBtn.style.display = "none";
} else if (imgId === "view-original-image" && viewRetakeBtn) {
viewRetakeBtn.style.display = "none";
}
};
debug(`开始加载图片: ${src}`);
tempImg.src = src;
}
// 监听文件上传成功事件
socket.on("thermal_upload_complete", function (data) {
debug("收到文件上传完成事件", data);
const imagePath = `/thermal_data/${data.timestamp}/${data.image_filename}`;
debug(`图片路径: ${imagePath}`);
showImage("original-image", imagePath);
analyzeBtn.disabled = false;
analyzeBtn.style.display = "block"; // 确保开始分析按钮可见
updateStatus("文件上传完成,可以开始分析", false);
});
// 监听新热图生成事件
socket.on("new_heatmap", function (data) {
debug("收到新热图事件", data);
const imgId = `heatmap-${data.type}`;
showImage(imgId, data.path);
});
// 监听分析状态更新
socket.on("analysis_status", function (data) {
debug("收到分析状态更新", data);
if (data.status === "vlm_analyzing") {
updateStatus(data.message, true, data.progress);
summaryContainer.style.display = "none";
summaryContent.innerHTML = "";
} else if (data.status === "generating_summary") {
updateStatus(data.message, true);
summaryContainer.style.display = "block";
isGeneratingSummary = true;
} else if (data.status === "completed") {
updateStatus(data.message, false);
isGeneratingSummary = false;
// 在分析完成后移除打字光标
const cursor = summaryContent.querySelector(".typing-cursor");
if (cursor) {
cursor.remove();
}
analyzeBtn.disabled = false;
analyzeBtn.style.display = "block"; // 分析完成后显示开始分析按钮
cancelBtn.style.display = "none";
currentTaskId = null;
// 重置分析状态
isAnalysisInProgress = false;
// 启用返回按钮
const backButton = document.getElementById("new-report-back-btn");
if (backButton) {
backButton.classList.remove("disabled");
}
// 分析完成后,刷新报告列表
loadReportsList();
}
});
// 监听总结文本块
socket.on("summary_chunk", function (data) {
debug("收到总结文本块", data);
if (isGeneratingSummary) {
const text = data.text;
// 移除旧的打字光标
const oldCursor = summaryContent.querySelector(".typing-cursor");
if (oldCursor) {
oldCursor.remove();
}
// 如果是第一个文本块,清空内容
if (!summaryContent.textContent) {
summaryContent.innerHTML = "";
}
// 将新的文本添加到现有内容中
const currentText =
summaryContent.getAttribute("data-raw-text") || "";
const newText = currentText + text;
summaryContent.setAttribute("data-raw-text", newText);
// 使用marked渲染Markdown
summaryContent.innerHTML = marked.parse(newText);
// 添加新的打字光标并确保其可见性
const cursor = document.createElement("span");
cursor.className = "typing-cursor";
// 确保光标添加到文本的末尾
// 找到最后一个段落或文本节点
let lastElement = summaryContent.lastElementChild;
// 如果最后一个元素是段落或其他包含文本的元素
if (lastElement) {
// 在有子节点的情况下,追加到最后一个子节点
if (lastElement.lastChild) {
lastElement.appendChild(cursor);
} else {
// 如果没有子节点,直接追加
lastElement.appendChild(cursor);
}
} else {
// 如果没有元素直接添加到summaryContent
summaryContent.appendChild(cursor);
}
// 确保光标在视野中 - 滚动到光标位置
cursor.scrollIntoView({ behavior: "smooth", block: "end" });
// 高亮代码块
summaryContent.querySelectorAll("pre code").forEach((block) => {
hljs.highlightBlock(block);
});
}
});
// 监听错误事件
socket.on("analysis_error", function (data) {
debug("收到错误事件", data);
updateStatus(`错误:${data.error}`, false);
// 在分析出错后移除打字光标
const cursor = summaryContent.querySelector(".typing-cursor");
if (cursor) {
cursor.remove();
}
analyzeBtn.disabled = false;
analyzeBtn.style.display = "block"; // 分析出错后显示开始分析按钮
cancelBtn.style.display = "none";
currentTaskId = null;
// 重置分析状态
isAnalysisInProgress = false;
// 启用返回按钮
const backButton = document.getElementById("new-report-back-btn");
if (backButton) {
backButton.classList.remove("disabled");
}
});
// 取消分析
cancelBtn.addEventListener("click", async function () {
debug("点击了取消分析按钮");
if (!currentTaskId) {
debug("没有正在进行的任务");
return;
}
try {
const response = await fetch(`/cancel_analysis/${currentTaskId}`, {
method: "POST",
});
const data = await response.json();
debug("取消请求响应", data);
if (data.status === "success") {
updateStatus("分析已取消", false);
// 在取消分析后移除打字光标
const cursor = summaryContent.querySelector(".typing-cursor");
if (cursor) {
cursor.remove();
}
analyzeBtn.disabled = false;
analyzeBtn.style.display = "block"; // 取消分析后显示开始分析按钮
cancelBtn.style.display = "none";
currentTaskId = null;
// 重置分析状态
isAnalysisInProgress = false;
// 启用返回按钮
const backButton = document.getElementById("new-report-back-btn");
if (backButton) {
backButton.classList.remove("disabled");
}
} else {
throw new Error(data.message);
}
} catch (error) {
debug("取消请求错误", error);
updateStatus(`取消失败:${error.message}`, false);
}
});
// 开始分析
analyzeBtn.addEventListener("click", async function () {
debug("点击了开始分析按钮");
try {
analyzeBtn.disabled = true;
analyzeBtn.style.display = "none"; // 隐藏开始分析按钮
cancelBtn.style.display = "block"; // 显示取消分析按钮
updateStatus("正在启动分析...", true);
summaryContainer.style.display = "none";
summaryContent.innerHTML = "";
// 设置分析状态为进行中
isAnalysisInProgress = true;
// 禁用返回按钮
const backButton = document.getElementById("new-report-back-btn");
if (backButton) {
backButton.classList.add("disabled");
}
const response = await fetch("/analyze_thermal_data", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
const data = await response.json();
debug("分析请求响应", data);
if (data.status === "success") {
currentTaskId = data.task_id;
} else {
throw new Error(data.message);
}
} catch (error) {
debug("分析请求错误", error);
updateStatus(`错误:${error.message}`, false);
analyzeBtn.disabled = false;
analyzeBtn.style.display = "block"; // 分析出错后显示开始分析按钮
cancelBtn.style.display = "none";
// 重置分析状态
isAnalysisInProgress = false;
// 启用返回按钮
const backButton = document.getElementById("new-report-back-btn");
if (backButton) {
backButton.classList.remove("disabled");
}
}
});
// 更新状态显示
function updateStatus(message, showSpinner, progress = null) {
debug(
`更新状态: ${message}, 显示加载动画: ${showSpinner}, 进度: ${progress}`
);
statusBox.style.display = "block";
statusMessage.textContent = message;
const spinner = statusBox.querySelector(".spinner");
if (spinner) {
spinner.style.display = showSpinner ? "block" : "none";
}
const progressContainer = document.getElementById("progress-container");
const progressBar = document.getElementById("progress-bar");
const progressText = document.getElementById("progress-text");
if (progress !== null) {
progressContainer.style.display = "block";
progressBar.style.width = `${progress}%`;
progressText.textContent = `${progress}%`;
} else {
progressContainer.style.display = "none";
progressText.textContent = "";
}
}
// 图片模态框功能
const modal = document.getElementById("imageModal");
const modalContentWrapper = document.querySelector(
".modal-content-wrapper"
);
const modalImg = document.getElementById("modalImage");
const zoomIndicator = document.querySelector(".zoom-indicator");
const zoomIndicatorText = document.getElementById("zoom-indicator-text");
// 图片缩放和拖动相关变量
let scale = 1;
let startDistance = 0;
let currentScale = 1;
let zoomIndicatorTimeout;
let isDragging = false;
let dragStartX = 0;
let dragStartY = 0;
let translateX = 0;
let translateY = 0;
// 触摸设备上的缩放/拖动变量
let touchZoomStart = null;
let touchZoomCenter = null;
// 缓存已经缩放过的图片URL
const resizedImageCache = {};
// 用于调整图片尺寸到统一分辨率的函数
function resizeImageToUniform(originalSrc, callback) {
// 检查缓存中是否已经有这个图片的调整版本
if (resizedImageCache[originalSrc]) {
callback(resizedImageCache[originalSrc]);
return;
}
const targetWidth = 1024; // 目标宽度为1024像素
const targetHeight = 768; // 目标高度为768像素
// 创建临时图片元素用于加载原始图片
const tempImg = new Image();
tempImg.crossOrigin = "Anonymous"; // 处理跨域图片
tempImg.onload = function () {
// 创建一个canvas元素进行图像处理
const canvas = document.createElement("canvas");
canvas.width = targetWidth;
canvas.height = targetHeight;
const ctx = canvas.getContext("2d");
// 清空canvas
ctx.clearRect(0, 0, targetWidth, targetHeight);
// 启用图像平滑处理,提高缩放质量
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = "high";
// 计算如何缩放和居中图像
let scale, offsetX, offsetY;
// 计算保持纵横比的缩放比例
const scaleWidth = targetWidth / tempImg.width;
const scaleHeight = targetHeight / tempImg.height;
// 取较小值以确保图像完全适应目标尺寸
scale = Math.min(scaleWidth, scaleHeight);
// 计算居中偏移
offsetX = (targetWidth - tempImg.width * scale) / 2;
offsetY = (targetHeight - tempImg.height * scale) / 2;
// 设置白色背景(可选)
ctx.fillStyle = "#FFFFFF";
ctx.fillRect(0, 0, targetWidth, targetHeight);
// 在canvas上绘制缩放后的图像
ctx.drawImage(
tempImg,
0,
0,
tempImg.width,
tempImg.height,
offsetX,
offsetY,
tempImg.width * scale,
tempImg.height * scale
);
// 将canvas转换为数据URL - 使用较高质量参数
const resizedImageUrl = canvas.toDataURL("image/png", 0.95);
// 将结果存入缓存
resizedImageCache[originalSrc] = resizedImageUrl;
// 返回结果
callback(resizedImageUrl);
};
tempImg.onerror = function () {
debug(`缩放图片失败: ${originalSrc}`);
// 如果出错就使用原图
callback(originalSrc);
};
// 开始加载图片
tempImg.src = originalSrc;
}
// 显示缩放指示器
function showZoomIndicator() {
zoomIndicatorText.textContent = scale.toFixed(2) + "x";
zoomIndicator.style.opacity = "1";
// 清除之前的定时器
if (zoomIndicatorTimeout) {
clearTimeout(zoomIndicatorTimeout);
}
// 设置新的定时器3秒后自动隐藏
zoomIndicatorTimeout = setTimeout(() => {
zoomIndicator.style.opacity = "0";
}, 3000);
}
// 更新图片变换样式
function updateImageTransform() {
modalImg.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
}
// 拖动事件处理
modalImg.addEventListener("mousedown", function (e) {
if (scale > 1) {
isDragging = true;
dragStartX = e.clientX - translateX;
dragStartY = e.clientY - translateY;
modalImg.style.cursor = "grabbing";
}
});
modalImg.addEventListener(
"touchstart",
function (e) {
if (e.touches.length === 1 && scale > 1) {
isDragging = true;
dragStartX = e.touches[0].clientX - translateX;
dragStartY = e.touches[0].clientY - translateY;
} else if (e.touches.length === 2) {
e.preventDefault(); // 阻止默认行为
startDistance = getDistance(e.touches[0], e.touches[1]);
currentScale = scale;
}
},
{ passive: false }
);
document.addEventListener("mousemove", function (e) {
if (isDragging) {
const newTranslateX = e.clientX - dragStartX;
const newTranslateY = e.clientY - dragStartY;
// 限制拖动范围 - 根据缩放比例和图片尺寸调整范围
const imgWidth = modalImg.clientWidth * scale;
const imgHeight = modalImg.clientHeight * scale;
const wrapperWidth = modalContentWrapper.clientWidth;
const wrapperHeight = modalContentWrapper.clientHeight;
const maxX = Math.max(0, (imgWidth - wrapperWidth) / 2);
const maxY = Math.max(0, (imgHeight - wrapperHeight) / 2);
translateX = Math.min(Math.max(newTranslateX, -maxX), maxX);
translateY = Math.min(Math.max(newTranslateY, -maxY), maxY);
updateImageTransform();
}
});
modalImg.addEventListener(
"touchmove",
function (e) {
if (isDragging && e.touches.length === 1) {
e.preventDefault();
const newTranslateX = e.touches[0].clientX - dragStartX;
const newTranslateY = e.touches[0].clientY - dragStartY;
// 限制拖动范围 - 根据缩放比例和图片尺寸调整范围
const imgWidth = modalImg.clientWidth * scale;
const imgHeight = modalImg.clientHeight * scale;
const wrapperWidth = modalContentWrapper.clientWidth;
const wrapperHeight = modalContentWrapper.clientHeight;
const maxX = Math.max(0, (imgWidth - wrapperWidth) / 2);
const maxY = Math.max(0, (imgHeight - wrapperHeight) / 2);
translateX = Math.min(Math.max(newTranslateX, -maxX), maxX);
translateY = Math.min(Math.max(newTranslateY, -maxY), maxY);
updateImageTransform();
} else if (e.touches.length === 2) {
e.preventDefault(); // 阻止默认行为
// 计算两指中心点
const touch1 = e.touches[0];
const touch2 = e.touches[1];
const centerX = (touch1.clientX + touch2.clientX) / 2;
const centerY = (touch1.clientY + touch2.clientY) / 2;
// 获取容器位置
const rect = modalContentWrapper.getBoundingClientRect();
// 计算中心点相对于容器的位置
const containerCenterX = centerX - rect.left;
const containerCenterY = centerY - rect.top;
// 计算中心点相对于图片中心的偏移
const offsetX = containerCenterX - rect.width / 2 - translateX;
const offsetY = containerCenterY - rect.height / 2 - translateY;
// 计算新的缩放比例
const currentDistance = getDistance(touch1, touch2);
const oldScale = scale;
const newScale = Math.max(
0.5,
Math.min(5, currentScale * (currentDistance / startDistance))
);
if (oldScale !== newScale) {
scale = newScale;
// 调整平移量,以保持触摸中心点的相对位置
const factor = newScale / oldScale;
translateX = translateX - offsetX * (factor - 1);
translateY = translateY - offsetY * (factor - 1);
updateImageTransform();
showZoomIndicator();
}
}
},
{ passive: false }
);
document.addEventListener("mouseup", function () {
if (isDragging) {
isDragging = false;
modalImg.style.cursor = "grab";
}
});
modalImg.addEventListener("touchend", function (e) {
if (isDragging && e.touches.length === 0) {
isDragging = false;
} else if (e.touches.length < 2) {
// 如果缩放过小自动复位到1
if (scale < 0.8) {
scale = 1;
translateX = 0;
translateY = 0;
updateImageTransform();
showZoomIndicator();
}
}
});
// 计算两个触摸点之间的距离
function getDistance(touch1, touch2) {
const dx = touch1.clientX - touch2.clientX;
const dy = touch1.clientY - touch2.clientY;
return Math.sqrt(dx * dx + dy * dy);
}
// 双击恢复原始大小
modalImg.addEventListener("dblclick", function () {
if (scale > 1.2) {
// 如果已经放大,则重置到默认大小
scale = 1;
translateX = 0;
translateY = 0;
} else {
// 如果是默认大小则放大到2倍
scale = 2;
}
updateImageTransform();
showZoomIndicator();
});
// 添加鼠标滚轮缩放功能,以鼠标位置为中心点缩放
modalImg.addEventListener(
"wheel",
function (e) {
e.preventDefault();
// 获取鼠标相对于图片容器的位置
const rect = modalContentWrapper.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
// 计算鼠标位置相对于图片中心的偏移量(考虑当前平移)
const offsetX = mouseX - rect.width / 2 - translateX;
const offsetY = mouseY - rect.height / 2 - translateY;
// 确定缩放方向和大小
const delta = e.deltaY > 0 ? 0.8 : 1.25; // 缩小为0.8倍放大为1.25倍
const oldScale = scale;
const newScale = Math.max(0.5, Math.min(5, scale * delta));
if (oldScale !== newScale) {
scale = newScale;
// 调整平移量,使鼠标位置保持在同一个相对位置
const factor = newScale / oldScale;
translateX = translateX - offsetX * (factor - 1);
translateY = translateY - offsetY * (factor - 1);
updateImageTransform();
showZoomIndicator();
// 更新鼠标样式
modalImg.style.cursor = scale > 1 ? "grab" : "default";
}
},
{ passive: false }
);
// 点击模态框背景关闭,但点击图片本身不关闭
modal.addEventListener("click", function (e) {
// 如果点击的是模态框背景(不是图片或包装器)
if (e.target === modal) {
hideModal();
}
});
// 阻止点击图片和包装器时关闭模态框
modalContentWrapper.addEventListener("click", function (e) {
e.stopPropagation();
});
// 自动计算合适的缩放比例 - 现在不再自动计算统一使用1.0
function calculateOptimalScale(img) {
return 1.0; // 统一使用1.0倍缩放
}
function showModal(img) {
debug("显示模态框", img.src);
if (img.src && img.style.display !== "none") {
modal.style.display = "block";
// 先显示加载动画
modalImg.src = ""; // 清空当前图像
document.getElementById("modalLoading").style.display = "block";
// 调整图片尺寸为统一分辨率
resizeImageToUniform(img.src, function (resizedImageUrl) {
// 隐藏加载动画
document.getElementById("modalLoading").style.display = "none";
// 设置模态框图片源为调整后的图片
modalImg.src = resizedImageUrl;
// 重置拖动位置
translateX = 0;
translateY = 0;
// 设置初始缩放比例为1.0(不再使用自动计算的比例)
scale = 1.0;
updateImageTransform();
// 显示缩放指示器
showZoomIndicator();
// 设置鼠标样式
modalImg.style.cursor = "default";
});
// 禁止页面滚动
document.body.style.overflow = "hidden";
}
}
function hideModal() {
debug("隐藏模态框");
modal.style.display = "none";
// 隐藏加载动画
document.getElementById("modalLoading").style.display = "none";
// 隐藏缩放指示器
zoomIndicator.style.opacity = "0";
if (zoomIndicatorTimeout) {
clearTimeout(zoomIndicatorTimeout);
}
// 恢复页面滚动
document.body.style.overflow = "";
}
// 添加分页相关变量
let currentPage = 1;
let isLoading = false;
let hasMoreReports = true;
// 修改加载报告列表函数,添加强制刷新参数
async function loadReportsList(append = false) {
if (isLoading || (!append && !hasMoreReports)) return;
const reportsGrid = document.getElementById("reports-grid");
const loadingElement = document.getElementById("reports-loading");
try {
isLoading = true;
// 显示加载状态
if (loadingElement) {
loadingElement.style.display = "flex";
}
// 异步获取报告数据
const response = await fetch(
`/get_available_reports?page=${currentPage}&per_page=10&_t=${Date.now()}` // 添加时间戳防止缓存
);
const data = await response.json();
// 隐藏加载状态
if (loadingElement) {
loadingElement.style.display = "none";
}
isLoading = false;
if (data.status === "success") {
// 更新分页状态
hasMoreReports = currentPage < data.pagination.total_pages;
if (data.reports.length === 0 && !append) {
reportsGrid.innerHTML = `
<div class="empty-reports">
<img src="{{ url_for('static', filename='images/thermal/empty.svg') }}" alt="暂无报告">
<p>暂无历史报告</p>
</div>
`;
} else {
// 创建一个文档片段来存储所有报告卡片
const fragment = document.createDocumentFragment();
data.reports.forEach((report) => {
const reportCard = document.createElement("div");
reportCard.className = "report-card";
reportCard.onclick = () => loadReport(report.id);
reportCard.innerHTML = `
<img src="${
report.thumbnail
}?_t=${Date.now()}" alt="报告缩略图" class="report-thumbnail">
<div class="report-info">
<div class="report-timestamp">${report.timestamp}</div>
</div>
`;
fragment.appendChild(reportCard);
});
// 如果是追加模式,保留现有内容
if (append) {
reportsGrid.appendChild(fragment);
} else {
reportsGrid.innerHTML = "";
reportsGrid.appendChild(fragment);
}
// 增加页码
currentPage++;
}
} else {
throw new Error(data.message);
}
} catch (error) {
debug("加载报告列表失败", error);
isLoading = false;
if (loadingElement) {
loadingElement.style.display = "none";
}
if (!append) {
reportsGrid.innerHTML = `
<div class="empty-reports">
<img src="{{ url_for('static', filename='images/thermal/error.svg') }}" alt="加载失败">
<p>加载报告列表失败: ${error.message}</p>
</div>
`;
}
}
}
// 添加滚动监听
function setupInfiniteScroll() {
const reportsGrid = document.getElementById("reports-grid");
// 使用Intersection Observer监听加载更多的触发器
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && hasMoreReports && !isLoading) {
loadReportsList(true);
}
});
},
{
root: reportsGrid,
rootMargin: "100px",
threshold: 0.1,
}
);
// 创建并监听触发器元素
const trigger = document.createElement("div");
trigger.id = "scroll-trigger";
trigger.style.height = "1px";
reportsGrid.appendChild(trigger);
observer.observe(trigger);
}
// 修改显示报告列表页面函数
function showReportsList() {
document.getElementById("reports-page").style.display = "block";
document.getElementById("new-report-page").style.display = "none";
document.getElementById("view-report-page").style.display = "none";
resetPageContent();
// 重置分页状态
currentPage = 1;
hasMoreReports = true;
isLoading = false;
// 立即显示加载状态
const loadingElement = document.getElementById("reports-loading");
if (loadingElement) {
loadingElement.style.display = "flex";
}
// 立即加载报告列表
loadReportsList();
// 设置无限滚动
setupInfiniteScroll();
}
// 新建报告按钮点击事件
newReportBtn.addEventListener("click", function () {
document.getElementById("reports-page").style.display = "none";
document.getElementById("new-report-page").style.display = "block";
document.getElementById("view-report-page").style.display = "none";
resetPageContent();
});
// 加载指定报告
async function loadReport(reportId) {
try {
const response = await fetch(`/load_report/${reportId}`);
const data = await response.json();
if (data.status === "success") {
// 切换到查看报告页面
document.getElementById("reports-page").style.display = "none";
document.getElementById("new-report-page").style.display = "none";
document.getElementById("view-report-page").style.display = "block";
// 确保重拍按钮被隐藏
if (viewRetakeBtn) {
viewRetakeBtn.style.display = "none";
}
// 显示图片
showImage("view-original-image", data.data.images.original);
showImage("view-heatmap-0", data.data.images.heatmap_0);
showImage("view-heatmap-1", data.data.images.heatmap_1);
showImage("view-heatmap-2", data.data.images.heatmap_2);
// 显示分析结果
const viewSummaryContainer = document.getElementById(
"view-summary-container"
);
const viewSummaryContent = document.getElementById(
"view-summary-content"
);
viewSummaryContainer.style.display = "block";
viewSummaryContent.innerHTML = marked.parse(
data.data.analysis_results
);
} else {
throw new Error(data.message);
}
} catch (error) {
debug("加载报告失败", error);
updateStatus(`加载报告失败: ${error.message}`, false);
}
}
// 页面加载完成后加载报告列表
document.addEventListener("DOMContentLoaded", function () {
debug("DOM加载完成");
showReportsList();
// 添加返回按钮事件监听器
const newReportBackBtn = document.getElementById("new-report-back-btn");
const viewReportBackBtn = document.getElementById(
"view-report-back-btn"
);
if (newReportBackBtn) {
newReportBackBtn.addEventListener("click", function () {
if (!isAnalysisInProgress) {
showReportsList();
} else {
debug("分析正在进行中,不允许返回列表");
updateStatus("请等待分析完成或取消分析后再返回", true);
}
});
}
if (viewReportBackBtn) {
viewReportBackBtn.addEventListener("click", function () {
showReportsList();
});
}
});
// 点击关闭按钮也可以关闭模态框
document.querySelector(".modal-close").onclick = function (e) {
e.stopPropagation();
hideModal();
};
// ESC键关闭模态框
document.addEventListener("keydown", function (e) {
if (modal.style.display === "block" && e.key === "Escape") {
hideModal();
}
});
// 弹窗显示的Promise用户点击后resolve结果
let popupResolve;
function showPopup(message, buttons = { confirm: true, cancel: true }) {
// 设置弹窗的消息内容,使用 innerHTML 以支持换行
document.getElementById("popup-message").innerHTML = message;
// 控制按钮显示
document.getElementById("confirm-btn").style.display = buttons.confirm
? "inline-block"
: "none";
document.getElementById("cancel-btn").style.display = buttons.cancel
? "inline-block"
: "none";
// 显示弹窗
document.getElementById("popup-modal").style.display = "flex";
// 返回Promise等待用户选择
return new Promise((resolve) => {
popupResolve = resolve; // 保存 resolve 函数
});
}
function confirmAction() {
// 用户点击确认按钮关闭弹窗并返回true
document.getElementById("popup-modal").style.display = "none";
popupResolve(true);
}
function cancelAction() {
// 用户点击取消按钮关闭弹窗并返回false
document.getElementById("popup-modal").style.display = "none";
popupResolve(false);
}
</script>
</body>
</html>