Procházet zdrojové kódy

Merge branch 'test' into test_zyh

Jack Zhou před 3 měsíci
rodič
revize
33a1f30d26
53 změnil soubory, kde provedl 7985 přidání a 81 odebrání
  1. 1 1
      package.json
  2. 3 1
      src/api/index.ts
  3. 321 0
      src/api/module/Delivery.ts
  4. 1 2
      src/auto-imports.d.ts
  5. 3 1
      src/components/AutoSelect/src/AutoSelect.vue
  6. 5 3
      src/components/DateRange/src/DateRange.vue
  7. 23 4
      src/components/DateRange/src/components/CalendarDate.vue
  8. 0 1
      src/components/MoreFilters/src/components/SelectValue.vue
  9. 11 1
      src/components/VBreadcrumb/src/VBreadcrumb.vue
  10. 28 1
      src/components/VTag/src/VTag.vue
  11. 29 0
      src/router/index.ts
  12. 73 2
      src/stores/modules/breadCrumb.ts
  13. 154 48
      src/styles/Antdui.scss
  14. 48 6
      src/styles/elementui.scss
  15. 41 0
      src/styles/theme.scss
  16. 3 0
      src/styles/vxeTable.scss
  17. 1 1
      src/views/AIApiLog/src/AIApiLog.vue
  18. 1 1
      src/views/ChatLog/src/ChatLog.vue
  19. 1 0
      src/views/DestinationDelivery/index.ts
  20. 316 0
      src/views/DestinationDelivery/src/DestinationDelivery.vue
  21. 1 0
      src/views/DestinationDelivery/src/components/ConfiguRations/index.ts
  22. 95 0
      src/views/DestinationDelivery/src/components/ConfiguRations/src/ConfiguRations.vue
  23. 248 0
      src/views/DestinationDelivery/src/components/ConfiguRations/src/components/ConfigurationsTable.vue
  24. 583 0
      src/views/DestinationDelivery/src/components/ConfiguRations/src/components/CreateNewRule.vue
  25. 750 0
      src/views/DestinationDelivery/src/components/ConfiguRations/src/components/RecommendDate.vue
  26. 201 0
      src/views/DestinationDelivery/src/components/ConfiguRations/src/components/SelectStation.vue
  27. 201 0
      src/views/DestinationDelivery/src/components/ConfiguRations/src/components/SelectValue.vue
  28. 168 0
      src/views/DestinationDelivery/src/components/ConfiguRations/src/components/SetBookingWindow.vue
  29. binární
      src/views/DestinationDelivery/src/components/ConfiguRations/src/images/default_configuration@2x.png
  30. binární
      src/views/DestinationDelivery/src/components/ConfiguRations/src/images/icon_success_big@2x.png
  31. 1 0
      src/views/DestinationDelivery/src/components/CreateNewBooking/index.ts
  32. 1482 0
      src/views/DestinationDelivery/src/components/CreateNewBooking/src/CreateNewbooking.vue
  33. 259 0
      src/views/DestinationDelivery/src/components/CreateNewBooking/src/components/NewbookingTable.vue
  34. binární
      src/views/DestinationDelivery/src/components/CreateNewBooking/src/images/default_add_address@2x.png
  35. binární
      src/views/DestinationDelivery/src/components/CreateNewBooking/src/images/default_destination_not_available@2x.png
  36. binární
      src/views/DestinationDelivery/src/components/CreateNewBooking/src/images/default_no_shipment@2x.png
  37. binární
      src/views/DestinationDelivery/src/components/CreateNewBooking/src/images/icon_success_big@2x.png
  38. 484 0
      src/views/DestinationDelivery/src/components/DeliveryDate.vue
  39. 1 0
      src/views/DestinationDelivery/src/components/ModifyBooking/index.ts
  40. 276 0
      src/views/DestinationDelivery/src/components/ModifyBooking/src/ModifyBooking.vue
  41. 152 0
      src/views/DestinationDelivery/src/components/ModifyBooking/src/components/SelectShipmentsTable.vue
  42. 1 0
      src/views/DestinationDelivery/src/components/TableView/index.ts
  43. 545 0
      src/views/DestinationDelivery/src/components/TableView/src/TableView.vue
  44. 229 0
      src/views/DestinationDelivery/src/components/TableView/src/components/BookingDetailDialog.vue
  45. 177 0
      src/views/DestinationDelivery/src/components/TableView/src/components/DetailStep.vue
  46. 190 0
      src/views/DestinationDelivery/src/components/TableView/src/components/DownloadDialog.vue
  47. 392 0
      src/views/DestinationDelivery/src/components/TableView/src/components/EmailDialog.vue
  48. 138 0
      src/views/DestinationDelivery/src/components/TableView/src/components/OperationLogProcess.vue
  49. 129 0
      src/views/DestinationDelivery/src/components/TableView/src/components/ShipmentInforTable.vue
  50. 143 0
      src/views/DestinationDelivery/src/components/TableView/src/components/TipsDialog.vue
  51. binární
      src/views/DestinationDelivery/src/components/TableView/src/img/table-empty-img.png
  52. 75 5
      src/views/Layout/src/components/Menu/MenuView.vue
  53. 1 3
      src/views/Tracking/src/components/TrackingTable/src/components/VGMView.vue

+ 1 - 1
package.json

@@ -43,7 +43,7 @@
     "sass-loader": "^16.0.2",
     "vue": "^3.4.29",
     "vue-draggable-plus": "^0.5.3",
-    "vue-json-pretty": "^2.4.0",
+    "vue-json-pretty": "^2.5.0",
     "vue-router": "^4.3.3",
     "vue3-puzzle-vcode": "^1.1.7",
     "vue3-virtual-scroller": "^0.2.3",

+ 3 - 1
src/api/index.ts

@@ -6,6 +6,7 @@ import * as other from './module/other'
 import * as notificationMessage from './module/notificationMessage'
 import * as system from './module/system'
 import * as AIRobot from './module/AIRobot'
+import * as Delivery from './module/Delivery'
 /**
  * api 对象接口定义
  */
@@ -25,7 +26,8 @@ const apis = generateApiMap({
   ...other,
   ...notificationMessage,
   ...system,
-  ...AIRobot
+  ...AIRobot,
+  ...Delivery
 })
 export default {
   ...apis // 取出所有可遍历属性赋值在新的对象上

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

@@ -0,0 +1,321 @@
+import HttpAxios from '@/utils/axios'
+
+const base = import.meta.env.VITE_API_HOST
+const baseUrl = `${base}/main_new_version.php`
+
+/**
+ * Select Country
+ */
+export const getDeliveryCountry = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_load',
+      operate: 'country',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * 选择了国家后的station list
+ */
+export const getDeliveryStation = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_load',
+      operate: 'station',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * KLN employee account List
+ */
+export const getKLNEmployeeList = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_load',
+      operate: 'employee_account',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * Port List
+ */
+export const getPortList = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_load',
+      operate: 'ports',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * Carrier List
+ */
+export const getCarrierList = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_load',
+      operate: 'carrier',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * Save rule
+ */
+export const handelSaveRule = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_config',
+      operate: 'save',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * Configuration table list
+ */
+export const getConfigurationList = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_config',
+      operate: 'search',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * delete Configuration table list
+ */
+export const deleteConfigurationList = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_config',
+      operate: 'delete',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * Create New Rule页面初始化
+ */
+export const InitCreateRule = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_config',
+      operate: 'add',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * Create New Booking页面初始化
+ */
+export const InitCreateBooking = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_booking',
+      operate: 'add',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * Create New Booking表格column
+ */
+export const BookingTableColumn = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_booking',
+      operate: 'destination_delivery_shipment_search',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * Create New Booking查询表格data
+ */
+export const BookingTableSearch = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_booking',
+      operate: 'search_shipment',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * 根据选择的list获取address book
+ */
+export const getAddressBookList = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_load',
+      operate: 'manage_address',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * 保存booking
+ */
+export const SaveBookingList = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_booking',
+      operate: 'save',
+      ...params
+    },
+    config
+  )
+}
+
+/**
+ * 获取delivery booking表格数据
+ */
+export const getDeliveryBookingTableColumn = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_booking',
+      operate: 'destination_delivery_search',
+      ...params
+    },
+    config
+  )
+}
+
+/**
+ * 获取delivery booking表格数据
+ */
+export const getDeliveryBookingTableData = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_booking',
+      operate: 'search',
+      ...params
+    },
+    config
+  )
+}
+
+/**
+ * 获取delivery booking dialog详情
+ */
+export const getDeliveryBookingDialogData = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_booking',
+      operate: 'view_detail',
+      ...params
+    },
+    config
+  )
+}
+
+/**
+ * delivery_booking 邮件留言Save
+ */
+export const saveDliveryBookingEmail = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_booking',
+      operate: 'email_message_board',
+      ...params
+    },
+    config
+  )
+}
+
+/**
+ * 审核delivery_booking
+ */
+export const reviewDliveryBooking = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_booking',
+      operate: 'review',
+      ...params
+    },
+    config
+  )
+}
+
+/**
+ *  Address List自动补全
+ */
+export const getAddressCountryCityData = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_load',
+      operate: 'country_city_load',
+      ...params
+    },
+    config
+  )
+}
+
+/**  
+ * 获取delivery date组件中展示数据
+ */
+export const getDeliveryDateData = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_booking',
+      operate: 'delivery_date_load',
+      ...params
+    },
+    config
+  )
+}
+
+/**
+ * 获取delivery email部分records
+ */
+export const getEmailRecords = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'destination_delivery_booking',
+      operate: 'email_message_init',
+      ...params
+    },
+    config
+  )
+}

+ 1 - 2
src/auto-imports.d.ts

@@ -3,7 +3,6 @@
 // @ts-nocheck
 // noinspection JSUnusedGlobalSymbols
 // Generated by unplugin-auto-import
