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

feat(图表): 优化图表显示并添加数据标签

- 为所有图表系列添加数据标签显示,提升数据可读性
- 统一饼图标签格式为"{b}\n{c} ({d}%)"
- 优化排名组件支持并列排名计算
- 调整图表标签字体大小和位置
- 修复数据字段引用从count改为totalCount
huoyi пре 1 месец
родитељ
комит
9c366de37d

+ 14 - 5
src/views/blockingData/blockingDataScreen/components/ModuleBrigadeOne.vue

@@ -221,7 +221,8 @@ const multiLineChartOption = (xAxisData, series) => ({
221 221
     symbolSize: 6,
222 222
     data: s.data,
223 223
     itemStyle: { color: s.color },
224
-    lineStyle: { color: s.color }
224
+    lineStyle: { color: s.color },
225
+    label: { show: true, position: 'top', fontSize: 9, color: s.color }
225 226
   }))
226 227
 })
227 228
 
@@ -231,7 +232,13 @@ const horizontalBarChartOption = (data, color) => ({
231 232
   grid: { left: '20%', top: '15%', right: '5%', bottom: '15%', containLabel: true },
232 233
   xAxis: { type: 'value', axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' }, splitLine: { lineStyle: { color: '#eee' } } },
233 234
   yAxis: { type: 'category', data: data.map(d => d.name), axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' } },
234
-  series: [{ type: 'bar', data: data.map(d => d.value), itemStyle: { color: color }, barWidth: 15 }]
235
+  series: [{ 
236
+    type: 'bar', 
237
+    data: data.map(d => d.value), 
238
+    itemStyle: { color: color }, 
239
+    barWidth: 15,
240
+    label: { show: true, position: 'right', fontSize: 10, color: color }
241
+  }]
235 242
 })
236 243
 
237 244
 const barLineChartOption = (xAxisData, barData, lineData) => ({
@@ -249,7 +256,8 @@ const barLineChartOption = (xAxisData, barData, lineData) => ({
249 256
       type: 'bar',
250 257
       data: barData,
251 258
       itemStyle: { color: '#3b82f6' },
252
-      barWidth: 20
259
+      barWidth: 20,
260
+      label: { show: true, position: 'top', fontSize: 9, color: '#3b82f6' }
253 261
     },
254 262
     {
255 263
       name: '平均查堵件数',
@@ -259,7 +267,8 @@ const barLineChartOption = (xAxisData, barData, lineData) => ({
259 267
       symbolSize: 6,
260 268
       data: lineData,
261 269
       itemStyle: { color: '#ec4899' },
262
-      lineStyle: { color: '#ec4899' }
270
+      lineStyle: { color: '#ec4899' },
271
+      label: { show: true, position: 'top', fontSize: 9, color: '#ec4899' }
263 272
     }
264 273
   ]
265 274
 })
@@ -276,7 +285,7 @@ const pieChartOption = (data, colors) => ({
276 285
     radius: '65%',
277 286
     center: ['50%', '55%'],
278 287
     data: data,
279
-    label: { show: true, formatter: '{b}', fontSize: 10 }
288
+    label: { show: true, formatter: '{b}\n{c} ({d}%)', fontSize: 10 }
280 289
   }]
281 290
 })
282 291
 

+ 5 - 4
src/views/blockingData/blockingDataScreen/components/ModuleBrigadeTwo.vue

@@ -173,7 +173,7 @@ const fetchData = async () => {
173 173
       supervisorRes.value.data.slice(0, 10).forEach(item => {
174 174
         rankData1.push({
175 175
           name: item.supervisorName || item.name || '',
176
-          value: item.count || item.value || 0
176
+          value: item.totalCount || item.value || 0
177 177
         })
178 178
       })
179 179
     }
@@ -183,7 +183,7 @@ const fetchData = async () => {
183 183
       teamRes.value.data.slice(0, 10).forEach(item => {
184 184
         rankData2.push({
185 185
           name: item.teamLeaderName || item.name || '',
186
-          value: item.count || item.value || 0
186
+          value: item.totalCount || item.value || 0
187 187
         })
188 188
       })
189 189
     }
@@ -193,7 +193,7 @@ const fetchData = async () => {
193 193
       userRes.value.data.slice(0, 10).forEach(item => {
194 194
         rankData3.push({
195 195
           name: item.userName || item.name || '',
196
-          value: item.count || item.value || 0
196
+          value: item.totalCount || item.value || 0
197 197
         })
198 198
       })
199 199
     }
@@ -401,7 +401,8 @@ const multiSeriesBarChartOption = (xAxisData, series) => ({
401 401
     type: 'bar',
402 402
     data: s.data,
403 403
     itemStyle: { color: s.color },
404
-    barWidth: 15
404
+    barWidth: 15,
405
+    label: { show: true, position: 'top', fontSize: 9, color: s.color }
405 406
   }))
406 407
 })
