@templmf/temp-solf-lmf 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1103 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>达标率看板</title>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/iview/3.5.4/styles/iview.css">
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.14/vue.min.js"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/iview/3.5.4/iview.min.js"></script>
10
+ <style>
11
+ body {
12
+ margin: 0;
13
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
14
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
15
+ min-height: 100vh;
16
+ }
17
+
18
+ .dashboard-container {
19
+ display: flex;
20
+ height: 100vh;
21
+ }
22
+
23
+ .sidebar {
24
+ width: 280px;
25
+ background: rgba(255, 255, 255, 0.95);
26
+ backdrop-filter: blur(10px);
27
+ border-right: 1px solid rgba(102, 126, 234, 0.1);
28
+ box-shadow: 0 8px 32px rgba(102, 126, 234, 0.1);
29
+ }
30
+
31
+ .sidebar-header {
32
+ padding: 32px 24px 24px;
33
+ border-bottom: 1px solid rgba(102, 126, 234, 0.1);
34
+ background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
35
+ color: white;
36
+ }
37
+
38
+ .sidebar-title {
39
+ font-size: 20px;
40
+ font-weight: 700;
41
+ margin-bottom: 8px;
42
+ }
43
+
44
+ .sidebar-subtitle {
45
+ font-size: 14px;
46
+ opacity: 0.8;
47
+ }
48
+
49
+ .sidebar-nav {
50
+ padding: 24px 16px;
51
+ }
52
+
53
+ .nav-item {
54
+ width: 100%;
55
+ margin-bottom: 8px;
56
+ padding: 12px 16px;
57
+ border: none;
58
+ background: transparent;
59
+ text-align: left;
60
+ border-radius: 8px;
61
+ transition: all 0.3s ease;
62
+ font-size: 14px;
63
+ color: #666;
64
+ cursor: pointer;
65
+ }
66
+
67
+ .nav-item:hover {
68
+ background: rgba(24, 144, 255, 0.08);
69
+ color: #1890ff;
70
+ transform: translateX(4px);
71
+ }
72
+
73
+ .nav-item.active {
74
+ background: linear-gradient(135deg, #1890ff, #096dd9);
75
+ color: white;
76
+ box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
77
+ }
78
+
79
+ .legend-box {
80
+ position: absolute;
81
+ bottom: 24px;
82
+ left: 16px;
83
+ right: 16px;
84
+ background: rgba(24, 144, 255, 0.05);
85
+ border: 1px solid rgba(24, 144, 255, 0.1);
86
+ border-radius: 12px;
87
+ padding: 20px;
88
+ }
89
+
90
+ .legend-title {
91
+ font-size: 14px;
92
+ font-weight: 600;
93
+ color: #1890ff;
94
+ margin-bottom: 12px;
95
+ }
96
+
97
+ .legend-item {
98
+ display: flex;
99
+ align-items: center;
100
+ margin-bottom: 8px;
101
+ font-size: 12px;
102
+ color: #666;
103
+ }
104
+
105
+ .legend-dot {
106
+ width: 12px;
107
+ height: 12px;
108
+ border-radius: 50%;
109
+ margin-right: 8px;
110
+ }
111
+
112
+ .main-content {
113
+ flex: 1;
114
+ overflow-y: auto;
115
+ padding: 32px;
116
+ background: rgba(255, 255, 255, 0.05);
117
+ }
118
+
119
+ .stats-grid {
120
+ display: grid;
121
+ grid-template-columns: repeat(4, 1fr);
122
+ gap: 24px;
123
+ margin-bottom: 32px;
124
+ }
125
+
126
+ .stat-card {
127
+ background: rgba(255, 255, 255, 0.95);
128
+ backdrop-filter: blur(10px);
129
+ border-radius: 16px;
130
+ padding: 24px;
131
+ border: 1px solid rgba(24, 144, 255, 0.1);
132
+ box-shadow: 0 8px 32px rgba(24, 144, 255, 0.08);
133
+ transition: all 0.3s ease;
134
+ }
135
+
136
+ .stat-card:hover {
137
+ transform: translateY(-4px);
138
+ box-shadow: 0 16px 48px rgba(24, 144, 255, 0.15);
139
+ }
140
+
141
+ .stat-header {
142
+ display: flex;
143
+ justify-content: space-between;
144
+ align-items: flex-start;
145
+ margin-bottom: 16px;
146
+ }
147
+
148
+ .stat-label {
149
+ font-size: 14px;
150
+ color: #666;
151
+ margin-bottom: 8px;
152
+ }
153
+
154
+ .stat-value {
155
+ font-size: 32px;
156
+ font-weight: 700;
157
+ color: #1890ff;
158
+ line-height: 1;
159
+ }
160
+
161
+ .stat-icon {
162
+ width: 48px;
163
+ height: 48px;
164
+ border-radius: 12px;
165
+ display: flex;
166
+ align-items: center;
167
+ justify-content: center;
168
+ font-size: 20px;
169
+ color: white;
170
+ }
171
+
172
+ .toolbar {
173
+ background: rgba(255, 255, 255, 0.95);
174
+ backdrop-filter: blur(10px);
175
+ border-radius: 16px;
176
+ padding: 20px 24px;
177
+ margin-bottom: 24px;
178
+ border: 1px solid rgba(24, 144, 255, 0.1);
179
+ box-shadow: 0 4px 24px rgba(24, 144, 255, 0.08);
180
+ }
181
+
182
+ .toolbar-content {
183
+ display: flex;
184
+ justify-content: space-between;
185
+ align-items: center;
186
+ }
187
+
188
+ .breadcrumb-nav {
189
+ display: flex;
190
+ align-items: center;
191
+ gap: 8px;
192
+ }
193
+
194
+ .breadcrumb-item {
195
+ background: none;
196
+ border: none;
197
+ color: #1890ff;
198
+ cursor: pointer;
199
+ font-size: 14px;
200
+ padding: 4px 8px;
201
+ border-radius: 4px;
202
+ transition: all 0.2s ease;
203
+ }
204
+
205
+ .breadcrumb-item:hover {
206
+ background: rgba(24, 144, 255, 0.08);
207
+ }
208
+
209
+ .breadcrumb-item.current {
210
+ color: #333;
211
+ cursor: default;
212
+ }
213
+
214
+ .toolbar-actions {
215
+ display: flex;
216
+ gap: 12px;
217
+ align-items: center;
218
+ }
219
+
220
+ .content-area {
221
+ background: rgba(255, 255, 255, 0.95);
222
+ backdrop-filter: blur(10px);
223
+ border-radius: 16px;
224
+ padding: 32px;
225
+ border: 1px solid rgba(24, 144, 255, 0.1);
226
+ box-shadow: 0 8px 32px rgba(24, 144, 255, 0.08);
227
+ }
228
+
229
+ .domain-grid {
230
+ display: grid;
231
+ grid-template-columns: repeat(3, 1fr);
232
+ gap: 32px;
233
+ }
234
+
235
+ .system-grid {
236
+ display: grid;
237
+ grid-template-columns: repeat(2, 1fr);
238
+ gap: 32px;
239
+ }
240
+
241
+ .card {
242
+ background: rgba(255, 255, 255, 0.9);
243
+ border-radius: 16px;
244
+ padding: 24px;
245
+ cursor: pointer;
246
+ transition: all 0.3s ease;
247
+ border: 2px solid transparent;
248
+ position: relative;
249
+ overflow: hidden;
250
+ }
251
+
252
+ .card::before {
253
+ content: '';
254
+ position: absolute;
255
+ top: 0;
256
+ left: 0;
257
+ right: 0;
258
+ height: 4px;
259
+ background: linear-gradient(90deg, #1890ff, #096dd9);
260
+ transform: scaleX(0);
261
+ transition: transform 0.3s ease;
262
+ }
263
+
264
+ .card:hover::before {
265
+ transform: scaleX(1);
266
+ }
267
+
268
+ .card:hover {
269
+ transform: translateY(-8px);
270
+ box-shadow: 0 16px 48px rgba(24, 144, 255, 0.2);
271
+ border-color: rgba(24, 144, 255, 0.2);
272
+ }
273
+
274
+ .card-header {
275
+ display: flex;
276
+ justify-content: space-between;
277
+ align-items: flex-start;
278
+ margin-bottom: 24px;
279
+ }
280
+
281
+ .card-title {
282
+ font-size: 18px;
283
+ font-weight: 700;
284
+ color: #1890ff;
285
+ margin: 0;
286
+ }
287
+
288
+ .trend-icon {
289
+ width: 24px;
290
+ height: 24px;
291
+ border-radius: 50%;
292
+ display: flex;
293
+ align-items: center;
294
+ justify-content: center;
295
+ font-size: 14px;
296
+ }
297
+
298
+ .rate-section {
299
+ margin-bottom: 24px;
300
+ }
301
+
302
+ .rate-label {
303
+ font-size: 14px;
304
+ color: #666;
305
+ margin-bottom: 8px;
306
+ }
307
+
308
+ .rate-value {
309
+ font-size: 36px;
310
+ font-weight: 700;
311
+ color: #1890ff;
312
+ line-height: 1;
313
+ margin-bottom: 12px;
314
+ }
315
+
316
+ .progress-bar {
317
+ width: 100%;
318
+ height: 8px;
319
+ background: rgba(24, 144, 255, 0.1);
320
+ border-radius: 4px;
321
+ overflow: hidden;
322
+ position: relative;
323
+ }
324
+
325
+ .progress-fill {
326
+ height: 100%;
327
+ background: linear-gradient(90deg, #1890ff, #096dd9);
328
+ border-radius: 4px;
329
+ transition: width 0.6s ease;
330
+ position: relative;
331
+ }
332
+
333
+ .progress-fill::after {
334
+ content: '';
335
+ position: absolute;
336
+ top: 0;
337
+ left: 0;
338
+ right: 0;
339
+ bottom: 0;
340
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
341
+ animation: shimmer 2s infinite;
342
+ }
343
+
344
+ @keyframes shimmer {
345
+ 0% { transform: translateX(-100%); }
346
+ 100% { transform: translateX(100%); }
347
+ }
348
+
349
+ .card-stats {
350
+ display: grid;
351
+ grid-template-columns: repeat(2, 1fr);
352
+ gap: 16px;
353
+ }
354
+
355
+ .stat-box {
356
+ background: rgba(24, 144, 255, 0.05);
357
+ border: 1px solid rgba(24, 144, 255, 0.1);
358
+ border-radius: 8px;
359
+ padding: 16px;
360
+ text-align: center;
361
+ }
362
+
363
+ .stat-number {
364
+ font-size: 20px;
365
+ font-weight: 700;
366
+ color: #1890ff;
367
+ margin-bottom: 4px;
368
+ }
369
+
370
+ .stat-text {
371
+ font-size: 12px;
372
+ color: #666;
373
+ }
374
+
375
+ .project-list {
376
+ margin-top: 16px;
377
+ padding-top: 16px;
378
+ border-top: 1px solid rgba(24, 144, 255, 0.1);
379
+ }
380
+
381
+ .project-item {
382
+ display: flex;
383
+ justify-content: space-between;
384
+ align-items: center;
385
+ padding: 8px 0;
386
+ font-size: 12px;
387
+ }
388
+
389
+ .project-name {
390
+ color: #333;
391
+ flex: 1;
392
+ margin-right: 8px;
393
+ overflow: hidden;
394
+ text-overflow: ellipsis;
395
+ white-space: nowrap;
396
+ }
397
+
398
+ .status-tag {
399
+ padding: 4px 8px;
400
+ border-radius: 12px;
401
+ font-size: 10px;
402
+ font-weight: 500;
403
+ }
404
+
405
+ .status-running { background: #f6ffed; color: #52c41a; border: 1px solid #b7eb8f; }
406
+ .status-maintenance { background: #fffbe6; color: #faad14; border: 1px solid #ffe58f; }
407
+ .status-error { background: #fff2f0; color: #ff4d4f; border: 1px solid #ffccc7; }
408
+
409
+ .project-card {
410
+ background: rgba(255, 255, 255, 0.9);
411
+ border-radius: 16px;
412
+ padding: 32px;
413
+ margin-bottom: 24px;
414
+ border: 2px solid rgba(24, 144, 255, 0.1);
415
+ position: relative;
416
+ overflow: hidden;
417
+ }
418
+
419
+ .project-header {
420
+ display: flex;
421
+ justify-content: space-between;
422
+ align-items: flex-start;
423
+ margin-bottom: 32px;
424
+ }
425
+
426
+ .project-title {
427
+ font-size: 24px;
428
+ font-weight: 700;
429
+ color: #1890ff;
430
+ margin-bottom: 12px;
431
+ }
432
+
433
+ .project-rate {
434
+ text-align: right;
435
+ }
436
+
437
+ .project-rate-value {
438
+ font-size: 32px;
439
+ font-weight: 700;
440
+ color: #1890ff;
441
+ margin-bottom: 4px;
442
+ }
443
+
444
+ .project-rate-label {
445
+ font-size: 14px;
446
+ color: #666;
447
+ }
448
+
449
+ .indicators-grid {
450
+ display: grid;
451
+ grid-template-columns: repeat(3, 1fr);
452
+ gap: 24px;
453
+ }
454
+
455
+ .indicator-card {
456
+ background: rgba(24, 144, 255, 0.03);
457
+ border: 1px solid rgba(24, 144, 255, 0.1);
458
+ border-radius: 12px;
459
+ padding: 24px;
460
+ text-align: center;
461
+ position: relative;
462
+ overflow: hidden;
463
+ }
464
+
465
+ .indicator-card::before {
466
+ content: '';
467
+ position: absolute;
468
+ top: 0;
469
+ left: 0;
470
+ right: 0;
471
+ height: 3px;
472
+ background: linear-gradient(90deg, #1890ff, #096dd9);
473
+ }
474
+
475
+ .indicator-label {
476
+ font-size: 14px;
477
+ font-weight: 600;
478
+ color: #666;
479
+ margin-bottom: 16px;
480
+ }
481
+
482
+ .indicator-value {
483
+ font-size: 48px;
484
+ font-weight: 700;
485
+ color: #1890ff;
486
+ margin-bottom: 12px;
487
+ line-height: 1;
488
+ }
489
+
490
+ .indicator-status {
491
+ padding: 6px 12px;
492
+ border-radius: 16px;
493
+ font-size: 12px;
494
+ font-weight: 600;
495
+ margin-bottom: 16px;
496
+ }
497
+
498
+ .status-pass {
499
+ background: #f6ffed;
500
+ color: #52c41a;
501
+ border: 1px solid #b7eb8f;
502
+ }
503
+
504
+ .status-fail {
505
+ background: #fff2f0;
506
+ color: #ff4d4f;
507
+ border: 1px solid #ffccc7;
508
+ }
509
+
510
+ .indicator-progress {
511
+ width: 100%;
512
+ height: 6px;
513
+ background: rgba(24, 144, 255, 0.1);
514
+ border-radius: 3px;
515
+ overflow: hidden;
516
+ }
517
+
518
+ .indicator-progress-fill {
519
+ height: 100%;
520
+ border-radius: 3px;
521
+ transition: width 0.8s ease;
522
+ }
523
+
524
+ .progress-excellent { background: linear-gradient(90deg, #52c41a, #73d13d); }
525
+ .progress-good { background: linear-gradient(90deg, #1890ff, #40a9ff); }
526
+ .progress-poor { background: linear-gradient(90deg, #ff4d4f, #ff7875); }
527
+
528
+ .back-btn {
529
+ background: rgba(24, 144, 255, 0.1);
530
+ border: 1px solid rgba(24, 144, 255, 0.2);
531
+ color: #1890ff;
532
+ border-radius: 8px;
533
+ padding: 8px 16px;
534
+ font-size: 14px;
535
+ cursor: pointer;
536
+ transition: all 0.2s ease;
537
+ }
538
+
539
+ .back-btn:hover {
540
+ background: rgba(24, 144, 255, 0.2);
541
+ transform: translateX(-2px);
542
+ }
543
+
544
+ .search-input {
545
+ width: 280px;
546
+ }
547
+
548
+ .ivu-input-wrapper {
549
+ border-radius: 8px;
550
+ }
551
+
552
+ .ivu-select {
553
+ width: 140px;
554
+ }
555
+
556
+ .ivu-btn {
557
+ border-radius: 8px;
558
+ font-weight: 500;
559
+ }
560
+
561
+ .chevron-icon {
562
+ color: #ccc;
563
+ margin: 0 4px;
564
+ }
565
+
566
+ /* 自定义滚动条 */
567
+ ::-webkit-scrollbar {
568
+ width: 6px;
569
+ }
570
+
571
+ ::-webkit-scrollbar-track {
572
+ background: rgba(24, 144, 255, 0.1);
573
+ border-radius: 3px;
574
+ }
575
+
576
+ ::-webkit-scrollbar-thumb {
577
+ background: rgba(24, 144, 255, 0.3);
578
+ border-radius: 3px;
579
+ }
580
+
581
+ ::-webkit-scrollbar-thumb:hover {
582
+ background: rgba(24, 144, 255, 0.5);
583
+ }
584
+ </style>
585
+ </head>
586
+ <body>
587
+ <div id="app">
588
+ <div class="dashboard-container">
589
+ <!-- 侧边栏 -->
590
+ <div class="sidebar">
591
+ <div class="sidebar-header">
592
+ <div class="sidebar-title">达标率看板</div>
593
+ <div class="sidebar-subtitle">多层级监控平台</div>
594
+ </div>
595
+
596
+ <div class="sidebar-nav">
597
+ <button
598
+ class="nav-item"
599
+ :class="{ active: currentView === 'domain' }"
600
+ @click="navigateToDomain"
601
+ >
602
+ <Icon type="md-grid" style="margin-right: 8px;" />
603
+ 领域总览
604
+ </button>
605
+
606
+ <button
607
+ v-if="selectedDomain"
608
+ class="nav-item"
609
+ :class="{ active: currentView === 'system' }"
610
+ @click="navigateToSystem"
611
+ >
612
+ <Icon type="md-browsers" style="margin-right: 8px;" />
613
+ {{ selectedDomain }}
614
+ </button>
615
+
616
+ <button
617
+ v-if="selectedSystem"
618
+ class="nav-item"
619
+ :class="{ active: currentView === 'project' }"
620
+ @click="navigateToProject"
621
+ >
622
+ <Icon type="md-cube" style="margin-right: 8px;" />
623
+ {{ selectedSystem }}
624
+ </button>
625
+ </div>
626
+
627
+ <div class="legend-box">
628
+ <div class="legend-title">达标率说明</div>
629
+ <div class="legend-item">
630
+ <div class="legend-dot" style="background: #52c41a;"></div>
631
+ <span>≥85% 优秀</span>
632
+ </div>
633
+ <div class="legend-item">
634
+ <div class="legend-dot" style="background: #1890ff;"></div>
635
+ <span>70-84% 良好</span>
636
+ </div>
637
+ <div class="legend-item">
638
+ <div class="legend-dot" style="background: #ff4d4f;"></div>
639
+ <span>&lt;70% 需改进</span>
640
+ </div>
641
+ </div>
642
+ </div>
643
+
644
+ <!-- 主内容区 -->
645
+ <div class="main-content">
646
+ <!-- 顶部统计卡片 -->
647
+ <div class="stats-grid">
648
+ <div class="stat-card">
649
+ <div class="stat-header">
650
+ <div>
651
+ <div class="stat-label">总体达标率</div>
652
+ <div class="stat-value">{{ overallStats.avgRate }}%</div>
653
+ </div>
654
+ <div class="stat-icon" style="background: linear-gradient(135deg, #1890ff, #096dd9);">
655
+ <Icon type="md-analytics" />
656
+ </div>
657
+ </div>
658
+ </div>
659
+
660
+ <div class="stat-card">
661
+ <div class="stat-header">
662
+ <div>
663
+ <div class="stat-label">领域数量</div>
664
+ <div class="stat-value">{{ overallStats.domains }}</div>
665
+ </div>
666
+ <div class="stat-icon" style="background: linear-gradient(135deg, #52c41a, #389e0d);">
667
+ <Icon type="md-apps" />
668
+ </div>
669
+ </div>
670
+ </div>
671
+
672
+ <div class="stat-card">
673
+ <div class="stat-header">
674
+ <div>
675
+ <div class="stat-label">系统数量</div>
676
+ <div class="stat-value">{{ overallStats.systems }}</div>
677
+ </div>
678
+ <div class="stat-icon" style="background: linear-gradient(135deg, #722ed1, #531dab);">
679
+ <Icon type="md-settings" />
680
+ </div>
681
+ </div>
682
+ </div>
683
+
684
+ <div class="stat-card">
685
+ <div class="stat-header">
686
+ <div>
687
+ <div class="stat-label">工程总数</div>
688
+ <div class="stat-value">{{ overallStats.projects }}</div>
689
+ </div>
690
+ <div class="stat-icon" style="background: linear-gradient(135deg, #fa8c16, #d46b08);">
691
+ <Icon type="md-construct" />
692
+ </div>
693
+ </div>
694
+ </div>
695
+ </div>
696
+
697
+ <!-- 工具栏 -->
698
+ <div class="toolbar">
699
+ <div class="toolbar-content">
700
+ <div class="breadcrumb-nav">
701
+ <template v-for="(item, index) in breadcrumb">
702
+ <button
703
+ :key="'btn-' + index"
704
+ class="breadcrumb-item"
705
+ :class="{ current: index === breadcrumb.length - 1 }"
706
+ @click="handleBreadcrumbClick(index)"
707
+ >
708
+ {{ item }}
709
+ </button>
710
+ <span v-if="index < breadcrumb.length - 1" :key="'sep-' + index" class="chevron-icon">
711
+ <Icon type="ios-arrow-forward" />
712
+ </span>
713
+ </template>
714
+ </div>
715
+
716
+ <div class="toolbar-actions">
717
+ <Button
718
+ v-if="currentView !== 'domain'"
719
+ class="back-btn"
720
+ @click="handleBack"
721
+ icon="md-arrow-back"
722
+ size="small"
723
+ >
724
+ 返回
725
+ </Button>
726
+
727
+ <Input
728
+ v-model="searchTerm"
729
+ placeholder="搜索..."
730
+ prefix="ios-search"
731
+ class="search-input"
732
+ size="small"
733
+ />
734
+
735
+ <Select v-model="filterStatus" size="small" style="width: 120px;">
736
+ <Option value="all">所有状态</Option>
737
+ <Option value="运行中">运行中</Option>
738
+ <Option value="维护中">维护中</Option>
739
+ <Option value="异常">异常</Option>
740
+ </Select>
741
+
742
+ <Button icon="md-refresh" size="small">刷新</Button>
743
+ <Button icon="md-download" size="small">导出</Button>
744
+ </div>
745
+ </div>
746
+ </div>
747
+
748
+ <!-- 主要内容区域 -->
749
+ <div class="content-area">
750
+ <!-- 领域视图 -->
751
+ <div v-if="currentView === 'domain'" class="domain-grid">
752
+ <div
753
+ v-for="domain in domainStats"
754
+ :key="domain.name"
755
+ class="card"
756
+ @click="handleDomainClick(domain.name)"
757
+ >
758
+ <div class="card-header">
759
+ <h3 class="card-title">{{ domain.name }}</h3>
760
+ <div class="trend-icon" :style="getTrendIconStyle(domain.rate)">
761
+ <Icon :type="getTrendIcon(domain.rate)" />
762
+ </div>
763
+ </div>
764
+
765
+ <div class="rate-section">
766
+ <div class="rate-label">达标率</div>
767
+ <div class="rate-value">{{ domain.rate.toFixed(1) }}%</div>
768
+ <div class="progress-bar">
769
+ <div
770
+ class="progress-fill"
771
+ :style="{ width: domain.rate + '%' }"
772
+ ></div>
773
+ </div>
774
+ </div>
775
+
776
+ <div class="card-stats">
777
+ <div class="stat-box">
778
+ <div class="stat-number">{{ domain.systemCount }}</div>
779
+ <div class="stat-text">系统</div>
780
+ </div>
781
+ <div class="stat-box">
782
+ <div class="stat-number">{{ domain.projectCount }}</div>
783
+ <div class="stat-text">工程</div>
784
+ </div>
785
+ </div>
786
+ </div>
787
+ </div>
788
+
789
+ <!-- 系统视图 -->
790
+ <div v-if="currentView === 'system'" class="system-grid">
791
+ <div
792
+ v-for="system in systemStats"
793
+ :key="system.name"
794
+ class="card"
795
+ @click="handleSystemClick(system.name)"
796
+ >
797
+ <div class="card-header">
798
+ <h3 class="card-title">{{ system.name }}</h3>
799
+ <div class="trend-icon" :style="getTrendIconStyle(system.rate)">
800
+ <Icon :type="getTrendIcon(system.rate)" />
801
+ </div>
802
+ </div>
803
+
804
+ <div class="rate-section">
805
+ <div class="rate-label">达标率</div>
806
+ <div class="rate-value">{{ system.rate.toFixed(1) }}%</div>
807
+ <div class="progress-bar">
808
+ <div
809
+ class="progress-fill"
810
+ :style="{ width: system.rate + '%' }"
811
+ ></div>
812
+ </div>
813
+ </div>
814
+
815
+ <div class="stat-box">
816
+ <div class="stat-number">{{ system.projectCount }}</div>
817
+ <div class="stat-text">个工程</div>
818
+ </div>
819
+
820
+ <div class="project-list">
821
+ <div
822
+ v-for="(project, index) in system.projects.slice(0, 3)"
823
+ :key="index"
824
+ class="project-item"
825
+ >
826
+ <span class="project-name">{{ project[0] }}</span>
827
+ <span class="status-tag" :class="getStatusClass(project[1].status)">
828
+ {{ project[1].status }}
829
+ </span>
830
+ </div>
831
+ <div v-if="system.projects.length > 3" class="project-item" style="justify-content: center; color: #999;">
832
+ 还有 {{ system.projects.length - 3 }} 个工程...
833
+ </div>
834
+ </div>
835
+ </div>
836
+ </div>
837
+
838
+ <!-- 工程视图 -->
839
+ <div v-if="currentView === 'project'">
840
+ <div
841
+ v-for="project in projectStats"
842
+ :key="project.name"
843
+ class="project-card"
844
+ >
845
+ <div class="project-header">
846
+ <div>
847
+ <h3 class="project-title">{{ project.name }}</h3>
848
+ <Tag :color="getStatusTagColor(project.status)">{{ project.status }}</Tag>
849
+ </div>
850
+ <div class="project-rate">
851
+ <div class="project-rate-value">{{ project.rate.toFixed(1) }}%</div>
852
+ <div class="project-rate-label">达标率</div>
853
+ </div>
854
+ </div>
855
+
856
+ <div class="indicators-grid">
857
+ <div
858
+ v-for="(score, index) in project.indicators"
859
+ :key="index"
860
+ class="indicator-card"
861
+ >
862
+ <div class="indicator-label">指标 {{ index + 1 }}</div>
863
+ <div class="indicator-value">{{ score }}</div>
864
+ <div class="indicator-status" :class="score >= 80 ? 'status-pass' : 'status-fail'">
865
+ {{ score >= 80 ? '达标' : '未达标' }}
866
+ </div>
867
+ <div class="indicator-progress">
868
+ <div
869
+ class="indicator-progress-fill"
870
+ :class="getProgressClass(score)"
871
+ :style="{ width: score + '%' }"
872
+ ></div>
873
+ </div>
874
+ </div>
875
+ </div>
876
+ </div>
877
+ </div>
878
+ </div>
879
+ </div>
880
+ </div>
881
+ </div>
882
+
883
+ <script>
884
+ new Vue({
885
+ el: '#app',
886
+ data() {
887
+ return {
888
+ // 模拟数据
889
+ mockData: {
890
+ "智能制造": {
891
+ "生产管理系统": {
892
+ "MES工程": { indicators: [85, 92, 78], status: "运行中" },
893
+ "WMS工程": { indicators: [90, 88, 95], status: "运行中" },
894
+ "质量管理工程": { indicators: [76, 82, 89], status: "维护中" }
895
+ },
896
+ "设备监控系统": {
897
+ "设备状态监控工程": { indicators: [92, 87, 91], status: "运行中" },
898
+ "预测维护工程": { indicators: [68, 74, 82], status: "异常" },
899
+ "能耗管理工程": { indicators: [89, 93, 85], status: "运行中" }
900
+ }
901
+ },
902
+ "数字化办公": {
903
+ "协同办公系统": {
904
+ "OA系统工程": { indicators: [88, 91, 87], status: "运行中" },
905
+ "文档管理工程": { indicators: [92, 89, 94], status: "运行中" },
906
+ "视频会议工程": { indicators: [79, 85, 88], status: "运行中" }
907
+ },
908
+ "人事管理系统": {
909
+ "招聘管理工程": { indicators: [85, 78, 90], status: "运行中" },
910
+ "绩效考核工程": { indicators: [72, 88, 85], status: "运行中" },
911
+ "薪资管理工程": { indicators: [95, 92, 89], status: "运行中" }
912
+ }
913
+ },
914
+ "数据分析": {
915
+ "商业智能系统": {
916
+ "报表平台工程": { indicators: [91, 86, 88], status: "运行中" },
917
+ "数据挖掘工程": { indicators: [77, 82, 79], status: "运行中" },
918
+ "实时分析工程": { indicators: [83, 89, 91], status: "运行中" }
919
+ }
920
+ }
921
+ },
922
+ currentView: 'domain',
923
+ selectedDomain: null,
924
+ selectedSystem: null,
925
+ breadcrumb: ['领域总览'],
926
+ searchTerm: '',
927
+ filterStatus: 'all'
928
+ };
929
+ },
930
+ computed: {
931
+ // 计算整体统计
932
+ overallStats() {
933
+ const domains = Object.keys(this.mockData).length;
934
+ const systems = Object.values(this.mockData).reduce((sum, domain) => sum + Object.keys(domain).length, 0);
935
+ const projects = Object.values(this.mockData).reduce((sum, domain) =>
936
+ sum + Object.values(domain).reduce((sSum, system) => sSum + Object.keys(system).length, 0), 0
937
+ );
938
+
939
+ const allRates = Object.values(this.mockData).map(domain => this.calculateDomainCompliance(domain));
940
+ const avgRate = allRates.reduce((sum, rate) => sum + rate, 0) / allRates.length;
941
+
942
+ return {
943
+ domains,
944
+ systems,
945
+ projects,
946
+ avgRate: avgRate.toFixed(1)
947
+ };
948
+ },
949
+
950
+ // 领域统计
951
+ domainStats() {
952
+ return Object.entries(this.mockData).map(([domainName, domainData]) => ({
953
+ name: domainName,
954
+ rate: this.calculateDomainCompliance(domainData),
955
+ systemCount: Object.keys(domainData).length,
956
+ projectCount: Object.values(domainData).reduce((sum, system) => sum + Object.keys(system).length, 0)
957
+ }));
958
+ },
959
+
960
+ // 系统统计
961
+ systemStats() {
962
+ if (!this.selectedDomain) return [];
963
+ const systemData = this.mockData[this.selectedDomain];
964
+ return Object.entries(systemData).map(([systemName, projects]) => ({
965
+ name: systemName,
966
+ rate: this.calculateSystemCompliance(projects),
967
+ projectCount: Object.keys(projects).length,
968
+ projects: Object.entries(projects)
969
+ }));
970
+ },
971
+
972
+ // 工程统计
973
+ projectStats() {
974
+ if (!this.selectedDomain || !this.selectedSystem) return [];
975
+ const projectData = this.mockData[this.selectedDomain][this.selectedSystem];
976
+ return Object.entries(projectData).map(([projectName, project]) => ({
977
+ name: projectName,
978
+ indicators: project.indicators,
979
+ status: project.status,
980
+ rate: this.calculateComplianceRate(project.indicators)
981
+ }));
982
+ }
983
+ },
984
+ methods: {
985
+ // 计算达标率 (≥80为达标)
986
+ calculateComplianceRate(indicators) {
987
+ const passCount = indicators.filter(score => score >= 80).length;
988
+ return (passCount / indicators.length) * 100;
989
+ },
990
+
991
+ // 计算系统达标率
992
+ calculateSystemCompliance(systemData) {
993
+ const projects = Object.values(systemData);
994
+ const totalRate = projects.reduce((sum, project) =>
995
+ sum + this.calculateComplianceRate(project.indicators), 0
996
+ );
997
+ return totalRate / projects.length;
998
+ },
999
+
1000
+ // 计算领域达标率
1001
+ calculateDomainCompliance(domainData) {
1002
+ const systems = Object.values(domainData);
1003
+ const totalRate = systems.reduce((sum, system) =>
1004
+ sum + this.calculateSystemCompliance(system), 0
1005
+ );
1006
+ return totalRate / systems.length;
1007
+ },
1008
+
1009
+ // 获取趋势图标
1010
+ getTrendIcon(rate) {
1011
+ if (rate >= 85) return 'md-trending-up';
1012
+ if (rate >= 70) return 'md-remove';
1013
+ return 'md-trending-down';
1014
+ },
1015
+
1016
+ // 获取趋势图标样式
1017
+ getTrendIconStyle(rate) {
1018
+ if (rate >= 85) return 'background: #52c41a; color: white;';
1019
+ if (rate >= 70) return 'background: #1890ff; color: white;';
1020
+ return 'background: #ff4d4f; color: white;';
1021
+ },
1022
+
1023
+ // 获取状态类名
1024
+ getStatusClass(status) {
1025
+ switch (status) {
1026
+ case '运行中': return 'status-running';
1027
+ case '维护中': return 'status-maintenance';
1028
+ case '异常': return 'status-error';
1029
+ default: return '';
1030
+ }
1031
+ },
1032
+
1033
+ // 获取状态标签颜色
1034
+ getStatusTagColor(status) {
1035
+ switch (status) {
1036
+ case '运行中': return 'success';
1037
+ case '维护中': return 'warning';
1038
+ case '异常': return 'error';
1039
+ default: return 'default';
1040
+ }
1041
+ },
1042
+
1043
+ // 获取进度条类名
1044
+ getProgressClass(score) {
1045
+ if (score >= 85) return 'progress-excellent';
1046
+ if (score >= 70) return 'progress-good';
1047
+ return 'progress-poor';
1048
+ },
1049
+
1050
+ // 导航处理
1051
+ handleDomainClick(domainName) {
1052
+ this.selectedDomain = domainName;
1053
+ this.currentView = 'system';
1054
+ this.breadcrumb = ['领域总览', domainName];
1055
+ },
1056
+
1057
+ handleSystemClick(systemName) {
1058
+ this.selectedSystem = systemName;
1059
+ this.currentView = 'project';
1060
+ this.breadcrumb = ['领域总览', this.selectedDomain, systemName];
1061
+ },
1062
+
1063
+ handleBack() {
1064
+ if (this.currentView === 'project') {
1065
+ this.currentView = 'system';
1066
+ this.breadcrumb = ['领域总览', this.selectedDomain];
1067
+ } else if (this.currentView === 'system') {
1068
+ this.currentView = 'domain';
1069
+ this.selectedDomain = null;
1070
+ this.breadcrumb = ['领域总览'];
1071
+ }
1072
+ },
1073
+
1074
+ handleBreadcrumbClick(index) {
1075
+ if (index === 0) {
1076
+ this.navigateToDomain();
1077
+ } else if (index === 1 && this.selectedDomain) {
1078
+ this.navigateToSystem();
1079
+ }
1080
+ },
1081
+
1082
+ navigateToDomain() {
1083
+ this.currentView = 'domain';
1084
+ this.selectedDomain = null;
1085
+ this.selectedSystem = null;
1086
+ this.breadcrumb = ['领域总览'];
1087
+ },
1088
+
1089
+ navigateToSystem() {
1090
+ this.currentView = 'system';
1091
+ this.selectedSystem = null;
1092
+ this.breadcrumb = ['领域总览', this.selectedDomain];
1093
+ },
1094
+
1095
+ navigateToProject() {
1096
+ this.currentView = 'project';
1097
+ this.breadcrumb = ['领域总览', this.selectedDomain, this.selectedSystem];
1098
+ }
1099
+ }
1100
+ });
1101
+ </script>
1102
+ </body>
1103
+ </html>