Browse Source

Merge branch 'blockingData' into dev

# Conflicts:
#	src/api/system/user.js
huoyi 1 month ago
parent
commit
264bf6081a
31 changed files with 5548 additions and 6 deletions
  1. 382 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. 260 0
      src/views/blockingData/blockingDataScreen/components/ModuleBrigadeOne.vue
  10. 105 0
      src/views/blockingData/blockingDataScreen/components/ModuleBrigadeThree.vue
  11. 309 0
      src/views/blockingData/blockingDataScreen/components/ModuleBrigadeTwo.vue
  12. 57 0
      src/views/blockingData/blockingDataScreen/components/ModuleContainer.vue
  13. 224 0
      src/views/blockingData/blockingDataScreen/components/ModuleFour.vue
  14. 575 0
      src/views/blockingData/blockingDataScreen/components/ModuleOne.vue
  15. 105 0
      src/views/blockingData/blockingDataScreen/components/ModuleThree.vue
  16. 329 0
      src/views/blockingData/blockingDataScreen/components/ModuleTwo.vue
  17. 217 0
      src/views/blockingData/blockingDataScreen/components/RankList.vue
  18. 319 0
      src/views/blockingData/blockingDataScreen/index.vue
  19. 0 0
      src/views/blockingData/blockingDataScreen/查堵大屏
  20. 548 0
      src/views/blockingData/dailyLuggageCheckInList/index.vue
  21. 0 0
      src/views/blockingData/dailyLuggageCheckInList/每日行李过检查堵表
  22. 427 0
      src/views/blockingData/dailyLuggageInspectionScheduleByTime/index.vue
  23. 0 0
      src/views/blockingData/dailyLuggageInspectionScheduleByTime/每日各时段查堵行李表
  24. 171 0
      src/views/blockingData/experienceDistributionStartupPerson/index.vue
  25. 0 0
      src/views/blockingData/experienceDistributionStartupPerson/开机人员年限分布
  26. 770 0
      src/views/blockingData/missedInspectionList/index.vue
  27. 0 0
      src/views/blockingData/missedInspectionList/漏检统计表
  28. 386 0
      src/views/blockingData/rateList/index.vue
  29. 0 0
      src/views/blockingData/rateList/速率表
  30. 14 0
      src/views/system/dept/index.vue
  31. 72 4
      src/views/system/user/components/UserInfoEdit.vue

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

@@ -0,0 +1,382 @@
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 brigadeCertificateDistribution(query) {
209
+    return request({
210
+        url: '/blocked/dashboard/brigadeCertificateDistribution',
211
+        method: 'get',
212
+        params: query
213
+    })
214
+}
215
+//T1速率统计数据
216
+export function t1RateStats(query) {
217
+    return request({
218
+        url: '/blocked/rate/screen/t1',
219
+        method: 'get',
220
+        params: query
221
+    })
222
+}
223
+//T2速率统计数据
224
+export function t2RateStats(query) {
225
+    return request({
226
+        url: '/blocked/rate/screen/t2',
227
+        method: 'get',
228
+        params: query
229
+    })
230
+}
231
+//国内速率统计数据
232
+export function insideRateStats(query) {
233
+    return request({
234
+        url: '/blocked/rate/screen/domestic',
235
+        method: 'get',
236
+        params: query
237
+    })
238
+}
239
+//国际速率统计数据
240
+export function outsideRateStats(query) {
241
+    return request({
242
+        url: '/blocked/rate/screen/international',
243
+        method: 'get',
244
+        params: query
245
+    })
246
+}
247
+//查询两楼每日查堵走势
248
+export function dailyTerminalTrend(query) {
249
+    return request({
250
+        url: '/blocked/dashboard/brigade/dailyTerminalTrend',
251
+        method: 'get',
252
+        params: query
253
+    })
254
+}
255
+//查询两楼每日过检图像数
256
+export function dailyTerminalLuggageTrend(query) {
257
+    return request({
258
+        url: '/blocked/dashboard/brigade/dailyTerminalLuggageTrend',
259
+        method: 'get',
260
+        params: query
261
+    })
262
+}
263
+//查询两楼查堵数(包含行检)
264
+export function terminalBlockedStats(query) {
265
+    return request({
266
+        url: '/blocked/dashboard/brigade/terminalBlockedStats',
267
+        method: 'get',
268
+        params: query
269
+    })
270
+}
271
+//查询查堵时间段过检行李数
272
+export function timePeriodLuggageStats(query) {
273
+    return request({
274
+        url: '/blocked/dashboard/brigade/timePeriodLuggageStats',
275
+        method: 'get',
276
+        params: query
277
+    })
278
+}
279
+//查询两楼每日查堵万分率
280
+export function dailyTerminalRateTrend(query) {
281
+    return request({
282
+        url: '/blocked/dashboard/brigade/dailyTerminalRateTrend',
283
+        method: 'get',
284
+        params: query
285
+    })
286
+}
287
+//查询查堵物品分布
288
+export function brigadeItemDistribution(query) {
289
+    return request({
290
+        url: '/blocked/dashboard/brigade/itemDistribution',
291
+        method: 'get',
292
+        params: query
293
+    })
294
+}
295
+//查堵-主管排行榜
296
+export function brigadeSupervisorRanking(query) {
297
+    return request({
298
+        url: '/blocked/dashboard/brigade/supervisorRanking',
299
+        method: 'get',
300
+        params: query
301
+    })
302
+}
303
+//查堵-班组排行榜
304
+export function brigadeTeamLeaderRanking(query) {
305
+    return request({
306
+        url: '/blocked/dashboard/brigade/teamLeaderRanking',
307
+        method: 'get',
308
+        params: query
309
+    })
310
+}
311
+//查堵-人员排行榜
312
+export function brigadeReviewedUserRanking(query) {
313
+    return request({
314
+        url: '/blocked/dashboard/brigade/reviewedUserRanking',
315
+        method: 'get',
316
+        params: query
317
+    })
318
+}
319
+//查询查堵男女比例
320
+export function brigadeGenderDistribution(query) {
321
+    return request({
322
+        url: '/blocked/dashboard/brigade/genderDistribution',
323
+        method: 'get',
324
+        params: query
325
+    })
326
+}
327
+//查询查堵人员证书级别分布
328
+export function brigadeCertificateDistributionDetail(query) {
329
+    return request({
330
+        url: '/blocked/dashboard/brigade/certificateLevelDistribution',
331
+        method: 'get',
332
+        params: query
333
+    })
334
+}
335
+//查询证书级别人员基数
336
+export function personnelCertificateBase(query) {
337
+    return request({
338
+        url: '/blocked/dashboard/brigade/personnelCertificateBase',
339
+        method: 'get',
340
+        params: query
341
+    })
342
+}
343
+//查询查堵物品位置分布
344
+export function brigadeItemLocationDistribution(query) {
345
+    return request({
346
+        url: '/blocked/dashboard/brigade/itemLocationDistribution',
347
+        method: 'get',
348
+        params: query
349
+    })
350
+}
351
+//查询查堵-简单/困难图像数
352
+export function brigadeDifficultyDistribution(query) {
353
+    return request({
354
+        url: '/blocked/dashboard/brigade/difficultyDistribution',
355
+        method: 'get',
356
+        params: query
357
+    })
358
+}
359
+//查询查堵-主管分管次数分布
360
+export function brigadeSupervisorDistribution(query) {
361
+    return request({
362
+        url: '/blocked/dashboard/brigade/supervisorDistribution',
363
+        method: 'get',
364
+        params: query
365
+    })
366
+}
367
+//查询查堵原因分类分布
368
+export function brigadeMissCheckReasonDistribution(query) {
369
+    return request({
370
+        url: '/blocked/dashboard/brigade/missCheckReasonDistribution',
371
+        method: 'get',
372
+        params: query
373
+    })
374
+}
375
+//查询查堵人员开机年限分布
376
+export function brigadeOperatingYearsDistribution(query) {
377
+    return request({
378
+        url: '/blocked/dashboard/brigade/operatingYearsDistribution',
379
+        method: 'get',
380
+        params: query
381
+    })
382
+}

+ 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',

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

