Просмотр исходного кода

Merge branch 'blockingData' into dev

# Conflicts:
#	src/hooks/chart.js
huoyi 1 месяц назад
Родитель
Сommit
9493a4cb4f

+ 24 - 0
src/api/blockingData/blockingDataScreen.js

@@ -204,6 +204,14 @@ export function certificateDistribution(query) {
204 204
         params: query
205 205
     })
206 206
 }
207
+//查询开机人员年限分布列表全量
208
+export function tenureListAll(query) {
209
+    return request({
210
+        url: '/blocked/tenure/listAll',
211
+        method: 'get',
212
+        params: query
213
+    })
214
+}
207 215
 //大队开机人员证书分布
208 216
 export function brigadeCertificateDistribution(query) {
209 217
     return request({
@@ -244,6 +252,22 @@ export function outsideRateStats(query) {
244 252
         params: query
245 253
     })
246 254
 }
255
+//月考成绩统计
256
+export function monthlyAssessment(query) {
257
+    return request({
258
+        url: '/blocked/missReview/screen/monthlyAssessment',
259
+        method: 'get',
260
+        params: query
261
+    })
262
+}
263
+//本月自测有无漏检统计
264
+export function selfTestHasMissCheck(query) {
265
+    return request({
266
+        url: '/blocked/missReview/screen/selfTestHasMissCheck',
267
+        method: 'get',
268
+        params: query
269
+    })
270
+}
247 271
 //查询两楼每日查堵走势
248 272
 export function dailyTerminalTrend(query) {
249 273
     return request({

+ 180 - 50
src/views/blockingData/blockingDataScreen/components/ModuleBrigadeOne.vue

@@ -6,7 +6,7 @@
6 6
           <div class="stat-card-inner">
7 7
             <div class="stat-left">
8 8
               <div class="stat-label">查堵总数</div>
9
-              <div class="stat-value">819</div>
9
+              <div class="stat-value">{{ statValue || 0 }}</div>
10 10
               <div class="stat-unit">起</div>
11 11
             </div>
12 12
           </div>
@@ -47,12 +47,20 @@
47 47
 </template>
48 48
 
49 49
 <script setup>
50
-import { ref, onMounted } from 'vue'
50
+import { ref, onMounted, watch } from 'vue'
51 51
 import * as echarts from 'echarts'
52 52
 import ModuleContainer from './ModuleContainer.vue'
53 53
 import { useEcharts } from '@/hooks/chart.js'
54
+import { formatDateHy } from '@/utils/index.js'
55
+import {
56
+  dailyTerminalTrend,
57
+  dailyTerminalLuggageTrend,
58
+  terminalBlockedStats,
59
+  timePeriodLuggageStats,
60
+  dailyTerminalRateTrend,
61
+  brigadeItemDistribution
62
+} from '@/api/blockingData/blockingDataScreen'
54 63
 
55
-// 接收筛选参数
56 64
 const props = defineProps({
57 65
   filterParams: {
58 66
     type: Object,
@@ -60,6 +68,27 @@ const props = defineProps({
60 68
   }
61 69
 })
62 70
 
71
+const loading = ref(false)
72
+const statValue = ref(0)
73
+
74
+const processFilterParams = (params) => {
75
+  const { dateRange, ...rest } = params
76
+  const processed = { ...rest }
77
+
78
+  if (dateRange && Array.isArray(dateRange) && dateRange.length === 2) {
79
+    processed.startTime = formatDateHy(dateRange[0])
80
+    processed.endTime = formatDateHy(dateRange[1])
81
+  }
82
+  if (processed.brigadeId == 'all') {
83
+    delete processed.brigadeId
84
+  }
85
+  if (processed.terminalId == 'all') {
86
+    delete processed.terminalId
87
+  }
88
+
89
+  return processed
90
+}
91
+
63 92
 const chart1 = ref(null)
64 93
 const chart2 = ref(null)
65 94
 const chart3 = ref(null)
@@ -74,6 +103,97 @@ const { setOption: setOption4 } = useEcharts(chart4)
74 103
 const { setOption: setOption5 } = useEcharts(chart5)
75 104
 const { setOption: setOption6 } = useEcharts(chart6)
76 105
 
106
+const fetchData = async () => {
107
+  loading.value = true
108
+  try {
109
+    const processedParams = processFilterParams(props.filterParams)
110
+    const [trendRes, luggageRes, blockedRes, timeRes, rateRes, itemRes] = await Promise.allSettled([
111
+      dailyTerminalTrend(processedParams),
112
+      dailyTerminalLuggageTrend(processedParams),
113
+      terminalBlockedStats(processedParams),
114
+      timePeriodLuggageStats(processedParams),
115
+      dailyTerminalRateTrend(processedParams),
116
+      brigadeItemDistribution(processedParams)
117
+    ])
118
+
119
+    if (trendRes.value?.data) {
120
+      const dates = trendRes.value.data.map(item => item.statDate?.split('T')[0] || item.date || item.name || '')
121
+      const t1TravelData = trendRes.value.data.map(item => item.t1TravelBlockedCount || 0)
122
+      const t2TravelData = trendRes.value.data.map(item => item.t2TravelBlockedCount || 0)
123
+      const t1WalkData = trendRes.value.data.map(item => item.t1WalkBlockedCount || 0)
124
+      const t2WalkData = trendRes.value.data.map(item => item.t2WalkBlockedCount || 0)
125
+      setOption1(multiLineChartOption(dates, [
126
+        { name: 'T1旅检查堵件数', data: t1TravelData, color: '#3b82f6' },
127
+        { name: 'T2旅检查堵件数', data: t2TravelData, color: '#22c55e' },
128
+        { name: 'T1行检查堵件数', data: t1WalkData, color: '#f97316' },
129
+        { name: 'T2行检查堵件数', data: t2WalkData, color: '#ec4899' }
130
+      ]))
131
+    }
132
+
133
+    if (luggageRes.value?.data) {
134
+      const dates = luggageRes.value.data.map(item => item.statDate?.split('T')[0] || item.date || item.name || '')
135
+      const t1TravelData = luggageRes.value.data.map(item => item.t1TravelBagCount || 0)
136
+      const t2TravelData = luggageRes.value.data.map(item => item.t2TravelBagCount || 0)
137
+      const t1WalkData = luggageRes.value.data.map(item => item.t1WalkBagCount || 0)
138
+      const t2WalkData = luggageRes.value.data.map(item => item.t2WalkBagCount || 0)
139
+      setOption2(multiLineChartOption(dates, [
140
+        { name: 'T1旅检过检行李数', data: t1TravelData, color: '#3b82f6' },
141
+        { name: 'T2旅检过检行李数', data: t2TravelData, color: '#22c55e' },
142
+        { name: 'T1行检过检行李数', data: t1WalkData, color: '#f97316' },
143
+        { name: 'T2行检过检行李数', data: t2WalkData, color: '#ec4899' }
144
+      ]))
145
+    }
146
+
147
+    if (blockedRes.value?.data) {
148
+      const blockedData = [
149
+        { name: 'T1', value: blockedRes.value.data.t1BlockedCount || 0 },
150
+        { name: 'T2', value: blockedRes.value.data.t2BlockedCount || 0 }
151
+      ]
152
+      setOption3(horizontalBarChartOption(blockedData, '#3b82f6'))
153
+      const total = blockedRes.value.data.totalBlockedCount || 0
154
+      statValue.value = total
155
+    }
156
+
157
+    if (timeRes.value?.data) {
158
+      const timePeriods = timeRes.value.data.map(item => item.timePeriod || '')
159
+      const avgLuggageData = timeRes.value.data.map(item => item.avgLuggageCount || 0)
160
+      const avgBlockedData = timeRes.value.data.map(item => item.avgBlockedCount || 0)
161
+      setOption4(barLineChartOption(timePeriods, avgLuggageData, avgBlockedData))
162
+    }
163
+
164
+    if (rateRes.value?.data) {
165
+      const dates = rateRes.value.data.map(item => item.statDate?.split('T')[0] || item.date || item.name || '')
166
+      const t1TravelRate = rateRes.value.data.map(item => item.t1TravelBlockRate || 0)
167
+      const t2TravelRate = rateRes.value.data.map(item => item.t2TravelBlockRate || 0)
168
+      const t1WalkRate = rateRes.value.data.map(item => item.t1WalkBlockRate || 0)
169
+      const t2WalkRate = rateRes.value.data.map(item => item.t2WalkBlockRate || 0)
170
+      setOption5(multiLineChartOption(dates, [
171
+        { name: 'T1旅检万分率', data: t1TravelRate, color: '#3b82f6' },
172
+        { name: 'T2旅检万分率', data: t2TravelRate, color: '#22c55e' },
173
+        { name: 'T1行检万分率', data: t1WalkRate, color: '#f97316' },
174
+        { name: 'T2行检万分率', data: t2WalkRate, color: '#ec4899' }
175
+      ]))
176
+    }
177
+
178
+    if (itemRes.value?.data) {
179
+      const colors = ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#10b981', '#f59e0b', '#ef4444']
180
+      const itemData = itemRes.value.data.map((item, index) => ({
181
+        name: item.missCheckItem ,
182
+        value: item.count 
183
+      }))
184
+      setOption6(pieChartOption(itemData, colors))
185
+    }
186
+  } catch (error) {
187
+    console.error('获取数据失败:', error)
188
+  } finally {
189
+    loading.value = false
190
+  }
191
+}
192
+
193
+watch(() => props.filterParams, () => {
194
+  fetchData()
195
+}, { deep: true })
196
+
77 197
 const xAxisData = Array.from({ length: 30 }, (_, i) => `${i + 1}日`)
78 198
 
79 199
 const generateData = (count, min, max) => {
@@ -84,26 +204,28 @@ const generateData = (count, min, max) => {
84 204
   return data
85 205
 }
86 206
 
87
-const lineChartOption = (data, color, title) => ({
207
+const multiLineChartOption = (xAxisData, series) => ({
88 208
   grid: { left: '10%', top: '15%', right: '5%', bottom: '15%', containLabel: true },
89 209
   xAxis: { type: 'category', data: xAxisData, axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' } },
90 210
   yAxis: { type: 'value', axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' }, splitLine: { lineStyle: { color: '#eee' } } },
91
-  series: [{ name: title, type: 'line', smooth: true, symbol: 'circle', symbolSize: 6, data: data, itemStyle: { color: color }, lineStyle: { color: color } }]
211
+  legend: { 
212
+    data: series.map(s => s.name),
213
+    top: '0%',
214
+    textStyle: { fontSize: 10 }
215
+  },
216
+  series: series.map(s => ({
217
+    name: s.name,
218
+    type: 'line',
219
+    smooth: true,
220
+    symbol: 'circle',
221
+    symbolSize: 6,
222
+    data: s.data,
223
+    itemStyle: { color: s.color },
224
+    lineStyle: { color: s.color }
225
+  }))
92 226
 })
93 227
 
94
-const areaChartOption = (data, color) => ({
95
-  grid: { left: '10%', top: '15%', right: '5%', bottom: '15%', containLabel: true },
96
-  xAxis: { type: 'category', data: xAxisData, axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' } },
97
-  yAxis: { type: 'value', axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' }, splitLine: { lineStyle: { color: '#eee' } } },
98
-  series: [{ type: 'line', smooth: true, symbol: 'circle', symbolSize: 6, data: data, itemStyle: { color: color }, lineStyle: { color: color }, areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: color + '40' }, { offset: 1, color: color + '10' }]) } }]
99
-})
100 228
 
101
-const barChartOption = (data, color) => ({
102
-  grid: { left: '15%', top: '15%', right: '5%', bottom: '15%', containLabel: true },
103
-  xAxis: { type: 'category', data: data.map(d => d.name), axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' } },
104
-  yAxis: { type: 'value', axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' }, splitLine: { lineStyle: { color: '#eee' } } },
105
-  series: [{ type: 'bar', data: data.map(d => d.value), itemStyle: { color: color }, barWidth: 20 }]
106
-})
107 229
 
108 230
 const horizontalBarChartOption = (data, color) => ({
109 231
   grid: { left: '20%', top: '15%', right: '5%', bottom: '15%', containLabel: true },
@@ -112,46 +234,55 @@ const horizontalBarChartOption = (data, color) => ({
112 234
   series: [{ type: 'bar', data: data.map(d => d.value), itemStyle: { color: color }, barWidth: 15 }]
113 235
 })
114 236
 
237
+const barLineChartOption = (xAxisData, barData, lineData) => ({
238
+  grid: { left: '10%', top: '15%', right: '5%', bottom: '15%', containLabel: true },
239
+  xAxis: { type: 'category', data: xAxisData, axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' } },
240
+  yAxis: { type: 'value', axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' }, splitLine: { lineStyle: { color: '#eee' } } },
241
+  legend: { 
242
+    data: ['平均过检行李数', '平均查堵件数'],
243
+    top: '0%',
244
+    textStyle: { fontSize: 10 }
245
+  },
246
+  series: [
247
+    {
248
+      name: '平均过检行李数',
249
+      type: 'bar',
250
+      data: barData,
251
+      itemStyle: { color: '#3b82f6' },
252
+      barWidth: 20
253
+    },
254
+    {
255
+      name: '平均查堵件数',
256
+      type: 'line',
257
+      smooth: true,
258
+      symbol: 'circle',
259
+      symbolSize: 6,
260
+      data: lineData,
261
+      itemStyle: { color: '#ec4899' },
262
+      lineStyle: { color: '#ec4899' }
263
+    }
264
+  ]
265
+})
266
+
115 267
 const pieChartOption = (data, colors) => ({
116 268
   color: colors,
269
+  legend: { 
270
+    show: true,
271
+    textStyle: { fontSize: 10 }
272
+  },
273
+  tooltip: { trigger: 'item' },
117 274
   series: [{
118 275
     type: 'pie',
119 276
     radius: '65%',
120 277
     center: ['50%', '55%'],
121 278
     data: data,
122
-    label: { show: true, formatter: '{b}\n{c}%', fontSize: 10 }
279
+    label: { show: true, formatter: '{b}', fontSize: 10 }
123 280
   }]
124 281
 })
125 282
 
126 283
 onMounted(() => {
127
-  setOption1(lineChartOption(generateData(30, 5, 20), '#3b82f6', '查堵数量'))
128
-  setOption2(areaChartOption(generateData(30, 10000, 30000), '#22c55e'))
129
-  setOption3(horizontalBarChartOption([{ name: 'T1', value: 388 }, { name: 'T2', value: 431 }], '#3b82f6'))
130
-  
131
-  const timeData = [
132
-    { name: '02:00-04:00', value: 20 },
133
-    { name: '04:00-06:00', value: 45 },
134
-    { name: '06:00-08:00', value: 80 },
135
-    { name: '08:00-10:00', value: 110 },
136
-    { name: '10:00-12:00', value: 95 },
137
-    { name: '12:00-14:00', value: 85 },
138
-    { name: '14:00-16:00', value: 100 },
139
-    { name: '16:00-18:00', value: 120 },
140
-    { name: '18:00-20:00', value: 115 },
141
-    { name: '20:00-22:00', value: 80 },
142
-    { name: '22:00-24:00', value: 45 }
143
-  ]
144
-  setOption4(barChartOption(timeData, '#3b82f6'))
284
+  fetchData()
145 285
   
146
-  setOption5(lineChartOption(generateData(30, 0.5, 3.0), '#ec4899', '查堵万分率'))
147
-  
148
-  setOption6(pieChartOption([
149
-    { name: '水果刀', value: 1.02 },
150
-    { name: '警棍', value: 0.37 },
151
-    { name: '甩棍', value: 0.52 },
152
-    { name: '打火机', value: 50.52 },
153
-    { name: '其他', value: 47.57 }
154
-  ], ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6']))
155 286
 })
156 287
 </script>
157 288
 
@@ -236,20 +367,19 @@ onMounted(() => {
236 367
   flex: 1;
237 368
   background: #fff;
238 369
   border-radius: 6px;
239
-  padding: 10px;
370
+  padding: 15px;
240 371
   border: 1px solid #eee;
241 372
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
242 373
   display: flex;
243 374
   flex-direction: column;
244
-  min-height: 200px;
375
+  min-height: 280px;
245 376
 }
246 377
 
247 378
 .chart-title {
248
-  font-size: 14px;
249
-  color: #333;
379
+ font-size: 17px;
380
+  color: black;
250 381
   margin-bottom: 5px;
251 382
   text-align: left;
252
-  font-weight: 500;
253 383
 }
254 384
 
255 385
 .echarts {

+ 83 - 19
src/views/blockingData/blockingDataScreen/components/ModuleBrigadeThree.vue

@@ -16,12 +16,16 @@
16 16
 </template>
17 17
 
18 18
 <script setup>
19
-import { ref, onMounted } from 'vue'
19
+import { ref, onMounted, watch } from 'vue'
20 20
 import * as echarts from 'echarts'
21 21
 import ModuleContainer from './ModuleContainer.vue'
22 22
 import { useEcharts } from '@/hooks/chart.js'
23
+import { formatDateHy } from '@/utils/index.js'
24
+import {
25
+  selfTestHasMissCheck,
26
+  monthlyAssessment
27
+} from '@/api/blockingData/blockingDataScreen'
23 28
 
24
-// 接收筛选参数
25 29
 const props = defineProps({
26 30
   filterParams: {
27 31
     type: Object,
@@ -29,36 +33,97 @@ const props = defineProps({
29 33
   }
30 34
 })
31 35
 
36
+const loading = ref(false)
37
+
38
+const processFilterParams = (params) => {
39
+  const { dateRange, ...rest } = params
40
+  const processed = { ...rest }
41
+
42
+  if (dateRange && Array.isArray(dateRange) && dateRange.length === 2) {
43
+    processed.startTime = formatDateHy(dateRange[0])
44
+    processed.endTime = formatDateHy(dateRange[1])
45
+  }
46
+  if (processed.brigadeId == 'all') {
47
+    delete processed.brigadeId
48
+  }
49
+  if (processed.terminalId == 'all') {
50
+    delete processed.terminalId
51
+  }
52
+
53
+  return processed
54
+}
55
+
32 56
 const chart1 = ref(null)
33 57
 const chart2 = ref(null)
34 58
 
35 59
 const { setOption: setOption1 } = useEcharts(chart1)
36 60
 const { setOption: setOption2 } = useEcharts(chart2)
37 61
 
62
+const fetchData = async () => {
63
+  loading.value = true
64
+  try {
65
+    const processedParams = processFilterParams(props.filterParams)
66
+    const [missCheckRes, assessmentRes] = await Promise.all([
67
+      selfTestHasMissCheck(processedParams),
68
+      monthlyAssessment(processedParams)
69
+    ])
70
+
71
+    if (missCheckRes.data) {
72
+      const colors = ['#22c55e', '#3b82f6', '#f97316']
73
+      const missCheckData = missCheckRes.data.map(item => ({
74
+        name: item.name || item.level || '未知',
75
+        value: item.total || item.count || 0
76
+      }))
77
+      setOption1(ringChartOption(missCheckData, colors))
78
+    }
79
+
80
+    if (assessmentRes.data) {
81
+      const colors = ['#22c55e', '#3b82f6', '#fbbf24', '#ef4444']
82
+      const assessmentData = assessmentRes.data.map(item => ({
83
+        name: item.level || item.name || '',
84
+        value: item.total || 0
85
+      }))
86
+      setOption2(ringChartOption(assessmentData, colors))
87
+    }
88
+  } catch (error) {
89
+    console.error('获取数据失败:', error)
90
+  } finally {
91
+    loading.value = false
92
+  }
93
+}
94
+
95
+watch(() => props.filterParams, () => {
96
+  fetchData()
97
+}, { deep: true })
98
+
38 99
 const ringChartOption = (data, colors) => ({
39 100
   color: colors,
101
+  tooltip: { trigger: 'item', formatter: '{b}: {c}' },
102
+  legend: {
103
+    data: data.map(item => item.name),
104
+    top: '0%',
105
+    textStyle: { fontSize: 10 }
106
+  },
40 107
   series: [{
41 108
     type: 'pie',
42 109
     radius: ['40%', '65%'],
43 110
     center: ['50%', '55%'],
44 111
     data: data,
45
-    label: { show: true, formatter: '{b}\n{c}%', fontSize: 10 }
112
+    label: {
113
+      show: true,
114
+      formatter: (params) => {
115
+        const total = data.reduce((sum, item) => sum + item.value, 0)
116
+        const percentage = ((params.value / total) * 100).toFixed(2)
117
+        return `${params.name}\n${params.value}(${percentage}%)`
118
+      },
119
+      fontSize: 10
120
+    }
46 121
   }]
47 122
 })
48 123
 
49 124
 onMounted(() => {
50
-  setOption1(ringChartOption([
51
-    { name: '漏检次数≤3次', value: 65.2 },
52
-    { name: '漏检次数4-6次', value: 22.8 },
53
-    { name: '漏检次数≥7次', value: 12.0 }
54
-  ], ['#22c55e', '#3b82f6', '#f97316']))
55
-
56
-  setOption2(ringChartOption([
57
-    { name: '优秀(90-100分)', value: 35.4 },
58
-    { name: '良好(80-89分)', value: 42.1 },
59
-    { name: '合格(70-79分)', value: 18.3 },
60
-    { name: '不合格(<70分)', value: 4.2 }
61
-  ], ['#22c55e', '#3b82f6', '#fbbf24', '#ef4444']))
125
+  fetchData()
126
+
62 127
 })
63 128
 </script>
64 129
 
@@ -86,15 +151,14 @@ onMounted(() => {
86 151
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
87 152
   display: flex;
88 153
   flex-direction: column;
89
-  min-height: 200px;
154
+  min-height: 280px;
90 155
 }
91 156
 
92 157
 .chart-title {
93
-  font-size: 14px;
94
-  color: #333;
158
+  font-size: 17px;
159
+  color: black;
95 160
   margin-bottom: 5px;
96 161
   text-align: left;
97
-  font-weight: 500;
98 162
 }
99 163
 
100 164
 .echarts {

+ 283 - 119
src/views/blockingData/blockingDataScreen/components/ModuleBrigadeTwo.vue

@@ -15,7 +15,7 @@
15 15
           <rank-list :rank-data="rankData3" title="查堵-员工排行榜" header-label="排名" header-name="被回查人" header-count="计数" />
16 16
         </div>
17 17
       </div>
18
-      
18
+
19 19
       <div class="chart-row">
20 20
         <div class="chart-item">
21 21
           <div class="chart-title">查堵男女比例</div>
@@ -30,7 +30,7 @@
30 30
           <div ref="chart6" class="echarts"></div>
31 31
         </div>
32 32
       </div>
33
-      
33
+
34 34
       <div class="chart-row">
35 35
         <div class="chart-item">
36 36
           <div class="chart-title">查堵物品位置</div>
@@ -39,19 +39,19 @@
39 39
         <div class="chart-item">
40 40
           <div class="chart-title">查堵-困难图像数</div>
41 41
           <div class="number-display">
42
-            <div class="number-value">819</div>
42
+            <div class="number-value">{{ difficultValue || 0 }}</div>
43 43
             <div class="number-unit">幅</div>
44 44
           </div>
45 45
         </div>
46 46
         <div class="chart-item">
47 47
           <div class="chart-title">查堵-简单图像数</div>
48 48
           <div class="number-display">
49
-            <div class="number-value">819</div>
49
+            <div class="number-value">{{ simpleValue || 0 }}</div>
50 50
             <div class="number-unit">幅</div>
51 51
           </div>
52 52
         </div>
53 53
       </div>
54
-      
54
+
55 55
       <div class="chart-row">
56 56
         <div class="chart-item">
57 57
           <div class="chart-title">查堵-主管分管次数</div>
@@ -62,7 +62,7 @@
62 62
           <div ref="chart11" class="echarts"></div>
63 63
         </div>
64 64
       </div>
65
-      
65
+
66 66
       <div class="chart-row">
67 67
         <div class="chart-item">
68 68
           <div class="chart-title">查堵人员开机年限分布</div>
@@ -78,13 +78,27 @@
78 78
 </template>
79 79
 
80 80
 <script setup>
81
-import { ref, onMounted, reactive } from 'vue'
81
+import { ref, onMounted, reactive, watch } from 'vue'
82 82
 import * as echarts from 'echarts'
83 83
 import ModuleContainer from './ModuleContainer.vue'
84 84
 import RankList from './RankList.vue'
85 85
 import { useEcharts } from '@/hooks/chart.js'
86
+import { formatDateHy } from '@/utils/index.js'
87
+import {
88
+  brigadeSupervisorRanking,
89
+  brigadeTeamLeaderRanking,
90
+  brigadeReviewedUserRanking,
91
+  brigadeGenderDistribution,
92
+  brigadeCertificateDistributionDetail,
93
+  personnelCertificateBase,
94
+  brigadeItemLocationDistribution,
95
+  brigadeDifficultyDistribution,
96
+  brigadeSupervisorDistribution,
97
+  brigadeMissCheckReasonDistribution,
98
+  brigadeOperatingYearsDistribution,
99
+  tenureListAll
100
+} from '@/api/blockingData/blockingDataScreen'
86 101
 
87
-// 接收筛选参数
88 102
 const props = defineProps({
89 103
   filterParams: {
90 104
     type: Object,
@@ -92,6 +106,31 @@ const props = defineProps({
92 106
   }
93 107
 })
94 108
 
109
+const loading = ref(false)
110
+const rankData1 = reactive([])
111
+const rankData2 = reactive([])
112
+const rankData3 = reactive([])
113
+const difficultValue = ref(0)
114
+const simpleValue = ref(0)
115
+
116
+const processFilterParams = (params) => {
117
+  const { dateRange, ...rest } = params
118
+  const processed = { ...rest }
119
+
120
+  if (dateRange && Array.isArray(dateRange) && dateRange.length === 2) {
121
+    processed.startTime = formatDateHy(dateRange[0])
122
+    processed.endTime = formatDateHy(dateRange[1])
123
+  }
124
+  if (processed.brigadeId == 'all') {
125
+    delete processed.brigadeId
126
+  }
127
+  if (processed.terminalId == 'all') {
128
+    delete processed.terminalId
129
+  }
130
+
131
+  return processed
132
+}
133
+
95 134
 const chart4 = ref(null)
96 135
 const chart5 = ref(null)
97 136
 const chart6 = ref(null)
@@ -110,54 +149,224 @@ const { setOption: setOption11 } = useEcharts(chart11)
110 149
 const { setOption: setOption12 } = useEcharts(chart12)
111 150
 const { setOption: setOption13 } = useEcharts(chart13)
112 151
 
113
-const rankData1 = reactive([
114
-  { name: '李鑫锋', value: 102 },
115
-  { name: '李学玲', value: 94 },
116
-  { name: '郑杰', value: 80 },
117
-  { name: '郭仁吉', value: 69 },
118
-  { name: '计沐', value: 67 },
119
-  { name: '方园', value: 62 },
120
-  { name: '符鑫日', value: 59 }
121
-])
122
-
123
-const rankData2 = reactive([
124
-  { name: '李雪琦', value: 44 },
125
-  { name: '林瑞玉', value: 42 },
126
-  { name: '吴明文', value: 40 },
127
-  { name: '吕吕俊', value: 36 },
128
-  { name: '潘任', value: 31 },
129
-  { name: '吴亚琪', value: 30 }
130
-])
131
-
132
-const rankData3 = reactive([
133
-  { name: '梁其松', value: 18 },
134
-  { name: '王名顺', value: 18 },
135
-  { name: '陈艳丽', value: 16 },
136
-  { name: '刘振华', value: 16 },
137
-  { name: '黄建成', value: 15 },
138
-  { name: '简句学', value: 15 },
139
-  { name: '郭可奇', value: 14 }
140
-])
152
+const fetchData = async () => {
153
+  loading.value = true
154
+  try {
155
+    const processedParams = processFilterParams(props.filterParams)
156
+    const [supervisorRes, teamRes, userRes, genderRes, certDetailRes, certBaseRes, locationRes, difficultyRes, supervisorDistRes, reasonRes, yearsRes, tenureRes] = await Promise.allSettled([
157
+      brigadeSupervisorRanking(processedParams),
158
+      brigadeTeamLeaderRanking(processedParams),
159
+      brigadeReviewedUserRanking(processedParams),
160
+      brigadeGenderDistribution(processedParams),
161
+      brigadeCertificateDistributionDetail(processedParams),
162
+      personnelCertificateBase(processedParams),
163
+      brigadeItemLocationDistribution(processedParams),
164
+      brigadeDifficultyDistribution(processedParams),
165
+      brigadeSupervisorDistribution(processedParams),
166
+      brigadeMissCheckReasonDistribution(processedParams),
167
+      brigadeOperatingYearsDistribution(processedParams),
168
+      tenureListAll(processedParams)
169
+    ])
170
+
171
+    if (supervisorRes.value?.data) {
172
+      rankData1.length = 0
173
+      supervisorRes.value.data.slice(0, 10).forEach(item => {
174
+        rankData1.push({
175
+          name: item.supervisorName || item.name || '',
176
+          value: item.count || item.value || 0
177
+        })
178
+      })
179
+    }
180
+
181
+    if (teamRes.value?.data) {
182
+      rankData2.length = 0
183
+      teamRes.value.data.slice(0, 10).forEach(item => {
184
+        rankData2.push({
185
+          name: item.teamLeaderName || item.name || '',
186
+          value: item.count || item.value || 0
187
+        })
188
+      })
189
+    }
190
+
191
+    if (userRes.value?.data) {
192
+      rankData3.length = 0
193
+      userRes.value.data.slice(0, 10).forEach(item => {
194
+        rankData3.push({
195
+          name: item.userName || item.name || '',
196
+          value: item.count || item.value || 0
197
+        })
198
+      })
199
+    }
200
+
201
+    if (genderRes.value?.data) {
202
+      const genderData = genderRes.value.data.map(item => ({
203
+        name: item.gender || item.name || '',
204
+        value: item.percentage || item.value || 0
205
+      }))
206
+      setOption4(pieChartOption(genderData, ['#ec4899', '#3b82f6']))
207
+    }
208
+
209
+    if (certDetailRes.value?.data) {
210
+      const certData = certDetailRes.value.data.map(item => ({
211
+        name: item.certificateLevel || item.name || '',
212
+        value: item.percentage || item.value || 0
213
+      }))
214
+      setOption5(pieChartOption(certData, ['#22c55e', '#3b82f6']))
215
+    }
216
+
217
+    if (certBaseRes.value?.data) {
218
+      const baseData = certBaseRes.value.data.map(item => ({
219
+        name: item.certificateLevel || item.name || '',
220
+        value: item.percentage || item.value || 0
221
+      }))
222
+      setOption6(pieChartOption(baseData, ['#22c55e', '#3b82f6']))
223
+    }
224
+
225
+    if (locationRes.value?.data) {
226
+      const colors = ['#8b5cf6', '#ec4899', '#f97316', '#22c55e', '#3b82f6', '#14b8a6', '#ef4444', '#fbbf24', '#6366f1', '#06b6d4', '#84cc16', '#e11d48']
227
+      const locationData = locationRes.value.data.map(item => ({
228
+        name: item.itemLocation || item.name || '',
229
+        value: item.percentage || item.value || 0
230
+      }))
231
+      setOption7(donutChartOption(locationData, colors))
232
+    }
233
+
234
+    if (difficultyRes.value?.data) {
235
+      let difficult = 0
236
+      let simple = 0
237
+      difficultyRes.value.data.forEach(item => {
238
+        if (item.difficultyLevel === '难') {
239
+          difficult = item.count || item.value || 0
240
+        } else if (item.difficultyLevel === '简单') {
241
+          simple = item.count || item.value || 0
242
+        }
243
+      })
244
+      difficultValue.value = difficult
245
+      simpleValue.value = simple
246
+    }
247
+
248
+    if (supervisorDistRes.value?.data) {
249
+      const colors = ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6', '#ef4444', '#fbbf24', '#6366f1', '#06b6d4', '#84cc16', '#e11d48', '#a855f7', '#f59e0b']
250
+      const supervisorData = supervisorDistRes.value.data.map(item => ({
251
+        name: item.supervisorName || item.name || '',
252
+        value: item.percentage || item.value || 0
253
+      }))
254
+      setOption10(donutChartOption(supervisorData, colors))
255
+    }
256
+
257
+    if (reasonRes.value?.data) {
258
+      const colors = ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6', '#ef4444', '#fbbf24']
259
+      const reasonData = reasonRes.value.data.map(item => ({
260
+        name: item.missCheckReasonCategory || '',
261
+        value: item.count || 0
262
+      }))
263
+      setOption11(pieChartOption(reasonData, colors))
264
+    }
265
+
266
+    if (yearsRes.value?.data) {
267
+      const colors = ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6']
268
+      const yearsData = yearsRes.value.data.map(item => ({
269
+        name: item.operatingYears || '',
270
+        value: item.count || 0
271
+      }))
272
+      setOption12(pieChartOption(yearsData, colors))
273
+    }
274
+
275
+    if (tenureRes.value?.data) {
276
+      const xAxisData = tenureRes.value.data.map(item => item.brigadeName || '')
277
+      const seriesData = [
278
+        {
279
+          name: '5年及以上',
280
+          data: tenureRes.value.data.map(item => item.cntGe5Year || 0),
281
+          color: '#3b82f6'
282
+        },
283
+        {
284
+          name: '4年',
285
+          data: tenureRes.value.data.map(item => item.cnt4Years || 0),
286
+          color: '#22c55e'
287
+        },
288
+        {
289
+          name: '3年',
290
+          data: tenureRes.value.data.map(item => item.cnt3Years || 0),
291
+          color: '#f97316'
292
+        },
293
+        {
294
+          name: '2年',
295
+          data: tenureRes.value.data.map(item => item.cnt2Years || 0),
296
+          color: '#ec4899'
297
+        },
298
+        {
299
+          name: '1年',
300
+          data: tenureRes.value.data.map(item => item.cnt1Years || 0),
301
+          color: '#8b5cf6'
302
+        },
303
+        {
304
+          name: '不足1年',
305
+          data: tenureRes.value.data.map(item => item.cntLt1Year || 0),
306
+          color: '#14b8a6'
307
+        }
308
+      ]
309
+      setOption13(multiSeriesBarChartOption(xAxisData, seriesData))
310
+    }
311
+  } catch (error) {
312
+    console.error('获取数据失败:', error)
313
+  } finally {
314
+    loading.value = false
315
+  }
316
+}
317
+
318
+watch(() => props.filterParams, () => {
319
+  fetchData()
320
+}, { deep: true })
141 321
 
142 322
 const pieChartOption = (data, colors) => ({
143 323
   color: colors,
324
+  tooltip: { trigger: 'item', formatter: '{b}: {c}' },
325
+  legend: {
326
+    data: data.map(item => item.name),
327
+    top: '0%',
328
+    textStyle: { fontSize: 10 }
329
+  },
144 330
   series: [{
145 331
     type: 'pie',
146 332
     radius: ['40%', '65%'],
147 333
     center: ['50%', '55%'],
148 334
     data: data,
149
-    label: { show: true, formatter: '{b}\n{c}%', fontSize: 10 }
335
+    label: {
336
+      show: true,
337
+      formatter: (params) => {
338
+        const total = data.reduce((sum, item) => sum + item.value, 0)
339
+        const percentage = ((params.value / total) * 100).toFixed(2)
340
+        return `${params.name}\n${params.value}(${percentage}%)`
341
+      },
342
+      fontSize: 10
343
+    }
150 344
   }]
151 345
 })
152 346
 
153 347
 const donutChartOption = (data, colors) => ({
154 348
   color: colors,
349
+  tooltip: { trigger: 'item', formatter: '{b}: {c}' },
350
+  legend: {
351
+    data: data.map(item => item.name),
352
+    top: '0%',
353
+    textStyle: { fontSize: 10 }
354
+  },
155 355
   series: [{
156 356
     type: 'pie',
157 357
     radius: ['30%', '55%'],
158 358
     center: ['50%', '55%'],
159 359
     data: data,
160
-    label: { show: true, formatter: '{b}', fontSize: 10, position: 'outside' }
360
+    label: {
361
+      show: true,
362
+      position: 'outside',
363
+      formatter: (params) => {
364
+        const total = data.reduce((sum, item) => sum + item.value, 0)
365
+        const percentage = ((params.value / total) * 100).toFixed(2)
366
+        return `${params.name} ${params.value}(${percentage}%)`
367
+      },
368
+      fontSize: 10
369
+    }
161 370
   }]
162 371
 })
163 372
 
@@ -168,81 +377,37 @@ const multiBarChartOption = (data, color) => ({
168 377
   series: [{ type: 'bar', data: data.map(d => d.value), itemStyle: { color: color }, barWidth: 20 }]
169 378
 })
170 379
 
380
+const multiSeriesBarChartOption = (xAxisData, series) => ({
381
+  grid: { left: '10%', top: '15%', right: '5%', bottom: '15%', containLabel: true },
382
+  xAxis: {
383
+    type: 'category',
384
+    data: xAxisData,
385
+    axisLine: { lineStyle: { color: '#999' } },
386
+    axisLabel: { fontSize: 10, color: '#666' }
387
+  },
388
+  yAxis: {
389
+    type: 'value',
390
+    axisLine: { lineStyle: { color: '#999' } },
391
+    axisLabel: { fontSize: 10, color: '#666' },
392
+    splitLine: { lineStyle: { color: '#eee' } }
393
+  },
394
+  legend: {
395
+    data: series.map(s => s.name),
396
+    top: '0%',
397
+    textStyle: { fontSize: 10 }
398
+  },
399
+  series: series.map(s => ({
400
+    name: s.name,
401
+    type: 'bar',
402
+    data: s.data,
403
+    itemStyle: { color: s.color },
404
+    barWidth: 15
405
+  }))
406
+})
407
+
171 408
 onMounted(() => {
172
-  setOption4(pieChartOption([
173
-    { name: '女', value: 52.26 },
174
-    { name: '男', value: 47.74 }
175
-  ], ['#ec4899', '#3b82f6']))
176
-
177
-  setOption5(pieChartOption([
178
-    { name: '高级', value: 49.81 },
179
-    { name: '中级', value: 50.19 }
180
-  ], ['#22c55e', '#3b82f6']))
181
-
182
-  setOption6(pieChartOption([
183
-    { name: '高级证书', value: 1.59 },
184
-    { name: '中级证书', value: 98.41 }
185
-  ], ['#22c55e', '#3b82f6']))
186
-
187
-  setOption7(donutChartOption([
188
-    { name: '中下', value: 6.35 },
189
-    { name: '中间', value: 11.85 },
190
-    { name: '右下', value: 7.74 },
191
-    { name: '右上', value: 7.56 },
192
-    { name: '左中', value: 7.26 },
193
-    { name: '左上', value: 6.61 },
194
-    { name: '左上', value: 11.16 },
195
-    { name: '右下', value: 6.45 },
196
-    { name: '右上', value: 12.42 },
197
-    { name: '中下', value: 7.41 },
198
-    { name: '右上', value: 6.11 },
199
-    { name: '左上', value: 10.24 }
200
-  ], ['#8b5cf6', '#ec4899', '#f97316', '#22c55e', '#3b82f6', '#14b8a6', '#ef4444', '#fbbf24', '#6366f1', '#06b6d4', '#84cc16', '#e11d48']))
201
-
202
-  setOption10(donutChartOption([
203
-    { name: '赵德峰', value: 12.46 },
204
-    { name: '刘佳', value: 7.94 },
205
-    { name: '郑晓华', value: 7.94 },
206
-    { name: '谢金艳', value: 7.82 },
207
-    { name: '潘仁芳', value: 7.33 },
208
-    { name: '张庆林', value: 7.2 },
209
-    { name: '陈颖', value: 7.2 },
210
-    { name: '李学芬', value: 7.2 },
211
-    { name: '李俊峰', value: 7.08 },
212
-    { name: '周雪梅', value: 7.08 },
213
-    { name: '刘旭', value: 6.46 },
214
-    { name: '李丽', value: 6.46 },
215
-    { name: '任静', value: 6.46 },
216
-    { name: '张静', value: 6.46 }
217
-  ], ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6', '#ef4444', '#fbbf24', '#6366f1', '#06b6d4', '#84cc16', '#e11d48', '#a855f7', '#f59e0b']))
218
-
219
-  setOption11(pieChartOption([
220
-    { name: '问题判断不清,未按规范检查导致漏检', value: 23.20 },
221
-    { name: '未按规范检查导致漏检', value: 7.04 },
222
-    { name: '判断不清,漏检', value: 6.48 },
223
-    { name: '未能辨别危险品', value: 5.92 },
224
-    { name: '未能辨别危险品', value: 5.92 },
225
-    { name: '未能辨别危险品', value: 2.88 },
226
-    { name: '简单漏检未能认真判断', value: 21.23 },
227
-    { name: '未能辨别危险品', value: 27.34 }
228
-  ], ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6', '#ef4444', '#fbbf24']))
229
-
230
-  setOption12(pieChartOption([
231
-    { name: '5年及5年以上', value: 30.71 },
232
-    { name: '4年人员数', value: 18.68 },
233
-    { name: '3年人员数', value: 22.47 },
234
-    { name: '2年人员数', value: 14.75 },
235
-    { name: '1年人员数', value: 6.76 },
236
-    { name: '不足1年人员数', value: 6.63 }
237
-  ], ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6']))
238
-
239
-  setOption13(multiBarChartOption([
240
-    { name: '安检一大队', value: 104 },
241
-    { name: '安检二大队', value: 39 },
242
-    { name: '安检三大队', value: 14 },
243
-    { name: '安检四大队', value: 13 },
244
-    { name: '安检五大队', value: 12 }
245
-  ], '#3b82f6'))
409
+  fetchData()
410
+
246 411
 })
247 412
 </script>
248 413
 
@@ -265,20 +430,19 @@ onMounted(() => {
265 430
   flex: 1;
266 431
   background: #fff;
267 432
   border-radius: 6px;
268
-  padding: 10px;
433
+  padding: 15px;
269 434
   border: 1px solid #eee;
270 435
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
271 436
   display: flex;
272 437
   flex-direction: column;
273
-  min-height: 200px;
438
+  min-height: 280px;
274 439
 }
275 440
 
276 441
 .chart-title {
277
-  font-size: 14px;
278
-  color: #333;
442
+  font-size: 17px;
443
+  color: black;
279 444
   margin-bottom: 5px;
280 445
   text-align: left;
281
-  font-weight: 500;
282 446
 }
283 447
 
284 448
 .echarts {
@@ -303,7 +467,7 @@ onMounted(() => {
303 467
 
304 468
 .number-unit {
305 469
   font-size: 14px;
306
-  color: #666;
470
+  color: #3b82f6;
307 471
   margin-top: 5px;
308 472
 }
309
-</style>
473
+</style>

+ 333 - 138
src/views/blockingData/blockingDataScreen/components/ModuleFour.vue

@@ -4,21 +4,21 @@
4 4
       <div class="chart-row">
5 5
         <div class="chart-item">
6 6
           <div class="chart-title">T1航站楼放行速率</div>
7
-          <div ref="chart1" class="echarts"></div>
7
+          <div ref="rateChart1" class="echarts"></div>
8 8
         </div>
9 9
         <div class="chart-item">
10 10
           <div class="chart-title">T2航站楼放行速率</div>
11
-          <div ref="chart2" class="echarts"></div>
11
+          <div ref="rateChart2" class="echarts"></div>
12 12
         </div>
13 13
       </div>
14 14
       <div class="chart-row">
15 15
         <div class="chart-item">
16 16
           <div class="chart-title">大队总平均速率对比(国内)</div>
17
-          <div ref="chart3" class="echarts"></div>
17
+          <div ref="rateChart3" class="echarts"></div>
18 18
         </div>
19 19
         <div class="chart-item">
20 20
           <div class="chart-title">大队总平均速率对比(国际+中转)</div>
21
-          <div ref="chart4" class="echarts"></div>
21
+          <div ref="rateChart4" class="echarts"></div>
22 22
         </div>
23 23
       </div>
24 24
     </div>
@@ -26,10 +26,12 @@
26 26
 </template>
27 27
 
28 28
 <script setup>
29
-import { ref, onMounted, onBeforeUnmount } from 'vue'
29
+import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
30 30
 import * as echarts from 'echarts'
31 31
 import ModuleContainer from './ModuleContainer.vue'
32 32
 import { useEcharts } from '@/hooks/chart.js'
33
+import { t1RateStats, t2RateStats, insideRateStats, outsideRateStats } from '@/api/blockingData/blockingDataScreen.js'
34
+import { formatDateHy } from '@/utils/index.js'
33 35
 
34 36
 // 接收筛选参数
35 37
 const props = defineProps({
@@ -39,147 +41,336 @@ const props = defineProps({
39 41
   }
40 42
 })
41 43
 
42
-const chart1 = ref(null)
43
-const chart2 = ref(null)
44
-const chart3 = ref(null)
45
-const chart4 = ref(null)
44
+const rateChart1 = ref(null)
45
+const rateChart2 = ref(null)
46
+const rateChart3 = ref(null)
47
+const rateChart4 = ref(null)
46 48
 
47
-const { setOption: setOption1 } = useEcharts(chart1)
48
-const { setOption: setOption2 } = useEcharts(chart2)
49
-const { setOption: setOption3 } = useEcharts(chart3)
50
-const { setOption: setOption4 } = useEcharts(chart4)
49
+const { setOption: setOption1 } = useEcharts(rateChart1)
50
+const { setOption: setOption2 } = useEcharts(rateChart2)
51
+const { setOption: setOption3 } = useEcharts(rateChart3)
52
+const { setOption: setOption4 } = useEcharts(rateChart4)
51 53
 
52
-const generateData = () => {
53
-  const data = []
54
-  for (let i = 0; i < 30; i++) {
55
-    data.push(Math.floor(Math.random() * 60) + 120)
54
+// 处理filterParams,将dateRange拆分为startTime和endTime
55
+const processFilterParams = (params) => {
56
+  const { dateRange, ...rest } = params
57
+  const processed = { ...rest }
58
+
59
+  if (dateRange && Array.isArray(dateRange) && dateRange.length === 2) {
60
+    processed.startDate = formatDateHy(dateRange[0])
61
+    processed.endDate = formatDateHy(dateRange[1])
62
+  }
63
+  if (processed.brigadeId == 'all') {
64
+    delete processed.brigadeId
65
+  }
66
+  if (processed.terminalId == 'all') {
67
+    delete processed.terminalId
56 68
   }
57
-  return data
58
-}
59 69
 
60
-const xAxisData = []
61
-for (let i = 1; i <= 30; i++) {
62
-  xAxisData.push(i + '日')
70
+  return processed
63 71
 }
64 72
 
65
-const lineChartOption = (data1, data2, color1, color2) => ({
66
-  grid: {
67
-    left: '10%',
68
-    top: '15%',
69
-    right: '5%',
70
-    bottom: '15%',
71
-    containLabel: true
72
-  },
73
-  xAxis: {
74
-    type: 'category',
75
-    data: xAxisData,
76
-    axisLine: { lineStyle: { color: '#999' } },
77
-    axisLabel: { fontSize: 10, color: '#666' }
78
-  },
79
-  yAxis: {
80
-    type: 'value',
81
-    axisLine: { lineStyle: { color: '#999' } },
82
-    axisLabel: { fontSize: 10, color: '#666' },
83
-    splitLine: { lineStyle: { color: '#eee' } }
84
-  },
85
-  legend: {
86
-    top: 0,
87
-    right: 10,
88
-    textStyle: { fontSize: 10 }
89
-  },
90
-  series: [
91
-    {
92
-      name: '高峰期时段',
93
-      type: 'line',
94
-      smooth: true,
95
-      symbol: 'circle',
96
-      symbolSize: 6,
97
-      data: data1,
98
-      itemStyle: { color: color1 },
99
-      areaStyle: {
100
-        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
101
-          { offset: 0, color: color1 + '40' },
102
-          { offset: 1, color: color1 + '10' }
103
-        ])
104
-      }
105
-    },
106
-    {
107
-      name: '非高峰期时段',
108
-      type: 'line',
109
-      smooth: true,
110
-      symbol: 'circle',
111
-      symbolSize: 6,
112
-      data: data2,
113
-      itemStyle: { color: color2 },
114
-      areaStyle: {
115
-        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
116
-          { offset: 0, color: color2 + '40' },
117
-          { offset: 1, color: color2 + '10' }
118
-        ])
119
-      }
73
+// 数据加载函数
74
+const loadData = async () => {
75
+  try {
76
+    const queryParams = processFilterParams(props.filterParams)
77
+    console.log('查询参数:', queryParams)
78
+
79
+    // 并行请求所有速率数据
80
+    const [t1RateRes, t2RateRes, insideRateRes, outsideRateRes] = await Promise.allSettled([
81
+      t1RateStats(queryParams),
82
+      t2RateStats(queryParams),
83
+      insideRateStats(queryParams),
84
+      outsideRateStats(queryParams)
85
+    ])
86
+
87
+    console.log('T1速率响应:', t1RateRes)
88
+    console.log('T2速率响应:', t2RateRes)
89
+    console.log('国内速率响应:', insideRateRes)
90
+    console.log('国际中转速率响应:', outsideRateRes)
91
+
92
+    // T1航站楼放行速率
93
+    const t1RateData = t1RateRes?.value?.data || []
94
+    console.log('T1速率数据:', t1RateData)
95
+    console.log('T1速率数据长度:', t1RateData.length)
96
+    console.log('T1速率数据类型:', typeof t1RateData)
97
+    console.log('T1速率数据是否为数组:', Array.isArray(t1RateData))
98
+    console.log('T1速率数据:', t1RateData)
99
+    if (t1RateData.length > 0) {
100
+      const dates = [...new Set(t1RateData.map(d => d.statDate))].sort()
101
+      
102
+      const seriesData = [
103
+        {
104
+          name: 'T1-A区速率(高峰期时段)',
105
+          type: 'line',
106
+          data: dates.map(date => {
107
+            const item = t1RateData.find(d => d.statDate === date)
108
+            return item ? item.t1AAreaRatePeak : 0
109
+          }),
110
+          itemStyle: { color: '#3b82f6' },
111
+          lineStyle: { width: 3 },
112
+          symbol: 'circle',
113
+          symbolSize: 8
114
+        },
115
+        {
116
+          name: 'T1-B区速率(高峰期时段)',
117
+          type: 'line',
118
+          data: dates.map(date => {
119
+            const item = t1RateData.find(d => d.statDate === date)
120
+            return item ? item.t1BAreaRatePeak : 0
121
+          }),
122
+          itemStyle: { color: '#22c55e' },
123
+          lineStyle: { width: 3 },
124
+          symbol: 'circle',
125
+          symbolSize: 8
126
+        }
127
+      ]
128
+
129
+      console.log('准备设置T1图表选项')
130
+      console.log('日期数据:', dates)
131
+      console.log('系列数据:', seriesData)
132
+
133
+      setOption1({
134
+        tooltip: {
135
+          trigger: 'axis',
136
+          formatter: function(params) {
137
+            let result = params[0].name + '<br/>'
138
+            params.forEach(param => {
139
+              result += `${param.marker} ${param.seriesName}: ${param.value} 件/小时<br/>`
140
+            })
141
+            return result
142
+          }
143
+        },
144
+        legend: {
145
+          data: ['T1-A区速率(高峰期时段)', 'T1-B区速率(高峰期时段)']
146
+        },
147
+        grid: {
148
+          left: '3%',
149
+          right: '4%',
150
+          bottom: '3%',
151
+          containLabel: true
152
+        },
153
+        xAxis: {
154
+          type: 'category',
155
+          data: dates,
156
+          axisLabel: {
157
+            rotate: 45
158
+          }
159
+        },
160
+        yAxis: {
161
+          type: 'value',
162
+          name: '速率',
163
+          axisLabel: {
164
+            formatter: '{value} 件/小时'
165
+          }
166
+        },
167
+        series: seriesData
168
+      })
169
+      console.log('T1图表设置完成')
120 170
     }
121
-  ]
122
-})
123 171
 
124
-const barLineChartOption = (data1, data2, data3, colors) => ({
125
-  grid: {
126
-    left: '10%',
127
-    top: '15%',
128
-    right: '5%',
129
-    bottom: '15%',
130
-    containLabel: true
131
-  },
132
-  xAxis: {
133
-    type: 'category',
134
-    data: xAxisData,
135
-    axisLine: { lineStyle: { color: '#999' } },
136
-    axisLabel: { fontSize: 10, color: '#666', rotate: 45 }
137
-  },
138
-  yAxis: {
139
-    type: 'value',
140
-    axisLine: { lineStyle: { color: '#999' } },
141
-    axisLabel: { fontSize: 10, color: '#666' },
142
-    splitLine: { lineStyle: { color: '#eee' } }
143
-  },
144
-  legend: {
145
-    top: 0,
146
-    right: 10,
147
-    textStyle: { fontSize: 10 }
148
-  },
149
-  series: [
150
-    {
151
-      name: '安检三大队',
152
-      type: 'bar',
153
-      data: data1,
154
-      itemStyle: { color: colors[0] },
155
-      barWidth: 8
156
-    },
157
-    {
158
-      name: '安检二大队',
159
-      type: 'line',
160
-      smooth: true,
161
-      symbol: 'circle',
162
-      symbolSize: 6,
163
-      data: data2,
164
-      itemStyle: { color: colors[1] }
165
-    },
166
-    {
167
-      name: '安检一大队',
168
-      type: 'line',
169
-      smooth: true,
170
-      symbol: 'circle',
171
-      symbolSize: 6,
172
-      data: data3,
173
-      itemStyle: { color: colors[2] }
172
+    // T2航站楼放行速率
173
+    const t2RateData = t2RateRes?.value?.data || []
174
+    if (t2RateData.length > 0) {
175
+      const dates = [...new Set(t2RateData.map(d => d.statDate))].sort()
176
+      
177
+      const seriesData = [
178
+        {
179
+          name: 'T2-国内速率(高峰期时段)',
180
+          type: 'line',
181
+          data: dates.map(date => {
182
+            const item = t2RateData.find(d => d.statDate === date)
183
+            return item ? item.t2DomesticRatePeak : 0
184
+          }),
185
+          itemStyle: { color: '#3b82f6' },
186
+          lineStyle: { width: 3 },
187
+          symbol: 'circle',
188
+          symbolSize: 8
189
+        },
190
+        {
191
+          name: 'T2-国际速率(高峰期时段)',
192
+          type: 'line',
193
+          data: dates.map(date => {
194
+            const item = t2RateData.find(d => d.statDate === date)
195
+            return item ? item.t2InternationalRatePeak : 0
196
+          }),
197
+          itemStyle: { color: '#22c55e' },
198
+          lineStyle: { width: 3 },
199
+          symbol: 'circle',
200
+          symbolSize: 8
201
+        }
202
+      ]
203
+
204
+      setOption2({
205
+        tooltip: {
206
+          trigger: 'axis',
207
+          formatter: function(params) {
208
+            let result = params[0].name + '<br/>'
209
+            params.forEach(param => {
210
+              result += `${param.marker} ${param.seriesName}: ${param.value} 件/小时<br/>`
211
+            })
212
+            return result
213
+          }
214
+        },
215
+        legend: {
216
+          data: ['T2-国内速率(高峰期时段)', 'T2-国际速率(高峰期时段)']
217
+        },
218
+        grid: {
219
+          left: '3%',
220
+          right: '4%',
221
+          bottom: '3%',
222
+          containLabel: true
223
+        },
224
+        xAxis: {
225
+          type: 'category',
226
+          data: dates,
227
+          axisLabel: {
228
+            rotate: 45
229
+          }
230
+        },
231
+        yAxis: {
232
+          type: 'value',
233
+          name: '速率',
234
+          axisLabel: {
235
+            formatter: '{value} 件/小时'
236
+          }
237
+        },
238
+        series: seriesData
239
+      })
174 240
     }
175
-  ]
176
-})
241
+
242
+    // 大队总平均速率对比(国内)
243
+    const insideRateData = insideRateRes?.value?.data || []
244
+    if (insideRateData.length > 0) {
245
+      const dates = [...new Set(insideRateData.map(d => d.statDate))].sort()
246
+      const brigadeNames = [...new Set(insideRateData.map(d => d.dutyBrigadeName))]
247
+      
248
+      const seriesData = brigadeNames.map((brigade, index) => ({
249
+        name: brigade,
250
+        type: 'line',
251
+        data: dates.map(date => {
252
+          const item = insideRateData.find(d => d.statDate === date && d.dutyBrigadeName === brigade)
253
+          return item ? item.avgRatePeak : 0
254
+        }),
255
+        itemStyle: {
256
+          color: ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6'][index % 5]
257
+        },
258
+        lineStyle: { width: 3 },
259
+        symbol: 'circle',
260
+        symbolSize: 8
261
+      }))
262
+
263
+      setOption3({
264
+        tooltip: {
265
+          trigger: 'axis',
266
+          formatter: function(params) {
267
+            let result = params[0].name + '<br/>'
268
+            params.forEach(param => {
269
+              result += `${param.marker} ${param.seriesName}: ${param.value} 件/小时<br/>`
270
+            })
271
+            return result
272
+          }
273
+        },
274
+        legend: {
275
+          data: brigadeNames
276
+        },
277
+        grid: {
278
+          left: '3%',
279
+          right: '4%',
280
+          bottom: '3%',
281
+          containLabel: true
282
+        },
283
+        xAxis: {
284
+          type: 'category',
285
+          data: dates,
286
+          axisLabel: {
287
+            rotate: 45
288
+          }
289
+        },
290
+        yAxis: {
291
+          type: 'value',
292
+          name: '速率',
293
+          axisLabel: {
294
+            formatter: '{value} 件/小时'
295
+          }
296
+        },
297
+        series: seriesData
298
+      })
299
+    }
300
+
301
+    // 大队总平均速率对比(国际+中转)
302
+    const outsideRateData = outsideRateRes?.value?.data || []
303
+    if (outsideRateData.length > 0) {
304
+      const dates = [...new Set(outsideRateData.map(d => d.statDate))].sort()
305
+      const brigadeNames = [...new Set(outsideRateData.map(d => d.dutyBrigadeName))]
306
+      
307
+      const seriesData = brigadeNames.map((brigade, index) => ({
308
+        name: brigade,
309
+        type: 'line',
310
+        data: dates.map(date => {
311
+          const item = outsideRateData.find(d => d.statDate === date && d.dutyBrigadeName === brigade)
312
+          return item ? item.avgRatePeak : 0
313
+        }),
314
+        itemStyle: {
315
+          color: ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6'][index % 5]
316
+        },
317
+        lineStyle: { width: 3 },
318
+        symbol: 'circle',
319
+        symbolSize: 8
320
+      }))
321
+
322
+      setOption4({
323
+        tooltip: {
324
+          trigger: 'axis',
325
+          formatter: function(params) {
326
+            let result = params[0].name + '<br/>'
327
+            params.forEach(param => {
328
+              result += `${param.marker} ${param.seriesName}: ${param.value} 件/小时<br/>`
329
+            })
330
+            return result
331
+          }
332
+        },
333
+        legend: {
334
+          data: brigadeNames
335
+        },
336
+        grid: {
337
+          left: '3%',
338
+          right: '4%',
339
+          bottom: '3%',
340
+          containLabel: true
341
+        },
342
+        xAxis: {
343
+          type: 'category',
344
+          data: dates,
345
+          axisLabel: {
346
+            rotate: 45
347
+          }
348
+        },
349
+        yAxis: {
350
+          type: 'value',
351
+          name: '速率',
352
+          axisLabel: {
353
+            formatter: '{value} 件/小时'
354
+          }
355
+        },
356
+        series: seriesData
357
+      })
358
+    }
359
+
360
+  } catch (error) {
361
+    console.error('加载速率数据失败:', error)
362
+  }
363
+}
364
+
365
+// 监听筛选参数变化
366
+watch(() => props.filterParams, () => {
367
+  loadData()
368
+}, { deep: true })
369
+
370
+
177 371
 
178 372
 onMounted(() => {
179
-  setOption1(lineChartOption(generateData(), generateData(), '#3b82f6', '#22c55e'))
180
-  setOption2(lineChartOption(generateData(), generateData(), '#2563eb', '#16a34a'))
181
-  setOption3(barLineChartOption(generateData(), generateData(), generateData(), ['#3b82f6', '#22c55e', '#f97316']))
182
-  setOption4(barLineChartOption(generateData(), generateData(), generateData(), ['#3b82f6', '#22c55e', '#f97316']))
373
+  loadData()
183 374
 })
184 375
 </script>
185 376
 
@@ -195,6 +386,8 @@ onMounted(() => {
195 386
   flex: 1;
196 387
   display: flex;
197 388
   gap: 10px;
389
+  min-height: 400px;
390
+  height: 400px;
198 391
 }
199 392
 
200 393
 .chart-item {
@@ -203,10 +396,11 @@ onMounted(() => {
203 396
   flex-direction: column;
204 397
   background: #fff;
205 398
   border-radius: 6px;
206
-  padding: 8px;
207
-
399
+  padding: 15px;
208 400
   border: 1px solid #eee;
209 401
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
402
+  min-height: 350px;
403
+  height: 100%;
210 404
 }
211 405
 
212 406
 .chart-title {
@@ -219,6 +413,7 @@ onMounted(() => {
219 413
 .echarts {
220 414
   flex: 1;
221 415
   width: 100%;
222
-  min-height: 0;
416
+  min-height: 300px;
417
+  height: 300px;
223 418
 }
224 419
 </style>

+ 625 - 30
src/views/blockingData/blockingDataScreen/components/ModuleOne.vue

@@ -432,51 +432,533 @@ const loadData = async () => {
432 432
     console.log(itemDistRes, areaDistRes, 55555)
433 433
     const itemDistData = itemDistRes?.value?.data || []
434 434
     if (itemDistData.length > 0) {
435
-      setOption7(ringPieOption(
436
-        itemDistData.map(d => ({ name: d.name, value: d.value })),
437
-        ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6']
438
-      ))
435
+      setOption7({
436
+        tooltip: {
437
+          trigger: 'item',
438
+          formatter: '{a} <br/>{b}: {c} ({d}%)'
439
+        },
440
+        legend: {
441
+          orient: 'vertical',
442
+          left: 'left',
443
+          data: itemDistData.map(d => d.missCheckItem)
444
+        },
445
+        series: [
446
+          {
447
+            name: '查堵物品分布',
448
+            type: 'pie',
449
+            radius: ['40%', '70%'],
450
+            avoidLabelOverlap: false,
451
+            itemStyle: {
452
+              
453
+              borderColor: '#fff',
454
+              borderWidth: 2
455
+            },
456
+            // label: {
457
+            //   show: true,
458
+            //   position: 'center'
459
+            // },
460
+            emphasis: {
461
+              label: {
462
+                show: true,
463
+                fontSize: '18',
464
+                fontWeight: 'bold'
465
+              }
466
+            },
467
+            labelLine: {
468
+              show: true
469
+            },
470
+            data: itemDistData.map((d, index) => ({
471
+              value: d.count,
472
+              name: d.missCheckItem,
473
+              itemStyle: {
474
+                color: ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6'][index % 6]
475
+              }
476
+            }))
477
+          }
478
+        ]
479
+      })
439 480
     }
440 481
 
441 482
 
442
-    const areaDistData = areaDistRes?.value?.data || []
443
-    if (areaDistData.length > 0) {
444
-      setOption8(ringPieOption(
445
-        areaDistData.map(d => ({ name: d.name, value: d.value })),
446
-        ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6']
447
-      ))
448
-    }
483
+    const areaDistData = areaDistRes?.value?.data || {}
484
+    
485
+    // 提取数据
486
+    const brigadeNames = areaDistData.brigadeNames || []
487
+    const luggageDataList = areaDistData.luggageDataList || []
488
+    const blockedDataList = areaDistData.blockedDataList || []
489
+    
490
+    // 柱状图数据(过检行李数)
491
+    const barSeries = [
492
+      {
493
+        name: 'T1旅检行李数',
494
+        type: 'bar',
495
+        data: brigadeNames.map(brigade => {
496
+          const item = luggageDataList.find(d => d.brigadeName === brigade)
497
+          return item ? item.t1PassengerLuggageCount : 0
498
+        }),
499
+        itemStyle: { color: '#3b82f6' }
500
+      },
501
+      {
502
+        name: 'T2旅检行李数',
503
+        type: 'bar',
504
+        data: brigadeNames.map(brigade => {
505
+          const item = luggageDataList.find(d => d.brigadeName === brigade)
506
+          return item ? item.t2PassengerLuggageCount : 0
507
+        }),
508
+        itemStyle: { color: '#22c55e' }
509
+      },
510
+      {
511
+        name: 'T1行检行李数',
512
+        type: 'bar',
513
+        data: brigadeNames.map(brigade => {
514
+          const item = luggageDataList.find(d => d.brigadeName === brigade)
515
+          return item ? item.t1CargoLuggageCount : 0
516
+        }),
517
+        itemStyle: { color: '#f97316' }
518
+      },
519
+      {
520
+        name: 'T2行检行李数',
521
+        type: 'bar',
522
+        data: brigadeNames.map(brigade => {
523
+          const item = luggageDataList.find(d => d.brigadeName === brigade)
524
+          return item ? item.t2CargoLuggageCount : 0
525
+        }),
526
+        itemStyle: { color: '#ec4899' }
527
+      }
528
+    ]
529
+    
530
+    // 折线图数据(查堵数)
531
+    const lineSeries = [
532
+      {
533
+        name: 'T1旅检查堵数',
534
+        type: 'line',
535
+        yAxisIndex: 1,
536
+        data: brigadeNames.map(brigade => {
537
+          const item = blockedDataList.find(d => d.brigadeName === brigade)
538
+          return item ? item.t1PassengerBlockedCount : 0
539
+        }),
540
+        itemStyle: { color: '#3b82f6' },
541
+        lineStyle: { width: 3 },
542
+        symbol: 'circle',
543
+        symbolSize: 8
544
+      },
545
+      {
546
+        name: 'T2旅检查堵数',
547
+        type: 'line',
548
+        yAxisIndex: 1,
549
+        data: brigadeNames.map(brigade => {
550
+          const item = blockedDataList.find(d => d.brigadeName === brigade)
551
+          return item ? item.t2PassengerBlockedCount : 0
552
+        }),
553
+        itemStyle: { color: '#22c55e' },
554
+        lineStyle: { width: 3 },
555
+        symbol: 'circle',
556
+        symbolSize: 8
557
+      },
558
+      {
559
+        name: 'T1行检查堵数',
560
+        type: 'line',
561
+        yAxisIndex: 1,
562
+        data: brigadeNames.map(brigade => {
563
+          const item = blockedDataList.find(d => d.brigadeName === brigade)
564
+          return item ? item.t1CargoBlockedCount : 0
565
+        }),
566
+        itemStyle: { color: '#f97316' },
567
+        lineStyle: { width: 3 },
568
+        symbol: 'circle',
569
+        symbolSize: 8
570
+      },
571
+      {
572
+        name: 'T2行检查堵数',
573
+        type: 'line',
574
+        yAxisIndex: 1,
575
+        data: brigadeNames.map(brigade => {
576
+          const item = blockedDataList.find(d => d.brigadeName === brigade)
577
+          return item ? item.t2CargoBlockedCount : 0
578
+        }),
579
+        itemStyle: { color: '#ec4899' },
580
+        lineStyle: { width: 3 },
581
+        symbol: 'circle',
582
+        symbolSize: 8
583
+      }
584
+    ]
585
+    
586
+    setOption8({
587
+      tooltip: {
588
+        trigger: 'axis',
589
+        axisPointer: {
590
+          type: 'cross'
591
+        },
592
+        formatter: function(params) {
593
+          let result = params[0].name + '<br/>'
594
+          params.forEach(param => {
595
+            if (param.seriesType === 'bar') {
596
+              result += `${param.marker} ${param.seriesName}: ${param.value.toLocaleString()} 件<br/>`
597
+            } else {
598
+              result += `${param.marker} ${param.seriesName}: ${param.value} 次<br/>`
599
+            }
600
+          })
601
+          return result
602
+        }
603
+      },
604
+      legend: {
605
+        data: [
606
+          'T1旅检行李数', 'T2旅检行李数', 'T1行检行李数', 'T2行检行李数',
607
+          'T1旅检查堵数', 'T2旅检查堵数', 'T1行检查堵数', 'T2行检查堵数'
608
+        ]
609
+      },
610
+      grid: {
611
+        left: '3%',
612
+        right: '4%',
613
+        bottom: '3%',
614
+        containLabel: true
615
+      },
616
+      xAxis: {
617
+        type: 'category',
618
+        data: brigadeNames,
619
+        axisLabel: {
620
+          rotate: 45
621
+        }
622
+      },
623
+      yAxis: [
624
+        {
625
+          type: 'value',
626
+          name: '行李数',
627
+          position: 'left',
628
+          axisLine: {
629
+            lineStyle: {
630
+              color: '#3b82f6'
631
+            }
632
+          },
633
+          axisLabel: {
634
+            formatter: '{value} 件'
635
+          }
636
+        },
637
+        {
638
+          type: 'value',
639
+          name: '查堵数',
640
+          position: 'right',
641
+          axisLine: {
642
+            lineStyle: {
643
+              color: '#ec4899'
644
+            }
645
+          },
646
+          axisLabel: {
647
+            formatter: '{value} 次'
648
+          }
649
+        }
650
+      ],
651
+      series: [...barSeries, ...lineSeries]
652
+    })
449 653
 
450 654
 
451 655
     // 查堵类型分布
452 656
     const discDistRes = await discriminationDistribution(queryParams)
453 657
     const discDistData = discDistRes.data || []
454
-    setOption9(barChartOption(discDistData, '#3b82f6'))
658
+    
659
+    // 按discriminationType分组数据
660
+    const groupedData = {}
661
+    discDistData.forEach(item => {
662
+      if (!groupedData[item.discriminationType]) {
663
+        groupedData[item.discriminationType] = []
664
+      }
665
+      groupedData[item.discriminationType].push(item)
666
+    })
667
+    
668
+    // 提取所有missCheckItem作为横坐标
669
+    const allMissCheckItems = [...new Set(discDistData.map(d => d.missCheckItem))]
670
+    
671
+    // 为每个discriminationType创建数据系列
672
+    const seriesData = Object.keys(groupedData).map((type, index) => {
673
+      const data = allMissCheckItems.map(item => {
674
+        const found = groupedData[type].find(d => d.missCheckItem === item)
675
+        return found ? found.count : 0
676
+      })
677
+      return {
678
+        name: type,
679
+        type: 'bar',
680
+        data: data,
681
+        itemStyle: {
682
+          color: ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6'][index % 6]
683
+        }
684
+      }
685
+    })
686
+    
687
+    setOption9({
688
+      tooltip: {
689
+        trigger: 'axis',
690
+        axisPointer: {
691
+          type: 'shadow'
692
+        }
693
+      },
694
+      legend: {
695
+        data: Object.keys(groupedData)
696
+      },
697
+      grid: {
698
+        left: '3%',
699
+        right: '4%',
700
+        bottom: '3%',
701
+        containLabel: true
702
+      },
703
+      xAxis: {
704
+        type: 'category',
705
+        data: allMissCheckItems,
706
+        axisLabel: {
707
+          rotate: 45
708
+        }
709
+      },
710
+      yAxis: {
711
+        type: 'value'
712
+      },
713
+      series: seriesData
714
+    })
455 715
 
456 716
     // 查堵时间段过检行李数及万分率
457 717
     const timePeriodRes = await timePeriodStats(queryParams)
458 718
     const timePeriodData = timePeriodRes.data || []
459
-    setOption10(dualAxisBarChartOption(
460
-      timePeriodData.map(d => d.luggageCount),
461
-      timePeriodData.map(d => d.rate),
462
-      '#3b82f6',
463
-      '#ec4899'
464
-    ))
719
+    
720
+    setOption10({
721
+      tooltip: {
722
+        trigger: 'axis',
723
+        axisPointer: {
724
+          type: 'cross'
725
+        },
726
+        formatter: function(params) {
727
+          let result = params[0].name + '<br/>'
728
+          params.forEach(param => {
729
+            if (param.seriesName === '过检行李数') {
730
+              result += `${param.marker} ${param.seriesName}: ${param.value} 件<br/>`
731
+            } else {
732
+              result += `${param.marker} ${param.seriesName}: ${param.value} ‰<br/>`
733
+            }
734
+          })
735
+          return result
736
+        }
737
+      },
738
+      legend: {
739
+        data: ['过检行李数', '查堵万分率']
740
+      },
741
+      grid: {
742
+        left: '3%',
743
+        right: '4%',
744
+        bottom: '3%',
745
+        containLabel: true
746
+      },
747
+      xAxis: {
748
+        type: 'category',
749
+        data: timePeriodData.map(d => d.timePeriod),
750
+        axisLabel: {
751
+          rotate: 45
752
+        }
753
+      },
754
+      yAxis: [
755
+        {
756
+          type: 'value',
757
+          name: '过检行李数',
758
+          position: 'left',
759
+          axisLine: {
760
+            lineStyle: {
761
+              color: '#3b82f6'
762
+            }
763
+          },
764
+          axisLabel: {
765
+            formatter: '{value} 件'
766
+          }
767
+        },
768
+        {
769
+          type: 'value',
770
+          name: '查堵万分率',
771
+          position: 'right',
772
+          axisLine: {
773
+            lineStyle: {
774
+              color: '#ec4899'
775
+            }
776
+          },
777
+          axisLabel: {
778
+            formatter: '{value} ‰'
779
+          }
780
+        }
781
+      ],
782
+      series: [
783
+        {
784
+          name: '过检行李数',
785
+          type: 'bar',
786
+          yAxisIndex: 0,
787
+          data: timePeriodData.map(d => d.totalLuggageCount),
788
+          itemStyle: {
789
+            color: '#3b82f6'
790
+          }
791
+        },
792
+        {
793
+          name: '查堵万分率',
794
+          type: 'line',
795
+          yAxisIndex: 1,
796
+          data: timePeriodData.map(d => d.blockRate),
797
+          itemStyle: {
798
+            color: '#ec4899'
799
+          },
800
+          lineStyle: {
801
+            width: 3
802
+          },
803
+          symbol: 'circle',
804
+          symbolSize: 8
805
+        }
806
+      ]
807
+    })
465 808
 
466 809
     // 每日过检行李数及万分率
467 810
     const dailyLuggageRes = await dailyLuggageAndRate(queryParams)
468 811
     const dailyLuggageData = dailyLuggageRes.data || []
469
-    setOption11(dualAxisBarChartOption(
470
-      dailyLuggageData.map(d => d.luggageCount),
471
-      dailyLuggageData.map(d => d.rate),
472
-      '#3b82f6',
473
-      '#ec4899'
474
-    ))
812
+    
813
+    setOption11({
814
+      tooltip: {
815
+        trigger: 'axis',
816
+        axisPointer: {
817
+          type: 'cross'
818
+        },
819
+        formatter: function(params) {
820
+          let result = params[0].name + '<br/>'
821
+          params.forEach(param => {
822
+            if (param.seriesName === '过检行李数') {
823
+              result += `${param.marker} ${param.seriesName}: ${param.value} 件<br/>`
824
+            } else {
825
+              result += `${param.marker} ${param.seriesName}: ${param.value} ‰<br/>`
826
+            }
827
+          })
828
+          return result
829
+        }
830
+      },
831
+      legend: {
832
+        data: ['过检行李数', '查堵万分率']
833
+      },
834
+      grid: {
835
+        left: '3%',
836
+        right: '4%',
837
+        bottom: '3%',
838
+        containLabel: true
839
+      },
840
+      xAxis: {
841
+        type: 'category',
842
+        data: dailyLuggageData.map(d => d.timePeriod),
843
+        axisLabel: {
844
+          rotate: 45
845
+        }
846
+      },
847
+      yAxis: [
848
+        {
849
+          type: 'value',
850
+          name: '过检行李数',
851
+          position: 'left',
852
+          axisLine: {
853
+            lineStyle: {
854
+              color: '#3b82f6'
855
+            }
856
+          },
857
+          axisLabel: {
858
+            formatter: '{value} 件'
859
+          }
860
+        },
861
+        {
862
+          type: 'value',
863
+          name: '查堵万分率',
864
+          position: 'right',
865
+          axisLine: {
866
+            lineStyle: {
867
+              color: '#ec4899'
868
+            }
869
+          },
870
+          axisLabel: {
871
+            formatter: '{value} ‰'
872
+          }
873
+        }
874
+      ],
875
+      series: [
876
+        {
877
+          name: '过检行李数',
878
+          type: 'bar',
879
+          yAxisIndex: 0,
880
+          data: dailyLuggageData.map(d => d.totalLuggageCount),
881
+          itemStyle: {
882
+            color: '#3b82f6'
883
+          }
884
+        },
885
+        {
886
+          name: '查堵万分率',
887
+          type: 'line',
888
+          yAxisIndex: 1,
889
+          data: dailyLuggageData.map(d => d.blockRate),
890
+          itemStyle: {
891
+            color: '#ec4899'
892
+          },
893
+          lineStyle: {
894
+            width: 3
895
+          },
896
+          symbol: 'circle',
897
+          symbolSize: 8
898
+        }
899
+      ]
900
+    })
475 901
 
476 902
     // AI复查图像总数
477 903
     const aiImageRes = await aiImageStats(queryParams)
478 904
     const aiImageData = aiImageRes.data || []
479
-    setOption12(lineChartOption(aiImageData.map(d => d.value), '#14b8a6', 'AI复查图像数'))
905
+    
906
+    setOption12({
907
+      tooltip: {
908
+        trigger: 'axis',
909
+        formatter: function(params) {
910
+          return `${params[0].name}<br/>AI复查图像数: ${params[0].value} 张`
911
+        }
912
+      },
913
+      grid: {
914
+        left: '3%',
915
+        right: '4%',
916
+        bottom: '3%',
917
+        containLabel: true
918
+      },
919
+      xAxis: {
920
+        type: 'category',
921
+        data: aiImageData.map(d => d.statDate ? d.statDate.split('T')[0] : ''),
922
+        axisLabel: {
923
+          rotate: 45
924
+        }
925
+      },
926
+      yAxis: {
927
+        type: 'value',
928
+        name: '图像数',
929
+        axisLabel: {
930
+          formatter: '{value} 张'
931
+        }
932
+      },
933
+      series: [
934
+        {
935
+          name: 'AI复查图像数',
936
+          type: 'line',
937
+          data: aiImageData.map(d => d.totalBlockedCount),
938
+          itemStyle: {
939
+            color: '#14b8a6'
940
+          },
941
+          lineStyle: {
942
+            width: 3
943
+          },
944
+          symbol: 'circle',
945
+          symbolSize: 8,
946
+          areaStyle: {
947
+            color: {
948
+              type: 'linear',
949
+              x: 0,
950
+              y: 0,
951
+              x2: 0,
952
+              y2: 1,
953
+              colorStops: [
954
+                { offset: 0, color: 'rgba(20, 184, 166, 0.3)' },
955
+                { offset: 1, color: 'rgba(20, 184, 166, 0.1)' }
956
+              ]
957
+            }
958
+          }
959
+        }
960
+      ]
961
+    })
480 962
 
481 963
     // AI漏判和误判图像总数
482 964
     const [aiMissRes, aiErrorRes] = await Promise.all([
@@ -487,8 +969,121 @@ const loadData = async () => {
487 969
     const aiMissData = aiMissRes.data || []
488 970
     const aiErrorData = aiErrorRes.data || []
489 971
 
490
-    setOption13(lineChartOption(aiMissData.map(d => d.value), '#ef4444', 'AI漏判图像数'))
491
-    setOption14(lineChartOption(aiErrorData.map(d => d.value), '#f97316', 'AI误判图像数'))
972
+    // AI漏判图像数
973
+    setOption13({
974
+      tooltip: {
975
+        trigger: 'axis',
976
+        formatter: function(params) {
977
+          return `${params[0].name}<br/>AI漏判图像数: ${params[0].value} 张`
978
+        }
979
+      },
980
+      grid: {
981
+        left: '3%',
982
+        right: '4%',
983
+        bottom: '3%',
984
+        containLabel: true
985
+      },
986
+      xAxis: {
987
+        type: 'category',
988
+        data: aiMissData.map(d => d.statDate ? d.statDate.split('T')[0] : ''),
989
+        axisLabel: {
990
+          rotate: 45
991
+        }
992
+      },
993
+      yAxis: {
994
+        type: 'value',
995
+        name: '图像数',
996
+        axisLabel: {
997
+          formatter: '{value} 张'
998
+        }
999
+      },
1000
+      series: [
1001
+        {
1002
+          name: 'AI漏判图像数',
1003
+          type: 'line',
1004
+          data: aiMissData.map(d => d.totalBlockedCount),
1005
+          itemStyle: {
1006
+            color: '#ef4444'
1007
+          },
1008
+          lineStyle: {
1009
+            width: 3
1010
+          },
1011
+          symbol: 'circle',
1012
+          symbolSize: 8,
1013
+          areaStyle: {
1014
+            color: {
1015
+              type: 'linear',
1016
+              x: 0,
1017
+              y: 0,
1018
+              x2: 0,
1019
+              y2: 1,
1020
+              colorStops: [
1021
+                { offset: 0, color: 'rgba(239, 68, 68, 0.3)' },
1022
+                { offset: 1, color: 'rgba(239, 68, 68, 0.1)' }
1023
+              ]
1024
+            }
1025
+          }
1026
+        }
1027
+      ]
1028
+    })
1029
+
1030
+    // AI误判图像数
1031
+    setOption14({
1032
+      tooltip: {
1033
+        trigger: 'axis',
1034
+        formatter: function(params) {
1035
+          return `${params[0].name}<br/>AI误判图像数: ${params[0].value} 张`
1036
+        }
1037
+      },
1038
+      grid: {
1039
+        left: '3%',
1040
+        right: '4%',
1041
+        bottom: '3%',
1042
+        containLabel: true
1043
+      },
1044
+      xAxis: {
1045
+        type: 'category',
1046
+        data: aiErrorData.map(d => d.statDate ? d.statDate.split('T')[0] : ''),
1047
+        axisLabel: {
1048
+          rotate: 45
1049
+        }
1050
+      },
1051
+      yAxis: {
1052
+        type: 'value',
1053
+        name: '图像数',
1054
+        axisLabel: {
1055
+          formatter: '{value} 张'
1056
+        }
1057
+      },
1058
+      series: [
1059
+        {
1060
+          name: 'AI误判图像数',
1061
+          type: 'line',
1062
+          data: aiErrorData.map(d => d.totalBlockedCount),
1063
+          itemStyle: {
1064
+            color: '#f97316'
1065
+          },
1066
+          lineStyle: {
1067
+            width: 3
1068
+          },
1069
+          symbol: 'circle',
1070
+          symbolSize: 8,
1071
+          areaStyle: {
1072
+            color: {
1073
+              type: 'linear',
1074
+              x: 0,
1075
+              y: 0,
1076
+              x2: 0,
1077
+              y2: 1,
1078
+              colorStops: [
1079
+                { offset: 0, color: 'rgba(249, 115, 22, 0.3)' },
1080
+                { offset: 1, color: 'rgba(249, 115, 22, 0.1)' }
1081
+              ]
1082
+            }
1083
+          }
1084
+        }
1085
+      ]
1086
+    })
492 1087
 
493 1088
   } catch (error) {
494 1089
     console.error('加载数据失败:', error)
@@ -793,17 +1388,17 @@ onMounted(() => {
793 1388
   flex: 1;
794 1389
   background: #fff;
795 1390
   border-radius: 6px;
796
-  padding: 10px;
1391
+  padding: 15px;
797 1392
   border: 1px solid #eee;
798 1393
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
799 1394
   display: flex;
800 1395
   flex-direction: column;
801
-  min-height: 200px;
1396
+  min-height: 280px;
802 1397
 }
803 1398
 
804 1399
 .chart-row.full-width .chart-item {
805 1400
   flex: none;
806
-  height: 200px;
1401
+  height: 280px;
807 1402
 }
808 1403
 
809 1404
 .chart-title {

+ 79 - 17
src/views/blockingData/blockingDataScreen/components/ModuleThree.vue

@@ -14,10 +14,12 @@
14 14
 </template>
15 15
 
16 16
 <script setup>
17
-import { ref, onMounted } from 'vue'
17
+import { ref, onMounted, watch } from 'vue'
18 18
 import * as echarts from 'echarts'
19 19
 import ModuleContainer from './ModuleContainer.vue'
20 20
 import { useEcharts } from '@/hooks/chart.js'
21
+import { monthlyAssessment, selfTestHasMissCheck } from '@/api/blockingData/blockingDataScreen.js'
22
+import { formatDateHy } from '@/utils/index.js'
21 23
 
22 24
 // 接收筛选参数
23 25
 const props = defineProps({
@@ -33,8 +35,78 @@ const chart2 = ref(null)
33 35
 const { setOption: setOption1 } = useEcharts(chart1)
34 36
 const { setOption: setOption2 } = useEcharts(chart2)
35 37
 
38
+// 处理filterParams,将dateRange拆分为startTime和endTime
39
+const processFilterParams = (params) => {
40
+  const { dateRange, ...rest } = params
41
+  const processed = { ...rest }
42
+
43
+  if (dateRange && Array.isArray(dateRange) && dateRange.length === 2) {
44
+    processed.startDate = formatDateHy(dateRange[0])
45
+    processed.endDate = formatDateHy(dateRange[1])
46
+  }
47
+  if (processed.brigadeId == 'all') {
48
+    delete processed.brigadeId
49
+  }
50
+  if (processed.terminalId == 'all') {
51
+    delete processed.terminalId
52
+  }
53
+
54
+  return processed
55
+}
56
+
57
+// 数据加载函数
58
+const loadData = async () => {
59
+  try {
60
+    const queryParams = processFilterParams(props.filterParams)
61
+
62
+    // 并行请求培训质控数据
63
+    const [selfTestRes, monthlyAssessmentRes] = await Promise.allSettled([
64
+      selfTestHasMissCheck(queryParams),
65
+      monthlyAssessment(queryParams)
66
+    ])
67
+
68
+    // 查堵-人员自测漏检次数(总累积)
69
+    const selfTestData = selfTestRes?.value?.data || []
70
+    
71
+    if (selfTestData.length > 0) {
72
+      setOption1(ringPieOption(
73
+        selfTestData.map(item => ({
74
+          name: item.name || item.level || '未知',
75
+          value: item.total || item.count || 0
76
+        })),
77
+        ['#3b82f6', '#22c55e', '#f97316', '#cbd5e1']
78
+      ))
79
+    }
80
+
81
+    // 查堵培训-人员月考成绩(总累积)
82
+    const monthlyAssessmentData = monthlyAssessmentRes?.value?.data || []
83
+    if (monthlyAssessmentData.length > 0) {
84
+      setOption2(ringPieOption(
85
+        monthlyAssessmentData.map(item => ({
86
+          name: item.name || item.grade || '未知',
87
+          value: item.total || item.count || 0
88
+        })),
89
+        ['#64748b', '#ef4444', '#22c55e', '#3b82f6', '#cbd5e1']
90
+      ))
91
+    }
92
+
93
+  } catch (error) {
94
+    console.error('加载培训质控数据失败:', error)
95
+  }
96
+}
97
+
98
+// 监听筛选参数变化
99
+watch(() => props.filterParams, () => {
100
+  loadData()
101
+}, { deep: true })
102
+
36 103
 const ringPieOption = (data, colors, centerOffset = ['50%', '55%']) => ({
37 104
   color: colors,
105
+  legend: {
106
+    data: data.map(item => item.name),
107
+    top: '0%',
108
+    textStyle: { fontSize: 10 }
109
+  },
38 110
   series: [{
39 111
     type: 'pie',
40 112
     radius: ['45%', '70%'],
@@ -54,20 +126,7 @@ const ringPieOption = (data, colors, centerOffset = ['50%', '55%']) => ({
54 126
 })
55 127
 
56 128
 onMounted(() => {
57
-  setOption1(ringPieOption([
58
-    { name: '4次', value: 26 },
59
-    { name: '3次', value: 9 },
60
-    { name: '2次', value: 8 },
61
-    { name: '空白', value: 57 }
62
-  ], ['#3b82f6', '#22c55e', '#f97316', '#cbd5e1']))
63
-  
64
-  setOption2(ringPieOption([
65
-    { name: '本月未开展', value: 12 },
66
-    { name: '不合格', value: 8 },
67
-    { name: '合格', value: 13 },
68
-    { name: '优秀', value: 6 },
69
-    { name: '空白', value: 61 }
70
-  ], ['#64748b', '#ef4444', '#22c55e', '#3b82f6', '#cbd5e1']))
129
+  loadData()
71 130
 })
72 131
 </script>
73 132
 
@@ -83,11 +142,13 @@ onMounted(() => {
83 142
   flex: 1;
84 143
   background: #fff;
85 144
   border-radius: 6px;
86
-  padding: 10px;
145
+  padding: 15px;
87 146
   border: 1px solid #eee;
88 147
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
89 148
   display: flex;
90 149
   flex-direction: column;
150
+  min-height: 350px;
151
+  height: 100%;
91 152
 }
92 153
 
93 154
 .chart-title {
@@ -100,6 +161,7 @@ onMounted(() => {
100 161
 .echarts {
101 162
   flex: 1;
102 163
   width: 100%;
103
-  min-height: 0;
164
+  min-height: 300px;
165
+  height: 300px;
104 166
 }
105 167
 </style>

+ 224 - 91
src/views/blockingData/blockingDataScreen/components/ModuleTwo.vue

@@ -44,11 +44,11 @@
44 44
 
45 45
       <div class="extra-row">
46 46
         <div class="chart-card">
47
-          <div class="chart-title">大队开机人员查堵分布</div>
47
+          <div class="chart-title">大队开机人员年限分布</div>
48 48
           <div ref="chart6" class="echarts"></div>
49 49
         </div>
50 50
         <div class="chart-card">
51
-          <div class="chart-title">大队开机人员查堵分布</div>
51
+          <div class="chart-title">大队开机人员证书分布</div>
52 52
           <div ref="chart7" class="echarts"></div>
53 53
         </div>
54 54
       </div>
@@ -57,12 +57,24 @@
57 57
 </template>
58 58
 
59 59
 <script setup>
60
-import { ref, onMounted, reactive } from 'vue'
60
+import { ref, onMounted, reactive, watch } from 'vue'
61 61
 import * as echarts from 'echarts'
62 62
 import ModuleContainer from './ModuleContainer.vue'
63 63
 import RankList from './RankList.vue'
64 64
 import { useEcharts } from '@/hooks/chart.js'
65
-
65
+import {
66
+  supervisorRanking,
67
+  teamLeaderRanking,
68
+  reviewedUserRanking,
69
+  itemLocationDistribution,
70
+  missCheckReasonDistribution,
71
+  brigadeOperatingYearsDistribution,
72
+  brigadeGenderDistribution,
73
+  brigadeDifficultyDistribution,
74
+  brigadeCertificateDistribution,
75
+  tenureListAll
76
+} from '@/api/blockingData/blockingDataScreen.js'
77
+import { formatDateHy } from '@/utils/index.js'
66 78
 // 接收筛选参数
67 79
 const props = defineProps({
68 80
   filterParams: {
@@ -86,50 +98,218 @@ const { setOption: setOption4 } = useEcharts(chart4)
86 98
 const { setOption: setOption5 } = useEcharts(chart5)
87 99
 const { setOption: setOption6 } = useEcharts(chart6)
88 100
 const { setOption: setOption7 } = useEcharts(chart7)
101
+// 处理filterParams,将dateRange拆分为startTime和endTime
102
+const processFilterParams = (params) => {
103
+  const { dateRange, ...rest } = params
104
+  const processed = { ...rest }
105
+
106
+  if (dateRange && Array.isArray(dateRange) && dateRange.length === 2) {
107
+    processed.startTime = formatDateHy(dateRange[0])
108
+    processed.endTime = formatDateHy(dateRange[1])
109
+  }
110
+  if (processed.brigadeId == 'all') {
111
+    delete processed.brigadeId
112
+  }
113
+  if (processed.terminalId == 'all') {
114
+    delete processed.terminalId
115
+  }
116
+
117
+  return processed
118
+}
119
+// 数据加载函数
120
+const loadData = async () => {
121
+  try {
122
+    const queryParams = processFilterParams(props.filterParams)
123
+
124
+    // 排行榜数据
125
+    const [supervisorRankRes, teamLeaderRankRes, reviewedUserRankRes] = await Promise.allSettled([
126
+      supervisorRanking(queryParams),
127
+      teamLeaderRanking(queryParams),
128
+      reviewedUserRanking(queryParams)
129
+    ])
130
+    
131
+    // 更新排行榜数据
132
+    if (supervisorRankRes?.value?.data) {
133
+      rankData1.splice(0, rankData1.length, ...supervisorRankRes.value.data.map((item, index) => ({
134
+        name: item.name || '未知',
135
+        value: item.totalCount || 0
136
+      })))
137
+    }
138
+
139
+    if (teamLeaderRankRes?.value?.data) {
140
+      rankData2.splice(0, rankData2.length, ...teamLeaderRankRes.value.data.map((item, index) => ({
141
+        name: item.name || '未知',
142
+        value: item.totalCount || 0
143
+      })))
144
+    }
145
+
146
+    if (reviewedUserRankRes?.value?.data) {
147
+      rankData3.splice(0, rankData3.length, ...reviewedUserRankRes.value.data.map((item, index) => ({
148
+        name: item.name || '未知',
149
+        value: item.totalCount || 0
150
+      })))
151
+    }
152
+
153
+    // 图表数据
154
+    const [itemLocationRes, missCheckReasonRes, operatingYearsRes, genderRes, difficultyRes, certificateRes, tenureListRes] = await Promise.allSettled([
155
+      itemLocationDistribution(queryParams),
156
+      missCheckReasonDistribution(queryParams),
157
+      brigadeOperatingYearsDistribution(queryParams),
158
+      brigadeGenderDistribution(queryParams),
159
+      brigadeDifficultyDistribution(queryParams),
160
+      brigadeCertificateDistribution(queryParams),
161
+      tenureListAll(queryParams)
162
+    ])
163
+
164
+    // 设置图表数据
165
+    setOption1(pieOption(
166
+      itemLocationRes?.value?.data?.map(item => ({
167
+        name: item.itemLocation || '未知',
168
+        value: item.count || 0
169
+      })) || [],
170
+      ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6', '#64748b']
171
+    ))
172
+
173
+    setOption2(pieOption(
174
+      missCheckReasonRes?.value?.data?.map(item => ({
175
+        name: item.name || item.reason || '未知',
176
+        value: item.value || item.count || 0
177
+      })) || [],
178
+      ['#ef4444', '#f97316', '#eab308', '#22c55e', '#3b82f6', '#64748b'],
179
+      true
180
+    ))
181
+
182
+    setOption3(barOption(
183
+      operatingYearsRes?.value?.data?.map(item => ({
184
+        name: item.operatingYears || '未知',
185
+        value: item.count || 0
186
+      })) || [],
187
+      ['#3b82f6', '#60a5fa', '#93c5fd', '#bfdbfe', '#dbeafe', '#eff6ff']
188
+    ))
189
+
190
+    setOption4(pieOption(
191
+      genderRes?.value?.data?.map(item => ({
192
+        name: item.gender || '未知',
193
+        value: item.count || 0
194
+      })) || [],
195
+      ['#3b82f6', '#ec4899']
196
+    ))
197
+
198
+    setOption5(pieOption(
199
+      difficultyRes?.value?.data?.map(item => ({
200
+        name: item.difficultyLevel || '未知',
201
+        value: item.count || 0
202
+      })) || [],
203
+      ['#93c5fd', '#3b82f6', '#64748b', '#e2e8f0']
204
+    ))
205
+
206
+    // 大队开机人员年限分布
207
+    const tenureListData = tenureListRes?.value?.data || []
208
+    if (tenureListData.length > 0) {
209
+      const categories = [...new Set(tenureListData.map(item => item.brigadeName))]
210
+      const tenureLevels = [
211
+        { name: '1年', key: 'cnt1Years' },
212
+        { name: '2年', key: 'cnt2Years' },
213
+        { name: '3年', key: 'cnt3Years' },
214
+        { name: '4年', key: 'cnt4Years' },
215
+        { name: '5年', key: 'cntGe5Year' }
216
+      ]
217
+      
218
+      const seriesData = tenureLevels.map(level => ({
219
+        name: level.name,
220
+        type: 'bar',
221
+        data: categories.map(brigade => {
222
+          const item = tenureListData.find(d => d.brigadeName === brigade)
223
+          return item ? item[level.key] : 0
224
+        }),
225
+        itemStyle: {
226
+          color: ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6'][tenureLevels.indexOf(level) % 5]
227
+        },
228
+        barWidth: 15
229
+      }))
230
+
231
+      setOption6({
232
+        grid: { left: '15%', top: '10%', right: '5%', bottom: '15%', containLabel: true },
233
+        xAxis: { 
234
+          type: 'category', 
235
+          data: categories, 
236
+          axisLabel: { fontSize: 9 },
237
+          axisTick: {
238
+            alignWithLabel: true
239
+          }
240
+        },
241
+        yAxis: { type: 'value', axisLabel: { fontSize: 9 } },
242
+        legend: { top: 0, right: 10, textStyle: { fontSize: 9 } },
243
+        series: seriesData
244
+      })
245
+    }
246
+
247
+    // 大队开机人员证书分布
248
+    const certificateData = certificateRes?.value?.data || []
249
+    if (certificateData.length > 0) {
250
+      const certificateCategories = ['高级证书', '中级证书', '初级证书']
251
+      const brigadeNames = [...new Set(certificateData.map(item => item.brigadeName))]
252
+      
253
+      const certificateSeriesData = brigadeNames.map((brigadeName, index) => {
254
+        const brigadeData = certificateData.find(d => d.brigadeName === brigadeName)
255
+        return {
256
+          name: brigadeName,
257
+          type: 'bar',
258
+          data: [
259
+            brigadeData?.seniorCount || 0,
260
+            brigadeData?.middleCount || 0,
261
+            brigadeData?.juniorCount || 0
262
+          ],
263
+          itemStyle: {
264
+            color: ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6'][index % 6]
265
+          },
266
+          barWidth: 20
267
+        }
268
+      })
269
+
270
+      setOption7({
271
+        grid: { left: '15%', top: '10%', right: '5%', bottom: '15%', containLabel: true },
272
+        xAxis: { 
273
+          type: 'category', 
274
+          data: certificateCategories, 
275
+          axisLabel: { fontSize: 10 },
276
+          axisLine: { lineStyle: { color: '#999' } }
277
+        },
278
+        yAxis: { 
279
+          type: 'value', 
280
+          axisLabel: { fontSize: 10 },
281
+          axisLine: { lineStyle: { color: '#999' } },
282
+          splitLine: { lineStyle: { color: '#eee' } }
283
+        },
284
+        legend: { 
285
+          top: 0, 
286
+          right: 10, 
287
+          textStyle: { fontSize: 10 } 
288
+        },
289
+        series: certificateSeriesData
290
+      })
291
+    }
292
+
293
+  } catch (error) {
294
+    console.error('加载数据失败:', error)
295
+  }
296
+}
297
+
298
+// 监听筛选参数变化
299
+watch(() => props.filterParams, () => {
300
+  loadData()
301
+}, { deep: true })
89 302
 
90 303
 const rankData1 = reactive([
91
-  { name: '李主管', value: 110 },
92
-  { name: '李海峰', value: 102 },
93
-  { name: '李华波', value: 94 },
94
-  { name: '方星星', value: 80 },
95
-  { name: '郭仁昌', value: 69 },
96
-  { name: '陈晓明', value: 67 },
97
-  { name: '万国', value: 62 },
98
-  { name: '杨高星', value: 60 },
99
-  { name: '符昌国', value: 59 },
100
-  { name: '李星国', value: 57 },
101
-  { name: '周良志', value: 56 },
102
-  { name: '杨旭', value: 53 }
304
+  
103 305
 ])
104 306
 
105 307
 const rankData2 = reactive([
106
-  { name: '李智辉', value: 44 },
107
-  { name: '林明玉', value: 42 },
108
-  { name: '吴帮文', value: 40 },
109
-  { name: '杨玉艳', value: 37 },
110
-  { name: '符玉玲', value: 37 },
111
-  { name: '黄登', value: 36 },
112
-  { name: '梁品俊', value: 35 },
113
-  { name: '陈勤丽', value: 35 },
114
-  { name: '潘政生', value: 34 },
115
-  { name: '周民伦', value: 34 },
116
-  { name: '王明星', value: 33 },
117
-  { name: '林文海', value: 33 }
308
+  
118 309
 ])
119 310
 
120 311
 const rankData3 = reactive([
121
-  { name: '梁翠兰', value: 21 },
122
-  { name: '陈秀强', value: 20 },
123
-  { name: '李国', value: 19 },
124
-  { name: '梁其佐', value: 18 },
125
-  { name: '王名标', value: 18 },
126
-  { name: '李书帆', value: 17 },
127
-  { name: '李亚峰', value: 17 },
128
-  { name: '杨燕钰', value: 16 },
129
-  { name: '刘燕萍', value: 16 },
130
-  { name: '庄伟', value: 16 },
131
-  { name: '黄富伦', value: 15 },
132
-  { name: '唐甸宇', value: 15 }
312
+ 
133 313
 ])
134 314
 
135 315
 const pieOption = (data, colors, isRing = false) => ({
@@ -177,54 +357,7 @@ const stackBarOption = (data, colors) => ({
177 357
 })
178 358
 
179 359
 onMounted(() => {
180
-  setOption1(pieOption([
181
-    { name: '包上', value: 10 },
182
-    { name: '包中', value: 15 },
183
-    { name: '包下', value: 12 },
184
-    { name: '左上', value: 18 },
185
-    { name: '左中', value: 20 },
186
-    { name: '左下', value: 15 },
187
-    { name: '右下', value: 10 }
188
-  ], ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6', '#64748b']))
189
-  setOption2(pieOption([
190
-    { name: '未注意到部位以及未仔细判图', value: 25 },
191
-    { name: '图像特征难以发现', value: 20 },
192
-    { name: '判图难度较大', value: 18 },
193
-    { name: '图像中判清物品', value: 15 },
194
-    { name: '判图不清,未能有效识别物品', value: 12 },
195
-    { name: '其他', value: 10 }
196
-  ], ['#ef4444', '#f97316', '#eab308', '#22c55e', '#3b82f6', '#64748b'], true))
197
-  setOption3(barOption([
198
-    { name: '6年及以上', value: 77 },
199
-    { name: '4-6年', value: 25 },
200
-    { name: '3年', value: 18 },
201
-    { name: '2年', value: 30 },
202
-    { name: '1年', value: 69 },
203
-    { name: '1年以下', value: 12 }
204
-  ], ['#3b82f6', '#60a5fa', '#93c5fd', '#bfdbfe', '#dbeafe', '#eff6ff']))
205
-  setOption4(pieOption([
206
-    { name: '男', value: 63 },
207
-    { name: '女', value: 37 }
208
-  ], ['#3b82f6', '#ec4899']))
209
-  setOption5(pieOption([
210
-    { name: '简单', value: 52 },
211
-    { name: '难', value: 30 },
212
-    { name: '未评判', value: 12 },
213
-    { name: '空白', value: 6 }
214
-  ], ['#93c5fd', '#3b82f6', '#64748b', '#e2e8f0']))
215
-  setOption6(stackBarOption([
216
-    { name: '6年及以上人数', data: [30, 25, 22] },
217
-    { name: '4-6年人数', data: [15, 20, 18] },
218
-    { name: '3年人数', data: [10, 12, 15] },
219
-    { name: '2年人数', data: [12, 15, 13] },
220
-    { name: '1年人数', data: [25, 30, 28] },
221
-    { name: '不足1年人数', data: [5, 8, 10] }
222
-  ], ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6']))
223
-  setOption7(stackBarOption([
224
-    { name: '安检一大队', data: [135, 120] },
225
-    { name: '安检二大队', data: [90, 110] },
226
-    { name: '安检三大队', data: [70, 130] }
227
-  ], ['#3b82f6', '#22c55e', '#f97316']))
360
+  loadData()
228 361
 })
229 362
 </script>
230 363
 
@@ -262,12 +395,12 @@ onMounted(() => {
262 395
   flex: 1;
263 396
   background: #fff;
264 397
   border-radius: 6px;
265
-  padding: 8px;
398
+  padding: 15px;
266 399
   border: 1px solid #eee;
267 400
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
268 401
   display: flex;
269 402
   flex-direction: column;
270
-  min-height: 120px;
403
+  min-height: 280px;
271 404
 }
272 405
 
273 406
 .chart-title {
@@ -324,6 +457,6 @@ onMounted(() => {
324 457
 .echarts {
325 458
   flex: 1;
326 459
   width: 100%;
327
-  min-height: 100px;
460
+  min-height: 250px;
328 461
 }
329 462
 </style>

+ 50 - 7
src/views/blockingData/blockingDataScreen/components/RankList.vue

@@ -6,7 +6,7 @@
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="'rank-item-' + getRankClass(index)">
9
+      <div v-for="(item, index) in rankData" :key="index" class="rank-item" :class="getItemClass(index)">
10 10
         <span class="rank-number" :class="'rank-number-' + getRankClass(index)">NO.{{ index + 1 }}</span>
11 11
         <div class="rank-info">
12 12
           <span class="rank-name">{{ item.name }}</span>
@@ -61,18 +61,25 @@ const getRankClass = (index) => {
61 61
 const getProgressWidth = (value, max) => {
62 62
   return (value / max) * 100
63 63
 }
64
+
65
+const getItemClass = (index) => {
66
+  const baseClass = 'rank-item rank-item-' + getRankClass(index)
67
+  if (index < 3) return baseClass
68
+  const isGray = (index - 3) % 2 === 0
69
+  return baseClass + (isGray ? ' rank-item-gray' : '')
70
+}
64 71
 </script>
65 72
 
66 73
 <style lang="less" scoped>
67 74
 .rank-list-container {
68 75
   width: 100%;
69
-  height: 100%;
76
+  height: 300px; /* 固定高度 */
70 77
   background: #fff;
71 78
   border-radius: 8px;
72 79
   border: 1px solid #e5e7eb;
73
-  // padding: 16px;
74 80
   display: flex;
75 81
   flex-direction: column;
82
+  overflow: hidden; /* 防止容器溢出 */
76 83
 }
77 84
 
78 85
 .rank-list-header {
@@ -80,7 +87,8 @@ const getProgressWidth = (value, max) => {
80 87
   align-items: center;
81 88
   padding: 8px;
82 89
   border-bottom: 1px solid #e5e7eb;
83
-  margin-bottom: 8px;
90
+ 
91
+  flex-shrink: 0; /* 防止头部被压缩 */
84 92
 }
85 93
 
86 94
 .header-label {
@@ -107,15 +115,40 @@ const getProgressWidth = (value, max) => {
107 115
 
108 116
 .rank-list-body {
109 117
   flex: 1;
110
-  overflow-y: auto;
118
+  overflow-y: auto; /* 启用垂直滚动 */
119
+
120
+  
121
+  /* 自定义滚动条样式 */
122
+  &::-webkit-scrollbar {
123
+    width: 6px;
124
+  }
125
+  
126
+  &::-webkit-scrollbar-track {
127
+    background: #f1f1f1;
128
+    border-radius: 3px;
129
+  }
130
+  
131
+  &::-webkit-scrollbar-thumb {
132
+    background: #c1c1c1;
133
+    border-radius: 3px;
134
+  }
135
+  
136
+  &::-webkit-scrollbar-thumb:hover {
137
+    background: #a8a8a8;
138
+  }
111 139
 }
112 140
 
113 141
 .rank-item {
114 142
   display: flex;
115 143
   align-items: center;
116 144
   padding: 10px;
145
+  border-radius: 4px;
146
+  
147
+  min-height: 40px; /* 最小高度确保项目可见 */
148
+}
117 149
 
118
-  // transition: background-color 0.2s;
150
+.rank-item:last-child {
151
+  margin-bottom: 0;
119 152
 }
120 153
 
121 154
 .rank-item-first {
@@ -134,12 +167,18 @@ const getProgressWidth = (value, max) => {
134 167
   background-color: #f9fafb;
135 168
 }
136 169
 
170
+.rank-item-gray {
171
+  background-color: #f3f4f6;
172
+}
173
+
137 174
 .rank-number {
138
-  //width: 80px;
139 175
   font-weight: bold;
140 176
   font-size: 15px;
141 177
   padding: 4px 8px;
142 178
   border-radius: 4px;
179
+  margin-right: 8px;
180
+  min-width: 60px;
181
+  text-align: center;
143 182
 }
144 183
 
145 184
 .rank-number-first {
@@ -175,6 +214,9 @@ const getProgressWidth = (value, max) => {
175 214
   color: #1f2937;
176 215
   font-weight: 500;
177 216
   min-width: 80px;
217
+  overflow: hidden;
218
+  text-overflow: ellipsis;
219
+  white-space: nowrap;
178 220
 }
179 221
 
180 222
 .rank-bar {
@@ -213,5 +255,6 @@ const getProgressWidth = (value, max) => {
213 255
   font-size: 15px;
214 256
   font-weight: 600;
215 257
   color: #1f2937;
258
+  margin-left: 8px;
216 259
 }
217 260
 </style>

+ 18 - 4
src/views/blockingData/blockingDataScreen/index.vue

@@ -38,7 +38,7 @@
38 38
           <span class="filter-label">漏检物品:</span>
39 39
           <el-tree-select v-model="filterParams.missCheckItem" :data="missedItemOptions"
40 40
             :props="{ value: 'id', label: 'name', children: 'children', disabled: data => data.children && data.children.length > 0 }"
41
-            value-key="id" placeholder="请选择漏检物品" check-strictly style="width: 200px" />
41
+            value-key="id" placeholder="请选择漏检物品" check-strictly style="width: 200px" clearable @change="missCheckItemChange"/>
42 42
         </div>
43 43
 
44 44
         <div class="filter-item">
@@ -47,17 +47,17 @@
47 47
             <el-option v-for="item in brigadeOptions" :key="item.value" :label="item.label" :value="item.value" />
48 48
           </el-select>
49 49
         </div>
50
-        <div class="filter-actions">
50
+        <!-- <div class="filter-actions">
51 51
           <el-button type="primary" @click="handleFilter">查询</el-button>
52 52
           <el-button @click="resetFilter">重置</el-button>
53
-        </div>
53
+        </div> -->
54 54
       </div>
55 55
 
56 56
 
57 57
     </div>
58 58
 
59 59
     <div class="screen-content">
60
-      <div v-if="filterParams.brigadeId" class="grid-layout">
60
+      <div v-if="filterParams.brigadeId=='all'" class="grid-layout">
61 61
         <div class="grid-item">
62 62
           <module-one :filter-params="filterParams" />
63 63
         </div>
@@ -131,9 +131,23 @@ function getMissedItemOptions() {
131 131
   })
132 132
 }
133 133
 const terminalChange = (value) => {
134
+  if(!value){
135
+    filterParams.terminal = ''
136
+    filterParams.terminalId = ''
137
+    return;
138
+  }
139
+
134 140
   filterParams.terminal = terminalOptions.value.find(item => item.value === value).label
135 141
 }
136 142
 
143
+const missCheckItemChange = (value) => { 
144
+  if(!value){
145
+    filterParams.missCheckItem = ''
146
+    return;
147
+
148
+  }
149
+}
150
+
137 151
 // 树形数据处理函数
138 152
 function handleTree(data, id, parentId) {
139 153
   const result = []