1199 lines
42 KiB
HTML
1199 lines
42 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Reports</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css">
|
|
<style>
|
|
/* 基础样式优化 */
|
|
body {
|
|
background-color: #f8f9fa;
|
|
color: #212529;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
/* 卡片样式现代化 */
|
|
.card {
|
|
border: none;
|
|
border-radius: 12px;
|
|
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
|
|
transition: transform 0.2s, box-shadow 0.2s;
|
|
margin-bottom: 24px;
|
|
}
|
|
.card-title {
|
|
font-size: 1.25rem; /* 20px */
|
|
font-weight: 600; /* 稍微加粗 */
|
|
margin-bottom: 0.75rem;
|
|
color: #2c3e50; /* 深色系,提高对比度 */
|
|
}
|
|
|
|
.card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 16px rgba(0,0,0,0.12);
|
|
}
|
|
|
|
.card-body {
|
|
padding: 24px;
|
|
}
|
|
|
|
/* 按钮样式优化 */
|
|
.btn {
|
|
border-radius: 8px; /* 减小圆角 */
|
|
padding: 6px 10px; /* 减小内边距 */
|
|
font-weight: 500;
|
|
font-size: 0.8rem; /* 14px */
|
|
transition: all 0.2s;
|
|
line-height: 1; /* 优化行高 */
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
backdrop-filter: blur(8px);
|
|
transition: all 0.3s ease;
|
|
}
|
|
.btn-sm { /* 更小尺寸的按钮 */
|
|
padding: 4px 10px;
|
|
font-size: 0.8rem; /* 13px */
|
|
border-radius: 8px;
|
|
line-height: 1;
|
|
}
|
|
.btn-warning {
|
|
background: linear-gradient(45deg, #6897fe, #3979e0);
|
|
border: none;
|
|
color: white;
|
|
}
|
|
|
|
.btn-warning:hover {
|
|
background: linear-gradient(45deg, #3979e0, #6897fe);
|
|
transform: translateY(-1px);
|
|
color: white;
|
|
}
|
|
|
|
.btn-success {
|
|
background: linear-gradient(45deg, #1fbff4, #61caf7); /* 明亮蓝色 */
|
|
border: none;
|
|
color: white;
|
|
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.2);
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: linear-gradient(45deg, #a5a4a4, #7b8389);
|
|
border: none;
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background: linear-gradient(45deg, #7b8389, #a5a4a4);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
/* 导航栏样式 */
|
|
.navbar {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 64px;
|
|
background: var(--surface-color, #FFFFFF);
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 0 24px;
|
|
z-index: 1000;
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
|
}
|
|
|
|
.navbar-brand {
|
|
font-size: 20px;
|
|
font-weight: 600;
|
|
color: var(--primary-color, #6366F1);
|
|
margin-right: 48px;
|
|
}
|
|
|
|
.navbar-menu {
|
|
display: flex;
|
|
gap: 32px;
|
|
}
|
|
|
|
/* 修改导航栏菜单项样式 */
|
|
.navbar-menu-item {
|
|
padding: 8px 16px;
|
|
cursor: pointer;
|
|
border-radius: 8px;
|
|
transition: all 0.3s;
|
|
color: var(--text-secondary, #64748B);
|
|
font-weight: 500;
|
|
background: transparent; /* 默认透明背景 */
|
|
}
|
|
|
|
/* 修改悬停和激活状态样式 */
|
|
.navbar-menu-item:hover {
|
|
background: linear-gradient(145deg, var(--primary-color, #6366F1), var(--primary-light, #818CF8));
|
|
color: white;
|
|
}
|
|
|
|
.navbar-menu-item.active {
|
|
background: linear-gradient(145deg, var(--primary-color, #6366F1), var(--primary-light, #818CF8));
|
|
color: white;
|
|
}
|
|
|
|
.navbar-right {
|
|
margin-left: auto;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 24px;
|
|
color: var(--text-secondary, #64748B);
|
|
}
|
|
|
|
/* 更新按钮样式 */
|
|
.navbar .btn {
|
|
padding: 8px 16px;
|
|
border-radius: 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-weight: 500;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.navbar .btn-primary {
|
|
background: linear-gradient(145deg, var(--primary-color, #6366F1), var(--primary-light, #818CF8));
|
|
color: white;
|
|
border: none;
|
|
}
|
|
|
|
.navbar .btn-secondary {
|
|
background: var(--background-color, #F8FAFC);
|
|
color: var(--text-secondary, #64748B);
|
|
border: none;
|
|
}
|
|
|
|
.navbar .btn:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.2);
|
|
}
|
|
|
|
/* 调整主内容区域的上边距,以适应固定导航栏 */
|
|
#mainContent {
|
|
margin-top: 64px;
|
|
}
|
|
|
|
/* 图表容器优化 */
|
|
.charts-container {
|
|
display: flex;
|
|
flex-direction: column; /* 垂直排列 */
|
|
gap: 20px; /* 图表之间的间距 */
|
|
width: 100%;
|
|
padding: 20px;
|
|
}
|
|
|
|
.chart-wrapper {
|
|
width: 100%;
|
|
background: white;
|
|
border-radius: 8px;
|
|
padding: 15px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.chart-container {
|
|
height: 300px; /* 每个图表的固定高度 */
|
|
width: 100%;
|
|
}
|
|
|
|
.chart-wrapper h5 {
|
|
margin: 0 0 10px 0;
|
|
color: #333;
|
|
font-size: 16px;
|
|
}
|
|
|
|
/* 数据显示区域优化 */
|
|
.data-container {
|
|
background-color: white;
|
|
padding: 20px;
|
|
border-radius: 12px;
|
|
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
|
|
}
|
|
|
|
/* 表单控件美化 */
|
|
.form-control {
|
|
border-radius: 8px;
|
|
border: 1px solid #dee2e6;
|
|
padding: 10px 16px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.form-control:focus {
|
|
border-color: #4a6bff;
|
|
box-shadow: 0 0 0 3px rgba(74,107,255,0.1);
|
|
}
|
|
|
|
/* 模态框优化 */
|
|
.modal-content {
|
|
border: none;
|
|
border-radius: 16px;
|
|
}
|
|
|
|
.modal-header {
|
|
border-bottom: 1px solid #eee;
|
|
padding: 20px 24px;
|
|
}
|
|
|
|
.modal-body {
|
|
padding: 24px;
|
|
}
|
|
|
|
/* 实验数据显示区域 */
|
|
#realTimeData {
|
|
height: 300px;
|
|
border-radius: 12px;
|
|
background: white;
|
|
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
|
|
}
|
|
|
|
/* 加载动画优化 */
|
|
.spinner-border {
|
|
width: 1.5rem;
|
|
height: 1.5rem;
|
|
border-width: 0.2em;
|
|
}
|
|
|
|
/* 警告提示框美化 */
|
|
.alert {
|
|
border: none;
|
|
border-radius: 12px;
|
|
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
|
|
}
|
|
|
|
/* 表格样式优化 */
|
|
.table {
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.table th {
|
|
background-color: #f8f9fa;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* 滚动条美化 */
|
|
::-webkit-scrollbar {
|
|
width: 8px;
|
|
height: 8px;
|
|
}
|
|
|
|
::-webkit-scrollbar-track {
|
|
background: #f1f1f1;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
background: #888;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: #555;
|
|
}
|
|
|
|
.hidden {
|
|
display: none;
|
|
}
|
|
.page-content {
|
|
display: none;
|
|
}
|
|
.page-content.active {
|
|
display: block;
|
|
}
|
|
.charts-container {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 20px;
|
|
margin: 20px 0;
|
|
}
|
|
.chart-wrapper {
|
|
background: white;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
width: calc(50% - 10px);
|
|
min-width: 300px;
|
|
}
|
|
.chart-container {
|
|
position: relative;
|
|
height: 250px;
|
|
width: 100%;
|
|
}
|
|
|
|
/* 按钮禁用状态样式 */
|
|
.btn:disabled,
|
|
.btn[disabled] {
|
|
background-color: #b8b8b8 !important; /* 使用 !important 确保覆盖其他样式 */
|
|
color: white !important;
|
|
border: none;
|
|
opacity: 0.65 !important; /* 防止 Bootstrap 默认的透明度 */
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
/* 确保禁用状态下的渐变背景也变为灰色 */
|
|
.btn-warning:disabled,
|
|
.btn-primary:disabled,
|
|
.btn-info:disabled {
|
|
background: #b8b8b8 !important;
|
|
background-image: none !important;
|
|
}
|
|
|
|
/* 禁用状态下取消悬停效果 */
|
|
.btn:disabled:hover {
|
|
transform: none !important;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1) !important;
|
|
}
|
|
/* 禁用状态下的按钮样式 */
|
|
.btn-success:disabled,
|
|
.btn-success[disabled] {
|
|
background: #adb0b4 !important; /* 使用纯色背景 */
|
|
background-image: none !important; /* 移除渐变效果 */
|
|
background-color: #adb0b4 !important;
|
|
border-color: #adb0b4 !important;
|
|
color: #fff !important;
|
|
opacity: 0.65 !important;
|
|
pointer-events: none;
|
|
transform: none !important;
|
|
}
|
|
|
|
/* 确保禁用状态下不会显示悬停效果 */
|
|
.btn-success:disabled:hover,
|
|
.btn-success[disabled]:hover {
|
|
background: #adb0b4 !important;
|
|
background-image: none !important;
|
|
background-color: #adb0b4 !important;
|
|
border-color: #adb0b4 !important;
|
|
transform: none !important;
|
|
}
|
|
/* 侧边栏样式 */
|
|
.sidebar {
|
|
position: fixed;
|
|
top: var(--header-height);
|
|
left: 0;
|
|
bottom: 0;
|
|
width: var(--sidebar-width);
|
|
background: var(--surface-color);
|
|
border-right: 1px solid var(--border-color);
|
|
}
|
|
|
|
.sidebar-menu {
|
|
list-style: none;
|
|
padding: 16px 0;
|
|
}
|
|
|
|
.sidebar-item {
|
|
padding: 12px 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
margin: 4px 8px;
|
|
border-radius: 8px;
|
|
color: var(--text-secondary);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.sidebar-item:hover {
|
|
background: linear-gradient(145deg, var(--primary-color), var(--primary-light));
|
|
color: white;
|
|
}
|
|
|
|
.sidebar-item.active {
|
|
background: linear-gradient(145deg, var(--primary-color), var(--primary-light));
|
|
color: white;
|
|
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.2);
|
|
}
|
|
|
|
.sidebar-item .bi {
|
|
font-size: 1.2rem;
|
|
}
|
|
|
|
/* 添加CSS变量 */
|
|
:root {
|
|
--header-height: 64px;
|
|
--sidebar-width: 240px;
|
|
--surface-color: #FFFFFF;
|
|
--border-color: #E2E8F0;
|
|
--primary-color: #6366F1;
|
|
--primary-light: #818CF8;
|
|
--text-secondary: #64748B;
|
|
}
|
|
|
|
/* 移除卡片样式 */
|
|
.sidebar.card {
|
|
border: none;
|
|
box-shadow: none;
|
|
background: none;
|
|
}
|
|
|
|
.sidebar .card-body {
|
|
padding: 0;
|
|
}
|
|
|
|
/* 主要内容区域 */
|
|
.main-content {
|
|
margin-left: var(--sidebar-width);
|
|
margin-top: var(--header-height);
|
|
padding: 24px 32px;
|
|
}
|
|
|
|
.page-header {
|
|
margin-bottom: 24px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.page-title {
|
|
font-size: 24px;
|
|
font-weight: 600;
|
|
background: linear-gradient(45deg, var(--primary-color), var(--primary-light));
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
}
|
|
|
|
/* 更新卡片样式 */
|
|
.card {
|
|
background: var(--surface-color);
|
|
border-radius: 16px;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
|
|
transition: transform 0.3s;
|
|
border: none;
|
|
}
|
|
|
|
.card:hover {
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.card-header {
|
|
padding: 20px 24px;
|
|
border-bottom: 1px solid var(--border-color);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
background: transparent;
|
|
}
|
|
|
|
.card-title {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.card-body {
|
|
padding: 24px;
|
|
}
|
|
|
|
/* 更新按钮样式 */
|
|
.btn {
|
|
padding: 10px 20px;
|
|
border-radius: 8px;
|
|
border: none;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
font-weight: 500;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: linear-gradient(145deg, var(--primary-color), var(--primary-light));
|
|
color: white;
|
|
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.2);
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 6px 16px rgba(99, 102, 241, 0.3);
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: var(--background-color);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background: var(--border-color);
|
|
}
|
|
|
|
/* 更新表单控件样式 */
|
|
.form-control {
|
|
padding: 10px 16px;
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 8px;
|
|
font-size: 14px;
|
|
transition: all 0.3s;
|
|
background: var(--background-color);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.form-control:focus {
|
|
border-color: var(--primary-color);
|
|
outline: none;
|
|
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
|
}
|
|
|
|
/* 告部分切换样式 */
|
|
.report-section {
|
|
display: none;
|
|
}
|
|
|
|
.report-section.active {
|
|
display: block;
|
|
}
|
|
|
|
#chartsContainer {
|
|
display: flex;
|
|
flex-direction: column; /* 垂直排列 */
|
|
gap: 20px; /* 图表之间的间距 */
|
|
width: 100%;
|
|
padding: 20px;
|
|
}
|
|
|
|
.chart-wrapper {
|
|
width: 100%;
|
|
background: white;
|
|
border-radius: 8px;
|
|
padding: 15px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.chart-container {
|
|
height: 300px; /* 每个图表的固定高度 */
|
|
width: 100%;
|
|
}
|
|
|
|
.chart-wrapper h5 {
|
|
margin: 0 0 10px 0;
|
|
color: #333;
|
|
font-size: 16px;
|
|
}
|
|
|
|
.chart-data-panel {
|
|
background: #f8f9fa;
|
|
border-radius: 12px;
|
|
padding: 20px;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
|
height: 100%;
|
|
min-height: 300px;
|
|
}
|
|
|
|
.custom-data-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 15px;
|
|
}
|
|
|
|
.custom-data-item {
|
|
background: white;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
transition: transform 0.2s;
|
|
}
|
|
|
|
.custom-data-item:hover {
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.custom-data-name {
|
|
font-size: 0.9rem;
|
|
color: #666;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.custom-data-value {
|
|
font-size: 1.5rem;
|
|
font-weight: 600;
|
|
color: #2c3e50;
|
|
display: flex;
|
|
align-items: baseline;
|
|
}
|
|
|
|
.custom-data-unit {
|
|
font-size: 0.9rem;
|
|
color: #666;
|
|
margin-left: 5px;
|
|
}
|
|
|
|
/* 响应式布局优化 */
|
|
@media (max-width: 991.98px) {
|
|
.chart-data-panel {
|
|
margin-top: 20px;
|
|
min-height: auto;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<nav class="navbar">
|
|
<div class="navbar-brand">Lab</div>
|
|
<div class="navbar-menu">
|
|
<div class="navbar-menu-item" onclick="window.location.href='lab.html'">Project</div>
|
|
<div class="navbar-menu-item" onclick="window.location.href='device.html'">Device</div>
|
|
<div class="navbar-menu-item active">Reports</div>
|
|
<div class="navbar-menu-item" onclick="window.location.href='paper.html'">Paper</div>
|
|
</div>
|
|
<div class="navbar-right">
|
|
<a href="https://beta.obscura.work/lab/device.html" target="_blank" class="btn btn-primary">
|
|
<i class="bi bi-plus-circle"></i>
|
|
Device Registration
|
|
</a>
|
|
<span class="nav-item" id="userInfo"></span>
|
|
<button class="btn btn-secondary" id="logoutBtn" onclick="logout()">
|
|
<i class="bi bi-box-arrow-right"></i>
|
|
Logout
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
<div class="main-content">
|
|
<div class="row">
|
|
<!-- 左侧侧边栏 -->
|
|
<div class="col-auto">
|
|
<div class="sidebar">
|
|
<div class="sidebar-menu">
|
|
<div class="sidebar-item" onclick="showReportsFilter()">
|
|
<i class="bi bi-funnel"></i>
|
|
<span>Filter</span>
|
|
</div>
|
|
<div class="sidebar-item" onclick="showReportsSort()">
|
|
<i class="bi bi-sort-down"></i>
|
|
<span>Sort</span>
|
|
</div>
|
|
<div class="sidebar-item" onclick="generateSummaryReport()">
|
|
<i class="bi bi-file-earmark-text"></i>
|
|
<span>Summary</span>
|
|
</div>
|
|
<div class="sidebar-item" onclick="exportAllReports()">
|
|
<i class="bi bi-download"></i>
|
|
<span>Download</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 右侧主要内容区 -->
|
|
<div class="col ps-0 pe-4">
|
|
<div class="card w-100">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h3 class="page-title">Reports</h3>
|
|
</div>
|
|
<div id="reportsList" class="mt-4">
|
|
<!-- Project reports list will be displayed here dynamically -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Reports Filter Modal -->
|
|
<div class="modal fade" id="reportsFilterModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Filter Reports</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="reportFilterForm">
|
|
<div class="mb-3">
|
|
<label class="form-label">Project Name</label>
|
|
<input type="text" class="form-control" id="filterReportProjectName">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Date Range</label>
|
|
<div class="row">
|
|
<div class="col">
|
|
<input type="date" class="form-control" id="filterReportStartDate">
|
|
</div>
|
|
<div class="col">
|
|
<input type="date" class="form-control" id="filterReportEndDate">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Report Type</label>
|
|
<select class="form-select" id="filterReportType">
|
|
<option value="">All</option>
|
|
<option value="project">Project</option>
|
|
<option value="experiment">experiment</option>
|
|
<option value="paper">Paper</option>
|
|
</select>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" onclick="resetReportFilters()">Reset</button>
|
|
<button type="button" class="btn btn-primary" onclick="applyReportFilters()">Apply</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Report Detail Page -->
|
|
<div id="reportDetailPage" class="page-content"> <!-- 移除 container mt-4 类 -->
|
|
<div class="row">
|
|
<!-- 左侧侧边栏 -->
|
|
<div class="col-auto">
|
|
<div class="sidebar">
|
|
<div class="sidebar-menu">
|
|
<div class="sidebar-item" onclick="switchReportSection('projectReport')">
|
|
<i class="bi bi-file-text"></i>
|
|
<span>Project</span>
|
|
</div>
|
|
<div class="sidebar-item" onclick="switchReportSection('paperReport')">
|
|
<i class="bi bi-book"></i>
|
|
<span>Paper</span>
|
|
</div>
|
|
<div class="sidebar-item" onclick="downloadCurrentReport()">
|
|
<i class="bi bi-download"></i>
|
|
<span>Download</span>
|
|
</div>
|
|
<div class="sidebar-item" onclick="showPage('reports')">
|
|
<i class="bi bi-arrow-left"></i>
|
|
<span>Back</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 右侧主要内容区 -->
|
|
<div class="col ps-0 pe-4">
|
|
<div class="card w-100">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h3 id="reportDetailTitle">Project Report</h3>
|
|
</div>
|
|
<div class="report-sections">
|
|
<div id="projectReportSection" class="report-section active">
|
|
<pre id="projectReportDetail" class="report-content bg-light p-3 rounded"></pre>
|
|
</div>
|
|
<div id="paperReportSection" class="report-section">
|
|
<pre id="paperReportDetail" class="report-content bg-light p-3 rounded"></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Reports Sort Modal -->
|
|
<div class="modal fade" id="reportsSortModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Sort Reports</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="reportSortForm">
|
|
<div class="mb-3">
|
|
<label class="form-label">Sort By</label>
|
|
<select class="form-select" id="reportSortField">
|
|
<option value="create_time">Creation Date</option>
|
|
<option value="project_name">Project Name</option>
|
|
<option value="report_type">Report Type</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Order</label>
|
|
<select class="form-select" id="reportSortOrder">
|
|
<option value="desc">Descending</option>
|
|
<option value="asc">Ascending</option>
|
|
</select>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="button" class="btn btn-primary" onclick="applyReportSort()">Apply</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
|
|
<script>
|
|
// API base URL
|
|
const API_BASE_URL = 'https://dev.obscura.work/lab';
|
|
let token = localStorage.getItem('token');
|
|
|
|
// 过滤和排序状态
|
|
let reportsFilter = {
|
|
projectName: '',
|
|
startDate: '',
|
|
endDate: '',
|
|
type: ''
|
|
};
|
|
|
|
let reportsSort = {
|
|
field: 'create_time',
|
|
order: 'desc'
|
|
};
|
|
|
|
// 页面加载时检查登录状态并加载报告
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
if (token) {
|
|
loadReports();
|
|
} else {
|
|
window.location.href = 'lab.html';
|
|
}
|
|
});
|
|
|
|
// Store current report data being viewed
|
|
let currentReportData = null;
|
|
|
|
// 格式化报告内容的辅助函数
|
|
function formatReportContent(jsonData) {
|
|
if (!jsonData) return '无数据';
|
|
|
|
return `<div style="white-space: pre-wrap; word-wrap: break-word;">` +
|
|
JSON.stringify(jsonData, null, 2)
|
|
// 移除所有括号和引号
|
|
.replace(/[{}"]/g, '')
|
|
.replace(/},/g, '')
|
|
.replace(/{ },/g, '')
|
|
.replace(/^\s*,/gm, '') // 处理行首的逗号
|
|
.replace(/Project Analysis Report:/, '<div style="text-align: center"><strong style="font-size: 24px;">Project Analysis Report</strong></div>')
|
|
.replace(/paper Summary Report:/, '<div style="text-align: center"><strong style="font-size: 24px;">Paper Summary Report</strong></div>')
|
|
// 加粗标题 (以数字开头的行)
|
|
.replace(/^(\s*\d+\.[^:\n]+:)/gm, '<strong>$1</strong>')
|
|
// 保持缩进
|
|
.split('\n')
|
|
.map(line => line.trimEnd())
|
|
.join('\n') +
|
|
'</div>';
|
|
}
|
|
|
|
// Add load project report list function
|
|
async function loadReports() {
|
|
try {
|
|
const response = await fetch(`${API_BASE_URL}/projects`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
if (response.ok) {
|
|
const projects = await response.json();
|
|
const reportsList = document.getElementById('reportsList');
|
|
|
|
const reportsHtml = projects.map(project => `
|
|
<div class="card mb-3" style="cursor: pointer;"
|
|
onclick="viewProjectReport('${project._id}', '${project.project_name}')">
|
|
<div class="card-body d-flex align-items-center">
|
|
<div class="flex-grow-1">
|
|
<h5 class="card-title mb-1">${project.project_name}</h5>
|
|
<p class="card-text mb-0">${project.description}</p>
|
|
</div>
|
|
<div>
|
|
<button class="btn btn-warning"
|
|
onclick="event.stopPropagation(); viewProjectReport('${project._id}', '${project.project_name}')">
|
|
View Report
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
reportsList.innerHTML = reportsHtml;
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load project report list:', error);
|
|
alert('Failed to load project report list, please try again');
|
|
}
|
|
}
|
|
|
|
// 修改后的 viewProjectReport 函数
|
|
async function viewProjectReport(projectId, projectName) {
|
|
try {
|
|
let projectData = null, paperData = null;
|
|
|
|
// 隐藏项目列表页面
|
|
document.querySelector('.main-content > .row').style.display = 'none';
|
|
|
|
// 1. 尝试获取项目报告
|
|
const projectResponse = await fetch(`${API_BASE_URL}/projects/${projectId}/report`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
if (projectResponse.ok) {
|
|
projectData = await projectResponse.json();
|
|
} else {
|
|
console.log('No project report found');
|
|
}
|
|
|
|
// 2. 如果没有项目报告,尝试获取文献分析报告
|
|
if (!projectData) {
|
|
const paperResponse = await fetch(`${API_BASE_URL}/references/${projectId}/summary_report`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
if (paperResponse.ok) {
|
|
paperData = await paperResponse.json();
|
|
} else {
|
|
console.log('No paper report found');
|
|
}
|
|
}
|
|
|
|
// 3. 如果两种报告都没有,显示提示信息
|
|
if (!projectData && !paperData) {
|
|
document.getElementById('reportDetailTitle').textContent = `Project Report - ${projectName}`;
|
|
document.getElementById('projectReportDetail').innerHTML =
|
|
'<div class="alert alert-info">No reports available for this project yet.</div>';
|
|
document.getElementById('paperReportDetail').innerHTML =
|
|
'<div class="alert alert-info">No paper analysis report available.</div>';
|
|
} else {
|
|
// 4. 保存当前报告数据
|
|
currentReportData = {
|
|
projectId,
|
|
projectName,
|
|
projectReport: projectData,
|
|
paperReport: paperData
|
|
};
|
|
|
|
// 5. 更新报告显示
|
|
document.getElementById('reportDetailTitle').textContent = `Project Report - ${projectName}`;
|
|
document.getElementById('projectReportDetail').innerHTML = projectData ?
|
|
formatReportContent(projectData) :
|
|
'<div class="alert alert-info">No project report available.</div>';
|
|
document.getElementById('paperReportDetail').innerHTML = paperData ?
|
|
formatReportContent(paperData) :
|
|
'<div class="alert alert-info">No paper analysis report available.</div>';
|
|
}
|
|
|
|
// 6. 显示报告详情页面
|
|
document.querySelectorAll('.page-content').forEach(page => {
|
|
page.classList.remove('active');
|
|
});
|
|
document.getElementById('reportDetailPage').classList.add('active');
|
|
|
|
} catch (error) {
|
|
console.error('Failed to view project report:', error);
|
|
alert('Failed to view project report, please try again');
|
|
}
|
|
}
|
|
|
|
// Download current report being viewed
|
|
function downloadCurrentReport() {
|
|
if (!currentReportData) return;
|
|
|
|
// Build complete report object
|
|
const combinedReport = {
|
|
project_report: currentReportData.projectReport,
|
|
paper_analysis: currentReportData.paperReport || "No paper analysis report found"
|
|
};
|
|
|
|
const reportText = JSON.stringify(combinedReport, null, 2);
|
|
const blob = new Blob([reportText], { type: 'application/json' });
|
|
const url = window.URL.createObjectURL(blob);
|
|
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `project_complete_report_${currentReportData.projectName}.json`;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
|
|
window.URL.revokeObjectURL(url);
|
|
document.body.removeChild(a);
|
|
}
|
|
|
|
// Add load reports filter function
|
|
function showReportsFilter() {
|
|
// 设置当前过滤值
|
|
document.getElementById('filterReportProjectName').value = reportsFilter.projectName;
|
|
document.getElementById('filterReportStartDate').value = reportsFilter.startDate;
|
|
document.getElementById('filterReportEndDate').value = reportsFilter.endDate;
|
|
document.getElementById('filterReportType').value = reportsFilter.type;
|
|
|
|
// 显示模态框
|
|
new bootstrap.Modal(document.getElementById('reportsFilterModal')).show();
|
|
}
|
|
|
|
// Add load reports sort function
|
|
function showReportsSort() {
|
|
// 设置当前排序值
|
|
document.getElementById('reportSortField').value = reportsSort.field;
|
|
document.getElementById('reportSortOrder').value = reportsSort.order;
|
|
|
|
// 显示模态框
|
|
new bootstrap.Modal(document.getElementById('reportsSortModal')).show();
|
|
}
|
|
|
|
// Reset reports filter
|
|
function resetReportFilters() {
|
|
reportsFilter = {
|
|
projectName: '',
|
|
startDate: '',
|
|
endDate: '',
|
|
type: ''
|
|
};
|
|
|
|
// 重置表单
|
|
document.getElementById('reportFilterForm').reset();
|
|
|
|
// 重新加载项目列表
|
|
loadReports();
|
|
|
|
// 关闭模态框
|
|
bootstrap.Modal.getInstance(document.getElementById('reportsFilterModal')).hide();
|
|
}
|
|
|
|
// Apply reports filter
|
|
function applyReportFilters() {
|
|
reportsFilter = {
|
|
projectName: document.getElementById('filterReportProjectName').value,
|
|
startDate: document.getElementById('filterReportStartDate').value,
|
|
endDate: document.getElementById('filterReportEndDate').value,
|
|
type: document.getElementById('filterReportType').value
|
|
};
|
|
|
|
// 重新加载项目列表
|
|
loadReports();
|
|
|
|
// 关闭模态框
|
|
bootstrap.Modal.getInstance(document.getElementById('reportsFilterModal')).hide();
|
|
}
|
|
|
|
// Apply reports sort
|
|
function applyReportSort() {
|
|
reportsSort = {
|
|
field: document.getElementById('reportSortField').value,
|
|
order: document.getElementById('reportSortOrder').value
|
|
};
|
|
|
|
// 重新加载项目列表
|
|
loadReports();
|
|
|
|
// 关闭模态框
|
|
bootstrap.Modal.getInstance(document.getElementById('reportsSortModal')).hide();
|
|
}
|
|
|
|
// Export all reports
|
|
async function exportAllReports() {
|
|
try {
|
|
const response = await fetch(`${API_BASE_URL}/projects`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
if (response.ok) {
|
|
const projects = await response.json();
|
|
const reports = projects.map(project => ({
|
|
project_name: project.project_name,
|
|
description: project.description,
|
|
project_reports: project.project_reports,
|
|
paper_analysis: project.paper_analysis
|
|
}));
|
|
|
|
const reportText = JSON.stringify(reports, null, 2);
|
|
const blob = new Blob([reportText], { type: 'application/json' });
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = 'all_reports.json';
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
document.body.removeChild(a);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to export all reports:', error);
|
|
alert('Failed to export all reports, please try again');
|
|
}
|
|
}
|
|
|
|
// Generate summary report
|
|
async function generateSummaryReport() {
|
|
try {
|
|
const response = await fetch(`${API_BASE_URL}/projects`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
if (response.ok) {
|
|
const projects = await response.json();
|
|
const summary = projects.map(project => ({
|
|
project_name: project.project_name,
|
|
description: project.description,
|
|
project_reports: project.project_reports,
|
|
paper_analysis: project.paper_analysis
|
|
}));
|
|
|
|
const summaryText = JSON.stringify(summary, null, 2);
|
|
const blob = new Blob([summaryText], { type: 'application/json' });
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = 'summary_report.json';
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
document.body.removeChild(a);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to generate summary report:', error);
|
|
alert('Failed to generate summary report, please try again');
|
|
}
|
|
}
|
|
|
|
// Switch between project report and paper analysis
|
|
function switchReportSection(section) {
|
|
// Remove active class from all sections
|
|
document.querySelectorAll('.report-section').forEach(section => {
|
|
section.classList.remove('active');
|
|
});
|
|
|
|
// Add active class to the selected section
|
|
document.getElementById(section + 'Section').classList.add('active');
|
|
}
|
|
|
|
// 切换报告部分
|
|
function switchReportSection(sectionId) {
|
|
// 隐藏所有报告部分
|
|
document.querySelectorAll('.report-section').forEach(section => {
|
|
section.classList.remove('active');
|
|
});
|
|
|
|
// 显示选中的报告部分
|
|
const selectedSection = document.getElementById(sectionId + 'Section');
|
|
if (selectedSection) {
|
|
selectedSection.classList.add('active');
|
|
}
|
|
|
|
// 更新标题
|
|
const title = sectionId === 'projectReport' ? 'Project Report' : 'paper Analysis';
|
|
document.getElementById('reportDetailTitle').textContent = `${title} - ${currentReportData.projectName}`;
|
|
|
|
// 更新侧边栏选中状态
|
|
document.querySelectorAll('.sidebar-item').forEach(item => {
|
|
if (item.textContent.trim().includes(title)) {
|
|
item.classList.add('active');
|
|
} else {
|
|
item.classList.remove('active');
|
|
}
|
|
});
|
|
}
|
|
|
|
// 返回报告列表
|
|
function showPage(pageId) {
|
|
if (pageId === 'reports') {
|
|
document.getElementById('reportDetailPage').classList.remove('active');
|
|
document.querySelector('.main-content > .row').style.display = 'flex';
|
|
loadReports();
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |