Преглед на файлове

Merge branch 'dev' into dev_zyh

Jack Zhou преди 4 месеца
родител
ревизия
cd64ba3ebd
променени са 32 файла, в които са добавени 3710 реда и са изтрити 18 реда
  1. 1 1
      package.json
  2. 3 1
      src/api/index.ts
  3. 201 0
      src/api/module/Delivery.ts
  4. 1 2
      src/auto-imports.d.ts
  5. 3 1
      src/components/AutoSelect/src/AutoSelect.vue
  6. 21 4
      src/components/DateRange/src/components/CalendarDate.vue
  7. 0 1
      src/components/MoreFilters/src/components/SelectValue.vue
  8. 11 1
      src/components/VBreadcrumb/src/VBreadcrumb.vue
  9. 15 0
      src/router/index.ts
  10. 60 1
      src/stores/modules/breadCrumb.ts
  11. 24 0
      src/styles/Antdui.scss
  12. 39 0
      src/styles/elementui.scss
  13. 2 3
      src/views/DestinationDelivery/src/DestinationDelivery.vue
  14. 1 0
      src/views/DestinationDelivery/src/components/ConfiguRations/index.ts
  15. 95 0
      src/views/DestinationDelivery/src/components/ConfiguRations/src/ConfiguRations.vue
  16. 259 0
      src/views/DestinationDelivery/src/components/ConfiguRations/src/components/ConfigurationsTable.vue
  17. 608 0
      src/views/DestinationDelivery/src/components/ConfiguRations/src/components/CreateNewRule.vue
  18. 750 0
      src/views/DestinationDelivery/src/components/ConfiguRations/src/components/RecommendDate.vue
  19. 201 0
      src/views/DestinationDelivery/src/components/ConfiguRations/src/components/SelectStation.vue
  20. 201 0
      src/views/DestinationDelivery/src/components/ConfiguRations/src/components/SelectValue.vue
  21. 168 0
      src/views/DestinationDelivery/src/components/ConfiguRations/src/components/SetBookingWindow.vue
  22. BIN
      src/views/DestinationDelivery/src/components/ConfiguRations/src/images/default_configuration@2x.png
  23. BIN
      src/views/DestinationDelivery/src/components/ConfiguRations/src/images/icon_success_big@2x.png
  24. 1 0
      src/views/DestinationDelivery/src/components/CreateNewBooking/index.ts
  25. 825 0
      src/views/DestinationDelivery/src/components/CreateNewBooking/src/CreateNewbooking.vue
  26. 208 0
      src/views/DestinationDelivery/src/components/CreateNewBooking/src/components/NewbookingTable.vue
  27. BIN
      src/views/DestinationDelivery/src/components/CreateNewBooking/src/images/default_add_address@2x.png
  28. BIN
      src/views/DestinationDelivery/src/components/CreateNewBooking/src/images/default_destination_not_available@2x.png
  29. BIN
      src/views/DestinationDelivery/src/components/CreateNewBooking/src/images/default_no_shipment@2x.png
  30. BIN
      src/views/DestinationDelivery/src/components/CreateNewBooking/src/images/icon_success_big@2x.png
  31. 12 1
      src/views/DestinationDelivery/src/components/TableView/src/TableView.vue
  32. 0 2
      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 // 取出所有可遍历属性赋值在新的对象上

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

@@ -0,0 +1,201 @@
+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
+  )
+}

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

