소스 검색

feat: 完成system message的部分样式

zhouyuhao 10 달 전
부모
커밋
1cd07c4e26

+ 2 - 0
src/styles/theme.scss

@@ -246,6 +246,8 @@
   --color-vxe-table-visited-row-bg: #f2f2f2;
 
   --color-public-tracking-empty-bg: #fff;
+
+  --color-system-message-nav-bg: #f6f6f6;
 }
 
 :root.dark {

+ 23 - 0
src/utils/tools.ts

@@ -36,3 +36,26 @@ export const formatTimezoneByUTCorGMT = (time: string, timezone: string) => {
     return `${formattedTime} ${gmtOffset}`
   }
 }
+
+const formatString = 'MMM/DD/YYYY'
+/**
+ * 根据用户偏好 格式化时间
+ */
+export const formatTimezoneByUser = (time: string, timezone?: string) => {
+  if (!time) return '--'
+  let formattedTime = ''
+  if (time.length > 12) {
+    formattedTime = moment(time).format(`${formatString} hh:mm A`)
+    if (!timezone) {
+      return formattedTime
+    }
+    let gmtOffset = ''
+    const timeZoneOffset = moment().tz(timezone).format('Z')
+    // 替换 "+07:00" 为 "GMT+07"
+    gmtOffset = `(GMT${timeZoneOffset.slice(0, 3)})`
+    return `${formattedTime} ${gmtOffset}`
+  } else {
+    formattedTime = moment(time).format(formatString)
+    return formattedTime
+  }
+}

+ 150 - 28
src/views/SystemMessage/src/SystemMessage.vue

@@ -1,5 +1,67 @@
 <script setup lang="ts">
-const collapseVModel = ref<string[]>([])
+import NotificationCard from './components/NotificationCard.vue'
+
+const collapseVModel = ref<string[]>(['1'])
+
+const navList = [
+  {
+    title: 'Milestone Update',
+    count: 2
+  },
+  {
+    title: 'Container Status Update',
+    count: 1
+  },
+  {
+    title: 'Departure/Arrival Delay',
+    count: '99+'
+  },
+  {
+    title: 'ETD/ETA Change',
+    count: 0
+  }
+]
+
+const activeItem = ref('Milestone Update')
+const setActiveItem = (item: string) => {
+  activeItem.value = item
+}
+
+const activeName = ref('first')
+
+const handleClick = () => {}
+
+const data = {
+  isRead: true,
+  title: 'Milestone Update',
+  mode: 'Ocean Freight',
+  no: 'HBOL: SHJN2301234',
+  tag: 'Booking Confirmed',
+  location: 'Hong Kong',
+  time: 'Jan 10, 2025 14:30 UTC+8'
+}
+const notificationList = [
+  {
+    numericRecords: 3,
+    isRead: true,
+    title: 'Milestone Update',
+    mode: 'Ocean Freight',
+    no: 'HBOL: SHJN2301234',
+    tag: 'Booking Confirmed',
+    location: 'Hong Kong',
+    time: 'Jan 10, 2025 14:30 UTC+8'
+  },
+  {
+    numericRecords: 0,
+    isRead: false,
+    title: 'Milestone Update Daily Summary (Jan 10, 2025)',
+    mode: 'Air Freight',
+    no: 'HBOL: SHJN2301234',
+    tag: 'Booking Confirmed',
+    location: 'Hong Kong',
+    time: 'Jan 10, 2025 14:30 UTC+8'
+  }
+]
 </script>
 
 <template>
@@ -8,23 +70,49 @@ const collapseVModel = ref<string[]>([])
     <div class="left-nav">
       <el-collapse v-model="collapseVModel">
         <el-collapse-item title="Event Notifications" name="1">
-          <div class="collapse-item">
-            <div class="active-sign"></div>
-            <span>Milestone Update</span>
-            <div class="count">
-              <span>2</span>
+          <div
+            @click="setActiveItem(item.title)"
+            class="collapse-item"
+            :class="{ 'is-active': item.title === activeItem }"
+            v-for="item in navList"
+            :key="item.title"
+          >
+            <div v-if="item.title === activeItem" class="active-sign"></div>
+            <span>{{ item.title }}</span>
+            <div class="count" v-if="item.count">
+              <span>{{ item.count }}</span>
             </div>
           </div>
-          <div class="collapse-item">
-            <div class="active-sign"></div>
-            Container Status Update
-          </div>
-          <div class="collapse-item">Departure/Arrival Delay</div>
-          <div class="collapse-item">ETD/ETA Change</div>
         </el-collapse-item>
       </el-collapse>
