|
|
@@ -0,0 +1,1283 @@
|
|
|
+<!DOCTYPE html>
|
|
|
+<html lang="zh-CN">
|
|
|
+<head>
|
|
|
+ <meta charset="UTF-8">
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
+ <title>施工方案审查结果 - 高级分析工具</title>
|
|
|
+ <style>
|
|
|
+ * {
|
|
|
+ margin: 0;
|
|
|
+ padding: 0;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+
|
|
|
+ body {
|
|
|
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
|
|
+ background: #0f172a;
|
|
|
+ min-height: 100vh;
|
|
|
+ color: #e2e8f0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .container {
|
|
|
+ max-width: 1600px;
|
|
|
+ margin: 0 auto;
|
|
|
+ padding: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .header {
|
|
|
+ background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
|
|
+ border: 1px solid #334155;
|
|
|
+ border-radius: 16px;
|
|
|
+ padding: 30px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .header h1 {
|
|
|
+ font-size: 28px;
|
|
|
+ color: #f8fafc;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .header .subtitle {
|
|
|
+ color: #94a3b8;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .controls {
|
|
|
+ display: flex;
|
|
|
+ gap: 15px;
|
|
|
+ margin-top: 20px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn {
|
|
|
+ padding: 12px 24px;
|
|
|
+ border: none;
|
|
|
+ border-radius: 8px;
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-primary {
|
|
|
+ background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
|
|
+ color: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-primary:hover {
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 8px 20px rgba(59, 130, 246, 0.4);
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-secondary {
|
|
|
+ background: #334155;
|
|
|
+ color: #e2e8f0;
|
|
|
+ border: 1px solid #475569;
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-secondary:hover {
|
|
|
+ background: #475569;
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-danger {
|
|
|
+ background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
|
|
+ color: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-success {
|
|
|
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
|
+ color: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ input[type="file"] {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Dashboard */
|
|
|
+ .dashboard {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
|
+ gap: 20px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-card {
|
|
|
+ background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
|
|
+ border: 1px solid #334155;
|
|
|
+ border-radius: 16px;
|
|
|
+ padding: 24px;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-card::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ height: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-card.total::before { background: #3b82f6; }
|
|
|
+ .stat-card.high::before { background: #ef4444; }
|
|
|
+ .stat-card.medium::before { background: #f59e0b; }
|
|
|
+ .stat-card.low::before { background: #10b981; }
|
|
|
+
|
|
|
+ .stat-icon {
|
|
|
+ font-size: 32px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-value {
|
|
|
+ font-size: 36px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #f8fafc;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-label {
|
|
|
+ color: #94a3b8;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-change {
|
|
|
+ font-size: 12px;
|
|
|
+ margin-top: 8px;
|
|
|
+ padding: 4px 8px;
|
|
|
+ border-radius: 4px;
|
|
|
+ display: inline-block;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-change.positive {
|
|
|
+ background: rgba(16, 185, 129, 0.2);
|
|
|
+ color: #10b981;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-change.negative {
|
|
|
+ background: rgba(239, 68, 68, 0.2);
|
|
|
+ color: #ef4444;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Charts Section */
|
|
|
+ .charts-section {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
|
|
+ gap: 20px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chart-card {
|
|
|
+ background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
|
|
+ border: 1px solid #334155;
|
|
|
+ border-radius: 16px;
|
|
|
+ padding: 24px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chart-card h3 {
|
|
|
+ font-size: 16px;
|
|
|
+ color: #f8fafc;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Progress bars */
|
|
|
+ .progress-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .progress-item {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .progress-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .progress-label {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #e2e8f0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .progress-value {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #94a3b8;
|
|
|
+ }
|
|
|
+
|
|
|
+ .progress-bar {
|
|
|
+ height: 8px;
|
|
|
+ background: #334155;
|
|
|
+ border-radius: 4px;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .progress-fill {
|
|
|
+ height: 100%;
|
|
|
+ border-radius: 4px;
|
|
|
+ transition: width 0.5s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .progress-fill.high { background: linear-gradient(90deg, #ef4444, #f87171); }
|
|
|
+ .progress-fill.medium { background: linear-gradient(90deg, #f59e0b, #fbbf24); }
|
|
|
+ .progress-fill.low { background: linear-gradient(90deg, #10b981, #34d399); }
|
|
|
+ .progress-fill.total { background: linear-gradient(90deg, #3b82f6, #60a5fa); }
|
|
|
+
|
|
|
+ /* Filters */
|
|
|
+ .filters {
|
|
|
+ background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
|
|
+ border: 1px solid #334155;
|
|
|
+ border-radius: 16px;
|
|
|
+ padding: 20px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ display: flex;
|
|
|
+ gap: 20px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-group {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-group label {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #94a3b8;
|
|
|
+ text-transform: uppercase;
|
|
|
+ letter-spacing: 0.5px;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-group select,
|
|
|
+ .filter-group input {
|
|
|
+ padding: 10px 16px;
|
|
|
+ background: #0f172a;
|
|
|
+ border: 1px solid #334155;
|
|
|
+ border-radius: 8px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #e2e8f0;
|
|
|
+ min-width: 150px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-group select:focus,
|
|
|
+ .filter-group input:focus {
|
|
|
+ outline: none;
|
|
|
+ border-color: #3b82f6;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* View Toggle */
|
|
|
+ .view-toggle {
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .view-btn {
|
|
|
+ padding: 10px 20px;
|
|
|
+ background: #1e293b;
|
|
|
+ border: 1px solid #334155;
|
|
|
+ border-radius: 8px;
|
|
|
+ color: #94a3b8;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .view-btn.active {
|
|
|
+ background: #3b82f6;
|
|
|
+ color: white;
|
|
|
+ border-color: #3b82f6;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Cards View */
|
|
|
+ .cards-container {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));
|
|
|
+ gap: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .review-card {
|
|
|
+ background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
|
|
+ border: 1px solid #334155;
|
|
|
+ border-radius: 16px;
|
|
|
+ overflow: hidden;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+
|
|
|
+ .review-card::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ height: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .review-card.high::before { background: #ef4444; }
|
|
|
+ .review-card.medium::before { background: #f59e0b; }
|
|
|
+ .review-card.low::before { background: #10b981; }
|
|
|
+ .review-card.none::before { background: #64748b; }
|
|
|
+
|
|
|
+ .review-card:hover {
|
|
|
+ transform: translateY(-4px);
|
|
|
+ border-color: #475569;
|
|
|
+ box-shadow: 0 20px 40px rgba(0,0,0,0.3);
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-header {
|
|
|
+ padding: 20px;
|
|
|
+ border-bottom: 1px solid #334155;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-header-top {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: flex-start;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chapter-tag {
|
|
|
+ padding: 6px 12px;
|
|
|
+ background: rgba(59, 130, 246, 0.2);
|
|
|
+ color: #60a5fa;
|
|
|
+ border-radius: 6px;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+
|
|
|
+ .risk-tag {
|
|
|
+ padding: 6px 12px;
|
|
|
+ border-radius: 6px;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+
|
|
|
+ .risk-tag.high {
|
|
|
+ background: rgba(239, 68, 68, 0.2);
|
|
|
+ color: #f87171;
|
|
|
+ }
|
|
|
+
|
|
|
+ .risk-tag.medium {
|
|
|
+ background: rgba(245, 158, 11, 0.2);
|
|
|
+ color: #fbbf24;
|
|
|
+ }
|
|
|
+
|
|
|
+ .risk-tag.low {
|
|
|
+ background: rgba(16, 185, 129, 0.2);
|
|
|
+ color: #34d399;
|
|
|
+ }
|
|
|
+
|
|
|
+ .risk-tag.none {
|
|
|
+ background: rgba(100, 116, 139, 0.2);
|
|
|
+ color: #94a3b8;
|
|
|
+ }
|
|
|
+
|
|
|
+ .check-item-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #f8fafc;
|
|
|
+ }
|
|
|
+
|
|
|
+ .check-item-code {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #64748b;
|
|
|
+ font-family: monospace;
|
|
|
+ margin-top: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-body {
|
|
|
+ padding: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .info-section {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .info-section:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .info-label {
|
|
|
+ font-size: 11px;
|
|
|
+ color: #64748b;
|
|
|
+ text-transform: uppercase;
|
|
|
+ letter-spacing: 0.5px;
|
|
|
+ margin-bottom: 6px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .info-content {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #e2e8f0;
|
|
|
+ line-height: 1.6;
|
|
|
+ padding: 12px;
|
|
|
+ background: #0f172a;
|
|
|
+ border-radius: 8px;
|
|
|
+ border-left: 3px solid #334155;
|
|
|
+ }
|
|
|
+
|
|
|
+ .info-content.location { border-left-color: #3b82f6; }
|
|
|
+ .info-content.suggestion { border-left-color: #8b5cf6; }
|
|
|
+ .info-content.reason { border-left-color: #ec4899; }
|
|
|
+ .info-content.issue { border-left-color: #ef4444; }
|
|
|
+
|
|
|
+ .card-footer {
|
|
|
+ padding: 16px 20px;
|
|
|
+ background: rgba(15, 23, 42, 0.5);
|
|
|
+ border-top: 1px solid #334155;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-badge {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ padding: 6px 12px;
|
|
|
+ border-radius: 6px;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-badge.has-issue {
|
|
|
+ background: rgba(239, 68, 68, 0.2);
|
|
|
+ color: #f87171;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-badge.no-issue {
|
|
|
+ background: rgba(16, 185, 129, 0.2);
|
|
|
+ color: #34d399;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-dot {
|
|
|
+ width: 8px;
|
|
|
+ height: 8px;
|
|
|
+ border-radius: 50%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-dot.has-issue {
|
|
|
+ background: #ef4444;
|
|
|
+ animation: pulse 2s infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-dot.no-issue {
|
|
|
+ background: #10b981;
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes pulse {
|
|
|
+ 0%, 100% { opacity: 1; }
|
|
|
+ 50% { opacity: 0.5; }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Table View */
|
|
|
+ .table-container {
|
|
|
+ background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
|
|
+ border: 1px solid #334155;
|
|
|
+ border-radius: 16px;
|
|
|
+ overflow: hidden;
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .table-container.show {
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+
|
|
|
+ table {
|
|
|
+ width: 100%;
|
|
|
+ border-collapse: collapse;
|
|
|
+ }
|
|
|
+
|
|
|
+ th, td {
|
|
|
+ padding: 16px;
|
|
|
+ text-align: left;
|
|
|
+ border-bottom: 1px solid #334155;
|
|
|
+ }
|
|
|
+
|
|
|
+ th {
|
|
|
+ background: #0f172a;
|
|
|
+ color: #94a3b8;
|
|
|
+ font-size: 12px;
|
|
|
+ text-transform: uppercase;
|
|
|
+ letter-spacing: 0.5px;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+
|
|
|
+ td {
|
|
|
+ color: #e2e8f0;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ tr:hover td {
|
|
|
+ background: rgba(59, 130, 246, 0.1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .table-risk {
|
|
|
+ padding: 4px 10px;
|
|
|
+ border-radius: 4px;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+
|
|
|
+ .table-risk.high {
|
|
|
+ background: rgba(239, 68, 68, 0.2);
|
|
|
+ color: #f87171;
|
|
|
+ }
|
|
|
+
|
|
|
+ .table-risk.medium {
|
|
|
+ background: rgba(245, 158, 11, 0.2);
|
|
|
+ color: #fbbf24;
|
|
|
+ }
|
|
|
+
|
|
|
+ .table-risk.low {
|
|
|
+ background: rgba(16, 185, 129, 0.2);
|
|
|
+ color: #34d399;
|
|
|
+ }
|
|
|
+
|
|
|
+ .table-risk.none {
|
|
|
+ background: rgba(100, 116, 139, 0.2);
|
|
|
+ color: #94a3b8;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Empty State */
|
|
|
+ .empty-state {
|
|
|
+ text-align: center;
|
|
|
+ padding: 100px 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .empty-state-icon {
|
|
|
+ font-size: 80px;
|
|
|
+ margin-bottom: 24px;
|
|
|
+ opacity: 0.5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .empty-state h2 {
|
|
|
+ font-size: 24px;
|
|
|
+ color: #f8fafc;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .empty-state p {
|
|
|
+ color: #64748b;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Toast */
|
|
|
+ .toast {
|
|
|
+ position: fixed;
|
|
|
+ bottom: 30px;
|
|
|
+ right: 30px;
|
|
|
+ background: #1e293b;
|
|
|
+ border: 1px solid #334155;
|
|
|
+ padding: 16px 24px;
|
|
|
+ border-radius: 12px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ transform: translateX(400px);
|
|
|
+ transition: transform 0.3s ease;
|
|
|
+ z-index: 1000;
|
|
|
+ box-shadow: 0 10px 40px rgba(0,0,0,0.3);
|
|
|
+ }
|
|
|
+
|
|
|
+ .toast.show {
|
|
|
+ transform: translateX(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ .toast.success { border-left: 4px solid #10b981; }
|
|
|
+ .toast.error { border-left: 4px solid #ef4444; }
|
|
|
+
|
|
|
+ /* Export Panel */
|
|
|
+ .export-panel {
|
|
|
+ background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
|
|
+ border: 1px solid #334155;
|
|
|
+ border-radius: 16px;
|
|
|
+ padding: 20px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .export-panel.show {
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+
|
|
|
+ .export-options {
|
|
|
+ display: flex;
|
|
|
+ gap: 15px;
|
|
|
+ margin-top: 15px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* File List */
|
|
|
+ .file-list {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 10px;
|
|
|
+ margin-top: 15px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .file-tag {
|
|
|
+ padding: 8px 16px;
|
|
|
+ background: rgba(59, 130, 246, 0.2);
|
|
|
+ border: 1px solid #3b82f6;
|
|
|
+ border-radius: 8px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #60a5fa;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .file-tag .remove {
|
|
|
+ cursor: pointer;
|
|
|
+ opacity: 0.7;
|
|
|
+ }
|
|
|
+
|
|
|
+ .file-tag .remove:hover {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ @media (max-width: 768px) {
|
|
|
+ .cards-container {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+
|
|
|
+ .charts-section {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filters {
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: stretch;
|
|
|
+ }
|
|
|
+
|
|
|
+ th, td {
|
|
|
+ padding: 12px;
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ </style>
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+ <div class="container">
|
|
|
+ <div class="header">
|
|
|
+ <h1>🔍 施工方案审查结果 - 高级分析工具</h1>
|
|
|
+ <p class="subtitle">支持多文件对比、风险分析和数据导出</p>
|
|
|
+
|
|
|
+ <div class="controls">
|
|
|
+ <label class="btn btn-primary">
|
|
|
+ 📁 选择JSON文件
|
|
|
+ <input type="file" id="fileInput" accept=".json" multiple>
|
|
|
+ </label>
|
|
|
+ <button class="btn btn-secondary" onclick="loadFromDefault()">
|
|
|
+ 📂 加载示例数据
|
|
|
+ </button>
|
|
|
+ <button class="btn btn-success" onclick="toggleExport()">
|
|
|
+ 📊 导出报告
|
|
|
+ </button>
|
|
|
+ <button class="btn btn-danger" onclick="clearAll()">
|
|
|
+ 🗑️ 清空数据
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="file-list" id="fileList"></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="export-panel" id="exportPanel">
|
|
|
+ <h3>导出选项</h3>
|
|
|
+ <div class="export-options">
|
|
|
+ <button class="btn btn-primary" onclick="exportToExcel()">📄 导出Excel</button>
|
|
|
+ <button class="btn btn-primary" onclick="exportToPDF()">📑 导出PDF</button>
|
|
|
+ <button class="btn btn-secondary" onclick="exportToJSON()">📋 导出JSON</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="dashboard" id="dashboard" style="display: none;">
|
|
|
+ <div class="stat-card total">
|
|
|
+ <div class="stat-icon">📊</div>
|
|
|
+ <div class="stat-value" id="totalCount">0</div>
|
|
|
+ <div class="stat-label">总检查项</div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-card high">
|
|
|
+ <div class="stat-icon">🔴</div>
|
|
|
+ <div class="stat-value" id="highRiskCount">0</div>
|
|
|
+ <div class="stat-label">高风险</div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-card medium">
|
|
|
+ <div class="stat-icon">🟡</div>
|
|
|
+ <div class="stat-value" id="mediumRiskCount">0</div>
|
|
|
+ <div class="stat-label">中风险</div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-card low">
|
|
|
+ <div class="stat-icon">🟢</div>
|
|
|
+ <div class="stat-value" id="lowRiskCount">0</div>
|
|
|
+ <div class="stat-label">低风险</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="charts-section" id="chartsSection" style="display: none;">
|
|
|
+ <div class="chart-card">
|
|
|
+ <h3>📊 风险分布</h3>
|
|
|
+ <div class="progress-list" id="riskDistribution"></div>
|
|
|
+ </div>
|
|
|
+ <div class="chart-card">
|
|
|
+ <h3>📈 章节统计</h3>
|
|
|
+ <div class="progress-list" id="chapterDistribution"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="filters" id="filters" style="display: none;">
|
|
|
+ <div class="filter-group">
|
|
|
+ <label>风险等级</label>
|
|
|
+ <select id="riskFilter" onchange="applyFilters()">
|
|
|
+ <option value="all">全部</option>
|
|
|
+ <option value="high">高风险</option>
|
|
|
+ <option value="medium">中风险</option>
|
|
|
+ <option value="low">低风险</option>
|
|
|
+ <option value="none">无风险</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <div class="filter-group">
|
|
|
+ <label>章节</label>
|
|
|
+ <select id="chapterFilter" onchange="applyFilters()">
|
|
|
+ <option value="all">全部</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <div class="filter-group">
|
|
|
+ <label>问题状态</label>
|
|
|
+ <select id="issueFilter" onchange="applyFilters()">
|
|
|
+ <option value="all">全部</option>
|
|
|
+ <option value="has-issue">存在问题</option>
|
|
|
+ <option value="no-issue">无问题</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <div class="filter-group">
|
|
|
+ <label>搜索</label>
|
|
|
+ <input type="text" id="searchInput" placeholder="关键词..." oninput="applyFilters()">
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="view-toggle" id="viewToggle" style="display: none;">
|
|
|
+ <button class="view-btn active" onclick="switchView('cards')">🎴 卡片视图</button>
|
|
|
+ <button class="view-btn" onclick="switchView('table')">📋 表格视图</button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div id="content">
|
|
|
+ <div class="empty-state">
|
|
|
+ <div class="empty-state-icon">📂</div>
|
|
|
+ <h2>请加载审查结果文件</h2>
|
|
|
+ <p>支持多文件同时加载进行对比分析</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="toast" id="toast">
|
|
|
+ <span class="toast-message">操作成功</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <script>
|
|
|
+ let allReviewItems = [];
|
|
|
+ let filteredItems = [];
|
|
|
+ let loadedFiles = [];
|
|
|
+ let currentView = 'cards';
|
|
|
+
|
|
|
+ document.getElementById('fileInput').addEventListener('change', handleFileSelect);
|
|
|
+
|
|
|
+ function handleFileSelect(event) {
|
|
|
+ const files = Array.from(event.target.files);
|
|
|
+ files.forEach(file => {
|
|
|
+ const reader = new FileReader();
|
|
|
+ reader.onload = function(e) {
|
|
|
+ try {
|
|
|
+ let content = e.target.result;
|
|
|
+ content = content.replace(/[\x00-\x08\x0b-\x0c\x0e-\x1f]/g, '');
|
|
|
+ const data = JSON.parse(content);
|
|
|
+ processData(data, file.name);
|
|
|
+ addFileToList(file.name);
|
|
|
+ showToast(`已加载: ${file.name}`, 'success');
|
|
|
+ } catch (err) {
|
|
|
+ showToast(`${file.name} 解析失败: ${err.message}`, 'error');
|
|
|
+ }
|
|
|
+ };
|
|
|
+ reader.readAsText(file);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function addFileToList(filename) {
|
|
|
+ if (!loadedFiles.includes(filename)) {
|
|
|
+ loadedFiles.push(filename);
|
|
|
+ renderFileList();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderFileList() {
|
|
|
+ const container = document.getElementById('fileList');
|
|
|
+ container.innerHTML = loadedFiles.map(file => `
|
|
|
+ <div class="file-tag">
|
|
|
+ ${file}
|
|
|
+ <span class="remove" onclick="removeFile('${file}')">✕</span>
|
|
|
+ </div>
|
|
|
+ `).join('');
|
|
|
+ }
|
|
|
+
|
|
|
+ function removeFile(filename) {
|
|
|
+ loadedFiles = loadedFiles.filter(f => f !== filename);
|
|
|
+ renderFileList();
|
|
|
+ }
|
|
|
+
|
|
|
+ function loadFromDefault() {
|
|
|
+ const sampleFiles = [
|
|
|
+ 'f926d2ad4428bfcbe12be8702e2c32ce-1773041592.json'
|
|
|
+ ];
|
|
|
+
|
|
|
+ sampleFiles.forEach(filename => {
|
|
|
+ fetch(`../../temp/construction_review/final_result/${filename}`)
|
|
|
+ .then(response => {
|
|
|
+ if (!response.ok) throw new Error('文件加载失败');
|
|
|
+ return response.text();
|
|
|
+ })
|
|
|
+ .then(text => {
|
|
|
+ text = text.replace(/[\x00-\x08\x0b-\x0c\x0e-\x1f]/g, '');
|
|
|
+ const data = JSON.parse(text);
|
|
|
+ processData(data, filename);
|
|
|
+ addFileToList(filename);
|
|
|
+ showToast(`已加载: ${filename}`, 'success');
|
|
|
+ })
|
|
|
+ .catch(err => {
|
|
|
+ showToast('加载失败: ' + err.message, 'error');
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function processData(data, filename) {
|
|
|
+ const aiReviewResult = data.ai_review_result || {};
|
|
|
+ const reviewResults = aiReviewResult.review_results || [];
|
|
|
+
|
|
|
+ reviewResults.forEach(result => {
|
|
|
+ Object.values(result).forEach(unit => {
|
|
|
+ if (unit.review_lists) {
|
|
|
+ unit.review_lists.forEach(item => {
|
|
|
+ allReviewItems.push({
|
|
|
+ ...item,
|
|
|
+ sourceFile: filename
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ updateUI();
|
|
|
+ }
|
|
|
+
|
|
|
+ function updateUI() {
|
|
|
+ populateChapterFilter();
|
|
|
+ updateStats();
|
|
|
+ updateCharts();
|
|
|
+ applyFilters();
|
|
|
+
|
|
|
+ document.getElementById('dashboard').style.display = 'grid';
|
|
|
+ document.getElementById('chartsSection').style.display = 'grid';
|
|
|
+ document.getElementById('filters').style.display = 'flex';
|
|
|
+ document.getElementById('viewToggle').style.display = 'flex';
|
|
|
+ }
|
|
|
+
|
|
|
+ function populateChapterFilter() {
|
|
|
+ const chapters = [...new Set(allReviewItems.map(item => item.chapter_code))];
|
|
|
+ const select = document.getElementById('chapterFilter');
|
|
|
+ select.innerHTML = '<option value="all">全部章节</option>';
|
|
|
+ chapters.forEach(chapter => {
|
|
|
+ if (chapter) {
|
|
|
+ const option = document.createElement('option');
|
|
|
+ option.value = chapter;
|
|
|
+ option.textContent = chapter;
|
|
|
+ select.appendChild(option);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function updateStats() {
|
|
|
+ let high = 0, medium = 0, low = 0, none = 0;
|
|
|
+
|
|
|
+ allReviewItems.forEach(item => {
|
|
|
+ const riskLevel = getRiskLevel(item);
|
|
|
+ switch(riskLevel) {
|
|
|
+ case 'high': high++; break;
|
|
|
+ case 'medium': medium++; break;
|
|
|
+ case 'low': low++; break;
|
|
|
+ default: none++; break;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ document.getElementById('totalCount').textContent = allReviewItems.length;
|
|
|
+ document.getElementById('highRiskCount').textContent = high;
|
|
|
+ document.getElementById('mediumRiskCount').textContent = medium;
|
|
|
+ document.getElementById('lowRiskCount').textContent = low;
|
|
|
+ }
|
|
|
+
|
|
|
+ function updateCharts() {
|
|
|
+ // Risk Distribution
|
|
|
+ const riskCounts = { high: 0, medium: 0, low: 0, none: 0 };
|
|
|
+ allReviewItems.forEach(item => {
|
|
|
+ riskCounts[getRiskLevel(item)]++;
|
|
|
+ });
|
|
|
+
|
|
|
+ const total = allReviewItems.length || 1;
|
|
|
+ const riskLabels = {
|
|
|
+ high: '高风险',
|
|
|
+ medium: '中风险',
|
|
|
+ low: '低风险',
|
|
|
+ none: '无风险'
|
|
|
+ };
|
|
|
+
|
|
|
+ document.getElementById('riskDistribution').innerHTML =
|
|
|
+ Object.entries(riskCounts).map(([key, count]) => `
|
|
|
+ <div class="progress-item">
|
|
|
+ <div class="progress-header">
|
|
|
+ <span class="progress-label">${riskLabels[key]}</span>
|
|
|
+ <span class="progress-value">${count} (${Math.round(count/total*100)}%)</span>
|
|
|
+ </div>
|
|
|
+ <div class="progress-bar">
|
|
|
+ <div class="progress-fill ${key}" style="width: ${count/total*100}%"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `).join('');
|
|
|
+
|
|
|
+ // Chapter Distribution
|
|
|
+ const chapterCounts = {};
|
|
|
+ allReviewItems.forEach(item => {
|
|
|
+ const chapter = item.chapter_code || '未知';
|
|
|
+ chapterCounts[chapter] = (chapterCounts[chapter] || 0) + 1;
|
|
|
+ });
|
|
|
+
|
|
|
+ const sortedChapters = Object.entries(chapterCounts)
|
|
|
+ .sort((a, b) => b[1] - a[1])
|
|
|
+ .slice(0, 5);
|
|
|
+
|
|
|
+ document.getElementById('chapterDistribution').innerHTML =
|
|
|
+ sortedChapters.map(([chapter, count]) => `
|
|
|
+ <div class="progress-item">
|
|
|
+ <div class="progress-header">
|
|
|
+ <span class="progress-label">${chapter}</span>
|
|
|
+ <span class="progress-value">${count}</span>
|
|
|
+ </div>
|
|
|
+ <div class="progress-bar">
|
|
|
+ <div class="progress-fill total" style="width: ${count/total*100}%"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `).join('');
|
|
|
+ }
|
|
|
+
|
|
|
+ function getRiskLevel(item) {
|
|
|
+ const riskInfo = item.risk_info || {};
|
|
|
+ const riskLevel = (riskInfo.risk_level || '').toLowerCase();
|
|
|
+
|
|
|
+ if (riskLevel.includes('高') || riskLevel.includes('high')) return 'high';
|
|
|
+ if (riskLevel.includes('中') || riskLevel.includes('medium')) return 'medium';
|
|
|
+ if (riskLevel.includes('低') || riskLevel.includes('low')) return 'low';
|
|
|
+ return 'none';
|
|
|
+ }
|
|
|
+
|
|
|
+ function applyFilters() {
|
|
|
+ const riskFilter = document.getElementById('riskFilter').value;
|
|
|
+ const chapterFilter = document.getElementById('chapterFilter').value;
|
|
|
+ const issueFilter = document.getElementById('issueFilter').value;
|
|
|
+ const searchInput = document.getElementById('searchInput').value.toLowerCase();
|
|
|
+
|
|
|
+ filteredItems = allReviewItems.filter(item => {
|
|
|
+ if (riskFilter !== 'all' && getRiskLevel(item) !== riskFilter) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (chapterFilter !== 'all' && item.chapter_code !== chapterFilter) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (issueFilter !== 'all') {
|
|
|
+ const hasIssue = item.exist_issue === true;
|
|
|
+ if (issueFilter === 'has-issue' && !hasIssue) return false;
|
|
|
+ if (issueFilter === 'no-issue' && hasIssue) return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (searchInput) {
|
|
|
+ const searchText = [
|
|
|
+ item.check_item,
|
|
|
+ item.check_item_code,
|
|
|
+ item.chapter_code,
|
|
|
+ item.check_result,
|
|
|
+ item.location,
|
|
|
+ item.suggestion,
|
|
|
+ item.reason
|
|
|
+ ].join(' ').toLowerCase();
|
|
|
+ if (!searchText.includes(searchInput)) return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ });
|
|
|
+
|
|
|
+ renderContent();
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderContent() {
|
|
|
+ if (currentView === 'cards') {
|
|
|
+ renderCards();
|
|
|
+ } else {
|
|
|
+ renderTable();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function switchView(view) {
|
|
|
+ currentView = view;
|
|
|
+ document.querySelectorAll('.view-btn').forEach(btn => btn.classList.remove('active'));
|
|
|
+ event.target.classList.add('active');
|
|
|
+ renderContent();
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderCards() {
|
|
|
+ const content = document.getElementById('content');
|
|
|
+
|
|
|
+ if (filteredItems.length === 0) {
|
|
|
+ content.innerHTML = `
|
|
|
+ <div class="empty-state">
|
|
|
+ <div class="empty-state-icon">🔍</div>
|
|
|
+ <h2>没有找到匹配的结果</h2>
|
|
|
+ <p>请尝试调整筛选条件</p>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const cardsHtml = filteredItems.map(item => {
|
|
|
+ const riskLevel = getRiskLevel(item);
|
|
|
+ const hasIssue = item.exist_issue === true;
|
|
|
+
|
|
|
+ return `
|
|
|
+ <div class="review-card ${riskLevel}">
|
|
|
+ <div class="card-header">
|
|
|
+ <div class="card-header-top">
|
|
|
+ <span class="chapter-tag">${escapeHtml(item.chapter_code || '未知章节')}</span>
|
|
|
+ <span class="risk-tag ${riskLevel}">${getRiskLabel(riskLevel)}</span>
|
|
|
+ </div>
|
|
|
+ <div class="check-item-title">${escapeHtml(item.check_item || '未命名检查项')}</div>
|
|
|
+ <div class="check-item-code">${escapeHtml(item.check_item_code || '')}</div>
|
|
|
+ </div>
|
|
|
+ <div class="card-body">
|
|
|
+ <div class="info-section">
|
|
|
+ <div class="info-label">📍 问题位置</div>
|
|
|
+ <div class="info-content location">${escapeHtml(item.location || item.check_result || '未指定')}</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ ${item.suggestion ? `
|
|
|
+ <div class="info-section">
|
|
|
+ <div class="info-label">💡 修改建议</div>
|
|
|
+ <div class="info-content suggestion">${escapeHtml(item.suggestion)}</div>
|
|
|
+ </div>
|
|
|
+ ` : ''}
|
|
|
+
|
|
|
+ ${item.reason ? `
|
|
|
+ <div class="info-section">
|
|
|
+ <div class="info-label">📝 审查依据</div>
|
|
|
+ <div class="info-content reason">${escapeHtml(item.reason)}</div>
|
|
|
+ </div>
|
|
|
+ ` : ''}
|
|
|
+ </div>
|
|
|
+ <div class="card-footer">
|
|
|
+ <div class="status-badge ${hasIssue ? 'has-issue' : 'no-issue'}">
|
|
|
+ <span class="status-dot ${hasIssue ? 'has-issue' : 'no-issue'}"></span>
|
|
|
+ <span>${hasIssue ? '存在问题' : '无问题'}</span>
|
|
|
+ </div>
|
|
|
+ <span style="color: #64748b; font-size: 12px;">${item.sourceFile || ''}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }).join('');
|
|
|
+
|
|
|
+ content.innerHTML = `<div class="cards-container">${cardsHtml}</div>`;
|
|
|
+ }
|
|
|
+
|
|
|
+ function renderTable() {
|
|
|
+ const content = document.getElementById('content');
|
|
|
+
|
|
|
+ if (filteredItems.length === 0) {
|
|
|
+ content.innerHTML = `
|
|
|
+ <div class="empty-state">
|
|
|
+ <div class="empty-state-icon">🔍</div>
|
|
|
+ <h2>没有找到匹配的结果</h2>
|
|
|
+ <p>请尝试调整筛选条件</p>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const rows = filteredItems.map(item => {
|
|
|
+ const riskLevel = getRiskLevel(item);
|
|
|
+ const hasIssue = item.exist_issue === true;
|
|
|
+
|
|
|
+ return `
|
|
|
+ <tr>
|
|
|
+ <td><span class="chapter-tag">${escapeHtml(item.chapter_code || '-')}</span></td>
|
|
|
+ <td>${escapeHtml(item.check_item || '-')}</td>
|
|
|
+ <td><span class="table-risk ${riskLevel}">${getRiskLabel(riskLevel)}</span></td>
|
|
|
+ <td>${escapeHtml((item.location || item.check_result || '-').substring(0, 50))}...</td>
|
|
|
+ <td>
|
|
|
+ <span class="status-badge ${hasIssue ? 'has-issue' : 'no-issue'}">
|
|
|
+ <span class="status-dot ${hasIssue ? 'has-issue' : 'no-issue'}"></span>
|
|
|
+ ${hasIssue ? '存在问题' : '无问题'}
|
|
|
+ </span>
|
|
|
+ </td>
|
|
|
+ <td style="color: #64748b; font-size: 12px;">${item.sourceFile || '-'}</td>
|
|
|
+ </tr>
|
|
|
+ `;
|
|
|
+ }).join('');
|
|
|
+
|
|
|
+ content.innerHTML = `
|
|
|
+ <div class="table-container show">
|
|
|
+ <table>
|
|
|
+ <thead>
|
|
|
+ <tr>
|
|
|
+ <th>章节</th>
|
|
|
+ <th>检查项</th>
|
|
|
+ <th>风险等级</th>
|
|
|
+ <th>问题描述</th>
|
|
|
+ <th>状态</th>
|
|
|
+ <th>来源文件</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody>${rows}</tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+
|
|
|
+ function getRiskLabel(riskLevel) {
|
|
|
+ const labels = {
|
|
|
+ high: '高风险',
|
|
|
+ medium: '中风险',
|
|
|
+ low: '低风险',
|
|
|
+ none: '无风险'
|
|
|
+ };
|
|
|
+ return labels[riskLevel] || '未知';
|
|
|
+ }
|
|
|
+
|
|
|
+ function escapeHtml(text) {
|
|
|
+ if (!text) return '';
|
|
|
+ const div = document.createElement('div');
|
|
|
+ div.textContent = text;
|
|
|
+ return div.innerHTML;
|
|
|
+ }
|
|
|
+
|
|
|
+ function toggleExport() {
|
|
|
+ const panel = document.getElementById('exportPanel');
|
|
|
+ panel.classList.toggle('show');
|
|
|
+ }
|
|
|
+
|
|
|
+ function exportToExcel() {
|
|
|
+ if (filteredItems.length === 0) {
|
|
|
+ showToast('没有可导出的数据', 'error');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ let csv = '\uFEFF章节,检查项,检查项代码,风险等级,问题位置,问题描述,修改建议,审查依据,是否存在问题,来源文件\n';
|
|
|
+
|
|
|
+ filteredItems.forEach(item => {
|
|
|
+ const row = [
|
|
|
+ item.chapter_code || '',
|
|
|
+ item.check_item || '',
|
|
|
+ item.check_item_code || '',
|
|
|
+ getRiskLabel(getRiskLevel(item)),
|
|
|
+ (item.location || '').replace(/,/g, ','),
|
|
|
+ (item.check_result || '').replace(/,/g, ','),
|
|
|
+ (item.suggestion || '').replace(/,/g, ','),
|
|
|
+ (item.reason || '').replace(/,/g, ','),
|
|
|
+ item.exist_issue ? '是' : '否',
|
|
|
+ item.sourceFile || ''
|
|
|
+ ];
|
|
|
+ csv += row.join(',') + '\n';
|
|
|
+ });
|
|
|
+
|
|
|
+ const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
|
|
+ const link = document.createElement('a');
|
|
|
+ link.href = URL.createObjectURL(blob);
|
|
|
+ link.download = `审查结果_${new Date().toISOString().slice(0,10)}.csv`;
|
|
|
+ link.click();
|
|
|
+
|
|
|
+ showToast('Excel导出成功', 'success');
|
|
|
+ }
|
|
|
+
|
|
|
+ function exportToPDF() {
|
|
|
+ showToast('PDF导出功能开发中...', 'success');
|
|
|
+ }
|
|
|
+
|
|
|
+ function exportToJSON() {
|
|
|
+ if (filteredItems.length === 0) {
|
|
|
+ showToast('没有可导出的数据', 'error');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const data = {
|
|
|
+ exportTime: new Date().toISOString(),
|
|
|
+ totalCount: filteredItems.length,
|
|
|
+ items: filteredItems
|
|
|
+ };
|
|
|
+
|
|
|
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
|
|
+ const link = document.createElement('a');
|
|
|
+ link.href = URL.createObjectURL(blob);
|
|
|
+ link.download = `审查结果_${new Date().toISOString().slice(0,10)}.json`;
|
|
|
+ link.click();
|
|
|
+
|
|
|
+ showToast('JSON导出成功', 'success');
|
|
|
+ }
|
|
|
+
|
|
|
+ function clearAll() {
|
|
|
+ allReviewItems = [];
|
|
|
+ filteredItems = [];
|
|
|
+ loadedFiles = [];
|
|
|
+ renderFileList();
|
|
|
+
|
|
|
+ document.getElementById('dashboard').style.display = 'none';
|
|
|
+ document.getElementById('chartsSection').style.display = 'none';
|
|
|
+ document.getElementById('filters').style.display = 'none';
|
|
|
+ document.getElementById('viewToggle').style.display = 'none';
|
|
|
+ document.getElementById('exportPanel').classList.remove('show');
|
|
|
+
|
|
|
+ document.getElementById('content').innerHTML = `
|
|
|
+ <div class="empty-state">
|
|
|
+ <div class="empty-state-icon">📂</div>
|
|
|
+ <h2>数据已清空</h2>
|
|
|
+ <p>请加载新的审查结果文件</p>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+
|
|
|
+ showToast('数据已清空', 'success');
|
|
|
+ }
|
|
|
+
|
|
|
+ function showToast(message, type) {
|
|
|
+ const toast = document.getElementById('toast');
|
|
|
+ toast.className = `toast ${type} show`;
|
|
|
+ toast.querySelector('.toast-message').textContent = message;
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ toast.classList.remove('show');
|
|
|
+ }, 3000);
|
|
|
+ }
|
|
|
+ </script>
|
|
|
+</body>
|
|
|
+</html>
|