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

Merge branch 'blockingData'

# Conflicts:
#	src/api/system/user.js
#	src/hooks/chart.js
huoyi пре 1 месец
родитељ
комит
b4f57ccc89
33 измењених фајлова са 7320 додато и 15 уклоњено
  1. 406 0
      src/api/blockingData/blockingDataScreen.js
  2. 62 0
      src/api/blockingData/dailyLuggageCheckInList.js
  3. 62 0
      src/api/blockingData/dailyLuggageInspectionScheduleByTime.js
  4. 19 0
      src/api/blockingData/experienceDistributionStartupPerson.js
  5. 62 0
      src/api/blockingData/missedInspection.js
  6. 62 0
      src/api/blockingData/rateList.js
  7. 9 0
      src/api/system/user.js
  8. 2 2
      src/router/index.js
  9. 1 1
      src/utils/dict.js
  10. 16 8
      src/utils/index.js
  11. 428 0
      src/views/blockingData/blockingDataScreen/components/ModuleBrigadeOne.vue
  12. 169 0
      src/views/blockingData/blockingDataScreen/components/ModuleBrigadeThree.vue
  13. 471 0
      src/views/blockingData/blockingDataScreen/components/ModuleBrigadeTwo.vue
  14. 57 0
      src/views/blockingData/blockingDataScreen/components/ModuleContainer.vue
  15. 419 0
      src/views/blockingData/blockingDataScreen/components/ModuleFour.vue
  16. 1432 0
      src/views/blockingData/blockingDataScreen/components/ModuleOne.vue
  17. 167 0
      src/views/blockingData/blockingDataScreen/components/ModuleThree.vue
  18. 457 0
      src/views/blockingData/blockingDataScreen/components/ModuleTwo.vue
  19. 283 0
      src/views/blockingData/blockingDataScreen/components/RankList.vue
  20. 347 0
      src/views/blockingData/blockingDataScreen/index.vue
  21. 0 0
      src/views/blockingData/blockingDataScreen/查堵大屏
  22. 547 0
      src/views/blockingData/dailyLuggageCheckInList/index.vue
  23. 0 0
      src/views/blockingData/dailyLuggageCheckInList/每日行李过检查堵表
  24. 427 0
      src/views/blockingData/dailyLuggageInspectionScheduleByTime/index.vue
  25. 0 0
      src/views/blockingData/dailyLuggageInspectionScheduleByTime/每日各时段查堵行李表
  26. 171 0
      src/views/blockingData/experienceDistributionStartupPerson/index.vue
  27. 0 0
      src/views/blockingData/experienceDistributionStartupPerson/开机人员年限分布
  28. 762 0
      src/views/blockingData/missedInspectionList/index.vue
  29. 0 0
      src/views/blockingData/missedInspectionList/漏检统计表
  30. 396 0
      src/views/blockingData/rateList/index.vue
  31. 0 0
      src/views/blockingData/rateList/速率表
  32. 14 0
      src/views/system/dept/index.vue
  33. 72 4
      src/views/system/user/components/UserInfoEdit.vue

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

@@ -0,0 +1,406 @@
1
+import request from '@/utils/request'
2
+
3
+
4
+//查询查堵总数
5
+export function totalCount(query) {
6
+    return request({
7
+        url: '/blocked/dashboard/totalCount',
8
+        method: 'get',
9
+        params: query
10
+    })
11
+}
12
+
13
+// 查询查堵总数柱状图
14
+export function brigadeChart(query) {
15
+    return request({
16
+        url: '/blocked/dashboard/brigadeChart',
17
+        method: 'get',
18
+        params: query
19
+    })
20
+}
21
+
22
+//查询总查堵万分率
23
+export function totalRate(query) {
24
+    return request({
25
+        url: '/blocked/dashboard/totalRate',
26
+        method: 'get',
27
+        params: query
28
+    })
29
+}
30
+//查询查堵万分率条形图
31
+export function brigadeRateChart(query) {
32
+    return request({
33
+        url: '/blocked/dashboard/brigadeRateChart',
34
+        method: 'get',
35
+        params: query
36
+    })
37
+}
38
+//每日查堵数量(总表)
39
+export function dailyTrend(query) {
40
+    return request({
41
+        url: '/blocked/dashboard/dailyTrend',
42
+        method: 'get',
43
+        params: query
44
+    })
45
+}
46
+//每日查堵数量(大队对比)
47
+export function dailyBrigadeComparison(query) {
48
+    return request({
49
+        url: '/blocked/dashboard/dailyBrigadeComparison',
50
+        method: 'get',
51
+        params: query
52
+    })
53
+}
54
+//每日查堵万分率(总表)
55
+export function dailyRateTrend(query) {
56
+    return request({
57
+        url: '/blocked/dashboard/dailyRateTrend',
58
+        method: 'get',
59
+        params: query
60
+    })
61
+}
62
+//每日查堵万分率(大队对比)
63
+export function dailyBrigadeRateComparison(query) {
64
+    return request({
65
+        url: '/blocked/dashboard/dailyBrigadeRateComparison',
66
+        method: 'get',
67
+        params: query
68
+    })
69
+}
70
+//每日过检图像数(行检+旅检总表)
71
+export function dailyLuggageTrend(query) {
72
+    return request({
73
+        url: '/blocked/dashboard/dailyLuggageTrend',
74
+        method: 'get',
75
+        params: query
76
+    })
77
+}
78
+
79
+//每日过检图像数(行检+旅检 大队对比)
80
+export function dailyBrigadeLuggageComparison(query) {
81
+    return request({
82
+        url: '/blocked/dashboard/dailyBrigadeLuggageComparison',
83
+        method: 'get',
84
+        params: query
85
+    })
86
+}
87
+//查堵物品分布
88
+export function itemDistribution(query) {
89
+    return request({
90
+        url: '/blocked/dashboard/itemDistribution',
91
+        method: 'get',
92
+        params: query
93
+    })
94
+}
95
+//区域查堵数量分布
96
+export function areaDistribution(query) {
97
+    return request({
98
+        url: '/blocked/dashboard/areaDistribution',
99
+        method: 'get',
100
+        params: query
101
+    })
102
+}
103
+//查堵类型分布
104
+export function discriminationDistribution(query) {
105
+    return request({
106
+        url: '/blocked/dashboard/discriminationDistribution',
107
+        method: 'get',
108
+        params: query
109
+    })
110
+}
111
+//查堵时间段过检行李数及万分率
112
+export function timePeriodStats(query) {
113
+    return request({
114
+        url: '/blocked/dashboard/timePeriodStats',
115
+        method: 'get',
116
+        params: query
117
+    })
118
+}
119
+//每日过检行李数及万分率
120
+export function dailyLuggageAndRate(query) {
121
+    return request({
122
+        url: '/blocked/dashboard/dailyLuggageAndRate',
123
+        method: 'get',
124
+        params: query
125
+    })
126
+}
127
+//查堵-AI复查图像总数
128
+export function aiImageStats(query) {
129
+    return request({
130
+        url: '/blocked/dashboard/aiImageStats',
131
+        method: 'get',
132
+        params: query
133
+    })
134
+}
135
+//查堵-AI漏判图像总数
136
+export function aiMissImageStats(query) {
137
+    return request({
138
+        url: '/blocked/dashboard/aiMissImageStats',
139
+        method: 'get',
140
+        params: query
141
+    })
142
+}
143
+//查堵-AI误判图像总数
144
+export function aiErrorImageStats(query) {
145
+    return request({
146
+        url: '/blocked/dashboard/aiErrorImageStats',
147
+        method: 'get',
148
+        params: query
149
+    })
150
+}
151
+//查堵-主管排行榜
152
+export function supervisorRanking(query) {
153
+    return request({
154
+        url: '/blocked/dashboard/supervisorRanking',
155
+        method: 'get',
156
+        params: query
157
+    })
158
+}
159
+//查堵-班组排行榜
160
+export function teamLeaderRanking(query) {
161
+    return request({
162
+        url: '/blocked/dashboard/teamLeaderRanking',
163
+        method: 'get',
164
+        params: query
165
+    })
166
+}
167
+//查堵-人员排行榜
168
+export function reviewedUserRanking(query) {
169
+    return request({
170
+        url: '/blocked/dashboard/reviewedUserRanking',
171
+        method: 'get',
172
+        params: query
173
+    })
174
+}
175
+//查堵-物品位置分布
176
+export function itemLocationDistribution(query) {
177
+    return request({
178
+        url: '/blocked/dashboard/itemLocationDistribution',
179
+        method: 'get',
180
+        params: query
181
+    })
182
+}
183
+//查堵-原因分类分布
184
+export function missCheckReasonDistribution(query) {
185
+    return request({
186
+        url: '/blocked/dashboard/missCheckReasonDistribution',
187
+        method: 'get',
188
+        params: query
189
+    })
190
+}
191
+//查堵-图像难易程度分布
192
+export function difficultyDistribution(query) {
193
+    return request({
194
+        url: '/blocked/dashboard/difficultyDistribution',
195
+        method: 'get',
196
+        params: query
197
+    })
198
+}
199
+//查堵-开机人员持证比例
200
+export function certificateDistribution(query) {
201
+    return request({
202
+        url: '/blocked/dashboard/certificateDistribution',
203
+        method: 'get',
204
+        params: query
205
+    })
206
+}
207
+//查询开机人员年限分布列表全量
208
+export function tenureListAll(query) {
209
+    return request({
210
+        url: '/blocked/tenure/listAll',
211
+        method: 'get',
212
+        params: query
213
+    })
214
+}
215
+//大队开机人员证书分布
216
+export function brigadeCertificateDistribution(query) {
217
+    return request({
218
+        url: '/blocked/dashboard/brigadeCertificateDistribution',
219
+        method: 'get',
220
+        params: query
221
+    })
222
+}
223
+//T1速率统计数据
224
+export function t1RateStats(query) {
225
+    return request({
226
+        url: '/blocked/rate/screen/t1',
227
+        method: 'get',
228
+        params: query
229
+    })
230
+}
231
+//T2速率统计数据
232
+export function t2RateStats(query) {
233
+    return request({
234
+        url: '/blocked/rate/screen/t2',
235
+        method: 'get',
236
+        params: query
237
+    })
238
+}
239
+//国内速率统计数据
240
+export function insideRateStats(query) {
241
+    return request({
242
+        url: '/blocked/rate/screen/domestic',
243
+        method: 'get',
244
+        params: query
245
+    })
246
+}
247
+//国际速率统计数据
248
+export function outsideRateStats(query) {
249
+    return request({
250
+        url: '/blocked/rate/screen/international',
251
+        method: 'get',
252
+        params: query
253
+    })
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
+}
271
+//查询两楼每日查堵走势
272
+export function dailyTerminalTrend(query) {
273
+    return request({
274
+        url: '/blocked/dashboard/brigade/dailyTerminalTrend',
275
+        method: 'get',
276
+        params: query
277
+    })
278
+}
279
+//查询两楼每日过检图像数
280
+export function dailyTerminalLuggageTrend(query) {
281
+    return request({
282
+        url: '/blocked/dashboard/brigade/dailyTerminalLuggageTrend',
283
+        method: 'get',
284
+        params: query
285
+    })
286
+}
287
+//查询两楼查堵数(包含行检)
288
+export function terminalBlockedStats(query) {
289
+    return request({
290
+        url: '/blocked/dashboard/brigade/terminalBlockedStats',
291
+        method: 'get',
292
+        params: query
293
+    })
294
+}
295
+//查询查堵时间段过检行李数
296
+export function timePeriodLuggageStats(query) {
297
+    return request({
298
+        url: '/blocked/dashboard/brigade/timePeriodLuggageStats',
299
+        method: 'get',
300
+        params: query
301
+    })
302
+}
303
+//查询两楼每日查堵万分率
304
+export function dailyTerminalRateTrend(query) {
305
+    return request({
306
+        url: '/blocked/dashboard/brigade/dailyTerminalRateTrend',
307
+        method: 'get',
308
+        params: query
309
+    })
310
+}
311
+//查询查堵物品分布
312
+export function brigadeItemDistribution(query) {
313
+    return request({
314
+        url: '/blocked/dashboard/brigade/itemDistribution',
315
+        method: 'get',
316
+        params: query
317
+    })
318
+}
319
+//查堵-主管排行榜
320
+export function brigadeSupervisorRanking(query) {
321
+    return request({
322
+        url: '/blocked/dashboard/brigade/supervisorRanking',
323
+        method: 'get',
324
+        params: query
325
+    })
326
+}
327
+//查堵-班组排行榜
328
+export function brigadeTeamLeaderRanking(query) {
329
+    return request({
330
+        url: '/blocked/dashboard/brigade/teamLeaderRanking',
331
+        method: 'get',
332
+        params: query
333
+    })
334
+}
335
+//查堵-人员排行榜
336
+export function brigadeReviewedUserRanking(query) {
337
+    return request({
338
+        url: '/blocked/dashboard/brigade/reviewedUserRanking',
339
+        method: 'get',
340
+        params: query
341
+    })
342
+}
343
+//查询查堵男女比例
344
+export function brigadeGenderDistribution(query) {
345
+    return request({
346
+        url: '/blocked/dashboard/brigade/genderDistribution',
347
+        method: 'get',
348
+        params: query
349
+    })
350
+}
351
+//查询查堵人员证书级别分布
352
+export function brigadeCertificateDistributionDetail(query) {
353
+    return request({
354
+        url: '/blocked/dashboard/brigade/certificateLevelDistribution',
355
+        method: 'get',
356
+        params: query
357
+    })
358
+}
359
+//查询证书级别人员基数
360
+export function personnelCertificateBase(query) {
361
+    return request({
362
+        url: '/blocked/dashboard/brigade/personnelCertificateBase',
363
+        method: 'get',
364
+        params: query
365
+    })
366
+}
367
+//查询查堵物品位置分布
368
+export function brigadeItemLocationDistribution(query) {
369
+    return request({
370
+        url: '/blocked/dashboard/brigade/itemLocationDistribution',
371
+        method: 'get',
372
+        params: query
373
+    })
374
+}
375
+//查询查堵-简单/困难图像数
376
+export function brigadeDifficultyDistribution(query) {
377
+    return request({
378
+        url: '/blocked/dashboard/brigade/difficultyDistribution',
379
+        method: 'get',
380
+        params: query
381
+    })
382
+}
383
+//查询查堵-主管分管次数分布
384
+export function brigadeSupervisorDistribution(query) {
385
+    return request({
386
+        url: '/blocked/dashboard/brigade/supervisorDistribution',
387
+        method: 'get',
388
+        params: query
389
+    })
390
+}
391
+//查询查堵原因分类分布
392
+export function brigadeMissCheckReasonDistribution(query) {
393
+    return request({
394
+        url: '/blocked/dashboard/brigade/missCheckReasonDistribution',
395
+        method: 'get',
396
+        params: query
397
+    })
398
+}
399
+//查询查堵人员开机年限分布
400
+export function brigadeOperatingYearsDistribution(query) {
401
+    return request({
402
+        url: '/blocked/dashboard/brigade/operatingYearsDistribution',
403
+        method: 'get',
404
+        params: query
405
+    })
406
+}

+ 62 - 0
src/api/blockingData/dailyLuggageCheckInList.js

@@ -0,0 +1,62 @@
1
+import request from '@/utils/request'
2
+
3
+// 查询每日行李查缉列表
4
+export function listDailyLuggageCheckIn(query) {
5
+  return request({
6
+    url: '/blocked/daily/list',
7
+    method: 'get',
8
+    params: query
9
+  })
10
+}
11
+
12
+// 查询每日行李查缉详细
13
+export function getDailyLuggageCheckIn(id) {
14
+  return request({
15
+    url: `/blocked/daily/${id}`,
16
+    method: 'get'
17
+  })
18
+}
19
+
20
+// 新增每日行李查缉
21
+export function addDailyLuggageCheckIn(data) {
22
+  return request({
23
+    url: '/blocked/daily',
24
+    method: 'post',
25
+    data: data
26
+  })
27
+}
28
+
29
+// 修改每日行李查缉
30
+export function updateDailyLuggageCheckIn(data) {
31
+  return request({
32
+    url: '/blocked/daily',
33
+    method: 'put',
34
+    data: data
35
+  })
36
+}
37
+
38
+// 删除每日行李查缉
39
+export function delDailyLuggageCheckIn(ids) {
40
+  return request({
41
+    url: `/blocked/daily/${ids}`,
42
+    method: 'delete'
43
+  })
44
+}
45
+
46
+// 导出每日行李查缉
47
+export function exportDailyLuggageCheckIn(data) {
48
+  return request({
49
+    url: '/blocked/daily/export',
50
+    method: 'post',
51
+    data: data
52
+  })
53
+}
54
+
55
+// 下载导入模板
56
+export function downloadTemplate() {
57
+  return request({
58
+    url: '/blocked/daily/importTemplate',
59
+    method: 'get',
60
+    responseType: 'blob'
61
+  })
62
+}

+ 62 - 0
src/api/blockingData/dailyLuggageInspectionScheduleByTime.js

@@ -0,0 +1,62 @@
1
+import request from '@/utils/request'
2
+
3
+// 查询每日各时段查堵行李列表
4
+export function listDailyLuggageInspectionScheduleByTime(query) {
5
+  return request({
6
+    url: '/blocked/pieceDaily/list',
7
+    method: 'get',
8
+    params: query
9
+  })
10
+}
11
+
12
+// 查询每日各时段查堵行李详细
13
+export function getDailyLuggageInspectionScheduleByTime(id) {
14
+  return request({
15
+    url: `/blocked/pieceDaily/${id}`,
16
+    method: 'get'
17
+  })
18
+}
19
+
20
+// 新增每日各时段查堵行李
21
+export function addDailyLuggageInspectionScheduleByTime(data) {
22
+  return request({
23
+    url: '/blocked/pieceDaily',
24
+    method: 'post',
25
+    data: data
26
+  })
27
+}
28
+
29
+// 修改每日各时段查堵行李
30
+export function updateDailyLuggageInspectionScheduleByTime(data) {
31
+  return request({
32
+    url: '/blocked/pieceDaily',
33
+    method: 'put',
34
+    data: data
35
+  })
36
+}
37
+
38
+// 删除每日各时段查堵行李
39
+export function delDailyLuggageInspectionScheduleByTime(ids) {
40
+  return request({
41
+    url: `/blocked/pieceDaily/${ids}`,
42
+    method: 'delete'
43
+  })
44
+}
45
+
46
+// 导出每日各时段查堵行李
47
+export function exportDailyLuggageInspectionScheduleByTime(data) {
48
+  return request({
49
+    url: '/blocked/pieceDaily/export',
50
+    method: 'post',
51
+    data: data
52
+  })
53
+}
54
+
55
+// 下载导入模板
56
+export function downloadTemplate() {
57
+  return request({
58
+    url: '/blocked/pieceDaily/importTemplate',
59
+    method: 'get',
60
+    responseType: 'blob'
61
+  })
62
+}

+ 19 - 0
src/api/blockingData/experienceDistributionStartupPerson.js

@@ -0,0 +1,19 @@
1
+import request from '@/utils/request'
2
+
3
+// 查询开机人员年限分布列表全量
4
+export function getTenurelistAll(query) {
5
+  return request({
6
+    url: '/blocked/tenure/listAll',
7
+    method: 'get',
8
+    params: query
9
+  })
10
+}
11
+
12
+//修改开机人员年限分布
13
+export function editTenure(data) {
14
+  return request({
15
+    url: '/blocked/tenure/edit',
16
+    method: 'post',
17
+    data: data
18
+  })
19
+}

+ 62 - 0
src/api/blockingData/missedInspection.js

@@ -0,0 +1,62 @@
1
+import request from '@/utils/request'
2
+
3
+// 查询漏检列表
4
+export function listMissedInspection(query) {
5
+  return request({
6
+    url: '/blocked/missReview/list',
7
+    method: 'get',
8
+    params: query
9
+  })
10
+}
11
+
12
+// 查询漏检详细
13
+export function getMissedInspection(id) {
14
+  return request({
15
+    url: `/blocked/missReview/${id}`,
16
+    method: 'get'
17
+  })
18
+}
19
+
20
+// 新增漏检
21
+export function addMissedInspection(data) {
22
+  return request({
23
+    url: '/blocked/missReview',
24
+    method: 'post',
25
+    data: data
26
+  })
27
+}
28
+
29
+// 修改漏检
30
+export function updateMissedInspection(data) {
31
+  return request({
32
+    url: '/blocked/missReview',
33
+    method: 'post',
34
+    data: data
35
+  })
36
+}
37
+
38
+// 删除漏检
39
+export function delMissedInspection(ids) {
40
+  return request({
41
+    url: `/blocked/missReview/${ids}`,
42
+    method: 'delete'
43
+  })
44
+}
45
+
46
+// 导出漏检
47
+export function exportMissedInspection(data) {
48
+  return request({
49
+    url: '/blocked/missReview/export',
50
+    method: 'post',
51
+    data: data
52
+  })
53
+}
54
+
55
+// 下载导入模板
56
+export function downloadTemplate() {
57
+  return request({
58
+    url: '/blocked/missReview/importTemplate',
59
+    method: 'get',
60
+    responseType: 'blob'
61
+  })
62
+}

+ 62 - 0
src/api/blockingData/rateList.js

@@ -0,0 +1,62 @@
1
+import request from '@/utils/request'
2
+
3
+// 查询速率统计列表
4
+export function listRate(query) {
5
+  return request({
6
+    url: '/blocked/rate/list',
7
+    method: 'get',
8
+    params: query
9
+  })
10
+}
11
+
12
+// 查询速率统计详细
13
+export function getRate(id) {
14
+  return request({
15
+    url: `/blocked/rate/${id}`,
16
+    method: 'get'
17
+  })
18
+}
19
+
20
+// 新增速率统计
21
+export function addRate(data) {
22
+  return request({
23
+    url: '/blocked/rate/add',
24
+    method: 'post',
25
+    data: data
26
+  })
27
+}
28
+
29
+// 修改速率统计
30
+export function updateRate(data) {
31
+  return request({
32
+    url: '/blocked/rate/edit',
33
+    method: 'post',
34
+    data: data
35
+  })
36
+}
37
+
38
+// 删除速率统计
39
+export function delRate(ids) {
40
+  return request({
41
+    url: `/blocked/rate/${ids}`,
42
+    method: 'delete'
43
+  })
44
+}
45
+
46
+// 导出速率统计
47
+export function exportRate() {
48
+  return request({
49
+    url: `/blocked/rate/export`,
50
+    method: 'post',
51
+  })
52
+}
53
+
54
+
55
+// 下载导入模板
56
+export function downloadTemplate() {
57
+  return request({
58
+    url: '/blocked/rate/importTemplate',
59
+    method: 'get',
60
+    responseType: 'blob'
61
+  })
62
+}

+ 9 - 0
src/api/system/user.js

@@ -151,3 +151,12 @@ export function getDeptUserTree(params) {
151 151
     params: params
152 152
   })
153 153
 }
154
+
155
+//根据用户ID和角色编码列表查询各级直属领导列表
156
+export function selectUserLeaderListByCondition(data) {
157
+  return request({
158
+    url: '/system/user/selectUserLeaderListByCondition',
159
+    method: 'post',
160
+    data: data
161
+  })
162
+}

+ 2 - 2
src/router/index.js

@@ -92,9 +92,9 @@ export const constantRoutes = [
92 92
     children: [
93 93
       {
94 94
         path: '/index',
95
-        component: () => import('@/views/dataBigScreen/dashboard'),
95
+        component: () => import('@/views/blockingData/blockingDataScreen/index'),
96 96
         name: 'Index',
97
-        meta: { title: '智慧大屏监控中心', icon: 'dashboard' }
97
+        meta: { title: '查堵大屏', icon: 'dashboard' }
98 98
       },
99 99
       // {
100 100
       //   path: '/dashboard-work',

+ 1 - 1
src/utils/dict.js

@@ -14,7 +14,7 @@ export function useDict(...args) {
14 14
         res.value[dictType] = dicts
15 15
       } else {
16 16
         getDicts(dictType).then(resp => {
17
-          res.value[dictType] = resp.data.map(p => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass }))
17
+          res.value[dictType] = resp.data.map(p => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass, remark: p.remark }))
18 18
           useDictStore().setDict(dictType, res.value[dictType])
19 19
         })
20 20
       }

+ 16 - 8
src/utils/index.js

@@ -5,12 +5,12 @@ import { parseTime } from './ruoyi'
5 5
  */
6 6
 export function formatDate(cellValue) {
7 7
   if (cellValue == null || cellValue == "") return ""
8
-  var date = new Date(cellValue) 
8
+  var date = new Date(cellValue)
9 9
   var year = date.getFullYear()
10 10
   var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
11
-  var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() 
12
-  var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours() 
13
-  var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() 
11
+  var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
12
+  var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
13
+  var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
14 14
   var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
15 15
   return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds
16 16
 }
