فهرست منبع

运单跟踪节点并行

zhongxiaoyu 1 سال پیش
والد
کامیت
12b5f135a5

+ 135 - 0
src/components/steps/index copy.vue

@@ -0,0 +1,135 @@
+<template>
+  <div class="steps flex">
+    <div v-for="(step, index) in steps" :key="index" class="node">
+      <div v-if="step.flag" class="node-status-success">
+        <span class="icon"></span>
+      </div>
+      <div v-else class="node-status-default"></div>
+      <template v-if="index < steps.length - 1">
+        <div
+          v-if="step.flag && steps[index + 1] && steps[index + 1].flag"
+          class="node-status-success-line"
+        ></div>
+        <div v-else class="node-status-default-line"></div>
+      </template>
+      <div
+        :style="{
+          width: step.labelWidth ? step.labelWidth + 'px' : 60 + 'px',
+          transform: `translateX(-${
+            step.labelWidth ? step.labelWidth / 3 : 60 / 3
+          }px)`,
+        }"
+        class="node-cap"
+      >
+        <div class="node-name">{{ step.name }}</div>
+        <div class="node-info">
+          <span
+            class="node-info-list"
+            v-for="(p, i) in step.descriptions"
+            :key="i"
+            >{{ p }}<br />
+          </span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { PropType } from 'vue'
+
+type StepData = {
+  name: string
+  flag: boolean
+  labelWidth?: number
+  descriptions: string[]
+}
+
+const props = defineProps({
+  steps: {
+    type: Array as PropType<StepData[]>,
+    default: () => [],
+  },
+})
+</script>
+
+<style lang="scss" scoped>
+.steps {
+  padding: 0 13px;
+  .node {
+    position: relative;
+    width: 100%;
+    &:last-child {
+      width: 2%;
+    }
+    &-name {
+      font-size: 14px;
+      font-family: Microsoft YaHei;
+      font-weight: bold;
+      color: #101116;
+    }
+    &-cap {
+      text-align: center;
+      margin-top: 10px;
+      .node-info {
+        display: flex;
+        flex-direction: column;
+        &-list {
+          // font-size: 12px;
+          font-family: DIN, Microsoft YaHei;
+          font-weight: 400;
+          color: #101116;
+          margin-top: 8px;
+        }
+      }
+    }
+  }
+  .node-status-success {
+    position: relative;
+    width: 24px;
+    height: 24px;
+    background: #ffffff;
+    border: 2px solid #d5327b;
+    border-radius: 50%;
+    z-index: 2;
+    .icon {
+      width: 14px;
+      height: 14px;
+      background: #d5327b;
+      border-radius: 50%;
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      margin-top: -7px;
+      margin-left: -7px;
+    }
+  }
+  .node-status-default {
+    width: 24px;
+    height: 24px;
+    background: #ffffff;
+    border: 2px solid #b7b1b4;
+    border-radius: 50%;
+    z-index: 2;
+    position: relative;
+  }
+  .node-status-success-line {
+    height: 6px;
+    background: #d5327b;
+    position: absolute;
+    width: 100%;
+    top: 9px;
+    left: 0;
+    z-index: 1;
+  }
+  .node-status-default-line {
+    height: 6px;
+    background: #b7b1b4;
+    position: absolute;
+    width: 100%;
+    top: 9px;
+    left: 0;
+    z-index: 1;
+  }
+}
+</style>

+ 310 - 94
src/components/steps/index.vue

@@ -1,137 +1,353 @@
 <template>
-  <div class="steps flex">
-    <div v-for="(step, index) in steps" :key="index" class="node">
-      <div v-if="step.flag" class="node-status-success">
-        <span class="icon"></span>
-      </div>
-      <div v-else class="node-status-default"></div>
-      <template v-if="index < steps.length - 1">
+  <div class="steps">
+    <div
+      v-for="(step, index) in steps"
+      :key="index"
+      class="step"
+      :style="stepStyle(index)"
+    >
+      <div
+        v-if="index > 0"
+        :class="[
+          'step-line',
+          'step-line-before',
+          step.preActive ? 'step-line-active' : 'step-line-inactive',
+        ]"
+      ></div>
+      <div
+        v-if="steps.length > 1 && index < steps.length - 1"
+        :class="[
+          'step-line',
+          'step-line-after',
+          step.nextActive ? 'step-line-active' : 'step-line-inactive',
+        ]"
+      ></div>
+      <template v-if="'name' in step">
         <div
