@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.
package/cankao.txt ADDED
@@ -0,0 +1,1553 @@
1
+ // 主组件 - ComplianceDashboard.vue
2
+ <template>
3
+ <div class="dashboard-container">
4
+ <!-- 侧边栏组件 -->
5
+ <sidebar-nav
6
+ :current-view="currentView"
7
+ :selected-domain="selectedDomain"
8
+ :selected-system="selectedSystem"
9
+ @navigate-to-domain="navigateToDomain"
10
+ @navigate-to-system="navigateToSystem"
11
+ @navigate-to-project="navigateToProject"
12
+ />
13
+
14
+ <!-- 主内容区 -->
15
+ <div class="main-content">
16
+ <!-- 顶部统计组件 -->
17
+ <stats-panel :overall-stats="overallStats" />
18
+
19
+ <!-- 工具栏组件 -->
20
+ <toolbar-panel
21
+ :breadcrumb="breadcrumb"
22
+ :current-view="currentView"
23
+ :search-term="searchTerm"
24
+ :filter-status="filterStatus"
25
+ @breadcrumb-click="handleBreadcrumbClick"
26
+ @back="handleBack"
27
+ @search-change="val => searchTerm = val"
28
+ @filter-change="val => filterStatus = val"
29
+ />
30
+
31
+ <!-- 内容区域组件 -->
32
+ <content-area
33
+ :current-view="currentView"
34
+ :domain-stats="domainStats"
35
+ :system-stats="systemStats"
36
+ :project-stats="projectStats"
37
+ @domain-click="handleDomainClick"
38
+ @system-click="handleSystemClick"
39
+ />
40
+ </div>
41
+ </div>
42
+ </template>
43
+
44
+ <script>
45
+ import SidebarNav from './components/SidebarNav.vue'
46
+ import StatsPanel from './components/StatsPanel.vue'
47
+ import ToolbarPanel from './components/ToolbarPanel.vue'
48
+ import ContentArea from './components/ContentArea.vue'
49
+
50
+ export default {
51
+ name: 'ComplianceDashboard',
52
+ components: {
53
+ SidebarNav,
54
+ StatsPanel,
55
+ ToolbarPanel,
56
+ ContentArea
57
+ },
58
+ data() {
59
+ return {
60
+ // 模拟数据
61
+ mockData: {
62
+ "智能制造": {
63
+ "生产管理系统": {
64
+ "MES工程": { indicators: [85, 92, 78], status: "运行中" },
65
+ "WMS工程": { indicators: [90, 88, 95], status: "运行中" },
66
+ "质量管理工程": { indicators: [76, 82, 89], status: "维护中" }
67
+ },
68
+ "设备监控系统": {
69
+ "设备状态监控工程": { indicators: [92, 87, 91], status: "运行中" },
70
+ "预测维护工程": { indicators: [68, 74, 82], status: "异常" },
71
+ "能耗管理工程": { indicators: [89, 93, 85], status: "运行中" }
72
+ }
73
+ },
74
+ "数字化办公": {
75
+ "协同办公系统": {
76
+ "OA系统工程": { indicators: [88, 91, 87], status: "运行中" },
77
+ "文档管理工程": { indicators: [92, 89, 94], status: "运行中" },
78
+ "视频会议工程": { indicators: [79, 85, 88], status: "运行中" }
79
+ },
80
+ "人事管理系统": {
81
+ "招聘管理工程": { indicators: [85, 78, 90], status: "运行中" },
82
+ "绩效考核工程": { indicators: [72, 88, 85], status: "运行中" },
83
+ "薪资管理工程": { indicators: [95, 92, 89], status: "运行中" }
84
+ }
85
+ },
86
+ "数据分析": {
87
+ "商业智能系统": {
88
+ "报表平台工程": { indicators: [91, 86, 88], status: "运行中" },
89
+ "数据挖掘工程": { indicators: [77, 82, 79], status: "运行中" },
90
+ "实时分析工程": { indicators: [83, 89, 91], status: "运行中" }
91
+ }
92
+ }
93
+ },
94
+ currentView: 'domain',
95
+ selectedDomain: null,
96
+ selectedSystem: null,
97
+ breadcrumb: ['领域总览'],
98
+ searchTerm: '',
99
+ filterStatus: 'all'
100
+ }
101
+ },
102
+ computed: {
103
+ // 计算整体统计
104
+ overallStats() {
105
+ const domains = Object.keys(this.mockData).length
106
+ const systems = Object.values(this.mockData).reduce((sum, domain) => sum + Object.keys(domain).length, 0)
107
+ const projects = Object.values(this.mockData).reduce((sum, domain) =>
108
+ sum + Object.values(domain).reduce((sSum, system) => sSum + Object.keys(system).length, 0), 0
109
+ )
110
+
111
+ const allRates = Object.values(this.mockData).map(domain => this.calculateDomainCompliance(domain))
112
+ const avgRate = allRates.reduce((sum, rate) => sum + rate, 0) / allRates.length
113
+
114
+ return {
115
+ domains,
116
+ systems,
117
+ projects,
118
+ avgRate: avgRate.toFixed(1)
119
+ }
120
+ },
121
+
122
+ // 领域统计
123
+ domainStats() {
124
+ return Object.entries(this.mockData).map(([domainName, domainData]) => ({
125
+ name: domainName,
126
+ rate: this.calculateDomainCompliance(domainData),
127
+ systemCount: Object.keys(domainData).length,
128
+ projectCount: Object.values(domainData).reduce((sum, system) => sum + Object.keys(system).length, 0)
129
+ }))
130
+ },
131
+
132
+ // 系统统计
133
+ systemStats() {
134
+ if (!this.selectedDomain) return []
135
+ const systemData = this.mockData[this.selectedDomain]
136
+ return Object.entries(systemData).map(([systemName, projects]) => ({
137
+ name: systemName,
138
+ rate: this.calculateSystemCompliance(projects),
139
+ projectCount: Object.keys(projects).length,
140
+ projects: Object.entries(projects)
141
+ }))
142
+ },
143
+
144
+ // 工程统计
145
+ projectStats() {
146
+ if (!this.selectedDomain || !this.selectedSystem) return []
147
+ const projectData = this.mockData[this.selectedDomain][this.selectedSystem]
148
+ return Object.entries(projectData).map(([projectName, project]) => ({
149
+ name: projectName,
150
+ indicators: project.indicators,
151
+ status: project.status,
152
+ rate: this.calculateComplianceRate(project.indicators)
153
+ }))
154
+ }
155
+ },
156
+ methods: {
157
+ // 计算达标率 (≥80为达标)
158
+ calculateComplianceRate(indicators) {
159
+ const passCount = indicators.filter(score => score >= 80).length
160
+ return (passCount / indicators.length) * 100
161
+ },
162
+
163
+ // 计算系统达标率
164
+ calculateSystemCompliance(systemData) {
165
+ const projects = Object.values(systemData)
166
+ const totalRate = projects.reduce((sum, project) =>
167
+ sum + this.calculateComplianceRate(project.indicators), 0
168
+ )
169
+ return totalRate / projects.length
170
+ },
171
+
172
+ // 计算领域达标率
173
+ calculateDomainCompliance(domainData) {
174
+ const systems = Object.values(domainData)
175
+ const totalRate = systems.reduce((sum, system) =>
176
+ sum + this.calculateSystemCompliance(system), 0
177
+ )
178
+ return totalRate / systems.length
179
+ },
180
+
181
+ // 导航处理
182
+ handleDomainClick(domainName) {
183
+ this.selectedDomain = domainName
184
+ this.currentView = 'system'
185
+ this.breadcrumb = ['领域总览', domainName]
186
+ },
187
+
188
+ handleSystemClick(systemName) {
189
+ this.selectedSystem = systemName
190
+ this.currentView = 'project'
191
+ this.breadcrumb = ['领域总览', this.selectedDomain, systemName]
192
+ },
193
+
194
+ handleBack() {
195
+ if (this.currentView === 'project') {
196
+ this.currentView = 'system'
197
+ this.breadcrumb = ['领域总览', this.selectedDomain]
198
+ } else if (this.currentView === 'system') {
199
+ this.currentView = 'domain'
200
+ this.selectedDomain = null
201
+ this.breadcrumb = ['领域总览']
202
+ }
203
+ },
204
+
205
+ handleBreadcrumbClick(index) {
206
+ if (index === 0) {
207
+ this.navigateToDomain()
208
+ } else if (index === 1 && this.selectedDomain) {
209
+ this.navigateToSystem()
210
+ }
211
+ },
212
+
213
+ navigateToDomain() {
214
+ this.currentView = 'domain'
215
+ this.selectedDomain = null
216
+ this.selectedSystem = null
217
+ this.breadcrumb = ['领域总览']
218
+ },
219
+
220
+ navigateToSystem() {
221
+ this.currentView = 'system'
222
+ this.selectedSystem = null
223
+ this.breadcrumb = ['领域总览', this.selectedDomain]
224
+ },
225
+
226
+ navigateToProject() {
227
+ this.currentView = 'project'
228
+ this.breadcrumb = ['领域总览', this.selectedDomain, this.selectedSystem]
229
+ }
230
+ }
231
+ }
232
+ </script>
233
+
234
+ <style scoped>
235
+ .dashboard-container {
236
+ display: flex;
237
+ height: 100vh;
238
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
239
+ }
240
+
241
+ .main-content {
242
+ flex: 1;
243
+ overflow-y: auto;
244
+ padding: 32px;
245
+ background: rgba(255, 255, 255, 0.05);
246
+ }
247
+
248
+ /* 自定义滚动条 */
249
+ ::-webkit-scrollbar {
250
+ width: 6px;
251
+ }
252
+
253
+ ::-webkit-scrollbar-track {
254
+ background: rgba(24, 144, 255, 0.1);
255
+ border-radius: 3px;
256
+ }
257
+
258
+ ::-webkit-scrollbar-thumb {
259
+ background: rgba(24, 144, 255, 0.3);
260
+ border-radius: 3px;
261
+ }
262
+
263
+ ::-webkit-scrollbar-thumb:hover {
264
+ background: rgba(24, 144, 255, 0.5);
265
+ }
266
+ </style>
267
+
268
+ ---
269
+
270
+ // 侧边栏组件 - components/SidebarNav.vue
271
+ <template>
272
+ <div class="sidebar">
273
+ <div class="sidebar-header">
274
+ <div class="sidebar-title">达标率看板</div>
275
+ <div class="sidebar-subtitle">多层级监控平台</div>
276
+ </div>
277
+
278
+ <div class="sidebar-nav">
279
+ <button
280
+ class="nav-item"
281
+ :class="{ active: currentView === 'domain' }"
282
+ @click="$emit('navigate-to-domain')"
283
+ >
284
+ <Icon type="md-grid" style="margin-right: 8px;" />
285
+ 领域总览
286
+ </button>
287
+
288
+ <button
289
+ v-if="selectedDomain"
290
+ class="nav-item"
291
+ :class="{ active: currentView === 'system' }"
292
+ @click="$emit('navigate-to-system')"
293
+ >
294
+ <Icon type="md-browsers" style="margin-right: 8px;" />
295
+ {{ selectedDomain }}
296
+ </button>
297
+
298
+ <button
299
+ v-if="selectedSystem"
300
+ class="nav-item"
301
+ :class="{ active: currentView === 'project' }"
302
+ @click="$emit('navigate-to-project')"
303
+ >
304
+ <Icon type="md-cube" style="margin-right: 8px;" />
305
+ {{ selectedSystem }}
306
+ </button>
307
+ </div>
308
+
309
+ <div class="legend-box">
310
+ <div class="legend-title">达标率说明</div>
311
+ <div class="legend-item">
312
+ <div class="legend-dot" style="background: #52c41a;"></div>
313
+ <span>≥85% 优秀</span>
314
+ </div>
315
+ <div class="legend-item">
316
+ <div class="legend-dot" style="background: #1890ff;"></div>
317
+ <span>70-84% 良好</span>
318
+ </div>
319
+ <div class="legend-item">
320
+ <div class="legend-dot" style="background: #ff4d4f;"></div>
321
+ <span>&lt;70% 需改进</span>
322
+ </div>
323
+ </div>
324
+ </div>
325
+ </template>
326
+
327
+ <script>
328
+ export default {
329
+ name: 'SidebarNav',
330
+ props: {
331
+ currentView: {
332
+ type: String,
333
+ required: true
334
+ },
335
+ selectedDomain: {
336
+ type: String,
337
+ default: null
338
+ },
339
+ selectedSystem: {
340
+ type: String,
341
+ default: null
342
+ }
343
+ }
344
+ }
345
+ </script>
346
+
347
+ <style scoped>
348
+ .sidebar {
349
+ width: 280px;
350
+ background: rgba(255, 255, 255, 0.95);
351
+ backdrop-filter: blur(10px);
352
+ border-right: 1px solid rgba(102, 126, 234, 0.1);
353
+ box-shadow: 0 8px 32px rgba(102, 126, 234, 0.1);
354
+ }
355
+
356
+ .sidebar-header {
357
+ padding: 32px 24px 24px;
358
+ border-bottom: 1px solid rgba(102, 126, 234, 0.1);
359
+ background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
360
+ color: white;
361
+ }
362
+
363
+ .sidebar-title {
364
+ font-size: 20px;
365
+ font-weight: 700;
366
+ margin-bottom: 8px;
367
+ }
368
+
369
+ .sidebar-subtitle {
370
+ font-size: 14px;
371
+ opacity: 0.8;
372
+ }
373
+
374
+ .sidebar-nav {
375
+ padding: 24px 16px;
376
+ }
377
+
378
+ .nav-item {
379
+ width: 100%;
380
+ margin-bottom: 8px;
381
+ padding: 12px 16px;
382
+ border: none;
383
+ background: transparent;
384
+ text-align: left;
385
+ border-radius: 8px;
386
+ transition: all 0.3s ease;
387
+ font-size: 14px;
388
+ color: #666;
389
+ cursor: pointer;
390
+ }
391
+
392
+ .nav-item:hover {
393
+ background: rgba(24, 144, 255, 0.08);
394
+ color: #1890ff;
395
+ transform: translateX(4px);
396
+ }
397
+
398
+ .nav-item.active {
399
+ background: linear-gradient(135deg, #1890ff, #096dd9);
400
+ color: white;
401
+ box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
402
+ }
403
+
404
+ .legend-box {
405
+ position: absolute;
406
+ bottom: 24px;
407
+ left: 16px;
408
+ right: 16px;
409
+ background: rgba(24, 144, 255, 0.05);
410
+ border: 1px solid rgba(24, 144, 255, 0.1);
411
+ border-radius: 12px;
412
+ padding: 20px;
413
+ }
414
+
415
+ .legend-title {
416
+ font-size: 14px;
417
+ font-weight: 600;
418
+ color: #1890ff;
419
+ margin-bottom: 12px;
420
+ }
421
+
422
+ .legend-item {
423
+ display: flex;
424
+ align-items: center;
425
+ margin-bottom: 8px;
426
+ font-size: 12px;
427
+ color: #666;
428
+ }
429
+
430
+ .legend-dot {
431
+ width: 12px;
432
+ height: 12px;
433
+ border-radius: 50%;
434
+ margin-right: 8px;
435
+ }
436
+ </style>
437
+
438
+ ---
439
+
440
+ // 统计面板组件 - components/StatsPanel.vue
441
+ <template>
442
+ <div class="stats-grid">
443
+ <div class="stat-card">
444
+ <div class="stat-header">
445
+ <div>
446
+ <div class="stat-label">总体达标率</div>
447
+ <div class="stat-value">{{ overallStats.avgRate }}%</div>
448
+ </div>
449
+ <div class="stat-icon" style="background: linear-gradient(135deg, #1890ff, #096dd9);">
450
+ <Icon type="md-analytics" />
451
+ </div>
452
+ </div>
453
+ </div>
454
+
455
+ <div class="stat-card">
456
+ <div class="stat-header">
457
+ <div>
458
+ <div class="stat-label">领域数量</div>
459
+ <div class="stat-value">{{ overallStats.domains }}</div>
460
+ </div>
461
+ <div class="stat-icon" style="background: linear-gradient(135deg, #52c41a, #389e0d);">
462
+ <Icon type="md-apps" />
463
+ </div>
464
+ </div>
465
+ </div>
466
+
467
+ <div class="stat-card">
468
+ <div class="stat-header">
469
+ <div>
470
+ <div class="stat-label">系统数量</div>
471
+ <div class="stat-value">{{ overallStats.systems }}</div>
472
+ </div>
473
+ <div class="stat-icon" style="background: linear-gradient(135deg, #722ed1, #531dab);">
474
+ <Icon type="md-settings" />
475
+ </div>
476
+ </div>
477
+ </div>
478
+
479
+ <div class="stat-card">
480
+ <div class="stat-header">
481
+ <div>
482
+ <div class="stat-label">工程总数</div>
483
+ <div class="stat-value">{{ overallStats.projects }}</div>
484
+ </div>
485
+ <div class="stat-icon" style="background: linear-gradient(135deg, #fa8c16, #d46b08);">
486
+ <Icon type="md-construct" />
487
+ </div>
488
+ </div>
489
+ </div>
490
+ </div>
491
+ </template>
492
+
493
+ <script>
494
+ export default {
495
+ name: 'StatsPanel',
496
+ props: {
497
+ overallStats: {
498
+ type: Object,
499
+ required: true
500
+ }
501
+ }
502
+ }
503
+ </script>
504
+
505
+ <style scoped>
506
+ .stats-grid {
507
+ display: grid;
508
+ grid-template-columns: repeat(4, 1fr);
509
+ gap: 24px;
510
+ margin-bottom: 32px;
511
+ }
512
+
513
+ .stat-card {
514
+ background: rgba(255, 255, 255, 0.95);
515
+ backdrop-filter: blur(10px);
516
+ border-radius: 16px;
517
+ padding: 24px;
518
+ border: 1px solid rgba(24, 144, 255, 0.1);
519
+ box-shadow: 0 8px 32px rgba(24, 144, 255, 0.08);
520
+ transition: all 0.3s ease;
521
+ }
522
+
523
+ .stat-card:hover {
524
+ transform: translateY(-4px);
525
+ box-shadow: 0 16px 48px rgba(24, 144, 255, 0.15);
526
+ }
527
+
528
+ .stat-header {
529
+ display: flex;
530
+ justify-content: space-between;
531
+ align-items: flex-start;
532
+ margin-bottom: 16px;
533
+ }
534
+
535
+ .stat-label {
536
+ font-size: 14px;
537
+ color: #666;
538
+ margin-bottom: 8px;
539
+ }
540
+
541
+ .stat-value {
542
+ font-size: 32px;
543
+ font-weight: 700;
544
+ color: #1890ff;
545
+ line-height: 1;
546
+ }
547
+
548
+ .stat-icon {
549
+ width: 48px;
550
+ height: 48px;
551
+ border-radius: 12px;
552
+ display: flex;
553
+ align-items: center;
554
+ justify-content: center;
555
+ font-size: 20px;
556
+ color: white;
557
+ }
558
+ </style>
559
+
560
+ ---
561
+
562
+ // 工具栏组件 - components/ToolbarPanel.vue
563
+ <template>
564
+ <div class="toolbar">
565
+ <div class="toolbar-content">
566
+ <div class="breadcrumb-nav">
567
+ <template v-for="(item, index) in breadcrumb">
568
+ <button
569
+ :key="'btn-' + index"
570
+ class="breadcrumb-item"
571
+ :class="{ current: index === breadcrumb.length - 1 }"
572
+ @click="$emit('breadcrumb-click', index)"
573
+ >
574
+ {{ item }}
575
+ </button>
576
+ <span v-if="index < breadcrumb.length - 1" :key="'sep-' + index" class="chevron-icon">
577
+ <Icon type="ios-arrow-forward" />
578
+ </span>
579
+ </template>
580
+ </div>
581
+
582
+ <div class="toolbar-actions">
583
+ <Button
584
+ v-if="currentView !== 'domain'"
585
+ class="back-btn"
586
+ @click="$emit('back')"
587
+ icon="md-arrow-back"
588
+ size="small"
589
+ >
590
+ 返回
591
+ </Button>
592
+
593
+ <Input
594
+ :value="searchTerm"
595
+ @input="$emit('search-change', $event)"
596
+ placeholder="搜索..."
597
+ prefix="ios-search"
598
+ class="search-input"
599
+ size="small"
600
+ />
601
+
602
+ <Select
603
+ :value="filterStatus"
604
+ @input="$emit('filter-change', $event)"
605
+ size="small"
606
+ style="width: 120px;"
607
+ >
608
+ <Option value="all">所有状态</Option>
609
+ <Option value="运行中">运行中</Option>
610
+ <Option value="维护中">维护中</Option>
611
+ <Option value="异常">异常</Option>
612
+ </Select>
613
+
614
+ <Button icon="md-refresh" size="small">刷新</Button>
615
+ <Button icon="md-download" size="small">导出</Button>
616
+ </div>
617
+ </div>
618
+ </div>
619
+ </template>
620
+
621
+ <script>
622
+ export default {
623
+ name: 'ToolbarPanel',
624
+ props: {
625
+ breadcrumb: {
626
+ type: Array,
627
+ required: true
628
+ },
629
+ currentView: {
630
+ type: String,
631
+ required: true
632
+ },
633
+ searchTerm: {
634
+ type: String,
635
+ required: true
636
+ },
637
+ filterStatus: {
638
+ type: String,
639
+ required: true
640
+ }
641
+ }
642
+ }
643
+ </script>
644
+
645
+ <style scoped>
646
+ .toolbar {
647
+ background: rgba(255, 255, 255, 0.95);
648
+ backdrop-filter: blur(10px);
649
+ border-radius: 16px;
650
+ padding: 20px 24px;
651
+ margin-bottom: 24px;
652
+ border: 1px solid rgba(24, 144, 255, 0.1);
653
+ box-shadow: 0 4px 24px rgba(24, 144, 255, 0.08);
654
+ }
655
+
656
+ .toolbar-content {
657
+ display: flex;
658
+ justify-content: space-between;
659
+ align-items: center;
660
+ }
661
+
662
+ .breadcrumb-nav {
663
+ display: flex;
664
+ align-items: center;
665
+ gap: 8px;
666
+ }
667
+
668
+ .breadcrumb-item {
669
+ background: none;
670
+ border: none;
671
+ color: #1890ff;
672
+ cursor: pointer;
673
+ font-size: 14px;
674
+ padding: 4px 8px;
675
+ border-radius: 4px;
676
+ transition: all 0.2s ease;
677
+ }
678
+
679
+ .breadcrumb-item:hover {
680
+ background: rgba(24, 144, 255, 0.08);
681
+ }
682
+
683
+ .breadcrumb-item.current {
684
+ color: #333;
685
+ cursor: default;
686
+ }
687
+
688
+ .toolbar-actions {
689
+ display: flex;
690
+ gap: 12px;
691
+ align-items: center;
692
+ }
693
+
694
+ .back-btn {
695
+ background: rgba(24, 144, 255, 0.1);
696
+ border: 1px solid rgba(24, 144, 255, 0.2);
697
+ color: #1890ff;
698
+ border-radius: 8px;
699
+ padding: 8px 16px;
700
+ font-size: 14px;
701
+ cursor: pointer;
702
+ transition: all 0.2s ease;
703
+ }
704
+
705
+ .back-btn:hover {
706
+ background: rgba(24, 144, 255, 0.2);
707
+ transform: translateX(-2px);
708
+ }
709
+
710
+ .search-input {
711
+ width: 280px;
712
+ }
713
+
714
+ .chevron-icon {
715
+ color: #ccc;
716
+ margin: 0 4px;
717
+ }
718
+ </style>
719
+
720
+ ---
721
+
722
+ // 内容区域组件 - components/ContentArea.vue
723
+ <template>
724
+ <div class="content-area">
725
+ <!-- 领域视图 -->
726
+ <domain-grid-view
727
+ v-if="currentView === 'domain'"
728
+ :domain-stats="domainStats"
729
+ @domain-click="$emit('domain-click', $event)"
730
+ />
731
+
732
+ <!-- 系统视图 -->
733
+ <system-grid-view
734
+ v-if="currentView === 'system'"
735
+ :system-stats="systemStats"
736
+ @system-click="$emit('system-click', $event)"
737
+ />
738
+
739
+ <!-- 工程视图 -->
740
+ <project-list-view
741
+ v-if="currentView === 'project'"
742
+ :project-stats="projectStats"
743
+ />
744
+ </div>
745
+ </template>
746
+
747
+ <script>
748
+ import DomainGridView from './DomainGridView.vue'
749
+ import SystemGridView from './SystemGridView.vue'
750
+ import ProjectListView from './ProjectListView.vue'
751
+
752
+ export default {
753
+ name: 'ContentArea',
754
+ components: {
755
+ DomainGridView,
756
+ SystemGridView,
757
+ ProjectListView
758
+ },
759
+ props: {
760
+ currentView: {
761
+ type: String,
762
+ required: true
763
+ },
764
+ domainStats: {
765
+ type: Array,
766
+ default: () => []
767
+ },
768
+ systemStats: {
769
+ type: Array,
770
+ default: () => []
771
+ },
772
+ projectStats: {
773
+ type: Array,
774
+ default: () => []
775
+ }
776
+ }
777
+ }
778
+ </script>
779
+
780
+ <style scoped>
781
+ .content-area {
782
+ background: rgba(255, 255, 255, 0.95);
783
+ backdrop-filter: blur(10px);
784
+ border-radius: 16px;
785
+ padding: 32px;
786
+ border: 1px solid rgba(24, 144, 255, 0.1);
787
+ box-shadow: 0 8px 32px rgba(24, 144, 255, 0.08);
788
+ }
789
+ </style>
790
+
791
+ ---
792
+
793
+ // 领域网格视图组件 - components/DomainGridView.vue
794
+ <template>
795
+ <div class="domain-grid">
796
+ <div
797
+ v-for="domain in domainStats"
798
+ :key="domain.name"
799
+ class="card"
800
+ @click="$emit('domain-click', domain.name)"
801
+ >
802
+ <div class="card-header">
803
+ <h3 class="card-title">{{ domain.name }}</h3>
804
+ <div class="trend-icon" :style="getTrendIconStyle(domain.rate)">
805
+ <Icon :type="getTrendIcon(domain.rate)" />
806
+ </div>
807
+ </div>
808
+
809
+ <div class="rate-section">
810
+ <div class="rate-label">达标率</div>
811
+ <div class="rate-value">{{ domain.rate.toFixed(1) }}%</div>
812
+ <div class="progress-bar">
813
+ <div
814
+ class="progress-fill"
815
+ :style="{ width: domain.rate + '%' }"
816
+ ></div>
817
+ </div>
818
+ </div>
819
+
820
+ <div class="card-stats">
821
+ <div class="stat-box">
822
+ <div class="stat-number">{{ domain.systemCount }}</div>
823
+ <div class="stat-text">系统</div>
824
+ </div>
825
+ <div class="stat-box">
826
+ <div class="stat-number">{{ domain.projectCount }}</div>
827
+ <div class="stat-text">工程</div>
828
+ </div>
829
+ </div>
830
+ </div>
831
+ </div>
832
+ </template>
833
+
834
+ <script>
835
+ export default {
836
+ name: 'DomainGridView',
837
+ props: {
838
+ domainStats: {
839
+ type: Array,
840
+ required: true
841
+ }
842
+ },
843
+ methods: {
844
+ getTrendIcon(rate) {
845
+ if (rate >= 85) return 'md-trending-up'
846
+ if (rate >= 70) return 'md-remove'
847
+ return 'md-trending-down'
848
+ },
849
+ getTrendIconStyle(rate) {
850
+ if (rate >= 85) return 'background: #52c41a; color: white;'
851
+ if (rate >= 70) return 'background: #1890ff; color: white;'
852
+ return 'background: #ff4d4f; color: white;'
853
+ }
854
+ }
855
+ }
856
+ </script>
857
+
858
+ <style scoped>
859
+ .domain-grid {
860
+ display: grid;
861
+ grid-template-columns: repeat(3, 1fr);
862
+ gap: 32px;
863
+ }
864
+
865
+ .card {
866
+ background: rgba(255, 255, 255, 0.9);
867
+ border-radius: 16px;
868
+ padding: 24px;
869
+ cursor: pointer;
870
+ transition: all 0.3s ease;
871
+ border: 2px solid transparent;
872
+ position: relative;
873
+ overflow: hidden;
874
+ }
875
+
876
+ .card::before {
877
+ content: '';
878
+ position: absolute;
879
+ top: 0;
880
+ left: 0;
881
+ right: 0;
882
+ height: 4px;
883
+ background: linear-gradient(90deg, #1890ff, #096dd9);
884
+ transform: scaleX(0);
885
+ transition: transform 0.3s ease;
886
+ }
887
+
888
+ .card:hover::before {
889
+ transform: scaleX(1);
890
+ }
891
+
892
+ .card:hover {
893
+ transform: translateY(-8px);
894
+ box-shadow: 0 16px 48px rgba(24, 144, 255, 0.2);
895
+ border-color: rgba(24, 144, 255, 0.2);
896
+ }
897
+
898
+ .card-header {
899
+ display: flex;
900
+ justify-content: space-between;
901
+ align-items: flex-start;
902
+ margin-bottom: 24px;
903
+ }
904
+
905
+ .card-title {
906
+ font-size: 18px;
907
+ font-weight: 700;
908
+ color: #1890ff;
909
+ margin: 0;
910
+ }
911
+
912
+ .trend-icon {
913
+ width: 24px;
914
+ height: 24px;
915
+ border-radius: 50%;
916
+ display: flex;
917
+ align-items: center;
918
+ justify-content: center;
919
+ font-size: 14px;
920
+ }
921
+
922
+ .rate-section {
923
+ margin-bottom: 24px;
924
+ }
925
+
926
+ .rate-label {
927
+ font-size: 14px;
928
+ color: #666;
929
+ margin-bottom: 8px;
930
+ }
931
+
932
+ .rate-value {
933
+ font-size: 36px;
934
+ font-weight: 700;
935
+ color: #1890ff;
936
+ line-height: 1;
937
+ margin-bottom: 12px;
938
+ }
939
+
940
+ .progress-bar {
941
+ width: 100%;
942
+ height: 8px;
943
+ background: rgba(24, 144, 255, 0.1);
944
+ border-radius: 4px;
945
+ overflow: hidden;
946
+ position: relative;
947
+ }
948
+
949
+ .progress-fill {
950
+ height: 100%;
951
+ background: linear-gradient(90deg, #1890ff, #096dd9);
952
+ border-radius: 4px;
953
+ transition: width 0.6s ease;
954
+ position: relative;
955
+ }
956
+
957
+ .progress-fill::after {
958
+ content: '';
959
+ position: absolute;
960
+ top: 0;
961
+ left: 0;
962
+ right: 0;
963
+ bottom: 0;
964
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
965
+ animation: shimmer 2s infinite;
966
+ }
967
+
968
+ @keyframes shimmer {
969
+ 0% { transform: translateX(-100%); }
970
+ 100% { transform: translateX(100%); }
971
+ }
972
+
973
+ .card-stats {
974
+ display: grid;
975
+ grid-template-columns: repeat(2, 1fr);
976
+ gap: 16px;
977
+ }
978
+
979
+ .stat-box {
980
+ background: rgba(24, 144, 255, 0.05);
981
+ border: 1px solid rgba(24, 144, 255, 0.1);
982
+ border-radius: 8px;
983
+ padding: 16px;
984
+ text-align: center;
985
+ }
986
+
987
+ .stat-number {
988
+ font-size: 20px;
989
+ font-weight: 700;
990
+ color: #1890ff;
991
+ margin-bottom: 4px;
992
+ }
993
+
994
+ .stat-text {
995
+ font-size: 12px;
996
+ color: #666;
997
+ }
998
+ </style>
999
+
1000
+ ---
1001
+
1002
+ // 系统网格视图组件 - components/SystemGridView.vue
1003
+ <template>
1004
+ <div class="system-grid">
1005
+ <div
1006
+ v-for="system in systemStats"
1007
+ :key="system.name"
1008
+ class="card"
1009
+ @click="$emit('system-click', system.name)"
1010
+ >
1011
+ <div class="card-header">
1012
+ <h3 class="card-title">{{ system.name }}</h3>
1013
+ <div class="trend-icon" :style="getTrendIconStyle(system.rate)">
1014
+ <Icon :type="getTrendIcon(system.rate)" />
1015
+ </div>
1016
+ </div>
1017
+
1018
+ <div class="rate-section">
1019
+ <div class="rate-label">达标率</div>
1020
+ <div class="rate-value">{{ system.rate.toFixed(1) }}%</div>
1021
+ <div class="progress-bar">
1022
+ <div
1023
+ class="progress-fill"
1024
+ :style="{ width: system.rate + '%' }"
1025
+ ></div>
1026
+ </div>
1027
+ </div>
1028
+
1029
+ <div class="stat-box">
1030
+ <div class="stat-number">{{ system.projectCount }}</div>
1031
+ <div class="stat-text">个工程</div>
1032
+ </div>
1033
+
1034
+ <div class="project-list">
1035
+ <div
1036
+ v-for="(project, index) in system.projects.slice(0, 3)"
1037
+ :key="index"
1038
+ class="project-item"
1039
+ >
1040
+ <span class="project-name">{{ project[0] }}</span>
1041
+ <span class="status-tag" :class="getStatusClass(project[1].status)">
1042
+ {{ project[1].status }}
1043
+ </span>
1044
+ </div>
1045
+ <div v-if="system.projects.length > 3" class="project-item" style="justify-content: center; color: #999;">
1046
+ 还有 {{ system.projects.length - 3 }} 个工程...
1047
+ </div>
1048
+ </div>
1049
+ </div>
1050
+ </div>
1051
+ </template>
1052
+
1053
+ <script>
1054
+ export default {
1055
+ name: 'SystemGridView',
1056
+ props: {
1057
+ systemStats: {
1058
+ type: Array,
1059
+ required: true
1060
+ }
1061
+ },
1062
+ methods: {
1063
+ getTrendIcon(rate) {
1064
+ if (rate >= 85) return 'md-trending-up'
1065
+ if (rate >= 70) return 'md-remove'
1066
+ return 'md-trending-down'
1067
+ },
1068
+ getTrendIconStyle(rate) {
1069
+ if (rate >= 85) return 'background: #52c41a; color: white;'
1070
+ if (rate >= 70) return 'background: #1890ff; color: white;'
1071
+ return 'background: #ff4d4f; color: white;'
1072
+ },
1073
+ getStatusClass(status) {
1074
+ switch (status) {
1075
+ case '运行中': return 'status-running'
1076
+ case '维护中': return 'status-maintenance'
1077
+ case '异常': return 'status-error'
1078
+ default: return ''
1079
+ }
1080
+ }
1081
+ }
1082
+ }
1083
+ </script>
1084
+
1085
+ <style scoped>
1086
+ .system-grid {
1087
+ display: grid;
1088
+ grid-template-columns: repeat(2, 1fr);
1089
+ gap: 32px;
1090
+ }
1091
+
1092
+ .card {
1093
+ background: rgba(255, 255, 255, 0.9);
1094
+ border-radius: 16px;
1095
+ padding: 24px;
1096
+ cursor: pointer;
1097
+ transition: all 0.3s ease;
1098
+ border: 2px solid transparent;
1099
+ position: relative;
1100
+ overflow: hidden;
1101
+ }
1102
+
1103
+ .card::before {
1104
+ content: '';
1105
+ position: absolute;
1106
+ top: 0;
1107
+ left: 0;
1108
+ right: 0;
1109
+ height: 4px;
1110
+ background: linear-gradient(90deg, #1890ff, #096dd9);
1111
+ transform: scaleX(0);
1112
+ transition: transform 0.3s ease;
1113
+ }
1114
+
1115
+ .card:hover::before {
1116
+ transform: scaleX(1);
1117
+ }
1118
+
1119
+ .card:hover {
1120
+ transform: translateY(-8px);
1121
+ box-shadow: 0 16px 48px rgba(24, 144, 255, 0.2);
1122
+ border-color: rgba(24, 144, 255, 0.2);
1123
+ }
1124
+
1125
+ .card-header {
1126
+ display: flex;
1127
+ justify-content: space-between;
1128
+ align-items: flex-start;
1129
+ margin-bottom: 24px;
1130
+ }
1131
+
1132
+ .card-title {
1133
+ font-size: 18px;
1134
+ font-weight: 700;
1135
+ color: #1890ff;
1136
+ margin: 0;
1137
+ }
1138
+
1139
+ .trend-icon {
1140
+ width: 24px;
1141
+ height: 24px;
1142
+ border-radius: 50%;
1143
+ display: flex;
1144
+ align-items: center;
1145
+ justify-content: center;
1146
+ font-size: 14px;
1147
+ }
1148
+
1149
+ .rate-section {
1150
+ margin-bottom: 24px;
1151
+ }
1152
+
1153
+ .rate-label {
1154
+ font-size: 14px;
1155
+ color: #666;
1156
+ margin-bottom: 8px;
1157
+ }
1158
+
1159
+ .rate-value {
1160
+ font-size: 36px;
1161
+ font-weight: 700;
1162
+ color: #1890ff;
1163
+ line-height: 1;
1164
+ margin-bottom: 12px;
1165
+ }
1166
+
1167
+ .progress-bar {
1168
+ width: 100%;
1169
+ height: 8px;
1170
+ background: rgba(24, 144, 255, 0.1);
1171
+ border-radius: 4px;
1172
+ overflow: hidden;
1173
+ position: relative;
1174
+ }
1175
+
1176
+ .progress-fill {
1177
+ height: 100%;
1178
+ background: linear-gradient(90deg, #1890ff, #096dd9);
1179
+ border-radius: 4px;
1180
+ transition: width 0.6s ease;
1181
+ position: relative;
1182
+ }
1183
+
1184
+ .progress-fill::after {
1185
+ content: '';
1186
+ position: absolute;
1187
+ top: 0;
1188
+ left: 0;
1189
+ right: 0;
1190
+ bottom: 0;
1191
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
1192
+ animation: shimmer 2s infinite;
1193
+ }
1194
+
1195
+ @keyframes shimmer {
1196
+ 0% { transform: translateX(-100%); }
1197
+ 100% { transform: translateX(100%); }
1198
+ }
1199
+
1200
+ .stat-box {
1201
+ background: rgba(24, 144, 255, 0.05);
1202
+ border: 1px solid rgba(24, 144, 255, 0.1);
1203
+ border-radius: 8px;
1204
+ padding: 16px;
1205
+ text-align: center;
1206
+ margin-bottom: 16px;
1207
+ }
1208
+
1209
+ .stat-number {
1210
+ font-size: 20px;
1211
+ font-weight: 700;
1212
+ color: #1890ff;
1213
+ margin-bottom: 4px;
1214
+ }
1215
+
1216
+ .stat-text {
1217
+ font-size: 12px;
1218
+ color: #666;
1219
+ }
1220
+
1221
+ .project-list {
1222
+ padding-top: 16px;
1223
+ border-top: 1px solid rgba(24, 144, 255, 0.1);
1224
+ }
1225
+
1226
+ .project-item {
1227
+ display: flex;
1228
+ justify-content: space-between;
1229
+ align-items: center;
1230
+ padding: 8px 0;
1231
+ font-size: 12px;
1232
+ }
1233
+
1234
+ .project-name {
1235
+ color: #333;
1236
+ flex: 1;
1237
+ margin-right: 8px;
1238
+ overflow: hidden;
1239
+ text-overflow: ellipsis;
1240
+ white-space: nowrap;
1241
+ }
1242
+
1243
+ .status-tag {
1244
+ padding: 4px 8px;
1245
+ border-radius: 12px;
1246
+ font-size: 10px;
1247
+ font-weight: 500;
1248
+ }
1249
+
1250
+ .status-running {
1251
+ background: #f6ffed;
1252
+ color: #52c41a;
1253
+ border: 1px solid #b7eb8f;
1254
+ }
1255
+
1256
+ .status-maintenance {
1257
+ background: #fffbe6;
1258
+ color: #faad14;
1259
+ border: 1px solid #ffe58f;
1260
+ }
1261
+
1262
+ .status-error {
1263
+ background: #fff2f0;
1264
+ color: #ff4d4f;
1265
+ border: 1px solid #ffccc7;
1266
+ }
1267
+ </style>
1268
+
1269
+ ---
1270
+
1271
+ // 工程列表视图组件 - components/ProjectListView.vue
1272
+ <template>
1273
+ <div>
1274
+ <div
1275
+ v-for="project in projectStats"
1276
+ :key="project.name"
1277
+ class="project-card"
1278
+ >
1279
+ <div class="project-header">
1280
+ <div>
1281
+ <h3 class="project-title">{{ project.name }}</h3>
1282
+ <Tag :color="getStatusTagColor(project.status)">{{ project.status }}</Tag>
1283
+ </div>
1284
+ <div class="project-rate">
1285
+ <div class="project-rate-value">{{ project.rate.toFixed(1) }}%</div>
1286
+ <div class="project-rate-label">达标率</div>
1287
+ </div>
1288
+ </div>
1289
+
1290
+ <div class="indicators-grid">
1291
+ <div
1292
+ v-for="(score, index) in project.indicators"
1293
+ :key="index"
1294
+ class="indicator-card"
1295
+ >
1296
+ <div class="indicator-label">指标 {{ index + 1 }}</div>
1297
+ <div class="indicator-value">{{ score }}</div>
1298
+ <div class="indicator-status" :class="score >= 80 ? 'status-pass' : 'status-fail'">
1299
+ {{ score >= 80 ? '达标' : '未达标' }}
1300
+ </div>
1301
+ <div class="indicator-progress">
1302
+ <div
1303
+ class="indicator-progress-fill"
1304
+ :class="getProgressClass(score)"
1305
+ :style="{ width: score + '%' }"
1306
+ ></div>
1307
+ </div>
1308
+ </div>
1309
+ </div>
1310
+ </div>
1311
+ </div>
1312
+ </template>
1313
+
1314
+ <script>
1315
+ export default {
1316
+ name: 'ProjectListView',
1317
+ props: {
1318
+ projectStats: {
1319
+ type: Array,
1320
+ required: true
1321
+ }
1322
+ },
1323
+ methods: {
1324
+ getStatusTagColor(status) {
1325
+ switch (status) {
1326
+ case '运行中': return 'success'
1327
+ case '维护中': return 'warning'
1328
+ case '异常': return 'error'
1329
+ default: return 'default'
1330
+ }
1331
+ },
1332
+ getProgressClass(score) {
1333
+ if (score >= 85) return 'progress-excellent'
1334
+ if (score >= 70) return 'progress-good'
1335
+ return 'progress-poor'
1336
+ }
1337
+ }
1338
+ }
1339
+ </script>
1340
+
1341
+ <style scoped>
1342
+ .project-card {
1343
+ background: rgba(255, 255, 255, 0.9);
1344
+ border-radius: 16px;
1345
+ padding: 32px;
1346
+ margin-bottom: 24px;
1347
+ border: 2px solid rgba(24, 144, 255, 0.1);
1348
+ position: relative;
1349
+ overflow: hidden;
1350
+ }
1351
+
1352
+ .project-header {
1353
+ display: flex;
1354
+ justify-content: space-between;
1355
+ align-items: flex-start;
1356
+ margin-bottom: 32px;
1357
+ }
1358
+
1359
+ .project-title {
1360
+ font-size: 24px;
1361
+ font-weight: 700;
1362
+ color: #1890ff;
1363
+ margin-bottom: 12px;
1364
+ }
1365
+
1366
+ .project-rate {
1367
+ text-align: right;
1368
+ }
1369
+
1370
+ .project-rate-value {
1371
+ font-size: 32px;
1372
+ font-weight: 700;
1373
+ color: #1890ff;
1374
+ margin-bottom: 4px;
1375
+ }
1376
+
1377
+ .project-rate-label {
1378
+ font-size: 14px;
1379
+ color: #666;
1380
+ }
1381
+
1382
+ .indicators-grid {
1383
+ display: grid;
1384
+ grid-template-columns: repeat(3, 1fr);
1385
+ gap: 24px;
1386
+ }
1387
+
1388
+ .indicator-card {
1389
+ background: rgba(24, 144, 255, 0.03);
1390
+ border: 1px solid rgba(24, 144, 255, 0.1);
1391
+ border-radius: 12px;
1392
+ padding: 24px;
1393
+ text-align: center;
1394
+ position: relative;
1395
+ overflow: hidden;
1396
+ }
1397
+
1398
+ .indicator-card::before {
1399
+ content: '';
1400
+ position: absolute;
1401
+ top: 0;
1402
+ left: 0;
1403
+ right: 0;
1404
+ height: 3px;
1405
+ background: linear-gradient(90deg, #1890ff, #096dd9);
1406
+ }
1407
+
1408
+ .indicator-label {
1409
+ font-size: 14px;
1410
+ font-weight: 600;
1411
+ color: #666;
1412
+ margin-bottom: 16px;
1413
+ }
1414
+
1415
+ .indicator-value {
1416
+ font-size: 48px;
1417
+ font-weight: 700;
1418
+ color: #1890ff;
1419
+ margin-bottom: 12px;
1420
+ line-height: 1;
1421
+ }
1422
+
1423
+ .indicator-status {
1424
+ padding: 6px 12px;
1425
+ border-radius: 16px;
1426
+ font-size: 12px;
1427
+ font-weight: 600;
1428
+ margin-bottom: 16px;
1429
+ display: inline-block;
1430
+ }
1431
+
1432
+ .status-pass {
1433
+ background: #f6ffed;
1434
+ color: #52c41a;
1435
+ border: 1px solid #b7eb8f;
1436
+ }
1437
+
1438
+ .status-fail {
1439
+ background: #fff2f0;
1440
+ color: #ff4d4f;
1441
+ border: 1px solid #ffccc7;
1442
+ }
1443
+
1444
+ .indicator-progress {
1445
+ width: 100%;
1446
+ height: 6px;
1447
+ background: rgba(24, 144, 255, 0.1);
1448
+ border-radius: 3px;
1449
+ overflow: hidden;
1450
+ }
1451
+
1452
+ .indicator-progress-fill {
1453
+ height: 100%;
1454
+ border-radius: 3px;
1455
+ transition: width 0.8s ease;
1456
+ }
1457
+
1458
+ .progress-excellent {
1459
+ background: linear-gradient(90deg, #52c41a, #73d13d);
1460
+ }
1461
+
1462
+ .progress-good {
1463
+ background: linear-gradient(90deg, #1890ff, #40a9ff);
1464
+ }
1465
+
1466
+ .progress-poor {
1467
+ background: linear-gradient(90deg, #ff4d4f, #ff7875);
1468
+ }
1469
+ </style>
1470
+
1471
+ ---
1472
+
1473
+ // 样式文件 - styles/dashboard.css
1474
+ body {
1475
+ margin: 0;
1476
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
1477
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1478
+ min-height: 100vh;
1479
+ }
1480
+
1481
+ /* iView 组件样式覆盖 */
1482
+ .ivu-input-wrapper {
1483
+ border-radius: 8px;
1484
+ }
1485
+
1486
+ .ivu-select {
1487
+ width: 140px;
1488
+ }
1489
+
1490
+ .ivu-btn {
1491
+ border-radius: 8px;
1492
+ font-weight: 500;
1493
+ }
1494
+
1495
+ /* 全局动画 */
1496
+ @keyframes fadeIn {
1497
+ from {
1498
+ opacity: 0;
1499
+ transform: translateY(20px);
1500
+ }
1501
+ to {
1502
+ opacity: 1;
1503
+ transform: translateY(0);
1504
+ }
1505
+ }
1506
+
1507
+ .fade-enter-active, .fade-leave-active {
1508
+ transition: all 0.3s ease;
1509
+ }
1510
+
1511
+ .fade-enter, .fade-leave-to {
1512
+ opacity: 0;
1513
+ transform: translateY(-20px);
1514
+ }
1515
+
1516
+ ---
1517
+
1518
+ // 使用示例 - main.js
1519
+ import Vue from 'vue'
1520
+ import iView from 'iview'
1521
+ import 'iview/dist/styles/iview.css'
1522
+ import './styles/dashboard.css'
1523
+ import ComplianceDashboard from './ComplianceDashboard.vue'
1524
+
1525
+ Vue.use(iView)
1526
+
1527
+ new Vue({
1528
+ el: '#app',
1529
+ components: {
1530
+ ComplianceDashboard
1531
+ },
1532
+ template: '<ComplianceDashboard />'
1533
+ })
1534
+
1535
+ ---
1536
+
1537
+ // 使用示例 - App.vue
1538
+ <template>
1539
+ <div id="app">
1540
+ <ComplianceDashboard />
1541
+ </div>
1542
+ </template>
1543
+
1544
+ <script>
1545
+ import ComplianceDashboard from './ComplianceDashboard.vue'
1546
+
1547
+ export default {
1548
+ name: 'App',
1549
+ components: {
1550
+ ComplianceDashboard
1551
+ }
1552
+ }
1553
+ </script>