Explorar o código

修改地图标记,实现顶部搜索栏功能,调整细节

zhouyuhao hai 1 ano
pai
achega
6ce8034c72
Modificáronse 24 ficheiros con 596 adicións e 263 borrados
  1. 14 0
      src/api/module/tracking.ts
  2. 6 1
      src/router/index.ts
  3. 35 0
      src/stores/modules/headerSearch.ts
  4. 1 0
      src/utils/axios.ts
  5. 4 2
      src/views/Booking/src/components/BookingDetail/src/components/ContainersView.vue
  6. 4 2
      src/views/Booking/src/components/BookingTable/src/BookingTable.vue
  7. 63 2
      src/views/Layout/src/components/Header/HeaderView.vue
  8. 4 4
      src/views/Layout/src/components/Menu/MenuView.vue
  9. 112 120
      src/views/Login/src/components/ChangePasswordCard.vue
  10. 4 2
      src/views/Login/src/loginView.vue
  11. 0 1
      src/views/Tracking/src/TrackingView.vue
  12. 26 2
      src/views/Tracking/src/components/PublicTracking/src/PublicTrackingSearch.vue
  13. 12 46
      src/views/Tracking/src/components/PublicTracking/src/components/MilestonesTable.vue
  14. 69 13
      src/views/Tracking/src/components/PublicTracking/src/components/PublicTrackingDetail.vue
  15. 1 1
      src/views/Tracking/src/components/TrackingDetail/src/TrackingDetail.vue
  16. 2 18
      src/views/Tracking/src/components/TrackingDetail/src/components/AttachmentView.vue
  17. 4 2
      src/views/Tracking/src/components/TrackingDetail/src/components/ContainersView.vue
  18. 175 29
      src/views/Tracking/src/components/TrackingDetail/src/components/MapView.vue
  19. 6 10
      src/views/Tracking/src/components/TrackingDetail/src/components/MilestonesTable.vue
  20. 0 0
      src/views/Tracking/src/components/TrackingDetail/src/images/destinationIcon.png
  21. BIN=BIN
      src/views/Tracking/src/components/TrackingDetail/src/images/originIcon.png
  22. BIN=BIN
      src/views/Tracking/src/components/TrackingDetail/src/images/transferIcon.png
  23. 50 6
      src/views/Tracking/src/components/TrackingTable/src/TrackingTable.vue
  24. 4 2
      src/views/Tracking/src/components/TrackingTable/src/components/VGMView.vue

+ 14 - 0
src/api/module/tracking.ts

@@ -49,6 +49,20 @@ export const getTrackingDetail = (params: any, config: any) => {
   )
 }
 
+/**
+ * 获取Tracking详情页中地图数据
+ */
+export const getTrackingDetailMapData = (params: any, config: any) => {
+  return HttpAxios.get(
+    `${baseUrl}`,
+    {
+      action: 'main_map_new',
+      ...params
+    },
+    config
+  )
+}
+
 /**
  * 获取Tracking详情页中AMS/ISF表格数据
  */

+ 6 - 1
src/router/index.ts

@@ -76,6 +76,11 @@ const router = createRouter({
             activeMenu: '/tracking'
           }
         },