-// biome-ignore lint: disable
 export {}
 declare global {
   const $api: typeof import('@/api/index')['default']
@@ -69,6 +68,6 @@ declare global {
 // for type re-export
 declare global {
   // @ts-ignore
-  export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
+  export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
   import('vue')
 }

+ 3 - 1
src/components/AutoSelect/src/AutoSelect.vue

@@ -79,13 +79,14 @@ const remoteMethod = (query: string) => {
     options.value = []
   }
 }
-const emit = defineEmits(['changeAutoSelect'])
+const emit = defineEmits(['changeAutoSelect', 'changeFocus'])
 // 选中改变
 const changeAutoSelect = () => {
   emit('changeAutoSelect', value)
 }
 // 清除警告样式
 const removeClass = () => {
+  emit('changeFocus', true)
   const input_change = document.getElementsByClassName('input_change')
   if (input_change.length) {
     if (input_change[0].classList.value.indexOf('AlertInput') != -1) {
@@ -106,6 +107,7 @@ const removeClass = () => {
       :placeholder="props.ASPlaceholder"
       collapse-tags
       @focus="removeClass"
+      @blur="emit('changeFocus', false)"
       :disabled="props.ASisDisabled"
       collapse-tags-tooltip
       :max-collapse-tags="3"

+ 5 - 3
src/components/DateRange/src/DateRange.vue

@@ -15,10 +15,12 @@ onMounted(() => {
   emitter.on('clearTag', (tag: any) => {
     if (tag.includes('ETD')) {
       clearDateStart()
-    } else if (tag.includes('ETA')) {
+    }
+    if (tag.includes('ETA')) {
+      clearDateEnd()
+    }
+    if (tag.includes('Creation Time')) {
       clearDateEnd()
-    } else {
-      clearDateCreation()
     }
   })
   emitter.on('clearDaterangeObj', () => {

+ 23 - 4
src/components/DateRange/src/components/CalendarDate.vue

@@ -21,9 +21,25 @@ const props = defineProps({
   isType: {
     type: Boolean,
     default: false
+  },
+  isNeedFooter: {
+    type: Boolean,
+    default: true
+  },
+  isETA: {
+    type: Boolean,
+    default: false
   }
 })
 const ETDDate = ref([])
+const CreatePlaceholder = computed(() => {
+  if (props.isETA) {
+    return ['Start ETA Time', 'End ETA Time']
+  } else {
+    return ['Start ATA Time', 'End ATA Time']
+  }
+
+})
 watch(
   () => props.Date,
   (current: any) => {
@@ -32,12 +48,14 @@ watch(
         current[0] ? dayjs(current[0]).format(valueFormatDate) : '',
         current[1] ? dayjs(current[1]).format(valueFormatDate) : ''
       ]
+    } else {
+      ETDDate.value  = []
     }
   },
   { immediate: true, deep: true }
 )
 
-const emit = defineEmits(['DateRangeChange', 'DateChange'])
+const emit = defineEmits(['DateRangeChange', 'DateChange', 'DateChangeFocus'])
 const open = ref(false)
 const Disabled = ref([false, false])
 const isShowExtra = ref(true)
@@ -68,6 +86,7 @@ const ChangeToday = (val: any) => {
   }
 }
 const handleCalendarOpen = (date: any) => {
+  emit('DateChangeFocus', !open.value)
   open.value = !open.value
   if (open.value == false) {
     if (date.length != 2) {
@@ -113,7 +132,7 @@ const isTwoDate = (date: any) => {
 </script>
 <template>
   <div>
-    <div class="ETD_title">{{ props.CalendarTitle }}</div>
+    <div class="ETD_title" v-if="props.CalendarTitle">{{ props.CalendarTitle }}</div>
     <a-range-picker
       separator="To"
       :showToday="false"
@@ -124,7 +143,7 @@ const isTwoDate = (date: any) => {
       :open="open"
       :disabled="Disabled"
       @change="changeRangeData"
-      :placeholder="['Start Time', 'End Time']"
+      :placeholder="isNeedFooter? ['Start Time', 'End Time'] : CreatePlaceholder"
       :format="userStore.dateFormat"
       valueFormat="MM/DD/YYYY"
       @openChange="handleCalendarOpen(ETDDate)"
@@ -138,7 +157,7 @@ const isTwoDate = (date: any) => {
           </svg>
         </span>
       </template>
-      <template #renderExtraFooter v-if="isShowExtra">
+      <template #renderExtraFooter v-if="isShowExtra && isNeedFooter">
         <div class="calender_flex">
           <div class="footer_left">
             <el-button class="el-button--noborder" @click="Earliest">Earliest Time</el-button>

+ 0 - 1
src/components/MoreFilters/src/components/SelectValue.vue

@@ -102,7 +102,6 @@ const TransportSearch = (visible: any) => {
   } else {
     dropdown1.value.handleOpen()
   }
-  checkedCount = []
 }
 const seeall = ref(false)
 const clickSeeAll = () => {

+ 11 - 1
src/components/VBreadcrumb/src/VBreadcrumb.vue

@@ -20,7 +20,17 @@ const jumpLink = (label: string, query: any) => {
     if(label == 'Monitoring Settings') {
       CancelRulesVisible.value = true
       monitoringQuery.value = query
-    } else {
+    } else if(label == 'Destination Delivery') {
+      router.push({
+        name: 'Destination Delivery',
+        query: query
+      })
+    } else if (label == 'Configurations'){
+      router.push({
+        name: 'Configurations',
+        query: query
+      })
+    }else {
       label &&
       router.push({
         name: label,

+ 28 - 1
src/components/VTag/src/VTag.vue

@@ -10,10 +10,15 @@ interface internalProps {
     | 'Arrived'
     | 'Completed'
     | 'Departed'
+    | 'Pending Approval'
+    | 'Approved'
+    | 'Rejected'
   large?: boolean
 }
 
 const mappingTable = new Map([
+  ['Approved', 'approved'],
+  ['Rejected', 'rejected'],
   ['Confirmed', 'confirmed'],
   ['Cancelled', 'cancelled'],
   ['Created', 'created'],
@@ -22,7 +27,8 @@ const mappingTable = new Map([
   ['Departure', 'departure'],
   ['Arrived', 'arrived'],
   ['Completed', 'completed'],
-  ['Departed', 'Departed']
+  ['Departed', 'Departed'],
+  ['Pending Approval', 'pending-approval']
 ])
 defineProps<internalProps>()
 </script>
@@ -122,6 +128,27 @@ defineProps<internalProps>()
       background-color: var(--color-tag-Departed);
     }
   }
+  &.v-tag__pending-approval {
+    background-color: var(--color-tag-unfinished-approval-bg);
+    color: var(--color-tag-unfinished-approval);
+    .dot {
+      background-color: var(--color-tag-unfinished-approval);
+    }
+  }
+  &.v-tag__approved {
+    background-color: var(--color-tag-approved-bg);
+    color: var(--color-tag-approved);
+    .dot {
+      background-color: var(--color-tag-approved);
+    }
+  }
+  &.v-tag__rejected {
+    background-color: var(--color-tag-rejected-bg);
+    color: var(--color-tag-rejected);
+    .dot {
+      background-color: var(--color-tag-rejected);
+    }
+  }
   & + .v-tag {
     margin-left: 8px;
   }

+ 29 - 0
src/router/index.ts

@@ -142,6 +142,35 @@ const router = createRouter({
           meta: {
             activeMenu: '/SystemSettings'
           }
+        },
+        {
+          path: '/destination-delivery',
+          name: 'Destination Delivery',
+          component: () => import('../views/DestinationDelivery')
+        },
+        {
+          path: '/destination-delivery/CreateNewBooking',
+          name: 'Create New Booking',
+          component: () => import('../views/DestinationDelivery/src/components/CreateNewBooking')
+        },
+        {
+          path: '/destination-delivery/ConfiguRations',
+          name: 'Configurations',
+          component: () => import('../views/DestinationDelivery/src/components/ConfiguRations')
+        },
+        {
+          path: '/destination-delivery/ConfiguRations/CreateNewRule',
+          name: 'Destination Create New Rule',
+          component: () => import('../views/DestinationDelivery/src/components/ConfiguRations/src/components/CreateNewRule.vue')
+        },
+        {
+          path: '/destination-delivery/modify-booking',
+          name: 'Modify Booking',
+          component: () => import('../views/DestinationDelivery/src/components/ModifyBooking'),
+          meta: {
+            breadName: 'Modify Booking',
+            activeMenu: '/destination-delivery'
+          }
         }
       ]
     }

+ 73 - 2
src/stores/modules/breadCrumb.ts

@@ -1,5 +1,4 @@
 import { defineStore } from 'pinia'
-import { collapseTextChangeRangesAcrossMultipleVersions } from 'typescript'
 
 interface Route {
   label: string
@@ -16,7 +15,10 @@ const whiteList = [
   'Add VGM',
   'Public Tracking Detail',
   'Create New Rule',
-  'System Message Detail'
+  'System Message Detail',
+  'Configurations',
+  'Create New Booking',
+  'Destination Create New Rule',
 ]
 
 export const useBreadCrumb = defineStore('breadCrumb', {
@@ -29,6 +31,20 @@ export const useBreadCrumb = defineStore('breadCrumb', {
       const index = this.routeList.findIndex((item) => item.label === toRoute.name)
       if (index !== -1) {
         this.routeList.splice(index + 1)
+        if (toRoute.name === 'Configurations') {
+          this.routeList = [
+            {
+              label: 'Destination Delivery',
+              path: '/destination-delivery',
+              query: ''
+            },
+            {
+              label: 'Configurations',
+              path: '/destination-delivery/ConfiguRations',
+              query: toRoute.query
+            }
+          ]
+        }
       } else if (toRoute.name === 'Public Tracking Detail') {
         this.routeList = [
           {
@@ -68,6 +84,61 @@ export const useBreadCrumb = defineStore('breadCrumb', {
             query: toRoute.query
           }
         ]
+      } else if (toRoute.name === 'Modify Booking') {
+        this.routeList = [
+          {
+            label: 'Destination Delivery',
+            path: '/destination-delivery',
+            query: ''
+          },
+          {
+            label: 'Modify Booking',
+            path: '/destination-delivery/modify-booking',
+            query: toRoute.query
+          }
+        ]
+      } else if (toRoute.name === 'Destination Create New Rule') {
+        let label = ''
+        if(toRoute.query.a != undefined) {
+          label = 'Modify Rule'
+        } else {
+          label = 'Create New Rule'
+        }
+        this.routeList = [
+          {
+            label: 'Destination Delivery',
+            path: '/destination-delivery',
+            query: ''
+          },
+          {
+            label: 'Configurations',
+            path: '/destination-delivery/ConfiguRations',
+            query: ''
+          },
+          {
+            label: label,
+            path: '/destination-delivery/CreateNewRule',
+            query: toRoute.query
+          }
+        ]
+      } else if (toRoute.name === 'Create New Booking') {let label = ''
+        if(toRoute.query.a != undefined) {
+          label = 'Modify Booking'
+        } else {
+          label = 'Create New Booking'
+        }
+        this.routeList = [
+          {
+            label: 'Destination Delivery',
+            path: '/destination-delivery',
+            query: ''
+          },
+          {
+            label: label,
+            path: '/destination-delivery/CreateNewBooking',
+            query: toRoute.query
+          }
+        ]
       } else if (toRoute.name && whiteList.includes(toRoute.name)) {
         this.routeList.push({
           label: toRoute?.meta?.breadName || toRoute.name,

+ 154 - 48
src/styles/Antdui.scss

@@ -7,7 +7,8 @@
   background-color: var(--management-bg-color);
   border: 1px solid var(--color-select-border);
 }
-.ant-picker:hover, .ant-picker-focused {
+.ant-picker:hover,
+.ant-picker-focused {
   border-color: var(--color-theme);
   box-shadow: none;
 }
@@ -15,71 +16,152 @@
   display: none;
 }
 .ant-picker-dropdown-range {
-  z-index: 9999 ;
+  z-index: 9999;
 }
 .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-today .ant-picker-cell-inner::before {
   border: none;
 }
-.ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-selected .ant-picker-cell-inner, .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-start .ant-picker-cell-inner, .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-end .ant-picker-cell-inner {
+.ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-selected .ant-picker-cell-inner,
+.ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-start .ant-picker-cell-inner,
+.ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-end .ant-picker-cell-inner {
   background-color: var(--color-theme);
-  color: #FFFFFF;
+  color: #ffffff;
 }
 .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-in-range::before {
   background-color: var(--color-orange-6);
 }
-.ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-start:not(.ant-picker-cell-range-start-single)::before, .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-end:not(.ant-picker-cell-range-end-single)::before {
+.ant-picker-dropdown
+  .ant-picker-cell-in-view.ant-picker-cell-range-start:not(
+    .ant-picker-cell-range-start-single
+  )::before,
+.ant-picker-dropdown
+  .ant-picker-cell-in-view.ant-picker-cell-range-end:not(
+    .ant-picker-cell-range-end-single
+  )::before {
   background-color: var(--color-orange-6);
 }
 .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-in-range {
   color: var(--color-theme);
 }
 .ant-picker-cell:hover:not(.ant-picker-cell-in-view).ant-picker-cell-inner,
-
-  .ant-picker-cell:hover:not(.ant-picker-cell-selected):not(.ant-picker-cell-range-start)
-
-    :not(.ant-picker-cell-range-end):not(.ant-picker-cell-range-hover-start):not(.ant-picker-cell-range-hover-end) {
-      background-color: var(--color-orange-6) !important;
-      color: var(--color-theme);
-  }
-  .ant-picker-dropdown .ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-start .ant-picker-cell-inner::after, .ant-picker-dropdown .ant-picker-date-panel .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-end .ant-picker-cell-inner::after {
-    background-color: var(--color-orange-6);
-  }
-  .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover::before, .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-start.ant-picker-cell-range-hover::before, .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-end.ant-picker-cell-range-hover::before, .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-start:not(.ant-picker-cell-range-start-single).ant-picker-cell-range-hover-start::before, .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-end:not(.ant-picker-cell-range-end-single).ant-picker-cell-range-hover-end::before, .ant-picker-panel>:not(.ant-picker-date-panel) .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-start::before, .ant-picker-panel>:not(.ant-picker-date-panel) .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-end::before {
-    background-color: var(--color-orange-6);
-  }
-  .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-hover-start:not(.ant-picker-cell-in-range):not(.ant-picker-cell-range-start):not(.ant-picker-cell-range-end)::after, .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-hover-end:not(.ant-picker-cell-in-range):not(.ant-picker-cell-range-start):not(.ant-picker-cell-range-end)::after, .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-hover-start.ant-picker-cell-range-start-single::after, .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-hover-start.ant-picker-cell-range-start.ant-picker-cell-range-end.ant-picker-cell-range-end-near-hover::after, .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-hover-end.ant-picker-cell-range-start.ant-picker-cell-range-end.ant-picker-cell-range-start-near-hover::after, .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-hover-end.ant-picker-cell-range-end-single::after, .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-hover:not(.ant-picker-cell-in-range)::after {
-    border-top: none;
-    border-bottom: none;
-  }
-  tr>.ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-hover:first-child::after, tr>.ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-hover-end:first-child::after, .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-start.ant-picker-cell-range-hover-edge-start.ant-picker-cell-range-hover-edge-start-near-range::after, .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-hover-edge-start:not(.ant-picker-cell-range-hover-edge-start-near-range)::after, .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-hover-start::after {
-    border-inline-start: none;
-  }
-  tr>.ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-hover:last-child::after, tr>.ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-hover-start:last-child::after, .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-end.ant-picker-cell-range-hover-edge-end.ant-picker-cell-range-hover-edge-end-near-range::after, .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-hover-edge-end:not(.ant-picker-cell-range-hover-edge-end-near-range)::after, .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-hover-end::after {
-    border-inline-end: none;
-  }
-  .ant-picker-dropdown .ant-picker-header-view button:hover {
-    color: var(--color-theme);
-  }
-  .ant-picker-dropdown .ant-picker-panel-container {
-    background-color: var(--management-bg-color);
-    border: 1px solid var(--border-color-2);
-  }
-  .ant-picker-footer {
-    border-top: 1px solid var(--border-color-2);
-  }
-  .ant-picker-dropdown .ant-picker-panel {
-    border: none;
-  }
-  .ant-picker-dropdown .ant-picker-panel-container .ant-picker-presets ul li:hover {
-    background: var(--color-orange-6);
-  }
- .ant-picker-dropdown .ant-picker-date-panel .ant-picker-content th {
+.ant-picker-cell:hover:not(.ant-picker-cell-selected):not(.ant-picker-cell-range-start)
+  :not(.ant-picker-cell-range-end):not(.ant-picker-cell-range-hover-start):not(
+    .ant-picker-cell-range-hover-end
+  ) {
+  background-color: var(--color-orange-6) !important;
+  color: var(--color-theme);
+}
+.ant-picker-dropdown
+  .ant-picker-date-panel
+  .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-start
+  .ant-picker-cell-inner::after,
+.ant-picker-dropdown
+  .ant-picker-date-panel
+  .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-end
+  .ant-picker-cell-inner::after {
+  background-color: var(--color-orange-6);
+}
+.ant-picker-dropdown
+  .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover::before,
+.ant-picker-dropdown
+  .ant-picker-cell-in-view.ant-picker-cell-range-start.ant-picker-cell-range-hover::before,
+.ant-picker-dropdown
+  .ant-picker-cell-in-view.ant-picker-cell-range-end.ant-picker-cell-range-hover::before,
+.ant-picker-dropdown
+  .ant-picker-cell-in-view.ant-picker-cell-range-start:not(
+    .ant-picker-cell-range-start-single
+  ).ant-picker-cell-range-hover-start::before,
+.ant-picker-dropdown
+  .ant-picker-cell-in-view.ant-picker-cell-range-end:not(
+    .ant-picker-cell-range-end-single
+  ).ant-picker-cell-range-hover-end::before,
+.ant-picker-panel
+  > :not(.ant-picker-date-panel)
+  .ant-picker-dropdown
+  .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-start::before,
+.ant-picker-panel
+  > :not(.ant-picker-date-panel)
+  .ant-picker-dropdown
+  .ant-picker-cell-in-view.ant-picker-cell-in-range.ant-picker-cell-range-hover-end::before {
+  background-color: var(--color-orange-6);
+}
+.ant-picker-dropdown
+  .ant-picker-cell-in-view.ant-picker-cell-range-hover-start:not(.ant-picker-cell-in-range):not(
+    .ant-picker-cell-range-start
+  ):not(.ant-picker-cell-range-end)::after,
+.ant-picker-dropdown
+  .ant-picker-cell-in-view.ant-picker-cell-range-hover-end:not(.ant-picker-cell-in-range):not(
+    .ant-picker-cell-range-start
+  ):not(.ant-picker-cell-range-end)::after,
+.ant-picker-dropdown
+  .ant-picker-cell-in-view.ant-picker-cell-range-hover-start.ant-picker-cell-range-start-single::after,
+.ant-picker-dropdown
+  .ant-picker-cell-in-view.ant-picker-cell-range-hover-start.ant-picker-cell-range-start.ant-picker-cell-range-end.ant-picker-cell-range-end-near-hover::after,
+.ant-picker-dropdown
+  .ant-picker-cell-in-view.ant-picker-cell-range-hover-end.ant-picker-cell-range-start.ant-picker-cell-range-end.ant-picker-cell-range-start-near-hover::after,
+.ant-picker-dropdown
+  .ant-picker-cell-in-view.ant-picker-cell-range-hover-end.ant-picker-cell-range-end-single::after,
+.ant-picker-dropdown
+  .ant-picker-cell-in-view.ant-picker-cell-range-hover:not(.ant-picker-cell-in-range)::after {
+  border-top: none;
+  border-bottom: none;
+}
+tr > .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-hover:first-child::after,
+tr
+  > .ant-picker-dropdown
+  .ant-picker-cell-in-view.ant-picker-cell-range-hover-end:first-child::after,
+.ant-picker-dropdown
+  .ant-picker-cell-in-view.ant-picker-cell-start.ant-picker-cell-range-hover-edge-start.ant-picker-cell-range-hover-edge-start-near-range::after,
+.ant-picker-dropdown
+  .ant-picker-cell-in-view.ant-picker-cell-range-hover-edge-start:not(
+    .ant-picker-cell-range-hover-edge-start-near-range
+  )::after,
+.ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-hover-start::after {
+  border-inline-start: none;
+}
+tr > .ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-hover:last-child::after,
+tr
+  > .ant-picker-dropdown
+  .ant-picker-cell-in-view.ant-picker-cell-range-hover-start:last-child::after,
+.ant-picker-dropdown
+  .ant-picker-cell-in-view.ant-picker-cell-end.ant-picker-cell-range-hover-edge-end.ant-picker-cell-range-hover-edge-end-near-range::after,
+.ant-picker-dropdown
+  .ant-picker-cell-in-view.ant-picker-cell-range-hover-edge-end:not(
+    .ant-picker-cell-range-hover-edge-end-near-range
+  )::after,
+.ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-hover-end::after {
+  border-inline-end: none;
+}
+.ant-picker-dropdown .ant-picker-header-view button:hover {
+  color: var(--color-theme);
+}
+.ant-picker-dropdown .ant-picker-panel-container {
+  background-color: var(--management-bg-color);
+  border: 1px solid var(--border-color-2);
+}
+.ant-picker-footer {
+  border-top: 1px solid var(--border-color-2);
+}
+.ant-picker-dropdown .ant-picker-panel {
+  border: none;
+}
+.ant-picker-dropdown .ant-picker-panel-container .ant-picker-presets ul li:hover {
+  background: var(--color-orange-6);
+}
+.ant-picker-dropdown .ant-picker-date-panel .ant-picker-content th {
   color: var(--color-neutral-2);
- }
-.ant-picker-dropdown .ant-picker-decade-panel,.ant-picker-dropdown .ant-picker-year-panel,.ant-picker-dropdown .ant-picker-quarter-panel,.ant-picker-dropdown .ant-picker-month-panel,.ant-picker-dropdown .ant-picker-week-panel,.ant-picker-dropdown .ant-picker-date-panel,.ant-picker-dropdown .ant-picker-time-panel {
+}
+.ant-picker-dropdown .ant-picker-decade-panel,
+.ant-picker-dropdown .ant-picker-year-panel,
+.ant-picker-dropdown .ant-picker-quarter-panel,
+.ant-picker-dropdown .ant-picker-month-panel,
+.ant-picker-dropdown .ant-picker-week-panel,
+.ant-picker-dropdown .ant-picker-date-panel,
+.ant-picker-dropdown .ant-picker-time-panel {
   border-left: 1px solid var(--border-color-2);
 }
-.ant-picker .ant-picker-input >input, .ant-picker .ant-picker-input >input::placeholder {
+.ant-picker .ant-picker-input > input,
+.ant-picker .ant-picker-input > input::placeholder {
   color: var(--color-neutral-1);
 }
 .ant-picker-dropdown .ant-picker-cell .ant-picker-cell-inner {
@@ -100,4 +182,28 @@
 }
 .ant-picker-dropdown.ant-picker-dropdown-placement-bottomLeft .ant-picker-range-arrow {
   display: none;
+}
+.ant-picker-input input::placeholder {
+  color: var(--color-neutral-3) !important;
+}
+.ant-picker-input input {
+  color: var(--color-neutral-1) !important;
+}
+.ant-picker-cell-inner {
+  display: flex !important;
+  align-items: center !important;
+  justify-content: center !important;
+  width: 32px !important;
+  height: 32px !important;
+  border-radius: 6px !important;
+}
+.ant-checkbox-checked .ant-checkbox-inner {
+  background-color: var(--color-theme) !important;
+  border-color: var(--color-theme) !important;
+}
+.ant-checkbox-wrapper:not(.ant-checkbox-wrapper-disabled):hover .ant-checkbox-inner, .ant-checkbox:not(.ant-checkbox-disabled):hover .ant-checkbox-inner {
+  border-color: var(--color-theme) !important;
+}
+.ant-checkbox-checked:after{
+  border-color: var(--color-theme) !important;
 }

+ 48 - 6
src/styles/elementui.scss

@@ -17,6 +17,7 @@
 
 .el-button.el-button--noborder--configuration {
   border: none;
+  background-color: transparent;
   span {
     color: var(--color-theme);
   }
@@ -29,6 +30,9 @@
     }
   }
 }
+.el-button.el-button--noborder--configuration.is-disabled {
+  opacity: 0.3;
+}
 
 button.el-button.el-button--text {
   height: 24px;
@@ -159,6 +163,19 @@ button.el-button--main.is-disabled {
     }
   }
 }
+.el-button.el-button--grey.is-disabled {
+  border: none;
+  background-color: var(--color-grey);
+  color: var(--color-neutral-1);
+  fill: var(--color-neutral-1);
+  &:hover {
+    background-color: var(--color-grey);
+    fill: var(--color-neutral-1);
+    span {
+      color: var(--color-neutral-1);
+    }
+  }
+}
 
 .el-button.el-button--blue {
   border: none;
@@ -533,6 +550,12 @@ div .el-select-dropdown__item.is-selected {
     color: var(--color-theme);
   }
 }
+div .custom-sheader-select .el-select-dropdown__item.is-selected {
+  span {
+    color: var(--color-neutral-1);
+    font-weight: 400;
+  }
+}
 div .el-select-dropdown__item.is-hovering {
   background-color: var(--border-hover-color);
 }
@@ -697,10 +720,12 @@ div .scoreDialog2 .el-dialog__body {
 }
 
 div .el-textarea__inner:hover {
-  box-shadow: 0 0 0 1px var(--color-theme);
+  box-shadow: 0 0 0 1px var(--color-theme) inset;
+  outline: none;
 }
 div .el-textarea__inner:focus {
-  box-shadow: 0 0 0 1px var(--color-theme);
+  box-shadow: 0 0 0 1px var(--color-theme) inset;
+  outline: none;
 }
 div .el-result__title {
   margin-top: 16px;
@@ -827,7 +852,8 @@ div .carousel .el-carousel__button {
 div .carousel .el-carousel__indicator.is-active button {
   background-color: var(--color-theme);
 }
-div .carousel .el-carousel__item--card, .el-carousel__item.is-animating {
+div .carousel .el-carousel__item--card,
+.el-carousel__item.is-animating {
   display: flex;
   align-items: center;
   justify-content: center;
@@ -836,7 +862,7 @@ div .carousel .el-carousel__item--card, .el-carousel__item.is-animating {
 div .carousel .el-carousel__arrow {
   opacity: 1;
   background-color: var(--color-carousel-card-bg);
-  box-shadow: 2px 2px 12px 0px rgba(0, 0, 0, 0.10);
+  box-shadow: 2px 2px 12px 0px rgba(0, 0, 0, 0.1);
 }
 div .carousel .el-carousel__arrow--left {
   left: 0;
@@ -856,7 +882,7 @@ div .carousel .el-carousel__arrow:hover {
   }
 }
 div .prompt-dialog {
-  min-height: 800px ;
+  min-height: 800px;
 }
 div .prompt-dialog-inner .el-dialog__header {
   padding: 0;
@@ -865,5 +891,21 @@ div .prompt-dialog-inner .el-dialog__header {
 div .prompt-dialog-inner .el-dialog__body {
   max-height: 720px;
   overflow-y: scroll;
-  line-height: 21px; 
+  line-height: 21px;
+}
+div .el-autocomplete-suggestion li.highlighted, .el-autocomplete-suggestion li {
+  padding: 0 !important;
+  &:hover { 
+    background-color: transparent !important;
+  }
+}
+div .no-data-item:hover {
+  &:hover {
+    background-color: var(--management-bg-color);
+  }
+}
+div .suggestion-item:hover {
+  &:hover {
+    background-color: var(--color-arrow-hoverL);
+  }
 }

+ 41 - 0
src/styles/theme.scss

@@ -14,6 +14,7 @@
   --color-white: #fff;
   --color-success: #00a870;
   --color-warning: #edb82f;
+  --color-warning-2: #E0A100;
   --color-danger: #c9353f;
   --color-grey: #f4f5f5;
 
@@ -72,6 +73,14 @@
   --color-tag-arrived-bg: #e7faf8;
   --color-tag-completed-bg: #e8fbe4;
   --color-tag-Departed-bg: #d9edfa;
+  --color-tag-unfinished-approval-bg: #fff4d1;
+  --color-tag-unfinished-approval: #e0a100;
+
+  --color-tag-approved-bg: rgb(232, 251, 228);
+  --color-tag-approved: rgb(91, 180, 98);
+
+  --color-tag-rejected-bg: rgba(201, 53, 63,0.2);
+  --color-tag-rejected: rgb(201, 53, 63);
 
   --color-border: #eaebed;
   --color-select-border: #eaebed;
@@ -317,6 +326,19 @@
   --color-prompt-diaolog-bg: #f8f9fd;
   --color-prompt-disabled-bg: #f4f4f4;
   --color-prompt-disabled-border: rgba(234, 235, 237, 0.3);
+
+  --color-card-icon-box-bg: #f0f1fb;
+  --color-card-number-cancelled: #243041;
+  --color-steps-unfinished-line: #b5b9bf;
+  --color-steps-current-icon-color: #e0a100;
+  --color-steps-current-icon-bg: #fff4d1;
+  --color-steps-rejected-bg: #fedcde;
+  --color-steps-approved-bg: #e8fbe4;
+  --color-steps-cancelled-bg: #ebeef1;
+  --color-booking-info-linear-bg: linear-gradient(90deg, #c4c9ee 0%, #e8e8ff 49.52%, #bfe1ff 100%);
+  --color-process-data-value-bg: #e8ebef;
+
+  --color-delivery-date-cell-hover-bg: #fff1e6;
   --color-tour-popover-bg: #fff;
   --color-tour-prev-btn-border: #eaebed;
   --color-tour-next-btn-bg: #2b2f36;
@@ -327,6 +349,7 @@
 
   --color-guide-icon-bg: rgba(237, 237, 237, 0.6);
   --color-guide-icon-hover-bg: rgba(237, 237, 237, 0.45);
+  --color--process-data-tips-bg: #e8ebef;
 }
 
 :root.dark {
@@ -522,6 +545,23 @@
     }
   }
 
+  --color-tag-unfinished-approval-bg: rgba(224, 161, 0, 0.2);
+  --color-tag-approved-bg: rgba(91, 180, 98, 0.2);
+  --color-tag-rejected-bg: rgb(255, 220, 222);
+
+  --color-card-icon-box-bg: #4f535c;
+  --color-card-number-cancelled: #babcc0;
+  --color-steps-unfinished-line: #6a6d73;
+  --color-steps-current-icon-color: #e0a100;
+  --color-steps-current-icon-bg: #534b30;
+  --color-steps-rejected-bg: #4f353d;
+  --color-steps-approved-bg: #394e44;
+  --color-steps-cancelled: #c2c4c7;
+  --color-steps-cancelled-bg: rgba(240, 241, 243, 0.2);
+  --color-booking-info-linear-bg: linear-gradient(90deg, #636db7 0%, #515195 49.52%, #7b9bc9 100%);
+  --color-process-data-value-bg: #4f5760;
+
+  --color-delivery-date-cell-hover-bg: rgba(255, 117, 0, 0.2);
   --color-tour-popover-bg: #cf5f00;
   --color-tour-prev-btn-border: #e6c5aa;
   --color-tour-next-btn-bg: #f0f1f3;
@@ -532,4 +572,5 @@
 
   --color-guide-icon-bg: rgba(237, 237, 237, 0.1);
   --color-guide-icon-hover-bg: rgba(237, 237, 237, 0.15);
+  --color--process-data-tips-bg: #4f5760;
 }

+ 3 - 0
src/styles/vxeTable.scss

@@ -181,3 +181,6 @@ div.vxe-table--context-menu-wrapper {
 .vxe-tooltip--content {
   color: #f0f1f3;
 }
+.vxe-tooltip--wrapper.theme--dark {
+  z-index: 9999 !important;
+}

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

@@ -129,7 +129,7 @@ const DateChange = (date: any) => {
   border-bottom: 1px solid var(--color-border);
   font-size: var(--font-size-6);
   font-weight: 700;
-  padding-left: 23.32px;
+  padding-left: 24px;
   align-items: center;
 }
 .heaer_top {

+ 1 - 1
src/views/ChatLog/src/ChatLog.vue

@@ -210,7 +210,7 @@ const DateChange = (date: any) => {
   border-bottom: 1px solid var(--color-border);
   font-size: var(--font-size-6);
   font-weight: 700;
-  padding-left: 23.32px;
+  padding-left: 24px;
   align-items: center;
 }
 .heaer_top {

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

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

+ 316 - 0
src/views/DestinationDelivery/src/DestinationDelivery.vue

@@ -0,0 +1,316 @@
+<script lang="ts" setup>
+import { useCalculatingHeight } from '@/hooks/calculatingHeight'
+import TableView from './components/TableView'
+import DeliveryDate from './components/DeliveryDate.vue'
+import { useRouter } from 'vue-router'
+
+const router = useRouter()
+const filterRef: Ref<HTMLElement | null> = ref(null)
+const containerHeight = useCalculatingHeight(document.documentElement, 376, [filterRef])
+const queryData = ref({
+  text_search: '',
+  created_time_start: '',
+  created_time_end: '',
+  delivery_mode: '',
+  delivery_date_start: '',
+  delivery_date_end: '',
+  filterTag: ['ALL']
+})
+
+const modeList = [
+  {
+    label: 'Truck',
+    value: 'Truck'
+  },
+  {
+    label: 'Rail',
+    value: 'Rail'
+  }
+]
+
+const numberCards = ref([
+  {
+    label: 'Total Bookings',
+    key: 'ALL',
+    value: 0,
+    color: '#2b2f36'
+  },
+  {
+    label: 'Pending Approval',
+    value: 0,
+    color: '#edb82f',
+    icon: 'icon_time_b'
+  },
+  {
+    label: 'Approved',
+    value: 0,
+    color: '#00a870',
+    icon: 'icon_confirm_b'
+  },
+  {
+    label: 'Rejected',
+    value: 0,
+    color: '#c9353f',
+    icon: 'icon_reject_b'
+  },
+  {
+    label: 'Cancelled',
+    value: 0,
+    color: '#243041',
+    icon: 'icon_cancelled_b'
+  }
+])
+const clickCard = (filterTagItem: string) => {
+  queryData.value.filterTag.pop()
+  queryData.value.filterTag.push(filterTagItem)
+  handleSearch()
+}
+
+const DateChange = (date: any) => {
+  queryData.value.created_time_start = date[0]
+  queryData.value.created_time_end = date[1]
+}
+const deliveryDataChange = (date) => {
+  queryData.value.delivery_date_start = date[0]
+  queryData.value.delivery_date_end = date[1]
+}
+
+const handleConfigurations = () => {
+  router.push({ name: 'Configurations' })
+}
+const handleCreate = () => {
+  router.push({ name: 'Create New Booking' })
+}
+
+const DateStart = ref([])
+const tableRef = ref()
+
+const setNumberCards = (cards) => {
+  numberCards.value = cards
+}
+
+const handleSearch = () => {
+  tableRef.value.SearchOperationLog()
+}
+</script>
+<template>
+  <div class="destination-delivery">
+    <div class="header">
+      <span>Destination Delivery</span>
+      <div class="operator">
+        <el-button
+          style="height: 40px"
+          type="default"
+          @click="handleConfigurations"
+          v-if="tableRef?.isEmployeeRole === true"
+        >
+          <span style="margin-right: 4px" class="font_family icon-icon_configurations_b"></span>
+          <span style="font-weight: 400">Configurations</span></el-button
+        >
+        <el-button
+          style="height: 38px"
+          class="el-button--main el-button--pain-theme"
+          @click="handleCreate"
+          v-if="tableRef?.isEmployeeRole === false"
+        >
+          <span style="margin-right: 4px" class="font_family icon-icon_add_b"></span>
+          <span style="font-weight: 400">Create New Booking</span>
+        </el-button>
+      </div>
+    </div>
+    <div class="display">
+      <div class="header_top">
+        <div class="date-tips_filter filter-item">
+          <span class="label">Delivery Date:</span>
+          <DeliveryDate @DateChange="deliveryDataChange" :Date="DateStart" />
+        </div>
+
+        <div class="tips_filter filter-item">
+          <span class="label">Delivery Mode:</span>
+          <el-select v-model="queryData.delivery_mode" placeholder="" clearable>
+            <el-option
+              v-for="item in modeList"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </div>
+        <div class="date-tips_filter filter-item">
+          <span class="label">Creation Date</span>
+          <CalendarDate @DateChange="DateChange"></CalendarDate>
+        </div>
+        <div class="input-tips_filter filter-item">
+          <el-input
+            placeholder="Search Question Booking No、HBOL No、MBL No、Container No、Consignee"
+            v-model="queryData.text_search"
+            class="log_input"
+          >
+            <template #prefix>
+              <span class="iconfont_icon">
+                <svg class="iconfont icon_dark" aria-hidden="true">
+                  <use xlink:href="#icon-icon_search_b"></use>
+                </svg>
+              </span>
+            </template>
+          </el-input>
+        </div>
+        <el-button class="el-button--dark" @click="handleSearch">Search</el-button>
+      </div>
+      <div class="number-cards">
+        <div
+          class="card"
+          :class="{
+            'is-active':
+              queryData.filterTag.includes(item.key as string | undefined) ||
+              queryData.filterTag.includes(item.label)
+          }"
+          @click="clickCard((item.key || item.label) as string)"
+          v-for="(item, index) in numberCards"
+          :key="index"
+        >
+          <div class="card-label">{{ item.label }}</div>
+          <div
+            class="card-value"
+            :style="{
+              color: item.label === 'Cancelled' ? `var(--color-card-number-cancelled)` : item.color
+            }"
+          >
+            {{ item.value }}
+          </div>
+          <div class="icon-box" v-if="item.icon">
+            <span class="font_family" :class="'icon-' + item.icon"></span>
+          </div>
+        </div>
+      </div>
+    </div>
+    <TableView
+      @get-number-cards="setNumberCards"
+      :height="containerHeight"
+      :queryData="queryData"
+      ref="tableRef"
+    ></TableView>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.header {
+  display: flex;
+  height: 68px;
+  border-bottom: 1px solid var(--color-border);
+  font-size: var(--font-size-6);
+  font-weight: 700;
+  padding: 0 24px;
+  align-items: center;
+  justify-content: space-between;
+}
+.header_top {
+  margin-bottom: 8px;
+  padding-right: 8px;
+  display: flex;
+  align-items: flex-end;
+}
+.number-cards {
+  display: flex;
+  justify-content: space-between;
+  margin: 8px 0;
+  gap: 8px;
+  .card {
+    position: relative;
+    flex: 1;
+    height: 80px;
+    padding: 12px 16px;
+    background-color: var(--color-email-bg);
+    border-radius: 12px;
+    & > .icon-box {
+      position: absolute;
+      right: 16px;
+      top: 12px;
+      width: 24px;
+      height: 24px;
+      border-radius: 6px;
+      background-color: var(--color-card-icon-box-bg);
+      text-align: center;
+      line-height: 24px;
+    }
+    &.is-active {
+      background-color: var(--color-mune-active-bg);
+      .card-label,
+      .card-value {
+        color: var(--color-theme) !important;
+      }
+      .icon-box {
+        background-color: rgba(#ff7500, 0.1);
+        span {
+          color: var(--color-theme);
+        }
+      }
+    }
+
+    .card-value {
+      margin-top: 4px;
+      font-size: 32px;
+      font-weight: 700;
+    }
+  }
+}
+
+.display {
+  border: 1px solid var(--color-border);
+  border-width: 0 0 1px 0;
+  padding: 0 24px;
+}
+:deep(.el-select__placeholder.is-transparent span) {
+  color: var(--tag-info-text-color) !important;
+}
+:deep(.ETD_title) {
+  margin-bottom: 0;
+}
+:deep(.ant-picker-range) {
+  width: 250px !important;
+  height: 32px;
+  background-color: var(--color-mode) !important;
+}
+.filter-item {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: flex-end;
+  justify-content: flex-end;
+  margin-right: 8px;
+  .label {
+    align-self: flex-start;
+    font-size: 12px;
+    margin-bottom: 3px;
+  }
+}
+.tips_filter {
+  min-width: 170px;
+  max-width: 220px;
+}
+.input-tips_filter {
+  max-width: 540px;
+  :deep(.el-input__wrapper) {
+    height: 32px;
+    input {
+      width: 100%;
+      white-space: nowrap; /* 防止换行 */
+      overflow: hidden; /* 超出部分隐藏 */
+      text-overflow: ellipsis; /* 超出部分显示为省略号 */
+    }
+  }
+}
+.date-tips_filter {
+  max-width: 250px;
+  height: 56px;
+}
+
+.destination-delivery {
+  position: relative;
+  background-color: var(--color-mode);
+}
+:deep(.log_input .el-input__wrapper) {
+  box-shadow: 0 0 0 1px var(--color-select-border);
+  border-radius: 6px;
+}
+</style>

+ 1 - 0
src/views/DestinationDelivery/src/components/ConfiguRations/index.ts

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

+ 95 - 0
src/views/DestinationDelivery/src/components/ConfiguRations/src/ConfiguRations.vue

@@ -0,0 +1,95 @@
+<script setup lang="ts">
+import ConfigurationsTable from './components/ConfigurationsTable.vue'
+import { useCalculatingHeight } from '@/hooks/calculatingHeight'
+import { useRouter } from 'vue-router'
+
+const filterRef: Ref<HTMLElement | null> = ref(null)
+  const router = useRouter()
+
+const AddRulesTableColumns = ref([
+  {
+    field: 'country',
+    title: 'Country',
+    type: 'link',
+    width: '10%',
+    formatter: ''
+  },
+  {
+    field: 'station',
+    title: 'Stations',
+    type: 'normal',
+    width: '20%',
+    formatter: ''
+  },{
+    field: 'booking_window_desc',
+    title: 'Booking Window',
+    type: 'normal',
+    formatter: ''
+  },
+  {
+    field: 'recommended_delivery_date_desc',
+    title: 'Reecommended Delivery Date',
+    type: 'normal',
+    formatter: ''
+  }
+])
+const containerHeight = useCalculatingHeight(document.documentElement, 290, [filterRef])
+
+const tabledatalength = ref()
+const gettabledatalength = (val: any) => {
+  tabledatalength.value = val
+}
+
+// 跳转Create New Rule页面
+const ToCreateRule = () => {
+  router.push({
+    path: '/destination-delivery/ConfiguRations/CreateNewRule',
+    query: {}
+  })
+}
+</script>
+
+<template>
+  <div>
+    <div class="Title">Configuration</div>
+    <div class="AddRules">
+      <div class="monitoring_flex">
+        <div class="subscribedTitle">Added Rules</div>
+        <el-button
+          class="el-button--main"
+          style="height: 40px"
+          v-if="tabledatalength != 0 && tabledatalength != null"
+          @click="ToCreateRule"
+          >+ Add Rule</el-button
+        >
+      </div>
+      <ConfigurationsTable :height="containerHeight" :ColumnsList="AddRulesTableColumns" @gettabledatalength="gettabledatalength"></ConfigurationsTable>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.Title {
+  display: flex;
+  height: 68px;
+  border: 1px solid var(--color-border);
+  border-top: none;
+  border-width: 1px 0 1px 0;
+  font-size: var(--font-size-6);
+  font-weight: 700;
+  padding: 0 24px;
+  align-items: center;
+  justify-content: space-between;
+}
+.AddRules {
+  font-size: 18px;
+  font-weight: 700;
+  padding: 37px 0 13px 24px;
+}
+.monitoring_flex {
+  display: flex;
+  justify-content: space-between;
+  padding-right: 24px;
+  align-items: end;
+}
+</style>

+ 248 - 0
src/views/DestinationDelivery/src/components/ConfiguRations/src/components/ConfigurationsTable.vue

@@ -0,0 +1,248 @@
+<script setup lang="ts">
+import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
+import { useRowClickStyle } from '@/hooks/rowClickStyle'
+import { formatNumber } from '@/utils/tools'
+import { ref, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import DefaultConfiguration from '../images/default_configuration@2x.png'
+
+const router = useRouter()
+
+interface ColumnConfig {
+  field: string
+  title: string
+  type: string
+  width?: number
+  formatter?: string
+}
+const props = defineProps<{
+  ColumnsList: ColumnConfig[]
+  height: number
+}>()
+
+const columnstest = ref(props.ColumnsList)
+
+const tableData = ref<VxeGridProps<any>>({
+  border: true,
+  round: true,
+  columns: [],
+  data: [],
+  scrollY: { enabled: true, oSize: 20, gt: 30 },
+  stripe: true,
+  emptyText: ' ',
+  showHeaderOverflow: true,
+  showOverflow: true,
+  headerRowStyle: {
+    backgroundColor: 'var(--color-table-header-bg)'
+  },
+  columnConfig: { resizable: true, useKey: true },
+  rowConfig: { isHover: true }
+})
+
+const tableRef = ref<VxeGridInstance | null>(null)
+const pageInfo = ref({ pageNo: 1, pageSize: 15, total: 0 })
+
+const handleColumns = (columns: any) => {
+  const newColumns = columns.map((item: any) => {
+    let curColumn: any = {
+      title: item.title,
+      field: item.field,
+      width: item.width
+    }
+    // 格式化
+    if (item.type === 'link') {
+      curColumn = {
+        ...curColumn,
+        slots: { default: 'countryNo' }
+      }
+    }
+    return curColumn
+  })
+  return newColumns
+}
+// 获取表格列
+const getTableColumns = async () => {
+  tableData.value.columns = handleColumns(columnstest.value)
+  tableData.value.columns?.push({
+    title: 'Operation',
+    fixed: 'left',
+    width: 100,
+    slots: { default: 'action' }
+  })
+}
+// 获取表格数据
+const getTableData = (isPageChange?: boolean) => {
+  $api
+    .getConfigurationList({
+      cp: pageInfo.value.pageNo,
+      ps: pageInfo.value.pageSize,
+      rc: isPageChange ? pageInfo.value.total : -1
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        pageInfo.value.total = Number(res.data.rc)
+        pageInfo.value.pageNo = res.data.cp
+        pageInfo.value.pageSize = res.data.ps
+        tableData.value.data = res.data.searchData
+        emits('gettabledatalength', res.data.searchData.length)
+      }
+    })
+}
+
+// 实现行点击样式
+useRowClickStyle(tableRef)
+
+const emits = defineEmits(['gettabledatalength'])
+// 点击删除
+const handleDelete = (row: any) => {
+  row.visible = true
+}
+
+const deleteMoniTable = (row: any) => {
+  row.visible = false
+  $api
+  .deleteConfigurationList({
+    a: row._serial_no
+  })
+  .then((res: any) => {
+    if (res.code === 200) {
+      tableData.value.data = tableData.value.data?.filter((item) => item._serial_no !== row._serial_no)
+      emits('gettabledatalength', tableData.value.data?.length)
+    }
+  })
+}
+
+// 编辑表格数据
+const handleEdit = (row: any) => {
+  router.push({
+    path: '/destination-delivery/ConfiguRations/CreateNewRule',
+    query: {a: row._serial_no}
+  })
+}
+// 添加新规则
+const clickAddNewRule = () => {
+  router.push('/destination-delivery/ConfiguRations/CreateNewRule')
+}
+
+onMounted(() => {
+  getTableColumns()
+  getTableData()
+})
+</script>
+
+<template>
+  <div class="SettingTable">
+    <vxe-grid
+      ref="tableRef"
+      :style="{ border: 'none'}"
+      v-bind="tableData"
+      :height="props.height"
+      @cell-dblclick="({ row }) => handleEdit(row)"
+    >
+      <!-- 空数据时的插槽 -->
+      <template #empty>
+        <div>
+          <img :src="DefaultConfiguration" width="100" />
+          <div class="empty-text">
+            Configure available destination delivery regions and time slots.
+          </div>
+          <el-button class="el-button--main" style="width: 117px; height: 40px;" @click="clickAddNewRule">+ Add Rule</el-button>
+        </div>
+      </template>
+      <!-- Tracking No字段的插槽 -->
+      <template #countryNo="{ row, column }">
+        <span
+          style="color: var(--color-theme)"
+        >
+          {{ row[column.field] }}
+        </span>
+      </template>
+      <template #action="{ row }">
+        <el-button class="el-button--blue" style="height: 24px" @click="handleEdit(row)">
+          <span class="font_family icon-icon_edit_b"></span>
+        </el-button>
+        <el-popover trigger="click" :visible="row.visible" placement="left" :width="480">
+          <div class="delete_title">
+            <span class="font_family icon_alert icon-icon_tipsfilled_b"></span>
+            Delete Rule
+          </div>
+          <p class="delete_content">Are you sure to delete this notification event?</p>
+          <div style="text-align: right; margin: 0; padding: 8px">
+            <el-button style="width: 100px" class="el-button--default" @click="row.visible = false"
+              >cancel</el-button
+            >
+            <el-button style="width: 100px" type="warning" @click="deleteMoniTable(row)">
+              OK
+            </el-button>
+          </div>
+          <template #reference>
+            <el-button @click="handleDelete(row)" class="el-button--blue" style="height: 24px">
+              <span class="font_family icon-icon_delete_b"></span>
+            </el-button>
+          </template>
+        </el-popover>
+      </template>
+    </vxe-grid>
+  </div>
+  <div class="pagination">
+    <span>Total {{ formatNumber(pageInfo.total) }}</span>
+    <el-pagination
+      v-model:current-page="pageInfo.pageNo"
+      v-model:page-size="pageInfo.pageSize"
+      layout="prev, pager, next"
+      :total="pageInfo.total"
+      :pager-count="5"
+      @size-change="getTableData(true)"
+      @current-change="getTableData(true)"
+      background
+    />
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.SettingTable {
+  margin: 13px 24px 0 0;
+  font-weight: 400;
+}
+.icon-icon_delete_b::before {
+  color: var(--color-btn-danger-bg);
+}
+.icon_alert::before {
+  color: var(--color-btn-warning-bg);
+}
+.delete_title {
+  font-size: 18px;
+  font-weight: 700;
+  padding: 20px 16px;
+  color: var(--color-neutral-1);
+}
+.delete_content {
+  font-size: 14px;
+  font-weight: 400;
+  color: var(--color-neutral-1);
+  padding: 15px 0 33px 37px;
+}
+.empty-text {
+  margin: 8px 0;
+  color: var(--color-neutral-2);
+}
+.pagination {
+  display: flex;
+  justify-content: space-between;
+  font-weight: 400;
+  font-size: 15px;
+  align-items: center;
+  margin-right: 24px;
+  border: 1px solid var(--color-border);
+  border-top: none;
+  padding: 4px 8px;
+  border-radius: 0 0 6px 6px;
+}
+:deep(.el-icon svg) {
+  width: 1em !important;
+}
+
+:deep(.vxe-table--empty-placeholder) {
+  height: 500px !important;
+}
+</style>

+ 583 - 0
src/views/DestinationDelivery/src/components/ConfiguRations/src/components/CreateNewRule.vue

@@ -0,0 +1,583 @@
+<script setup lang="ts">
+import SelectStation from './SelectStation.vue'
+import SetBookingWindow from './SetBookingWindow.vue'
+import RecommendDate from './RecommendDate.vue'
+import { useRouter } from 'vue-router'
+import submitsucessful from '../images/icon_success_big@2x.png'
+
+const router = useRouter()
+const { currentRoute } = router
+const { value } = currentRoute
+const { query } = value
+const { a } = query
+
+const activeRules = ref(['SelectStation', 'SelectBooking', 'KLNPLC', 'RecommendDeliveryDate'])
+const IsFirstActive = ref(true)
+const selectedCountry = ref('')
+const windowRadio = ref()
+const setbookingdata = ref({})
+const recommendata = ref({})
+const recommendRadio = ref()
+const windowBeforeDays = ref('')
+const windowAfterDays = ref('')
+const recommendCheckedList = ref([])
+const recommendCheckedAirList = ref([])
+const recommendCheckedSeaList = ref([])
+const IsTwoActive = ref(true)
+const IsFourActive = ref(true)
+const IsThreeActive = ref(true)
+const CancelRulesVisible = ref(false)
+const SaveedVisible = ref(false)
+const UnableSaveVisible = ref(false)
+const missingmessage = ref('')
+const KLNPLCvalue = ref('')
+interface KLNItem {
+  value: string
+  label: string
+}
+
+const KLNLists = ref<KLNItem[]>([])
+const countryCheckedList = ref([])
+const CountryCheckboxList = ref([])
+
+// 页面初始化
+const InitRuleData = () => {
+  if ( a!= undefined ) {
+    $api
+    .InitCreateRule({
+      a: a
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        const { returnData } = res.data
+        KLNPLCvalue.value = returnData.kln_pic
+        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
+      }
+    })
+  }
+}
+
+
+const CreateRuleDisabled = computed(() => {
+  // 1. 检查基本条件是否满足
+  if (
+    countryCheckedList.value.length === 0 ||
+    selectedCountry.value === '' ||
+    windowRadio.value === undefined ||
+    recommendRadio.value === undefined ||
+    KLNPLCvalue.value === '' || 
+    KLNPLCvalue.value === '无搜索结果'
+  ) {
+    return true;
+  }
+
+  // 2. 处理时间窗口条件
+  if (windowRadio.value !== 1) {
+    // 当 windowRadio 不为 1 时,需要验证时间窗口字段
+    if (windowBeforeDays.value === '' || windowAfterDays.value === '') {
+      return true;
+    }
+  }
+
+  // 3. 处理推荐日期条件
+  if (recommendRadio.value !== 1) {
+    // 3.1 确保至少选择了一个运输方式
+    if (recommendCheckedList.value.length === 0) {
+      return true;
+    }
+
+    // 3.2 验证航空规则(如果选择了 Air)
+    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;
+    }
+
+    // 3.3 验证海运规则(如果选择了 Sea)
+    if (recommendCheckedList.value.includes('Sea')) {
+      const isSeaValid = recommendCheckedSeaList.value.every(item => 
+        item.ports.length > 0 && 
+        item.carrier.length > 0 && 
+        item.recommended_delivery_from != '' && 
+        item.recommended_delivery_to != ''
+      );
+      
+      if (!isSeaValid) return true;
+    }
+  }
+  // 4. 所有条件都满足,返回 false(不禁用)
+  return false;
+});
+// select country
+const handleClickSelectCountry = (val:any) =>{
+  selectedCountry.value = val
+  countryCheckedList.value = []
+  getKLNList()
+}
+// select station list
+const handleChangeStation = (val:any) =>{
+  countryCheckedList.value = val
+  getKLNList()
+}
+
+// select booking window
+const bookingWindow = ref('')
+const bookingdetail = ref('')
+const changeBookingWindow = (radio: number, beforedays:any, afterdays:any) => {
+  windowRadio.value = radio
+  windowBeforeDays.value = beforedays
+  windowAfterDays.value = afterdays
+}
+
+// select recommend date
+const recommendDelivery = ref('')
+const recommenddetail = ref('')
+const checkRecommend = (checked: any, airlist: any, sealist: any, radio: number) => {
+  recommenddetail.value = ''
+  recommendCheckedList.value = checked
+  recommendCheckedAirList.value = airlist
+  recommendCheckedSeaList.value = sealist
+  recommendRadio.value = radio
+}
+
+// 获取KLN列表
+const getKLNList = (): Promise<KLNItem[]> => {
+  return new Promise((resolve) => {
+    $api.getKLNEmployeeList({ 
+      term: '',
+      station: countryCheckedList.value
+    })
+      .then((res: any) => {
+        if (res.code === 200) {
+          KLNLists.value = res.data
+        } else {
+          resolve([]); // 失败返回空数组
+        }
+      })
+      .catch(() => resolve([])); // 异常兜底
+  });
+};
+// 自动查询KLN
+const loading = ref(false)
+const options = ref<KLNItem[]>([])
+const querySearchAsync = (query: string) => {
+  if (query) {
+    loading.value = true
+    setTimeout(() => {
+      loading.value = false
+      options.value = KLNLists.value.filter((item) => {
+        return item.label.toLowerCase().includes(query.toLowerCase())
+      })
+    }, 1000)
+  } else {
+    options.value = []
+  }
+}
+// 保存
+const handleSubmitRule = () => {
+  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) {
+      return rest
+    } else {
+      return []
+    }
+  })
+  let airData = []
+  let airlistInfo = {}
+  let mergeData = []
+  if(windowRadio.value == 1) {
+    bookingWindow.value = 'No_Restrictions'
+    bookingdetail.value = 'No Specific time restrictions for creating booking'
+  } 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) {
+    bookingWindow.value = 'Restrictions_ETA_ATA'
+    bookingdetail.value = 'ETA/ATA: ' + windowBeforeDays.value + ' days before to ' + windowAfterDays.value + ' days after'
+  }
+  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'
+      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;' 
+      seaList.forEach((item) => {
+        item.ports = item.ports.join(',')
+        item.carrier = item.carrier.join(',')
+      })
+      mergeData = [...mergeData , ...seaList]
+    }
+  }
+  airData = Object.keys(seaList?.[0])
+  airData.forEach((item) => {
+    Object.assign(airlistInfo, {
+      [item]: mergeData.map((row) => row[item])
+    })
+  })
+  $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>
+  <div>
+    <div class="Title">
+      <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">
+          <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">
+          <span
+            style="
+              display: inline-block;
+              margin-top: -4px;
+              margin-right: 4px;
+              transform: rotate(-60deg);
+            "
+            class="font_family icon-icon_submit_b"
+          ></span>
+          <span style="font-weight: 400">Submit</span>
+        </el-button>
+        <!-- 取消保存 -->
+        <el-dialog v-model="CancelRulesVisible" width="480">
+          <div style="font-weight: 400">You have unsaved changes.</div>
+          <div style="font-weight: 400">Are you sure you want to leave this page?</div>
+          <template #footer>
+            <div class="dialog-footer">
+              <el-button type="default" @click="CancelRulesVisible = false" style="width: 100px"
+                >Cancel</el-button
+              >
+              <el-button class="el-button--warning" @click="router.back()" style="width: 100px">
+                OK
+              </el-button>
+            </div>
+          </template>
+          <template #header>
+            <div class="cancel_header">
+              <span class="iconfont_icon iconfont_warning">
+                <svg class="iconfont icon_warning" aria-hidden="true">
+                  <use xlink:href="#icon-icon_tipsfilled_b"></use>
+                </svg>
+              </span>
+              Unsaved Changes
+            </div>
+          </template>
+        </el-dialog>
+        <!-- 保存失败 -->
+        <el-dialog v-model="UnableSaveVisible" width="480">
+          <div>{{ missingmessage }}</div>
+          <div>Please complete all required fields.</div>
+          <template #footer>
+            <div class="dialog-footer">
+              <el-button
+                class="el-button--danger"
+                @click="UnableSaveVisible = false"
+                style="width: 100px"
+              >
+                OK
+              </el-button>
+            </div>
+          </template>
+          <template #header>
+            <div class="cancel_header">
+              <span class="iconfont_icon iconfont_warning">
+                <svg class="iconfont icon_danger" aria-hidden="true">
+                  <use xlink:href="#icon-icon_fail_fill_b"></use>
+                </svg>
+              </span>
+              Unable to Save
+            </div>
+          </template>
+        </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; margin-top: 20px">Saved successfully</div>
+        </el-dialog>
+      </div>
+    </div>
+    <div class="setting-content">
+      <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
+              @handleClickSelectCountry="handleClickSelectCountry"
+              @handleChangeStation="handleChangeStation"
+              :CheckboxList="CountryCheckboxList"
+              :CheckedList="countryCheckedList"
+              :SelectCountry="selectedCountry"
+              ></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>
+            </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>
+            </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 PLC
+              </div>
+            </template>
+            <div>
+              <el-select
+                v-model="KLNPLCvalue"
+                filterable
+                remote
+                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>
+</template>
+
+<style lang="scss" scoped>
+.Title {
+  display: flex;
+  height: 68px;
+  border: 1px solid var(--color-border);
+  border-top: none;
+  border-width: 1px 0 1px 0;
+  font-size: var(--font-size-6);
+  font-weight: 700;
+  padding: 0 24px;
+  align-items: center;
+  justify-content: space-between;
+}
+.operator {
+  display: flex;
+  align-items: center;
+  & > .el-button--text {
+    height: 32px;
+    font-size: 14px;
+    font-weight: 400;
+    span {
+      color: var(--color-theme);
+    }
+  }
+}
+.setting-content {
+  margin: 16px 24px 48px 24px;
+  padding-bottom: 24px;
+  border: 1px solid var(--color-system-border-1);
+  border-radius: 12px;
+}
+.setting-top-title {
+  background-color: var(--color-shipment-status-header-bg);
+  height: 48px;
+  padding: 0 16px;
+  display: flex;
+  align-items: center;
+  border-radius: 12px 12px 0 0;
+}
+.Rules_Title {
+  font-size: 14px;
+  font-weight: 700;
+}
+.stars_red {
+  color: var(--color-danger);
+}
+:deep(.el-collapse) {
+  border: none;
+  padding: 0 16px;
+}
+:deep(.el-collapse-item__header) {
+  border: none !important;
+}
+:deep(.el-collapse-item__arrow) {
+  width: 0 !important;
+  height: 0 !important;
+}
+:deep(.el-collapse-item__header.is-active) {
+  background-color: transparent !important;
+  border-color: transparent !important;
+}
+:deep(.el-collapse-item__arrow.is-active) {
+  transform: rotate(-180deg) !important;
+}
+:deep(.el-collapse-item__wrap) {
+  border-bottom: none;
+}
+.iconfont_icon {
+  margin-right: 8px;
+}
+:deep(.el-select__wrapper) {
+  box-shadow: none;
+  border: 1px solid var(--color-select-border);
+}
+:deep(.el-select__wrapper.is-hovering:not(.is-focused)) {
+  box-shadow: none;
+  border: 1px solid var(--color-theme);
+}
+:deep(.el-select__wrapper.is-focused ){
+  box-shadow: none;
+  border: 1px solid var(--color-theme);
+}
+.cancel_header {
+  font-size: 18px;
+  font-weight: 700;
+  color: var(--color-neutral-1);
+  display: flex;
+  align-items: center;
+}
+.icon_warning {
+  width: 22px;
+  height: 22px;
+  margin-right: 0;
+  fill: var(--color-btn-warning-bg);
+}
+.iconfont_warning {
+  display: flex;
+  align-items: center;
+}
+:deep(header.el-dialog__header) {
+  background-color: var(--color-system-body-bg);
+}
+:deep(footer.el-dialog__footer) {
+  border-top: none;
+}
+:deep(.el-dialog__body) {
+  font-weight: 400;
+}
+</style>

+ 750 - 0
src/views/DestinationDelivery/src/components/ConfiguRations/src/components/RecommendDate.vue

@@ -0,0 +1,750 @@
+<script setup lang="ts">
+import SelectValue from './SelectValue.vue'
+import { ref } from 'vue'
+
+// 定义类型接口
+interface RuleOption {
+  label: string;
+  value: string;
+}
+interface PortOption {
+  value: string
+  label: string
+  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;
+}
+
+// 定义 RuleItem 中数组字段的类型
+type ArrayFields = 'ports' | 'carrier';
+
+const props = defineProps({
+  recommendata: {
+    type: Object,
+    default: () => ({})
+  }
+})
+// 响应式变量
+const Recommendradio = ref()
+const AirPortRef = ref()
+const SeaPortRef = ref()
+const SeaCarrierRef = ref()
+const isRecommendETA = ref(false)
+const isAir = ref(false)
+const isSea = ref(false)
+const RecommendCheckedList = ref<string[]>([])
+// 选项配置
+const AirRuleTypeoptions = ref<RuleOption[]>([
+  { label: 'Specific Rule', value: 'Specific Rule' }
+])
+
+const RuleTypeoptions = ref<RuleOption[]>([
+  { label: 'Specific Rule', value: 'Specific Rule' },
+  { label: 'Single Dimension', value: 'Single Dimension' }
+])
+
+// 规则数据
+const AirContentList = ref<RuleItem[]>([
+  {
+    priority: 'P1',
+    rule_type: '*Default Rule',
+    ports: ['ALL'],
+    recommended_delivery_from: '',
+    recommended_delivery_to: '',
+    mode_type: 'air',
+    PortList:[]
+  }
+])
+const SeaContentList = ref<RuleItem[]>([
+  {
+    priority: 'P1',
+    rule_type: '*Default Rule',
+    ports: ['ALL'],
+    carrier: ['ALL'],
+    recommended_delivery_from: '',
+    recommended_delivery_to: '',
+    mode_type: 'sea',
+    PortList:[],
+    CarrierList: []
+  }
+])
+
+const recommendata = ref(props.recommendata)
+
+const initRecommendData = () => {
+  if(recommendata.value) {
+    Recommendradio.value = recommendata.value.Recommendradio
+  if(Recommendradio.value == 2) {
+    isRecommendETA.value = true
+    RecommendCheckedList.value = recommendata.value.RecommendCheckedList
+    if(RecommendCheckedList.value.includes('Air')) {
+      isAir.value = true
+    }
+    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 })
+
+
+// 创建规则项的工厂函数
+function createRuleItem(type: 'Air' | 'Sea', ruleType: string): RuleItem {
+  const baseItem = {
+    priority: 'P1',
+    rule_type: ruleType,
+    recommended_delivery_from: '',
+    recommended_delivery_to: '',
+  }
+  if (type === 'Air') {
+    return {
+      ...baseItem,
+      ports: ruleType === '*Default Rule' ? ['ALL'] : [],
+      mode_type: 'air',
+      PortList: JSON.parse(JSON.stringify(AirPorList.value))
+    }
+  }
+  return {
+    ...baseItem,
+    ports: ruleType === '*Default Rule' ? ['ALL'] : [],
+    carrier: ruleType === '*Default Rule' ? ['ALL'] : [],
+    mode_type: 'sea',
+    PortList: JSON.parse(JSON.stringify(SeaPortList.value)),
+    CarrierList: JSON.parse(JSON.stringify(SeaCarrierList.value))
+  }
+}
+
+// 选择checkbox
+const emits = defineEmits(['chackchangerecommend'])
+const CheckChange = (val: string[]) => {
+  isAir.value = val.includes('Air')
+  isSea.value = val.includes('Sea')
+  // 确保至少有一条规则
+  if (isAir.value && AirContentList.value.length === 0) {
+    AirContentList.value.push(createRuleItem('Air', '*Default Rule'))
+  }
+  if (isSea.value && SeaContentList.value.length === 0) {
+    SeaContentList.value.push(createRuleItem('Sea', '*Default Rule'))
+  }
+  updatePriorities()
+}
+
+const handleCheckboxClick = (event: Event) => {
+  const target = event.target as HTMLElement
+  const isCheckboxInput = target.closest('.el-checkbox__inner')
+  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)
+}
+// 修复后的 handleInput 函数
+const handleInput = (val: string, index: number, type: 'recommended_delivery_from' | 'recommended_delivery_to', list: RuleItem[]) => {
+  // 移除非数字字符
+  const numStr = val.replace(/[^\d]/g, '');
+  // 处理空值情况
+  if (numStr === '') {
+    list[index][type] = '';
+    return;
+  }
+  
+  // 转换为数字以进行范围检查
+  const num = parseInt(numStr, 10);
+  
+  // 确保最小值为1(但保持为字符串形式)
+  if (num < 1) {
+    list[index][type] = '1';
+  } else {
+    // 保持为字符串形式
+    list[index][type] = numStr;
+  }
+};
+// 删除数据
+const handleDelete = (index: number, list: RuleItem[], type: 'Air' | 'Sea') => {
+  list.splice(index, 1);
+  if (list.length === 0) {
+    if (type === 'Air') {
+      isAir.value = false
+      RecommendCheckedList.value = RecommendCheckedList.value.filter(item => item !== 'Air')
+    } else {
+      isSea.value = false
+      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 ruleType = hasDefaultRule ? 'Specific Rule' : '*Default Rule'
+  list.push(createRuleItem(type, ruleType))
+  updatePriorities()
+}
+// 根据RuleType的值来修改Priority的值
+const updatePriorities = () => {
+  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 => {
+    if (item.rule_type === '*Default Rule') {
+      if (type === 'Air') {
+        item.ports = ['ALL']
+      } else {
+        item.ports = ['ALL']
+        item.carrier = ['ALL']
+      }
+    }
+  })
+  if (length === 1) {
+    handleLengthOne(list, type)
+  } else if (length === 2) {
+    handleLengthTwo(list, type)
+  } else if (length >= 3) {
+    handleLengthThreePlus(list, type)
+  }
+}
+// 处理长度为1
+const handleLengthOne = (list: RuleItem[], type: string) => {
+  list.forEach(item => item.priority = 'P1')
+};
+// 处理长度为2
+const handleLengthTwo = (list: RuleItem[], type: string) => {
+  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')
+    return
+  }
+  // 包含 *Default Rule 和其他类型
+  if (types.has('*Default Rule')) {
+    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 => {
+      item.priority = item.rule_type === 'Specific Rule' ? 'P1' : 'P2'
+    })
+    return
+  }
+  // 其他情况
+  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 existingTypes = Object.keys(counts)
+  // 三个不同类型都存在
+  if (
+    existingTypes.includes('Specific Rule') &&
+    existingTypes.includes('Single Dimension') &&
+    existingTypes.includes('*Default Rule')
+  ) {
+    const priorityMap: Record<string, string> = {
+      'Specific Rule': 'P1',
+      'Single Dimension': 'P2',
+      '*Default Rule': 'P3'
+    }
+    list.forEach(item => {
+      item.priority = priorityMap[item.rule_type]
+    })
+    return
+  }
+  // 全为同一种类型的情况
+  if (existingTypes.length === 1) {
+    list.forEach(item => item.priority = 'P1')
+    return
+  }
+  // 处理 Specific + Default 组合
+  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 => {
+      item.priority = item.rule_type === '*Default Rule' ? 'P2' : 'P1'
+    })
+    return
+  }
+  // 存在两个Single Dimension
+  if (counts['Single Dimension'] === 2) {
+    if (existingTypes.includes('*Default Rule')) {
+      // 两个Single + 一个Default
+      list.forEach(item => {
+        item.priority = item.rule_type === '*Default Rule' ? 'P2' : 'P1'
+      })
+    } else if (existingTypes.includes('Specific Rule')) {
+      // 两个Single + 一个Specific
+      list.forEach(item => {
+        item.priority = item.rule_type === 'Specific Rule' ? 'P1' : 'P2'
+      })
+    }
+    return
+  }
+  // 其他情况
+  const defaultPriorityMap: Record<string, string> = {
+    'Specific Rule': 'P1',
+    'Single Dimension': 'P2',
+    '*Default Rule': 'P3'
+  }
+  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;
+  // 新增逻辑:检查是否从 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();
+      }
+    } 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');
+      
+      if (portsSelected && carrierSelected) {
+        item['rule_type'] = 'Specific Rule';
+        updatePriorities();
+      } else {
+        item['rule_type'] = 'Single Dimension';
+        updatePriorities();
+      }
+    }
+  }
+}
+// 改变规则类型
+const changeRuleType = (val: string, index: number, list: RuleItem[]) => {
+  const item = list[index]
+  // 保护默认规则
+  if (item.rule_type === '*Default Rule' && val !== '*Default Rule') {
+    // 从默认规则切换到其他规则时重置选项
+    if ('ports' in item) item.ports = []
+    if ('carrier' in item) item.carrier = []
+  } else if (val === '*Default Rule') {
+    // 切换到默认规则时设置默认值
+    if ('ports' in item) item.ports = ['ALL']
+    if ('carrier' in item) item.carrier = ['ALL']
+  }
+  item.rule_type = val
+  updatePriorities()
+}
+
+// 获取Air Port/Port/Carrier的值
+const AirPorList = ref([])
+const SeaPortList = ref([])
+const SeaCarrierList = ref([])
+const getPortList = (type: any) => {
+  $api.getPortList({ 
+    term: '',
+    mode: type
+  }).then((res: any) => {
+    if (res.code === 200) {
+      if(type === 'air') {
+        AirPorList.value = res.data
+        // 更新现有行的列表
+        AirContentList.value.forEach(item => {
+          item.PortList = JSON.parse(JSON.stringify(res.data))
+          setTimeout(() => {
+            if(AirPortRef.value) {
+              AirPortRef.value.forEach(item1 => { 
+                item1.initGetPortsList(item.PortList)
+              })
+            }
+          }, 1000);
+        })
+      }
+      if(type === 'sea') {
+        SeaPortList.value = res.data
+        // 更新现有行的列表
+        SeaContentList.value.forEach(item => {
+          item.PortList = JSON.parse(JSON.stringify(res.data))
+          setTimeout(() => {
+            if(SeaPortRef.value) {
+              SeaPortRef.value.forEach(item1 => { 
+                item1.initGetPortsList(item.PortList)
+              })
+            }
+          }, 1000);
+        })
+      }
+    }
+  })
+}
+
+// 获取Carrier列表
+const getCarrierList = () => {
+  $api.getCarrierList({ 
+    term: '',
+  }).then((res: any) => {
+    if (res.code === 200) {
+      SeaCarrierList.value = res.data
+      // 更新现有行的列表
+      SeaContentList.value.forEach(item => {
+        item.CarrierList = JSON.parse(JSON.stringify(res.data))
+        setTimeout(() => {
+          if(SeaCarrierRef.value) {
+            SeaCarrierRef.value.forEach(item1 => { 
+              item1.initGetPortsList(item.CarrierList)
+            })
+          }
+        }, 1000);
+      })
+    }
+  })
+}
+
+onMounted(() => {
+  getPortList('air')
+  getPortList('sea')
+  getCarrierList()
+})
+</script>
+
+<template>
+  <div>
+    <el-radio-group v-model="Recommendradio" @change="ChangeFrequency">
+      <el-radio :value="1">No Specific recommended time for choosing delivery date</el-radio>
+      <el-radio :value="2">
+        <div>Recommend Delivery Date (ETA/ATA)</div>
+        <div v-if="isRecommendETA" class="oceanCheckbox">
+          <el-checkbox-group v-model="RecommendCheckedList" @change="CheckChange">
+            <!-- Air 部分 -->
+            <el-checkbox class="delayedType" value="Air" @click="handleCheckboxClick">
+              <div class="titlecheckbox">
+                <div>Air</div>
+                <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 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="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 class="datetitle">To (ETA/ATA + Days)</div>
+                    </div>
+                  </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%;">
+                    <el-select
+                      v-model="item.rule_type"
+                      disabled
+                      style="width: 100%;"
+                      @change="val => changeRuleType(val, index, AirContentList)"
+                    >
+                      <el-option
+                        v-for="opt in AirRuleTypeoptions"
+                        :key="opt.value"
+                        :label="opt.label"
+                        :value="opt.value"
+                      />
+                    </el-select>
+                  </div>
+                  <div class="AirCoumlumn" style="width: 40%;">
+                    <SelectValue
+                      ref="AirPortRef"
+                      :SelectIndex="index"
+                      :SelectedValue="item.ports"
+                      :typeisDisabled="item.rule_type"
+                      :PortList="item.PortList"
+                      SelectType="Air"
+                      @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" 
+                      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" 
+                      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" 
+                      style="height: 24px"
+                    >
+                      <span class="font_family icon-icon_delete_b"></span>
+                    </el-button>
+                  </div>
+                </div>
+              </div>
+            </el-checkbox>
+            <!-- Sea 部分 -->
+            <el-checkbox class="delayedType" value="Sea" @click="handleCheckboxClick">
+              <div class="titlecheckbox">
+                <div>Sea</div>
+                <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 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="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 class="datetitle">To (ETA/ATA + Days)</div>
+                    </div>
+                  </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%;">
+                    <el-select
+                      v-model="item.rule_type"
+                      disabled
+                      style="width: 100%;"
+                      @change="val => changeRuleType(val, index, SeaContentList)"
+                    >
+                      <el-option
+                        v-for="opt in RuleTypeoptions"
+                        :key="opt.value"
+                        :label="opt.label"
+                        :value="opt.value"
+                      />
+                    </el-select>
+                  </div>
+                  <div class="AirCoumlumn" style="width: 23%;">
+                    <SelectValue
+                      ref="SeaPortRef"
+                      :SelectIndex="index"
+                      :SelectedValue="item.ports"
+                      :typeisDisabled="item.rule_type"
+                      :PortList="item.PortList"
+                      SelectType="Sea"
+                      @changeSelectedValue="val => changeSelectedValue(val, index, 'ports', SeaContentList)"
+                    />
+                  </div>
+                  <div class="AirCoumlumn" style="width: 23%;">
+                    <SelectValue
+                      ref="SeaCarrierRef"
+                      :SelectIndex="index"
+                      :SelectedValue="item.carrier"
+                      :typeisDisabled="item.rule_type"
+                      :PortList="item.CarrierList"
+                      SelectType="Sea"
+                      @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" 
+                      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" 
+                      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" 
+                      style="height: 24px"
+                    >
+                      <span class="font_family icon-icon_delete_b"></span>
+                    </el-button>
+                  </div>
+                </div>
+              </div>
+            </el-checkbox>
+          </el-checkbox-group>
+        </div>
+      </el-radio>
+    </el-radio-group>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+:deep(.el-radio-group) {
+  display: block;
+}
+:deep(.el-radio) {
+  display: flex;
+  min-height: 32px;
+  border: 1px solid var(--color-system-border);
+  background-color: var(--color-system-body-bg);
+  margin-bottom: 4px;
+  border-radius: 6px;
+  padding: 0 8px;
+  margin-right: 0;
+  height: fit-content;
+  line-height: 32px;
+  align-items: start;
+}
+:deep(.el-radio__input.is-checked + .el-radio__label) {
+  color: var(--color-neutral-1);
+}
+:deep( .el-radio__inner) {
+  border: 1px solid var(--color-system-checkbox-border);
+}
+:deep(.el-radio__inner) {
+  margin-top: 7px;
+}
+:deep(.el-checkbox-group) {
+  display: flex;
+  flex-direction: column;
+}
+.oceanCheckbox {
+  margin-bottom: 8px;
+}
+.delayedType {
+  align-items: start;
+  height: fit-content;
+  margin-right: 5px;
+  border-radius: 6px;
+  padding: 13px;
+}
+:deep(.el-checkbox) {
+  width: 100%;
+  border-radius: 6px;
+  background-color: var(--color-personal-preference-bg);
+  margin-bottom: 4px;
+}
+:deep(.el-checkbox__label) {
+  width: 100%;
+}
+.titlecheckbox {
+  width: 100%;
+  display: flex;
+  justify-content: space-between;
+}
+.icon_grey {
+  color: #b8bbbf;
+}
+.AirCoulumn {
+  display: flex;
+  border: 1px solid var(--color-system-border);
+  background-color: var(--color-personal-preference-bg);
+  border-radius: 6px 6px 0 0;
+}
+.AicoulumnTitile {
+  font-size: 14px;
+  font-weight: 700;
+  border-right: 1px solid var(--color-system-border);
+  height: 56px;
+  display: flex;
+  align-items: center;
+  padding: 0 8px;
+}
+.datetitle {
+  font-size: 12px;
+  font-weight: 400;
+  height: 24px;
+  width: 50%;
+  display: flex;
+  align-items: center;
+  padding: 0 8px;
+}
+.AicoulumnTitile2 {
+  font-weight: 700;
+  height: 32px;
+  display: flex;
+  border-bottom: 1px solid var(--color-system-border);
+  align-items: center;
+  padding: 0 8px;
+}
+.AirCoumlulnAdd {
+  font-size: 14px;
+  font-weight: 400;
+  height: 56px;
+  color: var(--color-theme);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 0 8px;
+}
+.AirContent {
+  display: flex;
+  border: 1px solid var(--color-system-border);
+  background-color: var(--color-mode);
+  border-top: none;
+  height: 40px;
+  align-items: center;
+}
+.AirContent:last-child {
+  border-radius: 0 0 6px 6px;
+}
+.AirCoumlumn {
+  border-right: 1px solid var(--color-system-border);
+  display: flex;
+  height: 40px;
+  align-items: center;
+  padding: 0 8px;
+}
+.AirDelete {
+  display: flex;
+  height: 40px;
+  align-items: center;
+  justify-content: center;
+}
+</style>

+ 201 - 0
src/views/DestinationDelivery/src/components/ConfiguRations/src/components/SelectStation.vue

@@ -0,0 +1,201 @@
+<script setup lang="ts">
+
+interface CountryItem {
+  value: string
+  label: string
+}
+
+const countrys = ref<CountryItem[]>([])
+interface OceanCheckboxItem {
+  value: string
+  label: string
+}
+
+interface Props {
+  CheckboxList: OceanCheckboxItem[]
+  CheckedList: Array<''>
+  SelectCountry: string
+}
+const props = defineProps<Props>()
+const CheckedList = ref(props.CheckedList)
+const CheckboxList = ref(props.CheckboxList)
+const SelectCountry = ref(props.SelectCountry)
+watch(
+  () => props.CheckboxList,
+  (current) => {
+    CheckboxList.value = current
+  }
+)
+watch(
+  () => props.CheckedList,
+  (current) => {
+    CheckedList.value = current
+  }
+)
+watch(
+  () => props.SelectCountry,
+  (current) => {
+    SelectCountry.value = current
+  }
+)
+
+const emits = defineEmits(['handleClickSelectCountry','handleChangeStation'])
+const handlechangestation = (val: any) => {
+  emits('handleChangeStation', val)
+}
+
+// 获取contry列表
+const getCountryList = (): Promise<CountryItem[]> => {
+  return new Promise((resolve) => {
+    $api.getDeliveryCountry({ term: '' })
+      .then((res: any) => {
+        if (res.code === 200) {
+          countrys.value = res.data
+        } else {
+          resolve([]); // 失败返回空数组
+        }
+      })
+      .catch(() => resolve([])); // 异常兜底
+  });
+};
+// 自动输入
+let timeout: ReturnType<typeof setTimeout>
+const isNodata = ref([])
+const querySearchAsync = (queryString: string, cb: (arg: any) => void) => {
+  const results = queryString
+    ? countrys.value.filter(createFilter(queryString))
+    : countrys.value
+  isNodata.value = results
+  clearTimeout(timeout)
+  timeout = setTimeout(() => {
+    if(results.length == 0) {
+      cb([{ 
+        isNoData: true, 
+        value: '无搜索结果' 
+      }]);
+    } else {
+      cb(results)
+    }
+  }, 1000 * Math.random())
+}
+
+const popperClass = computed(() => {
+  return isNodata.value.length == 0 ? 'no-data-item' : ''
+})
+const createFilter = (queryString: string) => {
+  return (restaurant: CountryItem) => {
+    return (
+      restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) != -1
+    )
+  }
+}
+
+const handleSelect = (item: Record<string, any>) => {
+  emits('handleClickSelectCountry',item.value)
+  $api.getDeliveryStation({ country: item.value })
+  .then((res: any) => {
+    if (res.code === 200) {
+      CheckboxList.value = res.data
+    }
+  })
+}
+
+const handleChange = (val: any) => {
+  if(val == '') {
+    CheckboxList.value = []
+  }
+}
+
+onMounted(() => {
+  getCountryList()
+})
+</script>
+
+<template>
+  <div>
+    <el-autocomplete
+      v-model="SelectCountry"
+      style="width: 400px;margin-bottom: 5px;"
+      placeholder="Select Country"
+      :popper-class="popperClass"
+      :fetch-suggestions="querySearchAsync"
+      @select="handleSelect"
+      @input="handleChange"
+    >
+      <template #default="{ item }">
+        <div :class="[
+          'suggestion-item',
+          { 'no-data-item': item.isNoData }
+        ]">
+          {{ item.value }}
+        </div>
+      </template>
+    </el-autocomplete>
+    <div class="station-list">
+      <div class="station-list-title">Station List</div>
+      <div class="oceanCheckbox">
+        <el-checkbox-group v-model="CheckedList" :class="{isEmpty :  CheckboxList.length === 0}" @change="handlechangestation">
+          <el-checkbox
+            v-for="item in CheckboxList"
+            :key="item.label"
+            :label="item.label"
+            :value="item.value"
+          >
+            {{ item.label }}
+          </el-checkbox>
+        </el-checkbox-group>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped> 
+.station-list {
+  background-color: var(--color-personal-preference-bg);
+  border-radius: 6px;
+  margin: 4px 0 0 0;
+  padding: 8px;
+}
+.station-list-title {
+  margin: 1px 0 13px 0;
+  font-size: 14px;
+  font-weight: 600;
+}
+:deep(.el-checkbox-group) {
+  border: 1px solid var(--color-system-border);
+  border-radius: 5px;
+}
+.isEmpty {
+  border: none;
+}
+:deep(.el-checkbox) {
+  width: 100%;
+  background-color: var(--color-system-body-bg);
+  border-bottom: 1px solid var(--color-system-border);
+  padding: 8px;
+}
+:deep(.el-checkbox:first-child) {
+  border-radius: 6px 6px 0 0;
+}
+:deep(.el-checkbox:last-child) {
+  border-radius: 0 0 6px 6px;
+  border-bottom: none;
+}
+.oceanCheckbox {
+  max-height: 321px;
+  overflow-x: hidden;
+  overflow-y: scroll;
+}
+.no-data-item {
+  color: var(--color-neutral-2);
+  text-align: center;
+  padding: 20px;
+  cursor: default;
+  &:hover {
+    background-color: transparent !important;
+  }
+}
+.suggestion-item {
+  padding: 5px 20px;
+}
+</style>

+ 201 - 0
src/views/DestinationDelivery/src/components/ConfiguRations/src/components/SelectValue.vue

@@ -0,0 +1,201 @@
+<script setup lang="ts">
+import { ref, watch } from 'vue'
+
+interface PortOption {
+  value: string
+  label: string
+  checked: boolean
+}
+
+const props = defineProps({
+  SelectedValue: {
+    type: Array as () => string[],
+    default: () => []
+  },
+  SelectIndex: {
+    type: Number,
+    default: 0
+  },
+  typeisDisabled: {
+    type: String,
+    default: ''
+  },
+  SelectType: {
+    type: String,
+    default: ''
+  },
+  PortList: {
+    type: Array as () => PortOption[],
+    default: () => []
+  }
+})
+const checkAll = ref(false)
+const value = ref<string[]>(props.SelectedValue)
+const SelectNumber = ref(props.SelectIndex)
+const typeisDisabled = ref(props.typeisDisabled)
+const PortList = ref(props.PortList)
+
+watch(
+  () => props.SelectedValue,
+  (val) => {
+    value.value = val
+    if(val[0] == 'ALL') {
+      checkAll.value = true
+    }
+  }
+, { immediate: true, deep: true })
+
+watch(
+  () => props.PortList,
+  (val) => {
+    PortList.value = val
+  }
+, { immediate: true, deep: true })
+
+const initGetPortsList = (val: any) => {
+  PortList.value = val.map(item => ({
+    ...item,
+    checked: value.value.includes(item.value)
+  }));
+}
+const emits = defineEmits(['changeSelectedValue', 'handelremovetag'])
+
+watch(value, (val) => {
+  if (val.length === 0) {
+    checkAll.value = false
+  } else if (val.length === PortList.value.length) {
+    checkAll.value = true
+    value.value = ['ALL']
+  }else if (val.length == 1 && val[0] == 'ALL') {
+    checkAll.value = true
+  } else {
+    checkAll.value = false
+  }
+  emits('changeSelectedValue', val, SelectNumber.value)
+})
+
+const handleCheckAll = (val) => {
+  if (val.target.checked) {
+    value.value = ['ALL']
+    PortList.value.forEach((item) => { 
+      item.checked = true
+    })
+  } else {
+    value.value = []
+    PortList.value.forEach((item) => { 
+      item.checked = false
+    })
+  }
+}
+
+const handelchangeSelect = () => {
+  value.value = PortList.value.filter(item => item.checked === true).map(item => item.value)
+}
+
+//移除tag
+const handelRemoveTag = (tag) => {
+  // 阻止事件冒泡和默认行为
+  event.stopPropagation()
+  event.preventDefault()
+  
+  // 更新选中值(移除当前标签)
+  const newValue = value.value.filter(item => item !== tag)
+  emits('changeSelectedValue', newValue, SelectNumber.value)
+  
+  // 更新对应的城市选中状态
+  const city = PortList.value.find(item => item.value === tag)
+  if (city) {
+    city.checked = false
+  }
+  
+  // 更新全选状态
+  checkAll.value = newValue.length === PortList.value.length
+}
+
+const displayNumber = computed(() => {
+  return props.SelectType === 'Air' ? 6 : 3
+})
+
+const visibleTags = computed(() => { 
+  return value.value.slice(0, displayNumber.value);
+})
+const hiddenTagsCount = computed(() => { 
+  return Math.max(value.value.length - displayNumber.value, 0);
+})
+
+defineExpose({
+  initGetPortsList
+})
+</script>
+<template>
+  <div style="width: 100%;">
+    <el-select
+      v-model="value"
+      multiple
+      filterable
+      :disabled="typeisDisabled  == '*Default Rule'"
+      placeholder="Select"
+      popper-class="custom-sheader-select"
+      @change="handelchangeSelect"
+    >
+      <template #header>
+        <a-checkbox v-model:checked="checkAll" @change="handleCheckAll">ALL</a-checkbox>
+      </template>
+      <template #tag>
+        <!-- 显示前3个标签 -->
+        <el-tag 
+          v-for="(tag, index) in visibleTags" 
+          :key="index"
+          :closable="!tag.includes('ALL')" 
+          @close="handelRemoveTag(tag)"
+          class="tag"
+        >
+          {{ tag }}
+        </el-tag>
+        
+        <!-- 显示折叠数量提示 -->
+        <el-tag v-if="hiddenTagsCount > 0" class="tag">
+          +{{ hiddenTagsCount }}
+        </el-tag>
+      </template>
+      <el-option
+        v-for="item in PortList"
+        :key="item.value"
+        :label="item.label"
+        :value="item.value"
+      >
+      <div style="width: 100%;" @click="item.checked = !item.checked">
+        <a-checkbox v-model:checked="item.checked"></a-checkbox>
+        <span class="label">{{ item.label }}</span>
+      </div>
+      </el-option>
+    </el-select>
+  </div>
+</template>
+<style lang="scss" scoped>
+.el-select-dropdown__item.is-selected {
+  background-color: transparent;
+  div {
+    color: var(--badge__content--warning);
+  }
+}
+.el-select-dropdown__item {
+  padding-left: 7.7px;
+  display: flex;
+  align-items: center;
+}
+.label {
+  margin-left: 8px;
+}
+.el-select-dropdown__item.is-hovering {
+  div {
+    color: var(--badge__content--warning);
+  }
+}
+.tag {
+  color: var(--color-neutral-1);
+  font-size: var(--font-size-2);
+  border-color: var(--tag-boder-color);
+  background-color: var(--tag-bg-color) !important;
+}
+</style>

+ 168 - 0
src/views/DestinationDelivery/src/components/ConfiguRations/src/components/SetBookingWindow.vue

@@ -0,0 +1,168 @@
+<script setup lang="ts">
+const isBookingETD = ref(false)
+const isBookingETA = ref(false)
+const beforeETDdays = ref('')
+const afterETDdays = ref('')
+const beforeETAdays = ref('')
+const afterETAdays = ref('')
+
+const props = defineProps({
+  setbookingdata: {
+    type: Object,
+    default: () => ({})
+  }
+})
+
+const windowradio = ref()
+const setbookingdata = ref(props.setbookingdata)
+const initBookingWindowData = () => {
+  if(setbookingdata.value) {
+    windowradio.value = setbookingdata.value.windowradio
+  if(windowradio.value == 2) {
+    isBookingETD.value = true
+    beforeETDdays.value = setbookingdata.value.windowBeforeDays
+    afterETDdays.value = setbookingdata.value.windowAfterDays
+  } else if (windowradio.value == 3) {
+    isBookingETA.value = true
+    beforeETAdays.value = setbookingdata.value.windowBeforeDays
+    afterETAdays.value = setbookingdata.value.windowAfterDays
+  }
+  }
+}
+
+watch(() => props.setbookingdata, (val) => { 
+  setbookingdata.value = val
+  initBookingWindowData()
+}, { immediate: true, deep: true })
+
+// 选择booking window
+const emits = defineEmits(['changeBookingWindow'])
+const ChangeFrequency = (val: any) => {
+  if(val == 1) {
+    isBookingETD.value = false
+    isBookingETA.value = false
+    beforeETDdays.value = ''
+    afterETDdays.value = ''
+    beforeETAdays.value = ''
+    afterETAdays.value = ''
+    emits('changeBookingWindow',windowradio.value, beforeETAdays.value, afterETAdays.value)
+  } else if(val == 2) {
+    isBookingETD.value = true
+    isBookingETA.value = false
+    beforeETAdays.value = ''
+    afterETAdays.value = ''
+    emits('changeBookingWindow',windowradio.value, beforeETDdays.value, afterETDdays.value)
+  } else if(val == 3) {
+    isBookingETD.value = false
+    isBookingETA.value = true
+    beforeETDdays.value = ''
+    afterETDdays.value = ''
+    emits('changeBookingWindow',windowradio.value, beforeETAdays.value, afterETAdays.value)
+  } else {
+    isBookingETD.value = false
+    isBookingETA.value = false
+    beforeETDdays.value = ''
+    afterETDdays.value = ''
+    beforeETAdays.value = ''
+    afterETAdays.value = ''
+  }
+}
+
+// 处理输入值,确保只能是正整数
+const validatePositiveInteger = (value: string) => {
+  // 移除非数字字符
+  let numStr = value.replace(/[^\d]/g, '');
+  // 如果为空字符串,直接返回
+  if (numStr === '') return '';
+  // 转换为整数
+  let num = parseInt(numStr, 10);
+  // 处理NaN情况,确保最小值为1
+  if (isNaN(num) || num < 1) {
+    return '1';
+  }
+  // 返回数字字符串(去掉前导零)
+  return num.toString();
+};
+
+// 处理输入变化
+const handleInput = (val: string, target: 'ETD_BEFORE' | 'ETD_AFTER' | 'ETA_BEFORE' | 'ETA_AFTER') => {
+  const validatedValue = validatePositiveInteger(val);
+  switch (target) {
+    case 'ETD_BEFORE':
+      beforeETDdays.value = validatedValue;
+      ChangeFrequency(2);
+      break;
+    case 'ETD_AFTER':
+      afterETDdays.value = validatedValue;
+      ChangeFrequency(2);
+      break;
+    case 'ETA_BEFORE':
+      beforeETAdays.value = validatedValue;
+      ChangeFrequency(3);
+      break;
+    case 'ETA_AFTER':
+      afterETAdays.value = validatedValue;
+      ChangeFrequency(3);
+      break;
+  }
+};
+
+</script>
+<template>
+  <div>
+    <el-radio-group v-model="windowradio" @change="ChangeFrequency">
+      <el-radio :value="1">No Specific time restrictions for creating booking</el-radio>
+      <el-radio :value="2">
+        <div>Booking Window (ETD/ATD)</div>
+        <div class="ETDWindow" v-if="isBookingETD">
+          <el-input style="width: 80px;height: 32px;" v-model="beforeETDdays" @input="val => handleInput(val, 'ETD_BEFORE')"></el-input>
+          days before to 
+          <el-input style="width: 80px;height: 32px;" v-model="afterETDdays" @input="val => handleInput(val, 'ETD_AFTER')"></el-input>
+          days after
+        </div>
+      </el-radio>
+      <el-radio :value="3">
+        <div>Booking Window (ETA/ATA)</div>
+        <div class="ETDWindow" v-if="isBookingETA">
+          <el-input style="width: 80px;height: 32px;" v-model="beforeETAdays" @input="val => handleInput(val, 'ETA_BEFORE')"></el-input>
+          days before to 
+          <el-input style="width: 80px;height: 32px;" v-model="afterETAdays" @input="val => handleInput(val, 'ETA_AFTER')"></el-input>
+          days after
+        </div>
+      </el-radio>
+    </el-radio-group>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+:deep(.el-radio-group) {
+  display: block;
+}
+:deep(.el-radio) {
+  display: flex;
+  min-height: 32px;
+  border: 1px solid var(--color-system-border);
+  background-color: var(--color-system-body-bg);
+  margin-bottom: 4px;
+  border-radius: 6px;
+  padding: 0 8px;
+  margin-right: 0;
+  height: fit-content;
+  line-height: 32px;
+  align-items: start;
+}
+:deep(.el-radio__input.is-checked + .el-radio__label) {
+  color: var(--color-neutral-1);
+}
+:deep( .el-radio__inner) {
+  border: 1px solid var(--color-system-checkbox-border);
+}
+:deep(.el-radio__inner) {
+  margin-top: 7px;
+}
+.ETDWindow {
+  margin: 4px 0 9px 0;
+  font-size: 14px;
+  font-weight: 400;
+}
+</style>

binární
src/views/DestinationDelivery/src/components/ConfiguRations/src/images/default_configuration@2x.png


binární
src/views/DestinationDelivery/src/components/ConfiguRations/src/images/icon_success_big@2x.png


+ 1 - 0
src/views/DestinationDelivery/src/components/CreateNewBooking/index.ts

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

+ 1482 - 0
src/views/DestinationDelivery/src/components/CreateNewBooking/src/CreateNewbooking.vue

@@ -0,0 +1,1482 @@
+<script setup lang="ts">
+import CalendarDate from '@/components/DateRange/src/components/CalendarDate.vue'
+import AutoSelect from '@/components/AutoSelect/src/AutoSelect.vue'
+import NewbookingTable from './components/NewbookingTable.vue'
+import AddNewAddress from './images/default_add_address@2x.png'
+import NotAvailable from './images/default_destination_not_available@2x.png'
+import NotShipment from './images/default_no_shipment@2x.png'
+import submitsucessful from './images/icon_success_big@2x.png'
+import { useUserStore } from '@/stores/modules/user'
+import { useRouter } from 'vue-router'
+
+const userStore = useUserStore()
+const router = useRouter()
+const { currentRoute } = router
+const { value } = currentRoute
+const { query } = value
+const { a } = query
+
+const CreateNewBOokingSearch = ref('')
+const status = ref('')
+const booking = ref('')
+const DateValue = ref('')
+const DeliveryTime = ref('')
+const bookingTableRef = ref()
+const VesselName = ref([])
+const VesselNametest = ref('')
+const ShipperValue = ref('')
+const ConsigneeValue = ref('')
+const getAddressListData = ref({})
+// const isFocused = ref(false)
+const isFocused = ref({
+  Shipper: false,
+  Consignee: false,
+  Vessel: false
+})
+const isataFocused = ref(false)
+const isetaFocused = ref(false)
+const ManageVisible = ref(false)
+const AddNewAddressVisible = ref(false)
+const NoPermissionVisible = ref(false)
+const CancelRulesVisible = ref(false)
+const NoEligibleVisible = ref(false)
+const isDisabled = ref(false)
+const SaveedVisible = ref(false)
+const UnableSaveVisible = ref(false)
+const isRecommendDate = ref(false)
+const missingmessage = ref('')
+const recommendateWarning = ref('')
+const ATATimeList = ref(null)
+const ETATimeList = ref(null)
+const Addressradio = ref()
+const LocationName = ref('')
+const AddressLine1 = ref('')
+const AddressLine2 = ref('')
+const AddressLine3 = ref('')
+const AddressLine4 = ref('')
+const CountryCode = ref('')
+const CityCode = ref('')
+const CountryCity = ref('')
+const PostalCode = ref('')
+const ContactPerson = ref('')
+const ContactNumber = ref('')
+const instructions = ref('')
+const modetypeValue = ref('Truck')
+const Requirements = ref('')
+const Modification = ref()
+const selectedAddressList = ref()
+const isselectedAddress = ref(null)
+const checkShipmentsSubmitInfo = ref({})
+const RecommendateList = ref([])
+const ModeType = ref([
+  {
+    label: 'Truck',
+    value: 'Truck'
+  },
+  {
+    label: 'Rail',
+    value: 'Rail'
+  }
+])
+
+// 设置无法点击
+
+const isNotClickAddress = computed(() => {
+  if(a == undefined) {
+    if(Object.keys(getAddressListData.value).length == 0) {
+      return true
+    }
+  }
+  
+  if(NoPermissionVisible.value || NoEligibleVisible.value || isDisabled.value) {
+    return true
+  }
+
+  return false
+})
+
+// 设置无法保存
+const isNotSubmit = computed(() => {
+  if(NoPermissionVisible.value || NoEligibleVisible.value || isDisabled.value) {
+    return true
+  }
+
+  if(a != undefined) {
+    if(
+      selectedAddressList.value == undefined ||
+      modetypeValue.value == '' ||
+      DateValue.value == '' ||
+      DeliveryTime.value == '' ||
+      Modification.value == null ||
+      Modification.value == ''
+    ) {
+      return true
+    } else {
+      return false
+    }
+  }
+
+  if(
+    Object.keys(checkShipmentsSubmitInfo.value).length == 0 ||
+    selectedAddressList.value == undefined ||
+    modetypeValue.value == '' ||
+    DateValue.value == '' ||
+    DeliveryTime.value == ''
+  ) {
+    return true
+  }
+
+  return false
+})
+
+// 控制Address是否超长
+const isShowLimitLine1 = ref(false)
+const isShowLimitLine2 = ref(false)
+const isShowLimitLine3 = ref(false)
+const isShowLimitLine4 = ref(false)
+const isShowLimitLine1Style = computed(() => {
+  return AddressLine1.value.length == 45
+})
+const isShowLimitLine2Style = computed(() => {
+  return AddressLine2.value.length == 45
+})
+const isShowLimitLine3Style = computed(() => {
+  return AddressLine3.value.length == 45
+})
+const isShowLimitLine4Style = computed(() => {
+  return AddressLine4.value.length == 45
+})
+const createInputLimiter = (targetRef, showRef) => {
+  return (val: string) => {
+    if (val.length > 45) {
+      targetRef.value = val.substring(0, 45)
+      showRef.value = true
+      setTimeout(() => {
+        showRef.value = false
+      }, 3000)
+    }
+  }
+}
+const handleAddressLine1 = createInputLimiter(AddressLine1, isShowLimitLine1)
+const handleAddressLine2 = createInputLimiter(AddressLine2, isShowLimitLine2)
+const handleAddressLine3 = createInputLimiter(AddressLine3, isShowLimitLine3)
+const handleAddressLine4 = createInputLimiter(AddressLine4, isShowLimitLine4)
+
+const ManageAddressList = ref([])
+// 需要特殊样式的日期列表
+const specialDates = ref([])
+
+
+const DateRangeChangeETA = (val:any) => {
+  ETATimeList.value = val.data
+}
+
+const DateRangeChangeATA = (val:any) => {
+  ATATimeList.value = val.data
+}
+
+const createShowLabel = (valueRef, focusKey) => computed(() => 
+  valueRef.value !== '' || isFocused.value[focusKey]
+)
+const showLabelShipper = createShowLabel(ShipperValue, 'Shipper')
+const showLabelConsignee = createShowLabel(ConsigneeValue, 'Consignee')
+const showLabelVessel = createShowLabel(VesselNametest, 'Vessel')
+
+const showataLabel = computed(() => {
+  return ATATimeList.value != null || isataFocused.value
+})
+
+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
+}
+
+const DateChangeFocusATA = (val: boolean) => {
+  isataFocused.value = val
+}
+const DateChangeFocusETA = (val: boolean) => {
+  isetaFocused.value = val
+}
+//自动补全查询
+interface CountryItem {
+  value: string
+  label: string
+}
+const countrys = ref<CountryItem[]>([])
+const Countryoptions = ref<CountryItem[]>([])
+const Countryloading = ref(false)
+const city = ref<CountryItem[]>([])
+const Cityoptions = ref<CountryItem[]>([])
+const cityloading = ref(false)
+const getAddressCountryCityData = (type: any) => {
+  $api.getAddressCountryCityData({
+     term: '',
+     term_type: type,
+     limit: type == 'country' ? CityCode.value : CountryCode.value
+  })
+  .then((res: any) => {
+    if (res.code === 200) {
+      if(type == 'country') {
+        countrys.value = res.data
+      } else {
+        city.value = res.data
+      }
+    }
+  })
+}
+const querySearchCountry = (query: string) => {
+  if (query) {
+    Countryloading.value = true
+    setTimeout(() => {
+      Countryloading.value = false
+      Countryoptions.value = countrys.value.filter((item) => {
+        return item.label.toLowerCase().includes(query.toLowerCase())
+      })
+    }, 1000)
+  } else {
+    Countryoptions.value = []
+  }
+}
+const querySearchCity = (query: string) => {
+  if (query) {
+    cityloading.value = true
+    setTimeout(() => {
+      cityloading.value = false
+      Cityoptions.value = city.value.filter((item) => {
+        return item.label.toLowerCase().includes(query.toLowerCase())
+      })
+    }, 1000)
+  } else {
+    Cityoptions.value = []
+  }
+}
+// 特殊日期样式
+const getCurrentStyle = (current: any) => {
+  const dateString = current.format('YYYY.MM.DD')
+  if (specialDates.value.includes(dateString)) {
+    return { 
+      background: '#b3e5d4', 
+      borderRadius: '6px',
+      color: `var(--color-neutral-1)`
+    }
+  }
+  // 默认样式
+  return {}
+}
+const submitAddressList = ref([])
+const currentEditAddress = ref<any>(null)
+const updateAddressList = (addressData) => {
+  const { sync_key, contact_type } = addressData
+  const existingIndex = submitAddressList.value.findIndex(
+    item => item.sync_key === sync_key
+  )
+  if (contact_type === 'Delete') {
+    submitAddressList.value.push({
+      ...addressData,
+      contact_type: 'Delete'
+    })
+    return
+  }
+  if (existingIndex !== -1) {
+    submitAddressList.value.splice(existingIndex, 1, {
+      ...submitAddressList.value[existingIndex],
+      ...addressData,
+      contact_type
+    })
+  } else {
+    submitAddressList.value.push(addressData)
+  }
+}
+const isAddNewAddressVisible = ref(false)
+const AddNewAddressDelivery = () => {
+  currentEditAddress.value = null
+  isAddNewAddressVisible.value = true
+  AddNewAddressVisible.value = true
+  AddressLine1.value = ''
+  AddressLine2.value = ''
+  AddressLine3.value = ''
+  AddressLine4.value = ''
+  CountryCode.value = ''
+  CityCode.value = ''
+  PostalCode.value = ''
+  ContactPerson.value = ''
+  ContactNumber.value = ''
+}
+// 保存新地址
+const SaveNewAddress = () => {
+  if(
+    AddressLine1.value != '' ||
+    AddressLine2.value != '' ||
+    AddressLine3.value != '' ||
+    AddressLine4.value != '' &&
+    CountryCode.value != '' &&
+    CityCode.value != '' &&
+    PostalCode.value != '' &&
+    ContactPerson.value != '' &&
+    ContactNumber.value != ''
+  ) {
+    const addressData = {
+      address_1: AddressLine1.value,
+      address_2: AddressLine2.value,
+      address_3: AddressLine3.value,
+      address_4: AddressLine4.value,
+      country: CountryCode.value,
+      city: CityCode.value,
+      postal_code: PostalCode.value,
+      contact_person: ContactPerson.value,
+      contact_number: ContactNumber.value,
+      contact_id: currentEditAddress.value? currentEditAddress.value.contact_id : '',
+      sync_key: currentEditAddress.value? currentEditAddress.value.sync_key : '',
+      from_station: currentEditAddress.value? currentEditAddress.value.from_station :'',
+      contact_type: 'Add',
+      create_user: 'Online_D_Address',
+      id: `temp_${Date.now()}`
+    }
+    if (currentEditAddress.value) {
+      const key = currentEditAddress.value.contact_type == 'Unedit' ? 'sync_key' : 'id'
+      addressData[key] = currentEditAddress.value[key]
+      addressData.contact_type = 'Modify'
+      const index = ManageAddressList.value.findIndex(
+        item => item[key] === currentEditAddress.value[key]
+      )
+      if (index !== -1) {
+        ManageAddressList.value.splice(index, 1, addressData)
+      }
+      updateAddressList(addressData)
+    } else {
+      ManageAddressList.value.push(addressData)
+      updateAddressList(addressData)
+    }
+    if(isAddNewAddressVisible.value) {
+      handleClickAddress()
+    }
+    AddNewAddressVisible.value = false
+    currentEditAddress.value = null
+  }
+}
+// 点击按钮
+const handleclickbutton = (val: any) => {
+  Requirements.value = Requirements.value + val
+}
+let delivery_address = ''
+const changeAddressRadio = () => {
+  ManageVisible.value = false
+  if(Addressradio.value != undefined) {
+    isselectedAddress.value = Addressradio.value
+  }
+  selectedAddressList.value = ManageAddressList.value[Addressradio.value]
+  delivery_address = `${selectedAddressList.value.contact_person}(${selectedAddressList.value.contact_number})\n${selectedAddressList.value.address_1}\n${selectedAddressList.value.address_2}\n${selectedAddressList.value.address_3}\n${selectedAddressList.value.address_4},${selectedAddressList.value.country},${selectedAddressList.value.city},${selectedAddressList.value.postal_code}`
+}
+// 页面初始化
+let checkShipments = []
+let checkShipmentsSubmit = []
+let checkShipmentsdata = []
+let checkShipmentsInfo = {}
+let checkShipmentsSubmitInfoData = {}
+const getInitBookingData = () => {
+  $api
+  .InitCreateBooking({
+    serial_no: a != undefined ? a: ''
+  })
+  .then((res: any) => {
+    if (res.code === 200) {
+      if(res.data.msg.includes('No Eligible Shipments for Booking')) {
+        NoEligibleVisible.value = true
+        isDisabled.value = true
+      }
+      if(res.data.msg.includes('success')) {
+        bookingTableRef.value.getTableData(res.data.data.tableData)
+        if(a) {
+          status.value = res.data.data.status
+          booking.value = res.data.data.booking_no
+          Requirements.value = res.data.data.special_requirements
+          modetypeValue.value = res.data.data.delivery_mode
+          DateValue.value = res.data.data.delivery_date
+          DeliveryTime.value = res.data.data.delivery_time
+          Modification.value = res.data.data.modify_reason
+          selectedAddressList.value = res.data.data.delivery_address_detail
+          isselectedAddress.value = ''
+          const sync_key = res.data.data.delivery_address_detail.sync_key
+          checkShipments = res.data.data.tableData.map(item => ({ consignee_id: item.consignee_id, country: item.dc_country}))
+          checkShipmentsdata = Object.keys(checkShipments?.[0])
+          checkShipmentsSubmit = Object.keys(res.data.data.tableData?.[0])
+          checkShipmentsdata.forEach((item) => {
+            Object.assign(checkShipmentsInfo, {
+              [item]: checkShipments.map((row) => row[item] )
+            })
+          })
+          checkShipmentsSubmit.forEach((item) => {
+            if(item == 'serial_no') return
+            Object.assign(checkShipmentsSubmitInfoData, {
+              [item]: res.data.data.tableData.map((row) => {
+                if(row[item] == null){
+                  return ''
+                } else {
+                  return row[item]
+                }
+              } )
+            })
+          })
+          $api
+          .getAddressBookList({
+            ...checkShipmentsInfo,
+            delivery_serial_no: a? a: ''
+          })
+          .then((res: any) => {
+            if (res.code === 200) {
+              ManageAddressList.value = res.data
+              ManageAddressList.value.forEach((item, index) => {
+                if(item.sync_key === sync_key) {
+                  selectedAddressList.value = item
+                  isselectedAddress.value = index
+                  Addressradio.value = index
+                }
+              })
+            }
+          })
+        }
+      }
+    }
+  })
+}
+// 查询Shipments
+const SearchShipment = () => {
+  let obj = {
+    text_search: CreateNewBOokingSearch.value,
+    vessel: VesselNametest.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] : '',
+    ata_end: ATATimeList.value != null ? ATATimeList.value[1] : ''
+  }
+  bookingTableRef.value.searchTableData(obj)
+}
+
+// 选择shipments获取address book
+const isrecommenddate = ref()
+const isConsistent = ref(false)
+// 判断date是否一致
+const areAllDateRangesSame = (date: any) => { 
+  const firstRange = date[0].date_range
+  return date.every(item => {
+    const currentRange = item.date_range
+    if(currentRange.length === firstRange.length &&
+      currentRange.every((date, index) => date === firstRange[index])) {
+        return true
+      } else {
+        isConsistent.value = true
+        RecommendateList.value = date.map(item => ({Hbol: item.Hbol}))
+        return false
+      }
+  })
+}
+// 遍历日期
+const getDateRangeArray = (startDateStr, endDateStr) => {
+  if(startDateStr != '' && endDateStr != '') {
+    const parseDate = (str) => {
+      const [year, month, day] = str.split('.').map(Number)
+      return new Date(year, month - 1, day) // 月份从0开始计数
+    }
+
+    const startDate = parseDate(startDateStr)
+    const endDate = parseDate(endDateStr)
+
+    const dateArray = []
+    const currentDate = new Date(startDate)
+    
+    while (currentDate <= endDate) {
+      // 格式化为 YYYY.MM.DD
+      const formattedDate = [
+        currentDate.getFullYear(),
+        (currentDate.getMonth() + 1).toString().padStart(2, '0'),
+        currentDate.getDate().toString().padStart(2, '0')
+      ].join('.')
+      dateArray.push(formattedDate)
+      currentDate.setDate(currentDate.getDate() + 1)
+    }
+
+    return dateArray
+  }
+}
+const selectChangeEvent = (val: any, date: any, submitInfo: any) => {
+  getAddressListData.value = {...val}
+  checkShipmentsSubmitInfo.value = {...submitInfo}
+  if(date.length != 0) {
+    isrecommenddate.value = areAllDateRangesSame(date)
+    if(isrecommenddate.value) {
+      specialDates.value = getDateRangeArray(date[0].date_range[0],date[0].date_range[1])
+    }
+  } else {
+    specialDates.value = []
+  }
+}
+
+// 点击 Address Book
+const handleClickAddress = () => {
+  if(!isAddNewAddressVisible.value) {
+    ManageVisible.value = true
+  }
+  const nonDeletedAddresses = ManageAddressList.value.filter(
+    item => item.contact_type !== 'Delete'
+  )
+  if(a == undefined) {
+    $api
+    .getAddressBookList({
+      ...getAddressListData.value,
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        const newAddresses = res.data.filter(
+          (apiAddr: any) => 
+            !nonDeletedAddresses.some(localAddr => localAddr.id === apiAddr.id)
+        )
+        ManageAddressList.value = [...nonDeletedAddresses, ...newAddresses]
+        ManageVisible.value = true
+      }
+    })
+  }
+}
+
+//选择日期
+const changeDatePicker = (val:any) => {
+  if(specialDates.value.length != 0 ){
+    if(isConsistent.value) {
+      isRecommendDate.value = true
+      recommendateWarning.value = 'This date for following shipments is outside recommended period.'
+      isConsistent.value = false
+    } else {
+      if(!specialDates.value.includes(val)) {
+        isRecommendDate.value = true
+        recommendateWarning.value = 'This date is outside our recommended period.'
+        isConsistent.value = false
+      }
+    }
+  } else {
+    isRecommendDate.value = true
+    recommendateWarning.value = 'This date for following shipments is outside recommended period.'
+    isConsistent.value = false
+  }
+}
+// 新增地址
+const handleClickAddNewAddress = () => {
+  currentEditAddress.value = null
+  AddNewAddressVisible.value = true
+  isAddNewAddressVisible.value = false
+  AddressLine1.value = ''
+  AddressLine2.value = ''
+  AddressLine3.value = ''
+  AddressLine4.value = ''
+  CountryCode.value = ''
+  CityCode.value = ''
+  PostalCode.value = ''
+  ContactPerson.value = ''
+  ContactNumber.value = ''
+}
+// 编辑地址
+const handleClickEditAddress = (val:any) => {
+  currentEditAddress.value = val
+  AddNewAddressVisible.value = true
+  AddressLine1.value = val.address_1
+  AddressLine2.value = val.address_2
+  AddressLine3.value = val.address_3
+  AddressLine4.value = val.address_4
+  CountryCode.value = val.country
+  CityCode.value = val.city
+  PostalCode.value = val.postal_code
+  ContactPerson.value = val.contact_person
+  ContactNumber.value = val.contact_number
+  const updatedAddress = {
+    ...val,
+    contact_type: val.contact_type == 'Unedit'? 'Modify' : 'Add'
+  }
+  updateAddressList(updatedAddress)
+}
+// 删除地址
+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' })
+  }
+  
+  ManageAddressList.value = ManageAddressList.value.filter((item) => item[key] !== val[key])
+
+  if (selectedAddressList.value?.[key] === val[key]) {
+    selectedAddressList.value = undefined
+    isselectedAddress.value = null
+  }
+}
+// 保存
+const SubmitBooking = () => {
+  let datetwo = ''
+  if (a != undefined) {
+    const value = DateValue.value
+    const parts = value.includes(' ') 
+      ? value.split(' ')[0].split('-') 
+      : value.split('.')
+    datetwo = `${parts[1]}/${parts[2]}/${parts[0]}`
+  } else {
+    const parts = DateValue.value.split('.')
+    datetwo = `${parts[1]}/${parts[2]}/${parts[0]}`
+  }
+  let result = []
+  if(submitAddressList.value.length != 0) {
+    result = submitAddressList.value.filter(item => 
+      item.id === selectedAddressList.value.id || 
+      item.contact_type === 'Delete'
+    )
+    const key = selectedAddressList.value.contact_type == 'Unedit' ? 'sync_key' : 'id'
+    const objExists = submitAddressList.value.some(item => item[key] === selectedAddressList.value[key])
+    if (!objExists) {
+      result.push(selectedAddressList.value)
+    }
+  }else {
+    result.push(selectedAddressList.value)
+  }
+  let addressshipment = []
+  let addressshipmentinfo = {}
+  addressshipment = Object.keys(selectedAddressList.value)
+  addressshipment.forEach((item) => {
+    Object.assign(addressshipmentinfo, {
+      [item]: result.map((row) => row[item] )
+    })
+  })
+  $api
+  .SaveBookingList({
+    serial_no: a != undefined ? a : '',
+    ...checkShipmentsSubmitInfo.value,
+    ...addressshipmentinfo,
+    ...checkShipmentsSubmitInfoData,
+    delivery_time: DeliveryTime.value,
+    delivery_mode: modetypeValue.value,
+    delivery_date: datetwo,
+    delivery_address: delivery_address,
+    special_requirements: Requirements.value,
+    modify_reason: Modification.value
+  })
+  .then((res: any) => {
+    if (res.code === 200 && res.data.msg == 'success') {
+      SaveedVisible.value = true
+      setTimeout(() => {
+        SaveedVisible.value = false
+        router.push({ name: 'Destination Delivery'})
+      }, 3000)
+    } else {
+        UnableSaveVisible.value = true
+        missingmessage.value = res.data.msg
+    }
+  })
+}
+
+onMounted(() => { 
+  getInitBookingData()
+})
+</script>
+
+<template>
+  <div>
+    <div class="Title">
+      <div v-if="a == undefined">Create New Booking</div>
+      <div v-else>Modify Booking</div>
+      <div class="flex">
+        <el-button @click="CancelRulesVisible = true" class="el-button--default create-button"><span class="font_family icon-icon_return_b"></span> Cancel</el-button>
+        <el-button :disabled="isNotSubmit" @click="SubmitBooking" class="el-button--main create-button"><span class="font_family icon-icon_submit_b"></span> Submit</el-button>
+      </div>
+    </div>
+    <div class="booking-info" v-if="a != undefined">
+      <div class="booking-no">
+        <span class="no">Booking No.{{ booking }}</span>
+        <v-tag class="tag" type="Pending Approval">{{ status }}</v-tag>
+      </div>
+    </div>
+    <el-divider v-if="a != undefined" style="margin: 8px 0" />
+    <!-- Select Shipments -->
+    <div class="select_shipments">
+      <div v-if="isNotClickAddress" class="alertIndormation">Please select same consignee with same delivery information </div>
+      <div style="margin-bottom: 16px;"><span class="stars_red">*</span>Select Shipments<span class="title_warning">*Please select items with the same consignee.</span></div>
+      <div class="shipments_search" v-if="a == undefined">
+        <div class="flex">
+          <el-input
+          placeholder="Enter Booking/HBL/PO/Carrier Booking No. "
+          v-model="CreateNewBOokingSearch"
+          style="width: 34%;"
+          class="log_input"
+        >
+          <template #prefix>
+            <span class="iconfont_icon">
+              <svg class="iconfont icon_search" aria-hidden="true">
+                <use xlink:href="#icon-icon_search_b"></use>
+              </svg>
+            </span>
+          </template>
+          </el-input>
+          <div class="input-with-label" style="margin: 0 8px;">
+            <!-- <AutoSelect ASPlaceholder="Input Vessel Name" :ASValue="VesselName" @changeFocus="changeFocus"></AutoSelect> -->
+            <el-input
+              placeholder="Shipper"
+              v-model="ShipperValue"
+              @focus="changeFocustest('Shipper',true)"
+              @blur="changeFocustest('Shipper',false)"
+              class="log_input"
+            >
+            </el-input>
+            <span v-if="showLabelShipper" class="border-label">Shipper</span>
+          </div>
+          <div class="input-with-label" style="margin-right: 8px;">
+            <!-- <AutoSelect ASPlaceholder="Input Vessel Name" :ASValue="VesselName" @changeFocus="changeFocus"></AutoSelect> -->
+            <el-input
+              placeholder="Consignee"
+              v-model="ConsigneeValue"
+              @focus="changeFocustest('Consignee',true)"
+              @blur="changeFocustest('Consignee',false)"
+              class="log_input"
+            >
+            </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" :isETA="true"
+            CalendarWidth="100%"
+            @DateRangeChange="DateRangeChangeETA" @DateChangeFocus="DateChangeFocusETA"></CalendarDate>
+            <span v-if="showETAlabel" class="border-label">ETA</span>
+          </div>
+          <div class="input-with-label" style="margin: 0 8px">
+            <CalendarDate :isNeedFooter="false"
+            CalendarWidth="100%" @DateRangeChange="DateRangeChangeATA" @DateChangeFocus="DateChangeFocusATA"></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> -->
+            <el-input
+              placeholder="Input Vessel Name"
+              v-model="VesselNametest"
+              @focus="changeFocustest('Vessel',true)"
+              @blur="changeFocustest('Vessel',false)"
+              class="log_input"
+            >
+            </el-input>
+            <span v-if="showLabelVessel" class="border-label">Vessel Name</span>
+          </div>
+          <div style="width: 108px;"></div>
+        </div>
+      </div>
+      <NewbookingTable ref="bookingTableRef" 
+        @selectChangeEvent="selectChangeEvent"
+      ></NewbookingTable>
+    </div>
+    <!-- Delivery Information -->
+    <div class="Delivery">Delivery Information</div>
+    <div class="delivery_address">
+      <div class="deliverty_flex">
+        <div><span class="stars_red">*</span>Delivery Address</div>
+        <el-button :disabled="isNotClickAddress" @click="handleClickAddress" class="el-button--noborder--configuration"><span class="font_family icon-icon_location_b"></span> Address Book</el-button>
+      </div>
+      <div class="empty_address" v-if="isselectedAddress == null">
+        <img :src="AddNewAddress" width="48" style="margin-bottom: 8px;" />
+        <el-button :disabled="isNotClickAddress" class="el-button--main" @click="AddNewAddressDelivery"> + Add New Address</el-button>
+      </div>
+      <div v-else class="addressradio" style="padding-bottom: 4px;">
+        <el-radio class="selectedAddress">
+          <div>
+            <div class="radio_top">
+              <div class="radio_title">{{ selectedAddressList.contact_person }} ({{ selectedAddressList.contact_number }})</div>
+            </div>
+            <div class="radio_content">
+              <div v-if="selectedAddressList.address_1 != null && selectedAddressList.address_1 != ''" class="radio_content_text">{{ selectedAddressList.address_1 }}</div>
+              <div v-if="selectedAddressList.address_2 != null && selectedAddressList.address_2 != ''" class="radio_content_text">{{ selectedAddressList.address_2 }}</div>
+              <div v-if="selectedAddressList.address_3 != null && selectedAddressList.address_3 != ''" class="radio_content_text">{{ selectedAddressList.address_3 }}</div>
+              <div v-if="selectedAddressList.address_4 != null && selectedAddressList.address_4 != ''" class="radio_content_text">{{ selectedAddressList.address_4 }}</div>
+              <div class="radio_content_text">{{ selectedAddressList.country }},{{ selectedAddressList.city }},{{ selectedAddressList.postal_code }}</div>
+            </div>
+          </div>
+        </el-radio>
+      </div>
+      <div class="delivery_type">
+        <div>
+          <div class="delivery_type_title"><span class="stars_red">*</span>Mode Type</div>
+          <el-select
+            v-model="modetypeValue"
+            placeholder="Select"
+            :disabled="isNotClickAddress"
+            style="width: 240px"
+          >
+            <el-option
+              v-for="item in ModeType"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </div>
+        <div style="margin: 0 12px;">
+          <div class="delivery_type_title"><span class="stars_red">*</span>Preferred Delivery Date</div>
+          <a-date-picker
+            v-model:value ="DateValue"
+            :disabled="isNotClickAddress"
+            @change="changeDatePicker"
+            :showToday="false"
+            style="width: 240px;"
+            :format="userStore.dateFormat"
+            placeholder="Please Select Date"
+            valueFormat="YYYY.MM.DD"
+          >
+            <template #renderExtraFooter>
+              <div class="recommended"><div class="recommend_color"></div>Recommended delivery date</div>
+            </template>
+            <template #dateRender="{ current }">
+              <div class="ant-picker-cell-inner" :style="getCurrentStyle(current)">
+                {{ current.date() }}
+              </div>
+            </template>
+          </a-date-picker>
+        </div>
+        <div>
+          <div class="delivery_type_title"><span class="stars_red">*</span>Delivery Time</div>
+          <el-time-select
+            v-model="DeliveryTime"
+            :disabled="isNotClickAddress"
+            start="00:00"
+            step="00:30"
+            end="23:30"
+            style="width: 240px;"
+            prefix-icon=""
+            placeholder="Please Select Time"
+          ></el-time-select>
+        </div>
+      </div>
+      <div class="delivery_type_title">Special Requirements</div>
+      <div class="flex" style="margin-top: 8px;">
+        <el-button :disabled="isNotClickAddress" class="el-button--grey" @click="handleclickbutton('Tail Lift Required')">Tail Lift Required</el-button>
+        <el-button :disabled="isNotClickAddress" class="el-button--grey" @click="handleclickbutton('Side Loading')">Side Loading</el-button>
+        <el-button :disabled="isNotClickAddress" class="el-button--grey" @click="handleclickbutton('Forklift Required')">Forklift Required</el-button>
+        <el-button :disabled="isNotClickAddress" class="el-button--grey" @click="handleclickbutton('Special Equipment')">Special Equipment</el-button>
+      </div>
+      <el-input :disabled="isNotClickAddress" v-model="Requirements" placeholder="Eenter any additional requirements or notes..." type="textarea" style="margin-top: 8px;" :rows="4"></el-input>
+      <div v-if="a!= undefined" class="delivery_type_title" style="margin-top: 16px;"><span class="stars_red">*</span>Modification Reason</div>
+      <el-input v-if="a!=undefined" :disabled="isNotClickAddress" v-model="Modification" placeholder="Eenter any additional requirements or notes..." type="textarea" style="margin-top: 8px;" :rows="4"></el-input>
+    </div>
+    <el-dialog
+      v-model="ManageVisible"
+      width="640"
+      class="ManageDialog"
+      :show-close="false"
+    >
+      <el-radio-group v-model="Addressradio">
+        <el-radio v-for="(item, index) in ManageAddressList" :key="index" :value="index">
+          <div class="addressradio">
+            <div class="radio_top">
+              <div class="radio_title">{{ item.contact_person }}({{ item.contact_number }})</div>
+              <div v-if="item.create_user == 'Online_D_Address'">
+                <el-button @click="handleClickEditAddress(item)" class="el-button--blue"><span class="font_family icon-icon_edit_b"></span></el-button>
+                <el-button @click="handleDeleteAddress(item)" class="el-button--blue"><span class="font_family icon-icon_delete_b"></span></el-button>
+              </div>
+            </div>
+            <div class="radio_content">
+              <div v-if="item.address_1 != null && item.address_1 != ''" class="radio_content_text">{{ item.address_1 }}</div>
+              <div v-if="item.address_2 != null && item.address_2 != ''" class="radio_content_text">{{ item.address_2 }}</div>
+              <div v-if="item.address_3 != null && item.address_3 != ''" class="radio_content_text">{{ item.address_3 }}</div>
+              <div v-if="item.address_4 != null && item.address_4 != ''" class="radio_content_text">{{ item.address_4 }}</div>
+              <div class="radio_content_text">{{ item.country }},{{ item.city }},{{ item.postal_code }}</div>
+            </div>
+          </div>
+        </el-radio>
+      </el-radio-group>
+      <template #header>
+        <div class="my-header">
+          <div class="header_Title">Manage Address</div>
+          <el-button  @click="handleClickAddNewAddress" class="el-button--noborder--configuration">+ Add New Address</el-button>
+        </div>
+      </template>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button class="el-button--default dialog-button" @click="ManageVisible = false">Cancel</el-button>
+          <el-button class="el-button--dark dialog-button" @click="changeAddressRadio">
+            OK
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+    <el-dialog
+      v-model="AddNewAddressVisible"
+      width="640"
+      title="Add New Delivery Address"
+      :show-close="false"
+    >
+      <div class="diaolog_add_title"><span class="stars_red">*</span>Address</div>
+      <el-tooltip
+        class="box-item"
+        :visible="isShowLimitLine1"
+        effect="dark"
+        content="Maximum character limit reached (45 characters)"
+        placement="bottom"
+      >
+        <el-input @input="handleAddressLine1" class="inputmargin2" placeholder="Line 1" v-model="AddressLine1">
+          <template #suffix>
+            <div class="character-counter" :class="{ 'limit-reached': isShowLimitLine1Style }">
+              {{ AddressLine1.length }}/45
+            </div>
+          </template>
+        </el-input>
+      </el-tooltip>
+      <el-tooltip
+        class="box-item"
+        :visible="isShowLimitLine2"
+        effect="dark"
+        content="Maximum character limit reached (45 characters)"
+        placement="bottom"
+      >
+        <el-input @input="handleAddressLine2" class="inputmargin2" placeholder="Line 2" v-model="AddressLine2">
+          <template #suffix>
+            <div class="character-counter" :class="{ 'limit-reached': isShowLimitLine2Style }">
+              {{ AddressLine2.length }}/45
+            </div>
+          </template>
+        </el-input>
+      </el-tooltip>
+      <el-tooltip
+        class="box-item"
+        :visible="isShowLimitLine3"
+        effect="dark"
+        content="Maximum character limit reached (45 characters)"
+        placement="bottom"
+      >
+        <el-input @input="handleAddressLine3" class="inputmargin2" placeholder="Line 3" v-model="AddressLine3">
+          <template #suffix>
+            <div class="character-counter" :class="{ 'limit-reached': isShowLimitLine3Style }">
+              {{ AddressLine3.length }}/45
+            </div>
+          </template>
+        </el-input>
+      </el-tooltip>
+      <el-tooltip
+        class="box-item"
+        :visible="isShowLimitLine4"
+        effect="dark"
+        content="Maximum character limit reached (45 characters)"
+        placement="bottom"
+      >
+        <el-input style="margin-bottom: 16px;" @input="handleAddressLine4" class="inputmargin2" placeholder="Line 4" v-model="AddressLine4">
+          <template #suffix>
+            <div class="character-counter" :class="{ 'limit-reached': isShowLimitLine4Style }">
+              {{ AddressLine4.length }}/45
+            </div>
+          </template>
+        </el-input>
+      </el-tooltip>
+      <div class="flex">
+        <div style="width: 33%;">
+          <div class="diaolog_add_title"><span class="stars_red">*</span>Country Code</div>
+          <el-select
+            v-model="CountryCode"
+            filterable
+            remote
+            @focus="getAddressCountryCityData('country')"
+            placeholder="Select Country"
+            :remote-method="querySearchCountry"
+            :loading="Countryloading"
+            class="inputmargin"
+          >
+            <el-option
+              v-for="item in Countryoptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </div>
+        <div style="margin: 0 9px;width: 33%;">
+          <div class="diaolog_add_title"><span class="stars_red">*</span>City Code</div>
+          <el-select
+            v-model="CityCode"
+            filterable
+            remote
+            @focus="getAddressCountryCityData('city')"
+            placeholder="Select City"
+            :remote-method="querySearchCity"
+            :loading="cityloading"
+            class="inputmargin"
+          >
+            <el-option
+              v-for="item in Cityoptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </div>
+        <div style="width: 33%;">
+          <div class="diaolog_add_title"><span class="stars_red">*</span>Postal Code</div>
+          <el-input class="inputmargin" placeholder="Enter postal code" v-model="PostalCode"></el-input>
+        </div>
+      </div>
+      <div class="diaolog_add_title_bold">Contact Information</div>
+      <div class="flex">
+        <div style="margin-right: 9px;width: 50%;">
+          <div class="diaolog_add_title"><span class="stars_red">*</span>Contact Person</div>
+          <el-input style="margin-top: 4px;" placeholder="Name" v-model="ContactPerson"></el-input>
+        </div>
+        <div style="width: 50%;">
+          <div class="diaolog_add_title"><span class="stars_red">*</span>Contact Number</div>
+          <el-input style="margin-top: 4px;" placeholder="Mobile Numer" v-model="ContactNumber"></el-input>
+        </div>
+      </div>
+      <template #footer>
+        <div class="dialog-footer" style="justify-content: end;">
+          <el-button class="el-button--default dialog-button" @click="AddNewAddressVisible = false">Cancel</el-button>
+          <el-button class="el-button--dark dialog-button" @click=SaveNewAddress>
+            Save Address
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+    <!-- 无站点权限弹框 -->
+    <el-dialog
+      v-model="NoPermissionVisible"
+      :show-close="false"
+      align-center
+      class="notDialog"
+      width="480"
+    >
+      <div class="flex_center">
+        <img :src="NotAvailable" width="100" />
+        <div class="alert_title">Destination Delivery Service Not Available</div>
+        <div class="alert_text">Destination delivery service is not yet available for your shipment destinations. To request this service, please contact the destination office first.</div>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button style="width: 80px;" class="el-button--dark" @click="NoPermissionVisible = false">OK</el-button>
+        </div>
+      </template>
+    </el-dialog>
+    <!-- 无符合条件弹框 -->
+    <el-dialog
+      v-model="NoEligibleVisible"
+      :show-close="false"
+      align-center
+      class="notDialog"
+      width="480"
+    >
+      <div class="flex_center">
+        <img :src="NotShipment" width="100" />
+        <div class="alert_title">No Eligible Shipments for Booking</div>
+        <div class="alert_text">Your shipments are currently outside the booking window. Eligible shipments will appear here when booking window opens.</div>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button style="width: 80px;" class="el-button--dark" @click="NoEligibleVisible = false">OK</el-button>
+        </div>
+      </template>
+    </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; margin-top: 20px">Saved successfully</div>
+    </el-dialog>
+    <!-- 保存失败 -->
+    <el-dialog v-model="UnableSaveVisible" width="480">
+      <div>{{ missingmessage }}</div>
+      <div>Please complete all required fields.</div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button
+            class="el-button--danger"
+            @click="UnableSaveVisible = false"
+            style="width: 100px"
+          >
+            OK
+          </el-button>
+        </div>
+      </template>
+      <template #header>
+        <div class="cancel_header">
+          <span class="iconfont_icon">
+            <svg class="iconfont icon_danger" aria-hidden="true">
+              <use xlink:href="#icon-icon_fail_fill_b"></use>
+            </svg>
+          </span>
+          Unable to Save
+        </div>
+      </template>
+    </el-dialog>
+    <!-- 取消保存 -->
+    <el-dialog v-model="CancelRulesVisible" width="480">
+      <div style="font-weight: 400">You have unsaved changes.</div>
+      <div style="font-weight: 400">Are you sure you want to leave this page?</div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="default" @click="CancelRulesVisible = false" style="width: 100px"
+            >Cancel</el-button
+          >
+          <el-button class="el-button--warning" @click="router.back()" style="width: 100px">
+            OK
+          </el-button>
+        </div>
+      </template>
+      <template #header>
+        <div class="cancel_header">
+          <span class="iconfont_icon iconfont_warning">
+            <svg class="iconfont icon_warning" aria-hidden="true">
+              <use xlink:href="#icon-icon_tipsfilled_b"></use>
+            </svg>
+          </span>
+          Unsaved Changes
+        </div>
+      </template>
+    </el-dialog>
+    <!-- 當選擇非建議日期内的date時給出的提示 -->
+    <el-dialog v-model="isRecommendDate" width="480">
+      <div>{{ recommendateWarning }}</div>
+      <div style="margin-top: 4px;">Additional storage fees may apply.</div>
+      <div style="margin-top: 8px;">
+        <div class="Notice" v-for="(item, index) in RecommendateList" :key="index">HOBL: {{ item.Hbol }}</div>
+      </div>
+      <template #footer>
+        <div class="dialog-footer" style="justify-content: end;">
+          <el-button
+            class="el-button--warning"
+            @click="isRecommendDate = false"
+            style="width: 100px"
+          >
+            OK
+          </el-button>
+        </div>
+      </template>
+      <template #header>
+        <div class="cancel_header">
+          <span class="iconfont_icon">
+            <svg class="iconfont iconfont_warning" aria-hidden="true">
+              <use xlink:href="#icon-icon_tipsfilled_b"></use>
+            </svg>
+          </span>
+          Additional Fees Notice
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.Title {
+  display: flex;
+  height: 68px;
+  border: 1px solid var(--color-border);
+  border-top: none;
+  border-width: 1px 0 1px 0;
+  font-size: var(--font-size-6);
+  font-weight: 700;
+  padding: 0 24px;
+  align-items: center;
+  justify-content: space-between;
+}
+.flex {
+  display: flex;
+}
+.deliverty_flex {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 4px;
+}
+.con-icon_submit_b {
+  transform: rotate(-45deg);
+}
+.create-button {
+  width: 115px;
+  height: 40px;
+}
+.select_shipments {
+  border: 1px solid var(--color-border);
+  margin: 17px 24px 12px 24px;
+  padding: 9px 16px 16px 16px;
+  border-radius: 12px;
+  position: relative;
+}
+.alertIndormation {
+  position: absolute;
+  left: 35%;
+  top: 30%;
+  z-index: 1213;
+  background-color: var(--color-btn-default-dark-bg);
+  color: var(--color-btn-default-bg-color);
+  padding: 2px 4px;
+  border-radius: 3px;
+  box-shadow: 1px 1px 6px 0 rgba(0, 0, 0, 0.25);
+}
+.delivery_address {
+  border: 1px solid var(--color-border);
+  margin: 0 24px 12px 24px;
+  padding: 9px 16px 16px 16px;
+  border-radius: 12px;
+}
+.stars_red {
+  color: var(--color-danger);
+}
+.title_warning {
+  margin-left: 8px;
+  color: var(--color-neutral-2);
+  font-size: 12px;
+}
+.shipments_search {
+  margin: 0 0 8px 0;
+}
+:deep(.log_input .el-input__wrapper) {
+  box-shadow: 0 0 0 1px var(--color-select-border) inset;
+  border-radius: 6px;
+}
+.input-with-label {
+  position: relative;
+  display: inline-block;
+  width: 34%;
+}
+.border-label {
+  position: absolute;
+  top: -7px;
+  left: 10px;
+  background: white; /* 用背景色覆盖边框 */
+  padding: 0 5px;
+  font-size: 12px;
+  color: var(--color-neutral-2);
+  z-index: 1;
+}
+.vessel_input {
+  height: 40px;
+  width: 100%;
+}
+:deep(.el-select__wrapper.is-filterable) {
+  height: 40px;
+}
+.Delivery {
+  font-size: 18px;
+  font-weight: 700;
+  margin: 11px 0 14px 24px;
+}
+.empty_address {
+  height: 122px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+  border-radius: 12px;
+  border: 2px dashed #ED6D00;
+  background: rgba(237, 109, 0, 0.05);
+}
+.delivery_type {
+  display: flex;
+  margin: 16px 0;
+}
+.delivery_type_title {
+  font-size: 14px;
+  font-weight: 700;
+  margin-bottom: 4px;
+}
+.recommended {
+  background: #F5F7FA;
+  border-bottom: 1px solid var(--color-border);
+  height: 24px;
+  display: flex;
+  align-items: center;
+  padding-left: 16px;
+  font-size: 10px;
+  color: var(--color-neutral-2);
+}
+.recommend_color {
+  width: 8px;
+  height: 8px;
+  border-radius: 1px;
+  opacity: 0.3;
+  background: #00A870;
+  margin-right: 4px;
+}
+.reco_button {
+  height: 56px;
+  display: flex;
+  align-items: center;
+  justify-content: end;
+  padding: 0 16px;
+}
+.recomend-button {
+  width: 80px;
+  height: 40px;
+}
+.el-button--grey {
+  padding: 10px 16px;
+  border-radius: 15px;
+}
+:deep(.el-textarea) {
+  .el-textarea__inner {
+    resize: none; // 去除右下角图标
+    padding: 5px 7px 5px 10px;
+    box-shadow: 0 0 0 1px var(--color-select-border) inset;
+    color: var(--color-neutral-1);
+  }
+}
+.dialog-button {
+  width: 115px;
+  height: 40px;
+}
+.my-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  height: 48px;
+}
+:deep(.ManageDialog .el-dialog__header) {
+  padding: 0 16px;
+  height: 48px;
+}
+:deep(.ManageDialog) {
+  max-height: 800px;
+  overflow-y: scroll;
+}
+.header_Title {
+  font-size: 18px;
+}
+.addressradio {
+  background-color: #F8F9FD;
+  border-radius: 12px;
+  padding: 13px 8px 16px 16px;
+}
+.radio_top {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+.radio_title {
+  font-size: 14px;
+  font-weight: 700;
+}
+.radio_content_text {
+  color: var(--color-neutral-2);
+  font-size: 14px;
+  height: 21px;
+}
+.radio_content {
+  margin-bottom: 8px;
+}
+:deep(.el-radio-group) {
+  display: block;
+}
+:deep(.el-radio) {
+  display: flex;
+  min-height: 32px;
+  margin-bottom: 8px;
+  border-radius: 12px;
+  margin-right: 0;
+  height: fit-content;
+  line-height: 32px;
+  align-items: baseline;
+}
+:deep(.selectedAddress) {
+  align-items: start;
+}
+:deep(.selectedAddress .radio_title) {
+  margin-top: -8px;
+}
+.el-button--blue {
+  width: 24px;
+  height: 24px;
+}
+.diaolog_add_title {
+  color: var(--color-neutral-2);
+  font-size: 12px;
+}
+:deep(.el-input__wrapper) {
+  height: 40px;
+}
+.inputmargin {
+  margin: 4px 0 16px 0;
+}
+.inputmargin2 {
+  margin-top: 4px ;
+}
+.diaolog_add_title_bold {
+  color: var(--color-neutral-1);
+  font-size: 14px;
+  font-weight: 700;
+  margin-bottom: 11px;
+}
+.dialog-footer {
+  display: flex;
+  align-items: center;
+  justify-content: end;
+}
+.flex_center {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+  padding: 0 19px;
+}
+.alert_title {
+  text-align: center;
+  font-size: 18px;
+  font-weight: 700;
+}
+.alert_text {
+  text-align: center;
+  font-size: 14px;
+  line-height: 21px;
+  margin-top: 8px;
+}
+:deep(.notDialog .el-dialog__header) {
+  display: none;
+}
+.iconfont_warning {
+  fill: var(--color-warning)
+}
+.Notice {
+  font-weight: 700;
+  margin-top: 4px;
+}
+.booking-info {
+  display: flex;
+  align-items: center;
+  height: 48px;
+  padding: 0 16px;
+  margin: 17px 24px 8px 24px;
+  border-radius: 12px;
+  background-image: var(--color-booking-info-linear-bg);
+
+  .booking-no {
+    display: flex;
+    align-items: center;
+    .no {
+      margin-top: 2px;
+      font-size: 18px;
+      font-weight: 700;
+      line-height: 21px;
+    }
+    .tag {
+      margin-left: 8px;
+    }
+  }
+  .created-time {
+    margin-top: 8px;
+    font-size: 12px;
+    color: var(--color-neutral-2);
+  }
+}
+:deep(.el-select__wrapper.is-filterable) {
+  height: 32px !important;
+}
+:deep(.inputmargin .el-select__wrapper.is-filterable) {
+  height: 40px !important;
+}
+.character-counter {
+  color: var(--color-neutral-3);
+  font-size: 12px;
+}
+.limit-reached {
+  color: var(--color-warning-2);
+}
+</style>

+ 259 - 0
src/views/DestinationDelivery/src/components/CreateNewBooking/src/components/NewbookingTable.vue

@@ -0,0 +1,259 @@
+<script setup lang="ts">
+import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
+import { useRowClickStyle } from '@/hooks/rowClickStyle'
+import { formatTimezone } from '@/utils/tools'
+import { ref, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+
+const router = useRouter()
+const { currentRoute } = router
+const { value } = currentRoute
+const { query } = value
+const { a } = query
+
+const tableLoadingTable = ref(false)
+const tableLoadingColumn = ref(false)
+const isNotActivated = ref(false)
+
+const tableData = ref<VxeGridProps<any>>({
+  border: true,
+  round: true,
+  maxHeight: 288,
+  columns: [],
+  data: [],
+  scrollY: { enabled: true, oSize: 20, gt: 30 },
+  emptyText: ' ',
+  showHeaderOverflow: true,
+  showOverflow: true,
+  headerRowStyle: {
+    backgroundColor: 'var(--color-table-header-bg)'
+  },
+  columnConfig: { resizable: true, useKey: true },
+  rowConfig: { isHover: true, isCurrent: true },
+})
+
+const tableRef = ref<VxeGridInstance | null>(null)
+
+const handleColumns = (columns: any) => {
+  const newColumns = columns.map((item: any) => {
+    let curColumn: any = {
+      title: item.title,
+      field: item.field,
+      width: '150px'
+    }
+    // 格式化
+    if (item.formatter === 'date') {
+      curColumn = {
+        ...curColumn,
+        formatter: ({ cellValue }: any) => formatTimezone(cellValue)
+      }
+    }  else if (item.type === 'link') {
+      curColumn = {
+        ...curColumn,
+        slots: { default: 'trackingNo' }
+      }
+    } else if (item.type == 'recommend') {
+      curColumn = {
+        ...curColumn,
+        formatter: ({ cellValue }: any) => {
+          const array = cellValue.split("-")
+          return `${formatTimezone(array[0])} - ${formatTimezone(array[1])}`
+        }
+      }
+    }
+    return curColumn
+  })
+  return newColumns
+}
+// 获取表格列
+const getTableColumns = async () => {
+  tableLoadingColumn.value = true
+  await $api.BookingTableColumn().then((res: any) => {
+    if (res.code === 200) {
+      if(a == undefined) {
+        tableData.value.columns = [
+          { type: 'checkbox', width: 50, fixed: 'left' },
+          ...handleColumns(res.data.TrackingTableColumns)
+        ]
+      }else {
+        tableData.value.columns = [
+          ...handleColumns(res.data.TrackingTableColumns)
+        ]
+      }
+    }
+  })
+  nextTick(() => {
+    tableLoadingColumn.value = false
+  })
+}
+// 获取表格数据
+const getTableData = (val: any) => {
+  tableData.value.data = val
+}
+
+// 搜索表格数据
+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
+    }
+  })
+}
+
+const emits = defineEmits(['selectChangeEvent'])
+// 选中
+let checkShipments = []
+let checkShipmentsSubmit = []
+let checkRecommend = []
+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];
+  for (let i = 1; i < arrary.length; i++) {
+    if (arrary[i].consignee_id !== first.consignee_id) {
+        return false;
+    }
+  }
+  return [first];
+}
+const selectChangeEvent = () => {
+  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  }))
+    const array = checkUniformArray(checkShipments)
+    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] )
+        })
+      })
+      checkShipmentsSubmit.forEach((item) => {
+        Object.assign(checkShipmentsSubmitInfo, {
+          [item]: records.map((row) => {
+            if(row[item] == null){
+              return ''
+            } else {
+              return row[item]
+            }
+          } )
+        })
+      })
+    } else {
+      checkShipmentsSubmitInfo = {}
+      checkShipmentsInfo = {}
+    }
+    emits('selectChangeEvent',checkShipmentsInfo, checkRecommend,checkShipmentsSubmitInfo)
+  }
+}
+// 全选
+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) {
+      checkShipmentsdata = Object.keys(checkShipments?.[0])
+      checkShipmentsSubmit = Object.keys(records?.[0])
+      checkShipmentsdata.forEach((item) => {
+        Object.assign(checkShipmentsInfo, {
+          [item]: checkShipments.map((row) => row[item] )
+        })
+      })
+      checkShipmentsSubmit.forEach((item) => {
+        Object.assign(checkShipmentsSubmitInfo, {
+          [item]: records.map((row) => {
+            if(row[item] == null){
+              return ''
+            } else {
+              return row[item]
+            }
+          } )
+        })
+      })
+    } else {
+      checkShipmentsSubmitInfo = {}
+    }
+    emits('selectChangeEvent',checkShipmentsInfo, checkRecommend,checkShipmentsSubmitInfo)
+  }
+}
+
+// 实现行点击样式
+useRowClickStyle(tableRef)
+
+onMounted(() => {
+  getTableColumns()
+})
+defineExpose({
+  getTableData,
+  searchTableData
+})
+
+</script>
+
+<template>
+  <div class="SettingTable">
+    <vxe-grid
+      ref="tableRef"
+      :style="{ border: 'none' }"
+      v-bind="tableData"
+      v-loading="tableLoadingTable || tableLoadingColumn"
+      @checkbox-change="selectChangeEvent"
+      @checkbox-all="selectAllChangeEvent"
+    >
+      <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>
+      </template>
+    </vxe-grid>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.font_family::before {
+  color: var(--color-btn-danger-bg);
+}
+.icon_alert::before {
+  color: var(--color-btn-warning-bg);
+}
+.delete_title {
+  font-size: 18px;
+  font-weight: 700;
+  padding: 20px 16px;
+  color: var(--color-neutral-1);
+}
+.delete_content {
+  font-size: 14px;
+  font-weight: 400;
+  color: var(--color-neutral-1);
+  padding: 15px 0 33px 37px;
+}
+:deep(.el-icon svg) {
+  width: 1em !important;
+}
+:deep(.vxe-table--render-default.is--round .vxe-table--border-line) {
+  border-radius: 6px;
+}
+.empty-text {
+  font-size: 14px;
+  font-weight: 400;
+  color: var(--color-neutral-1);
+  margin: 31px 0;
+}
+</style>