+ 21 - 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) => {
@@ -37,7 +53,7 @@ watch(
   { 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 +84,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 +130,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 +141,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 +155,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,

+ 15 - 0
src/router/index.ts

@@ -148,6 +148,21 @@ const router = createRouter({
           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',

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

@@ -16,7 +16,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 +32,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 = [
           {
@@ -81,6 +98,48 @@ export const useBreadCrumb = defineStore('breadCrumb', {
             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,

+ 24 - 0
src/styles/Antdui.scss

@@ -183,3 +183,27 @@ tr
 .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;
+}

+ 39 - 0
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);
 }
@@ -870,3 +893,19 @@ div .prompt-dialog-inner .el-dialog__body {
   overflow-y: scroll;
   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);
+  }
+}

+ 2 - 3
src/views/DestinationDelivery/src/DestinationDelivery.vue

@@ -73,11 +73,10 @@ const DateChange = (date: any) => {
 }
 
 const handleConfigurations = () => {
-  // Handle configurations logic here
-  console.log('Configurations clicked')
+  router.push({ name: 'Configurations' })
 }
 const handleCreate = () => {
-  // Handle create new booking logic here
+  router.push({ name: 'Create New Booking' })
 }
 
 const handleTest = () => {

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

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

@@ -0,0 +1,259 @@
+<script setup lang="ts">
+import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
+import { useRowClickStyle } from '@/hooks/rowClickStyle'
+import { formatTimezone, formatNumber } from '@/utils/tools'
+import { ref, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import { useVisitedRowState } from '@/stores/modules/visitedRow'
+import DefaultConfiguration from '../images/default_configuration@2x.png'
+
+const visitedRowState = useVisitedRowState()
+const router = useRouter()
+interface ColumnsListItem {
+  field: String
+  title: String
+  type: String
+  formatter: String
+}
+const props = defineProps({
+  ColumnsList: Array<ColumnsListItem>,
+  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: 'right',
+    width: 100,
+    slots: { default: 'action' }
+  })
+}
+// 获取表格数据
+const getTableData = (isPageChange?: boolean) => {
+  const rc = isPageChange ? pageInfo.value.total : -1
+  $api
+    .getConfigurationList({
+      cp: pageInfo.value.pageNo,
+      ps: pageInfo.value.pageSize,
+      rc
+    })
+    .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 handleedittow = (row: any) => {
+  router.push({
+    path: '/destination-delivery/ConfiguRations/CreateNewRule',
+    query: {a: row._serial_no}
+  })
+}
+
+const clickAddNewRule = () => {
+  router.push({
+    path: '/destination-delivery/ConfiguRations/CreateNewRule',
+    query: {}
+  })
+}
+
+onMounted(() => {
+  getTableColumns()
+  getTableData()
+})
+</script>
+
+<template>
+  <div class="SettingTable">
+    <vxe-grid
+      ref="tableRef"
+      :style="{ border: 'none'}"
+      v-bind="tableData"
+      :height="props.height"
+      @cell-dblclick="handleedit"
+    >
+      <!-- 空数据时的插槽 -->
+      <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="handleedittow(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>

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

@@ -0,0 +1,608 @@
+<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
+let timeout: ReturnType<typeof setTimeout>
+const isNodata = ref([])
+const querySearchAsync = (queryString: string, cb: (arg: any) => void) => {
+  const results = queryString
+    ? KLNLists.value.filter(createFilter(queryString))
+    : KLNLists.value
+  isNodata.value = results
+  clearTimeout(timeout)
+  timeout = setTimeout(() => {
+    if(results.length == 0) {
+      cb([{ 
+        isNoData: true, 
+        value: '无搜索结果' 
+      }]);
+    } else {
+      cb(results)
+    }
+  }, 1000 * Math.random())
+}
+
+const createFilter = (queryString: string) => {
+  return (restaurant: KLNItem) => {
+    return (
+      restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) != -1
+    )
+  }
+}
+
+// 保存
+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(() => {
+  getKLNList()
+  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-autocomplete
+                v-model="KLNPLCvalue"
+                style="width: 400px;margin-bottom: 5px;"
+                placeholder="Select Employee Account"
+                :fetch-suggestions="querySearchAsync"
+              >
+                <template #default="{ item }">
+                  <div :class="[
+                    'suggestion-item',
+                    { 'no-data-item': item.isNoData }
+                  ]">
+                    {{ item.value }}
+                  </div>
+                </template>
+              </el-autocomplete>
+            </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);
+}
+.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;
+}
+.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
src/views/DestinationDelivery/src/components/ConfiguRations/src/images/default_configuration@2x.png


BIN
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'

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

@@ -0,0 +1,825 @@
+<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 DateValue = ref('')
+const bookingTableRef = ref()
+const VesselName = ref([])
+const VesselNametest = ref('')
+const isFocused = ref(false)
+const isataFocused = ref(false)
+const isetaFocused = ref(false)
+const ManageVisible = ref(false)
+const AddNewAddressVisible = ref(false)
+const NoPermissionVisible = ref(false)
+const NoEligibleVisible = ref(false)
+const isDisabled = ref(false)
+const SaveedVisible = ref(false)
+const UnableSaveVisible = ref(false)
+const missingmessage = ref('')
+const ATATimeList = ref(null)
+const ETATimeList = ref(null)
+const Addressradio = ref()
+const LocationName = ref('')
+const AddressLine1 = ref('')
+const AddressLine2 = ref('')
+const CountryCity = ref('')
+const PostalCode = ref('')
+const ContactPerson = ref('')
+const ContactNumber = ref('')
+const instructions = ref('')
+const modetypeValue = ref('Truck')
+const Requirements = ref('')
+const selectedAddressList = ref()
+const isselectedAddress = ref(null)
+const checkShipmentsSubmitInfo = ref({})
+const ModeType = ref([
+  {
+    label: 'Truck',
+    value: 'Truck'
+  }
+])
+
+// 设置无法点击
+const isNotClickAddress = computed(() => {
+  return NoPermissionVisible.value || NoEligibleVisible.value || isDisabled.value
+})
+
+// 设置无法保存
+const isNotSubmit = computed(() => {
+  if(NoPermissionVisible.value || NoEligibleVisible.value || isDisabled.value) {
+    return true
+  }
+
+  if(
+    Object.keys(checkShipmentsSubmitInfo.value).length == 0 ||
+    selectedAddressList.value == undefined ||
+    modetypeValue.value == '' ||
+    DateValue.value == ''
+  ) {
+    return true
+  }
+  return false;
+})
+
+const ManageAddressList = ref([])
+// 需要特殊样式的日期列表
+const specialDates = ref([
+  '2025-05-27',
+  '2025-05-28', 
+]);
+
+
+const DateRangeChangeETA = (val:any) => {
+  ETATimeList.value = val.data
+}
+
+const DateRangeChangeATA = (val:any) => {
+  ATATimeList.value = val.data
+}
+
+const showLabel = computed(() => {
+  // return VesselName.value.length!= 0 || isFocused.value
+  return VesselNametest.value!= '' || isFocused.value
+})
+
+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 = (val: boolean) => {
+  isFocused.value = val
+}
+
+const DateChangeFocusATA = (val: boolean) => {
+  isataFocused.value = val
+}
+const DateChangeFocusETA = (val: boolean) => {
+  isetaFocused.value = val
+}
+
+// 特殊日期样式
+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 SaveNewAddress = () => {
+  if(
+    LocationName.value != '' &&
+    AddressLine1.value != '' &&
+    AddressLine2.value != '' &&
+    CountryCity.value != '' &&
+    PostalCode.value != '' &&
+    ContactPerson.value != '' &&
+    ContactNumber.value != ''
+  ) {
+    ManageAddressList.value.push({
+      location_name: LocationName.value,
+      address_1: AddressLine1.value,
+      address_2: AddressLine2.value,
+      country: CountryCity.value,
+      city: CountryCity.value,
+      postal_code: PostalCode.value,
+      contact_person: ContactPerson.value,
+      contact_number: ContactNumber.value,
+      notes: instructions.value,
+      deliver_address_is_new: 'yes'
+    })
+    AddNewAddressVisible.value = false
+  }
+}
+// 点击按钮
+const handleclickbutton = (val: any) => {
+  Requirements.value = Requirements.value + val
+}
+
+const changeAddressRadio = () => {
+  ManageVisible.value = false
+  if(Addressradio.value != undefined) {
+    isselectedAddress.value = Addressradio.value
+
+  }
+  selectedAddressList.value = ManageAddressList.value[Addressradio.value]
+}
+// 页面初始化
+const getInitBookingData = () => {
+  $api
+  .InitCreateBooking({
+    a: 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)
+      }
+    }
+  })
+}
+// 查询Shipments
+const SearchShipment = () => {
+  $api
+  .BookingTableSearch({
+    serial_no: a != undefined ? a: '',
+    text_search: CreateNewBOokingSearch.value,
+    vessel: VesselNametest.value,
+    eta_start: ETATimeList.value.length != 0 ? ETATimeList.value[0] : '',
+    eta_end: ETATimeList.value.length != 0 ? ETATimeList.value[1] : '',
+    ata_start: ATATimeList.value.length != 0 ? ATATimeList.value[0] : '',
+    ata_end: ATATimeList.value.length != 0 ? ATATimeList.value[1] : ''
+
+  })
+  .then((res: any) => {
+    if (res.code === 200) {
+      console.log(res.data)
+    }
+  })
+}
+
+// 选择shipments获取address book
+const isrecommenddate = ref()
+// 判断date是否一致
+const areAllDateRangesSame = (date: any) => { 
+  const firstRange = date[0].date_range;
+  return date.every(item => {
+    const currentRange = item.date_range;
+    return (
+      currentRange.length === firstRange.length &&
+      currentRange.every((date, index) => date === firstRange[index])
+    );
+  });
+}
+// 遍历日期
+const getDateRangeArray = (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) => {
+  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 = []
+  }
+  $api
+  .getAddressBookList({
+    ...val,
+
+  })
+  .then((res: any) => {
+    if (res.code === 200) {
+      ManageAddressList.value = res.data
+    }
+  })
+}
+
+// 保存
+const SubmitBooking = () => {
+  $api
+  .SaveBookingList({
+    ...checkShipmentsSubmitInfo.value,
+    ...selectedAddressList.value,
+    delivery_mode: modetypeValue.value,
+    delivery_date: DateValue.value,
+    special_requirements: Requirements.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>Create New Booking</div>
+      <div class="flex">
+        <el-button 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>
+    <!-- Select Shipments -->
+    <div class="select_shipments">
+      <div><span class="stars_red">*</span>Select Shipments</div>
+      <div class="flex shipments_search">
+        <el-input
+          placeholder="Enter Booking/HBL/PO/Carrier Booking No. "
+          v-model="CreateNewBOokingSearch"
+          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">
+          <CalendarDate style="margin: 0 8px;" :isNeedFooter="false" :isETA="true" @DateRangeChange="DateRangeChangeETA" @DateChangeFocus="DateChangeFocusETA"></CalendarDate>
+          <span v-if="showETAlabel" class="border-label">ETA</span>
+        </div>
+        <div class="input-with-label">
+          <CalendarDate :isNeedFooter="false" @DateRangeChange="DateRangeChangeATA" @DateChangeFocus="DateChangeFocusATA"></CalendarDate>
+          <span v-if="showataLabel" class="border-label">ATA</span>
+        </div>
+        <div class="input-with-label" style="margin: 0 8px;">
+          <!-- <AutoSelect ASPlaceholder="Input Vessel Name" :ASValue="VesselName" @changeFocus="changeFocus"></AutoSelect> -->
+          <el-input
+            placeholder="Input Vessel Name"
+            v-model="VesselNametest"
+            @focus="changeFocustest(true)"
+            @blur="changeFocustest(false)"
+            class="log_input"
+          >
+          </el-input>
+          <span v-if="showLabel" class="border-label">Vessel Name</span>
+        </div>
+        <el-button class="el-button--dark create-button" @click="SearchShipment">Search</el-button>
+      </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="ManageVisible = true" 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="AddNewAddressVisible = true"> + Add New Address</el-button>
+      </div>
+      <div v-else class="addressradio">
+        <el-radio>
+          <div>
+            <div class="radio_top">
+              <div class="radio_title">{{ selectedAddressList.location_name }}</div>
+            </div>
+            <div class="radio_content">
+              <div class="radio_content_text">{{ selectedAddressList.address_1 }}</div>
+              <div class="radio_content_text">{{ selectedAddressList.country }},{{ selectedAddressList.city }} {{ selectedAddressList.postal_code }}</div>
+            </div>
+            <div class="radio_bottom radio_content_text">
+              Contact: {{ selectedAddressList.contact_person }} {{ selectedAddressList.contact_number }}
+            </div>
+          </div>
+        </el-radio>
+      </div>
+      <div class="delivery_type">
+        <div style="margin-right: 12px;">
+          <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>
+        <div class="delivery_type_title"><span class="stars_red">*</span>Preferred Delivery Date</div>
+        <a-date-picker
+          v-model:value ="DateValue"
+          :disabled="isNotClickAddress"
+          :showToday="false"
+          style="width: 240px;"
+          :format="userStore.dateFormat"
+          placeholder="Please Select Date"
+          valueFormat="MM/DD/YYYY"
+        >
+          <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">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>
+    <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.location_name }}</div>
+              <div>
+                <el-button class="el-button--blue"><span class="font_family icon-icon_edit_b"></span></el-button>
+                <el-button class="el-button--blue"><span class="font_family icon-icon_delete_b"></span></el-button>
+              </div>
+            </div>
+            <div class="radio_content">
+              <div class="radio_content_text">{{ item.address_1 }}</div>
+              <div class="radio_content_text">{{ item.country }},{{ item.city }} {{ item.postal_code }}</div>
+            </div>
+            <div class="radio_bottom radio_content_text">
+              Contact: {{ item.contact_person }} {{ item.contact_number }}
+            </div>
+          </div>
+        </el-radio>
+      </el-radio-group>
+      <template #header>
+        <div class="my-header">
+          <div class="header_Title">Manage Address</div>
+          <el-button  @click="AddNewAddressVisible = true" 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>Location Name</div>
+      <el-input class="inputmargin" placeholder="Enter location name" v-model="LocationName"></el-input>
+      <div class="diaolog_add_title"><span class="stars_red">*</span>Address Line 1</div>
+      <el-input class="inputmargin" placeholder="Street address, P.O. Box, or company name" v-model="AddressLine1"></el-input>
+      <div class="diaolog_add_title">Address Line 2</div>
+      <el-input class="inputmargin" placeholder="Unit number, floor, etc. (optional)" v-model="AddressLine2"></el-input>
+      <div class="flex">
+        <div style="margin-right: 9px;width: 50%;">
+          <div class="diaolog_add_title"><span class="stars_red">*</span>Country/City</div>
+          <el-input class="inputmargin" placeholder="Unit number, floor, etc. (optional)" v-model="CountryCity"></el-input>
+        </div>
+        <div style="width: 50%;">
+          <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 class="inputmargin" 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 class="inputmargin" placeholder="Mobile Numer" v-model="ContactNumber"></el-input>
+        </div>
+      </div>
+      <div class="diaolog_add_title" style="margin-bottom: 4px;">Additional Notes</div>
+      <el-input type="textarea" :rows="3" placeholder="Ddelivery instructions, landmarks,etc. (optional)" v-model="instructions"></el-input>
+      <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 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>
+  </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: 32px 24px 12px 24px;
+  padding: 9px 16px 16px 16px;
+  border-radius: 12px;
+}
+.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);
+}
+.shipments_search {
+  margin: 16px 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: 35%;
+}
+.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;
+}
+.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;
+}
+.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;
+}
+.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: center;
+}
+.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;
+}
+</style>

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

