Преглед изворни кода

feat: 修改message中卡片格式

zhouyuhao пре 9 месеци
родитељ
комит
c6853a9f3d

+ 0 - 1
package.json

@@ -11,7 +11,6 @@
     "preview": "vite preview",
     "build-only": "vite build",
     "build:dev": "vite build --mode development",
-    "build:test": "vite build --mode test",
     "build:pro": "vite build --mode product",
     "type-check": "vue-tsc --build --force",
     "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",

+ 35 - 3
src/api/module/notificationMessage.ts

@@ -6,15 +6,47 @@ const baseUrl = `${base}/main_new_version.php`
 /**
  * 保存用户个人信息或日期和数字格式化配置
  * @param save_model profile 代表基本信息的save, no_profile 代表格式信息的save
- * @param user_name
- * @param email
+ * @param first_name
+ * @param last_name
  * @param date_fromat
  * @param numbers_format
  */
 export const saveUserInfo = (params: any) => {
-  return HttpAxios.get(`${baseUrl}`, {
+  return HttpAxios.post(`${baseUrl}`, {
     action: 'system_setting',
     operate: 'personal_profile_save',
     ...params
   })
 }
+
+/**
+ * 获取milestone消息列表
+ * @param save_model profile 代表基本信息的save, no_profile 代表格式信息的save
+ * @param user_name
+ * @param email
+ * @param date_fromat
+ * @param numbers_format
+ */
+export const getNotificationList = (params: any) => {
+  return HttpAxios.get(`${baseUrl}`, {
+    action: 'notifications_rules',
+    operate: 'notifications_init',
+    ...params
+  })
+}
+
+/**
+ * 获取notification消息的see all详情
+ * @param save_model profile 代表基本信息的save, no_profile 代表格式信息的save
+ * @param user_name
+ * @param email
+ * @param date_fromat
+ * @param numbers_format
+ */
+export const getNotificationDetails = (params: any) => {
+  return HttpAxios.post(`${baseUrl}`, {
+    action: 'notifications_rules',
+    operate: 'notifications_see_all',
+    ...params
+  })
+}

+ 30 - 12
src/components/NotificationMessageCard/src/components/EventCard.vue

@@ -2,13 +2,14 @@
 import { transportationMode } from '@/components/transportationMode'
 import { useRouter } from 'vue-router'
 import { getTimezone } from '@/utils/tools'
+import dayjs from 'dayjs'
 
 const router = useRouter()
 
 interface EventCardPropsData {
   type: string // 'milestone' | 'container' | 'delay' | 'change'
   numericRecords?: number // 多条记录数 (Daily Update消息)
-  isRead: boolean // 是否已读 (true 已读,false 未读)
+  isRead?: boolean // 是否已读 (true 已读,false 未读)
   title?: string // Milestone Update
   mode?: string // 运输方式
   no: string // HBOL: SHJN2301234
@@ -37,8 +38,8 @@ const props = defineProps<{
 const handleSeeAll = (data: any) => {
   router.push({
     name: 'System Message Detail',
-    params: {
-      data: JSON.stringify(data) // 将数据转换为字符串并作为查询参数传递
+    query: {
+      frequency_type: data.frequency_type
     }
   })
 }
@@ -140,19 +141,31 @@ const handleSeeAll = (data: any) => {
         <span class="font_family icon-icon_location_b"></span>
         <span>{{ data.location }}</span>
       </div>
-      <div class="delay-time" v-if="data.type === 'delay' && data.info?.time">
-        <span class="font_family icon-icon_delay_b"></span>
-        <span>{{ data.info.timeLabel }}</span>
-        <span>{{ data.info.time }}</span>
+      <div
+        :class="{ 'delay-time': data.type === 'delay', 'change-time': data.type === 'change' }"
+        v-if="(data.type === 'delay' || 'change') && data.info?.time"
+      >
+        <span
+          v-if="data.type === 'delay'"
+          style="margin-right: 5px"
+          class="font_family icon-icon_delay_b"
+        ></span>
+        <span v-else class="font_family icon-icon_time_b"></span>
+        <span style="margin-right: 2px">{{ data.info.timeLabel }}&nbsp;</span>
+        <span style="margin-right: 3px">{{
+          dayjs(data.info.time).format('MMM DD, YYYY hh:mm')
+        }}</span>
         <span>{{ getTimezone(data.info.timezone) }}</span>
       </div>
-      <div class="change-time" v-if="data.type === 'change' && data.info?.time">
+      <!-- <div class="change-time" v-if="data.type === 'change' && data.info?.time">
         <span style="margin-left: 1px" class="font_family icon-icon_time_b"></span>
         <span>{{ data.info?.time }}</span>
-      </div>
-      <div class="time">
-        <span style="margin-left: 1px" class="font_family icon-icon_time_b"></span>
-        <span>{{ data.time }}</span>
+      </div> -->
+      <div class="time" :class="{ grey: data.type === 'delay' || data.type === 'change' }">
+        <span class="font_family icon-icon_time_b"></span>
+        <span style="margin-right: 2px">{{ data.timeLabel }}</span>
+        <span style="margin-right: 3px">{{ dayjs(data.time).format('MMM DD, YYYY hh:mm') }}</span>
+        <span>{{ getTimezone(data.timezone) }}</span>
       </div>
       <div class="previous" v-if="data.previous">
         <span class="previous-icon"></span>
@@ -272,6 +285,11 @@ const handleSeeAll = (data: any) => {
       align-items: center;
       margin-top: 8px;
       height: 16px;
+      &.grey {
+        span {
+          color: var(--color-neutral-3);
+        }
+      }
       span {
         color: var(--color-neutral-2);
         font-size: 12px;

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

@@ -1,5 +1,6 @@
 import { defineStore } from 'pinia'
 import { useVisitedRowState } from './visitedRow'
+import dayjs from 'dayjs'
 
 interface UserInfo {
   uname: string
@@ -10,6 +11,8 @@ interface UserInfo {
   expire_day: number
   date_format: string
   numbers_format: string
+  PASSWORD_CHANGE_CYCLE: number // 密码修改周期(多少天需要改一次)
+  last_pwd_change: string // 上次密码修改时间
 }
 interface UserState {
   userInfo: UserInfo
@@ -29,6 +32,11 @@ export const useUserStore = defineStore('user', {
       } else {
         return state.userInfo.uname || ''
       }
+    },
+    expireDay(state) {
+      const userInfo = state.userInfo
+
+      return userInfo.PASSWORD_CHANGE_CYCLE - dayjs().diff(dayjs(userInfo.last_pwd_change), 'day')
     }
   },
   actions: {

+ 1 - 1
src/utils/tools.ts

@@ -26,7 +26,7 @@ export const formatTimezone = (time: string, timezone?: string) => {
  * @returns
  */
 export const getTimezone = (timezone: string): string => {
-  if (timezone) return ''
+  if (!timezone) return ''
   const offset = moment.tz(`${moment().year()}-01-01`, timezone).format('Z')
   return `UTC${offset.slice(0, 3)}`
 }

+ 9 - 0
src/views/Layout/src/components/Header/components/ChangePasswordDialog.vue

@@ -1,4 +1,9 @@
 <script setup lang="ts">
+import { useUserStore } from '@/stores/modules/user'
+import dayjs from 'dayjs'
+
+const userStore = useUserStore()
+
 const dialogVisible = ref(false)
 
 const openDialog = () => {
@@ -50,6 +55,10 @@ const handleUpdate = () => {
     .then((res) => {
       if (res.code === 200) {
         ElMessage.success('Password updated successfully')
+        userStore.setUserInfo({
+          ...userStore.userInfo,
+          last_pwd_change: dayjs().format('YYYY-MM-DD HH:mm:ss')
+        })
         dialogVisible.value = false
       } else if (res.code === 400) {
         if (res.msg === 'Old password is incorrect') {

+ 139 - 73
src/views/Layout/src/components/Header/components/NotificationDrawer.vue

@@ -1,7 +1,12 @@
 <script setup lang="ts">
 import NotificationMessageCard from '@/components/NotificationMessageCard/src/NotificationMessageCard.vue'
+import { useRouter } from 'vue-router'
+
+const router = useRouter()
+const loading = ref(false)
 
 const drawerModel = defineModel('drawerModel', { type: Boolean, default: false })
+
 const notificationType = ref('all')
 const notificationTypeList = ref([
   {
@@ -30,72 +35,116 @@ const notificationTypeList = ref([
   }
 ])
 
-const notificationList = [
-  {
-    notificationType: 'feature',
-    info: {
-      isRead: true,
-      title: 'Feature Update',
-      header: 'New feature online: Quick search has been released!',
-      content:
-        'We are pleased to announce that the quick search function is now officially online! You can now quickly find what you need by entering keywords, greatly improving your work efficiency. Go and experience it!'
-    }
-  },
-  {
-    notificationType: 'event',
-    info: {
-      type: 'milestone',
-      isMultiple: true,
-      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',
-      info: {
-        route: ['Hong Kong', 'Shanghai', 'Ningbo']
+const notificationList = ref<any[]>([])
+// const notificationList = [
+//   {
+//     notificationType: 'feature',
+//     info: {
+//       isRead: true,
+//       title: 'Feature Update',
+//       header: 'New feature online: Quick search has been released!',
+//       content:
+//         'We are pleased to announce that the quick search function is now officially online! You can now quickly find what you need by entering keywords, greatly improving your work efficiency. Go and experience it!'
+//     }
+//   },
+//   {
+//     notificationType: 'event',
+//     info: {
+//       type: 'milestone',
+//       isMultiple: true,
+//       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',
+//       info: {
+//         route: ['Hong Kong', 'Shanghai', 'Ningbo']
+//       }
+//     }
+//   },
+//   {
+//     notificationType: 'password',
+//     info: {
+//       title: 'Password Notifications',
+//       isExpiration: true,
+//       tips: 'Password Expiration in 311 Days',
+//       content:
+//         'Your password will expire in 7 days. To ensure the security of your account, please change your password as soon as possible.'
+//     }
+//   },
+//   {
+//     notificationType: 'password',
+//     info: {
+//       isRead: false,
+//       title: 'Password Notifications',
+//       isExpiration: false,
+//       tips: 'Password Expiration in 3 Days',
+//       content:
+//         'Your password will expire in 7 days. To ensure the security of your account, please change your password as soon as possible.'
+//     }
+//   },
+//   {
+//     notificationType: 'password',
+//     info: {
+//       isRead: true,
+//       title: 'Password Notifications',
+//       isExpiration: true,
+//       tips: 'Password Expiration in 31111 Days',
+//       content:
+//         'Your password will expire in 7 days. To ensure the security of your account, please change your password as soon as possible.'
+//     }
+//   }
+// ]
+
+const getNotificationList = () => {
+  loading.value = true
+  $api
+    .getNotificationList({
+      rules_type: 'Milestone_Update'
+    })
+    .then((res) => {
+      if (res.code === 200) {
+        notificationList.value = res.data
+        console.log(res, 'test')
       }
-    }
-  },
-  {
-    notificationType: 'password',
-    info: {
-      title: 'Password Notifications',
-      isExpiration: true,
-      tips: 'Password Expiration in 311 Days',
-      content:
-        'Your password will expire in 7 days. To ensure the security of your account, please change your password as soon as possible.'
-    }
-  },
-  {
-    notificationType: 'password',
-    info: {
-      isRead: false,
-      title: 'Password Notifications',
-      isExpiration: false,
-      tips: 'Password Expiration in 3 Days',
-      content:
-        'Your password will expire in 7 days. To ensure the security of your account, please change your password as soon as possible.'
-    }
-  },
-  {
-    notificationType: 'password',
-    info: {
-      isRead: true,
-      title: 'Password Notifications',
-      isExpiration: true,
-      tips: 'Password Expiration in 31111 Days',
-      content:
-        'Your password will expire in 7 days. To ensure the security of your account, please change your password as soon as possible.'
-    }
-  }
-]
+    })
+    .finally(() => {
+      loading.value = false
+    })
+}
+
+const handleMarkAllRead = () => {
+  // 标记所有为已读
+  // notificationList.value.forEach((item) => {
+  //   item.isRead = true
+  // })
+}
+
+const handleViewAll = () => {
+  router.push('/system-message')
+}
+
+const handleSettingMessage = () => {
+  // 跳转消息设置页面
+  // router.push('/')
+}
+
+const clearData = () => {
+  notificationList.value = []
+}
 </script>
 
 <template>
-  <el-drawer class="notice-drawer" v-model="drawerModel" size="432px">
+  <el-drawer
+    @open="getNotificationList"
+    @closed="clearData"
+    class="notice-drawer"
+    v-model="drawerModel"
+    size="432px"
+  >
     <template #header>
       <el-select size="large" v-model="notificationType" class="notification-type">
         <el-option
@@ -107,20 +156,34 @@ const notificationList = [
       </el-select>
     </template>
     <template #default>
-      <div class="notification-header">
-        <div class="mark-all-read">
-          <span class="font_family icon-icon_confirm_b show-icon"></span>
-          <span>Mark all read</span>
+      <div v-vloading="loading" style="height: 100%">
+        <div class="notification-header">
+          <el-button @click="handleMarkAllRead" class="mark-all-read el-button--text" size="small">
+            <span class="font_family icon-icon_confirm_b show-icon"></span>
+            <span>Mark all read</span>
+          </el-button>
+          <div class="view-all">
+            <el-button
+              style="height: 24px; width: 86px"
+              class="el-button--text"
+              @click="handleViewAll"
+            >
+              <span class="font_family icon-icon_confirm_b show-icon"></span>
+              <span>View all</span>
+            </el-button>
+            <el-button
+              class="el-button--text"
+              style="height: 24px; width: 24px; padding: 0"
+              @click="handleSettingMessage"
+            >
+              <span class="font_family icon-icon_administration_b"></span>
+            </el-button>
+          </div>
         </div>
-        <div class="view-all">
-          <span class="font_family icon-icon_confirm_b show-icon"></span>
-          <span>View all</span>
-          <span class="font_family icon-icon_administration_b setting"></span>
+        <div class="notification-content">
+          <NotificationMessageCard :data="notificationList" />
         </div>
       </div>
-      <div class="notification-content">
-        <NotificationMessageCard :data="notificationList" />
-      </div>
     </template>
   </el-drawer>
 </template>
@@ -235,6 +298,7 @@ div.layout-toolbar {
     .notification-header {
       display: flex;
       justify-content: space-between;
+      align-items: center;
       position: sticky;
       top: 0;
       height: 40px;
@@ -245,9 +309,11 @@ div.layout-toolbar {
       .mark-all-read {
         span {
           color: var(--color-theme);
+          font-size: 14px;
           vertical-align: middle;
         }
         .show-icon {
+          font-size: 16px;
           margin-right: 2px;
         }
       }

+ 35 - 6
src/views/SystemMessage/src/SystemMessage.vue

@@ -28,10 +28,24 @@ const setActiveItem = (item: string) => {
   activeItem.value = item
 }
 
+// const getNotificationList = () => {
+//   $api
+//     .getNotificationList({
+//       rules_type: 'Milestone_Update'
+//     })
+//     .then((res) => {
+//       if (res.code === 200) {
+//         notificationList.value = res.data.info
+//         console.log(res, 'test')
+//       }
+//     })
+// }
+
 const activeName = ref('first')
 
 const handleClick = () => {}
 
+// const notificationList = ref<any[]>([])
 const notificationList = [
   {
     type: 'milestone',
@@ -43,7 +57,9 @@ const notificationList = [
     no: 'SHJN2301234',
     tag: 'Booking Confirmed',
     location: 'Hong Kong',
-    time: 'Jan 10, 2025 14:30 UTC+8'
+    timeLabel: 'ATA: ',
+    time: '2510-12-1 14:30 UTC+8',
+    timezone: 'Asia/Shanghai'
   },
   {
     type: 'container',
@@ -52,7 +68,9 @@ const notificationList = [
     no: 'SHJN2301234',
     tag: 'Unloaded From Vessel',
     location: 'Hong Kong',
-    time: 'Jan 10, 2025 14:30 UTC+8',
+    timeLabel: 'ATA: ',
+    time: '2510-12-1 14:30 UTC+8',
+    timezone: 'Asia/Shanghai',
     previous: 'Previous: Departure from Shanghai (08:15 UTC+8)'
   },
   {
@@ -64,9 +82,13 @@ const notificationList = [
     no: 'SHJN2301234',
     tag: 'Departure Delay',
     location: 'Hong Kong',
-    time: 'Jan 10, 2025 14:30 UTC+8',
+    timeLabel: 'ATA: ',
+    time: '2510-12-1 14:30 UTC+8',
+    timezone: 'Asia/Shanghai',
     info: {
-      time: 'ATD: Jan 12, 16:30 (+2 days delay)',
+      timeLabel: 'ATA: ',
+      time: '2510-12-1 14:30 UTC+8',
+      timezone: 'Asia/Shanghai',
       departureDelayNum: 10,
       arrivalDelayNum: 8
     }
@@ -82,13 +104,20 @@ const notificationList = [
     info: {
       etdChangeNum: 20,
       etaChangeNum: 10,
-      time: 'Updated ETD: Jan 17, 15:00'
+      timeLabel: 'ATA: ',
+      time: '2510-12-1 14:30 UTC+8',
+      timezone: 'Asia/Shanghai'
     },
     location: 'Hong Kong',
     changeTime: 'Updated ETD: Jan 17, 15:00',
-    time: 'Jan 10, 2025 14:30 UTC+8'
+    timeLabel: 'ATA: ',
+    time: '2510-12-1 14:30 UTC+8',
+    timezone: 'Asia/Shanghai'
   }
 ]
+// onMounted(() => {
+//   getNotificationList()
+// })
 </script>
 
 <template>

+ 33 - 11
src/views/SystemMessage/src/components/PersonalProfile.vue

@@ -76,15 +76,13 @@ const handleDateFormat = (value: string) => {
   monthFormat.value = dateFormatExample.value[value][0].value
 }
 
-const numbersFormat = ref(userStore.userInfo?.numbers_format)
-
-// 判断用户是否指定数字格式化,没有指定则自动判断
-const isSpecifyNumbersFormat = () => {
-  numbersFormat.value = isEuropean() ? 'European' : 'US/UK'
+const initNumbersFormat = () => {
+  return userStore.userInfo?.numbers_format || (isEuropean() ? 'European' : 'US/UK')
 }
-isSpecifyNumbersFormat()
+const numbersFormat = ref(initNumbersFormat())
 
 const saveConfig = (model: string) => {
+  console.log(123123)
   let params = {}
   if (model === 'profile') {
     params = {
@@ -99,9 +97,27 @@ const saveConfig = (model: string) => {
       number_format: numbersFormat.value
     }
   }
+  console.log(params, 'value')
   $api.saveUserInfo(params).then((res: any) => {
-    if (res.cdoe === 200) {
-      console.log(res)
+    console.log(res, 'res')
+    if (res.code === 200) {
+      const updatedInfo =
+        model === 'profile'
+          ? {
+              ...userStore.userInfo,
+              first_name: form.firstName,
+              last_name: form.lastName
+            }
+          : {
+              ...userStore.userInfo,
+              date_format: monthFormat.value,
+              numbers_format: numbersFormat.value
+            }
+
+      userStore.setUserInfo(updatedInfo)
+      ElMessage.success('Save successfully')
+    } else {
+      ElMessage.error('Save failed')
     }
   })
 }
@@ -138,7 +154,9 @@ const saveConfig = (model: string) => {
               Password
               <span style="margin: 0 2px 0 8px" class="font_family icon-icon_time_b"></span>
               <span>Your password will be expire in</span>
-              <span style="margin-left: 4px; color: var(--color-theme)">4 day(s)</span>
+              <span style="margin-left: 4px; color: var(--color-theme)"
+                >{{ userStore.expireDay }} day(s)</span
+              >
             </p>
             <div class="password-change">
               <el-input
@@ -213,7 +231,7 @@ const saveConfig = (model: string) => {
                   style="padding: 0 40px"
                   class="el-button--dark save-icon"
                   size="large"
-                  >Save</el-button
+                  >Save1222</el-button
                 >
               </el-col>
             </el-row>
@@ -240,7 +258,11 @@ const saveConfig = (model: string) => {
                 </el-radio></el-col
               >
               <el-col>
-                <el-button style="padding: 0 40px" class="el-button--dark save-icon" size="large"
+                <el-button
+                  @click="saveConfig('no_profile')"
+                  style="padding: 0 40px"
+                  class="el-button--dark save-icon"
+                  size="large"
                   >Save</el-button
                 >
               </el-col>

+ 20 - 6
src/views/SystemMessage/src/components/SystemMessageDetail.vue

@@ -6,32 +6,46 @@ const notificationData: any = {
   numericRecords: 3,
   notificationList: [
     {
-      isRead: true,
       mode: 'Ocean Freight',
       no: 'HBOL: SHJN2301234',
       tag: 'Booking Confirmed',
       location: 'Hong Kong',
-      time: 'Jan 10, 2025 14:30 UTC+8'
+      timeLabel: 'ATA: ',
+      time: '2510-12-1 14:30 UTC+8',
+      timezone: 'Asia/Shanghai'
     },
     {
-      isRead: false,
       mode: 'Air Freight',
       no: 'HBOL: SHJN2301234',
       tag: 'Booking Confirmed',
       location: 'Hong Kong',
-      time: 'Jan 10, 2025 14:30 UTC+8'
+      timeLabel: 'ATA: ',
+      time: '2510-12-1 14:30 UTC+8',
+      timezone: 'Asia/Shanghai'
     },
     {
-      isRead: false,
       mode: 'Air Freight',
       no: 'HBOL: SHJN2301234',
       tag: 'Booking Confirmed',
       location: 'Hong Kong',
-      time: 'Jan 10, 2025 14:30 UTC+8',
+      timeLabel: 'ATA: ',
+      time: '2510-12-1 14:30 UTC+8',
+      timezone: 'Asia/Shanghai',
       previous: 'Previous: Departure from Shanghai (08:15 UTC+8)'
     }
   ]
 }
+
+const getNotificationList = () => {
+  $api.getNotificationDetails().then((res) => {
+    if (res.code === 200) {
+      console.log(res, 'test')
+    }
+  })
+}
+onMounted(() => {
+  getNotificationList()
+})
 </script>
 
 <template>

+ 7 - 0
src/views/SystemMessage/src/components/notificationType.ts

@@ -0,0 +1,7 @@
+export const notificationType = {
+  Milestone_Update: 'Milestone Update',
+  Container_Status_Update: 'Container Status Update',
+  Container_Arrival: 'Container Arrival',
+  'Departure/Arrival_Delay': 'Departure/Arrival Delay',
+  'ETD/ETA_Change': 'ETD/ETA Change'
+}