@@ -218,7 +218,7 @@ export function getTime(type) {
218 218
 export function debounce(func, wait, immediate) {
219 219
   let timeout, args, context, timestamp, result
220 220
 
221
-  const later = function() {
221
+  const later = function () {
222 222
     // 据上一次触发时间间隔
223 223
     const last = +new Date() - timestamp
224 224
 
@@ -235,7 +235,7 @@ export function debounce(func, wait, immediate) {
235 235
     }
236 236
   }
237 237
 
238
-  return function(...args) {
238
+  return function (...args) {
239 239
     context = this
240 240
     timestamp = +new Date()
241 241
     const callNow = immediate && !timeout
@@ -330,7 +330,7 @@ export function makeMap(str, expectsLowerCase) {
330 330
     ? val => map[val.toLowerCase()]
331 331
     : val => map[val]
332 332
 }
333
- 
333
+
334 334
 export const exportDefault = 'export default '
335 335
 
336 336
 export const beautifierConf = {
@@ -387,4 +387,12 @@ export function camelCase(str) {
387 387
 export function isNumberStr(str) {
388 388
   return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str)
389 389
 }
390
- 
390
+export function formatDateHy(dateString) {
391
+  if (!dateString) return ''
392
+  const date = new Date(dateString)
393
+  const year = date.getFullYear()
394
+  const month = String(date.getMonth() + 1).padStart(2, '0')
395
+  const day = String(date.getDate()).padStart(2, '0')
396
+  return `${year}-${month}-${day}`
397
+}
398
+

+ 428 - 0
src/views/blockingData/blockingDataScreen/components/ModuleBrigadeOne.vue

@@ -0,0 +1,428 @@
1
+<template>
2
+  <module-container title="模块一 运行数据分析">
3
+    <div class="module-brigade-content">
4
+      <div class="combined-row">
5
+        <div class="stat-card">
6
+          <div class="stat-card-inner">
7
+            <div class="stat-left">
8
+              <div class="stat-label">查堵总数</div>
9
+              <div class="stat-content">
10
+                <div class="stat-value">{{ statValue || 0 }}</div>
11
+                <div class="stat-unit">起</div>
12
+              </div>
13
+
14
+            </div>
15
+          </div>
16
+        </div>
17
+        <div class="chart-item">
18
+          <div class="chart-title">两楼每日查堵走势</div>
19
+          <div ref="chart1" class="echarts"></div>
20
+        </div>
21
+        <div class="chart-item">
22
+          <div class="chart-title">两楼每日过检图像数</div>
23
+          <div ref="chart2" class="echarts"></div>
24
+        </div>
25
+      </div>
26
+
27
+      <div class="chart-row">
28
+        <div class="chart-item">
29
+          <div class="chart-title">两楼查堵数(包含行检)</div>
30
+          <div ref="chart3" class="echarts"></div>
31
+        </div>
32
+        <div class="chart-item">
33
+          <div class="chart-title">查堵时间段过检行李数</div>
34
+          <div ref="chart4" class="echarts"></div>
35
+        </div>
36
+      </div>
37
+
38
+      <div class="chart-row">
39
+        <div class="chart-item">
40
+          <div class="chart-title">两楼每日查堵万分率</div>
41
+          <div ref="chart5" class="echarts"></div>
42
+        </div>
43
+        <div class="chart-item">
44
+          <div class="chart-title">查堵物品分布</div>
45
+          <div ref="chart6" class="echarts"></div>
46
+        </div>
47
+      </div>
48
+    </div>
49
+  </module-container>
50
+</template>
51
+
52
+<script setup>
53
+import { ref, onMounted, watch } from 'vue'
54
+import * as echarts from 'echarts'
55
+import ModuleContainer from './ModuleContainer.vue'
56
+import { useEcharts } from '@/hooks/chart.js'
57
+import { formatDateHy } from '@/utils/index.js'
58
+import {
59
+  dailyTerminalTrend,
60
+  dailyTerminalLuggageTrend,
61
+  terminalBlockedStats,
62
+  timePeriodLuggageStats,
63
+  dailyTerminalRateTrend,
64
+  brigadeItemDistribution
65
+} from '@/api/blockingData/blockingDataScreen'
66
+
67
+const props = defineProps({
68
+  filterParams: {
69
+    type: Object,
70
+    default: () => ({})
71
+  }
72
+})
73
+
74
+const loading = ref(false)
75
+const statValue = ref(0)
76
+
77
+const processFilterParams = (params) => {
78
+  const { dateRange, ...rest } = params
79
+  const processed = { ...rest }
80
+
81
+  if (dateRange && Array.isArray(dateRange) && dateRange.length === 2) {
82
+    processed.startTime = formatDateHy(dateRange[0])
83
+    processed.endTime = formatDateHy(dateRange[1])
84
+  }
85
+  if (processed.brigadeId == 'all') {
86
+    delete processed.brigadeId
87
+  }
88
+  if (processed.terminalId == 'all') {
89
+    delete processed.terminalId
90
+  }
91
+
92
+  return processed
93
+}
94
+
95
+const chart1 = ref(null)
96
+const chart2 = ref(null)
97
+const chart3 = ref(null)
98
+const chart4 = ref(null)
99
+const chart5 = ref(null)
100
+const chart6 = ref(null)
101
+
102
+const { setOption: setOption1 } = useEcharts(chart1)
103
+const { setOption: setOption2 } = useEcharts(chart2)
104
+const { setOption: setOption3 } = useEcharts(chart3)
105
+const { setOption: setOption4 } = useEcharts(chart4)
106
+const { setOption: setOption5 } = useEcharts(chart5)
107
+const { setOption: setOption6 } = useEcharts(chart6)
108
+
109
+const fetchData = async () => {
110
+  loading.value = true
111
+  try {
112
+    const processedParams = processFilterParams(props.filterParams)
113
+    const [trendRes, luggageRes, blockedRes, timeRes, rateRes, itemRes] = await Promise.allSettled([
114
+      dailyTerminalTrend(processedParams),
115
+      dailyTerminalLuggageTrend(processedParams),
116
+      terminalBlockedStats(processedParams),
117
+      timePeriodLuggageStats(processedParams),
118
+      dailyTerminalRateTrend(processedParams),
119
+      brigadeItemDistribution(processedParams)
120
+    ])
121
+
122
+    if (trendRes.value?.data) {
123
+      const dates = trendRes.value.data.map(item => item.statDate?.split('T')[0] || item.date || item.name || '')
124
+      const t1TravelData = trendRes.value.data.map(item => item.t1TravelBlockedCount || 0)
125
+      const t2TravelData = trendRes.value.data.map(item => item.t2TravelBlockedCount || 0)
126
+      const t1WalkData = trendRes.value.data.map(item => item.t1WalkBlockedCount || 0)
127
+      const t2WalkData = trendRes.value.data.map(item => item.t2WalkBlockedCount || 0)
128
+      setOption1(multiLineChartOption(dates, [
129
+        { name: 'T1旅检查堵件数', data: t1TravelData, color: '#3b82f6' },
130
+        { name: 'T2旅检查堵件数', data: t2TravelData, color: '#22c55e' },
131
+        { name: 'T1行检查堵件数', data: t1WalkData, color: '#f97316' },
132
+        { name: 'T2行检查堵件数', data: t2WalkData, color: '#ec4899' }
133
+      ]))
134
+    }
135
+
136
+    if (luggageRes.value?.data) {
137
+      const dates = luggageRes.value.data.map(item => item.statDate?.split('T')[0] || item.date || item.name || '')
138
+      const t1TravelData = luggageRes.value.data.map(item => item.t1TravelBagCount || 0)
139
+      const t2TravelData = luggageRes.value.data.map(item => item.t2TravelBagCount || 0)
140
+      const t1WalkData = luggageRes.value.data.map(item => item.t1WalkBagCount || 0)
141
+      const t2WalkData = luggageRes.value.data.map(item => item.t2WalkBagCount || 0)
142
+      setOption2(multiLineChartOption(dates, [
143
+        { name: 'T1旅检过检行李数', data: t1TravelData, color: '#3b82f6' },
144
+        { name: 'T2旅检过检行李数', data: t2TravelData, color: '#22c55e' },
145
+        { name: 'T1行检过检行李数', data: t1WalkData, color: '#f97316' },
146
+        { name: 'T2行检过检行李数', data: t2WalkData, color: '#ec4899' }
147
+      ]))
148
+    }
149
+
150
+    if (blockedRes.value?.data) {
151
+      const terminal = processedParams?.terminal || '';
152
+
153
+      const blockedData = [
154
+        ...(terminal.includes('T1') || terminal.includes('整体') || !terminal ? [{ name: 'T1', value: blockedRes.value.data.t1BlockedCount || 0 }] : []),
155
+        ...(terminal.includes('T2') || terminal.includes('整体') || !terminal ? [{ name: 'T2', value: blockedRes.value.data.t2BlockedCount || 0 }] : [])
156
+      ]
157
+      setOption3(horizontalBarChartOption(blockedData, '#3b82f6'))
158
+      const total = blockedRes.value.data.totalBlockedCount || 0
159
+      statValue.value = total
160
+    }
161
+
162
+    if (timeRes.value?.data) {
163
+      const timePeriods = timeRes.value.data.map(item => item.timePeriod || '')
164
+      const avgLuggageData = timeRes.value.data.map(item => item.avgLuggageCount || 0)
165
+      const avgBlockedData = timeRes.value.data.map(item => item.avgBlockedCount || 0)
166
+      setOption4(barLineChartOption(timePeriods, avgLuggageData, avgBlockedData))
167
+    }
168
+
169
+    if (rateRes.value?.data) {
170
+      const dates = rateRes.value.data.map(item => item.statDate?.split('T')[0] || item.date || item.name || '')
171
+      const t1TravelRate = rateRes.value.data.map(item => item.t1TravelBlockRate || 0)
172
+      const t2TravelRate = rateRes.value.data.map(item => item.t2TravelBlockRate || 0)
173
+      const t1WalkRate = rateRes.value.data.map(item => item.t1WalkBlockRate || 0)
174
+      const t2WalkRate = rateRes.value.data.map(item => item.t2WalkBlockRate || 0)
175
+      setOption5(multiLineChartOption(dates, [
176
+        { name: 'T1旅检万分率', data: t1TravelRate, color: '#3b82f6' },
177
+        { name: 'T2旅检万分率', data: t2TravelRate, color: '#22c55e' },
178
+        { name: 'T1行检万分率', data: t1WalkRate, color: '#f97316' },
179
+        { name: 'T2行检万分率', data: t2WalkRate, color: '#ec4899' }
180
+      ]))
181
+    }
182
+
183
+    if (itemRes.value?.data) {
184
+      const colors = ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#10b981', '#f59e0b', '#ef4444']
185
+      const itemData = itemRes.value.data.map((item, index) => ({
186
+        name: item.missCheckItem,
187
+        value: item.count
188
+      }))
189
+      setOption6(pieChartOption(itemData, colors))
190
+    }
191
+  } catch (error) {
192
+    console.error('获取数据失败:', error)
193
+  } finally {
194
+    loading.value = false
195
+  }
196
+}
197
+
198
+watch(() => props.filterParams, () => {
199
+  fetchData()
200
+}, { deep: true })
201
+
202
+const xAxisData = Array.from({ length: 30 }, (_, i) => `${i + 1}日`)
203
+
204
+const generateData = (count, min, max) => {
205
+  const data = []
206
+  for (let i = 0; i < count; i++) {
207
+    data.push(Math.floor(Math.random() * (max - min + 1)) + min)
208
+  }
209
+  return data
210
+}
211
+
212
+const multiLineChartOption = (xAxisData, series) => ({
213
+  grid: { left: '10%', top: '15%', right: '5%', bottom: '15%', containLabel: true },
214
+  tooltip: {
215
+    trigger: 'axis',
216
+    axisPointer: { type: 'cross' }
217
+  },
218
+  xAxis: { type: 'category', data: xAxisData, axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' } },
219
+  yAxis: { type: 'value', axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' }, splitLine: { lineStyle: { color: '#eee' } } },
220
+  legend: {
221
+    data: series.map(s => s.name),
222
+    top: '0%',
223
+    textStyle: { fontSize: 10 }
224
+  },
225
+  series: series.map(s => ({
226
+    name: s.name,
227
+    type: 'line',
228
+    smooth: true,
229
+    symbol: 'circle',
230
+    symbolSize: 6,
231
+    data: s.data,
232
+    itemStyle: { color: s.color },
233
+    lineStyle: { color: s.color },
234
+    label: { show: true, position: 'top', fontSize: 9, color: s.color }
235
+  }))
236
+})
237
+
238
+
239
+
240
+const horizontalBarChartOption = (data, color) => ({
241
+  grid: { left: '20%', top: '15%', right: '5%', bottom: '15%', containLabel: true },
242
+  tooltip: {
243
+    trigger: 'axis',
244
+    axisPointer: { type: 'shadow' }
245
+  },
246
+  xAxis: { type: 'value', axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' }, splitLine: { lineStyle: { color: '#eee' } } },
247
+  yAxis: { type: 'category', data: data.map(d => d.name), axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' } },
248
+  series: [{
249
+    type: 'bar',
250
+    data: data.map(d => d.value),
251
+    itemStyle: { color: color },
252
+    barWidth: 15,
253
+    label: { show: true, position: 'right', fontSize: 10, color: color }
254
+  }]
255
+})
256
+
257
+const barLineChartOption = (xAxisData, barData, lineData) => ({
258
+  grid: { left: '10%', top: '15%', right: '5%', bottom: '15%', containLabel: true },
259
+  tooltip: {
260
+    trigger: 'axis',
261
+    axisPointer: { type: 'cross' }
262
+  },
263
+  xAxis: { type: 'category', data: xAxisData, axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' } },
264
+  yAxis: { type: 'value', axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' }, splitLine: { lineStyle: { color: '#eee' } } },
265
+  legend: {
266
+    data: ['平均过检行李数', '平均查堵件数'],
267
+    top: '0%',
268
+    textStyle: { fontSize: 10 }
269
+  },
270
+  series: [
271
+    {
272
+      name: '平均过检行李数',
273
+      type: 'bar',
274
+      data: barData,
275
+      itemStyle: { color: '#3b82f6' },
276
+      barWidth: 20,
277
+      label: { show: true, position: 'top', fontSize: 9, color: '#3b82f6' }
278
+    },
279
+    {
280
+      name: '平均查堵件数',
281
+      type: 'line',
282
+      smooth: true,
283
+      symbol: 'circle',
284
+      symbolSize: 6,
285
+      data: lineData,
286
+      itemStyle: { color: '#ec4899' },
287
+      lineStyle: { color: '#ec4899' },
288
+      label: { show: true, position: 'top', fontSize: 9, color: '#ec4899' }
289
+    }
290
+  ]
291
+})
292
+
293
+const pieChartOption = (data, colors) => ({
294
+  color: colors,
295
+  legend: {
296
+    show: true,
297
+    textStyle: { fontSize: 10 }
298
+  },
299
+  tooltip: { trigger: 'item' },
300
+  series: [{
301
+    type: 'pie',
302
+    radius: '65%',
303
+    center: ['50%', '55%'],
304
+    data: data,
305
+    label: { show: true, formatter: '{b}\n{c} ({d}%)', fontSize: 10 }
306
+  }]
307
+})
308
+
309
+onMounted(() => {
310
+  fetchData()
311
+
312
+})
313
+</script>
314
+
315
+<style lang="less" scoped>
316
+.module-brigade-content {
317
+  height: 100%;
318
+  display: flex;
319
+  flex-direction: column;
320
+  gap: 10px;
321
+  overflow-y: auto;
322
+}
323
+
324
+.stats-row {
325
+  display: flex;
326
+  gap: 10px;
327
+  flex-shrink: 0;
328
+}
329
+
330
+.stat-card {
331
+  flex: 1;
332
+  min-height: 120px;
333
+}
334
+
335
+.stat-card-inner {
336
+  display: flex;
337
+  height: 100%;
338
+  align-items: center;
339
+  gap: 10px;
340
+}
341
+
342
+.stat-left {
343
+  background: #fff;
344
+  border-radius: 6px;
345
+  border: 1px solid #eee;
346
+  padding: 15px;
347
+  height: 100%;
348
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
349
+  flex: 1;
350
+  display: flex;
351
+  align-items: center;
352
+  flex-direction: column;
353
+
354
+  padding-right: 15px;
355
+}
356
+
357
+.stat-label {
358
+  width: 100%;
359
+  font-size: 17px;
360
+  color: black;
361
+  margin-bottom: 8px;
362
+}
363
+
364
+.stat-content {
365
+  margin-top: 25%;
366
+  display: flex;
367
+  flex-direction: column;
368
+  align-items: center;
369
+}
370
+
371
+.stat-value {
372
+  font-size: 48px;
373
+  font-weight: bold;
374
+  color: #3b82f6;
375
+}
376
+
377
+.stat-unit {
378
+  font-size: 14px;
379
+  color: #666;
380
+  margin-top: 5px;
381
+}
382
+
383
+.combined-row {
384
+  display: flex;
385
+  gap: 10px;
386
+  flex-shrink: 0;
387
+}
388
+
389
+.combined-row .stat-card {
390
+  flex: 0.5;
391
+  min-height: 200px;
392
+}
393
+
394
+.combined-row .chart-item {
395
+  flex: 1;
396
+}
397
+
398
+.chart-row {
399
+  display: flex;
400
+  gap: 10px;
401
+  flex-shrink: 0;
402
+}
403
+
404
+.chart-item {
405
+  flex: 1;
406
+  background: #fff;
407
+  border-radius: 6px;
408
+  padding: 15px;
409
+  border: 1px solid #eee;
410
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
411
+  display: flex;
412
+  flex-direction: column;
413
+  min-height: 400px;
414
+}
415
+
416
+.chart-title {
417
+  font-size: 17px;
418
+  color: black;
419
+  margin-bottom: 5px;
420
+  text-align: left;
421
+}
422
+
423
+.echarts {
424
+  flex: 1;
425
+  width: 100%;
426
+  min-height: 0;
427
+}
428
+</style>

+ 169 - 0
src/views/blockingData/blockingDataScreen/components/ModuleBrigadeThree.vue

@@ -0,0 +1,169 @@
1
+<template>
2
+  <module-container title="模块三 培训质控分析">
3
+    <div class="module-brigade-content">
4
+      <div class="chart-row">
5
+        <div class="chart-item">
6
+          <div class="chart-title">查堵-人员自测漏检次数</div>
7
+          <div ref="chart1" class="echarts"></div>
8
+        </div>
9
+        <div class="chart-item">
10
+          <div class="chart-title">查堵人员月考成绩</div>
11
+          <div ref="chart2" class="echarts"></div>
12
+        </div>
13
+      </div>
14
+    </div>
15
+  </module-container>
16
+</template>
17
+
18
+<script setup>
19
+import { ref, onMounted, watch } from 'vue'
20
+import * as echarts from 'echarts'
21
+import ModuleContainer from './ModuleContainer.vue'
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'
28
+
29
+const props = defineProps({
30
+  filterParams: {
31
+    type: Object,
32
+    default: () => ({})
33
+  }
34
+})
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
+
56
+const chart1 = ref(null)
57
+const chart2 = ref(null)
58
+
59
+const { setOption: setOption1 } = useEcharts(chart1)
60
+const { setOption: setOption2 } = useEcharts(chart2)
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
+
99
+const ringChartOption = (data, colors) => ({
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
+  },
107
+  series: [{
108
+    type: 'pie',
109
+    radius: ['35%', '45%'],
110
+    center: ['50%', '55%'],
111
+    data: data,
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
+    }
121
+  }]
122
+})
123
+
124
+onMounted(() => {
125
+  fetchData()
126
+
127
+})
128
+</script>
129
+
130
+<style lang="less" scoped>
131
+.module-brigade-content {
132
+  height: 100%;
133
+  display: flex;
134
+  flex-direction: column;
135
+  gap: 10px;
136
+  overflow-y: auto;
137
+}
138
+
139
+.chart-row {
140
+  display: flex;
141
+  gap: 10px;
142
+  flex-shrink: 0;
143
+}
144
+
145
+.chart-item {
146
+  flex: 1;
147
+  background: #fff;
148
+  border-radius: 6px;
149
+  padding: 10px;
150
+  border: 1px solid #eee;
151
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
152
+  display: flex;
153
+  flex-direction: column;
154
+  min-height: 400px;
155
+}
156
+
157
+.chart-title {
158
+  font-size: 17px;
159
+  color: black;
160
+  margin-bottom: 5px;
161
+  text-align: left;
162
+}
163
+
164
+.echarts {
165
+  flex: 1;
166
+  width: 100%;
167
+  min-height: 0;
168
+}
169
+</style>

+ 471 - 0
src/views/blockingData/blockingDataScreen/components/ModuleBrigadeTwo.vue

@@ -0,0 +1,471 @@
1
+<template>
2
+  <module-container title="模块二 查堵人员分析">
3
+    <div class="module-brigade-content">
4
+      <div class="chart-row">
5
+        <div class="chart-item">
6
+          <div class="chart-title">查堵-困难图像数</div>
7
+          <div class="number-display">
8
+            <div class="number-value">{{ difficultValue || 0 }}</div>
9
+            <div class="number-unit">幅</div>
10
+          </div>
11
+        </div>
12
+        <div class="chart-item">
13
+          <div class="chart-title">查堵-简单图像数</div>
14
+          <div class="number-display">
15
+            <div class="number-value">{{ simpleValue || 0 }}</div>
16
+            <div class="number-unit">幅</div>
17
+          </div>
18
+        </div>
19
+        <div class="chart-item">
20
+          <div class="chart-title">查堵物品位置</div>
21
+          <div ref="chart7" class="echarts"></div>
22
+        </div>
23
+      </div>
24
+      <div class="chart-row">
25
+        <div class="chart-item">
26
+          <div class="chart-title">查堵-主管排行榜</div>
27
+          <rank-list :rank-data="rankData1" title="查堵-主管排行榜" header-label="排名" header-name="分管主管" header-count="计数" />
28
+        </div>
29
+        <div class="chart-item">
30
+          <div class="chart-title">查堵-班组长排行榜</div>
31
+          <rank-list :rank-data="rankData2" title="查堵-班组长排行榜" header-label="排名" header-name="分管班组长" header-count="计数" />
32
+        </div>
33
+        <div class="chart-item">
34
+          <div class="chart-title">查堵-员工排行榜</div>
35
+          <rank-list :rank-data="rankData3" title="查堵-员工排行榜" header-label="排名" header-name="被回查人" header-count="计数" />
36
+        </div>
37
+      </div>
38
+
39
+      <div class="chart-row">
40
+        <div class="chart-item">
41
+          <div class="chart-title">查堵男女比例</div>
42
+          <div ref="chart4" class="echarts"></div>
43
+        </div>
44
+        <div class="chart-item">
45
+          <div class="chart-title">查堵人员证书级别分布</div>
46
+          <div ref="chart5" class="echarts"></div>
47
+        </div>
48
+        <div class="chart-item">
49
+          <div class="chart-title">证书级别人员基数</div>
50
+          <div ref="chart6" class="echarts"></div>
51
+        </div>
52
+      </div>
53
+
54
+
55
+
56
+      <div class="chart-row">
57
+        <div class="chart-item">
58
+          <div class="chart-title">查堵-主管分管次数</div>
59
+          <div ref="chart10" class="echarts"></div>
60
+        </div>
61
+        <div class="chart-item">
62
+          <div class="chart-title">查堵原因分类</div>
63
+          <div ref="chart11" class="echarts"></div>
64
+        </div>
65
+      </div>
66
+
67
+      <div class="chart-row">
68
+        <div class="chart-item">
69
+          <div class="chart-title">查堵人员开机年限分布</div>
70
+          <div ref="chart12" class="echarts"></div>
71
+        </div>
72
+        <div class="chart-item">
73
+          <div class="chart-title">大队开机年限人员分布</div>
74
+          <div ref="chart13" class="echarts"></div>
75
+        </div>
76
+      </div>
77
+    </div>
78
+  </module-container>
79
+</template>
80
+
81
+<script setup>
82
+import { ref, onMounted, reactive, watch } from 'vue'
83
+import * as echarts from 'echarts'
84
+import ModuleContainer from './ModuleContainer.vue'
85
+import RankList from './RankList.vue'
86
+import { useEcharts } from '@/hooks/chart.js'
87
+import { formatDateHy } from '@/utils/index.js'
88
+import {
89
+  brigadeSupervisorRanking,
90
+  brigadeTeamLeaderRanking,
91
+  brigadeReviewedUserRanking,
92
+  brigadeGenderDistribution,
93
+  brigadeCertificateDistributionDetail,
94
+  personnelCertificateBase,
95
+  brigadeItemLocationDistribution,
96
+  brigadeDifficultyDistribution,
97
+  brigadeSupervisorDistribution,
98
+  brigadeMissCheckReasonDistribution,
99
+  brigadeOperatingYearsDistribution,
100
+  tenureListAll
101
+} from '@/api/blockingData/blockingDataScreen'
102
+
103
+const props = defineProps({
104
+  filterParams: {
105
+    type: Object,
106
+    default: () => ({})
107
+  }
108
+})
109
+
110
+const loading = ref(false)
111
+const rankData1 = reactive([])
112
+const rankData2 = reactive([])
113
+const rankData3 = reactive([])
114
+const difficultValue = ref(0)
115
+const simpleValue = ref(0)
116
+
117
+const processFilterParams = (params) => {
118
+  const { dateRange, ...rest } = params
119
+  const processed = { ...rest }
120
+
121
+  if (dateRange && Array.isArray(dateRange) && dateRange.length === 2) {
122
+    processed.startTime = formatDateHy(dateRange[0])
123
+    processed.endTime = formatDateHy(dateRange[1])
124
+  }
125
+  if (processed.brigadeId == 'all') {
126
+    delete processed.brigadeId
127
+  }
128
+  if (processed.terminalId == 'all') {
129
+    delete processed.terminalId
130
+  }
131
+
132
+  return processed
133
+}
134
+
135
+const chart4 = ref(null)
136
+const chart5 = ref(null)
137
+const chart6 = ref(null)
138
+const chart7 = ref(null)
139
+const chart10 = ref(null)
140
+const chart11 = ref(null)
141
+const chart12 = ref(null)
142
+const chart13 = ref(null)
143
+
144
+const { setOption: setOption4 } = useEcharts(chart4)
145
+const { setOption: setOption5 } = useEcharts(chart5)
146
+const { setOption: setOption6 } = useEcharts(chart6)
147
+const { setOption: setOption7 } = useEcharts(chart7)
148
+const { setOption: setOption10 } = useEcharts(chart10)
149
+const { setOption: setOption11 } = useEcharts(chart11)
150
+const { setOption: setOption12 } = useEcharts(chart12)
151
+const { setOption: setOption13 } = useEcharts(chart13)
152
+
153
+const fetchData = async () => {
154
+  loading.value = true
155
+  try {
156
+    const processedParams = processFilterParams(props.filterParams)
157
+    const [supervisorRes, teamRes, userRes, genderRes, certDetailRes, certBaseRes, locationRes, difficultyRes, supervisorDistRes, reasonRes, yearsRes, tenureRes] = await Promise.allSettled([
158
+      brigadeSupervisorRanking(processedParams),
159
+      brigadeTeamLeaderRanking(processedParams),
160
+      brigadeReviewedUserRanking(processedParams),
161
+      brigadeGenderDistribution(processedParams),
162
+      brigadeCertificateDistributionDetail(processedParams),
163
+      personnelCertificateBase(processedParams),
164
+      brigadeItemLocationDistribution(processedParams),
165
+      brigadeDifficultyDistribution(processedParams),
166
+      brigadeSupervisorDistribution(processedParams),
167
+      brigadeMissCheckReasonDistribution(processedParams),
168
+      brigadeOperatingYearsDistribution(processedParams),
169
+      tenureListAll(processedParams)
170
+    ])
171
+
172
+    if (supervisorRes.value?.data) {
173
+      rankData1.length = 0
174
+      supervisorRes.value.data.slice(0, 10).forEach(item => {
175
+        rankData1.push({
176
+          name: item.supervisorName || item.name || '',
177
+          value: item.totalCount || item.value || 0
178
+        })
179
+      })
180
+    }
181
+
182
+    if (teamRes.value?.data) {
183
+      rankData2.length = 0
184
+      teamRes.value.data.slice(0, 10).forEach(item => {
185
+        rankData2.push({
186
+          name: item.teamLeaderName || item.name || '',
187
+          value: item.totalCount || item.value || 0
188
+        })
189
+      })
190
+    }
191
+
192
+    if (userRes.value?.data) {
193
+      rankData3.length = 0
194
+      userRes.value.data.slice(0, 10).forEach(item => {
195
+        rankData3.push({
196
+          name: item.userName || item.name || '',
197
+          value: item.totalCount || item.value || 0
198
+        })
199
+      })
200
+    }
201
+
202
+    if (genderRes.value?.data) {
203
+      const genderData = genderRes.value.data.map(item => ({
204
+        name: item.gender || item.name || '',
205
+        value: item.count || item.value || 0
206
+      }))
207
+      setOption4(pieChartOption(genderData, ['#ec4899', '#3b82f6']))
208
+    }
209
+
210
+    if (certDetailRes.value?.data) {
211
+      const certData = certDetailRes.value.data.map(item => ({
212
+        name: item.certificateLevel || item.name || '',
213
+        value: item.count || item.value || 0
214
+      }))
215
+      setOption5(pieChartOption(certData, ['#22c55e', '#3b82f6']))
216
+    }
217
+
218
+    if (certBaseRes.value?.data) {
219
+      const baseData = certBaseRes.value.data.map(item => ({
220
+        name: item.certificateLevel || item.name || '',
221
+        value: item.count || item.value || 0
222
+      }))
223
+      setOption6(pieChartOption(baseData, ['#22c55e', '#3b82f6']))
224
+    }
225
+
226
+    if (locationRes.value?.data) {
227
+      const colors = ['#8b5cf6', '#ec4899', '#f97316', '#22c55e', '#3b82f6', '#14b8a6', '#ef4444', '#fbbf24', '#6366f1', '#06b6d4', '#84cc16', '#e11d48']
228
+      const locationData = locationRes.value.data.map(item => ({
229
+        name: item.itemLocation || item.name || '',
230
+        value: item.count || item.value || 0
231
+      }))
232
+      setOption7(donutChartOption(locationData, colors))
233
+    }
234
+
235
+    if (difficultyRes.value?.data) {
236
+      let difficult = 0
237
+      let simple = 0
238
+      difficultyRes.value.data.forEach(item => {
239
+        if (item.difficultyLevel === '难') {
240
+          difficult = item.count || item.value || 0
241
+        } else if (item.difficultyLevel === '简单') {
242
+          simple = item.count || item.value || 0
243
+        }
244
+      })
245
+      difficultValue.value = difficult
246
+      simpleValue.value = simple
247
+    }
248
+
249
+    if (supervisorDistRes.value?.data) {
250
+      const colors = ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6', '#ef4444', '#fbbf24', '#6366f1', '#06b6d4', '#84cc16', '#e11d48', '#a855f7', '#f59e0b']
251
+      const supervisorData = supervisorDistRes.value.data.map(item => ({
252
+        name: item.supervisorName || item.name || '',
253
+        value: item.totalCount || item.value || 0
254
+      }))
255
+      setOption10(donutChartOption(supervisorData, colors))
256
+    }
257
+
258
+    if (reasonRes.value?.data) {
259
+      const colors = ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6', '#ef4444', '#fbbf24']
260
+      const reasonData = reasonRes.value.data.map(item => ({
261
+        name: item.missCheckReasonCategory || '',
262
+        value: item.count || 0
263
+      }))
264
+      setOption11(pieChartOption(reasonData, colors))
265
+    }
266
+
267
+    if (yearsRes.value?.data) {
268
+      const colors = ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6']
269
+      const yearsData = yearsRes.value.data.map(item => ({
270
+        name: item.operatingYears || '',
271
+        value: item.count || 0
272
+      }))
273
+      setOption12(pieChartOption(yearsData, colors))
274
+    }
275
+
276
+    if (tenureRes.value?.data) {
277
+      const xAxisData = tenureRes.value.data.map(item => item.brigadeName || '')
278
+      const seriesData = [
279
+        {
280
+          name: '5年及以上',
281
+          data: tenureRes.value.data.map(item => item.cntGe5Year || 0),
282
+          color: '#3b82f6'
283
+        },
284
+        {
285
+          name: '4年',
286
+          data: tenureRes.value.data.map(item => item.cnt4Years || 0),
287
+          color: '#22c55e'
288
+        },
289
+        {
290
+          name: '3年',
291
+          data: tenureRes.value.data.map(item => item.cnt3Years || 0),
292
+          color: '#f97316'
293
+        },
294
+        {
295
+          name: '2年',
296
+          data: tenureRes.value.data.map(item => item.cnt2Years || 0),
297
+          color: '#ec4899'
298
+        },
299
+        {
300
+          name: '1年',
301
+          data: tenureRes.value.data.map(item => item.cnt1Years || 0),
302
+          color: '#8b5cf6'
303
+        },
304
+        {
305
+          name: '不足1年',
306
+          data: tenureRes.value.data.map(item => item.cntLt1Year || 0),
307
+          color: '#14b8a6'
308
+        }
309
+      ]
310
+      setOption13(multiSeriesBarChartOption(xAxisData, seriesData))
311
+    }
312
+  } catch (error) {
313
+    console.error('获取数据失败:', error)
314
+  } finally {
315
+    loading.value = false
316
+  }
317
+}
318
+
319
+watch(() => props.filterParams, () => {
320
+  fetchData()
321
+}, { deep: true })
322
+
323
+const pieChartOption = (data, colors) => ({
324
+  color: colors,
325
+  tooltip: { trigger: 'item', formatter: '{b}: {c}' },
326
+  legend: {
327
+    data: data.map(item => item.name),
328
+    top: '0%',
329
+    textStyle: { fontSize: 10 }
330
+  },
331
+  series: [{
332
+    type: 'pie',
333
+    radius: ['35%', '45%'],
334
+    center: ['50%', '55%'],
335
+    data: data,
336
+    label: {
337
+      show: true,
338
+      formatter: '{b}\n{c} ({d}%)',
339
+      fontSize: 10
340
+    },
341
+  }]
342
+})
343
+
344
+const donutChartOption = (data, colors) => ({
345
+  color: colors,
346
+  tooltip: { trigger: 'item', formatter: '{b}: {c}' },
347
+  legend: {
348
+    data: data.map(item => item.name),
349
+    top: '0%',
350
+    textStyle: { fontSize: 10 }
351
+  },
352
+  series: [{
353
+    type: 'pie',
354
+    radius: ['30%', '55%'],
355
+    center: ['50%', '55%'],
356
+    data: data,
357
+    label: {
358
+      show: true,
359
+      position: 'outside',
360
+      formatter: (params) => {
361
+        const total = data.reduce((sum, item) => sum + item.value, 0)
362
+        const percentage = ((params.value / total) * 100).toFixed(2)
363
+        return `${params.name} ${params.value}(${percentage}%)`
364
+      },
365
+      fontSize: 10
366
+    }
367
+  }]
368
+})
369
+
370
+const multiBarChartOption = (data, color) => ({
371
+  grid: { left: '15%', top: '15%', right: '5%', bottom: '15%', containLabel: true },
372
+  xAxis: { type: 'category', data: data.map(d => d.name), axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' } },
373
+  yAxis: { type: 'value', axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' }, splitLine: { lineStyle: { color: '#eee' } } },
374
+  series: [{ type: 'bar', data: data.map(d => d.value), itemStyle: { color: color }, barWidth: 20 }]
375
+})
376
+
377
+const multiSeriesBarChartOption = (xAxisData, series) => ({
378
+  grid: { left: '10%', top: '15%', right: '5%', bottom: '15%', containLabel: true },
379
+  xAxis: {
380
+    type: 'category',
381
+    data: xAxisData,
382
+    axisLine: { lineStyle: { color: '#999' } },
383
+    axisLabel: { fontSize: 10, color: '#666' }
384
+  },
385
+  yAxis: {
386
+    type: 'value',
387
+    axisLine: { lineStyle: { color: '#999' } },
388
+    axisLabel: { fontSize: 10, color: '#666' },
389
+    splitLine: { lineStyle: { color: '#eee' } }
390
+  },
391
+  legend: {
392
+    data: series.map(s => s.name),
393
+    top: '0%',
394
+    textStyle: { fontSize: 10 }
395
+  },
396
+  series: series.map(s => ({
397
+    name: s.name,
398
+    type: 'bar',
399
+    data: s.data,
400
+    itemStyle: { color: s.color },
401
+    barWidth: 15,
402
+    label: { show: true, position: 'top', fontSize: 9, color: s.color }
403
+  }))
404
+})
405
+
406
+onMounted(() => {
407
+  fetchData()
408
+
409
+})
410
+</script>
411
+
412
+<style lang="less" scoped>
413
+.module-brigade-content {
414
+  height: 100%;
415
+  display: flex;
416
+  flex-direction: column;
417
+  gap: 10px;
418
+  overflow-y: auto;
419
+}
420
+
421
+.chart-row {
422
+  display: flex;
423
+  gap: 10px;
424
+  flex-shrink: 0;
425
+}
426
+
427
+.chart-item {
428
+  flex: 1;
429
+  background: #fff;
430
+  border-radius: 6px;
431
+  padding: 15px;
432
+  border: 1px solid #eee;
433
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
434
+  display: flex;
435
+  flex-direction: column;
436
+  min-height: 320px;
437
+}
438
+
439
+.chart-title {
440
+  font-size: 17px;
441
+  color: black;
442
+  margin-bottom: 5px;
443
+  text-align: left;
444
+}
445
+
446
+.echarts {
447
+  flex: 1;
448
+  width: 100%;
449
+  min-height: 0;
450
+}
451
+
452
+.number-display {
453
+  flex: 1;
454
+  display: flex;
455
+  flex-direction: column;
456
+  align-items: center;
457
+  justify-content: center;
458
+}
459
+
460
+.number-value {
461
+  font-size: 48px;
462
+  font-weight: bold;
463
+  color: #3b82f6;
464
+}
465
+
466
+.number-unit {
467
+  font-size: 14px;
468
+  color: #3b82f6;
469
+  margin-top: 5px;
470
+}
471
+</style>

+ 57 - 0
src/views/blockingData/blockingDataScreen/components/ModuleContainer.vue

@@ -0,0 +1,57 @@
1
+<template>
2
+  <div class="module-container">
3
+    <div class="module-header">
4
+      <span class="module-title">{{ title }}</span>
5
+    </div>
6
+    <div class="module-content">
7
+      <slot></slot>
8
+    </div>
9
+  </div>
10
+</template>
11
+
12
+<script setup>
13
+defineProps({
14
+  title: {
15
+    type: String,
16
+    default: ''
17
+  }
18
+})
19
+</script>
20
+
21
+<style lang="less" scoped>
22
+.module-container {
23
+  width: 100%;
24
+  height: 100%;
25
+  display: flex;
26
+  flex-direction: column;
27
+  // background: rgba(13, 80, 122, 0.3);
28
+  // border: 1px solid rgba(112, 207, 231, 0.3);
29
+  // border-radius: 4px;
30
+
31
+}
32
+
33
+.module-header {
34
+  width: 100%;
35
+  padding: 10px;
36
+  display: flex;
37
+  align-items: center;
38
+  text-align: center;
39
+  border: 1px solid #eee;
40
+  justify-content: center;
41
+  margin-bottom: 10px;
42
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
43
+}
44
+
45
+.module-title {
46
+  font-size: 27px;
47
+  font-weight: 600;
48
+  color: #2E75B6;
49
+  text-align: center;
50
+}
51
+
52
+.module-content {
53
+  flex: 1;
54
+
55
+
56
+}
57
+</style>

+ 419 - 0
src/views/blockingData/blockingDataScreen/components/ModuleFour.vue

@@ -0,0 +1,419 @@
1
+<template>
2
+  <module-container title="模块四 放行速率分析">
3
+    <div class="module-four-content">
4
+      <div class="chart-row">
5
+        <div class="chart-item">
6
+          <div class="chart-title">T1航站楼放行速率</div>
7
+          <div ref="rateChart1" class="echarts"></div>
8
+        </div>
9
+        <div class="chart-item">
10
+          <div class="chart-title">T2航站楼放行速率</div>
11
+          <div ref="rateChart2" class="echarts"></div>
12
+        </div>
13
+      </div>
14
+      <div class="chart-row">
15
+        <div class="chart-item">
16
+          <div class="chart-title">大队总平均速率对比(国内)</div>
17
+          <div ref="rateChart3" class="echarts"></div>
18
+        </div>
19
+        <div class="chart-item">
20
+          <div class="chart-title">大队总平均速率对比(国际+中转)</div>
21
+          <div ref="rateChart4" class="echarts"></div>
22
+        </div>
23
+      </div>
24
+    </div>
25
+  </module-container>
26
+</template>
27
+
28
+<script setup>
29
+import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
30
+import * as echarts from 'echarts'
31
+import ModuleContainer from './ModuleContainer.vue'
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'
35
+
36
+// 接收筛选参数
37
+const props = defineProps({
38
+  filterParams: {
39
+    type: Object,
40
+    default: () => ({})
41
+  }
42
+})
43
+
44
+const rateChart1 = ref(null)
45
+const rateChart2 = ref(null)
46
+const rateChart3 = ref(null)
47
+const rateChart4 = ref(null)
48
+
49
+const { setOption: setOption1 } = useEcharts(rateChart1)
50
+const { setOption: setOption2 } = useEcharts(rateChart2)
51
+const { setOption: setOption3 } = useEcharts(rateChart3)
52
+const { setOption: setOption4 } = useEcharts(rateChart4)
53
+
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
68
+  }
69
+
70
+  return processed
71
+}
72
+
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图表设置完成')
170
+    }
171
+
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
+      })
240
+    }
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
+
371
+
372
+onMounted(() => {
373
+  loadData()
374
+})
375
+</script>
376
+
377
+<style lang="less" scoped>
378
+.module-four-content {
379
+  height: 100%;
380
+  display: flex;
381
+  flex-direction: column;
382
+  gap: 10px;
383
+}
384
+
385
+.chart-row {
386
+  flex: 1;
387
+  display: flex;
388
+  gap: 10px;
389
+  min-height: 400px;
390
+  height: 400px;
391
+}
392
+
393
+.chart-item {
394
+  flex: 1;
395
+  display: flex;
396
+  flex-direction: column;
397
+  background: #fff;
398
+  border-radius: 6px;
399
+  padding: 15px;
400
+  border: 1px solid #eee;
401
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
402
+  min-height: 400px;  /* 400px */
403
+  height: 100%;
404
+}
405
+
406
+.chart-title {
407
+  font-size: 17px;
408
+  color: black;
409
+  margin-bottom: 5px;
410
+  text-align: left;
411
+}
412
+
413
+.echarts {
414
+  flex: 1;
415
+  width: 100%;
416
+  min-height: 400px;  /* 400px */
417
+  height: 400px;
418
+}
419
+</style>

Разлика између датотеке није приказан због своје велике величине
+ 1432 - 0
src/views/blockingData/blockingDataScreen/components/ModuleOne.vue


+ 167 - 0
src/views/blockingData/blockingDataScreen/components/ModuleThree.vue

@@ -0,0 +1,167 @@
1
+<template>
2
+  <module-container title="模块三 培训质控分析">
3
+    <div class="module-three-content">
4
+      <div class="chart-card">
5
+        <div class="chart-title">查堵-人员自测漏检次数(总累积)</div>
6
+        <div ref="chart1" class="echarts"></div>
7
+      </div>
8
+      <div class="chart-card">
9
+        <div class="chart-title">查堵培训-人员月考成绩(总累积)</div>
10
+        <div ref="chart2" class="echarts"></div>
11
+      </div>
12
+    </div>
13
+  </module-container>
14
+</template>
15
+
16
+<script setup>
17
+import { ref, onMounted, watch } from 'vue'
18
+import * as echarts from 'echarts'
19
+import ModuleContainer from './ModuleContainer.vue'
20
+import { useEcharts } from '@/hooks/chart.js'
21
+import { monthlyAssessment, selfTestHasMissCheck } from '@/api/blockingData/blockingDataScreen.js'
22
+import { formatDateHy } from '@/utils/index.js'
23
+
24
+// 接收筛选参数
25
+const props = defineProps({
26
+  filterParams: {
27
+    type: Object,
28
+    default: () => ({})
29
+  }
30
+})
31
+
32
+const chart1 = ref(null)
33
+const chart2 = ref(null)
34
+
35
+const { setOption: setOption1 } = useEcharts(chart1)
36
+const { setOption: setOption2 } = useEcharts(chart2)
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
+
103
+const ringPieOption = (data, colors, centerOffset = ['50%', '55%']) => ({
104
+  color: colors,
105
+  legend: {
106
+    data: data.map(item => item.name),
107
+    top: '0%',
108
+    textStyle: { fontSize: 10 }
109
+  },
110
+  series: [{
111
+    type: 'pie',
112
+    radius: ['45%', '70%'],
113
+    center: centerOffset,
114
+    data: data,
115
+    label: {
116
+      show: true,
117
+      formatter: '{b}\n{c} ({d}%)',
118
+      fontSize: 10
119
+    },
120
+    labelLine: {
121
+      show: true,
122
+      length: 15,
123
+      length2: 10
124
+    }
125
+  }]
126
+})
127
+
128
+onMounted(() => {
129
+  loadData()
130
+})
131
+</script>
132
+
133
+<style lang="less" scoped>
134
+.module-three-content {
135
+  height: 100%;
136
+  display: flex;
137
+  gap: 10px;
138
+
139
+}
140
+
141
+.chart-card {
142
+  flex: 1;
143
+  background: #fff;
144
+  border-radius: 6px;
145
+  padding: 15px;
146
+  border: 1px solid #eee;
147
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
148
+  display: flex;
149
+  flex-direction: column;
150
+  min-height: 400px;  /* 400px */
151
+  height: 100%;
152
+}
153
+
154
+.chart-title {
155
+  font-size: 17px;
156
+  color: black;
157
+  margin-bottom: 5px;
158
+  text-align: left;
159
+}
160
+
161
+.echarts {
162
+  flex: 1;
163
+  width: 100%;
164
+  min-height: 400px;  /* 400px */
165
+  height: 400px;
166
+}
167
+</style>

+ 457 - 0
src/views/blockingData/blockingDataScreen/components/ModuleTwo.vue

@@ -0,0 +1,457 @@
1
+<template>
2
+  <module-container title="模块二 查堵人员分析">
3
+    <div class="module-two-content">
4
+      <div class="top-row">
5
+        <div class="table-card">
6
+          <div class="chart-title">查堵-主管排行榜</div>
7
+          <rank-list :rank-data="rankData1" title="查堵-主管排行榜" header-label="排名" header-name="分管主管" header-count="计数" />
8
+        </div>
9
+        <div class="table-card">
10
+          <div class="chart-title">查堵-班组排行榜</div>
11
+          <rank-list :rank-data="rankData2" title="查堵-班组排行榜" header-label="排名" header-name="分管班组长" header-count="计数" />
12
+        </div>
13
+        <div class="table-card">
14
+          <div class="chart-title">查堵-人员排行榜</div>
15
+          <rank-list :rank-data="rankData3" title="查堵-人员排行榜" header-label="排名" header-name="被回查人" header-count="计数" />
16
+        </div>
17
+      </div>
18
+
19
+      <div class="middle-row">
20
+        <div class="chart-card">
21
+          <div class="chart-title">查堵-物品位置分布</div>
22
+          <div ref="chart1" class="echarts"></div>
23
+        </div>
24
+        <div class="chart-card">
25
+          <div class="chart-title">查堵-原因分类</div>
26
+          <div ref="chart2" class="echarts"></div>
27
+        </div>
28
+      </div>
29
+
30
+      <div class="bottom-row">
31
+        <div class="chart-card">
32
+          <div class="chart-title">查堵-开机人员年限分布</div>
33
+          <div ref="chart3" class="echarts"></div>
34
+        </div>
35
+        <div class="chart-card">
36
+          <div class="chart-title">查堵-开机人员性别比例</div>
37
+          <div ref="chart4" class="echarts"></div>
38
+        </div>
39
+        <div class="chart-card">
40
+          <div class="chart-title">查堵-图像难易程度比例</div>
41
+          <div ref="chart5" class="echarts"></div>
42
+        </div>
43
+      </div>
44
+
45
+      <div class="extra-row">
46
+        <div class="chart-card">
47
+          <div class="chart-title">大队开机人员年限分布</div>
48
+          <div ref="chart6" class="echarts"></div>
49
+        </div>
50
+        <div class="chart-card">
51
+          <div class="chart-title">大队开机人员证书分布</div>
52
+          <div ref="chart7" class="echarts"></div>
53
+        </div>
54
+      </div>
55
+    </div>
56
+  </module-container>
57
+</template>
58
+
59
+<script setup>
60
+import { ref, onMounted, reactive, watch } from 'vue'
61
+import * as echarts from 'echarts'
62
+import ModuleContainer from './ModuleContainer.vue'
63
+import RankList from './RankList.vue'
64
+import { useEcharts } from '@/hooks/chart.js'
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'
78
+// 接收筛选参数
79
+const props = defineProps({
80
+  filterParams: {
81
+    type: Object,
82
+    default: () => ({})
83
+  }
84
+})
85
+
86
+const chart1 = ref(null)
87
+const chart2 = ref(null)
88
+const chart3 = ref(null)
89
+const chart4 = ref(null)
90
+const chart5 = ref(null)
91
+const chart6 = ref(null)
92
+const chart7 = ref(null)
93
+
94
+const { setOption: setOption1 } = useEcharts(chart1)
95
+const { setOption: setOption2 } = useEcharts(chart2)
96
+const { setOption: setOption3 } = useEcharts(chart3)
97
+const { setOption: setOption4 } = useEcharts(chart4)
98
+const { setOption: setOption5 } = useEcharts(chart5)
99
+const { setOption: setOption6 } = useEcharts(chart6)
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.missCheckReasonCategory || 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
+    setOption4(pieOption(
190
+      genderRes?.value?.data?.map(item => ({
191
+        name: item.gender || '未知',
192
+        value: item.count || 0
193
+      })) || [],
194
+      ['#3b82f6', '#ec4899']
195
+    ))
196
+
197
+    setOption5(pieOption(
198
+      difficultyRes?.value?.data?.map(item => ({
199
+        name: item.difficultyLevel || '未知',
200
+        value: item.count || 0
201
+      })) || [],
202
+      ['#93c5fd', '#3b82f6', '#64748b', '#e2e8f0']
203
+    ))
204
+
205
+    // 大队开机人员年限分布
206
+    const tenureListData = tenureListRes?.value?.data || []
207
+    if (tenureListData.length > 0) {
208
+      const categories = [...new Set(tenureListData.map(item => item.brigadeName))]
209
+      const tenureLevels = [
210
+        { name: '1年', key: 'cnt1Years' },
211
+        { name: '2年', key: 'cnt2Years' },
212
+        { name: '3年', key: 'cnt3Years' },
213
+        { name: '4年', key: 'cnt4Years' },
214
+        { name: '5年', key: 'cntGe5Year' }
215
+      ]
216
+
217
+      const seriesData = tenureLevels.map(level => ({
218
+        name: level.name,
219
+        type: 'bar',
220
+        data: categories.map(brigade => {
221
+          const item = tenureListData.find(d => d.brigadeName === brigade)
222
+          return item ? item[level.key] : 0
223
+        }),
224
+        itemStyle: {
225
+          color: ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6'][tenureLevels.indexOf(level) % 5]
226
+        },
227
+        barWidth: 15,
228
+        label: { show: true, position: 'top', fontSize: 9 }
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
+          label: { show: true, position: 'top', fontSize: 9 }
268
+        }
269
+      })
270
+
271
+      setOption7({
272
+        grid: { left: '15%', top: '10%', right: '5%', bottom: '15%', containLabel: true },
273
+        xAxis: {
274
+          type: 'category',
275
+          data: certificateCategories,
276
+          axisLabel: { fontSize: 10 },
277
+          axisLine: { lineStyle: { color: '#999' } }
278
+        },
279
+        yAxis: {
280
+          type: 'value',
281
+          axisLabel: { fontSize: 10 },
282
+          axisLine: { lineStyle: { color: '#999' } },
283
+          splitLine: { lineStyle: { color: '#eee' } }
284
+        },
285
+        legend: {
286
+          top: 0,
287
+          right: 10,
288
+          textStyle: { fontSize: 10 }
289
+        },
290
+        series: certificateSeriesData
291
+      })
292
+    }
293
+
294
+  } catch (error) {
295
+    console.error('加载数据失败:', error)
296
+  }
297
+}
298
+
299
+// 监听筛选参数变化
300
+watch(() => props.filterParams, () => {
301
+  loadData()
302
+}, { deep: true })
303
+
304
+const rankData1 = reactive([
305
+
306
+])
307
+
308
+const rankData2 = reactive([
309
+
310
+])
311
+
312
+const rankData3 = reactive([
313
+
314
+])
315
+
316
+const pieOption = (data, colors, isRing = false) => ({
317
+  color: colors,
318
+  legend: {
319
+    orient: 'horizontal',
320
+    top: 'top',
321
+    textStyle: { fontSize: 10 }
322
+  },
323
+  series: [{
324
+    type: 'pie',
325
+    radius: isRing ? ['40%', '50%'] : '60%',
326
+    center: ['50%', '55%'],
327
+    data: data,
328
+    label: {
329
+      show: true,
330
+      formatter: '{b}\n{c} ({d}%)',
331
+      fontSize: 10
332
+    },
333
+
334
+  }]
335
+})
336
+
337
+const barOption = (data, colors) => ({
338
+  grid: { left: '15%', top: '10%', right: '5%', bottom: '10%', containLabel: true },
339
+  xAxis: { type: 'value', axisLabel: { fontSize: 9 } },
340
+  yAxis: { type: 'category', data: data.map(d => d.name), axisLabel: { fontSize: 9 } },
341
+  series: [{
342
+    type: 'bar',
343
+    data: data.map(d => d.value),
344
+    itemStyle: {
345
+      color: (params) => colors[params.dataIndex % colors.length]
346
+    },
347
+    barWidth: 15,
348
+    label: { show: true, position: 'right', fontSize: 10 }
349
+  }]
350
+})
351
+
352
+
353
+
354
+onMounted(() => {
355
+  loadData()
356
+})
357
+</script>
358
+
359
+<style lang="less" scoped>
360
+.module-two-content {
361
+  height: 100%;
362
+  display: flex;
363
+  flex-direction: column;
364
+  gap: 8px;
365
+  overflow-y: auto;
366
+}
367
+
368
+.top-row,
369
+.middle-row,
370
+.bottom-row,
371
+.extra-row {
372
+  display: flex;
373
+  gap: 8px;
374
+  flex-shrink: 0;
375
+}
376
+
377
+.table-card {
378
+  flex: 1;
379
+  background: #fff;
380
+  border-radius: 6px;
381
+  padding: 8px;
382
+  border: 1px solid #eee;
383
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
384
+  display: flex;
385
+  flex-direction: column;
386
+  min-height: 200px;
387
+}
388
+
389
+.chart-card {
390
+  flex: 1;
391
+  background: #fff;
392
+  border-radius: 6px;
393
+  padding: 15px;
394
+  border: 1px solid #eee;
395
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
396
+  display: flex;
397
+  flex-direction: column;
398
+  min-height: 400px;  
399
+}
400
+
401
+.chart-title {
402
+  font-size: 17px;
403
+  color: black;
404
+  margin-bottom: 5px;
405
+  text-align: left;
406
+}
407
+
408
+.rank-table {
409
+  flex: 1;
410
+  overflow-y: auto;
411
+}
412
+
413
+.rank-row {
414
+  display: flex;
415
+  align-items: center;
416
+  padding: 4px 6px;
417
+  border-bottom: 1px solid #f0f0f0;
418
+  font-size: 11px;
419
+}
420
+
421
+.rank-row:last-child {
422
+  border-bottom: none;
423
+}
424
+
425
+.rank-number {
426
+  width: 45px;
427
+  font-weight: bold;
428
+  margin-right: 8px;
429
+}
430
+
431
+.rank-1 {
432
+  color: #eab308;
433
+}
434
+
435
+.rank-2 {
436
+  color: #94a3b8;
437
+}
438
+
439
+.rank-3 {
440
+  color: #f97316;
441
+}
442
+
443
+.rank-name {
444
+  flex: 1;
445
+  color: #333;
446
+}
447
+
448
+.rank-value {
449
+  color: #666;
450
+}
451
+
452
+.echarts {
453
+  flex: 1;
454
+  width: 100%;
455
+  min-height: 250px;
456
+}
457
+</style>

+ 283 - 0
src/views/blockingData/blockingDataScreen/components/RankList.vue

@@ -0,0 +1,283 @@
1
+<template>
2
+  <div class="rank-list-container">
3
+    <div class="rank-list-header">
4
+      <span class="header-label">{{ headerLabel }}</span>
5
+      <span class="header-name">{{ headerName }}</span>
6
+      <span class="header-count">{{ headerCount }}</span>
7
+    </div>
8
+    <div class="rank-list-body">
9
+      <div v-for="(item, index) in rankedData" :key="index" class="rank-item" :class="getItemClass(item.rank, index)">
10
+        <span class="rank-number" :class="'rank-number-' + getRankClass(item.rank)">NO.{{ item.rank }}</span>
11
+        <div class="rank-info">
12
+          <span class="rank-name">{{ item.name }}</span>
13
+          <div class="rank-bar">
14
+            <div class="rank-bar-fill" :class="'rank-bar-fill-' + getRankClass(item.rank)" :style="{ width: getProgressWidth(item.value, maxValue) + '%' }"></div>
15
+          </div>
16
+        </div>
17
+        <span class="rank-count">{{ item.value }}</span>
18
+      </div>
19
+    </div>
20
+  </div>
21
+</template>
22
+
23
+<script setup>
24
+import { computed } from 'vue'
25
+
26
+const props = defineProps({
27
+  rankData: {
28
+    type: Array,
29
+    default: () => []
30
+  },
31
+  title: {
32
+    type: String,
33
+    default: '排行榜'
34
+  },
35
+  headerLabel: {
36
+    type: String,
37
+    default: '排名'
38
+  },
39
+  headerName: {
40
+    type: String,
41
+    default: '分管班组长'
42
+  },
43
+  headerCount: {
44
+    type: String,
45
+    default: '计数'
46
+  }
47
+})
48
+
49
+const maxValue = computed(() => {
50
+  if (props.rankData.length === 0) return 100
51
+  return Math.max(...props.rankData.map(item => item.value))
52
+})
53
+
54
+// 计算并列排名(中国式排名:并列后下一个排名+1)
55
+const rankedData = computed(() => {
56
+  if (props.rankData.length === 0) return []
57
+  
58
+  // 按value降序排序
59
+  const sorted = [...props.rankData].sort((a, b) => b.value - a.value)
60
+  
61
+  let currentRank = 1
62
+  let previousValue = null
63
+  
64
+  return sorted.map((item) => {
65
+    // 如果当前value小于前一个value,排名+1
66
+    if (previousValue !== null && item.value < previousValue) {
67
+      currentRank++
68
+    }
69
+    previousValue = item.value
70
+    return {
71
+      ...item,
72
+      rank: currentRank
73
+    }
74
+  })
75
+})
76
+
77
+const getRankClass = (rank) => {
78
+  if (rank === 1) return 'first'
79
+  if (rank === 2) return 'second'
80
+  if (rank === 3) return 'third'
81
+  return 'default'
82
+}
83
+
84
+const getProgressWidth = (value, max) => {
85
+  return (value / max) * 100
86
+}
87
+
88
+const getItemClass = (rank, index) => {
89
+  const baseClass = 'rank-item rank-item-' + getRankClass(rank)
90
+  if (rank <= 3) return baseClass
91
+  const isGray = (index - 3) % 2 === 0
92
+  return baseClass + (isGray ? ' rank-item-gray' : '')
93
+}
94
+</script>
95
+
96
+<style lang="less" scoped>
97
+.rank-list-container {
98
+  width: 100%;
99
+  height: 400px; /* 固定高度 */
100
+  background: #fff;
101
+  border-radius: 8px;
102
+  border: 1px solid #e5e7eb;
103
+  display: flex;
104
+  flex-direction: column;
105
+  overflow: hidden; /* 防止容器溢出 */
106
+}
107
+
108
+.rank-list-header {
109
+  display: flex;
110
+  align-items: center;
111
+  padding: 8px;
112
+  border-bottom: 1px solid #e5e7eb;
113
+ 
114
+  flex-shrink: 0; /* 防止头部被压缩 */
115
+}
116
+
117
+.header-label {
118
+  width: 80px;
119
+  font-size: 16px;
120
+  font-weight: 600;
121
+  color: #374151;
122
+}
123
+
124
+.header-name {
125
+  flex: 1;
126
+  font-size: 16px;
127
+  font-weight: 600;
128
+  color: #374151;
129
+}
130
+
131
+.header-count {
132
+  width: 80px;
133
+  text-align: right;
134
+  font-size: 16px;
135
+  font-weight: 600;
136
+  color: #374151;
137
+}
138
+
139
+.rank-list-body {
140
+  flex: 1;
141
+  overflow-y: auto; /* 启用垂直滚动 */
142
+
143
+  
144
+  /* 自定义滚动条样式 */
145
+  &::-webkit-scrollbar {
146
+    width: 6px;
147
+  }
148
+  
149
+  &::-webkit-scrollbar-track {
150
+    background: #f1f1f1;
151
+    border-radius: 3px;
152
+  }
153
+  
154
+  &::-webkit-scrollbar-thumb {
155
+    background: #c1c1c1;
156
+    border-radius: 3px;
157
+  }
158
+  
159
+  &::-webkit-scrollbar-thumb:hover {
160
+    background: #a8a8a8;
161
+  }
162
+}
163
+
164
+.rank-item {
165
+  display: flex;
166
+  align-items: center;
167
+  padding: 10px;
168
+  border-radius: 4px;
169
+  
170
+  min-height: 40px; /* 最小高度确保项目可见 */
171
+}
172
+
173
+.rank-item:last-child {
174
+  margin-bottom: 0;
175
+}
176
+
177
+.rank-item-first {
178
+  background-color: #fef9e7;
179
+}
180
+
181
+.rank-item-second {
182
+  background-color: #f3f4f6;
183
+}
184
+
185
+.rank-item-third {
186
+  background-color: #fef2f2;
187
+}
188
+
189
+.rank-item:hover {
190
+  background-color: #f9fafb;
191
+}
192
+
193
+.rank-item-gray {
194
+  background-color: #f3f4f6;
195
+}
196
+
197
+.rank-number {
198
+  font-weight: bold;
199
+  font-size: 15px;
200
+  padding: 4px 8px;
201
+  border-radius: 4px;
202
+  margin-right: 8px;
203
+  min-width: 60px;
204
+  text-align: center;
205
+}
206
+
207
+.rank-number-first {
208
+  background: linear-gradient(135deg, #EFB63D, #F8CA4D);
209
+  color: #fff;
210
+}
211
+
212
+.rank-number-second {
213
+  background: linear-gradient(135deg, #C4CAE1, #E0E4ED);
214
+  color: #fff;
215
+}
216
+
217
+.rank-number-third {
218
+  background: linear-gradient(135deg, #D6A089, #E9C0AF);
219
+  color: #fff;
220
+}
221
+
222
+.rank-number-default {
223
+  background-color: #e5e7eb;
224
+  color: #6b7280;
225
+}
226
+
227
+.rank-info {
228
+  flex: 1;
229
+  display: flex;
230
+  flex-direction: row;
231
+  align-items: center;
232
+  gap: 8px;
233
+}
234
+
235
+.rank-name {
236
+  font-size: 15px;
237
+  color: #1f2937;
238
+  font-weight: 500;
239
+  min-width: 80px;
240
+  overflow: hidden;
241
+  text-overflow: ellipsis;
242
+  white-space: nowrap;
243
+}
244
+
245
+.rank-bar {
246
+  flex: 1;
247
+  height: 12px;
248
+  background-color: #e5e7eb;
249
+  border-radius: 6px;
250
+  overflow: hidden;
251
+}
252
+
253
+.rank-bar-fill {
254
+  height: 100%;
255
+  border-radius: 6px;
256
+  transition: width 0.5s ease-out;
257
+}
258
+
259
+.rank-bar-fill-first {
260
+  background: #FFCA00;
261
+}
262
+
263
+.rank-bar-fill-second {
264
+  background: #C4C9DF;
265
+}
266
+
267
+.rank-bar-fill-third {
268
+  background: #D99B86;
269
+}
270
+
271
+.rank-bar-fill-default {
272
+  background: linear-gradient(90deg, #60a5fa, #3b82f6);
273
+}
274
+
275
+.rank-count {
276
+  width: 80px;
277
+  text-align: right;
278
+  font-size: 15px;
279
+  font-weight: 600;
280
+  color: #1f2937;
281
+  margin-left: 8px;
282
+}
283
+</style>

+ 347 - 0
src/views/blockingData/blockingDataScreen/index.vue

@@ -0,0 +1,347 @@
1
+<template>
2
+  <div class="blocking-data-screen">
3
+    <div class="screen-header">
4
+      <div class="header-title">盛世鹰眸质控系统【查堵】</div>
5
+    </div>
6
+
7
+    <div class="filter-section">
8
+      <div class="filter-row">
9
+        <div class="filter-item">
10
+          <span class="filter-label">日期:</span>
11
+          <el-date-picker v-model="filterParams.dateRange" type="daterange" range-separator="-" start-placeholder="开始日期"
12
+            end-placeholder="结束日期" value-format="YYYY-MM-DD HH:mm:ss" :default-time="defaultTime"
13
+            style="width: 280px" />
14
+        </div>
15
+
16
+        <div class="filter-item">
17
+          <span class="filter-label">分管主管:</span>
18
+          <el-select v-model="filterParams.supervisorId" placeholder="请选择主管" filterable clearable style="width: 200px">
19
+            <el-option v-for="item in supervisorOptions" :key="item.value" :label="item.label" :value="item.value" />
20
+          </el-select>
21
+        </div>
22
+
23
+        <div class="filter-item">
24
+          <span class="filter-label">分管班组长:</span>
25
+          <el-select v-model="filterParams.teamLeaderId" placeholder="请选择班组长" filterable clearable style="width: 200px">
26
+            <el-option v-for="item in teamLeaderOptions" :key="item.value" :label="item.label" :value="item.value" />
27
+          </el-select>
28
+        </div>
29
+        <div class="filter-item">
30
+          <span class="filter-label">航站楼:</span>
31
+          <el-select v-model="filterParams.terminalId" placeholder="请选择区域" clearable style="width: 200px"
32
+            @change="terminalChange">
33
+            <el-option v-for="item in terminalOptions" :key="item.value" :label="item.label" :value="item.value" />
34
+          </el-select>
35
+        </div>
36
+
37
+        <div class="filter-item">
38
+          <span class="filter-label">漏检物品:</span>
39
+          <el-tree-select v-model="filterParams.missCheckItem" :data="missedItemOptions"
40
+            :props="{ value: 'name', label: 'name', children: 'children', disabled: data => data.children && data.children.length > 0 }"
41
+            value-key="id" placeholder="请选择漏检物品" check-strictly style="width: 200px" clearable @change="missCheckItemChange"/>
42
+        </div>
43
+
44
+        <div class="filter-item">
45
+          <span class="filter-label">分大队:</span>
46
+          <el-select v-model="filterParams.brigadeId" placeholder="请选择大队" clearable style="width: 200px">
47
+            <el-option v-for="item in brigadeOptions" :key="item.value" :label="item.label" :value="item.value" />
48
+          </el-select>
49
+        </div>
50
+        <!-- <div class="filter-actions">
51
+          <el-button type="primary" @click="handleFilter">查询</el-button>
52
+          <el-button @click="resetFilter">重置</el-button>
53
+        </div> -->
54
+      </div>
55
+
56
+
57
+    </div>
58
+
59
+    <div class="screen-content">
60
+      <div v-if="filterParams.brigadeId=='all'" class="grid-layout">
61
+        <div class="grid-item">
62
+          <module-one :filter-params="filterParams" />
63
+        </div>
64
+        <div class="grid-item">
65
+          <module-two :filter-params="filterParams" />
66
+        </div>
67
+        <div class="grid-item">
68
+          <module-three :filter-params="filterParams" />
69
+        </div>
70
+        <div class="grid-item">
71
+          <module-four :filter-params="filterParams" />
72
+        </div>
73
+      </div>
74
+      <div v-else class="grid-layout">
75
+        <div class="grid-item">
76
+          <module-brigade-one :filter-params="filterParams" />
77
+        </div>
78
+        <div class="grid-item">
79
+          <module-brigade-two :filter-params="filterParams" />
80
+        </div>
81
+        <div class="grid-item">
82
+          <module-brigade-three :filter-params="filterParams" />
83
+        </div>
84
+      </div>
85
+    </div>
86
+  </div>
87
+</template>
88
+
89
+<script setup>
90
+import { ref, reactive, onMounted } from 'vue'
91
+import { listDept } from '@/api/system/dept'
92
+import { selectUserLeaderListByCondition } from '@/api/system/user'
93
+import { listCategory } from '@/api/system/category'
94
+import { listPosition } from '@/api/system/position'
95
+import ModuleOne from './components/ModuleOne.vue'
96
+import ModuleTwo from './components/ModuleTwo.vue'
97
+import ModuleThree from './components/ModuleThree.vue'
98
+import ModuleFour from './components/ModuleFour.vue'
99
+import ModuleBrigadeOne from './components/ModuleBrigadeOne.vue'
100
+import ModuleBrigadeTwo from './components/ModuleBrigadeTwo.vue'
101
+import ModuleBrigadeThree from './components/ModuleBrigadeThree.vue'
102
+
103
+// 默认时间设置
104
+const defaultTime = [
105
+  new Date(2000, 1, 1, 0, 0, 0),
106
+  new Date(2000, 1, 1, 23, 59, 59)
107
+]
108
+
109
+// 筛选参数
110
+const filterParams = reactive({
111
+  dateRange: [],
112
+  supervisorId: '',
113
+  teamLeaderId: '',
114
+  terminal: '',
115
+  terminalId: '',
116
+  missCheckItem: '',
117
+  brigadeId: 'all'
118
+})
119
+
120
+// 选项数据
121
+const supervisorOptions = ref([])
122
+const teamLeaderOptions = ref([])
123
+const brigadeOptions = ref([])
124
+const missedItemOptions = ref([])
125
+const terminalOptions = ref([])
126
+
127
+// 获取漏检物品树状选项
128
+function getMissedItemOptions() {
129
+  listCategory().then(response => {
130
+    missedItemOptions.value = handleTree(response.data, "id", "parentId")
131
+  })
132
+}
133
+const terminalChange = (value) => {
134
+  if(!value){
135
+    filterParams.terminal = ''
136
+    filterParams.terminalId = ''
137
+    return;
138
+  }
139
+
140
+  filterParams.terminal = terminalOptions.value.find(item => item.value === value).label
141
+}
142
+
143
+const missCheckItemChange = (value) => { 
144
+  if(!value){
145
+    filterParams.missCheckItem = ''
146
+    return;
147
+
148
+  }
149
+}
150
+
151
+// 树形数据处理函数
152
+function handleTree(data, id, parentId) {
153
+  const result = []
154
+  const map = {}
155
+
156
+  data.forEach(item => {
157
+    map[item[id]] = { ...item, children: [] }
158
+  })
159
+
160
+  data.forEach(item => {
161
+    const parent = map[item[parentId]]
162
+    if (parent) {
163
+      parent.children.push(map[item[id]])
164
+    } else {
165
+      result.push(map[item[id]])
166
+    }
167
+  })
168
+
169
+  return result
170
+}
171
+
172
+// 获取航站楼选项
173
+function getTerminalOptions() {
174
+  listPosition({ positionType: 'TERMINL' }).then(response => {
175
+    terminalOptions.value = (response.data || []).map(item => ({
176
+      value: item.id,
177
+      label: item.name
178
+    }))
179
+    // 添加"整体"选项到最前面
180
+    terminalOptions.value.unshift({ label: '整体', value: 'all' })
181
+  })
182
+}
183
+function getTeamLeaderOptions() {
184
+  selectUserLeaderListByCondition({ roleKeyList: ['banzuzhang'] }).then(response => {
185
+    teamLeaderOptions.value = (response.data || []).map(item => ({
186
+      value: item.userId,
187
+      label: item.nickName
188
+    }))
189
+  })
190
+}
191
+
192
+// 获取主管选项
193
+function getSupervisorOptions() {
194
+  selectUserLeaderListByCondition({ roleKeyList: ['kezhang'] }).then(response => {
195
+    supervisorOptions.value = (response.data || []).map(item => ({
196
+      value: item.userId,
197
+      label: item.nickName
198
+    }))
199
+  })
200
+}
201
+
202
+// 获取大队选项
203
+function getBrigadeOptions() {
204
+  listDept({}).then(response => {
205
+    const deptList = response.data || []
206
+    // 筛选类型为 BRIGADE 的部门,并过滤特定的大队ID
207
+    brigadeOptions.value = deptList.filter(item => item.deptType === 'BRIGADE' && [311, 314, 315].includes(item.deptId)).map(item => ({
208
+      value: item.deptId,
209
+      label: item.deptName
210
+    }))
211
+    // 添加"全站"选项到最前面
212
+    brigadeOptions.value.unshift({ label: '全站', value: 'all' })
213
+  })
214
+}
215
+const setDefaultDateRange = () => {
216
+  const now = new Date()
217
+  const startOfYear = new Date(now.getFullYear(), 0, 1, 0, 0, 0)
218
+  const endOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59)
219
+
220
+  // 格式化日期为 YYYY-MM-DD HH:mm:ss 格式
221
+  const formatDate = (date) => {
222
+    const year = date.getFullYear()
223
+    const month = String(date.getMonth() + 1).padStart(2, '0')
224
+    const day = String(date.getDate()).padStart(2, '0')
225
+    const hours = String(date.getHours()).padStart(2, '0')
226
+    const minutes = String(date.getMinutes()).padStart(2, '0')
227
+    const seconds = String(date.getSeconds()).padStart(2, '0')
228
+    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
229
+  }
230
+
231
+  filterParams.dateRange = [formatDate(startOfYear), formatDate(endOfToday)]
232
+}
233
+
234
+// 处理筛选
235
+const handleFilter = () => {
236
+  console.log('筛选参数:', filterParams)
237
+  // 强制触发响应式更新,确保子组件能监听到变化
238
+  filterParams.dateRange = [...filterParams.dateRange]
239
+}
240
+
241
+// 重置筛选
242
+const resetFilter = () => {
243
+  Object.assign(filterParams, {
244
+    dateRange: [],
245
+    supervisorId: '',
246
+    teamLeaderId: '',
247
+    terminal: '',
248
+    missCheckItem: '',
249
+    terminalId: '',
250
+    brigadeId: 'all'
251
+  })
252
+  setDefaultDateRange()
253
+}
254
+
255
+// 组件挂载时设置默认日期和获取用户数据
256
+onMounted(() => {
257
+  setDefaultDateRange()
258
+  getTeamLeaderOptions()
259
+  getSupervisorOptions()
260
+  getBrigadeOptions()
261
+  getMissedItemOptions()
262
+  getTerminalOptions()
263
+})
264
+</script>
265
+
266
+<style lang="less" scoped>
267
+.blocking-data-screen {
268
+  width: 100%;
269
+  height: 100vh;
270
+
271
+  display: flex;
272
+  flex-direction: column;
273
+  overflow: hidden;
274
+}
275
+
276
+.screen-header {
277
+  height: 60px;
278
+  display: flex;
279
+  align-items: center;
280
+  justify-content: space-between;
281
+  padding: 0 30px;
282
+
283
+  // border-bottom: 2px solid rgba(112, 207, 231, 0.5);
284
+}
285
+
286
+.header-title {
287
+  font-size: 24px;
288
+  font-weight: bold;
289
+  color: black;
290
+  // text-shadow: 0 0 10px rgba(112, 207, 231, 0.5);
291
+}
292
+
293
+.filter-section {
294
+  // background: #f8f9fa;
295
+  padding: 20px 30px;
296
+  border-bottom: 1px solid #e8e8e8;
297
+}
298
+
299
+.filter-row {
300
+  display: flex;
301
+  align-items: center;
302
+  gap: 20px;
303
+  margin-bottom: 15px;
304
+  flex-wrap: wrap;
305
+
306
+  &:last-child {
307
+    margin-bottom: 0;
308
+  }
309
+}
310
+
311
+.filter-item {
312
+  display: flex;
313
+  align-items: center;
314
+  gap: 8px;
315
+}
316
+
317
+.filter-label {
318
+  font-size: 14px;
319
+  color: #606266;
320
+  font-weight: 500;
321
+  white-space: nowrap;
322
+}
323
+
324
+.filter-actions {
325
+  display: flex;
326
+  align-items: center;
327
+  gap: 10px;
328
+  margin-left: auto;
329
+}
330
+
331
+.screen-content {
332
+  flex: 1;
333
+  padding: 20px;
334
+  overflow: auto;
335
+}
336
+
337
+.grid-layout {
338
+  display: grid;
339
+  grid-template-columns: 1fr;
340
+  gap: 20px;
341
+  height: 100%;
342
+}
343
+
344
+.grid-item {
345
+  width: 100%;
346
+}
347
+</style>

+ 0 - 0
src/views/blockingData/blockingDataScreen/查堵大屏


+ 547 - 0
src/views/blockingData/dailyLuggageCheckInList/index.vue

@@ -0,0 +1,547 @@
1
+<template>
2
+  <div class="app-container">
3
+    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch"> <el-form-item label="大队"
4
+        prop="brigadeId">
5
+        <el-select v-model="queryParams.brigadeId" placeholder="请选择大队" filterable clearable style="width: 200px">
6
+          <el-option v-for="item in brigadeOptions" :key="item.value" :label="item.label" :value="item.value" />
7
+        </el-select>
8
+      </el-form-item>
9
+      <el-form-item label="日期" prop="startDate">
10
+        <el-date-picker clearable v-model="queryParams.startDate" type="date" placeholder="选择日期" value-format="YYYY-MM-DD"
11
+          style="width: 200px" />
12
+      </el-form-item>
13
+      <el-form-item>
14
+        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
15
+        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
16
+      </el-form-item>
17
+    </el-form>
18
+
19
+    <el-row :gutter="10" class="mb8">
20
+      <el-col :span="1.5">
21
+        <el-button type="primary" plain icon="Plus" @click="handleAdd"
22
+          v-hasPermi="['dailyLuggageCheckIn:dailyLuggageCheckIn:add']">新增</el-button>
23
+      </el-col>
24
+      <el-col :span="1.5">
25
+        <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete"
26
+          v-hasPermi="['dailyLuggageCheckIn:dailyLuggageCheckIn:remove']">删除</el-button>
27
+      </el-col>
28
+      <el-col :span="1.5">
29
+        <el-button type="warning" plain icon="Download" @click="handleExport"
30
+          v-hasPermi="['dailyLuggageCheckIn:dailyLuggageCheckIn:export']">导出</el-button>
31
+      </el-col>
32
+      <el-col :span="1.5">
33
+        <el-button type="info" plain icon="Upload" @click="handleImport"
34
+          v-hasPermi="['dailyLuggageCheckIn:dailyLuggageCheckIn:import']">导入</el-button>
35
+      </el-col>
36
+      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
37
+    </el-row>
38
+
39
+    <el-table v-loading="loading" :data="dailyLuggageCheckInList" @selection-change="handleSelectionChange">
40
+      <el-table-column type="selection" width="55" align="center" />
41
+      <el-table-column label="日期" align="center" prop="statDate" min-width="120" />
42
+      <el-table-column label="班次" align="center" prop="shiftType" min-width="100" />
43
+      <el-table-column label="当班大队" align="center" prop="brigadeName" min-width="120" />
44
+      <el-table-column label="T1旅检过检行李数" align="center" prop="t1TravelLuggageCount" min-width="150" />
45
+      <el-table-column label="T2旅检过检行李数" align="center" prop="t2TravelLuggageCount" min-width="150" />
46
+      <el-table-column label="过检行李合计" align="center" prop="totalLuggageCount" min-width="120" />
47
+      <el-table-column label="T1旅检查堵件数" align="center" prop="t1TravelBlockedCount" min-width="140" />
48
+      <el-table-column label="T1旅检万分率" align="center" prop="t1TravelBlockRate" min-width="130" />
49
+      <el-table-column label="T2旅检查堵件数" align="center" prop="t2TravelBlockedCount" min-width="140" />
50
+      <el-table-column label="T2旅检万分率" align="center" prop="t2TravelBlockRate" min-width="130" />
51
+      <el-table-column label="T1行检过检行李数" align="center" prop="t1WalkLuggageCount" min-width="150" />
52
+      <el-table-column label="T2行检过检行李数" align="center" prop="t2WalkLuggageCount" min-width="150" />
53
+      <el-table-column label="T1行检查堵件数" align="center" prop="t1WalkBlockedCount" min-width="140" />
54
+      <el-table-column label="T1行检万分率" align="center" prop="t1WalkBlockRate" min-width="130" />
55
+      <el-table-column label="T2行检查堵件数" align="center" prop="t2WalkBlockedCount" min-width="140" />
56
+      <el-table-column label="T2行检万分率" align="center" prop="t2WalkBlockRate" min-width="130" />
57
+      <el-table-column label="查堵合计件数" align="center" prop="totalBlockedCount" min-width="120" />
58
+      <el-table-column label="当日查堵万分率" align="center" prop="dailyBlockRate" min-width="130" />
59
+      <el-table-column label="T1复查图像总数" align="center" prop="t1ReviewImageTotal" min-width="140" />
60
+      <el-table-column label="T1-AI标记总数" align="center" prop="t1AiMarkTotal" min-width="140" />
61
+      <el-table-column label="T1-AI误判总数" align="center" prop="t1AiErrorTotal" min-width="140" />
62
+      <el-table-column label="T1-AI漏判总数" align="center" prop="t1AiMissTotal" min-width="140" />
63
+      <el-table-column label="T2复查图像总数" align="center" prop="t2ReviewImageTotal" min-width="140" />
64
+      <el-table-column label="T2-AI标记总数" align="center" prop="t2AiMarkTotal" min-width="140" />
65
+      <el-table-column label="T2-AI误判总数" align="center" prop="t2AiErrorTotal" min-width="140" />
66
+      <el-table-column label="T2-AI漏判总数" align="center" prop="t2AiMissTotal" min-width="140" />
67
+      <el-table-column label="其他(VP通道)数量" align="center" prop="otherVipCount" min-width="150" />
68
+      <el-table-column label="AI复查图像总数" align="center" prop="aiReviewImageTotal" min-width="150" />
69
+      <el-table-column label="AI标记图像总数" align="center" prop="aiMarkTotal" min-width="150" />
70
+      <el-table-column label="AI漏判图像总数" align="center" prop="aiMissImageTotal" min-width="150" />
71
+      <el-table-column label="AI误判图像总数" align="center" prop="aiErrorImageTotal" min-width="150" />
72
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="140" fixed="right">
73
+        <template #default="scope">
74
+          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
75
+            v-hasPermi="['dailyLuggageCheckIn:dailyLuggageCheckIn:edit']">修改</el-button>
76
+          <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
77
+            v-hasPermi="['dailyLuggageCheckIn:dailyLuggageCheckIn:remove']">删除</el-button>
78
+        </template>
79
+      </el-table-column>
80
+    </el-table>
81
+
82
+    <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
83
+      v-model:limit="queryParams.pageSize" @pagination="getList" />
84
+
85
+    <!-- 添加或修改每日行李查缉对话框 -->
86
+    <el-dialog :title="title" v-model="open" width="800px" append-to-body @closed="reset" destroy-on-close>
87
+      <el-form ref="dailyLuggageCheckInRef" :model="form" :rules="rules" label-width="140px">
88
+        <el-row :gutter="20">
89
+          <el-col :span="12">
90
+            <el-form-item label="日期" prop="statDate">
91
+              <el-date-picker v-model="form.statDate" type="date" placeholder="选择日期" value-format="YYYY-MM-DD"
92
+                style="width: 100%" />
93
+            </el-form-item>
94
+          </el-col>
95
+          <el-col :span="12">
96
+            <el-form-item label="班次" prop="shiftType">
97
+              <el-select v-model="form.shiftType" placeholder="请选择班次" filterable style="width: 100%">
98
+                <el-option label="早班" value="早班" />
99
+                <el-option label="晚班" value="晚班" />
100
+              </el-select>
101
+            </el-form-item>
102
+          </el-col>
103
+        </el-row>
104
+        <el-form-item label="当班大队" prop="brigadeId">
105
+          <el-select v-model="form.brigadeId" placeholder="请选择当班大队" filterable style="width: 100%">
106
+            <el-option v-for="item in brigadeOptions" :key="item.value" :label="item.label" :value="item.value" />
107
+          </el-select>
108
+        </el-form-item>
109
+        <el-row :gutter="20">
110
+          <el-col :span="12">
111
+            <el-form-item label="T1旅检过检行李数" prop="t1TravelLuggageCount">
112
+              <el-input-number v-model="form.t1TravelLuggageCount" :min="0" :precision="0" style="width: 100%" />
113
+            </el-form-item>
114
+          </el-col>
115
+          <el-col :span="12">
116
+            <el-form-item label="T2旅检过检行李数" prop="t2TravelLuggageCount">
117
+              <el-input-number v-model="form.t2TravelLuggageCount" :min="0" :precision="0" style="width: 100%" />
118
+            </el-form-item>
119
+          </el-col>
120
+        </el-row>
121
+        <el-row :gutter="20">
122
+          <el-col :span="12">
123
+            <el-form-item label="T1行检过检行李数" prop="t1WalkLuggageCount">
124
+              <el-input-number v-model="form.t1WalkLuggageCount" :min="0" :precision="0" style="width: 100%" />
125
+            </el-form-item>
126
+          </el-col>
127
+          <el-col :span="12">
128
+            <el-form-item label="T2行检过检行李数" prop="t2WalkLuggageCount">
129
+              <el-input-number v-model="form.t2WalkLuggageCount" :min="0" :precision="0" style="width: 100%" />
130
+            </el-form-item>
131
+          </el-col>
132
+        </el-row>
133
+        <el-row :gutter="20">
134
+          <!--  <el-col :span="12">
135
+          <el-form-item label="过检行李合计" prop="totalLuggageCount">
136
+              <el-input-number v-model="form.totalLuggageCount" :min="0" style="width: 100%" />
137
+            </el-form-item> 
138
+          </el-col> -->
139
+        </el-row>
140
+        <el-row :gutter="20">
141
+          <el-col :span="12">
142
+            <el-form-item label="T1旅检查堵件数" prop="t1TravelBlockedCount">
143
+              <el-input-number v-model="form.t1TravelBlockedCount" :min="0" :precision="0" style="width: 100%" />
144
+            </el-form-item>
145
+          </el-col>
146
+          <el-col :span="12">
147
+            <el-form-item label="T2旅检查堵件数" prop="t2TravelBlockedCount">
148
+              <el-input-number v-model="form.t2TravelBlockedCount" :min="0" :precision="0" style="width: 100%" />
149
+            </el-form-item>
150
+          </el-col>
151
+          <!--  <el-col :span="12">
152
+            <el-form-item label="T1旅检万分率" prop="t1TravelBlockRate">
153
+              <el-input-number v-model="form.t1TravelBlockRate" :min="0" :precision="2" style="width: 100%" />
154
+            </el-form-item> 
155
+          </el-col>-->
156
+        </el-row>
157
+        <el-row :gutter="20">
158
+          <el-col :span="12">
159
+            <el-form-item label="T1行检查堵件数" prop="t1WalkBlockedCount">
160
+              <el-input-number v-model="form.t1WalkBlockedCount" :min="0" :precision="0" style="width: 100%" />
161
+            </el-form-item>
162
+          </el-col>
163
+          <el-col :span="12">
164
+            <el-form-item label="T2行检查堵件数" prop="t2WalkBlockedCount">
165
+              <el-input-number v-model="form.t2WalkBlockedCount" :min="0" :precision="0" style="width: 100%" />
166
+            </el-form-item>
167
+          </el-col>
168
+          <!--<el-col :span="12">
169
+             <el-form-item label="T2旅检万分率" prop="t2TravelBlockRate">
170
+              <el-input-number v-model="form.t2TravelBlockRate" :min="0" :precision="2" style="width: 100%" />
171
+            </el-form-item> 
172
+          </el-col>-->
173
+        </el-row>
174
+
175
+
176
+        <!--  <el-col :span="12">
177
+          <el-form-item label="T1行检万分率" prop="t1WalkBlockRate">
178
+              <el-input-number v-model="form.t1WalkBlockRate" :min="0" :precision="2" style="width: 100%" />
179
+            </el-form-item> 
180
+          </el-col>-->
181
+
182
+
183
+        <!-- <el-col :span="12">
184
+            <el-form-item label="T2行检万分率" prop="t2WalkBlockRate">
185
+              <el-input-number v-model="form.t2WalkBlockRate" :min="0" :precision="2" style="width: 100%" />
186
+            </el-form-item>
187
+          </el-col> -->
188
+
189
+
190
+          <!--   <el-col :span="12">
191
+           <el-form-item label="当日查堵万分率" prop="dailyBlockRate">
192
+              <el-input-number v-model="form.dailyBlockRate" :min="0" :precision="2" style="width: 100%" />
193
+            </el-form-item> 
194
+          </el-col>-->
195
+    
196
+        <el-row :gutter="20">
197
+          <el-col :span="12">
198
+            <el-form-item label="T1复查图像总数" prop="t1ReviewImageTotal">
199
+              <el-input-number v-model="form.t1ReviewImageTotal" :min="0" :precision="0" style="width: 100%" />
200
+            </el-form-item>
201
+          </el-col>
202
+          <el-col :span="12">
203
+            <el-form-item label="T1-AI标记总数" prop="t1AiMarkTotal">
204
+              <el-input-number v-model="form.t1AiMarkTotal" :min="0" :precision="0" style="width: 100%" />
205
+            </el-form-item>
206
+          </el-col>
207
+        </el-row>
208
+        <el-row :gutter="20">
209
+          <el-col :span="12">
210
+            <el-form-item label="T1-AI误判总数" prop="t1AiErrorTotal">
211
+              <el-input-number v-model="form.t1AiErrorTotal" :min="0" :precision="0" style="width: 100%" />
212
+            </el-form-item>
213
+          </el-col>
214
+          <el-col :span="12">
215
+            <el-form-item label="T1-AI漏判总数" prop="t1AiMissTotal">
216
+              <el-input-number v-model="form.t1AiMissTotal" :min="0" :precision="0" style="width: 100%" />
217
+            </el-form-item>
218
+          </el-col>
219
+        </el-row>
220
+        <el-row :gutter="20">
221
+          <el-col :span="12">
222
+            <el-form-item label="T2复查图像总数" prop="t2ReviewImageTotal">
223
+              <el-input-number v-model="form.t2ReviewImageTotal" :min="0" :precision="0" style="width: 100%" />
224
+            </el-form-item>
225
+          </el-col>
226
+          <el-col :span="12">
227
+            <el-form-item label="T2-AI标记总数" prop="t2AiMarkTotal">
228
+              <el-input-number v-model="form.t2AiMarkTotal" :min="0" :precision="0" style="width: 100%" />
229
+            </el-form-item>
230
+          </el-col>
231
+        </el-row>
232
+        <el-row :gutter="20">
233
+          <el-col :span="12">
234
+            <el-form-item label="T2-AI误判总数" prop="t2AiErrorTotal">
235
+              <el-input-number v-model="form.t2AiErrorTotal" :min="0" :precision="0" style="width: 100%" />
236
+            </el-form-item>
237
+          </el-col>
238
+          <el-col :span="12">
239
+            <el-form-item label="T2-AI漏判总数" prop="t2AiMissTotal">
240
+              <el-input-number v-model="form.t2AiMissTotal" :min="0" :precision="0" style="width: 100%" />
241
+            </el-form-item>
242
+          </el-col>
243
+        </el-row>
244
+        <el-form-item label="其他(VP通道)数量" prop="otherVipCount">
245
+          <el-input-number v-model="form.otherVipCount" :min="0" style="width: 100%" />
246
+        </el-form-item>
247
+
248
+      </el-form>
249
+      <template #footer>
250
+        <div class="dialog-footer">
251
+          <el-button type="primary" @click="submitForm">确 定</el-button>
252
+          <el-button @click="cancel">取 消</el-button>
253
+        </div>
254
+      </template>
255
+    </el-dialog>
256
+
257
+    <!-- 导入对话框 -->
258
+    <el-dialog title="导入" v-model="upload.open" width="500px" append-to-body>
259
+      <el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url"
260
+        :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess"
261
+        :auto-upload="false" drag>
262
+        <el-icon class="el-icon--upload"><upload-filled /></el-icon>
263
+        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
264
+        <template #tip>
265
+          <div class="el-upload__tip text-center">
266
+            <span>仅允许导入xls、xlsx格式文件。</span>
267
+            <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline"
268
+              @click="importTemplate">下载模板</el-link>
269
+          </div>
270
+        </template>
271
+      </el-upload>
272
+      <template #footer>
273
+        <div class="dialog-footer">
274
+          <el-button type="primary" @click="submitFileForm">确 定</el-button>
275
+          <el-button @click="upload.open = false">取 消</el-button>
276
+        </div>
277
+      </template>
278
+    </el-dialog>
279
+  </div>
280
+</template>
281
+
282
+<script setup>
283
+import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
284
+import { listDailyLuggageCheckIn, getDailyLuggageCheckIn, delDailyLuggageCheckIn, addDailyLuggageCheckIn, updateDailyLuggageCheckIn, exportDailyLuggageCheckIn, downloadTemplate } from '@/api/blockingData/dailyLuggageCheckInList'
285
+import { listDept } from '@/api/system/dept'
286
+import { getToken } from '@/utils/auth'
287
+import { UploadFilled } from '@element-plus/icons-vue'
288
+
289
+const { proxy } = getCurrentInstance()
290
+
291
+// 遮罩层
292
+const loading = ref(true)
293
+// 选中数组
294
+const ids = ref([])
295
+// 非单个禁用
296
+const single = ref(true)
297
+// 非多个禁用
298
+const multiple = ref(true)
299
+// 显示搜索条件
300
+const showSearch = ref(true)
301
+// 总条数
302
+const total = ref(0)
303
+// 每日行李查缉表格数据
304
+const dailyLuggageCheckInList = ref([])
305
+// 弹出层标题
306
+const title = ref('')
307
+// 是否显示弹出层
308
+const open = ref(false)
309
+
310
+const upload = reactive({
311
+  open: false,
312
+  isUploading: false,
313
+  headers: { Authorization: 'Bearer ' + getToken() },
314
+  url: import.meta.env.VITE_APP_BASE_API + '/blocked/daily/importData'
315
+})
316
+
317
+// 查询参数
318
+const queryParams = reactive({
319
+  pageNum: 1,
320
+  pageSize: 10,
321
+  statDate: null,
322
+  shiftType: null
323
+})
324
+
325
+// 表单参数
326
+const form = reactive({})
327
+
328
+// 表单校验
329
+const rules = {
330
+  statDate: [{ required: true, message: '日期不能为空', trigger: 'change' }],
331
+  shiftType: [{ required: true, message: '班次不能为空', trigger: 'change' }],
332
+  brigadeId: [{ required: true, message: '当班大队不能为空', trigger: 'change' }],
333
+  t1TravelLuggageCount: [{ required: true, message: 'T1旅检过检行李数不能为空', trigger: 'change' }],
334
+  t2TravelLuggageCount: [{ required: true, message: 'T2旅检过检行李数不能为空', trigger: 'change' }],
335
+  t1WalkLuggageCount: [{ required: true, message: 'T1行检过检行李数不能为空', trigger: 'change' }],
336
+  t2WalkLuggageCount: [{ required: true, message: 'T2行检过检行李数不能为空', trigger: 'change' }],
337
+  t1TravelBlockedCount: [{ required: true, message: 'T1旅检查堵件数不能为空', trigger: 'change' }],
338
+  t2TravelBlockedCount: [{ required: true, message: 'T2旅检查堵件数不能为空', trigger: 'change' }],
339
+  t1WalkBlockedCount: [{ required: true, message: 'T1行检查堵件数不能为空', trigger: 'change' }],
340
+  t2WalkBlockedCount: [{ required: true, message: 'T2行检查堵件数不能为空', trigger: 'change' }],
341
+  t1ReviewImageTotal: [{ required: true, message: 'T1复查图像总数不能为空', trigger: 'change' }],
342
+  t1AiMarkTotal: [{ required: true, message: 'T1-AI标记总数不能为空', trigger: 'change' }],
343
+  t1AiErrorTotal: [{ required: true, message: 'T1-AI误判总数不能为空', trigger: 'change' }],
344
+  t1AiMissTotal: [{ required: true, message: 'T1-AI漏判总数不能为空', trigger: 'change' }],
345
+  t2ReviewImageTotal: [{ required: true, message: 'T2复查图像总数不能为空', trigger: 'change' }],
346
+  t2AiMarkTotal: [{ required: true, message: 'T2-AI标记总数不能为空', trigger: 'change' }],
347
+  t2AiErrorTotal: [{ required: true, message: 'T2-AI误判总数不能为空', trigger: 'change' }],
348
+  t2AiMissTotal: [{ required: true, message: 'T2-AI漏判总数不能为空', trigger: 'change' }]
349
+  // otherVipCount 不设置必填校验
350
+}
351
+
352
+// 大队选项
353
+const brigadeOptions = ref([])
354
+
355
+/** 查询每日行李查缉列表 */
356
+function getList() {
357
+  loading.value = true
358
+  listDailyLuggageCheckIn(queryParams).then(response => {
359
+    dailyLuggageCheckInList.value = response.rows
360
+    total.value = response.total
361
+    loading.value = false
362
+  })
363
+}
364
+
365
+/** 获取部门列表 */
366
+function getDeptList() {
367
+  listDept({}).then(response => {
368
+    const deptList = response.data || []
369
+    brigadeOptions.value = deptList.filter(item => item.deptType === 'BRIGADE' && [311, 314, 315].includes(item.deptId)).map(item => ({
370
+      value: item.deptId,
371
+      label: item.deptName
372
+    }))
373
+  })
374
+}
375
+
376
+// 取消按钮
377
+function cancel() {
378
+  open.value = false
379
+  reset()
380
+}
381
+
382
+// 表单重置
383
+function reset() {
384
+  Object.assign(form, {
385
+    id: null,
386
+    statDate: null,
387
+    shiftType: null,
388
+    brigadeId: null,
389
+    brigadeName: null,
390
+    t1TravelLuggageCount: 0,
391
+    t2TravelLuggageCount: 0,
392
+    t1WalkLuggageCount: 0,
393
+    t2WalkLuggageCount: 0,
394
+    totalLuggageCount: 0,
395
+    t1TravelBlockedCount: 0,
396
+    t1TravelBlockRate: 0,
397
+    t2TravelBlockedCount: 0,
398
+    t2TravelBlockRate: 0,
399
+    t1WalkBlockedCount: 0,
400
+    t1WalkBlockRate: 0,
401
+    t2WalkBlockedCount: 0,
402
+    t2WalkBlockRate: 0,
403
+    totalBlockedCount: 0,
404
+    dailyBlockRate: 0,
405
+    t1ReviewImageTotal: 0,
406
+    t1AiMarkTotal: 0,
407
+    t1AiErrorTotal: 0,
408
+    t1AiMissTotal: 0,
409
+    t2ReviewImageTotal: 0,
410
+    t2AiMarkTotal: 0,
411
+    t2AiErrorTotal: 0,
412
+    t2AiMissTotal: 0,
413
+    otherVipCount: 0,
414
+    aiReviewImageTotal: 0,
415
+    aiMarkTotal: 0,
416
+    aiMissImageTotal: 0,
417
+    aiErrorImageTotal: 0
418
+  })
419
+  proxy.resetForm('dailyLuggageCheckInRef')
420
+}
421
+
422
+/** 搜索按钮操作 */
423
+function handleQuery() {
424
+  queryParams.pageNum = 1
425
+  getList()
426
+}
427
+
428
+/** 重置按钮操作 */
429
+function resetQuery() {
430
+  proxy.resetForm('queryRef')
431
+  handleQuery()
432
+}
433
+
434
+// 多选框选中数据
435
+function handleSelectionChange(selection) {
436
+  ids.value = selection.map(item => item.id)
437
+  single.value = selection.length !== 1
438
+  multiple.value = !selection.length
439
+}
440
+
441
+/** 新增按钮操作 */
442
+function handleAdd() {
443
+  reset()
444
+  // 设置默认当前日期
445
+  form.statDate = new Date().toISOString().split('T')[0]
446
+  open.value = true
447
+  title.value = '添加每日行李查缉'
448
+}
449
+
450
+/** 修改按钮操作 */
451
+function handleUpdate(row) {
452
+  reset()
453
+  const id = row.id || ids.value[0]
454
+  getDailyLuggageCheckIn(id).then(response => {
455
+    Object.assign(form, response.data)
456
+    open.value = true
457
+    title.value = '修改每日行李查缉'
458
+  })
459
+}
460
+
461
+/** 提交按钮 */
462
+function submitForm() {
463
+  proxy.$refs.dailyLuggageCheckInRef.validate(valid => {
464
+    if (valid) {
465
+      // 获取brigadeName
466
+      const selectedBrigade = brigadeOptions.value.find(item => item.value === form.brigadeId)
467
+      if (selectedBrigade) {
468
+        form.brigadeName = selectedBrigade.label
469
+      }
470
+
471
+      if (form.id != null) {
472
+        updateDailyLuggageCheckIn(form).then(response => {
473
+          proxy.$modal.msgSuccess('修改成功')
474
+          open.value = false
475
+          reset()
476
+          getList()
477
+        })
478
+      } else {
479
+        addDailyLuggageCheckIn(form).then(response => {
480
+          proxy.$modal.msgSuccess('新增成功')
481
+          open.value = false
482
+          reset()
483
+          getList()
484
+        })
485
+      }
486
+    }
487
+  })
488
+}
489
+
490
+/** 删除按钮操作 */
491
+function handleDelete(row) {
492
+  const id = row.id || ids.value
493
+  proxy.$modal.confirm('是否确认删除每日行李查缉编号为"' + id + '"的数据项?').then(function () {
494
+    return delDailyLuggageCheckIn(id)
495
+  }).then(() => {
496
+    getList()
497
+    proxy.$modal.msgSuccess('删除成功')
498
+  }).catch(() => { })
499
+}
500
+
501
+/** 导出按钮操作 */
502
+function handleExport() {
503
+  proxy.download('/blocked/daily/export', {
504
+    ...queryParams
505
+  }, `每日行李过检行李查堵_${new Date().getTime()}.xlsx`)
506
+}
507
+
508
+/** 导入按钮操作 */
509
+function handleImport() {
510
+  upload.open = true
511
+}
512
+
513
+/** 下载模板操作 */
514
+function importTemplate() {
515
+  proxy.download('blocked/daily/importTemplate', {}, `每日行李过检行李查堵模板_${new Date().getTime()}.xlsx`)
516
+}
517
+
518
+/** 文件上传中处理 */
519
+const handleFileUploadProgress = (event, file, fileList) => {
520
+  upload.isUploading = true
521
+}
522
+
523
+/** 文件上传成功处理 */
524
+const handleFileSuccess = (response, file, fileList) => {
525
+  upload.open = false
526
+  upload.isUploading = false
527
+  proxy.$refs.uploadRef.handleRemove(file)
528
+  proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true })
529
+  getList()
530
+}
531
+
532
+/** 提交上传文件 */
533
+function submitFileForm() {
534
+  proxy.$refs.uploadRef.submit()
535
+}
536
+
537
+onMounted(() => {
538
+  getList()
539
+  getDeptList()
540
+})
541
+</script>
542
+
543
+<style lang="less" scoped>
544
+.app-container {
545
+  padding: 20px;
546
+}
547
+</style>

+ 0 - 0
src/views/blockingData/dailyLuggageCheckInList/每日行李过检查堵表


+ 427 - 0
src/views/blockingData/dailyLuggageInspectionScheduleByTime/index.vue

@@ -0,0 +1,427 @@
1
+<template>
2
+  <div class="app-container">
3
+    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
4
+      <el-form-item label="大队" prop="brigadeId">
5
+        <el-select v-model="queryParams.brigadeId" placeholder="请选择大队" filterable clearable style="width: 200px">
6
+          <el-option v-for="item in brigadeOptions" :key="item.value" :label="item.label" :value="item.value" />
7
+        </el-select>
8
+      </el-form-item>
9
+      <el-form-item label="统计日期" prop="statDate">
10
+        <el-date-picker clearable v-model="queryParams.statDate" type="date" placeholder="选择统计日期"
11
+          value-format="YYYY-MM-DD" style="width: 200px" />
12
+      </el-form-item>
13
+      <el-form-item>
14
+        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
15
+        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
16
+      </el-form-item>
17
+    </el-form>
18
+
19
+    <el-row :gutter="10" class="mb8">
20
+      <el-col :span="1.5">
21
+        <el-button type="primary" plain icon="Plus" @click="handleAdd"
22
+          v-hasPermi="['dailyLuggageInspectionScheduleByTime:dailyLuggageInspectionScheduleByTime:add']">新增</el-button>
23
+      </el-col>
24
+      <el-col :span="1.5">
25
+        <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete"
26
+          v-hasPermi="['dailyLuggageInspectionScheduleByTime:dailyLuggageInspectionScheduleByTime:remove']">删除</el-button>
27
+      </el-col>
28
+      <el-col :span="1.5">
29
+        <el-button type="warning" plain icon="Download" @click="handleExport"
30
+          v-hasPermi="['dailyLuggageInspectionScheduleByTime:dailyLuggageInspectionScheduleByTime:export']">导出</el-button>
31
+      </el-col>
32
+      <el-col :span="1.5">
33
+        <el-button type="info" plain icon="Upload" @click="handleImport"
34
+          v-hasPermi="['dailyLuggageInspectionScheduleByTime:dailyLuggageInspectionScheduleByTime:import']">导入</el-button>
35
+      </el-col>
36
+      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
37
+    </el-row>
38
+
39
+    <el-table v-loading="loading" :data="dailyLuggageInspectionScheduleByTimeList"
40
+      @selection-change="handleSelectionChange">
41
+      <el-table-column type="selection" width="55" align="center" />
42
+      <el-table-column label="统计日期" align="center" prop="statDate" />
43
+      <el-table-column label="当班大队" align="center" prop="brigadeName" />
44
+      <el-table-column label="时间段" align="center" prop="timePeriod" />
45
+      <el-table-column label="备注" align="center" prop="remark" />
46
+      <el-table-column label="T1行检箱包数" align="center" prop="t1WalkBagCount" />
47
+      <el-table-column label="T1行检查堵数" align="center" prop="t1WalkBlockedCount" />
48
+      <el-table-column label="T2行检箱包数" align="center" prop="t2WalkBagCount" />
49
+      <el-table-column label="T2行检查堵数" align="center" prop="t2WalkBlockedCount" />
50
+      <el-table-column label="T1旅检箱包数" align="center" prop="t1TravelBagCount" />
51
+      <el-table-column label="T1旅检查堵数" align="center" prop="t1TravelBlockedCount" />
52
+      <el-table-column label="T2旅检箱包数" align="center" prop="t2TravelBagCount" />
53
+      <el-table-column label="T2旅检查堵数" align="center" prop="t2TravelBlockedCount" />
54
+      <el-table-column label="过检行李数" align="center" prop="totalLuggageCount" />
55
+      <el-table-column label="时段查堵件数" align="center" prop="totalBlockedCount" />
56
+      <el-table-column label="查堵万分率" align="center" prop="blockedRate" />
57
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="140">
58
+        <template #default="scope">
59
+          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
60
+            v-hasPermi="['dailyLuggageInspectionScheduleByTime:dailyLuggageInspectionScheduleByTime:edit']">修改</el-button>
61
+          <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
62
+            v-hasPermi="['dailyLuggageInspectionScheduleByTime:dailyLuggageInspectionScheduleByTime:remove']">删除</el-button>
63
+        </template>
64
+      </el-table-column>
65
+    </el-table>
66
+
67
+    <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
68
+      v-model:limit="queryParams.pageSize" @pagination="getList" />
69
+
70
+    <el-dialog :title="title" v-model="open" width="1000px" append-to-body @closed="reset" destroy-on-close>
71
+      <el-form ref="dailyLuggageInspectionScheduleByTimeRef" :model="form" :rules="rules" label-width="250px">
72
+        <el-row :gutter="20">
73
+          <el-col :span="12">
74
+            <el-form-item label="统计日期" prop="statDate">
75
+              <el-date-picker v-model="form.statDate" type="date" placeholder="选择统计日期" value-format="YYYY-MM-DD"
76
+                style="width: 100%" />
77
+            </el-form-item>
78
+          </el-col>
79
+          <el-col :span="12">
80
+            <el-form-item label="当班大队" prop="brigadeId">
81
+              <el-select v-model="form.brigadeId" placeholder="请选择当班大队" filterable style="width: 100%">
82
+                <el-option v-for="item in brigadeOptions" :key="item.value" :label="item.label" :value="item.value" />
83
+              </el-select>
84
+            </el-form-item>
85
+          </el-col>
86
+        </el-row>
87
+        <el-row :gutter="20">
88
+          <el-col :span="12">
89
+            <el-form-item label="时间段" prop="timePeriod">
90
+              <el-select v-model="form.timePeriod" placeholder="请选择时间段" filterable style="width: 100%">
91
+                <el-option v-for="dict in blocked_time_period" :key="dict.value" :label="dict.label"
92
+                  :value="dict.value" />
93
+              </el-select>
94
+            </el-form-item>
95
+          </el-col>
96
+          <el-col :span="12">
97
+            <el-form-item label="备注" prop="remark">
98
+              <el-input v-model="form.remark" placeholder="请输入备注" style="width: 100%" />
99
+            </el-form-item>
100
+          </el-col>
101
+        </el-row>
102
+        <el-row :gutter="20">
103
+          <el-col :span="12">
104
+            <el-form-item label="T1行检箱包数" prop="t1WalkBagCount">
105
+              <el-input-number v-model="form.t1WalkBagCount" :min="0" :precision="0" style="width: 100%" />
106
+            </el-form-item>
107
+          </el-col>
108
+          <el-col :span="12">
109
+            <el-form-item label="T1行检查堵数" prop="t1WalkBlockedCount">
110
+              <el-input-number v-model="form.t1WalkBlockedCount" :min="0" :precision="0" style="width: 100%" />
111
+            </el-form-item>
112
+          </el-col>
113
+        </el-row>
114
+        <el-row :gutter="20">
115
+          <el-col :span="12">
116
+            <el-form-item label="T2行检箱包数(国内+国际)" prop="t2WalkBagCount">
117
+              <el-input-number v-model="form.t2WalkBagCount" :min="0" :precision="0" style="width: 100%" />
118
+            </el-form-item>
119
+          </el-col>
120
+          <el-col :span="12">
121
+            <el-form-item label="T2行检查堵数" prop="t2WalkBlockedCount">
122
+              <el-input-number v-model="form.t2WalkBlockedCount" :min="0" :precision="0" style="width: 100%" />
123
+            </el-form-item>
124
+          </el-col>
125
+        </el-row>
126
+        <el-row :gutter="20">
127
+          <el-col :span="12">
128
+            <el-form-item label="T1旅检箱包数(国内+国际+中转)" prop="t1TravelBagCount">
129
+              <el-input-number v-model="form.t1TravelBagCount" :min="0" :precision="0" style="width: 100%" />
130
+            </el-form-item>
131
+          </el-col>
132
+          <el-col :span="12">
133
+            <el-form-item label="T1旅检查堵数" prop="t1TravelBlockedCount">
134
+              <el-input-number v-model="form.t1TravelBlockedCount" :min="0" :precision="0" style="width: 100%" />
135
+            </el-form-item>
136
+          </el-col>
137
+        </el-row>
138
+        <el-row :gutter="20">
139
+          <el-col :span="12">
140
+            <el-form-item label="T2旅检箱包数" prop="t2TravelBagCount">
141
+              <el-input-number v-model="form.t2TravelBagCount" :min="0" :precision="0" style="width: 100%" />
142
+            </el-form-item>
143
+          </el-col>
144
+          <el-col :span="12">
145
+            <el-form-item label="T2旅检查堵数" prop="t2TravelBlockedCount">
146
+              <el-input-number v-model="form.t2TravelBlockedCount" :min="0" :precision="0" style="width: 100%" />
147
+            </el-form-item>
148
+          </el-col>
149
+        </el-row>
150
+
151
+
152
+      </el-form>
153
+      <template #footer>
154
+        <div class="dialog-footer">
155
+          <el-button type="primary" @click="submitForm">确 定</el-button>
156
+          <el-button @click="cancel">取 消</el-button>
157
+        </div>
158
+      </template>
159
+    </el-dialog>
160
+
161
+    <el-dialog title="导入" v-model="upload.open" width="500px" append-to-body>
162
+      <el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url"
163
+        :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess"
164
+        :auto-upload="false" drag>
165
+        <el-icon class="el-icon--upload"><upload-filled /></el-icon>
166
+        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
167
+        <template #tip>
168
+          <div class="el-upload__tip text-center">
169
+            <span>仅允许导入xls、xlsx格式文件。</span>
170
+            <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline"
171
+              @click="importTemplate">下载模板</el-link>
172
+          </div>
173
+        </template>
174
+      </el-upload>
175
+      <template #footer>
176
+        <div class="dialog-footer">
177
+          <el-button type="primary" @click="submitFileForm">确 定</el-button>
178
+          <el-button @click="upload.open = false">取 消</el-button>
179
+        </div>
180
+      </template>
181
+    </el-dialog>
182
+  </div>
183
+</template>
184
+
185
+<script setup>
186
+import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
187
+import { listDailyLuggageInspectionScheduleByTime, getDailyLuggageInspectionScheduleByTime, delDailyLuggageInspectionScheduleByTime, addDailyLuggageInspectionScheduleByTime, updateDailyLuggageInspectionScheduleByTime, exportDailyLuggageInspectionScheduleByTime, downloadTemplate } from '@/api/blockingData/dailyLuggageInspectionScheduleByTime'
188
+import { listDept } from '@/api/system/dept'
189
+import { useDict } from '@/utils/dict'
190
+import { getToken } from '@/utils/auth'
191
+import { UploadFilled } from '@element-plus/icons-vue'
192
+
193
+const { proxy } = getCurrentInstance()
194
+const { blocked_time_period } = useDict('blocked_time_period')
195
+
196
+const loading = ref(true)
197
+const ids = ref([])
198
+const single = ref(true)
199
+const multiple = ref(true)
200
+const showSearch = ref(true)
201
+const total = ref(0)
202
+const dailyLuggageInspectionScheduleByTimeList = ref([])
203
+const title = ref('')
204
+const open = ref(false)
205
+
206
+
207
+const upload = reactive({
208
+  open: false,
209
+  isUploading: false,
210
+  headers: { Authorization: 'Bearer ' + getToken() },
211
+  url: import.meta.env.VITE_APP_BASE_API + '/blocked/pieceDaily/importData'
212
+})
213
+
214
+const queryParams = reactive({
215
+  pageNum: 1,
216
+  pageSize: 10,
217
+  brigadeId: null,
218
+  statDate: null
219
+})
220
+
221
+const form = reactive({})
222
+
223
+const rules = {
224
+  statDate: [{ required: true, message: '统计日期不能为空', trigger: 'change' }],
225
+  brigadeId: [{ required: true, message: '当班大队不能为空', trigger: 'change' }],
226
+  timePeriod: [{ required: true, message: '时间段不能为空', trigger: 'change' }],
227
+  t1WalkBagCount: [
228
+    { required: true, message: 'T1行检箱包数不能为空', trigger: 'blur' },
229
+    { type: 'number', message: '请输入有效的数字', trigger: 'blur' }
230
+  ],
231
+  t1WalkBlockedCount: [
232
+    { required: true, message: 'T1行检查堵数不能为空', trigger: 'blur' },
233
+    { type: 'number', message: '请输入有效的数字', trigger: 'blur' }
234
+  ],
235
+  t2WalkBagCount: [
236
+    { required: true, message: 'T2行检箱包数不能为空', trigger: 'blur' },
237
+    { type: 'number', message: '请输入有效的数字', trigger: 'blur' }
238
+  ],
239
+  t2WalkBlockedCount: [
240
+    { required: true, message: 'T2行检查堵数不能为空', trigger: 'blur' },
241
+    { type: 'number', message: '请输入有效的数字', trigger: 'blur' }
242
+  ],
243
+  t1TravelBagCount: [
244
+    { required: true, message: 'T1旅检箱包数不能为空', trigger: 'blur' },
245
+    { type: 'number', message: '请输入有效的数字', trigger: 'blur' }
246
+  ],
247
+  t1TravelBlockedCount: [
248
+    { required: true, message: 'T1旅检查堵数不能为空', trigger: 'blur' },
249
+    { type: 'number', message: '请输入有效的数字', trigger: 'blur' }
250
+  ],
251
+  t2TravelBagCount: [
252
+    { required: true, message: 'T2旅检箱包数不能为空', trigger: 'blur' },
253
+    { type: 'number', message: '请输入有效的数字', trigger: 'blur' }
254
+  ],
255
+  t2TravelBlockedCount: [
256
+    { required: true, message: 'T2旅检查堵数不能为空', trigger: 'blur' },
257
+    { type: 'number', message: '请输入有效的数字', trigger: 'blur' }
258
+  ]
259
+  // remark 不设置必填校验
260
+}
261
+
262
+const brigadeOptions = ref([])
263
+
264
+function getList() {
265
+  loading.value = true
266
+  listDailyLuggageInspectionScheduleByTime(queryParams).then(response => {
267
+    dailyLuggageInspectionScheduleByTimeList.value = response.rows
268
+    total.value = response.total
269
+    loading.value = false
270
+  })
271
+}
272
+
273
+function getDeptList() {
274
+  listDept({}).then(response => {
275
+    const deptList = response.data || []
276
+    brigadeOptions.value = deptList.filter(item => item.deptType === 'BRIGADE' && [311, 314, 315].includes(item.deptId)).map(item => ({
277
+      value: item.deptId,
278
+      label: item.deptName
279
+    }))
280
+
281
+  })
282
+}
283
+
284
+function cancel() {
285
+  open.value = false
286
+  reset()
287
+}
288
+
289
+function reset() {
290
+  Object.assign(form, {
291
+    id: null,
292
+    statDate: null,
293
+    brigadeId: null,
294
+    timePeriod: null,
295
+    remark: null,
296
+    t1WalkBagCount: null,
297
+    t1WalkBlockedCount: null,
298
+    t2WalkBagCount: null,
299
+    t2WalkBlockedCount: null,
300
+    t1TravelBagCount: null,
301
+    t1TravelBlockedCount: null,
302
+    t2TravelBagCount: null,
303
+    t2TravelBlockedCount: null,
304
+    totalLuggageCount: 0,
305
+    totalBlockedCount: 0,
306
+    blockedRate: 0
307
+  })
308
+  proxy.resetForm('dailyLuggageInspectionScheduleByTimeRef')
309
+}
310
+
311
+function handleQuery() {
312
+  queryParams.pageNum = 1
313
+  getList()
314
+}
315
+
316
+function resetQuery() {
317
+  proxy.resetForm('queryRef')
318
+  handleQuery()
319
+}
320
+
321
+function handleSelectionChange(selection) {
322
+  ids.value = selection.map(item => item.id)
323
+  single.value = selection.length !== 1
324
+  multiple.value = !selection.length
325
+}
326
+
327
+function handleAdd() {
328
+  reset()
329
+  // 设置默认当前日期
330
+  form.statDate = new Date().toISOString().split('T')[0]
331
+  open.value = true
332
+  title.value = '添加每日各时段查堵行李'
333
+}
334
+
335
+function handleUpdate(row) {
336
+  reset()
337
+  const id = row.id || ids.value[0]
338
+  getDailyLuggageInspectionScheduleByTime(id).then(response => {
339
+    Object.assign(form, response.data)
340
+    open.value = true
341
+    title.value = '修改每日各时段查堵行李'
342
+  })
343
+}
344
+
345
+function submitForm() {
346
+  proxy.$refs.dailyLuggageInspectionScheduleByTimeRef.validate(valid => {
347
+    if (valid) {
348
+      // 获取brigadeName
349
+      const selectedBrigade = brigadeOptions.value.find(item => item.value === form.brigadeId)
350
+      if (selectedBrigade) {
351
+        form.brigadeName = selectedBrigade.label
352
+      }
353
+
354
+      if (form.id != null) {
355
+        updateDailyLuggageInspectionScheduleByTime(form).then(response => {
356
+          proxy.$modal.msgSuccess('修改成功')
357
+          open.value = false
358
+          reset()
359
+          getList()
360
+        })
361
+      } else {
362
+        addDailyLuggageInspectionScheduleByTime(form).then(response => {
363
+          proxy.$modal.msgSuccess('新增成功')
364
+          open.value = false
365
+          reset()
366
+          getList()
367
+        })
368
+      }
369
+    }
370
+  })
371
+}
372
+
373
+function handleDelete(row) {
374
+  const id = row.id || ids.value
375
+  proxy.$modal.confirm('是否确认删除每日各时段查堵行李编号为"' + id + '"的数据项?').then(function () {
376
+    return delDailyLuggageInspectionScheduleByTime(id)
377
+  }).then(() => {
378
+    getList()
379
+    proxy.$modal.msgSuccess('删除成功')
380
+  }).catch(() => { })
381
+}
382
+
383
+function handleExport() {
384
+  proxy.download('/blocked/pieceDaily/export', {
385
+    ...queryParams
386
+  }, `每日各时段查堵行李_${new Date().getTime()}.xlsx`)
387
+}
388
+
389
+function handleImport() {
390
+  upload.open = true
391
+}
392
+
393
+/** 下载模板操作 */
394
+function importTemplate() {
395
+  proxy.download('blocked/pieceDaily/importTemplate', {}, `每日各时段查堵行李模板_${new Date().getTime()}.xlsx`)
396
+}
397
+
398
+/** 文件上传中处理 */
399
+const handleFileUploadProgress = (event, file, fileList) => {
400
+  upload.isUploading = true
401
+}
402
+
403
+/** 文件上传成功处理 */
404
+const handleFileSuccess = (response, file, fileList) => {
405
+  upload.open = false
406
+  upload.isUploading = false
407
+  proxy.$refs.uploadRef.handleRemove(file)
408
+  proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true })
409
+  getList()
410
+}
411
+
412
+/** 提交上传文件 */
413
+function submitFileForm() {
414
+  proxy.$refs.uploadRef.submit()
415
+}
416
+
417
+onMounted(() => {
418
+  getList()
419
+  getDeptList()
420
+})
421
+</script>
422
+
423
+<style lang="less" scoped>
424
+.app-container {
425
+  padding: 20px;
426
+}
427
+</style>

+ 0 - 0
src/views/blockingData/dailyLuggageInspectionScheduleByTime/每日各时段查堵行李表


+ 171 - 0
src/views/blockingData/experienceDistributionStartupPerson/index.vue

@@ -0,0 +1,171 @@
1
+<template>
2
+  <div class="experience-distribution">
3
+    <h1 class="page-title">开机人员年限分布</h1>
4
+
5
+    <div class="table-container">
6
+      <el-table :data="teams" border stripe class="distribution-table"
7
+        :header-cell-style="{ background: '#f5f7fa', color: '#606266' }">
8
+        <el-table-column prop="brigadeName" label="业务大队" align="center">
9
+          <template #default="{ row }">
10
+            <span class="team-name">{{ row.brigadeName }}</span>
11
+          </template>
12
+        </el-table-column>
13
+
14
+        <el-table-column label="5年及5年以上人员数" align="center">
15
+          <template #default="{ row }">
16
+            <el-input-number v-model="row.cntGe5Year" :min="0" :controls="false" size="small" placeholder="0"
17
+              class="number-input" />
18
+          </template>
19
+        </el-table-column>
20
+
21
+        <el-table-column label="4年人员数" align="center">
22
+          <template #default="{ row }">
23
+            <el-input-number v-model="row.cnt4Years" :min="0" :controls="false" size="small" placeholder="0"
24
+              class="number-input" />
25
+          </template>
26
+        </el-table-column>
27
+
28
+        <el-table-column label="3年人员数" align="center">
29
+          <template #default="{ row }">
30
+            <el-input-number v-model="row.cnt3Years" :min="0" :controls="false" size="small" placeholder="0"
31
+              class="number-input" />
32
+          </template>
33
+        </el-table-column>
34
+
35
+        <el-table-column label="2年人员数" align="center">
36
+          <template #default="{ row }">
37
+            <el-input-number v-model="row.cnt2Years" :min="0" :controls="false" size="small" placeholder="0"
38
+              class="number-input" />
39
+          </template>
40
+        </el-table-column>
41
+
42
+        <el-table-column label="1年人员数" align="center">
43
+          <template #default="{ row }">
44
+            <el-input-number v-model="row.cnt1Years" :min="0" :controls="false" size="small" placeholder="0"
45
+              class="number-input" />
46
+          </template>
47
+        </el-table-column>
48
+
49
+        <el-table-column label="不足1年人员数" width="130" align="center">
50
+          <template #default="{ row }">
51
+            <el-input-number v-model="row.cntLt1Year" :min="0" :controls="false" size="small" placeholder="0"
52
+              class="number-input" />
53
+          </template>
54
+        </el-table-column>
55
+      </el-table>
56
+    </div>
57
+
58
+    <div class="button-container">
59
+      <el-button type="primary" size="medium" @click="handleConfirm" class="confirm-button">确定</el-button>
60
+    </div>
61
+  </div>
62
+</template>
63
+
64
+<script>
65
+import { getTenurelistAll, editTenure } from '@/api/blockingData/experienceDistributionStartupPerson'
66
+export default {
67
+  name: 'ExperienceDistributionStartupPerson',
68
+  data() {
69
+    return {
70
+      teams: []
71
+    }
72
+  },
73
+  mounted() {
74
+    this.fetchData()
75
+  },
76
+  methods: {
77
+    async fetchData() {
78
+      try {
79
+        const response = await getTenurelistAll()
80
+        if (response.code === 200) {
81
+          this.teams = response.data || []
82
+        } else {
83
+          this.$message.error('获取数据失败:' + response.msg)
84
+        }
85
+      } catch (error) {
86
+        console.error('获取数据失败:', error)
87
+        this.$message.error('获取数据失败')
88
+      }
89
+    },
90
+    async handleConfirm() {
91
+      try {
92
+        const savePromises = this.teams.map(row => {
93
+          const saveData = {
94
+            id: row.id,
95
+            brigadeId: row.brigadeId,
96
+            brigadeName: row.brigadeName,
97
+            cntGe5Year: row.cntGe5Year || 0,
98
+            cnt4Years: row.cnt4Years || 0,
99
+            cnt3Years: row.cnt3Years || 0,
100
+            cnt2Years: row.cnt2Years || 0,
101
+            cnt1Years: row.cnt1Years || 0,
102
+            cntLt1Year: row.cntLt1Year || 0
103
+          }
104
+          return editTenure(saveData)
105
+        })
106
+        
107
+        const results = await Promise.all(savePromises)
108
+        const allSuccess = results.every(result => result.code === 200)
109
+        
110
+        if (allSuccess) {
111
+          this.$message.success('数据保存成功!')
112
+        } else {
113
+          this.$message.error('部分数据保存失败,请检查后重试')
114
+        }
115
+      } catch (error) {
116
+        console.error('保存数据失败:', error)
117
+        this.$message.error('保存数据失败')
118
+      }
119
+    }
120
+  }
121
+}
122
+</script>
123
+
124
+<style scoped>
125
+.experience-distribution {
126
+  padding: 20px;
127
+  max-width: 1200px;
128
+  margin: 0 auto;
129
+}
130
+
131
+.page-title {
132
+  text-align: center;
133
+  margin-bottom: 30px;
134
+  color: #333;
135
+  font-size: 24px;
136
+  font-weight: bold;
137
+}
138
+
139
+.table-container {
140
+  margin-bottom: 30px;
141
+}
142
+
143
+.distribution-table {
144
+  width: 100%;
145
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
146
+  border-radius: 4px;
147
+}
148
+
149
+.team-name {
150
+  font-weight: bold;
151
+  color: #333;
152
+}
153
+
154
+.number-input {
155
+  width: 100px;
156
+}
157
+
158
+.number-input :deep(.el-input__inner) {
159
+  text-align: center;
160
+}
161
+
162
+.button-container {
163
+  text-align: center;
164
+}
165
+
166
+.confirm-button {
167
+  width: 120px;
168
+  height: 40px;
169
+  font-size: 16px;
170
+}
171
+</style>

+ 0 - 0
src/views/blockingData/experienceDistributionStartupPerson/开机人员年限分布


+ 762 - 0
src/views/blockingData/missedInspectionList/index.vue

@@ -0,0 +1,762 @@
1
+<template>
2
+  <div class="app-container">
3
+    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
4
+      <el-form-item label="大队" prop="brigadeId">
5
+        <el-select v-model="queryParams.brigadeId" placeholder="请选择大队" filterable clearable style="width: 200px">
6
+          <el-option v-for="item in brigadeOptions" :key="item.value" :label="item.label" :value="item.value" />
7
+        </el-select>
8
+      </el-form-item>
9
+      <el-form-item label="回查日期" prop="reviewDate">
10
+        <el-date-picker clearable v-model="dateRange" value-format="YYYY-MM-DD" type="daterange" range-separator="-"
11
+          start-placeholder="开始日期" end-placeholder="结束日期" style="width: 300px">
12
+        </el-date-picker>
13
+      </el-form-item>
14
+      <el-form-item label="被回查人" prop="reviewedUserId">
15
+        <el-select v-model="queryParams.reviewedUserId" placeholder="请选择被回查人" filterable clearable style="width: 200px">
16
+          <el-option v-for="item in personOptions" :key="item.value" :label="item.label" :value="item.value" />
17
+        </el-select>
18
+      </el-form-item>
19
+      <el-form-item label="分管班组长" prop="teamLeaderId">
20
+        <el-select v-model="queryParams.teamLeaderId" placeholder="请选择分管班组长" filterable clearable style="width: 200px">
21
+          <el-option v-for="item in teamLeaderOptions" :key="item.value" :label="item.label" :value="item.value" />
22
+        </el-select>
23
+      </el-form-item>
24
+      <el-form-item label="分管主管" prop="supervisorId">
25
+        <el-select v-model="queryParams.supervisorId" placeholder="请选择分管主管" filterable clearable style="width: 200px">
26
+          <el-option v-for="item in supervisorOptions" :key="item.value" :label="item.label" :value="item.value" />
27
+        </el-select>
28
+      </el-form-item>
29
+      <el-form-item label="判别类型" prop="discriminationType">
30
+        <el-select v-model="queryParams.discriminationType" placeholder="请选择判别类型" filterable clearable
31
+          style="width: 200px">
32
+          <el-option v-for="item in discrimination_type" :key="item.value" :label="item.label" :value="item.value" />
33
+        </el-select>
34
+      </el-form-item>
35
+      <el-form-item>
36
+        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
37
+        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
38
+      </el-form-item>
39
+    </el-form>
40
+
41
+    <el-row :gutter="10" class="mb8">
42
+      <el-col :span="1.5">
43
+        <el-button type="primary" plain icon="Plus" @click="handleAdd"
44
+          v-hasPermi="['missedInspection:missedInspection:add']">新增</el-button>
45
+      </el-col>
46
+      <el-col :span="1.5">
47
+        <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete"
48
+          v-hasPermi="['missedInspection:missedInspection:remove']">删除</el-button>
49
+      </el-col>
50
+      <el-col :span="1.5">
51
+        <el-button type="warning" plain icon="Download" @click="handleExport"
52
+          v-hasPermi="['missedInspection:missedInspection:export']">导出</el-button>
53
+      </el-col>
54
+      <el-col :span="1.5">
55
+        <el-button type="info" plain icon="Upload" @click="handleImport"
56
+          v-hasPermi="['missedInspection:missedInspection:import']">导入</el-button>
57
+      </el-col>
58
+      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
59
+    </el-row>
60
+
61
+    <el-table v-loading="loading" :data="missedInspectionList" @selection-change="handleSelectionChange">
62
+      <el-table-column type="selection" width="55" align="center" />
63
+      <el-table-column label="大队" align="center" prop="brigadeName" min-width="120" />
64
+      <el-table-column label="航站楼" align="center" prop="terminalName" min-width="120" />
65
+      <el-table-column label="被回查人" align="center" prop="reviewedUserName" min-width="120" />
66
+      <el-table-column label="回查日期" align="center" prop="reviewDate" min-width="120" />
67
+      <el-table-column label="漏检时间" align="center" prop="missCheckTime" min-width="180" />
68
+      <el-table-column label="漏检时间段" align="center" prop="missCheckTimePeriod" min-width="150" />
69
+      <el-table-column label="上岗位置" align="center" prop="channelName" min-width="120" />
70
+      <el-table-column label="分管主管" align="center" prop="supervisorName" min-width="120" />
71
+      <el-table-column label="代管主管" align="center" prop="actingSupervisorName" min-width="120" />
72
+      <el-table-column label="分管班组长" align="center" prop="teamLeaderName" min-width="120" />
73
+      <el-table-column label="物品位置" align="center" prop="itemLocation" min-width="120" />
74
+      <el-table-column label="简单/难" align="center" prop="difficultyLevel" min-width="100" />
75
+      <el-table-column label="回查人" align="center" prop="reviewUserName" min-width="120" />
76
+      <el-table-column label="判别类型" align="center" prop="discriminationType" min-width="100" />
77
+      <el-table-column label="是否追回" align="center" prop="isRecovered" min-width="100">
78
+        <template #default="scope">
79
+          {{ scope.row.isRecovered == 1 ? '是' : '否' }}
80
+        </template>
81
+      </el-table-column>
82
+      <el-table-column label="开机年限" align="center" prop="machineOperatingYears" min-width="100" />
83
+      <el-table-column label="证书级别" align="center" prop="certificateLevel" min-width="100" />
84
+      <el-table-column label="人员性别" align="center" prop="gender" min-width="100" />
85
+      <el-table-column label="漏检原因分类" align="center" prop="missCheckReasonCategory" min-width="220" />
86
+      <el-table-column label="月考成绩" align="center" prop="monthlyAssessment" min-width="100" />
87
+      <el-table-column label="本月自测有无漏检" align="center" prop="selfTestHasMissCheck" min-width="160" />
88
+
89
+      <el-table-column label="漏检物品" align="center" prop="missCheckItem" min-width="120" />
90
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="140" fixed="right">
91
+        <template #default="scope">
92
+          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
93
+            v-hasPermi="['missedInspection:missedInspection:edit']">修改</el-button>
94
+          <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
95
+            v-hasPermi="['missedInspection:missedInspection:remove']">删除</el-button>
96
+        </template>
97
+      </el-table-column>
98
+    </el-table>
99
+
100
+    <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
101
+      v-model:limit="queryParams.pageSize" @pagination="getList" />
102
+
103
+    <!-- 添加或修改漏检对话框 -->
104
+    <el-dialog :title="title" v-model="open" width="1000px" append-to-body @closed="reset" destroy-on-close>
105
+      <el-form ref="missedInspectionRef" :model="form" :rules="rules" label-width="140px">
106
+        <el-row :gutter="20">
107
+          <el-col :span="12">
108
+            <el-form-item label="大队" prop="brigadeId">
109
+              <el-select v-model="form.brigadeId" placeholder="请选择大队" filterable style="width: 100%">
110
+                <el-option v-for="item in brigadeOptions" :key="item.value" :label="item.label" :value="item.value" />
111
+              </el-select>
112
+            </el-form-item>
113
+          </el-col>
114
+          <el-col :span="12">
115
+            <el-form-item label="航站楼" prop="terminalId">
116
+              <el-select v-model="form.terminalId" placeholder="请选择航站楼" filterable style="width: 100%">
117
+                <el-option v-for="item in channelOptions" :key="item.id" :label="item.name" :value="item.id" />
118
+              </el-select>
119
+            </el-form-item>
120
+          </el-col>
121
+        </el-row>
122
+        <el-row :gutter="20">
123
+          <el-col :span="12">
124
+            <el-form-item label="被回查人" prop="reviewedUserId">
125
+              <el-select v-model="form.reviewedUserId" placeholder="请选择被回查人" filterable style="width: 100%"
126
+                @change="handleReviewedUserChange">
127
+                <el-option v-for="item in personOptions" :key="item.value" :label="item.label" :value="item.value" />
128
+              </el-select>
129
+            </el-form-item>
130
+          </el-col>
131
+          <el-col :span="12">
132
+            <el-form-item label="回查日期" prop="reviewDate">
133
+              <el-date-picker v-model="form.reviewDate" type="date" placeholder="选择回查日期" value-format="YYYY-MM-DD"
134
+                style="width: 100%" />
135
+            </el-form-item>
136
+          </el-col>
137
+        </el-row>
138
+        <el-row :gutter="20">
139
+          <el-col :span="12">
140
+            <el-form-item label="漏检时间" prop="missCheckTime">
141
+              <el-date-picker v-model="form.missCheckTime" type="datetime" placeholder="请选择漏检时间"
142
+                value-format="YYYY-MM-DD HH:mm" style="width: 100%" />
143
+            </el-form-item>
144
+          </el-col>
145
+          <el-col :span="12">
146
+            <el-form-item label="漏检时间段" prop="missCheckTimePeriod">
147
+              <el-select v-model="form.missCheckTimePeriod" placeholder="请选择漏检时间段" filterable style="width: 100%">
148
+                <el-option v-for="item in blocked_time_period" :key="item.value" :label="item.label"
149
+                  :value="item.value" />
150
+              </el-select>
151
+            </el-form-item>
152
+          </el-col>
153
+        </el-row>
154
+        <el-row :gutter="20">
155
+          <el-col :span="12">
156
+            <el-form-item label="上岗位置" prop="channelId">
157
+              <el-select v-model="form.channelId" placeholder="请选择上岗位置" filterable style="width: 100%">
158
+                <el-option v-for="item in areaOptions" :key="item.id" :label="item.name" :value="item.id" />
159
+              </el-select>
160
+            </el-form-item>
161
+          </el-col>
162
+          <el-col :span="12">
163
+            <el-form-item label="漏检物品" prop="missCheckItem">
164
+              <el-tree-select v-model="form.missCheckItem" :data="missCheckItemOptions"
165
+                :props="{ value: 'name', label: 'name', children: 'children', disabled: data => data.children && data.children.length > 0 }"
166
+                value-key="name" placeholder="请选择漏检物品" check-strictly style="width: 100%" />
167
+            </el-form-item>
168
+          </el-col>
169
+
170
+        </el-row>
171
+        <el-row :gutter="20">
172
+          <el-col :span="12">
173
+            <el-form-item label="分管主管" prop="supervisorId">
174
+              <el-select v-model="form.supervisorId" placeholder="请选择分管主管" filterable style="width: 100%">
175
+                <el-option v-for="item in supervisorOptions" :key="item.value" :label="item.label"
176
+                  :value="item.value" />
177
+              </el-select>
178
+            </el-form-item>
179
+          </el-col>
180
+          <el-col :span="12">
181
+            <el-form-item label="代管主管" prop="actingSupervisorId">
182
+              <el-select v-model="form.actingSupervisorId" placeholder="请选择代管主管" filterable style="width: 100%">
183
+                <el-option v-for="item in supervisorOptions" :key="item.value" :label="item.label"
184
+                  :value="item.value" />
185
+              </el-select>
186
+            </el-form-item>
187
+          </el-col>
188
+
189
+        </el-row>
190
+        <el-row :gutter="20">
191
+          <el-col :span="12">
192
+            <el-form-item label="分管班组长" prop="teamLeaderId">
193
+              <el-select v-model="form.teamLeaderId" placeholder="请选择分管班组长" filterable style="width: 100%">
194
+                <el-option v-for="item in teamLeaderOptions" :key="item.value" :label="item.label"
195
+                  :value="item.value" />
196
+              </el-select>
197
+            </el-form-item>
198
+          </el-col>
199
+          <el-col :span="12">
200
+            <el-form-item label="物品位置" prop="itemLocation">
201
+              <el-select v-model="form.itemLocation" placeholder="请选择物品位置" filterable style="width: 100%">
202
+                <el-option v-for="item in blocked_item_position" :key="item.value" :label="item.label"
203
+                  :value="item.value" />
204
+              </el-select>
205
+            </el-form-item>
206
+          </el-col>
207
+
208
+        </el-row>
209
+        <el-row :gutter="20">
210
+          <el-col :span="12">
211
+            <el-form-item label="简单/难" prop="difficultyLevel">
212
+              <el-select v-model="form.difficultyLevel" placeholder="请选择简单/难" filterable style="width: 100%">
213
+                <el-option label="简单" value="简单" />
214
+                <el-option label="难" value="难" />
215
+              </el-select>
216
+            </el-form-item>
217
+          </el-col>
218
+          <el-col :span="12">
219
+            <el-form-item label="回查人" prop="reviewUserId">
220
+              <el-select v-model="form.reviewUserId" placeholder="请选择回查人" filterable style="width: 100%">
221
+                <el-option v-for="item in personOptions" :key="item.value" :label="item.label" :value="item.value" />
222
+              </el-select>
223
+            </el-form-item>
224
+          </el-col>
225
+
226
+        </el-row>
227
+        <el-row :gutter="20">
228
+          <el-col :span="12">
229
+            <el-form-item label="判别类型" prop="discriminationType">
230
+              <el-select v-model="form.discriminationType" placeholder="请选择判别类型" filterable style="width: 100%">
231
+                <el-option v-for="item in discrimination_type" :key="item.value" :label="item.label"
232
+                  :value="item.value" />
233
+              </el-select>
234
+            </el-form-item>
235
+          </el-col>
236
+          <el-col :span="12">
237
+            <el-form-item label="是否追回" prop="isRecovered">
238
+              <el-select v-model="form.isRecovered" placeholder="请选择是否追回" filterable style="width: 100%">
239
+                <el-option label="是" :value="1" />
240
+                <el-option label="否" :value="0" />
241
+              </el-select>
242
+            </el-form-item>
243
+          </el-col>
244
+
245
+        </el-row>
246
+        <el-row :gutter="20">
247
+          <el-col :span="12">
248
+            <el-form-item label="开机年限" prop="machineOperatingYears">
249
+              <el-select v-model="form.machineOperatingYears" placeholder="请选择开机年限" filterable style="width: 100%">
250
+                <el-option v-for="item in blocked_operating_years" :key="item.value" :label="item.label"
251
+                  :value="item.value" />
252
+              </el-select>
253
+            </el-form-item>
254
+          </el-col>
255
+          <el-col :span="12">
256
+            <el-form-item label="证书级别" prop="certificateLevel">
257
+              <el-select v-model="form.certificateLevel" placeholder="请输入证书级别" filterable style="width: 100%">
258
+                <el-option label="高级" value="高级" />
259
+                <el-option label="中级" value="中级" />
260
+                <el-option label="初级" value="初级" />
261
+              </el-select>
262
+            </el-form-item>
263
+          </el-col>
264
+
265
+        </el-row>
266
+        <el-row :gutter="20">
267
+          <el-col :span="12">
268
+            <el-form-item label="人员性别" prop="gender">
269
+              <el-select v-model="form.gender" placeholder="请选择人员性别" filterable style="width: 100%">
270
+                <el-option label="男" value="男" />
271
+                <el-option label="女" value="女" />
272
+              </el-select>
273
+            </el-form-item>
274
+          </el-col>
275
+          <el-col :span="12">
276
+            <el-form-item label="漏检原因分类" prop="missCheckReasonCategory">
277
+              <el-select v-model="form.missCheckReasonCategory" placeholder="请选择漏检原因分类" filterable style="width: 100%">
278
+                <el-option v-for="item in blocked_miss_check_reason" :key="item.value" :label="item.label"
279
+                  :value="item.value" />
280
+              </el-select>
281
+            </el-form-item>
282
+          </el-col>
283
+
284
+        </el-row>
285
+        <el-row :gutter="20">
286
+          <el-col :span="12">
287
+            <el-form-item label="月考成绩" prop="monthlyAssessment">
288
+              <el-select v-model="form.monthlyAssessment" placeholder="请选择月考成绩" filterable style="width: 100%">
289
+                <el-option v-for="item in blocked_monthly_exam_result" :key="item.value" :label="item.label"
290
+                  :value="item.value" />
291
+              </el-select>
292
+            </el-form-item>
293
+          </el-col>
294
+          <el-col :span="12">
295
+            <el-form-item label="本月自测有无漏检" prop="selfTestHasMissCheck">
296
+              <el-input-number v-model="form.selfTestHasMissCheck" :min="0" :precision="0" placeholder="请输入0-2的整数"
297
+                style="width: 100%" />
298
+            </el-form-item>
299
+          </el-col>
300
+
301
+        </el-row>
302
+      </el-form>
303
+      <template #footer>
304
+        <div class="dialog-footer">
305
+          <el-button type="primary" @click="submitForm">确 定</el-button>
306
+          <el-button @click="cancel">取 消</el-button>
307
+        </div>
308
+      </template>
309
+    </el-dialog>
310
+
311
+    <!-- 导入对话框 -->
312
+    <el-dialog title="导入" v-model="upload.open" width="500px" append-to-body>
313
+      <el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url"
314
+        :data="upload.data" :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess"
315
+        :auto-upload="false" drag>
316
+        <el-icon class="el-icon--upload"><upload-filled /></el-icon>
317
+        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
318
+        <template #tip>
319
+          <div class="el-upload__tip text-center">
320
+            <span>仅允许导入xls、xlsx格式文件。</span>
321
+            <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline"
322
+              @click="importTemplate">下载模板</el-link>
323
+          </div>
324
+        </template>
325
+      </el-upload>
326
+      <template #footer>
327
+        <div class="dialog-footer">
328
+          <el-button type="primary" @click="submitFileForm" :loading="upload.isUploading">确 定</el-button>
329
+          <el-button @click="upload.open = false">取 消</el-button>
330
+        </div>
331
+      </template>
332
+    </el-dialog>
333
+  </div>
334
+</template>
335
+
336
+<script setup>
337
+import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
338
+import { listPosition } from '@/api/system/position'
339
+import { listMissedInspection, getMissedInspection, delMissedInspection, addMissedInspection, updateMissedInspection, exportMissedInspection, downloadTemplate } from '@/api/blockingData/missedInspection'
340
+import { listDept } from '@/api/system/dept'
341
+import { listUser, selectUserLeaderListByCondition, getUser } from '@/api/system/user'
342
+import { listCategory } from '@/api/system/category'
343
+import { useDict } from '@/utils/dict'
344
+import { getToken } from '@/utils/auth'
345
+import { UploadFilled } from '@element-plus/icons-vue'
346
+
347
+const { proxy } = getCurrentInstance()
348
+
349
+// 遮罩层
350
+const loading = ref(true)
351
+// 选中数组
352
+const ids = ref([])
353
+// 非单个禁用
354
+const single = ref(true)
355
+// 非多个禁用
356
+const multiple = ref(true)
357
+// 显示搜索条件
358
+const showSearch = ref(true)
359
+// 总条数
360
+const total = ref(0)
361
+// 漏检表格数据
362
+const missedInspectionList = ref([])
363
+// 弹出层标题
364
+const title = ref('')
365
+// 是否显示弹出层
366
+const open = ref(false)
367
+// 日期范围
368
+const dateRange = ref([])
369
+// 漏检物品树状选项
370
+const missCheckItemOptions = ref([])
371
+
372
+const upload = reactive({
373
+  open: false,
374
+  isUploading: false,
375
+  headers: { Authorization: 'Bearer ' + getToken() },
376
+  url: import.meta.env.VITE_APP_BASE_API + '/blocked/missReview/importData',
377
+  data: { updateSupport: true }
378
+})
379
+
380
+// 查询参数
381
+const queryParams = reactive({
382
+  pageNum: 1,
383
+  pageSize: 10,
384
+  brigadeId: null,
385
+  reviewDate: null,
386
+  reviewedUserId: null,
387
+  teamLeaderId: null,
388
+  supervisorId: null,
389
+  discriminationType: null
390
+})
391
+
392
+// 表单参数
393
+const form = reactive({})
394
+
395
+// 表单校验
396
+const rules = {
397
+  brigadeId: [{ required: true, message: '大队不能为空', trigger: 'change' }],
398
+  terminalId: [{ required: true, message: '航站楼不能为空', trigger: 'change' }],
399
+  reviewedUserId: [{ required: true, message: '被回查人不能为空', trigger: 'change' }],
400
+  reviewDate: [{ required: true, message: '回查日期不能为空', trigger: 'change' }],
401
+  missCheckTime: [{ required: true, message: '漏检时间不能为空', trigger: 'change' }],
402
+  missCheckTimePeriod: [{ required: true, message: '漏检时间段不能为空', trigger: 'change' }],
403
+  channelId: [{ required: true, message: '上岗位置不能为空', trigger: 'change' }],
404
+  missCheckItem: [{ required: true, message: '漏检物品不能为空', trigger: 'change' }],
405
+  supervisorId: [{ required: true, message: '分管主管不能为空', trigger: 'change' }],
406
+  teamLeaderId: [{ required: true, message: '分管班组长不能为空', trigger: 'change' }],
407
+  itemLocation: [{ required: true, message: '物品位置不能为空', trigger: 'change' }],
408
+  difficultyLevel: [{ required: true, message: '简单/难不能为空', trigger: 'change' }],
409
+  reviewUserId: [{ required: true, message: '回查人不能为空', trigger: 'change' }],
410
+  discriminationType: [{ required: true, message: '判别类型不能为空', trigger: 'change' }],
411
+  isRecovered: [{ required: true, message: '是否追回不能为空', trigger: 'change' }],
412
+  machineOperatingYears: [{ required: true, message: '开机年限不能为空', trigger: 'change' }],
413
+  certificateLevel: [{ required: true, message: '证书级别不能为空', trigger: 'change' }],
414
+  gender: [{ required: true, message: '人员性别不能为空', trigger: 'change' }],
415
+  missCheckReasonCategory: [{ required: true, message: '漏检原因分类不能为空', trigger: 'change' }],
416
+  monthlyAssessment: [{ required: true, message: '月考成绩不能为空', trigger: 'change' }],
417
+  selfTestHasMissCheck: [
418
+    { required: true, message: '本月自测有无漏检不能为空', trigger: 'change' },
419
+
420
+  ]
421
+  // actingSupervisorId 不设置必填校验
422
+}
423
+
424
+// 字典数据
425
+const { discrimination_type, blocked_time_period, blocked_item_position, blocked_monthly_exam_result, blocked_operating_years, blocked_miss_check_reason, sys_user_qualification_level } = useDict('discrimination_type', 'blocked_time_period', 'blocked_item_position', 'blocked_monthly_exam_result', 'blocked_operating_years', 'blocked_miss_check_reason', 'sys_user_qualification_level')
426
+
427
+// 大队选项
428
+const brigadeOptions = ref([])
429
+// 人员选项
430
+const personOptions = ref([])
431
+// 班组长选项
432
+const teamLeaderOptions = ref([])
433
+// 主管选项
434
+const supervisorOptions = ref([])
435
+// 航站楼选项
436
+const areaOptions = ref([])
437
+// 上岗位置选项
438
+const channelOptions = ref([])
439
+
440
+/** 获取职位列表 */
441
+function getPositionList() {
442
+  // 获取航站楼选项
443
+  listPosition({ positionType: 'CHANNEL' }).then(response => {
444
+    areaOptions.value = response.data || []
445
+  })
446
+
447
+  // 获取上岗位置选项
448
+  listPosition({ positionType: 'TERMINL' }).then(response => {
449
+    channelOptions.value = response.data || []
450
+  })
451
+}
452
+
453
+/** 查询漏检列表 */
454
+function getList() {
455
+  loading.value = true
456
+  if (dateRange.value && dateRange.value.length === 2) {
457
+    queryParams.startDate = dateRange.value[0]
458
+    queryParams.endDate = dateRange.value[1]
459
+  } else {
460
+    queryParams.reviewDateStart = null
461
+    queryParams.reviewDateEnd = null
462
+  }
463
+  listMissedInspection(queryParams).then(response => {
464
+    missedInspectionList.value = response.rows
465
+    total.value = response.total
466
+    loading.value = false
467
+  })
468
+}
469
+
470
+/** 获取部门列表 */
471
+function getDeptList() {
472
+  listDept({}).then(response => {
473
+    const deptList = response.data || []
474
+    brigadeOptions.value = deptList.filter(item => item.deptType === 'BRIGADE' && [311, 314, 315].includes(item.deptId)).map(item => ({
475
+      value: item.deptId,
476
+      label: item.deptName
477
+    }))
478
+  })
479
+}
480
+
481
+/** 获取漏检物品树状选项 */
482
+function getMissCheckItemOptions() {
483
+  listCategory().then(response => {
484
+    missCheckItemOptions.value = proxy.handleTree(response.data, "id", "parentId")
485
+  })
486
+}
487
+function getTeamLeaderOptions() {
488
+  selectUserLeaderListByCondition({ roleKeyList: ['banzuzhang'] }).then(response => {
489
+    teamLeaderOptions.value = (response.data || []).map(item => ({
490
+      value: item.userId,
491
+      label: item.nickName
492
+    }))
493
+  })
494
+}
495
+
496
+/** 获取主管选项 */
497
+function getSupervisorOptions() {
498
+  selectUserLeaderListByCondition({ roleKeyList: ['kezhang'] }).then(response => {
499
+    supervisorOptions.value = (response.data || []).map(item => ({
500
+      value: item.userId,
501
+      label: item.nickName
502
+    }))
503
+  })
504
+}
505
+
506
+/** 被回查人变更事件 */
507
+function handleReviewedUserChange(userId) {
508
+  if (userId) {
509
+    //请求回查人班组长
510
+    selectUserLeaderListByCondition({ userId: userId, roleKeyList: ['banzuzhang'] }).then(response => {
511
+      if (response.data && response.data.length > 0) {
512
+        const data = response.data[0]
513
+        form.teamLeaderId = data.userId
514
+      }
515
+    })
516
+    selectUserLeaderListByCondition({ userId: userId, roleKeyList: ['kezhang'] }).then(response => {
517
+      if (response.data && response.data.length > 0) {
518
+        const data = response.data[0]
519
+        form.supervisorId = data.userId
520
+      }
521
+    })
522
+    getUser(userId).then(response => {
523
+      const data = response?.data;
524
+      form.certificateLevel = sys_user_qualification_level.value.find(item => item.value == data?.qualificationLevel).remark
525
+      form.gender = data?.sex == '0' ? '男' : '女'
526
+    })
527
+  } else {
528
+    form.supervisorId = null
529
+    form.teamLeaderId = null
530
+    form.certificateLevel = null
531
+    form.gender = null
532
+  }
533
+}
534
+
535
+/** 获取用户列表 */
536
+function getUserList() {
537
+  listUser({ pageNum: 1, pageSize: 1000 }).then(response => {
538
+    personOptions.value = response.rows.map(item => ({
539
+      value: item.userId,
540
+      label: item.nickName
541
+    }))
542
+  })
543
+}
544
+
545
+// 取消按钮
546
+function cancel() {
547
+  open.value = false
548
+  reset()
549
+}
550
+
551
+// 表单重置
552
+function reset() {
553
+  Object.assign(form, {
554
+    id: null,
555
+    brigadeId: null,
556
+    terminalId: null,
557
+    terminalName: null,
558
+    reviewedUserId: null,
559
+    reviewDate: null,
560
+    missCheckTime: null,
561
+    missCheckTimePeriod: null,
562
+    channelId: null,
563
+    channelName: null,
564
+    supervisorId: null,
565
+    actingSupervisorId: null,
566
+    teamLeaderId: null,
567
+    itemLocation: null,
568
+    difficultyLevel: null,
569
+    reviewUserId: null,
570
+    discriminationType: null,
571
+    isRecovered: null,
572
+    machineOperatingYears: null,
573
+    certificateLevel: null,
574
+    gender: null,
575
+    missCheckReasonCategory: null,
576
+    monthlyAssessment: null,
577
+    selfTestHasMissCheck: 0,
578
+    missCheckItem: null
579
+  })
580
+  proxy.resetForm('missedInspectionRef')
581
+}
582
+
583
+/** 搜索按钮操作 */
584
+function handleQuery() {
585
+  queryParams.pageNum = 1
586
+  getList()
587
+}
588
+
589
+/** 重置按钮操作 */
590
+function resetQuery() {
591
+  dateRange.value = []
592
+  proxy.resetForm('queryRef')
593
+  handleQuery()
594
+}
595
+
596
+// 多选框选中数据
597
+function handleSelectionChange(selection) {
598
+  ids.value = selection.map(item => item.id)
599
+  single.value = selection.length !== 1
600
+  multiple.value = !selection.length
601
+}
602
+
603
+/** 新增按钮操作 */
604
+function handleAdd() {
605
+  reset()
606
+  // 设置默认当前时间
607
+  form.missCheckTime = new Date().toISOString().replace('T', ' ').substring(0, 16)
608
+  // 设置默认当前日期
609
+  form.reviewDate = new Date().toISOString().split('T')[0]
610
+  open.value = true
611
+  title.value = '添加漏检'
612
+}
613
+
614
+/** 修改按钮操作 */
615
+function handleUpdate(row) {
616
+  reset()
617
+  const id = row.id || ids.value[0]
618
+  getMissedInspection(id).then(response => {
619
+    Object.assign(form, response.data)
620
+    open.value = true
621
+    title.value = '修改漏检'
622
+  })
623
+}
624
+
625
+/** 提交按钮 */
626
+function submitForm() {
627
+  proxy.$refs.missedInspectionRef.validate(valid => {
628
+    if (valid) {
629
+      // 处理大队ID和名称映射
630
+      if (form.brigadeId) {
631
+        const brigade = brigadeOptions.value.find(item => item.value === form.brigadeId)
632
+        form.brigadeName = brigade ? brigade.label : null
633
+      }
634
+
635
+      // 处理航站楼ID和名称映射
636
+      if (form.terminalId) {
637
+      
638
+        const area = channelOptions.value.find(item => item.id === form.terminalId)
639
+        form.terminalName = area ? area.name : null
640
+      }
641
+
642
+      // 处理被回查人ID和名称映射
643
+      if (form.reviewedUserId) {
644
+        const reviewedUser = personOptions.value.find(item => item.value === form.reviewedUserId)
645
+        form.reviewedUserName = reviewedUser ? reviewedUser.label : null
646
+      }
647
+
648
+      // 处理上岗位置ID和名称映射
649
+      if (form.channelId) {
650
+      
651
+        const channel = areaOptions.value.find(item => item.id === form.channelId)
652
+        form.channelName = channel ? channel.name : null
653
+      }
654
+
655
+      // 处理分管主管ID和名称映射
656
+      if (form.supervisorId) {
657
+        const supervisor = supervisorOptions.value.find(item => item.value === form.supervisorId)
658
+        form.supervisorName = supervisor ? supervisor.label : null
659
+      }
660
+
661
+      // 处理代管主管ID和名称映射
662
+      if (form.actingSupervisorId) {
663
+      
664
+        const actingSupervisor = supervisorOptions.value.find(item => item.value === form.actingSupervisorId)
665
+        form.actingSupervisorName = actingSupervisor ? actingSupervisor.label : null
666
+      }
667
+
668
+      // 处理分管班组长ID和名称映射
669
+      if (form.teamLeaderId) {
670
+        const teamLeader = teamLeaderOptions.value.find(item => item.value === form.teamLeaderId)
671
+        form.teamLeaderName = teamLeader ? teamLeader.label : null
672
+      }
673
+
674
+      // 处理回查人ID和名称映射
675
+      if (form.reviewUserId) {
676
+        const reviewUser = personOptions.value.find(item => item.value === form.reviewUserId)
677
+        form.reviewUserName = reviewUser ? reviewUser.label : null
678
+      }
679
+
680
+      if (form.id != null) {
681
+        updateMissedInspection(form).then(response => {
682
+          proxy.$modal.msgSuccess('修改成功')
683
+          open.value = false
684
+          reset()
685
+          getList()
686
+        })
687
+      } else {
688
+        addMissedInspection(form).then(response => {
689
+          proxy.$modal.msgSuccess('新增成功')
690
+          open.value = false
691
+          reset()
692
+          getList()
693
+        })
694
+      }
695
+    }
696
+  })
697
+}
698
+
699
+/** 删除按钮操作 */
700
+function handleDelete(row) {
701
+  const id = row.id || ids.value
702
+  proxy.$modal.confirm('是否确认删除漏检编号为"' + id + '"的数据项?').then(function () {
703
+    return delMissedInspection(id)
704
+  }).then(() => {
705
+    getList()
706
+    proxy.$modal.msgSuccess('删除成功')
707
+  }).catch(() => { })
708
+}
709
+
710
+/** 导出按钮操作 */
711
+function handleExport() {
712
+  proxy.download('/blocked/missReview/export', {
713
+    ...queryParams
714
+  }, `漏检_${new Date().getTime()}.xlsx`)
715
+}
716
+
717
+/** 导入按钮操作 */
718
+function handleImport() {
719
+  upload.open = true
720
+}
721
+
722
+/** 下载模板操作 */
723
+function importTemplate() {
724
+  proxy.download('blocked/missReview/importTemplate', {}, `漏检模板_${new Date().getTime()}.xlsx`)
725
+}
726
+
727
+/** 文件上传中处理 */
728
+const handleFileUploadProgress = (event, file, fileList) => {
729
+  upload.isUploading = true
730
+}
731
+
732
+/** 文件上传成功处理 */
733
+const handleFileSuccess = (response, file, fileList) => {
734
+  upload.open = false
735
+  upload.isUploading = false
736
+  proxy.$refs.uploadRef.handleRemove(file)
737
+  proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true })
738
+  getList()
739
+}
740
+
741
+/** 提交上传文件 */
742
+function submitFileForm() {
743
+  proxy.$refs.uploadRef.submit()
744
+}
745
+
746
+onMounted(() => {
747
+  getUserList()
748
+  getList()
749
+  getDeptList()
750
+
751
+  getPositionList()
752
+  getTeamLeaderOptions()
753
+  getSupervisorOptions()
754
+  getMissCheckItemOptions()
755
+})
756
+</script>
757
+
758
+<style lang="less" scoped>
759
+.app-container {
760
+  padding: 20px;
761
+}
762
+</style>

+ 0 - 0
src/views/blockingData/missedInspectionList/漏检统计表


+ 396 - 0
src/views/blockingData/rateList/index.vue

@@ -0,0 +1,396 @@
1
+<template>
2
+  <div class="app-container">
3
+    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
4
+      <el-form-item label="当班大队" prop="dutyBrigadeId">
5
+        <el-select v-model="queryParams.dutyBrigadeId" placeholder="请选择当班大队" clearable style="width: 200px">
6
+          <el-option v-for="item in brigadeOptions" :key="item.value" :label="item.label" :value="item.value" />
7
+        </el-select>
8
+      </el-form-item>
9
+      <el-form-item label="日期" prop="statDate">
10
+        <el-date-picker clearable v-model="queryParams.statDate" type="date" placeholder="选择日期"
11
+          value-format="YYYY-MM-DD" style="width: 200px" />
12
+      </el-form-item>
13
+      <el-form-item label="班次" prop="shift">
14
+        <el-select v-model="queryParams.shift" placeholder="请选择班次" clearable style="width: 200px">
15
+          <el-option v-for="dict in shift" :key="dict.value" :label="dict.label" :value="dict.value" />
16
+        </el-select>
17
+      </el-form-item>
18
+      <el-form-item>
19
+        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
20
+        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
21
+      </el-form-item>
22
+    </el-form>
23
+
24
+    <el-row :gutter="10" class="mb8">
25
+      <el-col :span="1.5">
26
+        <el-button type="primary" plain icon="Plus" @click="handleAdd"
27
+          v-hasPermi="['rateList:rateList:add']">新增</el-button>
28
+      </el-col>
29
+      <el-col :span="1.5">
30
+        <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete"
31
+          v-hasPermi="['rateList:rateList:remove']">删除</el-button>
32
+      </el-col>
33
+      <el-col :span="1.5">
34
+        <el-button type="warning" plain icon="Download" @click="handleExport"
35
+          v-hasPermi="['rateList:rateList:export']">导出</el-button>
36
+      </el-col>
37
+      <el-col :span="1.5">
38
+        <el-button type="info" plain icon="Upload" @click="handleImport"
39
+          v-hasPermi="['rateList:rateList:import']">导入</el-button>
40
+      </el-col>
41
+      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
42
+    </el-row>
43
+
44
+    <el-table v-loading="loading" :data="rateList" @selection-change="handleSelectionChange">
45
+      <el-table-column type="selection" width="55" align="center" />
46
+      <el-table-column label="日期" align="center" prop="statDate" />
47
+      <el-table-column label="旅检国内区域平均速率(高峰期时段)" align="center" prop="travelInspectionDomesticAvgRatePeak"
48
+        min-width="120" />
49
+      <el-table-column label="当班大队" align="center" prop="dutyBrigadeName" />
50
+      <el-table-column label="T1-A区速率(高峰期时段)" align="center" prop="t1AAreaRatePeak" />
51
+      <el-table-column label="T1-B区速率(高峰期时段)" align="center" prop="t1BAreaRatePeak" />
52
+      <el-table-column label="T2-国内速率(高峰期时段)" align="center" prop="t2DomesticRatePeak" />
53
+      <el-table-column label="T2-国际速率(高峰期时段)" align="center" prop="t2InternationalRatePeak" />
54
+      <el-table-column label="班次" align="center" prop="shift" min-width="100">
55
+        <template #default="scope">
56
+          {{ getDictLabel(shift, scope.row.shift) }}
57
+        </template>
58
+      </el-table-column>
59
+      <el-table-column label="T2-中转(高峰期时段)" align="center" prop="t2TransferRatePeak" />
60
+      <el-table-column label="国际及中转区域平均速率(高峰期时段)" align="center" prop="internationalTransferAvgRatePeak"
61
+        min-width="120" />
62
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" min-width="180">
63
+        <template #default="scope">
64
+          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
65
+            v-hasPermi="['rateList:rateList:edit']">修改</el-button>
66
+          <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
67
+            v-hasPermi="['rateList:rateList:remove']">删除</el-button>
68
+        </template>
69
+      </el-table-column>
70
+    </el-table>
71
+
72
+    <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
73
+      v-model:limit="queryParams.pageSize" @pagination="getList" />
74
+
75
+    <el-dialog :title="title" v-model="open" width="1000px" append-to-body @closed="reset" destroy-on-close>
76
+      <el-form ref="rateListRef" :model="form" :rules="rules" label-width="280px">
77
+        <el-row :gutter="20">
78
+          <el-col :span="12">
79
+            <el-form-item label="日期" prop="statDate">
80
+              <el-date-picker v-model="form.statDate" type="date" placeholder="选择日期" value-format="YYYY-MM-DD"
81
+                style="width: 100%" />
82
+            </el-form-item>
83
+          </el-col>
84
+          <el-col :span="12">
85
+            <el-form-item label="当班大队" prop="dutyBrigadeId">
86
+              <el-select v-model="form.dutyBrigadeId" placeholder="请选择当班大队" style="width: 100%">
87
+                <el-option v-for="item in brigadeOptions" :key="item.value" :label="item.label" :value="item.value" />
88
+              </el-select>
89
+            </el-form-item>
90
+          </el-col>
91
+        </el-row>
92
+        <el-row :gutter="20">
93
+          <el-col :span="12">
94
+            <el-form-item label="班次" prop="shift">
95
+              <el-select v-model="form.shift" placeholder="请选择班次" style="width: 100%">
96
+                <el-option v-for="dict in shift" :key="dict.value" :label="dict.label" :value="dict.value" />
97
+              </el-select>
98
+            </el-form-item>
99
+          </el-col>
100
+          <el-col :span="12">
101
+            <el-form-item label="旅检国内区域平均速率(高峰期时段)" prop="travelInspectionDomesticAvgRatePeak">
102
+              <el-input-number v-model="form.travelInspectionDomesticAvgRatePeak" :min="0" :precision="2"
103
+                style="width: 100%" />
104
+            </el-form-item>
105
+          </el-col>
106
+        </el-row>
107
+        <el-row :gutter="20">
108
+          <el-col :span="12">
109
+            <el-form-item label="T1-A区速率(高峰期时段)" prop="t1AAreaRatePeak">
110
+              <el-input-number v-model="form.t1AAreaRatePeak" :min="0" :precision="2" style="width: 100%" />
111
+            </el-form-item>
112
+          </el-col>
113
+          <el-col :span="12">
114
+            <el-form-item label="T1-B区速率(高峰期时段)" prop="t1BAreaRatePeak">
115
+              <el-input-number v-model="form.t1BAreaRatePeak" :min="0" :precision="2" style="width: 100%" />
116
+            </el-form-item>
117
+          </el-col>
118
+        </el-row>
119
+        <el-row :gutter="20">
120
+          <el-col :span="12">
121
+            <el-form-item label="T2-国内速率(高峰期时段)" prop="t2DomesticRatePeak">
122
+              <el-input-number v-model="form.t2DomesticRatePeak" :min="0" :precision="2" style="width: 100%" />
123
+            </el-form-item>
124
+          </el-col>
125
+          <el-col :span="12">
126
+            <el-form-item label="T2-国际速率(高峰期时段)" prop="t2InternationalRatePeak">
127
+              <el-input-number v-model="form.t2InternationalRatePeak" :min="0" :precision="2" style="width: 100%" />
128
+            </el-form-item>
129
+          </el-col>
130
+        </el-row>
131
+        <el-row :gutter="20">
132
+          <el-col :span="12">
133
+            <el-form-item label="T2-中转(高峰期时段)" prop="t2TransferRatePeak">
134
+              <el-input-number v-model="form.t2TransferRatePeak" :min="0" :precision="2" style="width: 100%" />
135
+            </el-form-item>
136
+          </el-col>
137
+          <el-col :span="12">
138
+            <el-form-item label="国际及中转区域平均速率(高峰期时段)" prop="internationalTransferAvgRatePeak">
139
+              <el-input-number v-model="form.internationalTransferAvgRatePeak" :min="0" :precision="2"
140
+                style="width: 100%" />
141
+            </el-form-item>
142
+          </el-col>
143
+        </el-row>
144
+      </el-form>
145
+      <template #footer>
146
+        <div class="dialog-footer">
147
+          <el-button type="primary" @click="submitForm">确 定</el-button>
148
+          <el-button @click="cancel">取 消</el-button>
149
+        </div>
150
+      </template>
151
+    </el-dialog>
152
+
153
+    <el-dialog title="导入" v-model="upload.open" width="500px" append-to-body>
154
+      <el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" :action="upload.url"
155
+        :disabled="upload.isUploading" :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess"
156
+        :auto-upload="false" drag>
157
+        <el-icon class="el-icon--upload"><upload-filled /></el-icon>
158
+        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
159
+        <template #tip>
160
+          <div class="el-upload__tip text-center">
161
+            <span>仅允许导入xls、xlsx格式文件。</span>
162
+            <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline"
163
+              @click="importTemplate">下载模板</el-link>
164
+          </div>
165
+        </template>
166
+      </el-upload>
167
+      <template #footer>
168
+        <div class="dialog-footer">
169
+          <el-button type="primary" @click="submitFileForm">确 定</el-button>
170
+          <el-button @click="upload.open = false">取 消</el-button>
171
+        </div>
172
+      </template>
173
+    </el-dialog>
174
+  </div>
175
+</template>
176
+
177
+<script setup>
178
+import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
179
+import { listRate, getRate, delRate, addRate, updateRate, exportRate, downloadTemplate } from '@/api/blockingData/rateList'
180
+import { listDept } from '@/api/system/dept'
181
+import { getToken } from '@/utils/auth'
182
+import { UploadFilled } from '@element-plus/icons-vue'
183
+
184
+const { proxy } = getCurrentInstance()
185
+const { shift } = proxy.useDict('shift')
186
+
187
+const loading = ref(true)
188
+const ids = ref([])
189
+const single = ref(true)
190
+const multiple = ref(true)
191
+const showSearch = ref(true)
192
+const total = ref(0)
193
+const rateList = ref([])
194
+const title = ref('')
195
+const open = ref(false)
196
+
197
+const upload = reactive({
198
+  open: false,
199
+  isUploading: false,
200
+  headers: { Authorization: 'Bearer ' + getToken() },
201
+  url: import.meta.env.VITE_APP_BASE_API + '/blocked/rate/importData'
202
+})
203
+
204
+const queryParams = reactive({
205
+  pageNum: 1,
206
+  pageSize: 10,
207
+  dutyBrigadeId: null,
208
+  statDate: null,
209
+  shift: null
210
+})
211
+
212
+const form = reactive({})
213
+
214
+const rules = {
215
+  statDate: [{ required: true, message: '日期不能为空', trigger: 'change' }],
216
+  dutyBrigadeId: [{ required: true, message: '当班大队不能为空', trigger: 'change' }],
217
+  shift: [{ required: true, message: '班次不能为空', trigger: 'change' }],
218
+  travelInspectionDomesticAvgRatePeak: [{ required: true, message: '旅检国内区域平均速率(高峰期时段)不能为空', trigger: 'change' }],
219
+  t1AAreaRatePeak: [{ required: true, message: 'T1-A区速率(高峰期时段)不能为空', trigger: 'change' }],
220
+  t1BAreaRatePeak: [{ required: true, message: 'T1-B区速率(高峰期时段)不能为空', trigger: 'change' }],
221
+  t2DomesticRatePeak: [{ required: true, message: 'T2-国内速率(高峰期时段)不能为空', trigger: 'change' }],
222
+  t2InternationalRatePeak: [{ required: true, message: 'T2-国际速率(高峰期时段)不能为空', trigger: 'change' }],
223
+  t2TransferRatePeak: [{ required: true, message: 'T2-中转(高峰期时段)不能为空', trigger: 'change' }],
224
+  internationalTransferAvgRatePeak: [{ required: true, message: '国际及中转区域平均速率(高峰期时段)不能为空', trigger: 'change' }]
225
+}
226
+
227
+const brigadeOptions = ref([])
228
+
229
+function getList() {
230
+  loading.value = true
231
+  listRate(queryParams).then(response => {
232
+    rateList.value = response.rows
233
+    total.value = response.total
234
+    loading.value = false
235
+  })
236
+}
237
+
238
+const getDictLabel = (dictList, value) => {
239
+  if (!dictList || !value) return ''
240
+  const dict = dictList.find(item => item.value === value)
241
+  return dict ? dict.label : value
242
+}
243
+
244
+function getDeptList() {
245
+  listDept({}).then(response => {
246
+    const deptList = response.data || []
247
+    brigadeOptions.value = deptList.filter(item => item.deptType === 'BRIGADE' && [311, 314, 315].includes(item.deptId)).map(item => ({
248
+      value: item.deptId,
249
+      label: item.deptName
250
+    }))
251
+  })
252
+}
253
+
254
+function cancel() {
255
+  open.value = false
256
+  reset()
257
+}
258
+
259
+function reset() {
260
+  Object.assign(form, {
261
+    id: null,
262
+    statDate: null,
263
+    dutyBrigadeId: null,
264
+    shift: null,
265
+    shiftDesc: null,
266
+    travelInspectionDomesticAvgRatePeak: 0,
267
+    t1AAreaRatePeak: 0,
268
+    t1BAreaRatePeak: 0,
269
+    t2DomesticRatePeak: 0,
270
+    t2InternationalRatePeak: 0,
271
+    t2TransferRatePeak: 0,
272
+    internationalTransferAvgRatePeak: 0
273
+  })
274
+  proxy.resetForm('rateListRef')
275
+}
276
+
277
+function handleQuery() {
278
+  queryParams.pageNum = 1
279
+  getList()
280
+}
281
+
282
+function resetQuery() {
283
+  proxy.resetForm('queryRef')
284
+  handleQuery()
285
+}
286
+
287
+function handleSelectionChange(selection) {
288
+  ids.value = selection.map(item => item.id)
289
+  single.value = selection.length !== 1
290
+  multiple.value = !selection.length
291
+}
292
+
293
+function handleAdd() {
294
+  reset()
295
+  // 设置默认当前日期
296
+  form.statDate = new Date().toISOString().split('T')[0]
297
+  open.value = true
298
+  title.value = '添加速率统计'
299
+}
300
+
301
+function handleUpdate(row) {
302
+  reset()
303
+  const id = row.id || ids.value[0]
304
+  getRate(id).then(response => {
305
+    Object.assign(form, response.data)
306
+    open.value = true
307
+    title.value = '修改速率统计'
308
+  })
309
+}
310
+
311
+function submitForm() {
312
+  proxy.$refs.rateListRef.validate(valid => {
313
+    if (valid) {
314
+      if (form.shift) {
315
+        const shiftItem = shift.value.find(item => item.value === form.shift)
316
+        form.shiftDesc = shiftItem ? shiftItem.label : null
317
+      }
318
+      if (form.dutyBrigadeId) {
319
+        const brigadeItem = brigadeOptions.value.find(item => item.value === form.dutyBrigadeId)
320
+        form.dutyBrigadeName = brigadeItem ? brigadeItem.label : null
321
+      }
322
+
323
+      if (form.id != null) {
324
+        updateRate(form).then(response => {
325
+          proxy.$modal.msgSuccess('修改成功')
326
+          open.value = false
327
+          reset()
328
+          getList()
329
+        })
330
+      } else {
331
+        addRate(form).then(response => {
332
+          proxy.$modal.msgSuccess('新增成功')
333
+          open.value = false
334
+          reset()
335
+          getList()
336
+        })
337
+      }
338
+    }
339
+  })
340
+}
341
+
342
+function handleDelete(row) {
343
+  const id = row.id || ids.value
344
+  proxy.$modal.confirm('是否确认删除速率统计编号为"' + id + '"的数据项?').then(function () {
345
+    return delRate(id)
346
+  }).then(() => {
347
+    getList()
348
+    proxy.$modal.msgSuccess('删除成功')
349
+  }).catch(() => { })
350
+}
351
+
352
+function handleExport() {
353
+  proxy.download('blocked/rate/export', {
354
+    ...queryParams
355
+  }, `速率统计_${new Date().getTime()}.xlsx`)
356
+}
357
+
358
+function handleImport() {
359
+  upload.open = true
360
+}
361
+
362
+/** 下载模板操作 */
363
+function importTemplate() {
364
+  proxy.download('/blocked/rate/importTemplate', {}, `速率统计模板${new Date().getTime()}.xlsx`)
365
+}
366
+
367
+/** 文件上传中处理 */
368
+const handleFileUploadProgress = (event, file, fileList) => {
369
+  upload.isUploading = true
370
+}
371
+
372
+/** 文件上传成功处理 */
373
+const handleFileSuccess = (response, file, fileList) => {
374
+  upload.open = false
375
+  upload.isUploading = false
376
+  proxy.$refs.uploadRef.handleRemove(file)
377
+  proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true })
378
+  getList()
379
+}
380
+
381
+/** 提交上传文件 */
382
+function submitFileForm() {
383
+  proxy.$refs.uploadRef.submit()
384
+}
385
+
386
+onMounted(() => {
387
+  getList()
388
+  getDeptList()
389
+})
390
+</script>
391
+
392
+<style lang="less" scoped>
393
+.app-container {
394
+  padding: 20px;
395
+}
396
+</style>