binární
src/views/DestinationDelivery/src/components/CreateNewBooking/src/images/default_add_address@2x.png


binární
src/views/DestinationDelivery/src/components/CreateNewBooking/src/images/default_destination_not_available@2x.png


binární
src/views/DestinationDelivery/src/components/CreateNewBooking/src/images/default_no_shipment@2x.png


binární
src/views/DestinationDelivery/src/components/CreateNewBooking/src/images/icon_success_big@2x.png


+ 484 - 0
src/views/DestinationDelivery/src/components/DeliveryDate.vue

@@ -0,0 +1,484 @@
+<script lang="ts" setup>
+import dayjs from 'dayjs'
+import { ref, watch } from 'vue'
+import { useUserStore } from '@/stores/modules/user'
+
+const userStore = useUserStore()
+const valueFormatDate = 'MM/DD/YYYY'
+// type RangeValue = [Dayjs, Dayjs]
+// const ETDDate = ref<RangeValue>()
+const props = defineProps({
+  CalendarWidth: {
+    type: String,
+    default: '368px'
+  },
+  CalendarTitle: {
+    type: String
+  },
+  Date: {
+    type: Array
+  },
+  isType: {
+    type: Boolean,
+    default: false
+  }
+})
+const ETDDate = ref([])
+watch(
+  () => props.Date,
+  (current: any) => {
+    if (current?.length == 2) {
+      ETDDate.value = [
+        current[0] ? dayjs(current[0]).format(valueFormatDate) : '',
+        current[1] ? dayjs(current[1]).format(valueFormatDate) : ''
+      ]
+    }
+  },
+  { immediate: true, deep: true }
+)
+const getDeliveryDateData = () => {
+  $api.getDeliveryDateData().then((res) => {
+    if (res.code === 200) {
+      const data = res.data.data
+      stateDataList.value = data
+    }
+  })
+}
+onMounted(() => {
+  getDeliveryDateData()
+})
+
+const stateDataList = ref({})
+const isShowStatus = (date: string) => {
+  return stateDataList.value[date] ? true : false
+}
+
+const emit = defineEmits(['DateRangeChange', 'DateChange'])
+const open = ref(false)
+const Disabled = ref([false, false])
+const isShowExtra = ref(true)
+
+const DateList = ref()
+DateList.value = []
+const daterange = (val: any) => {
+  if (DateList.value.length == 2 && val != undefined) {
+    const rangedata = {
+      title: props.CalendarTitle,
+      data: DateList.value
+    }
+    emit('DateRangeChange', rangedata)
+  }
+}
+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
+    daterange(DateList.value[1])
+  } else {
+    ETDDate.value[1] = dayjs()
+    const date1 = dayjs(String(ETDDate.value[1])).format(valueFormatDate)
+    DateList.value[1] = date1
+    daterange(DateList.value[0])
+  }
+}
+const handleCalendarOpen = (date: any) => {
+  open.value = !open.value
+  if (open.value == false) {
+    if (date.length != 2) {
+      DateList.value = []
+      ETDDate.value = []
+    }
+  }
+}
+const Earliest = () => {
+  ETDDate.value[0] = dayjs('Oct-05-2009')
+  const date1 = dayjs(String(ETDDate.value[0])).format(valueFormatDate)
+  DateList.value[0] = date1
+  daterange(DateList.value[1])
+}
+const Latest = () => {
+  ETDDate.value[1] = dayjs()
+  const date1 = dayjs(String(ETDDate.value[1])).format(valueFormatDate)
+  DateList.value[1] = date1
+  daterange(DateList.value[0])
+}
+const changeRangeData = (value: any) => {
+  DateList.value = value
+  if (value != '') {
+    const rangedata = {
+      title: props.CalendarTitle,
+      data: value
+    }
+    emit('DateRangeChange', rangedata)
+    emit('DateChange', value)
+  }
+}
+const handlePanelChange = (value: any, mode: any) => {
+  if (mode[0] == 'year' || mode[0] == 'month') {
+    isShowExtra.value = false
+  } else {
+    isShowExtra.value = true
+  }
+}
+</script>
+<template>
+  <div class="delivery-date">
+    <div class="ETD_title">{{ props.CalendarTitle }}</div>
+    <a-range-picker
+      separator="To"
+      :showToday="false"
+      popupClassName="delivery-date-range-picker"
+      :style="{
+        backgroundColor: props.isType ? 'var(--more-type-bg-color)' : 'var(--management-bg-color)'
+      }"
+      :presetsWidth="500"
+      :open="open"
+      :disabled="Disabled"
+      @change="changeRangeData"
+      :placeholder="['Start Time', 'End Time']"
+      :format="userStore.dateFormat"
+      valueFormat="MM/DD/YYYY"
+      @openChange="handleCalendarOpen(ETDDate)"
+      @panelChange="handlePanelChange"
+      v-model:value="ETDDate"
+    >
+      <template #dateRender="{ current }">
+        <div class="date-cell">
+          <span class="date-text">{{ dayjs(current).date() }}</span>
+          <div class="status-list" v-if="isShowStatus(current.format('YYYY-MM-DD'))">
+            <div class="status-item pending">
+              <span class="status-text">Pending</span>
+              <div class="count">
+                <span>3</span>
+              </div>
+            </div>
+            <div class="status-item approved">
+              <span class="status-text">Approved</span>
+              <div class="count">
+                <span>3</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </template>
+      <template #suffixIcon>
+        <span class="iconfont_icon">
+          <svg class="iconfont icon_suffix" aria-hidden="true">
+            <use xlink:href="#icon-icon_date_b"></use>
+          </svg>
+        </span>
+      </template>
+      <template #renderExtraFooter v-if="isShowExtra">
+        <div class="calender_flex">
+          <div class="footer_left">
+            <el-button class="el-button--noborder" @click="Earliest">Earliest Time</el-button>
+            <el-button class="el-button--noborder" @click="ChangeToday('Earliest')"
+              >Today</el-button
+            >
+          </div>
+          <div class="footer_left footer_right">
+            <el-button @click="Latest" class="el-button--noborder">Latest Time</el-button>
+            <el-button class="el-button--noborder" @click="ChangeToday('Latest')">Today</el-button>
+          </div>
+        </div>
+      </template>
+    </a-range-picker>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.calender_flex {
+  display: flex;
+  justify-content: space-between;
+}
+.footer_left {
+  display: flex;
+  flex: 50%;
+  padding: 0 0 0 5px;
+  height: 48px;
+  display: flex;
+  align-items: center;
+}
+.footer_right {
+  border-left: 1px solid var(--border-color-2);
+  padding-left: 8px;
+}
+.el-button--noborder {
+  color: var(--color-theme);
+}
+.ETD_title {
+  font-size: var(--font-size-2);
+  margin-bottom: 4px;
+  color: var(--color-neutral-2);
+}
+.iconfont_icon {
+  margin-right: 0;
+}
+.iconfont {
+  width: 14px;
+  height: 14px;
+}
+.icon_suffix {
+  fill: var(--color-neutral-1);
+}
+</style>
+
+<style lang="scss">
+div.delivery-date-range-picker {
+  .ant-picker-cell:hover:not(.ant-picker-cell-selected):not(.ant-picker-cell-range-start)
+    :not(.ant-picker-cell-range-end):not(.ant-picker-cell-range-hover-start):not(
+      .ant-picker-cell-range-hover-end
+    ) {
+    background-color: transparent !important;
+  }
+
+  .ant-picker-panel,
+  .ant-picker-date-panel {
+    width: 500px !important;
+  }
+  .ant-picker-content {
+    width: 476px !important;
+  }
+  td.ant-picker-cell {
+    width: 64px;
+    height: 64px;
+    overflow: hidden;
+    &:not(.ant-picker-cell-in-view) {
+      .date-text {
+        color: var(--color-neutral-5);
+      }
+    }
+
+    &:not(.ant-picker-cell-range-end):hover {
+      background-color: var(--border-hover-color);
+      border-radius: 12px;
+    }
+    &.ant-picker-cell-selected.ant-picker-cell-in-view,
+    &.ant-picker-cell-range-end.ant-picker-cell-in-view,
+    &.ant-picker-cell-range-start.ant-picker-cell-in-view {
+      background: var(--color-theme) !important;
+      border-radius: 12px;
+    }
+    &.ant-picker-cell-range-start.ant-picker-cell-in-view {
+      border-top-right-radius: 0;
+      border-bottom-right-radius: 0;
+    }
+    &.ant-picker-cell-range-end.ant-picker-cell-in-view {
+      border-top-left-radius: 0;
+      border-bottom-left-radius: 0;
+    }
+    &.ant-picker-cell-range-start.ant-picker-cell-range-end {
+      border-radius: 12px;
+    }
+  }
+  .ant-picker-cell-in-view.ant-picker-cell-in-range {
+    background-color: var(--color-orange-6);
+    border-radius: 0 !important;
+  }
+
+  .ant-picker-cell:hover:not(.ant-picker-cell-in-view).ant-picker-cell-inner,
+  .ant-picker-cell:hover:not(.ant-picker-cell-selected):not(.ant-picker-cell-range-start)
+    :not(.ant-picker-cell-range-end):not(.ant-picker-cell-range-hover-start) {
+    .status-text {
+      background: transparent !important;
+    }
+    .pending {
+      background-color: #fff4d1 !important;
+      .count {
+        background: #e0a100 !important;
+
+        span {
+          color: #fff4d1 !important;
+          background-color: transparent !important;
+        }
+      }
+      .status-text {
+        color: #e0a100 !important;
+      }
+    }
+    .approved {
+      background-color: #e8fbe4 !important;
+      .count {
+        background-color: #5bb462 !important;
+        span {
+          background-color: transparent !important;
+          color: #e8fbe4 !important;
+        }
+      }
+      .status-text {
+        color: #5bb462 !important;
+      }
+    }
+  }
+
+  &:where(.css-dev-only-do-not-override-1p3hq3p).ant-picker-dropdown
+    .ant-picker-cell
+    .ant-picker-cell-inner {
+    transition: none;
+  }
+  // 截止日期半圆的背景
+  // &:where(.css-dev-only-do-not-override-1p3hq3p).ant-picker-dropdown
+  //   .ant-picker-cell-in-view.ant-picker-cell-range-end:not(
+  //     .ant-picker-cell-range-end-single
+  //   )::before,
+  // &:where(.css-dev-only-do-not-override-1p3hq3p).ant-picker-dropdown
+  //   .ant-picker-cell-in-view.ant-picker-cell-range-start:not(
+  //     .ant-picker-cell-range-start-single
+  //   )::before {
+  //   height: 64px;
+  //   background: var(--color-orange-6);
+  // }
+  // 截止日期半圆的背景透明
+  &:where(.css-dev-only-do-not-override-1p3hq3p).ant-picker-dropdown
+    .ant-picker-cell-in-view.ant-picker-cell-range-start:not(
+      .ant-picker-cell-range-start-single
+    )::before,
+  &:where(.css-dev-only-do-not-override-1p3hq3p).ant-picker-dropdown
+    .ant-picker-cell-in-view.ant-picker-cell-range-end:not(
+      .ant-picker-cell-range-end-single
+    )::before {
+    background-color: transparent !important;
+    display: none !important;
+  }
+
+  &:where(.css-dev-only-do-not-override-1p3hq3p).ant-picker-dropdown
+    .ant-picker-cell-in-view.ant-picker-cell-in-range::before {
+    background-color: transparent !important;
+  }
+
+  &:where(.css-dev-only-do-not-override-1p3hq3p).ant-picker-dropdown
+    .ant-picker-cell-disabled::before {
+    height: 64px;
+  }
+  &.ant-picker-cell:hover:not(.ant-picker-cell-selected):not(.ant-picker-cell-range-start)
+    :not(.ant-picker-cell-range-end):not(.ant-picker-cell-range-hover-start):not(
+      .ant-picker-cell-range-hover-end
+    ) {
+    background-color: transparent !important;
+  }
+  .date-cell {
+    position: relative;
+    height: 100%;
+    padding-top: 26px;
+    padding: 22px 6px 0;
+    .date-text {
+      position: absolute;
+      top: 3px;
+      left: 8px;
+      font-size: 12px;
+    }
+    .status-list {
+      .status-item {
+        display: flex;
+        justify-content: space-between;
+        height: 16px;
+        padding: 3px 4px 4px;
+        border-radius: 3px;
+        .status-text {
+          font-size: 8px;
+        }
+        .status-number {
+          font-size: 6px;
+        }
+      }
+      .pending {
+        margin-bottom: 2px;
+        background-color: #fff4d1;
+      }
+      .approved {
+        background-color: #e8fbe4;
+      }
+    }
+  }
+  .pending {
+    .status-text {
+      color: #e0a100;
+    }
+    .count {
+      background: #e0a100;
+    }
+  }
+  .approved {
+    .status-text {
+      color: #5bb462;
+    }
+    .count {
+      background-color: #5bb462;
+    }
+  }
+  .ant-picker-cell-in-view {
+    &.ant-picker-cell-range-start,
+    &.ant-picker-cell-range-end,
+    &.ant-picker-cell-selected {
+      .date-text {
+        color: #fff !important;
+      }
+      .pending {
+        background-color: #fff !important;
+        .status-text {
+          color: #e0a100;
+        }
+        .count {
+          background: #e0a100;
+        }
+        &:hover {
+          background-color: #e0a100 !important;
+          .status-text {
+            color: #fff;
+          }
+          .count {
+            background-color: #fff;
+            span {
+              color: #e0a100;
+            }
+          }
+        }
+      }
+      .approved {
+        background-color: #fff !important;
+        .status-text {
+          color: #5bb462;
+        }
+        .count {
+          background-color: #5bb462;
+        }
+        &:hover {
+          background-color: #5bb462 !important;
+          .status-text {
+            color: #fff;
+          }
+          .count {
+            background-color: #fff;
+            span {
+              color: #5bb462;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .count {
+    display: flex;
+    justify-content: center;
+    height: 10px;
+    min-width: 10px;
+    background-color: var(--color-theme);
+    border-radius: 9px;
+    span {
+      font-size: 8px;
+      color: var(--color-white);
+    }
+  }
+  .ant-picker-cell:not(.ant-picker-cell-in-view) {
+    .status-list {
+      display: none !important;
+    }
+  }
+}
+</style>
+ant-picker-cell ant-picker-cell-in-view ant-picker-cell-range-end ant-picker-cell-range-end-single
+ant-picker-cell-selected
+<style lang="scss"></style>

+ 1 - 0
src/views/DestinationDelivery/src/components/ModifyBooking/index.ts

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

+ 276 - 0
src/views/DestinationDelivery/src/components/ModifyBooking/src/ModifyBooking.vue

@@ -0,0 +1,276 @@
+<script lang="ts" setup>
+import SelectShipmentsTable from './components/SelectShipmentsTable.vue'
+import { useRouter } from 'vue-router'
+
+const router = useRouter()
+
+const selectVModel = ref('')
+const optionOptions = ref([
+  { key: '1', value: 'option1', label: 'Option 1' },
+  { key: '2', value: 'option2', label: 'Option 2' },
+  { key: '3', value: 'option3', label: 'Option 3' }
+])
+
+const inputVModel = ref('')
+const radioVModel = ref(1)
+const deliveryDate = ref('')
+
+const handleCancel = () => {
+  // Logic to handle cancel action
+  router.push({ name: 'Destination Delivery' })
+}
+</script>
+<template>
+  <div class="modify-booking">
+    <div class="header">
+      <span>Modify Booking</span>
+      <div class="operator">
+        <el-button @click="handleCancel" 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">
+          <span
+            style="
+              display: inline-block;
+              margin-top: -4px;
+              margin-right: 4px;
+              transform: rotate(-60deg);
+            "
+            class="font_family icon-icon_submit_b"
+          ></span>
+          <span style="font-weight: 400">Submit</span>
+        </el-button>
+      </div>
+    </div>
+    <div class="content">
+      <div class="booking-info">
+        <div class="booking-no">
+          <span class="no">Booking No.B83131200164</span>
+          <v-tag class="tag" type="Pending Approval">Pending Approval</v-tag>
+        </div>
+      </div>
+      <el-divider style="margin: 8px 0" />
+      <SelectShipmentsTable></SelectShipmentsTable>
+      <el-divider style="margin: 8px 0 20px" />
+      <div class="delivery-information">
+        <div class="label">Delivery Information</div>
+        <div class="delivery-info-content">
+          <div class="delivery-address-header">
+            <div class="label"><span class="require-icon">*</span>Delivery Address</div>
+            <div class="operator">
+              <el-button class="el-button--text" style="height: 32px">
+                <span class="font_family icon-icon_add_b" style="margin-right: 4px"></span>
+                <span>Add New Address</span>
+              </el-button>
+              <el-button class="el-button--text" style="height: 32px">
+                <span
+                  class="font_family icon-icon_configurations_b"
+                  style="margin-right: 4px"
+                ></span>
+                <span>Manage Address</span>
+              </el-button>
+            </div>
+          </div>
+          <div class="delivery-address-content">
+            <el-radio-group v-model="radioVModel">
+              <el-radio :value="1" label="string">
+                <div class="delivery-address-label">Main Distribution Center</div>
+                <div class="delivery-address-info">
+                  <p>160#BEIJING ROAD, JINGAN District,</p>
+                  <p>Shenzhen, China</p>
+                  <p class="time">Contact: John Doe (+65 9123 4567)</p>
+                </div>
+              </el-radio>
+            </el-radio-group>
+          </div>
+          <div class="filetr-item mode-type">
+            <div class="label"><span class="require-icon">*</span>Mode Type</div>
+            <el-select v-model="selectVModel" clearable placeholder="Please Select Type">
+              <el-option
+                v-for="item in optionOptions"
+                :key="item.key"
+                :value="item.value"
+                :label="item.label"
+              ></el-option>
+            </el-select>
+          </div>
+          <div class="filetr-item mode-type">
+            <div class="label"><span class="require-icon">*</span>Preferred Delivery Date</div>
+
+            <el-date-picker v-model="deliveryDate" type="date" placeholder="Pick a day" />
+          </div>
+          <div class="special-requirements">
+            <div class="label">Special Requirements</div>
+            <div class="tag-list">
+              <div class="tag-item">Tail Lift Required</div>
+              <div class="tag-item">Side Loading</div>
+              <div class="tag-item">Forklift Required</div>
+              <div class="tag-item">Special Equipment</div>
+            </div>
+            <el-input
+              type="textarea"
+              class="input-textarea"
+              v-model="inputVModel"
+              placeholder="Enter any additional requirements or notes..."
+            ></el-input>
+          </div>
+          <div class="modification-reason">
+            <div class="label" style="margin-bottom: 4px">
+              <span class="require-icon">*</span>Modification Reason
+            </div>
+            <el-input class="input-textarea" type="textarea" v-model="inputVModel"></el-input>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.modify-booking {
+  position: relative;
+  background-color: var(--color-mode);
+}
+.header {
+  display: flex;
+  height: 68px;
+  border-bottom: 1px solid var(--color-border);
+  font-size: var(--font-size-6);
+  font-weight: 700;
+  padding: 0 24px;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.content {
+  padding: 16px 24px 48px;
+}
+
+.booking-info {
+  display: flex;
+  align-items: center;
+  height: 48px;
+  padding: 0 16px;
+  border-radius: 12px;
+  background-image: var(--color-booking-info-linear-bg);
+
+  .booking-no {
+    display: flex;
+    align-items: center;
+    .no {
+      margin-top: 2px;
+      font-size: 18px;
+      font-weight: 700;
+      line-height: 21px;
+    }
+    .tag {
+      margin-left: 8px;
+    }
+  }
+  .created-time {
+    margin-top: 8px;
+    font-size: 12px;
+    color: var(--color-neutral-2);
+  }
+}
+
+.delivery-information {
+  & > .label {
+    font-size: 18px;
+    font-weight: 700;
+    margin-bottom: 16px;
+  }
+  .delivery-info-content {
+    padding: 16px;
+    border: 1px solid var(--color-border);
+    border-radius: 12px;
+    .label {
+      font-weight: 700;
+      line-height: 22px;
+    }
+    .delivery-address-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 4px;
+      & > .operator {
+        display: flex;
+        align-items: center;
+        & > .el-button--text {
+          height: 32px;
+          font-size: 14px;
+          font-weight: 400;
+          span {
+            color: var(--color-theme);
+          }
+        }
+      }
+    }
+    .delivery-address-content {
+      height: 122px;
+      margin-bottom: 16px;
+      padding: 10px 12px 8px 8px;
+      border-radius: 12px;
+      background-color: var(--color-share-link-bg);
+      :deep(.el-radio__label) {
+        display: flex;
+        flex-direction: column;
+        height: 100%;
+        justify-content: space-between;
+      }
+      .delivery-address-label {
+        margin-top: 9px;
+        margin-bottom: 2px;
+        font-weight: 700;
+      }
+      .delivery-address-info {
+        margin-top: 8px;
+        p {
+          line-height: 21px;
+          color: var(--color-neutral-2);
+        }
+        & > .time {
+          margin-top: 6px;
+          font-size: 12px;
+        }
+      }
+    }
+    .filetr-item {
+      display: inline-flex;
+      flex-direction: column;
+      width: 240px;
+      margin-right: 16px;
+      & > .label {
+        margin-bottom: 4px;
+      }
+    }
+    .special-requirements {
+      margin: 16px 0;
+    }
+    .input-textarea {
+      :deep(.el-textarea__inner) {
+        height: 80px;
+        resize: none;
+        line-height: 22px;
+      }
+    }
+    .tag-list {
+      margin-top: 8px;
+      margin-bottom: 8px;
+      .tag-item {
+        display: inline-block;
+        height: 32px;
+        padding: 10px 16px;
+        margin-right: 8px;
+        background-color: var(--color-download-file-filter-tag-bg);
+        border-radius: 15px;
+        font-size: 12px;
+      }
+    }
+  }
+  .require-icon {
+    color: red;
+  }
+}
+</style>

+ 152 - 0
src/views/DestinationDelivery/src/components/ModifyBooking/src/components/SelectShipmentsTable.vue

@@ -0,0 +1,152 @@
+<script setup lang="ts">
+import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
+// import { autoWidth } from '@/utils/table'
+import { useRowClickStyle } from '@/hooks/rowClickStyle'
+import { formatTimezone, formatNumber } from '@/utils/tools'
+
+const props = defineProps({
+  data: Object
+})
+const tableRef = ref<VxeGridInstance | null>(null)
+const tableData = ref<VxeGridProps<any>>({
+  minHeight: 70,
+  border: true,
+  round: true,
+  columns: [],
+  data: [],
+  scrollY: { enabled: true, oSize: 20, gt: 30 },
+  emptyText: ' ',
+  showHeaderOverflow: true,
+  showOverflow: true,
+  headerRowStyle: {
+    backgroundColor: 'var(--color-table-header-bg)'
+  },
+  columnConfig: { resizable: true, useKey: true },
+  rowConfig: { isHover: true }
+})
+
+const handleColumns = (columns: any) => {
+  const newColumns = columns.map((item: any) => {
+    let curColumn: any = {
+      title: item.title,
+      field: item.field,
+      minWidth: 30
+    }
+
+    // 格式化
+    if (item.formatter === 'date' || item.formatter === 'dateTime') {
+      curColumn = {
+        ...curColumn,
+        formatter: ({ cellValue }: any) => formatTimezone(cellValue)
+      }
+    } else if (item.formatter === 'number') {
+      curColumn = {
+        ...curColumn,
+        formatter: ({ cellValue }: any) => formatNumber(Number(cellValue), item?.digits)
+      }
+    }
+    return curColumn
+  })
+  return newColumns
+}
+watch(
+  () => props.data,
+  (newVal) => {
+    const containers = newVal?.containers
+    if (containers && containers.container_column) {
+      tableData.value.columns = handleColumns(containers.container_column)
+      tableData.value.data = containers.container_data
+      // nextTick(() => {
+      //   tableRef.value && autoWidth(tableData.value, tableRef.value)
+      // })
+    }
+  },
+  {
+    immediate: true,
+    deep: true
+  }
+)
+
+const tableLoadingColumn = ref(false)
+const tableLoadingTableData = ref(false)
+
+const tableOriginColumnsField = ref()
+// 获取表格列
+const getTableColumns = async () => {
+  tableLoadingColumn.value = true
+  await $api.getOperationTableColumns().then((res: any) => {
+    if (res.code === 200) {
+      tableData.value.columns = [
+        { type: 'checkbox', width: 50, fixed: 'left' },
+        ...handleColumns(res.data.OperationTableColumns),
+        {}
+      ]
+
+      tableOriginColumnsField.value = res.data.OperationTableColumns
+
+      tableLoadingColumn.value = false
+    }
+  })
+}
+let searchdata: any = {}
+// 获得表格数据后赋值
+const assignTableData = (data: any) => {
+  tableData.value.data = data.searchData || []
+}
+
+// 获取表格数据
+const getTableData = async () => {
+  const rc = -1
+  tableLoadingTableData.value = true
+  await $api
+    .SearchOperationLog({
+      cp: 1,
+      ps: 6,
+      rc,
+      ...searchdata
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        assignTableData(res.data)
+      }
+    })
+    .finally(() => {
+      tableLoadingTableData.value = false
+    })
+}
+
+onMounted(() => {
+  Promise.all([getTableColumns(), getTableData()]).finally(() => {})
+})
+// 实现行点击样式
+useRowClickStyle(tableRef)
+</script>
+
+<template>
+  <div class="shipment-table">
+    <div class="label">*Select Shipments</div>
+    <vxe-grid
+      ref="tableRef"
+      v-vloading="tableLoadingTableData || tableLoadingColumn"
+      v-bind="tableData"
+      height="240"
+    >
+      <template #empty v-if="!tableLoadingTableData && tableData.data.length === 0">
+        <div class="empty">No data</div>
+      </template>
+    </vxe-grid>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.shipment-table {
+  padding: 16px;
+  border: 1px solid var(--color-border);
+  border-radius: 12px;
+  .label {
+    margin-bottom: 16px;
+    font-size: 18px;
+    font-weight: 700;
+  }
+}
+</style>

+ 1 - 0
src/views/DestinationDelivery/src/components/TableView/index.ts

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

+ 545 - 0
src/views/DestinationDelivery/src/components/TableView/src/TableView.vue

@@ -0,0 +1,545 @@
+<script setup lang="ts">
+import { ref, nextTick, onMounted } from 'vue'
+import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
+import { autoWidth } from '@/utils/table'
+import { useRowClickStyle } from '@/hooks/rowClickStyle'
+import dayjs from 'dayjs'
+import { formatTimezone, formatNumber } from '@/utils/tools'
+import BookingDetailDialog from './components/BookingDetailDialog.vue'
+import EmailDialog from './components/EmailDialog.vue'
+import TipsDialog from './components/TipsDialog.vue'
+import { useRouter } from 'vue-router'
+const router = useRouter()
+
+const props = defineProps({
+  height: {
+    type: Number,
+    default: 440
+  },
+  queryData: {
+    type: Object,
+    default: () => ({})
+  }
+})
+
+const tableOriginColumnsField = ref()
+const handleColumns = (columns: any, status?: string) => {
+  const newColumns = columns.map((item: any) => {
+    let curColumn: any = {
+      title: item.title,
+      field: item.field,
+      sortable: true,
+      minWidth: 120
+    }
+    // 设置插槽
+    if (item.type === 'status' && status !== 'all') {
+      curColumn = {
+        ...curColumn,
+        slots: { default: 'status' }
+      }
+    } else if (item.type === 'multiple_link') {
+      curColumn = {
+        ...curColumn,
+        slots: { default: 'multLink' }
+      }
+    }
+    // 格式化
+    if (item.formatter === 'date' || item.formatter === 'dateTime') {
+      curColumn = {
+        ...curColumn,
+        formatter: ({ cellValue }: any) => formatTimezone(cellValue)
+      }
+    } else if (item.formatter === 'range') {
+      curColumn = {
+        ...curColumn,
+        formatter: ({ cellValue }: any) => {
+          if (!cellValue) return '--'
+          const rangeData = cellValue.split(';')
+          const leftDate = rangeData[0] ? formatTimezone(rangeData[0]) : '_'
+          const rightDate = rangeData[1] ? formatTimezone(rangeData[0]) : '_'
+          return leftDate + ' -- ' + rightDate
+        }
+      }
+    }
+    return curColumn
+  })
+  return newColumns
+}
+
+// 获取表格列
+const getTableColumns = async () => {
+  tableLoadingColumn.value = true
+  await $api.getDeliveryBookingTableColumn().then((res: any) => {
+    if (res.code === 200) {
+      tableData.value.columns = [...handleColumns(res.data.TrackingTableColumns)]
+      const index = tableData.value.columns.findIndex((item: any) => item.title === 'Action')
+      if (index === -1) {
+        tableData.value.columns.push({
+          title: 'Action',
+          fixed: 'left',
+          width: 130,
+          slots: { default: 'action' }
+        })
+      }
+      tableOriginColumnsField.value = res.data.OperationTableColumns
+    }
+  })
+  nextTick(() => {
+    tableRef.value && autoWidth(tableData.value, tableRef.value)
+    tableLoadingColumn.value = false
+    selectedTableData.value = []
+  })
+}
+
+const pageInfo = ref({ pageNo: 1, pageSize: 20, total: 0 })
+const tempSearch = ref()
+// 获得表格数据后赋值
+const assignTableData = (data: any) => {
+  tableData.value.data = data.searchData || []
+  pageInfo.value.total = Number(data.rc) || 0
+  tempSearch.value = data.tmp_search
+
+  if (tableData.value.columns.length > 0) {
+    const index = tableData.value.columns.findIndex((item: any) => item.title === 'Action')
+    if (index === -1) {
+      tableData.value.columns.push({
+        title: 'Action',
+        fixed: 'left',
+        width: 130,
+        slots: { default: 'action' }
+      })
+    }
+  }
+}
+
+const isEmployeeRole = ref(null)
+
+const emit = defineEmits(['getNumberCards'])
+const rtnNumberCards = (data) => {
+  const cards = [
+    {
+      label: 'Total Bookings',
+      value: data.All || 0,
+      color: '#2b2f36',
+      key: 'ALL'
+    },
+    {
+      label: 'Pending Approval',
+      value: data.pending_approval_rc || 0,
+      color: '#edb82f',
+      icon: 'icon_time_b'
+    },
+    {
+      label: 'Approved',
+      value: data.approved_rc || 0,
+      color: '#00a870',
+      icon: 'icon_confirm_b'
+    },
+    {
+      label: 'Rejected',
+      value: data.rejected_rc || 0,
+      color: '#c9353f',
+      icon: 'icon_reject_b'
+    },
+    {
+      label: 'Cancelled',
+      value: data.cancelled_rc || 0,
+      color: '#243041',
+      icon: 'icon_cancelled_b'
+    }
+  ]
+  emit('getNumberCards', cards)
+}
+// 获取表格数据
+const getTableData = async (isPageChange?: boolean) => {
+  const rc = isPageChange ? pageInfo.value.total : -1
+  tableLoadingTableData.value = true
+  await $api
+    .getDeliveryBookingTableData({
+      cp: pageInfo.value.pageNo,
+      ps: pageInfo.value.pageSize,
+      rc,
+      ...props.queryData
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        isEmployeeRole.value = res.data.is_employee || false
+        assignTableData(res.data)
+        rtnNumberCards(res.data)
+      }
+    })
+    .finally(() => {
+      selectedTableData.value = []
+      nextTick(() => {
+        tableRef.value && autoWidth(tableData.value, tableRef.value)
+        tableLoadingTableData.value = false
+      })
+    })
+}
+const SearchOperationLog = () => {
+  tableLoadingTableData.value = true
+  $api
+    .getDeliveryBookingTableData({
+      cp: pageInfo.value.pageNo,
+      ps: pageInfo.value.pageSize,
+      rc: -1,
+      ...props.queryData
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        isEmployeeRole.value = res.data.is_employee || false
+        assignTableData(res.data)
+        rtnNumberCards(res.data)
+      }
+    })
+    .finally(() => {
+      selectedTableData.value = []
+      nextTick(() => {
+        tableRef.value && autoWidth(tableData.value, tableRef.value)
+        tableLoadingTableData.value = false
+      })
+    })
+}
+onMounted(() => {
+  Promise.all([getTableColumns(), getTableData(false)]).finally(() => {
+    nextTick(() => {
+      tableRef.value && autoWidth(tableData.value, tableRef.value)
+    })
+  })
+})
+
+const tableRef = ref<VxeGridInstance>()
+const tableData = ref<VxeGridProps<any>>({
+  border: true,
+  round: true,
+  columns: [],
+  data: [],
+  scrollY: { enabled: true, oSize: 20, gt: 30 },
+  emptyText: ' ',
+  showHeaderOverflow: true,
+  showOverflow: true,
+  headerRowStyle: {
+    backgroundColor: 'var(--color-table-header-bg)'
+  },
+  sortConfig: {
+    sortMethod: (params) => {
+      const { data, sortList } = params
+
+      // 如果没有排序条件,直接返回原数据
+      if (sortList.length === 0) return data
+
+      // 对数据进行多重排序
+      const sortedData = [...data].sort((a, b) => {
+        for (const { field, order } of sortList) {
+          const curColumn = tableOriginColumnsField.value.find((item: any) => item.field === field)
+          if (!curColumn) continue
+
+          const typeName = curColumn.type
+          const aValue = a[field]
+          const bValue = b[field]
+
+          const compareResult = (aValue: any, bValue: any) => {
+            // 如果 aValue 或 bValue 是 null 或 undefined,优先处理这些值
+            if (aValue == null && bValue == null) {
+              return 0 // 如果两个值都为 null 或 undefined,视为相等
+            } else if (aValue == null) {
+              return -1 // 如果 aValue 是 null,bValue 不是,则将 aValue 视为较小值
+            } else if (bValue == null) {
+              return 1 // 如果 bValue 是 null,aValue 不是,则将 bValue 视为较小值
+            }
+
+            if (typeName === 'datetime' || typeName === 'date' || typeName === 'time') {
+              return dayjs(aValue).unix() - dayjs(bValue).unix()
+            } else if (isNaN(Number(aValue)) || isNaN(Number(bValue))) {
+              return aValue.localeCompare(bValue)
+            } else {
+              return Number(aValue) - Number(bValue)
+            }
+          }
+
+          const result = compareResult(aValue, bValue)
+          if (result !== 0) {
+            return order === 'asc' ? result : -result
+          }
+        }
+
+        return 0 // 如果所有字段都相等
+      })
+
+      return sortedData
+    }
+  },
+  columnConfig: { resizable: true, useKey: true },
+  rowConfig: { isHover: true, isCurrent: true },
+  exportConfig: {
+    types: ['csv', 'html', 'txt', 'xlsx'],
+    modes: ['current', 'selected', 'all']
+  }
+})
+
+// 实现行点击样式
+useRowClickStyle(tableRef)
+
+const tableLoadingColumn = ref(false)
+const tableLoadingTableData = ref(false)
+
+const selectedTableData = ref([])
+
+const bookingDetailDiaRef = ref()
+const clickViewBtn = (row: any) => {
+  bookingDetailDiaRef.value.openDialog(row)
+}
+
+const emailDialogRef = ref()
+const clickEmailBtn = (row: any) => {
+  emailDialogRef.value.openDialog(row)
+}
+
+// edit
+const handleEdit = (row: any) => {
+  router.push({
+    path: '/destination-delivery/CreateNewBooking',
+    query: { a: row._serial_no }
+  })
+}
+
+const handleCreate = () => {
+  router.push({ name: 'Create New Booking' })
+}
+
+const testData = [
+  {
+    key: 'A1703530062',
+    value: 'CD1Xeh4rG%2FPmYIiCHAw2%2B0Gw8BFZ8k%2F2pQDkYX3vuf6iZ3kcQXj%2BzQ'
+  },
+  {
+    key: 'A1703530062',
+    value: 'CD1Xeh4rG%2FPmYIiCHAw2%2B0Gw8BFZ8k%2F2pQDkYX3vuf6iZ3kcQXj%2BzQ'
+  }
+]
+
+const handleMultLinkClick = (item: any) => {
+  router.push({
+    path: '/tracking/detail',
+    query: { a: item.value, _schemas: item.order_from }
+  })
+}
+
+const tipsDialogRef = ref()
+const handleTips = (type: string, row: any) => {
+  tipsDialogRef.value.openDialog(type, row)
+}
+const handleChangeRowState = () => {
+  SearchOperationLog()
+}
+
+defineExpose({
+  SearchOperationLog,
+  isEmployeeRole
+})
+</script>
+
+<template>
+  <div
+    style="padding: 0px 20px"
+    class="table-box"
+    element-loading-text="Loading..."
+    element-loading-custom-class="element-loading"
+    element-loading-background="rgb(43, 47, 54, 0.7)"
+  >
+    <div class="table-tools">
+      <div class="left-total-records">Booking List</div>
+    </div>
+    <vxe-grid
+      ref="tableRef"
+      v-vloading="tableLoadingTableData || tableLoadingColumn"
+      :height="props.height"
+      @cell-dblclick="({ row }) => handleEdit(row)"
+      :style="{ border: 'none' }"
+      v-bind="tableData"
+    >
+      <!-- 空数据时的插槽 -->
+      <template #empty v-if="!tableLoadingTableData && tableData.data.length === 0">
+        <div class="empty-box">
+          <img class="empty-img" src="./img/table-empty-img.png" alt="" />
+          <p>You haven't created any destination delivery bookings yet.</p>
+          <p>Book truck or rail delivery for your shipments to save time and</p>
+          <p>ensure smooth last-mile delivery.</p>
+          <el-button
+            style="height: 40px"
+            v-if="isEmployeeRole === false"
+            class="el-button--main el-button--pain-theme"
+            @click="handleCreate"
+          >
+            <span style="margin-right: 4px" class="font_family icon-icon_add_b"></span>
+            <span style="font-weight: 400">Create New Booking</span>
+          </el-button>
+        </div>
+      </template>
+      <!-- action操作的插槽 -->
+      <template #action="{ row, rowIndex }">
+        <!-- view -->
+        <el-button
+          @click="clickViewBtn(row)"
+          class="action-btn el-button--blue"
+          style="height: 24px; width: 24px"
+        >
+          <span class="font_family icon-icon_view_b"> </span>
+        </el-button>
+        <!-- email -->
+        <el-button
+          v-if="!isEmployeeRole && row.status === 'Approved'"
+          @click="clickEmailBtn(row)"
+          class="action-btn el-button--blue"
+          style="height: 24px; width: 24px"
+        >
+          <span class="font_family icon-icon_email_b"> </span>
+        </el-button>
+        <!-- edit -->
+        <el-button
+          @click="handleEdit(row)"
+          class="action-btn el-button--blue"
+          style="height: 24px; width: 24px"
+          v-if="!isEmployeeRole && (row.status === 'Pending Approval' || row.status === 'Rejected')"
+        >
+          <span class="font_family icon-icon_edit_b"> </span>
+        </el-button>
+        <!-- cancel -->
+        <el-button
+          @click="handleTips('cancel', row)"
+          class="action-btn el-button--blue"
+          style="height: 24px; width: 24px"
+          v-if="!isEmployeeRole && row.status === 'Pending Approval'"
+        >
+          <span class="font_family icon-icon_cancelled_b"> </span>
+        </el-button>
+        <!-- approve -->
+        <el-button
+          @click="handleTips('approve', row)"
+          class="action-btn el-button--blue"
+          v-if="isEmployeeRole && row.status === 'Pending Approval'"
+          style="height: 24px; width: 24px"
+        >
+          <span class="font_family icon-icon_confirm_b"> </span>
+        </el-button>
+        <!-- reject -->
+        <el-button
+          v-if="isEmployeeRole && row.status === 'Pending Approval'"
+          @click="handleTips('reject', row)"
+          class="action-btn el-button--blue"
+          style="height: 24px; width: 24px"
+        >
+          <span class="font_family icon-icon_reject_b"> </span>
+        </el-button>
+      </template>
+      <!-- Status字段的插槽 -->
+      <template #status="{ row, column }">
+        <VTag :type="row[column.field]">{{ row[column.field] }}</VTag>
+      </template>
+      <!-- Booking No字段的插槽 -->
+      <template #multLink="{ row, column }">
+        <div
+          style="display: inline-block"
+          v-for="(item, index) in row[column.field]"
+          :key="item.key"
+        >
+          <span v-if="index > 0">、</span>
+          <span
+            style="color: var(--color-theme); cursor: pointer"
+            @click="handleMultLinkClick(item)"
+            >{{ item.key }}</span
+          >
+        </div>
+      </template>
+    </vxe-grid>
+
+    <div class="bottom-pagination">
+      <div class="left-total-records">Total {{ formatNumber(pageInfo.total) }}</div>
+      <div class="right-pagination">
+        <el-pagination
+          v-model:current-page="pageInfo.pageNo"
+          v-model:page-size="pageInfo.pageSize"
+          :page-sizes="[20, 50, 100, 150]"
+          :pager-count="3"
+          background
+          layout="sizes, prev, pager, next"
+          :total="pageInfo.total"
+          @size-change="getTableData(true)"
+          @current-change="getTableData(true)"
+        />
+      </div>
+    </div>
+
+    <BookingDetailDialog ref="bookingDetailDiaRef" />
+    <EmailDialog ref="emailDialogRef" />
+    <TipsDialog ref="tipsDialogRef" @state-change="handleChangeRowState" />
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.table-tools {
+  position: relative;
+  display: flex;
+  justify-content: space-between;
+  height: 48px;
+  padding: 8px 0;
+
+  .left-total-records {
+    font-size: 16px;
+    font-weight: 700;
+    line-height: 32px;
+  }
+}
+
+.empty-box {
+  .empty-img {
+    width: 100px;
+    height: 100px;
+    margin-bottom: 8px;
+  }
+  p {
+    color: var(--color-neutral-2);
+    line-height: 21px;
+  }
+  .el-button {
+    margin-top: 8px;
+  }
+}
+
+.bottom-pagination {
+  display: flex;
+  justify-content: space-between;
+  height: 40px;
+  margin-top: -1px;
+  padding: 4px 8px;
+  border: 1px solid var(--color-border);
+  border-radius: 0 0 12px 12px;
+
+  .left-total-records {
+    line-height: 32px;
+  }
+
+  .right-pagination {
+    display: flex;
+    align-items: center;
+  }
+}
+.table-box {
+  position: relative;
+  overflow: hidden;
+
+  .all-table {
+    position: absolute;
+    top: -100000px;
+    width: 20px;
+  }
+}
+
+.action-btn {
+  height: 24px;
+  width: 24px;
+  &:hover {
+    background-color: var(--color-btn-action-bg-hover);
+  }
+}
+</style>

+ 229 - 0
src/views/DestinationDelivery/src/components/TableView/src/components/BookingDetailDialog.vue

@@ -0,0 +1,229 @@
+<script setup lang="ts">
+import DetailStep from './DetailStep.vue'
+import ShipmentInforTable from './ShipmentInforTable.vue'
+import OperationLogProcess from './OperationLogProcess.vue'
+import { formatTimezone } from '@/utils/tools'
+
+const visible = ref(false)
+const loading = ref(false)
+
+const shipmentInfoTableData = ref([])
+const processList = ref()
+const rowData = ref()
+const openDialog = (row: any) => {
+  visible.value = true
+  loading.value = true
+  $api
+    .getDeliveryBookingDialogData({ serial_no: row._serial_no })
+    .then((res) => {
+      const data = res.data.data
+      let status = data.status
+      if (data.update_time !== data.create_time && data.status === 'Pending Approval') {
+        status = 'Modified'
+      }
+      handleStepData(status, data)
+      processList.value = data.operation_log
+      rowData.value = data
+      shipmentInfoTableData.value = data.shipmentsData || []
+    })
+    .finally(() => {
+      loading.value = false
+    })
+}
+
+const stepList: any = ref([])
+
+const handleStepData = (status, data) => {
+  stepList.value.push({
+    label: 'Created',
+    date: data.created_time || '--',
+    icon: 'icon_submit_b'
+  })
+  if (status === 'Modified') {
+    stepList.value.push({
+      label: 'Modified',
+      date: data.update_time || '--',
+      icon: 'icon_edit_b'
+    })
+  }
+  if (status === 'Pending Approval' || status === 'Modified') {
+    stepList.value.push({
+      label: 'Pending',
+      date: 'Current',
+      icon: 'icon_time_b',
+      status: 'current'
+    })
+    stepList.value.push({
+      label: 'Approved',
+      date: '--',
+      icon: 'icon_confirm_b',
+      status: 'unfinished'
+    })
+  } else if (status === 'Approved') {
+    stepList.value.push({
+      label: 'Approved',
+      date: data.update_time || '--',
+      icon: 'icon_confirm_b',
+      status: ''
+    })
+  } else if (status === 'Rejected') {
+    stepList.value.push({
+      label: 'Rejected',
+      date: data.update_time || '--',
+      icon: 'icon_reject_b',
+      status: 'rejected',
+      description:
+        'This is some description information about the pending task, if there is too much content.'
+    })
+  } else if (status === 'Cancelled') {
+    stepList.value.push({
+      label: 'Cancelled',
+      date: data.update_time || '--',
+      icon: 'icon_cancelled_b',
+      status: 'cancelled'
+    })
+  }
+}
+
+const clearData = () => {
+  shipmentInfoTableData.value = []
+  stepList.value = []
+  rowData.value = {}
+}
+defineExpose({
+  openDialog
+})
+</script>
+
+<template>
+  <el-dialog
+    title="Booking Detail"
+    class="booking-detail-dialog"
+    v-model="visible"
+    :close-on-click-modal="false"
+    width="1000px"
+    top="10vh"
+    @closed="clearData"
+    v-if="visible"
+  >
+    <div v-vloading="loading">
+      <DetailStep :stepList="stepList" />
+      <div class="booking-info">
+        <div class="booking-no">
+          <span class="no">Booking No.{{ rowData?.booking_no }}</span>
+          <v-tag class="tag" :type="rowData?.status">{{ rowData?.status }}</v-tag>
+        </div>
+        <div class="created-time">{{ formatTimezone(rowData?.created_time) }}</div>
+      </div>
+      <ShipmentInforTable :data="shipmentInfoTableData" />
+      <div class="delivery-information">
+        <div class="label">Delivery Information</div>
+        <div class="delivery-info">
+          <div class="delivery-item inline-flex" style="width: 200px">
+            <span class="item-label">Mode Type</span>
+            <span class="item-value">{{ rowData?.delivery_mode || '--' }}</span>
+          </div>
+          <div class="delivery-item inline-flex">
+            <span class="item-label">Delivery Date</span>
+            <span class="item-value">
+              <span class="font_family icon-icon_date_b" style="margin-right: 4px"></span>
+              <span style="margin-top: 1px">{{ formatTimezone(rowData?.delivery_date) }}</span>
+            </span>
+          </div>
+          <div class="delivery-item">
+            <span class="item-label">Delivery Address</span>
+            <span class="item-value">
+              <span class="font_family icon-icon_location_b" style="margin-right: 2px"></span>
+              <span style="margin-top: 1px">{{ rowData?.delivery_address || '--' }}</span>
+            </span>
+          </div>
+          <div class="delivery-item">
+            <span class="item-label">Special Requirements</span>
+            <span class="item-value">
+              <span class="font_family icon-icon_paragraph_b" style="margin-right: 2px"></span>
+              <span style="margin-top: 1px">{{ rowData?.special_requirements || '--' }}</span>
+            </span>
+          </div>
+        </div>
+      </div>
+      <el-divider style="margin-top: 8px; margin-bottom: 20px" />
+      <OperationLogProcess :processList="processList" />
+    </div>
+  </el-dialog>
+</template>
+
+<style lang="scss" scoped>
+.booking-info {
+  margin: 34px 16px 0;
+  padding: 8px 16px;
+  border-radius: 12px;
+  background-image: var(--color-booking-info-linear-bg);
+
+  .booking-no {
+    display: flex;
+    align-items: center;
+    .no {
+      margin-top: 2px;
+      font-size: 18px;
+      font-weight: 700;
+      line-height: 21px;
+    }
+    .tag {
+      margin-left: 8px;
+    }
+  }
+  .created-time {
+    margin-top: 8px;
+    font-size: 12px;
+    color: var(--color-neutral-2);
+  }
+}
+
+.delivery-information {
+  margin: 20px 16px 0;
+  .label {
+    margin-bottom: 14px;
+    font-size: 18px;
+    font-weight: 700;
+  }
+
+  .delivery-info {
+    padding: 12px 8px 20px;
+    border-radius: 12px;
+    background-color: var(--color-share-link-bg);
+
+    .delivery-item {
+      display: flex;
+      flex-direction: column;
+      margin-bottom: 16px;
+
+      .item-label {
+        margin-bottom: 8px;
+        font-size: 12px;
+        color: var(--color-neutral-2);
+      }
+      .item-value {
+        display: flex;
+        align-items: center;
+        font-weight: 700;
+      }
+      &.inline-flex {
+        display: inline-flex;
+      }
+      &:last-child {
+        margin-bottom: 0;
+      }
+    }
+  }
+}
+</style>
+<style lang="scss">
+div.booking-detail-dialog {
+  height: 80%;
+  .el-dialog__body {
+    padding: 0;
+    height: calc(100% - 48px);
+    overflow: auto;
+  }
+}
+</style>

+ 177 - 0
src/views/DestinationDelivery/src/components/TableView/src/components/DetailStep.vue

@@ -0,0 +1,177 @@
+<script setup lang="ts">
+import { formatTimezone } from '@/utils/tools'
+const props = defineProps<{
+  stepList: Array<{
+    id?: number
+    label: string
+    date?: string
+    icon: string
+    status: 'completed' | 'current' | 'unfinished' | 'rejected' | 'approved' | 'cancelled'
+    description?: string
+  }>
+}>()
+</script>
+
+<template>
+  <div class="step-container">
+    <template v-for="(step, index) in props.stepList" :key="index">
+      <!-- 单个步骤 -->
+      <div class="step">
+        <div
+          class="step-icon"
+          :class="{
+            'step-icon-approved': step.label === 'Approved' && step.status !== 'unfinished',
+            'step-icon-unfinished': step.status === 'unfinished',
+            'step-icon-current': step.status === 'current',
+            'step-icon-rejected': step.status === 'rejected',
+            'step-icon-cancelled': step.status === 'cancelled'
+          }"
+        >
+          <span
+            :style="{ transform: step.label === 'Created' ? 'rotate(-60deg)' : '' }"
+            class="font_family"
+            :class="'icon-' + step.icon"
+          ></span>
+        </div>
+        <div
+          class="step-text"
+          :class="{
+            'step-text-unfinished': step.status === 'unfinished'
+          }"
+        >
+          <div class="step-title">
+            {{ step.label }}
+            <el-tooltip
+              trigger="hover"
+              effect="dark"
+              placement="top"
+              :content="step.description || ''"
+            >
+              <span
+                style="font-size: 12px"
+                v-if="step.status === 'rejected'"
+                class="font_family icon-icon_info_b"
+              ></span>
+            </el-tooltip>
+          </div>
+          <div class="step-date">
+            {{
+              step.date !== '--' && step.date !== 'Current' ? formatTimezone(step.date) : step.date
+            }}
+          </div>
+        </div>
+      </div>
+
+      <!-- 连线(非最后一个) -->
+      <div
+        v-if="index < stepList.length - 1"
+        :class="[
+          stepList?.[index + 1]?.status === 'unfinished'
+            ? 'step-line-unfinished'
+            : 'step-line-completed'
+        ]"
+        class="step-line"
+      ></div>
+    </template>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.step-container {
+  display: flex;
+  align-items: center;
+  width: 100%;
+  height: 34px;
+  margin-top: 24px;
+  padding: 0 100px;
+}
+
+.step {
+  display: flex;
+  align-items: center;
+  flex-shrink: 0;
+}
+
+.step-icon {
+  width: 32px;
+  height: 32px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: var(--color-neutral-1);
+  span {
+    color: var(--color-mode);
+    font-size: 16px;
+  }
+  &.step-icon-unfinished {
+    background: var(--color-steps-unfinished-line);
+  }
+
+  &.step-icon-current {
+    background: var(--color-steps-current-icon-bg);
+    span {
+      color: var(--color-steps-current-icon-color);
+    }
+  }
+  &.step-icon-rejected {
+    background: var(--color-steps-rejected-bg);
+    span {
+      color: #c9353f;
+    }
+  }
+  &.step-icon-approved {
+    background: var(--color-steps-approved-bg);
+    span {
+      color: #5bb462;
+    }
+  }
+  &.step-icon-cancelled {
+    background: var(--color-steps-cancelled-bg);
+    span {
+      color: var(--color-steps-cancelled);
+    }
+  }
+}
+
+.step-text {
+  margin-left: 8px;
+  text-align: left;
+  white-space: nowrap;
+  &.step-text-unfinished {
+    .step-title,
+    .step-date {
+      color: var(--color-steps-unfinished-line);
+      span {
+        color: var(--color-steps-unfinished-line);
+      }
+    }
+  }
+}
+
+.step-title {
+  font-weight: 600;
+  color: var(--color-neutral-1);
+  span {
+    color: var(--color-neutral-1);
+  }
+}
+
+.step-date {
+  font-size: 12px;
+  color: var(--color-neutral-2);
+}
+
+.step-line {
+  height: 0;
+  flex: 1;
+  align-self: flex-start;
+  margin: 8px 16px 0;
+  &.step-line-completed {
+    border-top: 1px solid var(--color-neutral-1);
+  }
+  &.step-line-unfinished {
+    border-top: 1px dashed var(--color-steps-unfinished-line);
+  }
+}
+</style>