@@ -0,0 +1,208 @@
+<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'
+
+const tableLoadingTable = ref(false)
+const tableLoadingColumn = ref(false)
+const isNotActivated = ref(false)
+
+const recommendedDate = ref([])
+
+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) {
+      tableData.value.columns = [
+        { type: 'checkbox', width: 50, fixed: 'left' },
+        ...handleColumns(res.data.TrackingTableColumns)
+      ]
+    }
+  })
+  nextTick(() => {
+    tableLoadingColumn.value = false
+  })
+}
+// 获取表格数据
+const getTableData = (val: any) => {
+  tableData.value.data = val
+}
+
+const emits = defineEmits(['selectChangeEvent'])
+// 选中
+let checkShipments = []
+let checkShipmentsSubmit = []
+let checkRecommend = []
+let checkShipmentsdata = []
+let checkShipmentsInfo = {}
+let checkShipmentsSubmitInfo = {}
+const selectChangeEvent = () => {
+  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('-') }))
+    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) => row[item] )
+        })
+      })
+    } else {
+      checkShipmentsSubmitInfo = {}
+    }
+    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('-') }))
+    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) => row[item] )
+        })
+      })
+    } else {
+      checkShipmentsSubmitInfo = {}
+    }
+    emits('selectChangeEvent',checkShipmentsInfo, checkRecommend,checkShipmentsSubmitInfo)
+  }
+}
+
+// 实现行点击样式
+useRowClickStyle(tableRef)
+
+onMounted(() => {
+  getTableColumns()
+})
+defineExpose({
+  getTableData
+})
+
+</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
src/views/DestinationDelivery/src/components/CreateNewBooking/src/images/default_add_address@2x.png