+      <div
+        @click="setActiveItem('Feature Update')"
+        class="collapse-item"
+        style="margin-top: 4px; font-weight: 700"
+        :class="{ 'is-active': activeItem === 'Feature Update' }"
+      >
+        <div v-if="activeItem === 'Feature Update'" class="active-sign"></div>
+        <span>Feature Update</span>
+        <div class="count">
+          <span>33</span>
+        </div>
+      </div>
+    </div>
+    <div class="right-content">
+      <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
+        <el-tab-pane label="All Notifications" name="first">
+          <div style="display: flex; flex-direction: column; gap: 16px; padding: 10px 16px">
+            <NotificationCard
+              v-for="(item, index) in notificationList"
+              :key="index"
+              :data="item"
+            ></NotificationCard>
+          </div>
+        </el-tab-pane>
+        <el-tab-pane label="Unread" name="second">Config</el-tab-pane>
+        <el-tab-pane label="Read" name="third">Role</el-tab-pane>
+      </el-tabs>
     </div>
-    <div class="right-content"></div>
   </div>
 </template>
 
@@ -38,33 +126,44 @@ const collapseVModel = ref<string[]>([])
   padding: 0 24px;
   align-items: center;
 }
+.system-message {
+  display: flex;
+}
 .left-nav {
   width: 280px;
   padding: 24px;
-  padding-right: 16px;
+  padding-right: 0;
+  border-right: 1px solid var(--color-border);
+  .el-collapse {
+    padding-right: 16px;
+    border-top: none;
+    :deep(.el-collapse-item__header) {
+      font-weight: 700;
+    }
+  }
   .collapse-item {
-    position: relative;
     display: flex;
     align-items: center;
     justify-content: space-between;
+    position: relative;
     width: 240px;
     height: 48px;
+    margin-bottom: 4px;
     padding: 0 16px;
     border-radius: 12px;
     &:hover {
-      background-color: var(--color-table-header-bg);
-      .active-sign {
-        position: absolute;
-        top: 50%;
-        left: 0;
-        transform: translateY(-50%);
-        width: 4px;
-        height: 21px;
-        border-radius: 12px;
-        background-color: var(--color-theme);
-      }
+      background-color: var(--color-system-message-nav-bg);
+    }
+    .active-sign {
+      position: absolute;
+      top: 50%;
+      left: 0;
+      transform: translateY(-50%);
+      width: 4px;
+      height: 21px;
+      border-radius: 12px;
+      background-color: var(--color-theme);
     }
-
     .count {
       display: inline-flex;
       justify-content: center;
@@ -83,7 +182,14 @@ const collapseVModel = ref<string[]>([])
       }
     }
     &.is-active {
-      background-color: var(--color-table-header-bg);
+      background-color: var(--color-system-message-nav-bg);
+      & > span {
+        font-weight: 700;
+        color: var(--color-theme);
+      }
+    }
+    &:last-child {
+      margin-bottom: 8px;
     }
   }
   :deep(.el-collapse-item__header) {
@@ -91,4 +197,20 @@ const collapseVModel = ref<string[]>([])
     padding: 16px;
   }
 }
+
+.right-content {
+  flex: 1;
+  padding-top: 24px;
+  :deep(.el-tabs__nav-scroll) {
+    padding-left: 16px;
+    border-bottom: 1px solid var(--color-border);
+    .el-tabs__item {
+      font-weight: 400;
+      color: var(--color-neutral-1);
+      &.is-active {
+        font-weight: 700;
+      }
+    }
+  }
+}
 </style>

+ 144 - 0
src/views/SystemMessage/src/components/NotificationCard.vue

