2398 lines
71 KiB
HTML
2398 lines
71 KiB
HTML
<!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()">×</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>
|