Explorar o código

refactor(warningManage): 重构预警相关接口及页面逻辑

- 将红线指标预警相关接口移至新文件 redLineWarning.js
- 将预警页面数据接口调整到 warningManage 目录下
- 移除 warningPage 文件夹内冗余接口和样式代码
- 删除红线预警和预警页面中多余的统计卡片及相关样式
- 修改红线预警模块数据请求逻辑,使用单一接口替代原有多个接口合并调用
- 调整查询条件回显逻辑,支持更多参数回传并自动刷新数据
- 在预警页面员工列表中新增“详情”操作列,支持跳转至红线预警详情页
- 移除预警页面中冗余的 summaryCards 数据及相关 UI 代码
- 优化员工预警行样式及新增详情链接样式,提升交互体验
huoyi hai 2 días
pai
achega
a68d6f6667

+ 5 - 0
src/api/warningManage/redLineWarning.js

@@ -0,0 +1,5 @@
1
+import request from '@/utils/request'
2
+//红线指标预警
3
+export function getRedLineWarningPageData(data) {
4
+    return request({ url: '/ledger/warning/ledgerReduceDetail', method: 'post', data })
5
+}

src/api/warningPage/warningPage.js → src/api/warningManage/warningPage.js


+ 29 - 127
src/views/warningManage/redLineWarning/index.vue

@@ -43,20 +43,6 @@
43 43
             </div>
44 44
         </div>
45 45
 
46
-        <div class="cards-container">
47
-            <div class="cards-grid" style="overflow-x: auto; white-space: nowrap;">
48
-                <div class="card" v-for="(item, index) in summaryCards" :key="index"
49
-                    style="display: inline-block; width: 200px; flex-shrink: 0;">
50
-                    <div class="card-header">
51
-                        <i :class="item.icon"></i>
52
-                        <h3>{{ item.title }}</h3>
53
-                        <span class="card-badge">{{ item.badge }}</span>
54
-                    </div>
55
-                    <div class="value-large" :style="{ color: item.color }">{{ item.value }}</div>
56
-                </div>
57
-            </div>
58
-        </div>
59
-
60 46
         <div class="employee-section">
61 47
             <div class="section-title">
62 48
                 <i class="fas fa-users" style="color:#dc2626;"></i> 员工综合预警
@@ -124,9 +110,9 @@
124 110
 </template>
125 111
 
126 112
 <script setup>
127
-import { ref, computed, onMounted, watch } from 'vue'
113
+import { ref, computed, reactive, onMounted, watch } from 'vue'
128 114
 import { getDeptUserTree } from '@/api/item/items'
129
-import { getWarningPageData, getEmployeeWarningPageData } from '@/api/warningPage/warningPage'
115
+import { getRedLineWarningPageData } from '@/api/warningManage/redLineWarning'
130 116
 import { useDict } from '@/utils/dict'
131 117
 import { useRoute } from 'vue-router'
132 118
 
@@ -143,17 +129,7 @@ const selectedAlertLevel = ref('')
143 129
 const selectedOrg = ref('')
144 130
 const cascadeOptions = ref([])
145 131
 
146
-const summaryCards = ref([
147
-    { icon: 'fas fa-clipboard-list', title: '部门监察问题', badge: '部门级', value: '13项', color: '#b45309' },
148
-    { icon: 'fas fa-microchip', title: '实时质控拦截', badge: '部门级', value: '347次', color: '#2563eb' },
149
-    { icon: 'fas fa-bug', title: '不安全事件', badge: '一级预警', value: '18起', color: '#dc2626' },
150
-    { icon: 'fas fa-shield-virus', title: '安保测试记录', badge: '部门级', value: '4项', color: '#e67e22' },
151
-    { icon: 'fas fa-comment-dots', title: '旅客服务投诉', badge: '服务响应', value: '11件', color: '#e67e22' },
152
-    { icon: 'fas fa-clipboard-check', title: '服务巡查', badge: '部门级', value: '5项', color: '#333' },
153
-    { icon: 'fas fa-graduation-cap', title: '培训及考试成绩', badge: '平均分数', value: '92.4分', color: '#333' },
154
-    { icon: 'fas fa-plane-departure', title: '航站楼', badge: '吞吐量', value: '2.8万', color: '#059669' },
155
-    { icon: 'fas fa-gift', title: '小额奖励', badge: '奖励次数', value: '156次', color: '#7c3aed' }
156
-])
132
+
157 133
 