@@ -0,0 +1,260 @@
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-value">819</div>
10
+              <div class="stat-unit">起</div>
11
+            </div>
12
+          </div>
13
+        </div>
14
+        <div class="chart-item">
15
+          <div class="chart-title">两楼每日查堵走势</div>
16
+          <div ref="chart1" class="echarts"></div>
17
+        </div>
18
+        <div class="chart-item">
19
+          <div class="chart-title">两楼每日过检图像数</div>
20
+          <div ref="chart2" class="echarts"></div>
21
+        </div>
22
+      </div>
23
+      
24
+      <div class="chart-row">
25
+        <div class="chart-item">
26
+          <div class="chart-title">两楼查堵数(包含行检)</div>
27
+          <div ref="chart3" class="echarts"></div>
28
+        </div>
29
+        <div class="chart-item">
30
+          <div class="chart-title">查堵时间段过检行李数</div>
31
+          <div ref="chart4" class="echarts"></div>
32
+        </div>
33
+      </div>
34
+      
35
+      <div class="chart-row">
36
+        <div class="chart-item">
37
+          <div class="chart-title">两楼每日查堵万分率</div>
38
+          <div ref="chart5" class="echarts"></div>
39
+        </div>
40
+        <div class="chart-item">
41
+          <div class="chart-title">查堵物品分布</div>
42
+          <div ref="chart6" class="echarts"></div>
43
+        </div>
44
+      </div>
45
+    </div>
46
+  </module-container>
47
+</template>
48
+
49
+<script setup>
50
+import { ref, onMounted } from 'vue'
51
+import * as echarts from 'echarts'
52
+import ModuleContainer from './ModuleContainer.vue'
53
+import { useEcharts } from '@/hooks/chart.js'
54
+
55
+// 接收筛选参数
56
+const props = defineProps({
57
+  filterParams: {
58
+    type: Object,
59
+    default: () => ({})
60
+  }
61
+})
62
+
63
+const chart1 = ref(null)
64
+const chart2 = ref(null)
65
+const chart3 = ref(null)
66
+const chart4 = ref(null)
67
+const chart5 = ref(null)
68
+const chart6 = ref(null)
69
+
70
+const { setOption: setOption1 } = useEcharts(chart1)
71
+const { setOption: setOption2 } = useEcharts(chart2)
72
+const { setOption: setOption3 } = useEcharts(chart3)
73
+const { setOption: setOption4 } = useEcharts(chart4)
74
+const { setOption: setOption5 } = useEcharts(chart5)
75
+const { setOption: setOption6 } = useEcharts(chart6)
76
+
77
+const xAxisData = Array.from({ length: 30 }, (_, i) => `${i + 1}日`)
78
+
79
+const generateData = (count, min, max) => {
80
+  const data = []
81
+  for (let i = 0; i < count; i++) {
82
+    data.push(Math.floor(Math.random() * (max - min + 1)) + min)
83
+  }
84
+  return data
85
+}
86
+
87
+const lineChartOption = (data, color, title) => ({
88
+  grid: { left: '10%', top: '15%', right: '5%', bottom: '15%', containLabel: true },
89
+  xAxis: { type: 'category', data: xAxisData, axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' } },
90
+  yAxis: { type: 'value', axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' }, splitLine: { lineStyle: { color: '#eee' } } },
91
+  series: [{ name: title, type: 'line', smooth: true, symbol: 'circle', symbolSize: 6, data: data, itemStyle: { color: color }, lineStyle: { color: color } }]
92
+})
93
+
94
+const areaChartOption = (data, color) => ({
95
+  grid: { left: '10%', top: '15%', right: '5%', bottom: '15%', containLabel: true },
96
+  xAxis: { type: 'category', data: xAxisData, axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' } },
97
+  yAxis: { type: 'value', axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' }, splitLine: { lineStyle: { color: '#eee' } } },
98
+  series: [{ type: 'line', smooth: true, symbol: 'circle', symbolSize: 6, data: data, itemStyle: { color: color }, lineStyle: { color: color }, areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: color + '40' }, { offset: 1, color: color + '10' }]) } }]
99
+})
100
+
101
+const barChartOption = (data, color) => ({
102
+  grid: { left: '15%', top: '15%', right: '5%', bottom: '15%', containLabel: true },
103
+  xAxis: { type: 'category', data: data.map(d => d.name), axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' } },
104
+  yAxis: { type: 'value', axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' }, splitLine: { lineStyle: { color: '#eee' } } },
105
+  series: [{ type: 'bar', data: data.map(d => d.value), itemStyle: { color: color }, barWidth: 20 }]
106
+})
107
+
108
+const horizontalBarChartOption = (data, color) => ({
109
+  grid: { left: '20%', top: '15%', right: '5%', bottom: '15%', containLabel: true },
110
+  xAxis: { type: 'value', axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' }, splitLine: { lineStyle: { color: '#eee' } } },
111
+  yAxis: { type: 'category', data: data.map(d => d.name), axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' } },
112
+  series: [{ type: 'bar', data: data.map(d => d.value), itemStyle: { color: color }, barWidth: 15 }]
113
+})
114
+
115
+const pieChartOption = (data, colors) => ({
116
+  color: colors,
117
+  series: [{
118
+    type: 'pie',
119
+    radius: '65%',
120
+    center: ['50%', '55%'],
121
+    data: data,
122
+    label: { show: true, formatter: '{b}\n{c}%', fontSize: 10 }
123
+  }]
124
+})
125
+
126
+onMounted(() => {
127
+  setOption1(lineChartOption(generateData(30, 5, 20), '#3b82f6', '查堵数量'))
128
+  setOption2(areaChartOption(generateData(30, 10000, 30000), '#22c55e'))
129
+  setOption3(horizontalBarChartOption([{ name: 'T1', value: 388 }, { name: 'T2', value: 431 }], '#3b82f6'))
130
+  
131
+  const timeData = [
132
+    { name: '02:00-04:00', value: 20 },
133
+    { name: '04:00-06:00', value: 45 },
134
+    { name: '06:00-08:00', value: 80 },
135
+    { name: '08:00-10:00', value: 110 },
136
+    { name: '10:00-12:00', value: 95 },
137
+    { name: '12:00-14:00', value: 85 },
138
+    { name: '14:00-16:00', value: 100 },
139
+    { name: '16:00-18:00', value: 120 },
140
+    { name: '18:00-20:00', value: 115 },
141
+    { name: '20:00-22:00', value: 80 },
142
+    { name: '22:00-24:00', value: 45 }
143
+  ]
144
+  setOption4(barChartOption(timeData, '#3b82f6'))
145
+  
146
+  setOption5(lineChartOption(generateData(30, 0.5, 3.0), '#ec4899', '查堵万分率'))
147
+  
148
+  setOption6(pieChartOption([
149
+    { name: '水果刀', value: 1.02 },
150
+    { name: '警棍', value: 0.37 },
151
+    { name: '甩棍', value: 0.52 },
152
+    { name: '打火机', value: 50.52 },
153
+    { name: '其他', value: 47.57 }
154
+  ], ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6']))
155
+})
156
+</script>
157
+
158
+<style lang="less" scoped>
159
+.module-brigade-content {
160
+  height: 100%;
161
+  display: flex;
162
+  flex-direction: column;
163
+  gap: 10px;
164
+  overflow-y: auto;
165
+}
166
+
167
+.stats-row {
168
+  display: flex;
169
+  gap: 10px;
170
+  flex-shrink: 0;
171
+}
172
+
173
+.stat-card {
174
+  flex: 1;
175
+  background: #fff;
176
+  border-radius: 6px;
177
+  padding: 15px;
178
+  border: 1px solid #eee;
179
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
180
+  min-height: 120px;
181
+}
182
+
183
+.stat-card-inner {
184
+  display: flex;
185
+  height: 100%;
186
+  align-items: center;
187
+  justify-content: center;
188
+}
189
+
190
+.stat-left {
191
+  display: flex;
192
+  flex-direction: column;
193
+  align-items: center;
194
+}
195
+
196
+.stat-label {
197
+  font-size: 14px;
198
+  color: #666;
199
+  margin-bottom: 8px;
200
+}
201
+
202
+.stat-value {
203
+  font-size: 48px;
204
+  font-weight: bold;
205
+  color: #3b82f6;
206
+}
207
+
208
+.stat-unit {
209
+  font-size: 14px;
210
+  color: #666;
211
+  margin-top: 5px;
212
+}
213
+
214
+.combined-row {
215
+  display: flex;
216
+  gap: 10px;
217
+  flex-shrink: 0;
218
+}
219
+
220
+.combined-row .stat-card {
221
+  flex: 0.5;
222
+  min-height: 200px;
223
+}
224
+
225
+.combined-row .chart-item {
226
+  flex: 1;
227
+}
228
+
229
+.chart-row {
230
+  display: flex;
231
+  gap: 10px;
232
+  flex-shrink: 0;
233
+}
234
+
235
+.chart-item {
236
+  flex: 1;
237
+  background: #fff;
238
+  border-radius: 6px;
239
+  padding: 10px;
240
+  border: 1px solid #eee;
241
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
242
+  display: flex;
243
+  flex-direction: column;
244
+  min-height: 200px;
245
+}
246
+
247
+.chart-title {
248
+  font-size: 14px;
249
+  color: #333;
250
+  margin-bottom: 5px;
251
+  text-align: left;
252
+  font-weight: 500;
253
+}
254
+
255
+.echarts {
256
+  flex: 1;
257
+  width: 100%;
258
+  min-height: 0;
259
+}
260
+</style>

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

@@ -0,0 +1,105 @@
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 } from 'vue'
20
+import * as echarts from 'echarts'
21
+import ModuleContainer from './ModuleContainer.vue'
22
+import { useEcharts } from '@/hooks/chart.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
+const ringChartOption = (data, colors) => ({
39
+  color: colors,
40
+  series: [{
41
+    type: 'pie',
42
+    radius: ['40%', '65%'],
43
+    center: ['50%', '55%'],
44
+    data: data,
45
+    label: { show: true, formatter: '{b}\n{c}%', fontSize: 10 }
46
+  }]
47
+})
48
+
49
+onMounted(() => {
50
+  setOption1(ringChartOption([
51
+    { name: '漏检次数≤3次', value: 65.2 },
52
+    { name: '漏检次数4-6次', value: 22.8 },
53
+    { name: '漏检次数≥7次', value: 12.0 }
54
+  ], ['#22c55e', '#3b82f6', '#f97316']))
55
+
56
+  setOption2(ringChartOption([
57
+    { name: '优秀(90-100分)', value: 35.4 },
58
+    { name: '良好(80-89分)', value: 42.1 },
59
+    { name: '合格(70-79分)', value: 18.3 },
60
+    { name: '不合格(<70分)', value: 4.2 }
61
+  ], ['#22c55e', '#3b82f6', '#fbbf24', '#ef4444']))
62
+})
63
+</script>
64
+
65
+<style lang="less" scoped>
66
+.module-brigade-content {
67
+  height: 100%;
68
+  display: flex;
69
+  flex-direction: column;
70
+  gap: 10px;
71
+  overflow-y: auto;
72
+}
73
+
74
+.chart-row {
75
+  display: flex;
76
+  gap: 10px;
77
+  flex-shrink: 0;
78
+}
79
+
80
+.chart-item {
81
+  flex: 1;
82
+  background: #fff;
83
+  border-radius: 6px;
84
+  padding: 10px;
85
+  border: 1px solid #eee;
86
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
87
+  display: flex;
88
+  flex-direction: column;
89
+  min-height: 200px;
90
+}
91
+
92
+.chart-title {
93
+  font-size: 14px;
94
+  color: #333;
95
+  margin-bottom: 5px;
96
+  text-align: left;
97
+  font-weight: 500;
98
+}
99
+
100
+.echarts {
101
+  flex: 1;
102
+  width: 100%;
103
+  min-height: 0;
104
+}
105
+</style>

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

@@ -0,0 +1,309 @@
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
+          <rank-list :rank-data="rankData1" title="查堵-主管排行榜" header-label="排名" header-name="分管主管" header-count="计数" />
8
+        </div>
9
+        <div class="chart-item">
10
+          <div class="chart-title">查堵-班组长排行榜</div>
11
+          <rank-list :rank-data="rankData2" title="查堵-班组长排行榜" header-label="排名" header-name="分管班组长" header-count="计数" />
12
+        </div>
13
+        <div class="chart-item">
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="chart-row">
20
+        <div class="chart-item">
21
+          <div class="chart-title">查堵男女比例</div>
22
+          <div ref="chart4" class="echarts"></div>
23
+        </div>
24
+        <div class="chart-item">
25
+          <div class="chart-title">查堵人员证书级别分布</div>
26
+          <div ref="chart5" class="echarts"></div>
27
+        </div>
28
+        <div class="chart-item">
29
+          <div class="chart-title">证书级别人员基数</div>
30
+          <div ref="chart6" class="echarts"></div>
31
+        </div>
32
+      </div>
33
+      
34
+      <div class="chart-row">
35
+        <div class="chart-item">
36
+          <div class="chart-title">查堵物品位置</div>
37
+          <div ref="chart7" class="echarts"></div>
38
+        </div>
39
+        <div class="chart-item">
40
+          <div class="chart-title">查堵-困难图像数</div>
41
+          <div class="number-display">
42
+            <div class="number-value">819</div>
43
+            <div class="number-unit">幅</div>
44
+          </div>
45
+        </div>
46
+        <div class="chart-item">
47
+          <div class="chart-title">查堵-简单图像数</div>
48
+          <div class="number-display">
49
+            <div class="number-value">819</div>
50
+            <div class="number-unit">幅</div>
51
+          </div>
52
+        </div>
53
+      </div>
54
+      
55
+      <div class="chart-row">
56
+        <div class="chart-item">
57
+          <div class="chart-title">查堵-主管分管次数</div>
58
+          <div ref="chart10" class="echarts"></div>
59
+        </div>
60
+        <div class="chart-item">
61
+          <div class="chart-title">查堵原因分类</div>
62
+          <div ref="chart11" class="echarts"></div>
63
+        </div>
64
+      </div>
65
+      
66
+      <div class="chart-row">
67
+        <div class="chart-item">
68
+          <div class="chart-title">查堵人员开机年限分布</div>
69
+          <div ref="chart12" class="echarts"></div>
70
+        </div>
71
+        <div class="chart-item">
72
+          <div class="chart-title">大队开机年限人员分布</div>
73
+          <div ref="chart13" class="echarts"></div>
74
+        </div>
75
+      </div>
76
+    </div>
77
+  </module-container>
78
+</template>
79
+
80
+<script setup>
81
+import { ref, onMounted, reactive } from 'vue'
82
+import * as echarts from 'echarts'
83
+import ModuleContainer from './ModuleContainer.vue'
84
+import RankList from './RankList.vue'
85
+import { useEcharts } from '@/hooks/chart.js'
86
+
87
+// 接收筛选参数
88
+const props = defineProps({
89
+  filterParams: {
90
+    type: Object,
91
+    default: () => ({})
92
+  }
93
+})
94
+
95
+const chart4 = ref(null)
96
+const chart5 = ref(null)
97
+const chart6 = ref(null)
98
+const chart7 = ref(null)
99
+const chart10 = ref(null)
100
+const chart11 = ref(null)
101
+const chart12 = ref(null)
102
+const chart13 = ref(null)
103
+
104
+const { setOption: setOption4 } = useEcharts(chart4)
105
+const { setOption: setOption5 } = useEcharts(chart5)
106
+const { setOption: setOption6 } = useEcharts(chart6)
107
+const { setOption: setOption7 } = useEcharts(chart7)
108
+const { setOption: setOption10 } = useEcharts(chart10)
109
+const { setOption: setOption11 } = useEcharts(chart11)
110
+const { setOption: setOption12 } = useEcharts(chart12)
111
+const { setOption: setOption13 } = useEcharts(chart13)
112
+
113
+const rankData1 = reactive([
114
+  { name: '李鑫锋', value: 102 },
115
+  { name: '李学玲', value: 94 },
116
+  { name: '郑杰', value: 80 },
117
+  { name: '郭仁吉', value: 69 },
118
+  { name: '计沐', value: 67 },
119
+  { name: '方园', value: 62 },
120
+  { name: '符鑫日', value: 59 }
121
+])
122
+
123
+const rankData2 = reactive([
124
+  { name: '李雪琦', value: 44 },
125
+  { name: '林瑞玉', value: 42 },
126
+  { name: '吴明文', value: 40 },
127
+  { name: '吕吕俊', value: 36 },
128
+  { name: '潘任', value: 31 },
129
+  { name: '吴亚琪', value: 30 }
130
+])
131
+
132
+const rankData3 = reactive([
133
+  { name: '梁其松', value: 18 },
134
+  { name: '王名顺', value: 18 },
135
+  { name: '陈艳丽', value: 16 },
136
+  { name: '刘振华', value: 16 },
137
+  { name: '黄建成', value: 15 },
138
+  { name: '简句学', value: 15 },
139
+  { name: '郭可奇', value: 14 }
140
+])
141
+
142
+const pieChartOption = (data, colors) => ({
143
+  color: colors,
144
+  series: [{
145
+    type: 'pie',
146
+    radius: ['40%', '65%'],
147
+    center: ['50%', '55%'],
148
+    data: data,
149
+    label: { show: true, formatter: '{b}\n{c}%', fontSize: 10 }
150
+  }]
151
+})
152
+
153
+const donutChartOption = (data, colors) => ({
154
+  color: colors,
155
+  series: [{
156
+    type: 'pie',
157
+    radius: ['30%', '55%'],
158
+    center: ['50%', '55%'],
159
+    data: data,
160
+    label: { show: true, formatter: '{b}', fontSize: 10, position: 'outside' }
161
+  }]
162
+})
163
+
164
+const multiBarChartOption = (data, color) => ({
165
+  grid: { left: '15%', top: '15%', right: '5%', bottom: '15%', containLabel: true },
166
+  xAxis: { type: 'category', data: data.map(d => d.name), axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' } },
167
+  yAxis: { type: 'value', axisLine: { lineStyle: { color: '#999' } }, axisLabel: { fontSize: 10, color: '#666' }, splitLine: { lineStyle: { color: '#eee' } } },
168
+  series: [{ type: 'bar', data: data.map(d => d.value), itemStyle: { color: color }, barWidth: 20 }]
169
+})
170
+
171
+onMounted(() => {
172
+  setOption4(pieChartOption([
173
+    { name: '女', value: 52.26 },
174
+    { name: '男', value: 47.74 }
175
+  ], ['#ec4899', '#3b82f6']))
176
+
177
+  setOption5(pieChartOption([
178
+    { name: '高级', value: 49.81 },
179
+    { name: '中级', value: 50.19 }
180
+  ], ['#22c55e', '#3b82f6']))
181
+
182
+  setOption6(pieChartOption([
183
+    { name: '高级证书', value: 1.59 },
184
+    { name: '中级证书', value: 98.41 }
185
+  ], ['#22c55e', '#3b82f6']))
186
+
187
+  setOption7(donutChartOption([
188
+    { name: '中下', value: 6.35 },
189
+    { name: '中间', value: 11.85 },
190
+    { name: '右下', value: 7.74 },
191
+    { name: '右上', value: 7.56 },
192
+    { name: '左中', value: 7.26 },
193
+    { name: '左上', value: 6.61 },
194
+    { name: '左上', value: 11.16 },
195
+    { name: '右下', value: 6.45 },
196
+    { name: '右上', value: 12.42 },
197
+    { name: '中下', value: 7.41 },
198
+    { name: '右上', value: 6.11 },
199
+    { name: '左上', value: 10.24 }
200
+  ], ['#8b5cf6', '#ec4899', '#f97316', '#22c55e', '#3b82f6', '#14b8a6', '#ef4444', '#fbbf24', '#6366f1', '#06b6d4', '#84cc16', '#e11d48']))
201
+
202
+  setOption10(donutChartOption([
203
+    { name: '赵德峰', value: 12.46 },
204
+    { name: '刘佳', value: 7.94 },
205
+    { name: '郑晓华', value: 7.94 },
206
+    { name: '谢金艳', value: 7.82 },
207
+    { name: '潘仁芳', value: 7.33 },
208
+    { name: '张庆林', value: 7.2 },
209
+    { name: '陈颖', value: 7.2 },
210
+    { name: '李学芬', value: 7.2 },
211
+    { name: '李俊峰', value: 7.08 },
212
+    { name: '周雪梅', value: 7.08 },
213
+    { name: '刘旭', value: 6.46 },
214
+    { name: '李丽', value: 6.46 },
215
+    { name: '任静', value: 6.46 },
216
+    { name: '张静', value: 6.46 }
217
+  ], ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6', '#ef4444', '#fbbf24', '#6366f1', '#06b6d4', '#84cc16', '#e11d48', '#a855f7', '#f59e0b']))
218
+
219
+  setOption11(pieChartOption([
220
+    { name: '问题判断不清,未按规范检查导致漏检', value: 23.20 },
221
+    { name: '未按规范检查导致漏检', value: 7.04 },
222
+    { name: '判断不清,漏检', value: 6.48 },
223
+    { name: '未能辨别危险品', value: 5.92 },
224
+    { name: '未能辨别危险品', value: 5.92 },
225
+    { name: '未能辨别危险品', value: 2.88 },
226
+    { name: '简单漏检未能认真判断', value: 21.23 },
227
+    { name: '未能辨别危险品', value: 27.34 }
228
+  ], ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6', '#ef4444', '#fbbf24']))
229
+
230
+  setOption12(pieChartOption([
231
+    { name: '5年及5年以上', value: 30.71 },
232
+    { name: '4年人员数', value: 18.68 },
233
+    { name: '3年人员数', value: 22.47 },
234
+    { name: '2年人员数', value: 14.75 },
235
+    { name: '1年人员数', value: 6.76 },
236
+    { name: '不足1年人员数', value: 6.63 }
237
+  ], ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6']))
238
+
239
+  setOption13(multiBarChartOption([
240
+    { name: '安检一大队', value: 104 },
241
+    { name: '安检二大队', value: 39 },
242
+    { name: '安检三大队', value: 14 },
243
+    { name: '安检四大队', value: 13 },
244
+    { name: '安检五大队', value: 12 }
245
+  ], '#3b82f6'))
246
+})
247
+</script>
248
+
249
+<style lang="less" scoped>
250
+.module-brigade-content {
251
+  height: 100%;
252
+  display: flex;
253
+  flex-direction: column;
254
+  gap: 10px;
255
+  overflow-y: auto;
256
+}
257
+
258
+.chart-row {
259
+  display: flex;
260
+  gap: 10px;
261
+  flex-shrink: 0;
262
+}
263
+
264
+.chart-item {
265
+  flex: 1;
266
+  background: #fff;
267
+  border-radius: 6px;
268
+  padding: 10px;
269
+  border: 1px solid #eee;
270
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
271
+  display: flex;
272
+  flex-direction: column;
273
+  min-height: 200px;
274
+}
275
+
276
+.chart-title {
277
+  font-size: 14px;
278
+  color: #333;
279
+  margin-bottom: 5px;
280
+  text-align: left;
281
+  font-weight: 500;
282
+}
283
+
284
+.echarts {
285
+  flex: 1;
286
+  width: 100%;
287
+  min-height: 0;
288
+}
289
+
290
+.number-display {
291
+  flex: 1;
292
+  display: flex;
293
+  flex-direction: column;
294
+  align-items: center;
295
+  justify-content: center;
296
+}
297
+
298
+.number-value {
299
+  font-size: 48px;
300
+  font-weight: bold;
301
+  color: #3b82f6;
302
+}
303
+
304
+.number-unit {
305
+  font-size: 14px;
306
+  color: #666;
307
+  margin-top: 5px;
308
+}
309
+</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>

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

@@ -0,0 +1,224 @@
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="chart1" class="echarts"></div>
8
+        </div>
9
+        <div class="chart-item">
10
+          <div class="chart-title">T2航站楼放行速率</div>
11
+          <div ref="chart2" 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="chart3" class="echarts"></div>
18
+        </div>
19
+        <div class="chart-item">
20
+          <div class="chart-title">大队总平均速率对比(国际+中转)</div>
21
+          <div ref="chart4" class="echarts"></div>
22
+        </div>
23
+      </div>
24
+    </div>
25
+  </module-container>
26
+</template>
27
+
28
+<script setup>
29
+import { ref, onMounted, onBeforeUnmount } from 'vue'
30
+import * as echarts from 'echarts'
31
+import ModuleContainer from './ModuleContainer.vue'
32
+import { useEcharts } from '@/hooks/chart.js'
33
+
34
+// 接收筛选参数
35
+const props = defineProps({
36
+  filterParams: {
37
+    type: Object,
38
+    default: () => ({})
39
+  }
40
+})
41
+
42
+const chart1 = ref(null)
43
+const chart2 = ref(null)
44
+const chart3 = ref(null)
45
+const chart4 = ref(null)
46
+
47
+const { setOption: setOption1 } = useEcharts(chart1)
48
+const { setOption: setOption2 } = useEcharts(chart2)
49
+const { setOption: setOption3 } = useEcharts(chart3)
50
+const { setOption: setOption4 } = useEcharts(chart4)
51
+
52
+const generateData = () => {
53
+  const data = []
54
+  for (let i = 0; i < 30; i++) {
55
+    data.push(Math.floor(Math.random() * 60) + 120)
56
+  }
57
+  return data
58
+}
59
+
60
+const xAxisData = []
61
+for (let i = 1; i <= 30; i++) {
62
+  xAxisData.push(i + '日')
63
+}
64
+
65
+const lineChartOption = (data1, data2, color1, color2) => ({
66
+  grid: {
67
+    left: '10%',
68
+    top: '15%',
69
+    right: '5%',
70
+    bottom: '15%',
71
+    containLabel: true
72
+  },
73
+  xAxis: {
74
+    type: 'category',
75
+    data: xAxisData,
76
+    axisLine: { lineStyle: { color: '#999' } },
77
+    axisLabel: { fontSize: 10, color: '#666' }
78
+  },
79
+  yAxis: {
80
+    type: 'value',
81
+    axisLine: { lineStyle: { color: '#999' } },
82
+    axisLabel: { fontSize: 10, color: '#666' },
83
+    splitLine: { lineStyle: { color: '#eee' } }
84
+  },
85
+  legend: {
86
+    top: 0,
87
+    right: 10,
88
+    textStyle: { fontSize: 10 }
89
+  },
90
+  series: [
91
+    {
92
+      name: '高峰期时段',
93
+      type: 'line',
94
+      smooth: true,
95
+      symbol: 'circle',
96
+      symbolSize: 6,
97
+      data: data1,
98
+      itemStyle: { color: color1 },
99
+      areaStyle: {
100
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
101
+          { offset: 0, color: color1 + '40' },
102
+          { offset: 1, color: color1 + '10' }
103
+        ])
104
+      }
105
+    },
106
+    {
107
+      name: '非高峰期时段',
108
+      type: 'line',
109
+      smooth: true,
110
+      symbol: 'circle',
111
+      symbolSize: 6,
112
+      data: data2,
113
+      itemStyle: { color: color2 },
114
+      areaStyle: {
115
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
116
+          { offset: 0, color: color2 + '40' },
117
+          { offset: 1, color: color2 + '10' }
118
+        ])
119
+      }
120
+    }
121
+  ]
122
+})
123
+
124
+const barLineChartOption = (data1, data2, data3, colors) => ({
125
+  grid: {
126
+    left: '10%',
127
+    top: '15%',
128
+    right: '5%',
129
+    bottom: '15%',
130
+    containLabel: true
131
+  },
132
+  xAxis: {
133
+    type: 'category',
134
+    data: xAxisData,
135
+    axisLine: { lineStyle: { color: '#999' } },
136
+    axisLabel: { fontSize: 10, color: '#666', rotate: 45 }
137
+  },
138
+  yAxis: {
139
+    type: 'value',
140
+    axisLine: { lineStyle: { color: '#999' } },
141
+    axisLabel: { fontSize: 10, color: '#666' },
142
+    splitLine: { lineStyle: { color: '#eee' } }
143
+  },
144
+  legend: {
145
+    top: 0,
146
+    right: 10,
147
+    textStyle: { fontSize: 10 }
148
+  },
149
+  series: [
150
+    {
151
+      name: '安检三大队',
152
+      type: 'bar',
153
+      data: data1,
154
+      itemStyle: { color: colors[0] },
155
+      barWidth: 8
156
+    },
157
+    {
158
+      name: '安检二大队',
159
+      type: 'line',
160
+      smooth: true,
161
+      symbol: 'circle',
162
+      symbolSize: 6,
163
+      data: data2,
164
+      itemStyle: { color: colors[1] }
165
+    },
166
+    {
167
+      name: '安检一大队',
168
+      type: 'line',
169
+      smooth: true,
170
+      symbol: 'circle',
171
+      symbolSize: 6,
172
+      data: data3,
173
+      itemStyle: { color: colors[2] }
174
+    }
175
+  ]
176
+})
177
+
178
+onMounted(() => {
179
+  setOption1(lineChartOption(generateData(), generateData(), '#3b82f6', '#22c55e'))
180
+  setOption2(lineChartOption(generateData(), generateData(), '#2563eb', '#16a34a'))
181
+  setOption3(barLineChartOption(generateData(), generateData(), generateData(), ['#3b82f6', '#22c55e', '#f97316']))
182
+  setOption4(barLineChartOption(generateData(), generateData(), generateData(), ['#3b82f6', '#22c55e', '#f97316']))
183
+})
184
+</script>
185
+
186
+<style lang="less" scoped>
187
+.module-four-content {
188
+  height: 100%;
189
+  display: flex;
190
+  flex-direction: column;
191
+  gap: 10px;
192
+}
193
+
194
+.chart-row {
195
+  flex: 1;
196
+  display: flex;
197
+  gap: 10px;
198
+}
199
+
200
+.chart-item {
201
+  flex: 1;
202
+  display: flex;
203
+  flex-direction: column;
204
+  background: #fff;
205
+  border-radius: 6px;
206
+  padding: 8px;
207
+
208
+  border: 1px solid #eee;
209
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
210
+}
211
+
212
+.chart-title {
213
+  font-size: 17px;
214
+  color: black;
215
+  margin-bottom: 5px;
216
+  text-align: left;
217
+}
218
+
219
+.echarts {
220
+  flex: 1;
221
+  width: 100%;
222
+  min-height: 0;
223
+}
224
+</style>

+ 575 - 0
src/views/blockingData/blockingDataScreen/components/ModuleOne.vue

@@ -0,0 +1,575 @@
1
+<template>
2
+  <module-container title="模块一 运行数据分析">
3
+    <div class="module-one-content">
4
+      <!-- 新增:统计卡片行 -->
5
+      <div class="stats-row">
6
+        <div class="stat-card">
7
+          <div class="stat-card-inner">
8
+            <div class="stat-left">
9
+              <div class="stat-label">查堵总数(所有大队)</div>
10
+              <div class="stat-value">2327</div>
11
+            </div>
12
+            <div class="stat-right">
13
+              <div class="stat-title">查堵总数</div>
14
+              <div ref="chart0a" class="echarts-small"></div>
15
+            </div>
16
+          </div>
17
+        </div>
18
+        <div class="stat-card">
19
+          <div class="stat-card-inner">
20
+            <div class="stat-left">
21
+              <div class="stat-label">总查堵万分率</div>
22
+              <div class="stat-value">2.22</div>
23
+            </div>
24
+            <div class="stat-right">
25
+              <div class="stat-title">总查堵万分率</div>
26
+              <div ref="chart0b" class="echarts-small"></div>
27
+            </div>
28
+          </div>
29
+        </div>
30
+      </div>
31
+      
32
+      <!-- 第一行:每日查堵数量 -->
33
+      <div class="chart-row">
34
+        <div class="chart-item">
35
+          <div class="chart-title">每日查堵数量(总表)</div>
36
+          <div ref="chart1" class="echarts"></div>
37
+        </div>
38
+        <div class="chart-item">
39
+          <div class="chart-title">每日查堵数量(大队对比表)</div>
40
+          <div ref="chart2" class="echarts"></div>
41
+        </div>
42
+      </div>
43
+      
44
+      <!-- 第二行:每日查堵万分率 -->
45
+      <div class="chart-row">
46
+        <div class="chart-item">
47
+          <div class="chart-title">每日查堵万分率</div>
48
+          <div ref="chart3" class="echarts"></div>
49
+        </div>
50
+        <div class="chart-item">
51
+          <div class="chart-title">每日查堵万分率(大队对比表)</div>
52
+          <div ref="chart4" class="echarts"></div>
53
+        </div>
54
+      </div>
55
+      
56
+      <!-- 第三行:每日过检图像数 -->
57
+      <div class="chart-row">
58
+        <div class="chart-item">
59
+          <div class="chart-title">每日过检图像数(行检+旅检总表)</div>
60
+          <div ref="chart5" class="echarts"></div>
61
+        </div>
62
+        <div class="chart-item">
63
+          <div class="chart-title">每日过检图像数(行检+旅检大队对比表)</div>
64
+          <div ref="chart6" class="echarts"></div>
65
+        </div>
66
+      </div>
67
+      
68
+      <!-- 第四行:查堵物品分布和区域查堵数量分布 -->
69
+      <div class="chart-row">
70
+        <div class="chart-item">
71
+          <div class="chart-title">查堵物品分布</div>
72
+          <div ref="chart7" class="echarts"></div>
73
+        </div>
74
+        <div class="chart-item">
75
+          <div class="chart-title">区域查堵数量分布</div>
76
+          <div ref="chart8" class="echarts"></div>
77
+        </div>
78
+      </div>
79
+      
80
+      <!-- 第五行:查堵类型分布 -->
81
+      <div class="chart-row full-width">
82
+        <div class="chart-item">
83
+          <div class="chart-title">查堵类型分布</div>
84
+          <div ref="chart9" class="echarts"></div>
85
+        </div>
86
+      </div>
87
+      
88
+      <!-- 第六行:查堵时间段过检行李数及万分率 -->
89
+      <div class="chart-row full-width">
90
+        <div class="chart-item">
91
+          <div class="chart-title">查堵时间段过检行李数及万分率(总表)</div>
92
+          <div ref="chart10" class="echarts"></div>
93
+        </div>
94
+      </div>
95
+      
96
+      <!-- 第七行:每日过检行李数及万分率 -->
97
+      <div class="chart-row full-width">
98
+        <div class="chart-item">
99
+          <div class="chart-title">每日过检行李数及万分率</div>
100
+          <div ref="chart11" class="echarts"></div>
101
+        </div>
102
+      </div>
103
+      
104
+      <!-- 第八行:查堵-AI复查图像总数 -->
105
+      <div class="chart-row full-width">
106
+        <div class="chart-item">
107
+          <div class="chart-title">查堵-AI复查图像总数</div>
108
+          <div ref="chart12" class="echarts"></div>
109
+        </div>
110
+      </div>
111
+      
112
+      <!-- 第九行:AI漏判和误判图像总数 -->
113
+      <div class="chart-row">
114
+        <div class="chart-item">
115
+          <div class="chart-title">查堵-AI漏判图像总数</div>
116
+          <div ref="chart13" class="echarts"></div>
117
+        </div>
118
+        <div class="chart-item">
119
+          <div class="chart-title">查堵-AI误判图像总数</div>
120
+          <div ref="chart14" class="echarts"></div>
121
+        </div>
122
+      </div>
123
+    </div>
124
+  </module-container>
125
+</template>
126
+
127
+<script setup>
128
+import { ref, onMounted } from 'vue'
129
+import * as echarts from 'echarts'
130
+import ModuleContainer from './ModuleContainer.vue'
131
+import { useEcharts } from '@/hooks/chart.js'
132
+
133
+// 接收筛选参数
134
+const props = defineProps({
135
+  filterParams: {
136
+    type: Object,
137
+    default: () => ({})
138
+  }
139
+})
140
+
141
+// 图表引用
142
+const chart0a = ref(null)
143
+const chart0b = ref(null)
144
+const chart1 = ref(null)
145
+const chart2 = ref(null)
146
+const chart3 = ref(null)
147
+const chart4 = ref(null)
148
+const chart5 = ref(null)
149
+const chart6 = ref(null)
150
+const chart7 = ref(null)
151
+const chart8 = ref(null)
152
+const chart9 = ref(null)
153
+const chart10 = ref(null)
154
+const chart11 = ref(null)
155
+const chart12 = ref(null)
156
+const chart13 = ref(null)
157
+const chart14 = ref(null)
158
+
159
+// 图表设置函数
160
+const { setOption: setOption0a } = useEcharts(chart0a)
161
+const { setOption: setOption0b } = useEcharts(chart0b)
162
+const { setOption: setOption1 } = useEcharts(chart1)
163
+const { setOption: setOption2 } = useEcharts(chart2)
164
+const { setOption: setOption3 } = useEcharts(chart3)
165
+const { setOption: setOption4 } = useEcharts(chart4)
166
+const { setOption: setOption5 } = useEcharts(chart5)
167
+const { setOption: setOption6 } = useEcharts(chart6)
168
+const { setOption: setOption7 } = useEcharts(chart7)
169
+const { setOption: setOption8 } = useEcharts(chart8)
170
+const { setOption: setOption9 } = useEcharts(chart9)
171
+const { setOption: setOption10 } = useEcharts(chart10)
172
+const { setOption: setOption11 } = useEcharts(chart11)
173
+const { setOption: setOption12 } = useEcharts(chart12)
174
+const { setOption: setOption13 } = useEcharts(chart13)
175
+const { setOption: setOption14 } = useEcharts(chart14)
176
+
177
+// 生成模拟数据
178
+const generateData = (count, min, max) => {
179
+  const data = []
180
+  for (let i = 0; i < count; i++) {
181
+    data.push(Math.floor(Math.random() * (max - min + 1)) + min)
182
+  }
183
+  return data
184
+}
185
+
186
+// X轴数据(30天)
187
+const xAxisData = []
188
+for (let i = 1; i <= 30; i++) {
189
+  xAxisData.push(i + '日')
190
+}
191
+
192
+// 小型柱状图配置(用于统计卡片)
193
+const smallBarOption = (data, color, isHorizontal = false) => ({
194
+  grid: {
195
+    left: '5%',
196
+    top: '5%',
197
+    right: '5%',
198
+    bottom: '5%',
199
+    containLabel: true
200
+  },
201
+  xAxis: {
202
+    type: isHorizontal ? 'value' : 'category',
203
+    data: isHorizontal ? null : ['1', '2', '3'],
204
+    show: false
205
+  },
206
+  yAxis: {
207
+    type: isHorizontal ? 'category' : 'value',
208
+    data: isHorizontal ? ['1', '2', '3'] : null,
209
+    show: false
210
+  },
211
+  series: [{
212
+    type: 'bar',
213
+    data: data,
214
+    itemStyle: { color: color },
215
+    barWidth: isHorizontal ? 8 : 12
216
+  }]
217
+})
218
+
219
+// 折线图配置(总表)
220
+const lineChartOption = (data, color, title) => ({
221
+  grid: {
222
+    left: '10%',
223
+    top: '15%',
224
+    right: '5%',
225
+    bottom: '15%',
226
+    containLabel: true
227
+  },
228
+  xAxis: {
229
+    type: 'category',
230
+    data: xAxisData,
231
+    axisLine: { lineStyle: { color: '#999' } },
232
+    axisLabel: { fontSize: 10, color: '#666' }
233
+  },
234
+  yAxis: {
235
+    type: 'value',
236
+    axisLine: { lineStyle: { color: '#999' } },
237
+    axisLabel: { fontSize: 10, color: '#666' },
238
+    splitLine: { lineStyle: { color: '#eee' } }
239
+  },
240
+  series: [{
241
+    name: title,
242
+    type: 'line',
243
+    smooth: true,
244
+    symbol: 'circle',
245
+    symbolSize: 6,
246
+    data: data,
247
+    itemStyle: { color: color },
248
+    lineStyle: { color: color }
249
+  }]
250
+})
251
+
252
+// 多折线图配置(大队对比)
253
+const multiLineChartOption = (dataList, colors, legends) => ({
254
+  grid: {
255
+    left: '10%',
256
+    top: '15%',
257
+    right: '5%',
258
+    bottom: '15%',
259
+    containLabel: true
260
+  },
261
+  xAxis: {
262
+    type: 'category',
263
+    data: xAxisData,
264
+    axisLine: { lineStyle: { color: '#999' } },
265
+    axisLabel: { fontSize: 10, color: '#666' }
266
+  },
267
+  yAxis: {
268
+    type: 'value',
269
+    axisLine: { lineStyle: { color: '#999' } },
270
+    axisLabel: { fontSize: 10, color: '#666' },
271
+    splitLine: { lineStyle: { color: '#eee' } }
272
+  },
273
+  legend: {
274
+    top: 0,
275
+    right: 10,
276
+    textStyle: { fontSize: 10 }
277
+  },
278
+  series: dataList.map((data, index) => ({
279
+    name: legends[index],
280
+    type: 'line',
281
+    smooth: true,
282
+    symbol: 'circle',
283
+    symbolSize: 6,
284
+    data: data,
285
+    itemStyle: { color: colors[index] },
286
+    lineStyle: { color: colors[index] }
287
+  }))
288
+})
289
+
290
+// 环形饼图配置
291
+const ringPieOption = (data, colors) => ({
292
+  color: colors,
293
+  series: [{
294
+    type: 'pie',
295
+    radius: ['45%', '70%'],
296
+    center: ['50%', '55%'],
297
+    data: data,
298
+    label: {
299
+      show: true,
300
+      formatter: '{b}\n{c}%',
301
+      fontSize: 10
302
+    },
303
+    labelLine: {
304
+      show: true,
305
+      length: 15,
306
+      length2: 10
307
+    }
308
+  }]
309
+})
310
+
311
+// 柱状图配置
312
+const barChartOption = (data, color, isHorizontal = false) => ({
313
+  grid: {
314
+    left: '15%',
315
+    top: '15%',
316
+    right: '5%',
317
+    bottom: '15%',
318
+    containLabel: true
319
+  },
320
+  xAxis: {
321
+    type: isHorizontal ? 'value' : 'category',
322
+    data: isHorizontal ? null : data.map(d => d.name),
323
+    axisLine: { lineStyle: { color: '#999' } },
324
+    axisLabel: { fontSize: 10, color: '#666' }
325
+  },
326
+  yAxis: {
327
+    type: isHorizontal ? 'category' : 'value',
328
+    data: isHorizontal ? data.map(d => d.name) : null,
329
+    axisLine: { lineStyle: { color: '#999' } },
330
+    axisLabel: { fontSize: 10, color: '#666' },
331
+    splitLine: { lineStyle: { color: '#eee' } }
332
+  },
333
+  series: [{
334
+    type: 'bar',
335
+    data: isHorizontal ? data.map(d => d.value) : data,
336
+    itemStyle: { color: color },
337
+    barWidth: isHorizontal ? 15 : 20
338
+  }]
339
+})
340
+
341
+// 双Y轴柱状图配置(用于行李数和万分率)
342
+const dualAxisBarChartOption = (data1, data2, color1, color2) => ({
343
+  grid: {
344
+    left: '10%',
345
+    top: '15%',
346
+    right: '10%',
347
+    bottom: '15%',
348
+    containLabel: true
349
+  },
350
+  xAxis: {
351
+    type: 'category',
352
+    data: xAxisData,
353
+    axisLine: { lineStyle: { color: '#999' } },
354
+    axisLabel: { fontSize: 10, color: '#666' }
355
+  },
356
+  yAxis: [
357
+    {
358
+      type: 'value',
359
+      name: '行李数',
360
+      axisLine: { lineStyle: { color: color1 } },
361
+      axisLabel: { fontSize: 10, color: color1 }
362
+    },
363
+    {
364
+      type: 'value',
365
+      name: '万分率',
366
+      axisLine: { lineStyle: { color: color2 } },
367
+      axisLabel: { fontSize: 10, color: color2 }
368
+    }
369
+  ],
370
+  series: [
371
+    {
372
+      name: '行李数',
373
+      type: 'bar',
374
+      data: data1,
375
+      itemStyle: { color: color1 },
376
+      barWidth: 15
377
+    },
378
+    {
379
+      name: '万分率',
380
+      type: 'line',
381
+      yAxisIndex: 1,
382
+      smooth: true,
383
+      data: data2,
384
+      itemStyle: { color: color2 },
385
+      lineStyle: { color: color2 }
386
+    }
387
+  ]
388
+})
389
+
390
+onMounted(() => {
391
+  // 统计卡片图表
392
+  setOption0a(smallBarOption([120, 80, 150], '#3b82f6'))
393
+  setOption0b(smallBarOption([2.2, 1.8, 2.5], '#3b82f6', true))
394
+  
395
+  // 第一行:每日查堵数量
396
+  setOption1(lineChartOption(generateData(30, 50, 150), '#3b82f6', '查堵数量'))
397
+  setOption2(multiLineChartOption(
398
+    [generateData(30, 20, 80), generateData(30, 15, 70), generateData(30, 10, 60)],
399
+    ['#3b82f6', '#22c55e', '#f97316'],
400
+    ['安检一大队', '安检二大队', '安检三大队']
401
+  ))
402
+  
403
+  // 第二行:每日查堵万分率
404
+  setOption3(lineChartOption(generateData(30, 0.5, 3.5), '#ec4899', '万分率'))
405
+  setOption4(multiLineChartOption(
406
+    [generateData(30, 0.3, 2.5), generateData(30, 0.2, 2.0), generateData(30, 0.1, 1.8)],
407
+    ['#3b82f6', '#22c55e', '#f97316'],
408
+    ['安检一大队', '安检二大队', '安检三大队']
409
+  ))
410
+  
411
+  // 第三行:每日过检图像数
412
+  setOption5(lineChartOption(generateData(30, 1000, 5000), '#8b5cf6', '过检图像数'))
413
+  setOption6(multiLineChartOption(
414
+    [generateData(30, 400, 2000), generateData(30, 300, 1800), generateData(30, 200, 1600)],
415
+    ['#3b82f6', '#22c55e', '#f97316'],
416
+    ['安检一大队', '安检二大队', '安检三大队']
417
+  ))
418
+  
419
+  // 第四行:查堵物品分布和区域查堵数量分布
420
+  setOption7(ringPieOption([
421
+    { name: '刀具', value: 25 },
422
+    { name: '液体', value: 20 },
423
+    { name: '电子产品', value: 18 },
424
+    { name: '其他', value: 15 },
425
+    { name: '打火机', value: 12 },
426
+    { name: '化妆品', value: 10 }
427
+  ], ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6']))
428
+  
429
+  setOption8(barChartOption([
430
+    { name: 'T1国内', value: 120 },
431
+    { name: 'T1国际', value: 80 },
432
+    { name: 'T2国内', value: 150 },
433
+    { name: 'T2国际', value: 60 },
434
+    { name: '中转区', value: 40 }
435
+  ], '#3b82f6', true))
436
+  
437
+  // 第五行:查堵类型分布
438
+  setOption9(barChartOption(generateData(6, 20, 100), '#3b82f6'))
439
+  
440
+  // 第六行:查堵时间段过检行李数及万分率
441
+  setOption10(dualAxisBarChartOption(
442
+    generateData(24, 100, 500), // 行李数
443
+    generateData(24, 0.5, 3.0), // 万分率
444
+    '#3b82f6', '#ec4899'
445
+  ))
446
+  
447
+  // 第七行:每日过检行李数及万分率
448
+  setOption11(dualAxisBarChartOption(
449
+    generateData(30, 1000, 5000), // 行李数
450
+    generateData(30, 0.5, 3.5),  // 万分率
451
+    '#3b82f6', '#ec4899'
452
+  ))
453
+  
454
+  // 第八行:查堵-AI复查图像总数
455
+  setOption12(lineChartOption(generateData(30, 50, 200), '#14b8a6', 'AI复查图像数'))
456
+  
457
+  // 第九行:AI漏判和误判图像总数
458
+  setOption13(lineChartOption(generateData(30, 5, 30), '#ef4444', 'AI漏判图像数'))
459
+  setOption14(lineChartOption(generateData(30, 3, 25), '#f97316', 'AI误判图像数'))
460
+})
461
+</script>
462
+
463
+<style lang="less" scoped>
464
+.module-one-content {
465
+  height: 100%;
466
+  display: flex;
467
+  flex-direction: column;
468
+  gap: 10px;
469
+  overflow-y: auto;
470
+  
471
+}
472
+
473
+.stats-row {
474
+  display: flex;
475
+  gap: 10px;
476
+  flex-shrink: 0;
477
+}
478
+
479
+.stat-card {
480
+  flex: 1;
481
+  background: #fff;
482
+  border-radius: 6px;
483
+  padding: 15px;
484
+  border: 1px solid #eee;
485
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
486
+  min-height: 120px;
487
+}
488
+
489
+.stat-card-inner {
490
+  display: flex;
491
+  height: 100%;
492
+  align-items: center;
493
+}
494
+
495
+.stat-left {
496
+  flex: 1;
497
+  display: flex;
498
+  flex-direction: column;
499
+  align-items: flex-start;
500
+  padding-right: 15px;
501
+}
502
+
503
+.stat-label {
504
+  font-size: 12px;
505
+  color: #666;
506
+  margin-bottom: 8px;
507
+}
508
+
509
+.stat-value {
510
+  font-size: 24px;
511
+  font-weight: bold;
512
+  color: #3b82f6;
513
+}
514
+
515
+.stat-right {
516
+  flex: 1;
517
+  display: flex;
518
+  flex-direction: column;
519
+  align-items: flex-end;
520
+  height: 100%;
521
+}
522
+
523
+.stat-title {
524
+  font-size: 12px;
525
+  color: #333;
526
+  margin-bottom: 5px;
527
+  font-weight: 500;
528
+}
529
+
530
+.echarts-small {
531
+  width: 100%;
532
+  height: 60px;
533
+  min-height: 60px;
534
+}
535
+
536
+.chart-row {
537
+  display: flex;
538
+  gap: 10px;
539
+  flex-shrink: 0;
540
+}
541
+
542
+.chart-row.full-width {
543
+  flex-direction: column;
544
+}
545
+
546
+.chart-item {
547
+  flex: 1;
548
+  background: #fff;
549
+  border-radius: 6px;
550
+  padding: 10px;
551
+  border: 1px solid #eee;
552
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
553
+  display: flex;
554
+  flex-direction: column;
555
+  min-height: 200px;
556
+}
557
+
558
+.chart-row.full-width .chart-item {
559
+  flex: none;
560
+  height: 200px;
561
+}
562
+
563
+.chart-title {
564
+  font-size: 17px;
565
+  color: black;
566
+  margin-bottom: 5px;
567
+  text-align: left;
568
+}
569
+
570
+.echarts {
571
+  flex: 1;
572
+  width: 100%;
573
+  min-height: 0;
574
+}
575
+</style>

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

@@ -0,0 +1,105 @@
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 } from 'vue'
18
+import * as echarts from 'echarts'
19
+import ModuleContainer from './ModuleContainer.vue'
20
+import { useEcharts } from '@/hooks/chart.js'
21
+
22
+// 接收筛选参数
23
+const props = defineProps({
24
+  filterParams: {
25
+    type: Object,
26
+    default: () => ({})
27
+  }
28
+})
29
+
30
+const chart1 = ref(null)
31
+const chart2 = ref(null)
32
+
33
+const { setOption: setOption1 } = useEcharts(chart1)
34
+const { setOption: setOption2 } = useEcharts(chart2)
35
+
36
+const ringPieOption = (data, colors, centerOffset = ['50%', '55%']) => ({
37
+  color: colors,
38
+  series: [{
39
+    type: 'pie',
40
+    radius: ['45%', '70%'],
41
+    center: centerOffset,
42
+    data: data,
43
+    label: {
44
+      show: true,
45
+      formatter: '{b}\n{c}%',
46
+      fontSize: 11
47
+    },
48
+    labelLine: {
49
+      show: true,
50
+      length: 15,
51
+      length2: 10
52
+    }
53
+  }]
54
+})
55
+
56
+onMounted(() => {
57
+  setOption1(ringPieOption([
58
+    { name: '4次', value: 26 },
59
+    { name: '3次', value: 9 },
60
+    { name: '2次', value: 8 },
61
+    { name: '空白', value: 57 }
62
+  ], ['#3b82f6', '#22c55e', '#f97316', '#cbd5e1']))
63
+  
64
+  setOption2(ringPieOption([
65
+    { name: '本月未开展', value: 12 },
66
+    { name: '不合格', value: 8 },
67
+    { name: '合格', value: 13 },
68
+    { name: '优秀', value: 6 },
69
+    { name: '空白', value: 61 }
70
+  ], ['#64748b', '#ef4444', '#22c55e', '#3b82f6', '#cbd5e1']))
71
+})
72
+</script>
73
+
74
+<style lang="less" scoped>
75
+.module-three-content {
76
+  height: 100%;
77
+  display: flex;
78
+  gap: 10px;
79
+  
80
+}
81
+
82
+.chart-card {
83
+  flex: 1;
84
+  background: #fff;
85
+  border-radius: 6px;
86
+  padding: 10px;
87
+  border: 1px solid #eee;
88
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
89
+  display: flex;
90
+  flex-direction: column;
91
+}
92
+
93
+.chart-title {
94
+ font-size: 17px;
95
+  color: black;
96
+  margin-bottom: 5px;
97
+  text-align: left;
98
+}
99
+
100
+.echarts {
101
+  flex: 1;
102
+  width: 100%;
103
+  min-height: 0;
104
+}
105
+</style>

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

@@ -0,0 +1,329 @@
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 } 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
+
66
+// 接收筛选参数
67
+const props = defineProps({
68
+  filterParams: {
69
+    type: Object,
70
+    default: () => ({})
71
+  }
72
+})
73
+
74
+const chart1 = ref(null)
75
+const chart2 = ref(null)
76
+const chart3 = ref(null)
77
+const chart4 = ref(null)
78
+const chart5 = ref(null)
79
+const chart6 = ref(null)
80
+const chart7 = ref(null)
81
+
82
+const { setOption: setOption1 } = useEcharts(chart1)
83
+const { setOption: setOption2 } = useEcharts(chart2)
84
+const { setOption: setOption3 } = useEcharts(chart3)
85
+const { setOption: setOption4 } = useEcharts(chart4)
86
+const { setOption: setOption5 } = useEcharts(chart5)
87
+const { setOption: setOption6 } = useEcharts(chart6)
88
+const { setOption: setOption7 } = useEcharts(chart7)
89
+
90
+const rankData1 = reactive([
91
+  { name: '李主管', value: 110 },
92
+  { name: '李海峰', value: 102 },
93
+  { name: '李华波', value: 94 },
94
+  { name: '方星星', value: 80 },
95
+  { name: '郭仁昌', value: 69 },
96
+  { name: '陈晓明', value: 67 },
97
+  { name: '万国', value: 62 },
98
+  { name: '杨高星', value: 60 },
99
+  { name: '符昌国', value: 59 },
100
+  { name: '李星国', value: 57 },
101
+  { name: '周良志', value: 56 },
102
+  { name: '杨旭', value: 53 }
103
+])
104
+
105
+const rankData2 = reactive([
106
+  { name: '李智辉', value: 44 },
107
+  { name: '林明玉', value: 42 },
108
+  { name: '吴帮文', value: 40 },
109
+  { name: '杨玉艳', value: 37 },
110
+  { name: '符玉玲', value: 37 },
111
+  { name: '黄登', value: 36 },
112
+  { name: '梁品俊', value: 35 },
113
+  { name: '陈勤丽', value: 35 },
114
+  { name: '潘政生', value: 34 },
115
+  { name: '周民伦', value: 34 },
116
+  { name: '王明星', value: 33 },
117
+  { name: '林文海', value: 33 }
118
+])
119
+
120
+const rankData3 = reactive([
121
+  { name: '梁翠兰', value: 21 },
122
+  { name: '陈秀强', value: 20 },
123
+  { name: '李国', value: 19 },
124
+  { name: '梁其佐', value: 18 },
125
+  { name: '王名标', value: 18 },
126
+  { name: '李书帆', value: 17 },
127
+  { name: '李亚峰', value: 17 },
128
+  { name: '杨燕钰', value: 16 },
129
+  { name: '刘燕萍', value: 16 },
130
+  { name: '庄伟', value: 16 },
131
+  { name: '黄富伦', value: 15 },
132
+  { name: '唐甸宇', value: 15 }
133
+])
134
+
135
+const pieOption = (data, colors, isRing = false) => ({
136
+  color: colors,
137
+  series: [{
138
+    type: 'pie',
139
+    radius: isRing ? ['50%', '70%'] : '60%',
140
+    center: ['50%', '55%'],
141
+    data: data,
142
+    label: {
143
+      show: true,
144
+      formatter: '{b}\n{c}%',
145
+      fontSize: 10
146
+    }
147
+  }]
148
+})
149
+
150
+const barOption = (data, colors) => ({
151
+  grid: { left: '15%', top: '10%', right: '5%', bottom: '10%', containLabel: true },
152
+  xAxis: { type: 'value', axisLabel: { fontSize: 9 } },
153
+  yAxis: { type: 'category', data: data.map(d => d.name), axisLabel: { fontSize: 9 } },
154
+  series: [{
155
+    type: 'bar',
156
+    data: data.map(d => d.value),
157
+    itemStyle: {
158
+      color: (params) => colors[params.dataIndex % colors.length]
159
+    },
160
+    barWidth: 15
161
+  }]
162
+})
163
+
164
+const stackBarOption = (data, colors) => ({
165
+  grid: { left: '15%', top: '10%', right: '5%', bottom: '15%', containLabel: true },
166
+  xAxis: { type: 'category', data: ['安检二大队', '安检三大队', '安检一大队'], axisLabel: { fontSize: 9 } },
167
+  yAxis: { type: 'value', axisLabel: { fontSize: 9 } },
168
+  legend: { top: 0, right: 10, textStyle: { fontSize: 9 } },
169
+  series: data.map((d, i) => ({
170
+    name: d.name,
171
+    type: 'bar',
172
+    stack: 'total',
173
+    data: d.data,
174
+    itemStyle: { color: colors[i] },
175
+    barWidth: 25
176
+  }))
177
+})
178
+
179
+onMounted(() => {
180
+  setOption1(pieOption([
181
+    { name: '包上', value: 10 },
182
+    { name: '包中', value: 15 },
183
+    { name: '包下', value: 12 },
184
+    { name: '左上', value: 18 },
185
+    { name: '左中', value: 20 },
186
+    { name: '左下', value: 15 },
187
+    { name: '右下', value: 10 }
188
+  ], ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6', '#64748b']))
189
+  setOption2(pieOption([
190
+    { name: '未注意到部位以及未仔细判图', value: 25 },
191
+    { name: '图像特征难以发现', value: 20 },
192
+    { name: '判图难度较大', value: 18 },
193
+    { name: '图像中判清物品', value: 15 },
194
+    { name: '判图不清,未能有效识别物品', value: 12 },
195
+    { name: '其他', value: 10 }
196
+  ], ['#ef4444', '#f97316', '#eab308', '#22c55e', '#3b82f6', '#64748b'], true))
197
+  setOption3(barOption([
198
+    { name: '6年及以上', value: 77 },
199
+    { name: '4-6年', value: 25 },
200
+    { name: '3年', value: 18 },
201
+    { name: '2年', value: 30 },
202
+    { name: '1年', value: 69 },
203
+    { name: '1年以下', value: 12 }
204
+  ], ['#3b82f6', '#60a5fa', '#93c5fd', '#bfdbfe', '#dbeafe', '#eff6ff']))
205
+  setOption4(pieOption([
206
+    { name: '男', value: 63 },
207
+    { name: '女', value: 37 }
208
+  ], ['#3b82f6', '#ec4899']))
209
+  setOption5(pieOption([
210
+    { name: '简单', value: 52 },
211
+    { name: '难', value: 30 },
212
+    { name: '未评判', value: 12 },
213
+    { name: '空白', value: 6 }
214
+  ], ['#93c5fd', '#3b82f6', '#64748b', '#e2e8f0']))
215
+  setOption6(stackBarOption([
216
+    { name: '6年及以上人数', data: [30, 25, 22] },
217
+    { name: '4-6年人数', data: [15, 20, 18] },
218
+    { name: '3年人数', data: [10, 12, 15] },
219
+    { name: '2年人数', data: [12, 15, 13] },
220
+    { name: '1年人数', data: [25, 30, 28] },
221
+    { name: '不足1年人数', data: [5, 8, 10] }
222
+  ], ['#3b82f6', '#22c55e', '#f97316', '#ec4899', '#8b5cf6', '#14b8a6']))
223
+  setOption7(stackBarOption([
224
+    { name: '安检一大队', data: [135, 120] },
225
+    { name: '安检二大队', data: [90, 110] },
226
+    { name: '安检三大队', data: [70, 130] }
227
+  ], ['#3b82f6', '#22c55e', '#f97316']))
228
+})
229
+</script>
230
+
231
+<style lang="less" scoped>
232
+.module-two-content {
233
+  height: 100%;
234
+  display: flex;
235
+  flex-direction: column;
236
+  gap: 8px;
237
+  overflow-y: auto;
238
+}
239
+
240
+.top-row,
241
+.middle-row,
242
+.bottom-row,
243
+.extra-row {
244
+  display: flex;
245
+  gap: 8px;
246
+  flex-shrink: 0;
247
+}
248
+
249
+.table-card {
250
+  flex: 1;
251
+  background: #fff;
252
+  border-radius: 6px;
253
+  padding: 8px;
254
+  border: 1px solid #eee;
255
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
256
+  display: flex;
257
+  flex-direction: column;
258
+  min-height: 200px;
259
+}
260
+
261
+.chart-card {
262
+  flex: 1;
263
+  background: #fff;
264
+  border-radius: 6px;
265
+  padding: 8px;
266
+  border: 1px solid #eee;
267
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
268
+  display: flex;
269
+  flex-direction: column;
270
+  min-height: 120px;
271
+}
272
+
273
+.chart-title {
274
+  font-size: 17px;
275
+  color: black;
276
+  margin-bottom: 5px;
277
+  text-align: left;
278
+}
279
+
280
+.rank-table {
281
+  flex: 1;
282
+  overflow-y: auto;
283
+}
284
+
285
+.rank-row {
286
+  display: flex;
287
+  align-items: center;
288
+  padding: 4px 6px;
289
+  border-bottom: 1px solid #f0f0f0;
290
+  font-size: 11px;
291
+}
292
+
293
+.rank-row:last-child {
294
+  border-bottom: none;
295
+}
296
+
297
+.rank-number {
298
+  width: 45px;
299
+  font-weight: bold;
300
+  margin-right: 8px;
301
+}
302
+
303
+.rank-1 {
304
+  color: #eab308;
305
+}
306
+
307
+.rank-2 {
308
+  color: #94a3b8;
309
+}
310
+
311
+.rank-3 {
312
+  color: #f97316;
313
+}
314
+
315
+.rank-name {
316
+  flex: 1;
317
+  color: #333;
318
+}
319
+
320
+.rank-value {
321
+  color: #666;
322
+}
323
+
324
+.echarts {
325
+  flex: 1;
326
+  width: 100%;
327
+  min-height: 100px;
328
+}
329
+</style>

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

@@ -0,0 +1,217 @@
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 rankData" :key="index" class="rank-item" :class="'rank-item-' + getRankClass(index)">
10
+        <span class="rank-number" :class="'rank-number-' + getRankClass(index)">NO.{{ index + 1 }}</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(index)" :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
+const getRankClass = (index) => {
55
+  if (index === 0) return 'first'
56
+  if (index === 1) return 'second'
57
+  if (index === 2) return 'third'
58
+  return 'default'
59
+}
60
+
61
+const getProgressWidth = (value, max) => {
62
+  return (value / max) * 100
63
+}
64
+</script>
65
+
66
+<style lang="less" scoped>
67
+.rank-list-container {
68
+  width: 100%;
69
+  height: 100%;
70
+  background: #fff;
71
+  border-radius: 8px;
72
+  border: 1px solid #e5e7eb;
73
+  // padding: 16px;
74
+  display: flex;
75
+  flex-direction: column;
76
+}
77
+
78
+.rank-list-header {
79
+  display: flex;
80
+  align-items: center;
81
+  padding: 8px;
82
+  border-bottom: 1px solid #e5e7eb;
83
+  margin-bottom: 8px;
84
+}
85
+
86
+.header-label {
87
+  width: 80px;
88
+  font-size: 16px;
89
+  font-weight: 600;
90
+  color: #374151;
91
+}
92
+
93
+.header-name {
94
+  flex: 1;
95
+  font-size: 16px;
96
+  font-weight: 600;
97
+  color: #374151;
98
+}
99
+
100
+.header-count {
101
+  width: 80px;
102
+  text-align: right;
103
+  font-size: 16px;
104
+  font-weight: 600;
105
+  color: #374151;
106
+}
107
+
108
+.rank-list-body {
109
+  flex: 1;
110
+  overflow-y: auto;
111
+}
112
+
113
+.rank-item {
114
+  display: flex;
115
+  align-items: center;
116
+  padding: 10px;
117
+
118
+  // transition: background-color 0.2s;
119
+}
120
+
121
+.rank-item-first {
122
+  background-color: #fef9e7;
123
+}
124
+
125
+.rank-item-second {
126
+  background-color: #f3f4f6;
127
+}
128
+
129
+.rank-item-third {
130
+  background-color: #fef2f2;
131
+}
132
+
133
+.rank-item:hover {
134
+  background-color: #f9fafb;
135
+}
136
+
137
+.rank-number {
138
+  //width: 80px;
139
+  font-weight: bold;
140
+  font-size: 15px;
141
+  padding: 4px 8px;
142
+  border-radius: 4px;
143
+}
144
+
145
+.rank-number-first {
146
+  background: linear-gradient(135deg, #EFB63D, #F8CA4D);
147
+  color: #fff;
148
+}
149
+
150
+.rank-number-second {
151
+  background: linear-gradient(135deg, #C4CAE1, #E0E4ED);
152
+  color: #fff;
153
+}
154
+
155
+.rank-number-third {
156
+  background: linear-gradient(135deg, #D6A089, #E9C0AF);
157
+  color: #fff;
158
+}
159
+
160
+.rank-number-default {
161
+  background-color: #e5e7eb;
162
+  color: #6b7280;
163
+}
164
+
165
+.rank-info {
166
+  flex: 1;
167
+  display: flex;
168
+  flex-direction: row;
169
+  align-items: center;
170
+  gap: 8px;
171
+}
172
+
173
+.rank-name {
174
+  font-size: 15px;
175
+  color: #1f2937;
176
+  font-weight: 500;
177
+  min-width: 80px;
178
+}
179
+
180
+.rank-bar {
181
+  flex: 1;
182
+  height: 12px;
183
+  background-color: #e5e7eb;
184
+  border-radius: 6px;
185
+  overflow: hidden;
186
+}
187
+
188
+.rank-bar-fill {
189
+  height: 100%;
190
+  border-radius: 6px;
191
+  transition: width 0.5s ease-out;
192
+}
193
+
194
+.rank-bar-fill-first {
195
+  background: #FFCA00;
196
+}
197
+
198
+.rank-bar-fill-second {
199
+  background: #C4C9DF;
200
+}
201
+
202
+.rank-bar-fill-third {
203
+  background: #D99B86;
204
+}
205
+
206
+.rank-bar-fill-default {
207
+  background: linear-gradient(90deg, #60a5fa, #3b82f6);
208
+}
209
+
210
+.rank-count {
211
+  width: 80px;
212
+  text-align: right;
213
+  font-size: 15px;
214
+  font-weight: 600;
215
+  color: #1f2937;
216
+}
217
+</style>

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

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

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


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

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


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

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

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


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

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

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


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

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

File diff suppressed because it is too large
+ 72 - 4
src/views/system/user/components/UserInfoEdit.vue