Browse Source

Merge branch 'master_zyh' of United_Software/k_online_ui into master

Jack Zhou 4 days ago
parent
commit
5418d50e4c
53 changed files with 2740 additions and 1029 deletions
  1. 2 0
      .gitignore
  2. 1 0
      package.json
  3. BIN
      public/videos/demo-video.mp4
  4. 5 1
      src/App.vue
  5. 15 0
      src/api/module/Delivery.ts
  6. 37 0
      src/api/module/tracking.ts
  7. 2 6
      src/components/AddRules/src/components/NotiMethods.vue
  8. 2 6
      src/components/CreateAddRules/src/components/NotiMethods.vue
  9. 9 10
      src/components/CustomizeColumns/src/CustomizeColumns.vue
  10. 157 208
      src/components/DateRange/src/DateRange.vue
  11. 8 20
      src/components/DateRange/src/components/CalendarDate.vue
  12. 33 18
      src/components/TransportMode/src/TransportMode.vue
  13. 1 0
      src/components/VEllipsisTooltip/index.ts
  14. 212 0
      src/components/VEllipsisTooltip/src/VEllipsisTooltip.vue
  15. 4 1
      src/main.ts
  16. 13 2
      src/router/index.ts
  17. 5 3
      src/stores/modules/breadCrumb.ts
  18. 20 0
      src/stores/modules/trackingDownloadData.ts
  19. 168 4
      src/styles/icons/iconfont.css
  20. 0 0
      src/styles/icons/iconfont.js
  21. 82 0
      src/styles/icons/iconfont.svg
  22. BIN
      src/styles/icons/iconfont.ttf
  23. BIN
      src/styles/icons/iconfont.woff
  24. BIN
      src/styles/icons/iconfont.woff2
  25. 4 0
      src/styles/theme.scss
  26. 5 1
      src/utils/table.ts
  27. 65 28
      src/views/Booking/src/BookingView.vue
  28. 6 2
      src/views/Booking/src/components/BookingTable/src/BookingTable.vue
  29. 28 20
      src/views/Dashboard/src/DashboardView.vue
  30. 49 15
      src/views/Dashboard/src/components/BarChart.vue
  31. 74 39
      src/views/Dashboard/src/components/PieChart.vue
  32. 0 1
      src/views/Dashboard/src/components/RecentStatus.vue
  33. 35 99
      src/views/Dashboard/src/components/RevenueChart.vue
  34. 32 7
      src/views/Dashboard/src/components/SellerChart.vue
  35. 1 1
      src/views/Dashboard/src/components/TopMap.vue
  36. 221 190
      src/views/DestinationDelivery/src/components/ConfiguRations/src/components/CreateNewRule.vue
  37. 245 150
      src/views/DestinationDelivery/src/components/ConfiguRations/src/components/RecommendDate.vue
  38. 77 50
      src/views/DestinationDelivery/src/components/CreateNewBooking/src/CreateNewbooking.vue
  39. 187 45
      src/views/DestinationDelivery/src/components/CreateNewBooking/src/components/NewbookingTable.vue
  40. 10 1
      src/views/Layout/src/components/Header/HeaderView.vue
  41. 2 0
      src/views/Layout/src/components/Menu/MenuView.vue
  42. 150 82
      src/views/Tracking/src/TrackingView.vue
  43. 1 0
      src/views/Tracking/src/components/DownloadAttachment/index.ts
  44. 616 0
      src/views/Tracking/src/components/DownloadAttachment/src/DownloadAttachment.vue
  45. BIN
      src/views/Tracking/src/components/DownloadAttachment/src/images/empty-img.png
  46. 0 1
      src/views/Tracking/src/components/TrackingDetail/src/TrackingDetail.vue
  47. 1 1
      src/views/Tracking/src/components/TrackingDetail/src/components/AttachmentView.vue
  48. 4 4
      src/views/Tracking/src/components/TrackingGuide.vue
  49. 129 13
      src/views/Tracking/src/components/TrackingTable/src/TrackingTable.vue
  50. BIN
      src/views/Tracking/src/image/dark-download-guide.png
  51. BIN
      src/views/Tracking/src/image/download-guide.png
  52. 1 0
      src/views/Video/index.ts
  53. 21 0
      src/views/Video/src/VideoView.vue

+ 2 - 0
.gitignore

@@ -17,6 +17,8 @@ components.d.ts
 package-lock.json
 pnpm-lock.yaml
 auto-imports.d.ts
+*.rar
+*.zip
 
 stats.html
 

+ 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
+  )
 }

+ 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'
+      }
+    }
+  )
+}

+ 2 - 6
src/components/AddRules/src/components/NotiMethods.vue

