Explorar o código

Merge branch 'dev' into dev_g

AmandaG hai 1 ano
pai
achega
1a73e36142
Modificáronse 29 ficheiros con 411 adicións e 85 borrados
  1. 4 1
      src/components/ContainerStatus/src/ContainerStatus.vue
  2. 17 4
      src/hooks/useOverflow.ts
  3. 2 2
      src/router/index.ts
  4. 1 8
      src/stores/modules/user.ts
  5. 3 0
      src/styles/elementui.scss
  6. 12 4
      src/styles/icons/iconfont.css
  7. 0 0
      src/styles/icons/iconfont.js
  8. 4 0
      src/styles/icons/iconfont.svg
  9. BIN=BIN
      src/styles/icons/iconfont.ttf
  10. BIN=BIN
      src/styles/icons/iconfont.woff
  11. BIN=BIN
      src/styles/icons/iconfont.woff2
  12. 1 1
      src/utils/axios.ts
  13. 2 2
      src/utils/table.ts
  14. 16 4
      src/views/Booking/src/components/BookingDetail/src/BookingDetail.vue
  15. 0 1
      src/views/Booking/src/components/BookingDetail/src/components/BasicInformation.vue
  16. 1 0
      src/views/Booking/src/components/BookingDetail/src/components/EmailView.vue
  17. 3 1
      src/views/Booking/src/components/BookingTable/src/components/DownloadDialog.vue
  18. 2 1
      src/views/Login/src/components/ChangePasswordCard.vue
  19. BIN=BIN
      src/views/Login/src/image/bg-login-card.png
  20. 42 30
      src/views/Login/src/loginView.vue
  21. 24 2
      src/views/Tracking/src/components/PublicTracking/src/PublicTrackingSearch.vue
  22. 0 1
      src/views/Tracking/src/components/PublicTracking/src/components/BasicInformation.vue
  23. 16 4
      src/views/Tracking/src/components/PublicTracking/src/components/PublicTrackingDetail.vue
  24. 207 0
      src/views/Tracking/src/components/PublicTracking/src/components/SlideVerify.vue
  25. 1 0
      src/views/Tracking/src/components/TrackingDetail/src/TrackingDetail.vue
  26. 0 1
      src/views/Tracking/src/components/TrackingDetail/src/components/BasicInformation.vue
  27. 1 0
      src/views/Tracking/src/components/TrackingDetail/src/components/EmailDrawer.vue
  28. 49 17
      src/views/Tracking/src/components/TrackingDetail/src/components/RoutesView.vue
  29. 3 1
      src/views/Tracking/src/components/TrackingTable/src/components/DownloadDialog.vue

+ 4 - 1
src/components/ContainerStatus/src/ContainerStatus.vue

@@ -5,7 +5,7 @@ const props = defineProps({
   data: Object
 })
 
