index.vue 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281
  1. <!--
  2. * @Author: your name
  3. * @Date: 2022-01-17 10:39:22
  4. * @LastEditTime: 2022-08-12 19:14:09
  5. * @LastEditors: your name
  6. * @Description: 行李视图
  7. -->
  8. <template>
  9. <div class="baggage-view">
  10. <div
  11. class="part1"
  12. :style="{ height: basicInfoHeight }"
  13. >
  14. <div class="title">
  15. <span>行李基本信息</span>
  16. <el-radio-group
  17. v-model="infoBtn"
  18. class="radioBtn"
  19. size="mini"
  20. fill="#FFFFFF"
  21. text-color="#28344D"
  22. >
  23. <el-radio-button
  24. v-for="item in infoRadios"
  25. :key="item"
  26. :label="item"
  27. />
  28. </el-radio-group>
  29. <!-- <BackButton /> -->
  30. </div>
  31. <div
  32. v-show="basicInfoOpen"
  33. class="part1_info"
  34. >
  35. <el-row :gutter="12">
  36. <el-col
  37. v-for="(item, index) in baggageBasicInfoCols"
  38. :key="index"
  39. :xl="[0, 2, 4].includes(index % 7) ? 4 : 3"
  40. :sm="6"
  41. :xs="6"
  42. >
  43. <span class="label">{{ item.label }}:</span>
  44. <span
  45. class="content"
  46. :class="{
  47. click: item.prop === 'compensationSign' && baggageBasicInfo[item.prop]
  48. }"
  49. :title="formattedBaggageInfo(item.prop)"
  50. @click="baggageBasicInfoClickHandler(item.prop)"
  51. >{{ formattedBaggageInfo(item.prop) }}</span
  52. >
  53. </el-col>
  54. </el-row>
  55. </div>
  56. <div
  57. class="button-toggle"
  58. @click="basicInfoToggle"
  59. >
  60. <i :class="basicInfoOpen ? 'el-icon-caret-top' : 'el-icon-caret-bottom'" />
  61. </div>
  62. </div>
  63. <div
  64. v-show="infoBtn === infoRadios[0]"
  65. class="part2"
  66. :style="{ height: trackListHeight }"
  67. >
  68. <div class="part2_info">
  69. <div class="title">行李跟踪信息</div>
  70. <div class="type normal">
  71. {{ baggageBasicInfo.BagStatus }}
  72. </div>
  73. <div class="airline">
  74. <el-select
  75. v-model="selectedAirline"
  76. size="mini"
  77. class="airline-select"
  78. >
  79. <el-option
  80. v-for="airline in airlineList"
  81. :key="airline.value"
  82. :value="airline.value"
  83. :label="airline.label"
  84. />
  85. </el-select>
  86. </div>
  87. <div class="baggage-track-chart">
  88. <div class="step-line">
  89. <div
  90. v-for="(line, index) in 6"
  91. :key="index"
  92. :class="['step-line-segment', { 'step-line-active': activeStepLine(index) }]"
  93. />
  94. </div>
  95. <div
  96. v-for="(item, index) in stepNodes"
  97. :key="index"
  98. :class="{ 'step-item': true, 'active-item': item.status }"
  99. >
  100. <div class="step-circle">
  101. <span class="step-name">{{ item.nodeName }}</span>
  102. </div>
  103. <div class="step-info">
  104. <div :class="statusClasses(item.status)">{{ item.status }}</div>
  105. <span class="step-time">{{ item.processingTime }}</span>
  106. <div class="step-location">{{ item.locationId }}</div>
  107. </div>
  108. </div>
  109. </div>
  110. </div>
  111. <div class="btns">
  112. <img
  113. class="btn-square btn-shadow"
  114. src="@/assets/baggage/ic_export.png"
  115. title="导出"
  116. @click="exportHandler('table', '行李节点列表')"
  117. />
  118. <img
  119. class="btn-square btn-shadow"
  120. src="@/assets/baggage/ic_setting.png"
  121. title="列设置"
  122. @click="show"
  123. />
  124. </div>
  125. <div
  126. class="button-toggle"
  127. @click="trackListToggle"
  128. >
  129. <i :class="trackListOpen ? 'el-icon-caret-top' : 'el-icon-caret-bottom'" />
  130. </div>
  131. </div>
  132. <div
  133. v-show="infoBtn == infoRadios[0]"
  134. class="part3"
  135. :style="{
  136. height: `calc(100vh - 80px - ${basicInfoHeight} - ${trackListHeight} - 3 * 8px - 20px)`
  137. }"
  138. >
  139. <el-table
  140. ref="table"
  141. :data="baggageTableData"
  142. height="100%"
  143. size="mini"
  144. border
  145. fit
  146. :header-cell-class-name="headerCellClass"
  147. :header-cell-style="{ color: '#101116' }"
  148. :cell-class-name="cellClass"
  149. :span-method="tableSpanMethod"
  150. @cell-click="cellClickHandler"
  151. >
  152. <el-table-column
  153. v-for="item in tableColsCopy"
  154. :key="item.index"
  155. :prop="item.prop"
  156. :label="item.name"
  157. :align="item.align || 'center'"
  158. :width="item.width"
  159. show-overflow-tooltip
  160. :fixed="item.fixed"
  161. >
  162. <template slot="header">
  163. <div class="cell-content">{{ item.name }}</div>
  164. </template>
  165. <template slot-scope="scope">
  166. <div class="cell-content">{{ scope.row[item.prop] }}</div>
  167. </template>
  168. </el-table-column>
  169. </el-table>
  170. </div>
  171. <div
  172. v-show="infoBtn === infoRadios[1]"
  173. class="part4"
  174. :style="{
  175. height: `calc(100vh - 80px - ${basicInfoHeight} - 2 * 8px - 20px)`
  176. }"
  177. >
  178. <header class="head">
  179. <div class="title">行李跟踪信息</div>
  180. <div class="btns">
  181. <img
  182. class="btn-square btn-shadow"
  183. src="@/assets/baggage/ic_export.png"
  184. title="导出"
  185. @click="exportMessageToExcel"
  186. />
  187. </div>
  188. </header>
  189. <main class="main">
  190. <template v-if="messageList.length">
  191. <el-row
  192. :gutter="24"
  193. type="flex"
  194. >
  195. <el-col
  196. v-for="(message, index) in messageList"
  197. :key="index"
  198. :span="6"
  199. >
  200. <div class="card">
  201. <div class="message-date">{{ message.date }}</div>
  202. <div class="message-content">
  203. {{ message.dataContent.replaceAll(/[\r\n]{2,}/g, '\n').replaceAll('\\', '') }}
  204. </div>
  205. </div>
  206. </el-col>
  207. </el-row>
  208. </template>
  209. <template v-else>
  210. <el-empty
  211. :image-size="1"
  212. description="暂无数据"
  213. />
  214. </template>
  215. </main>
  216. </div>
  217. <!--列设置-->
  218. <Dialog
  219. :flag="dialogFlag"
  220. class="dialog-check-group"
  221. >
  222. <div class="dialog-wrapper">
  223. <div class="title">列设置</div>
  224. <div class="content">
  225. <el-tree
  226. ref="columnSetTree"
  227. :data="tableCols"
  228. :class="colsCheckClass"
  229. show-checkbox
  230. node-key="index"
  231. :default-expand-all="true"
  232. :props="{
  233. label: 'name',
  234. children: 'children'
  235. }"
  236. :default-checked-keys="checkedKeysTemp"
  237. @check="handleCheck"
  238. />
  239. </div>
  240. <div class="foot right t30">
  241. <el-button
  242. size="medium"
  243. class="r24"
  244. type="primary"
  245. @click="onCheck('baggageTableData')"
  246. >确定</el-button
  247. >
  248. <el-button
  249. size="medium"
  250. @click="hide"
  251. >取消</el-button
  252. >
  253. </div>
  254. </div>
  255. </Dialog>
  256. </div>
  257. </template>
  258. <script>
  259. // import BackButton from '@/components/BackButton'
  260. import Dialog from '@/layout/components/Dialog/index.vue'
  261. import { myQuery } from '@/api/dataIntegration'
  262. import { BaggageMessageQuery } from '@/api/flight'
  263. import tableColsMixin from '../../mixins/tableCols'
  264. import { throttledExportToExcel } from '@/utils/table'
  265. import * as XLSX from 'xlsx'
  266. import XLSX_STYLE from 'xlsx-style'
  267. import FileSaver from 'file-saver'
  268. import { mapGetters } from 'vuex'
  269. export default {
  270. name: 'BaggageView',
  271. components: {
  272. // BackButton,
  273. Dialog
  274. },
  275. mixins: [tableColsMixin],
  276. data() {
  277. return {
  278. queryData: {},
  279. airlineList: [],
  280. selectedAirline: '',
  281. debounceTime: 300,
  282. baggageBasicInfoCols: [
  283. {
  284. label: '行李牌号',
  285. prop: 'bagNo'
  286. },
  287. {
  288. label: '航班号',
  289. prop: 'flightNO'
  290. },
  291. {
  292. label: '特殊行李类型',
  293. prop: 'specialType'
  294. },
  295. {
  296. label: 'PNR编号',
  297. prop: 'PNR'
  298. },
  299. {
  300. label: '旅客',
  301. prop: 'name'
  302. },
  303. {
  304. label: '装载序列号',
  305. prop: 'loadSequenceIndex'
  306. },
  307. {
  308. label: '总件数',
  309. prop: 'totalNumber'
  310. },
  311. {
  312. label: '总重量',
  313. prop: 'totalWeight'
  314. },
  315. {
  316. label: '尺寸',
  317. prop: 'size'
  318. },
  319. {
  320. label: '常旅客号',
  321. prop: 'frequentFlyerNumber'
  322. },
  323. {
  324. label: '常旅客级别',
  325. prop: 'frequentFlyerClass'
  326. },
  327. {
  328. label: '取消值机',
  329. prop: 'whetherToCancelTheCheckIn'
  330. },
  331. {
  332. label: '是否可装载',
  333. prop: 'isItLoadable'
  334. },
  335. {
  336. label: '是否可运输',
  337. prop: 'isItTransportable'
  338. },
  339. {
  340. label: '行李激活状态',
  341. prop: 'activeState'
  342. },
  343. {
  344. label: '无BSM状态',
  345. prop: 'noBSM'
  346. },
  347. {
  348. label: '中转进航班',
  349. prop: 'inFlightNo'
  350. },
  351. {
  352. label: '中转出航班',
  353. prop: 'transferFlightNo'
  354. },
  355. {
  356. label: '速运标记',
  357. prop: 'expressSign'
  358. },
  359. {
  360. label: '破损标记',
  361. prop: 'brokenSign'
  362. },
  363. {
  364. label: '投诉标记',
  365. prop: 'complaintSign'
  366. },
  367. {
  368. label: '赔偿标记',
  369. prop: 'compensationSign'
  370. },
  371. {
  372. label: '异常状态',
  373. prop: 'bagExcType'
  374. },
  375. {
  376. label: '企业或团队名称',
  377. prop: 'teamOrGroup'
  378. }
  379. ],
  380. baggageBasicInfo: {},
  381. infoBtn: '',
  382. infoRadios: ['跟踪信息', '跟踪报文'],
  383. messageList: [],
  384. stepNodes: [],
  385. tableCols: [
  386. { name: '航班号', prop: 'flightNO', fixed: true },
  387. { name: '航班日期', prop: 'flightDate', width: 95, fixed: true },
  388. { name: '起飞航站\n起飞时间', prop: 'departureInfo', width: 100 },
  389. { name: '目的航站\n降落时间', prop: 'landingInfo', width: 100 },
  390. { name: '旅客舱位', prop: 'passengerCompartment', width: 70 },
  391. { name: '旅客座位号', prop: 'passengerSeatNumber' },
  392. { name: '值机序号', prop: 'passengerCheckInNumber', width: 70 },
  393. { name: '节点标识', prop: 'nodeCode', width: 100 },
  394. { name: '节点名称', prop: 'nodeName', width: 70 },
  395. { name: '位置标识', prop: 'locationCode' },
  396. { name: '位置描述', prop: 'locationRemark' },
  397. { name: '读取时间', prop: 'dealTime', width: 158 },
  398. { name: '结果', prop: 'status', width: 60 },
  399. { name: '次级代码', prop: 'secondaryCode', width: 70 },
  400. { name: '操作人', prop: 'AgentCode', width: 90 },
  401. { name: '设备ID', prop: 'DeviceCode' },
  402. { name: '发往位置', prop: 'toLocation', width: 70 },
  403. { name: '发往位置描述', prop: 'toLocationMark', width: 100 },
  404. { name: '装载序号', prop: 'LoadSN', width: 70 },
  405. { name: '容器编号', prop: 'U_Device_ID', width: 110 },
  406. { name: '数据来源', prop: 'dataSource' }
  407. ],
  408. baggageTableData: [],
  409. spanArr: [],
  410. pos: 0,
  411. queryLoop: null,
  412. queryTrackLoop: null,
  413. basicInfoOpen: true,
  414. trackListOpen: false
  415. }
  416. },
  417. computed: {
  418. ...mapGetters(['sidebar']),
  419. activeStepLine() {
  420. return function (index) {
  421. return this.stepNodes[index].status && this.stepNodes[index + 1].status
  422. }
  423. },
  424. statusClasses() {
  425. return function (status) {
  426. const classes = ['step-status']
  427. if (typeof status === 'string') {
  428. if (status.includes('正常') || status.includes('通过')) {
  429. classes.push('step-status-normal')
  430. } else {
  431. classes.push('step-status-abnormal')
  432. }
  433. }
  434. return classes
  435. }
  436. },
  437. formattedBaggageInfo() {
  438. return function (prop) {
  439. const value = this.baggageBasicInfo[prop]
  440. if ((value ?? '') === '') {
  441. return ''
  442. } else {
  443. switch (prop) {
  444. case 'transitSign':
  445. return Number(value) === 0 ? '非中转' : '中转'
  446. case 'activeState':
  447. return Number(value) === 0 ? '未激活' : '激活'
  448. case 'whetherToCancelTheCheckIn':
  449. return Number(value) === 1 ? '取消' : ''
  450. case 'isItLoadable':
  451. case 'isItTransportable':
  452. case 'noBSM':
  453. case 'expressSign':
  454. case 'brokenSign':
  455. case 'complaintSign':
  456. // case 'compensationSign':
  457. return Number(value) === 1 || value === 'Y' ? '是' : '否'
  458. default:
  459. return value
  460. }
  461. }
  462. }
  463. },
  464. basicInfoHeight() {
  465. return this.basicInfoOpen ? '220px' : '68px'
  466. },
  467. trackListHeight() {
  468. return this.trackListOpen ? '176px' : '81px'
  469. }
  470. },
  471. watch: {
  472. infoBtn: {
  473. handler(val) {
  474. this.stopLoopAll()
  475. if (val === this.infoRadios[0]) {
  476. if (this.selectedAirline) {
  477. this.startQueryTrack()
  478. }
  479. this.startQueryDetails()
  480. } else if (val === this.infoRadios[1]) {
  481. this.startQueryMessage()
  482. }
  483. },
  484. immediate: true
  485. },
  486. selectedAirline() {
  487. this.stopQueryTrack()
  488. this.startQueryTrack()
  489. }
  490. },
  491. created() {
  492. this.resetStepNodes()
  493. const { flightNO, flightDate, bagSN } = this.$route.query
  494. if (flightNO && flightDate && bagSN) {
  495. this.queryData = { flightNO, flightDate, bagSN }
  496. this.queryBasicInfo()
  497. this.queryAirline()
  498. this.infoBtn = this.infoRadios[0]
  499. } else {
  500. this.$router.push('/')
  501. }
  502. },
  503. activated() {
  504. this.resizeHandler()
  505. this.debouncedResizeHandler = this._.debounce(this.resizeHandler, this.debounceTime)
  506. window.addEventListener('resize', this.debouncedResizeHandler)
  507. },
  508. updated() {
  509. this.resizeHandler()
  510. },
  511. deactivated() {
  512. this.stopLoopAll()
  513. window.removeEventListener('resize', this.debouncedResizeHandler)
  514. },
  515. methods: {
  516. basicInfoToggle() {
  517. this.basicInfoOpen = !this.basicInfoOpen
  518. },
  519. trackListToggle() {
  520. this.trackListOpen = !this.trackListOpen
  521. },
  522. resizeHandler() {
  523. this.$refs['table']?.doLayout()
  524. },
  525. baggageBasicInfoClickHandler(prop) {
  526. if (prop !== 'compensationSign' || !this.baggageBasicInfo[prop]) {
  527. return
  528. }
  529. this.$store.dispatch('app/setAbnormalBaggageQueryParams', {
  530. flightNO: this.queryData.flightNO,
  531. flightDate: this.queryData.flightDate,
  532. bagSN: this.queryData.bagSN,
  533. fileNumber: this.baggageBasicInfo[prop]
  534. })
  535. this.$store.dispatch('app/toggleAbnormalBaggageDialogFlag', true)
  536. },
  537. startQueryDetails() {
  538. this.queryDetails()
  539. this.queryLoop = setInterval(this.queryDetails.bind(this), LOOP_INTERVAL.baggageDetails)
  540. },
  541. startQueryTrack() {
  542. this.queryTrack()
  543. this.queryTrackLoop = setInterval(this.queryTrack.bind(this), LOOP_INTERVAL.baggageTrack)
  544. },
  545. startQueryMessage() {
  546. this.queryMessage()
  547. this.queryLoop = setInterval(this.queryMessage.bind(this), LOOP_INTERVAL.baggageMessage)
  548. },
  549. stopQueryTrack() {
  550. this.queryTrackLoop && clearInterval(this.queryTrackLoop)
  551. this.queryTrackLoop = null
  552. },
  553. stopLoopAll() {
  554. this.queryLoop && clearInterval(this.queryLoop)
  555. this.queryLoop = null
  556. this.stopQueryTrack()
  557. },
  558. resetStepNodes() {
  559. this.stepNodes = [
  560. {
  561. nodeCode: 'CHECKIN',
  562. nodeName: '值机'
  563. },
  564. {
  565. nodeCode: 'SECURITY',
  566. nodeName: '安检'
  567. },
  568. {
  569. nodeCode: 'SORT',
  570. nodeName: '分拣'
  571. },
  572. {
  573. nodeCode: 'LOAD',
  574. nodeName: '装车'
  575. },
  576. {
  577. nodeCode: 'INFL',
  578. nodeName: '装机'
  579. },
  580. {
  581. nodeCode: 'UNLOAD',
  582. nodeName: '卸机'
  583. },
  584. {
  585. nodeCode: 'ARRIVED',
  586. nodeName: '到达'
  587. }
  588. ]
  589. },
  590. initTableData(tableData) {
  591. const spanArr = []
  592. let pos = 0
  593. for (let i = 0; i < tableData.length; i++) {
  594. if (i === 0) {
  595. spanArr.push(1)
  596. } else {
  597. if (
  598. tableData[i]['flightNO'] === tableData[i - 1]['flightNO'] &&
  599. tableData[i]['flightDate'] === tableData[i - 1]['flightDate'] &&
  600. tableData[i]['departureAirport'] === tableData[i - 1]['departureAirport'] &&
  601. tableData[i]['landingAirport'] === tableData[i - 1]['landingAirport']
  602. ) {
  603. spanArr[pos] += 1
  604. spanArr.push(0)
  605. } else {
  606. spanArr.push(1)
  607. pos = i
  608. }
  609. }
  610. }
  611. this.spanArr = spanArr
  612. this.pos = pos
  613. },
  614. headerCellClass({ row, column, rowIndex, columnIndex }) {
  615. if (['departureInfo', 'landingInfo'].includes(column.property)) {
  616. return 'pre-line'
  617. }
  618. },
  619. cellClass({ row, column, rowIndex, columnIndex }) {
  620. const classes = []
  621. if (
  622. ['flightNO', 'U_Device_ID'].includes(column.property) &&
  623. row[column.property] &&
  624. row[column.property] !== 'FBULK'
  625. ) {
  626. classes.push('cell-click')
  627. }
  628. if (['departureInfo', 'landingInfo'].includes(column.property)) {
  629. classes.push('pre-line')
  630. }
  631. return classes.join(' ')
  632. },
  633. cellClickHandler(row, column, cell, event) {
  634. if (row[column.property] && row[column.property] !== 'FBULK') {
  635. switch (column.property) {
  636. case 'flightNO':
  637. this.$router.push({
  638. path: `${this.$route.path.split('/').slice(0, -1).join('/')}/flightView`,
  639. query: {
  640. flightNO: row.flightNO,
  641. flightDate: row.flightDate
  642. }
  643. })
  644. break
  645. case 'U_Device_ID':
  646. this.$router.push({
  647. path: `${this.$route.path.split('/').slice(0, -1).join('/')}/containerView`,
  648. query: {
  649. flightNO: row.flightNO,
  650. flightDate: row.flightDate,
  651. departureAirport: row.departureAirport,
  652. landingAirport: row.landingAirport,
  653. containerID: row.U_Device_ID
  654. }
  655. })
  656. break
  657. default:
  658. break
  659. }
  660. }
  661. },
  662. tableSpanMethod({ row, column, rowIndex, columnIndex }) {
  663. if (['flightNO', 'flightDate', 'departureInfo', 'landingInfo'].includes(column['property'])) {
  664. const _row = this.spanArr[rowIndex]
  665. const _col = _row > 0 ? 1 : 0
  666. return {
  667. rowspan: _row,
  668. colspan: _col
  669. }
  670. }
  671. },
  672. exportHandler(refName, tableName) {
  673. const table = this.$refs[refName].$el.cloneNode(true)
  674. const { bagSN, flightNO, flightDate } = this.queryData
  675. const fileName = `${tableName}-${bagSN}-${flightNO}-${flightDate}.xlsx`
  676. throttledExportToExcel(table, tableName, fileName)
  677. },
  678. exportMessageToExcel() {
  679. const xlsxDatas = [['Date & Time', 'Message']]
  680. xlsxDatas.push(
  681. ...this.messageList.map(message => [
  682. message.date,
  683. message.dataContent.replaceAll(/[\r\n]{2,}/g, '\n').replaceAll('\\', '')
  684. ])
  685. )
  686. const columnWidths = []
  687. xlsxDatas.forEach(row => {
  688. // 计算每一列宽度,考虑换行
  689. row.forEach((cell, columnIndex) => {
  690. const cellWidth = Math.max(
  691. ...cell
  692. .toString()
  693. .split('\n')
  694. .map(cellRow =>
  695. cellRow.split('').reduce((pre, curr) => {
  696. const letterSize = curr.charCodeAt(0) > 255 ? 2 : 1
  697. return pre + letterSize
  698. }, 0)
  699. )
  700. )
  701. if ((!columnWidths[columnIndex] && cellWidth > 0) || cellWidth > columnWidths[columnIndex]) {
  702. columnWidths[columnIndex] = cellWidth
  703. }
  704. })
  705. })
  706. // 生成表格数据
  707. const sheet = XLSX.utils.aoa_to_sheet(xlsxDatas)
  708. // 添加列宽度
  709. sheet['!cols'] = columnWidths.map(width => ({
  710. wch: width + 2
  711. }))
  712. // 表格对齐、添加边框
  713. const borderStyle = {
  714. style: 'medium',
  715. color: {
  716. rgb: 'FFFFFF'
  717. }
  718. }
  719. const reg = /^[A-Z]+([\d]+$)/
  720. for (const key in sheet) {
  721. const match = reg.test(key)
  722. if (match) {
  723. const rowIndex = reg.exec(key)[1]
  724. let cellStyle = {
  725. alignment: {
  726. horizontal: 'center',
  727. vertical: 'center',
  728. wrapText: true
  729. }
  730. }
  731. if (Number(rowIndex) === 1) {
  732. cellStyle = {
  733. ...cellStyle,
  734. border: {
  735. top: borderStyle,
  736. right: borderStyle,
  737. bottom: borderStyle,
  738. left: borderStyle
  739. },
  740. font: {
  741. color: {
  742. rgb: 'FFFFFF'
  743. }
  744. },
  745. fill: {
  746. fgColor: {
  747. rgb: '3366FF'
  748. }
  749. }
  750. }
  751. } else {
  752. cellStyle.alignment.horizontal = 'left'
  753. }
  754. sheet[key].s = cellStyle
  755. }
  756. }
  757. // 表格数据转换
  758. const workBook = XLSX.utils.book_new()
  759. XLSX.utils.book_append_sheet(workBook, sheet, '行李原始报文')
  760. const tableWrite = XLSX_STYLE.write(workBook, {
  761. bookType: 'xlsx',
  762. bookSST: true,
  763. type: 'buffer',
  764. cellStyles: true
  765. })
  766. // 下载表格
  767. const { bagSN, flightNO, flightDate } = this.queryData
  768. const fileName = `行李原始报文-${bagSN}-${flightNO}-${flightDate}.xlsx`
  769. FileSaver.saveAs(new Blob([tableWrite], { type: 'application/octet-stream' }), fileName)
  770. },
  771. // 行李详情基础信息
  772. queryBaggageBasicInfo(dataContent) {
  773. return myQuery(DATACONTENT_ID.baggageBasicInfo, ...dataContent)
  774. },
  775. // 行李航段
  776. queryBaggageAirline(dataContent) {
  777. return myQuery(DATACONTENT_ID.baggageAirline, ...dataContent)
  778. },
  779. // 行李详情追踪链
  780. queryBaggageTrack(dataContent) {
  781. return myQuery(DATACONTENT_ID.baggageTrack, ...dataContent)
  782. },
  783. // 行李详情表格
  784. queryBaggageDetails(dataContent) {
  785. return myQuery(DATACONTENT_ID.baggageDetails, ...dataContent)
  786. },
  787. // 原始报文
  788. async queryBaggageMessage(dataContent) {
  789. try {
  790. const { code, returnData, message } = await BaggageMessageQuery({
  791. id: DATACONTENT_ID.baggageMessage,
  792. dataContent
  793. })
  794. if (Number(code) === 0) {
  795. return returnData.listValues
  796. } else {
  797. this.$message.error(message ?? '失败')
  798. }
  799. } catch (error) {
  800. this.$message.error('失败')
  801. }
  802. },
  803. async queryBasicInfo(queryData = this.queryData) {
  804. const { flightNO, flightDate, bagSN } = queryData
  805. const dataContent = [flightNO, flightDate, bagSN]
  806. try {
  807. const baggageBasicInfo = await this.queryBaggageBasicInfo(dataContent)
  808. if (baggageBasicInfo.length) {
  809. baggageBasicInfo[0].flightNO = flightNO
  810. this.baggageBasicInfo = baggageBasicInfo[0]
  811. }
  812. } catch (error) {
  813. this.$message.error('失败')
  814. }
  815. },
  816. async queryDetails(queryData = this.queryData) {
  817. function setDataSource(item) {
  818. if (item['b_type'] !== 'BSM') {
  819. const resourceCode = item['resourceFile']?.slice(-4)
  820. switch (resourceCode) {
  821. case '0100':
  822. if (
  823. (item['DeviceCode'] && item['DeviceCode'].toUpperCase() === 'STARHUB') ||
  824. (!item['DeviceCode'] && !['LOAD', 'INFL'].includes(item['nodeCode']))
  825. ) {
  826. item['dataSource'] = '星盟'
  827. } else {
  828. item['dataSource'] = 'BRS'
  829. }
  830. break
  831. case '0101':
  832. item['dataSource'] = 'RFID'
  833. break
  834. case '0102':
  835. item['dataSource'] = '首都机场'
  836. break
  837. default:
  838. break
  839. }
  840. }
  841. }
  842. const { flightNO, flightDate, bagSN } = queryData
  843. const dataContent = [flightNO, flightDate, bagSN]
  844. try {
  845. const baggageDetails = await this.queryBaggageDetails(new Array(6).fill(dataContent).flat())
  846. this.baggageTableData = baggageDetails.map((item, index) => {
  847. if (item['dealTime']) {
  848. item['dealTime'] = item['dealTime'].replace('T', ' ')
  849. }
  850. item['departureInfo'] = `${item['departureAirport']}\n${
  851. item['departureTime'] ? item['departureTime'].replace('T', '\n') : ''
  852. }`
  853. item['landingInfo'] = `${item['landingAirport']}\n${
  854. item['landingTime'] ? item['landingTime'].replace('T', '\n') : ''
  855. }`
  856. setDataSource(item)
  857. return item
  858. })
  859. this.initTableData(this.baggageTableData)
  860. } catch (error) {
  861. this.$message.error('失败')
  862. }
  863. },
  864. async queryAirline(queryData = this.queryData) {
  865. const { flightNO, flightDate, bagSN } = queryData
  866. const dataContent = [flightNO, flightDate, bagSN]
  867. try {
  868. const result = await this.queryBaggageAirline(dataContent)
  869. this.airlineList = result.map(({ flightNO, flightDate, departureAirport, arriveAirport, luggageSN }) => ({
  870. label: `${departureAirport}-${arriveAirport}`,
  871. value: `${flightNO},${flightDate},${luggageSN}`,
  872. flightNO,
  873. flightDate
  874. }))
  875. if (this.airlineList.length) {
  876. let currentIndex = this.airlineList.findIndex(
  877. ({ flightNO, flightDate }) => flightNO === queryData.flightNO && flightDate === queryData.flightDate
  878. )
  879. currentIndex = currentIndex > -1 ? currentIndex : 0
  880. this.selectedAirline = this.airlineList[currentIndex].value
  881. } else {
  882. this.selectedAirline = ''
  883. }
  884. } catch (error) {
  885. this.$message.error('失败')
  886. }
  887. },
  888. async queryTrack() {
  889. function isSameStep(code1, code2) {
  890. const sameStepCodes = ['ARRIVED', 'TRANSFER']
  891. return sameStepCodes.includes(code1) && sameStepCodes.includes(code2)
  892. }
  893. try {
  894. const result = await this.queryBaggageTrack(this.selectedAirline.split(','))
  895. this.resetStepNodes()
  896. result.forEach(({ nodeCode, nodeName, processingTime, locationId, status }) => {
  897. const replaceIndex = this.stepNodes.findIndex(
  898. stepNode => stepNode.nodeCode === nodeCode || isSameStep(stepNode.nodeCode, nodeCode)
  899. )
  900. if (replaceIndex > -1) {
  901. this.stepNodes.splice(replaceIndex, 1, {
  902. nodeCode,
  903. nodeName,
  904. processingTime: processingTime?.replace('T', '\n'),
  905. locationId,
  906. status
  907. })
  908. }
  909. })
  910. } catch (error) {
  911. this.$message.error('失败')
  912. }
  913. },
  914. async queryMessage(queryData = this.queryData) {
  915. const { flightNO, flightDate, bagSN } = queryData
  916. const dataContent = [flightNO, flightDate, bagSN]
  917. try {
  918. const result = await this.queryBaggageMessage(dataContent)
  919. const messageList = result.reduce((list, message, currentIndex, arr) => {
  920. const messageObject = typeof message === 'string' ? JSON.parse(message) : message
  921. // 确保是当前行李的报文
  922. const isCorrectBaggage = messageObject.dataContent.match(/\.N\/([0-9]{10})/)?.[1] === bagSN
  923. // 相同报文去重
  924. const index = arr.findIndex(testMessage => {
  925. const testMessageObject = typeof testMessage === 'string' ? JSON.parse(testMessage) : testMessage
  926. return (
  927. messageObject.dataContent === testMessageObject.dataContent &&
  928. messageObject.ssid === testMessageObject.ssid
  929. )
  930. })
  931. if (isCorrectBaggage && currentIndex === index) {
  932. return [...list, messageObject]
  933. } else {
  934. return list
  935. }
  936. }, [])
  937. this.messageList = this._.sortBy(messageList, 'ssid')
  938. } catch (error) {
  939. this.$message.error('失败')
  940. }
  941. }
  942. }
  943. }
  944. </script>
  945. <style
  946. lang="scss"
  947. scoped
  948. >
  949. .baggage-view {
  950. width: 100%;
  951. height: calc(100vh - 80px);
  952. overflow: hidden;
  953. background: #dfe3ea;
  954. padding: 8px 8px 0;
  955. .part1 {
  956. width: 100%;
  957. // height: 232px;
  958. background: #041741;
  959. padding: 16px 30px;
  960. overflow: hidden;
  961. position: relative;
  962. ::v-deep .title {
  963. font-size: 18px;
  964. font-weight: bold;
  965. color: #ffffff;
  966. display: flex;
  967. flex-direction: row;
  968. align-items: center;
  969. .radioBtn {
  970. margin-left: 32px;
  971. padding: 5px;
  972. background: #000d2a;
  973. .el-radio-button__inner {
  974. background: #000d2a;
  975. color: #fff;
  976. border: none;
  977. font-weight: bold;
  978. }
  979. .el-radio-button:first-child .el-radio-button__inner {
  980. border: none;
  981. }
  982. }
  983. .btn-back {
  984. color: #ffffff;
  985. }
  986. }
  987. .part1_info {
  988. width: 100%;
  989. color: #fff;
  990. font-size: 14px;
  991. font-weight: 400;
  992. color: #ffffff;
  993. > .el-row > .el-col {
  994. height: 38px;
  995. line-height: 38px;
  996. display: flex;
  997. .label {
  998. flex-basis: 126px;
  999. text-align: right;
  1000. }
  1001. .content {
  1002. flex: 1;
  1003. margin: 0;
  1004. white-space: nowrap;
  1005. overflow: hidden;
  1006. text-overflow: ellipsis;
  1007. &.click {
  1008. text-decoration: underline;
  1009. cursor: pointer;
  1010. }
  1011. }
  1012. }
  1013. }
  1014. }
  1015. .part2 {
  1016. margin: 8px 0;
  1017. width: 100%;
  1018. padding: 24px 30px 28px;
  1019. overflow: hidden;
  1020. background: #ffffff;
  1021. display: flex;
  1022. flex-direction: row;
  1023. justify-content: space-between;
  1024. align-items: flex-start;
  1025. position: relative;
  1026. .part2_info {
  1027. flex: 1;
  1028. display: flex;
  1029. flex-direction: row;
  1030. justify-content: flex-start;
  1031. align-items: flex-start;
  1032. line-height: 42px;
  1033. .title {
  1034. width: 120px;
  1035. font-size: 18px;
  1036. font-weight: bold;
  1037. color: #303133;
  1038. margin-right: 20px;
  1039. }
  1040. .type {
  1041. font-size: 18px;
  1042. font-weight: bold;
  1043. margin-right: 20px;
  1044. .warn {
  1045. color: #df3559;
  1046. }
  1047. .normal {
  1048. color: #519f6b;
  1049. }
  1050. }
  1051. .airline {
  1052. width: 120px;
  1053. margin-right: 20px;
  1054. }
  1055. .baggage-track-chart {
  1056. flex: 1;
  1057. height: 124px;
  1058. max-width: 1280px;
  1059. position: relative;
  1060. display: flex;
  1061. flex-direction: row;
  1062. justify-content: space-between;
  1063. width: 100%;
  1064. }
  1065. .step-line {
  1066. width: calc(100% - 80px);
  1067. height: 10px;
  1068. position: absolute;
  1069. top: 16px;
  1070. right: 0;
  1071. left: 0;
  1072. margin: auto;
  1073. display: flex;
  1074. .step-line-segment {
  1075. width: calc(100% / 6);
  1076. height: 100%;
  1077. background: #afb4bf;
  1078. &.step-line-active {
  1079. background: #2d67e3;
  1080. }
  1081. }
  1082. }
  1083. .step-item {
  1084. width: 80px;
  1085. height: 100%;
  1086. text-align: center;
  1087. font-size: 14px;
  1088. display: flex;
  1089. flex-direction: column;
  1090. align-items: center;
  1091. justify-content: flex-start;
  1092. z-index: 1;
  1093. font-family: Helvetica, 'Microsoft Yahei';
  1094. .step-circle {
  1095. width: 42px;
  1096. height: 42px;
  1097. border-radius: 50%;
  1098. background: #aaacb2;
  1099. .step-name {
  1100. color: #ffffff;
  1101. font-size: 14px;
  1102. font-weight: bold;
  1103. }
  1104. }
  1105. .step-info {
  1106. margin-top: 15px;
  1107. color: #101116;
  1108. line-height: 22px;
  1109. .step-status {
  1110. &-normal {
  1111. color: #4ab36f;
  1112. }
  1113. &-abnormal {
  1114. color: #e9af4b;
  1115. }
  1116. }
  1117. .step-time {
  1118. white-space: pre-line;
  1119. font-size: 12px;
  1120. line-height: 20px;
  1121. }
  1122. }
  1123. &.active-item .step-circle {
  1124. background: #2d67e3;
  1125. }
  1126. }
  1127. }
  1128. .btns {
  1129. margin-top: 6px;
  1130. }
  1131. }
  1132. .part3 {
  1133. width: 100%;
  1134. // header-80px、part1-232px、part2-128px、间隙3*8px、底部44px
  1135. // height: calc(100vh - 80px - 232px - 128px - 3 * 8px - 44px);
  1136. background: #ffffff;
  1137. ::v-deep .el-table {
  1138. width: 100%;
  1139. // &.el-table--striped {
  1140. // .el-table__body tr.el-table__row--striped td.el-table__cell,
  1141. // .el-table__header .el-table__cell {
  1142. // background: #ffffff;
  1143. // }
  1144. // }
  1145. .el-table__cell {
  1146. // background: #f0f3f7;
  1147. padding: 0;
  1148. &.cell-click {
  1149. cursor: pointer;
  1150. .cell {
  1151. color: #2d7cff;
  1152. }
  1153. }
  1154. .cell {
  1155. padding: 0;
  1156. word-spacing: 0;
  1157. font-size: 14px;
  1158. font-family: Helvetica, 'Microsoft YaHei';
  1159. font-weight: 400;
  1160. color: #303133;
  1161. width: 100% !important;
  1162. .cell-content {
  1163. padding: 6px 0;
  1164. }
  1165. }
  1166. }
  1167. .el-table__body .el-table__cell .cell {
  1168. padding: 6px 10px;
  1169. .cell-content {
  1170. display: inline;
  1171. padding: 0;
  1172. }
  1173. }
  1174. }
  1175. }
  1176. .part4 {
  1177. width: 100%;
  1178. // height: calc(100vh - 80px - 232px - 2 * 8px - 44px);
  1179. .head {
  1180. padding: 16px 24px 11px 30px;
  1181. background: transparent;
  1182. display: flex;
  1183. justify-content: space-between;
  1184. .title {
  1185. line-height: 30px;
  1186. font-size: 18px;
  1187. font-weight: bold;
  1188. color: #303133;
  1189. }
  1190. }
  1191. .main {
  1192. height: calc(100% - 57px);
  1193. overflow-y: auto;
  1194. overflow-x: hidden;
  1195. ::v-deep .el-row {
  1196. flex-wrap: wrap;
  1197. .card {
  1198. width: 100%;
  1199. min-height: 440px;
  1200. padding: 20px;
  1201. background: #ffffff;
  1202. box-shadow: 0px 3px 2px 0px rgba(0, 0, 0, 0.29);
  1203. margin-bottom: 24px;
  1204. > .message-date {
  1205. width: 180px;
  1206. height: 26px;
  1207. line-height: 14px;
  1208. font-size: 14px;
  1209. font-family: Helvetica;
  1210. color: #afb4bf;
  1211. border-bottom: 1px solid #afb4bf;
  1212. margin-bottom: 18px;
  1213. }
  1214. > .message-content {
  1215. white-space: pre-line;
  1216. line-height: 24px;
  1217. font-size: 14px;
  1218. color: #303133;
  1219. word-break: break-all;
  1220. }
  1221. }
  1222. }
  1223. }
  1224. }
  1225. .btns {
  1226. height: 30px;
  1227. display: flex;
  1228. .btn-square {
  1229. margin-left: 10px;
  1230. width: 30px;
  1231. }
  1232. }
  1233. .button-toggle {
  1234. position: absolute;
  1235. bottom: 0;
  1236. left: 50%;
  1237. transform: translateX(-50%);
  1238. z-index: 1000;
  1239. width: 20px;
  1240. height: 15px;
  1241. border-radius: 4px;
  1242. background-color: #2d67e3;
  1243. color: #ffffff;
  1244. cursor: pointer;
  1245. text-align: center;
  1246. }
  1247. }
  1248. </style>
  1249. <style
  1250. scoped
  1251. lang="scss"
  1252. >
  1253. ::v-deep .baggage-view {
  1254. .el-popover {
  1255. &.popover-dark {
  1256. background: #303133;
  1257. color: #ffffff;
  1258. border: none;
  1259. }
  1260. .pre-line {
  1261. white-space: pre-line;
  1262. }
  1263. }
  1264. .el-popper[x-placement^='top'].popover-dark .popper__arrow::after {
  1265. border-top-color: #303133;
  1266. }
  1267. .el-popper[x-placement^='right'].popover-dark .popper__arrow::after {
  1268. border-right-color: #303133;
  1269. }
  1270. .el-popper[x-placement^='bottom'].popover-dark .popper__arrow::after {
  1271. border-bottom-color: #303133;
  1272. }
  1273. .el-popper[x-placement^='left'].popover-dark .popper__arrow::after {
  1274. border-left-color: #303133;
  1275. }
  1276. }
  1277. </style>