+ 190 - 0
src/views/DestinationDelivery/src/components/TableView/src/components/DownloadDialog.vue

@@ -0,0 +1,190 @@
+<script setup lang="ts">
+const dialogVisible = ref(false)
+
+const openDialog = (selectedColumns: string[], slectedDataNumber: number) => {
+  selectedDataNumber.value = slectedDataNumber
+  columns.value = selectedColumns
+  dialogVisible.value = true
+}
+
+const isShowSelectColumn = ref(false)
+
+const downloadFilter = ref(1)
+const selectedDataNumber = ref(0)
+
+const columns = ref()
+
+const emits = defineEmits<{ export: [number] }>()
+const handleDownload = () => {
+  emits('export', downloadFilter.value)
+}
+
+const clearData = () => {
+  isShowSelectColumn.value = false
+  downloadFilter.value = 1
+}
+
+defineExpose({
+  openDialog,
+  handleDownload
+})
+</script>
+
+<template>
+  <div>
+    <el-dialog @close="clearData" v-model="dialogVisible" title="Download File" width="540">
+      <div class="download-dialog">
+        <div class="select-data">
+          <div style="display: inline-block">
+            Select data on your Opeartion Log list:<span style="color: var(--color-theme)">{{
+              selectedDataNumber
+            }}</span>
+          </div>
+        </div>
+        <div class="download-filter">
+          <el-radio-group v-model="downloadFilter">
+            <el-radio :value="1"
+              >Download with selected columns
+              <span class="column-number">{{ columns.length }}</span>
+              <SeeAllIcon v-model="isShowSelectColumn" />
+            </el-radio>
+            <div
+              v-if="isShowSelectColumn"
+              class="select-columns"
+              :class="{ show: isShowSelectColumn }"
+            >
+              <div class="title">Selected columns</div>
+              <div class="content">
+                <div class="column-item" v-for="item in columns" :key="item">{{ item }}</div>
+              </div>
+            </div>
+            <el-radio :value="2">Download with all columns</el-radio>
+          </el-radio-group>
+        </div>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button class="cancel-btn" type="default" @click="dialogVisible = false"
+            >Cancel</el-button
+          >
+          <el-button class="download-btn el-button--dark" @click="handleDownload"
+            ><span style="margin-right: 8px" class="font_family icon-icon_download_b"></span>
+            Download</el-button
+          >
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.download-dialog {
+  color: var(--color-neutral-1);
+}
+
+.select-data {
+  font-weight: 700;
+}
+
+.data-filter {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  max-height: 120px;
+  margin-top: 8px;
+  overflow: auto;
+
+  .filter-item {
+    height: 22px;
+    padding: 0px 8px;
+    background-color: var(--color-download-file-filter-tag-bg);
+    border-radius: 12px;
+    line-height: 22px;
+    font-size: 12px;
+  }
+}
+
+.download-filter {
+  margin-top: 16px;
+
+  .el-radio-group {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+
+    .el-radio {
+      height: 40px;
+      align-items: center;
+    }
+
+    :deep(.el-radio__label) {
+      margin-top: 2px;
+      font-weight: 700;
+      color: var(--color-neutral-1);
+    }
+
+    .column-number {
+      padding: 3px 5px;
+      background-color: var(--color-theme);
+      border-radius: 12px;
+      font-size: 12px;
+      font-weight: 700;
+      color: #fff;
+    }
+
+    .see-all-btn {
+      margin-left: 8px;
+      color: var(--color-theme);
+      font-size: 12px;
+    }
+
+    .select-columns {
+      max-height: 350px;
+      padding: 8px;
+      margin-top: 8px;
+      background-color: var(--color-dialog-header-bg);
+      border-radius: 6px;
+      overflow: hidden;
+
+      &.show {
+        max-height: 500px;
+      }
+
+      .title {
+        font-size: 12px;
+        font-weight: 700;
+      }
+
+      .content {
+        display: flex;
+        flex-wrap: wrap;
+        margin-top: 8px;
+        gap: 8px;
+
+        .column-item {
+          height: 22px;
+          padding: 0px 8px;
+          background-color: var(--color-download-file-selected-column-tag-bg);
+          line-height: 22px;
+          border-radius: 12px;
+          font-size: 12px;
+        }
+      }
+    }
+  }
+}
+
+.dialog-footer {
+  .el-button {
+    height: 40px;
+  }
+
+  .cancel-btn {
+    width: 115px;
+  }
+
+  .download-btn {
+    width: 136px;
+  }
+}
+</style>

