Sfoglia il codice sorgente

Merge branch 'new-homePage' into dev

huoyi 1 mese fa
parent
commit
85f31bd098

+ 133 - 0
src/api/home-new/home-new.js

@@ -0,0 +1,133 @@
1
+import request from '@/utils/request'
2
+
3
+// 查询我的任务列表(当天有效任务)shudong
4
+export function getHomePage(params) {
5
+  return request({
6
+    url: '/check/largeScreen/homePage',
7
+    method: 'get',
8
+    params: params
9
+  })
10
+}
11
+//获取查获上报数据  xiaoxiong
12
+export function getSeizureReport(params) {
13
+  return request({
14
+    url: '/system/check/seizureReport/data',
15
+    method: 'get',
16
+    params: params
17
+  })
18
+}
19
+
20
+//获取考勤统计数据   binge
21
+export function getAttendanceStats(params) {
22
+  return request({
23
+    url: '/attendance/stats/getAttendanceStats',
24
+    method: 'get',
25
+    params: params
26
+  })
27
+}
28
+
29
+//抽问抽答,首页   binge
30
+export function getAccuracyStatistics(params) {
31
+  return request({
32
+    url: '/exam/daily/accuracy-statistics',
33
+    method: 'get',
34
+    params: params
35
+  })
36
+}
37
+
38
+//根据角色获取查获排名  xiaoxiong
39
+export function getSeizureRanking(data) {
40
+  return request({
41
+    url: '/item/seizure/ranking/getRankingByRole',
42
+    method: 'post',
43
+    data: data
44
+  })
45
+}
46
+
47
+
48
+//获取检查排名  shudong
49
+export function getCheckRanking(params) {
50
+  return request({
51
+    url: '/system/homePage/homePageRanking',
52
+    method: 'get',
53
+    params: params
54
+  })
55
+}
56
+
57
+//首页-整体   shudong binge xiaoxiong
58
+export function getHomePageWhole(params) {
59
+  return request({
60
+    url: '/system/homePage/homePageWhole',
61
+    method: 'get',
62
+    params: params
63
+  })
64
+}
65
+//首页-明细(能力对比) shudong binge xiaoxiong
66
+export function getHomePageDetail(data) {
67
+  return request({
68
+    url: '/system/homePage/homePageDetail',
69
+    method: 'post',
70
+    data: data
71
+  })
72
+}
73
+
74
+//根据角色标识查询今日上岗用户列表
75
+export function selectUserListByRoleKey(data) {
76
+  return request({
77
+    url: '/attendance/postRecord/selectUserListByRoleKey',
78
+    method: 'post',
79
+    data: data
80
+  })
81
+}
82
+
83
+//首页报表-整体
84
+export function getHomeReportWhole(params) {
85
+  return request({
86
+    url: '/system/homeReport/homeReportWhole',
87
+    method: 'get',
88
+    params: params
89
+  })
90
+}
91
+
92
+//绩效指标列表
93
+export function getMetrics(data) {
94
+  return request({
95
+    url: '/item/performance/metrics',
96
+    method: 'post',
97
+    data: data
98
+  })
99
+}
100
+
101
+//质控活动-问题分布统计
102
+export function getCheckProblemDistribution(query) {
103
+  return request({
104
+    url: '/system/analysisReport/checkProblemDistribution',
105
+    method: 'get',
106
+    params: query
107
+  })
108
+}
109
+
110
+//培训测试正确率分析
111
+export function getAccuracyAnalysis(query) {
112
+  return request({
113
+    url: '/exam/quiz/accuracy-analysis',
114
+    method: 'get',
115
+    params: query
116
+  })
117
+}
118
+//查询全站查获违禁品 TOP3
119
+export function getProhibitedTop3(query) {
120
+  return request({
121
+    url: '/item/performance/prohibited-top3',
122
+    method: 'get',
123
+    params: query
124
+  })
125
+}
126
+//查询隐匿重点部位 Top1
127
+export function getConcealmentPositionTop1(query) {
128
+  return request({
129
+    url: '/item/performance/concealment-position-top1',
130
+    method: 'get',
131
+    params: query
132
+  })
133
+}

+ 1 - 1
src/components/custom-tabbar.vue

