zhaoke 2 年之前
父节点
当前提交
978faa9cdf

+ 29 - 44
src/router/routes/routes-file-five.js

@@ -282,67 +282,51 @@ const transferRoutes = {
   ]
 }
 
-// 高级查询
-const advanceRoutes = {
-  path: '/',
-  redirect: '/advance',
+const flightViewRoutes = {
+  path: '/flightView',
+  name: 'flightView',
   component: Layout,
+  hidden: true,
   children: [
     {
-      path: '/advance',
-      component: () => import('@/views/advancedQuery'),
-      meta: {
-        title: '高级查询',
-        imgstyle: 'ic_list_nav_search_default.png',
-        imgstyleup: 'ic_list_nav_search_check.png'
-      },
+      path: '/flightView',
+      name: 'ArrivalNav',
+      component: () => import('@/views/flightViewManagement'),
       children: [
         {
-          path: '/advance',
-          name: 'AdvancedQuery',
-          component: () => import('@/views/advancedQuery'),
-          meta: { title: '高级查询', isPage: 'advance_page', keepAlive: true },
+          path: '/flightView',
+          name: 'ArrivalFlightNav',
+          component: () => import('@/views/flightViewManagement'),
+          meta: { title: '航班视图', keepAlive: true },
           children: [
             {
-              path: '/advance',
-              name: 'AdvancedHome',
-              component: () => import('@/views/advancedQuery/views/advancedHome.vue'),
-              meta: { keepAlive: true }
-            },
-            {
-              path: 'advanceNew',
-              name: 'AdvancedNew',
-              component: () => import('@/views/advancedQuery/views/advancedNew.vue'),
+              path: '/flightView',
+              name: 'ArrivalFlightView',
+              component: () => import('@/views/flightViewManagement/components/arrival/flight'),
               meta: { keepAlive: true }
             },
             {
-              path: 'flightView',
-              name: 'AdvancedFlight',
-              component: () => import('@/views/advancedQuery/views/advancedFlight.vue'),
-              meta: { title: '航班视图', keepAlive: true }
-            },
-            {
-              path: 'baggageView',
-              name: 'AdvancedBag',
-              component: () => import('@/views/advancedQuery/views/advancedBag.vue'),
-              meta: { title: '行李视图', keepAlive: true }
+              path: '/flightView/baggageView',
+              name: 'ArrivalBaggageView',
+              component: () => import('@/views/flightViewManagement/components/arrival/baggage'),
+              meta: { title: '行李视图' }
             },
             {
-              path: 'containerView',
-              name: 'AdvancedContainerNav',
-              component: () => import('@/views/advancedQuery'),
+              path: '/flightView/containerView',
+              name: 'ArrivalContainerNav',
+              component: () => import('@/views/flightViewManagement'),
               meta: { title: '容器视图', keepAlive: true },
               children: [
                 {
-                  path: '/advance/containerView',
-                  name: 'AdvancedContainerView',
-                  component: () => import('@/views/advancedQuery/views/advancedContainer.vue'),
+                  path: '/flightView/containerView',
+                  name: 'ArrivalContainerView',
+                  component: () => import('@/views/flightViewManagement/components/arrival/container'),
                   meta: { keepAlive: true }
                 },
                 {
-                  path: '/advance/containerHistory',
-                  name: 'AdvancedContainerHistory',
-                  component: () => import('@/views/advancedQuery/views/advancedContainerHistory.vue'),
+                  path: '/flightView/containerHistory',
+                  name: 'ArrivalContainerHistory',
+                  component: () => import('@/views/flightViewManagement/components/arrival/containerHistory'),
                   meta: { title: '容器历史', keepAlive: true }
                 }
               ]
@@ -354,4 +338,5 @@ const advanceRoutes = {
   ]
 }
 
-export default [advanceRoutes, departureRoutes, arrivalRoutes, transferRoutes]
+
+export default [flightViewRoutes, departureRoutes, arrivalRoutes, transferRoutes]

+ 24 - 0
src/views/flightViewManagement/components/arrival/baggage.vue

@@ -0,0 +1,24 @@
+<!--
+ * @Author: Badguy
+ * @Date: 2022-03-09 11:51:26
+ * @LastEditTime: 2022-03-09 11:51:26
+ * @LastEditors: your name
+ * @Description: 进港行李视图
+ * have a nice day!
+-->
+
+<template>
+  <BaggageView />
+</template>
+
+<script>
+import BaggageView from '../baggage'
+
+export default {
+  name: 'ArrivalBaggageView',
+  components: {
+    BaggageView
+  }
+}
+</script>
+

+ 23 - 0
src/views/flightViewManagement/components/arrival/container.vue

@@ -0,0 +1,23 @@
+<!--
+ * @Author: Badguy
+ * @Date: 2022-03-09 11:51:26
+ * @LastEditTime: 2022-03-09 11:51:26
+ * @LastEditors: your name
+ * @Description: 进港容器视图
+ * have a nice day!
+-->
+
+<template>
+  <ContainerView />
+</template>
+
+<script>
+import ContainerView from '../container'
+
+export default {
+  name: 'ArrivalContainerView',
+  components: {
+    ContainerView
+  }
+}
+</script>

+ 14 - 0
src/views/flightViewManagement/components/arrival/containerHistory.vue

@@ -0,0 +1,14 @@
+<template>
+  <ContainerHistory />
+</template>
+
+<script>
+import ContainerHistory from '../containerHistory'
+
+export default {
+  name: 'ArrivalContainerHistory',
+  components: {
+    ContainerHistory
+  }
+}
+</script>

+ 24 - 0
src/views/flightViewManagement/components/arrival/flight.vue

@@ -0,0 +1,24 @@
+<!--
+ * @Author: Badguy
+ * @Date: 2022-03-09 11:49:13
+ * @LastEditTime: 2022-03-09 11:50:43
+ * @LastEditors: your name
+ * @Description: 进港行李视图
+ * have a nice day!
+-->
+
+<template>
+  <FlightView />
+</template>
+
+<script>
+import FlightView from '../flight'
+
+export default {
+  name: 'ArrivalFlightView',
+  components: {
+    FlightView
+  }
+}
+</script>
+

+ 738 - 0
src/views/flightViewManagement/components/arrival/index.vue

@@ -0,0 +1,738 @@
+<!--
+ * @Author: zk
+ * @Date: 2022-01-17 10:39:22
+ * @LastEditTime: 2022-06-21 17:52:33
+ * @LastEditors: your name
+ * @Description: 进港01
+-->
+<template>
+  <div class="arrival-one">
+    <!--功能区-表单-->
+    <div ref="formWrap" class="terminal-form-wrap">
+      <el-form ref="form" :inline="true" :model="formData" :rules="rules" class="form">
+        <div class="form-left">
+          <el-form-item prop="currentAirport">
+            <!-- <el-cascader
+            v-model="formData.currentAirport"
+            style="width:144px;margin-left:10px"
+            placeholder="全部机场"
+            size="small"
+            :options="currentAirportList"
+            :props="currentAirportProps"
+            collapse-tags
+            clearable
+            filterable
+            @change="setCurrentAirport"
+          /> -->
+            <el-select v-model="formData.currentAirport" class="input-shadow" size="small" style="width: 150px" filterable default-first-option placeholder="请选择机场" @change="airPortChange">
+              <el-option v-for="(item, index) in AirportList" :key="index" :label="item.planLandingApt" :value="item.planLandingApt" />
+            </el-select>
+          </el-form-item>
+          <!-- <el-form-item prop="startDate">
+            <el-date-picker
+              v-model="formData.startDate"
+              class="input-shadow"
+              style="width: 216px"
+              size="small"
+              type="date"
+              value-format="yyyy-MM-dd"
+              placeholder="开始时间"
+              @change="startDateChangeHandler"
+            />
+          </el-form-item>
+          <el-form-item prop="endDate">
+            <el-date-picker
+              v-model="formData.endDate"
+              class="input-shadow"
+              style="width: 216px"
+              size="small"
+              type="date"
+              value-format="yyyy-MM-dd"
+              placeholder="结束时间"
+              @change="endDateChangeHandler"
+            />
+          </el-form-item> -->
+          <el-form-item prop="flightDate" label="航班日期">
+            <el-date-picker v-model="formData.flightDate" :clearable="false" size="small" style="width: 300px" type="daterange" value-format="yyyy-MM-dd" start-placeholder="开始日期" end-placeholder="结束日期" :picker-options="dateRangePickerOptions" @change="dateChangeHandler" />
+          </el-form-item>
+          <el-form-item>
+            <div class="box-item">
+              <p>预计装载总数:</p>
+              <li v-for="(item, index) in orderNum" :key="index" :class="{ 'number-item': !isNaN(item), 'mark-item': isNaN(item) }">
+                <span v-if="!isNaN(item)">
+                  <i ref="numberItem">0123456789</i>
+                </span>
+                <span v-else class="comma">{{ item }}</span>
+              </li>
+            </div>
+          </el-form-item>
+        </div>
+        <div class="form-right" @keyup.enter="onSubmit(1)">
+          <el-form-item prop="search">
+            <el-popover :value="popoverVisible" placement="bottom" trigger="manual">
+              <span>请输入航班号(示例:CA1234)或行李牌号(示例:1234567890)</span>
+              <el-input slot="reference" v-model="formData.search" class="input-shadow" style="width: 240px; margin-left: 105px" size="small" placeholder="请输入内容" prefix-icon="el-icon-search" clearable @focus="popoverVisible = true" @blur="popoverVisible = false" />
+            </el-popover>
+          </el-form-item>
+          <el-form-item>
+            <el-button class="btn-shadow" size="mini" type="primary" @click="onSubmit(1)">搜索</el-button>
+          </el-form-item>
+          <!-- <el-form-item v-is="['is_timeIcon']">
+            <TimeZoneSelector />
+          </el-form-item> -->
+          <el-form-item>
+            <TimeZoneSelector />
+          </el-form-item>
+          <!-- <el-form-item v-is="['is_columnSettings']">
+            <img class="btn-img btn-shadow" src="@/assets/baggage/ic_setting.png" title="列设置" @click="show" />
+          </el-form-item> -->
+          <el-form-item>
+            <img class="btn-img btn-shadow" src="@/assets/baggage/ic_setting.png" title="列设置" @click="show" />
+          </el-form-item>
+          <el-form-item v-is="['dm_dt_columnSettings']">
+            <img class="btn-img btn-shadow" src="@/assets/baggage/ic_export.png" title="导出" @click="exportHandler('table', '航站进港列表')" />
+          </el-form-item>
+        </div>
+      </el-form>
+    </div>
+    <!--表格-->
+    <div v-loading="loading" class="terminal-table" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading" element-loading-background="rgba(0, 0, 0, 0.8)">
+      <el-table ref="table" class="table" :height="computedTableHeight" :data="dealedTableData" :header-cell-class-name="headerCellClass" :row-class-name="tableRowClassName" :cell-class-name="cellClass" show-summary :summary-method="summaryMethod" border stripe fit @cell-click="cellClickHandler">
+        <el-table-column v-for="col in tableColsCopy" :key="col.prop" :prop="col.prop" :label="col.label" :width="col.width" :fixed="col.fixed">
+          <el-table-column v-for="childCol in col.children" :key="childCol.prop" :prop="childCol.prop" :label="childCol.label" :width="childCol.width" :formatter="tableFormat">
+            <template #header>
+              <el-tooltip :content="childCol.desc || childCol.label" placement="top">
+                <TableHeaderCell :label="childCol.label" :filter-options="tableDataFilters[childCol.prop]" :filter-values.sync="filterValues[childCol.prop]" :sortable="childCol.sortable" :sort-rule.sync="tableDataSortRules[childCol.prop]" />
+              </el-tooltip>
+            </template>
+          </el-table-column>
+        </el-table-column>
+      </el-table>
+    </div>
+    <!--列设置-->
+    <Dialog :flag="dialogFlag" class="dialog-check-group">
+      <div class="dialog-wrapper">
+        <div class="title">列设置</div>
+        <div class="content">
+          <el-tree ref="columnSetTree" :data="tableCols" :class="colsCheckClass" show-checkbox node-key="index" :default-expand-all="true" :props="{
+              label: 'label',
+              children: 'children',
+            }" :default-checked-keys="checkedKeysTemp" @check="handleCheck" />
+        </div>
+        <div class="foot right t30">
+          <el-button size="medium" class="r24" type="primary" @click="onCheck">确定</el-button>
+          <el-button size="medium" @click="hide">取消</el-button>
+        </div>
+      </div>
+    </Dialog>
+  </div>
+</template>
+
+<script>
+import Dialog from "@/layout/components/Dialog";
+import TimeZoneSelector from "@/components/TimeZoneSelector";
+import terminalMixin from "../../mixins/terminal";
+import formMixin from "../../mixins/form";
+import tableColsMixin from "../../mixins/tableCols";
+import timeZoneMixin from "../../mixins/timeZone";
+import { getQuery } from "@/api/flight";
+import TableHeaderCell from "@/components/TableHeaderCell";
+import { setTableFilters, throttledExportToExcel } from "@/utils/table";
+import { getToken } from '@/utils/auth';
+
+export default {
+  name: "DepartureTerminalView",
+  components: { Dialog, TimeZoneSelector, TableHeaderCell },
+  mixins: [terminalMixin, formMixin, tableColsMixin, timeZoneMixin],
+  data () {
+    return {
+      orderNum: ["0", "0", "0", "0", "0", "0"], // 默认总数
+      popoverVisible: false,
+      // 初始表头
+      tableCols: [
+        {
+          prop: "flightInfo",
+          label: "航班信息",
+          width: 185,
+          fixed: "left",
+          children: [
+            {
+              prop: "flightNO",
+              label: "航班号",
+              desc: "指航班编号",
+              width: 80,
+              filterable: true,
+              sortable: true,
+            },
+            {
+              prop: "flightDate",
+              label: "执飞日期",
+              desc: "指航班计划起飞日期(不变的,机票上),不是预计起飞日期(预计起飞时间可能多个),也不是实际起飞日期(实际起飞等于最后预计)",
+              width: 105,
+              filterable: true,
+              sortable: true,
+            },
+            {
+              prop: "arrivalTime",
+              label: "降落时间",
+              desc: "根据优先级别显示时间。优先级别:1.实际降落时间,2.预计降落时间,3.计划降落时间",
+              width: 150,
+              filterable: true,
+              sortable: true,
+            },
+            {
+              prop: "departureAirport",
+              label: "起飞航站",
+              desc: "指航班执飞航段的起飞航站,以航站三字码显示",
+              width: 85,
+              filterable: true,
+              sortable: true,
+            },
+            {
+              prop: "arrivalTerminal",
+              label: "到达航站楼",
+              desc: "指航班执飞航段的目的航站的航站楼代码",
+              width: 100,
+              filterable: true,
+              sortable: true,
+            },
+            {
+              prop: "luggageCarousel",
+              label: "行李转盘",
+              desc: "指航班进港,旅客提取行李转盘的代码",
+              width: 85,
+              filterable: true,
+              sortable: true,
+            },
+            {
+              prop: "parkingSpace",
+              label: "停机位",
+              desc: "指航班的停机位代码,数据是变化的,仅显示最新信息",
+              filterable: true,
+              sortable: true,
+            },
+          ],
+        },
+        {
+          prop: "originAirportBaggageInfo",
+          label: "始飞站行李信息",
+          children: [
+            {
+              prop: "checkIns",
+              label: "值机",
+              desc: "指已办理值机托运的行李数量,含取消托运的行李数量,含未激活",
+            },
+            {
+              prop: "projectedLoad",
+              label: "预计装载",
+              desc: "指已办理值机托运的行李数量,不含取消托运的行李数量,不包含未激活",
+            },
+            {
+              prop: "loadedQuantity",
+              label: "已装载",
+              desc: "指实际装机完成的行李数量,不包含取消托运的行李数量",
+            },
+          ],
+        },
+        {
+          prop: "arrvivalBaggageInfo",
+          label: "到达行李信息",
+          children: [
+            {
+              prop: "numberOfDestinationArrivals",
+              label: "到达",
+              desc: "指行李到达提取转盘的行李数量,数据是变化的,仅显示最新信息",
+            },
+            {
+              prop: "endPointNotReached",
+              label: "未到达",
+              desc: "指行李仍未到达提取转盘的行李数量,数据是变化的,仅显示最新信息",
+            },
+            {
+              prop: "specialQuantity",
+              label: "特殊",
+              desc: "指非正常行李的数量,包括(装笼动物、乘务员行李、易碎行李、VIP 行李等),参考 BSM 报文.E 项",
+              width: 65,
+            },
+            {
+              prop: "numberOfClaims",
+              label: "理赔",
+              desc: "指航班收到旅客申请理赔的行李数量",
+              width: 65,
+            },
+          ],
+        },
+        {
+          prop: "uninstallInfo",
+          label: "卸载状态",
+          children: [
+            {
+              prop: "uninstalled",
+              label: "已卸载",
+              desc: "指卸机的行李数量,数据是变化的,仅显示最新信息",
+            },
+            {
+              prop: "numberToBeUninstalled",
+              label: "待卸载",
+              desc: "指仍未卸机的行李数量,数据是变化的,仅显示最新信息",
+            },
+          ],
+        },
+        {
+          prop: "terminationdBaggageInfo",
+          label: "终止行李",
+          children: [
+            {
+              prop: "terminateArrivalQuantity",
+              label: "到达",
+              desc: "指旅客已到达目的站的行李数量,数据是变化的,仅显示最新信息",
+            },
+            {
+              prop: "terminateUnreachedQuantity",
+              label: "未到达",
+              desc: "指未到达目的站的行李数量,数据是变化的,仅显示最新信息",
+            },
+          ],
+        },
+        {
+          prop: "transferBaggageInfo",
+          label: "转运行李",
+          children: [
+            {
+              prop: "quantityShipped",
+              label: "已转运",
+              desc: "指当前航班中转出的行李已完成转运的行李数量,数据是变化的,仅显示最新信息",
+            },
+            {
+              prop: "undeliveredQuantity",
+              label: "未转运",
+              desc: "指当前航班中转出的行李未完成转运的行李数量,数据是变化的,仅显示最新信息",
+            },
+          ],
+        },
+        {
+          prop: "baggageDistributionInfo",
+          label: "行李分布",
+          children: [
+            {
+              prop: "numberOfContainers",
+              label: "容器",
+              desc: "指当前航班使用容器装载的行李数量,数据是变化的,仅显示最新信息",
+            },
+            {
+              prop: "numberOfBulk",
+              label: "散装",
+              desc: "指当前航班没有使用容器装载的行李数量,数据是变化的,仅显示最新信息",
+              width: 65,
+            },
+          ],
+        },
+      ],
+      tableDataSortRules: {
+        flightCanceled: "ascending",
+      },
+      loading: false,
+      AirportList: [],
+      loopEvent: null,
+      arrivalCount: 0,
+      baggageCount: 0,
+      hasSetTableScroll: false,
+      table: null,
+    };
+  },
+  computed: {
+    singleDay () {
+      return this.startDate === this.endDate;
+    },
+  },
+  mounted () {
+    this.getAirPortData();
+    this.table = this.$refs.table.bodyWrapper;
+    const that = this;
+    this.table.addEventListener("scroll", () => {
+      that.scrollTop = this.table.scrollTop;
+    });
+  },
+  activated () {
+    this.table.scrollTop = this.scrollTop;
+    this.getTableData();
+    this.loopEvent = setInterval(this.getTableData, LOOP_INTERVAL.arrivalTable);
+  },
+  deactivated () {
+    if (this.loopEvent) {
+      clearInterval(this.loopEvent);
+      this.loopEvent = null;
+    }
+  },
+  beforeDestroy () {
+    if (this.loopEvent) {
+      clearInterval(this.loopEvent);
+      this.loopEvent = null;
+    }
+  },
+  methods: {
+    resetLoopEvent () {
+      this.loading = true;
+      this.hasSetTableScroll = false;
+      this.loopEvent && clearInterval(this.loopEvent);
+      this.getTableData();
+      this.loopEvent = setInterval(this.getTableData, LOOP_INTERVAL.arrivalTable);
+    },
+    airPortChange () {
+      this.resetLoopEvent();
+    },
+    dateChangeHandler () {
+      this.resetLoopEvent();
+    },
+    // 选择机场
+    async getAirPortData () {
+      try {
+        const res = await getQuery({
+          serviceId: DATACONTENT_ID.arrivalAirId,
+          dataContent: {},
+          event: '0',
+          token: getToken()
+        });
+        if (Number(res.code) === 0) {
+          this.AirportList = this._.orderBy(res.returnData, (o) => o.planLandingApt);
+          this.formData.currentAirport = "PEK";
+          this.resetLoopEvent();
+        } else {
+          this.$message.error(res.message);
+        }
+      } catch (error) {
+        this.$message.error("失败");
+      }
+    },
+    tableRowClassName ({ row, rowIndex }) {
+      const classes = [];
+      if (row.flightStatus === "DLY") {
+        classes.push("bgl-delayed");
+      }
+      if (row.flightStatus === "CAN") {
+        classes.push("bgl-canceled");
+      }
+      if (row.hasArrived) {
+        classes.push("bgl-hui");
+        if (rowIndex === this.arrivalCount - 1) {
+          classes.push("redBorder");
+        }
+      }
+      return classes.join(" ");
+    },
+    headerCellClass ({ row, column }) {
+      const classes = [];
+      const rule = this.tableDataSortRules[column.property];
+      if (rule) {
+        classes.push(rule);
+      }
+      return classes.join(" ");
+    },
+    // 获取表单下拉框数据
+    // getFormData(params) {
+    //   this.relatedAirportQuery({
+    //     ...params,
+    //     type: 'OUT'
+    //   })
+    //   this.outgoingAirlineQuery(params)
+    //   this.craftTypeQuery(params)
+    //   this.flightAttrQuery(params)
+    // },
+    // 获取表格数据
+    async getTableData () {
+      if (!this.formData.currentAirport || !this.startDate || !this.endDate) {
+        return;
+      }
+      const arr = [this.formData.currentAirport, this.startDate, this.endDate];
+      try {
+        const res = await getQuery({
+          serviceId: DATACONTENT_ID.arrivalTableId,
+          dataContent: {
+            departureAirport: this.formData.currentAirport,
+            startTime: this.startDate,
+            endTime: this.endDate
+          },
+          event: '0',
+          token: getToken()
+        });
+        if (Number(res.code) === 0) {
+          this.initTableData(res.returnData);
+        } else {
+
+        }
+        this.loading = false;
+      } catch (error) {
+        if (this.loopEvent) {
+          clearInterval(this.loopEvent);
+          this.loopEvent = null;
+        }
+        this.loading = false;
+
+      }
+    },
+    initTableData (tableData) {
+      this.arrivalCount = 0;
+      this.baggageCount = 0;
+      tableData.forEach((item) => {
+        item["flightCanceled"] = item["flightStatus"] === "CAN" ? 1 : 0;
+        if (this.hasArrived(item)) {
+          this.arrivalCount++;
+        }
+        this.baggageCount = this.baggageCount + item.projectedLoad;
+      });
+      this.tableData = this._.orderBy(tableData, ["hasArrived", "arrivalTime"], ["desc", "asc"]);
+      setTableFilters(this.tableData, this.tableDataFilters);
+      this.toOrderNum(this.baggageCount);
+      this.$nextTick(() => {
+        this.setTableScroll();
+      });
+    },
+    hasArrived (flight) {
+      if (flight.arrivalTime) {
+        const now = new Date();
+        const arrivalTime = new Date(flight.arrivalTime);
+        flight["hasArrived"] = now > arrivalTime && !flight["flightCanceled"];
+      } else {
+        flight["hasArrived"] = false;
+      }
+      return flight["hasArrived"];
+    },
+    setTableScroll () {
+      if (!this.singleDay || this.hasSetTableScroll || this.arrivalCount === 0) {
+        return;
+      }
+      const table = this.$refs["table"].$el;
+      const scrollParent = table.querySelector(".el-table__body-wrapper");
+      if (scrollParent.scrollHeight <= scrollParent.offsetHeight) {
+        return;
+      }
+      const lastRow = table.querySelectorAll(".el-table__body tr")[this.arrivalCount - 1];
+      setTimeout(() => {
+        const scrollMid = lastRow.offsetTop + lastRow.offsetHeight - scrollParent.offsetHeight / 2;
+        const scrollMax = scrollParent.scrollHeight - scrollParent.offsetHeight;
+        if (scrollMid > 0) {
+          const scrollHeight = Math.min(scrollMid, scrollMax);
+          scrollParent.scrollTo(0, scrollHeight);
+        }
+      }, 0);
+      this.hasSetTableScroll = true;
+    },
+    setNumberTransform () {
+      const numberItems = this.$refs.numberItem; // 拿到数字的ref,计算元素数量
+      const numberArr = this.orderNum.filter((item) => !isNaN(item));
+      // 结合CSS 对数字字符进行滚动,显示订单数量
+      for (let index = 0; index < numberItems.length; index++) {
+        const elem = numberItems[index];
+        elem.style.transform = `translate(-50%, -${numberArr[index] * 10}%)`;
+      }
+    },
+
+    toOrderNum (num) {
+      num = num.toString();
+      if (num.length < 6) {
+        num = "0" + num; // 如未满八位数,添加"0"补位
+        this.toOrderNum(num); // 递归添加"0"补位
+      } else if (num.length >= 6) {
+        this.orderNum = num.split(""); // 将其便变成数据,渲染至滚动数组
+      } else {
+        // 订单总量数字超过八位显示异常
+        this.$message.warning("总量数字过大");
+      }
+      this.setNumberTransform();
+    },
+    exportHandler (refName, tableName) {
+      if (this.loading) {
+        return;
+      }
+      const table = this.$refs[refName].$el.cloneNode(true);
+      const fileName = `${tableName}-${this.currentAirport}-${this.startDate}-${this.endDate}.xlsx`;
+      throttledExportToExcel(table, tableName, fileName, 2);
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.terminal-form-wrap {
+  padding-top: 11px;
+  padding-left: 5px;
+  ::v-deep .form {
+    display: flex;
+    justify-content: space-between;
+    .form-left {
+      flex: 1;
+    }
+    .form-right {
+      flex: 0 1 auto;
+    }
+    .el-form-item {
+      margin-bottom: 0px;
+      margin-right: 8px;
+      button,
+      input,
+      optgroup,
+      select,
+      textarea {
+        font-family: Helvetica, "Microsoft YaHei";
+        font-size: 14px;
+      }
+      .el-switch__label {
+        color: #303133;
+      }
+      .el-form-item__error {
+        z-index: 10;
+      }
+    }
+    .btn-img {
+      position: relative;
+      top: 6px;
+    }
+  }
+  .box-item {
+    position: relative;
+    height: 50px;
+    font-size: 18px;
+    line-height: 32px;
+    text-align: center;
+    list-style: none;
+    color: #2d7cff;
+    writing-mode: vertical-lr;
+    text-orientation: upright;
+    /*文字禁止编辑*/
+    -moz-user-select: none; /*火狐*/
+    -webkit-user-select: none; /*webkit浏览器*/
+    -ms-user-select: none; /*IE10*/
+    -khtml-user-select: none; /*早期浏览器*/
+    user-select: none;
+    /* overflow: hidden; */
+    p {
+      line-height: 32px;
+      writing-mode: horizontal-tb !important;
+      text-orientation: none !important;
+      /*文字禁止编辑*/
+      -moz-user-select: none; /*火狐*/
+      -webkit-user-select: none; /*webkit浏览器*/
+      -ms-user-select: none; /*IE10*/
+      -khtml-user-select: none; /*早期浏览器*/
+      user-select: none;
+      margin-top: 5px;
+    }
+  }
+  /* 默认逗号设置 */
+  .mark-item {
+    width: 10px;
+    height: 32px;
+    margin-right: 5px;
+    line-height: 10px;
+    font-size: 18px;
+    position: relative;
+    & > span {
+      position: absolute;
+      width: 100%;
+      bottom: 0;
+      writing-mode: vertical-rl;
+      text-orientation: upright;
+    }
+  }
+  /*滚动数字设置*/
+  .number-item {
+    width: 41px;
+    height: 42px;
+    /* 背景图片 */
+    // background: url('/images/text-bg-blue.png') no-repeat center center;
+    // background-size: 100% 100%;
+    // background: #ccc;
+    list-style: none;
+    margin-right: 5px;
+    // background:rgba(250,250,250,1);
+    border-radius: 4px;
+    border: 3px solid rgb(221, 221, 221);
+    & > span {
+      position: relative;
+      display: inline-block;
+      margin-right: 10px;
+      width: 100%;
+      height: 100%;
+      writing-mode: vertical-rl;
+      text-orientation: upright;
+      overflow: hidden;
+      & > i {
+        font-style: normal;
+        position: absolute;
+        top: 11px;
+        left: 50%;
+        transform: translate(-50%, -1%);
+        transition: transform 1s ease-in-out;
+        letter-spacing: 10px;
+      }
+    }
+  }
+  .number-item:last-child {
+    margin-right: 0;
+  }
+}
+.terminal-table {
+  width: 100%;
+  ::v-deep .table {
+    width: 100%;
+    .cell {
+      padding: 0;
+      text-align: center;
+      font-size: 14px;
+      font-family: Helvetica, "Microsoft YaHei";
+      letter-spacing: 0;
+    }
+    .cell-click {
+      cursor: pointer;
+      color: #2d7cff;
+      &.cell-clicked {
+        color: purple;
+      }
+    }
+    .el-table__header-wrapper,
+    .el-table__fixed-header-wrapper {
+      .cell {
+        font-weight: bold;
+        color: #101116;
+      }
+      .has-gutter {
+        tr {
+          .bgl-huang {
+            background: #fcf0b1;
+          }
+        }
+      }
+    }
+    .el-table__body-wrapper,
+    .el-table__fixed-body-wrapper {
+      tr.bgl-hui {
+        background: #d2d6df;
+        td {
+          background: #d2d6df;
+        }
+        &.redBorder {
+          position: relative;
+          &::after {
+            content: "";
+            position: absolute;
+            left: 0;
+            bottom: 0;
+            width: 100%;
+            height: 2px;
+            background: #e83f82;
+          }
+        }
+      }
+      tr.bgl-delayed td {
+        background: #fcf0b1;
+      }
+      tr.bgl-canceled td {
+        background: #f7babe;
+      }
+    }
+    .el-table__cell.is-hidden > * {
+      visibility: visible;
+    }
+  }
+}
+</style>

+ 1104 - 0
src/views/flightViewManagement/components/baggage/index.vue

@@ -0,0 +1,1104 @@
+<!--
+ * @Author: your name
+ * @Date: 2022-01-17 10:39:22
+ * @LastEditTime: 2022-08-12 19:14:09
+ * @LastEditors: your name
+ * @Description: 行李视图
+-->
+<template>
+  <div class="baggage-view">
+    <div ref="basicInfo" class="part1">
+      <div class="title">
+        <span>行李基本信息</span>
+        <el-radio-group v-model="infoBtn" class="radioBtn" size="mini" fill="#FFFFFF" text-color="#28344D">
+          <el-radio-button v-for="item in infoRadios" :key="item" :label="item" />
+        </el-radio-group>
+        <!-- <BackButton /> -->
+      </div>
+      <div class="part1_info">
+        <el-row :gutter="12">
+          <el-col v-for="(item, index) in baggageBasicInfoCols" :key="index" :xl="[0, 2, 4].includes(index % 7) ? 4 : 3" :sm="6" :xs="6">
+            <span class="label">{{ item.label }}:</span><span class="content" :class="{
+                'click':
+                  item.prop === 'compensationSign' &&
+                  baggageBasicInfo[item.prop],
+              }" :title="formattedBaggageInfo(item.prop)" @click="baggageBasicInfoClickHandler(item.prop)">{{ formattedBaggageInfo(item.prop) }}</span>
+          </el-col>
+        </el-row>
+      </div>
+    </div>
+    <div v-show="infoBtn === infoRadios[0]" class="part2">
+      <div class="part2_info">
+        <div class="title">行李跟踪信息</div>
+        <div class="type normal">
+          {{ baggageBasicInfo.BagStatus }}
+        </div>
+        <div class="airline">
+          <el-select v-model="selectedAirline" size="mini" class="airline-select">
+            <el-option v-for="airline in airlineList" :key="airline.value" :value="airline.value" :label="airline.label" />
+          </el-select>
+        </div>
+        <div class="baggage-track-chart">
+          <div class="step-line">
+            <div v-for="(line, index) in 6" :key="index" :class="['step-line-segment', { 'step-line-active': activeStepLine(index) }]" />
+          </div>
+          <div v-for="(item, index) in stepNodes" :key="index" :class="{ 'step-item': true, 'active-item': item.status }">
+            <div class="step-circle">
+              <span class="step-name">{{ item.nodeName }}</span>
+            </div>
+            <div class="step-info">
+              <div :class="statusClasses(item.status)">{{ item.status }}</div>
+              <span class="step-time">{{ item.processingTime }}</span>
+              <div class="step-location">{{ item.locationId }}</div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="btns">
+        <img class="btn-square btn-shadow" src="@/assets/baggage/ic_export.png" title="导出" @click="exportHandler('table', '行李节点列表')">
+        <img class="btn-square btn-shadow" src="@/assets/baggage/ic_setting.png" title="列设置" @click="show">
+      </div>
+    </div>
+    <div v-show="infoBtn == infoRadios[0]" class="part3" :style="{
+        'height': `calc(100vh - 80px - ${basicInfoHeight}px - 176px - 3 * 8px - 20px)`
+      }">
+      <el-table ref="table" :data="baggageTableData" height="100%" size="mini" border fit :header-cell-class-name="headerCellClass" :header-cell-style="{ color: '#101116' }" :cell-class-name="cellClass" :span-method="tableSpanMethod" @cell-click="cellClickHandler">
+        <el-table-column v-for="item in tableColsCopy" :key="item.index" :prop="item.prop" :label="item.name" :align="item.align || 'center'" :width="item.width" show-overflow-tooltip :fixed="item.fixed">
+          <template slot="header">
+            <div class="cell-content">{{ item.name }}</div>
+          </template>
+          <template slot-scope="scope">
+            <div class="cell-content">{{ scope.row[item.prop] }}</div>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <div v-show="infoBtn === infoRadios[1]" class="part4" :style="{
+        'height': `calc(100vh - 80px - ${basicInfoHeight}px - 2 * 8px - 20px)`
+      }">
+      <header class="head">
+        <div class="title">行李跟踪信息</div>
+        <div class="btns">
+          <img class="btn-square btn-shadow" src="@/assets/baggage/ic_export.png" title="导出" @click="exportMessageToExcel">
+        </div>
+      </header>
+      <main class="main">
+        <template v-if="messageList.length">
+          <el-row :gutter="24" type="flex">
+            <el-col v-for="(message, index) in messageList" :key="index" :span="6">
+              <div class="card">
+                <div class="message-date">{{ message.date }}</div>
+                <div class="message-content">
+                  {{ message.dataContent.replaceAll(/[\r\n]{2,}/g, '\n').replaceAll('\\', '') }}
+                </div>
+              </div>
+            </el-col>
+          </el-row>
+        </template>
+        <template v-else>
+          <el-empty :image-size="1" description="暂无数据" />
+        </template>
+      </main>
+    </div>
+    <!--列设置-->
+    <Dialog :flag="dialogFlag" class="dialog-check-group">
+      <div class="dialog-wrapper">
+        <div class="title">列设置</div>
+        <div class="content">
+          <el-tree ref="columnSetTree" :data="tableCols" :class="colsCheckClass" show-checkbox node-key="index" :default-expand-all="true" :props="{
+              label: 'name',
+              children: 'children',
+            }" :default-checked-keys="checkedKeysTemp" @check="handleCheck" />
+        </div>
+        <div class="foot right t30">
+          <el-button size="medium" class="r24" type="primary" @click="onCheck('baggageTableData')">确定</el-button>
+          <el-button size="medium" @click="hide">取消</el-button>
+        </div>
+      </div>
+    </Dialog>
+  </div>
+</template>
+<script>
+// import BackButton from '@/components/BackButton'
+import Dialog from '@/layout/components/Dialog/index.vue'
+import { myQuery } from '@/api/dataIntegration'
+import { BaggageMessageQuery } from '@/api/flight'
+import tableColsMixin from '../../mixins/tableCols'
+import { throttledExportToExcel } from '@/utils/table'
+import * as XLSX from 'xlsx'
+import XLSX_STYLE from 'xlsx-style'
+import FileSaver from 'file-saver'
+import { mapGetters } from 'vuex'
+import service from '@/utils/request-new'
+
+export default {
+  name: 'BaggageView',
+  components: {
+    // BackButton,
+    Dialog
+  },
+  mixins: [tableColsMixin],
+  data () {
+    return {
+      queryData: {},
+      airlineList: [],
+      selectedAirline: '',
+      basicInfoHeight: 0,
+      debounceTime: 300,
+      baggageBasicInfoCols: [
+        {
+          label: '行李牌号',
+          prop: 'B2'
+        },
+        {
+          label: '航班号',
+          prop: 'F1'
+        },
+        {
+          label: '特殊行李类型',
+          prop: 'E1'
+        },
+        {
+          label: 'PNR编号',
+          prop: 'L1'
+        },
+        {
+          label: '旅客',
+          prop: 'passengerName'
+        },
+        {
+          label: '装载序列号',
+          prop: 'Q1'
+        },
+        {
+          label: '总件数',
+          prop: 'W2'
+        },
+        {
+          label: '总重量',
+          prop: 'W3'
+        },
+        {
+          label: '尺寸',
+          prop: 'W5'
+        },
+
+        {
+          label: '常旅客号',
+          prop: 'Y1'
+        },
+        {
+          label: '常旅客级别',
+          prop: 'Y2'
+        },
+        {
+          label: '取消值机',
+          prop: 'S3'
+        },
+        {
+          label: '是否可装载',
+          prop: 'S1'
+        },
+        {
+          label: '是否可运输',
+          prop: 'S7'
+        },
+        {
+          label: '行李激活状态',
+          prop: 'S8'
+        },
+        {
+          label: '无BSM状态',
+          prop: 'noBSM'
+        },
+        {
+          label: '中转进航班',
+          prop: 'transferInFlightNo'
+        },
+        {
+          label: '中转出航班',
+          prop: 'O01'
+        },
+        {
+          label: '速运标记',
+          prop: 'E1'
+        },
+        {
+          label: '破损标记',
+          prop: 'brokenSign'
+        },
+        {
+          label: '投诉标记',
+          prop: 'complaintSign'
+        },
+        {
+          label: '赔偿标记',
+          prop: 'compensationSign'
+        },
+        {
+          label: '异常状态',
+          prop: 'B1'
+        },
+        {
+          label: '企业或团队名称',
+          prop: 'C1'
+        }
+      ],
+      baggageBasicInfo: {},
+      infoBtn: '',
+      infoRadios: ['跟踪信息', '跟踪报文'],
+      messageList: [],
+      stepNodes: [],
+      tableCols: [
+        { name: '航班号', prop: 'F1', fixed: true },
+        { name: '航班日期', prop: 'F2', width: 95, fixed: true },
+        { name: '起飞航站\n起飞时间', prop: 'departureInfo', width: 100 },
+        { name: '目的航站\n降落时间', prop: 'landingInfo', width: 100 },
+        { name: '旅客舱位', prop: 'U4', width: 70 },
+        { name: '旅客座位号', prop: 'S2' },
+        { name: '值机序号', prop: 'passengerCheckInNumber', width: 70 },
+        { name: '节点标识', prop: 'nodeCode', width: 100 },
+        { name: '节点名称', prop: 'nodeName', width: 70 },
+        { name: '位置标识', prop: 'J8' },
+        { name: '位置描述', prop: 'J6' },
+        { name: '读取时间', prop: 'J5', width: 158 },
+        { name: '结果', prop: 'status', width: 60 },
+        { name: '次级代码', prop: 'J1', width: 70 },
+        { name: '操作人', prop: 'J2', width: 90 },
+        { name: '设备ID', prop: 'J3' },
+        { name: '发往位置', prop: 'J9', width: 70 },
+        { name: '发往位置描述', prop: 'J7', width: 100 },
+        { name: '装载序号', prop: 'Q1', width: 70 },
+        { name: '容器编号', prop: 'U1', width: 110 },
+        { name: '数据来源', prop: 'dataSource' }
+      ],
+      baggageTableData: [],
+      spanArr: [],
+      pos: 0,
+      queryLoop: null,
+      queryTrackLoop: null
+    }
+  },
+  computed: {
+    ...mapGetters(['sidebar']),
+    activeStepLine () {
+      return function (index) {
+        return this.stepNodes[index].status && this.stepNodes[index + 1].status
+      }
+    },
+    statusClasses () {
+      return function (status) {
+        const classes = ['step-status']
+        if (typeof status === 'string') {
+          if (status.includes('正常') || status.includes('通过')) {
+            classes.push('step-status-normal')
+          } else {
+            classes.push('step-status-abnormal')
+          }
+        }
+        return classes
+      }
+    },
+    formattedBaggageInfo () {
+      return function (prop) {
+        const value = this.baggageBasicInfo[prop]
+        if ((value ?? '') === '') {
+          return ''
+        } else {
+          switch (prop) {
+            case 'transitSign':
+              return Number(value) === 0 ? '非中转' : '中转'
+            case 'activeState':
+              return Number(value) === 0 ? '未激活' : '激活'
+            case 'whetherToCancelTheCheckIn':
+              return Number(value) === 1 ? '取消' : ''
+            case 'isItLoadable':
+            case 'isItTransportable':
+            case 'noBSM':
+            case 'expressSign':
+            case 'brokenSign':
+            case 'complaintSign':
+              // case 'compensationSign':
+              return Number(value) === 1 || value === 'Y' ? '是' : '否'
+            default:
+              return value
+          }
+        }
+      }
+    }
+  },
+  watch: {
+    infoBtn: {
+      handler (val) {
+        this.stopLoopAll()
+        if (val === this.infoRadios[0]) {
+          // if (this.selectedAirline) {
+          //   this.startQueryTrack()
+          // }
+          this.startQueryDetails()
+        } else if (val === this.infoRadios[1]) {
+          this.startQueryMessage()
+        }
+      },
+      immediate: true
+    },
+    selectedAirline () {
+      this.stopQueryTrack()
+      // this.startQueryTrack()
+    }
+  },
+  created () {
+    this.resetStepNodes()
+    const queryObj = this.$route.query
+    if (queryObj && Object.keys(queryObj).length) {
+      this.queryData = queryObj
+      this.queryBasicInfo()
+      this.queryAirline()
+      this.infoBtn = this.infoRadios[0]
+    }
+  },
+  activated () {
+    this.resizeHandler()
+    this.debouncedResizeHandler = this._.debounce(this.resizeHandler, this.debounceTime)
+    window.addEventListener('resize', this.debouncedResizeHandler)
+  },
+  updated () {
+    this.resizeHandler()
+  },
+  deactivated () {
+    this.stopLoopAll()
+    window.removeEventListener('resize', this.debouncedResizeHandler)
+  },
+  methods: {
+    resizeHandler () {
+      this.basicInfoHeight = this.$refs['basicInfo'].offsetHeight
+      this.$refs['table']?.doLayout()
+    },
+    baggageBasicInfoClickHandler (prop) {
+      if (prop !== 'compensationSign' || !this.baggageBasicInfo[prop]) {
+        return
+      }
+      this.$store.dispatch('app/setAbnormalBaggageQueryParams', {
+        flightNO: this.queryData.flightNO,
+        flightDate: this.queryData.flightDate,
+        bagSN: this.queryData.bagSN,
+        fileNumber: this.baggageBasicInfo[prop]
+      })
+      this.$store.dispatch('app/toggleAbnormalBaggageDialogFlag', true)
+    },
+    startQueryDetails () {
+      this.queryDetails()
+      this.queryLoop = setInterval(this.queryDetails.bind(this), LOOP_INTERVAL.baggageDetails)
+    },
+    startQueryTrack () {
+      this.queryTrack()
+      this.queryTrackLoop = setInterval(this.queryTrack.bind(this), LOOP_INTERVAL.baggageTrack)
+    },
+    startQueryMessage () {
+      this.queryMessage()
+      this.queryLoop = setInterval(this.queryMessage.bind(this), LOOP_INTERVAL.baggageMessage)
+    },
+    stopQueryTrack () {
+      this.queryTrackLoop && clearInterval(this.queryTrackLoop)
+      this.queryTrackLoop = null
+    },
+    stopLoopAll () {
+      this.queryLoop && clearInterval(this.queryLoop)
+      this.queryLoop = null
+      this.stopQueryTrack()
+    },
+    resetStepNodes () {
+      this.stepNodes = [
+        {
+          nodeCode: 'CHECKIN',
+          nodeName: '值机'
+        },
+        {
+          nodeCode: 'SECURITY',
+          nodeName: '安检'
+        },
+        {
+          nodeCode: 'SORT',
+          nodeName: '分拣'
+        },
+        {
+          nodeCode: 'LOAD',
+          nodeName: '装车'
+        },
+        {
+          nodeCode: 'INFL',
+          nodeName: '装机'
+        },
+        {
+          nodeCode: 'UNLOAD',
+          nodeName: '卸机'
+        },
+        {
+          nodeCode: 'ARRIVED',
+          nodeName: '到达'
+        }
+      ]
+    },
+    initTableData (tableData) {
+      const spanArr = []
+      let pos = 0
+      for (let i = 0; i < tableData.length; i++) {
+        if (i === 0) {
+          spanArr.push(1)
+        } else {
+          if (
+            tableData[i]['F1'] === tableData[i - 1]['F1'] &&
+            tableData[i]['F2'] === tableData[i - 1]['F2'] &&
+            tableData[i]['departureAirport'] === tableData[i - 1]['departureAirport'] &&
+            tableData[i]['landingAirport'] === tableData[i - 1]['landingAirport']
+          ) {
+            spanArr[pos] += 1
+            spanArr.push(0)
+          } else {
+            spanArr.push(1)
+            pos = i
+          }
+        }
+      }
+      this.spanArr = spanArr
+      this.pos = pos
+    },
+    headerCellClass ({ row, column, rowIndex, columnIndex }) {
+      if (['departureInfo', 'landingInfo'].includes(column.property)) {
+        return 'pre-line'
+      }
+    },
+    cellClass ({ row, column, rowIndex, columnIndex }) {
+      const classes = []
+      if (
+        ['flightNO', 'U_Device_ID'].includes(column.property) &&
+        row[column.property] &&
+        row[column.property] !== 'FBULK'
+      ) {
+        classes.push('cell-click')
+      }
+      if (['departureInfo', 'landingInfo'].includes(column.property)) {
+        classes.push('pre-line')
+      }
+      return classes.join(' ')
+    },
+    cellClickHandler (row, column, cell, event) {
+      if (row[column.property] && row[column.property] !== 'FBULK') {
+        switch (column.property) {
+          case 'flightNO':
+            this.$router.push({
+              path: `${this.$route.path.split('/').slice(0, -1).join('/')}/flightView`,
+              query: {
+                flightNO: row.flightNO,
+                flightDate: row.flightDate
+              }
+            })
+            break
+          case 'U_Device_ID':
+            this.$router.push({
+              path: `${this.$route.path.split('/').slice(0, -1).join('/')}/containerView`,
+              query: {
+                flightNO: row.flightNO,
+                flightDate: row.flightDate,
+                departureAirport: row.departureAirport,
+                landingAirport: row.landingAirport,
+                containerID: row.U_Device_ID
+              }
+            })
+            break
+          default:
+            break
+        }
+      }
+    },
+    tableSpanMethod ({ row, column, rowIndex, columnIndex }) {
+      if (['flightNO', 'flightDate', 'departureInfo', 'landingInfo'].includes(column['property'])) {
+        const _row = this.spanArr[rowIndex]
+        const _col = _row > 0 ? 1 : 0
+        return {
+          rowspan: _row,
+          colspan: _col
+        }
+      }
+    },
+    exportHandler (refName, tableName) {
+      const table = this.$refs[refName].$el.cloneNode(true)
+      const { bagSN, flightNO, flightDate } = this.queryData
+      const fileName = `${tableName}-${bagSN}-${flightNO}-${flightDate}.xlsx`
+      throttledExportToExcel(table, tableName, fileName)
+    },
+    exportMessageToExcel () {
+      const xlsxDatas = [['Date & Time', 'Message']]
+      xlsxDatas.push(
+        ...this.messageList.map(message => [
+          message.date,
+          message.dataContent.replaceAll(/[\r\n]{2,}/g, '\n').replaceAll('\\', '')
+        ])
+      )
+      const columnWidths = []
+      xlsxDatas.forEach(row => {
+        // 计算每一列宽度,考虑换行
+        row.forEach((cell, columnIndex) => {
+          const cellWidth = Math.max(
+            ...cell
+              .toString()
+              .split('\n')
+              .map(cellRow =>
+                cellRow.split('').reduce((pre, curr) => {
+                  const letterSize = curr.charCodeAt(0) > 255 ? 2 : 1
+                  return pre + letterSize
+                }, 0)
+              )
+          )
+          if ((!columnWidths[columnIndex] && cellWidth > 0) || cellWidth > columnWidths[columnIndex]) {
+            columnWidths[columnIndex] = cellWidth
+          }
+        })
+      })
+      // 生成表格数据
+      const sheet = XLSX.utils.aoa_to_sheet(xlsxDatas)
+      // 添加列宽度
+      sheet['!cols'] = columnWidths.map(width => ({
+        wch: width + 2
+      }))
+      // 表格对齐、添加边框
+      const borderStyle = {
+        style: 'medium',
+        color: {
+          rgb: 'FFFFFF'
+        }
+      }
+      const reg = /^[A-Z]+([\d]+$)/
+      for (const key in sheet) {
+        const match = reg.test(key)
+        if (match) {
+          const rowIndex = reg.exec(key)[1]
+          let cellStyle = {
+            alignment: {
+              horizontal: 'center',
+              vertical: 'center',
+              wrapText: true
+            }
+          }
+          if (Number(rowIndex) === 1) {
+            cellStyle = {
+              ...cellStyle,
+              border: {
+                top: borderStyle,
+                right: borderStyle,
+                bottom: borderStyle,
+                left: borderStyle
+              },
+              font: {
+                color: {
+                  rgb: 'FFFFFF'
+                }
+              },
+              fill: {
+                fgColor: {
+                  rgb: '3366FF'
+                }
+              }
+            }
+          } else {
+            cellStyle.alignment.horizontal = 'left'
+          }
+          sheet[key].s = cellStyle
+        }
+      }
+      // 表格数据转换
+      const workBook = XLSX.utils.book_new()
+      XLSX.utils.book_append_sheet(workBook, sheet, '行李原始报文')
+      const tableWrite = XLSX_STYLE.write(workBook, {
+        bookType: 'xlsx',
+        bookSST: true,
+        type: 'buffer',
+        cellStyles: true
+      })
+      // 下载表格
+      const { bagSN, flightNO, flightDate } = this.queryData
+      const fileName = `行李原始报文-${bagSN}-${flightNO}-${flightDate}.xlsx`
+      FileSaver.saveAs(new Blob([tableWrite], { type: 'application/octet-stream' }), fileName)
+    },
+    // 行李详情基础信息
+    queryBaggageBasicInfo (dataContent) {
+      return myQuery({ serviceId: 18127, dataContent })
+    },
+    // 行李航段
+    queryBaggageAirline (dataContent) {
+      return myQuery({ serviceId: DATACONTENT_ID.baggageAirline, dataContent })
+    },
+    // 行李详情追踪链
+    queryBaggageTrack (dataContent) {
+      return myQuery({ serviceId: DATACONTENT_ID.baggageTrack, dataContent })
+    },
+    // 行李详情表格
+    queryBaggageDetails (dataContent) {
+      return myQuery({ serviceId: 18128, dataContent })
+    },
+    // 原始报文
+    async queryBaggageMessage (dataContent) {
+      try {
+        const { code, returnData, message } = await BaggageMessageQuery({
+          serviceId: DATACONTENT_ID.baggageMessage,
+          dataContent
+        })
+        if (Number(code) === 0) {
+          return returnData
+        } else {
+          this.$message.error(message ?? '失败')
+        }
+      } catch (error) {
+        this.$message.error('失败')
+      }
+    },
+    async queryBasicInfo (queryData = this.queryData) {
+      const dataContent = queryData
+      try {
+        const baggageBasicInfo = await this.queryBaggageBasicInfo(dataContent)
+        if (baggageBasicInfo.length) {
+          this.baggageBasicInfo = baggageBasicInfo[baggageBasicInfo.length - 1]
+        }
+      } catch (error) {
+        this.$message.error('失败')
+      }
+    },
+    async queryDetails (queryData = this.queryData) {
+      function setDataSource (item) {
+        if (item['b_type'] !== 'BSM') {
+          const resourceCode = item['resourceFile']?.slice(-4)
+          switch (resourceCode) {
+            case '0100':
+              if (
+                (item['DeviceCode'] && item['DeviceCode'].toUpperCase() === 'STARHUB') ||
+                (!item['DeviceCode'] && !['LOAD', 'INFL'].includes(item['nodeCode']))
+              ) {
+                item['dataSource'] = 'Manual Load'
+              } else {
+                item['dataSource'] = 'BRS'
+              }
+              break
+            case '0101':
+              item['dataSource'] = 'RFID'
+              break
+            case '0102':
+              item['dataSource'] = '首都机场'
+              break
+            default:
+              break
+          }
+        }
+      }
+      const dataContent = queryData
+      try {
+        const baggageDetails = await this.queryBaggageDetails(dataContent)
+        this.baggageTableData = baggageDetails.map((item, index) => {
+          if (item['dealTime']) {
+            item['dealTime'] = item['dealTime'].replace('T', ' ')
+          }
+          item['departureInfo'] = `${item['departureAirport']}\n${item['departureTime'] ? item['departureTime'].replace('T', '\n') : ''
+            }`
+          item['landingInfo'] = `${item['landingAirport']}\n${item['landingTime'] ? item['landingTime'].replace('T', '\n') : ''
+            }`
+          setDataSource(item)
+          return item
+        })
+        this.initTableData(this.baggageTableData)
+      } catch (error) {
+        this.$message.error('失败')
+      }
+    },
+    async queryAirline (queryData = this.queryData) {
+      const dataContent = queryData
+      try {
+        const result = await this.queryBaggageAirline(dataContent)
+        this.airlineList = result.map(({ flightNO, flightDate, departureAirport, arriveAirport, luggageSN }) => ({
+          label: `${departureAirport}-${arriveAirport}`,
+          value: `${flightNO},${flightDate},${luggageSN}`,
+          flightNO,
+          flightDate
+        }))
+        if (this.airlineList.length) {
+          let currentIndex = this.airlineList.findIndex(
+            ({ flightNO, flightDate }) =>
+              flightNO === queryData.flightNO &&
+              flightDate === queryData.flightDate
+          )
+          currentIndex = currentIndex > -1 ? currentIndex : 0
+          this.selectedAirline = this.airlineList[currentIndex].value
+        } else {
+          this.selectedAirline = ''
+        }
+      } catch (error) {
+        this.$message.error('失败')
+      }
+    },
+    async queryTrack () {
+      function isSameStep (code1, code2) {
+        const sameStepCodes = ['ARRIVED', 'TRANSFER']
+        return sameStepCodes.includes(code1) && sameStepCodes.includes(code2)
+      }
+      try {
+        const result = await this.queryBaggageTrack(this.selectedAirline.split(','))
+        this.resetStepNodes()
+        result.forEach(({ nodeCode, nodeName, processingTime, locationId, status }) => {
+          const replaceIndex = this.stepNodes.findIndex(
+            stepNode => stepNode.nodeCode === nodeCode || isSameStep(stepNode.nodeCode, nodeCode)
+          )
+          if (replaceIndex > -1) {
+            this.stepNodes.splice(replaceIndex, 1, {
+              nodeCode,
+              nodeName,
+              processingTime: processingTime?.replace('T', '\n'),
+              locationId,
+              status
+            })
+          }
+        })
+      } catch (error) {
+        this.$message.error('失败')
+      }
+    },
+    async queryMessage (queryData = this.queryData) {
+      const { flightNO, flightDate, bagSN } = queryData
+      const dataContent = { flightNO, flightDate, bagSN }
+      try {
+        const result = await this.queryBaggageMessage(dataContent)
+        const messageList = result.reduce((list, message, currentIndex, arr) => {
+          const messageObject = typeof message === 'string' ? JSON.parse(message) : message
+          // 确保是当前行李的报文
+          const isCorrectBaggage = messageObject.dataContent.includes(bagSN.slice(0, 7))
+          // 相同报文去重
+          const index = arr.findIndex(testMessage => {
+            const testMessageObject = typeof testMessage === 'string' ? JSON.parse(testMessage) : testMessage
+            return (
+              messageObject.dataContent === testMessageObject.dataContent &&
+              messageObject.ssid === testMessageObject.ssid
+            )
+          })
+          if (isCorrectBaggage && currentIndex === index) {
+            return [...list, messageObject]
+          } else {
+            return list
+          }
+        }, [])
+        this.messageList = this._.sortBy(messageList, 'ssid')
+      } catch (error) {
+        this.$message.error('失败')
+      }
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.baggage-view {
+  width: 100%;
+  height: calc(100vh - 80px);
+  overflow: hidden;
+  background: #dfe3ea;
+  padding: 8px 8px 0;
+  .part1 {
+    width: 100%;
+    // height: 232px;
+    background: #041741;
+    padding: 16px 30px;
+    ::v-deep .title {
+      font-size: 18px;
+      font-weight: bold;
+      color: #ffffff;
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+      .radioBtn {
+        margin-left: 32px;
+        padding: 5px;
+        background: #000d2a;
+        .el-radio-button__inner {
+          background: #000d2a;
+          color: #fff;
+          border: none;
+          font-weight: bold;
+        }
+        .el-radio-button:first-child .el-radio-button__inner {
+          border: none;
+        }
+      }
+      .btn-back {
+        color: #ffffff;
+      }
+    }
+    .part1_info {
+      width: 100%;
+      color: #fff;
+      font-size: 14px;
+      font-weight: 400;
+      color: #ffffff;
+      > .el-row > .el-col {
+        height: 38px;
+        line-height: 38px;
+        display: flex;
+        .label {
+          flex-basis: 126px;
+          text-align: right;
+        }
+        .content {
+          flex: 1;
+          margin: 0;
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          &.click {
+            text-decoration: underline;
+            cursor: pointer;
+          }
+        }
+      }
+    }
+  }
+  .part2 {
+    margin: 8px 0;
+    width: 100%;
+    padding: 24px 30px 28px;
+    background: #ffffff;
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    align-items: flex-start;
+    .part2_info {
+      flex: 1;
+      display: flex;
+      flex-direction: row;
+      justify-content: flex-start;
+      align-items: flex-start;
+      line-height: 42px;
+
+      .title {
+        width: 120px;
+        font-size: 18px;
+        font-weight: bold;
+        color: #303133;
+        margin-right: 20px;
+      }
+      .type {
+        font-size: 18px;
+        font-weight: bold;
+        margin-right: 20px;
+        .warn {
+          color: #df3559;
+        }
+        .normal {
+          color: #519f6b;
+        }
+      }
+      .airline {
+        width: 120px;
+        margin-right: 20px;
+      }
+      .baggage-track-chart {
+        flex: 1;
+        height: 124px;
+        max-width: 1280px;
+        position: relative;
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+        width: 100%;
+      }
+      .step-line {
+        width: calc(100% - 80px);
+        height: 10px;
+        position: absolute;
+        top: 16px;
+        right: 0;
+        left: 0;
+        margin: auto;
+        display: flex;
+        .step-line-segment {
+          width: calc(100% / 6);
+          height: 100%;
+          background: #afb4bf;
+          &.step-line-active {
+            background: #2d67e3;
+          }
+        }
+      }
+      .step-item {
+        width: 80px;
+        height: 100%;
+        text-align: center;
+        font-size: 14px;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: flex-start;
+        z-index: 1;
+        font-family: Helvetica, "Microsoft Yahei";
+        .step-circle {
+          width: 42px;
+          height: 42px;
+          border-radius: 50%;
+          background: #aaacb2;
+          .step-name {
+            color: #ffffff;
+            font-size: 14px;
+            font-weight: bold;
+          }
+        }
+        .step-info {
+          margin-top: 15px;
+          color: #101116;
+          line-height: 22px;
+          .step-status {
+            &-normal {
+              color: #4ab36f;
+            }
+            &-abnormal {
+              color: #e9af4b;
+            }
+          }
+          .step-time {
+            white-space: pre-line;
+            font-size: 12px;
+            line-height: 20px;
+          }
+        }
+        &.active-item .step-circle {
+          background: #2d67e3;
+        }
+      }
+    }
+    .btns {
+      margin-top: 6px;
+    }
+  }
+  .part3 {
+    width: 100%;
+    // header-80px、part1-232px、part2-128px、间隙3*8px、底部44px
+    // height: calc(100vh - 80px - 232px - 128px - 3 * 8px - 44px);
+    background: #ffffff;
+    ::v-deep .el-table {
+      width: 100%;
+      // &.el-table--striped {
+      //   .el-table__body tr.el-table__row--striped td.el-table__cell,
+      //   .el-table__header .el-table__cell {
+      //     background: #ffffff;
+      //   }
+      // }
+      .el-table__cell {
+        // background: #f0f3f7;
+        padding: 0;
+        &.cell-click {
+          cursor: pointer;
+          .cell {
+            color: #2d7cff;
+          }
+        }
+        .cell {
+          padding: 0;
+          word-spacing: 0;
+          font-size: 14px;
+          font-family: Helvetica, "Microsoft YaHei";
+          font-weight: 400;
+          color: #303133;
+          width: 100% !important;
+          .cell-content {
+            padding: 6px 0;
+          }
+        }
+      }
+      .el-table__body .el-table__cell .cell {
+        padding: 6px 10px;
+        .cell-content {
+          display: inline;
+          padding: 0;
+        }
+      }
+    }
+  }
+  .part4 {
+    width: 100%;
+    // height: calc(100vh - 80px - 232px - 2 * 8px - 44px);
+    .head {
+      padding: 16px 24px 11px 30px;
+      background: transparent;
+      display: flex;
+      justify-content: space-between;
+      .title {
+        line-height: 30px;
+        font-size: 18px;
+        font-weight: bold;
+        color: #303133;
+      }
+    }
+    .main {
+      height: calc(100% - 57px);
+      overflow-y: auto;
+      overflow-x: hidden;
+      ::v-deep .el-row {
+        flex-wrap: wrap;
+        .card {
+          width: 100%;
+          min-height: 440px;
+          padding: 20px;
+          background: #ffffff;
+          box-shadow: 0px 3px 2px 0px rgba(0, 0, 0, 0.29);
+          margin-bottom: 24px;
+          > .message-date {
+            width: 180px;
+            height: 26px;
+            line-height: 14px;
+            font-size: 14px;
+            font-family: Helvetica;
+            color: #afb4bf;
+            border-bottom: 1px solid #afb4bf;
+            margin-bottom: 18px;
+          }
+          > .message-content {
+            white-space: pre-line;
+            line-height: 24px;
+            font-size: 14px;
+            color: #303133;
+            word-break: break-all;
+          }
+        }
+      }
+    }
+  }
+  .btns {
+    height: 30px;
+    display: flex;
+    .btn-square {
+      margin-left: 10px;
+      width: 30px;
+    }
+  }
+}
+</style>
+
+<style scoped lang="scss">
+::v-deep .baggage-view {
+  .el-popover {
+    &.popover-dark {
+      background: #303133;
+      color: #ffffff;
+      border: none;
+    }
+    .pre-line {
+      white-space: pre-line;
+    }
+  }
+  .el-popper[x-placement^="top"].popover-dark .popper__arrow::after {
+    border-top-color: #303133;
+  }
+  .el-popper[x-placement^="right"].popover-dark .popper__arrow::after {
+    border-right-color: #303133;
+  }
+  .el-popper[x-placement^="bottom"].popover-dark .popper__arrow::after {
+    border-bottom-color: #303133;
+  }
+  .el-popper[x-placement^="left"].popover-dark .popper__arrow::after {
+    border-left-color: #303133;
+  }
+}
+</style>

+ 558 - 0
src/views/flightViewManagement/components/container/index.vue

@@ -0,0 +1,558 @@
+<template>
+  <div class="container-view">
+    <div class="container-basic">
+      <div class="title">
+        <div class="manageTitle">容器基本信息</div>
+        <el-button
+          type="primary"
+          size="small"
+          @click="toContainerHistory"
+        >历史记录</el-button>
+      </div>
+      <div
+        ref="basicBox"
+        class="basic-info-box"
+      >
+        <el-row :gutter="10">
+          <el-col
+            :xs="6"
+            :sm="6"
+            :xl="4"
+          >
+            <div class="grid-content">容器编号:{{ $route.query.containerID }}</div>
+          </el-col>
+          <el-col
+            :xs="6"
+            :sm="6"
+            :xl="3"
+          >
+            <div class="grid-content">容器类型:{{ $route.query.containerType }}</div>
+          </el-col>
+          <el-col
+            :xs="6"
+            :sm="6"
+            :xl="3"
+          >
+            <div class="grid-content">舱位:{{ $route.query.containerSpace }}</div>
+          </el-col>
+          <el-col
+            :xs="6"
+            :sm="6"
+            :xl="3"
+          >
+            <div class="grid-content">航班号:{{ $route.query.flightNO }}</div>
+          </el-col>
+          <el-col
+            :xs="12"
+            :sm="12"
+            :xl="5"
+          >
+            <div class="grid-content">航班日期:{{ $route.query.flightDate }}</div>
+          </el-col>
+          <el-col
+            :xs="6"
+            :sm="6"
+            :xl="3"
+          >
+            <div class="grid-content">起飞站:{{ $route.query.departureAirport }}</div>
+          </el-col>
+          <el-col
+            :xs="6"
+            :sm="6"
+            :xl="3"
+          >
+            <div class="grid-content">目的站:{{ $route.query.landingAirport }}</div>
+          </el-col>
+        </el-row>
+      </div>
+    </div>
+    <div class="container-table">
+      <div class="title">
+        <div class="manageTitle">容器行李列表</div>
+        <!-- <TimeZoneSelector /> -->
+        <img
+          class="btn-square btn-shadow"
+          src="@/assets/baggage/ic_export.png"
+          title="导出"
+          @click="exportHandler('table', '容器行李列表')"
+        >
+        <img
+          class="btn-square btn-shadow"
+          src="@/assets/baggage/ic_setting.png"
+          title="列设置"
+          @click="show"
+        >
+      </div>
+      <div class="table-wrapper">
+        <el-table
+          ref="table"
+          :data="dealedTableData"
+          :height="computedTableHeight"
+          border
+          stripe
+          size="mini"
+          show-summary
+          :summary-method="summaryRow(dealedTableData.length)"
+          :header-cell-class-name="headerCellClass"
+          :header-cell-style="{ color: '#101116' }"
+          :row-class-name="rowClass"
+          :cell-class-name="cellClass"
+          @cell-click="cellClickHandler"
+        >
+          <el-table-column
+            v-for="col in tableColsCopy"
+            :key="col.index"
+            :prop="col.prop"
+            :label="col.label"
+            :align="col.align || 'center'"
+            :width="col.width"
+            :fixed="col.fixed"
+            :formatter="tableFormat"
+          >
+            <template #header>
+              <TableHeaderCell
+                :label="col.label"
+                :filter-options="tableDataFilters[col.prop]"
+                :filter-values.sync="filterValues[col.prop]"
+                :sortable="col.sortable"
+                :sort-rule.sync="tableDataSortRules[col.prop]"
+              />
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </div>
+    <!--列设置-->
+    <Dialog
+      :flag="dialogFlag"
+      class="dialog-check-group"
+    >
+      <div class="dialog-wrapper">
+        <div class="title">列设置</div>
+        <div class="content">
+          <el-tree
+            ref="columnSetTree"
+            :data="tableCols"
+            :class="colsCheckClass"
+            show-checkbox
+            node-key="index"
+            :default-expand-all="true"
+            :props="{
+              label: 'label',
+              children: 'children',
+            }"
+            :default-checked-keys="checkedKeysTemp"
+            @check="handleCheck"
+          />
+        </div>
+        <div class="foot right t30">
+          <el-button
+            size="medium"
+            class="r24"
+            type="primary"
+            @click="onCheck('tableData')"
+          >确定</el-button>
+          <el-button
+            size="medium"
+            @click="hide"
+          >取消</el-button>
+        </div>
+      </div>
+    </Dialog>
+  </div>
+</template>
+
+<script>
+import Dialog from '@/layout/components/Dialog/index.vue'
+// import TimeZoneSelector from '@/components/TimeZoneSelector'
+import { myQuery } from '@/api/dataIntegration'
+import tableColsMixin from '../../mixins/tableCols'
+// import timeZoneMixin from '../../mixins/timeZone'
+import TableHeaderCell from '@/components/TableHeaderCell'
+import { setTableFilters } from '@/utils/table'
+import { mapGetters } from 'vuex'
+import { throttledExportToExcel } from '@/utils/table'
+export default {
+  name: 'ContainerView',
+  components: {
+    // TimeZoneSelector,
+    Dialog,
+    TableHeaderCell
+  },
+  mixins: [
+    tableColsMixin
+    //  timeZoneMixin
+  ],
+  data() {
+    return {
+      loading: false,
+      queryData: {},
+      computedTableHeight: undefined,
+      tableCols: [
+        {
+          prop: 'passengerName',
+          label: '旅客姓名',
+          desc: '指旅客姓名的拼音大写',
+          filterable: true,
+          sortable: true
+        },
+        {
+          prop: 'luggageSN',
+          label: '行李牌号',
+          desc: '指行李的10位数字行李牌号码',
+          filterable: true,
+          sortable: true
+        },
+        {
+          prop: 'specialType',
+          label: '特殊行李类型',
+          desc: '指有别于普通托运行李的特殊行李分类,包括(装笼动物、机组行李、易碎行李、VIP行李等),参考BSM报文.E项说明',
+          width: 120,
+          filterable: true,
+          sortable: true
+        },
+        {
+          prop: 'isDEL',
+          label: '删除',
+          desc: '指旅客是否取消值机托运,根据BSM报文状态是否有DEL判断,已删除的行李记录为斜体灰色字体',
+          filterable: true,
+          sortable: true
+        },
+        {
+          prop: 'activeState',
+          label: '激活',
+          desc: '指托运行李是否被激活,参照BSM报文.S项说明',
+          filterable: true,
+          sortable: true
+        },
+        {
+          prop: 'luggageWeight',
+          label: '重量',
+          desc: '指托运行李的重量,参照BSM报文.W项说明'
+        },
+        {
+          prop: 'lastStatus',
+          label: '最新状态',
+          desc: '指托运行李的当前查询时间所在的节点状态',
+          filterable: true,
+          sortable: true
+        },
+        {
+          prop: 'lastLocation',
+          label: '最新位置',
+          desc: '指托运行李的当前查询时间所在的节点状态的识读位置代号',
+          filterable: true,
+          sortable: true
+        },
+        {
+          prop: 'inFlightNO',
+          label: '中转进航班',
+          desc: '指有中转行李转出的进港航班号',
+          width: 110,
+          filterable: true,
+          sortable: true
+        },
+        {
+          prop: 'transferFlightNO',
+          label: '中转出航班',
+          desc: '指有中转行李转入的离港航班号',
+          width: 110,
+          filterable: true,
+          sortable: true
+        }
+      ],
+      tableData: [],
+      tableDataFilters: {},
+      filterValues: {},
+      tableDataSortRules: {}
+    }
+  },
+  computed: {
+    ...mapGetters(['clickedCells']),
+    dealedTableData() {
+      const filtered = this.tableData.filter(item => {
+        let flag = true
+        Object.entries(this.filterValues).forEach(([key, arr]) => {
+          if (arr.length && !arr.includes(String(item[key]))) {
+            flag = false
+          }
+        })
+        return flag
+      })
+      const sortRules = Object.entries(this.tableDataSortRules).reduce(
+        (pre, [key, value]) => {
+          if (value) {
+            pre[0].push(key)
+            value = value === 'ascending' ? 'asc' : 'desc'
+            pre[1].push(value)
+          }
+          return pre
+        },
+        [[], []]
+      )
+      return this._.orderBy(filtered, sortRules[0], sortRules[1])
+    }
+  },
+  watch: {
+    loading(val) {
+      if (val) {
+        this.fullscreenLoading = this.$loading({
+          lock: true,
+          text: '加载中',
+          spinner: 'el-icon-loading',
+          background: 'rgba(0, 0, 0, 0.7)'
+        })
+      } else {
+        this.fullscreenLoading?.close()
+      }
+    }
+  },
+  created() {
+    const { flightNO, flightDate, departureAirport, landingAirport, containerID } = this.$route.query
+    if (flightNO && flightDate && departureAirport && landingAirport && containerID) {
+      this.queryData = { flightNO, flightDate, departureAirport, landingAirport, containerID }
+      this.queryContainerBaggage([flightNO, flightDate, departureAirport, landingAirport, containerID])
+    } else {
+      this.$router.push('/')
+    }
+    Object.values(this.tableCols).forEach(({ prop, filterable, sortable }) => {
+      if (filterable) {
+        this.$set(this.tableDataFilters, prop, [])
+        this.$set(this.filterValues, prop, [])
+      }
+      if (sortable) {
+        this.$set(this.tableDataSortRules, prop, '')
+      }
+    })
+  },
+  activated() {
+    this.setTableHeight()
+  },
+  updated() {
+    this.setTableHeight()
+  },
+  deactivated() {
+    this.fullscreenLoading?.close()
+  },
+  methods: {
+    setTableHeight() {
+      const headerHeight = 80
+      const bottomBlankHeight = 16
+      const titleHeight = 64
+      const basicBoxHeight = this.$refs['basicBox'].offsetHeight
+      this.computedTableHeight = `calc(100vh - ${
+        headerHeight + bottomBlankHeight + titleHeight * 2 + basicBoxHeight
+      }px)`
+      this.$nextTick(() => {
+        this.$refs['table']?.doLayout()
+      })
+    },
+    rowClass({ row, rowIndex }) {
+      const classes = []
+      if (row['isDEL'] === 'DEL') {
+        classes.push('bgl-deleted')
+        if (row['waitOFF'] === 1) {
+          classes.push('bgl-toUnload')
+        }
+      }
+      return classes.join(' ')
+    },
+    // 给表头单元格加上 ascending 或 descending 使用 element 自带的排序箭头变色
+    headerCellClass({ row, column, rowIndex, columnIndex }) {
+      const classes = []
+      const rule = this.tableDataSortRules[column.property]
+      if (rule) {
+        classes.push(rule)
+      }
+      return classes.join(' ')
+    },
+    cellClass({ row, column, rowIndex, columnIndex }) {
+      const classes = []
+      if (
+        ['checkInTime', 'DealInfo', 'sortLocationMark', 'loadLocationMark', 'inflLocationMark'].includes(
+          column.property
+        )
+      ) {
+        classes.push('pre-line')
+      }
+      if (
+        [
+          'passengerName',
+          'luggageSN'
+          // 'inFlightNO',
+          // 'transferFlightNO'
+        ].includes(column.property) &&
+        row[column.property]
+      ) {
+        classes.push('cell-click')
+        if (
+          this.clickedCells.some(
+            cell =>
+              cell.pageName === this.$route.name &&
+              Object.entries(cell.row).every(([key, value]) => row[key] === value) &&
+              cell.columnProp === column.property
+          )
+        ) {
+          classes.push('cell-clicked')
+        }
+      }
+      return classes.join(' ')
+    },
+    cellClickHandler(row, column, cell, event) {
+      if (
+        [
+          'passengerName',
+          'luggageSN'
+          // 'inFlightNO',
+          //  'transferFlightNO'
+        ].includes(column.property) &&
+        row[column.property]
+      ) {
+        this.$store.dispatch('keepAlive/addClickedCell', {
+          row,
+          columnProp: column.property,
+          pageName: this.$route.name
+        })
+        switch (column.property) {
+          case 'passengerName':
+            this.$store.dispatch('app/setPassengerQueryParams', {
+              flightNO: this.queryData.flightNO,
+              flightDate: this.queryData.flightDate,
+              passengerName: row.passengerName
+            })
+            this.$store.dispatch('app/togglePassengerDialogFlag', true)
+            break
+          case 'luggageSN':
+            this.$router.push({
+              path: `${this.$route.path.split('/').slice(0, -1).join('/')}/baggageView`,
+              query: {
+                bagSN: row.luggageSN,
+                flightNO: this.queryData.flightNO,
+                flightDate: this.queryData.flightDate
+              }
+            })
+            break
+          default:
+            break
+        }
+      }
+    },
+    toContainerHistory() {
+      this.$router.push({
+        path: `${this.$route.path.split('/').slice(0, -1).join('/')}/containerHistory`,
+        query: {
+          containerID: this.queryData.containerID
+        }
+      })
+    },
+    // 统计行数
+    summaryRow(num) {
+      return function () {
+        return ['合计', `共${num}件`]
+      }
+    },
+    tableFormat(row, column, cellValue) {
+      switch (column.property) {
+        case 'isDEL':
+          return cellValue === 'DEL' ? '删除' : ''
+        case 'activeState':
+          return Number(cellValue) === 1 ? '激活' : '未激活'
+        default:
+          return cellValue ?? ''
+      }
+    },
+    exportHandler(refName, tableName) {
+      const table = this.$refs[refName].$el.cloneNode(true)
+      const fileName = `${tableName}-${Object.values(this.queryData).join('-')}.xlsx`
+      throttledExportToExcel(table, tableName, fileName)
+    },
+    async queryContainerBaggage(dataContent) {
+      this.loading = true
+      this.tableData = []
+      try {
+        const tableData = await myQuery(DATACONTENT_ID.containerBaggage, ...dataContent)
+        this.tableData = tableData
+        setTableFilters(this.tableData, this.tableDataFilters)
+      } catch {
+        this.$message.error('失败')
+      }
+      this.loading = false
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.container-view {
+  width: 100%;
+  height: calc(100vh - 80px - 16px);
+  overflow: hidden;
+  background: #dfe3ea;
+  padding: 0px 8px;
+  .title {
+    display: flex;
+    justify-content: space-between;
+    padding: 16px 0;
+    margin-right: 0;
+  }
+  .container-basic {
+    .basic-info-box {
+      width: 100%;
+      background: #041741;
+      padding: 16px 30px;
+      color: #fff;
+    }
+  }
+  .container-table {
+    .title {
+      height: 64px;
+      .manageTitle {
+        flex: 1;
+      }
+      .el-dropdown {
+        height: 30px;
+      }
+      .btn-square {
+        height: 30px;
+        margin-left: 20px;
+        &:last-child {
+          margin-right: 30px;
+        }
+      }
+    }
+    .table-wrapper {
+      background-color: #fff;
+      ::v-deep .el-table {
+        width: 100%;
+        .cell {
+          padding: 0;
+          text-align: center;
+          font-size: 14px;
+          font-family: Helvetica, 'Microsoft YaHei';
+          letter-spacing: 0;
+        }
+        .cell-click {
+          cursor: pointer;
+          color: #2d7cff;
+          &.cell-clicked {
+            color: purple;
+          }
+        }
+        .el-table__body-wrapper,
+        .el-table__fixed-body-wrapper {
+          tr.bgl-deleted {
+            background: #d2d6df;
+            td {
+              background: #d2d6df;
+              font-style: italic;
+            }
+            &.bgl-warning td {
+              background: lightcoral;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 489 - 0
src/views/flightViewManagement/components/containerHistory/index.vue

@@ -0,0 +1,489 @@
+<template>
+  <div class="container-history">
+    <div class="container-left">
+      <div class="title">
+        <span class="manageTitle">容器历史</span>
+        <!-- <BackButton /> -->
+      </div>
+      <div
+        v-loading="treeLoading"
+        element-loading-text="拼命加载中"
+        element-loading-spinner="el-icon-loading"
+        element-loading-background="rgba(0, 0, 0, 0.8)"
+        class="container-tree"
+      >
+        <el-tree
+          :data="containerHistoryTree"
+          :props="defaultTreeProps"
+          node-key="index"
+          default-expand-all
+          :expand-on-click-node="false"
+          @current-change="currentChangeHandler"
+        >
+          <span
+            slot-scope="{ node, data }"
+            class="el-tree-node__label"
+          >
+            <el-tooltip
+              v-if="data.index === -1"
+              class="item"
+              effect="dark"
+              :content="msg"
+              placement="top"
+            >
+              <span>{{ node.label }}</span>
+            </el-tooltip>
+            <span v-else>{{ node.label }}</span>
+          </span>
+        </el-tree>
+      </div>
+    </div>
+    <div
+      v-loading="tableLoading"
+      element-loading-text="拼命加载中"
+      element-loading-spinner="el-icon-loading"
+      element-loading-background="rgba(0, 0, 0, 0.8)"
+      class="container-right"
+    >
+      <el-table
+        ref="table"
+        :data="dealedTableData"
+        border
+        stripe
+        fit
+        height="calc(100vh - 80px - 17px - 20px)"
+        :header-cell-class-name="headerCellClass"
+        :row-class-name="tableRowClassName"
+        :cell-class-name="cellClass"
+        @cell-click="cellClickHandler"
+      >
+        <el-table-column
+          v-for="col in tableCols"
+          :key="col.prop"
+          :prop="col.prop"
+          :label="col.label"
+          :width="col.width"
+          :fixed="col.fixed"
+          :formatter="tableFormat"
+        >
+          <template #header>
+            <el-tooltip
+              :content="col.desc || col.label"
+              placement="top"
+            >
+              <TableHeaderCell
+                :label="col.label"
+                :filter-options="tableDataFilters[col.prop]"
+                :filter-values.sync="filterValues[col.prop]"
+                :sortable="col.sortable"
+                :sort-rule.sync="tableDataSortRules[col.prop]"
+              />
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+  </div>
+</template>
+
+<script>
+// import BackButton from '@/components/BackButton'
+import TableHeaderCell from '@/components/TableHeaderCell'
+import { mapGetters } from 'vuex'
+import { myQuery } from '@/api/dataIntegration'
+import { setTableFilters } from '@/utils/table'
+
+export default {
+  name: 'ContainerHistory',
+  components: {
+    // BackButton,
+    TableHeaderCell
+  },
+  data() {
+    return {
+      queryData: {},
+      containerHistory: [],
+      selectedHistoryData: {},
+      defaultTreeProps: {
+        children: 'children',
+        label: 'label'
+      },
+      treeLoading: false,
+      tableLoading: false,
+      tableCols: [
+        {
+          prop: 'passengerName',
+          label: '旅客姓名',
+          desc: '指旅客姓名的拼音大写',
+          filterable: true,
+          sortable: true
+        },
+        {
+          prop: 'luggageSN',
+          label: '行李牌号',
+          desc: '指行李的10位数字行李牌号码',
+          filterable: true,
+          sortable: true
+        },
+        {
+          prop: 'specialType',
+          label: '特殊行李类型',
+          desc: '指有别于普通托运行李的特殊行李分类,包括(装笼动物、机组行李、易碎行李、VIP行李等),参考BSM报文.E项说明',
+          width: 115,
+          filterable: true,
+          sortable: true
+        },
+        {
+          prop: 'isDEL',
+          label: '删除',
+          desc: '指旅客是否取消值机托运,根据BSM报文状态是否有DEL判断,已删除的行李记录为斜体灰色字体',
+          filterable: true,
+          sortable: true
+        },
+        {
+          prop: 'activeState',
+          label: '激活',
+          desc: '指托运行李是否被激活,参照BSM报文.S项说明',
+          filterable: true,
+          sortable: true
+        },
+        {
+          prop: 'luggageWeight',
+          label: '重量',
+          desc: '指托运行李的重量,参照BSM报文.W项说明'
+        },
+        {
+          prop: 'lastStatus',
+          label: '最新状态',
+          desc: '指托运行李的当前查询时间所在的节点状态',
+          filterable: true,
+          sortable: true
+        },
+        {
+          prop: 'lastLocation',
+          label: '最新位置',
+          desc: '指托运行李的当前查询时间所在的节点状态的识读位置代号',
+          filterable: true,
+          sortable: true
+        },
+        {
+          prop: 'inFlightNO',
+          label: '中转进航班',
+          desc: '指有中转行李转出的进港航班号',
+          width: 110,
+          filterable: true,
+          sortable: true
+        },
+        {
+          prop: 'transferFlightNO',
+          label: '中转出航班',
+          desc: '指有中转行李转入的离港航班号',
+          width: 110,
+          filterable: true,
+          sortable: true
+        }
+      ],
+      tableData: [],
+      tableDataFilters: {},
+      filterValues: {},
+      tableDataSortRules: {},
+      msg: '容器历史默认显示最近10条历史,如果需要可联系管理员进行配置'
+    }
+  },
+  computed: {
+    ...mapGetters(['clickedCells']),
+    containerHistoryTree() {
+      return [
+        {
+          index: -1,
+          label: `容器历史-${this.queryData.containerID}`,
+          children: this.containerHistory.map(({ flightNO, flightDate, departureAirport, arriveAirport }, index) => ({
+            index,
+            label: [flightNO, flightDate.replaceAll('-', '/'), departureAirport, arriveAirport].join('-')
+          }))
+        }
+      ]
+    },
+    dealedTableData() {
+      const filtered = this.tableData.filter(item => {
+        let flag = true
+        Object.entries(this.filterValues).forEach(([key, arr]) => {
+          if (arr.length && !arr.includes(String(item[key]))) {
+            flag = false
+          }
+        })
+        return flag
+      })
+      const sortRules = Object.entries(this.tableDataSortRules).reduce(
+        (pre, [key, value]) => {
+          if (value) {
+            pre[0].push(key)
+            value = value === 'ascending' ? 'asc' : 'desc'
+            pre[1].push(value)
+          }
+          return pre
+        },
+        [[], []]
+      )
+      return this._.orderBy(filtered, sortRules[0], sortRules[1])
+    }
+  },
+  created() {
+    const { containerID } = this.$route.query
+    if (containerID) {
+      this.queryData = { containerID }
+      this.queryContainerHistory([containerID])
+    } else {
+      this.$router.push('/')
+    }
+    Object.values(this.tableCols).forEach(({ prop, filterable, sortable }) => {
+      if (filterable) {
+        this.$set(this.tableDataFilters, prop, [])
+        this.$set(this.filterValues, prop, [])
+      }
+      if (sortable) {
+        this.$set(this.tableDataSortRules, prop, '')
+      }
+    })
+  },
+  updated() {
+    // table数据更新
+    this.$nextTick(() => {
+      this.$refs.table.doLayout()
+    })
+  },
+  deactivated() {
+    this.treeLoading = false
+    this.tableLoading = false
+  },
+  methods: {
+    currentChangeHandler({ index }) {
+      if (index > -1) {
+        this.selectedHistoryData = this.containerHistory[index]
+        const { flightNO, flightDate, departureAirport, arriveAirport, containerID } = this.selectedHistoryData
+        const dataContent = [flightNO, flightDate, departureAirport, arriveAirport, containerID]
+        this.queryBaggageList(dataContent)
+      }
+    },
+    // 给表头单元格加上 ascending 或 descending 使用 element 自带的排序箭头变色
+    headerCellClass({ row, column, rowIndex, columnIndex }) {
+      const classes = []
+      const rule = this.tableDataSortRules[column.property]
+      if (rule) {
+        classes.push(rule)
+      }
+      return classes.join(' ')
+    },
+    tableRowClassName({ row, rowIndex }) {
+      const classes = []
+      if (row.isDEL === 'DEL') {
+        classes.push('bgl-deleted')
+      }
+      return classes.join(' ')
+    },
+    cellClass({ row, column, rowIndex, columnIndex }) {
+      const classes = []
+      if (
+        [
+          'passengerName',
+          'luggageSN'
+          // 'inFlightNO',
+          // 'transferFlightNO'
+        ].includes(column.property) &&
+        row[column.property]
+      ) {
+        classes.push('cell-click')
+        if (
+          this.clickedCells.some(
+            cell =>
+              cell.pageName === 'advance' &&
+              Object.entries(cell.row).every(([key, value]) => row[key] === value) &&
+              cell.columnProp === column.property
+          )
+        ) {
+          classes.push('cell-clicked')
+        }
+      }
+      return classes.join(' ')
+    },
+    cellClickHandler(row, column, cell, event) {
+      if (
+        [
+          'passengerName',
+          'luggageSN'
+          // 'inFlightNO',
+          // 'transferFlightNO'
+        ].includes(column.property) &&
+        row[column.property]
+      ) {
+        this.$store.dispatch('keepAlive/addClickedCell', {
+          row,
+          columnProp: column.property,
+          pageName: this.$route.name
+        })
+        switch (column.property) {
+          case 'passengerName':
+            this.$store.dispatch('app/setPassengerQueryParams', {
+              flightNO: this.selectedHistoryData.flightNO,
+              flightDate: this.selectedHistoryData.flightDate,
+              passengerName: row.passengerName
+            })
+            this.$store.dispatch('app/togglePassengerDialogFlag', true)
+            break
+          case 'luggageSN':
+            this.$router.push({
+              path: `/${this.$route.path.split('/').slice(1, -1).join('/')}/baggageView`,
+              query: {
+                flightNO: this.selectedHistoryData.flightNO,
+                flightDate: this.selectedHistoryData.flightDate,
+                bagSN: row.luggageSN
+              }
+            })
+            break
+          // case 'inFlightNO':
+          //   this.$router.push({
+          //     path: '/advance/flightView',
+          //     query: {
+          //       flightNO: row.inFlightNO,
+          //       flightDate: row.preFlightDate
+          //     }
+          //   })
+          //   break
+          // case 'transferFlightNO':
+          //   this.$router.push({
+          //     path: '/advance/flightView',
+          //     query: {
+          //       flightNO: row.transferFlightNO,
+          //       flightDate: row.transferFlightDate
+          //     }
+          //   })
+          //   break
+          default:
+            break
+        }
+      }
+    },
+    tableFormat(row, column, cellValue) {
+      switch (column.property) {
+        case 'isDEL':
+          return cellValue === 'DEL' ? '删除' : ''
+        case 'activeState':
+          return Number(cellValue) === 1 ? '激活' : '未激活'
+        default:
+          return cellValue ?? ''
+      }
+    },
+    async queryContainerHistory(dataContent) {
+      this.treeLoading = true
+      this.tableData = []
+      try {
+        const result = await myQuery(DATACONTENT_ID.containerHistory, ...dataContent)
+        this.containerHistory = result
+        this.msg = '容器历史默认显示最近' + this.containerHistory[0].k + '条历史,如果需要可联系管理员进行配置'
+      } catch (error) {
+        this.$message.error('失败')
+      }
+      this.treeLoading = false
+    },
+    async queryBaggageList(dataContent) {
+      this.tableLoading = true
+      this.tableData = []
+      try {
+        const result = await myQuery(DATACONTENT_ID.containerBaggage, ...dataContent)
+        this.tableData = result
+        setTableFilters(this.tableData, this.tableDataFilters)
+      } catch (error) {
+        this.$message.error('失败')
+      }
+      this.tableLoading = false
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.container-history {
+  padding: 17px 24px 20px;
+  display: flex;
+  .container-left {
+    flex: 0 1 352px;
+    margin-right: 16px;
+    background-color: #ffffff;
+    .title {
+      height: 32px;
+      .manageTitle {
+        margin-right: 0;
+      }
+    }
+    ::v-deep .el-tree {
+      height: calc(100vh - 80px - 32px - 17px - 20px);
+      overflow-x: hidden;
+      overflow-y: auto;
+      > .el-tree-node {
+        > .el-tree-node__children {
+          display: block;
+        }
+        .el-tree-node__content {
+          height: 38px;
+          .el-tree-node__label {
+            margin-left: 0;
+            letter-spacing: 1px;
+            line-height: 38px;
+            font-size: 14px;
+            font-family: Helvetica, 'Microsoft YaHei';
+            font-weight: bold;
+            color: #101116;
+          }
+        }
+        > .el-tree-node__content {
+          > .el-tree-node__expand-icon.el-icon-caret-right:not(.is-leaf):before {
+            font-size: 16px;
+            color: #101116;
+          }
+          > .el-tree-node__label {
+            font-size: 16px;
+          }
+        }
+      }
+    }
+  }
+
+  .container-right {
+    flex: 1;
+    ::v-deep .el-table {
+      width: 100%;
+      .cell {
+        padding: 0;
+        text-align: center;
+        font-size: 14px;
+        font-family: Helvetica, 'Microsoft YaHei';
+        letter-spacing: 0;
+      }
+      .cell-click {
+        cursor: pointer;
+        color: #2d7cff;
+        &.cell-clicked {
+          color: purple;
+        }
+      }
+      .el-table__header-wrapper,
+      .el-table__fixed-header-wrapper {
+        .cell {
+          font-weight: bold;
+          color: #101116;
+        }
+      }
+      .el-table__body-wrapper,
+      .el-table__fixed-body-wrapper {
+        tr.bgl-deleted {
+          background: #d2d6df;
+          td {
+            background: #d2d6df;
+            font-style: italic;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 24 - 0
src/views/flightViewManagement/components/departure/baggage.vue

@@ -0,0 +1,24 @@
+<!--
+ * @Author: Badguy
+ * @Date: 2022-03-09 11:51:26
+ * @LastEditTime: 2022-03-09 11:53:12
+ * @LastEditors: your name
+ * @Description: 离港行李视图
+ * have a nice day!
+-->
+
+<template>
+  <BaggageView />
+</template>
+
+<script>
+import BaggageView from '../baggage'
+
+export default {
+  name: 'DepartureBaggageView',
+  components: {
+    BaggageView
+  }
+}
+</script>
+

+ 23 - 0
src/views/flightViewManagement/components/departure/container.vue

@@ -0,0 +1,23 @@
+<!--
+ * @Author: Badguy
+ * @Date: 2022-03-09 11:51:26
+ * @LastEditTime: 2022-03-09 11:51:26
+ * @LastEditors: your name
+ * @Description: 离港容器视图
+ * have a nice day!
+-->
+
+<template>
+  <ContainerView />
+</template>
+
+<script>
+import ContainerView from '../container'
+
+export default {
+  name: 'DepartureContainerView',
+  components: {
+    ContainerView
+  }
+}
+</script>

+ 14 - 0
src/views/flightViewManagement/components/departure/containerHistory.vue

@@ -0,0 +1,14 @@
+<template>
+  <ContainerHistory />
+</template>
+
+<script>
+import ContainerHistory from '../containerHistory'
+
+export default {
+  name: 'DepartureContainerHistory',
+  components: {
+    ContainerHistory
+  }
+}
+</script>

+ 24 - 0
src/views/flightViewManagement/components/departure/flight.vue

@@ -0,0 +1,24 @@
+<!--
+ * @Author: Badguy
+ * @Date: 2022-03-09 11:49:13
+ * @LastEditTime: 2022-03-09 11:53:27
+ * @LastEditors: your name
+ * @Description: 离港行李视图
+ * have a nice day!
+-->
+
+<template>
+  <FlightView />
+</template>
+
+<script>
+import FlightView from '../flight'
+
+export default {
+  name: 'DepartureFlightView',
+  components: {
+    FlightView
+  }
+}
+</script>
+

+ 805 - 0
src/views/flightViewManagement/components/departure/index.vue

@@ -0,0 +1,805 @@
+<!--
+ * @Author: zk
+ * @Date: 2022-01-17 10:39:22
+ * @LastEditTime: 2022-06-22 17:39:49
+ * @LastEditors: your name
+ * @Description: 离港01
+-->
+<template>
+  <div class="departure-one">
+    <!--功能区-表单-->
+    <div ref="formWrap" class="terminal-form-wrap">
+      <el-form ref="form" :inline="true" :model="formData" :rules="rules" class="form">
+        <div class="form-left">
+          <el-form-item prop="currentAirport">
+            <!-- <el-cascader
+            v-model="formData.currentAirport"
+            style="width:144px;margin-left:10px"
+            placeholder="全部机场"
+            size="small"
+            :options="currentAirportList"
+            :props="currentAirportProps"
+            collapse-tags
+            clearable
+            filterable
+            @change="setCurrentAirport"
+          /> -->
+            <el-select v-model="formData.currentAirport" class="input-shadow" size="small" style="width: 150px" filterable default-first-option placeholder="请选择机场" @change="airPortChange">
+              <el-option v-for="(item, index) in AirportList" :key="index" :label="item.planDepartureApt" :value="item.planDepartureApt" />
+            </el-select>
+          </el-form-item>
+          <!-- <el-form-item prop="startDate">
+            <el-date-picker
+              v-model="formData.startDate"
+              class="input-shadow"
+              style="width:216px;"
+              size="small"
+              type="date"
+              value-format="yyyy-MM-dd"
+              placeholder="开始时间"
+              @change="startDateChangeHandler"
+            />
+          </el-form-item>
+          <el-form-item prop="endDate">
+            <el-date-picker
+              v-model="formData.endDate"
+              class="input-shadow"
+              style="width:216px;"
+              size="small"
+              type="date"
+              value-format="yyyy-MM-dd"
+              placeholder="结束时间"
+              @change="endDateChangeHandler"
+            />
+          </el-form-item> -->
+          <el-form-item prop="flightDate" label="航班日期">
+            <el-date-picker v-model="formData.flightDate" :clearable="false" size="small" style="width: 300px" type="daterange" value-format="yyyy-MM-dd" start-placeholder="开始日期" end-placeholder="结束日期" :picker-options="dateRangePickerOptions" @change="dateChangeHandler" />
+          </el-form-item>
+          <el-form-item>
+            <div class="box-item">
+              <p>预计装载总数:</p>
+              <li v-for="(item, index) in orderNum" :key="index" :class="{ 'number-item': !isNaN(item), 'mark-item': isNaN(item) }">
+                <span v-if="!isNaN(item)">
+                  <i ref="numberItem">0123456789</i>
+                </span>
+                <span v-else class="comma">{{ item }}</span>
+              </li>
+            </div>
+          </el-form-item>
+        </div>
+        <div class="form-right" @keyup.enter="onSubmit(0)">
+          <el-form-item prop="search">
+            <el-popover :value="popoverVisible" placement="bottom" trigger="manual">
+              <span>请输入航班号(示例:CA1234)或行李牌号(示例:1234567890)</span>
+              <el-input slot="reference" v-model="formData.search" class="input-shadow" style="width: 240px; margin-left: 105px" size="small" placeholder="请输入内容" prefix-icon="el-icon-search" clearable @focus="popoverVisible = true" @blur="popoverVisible = false" />
+            </el-popover>
+          </el-form-item>
+          <el-form-item>
+            <el-button class="btn-shadow" size="mini" type="primary" @click="onSubmit(0)">搜索</el-button>
+          </el-form-item>
+          <!-- <el-form-item v-is="['dm_dt_timeIcon']">
+            <TimeZoneSelector />
+          </el-form-item> -->
+          <el-form-item>
+            <TimeZoneSelector />
+          </el-form-item>
+          <!-- <el-form-item v-is="['dm_dt_columnSettings']">
+            <img class="btn-img btn-shadow" src="@/assets/baggage/ic_setting.png" title="列设置" @click="show" />
+          </el-form-item> -->
+          <el-form-item>
+            <img class="btn-img btn-shadow" src="@/assets/baggage/ic_setting.png" title="列设置" @click="show" />
+          </el-form-item>
+          <!-- <el-form-item v-is="['dm_dt_columnSettings']">
+            <img class="btn-img btn-shadow" src="@/assets/baggage/ic_export.png" title="导出" @click="exportHandler('table', '航站离港列表')" />
+          </el-form-item> -->
+          <el-form-item>
+            <img class="btn-img btn-shadow" src="@/assets/baggage/ic_export.png" title="导出" @click="exportHandler('table', '航站离港列表')" />
+          </el-form-item>
+        </div>
+      </el-form>
+    </div>
+    <!--表格-->
+    <div v-loading="loading" class="terminal-table" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading" element-loading-background="rgba(0, 0, 0, 0.8)">
+      <el-table ref="table" class="table" :height="computedTableHeight" :data="dealedTableData" :header-cell-class-name="headerCellClass" :row-class-name="tableRowClassName" :cell-class-name="cellClass" show-summary :summary-method="summaryMethod" border stripe fit @cell-click="cellClickHandler">
+        <el-table-column v-for="col in tableColsCopy" :key="col.prop" :prop="col.prop" :label="col.label" :width="col.width" :fixed="col.fixed" :formatter="tableFormat">
+          <template #header>
+            <el-tooltip :content="col.desc || childCol.label" placement="top">
+              <TableHeaderCell :label="col.label" :filter-options="tableDataFilters[col.prop]" :filter-values.sync="filterValues[col.prop]" :sortable="col.sortable" :sort-rule.sync="tableDataSortRules[col.prop]" />
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+    <!--列设置-->
+    <Dialog :flag="dialogFlag" class="dialog-check-group">
+      <div class="dialog-wrapper">
+        <div class="title">列设置</div>
+        <div class="content">
+          <el-tree ref="columnSetTree" :data="tableCols" :class="colsCheckClass" show-checkbox node-key="index" :default-expand-all="true" :props="{
+              label: 'label',
+              children: 'children',
+            }" :default-checked-keys="checkedKeysTemp" @check="handleCheck" />
+        </div>
+        <div class="foot right t30">
+          <el-button size="medium" class="r24" type="primary" @click="onCheck">确定</el-button>
+          <el-button size="medium" @click="hide">取消</el-button>
+        </div>
+      </div>
+    </Dialog>
+  </div>
+</template>
+
+<script>
+import Dialog from "@/layout/components/Dialog";
+import TimeZoneSelector from "@/components/TimeZoneSelector";
+import terminalMixin from "../../mixins/terminal";
+import formMixin from "../../mixins/form";
+import tableColsMixin from "../../mixins/tableCols";
+import timeZoneMixin from "../../mixins/timeZone";
+import { getQuery } from "@/api/flight";
+import { GeneralDataReception } from "@/api/dataIntegration";
+import TableHeaderCell from "@/components/TableHeaderCell";
+import { setTableFilters, throttledExportToExcel, timeInZone } from "@/utils/table";
+import { parseTime } from "@/utils/index";
+import { mapGetters } from "vuex";
+import { getToken } from '@/utils/auth';
+
+export default {
+  name: "DepartureTerminalView",
+  components: { Dialog, TimeZoneSelector, TableHeaderCell },
+  mixins: [terminalMixin, formMixin, tableColsMixin, timeZoneMixin],
+  data () {
+    return {
+      orderNum: ["0", "0", "0", "0", "0", "0"], // 默认总数
+      popoverVisible: false,
+      // 初始表头
+      tableCols: [
+        {
+          prop: "flightNO",
+          label: "航班号",
+          desc: "指航班编号",
+          width: 80,
+          fixed: "left",
+          filterable: true,
+          sortable: true,
+        },
+        {
+          prop: "flightDate",
+          label: "执飞日期",
+          desc: "指航班计划起飞日期(不变的,机票上),不是预计起飞日期(预计起飞时间可能多个),也不是实际起飞日期(实际起飞等于最后预计)",
+          width: 105,
+          fixed: "left",
+          filterable: true,
+          sortable: true,
+        },
+        {
+          prop: "planDepartureTime",
+          label: "起飞时间",
+          desc: "根据优先级别显示时间。优先级别:1.实际起飞时间,2.预计起飞时间,3.计划起飞时间",
+          width: 150,
+          filterable: true,
+          sortable: true,
+        },
+        {
+          prop: "targetAirport",
+          label: "目的站",
+          desc: "指航班执飞航段的目的航站,以航站三字码显示",
+          filterable: true,
+          sortable: true,
+        },
+        {
+          prop: "departureBuild",
+          label: "航站楼",
+          desc: "指航班执飞航段的目的航站楼",
+          filterable: true,
+          sortable: true,
+        },
+        {
+          prop: "bordingGate",
+          label: "登机口",
+          desc: "指航班的登机口代码,数据是变化的,仅显示最新信息",
+          filterable: true,
+          sortable: true,
+        },
+        {
+          prop: "standForDepartrue",
+          label: "停机位",
+          desc: "指航班的停机位代码,数据是变化的,仅显示最新信息",
+          filterable: true,
+          sortable: true,
+        },
+        {
+          prop: "checkInTravellerNumber",
+          label: "托运旅客",
+          desc: "指航班已办理行李托运业务的旅客人数,含取消托运的旅客人数",
+        },
+        {
+          prop: "checkInNumber",
+          label: "值机数",
+          desc: "指已办理值机托运的行李数量,含取消托运的行李数量,含未激活",
+        },
+        {
+          prop: "unActive",
+          label: "未激活",
+          desc: "指最后的 BSM 报文“.S”中行李状态为“I”的行李数量,含取消托运的行李数量",
+        },
+        {
+          prop: "preLoad",
+          label: "预计装载",
+          desc: "指已办理值机托运的行李数量,不含取消托运的行李数量,不包含未激活",
+        },
+        {
+          prop: "checkNumber",
+          label: "安检",
+          desc: "指进行安检的行李数量,含取消托运的行李数量",
+        },
+        {
+          prop: "sortNumber",
+          label: "分拣",
+          desc: "指已分拣完成的行李数量,含取消托运的行李数量",
+        },
+        {
+          prop: "loadNumber",
+          label: "装车",
+          desc: "指已在分拣口装车完成的行李数量,含取消托运的行李数量",
+        },
+        {
+          prop: "boardID",
+          label: "装机",
+          desc: "指装机完成的行李数量,不含取消托运的行李数量",
+        },
+        {
+          prop: "toUnload",
+          label: "待翻减",
+          desc: "指旅客在办理行李托运后,旅客取消该行李的托运并且行李此时已经过装车节点,而没有完成翻减的行李数量(须翻减总数减去已翻减数)",
+        },
+        {
+          prop: "OFFCount",
+          label: "已翻减",
+          desc: "指旅客在办理行李托运后,旅客取消该行李的托运并且行李此时已经过装车节点,且已完成翻减的行李数量",
+        },
+        {
+          prop: "noCheckInNumber",
+          label: "取消托运",
+          desc: "指旅客在办理行李托运后,又取消托运的行李总数量",
+        },
+        {
+          prop: "noBSM",
+          label: "无BSM",
+          desc: "行李有处理信息(BPM)但无值机信息(BSM)的行李数量",
+        },
+        {
+          prop: "warning",
+          label: "风险预警",
+          desc: "指依据航班信息中预计起飞时间和当前时间差,与根据分拣到停机位设置的报警阈值对比,超过阈值的为风险行李,本项显示风险预警行李数量",
+        },
+        {
+          prop: "exceptions",
+          label: "未装机行李",
+          desc: "指航班关闭货舱门后,应装而未装的行李数量",
+        },
+        {
+          prop: "midIn",
+          label: "中转进行李",
+          desc: "指从其他航班中转到当前航班的行李数量",
+        },
+      ],
+      tableDataSortRules: {
+        flightCanceled: "ascending",
+      },
+      loading: false,
+      AirportList: [],
+      loopEvent: null,
+      leaveCount: 0,
+      baggageCount: 0,
+      hasSetTableScroll: false,
+      table: null,
+      WarningData: [],
+    };
+  },
+  computed: {
+    singleDay () {
+      return this.startDate === this.endDate;
+    },
+    ...mapGetters(["timeZone"]),
+  },
+  mounted () {
+    this.getAirPortData();
+    this.table = this.$refs.table.bodyWrapper;
+    const that = this;
+    this.table.addEventListener("scroll", () => {
+      that.scrollTop = this.table.scrollTop;
+    });
+  },
+  activated () {
+    this.table.scrollTop = this.scrollTop;
+    this.getTableData();
+    this.getWarningData();
+    this.loopEvent = setInterval(this.getTableData, LOOP_INTERVAL.departureTable);
+  },
+  deactivated () {
+    if (this.loopEvent) {
+      clearInterval(this.loopEvent);
+      this.loopEvent = null;
+    }
+  },
+  beforeDestroy () {
+    if (this.loopEvent) {
+      clearInterval(this.loopEvent);
+      this.loopEvent = null;
+    }
+  },
+  methods: {
+    resetLoopEvent () {
+      this.loading = true;
+      this.hasSetTableScroll = false;
+      this.loopEvent && clearInterval(this.loopEvent);
+      this.getTableData();
+      this.loopEvent = setInterval(this.getTableData, LOOP_INTERVAL.departureTable);
+    },
+    airPortChange () {
+      this.resetLoopEvent();
+    },
+    dateChangeHandler () {
+      this.resetLoopEvent();
+    },
+    async getAirPortData () {
+      try {
+        const res = await getQuery({
+          serviceId: DATACONTENT_ID.departureAirMainId,
+          dataContent: {},
+          event: '0',
+          token: getToken()
+        });
+        if (Number(res.code) === 0) {
+          this.AirportList = this._.orderBy(res.returnData, (o) => o.planDepartureApt);
+          this.formData.currentAirport = "PEK";
+          this.resetLoopEvent();
+        } else {
+          this.$message.error(res.message);
+        }
+      } catch (error) {
+        this.$message.error("失败");
+      }
+    },
+    async getWarningData () {
+      try {
+        const res = await getQuery({
+          serviceId: DATACONTENT_ID.departureWarningId,
+          dataContent: {},
+          event: '0',
+          token: getToken()
+        });
+        if (Number(res.code) === 0) {
+          const { listValues } = res.returnData;
+          this.WarningData = listValues;
+        } else {
+          this.$message.error(res.message);
+        }
+      } catch (error) {
+        this.$message.error("失败");
+      }
+    },
+    tableRowClassName ({ row, rowIndex }) {
+      const classes = [];
+      if (row.flightStatus === "DLY") {
+        classes.push("bgl-delayed");
+      }
+      if (row.flightStatus === "CAN") {
+        classes.push("bgl-canceled");
+      }
+      if (row.hasTakeOff === 1) {
+        classes.push("bgl-hui");
+        if (rowIndex === this.leaveCount - 1) {
+          classes.push("redBorder");
+        }
+      }
+      return classes.join(" ");
+    },
+    headerCellClass ({ row, column }) {
+      const classes = [];
+      if (["warning", "exceptions", "midIn"].includes(column.property)) {
+        classes.push("bgl-huang");
+      }
+      const rule = this.tableDataSortRules[column.property];
+      if (rule) {
+        classes.push(rule);
+      }
+      return classes.join(" ");
+    },
+    // 获取表单下拉框数据
+    // getFormData(params) {
+    //   this.relatedAirportQuery({
+    //     ...params,
+    //     type: 'OUT'
+    //   })
+    //   this.outgoingAirlineQuery(params)
+    //   this.craftTypeQuery(params)
+    //   this.flightAttrQuery(params)
+    // },
+    // 获取表格数据
+    async getTableData () {
+      if (!this.formData.currentAirport || !this.startDate || !this.endDate) {
+        return;
+      }
+      const arr = [this.formData.currentAirport, this.startDate, this.endDate];
+      try {
+        const res = await getQuery({
+          serviceId: DATACONTENT_ID.departureTableMainId,
+          dataContent: {
+            departureAirport: this.formData.currentAirport,
+            startTime: this.startDate,
+            endTime: this.endDate
+          },
+          event: '0',
+          token: getToken()
+        });
+        if (Number(res.code) === 0) {
+          this.initTableData(res.returnData.listValues);
+        } else {
+        }
+        this.loading = false;
+      } catch (error) {
+        if (this.loopEvent) {
+          clearInterval(this.loopEvent);
+          this.loopEvent = null;
+        }
+        this.loading = false;
+      }
+    },
+    initTableData (tableData) {
+      const currentTime = new Date();
+      const curTime = this.formatTime(currentTime);
+      this.leaveCount = 0;
+      this.baggageCount = 0;
+      tableData.forEach((item) => {
+        item["flightCanceled"] = item["flightStatus"] === "CAN" ? 1 : 0;
+        item["toUnload"] = item['tounLoad'] - item["OFFCount"];
+        item["exceptions"] = item["preLoad"] - item["boardID"];
+        if (item["hasTakeOff"] === 1 && !item["flightCanceled"]) {
+          this.leaveCount++;
+        }
+        if (item["hasTakeOff"] !== 1 && !item["flightCanceled"] && item["preLoad"] - Math.max(item.loadNumber, item.boardID) > 0) {
+          this.WarningData.forEach((p) => {
+            const startTime = this.formatTime(timeInZone((p.startDate ?? "").replace("T", " "), this.timeZone), 2);
+            const endTime = this.formatTime(timeInZone((p.endDate ?? "").replace("T", " "), this.timeZone), 2);
+            const planTime = this.formatTime(timeInZone((item.planDepartureTime ?? "").replace("T", " "), this.timeZone), 2);
+            const capTime = Math.ceil((planTime - curTime) / (1000 * 60));
+            if (Number(startTime) - Number(curTime) < 0 && Number(endTime) - Number(curTime) > 0) {
+              const newItem = _.cloneDeep(item);
+              if (p.flightNO && p.flightNO == item.flightNO) {
+                if (p.warningDuration && capTime - p.warningDuration < 0 && capTime - p.alarmDuration > 0) {
+                  item["warning"] = item["preLoad"] - Math.max(item.loadNumber, item.boardID);
+                  item["warningState"] = 1;
+                  const returnedTarget = Object.assign(newItem, p);
+                  this.sendLog(returnedTarget);
+                }
+                if (p.alarmDuration && capTime - p.alarmDuration < 0) {
+                  item["warning"] = item["preLoad"] - Math.max(item.loadNumber, item.boardID);
+                  item["warningState"] = 2;
+                  const returnedTarget = Object.assign(newItem, p);
+                  this.sendLog(returnedTarget);
+                }
+              } else if (!p.flightNO && p.IATACode) {
+                if (newItem.flightNO.substring(0, 2) == p.IATACode) {
+                  if (p.warningDuration && capTime - p.warningDuration < 0 && capTime - p.alarmDuration > 0) {
+                    item["warning"] = item["preLoad"] - Math.max(item.loadNumber, item.boardID);
+                    item["warningState"] = 1;
+                    const returnedTarget = Object.assign(newItem, p);
+                    this.sendLog(returnedTarget);
+                  }
+                  if (p.alarmDuration && capTime - p.alarmDuration < 0) {
+                    item["warning"] = item["preLoad"] - Math.max(item.loadNumber, item.boardID);
+                    item["warningState"] = 2;
+                    const returnedTarget = Object.assign(newItem, p);
+                    this.sendLog(returnedTarget);
+                  }
+                }
+              }
+            }
+          });
+        }
+        this.baggageCount = this.baggageCount + item.preLoad;
+      });
+      this.tableData = this._.orderBy(tableData, ["hasTakeOff", "planDepartureTime"], ["desc", "asc"]);
+      setTableFilters(this.tableData, this.tableDataFilters);
+      this.toOrderNum(this.baggageCount);
+      this.$nextTick(() => {
+        this.setTableScroll();
+      });
+    },
+    formatTime (date, type = 1) {
+      let time = null;
+      if (type == 1) {
+        time = parseTime(date, "{y}-{m}-{d} {h}:{i}:{s}");
+      } else {
+        time = date;
+      }
+      const newTimt = new Date(time);
+      return newTimt.getTime();
+    },
+    async sendLog (obj) {
+      try {
+        const newObj = {
+          logTime: parseTime(new Date(), "{y}-{m}-{d} {h}:{i}:{s}"),
+          logInfo: JSON.stringify(obj),
+          flightNO: obj.flightNO,
+          flightDate: obj.flightDate,
+          luggageSN: "",
+          strategyUseID: obj.alarmStrategyID,
+          event: 1,
+        };
+        await GeneralDataReception({
+          serviceId: SERVICE_ID.departureScId,
+          dataContent: JSON.stringify(newObj),
+        });
+      } catch (error) {
+        this.$message.error("失败");
+      }
+    },
+    setTableScroll () {
+      if (!this.singleDay || this.hasSetTableScroll || this.leaveCount === 0) {
+        return;
+      }
+      const table = this.$refs["table"].$el;
+      const scrollParent = table.querySelector(".el-table__body-wrapper");
+      if (scrollParent.scrollHeight <= scrollParent.offsetHeight) {
+        return;
+      }
+      const lastRow = table.querySelectorAll(".el-table__body tr")[this.leaveCount - 1];
+      setTimeout(() => {
+        const scrollMid = lastRow.offsetTop + lastRow.offsetHeight - scrollParent.offsetHeight / 2;
+        const scrollMax = scrollParent.scrollHeight - scrollParent.offsetHeight;
+        if (scrollMid > 0) {
+          const scrollHeight = Math.min(scrollMid, scrollMax);
+          scrollParent.scrollTo(0, scrollHeight);
+        }
+      }, 0);
+      this.hasSetTableScroll = true;
+    },
+    setNumberTransform () {
+      const numberItems = this.$refs.numberItem; // 拿到数字的ref,计算元素数量
+      const numberArr = this.orderNum.filter((item) => !isNaN(item));
+      // 结合CSS 对数字字符进行滚动,显示订单数量
+      for (let index = 0; index < numberItems.length; index++) {
+        const elem = numberItems[index];
+        elem.style.transform = `translate(-50%, -${numberArr[index] * 10}%)`;
+      }
+    },
+
+    toOrderNum (num) {
+      num = num.toString();
+      if (num.length < 6) {
+        num = "0" + num; // 如未满八位数,添加"0"补位
+        this.toOrderNum(num); // 递归添加"0"补位
+      } else if (num.length >= 6) {
+        this.orderNum = num.split(""); // 将其便变成数据,渲染至滚动数组
+      } else {
+        // 订单总量数字超过八位显示异常
+        this.$message.warning("总量数字过大");
+      }
+      this.setNumberTransform();
+    },
+    exportHandler (refName, tableName) {
+      if (this.loading) {
+        return;
+      }
+      const table = this.$refs[refName].$el.cloneNode(true);
+      const fileName = `${tableName}-${this.currentAirport}-${this.startDate}-${this.endDate}.xlsx`;
+      throttledExportToExcel(table, tableName, fileName);
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.terminal-form-wrap {
+  padding-top: 11px;
+  padding-left: 5px;
+  ::v-deep .form {
+    display: flex;
+    justify-content: space-between;
+    .form-left {
+      flex: 1;
+    }
+    .form-right {
+      flex: 0 1 auto;
+    }
+    .el-form-item {
+      margin-bottom: 0px;
+      margin-right: 8px;
+      button,
+      input,
+      optgroup,
+      select,
+      textarea {
+        font-family: Helvetica, "Microsoft YaHei";
+        font-size: 14px;
+      }
+      .el-switch__label {
+        color: #303133;
+      }
+      .el-form-item__error {
+        z-index: 10;
+      }
+    }
+    .btn-img {
+      position: relative;
+      top: 6px;
+    }
+  }
+  .box-item {
+    position: relative;
+    height: 50px;
+    font-size: 18px;
+    line-height: 32px;
+    text-align: center;
+    list-style: none;
+    color: #2d7cff;
+    writing-mode: vertical-lr;
+    text-orientation: upright;
+    /*文字禁止编辑*/
+    -moz-user-select: none; /*火狐*/
+    -webkit-user-select: none; /*webkit浏览器*/
+    -ms-user-select: none; /*IE10*/
+    -khtml-user-select: none; /*早期浏览器*/
+    user-select: none;
+    /* overflow: hidden; */
+    p {
+      line-height: 32px;
+      writing-mode: horizontal-tb !important;
+      text-orientation: none !important;
+      /*文字禁止编辑*/
+      -moz-user-select: none; /*火狐*/
+      -webkit-user-select: none; /*webkit浏览器*/
+      -ms-user-select: none; /*IE10*/
+      -khtml-user-select: none; /*早期浏览器*/
+      user-select: none;
+      margin-top: 5px;
+    }
+  }
+  /* 默认逗号设置 */
+  .mark-item {
+    width: 10px;
+    height: 32px;
+    margin-right: 5px;
+    line-height: 10px;
+    font-size: 18px;
+    position: relative;
+    & > span {
+      position: absolute;
+      width: 100%;
+      bottom: 0;
+      writing-mode: vertical-rl;
+      text-orientation: upright;
+    }
+  }
+  /*滚动数字设置*/
+  .number-item {
+    width: 41px;
+    height: 42px;
+    /* 背景图片 */
+    // background: url(/images/text-bg-blue.png) no-repeat center center;
+    // background-size: 100% 100%;
+    // background: #ccc;
+    list-style: none;
+    margin-right: 5px;
+    // background:rgba(250,250,250,1);
+    border-radius: 4px;
+    border: 3px solid rgb(221, 221, 221);
+    & > span {
+      position: relative;
+      display: inline-block;
+      margin-right: 10px;
+      width: 100%;
+      height: 100%;
+      writing-mode: vertical-rl;
+      text-orientation: upright;
+      overflow: hidden;
+      & > i {
+        font-style: normal;
+        position: absolute;
+        top: 11px;
+        left: 50%;
+        transform: translate(-50%, -1%);
+        transition: transform 1s ease-in-out;
+        letter-spacing: 10px;
+      }
+    }
+  }
+  .number-item:last-child {
+    margin-right: 0;
+  }
+}
+.terminal-table {
+  width: 100%;
+  ::v-deep .table {
+    width: 100%;
+    .cell {
+      padding: 0;
+      text-align: center;
+      font-size: 14px;
+      font-family: Helvetica, "Microsoft YaHei";
+      letter-spacing: 0;
+    }
+    .cell-click {
+      cursor: pointer;
+      color: #2d7cff;
+      &.cell-clicked {
+        color: purple;
+      }
+    }
+    .el-table__header-wrapper,
+    .el-table__fixed-header-wrapper {
+      .cell {
+        font-weight: bold;
+        color: #101116;
+      }
+      .has-gutter {
+        tr {
+          .bgl-huang {
+            background: #fcf0b1;
+          }
+        }
+      }
+    }
+    .el-table__body-wrapper,
+    .el-table__fixed-body-wrapper {
+      tr.bgl-hui {
+        td {
+          background: #d2d6df;
+        }
+        &.redBorder {
+          position: relative;
+          &::after {
+            content: "";
+            position: absolute;
+            left: 0;
+            bottom: 0;
+            width: 100%;
+            height: 2px;
+            background: #e83f82;
+          }
+        }
+      }
+      tr.bgl-delayed td {
+        background: #fcf0b1;
+      }
+      tr.bgl-canceled td {
+        background: #f7babe;
+      }
+      td.cell-toUnload {
+        background: lightcoral !important;
+        position: relative;
+        &::after {
+          content: "";
+          display: block;
+          width: 100%;
+          height: 100%;
+          position: absolute;
+          top: 0;
+          left: 0;
+          border: 2px dashed red;
+        }
+      }
+      td.cell-toUnloadNew {
+        background: #fcf0b1 !important;
+        position: relative;
+        &::after {
+          content: "";
+          display: block;
+          width: 100%;
+          height: 100%;
+          position: absolute;
+          top: 0;
+          left: 0;
+          border: 2px dashed #e28913;
+        }
+      }
+      .el-table__cell.is-hidden > * {
+        visibility: visible;
+      }
+    }
+  }
+}
+</style>

+ 1334 - 0
src/views/flightViewManagement/components/flight/index.vue

@@ -0,0 +1,1334 @@
+<!--
+ * @Author: your name
+ * @Date: 2022-01-17 10:39:22
+ * @LastEditTime: 2022-08-12 18:59:39
+ * @LastEditors: your name
+ * @Description: 航班视图
+-->
+<template>
+  <div class="flight-view">
+    <div class="flight-wrapper">
+      <div class="part1">
+        <div class="title">
+          <span class="manageTitle">航班基本信息</span>
+          <!-- <BackButton /> -->
+        </div>
+        <div class="part1-wrapper">
+          <div class="airline">
+            {{ queryData.F1 }}
+            <el-select v-model="selectedAirline" size="mini" class="airline-select">
+              <el-option v-for="airline in airlineList" :key="airline.value" :value="airline.value" :label="airline.label" />
+            </el-select>
+          </div>
+          <div class="part1_info">
+            <div class="part1_info_box">
+              <el-row>
+                <el-col :xs="24" :sm="24" :xl="12">
+                  <span>起飞机场简称:</span>
+                </el-col>
+                <el-col :xs="24" :sm="24" :xl="12">
+                  <span>{{ flightInfo.departureName }}</span>
+                </el-col>
+              </el-row>
+              <!-- <el-row>
+                <el-col :xs="24" :sm="24" :xl="12">
+                  <span>起飞机场三字码:</span>
+                </el-col>
+                <el-col :xs="24" :sm="24" :xl="12">
+                  <span>{{ flightInfo.planDepartureApt }}</span>
+                </el-col>
+              </el-row> -->
+              <el-row>
+                <!-- <el-col :xs="24" :sm="24" :xl="12">
+                  <span>航站楼:{{ flightInfo.departureBuild }}</span>
+                </el-col> -->
+                <el-col :span="24">
+                  <span>分拣转盘:{{ flightInfo.sortCarousel }}</span>
+                </el-col>
+              </el-row>
+              <el-row>
+                <el-col :xs="24" :sm="24" :xl="12">
+                  <span> 日期:{{ flightInfo.planDepartureTime && flightInfo.planDepartureTime.split('T')[0] }} </span>
+                </el-col>
+                <el-col :xs="24" :sm="24" :xl="12">
+                  <span> 时间:{{ flightInfo.planDepartureTime && flightInfo.planDepartureTime.split('T')[1] }} </span>
+                </el-col>
+              </el-row>
+              <el-row>
+                <el-col :xs="24" :sm="24" :xl="12">
+                  <span>登机口:{{ flightInfo.bordingGate }}</span>
+                </el-col>
+                <el-col :xs="24" :sm="24" :xl="12">
+                  <span>停机位:{{ flightInfo.standForDepartrue }}</span>
+                </el-col>
+              </el-row>
+            </div>
+            <i class="part1_info_arrow_right el-icon-caret-right" />
+            <div class="part1_info_box">
+              <!-- <el-row>
+                <el-col :span="12">
+                  <span>航班号:</span>
+                </el-col>
+                <el-col :span="12">
+                  <span>{{ flightInfo.flightNO }}</span>
+                </el-col>
+              </el-row> -->
+              <el-row>
+                <el-col :xs="16" :sm="16" :xl="12">
+                  <span>航班状态:</span>
+                </el-col>
+                <el-col :xs="8" :sm="8" :xl="12">
+                  <!-- <span>{{ flightInfo.flightStatus === null ? "正常" : flightInfo.flightStatus }}</span> -->
+                  <span>{{ flightInfo.flightStateCN }}</span>
+                </el-col>
+              </el-row>
+              <el-row>
+                <el-col :xs="16" :sm="16" :xl="12">
+                  <span>机号:</span>
+                </el-col>
+                <el-col :xs="8" :sm="8" :xl="12">
+                  <span>{{ flightInfo.craftNo }}</span>
+                </el-col>
+              </el-row>
+              <el-row>
+                <el-col :xs="16" :sm="16" :xl="12">
+                  <span>托运旅客数:</span>
+                </el-col>
+                <el-col :xs="8" :sm="8" :xl="12">
+                  <span>{{ flightInfo.count1 }}</span>
+                </el-col>
+              </el-row>
+              <el-row>
+                <el-col :xs="16" :sm="16" :xl="12">
+                  <span>终点行李数:</span>
+                </el-col>
+                <el-col :xs="8" :sm="8" :xl="12">
+                  <span>{{ flightInfo.count2 }}</span>
+                </el-col>
+              </el-row>
+              <!-- <el-row>
+                <el-col :xs="18" :sm="18" :xl="12">
+                  <span>中转进行李数:</span>
+                </el-col>
+                <el-col :xs="6" :sm="6" :xl="12">
+                  <span>{{ flightInfo.count4 }}</span>
+                </el-col>
+              </el-row>
+              <el-row>
+                <el-col :xs="18" :sm="18" :xl="12">
+                  <span>中转出行李数:</span>
+                </el-col>
+                <el-col :xs="6" :sm="6" :xl="12">
+                  <span>{{ flightInfo.count3 }}</span>
+                </el-col>
+              </el-row> -->
+            </div>
+            <i class="part1_info_arrow_right el-icon-caret-right" />
+            <div class="part1_info_box">
+              <el-row>
+                <el-col :xs="24" :sm="24" :xl="12">
+                  <span>降落机场简称:</span>
+                </el-col>
+                <el-col :xs="24" :sm="24" :xl="12">
+                  <span>{{ flightInfo.landingName }}</span>
+                </el-col>
+              </el-row>
+              <!-- <el-row>
+                <el-col :xs="24" :sm="24" :xl="12">
+                  <span>降落机场三字码:</span>
+                </el-col>
+                <el-col :xs="24" :sm="24" :xl="12">
+                  <span>{{ flightInfo.planLandingApt }}</span>
+                </el-col>
+              </el-row> -->
+              <!-- <el-row>
+                <el-col :span="12">
+                  <span>航站楼:</span>
+                </el-col>
+                <el-col :span="12">
+                  <span>{{ flightInfo.landingBuild }}</span>
+                </el-col>
+              </el-row> -->
+              <el-row>
+                <el-col :xs="24" :sm="24" :xl="12">
+                  <span> 日期:{{ flightInfo.expectLandingTime && flightInfo.expectLandingTime.split('T')[0] }} </span>
+                </el-col>
+                <el-col :xs="24" :sm="24" :xl="12">
+                  <span> 时间:{{ flightInfo.expectLandingTime && flightInfo.expectLandingTime.split('T')[1] }} </span>
+                </el-col>
+              </el-row>
+              <el-row>
+                <el-col :xs="16" :sm="16" :xl="12">
+                  <span>提取转盘:</span>
+                </el-col>
+                <el-col :xs="8" :sm="8" :xl="12">
+                  <span>{{ flightInfo.carousel }}</span>
+                </el-col>
+              </el-row>
+              <el-row>
+                <el-col :xs="16" :sm="16" :xl="12">
+                  <span>停机位:</span>
+                </el-col>
+                <el-col :xs="8" :sm="8" :xl="12">
+                  <span>{{ flightInfo.standForLanding }}</span>
+                </el-col>
+              </el-row>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="part2">
+        <div class="title">
+          <span class="manageTitle">航班容器列表</span>
+          <img class="btn-square btn-shadow" src="@/assets/baggage/ic_export.png" title="导出" @click="exportHandler('containerTable', '航班容器列表')" />
+        </div>
+        <el-table ref="containerTable" :data="containerTableData" border style="width: 100%" height="290px" stripe size="mini" show-summary :summary-method="summaryMethod" :header-cell-style="{ color: '#101116' }" :row-class-name="rowClass" :cell-class-name="cellClass" @cell-click="cellClickHandler">
+          <el-table-column v-for="col in containerTableColumn" :key="col.id" :prop="col.prop" :label="col.label" :width="col.width" :align="col.align || 'center'" :show-overflow-tooltip="true" />
+        </el-table>
+      </div>
+      <!-- <div
+        class="part3"
+        style="padding-right: 4px"
+      >
+        <div class="title">中转进</div>
+        <el-table
+          ref="transferInBaggageTable"
+          :data="transferInBaggageTableData"
+          border
+          style="width: 100%"
+          height="162"
+          stripe
+          size="mini"
+          show-summary
+          :summary-method="summaryMethod"
+          :header-cell-style="{ color: '#101116' }"
+          :cell-class-name="cellClass"
+          @cell-click="cellClickHandler"
+        >
+          <el-table-column
+            v-for="col in transferInTableColumn"
+            :key="col.id"
+            :prop="col.prop"
+            :label="col.label"
+            :align="col.align || 'center'"
+            :show-overflow-tooltip="true"
+          />
+        </el-table>
+      </div>
+      <div
+        class="part3"
+        style="padding-left: 4px"
+      >
+        <div class="title">中转出</div>
+        <el-table
+          ref="transferOutBaggageTable"
+          :data="transferOutBaggageTableData"
+          border
+          style="width: 100%"
+          height="162"
+          stripe
+          size="mini"
+          show-summary
+          :summary-method="summaryMethod"
+          :header-cell-style="{ color: '#101116' }"
+          :cell-class-name="cellClass"
+          @cell-click="cellClickHandler"
+        >
+          <el-table-column
+            v-for="col in transferOutTableColumn"
+            :key="col.id"
+            :prop="col.prop"
+            :label="col.label"
+            :align="col.align || 'center'"
+            :show-overflow-tooltip="true"
+          />
+        </el-table>
+      </div> -->
+      <div class="drag-mask" :class="{ active: dragActive }" />
+      <div class="part4" :style="draggableStyle">
+        <div ref="dragBox" class="drag-box" :class="{ active: dragActive }">
+          <i class="drag-icon el-icon-d-caret" />
+          <div class="drag-line" :style="dragLineStyle" />
+        </div>
+        <div class="title">
+          <span class="manageTitle">航班行李列表</span>
+          <div class="filter-select">
+            <span class="label">快捷筛选</span>
+            <!-- <el-select
+              v-model="selectedFilter"
+              size="mini"
+              placeholder="无"
+              clearable
+            >
+              <el-option
+                v-for="option in filterSelectOptions"
+                :key="option.value"
+                :value="option.value"
+                :label="option.label"
+              />
+            </el-select> -->
+            <el-cascader v-model="selectedFilter" :options="filterSelectOptions" :props="{ expandTrigger: 'hover', checkStrictly: true }" size="mini" placeholder="无" clearable />
+          </div>
+          <TimeZoneSelector />
+          <img class="btn-square btn-shadow" src="@/assets/baggage/ic_export.png" title="导出" @click="exportHandler('flightBaggageTable', '航班行李列表')" />
+          <img class="btn-square btn-shadow" src="@/assets/baggage/ic_setting.png" title="列设置" @click="show" />
+        </div>
+        <el-table ref="flightBaggageTable" :data="dealedTableData" border style="width: 100%" height="calc(100% - 64px)" stripe size="mini" show-summary :summary-method="summaryRow(dealedTableData.length)" :header-cell-class-name="headerCellClass" :header-cell-style="{ color: '#101116' }" :row-class-name="rowClass" :cell-class-name="cellClass" @cell-click="cellClickHandler">
+          <el-table-column v-for="col in tableColsCopy" :key="col.index" :prop="col.prop" :label="col.label" :align="col.align || 'center'" :width="col.width" :fixed="col.fixed" :formatter="tableFormat">
+            <template #header>
+              <TableHeaderCell :label="col.label" :filter-options="flightBaggageTableFilters[col.prop]" :filter-values.sync="filterValues[col.prop]" :sortable="col.sortable" :sort-rule.sync="tableDataSortRules[col.prop]" />
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </div>
+    <!--列设置-->
+    <Dialog :flag="dialogFlag" class="dialog-check-group">
+      <div class="dialog-wrapper">
+        <div class="title">列设置</div>
+        <div class="content">
+          <el-tree ref="columnSetTree" :data="tableCols" :class="colsCheckClass" show-checkbox node-key="index" :default-expand-all="true" :props="{
+              label: 'label',
+              children: 'children'
+            }" :default-checked-keys="checkedKeysTemp" @check="handleCheck" />
+        </div>
+        <div class="foot right t30">
+          <el-button size="medium" class="r24" type="primary" @click="onCheck('flightBaggageTableData')">确定</el-button>
+          <el-button size="medium" @click="hide">取消</el-button>
+        </div>
+      </div>
+    </Dialog>
+  </div>
+</template>
+<script>
+// import BackButton from '@/components/BackButton'
+import Dialog from '@/layout/components/Dialog/index.vue'
+import TimeZoneSelector from '@/components/TimeZoneSelector'
+import { myQuery } from '@/api/dataIntegration'
+import tableColsMixin from '../../mixins/tableCols'
+import timeZoneMixin from '../../mixins/timeZone'
+import TableHeaderCell from '@/components/TableHeaderCell'
+import { setTableFilters } from '@/utils/table'
+import { mapGetters } from 'vuex'
+import { throttledExportToExcel } from '@/utils/table'
+
+export default {
+  name: 'FlightView',
+  components: {
+    Dialog,
+    TimeZoneSelector,
+    TableHeaderCell
+  },
+  mixins: [tableColsMixin, timeZoneMixin],
+  data () {
+    return {
+      loading: false,
+      airlineList: [],
+      selectedAirline: '',
+      queryData: {},
+      flightInfo: {},
+      warningRules: [],
+      debounceTime: 300,
+      containerTableColumn: [
+        { label: '容器编号', prop: 'containerNumber', width: 100 },
+        { label: '类型', prop: 'style' },
+        { label: '行李数', prop: 'numberOfBags' },
+        { label: '舱位', prop: 'containerSpace' }
+      ],
+      // transferInTableColumn: [
+      //   { label: '航班号', prop: 'preFlightNO' },
+      //   { label: '日期', prop: 'preFlightDate' },
+      //   { label: '时间', prop: 'flightTime' },
+      //   { label: '始发站', prop: 'planDepartureApt' },
+      //   { label: '航班状态', prop: 'flightStatus' },
+      //   { label: '中转数', prop: 'totalNumber' }
+      // ],
+      // transferOutTableColumn: [
+      //   { label: '航班号', prop: 'transferFlightNO' },
+      //   { label: '日期', prop: 'transferFlightDate' },
+      //   { label: '时间', prop: 'flightTime' },
+      //   { label: '始发站', prop: 'planDepartureApt' },
+      //   { label: '目的站', prop: 'planLandingApt' },
+      //   { label: '中转数', prop: 'transferNumber' }
+      // ],
+      tableCols: [
+        {
+          label: '旅客姓名',
+          prop: 'passengerName',
+          width: 140,
+          fixed: 'left',
+          filterable: true,
+          sortable: true
+        },
+        {
+          label: '行李牌号',
+          prop: 'B2',
+          width: 120,
+          fixed: 'left',
+          filterable: true,
+          sortable: true
+        },
+        {
+          label: '特殊行李类型',
+          prop: 'E1',
+          width: 120,
+          filterable: true,
+          sortable: true
+        },
+        {
+          label: '容器编号',
+          prop: 'U_Device_ID',
+          width: 120,
+          filterable: true,
+          sortable: true
+        },
+        { label: '装载序号', prop: 'LoadSN' },
+        { label: '值机', prop: 'checkInTime', width: 140 },
+        {
+          label: '状态',
+          prop: 'dataState',
+          filterable: true,
+          sortable: true
+        },
+        { label: '安检', prop: 'securityTime', width: 140 },
+        { label: '分拣', prop: 'sortTime', width: 140 },
+        { label: '装车', prop: 'loadTime', width: 140 },
+        { label: '装机', prop: 'inflTime', width: 140 },
+        {
+          label: '中转进航班',
+          prop: 'transferInFlightNo',
+          filterable: true,
+          sortable: true,
+          width: 105
+        },
+        {
+          label: '中转出航班',
+          prop: 'O01',
+          filterable: true,
+          sortable: true,
+          width: 105
+        },
+        {
+          label: '卷宗号',
+          prop: 'L1',
+          filterable: true,
+          sortable: true,
+          width: 100
+        }
+      ],
+      containerTableData: [], // 容器统计
+      transferInBaggageTableData: [], // 中转进
+      transferOutBaggageTableData: [], // 中转出
+      flightBaggageTableData: [], // 行李列表
+      flightBaggageTableFilters: {},
+      filterValues: {},
+      tableDataSortRules: {},
+      warningContainers: [],
+      dragHeight: 0,
+      dragY: 0,
+      dragActive: false,
+      filterSelectOptions: [
+        {
+          label: '值机',
+          value: 'checkInTime'
+        },
+        {
+          label: '未激活',
+          value: 'unActive' // STATUS为'I'
+        },
+        {
+          label: '预计装载',
+          value: 'preLoad' // STATUS不为'I',isDEL不为'del'
+        },
+        {
+          label: '已装载',
+          value: 'loaded' // 'loadTime'不为空,isDEL不为'DEL'
+        },
+        {
+          label: '安检',
+          value: 'securityTime'
+        },
+        {
+          label: '分拣',
+          value: 'sortTime'
+        },
+        {
+          label: '装车',
+          value: 'loadTime'
+        },
+        {
+          label: '装机',
+          value: 'inflTime'
+        },
+        {
+          label: '到达',
+          value: 'arrivedID' // 1/0
+        },
+        {
+          label: '卸载',
+          value: 'unloadID' // 1/0
+        },
+        {
+          label: '终点到达',
+          value: 'destination' // 'arrivedID'为1,transferFlightNO为null
+        },
+        {
+          label: '容器',
+          value: 'inContainer' // 有容器ID
+        },
+        {
+          label: '散装',
+          value: 'FBULK' // 容器ID为'FBULK'
+        },
+        {
+          label: '待翻减',
+          value: 'toUnload' // 装车或装机后,isDEL为'DEL',waitOFF为1
+        },
+        {
+          label: '已翻减',
+          value: 'unloaded' // 装车或装机后,isDEL为'DEL',waitOFF为0
+        },
+        {
+          label: '取消托运',
+          value: 'canceled' // isDEL为'DEL'
+        },
+        {
+          label: '无BSM',
+          value: 'NOBSM' // 1/0
+        },
+        {
+          label: '预警',
+          value: 'warning'
+        },
+        {
+          label: '报警',
+          value: 'alarm'
+        },
+        {
+          label: '中转进航班',
+          value: 'inFlightNO',
+          children: []
+        },
+        {
+          label: '中转出航班',
+          value: 'transferFlightNO',
+          children: []
+        }
+      ],
+      selectedFilter: []
+    }
+  },
+  computed: {
+    ...mapGetters(['clickedCells']),
+    fasterFilteredTableData () {
+      const [key, value] = this.selectedFilter
+      if (!key) {
+        return this.flightBaggageTableData
+      }
+      const that = this
+      function isWarning (row) {
+        if (row['STATUS'] === 'I' || row['loadTime'] || row['isDEL'] === 'DEL') {
+          return 0
+        }
+        const planDepartureTime = that.flightInfo.planDepartureTime
+        if (!planDepartureTime) {
+          return 0
+        }
+        const currentTime = new Date().getTime()
+        const departureTime = new Date(planDepartureTime.replace('T', ' ')).getTime()
+        if (currentTime > departureTime) {
+          return 0
+        }
+
+        let flag = 0
+        const duration = departureTime - currentTime
+        that.warningRules.some(rule => {
+          if (!rule.startDate || !rule.endDate) {
+            return false
+          }
+          const ruleStartTime = new Date(rule.startDate).getTime()
+          const ruleEndTime = new Date(rule.endDate).getTime()
+          if (currentTime < ruleStartTime || currentTime > ruleEndTime) {
+            return false
+          }
+          if (rule.alarmDuration && duration < rule.alarmDuration * 60 * 1000) {
+            flag = 1
+            return true
+          } else if (rule.warningDuration && duration < rule.warningDuration * 60 * 1000) {
+            flag = 2
+            return true
+          } else {
+            return false
+          }
+        })
+        return flag
+      }
+      return this.flightBaggageTableData.filter(row => {
+        switch (key) {
+          case 'arrivedID':
+          case 'unloadID':
+          case 'NOBSM':
+            return row[key] === 1
+          case 'unActive':
+            return row['STATUS'] === 'I'
+          case 'preLoad':
+            return row['STATUS'] !== 'I' && row['isDEL'] !== 'DEL'
+          case 'loaded':
+            return row['loadTime'] && row['isDEL'] !== 'DEL'
+          case 'destination':
+            return row['arrivedID'] === 1 && !row['transferFlightNO']
+          case 'inContainer':
+            return row['U_Device_ID'] && row['U_Device_ID'] !== 'FBULK'
+          case 'FBULK':
+            return row['U_Device_ID'] === 'FBULK'
+          case 'toUnload':
+            return (row['loadTime'] || row['inflTime']) && row['isDEL'] === 'DEL' && row['waitOFF'] === 1
+          case 'unloaded':
+            return (row['loadTime'] || row['inflTime']) && row['isDEL'] === 'DEL' && row['waitOFF'] !== 1
+          case 'canceled':
+            return row['isDEL'] === 'DEL'
+          case 'inFlightNO':
+          case 'transferFlightNO':
+            return value ? row[key] === value : (row[key] ?? '') !== ''
+          case 'warning':
+            return isWarning(row) === 2
+          case 'alarm':
+            return isWarning(row) === 1
+          default:
+            return (row[key] ?? '') !== ''
+        }
+      })
+    },
+    dealedTableData () {
+      const filtered = this.fasterFilteredTableData.filter(item => {
+        let flag = true
+        Object.entries(this.filterValues).forEach(([key, arr]) => {
+          if (arr.length && !arr.includes(String(item[key]))) {
+            flag = false
+          }
+        })
+        return flag
+      })
+      const sortRules = Object.entries(this.tableDataSortRules).reduce(
+        (pre, [key, value]) => {
+          if (value) {
+            pre[0].push(key)
+            value = value === 'ascending' ? 'asc' : 'desc'
+            pre[1].push(value)
+          }
+          return pre
+        },
+        [[], []]
+      )
+      return this._.orderBy(filtered, sortRules[0], sortRules[1])
+    },
+    draggableStyle () {
+      return {
+        height: `calc(100vh - 80px - 64px - 16px - 290px + ${this.dragHeight}px)`
+      }
+    },
+    dragLineStyle () {
+      return {
+        transform: `translateY(${this.dragHeight - this.dragY}px)`
+      }
+    }
+  },
+  watch: {
+    loading (val) {
+      if (val) {
+        this.fullscreenLoading = this.$loading({
+          lock: true,
+          text: '加载中',
+          spinner: 'el-icon-loading',
+          background: 'rgba(0, 0, 0, 0.7)'
+        })
+      } else {
+        this.fullscreenLoading?.close()
+      }
+    },
+    selectedAirline (val) {
+      if (!val) {
+        return
+      }
+      const { flightNO, flightDate } = this.queryData
+      const [departureAirport, landingAirport] = val.split('-')
+      this.queryAll({ flightNO, flightDate, departureAirport, arriveAirport: landingAirport })
+    },
+    selectedFilter (val) {
+      const { path, query } = this.$route
+      const newQuery = { ...query }
+      delete newQuery.fastFilter
+      if (val.length) {
+        newQuery.fastFilter = val.join(',')
+      }
+      this.$router.replace({
+        path,
+        query: newQuery
+      })
+    }
+  },
+  created () {
+    const { F1, F2, departureAirport, landingAirport } = this.$route.query
+    if (F1 && F2) {
+      this.queryAirline({ F1, F2 })
+      this.queryData = {
+        F1,
+        F2,
+        departureAirport,
+        landingAirport
+      }
+    }
+    Object.values(this.tableCols).forEach(({ prop, filterable, sortable }) => {
+      if (filterable) {
+        this.$set(this.flightBaggageTableFilters, prop, [])
+        this.$set(this.filterValues, prop, [])
+      }
+      if (sortable) {
+        this.$set(this.tableDataSortRules, prop, '')
+      }
+    })
+  },
+  mounted () {
+    this.setDragBox()
+  },
+  updated () {
+    this.resizeHandler()
+  },
+  activated () {
+    const { fastFilter } = this.$route.query
+    this.selectedFilter = fastFilter ? fastFilter.split(',') : []
+    this.getWarningData()
+    this.resizeHandler()
+    this.debouncedResizeHandler = this._.debounce(this.resizeHandler, this.debounceTime)
+    window.addEventListener('resize', this.debouncedResizeHandler)
+  },
+  deactivated () {
+    this.fullscreenLoading?.close()
+    window.removeEventListener('resize', this.debouncedResizeHandler)
+  },
+  methods: {
+    setDragBox () {
+      const dragBox = this.$refs['dragBox']
+      const offsetTop = dragBox.offsetParent.offsetTop
+      const that = this
+      let mousedownY
+      let dragY
+      let dragHeight
+      function mousemoveHandler (e) {
+        e.stopPropagation()
+        e.preventDefault()
+        dragY = dragHeight + mousedownY - e.screenY
+        dragY = dragY < 0 ? 0 : dragY > offsetTop ? offsetTop : dragY
+        that.dragY = dragY
+      }
+      function mouseupHandler (e) {
+        e.stopPropagation()
+        e.preventDefault()
+        that.dragHeight = that.dragY
+        that.dragY = 0
+        that.dragActive = false
+        document.removeEventListener('mousemove', mousemoveHandler)
+        document.removeEventListener('mouseup', mouseupHandler)
+      }
+      dragBox.addEventListener('mousedown', e => {
+        that.dragActive = true
+        mousedownY = e.screenY
+        dragHeight = that.dragHeight
+        that.dragY = dragHeight
+        document.addEventListener('mousemove', mousemoveHandler)
+        document.addEventListener('mouseup', mouseupHandler)
+      })
+    },
+    resizeHandler () {
+      this.$refs['containerTable']?.doLayout()
+      // this.$refs['transferInBaggageTable']?.doLayout()
+      // this.$refs['transferOutBaggageTable']?.doLayout()
+      this.$refs['flightBaggageTable']?.doLayout()
+    },
+    setfastFilterValues () {
+      const inFlightNOList = new Set()
+      const transferFlightNOList = new Set()
+      this.flightBaggageTableData.forEach(item => {
+        item['inFlightNO'] && inFlightNOList.add(item['inFlightNO'])
+        item['transferFlightNO'] && transferFlightNOList.add(item['transferFlightNO'])
+      })
+      this.filterSelectOptions.splice(
+        this.filterSelectOptions.length - 2,
+        2,
+        {
+          label: '中转进航班',
+          value: 'inFlightNO',
+          children: [...inFlightNOList].map(value => ({
+            label: value,
+            value
+          }))
+        },
+        {
+          label: '中转出航班',
+          value: 'transferFlightNO',
+          children: [...transferFlightNOList].map(value => ({
+            label: value,
+            value
+          }))
+        }
+      )
+    },
+    rowClass ({ row, rowIndex }) {
+      const classes = []
+      if (
+        this.warningContainers.includes(row['containerNumber']) ||
+        ((row['loadTime'] || row['inflTime']) && row['isDEL'] === 'DEL' && row['waitOFF'] === 1)
+      ) {
+        classes.push('row-warning')
+      }
+      if (row['Status'] === 'DEL') {
+        classes.push('bgl-deleted')
+      }
+      return classes.join(' ')
+    },
+    // 给表头单元格加上 ascending 或 descending 使用 element 自带的排序箭头变色
+    headerCellClass ({ row, column, rowIndex, columnIndex }) {
+      const classes = []
+      const rule = this.tableDataSortRules[column.property]
+      if (rule) {
+        classes.push(rule)
+      }
+      return classes.join(' ')
+    },
+    cellClass ({ row, column, rowIndex, columnIndex }) {
+      const classes = []
+      if (
+        ['checkInTime', 'DealInfo', 'sortLocationMark', 'loadLocationMark', 'inflLocationMark'].includes(
+          column.property
+        )
+      ) {
+        classes.push('pre-line')
+      }
+      if (
+        [
+          'containerNumber',
+          'preFlightNO',
+          // 'totalNumber',
+          'inFlightNO',
+          'transferFlightNO',
+          // 'transferNumber',
+          'PassengerNameUpcase',
+          'BagSN',
+          'U_Device_ID',
+          'fileNumber'
+        ].includes(column.property) &&
+        row[column.property] &&
+        row[column.property] !== 'FBULK'
+      ) {
+        classes.push('cell-click')
+        if (
+          this.clickedCells.some(
+            cell =>
+              cell.pageName === this.$route.name &&
+              Object.entries(cell.row).every(([key, value]) => row[key] === value) &&
+              cell.columnProp === column.property
+          )
+        ) {
+          classes.push('cell-clicked')
+        }
+      }
+      return classes.join(' ')
+    },
+    cellClickHandler (row, column, cell, event) {
+      if (
+        [
+          'containerNumber',
+          'preFlightNO',
+          // 'totalNumber',
+          'inFlightNO',
+          'transferFlightNO',
+          // 'transferNumber',
+          'PassengerNameUpcase',
+          'BagSN',
+          'U_Device_ID',
+          'fileNumber'
+        ].includes(column.property) &&
+        row[column.property] &&
+        row[column.property] !== 'FBULK'
+      ) {
+        this.$store.dispatch('keepAlive/addClickedCell', {
+          row,
+          columnProp: column.property,
+          pageName: this.$route.name
+        })
+        switch (column.property) {
+          case 'containerNumber': {
+            const [departureAirport, landingAirport] = this.selectedAirline.split('-')
+            this.$router.push({
+              path: `${this.$route.path.split('/').slice(0, -1).join('/')}/containerView`,
+              query: {
+                flightNO: this.queryData.flightNO,
+                flightDate: this.queryData.flightDate,
+                departureAirport,
+                landingAirport,
+                containerID: row.containerNumber,
+                containerType: row.style,
+                containerSpace: row.containerSpace
+              }
+            })
+            break
+          }
+          case 'preFlightNO':
+            this.$router.push({
+              path: `${this.$route.path.split('/').slice(0, -1).join('/')}/flightView`,
+              query: {
+                flightNO: row.preFlightNO,
+                flightDate: row.preFlightDate
+              }
+            })
+            break
+          // case 'totalNumber':
+          //   this.$router.push({
+          //     path: '/advance',
+          //     query: {
+          //       flightNO: this.queryData.flightNO,
+          //       startDate: this.queryData.flightDate,
+          //       endDate: this.queryData.flightDate,
+          //       transferArrival: row.preFlightNO
+          //     }
+          //   })
+          //   break
+          case 'inFlightNO':
+            this.$router.push({
+              path: `${this.$route.path.split('/').slice(0, -1).join('/')}/flightView`,
+              query: {
+                flightNO: row.inFlightNO,
+                flightDate: row.inFlightDate
+              }
+            })
+            break
+          case 'transferFlightNO':
+            this.$router.push({
+              path: `${this.$route.path.split('/').slice(0, -1).join('/')}/flightView`,
+              query: {
+                flightNO: row.transferFlightNO,
+                flightDate: row.transferFlightDate
+              }
+            })
+            break
+          // case 'transferNumber':
+          //   this.$router.push({
+          //     path: '/advance',
+          //     query: {
+          //       flightNO: this.queryData.flightNO,
+          //       startDate: this.queryData.flightDate,
+          //       endDate: this.queryData.flightDate,
+          //       transferDeparture: row.transferFlightNO
+          //     }
+          //   })
+          //   break
+          case 'PassengerNameUpcase':
+            this.$store.dispatch('app/setPassengerQueryParams', {
+              flightNO: this.queryData.flightNO,
+              flightDate: this.queryData.flightDate,
+              passengerName: row.PassengerNameUpcase
+            })
+            this.$store.dispatch('app/togglePassengerDialogFlag', true)
+            break
+          case 'BagSN':
+            this.$router.push({
+              path: `${this.$route.path.split('/').slice(0, -1).join('/')}/baggageView`,
+              query: {
+                bagSN: row.BagSN,
+                flightNO: this.queryData.flightNO,
+                flightDate: this.queryData.flightDate
+              }
+            })
+            break
+          case 'U_Device_ID': {
+            const [departureAirport, landingAirport] = this.selectedAirline.split('-')
+            const container = this.containerTableData.find(({ containerNumber }) => containerNumber === row.U_Device_ID)
+            this.$router.push({
+              path: `${this.$route.path.split('/').slice(0, -1).join('/')}/containerView`,
+              query: {
+                flightNO: this.queryData.flightNO,
+                flightDate: this.queryData.flightDate,
+                departureAirport,
+                landingAirport,
+                containerID: row.U_Device_ID,
+                containerType: container.style,
+                containerSpace: container.containerSpace
+              }
+            })
+            break
+          }
+          case 'fileNumber':
+            this.$store.dispatch('app/setAbnormalBaggageQueryParams', {
+              flightNO: this.queryData.flightNO,
+              flightDate: this.queryData.flightDate,
+              bagSN: row.BagSN,
+              fileNumber: row.fileNumber
+            })
+            this.$store.dispatch('app/toggleAbnormalBaggageDialogFlag', true)
+            break
+          default:
+            break
+        }
+      }
+    },
+    // 合计行
+    summaryMethod ({ columns, data }) {
+      const sums = []
+      if (columns.length > 0) {
+        columns.forEach((column, index) => {
+          if (index === 0) {
+            sums[index] = '合计'
+          } else if (
+            // 需要计算的列
+            [
+              'numberOfBags'
+              // 'totalNumber',
+              // 'transferNumber'
+            ].includes(column.property)
+          ) {
+            const values = data.map(item => Number(item[column.property]))
+            if (values.some(value => !isNaN(value))) {
+              sums[index] = values.reduce((prev, curr) => {
+                const value = Number(curr)
+                if (!isNaN(value)) {
+                  return Number(prev) + Number(curr)
+                } else {
+                  return Number(prev)
+                }
+              }, 0)
+            } else {
+              sums[index] = 0
+            }
+          } else {
+            // 过滤某些字段不参与计算
+            sums[index] = '-'
+          }
+        })
+      }
+      return sums
+    },
+    // 统计行数
+    summaryRow (num) {
+      return function () {
+        return ['合计', `共${num}件`]
+      }
+    },
+    exportHandler (refName, tableName) {
+      const table = this.$refs[refName].$el.cloneNode(true)
+      const fileName = `${tableName}-${this.queryData.flightNO}-${this.queryData.flightDate}.xlsx`
+      throttledExportToExcel(table, tableName, fileName)
+    },
+    queryflightInfo (dataContent) {
+      return myQuery({ serviceId: DATACONTENT_ID.flightInfo, dataContent })
+    },
+    queryContainer (dataContent) {
+      return myQuery({ serviceId: DATACONTENT_ID.flightContainer, dataContent })
+    },
+    queryBaggageByFlightNO (dataContent) {
+      return myQuery({ serviceId: DATACONTENT_ID.flightBaggage, dataContent })
+    },
+    async queryAirline (dataContent) {
+      try {
+        this.selectedAirline = ''
+        this.flightInfo = {}
+        this.containerTableData = []
+        this.flightBaggageTableData = []
+        const listValues = await myQuery({ serviceId: 18132, dataContent })
+        this.airlineList = listValues.map(({ airport_of_takeoff, destination_airport, departureBuild, landingBuild }) => ({
+          label: `${airport_of_takeoff}${departureBuild ? `(${departureBuild})` : ''}-${destination_airport}${landingBuild ? `(${landingBuild})` : ''
+            }`,
+          value: `${airport_of_takeoff}-${destination_airport}`
+        }))
+        if (this.airlineList.length) {
+          let currentIndex = this.airlineList.findIndex(
+            ({ value }) => value === `${this.queryData.departureAirport}-${this.queryData.landingAirport}`
+          )
+          currentIndex = currentIndex > -1 ? currentIndex : 0
+          this.selectedAirline = this.airlineList[currentIndex].value
+        } else {
+          this.$message.info('未查询到航班数据')
+        }
+      } catch (error) {
+        this.$message.error('失败')
+      }
+    },
+    async queryAll (dataContent) {
+      this.loading = true
+      this.flightInfo = {}
+      try {
+        const [flightInfo, containerTableDataData, flightBaggageTableData] = await Promise.all([
+          this.queryflightInfo(dataContent),
+          this.queryContainer(dataContent),
+          this.queryBaggageByFlightNO(dataContent)
+        ])
+        if (flightInfo.length) {
+          this.flightInfo = flightInfo[0]
+        } else {
+          this.$message.info('未查询到航班基础数据')
+        }
+        this.containerTableData = containerTableDataData
+        this.warningContainers = []
+        this.flightBaggageTableData = flightBaggageTableData.map(item => {
+          if ((item['loadTime'] || item['inflTime']) && item['isDEL'] === 'DEL' && item['waitOFF'] === 1) {
+            this.warningContainers.push(item['U_Device_ID'])
+          }
+          return item
+        })
+        setTableFilters(this.flightBaggageTableData, this.flightBaggageTableFilters)
+        this.setfastFilterValues()
+      } catch (error) {
+        this.$message.error('失败')
+      }
+      this.loading = false
+    },
+    async getWarningData () {
+      try {
+        const listValues = await myQuery({ serviceId: DATACONTENT_ID.departureWarningId, dataContent: {} })
+        this.warningRules = listValues
+      } catch (error) {
+        this.$message.error('失败')
+      }
+    }
+  }
+}
+</script>
+
+<style
+  lang="scss"
+  scoped
+>
+.flight-view {
+  padding: 0 0 16px 8px;
+  overflow: hidden;
+  background: #dfe3ea;
+  .flight-wrapper {
+    padding-right: 8px;
+    width: 100%;
+    height: calc(100vh - 80px - 16px);
+    display: flex;
+    flex-direction: row;
+    justify-content: flex-start;
+    align-content: flex-start;
+    flex-wrap: wrap;
+    position: relative;
+    .title {
+      padding: 16px 0;
+      display: flex;
+      flex-direction: row;
+      justify-content: space-between;
+      align-items: center;
+      background: #dfe3ea;
+      .manageTitle {
+        flex: 1;
+        height: 32px;
+        margin-right: 0;
+      }
+      .btn-square {
+        margin-left: 20px;
+        &:last-child {
+          margin-right: 30px;
+        }
+      }
+    }
+    .part1 {
+      width: 71.15%;
+      .title {
+        justify-content: flex-start;
+        .manageTitle {
+          flex: 0 1 auto;
+        }
+      }
+      .part1-wrapper {
+        height: 290px;
+        background: #041741;
+        padding: 24px;
+        .airline {
+          font-size: 18px;
+          font-weight: bold;
+          color: #ffffff;
+          ::v-deep .airline-select {
+            .el-input__inner {
+              width: 200px;
+              background-color: transparent;
+              font-size: 18px;
+              font-weight: bold;
+              color: #ffffff;
+            }
+            .el-select__caret {
+              color: #ffffff;
+            }
+          }
+        }
+        .part1_info {
+          width: 100%;
+          display: flex;
+          flex-direction: row;
+          justify-content: space-between;
+          align-items: center;
+          padding-top: 24px;
+
+          .part1_info_box {
+            width: calc(33.333% - 48px);
+            height: 190px;
+            background: #2c416d;
+            padding: 24px 2% 12px;
+            display: flex;
+            flex-direction: column;
+            align-content: space-around;
+            overflow-x: hidden;
+            overflow-y: auto;
+            .el-row {
+              margin-bottom: 24px;
+              &:last-child {
+                margin: 0;
+              }
+              @media only screen and (max-width: 1919px) {
+                .el-col-sm-24:nth-child(2) {
+                  margin-top: 24px;
+                }
+              }
+            }
+            span {
+              display: inline-block;
+              font-size: 16px;
+              font-weight: 400;
+              color: #ffffff;
+            }
+          }
+          .part1_info_arrow_right {
+            font-size: 32px;
+            color: #ffffff;
+          }
+        }
+      }
+    }
+    .part2 {
+      width: calc(100% - 71.15%);
+      padding-left: 16px;
+      .title .btn-square:last-child {
+        margin-right: 22px;
+      }
+    }
+    .part4 {
+      width: 100%;
+      position: absolute;
+      z-index: 100;
+      bottom: 0;
+      .drag-box {
+        position: absolute;
+        top: 0;
+        width: 100%;
+        height: 62px;
+        z-index: 101;
+        // cursor: row-resize;
+        .drag-icon {
+          width: 60px;
+          height: 60px;
+          line-height: 60px;
+          position: absolute;
+          top: 0;
+          right: 0;
+          bottom: 0;
+          left: 0;
+          margin: auto;
+          font-size: 20px;
+          text-align: center;
+          // background: #385086;
+          color: #ffffff;
+          // &:nth-child(1) {
+          //   top: 0;
+          //   border-radius: 0 0 8px 8px;
+          // }
+          // &:nth-child(2) {
+          //   bottom: 0;
+          //   border-radius: 8px 8px 0 0;
+          // }
+        }
+        &:hover,
+        &.active {
+          .drag-icon {
+            color: #2d67e3;
+          }
+        }
+        .drag-line {
+          display: none;
+          width: 100%;
+          height: 0;
+          border-top: 1px dashed #000000;
+          position: absolute;
+          top: 0;
+          z-index: 101;
+        }
+        &.active .drag-line {
+          display: block;
+        }
+      }
+      .title {
+        .manageTitle {
+          flex: 0 1 auto;
+        }
+        ::v-deep .filter-select {
+          margin-left: 24px;
+          flex: 1;
+          height: 32px;
+          display: flex;
+          align-items: center;
+          .label,
+          .el-input__inner {
+            font-family: Helvetica, "Microsoft YaHei";
+          }
+          .label {
+            padding-right: 14px;
+          }
+          .el-cascader {
+            z-index: 102;
+          }
+        }
+        .el-dropdown {
+          height: 30px;
+          z-index: 102;
+        }
+        .btn-square {
+          z-index: 102;
+          &:last-child {
+            margin-right: 30px;
+          }
+        }
+      }
+    }
+    .drag-mask {
+      width: 100%;
+      height: 100%;
+      position: absolute;
+      top: 0;
+      z-index: 99;
+      display: none;
+      &.active {
+        display: block;
+      }
+    }
+  }
+  ::v-deep .el-table {
+    .cell-click {
+      cursor: pointer;
+      color: #2d7cff;
+      &.cell-clicked {
+        color: purple;
+      }
+    }
+    .el-table__body-wrapper,
+    .el-table__fixed-body-wrapper {
+      tr.bgl-deleted {
+        background: #d2d6df;
+        td {
+          background: #d2d6df;
+          font-style: italic;
+        }
+      }
+      .row-warning .el-table__cell {
+        background: lightcoral;
+      }
+    }
+  }
+}
+</style>

+ 18 - 0
src/views/flightViewManagement/index.vue

@@ -0,0 +1,18 @@
+<!--
+ * @Author: Badguy
+ * @Date: 2022-02-23 11:22:45
+ * @LastEditTime: 2022-03-09 16:17:28
+ * @LastEditors: your name
+ * @Description: 行李管理
+ * have a nice day!
+-->
+
+<template>
+  <router-view />
+</template>
+
+<script>
+export default {
+  name: 'BaggageManagement'
+}
+</script>

+ 272 - 0
src/views/flightViewManagement/mixins/form.js

@@ -0,0 +1,272 @@
+/*
+ * @Author: Badguy
+ * @Date: 2022-03-04 14:45:03
+ * @LastEditTime: 2022-06-16 10:51:52
+ * @LastEditors: your name
+ * @Description: 航站视图通用表单部分
+ * have a nice day!
+ */
+
+import { parseTime } from '@/utils'
+// const defaultStartTime = new Date(new Date(new Date().toLocaleDateString()).getTime())
+// const defaultEndTime = new Date(new Date(new Date().toLocaleDateString()).getTime() + 24 * 60 * 60 * 1000 - 1)
+const defaultDate = parseTime(new Date(), '{y}-{m}-{d}')
+const dateValidator = (rule, value, callback) => {
+  if (value && value[0] && value[1]) {
+    callback()
+  } else {
+    callback(new Error('请选择航班日期'))
+  }
+}
+
+export default {
+  data () {
+    return {
+      formData: {
+        // 搜索表单数据
+        currentAirport: [],
+        relatedAirport: [],
+        inboundCarrier: [],
+        outgoingAirline: [],
+        craftType: [],
+        flightAttr: [],
+        flightDate: [defaultDate, defaultDate],
+        // startDate: defaultDate,
+        // endDate: defaultDate,
+        search: ''
+      },
+      dateRangePickerOptions: {
+        onPick: this.dateRangePickHandler,
+        disabledDate: this.dateRangeDisabled
+      },
+      currentAirportList: [],
+      relatedAirportList: [],
+      carrierList: [],
+      craftTypeList: [],
+      flightAttrList: [],
+      currentAirportProps: {
+        // multiple: true,
+        checkStrictly: true,
+        expandTrigger: 'hover',
+        value: 'code3',
+        label: 'name',
+        children: 'builds'
+      },
+      relatedAirportProps: {
+        multiple: true,
+        value: 'code3',
+        label: 'name'
+      },
+      carrierProps: {
+        multiple: true,
+        value: 'code2',
+        label: 'name'
+      },
+      craftTypeProps: {
+        multiple: true,
+        value: 'code3',
+        label: 'name'
+      },
+      flightAttrProps: {
+        multiple: true,
+        value: 'code',
+        label: 'name'
+      },
+      // 表单规则
+      rules: {
+        currentAirport: [{ required: true, message: '请选择当前机场', trigger: ['change', 'blur'] }],
+        // startDate: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
+        // endDate: [{ required: true, message: '请选择结束时间', trigger: 'change' }]
+        flightDate: [{ validator: dateValidator, trigger: ['change', 'blur'] }]
+      }
+    }
+  },
+  computed: {
+    currentAirport () {
+      return this.getSingleData(this.formData.currentAirport)
+    },
+    relatedAirport () {
+      return this.formData.relatedAirport.map(item => item[0])
+    },
+    inboundCarrier () {
+      return this.formData.inboundCarrier.map(item => item[0])
+    },
+    outgoingAirline () {
+      return this.formData.outgoingAirline.map(item => item[0])
+    },
+    craftType () {
+      return this.formData.craftType.map(item => item[0])
+    },
+    flightAttr () {
+      return this.formData.flightAttr.map(item => item[0])
+    },
+    startDate () {
+      // return parseTime(this.formData.startDate).split(' ')[0]
+      // return this.formData.startDate
+      return this.formData.flightDate[0]
+    },
+    endDate () {
+      // return parseTime(this.formData.endDate).split(' ')[0]
+      // return this.formData.endDate
+      return this.formData.flightDate[1]
+    }
+  },
+  watch: {
+    'formData.flightDate': {
+      handler (val) {
+
+        if (val === null) {
+          this.formData.flightDate = ['', '']
+        }
+      },
+      deep: true
+    }
+  },
+  methods: {
+    dateRangePickHandler ({ maxDate, minDate }) {
+      if (!maxDate) {
+        this.pickedDate = minDate
+      } else {
+        this.pickedDate = null
+      }
+    },
+    dateRangeDisabled (date) {
+      return this.pickedDate ? Math.abs(date - this.pickedDate) > 2 * 24 * 60 * 60 * 1000 : false
+    },
+    // 机场数据处理(多选)
+    getMultipleData (arr) {
+      const newArr = []
+      arr.length &&
+        arr.forEach(airport => {
+          const temp = this._.cloneDeep(this.currentAirportList.find(airport1 => airport1.code3 === airport[0]))
+          if (temp) {
+            temp.builds = airport[1] ? [{ name: airport[1] }] : []
+            const item = newArr.find(item => item.code3 === temp.code3)
+            if (item) {
+              item.builds.push(...temp.builds)
+            } else {
+              newArr.push(temp)
+            }
+          }
+        })
+      return newArr
+    },
+    // 机场数据处理(单选)
+    getSingleData (arr) {
+      const newArr = []
+      if (arr.length > 0) {
+        const temp = this._.cloneDeep(this.currentAirportList.find(airport1 => airport1.code3 === arr[0]))
+        if (temp) {
+          temp.builds = arr[1] ? [{ name: arr[1] }] : []
+          newArr.push(temp)
+        }
+      }
+      return newArr
+    },
+    // 清除表单数据
+    formClear (range) {
+      if (range === 'all') {
+        this.formData.currentAirport = []
+        this.currentAirportList = []
+      }
+      this.formData.relatedAirport = []
+      this.relatedAirportList = []
+      this.formData.inboundCarrier = []
+      this.formData.outgoingAirline = []
+      this.carrierList = []
+      this.formData.craftType = []
+      this.craftTypeList = []
+      this.formData.flightAttr = []
+      this.flightAttrList = []
+    },
+    // 当前机场变更
+    setCurrentAirport (data) {
+      this.formClear()
+      if (data.length === 0) {
+        return
+      }
+      const params = {
+        currentAirport: this.currentAirport,
+        startDate: this.startDate,
+        endDate: this.endDate
+      }
+      this.getFormData(params)
+    },
+    // 日期限制
+    // startDateChangeHandler(val) {
+    //   if (!val || !this.endDate) {
+    //     return
+    //   }
+    //   const startDate = new Date(val)
+    //   const endDate = new Date(this.endDate)
+    //   if (startDate > endDate) {
+    //     this.formData.endDate = ''
+    //     this.$message.info('结束时间不能早于开始时间,请重新选择')
+    //   } else if (endDate - startDate > 2 * 24 * 60 * 60 * 1000) {
+    //     this.formData.endDate = ''
+    //     this.$message.info('时间跨度不能超过三天,请重新选择')
+    //   } else {
+    //     this.getTableData()
+    //   }
+    // },
+    // endDateChangeHandler(val) {
+    //   if (!val || !this.startDate) {
+    //     return
+    //   }
+    //   const startDate = new Date(this.startDate)
+    //   const endDate = new Date(val)
+    //   if (startDate > endDate) {
+    //     this.formData.startDate = ''
+    //     this.$message.info('开始时间不能晚于结束时间,请重新选择')
+    //   } else if (endDate - startDate > 2 * 24 * 60 * 60 * 1000) {
+    //     this.formData.startDate = ''
+    //     this.$message.info('时间跨度不能超过三天,请重新选择')
+    //   } else {
+    //     this.getTableData()
+    //   }
+    // },
+    // 搜索
+    onSubmit (data) {
+      this.$refs['form'].validate(valid => {
+        if (valid) {
+          const az = /^[a-zA-Z]+$/
+          const azNum = /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]*$/
+          // const top2 = /^[a-zA-Z]{2}\w*$/
+          const top2 = /^([a-zA-Z][0-9])|([0-9][a-zA-Z])|([a-zA-Z]{2})/
+          const num = /^[0-9]+$/
+          const bagNo = /^[a-zA-Z]{2}[0-9]{6}$/
+          const queryData = {
+            startDate: this.startDate,
+            endDate: this.endDate
+          }
+          if (Number(data) === 1) {
+            queryData['destination'] = this.formData.currentAirport
+          } else {
+            queryData['departureStation'] = this.formData.currentAirport
+          }
+          if (az.test(this.formData.search)) {
+            // 纯字母则为旅客姓名
+            queryData['passengerName'] = this.formData.search
+          } else if (azNum.test(this.formData.search) && top2.test(this.formData.search)&&this.formData.search.length <8) {
+            // 字母加数字且前两位为字母则为航班号
+            queryData['flightNO'] = this.formData.search
+          } else if ((num.test(this.formData.search) && this.formData.search.length === 10)||((bagNo.test(this.formData.search)&&this.formData.search.length >7))) {
+            // 纯数字且位数等于10则为行李牌号
+            queryData['baggageNO'] = this.formData.search
+          } else {
+            // this.$message.error('请输入有效查询信息如航班号、旅客姓名首字母、行李牌号')
+          }
+          this.$router.push({
+            path: '/advance',
+            query: {
+              ...queryData,
+              singleJump: true
+            }
+          })
+        } else {
+          return false
+        }
+      })
+    }
+  }
+}

+ 98 - 0
src/views/flightViewManagement/mixins/tableCols.js

@@ -0,0 +1,98 @@
+/*
+ * @Author: Badguy
+ * @Date: 2022-03-04 11:50:22
+ * @LastEditTime: 2022-03-15 17:56:34
+ * @LastEditors: your name
+ * @Description: 航站视图表格通用部分
+ * have a nice day!
+ */
+
+export default {
+  data() {
+    return {
+      // 筛选后表头
+      tableColsCopy: [],
+      // 列设置弹框选中
+      checkedKeys: [],
+      checkedKeysTemp: [],
+      halfCheckedKeys: [],
+      // 列设置弹框开关
+      dialogFlag: false
+    }
+  },
+  created() {
+    this.initTableCols()
+  },
+  updated() {
+    // table数据更新
+    this.$nextTick(() => {
+      this.$refs.table?.doLayout()
+    })
+  },
+  computed: {
+    colsCheckClass() {
+      return this.tableCols.some(col => col.children?.length) ? 'has-children' : 'no-children'
+    }
+  },
+  methods: {
+    // 列设置-初始化
+    initTableCols() {
+      const that = this
+      function setTableCols(cols) {
+        for (const col of cols) {
+          col.index = that.checkedKeys.length
+          that.checkedKeys.push(that.checkedKeys.length)
+          if (col.children?.length) {
+            setTableCols(col.children)
+          }
+        }
+      }
+      setTableCols(this.tableCols)
+      this.tableColsCopy = this._.cloneDeep(this.tableCols)
+      this.checkedKeysTemp = [...this.checkedKeys]
+    },
+    // 列设置-确定
+    handleCheck(data, checked) {
+      this.checkedKeysTemp = [...checked.checkedKeys]
+      this.halfCheckedKeys = [...checked.halfCheckedKeys]
+    },
+    onCheck(tableDataName = 'tableData') {
+      if (this.dialogFlag === false) {
+        return
+      }
+      this.loading = true
+      const tableDataTemp = this._.cloneDeep(this[tableDataName])
+      this[tableDataName] = []
+      this.dialogFlag = false
+      this.checkedKeys = [...this.checkedKeysTemp]
+      this.tableColsCopy = this.colsFilter(this._.cloneDeep(this.tableCols))
+      setTimeout(() => {
+        if (!this[tableDataName].length) {
+          this[tableDataName] = tableDataTemp
+        }
+        this.loading = false
+      }, 500)
+    },
+    colsFilter(cols) {
+      const temp = cols.filter(col => {
+        if (this.halfCheckedKeys.includes(col.index)) {
+          col.children = this.colsFilter(col.children)
+          return true
+        } else if (this.checkedKeys.includes(col.index)) {
+          return true
+        }
+        return false
+      })
+      return temp
+    },
+    // 弹框展开
+    show() {
+      this.dialogFlag = true
+    },
+    // 弹框关闭
+    hide() {
+      this.dialogFlag = false
+      this.checkedKeysTemp = [...this.checkedKeys]
+    }
+  }
+}

+ 420 - 0
src/views/flightViewManagement/mixins/terminal.js

@@ -0,0 +1,420 @@
+/*
+ * @Author: Badguy
+ * @Date: 2022-03-04 11:41:55
+ * @LastEditTime: 2022-08-26 15:32:54
+ * @LastEditors: your name
+ * @Description: 航站视图通用部分
+ * have a nice day!
+ */
+
+import { mapGetters } from 'vuex'
+import { commonTableCellClass } from '@/utils/table'
+
+export default {
+  data() {
+    return {
+      // 表格数据
+      tableData: [],
+      tableDataFilters: {},
+      filterValues: {},
+      tableDataSortRules: {},
+      spanArr: [],
+      pos: 0,
+      loading: false,
+      computedTableHeight: undefined,
+      debounceTime: 300
+    }
+  },
+  created() {
+    this.setFilterAndSort(this.tableCols)
+  },
+  updated() {
+    this.resizeHandler()
+  },
+  activated() {
+    this.resizeHandler()
+    this.debouncedResizeHandler = this._.debounce(this.resizeHandler, this.debounceTime)
+    window.addEventListener('resize', this.debouncedResizeHandler)
+  },
+  deactivated() {
+    window.removeEventListener('resize', this.debouncedResizeHandler)
+  },
+  computed: {
+    ...mapGetters(['clickedCells']),
+    dealedTableData() {
+      const filtered = this.tableData.filter(item => {
+        let flag = true
+        Object.entries(this.filterValues).forEach(([key, arr]) => {
+          if (arr.length && !arr.includes(String(item[key]))) {
+            flag = false
+          }
+        })
+        return flag
+      })
+      const sortRules = Object.entries(this.tableDataSortRules).reduce(
+        (pre, [key, value]) => {
+          if (value) {
+            pre[0].push(key)
+            value = value === 'ascending' ? 'asc' : 'desc'
+            pre[1].push(value)
+          }
+          return pre
+        },
+        [[], []]
+      )
+      return this._.orderBy(filtered, sortRules[0], sortRules[1])
+    }
+  },
+  watch: {
+    dealedTableData: {
+      handler(val) {
+        this.spanArr = []
+        let contactDot = this.contactDot
+        val.forEach((item, index, arr) => {
+          if (index === 0) {
+            this.spanArr.push(1)
+          } else {
+            if (
+              item['flightNO'] === arr[index - 1]['flightNO'] &&
+              item['flightDate'] === arr[index - 1]['flightDate']
+            ) {
+              this.spanArr[contactDot] += 1
+              this.spanArr.push(0)
+            } else {
+              this.spanArr.push(1)
+              contactDot = index
+            }
+          }
+        })
+      },
+      deep: true
+    }
+  },
+  methods: {
+    // 设置表格高度
+    resizeHandler() {
+      const headerHeight = 80
+      const bottomBlankHeight = 41
+      const formWrapHeight = this.$refs['formWrap'].offsetHeight
+      this.computedTableHeight = `calc(100vh - ${headerHeight + bottomBlankHeight + formWrapHeight}px)`
+      this.$refs.table?.doLayout()
+    },
+    // 设置筛选和排序
+    setFilterAndSort(tableCols) {
+      const self = this
+      Object.values(tableCols).forEach(({ prop, filterable, sortable, children }) => {
+        if (children) {
+          self.setFilterAndSort(children)
+        } else {
+          if (filterable) {
+            self.$set(self.tableDataFilters, prop, [])
+            self.$set(self.filterValues, prop, [])
+          }
+          if (sortable) {
+            self.$set(self.tableDataSortRules, prop, '')
+          }
+        }
+      })
+    },
+    // 合计行
+    summaryMethod({ columns, data }) {
+      const sums = []
+      if (columns.length > 0) {
+        columns.forEach((column, index) => {
+          if (index === 0) {
+            sums[index] = '合计'
+          } else if (index === 1) {
+            sums[index] = '航班数:' + this.tableData.length
+          } else if (
+            // 需要计算的列
+            [
+              'passagernum',
+              'checkNumber',
+              'not_actived',
+              'expect_load',
+              'security_all',
+              'sortNumber',
+              'loadNumber',
+              'boardID',
+              'toUnload',
+              'OFFCount',
+              'delbag',
+              'noBSM',
+              'reach',
+              'did_not_arrive',
+              'special',
+              'claim',
+              'uninstalled',
+              'terminateArrive',
+              'terminatedNotArrived',
+              'delivered',
+              'not_shipped',
+              'container',
+              'bulk',
+              'checkInTravellerNumber',
+              'checkInNumber',
+              'unActive',
+              'preLoad',
+              'noCheckInNumber',
+              'midIn',
+              'checkIns',
+              'projectedLoad',
+              'loadedQuantity',
+              'numberOfDestinationArrivals',
+              'endPointNotReached',
+              'specialQuantity',
+              'numberOfClaims',
+              'numberToBeUninstalled',
+              'terminateArrivalQuantity',
+              'terminateUnreachedQuantity',
+              'quantityShipped',
+              'undeliveredQuantity',
+              'numberOfContainers',
+              'numberOfBulk',
+              'inTransferBaggageCount',
+              'inTransferredBaggageCount',
+              'outTransferBaggageCount',
+              'outTransferredBaggageCount',
+              'exceptions',
+              'warning'
+            ].includes(column.property)
+          ) {
+            const values = data.map(item => Number(item[column.property]))
+            if (values.some(value => !isNaN(value))) {
+              sums[index] = values.reduce((prev, curr) => {
+                const value = Number(curr)
+                if (!isNaN(value)) {
+                  return Number(prev) + Number(curr)
+                } else {
+                  return Number(prev)
+                }
+              }, 0)
+            } else {
+              sums[index] = 0
+            }
+          } else {
+            // 过滤某些字段不参与计算
+            sums[index] = '-'
+          }
+        })
+      }
+      return sums
+    },
+    cellClass({ row, column, rowIndex, columnIndex }) {
+      const classes = commonTableCellClass({
+        row,
+        column,
+        rowIndex,
+        columnIndex
+      })
+      if (
+        [
+          'flightNO',
+          'preFlightNO',
+          'inTransferBaggageCount',
+          'inTransferredBaggageCount',
+          'outTransferBaggageCount',
+          'outTransferredBaggageCount',
+          'toUnload',
+          'OFFCount',
+          'checkInNumber',
+          'unActive',
+          'preLoad',
+          'warning',
+          'midIn',
+          'noCheckInNumber',
+          'checkNumber',
+          'sortNumber',
+          'loadNumber',
+          'boardID',
+          'checkIns',
+          'terminateArrivalQuantity',
+          'projectedLoad',
+          'loadedQuantity',
+          'numberOfDestinationArrivals',
+          'uninstalled',
+          'numberOfContainers',
+          'numberOfBulk',
+          'noBSM'
+        ].includes(column.property) &&
+        row[column.property]
+      ) {
+        classes.push('cell-click')
+        if (
+          this.clickedCells.some(
+            cell =>
+              cell.pageName === this.$route.name &&
+              Object.entries(cell.row).every(([key, value]) => row[key] === value) &&
+              cell.columnProp === column.property
+          )
+        ) {
+          classes.push('cell-clicked')
+        }
+      }
+      if (column.property === 'toUnload' && row[column.property]) {
+        classes.push('cell-toUnload')
+      }
+      if (column.property === 'warning' && row['warningState'] && row['warningState'] == 2) {
+        classes.push('cell-toUnload')
+      }
+      if (column.property === 'warning' && row['warningState'] && row['warningState'] == 1) {
+        classes.push('cell-toUnloadNew')
+      }
+      if (column.property === 'outTransferredBaggageCount' && row['warningState'] && row['warningState'] == 2) {
+        classes.push('cell-toUnload')
+      }
+      if (column.property === 'outTransferredBaggageCount' && row['warningState'] && row['warningState'] == 1) {
+        classes.push('cell-toUnloadNew')
+      }
+      if (column.property === 'outTransferBaggageCount' && row['sharpSign']) {
+        classes.push('cell-toUnloadNew')
+      }
+      if (column.property === 'inTransferredBaggageCount' && row['warningState'] && row['warningState'] == 2) {
+        classes.push('cell-toUnload')
+      }
+      if (column.property === 'inTransferredBaggageCount' && row['warningState'] && row['warningState'] == 1) {
+        classes.push('cell-toUnloadNew')
+      }
+      if (column.property === 'inTransferBaggageCount' && row['sharpSign']) {
+        classes.push('cell-toUnloadNew')
+      }
+      return classes.join(' ')
+    },
+    cellClickHandler(row, column, cell, event) {
+      if (
+        [
+          'flightNO',
+          'preFlightNO',
+          'inTransferBaggageCount',
+          'inTransferredBaggageCount',
+          'outTransferBaggageCount',
+          'outTransferredBaggageCount',
+          'toUnload',
+          'OFFCount',
+          'checkInNumber',
+          'unActive',
+          'preLoad',
+          'warning',
+          'midIn',
+          'noCheckInNumber',
+          'checkNumber',
+          'sortNumber',
+          'loadNumber',
+          'boardID',
+          'checkIns',
+          'terminateArrivalQuantity',
+          'projectedLoad',
+          'loadedQuantity',
+          'numberOfDestinationArrivals',
+          'uninstalled',
+          'numberOfContainers',
+          'numberOfBulk',
+          'noBSM'
+        ].includes(column.property) &&
+        row[column.property]
+      ) {
+        this.$store.dispatch('keepAlive/addClickedCell', {
+          row,
+          columnProp: column.property,
+          pageName: this.$route.name
+        })
+        const path = `${this.$route.path}/flightView`
+        const query = {}
+        switch (column.property) {
+          case 'flightNO':
+            Object.assign(query, {
+              flightNO: row.flightNO,
+              flightDate: row.flightDate
+            })
+            break
+          case 'preFlightNO':
+            Object.assign(query, {
+              flightNO: row.preFlightNO,
+              flightDate: row.preFlightDate
+            })
+            break
+          case 'inTransferBaggageCount':
+          case 'outTransferBaggageCount':
+            Object.assign(query, {
+              flightNO: row.preFlightNO,
+              flightDate: row.preFlightDate,
+              fastFilter: `transferFlightNO,${row.flightNO}`
+            })
+            break
+          case 'inTransferredBaggageCount':
+          case 'outTransferredBaggageCount':
+            Object.assign(query, {
+              flightNO: row.flightNO,
+              flightDate: row.flightDate,
+              fastFilter: `inFlightNO,${row.preFlightNO}`
+            })
+            break
+          case 'warning':
+            Object.assign(query, {
+              flightNO: row.flightNO,
+              flightDate: row.flightDate,
+              fastFilter: row['warningState'] === 1 ? 'warning' : 'alarm'
+            })
+            break
+          default: {
+            const reflect = {
+              toUnload: 'toUnload', // 装车或装机后,isDEL为'DEL',waitOFF为1
+              OFFCount: 'unloaded', // 装车或装机后,isDEL为'DEL',waitOFF为0
+              unActive: 'unActive', // STATUS为'I'
+              preLoad: 'preLoad', // STATUS不为'I',isDEL不为'del'
+              projectedLoad: 'preLoad',
+              midIn: 'inFlightNO',
+              noCheckInNumber: 'canceled', // isDEL为'DEL'
+              noBSM: 'NOBSM', // 1/0
+              checkInNumber: 'checkInTime',
+              checkNumber: 'securityTime',
+              sortNumber: 'sortTime',
+              loadNumber: 'loadTime',
+              boardID: 'inflTime',
+              checkIns: 'checkInTime',
+              numberOfDestinationArrivals: 'arrivedID', // 1/0
+              uninstalled: 'unloadID', // 1/0
+              loadedQuantity: 'loaded', // 'loadTime'不为空,isDEL不为'DEL'
+              terminateArrivalQuantity: 'destination', // 'arrivedID'为1,transferFlightNO为null
+              numberOfContainers: 'inContainer', // 有容器ID
+              numberOfBulk: 'FBULK' // 容器ID为'FBULK'
+            }
+            Object.assign(query, {
+              flightNO: row.flightNO,
+              flightDate: row.flightDate,
+              fastFilter: reflect[column.property]
+            })
+            break
+          }
+        }
+        switch (this.$route.path.split('/').at(-1)) {
+          case 'departure':
+            Object.assign(query, {
+              departureAirport: this.formData.currentAirport ?? '',
+              landingAirport: row.targetAirport ?? ''
+            })
+            break
+          case 'arrival':
+            Object.assign(query, {
+              departureAirport: row.departureAirport ?? '',
+              landingAirport: this.formData.currentAirport ?? ''
+            })
+            break
+          case 'transferDeparture':
+          case 'transferArrival':
+            Object.assign(query, {
+              departureAirport: row.preAirport ?? '',
+              landingAirport: row.targetAirport ?? ''
+            })
+            break
+          default:
+            break
+        }
+        this.$router.push({
+          path,
+          query
+        })
+      }
+    }
+  }
+}

+ 48 - 0
src/views/flightViewManagement/mixins/timeZone.js

@@ -0,0 +1,48 @@
+/*
+ * @Author: Badguy
+ * @Date: 2022-05-17 17:04:32
+ * @LastEditTime: 2022-05-27 15:04:44
+ * @LastEditors: your name
+ * @Description: 时区相关
+ * have a nice day!
+ */
+import { timeInZone } from '@/utils/table'
+import { mapGetters } from 'vuex'
+
+export default {
+  computed: {
+    ...mapGetters(['timeZone'])
+  },
+  methods: {
+    // 表格数据格式化
+    tableFormat(row, column, cellValue) {
+      switch (column.property) {
+        case 'arrivalTime':
+        case 'planDepartureTime':
+          return timeInZone((cellValue ?? '').replace('T', ' '), this.timeZone)
+        case 'actualDepartureTime':
+        case 'actualLandingTime':
+          return timeInZone((cellValue ?? '').replace('T', ' '), this.timeZone).replace(' ', '\n')
+        case 'checkInTime':
+        case 'securityTime':
+        case 'sortTime':
+        case 'loadTime':
+        case 'inflTime':
+          return cellValue ? `${timeInZone(cellValue.split(',')[0], this.timeZone)}\n${cellValue.split(',')[1]}` : ''
+        // return `${cellValue ?? ''}\n${getTimeInZone(row['checkInTime'], this.timeZone)}`
+        // case 'DealInfo':
+        //   return `${cellValue ?? ''}\n${getTimeInZone(row['DealTime'], this.timeZone)}`
+        // case 'sortLocationMark':
+        //   return `${cellValue ?? ''}\n${getTimeInZone(row['sortDealTime'], this.timeZone)}`
+        // case 'loadLocationMark':
+        //   return `${cellValue ?? ''}\n${getTimeInZone(row['loadDealTime'], this.timeZone)}`
+        // case 'inflLocationMark':
+        //   return `${cellValue ?? ''}\n${getTimeInZone(row['inflLoadDealTime'], this.timeZone)}`
+        case 'timeDifference':
+          return cellValue <= -120 ? '-2h+' : cellValue >= 120 ? '2h+' : cellValue
+        default:
+          return cellValue ?? ''
+      }
+    }
+  }
+}