+ 392 - 0
src/views/DestinationDelivery/src/components/TableView/src/components/EmailDialog.vue

@@ -0,0 +1,392 @@
+<script setup lang="ts">
+import '@wangeditor/editor/dist/css/style.css'
+import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
+import { i18nChangeLanguage, DomEditor } from '@wangeditor/editor'
+import { formatTimezone } from '@/utils/tools'
+
+i18nChangeLanguage('en')
+
+const visible = ref(false)
+const rowData = ref()
+const openDialog = (row) => {
+  emailData.value.email = row.kln_pic
+  $api
+    .getEmailRecords({
+      serial_no: row._serial_no
+    })
+    .then((res) => {
+      if (res.code === 200) {
+        emailRecords.value = res.data.emailRecords
+      }
+    })
+  visible.value = true
+  rowData.value = row
+}
+const props = defineProps({
+  data: Object
+})
+
+const emailData = ref({
+  email: '',
+  ccEmail: ''
+})
+
+const emailRecords: any = ref([])
+watch(
+  () => props.data,
+  (newVal) => {
+    if (newVal) {
+      const email = newVal?.email
+      emailData.value.email = email?.email
+      emailData.value.ccEmail = email?.cc_email
+      emailRecords.value = email?.emailRecords
+    }
+  },
+  {
+    immediate: true,
+    deep: true
+  }
+)
+
+const editorRef = shallowRef()
+const mode = ref('default')
+// 内容 HTML
+const valueHtml = ref('')
+
+const toolbarConfig = {
+  excludeKeys: [
+    'headerSelect',
+    'italic',
+    'fontFamily',
+    'lineHeight',
+    'group-more-style', // 排除菜单组,写菜单组 key 的值即可
+    'group-video', // 插入视频
+    'insertTable', // 插入表格
+    'codeBlock', // 代码块
+    'fullScreen' // 全屏
+  ]
+}
+const editorConfig = {
+  MENU_CONF: {
+    uploadImage: {
+      server: '/api/upload',
+      base64LimitSize: 5 * 1024 * 1024
+    }
+  }
+}
+
+// 组件销毁时,也及时销毁编辑器
+onBeforeUnmount(() => {
+  const editor = editorRef.value
+  if (editor == null) return
+  editor.destroy()
+})
+
+const handleCreated = (editor: any) => {
+  editorRef.value = editor // 记录 editor 实例,重要!
+  // 在 nextTick 中获取 toolbar 实例
+  nextTick(() => {
+    const toolbar = DomEditor.getToolbar(editor)
+  })
+}
+
+const editorIconList = [
+  {
+    dataMenuKey: 'blockquote',
+    svgUrl: () => import('@/icons/icon_quotes_b.svg')
+  },
+  {
+    dataMenuKey: 'bold',
+    svgUrl: () => import('@/icons/icon_bold_b.svg')
+  },
+  // {
+  //   dataMenuKey: 'underline',
+  //   svgUrl: () => import('@/icons/icon_underline_b.svg')
+  // },
+  {
+    dataMenuKey: 'undo',
+    svgUrl: () => import('@/icons/icon_revoke__b.svg')
+  },
+  {
+    dataMenuKey: 'redo',
+    svgUrl: () => import('@/icons/icon_redo_b.svg')
+  }
+]
+// Vue 组件生命周期钩子函数
+onMounted(async () => {
+  for (const item of editorIconList) {
+    const svgModule = await item.svgUrl()
+    const svgUrl = svgModule.default // 获取 SVG 文件的 URL
+    replaceSvgByDataKey(item.dataMenuKey, svgUrl)
+  }
+})
+const replaceSvgByDataKey = (dataMenuKey: any, svgUrl: any) => {
+  const observer = new MutationObserver((mutationsList, observer) => {
+    const element = document.querySelector(`[data-menu-key="${dataMenuKey}"]`)
+    if (element) {
+      // 获取 SVG 内容
+      fetch(svgUrl)
+        .then((res) => res.text())
+        .then((svgContent) => {
+          // 查找旧的 SVG 标签
+          const oldSvg = element.querySelector('svg')
+          if (oldSvg) {
+            oldSvg.outerHTML = svgContent // 替换 SVG
+          }
+        })
+      observer.disconnect() // 找到元素后停止观察
+    }
+  })
+
+  observer.observe(document.body, {
+    childList: true,
+    subtree: true
+  })
+}
+
+const handleFocusEditor = () => {
+  editorRef.value.focus()
+}
+
+const sendEmail = () => {
+  const html = editorRef.value.getHtml()
+  const text = editorRef.value.getText()
+  $api
+    .saveDliveryBookingEmail({
+      email: emailData.value.email,
+      communication_cc: emailData.value.ccEmail,
+      serial_no: rowData.value._serial_no,
+      content: html,
+      text,
+      h_bol: rowData.value.h_bol
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        ElMessage.success('Email sent successfully')
+        emailRecords.value = res.data.emailRecords
+      }
+    })
+    .catch(() => {
+      ElMessage.error('Failed to send email')
+    })
+}
+
+const clearData = () => {
+  valueHtml.value = ''
+  emailData.value = {
+    email: '',
+    ccEmail: ''
+  }
+  emailRecords.value = []
+}
+
+defineExpose({
+  openDialog
+})
+</script>
+
+<template>
+  <el-dialog
+    title="Communication"
+    class="delivery-email-dialog"
+    v-model="visible"
+    :close-on-click-modal="false"
+    width="1000px"
+    top="12vh"
+    @closed="clearData"
+    v-if="visible"
+  >
+    <div class="email-view">
+      <div class="email-path">
+        <span class="font_family icon-icon_email_b" style="font-size: 18px"></span>
+        <span class="label">Communicate with us:&nbsp;</span>
+        <span class="content">{{ emailData.email }}</span>
+      </div>
+      <div class="separated-by">
+        <el-input v-model="emailData.ccEmail">
+          <template #prefix>
+            <div
+              style="
+                display: flex;
+                align-items: center;
+                color: var(--color-neutral-1);
+                cursor: default;
+              "
+            >
+              <span style="font-weight: 600">CC:</span>
+              <el-tooltip
+                class="box-item"
+                effect="dark"
+                content="Separated by;"
+                placement="top-start"
+                :offset="-8"
+              >
+                <span class="font_family icon-icon_tipsfilled_b" style="font-size: 19px"></span>
+              </el-tooltip>
+            </div>
+          </template>
+        </el-input>
+      </div>
+      <div class="text-editor">
+        <Toolbar
+          style="border-bottom: 1px solid #ccc"
+          :editor="editorRef"
+          :defaultConfig="toolbarConfig"
+          :mode="mode"
+        />
+        <Editor
+          v-model="valueHtml"
+          :defaultConfig="editorConfig"
+          :mode="mode"
+          @onCreated="handleCreated"
+          @click="handleFocusEditor"
+        />
+      </div>
+      <div>
+        <el-button
+          @click="sendEmail"
+          class="el-button--dark"
+          style="float: right; margin: 8px 0 14px 0; height: 40px"
+          ><span class="font_family icon-icon_submit_b" style="margin-right: 4px"></span> Send
+          Email</el-button
+        >
+      </div>
+    </div>
+    <el-divider style="margin: 16px 0; border-top-color: var(--color-divider)" />
+    <div class="show-records">
+      <div class="record-item" v-for="(item, index) in emailRecords" :key="index">
+        <div class="header">
+          <div class="avatar">
+            <div>{{ item.name?.slice(0, 1) }}</div>
+          </div>
+          <div class="name">{{ item.name }}</div>
+          <div class="date">{{ formatTimezone(item.creatTime) }}</div>
+        </div>
+        <div class="content">
+          {{ item.content }}
+        </div>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+
+<style lang="scss" scoped>
+.email-view {
+  display: flex;
+  flex-direction: column;
+  padding: 16px;
+  padding-bottom: 0;
+  border-radius: 12px;
+  background: var(--color-email-bg);
+}
+
+:deep(.w-e-text-container) {
+  min-height: 170px;
+  border-radius: 0 0 6px 6px;
+  p {
+    margin: 0px;
+  }
+}
+
+.email-path {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: 12px;
+  font-weight: 600;
+
+  & > .label {
+    margin-left: 8px;
+    padding-top: 2px;
+    color: var(--color-neutral-1);
+    white-space: nowrap;
+  }
+
+  & > .content {
+    display: inline-block;
+    flex: 1;
+    padding-top: 2px;
+    line-height: 18px;
+    color: var(--color-theme);
+    word-break: break-all;
+  }
+}
+.separated-by {
+  :deep(.el-input__wrapper) {
+    box-shadow: 0 0 0 1px var(--color-email-border) inset;
+  }
+}
+
+.text-editor {
+  margin-top: 16px;
+  border-radius: 6px;
+  border: 1px solid var(--color-email-border);
+  // overflow: hidden;
+  :deep(div.w-e-toolbar) {
+    border-radius: 6px 6px 0 0;
+  }
+}
+
+.record-item {
+  margin-top: 16px;
+
+  & > .header {
+    display: flex;
+    align-items: center;
+    padding: 0px 16px 8px;
+    padding-left: 0px;
+
+    .avatar {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      width: 24px;
+      height: 24px;
+      text-align: center;
+      border-radius: 50%;
+      background-color: var(--color-theme);
+      div {
+        height: 14px;
+        line-height: 14px;
+        color: var(--color-avatar);
+        font-weight: 700;
+      }
+    }
+
+    .name {
+      height: 24px;
+      margin-left: 4px;
+      margin-right: 8px;
+      line-height: 24px;
+      font-weight: 700;
+      color: var(--color-neutral-1);
+    }
+
+    .date {
+      height: 24px;
+      line-height: 24px;
+      font-size: 12px;
+      color: var(--color-neutral-2);
+    }
+  }
+
+  & > .content {
+    padding: 16px 6px;
+    background-color: var(--color-share-link-bg);
+    border-radius: 6px;
+  }
+}
+
+:deep(.text-editor) {
+  & > div:first-of-type {
+    border-bottom: 1px solid var(--color-email-border) !important;
+  }
+}
+</style>
+<style>
+.delivery-email-dialog {
+  .show-records {
+    max-height: 300px;
+    overflow: auto;
+  }
+}
+</style>