-          v-if="step.flag && steps[index + 1] && steps[index + 1].flag"
-          class="node-status-success-line"
-        ></div>
-        <div v-else class="node-status-default-line"></div>
+          :class="[
+            'step-state',
+            step.active ? 'step-state-active' : 'step-state-inactive',
+          ]"
+        >
+          <i class="step-state-icon" />
+        </div>
+        <div class="step-info">
+          <div class="step-info-name">{{ step.name }}</div>
+          <div class="step-info-desc">
+            <span
+              class="step-info-desc-item"
+              v-for="(p, i) in step.descriptions"
+              :key="i"
+              >{{ p }}
+            </span>
+          </div>
+        </div>
       </template>
-      <div
-        :style="{
-          width: step.labelWidth ? step.labelWidth + 'px' : 60 + 'px',
-          transform: `translateX(-${
-            step.labelWidth ? step.labelWidth / 3 : 60 / 3
-          }px)`,
-        }"
-        class="node-cap"
-      >
-        <div class="node-name">{{ step.name }}</div>
-        <div class="node-info">
-          <span
-            class="node-info-list"
-            v-for="(p, i) in step.descriptions"
-            :key="i"
-            >{{ p }}<br />
-            <!-- <br v-if="i == 1" /> -->
-            <!-- <i v-if="i % 2 == 0">/</i> -->
-          </span>
+      <template v-else>
+        <div
+          v-for="(subStep, i) in step.children"
+          :key="i"
+          :class="[
+            'step',
+            'sub-step',
+            i === 0 ? 'sub-step-up' : 'sub-step-down',
+            subStep.active ? 'sub-step-active' : 'sub-step-inactive',
+          ]"
+        >
+          <div
+            v-if="index > 0"
+            :class="[
+              'step-line',
+              'step-line-before',
+              subStep.active ? 'step-line-active' : 'step-line-inactive',
+            ]"
+          ></div>
+          <div
+            v-if="index < steps.length - 1"
+            :class="[
+              'step-line',
+              'step-line-after',
+              subStep.active ? 'step-line-active' : 'step-line-inactive',
+            ]"
+          ></div>
+          <div
+            :class="[
+              'step-state',
+              subStep.active ? 'step-state-active' : 'step-state-inactive',
+            ]"
+          >
+            <i class="step-state-icon" />
+          </div>
+          <div class="step-info">
+            <div class="step-info-name">{{ subStep.name }}</div>
+            <div class="step-info-desc">
+              <span
+                class="step-info-desc-item"
+                v-for="(p, i) in subStep.descriptions"
+                :key="i"
+                >{{ p }}
+              </span>
+            </div>
+          </div>
         </div>
-      </div>
+      </template>
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { PropType } from 'vue'
+import { CSSProperties, PropType } from 'vue'
 
 type StepData = {
   name: string
-  flag: boolean
+  active: boolean
+  preActive?: boolean
+  nextActive?: boolean
   labelWidth?: number
   descriptions: string[]
 }
+type CombinedStepData = {
+  active: boolean
+  children: [StepData, StepData]
+  labelWidth?: number
+  preActive?: boolean
+  nextActive?: boolean
+}
 
 const props = defineProps({
   steps: {
-    type: Array as PropType<StepData[]>,
+    type: Array as PropType<(StepData | CombinedStepData)[]>,
     default: () => [],
   },
 })
