소스 검색

feat: 合并test_zyh分支代码

Jack Zhou 1 주 전
부모
커밋
204cc96c66
39개의 변경된 파일2085개의 추가작업 그리고 281개의 파일을 삭제
  1. 1 0
      package.json
  2. BIN
      public/videos/demo-video.mp4
  3. 5 1
      src/App.vue
  4. 15 0
      src/api/module/Delivery.ts
  5. 0 1
      src/api/module/login.ts
  6. 37 0
      src/api/module/tracking.ts
  7. 5 5
      src/components/CustomizeColumns/src/CustomizeColumns.vue
  8. 183 4
      src/components/ShipmentStatus/src/ShipmentStatus.vue
  9. 1 0
      src/components/VEllipsisTooltip/index.ts
  10. 212 0
      src/components/VEllipsisTooltip/src/VEllipsisTooltip.vue
  11. 4 1
      src/main.ts
  12. 12 1
      src/router/index.ts
  13. 1 0
      src/stores/modules/breadCrumb.ts
  14. 14 0
      src/stores/modules/notificationMessage.ts
  15. 20 0
      src/stores/modules/trackingDownloadData.ts
  16. 168 4
      src/styles/icons/iconfont.css
  17. 0 0
      src/styles/icons/iconfont.js
  18. 82 0
      src/styles/icons/iconfont.svg
  19. BIN
      src/styles/icons/iconfont.ttf
  20. BIN
      src/styles/icons/iconfont.woff
  21. BIN
      src/styles/icons/iconfont.woff2
  22. 4 0
      src/styles/theme.scss
  23. 5 1
      src/utils/table.ts
  24. 1 1
      src/views/AIApiLog/src/components/LogDialog.vue
  25. 245 150
      src/views/DestinationDelivery/src/components/ConfiguRations/src/components/RecommendDate.vue
  26. 77 50
      src/views/DestinationDelivery/src/components/CreateNewBooking/src/CreateNewbooking.vue
  27. 187 45
      src/views/DestinationDelivery/src/components/CreateNewBooking/src/components/NewbookingTable.vue
  28. 10 1
      src/views/Layout/src/components/Header/HeaderView.vue
  29. 29 1
      src/views/Layout/src/components/Header/components/TrainingCard.vue
  30. 1 0
      src/views/Tracking/src/components/DownloadAttachment/index.ts
  31. 616 0
      src/views/Tracking/src/components/DownloadAttachment/src/DownloadAttachment.vue
  32. BIN
      src/views/Tracking/src/components/DownloadAttachment/src/images/empty-img.png
  33. 1 1
      src/views/Tracking/src/components/TrackingDetail/src/components/AttachmentView.vue
  34. 4 4
      src/views/Tracking/src/components/TrackingGuide.vue
  35. 123 10
      src/views/Tracking/src/components/TrackingTable/src/TrackingTable.vue
  36. BIN
      src/views/Tracking/src/image/dark-download-guide.png
  37. BIN
      src/views/Tracking/src/image/download-guide.png
  38. 1 0
      src/views/Video/index.ts
  39. 21 0
      src/views/Video/src/VideoView.vue

+ 1 - 0
package.json

@@ -40,6 +40,7 @@
     "moment": "^2.30.1",
     "moment-timezone": "^0.5.46",
     "pinia": "^2.2.2",
+    "pinia-plugin-persistedstate": "^4.5.0",
     "sass-loader": "^14.1.1",
     "vue": "^3.4.29",
     "vue-draggable-plus": "^0.5.3",

BIN
public/videos/demo-video.mp4


+ 5 - 1
src/App.vue

@@ -1,9 +1,13 @@
 <script setup lang="ts">
 import { RouterView } from 'vue-router'
+import { useRoute } from 'vue-router'
+import VideoView from '@/views/Video/src/VideoView.vue'
+const route = useRoute()
 </script>
 
 <template>
-  <RouterView />
+  <VideoView v-if="route.name === 'Demo Video'" />
+  <RouterView v-else />
 </template>
 
 <style scoped></style>

+ 15 - 0
src/api/module/Delivery.ts

@@ -318,4 +318,19 @@ export const getEmailRecords = (params: any, config: any) => {
     },
     config
   )
+}
+
+/**
+ * create new booking 表格中 Packing List 和 Commercial Invoice 列的下载功能
+ */
+export const downloadBookingTableFile = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_booking',
+      operate: 'batch_download_ci_p_file',
+      ...params
+    },
+    config
+  )
 }

+ 0 - 1
src/api/module/login.ts

@@ -106,7 +106,6 @@ export const resetAndActivatePassword = (params: any, config: any) => {
   )
 }
 
-
 /**
  * 获取public tracking detail详情数据
  */

+ 37 - 0
src/api/module/tracking.ts

@@ -1,4 +1,5 @@
 import HttpAxios from '@/utils/axios'
+import axios from 'axios'
 
 const base = import.meta.env.VITE_API_HOST
 const baseUrl = `${base}/main_new_version.php`
@@ -197,3 +198,39 @@ export const uploadFile = (params: any, config: any) => {
     config
   )
 }
+
+
+/**
+ * 获取Download Attachment页数据
+ */
+export const getDownloadAttachmentData = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'ocean_order',
+      operate: 'batch_download_load',
+      ...params
+    },
+    config
+  )
+}
+
+/**
+ * 下载对应的附件
+ */
+export const downloadAttachment = (params: any, config: any) => {
+  return axios.post(
+    baseUrl,
+    {
+      action: 'ocean_order',
+      operate: 'batch_download',
+      ...params
+    },
+    {
+      responseType: 'blob',
+      headers: {
+        'Content-Type': 'multipart/form-data'
+      }
+    }
+  )
+}

+ 5 - 5
src/components/CustomizeColumns/src/CustomizeColumns.vue

@@ -235,23 +235,23 @@ const handleRightRemove = (e: any) => {
     return index !== -1
   })
 
-  if (curGroup.name !== originalGroup.name && curGroup.name !== 'All') {
+  if (curGroup.name !== originalGroup?.name && curGroup.name !== 'All') {
     // 从当前分组中删除移入的数据
     curGroup.children.forEach((item: any, index: number) => {
       item.field === curItem.field && curGroup.children.splice(index, 1)
     })
     // 在对应分组中添加移入的数据
     groupColumns.value.forEach((item: any) => {
-      item.name === originalGroup.name && item.children.push(curItem)
+      item.name === originalGroup?.name && item.children.push(curItem)
     })
     // 添加到All分组里
     groupColumns.value[0].children.push(curItem)
   } else if (curGroup.name === 'All') {
     // 在对应分组中添加移入的数据
     groupColumns.value.forEach((item: any) => {
-      item.name === originalGroup.name && item.children.push(curItem)
+      item.name === originalGroup?.name && item.children.push(curItem)
     })
-  } else if (curGroup.name === originalGroup.name) {
+  } else if (curGroup.name === originalGroup?.name) {
     groupColumns.value[0].children.push(curItem)
   }
 }
@@ -274,7 +274,7 @@ const handleDeleteSelect = (curItem: any) => {
   })
   // 在对应分组中添加移入的数据
   groupColumns.value.forEach((item: any) => {
-    item.name === originalGroup.name && item.children.push(curItem)
+    item.name === originalGroup?.name && item.children.push(curItem)
   })
   // 添加到All分组里
   groupColumns.value[0].children.push(curItem)

+ 183 - 4
src/components/ShipmentStatus/src/ShipmentStatus.vue

@@ -22,6 +22,40 @@ watch(
   }
 )
 
+const isShowDeliveryInfo = ref(false)
+const deliveredInfoRef = ref()
+const simplexContentRef = ref()
+const handleSeeAllDeliveryInfo = () => {
+  nextTick(async () => {
+    const container = simplexContentRef.value
+    const elements = deliveredInfoRef.value
+
+    if (!container || !elements) return
+
+    const targetElement = Array.isArray(elements) ? elements[0] : elements
+    if (!targetElement) return
+
+    // 📏 获取布局信息
+    const containerRect = container.getBoundingClientRect()
+    const elementRect = targetElement.getBoundingClientRect()
+
+    const relativeTop = elementRect.top - containerRect.top
+    const scrollTop = container.scrollTop + relativeTop - 30
+
+    const maxScrollTop = container.scrollHeight - container.clientHeight
+    const safeScrollTop = Math.max(0, Math.min(scrollTop, maxScrollTop))
+
+    try {
+      container.scrollTo({
+        top: safeScrollTop,
+        behavior: 'smooth'
+      })
+    } catch (error) {
+      // 💠 兼容性兜底:某些浏览器不支持 'smooth' 滚动
+      container.scrollTop = safeScrollTop
+    }
+  })
+}
 // 中间点 每两个节点之间加上26px  上边距离28px 下边距离54px  如果没有中间点则高度为56px
 const getSimplexLineHeight = (index: number) => {
   if (index === 0) {
@@ -30,17 +64,34 @@ const getSimplexLineHeight = (index: number) => {
     return 28 + 56 + 26 * (index - 1)
   }
 }
+const getDeliverySimplexHeight = (stepItem) => {
+  let curHeight = 0
+  curHeight = 28 + 26 * (stepItem.children?.length > 0 ? stepItem.children.length - 1 : 0)
+  // if (!stepItem.deliveredData?.length) return curHeight
+  // curHeight =
+  //   curHeight + 90 * stepItem.deliveredData?.length + 8 * (stepItem.deliveredData?.length - 1)
+  return curHeight
+}
+const getDeliverySimplexLineHeight = (stepItem) => {
+  if (!stepItem.children?.length) return 0
+  return 28 + 26 * (stepItem.children.length - 1)
+}
+
+const getDeliveryInfoTop = (stepItem) => {
+  if (!stepItem.children?.length) return 0
+  return 30 + 26 * stepItem.children.length
+}
+
 const getDateHeight = (index: number) => {
   return 42 + 26 * index
 }
-
 const pathRef = ref()
 
 const { isOverflow: isPathOverflow } = useOverflow(pathRef, props)
 </script>
 
 <template>
-  <div class="simplex-content">
+  <div class="simplex-content" ref="simplexContentRef">
     <div
       class="detail-step-item"
       :class="{
@@ -73,6 +124,49 @@ const { isOverflow: isPathOverflow } = useOverflow(pathRef, props)
             <div class="label">{{ dateItem.label }}</div>
             <div class="divider"></div>
             <div class="date">{{ formatTimezone(dateItem.date) }}</div>
+            <!-- <el-button
+              @click="isShowDeliveryInfo = !isShowDeliveryInfo"
+              v-if="dateItem.label === 'Delivered'"
+              class="see-all-icon el-button--text"
+            >
+              See All
+              <span class="font_family icon-icon_next_b"></span>
+            </el-button> -->
+            <see-all-icon
+              v-if="dateItem.label === 'Delivered'"
+              v-model="isShowDeliveryInfo"
+              @collapse="handleSeeAllDeliveryInfo"
+            ></see-all-icon>
+          </div>
+          <div
+            :style="{ top: getDeliveryInfoTop(stepItem) + 'px' }"
+            class="delivered-info"
+            style="scroll-margin-top: 20px"
+            v-if="stepItem.label === 'Place of Delivery' && isShowDeliveryInfo"
+            ref="deliveredInfoRef"
+          >
+            <div
+              class="delivered-item"
+              v-for="(deliveredItem, deliveredIndex) in stepItem.deliveredData"
+              :key="deliveredIndex"
+            >
+              <div class="top">
+                <div class="top-left">
+                  <div class="label">{{ deliveredItem.label }}</div>
+                  <div class="date">
+                    {{ formatTimezone(deliveredItem.date, deliveredItem.timezone) }}
+                  </div>
+                </div>
+                <div class="top-right">{{ deliveredItem.location }}</div>
+              </div>
+              <div class="bottom">
+                <span class="font_family icon-icon_container_b"></span>
+                <div style="margin-top: 1px; margin-left: 4px">Container:</div>
+                <div style="margin-left: 4px; margin-top: 3px; font-weight: 700">
+                  {{ deliveredItem.container }}
+                </div>
+              </div>
+            </div>
           </div>
         </div>
       </div>
@@ -81,6 +175,22 @@ const { isOverflow: isPathOverflow } = useOverflow(pathRef, props)
         v-if="stepItem.label !== 'Place of Delivery'"
         :style="{ height: getSimplexLineHeight(stepItem.children?.length || 0) + 'px' }"
       ></div>
+      <div
+        class="place-of-delivery-line"
+        v-if="stepItem.label === 'Place of Delivery'"
+        :style="{ height: getDeliverySimplexHeight(stepItem) + 'px' }"
+      >
+        <div
+          class="dashed-line"
+          :style="{
+            height: getDeliverySimplexLineHeight(stepItem) + 'px',
+            borderStyle: stepItem.isArrival ? 'solid' : 'dashed',
+            borderColor: stepItem.isArrival
+              ? 'var(--color-neutral-2)'
+              : 'var(--color-shipment-status-label-bg)'
+          }"
+        ></div>
+      </div>
     </div>
   </div>
 </template>
@@ -88,8 +198,11 @@ const { isOverflow: isPathOverflow } = useOverflow(pathRef, props)
 <style lang="scss" scoped>
 // 单式样式
 .simplex-content {
+  position: relative;
   width: 100%;
-  padding: 24px 8px 9px 16px;
+  height: 100%;
+  overflow: auto;
+  padding: 24px 8px 24px 16px;
 }
 .detail-step-item {
   & > .data {
@@ -164,6 +277,7 @@ const { isOverflow: isPathOverflow } = useOverflow(pathRef, props)
         gap: 8px;
         width: calc(100% + 20px);
         .label {
+          margin-left: 3px;
           font-weight: 700;
         }
         .divider {
@@ -175,14 +289,79 @@ const { isOverflow: isPathOverflow } = useOverflow(pathRef, props)
         .date {
           font-size: 12px;
         }
+        :deep(.see-all-icon) {
+          width: 52px;
+          height: 24px;
+          padding-top: 3px;
+          span {
+            font-size: 12px;
+          }
+          .btn {
+            margin-left: 2px;
+          }
+        }
+      }
+      .delivered-info {
+        position: absolute;
+        margin-top: 8px;
+        width: 100%;
+        .delivered-item {
+          width: 100%;
+          margin-bottom: 8px;
+          border-radius: 6px;
+          background: var(--color-table-header-bg);
+          &:last-child {
+            margin-bottom: 20px;
+          }
+          .top {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            height: 58px;
+            border-bottom: 1px solid var(--color-border);
+            .top-left {
+              flex: 1;
+              display: flex;
+              flex-direction: column;
+              gap: 4px;
+              padding: 8px;
+              border-right: 1px solid var(--color-border);
+              .label {
+                font-weight: 700;
+              }
+              .date {
+                font-size: 12px;
+              }
+            }
+            .top-right {
+              padding: 0 23px;
+              text-align: center;
+              font-weight: 700;
+              color: var(--color-neutral-1);
+            }
+          }
+          .bottom {
+            display: flex;
+            align-items: center;
+            height: 32px;
+            padding: 8px;
+          }
+        }
       }
     }
   }
   & > .line {
-    height: 72px;
     margin-left: 7px;
     border-left: 1px solid var(--color-neutral-1);
   }