@@ -77,11 +77,7 @@ defineExpose({
   <div style="margin-top: 11px">
     <div class="Method">
       <el-checkbox-group v-model="checkMethodList" @change="changeMethod">
-        <el-checkbox
-          class="methodcheckbox"
-          value="By Email"
-          v-if="user_type != null && user_type != 'customer'"
-        >
+        <el-checkbox class="methodcheckbox" value="By Email">
           <div>By Email</div>
           <div class="methos_image"><img src="../images/illustration_email@2x.png" /></div>
         </el-checkbox>
@@ -109,4 +105,4 @@ defineExpose({
 .methos_image {
   margin: 9px 0;
 }
-</style>
+</style>

+ 2 - 6
src/components/CreateAddRules/src/components/NotiMethods.vue

@@ -65,11 +65,7 @@ const user_type = localStorage.getItem('user_type')
   <div style="margin-top: 11px">
     <div class="Method">
       <el-checkbox-group v-model="checkMethodList" @change="changeMethod">
-        <el-checkbox
-          class="methodcheckbox"
-          value="By Email"
-          v-if="user_type != null && user_type != 'customer'"
-        >
+        <el-checkbox class="methodcheckbox" value="By Email">
           <div>By Email</div>
           <div class="methos_image"><img src="../images/illustration_email@2x.png" /></div>
         </el-checkbox>
@@ -97,4 +93,4 @@ const user_type = localStorage.getItem('user_type')
 .methos_image {
   margin: 9px 0;
 }
-</style>
+</style>

+ 9 - 10
src/components/CustomizeColumns/src/CustomizeColumns.vue

@@ -166,10 +166,12 @@ const getData = async (reset?: string) => {
 }
 
 const params = ref()
+const tipsString = ref('')
 // rightDistance是右侧箭头消失所需要translateX的值
-const openDialog = async (paramsData: Object, rightDistance: number) => {
+const openDialog = async (paramsData: Object, rightDistance: number, tips: string) => {
   firstLoad.value = localStorage.getItem('firstLoadCustomizeColumns')
   params.value = paramsData
+  tipsString.value = tips
   dialogVisible.value = true
   await getData()
   rightArrowHideDistance.value = rightDistance
@@ -233,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)
   }
 }
@@ -272,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)
@@ -361,10 +363,7 @@ defineExpose({
       </div>
       <div class="tips">
         <span style="font-size: 16px">* </span>
-        <span
-          >Drag item over to this selection or click "add" icon to show the column on your
-          {{ route.path.includes('booking') ? 'booking' : 'shipment' }} list</span
-        >
+        <span>{{ tipsString }}</span>
       </div>
     </div>
     <div class="draggable-list">

+ 157 - 208
src/components/DateRange/src/DateRange.vue

@@ -11,16 +11,18 @@ const formatDate = userStore.dateFormat
 const valueFormatDate = 'MM/DD/YYYY'
 
 onMounted(() => {
-  defaultDate()
+  const emitPayload = defaultDate()
+  // 统一 emit(只调用一次)
+  emit('defaultDate', daterangeObj2, emitPayload)
   emitter.on('clearTag', (tag: any) => {
     if (tag.includes('ETD')) {
-      clearDateStart()
+      clearETdDateRange()
     }
     if (tag.includes('ETA')) {
-      clearDateEnd()
+      clearEtaDateRange()
     }
     if (tag.includes('Creation Time')) {
-      clearDateEnd()
+      clearEtaDateRange()
     }
   })
   emitter.on('clearDaterangeObj', () => {
@@ -45,8 +47,8 @@ const AddDateType = ref()
 AddDateType.value = []
 const AddType = () => {
   AddDateType.value.push({
-    value: '',
-    number: ''
+    label: '',
+    value: ''
   })
 }
 const deleteType = (i: any) => {
@@ -57,204 +59,155 @@ const deleteType = (i: any) => {
 const props = defineProps({
   isShipment: Boolean
 })
-let daterangeObj: any = {}
-const DateStart = ref()
-DateStart.value = []
-const DateEnd = ref()
-DateEnd.value = []
+const etdDateRange = ref()
+etdDateRange.value = []
+const etaDateRange = ref()
+etaDateRange.value = []
 const DateCreation = ref()
-DateCreation.value = []
-const searchTableQeury = ref()
-const searchTableQeuryTracking = ref()
 // 查询默认日期
 const defaultDate = () => {
-  if (props.isShipment) {
-    if (
-      sessionStorage.getItem('clickParams') == null ||
-      sessionStorage.getItem('clickParams') == '{}'
-    ) {
-      if (sessionStorage.getItem('searchTableQeuryTracking') == null) {
-        DateStart.value = [dayjs().subtract(2, 'month').startOf('month'), dayjs().add(1, 'month')]
-        daterangeObj.ETD =
-          DateStart.value[0].format(formatDate) + ' To ' + DateStart.value[1].format(formatDate)
-        const obj = {
-          title: 'ETD',
-          data: [
-            DateStart.value[0].format(valueFormatDate),
-            DateStart.value[1].format(valueFormatDate)
-          ]
-        }
-
-        daterangeObj2.ETD = obj
-      } else {
-        searchTableQeuryTracking.value =
-          JSON.parse(sessionStorage.getItem('searchTableQeuryTracking') as string) || {}
-        if (searchTableQeuryTracking.value.etd_start) {
-          DateStart.value = [
-            dayjs(searchTableQeuryTracking.value.etd_start),
-            dayjs(searchTableQeuryTracking.value.etd_end)
-          ]
-          daterangeObj.ETD =
-            DateStart.value[0].format(formatDate) + ' To ' + DateStart.value[1].format(formatDate)
-          const obj = {
-            title: 'ETD',
-            data: [
-              DateStart.value[0].format(valueFormatDate),
-              DateStart.value[1].format(valueFormatDate)
-            ]
-          }
-          daterangeObj2.ETD = obj
-        }
-        if (searchTableQeuryTracking.value.eta_start) {
-          DateEnd.value = [
-            dayjs(searchTableQeuryTracking.value.eta_start),
-            dayjs(searchTableQeuryTracking.value.eta_end)
-          ]
-          daterangeObj.ETA =
-            DateEnd.value[0].format(formatDate) + ' To ' + DateEnd.value[1].format(formatDate)
-          const obj = {
-            title: 'ETA',
-            data: [
-              DateEnd.value[0].format(valueFormatDate),
-              DateEnd.value[1].format(valueFormatDate)
-            ]
-          }
-          daterangeObj2.ETA = obj
-        }
-        if (searchTableQeuryTracking.value.created_time_start) {
-          DateCreation.value = [
-            dayjs(searchTableQeuryTracking.value.created_time_start),
-            dayjs(searchTableQeuryTracking.value.created_time_end)
-          ]
-          daterangeObj['Creation Time'] =
-            DateCreation.value[0].format(formatDate) +
-            ' To ' +
-            DateCreation.value[1].format(formatDate)
-          const obj = {
-            title: 'Creation Time',
-            data: [
-              DateCreation.value[0].format(valueFormatDate),
-              DateCreation.value[1].format(valueFormatDate)
-            ]
-          }
-          daterangeObj2['Creation Time'] = obj
-        }
-      }
-      emit('defaultDate', daterangeObj, daterangeObj2, searchTableQeuryTracking.value)
-    } else {
-      const data = JSON.parse(sessionStorage.getItem('reportList') as string) || {}
-      searchTableQeuryTracking.value =
-        JSON.parse(sessionStorage.getItem('searchTableQeuryTracking') as string) || {}
-      if (data.eta_start) {
-        DateEnd.value = [dayjs(data.eta_start), dayjs(data.eta_end)]
-        daterangeObj.ETA =
-          DateEnd.value[0].format(formatDate) + ' To ' + DateEnd.value[1].format(formatDate)
-        const obj = {
-          title: 'ETA',
-          data: [DateEnd.value[0].format(valueFormatDate), DateEnd.value[1].format(valueFormatDate)]
-        }
+  // 工具函数:安全格式化日期范围
+  const formatDateRange = (startStr: string | null, endStr: string | null) => {
+    if (!startStr || !endStr) return [null, null]
+    const start = dayjs(startStr)
+    const end = dayjs(endStr)
+    return start.isValid() && end.isValid()
+      ? [start.format(formatDate), end.format(formatDate)]
+      : [null, null]
+  }
 
-        daterangeObj2.ETA = obj
-        emit('defaultDate', daterangeObj, daterangeObj2, searchTableQeuryTracking.value)
-      }
-      if (data.etd_start) {
-        DateStart.value = [dayjs(data.etd_start), dayjs(data.etd_end)]
-        daterangeObj.ETD =
-          DateStart.value[0].format(formatDate) + ' To ' + DateStart.value[1].format(formatDate)
-        const obj = {
-          title: 'ETD',
-          data: [
-            DateStart.value[0].format(valueFormatDate),
-            DateStart.value[1].format(valueFormatDate)
-          ]
-        }
-        daterangeObj2.ETD = obj
-        emit('defaultDate', daterangeObj, daterangeObj2, searchTableQeuryTracking.value)
-      }
+  // 工具函数:设置 daterangeObj2 条目
+  const setRange = (key: string, title: string, startStr: string | null, endStr: string | null) => {
+    const [start, end] = formatDateRange(startStr, endStr)
+    if (start && end) {
+      daterangeObj2[key] = { title, data: [start, end] }
     }
-  } else {
-    if (sessionStorage.getItem('searchTableQeury') == null) {
-      DateStart.value = [dayjs().subtract(2, 'month').startOf('month'), dayjs().add(1, 'month')]
-      daterangeObj.ETD =
-        DateStart.value[0].format(formatDate) + ' To ' + DateStart.value[1].format(formatDate)
-      const obj = {
-        title: 'ETD',
-        data: [
-          DateStart.value[0].format(valueFormatDate),
-          DateStart.value[1].format(valueFormatDate)
-        ]
-      }
-      daterangeObj2.ETD = obj
+  }
+
+  // 工具函数:设置 AddDateType(仅用于 Creation Time)
+  const setAddDateType = (startStr: string | null, endStr: string | null) => {
+    const [start, end] = formatDateRange(startStr, endStr)
+    AddDateType.value = [{ label: 'Creation Time', value: [start, end] }]
+  }
+
+  // 默认时间范围(2个月前月初 到 下个月)
+  const getDefaultRange = () => [
+    dayjs().subtract(2, 'month').startOf('month').format(formatDate),
+    dayjs().add(1, 'month').format(formatDate)
+  ]
+
+  // ----------------------------
+  // 主逻辑开始
+  // ----------------------------
+
+  let trackingData: Record<string, any> = {}
+  let emitPayload: Record<string, any> = {}
+
+  if (props.isShipment) {
+    const clickParams = sessionStorage.getItem('clickParams')
+    if (clickParams && clickParams !== '{}') {
+      // 场景 A: 有 clickParams → 读取 reportList
+      const reportList = JSON.parse(sessionStorage.getItem('reportList') || '{}')
+      trackingData = JSON.parse(sessionStorage.getItem('searchTableQeuryTracking') || '{}')
+      etdDateRange.value = [
+        reportList.etd_start
+          ? dayjs(reportList.etd_start, valueFormatDate).format(formatDate)
+          : null,
+        reportList.etd_end ? dayjs(reportList.etd_end, valueFormatDate).format(formatDate) : null
+      ]
+      etaDateRange.value = [
+        reportList.eta_start
+          ? dayjs(reportList.eta_start, valueFormatDate).format(formatDate)
+          : null,
+        reportList.eta_end ? dayjs(reportList.eta_end, valueFormatDate).format(formatDate) : null
+      ]
+      setRange('ETD', 'ETD', reportList.etd_start, reportList.etd_end)
+      setRange('ETA', 'ETA', reportList.eta_start, reportList.eta_end)
     } else {
-      searchTableQeury.value =
-        JSON.parse(sessionStorage.getItem('searchTableQeury') as string) || {}
-      if (searchTableQeury.value.f_etd_start) {
-        DateStart.value = [
-          dayjs(searchTableQeury.value.f_etd_start).format(valueFormatDate),
-          dayjs(searchTableQeury.value.f_etd_end).format(valueFormatDate)
+      // 场景 B: 无 clickParams → 读取 searchTableQeuryTracking
+      const stored = sessionStorage.getItem('searchTableQeuryTracking')
+      if (!stored) {
+        // 子场景 B1: 无存储 → 用默认值
+        const [start, end] = getDefaultRange()
+        etdDateRange.value = [start, end]
+        setRange('ETD', 'ETD', start, end)
+      } else {
+        // 子场景 B2: 有存储
+        trackingData = JSON.parse(stored)
+        etdDateRange.value = [
+          trackingData.etd_start ? trackingData.etd_start : null,
+          trackingData.etd_end ? trackingData.etd_end : null
         ]
-        daterangeObj.ETD =
-          DateStart.value[0].format(formatDate) + ' To ' + DateStart.value[1].format(formatDate)
-        const obj = {
-          title: 'ETD',
-          data: [
-            DateStart.value[0].format(valueFormatDate),
-            DateStart.value[1].format(valueFormatDate)
-          ]
-        }
-        daterangeObj2.ETD = obj
-      }
-      if (searchTableQeury.value.m_eta_start) {
-        DateEnd.value = [
-          dayjs(searchTableQeury.value.m_eta_start),
-          dayjs(searchTableQeury.value.m_eta_end)
+        etaDateRange.value = [
+          trackingData.eta_start ? trackingData.eta_start : null,
+          trackingData.eta_end ? trackingData.eta_end : null
         ]
-        daterangeObj.ETA =
-          DateEnd.value[0].format(formatDate) + ' To ' + DateEnd.value[1].format(formatDate)
-        const obj = {
-          title: 'ETA',
-          data: [DateEnd.value[0].format(valueFormatDate), DateEnd.value[1].format(valueFormatDate)]
+        setRange('ETD', 'ETD', trackingData.etd_start, trackingData.etd_end)
+        setRange('ETA', 'ETA', trackingData.eta_start, trackingData.eta_end)
+        if (trackingData.created_time_start) {
+          setAddDateType(trackingData.created_time_start, trackingData.created_time_end)
+          setRange(
+            'Creation Time',
+            'Creation Time',
+            trackingData.created_time_start,
+            trackingData.created_time_end
+          )
         }
-        daterangeObj2.ETA = obj
       }
-      if (searchTableQeury.value.created_time_start) {
-        DateCreation.value = [
-          dayjs(searchTableQeury.value.created_time_start),
-          dayjs(searchTableQeury.value.created_time_end)
-        ]
-        daterangeObj['Creation Time'] =
-          DateCreation.value[0].format(formatDate) +
-          ' To ' +
-          DateCreation.value[1].format(formatDate)
-        const obj = {
-          title: 'Creation Time',
-          data: [
-            DateCreation.value[0].format(valueFormatDate),
-            DateCreation.value[1].format(valueFormatDate)
-          ]
-        }
-        daterangeObj2['Creation Time'] = obj
+    }
+    emitPayload = trackingData
+  } else {
+    // 非 shipment 场景
+    const stored = sessionStorage.getItem('searchTableQeury')
+    if (!stored) {
+      // 无存储 → 默认值
+      const [start, end] = getDefaultRange()
+      etdDateRange.value = [start, end]
+      setRange('ETD', 'ETD', start, end)
+    } else {
+      // 有存储
+      const queryData = JSON.parse(stored)
+      emitPayload = queryData
+
+      etdDateRange.value = [
+        queryData.f_etd_start ? queryData.f_etd_start : null,
+        queryData.f_etd_end ? queryData.f_etd_end : null
+      ]
+      etaDateRange.value = [
+        queryData.m_eta_start ? queryData.m_eta_start : null,
+        queryData.m_eta_end ? queryData.m_eta_end : null
+      ]
+      setRange('ETD', 'ETD', queryData.f_etd_start, queryData.f_etd_end)
+      setRange('ETA', 'ETA', queryData.m_eta_start, queryData.m_eta_end)
+      if (queryData.created_time_start) {
+        setAddDateType(queryData.created_time_start, queryData.created_time_end)
+        setRange(
+          'Creation Time',
+          'Creation Time',
+          queryData.created_time_start,
+          queryData.created_time_end
+        )
       }
     }
-    emit('defaultDate', daterangeObj, daterangeObj2, searchTableQeury.value)
   }
+  return emitPayload
 }
 const daterangedata = ref()
 daterangedata.value = []
 let daterangeObj2: any = {}
 const DateRangeChange = (val: any) => {
   if (val.data != null) {
-    const date1 = dayjs(String(val.data[0])).format(formatDate)
-    const date2 = dayjs(String(val.data[1])).format(formatDate)
-    daterangeObj[val.title] = date1 + ' To ' + date2
+    val.data = [
+      dayjs(val.data[0], valueFormatDate).format(formatDate),
+      dayjs(val.data[1], valueFormatDate).format(formatDate)
+    ]
     daterangeObj2[val.title] = val
   } else {
-    delete daterangeObj[val.title]
     delete daterangeObj2[val.title]
     if (val.title == 'ETD') {
-      DateStart.value = []
+      etdDateRange.value = []
     } else if (val.title == 'ETA') {
-      DateEnd.value = []
+      etaDateRange.value = []
     } else {
       DateCreation.value = []
       AddDateType.value = []
@@ -264,34 +217,26 @@ const DateRangeChange = (val: any) => {
 }
 const emit = defineEmits(['DateRangeSearch', 'clearDaterangeTags', 'defaultDate'])
 const DateRangeSearch = () => {
-  emit('DateRangeSearch', daterangeObj, daterangeObj2)
+  emit('DateRangeSearch', daterangeObj2)
   Date_visible.value = false
 }
-const CalendarTitle = ref('Date Range')
-watch(
-  () => DateType.value,
-  (current) => {
-    CalendarTitle.value = current
-  }
-)
+
 // 清除
-const clearrest = () => {
+const clearRest = () => {
   emit('clearDaterangeTags')
-  clearDateStart()
-  clearDateEnd()
+  clearETdDateRange()
+  clearEtaDateRange()
   clearDateCreation()
   clearDaterangeObj()
 }
 // 清除EDT
-const clearDateStart = () => {
-  DateStart.value = []
-  delete daterangeObj['ETD']
+const clearETdDateRange = () => {
+  etdDateRange.value = []
   delete daterangeObj2['ETD']
 }
 // 清除EDA
-const clearDateEnd = () => {
-  DateEnd.value = []
-  delete daterangeObj['ETA']
+const clearEtaDateRange = () => {
+  etaDateRange.value = []
   delete daterangeObj2['ETA']
 }
 // 清除Creation Time
@@ -299,19 +244,23 @@ const clearDateCreation = () => {
   DateCreation.value = []
   AddDateType.value = []
   DateType.value = ''
-  CalendarTitle.value = 'Date Range'
-  delete daterangeObj['Creation Time']
   delete daterangeObj2['Creation Time']
 }
 // 清除 daterangeObj
 const clearDaterangeObj = () => {
-  daterangeObj = {}
   daterangeObj2 = {}
 }
 </script>
 <template>
   <div class="select">
-    <el-popover trigger="click" :width="400" :visible="Date_visible" popper-class="DaterangeClass">
+    <el-popover
+      trigger="click"
+      :width="400"
+      :visible="Date_visible"
+      popper-class="DaterangeClass"
+      @before-enter="defaultDate()"
+      @hide="clearDateCreation()"
+    >
       <template #reference>
         <div class="Date_Range" @blur="Date_visible = false" @click="Date_visible = !Date_visible">
           <div class="select_title">Date Range</div>
@@ -327,18 +276,18 @@ const clearDaterangeObj = () => {
         <div class="ETD">
           <CalendarDate
             CalendarTitle="ETD"
-            :Date="DateStart"
+            :Date="etdDateRange"
             @DateRangeChange="DateRangeChange"
           ></CalendarDate>
         </div>
         <div class="ETA">
           <CalendarDate
             CalendarTitle="ETA"
-            :Date="DateEnd"
+            :Date="etaDateRange"
             @DateRangeChange="DateRangeChange"
           ></CalendarDate>
         </div>
-        <div class="AddType" v-for="(item, index) in AddDateType" :key="item">
+        <div class="AddType" v-for="(item, index) in AddDateType" :key="item.value">
           <div>
             <div class="ETD_title Date_Title">
               <div class="Date_type">Date Type</div>
@@ -351,7 +300,7 @@ const clearDaterangeObj = () => {
             <el-select
               :suffix-icon="IconDropDown"
               placeholder="Please Select Date Type"
-              v-model="DateType"
+              v-model="item.label"
             >
               <el-option
                 v-for="item in DateTypeoptions"
@@ -364,9 +313,9 @@ const clearDaterangeObj = () => {
           </div>
           <div style="margin-top: 16px">
             <CalendarDate
-              :CalendarTitle="CalendarTitle"
+              :CalendarTitle="item.label || 'Date Range'"
               CalendarWidth="352px"
-              :Date="DateCreation"
+              :Date="item.value"
               @DateRangeChange="DateRangeChange"
               :isType="true"
             ></CalendarDate>
@@ -376,7 +325,7 @@ const clearDaterangeObj = () => {
           <el-button class="el-button--noborder moretype">+ More Date Type</el-button>
         </div>
         <div class="daterange_bottom">
-          <div><el-button type="default" @click="clearrest" class="Clear">Reset</el-button></div>
+          <div><el-button type="default" @click="clearRest" class="Clear">Reset</el-button></div>
           <div>
             <el-button class="search el-button--dark" @click="DateRangeSearch">Search</el-button>
           </div>

+ 8 - 20
src/components/DateRange/src/components/CalendarDate.vue

@@ -4,6 +4,7 @@ import { ref, watch } from 'vue'
 import { useUserStore } from '@/stores/modules/user'
 
 const userStore = useUserStore()
+const formatDate = userStore.dateFormat
 const valueFormatDate = 'MM/DD/YYYY'
 // type RangeValue = [Dayjs, Dayjs]
 // const ETDDate = ref<RangeValue>()
@@ -48,8 +49,8 @@ watch(
   (current: any) => {
     if (current?.length == 2) {
       ETDDate.value = [
-        current[0] ? dayjs(current[0]).format(valueFormatDate) : '',
-        current[1] ? dayjs(current[1]).format(valueFormatDate) : ''
+        current[0] ? dayjs(current[0], formatDate).format(valueFormatDate) : '',
+        current[1] ? dayjs(current[1], formatDate).format(valueFormatDate) : ''
       ]
     } else {
       ETDDate.value = []
@@ -76,15 +77,10 @@ const daterange = (val: any) => {
 }
 const ChangeToday = (val: any) => {
   if (val == 'Earliest') {
-    // ETDDate.value = [dayjs(), dayjs()]
-    ETDDate.value[0] = dayjs()
-    const date1 = dayjs(String(ETDDate.value[0])).format(valueFormatDate)
-    DateList.value[0] = date1
+    DateList.value[0] = dayjs().format(valueFormatDate)
     daterange(DateList.value[1])
   } else {
-    ETDDate.value[1] = dayjs()
-    const date1 = dayjs(String(ETDDate.value[1])).format(valueFormatDate)
-    DateList.value[1] = date1
+    DateList.value[1] = dayjs().format(valueFormatDate)
     daterange(DateList.value[0])
   }
 }
@@ -99,15 +95,11 @@ const handleCalendarOpen = (date: any) => {
   }
 }
 const Earliest = () => {
-  ETDDate.value[0] = dayjs('Oct-05-2009')
-  const date1 = dayjs(String(ETDDate.value[0])).format(valueFormatDate)
-  DateList.value[0] = date1
+  DateList.value[0] = dayjs('Oct-05-2009').format(valueFormatDate)
   daterange(DateList.value[1])
 }
 const Latest = () => {
-  ETDDate.value[1] = dayjs()
-  const date1 = dayjs(String(ETDDate.value[1])).format(valueFormatDate)
-  DateList.value[1] = date1
+  DateList.value[1] = dayjs().format(valueFormatDate)
   daterange(DateList.value[0])
 }
 const changeRangeData = (value: any) => {
@@ -128,10 +120,6 @@ const handlePanelChange = (value: any, mode: any) => {
     isShowExtra.value = true
   }
 }
-// 判断失焦时是否两个都有值
-const isTwoDate = (date: any) => {
-  console.log(date)
-}
 </script>
 <template>
   <div>
@@ -143,7 +131,7 @@ const isTwoDate = (date: any) => {
         width: props.CalendarWidth,
         backgroundColor: props.isType ? 'var(--more-type-bg-color)' : 'var(--management-bg-color)'
       }"
-      :popupClassName="{ 'th-color': props.isShowPopupClass }"
+      :popupClassName="props.isShowPopupClass ? 'th-color' : ''"
       :open="open"
       :disabled="Disabled"
       @change="changeRangeData"

+ 33 - 18
src/components/TransportMode/src/TransportMode.vue

@@ -2,6 +2,7 @@
 import { ref, onMounted, onBeforeMount, watch, computed } from 'vue'
 import type { DropdownInstance } from 'element-plus'
 import emitter from '@/utils/bus'
+import { cloneDeep } from 'lodash'
 
 interface ListItem {
   name: string
@@ -16,22 +17,24 @@ interface Props {
 }
 const props = withDefaults(defineProps<Props>(), {})
 const TransportList = ref(props.TransportListItem)
+const updateTransportList = (data: any) => {
+  TransportList.value = cloneDeep(data)
+  TransportList.value.forEach((item: any) => {
+    if (item.checked) {
+      checkedCount.push(item.sname)
+      const map = new Map()
+      checkedCount.forEach((item) => map.set(item, true))
+      checkedCount = [...map.keys()]
+    }
+    if (checkedCount.length == TransportList.value.length) {
+      checkAll.value = true
+    }
+  })
+}
 watch(
   () => props.TransportListItem,
   (current) => {
-    console.log(current)
-    TransportList.value = current
-    TransportList.value.forEach((item: any) => {
-      if (item.checked) {
-        checkedCount.push(item.sname)
-        const map = new Map()
-        checkedCount.forEach((item) => map.set(item, true))
-        checkedCount = [...map.keys()]
-      }
-      if (checkedCount.length == TransportList.value.length) {
-        checkAll.value = true
-      }
-    })
+    updateTransportList(current)
   }
 )
 
@@ -49,7 +52,7 @@ onBeforeMount(() => {
 })
 
 const checkAll = ref(false)
-const dropdown1 = ref<DropdownInstance>()
+const dropdownVisible = ref<DropdownInstance>()
 let checkedCount: any[] = []
 const TotalAll = computed(() => {
   var total_sum = 0
@@ -120,11 +123,11 @@ const TransportSearch = (visible: any) => {
   }
   TransportData.data = changedata.value
   emit('TransportSearch', TransportData)
-  if (!dropdown1.value) return
+  if (!dropdownVisible.value) return
   if (visible) {
-    dropdown1.value.handleClose()
+    dropdownVisible.value.handleClose()
   } else {
-    dropdown1.value.handleOpen()
+    dropdownVisible.value.handleOpen()
   }
 }
 const searchTableQeury = ref()
@@ -201,10 +204,22 @@ const defaultTransport = () => {
     emit('defaultTransport', TransportData, searchTableQeury.value)
   }
 }
+
+// 每次打开时都应该重新赋值
+const handleDropdownVisibleChange = (visible: boolean) => {
+  if (visible) {
+    updateTransportList(props.TransportListItem)
+  }
+}
 </script>
 <template>
   <div class="select">
-    <el-dropdown ref="dropdown1" trigger="click" :hide-on-click="false">
+    <el-dropdown
+      ref="dropdownVisible"
+      trigger="click"
+      :hide-on-click="false"
+      @visible-change="handleDropdownVisibleChange"
+    >
       <div class="el-dropdown-link">
         <div class="select_title">Transport Mode</div>
         <span class="iconfont_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)

+ 13 - 2
src/router/index.ts

@@ -41,6 +41,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',
@@ -171,7 +178,11 @@ const router = createRouter({
             breadName: 'Modify Booking',
             activeMenu: '/destination-delivery'
           }
-        }
+        }, {
+          path: '/demo-video',
+          name: 'Demo Video',
+          component: () => import('../views/Video'),
+        },
       ]
     }
   ]
@@ -208,7 +219,7 @@ router.beforeEach(async (to, from, next) => {
   }
 
   // 未登录白名单
-  const whiteList = ['/login', '/public-tracking', '/public-tracking/detail', '/reset-password']
+  const whiteList = ['/login', '/public-tracking', '/public-tracking/detail', '/reset-password', '/demo-video']
   // 判断是否登录
   if (!whiteList.includes(to.path) && !userStore.isLogin) {
     const userStore = useUserStore()

+ 5 - 3
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',
@@ -99,7 +100,7 @@ export const useBreadCrumb = defineStore('breadCrumb', {
         ]
       } else if (toRoute.name === 'Destination Create New Rule') {
         let label = ''
-        if(toRoute.query.a != undefined) {
+        if (toRoute.query.a != undefined) {
           label = 'Modify Rule'
         } else {
           label = 'Create New Rule'
@@ -121,8 +122,9 @@ export const useBreadCrumb = defineStore('breadCrumb', {
             query: toRoute.query
           }
         ]
-      } else if (toRoute.name === 'Create New Booking') {let label = ''
-        if(toRoute.query.a != undefined) {
+      } else if (toRoute.name === 'Create New Booking') {
+        let label = ''
+        if (toRoute.query.a != undefined) {
           label = 'Modify Booking'
         } else {
           label = 'Create New Booking'

+ 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";
 }

File diff suppressed because it is too large
+ 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

@@ -354,6 +354,8 @@
   --color-ant-picker-th: #b5b9bf;
 
   --color-json-item-hover: #e6f7ff;
+  
+  --color-attchment-summary-bg: #f9fafb;
 }
 
 :root.dark {
@@ -581,5 +583,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,

+ 65 - 28
src/views/Booking/src/BookingView.vue

@@ -7,6 +7,12 @@ import DateRange from '@/components/DateRange'
 import MoreFilters from '@/components/MoreFilters'
 import { ref, reactive } from 'vue'
 import { useCalculatingHeight } from '@/hooks/calculatingHeight'
+import { useUserStore } from '@/stores/modules/user'
+import dayjs from 'dayjs'
+
+const userStore = useUserStore()
+const formatDate = userStore.dateFormat
+const valueFormatDate = 'MM/DD/YYYY'
 
 const filterRef: Ref<HTMLElement | null> = ref(null)
 
@@ -21,7 +27,7 @@ const filterData = reactive({
   daterangeData: [] as Array<string>,
   morefiltersData: [] as Array<string>
 })
-onMounted(() => {})
+
 const tagsData: any = ref([])
 const handleClose = (tag: any) => {
   emitter.emit('clearTag', tag)
@@ -111,18 +117,13 @@ const defaultTransport = (val: any, value: any) => {
   renderTagsData()
 }
 // defaultDate
-const defaultDate = (val: any, value: any, data: any) => {
-  filterData.daterangeData = []
-  if (Object.keys(val).length) {
-    for (const key in val) {
-      let str = `${key}:${val[key]}`
-      filterData.daterangeData.push(str)
-    }
-  }
+const defaultDate = (dateRangeData: any, data: any) => {
+  setFilterData(dateRangeData)
+
   if (sessionStorage.getItem('searchTableQeury') == null) {
-    for (const key in value) {
-      searchTableQeury.f_etd_start = value[key].data[0]
-      searchTableQeury.f_etd_end = value[key].data[1]
+    for (const key in dateRangeData) {
+      searchTableQeury.f_etd_start = dateRangeData[key].data[0]
+      searchTableQeury.f_etd_end = dateRangeData[key].data[1]
     }
   } else {
     searchTableQeury = data
@@ -133,16 +134,20 @@ const defaultDate = (val: any, value: any, data: any) => {
   getbookingdata()
   renderTagsData()
 }
-//DateRangeSearch
-const DateRangeSearch = (val: any, value: any) => {
+
+const setFilterData = (dateRangeData: any) => {
   filterData.daterangeData = []
-  if (Object.keys(val).length) {
-    for (const key in val) {
-      let str = `${key}:${val[key]}`
-      filterData.daterangeData.push(str)
-    }
+  for (const key in dateRangeData) {
+    const startEnd = dateRangeData[key].data[0] + ' To ' + dateRangeData[key].data[1]
+    let str = `${key}:${startEnd}`
+    filterData.daterangeData.push(str)
   }
-  if (Object.keys(value).length == 0) {
+}
+//DateRangeSearch
+const DateRangeSearch = (dateRangeData: any) => {
+  setFilterData(dateRangeData)
+
+  if (Object.keys(dateRangeData).length == 0) {
     delete searchTableQeury.f_etd_start
     delete searchTableQeury.f_etd_end
     delete searchTableQeury.m_eta_start
@@ -150,16 +155,31 @@ const DateRangeSearch = (val: any, value: any) => {
     delete searchTableQeury.created_time_start
     delete searchTableQeury.created_time_end
   }
-  for (const key in value) {
+  const fieldList = [
+    {
+      title: 'ETD',
+      keys: ['f_etd_start', 'f_etd_end']
+    },
+    { title: 'ETA', keys: ['m_eta_start', 'm_eta_end'] },
+    { title: 'Creation Time', keys: ['created_time_start', 'created_time_end'] }
+  ]
+  fieldList.forEach((item) => {
+    if (!dateRangeData.hasOwnProperty(item.title)) {
+      // 删除不存在的字段
+      searchTableQeury[item.keys[0]] = undefined
+      searchTableQeury[item.keys[1]] = undefined
+    }
+  })
+  for (const key in dateRangeData) {
     if (key == 'ETD') {
-      searchTableQeury.f_etd_start = value[key].data[0]
-      searchTableQeury.f_etd_end = value[key].data[1]
+      searchTableQeury.f_etd_start = dateRangeData[key].data[0]
+      searchTableQeury.f_etd_end = dateRangeData[key].data[1]
     } else if (key == 'ETA') {
-      searchTableQeury.m_eta_start = value[key].data[0]
-      searchTableQeury.m_eta_end = value[key].data[1]
+      searchTableQeury.m_eta_start = dateRangeData[key].data[0]
+      searchTableQeury.m_eta_end = dateRangeData[key].data[1]
     } else {
-      searchTableQeury.created_time_start = value[key].data[0]
-      searchTableQeury.created_time_end = value[key].data[1]
+      searchTableQeury.created_time_start = dateRangeData[key].data[0]
+      searchTableQeury.created_time_end = dateRangeData[key].data[1]
     }
   }
   sessionStorage.setItem('searchTableQeury', JSON.stringify(searchTableQeury))
@@ -329,7 +349,23 @@ interface ListItem {
 const TagsList = ref<ListItem[]>([])
 const filterTag = ref(['All'])
 const isShowAlertIcon = ref(false)
+const setSearchQeury = () => {}
 const getbookingdata = () => {
+  const dateRangeKeys = [
+    'f_etd_start',
+    'f_etd_end',
+    'm_eta_start',
+    'm_eta_end',
+    'created_time_start',
+    'created_time_end'
+  ]
+  const curRangeData: any = {}
+  for (const key of dateRangeKeys) {
+    if (searchTableQeury[key] !== undefined) {
+      curRangeData[key] = dayjs(searchTableQeury[key], formatDate).format(valueFormatDate)
+    }
+  }
+
   tableLoadingTableData.value = true
   BookingTable_ref.value.getLoadingData(tableLoadingTableData.value)
   $api
@@ -338,7 +374,8 @@ const getbookingdata = () => {
       ps: BookingTable_ref.value.pageInfo.pageSize,
       rc: -1,
       other_filed: '',
-      ...searchTableQeury
+      ...searchTableQeury,
+      ...curRangeData
     })
     .then((res: any) => {
       if (res.code === 200) {

+ 6 - 2
src/views/Booking/src/components/BookingTable/src/BookingTable.vue

@@ -397,7 +397,11 @@ const handleCustomizeColumns = () => {
       model_name: 'Booking_Search'
     }
   }
-  CustomizeColumnsRef.value.openDialog(params, -220)
+  CustomizeColumnsRef.value.openDialog(
+    params,
+    -220,
+    'Drag item over to this selection or click "add" icon to show the column on your booking list'
+  )
 }
 // 定制表格
 const customizeColumns = async () => {
@@ -422,13 +426,13 @@ const handleLinkClick = (row: any, column: any) => {
       path: '/booking/detail',
       query: { a: row.__serial_no, _schemas: row._schemas, status: row.Status }
     })
-    visitedRowState.setBookingTableData(row['__serial_no'])
   } else if (column.title === 'HBOL/HAWB No.') {
     router.push({
       path: '/tracking/detail',
       query: { a: row.__serial_no, _schemas: row._schemas }
     })
   }
+  visitedRowState.setBookingTableData(row['__serial_no'])
 }
 
 const selectedNumber = ref(0)

+ 28 - 20
src/views/Dashboard/src/DashboardView.vue

@@ -141,7 +141,7 @@ const GetKpiData = (val: any) => {
     })
     .then((res: any) => {
       if (res.code === 200) {
-        KPIobj.ETD_Title = `{a|${res.data.title1}}{b|${res.data.title2}}`
+        KPIobj.ETD_Title = `{a|${res.data.title1}} {b|${res.data.title2}}`
         KPIobj.ETDList = res.data.ETDList
         KPIobj.ETD_Radius = res.data.ETD_Radius
         KPIobj.download_name = res.data.download_name
@@ -158,7 +158,7 @@ const GetKpiData = (val: any) => {
     })
     .then((res: any) => {
       if (res.code === 200) {
-        Arrivalobj.ETD_Title = `{a|${res.data.title1}}{b|${res.data.title2}}`
+        Arrivalobj.ETD_Title = `{a|${res.data.title1}} {b|${res.data.title2}}`
         Arrivalobj.ETDList = res.data.ETDList
         Arrivalobj.ETD_Radius = res.data.ETD_Radius
         Arrivalobj.download_name = res.data.download_name
@@ -193,7 +193,7 @@ const GetPendingEcharts = (val: any) => {
     })
     .then((res: any) => {
       if (res.code === 200) {
-        Pendingobj.ETD_Title = `{a|${res.data.title1}}{b|${res.data.title2}}`
+        Pendingobj.ETD_Title = `{a|${res.data.title1}} {b|${res.data.title2}}`
         Pendingobj.ETDList = res.data.ETDList
         Pendingobj.ETD_Radius = res.data.ETD_Radius
         Pendingobj.download_name = res.data.download_name
@@ -210,7 +210,7 @@ const GetPendingEcharts = (val: any) => {
     })
     .then((res: any) => {
       if (res.code === 200) {
-        PendingArrivalobj.ETD_Title = `{a|${res.data.title1}}{b|${res.data.title2}}`
+        PendingArrivalobj.ETD_Title = `{a|${res.data.title1}} {b|${res.data.title2}}`
         PendingArrivalobj.ETDList = res.data.ETDList
         PendingArrivalobj.ETD_Radius = res.data.ETD_Radius
         PendingArrivalobj.download_name = res.data.download_name
@@ -582,11 +582,7 @@ const ClickParams = (val: any) => {
   }
   //  PendingArrival点击跳转
   else if (val == 'Pending1') {
-    handleTurnData(
-      dashboardObj.PendingDefaultData.date_start,
-      dashboardObj.PendingDefaultData.date_end,
-      ''
-    )
+    handleTurnData('', '', '') // 因为Pending没有时间筛选,所以传空
     reportList._reportRef = pie_chart_pending_arrival.value[0].paramsdata.name
     reportList._reportType = 'r3'
     handlereportlist(
@@ -606,11 +602,7 @@ const ClickParams = (val: any) => {
   }
   // PendingDeparture点击跳转
   else if (val == 'Pending0') {
-    handleTurnData(
-      dashboardObj.PendingDefaultData.date_start,
-      dashboardObj.PendingDefaultData.date_end,
-      ''
-    )
+    handleTurnData('', '', '') // 因为Pending没有时间筛选,所以传空
     reportList._reportType = 'r4'
     reportList._reportRef = pie_chart_pending_departure.value[0].paramsdata.name
     handlereportlist(
@@ -659,7 +651,7 @@ const ClickParams = (val: any) => {
       dashboardObj.KPIDefaulteData.date_end,
       ''
     )
-    reportList._reportRef = pie_chart_kpi_arrival.value[0].paramsdata.name
+    reportList._reportRef = pie_chart_kpi_arrival.value[0]?.paramsdata?.name || ''
     reportList._reportType = 'ata_r3'
     handlereportlist(
       dashboardObj.KPIDefaulteData.transportation,
@@ -670,7 +662,7 @@ const ClickParams = (val: any) => {
     sessionStorage.setItem('reportList', JSON.stringify(reportList))
     let obj: any = {}
     obj.title = 'KPI Arrival'
-    obj.name = pie_chart_kpi_arrival.value[0].paramsdata.name
+    obj.name = pie_chart_kpi_arrival.value[0]?.paramsdata?.name || ''
     sessionStorage.setItem('tagsList', JSON.stringify(obj))
     router.push({
       path: '/tracking'
@@ -880,10 +872,21 @@ function handleImageClick(event) {
             <div class="management_content" v-for="(item, index) in Management" :key="index">
               <div class="management_flex">
                 <div class="content_title">{{ item.title }}</div>
-                <div><el-switch v-model="item.switchValue" :disabled="item.isRevenueDisplay != undefined && item.isRevenueDisplay == false" /></div>
+                <div>
+                  <el-switch
+                    v-model="item.switchValue"
+                    :disabled="item.isRevenueDisplay != undefined && item.isRevenueDisplay == false"
+                  />
+                </div>
               </div>
               <div class="content_text">{{ item.text }}</div>
-              <div class="content_text_warining" v-if="item.isRevenueDisplay != undefined && item.isRevenueDisplay == false">*To ensure the accuracy of the data display, this report needs to be configured and displayed after communicating clearly with Sales.</div>
+              <div
+                class="content_text_warining"
+                v-if="item.isRevenueDisplay != undefined && item.isRevenueDisplay == false"
+              >
+                *To ensure the accuracy of the data display, this report needs to be configured and
+                displayed after communicating clearly with Sales.
+              </div>
             </div>
             <el-divider />
             <div class="tips">
@@ -1167,6 +1170,7 @@ function handleImageClick(event) {
                   v-vloading="containerLoading"
                   style="height: 300px"
                   :isRevenue="true"
+                  save-image-name="Container Count"
                   :barHeight="{ height: '300px' }"
                 ></BarChart>
               </template>
@@ -1212,6 +1216,7 @@ function handleImageClick(event) {
                       :SellerData="Top10Obj.OriginData"
                       v-vloading="TopOriginLoading"
                       :Interval="Top1OInterval"
+                      saveImageName="Top 10 Origin"
                     ></SellerChart>
                   </div>
                   <div class="map">
@@ -1229,10 +1234,11 @@ function handleImageClick(event) {
                       :SellerData="Top10Obj.DestinationData"
                       :Interval="Top1OInterval_dest"
                       v-vloading="TopOriginLoading"
-                      style="height: 272px"
+                      saveImageName="Top 10 Destination"
+                      style="height: 310px"
                     ></SellerChart>
                   </div>
-                  <div class="map" style="height: 272px">
+                  <div class="map" style="height: 310px">
                     <!-- <TopMap :obj="dashboardObj.Top10faultData" ref="Top10Destinationref"></TopMap> -->
                     <TopMap ref="Top10Destinationref"></TopMap>
                   </div>
@@ -1267,6 +1273,7 @@ function handleImageClick(event) {
                 <BarChart
                   ref="seller_chart_CO2_origin"
                   :BarData="EmissionObj"
+                  save-image-name="CO2e Emission by Origin (Top 10)"
                   @clickParams="ClickParams(item.title)"
                   v-vloading="EmissionLoading"
                   style="height: 250px"
@@ -1301,6 +1308,7 @@ function handleImageClick(event) {
                   v-vloading="DestinationLoading"
                   style="height: 250px"
                   :isRevenue="true"
+                  save-image-name="CO2e Emission by Destination (Top 10)"
                   @clickParams="ClickParams(item.title)"
                   :barHeight="{ height: '250px' }"
                 ></BarChart>

+ 49 - 15
src/views/Dashboard/src/components/BarChart.vue

@@ -7,7 +7,8 @@ import { formatNumber } from '@/utils/tools'
 const themeStore = useThemeStore()
 const props = defineProps({
   BarData: Object,
-  barHeight: Object
+  barHeight: Object,
+  saveImageName: String
 })
 const bar_data = ref(props.BarData)
 const bar_ref = ref()
@@ -21,7 +22,9 @@ watch(
     initOption.yAxis.max = Max.value
     initOption.yAxis.interval = interval.value
     initOption.title.text = bar_title.value
-    initChart()
+    nextTick(() => {
+      barChart.value.setOption(initOption)
+    })
   },
   {
     deep: true
@@ -62,7 +65,7 @@ const Name = computed(() => {
 const initOption = reactive({
   //标题
   title: {
-    text: bar_title.value || 'Total:', //主标题
+    text: bar_title.value || '', //主标题
     left: 19,
     top: 9.5,
     textStyle: {
@@ -144,13 +147,13 @@ const initOption = reactive({
       show: true,
       lineStyle: {
         type: 'dashed',
-        color: '#eaebed'
+        color: '#3F434A'
       }
     },
     axisLine: {
       show: true,
       lineStyle: {
-        color: '#eaebed'
+        color: '#3F434A'
       }
     },
     axisLabel: {
@@ -177,24 +180,56 @@ const initOption = reactive({
       color: '#646A73'
     }
   },
+  toolbox: {
+    top: 6,
+    right: 8,
+    iconStyle: {
+      borderColor: '#2B2F36'
+    },
+    emphasis: {
+      iconStyle: {
+        borderColor: '#ff7500'
+      } // hover上去时的颜色
+    },
+    feature: {
+      saveAsImage: {
+        show: true,
+        name: bar_title.value || props.saveImageName || 'data',
+        type: 'png',
+        backgroundColor: '#fff'
+      }
+    },
+    showTitle: false
+  },
   series: bar_series.value
 })
 onMounted(() => {
   initChart()
   clickParams()
+
   watch(
     () => themeStore.theme,
     (newVal) => {
       if (newVal === 'dark') {
-        initOption.xAxis.axisLine.lineStyle.color = '#3F434A'
-        initOption.yAxis.axisLine.lineStyle.color = '#3F434A'
-        initOption.yAxis.splitLine.lineStyle.color = '#3F434A'
-        initChart()
+        initOption.title.textStyle.color = '#f0f1f3'
+        initOption.xAxis.axisLine.lineStyle.color = '#8d9095'
+        initOption.yAxis.axisLine.lineStyle.color = '#8d9095'
+        initOption.yAxis.splitLine.lineStyle.color = '#8d9095'
+        initOption.toolbox.iconStyle.borderColor = '#f0f1f3'
+        initOption.toolbox.feature.saveAsImage.backgroundColor = '#3F434A'
+        nextTick(() => {
+          barChart.value.setOption(initOption)
+        })
       } else {
+        initOption.title.textStyle.color = '#2B2F36'
         initOption.xAxis.axisLine.lineStyle.color = '#eaebed'
         initOption.yAxis.axisLine.lineStyle.color = '#eaebed'
         initOption.yAxis.splitLine.lineStyle.color = '#eaebed'
-        initChart()
+        initOption.toolbox.iconStyle.borderColor = '#2B2F36'
+        initOption.toolbox.feature.saveAsImage.backgroundColor = '#fff'
+        nextTick(() => {
+          barChart.value.setOption(initOption)
+        })
       }
     },
     {
@@ -210,21 +245,20 @@ const paramsdata = ref({
   type: ''
 })
 const clickParams = () => {
-  const bar_chart = echarts.init(bar_ref.value)
   // 监听点击事件
-  bar_chart.on('click', function (params) {
+  barChart.value.on('click', function (params) {
     paramsdata.value.name = params.name
     paramsdata.value.type = params.seriesName
     emits('ClickParams')
   })
 }
+const barChart = ref()
 const initChart = () => {
-  const bar_chart = echarts.init(bar_ref.value)
+  barChart.value = echarts.init(bar_ref.value)
   //图表响应式
   window.addEventListener('resize', () => {
-    bar_chart.resize()
+    barChart.value.resize()
   })
-  bar_chart.setOption(initOption)
 }
 
 defineExpose({

+ 74 - 39
src/views/Dashboard/src/components/PieChart.vue

@@ -4,6 +4,7 @@ import * as echarts from 'echarts'
 import { useThemeStore } from '@/stores/modules/theme'
 import { onMounted, ref, reactive, watch, computed } from 'vue'
 import { formatNumber } from '@/utils/tools'
+
 const props = defineProps({
   PieData: Object
 })
@@ -15,7 +16,9 @@ watch(
     pie_data.value = current
     initOption.title.text = pie_title.value
     initOption.toolbox.feature.saveAsImage.name = downloadName.value
-    initChart()
+    nextTick(() => {
+      pieChart.value.setOption(initOption)
+    })
   },
   {
     deep: true
@@ -46,6 +49,25 @@ const downloadName = computed(() => {
 })
 
 const initOption: any = reactive({
+  // graphic: {
+  //   elements: [
+  //     {
+  //       type: 'text',
+  //       right: '16',
+  //       bottom: '16',
+  //       style: {
+  //         text: 'Total: $12,500',
+  //         fontSize: 14,
+  //         fill: '#666'
+  //       },
+  //       // 直接设置 onclick 回调
+  //       onclick: function () {
+  //         alert('Total clicked!')
+  //         // 或者执行其他逻辑
+  //       }
+  //     }
+  //   ]
+  // },
   //标题
   title: {
     text: pie_title.value || '', //主标题
@@ -90,6 +112,8 @@ const initOption: any = reactive({
     }
   },
   toolbox: {
+    top: 6,
+    right: 8,
     iconStyle: {
       borderColor: '#2B2F36'
     },
@@ -98,10 +122,10 @@ const initOption: any = reactive({
         borderColor: '#ff7500'
       } // hover上去时的颜色
     },
-    show: false, // 显示工具箱
+    show: true, // 显示工具箱
     feature: {
-      restore: { show: true },
-      saveAsImage: { show: true, name: downloadName.value }
+      // restore: { show: true },
+      saveAsImage: { show: true, name: downloadName.value, type: 'png', backgroundColor: '#fff' }
     },
     showTitle: false
   },
@@ -143,15 +167,13 @@ const initOption: any = reactive({
       length2: 0,
       maxSurfaceAngle: 80
     },
-    labelLayout: function (params: any) {
-      const pie_chart = echarts.init(pie_ref.value)
-      const isLeft = params.labelRect.x < pie_chart.getWidth() / 2
+    labelLayout: (params) => {
+      const isLeft = params.labelRect.x < containerWidth.value / 2
       const points = params.labelLinePoints
-      // Update the end point.
-      points[2][0] = isLeft ? params.labelRect.x : params.labelRect.x + params.labelRect.width
-      return {
-        labelLinePoints: points
+      if (points?.[2]) {
+        points[2][0] = isLeft ? params.labelRect.x : params.labelRect.x + params.labelRect.width
       }
+      return { labelLinePoints: points }
     }
   },
   legend: {
@@ -168,49 +190,62 @@ const initOption: any = reactive({
     }
   }
 })
-
+const containerWidth = ref(0)
+onMounted(() => {
+  if (pie_ref.value) {
+    containerWidth.value = pie_ref.value.clientWidth
+    // 如果需要响应式 resize,可用 ResizeObserver
+  }
+})
 onMounted(() => {
   initChart()
   clickParams()
-  watch(
-    () => themeStore.theme,
-    (newVal) => {
-      if (newVal === 'dark') {
-        initOption.title.textStyle.color = '#f0f1f3'
-        initOption.series.label.rich.time.color = 'rgba(240,241,243,0.7)'
-        initOption.series.label.rich.name.color = 'rgba(240,241,243,0.7)'
-        initOption.legend.textStyle.color = 'rgba(240,241,243,0.7)'
-        initChart()
-      } else {
-        initOption.title.textStyle.color = '#2B2F36'
-        initOption.series.label.rich.time.color = '#999'
-        initOption.series.label.rich.name.color = '#646A73'
-        initOption.legend.textStyle.color = '#646A73'
-        initChart()
-      }
-    },
-    {
-      immediate: true,
-      deep: true
-    }
-  )
 })
+watch(
+  () => themeStore.theme,
+  (newVal) => {
+    if (newVal === 'dark') {
+      initOption.title.textStyle.color = '#f0f1f3'
+      initOption.series.label.rich.time.color = 'rgba(240,241,243,0.7)'
+      initOption.series.label.rich.name.color = 'rgba(240,241,243,0.7)'
+      initOption.legend.textStyle.color = 'rgba(240,241,243,0.7)'
+      initOption.toolbox.iconStyle.borderColor = '#f0f1f3'
+      initOption.toolbox.feature.saveAsImage.backgroundColor = '#2B2F36'
+      nextTick(() => {
+        pieChart.value.setOption(initOption)
+      })
+    } else {
+      initOption.title.textStyle.color = '#2B2F36'
+      initOption.series.label.rich.time.color = '#999'
+      initOption.series.label.rich.name.color = '#646A73'
+      initOption.legend.textStyle.color = '#646A73'
+      initOption.toolbox.iconStyle.borderColor = '#2B2F36'
+      initOption.toolbox.feature.saveAsImage.backgroundColor = '#fff'
+      nextTick(() => {
+        pieChart.value.setOption(initOption)
+      })
+    }
+  },
+  {
+    immediate: true,
+    deep: true
+  }
+)
 const emits = defineEmits(['ClickParams'])
 const paramsdata = ref()
+const pieChart = ref()
 const clickParams = () => {
-  const pie_chart = echarts.init(pie_ref.value)
   // 监听点击事件
-  pie_chart.on('click', function (params) {
+  pieChart.value.on('click', function (params) {
     paramsdata.value = params.data
     emits('ClickParams')
   })
 }
 const initChart = () => {
-  const pie_chart = echarts.init(pie_ref.value)
-  pie_chart.setOption(initOption)
+  pieChart.value = echarts.init(pie_ref.value)
   //图表响应式
   window.addEventListener('resize', () => {
-    pie_chart.resize()
+    pieChart.value.resize()
   })
 }
 defineExpose({

+ 0 - 1
src/views/Dashboard/src/components/RecentStatus.vue

@@ -45,7 +45,6 @@ const SubscribeShipments = (val: any) => {
     })
     .then((res: any) => {
       if (res.code === 200) {
-        console.log(res.data)
       }
     })
 }

+ 35 - 99
src/views/Dashboard/src/components/RevenueChart.vue

@@ -23,9 +23,10 @@ watch(
     initOption.xAxis.data = barName.value
     initOption.series = bar_series.value
     initOption.legend.data = Name.value
-    initOption.toolbox.feature.saveAsImage.name = downloadName.value
     initOption.toolbox.show = isShowTooltips.value
-    initChart()
+    nextTick(() => {
+      revenueChart.value.setOption(initOption)
+    })
   },
   {
     deep: true
@@ -37,7 +38,9 @@ watch(
     bar_data.value = current
     initOption.yAxis.max = Max.value
     initOption.yAxis.interval = interval.value
-    initChart()
+    nextTick(() => {
+      revenueChart.value.setOption(initOption)
+    })
   },
   {
     deep: true,
@@ -74,86 +77,10 @@ const Name = computed(() => {
     return []
   }
 })
-const downloadName = computed(() => {
-  return bar_data.value?.download_name
-})
+
 const isShowTooltips = computed(() => {
   return bar_data.value?.isShowTooltips
 })
-const exportData = ({ filename }: any) => {
-  $api
-    .RevenueDownload({
-      date_start: props.RevenueStartDate,
-      date_end: props.RevenueEndDate
-    })
-    .then((res: any) => {
-      if (res.code === 200) {
-        let header: any = []
-        let data: any = []
-        let sheetname: any = []
-        let filterA = res.data.r2_cloumn.map((el: any) => {
-          return el.dataIndex
-        })
-        let titleA = res.data.r2_cloumn.map((el: any) => {
-          return el.title
-        })
-        let filterB = res.data.r3_cloumn.map((el: any) => {
-          return el.dataIndex
-        })
-        let titleB = res.data.r3_cloumn.map((el: any) => {
-          return el.title
-        })
-        let result: any = [
-          {
-            tHeader: titleA,
-            filterVal: filterA,
-            tableDatas: res.data.r2,
-            sheetName: res.data.r2_title
-          },
-          {
-            tHeader: titleB,
-            filterVal: filterB,
-            tableDatas: res.data.r3,
-            sheetName: res.data.r3_title
-          }
-        ]
-        let formatJson = (filterVal: any, jsonData: any) => {
-          return jsonData.map((v: any) => filterVal.map((j: any) => v[j]))
-        }
-        for (var i in result) {
-          header.push(result[i].tHeader)
-          sheetname.push(result[i].sheetName)
-          data.push(formatJson(result[i].filterVal, result[i].tableDatas))
-        }
-        // 将表头插入数据数组中
-        for (let i = 0; i < header.length; i++) {
-          data[i].unshift(header[i])
-        }
-        let ws_name = sheetname
-        // 创建工作簿对象
-        let wb = XLSX.utils.book_new()
-        let ws: any = []
-        // 创建每个工作表并设置列宽
-        for (let j = 0; j < header.length; j++) {
-          ws.push(XLSX.utils.aoa_to_sheet(data[j]))
-          let arr: any = []
-          header[j].forEach((val: any) => {
-            arr.push({
-              wpx: 120
-            })
-          })
-          ws[j]['!cols'] = arr
-        }
-        // 将工作表对象添加到工作簿中
-        for (let k = 0; k < header.length; k++) {
-          wb.SheetNames.push(ws_name[k])
-          wb.Sheets[ws_name[k]] = ws[k]
-        }
-        XLSX.writeFile(wb, filename + '.xlsx') // 导出文件
-      }
-    })
-    .finally(() => {})
-}
 
 // 数额
 const initOption = reactive({
@@ -284,19 +211,20 @@ const initOption = reactive({
         borderColor: '#ff7500'
       } // hover上去时的颜色
     },
-    itemSize: 25,
     show: isShowTooltips.value, // 显示工具箱
+    top: 6,
+    right: 8,
     feature: {
-      saveAsImage: { show: false, name: downloadName.value },
-      myTool1: {
-        show: true,
-        title: 'update',
-        icon: 'image://' + updateIcon,
-        onclick: function () {
-          let filename = 'Revenue Spent Details ' + barName.value[0] + '-' + barName.value[barName.value.length - 1]
-          exportData({ filename: filename })
-        }
-      }
+      saveAsImage: { show: true, name: 'Revenue Spent', type: 'png', backgroundColor: '#fff' }
+      // myTool1: {
+      //   show: true,
+      //   title: 'update',
+      //   icon: 'image://' + updateIcon,
+      //   onclick: function () {
+      //     let filename = 'Revenue Spent Details ' + barName.value[0] + '-' + barName.value[barName.value.length - 1]
+      //     exportData({ filename: filename })
+      //   }
+      // }
     },
     showTitle: false
   }
@@ -307,19 +235,27 @@ onMounted(() => {
     () => themeStore.theme,
     (newVal) => {
       if (newVal === 'dark') {
-        initOption.title.textStyle.color = '#f0f1f3'
+        initOption.title.textStyle.color = '#fff'
         initOption.xAxis.axisLine.lineStyle.color = '#3F434A'
         initOption.xAxis.axisLine.lineStyle.color = '#3F434A'
         initOption.yAxis.axisLine.lineStyle.color = '#3F434A'
         initOption.legend.textStyle.color = 'rgba(240,241,243,0.7)'
-        initChart()
+        initOption.toolbox.iconStyle.borderColor = '#f0f1f3'
+        initOption.toolbox.feature.saveAsImage.backgroundColor = '#3F434A'
+        nextTick(() => {
+          revenueChart.value.setOption(initOption)
+        })
       } else {
         initOption.title.textStyle.color = '#2B2F36'
         initOption.xAxis.axisLine.lineStyle.color = '#eaebed'
         initOption.yAxis.axisLine.lineStyle.color = '#eaebed'
         initOption.yAxis.splitLine.lineStyle.color = '#eaebed'
         initOption.legend.textStyle.color = '#646A73'
-        initChart()
+        initOption.toolbox.iconStyle.borderColor = '#2B2F36'
+        initOption.toolbox.feature.saveAsImage.backgroundColor = '#fff'
+        nextTick(() => {
+          revenueChart.value.setOption(initOption)
+        })
       }
     },
     {
@@ -329,13 +265,14 @@ onMounted(() => {
   )
 })
 
+const revenueChart = ref()
 const initChart = () => {
-  const bar_chart = echarts.init(bar_ref.value)
+  revenueChart.value = echarts.init(bar_ref.value)
   //图表响应式
   window.addEventListener('resize', () => {
-    bar_chart.resize()
+    revenueChart.value.resize()
   })
-  bar_chart.on('legendselectchanged', function (event: any) {
+  revenueChart.value.on('legendselectchanged', function (event: any) {
     const selected = event.selected
     let trueCount = 0
     let trueObj: any = {}
@@ -380,9 +317,8 @@ const initChart = () => {
       initOption.yAxis.max = max
       initOption.yAxis.interval = interval
     }
-    bar_chart.setOption(initOption)
+    revenueChart.value.setOption(initOption)
   })
-  bar_chart.setOption(initOption)
 }
 </script>
 

+ 32 - 7
src/views/Dashboard/src/components/SellerChart.vue

@@ -7,7 +7,8 @@ import { formatNumber } from '@/utils/tools'
 const themeStore = useThemeStore()
 const props = defineProps({
   SellerData: Array,
-  Interval: Object
+  Interval: Object,
+  saveImageName: String
 })
 const seller_data = ref(props.SellerData)
 const seller_interval = ref(props.Interval)
@@ -16,7 +17,9 @@ watch(
   () => props.SellerData,
   (current) => {
     seller_data.value = current
-    initChart()
+    nextTick(() => {
+      initChart()
+    })
   },
   {
     deep: true
@@ -28,7 +31,9 @@ watch(
     seller_interval.value = current
     initOption.xAxis.max = Max.value
     initOption.xAxis.interval = interval.value
-    initChart()
+    nextTick(() => {
+      initChart()
+    })
   },
   {
     deep: true
@@ -63,10 +68,10 @@ const ColorValue = computed(() => {
 const initOption = reactive({
   // 间距
   grid: {
-    top: '5%',
+    top: '12%',
     left: '3%',
     right: '6%',
-    bottom: '5%',
+    bottom: '3%',
     containLabel: true // 距离包含坐标轴上的文字
   },
   // hover时的文字显示
@@ -163,7 +168,23 @@ const initOption = reactive({
         }
       }
     }
-  ]
+  ],
+  toolbox: {
+    top: 4,
+    right: 8,
+    iconStyle: {
+      borderColor: '#ed6d00'
+    },
+    emphasis: {
+      iconStyle: {
+        borderColor: '#ff7500'
+      } // hover上去时的颜色
+    },
+    feature: {
+      saveAsImage: { show: true, name: props.saveImageName, type: 'png', backgroundColor: '#fff' }
+    },
+    showTitle: false
+  }
 })
 
 onMounted(() => {
@@ -174,9 +195,13 @@ onMounted(() => {
     (newVal) => {
       if (newVal === 'dark') {
         initOption.xAxis.splitLine.lineStyle.color = '#3F434A'
+        initOption.toolbox.iconStyle.borderColor = '#f0f1f3'
+        initOption.toolbox.feature.saveAsImage.backgroundColor = '#2B2F36'
         initChart()
       } else {
         initOption.xAxis.splitLine.lineStyle.color = '#eaebed'
+        initOption.toolbox.iconStyle.borderColor = '#2B2F36'
+        initOption.toolbox.feature.saveAsImage.backgroundColor = '#fff'
         initChart()
       }
     },
@@ -235,6 +260,6 @@ defineExpose({
 }
 #seller_chart {
   width: 100%;
-  height: 272px;
+  height: 310px;
 }
 </style>

+ 1 - 1
src/views/Dashboard/src/components/TopMap.vue

@@ -137,7 +137,7 @@ defineExpose({
 <template>
   <div
     id="map"
-    style="width: 100%; height: 272px"
+    style="width: 100%; height: 310px"
     :class="{ 'dark-mode': themeStore.theme === 'dark' }"
   ></div>
 </template>

+ 221 - 190
src/views/DestinationDelivery/src/components/ConfiguRations/src/components/CreateNewRule.vue

@@ -42,37 +42,36 @@ const CountryCheckboxList = ref([])
 
 // 页面初始化
 const InitRuleData = () => {
-  if ( a!= undefined ) {
+  if (a != undefined) {
     $api
-    .InitCreateRule({
-      a: a
-    })
-    .then((res: any) => {
-      if (res.code === 200) {
-        const { returnData } = res.data
-        KLNPLCvalue.value = returnData.KLNPLCvalue
-        selectedCountry.value = returnData.country
-        setbookingdata.value = returnData.SetBookingWindow
-        windowRadio.value = returnData.SetBookingWindow.windowradio
-        if(windowRadio.value != 1) {
-          windowBeforeDays.value = returnData.booking_window_date_start
-          windowAfterDays.value = returnData.booking_window_date_end
-        }
-        recommendRadio.value = returnData.RcommendDeliveryDate.Recommendradio
-        if(recommendRadio.value != 1) {
-          recommendCheckedList.value = returnData.RcommendDeliveryDate.RecommendCheckedList
-          recommendCheckedAirList.value = returnData.RcommendDeliveryDate.RecommendCheckedAirList
-          recommendCheckedSeaList.value = returnData.RcommendDeliveryDate.RecommendCheckedSeaList
+      .InitCreateRule({
+        a: a
+      })
+      .then((res: any) => {
+        if (res.code === 200) {
+          const { returnData } = res.data
+          KLNPLCvalue.value = returnData.KLNPLCvalue
+          selectedCountry.value = returnData.country
+          setbookingdata.value = returnData.SetBookingWindow
+          windowRadio.value = returnData.SetBookingWindow.windowradio
+          if (windowRadio.value != 1) {
+            windowBeforeDays.value = returnData.booking_window_date_start
+            windowAfterDays.value = returnData.booking_window_date_end
+          }
+          recommendRadio.value = returnData.RcommendDeliveryDate.Recommendradio
+          if (recommendRadio.value != 1) {
+            recommendCheckedList.value = returnData.RcommendDeliveryDate.RecommendCheckedList
+            recommendCheckedAirList.value = returnData.RcommendDeliveryDate.RecommendCheckedAirList
+            recommendCheckedSeaList.value = returnData.RcommendDeliveryDate.RecommendCheckedSeaList
+          }
+          countryCheckedList.value = returnData.station
+          CountryCheckboxList.value = returnData.CountryCheckedList
+          recommendata.value = returnData.RcommendDeliveryDate
         }
-        countryCheckedList.value = returnData.station
-        CountryCheckboxList.value = returnData.CountryCheckedList
-        recommendata.value = returnData.RcommendDeliveryDate
-      }
-    })
+      })
   }
 }
 
-
 const CreateRuleDisabled = computed(() => {
   // 1. 检查基本条件是否满足
   if (
@@ -82,14 +81,14 @@ const CreateRuleDisabled = computed(() => {
     recommendRadio.value === undefined ||
     KLNPLCvalue.value.length === 0
   ) {
-    return true;
+    return true
   }
 
   // 2. 处理时间窗口条件
   if (windowRadio.value !== 1) {
     // 当 windowRadio 不为 1 时,需要验证时间窗口字段
     if (windowBeforeDays.value === '' || windowAfterDays.value === '') {
-      return true;
+      return true
     }
   }
 
@@ -97,55 +96,54 @@ const CreateRuleDisabled = computed(() => {
   if (recommendRadio.value !== 1) {
     // 3.1 确保至少选择了一个运输方式
     if (recommendCheckedList.value.length === 0) {
-      return true;
+      return true
     }
 
     // 3.2 验证航空规则(如果选择了 Air)
-    console.log(recommendCheckedList.value)
     if (recommendCheckedList.value.includes('Air')) {
-      const isAirValid = recommendCheckedAirList.value.every(item => 
-        item.ports.length > 0 && 
-        item.recommended_delivery_from !== '' && 
-        item.recommended_delivery_to !== ''
-      );
-      
-      if (!isAirValid) return true;
+      const isAirValid = recommendCheckedAirList.value.every(
+        (item) =>
+          item.ports.length > 0 &&
+          item.recommended_delivery_from !== '' &&
+          item.recommended_delivery_to !== ''
+      )
+
+      if (!isAirValid) return true
     }
 
     // 3.3 验证海运规则(如果选择了 Sea)
     if (recommendCheckedList.value.includes('Sea')) {
-      const hasInvalidSeaItem = recommendCheckedSeaList.value.some(item => {
-        const hasValidDeliveryTime = 
-          item.recommended_delivery_from && 
-          item.recommended_delivery_to;
-        
-        const hasRequiredFields = item.rule_type !== 'Single Dimension'
-          ? item.ports.length > 0 && item.carrier.length > 0
-          : item.ports.length > 0 || item.carrier.length > 0;
-        
-        return !hasValidDeliveryTime || !hasRequiredFields;
-      });
-      
-      if (hasInvalidSeaItem) return true;
+      const hasInvalidSeaItem = recommendCheckedSeaList.value.some((item) => {
+        const hasValidDeliveryTime = item.recommended_delivery_from && item.recommended_delivery_to
+
+        const hasRequiredFields =
+          item.rule_type !== 'Single Dimension'
+            ? item.ports.length > 0 && item.carrier.length > 0
+            : item.ports.length > 0 || item.carrier.length > 0
+
+        return !hasValidDeliveryTime || !hasRequiredFields
+      })
+
+      if (hasInvalidSeaItem) return true
     }
   }
   // 4. 所有条件都满足,返回 false(不禁用)
-  return false;
-});
-// select country 
-const handleClickSelectCountry = (val:any) =>{
+  return false
+})
+// select country
+const handleClickSelectCountry = (val: any) => {
   selectedCountry.value = val
   countryCheckedList.value = []
 }
 // select station list
-const handleChangeStation = (val:any) =>{
+const handleChangeStation = (val: any) => {
   countryCheckedList.value = val
 }
 
 // select booking window
 const bookingWindow = ref('')
 const bookingdetail = ref('')
-const changeBookingWindow = (radio: number, beforedays:any, afterdays:any) => {
+const changeBookingWindow = (radio: number, beforedays: any, afterdays: any) => {
   windowRadio.value = radio
   windowBeforeDays.value = beforedays
   windowAfterDays.value = afterdays
@@ -169,10 +167,11 @@ const querySearchAsync = (query: string) => {
   if (query) {
     loading.value = true
     setTimeout(() => {
-      $api.getKLNEmployeeList({ 
-        term: query,
-        station: countryCheckedList.value
-      })
+      $api
+        .getKLNEmployeeList({
+          term: query,
+          station: countryCheckedList.value
+        })
         .then((res: any) => {
           if (res.code === 200) {
             loading.value = false
@@ -189,17 +188,17 @@ const querySearchAsync = (query: string) => {
 }
 // 保存
 const handleSubmitRule = () => {
-  const airlist = recommendCheckedAirList.value.map(item => {
-    const {PortList, ...rest} = item
-    if(recommendRadio.value == 2) {
+  const airlist = recommendCheckedAirList.value.map((item) => {
+    const { PortList, ...rest } = item
+    if (recommendRadio.value == 2) {
       return rest
     } else {
       return []
     }
   })
-  const seaList = recommendCheckedSeaList.value.map(item => {
-    const {PortList,CarrierList, ...rest} = item
-    if(recommendRadio.value == 2) {
+  const seaList = recommendCheckedSeaList.value.map((item) => {
+    const { PortList, CarrierList, ...rest } = item
+    if (recommendRadio.value == 2) {
       return rest
     } else {
       return []
@@ -208,39 +207,59 @@ const handleSubmitRule = () => {
   let airData = []
   let airlistInfo = {}
   let mergeData = []
-  if(windowRadio.value == 1) {
+  if (windowRadio.value == 1) {
     bookingWindow.value = 'No_Restrictions'
     bookingdetail.value = 'No Specific time restrictions for creating booking'
-  } else if(windowRadio.value == 2) {
+  } else if (windowRadio.value == 2) {
     bookingWindow.value = 'Restrictions_ETD_ATD'
-    bookingdetail.value = 'ETD/ATD: ' + windowBeforeDays.value + ' days before to ' + windowAfterDays.value + ' days after'
-  } else if(windowRadio.value == 3) {
+    bookingdetail.value =
+      'ETD/ATD: ' +
+      windowBeforeDays.value +
+      ' days before to ' +
+      windowAfterDays.value +
+      ' days after'
+  } else if (windowRadio.value == 3) {
     bookingWindow.value = 'Restrictions_ETA_ATA'
-    bookingdetail.value = 'ETA/ATA: ' + windowBeforeDays.value + ' days before to ' + windowAfterDays.value + ' days after'
+    bookingdetail.value =
+      'ETA/ATA: ' +
+      windowBeforeDays.value +
+      ' days before to ' +
+      windowAfterDays.value +
+      ' days after'
   }
-  if(recommendRadio.value == 1) {
+  if (recommendRadio.value == 1) {
     recommendDelivery.value = 'No_Recommended'
     recommenddetail.value = 'No Specific recommended time for choosing delivery date'
   } else {
     recommendDelivery.value = 'Delivery_ETA_ATA'
-    if(recommendCheckedList.value.includes('Air')) {
-      recommenddetail.value += 'Air:\nDefault Rule- Air Port: ALL,\nRecommend Delivery Date: ETA/ATA+' + airlist[0].recommended_delivery_from + ' Days to ETA/ATA+'+ airlist[0].recommended_delivery_to + ' Days;\n'
+    if (recommendCheckedList.value.includes('Air')) {
+      recommenddetail.value +=
+        'Air:\nDefault Rule- Air Port: ALL,\nRecommend Delivery Date: ETA/ATA+' +
+        airlist[0].recommended_delivery_from +
+        ' Days to ETA/ATA+' +
+        airlist[0].recommended_delivery_to +
+        ' Days;\n'
       airlist.forEach((item) => {
         item.ports = item.ports.join(',')
         item.carrier = ''
       })
       mergeData = [...airlist]
     }
-    if(recommendCheckedList.value.includes('Sea')) {
-      recommenddetail.value += 'Sea:\nDefault Rule- ort: ALL, Carrier: ALL,\nRecommend Delivery Date: ETA/ATA+' + seaList[0].recommended_delivery_from + ' Days to ETA/ATA+'+ seaList[0].recommended_delivery_to + ' Days;' 
+    if (recommendCheckedList.value.includes('Sea')) {
+      recommenddetail.value +=
+        'Sea:\nDefault Rule- ort: ALL, Carrier: ALL,\nRecommend Delivery Date: ETA/ATA+' +
+        seaList[0].recommended_delivery_from +
+        ' Days to ETA/ATA+' +
+        seaList[0].recommended_delivery_to +
+        ' Days;'
       seaList.forEach((item) => {
         item.ports = item.ports.join(',')
         item.carrier = item.carrier.join(',')
       })
-      mergeData = [...mergeData , ...seaList]
+      mergeData = [...mergeData, ...seaList]
     }
   }
-  if(seaList.length != 0) {
+  if (seaList.length != 0) {
     airData = Object.keys(seaList?.[0])
     airData.forEach((item) => {
       Object.assign(airlistInfo, {
@@ -248,36 +267,37 @@ const handleSubmitRule = () => {
       })
     })
   }
-  $api.handelSaveRule({
-    serial_no: a != undefined ? a: '',
-    country: selectedCountry.value,
-    station: countryCheckedList.value,
-    booking_window: bookingWindow.value,
-    booking_window_date_start: windowBeforeDays.value,
-    booking_window_date_end: windowAfterDays.value,
-    recommended_delivery: recommendDelivery.value,
-    booking_window_desc: bookingdetail.value,
-    kln_pic: KLNPLCvalue.value,
-    recommended_delivery_date_desc: recommenddetail.value,
-    ...airlistInfo
-  }).then((res: any) => {
-    if (res.code === 200 && res.data.msg == 'success') {
-      SaveedVisible.value = true
-      setTimeout(() => {
-        SaveedVisible.value = false
-        router.push({ name: 'Configurations'})
-      }, 3000)
-    } else {
+  $api
+    .handelSaveRule({
+      serial_no: a != undefined ? a : '',
+      country: selectedCountry.value,
+      station: countryCheckedList.value,
+      booking_window: bookingWindow.value,
+      booking_window_date_start: windowBeforeDays.value,
+      booking_window_date_end: windowAfterDays.value,
+      recommended_delivery: recommendDelivery.value,
+      booking_window_desc: bookingdetail.value,
+      kln_pic: KLNPLCvalue.value,
+      recommended_delivery_date_desc: recommenddetail.value,
+      ...airlistInfo
+    })
+    .then((res: any) => {
+      if (res.code === 200 && res.data.msg == 'success') {
+        SaveedVisible.value = true
+        setTimeout(() => {
+          SaveedVisible.value = false
+          router.push({ name: 'Configurations' })
+        }, 3000)
+      } else {
         UnableSaveVisible.value = true
         missingmessage.value = res.data.msg
-    }
-  })
+      }
+    })
 }
 
 onMounted(() => {
   InitRuleData()
 })
-
 </script>
 
 <template>
@@ -286,11 +306,20 @@ onMounted(() => {
       <div v-if="a != undefined">Modify Rule</div>
       <div v-else>Create New Rule</div>
       <div class="operator">
-        <el-button @click="CancelRulesVisible = true" style="height: 40px; width: 115px" type="default">
+        <el-button
+          @click="CancelRulesVisible = true"
+          style="height: 40px; width: 115px"
+          type="default"
+        >
           <span style="margin-right: 4px" class="font_family icon-icon_return_b"></span>
           <span style="font-weight: 400">Cancel</span></el-button
         >
-        <el-button style="height: 40px; width: 120px" class="el-button--main el-button--pain-theme" :disabled="CreateRuleDisabled" @click="handleSubmitRule">
+        <el-button
+          style="height: 40px; width: 120px"
+          class="el-button--main el-button--pain-theme"
+          :disabled="CreateRuleDisabled"
+          @click="handleSubmitRule"
+        >
           <span
             style="
               display: inline-block;
@@ -355,7 +384,9 @@ onMounted(() => {
         </el-dialog>
         <!-- 保存成功 -->
         <el-dialog v-model="SaveedVisible" width="320" style="height: 212px">
-          <div style="text-align: center"><el-image :src="submitsucessful" style="width: 64px;" /></div>
+          <div style="text-align: center">
+            <el-image :src="submitsucessful" style="width: 64px" />
+          </div>
           <div style="text-align: center; margin-top: 20px">Saved successfully</div>
         </el-dialog>
       </div>
@@ -364,107 +395,107 @@ onMounted(() => {
       <div class="setting-top-title">Setting</div>
       <el-collapse v-model="activeRules" @change="IsFirstActive = !IsFirstActive">
         <el-collapse-item name="SelectStation">
-            <template #title>
-              <div class="Rules_Title">
-                <span class="iconfont_icon icon_dark">
-                  <svg class="iconfont" aria-hidden="true">
-                    <use
-                      :xlink:href="IsFirstActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
-                    ></use>
-                  </svg>
-                </span>
-                <span class="stars_red">*</span>Select Station (Enable Booking)
-              </div>
-            </template>
-            <div>
-              <SelectStation
+          <template #title>
+            <div class="Rules_Title">
+              <span class="iconfont_icon icon_dark">
+                <svg class="iconfont" aria-hidden="true">
+                  <use
+                    :xlink:href="IsFirstActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
+                  ></use>
+                </svg>
+              </span>
+              <span class="stars_red">*</span>Select Station (Enable Booking)
+            </div>
+          </template>
+          <div>
+            <SelectStation
               @handleClickSelectCountry="handleClickSelectCountry"
               @handleChangeStation="handleChangeStation"
               :CheckboxList="CountryCheckboxList"
               :CheckedList="countryCheckedList"
               :SelectCountry="selectedCountry"
-              ></SelectStation>
-            </div>
-          </el-collapse-item>
+            ></SelectStation>
+          </div>
+        </el-collapse-item>
       </el-collapse>
       <el-collapse v-model="activeRules" @change="IsTwoActive = !IsTwoActive">
         <el-collapse-item name="SelectBooking">
-            <template #title>
-              <div class="Rules_Title">
-                <span class="iconfont_icon icon_dark">
-                  <svg class="iconfont" aria-hidden="true">
-                    <use
-                      :xlink:href="IsTwoActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
-                    ></use>
-                  </svg>
-                </span>
-                <span class="stars_red">*</span>Set Booking Window
-              </div>
-            </template>
-            <div>
-              <SetBookingWindow
-                :setbookingdata="setbookingdata"
-                @changeBookingWindow="changeBookingWindow"
-              ></SetBookingWindow>
+          <template #title>
+            <div class="Rules_Title">
+              <span class="iconfont_icon icon_dark">
+                <svg class="iconfont" aria-hidden="true">
+                  <use
+                    :xlink:href="IsTwoActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
+                  ></use>
+                </svg>
+              </span>
+              <span class="stars_red">*</span>Set Booking Window
             </div>
-          </el-collapse-item>
+          </template>
+          <div>
+            <SetBookingWindow
+              :setbookingdata="setbookingdata"
+              @changeBookingWindow="changeBookingWindow"
+            ></SetBookingWindow>
+          </div>
+        </el-collapse-item>
       </el-collapse>
       <el-collapse v-model="activeRules" @change="IsThreeActive = !IsThreeActive">
         <el-collapse-item name="RecommendDeliveryDate">
-            <template #title>
-              <div class="Rules_Title">
-                <span class="iconfont_icon icon_dark">
-                  <svg class="iconfont" aria-hidden="true">
-                    <use
-                      :xlink:href="IsThreeActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
-                    ></use>
-                  </svg>
-                </span>
-                <span class="stars_red">*</span>Recommend Delivery Date 
-              </div>
-            </template>
-            <div>
-              <RecommendDate
-                :recommendata="recommendata"
-                @chackchangerecommend="checkRecommend"
-              ></RecommendDate>
+          <template #title>
+            <div class="Rules_Title">
+              <span class="iconfont_icon icon_dark">
+                <svg class="iconfont" aria-hidden="true">
+                  <use
+                    :xlink:href="IsThreeActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
+                  ></use>
+                </svg>
+              </span>
+              <span class="stars_red">*</span>Recommend Delivery Date
             </div>
-          </el-collapse-item>
+          </template>
+          <div>
+            <RecommendDate
+              :recommendata="recommendata"
+              @chackchangerecommend="checkRecommend"
+            ></RecommendDate>
+          </div>
+        </el-collapse-item>
       </el-collapse>
       <el-collapse v-model="activeRules" @change="IsFourActive = !IsFourActive">
         <el-collapse-item name="KLNPLC">
-            <template #title>
-              <div class="Rules_Title">
-                <span class="iconfont_icon icon_dark">
-                  <svg class="iconfont" aria-hidden="true">
-                    <use
-                      :xlink:href="IsFourActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
-                    ></use>
-                  </svg>
-                </span>
-                <span class="stars_red">*</span>KLN PIC
-              </div>
-            </template>
-            <div>
-              <el-select
-                v-model="KLNPLCvalue"
-                filterable
-                remote
-                multiple
-                placeholder="Select Employee Account"
-                :remote-method="querySearchAsync"
-                :loading="loading"
-                style="width: 400px;margin-bottom: 5px;"
-              >
-                <el-option
-                  v-for="item in options"
-                  :key="item.value"
-                  :label="item.label"
-                  :value="item.value"
-                />
-              </el-select>
+          <template #title>
+            <div class="Rules_Title">
+              <span class="iconfont_icon icon_dark">
+                <svg class="iconfont" aria-hidden="true">
+                  <use
+                    :xlink:href="IsFourActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
+                  ></use>
+                </svg>
+              </span>
+              <span class="stars_red">*</span>KLN PIC
             </div>
-          </el-collapse-item>
+          </template>
+          <div>
+            <el-select
+              v-model="KLNPLCvalue"
+              filterable
+              remote
+              multiple
+              placeholder="Select Employee Account"
+              :remote-method="querySearchAsync"
+              :loading="loading"
+              style="width: 400px; margin-bottom: 5px"
+            >
+              <el-option
+                v-for="item in options"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
+              />
+            </el-select>
+          </div>
+        </el-collapse-item>
       </el-collapse>
     </div>
   </div>
@@ -550,7 +581,7 @@ onMounted(() => {
   box-shadow: none;
   border: 1px solid var(--color-theme);
 }
-:deep(.el-select__wrapper.is-focused ){
+:deep(.el-select__wrapper.is-focused) {
   box-shadow: none;
   border: 1px solid var(--color-theme);
 }
@@ -580,4 +611,4 @@ onMounted(() => {
 :deep(.el-dialog__body) {
   font-weight: 400;
 }
-</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>

+ 2 - 0
src/views/Layout/src/components/Menu/MenuView.vue

@@ -148,6 +148,7 @@ router.afterEach(() => {
 
 // 路由跳转函数
 const changeRouter = (path: any) => {
+  sessionStorage.removeItem('reportList')
   if (localStorage.getItem('loginAI')) {
     localStorage.removeItem('loginAI')
     emitter.emit('login-success')
@@ -165,6 +166,7 @@ const changeRouter = (path: any) => {
   }
   if (sessionStorage.getItem('searchTableQeuryTracking')) {
     sessionStorage.removeItem('searchTableQeuryTracking')
+    sessionStorage.removeItem('tagsList')
   }
   if (sessionStorage.getItem('clickParams')) {
     sessionStorage.removeItem('clickParams')

+ 150 - 82
src/views/Tracking/src/TrackingView.vue

@@ -8,6 +8,12 @@ import MoreFilters from '@/components/MoreFilters'
 import { ref, reactive, onMounted } from 'vue'
 import { useCalculatingHeight } from '@/hooks/calculatingHeight'
 import { useHeaderSearch } from '@/stores/modules/headerSearch'
+import { useUserStore } from '@/stores/modules/user'
+import dayjs from 'dayjs'
+
+const userStore = useUserStore()
+const formatDate = userStore.dateFormat
+const valueFormatDate = 'MM/DD/YYYY'
 
 const headerSearch = useHeaderSearch()
 
@@ -33,18 +39,18 @@ const handleClose = (tag: any) => {
     sessionStorage.getItem('reportList') != null ||
     sessionStorage.getItem('reportList') != '{}'
   ) {
-    const reportlist = JSON.parse(sessionStorage.getItem('reportList') as string) || {}
+    const reportList = JSON.parse(sessionStorage.getItem('reportList') as string) || {}
     let data = JSON.parse(sessionStorage.getItem('tagsList') as string) || {}
     if (tag.includes('Transport')) {
-      delete reportlist.transport_mode
+      delete reportList.transport_mode
     } else if (tag.includes('Day') || tag.includes('CO2e')) {
-      delete reportlist._reportRef
-      delete reportlist._reportType
-      delete reportlist._reportRefe_date
-      delete reportlist._reportRefb_date
-      delete reportlist._reportStationType
-      delete reportlist._reportDataType
-      delete reportlist._reportStationType
+      delete reportList._reportRef
+      delete reportList._reportType
+      delete reportList._reportRefe_date
+      delete reportList._reportRefb_date
+      delete reportList._reportStationType
+      delete reportList._reportDataType
+      delete reportList._reportStationType
       filterData.dashboardData = []
       data = {}
     } else if (tag.includes('ETD')) {
@@ -53,24 +59,24 @@ const handleClose = (tag: any) => {
           filterData.daterangeData.splice(index, 1)
         }
       })
-      delete reportlist.etd_start
-      delete reportlist.etd_end
+      delete reportList.etd_start
+      delete reportList.etd_end
     } else if (tag.includes('ETA')) {
       filterData.daterangeData.forEach((item: any, index: any) => {
         if (item.includes('ETA')) {
           filterData.daterangeData.splice(index, 1)
         }
       })
-      delete reportlist.eta_start
-      delete reportlist.eta_end
+      delete reportList.eta_start
+      delete reportList.eta_end
     } else if (tag.includes('Origin')) {
-      delete reportlist.shipper_city
-      delete reportlist._city_name
+      delete reportList.shipper_city
+      delete reportList._city_name
       filterData.dashboardData = []
     } else if (tag.includes('Destination')) {
-      delete reportlist.consignee_city
+      delete reportList.consignee_city
     }
-    sessionStorage.setItem('reportList', JSON.stringify(reportlist))
+    sessionStorage.setItem('reportList', JSON.stringify(reportList))
     sessionStorage.setItem('tagsList', JSON.stringify(data))
   }
   if (tag.includes('Transport')) {
@@ -142,14 +148,16 @@ const handleClose = (tag: any) => {
   } else if (tag.includes('Voyage')) {
     delete searchTableQeuryTracking['f_voyage/voyage']
   }
+
   sessionStorage.setItem('searchTableQeuryTracking', JSON.stringify(searchTableQeuryTracking))
-  Gettrackingdata()
+  getTrackingData()
 }
 // 筛选框查询
 const FiltersSeach = (val: any, value: any) => {
   searchTableQeuryTracking[val] = value
+
   sessionStorage.setItem('searchTableQeuryTracking', JSON.stringify(searchTableQeuryTracking))
-  Gettrackingdata()
+  getTrackingData()
 }
 //TransportSearch
 const TransportSearch = (val: any) => {
@@ -179,36 +187,33 @@ const defaultTransport = (val: any, value: any) => {
     searchTableQeuryTracking = value
   }
 }
-//defaultDate
-const defaultDate = (val: any, value: any, data: any) => {
+const setFilterData = (dateRangeData: any) => {
   filterData.daterangeData = []
-  if (Object.keys(val).length) {
-    for (const key in val) {
-      let str = `${key}:${val[key]}`
-      filterData.daterangeData.push(str)
-    }
+  for (const key in dateRangeData) {
+    const startEnd = dateRangeData[key].data[0] + ' To ' + dateRangeData[key].data[1]
+    let str = `${key}:${startEnd}`
+    filterData.daterangeData.push(str)
   }
+}
+//defaultDate
+const defaultDate = (dateRangeData: any, data: any) => {
+  setFilterData(dateRangeData)
 
   if (sessionStorage.getItem('searchTableQeuryTracking') == null) {
     if (
       sessionStorage.getItem('clickParams') === null ||
       sessionStorage.getItem('clickParams') === '{}'
     ) {
-      if (Object.keys(value).length == 0) {
+      if (Object.keys(dateRangeData).length == 0) {
         delete searchTableQeuryTracking.etd_start
         delete searchTableQeuryTracking.etd_end
       }
-      for (const key in value) {
-        searchTableQeuryTracking.etd_start = value[key].data[0]
-        searchTableQeuryTracking.etd_end = value[key].data[1]
+      for (const key in dateRangeData) {
+        searchTableQeuryTracking.etd_start = dateRangeData[key].data[0]
+
+        searchTableQeuryTracking.etd_end = dateRangeData[key].data[1]
       }
     }
-    // else {
-    //   for (const key in value) {
-    //     searchTableQeuryTracking.etd_start = value[key].data[0]
-    //     searchTableQeuryTracking.etd_end = value[key].data[1]
-    //   }
-    // }
   } else {
     searchTableQeuryTracking = data
     if (searchTableQeuryTracking._textSearch) {
@@ -221,22 +226,16 @@ const defaultDate = (val: any, value: any, data: any) => {
   if (trackingData) {
     // 根据顶部搜索框的搜索结果赋值
     initDataByHeaderSearch()
-  } else {
-    Gettrackingdata()
+  } else if (!trackingData && !sessionStorage.getItem('clickParams')) {
+    getTrackingData()
   }
 
   renderTagsData()
 }
 //DateRangeSearch
-const DateRangeSearch = (val: any, value: any) => {
-  filterData.daterangeData = []
-  if (Object.keys(val).length) {
-    for (const key in val) {
-      let str = `${key}:${val[key]}`
-      filterData.daterangeData.push(str)
-    }
-  }
-  if (Object.keys(value).length == 0) {
+const DateRangeSearch = (dateRangeData: any) => {
+  setFilterData(dateRangeData)
+  if (Object.keys(dateRangeData).length == 0) {
     delete searchTableQeuryTracking.etd_start
     delete searchTableQeuryTracking.etd_end
     delete searchTableQeuryTracking.eta_start
@@ -244,20 +243,35 @@ const DateRangeSearch = (val: any, value: any) => {
     delete searchTableQeuryTracking.created_time_start
     delete searchTableQeuryTracking.created_time_end
   }
-  for (const key in value) {
+  const fieldList = [
+    {
+      title: 'ETD',
+      keys: ['etd_start', 'etd_end']
+    },
+    { title: 'ETA', keys: ['eta_start', 'eta_end'] },
+    { title: 'Creation Time', keys: ['created_time_start', 'created_time_end'] }
+  ]
+  fieldList.forEach((item) => {
+    if (!dateRangeData.hasOwnProperty(item.title)) {
+      // 删除不存在的字段
+      searchTableQeuryTracking[item.keys[0]] = undefined
+      searchTableQeuryTracking[item.keys[1]] = undefined
+    }
+  })
+  for (const key in dateRangeData) {
     if (key == 'ETD') {
-      searchTableQeuryTracking.etd_start = value[key].data[0]
-      searchTableQeuryTracking.etd_end = value[key].data[1]
+      searchTableQeuryTracking.etd_start = dateRangeData[key].data[0]
+      searchTableQeuryTracking.etd_end = dateRangeData[key].data[1]
     } else if (key == 'ETA') {
-      searchTableQeuryTracking.eta_start = value[key].data[0]
-      searchTableQeuryTracking.eta_end = value[key].data[1]
+      searchTableQeuryTracking.eta_start = dateRangeData[key].data[0]
+      searchTableQeuryTracking.eta_end = dateRangeData[key].data[1]
     } else {
-      searchTableQeuryTracking.created_time_start = value[key].data[0]
-      searchTableQeuryTracking.created_time_end = value[key].data[1]
+      searchTableQeuryTracking.created_time_start = dateRangeData[key].data[0]
+      searchTableQeuryTracking.created_time_end = dateRangeData[key].data[1]
     }
   }
   sessionStorage.setItem('searchTableQeuryTracking', JSON.stringify(searchTableQeuryTracking))
-  Gettrackingdata()
+  getTrackingData()
   renderTagsData()
 }
 //MoreFiltersSearch
@@ -325,7 +339,7 @@ const MoreFiltersSearch = (val: any, value: any) => {
     }
   }
   sessionStorage.setItem('searchTableQeuryTracking', JSON.stringify(searchTableQeuryTracking))
-  Gettrackingdata()
+  getTrackingData()
   renderTagsData()
 }
 const defaultMorefilters = (val: any, value: any, data: any) => {
@@ -417,19 +431,18 @@ const clearfilters = () => {
     sessionStorage.getItem('reportList') != null ||
     sessionStorage.getItem('reportList') != '{}'
   ) {
-    let reportlist = JSON.parse(sessionStorage.getItem('reportList') as string) || {}
-    let data = JSON.parse(sessionStorage.getItem('tagsList') as string) || {}
-    reportlist = {}
-    data = {}
-    sessionStorage.setItem('reportList', JSON.stringify(reportlist))
-    sessionStorage.setItem('tagsList', JSON.stringify(data))
-  } else {
-    searchTableQeuryTracking.filterTag = ['All']
+    // sessionStorage.removeItem('reportList')
+    sessionStorage.removeItem('tagsList')
   }
-  Gettrackingdata()
+  getTrackingData()
   renderTagsData()
 }
 const renderTagsData = () => {
+  const data = JSON.parse(sessionStorage.getItem('tagsList') as string) || {}
+  if (Object.keys(data).length != 0) {
+    let str = `${data.title}:${data.name}`
+    !filterData.dashboardData.includes(str) && filterData.dashboardData.push(str)
+  }
   tagsData.value = []
   if (filterData.transportData.length) {
     tagsData.value.push(filterData.transportData[0])
@@ -496,6 +509,7 @@ const initDataByHeaderSearch = () => {
     sessionStorage.removeItem('searchTableQeuryTracking')
     searchTableQeuryTracking._textSearch = TrackingSearch.value
     searchTableQeuryTracking.filterTag = ['All']
+
     sessionStorage.setItem('searchTableQeuryTracking', JSON.stringify(searchTableQeuryTracking))
     setTimeout(() => {
       emitter.emit('clearTag', 'Shipment status')
@@ -549,7 +563,22 @@ interface ListItem {
 const TagsList = ref<ListItem[]>([])
 const filterTag = ref(['All'])
 const isShowAlertIcon = ref(false)
-const Gettrackingdata = () => {
+const getTrackingData = () => {
+  const dateRangeKeys = [
+    'etd_start',
+    'etd_end',
+    'eta_start',
+    'eta_end',
+    'created_time_start',
+    'created_time_end'
+  ]
+  const curRangeData = {}
+  for (const key of dateRangeKeys) {
+    if (searchTableQeuryTracking[key]) {
+      curRangeData[key] = dayjs(searchTableQeuryTracking[key], formatDate).format(valueFormatDate)
+    }
+  }
+
   tableLoadingTableData.value = true
   TrackingTable_ref.value.getLoadingData(tableLoadingTableData.value)
   $api
@@ -558,7 +587,8 @@ const Gettrackingdata = () => {
       ps: TrackingTable_ref.value.pageInfo.pageSize,
       rc: -1,
       other_filed: '',
-      ...searchTableQeuryTracking
+      ...searchTableQeuryTracking,
+      ...curRangeData
     })
     .then((res: any) => {
       if (res.code === 200) {
@@ -594,28 +624,64 @@ const Gettrackingdata = () => {
         }
       }
     })
+    .finally(() => {
+      sessionStorage.removeItem('clickParams')
+    })
 }
 onMounted(() => {
   if (
     sessionStorage.getItem('clickParams') != null &&
     sessionStorage.getItem('clickParams') != '{}'
   ) {
-    if (sessionStorage.getItem('searchTableQeuryTracking') != null) {
-      searchTableQeuryTracking = {}
-    }
-    const data = JSON.parse(sessionStorage.getItem('tagsList') as string) || {}
-    const reportlist = JSON.parse(sessionStorage.getItem('reportList') as string) || {}
-    for (const key in reportlist) {
-      searchTableQeuryTracking[key] = reportlist[key]
-    }
-    if (reportlist._city_name == '' || reportlist._city_name == undefined) {
-      if (Object.keys(data).length != 0) {
-        let str = `${data.title}:${data.name}`
-        filterData.dashboardData.push(str)
+    const reportList = JSON.parse(sessionStorage.getItem('reportList') as string) || {}
+    for (const key in reportList) {
+      let str = ''
+      switch (key) {
+        case 'etd_start':
+          const startDate = dayjs(reportList['etd_start'], valueFormatDate).format(formatDate)
+          searchTableQeuryTracking['etd_start'] = startDate
+          const endDate = dayjs(reportList['etd_end'], valueFormatDate).format(formatDate)
+          searchTableQeuryTracking['etd_end'] = endDate
+          const startEnd = startDate + ' To ' + endDate
+          str = `ETD:${startEnd}`
+          // filterData.daterangeData.push(str)
+          break
+
+        case 'etd_end':
+          break
+        case 'eta_start':
+          const etaStart = dayjs(reportList['eta_start'], valueFormatDate).format(formatDate)
+          searchTableQeuryTracking['eta_start'] = etaStart
+          const etaEnd = dayjs(reportList['eta_end'], valueFormatDate).format(formatDate)
+          searchTableQeuryTracking['eta_end'] = etaEnd
+          str = `ETA:${etaStart} To ${etaEnd}`
+          // filterData.daterangeData.push(str)
+          break
+
+        case 'eta_end':
+          break
+        case 'created_time_start':
+          const createdTimeStart = dayjs(reportList['created_time_start'], valueFormatDate).format(
+            formatDate
+          )
+          searchTableQeuryTracking['created_time_start'] = createdTimeStart
+          const createdTimeEnd = dayjs(reportList['created_time_end'], valueFormatDate).format(
+            formatDate
+          )
+          searchTableQeuryTracking['created_time_end'] = createdTimeEnd
+          str = `Creation Time:${createdTimeStart} To ${createdTimeEnd}`
+          // filterData.daterangeData.push(str)
+          break
+        case 'created_time_end':
+          break
+        default:
+          searchTableQeuryTracking[key] = reportList[key]
       }
     }
+
+    // sessionStorage.removeItem('reportList')
     sessionStorage.setItem('searchTableQeuryTracking', JSON.stringify(searchTableQeuryTracking))
-    Gettrackingdata()
+    getTrackingData()
   }
   renderTagsData()
 })
@@ -625,17 +691,19 @@ const changeTag = (val: any, boolean: any) => {
   let str = 'Shipment status: ' + val
   filterData.filtersTagData.push(str)
   filterTag.value = val
+
   sessionStorage.setItem('searchTableQeuryTracking', JSON.stringify(searchTableQeuryTracking))
   if (boolean) {
-    Gettrackingdata()
+    getTrackingData()
   }
   renderTagsData()
 }
 // 点击search按钮
 const SearchInput = () => {
   searchTableQeuryTracking._textSearch = TrackingSearch.value
+
   sessionStorage.setItem('searchTableQeuryTracking', JSON.stringify(searchTableQeuryTracking))
-  Gettrackingdata()
+  getTrackingData()
 }
 
 import TrackingGuide from './components/TrackingGuide.vue'

+ 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


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

@@ -140,7 +140,6 @@ const SubscribeShipments = () => {
     })
     .then((res: any) => {
       if (res.code === 200) {
-        console.log(res.data)
       }
     })
 }

+ 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;
   }
 }

+ 129 - 13
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)
 // 获取导出表格数据
@@ -483,7 +510,11 @@ const handleCustomizeColumns = () => {
       model_name: 'Ocean_Search'
     }
   }
-  CustomizeColumnsRef.value.openDialog(params, -220)
+  CustomizeColumnsRef.value.openDialog(
+    params,
+    -220,
+    'Drag item over to this selection or click "add" icon to show the column on your shipment list'
+  )
 }
 
 // 定制表格
@@ -514,8 +545,8 @@ const handleLinkClick = (row: any, column: any) => {
       path: '/tracking/detail',
       query: { a: row.__serial_no, _schemas: row._schemas }
     })
-    visitedRowState.setTrackingTableData(row['__serial_no'])
   }
+  visitedRowState.setTrackingTableData(row['__serial_no'])
 }
 
 const selectedNumber = ref(0)
@@ -574,7 +605,6 @@ const SubscribeShipments = (row: any) => {
     })
     .then((res: any) => {
       if (res.code === 200) {
-        console.log(res.data)
       }
     })
 }
@@ -599,17 +629,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
@@ -716,6 +773,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;
@@ -728,6 +808,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 {
@@ -782,3 +879,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>

Some files were not shown because too many files changed in this diff