Ver Fonte

feat: 实现Tracking详情页地图部分样式

zhouyuhao há 1 ano atrás
pai
commit
22b5533c98

+ 1 - 0
package.json

@@ -23,6 +23,7 @@
     "element-plus": "^2.8.1",
     "exceljs": "^4.4.0",
     "js-md5": "^0.8.3",
+    "leaflet": "^1.9.4",
     "lodash": "^4.17.21",
     "mitt": "^3.0.1",
     "moment": "^2.30.1",

+ 119 - 79
src/components/ContainerStatus/src/ContainerStatus.vue

@@ -5,38 +5,12 @@ const props = defineProps({
   data: Object
 })
 
-const containerStatusData: any = ref([
-  {
-    title: 'Empty Equipment Dispatched',
-    date: 'May-31-2024 10:03(GMT+02)',
-    country: 'CN,SHK'
-  },
-  {
-    title: 'In-Gate',
-    date: 'May-31-2024 10:03(GMT+02)',
-    country: 'CN,SHK'
-  },
-  {
-    title: 'Loaded On Vessel',
-    date: 'Jun-05-2024 10:03(GMT+02)',
-    country: 'CN,SHK'
-  },
-  {
-    title: 'Vessel Departure',
-    date: 'Jun-06-2024 10:03(GMT+02)',
-    country: 'CN,SHK'
-  },
-  {
-    title: 'Vessel Arrival',
-    date: 'Jun-07-2024 14:00(GMT+02)',
-    country: 'ES,BCN'
-  }
-])
+const containerStatusData: any = ref([])
 watch(
   () => props.data,
   (newVal) => {
     if (newVal) {
-      containerStatusData.value = newVal[0].content
+      containerStatusData.value = newVal
     } else {
       containerStatusData.value = []
     }
@@ -54,72 +28,138 @@ const formatDate = (date: string) => {
 
 <template>
   <div class="container-status">
-    <div class="step-item" v-for="(item, index) in containerStatusData" :key="item.title">
-      <div class="step-data">
-        <div class="step-dot-icon"></div>
-        <div class="info">
-          <div class="left-info">
-            <div class="title">{{ item.title }}</div>
-            <div class="date">{{ formatDate(item.date) }}</div>
+    <el-scrollbar style="height: 394px">
+      <el-collapse class="container" v-model="activeNames" @change="handleChange">
+        <el-collapse-item
+          :title="containers.label"
+          v-for="(containers, name) in containerStatusData"
+          :key="name"
+          :name="name"
+        >
+          <template #title>
+            <div class="title">
+              Container <span>{{ containers.label }}</span>
+            </div>
+          </template>
+          <div class="step-item" v-for="(item, index) in containers.content" :key="item.title">
+            <div class="step-data">
+              <div class="step-dot-icon"></div>
+              <div class="info">
+                <div class="left-info">
+                  <div class="title">{{ item.title }}</div>
+                  <div class="date">{{ formatDate(item.date) }}</div>
+                </div>
+                <div class="right-country">{{ item.country }}</div>
+              </div>
+            </div>
+            <div class="line" v-if="index + 1 !== containers.content.length"></div>
           </div>
-          <div class="right-country">{{ item.country }}</div>
-        </div>
-      </div>
-      <div class="line" v-if="index + 1 !== containerStatusData.length"></div>
+        </el-collapse-item>
+      </el-collapse>
+    </el-scrollbar>
+    <div class="footer">
+      Tracking on carrier website:
+      <a href="http://www.rcjgroup.com/" target="_blank" class="link">http://www.rcjgroup.com/</a>
     </div>
   </div>
 </template>
 
 <style lang="scss" scoped>
 .container-status {
+  position: relative;
   width: 100%;
-  padding: 22px 0 22px;
+  height: 100%;
+  overflow: auto;
+  .container {
+    // height: 394px;
+    padding-bottom: 8px;
+    // overflow: auto;
+  }
+  .footer {
+    line-height: 38px;
+    color: #999;
+    font-size: 12px;
+    text-align: center;
+    border-top: 1px solid var(--color-border);
+    overflow: hidden;
+    .link {
+      text-decoration: none;
+      color: var(--color-theme);
+    }
+  }
 }
-
-.step-data {
-  display: flex;
-  gap: 8px;
-  height: 8px;
-  width: 100%;
-  .step-dot-icon {
-    height: 8px;
-    width: 8px;
-    background-color: var(--color-neutral-1);
-    border-radius: 50%;
+</style>
+<style lang="scss">
+.container-status {
+  .el-collapse {
+    height: auto;
+    border-bottom: 0;
+  }
+  .el-collapse-item__content {
+    padding: 30px 16px 38px;
+    border-top: 1px solid var(--color-border);
+  }
+  .el-collapse-item__header {
+    height: 40px;
+    padding-left: 16px;
+    padding-right: 8px;
+    line-height: 50px;
+    & > .title {
+      font-size: 16px;
+      span {
+        margin-left: 4px;
+        font-weight: 700;
+        color: var(--color-neutral-1);
+      }
+    }
   }
-  .info {
-    flex: 1;
+  .step-data {
     display: flex;
-    height: 52px;
-    margin-top: -22px;
-    background-color: var(--color-table-header-bg);
-    border: 1px solid var(--color-border);
-    border-radius: 6px;
-    color: var(--color-neutral-1);
-    .left-info {
+    gap: 8px;
+    height: 8px;
+    width: 100%;
+    .step-dot-icon {
+      height: 8px;
+      width: 8px;
+      background-color: var(--color-neutral-1);
+      border-radius: 50%;
+    }
+    .info {
       flex: 1;
-      padding: 8px 10px;
-      border-right: 1px solid var(--color-border);
-      .title {
-        font-size: 14px;
-        font-weight: 700;
+      display: flex;
+      height: 52px;
+      margin-top: -22px;
+      background-color: var(--color-table-header-bg);
+      border: 1px solid var(--color-border);
+      border-radius: 6px;
+      color: var(--color-neutral-1);
+      .left-info {
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+        padding: 8px 10px;
+        border-right: 1px solid var(--color-border);
+        .title {
+          font-size: 14px;
+          line-height: 18px;
+          font-weight: 700;
+        }
+        .date {
+          font-size: 12px;
+        }
       }
-      .date {
-        margin-top: 4px;
-        font-size: 12px;
+      .right-country {
+        padding: 0 24px;
+        line-height: 52px;
+        font-weight: 700;
       }
     }
-    .right-country {
-      padding: 0 24px;
-      line-height: 52px;
-      font-weight: 700;
-    }
   }
-}
-.line {
-  height: 60px;
-  width: 0;
-  margin-left: 3px;
-  border-left: 1px solid var(--color-neutral-1);
+  .line {
+    height: 60px;
+    width: 0;
+    margin-left: 3px;
+    border-left: 1px solid var(--color-neutral-1);
+  }
 }
 </style>

+ 16 - 3
src/components/CustomizeColumns/src/CustomizeColumns.vue

@@ -456,9 +456,22 @@ defineExpose({
       </div>
     </div>
     <template #footer>
-      <el-button type="default" @click="dialogVisible = false">Cancel</el-button>
-      <el-button type="default" @click="handleReset">Reset to default</el-button>
-      <el-button class="el-button--dark" @click="handleApply"> Apply </el-button>
+      <el-button
+        type="default"
+        style="height: 40px; padding: 8px 40px"
+        @click="dialogVisible = false"
+        >Cancel</el-button
+      >
+      <el-button type="default" style="height: 40px; padding: 8px 20px" @click="handleReset"
+        >Reset to default</el-button
+      >
+      <el-button
+        class="el-button--dark"
+        style="height: 40px; padding: 8px 40px"
+        @click="handleApply"
+      >
+        Apply
+      </el-button>
     </template>
     <el-tour
       :target-area-clickable="false"

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

@@ -81,7 +81,7 @@ const formatDate = (date: string) => {
 // 单式样式
 .simplex-content {
   width: 100%;
-  padding: 8px 0 9px;
+  padding: 24px 8px 9px 16px;
 }
 .detail-step-item {
   & > .data {

+ 1 - 11
src/components/VBreadcrumd/src/VBreadcrumd.vue

@@ -21,17 +21,7 @@ const path = computed(() => {
   // 如果没有找到或者不符合条件,则返回null
   return null
 })
-watch(
-  () => path.value,
-  (newVal) => {
-    if (newVal) {
-      console.log('pathName:', newVal)
-    }
-  },
-  {
-    immediate: true
-  }
-)
+
 const mapPathName = {
   booking: 'Booking',
   tracking: 'Tracking',

+ 6 - 0
src/components/transportationMode.ts

@@ -0,0 +1,6 @@
+export const transportationMode = {
+  'Ocean Freight': 'icon_ocean_b',
+  'Air Freight': 'icon_airplane_b',
+  'Road Freight': 'icon_truck_b',
+  'Rail Freight': 'icon_railway_b'
+}

+ 7 - 1
src/views/Booking/src/components/BookingDetail/src/BookingDetail.vue

@@ -5,6 +5,7 @@ import BasicInformation from './components/BasicInformation.vue'
 import ContainersView from './components/ContainersView.vue'
 import EmailView from './components/EmailView.vue'
 import { cloneDeep } from 'lodash'
+import { transportationMode } from '@/components/TransportationMode'
 
 // 可拖拽模块的列表
 const boxList = ref([
@@ -91,7 +92,11 @@ const formatTime = (time: string) => {
   <div class="booking-detail" v-vloading="loading">
     <div class="header">
       <div class="detail-status">
-        <span class="font_family icon-icon_ocean_b" style="font-size: 64px"></span>
+        <span
+          class="font_family"
+          :class="[`icon-${transportationMode?.[allData?.transportInfo?.mode]}`]"
+          style="font-size: 64px"
+        ></span>
         <div class="no">Booking No. {{ allData?.transportInfo?.['bookingNo.'] }}</div>
         <VTag large type="Confirmed">{{ allData?.transportInfo?.status }}</VTag>
       </div>
@@ -210,6 +215,7 @@ const formatTime = (time: string) => {
       position: relative;
       display: flex;
       align-items: center;
+      height: 64px;
       padding: 0 16px;
       border-bottom: 1px solid var(--color-border);
       .no {

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

@@ -1,4 +1,5 @@
 <script setup lang="ts">
+import { transportationMode } from '@/components/TransportationMode'
 import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
 import DownloadDialog from './components/DownloadDialog.vue'
 import { autoWidth } from '@/utils/table'
@@ -371,9 +372,13 @@ defineExpose({
         </VEmpty>
       </template>
       <!-- Transportation Mode字段的插槽 -->
-      <template #mode>
+      <template #mode="{ row, column }">
         <div>
-          <span class="font_family icon-icon_ocean_b" style="font-size: 24px"></span>
+          <span
+            class="font_family"
+            :class="[`icon-${transportationMode?.[row[column.field]]}`]"
+            style="font-size: 24px"
+          ></span>
         </div>
       </template>
       <!-- Status字段的插槽 -->

+ 1 - 1
src/views/Layout/src/LayoutView.vue

@@ -69,7 +69,7 @@ const handleMenuCollapse = (val: boolean) => {
   .layout-content {
     padding: 0;
     & > .el-scrollbar {
-      :deep(.el-scrollbar__view) {
+      :deep(& > .el-scrollbar__view) {
         height: 100%;
       }
       :deep(.el-scrollbar__wrap--hidden-default) {

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

@@ -193,9 +193,6 @@ const test = (type: string) => {
       }
     }
   }
-  .transport-map {
-    margin: 20px 0;
-  }
 }
 
 .info-content {

+ 18 - 3
src/views/Tracking/src/components/TrackingDetail/src/TrackingDetail.vue

@@ -9,7 +9,9 @@ import TransportStep from './components/TransportStep.vue'
 import MilestonesTable from './components/MilestonesTable.vue'
 import RoutesView from './components/RoutesView.vue'
 import AttachmentView from './components/AttachmentView.vue'
+import MapView from './components/MapView.vue'
 import { cloneDeep } from 'lodash'
+import { transportationMode } from '@/components/TransportationMode'
 
 // 可拖拽模块的列表
 const boxList = ref([
@@ -109,7 +111,11 @@ const formatTime = (time: string) => {
   <div class="tracking-detail" v-vloading="loading">
     <div class="header">
       <div class="detail-status">
-        <span class="font_family icon-icon_ocean_b" style="font-size: 64px"></span>
+        <span
+          class="font_family"
+          :class="[`icon-${transportationMode?.[allData?.transportInfo?.mode]}`]"
+          style="font-size: 64px"
+        ></span>
         <div class="no">Tracking No. {{ allData?.transportInfo?.['Tracking No.'] }}</div>
         <VTag large type="Confirmed">{{ allData?.transportInfo?.status }}</VTag>
         <div class="right-operation">
@@ -159,7 +165,8 @@ const formatTime = (time: string) => {
       </div>
     </div>
     <div class="transport-map">
-      <TransportStep :data="allData"></TransportStep>
+      <MapView></MapView>
+      <TransportStep class="transport-step" :data="allData"></TransportStep>
     </div>
     <div class="info-content">
       <VueDraggable
@@ -270,6 +277,7 @@ const formatTime = (time: string) => {
       display: flex;
       align-items: center;
       padding: 0 16px;
+      height: 64px;
       border-bottom: 1px solid var(--color-border);
 
       .right-operation {
@@ -360,7 +368,14 @@ const formatTime = (time: string) => {
     }
   }
   .transport-map {
-    margin: 20px 0;
+    position: relative;
+    margin-bottom: 8px;
+    .transport-step {
+      position: absolute;
+      top: 8px;
+      right: 16px;
+      z-index: 1000;
+    }
   }
 }
 

+ 32 - 0
src/views/Tracking/src/components/TrackingDetail/src/components/MapView.vue

@@ -0,0 +1,32 @@
+<!-- src/components/MapView.vue -->
+<template>
+  <div id="map" style="width: 100%; height: 520px"></div>
+</template>
+
+<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()
+  }
+}
+</script>
+
+<style>
+@import 'leaflet/dist/leaflet.css';
+</style>

+ 9 - 5
src/views/Tracking/src/components/TrackingDetail/src/components/TransportStep.vue

@@ -29,12 +29,12 @@ const handleTabClick = (name: string) => {
       </div>
     </div>
     <div class="content">
-      <div v-if="activeName === 'shipmentStatus'">
+      <template v-if="activeName === 'shipmentStatus'">
         <ShipmentStatus :data="props.data?.simplexData" />
-      </div>
-      <div v-else-if="activeName === 'containerStatus'">
+      </template>
+      <template v-else-if="activeName === 'containerStatus'">
         <ContainerStatus :data="props.data?.containerStatusData" />
-      </div>
+      </template>
     </div>
   </div>
 </template>
@@ -42,6 +42,10 @@ const handleTabClick = (name: string) => {
 <style lang="scss" scoped>
 .transport-step {
   width: 400px;
+  height: 484px;
+  background-color: #fff;
+  border: 1px solid #eaebed;
+  border-radius: 3px;
   .header {
     display: flex;
     height: 48px;
@@ -62,7 +66,7 @@ const handleTabClick = (name: string) => {
     }
   }
   .content {
-    padding: 16px;
+    height: calc(100% - 48px);
   }
 }
 </style>

+ 7 - 2
src/views/Tracking/src/components/TrackingTable/src/TrackingTable.vue

@@ -5,6 +5,7 @@ import { autoWidth } from '@/utils/table'
 import { useRowClickStyle } from '@/hooks/rowClickStyle'
 import dayjs from 'dayjs'
 import { useRouter } from 'vue-router'
+import { transportationMode } from '@/components/TransportationMode'
 
 const router = useRouter()
 const props = defineProps({
@@ -310,9 +311,13 @@ const handleCheckAllChange = ({ records }: any) => {
         </el-button>
       </template>
       <!-- Transportation Mode字段的插槽 -->
-      <template #mode>
+      <template #mode="{ row, column }">
         <div>
-          <span class="font_family icon-icon_ocean_b" style="font-size: 24px"></span>
+          <span
+            class="font_family"
+            :class="[`icon-${transportationMode?.[row[column.field]]}`]"
+            style="font-size: 24px"
+          ></span>
         </div>
       </template>
       <!-- Status字段的插槽 -->