Преглед изворни кода

Merge branch 'profile&newQ20260527' into dev

huoyi пре 1 недеља
родитељ
комит
45b39073e6

+ 6 - 1
src/pages/components/AreaDistribution.vue

@@ -50,11 +50,16 @@ export default {
50 50
           trigger: 'axis',
51 51
           axisPointer: { type: 'shadow' }
52 52
         },
53
+        legend: {
54
+          data: ['查获数量'],
55
+          textStyle: { color: '#666', fontSize: 10 },
56
+          top: 0
57
+        },
53 58
         grid: {
54 59
           left: '15%',
55 60
           right: '5%',
56 61
           bottom: '15%',
57
-          top: '10%'
62
+          top: '22%'
58 63
         },
59 64
         xAxis: {
60 65
           type: 'category',

+ 7 - 1
src/pages/components/DailySeizureChart.vue

@@ -81,11 +81,16 @@ export default {
81 81
           trigger: 'axis',
82 82
           axisPointer: { type: 'shadow' }
83 83
         },
84
+        legend: {
85
+          data: ['查获总数'],
86
+          textStyle: { color: '#666', fontSize: 10 },
87
+          top: 0
88
+        },
84 89
         grid: {
85 90
           left: '10%',
86 91
           right: '5%',
87 92
           bottom: '18%',
88
-          top: '10%'
93
+          top: '22%'
89 94
         },
90 95
         xAxis: {
91 96
           type: 'category',
@@ -166,6 +171,7 @@ export default {
166 171
           axisPointer: { type: 'shadow' }
167 172
         },
168 173
         legend: {
174
+          type:'scroll',
169 175
           data: (this.chartsData.dept.deptData || []).map(d => d.name),
170 176
           textStyle: {
171 177
             color: '#333',

+ 6 - 1
src/pages/components/InterceptionDistribution.vue

@@ -45,11 +45,16 @@ export default {
45 45
             const option = {
46 46
                 responsive: true,
47 47
                 maintainAspectRatio: false,
48
+                legend: {
49
+                    data: ['拦截数量'],
50
+                    textStyle: { color: '#666', fontSize: 10 },
51
+                    top: 0
52
+                },
48 53
                 grid: {
49 54
                     left: '25%',
50 55
                     right: '10%',
51 56
                     bottom: '10%',
52
-                    top: '10%'
57
+                    top: '22%'
53 58
                 },
54 59
                 xAxis: {
55 60
                     type: 'value',

+ 10 - 4
src/pages/components/ItemDistribution.vue

@@ -1,12 +1,12 @@
1 1
 <template>
2 2
   <div class="item-distribution">
3 3
     <div class="chart-container" ref="itemChart"></div>
4
-    <div class="legend-grid">
4
+    <!-- <div class="legend-grid">
5 5
       <div v-for="(item, index) in chartsData.items" :key="index" class="legend-item">
6 6
         <div class="legend-dot" :style="{ backgroundColor: item.color }"></div>
7 7
         <div class="legend-text">{{ item.name }}: {{ item.value }}</div>
8 8
       </div>
9
-    </div>
9
+    </div> -->
10 10
   </div>
11 11
 </template>
12 12
 
@@ -57,9 +57,15 @@ export default {
57 57
           trigger: 'item',
58 58
           formatter: '{b}: {c} ({d}%)'
59 59
         },
60
+        legend: {
61
+          type: 'scroll',
62
+          data: this.chartsData.items.map(item => item.name),
63
+          textStyle: { color: '#666', fontSize: 10 },
64
+          top: 0
65
+        },
60 66
         series: [{
61 67
           type: 'pie',
62
-          radius: ['50%', '70%'],
68
+          radius: ['30%', '47%'],
63 69
           data: this.chartsData.items.map(item => ({
64 70
             name: item.name,
65 71
             value: item.value,
@@ -90,7 +96,7 @@ export default {
90 96
 <style lang="scss" scoped>
91 97
 .item-distribution {
92 98
   .chart-container {
93
-    height: 300rpx;
99
+    height: 450rpx;
94 100
   }
95 101
 
96 102
   .legend-grid {

+ 26 - 10
src/pages/components/MemberBasicDistribution.vue

@@ -75,9 +75,14 @@ export default {
75 75
           trigger: 'item',
76 76
           formatter: '{b}: {c} ({d}%)'
77 77
         },
78
+        legend: {
79
+          data: this.chartsData.gender.labels,
80
+          textStyle: { color: '#666', fontSize: 10 },
81
+          top: 0
82
+        },
78 83
         series: [{
79 84
           type: 'pie',
80
-          radius: ['40%', '60%'],
85
+          radius: ['25%', '40%'],
81 86
           data: this.chartsData.gender.data.map((value, index) => ({
82 87
             name: this.chartsData.gender.labels[index],
83 88
             value: value
@@ -108,19 +113,25 @@ export default {
108 113
           trigger: 'item',
109 114
           formatter: '{b}: {c} ({d}%)'
110 115
         },
116
+        legend: {
117
+          type: 'scroll',
118
+          data: this.chartsData.ethnicity.labels,
119
+          textStyle: { color: '#666', fontSize: 10 },
120
+          top: 0
121
+        },
111 122
         series: [{
112 123
           type: 'pie',
113
-          radius: ['40%', '60%'],
124
+          radius: ['25%', '40%'],
114 125
           data: this.chartsData.ethnicity.data.map((value, index) => ({
115 126
             name: this.chartsData.ethnicity.labels[index],
116 127
             value: value
117 128
           })),
118
-          itemStyle: {
119
-            color: function(params) {
120
-              const colors = ['#A78BFA', '#34D399']
121
-              return colors[params.dataIndex]
122
-            }
123
-          },
129
+          // itemStyle: {
130
+          //   color: function(params) {
131
+          //     const colors = ['#A78BFA', '#34D399']
132
+          //     return colors[params.dataIndex]
133
+          //   }
134
+          // },
124 135
           label: {
125 136
             show: true,
126 137
             color: '#333',
@@ -141,9 +152,14 @@ export default {
141 152
           trigger: 'item',
142 153
           formatter: '{b}: {c} ({d}%)'
143 154
         },
155
+        legend: {
156
+          data: this.chartsData.political.labels,
157
+          textStyle: { color: '#666', fontSize: 10 },
158
+          top: 0
159
+        },
144 160
         series: [{
145 161
           type: 'pie',
146
-          radius: ['40%', '60%'],
162
+          radius: ['25%', '40%'],
147 163
           data: this.chartsData.political.data.map((value, index) => ({
148 164
             name: this.chartsData.political.labels[index],
149 165
             value: value
@@ -195,7 +211,7 @@ export default {
195 211
 
196 212
     .chart-container {
197 213
       flex: 1;
198
-      height:250rpx;
214
+      height:450rpx;
199 215
     }
200 216
   }
201 217
 }

+ 29 - 9
src/pages/components/MemberPositionDistribution.vue

@@ -73,11 +73,16 @@ export default {
73 73
           trigger: 'axis',
74 74
           axisPointer: { type: 'shadow' }
75 75
         },
76
+        legend: {
77
+          data: ['人数'],
78
+          textStyle: { color: '#666', fontSize: 10 },
79
+          top: 0
80
+        },
76 81
         grid: {
77 82
           left: '10%',
78 83
           right: '5%',
79 84
           bottom: '14%',
80
-          top: '10%'
85
+          top: '22%'
81 86
         },
82 87
         xAxis: {
83 88
           type: 'category',
@@ -89,7 +94,8 @@ export default {
89 94
           },
90 95
           axisLabel: {
91 96
             color: '#666',
92
-            fontSize: 10
97
+            fontSize: 10,
98
+            rotate: 30
93 99
           }
94 100
         },
95 101
         yAxis: {
@@ -107,7 +113,8 @@ export default {
107 113
           },
108 114
           axisLabel: {
109 115
             color: '#666',
110
-            fontSize: 10
116
+            fontSize: 10,
117
+            rotate: 30
111 118
           }
112 119
         },
113 120
         series: [{
@@ -142,11 +149,16 @@ export default {
142 149
           trigger: 'axis',
143 150
           axisPointer: { type: 'shadow' }
144 151
         },
152
+        legend: {
153
+          data: ['人数'],
154
+          textStyle: { color: '#666', fontSize: 10 },
155
+          top: 0
156
+        },
145 157
         grid: {
146 158
           left: '10%',
147 159
           right: '5%',
148 160
           bottom: '14%',
149
-          top: '10%'
161
+          top: '22%'
150 162
         },
151 163
         xAxis: {
152 164
           type: 'category',
@@ -158,7 +170,8 @@ export default {
158 170
           },
159 171
           axisLabel: {
160 172
             color: '#666',
161
-            fontSize: 10
173
+            fontSize: 10,
174
+            rotate: 30
162 175
           }
163 176
         },
164 177
         yAxis: {
@@ -176,7 +189,8 @@ export default {
176 189
           },
177 190
           axisLabel: {
178 191
             color: '#666',
179
-            fontSize: 10
192
+            fontSize: 10,
193
+            rotate: 30
180 194
           }
181 195
         },
182 196
         series: [{
@@ -211,11 +225,16 @@ export default {
211 225
           trigger: 'axis',
212 226
           axisPointer: { type: 'shadow' }
213 227
         },
228
+        legend: {
229
+          data: ['人数'],
230
+          textStyle: { color: '#666', fontSize: 10 },
231
+          top: 0
232
+        },
214 233
         grid: {
215 234
           left: '10%',
216 235
           right: '5%',
217 236
           bottom: '20%',
218
-          top: '10%'
237
+          top: '22%'
219 238
         },
220 239
         xAxis: {
221 240
           type: 'category',
@@ -229,7 +248,7 @@ export default {
229 248
             color: '#666',
230 249
             fontSize: 10,
231 250
             rotate: 30,
232
-            margin: 15
251
+         
233 252
           }
234 253
         },
235 254
         yAxis: {
@@ -247,7 +266,8 @@ export default {
247 266
           },
248 267
           axisLabel: {
249 268
             color: '#666',
250
-            fontSize: 10
269
+            fontSize: 10,
270
+            rotate: 30
251 271
           }
252 272
         },
253 273
         dataZoom: [{

+ 21 - 3
src/pages/components/SecurityTestCharts.vue

@@ -83,9 +83,15 @@ export default {
83 83
           trigger: 'item',
84 84
           formatter: '{b}: {c} ({d}%)'
85 85
         },
86
+        legend: {
87
+          type: 'scroll',
88
+          data: this.chartsData.items.labels,
89
+          textStyle: { color: '#666', fontSize: 10 },
90
+          top: 0
91
+        },
86 92
         series: [{
87 93
           type: 'pie',
88
-          radius: ['50%', '70%'],
94
+          radius: ['30%', '40%'],
89 95
           data: this.chartsData.items.labels.map((label, index) => ({
90 96
             name: label,
91 97
             value: this.chartsData.items.data[index]
@@ -112,13 +118,19 @@ export default {
112 118
       const option = {
113 119
         responsive: true,
114 120
         maintainAspectRatio: false,
121
+        legend: {
122
+          type: 'scroll',
123
+          data: this.chartsData.results.labels,
124
+          textStyle: { color: '#666', fontSize: 10 },
125
+          top: 0
126
+        },
115 127
         tooltip: {
116 128
           trigger: 'item',
117 129
           formatter: '{b}: {c} ({d}%)'
118 130
         },
119 131
         series: [{
120 132
           type: 'pie',
121
-          radius: ['50%', '70%'],
133
+          radius: ['30%', '40%'],
122 134
           data: this.chartsData.results.labels.map((label, index) => ({
123 135
             name: label,
124 136
             value: this.chartsData.results.data[index]
@@ -149,9 +161,15 @@ export default {
149 161
           trigger: 'item',
150 162
           formatter: '{b}: {c} ({d}%)'
151 163
         },
164
+        legend: {
165
+          type: 'scroll',
166
+          data: this.chartsData.areas.labels,
167
+          textStyle: { color: '#666', fontSize: 10 },
168
+          top: 0
169
+        },
152 170
         series: [{
153 171
           type: 'pie',
154
-          radius: ['50%', '70%'],
172
+          radius: ['30%', '40%'],
155 173
           data: this.chartsData.areas.labels.map((label, index) => ({
156 174
             name: label,
157 175
             value: this.chartsData.areas.data[index]

+ 6 - 1
src/pages/components/SeizedNumAll.vue

@@ -47,7 +47,12 @@ export default {
47 47
                     left: '15%',
48 48
                     right: '5%',
49 49
                     bottom: '15%',
50
-                    top: '10%'
50
+                    top: '22%'
51
+                },
52
+                legend: {
53
+                    data: ['查获数'],
54
+                    textStyle: { color: '#666', fontSize: 10 },
55
+                    top: 0
51 56
                 },
52 57
                 xAxis: {
53 58
                     type: 'category',

+ 6 - 1
src/pages/components/SeizureInfo.vue

@@ -130,11 +130,16 @@ export default {
130 130
           trigger: 'axis',
131 131
           axisPointer: { type: 'shadow' }
132 132
         },
133
+        legend: {
134
+          data: ['查获数量'],
135
+          textStyle: { color: '#666', fontSize: 10 },
136
+          top: 0
137
+        },
133 138
         grid: {
134 139
           left: '15%',
135 140
           right: '5%',
136 141
           bottom: '15%',
137
-          top: '10%'
142
+          top: '22%'
138 143
         },
139 144
         xAxis: {
140 145
           type: 'category',

+ 6 - 1
src/pages/components/SupervisionDistribution.vue

@@ -47,7 +47,7 @@ export default {
47 47
                     left: '15%',
48 48
                     right: '5%',
49 49
                     bottom: '20%',
50
-                    top: '10%'
50
+                    top: '22%'
51 51
                 },
52 52
                 xAxis: {
53 53
                     type: 'category',
@@ -101,6 +101,11 @@ export default {
101 101
                         barWidth: '40%'
102 102
                     }
103 103
                 ],
104
+                legend: {
105
+                    data: ['查获数量'],
106
+                    textStyle: { color: '#666', fontSize: 10 },
107
+                    top: 0
108
+                },
104 109
                 tooltip: {
105 110
                     trigger: 'axis',
106 111
                     axisPointer: { type: 'shadow' }

+ 6 - 1
src/pages/components/UnsafeItemsChart.vue

@@ -61,6 +61,11 @@ export default {
61 61
           trigger: 'item',
62 62
           formatter: '{b}: {c} ({d}%)'
63 63
         },
64
+        legend: {
65
+          data: this.chartsData.items.map(item => item.name),
66
+          textStyle: { color: '#666', fontSize: 10 },
67
+          top: 0
68
+        },
64 69
         series: [{
65 70
           type: 'pie',
66 71
           radius: ['50%', '70%'],
@@ -94,7 +99,7 @@ export default {
94 99
 <style lang="scss" scoped>
95 100
 .unsafe-items-chart {
96 101
   .chart-container {
97
-    height: 220rpx;
102
+    height: 400rpx;
98 103
   }
99 104
 
100 105
   .items-list {

+ 7 - 2
src/pages/components/UnsafePositionChart.vue

@@ -56,11 +56,16 @@ export default {
56 56
           trigger: 'axis',
57 57
           axisPointer: { type: 'shadow' }
58 58
         },
59
+        legend: {
60
+          data: ['人数'],
61
+          textStyle: { color: '#666', fontSize: 10 },
62
+          top: 0
63
+        },
59 64
         grid: {
60 65
           left: '15%',
61 66
           right: '5%',
62 67
           bottom: '15%',
63
-          top: '10%'
68
+          top: '22%'
64 69
         },
65 70
         xAxis: {
66 71
           type: 'category',
@@ -126,7 +131,7 @@ export default {
126 131
 <style lang="scss" scoped>
127 132
 .unsafe-position-chart {
128 133
   .chart-container {
129
-    height: 240rpx;
134
+    height: 400rpx;
130 135
   }
131 136
 
132 137
   .position-info {

+ 6 - 1
src/pages/components/UnsafeTypesChart.vue

@@ -61,6 +61,11 @@ export default {
61 61
           trigger: 'item',
62 62
           formatter: '{b}: {c} ({d}%)'
63 63
         },
64
+        legend: {
65
+          data: this.chartsData.types.map(item => item.name),
66
+          textStyle: { color: '#666', fontSize: 10 },
67
+          top: 0
68
+        },
64 69
         series: [{
65 70
           type: 'pie',
66 71
           radius: ['50%', '70%'],
@@ -94,7 +99,7 @@ export default {
94 99
 <style lang="scss" scoped>
95 100
 .unsafe-types-chart {
96 101
   .chart-container {
97
-    height: 220rpx;
102
+    height: 400rpx;
98 103
   }
99 104
 
100 105
   .types-list {

+ 2 - 2
src/pages/deptProfile/index.vue

@@ -59,7 +59,7 @@
59 59
             </SectionTitle>
60 60
 
61 61
             <view v-if="activeTab === 'profile'">
62
-                <SectionTitle title="维得分一览">
62
+                <SectionTitle title="维得分一览">
63 63
                     <ProfileRadar :chartsData="radarData" />
64 64
                 </SectionTitle>
65 65
 
@@ -77,7 +77,7 @@
77 77
             </view>
78 78
 
79 79
             <view v-if="activeTab === 'data'">
80
-                <SectionTitle title="每日通道过检率">
80
+                <SectionTitle title="通道高峰过检率">
81 81
                     <PassengerChart :chartsData="passengerData" />
82 82
                 </SectionTitle>
83 83
 

+ 388 - 72
src/pages/employeeProfile/index.vue

@@ -18,15 +18,15 @@
18 18
                     </view>
19 19
                 </scroll-view>
20 20
                 <view v-if="selectedTimeTag === 4" class="date-range-picker">
21
-                    <picker mode="date" :value="startDate" @change="onStartDateChange">
22
-                        <view class="date-input" :class="{ filled: startDate }">
23
-                            {{ startDate || '开始日期' }}
21
+                    <picker mode="date" :value="beginTime" @change="onBeginTimeChange">
22
+                        <view class="date-input" :class="{ filled: beginTime }">
23
+                            {{ beginTime || '开始日期' }}
24 24
                         </view>
25 25
                     </picker>
26 26
                     <text class="date-separator">至</text>
27
-                    <picker mode="date" :value="endDate" @change="onEndDateChange">
28
-                        <view class="date-input" :class="{ filled: endDate }">
29
-                            {{ endDate || '结束日期' }}
27
+                    <picker mode="date" :value="endTime" @change="onEndTimeChange">
28
+                        <view class="date-input" :class="{ filled: endTime }">
29
+                            {{ endTime || '结束日期' }}
30 30
                         </view>
31 31
                     </picker>
32 32
                 </view>
@@ -98,7 +98,8 @@
98 98
                             <view class="info-item">
99 99
                                 <view class="info-label">标签:</view>
100 100
                                 <view class="info-value" v-if="portrait.userTags">
101
-                                    <text class="tag" v-for="tag in portrait.userTags.split(',')" :key="tag">{{ tag }}</text>
101
+                                    <text class="tag" v-for="tag in portrait.userTags.split(',')" :key="tag">{{ tag
102
+                                        }}</text>
102 103
                                 </view>
103 104
                             </view>
104 105
                         </view>
@@ -106,13 +107,13 @@
106 107
                         <view class="score-section">
107 108
                             <div class="score-circle" ref="scoreCircle"></div>
108 109
                             <view class="score-box">
109
-                                <view class="score-row">
110
+                                <!-- <view class="score-row">
110 111
                                     <text class="score-label">评分:</text>
111 112
                                     <text class="score-val">{{ portrait.totalScore || 0 }}</text>
112
-                                </view>
113
+                                </view> -->
113 114
                                 <view class="score-row">
114
-                                    <text class="score-label">标签得分:</text>
115
-                                    <text class="score-val">{{ tagScoreDisplay }}</text>
115
+                                    <text class="score-label">附加分:</text>
116
+                                    <text class="score-val">{{ tagScoreDisplay }}</text>
116 117
                                 </view>
117 118
                             </view>
118 119
                         </view>
@@ -146,7 +147,13 @@
146 147
                 </SectionTitle>
147 148
 
148 149
                 <SectionTitle v-if="activeTab === 'profile'" title="个人能力">
149
-                    <div class="chart-container" ref="radarChart"></div>
150
+                    <div class="chart-legend">
151
+                        <div class="legend-item legend-warning"><span></span>预警线(低于75分)</div>
152
+                        <!-- <div class="legend-item legend-normal"><span></span>正常线(75~90分)</div> -->
153
+                        <div class="legend-item legend-excellent"><span></span>优秀线(高于90分)</div>
154
+                        <div class="legend-item legend-current"><span></span>当前员工分值</div>
155
+                    </div>
156
+                    <div class="chart-container" ref="radarChart" ></div>
150 157
                 </SectionTitle>
151 158
 
152 159
                 <SectionTitle v-if="activeTab === 'profile'" title="补充信息">
@@ -211,7 +218,7 @@
211 218
                         <view class="warning-score-item">
212 219
                             <text class="warning-score-label">综合得分</text>
213 220
                             <text class="warning-score-value" :class="scoreLevelClass">{{ portrait.totalScore || 0
214
-                                }}</text>
221
+                            }}</text>
215 222
                         </view>
216 223
                     </view>
217 224
                     <view class="warning-detail" v-if="scoreDetails.length">
@@ -243,14 +250,8 @@
243 250
                 </view>
244 251
                 <scroll-view v-if="!employeeSearchKeyword.trim()" scroll-y class="tree-list">
245 252
                     <template v-for="(node, index) in deptTreeData">
246
-                        <employee-tree-node
247
-                            :key="node.id"
248
-                            :node="node"
249
-                            :expanded-ids="expandedDeptIds"
250
-                            :selected-id="selectedEmployeeId"
251
-                            @toggle="toggleDeptExpand"
252
-                            @select="onEmployeeSelect"
253
-                        />
253
+                        <employee-tree-node :key="node.id" :node="node" :expanded-ids="expandedDeptIds"
254
+                            :selected-id="selectedEmployeeId" @toggle="toggleDeptExpand" @select="onEmployeeSelect" />
254 255
                     </template>
255 256
                 </scroll-view>
256 257
                 <scroll-view v-else scroll-y class="employee-list">
@@ -269,6 +270,54 @@
269 270
                 </scroll-view>
270 271
             </view>
271 272
         </u-popup>
273
+
274
+        <!-- 雷达图tooltip -->
275
+        <u-popup :show="showRadarTooltipPopup" mode="center" :round="12"
276
+            :mask-close-able="true" @close="closeRadarPopup"> 
277
+
278
+            <div class="radar-tooltip-popup">
279
+                <div class="tooltip-card">
280
+                    <!-- 标题 -->
281
+                    <view class="tooltip-header">
282
+                        <text class="tooltip-title">{{ activeDimName }}</text>
283
+                        <u-icon name="close" color="#666666" size="20" @click="closeRadarPopup" />
284
+                    </view>
285
+
286
+                    <!-- 加分区域 -->
287
+                    <div class="tooltip-section">
288
+                        <div class="section-list">
289
+                            <template v-if="addGroupList.length">
290
+                                <div class="list-item" v-for="(item, i) in addGroupList" :key="i">
291
+                                    <span>{{ item.name }}:</span>
292
+                                    <span class="add-text">+{{ item.total }}分</span>
293
+                                </div>
294
+                            </template>
295
+                            <div v-else class="p-empty">暂无加分记录</div>
296
+                        </div>
297
+                        <div class="section-total add">
298
+                            <span>合计加分:</span>
299
+                            <span>{{ addTotal }}分</span>
300
+                        </div>
301
+                    </div>
302
+                    <!-- 扣分区域 -->
303
+                    <div class="tooltip-section">
304
+                        <div class="section-list">
305
+                            <template v-if="deductGroupList.length">
306
+                                <div class="list-item" v-for="(item, i) in deductGroupList" :key="i">
307
+                                    <span>{{ item.name }}:</span>
308
+                                    <span class="deduct-text">{{ item.total }}分</span>
309
+                                </div>
310
+                            </template>
311
+                            <div v-else class="p-empty">暂无扣分记录</div>
312
+                        </div>
313
+                        <div class="section-total deduct">
314
+                            <span>合计扣分:</span>
315
+                            <span>{{ deductTotal }}分</span>
316
+                        </div>
317
+                    </div>
318
+                </div>
319
+            </div>
320
+        </u-popup>
272 321
     </view>
273 322
 </template>
274 323
 
@@ -309,8 +358,8 @@ export default {
309 358
             timeTags: ['近一周', '近一月', '近三月', '近一年', '自定义时间范围'],
310 359
             currentTime: '',
311 360
             timer: null,
312
-            startDate: '',
313
-            endDate: '',
361
+            beginTime: '',
362
+            endTime: '',
314 363
             searchKeyword: '',
315 364
             portrait: { dimensions: [], awards: [] },
316 365
             tagScoreData: null,
@@ -329,7 +378,14 @@ export default {
329 378
             employeeLoading: false,
330 379
             expandedDeptIds: [],
331 380
             isSecurityCheck: false,
332
-            userInfo: null
381
+            userInfo: null,
382
+            // 雷达图tooltip相关
383
+            activeDimName: null,
384
+            radarTooltipPosition: { x: 0, y: 0 },
385
+            // 控制雷达图 tooltip popup 显示
386
+            showRadarTooltipPopup: false,
387
+            // 评分明细数据
388
+            allScoreDetails: []
333 389
         }
334 390
     },
335 391
     computed: {
@@ -350,11 +406,7 @@ export default {
350 406
             return age + '岁'
351 407
         },
352 408
         tagScoreDisplay() {
353
-            if (this.tagScoreData == null) return '0'
354
-            if (typeof this.tagScoreData === 'object') {
355
-                return this.tagScoreData.totalScore ?? this.tagScoreData.score ?? this.tagScoreData ?? '0'
356
-            }
357
-            return this.tagScoreData
409
+            return this.portrait?.userTags?.split(',').length || 0
358 410
         },
359 411
         scoreLevelClass() {
360 412
             if ((this.portrait.totalScore || 0) < 75) return 'score-danger'
@@ -376,6 +428,39 @@ export default {
376 428
             return this.employeeList.filter(item =>
377 429
                 (item.nickName || '').toLowerCase().includes(keyword)
378 430
             )
431
+        },
432
+        // 雷达图tooltip相关计算
433
+        addList() {
434
+            const all = (this.allScoreDetails || []).filter(d => d.totalScore != null && Number(d.totalScore) > 0)
435
+            return this.activeDimName ? all.filter(d => d.dimensionName === this.activeDimName) : all
436
+        },
437
+        deductList() {
438
+            const all = (this.allScoreDetails || []).filter(d => d.totalScore != null && Number(d.totalScore) < 0)
439
+            return this.activeDimName ? all.filter(d => d.dimensionName === this.activeDimName) : all
440
+        },
441
+        addGroupList() {
442
+            const groups = {}
443
+            this.addList.forEach(d => {
444
+                const key = d.level2Name || '其他'
445
+                groups[key] = (groups[key] || 0) + Number(d.totalScore)
446
+            })
447
+            return Object.entries(groups).map(([name, total]) => ({ name, total: total.toFixed(2) }))
448
+        },
449
+        deductGroupList() {
450
+            const groups = {}
451
+            this.deductList.forEach(d => {
452
+                const key = d.level2Name || '其他'
453
+                groups[key] = (groups[key] || 0) + Number(d.totalScore)
454
+            })
455
+            return Object.entries(groups).map(([name, total]) => ({ name, total: total.toFixed(2) }))
456
+        },
457
+        addTotal() {
458
+            const s = this.addList.reduce((acc, d) => acc + Number(d.totalScore), 0)
459
+            return (s > 0 ? '+' : '') + s.toFixed(2)
460
+        },
461
+        deductTotal() {
462
+            const s = this.deductList.reduce((acc, d) => acc + Number(d.totalScore), 0)
463
+            return s.toFixed(2)
379 464
         }
380 465
     },
381 466
     mounted() {
@@ -383,6 +468,8 @@ export default {
383 468
         this.timer = setInterval(() => {
384 469
             this.updateTime()
385 470
         }, 1000)
471
+        // 默认计算时间范围(近一年)
472
+        this.onTimeTagClick(this.selectedTimeTag)
386 473
         this.fetchEmployeeList()
387 474
     },
388 475
     beforeDestroy() {
@@ -562,16 +649,55 @@ export default {
562 649
         onTimeTagClick(index) {
563 650
             this.selectedTimeTag = index
564 651
             if (index === 4) {
565
-                this.startDate = ''
566
-                this.endDate = ''
652
+                this.beginTime = ''
653
+                this.endTime = ''
567 654
                 return
568 655
             }
656
+            // 自动计算时间范围
657
+            const now = new Date()
658
+            // 设置结束时间为今天
659
+            const endTime = new Date(now.getFullYear(), now.getMonth(), now.getDate())
660
+            // 设置开始时间
661
+            let beginTime
662
+            switch (index) {
663
+                case 0: // 近一周
664
+                    beginTime = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
665
+                    break
666
+                case 1: // 近一月
667
+                    beginTime = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate())
668
+                    break
669
+                case 2: // 近三月
670
+                    beginTime = new Date(now.getFullYear(), now.getMonth() - 3, now.getDate())
671
+                    break
672
+                case 3: // 近一年
673
+                    beginTime = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate())
674
+                    break
675
+                default:
676
+                    beginTime = now
677
+            }
678
+            // 格式化日期为 YYYY-MM-DD
679
+            this.beginTime = this.formatDate(beginTime)
680
+            this.endTime = this.formatDate(endTime)
681
+            // 选择时间标签后立即查询
682
+            if (this.selectedEmployeeId) {
683
+                this.fetchEmployeePortrait()
684
+            }
569 685
         },
570
-        onStartDateChange(e) {
571
-            this.startDate = e.detail.value
686
+        onBeginTimeChange(e) {
687
+            const dateStr = e.detail.value
688
+            this.beginTime = dateStr
689
+            // 自定义时间选择后立即查询
690
+            if (this.selectedEmployeeId && this.endTime) {
691
+                this.fetchEmployeePortrait()
692
+            }
572 693
         },
573
-        onEndDateChange(e) {
574
-            this.endDate = e.detail.value
694
+        onEndTimeChange(e) {
695
+            const dateStr = e.detail.value
696
+            this.endTime = dateStr
697
+            // 自定义时间选择后立即查询
698
+            if (this.selectedEmployeeId && this.beginTime) {
699
+                this.fetchEmployeePortrait()
700
+            }
575 701
         },
576 702
         formatWorkDate(d) {
577 703
             if (!d) return '-'
@@ -589,6 +715,13 @@ export default {
589 715
             if (this.searchKeyword.trim()) {
590 716
                 params.personName = this.searchKeyword.trim()
591 717
             }
718
+            // 添加时间参数
719
+            if (this.beginTime) {
720
+                params.beginTime = this.beginTime
721
+            }
722
+            if (this.endTime) {
723
+                params.endTime = this.endTime
724
+            }
592 725
 
593 726
             const portraitPromise = getEmployeePortrait(params).then(res => {
594 727
                 if (res.code === 200 && res.data) {
@@ -596,12 +729,15 @@ export default {
596 729
                     this.portrait.awards.forEach(item => {
597 730
                         item.color = getRandomHexColor()
598 731
                     })
599
-                    this.scoreDetails = res.data.scoreDetails || []
732
+                    // 只取数组中最后一个元素的 scoreDetails
733
+                    const allScoreDetails = res.data.scoreDetails || []
734
+                    this.allScoreDetails = allScoreDetails
735
+                    this.scoreDetails = allScoreDetails.length > 0 ? allScoreDetails[allScoreDetails.length - 1] : []
600 736
                 }
601 737
             }).catch(() => { })
602 738
 
603 739
             const tagPromise = countTagScore(params).then(res => {
604
-                
740
+
605 741
                 const data = res.data
606 742
                 if (Array.isArray(data)) {
607 743
                     const found = data.find(item => item.userId === this.selectedEmployeeId)
@@ -613,21 +749,21 @@ export default {
613 749
 
614 750
             Promise.all([portraitPromise, tagPromise]).finally(() => {
615 751
                 this.$nextTick(() => {
616
-                    
752
+
617 753
                     this.initRadarChart()
618 754
                     this.initScoreChart()
619 755
                 })
620 756
             })
621 757
         },
622 758
         initScoreChart() {
623
-            
759
+
624 760
             if (!this.$refs.scoreCircle) return
625 761
             if (this.scoreChartInstance) {
626 762
                 this.scoreChartInstance.dispose()
627 763
             }
628 764
             this.scoreChartInstance = echarts.init(this.$refs.scoreCircle)
629 765
             const score = this.portrait.totalScore || 0
630
-            
766
+
631 767
             const option = {
632 768
                 series: [
633 769
                     {
@@ -697,58 +833,113 @@ export default {
697 833
             this.scoreChartInstance.setOption(option)
698 834
         },
699 835
         initRadarChart() {
700
-            
836
+
701 837
             if (!this.$refs.radarChart) return
702 838
             if (this.radarChartInstance) {
703 839
                 this.radarChartInstance.dispose()
704 840
             }
705 841
             this.radarChartInstance = echarts.init(this.$refs.radarChart)
706 842
             const dimensions = this.portrait.dimensions || []
707
-            const maxScore = dimensions.length > 0 ? Math.max(...dimensions.map(d => d.score || 0)) : 100
708
-            const indicator = dimensions.map(d => ({
709
-                name: d.name + '\n' + d.score,
710
-                max: maxScore
711
-            }))
843
+
844
+            // 仿照 index copy.vue 的配置
845
+            const radarData = {
846
+                grounp: dimensions.map(d => ({ name: d.name + '\n\n' + d.score, max: 100 })),
847
+                data: [{
848
+                    name: '个人能力',
849
+                    value: dimensions.map(attr => attr.score),
850
+                    symbolSize: 10,
851
+                    areaStyle: {
852
+                        show: false,
853
+                        opacity: 0
854
+                    },
855
+                    lineStyle: {
856
+                        color: '#4DC8FE',
857
+                        width: 1
858
+                    },
859
+                    itemStyle: { color: '#fff', borderWidth: 1, borderColor: '#00C8DA', borderJoin: 'round' }
860
+                }]
861
+            }
862
+
712 863
             const option = {
713 864
                 radar: {
714
-                    indicator: indicator,
865
+                    indicator: radarData.grounp,
715 866
                     center: ['50%', '52%'],
716
-                    radius: '60%',
867
+                    radius: '50%',
717 868
                     splitNumber: 8,
718
-                    axisLine: {
719
-                        lineStyle: { color: 'rgba(0,0,0,0.15)' }
720
-                    },
721
-                    splitLine: {
722
-                        lineStyle: {
723
-                            color: ['rgba(0,0,0,0.1)', 'rgba(0,0,0,0.1)', 'rgba(0,0,0,0.1)', 'rgba(0,0,0,0.1)', 'rgba(0,0,0,0.1)', 'rgba(0,0,0,0.1)', '#fe4322', '#8EC742', 'rgba(0,0,0,0.1)']
724
-                        }
725
-                    },
869
+                    axisLine: { lineStyle: { color: '#ccc' } },
870
+                    splitLine: { lineStyle: { color: ['#ccc', '#ccc', '#ccc', '#ccc', '#ccc', '#ccc', '#fe4322', '#8EC742', '#ccc'], width: 1 } },
726 871
                     splitArea: { show: false },
727 872
                     axisName: {
728 873
                         color: '#333',
729 874
                         fontSize: 12
730 875
                     }
731 876
                 },
732
-                series: [{
733
-                    type: 'radar',
734
-                    data: [{
735
-                        name: '个人能力',
736
-                        value: dimensions.map(d => d.score),
737
-                        areaStyle: { color: 'rgba(77, 200, 254, 0.2)' },
738
-                        lineStyle: { color: '#4DC8FE', width: 2 },
739
-                        itemStyle: {
740
-                            color: '#333',
741
-                            borderWidth: 1,
742
-                            borderColor: '#00C8DA'
743
-                        },
877
+                series: [
878
+                    {
879
+                        type: 'radar',
880
+                        data: radarData.data,
744 881
                         symbol: 'circle',
745
-                        symbolSize: 8
746
-                    }]
747
-                }]
882
+                        symbolSize: 13,
883
+                        label: {
884
+                            show: false,
885
+                            formatter: (p) => p.value,
886
+                            color: '#333',
887
+                            fontSize: 16,
888
+                            fontWeight: 'bold'
889
+                        }
890
+                    }
891
+                ]
748 892
             }
893
+
749 894
             this.radarChartInstance.setOption(option)
895
+
896
+            // 绑定鼠标事件
897
+            this.bindRadarEvents()
898
+        },
899
+        bindRadarEvents() {
900
+            if (!this.radarChartInstance || !this.$refs.radarChart) return
901
+
902
+            this.radarChartInstance.off('click')
903
+
904
+            this.radarChartInstance.on('click', (params) => {
905
+                if (!Array.isArray(this.portrait.dimensions) || !this.portrait.dimensions.length) return
906
+
907
+                const rect = this.$refs.radarChart.getBoundingClientRect()
908
+                if (!rect) return
909
+
910
+                const cx = rect.width / 2
911
+                const cy = rect.height / 2
912
+                const dx = params.event.offsetX - cx
913
+                const dy = params.event.offsetY - cy
914
+                const distance = Math.sqrt(dx * dx + dy * dy)
915
+                const radarRadius = rect.width * 0.35 // 对应 radius: 65%
916
+
917
+                if (distance > radarRadius) {
918
+                    return
919
+                }
920
+
921
+                // 角度计算
922
+                let angle = Math.atan2(dx, -dy) * (180 / Math.PI)
923
+                if (angle < 0) angle += 360
924
+                const count = this.portrait.dimensions.length
925
+                const step = 360 / count
926
+                let idx = Math.abs(Math.round(angle / step) % count - count)
927
+                if (idx < 0) idx += count
928
+                if (idx >= count) idx = 0
929
+
930
+                // 显示tooltip popup - 加上小延迟避免 u-popup 立即关闭的问题
931
+                this.activeDimName = this.portrait.dimensions[idx].name
932
+                setTimeout(() => {
933
+                    this.showRadarTooltipPopup = true
934
+                }, 50)
935
+            })
936
+        },
937
+        // 关闭雷达图 tooltip popup
938
+        closeRadarPopup() {
939
+            this.showRadarTooltipPopup = false
750 940
         }
751 941
     }
942
+
752 943
 }
753 944
 </script>
754 945
 
@@ -947,7 +1138,7 @@ export default {
947 1138
     border-bottom: 2rpx solid transparent;
948 1139
     transition: all 0.3s;
949 1140
 
950
-     &.active {
1141
+    &.active {
951 1142
         color: #333;
952 1143
         border-bottom-color: #333;
953 1144
     }
@@ -1147,6 +1338,63 @@ export default {
1147 1338
     height: 500rpx;
1148 1339
 }
1149 1340
 
1341
+.chart-legend {
1342
+    display: flex;
1343
+    justify-content: center;
1344
+    align-items: center;
1345
+    flex-wrap: wrap;
1346
+    gap: 10px 18px;
1347
+    padding-top: 8px;
1348
+    color: #fff;
1349
+    font-size: 14px;
1350
+
1351
+}
1352
+
1353
+.legend-item {
1354
+    display: flex;
1355
+    align-items: center;
1356
+    gap: 6rpx;
1357
+
1358
+    span {
1359
+        width: 24rpx;
1360
+        height: 10rpx;
1361
+        border: 2rpx solid currentColor;
1362
+        border-radius: 2rpx;
1363
+    }
1364
+}
1365
+
1366
+.legend-warning {
1367
+    color: #fe4322;
1368
+
1369
+    span {
1370
+        background-color: #fe4322;
1371
+    }
1372
+}
1373
+
1374
+.legend-normal {
1375
+    color: black;
1376
+
1377
+    span {
1378
+        background-color: gray;
1379
+    }
1380
+}
1381
+
1382
+.legend-excellent {
1383
+    color: #8EC742;
1384
+
1385
+    span {
1386
+        background-color: #8EC742;
1387
+    }
1388
+}
1389
+
1390
+.legend-current {
1391
+    color: #1890ff;
1392
+
1393
+    span {
1394
+        background-color: #1890ff;
1395
+    }
1396
+}
1397
+
1150 1398
 .supp-grid {
1151 1399
     display: grid;
1152 1400
     grid-template-columns: 1fr 1fr;
@@ -1318,4 +1566,72 @@ export default {
1318 1566
         }
1319 1567
     }
1320 1568
 }
1569
+
1570
+.radar-tooltip-popup {
1571
+    padding: 20rpx;
1572
+    min-width: 300px;
1573
+
1574
+    .p-empty {
1575
+        text-align: center;
1576
+        margin-top: 10rpx;
1577
+        font-size: 24rpx;
1578
+        color: #999;
1579
+    }
1580
+
1581
+    .tooltip-header {
1582
+        display: flex;
1583
+        justify-content: space-between;
1584
+        align-items: center;
1585
+        padding: 0 10rpx 20rpx;
1586
+        border-bottom: 1rpx solid #eee;
1587
+        margin-bottom: 20rpx;
1588
+
1589
+        .tooltip-title {
1590
+            font-size: 32rpx;
1591
+            font-weight: bold;
1592
+            color: #333;
1593
+        }
1594
+    }
1595
+
1596
+    .tooltip-section {
1597
+        padding: 0 10rpx;
1598
+        margin-bottom: 16rpx;
1599
+    }
1600
+
1601
+    .list-item {
1602
+        margin-top: 8rpx;
1603
+        display: flex;
1604
+        align-items: center;
1605
+        justify-content: space-between;
1606
+        font-size: 26rpx;
1607
+        color: #333;
1608
+
1609
+        .deduct-text {
1610
+            color: #ff4d4f;
1611
+        }
1612
+
1613
+        .add-text {
1614
+            color: #00b42a;
1615
+        }
1616
+    }
1617
+
1618
+    .section-total {
1619
+        margin-top: 12rpx;
1620
+        padding-top: 12rpx;
1621
+        border-top: 1rpx solid #f0f0f0;
1622
+        display: flex;
1623
+        align-items: center;
1624
+        justify-content: space-between;
1625
+        font-weight: 500;
1626
+        font-size: 28rpx;
1627
+
1628
+        &.deduct {
1629
+            color: #ff4d4f;
1630
+        }
1631
+
1632
+        &.add {
1633
+            color: #00b42a;
1634
+        }
1635
+    }
1636
+}
1321 1637
 </style>

+ 1 - 1
src/pages/groupProfile/index.vue

@@ -77,7 +77,7 @@
77 77
             </view>
78 78
 
79 79
             <view v-if="activeTab === 'data'">
80
-                <SectionTitle title="当日开航每小时通道过检率">
80
+                <SectionTitle title="通道高峰过检率">
81 81
                     <PassengerChart :chartsData="passengerData" />
82 82
                 </SectionTitle>
83 83
 

+ 1 - 1
src/pages/teamProfile/index.vue

@@ -77,7 +77,7 @@
77 77
             </view>
78 78
 
79 79
             <view v-if="activeTab === 'data'">
80
-                <SectionTitle title="当日开航每小时通道过检率">
80
+                <SectionTitle title="通道高峰过检率">
81 81
                     <PassengerChart :chartsData="passengerData" />
82 82
                 </SectionTitle>
83 83