407 408
 

+ 76 - 61
src/views/blockingData/blockingDataScreen/components/ModuleOne.vue

@@ -23,7 +23,7 @@
23 23
             <div class="stat-left">
24 24
               <div class="stat-label">总查堵万分率</div>
25 25
               <div class="stat-content">
26
-                <div class="stat-value">{{ totalBlockedRate }}</div>
26
+                <div class="stat-value">{{ totalBlockedRate.toFixed(2) }}</div>
27 27
                 <div class="stat-unit">‱</div>
28 28
               </div>
29 29
             </div>
@@ -453,14 +453,15 @@ const loadData = async () => {
453 453
               borderColor: '#fff',
454 454
               borderWidth: 2
455 455
             },
456
-            // label: {
457
-            //   show: true,
458
-            //   position: 'center'
459
-            // },
456
+            label: {
457
+              show: true,
458
+              formatter: '{b}\n{c} ({d}%)',
459
+              fontSize: 10
460
+            },
460 461
             emphasis: {
461 462
               label: {
462 463
                 show: true,
463
-                fontSize: '18',
464
+                fontSize: '14',
464 465
                 fontWeight: 'bold'
465 466
               }
466 467
             },
@@ -496,7 +497,8 @@ const loadData = async () => {
496 497
           const item = luggageDataList.find(d => d.brigadeName === brigade)
497 498
           return item ? item.t1PassengerLuggageCount : 0
498 499
         }),
499
-        itemStyle: { color: '#3b82f6' }
500
+        itemStyle: { color: '#3b82f6' },
501
+        label: { show: true, position: 'top', fontSize: 10, color: '#3b82f6' }
500 502
       },
501 503
       {
502 504
         name: 'T2旅检行李数',
@@ -505,7 +507,8 @@ const loadData = async () => {
505 507
           const item = luggageDataList.find(d => d.brigadeName === brigade)
506 508
           return item ? item.t2PassengerLuggageCount : 0
507 509
         }),
508
-        itemStyle: { color: '#22c55e' }
510
+        itemStyle: { color: '#22c55e' },
511
+        label: { show: true, position: 'top', fontSize: 10, color: '#22c55e' }
509 512
       },
510 513
       {
511 514
         name: 'T1行检行李数',
@@ -514,7 +517,8 @@ const loadData = async () => {
514 517
           const item = luggageDataList.find(d => d.brigadeName === brigade)
515 518
           return item ? item.t1CargoLuggageCount : 0
516 519
         }),
517
-        itemStyle: { color: '#f97316' }
520
+        itemStyle: { color: '#f97316' },
521
+        label: { show: true, position: 'top', fontSize: 10, color: '#f97316' }
518 522
       },
519 523
       {
520 524
         name: 'T2行检行李数',
@@ -523,7 +527,8 @@ const loadData = async () => {
523 527
           const item = luggageDataList.find(d => d.brigadeName === brigade)
524 528
           return item ? item.t2CargoLuggageCount : 0
525 529
         }),
526
-        itemStyle: { color: '#ec4899' }
530
+        itemStyle: { color: '#ec4899' },
531
+        label: { show: true, position: 'top', fontSize: 10, color: '#ec4899' }
527 532
       }
528 533
     ]
529 534
     
@@ -540,7 +545,8 @@ const loadData = async () => {
540 545
         itemStyle: { color: '#3b82f6' },
541 546
         lineStyle: { width: 3 },
542 547
         symbol: 'circle',
543
-        symbolSize: 8
548
+        symbolSize: 8,
549
+        label: { show: true, position: 'top', fontSize: 10, color: '#3b82f6' }
544 550
       },