-const activeNames = ref<string[]>([])
+const activeNames = ref<number[]>([])
 
 const containerStatusData: any = ref([])
 watch(
@@ -13,6 +13,9 @@ watch(
   (newVal) => {
     if (newVal) {
       containerStatusData.value = newVal
+      if (containerStatusData.value.length === 1) {
+        activeNames.value = [0]
+      }
     } else {
       containerStatusData.value = []
     }

+ 17 - 4
src/hooks/useOverflow.ts

@@ -1,7 +1,9 @@
 import { ref, watch, onMounted, nextTick } from 'vue'
 
 export const useOverflow = (elementRef, dataRef) => {
-  const isOverflow = ref(false)
+  // 根据 elementRef 类型来初始化 isOverflow
+  const isOverflow = Array.isArray(elementRef.value) ? ref([]) : ref(false)
+
   const checkOverflow = (element) => {
     if (element) {
       return element.scrollWidth > element.clientWidth
@@ -9,19 +11,30 @@ export const useOverflow = (elementRef, dataRef) => {
     return false
   }
 
+  // 用于处理单个或多个元素的溢出检查
+  const updateOverflowStatus = () => {
+    if (Array.isArray(elementRef.value)) {
+      // 如果是数组,检查每个元素
+      isOverflow.value = elementRef.value.map((el) => checkOverflow(el))
+    } else {
+      // 如果是单个元素,直接返回布尔值
+      isOverflow.value = checkOverflow(elementRef.value)
+    }
+  }
+
   // 初次 mounted 时检查是否溢出
   onMounted(() => {
     nextTick(() => {
-      isOverflow.value = checkOverflow(elementRef.value)
+      updateOverflowStatus()
     })
   })
 
   // 监听数据变化,更新溢出状态
   watch(
-    () => dataRef.value,
+    () => dataRef,
     () => {
       nextTick(() => {
-        isOverflow.value = checkOverflow(elementRef.value)
+        updateOverflowStatus()
       })
     },
     {

+ 2 - 2
src/router/index.ts

@@ -97,14 +97,14 @@ router.beforeEach(async (to, from, next) => {
   // 如果手动跳转登录页,清除登录信息
   if (to.path === '/login') {
     const userStore = useUserStore()
-    userStore.clearUsername()
+    userStore.logout()
   }
   // 未登录白名单
   const whiteList = ['/login', '/public-tracking', '/public-tracking/detail', '/reset-password']
   // 判断是否登录
   if (!whiteList.includes(to.path) && !localStorage.getItem('username')) {
     const userStore = useUserStore()
-    userStore.clearUsername()
+    userStore.logout()
     if (whiteList.includes(from.path)) {
       ElMessage.warning({
         message: 'Please log in to use this feature.',

+ 1 - 8
src/stores/modules/user.ts

@@ -14,14 +14,7 @@ export const useUserStore = defineStore('user', {
       this.username = username
     },
     logout() {
-      $api.logout().then((res: any) => {
-        if (res.code === 200) {
-          localStorage.removeItem('username')
-          this.username = ''
-        }
-      })
-    },
-    clearUsername() {
+      $api.logout().then(() => {})
       localStorage.removeItem('username')
       this.username = ''
     }

+ 3 - 0
src/styles/elementui.scss

@@ -222,6 +222,9 @@ button.el-dialog__headerbtn:focus .el-dialog__close,
 button.el-dialog__headerbtn:hover .el-dialog__close {
   color: var(--color-theme);
 }
+div.el-overlay {
+  background-color: rgba(43, 47, 54, 0.7);
+}
 
 // radio
 label.el-radio {

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

@@ -1,9 +1,9 @@
 @font-face {
   font-family: "font_family"; /* Project id 4672385 */
-  src: url('iconfont.woff2?t=1726717215398') format('woff2'),
-       url('iconfont.woff?t=1726717215398') format('woff'),
-       url('iconfont.ttf?t=1726717215398') format('truetype'),
-       url('iconfont.svg?t=1726717215398#font_family') format('svg');
+  src: url('iconfont.woff2?t=1729653017918') format('woff2'),
+       url('iconfont.woff?t=1729653017918') format('woff'),
+       url('iconfont.ttf?t=1729653017918') format('truetype'),
+       url('iconfont.svg?t=1729653017918#font_family') format('svg');
 }
 
 .font_family {
@@ -14,6 +14,14 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.icon-icon_drag__line_b:before {
+  content: "\e6c3";
+}
+
+.icon-icon_unverified_b:before {
+  content: "\e6c2";
+}
+
 .icon-icon_location_fill_b:before {
   content: "\e6c0";
 }

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
src/styles/icons/iconfont.js


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

@@ -14,6 +14,10 @@
     />
       <missing-glyph />
       
+      <glyph glyph-name="icon_drag__line_b" unicode="&#59075;" d="M447.104 370.432a19.2 19.2 0 0 1 0 27.136L236.672 608a19.2 19.2 0 0 0 0 27.136l40.704 40.768a19.2 19.2 0 0 0 27.136 0l257.92-257.92a48 48 0 0 0 0-67.904l-257.92-257.92a19.2 19.2 0 0 0-27.136 0l-40.704 40.704a19.2 19.2 0 0 0 0 27.136l210.432 210.432z m256 0a19.2 19.2 0 0 1 0 27.136L492.672 608a19.2 19.2 0 0 0 0 27.136l40.704 40.768a19.2 19.2 0 0 0 27.136 0l257.92-257.92a48 48 0 0 0 0-67.904l-257.92-257.92a19.2 19.2 0 0 0-27.136 0l-40.704 40.704a19.2 19.2 0 0 0 0 27.136l210.432 210.432z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_unverified_b" unicode="&#59074;" d="M512-128A512 512 0 1 1 512 896a512 512 0 0 1 0-1024z m-38.656 313.728a48 48 0 0 0-67.84 0L188.032 403.072 256 471.04l183.424-183.424L768 616.192l67.84-67.84-362.496-362.56z"  horiz-adv-x="1024" />
+      
       <glyph glyph-name="icon_location_fill_b" unicode="&#59072;" d="M558.08-32.896c15.424 0 317.12 296.704 317.12 516.864a317.056 317.056 0 1 1-634.112 0c0-225.28 301.632-516.864 317.056-516.864z m0 400.96a128 128 0 1 0 0 256 128 128 0 0 0 0-256z"  horiz-adv-x="1088" />
       
       <glyph glyph-name="icon_unmark_b" unicode="&#59073;" d="M508.416 252.032L206.208 62.08V691.2a51.2 51.2 0 0 0 51.2 51.2h501.952a51.2 51.2 0 0 0 51.2-51.2v-629.056l-302.144 189.952z m0-90.688l329.92-207.36a32 32 0 0 1 49.024 27.072V691.136a128 128 0 0 1-128 128H257.408a128 128 0 0 1-128-128v-710.08a32 32 0 0 1 49.088-27.136l329.92 207.36z"  horiz-adv-x="1024" />

BIN=BIN
src/styles/icons/iconfont.ttf


BIN=BIN
src/styles/icons/iconfont.woff


BIN=BIN
src/styles/icons/iconfont.woff2


+ 1 - 1
src/utils/axios.ts

@@ -58,7 +58,7 @@ class HttpAxios {
     if (response.status === 200) {
       if (response.data.code === 401 || response.data.code === 403) {
         const userStore = useUserStore()
-        userStore.clearUsername()
+        userStore.logout()
         router.push('/login')
         ElMessage.warning({
           message: 'Please log in to use this feature.',

+ 2 - 2
src/utils/table.ts

@@ -32,10 +32,10 @@ export const autoWidth = (tableData: VxeGridProps, grid: VxeGridInstance) => {
       })
       // column.title.length > curStr.length && (curStr = column.title)
       // 表头的宽度如果小于表格内容的宽度
-      if (width < curStr.length * 10 + 20) {
+      if (width < curStr.length * 11 + 20) {
         // width = curStr.length * 10 + 20
         if (curStr.length > 20) {
-          width = curStr.length * 8 + 40
+          width = curStr.length * 9 + 40
         } else {
           width = curStr.length * 11 + 20
         }

+ 16 - 4
src/views/Booking/src/components/BookingDetail/src/BookingDetail.vue

@@ -7,6 +7,7 @@ import EmailView from './components/EmailView.vue'
 import { cloneDeep } from 'lodash'
 import { transportationMode } from '@/components/TransportationMode'
 import { useRoute } from 'vue-router'
+import { useOverflow } from '@/hooks/useOverflow'
 
 const route = useRoute()
 
@@ -88,6 +89,11 @@ getData()
 const formatTime = (time: string) => {
   return time ? dayjs(time).format('MMM-DD-YYYY hh:mm A') : '--'
 }
+
+const originRef = ref()
+const destinationRef = ref()
+const { isOverflow: isOriginOverflow } = useOverflow(originRef, allData)
+const { isOverflow: isDestinationOverflow } = useOverflow(destinationRef, allData)
 </script>
 
 <template>
@@ -110,12 +116,15 @@ const formatTime = (time: string) => {
             <div class="title">Origin</div>
             <div class="content">
               <!-- <span>{{ allData?.transportInfo?.origin }}</span> -->
-              <el-tooltip placement="top">
+              <el-tooltip v-if="isOriginOverflow" placement="top">
                 <template #content>{{ allData?.transportInfo?.origin || '--' }}</template>
-                <span class="info single-line-ellipsis">{{
+                <span ref="originRef" class="info single-line-ellipsis">{{
                   allData?.transportInfo?.origin || '--'
                 }}</span>
               </el-tooltip>
+              <span v-else ref="originRef" class="info single-line-ellipsis">{{
+                allData?.transportInfo?.origin || '--'
+              }}</span>
               <div class="line_container">
                 <hr color="#000000" />
                 <div class="right-icon"></div>
@@ -125,12 +134,15 @@ const formatTime = (time: string) => {
           <div class="destination">
             <div class="title">Destination</div>
             <div class="content">
-              <el-tooltip placement="top">
+              <el-tooltip v-if="isDestinationOverflow" placement="top">
                 <template #content>{{ allData?.transportInfo?.destination || '--' }}</template>
-                <span class="info single-line-ellipsis">{{
+                <span ref="destinationRef" class="info single-line-ellipsis">{{
                   allData?.transportInfo?.destination || '--'
                 }}</span>
               </el-tooltip>
+              <span v-else ref="destinationRef" class="info single-line-ellipsis">{{
+                allData?.transportInfo?.destination || '--'
+              }}</span>
             </div>
           </div>
         </div>

+ 0 - 1
src/views/Booking/src/components/BookingDetail/src/components/BasicInformation.vue

@@ -654,7 +654,6 @@ defineExpose({
   align-items: center;
   height: 16px !important;
   width: 16px;
-  margin-top: -1px;
   padding: 0 !important;
   color: var(--color-neutral-2);
   & > span {

+ 1 - 0
src/views/Booking/src/components/BookingDetail/src/components/EmailView.vue

@@ -181,6 +181,7 @@ const sendEmail = () => {
               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>

+ 3 - 1
src/views/Booking/src/components/BookingTable/src/components/DownloadDialog.vue

@@ -37,7 +37,9 @@ defineExpose({
       <div class="download-dialog">
         <div class="select-data">
           <div style="display: inline-block">
-            Select data on your shipment list:<span style="color: var(--color-theme)">1330</span>
+            Select data on your booking list:<span style="color: var(--color-theme)">{{
+              listData.length || 0
+            }}</span>
           </div>
         </div>
         <div class="data-filter">

+ 2 - 1
src/views/Login/src/components/ChangePasswordCard.vue

@@ -242,7 +242,8 @@ const checkPassword = () => {
     margin-bottom: 24px;
     padding: 40px 40px 0;
     background: url(../image/bg-login-card.png) no-repeat center center;
-
+    background-position: top left; /* 从左上角开始显示 */
+    background-size: 400px 100px; /* 保持背景图像的原始尺寸 */
     .welcome {
       margin-bottom: 16px;
       font-size: 24px;

BIN=BIN
src/views/Login/src/image/bg-login-card.png


+ 42 - 30
src/views/Login/src/loginView.vue

@@ -36,6 +36,26 @@ const saveCredentials = () => {
   localStorage.setItem('password', encryptedPassword)
 }
 
+// 验证当前用户是否存在
+const handleCheckUser = () => {
+  if (!loginForm.value.username) {
+    return
+  }
+  // 这里是验证用户是否存在的逻辑
+  $api.isUserNameExit({ uname: loginForm.value.username }).then((res: any) => {
+    if (res.code === 200) {
+      if (res.data.msg !== 'no_exist') {
+        isUserNameExit.value = true
+      } else {
+        loginError.value.username = true
+        isUserNameExit.value = false
+      }
+    } else {
+      isUserNameExit.value = false
+    }
+  })
+}
+
 // 读取存储的账号和解密后的密码
 const getCredentials = () => {
   const username = localStorage.getItem('account')
@@ -45,6 +65,8 @@ const getCredentials = () => {
     loginForm.value.username = username
     loginForm.value.password = password
     isRememerPwd.value = true
+    // 验证用户名是否存在
+    handleCheckUser()
   }
 }
 
@@ -69,6 +91,7 @@ watch(status, () => {
     code: false
   }
   isRememerPwd.value = false
+  isUserNameExit.value = false
   verificationCode.value = ''
   getCode()
 })
@@ -100,30 +123,13 @@ const getCode = () => {
 }
 getCode()
 
-// 验证当前用户是否存在
-const handleCheckUser = () => {
-  if (!loginForm.value.username) {
-    return
-  }
-  // 这里是验证用户是否存在的逻辑
-  $api.isUserNameExit({ uname: loginForm.value.username }).then((res: any) => {
-    if (res.code === 200) {
-      if (res.data.msg !== 'no_exist') {
-        isUserNameExit.value = true
-      } else {
-        loginError.value.username = true
-        isUserNameExit.value = false
-      }
-    } else {
-      isUserNameExit.value = false
-    }
-  })
-}
-
 const userStore = useUserStore()
 
 // 点击登录按钮
 const handleLogin = () => {
+  if (!isUserNameExit.value || !loginForm.value.username) {
+    return
+  }
   // 这里是登录逻辑
   $api
     .login({
@@ -189,6 +195,7 @@ const handleLogin = () => {
 const backLogin = (emailTips: boolean) => {
   status.value = 'login'
   isEmailTips.value = emailTips
+  emailTipsContent.value = emailTips ? 'Your password sent to registered email.' : ''
   // 如果是成功忘记密码,清空保存的账号密码
   // 如果是直接返回登录,获取保存的账号密码
   setTimeout(() => {
@@ -204,10 +211,12 @@ const isUserNameExit = ref(false)
 
 const handleForgot = () => {
   status.value = 'reset'
-  isUserNameExit.value = false
   handleDeleteEmailTips()
 }
 const handleSendPassword = () => {
+  if (!isUserNameExit.value || !loginForm.value.username) {
+    return
+  }
   // 这里是发送密码逻辑
   $api
     .forgotPassword({
@@ -218,8 +227,16 @@ const handleSendPassword = () => {
     .then((res: any) => {
       if (res.code === 200) {
         backLogin(true)
+      } else if (res.code === 400) {
+        const { data } = res
+        if (data.msg === 'verifcation_error') {
+          loginError.value.code = true
+        }
       }
     })
+    .finally(() => {
+      getCode()
+    })
 }
 
 const isEmailTips = ref(false)
@@ -231,7 +248,7 @@ const initEmailTips = () => {
     emailTipsContent.value = 'Changed successfully. Please log in.'
   } else {
     isEmailTips.value = false
-    emailTipsContent.value = 'New Password sent to registered email.'
+    emailTipsContent.value = 'Your password sent to registered email.'
   }
 }
 initEmailTips()
@@ -443,15 +460,10 @@ const errorTipsRef = ref()
     align-items: center;
     height: 94px;
     padding-top: 32px;
-    // background: linear-gradient(
-    //   251deg,
-    //   #ffffff4d,
-    //   #fff4eb80 22.66%,
-    //   #f0f3ff80 44.57%,
-    //   #e0f7f999 80.46%,
-    //   #ffffff4d
-    // );
     background: url(../src/image/bg-login-card.png) no-repeat center center;
+    background-position: top left; /* 从左上角开始显示 */
+    background-size: 400px 100px; /* 保持背景图像的原始尺寸 */
+
     .welcome {
       margin-bottom: 16px;
       font-size: 24px;

+ 24 - 2
src/views/Tracking/src/components/PublicTracking/src/PublicTrackingSearch.vue

@@ -1,6 +1,8 @@
 <script setup lang="ts">
 import { useRouter } from 'vue-router'
 import { useHeaderSearch } from '@/stores/modules/headerSearch'
+import SlideVerify from './components/SlideVerify.vue'
+import CryptoJS from 'crypto-js'
 
 const router = useRouter()
 
@@ -28,16 +30,23 @@ watch(
 )
 
 const loading = ref(false)
+const confirmVerifyStatus = ref('')
 const handleSearchNo = () => {
   if (!inputVModel.value) {
     return
   }
   loading.value = true
   $api
-    .getPublicTrackingDetail({ reference_number: inputVModel.value })
+    .getPublicTrackingDetail({
+      reference_number: inputVModel.value,
+      verifcation_code: confirmVerifyStatus.value
+    })
     .then((res) => {
       if (res.code === 200) {
         const { data } = res
+        if (data.msg === 'visit limit') {
+          slideVerifyRef.value?.openDialog()
+        }
         if (data.msg === 'No matches') {
           searchResult.value = 'error'
         } else if (data.msg === 'Multiple results') {
@@ -50,8 +59,20 @@ const handleSearchNo = () => {
     })
     .finally(() => {
       loading.value = false
+      confirmVerifyStatus.value = ''
     })
 }
+
+const slideVerifyRef = ref<InstanceType<typeof SlideVerify> | null>(null)
+const secretKey = 'fT5!R1k$7Mv@4Q9X'
+// AES 加密函数
+const encryptPassword = (password) => {
+  return CryptoJS.AES.encrypt(password, secretKey).toString()
+}
+// 验证滑块成功
+const confirmVerify = () => {
+  confirmVerifyStatus.value = encryptPassword('completed')
+}
 </script>
 
 <template>
@@ -93,6 +114,7 @@ const handleSearchNo = () => {
         </VEmpty>
       </div>
     </div>
+    <SlideVerify ref="slideVerifyRef" @verify-success="confirmVerify"></SlideVerify>
   </div>
 </template>
 
@@ -143,7 +165,7 @@ const handleSearchNo = () => {
       background-color: var(--color-table-header-bg);
     }
     :deep(.el-input-group__append) {
-      padding: 0 12px;
+      padding: 0;
       background-color: black;
       border: 1px solid black;
       border-radius: 0 6px 6px 0;

+ 0 - 1
src/views/Tracking/src/components/PublicTracking/src/components/BasicInformation.vue

@@ -402,7 +402,6 @@ const handleCopy = (data: any) => {
   height: 16px !important;
   width: 16px;
   padding: 0 !important;
-  margin-top: -1px;
   color: var(--color-neutral-2);
   & > span {
     display: block;

+ 16 - 4
src/views/Tracking/src/components/PublicTracking/src/components/PublicTrackingDetail.vue

@@ -4,6 +4,7 @@ import MilestonesTable from './MilestonesTable.vue'
 import { transportationMode } from '@/components/TransportationMode'
 import { useRoute } from 'vue-router'
 import dayjs from 'dayjs'
+import { useOverflow } from '@/hooks/useOverflow'
 
 const route = useRoute()
 
@@ -66,6 +67,11 @@ if (Object.keys(sharedData).length === 0) {
 const formatTime = (time: string) => {
   return time ? dayjs(time).format('MMM-DD-YYYY hh:mm A') : '--'
 }
+
+const originRef = ref()
+const destinationRef = ref()
+const { isOverflow: isOriginOverflow } = useOverflow(originRef, allData)
+const { isOverflow: isDestinationOverflow } = useOverflow(destinationRef, allData)
 </script>
 
 <template>
@@ -85,12 +91,15 @@ const formatTime = (time: string) => {
           <div class="origin">
             <div class="title">Origin</div>
             <div class="content">
-              <el-tooltip placement="top">
+              <el-tooltip v-if="isOriginOverflow" placement="top">
                 <template #content>{{ allData?.transportInfo?.origin || '--' }}</template>
-                <span class="info single-line-ellipsis">{{
+                <span ref="originRef" class="info single-line-ellipsis">{{
                   allData?.transportInfo?.origin || '--'
                 }}</span>
               </el-tooltip>
+              <span ref="originRef" v-else class="info single-line-ellipsis">{{
+                allData?.transportInfo?.origin || '--'
+              }}</span>
               <div class="line_container">
                 <hr color="#000000" />
                 <div class="right-icon"></div>
@@ -100,12 +109,15 @@ const formatTime = (time: string) => {
           <div class="destination">
             <div class="title">Destination</div>
             <div class="content">
-              <el-tooltip placement="top">
+              <el-tooltip v-if="isDestinationOverflow" placement="top">
                 <template #content>{{ allData?.transportInfo?.destination || '--' }}</template>
-                <span class="info single-line-ellipsis">{{
+                <span ref="destinationRef" class="info single-line-ellipsis">{{
                   allData?.transportInfo?.destination || '--'
                 }}</span>
               </el-tooltip>
+              <span v-else ref="destinationRef" class="info single-line-ellipsis">{{
+                allData?.transportInfo?.destination || '--'
+              }}</span>
             </div>
           </div>
         </div>

+ 207 - 0
src/views/Tracking/src/components/PublicTracking/src/components/SlideVerify.vue

@@ -0,0 +1,207 @@
+<script lang="ts" setup>
+const dialogVisible = ref(false)
+
+const openDialog = () => {
+  dialogVisible.value = true
+}
+
+const position = ref(0)
+const isDragging = ref(false)
+const verifyText = ref('Swipe right to verify')
+const sliderState = ref<'start' | 'success' | 'error' | 'dragging'>('start')
+const styleMap = {
+  start: {
+    thumbColor: 'var(--color-neutral-1)',
+    thumbIcon: 'icon-icon_drag__line_b',
+    trackBackground: '#87909e'
+  },
+  dragging: {
+    thumbColor: 'var(--color-neutral-1)',
+    thumbIcon: 'icon-icon_drag__line_b',
+    trackBackground: 'var(--color-success)'
+  },
+  success: {
+    thumbColor: '#fff',
+    thumbIcon: 'icon-icon_confirm_b',
+    trackBackground: 'var(--color-success)'
+  },
+  error: {
+    thumbColor: '#fff',
+    thumbIcon: 'icon-icon_reject_b',
+    trackBackground: '#c7353f'
+  }
+}
+const trackRef = ref<HTMLElement | null>(null)
+
+const getTrackBackground = () => {
+  const trackWidth = trackRef.value?.offsetWidth || 320
+  const progress = (position.value / (trackWidth - 40)) * 100 // 百分比
+  if (sliderState.value === 'start') {
+    return styleMap.start.trackBackground // 初始时灰色
+  } else if (sliderState.value === 'dragging') {
+    return `linear-gradient(90deg, ${styleMap.success.trackBackground} ${progress}%, ${styleMap.start.trackBackground} ${progress}%)`
+  } else if (sliderState.value === 'error') {
+    return `linear-gradient(90deg, ${styleMap.error.trackBackground} ${progress}%, ${styleMap.start.trackBackground} ${progress}%)`
+  }
+  return styleMap.success.trackBackground // 成功时整条绿色
+}
+
+const startDrag = () => {
+  if (sliderState.value === 'success') {
+    return
+  }
+
+  isDragging.value = true
+  document.addEventListener('mousemove', onDrag)
+  document.addEventListener('mouseup', stopDrag)
+}
+
+const onDrag = (event: MouseEvent) => {
+  if (isDragging.value) {
+    if (trackRef.value) {
+      sliderState.value = 'dragging'
+      verifyText.value = 'Swipe right to verify'
+      const rect = trackRef.value.getBoundingClientRect()
+      const offsetX = event.clientX - rect.left
+      position.value = Math.min(Math.max(offsetX, 0), rect.width - 40) // 40是滑块的宽度
+    }
+  }
+}
+
+const emit = defineEmits<{
+  verifySuccess: []
+}>()
+const stopDrag = () => {
+  isDragging.value = false
+  document.removeEventListener('mousemove', onDrag)
+  document.removeEventListener('mouseup', stopDrag)
+
+  if (trackRef.value) {
+    const trackWidth = trackRef.value.offsetWidth
+    if (position.value >= trackWidth - 40) {
+      sliderState.value = 'success'
+      verifyText.value = 'Verification successful'
+      setTimeout(() => {
+        dialogVisible.value = false
+        emit('verifySuccess')
+      }, 500)
+    } else {
+      sliderState.value = 'error'
+      verifyText.value = 'Verification failed'
+      setTimeout(() => {
+        sliderState.value = 'start'
+        verifyText.value = 'Swipe right to verify'
+        position.value = 0
+      }, 3000)
+    }
+  }
+}
+
+const moveSlider = (event: MouseEvent) => {
+  if (sliderState.value !== 'success') {
+    onDrag(event)
+  }
+}
+defineExpose({
+  openDialog
+})
+</script>
+
+<template>
+  <el-dialog top="30vh" v-model="dialogVisible" width="400" class="slide-verify-dialog">
+    <div class="content">
+      <p>Please drag the slider below to complete the</p>
+      <p>verification to ensure normal access</p>
+      <div class="slider-container">
+        <div
+          class="slider-track"
+          :style="{ background: getTrackBackground() }"
+          @click="moveSlider"
+          ref="trackRef"
+        >
+          {{ verifyText }}
+          <div
+            class="slider-thumb"
+            :style="{ left: `${position}px`, borderColor: styleMap[sliderState].trackBackground }"
+            @mousedown="startDrag"
+          >
+            <span
+              v-if="sliderState === 'start' || sliderState === 'dragging'"
+              class="font_family"
+              :style="{ color: styleMap[sliderState].thumbColor }"
+              :class="[styleMap[sliderState].thumbIcon]"
+            ></span>
+            <span
+              v-else
+              class="font_family other-state"
+              :style="{
+                color: styleMap[sliderState].thumbColor,
+                backgroundColor: styleMap[sliderState].trackBackground
+              }"
+              :class="[styleMap[sliderState].thumbIcon]"
+            ></span>
+          </div>
+        </div>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+
+<style lang="scss" scoped>
+.content {
+  padding: 40px 0;
+  text-align: center;
+  & > p {
+    line-height: 21px;
+  }
+}
+
+.slider-container {
+  width: 320px;
+  margin: 16px auto 0;
+  text-align: center;
+  user-select: none;
+}
+.slider-track {
+  position: relative;
+  width: 100%;
+  height: 40px;
+  padding: 1px;
+  background: #868f9d;
+  border-radius: 6px;
+  line-height: 38px;
+  color: #fff;
+}
+.slider-thumb {
+  position: absolute;
+  top: 0px;
+  left: 10px;
+  width: 40px;
+  height: 40px;
+  background: #fff;
+  cursor: pointer;
+  border-radius: 6px;
+  border: 1px solid #868f9d;
+  .font_family {
+    font-size: 14px;
+    &.other-state {
+      height: 16px;
+      width: 16px;
+      padding: 1px;
+      border-radius: 50%;
+      font-size: 14px;
+    }
+  }
+}
+</style>
+
+<style lang="scss">
+.slide-verify-dialog {
+  .el-dialog__header {
+    display: none;
+  }
+  .el-dialog__body {
+    padding: 0;
+  }
+}
+</style>

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

@@ -108,6 +108,7 @@ getData()
 const formatTime = (time: string) => {
   return time ? dayjs(time).format('MMM-DD-YYYY hh:mm A') : '--'
 }
+
 const originRef = ref()
 const destinationRef = ref()
 const { isOverflow: isOriginOverflow } = useOverflow(originRef, allData)

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

@@ -650,7 +650,6 @@ defineExpose({
   height: 16px !important;
   width: 16px;
   padding: 0 !important;
-  margin-top: -1px;
   color: var(--color-neutral-2);
   & > span {
     display: block;

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

@@ -194,6 +194,7 @@ defineExpose({
                 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>

+ 49 - 17
src/views/Tracking/src/components/TrackingDetail/src/components/RoutesView.vue

@@ -1,6 +1,7 @@
 <script setup lang="ts">
 import dayjs from 'dayjs'
 import { transportationMode } from '@/components/TransportationMode'
+import { useOverflow } from '@/hooks/useOverflow'
 
 const props = defineProps({
   data: Object
@@ -52,6 +53,16 @@ watch(
 const formatDate = (date: string) => {
   return date ? dayjs(date).format('MMM-DD-YYYY HH:mm A') : '--'
 }
+
+const basicOriginRef = ref()
+const basicDestinationRef = ref()
+const detailOriginRef = ref()
+const detailDestinationRef = ref()
+
+const { isOverflow: isBasicOriginOverflow } = useOverflow(basicOriginRef, props)
+const { isOverflow: isBasicDestinationOverflow } = useOverflow(basicDestinationRef, props)
+const { isOverflow: isDetailOriginOverflow } = useOverflow(detailOriginRef, props)
+const { isOverflow: isDetailDestinationOverflow } = useOverflow(detailDestinationRef, props)
 </script>
 
 <template>
@@ -74,10 +85,15 @@ const formatDate = (date: string) => {
             <div class="origin">
               <div class="title">Origin</div>
               <div class="content">
-                <el-tooltip placement="top">
+                <el-tooltip v-if="isBasicOriginOverflow?.[index]" placement="top">
                   <template #content>{{ item.origin || '--' }}</template>
-                  <span class="single-line-ellipsis">{{ item.origin || '--' }}</span>
+                  <span ref="basicOriginRef" class="single-line-ellipsis">{{
+                    item.origin || '--'
+                  }}</span>
                 </el-tooltip>
+                <span v-else ref="basicOriginRef" class="single-line-ellipsis">{{
+                  item.origin || '--'
+                }}</span>
 
                 <div class="line_container">
                   <hr color="#000000" />
@@ -87,10 +103,15 @@ const formatDate = (date: string) => {
             </div>
             <div class="destination" ref="">
               <div class="title">Destination</div>
-              <el-tooltip placement="top">
+              <el-tooltip v-if="isBasicDestinationOverflow?.[index]" placement="top">
                 <template #content>{{ item.destination || '--' }}</template>
-                <span class="content single-line-ellipsis">{{ item.destination || '--' }}</span>
+                <span ref="basicDestinationRef" class="content single-line-ellipsis">{{
+                  item.destination || '--'
+                }}</span>
               </el-tooltip>
+              <span v-else ref="basicDestinationRef" class="content single-line-ellipsis">{{
+                item.destination || '--'
+              }}</span>
             </div>
           </div>
           <div class="etd border-right">
@@ -112,12 +133,18 @@ const formatDate = (date: string) => {
               <div class="place">
                 <span style="font-size: 24px" class="font_family icon-icon_location_fill_b"></span>
 
-                <el-tooltip placement="top">
+                <el-tooltip v-if="isDetailOriginOverflow?.[index]" placement="top">
                   <template #content>{{ item.origin || '--' }}</template>
-                  <span class="label origin-label single-line-ellipsis">{{
+                  <span ref="detailOriginRef" class="label origin-label single-line-ellipsis">{{
                     item.origin || '--'
                   }}</span>
                 </el-tooltip>
+                <span
+                  v-else
+                  ref="detailOriginRef"
+                  class="label origin-label single-line-ellipsis"
+                  >{{ item.origin || '--' }}</span
+                >
                 <div class="line_container">
                   <hr color="#000000" />
                   <div class="right-icon"></div>
@@ -138,10 +165,15 @@ const formatDate = (date: string) => {
               <div class="place">
                 <span style="font-size: 24px" class="font_family icon-icon_location_fill_b"></span>
 
-                <el-tooltip placement="top">
+                <el-tooltip v-if="isDetailDestinationOverflow?.[index]" placement="top">
                   <template #content>{{ item.destination || '--' }}</template>
-                  <span class="label single-line-ellipsis">{{ item.destination || '--' }}</span>
+                  <span ref="detailDestinationRef" class="label single-line-ellipsis">{{
+                    item.destination || '--'
+                  }}</span>
                 </el-tooltip>
+                <span v-else ref="detailDestinationRef" class="label single-line-ellipsis">{{
+                  item.destination || '--'
+                }}</span>
               </div>
               <div class="eta">
                 <span class="font_family icon-icon_date_b"></span>
@@ -226,15 +258,14 @@ const formatDate = (date: string) => {
   }
 
   .destination {
-    max-width: calc(40% - 16px);
-    display: flex;
-    flex-direction: column;
-    justify-content: space-between;
+    max-width: 40%;
     margin-left: 16px;
-    & > div.content {
-      display: inline-block;
+    & > .content {
       width: 100%;
-      overflow: hidden;
+      margin-top: 8px;
+      padding-top: 2px;
+      font-size: 18px;
+      font-weight: 700;
     }
   }
   .place {
@@ -248,9 +279,10 @@ const formatDate = (date: string) => {
       margin-top: 2px;
     }
     .origin {
-      width: 60%;
+      width: calc(60% - 16px);
     }
-
+  }
+  .origin {
     .content {
       position: relative;
       display: flex;

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

@@ -36,7 +36,9 @@ defineExpose({
       <div class="download-dialog">
         <div class="select-data">
           <div style="display: inline-block">
-            Select data on your shipment list:<span style="color: var(--color-theme)">1330</span>
+            Select data on your shipment list:<span style="color: var(--color-theme)">{{
+              listData.length || 0
+            }}</span>
           </div>
         </div>
         <div class="data-filter">

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio