|
|
@@ -7,7 +7,10 @@
|
|
7
|
7
|
<div class="stat-card-inner">
|
|
8
|
8
|
<div class="stat-left">
|
|
9
|
9
|
<div class="stat-label">查堵总数(所有大队)</div>
|
|
10
|
|
- <div class="stat-value">2327</div>
|
|
|
10
|
+ <div class="stat-content">
|
|
|
11
|
+ <div class="stat-value">{{ totalBlockedCount }}</div>
|
|
|
12
|
+ <div class="stat-unit">起</div>
|
|
|
13
|
+ </div>
|
|
11
|
14
|
</div>
|
|
12
|
15
|
<div class="stat-right">
|
|
13
|
16
|
<div class="stat-title">查堵总数</div>
|
|
|
@@ -19,7 +22,10 @@
|
|
19
|
22
|
<div class="stat-card-inner">
|
|
20
|
23
|
<div class="stat-left">
|
|
21
|
24
|
<div class="stat-label">总查堵万分率</div>
|
|
22
|
|
- <div class="stat-value">2.22</div>
|
|
|
25
|
+ <div class="stat-content">
|
|
|
26
|
+ <div class="stat-value">{{ totalBlockedRate }}</div>
|
|
|
27
|
+ <div class="stat-unit">‱</div>
|
|
|
28
|
+ </div>
|
|
23
|
29
|
</div>
|
|
24
|
30
|
<div class="stat-right">
|
|
25
|
31
|
<div class="stat-title">总查堵万分率</div>
|
|
|
@@ -28,7 +34,7 @@
|
|
28
|
34
|
</div>
|
|
29
|
35
|
</div>
|
|
30
|
36
|
</div>
|
|
31
|
|
-
|
|
|
37
|
+
|
|
32
|
38
|
<!-- 第一行:每日查堵数量 -->
|
|
33
|
39
|
<div class="chart-row">
|
|
34
|
40
|
<div class="chart-item">
|
|
|
@@ -40,7 +46,7 @@
|
|
40
|
46
|
<div ref="chart2" class="echarts"></div>
|
|
41
|
47
|
</div>
|
|
42
|
48
|
</div>
|
|
43
|
|
-
|
|
|
49
|
+
|
|
44
|
50
|
<!-- 第二行:每日查堵万分率 -->
|
|
45
|
51
|
<div class="chart-row">
|
|
46
|
52
|
<div class="chart-item">
|
|
|
@@ -52,7 +58,7 @@
|
|
52
|
58
|
<div ref="chart4" class="echarts"></div>
|
|
53
|
59
|
</div>
|
|
54
|
60
|
</div>
|
|
55
|
|
-
|
|
|
61
|
+
|
|
56
|
62
|
<!-- 第三行:每日过检图像数 -->
|
|
57
|
63
|
<div class="chart-row">
|
|
58
|
64
|
<div class="chart-item">
|
|
|
@@ -64,7 +70,7 @@
|
|
64
|
70
|
<div ref="chart6" class="echarts"></div>
|
|
65
|
71
|
</div>
|
|
66
|
72
|
</div>
|
|
67
|
|
-
|
|
|
73
|
+
|
|
68
|
74
|
<!-- 第四行:查堵物品分布和区域查堵数量分布 -->
|
|
69
|
75
|
<div class="chart-row">
|
|
70
|
76
|
<div class="chart-item">
|
|
|
@@ -76,7 +82,7 @@
|
|
76
|
82
|
<div ref="chart8" class="echarts"></div>
|
|
77
|
83
|
</div>
|
|
78
|
84
|
</div>
|
|
79
|
|
-
|
|
|
85
|
+
|
|
80
|
86
|
<!-- 第五行:查堵类型分布 -->
|
|
81
|
87
|
<div class="chart-row full-width">
|
|
82
|
88
|
<div class="chart-item">
|
|
|
@@ -84,7 +90,7 @@
|
|
84
|
90
|
<div ref="chart9" class="echarts"></div>
|
|
85
|
91
|
</div>
|
|
86
|
92
|
</div>
|
|
87
|
|
-
|
|
|
93
|
+
|
|
88
|
94
|
<!-- 第六行:查堵时间段过检行李数及万分率 -->
|
|
89
|
95
|
<div class="chart-row full-width">
|
|
90
|
96
|
<div class="chart-item">
|
|
|
@@ -92,7 +98,7 @@
|
|
92
|
98
|
<div ref="chart10" class="echarts"></div>
|
|
93
|
99
|
</div>
|
|
94
|
100
|
</div>
|
|
95
|
|
-
|
|
|
101
|
+
|
|
96
|
102
|
<!-- 第七行:每日过检行李数及万分率 -->
|
|
97
|
103
|
<div class="chart-row full-width">
|
|
98
|
104
|
<div class="chart-item">
|
|
|
@@ -100,7 +106,7 @@
|
|
100
|
106
|
<div ref="chart11" class="echarts"></div>
|
|
101
|
107
|
</div>
|
|
102
|
108
|
</div>
|
|
103
|
|
-
|
|
|
109
|
+
|
|
104
|
110
|
<!-- 第八行:查堵-AI复查图像总数 -->
|
|
105
|
111
|
<div class="chart-row full-width">
|
|
106
|
112
|
<div class="chart-item">
|
|
|
@@ -108,7 +114,7 @@
|
|
108
|
114
|
<div ref="chart12" class="echarts"></div>
|
|
109
|
115
|
</div>
|
|
110
|
116
|
</div>
|
|
111
|
|
-
|
|
|
117
|
+
|
|
112
|
118
|
<!-- 第九行:AI漏判和误判图像总数 -->
|
|
113
|
119
|
<div class="chart-row">
|
|
114
|
120
|
<div class="chart-item">
|
|
|
@@ -125,10 +131,31 @@
|
|
125
|
131
|
</template>
|
|
126
|
132
|
|
|
127
|
133
|
<script setup>
|
|
128
|
|
-import { ref, onMounted } from 'vue'
|
|
|
134
|
+import { ref, onMounted, watch } from 'vue'
|
|
129
|
135
|
import * as echarts from 'echarts'
|
|
|
136
|
+import { formatDateHy } from '@/utils/index.js'
|
|
130
|
137
|
import ModuleContainer from './ModuleContainer.vue'
|
|
131
|
138
|
import { useEcharts } from '@/hooks/chart.js'
|
|
|
139
|
+import {
|
|
|
140
|
+ totalCount,
|
|
|
141
|
+ brigadeChart,
|
|
|
142
|
+ totalRate,
|
|
|
143
|
+ brigadeRateChart,
|
|
|
144
|
+ dailyTrend,
|
|
|
145
|
+ dailyBrigadeComparison,
|
|
|
146
|
+ dailyRateTrend,
|
|
|
147
|
+ dailyBrigadeRateComparison,
|
|
|
148
|
+ dailyLuggageTrend,
|
|
|
149
|
+ dailyBrigadeLuggageComparison,
|
|
|
150
|
+ itemDistribution,
|
|
|
151
|
+ areaDistribution,
|
|
|
152
|
+ discriminationDistribution,
|
|
|
153
|
+ timePeriodStats,
|
|
|
154
|
+ dailyLuggageAndRate,
|
|
|
155
|
+ aiImageStats,
|
|
|
156
|
+ aiMissImageStats,
|
|
|
157
|
+ aiErrorImageStats
|
|
|
158
|
+} from '@/api/blockingData/blockingDataScreen.js'
|
|
132
|
159
|
|
|
133
|
160
|
// 接收筛选参数
|
|
134
|
161
|
const props = defineProps({
|
|
|
@@ -137,6 +164,28 @@ const props = defineProps({
|
|
137
|
164
|
default: () => ({})
|
|
138
|
165
|
}
|
|
139
|
166
|
})
|
|
|
167
|
+const totalBlockedCount = ref(0)
|
|
|
168
|
+const totalBlockedRate = ref(0)
|
|
|
169
|
+// 处理filterParams,将dateRange拆分为startTime和endTime
|
|
|
170
|
+const processFilterParams = (params) => {
|
|
|
171
|
+ const { dateRange, ...rest } = params
|
|
|
172
|
+ const processed = { ...rest }
|
|
|
173
|
+
|
|
|
174
|
+ if (dateRange && Array.isArray(dateRange) && dateRange.length === 2) {
|
|
|
175
|
+ processed.startTime = formatDateHy(dateRange[0])
|
|
|
176
|
+ processed.endTime = formatDateHy(dateRange[1])
|
|
|
177
|
+ }
|
|
|
178
|
+ if (processed.brigadeId == 'all') {
|
|
|
179
|
+ delete processed.brigadeId
|
|
|
180
|
+ }
|
|
|
181
|
+ if (processed.terminalId == 'all') {
|
|
|
182
|
+ delete processed.terminalId
|
|
|
183
|
+ }
|
|
|
184
|
+
|
|
|
185
|
+ return processed
|
|
|
186
|
+}
|
|
|
187
|
+
|
|
|
188
|
+
|
|
140
|
189
|
|
|
141
|
190
|
// 图表引用
|
|
142
|
191
|
const chart0a = ref(null)
|
|
|
@@ -174,50 +223,282 @@ const { setOption: setOption12 } = useEcharts(chart12)
|
|
174
|
223
|
const { setOption: setOption13 } = useEcharts(chart13)
|
|
175
|
224
|
const { setOption: setOption14 } = useEcharts(chart14)
|
|
176
|
225
|
|
|
177
|
|
-// 生成模拟数据
|
|
178
|
|
-const generateData = (count, min, max) => {
|
|
179
|
|
- const data = []
|
|
180
|
|
- for (let i = 0; i < count; i++) {
|
|
181
|
|
- data.push(Math.floor(Math.random() * (max - min + 1)) + min)
|
|
|
226
|
+// 加载所有数据
|
|
|
227
|
+const loadData = async () => {
|
|
|
228
|
+ const queryParams = processFilterParams(props.filterParams)
|
|
|
229
|
+
|
|
|
230
|
+ try {
|
|
|
231
|
+ // 统计卡片数据
|
|
|
232
|
+ const [totalCountRes, totalRateRes] = await Promise.allSettled([
|
|
|
233
|
+ totalCount(queryParams),
|
|
|
234
|
+ totalRate(queryParams)
|
|
|
235
|
+ ])
|
|
|
236
|
+
|
|
|
237
|
+ totalBlockedCount.value = totalCountRes.value?.data?.totalBlockedCount || 0
|
|
|
238
|
+ totalBlockedRate.value = totalRateRes.value?.data?.totalBlockedRate || 0
|
|
|
239
|
+
|
|
|
240
|
+
|
|
|
241
|
+ const [brigadeChartRes, brigadeRateRes] = await Promise.allSettled([
|
|
|
242
|
+ brigadeChart(queryParams),
|
|
|
243
|
+ brigadeRateChart(queryParams)
|
|
|
244
|
+ ])
|
|
|
245
|
+
|
|
|
246
|
+ const brigadeChartData = brigadeChartRes.value?.data || []
|
|
|
247
|
+ const brigadeRateData = brigadeRateRes.value?.data || []
|
|
|
248
|
+
|
|
|
249
|
+ const chart0aOption = {
|
|
|
250
|
+ grid: {
|
|
|
251
|
+ left: '5%',
|
|
|
252
|
+ top: '5%',
|
|
|
253
|
+ right: '5%',
|
|
|
254
|
+ bottom: '5%',
|
|
|
255
|
+ containLabel: true
|
|
|
256
|
+ },
|
|
|
257
|
+ xAxis: {
|
|
|
258
|
+ type: 'category',
|
|
|
259
|
+ data: brigadeChartData.map(d => d.brigadeName),
|
|
|
260
|
+ show: true,
|
|
|
261
|
+ axisLine: { lineStyle: { color: '#999' } },
|
|
|
262
|
+ axisLabel: { fontSize: 10, color: '#666' }
|
|
|
263
|
+ },
|
|
|
264
|
+ yAxis: {
|
|
|
265
|
+ type: 'value',
|
|
|
266
|
+ show: true,
|
|
|
267
|
+ axisLine: { lineStyle: { color: '#999' } },
|
|
|
268
|
+ axisLabel: { fontSize: 10, color: '#666' }
|
|
|
269
|
+ },
|
|
|
270
|
+ series: [{
|
|
|
271
|
+ type: 'bar',
|
|
|
272
|
+ data: brigadeChartData.map(d => d.totalBlockedCount),
|
|
|
273
|
+ itemStyle: { color: '#3b82f6' },
|
|
|
274
|
+ barWidth: 12,
|
|
|
275
|
+ label: {
|
|
|
276
|
+ show: true,
|
|
|
277
|
+ position: 'top',
|
|
|
278
|
+ fontSize: 10
|
|
|
279
|
+ }
|
|
|
280
|
+ }]
|
|
|
281
|
+ }
|
|
|
282
|
+
|
|
|
283
|
+ const chart0bOption = {
|
|
|
284
|
+ grid: {
|
|
|
285
|
+ left: '5%',
|
|
|
286
|
+ top: '5%',
|
|
|
287
|
+ right: '5%',
|
|
|
288
|
+ bottom: '5%',
|
|
|
289
|
+ containLabel: true
|
|
|
290
|
+ },
|
|
|
291
|
+ xAxis: {
|
|
|
292
|
+ type: 'value',
|
|
|
293
|
+ show: true,
|
|
|
294
|
+ axisLine: { lineStyle: { color: '#999' } },
|
|
|
295
|
+ axisLabel: { fontSize: 10, color: '#666' }
|
|
|
296
|
+ },
|
|
|
297
|
+ yAxis: {
|
|
|
298
|
+ type: 'category',
|
|
|
299
|
+ data: brigadeRateData.map(d => d.brigadeName),
|
|
|
300
|
+ show: true,
|
|
|
301
|
+ axisLine: { lineStyle: { color: '#999' } },
|
|
|
302
|
+ axisLabel: { fontSize: 10, color: '#666' }
|
|
|
303
|
+ },
|
|
|
304
|
+ series: [{
|
|
|
305
|
+ type: 'bar',
|
|
|
306
|
+ data: brigadeRateData.map(d => d.avgBlockedRate),
|
|
|
307
|
+ itemStyle: { color: '#3b82f6' },
|
|
|
308
|
+ barWidth: 8,
|
|
|
309
|
+ label: {
|
|
|
310
|
+ show: true,
|
|
|
311
|
+ position: 'right',
|
|
|
312
|
+ fontSize: 10
|
|
|
313
|
+ }
|
|
|
314
|
+ }]
|
|
|
315
|
+ }
|
|
|
316
|
+
|
|
|
317
|
+ setOption0a(chart0aOption)
|
|
|
318
|
+ setOption0b(chart0bOption)
|
|
|
319
|
+
|
|
|
320
|
+ // 每日查堵数量
|
|
|
321
|
+ const [dailyTrendRes, dailyBrigadeRes] = await Promise.allSettled([
|
|
|
322
|
+ dailyTrend(queryParams),
|
|
|
323
|
+ dailyBrigadeComparison(queryParams)
|
|
|
324
|
+ ])
|
|
|
325
|
+ console.log(dailyTrendRes, dailyBrigadeRes, 1)
|
|
|
326
|
+
|
|
|
327
|
+ const trendData = dailyTrendRes?.value?.data || []
|
|
|
328
|
+ const brigadeData = dailyBrigadeRes?.value?.data || []
|
|
|
329
|
+
|
|
|
330
|
+ setOption1(lineChartOption(
|
|
|
331
|
+ trendData.map(d => d.totalBlockedCount),
|
|
|
332
|
+ '#3b82f6',
|
|
|
333
|
+ '查堵数量',
|
|
|
334
|
+ trendData.map(d => d.statDate ? d.statDate.split('T')[0] : '')
|
|
|
335
|
+ ))
|
|
|
336
|
+
|
|
|
337
|
+ const uniqueBrigades = [...new Set(brigadeData.map(d => d.brigadeName))]
|
|
|
338
|
+ const allDates = [...new Set(brigadeData.map(d => d.statDate ? d.statDate.split('T')[0] : ''))].sort()
|
|
|
339
|
+ const brigadeDatasets = uniqueBrigades.map(brigadeName => {
|
|
|
340
|
+ return allDates.map(date => {
|
|
|
341
|
+ const found = brigadeData.find(d =>
|
|
|
342
|
+ d.brigadeName === brigadeName && d.statDate && d.statDate.split('T')[0] === date
|
|
|
343
|
+ )
|
|
|
344
|
+ return found ? found.totalBlockedCount : 0
|
|
|
345
|
+ })
|
|
|
346
|
+ })
|
|
|
347
|
+
|
|
|
348
|
+ setOption2(multiLineChartOption(
|
|
|
349
|
+ brigadeDatasets,
|
|
|
350
|
+ ['#3b82f6', '#22c55e', '#f97316'],
|
|
|
351
|
+ uniqueBrigades,
|
|
|
352
|
+ allDates
|
|
|
353
|
+ ))
|
|
|
354
|
+
|
|
|
355
|
+ // 每日查堵万分率
|
|
|
356
|
+ const [dailyRateTrendRes, dailyBrigadeRateRes] = await Promise.allSettled([
|
|
|
357
|
+ dailyRateTrend(queryParams),
|
|
|
358
|
+ dailyBrigadeRateComparison(queryParams)
|
|
|
359
|
+ ])
|
|
|
360
|
+ console.log(dailyRateTrendRes, dailyBrigadeRateRes, 3)
|
|
|
361
|
+ const rateTrendData = dailyRateTrendRes?.value?.data || []
|
|
|
362
|
+ const rateBrigadeData = dailyBrigadeRateRes?.value?.data || []
|
|
|
363
|
+
|
|
|
364
|
+ setOption3(lineChartOption(
|
|
|
365
|
+ rateTrendData.map(d => d.avgBlockedRate),
|
|
|
366
|
+ '#ec4899',
|
|
|
367
|
+ '万分率',
|
|
|
368
|
+ rateTrendData.map(d => d.statDate ? d.statDate.split('T')[0] : '')
|
|
|
369
|
+ ))
|
|
|
370
|
+
|
|
|
371
|
+ const rateAllDates = [...new Set(rateBrigadeData.map(d => d.statDate ? d.statDate.split('T')[0] : ''))].sort()
|
|
|
372
|
+ const rateUniqueBrigades = [...new Set(rateBrigadeData.map(d => d.brigadeName))]
|
|
|
373
|
+ const rateBrigadeDatasets = rateUniqueBrigades.map(brigadeName => {
|
|
|
374
|
+ return rateAllDates.map(date => {
|
|
|
375
|
+ const found = rateBrigadeData.find(d =>
|
|
|
376
|
+ d.brigadeName === brigadeName && d.statDate && d.statDate.split('T')[0] === date
|
|
|
377
|
+ )
|
|
|
378
|
+ return found ? found.avgBlockedRate : 0
|
|
|
379
|
+ })
|
|
|
380
|
+ })
|
|
|
381
|
+
|
|
|
382
|
+ setOption4(multiLineChartOption(
|
|
|
383
|
+ rateBrigadeDatasets,
|
|
|
384
|
+ ['#3b82f6', '#22c55e', '#f97316'],
|
|
|
385
|
+ rateUniqueBrigades,
|
|
|
386
|
+ rateAllDates
|
|
|
387
|
+ ))
|
|
|
388
|
+
|
|
|
389
|
+ // 每日过检图像数
|
|
|
390
|
+ const [dailyLuggageTrendRes, dailyBrigadeLuggageRes] = await Promise.allSettled([
|
|
|
391
|
+ dailyLuggageTrend(queryParams),
|
|
|
392
|
+ dailyBrigadeLuggageComparison(queryParams)
|
|
|
393
|
+ ])
|
|
|
394
|
+
|
|
|
395
|
+ const luggageTrendData = dailyLuggageTrendRes?.value?.data || []
|
|
|
396
|
+ const luggageBrigadeData = dailyBrigadeLuggageRes?.value?.data || []
|
|
|
397
|
+ console.log(luggageTrendData, luggageBrigadeData, 555)
|
|
|
398
|
+ if (luggageTrendData.length > 0) {
|
|
|
399
|
+ setOption5(lineChartOption(
|
|
|
400
|
+ luggageTrendData.map(d => d.totalLuggageCount),
|
|
|
401
|
+ '#8b5cf6',
|
|
|
402
|
+ '过检图像数',
|
|
|
403
|
+ luggageTrendData.map(d => d.statDate ? d.statDate.split('T')[0] : '')
|
|
|
404
|
+ ))
|
|
|
405
|
+ }
|
|
|
406
|
+
|
|
|
407
|
+
|
|
|
408
|
+ const luggageAllDates = [...new Set(luggageBrigadeData.map(d => d.statDate ? d.statDate.split('T')[0] : ''))].sort()
|
|
|
409
|
+ const luggageUniqueBrigades = [...new Set(luggageBrigadeData.map(d => d.brigadeName))]
|
|
|
410
|
+ const luggageBrigadeDatasets = luggageUniqueBrigades.map(brigadeName => {
|
|
|
411
|
+ return luggageAllDates.map(date => {
|
|
|
412
|
+ const found = luggageBrigadeData.find(d =>
|
|
|
413
|
+ d.brigadeName === brigadeName && d.statDate && d.statDate.split('T')[0] === date
|
|
|
414
|
+ )
|
|
|
415
|
+ return found ? found.totalLuggageCount : 0
|
|
|
416
|
+ })
|
|
|
417
|
+ })
|
|
|
418
|
+ if (luggageBrigadeDatasets.length > 0) {
|
|
|
419
|
+ setOption6(multiLineChartOption(
|
|
|
420
|
+ luggageBrigadeDatasets,
|
|
|
421
|
+ ['#3b82f6', '#22c55e', '#f97316'],
|
|
|
422
|
+ luggageUniqueBrigades,
|
|
|
423
|
+ luggageAllDates
|
|
|
424
|
+ ))
|
|
|
425
|
+ }
|
|
|
426
|
+
|
|
|
427
|
+ // 查堵物品分布和区域查堵数量分布
|
|
|
428
|
+ const [itemDistRes, areaDistRes] = await Promise.allSettled([
|
|
|
429
|
+ itemDistribution(queryParams),
|
|
|
430
|
+ areaDistribution(queryParams)
|
|
|
431
|
+ ])
|
|
|
432
|
+ console.log(itemDistRes, areaDistRes, 55555)
|
|
|
433
|
+ const itemDistData = itemDistRes?.value?.data || []
|
|
|
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
|
+ ))
|
|
|
439
|
+ }
|
|
|
440
|
+
|
|
|
441
|
+
|
|
|
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
|
+ }
|
|
|
449
|
+
|
|
|
450
|
+
|
|
|
451
|
+ // 查堵类型分布
|
|
|
452
|
+ const discDistRes = await discriminationDistribution(queryParams)
|
|
|
453
|
+ const discDistData = discDistRes.data || []
|
|
|
454
|
+ setOption9(barChartOption(discDistData, '#3b82f6'))
|
|
|
455
|
+
|
|
|
456
|
+ // 查堵时间段过检行李数及万分率
|
|
|
457
|
+ const timePeriodRes = await timePeriodStats(queryParams)
|
|
|
458
|
+ const timePeriodData = timePeriodRes.data || []
|
|
|
459
|
+ setOption10(dualAxisBarChartOption(
|
|
|
460
|
+ timePeriodData.map(d => d.luggageCount),
|
|
|
461
|
+ timePeriodData.map(d => d.rate),
|
|
|
462
|
+ '#3b82f6',
|
|
|
463
|
+ '#ec4899'
|
|
|
464
|
+ ))
|
|
|
465
|
+
|
|
|
466
|
+ // 每日过检行李数及万分率
|
|
|
467
|
+ const dailyLuggageRes = await dailyLuggageAndRate(queryParams)
|
|
|
468
|
+ const dailyLuggageData = dailyLuggageRes.data || []
|
|
|
469
|
+ setOption11(dualAxisBarChartOption(
|
|
|
470
|
+ dailyLuggageData.map(d => d.luggageCount),
|
|
|
471
|
+ dailyLuggageData.map(d => d.rate),
|
|
|
472
|
+ '#3b82f6',
|
|
|
473
|
+ '#ec4899'
|
|
|
474
|
+ ))
|
|
|
475
|
+
|
|
|
476
|
+ // AI复查图像总数
|
|
|
477
|
+ const aiImageRes = await aiImageStats(queryParams)
|
|
|
478
|
+ const aiImageData = aiImageRes.data || []
|
|
|
479
|
+ setOption12(lineChartOption(aiImageData.map(d => d.value), '#14b8a6', 'AI复查图像数'))
|
|
|
480
|
+
|
|
|
481
|
+ // AI漏判和误判图像总数
|
|
|
482
|
+ const [aiMissRes, aiErrorRes] = await Promise.all([
|
|
|
483
|
+ aiMissImageStats(queryParams),
|
|
|
484
|
+ aiErrorImageStats(queryParams)
|
|
|
485
|
+ ])
|
|
|
486
|
+
|
|
|
487
|
+ const aiMissData = aiMissRes.data || []
|
|
|
488
|
+ const aiErrorData = aiErrorRes.data || []
|
|
|
489
|
+
|
|
|
490
|
+ setOption13(lineChartOption(aiMissData.map(d => d.value), '#ef4444', 'AI漏判图像数'))
|
|
|
491
|
+ setOption14(lineChartOption(aiErrorData.map(d => d.value), '#f97316', 'AI误判图像数'))
|
|
|
492
|
+
|
|
|
493
|
+ } catch (error) {
|
|
|
494
|
+ console.error('加载数据失败:', error)
|
|
182
|
495
|
}
|
|
183
|
|
- return data
|
|
184
|
496
|
}
|
|
185
|
497
|
|
|
186
|
|
-// X轴数据(30天)
|
|
187
|
|
-const xAxisData = []
|
|
188
|
|
-for (let i = 1; i <= 30; i++) {
|
|
189
|
|
- xAxisData.push(i + '日')
|
|
190
|
|
-}
|
|
191
|
498
|
|
|
192
|
|
-// 小型柱状图配置(用于统计卡片)
|
|
193
|
|
-const smallBarOption = (data, color, isHorizontal = false) => ({
|
|
194
|
|
- grid: {
|
|
195
|
|
- left: '5%',
|
|
196
|
|
- top: '5%',
|
|
197
|
|
- right: '5%',
|
|
198
|
|
- bottom: '5%',
|
|
199
|
|
- containLabel: true
|
|
200
|
|
- },
|
|
201
|
|
- xAxis: {
|
|
202
|
|
- type: isHorizontal ? 'value' : 'category',
|
|
203
|
|
- data: isHorizontal ? null : ['1', '2', '3'],
|
|
204
|
|
- show: false
|
|
205
|
|
- },
|
|
206
|
|
- yAxis: {
|
|
207
|
|
- type: isHorizontal ? 'category' : 'value',
|
|
208
|
|
- data: isHorizontal ? ['1', '2', '3'] : null,
|
|
209
|
|
- show: false
|
|
210
|
|
- },
|
|
211
|
|
- series: [{
|
|
212
|
|
- type: 'bar',
|
|
213
|
|
- data: data,
|
|
214
|
|
- itemStyle: { color: color },
|
|
215
|
|
- barWidth: isHorizontal ? 8 : 12
|
|
216
|
|
- }]
|
|
217
|
|
-})
|
|
218
|
499
|
|
|
219
|
500
|
// 折线图配置(总表)
|
|
220
|
|
-const lineChartOption = (data, color, title) => ({
|
|
|
501
|
+const lineChartOption = (data, color, title, xAxisData = []) => ({
|
|
221
|
502
|
grid: {
|
|
222
|
503
|
left: '10%',
|
|
223
|
504
|
top: '15%',
|
|
|
@@ -229,7 +510,7 @@ const lineChartOption = (data, color, title) => ({
|
|
229
|
510
|
type: 'category',
|
|
230
|
511
|
data: xAxisData,
|
|
231
|
512
|
axisLine: { lineStyle: { color: '#999' } },
|
|
232
|
|
- axisLabel: { fontSize: 10, color: '#666' }
|
|
|
513
|
+ axisLabel: { fontSize: 10, color: '#666', rotate: 0 }
|
|
233
|
514
|
},
|
|
234
|
515
|
yAxis: {
|
|
235
|
516
|
type: 'value',
|
|
|
@@ -250,7 +531,7 @@ const lineChartOption = (data, color, title) => ({
|
|
250
|
531
|
})
|
|
251
|
532
|
|
|
252
|
533
|
// 多折线图配置(大队对比)
|
|
253
|
|
-const multiLineChartOption = (dataList, colors, legends) => ({
|
|
|
534
|
+const multiLineChartOption = (dataList, colors, legends, xAxisData = []) => ({
|
|
254
|
535
|
grid: {
|
|
255
|
536
|
left: '10%',
|
|
256
|
537
|
top: '15%',
|
|
|
@@ -262,7 +543,7 @@ const multiLineChartOption = (dataList, colors, legends) => ({
|
|
262
|
543
|
type: 'category',
|
|
263
|
544
|
data: xAxisData,
|
|
264
|
545
|
axisLine: { lineStyle: { color: '#999' } },
|
|
265
|
|
- axisLabel: { fontSize: 10, color: '#666' }
|
|
|
546
|
+ axisLabel: { fontSize: 10, color: '#666', rotate: 0 }
|
|
266
|
547
|
},
|
|
267
|
548
|
yAxis: {
|
|
268
|
549
|
type: 'value',
|
|
|
@@ -349,7 +630,7 @@ const dualAxisBarChartOption = (data1, data2, color1, color2) => ({
|
|
349
|
630
|
},
|
|
350
|
631
|
xAxis: {
|
|
351
|
632
|
type: 'category',
|
|
352
|
|
- data: xAxisData,
|
|
|
633
|
+ data: [],
|
|
353
|
634
|
axisLine: { lineStyle: { color: '#999' } },
|
|
354
|
635
|
axisLabel: { fontSize: 10, color: '#666' }
|
|
355
|
636
|
},
|
|
|
@@ -387,76 +668,15 @@ const dualAxisBarChartOption = (data1, data2, color1, color2) => ({
|
|
387
|
668
|
]
|
|
388
|
669
|
})
|
|
389
|
670
|
|
|
|
671
|
+
|
|
|
672
|
+
|
|
|
673
|
+// 监听filterParams变化
|
|
|
674
|
+watch(() => props.filterParams, () => {
|
|
|
675
|
+ loadData()
|
|
|
676
|
+}, { deep: true })
|
|
|
677
|
+
|
|
390
|
678
|
onMounted(() => {
|
|
391
|
|
- // 统计卡片图表
|
|
392
|
|
- setOption0a(smallBarOption([120, 80, 150], '#3b82f6'))
|
|
393
|
|
- setOption0b(smallBarOption([2.2, 1.8, 2.5], '#3b82f6', true))
|
|
394
|
|
-
|
|
395
|
|
- // 第一行:每日查堵数量
|
|
396
|
|
- setOption1(lineChartOption(generateData(30, 50, 150), '#3b82f6', '查堵数量'))
|
|
397
|
|
- setOption2(multiLineChartOption(
|
|
398
|
|
- [generateData(30, 20, 80), generateData(30, 15, 70), generateData(30, 10, 60)],
|
|
399
|
|
- ['#3b82f6', '#22c55e', '#f97316'],
|
|
400
|
|
- ['安检一大队', '安检二大队', '安检三大队']
|
|
401
|
|
- ))
|
|
402
|
|
-
|
|
403
|
|
- // 第二行:每日查堵万分率
|
|
404
|
|
- setOption3(lineChartOption(generateData(30, 0.5, 3.5), '#ec4899', '万分率'))
|
|
405
|
|
- setOption4(multiLineChartOption(
|
|
406
|
|
- [generateData(30, 0.3, 2.5), generateData(30, 0.2, 2.0), generateData(30, 0.1, 1.8)],
|
|
407
|
|
- ['#3b82f6', '#22c55e', '#f97316'],
|
|
408
|
|
- ['安检一大队', '安检二大队', '安检三大队']
|
|
409
|
|
- ))
|
|
410
|
|
-
|
|
411
|
|
- // 第三行:每日过检图像数
|
|
412
|
|
- setOption5(lineChartOption(generateData(30, 1000, 5000), '#8b5cf6', '过检图像数'))
|
|
413
|
|
- setOption6(multiLineChartOption(
|
|
414
|
|
- [generateData(30, 400, 2000), generateData(30, 300, 1800), generateData(30, 200, 1600)],
|
|
415
|
|
- ['#3b82f6', '#22c55e', '#f97316'],
|
|
416
|
|
- ['安检一大队', '安检二大队', '安检三大队']
|
|
417
|
|
- ))
|
|
418
|
|
-
|
|
419
|
|
- // 第四行:查堵物品分布和区域查堵数量分布
|
|
420
|
|
- setOption7(ringPieOption([
|
|
421
|
|
- { name: '刀具', value: 25 },
|
|
422
|
|
- { name: '液体', value: 20 },
|
|
423
|
|
- { name: '电子产品', value: 18 },
|
|
424
|
|
- { name: '其他', value: 15 },
|
|
425
|
|
- { name: '打火机', value: 12 },
|
|
426
|
|
- { name: '化妆品', value: 10 }
|
|
427
|
|
- ], ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6']))
|
|
428
|
|
-
|
|
429
|
|
- setOption8(barChartOption([
|
|
430
|
|
- { name: 'T1国内', value: 120 },
|
|
431
|
|
- { name: 'T1国际', value: 80 },
|
|
432
|
|
- { name: 'T2国内', value: 150 },
|
|
433
|
|
- { name: 'T2国际', value: 60 },
|
|
434
|
|
- { name: '中转区', value: 40 }
|
|
435
|
|
- ], '#3b82f6', true))
|
|
436
|
|
-
|
|
437
|
|
- // 第五行:查堵类型分布
|
|
438
|
|
- setOption9(barChartOption(generateData(6, 20, 100), '#3b82f6'))
|
|
439
|
|
-
|
|
440
|
|
- // 第六行:查堵时间段过检行李数及万分率
|
|
441
|
|
- setOption10(dualAxisBarChartOption(
|
|
442
|
|
- generateData(24, 100, 500), // 行李数
|
|
443
|
|
- generateData(24, 0.5, 3.0), // 万分率
|
|
444
|
|
- '#3b82f6', '#ec4899'
|
|
445
|
|
- ))
|
|
446
|
|
-
|
|
447
|
|
- // 第七行:每日过检行李数及万分率
|
|
448
|
|
- setOption11(dualAxisBarChartOption(
|
|
449
|
|
- generateData(30, 1000, 5000), // 行李数
|
|
450
|
|
- generateData(30, 0.5, 3.5), // 万分率
|
|
451
|
|
- '#3b82f6', '#ec4899'
|
|
452
|
|
- ))
|
|
453
|
|
-
|
|
454
|
|
- // 第八行:查堵-AI复查图像总数
|
|
455
|
|
- setOption12(lineChartOption(generateData(30, 50, 200), '#14b8a6', 'AI复查图像数'))
|
|
456
|
|
-
|
|
457
|
|
- // 第九行:AI漏判和误判图像总数
|
|
458
|
|
- setOption13(lineChartOption(generateData(30, 5, 30), '#ef4444', 'AI漏判图像数'))
|
|
459
|
|
- setOption14(lineChartOption(generateData(30, 3, 25), '#f97316', 'AI误判图像数'))
|
|
|
679
|
+ loadData()
|
|
460
|
680
|
})
|
|
461
|
681
|
</script>
|
|
462
|
682
|
|
|
|
@@ -467,7 +687,7 @@ onMounted(() => {
|
|
467
|
687
|
flex-direction: column;
|
|
468
|
688
|
gap: 10px;
|
|
469
|
689
|
overflow-y: auto;
|
|
470
|
|
-
|
|
|
690
|
+
|
|
471
|
691
|
}
|
|
472
|
692
|
|
|
473
|
693
|
.stats-row {
|
|
|
@@ -478,11 +698,8 @@ onMounted(() => {
|
|
478
|
698
|
|
|
479
|
699
|
.stat-card {
|
|
480
|
700
|
flex: 1;
|
|
481
|
|
- background: #fff;
|
|
482
|
|
- border-radius: 6px;
|
|
483
|
|
- padding: 15px;
|
|
484
|
|
- border: 1px solid #eee;
|
|
485
|
|
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
|
701
|
+
|
|
|
702
|
+
|
|
486
|
703
|
min-height: 120px;
|
|
487
|
704
|
}
|
|
488
|
705
|
|
|
|
@@ -490,47 +707,76 @@ onMounted(() => {
|
|
490
|
707
|
display: flex;
|
|
491
|
708
|
height: 100%;
|
|
492
|
709
|
align-items: center;
|
|
|
710
|
+ gap: 10px;
|
|
|
711
|
+
|
|
493
|
712
|
}
|
|
494
|
713
|
|
|
495
|
714
|
.stat-left {
|
|
|
715
|
+ background: #fff;
|
|
|
716
|
+ border-radius: 6px;
|
|
|
717
|
+ border: 1px solid #eee;
|
|
|
718
|
+ padding: 15px;
|
|
|
719
|
+ height: 100%;
|
|
|
720
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
496
|
721
|
flex: 1;
|
|
497
|
722
|
display: flex;
|
|
|
723
|
+ align-items: center;
|
|
498
|
724
|
flex-direction: column;
|
|
499
|
|
- align-items: flex-start;
|
|
|
725
|
+
|
|
500
|
726
|
padding-right: 15px;
|
|
501
|
727
|
}
|
|
502
|
728
|
|
|
503
|
729
|
.stat-label {
|
|
504
|
|
- font-size: 12px;
|
|
505
|
|
- color: #666;
|
|
|
730
|
+ width: 100%;
|
|
|
731
|
+ font-size: 17px;
|
|
|
732
|
+ color: black;
|
|
506
|
733
|
margin-bottom: 8px;
|
|
507
|
734
|
}
|
|
508
|
735
|
|
|
|
736
|
+.stat-content {
|
|
|
737
|
+ margin-top: 15%;
|
|
|
738
|
+ display: flex;
|
|
|
739
|
+ flex-direction: column;
|
|
|
740
|
+ align-items: center;
|
|
|
741
|
+}
|
|
|
742
|
+
|
|
509
|
743
|
.stat-value {
|
|
|
744
|
+ font-size: 80px;
|
|
|
745
|
+ font-weight: bold;
|
|
|
746
|
+ color: #3b82f6;
|
|
|
747
|
+
|
|
|
748
|
+}
|
|
|
749
|
+
|
|
|
750
|
+.stat-unit {
|
|
510
|
751
|
font-size: 24px;
|
|
511
|
752
|
font-weight: bold;
|
|
512
|
753
|
color: #3b82f6;
|
|
513
|
754
|
}
|
|
514
|
755
|
|
|
515
|
756
|
.stat-right {
|
|
|
757
|
+ background: #fff;
|
|
|
758
|
+ border-radius: 6px;
|
|
|
759
|
+ border: 1px solid #eee;
|
|
|
760
|
+ padding: 15px;
|
|
|
761
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
516
|
762
|
flex: 1;
|
|
517
|
763
|
display: flex;
|
|
518
|
764
|
flex-direction: column;
|
|
519
|
|
- align-items: flex-end;
|
|
|
765
|
+ align-items: flex-start;
|
|
520
|
766
|
height: 100%;
|
|
521
|
767
|
}
|
|
522
|
768
|
|
|
523
|
769
|
.stat-title {
|
|
524
|
|
- font-size: 12px;
|
|
525
|
|
- color: #333;
|
|
|
770
|
+ font-size: 17px;
|
|
|
771
|
+ color: black;
|
|
526
|
772
|
margin-bottom: 5px;
|
|
527
|
773
|
font-weight: 500;
|
|
528
|
774
|
}
|
|
529
|
775
|
|
|
530
|
776
|
.echarts-small {
|
|
531
|
777
|
width: 100%;
|
|
532
|
|
- height: 60px;
|
|
533
|
|
- min-height: 60px;
|
|
|
778
|
+ height: 300px;
|
|
|
779
|
+ min-height: 300px;
|
|
534
|
780
|
}
|
|
535
|
781
|
|
|
536
|
782
|
.chart-row {
|