545 551
       {
546 552
         name: 'T2旅检查堵数',
@@ -553,7 +559,8 @@ const loadData = async () => {
553 559
         itemStyle: { color: '#22c55e' },
554 560
         lineStyle: { width: 3 },
555 561
         symbol: 'circle',
556
-        symbolSize: 8
562
+        symbolSize: 8,
563
+        label: { show: true, position: 'top', fontSize: 10, color: '#22c55e' }
557 564
       },
558 565
       {
559 566
         name: 'T1行检查堵数',
@@ -566,7 +573,8 @@ const loadData = async () => {
566 573
         itemStyle: { color: '#f97316' },
567 574
         lineStyle: { width: 3 },
568 575
         symbol: 'circle',
569
-        symbolSize: 8
576
+        symbolSize: 8,
577
+        label: { show: true, position: 'top', fontSize: 10, color: '#f97316' }
570 578
       },
571 579
       {
572 580
         name: 'T2行检查堵数',
@@ -579,7 +587,8 @@ const loadData = async () => {
579 587
         itemStyle: { color: '#ec4899' },
580 588
         lineStyle: { width: 3 },
581 589
         symbol: 'circle',
582
-        symbolSize: 8
590
+        symbolSize: 8,
591
+        label: { show: true, position: 'top', fontSize: 10, color: '#ec4899' }
583 592
       }
584 593
     ]
585 594
     
@@ -602,23 +611,25 @@ const loadData = async () => {
602 611
         }
603 612
       },
604 613
       legend: {
614
+       
605 615
         data: [
606 616
           'T1旅检行李数', 'T2旅检行李数', 'T1行检行李数', 'T2行检行李数',
607 617
           'T1旅检查堵数', 'T2旅检查堵数', 'T1行检查堵数', 'T2行检查堵数'
608 618
         ]
609 619
       },
610 620
       grid: {
621
+       top: '80',
611 622
         left: '3%',
612 623
         right: '4%',
613
-        bottom: '3%',
624
+         bottom: '1%',
614 625
         containLabel: true
615 626
       },
616 627
       xAxis: {
617 628
         type: 'category',
618 629
         data: brigadeNames,
619
-        axisLabel: {
620
-          rotate: 45
621
-        }
630
+        // axisLabel: {
631
+        //   rotate: 45
632
+        // }
622 633
       },
623 634
       yAxis: [
624 635
         {
@@ -674,13 +685,13 @@ const loadData = async () => {
674 685
         const found = groupedData[type].find(d => d.missCheckItem === item)
675 686
         return found ? found.count : 0
676 687
       })
688
+      const color = ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6'][index % 6]
677 689
       return {
678 690
         name: type,
679 691
         type: 'bar',
680 692
         data: data,
681
-        itemStyle: {
682
-          color: ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6'][index % 6]
683
-        }
693
+        itemStyle: { color: color },
694
+        label: { show: true, position: 'top', fontSize: 10, color: color }
684 695
       }
685 696
     })
686 697
     
@@ -785,23 +796,19 @@ const loadData = async () => {
785 796
           type: 'bar',
786 797
           yAxisIndex: 0,
787 798
           data: timePeriodData.map(d => d.totalLuggageCount),
788
-          itemStyle: {
789
-            color: '#3b82f6'
790
-          }
799
+          itemStyle: { color: '#3b82f6' },
800
+          label: { show: true, position: 'top', fontSize: 10, color: '#3b82f6' }
791 801
         },
792 802
         {
793 803
           name: '查堵万分率',
794 804
           type: 'line',
795 805
           yAxisIndex: 1,
796 806
           data: timePeriodData.map(d => d.blockRate),
797
-          itemStyle: {
798
-            color: '#ec4899'
799
-          },
800
-          lineStyle: {
801
-            width: 3
802
-          },
807
+          itemStyle: { color: '#ec4899' },
808
+          lineStyle: { width: 3 },
803 809
           symbol: 'circle',
804
-          symbolSize: 8
810
+          symbolSize: 8,
811
+          label: { show: true, position: 'top', fontSize: 10, color: '#ec4899' }
805 812
         }
806 813
       ]
807 814
     })
