index.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909
  1. <template>
  2. <div class="run-screen-container">
  3. <!-- 页面标题 -->
  4. <div class="page-title">
  5. <h1>盛世鹰眸质控系统[生产运行]</h1>
  6. </div>
  7. <!-- 查询条件 -->
  8. <div class="filter-container">
  9. <span style="margin-right: 10px;">日期</span>
  10. <el-date-picker
  11. v-model="dateRange"
  12. type="daterange"
  13. range-separator="-"
  14. start-placeholder="开始日期"
  15. end-placeholder="结束日期"
  16. value-format="YYYY-MM-DD"
  17. style="width: 250px;"
  18. @change="handleDateChange"
  19. />
  20. </div>
  21. <!-- 模块一:运行数据 -->
  22. <div class="module-title-wrapper">
  23. <div class="module-title">模块一:运行数据</div>
  24. </div>
  25. <!-- 第一行 -->
  26. <el-row :gutter="20" class="row-margin">
  27. <!-- 左边50%:两个div -->
  28. <el-col :span="12">
  29. <el-row :gutter="20" style="height: 100%;">
  30. <el-col :span="12">
  31. <div class="data-card">
  32. <div class="card-title">旅检过检总人数</div>
  33. <div class="card-value-wrapper" style="color: #E8B140;">
  34. <span class="card-value">{{ runData.passengerTotal }}</span>
  35. <span class="card-unit">人</span>
  36. </div>
  37. </div>
  38. </el-col>
  39. <el-col :span="12">
  40. <div class="data-card">
  41. <div class="card-title">行检过检总行李数</div>
  42. <div class="card-value-wrapper" style="color: #26B6BE;">
  43. <span class="card-value">{{ runData.luggageTotal }}</span>
  44. <span class="card-unit">件</span>
  45. </div>
  46. </div>
  47. </el-col>
  48. </el-row>
  49. </el-col>
  50. <!-- 右边50%:T1旅检过检人数折线图 -->
  51. <el-col :span="12">
  52. <div class="chart-card">
  53. <div class="chart-title">T1旅检过检人数</div>
  54. <div ref="t1TravelChart" class="chart-container"></div>
  55. </div>
  56. </el-col>
  57. </el-row>
  58. <!-- 第二行 -->
  59. <el-row :gutter="20" class="row-margin">
  60. <el-col :span="12">
  61. <div class="chart-card">
  62. <div class="chart-title">T2旅检过检人数</div>
  63. <div ref="t2TravelChart" class="chart-container"></div>
  64. </div>
  65. </el-col>
  66. <el-col :span="12">
  67. <div class="chart-card">
  68. <div class="chart-title">行检过检数</div>
  69. <div ref="luggageChart" class="chart-container"></div>
  70. </div>
  71. </el-col>
  72. </el-row>
  73. <!-- 第三行 -->
  74. <el-row :gutter="20" class="row-margin">
  75. <el-col :span="8">
  76. <div class="data-card">
  77. <div class="card-title">国内货站总过检数</div>
  78. <div class="card-value-wrapper" style="color: #E8B140;padding: 30px;">
  79. <span class="card-value">{{ runData.domesticCargoTotal }}</span>
  80. <span class="card-unit">件</span>
  81. </div>
  82. </div>
  83. </el-col>
  84. <el-col :span="8">
  85. <div class="data-card">
  86. <div class="card-title">国际货站总过检数</div>
  87. <div class="card-value-wrapper" style="color: #26B6BE;padding: 30px;">
  88. <span class="card-value">{{ runData.intlCargoTotal }}</span>
  89. <span class="card-unit">件</span>
  90. </div>
  91. </div>
  92. </el-col>
  93. <el-col :span="8">
  94. <div class="data-card">
  95. <div class="card-title">道口车辆过检数</div>
  96. <div class="card-value-wrapper" style="color: #5680C9;padding: 30px;">
  97. <span class="card-value">{{ runData.vehicleCheckTotal }}</span>
  98. <span class="card-unit">辆</span>
  99. </div>
  100. </div>
  101. </el-col>
  102. </el-row>
  103. <!-- 第四行 -->
  104. <el-row :gutter="20" class="row-margin">
  105. <el-col :span="12">
  106. <div class="chart-card">
  107. <div class="chart-title">货物过检数</div>
  108. <div ref="cargoChart" class="chart-container"></div>
  109. </div>
  110. </el-col>
  111. <el-col :span="12">
  112. <div class="chart-card">
  113. <div class="chart-title">车辆过检数</div>
  114. <div ref="vehicleChart" class="chart-container"></div>
  115. </div>
  116. </el-col>
  117. </el-row>
  118. <!-- 模块二:查获/收缴数据 -->
  119. <div class="module-title-wrapper">
  120. <div class="module-title">模块二:查获/收缴数据</div>
  121. </div>
  122. <!-- 第一行 -->
  123. <el-row :gutter="20" class="row-margin">
  124. <el-col :span="12">
  125. <div class="chart-card">
  126. <div class="chart-title">查获数据</div>
  127. <div ref="seizePieChart" class="chart-container"></div>
  128. </div>
  129. </el-col>
  130. <el-col :span="12">
  131. <div class="chart-card">
  132. <div class="chart-title">大队查获数对比图</div>
  133. <div ref="teamSeizeBarChart" class="chart-container"></div>
  134. </div>
  135. </el-col>
  136. </el-row>
  137. <!-- 第二行 -->
  138. <el-row :gutter="20" class="row-margin">
  139. <el-col :span="12">
  140. <div class="chart-card">
  141. <div class="chart-title">T1区域各大队查获数对比图</div>
  142. <div ref="t1TeamBarChart" class="chart-container"></div>
  143. </div>
  144. </el-col>
  145. <el-col :span="12">
  146. <div class="chart-card">
  147. <div class="chart-title">T2区域各大队查获数对比图</div>
  148. <div ref="t2TeamBarChart" class="chart-container"></div>
  149. </div>
  150. </el-col>
  151. </el-row>
  152. <!-- 第三行 -->
  153. <el-row :gutter="20" class="row-margin">
  154. <el-col :span="12">
  155. <div class="chart-card">
  156. <div class="chart-title">不合格充电宝劝阻数组</div>
  157. <div ref="powerBankPieChart" class="chart-container"></div>
  158. </div>
  159. </el-col>
  160. <el-col :span="12">
  161. <div class="chart-card">
  162. <div class="chart-title">待检区收缴禁限带物品数据表</div>
  163. <div ref="waitingAreaPieChart" class="chart-container"></div>
  164. </div>
  165. </el-col>
  166. </el-row>
  167. </div>
  168. </template>
  169. <script setup name="RunScreen">
  170. import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'
  171. import * as echarts from 'echarts'
  172. // 日期范围
  173. const currentYear = new Date().getFullYear()
  174. const defaultStartDate = `${currentYear}-01-01`
  175. const defaultEndDate = new Date().toISOString().split('T')[0]
  176. const dateRange = ref([defaultStartDate, defaultEndDate])
  177. const queryParams = reactive({
  178. startDate: defaultStartDate,
  179. endDate: defaultEndDate
  180. })
  181. // 图表引用
  182. import {
  183. getOperationSummary,
  184. getT1PassengerTrend,
  185. getT2PassengerTrend,
  186. getLuggageCheckTrend,
  187. getCargoTrend,
  188. getVehicleCheckTrend,
  189. getSeizureCategory,
  190. getBrigadeSeizureCompare,
  191. getPowerBankSummary,
  192. getPendingConfiscate
  193. } from '@/api/runData/runScreen.js'
  194. // 图表引用
  195. const t1TravelChart = ref(null)
  196. const t2TravelChart = ref(null)
  197. const luggageChart = ref(null)
  198. const cargoChart = ref(null)
  199. const vehicleChart = ref(null)
  200. const seizePieChart = ref(null)
  201. const teamSeizeBarChart = ref(null)
  202. const t1TeamBarChart = ref(null)
  203. const t2TeamBarChart = ref(null)
  204. const powerBankPieChart = ref(null)
  205. const waitingAreaPieChart = ref(null)
  206. // 图表实例
  207. let t1TravelChartInstance = null
  208. let t2TravelChartInstance = null
  209. let luggageChartInstance = null
  210. let cargoChartInstance = null
  211. let vehicleChartInstance = null
  212. let seizePieChartInstance = null
  213. let teamSeizeBarChartInstance = null
  214. let t1TeamBarChartInstance = null
  215. let t2TeamBarChartInstance = null
  216. let powerBankPieChartInstance = null
  217. let waitingAreaPieChartInstance = null
  218. // 运行数据
  219. const runData = ref({
  220. travelInspectionTotal: 0,
  221. luggageInspectionTotal: 0,
  222. domesticCargoTotal: 0,
  223. internationalCargoTotal: 0,
  224. vehicleInspectionTotal: 0
  225. })
  226. const t1PassengerData = ref([])
  227. const t2PassengerData = ref([])
  228. const luggageCheckData = ref([])
  229. const cargoData = ref([])
  230. const vehicleCheckData = ref([])
  231. const seizureCategoryData = ref([])
  232. const brigadeSeizureData = ref([])
  233. const powerBankData = ref({})
  234. const pendingConfiscateData = ref({})
  235. // 日期范围变化
  236. function handleDateChange(val) {
  237. if (val && val.length === 2) {
  238. queryParams.startDate = val[0]
  239. queryParams.endDate = val[1]
  240. } else {
  241. queryParams.startDate = defaultStartDate
  242. queryParams.endDate = defaultEndDate
  243. }
  244. loadData()
  245. }
  246. async function loadData() {
  247. try {
  248. const query = { ...queryParams }
  249. const [
  250. summaryRes,
  251. t1PassengerRes,
  252. t2PassengerRes,
  253. luggageRes,
  254. cargoRes,
  255. vehicleRes,
  256. seizureRes,
  257. brigadeRes,
  258. powerBankRes,
  259. pendingRes
  260. ] = await Promise.all([
  261. getOperationSummary(query),
  262. getT1PassengerTrend(query),
  263. getT2PassengerTrend(query),
  264. getLuggageCheckTrend(query),
  265. getCargoTrend(query),
  266. getVehicleCheckTrend(query),
  267. getSeizureCategory(query),
  268. getBrigadeSeizureCompare(query),
  269. getPowerBankSummary(query),
  270. getPendingConfiscate(query)
  271. ])
  272. if (summaryRes.data) {
  273. runData.value = summaryRes.data
  274. }
  275. if (t1PassengerRes.data) {
  276. t1PassengerData.value = t1PassengerRes.data
  277. }
  278. if (t2PassengerRes.data) {
  279. t2PassengerData.value = t2PassengerRes.data
  280. }
  281. if (luggageRes.data) {
  282. luggageCheckData.value = luggageRes.data
  283. }
  284. if (cargoRes.data) {
  285. cargoData.value = cargoRes.data
  286. }
  287. if (vehicleRes.data) {
  288. vehicleCheckData.value = vehicleRes.data
  289. }
  290. if (seizureRes.data) {
  291. seizureCategoryData.value = seizureRes.data
  292. }
  293. if (brigadeRes.data) {
  294. brigadeSeizureData.value = brigadeRes.data
  295. }
  296. if (powerBankRes.data) {
  297. powerBankData.value = powerBankRes.data
  298. }
  299. if (pendingRes.data) {
  300. pendingConfiscateData.value = pendingRes.data
  301. }
  302. nextTick(() => {
  303. initCharts()
  304. })
  305. } catch (error) {
  306. console.error('加载数据失败:', error)
  307. }
  308. }
  309. // 初始化所有图表
  310. function initCharts() {
  311. // T1旅检过检人数折线图
  312. if (t1TravelChart.value) {
  313. t1TravelChartInstance = echarts.init(t1TravelChart.value)
  314. const t1Data = t1PassengerData.value || []
  315. t1TravelChartInstance.setOption({
  316. tooltip: { trigger: 'axis' },
  317. legend: { data: ['A区', 'B区'], top: 30 },
  318. xAxis: {
  319. type: 'category',
  320. data: t1Data.map(item => item.recordDate || item.azone || '')
  321. },
  322. yAxis: { type: 'value' },
  323. series: [
  324. {
  325. name: 'A区',
  326. data: t1Data.map(item => item.azone || 0),
  327. type: 'line',
  328. smooth: true,
  329. lineStyle: { color: '#E8B140' },
  330. areaStyle: { color: 'rgba(232, 177, 64, 0.2)' }
  331. },
  332. {
  333. name: 'B区',
  334. data: t1Data.map(item => item.bzone || 0),
  335. type: 'line',
  336. smooth: true,
  337. lineStyle: { color: '#5470c6' },
  338. areaStyle: { color: 'rgba(84, 112, 198, 0.2)' }
  339. }
  340. ]
  341. })
  342. }
  343. // T2旅检过检人数折线图
  344. if (t2TravelChart.value) {
  345. t2TravelChartInstance = echarts.init(t2TravelChart.value)
  346. const t2Data = t2PassengerData.value || []
  347. t2TravelChartInstance.setOption({
  348. tooltip: { trigger: 'axis' },
  349. legend: { data: ['国内旅检', '国际旅检'], top: 30 },
  350. xAxis: {
  351. type: 'category',
  352. data: t2Data.map(item => item.recordDate || '')
  353. },
  354. yAxis: { type: 'value' },
  355. series: [
  356. {
  357. name: '国内旅检',
  358. data: t2Data.map(item => item.domesticPassenger || 0),
  359. type: 'line',
  360. smooth: true,
  361. lineStyle: { color: '#91cc75' },
  362. areaStyle: { color: 'rgba(145, 204, 117, 0.2)' }
  363. },
  364. {
  365. name: '国际旅检',
  366. data: t2Data.map(item => item.intlPassenger || 0),
  367. type: 'line',
  368. smooth: true,
  369. lineStyle: { color: '#fac858' },
  370. areaStyle: { color: 'rgba(250, 200, 88, 0.2)' }
  371. }
  372. ]
  373. })
  374. }
  375. // 行检过检数折线图
  376. if (luggageChart.value) {
  377. luggageChartInstance = echarts.init(luggageChart.value)
  378. const luggageData = luggageCheckData.value || []
  379. luggageChartInstance.setOption({
  380. tooltip: { trigger: 'axis' },
  381. legend: { data: ['T1行检', 'T2行检'], top: 30 },
  382. xAxis: {
  383. type: 'category',
  384. data: luggageData.map(item => item.recordDate || '')
  385. },
  386. yAxis: { type: 'value' },
  387. series: [
  388. {
  389. name: 'T1行检',
  390. data: luggageData.map(item => item.t1LuggageCheck || 0),
  391. type: 'line',
  392. smooth: true,
  393. lineStyle: { color: '#ee6666' },
  394. areaStyle: { color: 'rgba(238, 102, 102, 0.2)' }
  395. },
  396. {
  397. name: 'T2行检',
  398. data: luggageData.map(item => item.t2LuggageCheck || 0),
  399. type: 'line',
  400. smooth: true,
  401. lineStyle: { color: '#73c0de' },
  402. areaStyle: { color: 'rgba(115, 192, 222, 0.2)' }
  403. }
  404. ]
  405. })
  406. }
  407. // 货物过检数折线图
  408. if (cargoChart.value) {
  409. cargoChartInstance = echarts.init(cargoChart.value)
  410. const cargoTrendData = cargoData.value || []
  411. cargoChartInstance.setOption({
  412. tooltip: { trigger: 'axis' },
  413. legend: { data: ['国内货站', '国际货站'], top: 30 },
  414. xAxis: {
  415. type: 'category',
  416. data: cargoTrendData.map(item => item.recordDate || '')
  417. },
  418. yAxis: { type: 'value' },
  419. series: [
  420. {
  421. name: '国内货站',
  422. data: cargoTrendData.map(item => item.domesticCargo || 0),
  423. type: 'line',
  424. smooth: true,
  425. lineStyle: { color: '#E8B140' },
  426. areaStyle: { color: 'rgba(232, 177, 64, 0.2)' }
  427. },
  428. {
  429. name: '国际货站',
  430. data: cargoTrendData.map(item => item.intlCargo || 0),
  431. type: 'line',
  432. smooth: true,
  433. lineStyle: { color: '#26B6BE' },
  434. areaStyle: { color: 'rgba(38, 182, 190, 0.2)' }
  435. }
  436. ]
  437. })
  438. }
  439. // 车辆过检数折线图
  440. if (vehicleChart.value) {
  441. vehicleChartInstance = echarts.init(vehicleChart.value)
  442. const vehicleData = vehicleCheckData.value || []
  443. vehicleChartInstance.setOption({
  444. tooltip: { trigger: 'axis' },
  445. legend: { data: ['南侧车检', '北侧车检'], top: 30 },
  446. xAxis: {
  447. type: 'category',
  448. data: vehicleData.map(item => item.recordDate || '')
  449. },
  450. yAxis: { type: 'value' },
  451. series: [
  452. {
  453. name: '南侧车检',
  454. data: vehicleData.map(item => item.southVehicleCheck || 0),
  455. type: 'line',
  456. smooth: true,
  457. lineStyle: { color: '#E8B140' },
  458. areaStyle: { color: 'rgba(232, 177, 64, 0.2)' }
  459. },
  460. {
  461. name: '北侧车检',
  462. data: vehicleData.map(item => item.northVehicleCheck || 0),
  463. type: 'line',
  464. smooth: true,
  465. lineStyle: { color: '#5470c6' },
  466. areaStyle: { color: 'rgba(84, 112, 198, 0.2)' }
  467. }
  468. ]
  469. })
  470. }
  471. // 查获数据环形图
  472. if (seizePieChart.value) {
  473. seizePieChartInstance = echarts.init(seizePieChart.value)
  474. const seizureData = seizureCategoryData.value || []
  475. seizePieChartInstance.setOption({
  476. tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
  477. legend: { orient: 'vertical', right: 10, top: 'center' },
  478. series: [{
  479. name: '查获数据',
  480. type: 'pie',
  481. radius: ['40%', '70%'],
  482. center: ['40%', '50%'],
  483. data: seizureData.map(item => ({ name: item.name, value: item.total })),
  484. emphasis: {
  485. itemStyle: {
  486. shadowBlur: 10,
  487. shadowOffsetX: 0,
  488. shadowColor: 'rgba(0, 0, 0, 0.5)'
  489. }
  490. },
  491. label: {
  492. formatter: '{b}: {c} ({d}%)'
  493. }
  494. }]
  495. })
  496. }
  497. // 大队查获数对比柱状图
  498. if (teamSeizeBarChart.value) {
  499. teamSeizeBarChartInstance = echarts.init(teamSeizeBarChart.value)
  500. const brigadeData = brigadeSeizureData.value || []
  501. const shifts = [...new Set(brigadeData.map(item => item.shift))]
  502. const brigades = [...new Set(brigadeData.map(item => item.brigade))]
  503. const series = brigades.map(brigade => ({
  504. name: brigade,
  505. data: shifts.map(shift => {
  506. const item = brigadeData.find(d => d.shift === shift && d.brigade === brigade)
  507. return item ? item.grandTotal : 0
  508. }),
  509. type: 'bar',
  510. label: {
  511. show: true,
  512. position: 'top',
  513. formatter: '{c}'
  514. }
  515. }))
  516. teamSeizeBarChartInstance.setOption({
  517. tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
  518. legend: { data: brigades, top: 30 },
  519. xAxis: {
  520. type: 'category',
  521. data: shifts
  522. },
  523. yAxis: { type: 'value' },
  524. series: series
  525. })
  526. }
  527. // T1区域各大队查获数对比柱状图
  528. if (t1TeamBarChart.value) {
  529. t1TeamBarChartInstance = echarts.init(t1TeamBarChart.value)
  530. const brigadeData = brigadeSeizureData.value || []
  531. const shifts = [...new Set(brigadeData.map(item => item.shift))]
  532. const brigades = [...new Set(brigadeData.map(item => item.brigade))]
  533. const series = brigades.map(brigade => ({
  534. name: brigade,
  535. data: shifts.map(shift => {
  536. const item = brigadeData.find(d => d.shift === shift && d.brigade === brigade)
  537. return item ? item.t1Total : 0
  538. }),
  539. type: 'bar',
  540. label: {
  541. show: true,
  542. position: 'top',
  543. formatter: '{c}'
  544. }
  545. }))
  546. t1TeamBarChartInstance.setOption({
  547. tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
  548. legend: { data: brigades, top: 30 },
  549. xAxis: {
  550. type: 'category',
  551. data: shifts
  552. },
  553. yAxis: { type: 'value' },
  554. series: series
  555. })
  556. }
  557. // T2区域各大队查获数对比柱状图
  558. if (t2TeamBarChart.value) {
  559. t2TeamBarChartInstance = echarts.init(t2TeamBarChart.value)
  560. const brigadeData = brigadeSeizureData.value || []
  561. const shifts = [...new Set(brigadeData.map(item => item.shift))]
  562. const brigades = [...new Set(brigadeData.map(item => item.brigade))]
  563. const series = brigades.map(brigade => ({
  564. name: brigade,
  565. data: shifts.map(shift => {
  566. const item = brigadeData.find(d => d.shift === shift && d.brigade === brigade)
  567. return item ? item.t2Total : 0
  568. }),
  569. type: 'bar',
  570. label: {
  571. show: true,
  572. position: 'top',
  573. formatter: '{c}'
  574. }
  575. }))
  576. t2TeamBarChartInstance.setOption({
  577. tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
  578. legend: { data: brigades, top: 30 },
  579. xAxis: {
  580. type: 'category',
  581. data: shifts
  582. },
  583. yAxis: { type: 'value' },
  584. series: series
  585. })
  586. }
  587. // 不合格充电宝劝阻数组环形图
  588. if (powerBankPieChart.value) {
  589. powerBankPieChartInstance = echarts.init(powerBankPieChart.value)
  590. const pbData = powerBankData.value || {}
  591. const powerBankChartData = [
  592. { name: '邮寄', value: pbData.mailTotal || 0 },
  593. { name: '暂存', value: pbData.tempStoreTotal || 0 },
  594. { name: '自弃', value: pbData.abandonTotal || 0 },
  595. { name: '召回', value: pbData.recallTotal || 0 },
  596. { name: '无3C标识', value: pbData.no3cTotal || 0 },
  597. { name: '标识不清', value: pbData.unclearMarkTotal || 0 },
  598. { name: '超规数量', value: pbData.excessQtyTotal || 0 },
  599. { name: '其他', value: pbData.bothTotal || 0 }
  600. ].filter(item => item.value > 0)
  601. powerBankPieChartInstance.setOption({
  602. tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
  603. legend: { orient: 'vertical', right: 10, top: 'center' },
  604. series: [{
  605. name: '不合格充电宝',
  606. type: 'pie',
  607. radius: ['40%', '70%'],
  608. center: ['40%', '50%'],
  609. data: powerBankChartData,
  610. emphasis: {
  611. itemStyle: {
  612. shadowBlur: 10,
  613. shadowOffsetX: 0,
  614. shadowColor: 'rgba(0, 0, 0, 0.5)'
  615. }
  616. },
  617. label: {
  618. formatter: '{b}: {c} ({d}%)'
  619. }
  620. }]
  621. })
  622. }
  623. // 待检区收缴禁限带物品数据表环形图
  624. if (waitingAreaPieChart.value) {
  625. waitingAreaPieChartInstance = echarts.init(waitingAreaPieChart.value)
  626. const pendingData = pendingConfiscateData.value || {}
  627. const pendingChartData = [
  628. { name: '火种', value: pendingData.fireSourceTotal || 0 },
  629. { name: '液态物品', value: pendingData.liquidTotal || 0 },
  630. { name: '其他', value: pendingData.otherTotal || 0 }
  631. ].filter(item => item.value > 0)
  632. waitingAreaPieChartInstance.setOption({
  633. tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
  634. legend: { orient: 'vertical', right: 10, top: 'center' },
  635. series: [{
  636. name: '禁限带物品',
  637. type: 'pie',
  638. radius: ['40%', '70%'],
  639. center: ['40%', '50%'],
  640. data: pendingChartData,
  641. emphasis: {
  642. itemStyle: {
  643. shadowBlur: 10,
  644. shadowOffsetX: 0,
  645. shadowColor: 'rgba(0, 0, 0, 0.5)'
  646. }
  647. },
  648. label: {
  649. formatter: '{b}: {c} ({d}%)'
  650. }
  651. }]
  652. })
  653. }
  654. }
  655. // 窗口大小变化时重新调整图表大小
  656. function handleResize() {
  657. const charts = [
  658. t1TravelChartInstance, t2TravelChartInstance, luggageChartInstance,
  659. cargoChartInstance, vehicleChartInstance, seizePieChartInstance,
  660. teamSeizeBarChartInstance, t1TeamBarChartInstance, t2TeamBarChartInstance,
  661. powerBankPieChartInstance, waitingAreaPieChartInstance
  662. ]
  663. charts.forEach(chart => {
  664. if (chart) {
  665. chart.resize()
  666. }
  667. })
  668. }
  669. // 组件挂载时初始化图表
  670. onMounted(() => {
  671. loadData()
  672. window.addEventListener('resize', handleResize)
  673. })
  674. // 组件卸载时销毁图表
  675. onUnmounted(() => {
  676. const charts = [
  677. t1TravelChartInstance, t2TravelChartInstance, luggageChartInstance,
  678. cargoChartInstance, vehicleChartInstance, seizePieChartInstance,
  679. teamSeizeBarChartInstance, t1TeamBarChartInstance, t2TeamBarChartInstance,
  680. powerBankPieChartInstance, waitingAreaPieChartInstance
  681. ]
  682. charts.forEach(chart => {
  683. if (chart) {
  684. chart.dispose()
  685. }
  686. })
  687. window.removeEventListener('resize', handleResize)
  688. })
  689. </script>
  690. <style scoped>
  691. .run-screen-container {
  692. padding: 20px;
  693. background: #f0f2f5;
  694. min-height: 100vh;
  695. }
  696. .page-title {
  697. text-align: center;
  698. margin-bottom: 30px;
  699. }
  700. .page-title h1 {
  701. font-size: 28px;
  702. color: #333;
  703. font-weight: bold;
  704. margin: 0;
  705. }
  706. .filter-container {
  707. width: 100%;
  708. background: #fff;
  709. padding: 15px;
  710. border-radius: 4px;
  711. margin-bottom: 20px;
  712. }
  713. .module-title-wrapper {
  714. text-align: center;
  715. width: 100%;
  716. background: #fff;
  717. border-radius: 8px;
  718. padding: 15px 20px;
  719. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  720. margin-bottom: 20px;
  721. }
  722. .module-title {
  723. font-size: 20px;
  724. font-weight: bold;
  725. color: #333;
  726. margin: 0;
  727. }
  728. .module-section {
  729. width: 100%;
  730. background: #fff;
  731. border-radius: 8px;
  732. padding: 20px;
  733. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  734. margin-bottom: 20px;
  735. }
  736. .row-margin {
  737. margin-bottom: 20px;
  738. }
  739. .data-card {
  740. background: #fff;
  741. border-radius: 8px;
  742. padding: 15px;
  743. color: #333;
  744. height: 100%;
  745. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  746. border: 1px solid #e6e8eb;
  747. }
  748. .card-title {
  749. font-size: 16px;
  750. color: #333;
  751. font-weight: bold;
  752. text-align: left;
  753. }
  754. .card-value-wrapper {
  755. height: 100%;
  756. display: flex;
  757. flex-direction: column;
  758. align-items: center;
  759. justify-content: center;
  760. }
  761. .card-value {
  762. font-size: 50px;
  763. font-weight: bold;
  764. }
  765. .card-unit {
  766. font-size: 16px;
  767. }
  768. .chart-card {
  769. background: #fff;
  770. border-radius: 8px;
  771. padding: 15px;
  772. border: 1px solid #e6e8eb;
  773. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
  774. height: 300px;
  775. }
  776. .chart-title {
  777. font-size: 16px;
  778. font-weight: bold;
  779. color: #333;
  780. margin-bottom: 15px;
  781. text-align: left;
  782. }
  783. .chart-container {
  784. width: 100%;
  785. height: calc(100% - 30px);
  786. }
  787. /* 响应式设计 */
  788. @media (max-width: 1200px) {
  789. .data-card {
  790. height: 100px;
  791. }
  792. .card-value {
  793. font-size: 30px;
  794. }
  795. .chart-card {
  796. height: 250px;
  797. }
  798. }
  799. @media (max-width: 768px) {
  800. .run-screen-container {
  801. padding: 10px;
  802. }
  803. .module-section {
  804. padding: 15px;
  805. }
  806. .data-card {
  807. height: 80px;
  808. padding: 15px;
  809. }
  810. .card-title {
  811. font-size: 14px;
  812. }
  813. .card-value {
  814. font-size: 20px;
  815. }
  816. .chart-card {
  817. height: 200px;
  818. }
  819. }
  820. </style>