+        {
+          path: '/reset-password',
+          name: 'Reset Password',
+          component: () => import('../views/Login/src/components/ChangePasswordCard.vue')
+        },
         {
           path: '/Operationlog',
           name: 'Operationlog',
@@ -89,7 +94,7 @@ const router = createRouter({
 // * 路由拦截 beforeEach
 router.beforeEach(async (to, from, next) => {
   // 未登录白名单
-  const whiteList = ['/login', '/public-tracking', '/public-tracking/detail']
+  const whiteList = ['/login', '/public-tracking', '/public-tracking/detail', '/reset-password']
   // 判断是否登录
   if (!whiteList.includes(to.path) && !localStorage.getItem('username')) {
     if (whiteList.includes(from.path)) {

+ 35 - 0
src/stores/modules/headerSearch.ts

@@ -0,0 +1,35 @@
+import { defineStore } from 'pinia'
+
+interface HeaderSearch {
+  searchValue: string
+  searchResult?: string
+  isChangeByLogin: boolean
+}
+export const useHeaderSearch = defineStore('headerSearch', {
+  state: (): HeaderSearch => ({
+    searchValue: JSON.parse(localStorage.getItem('searchData'))?.searchValue || '',
+    searchResult: JSON.parse(localStorage.getItem('searchData'))?.searchResult || '',
+    isChangeByLogin: Boolean(localStorage.getItem('isChangeByLogin')) || false
+  }),
+  getters: {},
+  actions: {
+    setSearchData(searchData: any) {
+      localStorage.setItem('searchData', JSON.stringify(searchData))
+      this.searchValue = searchData.searchValue
+      this.searchResult = searchData.searchResult
+    },
+    setChangeByLogin(isChangeByLogin: boolean) {
+      localStorage.setItem('isChangeByLogin', JSON.stringify(isChangeByLogin))
+      this.isChangeByLogin = isChangeByLogin
+    },
+    clearSearchData() {
+      localStorage.removeItem('searchData')
+      this.searchValue = ''
+      this.searchResult = ''
+    },
+    clearChangeByLogin() {
+      localStorage.removeItem('isChangeByLogin')
+      this.isChangeByLogin = false
+    }
+  }
+})

+ 1 - 0
src/utils/axios.ts

@@ -58,6 +58,7 @@ class HttpAxios {
         router.push('/login')
         ElMessage.warning('Please log in to use this feature.')
       } else if (response.data.code !== 200 && response.data.code !== 400) {
+        console.log(response.data)
         ElMessageBox.alert(
           response.data?.data?.msg || 'The request failed. Please try again later',
           'Prompt',

+ 4 - 2
src/views/Booking/src/components/BookingDetail/src/components/ContainersView.vue

@@ -40,12 +40,14 @@ const handleColumns = (columns: any) => {
         sortBy: ({ row, column }: any) => {
           return dayjs(row[column.field]).unix()
         },
-        formatter: ({ cellValue }: any) => dayjs(cellValue).format('MMM-YYYY-DD ') || '--'
+        formatter: ({ cellValue }: any) =>
+          cellValue ? dayjs(cellValue).format('MMM-YYYY-DD ') : '--'
       }
     } else if (item.formatter === 'dateTime') {
       curColumn = {
         ...curColumn,
-        formatter: ({ cellValue }: any) => dayjs(cellValue).format('MMM-YYYY-DD HH:mm:ss') || '--'
+        formatter: ({ cellValue }: any) =>
+          cellValue ? dayjs(cellValue).format('MMM-YYYY-DD HH:mm:ss') : '--'
       }
     }
     return curColumn

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

@@ -52,12 +52,14 @@ const handleColumns = (columns: any, status?: string) => {
         sortBy: ({ row, column }: any) => {
           return dayjs(row[column.field]).unix()
         },
-        formatter: ({ cellValue }: any) => dayjs(cellValue).format('MMM-YYYY-DD ') || '--'
+        formatter: ({ cellValue }: any) =>
+          cellValue ? dayjs(cellValue).format('MMM-YYYY-DD ') : '--'
       }
     } else if (item.formatter === 'dateTime') {
       curColumn = {
         ...curColumn,
-        formatter: ({ cellValue }: any) => dayjs(cellValue).format('MMM-YYYY-DD HH:mm:ss') || '--'
+        formatter: ({ cellValue }: any) =>
+          cellValue ? dayjs(cellValue).format('MMM-YYYY-DD HH:mm:ss') : '--'
       }
     }
     return curColumn

+ 63 - 2
src/views/Layout/src/components/Header/HeaderView.vue

@@ -5,10 +5,70 @@ import ChangePasswordDialog from './components/ChangePasswordDialog.vue'
 import LogoutDialog from './components/LogoutDialog.vue'
 import { useRouter } from 'vue-router'
 import { useUserStore } from '@/stores/modules/user'
+import { useHeaderSearch } from '@/stores/modules/headerSearch'
 
 const userStore = useUserStore()
 const router = useRouter()
-const input1 = ref('')
+const headerSearch = useHeaderSearch()
+
+const searchValue = ref('')
+const handleSearch = () => {
+  // 先判断是否登录
+  // 未登录
+  if (!localStorage.getItem('username')) {
+    $api.getPublicTrackingDetail({ reference_number: searchValue.value }).then((res) => {
+      if (res.code === 200) {
+        const { data } = res
+        if (data.msg === 'No matches') {
+          headerSearch.setSearchData({
+            searchValue: searchValue.value,
+            searchResult: 'error'
+          })
+          router.push({
+            name: 'Public Tracking'
+          })
+        } else if (data.msg === 'Multiple results') {
+          headerSearch.setSearchData({
+            searchValue: searchValue.value,
+            searchResult: 'multiple'
+          })
+          router.push({
+            name: 'Public Tracking'
+          })
+        } else {
+          sessionStorage.setItem('publicTrackingData', JSON.stringify(data.data))
+          router.push(`/public-tracking/detail?searchNo=${searchValue.value}`)
+        }
+      }
+    })
+  } else {
+    // 已登录
+    $api
+      .getTrackingTableData({
+        _textSearch: searchValue.value
+      })
+      .then((res) => {
+        if (res.code === 200) {
+          const { searchData } = res.data
+          if (searchData.length === 1) {
+            router.push({
+              name: 'Tracking Detail',
+              query: {
+                a: res.data.searchData[0].__serial_no,
+                _schemas: res.data.searchData[0].__schemas
+              }
+            })
+          } else if (searchData.length !== 1) {
+            headerSearch.setChangeByLogin(true)
+            localStorage.setItem('TrackingData', JSON.stringify(res.data))
+            router.push({
+              name: 'Tracking'
+            })
+          }
+        }
+      })
+  }
+}
 
 const downloadKLNPortalRef = ref()
 const handleDownload = () => {
@@ -36,8 +96,9 @@ const handleLogin = () => {
     <VBreadcrumd></VBreadcrumd>
     <div class="right-info">
       <el-input
-        v-model="input1"
+        v-model="searchValue"
         size="large"
+        @keyup.enter="handleSearch"
         placeholder="Search a reference number to see shipment details"
         :prefix-icon="Search"
       />

+ 4 - 4
src/views/Layout/src/components/Menu/MenuView.vue

@@ -108,11 +108,9 @@ const getAllMenuPaths = (menuList: any) => {
   })
   return paths
 }
-// 获取所有菜单项的路径(包括子菜单)
-const menuPaths = getAllMenuPaths(menuList)
 
 // 未登录白名单
-const whiteList = ['/login', '/public-tracking', '/public-tracking/detail']
+const whiteList = ['/login', '/public-tracking', '/public-tracking/detail', '/reset-password']
 
 // 判断是否允许跳转
 const isAllowJump = (path: any) => {
@@ -163,7 +161,9 @@ const menuRef = ref()
   >
     <template v-for="item in menuList" :key="item.index">
       <el-menu-item
-        :class="{ 'clear-active-style': route.path === '/login' }"
+        :class="{
+          'clear-active-style': route.path === '/login' || route.path === '/reset-password'
+        }"
         v-if="item.type !== 'list'"
         :index="item.path"
       >

+ 112 - 120
src/views/Login/src/components/ChangePasswordCard.vue

@@ -1,6 +1,5 @@
 <script setup lang="ts">
 import { useRouter } from 'vue-router'
-import { useUserStore } from '@/stores/modules/user'
 
 const router = useRouter()
 const loginForm = ref({
@@ -16,8 +15,6 @@ const loginError: any = ref({
   confirmPassword: false
 })
 
-const userStore = useUserStore()
-
 const handleChangePwd = () => {
   $api
     .resetPwd({
@@ -27,35 +24,7 @@ const handleChangePwd = () => {
     })
     .then((res: any) => {
       if (res.code === 200) {
-        const { data } = res
-        if (data.msg === 'today') {
-          ElMessageBox.alert('Your password will expire today, please reset', 'Prompt', {
-            confirmButtonText: 'OK',
-            type: 'warning',
-            confirmButtonClass: 'el-button--dark'
-          })
-        } else if (data.msg === 'last') {
-          ElMessageBox.alert(
-            `Your password will expire in${data.data}days, please reset`,
-            'Prompt',
-            {
-              confirmButtonText: 'OK',
-              type: 'warning',
-              confirmButtonClass: 'el-button--dark'
-            }
-          )
-        }
-        userStore.setUsername(res.data.uname || '')
-        router.push('/')
-      } else if (res.code === 400) {
-        // 验证码错误
-        if (res.data.msg === 'password_error') {
-          loginError.value.password = true
-        } else if (res.data.msg === 'verifcation_error') {
-          loginError.value.code = true
-        } else if (res.data.msg === 'error_times') {
-          errorTipsRef.value.openDialog()
-        }
+        console.log(res)
       }
     })
 }
@@ -78,106 +47,129 @@ const emit = defineEmits<{
 }>()
 const handleForgot = () => {
   emit('changeStatus', 'reset')
+  router.push({
+    name: 'Login',
+    query: {
+      status: 'reset'
+    }
+  })
 }
 const handleBackLogin = () => {
   emit('changeStatus', 'login')
+  router.push({
+    name: 'Login',
+    query: {
+      status: 'login'
+    }
+  })
 }
-
-const errorTipsRef = ref()
 </script>
 
 <template>
-  <el-card class="login-card">
-    <div class="title">
-      <span class="welcome">Change Password</span>
-      <span class="tips">Password expired, please change your password.</span>
-    </div>
-
-    <div class="login-form">
-      <div class="label">
-        <span>User Name</span>
-      </div>
-      <el-input :disabled="true" ref="userNameRef" v-model="loginForm.username" class="user-name">
-        <template #prefix>
-          <span class="font_family icon-icon_username_b"></span>
-        </template>
-        <template #suffix>
-          <span v-if="isUserNameExit" class="font_family icon-icon_confirm_b confirm-icon"></span>
-        </template>
-      </el-input>
-      <div class="label">
-        <span>Old Password</span>
-        <span class="forgot-password" @click="handleForgot">Forgot Password?</span>
-      </div>
-      <el-input
-        ref="passWordRef"
-        :class="{ 'is-error': loginError.oldPassword }"
-        v-model="loginForm.oldPassword"
-        type="password"
-        placeholder="Please input password"
-        show-password
-        @focus="hiddenError('oldPassword')"
-      >
-        <template #prefix>
-          <span class="font_family icon-icon_password_b"></span>
-        </template>
-      </el-input>
-      <div class="error" v-if="loginError.oldPassword">Incorrect password. Please try again.</div>
-      <div class="label">
-        <span>New Password</span>
-      </div>
-      <el-input
-        ref="passWordRef"
-        :class="{ 'is-error': loginError.newPassword }"
-        v-model="loginForm.newPassword"
-        type="password"
-        placeholder="Please input password"
-        show-password
-        @focus="hiddenError('newPassword')"
-      >
-        <template #prefix>
-          <span class="font_family icon-icon_password_b"></span>
-        </template>
-      </el-input>
-      <div class="error" v-if="loginError.newPassword">Incorrect password. Please try again.</div>
-      <div class="label">
-        <span>Confirm New Password</span>
+  <div class="login">
+    <el-card class="login-card">
+      <div class="title">
+        <span class="welcome">Change Password</span>
+        <span class="tips">Password expired, please change your password.</span>
       </div>
-      <el-input
-        ref="passWordRef"
-        :class="{ 'is-error': loginError.confirmPassword }"
-        v-model="loginForm.confirmPassword"
-        type="password"
-        placeholder="Please input password"
-        show-password
-        @focus="hiddenError('confirmPassword')"
-        @blur="confirmPwd"
-      >
-        <template #prefix>
-          <span class="font_family icon-icon_password_b"></span>
-        </template>
-      </el-input>
-      <div class="error" v-if="loginError.confirmPassword">
-        The password does not match. Please try again.
-      </div>
-      <el-button @click="handleChangePwd" class="el-button--dark login-btn"
-        >Change Password</el-button
-      >
-      <div @click="handleBackLogin" class="back-text">
-        <span class="font_family icon-icon_back_b"></span>
-        <span class="text"> Back to login</span>
-      </div>
-    </div>
-    <template #footer>
-      <div class="license">
-        <span>© 2024 KTreker from <span class="company">Kerry Logistics</span></span>
-        <span>Version 0.67</span>
+
+      <div class="login-form">
+        <div class="label">
+          <span>User Name</span>
+        </div>
+        <el-input :disabled="true" ref="userNameRef" v-model="loginForm.username" class="user-name">
+          <template #prefix>
+            <span class="font_family icon-icon_username_b"></span>
+          </template>
+          <template #suffix>
+            <span v-if="isUserNameExit" class="font_family icon-icon_confirm_b confirm-icon"></span>
+          </template>
+        </el-input>
+        <div class="label">
+          <span>Old Password</span>
+          <span class="forgot-password" @click="handleForgot">Forgot Password?</span>
+        </div>
+        <el-input
+          ref="passWordRef"
+          :class="{ 'is-error': loginError.oldPassword }"
+          v-model="loginForm.oldPassword"
+          type="password"
+          placeholder="Please input password"
+          show-password
+          @focus="hiddenError('oldPassword')"
+        >
+          <template #prefix>
+            <span class="font_family icon-icon_password_b"></span>
+          </template>
+        </el-input>
+        <div class="error" v-if="loginError.oldPassword">Incorrect password. Please try again.</div>
+        <div class="label">
+          <span>New Password</span>
+        </div>
+        <el-input
+          ref="passWordRef"
+          :class="{ 'is-error': loginError.newPassword }"
+          v-model="loginForm.newPassword"
+          type="password"
+          placeholder="Please input password"
+          show-password
+          @focus="hiddenError('newPassword')"
+        >
+          <template #prefix>
+            <span class="font_family icon-icon_password_b"></span>
+          </template>
+        </el-input>
+        <div class="error" v-if="loginError.newPassword">Incorrect password. Please try again.</div>
+        <div class="label">
+          <span>Confirm New Password</span>
+        </div>
+        <el-input
+          ref="passWordRef"
+          :class="{ 'is-error': loginError.confirmPassword }"
+          v-model="loginForm.confirmPassword"
+          type="password"
+          placeholder="Please input password"
+          show-password
+          @focus="hiddenError('confirmPassword')"
+          @blur="confirmPwd"
+        >
+          <template #prefix>
+            <span class="font_family icon-icon_password_b"></span>
+          </template>
+        </el-input>
+        <div class="error" v-if="loginError.confirmPassword">
+          The password does not match. Please try again.
+        </div>
+        <el-button @click="handleChangePwd" class="el-button--dark login-btn"
+          >Change Password</el-button
+        >
+        <div @click="handleBackLogin" class="back-text">
+          <span class="font_family icon-icon_back_b"></span>
+          <span class="text"> Back to login</span>
+        </div>
       </div>
-    </template>
-  </el-card>
+      <template #footer>
+        <div class="license">
+          <span>© 2024 KTreker from <span class="company">Kerry Logistics</span></span>
+          <span>Version 0.67</span>
+        </div>
+      </template>
+    </el-card>
+  </div>
 </template>
 
 <style lang="scss" scoped>
+.login {
+  position: relative;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100%;
+  width: 100%;
+  background: url(../image/bg-image.png) no-repeat center center;
+  background-size: cover;
+}
+
 .login-card {
   width: 400px;
 

+ 4 - 2
src/views/Login/src/loginView.vue

@@ -1,11 +1,12 @@
 <script setup lang="ts">
-import { useRouter } from 'vue-router'
+import { useRouter, useRoute } from 'vue-router'
 import ErrorTips from './components/ErrorTips.vue'
 import { useUserStore } from '@/stores/modules/user'
 import ChangePasswordCard from './components/ChangePasswordCard.vue'
 import ScoringSystem from '@/views/Dashboard/src/components/ScoringSystem.vue'
 
 const router = useRouter()
+const route = useRoute()
 const loginForm = ref({
   username: 'ra.admin',
   password: 'abc123456789',
@@ -13,7 +14,7 @@ const loginForm = ref({
   code: ''
 })
 
-const status = ref('login')
+const status = ref(route.query.status || 'login')
 watch(status, () => {
   loginForm.value = {
     username: 'ra.admin',
@@ -162,6 +163,7 @@ const handleChangeStatus = (newStatus: string) => {
 }
 const test = () => {
   status.value = 'changePwd'
+  router.push('/reset-password')
 }
 </script>
 

+ 0 - 1
src/views/Tracking/src/TrackingView.vue

@@ -12,7 +12,6 @@ import { useRouter } from 'vue-router'
 const router = useRouter()
 
 const filterRef: Ref<HTMLElement | null> = ref(null)
-// const containerHeight: any = ref(0)
 
 const containerHeight = useCalculatingHeight(document.documentElement, 246, [filterRef])
 

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

@@ -1,9 +1,33 @@
 <script setup lang="ts">
-import { useRouter } from 'vue-router'
+import { useRouter, useRoute } from 'vue-router'
+import { useHeaderSearch } from '@/stores/modules/headerSearch'
+
 const router = useRouter()
-const inputVModel = ref('A170353006211')
+const route = useRoute()
 
+const headerSearch = useHeaderSearch()
 const searchResult = ref('')
+
+const inputVModel = ref('A170353006211')
+// 从 store 中获取数据并绑定到输入框
+const headerSearchdData = computed(() => headerSearch.searchValue)
+
+// 监听 sharedData 的变化并更新 inputValue
+headerSearchdData.value && (inputVModel.value = headerSearchdData.value)
+searchResult.value = headerSearch.searchResult
+
+// 当 sharedData 发生变化时,更新 inputValue
+watch(
+  () => headerSearchdData.value,
+  (newData) => {
+    // if (newData) {
+    inputVModel.value = headerSearchdData.value
+    searchResult.value = headerSearch.searchResult
+    // headerSearch.clearSearchData()
+    // }
+  }
+)
+
 const loading = ref(false)
 const handleSearchNo = () => {
   loading.value = true

+ 12 - 46
src/views/Tracking/src/components/PublicTracking/src/components/MilestonesTable.vue

@@ -1,9 +1,13 @@
 <script setup lang="ts">
 import dayjs from 'dayjs'
+import timezone from 'dayjs/plugin/timezone'
+import utc from 'dayjs/plugin/utc'
 import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
 import { autoWidth } from '@/utils/table'
 import { useRowClickStyle } from '@/hooks/rowClickStyle'
 
+dayjs.extend(utc)
+dayjs.extend(timezone)
 const props = defineProps({
   data: Object
 })
@@ -12,42 +16,8 @@ const tableData = ref<VxeGridProps<any>>({
   border: true,
   minHeight: 70,
   maxHeight: 440,
-  columns: [
-    {
-      field: 'milestones',
-      title: 'Milestones',
-      minWidth: 120
-    },
-    {
-      field: 'dateTime',
-      title: 'Date Time',
-      minWidth: 80
-    },
-    {
-      field: 'locations',
-      title: 'Locations',
-      minWidth: 80
-    },
-    {
-      field: 'remarks',
-      title: 'Remarks',
-      minWidth: 80
-    }
-  ],
-  data: [
-    {
-      milestones: 'Milestone 1',
-      dateTime: 'Jun-08-2024 12:00 AM',
-      locations: 'Shenzhen',
-      remarks: 'Remarks 1'
-    },
-    {
-      milestones: 'Milestone 2',
-      dateTime: 'Jun-10-2024 12:00 AM',
-      locations: 'Valencia',
-      remarks: 'Remarks 2'
-    }
-  ],
+  columns: [],
+  data: [],
   scrollY: { enabled: true, oSize: 20, gt: 30 },
   stripe: true,
   emptyText: ' ',
@@ -68,18 +38,14 @@ const handleColumns = (columns: any) => {
     }
 
     // 格式化
-    if (item.formatter === 'date') {
-      curColumn = {
-        ...curColumn,
-        sortBy: ({ row, column }: any) => {
-          return dayjs(row[column.field]).unix()
-        },
-        formatter: ({ cellValue }: any) => dayjs(cellValue).format('MMM-YYYY-DD ') || '--'
-      }
-    } else if (item.formatter === 'dateTime') {
+    if (item.type === 'dateTime') {
       curColumn = {
         ...curColumn,
-        formatter: ({ cellValue }: any) => dayjs(cellValue).format('MMM-YYYY-DD HH:mm:ss') || '--'
+        formatter: ({ cellValue, row }: any) => {
+          return cellValue
+            ? dayjs(cellValue).tz(row.timezone).format('MMM-DD-YYYY hh:mm A (z)')
+            : '--'
+        }
       }
     }
     return curColumn

+ 69 - 13
src/views/Tracking/src/components/PublicTracking/src/components/PublicTrackingDetail.vue

@@ -2,10 +2,66 @@
 import BasicInformation from './BasicInformation.vue'
 import MilestonesTable from './MilestonesTable.vue'
 import { transportationMode } from '@/components/TransportationMode'
+import { useRoute } from 'vue-router'
 import dayjs from 'dayjs'
 
-const data: any = JSON.parse(sessionStorage.getItem('publicTrackingData'))
-console.log(data)
+const route = useRoute()
+
+const allData: any = ref({
+  transportInfo: {
+    'Tracking No.': '',
+    mode: '',
+    status: '',
+    origin: '',
+    destination: '',
+    etd: '',
+    atd: '',
+    eta: '',
+    ata: ''
+  },
+  basicInfo: {
+    'HAWB/HBOL': '',
+    Carrier_Booking_No: '',
+    PO_NO: ''
+  },
+  businessPartners: {
+    origin: {
+      company: '',
+      address: '',
+      phone: ''
+    },
+    destination: {
+      company: '',
+      address: '',
+      phone: ''
+    }
+  },
+  packing: {
+    'Quantity/Unit': '',
+    'G. Weight': '',
+    'Ch. Weight': '',
+    Volume: ''
+  }
+})
+const sharedData = JSON.parse(sessionStorage.getItem('publicTrackingData') || '{}')
+sessionStorage.removeItem('publicTrackingData')
+if (Object.keys(sharedData).length === 0) {
+  const loading = ref(false)
+  loading.value = true
+  $api
+    .getPublicTrackingDetail({ reference_number: route.query.searchNo })
+    .then((res) => {
+      if (res.code === 200) {
+        const { data } = res
+        allData.value = data.data
+      }
+    })
+    .finally(() => {
+      loading.value = false
+    })
+} else {
+  allData.value = sharedData
+}
 
 const formatTime = (time: string) => {
   return time ? dayjs(time).format('MMM-DD-YYYY hh:mm A') : '--'
@@ -18,18 +74,18 @@ const formatTime = (time: string) => {
       <div class="detail-status">
         <span
           class="font_family"
-          :class="[`icon-${transportationMode?.[data?.transportInfo?.mode]}`]"
+          :class="[`icon-${transportationMode?.[allData?.transportInfo?.mode]}`]"
           style="font-size: 64px"
         ></span>
-        <div class="no">Tracking No. {{ data.transportInfo['Tracking No.'] }}</div>
-        <VTag large type="Confirmed">{{ data.transportInfo?.status }}</VTag>
+        <div class="no">Tracking No. {{ allData.transportInfo['Tracking No.'] }}</div>
+        <VTag large type="Confirmed">{{ allData.transportInfo?.status }}</VTag>
       </div>
       <div class="detail-info">
         <div class="item transport-way">
           <div class="place">
             <div class="title">Origin</div>
             <div class="content">
-              <span>{{ data.transportInfo?.origin }}</span>
+              <span>{{ allData.transportInfo?.origin }}</span>
               <div class="line_container">
                 <hr color="#000000" />
                 <div class="right-icon"></div>
@@ -38,23 +94,23 @@ const formatTime = (time: string) => {
           </div>
           <div class="place">
             <div class="title">Destination</div>
-            <div class="content">{{ data.transportInfo?.destination }}</div>
+            <div class="content">{{ allData.transportInfo?.destination }}</div>
           </div>
         </div>
         <div class="item">
           <div class="title">ETD/ATD</div>
           <div class="content">
-            <span>{{ formatTime(data?.transportInfo?.etd) }} / </span>
+            <span>{{ formatTime(allData?.transportInfo?.etd) }} / </span>
             <span style="color: var(--color-neutral-1)">{{
-              formatTime(data?.transportInfo?.atd)
+              formatTime(allData?.transportInfo?.atd)
             }}</span>
           </div>
         </div>
         <div class="item">
           <div class="title">ETA/ATA</div>
           <div class="content">
-            <span>{{ formatTime(data?.transportInfo?.eta) }} / </span>
-            <span>{{ formatTime(data?.transportInfo?.ata) }}</span>
+            <span>{{ formatTime(allData?.transportInfo?.eta) }} / </span>
+            <span>{{ formatTime(allData?.transportInfo?.ata) }}</span>
           </div>
         </div>
       </div>
@@ -70,14 +126,14 @@ const formatTime = (time: string) => {
           </div>
         </template>
         <template #content>
-          <BasicInformation :data="data" ref="basicInformationRef"></BasicInformation>
+          <BasicInformation :data="allData" ref="basicInformationRef"></BasicInformation>
         </template>
       </VBox>
       <!-- Milestones -->
       <VBox style="margin-top: 16px" :isSeeAll="false" :is-draggable="false">
         <template #header>Milestones</template>
         <template #content>
-          <MilestonesTable :data="data"></MilestonesTable>
+          <MilestonesTable :data="allData"></MilestonesTable>
         </template>
       </VBox>
     </div>

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

@@ -211,7 +211,7 @@ const formatTime = (time: string) => {
       </div>
     </div>
     <div class="transport-map">
-      <MapView></MapView>
+      <MapView :serial_no="allData?.serial_no" :uncode="allData?.uncode"></MapView>
       <TransportStep class="transport-step" :data="allData"></TransportStep>
     </div>
     <div class="info-content">

+ 2 - 18
src/views/Tracking/src/components/TrackingDetail/src/components/AttachmentView.vue

@@ -61,22 +61,6 @@ const handleColumns = (columns: any) => {
         slots: { default: 'file' }
       }
     }
-
-    // 格式化
-    if (item.formatter === 'date') {
-      curColumn = {
-        ...curColumn,
-        sortBy: ({ row, column }: any) => {
-          return dayjs(row[column.field]).unix()
-        },
-        formatter: ({ cellValue }: any) => dayjs(cellValue).format('MMM-YYYY-DD ') || '--'
-      }
-    } else if (item.formatter === 'dateTime') {
-      curColumn = {
-        ...curColumn,
-        formatter: ({ cellValue }: any) => dayjs(cellValue).format('MMM-YYYY-DD HH:mm:ss') || '--'
-      }
-    }
     return curColumn
   })
   return newColumns
@@ -146,9 +130,9 @@ const openUploadFilesDialog = () => {
         <el-button @click="handleDownload(row)" class="el-button--icon">
           <span class="font_family icon-icon_download_b"></span>
         </el-button>
-        <el-button @click="handleDelete(row)" class="el-button--icon">
+        <!-- <el-button @click="handleDelete(row)" class="el-button--icon">
           <span class="font_family icon-icon_delete_b"></span>
-        </el-button>
+        </el-button> -->
       </template>
       <template #file="{ row, column }">
         <div class="file" v-if="row[column.field]?.file_name">

+ 4 - 2
src/views/Tracking/src/components/TrackingDetail/src/components/ContainersView.vue

@@ -41,12 +41,14 @@ const handleColumns = (columns: any) => {
         sortBy: ({ row, column }: any) => {
           return dayjs(row[column.field]).unix()
         },
-        formatter: ({ cellValue }: any) => dayjs(cellValue).format('MMM-YYYY-DD ') || '--'
+        formatter: ({ cellValue }: any) =>
+          cellValue ? dayjs(cellValue).format('MMM-YYYY-DD ') : '--'
       }
     } else if (item.formatter === 'dateTime') {
       curColumn = {
         ...curColumn,
-        formatter: ({ cellValue }: any) => dayjs(cellValue).format('MMM-YYYY-DD HH:mm:ss') || '--'
+        formatter: ({ cellValue }: any) =>
+          cellValue ? dayjs(cellValue).format('MMM-YYYY-DD HH:mm:ss') : '--'
       }
     }
     return curColumn

+ 175 - 29
src/views/Tracking/src/components/TrackingDetail/src/components/MapView.vue

@@ -1,14 +1,55 @@
-<!-- src/components/MapView.vue -->
 <template>
   <div id="map" style="width: 100%; height: 520px"></div>
 </template>
 <script setup lang="ts">
 import L from 'leaflet'
-import Location from '../images/location.png'
+import DestinationIcon from '../images/destinationIcon.png'
+import OriginIcon from '../images/originIcon.png'
+import TransferIcon from '../images/transferIcon.png'
+import { onMounted, ref, watch } from 'vue'
 
+const props = defineProps<{
+  serial_no: string
+  uncoded: string
+}>()
+
+const markerPositions = ref([])
+// 创建自定义图标
+const originIcon = L.icon({
+  iconUrl: OriginIcon,
+  iconSize: [20, 20],
+  iconAnchor: [10, 20],
+  popupAnchor: [0, -8]
+})
+
+const destinationIcon = L.icon({
+  iconUrl: DestinationIcon,
+  iconSize: [20, 20],
+  iconAnchor: [10, 20],
+  popupAnchor: [0, -8]
+})
+
+const transferIcon = L.icon({
+  iconUrl: TransferIcon,
+  iconSize: [20, 20],
+  iconAnchor: [10, 20],
+  popupAnchor: [0, -8]
+})
+
+let map: L.Map | null = null
+
+// 定义响应式的重置缩放中心和级别
+const resetZoomCenter: any = ref([51.505, -0.09]) // 默认中心
+const resetZoomLevel = ref(5) // 默认缩放级别
+
+// 初始化地图(不添加标记)
 const initMap = () => {
-  // 地图初始化
-  const map = L.map('map').setView([51.505, -0.09], 5)
+  if (map) {
+    // 如果地图已经初始化,直接返回
+    return
+  }
+
+  map = L.map('map').setView([51.505, -0.09], 3)
 
   // 添加 TileLayer
   L.tileLayer('https://map.kerryapex.com/osm_tiles/{z}/{x}/{y}.png', {
@@ -16,37 +57,119 @@ const initMap = () => {
       '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
   }).addTo(map)
 
-  const popupOptions = {
-    closeButton: false, // 移除关闭按钮
-    autoClose: false, // 禁止点击其他地方自动关闭
-    closeOnClick: false // 禁止点击地图时自动关闭
-  }
+  // 创建自定义重置缩放按钮控件
+  const ResetZoomControl = L.Control.extend({
+    options: {
+      position: 'topleft'
+    },
+    onAdd: function () {
+      const container = L.DomUtil.create('div', 'reset-zoom-control leaflet-bar')
 
-  const customIcon = L.icon({
-    iconUrl: Location,
-    iconSize: [20, 20], // 图标尺寸
-    iconAnchor: [10, 20], // 图标锚点
-    popupAnchor: [0, -8] // 弹出层位置
+      // 创建重置缩放级别的按钮
+      const resetZoomButton = L.DomUtil.create('a', 'reset-zoom-button leaflet-bar-part', container)
+      resetZoomButton.href = '#'
+      resetZoomButton.title = 'Reset Zoom'
+      resetZoomButton.innerHTML = `<div class="outer-ring" style="height: 100%; padding: 4px;border: 1px solid #2b2f36;border-radius: 50%;">
+        <div class="inner-ring" style=" height: 100%; width: 100%; background-color: #2b2f36; border-radius: 50%;"></div>
+        </div>`
+      resetZoomButton.setAttribute('role', 'button')
+
+      // 在点击时,使用响应式变量控制重置行为
+      resetZoomButton.addEventListener('click', () => {
+        map!.setView(resetZoomCenter.value, resetZoomLevel.value) // 恢复到指定中心和缩放级别
+      })
+
+      return container
+    }
   })
 
-  const marker = L.marker([51.5, -0.09], { icon: customIcon }).addTo(map)
+  // 添加自定义重置缩放按钮控件到地图
+  new ResetZoomControl().addTo(map)
+}
+
+// 根据接口数据在地图上添加标记
+const addMarkersToMap = () => {
+  if (!map) return // 确保地图已经初始化
+  const latLngBounds: any = [] // 用来存储所有标记的坐标
+  markerPositions.value.forEach((position) => {
+    const marker = L.marker([position.lat, position.lng], { icon: position.icon }).addTo(map)
+
+    // 弹出窗口内容
+    const customPopupContent = `
+      <div class="popup-content">
+        <p class="label">${position.label}</p>
+        <p>
+          <span class="font_family icon-icon_location_fill_b" style="color: ${position.iconColor}"></span>
+          <span>${position.city}</span>
+        </p>
+      </div>
+    `
+    marker
+      .bindPopup(customPopupContent, {
+        closeButton: false,
+        autoClose: false,
+        closeOnClick: false
+      })
+      .openPopup()
+    // 将标记的经纬度添加到 latLngBounds 中
+    latLngBounds.push([position.lat, position.lng])
+  })
+  // 如果有标记,则自动调整视图
+  if (latLngBounds.length > 0) {
+    const bounds = L.latLngBounds(latLngBounds)
+    map!.fitBounds(bounds, { paddingTopLeft: [20, 20], paddingBottomRight: [400, 40] }) // 自动缩放并调整视图,padding 可调整视图边距
 
-  const customPopupContent = `
-    <div class="popup-content">
-      <p class="label">origin</p>
-      <p>
-        <span class="font_family icon-icon_location_fill_b" style="color: #ED6D00"></span>
-        <span>ShenZhen,SG</span>
-      </p>
-    </div>
-  `
+    // 更新重置按钮的中心为地图当前的中心
+    // resetZoomCenter.value = bounds.getCenter() // 更新中心点为数据中心
+    // resetZoomLevel.value = map!.getZoom() // 更新缩放级别为当前缩放级别
+  }
+}
 
-  // 绑定弹出框并立即展示
-  marker.bindPopup(customPopupContent, popupOptions).openPopup()
+// 请求接口并处理标记
+const getMarker = () => {
+  $api
+    .getTrackingDetailMapData({
+      serial_no: props.serial_no,
+      uncoded: props.uncoded
+    })
+    .then((res) => {
+      if (res.code === 200) {
+        const { data } = res
+        data.forEach((item) => {
+          const iconColorList = {
+            Destination: { color: '#24ca5a', icon: destinationIcon },
+            Origin: { color: '#ED6D00', icon: originIcon },
+            Transfer: { color: '#ed0000', icon: transferIcon }
+          }
+          markerPositions.value.push({
+            lat: item.lat,
+            lng: item.lng,
+            city: item.infor,
+            label: item.label,
+            icon: iconColorList[item.label].icon,
+            iconColor: iconColorList[item.label].color
+          })
+        })
+        // 接口请求完成后再添加标记
+        addMarkersToMap()
+      }
+    })
 }
 
+// 监听 `serial_no` 变化
+watch(
+  () => props.serial_no,
+  (val) => {
+    if (val) {
+      getMarker()
+    }
+  },
+  { immediate: true }
+)
+
+// 当组件挂载时初始化地图
 onMounted(() => {
-  initMap()
+  initMap() // 初始化地图,不加标记
 })
 </script>
 
@@ -77,10 +200,33 @@ onMounted(() => {
   }
 }
 
-/* 自定义弹出窗口箭头 */
+/* 自定义重置缩放按钮控件样式 */
+.reset-zoom-control {
+  margin-top: 10px; /* 增加上边距,使按钮与默认缩放按钮之间有间距 */
+  border-radius: 4px;
+  background-color: white;
+  border: 1px solid #ccc;
+  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.3);
+}
+
+.reset-zoom-button {
+  background-color: white;
+  border: none;
+  cursor: pointer;
+  padding: 6px;
+}
+
+/* 隐藏默认的弹出窗口箭头 */
 .leaflet-popup-tip {
   display: none;
 }
-.popup-content {
+.transport-map {
+  .leaflet-touch {
+    .leaflet-bar {
+      border: 0;
+      border-radius: 4px;
+      overflow: hidden;
+    }
+  }
 }
 </style>

+ 6 - 10
src/views/Tracking/src/components/TrackingDetail/src/components/MilestonesTable.vue

@@ -71,18 +71,14 @@ const handleColumns = (columns: any) => {
     }
 
     // 格式化
-    if (item.formatter === 'date') {
+    if (item.formatter === 'dateTime') {
       curColumn = {
         ...curColumn,
-        sortBy: ({ row, column }: any) => {
-          return dayjs(row[column.field]).unix()
-        },
-        formatter: ({ cellValue }: any) => dayjs(cellValue).format('MMM-YYYY-DD ') || '--'
-      }
-    } else if (item.formatter === 'dateTime') {
-      curColumn = {
-        ...curColumn,
-        formatter: ({ cellValue }: any) => dayjs(cellValue).format('MMM-YYYY-DD HH:mm:ss') || '--'
+        formatter: ({ cellValue, row }: any) => {
+          return cellValue
+            ? dayjs(cellValue).tz(row.timezone).format('MMM-DD-YYYY hh:mm A (z)')
+            : '--'
+        }
       }
     }
     return curColumn

+ 0 - 0
src/views/Tracking/src/components/TrackingDetail/src/images/location.png → src/views/Tracking/src/components/TrackingDetail/src/images/destinationIcon.png


BIN=BIN
src/views/Tracking/src/components/TrackingDetail/src/images/originIcon.png


BIN=BIN
src/views/Tracking/src/components/TrackingDetail/src/images/transferIcon.png


+ 50 - 6
src/views/Tracking/src/components/TrackingTable/src/TrackingTable.vue

@@ -5,8 +5,11 @@ import { useRowClickStyle } from '@/hooks/rowClickStyle'
 import dayjs from 'dayjs'
 import { useRouter } from 'vue-router'
 import { transportationMode } from '@/components/TransportationMode'
+import { useHeaderSearch } from '@/stores/modules/headerSearch'
+import { fa } from 'element-plus/es/locales.mjs'
 
 const router = useRouter()
+const headerSearch = useHeaderSearch()
 const props = defineProps({
   height: {
     type: Number,
@@ -51,12 +54,14 @@ const handleColumns = (columns: any, status?: string) => {
         sortBy: ({ row, column }: any) => {
           return dayjs(row[column.field]).unix()
         },
-        formatter: ({ cellValue }: any) => dayjs(cellValue).format('MMM-YYYY-DD ') || '--'
+        formatter: ({ cellValue }: any) =>
+          cellValue ? dayjs(cellValue).format('MMM-YYYY-DD ') : '--'
       }
     } else if (item.formatter === 'dateTime') {
       curColumn = {
         ...curColumn,
-        formatter: ({ cellValue }: any) => dayjs(cellValue).format('MMM-YYYY-DD HH:mm:ss') || '--'
+        formatter: ({ cellValue }: any) =>
+          cellValue ? dayjs(cellValue).format('MMM-YYYY-DD HH:mm:ss') : '--'
       }
     }
     return curColumn
@@ -88,7 +93,32 @@ const TagsList = ref()
 
 // 获取表格数据
 let filterdataobj: any = {}
+const getSharedTableData = () => {
+  const trackingData = JSON.parse(localStorage.getItem('TrackingData'))
+  if (trackingData) {
+    trackingTable.value.data = trackingData.searchData
+    pageInfo.value.total = Number(trackingData.rc)
+    TransportListItem.value = trackingData.TransportList
+    TagsList.value = trackingData.tagsList
+
+    // 拥有所有字段的表格
+    setTimeout(() => {
+      allTable.value.columns = handleColumns(trackingData.allColums, 'all')
+      allTable.value.data = trackingData.searchData
+      nextTick(() => {
+        allTableRef.value && autoWidth(allTable.value, allTableRef.value)
+      })
+    }, 1000)
+    localStorage.removeItem('TrackingData')
+    return true
+  }
+  return false
+}
+
 const getTableData = async (isInit: boolean, isPageChange?: boolean) => {
+  if (getSharedTableData()) {
+    return
+  }
   const rc = isPageChange ? pageInfo.value.total : -1
   tableLoading.value = true
   await $api
@@ -120,10 +150,21 @@ const getTableData = async (isInit: boolean, isPageChange?: boolean) => {
     !isInit && (tableLoading.value = false)
   })
 }
+
+// 当 sharedData 发生变化时,更新 inputValue
+watch(
+  () => headerSearch.isChangeByLogin,
+  (newData) => {
+    if (newData) {
+      getSharedTableData()
+      headerSearch.clearChangeByLogin()
+    }
+  }
+)
+
 // 查询列表数据
 const searchTableData = (data: any) => {
   tableLoading.value = true
-  console.log(data)
   filterdataobj = data
   $api
     .getTrackingTableData({
@@ -139,7 +180,10 @@ const searchTableData = (data: any) => {
         if (res.data.searchData.length == 1) {
           router.push({
             path: '/tracking/detail',
-            query: { a: res.data.searchData.__serial_no, _schemas: res.data.searchData.__schemas }
+            query: {
+              a: res.data.searchData[0].__serial_no,
+              _schemas: res.data.searchData[0].__schemas
+            }
           })
         } else {
           trackingTable.value.data = res.data.searchData
@@ -279,7 +323,7 @@ const handleLinkClick = (row: any, column: any) => {
   } else if (column.field === 'h_bol') {
     router.push({
       path: '/tracking/detail',
-      query: { a: row.__serial_no, _schemas: row._schemas }
+      query: { a: row.__serial_no, _schemas: row._schemas, serial_no: row.serial_no }
     })
   }
 }
@@ -377,7 +421,7 @@ defineExpose({
     </vxe-grid>
     <vxe-grid :height="10" ref="allTableRef" class="all-table" v-bind="allTable"> </vxe-grid>
     <div class="bottom-pagination">
-      <div class="left-total-records">Total 181</div>
+      <div class="left-total-records">Total {{ pageInfo.total }}</div>
       <div class="right-pagination">
         <el-pagination
           v-model:current-page="pageInfo.pageNo"

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

@@ -156,12 +156,14 @@ const handleColumns = (columns: any) => {
         sortBy: ({ row, column }: any) => {
           return dayjs(row[column.field]).unix()
         },
-        formatter: ({ cellValue }: any) => dayjs(cellValue).format('MMM-YYYY-DD ') || '--'
+        formatter: ({ cellValue }: any) =>
+          cellValue ? dayjs(cellValue).format('MMM-YYYY-DD ') : '--'
       }
     } else if (item.formatter === 'dateTime') {
       curColumn = {
         ...curColumn,
-        formatter: ({ cellValue }: any) => dayjs(cellValue).format('MMM-YYYY-DD HH:mm:ss') || '--'
+        formatter: ({ cellValue }: any) =>
+          cellValue ? dayjs(cellValue).format('MMM-YYYY-DD HH:mm:ss') : '--'
       }
     }
     return curColumn