@@ -0,0 +1,144 @@
+<script setup lang="ts">
+import { transportationMode } from '@/components/TransportationMode'
+const props = defineProps<{
+  data: {
+    numericRecords: number
+    isRead: boolean
+    title: string
+    mode: string
+    no: string
+    tag: string
+    location: string
+    time: string
+  }
+}>()
+</script>
+
+<template>
+  <div class="notification-card" :class="{ 'is-read': data.isRead }">
+    <div class="header">
+      <div class="status-icon"></div>
+      <div class="title">{{ data.title }}</div>
+    </div>
+    <div class="content">
+      <div class="more-tips" v-if="data.numericRecords">
+        <span>Latest Status Updates ({{ data.numericRecords }})</span>
+        <el-button class="see-all-icon el-button--text">
+          See All
+          <span class="font_family icon-icon_next_b"></span>
+        </el-button>
+      </div>
+      <div class="base-info">
+        <span class="font_family" :class="[`icon-${transportationMode?.[data.mode]}`]"></span>
+        <span class="no">{{ data.no }}</span>
+        <div class="tag">
+          <span class="dot"></span>
+          {{ data.tag }}
+        </div>
+      </div>
+      <div class="location">
+        <span class="font_family icon-icon_location_b"></span>
+        <span>{{ data.location }}</span>
+      </div>
+      <div class="time">
+        <span style="margin-left: 1px" class="font_family icon-icon_time_b"></span>
+        <span>{{ data.time }}</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.notification-card {
+  max-width: 640px;
+  &.is-read {
+    .header {
+      .status-icon {
+        background-color: var(--color-border);
+      }
+      .title {
+        color: var(--color-neutral-1);
+        opacity: 0.5;
+      }
+    }
+  }
+  .header {
+    display: flex;
+    align-items: center;
+    margin-bottom: 8px;
+    .status-icon {
+      width: 10px;
+      height: 10px;
+      border-radius: 50%;
+      background-color: var(--color-theme);
+      margin-right: 10px;
+    }
+    .title {
+      font-weight: 700;
+    }
+  }
+  .content {
+    padding: 16px 8px;
+    padding-top: 0;
+    background-color: var(--color-header-bg);
+    border-radius: 6px;
+    .more-tips {
+      display: flex;
+      justify-content: space-between;
+      padding: 8px 0;
+      border-bottom: 1px dashed var(--color-border);
+      font-size: 12px;
+      line-height: 24px;
+      .see-all-icon {
+        width: 68px;
+        height: 24px;
+        :deep(span) {
+          color: var(--color-theme);
+        }
+      }
+    }
+    .base-info {
+      display: flex;
+      padding-top: 16px;
+      .no {
+        margin-left: 8px;
+        font-weight: 700;
+        line-height: 18px;
+      }
+      .tag {
+        display: flex;
+        align-items: center;
+        margin-left: 4px;
+        padding-left: 8px;
+        padding-right: 6px;
+        line-height: 18px;
+        background-color: #e6f1eb;
+        font-size: 10px;
+        font-weight: 700;
+        border-radius: 3px;
+        color: #5bb462;
+        .dot {
+          width: 8px;
+          height: 8px;
+          border-radius: 50%;
+          background-color: var(--color-tag-completed);
+          margin-right: 4px;
+        }
+      }
+    }
+    .location,
+    .time {
+      display: flex;
+      align-items: center;
+      margin-top: 8px;
+      color: var(--color-neutral-2);
+      font-size: 12px;
+      .font_family {
+        font-family: 'iconfont';
+        color: var(--color-neutral-2);
+        margin-right: 8px;
+      }
+    }
+  }
+}
+</style>

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

@@ -1,7 +1,7 @@
 <script setup lang="ts">
-import dayjs from 'dayjs'
 import { transportationMode } from '@/components/TransportationMode'
 import { useOverflow } from '@/hooks/useOverflow'
+import { formatTimezoneByUser } from '@/utils/tools'
 
 const props = defineProps({
   data: Object
@@ -50,16 +50,6 @@ watch(
   }
 )
 
-const formatDate = (date: string) => {
-  if (!date) {
-    return '--'
-  } else {
-    return date.length > 12
-      ? dayjs(date).format('MMM-DD-YYYY hh:mm A')
-      : dayjs(date).format('MMM-DD-YYYY')
-  }
-}
-
 const basicOriginRef = ref()
 const basicDestinationRef = ref()
 const detailOriginRef = ref()
@@ -117,11 +107,11 @@ const { isOverflow: isDetailDestinationOverflow } = useOverflow(detailDestinatio
           </div>
           <div class="etd border-right">
             <div class="title">ETD</div>
-            <div class="content">{{ formatDate(item.etd) }}</div>
+            <div class="content">{{ formatTimezoneByUser(item.etd) }}</div>
           </div>
           <div class="eta">
             <div class="title">ETA</div>
-            <div class="content">{{ formatDate(item.eta) }}</div>
+            <div class="content">{{ formatTimezoneByUser(item.eta) }}</div>
             <span
               :class="{ collapse: item.isCollapse }"
               class="font_family icon-icon_dropdown_b"
@@ -154,12 +144,12 @@ const { isOverflow: isDetailDestinationOverflow } = useOverflow(detailDestinatio
               <div class="etd">
                 <span class="font_family icon-icon_date_b"></span>
                 <span>ETD: </span>
-                <span class="value">{{ formatDate(item.etd) }}</span>
+                <span class="value">{{ formatTimezoneByUser(item.etd) }}</span>
               </div>
               <div class="atd">
                 <span class="font_family icon-icon_date_b"></span>
                 <span>ATD: </span>
-                <span class="value">{{ formatDate(item.atd) }}</span>
+                <span class="value">{{ formatTimezoneByUser(item.atd) }}</span>
               </div>
             </div>
             <div class="destination">
@@ -179,12 +169,12 @@ const { isOverflow: isDetailDestinationOverflow } = useOverflow(detailDestinatio
               <div class="eta">
                 <span class="font_family icon-icon_date_b"></span>
                 <span>ETA: </span>
-                <span class="value">{{ formatDate(item.eta) }}</span>
+                <span class="value">{{ formatTimezoneByUser(item.eta) }}</span>
               </div>
               <div class="ata">
                 <span class="font_family icon-icon_date_b"></span>
                 <span>ATA: </span>
-                <span class="value">{{ formatDate(item.ata) }}</span>
+                <span class="value">{{ formatTimezoneByUser(item.ata) }}</span>
               </div>
             </div>
           </div>