@@ -878,23 +885,19 @@ const loadData = async () => {
878 885
           type: 'bar',
879 886
           yAxisIndex: 0,
880 887
           data: dailyLuggageData.map(d => d.totalLuggageCount),
881
-          itemStyle: {
882
-            color: '#3b82f6'
883
-          }
888
+          itemStyle: { color: '#3b82f6' },
889
+          label: { show: true, position: 'top', fontSize: 10, color: '#3b82f6' }
884 890
         },
885 891
         {
886 892
           name: '查堵万分率',
887 893
           type: 'line',
888 894
           yAxisIndex: 1,
889 895
           data: dailyLuggageData.map(d => d.blockRate),
890
-          itemStyle: {
891
-            color: '#ec4899'
892
-          },
893
-          lineStyle: {
894
-            width: 3
895
-          },
896
+          itemStyle: { color: '#ec4899' },
897
+          lineStyle: { width: 3 },
896 898
           symbol: 'circle',
897
-          symbolSize: 8
899
+          symbolSize: 8,
900
+          label: { show: true, position: 'top', fontSize: 10, color: '#ec4899' }
898 901
         }
899 902
       ]
900 903
     })
@@ -935,14 +938,11 @@ const loadData = async () => {
935 938
           name: 'AI复查图像数',
936 939
           type: 'line',
937 940
           data: aiImageData.map(d => d.totalBlockedCount),
938
-          itemStyle: {
939
-            color: '#14b8a6'
940
-          },
941
-          lineStyle: {
942
-            width: 3
943
-          },
941
+          itemStyle: { color: '#14b8a6' },
942
+          lineStyle: { width: 3 },
944 943
           symbol: 'circle',
945 944
           symbolSize: 8,
945
+          label: { show: true, position: 'top', fontSize: 10, color: '#14b8a6' },
946 946
           areaStyle: {
947 947
             color: {
948 948
               type: 'linear',
@@ -1002,14 +1002,11 @@ const loadData = async () => {
1002 1002
           name: 'AI漏判图像数',
1003 1003
           type: 'line',
1004 1004
           data: aiMissData.map(d => d.totalBlockedCount),
1005
-          itemStyle: {
1006
-            color: '#ef4444'
1007
-          },
1008
-          lineStyle: {
1009
-            width: 3
1010
-          },
1005
+          itemStyle: { color: '#ef4444' },
1006
+          lineStyle: { width: 3 },
1011 1007
           symbol: 'circle',
1012 1008
           symbolSize: 8,
1009
+          label: { show: true, position: 'top', fontSize: 10, color: '#ef4444' },
1013 1010
           areaStyle: {
1014 1011
             color: {
1015 1012
               type: 'linear',
@@ -1059,15 +1056,13 @@ const loadData = async () => {
1059 1056
         {
1060 1057
           name: 'AI误判图像数',
1061 1058
           type: 'line',
1059
+
1062 1060
           data: aiErrorData.map(d => d.totalBlockedCount),
1063
-          itemStyle: {
1064
-            color: '#f97316'
1065
-          },
1066
-          lineStyle: {
1067
-            width: 3
1068
-          },
1061
+          itemStyle: { color: '#f97316' },
1062
+          lineStyle: { width: 3 },
1069 1063
           symbol: 'circle',
1070 1064
           symbolSize: 8,
1065
+          label: { show: true, position: 'top', fontSize: 10, color: '#f97316' },
1071 1066
           areaStyle: {
1072 1067
             color: {
1073 1068
               type: 'linear',
@@ -1094,6 +1089,10 @@ const loadData = async () => {
1094 1089
 
1095 1090
 // 折线图配置(总表)
1096 1091
 const lineChartOption = (data, color, title, xAxisData = []) => ({
1092
+  tooltip: {
1093
+    trigger: 'axis',
1094
+    axisPointer: { type: 'line' }
1095
+  },
1097 1096
   grid: {
1098 1097
     left: '10%',
1099 1098
     top: '15%',
@@ -1121,12 +1120,22 @@ const lineChartOption = (data, color, title, xAxisData = []) => ({
1121 1120
     symbolSize: 6,
1122 1121
     data: data,
1123 1122
     itemStyle: { color: color },
1124
-    lineStyle: { color: color }
1123
+    lineStyle: { color: color },
1124
+    label: {
1125
+      show: true,
1126
+      position: 'top',
1127
+      fontSize: 10,
1128
+      color: color
1129
+    }
1125 1130
   }]
1126 1131
 })
1127 1132
 
1128 1133
 // 多折线图配置(大队对比)
1129 1134
 const multiLineChartOption = (dataList, colors, legends, xAxisData = []) => ({
1135
+  tooltip: {
1136
+    trigger: 'axis',
1137
+    axisPointer: { type: 'line' }
1138
+  },
1130 1139
   grid: {
1131 1140
     left: '10%',
1132 1141
     top: '15%',
@@ -1159,7 +1168,13 @@ const multiLineChartOption = (dataList, colors, legends, xAxisData = []) => ({
1159 1168
     symbolSize: 6,
1160 1169
     data: data,
1161 1170
     itemStyle: { color: colors[index] },
1162
-    lineStyle: { color: colors[index] }
1171
+    lineStyle: { color: colors[index] },
1172
+    label: {
1173
+      show: true,
1174
+      position: 'top',
1175
+      fontSize: 10,
1176
+      color: colors[index]
1177
+    }
1163 1178
   }))
1164 1179
 })
1165 1180
 

+ 5 - 5
src/views/blockingData/blockingDataScreen/components/ModuleThree.vue

@@ -67,7 +67,7 @@ const loadData = async () => {
67 67
 
68 68
     // 查堵-人员自测漏检次数(总累积)
69 69
     const selfTestData = selfTestRes?.value?.data || []
70
-    
70
+
71 71
     if (selfTestData.length > 0) {
72 72
       setOption1(ringPieOption(
73 73
         selfTestData.map(item => ({
@@ -114,8 +114,8 @@ const ringPieOption = (data, colors, centerOffset = ['50%', '55%']) => ({
114 114
     data: data,
115 115
     label: {
116 116
       show: true,
117
-      formatter: '{b}\n{c}%',
118
-      fontSize: 11
117
+      formatter: '{b}\n{c} ({d}%)',
118
+      fontSize: 10
119 119
     },
120 120
     labelLine: {
121 121
       show: true,
@@ -135,7 +135,7 @@ onMounted(() => {
135 135
   height: 100%;
136 136
   display: flex;
137 137
   gap: 10px;
138
-  
138
+
139 139
 }
140 140
 
141 141
 .chart-card {
@@ -152,7 +152,7 @@ onMounted(() => {
152 152
 }
153 153
 
154 154
 .chart-title {
155
- font-size: 17px;
155
+  font-size: 17px;
156 156
   color: black;
157 157
   margin-bottom: 5px;
158 158
   text-align: left;

+ 8 - 18
src/views/blockingData/blockingDataScreen/components/ModuleTwo.vue

@@ -225,7 +225,8 @@ const loadData = async () => {
225 225
         itemStyle: {
226 226
           color: ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6'][tenureLevels.indexOf(level) % 5]
227 227
         },
228
-        barWidth: 15
228
+        barWidth: 15,
229
+        label: { show: true, position: 'top', fontSize: 9 }
229 230
       }))
230 231
 
231 232
       setOption6({
@@ -263,7 +264,8 @@ const loadData = async () => {
263 264
           itemStyle: {
264 265
             color: ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6'][index % 6]
265 266
           },
266
-          barWidth: 20
267
+          barWidth: 20,
268
+          label: { show: true, position: 'top', fontSize: 9 }
267 269
         }
268 270
       })
269 271
 
@@ -321,7 +323,7 @@ const pieOption = (data, colors, isRing = false) => ({
321 323
     data: data,
322 324
     label: {
323 325
       show: true,
324
-      formatter: '{b}\n{c}%',
326
+      formatter: '{b}\n{c} ({d}%)',
325 327
       fontSize: 10
326 328
     }
327 329
   }]
@@ -337,24 +339,12 @@ const barOption = (data, colors) => ({
337 339
     itemStyle: {
338 340
       color: (params) => colors[params.dataIndex % colors.length]
339 341
     },
340
-    barWidth: 15
342
+    barWidth: 15,
343
+    label: { show: true, position: 'right', fontSize: 10 }
341 344
   }]
342 345
 })
343 346
 
344
-const stackBarOption = (data, colors) => ({
345
-  grid: { left: '15%', top: '10%', right: '5%', bottom: '15%', containLabel: true },
346
-  xAxis: { type: 'category', data: ['安检二大队', '安检三大队', '安检一大队'], axisLabel: { fontSize: 9 } },
347
-  yAxis: { type: 'value', axisLabel: { fontSize: 9 } },
348
-  legend: { top: 0, right: 10, textStyle: { fontSize: 9 } },
349
-  series: data.map((d, i) => ({
350
-    name: d.name,
351
-    type: 'bar',
352
-    stack: 'total',
353
-    data: d.data,
354
-    itemStyle: { color: colors[i] },
355
-    barWidth: 25
356
-  }))
357
-})
347
+
358 348
 
359 349
 onMounted(() => {
360 350
   loadData()

+ 33 - 10
src/views/blockingData/blockingDataScreen/components/RankList.vue

@@ -6,12 +6,12 @@
6 6
       <span class="header-count">{{ headerCount }}</span>
7 7
     </div>
8 8
     <div class="rank-list-body">
9
-      <div v-for="(item, index) in rankData" :key="index" class="rank-item" :class="getItemClass(index)">
10
-        <span class="rank-number" :class="'rank-number-' + getRankClass(index)">NO.{{ index + 1 }}</span>
9
+      <div v-for="(item, index) in rankedData" :key="index" class="rank-item" :class="getItemClass(item.rank, index)">
10
+        <span class="rank-number" :class="'rank-number-' + getRankClass(item.rank)">NO.{{ item.rank }}</span>
11 11
         <div class="rank-info">
12 12
           <span class="rank-name">{{ item.name }}</span>
13 13
           <div class="rank-bar">
14
-            <div class="rank-bar-fill" :class="'rank-bar-fill-' + getRankClass(index)" :style="{ width: getProgressWidth(item.value, maxValue) + '%' }"></div>
14
+            <div class="rank-bar-fill" :class="'rank-bar-fill-' + getRankClass(item.rank)" :style="{ width: getProgressWidth(item.value, maxValue) + '%' }"></div>
15 15
           </div>
16 16
         </div>
17 17
         <span class="rank-count">{{ item.value }}</span>
@@ -51,10 +51,33 @@ const maxValue = computed(() => {
51 51
   return Math.max(...props.rankData.map(item => item.value))
52 52
 })
53 53
 
54
-const getRankClass = (index) => {
55
-  if (index === 0) return 'first'
56
-  if (index === 1) return 'second'
57
-  if (index === 2) return 'third'
54
+// 计算并列排名(中国式排名:并列后下一个排名+1)
55
+const rankedData = computed(() => {
56
+  if (props.rankData.length === 0) return []
57
+  
58
+  // 按value降序排序
59
+  const sorted = [...props.rankData].sort((a, b) => b.value - a.value)
60
+  
61
+  let currentRank = 1
62
+  let previousValue = null
63
+  
64
+  return sorted.map((item) => {
65
+    // 如果当前value小于前一个value,排名+1
66
+    if (previousValue !== null && item.value < previousValue) {
67
+      currentRank++
68
+    }
69
+    previousValue = item.value
70
+    return {
71
+      ...item,
72
+      rank: currentRank
73
+    }
74
+  })
75
+})
76
+
77
+const getRankClass = (rank) => {
78
+  if (rank === 1) return 'first'
79
+  if (rank === 2) return 'second'
80
+  if (rank === 3) return 'third'
58 81
   return 'default'
59 82
 }
60 83
 
@@ -62,9 +85,9 @@ const getProgressWidth = (value, max) => {
62 85
   return (value / max) * 100
63 86
 }
64 87
 
65
-const getItemClass = (index) => {
66
-  const baseClass = 'rank-item rank-item-' + getRankClass(index)
67
-  if (index < 3) return baseClass
88
+const getItemClass = (rank, index) => {
89
+  const baseClass = 'rank-item rank-item-' + getRankClass(rank)
90
+  if (rank <= 3) return baseClass
68 91
   const isGray = (index - 3) % 2 === 0
69 92
   return baseClass + (isGray ? ' rank-item-gray' : '')
70 93
 }