158 134
 const newNames = [
159 135
     "刘孟", "王苡衡", "周海涵", "何欣怡", "王崎",
@@ -361,17 +337,7 @@ const getDateRangeFromActive = () => {
361 337
     return { startDate: formatDate(start), endDate: formatDate(now) }
362 338
 }
363 339
 
364
-const warningDataMap = {
365
-    ledgerSupervisionProblem: 0,
366
-    ledgerRealtimeInterception: 1,
367
-    ledgerUnsafeEvent: 2,
368
-    ledgerSecurityTest: 3,
369
-    ledgerComplaint: 4,
370
-    ledgerServicePatrol: 5,
371
-    ledgerExamScore: 6,
372
-    ledgerTerminalBonus: 7,
373
-    ledgerRewardApproval: 8
374
-}
340
+
375 341
 
376 342
 const fetchWarningData = async () => {
377 343
     queryParams.pageNum = 1
@@ -396,23 +362,9 @@ const fetchWarningData = async () => {
396 362
     }
397 363
 
398 364
     try {
399
-        const [r1, r2] = await Promise.all([
400
-            getWarningPageData(params),
401
-            getEmployeeWarningPageData(params)
402
-        ])
365
+        const r1 = await getRedLineWarningPageData(params)
403 366
         if (r1.data) {
404
-            const d = r1.data
405
-            summaryCards.value.forEach((card, idx) => {
406
-                for (const [key, i] of Object.entries(warningDataMap)) {
407
-                    if (i === idx) {
408
-                        card.value = d[key] !== undefined ? String(d[key]) : card.value
409
-                        break
410
-                    }
411
-                }
412
-            })
413
-        }
414
-        if (r2.data) {
415
-            employeesData.value = r2.data
367
+            employeesData.value = r1.data
416 368
         }
417 369
     } catch (error) {
418 370
         console.error('获取预警数据失败:', error)
@@ -432,16 +384,36 @@ onMounted(async () => {
432 384
     fetchWarningData()
433 385
 })
434 386
 
435
-// 监听路由参数变化,回显到级联选择器
387
+// 监听路由参数变化,回显查询条件并查询
436 388
 watch(() => route.query, (query) => {
437
-    const { id } = query
389
+    const { id, startDate: sd, endDate: ed, activeRange: ar, alertLevel, org } = query
390
+
391
+    // 回显员工选择
438 392
     if (id) {
439 393
         selectedOrg.value = `user_${id}`
394
+    } else if (org) {
395
+        selectedOrg.value = org
440 396
     } else {
441 397
         selectedOrg.value = ''
442 398
     }
399
+
400
+    // 回显日期范围
401
+    if (sd && ed) {
402
+        startDate.value = sd
403
+        endDate.value = ed
404
+        activeRange.value = 'custom'
405
+    } else {
406
+        startDate.value = null
407
+        endDate.value = null
408
+        activeRange.value = ar || 'month'
409
+    }
410
+
411
+    // 回显预警等级
412
+    selectedAlertLevel.value = alertLevel || ''
413
+
414
+    queryParams.pageNum = 1
443 415
     fetchWarningData()
444
-})
416
+}, { immediate: true })
445 417
 </script>
446 418
 
447 419
 <style scoped>
@@ -615,77 +587,7 @@ watch(() => route.query, (query) => {
615 587
     background: #1d4ed8;
616 588
 }
617 589
 
618
-.cards-container {
619
-    width: 100%;
620
-    margin-bottom: 36px;
621
-    overflow-x: auto;
622
-}
623 590
 
624
-.cards-grid {
625
-    display: flex;
626
-
627
-    gap: 16px;
628
-    flex-wrap: nowrap;
629
-    min-width: max-content;
630
-}
631
-
632
-.card {
633
-    background: #ffffff;
634
-    border-radius: 20px;
635
-    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.03), 0 1px 2px rgba(0, 0, 0, 0.05);
636
-    transition: all 0.2s;
637
-    border: 1px solid #eef2f6;
638
-    padding: 1rem 0.5rem;
639
-    display: flex;
640
-    flex-direction: column;
641
-    text-align: center;
642
-    min-width: 280px;
643
-    flex-shrink: 0;
644
-}
645
-
646
-.card:hover {
647
-    transform: translateY(-3px);
648
-    box-shadow: 0 12px 20px -10px rgba(0, 0, 0, 0.1);
649
-    border-color: #cbd5e1;
650
-}
651
-
652
-.card-header {
653
-    display: flex;
654
-    flex-direction: column;
655
-    align-items: center;
656
-    gap: 6px;
657
-    border-bottom: none;
658
-    padding-bottom: 0;
659
-    margin-bottom: 12px;
660
-}
661
-
662
-.card-header i {
663
-    font-size: 1.5rem;
664
-    color: #2563eb;
665
-}
666
-
667
-.card-header h3 {
668
-    font-size: 0.85rem;
669
-    font-weight: 700;
670
-    margin: 0;
671
-    white-space: nowrap;
672
-}
673
-
674
-.card-badge {
675
-    font-size: 0.6rem;
676
-    background: #f1f5f9;
677
-    padding: 2px 8px;
678
-    border-radius: 30px;
679
-    margin-top: 4px;
680
-    display: inline-block;
681
-}
682
-
683
-.value-large {
684
-    font-size: 1.8rem;
685
-    font-weight: 800;
686
-    line-height: 1.2;
687
-    margin: 8px 0 4px 0;
688
-}
689 591
 
690 592
 .employee-section {
691 593
     margin-top: 20px;

+ 37 - 127
src/views/warningManage/warningPage/index.vue

@@ -43,20 +43,6 @@
43 43
             </div>
44 44
         </div>
45 45
 
46
-        <div class="cards-container">
47
-            <div class="cards-grid" style="overflow-x: auto; white-space: nowrap;">
48
-                <div class="card" v-for="(item, index) in summaryCards" :key="index"
49
-                    style="display: inline-block; width: 200px; flex-shrink: 0;">
50
-                    <div class="card-header">
51
-                        <i :class="item.icon"></i>
52
-                        <h3>{{ item.title }}</h3>
53
-                        <span class="card-badge">{{ item.badge }}</span>
54
-                    </div>
55
-                    <div class="value-large" :style="{ color: item.color }">{{ item.value }}</div>
56
-                </div>
57
-            </div>
58
-        </div>
59
-
60 46
         <div class="employee-section">
61 47
             <div class="section-title">
62 48
                 <i class="fas fa-users" style="color:#dc2626;"></i> 员工综合预警
@@ -101,6 +87,11 @@
101 87
                                 <span v-else>{{ getAlertLabel(row.statusLabel) }}</span>
102 88
                             </template>
103 89
                         </el-table-column>
90
+                        <el-table-column label="详情" width="80">
91
+                            <template #default="{ row }">
92
+                                <span class="detail-link" @click="goToDetail(row)">详情</span>
93
+                            </template>
94
+                        </el-table-column>
104 95
                     </el-table>
105 96
                     <el-pagination v-show="total > 0" :total="total" v-model:current-page="queryParams.pageNum"
106 97
                         v-model:page-size="queryParams.pageSize" :page-sizes="[10, 20, 50, 100]"
@@ -124,13 +115,15 @@
124 115
 </template>
125 116
 
126 117
 <script setup>
127
-import { ref, computed, onMounted, watch } from 'vue'
118
+import { ref, computed, reactive, onMounted, watch } from 'vue'
119
+import { useRouter } from 'vue-router'
128 120
 import { getDeptUserTree } from '@/api/item/items'
129
-import { getWarningPageData, getEmployeeWarningPageData } from '@/api/warningPage/warningPage'
121
+import { getEmployeeWarningPageData } from '@/api/warningManage/warningPage'
130 122
 import { useDict } from '@/utils/dict'
131 123
 import { useRoute } from 'vue-router'
132 124
 
133 125
 const route = useRoute()
126
+const router = useRouter()
134 127
 
135 128
 const { alert_level } = useDict('alert_level')
136 129
 
@@ -143,18 +136,6 @@ const selectedAlertLevel = ref('')
143 136
 const selectedOrg = ref('')
144 137
 const cascadeOptions = ref([])
145 138
 
146
-const summaryCards = ref([
147
-    { icon: 'fas fa-clipboard-list', title: '部门监察问题', badge: '部门级', value: '13项', color: '#b45309' },
148
-    { icon: 'fas fa-microchip', title: '实时质控拦截', badge: '部门级', value: '347次', color: '#2563eb' },
149
-    { icon: 'fas fa-bug', title: '不安全事件', badge: '一级预警', value: '18起', color: '#dc2626' },
150
-    { icon: 'fas fa-shield-virus', title: '安保测试记录', badge: '部门级', value: '4项', color: '#e67e22' },
151
-    { icon: 'fas fa-comment-dots', title: '旅客服务投诉', badge: '服务响应', value: '11件', color: '#e67e22' },
152
-    { icon: 'fas fa-clipboard-check', title: '服务巡查', badge: '部门级', value: '5项', color: '#333' },
153
-    { icon: 'fas fa-graduation-cap', title: '培训及考试成绩', badge: '平均分数', value: '92.4分', color: '#333' },
154
-    { icon: 'fas fa-plane-departure', title: '航站楼', badge: '吞吐量', value: '2.8万', color: '#059669' },
155
-    { icon: 'fas fa-gift', title: '小额奖励', badge: '奖励次数', value: '156次', color: '#7c3aed' }
156
-])
157
-
158 139
 const newNames = [
159 140
     "刘孟", "王苡衡", "周海涵", "何欣怡", "王崎",
160 141
     "黄政", "刘韵儿", "汪安霖", "张诗敏", "张维", "陈凯琳"
@@ -326,6 +307,23 @@ const getRowClass = ({ row }) => {
326 307
     return ""
327 308
 }
328 309
 
310
+const goToDetail = (row) => {
311
+    const query = { }
312
+    if (startDate.value && endDate.value) {
313
+        query.startDate = formatDate(startDate.value)
314
+        query.endDate = formatDate(endDate.value)
315
+    } else {
316
+        query.activeRange = activeRange.value
317
+    }
318
+    if (selectedAlertLevel.value) {
319
+        query.alertLevel = selectedAlertLevel.value
320
+    }
321
+    if (selectedOrg.value) {
322
+        query.org = selectedOrg.value
323
+    }
324
+    router.push({ path: '/warningManage/redLineWarning', query })
325
+}
326
+
329 327
 const getAlertLabel = (value) => {
330 328
     if (!value) return ''
331 329
     const item = alert_level.value.find(d => d.value === value)
@@ -379,18 +377,6 @@ const getDateRangeFromActive = () => {
379 377
     return { startDate: formatDate(start), endDate: formatDate(now) }
380 378
 }
381 379
 
382
-const warningDataMap = {
383
-    ledgerSupervisionProblem: 0,
384
-    ledgerRealtimeInterception: 1,
385
-    ledgerUnsafeEvent: 2,
386
-    ledgerSecurityTest: 3,
387
-    ledgerComplaint: 4,
388
-    ledgerServicePatrol: 5,
389
-    ledgerExamScore: 6,
390
-    ledgerTerminalBonus: 7,
391
-    ledgerRewardApproval: 8
392
-}
393
-
394 380
 const fetchWarningData = async () => {
395 381
     queryParams.pageNum = 1
396 382
     let params = {}
@@ -414,21 +400,7 @@ const fetchWarningData = async () => {
414 400
     }
415 401
 
416 402
     try {
417
-        const [r1, r2] = await Promise.all([
418
-            getWarningPageData(params),
419
-            getEmployeeWarningPageData(params)
420
-        ])
421
-        if (r1.data) {
422
-            const d = r1.data
423
-            summaryCards.value.forEach((card, idx) => {
424
-                for (const [key, i] of Object.entries(warningDataMap)) {
425
-                    if (i === idx) {
426
-                        card.value = d[key] !== undefined ? String(d[key]) : card.value
427
-                        break
428
-                    }
429
-                }
430
-            })
431
-        }
403
+        const r2 = await getEmployeeWarningPageData(params)
432 404
         if (r2.data) {
433 405
             employeesData.value = r2.data
434 406
         }
@@ -633,78 +605,6 @@ watch(() => route.query, (query) => {
633 605
     background: #1d4ed8;
634 606
 }
635 607
 
636
-.cards-container {
637
-    width: 100%;
638
-    margin-bottom: 36px;
639
-    overflow-x: auto;
640
-}
641
-
642
-.cards-grid {
643
-    display: flex;
644
-
645
-    gap: 16px;
646
-    flex-wrap: nowrap;
647
-    min-width: max-content;
648
-}
649
-
650
-.card {
651
-    background: #ffffff;
652
-    border-radius: 20px;
653
-    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.03), 0 1px 2px rgba(0, 0, 0, 0.05);
654
-    transition: all 0.2s;
655
-    border: 1px solid #eef2f6;
656
-    padding: 1rem 0.5rem;
657
-    display: flex;
658
-    flex-direction: column;
659
-    text-align: center;
660
-    min-width: 280px;
661
-    flex-shrink: 0;
662
-}
663
-
664
-.card:hover {
665
-    transform: translateY(-3px);
666
-    box-shadow: 0 12px 20px -10px rgba(0, 0, 0, 0.1);
667
-    border-color: #cbd5e1;
668
-}
669
-
670
-.card-header {
671
-    display: flex;
672
-    flex-direction: column;
673
-    align-items: center;
674
-    gap: 6px;
675
-    border-bottom: none;
676
-    padding-bottom: 0;
677
-    margin-bottom: 12px;
678
-}
679
-
680
-.card-header i {
681
-    font-size: 1.5rem;
682
-    color: #2563eb;
683
-}
684
-
685
-.card-header h3 {
686
-    font-size: 0.85rem;
687
-    font-weight: 700;
688
-    margin: 0;
689
-    white-space: nowrap;
690
-}
691
-
692
-.card-badge {
693
-    font-size: 0.6rem;
694
-    background: #f1f5f9;
695
-    padding: 2px 8px;
696
-    border-radius: 30px;
697
-    margin-top: 4px;
698
-    display: inline-block;
699
-}
700
-
701
-.value-large {
702
-    font-size: 1.8rem;
703
-    font-weight: 800;
704
-    line-height: 1.2;
705
-    margin: 8px 0 4px 0;
706
-}
707
-
708 608
 .employee-section {
709 609
     margin-top: 20px;
710 610
     width: 100%;
@@ -792,6 +692,16 @@ watch(() => route.query, (query) => {
792 692
     font-size: 0.85rem;
793 693
 }
794 694
 
695
+.detail-link {
696
+    color: #2563eb;
697
+    cursor: pointer;
698
+    font-weight: 500;
699
+}
700
+
701
+.detail-link:hover {
702
+    text-decoration: underline;
703
+}
704
+
795 705
 footer {
796 706
     text-align: center;
797 707
     margin-top: 32px;