ModuleBrigadeOne.vue 12 KB

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