Explorar o código

Merge branch 'feat_delivery_zyh' of United_Software/k_online_ui into dev

Jack Zhou hai 4 meses
pai
achega
3ad52685c1
Modificáronse 26 ficheiros con 3251 adicións e 65 borrados
  1. 10 1
      src/components/VTag/src/VTag.vue
  2. 14 0
      src/router/index.ts
  3. 13 0
      src/stores/modules/breadCrumb.ts
  4. 131 49
      src/styles/Antdui.scss
  5. 10 7
      src/styles/elementui.scss
  6. 27 0
      src/styles/theme.scss
  7. 1 1
      src/views/AIApiLog/src/AIApiLog.vue
  8. 1 1
      src/views/ChatLog/src/ChatLog.vue
  9. 1 0
      src/views/DestinationDelivery/index.ts
  10. 289 0
      src/views/DestinationDelivery/src/DestinationDelivery.vue
  11. 486 0
      src/views/DestinationDelivery/src/components/DeliveryDate.vue
  12. 1 0
      src/views/DestinationDelivery/src/components/ModifyBooking/index.ts
  13. 276 0
      src/views/DestinationDelivery/src/components/ModifyBooking/src/ModifyBooking.vue
  14. 152 0
      src/views/DestinationDelivery/src/components/ModifyBooking/src/components/SelectShipmentsTable.vue
  15. 1 0
      src/views/DestinationDelivery/src/components/TableView/index.ts
  16. 457 0
      src/views/DestinationDelivery/src/components/TableView/src/TableView.vue
  17. 176 0
      src/views/DestinationDelivery/src/components/TableView/src/components/BookingDetailDialog.vue
  18. 166 0
      src/views/DestinationDelivery/src/components/TableView/src/components/DetailStep.vue
  19. 190 0
      src/views/DestinationDelivery/src/components/TableView/src/components/DownloadDialog.vue
  20. 376 0
      src/views/DestinationDelivery/src/components/TableView/src/components/EmailDialog.vue
  21. 127 0
      src/views/DestinationDelivery/src/components/TableView/src/components/OperationLogProcess.vue
  22. 172 0
      src/views/DestinationDelivery/src/components/TableView/src/components/ShipmentInforTable.vue
  23. 108 0
      src/views/DestinationDelivery/src/components/TableView/src/components/TipsDialog.vue
  24. BIN=BIN
      src/views/DestinationDelivery/src/components/TableView/src/img/table-empty-img.png
  25. 65 5
      src/views/Layout/src/components/Menu/MenuView.vue
  26. 1 1
      src/views/Tracking/src/components/TrackingTable/src/components/VGMView.vue

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

@@ -10,6 +10,7 @@ interface internalProps {
     | 'Arrived'
     | 'Completed'
     | 'Departed'
+    | 'Pending Approval'
   large?: boolean
 }
 
@@ -22,7 +23,8 @@ const mappingTable = new Map([
   ['Departure', 'departure'],
   ['Arrived', 'arrived'],
   ['Completed', 'completed'],
-  ['Departed', 'Departed']
+  ['Departed', 'Departed'],
+  ['Pending Approval', 'pending-approval']
 ])
 defineProps<internalProps>()
 </script>
@@ -122,6 +124,13 @@ defineProps<internalProps>()
       background-color: var(--color-tag-Departed);
     }
   }
+  &.v-tag__pending-approval {
+    background-color: var(--color-tag-unfinished-approval-bg);
+    color: var(--color-tag-unfinished-approval);
+    .dot {
+      background-color: var(--color-tag-unfinished-approval);
+    }
+  }
   & + .v-tag {
     margin-left: 8px;
   }

+ 14 - 0
src/router/index.ts

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

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

@@ -68,6 +68,19 @@ export const useBreadCrumb = defineStore('breadCrumb', {
             query: toRoute.query
           }
         ]