BIN
src/views/DestinationDelivery/src/components/CreateNewBooking/src/images/default_destination_not_available@2x.png


BIN
src/views/DestinationDelivery/src/components/CreateNewBooking/src/images/default_no_shipment@2x.png


BIN
src/views/DestinationDelivery/src/components/CreateNewBooking/src/images/icon_success_big@2x.png


+ 12 - 1
src/views/DestinationDelivery/src/components/TableView/src/TableView.vue

@@ -8,6 +8,8 @@ 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: {
@@ -274,6 +276,15 @@ const clickEmailBtn = (row: any) => {
   emailDialogRef.value.openDialog(row)
 }
 
+// edit
+const handelEdit = (row: any) => {
+  console.log(row)
+  router.push({
+    path: '/destination-delivery/CreateNewBooking',
+    query: { a: row._serial_no}
+  })
+}
+
 const handleCreate = () => {
   // Handle create new booking logic here
   console.log('Create new booking')
@@ -347,7 +358,7 @@ defineExpose({
         </el-button>
         <!-- edit -->
         <el-button
-          @click="tipsDialogModel = true"
+          @click="handelEdit(row)"
           class="action-btn el-button--blue"
           style="height: 24px; width: 24px"
         >

+ 0 - 2
src/views/Tracking/src/components/TrackingTable/src/components/VGMView.vue

@@ -317,7 +317,6 @@ const handleSave = () => {
       ...item
     }
   })
-
   let variableList = []
   let tableInfo = {}
   if (tableRowData.length !== 0) {
@@ -335,7 +334,6 @@ const handleSave = () => {
       })
     })
   }
-
   $api
     .saveVGMData({
       serial_no: allData.value.serial_no,