+ 138 - 0
src/views/DestinationDelivery/src/components/TableView/src/components/OperationLogProcess.vue

@@ -0,0 +1,138 @@
+<script setup lang="ts">
+import { formatTimezone } from '@/utils/tools'
+const props = withDefaults(
+  defineProps<{
+    processList: Array<any>
+  }>(),
+  {
+    processList: []
+  }
+)
+// const processList = ref([
+//   {
+//     time: 'Jun-01-2024 16:25:31 ',
+//     timezone: 'UTC+3',
+//     label: 'Submit Booking',
+//     createdBy: 'Customer A',
+//     tips: '--'
+//   },
+//   {
+//     time: 'May-15-2024 16:25:31 ',
+//     timezone: 'UTC+3',
+//     label: 'Reject Booking',
+//     createdBy: 'John Doe',
+//     tips: 'Too early'
+//   }
+// ])
+</script>
+
+<template>
+  <div class="operation-log">
+    <div class="label">Operation Log</div>
+    <div class="process">
+      <!-- <div class="left-line"></div> -->
+      <div class="right-process-box">
+        <div class="process-item" v-for="(item, index) in processList" :key="index">
+          <div class="left-process-line">
+            <div class="icon-box"></div>
+            <div class="process-line" v-if="index !== processList.length - 1"></div>
+          </div>
+          <div class="process-content">
+            <p class="process-time">{{ formatTimezone(item.time, item.timezone) }}</p>
+            <div class="process-data">
+              <div class="process-data-label">{{ item.label }}</div>
+              <div class="process-data-user">{{ item.createdBy }}</div>
+              <div class="--process-data-tips">{{ item.tips || '--' }}</div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.operation-log {
+  padding: 0 16px;
+  .label {
+    margin-bottom: 16px;
+    font-size: 18px;
+    font-weight: 700;
+  }
+  .process {
+    position: relative;
+    display: flex;
+    align-items: center;
+    .left-line {
+      position: absolute;
+      left: 8px;
+      top: 0;
+      bottom: 0;
+      width: 2px;
+      background-color: var(--color-neutral-3);
+    }
+    .right-process-box {
+      flex-grow: 1;
+      display: flex;
+      flex-direction: column;
+
+      .process-item {
+        display: flex;
+        align-items: center;
+        height: 126px;
+        .left-process-line {
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          height: 100%;
+          margin-right: 8px;
+          .icon-box {
+            width: 8px;
+            height: 8px;
+            border-radius: 50%;
+            background-color: var(--color-neutral-1);
+          }
+          .process-line {
+            flex: 1;
+            border-left: 1px dashed var(--color-neutral-1);
+          }
+        }
+
+        .process-content {
+          margin-top: -3px;
+          .process-time {
+            font-size: 14px;
+            font-weight: 500;
+            color: var(--color-neutral-1);
+          }
+          .process-data {
+            width: 480px;
+            padding: 8px;
+            margin-top: 8px;
+            margin-bottom: 16px;
+            border-radius: 12px;
+            background: var(--color-share-link-bg);
+            .process-data-label {
+              font-weight: 700;
+            }
+            .process-data-user {
+              margin-top: 4px;
+              font-size: 12px;
+              color: var(--color-neutral-2);
+            }
+            .--process-data-tips {
+              width: 464px;
+              height: 28px;
+              margin-top: 8px;
+              padding: 0 8px;
+              border-radius: 6px;
+              line-height: 28px;
+              background: var(--color--process-data-tips-bg);
+            }
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 129 - 0
src/views/DestinationDelivery/src/components/TableView/src/components/ShipmentInforTable.vue

@@ -0,0 +1,129 @@
+<script setup lang="ts">
+import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
+import { useRowClickStyle } from '@/hooks/rowClickStyle'
+import { formatTimezone, formatNumber } from '@/utils/tools'
+
+const props = defineProps({
+  data: Object
+})
+const tableRef = ref<VxeGridInstance | null>(null)
+const tableData = ref<VxeGridProps<any>>({
+  minHeight: 70,
+  border: true,
+  round: true,
+  columns: [
+    {
+      field: 'HBOL No.',
+      title: 'HBOL No.',
+      width: 120
+    },
+    {
+      field: 'Container No.',
+      title: 'Container No.'
+    },
+    {
+      field: 'Service Type',
+      title: 'Service Type'
+    },
+    {
+      field: 'ETA',
+      title: 'ETA',
+      minWidth: 100,
+      formatter: ({ cellValue }: any) => formatTimezone(cellValue)
+    },
+
+    {
+      field: 'Recommended Delivery Date',
+      title: 'Recommended Delivery Date',
+      width: 220,
+      formatter: ({ cellValue }: any) => {
+        if (!cellValue) return '--'
+        const rangeData = cellValue.split(';')
+        const leftDate = rangeData[0] ? formatTimezone(rangeData[0]) : '_'
+        const rightDate = rangeData[1] ? formatTimezone(rangeData[0]) : '_'
+        return leftDate + ' -- ' + rightDate
+      }
+    }
+  ],
+  data: [],
+  scrollY: { enabled: true, oSize: 20, gt: 30 },
+  emptyText: ' ',
+  showHeaderOverflow: true,
+  showOverflow: true,
+  headerRowStyle: {
+    backgroundColor: 'var(--color-table-header-bg)'
+  },
+  columnConfig: { resizable: true, useKey: true },
+  rowConfig: { isHover: true }
+})
+
+// 获得表格数据后赋值
+const assignTableData = (data: any) => {
+  tableData.value.data = data || []
+}
+watch(
+  () => props.data,
+  (newData) => {
+    if (newData) {
+      assignTableData(newData)
+    }
+  },
+  { immediate: true, deep: true }
+)
+const tableLoadingTable = ref()
+
+let searchdata: any = {}
+
+// 获取表格数据
+const getTableData = async () => {
+  const rc = -1
+  tableLoadingTable.value = true
+  await $api
+    .SearchOperationLog({
+      cp: 1,
+      ps: 6,
+      rc,
+      ...searchdata
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        assignTableData(res.data)
+      }
+    })
+    .finally(() => {
+      tableLoadingTable.value = false
+    })
+}
+
+// 实现行点击样式
+useRowClickStyle(tableRef)
+</script>
+
+<template>
+  <div class="shipment-infor-table">
+    <div class="label">Shipment Information</div>
+    <vxe-grid
+      v-vloading="tableLoadingTable"
+      ref="tableRef"
+      v-bind="tableData"
+      max-height="240"
+      min-height="120"
+    >
+      <template #empty v-if="!tableLoadingTable && tableData.data.length === 0">
+        <div class="empty">No data</div>
+      </template>
+    </vxe-grid>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.shipment-infor-table {
+  margin-top: 20px;
+  padding: 8px 16px;
+  .label {
+    margin-bottom: 14px;
+    font-size: 18px;
+    font-weight: 700;
+  }
+}
+</style>

+ 143 - 0
src/views/DestinationDelivery/src/components/TableView/src/components/TipsDialog.vue

@@ -0,0 +1,143 @@
+<script setup lang="ts">
+const dialogType = ref()
+const bookingNo = ref()
+const dialogVisible = ref(false)
+const serialNo = ref()
+const tipList = ref({
+  reject: 'Are you sure you want to Reject Booking ',
+  approve: 'Are you sure you want to Approve Booking ',
+  cancel: 'Are you sure you want to Cancel Booking '
+})
+const notes = ref()
+
+const typeList = {
+  approve: 'Approve',
+  reject: 'Reject',
+  cancel: 'Cancel'
+}
+
+const openDialog = (type: string, row: any) => {
+  dialogVisible.value = true
+  bookingNo.value = row.booking_no
+  dialogType.value = type
+  serialNo.value = row._serial_no
+}
+const emit = defineEmits(['stateChange'])
+
+const handleSubmit = () => {
+  if (dialogType.value === 'reject' && !notes.value) {
+    ElMessage.warning('A remark must be filled in for the rejection operation.')
+    return
+  }
+  $api
+    .reviewDliveryBooking({
+      serial_no: serialNo.value,
+      status: typeList[dialogType.value],
+      notes: notes.value
+    })
+    .then((res) => {
+      if (res.code === 200) {
+        dialogVisible.value = false
+        ElMessage.success(`${typeList[dialogType.value]} successfully`)
+        emit('stateChange')
+      } else {
+        ElMessage.error(`${typeList[dialogType.value]} failed, Please try again.`)
+      }
+    })
+    .catch(() => {
+      ElMessage.error(`${typeList[dialogType.value]} failed, Please try again.`)
+    })
+}
+const clearData = () => {
+  bookingNo.value = ''
+  dialogType.value = ''
+  serialNo.value = ''
+  notes.value = ''
+}
+
+defineExpose({
+  openDialog
+})
+</script>
+
+<template>
+  <el-dialog
+    v-model="dialogVisible"
+    :show-close="false"
+    title=""
+    width="800px"
+    @closed="clearData"
+    top="20vh"
+    class="tips-dialog"
+  >
+    <div class="tips-dialog-title">
+      <span style="" class="font_family icon-icon_tipsfilled_b"></span>
+      <span style="color: #c9353f" v-if="dialogType === 'reject'">*</span>
+      <span> {{ tipList[dialogType] + bookingNo + '?' }}</span>
+    </div>
+    <el-input
+      :autosize="false"
+      type="textarea"
+      v-model="notes"
+      class="input-textarea"
+      style="height: 120px"
+      placeholder="Input remarks"
+    ></el-input>
+    <template #footer>
+      <el-button class="cancel-btn" type="default" @click="dialogVisible = false">Cancel</el-button>
+      <el-button
+        class="submit-btn"
+        :class="{
+          'el-button--success': dialogType === 'approve',
+          'el-button--danger': dialogType === 'reject',
+          'el-button--warning': dialogType === 'cancel'
+        }"
+        @click="handleSubmit"
+        >{{ typeList[dialogType] }} Booking</el-button
+      >
+    </template>
+  </el-dialog>
+</template>
+
+<style lang="scss" scoped>
+.tips-dialog-title {
+  display: flex;
+  align-items: center;
+  margin-top: 8px;
+  margin-bottom: 8px;
+  font-size: 18px;
+  font-weight: 700;
+  line-height: 24px;
+  .font_family {
+    margin-right: 4px;
+    color: #f19d38;
+    font-size: 28px;
+  }
+}
+.input-textarea {
+  :deep(.el-textarea__inner) {
+    height: 120px;
+    resize: none;
+    line-height: 22px;
+  }
+}
+
+.cancel-btn {
+  width: 100px;
+  height: 40px;
+}
+.submit-btn {
+  height: 40px;
+}
+</style>
+<style lang="scss">
+.tips-dialog {
+  .el-dialog__header {
+    display: none;
+  }
+  .el-dialog__footer {
+    padding-top: 2px;
+    border-top: none;
+  }
+}
+</style>

binární
src/views/DestinationDelivery/src/components/TableView/src/img/table-empty-img.png


+ 75 - 5
src/views/Layout/src/components/Menu/MenuView.vue

@@ -18,11 +18,82 @@ watch(
   }
 )
 const getMenuList = () => {
-  $api.getMenuList().then((res) => {
-    if (res.code === 200) {
-      menuList.value = res.data
+  // $api.getMenuList().then((res) => {
+  //   if (res.code === 200) {
+  //     menuList.value = res.data
+  //   }
+  // })
+  menuList.value = [
+    {
+      index: '1',
+      label: 'Dashboard',
+      icon: 'icon_data_fill_b',
+      path: '/dashboard'
+    },
+    {
+      index: '2',
+      label: 'Booking',
+      icon: 'icon_booking__fill_b',
+      // path: '/booking',
+      type: 'list',
+      children: [
+        {
+          index: '2-1',
+          label: 'Booking Management',
+          path: '/booking'
+        },
+        {
+          index: '2-2',
+          label: 'Destination Delivery',
+          path: '/destination-delivery'
+        }
+      ]
+    },
+    {
+      index: '3',
+      label: 'Tracking',
+      icon: 'icon_tracking__fill_b',
+      path: '/tracking'
+    },
+    {
+      index: '4',
+      label: 'System Management',
+      icon: 'icon_system__management_fill_b',
+      type: 'list',
+      children: [
+        {
+          index: '4-1',
+          label: 'System Message',
+          path: '/system-message'
+        },
+        {
+          index: '4-2',
+          label: 'System Settings',
+          path: '/SystemSettings'
+        },
+        {
+          index: '4-3',
+          label: 'Chat Log',
+          path: '/chat-log'
+        },
+        {
+          index: '4-4',
+          label: 'AI API Log',
+          path: '/ai-api-log'
+        },
+        {
+          index: '4-5',
+          label: 'Operation Log',
+          path: '/Operationlog'
+        },
+        {
+          index: '4-6',
+          label: 'Prompt Configuration',
+          path: '/PromptConfiguration'
+        }
+      ]
     }
-  })
+  ]
 }
 getMenuList()
 //监听窗口大小
@@ -145,7 +216,6 @@ const jumpLink = (link: string) => {
       @select="changeRouter"
       :default-active="activeMenu"
       :default-openeds="openeds"
-      :unique-opened="true"
       :collapse="isCollapse"
     >
       <template v-for="item in menuList" :key="item.index">

+ 1 - 3
src/views/Tracking/src/components/TrackingTable/src/components/VGMView.vue

@@ -61,7 +61,7 @@ const generalInfo = ref({
 const tableRef = ref<VxeGridInstance | null>(null)
 const tableData = ref<VxeGridProps<any>>({
   minHeight: 70,
-  height: '330',
+  height: '390',
   border: true,
   round: true,
   columns: [],
@@ -323,7 +323,6 @@ const handleSave = () => {
       ...item
     }
   })
-
   let variableList = []
   let tableInfo = {}
   if (tableRowData.length !== 0) {
@@ -341,7 +340,6 @@ const handleSave = () => {
       })
     })
   }
-
   $api
     .saveVGMData({
       serial_no: allData.value.serial_no,