+      } else if (toRoute.name === 'Modify Booking') {
+        this.routeList = [
+          {
+            label: 'Destination Delivery',
+            path: '/destination-delivery',
+            query: ''
+          },
+          {
+            label: 'Modify Booking',
+            path: '/destination-delivery/modify-booking',
+            query: toRoute.query
+          }
+        ]
       } else if (toRoute.name && whiteList.includes(toRoute.name)) {
         this.routeList.push({
           label: toRoute?.meta?.breadName || toRoute.name,

+ 131 - 49
src/styles/Antdui.scss

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

+ 10 - 7
src/styles/elementui.scss

@@ -697,10 +697,12 @@ div .scoreDialog2 .el-dialog__body {
 }
 
 div .el-textarea__inner:hover {
-  box-shadow: 0 0 0 1px var(--color-theme);
+  box-shadow: 0 0 0 1px var(--color-theme) inset;
+  outline: none;
 }
 div .el-textarea__inner:focus {
-  box-shadow: 0 0 0 1px var(--color-theme);
+  box-shadow: 0 0 0 1px var(--color-theme) inset;
+  outline: none;
 }
 div .el-result__title {
   margin-top: 16px;
@@ -827,7 +829,8 @@ div .carousel .el-carousel__button {
 div .carousel .el-carousel__indicator.is-active button {
   background-color: var(--color-theme);
 }
-div .carousel .el-carousel__item--card, .el-carousel__item.is-animating {
+div .carousel .el-carousel__item--card,
+.el-carousel__item.is-animating {
   display: flex;
   align-items: center;
   justify-content: center;
@@ -836,7 +839,7 @@ div .carousel .el-carousel__item--card, .el-carousel__item.is-animating {
 div .carousel .el-carousel__arrow {
   opacity: 1;
   background-color: var(--color-carousel-card-bg);
-  box-shadow: 2px 2px 12px 0px rgba(0, 0, 0, 0.10);
+  box-shadow: 2px 2px 12px 0px rgba(0, 0, 0, 0.1);
 }
 div .carousel .el-carousel__arrow--left {
   left: 0;
@@ -856,7 +859,7 @@ div .carousel .el-carousel__arrow:hover {
   }
 }
 div .prompt-dialog {
-  min-height: 800px ;
+  min-height: 800px;
 }
 div .prompt-dialog-inner .el-dialog__header {
   padding: 0;
@@ -865,5 +868,5 @@ div .prompt-dialog-inner .el-dialog__header {
 div .prompt-dialog-inner .el-dialog__body {
   max-height: 720px;
   overflow-y: scroll;
-  line-height: 21px; 
-}
+  line-height: 21px;
+}

+ 27 - 0
src/styles/theme.scss

@@ -72,6 +72,8 @@
   --color-tag-arrived-bg: #e7faf8;
   --color-tag-completed-bg: #e8fbe4;
   --color-tag-Departed-bg: #d9edfa;
+  --color-tag-unfinished-approval-bg: #fbfbfe;
+  --color-tag-unfinished-approval: #e0a100;
 
   --color-border: #eaebed;
   --color-select-border: #eaebed;
@@ -317,6 +319,18 @@
   --color-prompt-diaolog-bg: #f8f9fd;
   --color-prompt-disabled-bg: #f4f4f4;
   --color-prompt-disabled-border: rgba(234, 235, 237, 0.3);
+
+  --color-card-icon-box-bg: #f0f1fb;
+  --color-card-number-cancelled: #243041;
+  --color-steps-unfinished-line: #b5b9bf;
+  --color-steps-current-icon-color: #e0a100;
+  --color-steps-current-icon-bg: #fff4d1;
+  --color-steps-rejected-bg: #fedcde;
+  --color-steps-approved-bg: #e8fbe4;
+  --color-booking-info-linear-bg: linear-gradient(90deg, #c4c9ee 0%, #e8e8ff 49.52%, #bfe1ff 100%);
+  --color-process-data-value-bg: #e8ebef;
+
+  --color-delivery-date-cell-hover-bg: #fff1e6;
   --color-tour-popover-bg: #fff;
   --color-tour-prev-btn-border: #eaebed;
   --color-tour-next-btn-bg: #2b2f36;
@@ -522,6 +536,19 @@
     }
   }
 
+  --color-tag-unfinished-approval-bg: #eeeff6;
+
+  --color-card-icon-box-bg: #4f535c;
+  --color-card-number-cancelled: #babcc0;
+  --color-steps-unfinished-line: #6a6d73;
+  --color-steps-current-icon-color: #e0a100;
+  --color-steps-current-icon-bg: #534b30;
+  --color-steps-rejected-bg: #4f353d;
+  --color-steps-approved-bg: #394e44;
+  --color-booking-info-linear-bg: linear-gradient(90deg, #636db7 0%, #515195 49.52%, #7b9bc9 100%);
+  --color-process-data-value-bg: #4f5760;
+
+  --color-delivery-date-cell-hover-bg: rgba(255, 117, 0, 0.2);
   --color-tour-popover-bg: #cf5f00;
   --color-tour-prev-btn-border: #e6c5aa;
   --color-tour-next-btn-bg: #f0f1f3;

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

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

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

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

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

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

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

@@ -0,0 +1,289 @@
+<script lang="ts" setup>
+import { useCalculatingHeight } from '@/hooks/calculatingHeight'
+import TableView from './components/TableView'
+import DeliveryDate from './components/DeliveryDate.vue'
+import { useRouter } from 'vue-router'
+
+const router = useRouter()
+const OperationSearch = ref()
+const filterRef: Ref<HTMLElement | null> = ref(null)
+const containerHeight = useCalculatingHeight(document.documentElement, 376, [filterRef])
+const searchData = ref({
+  inputModel: '',
+  startDate: '',
+  endDate: '',
+  userType: '',
+  questionType: '',
+  answerType: '',
+  answerSatisfaction: '',
+  comparator: 'thanOrEqual',
+  responseDuration: 0
+})
+
+const userTypeList = [
+  {
+    label: 'Customer',
+    value: 'customer'
+  },
+  {
+    label: 'Employee',
+    value: 'employee'
+  }
+]
+
+const numberCards = [
+  {
+    label: 'Total Bookings',
+    value: 0,
+    color: '#2b2f36'
+  },
+  {
+    label: 'Pending Approval',
+    value: 0,
+    color: '#edb82f',
+    icon: 'icon_time_b'
+  },
+  {
+    label: 'Approved',
+    value: 0,
+    color: '#00a870',
+    icon: 'icon_confirm_b'
+  },
+  {
+    label: 'Rejected',
+    value: 0,
+    color: '#c9353f',
+    icon: 'icon_reject_b'
+  },
+  {
+    label: 'Cancelled',
+    value: 0,
+    color: '#243041',
+    icon: 'icon_cancelled_b'
+  }
+]
+const activeCard = ref(0)
+const clickCard = (index: number) => {
+  activeCard.value = index
+}
+
+const DateChange = (date: any) => {
+  searchData.value.startDate = date[0]
+  searchData.value.endDate = date[1]
+}
+
+const handleConfigurations = () => {
+  // Handle configurations logic here
+  console.log('Configurations clicked')
+}
+const handleCreate = () => {
+  // Handle create new booking logic here
+}
+
+const handleTest = () => {
+  // Handle test logic here
+  router.push({ name: 'Modify Booking' })
+}
+
+const DateStart = ref([])
+const tableRef = ref()
+</script>
+<template>
+  <div class="destination-delivery">
+    <div class="header">
+      <span>Destination Delivery</span>
+      <div class="operator">
+        <el-button style="height: 40px" type="default" @click="handleConfigurations">
+          <span style="margin-right: 4px" class="font_family icon-icon_configurations_b"></span>
+          <span style="font-weight: 400">Configurations</span></el-button
+        >
+        <el-button
+          style="height: 38px"
+          class="el-button--main el-button--pain-theme"
+          @click="handleCreate"
+        >
+          <span style="margin-right: 4px" class="font_family icon-icon_add_b"></span>
+          <span style="font-weight: 400">Create New Booking</span>
+        </el-button>
+
+        <el-button @click="handleTest">测试</el-button>
+      </div>
+    </div>
+    <div class="display">
+      <div class="heaer_top">
+        <div class="date-tips_filter">
+          <CalendarDate @DateChange="DateChange"></CalendarDate>
+        </div>
+        <div class="tips_filter">
+          <el-select v-model="searchData.userType" placeholder="User Type">
+            <el-option
+              v-for="item in userTypeList"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </div>
+        <div class="date-tips_filter">
+          <DeliveryDate :Date="DateStart" />
+        </div>
+        <div class="input-tips_filter">
+          <el-input
+            placeholder="Search Question ID、User"
+            v-model="OperationSearch"
+            class="log_input"
+          >
+            <template #prefix>
+              <span class="iconfont_icon">
+                <svg class="iconfont icon_dark" aria-hidden="true">
+                  <use xlink:href="#icon-icon_search_b"></use>
+                </svg>
+              </span>
+            </template>
+          </el-input>
+        </div>
+        <el-button class="el-button--dark">Search</el-button>
+      </div>
+      <div class="number-cards">
+        <div
+          class="card"
+          :class="{ 'is-active': index === activeCard }"
+          @click="clickCard(index)"
+          v-for="(item, index) in numberCards"
+          :key="index"
+        >
+          <div class="card-label">{{ item.label }}</div>
+          <div
+            class="card-value"
+            :style="{
+              color: item.label === 'Cancelled' ? `var(--color-card-number-cancelled)` : item.color
+            }"
+          >
+            {{ item.value }}
+          </div>
+          <div class="icon-box" v-if="item.icon">
+            <span class="font_family" :class="'icon-' + item.icon"></span>
+          </div>
+        </div>
+      </div>
+    </div>
+    <TableView :height="containerHeight" ref="tableRef"></TableView>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.header {
+  display: flex;
+  height: 68px;
+  border-bottom: 1px solid var(--color-border);
+  font-size: var(--font-size-6);
+  font-weight: 700;
+  padding: 0 24px;
+  align-items: center;
+  justify-content: space-between;
+}
+.heaer_top {
+  margin-top: 6.57px;
+  margin-bottom: 8px;
+  padding-right: 8px;
+  display: flex;
+}
+.number-cards {
+  display: flex;
+  justify-content: space-between;
+  margin: 8px 0;
+  gap: 8px;
+  .card {
+    position: relative;
+    flex: 1;
+    height: 80px;
+    padding: 12px 16px;
+    background-color: var(--color-email-bg);
+    border-radius: 12px;
+    & > .icon-box {
+      position: absolute;
+      right: 16px;
+      top: 12px;
+      width: 24px;
+      height: 24px;
+      border-radius: 6px;
+      background-color: var(--color-card-icon-box-bg);
+      text-align: center;
+      line-height: 24px;
+    }
+    &.is-active {
+      background-color: var(--color-mune-active-bg);
+      .card-label,
+      .card-value {
+        color: var(--color-theme) !important;
+      }
+      .icon-box {
+        background-color: rgba(#ff7500, 0.1);
+        span {
+          color: var(--color-theme);
+        }
+      }
+    }
+
+    .card-value {
+      margin-top: 4px;
+      font-size: 32px;
+      font-weight: 700;
+    }
+  }
+}
+
+.display {
+  border: 1px solid var(--color-border);
+  border-width: 0 0 1px 0;
+  padding: 0 24px;
+}
+:deep(.el-select__placeholder.is-transparent span) {
+  color: var(--tag-info-text-color) !important;
+}
+:deep(.ETD_title) {
+  margin-bottom: 0;
+}
+:deep(.ant-picker-range) {
+  width: 250px !important;
+  height: 32px;
+  background-color: var(--color-mode) !important;
+}
+.tips_filter {
+  flex: 1;
+  height: 30px;
+  max-width: 170px;
+  margin-right: 8px;
+}
+.input-tips_filter {
+  flex: 1;
+  max-width: 320px;
+  height: 32px;
+  margin-right: 8px;
+  :deep(.el-input__wrapper) {
+    height: 32px;
+  }
+}
+.date-tips_filter {
+  flex: 1;
+  max-width: 250px;
+  height: 32px;
+  margin-right: 8px;
+}
+.comparator-tips_filter {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  max-width: 260px;
+  height: 32px;
+  margin-right: 8px;
+}
+.destination-delivery {
+  position: relative;
+  background-color: var(--color-mode);
+}
+:deep(.log_input .el-input__wrapper) {
+  box-shadow: 0 0 0 1px var(--color-select-border);
+  border-radius: 6px;
+}
+</style>

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

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

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

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

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

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

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

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

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

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

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

@@ -0,0 +1,457 @@
+<script setup lang="ts">
+import { ref, nextTick, onMounted } from 'vue'
+import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
+// import { autoWidth } from '@/utils/table'
+import { useRowClickStyle } from '@/hooks/rowClickStyle'
+import dayjs from 'dayjs'
+import { formatTimezone, formatNumber } from '@/utils/tools'
+import BookingDetailDialog from './components/BookingDetailDialog.vue'
+import EmailDialog from './components/EmailDialog.vue'
+import TipsDialog from './components/TipsDialog.vue'
+
+const props = defineProps({
+  height: {
+    type: Number,
+    default: 440
+  }
+})
+
+const tableOriginColumnsField = ref()
+const handleColumns = (columns: any, status?: string) => {
+  const newColumns = columns.map((item: any) => {
+    let curColumn: any = {
+      title: item.title,
+      field: item.field,
+      sortable: true,
+      minWidth: 120
+    }
+    // 设置插槽
+    if (item.type === 'status' && status !== 'all') {
+      curColumn = {
+        ...curColumn,
+        slots: { default: 'status' }
+      }
+    } else if (item.type === 'link' && status !== 'all') {
+      curColumn = {
+        ...curColumn,
+        slots: { default: 'link' }
+      }
+    } else if (item.type === 'mode' && status !== 'all') {
+      curColumn = {
+        ...curColumn,
+        slots: { default: 'mode' },
+        formatter: ({ cellValue }: any) => {
+          return cellValue
+        }
+      }
+    }
+    // 格式化
+    if (item.formatter === 'date' || item.formatter === 'dateTime') {
+      curColumn = {
+        ...curColumn,
+        formatter: ({ cellValue }: any) => formatTimezone(cellValue)
+      }
+    }
+    return curColumn
+  })
+  return newColumns
+}
+
+// 获取表格列
+const getTableColumns = async () => {
+  tableLoadingColumn.value = true
+  await $api.getOperationTableColumns().then((res: any) => {
+    if (res.code === 200) {
+      tableData.value.columns = [
+        { type: 'checkbox', width: 50, fixed: 'left' },
+        ...handleColumns(res.data.OperationTableColumns)
+      ]
+      console.log('tableData.value.columns', tableData.value.columns)
+      const index = tableData.value.columns.findIndex((item: any) => item.title === 'Action')
+      if (index === -1) {
+        tableData.value.columns.push({
+          title: 'Action',
+          fixed: 'right',
+          width: 130,
+          slots: { default: 'action' }
+        })
+      }
+      tableOriginColumnsField.value = res.data.OperationTableColumns
+    }
+  })
+  nextTick(() => {
+    // tableRef.value && autoWidth(tableData.value, tableRef.value)
+    tableLoadingColumn.value = false
+    selectedTableData.value = []
+  })
+}
+
+const pageInfo = ref({ pageNo: 1, pageSize: 20, total: 0 })
+const tempSearch = ref()
+// 获得表格数据后赋值
+const assignTableData = (data: any) => {
+  tableData.value.data = data.searchData || []
+  pageInfo.value.total = Number(data.rc) || 0
+  tempSearch.value = data.tmp_search
+
+  if (tableData.value.columns.length > 0) {
+    const index = tableData.value.columns.findIndex((item: any) => item.title === 'Action')
+    if (index === -1) {
+      tableData.value.columns.push({
+        title: 'Action',
+        fixed: 'right',
+        width: 130,
+        slots: { default: 'action' }
+      })
+    }
+  }
+}
+
+let searchdata: any = {}
+// 获取表格数据
+const getTableData = async (isPageChange?: boolean) => {
+  const rc = isPageChange ? pageInfo.value.total : -1
+  tableLoadingTableData.value = true
+  await $api
+    .SearchOperationLog({
+      cp: pageInfo.value.pageNo,
+      ps: pageInfo.value.pageSize,
+      rc,
+      ...searchdata
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        assignTableData(res.data)
+      }
+    })
+    .finally(() => {
+      selectedTableData.value = []
+      nextTick(() => {
+        // tableRef.value && autoWidth(tableData.value, tableRef.value)
+        tableLoadingTableData.value = false
+      })
+    })
+}
+const SearchOperationLog = (val: any) => {
+  searchdata = val
+  tableLoadingTableData.value = true
+  $api
+    .SearchOperationLog({
+      cp: pageInfo.value.pageNo,
+      ps: pageInfo.value.pageSize,
+      rc: -1,
+      ...val
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        assignTableData(res.data)
+      }
+    })
+    .finally(() => {
+      selectedTableData.value = []
+      nextTick(() => {
+        // tableRef.value && autoWidth(tableData.value, tableRef.value)
+        tableLoadingTableData.value = false
+      })
+    })
+}
+onMounted(() => {
+  Promise.all([getTableColumns(), getTableData(false)]).finally(() => {
+    // nextTick(() => {
+    //   tableRef.value && autoWidth(tableData.value, tableRef.value)
+    // })
+  })
+})
+
+const tableRef = ref<VxeGridInstance>()
+const tableData = ref<VxeGridProps<any>>({
+  border: true,
+  round: true,
+  columns: [],
+  data: [],
+  scrollY: { enabled: true, oSize: 20, gt: 30 },
+  emptyText: ' ',
+  showHeaderOverflow: true,
+  showOverflow: true,
+  headerRowStyle: {
+    backgroundColor: 'var(--color-table-header-bg)'
+  },
+  sortConfig: {
+    sortMethod: (params) => {
+      const { data, sortList } = params
+
+      // 如果没有排序条件,直接返回原数据
+      if (sortList.length === 0) return data
+
+      // 对数据进行多重排序
+      const sortedData = [...data].sort((a, b) => {
+        for (const { field, order } of sortList) {
+          const curColumn = tableOriginColumnsField.value.find((item: any) => item.field === field)
+          if (!curColumn) continue
+
+          const typeName = curColumn.type
+          const aValue = a[field]
+          const bValue = b[field]
+
+          const compareResult = (aValue: any, bValue: any) => {
+            // 如果 aValue 或 bValue 是 null 或 undefined,优先处理这些值
+            if (aValue == null && bValue == null) {
+              return 0 // 如果两个值都为 null 或 undefined,视为相等
+            } else if (aValue == null) {
+              return -1 // 如果 aValue 是 null,bValue 不是,则将 aValue 视为较小值
+            } else if (bValue == null) {
+              return 1 // 如果 bValue 是 null,aValue 不是,则将 bValue 视为较小值
+            }
+
+            if (typeName === 'datetime' || typeName === 'date' || typeName === 'time') {
+              return dayjs(aValue).unix() - dayjs(bValue).unix()
+            } else if (isNaN(Number(aValue)) || isNaN(Number(bValue))) {
+              return aValue.localeCompare(bValue)
+            } else {
+              return Number(aValue) - Number(bValue)
+            }
+          }
+
+          const result = compareResult(aValue, bValue)
+          if (result !== 0) {
+            return order === 'asc' ? result : -result
+          }
+        }
+
+        return 0 // 如果所有字段都相等
+      })
+
+      return sortedData
+    }
+  },
+  columnConfig: { resizable: true, useKey: true },
+  rowConfig: { isHover: true },
+  exportConfig: {
+    types: ['csv', 'html', 'txt', 'xlsx'],
+    modes: ['current', 'selected', 'all']
+  }
+})
+
+// 实现行点击样式
+useRowClickStyle(tableRef)
+
+const CustomizeColumnsRef = ref()
+// 打开定制表格弹窗
+const handleCustomizeColumns = () => {
+  const params = {
+    getData: {
+      action: 'ocean_booking',
+      operate: 'setting_display'
+    },
+    saveData: {
+      action: 'ajax',
+      operate: 'save_setting_display',
+      model_name: 'Booking_Search'
+    }
+  }
+  CustomizeColumnsRef.value.openDialog(params, -220)
+}
+// 定制表格
+const customizeColumns = async () => {
+  await getTableColumns()
+  // nextTick(() => {
+  //   tableRef.value && autoWidth(tableData.value, tableRef.value)
+  // })
+}
+
+const tableLoadingColumn = ref(false)
+const tableLoadingTableData = ref(false)
+
+const selectedTableData = ref([])
+
+const bookingDetailDiaRef = ref()
+const clickViewBtn = (row: any) => {
+  bookingDetailDiaRef.value.openDialog(row)
+}
+
+const emailDialogRef = ref()
+const clickEmailBtn = (row: any) => {
+  emailDialogRef.value.openDialog(row)
+}
+
+const handleCreate = () => {
+  // Handle create new booking logic here
+  console.log('Create new booking')
+}
+
+const tipsDialogModel = ref(false)
+
+defineExpose({
+  SearchOperationLog
+})
+</script>
+
+<template>
+  <div
+    style="padding: 0px 20px"
+    class="table-box"
+    element-loading-text="Loading..."
+    element-loading-custom-class="element-loading"
+    element-loading-background="rgb(43, 47, 54, 0.7)"
+  >
+    <div class="table-tools">
+      <div class="left-total-records">Booking List</div>
+      <div class="right-tools-btn">
+        <el-button type="default" @click="handleCustomizeColumns">
+          <span style="margin-right: 8px" class="font_family icon-icon_column_b"></span>
+          Customize Columns
+        </el-button>
+      </div>
+    </div>
+    <vxe-grid
+      ref="tableRef"
+      v-vloading="tableLoadingTableData || tableLoadingColumn"
+      :height="props.height"
+      :style="{ border: 'none' }"
+      v-bind="tableData"
+    >
+      <!-- 空数据时的插槽 -->
+      <template #empty v-if="!tableLoadingTableData && tableData.data.length === 0">
+        <div class="empty-box">
+          <img class="empty-img" src="./img/table-empty-img.png" alt="" />
+          <p>You haven't created any destination delivery bookings yet.</p>
+          <p>Book truck or rail delivery for your shipments to save time and</p>
+          <p>ensure smooth last-mile delivery.</p>
+          <el-button
+            style="height: 40px"
+            class="el-button--main el-button--pain-theme"
+            @click="handleCreate"
+          >
+            <span style="margin-right: 4px" class="font_family icon-icon_add_b"></span>
+            <span style="font-weight: 400">Create New Booking</span>
+          </el-button>
+        </div>
+      </template>
+      <!-- action操作的插槽 -->
+      <template #action="{ row }">
+        <!-- view  -->
+        <el-button
+          @click="clickViewBtn(row)"
+          class="action-btn el-button--blue"
+          style="height: 24px; width: 24px"
+        >
+          <span style="color: 'red'" class="font_family icon-icon_view_b"> </span>
+        </el-button>
+        <!-- email -->
+        <el-button
+          @click="clickEmailBtn(row)"
+          class="action-btn el-button--blue"
+          style="height: 24px; width: 24px"
+        >
+          <span style="color: 'red'" class="font_family icon-icon_email_b"> </span>
+        </el-button>
+        <!-- edit -->
+        <el-button
+          @click="tipsDialogModel = true"
+          class="action-btn el-button--blue"
+          style="height: 24px; width: 24px"
+        >
+          <span style="color: 'red'" class="font_family icon-icon_edit_b"> </span>
+        </el-button>
+        <!-- confirm -->
+        <el-button class="action-btn el-button--blue" style="height: 24px; width: 24px">
+          <span style="color: 'red'" class="font_family icon-icon_confirm_b"> </span>
+        </el-button>
+        <!-- reject -->
+        <el-button class="action-btn el-button--blue" style="height: 24px; width: 24px">
+          <span style="color: 'red'" class="font_family icon-icon_reject_b"> </span>
+        </el-button>
+      </template>
+    </vxe-grid>
+
+    <div class="bottom-pagination">
+      <div class="left-total-records">Total {{ formatNumber(pageInfo.total) }}</div>
+      <div class="right-pagination">
+        <el-pagination
+          v-model:current-page="pageInfo.pageNo"
+          v-model:page-size="pageInfo.pageSize"
+          :page-sizes="[20, 50, 100, 150]"
+          :pager-count="3"
+          background
+          layout="sizes, prev, pager, next"
+          :total="pageInfo.total"
+          @size-change="getTableData(true)"
+          @current-change="getTableData(true)"
+        />
+      </div>
+    </div>
+
+    <CustomizeColumns @customize="customizeColumns" ref="CustomizeColumnsRef" />
+    <BookingDetailDialog ref="bookingDetailDiaRef" />
+    <EmailDialog ref="emailDialogRef" />
+    <TipsDialog ref="tipsDialogRef" v-model="tipsDialogModel" type="reject" booking-no="123" />
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.table-tools {
+  position: relative;
+  display: flex;
+  justify-content: space-between;
+  height: 48px;
+  padding: 8px 0;
+
+  .left-total-records {
+    font-size: 16px;
+    font-weight: 700;
+    line-height: 32px;
+  }
+}
+
+.empty-box {
+  .empty-img {
+    width: 100px;
+    height: 100px;
+    margin-bottom: 8px;
+  }
+  p {
+    color: var(--color-neutral-2);
+    line-height: 21px;
+  }
+  .el-button {
+    margin-top: 8px;
+  }
+}
+
+.bottom-pagination {
+  display: flex;
+  justify-content: space-between;
+  height: 40px;
+  margin-top: -1px;
+  padding: 4px 8px;
+  border: 1px solid var(--color-border);
+  border-radius: 0 0 12px 12px;
+
+  .left-total-records {
+    line-height: 32px;
+  }
+
+  .right-pagination {
+    display: flex;
+    align-items: center;
+  }
+}
+.table-box {
+  position: relative;
+  overflow: hidden;
+
+  .all-table {
+    position: absolute;
+    top: -100000px;
+    width: 20px;
+  }
+}
+
+.action-btn {
+  height: 24px;
+  width: 24px;
+  &:hover {
+    background-color: var(--color-btn-action-bg-hover);
+  }
+}
+</style>

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

@@ -0,0 +1,176 @@
+<script setup lang="ts">
+import DetailStep from './DetailStep.vue'
+import ShipmentInforTable from './ShipmentInforTable.vue'
+import OperationLogProcess from './OperationLogProcess.vue'
+
+const visible = ref(false)
+
+const openDialog = (row: any) => {
+  visible.value = true
+  console.log(row)
+}
+
+const stepList: any = ref([
+  {
+    id: 1,
+    label: 'Created',
+    date: 'Jun-01-2024',
+    icon: 'icon_submit_b',
+    status: 'completed'
+  },
+  {
+    label: 'Pending',
+    date: 'Current',
+    icon: 'icon_time_b',
+    status: 'current'
+  },
+  {
+    label: 'Rejected',
+    date: 'Jun-03-2024',
+    icon: 'icon_reject_b',
+    status: 'rejected'
+  },
+  {
+    label: 'Approved',
+    date: 'Jun-03-2024',
+    icon: 'icon_confirm_b',
+    status: 'current'
+  }
+])
+
+defineExpose({
+  openDialog
+})
+</script>
+
+<template>
+  <el-dialog
+    title="Booking Detail"
+    class="booking-detail-dialog"
+    v-model="visible"
+    :close-on-click-modal="false"
+    width="1000px"
+    top="10vh"
+    v-if="visible"
+  >
+    <DetailStep :stepList="stepList" />
+    <div class="booking-info">
+      <div class="booking-no">
+        <span class="no">Booking No.B83131200164</span>
+        <v-tag class="tag" type="Pending Approval">Pending Approval</v-tag>
+      </div>
+      <div class="created-time">Created Time:Jun-01-2024</div>
+    </div>
+    <ShipmentInforTable></ShipmentInforTable>
+    <div class="delivery-information">
+      <div class="label">Delivery Information</div>
+      <div class="delivery-info">
+        <div class="delivery-item inline-flex" style="width: 200px">
+          <span class="item-label">Mode Type</span>
+          <span class="item-value">Shanghai, China</span>
+        </div>
+        <div class="delivery-item inline-flex">
+          <span class="item-label">Delivery Date</span>
+          <span class="item-value">
+            <span class="font_family icon-icon_date_b" style="margin-right: 4px"></span>
+            <span style="margin-top: 1px">Jun-15-2024</span>
+          </span>
+        </div>
+        <div class="delivery-item">
+          <span class="item-label">Delivery Address</span>
+          <span class="item-value">
+            <span class="font_family icon-icon_location_b" style="margin-right: 2px"></span>
+            <span style="margin-top: 1px"
+              >Main Distribution Center,160#BEIJING ROAD, JINGAN District, Shenzhen, China</span
+            >
+          </span>
+        </div>
+        <div class="delivery-item">
+          <span class="item-label">Special Requirements</span>
+          <span class="item-value">
+            <span class="font_family icon-icon_paragraph_b" style="margin-right: 2px"></span>
+            <span style="margin-top: 1px">Tail Lift Required</span>
+          </span>
+        </div>
+      </div>
+    </div>
+    <el-divider style="margin-top: 8px; margin-bottom: 20px" />
+    <OperationLogProcess />
+  </el-dialog>
+</template>
+
+<style lang="scss" scoped>
+.booking-info {
+  margin: 34px 16px 0;
+  padding: 8px 16px;
+  border-radius: 12px;
+  background-image: var(--color-booking-info-linear-bg);
+
+  .booking-no {
+    display: flex;
+    align-items: center;
+    .no {
+      margin-top: 2px;
+      font-size: 18px;
+      font-weight: 700;
+      line-height: 21px;
+    }
+    .tag {
+      margin-left: 8px;
+    }
+  }
+  .created-time {
+    margin-top: 8px;
+    font-size: 12px;
+    color: var(--color-neutral-2);
+  }
+}
+
+.delivery-information {
+  margin: 20px 16px 0;
+  .label {
+    margin-bottom: 14px;
+    font-size: 18px;
+    font-weight: 700;
+  }
+
+  .delivery-info {
+    padding: 12px 8px 20px;
+    border-radius: 12px;
+    background-color: var(--color-share-link-bg);
+
+    .delivery-item {
+      display: flex;
+      flex-direction: column;
+      margin-bottom: 16px;
+
+      .item-label {
+        margin-bottom: 8px;
+        font-size: 12px;
+        color: var(--color-neutral-2);
+      }
+      .item-value {
+        display: flex;
+        align-items: center;
+        font-weight: 700;
+      }
+      &.inline-flex {
+        display: inline-flex;
+      }
+      &:last-child {
+        margin-bottom: 0;
+      }
+    }
+  }
+}
+</style>
+<style lang="scss">
+div.booking-detail-dialog {
+  height: 80%;
+  .el-dialog__body {
+    padding: 0;
+    height: calc(100% - 48px);
+    overflow: auto;
+  }
+}
+</style>

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

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

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

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

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

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

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

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

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

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

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

@@ -0,0 +1,108 @@
+<script setup lang="ts">
+const props = defineProps<{
+  type: 'approve' | 'reject' | 'cancel'
+  bookingNo: string
+}>()
+const model = defineModel<boolean>({ type: Boolean, default: false })
+
+const tipList = ref({
+  reject: 'Are you sure you want to Reject Booking ',
+  approve: 'Are you sure you want to Approve Booking ',
+  cancel: 'Are you sure you want to Cancel Booking '
+})
+
+const typeList = ref({
+  approve: 'Approve',
+  reject: 'Reject',
+  cancel: 'Cancel'
+})
+
+const isShowRequired = computed(() => {
+  return props.type === 'reject'
+})
+const openDialog = () => {
+  model.value = true
+}
+defineExpose({
+  openDialog
+})
+</script>
+
+<template>
+  <el-dialog
+    v-model="model"
+    :show-close="false"
+    title=""
+    width="800px"
+    top="20vh"
+    class="tips-dialog"
+  >
+    <div class="tips-dialog-title">
+      <span style="" class="font_family icon-icon_tipsfilled_b"></span>
+      <span style="color: #c9353f" v-if="isShowRequired">*</span>
+      <span> {{ tipList[props.type] + props.bookingNo + '?' }}</span>
+    </div>
+    <el-input
+      :autosize="false"
+      type="textarea"
+      class="input-textarea"
+      style="height: 120px"
+      placeholder="Input remarks"
+    ></el-input>
+    <template #footer>
+      <el-button class="cancel-btn" type="default" @click="model = false">Cancel</el-button>
+      <el-button
+        class="submit-btn"
+        :class="{
+          'el-button--success': props.type === 'approve',
+          'el-button--danger': props.type === 'reject',
+          'el-button--warning': props.type === 'cancel'
+        }"
+        >{{ typeList[props.type] }} Booking</el-button
+      >
+    </template>
+  </el-dialog>
+</template>
+
+<style lang="scss" scoped>
+.tips-dialog-title {
+  display: flex;
+  align-items: center;
+  margin-top: 8px;
+  margin-bottom: 8px;
+  font-size: 18px;
+  font-weight: 700;
+  line-height: 24px;
+  .font_family {
+    margin-right: 4px;
+    color: #f19d38;
+    font-size: 28px;
+  }
+}
+.input-textarea {
+  :deep(.el-textarea__inner) {
+    height: 120px;
+    resize: none;
+    line-height: 22px;
+  }
+}
+
+.cancel-btn {
+  width: 100px;
+  height: 40px;
+}
+.submit-btn {
+  height: 40px;
+}
+</style>
+<style lang="scss">
+.tips-dialog {
+  .el-dialog__header {
+    display: none;
+  }
+  .el-dialog__footer {
+    padding-top: 2px;
+    border-top: none;
+  }
+}
+</style>

BIN=BIN
src/views/DestinationDelivery/src/components/TableView/src/img/table-empty-img.png


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

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

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

@@ -61,7 +61,7 @@ const generalInfo = ref({
 const tableRef = ref<VxeGridInstance | null>(null)
 const tableData = ref<VxeGridProps<any>>({
   minHeight: 70,
-  height: '330',
+  height: '390',
   border: true,
   round: true,
   columns: [],