+
+const hasMultiple = ref(false)
+
+watchEffect(() => {
+  const steps = props.steps
+  const { length } = steps
+  for (let i = 0; i < length - 1; i++) {
+    const currentStep = steps[i]
+    const nextStep = steps[i + 1]
+    const allActive = currentStep.active && nextStep.active
+    currentStep.nextActive = nextStep.preActive = allActive
+
+    if ('children' in currentStep || 'children' in nextStep) {
+      hasMultiple.value = true
+    }
+  }
+})
+
+const stepStyle = (index: number) => {
+  const style: CSSProperties = {}
+  const steps = props.steps
+  const { length } = steps
+  let width: string | number = 0
+  if (length === 1) {
+    width = '100%'
+  }
+  if (length === 2) {
+    width = '50%'
+  }
+  if (length > 2) {
+    const splited = 100 / ((length - 2) * 2 + 2)
+    if (index === 0 || index === length - 1) {
+      width = `${splited}%`
+    } else {
+      width = `${splited * 2}%`
+    }
+  }
+  style.width = width
+  if (!hasMultiple.value) {
+    style.transform = 'translate(0, -30%)'
+  }
+  return style
+}
 </script>
 
 <style lang="scss" scoped>
 .steps {
+  height: calc(100% - 16px - 10px);
   padding: 0 13px;
-  .node {
+  display: flex;
+  align-items: center;
+  .step {
+    height: 100%;
     position: relative;
-    width: 100%;
+    &-line {
+      height: 6px;
+      position: absolute;
+      width: 50%;
+      top: 50%;
+      transform: translate(0, -50%);
+      z-index: 1;
+      &-before {
+        left: 0;
+      }
+      &-after {
+        right: 0;
+      }
+      &-active {
+        background: #d5327b;
+      }
+      &-inactive {
+        background: #b7b1b4;
+      }
+    }
+    &:first-child,
     &:last-child {
-      width: 2%;
+      .step-line {
+        width: 100%;
+      }
     }
-    &-name {
-      font-size: 14px;
-      font-family: Microsoft YaHei;
-      font-weight: bold;
-      color: #101116;
+    &-state {
+      position: absolute;
+      width: 24px;
+      height: 24px;
+      background: #ffffff;
+      border: 2px solid;
+      border-radius: 50%;
+      z-index: 2;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      .step-state-icon {
+        width: 14px;
+        height: 14px;
+        border-radius: 50%;
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        margin-top: -7px;
+        margin-left: -7px;
+      }
+      &-active {
+        border-color: #d5327b;
+        .step-state-icon {
+          background-color: #d5327b;
+        }
+      }
+      &-inactive {
+        border-color: #b7b1b4;
+        .step-state-icon {
+          background-color: #b7b1b4;
+        }
+      }
     }
-    &-cap {
+    &-info {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, 24px);
       text-align: center;
-      margin-top: 10px;
-      .node-info {
+      &-name {
+        font-size: 14px;
+        font-family: Microsoft YaHei;
+        font-weight: bold;
+        color: #101116;
+      }
+      &-desc {
         display: flex;
         flex-direction: column;
-        &-list {
-          // font-size: 12px;
+        &-item {
+          height: 22px;
+          line-height: 22px;
           font-family: DIN, Microsoft YaHei;
           font-weight: 400;
           color: #101116;
-          margin-top: 8px;
         }
       }
     }
-  }
-  .node-status-success {
-    position: relative;
-    width: 24px;
-    height: 24px;
-    background: #ffffff;
-    border: 2px solid #d5327b;
-    border-radius: 50%;
-    z-index: 2;
-    .icon {
-      width: 14px;
-      height: 14px;
-      background: #d5327b;
-      border-radius: 50%;
-      position: absolute;
-      top: 50%;
-      left: 50%;
-      margin-top: -7px;
-      margin-left: -7px;
+
+    &:first-child:not(.sub-step) {
+      .step-state,
+      .step-info {
+        left: 0;
+      }
+    }
+    &:last-child:not(.sub-step) {
+      .step-state {
+        left: unset;
+        right: 0;
+        transform: translate(50%, -50%);
+      }
+      .step-info {
+        left: unset;
+        right: 0;
+        transform: translate(50%, 24px);
+      }
+    }
+
+    &.sub-step {
+      margin: 0 auto;
+      width: 80%;
+      height: 50%;
+      z-index: 2;
+      background-color: #fff;
+      &.sub-step-active {
+        z-index: 11;
+      }
+      &.sub-step-inactive {
+        z-index: 10;
+      }
+      .step-line::after {
+        content: '';
+        display: block;
+        width: 6px;
+        height: 20px;
+        position: absolute;
+      }
+      .step-line-active::after {
+        background-color: #d5327b;
+      }
+      .step-line-inactive::after {
+        background-color: #b7b1b4;
+      }
+      .step-line-before::after {
+        left: 0;
+      }
+      .step-line-after::after {
+        right: 0;
+      }
+      &.sub-step-up {
+        .step-line {
+          top: unset;
+          bottom: 20px;
+          transform: translate(0, 50%);
+          border-radius: 6px 6px 0 0;
+          &::after {
+            bottom: 0;
+            transform: translate(0, 100%);
+          }
+        }
+        .step-state {
+          top: unset;
+          bottom: 20px;
+          transform: translate(-50%, 50%);
+        }
+        .step-info {
+          top: 0;
+          bottom: unset;
+          transform: translate(-50%, 12px);
+        }
+      }
+      &.sub-step-down {
+        .step-line {
+          top: 20px;
+          border-radius: 0 0 6px 6px;
+          &::after {
+            top: 0;
+            transform: translate(0, -100%);
+          }
+        }
+        .step-state {
+          top: 20px;
+        }
+        .step-info {
+          transform: translate(-50%, -12px);
+        }
+      }
     }
-  }
-  .node-status-default {
-    width: 24px;
-    height: 24px;
-    background: #ffffff;
-    border: 2px solid #b7b1b4;
-    border-radius: 50%;
-    z-index: 2;
-    position: relative;
-  }
-  .node-status-success-line {
-    height: 6px;
-    background: #d5327b;
-    position: absolute;
-    width: 100%;
-    top: 9px;
-    left: 0;
-    z-index: 1;
-  }
-  .node-status-default-line {
-    height: 6px;
-    background: #b7b1b4;
-    position: absolute;
-    width: 100%;
-    top: 9px;
-    left: 0;
-    z-index: 1;
   }
 }
 </style>

+ 0 - 1
src/views/dataQuery/components/DataQueryView/index.vue

@@ -204,7 +204,6 @@ import { useTable } from "./useTable";
 import { useTableColumnSet } from "@/hooks/useTableColumnSet";
 import { CommonTableFormatter } from "~/common";
 import { Query } from "@/api/webApi";
-import { json } from "body-parser";
 
 const props = defineProps({
   name: {

+ 21 - 7
src/views/realTime/components/WaybillView/index.vue

@@ -38,7 +38,9 @@
     <div
       v-show="trackAirlines.length"
       :style="{
-        maxHeight: trackAirlines.length > 1 ? `${208 * 2 + 8}px` : '208px',
+        maxHeight: `${
+          trackAirlines.length > 1 ? 208 * 2 + 8 : trackRowHeight
+        }px`,
       }"
       class="waybill-track"
     >
@@ -48,6 +50,7 @@
             v-for="trackAirline in trackAirlines"
             :key="trackAirline.flightNO"
             class="waybill-track-row"
+            :style="{ height: `${trackRowHeight}px` }"
           >
             <div
               v-for="(trackAirport, index) in trackAirline.airports"
@@ -118,6 +121,16 @@ import { CommonData, CommonTableFormatter, CommonValue } from '~/common'
 import { useLoop } from '@/hooks/useLoop'
 import { datetimeToTime } from '@/utils/validate'
 
+const trackRowHeight = computed(() =>
+  trackAirlines.value.some(airline =>
+    airline.airports.some(airport =>
+      airport.trackSteps.some(step => 'children' in step)
+    )
+  )
+    ? 280
+    : 208
+)
+
 const props = defineProps({
   name: {
     type: String,
@@ -179,11 +192,13 @@ watch(trackData, data => {
   })
 })
 
-const loopFuncs = [getWaybillInfo, getTableData]
-if (isDeparture) {
-  loopFuncs.push(getPullTableData)
-}
-useLoop(loopFuncs, 'waybill')
+// const loopFuncs = [getWaybillInfo, getTableData]
+// if (isDeparture) {
+//   loopFuncs.push(getPullTableData)
+// }
+// useLoop(loopFuncs, 'waybill')
+
+onMounted(getTableData)
 
 const { trackAirlines, trackBoxStyle } = useTrackData(props.name, trackData)
 
@@ -352,7 +367,6 @@ const { cellClickHandler } = useTableCellClick(`${props.name}Goods`)
     height: 0;
     flex: 1.5;
     &-row {
-      height: 208px;
       display: flex;
       &:not(:last-child) {
         margin-bottom: 8px;

+ 52 - 21
src/views/realTime/hooks/useTrackData.ts

@@ -5,19 +5,25 @@ import { CommonData, MaybeRef } from '~/common'
 interface TrackNode {
   name: string
   nodeCode: string
-  flag: boolean
+  active: boolean
   labelWidth?: number
   descriptions: string[]
+  sync?: boolean
 }
-interface TrackAirport {
+interface CombinedTrackNode {
+  active: boolean
+  labelWidth?: number
+  children: [TrackNode, TrackNode]
+}
+interface TrackAirport<T = TrackNode> {
   airport: string
   isDeparture: boolean
-  trackSteps: TrackNode[]
+  trackSteps: T[]
 }
-interface TrackAirline {
+interface TrackAirline<T = TrackNode> {
   flightNO: string
   flightDate: string
-  airports: TrackAirport[]
+  airports: TrackAirport<T>[]
 }
 
 const trackNodesMap = {
@@ -38,6 +44,7 @@ const trackNodesMap = {
     {
       name: '加货',
       nodeCode: 'ACC_BUP',
+      sync: true,
     },
     {
       name: '预配载',
@@ -248,7 +255,7 @@ export function useTrackData(name: string, trackData: MaybeRef<CommonData[]>) {
     return unref(trackData)
   })
 
-  const trackAirlines = ref<TrackAirline[]>([])
+  const trackAirlines = ref<TrackAirline<TrackNode | CombinedTrackNode>[]>([])
   const getTrackAirlines = () => {
     const airlines = unref(computedTrackData).reduce(
       (
@@ -283,15 +290,14 @@ export function useTrackData(name: string, trackData: MaybeRef<CommonData[]>) {
           ? String(departureAirport ?? '')
           : String(arriveAirport ?? '')
         const trackNode = {
-          flag: Boolean(
-            execPosition ||
-              ConsignmentItemPackagingQuantityQuantity ||
-              execResult ||
-              execTime
+          active: Boolean(
+            // execPosition ||
+            //   ConsignmentItemPackagingQuantityQuantity ||
+            execResult || execTime
           ),
           descriptions: [
-            String(execPosition ?? ''),
-            String(ConsignmentItemPackagingQuantityQuantity ?? ''),
+            // String(execPosition ?? ''),
+            // String(ConsignmentItemPackagingQuantityQuantity ?? ''),
             execResult ? '通过' : '未通过',
             datetimeToTime(execTime, flightDate),
           ],
@@ -315,8 +321,8 @@ export function useTrackData(name: string, trackData: MaybeRef<CommonData[]>) {
           } else {
             return {
               ...node,
-              flag: false,
-              descriptions: [],
+              active: false,
+              descriptions: ['', ''],
             }
           }
         })
@@ -366,19 +372,41 @@ export function useTrackData(name: string, trackData: MaybeRef<CommonData[]>) {
     trackAirlines.value = airlines.map(airline => {
       const dealedAirports = airline.airports.map((airport, index) => ({
         ...airport,
-        trackSteps: airport.trackSteps.filter(
-          (node, i, steps) =>
-            node.flag ||
+        trackSteps: airport.trackSteps.reduce((prevSteps, node, i, steps) => {
+          if (
+            node.active ||
             (index > 0
               ? ['IMP_TALLY', 'FSUDLV'].includes(node.nodeCode) &&
                 steps.some(
                   node =>
-                    ['IMP_TALLY', 'FSUDLV'].includes(node.nodeCode) && node.flag
+                    ['IMP_TALLY', 'FSUDLV'].includes(node.nodeCode) &&
+                    node.active
                 )
               : !['CARGOS_OFFLOAD', 'OFFLOAD_CONFIRM', 'BILL_RETURN'].includes(
                   node.nodeCode
                 ))
-        ),
+          ) {
+            if (node.sync && prevSteps.length > 0) {
+              const lastNode = prevSteps.pop() as TrackNode | CombinedTrackNode
+              if ('nodeCode' in lastNode) {
+                const active = node.active || lastNode.active
+                const labelWidth =
+                  Math.max(node.labelWidth ?? 0, lastNode.labelWidth ?? 0) ||
+                  undefined
+                prevSteps.push({
+                  active,
+                  labelWidth,
+                  children: [lastNode, node],
+                })
+              } else {
+                prevSteps.push(lastNode, node)
+              }
+            } else {
+              prevSteps.push(node)
+            }
+          }
+          return prevSteps
+        }, [] as (TrackNode | CombinedTrackNode)[]),
       }))
       const sortedAirports =
         name.includes('Departure') === airline.airports[0].isDeparture
@@ -396,7 +424,10 @@ export function useTrackData(name: string, trackData: MaybeRef<CommonData[]>) {
   })
 
   const trackBoxStyle = computed(
-    () => (airports: TrackAirport[], index: number) => {
+    () => (
+      airports: TrackAirport<TrackNode | CombinedTrackNode>[],
+      index: number
+    ) => {
       const style: CSSProperties = {}
       const totalLength = airports.reduce((pre, current) => {
         return pre + current.trackSteps.length - 1