+ 0 - 0
src/views/blockingData/rateList/速率表


+ 14 - 0
src/views/system/dept/index.vue

@@ -124,6 +124,18 @@
124 124
                      </el-select>
125 125
                   </el-form-item>
126 126
                </el-col>
127
+               <el-col :span="12" v-if="form.deptType === 'BRIGADE'">
128
+                  <el-form-item label="大队性质" prop="brigadeNature">
129
+                     <el-select v-model="form.brigadeNature" placeholder="请选择大队性质">
130
+                        <el-option
131
+                        v-for="dict in brigade_nature"
132
+                        :key="dict.value"
133
+                        :label="dict.label"
134
+                        :value="dict.value"
135
+                        ></el-option>
136
+                     </el-select>
137
+                  </el-form-item>
138
+               </el-col>
127 139
                <el-col :span="12">
128 140
                   <el-form-item label="显示排序" prop="orderNum">
129 141
                      <el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
@@ -173,6 +185,7 @@ import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild }
173 185
 const { proxy } = getCurrentInstance()
174 186
 const { sys_normal_disable } = proxy.useDict("sys_normal_disable")
175 187
 const { base_dept_type } = proxy.useDict('base_dept_type')
188
+const { brigade_nature } = proxy.useDict('brigade_nature')
176 189
 const deptList = ref([])
177 190
 const open = ref(false)
178 191
 const loading = ref(true)
@@ -222,6 +235,7 @@ function reset() {
222 235
     parentId: undefined,
223 236
     deptName: undefined,
224 237
     deptType: undefined,
238
+    brigadeNature: undefined,
225 239
     orderNum: 0,
226 240
     leader: undefined,
227 241
     phone: undefined,

Разлика између датотеке није приказан због своје велике величине
+ 72 - 4
src/views/system/user/components/UserInfoEdit.vue