+  & > .place-of-delivery-line {
+    margin-left: 7px;
+    // border-left: 1px dashed var(--color-neutral-3);
+    .dashed-line {
+      height: 50px;
+      border-left: 1px dashed var(--color-neutral-2);
+    }
+  }
   &.last {
     & > .data {
       .left-step-icon {

+ 1 - 0
src/components/VEllipsisTooltip/index.ts

@@ -0,0 +1 @@
+export { default } from './src/VEllipsisTooltip.vue'

+ 212 - 0
src/components/VEllipsisTooltip/src/VEllipsisTooltip.vue

@@ -0,0 +1,212 @@
+<template>
+  <el-tooltip
+    :disabled="!shouldShowTooltip"
+    :content="tooltipContent"
+    :popper-class="popperClass"
+    :placement="placement"
+    v-bind="tooltipProps"
+    ref="tooltipRef"
+  >
+    <div
+      ref="containerRef"
+      class="ellipsis-container"
+      :class="{ 'is-clamp-multi': lineClamp > 1 }"
+      :style="finalContainerStyle"
+      @mouseenter="handleMouseEnter"
+      @mouseleave="handleMouseLeave"
+    >
+      <slot>
+        <span ref="textRef" class="ellipsis-text" :style="textStyle">
+          {{ content }}
+        </span>
+      </slot>
+    </div>
+  </el-tooltip>
+</template>
+
+<script setup>
+import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
+
+const props = defineProps({
+  content: String,
+  maxWidth: {
+    type: Number,
+    default: 200
+  },
+  maxHeight: {
+    type: [Number, String],
+    default: ''
+  },
+  lineClamp: {
+    type: Number,
+    default: 1
+  },
+  tooltipProps: {
+    type: Object,
+    default: () => ({})
+  },
+  popperClass: {
+    type: String,
+    default: 'ellipsis-tooltip'
+  },
+  placement: {
+    type: String,
+    default: 'top'
+  },
+  containerStyle: {
+    type: Object,
+    default: () => ({})
+  },
+  textStyle: {
+    type: Object,
+    default: () => ({})
+  }
+})
+
+// --- DOM refs ---
+const containerRef = ref(null)
+const textRef = ref(null)
+const tooltipRef = ref(null)
+
+// --- 是否应显示 tooltip ---
+const shouldShowTooltip = ref(false)
+
+// --- 动态计算样式类 ---
+const containerClasses = computed(() => ({
+  'is-clamp-multi': props.lineClamp > 1
+}))
+
+// --- 实际用于 Tooltip 显示的内容 ---
+const tooltipContent = computed(() => props.content || '')
+
+// --- 容器样式(max-width / max-height)---
+const finalContainerStyle = computed(() => ({
+  maxWidth: props.maxWidth + 'px',
+  maxHeight: props.maxHeight
+    ? typeof props.maxHeight === 'number'
+      ? props.maxHeight + 'px'
+      : props.maxHeight
+    : 'none',
+  ...props.containerStyle
+}))
+
+// --- 文本样式(根据 lineClamp 应用不同 CSS)---
+const finalTextStyle = computed(() => {
+  const style = { ...props.textStyle }
+  if (props.lineClamp <= 1) {
+    return {
+      display: 'block',
+      overflow: 'hidden',
+      textOverflow: 'ellipsis',
+      whiteSpace: 'nowrap',
+      ...style
+    }
+  } else {
+    return {
+      display: '-webkit-box',
+      WebkitBoxOrient: 'vertical',
+      WebkitLineClamp: props.lineClamp,
+      overflow: 'hidden',
+      textOverflow: 'ellipsis',
+      wordBreak: 'break-all', // 多行建议启用
+      ...style
+    }
+  }
+})
+
+// --- 检查是否溢出 ---
+const checkOverflow = async () => {
+  await nextTick() // 确保 DOM 更新
+  const container = containerRef.value
+  const textEl = textRef.value || container?.querySelector('.ellipsis-text')
+
+  if (!container || !textEl) return
+
+  let isOverflowing = false
+
+  if (props.lineClamp <= 1) {
+    isOverflowing = textEl.scrollWidth > container.clientWidth
+  } else {
+    // 多行看高度是否溢出(line-clamp 截断)
+    isOverflowing = textEl.scrollHeight > container.clientHeight
+  }
+
+  shouldShowTooltip.value = isOverflowing
+}
+
+// --- 鼠标事件用于手动触发检查(防抖)---
+let resizeTimer
+const handleMouseEnter = () => {
+  clearTimeout(resizeTimer)
+  resizeTimer = setTimeout(checkOverflow, 50)
+}
+
+// 可选:也可监听 resize
+onMounted(() => {
+  window.addEventListener('resize', checkOverflow)
+  checkOverflow()
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', checkOverflow)
+  clearTimeout(resizeTimer)
+})
+
+// 监听 props 变化
+watch(
+  () => [props.content, props.lineClamp, props.maxWidth, props.maxHeight],
+  () => {
+    checkOverflow()
+  },
+  { immediate: true }
+)
+</script>
+
+<style lang="scss" scoped>
+.ellipsis-container {
+  display: inline-block;
+  max-width: v-bind('finalContainerStyle.maxWidth');
+  max-height: v-bind('finalContainerStyle.maxHeight');
+  overflow: hidden;
+  vertical-align: top;
+  line-height: 32px;
+}
+
+.ellipsis-text {
+  width: 100%; // 利用父容器宽度
+}
+.ellipsis-container {
+  display: inline-block;
+  max-width: v-bind('finalContainerStyle.maxWidth');
+  max-height: v-bind('finalContainerStyle.maxHeight');
+  overflow: hidden;
+  line-height: 1.5;
+}
+
+.ellipsis-text {
+  width: 100%;
+  display: block;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.ellipsis-container.is-clamp-multi .ellipsis-text {
+  display: -webkit-box;
+  line-clamp: v-bind('lineClamp');
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: v-bind('lineClamp');
+  white-space: normal;
+  word-break: break-all;
+}
+</style>
+
+<!-- 全局样式建议提取到全局 SCSS 文件 -->
+<style>
+.ellipsis-tooltip {
+  max-width: 200px;
+  word-break: break-word;
+  white-space: pre-line;
+  margin-bottom: -15px;
+}
+</style>

+ 4 - 1
src/main.ts

@@ -22,6 +22,7 @@ import { createPinia } from 'pinia'
 import App from './App.vue'
 import router from './router'
 import { useThemeStore } from '@/stores/modules/theme'
+import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
 
 const app = createApp(App)
 
@@ -35,7 +36,9 @@ VXETable.use(VXETablePluginExportXLSX, {
 VXETable.setI18n('en-US', enUS)
 VXETable.setLanguage('en-US')
 
-app.use(createPinia())
+const pinia = createPinia()
+pinia.use(piniaPluginPersistedstate)
+app.use(pinia)
 app.use(VXETable)
 app.use(VxeUI)
 app.use(router)

+ 12 - 1
src/router/index.ts

@@ -62,6 +62,13 @@ const router = createRouter({
           meta: {
             activeMenu: '/tracking'
           }
+        }, {
+          path: '/tracking/download-attachment',
+          name: 'Tracking Download Attachment',
+          component: () => import('../views/Tracking/src/components/DownloadAttachment'),
+          meta: {
+            activeMenu: '/tracking'
+          }
         },
         {
           path: '/shipment/detail',
@@ -205,7 +212,11 @@ const router = createRouter({
             breadName: 'Modify Booking',
             activeMenu: '/destination-delivery'
           }
-        }
+        }, {
+          path: '/demo-video',
+          name: 'Demo Video',
+          component: () => import('../views/Video'),
+        },
       ]
     }
   ]

+ 1 - 0
src/stores/modules/breadCrumb.ts

@@ -12,6 +12,7 @@ interface BreadCrumb {
 const whiteList = [
   'Booking Detail',
   'Tracking Detail',
+  'Tracking Download Attachment',
   'Add VGM',
   'Public Tracking Detail',
   'Create New Rule',

+ 14 - 0
src/stores/modules/notificationMessage.ts

@@ -59,6 +59,20 @@ export const useNotificationMessage = defineStore('notificationMessage', {
         }
       })
     },
+    // 在轮播后将消息置为已读
+    async markMessageAsReadAfterCarousel(displayedIds: string[]) {
+      if (displayedIds.length === 0) return
+
+      await $api.setMessageRead({ id: displayedIds }).then((res) => {
+        if (res.code === 200) {
+          this.readCardMap = []
+          localStorage.setItem('readCardMap', JSON.stringify(this.readCardMap))
+
+          // 在将消息标记为已读后,再次检查是否有新消息
+          this.hasUnreadMessages()
+        }
+      })
+    },
     clearData() {
       this.notificationMsgList = []
       this.readCardMap = []

+ 20 - 0
src/stores/modules/trackingDownloadData.ts

@@ -0,0 +1,20 @@
+// stores/useDataStore.js
+import { defineStore } from 'pinia';
+
+export const useTrackingDownloadData = defineStore('trackingDownloadData', {
+  state: () => ({
+    serialNoArr: [],
+    schemasArr: []
+  }),
+  actions: {
+    setData(serial_no_arr: string[], schemas_arr: string[]) {
+      this.serialNoArr = serial_no_arr;
+      this.schemasArr = schemas_arr;
+    }
+  },
+  // 持久化配置
+  persist: {
+    storage: sessionStorage // 关闭浏览器就清掉
+    // 或 storage: localStorage // 永久保留
+  }
+});

+ 168 - 4
src/styles/icons/iconfont.css

@@ -1,9 +1,9 @@
 @font-face {
   font-family: "font_family"; /* Project id 4672385 */
-  src: url('iconfont.woff2?t=1750149505564') format('woff2'),
-       url('iconfont.woff?t=1750149505564') format('woff'),
-       url('iconfont.ttf?t=1750149505564') format('truetype'),
-       url('iconfont.svg?t=1750149505564#font_family') format('svg');
+  src: url('iconfont.woff2?t=1763520739011') format('woff2'),
+       url('iconfont.woff?t=1763520739011') format('woff'),
+       url('iconfont.ttf?t=1763520739011') format('truetype'),
+       url('iconfont.svg?t=1763520739011#font_family') format('svg');
 }
 
 .font_family {
@@ -14,6 +14,170 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.icon-icon_video_b:before {
+  content: "\e743";
+}
+
+.icon-icon_filters_up_b:before {
+  content: "\e742";
+}
+
+.icon-icon_charge_trucking:before {
+  content: "\e741";
+}
+
+.icon-icon_charge_hubrouting:before {
+  content: "\e73a";
+}
+
+.icon-icon_charge_gatewayhandling:before {
+  content: "\e73b";
+}
+
+.icon-icon_charge_airfreight:before {
+  content: "\e73c";
+}
+
+.icon-icon_charge_localhandling:before {
+  content: "\e73d";
+}
+
+.icon-icon_charge_customs:before {
+  content: "\e73e";
+}
+
+.icon-icon_charge_terminalcharge:before {
+  content: "\e73f";
+}
+
+.icon-icon_charge_airfreightrouting:before {
+  content: "\e740";
+}
+
+.icon-icon_barcode_b:before {
+  content: "\e737";
+}
+
+.icon-icon_booking__fill_b1:before {
+  content: "\e738";
+}
+
+.icon-icon_customer_b:before {
+  content: "\e739";
+}
+
+.icon-icon_warning_b:before {
+  content: "\e72d";
+}
+
+.icon-icon_update_b:before {
+  content: "\e72e";
+}
+
+.icon-icon_purchase__order_b:before {
+  content: "\e72f";
+}
+
+.icon-icon_unshare_b:before {
+  content: "\e730";
+}
+
+.icon-icon_organization_b:before {
+  content: "\e731";
+}
+
+.icon-icon_sortingby_b:before {
+  content: "\e732";
+}
+
+.icon-icon_sales__order_b:before {
+  content: "\e733";
+}
+
+.icon-icon_sendtest_b:before {
+  content: "\e734";
+}
+
+.icon-icon_time_full_b:before {
+  content: "\e735";
+}
+
+.icon-icon_datasource_b:before {
+  content: "\e736";
+}
+
+.icon-icon_unpack_b:before {
+  content: "\e72c";
+}
+
+.icon-icon_vgm_n_b:before {
+  content: "\e72b";
+}
+
+.icon-icon_booking_resend:before {
+  content: "\e72a";
+}
+
+.icon-icon_hbl_reopen:before {
+  content: "\e728";
+}
+
+.icon-icon_eci_st_retrigger:before {
+  content: "\e729";
+}
+
+.icon-icon_next_b1:before {
+  content: "\e727";
+}
+
+.icon-icon_default_screen_b:before {
+  content: "\e726";
+}
+
+.icon-icon_update__detail_b:before {
+  content: "\e725";
+}
+
+.icon-icon_brno_b:before {
+  content: "\e71d";
+}
+
+.icon-icon_smart_b:before {
+  content: "\e71e";
+}
+
+.icon-icon_map_b:before {
+  content: "\e71f";
+}
+
+.icon-icon_template_b1:before {
+  content: "\e720";
+}
+
+.icon-icon_door_b:before {
+  content: "\e721";
+}
+
+.icon-icon_map_point_b:before {
+  content: "\e722";
+}
+
+.icon-icon_current__location_b:before {
+  content: "\e723";
+}
+
+.icon-icon_address_book_b:before {
+  content: "\e724";
+}
+
+.icon-icon_guideright_b:before {
+  content: "\e71b";
+}
+
+.icon-icon_guideleft_b:before {
+  content: "\e71c";
+}
+
 .icon-icon_guidelines_b:before {
   content: "\e719";
 }

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 0 - 0
src/styles/icons/iconfont.js


+ 82 - 0
src/styles/icons/iconfont.svg

@@ -14,6 +14,88 @@
     />
       <missing-glyph />
       
+      <glyph glyph-name="icon_video_b" unicode="&#59203;" d="M871.104 818.24c47.744 0 86.4-38.656 86.4-86.4v-734.08a86.4 86.4 0 0 0-86.4-86.4H137.024a86.4 86.4 0 0 0-86.4 86.4V731.84c0 47.744 38.656 86.4 86.4 86.4h734.08z m-743.68-820.48c0-5.312 4.288-9.6 9.6-9.6h734.08a9.6 9.6 0 0 1 9.6 9.6V503.616H127.36v-505.856z m276.032 397.312a16 16 0 0 0 24 13.888l256.704-148.288a16 16 0 0 0 0-27.648L427.52 84.736a16 16 0 0 0-24 13.824V395.072zM136.96 741.504a9.6 9.6 0 0 1-9.6-9.6v-151.552h144l-12.608 36.992a22548.48 22548.48 0 0 0-41.792 124.16H136.96z m194.56-99.456l20.992-61.696h139.52l-12.608 36.992c-13.696 40.192-29.376 86.912-41.792 124.16H297.984c10.752-31.808 22.72-67.648 33.6-99.456z m220.672 0l20.992-61.696h139.52l-12.608 36.992c-13.632 40.192-29.376 86.912-41.792 124.16H518.656c10.752-31.872 22.784-67.648 33.6-99.456z m220.672 0l20.992-61.696h86.784V731.904a9.6 9.6 0 0 1-9.6 9.6h-131.712c10.688-31.872 22.72-67.648 33.536-99.456z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_filters_up_b" unicode="&#59202;" d="M267.648 79.68L512 324.032l244.416-244.352 54.272 54.336-271.552 271.488a38.4 38.4 0 0 1-54.272 0l-271.552-271.488 54.336-54.336z m0 271.488L512 595.584l244.416-244.416 54.272 54.336-271.552 271.488a38.4 38.4 0 0 1-54.272 0L213.312 405.504l54.336-54.336z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_charge_trucking" unicode="&#59201;" d="M629.12 706.944a32 32 0 0 0 32-32v-63.2h158.4a32 32 0 0 0 22.304-9.056l2.432-2.624 140.48-171.168c4.672-5.696 7.264-12.896 7.264-20.288v-297.408a32 32 0 0 0-32-32h-101.12a120.448 120.448 0 0 0-227.968-1.184l-1.792-0.064h-252.8a120.448 120.448 0 0 0-227.52 0H64a32 32 0 0 0-32 32V674.912a32 32 0 0 0 32 32h565.12zM262.56 173.952a56.448 56.448 0 1 1 0-112.896 56.448 56.448 0 0 1 0 112.864z m482.176 0a56.448 56.448 0 1 1 0-112.896 56.448 56.448 0 0 1 0 112.864zM96 141.952h48.608a120.48 120.48 0 0 0 235.936 0h215.904V579.744c0 2.24 0.224 4.448 0.672 6.56v56.64H96v-500.992z m565.12 62.208a120.48 120.48 0 0 0 201.28-60.96H928V397.12l-123.616 150.592H661.12V204.16z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_charge_hubrouting" unicode="&#59194;" d="M512 820.16a112.192 112.192 0 0 0 32-219.68v-67.968a184.8 184.8 0 0 0 133.344-99.712l90.592 30.848a112.192 112.192 0 1 0 22.56-59.904l-95.104-32.384a184 184 0 0 0-53.76-152l58.944-56.224a112.192 112.192 0 1 0-49.408-41.312l-63.424 60.512A183.872 183.872 0 0 0 512 166.08a183.936 183.936 0 0 0-75.776 16.32l-68.576-65.472a112.192 112.192 0 1 0-46.944 43.648l61.568 58.816a184.032 184.032 0 0 0-53.44 154.208l-93.984 32a112.192 112.192 0 1 0 21.376 60.32l91.392-31.104A184.8 184.8 0 0 0 480 532.48V600.48a112.224 112.224 0 0 0 32 219.712zM271.008 108.16a48.192 48.192 0 1 1 0-96.352 48.192 48.192 0 0 1 0 96.384z m473.792 0a48.192 48.192 0 1 1 0-96.352 48.192 48.192 0 0 1 0 96.384zM512 471.36a120.64 120.64 0 1 1 0-241.28 120.64 120.64 0 0 1 0 241.28zM144.192 519.808a48.192 48.192 0 1 1 0-96.384 48.192 48.192 0 0 1 0 96.384z m735.616 0a48.192 48.192 0 1 1 0-96.384 48.192 48.192 0 0 1 0 96.384zM512 756.192a48.192 48.192 0 1 1 0-96.384 48.192 48.192 0 0 1 0 96.384z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_charge_gatewayhandling" unicode="&#59195;" d="M967.008 846.88a32 32 0 0 0 32-32v-621.184a32 32 0 0 0-32-32H825.28v-232.48a32 32 0 0 0-32-32H71.008a32 32 0 0 0-32 32V481.984a32 32 0 0 0 32 32h248.16l3.136-0.128a32 32 0 0 0 19.488-9.216l64-64.032V814.88a32 32 0 0 0 32 32H967.04z m-864-885.664H761.28V336.992H432.16a32 32 0 0 0-22.624 9.376L305.92 449.984H103.008v-488.768z m366.816 439.776H793.28a32 32 0 0 0 32-32v-143.296h109.696V782.88H469.824V400.96z m363.84 84.384h-276.928v64h276.928v-64z m0 125.376h-276.928v64h276.928v-64z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_charge_airfreight" unicode="&#59196;" d="M513.568 864c55.264 0 100.064-44.8 100.064-100.064v-233.568l338.656-211.712a32 32 0 0 0 15.04-27.136v-88.416a32 32 0 0 0-41.632-30.496l-312.064 98.56v-185.28l76.288-58.208a32 32 0 0 0 12.576-25.44v-66.304a32 32 0 0 0-40.96-30.72l-148.192 43.072-147.52-43.072a32 32 0 0 0-40.96 30.72V2.24a32 32 0 0 0 12.736 25.536l75.904 57.376v187.008l-315.2-99.552a32 32 0 0 0-41.6 30.496V291.52a32 32 0 0 0 15.04 27.136l341.76 213.664V763.936C413.504 819.2 458.304 864 513.568 864z m0-64c-19.904 0-36.064-16.16-36.064-36.064v-249.376a32 32 0 0 0-15.04-27.136l-341.76-213.6v-27.072l315.2 99.52a32 32 0 0 0 41.6-30.496v-246.56a32 32 0 0 0-12.736-25.536l-75.904-57.344v-7.712l115.488 33.696c5.824 1.696 12.064 1.696 17.92 0l116.224-33.76v7.872l-73.504 56a31.936 31.936 0 0 0-15.36 27.328v245.024a32 32 0 0 0 41.6 30.496l312.096-98.56v27.104l-338.688 211.68a32 32 0 0 0-15.04 27.136V763.936c0 19.936-16.128 36.064-36.032 36.064z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_charge_localhandling" unicode="&#59197;" d="M434.304 815.488a32 32 0 0 0 23.584-1.312l386.144-179.392a32 32 0 0 0 11.264-8.32c0.224-0.192 0.384-0.416 0.576-0.64a30.656 30.656 0 0 0 1.984-2.688l0.64-0.992c0.576-0.96 1.12-1.92 1.6-2.912l0.384-0.896c0.48-1.056 0.864-2.144 1.216-3.232l0.288-0.896c0.32-1.152 0.544-2.304 0.736-3.456l0.192-0.896c0.192-1.44 0.32-2.88 0.32-4.352v-175.392h2.688a32 32 0 0 0 27.712-16l104-180.16a32 32 0 0 0 0-32l-104-180.16c-5.696-9.856-16.288-16-27.712-16l-207.968 0.064a32 32 0 0 0-27.712 16L624.32 32l-10.88-5.024-159.2-73.28a31.904 31.904 0 0 0-6.176-1.984c-0.448-0.096-0.896-0.256-1.376-0.32l-1.44-0.192c-2.176-0.32-4.384-0.48-6.592-0.32l-0.352 0.064a31.808 31.808 0 0 0-11.008 2.848L42.72 133.376a32 32 0 0 0-18.464 28.992V594.176a31.808 31.808 0 0 0 16.448 40.448L431.04 814.208l3.264 1.28z m38.592-410.112v-372.64l113.792 52.352 5.536 2.56-65.984 114.336a32 32 0 0 0 0 32l103.968 180.128 2.368 3.52a32 32 0 0 0 25.344 12.48h141.312V555.52l-326.336-150.144z m-384.64-222.624l320.608-149.696V405.472L88.256 554.4v-371.648z m502.624 35.2l85.504-148.096h171.072l85.504 148.128-85.504 148.128h-171.072l-85.504-148.16z m122.464 84.16a97.216 97.216 0 1 0 97.216-168.416 97.216 97.216 0 0 0-97.216 168.416z m77.344-67.584a33.184 33.184 0 1 1-57.472-33.248 33.184 33.184 0 0 1 57.472 33.28zM131.2 605.856l309.728-144.64 131.84 60.672-289.664 153.856L131.2 605.856z m224.992 103.488l289.728-153.824 108.96 50.112-310.56 144.256-88.128-40.544z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_charge_customs" unicode="&#59198;" d="M501.056 862.848a32 32 0 0 0 19.648-0.832L873.6 731.456a32 32 0 0 0 20.896-29.984v-404.48c0-21.952-7.04-44.032-17.056-64.64-10.112-20.864-24.192-42.176-40.384-63.04-32.384-41.792-75.36-84.576-118.816-122.784a1324.544 1324.544 0 0 0-125.344-97.792c-18.368-12.48-35.2-22.976-49.184-30.432a186.944 186.944 0 0 0-19.84-9.408 63.2 63.2 0 0 0-22.336-4.896c-7.936 0-15.584 2.24-20.864 4.032a179.104 179.104 0 0 0-19.232 8.128c-13.472 6.528-29.6 15.68-47.168 26.784a982.048 982.048 0 0 0-118.976 89.696c-41.184 36.16-81.92 78.24-112.608 122.368-30.304 43.584-53.184 92.608-53.184 141.952V701.472a32 32 0 0 0 20.736 29.952l348.096 130.56 2.72 0.864zM193.504 679.36V296.96c0-30.4 14.592-66.4 41.76-105.472 26.752-38.464 63.456-76.704 102.272-110.816a918.112 918.112 0 0 1 111.008-83.712c16.192-10.24 30.08-18.08 40.704-23.2 5.056-2.432 8.992-4.064 11.776-5.024 2.912 1.152 7.072 3.04 12.48 5.92 11.296 6.08 26.112 15.2 43.424 26.976 34.56 23.488 77.44 56.32 119.072 92.928 41.76 36.736 81.472 76.544 110.464 113.92 14.496 18.688 25.792 36.16 33.376 51.744 7.68 15.84 10.656 28.064 10.656 36.736V679.2L509.664 797.888l-316.16-118.56zM511.936 640a102.912 102.912 0 0 0 32-200.672v-61.184h64.288v-64h-64.32v-122.56c35.52 7.84 62.304 27.744 82.432 52.16 20.448 24.768 33.536 53.696 40.64 77.376h66.112c-7.52-33.728-25.28-79.232-57.376-118.112-35.68-43.264-89.792-79.04-165.536-79.04-75.648 0-130.304 35.712-166.784 78.72-33.024 38.976-51.808 84.704-60 118.432h66.496c7.712-23.616 21.44-52.384 42.368-77.056 21.536-25.44 50.048-45.952 87.68-53.216v123.296h-64.288v64h64.256V439.36A102.912 102.912 0 0 0 511.936 640z m0-64a38.912 38.912 0 1 1 0-77.824 38.912 38.912 0 0 1 0 77.824z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_charge_terminalcharge" unicode="&#59199;" d="M992 615.04v-645.312h-64V570.752l-422.496 159.04L96 571.008v-601.28H32V614.816L493.76 793.92l11.392 4.384L992 615.04zM454.464-30.208h-123.04V92.8h123.04v-123.008z m283.584 0h-123.04V92.8h123.04v-123.008z m-139.136 122.976H475.84v123.04h123.04V92.8z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_charge_airfreightrouting" unicode="&#59200;" d="M845.664 799.68A64 64 0 0 0 903.136 736v-155.776l-0.352-6.528a64 64 0 0 0-57.12-57.12l-6.528-0.352H455.232l-6.56 0.32a64 64 0 0 0-57.12 57.152l-0.32 6.528V627.424H275.808a106.08 106.08 0 0 1 0-212.16H748.8a169.504 169.504 0 0 0 0-338.976h-119.136v-44.32l-0.352-6.528a64 64 0 0 0-57.12-57.12l-6.528-0.352H181.728l-6.528 0.32a64 64 0 0 0-57.152 57.152l-0.32 6.528v155.776a64 64 0 0 0 57.472 63.68l6.528 0.32h383.904l6.528-0.32a64 64 0 0 0 57.472-63.68v-47.456h119.136a105.472 105.472 0 1 1 0 210.976H275.84a170.08 170.08 0 1 0 0 340.16h115.424V736a64 64 0 0 0 57.44 63.68l6.56 0.32h383.904l6.528-0.32zM181.728 31.936h383.904v155.776H181.728V32z m273.504 548.256h383.904V736H455.232v-155.776z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_barcode_b" unicode="&#59191;" d="M198.848 61.312h-86.4V706.624h86.4v-645.312z m151.296 0H281.152V706.624H350.08v-645.312z m160.896 0H407.424V706.624h103.68v-645.312z m122.112 0H564.096V706.624h69.056v-645.312z m123.072 0h-68.992V706.624h68.992v-645.312z m155.264 0h-103.616V706.624h103.68v-645.312z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_booking__fill_b1" unicode="&#59192;" d="M1000.32 2.56a96 96 0 0 0-96-96h-768a96 96 0 0 0-96 96V489.344h960v-486.784z m-539.072 133.76L339.328 258.176l-27.136 27.2-54.336-54.336 27.2-27.072 149.12-149.12a38.4 38.4 0 0 1 54.272 0l294.272 294.272-54.208 54.336-267.264-267.136zM312 791.68h432.896V861.504h76.736v-69.952h82.752a96 96 0 0 0 96-96v-131.968h-960V695.616a96 96 0 0 0 96 96h98.88V861.568h76.736v-69.952z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_customer_b" unicode="&#59193;" d="M663.744 277.312c65.536-25.024 196.672-83.2 236.032-131.328 40.32-49.344 56.96-121.536 60.288-151.424H64c1.472 30.016 15.168 102.272 58.24 151.424 41.792 47.808 168 106.112 230.72 131.264a6.4 6.4 0 0 0 7.872-2.752L460.16 102.4a6.4 6.4 0 0 1 11.52 0.832l12.608 31.552a6.4 6.4 0 0 1-0.384 5.568l-28.352 49.088a6.4 6.4 0 0 0 1.024 7.744l48.768 48.832a6.4 6.4 0 0 0 9.088 0l49.408-49.024a6.4 6.4 0 0 0 1.28-7.424l-24.96-49.472a6.464 6.464 0 0 1-0.32-4.992l10.496-30.464a6.4 6.4 0 0 1 11.648-1.024l93.952 170.88a6.4 6.4 0 0 0 7.808 2.88z m-151.68 496.064a216.576 216.576 0 1 0 0-433.152 216.576 216.576 0 0 0 0 433.152z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_warning_b" unicode="&#59181;" d="M910.976-115.2H113.024v76.8h797.952v-76.8zM512 635.392a297.664 297.664 0 0 0 297.664-297.728v-295.424H214.4V337.664A297.728 297.728 0 0 0 512 635.392z m0-76.8a220.864 220.864 0 0 1-220.864-220.928v-218.624h139.776v135.168h76.8v-135.168h225.152V337.664A220.864 220.864 0 0 1 512 558.592z m-327.232-11.968l-39.872-65.536L19.648 557.184 59.52 622.72l125.248-76.096z m819.648 10.56l-125.248-76.16-39.872 65.6 125.248 76.16 39.872-65.6zM551.808 736.64h-76.8V883.2h76.8v-146.56z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_update_b" unicode="&#59182;" d="M751.68 400.064h87.424a333.12 333.12 0 0 1-560.96 224l-26.304 27.968-26.432 27.968a409.792 409.792 0 0 0 690.496-279.936h88.512L878.08 274.944 751.68 400z m-234.24-423.68A409.792 409.792 0 0 0 108.032 368H19.584L145.92 493.056 272.32 368H184.896a333.12 333.12 0 0 1 560.96-224.064l26.304-27.904 26.368-27.968a408.64 408.64 0 0 0-281.088-111.552z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_purchase__order_b" unicode="&#59183;" d="M388.544 112.512a63.36 63.36 0 1 0 0-126.848 63.36 63.36 0 0 0 0 126.848z m440.448 0a63.36 63.36 0 1 0 0-126.848 63.36 63.36 0 0 0 0 126.848z m-562.56 704a38.4 38.4 0 0 0 30.848-28.544l27.328-110.272h516.8a96 96 0 0 0 92.928-120.128l-66.816-256.96a96 96 0 0 0-92.864-71.872H411.52l-22.4-29.44h503.232a38.4 38.4 0 1 0 0-76.8h-580.48a38.464 38.464 0 0 0-30.592 61.568l65.472 86.528-116.8 469.76H133.504a38.4 38.4 0 1 0 0 76.736h126.528l6.4-0.512z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_unshare_b" unicode="&#59184;" d="M512 800a416 416 0 1 0 0-832 416 416 0 0 0 0 832zM310.208 656.64a339.2 339.2 0 0 1 337.024-583.744L310.144 656.768zM512 723.2c-48.128 0-93.888-10.048-135.296-28.096l336.96-583.68A339.2 339.2 0 0 1 512 723.2z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_organization_b" unicode="&#59185;" d="M704.512 832a64 64 0 0 0 64-64v-177.6l-0.32-6.528a64 64 0 0 0-57.152-57.152l-6.528-0.32H550.656v-87.488h288.512a70.4 70.4 0 0 0 70.4-70.4v-126.912h50.944l6.464-0.32a64 64 0 0 0 57.536-63.68V0l-0.384-6.528a64 64 0 0 0-57.152-57.088l-6.464-0.384h-177.92l-6.592 0.384a64 64 0 0 0-57.152 57.088L718.528 0v177.6a64 64 0 0 0 57.472 63.68l6.528 0.32h50.304V362.112h-282.24v-120.512h50.688l6.464-0.32a64 64 0 0 0 57.536-63.68V0l-0.384-6.528a64 64 0 0 0-57.152-57.088L601.28-64h-177.92l-6.592 0.384a64 64 0 0 0-57.152 57.088L359.296 0v177.6a64 64 0 0 0 57.472 63.68l6.528 0.32h50.56V362.112H181.824v-120.512h60.16l6.4-0.32a64 64 0 0 0 57.6-63.68V0l-0.448-6.528a64 64 0 0 0-57.088-57.088L241.92-64H64l-6.592 0.384a64 64 0 0 0-57.088 57.088L0 0v177.6a64 64 0 0 0 57.408 63.68L64 241.664h40.96V368.64a70.4 70.4 0 0 0 70.464 70.4h298.432V526.336H320.512l-6.592 0.32a64 64 0 0 0-57.088 57.152l-0.32 6.528V768a64 64 0 0 0 64 64h384zM76.8 12.8h152.32v152H76.8V12.8z m359.296 0h152.32v152h-152.32V12.8z m359.296 0h152.32v152h-152.32V12.8z m-462.08 590.4h358.4V755.2h-358.4v-152z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_sortingby_b" unicode="&#59186;" d="M786.368 152.832L889.6 256l38.464-38.528 38.528-38.4-196.032-196.032a54.464 54.464 0 0 0-72.96-3.584l-3.968 3.584-196.032 196.032L574.464 256l103.168-103.168V746.496h108.8v-593.664zM291.968 800.896c14.464 0 28.352-5.76 38.528-16l196.032-195.968-38.528-38.4-38.4-38.528-103.232 103.232v-593.728h-108.8V615.232L134.528 512 57.536 588.928l196.032 196.032 3.904 3.584a54.4 54.4 0 0 0 34.56 12.352z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_sales__order_b" unicode="&#59187;" d="M817.664 753.6a64 64 0 0 0 63.104-63.04l5.12-353.408c0-0.256 0.128-0.512 0.32-0.64a1.024 1.024 0 0 0 0-1.408L522.176-28.8a64 64 0 0 0-90.496 0L99.136 303.744a64 64 0 0 0 0 90.496l364.032 364.16v0.256c0 0.064 0 0.192 0.128 0.192l354.368-5.248z m-110.016-174.208A128 128 0 1 1 526.72 398.336a128 128 0 0 1 181.056 181.056z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_sendtest_b" unicode="&#59188;" d="M928 833.6a38.4 38.4 0 0 0 38.4-38.4v-822.4a38.4 38.4 0 0 0-38.4-38.4h-832a38.4 38.4 0 0 0-38.4 38.4V795.136a38.4 38.4 0 0 0 38.4 38.4h832zM134.4 11.2h755.2V756.864H134.4v-745.664z m328.128 531.456a38.4 38.4 0 0 0 30.272-23.04l78.464-184.448 63.04 77.056a38.464 38.464 0 0 0 29.76 14.08h154.88v-76.8H682.24l-91.648-112.128a38.4 38.4 0 0 0-65.088 9.344L447.552 429.632l-63.104-80.64a38.4 38.4 0 0 0-30.272-14.784h-147.2v76.8h128.64l91.648 117.248 3.392 3.712a38.4 38.4 0 0 0 31.872 10.624z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_time_full_b" unicode="&#59189;" d="M512 800a416 416 0 1 0 0-832 416 416 0 0 0 0 832z m-62.08-176.576V355.84a38.464 38.464 0 0 1 38.336-38.464h187.776v76.8H526.72V623.424h-76.8z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_datasource_b" unicode="&#59190;" d="M96 800a64 64 0 0 1-64-64v-704l0.32-6.528a64 64 0 0 1 57.088-57.152l6.592-0.32h832l6.528 0.32a64 64 0 0 1 57.472 63.68v704a64 64 0 0 1-64 64h-832z m819.2-755.2H108.8v173.76h806.4V44.8z m-730.56 47.872h128v76.8h-128v-76.8zM915.2 295.296H108.8V470.4h806.4v-175.04zM184.64 344.448h128v76.8h-128v-76.8zM915.2 547.072H108.8V723.2h806.4v-176.128zM184.64 596.16h128v76.8h-128v-76.8z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_unpack_b" unicode="&#59180;" d="M668.16 844.032a38.4 38.4 0 0 0 31.232 2.112l4.224-1.92 333.76-172.16a38.4 38.4 0 0 0 0.512-67.968l-85.312-45.76 85.12-44.928a38.4 38.4 0 0 0 0.512-67.648l-129.92-70.976v-241.6a38.4 38.4 0 0 0-20.352-33.92l-328.448-175.296a38.4 38.4 0 0 0-35.84-0.128l-334.976 175.36a38.4 38.4 0 0 0-20.544 33.92V376.576L41.28 445.76a38.4 38.4 0 0 0 0.512 67.648l85.12 44.928L41.6 604.16a38.4 38.4 0 0 0 0.512 67.968L375.872 844.16l4.224 1.92a38.4 38.4 0 0 0 31.296-2.112L539.776 776.32 668.096 844.032zM244.8 156.416l260.736-136.32V318.72l-98.56-52.928a38.4 38.4 0 0 0-36.544 0.128L244.864 334.528v-178.112z m464.128 109.504a38.4 38.4 0 0 0-36.544-0.128L582.4 314.24v-290.816l249.024 132.864v176.64l-122.432-66.944zM140.992 478.912L389.12 343.36l69.312 37.184-249.92 134.08-67.52-35.712z m480-98.368l69.376-37.12 248.128 135.488-67.584 35.712-249.92-134.08z m-330.624 177.28l249.408-133.632 249.28 133.696-249.28 131.648-249.408-131.648zM142.208 637.376l66.56-35.712 248.768 131.328-64.256 33.92-251.072-129.536z m479.68 95.616L870.656 601.6l66.688 35.712-251.072 129.536-64.384-33.92z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_vgm_n_b" unicode="&#59179;" d="M615.04 753.6a32 32 0 0 0 32-32v-64c0-1.088-0.32-2.176-0.448-3.264h67.264l8.32-0.384a96 96 0 0 0 85.312-74.496l101.12-448a96 96 0 0 0-82.368-116.48l-11.328-0.64H223.04a96 96 0 0 0-95.488 105.92l1.92 11.2 100.992 448a96 96 0 0 0 93.632 74.88h67.2c-0.128 1.088-0.32 2.176-0.32 3.264v64a32 32 0 0 0 32 32h192zM324.032 577.472a19.2 19.2 0 0 1-18.752-14.912l-101.056-448a19.2 19.2 0 0 1 18.752-23.424h591.872a19.2 19.2 0 0 1 18.752 23.424l-101.056 448a19.2 19.2 0 0 1-18.752 14.912h-389.76z m320.32-211.2c23.04 0 40.32-2.112 51.776-6.272 11.52-4.16 21.056-10.688 28.544-19.584 7.616-8.704 13.312-19.84 17.152-33.344l-62.4-11.136a33.664 33.664 0 0 1-13.12 18.112 40.448 40.448 0 0 1-23.36 6.272 42.432 42.432 0 0 1-33.92-14.848c-8.384-9.792-12.608-25.28-12.608-46.592 0-22.592 4.224-38.784 12.672-48.512 8.576-9.664 20.48-14.528 35.712-14.528 7.232 0 14.08 1.024 20.672 3.072 6.592 2.112 14.08 5.696 22.528 10.688v19.712h-43.2v43.52h99.84v-89.152a206.656 206.656 0 0 0-50.752-26.688c-14.72-4.672-32.064-7.04-52.224-7.04-24.832 0-45.12 4.288-60.8 12.736a87.488 87.488 0 0 0-36.352 37.76 124.8 124.8 0 0 0-12.8 57.664c0 22.848 4.672 42.688 14.08 59.52 9.408 16.896 23.232 29.76 41.408 38.464 14.144 6.784 33.216 10.176 57.152 10.176z m-283.136-3.584v-78.976l67.776 78.976h85.888l-76.16-78.848 79.552-130.304H438.72l-44.032 86.08-33.408-35.008v-51.072h-64.64V362.688h64.64z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_booking_resend" unicode="&#59178;" d="M114.858667 750.506667a21.333333 21.333333 0 0 0 30.634666 21.077333l751.786667-368.384a21.333333 21.333333 0 0 0 0-38.314667L168.192 7.594667l-22.741333-11.136a21.333333 21.333333 0 0 0-30.634667 21.034666l0.554667 3.242667 6.186666 24.618667 83.072 333.482666a21.461333 21.461333 0 0 1 0 10.325334L115.413333 747.221333l-0.554666 3.285334z m153.429333-398.677334l-62.208-249.6 509.44 249.6H268.288z m-0.938667 68.266667h440.32L206.08 665.813333l61.269333-245.76z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_hbl_reopen" unicode="&#59176;" d="M631.381333-41.984l-64-1.28-3.669333 179.413333 64 1.28 3.669333-179.413333z m237.482667 92.714667l-48.256-42.026667-117.802667 135.424 48.213334 41.984 117.845333-135.381333zM372.48 444.458667l-130.688-133.333334a113.066667 113.066667 0 0 1 161.536-158.250666l130.56 133.290666 26.026667-25.386666 25.856-25.344-130.645334-133.290667a185.6 185.6 0 0 0-265.088 259.712l130.56 133.290667 51.882667-50.688z m556.714667-114.005334l-0.298667-64-179.498667 0.810667 0.298667 64 179.498667-0.810667zM574.293333 751.018667a185.6 185.6 0 0 0 262.442667-262.485334l-131.925333-131.968-51.285334 51.285334 131.968 131.968A113.066667 113.066667 0 0 1 625.578667 699.733333L493.653333 567.765333l-25.642666 25.6-25.6 25.685334 131.925333 131.968zM244.309333 546.56l-1.621333-64-179.413333 4.48 1.578666 64 179.456-4.48z m49.066667 122.154667l-49.109333-41.002667-115.029334 137.813333 49.066667 40.96 115.072-137.813333z m139.178667 5.12h-64V853.333333h64v-179.498666z"  horiz-adv-x="1066" />
+      
+      <glyph glyph-name="icon_eci_st_retrigger" unicode="&#59177;" d="M751.658667 400h89.6a335.274667 335.274667 0 0 1-564.693334 225.706667L226.816 678.4a406.528 406.528 0 0 0 279.68 111.018667c219.050667 0 397.696-172.714667 407.296-389.418667h90.666667l-126.421334-125.098667-126.378666 125.098667z m-234.282667-421.461333c-219.050667 0-397.696 172.714667-407.296 389.418666H19.541333l126.421334 125.098667 126.378666-125.098667H182.613333a335.274667 335.274667 0 0 1 564.693334-225.664l49.749333-52.736a406.528 406.528 0 0 0-279.68-111.018666z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_next_b1" unicode="&#59175;" d="M706.112 336l-144.32-144.32a48 48 0 0 1 67.84-67.84l226.304 226.24a48 48 0 0 1 0 67.84L629.632 644.288a48 48 0 0 1-67.84-67.84l144.32-144.384h-476.16a48 48 0 1 1 0-96h476.16z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_default_screen_b" unicode="&#59174;" d="M372.864 283.84a38.4 38.4 0 0 0 38.4-38.4v-246.912h-76.8v208.512H126.08v76.8h246.848z m525.12-76.8h-208.512v-208.512H612.736v246.912a38.4 38.4 0 0 0 38.4 38.4h246.848v-76.8z m-486.656 315.52a38.4 38.4 0 0 0-38.4-38.4H126.08v76.8h208.512V769.408h76.736v-246.848z m278.144 38.4h208.512v-76.8h-246.848a38.4 38.4 0 0 0-38.4 38.4V769.408h76.8v-208.448z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_update__detail_b" unicode="&#59173;" d="M272.32 367.936H184.896a333.12 333.12 0 0 1 560.96-224.064l26.304-27.904 26.368-27.968a409.792 409.792 0 0 0-690.56 279.936H19.648L145.92 493.056 272.32 368z m234.24 423.68a409.792 409.792 0 0 0 409.408-391.552h88.448L878.08 274.944 751.68 400h87.424a333.12 333.12 0 0 1-560.96 224l-26.304 27.968-26.432 27.968a408.64 408.64 0 0 0 281.152 111.552zM371.264 422.4A38.4 38.4 0 1 0 371.2 345.6a38.4 38.4 0 0 0 0 76.8z m140.8 0A38.4 38.4 0 1 0 512 345.6a38.4 38.4 0 0 0 0 76.8z m140.8 0a38.4 38.4 0 1 0 0-76.8 38.4 38.4 0 0 0 0 76.8z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_brno_b" unicode="&#59165;" d="M839.04 832a64 64 0 0 0 64-64v-768l-0.32-6.528a64 64 0 0 0-57.152-57.152l-6.592-0.32h-640l-6.528 0.32a64 64 0 0 0-57.152 57.152L135.04 0V768a64 64 0 0 0 64 64h640z m-627.2-819.2h614.4V755.2h-614.4v-742.4z m531.2 80h-448v76.8h448v-76.8z m0 150.336h-448V319.872h448v-76.8zM532.096 658.816c4.096-0.448 8.064-1.024 12.032-1.792 2.048-0.448 4.032-1.024 6.08-1.536 1.984-0.512 4.032-0.896 5.952-1.472l5.824-2.048 4.8-1.728 2.176-0.896c1.152-0.448 2.304-1.088 3.456-1.6 2.112-0.96 4.224-1.92 6.272-3.008a128.32 128.32 0 0 0 53.952-54.08l2.816-5.76 2.432-5.376 2.176-6.08 1.92-5.376 1.6-6.336c0.448-1.856 0.96-3.712 1.344-5.632 0.448-2.112 0.64-4.352 1.024-6.528 0.832-6.08 1.472-12.288 1.472-18.56a128.64 128.64 0 0 0-5.632-37.184c-0.64-2.048-1.28-4.096-2.048-6.08l-1.92-5.376c-0.64-1.6-1.472-3.2-2.176-4.8a128.768 128.768 0 0 0-2.88-5.952c-0.896-1.728-1.92-3.392-2.88-5.056a128.128 128.128 0 0 0-10.496-15.424c-1.088-1.408-2.24-2.688-3.392-4.032l-4.8-5.312-3.392-3.264a129.152 129.152 0 0 0-36.16-24.512l-3.968-1.792-5.76-2.112c-2.176-0.768-4.288-1.536-6.464-2.176-1.728-0.512-3.456-0.896-5.12-1.28a128.832 128.832 0 0 0-31.36-4.032 129.984 129.984 0 0 0-25.28 2.56 127.744 127.744 0 0 0-11.392 2.816c-1.792 0.448-3.52 1.152-5.312 1.728a128.064 128.064 0 0 0-16.64 7.04c-1.536 0.832-3.136 1.6-4.672 2.496-1.92 1.088-3.84 2.304-5.696 3.456l-4.352 2.88a128.64 128.64 0 0 0-5.12 3.84c-1.28 1.024-2.624 1.92-3.84 3.008a129.216 129.216 0 0 0-16.384 16.32l-2.816 3.584a128.896 128.896 0 0 0-4.096 5.568l-2.944 4.48a128.512 128.512 0 0 0-8.448 15.68l-2.24 5.12a127.68 127.68 0 0 0-4.032 11.776l-1.408 5.312A128.512 128.512 0 0 0 519.04 659.456l13.12-0.64z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_smart_b" unicode="&#59166;" d="M768.512 315.328a32 32 0 0 0 55.488 0l32.704-56.704c2.432-4.224 5.76-7.872 9.856-10.56l49.664-33.408a32 32 0 0 0 0-53.12L866.56 128a31.936 31.936 0 0 1-9.856-10.56l-32.704-56.704a32 32 0 0 0-55.488 0l-32.064 55.68a32 32 0 0 1-11.776 11.712l-55.68 32.192a32 32 0 0 0 0 55.424l55.68 32.128a32 32 0 0 1 11.776 11.712l32.128 55.68zM408.192 707.2a32 32 0 0 0 55.424 0l100.48-174.08a32 32 0 0 1 9.728-10.56l150.4-101.12a32 32 0 0 0 0-53.12l-150.4-101.12a32 32 0 0 1-9.792-10.56l-100.48-174.08a32 32 0 0 0-55.36 0l-99.84 173.056a32 32 0 0 1-11.712 11.712l-173.056 99.84a32 32 0 0 0 0 55.488l173.056 99.84a32 32 0 0 1 11.712 11.648l99.84 173.056z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_map_b" unicode="&#59167;" d="M532.032 840.96a254.848 254.848 0 0 0 241.664-254.464l-0.64-16.768a290.688 290.688 0 0 0-15.36-69.248l199.36 53.312a38.4 38.4 0 0 0 48.32-37.056v-461.056a38.4 38.4 0 0 0-27.52-36.8l-307.904-90.624a38.528 38.528 0 0 0-24.96 1.152l-310.592 122.56-247.68-111.104a38.4 38.4 0 0 0-54.08 35.008V436.928a38.4 38.4 0 0 0 22.656 35.072l209.92 94.08-0.384 3.328-0.704 17.088A254.848 254.848 0 0 0 518.912 841.344l13.056-0.32zM109.312 412.096v-376.832l208.384 93.44 7.296 2.432a38.528 38.528 0 0 0 22.528-1.728l313.28-123.648 267.776 78.72V466.752l-212.8-56.96c-72.384-126.976-188.16-238.336-196.928-238.592l-1.856 0.64c-21.12 11.52-178.56 166.08-233.792 318.144L109.376 412.032z m409.6 352.448a178.048 178.048 0 0 1-178.048-177.92c0-29.248 10.112-65.92 30.464-107.904 19.968-41.344 47.424-83.072 76.48-121.024 24.768-32.384 49.92-60.8 70.72-82.624 20.864 22.144 46.272 51.2 71.232 83.968 27.136 35.712 52.864 74.624 72.448 113.152l-0.704 2.56 2.304 0.64 2.624 5.12c20.416 42.112 30.4 78.144 30.4 106.048a177.984 177.984 0 0 1-177.92 177.984z m10.368-65.92c51.84-5.248 92.352-49.088 92.352-102.4l-0.512-10.496A102.848 102.848 0 0 0 518.848 493.44l-10.56 0.512A102.976 102.976 0 0 0 416.512 585.6L416 596.16c0 56.832 46.08 102.912 102.848 102.912l10.496-0.512z m-10.496-76.288a26.112 26.112 0 1 1 0.064-52.224 26.112 26.112 0 0 1 0 52.224z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_template_b1" unicode="&#59168;" d="M898.56 559.872v-13.44c0.128-1.088 0.064-2.176 0-3.328V32a134.4 134.4 0 0 0-134.336-134.4H273.728a134.4 134.4 0 0 0-134.4 134.4v704c0 74.24 60.16 134.4 134.4 134.4h314.368l310.528-310.528zM273.792 793.6a57.6 57.6 0 0 1-57.6-57.6v-704l0.32-5.888a57.6 57.6 0 0 1 57.28-51.712h490.496a57.6 57.6 0 0 1 57.344 51.712l0.32 5.888V505.6h-153.6a134.4 134.4 0 0 0-134.4 134.336V793.6H273.664zM610.624 640c0-29.824 22.72-54.4 51.712-57.28l5.888-0.32h99.456L610.56 739.392v-99.456z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_door_b" unicode="&#59169;" d="M446.912 784a96 96 0 0 0 131.328 0.96l394.624-364.288-43.392-46.976-42.24 39.04v-423.104a32 32 0 0 0-32-32H618.432a32 32 0 0 0-32 32v174.08H441.152v-174.08a32 32 0 0 0-32-32H185.344a32 32 0 0 0-32 32V416l-44.096-41.984-44.16 46.336L446.976 784z m87.872-46.016a32 32 0 0 1-43.712-0.384L217.344 476.928v-455.296h159.808v174.08a32 32 0 0 0 32 32h209.344a32 32 0 0 0 32-32v-174.08h172.672V471.872L534.784 737.92z m317.184 17.92a32 32 0 0 0 32-32v-144.64h-64v112.64h-129.024v64h161.024z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_map_point_b" unicode="&#59170;" d="M823.296 462.677333c0-220.202667-301.653333-516.906667-317.013333-516.906666-15.445333 0-317.098667 291.584-317.098667 516.906666a317.056 317.056 0 1 0 634.112 0zM506.24 474.709333m-128 0a128 128 0 1 1 256 0 128 128 0 1 1-256 0Z"  horiz-adv-x="1066" />
+      
+      <glyph glyph-name="icon_current__location_b" unicode="&#59171;" d="M852.608 796.288c55.04 13.44 104.96-43.2 80.128-97.792L620.16 11.008c-31.36-68.864-134.464-46.528-134.464 29.184v325.76H169.216c-76.992 0-98.112 105.792-27.072 135.36l699.392 291.392 11.072 3.584zM201.216 442.752h361.152v-373.12L856.064 715.52 201.216 442.752z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_address_book_b" unicode="&#59172;" d="M887.04 832a64 64 0 0 0 64-64v-768l-0.32-6.528a64 64 0 0 0-63.744-57.472H215.04l-6.528 0.32a64 64 0 0 0-57.152 57.152L151.04 0v165.504h-64v76.8h64V525.696h-64v76.8h64V768a64 64 0 0 0 57.472 63.68L214.976 832h672zM227.84 602.496h64v-76.8h-64v-283.456h64v-76.8h-64V12.8h646.4V755.2H227.84v-152.704z m527.936-466.688h-398.72v76.8h398.72v-76.8z m0 214.4h-398.72V426.88h398.72v-76.8z m0 214.4h-398.72V641.28h398.72v-76.8z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_guideright_b" unicode="&#59163;" d="M870.4 384.512a38.4 38.4 0 0 1-35.392 38.016 237.824 237.824 0 0 0-211.84 177.28l-14.144 54.336-74.368-19.392 14.144-54.4A313.28 313.28 0 0 1 647.04 422.4H192v-76.8h456.896a319.936 319.936 0 0 1-100.8-160.896l-13.376-50.88 74.24-19.648 13.44 51.008a244.096 244.096 0 0 0 213.248 180.8 38.4 38.4 0 0 1 34.752 38.528z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_guideleft_b" unicode="&#59164;" d="M181.44 384.512a38.4 38.4 0 0 0 35.392 38.016A237.824 237.824 0 0 1 428.8 599.808l14.08 54.336 74.368-19.392-14.08-54.4A313.28 313.28 0 0 0 404.736 422.4h455.04v-76.8H402.944a319.936 319.936 0 0 0 100.736-160.896l13.376-50.88-74.24-19.648-13.44 51.008A244.096 244.096 0 0 1 216.32 345.984a38.4 38.4 0 0 0-34.816 38.528z"  horiz-adv-x="1088" />
+      
       <glyph glyph-name="icon_guidelines_b" unicode="&#59161;" d="M603.84-2.88a38.4 38.4 0 0 0 0-75.264l-7.744-0.704h-160a38.4 38.4 0 1 0 0 76.736h160l7.68-0.768zM516.096 647.36a269.376 269.376 0 0 0 144.576-496.512c-0.896-2.432-1.92-6.144-3.008-11.2a324.672 324.672 0 0 1-5.12-43.712 72.128 72.128 0 0 0-71.04-68.224l-127.296-0.832a72.32 72.32 0 0 0-72.064 67.648 298.24 298.24 0 0 1-5.76 43.52 74.304 74.304 0 0 1-3.712 12.16 269.312 269.312 0 0 0 143.424 497.216z m-234.24-489.408l27.136-27.2-76.928-76.864-54.208 54.272 76.8 76.928 27.264-27.136z m572.608-49.792l-54.272-54.272-76.928 76.864 27.136 27.2 27.2 27.136 76.864-76.928zM516.096 570.624a192.64 192.64 0 0 1-99.008-357.76l4.8-3.2c10.496-8 16.512-18.56 19.968-25.92 4.288-9.216 7.296-19.264 9.408-28.48 3.968-17.152 5.952-36.096 7.104-51.52l117.76 0.768c1.024 15.296 2.816 33.92 6.272 50.688 1.92 9.024 4.48 18.944 8.448 27.968a62.848 62.848 0 0 0 24.256 29.632l10.24 6.72a192.64 192.64 0 0 1-109.312 351.104zM184.96 350.144H72.448V426.88H184.96v-76.8z m774.784 0H847.36V426.88h112.448v-76.8zM309.056 653.76l-27.072-27.2-27.2-27.136-76.864 76.928 54.208 54.272 76.928-76.864z m545.472 22.592L777.6 599.424l-27.2 27.136-27.136 27.2 76.928 76.864 54.272-54.272z m-300.608 22.912h-76.8V807.488h76.8v-108.224z"  horiz-adv-x="1088" />
       
       <glyph glyph-name="icon_cancelled_b" unicode="&#59162;" d="M906.56 191.936l-27.136-27.136-80.896-80.768 80.896-80.896 27.2-27.136-54.4-54.336-27.072 27.2-80.896 80.896-80.768-80.896-27.136-27.2-54.336 54.336 27.2 27.136 80.832 80.896-80.832 80.768L582.016 192l54.336 54.336 27.136-27.2 80.768-80.832 80.896 80.832 27.136 27.2 54.272-54.336zM788.288 846.4a102.4 102.4 0 0 0 102.4-102.4v-458.24h-76.8v458.24a25.6 25.6 0 0 1-25.6 25.6H233.792a25.6 25.6 0 0 1-25.6-25.6v-704c0-14.08 11.456-25.6 25.6-25.6h277.248v-76.8H233.792a102.4 102.4 0 0 0-102.4 102.4v704a102.4 102.4 0 0 0 102.4 102.4h554.496z m-91.136-550.592H335.488v76.8h361.664v-76.8z m0 205.632H335.488v76.8h361.664v-76.8z"  horiz-adv-x="1088" />

BIN
src/styles/icons/iconfont.ttf


BIN
src/styles/icons/iconfont.woff


BIN
src/styles/icons/iconfont.woff2


+ 4 - 0
src/styles/theme.scss

@@ -358,6 +358,8 @@
   // report
   --color-schedule-bg: #F6F8FA;
   --color-schedule-details-bg: #F5F7FA;
+  
+  --color-attchment-summary-bg: #f9fafb;
 }
 
 :root.dark {
@@ -585,5 +587,7 @@
   --color-ant-picker-th: rgba(240, 241, 243,0.3);
 
   --color-json-item-hover: #3e5966;
+
+  --color-attchment-summary-bg: #2b2f36;
 }
   

+ 5 - 1
src/utils/table.ts

@@ -6,7 +6,7 @@ import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
  * @param grid 表格实例
  * @returns
  */
-export const autoWidth = (tableData: VxeGridProps, grid: VxeGridInstance) => {
+export const autoWidth = (tableData: VxeGridProps, grid: VxeGridInstance, customizeWidth?: { [key: string]: number }) => {
   const columns = tableData.columns
   const data = tableData.data
   const columnsWidth: { width: number; field: any }[] = []
@@ -47,6 +47,10 @@ export const autoWidth = (tableData: VxeGridProps, grid: VxeGridInstance) => {
       if (field === 'Mode') {
         width = 80
       }
+      // 如果有自定义宽度,则使用自定义宽度
+      if (customizeWidth && customizeWidth[field]) {
+        width = customizeWidth[field]
+      }
 
       columnsWidth.push({
         width,

+ 1 - 1
src/views/AIApiLog/src/components/LogDialog.vue

@@ -91,4 +91,4 @@ defineExpose({
     background-color: var(--color-json-item-hover);
   }
 }
-</style>
+</style>

+ 245 - 150
src/views/DestinationDelivery/src/components/ConfiguRations/src/components/RecommendDate.vue

@@ -1,11 +1,12 @@
 <script setup lang="ts">
 import SelectValue from './SelectValue.vue'
 import { ref } from 'vue'
+import { cloneDeep } from 'lodash'
 
 // 定义类型接口
 interface RuleOption {
-  label: string;
-  value: string;
+  label: string
+  value: string
 }
 interface PortOption {
   value: string
@@ -13,21 +14,20 @@ interface PortOption {
   checked: boolean
 }
 
-
 interface RuleItem {
-  priority: string;
-  rule_type: string;
-  mode_type: string;
-  ports?: string[];
-  carrier?: string[];
-  PortList?:PortOption[];
-  CarrierList?:PortOption[];
-  recommended_delivery_from: string;
-  recommended_delivery_to: string;
+  priority: string
+  rule_type: string
+  mode_type: string
+  ports?: string[]
+  carrier?: string[]
+  PortList?: PortOption[]
+  CarrierList?: PortOption[]
+  recommended_delivery_from: string
+  recommended_delivery_to: string
 }
 
 // 定义 RuleItem 中数组字段的类型
-type ArrayFields = 'ports' | 'carrier';
+type ArrayFields = 'ports' | 'carrier'
 
 const props = defineProps({
   recommendata: {
@@ -45,9 +45,7 @@ const isAir = ref(false)
 const isSea = ref(false)
 const RecommendCheckedList = ref<string[]>([])
 // 选项配置
-const AirRuleTypeoptions = ref<RuleOption[]>([
-  { label: 'Specific Rule', value: 'Specific Rule' }
-])
+const AirRuleTypeoptions = ref<RuleOption[]>([{ label: 'Specific Rule', value: 'Specific Rule' }])
 
 const RuleTypeoptions = ref<RuleOption[]>([
   { label: 'Specific Rule', value: 'Specific Rule' },
@@ -63,7 +61,7 @@ const AirContentList = ref<RuleItem[]>([
     recommended_delivery_from: '',
     recommended_delivery_to: '',
     mode_type: 'air',
-    PortList:[]
+    PortList: []
   }
 ])
 const SeaContentList = ref<RuleItem[]>([
@@ -75,36 +73,39 @@ const SeaContentList = ref<RuleItem[]>([
     recommended_delivery_from: '',
     recommended_delivery_to: '',
     mode_type: 'sea',
-    PortList:[],
+    PortList: [],
     CarrierList: []
   }
 ])
 
-const recommendata = ref(props.recommendata)
+const recommendata = ref()
 
 const initRecommendData = () => {
-  if(recommendata.value) {
+  if (recommendata.value) {
     Recommendradio.value = recommendata.value.Recommendradio
-    if(Recommendradio.value == 2) {
+    if (Recommendradio.value == 2) {
       isRecommendETA.value = true
       RecommendCheckedList.value = recommendata.value.RecommendCheckedList
-      if(RecommendCheckedList.value.includes('Air')) {
+      if (RecommendCheckedList.value.includes('Air')) {
         isAir.value = true
       }
-      if(RecommendCheckedList.value.includes('Sea')) {
+      if (RecommendCheckedList.value.includes('Sea')) {
         isSea.value = true
       }
       AirContentList.value = recommendata.value.RecommendCheckedAirList
       SeaContentList.value = recommendata.value.RecommendCheckedSeaList
     }
-    }
+  }
 }
 
-watch(() => props.recommendata, (val) => { 
-  recommendata.value = val
-  initRecommendData()
-}, { immediate: true, deep: true })
-
+watch(
+  () => props.recommendata,
+  (val) => {
+    recommendata.value = cloneDeep(val)
+    initRecommendData()
+  },
+  { immediate: true, deep: true }
+)
 
 // 创建规则项的工厂函数
 function createRuleItem(type: 'Air' | 'Sea', ruleType: string): RuleItem {
@@ -112,13 +113,13 @@ function createRuleItem(type: 'Air' | 'Sea', ruleType: string): RuleItem {
     priority: 'P1',
     rule_type: ruleType,
     recommended_delivery_from: '',
-    recommended_delivery_to: '',
+    recommended_delivery_to: ''
   }
   if (type === 'Air') {
     return {
       ...baseItem,
       ports: ruleType === '*Default Rule' ? ['ALL'] : [],
-      mode_type: 'air',
+      mode_type: 'air'
       // PortList: JSON.parse(JSON.stringify(AirPorList.value))
     }
   }
@@ -126,7 +127,7 @@ function createRuleItem(type: 'Air' | 'Sea', ruleType: string): RuleItem {
     ...baseItem,
     ports: ruleType === '*Default Rule' ? ['ALL'] : [],
     carrier: ruleType === '*Default Rule' ? ['ALL'] : [],
-    mode_type: 'sea',
+    mode_type: 'sea'
     // PortList: JSON.parse(JSON.stringify(SeaPortList.value)),
     // CarrierList: JSON.parse(JSON.stringify(SeaCarrierList.value))
   }
@@ -149,56 +150,68 @@ const CheckChange = (val: string[]) => {
 
 const handleCheckboxClick = (event: Event) => {
   const target = event.target as HTMLElement
-  const isCheckboxInput = target.closest('.el-checkbox__inner')
+  const isCheckboxInput = target.closest('.el-checkbox__input')
   const isCheckboxTitle = target.closest('.titlecheckbox')
   if (!isCheckboxInput && !isCheckboxTitle) {
     event.preventDefault()
   }
 }
+
 // 选择booking window
 const ChangeFrequency = (val: number) => {
   isRecommendETA.value = val === 2
-  emits('chackchangerecommend', RecommendCheckedList.value, AirContentList.value, SeaContentList.value, Recommendradio.value)
+  emits(
+    'chackchangerecommend',
+    RecommendCheckedList.value,
+    AirContentList.value,
+    SeaContentList.value,
+    Recommendradio.value
+  )
 }
 // 修复后的 handleInput 函数
-const handleInput = (val: string, index: number, type: 'recommended_delivery_from' | 'recommended_delivery_to', list: RuleItem[]) => {
+const handleInput = (
+  val: string,
+  index: number,
+  type: 'recommended_delivery_from' | 'recommended_delivery_to',
+  list: RuleItem[]
+) => {
   // 移除非数字字符
-  const numStr = val.replace(/[^\d]/g, '');
+  const numStr = val.replace(/[^\d]/g, '')
   // 处理空值情况
   if (numStr === '') {
-    list[index][type] = '';
-    return;
+    list[index][type] = ''
+    return
   }
-  
+
   // 转换为数字以进行范围检查
-  const num = parseInt(numStr, 10);
-  
+  const num = parseInt(numStr, 10)
+
   // 确保最小值为1(但保持为字符串形式)
   if (num < 1) {
-    list[index][type] = '1';
+    list[index][type] = '1'
   } else {
     // 保持为字符串形式
-    list[index][type] = numStr;
+    list[index][type] = numStr
   }
-};
+}
 // 删除数据
 const handleDelete = (index: number, list: RuleItem[], type: 'Air' | 'Sea') => {
-  list.splice(index, 1);
+  list.splice(index, 1)
   if (list.length === 0) {
     if (type === 'Air') {
       isAir.value = false
-      RecommendCheckedList.value = RecommendCheckedList.value.filter(item => item !== 'Air')
+      RecommendCheckedList.value = RecommendCheckedList.value.filter((item) => item !== 'Air')
     } else {
       isSea.value = false
-      RecommendCheckedList.value = RecommendCheckedList.value.filter(item => item !== 'Sea')
+      RecommendCheckedList.value = RecommendCheckedList.value.filter((item) => item !== 'Sea')
     }
   }
   updatePriorities()
-};
+}
 // 添加数据
 const AddRuleItem = (list: RuleItem[], type: 'Air' | 'Sea') => {
   // 检查是否已存在默认规则
-  const hasDefaultRule = list.some(item => item.rule_type === '*Default Rule')
+  const hasDefaultRule = list.some((item) => item.rule_type === '*Default Rule')
   // 如果已经有默认规则,则创建特定规则
   const ruleType = hasDefaultRule ? 'Specific Rule' : '*Default Rule'
   list.push(createRuleItem(type, ruleType))
@@ -206,15 +219,21 @@ const AddRuleItem = (list: RuleItem[], type: 'Air' | 'Sea') => {
 }
 // 根据RuleType的值来修改Priority的值
 const updatePriorities = () => {
-  emits('chackchangerecommend', RecommendCheckedList.value, AirContentList.value, SeaContentList.value,Recommendradio.value)
+  emits(
+    'chackchangerecommend',
+    RecommendCheckedList.value,
+    AirContentList.value,
+    SeaContentList.value,
+    Recommendradio.value
+  )
   updateListPriorities(AirContentList.value, 'Air')
   updateListPriorities(SeaContentList.value, 'Sea')
-};
+}
 // 统一更新列表优先级
 const updateListPriorities = (list: RuleItem[], type: 'Air' | 'Sea') => {
   const length = list.length
   // 保护默认规则的数据
-  list.forEach(item => {
+  list.forEach((item) => {
     if (item.rule_type === '*Default Rule') {
       if (type === 'Air') {
         item.ports = ['ALL']
@@ -234,40 +253,43 @@ const updateListPriorities = (list: RuleItem[], type: 'Air' | 'Sea') => {
 }
 // 处理长度为1
 const handleLengthOne = (list: RuleItem[], type: string) => {
-  list.forEach(item => item.priority = 'P1')
-};
+  list.forEach((item) => (item.priority = 'P1'))
+}
 // 处理长度为2
 const handleLengthTwo = (list: RuleItem[], type: string) => {
-  const types = new Set(list.map(i => i.rule_type))
+  const types = new Set(list.map((i) => i.rule_type))
   // 两个都是 *Default Rule
   if (types.size === 1 && types.has('*Default Rule')) {
-    list.forEach(item => item.priority = 'P1')
+    list.forEach((item) => (item.priority = 'P1'))
     return
   }
   // 包含 *Default Rule 和其他类型
   if (types.has('*Default Rule')) {
-    list.forEach(item => {
+    list.forEach((item) => {
       item.priority = item.rule_type === '*Default Rule' ? 'P2' : 'P1'
     })
     return
   }
   // 同时存在 Specific Rule 和 Single Dimension
   if (types.has('Specific Rule') && types.has('Single Dimension')) {
-    list.forEach(item => {
+    list.forEach((item) => {
       item.priority = item.rule_type === 'Specific Rule' ? 'P1' : 'P2'
     })
     return
   }
   // 其他情况
-  list.forEach(item => item.priority = 'P1')
-};
+  list.forEach((item) => (item.priority = 'P1'))
+}
 // 处理长度≥3
 const handleLengthThreePlus = (list: RuleItem[], type: string) => {
   // 统计各类型数量
-  const counts = list.reduce((acc, cur) => {
-    acc[cur.rule_type] = (acc[cur.rule_type] || 0) + 1
-    return acc
-  }, {} as Record<string, number>)
+  const counts = list.reduce(
+    (acc, cur) => {
+      acc[cur.rule_type] = (acc[cur.rule_type] || 0) + 1
+      return acc
+    },
+    {} as Record<string, number>
+  )
   // 获取所有存在的类型
   const existingTypes = Object.keys(counts)
   // 三个不同类型都存在
@@ -281,28 +303,30 @@ const handleLengthThreePlus = (list: RuleItem[], type: string) => {
       'Single Dimension': 'P2',
       '*Default Rule': 'P3'
     }
-    list.forEach(item => {
+    list.forEach((item) => {
       item.priority = priorityMap[item.rule_type]
     })
     return
   }
   // 全为同一种类型的情况
   if (existingTypes.length === 1) {
-    list.forEach(item => item.priority = 'P1')
+    list.forEach((item) => (item.priority = 'P1'))
     return
   }
   // 处理 Specific + Default 组合
-  if (existingTypes.length === 2 && 
-      existingTypes.includes('Specific Rule') && 
-      existingTypes.includes('*Default Rule')) {
-    list.forEach(item => {
+  if (
+    existingTypes.length === 2 &&
+    existingTypes.includes('Specific Rule') &&
+    existingTypes.includes('*Default Rule')
+  ) {
+    list.forEach((item) => {
       item.priority = item.rule_type === 'Specific Rule' ? 'P1' : 'P2'
     })
     return
   }
   // 存在两个Default Rule
   if (counts['*Default Rule'] === 2 && existingTypes.length === 2) {
-    list.forEach(item => {
+    list.forEach((item) => {
       item.priority = item.rule_type === '*Default Rule' ? 'P2' : 'P1'
     })
     return
@@ -311,12 +335,12 @@ const handleLengthThreePlus = (list: RuleItem[], type: string) => {
   if (counts['Single Dimension'] === 2) {
     if (existingTypes.includes('*Default Rule')) {
       // 两个Single + 一个Default
-      list.forEach(item => {
+      list.forEach((item) => {
         item.priority = item.rule_type === '*Default Rule' ? 'P2' : 'P1'
       })
     } else if (existingTypes.includes('Specific Rule')) {
       // 两个Single + 一个Specific
-      list.forEach(item => {
+      list.forEach((item) => {
         item.priority = item.rule_type === 'Specific Rule' ? 'P1' : 'P2'
       })
     }
@@ -328,33 +352,39 @@ const handleLengthThreePlus = (list: RuleItem[], type: string) => {
     'Single Dimension': 'P2',
     '*Default Rule': 'P3'
   }
-  list.forEach(item => {
+  list.forEach((item) => {
     item.priority = defaultPriorityMap[item.rule_type] || 'P3'
   })
 }
 // 修复:改变选项值 - 使用类型保护
-const changeSelectedValue = (val: string[], index: number, field: ArrayFields, list: RuleItem[]) => {
-  const item = list[index] as Record<ArrayFields, string[]>;
-  item[field] = val;
+const changeSelectedValue = (
+  val: string[],
+  index: number,
+  field: ArrayFields,
+  list: RuleItem[]
+) => {
+  const item = list[index] as Record<ArrayFields, string[]>
+  item[field] = val
   // 新增逻辑:检查是否从 Single Dimension 变为 Specific Rule
   if (item['rule_type'] != '*Default Rule') {
     if (item['mode_type'] === 'air') {
       // Air 规则:只检查 ports
       if (item.ports && item.ports.length > 0 && !item.ports.includes('ALL')) {
-        item['rule_type'] = 'Specific Rule';
-        updatePriorities();
+        item['rule_type'] = 'Specific Rule'
+        updatePriorities()
       }
     } else if (item['mode_type'] === 'sea') {
       // Sea 规则:检查 ports 和 carrier
-      const portsSelected = item.ports && item.ports.length > 0 && !item.ports.includes('ALL');
-      const carrierSelected = item.carrier && item.carrier.length > 0 && !item.carrier.includes('ALL');
-      
+      const portsSelected = item.ports && item.ports.length > 0 && !item.ports.includes('ALL')
+      const carrierSelected =
+        item.carrier && item.carrier.length > 0 && !item.carrier.includes('ALL')
+
       if (portsSelected && carrierSelected) {
-        item['rule_type'] = 'Specific Rule';
-        updatePriorities();
+        item['rule_type'] = 'Specific Rule'
+        updatePriorities()
       } else {
-        item['rule_type'] = 'Single Dimension';
-        updatePriorities();
+        item['rule_type'] = 'Single Dimension'
+        updatePriorities()
       }
     }
   }
@@ -387,32 +417,53 @@ const changeRuleType = (val: string, index: number, list: RuleItem[]) => {
           <el-checkbox-group v-model="RecommendCheckedList" @change="CheckChange">
             <!-- Air 部分 -->
             <el-checkbox class="delayedType" value="Air" @click="handleCheckboxClick">
-              <div class="titlecheckbox">
+              <div class="titlecheckbox clickable-area">
                 <div>Air</div>
-                <span class="icon_grey font_family" :class="isAir ? 'icon-icon_dropdown_b' : 'icon-icon_up_b'"></span>
+                <span
+                  class="icon_grey font_family"
+                  :class="isAir ? 'icon-icon_dropdown_b' : 'icon-icon_up_b'"
+                ></span>
               </div>
-              <div v-if="isAir" class="radiocheckbox" style="margin-top: 16px">
+              <div v-if="isAir" class="radiocheckbox" style="margin-top: 16px; padding-left: 8px">
                 <div class="AirCoulumn">
-                  <div class="AicoulumnTitile" style="width: 10%;">priority</div>
-                  <div class="AicoulumnTitile" style="width: 20%;">Rule Type</div>
-                  <div class="AicoulumnTitile" style="width: 40%;">Air Port</div>
-                  <div style="display: flex;flex-direction: column;border-right: 1px solid var(--color-system-border);width: 20%;">
+                  <div class="AicoulumnTitile" style="width: 10%">priority</div>
+                  <div class="AicoulumnTitile" style="width: 20%">Rule Type</div>
+                  <div class="AicoulumnTitile" style="width: 40%">Air Port</div>
+                  <div
+                    style="
+                      display: flex;
+                      flex-direction: column;
+                      border-right: 1px solid var(--color-system-border);
+                      width: 20%;
+                    "
+                  >
                     <div class="AicoulumnTitile2">Recommended Delivery Date</div>
-                    <div style="display: flex;height: 24px;align-items: center;">
-                      <div class="datetitle" style="border-right: 1px solid var(--color-system-border);">From (ETA/ATA + Days)</div>
+                    <div style="display: flex; height: 24px; align-items: center">
+                      <div
+                        class="datetitle"
+                        style="border-right: 1px solid var(--color-system-border)"
+                      >
+                        From (ETA/ATA + Days)
+                      </div>
                       <div class="datetitle">To (ETA/ATA + Days)</div>
                     </div>
                   </div>
-                  <div class="AirCoumlulnAdd" style="width: 10%;" @click="AddRuleItem(AirContentList, 'Air')">+ Add</div>
+                  <div
+                    class="AirCoumlulnAdd"
+                    style="width: 10%"
+                    @click="AddRuleItem(AirContentList, 'Air')"
+                  >
+                    + Add
+                  </div>
                 </div>
                 <div class="AirContent" v-for="(item, index) in AirContentList" :key="index">
-                  <div class="AirCoumlumn" style="width: 10%;">{{ item.priority }}</div>
-                  <div class="AirCoumlumn" style="width: 20%;">
+                  <div class="AirCoumlumn" style="width: 10%">{{ item.priority }}</div>
+                  <div class="AirCoumlumn" style="width: 20%">
                     <el-select
                       v-model="item.rule_type"
                       disabled
-                      style="width: 100%;"
-                      @change="val => changeRuleType(val, index, AirContentList)"
+                      style="width: 100%"
+                      @change="(val) => changeRuleType(val, index, AirContentList)"
                     >
                       <el-option
                         v-for="opt in AirRuleTypeoptions"
@@ -422,35 +473,42 @@ const changeRuleType = (val: string, index: number, list: RuleItem[]) => {
                       />
                     </el-select>
                   </div>
-                  <div class="AirCoumlumn" style="width: 40%;">
+                  <div class="AirCoumlumn" style="width: 40%">
                     <SelectValue
                       ref="AirPortRef"
                       :SelectIndex="index"
                       :SelectedValue="item.ports"
                       :typeisDisabled="item.rule_type"
                       SelectType="air"
-                      @changeSelectedValue="val => changeSelectedValue(val, index, 'ports', AirContentList)"
+                      @changeSelectedValue="
+                        (val) => changeSelectedValue(val, index, 'ports', AirContentList)
+                      "
                     />
                   </div>
-                  <div class="AirCoumlumn" style="width: 10%;">
-                    <el-input 
-                      @input="val => handleInput(val, index, 'recommended_delivery_from', AirContentList)" 
-                      placeholder="Input" 
+                  <div class="AirCoumlumn" style="width: 10%">
+                    <el-input
+                      @input="
+                        (val) =>
+                          handleInput(val, index, 'recommended_delivery_from', AirContentList)
+                      "
+                      placeholder="Input"
                       v-model="item.recommended_delivery_from"
                     />
                   </div>
-                  <div class="AirCoumlumn" style="width: 10%;">
-                    <el-input 
-                      @input="val => handleInput(val, index, 'recommended_delivery_to', AirContentList)"  
-                      placeholder="Input" 
+                  <div class="AirCoumlumn" style="width: 10%">
+                    <el-input
+                      @input="
+                        (val) => handleInput(val, index, 'recommended_delivery_to', AirContentList)
+                      "
+                      placeholder="Input"
                       v-model="item.recommended_delivery_to"
                     />
                   </div>
-                  <div class="AirDelete" style="width: 10%;">
-                    <el-button 
-                      v-if="item.rule_type !== '*Default Rule'" 
-                      @click="handleDelete(index, AirContentList, 'Air')" 
-                      class="el-button--blue" 
+                  <div class="AirDelete" style="width: 10%">
+                    <el-button
+                      v-if="item.rule_type !== '*Default Rule'"
+                      @click="handleDelete(index, AirContentList, 'Air')"
+                      class="el-button--blue"
                       style="height: 24px"
                     >
                       <span class="font_family icon-icon_delete_b"></span>
@@ -461,33 +519,54 @@ const changeRuleType = (val: string, index: number, list: RuleItem[]) => {
             </el-checkbox>
             <!-- Sea 部分 -->
             <el-checkbox class="delayedType" value="Sea" @click="handleCheckboxClick">
-              <div class="titlecheckbox">
+              <div class="titlecheckbox clickable-area">
                 <div>Sea</div>
-                <span class="icon_grey font_family" :class="isSea ? 'icon-icon_dropdown_b' : 'icon-icon_up_b'"></span>
+                <span
+                  class="icon_grey font_family"
+                  :class="isSea ? 'icon-icon_dropdown_b' : 'icon-icon_up_b'"
+                ></span>
               </div>
-              <div v-if="isSea" style="margin-top: 16px">
+              <div v-if="isSea" style="margin-top: 16px; padding-left: 8px">
                 <div class="AirCoulumn">
-                  <div class="AicoulumnTitile" style="width: 10%;">priority</div>
-                  <div class="AicoulumnTitile" style="width: 14%;">Rule Type</div>
-                  <div class="AicoulumnTitile" style="width: 23%;">Port</div>
-                  <div class="AicoulumnTitile" style="width: 23%;">Carrier</div>
-                  <div style="display: flex;flex-direction: column;border-right: 1px solid var(--color-system-border);width: 20%;">
+                  <div class="AicoulumnTitile" style="width: 10%">priority</div>
+                  <div class="AicoulumnTitile" style="width: 14%">Rule Type</div>
+                  <div class="AicoulumnTitile" style="width: 23%">Port</div>
+                  <div class="AicoulumnTitile" style="width: 23%">Carrier</div>
+                  <div
+                    style="
+                      display: flex;
+                      flex-direction: column;
+                      border-right: 1px solid var(--color-system-border);
+                      width: 20%;
+                    "
+                  >
                     <div class="AicoulumnTitile2">Recommended Delivery Date</div>
-                    <div style="display: flex;height: 24px;align-items: center;">
-                      <div class="datetitle" style="border-right: 1px solid var(--color-system-border);">From (ETA/ATA + Days)</div>
+                    <div style="display: flex; height: 24px; align-items: center">
+                      <div
+                        class="datetitle"
+                        style="border-right: 1px solid var(--color-system-border)"
+                      >
+                        From (ETA/ATA + Days)
+                      </div>
                       <div class="datetitle">To (ETA/ATA + Days)</div>
                     </div>
                   </div>
-                  <div class="AirCoumlulnAdd" style="width: 10%;" @click="AddRuleItem(SeaContentList, 'Sea')">+ Add</div>
+                  <div
+                    class="AirCoumlulnAdd"
+                    style="width: 10%"
+                    @click="AddRuleItem(SeaContentList, 'Sea')"
+                  >
+                    + Add
+                  </div>
                 </div>
                 <div class="AirContent" v-for="(item, index) in SeaContentList" :key="index">
-                  <div class="AirCoumlumn" style="width: 10%;">{{ item.priority }}</div>
-                  <div class="AirCoumlumn" style="width: 14%;">
+                  <div class="AirCoumlumn" style="width: 10%">{{ item.priority }}</div>
+                  <div class="AirCoumlumn" style="width: 14%">
                     <el-select
                       v-model="item.rule_type"
                       disabled
-                      style="width: 100%;"
-                      @change="val => changeRuleType(val, index, SeaContentList)"
+                      style="width: 100%"
+                      @change="(val) => changeRuleType(val, index, SeaContentList)"
                     >
                       <el-option
                         v-for="opt in RuleTypeoptions"
@@ -497,45 +576,54 @@ const changeRuleType = (val: string, index: number, list: RuleItem[]) => {
                       />
                     </el-select>
                   </div>
-                  <div class="AirCoumlumn" style="width: 23%;">
+                  <div class="AirCoumlumn" style="width: 23%">
                     <SelectValue
                       ref="SeaPortRef"
                       :SelectIndex="index"
                       :SelectedValue="item.ports"
                       :typeisDisabled="item.rule_type"
                       SelectType="sea"
-                      @changeSelectedValue="val => changeSelectedValue(val, index, 'ports', SeaContentList)"
+                      @changeSelectedValue="
+                        (val) => changeSelectedValue(val, index, 'ports', SeaContentList)
+                      "
                     />
                   </div>
-                  <div class="AirCoumlumn" style="width: 23%;">
+                  <div class="AirCoumlumn" style="width: 23%">
                     <SelectValue
                       ref="SeaCarrierRef"
                       :SelectIndex="index"
                       :SelectedValue="item.carrier"
                       :typeisDisabled="item.rule_type"
                       SelectType="carrier"
-                      @changeSelectedValue="val => changeSelectedValue(val, index, 'carrier', SeaContentList)"
+                      @changeSelectedValue="
+                        (val) => changeSelectedValue(val, index, 'carrier', SeaContentList)
+                      "
                     />
                   </div>
-                  <div class="AirCoumlumn" style="width: 10%;">
-                    <el-input 
-                      @input="val => handleInput(val, index, 'recommended_delivery_from', SeaContentList)" 
-                      placeholder="Input" 
+                  <div class="AirCoumlumn" style="width: 10%">
+                    <el-input
+                      @input="
+                        (val) =>
+                          handleInput(val, index, 'recommended_delivery_from', SeaContentList)
+                      "
+                      placeholder="Input"
                       v-model="item.recommended_delivery_from"
                     />
                   </div>
-                  <div class="AirCoumlumn" style="width: 10%;">
-                    <el-input 
-                      @input="val => handleInput(val, index, 'recommended_delivery_to', SeaContentList)"  
-                      placeholder="Input" 
+                  <div class="AirCoumlumn" style="width: 10%">
+                    <el-input
+                      @input="
+                        (val) => handleInput(val, index, 'recommended_delivery_to', SeaContentList)
+                      "
+                      placeholder="Input"
                       v-model="item.recommended_delivery_to"
                     />
                   </div>
-                  <div class="AirDelete" style="width: 10%;">
-                    <el-button 
-                      v-if="item.rule_type !== '*Default Rule'" 
-                      @click="handleDelete(index, SeaContentList, 'Sea')" 
-                      class="el-button--blue" 
+                  <div class="AirDelete" style="width: 10%">
+                    <el-button
+                      v-if="item.rule_type !== '*Default Rule'"
+                      @click="handleDelete(index, SeaContentList, 'Sea')"
+                      class="el-button--blue"
                       style="height: 24px"
                     >
                       <span class="font_family icon-icon_delete_b"></span>
@@ -571,7 +659,7 @@ const changeRuleType = (val: string, index: number, list: RuleItem[]) => {
 :deep(.el-radio__input.is-checked + .el-radio__label) {
   color: var(--color-neutral-1);
 }
-:deep( .el-radio__inner) {
+:deep(.el-radio__inner) {
   border: 1px solid var(--color-system-checkbox-border);
 }
 :deep(.el-radio__inner) {
@@ -583,13 +671,17 @@ const changeRuleType = (val: string, index: number, list: RuleItem[]) => {
 }
 .oceanCheckbox {
   margin-bottom: 8px;
+  :deep(.el-checkbox__input) {
+    padding: 4px;
+  }
 }
 .delayedType {
   align-items: start;
   height: fit-content;
   margin-right: 5px;
   border-radius: 6px;
-  padding: 13px;
+  padding: 8px;
+  padding-left: 4px;
 }
 :deep(.el-checkbox) {
   width: 100%;
@@ -599,11 +691,14 @@ const changeRuleType = (val: string, index: number, list: RuleItem[]) => {
 }
 :deep(.el-checkbox__label) {
   width: 100%;
+  padding-left: 0px;
 }
 .titlecheckbox {
   width: 100%;
   display: flex;
   justify-content: space-between;
+  padding-left: 4px;
+  padding-top: 4px;
 }
 .icon_grey {
   color: #b8bbbf;
@@ -674,4 +769,4 @@ const changeRuleType = (val: string, index: number, list: RuleItem[]) => {
   align-items: center;
   justify-content: center;
 }
-</style>
+</style>

+ 77 - 50
src/views/DestinationDelivery/src/components/CreateNewBooking/src/CreateNewbooking.vue

@@ -27,13 +27,15 @@ const VesselName = ref([])
 const VesselNametest = ref('')
 const ShipperValue = ref('')
 const ConsigneeValue = ref('')
+const deliveryDate = ref('')
 const DeliveryReference = ref('')
 const getAddressListData = ref({})
 // const isFocused = ref(false)
 const isFocused = ref({
   Shipper: false,
   Consignee: false,
-  Vessel: false
+  Vessel: false,
+  deliveryDate: false
 })
 const isataFocused = ref(false)
 const isetaFocused = ref(false)
@@ -198,10 +200,6 @@ const showETAlabel = computed(() => {
   return ETATimeList.value != null || isetaFocused.value
 })
 
-const changeFocus = (val: boolean) => {
-  // isFocused.value = val
-}
-
 const changeFocustest = (type: any, val: boolean) => {
   isFocused.value[type] = val
 }
@@ -227,34 +225,34 @@ const querySearchCountry = (query: string) => {
   Countryloading.value = true
   setTimeout(() => {
     $api
-    .getAddressCountryCityData({
-      term: query,
-      term_type: 'country',
-      limit: CityCode.value != ''  ? CityCode.value : ''
-    })
-    .then((res: any) => {
-      if (res.code === 200) {
-        Countryoptions.value = res.data
-        Countryloading.value = false
-      }
-    })
+      .getAddressCountryCityData({
+        term: query,
+        term_type: 'country',
+        limit: CityCode.value != '' ? CityCode.value : ''
+      })
+      .then((res: any) => {
+        if (res.code === 200) {
+          Countryoptions.value = res.data
+          Countryloading.value = false
+        }
+      })
   }, 1000)
 }
 const querySearchCity = (query: string) => {
   cityloading.value = true
   setTimeout(() => {
     $api
-    .getAddressCountryCityData({
-      term: query,
-      term_type: 'city',
-      limit: CountryCode.value != ''  ? CountryCode.value : ''
-    })
-    .then((res: any) => {
-      if (res.code === 200) {
-        Cityoptions.value = res.data
-        cityloading.value = false
-      }
-    })
+      .getAddressCountryCityData({
+        term: query,
+        term_type: 'city',
+        limit: CountryCode.value != '' ? CountryCode.value : ''
+      })
+      .then((res: any) => {
+        if (res.code === 200) {
+          Cityoptions.value = res.data
+          cityloading.value = false
+        }
+      })
   }, 1000)
 }
 // 特殊日期样式
@@ -310,12 +308,12 @@ const AddNewAddressDelivery = () => {
 // 保存新地址
 const SaveNewAddress = () => {
   if (
-    CountryCode.value != '' &&
-    CityCode.value != '' &&
-    PostalCode.value != '' &&
-    ContactPerson.value != '' &&
-    ContactNumber.value != '' &&
-    AddressLine1.value != '' ||
+    (CountryCode.value != '' &&
+      CityCode.value != '' &&
+      PostalCode.value != '' &&
+      ContactPerson.value != '' &&
+      ContactNumber.value != '' &&
+      AddressLine1.value != '') ||
     AddressLine2.value != '' ||
     AddressLine3.value != '' ||
     AddressLine4.value != ''
@@ -464,6 +462,7 @@ const SearchShipment = () => {
     vessel: VesselNametest.value,
     consignee: ConsigneeValue.value,
     shipper: ShipperValue.value,
+    recommended_delivery_date: deliveryDate.value,
     eta_start: ETATimeList.value != null ? ETATimeList.value[0] : '',
     eta_end: ETATimeList.value != null ? ETATimeList.value[1] : '',
     ata_start: ATATimeList.value != null ? ATATimeList.value[0] : '',
@@ -616,7 +615,6 @@ const handleClickEditAddress = (val: any) => {
 // 删除地址
 const handleDeleteAddress = (val: any) => {
   const key = val.contact_type == 'Unedit' ? 'sync_key' : 'id'
-  console.log(val.contact_type)
   if (val.contact_type !== 'Add') {
     updateAddressList({ ...val, contact_type: 'Delete' })
   }
@@ -730,11 +728,10 @@ onMounted(() => {
         >
       </div>
       <div class="shipments_search" v-if="a == undefined">
-        <div class="flex">
+        <div class="left-filter-search">
           <el-input
             placeholder="Enter Booking/HBL/PO/Carrier Booking No. "
             v-model="CreateNewBOokingSearch"
-            style="width: 34%"
             class="log_input"
           >
             <template #prefix>
@@ -769,14 +766,7 @@ onMounted(() => {
             </el-input>
             <span v-if="showLabelConsignee" class="border-label">Consignee</span>
           </div>
-          <el-button
-            style="width: 108px"
-            class="el-button--dark create-button"
-            @click="SearchShipment"
-            >Search</el-button
-          >
-        </div>
-        <div class="flex" style="margin-top: 8px">
+
           <div class="input-with-label">
             <CalendarDate
               :isNeedFooter="false"
@@ -796,7 +786,26 @@ onMounted(() => {
             ></CalendarDate>
             <span v-if="showataLabel" class="border-label">ATA</span>
           </div>
+
           <div class="input-with-label" style="margin-right: 8px">
+            <!-- <AutoSelect ASPlaceholder="Input Vessel Name" :ASValue="VesselName" @changeFocus="changeFocus"></AutoSelect> -->
+            <a-date-picker
+              v-model:value="deliveryDate"
+              :showToday="false"
+              @focus="isFocused.deliveryDate = true"
+              @blur="isFocused.deliveryDate = false"
+              :format="userStore.dateFormat"
+              placeholder="Recommended Delivery Date"
+              valueFormat="MM/DD/YYYY"
+              style="width: 100%; height: 40px"
+            >
+            </a-date-picker>
+            <span v-if="isFocused.deliveryDate" class="border-label"
+              >Recommended Delivery Date</span
+            >
+          </div>
+
+          <div class="input-with-label">
             <!-- <AutoSelect ASPlaceholder="Input Vessel Name" :ASValue="VesselName" @changeFocus="changeFocus"></AutoSelect> -->
             <el-input
               placeholder="Input Vessel Name"
@@ -804,11 +813,19 @@ onMounted(() => {
               @focus="changeFocustest('Vessel', true)"
               @blur="changeFocustest('Vessel', false)"
               class="log_input"
+              style="width: 100%"
             >
             </el-input>
             <span v-if="showLabelVessel" class="border-label">Vessel Name</span>
           </div>
-          <div style="width: 108px"></div>
+        </div>
+        <div class="right-btn">
+          <el-button
+            style="width: 108px"
+            class="el-button--dark create-button"
+            @click="SearchShipment"
+            >Search</el-button
+          >
         </div>
       </div>
       <NewbookingTable
@@ -937,9 +954,14 @@ onMounted(() => {
             placeholder="Please Select Time"
           ></el-time-select>
         </div>
-        <div style="margin-left: 12px;">
+        <div style="margin-left: 12px">
           <div class="delivery_type_title">Delivery Reference</div>
-          <el-tooltip class="item" effect="dark" content="Reference to be quoted on arrival at the Warehouse/DC" placement="bottom">
+          <el-tooltip
+            class="item"
+            effect="dark"
+            content="Reference to be quoted on arrival at the Warehouse/DC"
+            placement="bottom"
+          >
             <el-input v-model="DeliveryReference" class="delivery_reference"></el-input>
           </el-tooltip>
         </div>
@@ -1431,7 +1453,14 @@ onMounted(() => {
   font-weight: 400;
 }
 .shipments_search {
-  margin: 0 0 8px 0;
+  display: flex;
+  margin-bottom: 16px;
+}
+.left-filter-search {
+  flex: 1;
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  grid-gap: 8px;
 }
 :deep(.log_input .el-input__wrapper) {
   box-shadow: 0 0 0 1px var(--color-select-border) inset;
@@ -1439,8 +1468,6 @@ onMounted(() => {
 }
 .input-with-label {
   position: relative;
-  display: inline-block;
-  width: 34%;
 }
 .border-label {
   position: absolute;

+ 187 - 45
src/views/DestinationDelivery/src/components/CreateNewBooking/src/components/NewbookingTable.vue

@@ -4,6 +4,7 @@ import { useRowClickStyle } from '@/hooks/rowClickStyle'
 import { formatTimezone } from '@/utils/tools'
 import { ref, onMounted } from 'vue'
 import { useRouter } from 'vue-router'
+import { autoWidth } from '@/utils/table'
 
 const router = useRouter()
 const { currentRoute } = router
@@ -30,7 +31,7 @@ const tableData = ref<VxeGridProps<any>>({
     backgroundColor: 'var(--color-table-header-bg)'
   },
   columnConfig: { resizable: true, useKey: true },
-  rowConfig: { isHover: true, isCurrent: true },
+  rowConfig: { isHover: true, isCurrent: true }
 })
 
 const tableRef = ref<VxeGridInstance | null>(null)
@@ -48,7 +49,7 @@ const handleColumns = (columns: any) => {
         ...curColumn,
         formatter: ({ cellValue }: any) => formatTimezone(cellValue)
       }
-    }  else if (item.type === 'link') {
+    } else if (item.type === 'link') {
       curColumn = {
         ...curColumn,
         slots: { default: 'trackingNo' }
@@ -57,10 +58,15 @@ const handleColumns = (columns: any) => {
       curColumn = {
         ...curColumn,
         formatter: ({ cellValue }: any) => {
-          const array = cellValue.split("-")
+          const array = cellValue.split('-')
           return `${formatTimezone(array[0])} - ${formatTimezone(array[1])}`
         }
       }
+    } else if (item.type === 'download') {
+      curColumn = {
+        ...curColumn,
+        slots: { default: 'download' }
+      }
     }
     return curColumn
   })
@@ -71,20 +77,23 @@ const getTableColumns = async () => {
   tableLoadingColumn.value = true
   await $api.BookingTableColumn().then((res: any) => {
     if (res.code === 200) {
-      if(a == undefined) {
+      if (a == undefined) {
         tableData.value.columns = [
           { type: 'checkbox', width: 50, fixed: 'left' },
           ...handleColumns(res.data.TrackingTableColumns)
         ]
-      }else {
-        tableData.value.columns = [
-          ...handleColumns(res.data.TrackingTableColumns)
-        ]
+      } else {
+        tableData.value.columns = [...handleColumns(res.data.TrackingTableColumns)]
       }
     }
   })
   nextTick(() => {
     tableLoadingColumn.value = false
+    tableRef.value &&
+      autoWidth(tableData.value, tableRef.value, {
+        packing_list: 190,
+        commercial_invoice: 180
+      })
   })
 }
 // 获取表格数据
@@ -96,15 +105,24 @@ const getTableData = (val: any) => {
 const searchTableData = (val: any) => {
   tableLoadingTable.value = true
   $api
-  .BookingTableSearch({
-    ...val
-  })
-  .then((res: any) => {
-    if (res.code === 200) {
-      tableLoadingTable.value = false
-      tableData.value.data = res.data.data
-    }
-  })
+    .BookingTableSearch({
+      ...val
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        tableLoadingTable.value = false
+        tableData.value.data = res.data.data
+      }
+    })
+    .finally(() => {
+      nextTick(() => {
+        tableRef.value &&
+          autoWidth(tableData.value, tableRef.value, {
+            packing_list: 190,
+            commercial_invoice: 180
+          })
+      })
+    })
 }
 
 const emits = defineEmits(['selectChangeEvent'])
@@ -116,79 +134,173 @@ let checkShipmentsdata = []
 let checkShipmentsInfo = {}
 let checkShipmentsSubmitInfo = {}
 const checkUniformArray = (arrary: Array<{ consignee_id: any; country: any }>) => {
-  if (arrary.length === 0) return false;
-  const first = arrary[0];
+  if (arrary.length === 0) return false
+  const first = arrary[0]
   for (let i = 1; i < arrary.length; i++) {
     if (arrary[i].consignee_id !== first.consignee_id) {
-        return false;
+      return false
     }
   }
-  return [first];
+  return [first]
+}
+
+// 将具有相同same_mbol的行选中或取消选中
+const selectRowsWithSameMbol = ({ row, checked }) => {
+  const key = row.same_mbol
+  if (!key) return
+  const tableRowData = tableRef.value?.getTableData().fullData || []
+  tableRowData.forEach((item, index) => {
+    if (item.same_mbol === key) {
+      tableRef.value?.setCheckboxRow(item, checked)
+    }
+  })
 }
-const selectChangeEvent = () => {
+const selectChangeEvent = (selectItem) => {
+  selectRowsWithSameMbol(selectItem)
   const $grid = tableRef.value
   if ($grid) {
     const records = $grid.getCheckboxRecords()
-    checkShipments = records.map(item => ({ consignee_id: item.consignee_id, country: item.dc_country }))
-    checkRecommend = records.map(item => ({ date_range: item.date_range.split('-'), Hbol: item.h_bol  }))
+    checkShipments = records.map((item) => ({
+      consignee_id: item.consignee_id,
+      country: item.dc_country
+    }))
+    checkRecommend = records.map((item) => ({
+      date_range: item.date_range.split('-'),
+      Hbol: item.h_bol
+    }))
     const array = checkUniformArray(checkShipments)
-    if(array != false) {
+    if (array != false) {
       checkShipmentsdata = Object.keys(checkUniformArray(checkShipments)?.[0])
       checkShipmentsSubmit = Object.keys(records?.[0])
       checkShipmentsdata.forEach((item) => {
         Object.assign(checkShipmentsInfo, {
-          [item]: array.map((row) => row[item] )
+          [item]: array.map((row) => row[item])
         })
       })
       checkShipmentsSubmit.forEach((item) => {
         Object.assign(checkShipmentsSubmitInfo, {
           [item]: records.map((row) => {
-            if(row[item] == null){
+            if (row[item] == null) {
               return ''
             } else {
               return row[item]
             }
-          } )
+          })
         })
       })
     } else {
       checkShipmentsSubmitInfo = {}
       checkShipmentsInfo = {}
     }
-    emits('selectChangeEvent',checkShipmentsInfo, checkRecommend,checkShipmentsSubmitInfo)
+    emits('selectChangeEvent', checkShipmentsInfo, checkRecommend, checkShipmentsSubmitInfo)
   }
 }
 // 全选
-const selectAllChangeEvent= () => {
+const selectAllChangeEvent = () => {
   const $grid = tableRef.value
   if ($grid) {
     const records = $grid.getCheckboxRecords()
-    checkShipments = records.map(item => ({ consignee_id: item.consignee_id }))
-    checkRecommend = records.map(item => ({ date_range: item.date_range.split('-'), Hbol: item.h_bol }))
-    if(checkShipments.length != 0) {
+    checkShipments = records.map((item) => ({ consignee_id: item.consignee_id }))
+    checkRecommend = records.map((item) => ({
+      date_range: item.date_range.split('-'),
+      Hbol: item.h_bol
+    }))
+    if (checkShipments.length != 0) {
       checkShipmentsdata = Object.keys(checkShipments?.[0])
       checkShipmentsSubmit = Object.keys(records?.[0])
       checkShipmentsdata.forEach((item) => {
         Object.assign(checkShipmentsInfo, {
-          [item]: checkShipments.map((row) => row[item] )
+          [item]: checkShipments.map((row) => row[item])
         })
       })
       checkShipmentsSubmit.forEach((item) => {
         Object.assign(checkShipmentsSubmitInfo, {
           [item]: records.map((row) => {
-            if(row[item] == null){
+            if (row[item] == null) {
               return ''
             } else {
               return row[item]
             }
-          } )
+          })
         })
       })
     } else {
       checkShipmentsSubmitInfo = {}
     }
-    emits('selectChangeEvent',checkShipmentsInfo, checkRecommend,checkShipmentsSubmitInfo)
+    emits('selectChangeEvent', checkShipmentsInfo, checkRecommend, checkShipmentsSubmitInfo)
+  }
+}
+
+function base64ToBlob(base64, mimeType) {
+  const byteString = atob(base64.split(',')[0].startsWith('data:') ? base64.split(',')[1] : base64)
+  const ab = new ArrayBuffer(byteString.length)
+  const ia = new Uint8Array(ab)
+  for (let i = 0; i < byteString.length; i++) {
+    ia[i] = byteString.charCodeAt(i)
   }
+  return new Blob([ia], { type: mimeType })
+}
+
+const handleDownload = (serialNo: string, field: string) => {
+  const fileType = field === 'commercial_invoice' ? 'C/I' : 'Packing List'
+  $api
+    .downloadBookingTableFile({
+      serial_no: serialNo,
+      file_type: fileType
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        // 使用
+        const base64 = res.data.data // 纯 base64 字符串,不含 data: 前缀
+        const mimeType = 'application/octet-stream'
+        const fileName = res.data.filename || 'download'
+
+        const blob = base64ToBlob(base64, mimeType)
+        const url = URL.createObjectURL(blob)
+
+        const link = document.createElement('a')
+        link.href = url
+        link.download = fileName
+        document.body.appendChild(link)
+        link.click()
+        document.body.removeChild(link)
+
+        // 可选:下载完成后释放内存
+        link.onclick = () => {
+          setTimeout(() => {
+            URL.revokeObjectURL(url) // 释放对象 URL
+          }, 100)
+        }
+      }
+    })
+}
+
+const CustomizeColumnsRef = ref()
+// 打开定制表格弹窗
+const handleCustomizeColumns = () => {
+  const params = {
+    getData: {
+      action: 'destination_delivery_booking',
+      operate: 'destination_delivery_shipment_display'
+    },
+    saveData: {
+      action: 'ajax',
+      operate: 'save_setting_display',
+      model_name: 'destination_delivery_shipment_search'
+    }
+  }
+  CustomizeColumnsRef.value.openDialog(
+    params,
+    -220,
+    'Drag item over to this selection or click "add" icon to show the field on delivery booking list'
+  )
+}
+// 定制表格
+const customizeColumns = async () => {
+  await getTableColumns()
+  nextTick(() => {
+    tableRef.value && autoWidth(tableData.value, tableRef.value)
+  })
 }
 
 // 实现行点击样式
@@ -201,11 +313,18 @@ defineExpose({
   getTableData,
   searchTableData
 })
-
 </script>
 
 <template>
-  <div class="SettingTable">
+  <div class="new-booking-table">
+    <el-button
+      style="width: 170px; align-self: flex-end"
+      type="default"
+      @click="handleCustomizeColumns"
+    >
+      <span style="margin-right: 6px" class="font_family icon-icon_column_b"></span>
+      Customize Columns
+    </el-button>
     <vxe-grid
       ref="tableRef"
       :style="{ border: 'none' }"
@@ -214,22 +333,29 @@ defineExpose({
       @checkbox-change="selectChangeEvent"
       @checkbox-all="selectAllChangeEvent"
     >
+      <!-- download下载的插槽 -->
+      <template #download="{ row, column }">
+        <div class="download-btn" @click="handleDownload(row.serial_no, column.field)">
+          <span class="font_family icon-icon_download_b icon-style"> </span>
+          <span
+            >{{ row.h_bol
+            }}{{ column.field === 'commercial_invoice' ? '.CI.zip' : '._PL.zip' }}</span
+          >
+        </div>
+      </template>
       <template #empty>
         <div v-if="isNotActivated" class="empty-text">
           This service isn't activated yet. Please contact our team to enable it.
         </div>
-        <div v-else class="empty-text">
-          No eligible shipments found to create a new booking.
-        </div>
+        <div v-else class="empty-text">No eligible shipments found to create a new booking.</div>
       </template>
     </vxe-grid>
+
+    <CustomizeColumns @customize="customizeColumns" ref="CustomizeColumnsRef" />
   </div>
 </template>
 
 <style lang="scss" scoped>
-.font_family::before {
-  color: var(--color-btn-danger-bg);
-}
 .icon_alert::before {
   color: var(--color-btn-warning-bg);
 }
@@ -257,4 +383,20 @@ defineExpose({
   color: var(--color-neutral-1);
   margin: 31px 0;
 }
+.download-btn {
+  cursor: pointer;
+
+  &:hover,
+  &:focus {
+    span,
+    .icon-style {
+      color: var(--color-theme) !important;
+    }
+  }
+}
+.new-booking-table {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
 </style>

+ 10 - 1
src/views/Layout/src/components/Header/HeaderView.vue

@@ -215,6 +215,11 @@ const handleCloseThemePopover = () => {
   isPopoverVisible.value = false
   document.removeEventListener('click', handleClickOutside)
 }
+
+const handleDemoVideo = () => {
+  const { href } = router.resolve({ name: 'Demo Video' })
+  window.open(href, '_blank')
+}
 </script>
 
 <template>
@@ -352,7 +357,11 @@ const handleCloseThemePopover = () => {
             <span class="font_family icon-icon_manual_b"></span>
             User Manual
           </div>
-          <div class="item" @click="handleLogout">
+          <div class="item" style="margin-left: 1px" @click="handleDemoVideo">
+            <span class="font_family icon-icon_video_b"></span>
+            Demo Video
+          </div>
+          <div class="item" style="margin-left: 2px" @click="handleLogout">
             <span class="font_family icon-icon_export_b"></span>
             Logout
           </div>

+ 29 - 1
src/views/Layout/src/components/Header/components/TrainingCard.vue

@@ -11,7 +11,18 @@ const notificationMsgStore = useNotificationMessage()
 const notificationList = ref([])
 
 const curIndex = ref(0)
+const displayedList = ref([])
+const isNewCard = (id) => {
+  if (!id) return false
+  return !displayedList.value.some((item) => item.info.id === id)
+}
 const curCard = computed(() => {
+  if (
+    notificationList.value[curIndex.value] &&
+    isNewCard(notificationList.value[curIndex.value].info.id)
+  ) {
+    displayedList.value.push(notificationList.value[curIndex.value])
+  }
   return notificationList.value[curIndex.value] || null
 })
 
@@ -35,6 +46,11 @@ const trainingCardAfterLogin = () => {
   // 如果消息展示完毕,清除定时器
   if (curIndex.value >= notificationList.value.length) {
     clearInterval(trainingIntervalId)
+    // 将已经展示的消息标记为已读
+    notificationMsgStore.markMessageAsReadAfterCarousel(
+      displayedList.value.map((item) => item.info.id)
+    )
+    displayedList.value = []
   }
 }
 
@@ -54,6 +70,12 @@ const nextNotification = () => {
   if (curIndex.value >= notificationList.value.length) {
     clearInterval(trainingIntervalId)
     result = false
+
+    // 将已经展示的消息标记为已读
+    notificationMsgStore.markMessageAsReadAfterCarousel(
+      displayedList.value.map((item) => item.info.id)
+    )
+    displayedList.value = []
   }
   return result
 }
@@ -104,6 +126,12 @@ const getNotificationList = (time?: string) => {
 
 // 关闭消息,如果是登录后自动轮播的消息,则需清除定时器,如果不是则继续轮播下一条消息
 const closeMessage = () => {
+  // 将已经展示的消息标记为已读
+  notificationMsgStore.markMessageAsReadAfterCarousel(
+    displayedList.value.map((item) => item.info.id)
+  )
+  displayedList.value = []
+
   if (!newMessageIntervalId) {
     clearInterval(trainingIntervalId)
     curIndex.value = -1
@@ -132,9 +160,9 @@ const closeMessage = () => {
       <span class="font_family icon-icon_reject_b"></span>
     </el-button>
     <NotificationMessageCard
-      :isObserver="false"
       v-if="curCard"
       :data="[curCard]"
+      :isObserver="false"
       :topOffset="0"
       :isDrawer="true"
     ></NotificationMessageCard>

+ 1 - 0
src/views/Tracking/src/components/DownloadAttachment/index.ts

@@ -0,0 +1 @@
+export { default } from './src/DownloadAttachment.vue'

+ 616 - 0
src/views/Tracking/src/components/DownloadAttachment/src/DownloadAttachment.vue

@@ -0,0 +1,616 @@
+<script setup lang="ts">
+import { useTrackingDownloadData } from '@/stores/modules/trackingDownloadData'
+import emitter from '@/utils/bus'
+import { useRouter } from 'vue-router'
+import { useUserStore } from '@/stores/modules/user'
+
+const userStore = useUserStore()
+const router = useRouter()
+const trackingDownloadData = useTrackingDownloadData()
+const attachmentData = ref([])
+const bodyLoading = ref(false)
+const pageLoading = ref(false)
+
+// const shipments = ref(attachmentData)
+const getAttachmentData = () => {
+  pageLoading.value = true
+  $api
+    .getDownloadAttachmentData({
+      serial_no_arr: trackingDownloadData.serialNoArr,
+      schemas_arr: trackingDownloadData.schemasArr
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        attachmentData.value = res.data
+      }
+    })
+    .finally(() => {
+      pageLoading.value = false
+    })
+}
+onMounted(() => {
+  getAttachmentData()
+})
+
+// === 1. 全选状态计算 ===
+const isAllSelected = computed({
+  get() {
+    return attachmentData.value.every((item) => item.isSelect || item.typeList?.length === 0)
+  },
+  set(val) {
+    attachmentData.value.forEach((item) => {
+      if (item.typeList?.length === 0) return
+      item.isSelect = val
+      // 同步子级
+      if (item.typeList) {
+        item.typeList.forEach((type) => {
+          if (type.attachmentList) {
+            type.attachmentList.forEach((att) => {
+              att.isSelect = val
+            })
+          }
+        })
+      }
+    })
+  }
+})
+
+// 父级变化时,更新子级状态
+const handleParentToggle = (ship) => {
+  const newVal = ship.isSelect
+  ship.typeList.forEach((type) => {
+    if (type.attachmentList) {
+      type.attachmentList.forEach((att) => {
+        att.isSelect = newVal
+      })
+    }
+  })
+}
+
+// 子级变化时,更新父级状态
+const handleChildToggle = (ship) => {
+  if (!ship.typeList || ship.typeList.length === 0) {
+    // 如果没有子项,直接返回当前状态或设为 false
+    ship.isSelect = false
+    return
+  }
+
+  // 判断所有 attachment 是否都选中
+  const allSelected = ship.typeList.every((type) =>
+    type.attachmentList?.every((att) => att.isSelect)
+  )
+
+  ship.isSelect = allSelected
+}
+
+// === 3. 初始化数据结构(确保每个 attachment 都有 isSelect)
+// 如果原始数据不完整,可以预处理
+const initShipments = () => {
+  attachmentData.value.forEach((item) => {
+    if (!item.isSelect) item.isSelect = false
+    if (item.typeList) {
+      item.typeList.forEach((type) => {
+        if (type.attachmentList) {
+          type.attachmentList.forEach((att) => {
+            if (!att.isSelect) att.isSelect = false
+          })
+        }
+      })
+    }
+  })
+}
+
+initShipments()
+const summaryList = ref([])
+const allChooseFiles = computed(() => {
+  return summaryList.value.reduce((acc, curr) => {
+    acc += curr.attachmentList.length
+    return acc
+  }, 0)
+})
+
+const generateSummary = () => {
+  const map = new Map() // 用 label 作为 key
+
+  attachmentData.value.forEach((item) => {
+    item?.typeList?.forEach((type) => {
+      // 遍历该类型下的所有附件
+      type?.attachmentList?.forEach((attach) => {
+        if (attach.isSelect) {
+          const label = type.label
+          if (!map.has(label)) {
+            map.set(label, {
+              label,
+              number: 0,
+              attachmentList: []
+            })
+          }
+          const group = map.get(label)
+          group.number += 1 // 每选中一个就 +1
+
+          group.attachmentList.push({ name: attach.name })
+        }
+      })
+    })
+  })
+
+  // 转为数组
+  summaryList.value = Array.from(map.values())
+}
+
+// 👇 监听 attachmentData 中所有 isSelect 的变化
+watch(
+  () => {
+    // 创建一个扁平化的路径数组,用于监听所有 isSelect
+    return attachmentData.value.map((item) =>
+      item.typeList?.map((type) => type.attachmentList?.map((att) => att.isSelect))
+    )
+  },
+  () => {
+    generateSummary()
+  },
+  { deep: true }
+)
+
+const handleFileDownload = (row: any) => {
+  // 如果from_system的值是TOPOCEAN_KSMART,不需要拼接url
+  const url = row?.url
+  // 创建一个隐藏的 <a> 标签
+  const link = document.createElement('a')
+  link.href = row?.is_topocean ? url : import.meta.env.VITE_API_HOST + '/' + url
+  link.target = '_blank'
+
+  // 指定下载文件名(可选)
+  // link.download = row?.file_name || 'file'
+
+  // 添加到 DOM 中,触发点击事件,然后移除
+  document.body.appendChild(link)
+  link.click()
+  document.body.removeChild(link)
+}
+
+const getFileNameFromContentDisposition = (contentDisposition) => {
+  const filenameStart = contentDisposition.indexOf('filename=')
+  if (filenameStart === -1) return null // 如果没有找到,直接返回
+
+  const substring = contentDisposition.slice(filenameStart + 9) // 9 是 'filename='.length
+
+  const firstQuote = substring.indexOf('"')
+
+  if (firstQuote === -1) return null // 如果没有找到开始引号,直接返回
+
+  const secondQuote = substring.indexOf('"', firstQuote + 1)
+
+  if (secondQuote === -1) return null // 如果没有找到结束引号,直接返回
+
+  return substring.slice(firstQuote + 1, secondQuote)
+}
+const handleDownloadAllSelectedFiles = (label?: string) => {
+  const selectedFiles = []
+  attachmentData.value.forEach((item) => {
+    item?.typeList?.forEach((type) => {
+      // 如果选择了 label,则只下载该类型的附件
+      if (label && type.label !== label) return
+      type?.attachmentList?.forEach((attach) => {
+        if (attach.isSelect) {
+          selectedFiles.push(attach)
+        }
+      })
+    })
+  })
+  if (selectedFiles.length === 0) {
+    ElMessage.warning('Please select at least one file to download.')
+    return
+  }
+  bodyLoading.value = true
+
+  $api
+    .downloadAttachment({
+      data: selectedFiles
+    })
+    .then((res: any) => {
+      if (res.status !== 200) {
+        ElMessageBox.alert('The request failed. Please try again later', 'Prompt', {
+          confirmButtonText: 'OK',
+          confirmButtonClass: 'el-button--dark'
+        })
+        return
+      }
+      if (res.data?.code === 403) {
+        sessionStorage.clear()
+        emitter.emit('login-out')
+        router.push('/login')
+        userStore.logout()
+        ElMessage.warning({
+          message: 'Please log in to use this feature.',
+          grouping: true
+        })
+        return
+      } else if (res.data?.code === 500) {
+        ElMessageBox.alert(res.data.msg, 'Prompt', {
+          confirmButtonText: 'OK',
+          confirmButtonClass: 'el-button--dark'
+        })
+        return
+      }
+      const fileName = getFileNameFromContentDisposition(res.headers['content-disposition'])
+      const blob = new Blob([res.data], { type: 'application/zip' })
+      const downloadUrl = window.URL.createObjectURL(blob)
+      const a = document.createElement('a')
+      a.download = fileName
+      a.href = downloadUrl
+      document.body.appendChild(a)
+      a.click()
+      window.URL.revokeObjectURL(downloadUrl)
+      document.body.removeChild(a)
+    })
+    .finally(() => {
+      bodyLoading.value = false
+    })
+}
+</script>
+
+<template>
+  <div
+    class="tracking-download-attachment"
+    v-loading.fullscreen.lock="bodyLoading"
+    element-loading-text="Loading..."
+    element-loading-custom-class="element-loading"
+    element-loading-background="rgb(43, 47, 54, 0.7)"
+    v-vloading="pageLoading"
+  >
+    <div class="left-select-section">
+      <div class="header-select-all">
+        <el-checkbox v-model="isAllSelected"><span>Select All</span></el-checkbox>
+      </div>
+      <div class="attachment-list">
+        <div class="attachment-item" v-for="attItem in attachmentData" :key="attItem.id">
+          <div class="top-number">
+            <el-checkbox
+              :disabled="!attItem?.typeList?.length"
+              @change="handleParentToggle(attItem)"
+              v-model="attItem.isSelect"
+            >
+              <span class="font_family icon-icon_ocean_b"></span>
+              <el-tooltip effect="dark" :content="`Attachment ${attItem.no}`" placement="top">
+                <span class="label ellipsis-text">Attachment {{ attItem.no }}</span>
+              </el-tooltip>
+            </el-checkbox>
+          </div>
+          <div class="attachment-content">
+            <div
+              class="attachment-type"
+              v-for="typeItem in attItem?.typeList"
+              :key="typeItem.label"
+            >
+              <div class="type-label">
+                {{ typeItem.label }} ({{ typeItem.attachmentList.length }})
+              </div>
+              <div class="type-attachment-list">
+                <div
+                  class="attachment-file"
+                  v-for="fileItem in typeItem.attachmentList"
+                  :key="fileItem.name"
+                >
+                  <el-checkbox v-model="fileItem.isSelect" @change="handleChildToggle(attItem)">
+                    <span>{{ fileItem.name }}</span></el-checkbox
+                  >
+                  <span
+                    @click="handleFileDownload(fileItem)"
+                    class="font_family icon-icon_download_b"
+                  ></span>
+                </div>
+              </div>
+            </div>
+            <div class="empty-attachment" v-if="!attItem?.typeList?.length">no file</div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="right-summary-section">
+      <div class="title">Attachment Summary</div>
+      <div class="summary-content">
+        <el-button
+          class="el-button--main el-button--pain-theme"
+          style="width: 100%; margin-bottom: 8px"
+          @click="handleDownloadAllSelectedFiles()"
+        >
+          <span class="font_family icon-icon_download_b"></span>
+          <span>Download Selected ({{ allChooseFiles }})</span>
+        </el-button>
+        <el-collapse
+          style="margin: 8px 0"
+          expand-icon-position="left"
+          v-for="(typeItem, index) in summaryList"
+          :key="index"
+        >
+          <div class="right-download">
+            <div class="count" v-if="typeItem?.attachmentList?.length">
+              <span>{{ typeItem?.attachmentList?.length }}</span>
+            </div>
+            <span
+              @click="handleDownloadAllSelectedFiles(typeItem.label)"
+              class="font_family icon-icon_download_b"
+            ></span>
+          </div>
+          <el-collapse-item :title="typeItem.label" :name="index.toString()">
+            <template #icon="{ isActive }">
+              <span
+                :class="{ 'is-active': isActive }"
+                class="font_family icon-icon_up_b custom-arrow"
+              ></span>
+            </template>
+
+            <div class="attachment-list">
+              <div
+                class="attachment-item"
+                v-for="attItem in typeItem?.attachmentList"
+                :key="attItem.name"
+              >
+                <v-ellipsis-tooltip
+                  :max-width="276"
+                  :max-height="32"
+                  :line-clamp="1"
+                  :content="attItem.name"
+                ></v-ellipsis-tooltip>
+              </div>
+            </div>
+          </el-collapse-item>
+        </el-collapse>
+        <div class="empty-file-data" v-if="!summaryList?.length">
+          <img src="./images/empty-img.png" alt="empty-data" />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.tracking-download-attachment {
+  display: flex;
+  height: 100%;
+  padding-left: 24px;
+  .left-select-section {
+    flex: 1;
+    .header-select-all {
+      :deep(.el-checkbox__inner) {
+        &::after {
+          top: 1px;
+          left: 7px;
+          height: 14px;
+          width: 6px;
+          border-width: 2.5px;
+        }
+      }
+    }
+  }
+  :deep(.el-checkbox__inner) {
+    &::after {
+      top: 1px;
+      left: 4px;
+      height: 9px;
+      width: 4px;
+      border-width: 2px;
+    }
+  }
+  .right-summary-section {
+    width: 340px;
+    height: 100%;
+    border: 1px solid var(--color-border);
+    min-height: 400px;
+    background-color: var(--color-attchment-summary-bg);
+    .empty-file-data {
+      padding-top: 68px;
+      text-align: center;
+    }
+  }
+}
+.left-select-section {
+  height: 100%;
+  overflow: auto;
+  .header-select-all {
+    margin: 16px 0;
+    span {
+      font-size: 18px;
+      font-weight: 700;
+    }
+    :deep(.el-checkbox__inner) {
+      width: 24px;
+      height: 24px;
+    }
+  }
+  & > .attachment-list {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(326px, 1fr));
+    grid-template-rows: 320px;
+    gap: 8px;
+    padding-bottom: 36px;
+    padding-right: 24px;
+    height: calc(100% - 64px);
+    overflow: auto;
+    :deep(.el-checkbox__label) {
+      display: flex;
+      align-items: center;
+      & > .label {
+        margin-top: 3px;
+      }
+    }
+  }
+}
+.left-select-section .attachment-list .attachment-item {
+  height: 320px;
+  border: 1px solid var(--color-border);
+  border-radius: 12px;
+  overflow: hidden;
+
+  .top-number {
+    display: flex;
+    align-items: center;
+    height: 48px;
+    padding: 13px 8px;
+    background-color: var(--color-dialog-header-bg);
+    :deep(.el-checkbox) {
+      width: 100%;
+      .el-checkbox__label {
+        width: calc(100% - 8px);
+      }
+    }
+
+    .font_family {
+      font-size: 24px;
+      margin-right: 8px;
+    }
+    .label {
+      font-size: 18px;
+    }
+    .ellipsis-text {
+      width: calc(100% - 50px);
+      display: block;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+    :deep(.el-checkbox__inner) {
+      width: 16px;
+      height: 16px;
+    }
+  }
+  .attachment-content {
+    padding: 13px 8px;
+    overflow: auto;
+    height: calc(100% - 48px);
+    .empty-attachment {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      height: 100%;
+      color: var(--color-neutral-2);
+    }
+  }
+  .attachment-type {
+    margin-bottom: 8px;
+    .type-label {
+      margin: 5px 0;
+      font-size: 12px;
+      color: var(--color-neutral-2);
+    }
+    .type-attachment-list {
+      display: flex;
+      flex-direction: column;
+      border-radius: 6px;
+      overflow: hidden;
+
+      .attachment-file {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        height: 40px;
+        padding: 0 8px;
+        background-color: var(--color-personal-preference-bg);
+        &:nth-child(n + 2) {
+          border-top: 1px solid var(--color-border);
+        }
+        :deep(.el-checkbox__inner) {
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          width: 16px;
+          height: 16px;
+        }
+        .icon-icon_file_pdf {
+          color: #e74c3c;
+          margin-right: 8px;
+        }
+      }
+    }
+  }
+}
+.right-summary-section {
+  .title {
+    font-size: 24px;
+    font-weight: 700;
+    padding: 16px 8px;
+    border-bottom: 1px solid var(--color-border);
+  }
+  .summary-content {
+    height: calc(100% - 64px);
+    padding: 16px 8px;
+    padding-bottom: 20px;
+    overflow: auto;
+  }
+
+  .el-collapse {
+    position: relative;
+    padding: 0 8px;
+    background-color: var(--color-mode);
+    border: 1px solid var(--color-border);
+    border-radius: 12px;
+    overflow: hidden;
+    :deep(.el-collapse-item__wrap) {
+      border: none;
+    }
+    :deep(.el-collapse-item__header) {
+      gap: 3px;
+      border: none;
+    }
+    :deep(.el-collapse-item__title) {
+      font-weight: 700;
+    }
+    .right-download {
+      position: absolute;
+      right: 14px;
+      top: 14px;
+      display: flex;
+      align-items: center;
+      gap: 16px;
+    }
+    .count {
+      display: inline-flex;
+      align-items: center;
+      justify-content: center;
+      height: 16px;
+      // padding-top: 1px;
+      padding-left: 5px;
+      padding-right: 4px;
+      min-width: 16px;
+      background-color: var(--color-theme);
+      border-radius: 9px;
+      font-size: 10px;
+      font-weight: 700;
+      line-height: 18px;
+      text-align: center;
+      span {
+        height: 17px;
+        color: var(--color-white);
+        font-weight: 700;
+      }
+    }
+    .custom-arrow {
+      transform: rotate(90deg);
+      transition: transform 0.3s ease;
+      transform: rotate(90deg);
+    }
+
+    .custom-arrow.is-active {
+      transform: rotate(180deg);
+    }
+  }
+  .attachment-list {
+    margin-bottom: 8px;
+    border-radius: 8px;
+    overflow: hidden;
+    .attachment-item {
+      height: 32px;
+      padding: 7px 8px 0;
+      border-bottom: 1px solid var(--color-border);
+      background-color: var(--color-personal-preference-bg);
+      &:last-child {
+        border-bottom: none;
+      }
+      span {
+        color: var(--color-neutral-2);
+      }
+    }
+  }
+}
+</style>

BIN
src/views/Tracking/src/components/DownloadAttachment/src/images/empty-img.png


+ 1 - 1
src/views/Tracking/src/components/TrackingDetail/src/components/AttachmentView.vue

@@ -97,7 +97,7 @@ const handleDownload = (row: any) => {
   link.click()
   document.body.removeChild(link)
 }
-const handleDelete = (row: any) => {}
+
 const uploadFilesRef = ref<InstanceType<typeof UploadFilesDialog> | null>(null)
 
 const openUploadFilesDialog = () => {

+ 4 - 4
src/views/Tracking/src/components/TrackingGuide.vue

@@ -234,14 +234,14 @@ defineExpose({
 
 <style lang="scss" scoped>
 .download-file-guide-class {
-  right: 187px;
+  right: 183px;
   top: 246px;
-  width: 431px;
+  width: 692px;
   height: 304px;
   transform: translate(0.7px, -0.3px);
   &.download-file-guide-dark-class {
-    right: 187px;
-    width: 431px;
+    right: 183px;
+    width: 695px;
     height: 304px;
   }
 }

+ 123 - 10
src/views/Tracking/src/components/TrackingTable/src/TrackingTable.vue

@@ -10,9 +10,11 @@ import { useLoadingState } from '@/stores/modules/loadingState'
 import { useThemeStore } from '@/stores/modules/theme'
 import { useVisitedRowState } from '@/stores/modules/visitedRow'
 import { formatTimezone, formatNumber } from '@/utils/tools'
+import { useTrackingDownloadData } from '@/stores/modules/trackingDownloadData'
 
 const visitedRowState = useVisitedRowState()
 const themeStore = useThemeStore()
+const trackingDownloadData = useTrackingDownloadData()
 
 const router = useRouter()
 const props = defineProps({
@@ -375,6 +377,7 @@ onMounted(() => {
   tableRef.value && autoWidth(trackingTable.value, tableRef.value)
 })
 
+const upIcon = ref(false)
 const downloadDialogRef = ref()
 const handleDownload = () => {
   const curSelectedColumns: string[] = []
@@ -389,6 +392,30 @@ const handleDownload = () => {
     selectedNumber.value || pageInfo.value.total
   )
 }
+const handleDownloadAttachments = () => {
+  const serial_no_arr: string[] = []
+  const schemas_arr: string[] = []
+  // 将选中的记录的 serial_no 和 _schemas 收集到数组中
+  const selectedRecords = tableRef.value.getCheckboxRecords()
+
+  if (selectedRecords.length === 0) {
+    ElMessageBox.alert('Please select at least one record to download attachments', {
+      confirmButtonText: 'OK',
+      confirmButtonClass: 'el-button--dark',
+      customClass: 'tracking-table-download-alert-popup'
+    })
+    return
+  }
+  selectedRecords.forEach((item: any) => {
+    serial_no_arr.push(item.serial_no)
+    schemas_arr.push(item._schemas)
+  })
+  trackingDownloadData.setData(serial_no_arr, schemas_arr)
+
+  router.push({
+    name: 'Tracking Download Attachment'
+  })
+}
 
 const exportLoading = ref(false)
 // 获取导出表格数据
@@ -603,17 +630,44 @@ defineExpose({
     <div class="table-tools">
       <div class="left-total-records">{{ selectedNumber }} Selected</div>
       <div class="right-tools-btn">
-        <el-button
-          class="el-button--main el-button--pain-theme"
-          @click="handleDownload"
-          :style="{
-            paddingRight: themeStore.theme === 'dark' ? '13px' : '16px',
-            paddingLeft: themeStore.theme === 'dark' ? '13px' : '11px'
-          }"
+        <el-popover
+          trigger="click"
+          top="15vh"
+          class="box-item"
+          :width="226"
+          placement="bottom-start"
         >
-          <span style="margin-right: 7px" class="font_family icon-icon_download_b"></span>
-          Download
-        </el-button>
+          <template #reference>
+            <el-button
+              class="el-button--main el-button--pain-theme download-btn"
+              @click="upIcon = !upIcon"
+              :style="{
+                paddingRight: themeStore.theme === 'dark' ? '13px' : '16px',
+                paddingLeft: themeStore.theme === 'dark' ? '13px' : '11px'
+              }"
+            >
+              <span style="margin-right: 7px" class="font_family icon-icon_download_b"></span>
+              Download
+              <span
+                class="font_family icon-icon_up_b download-up-icon"
+                :class="{ 'rotate-icon': upIcon }"
+              ></span>
+            </el-button>
+          </template>
+          <template #default>
+            <div style="width: 226px; padding: 8px">
+              <div class="download-option-item" @click="handleDownload">
+                <span class="font_family icon-icon_download_b"></span>
+                <span>Download Shipment Details</span>
+              </div>
+              <div class="download-option-item" @click="handleDownloadAttachments">
+                <span class="font_family icon-icon_download__template_b"></span>
+                <span>Download Attachments</span>
+              </div>
+            </div>
+          </template>
+        </el-popover>
+
         <el-button style="padding-left: 10px" type="default" @click="handleCustomizeColumns">
           <span style="margin-right: 6px" class="font_family icon-icon_column_b"></span>
           Customize Columns
@@ -720,6 +774,29 @@ defineExpose({
 </template>
 
 <style lang="scss" scoped>
+.download-option-item {
+  display: flex;
+  align-items: center;
+  padding: 6px 8px;
+  border-radius: 4px;
+  cursor: pointer;
+  &:hover {
+    background-color: var(--color-btn-action-bg-hover);
+    span {
+      color: var(--color-theme);
+    }
+  }
+  span {
+    &:first-child {
+      font-size: 16px;
+      margin-right: 4px;
+      line-height: 18px;
+    }
+    line-height: 22px;
+    color: var(--color-text-secondary);
+  }
+}
+
 .table-tools {
   position: relative;
   display: flex;
@@ -732,6 +809,23 @@ defineExpose({
     font-weight: 700;
     line-height: 32px;
   }
+  .right-tools-btn {
+    // .download-btn {
+    //   &:hover {
+    //     .download-up-icon {
+    //       transform: rotate(360deg);
+    //     }
+    //   }
+    // }
+    .download-up-icon {
+      margin-left: 2px;
+      transition: all 0.5s ease;
+      transform: rotate(180deg);
+      &.rotate-icon {
+        transform: rotate(0deg);
+      }
+    }
+  }
 }
 
 .bottom-pagination {
@@ -786,3 +880,22 @@ defineExpose({
   }
 }
 </style>
+<style lang="scss">
+.tracking-table-download-alert-popup {
+  width: 400px;
+
+  .el-message-box__header {
+    display: none;
+  }
+  .el-message-box__content {
+    padding-top: 9px;
+  }
+  div.el-message-box__btns {
+    border: none;
+    .el-button--dark {
+      width: 100px;
+      height: 40px;
+    }
+  }
+}
+</style>

BIN
src/views/Tracking/src/image/dark-download-guide.png


BIN
src/views/Tracking/src/image/download-guide.png


+ 1 - 0
src/views/Video/index.ts

@@ -0,0 +1 @@
+export { default } from './src/VideoView.vue'

+ 21 - 0
src/views/Video/src/VideoView.vue

@@ -0,0 +1,21 @@
+<script setup lang="ts"></script>
+
+<template>
+  <div class="video-view">
+    <video controls style="width: calc(100% - 20px); height: 100%">
+      <source src="/videos/demo-video.mp4" type="video/mp4" />
+    </video>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.video-view {
+  width: 100%;
+  height: 100%;
+  padding: 25px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background-color: #000;
+}
+</style>

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.