@@ -14,7 +14,7 @@ export default {
14 14
     return {
15 15
       list: [
16 16
         {
17
-          pagePath: "pages/home/index",
17
+          pagePath: "pages/home-new/index",
18 18
           iconPath: "/static/images/tabbar/home.png",
19 19
           selectedIconPath: "/static/images/tabbar/home_.png",
20 20
           text: "首页"

+ 81 - 0
src/components/select-tag/select-tag.vue

@@ -0,0 +1,81 @@
1
+<template>
2
+    <div class="time-tag-container">
3
+        <div class="custom-tag-container">
4
+            <div v-for="tag in tags" :key="tag.value" class="custom-tag"
5
+                :class="{ 'active': selectedValue === tag.value }" @click="handleTagClick(tag)">
6
+                <span class="tag-text">{{ tag.label }}</span>
7
+            </div>
8
+        </div>
9
+    </div>
10
+</template>
11
+
12
+<script>
13
+export default {
14
+    name: 'SelectTag',
15
+    props: {
16
+        tags: {
17
+            type: Array,
18
+            default: () => []
19
+        },
20
+        selectedValue: {
21
+            type: String,
22
+            default: ''
23
+        }
24
+    },
25
+    methods: {
26
+        handleTagClick(tag) {
27
+            if (tag.value === 'custom') {
28
+                // 触发自定义时间选择器显示事件
29
+                this.$emit('show-custom-picker', tag.value);
30
+            } else {
31
+                // 触发普通时间范围变化事件
32
+                this.$emit('change', tag.value);
33
+            }
34
+        }
35
+    }
36
+}
37
+</script>
38
+
39
+<style lang="scss" scoped>
40
+.time-tag-container {
41
+    display: flex;
42
+    justify-content: flex-start;
43
+    margin-top: 30rpx;
44
+}
45
+
46
+.custom-tag-container {
47
+    display: flex;
48
+    flex-wrap: wrap;
49
+    gap: 5rpx;
50
+    align-items: center;
51
+}
52
+
53
+.custom-tag {
54
+    display: flex;
55
+    align-items: center;
56
+    padding: 7rpx 15rpx;
57
+
58
+    color: #FFFFFF;
59
+
60
+    border-radius: 20rpx;
61
+    font-size: 24rpx;
62
+    cursor: pointer;
63
+    transition: all 0.3s ease;
64
+}
65
+
66
+.custom-tag.active {
67
+    background: rgba(255, 255, 255, 0.2);
68
+    color: #FFFFFF;
69
+    font-weight: bold;
70
+    background: rgba(255, 255, 255, 0.2);
71
+}
72
+
73
+// .custom-tag:hover {
74
+//     background: rgba(255, 255, 255, 0.4);
75
+//     border-color: rgba(255, 255, 255, 0.6);
76
+// }
77
+
78
+.tag-text {
79
+    // margin-right: 8rpx;
80
+}
81
+</style>

+ 27 - 2
src/pages.json

@@ -26,6 +26,13 @@
26 26
       }
27 27
     },
28 28
     {
29
+      "path": "pages/home-new/index",
30
+      "style": {
31
+        "navigationBarTitleText": "安检分级质控系统移动端",
32
+        "navigationStyle": "custom"
33
+      }
34
+    },
35
+    {
29 36
       "path": "pages/work/index",
30 37
       "style": {
31 38
         "navigationBarTitleText": "工作台"
@@ -38,6 +45,18 @@
38 45
       }
39 46
     },
40 47
     {
48
+      "path": "pages/capabilityComparison/index",
49
+      "style": {
50
+        "navigationBarTitleText": "能力对比"
51
+      }
52
+    },
53
+    {
54
+      "path": "pages/statisticalReport/index",
55
+      "style": {
56
+        "navigationBarTitleText": "整体报表"
57
+      }
58
+    },
59
+    {
41 60
       "path": "pages/mine/avatar/index",
42 61
       "style": {
43 62
         "navigationBarTitleText": "修改头像"
@@ -146,6 +165,12 @@
146 165
       }
147 166
     },
148 167
     {
168
+      "path": "pages/home-new/index",
169
+      "style": {
170
+        "navigationBarTitleText": "首页"
171
+      }
172
+    },
173
+    {
149 174
       "path": "pages/personal-center/index",
150 175
       "style": {
151 176
         "navigationBarTitleText": "个人中心"
@@ -217,7 +242,7 @@
217 242
         "navigationBarTitleText": "公告详情"
218 243
       }
219 244
     },
220
-       {
245
+    {
221 246
       "path": "pages/announcement/noticeDetail",
222 247
       "style": {
223 248
         "navigationBarTitleText": "通知详情"
@@ -294,7 +319,7 @@
294 319
     "marginBottom": "0px",
295 320
     "list": [
296 321
       {
297
-        "pagePath": "pages/home/index",
322
+        "pagePath": "pages/home-new/index",
298 323
         "iconPath": "static/images/tabbar/home.png",
299 324
         "selectedIconPath": "static/images/tabbar/home_.png",
300 325
         "text": "首页"

File diff suppressed because it is too large
+ 1460 - 0
src/pages/capabilityComparison/index.vue


+ 0 - 0
src/pages/capabilityComparison/能力对比


+ 157 - 0
src/pages/home-new/components/notice.vue

@@ -0,0 +1,157 @@
1
+<template>
2
+  <div class="tips" @click="navigateToDetail(currentNotice)">
3
+    <img alt="" src="@/static/images/notice.png">
4
+    <div class="notice-container">
5
+      <transition name="slide-up-down" mode="out-in">
6
+        <div :key="currentNotice && currentNotice.noticeId" class="notice-content">
7
+          {{ currentNotice && currentNotice.noticeTitle ? currentNotice.noticeTitle : '暂无通知' }}
8
+        </div>
9
+      </transition>
10
+    </div>
11
+  </div>
12
+</template>
13
+
14
+<script>
15
+import { getNoticeList } from "@/api/announcement/announcement.js";
16
+
17
+export default {
18
+  name: 'Notice',
19
+  data() {
20
+    return {
21
+      noticeList: [],
22
+      currentIndex: 0,
23
+      currentNotice: null,
24
+      timer: null
25
+    };
26
+  },
27
+  mounted() {
28
+    // 组件首次加载时获取数据
29
+    // this.fetchNoticeData();
30
+  },
31
+  destroyed() {
32
+    // 清理定时器
33
+    if (this.timer) {
34
+      clearInterval(this.timer);
35
+    }
36
+  },
37
+  methods: {
38
+    // 提供给父组件调用的刷新方法
39
+    refreshData() {
40
+      this.fetchNoticeData();
41
+    },
42
+
43
+    // 获取公告数据
44
+    async fetchNoticeData() {
45
+
46
+      try {
47
+        const response = await getNoticeList({
48
+          status: '0',
49
+          noticeType: 1,
50
+          pageSize: 999
51
+        });
52
+
53
+        // 假设response.data包含公告列表
54
+        this.noticeList = response.rows || [];
55
+        if (this.noticeList.length > 0) {
56
+          this.currentNotice = this.noticeList[0];
57
+          if (this.timer) {
58
+            clearInterval(this.timer);
59
+          }
60
+          // 数据加载完成后启动轮播
61
+          this.startCarousel();
62
+        }
63
+      } catch (error) {
64
+        console.error('获取公告数据失败:', error);
65
+      }
66
+    },
67
+
68
+    // 开始轮播
69
+    startCarousel() {
70
+      if (this.noticeList.length <= 1) return;
71
+
72
+      this.timer = setInterval(() => {
73
+        this.currentIndex = (this.currentIndex + 1) % this.noticeList.length;
74
+        this.currentNotice = this.noticeList[this.currentIndex];
75
+      }, 3000); // 5秒轮播一次
76
+    },
77
+
78
+    // 跳转到详情页
79
+    navigateToDetail(notice) {
80
+      if (!notice || !notice.noticeId) return;
81
+
82
+      uni.navigateTo({
83
+        url: '/pages/announcement/noticeDetail?id=' + notice.noticeId
84
+      });
85
+    }
86
+  }
87
+}
88
+</script>
89
+
90
+<style lang="scss" scoped>
91
+.tips {
92
+  display: flex;
93
+  align-items: center;
94
+  padding: 16rpx 28rpx;
95
+  background: #FFF4F4;
96
+  border-radius: 32rpx;
97
+  font-size: 24rpx;
98
+  color: #222222;
99
+  line-height: 28rpx;
100
+  margin-bottom: 32rpx;
101
+  cursor: pointer; // 鼠标指针变为手型
102
+  transition: all 0.3s ease; // 添加过渡效果
103
+  overflow: hidden;
104
+
105
+  img {
106
+    width: 66rpx;
107
+    padding-right: 28rpx;
108
+    border-right: 1px solid rgba(0, 0, 0, 0.1);
109
+    margin-right: 28rpx;
110
+    flex-shrink: 0;
111
+  }
112
+
113
+  .notice-container {
114
+    flex: 1;
115
+    overflow: hidden;
116
+    height: 28rpx;
117
+    position: relative;
118
+  }
119
+
120
+  .notice-content {
121
+    position: absolute;
122
+    top: 0;
123
+    left: 0;
124
+    width: 100%;
125
+    white-space: nowrap;
126
+    overflow: hidden;
127
+    text-overflow: ellipsis;
128
+  }
129
+
130
+  // 上下滚动动画
131
+  .slide-up-down-enter-active,
132
+  .slide-up-down-leave-active {
133
+    transition: all 0.5s ease;
134
+  }
135
+
136
+  .slide-up-down-enter-from {
137
+    transform: translateY(30rpx);
138
+    opacity: 0;
139
+  }
140
+
141
+  .slide-up-down-leave-to {
142
+    transform: translateY(-30rpx);
143
+    opacity: 0;
144
+  }
145
+
146
+  // 鼠标悬停效果
147
+  &:hover {
148
+    background: #FFE8E8; // 背景色变深
149
+    transform: scale(1.01); // 轻微放大
150
+  }
151
+
152
+  // 点击效果
153
+  &:active {
154
+    transform: scale(0.99); // 轻微缩小
155
+  }
156
+}
157
+</style>

+ 158 - 0
src/pages/home-new/components/performanceAnalysis.vue

@@ -0,0 +1,158 @@
1
+<template>
2
+    <div class="performance-analysis-container">
3
+        <div class="section-title">绩效分析</div>
4
+
5
+        <div class="cards-container">
6
+            <div v-for="(item, index) in performanceData" :key="index" class="card-item">
7
+                <div class="card-header">
8
+                    <div class="card-title">{{ item.name }}数据明细</div>
9
+                    <image :src="getTrophyIcon(item.rank)" class="trophy-icon"></image>
10
+                </div>
11
+                <div class="card-content">
12
+                    <div class="data-item">
13
+                        <div class="data-value">{{ item.totalScore || '0.0' }}</div>
14
+                        <div class="data-label">总分</div>
15
+                    </div>
16
+                    <div class="divider"></div>
17
+                    <div class="data-item">
18
+                        <div class="data-value">{{ item.seizureEfficiency || '0.0' }}</div>
19
+                        <div class="data-label">查获效率</div>
20
+                    </div>
21
+                    <div class="data-item">
22
+                        <div class="data-value">{{ item.inspectionPassRate || '0.0' }}</div>
23
+                        <div class="data-label">巡检合格率</div>
24
+                    </div>
25
+                    <div class="data-item">
26
+                        <div class="data-value">{{ item.trainingScore || '0.0' }}</div>
27
+                        <div class="data-label">培训得分</div>
28
+                    </div>
29
+                </div>
30
+            </div>
31
+        </div>
32
+    </div>
33
+</template>
34
+
35
+<script>
36
+export default {
37
+    name: 'PerformanceAnalysis',
38
+    data() {
39
+        return {
40
+            trophyImages: {
41
+                one: '/static/images/icon/one.png',
42
+                two: '/static/images/icon/two.png',
43
+                three: '/static/images/icon/three.png'
44
+            }
45
+        }
46
+    },
47
+    props: {
48
+        performanceData: {
49
+            type: Array,
50
+            default: () => []
51
+        }
52
+    },
53
+    watch: {
54
+        // performanceData: {
55
+        //     handler(newVal) {
56
+        //         console.log('绩效数据已更新:', newVal);
57
+                
58
+        //     },
59
+        //     immediate: true,
60
+        //     deep: true
61
+        // }
62
+    },
63
+    methods: {
64
+        getTrophyIcon(rank) {
65
+            switch(rank) {
66
+                case 1:
67
+                    return this.trophyImages.one;
68
+                case 2:
69
+                    return this.trophyImages.two;
70
+                case 3:
71
+                    return this.trophyImages.three;
72
+                default:
73
+                    return ''; // 没有奖杯图标
74
+            }
75
+        }
76
+    }
77
+}
78
+</script>
79
+
80
+<style lang="scss" scoped>
81
+.performance-analysis-container {
82
+    background: #FFFFFF;
83
+    border-radius: 20rpx;
84
+    padding: 30rpx;
85
+    margin-bottom: 70rpx;
86
+    box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
87
+}
88
+
89
+.section-title {
90
+    font-size: 32rpx;
91
+    font-weight: bold;
92
+    color: #333333;
93
+    margin-bottom: 30rpx;
94
+}
95
+
96
+.cards-container {
97
+    display: flex;
98
+    flex-direction: column;
99
+    gap: 30rpx;
100
+}
101
+
102
+.card-item {
103
+    background: #EFF2FE;
104
+    border-radius: 30rpx;
105
+    padding: 20rpx;
106
+}
107
+
108
+
109
+
110
+.card-header {
111
+    display: flex;
112
+    justify-content: space-between;
113
+    align-items: center;
114
+    margin-bottom: 30rpx;
115
+}
116
+
117
+.card-title {
118
+    font-size: 30rpx;
119
+    font-weight: bold;
120
+    color: #333333;
121
+}
122
+
123
+.trophy-icon {
124
+    width: 45rpx;
125
+    height: 45rpx;
126
+}
127
+
128
+.card-content {
129
+    display: flex;
130
+    align-items: center;
131
+    gap: 30rpx;
132
+}
133
+
134
+.data-item {
135
+    flex: 1;
136
+    display: flex;
137
+    flex-direction: column;
138
+    align-items: center;
139
+}
140
+
141
+.data-value {
142
+    font-size: 36rpx;
143
+    font-weight: bold;
144
+    color: #333333;
145
+    margin-bottom: 8rpx;
146
+}
147
+
148
+.data-label {
149
+    font-size: 24rpx;
150
+    color: #666666;
151
+}
152
+
153
+.divider {
154
+    width: 2rpx;
155
+    height: 80rpx;
156
+    background: #CCD3EF;
157
+}
158
+</style>

+ 281 - 0
src/pages/home-new/components/qualityControlAnalysis.vue

@@ -0,0 +1,281 @@
1
+<template>
2
+    <div class="quality-control-container">
3
+        <div class="section-title">质控分析</div>
4
+
5
+        <div class="data-block">
6
+            <div class="block-title-row">
7
+                <div class="block-title">查获数据TOP3</div>
8
+            </div>
9
+            <div class="data-grid" v-if="prohibitedTop3 && prohibitedTop3.length > 0">
10
+                <div v-for="(item, index) in prohibitedTop3" :key="index" class="data-item">
11
+                    <div class="item-label">{{ item.categoryName }}总数</div>
12
+                    <div class="item-value">{{ item.quantity }}</div>
13
+                    <div class="item-ratio">占比<span class="ratio-highlight">{{ item.percentage }}%</span></div>
14
+                </div>
15
+            </div>
16
+            <div v-else class="no-data-text">暂无查获数据</div>
17
+            <div class="note-text" v-if="prohibitedTop3Desc">注:{{ prohibitedTop3Desc }}</div>
18
+        </div>
19
+
20
+        <div class="data-block">
21
+            <div class="block-title-row">
22
+                <div class="block-title">质控发现问题</div>
23
+                <div class="total-count">总数:<span class="count-highlight">{{ checkProblemDistributionTotal }}</span>
24
+                </div>
25
+            </div>
26
+            <div class="data-grid" v-if="checkProblemDistribution && checkProblemDistribution.length > 0">
27
+                <div v-for="(item, index) in checkProblemDistribution" :key="index" class="data-item">
28
+                    <div class="item-label">{{ item.name }}类总数</div>
29
+                    <div class="item-value">{{ item.total }}</div>
30
+                    <div class="item-ratio">占比<span class="ratio-highlight">{{ item.percentage }}%</span></div>
31
+                </div>
32
+            </div>
33
+            <div v-else class="no-data-text">暂未发现问题</div>
34
+            <div class="note-text" v-if="checkProblemDistributionDesc">注:{{ checkProblemDistributionDesc }}</div>
35
+        </div>
36
+
37
+        <div class="data-block">
38
+            <div class="block-title-row">
39
+                <div class="block-title">培训测试正确率分析</div>
40
+                <div class="total-count">平均正确率:<span class="count-highlight">{{ avgCorrectRate }}%</span></div>
41
+            </div>
42
+            <div class="data-grid" v-if="accuracyAnalysis && accuracyAnalysis.length > 0">
43
+                <div v-for="(item, index) in accuracyAnalysis" :key="index" class="data-item">
44
+                    <div class="item-label">{{ item.categoryName }}</div>
45
+                    <div class="item-value" :class="getValueClass(item.color)">{{ item.correctRate }}%</div>
46
+                    <div class="item-status" :class="getValueClass(item.color)">{{ item.label }}</div>
47
+                </div>
48
+            </div>
49
+            <div v-else class="no-data-text">暂无培训测试数据</div>
50
+            <div class="note-text" v-if="accuracyAnalysisDesc">注:{{ accuracyAnalysisDesc }}</div>
51
+        </div>
52
+    </div>
53
+</template>
54
+
55
+<script>
56
+
57
+export default {
58
+    name: 'QualityControlAnalysis',
59
+    data() {
60
+        return {
61
+            checkProblemDistribution: [],
62
+            checkProblemDistributionTotal: 0,
63
+            checkProblemDistributionDesc: '',
64
+            avgCorrectRate: 0,
65
+            accuracyAnalysisDesc: '',
66
+            accuracyAnalysis: [],
67
+            prohibitedTop3: [],
68
+            prohibitedTop3Desc: ''
69
+        }
70
+    },
71
+    props: {
72
+        checkProblemDistributionData: {
73
+            type: Object,
74
+            default: () => ({})
75
+        },
76
+        accuracyAnalysisData: {
77
+            type: Object,
78
+            default: () => ({})
79
+        },
80
+        prohibitedTop3Data: {
81
+            type: Array,
82
+            default: () => ([])
83
+        },
84
+        concealmentPositionTop1Data: {
85
+            type: Array,
86
+            default: () => ([])
87
+        },
88
+    },
89
+    watch: {
90
+        // 监听checkProblemDistributionData变化,重新计算数据
91
+        checkProblemDistributionData: {
92
+            handler(newVal) {
93
+                this.calculateCheckProblemDistribution();
94
+            },
95
+            deep: true,
96
+            immediate: true
97
+        },
98
+        accuracyAnalysisData: {
99
+            handler(newVal) {
100
+                this.calculateAccuracyAnalysis();
101
+            },
102
+            deep: true,
103
+            immediate: true
104
+        },
105
+        prohibitedTop3Data: {
106
+            handler(newVal) {
107
+                this.calculateProhibitedTop3();
108
+            },
109
+            deep: true,
110
+            immediate: true
111
+        },
112
+        concealmentPositionTop1Data: {
113
+            handler(newVal) {
114
+                this.calculateProhibitedTop1();
115
+            },
116
+            deep: true,
117
+            immediate: true
118
+        }
119
+    },
120
+    methods: {
121
+        calculateProhibitedTop1() {
122
+            let str = this.concealmentPositionTop1Data.map(item => `${item.checkPositionNameOne}/${item.checkPositionNameTwo}`).join('/');
123
+
124
+            this.prohibitedTop3Desc = str ? `隐匿重点部位以[${str}]为主` : "";
125
+        },
126
+        calculateAccuracyAnalysis() {
127
+            const { avgCorrectRate, note, categoryList } = this.accuracyAnalysisData || [];
128
+            this.avgCorrectRate = avgCorrectRate;
129
+            this.accuracyAnalysisDesc = note;
130
+            this.accuracyAnalysis = categoryList
131
+        },
132
+
133
+        // 计算违禁品TOP3数据
134
+        calculateProhibitedTop3() {
135
+
136
+            this.prohibitedTop3 = this.prohibitedTop3Data || [];
137
+        },
138
+
139
+        // 根据颜色获取值对应的CSS类
140
+        getValueClass(color) {
141
+            if (color === 'green') return 'value-green';
142
+            if (color === 'red') return 'value-red';
143
+            return '';
144
+        },
145
+
146
+
147
+        // 计算检查问题分布数据
148
+        calculateCheckProblemDistribution() {
149
+
150
+            const { checkProblemDistributionItemDtoList, desc } = this.checkProblemDistributionData || [];
151
+            this.checkProblemDistributionDesc = desc;
152
+            // 计算总问题数
153
+            this.checkProblemDistributionTotal = (checkProblemDistributionItemDtoList || []).reduce((sum, item) => sum + (item.total || 0), 0);
154
+
155
+            // 计算每个项目的百分比
156
+            this.checkProblemDistribution = (checkProblemDistributionItemDtoList || []).map(item => ({
157
+                ...item,
158
+                percentage: this.checkProblemDistributionTotal > 0 ?
159
+                    ((item.total / this.checkProblemDistributionTotal) * 100).toFixed(2) : '0.00'
160
+            }));
161
+        }
162
+    }
163
+}
164
+</script>
165
+
166
+<style lang="scss" scoped>
167
+.quality-control-container {
168
+    background: #FFFFFF;
169
+    border-radius: 20rpx;
170
+    padding: 30rpx;
171
+    margin-bottom: 30rpx;
172
+    box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
173
+}
174
+
175
+.section-title {
176
+    font-size: 34rpx;
177
+    font-weight: bold;
178
+    color: #333333;
179
+    margin-bottom: 30rpx;
180
+}
181
+
182
+.data-block {
183
+    background: #EFF2FE;
184
+    border-radius: 15rpx;
185
+    padding: 25rpx;
186
+    margin-bottom: 25rpx;
187
+
188
+    &:last-child {
189
+        margin-bottom: 0;
190
+    }
191
+}
192
+
193
+.block-title-row {
194
+    display: flex;
195
+    justify-content: space-between;
196
+    align-items: center;
197
+    margin-bottom: 25rpx;
198
+}
199
+
200
+.block-title {
201
+    font-size: 30rpx;
202
+    font-weight: bold;
203
+    color: #333333;
204
+}
205
+
206
+.total-count {
207
+    font-size: 26rpx;
208
+    color: #666666;
209
+}
210
+
211
+.count-highlight {
212
+    color: #1890FF;
213
+    font-weight: bold;
214
+}
215
+
216
+.data-grid {
217
+    display: grid;
218
+    grid-template-columns: repeat(3, 1fr);
219
+    gap: 20rpx;
220
+    margin-bottom: 20rpx;
221
+}
222
+
223
+.data-item {
224
+    display: flex;
225
+    flex-direction: column;
226
+    align-items: center;
227
+    text-align: center;
228
+}
229
+
230
+.item-label {
231
+    font-size: 24rpx;
232
+    color: #666666;
233
+    margin-bottom: 8rpx;
234
+}
235
+
236
+.item-value {
237
+    font-size: 34rpx;
238
+    font-weight: bold;
239
+    color: #333333;
240
+    margin-bottom: 8rpx;
241
+}
242
+
243
+.value-green {
244
+    color: #00AE41 !important;
245
+}
246
+
247
+.value-red {
248
+    color: #F96060 !important;
249
+}
250
+
251
+.item-ratio {
252
+    font-size: 24rpx;
253
+    color: #666666;
254
+}
255
+
256
+.ratio-highlight {
257
+    color: #F96060;
258
+    font-weight: bold;
259
+}
260
+
261
+.item-status {
262
+    font-size: 24rpx;
263
+    color: #666666;
264
+}
265
+
266
+
267
+
268
+.note-text {
269
+    font-size: 24rpx;
270
+    color: #F96060;
271
+    line-height: 1.6;
272
+}
273
+
274
+.no-data-text {
275
+    font-size: 28rpx;
276
+    color: #999999;
277
+    text-align: center;
278
+    padding: 40rpx 0;
279
+    font-style: italic;
280
+}
281
+</style>

+ 596 - 0
src/pages/home-new/components/total-detail.vue

@@ -0,0 +1,596 @@
1
+<template>
2
+    <div class="total-detail-container">
3
+        <!-- 标题和链接区域 -->
4
+        <div class="item-header">
5
+            <div class="title-section">
6
+                <div class="item-title">整体</div>
7
+                <div class="item-link report-link"
8
+                    @click="handleReportLinkClick">整体报表</div>
9
+            </div>
10
+            <div class="item-links">
11
+                <div class="item-link"
12
+                    v-if="!role.includes('SecurityCheck') && !(role.includes('banzuzhang') && selectedRole == 'individual')"
13
+                    @click="handleLinkClick('/pages/capabilityComparison/index')">能力对比</div>
14
+            </div>
15
+        </div>
16
+
17
+        <!-- 雷达图区域 -->
18
+        <div class="chart-section">
19
+            <div class="chart-container">
20
+                <div id="radar-chart" class="radar-chart"></div>
21
+            </div>
22
+
23
+        </div>
24
+
25
+        <!-- 数据展示区域 -->
26
+        <div class="data-section">
27
+            <div v-for="(item, index) in dataItems" :key="index" class="data-item">
28
+                <div class="item-title">{{ item.title }}</div>
29
+                <div class="data-content-section">
30
+                    <div class="data-content" v-for="ele in item.list">
31
+                        <div class="data-value">{{ ele.value }}</div>
32
+                        <div class="data-label">{{ ele.label }}</div>
33
+                    </div>
34
+                </div>
35
+            </div>
36
+        </div>
37
+    </div>
38
+</template>
39
+
40
+<script>
41
+import * as echarts from 'echarts';
42
+
43
+
44
+export default {
45
+    name: 'TotalDetail',
46
+    data() {
47
+        return {
48
+            chart: null,
49
+
50
+
51
+            dataItems: []
52
+        }
53
+    },
54
+    props: {
55
+        homePageWholeData: {
56
+            type: Array,
57
+            default: () => []
58
+        },
59
+        startDate: {
60
+            type: String,
61
+            default: ''
62
+        },
63
+        endDate: {
64
+            type: String,
65
+            default: ''
66
+        },
67
+        timeRange: {
68
+            type: String,
69
+            default: 'year'
70
+        },
71
+        selectedRole: {
72
+            type: String,
73
+            default: ''
74
+        }
75
+    },
76
+    watch: {
77
+        homePageWholeData: {
78
+            handler(newValue) {
79
+                this.updateData(newValue);
80
+            },
81
+            deep: true,
82
+            immediated: true,
83
+        }
84
+    },
85
+    computed: {
86
+        role() {
87
+            return this.$store?.state?.user?.roles
88
+        },
89
+        currentUser() {
90
+            return this.$store.state.user;
91
+        }
92
+    },
93
+    mounted() {
94
+        // this.initChart();
95
+
96
+    },
97
+
98
+    beforeDestroy() {
99
+        if (this.chart) {
100
+            this.chart.dispose();
101
+        }
102
+    },
103
+    methods: {
104
+        updateData(newValue) {
105
+            console.log(newValue, "newValue")
106
+            this.dataItems = newValue.map(item => {
107
+                return {
108
+                    title: item.name + "数据明细",
109
+                    list: [
110
+                        { label: '人均查获数量', value: item.seizureCount || 0 },
111
+                        { label: '人均在岗时长', value: item.workingHours || 0 },
112
+                        { label: '巡检合格率', value: `${((item.checkPassRate * 100) || 0).toFixed(2)}%` },
113
+                        { label: '抽问抽答正确率', value: `${((item.answersAccuracy * 100) || 0).toFixed(2)}%` },
114
+                        { label: '培训答题平均分', value: item.learningGrowthScore || 0 }
115
+                    ]
116
+                }
117
+            })
118
+
119
+            // 同时更新雷达图数据
120
+            this.updateChartWithData();
121
+        },
122
+        handleLinkClick(link) {
123
+            if (link) {
124
+                const roles = this.currentUser.roles;
125
+                const userInfo = this.currentUser.userInfo;
126
+
127
+                let obj = {};
128
+                if (roles.includes('SecurityCheck')) {
129
+                    obj = {
130
+                        id: userInfo.userId,
131
+                        name: userInfo.nickName,
132
+                        type: 'USER'
133
+                    }
134
+                }
135
+                if (roles.includes('banzuzhang')) {
136
+                    obj = {
137
+                        id: userInfo.teamsId,
138
+                        name: userInfo.teamsName,
139
+                        type: 'DEPT'
140
+                    }
141
+                }
142
+                if (roles.includes('kezhang')) {
143
+                    obj = {
144
+                        id: userInfo.departmentId,
145
+                        name: userInfo.departmentName,
146
+                        type: 'DEPT'
147
+                    }
148
+                }
149
+                if (roles.includes('test') || roles.includes('zhijianke')) {
150
+                    obj = {
151
+                        id: userInfo.stationId,
152
+                        name: userInfo.stationName,
153
+                        type: 'DEPT'
154
+                    }
155
+                }
156
+
157
+                // 添加时间参数
158
+                const timeParams = {
159
+                    startDate: this.startDate,
160
+                    endDate: this.endDate,
161
+                    timeRange: this.timeRange
162
+                };
163
+
164
+                // 合并所有参数
165
+                const allParams = {
166
+                    ...obj,
167
+                    ...timeParams
168
+                };
169
+
170
+                // 构建带参数的URL
171
+                let finalUrl = link;
172
+                if (Object.keys(allParams).length > 0) {
173
+                    // 检查URL是否已有参数
174
+                    const separator = link.includes('?') ? '&' : '?';
175
+                    const queryString = Object.keys(allParams)
176
+                        .filter(key => allParams[key]) // 过滤掉空值
177
+                        .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(allParams[key])}`)
178
+                        .join('&');
179
+
180
+                    if (queryString) {
181
+                        finalUrl = `${link}${separator}${queryString}`;
182
+                    }
183
+                }
184
+
185
+                uni.navigateTo({
186
+                    url: finalUrl
187
+                });
188
+            }
189
+        },
190
+
191
+
192
+
193
+
194
+        updateChartWithData() {
195
+            const chartDom = document.getElementById('radar-chart');
196
+            if (!chartDom) {
197
+                console.warn('雷达图容器未找到');
198
+                return;
199
+            }
200
+
201
+            this.chart = echarts.init(chartDom);
202
+            // 根据接口数据更新雷达图
203
+            if (this.chart && this.homePageWholeData && this.homePageWholeData.length > 0) {
204
+                // 使用Graph后缀的数据来显示雷达图
205
+                const seriesData = this.homePageWholeData.map(item => ({
206
+                    name: item.name,
207
+                    value: [
208
+                        item.seizureCountGraph || 0,
209
+                        item.workingHoursGraph || 0,
210
+                        item.checkPassRateGraph || 0,
211
+                        item.answersAccuracyGraph || 0,
212
+                        item.learningGrowthScoreGraph || 0
213
+                    ]
214
+                }));
215
+
216
+                // 计算每个指标的最大值,用于雷达图的max值
217
+                const indicators = [
218
+                    { name: '查获能力', max: Math.max(...seriesData.map(d => d.value[0])) },
219
+                    { name: '在岗时长', max: Math.max(...seriesData.map(d => d.value[1])) },
220
+                    { name: '巡检合格率', max: Math.max(...seriesData.map(d => d.value[2])) },
221
+                    { name: '抽问抽答', max: Math.max(...seriesData.map(d => d.value[3])) },
222
+                    { name: '培训答题', max: Math.max(...seriesData.map(d => d.value[4])) }
223
+                ];
224
+
225
+                const option = {
226
+                    legend: {
227
+                        type: 'scroll',
228
+                        orient: 'horizontal',
229
+                        bottom: 10,
230
+                        textStyle: {
231
+                            fontSize: 12,
232
+                            color: '#666'
233
+                        },
234
+                        itemWidth: 12,
235
+                        itemHeight: 12,
236
+                        data: seriesData.map(item => item.name)
237
+                    },
238
+                    radar: {
239
+                        shape: 'circle',
240
+                        indicator: indicators,
241
+                        splitNumber: 4,
242
+                        center: ['50%', '45%'],
243
+                        radius: '55%',
244
+                        axisName: {
245
+                            color: '#666',
246
+                            fontSize: 12
247
+                        },
248
+                        splitLine: {
249
+                            lineStyle: {
250
+                                color: ['#E6E6E6', '#E6E6E6', '#E6E6E6', '#E6E6E6']
251
+                            }
252
+                        },
253
+                        splitArea: {
254
+                            show: true,
255
+                            areaStyle: {
256
+                                color: ['#F8F8F8', '#FFFFFF']
257
+                            }
258
+                        },
259
+                        axisLine: {
260
+                            lineStyle: {
261
+                                color: '#E6E6E6'
262
+                            }
263
+                        }
264
+                    },
265
+                    series: [
266
+                        {
267
+                            name: '能力对比',
268
+                            type: 'radar',
269
+                            data: seriesData.map((item, index) => ({
270
+                                value: item.value,
271
+                                name: item.name,
272
+                                areaStyle: {
273
+                                    color: this.getChartColor(index, 0.3)
274
+                                },
275
+                                lineStyle: {
276
+                                    color: this.getChartColor(index),
277
+                                    width: 2
278
+                                },
279
+                                itemStyle: {
280
+                                    color: this.getChartColor(index)
281
+                                }
282
+                            }))
283
+                        }
284
+                    ],
285
+                    // 添加点击事件
286
+                    tooltip: {
287
+                        show: false
288
+                    },
289
+
290
+                };
291
+
292
+                this.chart.setOption(option);
293
+
294
+
295
+
296
+                // 响应式调整
297
+                window.addEventListener('resize', () => {
298
+                    this.chart.resize();
299
+                });
300
+            }
301
+        },
302
+
303
+        // 获取图表颜色
304
+        getChartColor(index, opacity = 1) {
305
+            const colors = ['#8FA5EC', '#FF9F7F', '#6ECEB2', '#FFD700', '#BA55D3'];
306
+            const color = colors[index % colors.length];
307
+            if (opacity < 1) {
308
+                // 转换为RGBA格式
309
+                const r = parseInt(color.slice(1, 3), 16);
310
+                const g = parseInt(color.slice(3, 5), 16);
311
+                const b = parseInt(color.slice(5, 7), 16);
312
+                return `rgba(${r}, ${g}, ${b}, ${opacity})`;
313
+            }
314
+            return color;
315
+        },
316
+
317
+        // 处理雷达图点击事件
318
+        handleRadarChartClick(params) {
319
+            if (params.componentType === 'series' && params.seriesType === 'radar') {
320
+                const clickedData = params.data;
321
+                const seriesIndex = params.seriesIndex;
322
+                const dataIndex = params.dataIndex;
323
+
324
+                // 获取对应的数据项
325
+                const dataItem = this.homePageWholeData[dataIndex];
326
+
327
+                if (dataItem) {
328
+                    // 显示详细数据弹窗
329
+                    this.showRadarDataDetail(dataItem);
330
+                }
331
+            }
332
+        },
333
+
334
+        // 显示雷达图数据详情
335
+        showRadarDataDetail(dataItem) {
336
+            const labels = ['查获数量', '在岗时长', '巡检合格率', '抽问抽答', '培训答题'];
337
+            const values = [
338
+                dataItem.seizureCount || 0,
339
+                dataItem.workingHours || 0,
340
+                dataItem.checkPassRate || 0,
341
+                dataItem.answersAccuracy || 0,
342
+                dataItem.learningGrowthScore || 0
343
+            ];
344
+
345
+            let detailHtml = `<div style="font-size: 16px; font-weight: bold; margin-bottom: 12px; text-align: center;">${dataItem.name} - 详细数据</div>`;
346
+
347
+            labels.forEach((label, index) => {
348
+                detailHtml += `<div style="display: flex; justify-content: space-between; margin: 8px 0; padding: 4px 0; border-bottom: 1px solid #f0f0f0;">
349
+                    <span style="color: #666;">${label}:</span>
350
+                    <span style="font-weight: bold; color: #1890ff;">${values[index]}</span>
351
+                </div>`;
352
+            });
353
+
354
+            uni.showModal({
355
+                title: '详细数据',
356
+                content: detailHtml,
357
+                showCancel: false,
358
+                confirmText: '关闭',
359
+                confirmColor: '#1890ff'
360
+            });
361
+        },
362
+
363
+        // 处理整体报表链接点击
364
+        handleReportLinkClick() {
365
+            // 根据时间范围自动计算开始和结束时间
366
+            const { startDate, endDate } = this.calculateDateRange(this.timeRange);
367
+            
368
+            // 构建跳转参数
369
+            const params = {
370
+                startDate: startDate,
371
+                endDate: endDate,
372
+            };
373
+
374
+            // 过滤掉空值
375
+            const queryParams = Object.keys(params)
376
+                .filter(key => params[key] && params[key] !== '')
377
+                .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
378
+                .join('&');
379
+
380
+            // 构建跳转URL
381
+            const url = queryParams ? 
382
+                `/pages/statisticalReport/index?${queryParams}` : 
383
+                '/pages/statisticalReport/index';
384
+
385
+            uni.navigateTo({
386
+                url: url
387
+            });
388
+        },
389
+
390
+        // 根据时间范围计算开始和结束时间
391
+        calculateDateRange(timeRange) {
392
+            const today = new Date();
393
+            const yesterday = new Date(today);
394
+            yesterday.setDate(today.getDate() - 1);
395
+
396
+            let startDate = new Date(yesterday);
397
+            let endDate = new Date(yesterday);
398
+
399
+            switch (timeRange) {
400
+                case 'week':
401
+                    // 近一周:从7天前到昨天
402
+                    startDate.setDate(yesterday.getDate() - 6);
403
+                    break;
404
+                case 'month':
405
+                    // 近一月:从30天前到昨天
406
+                    startDate.setDate(yesterday.getDate() - 29);
407
+                    break;
408
+                case 'quarter':
409
+                    // 近三月:从90天前到昨天
410
+                    startDate.setDate(yesterday.getDate() - 89);
411
+                    break;
412
+                case 'halfYear':
413
+                    // 近半年:从180天前到昨天
414
+                    startDate.setDate(yesterday.getDate() - 179);
415
+                    break;
416
+                case 'year':
417
+                    // 近一年:从365天前到昨天
418
+                    startDate.setDate(yesterday.getDate() - 364);
419
+                    break;
420
+                case 'custom':
421
+                    // 自定义时间:使用用户选择的日期
422
+                    if (this.startDate && this.endDate) {
423
+                        startDate = new Date(this.startDate);
424
+                        endDate = new Date(this.endDate);
425
+                    }
426
+                    break;
427
+                default:
428
+                    // 默认近一年
429
+                    startDate.setDate(yesterday.getDate() - 364);
430
+            }
431
+
432
+            return {
433
+                startDate: this.formatDateForInput(startDate),
434
+                endDate: this.formatDateForInput(endDate)
435
+            };
436
+        },
437
+
438
+        // 格式化日期为输入框格式
439
+        formatDateForInput(date) {
440
+            const year = date.getFullYear();
441
+            const month = String(date.getMonth() + 1).padStart(2, '0');
442
+            const day = String(date.getDate()).padStart(2, '0');
443
+            return `${year}-${month}-${day}`;
444
+        }
445
+
446
+
447
+
448
+    }
449
+}
450
+</script>
451
+
452
+<style lang="scss" scoped>
453
+.total-detail-container {
454
+    background: #FFFFFF;
455
+    border-radius: 20rpx;
456
+    padding: 30rpx;
457
+    margin-bottom: 30rpx;
458
+    box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
459
+}
460
+
461
+.item-header {
462
+    display: flex;
463
+    justify-content: space-between;
464
+    align-items: flex-start;
465
+    margin-bottom: 25rpx;
466
+}
467
+
468
+.title-section {
469
+    display: flex;
470
+    align-items: center;
471
+    gap: 20rpx;
472
+}
473
+
474
+.item-title {
475
+    font-size: 32rpx;
476
+    font-weight: bold;
477
+    color: #333333;
478
+    margin-bottom: 0;
479
+}
480
+
481
+.item-link {
482
+    font-size: 26rpx;
483
+    color: #1890FF;
484
+    cursor: pointer;
485
+}
486
+
487
+.report-link {
488
+    margin-left: 0;
489
+}
490
+
491
+.chart-section {
492
+    margin-bottom: 30rpx;
493
+}
494
+
495
+.chart-container {
496
+    width: 100%;
497
+    height: 600rpx;
498
+    margin-bottom: 20rpx;
499
+}
500
+
501
+.radar-chart {
502
+    width: 100%;
503
+    height: 100%;
504
+}
505
+
506
+.chart-legend {
507
+    display: flex;
508
+    justify-content: center;
509
+    gap: 40rpx;
510
+}
511
+
512
+.legend-item {
513
+    display: flex;
514
+    align-items: center;
515
+    gap: 10rpx;
516
+}
517
+
518
+.legend-color {
519
+    width: 20rpx;
520
+    height: 20rpx;
521
+    border-radius: 4rpx;
522
+}
523
+
524
+.legend-text {
525
+    font-size: 24rpx;
526
+    color: #666;
527
+}
528
+
529
+.data-section {}
530
+
531
+.data-item {
532
+    background: #EFF2FE;
533
+    border-radius: 15rpx;
534
+    padding: 20rpx;
535
+    text-align: center;
536
+    display: flex;
537
+    flex-direction: column;
538
+    align-items: flex-start;
539
+    margin-bottom: 30rpx;
540
+}
541
+
542
+.data-content-section {
543
+    display: flex;
544
+}
545
+
546
+.data-content {
547
+    flex: 1;
548
+    display: flex;
549
+    flex-direction: column;
550
+    align-items: center;
551
+}
552
+
553
+.data-value {
554
+    font-size: 32rpx;
555
+    font-weight: bold;
556
+    color: #333333;
557
+    margin-bottom: 8rpx;
558
+}
559
+
560
+.data-label {
561
+    font-size: 24rpx;
562
+    color: #666;
563
+}
564
+
565
+.rank-section {
566
+    margin-top: 30rpx;
567
+}
568
+
569
+.rank-item {
570
+    display: flex;
571
+    align-items: center;
572
+    margin-bottom: 10rpx;
573
+
574
+
575
+
576
+}
577
+
578
+.rank-label {
579
+    width: 80rpx;
580
+    font-size: 26rpx;
581
+    color: #333;
582
+    font-weight: 500;
583
+}
584
+
585
+.rank-progress {
586
+    flex: 1;
587
+    margin: 0 20rpx;
588
+}
589
+
590
+.rank-info {
591
+    width: 100rpx;
592
+    text-align: right;
593
+    font-size: 26rpx;
594
+    color: #666;
595
+}
596
+</style>

+ 500 - 0
src/pages/home-new/components/type-detail.vue

@@ -0,0 +1,500 @@
1
+<template>
2
+    <div class="type-detail-container">
3
+        <div v-for="(item, index) in dataList" :key="index" class="type-item">
4
+            <!-- 标题和链接区域 -->
5
+            <div class="item-header">
6
+                <div class="item-title">{{ item.title }}</div>
7
+                <div class="item-link" @click="handleLinkClick(item.link)">{{ item.linkText }}</div>
8
+            </div>
9
+
10
+            <!-- 主要内容区域 -->
11
+            <div class="item-content">
12
+                <!-- 数量统计区域 -->
13
+                <div class="stat-section">
14
+                    <div class="stat-header">
15
+                        <div class="stat-title">{{ item.statTitle }}</div>
16
+                        <div class="stat-trend" v-if="!role.includes('test') && !role.includes('zhijianke')">
17
+                            绿色为高于平均值,红色为低于平均值
18
+                        </div>
19
+                    </div>
20
+
21
+
22
+                    <div class="data-grid">
23
+                        <div v-for="(dataItem, dataIndex) in item.dataItems" :key="dataIndex" class="data-item">
24
+
25
+                            <div class="data-value">
26
+                                <image v-if="dataItem.isImage" :src="dataItem.value" class="data-image" />
27
+                                <span v-else :style="{ color: dataItem.color || '#333333' }">{{ dataItem.value }}</span>
28
+                            </div>
29
+                            <div class="data-label">{{ dataItem.label }}</div>
30
+
31
+                            <div v-if="dataIndex == item.dividerIndex" class="divider"></div>
32
+                        </div>
33
+                    </div>
34
+                </div>
35
+
36
+                <!-- 巡检部分的问题整改区域 -->
37
+                <div v-if="item.title === '巡检'" class="problem-rect-section">
38
+                    <div class="problem-title">问题整改</div>
39
+                    <div class="problem-grid">
40
+                        <!-- 左边:已办结问题 -->
41
+                        <div class="problem-item left-item">
42
+
43
+                            <div class="problem-value">{{ item.completed }}</div>
44
+                            <div class="problem-label">{{ '已办结' }}</div>
45
+                        </div>
46
+                        <!-- 右边:待办结问题 -->
47
+                        <div class="problem-item right-item">
48
+                            <div class="problem-value">{{ item.pending }}</div>
49
+                            <div class="problem-label">{{ '办理中' }}</div>
50
+                        </div>
51
+                    </div>
52
+                </div>
53
+
54
+                <!-- 排名区域 -->
55
+                <div class="rank-section" v-if="item.rankList.length > 0">
56
+                    <div v-for="(rank, i) in item.rankList" :key="i" class="rank-item">
57
+                        <div class="rank-label">{{ rank.label }}</div>
58
+                        <div class="rank-progress">
59
+                            <h-rank-line :percentage="Number(rank.percentage)" endType="round" :height="10"
60
+                                :color="rank.type == 'station' ? ['#F9CA91', '#ED9E3E'] : ['#87BFFC', '#5580F7']">
61
+                                <div class="rank-info"><span
62
+                                        :style="{ color: rank.type == 'station' ? '#ED9E3E' : '#5580F7' }">{{
63
+                                            rank.current
64
+                                        }}</span>/{{ rank.total }}</div>
65
+                            </h-rank-line>
66
+                        </div>
67
+                    </div>
68
+                </div>
69
+
70
+                <!-- test角色特殊显示:主管合格率排行和班组合格率排行 -->
71
+                <div class="rank-section" v-if="role && (role.includes('test') || role.includes('zhijianke'))">
72
+                    <!-- 主管合格率排行 -->
73
+                    <div class="rank-header">
74
+                        <div class="rank-title">{{ item.title === '查获上报' ? '主管查获总数排名' : item.title == '抽问抽答' ? '主管正确率排名'
75
+                            :
76
+                            '主管合格率排名' }}</div>
77
+                        <div class="sort-buttons">
78
+                            <div :class="['sort-btn', { 'active': item.deptSortType === 'asc' }]"
79
+                                @click="$emit('sort-change', item.title, 'dept', 'asc')">正序</div>
80
+                            <div :class="['sort-btn', { 'active': item.deptSortType === 'desc' }]"
81
+                                @click="$emit('sort-change', item.title, 'dept', 'desc')">倒序</div>
82
+                        </div>
83
+                    </div>
84
+
85
+                    <!-- 主管排名显示 -->
86
+                    <template v-if="item.deptSortType === 'asc'">
87
+                        <div v-for="(dept, i) in (item.departmentRank || []).slice(0, 5)" :key="'dept-asc-' + i"
88
+                            class="rank-item">
89
+                            <div class="rank-label">{{ dept.name }}</div>
90
+                            <div class="rank-progress">
91
+                                <h-rank-line
92
+                                    :percentage="item.title !== '查获上报' ? Number(dept.passRate || 0) : getPercentage(dept.passRate, item.departmentRank)"
93
+                                    endType="round" :height="10" :color="['#F9CA91', '#ED9E3E']">
94
+                                    <div class="rank-info"><span style="color: #999999">{{ dept.passRate || 0 }}</span>
95
+                                    </div>
96
+                                </h-rank-line>
97
+                            </div>
98
+                        </div>
99
+                    </template>
100
+                    <template v-else>
101
+                        <div v-for="(dept, i) in (item.bottomDepartmentRank || []).slice(0, 5)" :key="'dept-desc-' + i"
102
+                            class="rank-item">
103
+                            <div class="rank-label">{{ dept.name }}</div>
104
+                            <div class="rank-progress">
105
+                                <h-rank-line
106
+                                    :percentage="item.title !== '查获上报' ? Number(dept.passRate || 0) : getPercentage(dept.passRate, item.bottomDepartmentRank)"
107
+                                    endType="round" :height="10" :color="['#F9CA91', '#ED9E3E']">
108
+                                    <div class="rank-info"><span style="color: #999999">{{ dept.passRate || 0 }}</span>
109
+                                    </div>
110
+                                </h-rank-line>
111
+                            </div>
112
+                        </div>
113
+                    </template>
114
+
115
+                    <!-- 班组合格率排行 -->
116
+                    <div class="rank-header">
117
+                        <div class="rank-title">{{ item.title ===
118
+                            '查获上报' ? '班组查获总数排名' : item.title == '抽问抽答' ? '班组正确率排名' : '班组合格率排名' }}</div>
119
+                        <div class="sort-buttons">
120
+                            <div :class="['sort-btn', { 'active': item.teamSortType === 'asc' }]"
121
+                                @click="$emit('sort-change', item.title, 'team', 'asc')">正序</div>
122
+                            <div :class="['sort-btn', { 'active': item.teamSortType === 'desc' }]"
123
+                                @click="$emit('sort-change', item.title, 'team', 'desc')">倒序</div>
124
+                        </div>
125
+                    </div>
126
+
127
+                    <!-- 班组排名显示 -->
128
+                    <template v-if="item.teamSortType === 'asc'">
129
+                        <div v-for="(team, i) in (item.teamRank || []).slice(0, 5)" :key="'team-asc-' + i"
130
+                            class="rank-item">
131
+                            <div class="rank-label">{{ team.name }}</div>
132
+                            <div class="rank-progress">
133
+                                <h-rank-line
134
+                                    :percentage="item.title !== '查获上报' ? Number(team.passRate || 0) : getPercentage(team.passRate, item.teamRank)"
135
+                                    endType="round" :height="10" :color="['#87BFFC', '#5580F7']">
136
+                                    <div class="rank-info"><span style="color: #999999">{{ team.passRate || 0 }}</span>
137
+                                    </div>
138
+                                </h-rank-line>
139
+                            </div>
140
+                        </div>
141
+                    </template>
142
+                    <template v-else>
143
+                        <div v-for="(team, i) in (item.bottomTeamRank || []).slice(0, 5)" :key="'team-desc-' + i"
144
+                            class="rank-item">
145
+                            <div class="rank-label">{{ team.name }}</div>
146
+                            <div class="rank-progress">
147
+                                <h-rank-line
148
+                                    :percentage="item.title !== '查获上报' ? Number(team.passRate || 0) : getPercentage(team.passRate, item.bottomTeamRank)"
149
+                                    endType="round" :height="10" :color="['#87BFFC', '#5580F7']">
150
+                                    <div class="rank-info"><span style="color: #999999">{{ team.passRate || 0 }}</span>
151
+                                    </div>
152
+                                </h-rank-line>
153
+                            </div>
154
+                        </div>
155
+                    </template>
156
+                </div>
157
+
158
+            </div>
159
+        </div>
160
+    </div>
161
+</template>
162
+
163
+<script>
164
+import HRankLine from "@/components/h-rank-line/h-rank-line.vue";
165
+
166
+export default {
167
+    name: 'TypeDetail',
168
+    components: {
169
+        HRankLine
170
+    },
171
+    props: {
172
+        dataList: {
173
+            type: Array,
174
+            default: () => []
175
+        }
176
+    },
177
+    emits: ['sort-change'],
178
+    computed: {
179
+        role() {
180
+            return this.$store?.state?.user?.roles
181
+        }
182
+    },
183
+    watch: {
184
+        dataList: {
185
+            handler(newValue, oldValue) {
186
+                console.log('dataList', newValue)
187
+            }
188
+        }
189
+    },
190
+    methods: {
191
+        handleLinkClick(link) {
192
+            if (link) {
193
+                // 检查是否是tabBar页面
194
+                const tabBarPages = [
195
+                    '/pages/home-new/index',
196
+                    '/pages/myToDoList/index',
197
+                    '/pages/work/index',
198
+                    '/pages/mine/index'
199
+                ];
200
+
201
+
202
+                if (tabBarPages.includes(link)) {
203
+                    uni.switchTab({
204
+                        url: link
205
+                    });
206
+                } else {
207
+                    uni.navigateTo({
208
+                        url: link
209
+                    });
210
+                }
211
+            }
212
+        },
213
+
214
+        // 计算百分比:使用数组中的最大值作为分母
215
+        getPercentage(value, array) {
216
+            if (!array || !Array.isArray(array) || array.length === 0) {
217
+                return 0;
218
+            }
219
+
220
+            // 找到数组中的最大值
221
+            const maxValue = Math.max(...array.map(item => Number(item.passRate) || 0));
222
+
223
+            // 如果最大值为0,则返回0避免除以0
224
+            if (maxValue === 0) {
225
+                return 0;
226
+            }
227
+
228
+            // 计算百分比
229
+            const percentage = (Number(value) || 0) / maxValue * 100;
230
+
231
+            // 确保百分比在0-100之间
232
+            return Math.min(Math.max(percentage, 0), 100);
233
+        },
234
+
235
+
236
+
237
+
238
+    }
239
+}
240
+</script>
241
+
242
+<style lang="scss" scoped>
243
+.type-detail-container {
244
+    margin-top: 30rpx;
245
+}
246
+
247
+.type-item {
248
+    background: #FFFFFF;
249
+    border-radius: 20rpx;
250
+    padding: 30rpx;
251
+    margin-bottom: 30rpx;
252
+    box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
253
+}
254
+
255
+.item-header {
256
+    display: flex;
257
+    justify-content: space-between;
258
+    align-items: center;
259
+    margin-bottom: 25rpx;
260
+}
261
+
262
+.item-title {
263
+    font-size: 32rpx;
264
+    font-weight: bold;
265
+    color: #333333;
266
+}
267
+
268
+.item-link {
269
+    font-size: 26rpx;
270
+    color: #1890FF;
271
+    cursor: pointer;
272
+}
273
+
274
+.item-content {
275
+    .stat-section {
276
+        background: #EFF2FE;
277
+        border-radius: 15rpx;
278
+        padding: 15rpx;
279
+    }
280
+
281
+    .stat-header {
282
+        display: flex;
283
+        justify-content: space-between;
284
+        align-items: center;
285
+        margin-bottom: 20rpx;
286
+    }
287
+
288
+    .stat-title {
289
+        font-size: 28rpx;
290
+        color: #3D3D3D;
291
+        font-weight: bold;
292
+        font-weight: 600;
293
+    }
294
+
295
+    .stat-trend {
296
+        font-size: 24rpx;
297
+        padding: 6rpx 12rpx;
298
+        border-radius: 8rpx;
299
+        color: #999999;
300
+    }
301
+
302
+    .trend-up {
303
+        color: #52C41A;
304
+        background: rgba(82, 196, 26, 0.1);
305
+    }
306
+
307
+    .trend-down {
308
+        color: #FF4D4F;
309
+        background: rgba(255, 77, 79, 0.1);
310
+    }
311
+
312
+    .data-grid {
313
+        display: flex;
314
+        justify-content: space-evenly;
315
+    }
316
+
317
+    .data-item {
318
+        display: flex;
319
+        flex: 1;
320
+        flex-direction: column;
321
+        align-items: center;
322
+        text-align: center;
323
+        position: relative;
324
+
325
+    }
326
+
327
+    .data-value {
328
+        font-size: 34rpx;
329
+        font-weight: bold;
330
+        color: #333333;
331
+        margin-bottom: 8rpx;
332
+        display: flex;
333
+        align-items: center;
334
+        justify-content: center;
335
+        min-height: 50rpx;
336
+    }
337
+
338
+    .data-image {
339
+        width: 40rpx;
340
+        height: 40rpx;
341
+        border-radius: 8rpx;
342
+    }
343
+
344
+    .data-label {
345
+        font-size: 24rpx;
346
+        color: #999999;
347
+    }
348
+
349
+    .divider {
350
+        position: absolute;
351
+        right: -4%;
352
+        top: 50%;
353
+        transform: translateY(-50%);
354
+        width: 1rpx;
355
+        height: 60%;
356
+        background: rgba(0, 0, 0, 0.1);
357
+    }
358
+
359
+    /* 问题整改区域样式 */
360
+    .problem-rect-section {
361
+        margin-top: 25rpx;
362
+    }
363
+
364
+    .problem-title {
365
+        font-size: 28rpx;
366
+        color: #3D3D3D;
367
+        font-weight: bold;
368
+        margin-bottom: 20rpx;
369
+    }
370
+
371
+    .problem-grid {
372
+        display: flex;
373
+        gap: 20rpx;
374
+    }
375
+
376
+    .problem-item {
377
+        flex: 1;
378
+        border-radius: 15rpx;
379
+        padding: 25rpx;
380
+        display: flex;
381
+        flex-direction: column;
382
+        align-items: flex-start;
383
+        text-align: center;
384
+    }
385
+
386
+    .left-item {
387
+        background: #EFFFF5;
388
+    }
389
+
390
+    .right-item {
391
+        background: #FFF6F6;
392
+    }
393
+
394
+    .problem-value {
395
+        font-size: 36rpx;
396
+        font-weight: bold;
397
+        margin-bottom: 8rpx;
398
+        min-height: 50rpx;
399
+        display: flex;
400
+        align-items: center;
401
+        justify-content: center;
402
+    }
403
+
404
+    .left-item .problem-value {
405
+        color: #00AE41;
406
+    }
407
+
408
+    .right-item .problem-value {
409
+        color: #F96060;
410
+    }
411
+
412
+    .problem-label {
413
+        font-size: 24rpx;
414
+        color: #999999;
415
+    }
416
+
417
+    /* 排名区域样式 */
418
+    .rank-section {
419
+        margin-top: 25rpx;
420
+    }
421
+
422
+    .rank-header {
423
+        display: flex;
424
+        justify-content: space-between;
425
+        align-items: center;
426
+        margin-bottom: 15rpx;
427
+        margin-top: 20rpx;
428
+    }
429
+
430
+    .rank-title {
431
+        font-size: 28rpx;
432
+        color: #3D3D3D;
433
+        font-weight: bold;
434
+    }
435
+
436
+    .sort-buttons {
437
+        display: flex;
438
+
439
+        div:nth-child(1) {
440
+
441
+            border-radius: 10rpx 0 0 10rpx;
442
+        }
443
+
444
+        div:nth-child(2) {
445
+
446
+            border-radius: 0 10rpx 10rpx 0;
447
+        }
448
+    }
449
+
450
+    .sort-btn {
451
+        padding: 4rpx 20rpx;
452
+        border: 2rpx solid #1890FF;
453
+
454
+        background: #FFFFFF;
455
+        color: #1890FF;
456
+        font-size: 24rpx;
457
+        cursor: pointer;
458
+        transition: all 0.3s ease;
459
+    }
460
+
461
+    .sort-btn.active {
462
+        background: #1890FF;
463
+        color: #FFFFFF;
464
+    }
465
+
466
+    // .sort-btn:not(.active):hover {
467
+    //     background: #F0F8FF;
468
+    // }
469
+
470
+    .rank-item {
471
+        display: flex;
472
+        align-items: center;
473
+        margin-bottom: 10rpx;
474
+    }
475
+
476
+    .rank-label {
477
+        width: 120rpx;
478
+        font-size: 26rpx;
479
+        color: #333;
480
+        font-weight: 500;
481
+    }
482
+
483
+    .rank-progress {
484
+        flex: 1;
485
+        margin: 0 20rpx;
486
+    }
487
+
488
+    .rank-info {
489
+        width: 100rpx;
490
+        text-align: right;
491
+        font-size: 26rpx;
492
+        color: #666;
493
+
494
+        span {
495
+            font-size: 29rpx;
496
+            font-weight: bold;
497
+        }
498
+    }
499
+}
500
+</style>

File diff suppressed because it is too large
+ 1704 - 0
src/pages/home-new/index.vue


+ 1 - 1
src/pages/mine/setting/index.vue

@@ -50,7 +50,7 @@
50 50
       handleLogout() {
51 51
         this.$modal.confirm('确定注销并退出系统吗?').then(() => {
52 52
           this.$store.dispatch('LogOut').then(() => {}).finally(()=>{
53
-            this.$tab.reLaunch('/pages/home/index')
53
+            this.$tab.reLaunch('/pages/home-new/index')
54 54
           })
55 55
         })
56 56
       }

+ 307 - 0
src/pages/statisticalReport/index.vue

@@ -0,0 +1,307 @@
1
+<template>
2
+  <HomeContainer>
3
+    <!-- 时间范围显示 -->
4
+    <!-- <view v-if="startDate && endDate" class="time-range-display">
5
+      <text class="time-range-text">时间范围:{{ startDate }} 至 {{ endDate }}</text>
6
+    </view> -->
7
+
8
+    <!-- 统计表格区域 -->
9
+    <view class="statistic-tables-section">
10
+      <!-- 查获数量统计表格 -->
11
+      <StatisticTable title="查获数量" :columns="seizureColumns" :data="seizureTableData" :blankData="'0'"
12
+        class="statistic-table" />
13
+
14
+      <!-- 在岗时长统计表格 -->
15
+      <StatisticTable title="在岗时长" :columns="workingColumns" :data="workingTableData" :blankData="'0'"
16
+        class="statistic-table" />
17
+
18
+      <!-- 巡检合格率统计表格 -->
19
+      <StatisticTable title="巡检合格率" :columns="inspectionColumns" :data="inspectionTableData" :blankData="'0'"
20
+        class="statistic-table" />
21
+
22
+      <!-- 抽问抽答正确率统计表格 -->
23
+      <StatisticTable title="抽问抽答正确率" :columns="questionColumns" :data="questionTableData" :blankData="'0'"
24
+        class="statistic-table" />
25
+
26
+      <!-- 培训答题分数统计表格 -->
27
+      <StatisticTable title="培训答题分数" :columns="learningColumns" :data="learningTableData" :blankData="'0'"
28
+        class="statistic-table" />
29
+    </view>
30
+  </HomeContainer>
31
+</template>
32
+
33
+<script>
34
+import HomeContainer from '@/components/HomeContainer.vue'
35
+import HeadTitle from '@/components/HeadTitle.vue'
36
+import StatisticTable from '@/components/statistic-table/statistic-table.vue'
37
+import { getHomeReportWhole } from '@/api/home-new/home-new.js'
38
+export default {
39
+  name: 'StatisticalReport',
40
+  components: {
41
+    HomeContainer,
42
+    HeadTitle,
43
+    StatisticTable
44
+  },
45
+  data() {
46
+    return {
47
+      // 时间参数
48
+      startDate: '',
49
+      endDate: '',
50
+      // 表格列配置
51
+      seizureColumns: [
52
+        { props: 'name', title: '查获数量' },
53
+        { props: 'totalNumber', title: '总数' },
54
+        { props: 'averageNumber', title: '平均数' },
55
+        { props: 'medianNumber', title: '中位数' },
56
+        { props: 'maxNumber', title: '最大值' },
57
+        { props: 'minNumber', title: '最小值' }
58
+      ],
59
+      workingColumns: [
60
+        { props: 'name', title: '在岗时长' },
61
+        { props: 'totalNumber', title: '总数' },
62
+        { props: 'averageNumber', title: '平均数' },
63
+        { props: 'medianNumber', title: '中位数' },
64
+        { props: 'maxNumber', title: '最大值' },
65
+        { props: 'minNumber', title: '最小值' }
66
+      ],
67
+      inspectionColumns: [
68
+        { props: 'name', title: '巡检合格率(%)' },
69
+        // { props: 'totalNumber', title: '总数' },
70
+        { props: 'averageNumber', title: '平均数' },
71
+        { props: 'medianNumber', title: '中位数' },
72
+        { props: 'maxNumber', title: '最大值' },
73
+        { props: 'minNumber', title: '最小值' }
74
+      ],
75
+      questionColumns: [
76
+        { props: 'name', title: '抽问抽答正确率(%)' },
77
+        // { props: 'totalNumber', title: '总数' },
78
+        { props: 'averageNumber', title: '平均数' },
79
+        { props: 'medianNumber', title: '中位数' },
80
+        { props: 'maxNumber', title: '最大值' },
81
+        { props: 'minNumber', title: '最小值' }
82
+      ],
83
+      learningColumns: [
84
+        { props: 'name', title: '培训答题分数' },
85
+        // { props: 'totalNumber', title: '总数' },
86
+        { props: 'averageNumber', title: '平均数' },
87
+        { props: 'medianNumber', title: '中位数' },
88
+        { props: 'maxNumber', title: '最大值' },
89
+        { props: 'minNumber', title: '最小值' }
90
+      ],
91
+      // 统计表格数据
92
+      seizureTableData: [],      // 查获数量 - seizureList
93
+      workingTableData: [],      // 在岗时长 - workingList
94
+      inspectionTableData: [],   // 巡检合格率 - checkList
95
+      questionTableData: [],     // 抽问抽答正确率 - answerList
96
+      learningTableData: []      // 培训答题分数 - learningList
97
+    }
98
+  },
99
+
100
+  async onLoad(options) {
101
+    // 接收页面参数
102
+    this.startDate = options.startDate || ''
103
+    this.endDate = options.endDate || ''
104
+
105
+    console.log('接收到的时间参数:', {
106
+      startDate: this.startDate,
107
+      endDate: this.endDate
108
+    })
109
+
110
+    // 页面加载时获取数据
111
+    await this.loadData()
112
+  },
113
+
114
+  methods: {
115
+    // 加载数据方法
116
+    async loadData() {
117
+      try {
118
+        // 根据时间参数构建查询条件
119
+        const queryParams = {}
120
+        if (this.startDate && this.endDate) {
121
+          queryParams.startDate = this.startDate
122
+          queryParams.endDate = this.endDate
123
+        }
124
+
125
+        console.log('加载数据的查询参数:', queryParams)
126
+
127
+        // 调用getHomeReportWhole接口获取真实数据
128
+        const response = await getHomeReportWhole(queryParams)
129
+
130
+        if (response.code === 200 && response.data) {
131
+          // 处理接口返回的真实数据
132
+          const { checkList, learningList, seizureList, workingList, answerList } = response.data;
133
+
134
+          // 分配数据到对应的表格
135
+          this.seizureTableData = seizureList || this.generateMockDepartmentData('查获数量')
136
+          this.workingTableData = workingList || this.generateMockDepartmentData('在岗时长')
137
+          this.inspectionTableData = checkList.map((item) => {
138
+            return {
139
+              ...item,
140
+              medianNumber: `${(item.medianNumber * 100).toFixed(2)}`,
141
+              maxNumber: `${(item.maxNumber * 100).toFixed(2)}`,
142
+              minNumber: `${(item.minNumber * 100).toFixed(2)}`,
143
+              averageNumber: `${(item.averageNumber * 100).toFixed(2)}`
144
+            }
145
+          }) || this.generateMockDepartmentData('巡检合格率')
146
+          this.questionTableData = answerList.map((item) => {
147
+            return {
148
+              ...item,
149
+              medianNumber: `${(item.medianNumber).toFixed(2)}`,
150
+              maxNumber: `${(item.maxNumber).toFixed(2)}`,
151
+              minNumber: `${(item.minNumber).toFixed(2)}`,
152
+              averageNumber: `${(item.averageNumber).toFixed(2)}`
153
+            }
154
+          }) || this.generateMockDepartmentData('抽问抽答正确率')
155
+          this.learningTableData = learningList || this.generateMockDepartmentData('培训答题分数')
156
+
157
+          // 如果有时间参数,显示时间范围提示
158
+          // if (this.startDate && this.endDate) {
159
+          //   uni.showToast({
160
+          //     title: `已加载 ${this.startDate} 至 ${this.endDate} 的数据`,
161
+          //     icon: 'none',
162
+          //     duration: 2000
163
+          //   })
164
+          // }
165
+        } else {
166
+          // 如果接口返回失败,使用模拟数据
167
+          console.warn('接口返回数据异常,使用模拟数据')
168
+          this.seizureTableData = this.generateMockDepartmentData('查获数量')
169
+          this.workingTableData = this.generateMockDepartmentData('在岗时长')
170
+          this.inspectionTableData = this.generateMockDepartmentData('巡检合格率')
171
+          this.questionTableData = this.generateMockDepartmentData('抽问抽答正确率')
172
+          this.learningTableData = this.generateMockDepartmentData('培训答题分数')
173
+        }
174
+      } catch (error) {
175
+        console.error('加载数据失败:', error)
176
+        // 如果接口调用失败,使用模拟数据
177
+        this.seizureTableData = this.generateMockDepartmentData('查获数量')
178
+        this.workingTableData = this.generateMockDepartmentData('在岗时长')
179
+        this.inspectionTableData = this.generateMockDepartmentData('巡检合格率')
180
+        this.questionTableData = this.generateMockDepartmentData('抽问抽答正确率')
181
+        this.learningTableData = this.generateMockDepartmentData('培训答题分数')
182
+
183
+        uni.showToast({
184
+          title: '数据加载失败,使用模拟数据',
185
+          icon: 'none'
186
+        })
187
+      }
188
+    },
189
+
190
+
191
+
192
+
193
+
194
+    // 生成模拟科室数据(根据类型生成不同科室的统计数据)
195
+    generateMockDepartmentData(type) {
196
+      // 模拟科室列表
197
+      const departments = ['安检一科', '安检二科', '安检三科', '安检四科', '安检五科']
198
+
199
+      // 根据类型设置基础数值范围
200
+      const baseConfig = {
201
+        '查获数量': { baseTotal: 800, baseAvg: 20, baseMedian: 18, baseMax: 40, baseMin: 5 },
202
+        '在岗时长': { baseTotal: 1200, baseAvg: 30, baseMedian: 28, baseMax: 60, baseMin: 15 },
203
+        '巡检合格率': { baseTotal: 1000, baseAvg: 25, baseMedian: 23, baseMax: 50, baseMin: 10 },
204
+        '抽问抽答正确率': { baseTotal: 600, baseAvg: 15, baseMedian: 14, baseMax: 30, baseMin: 3 },
205
+        '培训答题分数': { baseTotal: 900, baseAvg: 22, baseMedian: 21, baseMax: 45, baseMin: 8 }
206
+      }
207
+
208
+      const config = baseConfig[type] || baseConfig['查获数量']
209
+
210
+      return departments.map((dept, index) => {
211
+        // 为每个科室生成略有差异的数据
212
+        const variation = (index + 1) * 0.1 // 每个科室数据略有不同
213
+
214
+        return {
215
+          name: dept,
216
+          totalNumber: Math.round(config.baseTotal * (1 + variation * 0.2)),
217
+          averageNumber: (config.baseAvg * (1 + variation * 0.1)).toFixed(1),
218
+          medianNumber: (config.baseMedian * (1 + variation * 0.1)).toFixed(1),
219
+          maxNumber: Math.round(config.baseMax * (1 + variation * 0.3)),
220
+          minNumber: Math.round(config.baseMin * (1 + variation * 0.1))
221
+        }
222
+      })
223
+    },
224
+
225
+    // 生成模拟表格数据(根据时间参数调整数据)
226
+    generateMockTableData(type, queryParams = {}) {
227
+      // 根据时间范围调整数据值
228
+      let baseValue = 1000
229
+      let completionRate = 98.5
230
+      let passRate = 96.2
231
+      let avgTime = 15
232
+
233
+      // 如果有时间参数,根据时间范围调整数据
234
+      if (queryParams.startDate && queryParams.endDate) {
235
+        // 计算时间跨度(天数)
236
+        const start = new Date(queryParams.startDate)
237
+        const end = new Date(queryParams.endDate)
238
+        const diffTime = Math.abs(end - start)
239
+        const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
240
+
241
+        // 根据时间跨度调整数据
242
+        baseValue = Math.round(1000 * (diffDays / 30)) // 假设30天为基准
243
+        completionRate = 98.5 - (30 - Math.min(diffDays, 30)) * 0.1
244
+        passRate = 96.2 - (30 - Math.min(diffDays, 30)) * 0.05
245
+        avgTime = 15 + (30 - Math.min(diffDays, 30)) * 0.2
246
+      }
247
+
248
+      return [
249
+        { label: `${type}总数`, value: baseValue.toLocaleString() },
250
+        { label: `${type}完成率`, value: `${completionRate.toFixed(1)}%` },
251
+        { label: `${type}合格率`, value: `${passRate.toFixed(1)}%` },
252
+        { label: `${type}平均用时`, value: `${avgTime.toFixed(0)}分钟` }
253
+      ]
254
+    }
255
+  }
256
+}
257
+</script>
258
+
259
+<style lang="scss" scoped>
260
+.statistical-report {
261
+  min-height: 100vh;
262
+  background-color: #f5f7fa;
263
+}
264
+
265
+// 时间范围显示样式
266
+.time-range-display {
267
+  background-color: #ffffff;
268
+  padding: 24rpx 32rpx;
269
+  margin-bottom: 24rpx;
270
+  border-radius: 12rpx;
271
+  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
272
+
273
+  .time-range-text {
274
+    font-size: 28rpx;
275
+    color: #666;
276
+    font-weight: 500;
277
+  }
278
+}
279
+
280
+.page-header {
281
+  margin-bottom: 32rpx;
282
+}
283
+
284
+.statistic-tables-section {
285
+  .statistic-table {
286
+    margin-bottom: 32rpx;
287
+  }
288
+}
289
+
290
+// 响应式适配
291
+@media (max-width: 750px) {
292
+  .time-range-display {
293
+    padding: 20rpx 24rpx;
294
+    margin-bottom: 20rpx;
295
+
296
+    .time-range-text {
297
+      font-size: 26rpx;
298
+    }
299
+  }
300
+
301
+  .statistic-tables-section {
302
+    .statistic-table {
303
+      margin-bottom: 24rpx;
304
+    }
305
+  }
306
+}
307
+</style>

+ 7 - 31
src/pages/workProfile/index.vue

@@ -165,29 +165,9 @@ export default {
165 165
         handleLevelChange(value) {
166 166
             console.log("value===", value, this.levelOptions)
167 167
             let targetObj = {}
168
-            // 递归查找函数
169
-            const findInTree = (items, id) => {
170
-                for (let item of items) {
171
-                    if (item.id == id) {
172
-                        return item;
173
-                    }
174
-                    if (item.children && item.children.length > 0) {
175
-                        const found = findInTree(item.children, id);
176
-                        if (found) return found;
177
-                    }
178
-                }
179
-                return null;
180
-            };
181
-
182
-            targetObj = findInTree(this.levelOptions, value);
183
-            if (!targetObj) {
184
-                console.warn('未找到匹配的部门对象,id:', value);
185
-                return;
186
-            }
187
-            console.log("targetObj===", targetObj)
188
-
189
-            this.selectedLevelId = targetObj.id;
190
-            this.selectedLevelType = targetObj.deptType == 'STATION' ? 'station' : targetObj.deptType == 'BRIGADE' ? 'brigade' : 'department'
168
+            targetObj = this.levelOptions.find(item => item.id == value);
169
+            this.selectedLevelId = targetObj.value
170
+            this.selectedLevelType = targetObj.deptType == 'STATION' ? 'station' : 'department'
191 171
             this.loadData()
192 172
         },
193 173
         loadData() {
@@ -213,11 +193,11 @@ export default {
213 193
         },
214 194
         //获取抽问抽答完成率
215 195
         getLevelRateData() {
216
-            let params = {};
196
+            let params = {}
217 197
             let api = this.selectedLevelType == 'station' ? getStationLevelRate : getDepartmentLevelRate
218 198
             if (this.selectedLevelType == 'station') {
219 199
                 params.siteId = this.selectedLevelId
220
-            } else if (this.selectedLevelType == 'department' || this.selectedLevelType == 'brigade') {
200
+            } else if (this.selectedLevelType == 'department') {
221 201
                 params.deptId = this.selectedLevelId
222 202
             }
223 203
             api(params).then(res => {
@@ -251,6 +231,7 @@ export default {
251 231
 
252 232
                 })
253 233
             }
234
+
254 235
             if (this.isJingLi) {
255 236
 
256 237
                 const deptId = this.currentUser.deptId;
@@ -302,16 +283,11 @@ export default {
302 283
             let otherparams = {
303 284
                 deptId: this.selectedLevelId || ''
304 285
             }
305
-            console.log("params===", params, this.selectedLevelType)
306 286
             if (this.selectedLevelType == 'station') {
307 287
                 params.checkedSiteId = this.selectedLevelId
308
-            } 
309
-            if (this.selectedLevelType == 'department') {
288
+            } else if (this.selectedLevelType == 'department') {
310 289
                 params.checkedDepartmentId = this.selectedLevelId
311 290
             }
312
-            if (this.selectedLevelType == 'brigade') {
313
-                params.checkedBrigadeId = this.selectedLevelId
314
-            }
315 291
             getPortrait(params).then(res => {
316 292
                 this.portraitData = res.data || {};
317 293
             })

BIN
src/static/images/icon/bronze.png


BIN
src/static/images/icon/gold.png


BIN
src/static/images/icon/one.png


BIN
src/static/images/icon/orange-face.png


BIN
src/static/images/icon/red-face.png


BIN
src/static/images/icon/silver.png


BIN
src/static/images/icon/three.png


BIN
src/static/images/icon/two.png


BIN
src/static/images/icon/yellow-face.png