瀏覽代碼

feat: 实现Tracking详情页地图以及路由跳转逻辑

zhouyuhao 1 年之前
父節點
當前提交
b2f9cf22f5

+ 1 - 0
package.json

@@ -40,6 +40,7 @@
   "devDependencies": {
     "@rushstack/eslint-patch": "^1.8.0",
     "@tsconfig/node20": "^20.1.4",
+    "@types/leaflet": "^1.9.12",
     "@types/lodash": "^4.17.7",
     "@types/node": "^20.14.5",
     "@vitejs/plugin-vue": "^5.0.5",

+ 1 - 2
src/App.vue

@@ -1,6 +1,5 @@
 <script setup lang="ts">
-import { RouterLink, RouterView } from 'vue-router'
-import HelloWorld from './components/HelloWorld.vue'
+import { RouterView } from 'vue-router'
 </script>
 
 <template>

+ 1 - 1
src/components/VBox/src/VBox.vue

@@ -74,7 +74,7 @@ const vBoxPopoverRef = ref()
             </div>
           </div>
           <template #reference v-if="props.isDraggable">
-            <el-button type="text" class="sort handle-draggable">
+            <el-button class="sort handle-draggable el-button--text">
               <span class="font_family icon-icon_dragsort__b" style="font-size: 16px"></span>
             </el-button>
           </template>

+ 6 - 3
src/components/VLoading/src/VLoading.vue

@@ -8,7 +8,7 @@
 interface internalProps {
   target?: string
   loading: boolean
-  isLoadingBackground: boolean
+  isLoadingBackground?: boolean
 }
 
 const props = withDefaults(defineProps<internalProps>(), {
@@ -18,8 +18,11 @@ const props = withDefaults(defineProps<internalProps>(), {
 </script>
 <template>
   <Teleport :to="props.target">
-    <div v-if="props.loading" class="v-loading-mask"
-      :style="{ background: props.isLoadingBackground ? 'none' : '#2B2F36' }">
+    <div
+      v-if="props.loading"
+      class="v-loading-mask"
+      :style="{ background: props.isLoadingBackground ? 'none' : '#2B2F36' }"
+    >
       <div class="v-loading-spinner">
         <div>
           <img class="circular" src="./images/icon_loading.png" alt="" />

+ 1 - 1
src/components/transportationMode.ts

@@ -1,4 +1,4 @@
-export const transportationMode = {
+export const transportationMode: any = {
   'Ocean Freight': 'icon_ocean_b',
   'Air Freight': 'icon_airplane_b',
   'Road Freight': 'icon_truck_b',

+ 0 - 1
src/router/index.ts

@@ -85,7 +85,6 @@ router.beforeEach(async (to, from, next) => {
   if (!whiteList.includes(to.path) && !localStorage.getItem('token')) {
     if (whiteList.includes(from.path)) {
       ElMessage.warning('Please log in to use this feature.')
-      console.log('跳转')
       next(false)
       return
     } else {

+ 38 - 1
src/styles/elementui.scss

@@ -250,7 +250,6 @@ div.el-message--error {
   --el-message-border-color: #faebec;
   --el-message-text-color: var(--color-danger);
 }
-
 div.el-message--success {
   --el-message-bg-color: #e5f6f1;
   --el-message-border-color: #e5f6f1;
@@ -263,6 +262,44 @@ div.el-message--warning {
   --el-message-text-color: #f19d38;
 }
 
+// message box
+div.el-message-box {
+  --el-messagebox-width: 640px;
+  --el-messagebox-border-radius: 12px;
+  padding: 0;
+  .el-message-box__header {
+    height: 56px;
+    padding: 16px;
+    background-color: var(--color-table-header-bg);
+    & > span {
+      font-weight: 700;
+      font-size: var(--font-size-4);
+    }
+  }
+  .el-message-box__container {
+    padding: 24px 16px;
+    gap: 6px;
+  }
+  .el-message-box__headerbtn {
+    height: 56px;
+  }
+  .el-message-box__status {
+    font-size: 16px;
+  }
+  .el-message-box__title {
+    font-weight: 700;
+    font-size: var(--font-size-4);
+  }
+  .el-message-box__message {
+    font-size: var(--font-size-3);
+  }
+  .el-message-box__btns {
+    padding: 8px;
+    padding-right: 16px;
+    border-top: 1px solid var(--color-border);
+  }
+}
+
 // drawer抽屉
 div.el-drawer {
   .el-drawer__header {

+ 11 - 3
src/utils/axios.ts

@@ -1,4 +1,5 @@
 import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse } from 'axios'
+import router from '@/router'
 import { ElMessage, ElMessageBox } from 'element-plus'
 // import {
 //   showFullScreenLoading,
@@ -46,14 +47,21 @@ class HttpAxios {
     const _config = { timeout: this.timeout }
     return { ...config, ..._config }
   }
-
+  /**
+   * 返回拦截
+   * @param response
+   * @returns
+   */
   _responseInterceptors = (response: AxiosResponse) => {
     if (response.status === 200) {
-      if (response.data.code !== 200) {
+      if (response.data.code === 401 || response.data.code === 403) {
+        router.push('/login')
+        ElMessage.warning('Please log in to use this feature.')
+      } else if (response.data.code !== 200 && response.data.code !== 400) {
         ElMessageBox.alert(
           response.data?.data?.msg || 'The request failed. Please try again later',
           'Prompt',
-          { confirmButtonText: 'OK' }
+          { confirmButtonText: 'OK', confirmButtonClass: 'el-button--dark' }
         )
       }
       return response.data

+ 5 - 2
src/views/Booking/src/components/BookingDetail/src/components/BasicInformation.vue

@@ -262,7 +262,10 @@ watch(
 
 // 跳转到shipment页面
 const handLink = (id: string) => {
-  router.push({ path: '/tracking', query: { id } })
+  router.push({
+    path: '/tracking',
+    query: { a: props?.data?.__serial_no, _schemas: props?.data?._schemas }
+  })
 }
 
 const handleCopy = (data: any) => {
@@ -299,7 +302,7 @@ defineExpose({
           <!-- <el-button
             style="font-size: 12px"
             v-if="item.label === 'Ref No.'"
-            type="text"
+            class="el-button-text"
             @click="addReference"
           >
             <span class="font_family icon-icon_add_b"></span>

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

@@ -72,7 +72,8 @@ const getTableColumns = async (isInit: boolean) => {
     if (res.code === 200) {
       bookingTable.value.columns = [
         { type: 'checkbox', width: 50, fixed: 'left' },
-        ...handleColumns(res.data.BookingTableColumns)
+        ...handleColumns(res.data.BookingTableColumns),
+        { field: '__serial_no', width: 300, fixed: 'left' }
       ]
       tableOriginColumnsField.value = res.data.BookingTableColumns
     }
@@ -225,6 +226,8 @@ const allTableRef = ref<VxeGridInstance>()
 const allTable = ref<VxeGridProps<any>>({
   columns: [],
   data: [],
+  showHeaderOverflow: true,
+  showOverflow: true,
   scrollY: { enabled: true, oSize: 5, gt: 2 },
   scrollX: { enabled: true, gt: 2 },
   exportConfig: {
@@ -298,7 +301,7 @@ const customizeColumns = async () => {
 const handleCellDblclick = ({ row }: any) => {
   router.push({
     path: '/booking/detail',
-    query: { id: row.booking_no }
+    query: { a: row.__serial_no, _schemas: row.__schemas }
   })
 }
 // 点击link字段是时
@@ -306,12 +309,12 @@ const handleLinkClick = (row: any, column: any) => {
   if (column.field === 'booking_no') {
     router.push({
       path: '/booking/detail',
-      query: { id: row.booking_no }
+      query: { a: row.__serial_no, _schemas: row.__schemas }
     })
   } else if (column.field === 'h_bol') {
     router.push({
       path: '/tracking/detail',
-      query: { id: row.h_bol }
+      query: { a: row.__serial_no, _schemas: row.__schemas }
     })
   }
 }

+ 39 - 46
src/views/Layout/src/components/Menu/MenuView.vue

@@ -67,39 +67,36 @@ const menuList = [
   }
 ]
 
-const activeMenu = ref()
-activeMenu.value = (route.meta?.activeMenu as string) || route.path
-// console.log('activeMenu', activeMenu.value)
+//监听窗口大小
+const handler = () => {
+  return (() => {
+    let screenWidth = document.body.clientWidth
+    let screenHeight = document.body.clientHeight
+    if (screenWidth < 1400) {
+      isCollapse.value = true
+    } else {
+      isCollapse.value = false
+    }
+  })()
+}
+const listeningWindow = () => {
+  window.addEventListener('resize', handler)
+}
+
+const unListeningWindow = () => {
+  window.removeEventListener('resize', handler)
+}
+
+watchEffect(() => {
+  listeningWindow()
+})
 
-// // 未登录白名单
-// const whiteList = ['/login', '/public-tracking', '/public-tracking/detail']
-// // 判断是否允许跳转
-// const isAllowJump = (path: string) => {
-//   // 判断是否登录
-//   if (!whiteList.includes(path) && !localStorage.getItem('token')) {
-//     if (whiteList.includes(route.path)) {
-//       ElMessage.warning('Please log in to use this feature.')
-//       console.log('跳转')
-//       activeMenu.value = (route.meta?.activeMenu as string) || route.path
-//       return false
-//     } else {
-//       ElMessage.warning('Please log in to use this feature.')
-//       return '/public-tracking'
-//     }
-//   } else {
-//     return path
-//   }
-// }
+onUnmounted(() => {
+  unListeningWindow()
+})
 
-// const changeRouter = (path: string) => {
-//   let toPath = path
-//   if (path === '/tracking' && !localStorage.getItem('token')) {
-//     toPath = '/public-tracking'
-//   }
-//   if (isAllowJump(toPath)) {
-//     router.push(isAllowJump(toPath))
-//   }
-// }
+const activeMenu = ref()
+activeMenu.value = (route.meta?.activeMenu as string) || route.path
 
 const getAllMenuPaths = (menuList: any) => {
   let paths: any = []
@@ -129,8 +126,7 @@ const isAllowJump = (path: any) => {
   return true
 }
 // 路由后置守卫
-router.afterEach((to, from) => {
-  console.log('路由后置')
+router.afterEach(() => {
   activeMenu.value = (route.meta?.activeMenu as string) || route.path
 })
 
@@ -144,9 +140,6 @@ const changeRouter = (path: any) => {
   // 如果允许跳转,执行跳转
   if (isAllowJump(toPath)) {
     router.push(toPath)
-    console.log('允许跳转')
-
-    console.log('nextTick', activeMenu.value)
   } else {
     // 如果不允许跳转,保持当前 activeMenu 不变
     nextTick(() => {
@@ -155,22 +148,13 @@ const changeRouter = (path: any) => {
   }
 }
 
-// 计算属性,返回当前菜单项
-const computedActiveMenu = computed(() =>
-  menuPaths.includes(activeMenu.value) ? activeMenu.value : ''
-)
-
 const handleCollapseClick = () => {
   isCollapse.value = !isCollapse.value
 }
 const menuRef = ref()
-const test = () => {
-  console.log('test', activeMenu.value)
-}
 </script>
 
 <template>
-  <el-button @click="test">测试</el-button>
   <el-menu
     ref="menuRef"
     class="layout-menu"
@@ -179,7 +163,11 @@ const test = () => {
     :collapse="isCollapse"
   >
     <template v-for="item in menuList" :key="item.index">
-      <el-menu-item v-if="item.type !== 'list'" :index="item.path">
+      <el-menu-item
+        :class="{ 'clear-active-style': route.path === '/login' }"
+        v-if="item.type !== 'list'"
+        :index="item.path"
+      >
         <span class="font_family" :class="[`icon-${item.icon}`]"></span>
         <template #title>{{ item.label }}</template>
       </el-menu-item>
@@ -246,6 +234,11 @@ const test = () => {
     padding-left: 8px;
   }
 }
+li.clear-active-style {
+  background-color: transparent !important;
+  color: var(--color-neutral-1) !important;
+  font-weight: normal !important;
+}
 
 :deep(.el-sub-menu__title) {
   height: 40px;

+ 21 - 5
src/views/Login/src/loginView.vue

@@ -1,6 +1,8 @@
 <script setup lang="ts">
+import { useRouter } from 'vue-router'
 import ErrorTips from './components/ErrorTips.vue'
 
+const router = useRouter()
 const loginForm = ref({
   username: 'ra.admin',
   password: 'abc123456789',
@@ -36,8 +38,11 @@ const verificationCode = ref()
 // 获取验证码
 const getCode = () => {
   $api.getVerifcationCode().then((res: any) => {
-    verificationCode.value = `data:image/png;base64,${res.data.imagePngBase64}`
-    console.log(res)
+    if (res.code === 200) {
+      verificationCode.value = `data:image/png;base64,${res.data.imagePngBase64}`
+    } else {
+      verificationCode.value = ''
+    }
   })
 }
 getCode()
@@ -72,13 +77,13 @@ const handleLogin = () => {
       verifcation_code: loginForm.value.code
     })
     .then((res: any) => {
-      console.log(res)
       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'
+            type: 'warning',
+            confirmButtonClass: 'el-button--dark'
           })
         } else if (data.msg === 'last') {
           ElMessageBox.alert(
@@ -86,10 +91,21 @@ const handleLogin = () => {
             'Prompt',
             {
               confirmButtonText: 'OK',
-              type: 'warning'
+              type: 'warning',
+              confirmButtonClass: 'el-button--dark'
             }
           )
         }
+        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()
+        }
       }
     })
     .finally(() => {

+ 0 - 24
src/views/Tracking/src/components/PublicTracking/src/components/PublicTrackingDetail.vue

@@ -1,26 +1,6 @@
 <script setup lang="ts">
 import BasicInformation from './BasicInformation.vue'
 import MilestonesTable from './MilestonesTable.vue'
-
-const test = (type: string) => {
-  ElMessageBox.confirm('proxy will permanently delete the file. Continue?', 'Warning', {
-    confirmButtonText: 'OK',
-    cancelButtonText: 'Cancel',
-    type: 'warning'
-  })
-    .then(() => {
-      ElMessage({
-        type: 'success',
-        message: 'Delete completed'
-      })
-    })
-    .catch(() => {
-      ElMessage({
-        type: 'info',
-        message: 'Delete canceled'
-      })
-    })
-}
 </script>
 
 <template>
@@ -86,10 +66,6 @@ const test = (type: string) => {
         </template>
       </VBox>
     </div>
-    <el-button @click="test('error')">error</el-button>
-    <el-button @click="test('success')">success</el-button>
-    <el-button @click="test('info')">info</el-button>
-    <el-button @click="test('warning')">warning</el-button>
   </div>
 </template>
 

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

@@ -95,7 +95,7 @@ defineExpose({
 <template>
   <el-dialog v-model="dialogVisible" :width="800" title="Ref No." @closed="clearData">
     <div>
-      <el-button style="margin-bottom: 8px" type="text" @click="addNewReference">
+      <el-button style="margin-bottom: 8px" class="el-button-text" @click="addNewReference">
         <span class="font_family icon-icon_add_b"></span>
         Add Reference
       </el-button>

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

@@ -299,7 +299,7 @@ defineExpose({
           <!-- <el-button
             style="font-size: 12px"
             v-if="item.label === 'Ref No.'"
-            type="text"
+            class="el-button--text"
             @click="addReference"
           >
             <span class="font_family icon-icon_add_b"></span>

+ 73 - 21
src/views/Tracking/src/components/TrackingDetail/src/components/MapView.vue

@@ -2,31 +2,83 @@
 <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'
 
-<script>
-import L from 'leaflet' // 导入 Leaflet
-
-export default {
-  name: 'MapView',
-  mounted() {
-    // 地图初始化
-    const map = L.map('map').setView([51.505, -0.09], 3)
-
-    // 添加 TileLayer
-    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
-      attribution:
-        '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
-    }).addTo(map)
-
-    // 添加 marker
-    L.marker([51.5, -0.09])
-      .addTo(map)
-      .bindPopup('A pretty CSS3 popup.<br> Easily customizable.')
-      .openPopup()
+const initMap = () => {
+  // 地图初始化
+  const map = L.map('map').setView([51.505, -0.09], 3)
+
+  // 添加 TileLayer
+  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
+    attribution:
+      '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
+  }).addTo(map)
+
+  const popupOptions = {
+    closeButton: false, // 移除关闭按钮
+    autoClose: false, // 禁止点击其他地方自动关闭
+    closeOnClick: false // 禁止点击地图时自动关闭
   }
+
+  const customIcon = L.icon({
+    iconUrl: Location,
+    iconSize: [20, 20], // 图标尺寸
+    iconAnchor: [10, 20], // 图标锚点
+    popupAnchor: [0, -8] // 弹出层位置
+  })
+
+  const marker = L.marker([51.5, -0.09], { icon: customIcon }).addTo(map)
+
+  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>
+        ShenZhen,SG
+      </p>
+    </div>
+  `
+
+  // 绑定弹出框并立即展示
+  marker.bindPopup(customPopupContent, popupOptions).openPopup()
 }
+
+onMounted(() => {
+  initMap()
+})
 </script>
 
-<style>
+<style lang="scss">
 @import 'leaflet/dist/leaflet.css';
+.leaflet-popup-content-wrapper {
+  border-radius: 6px;
+}
+.leaflet-popup-content {
+  padding: 4px;
+  margin: 0;
+  .popup-content {
+    p {
+      margin: 0;
+      span {
+        margin-left: -2px;
+        font-size: 12px;
+        font-weight: 700;
+      }
+    }
+    .label {
+      margin-bottom: 4px;
+      font-size: 12px;
+      color: var(--color-neutral-2);
+    }
+  }
+}
+
+/* 自定义弹出窗口箭头 */
+.leaflet-popup-tip {
+  display: none;
+}
+.popup-content {
+}
 </style>

二進制
src/views/Tracking/src/components/TrackingDetail/src/images/location.png


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

@@ -238,7 +238,7 @@ const customizeColumns = async () => {
 const handleCellDblclick = ({ row }: any) => {
   router.push({
     path: '/tracking/detail',
-    query: { id: row.h_bol }
+    query: { a: row.__serial_no, _schemas: row.__schemas }
   })
 }
 // 点击link字段是时
@@ -246,12 +246,12 @@ const handleLinkClick = (row: any, column: any) => {
   if (column.field === 'booking_no') {
     router.push({
       path: '/booking/detail',
-      query: { id: row.booking_no }
+      query: { a: row.__serial_no, _schemas: row.__schemas }
     })
   } else if (column.field === 'h_bol') {
     router.push({
       path: '/tracking/detail',
-      query: { id: row.h_bol }
+      query: { a: row.__serial_no, _schemas: row.__schemas }
     })
   }
 }