Răsfoiți Sursa

Merge branch 'feature' of United_Software/k_online_ui into dev

Jack Zhou 8 luni în urmă
părinte
comite
3fc28b4a1c
50 a modificat fișierele cu 1501 adăugiri și 1025 ștergeri
  1. 47 2
      src/api/module/notificationMessage.ts
  2. 2 1
      src/auto-imports.d.ts
  3. 105 35
      src/components/AddRules/src/AddRules.vue
  4. 1 0
      src/components/AddRules/src/components/AddedrluesTag.vue
  5. 10 4
      src/components/AddRules/src/components/DelayedType.vue
  6. 15 5
      src/components/AddRules/src/components/ETDShipments.vue
  7. 5 1
      src/components/AddRules/src/components/NotiFrequency.vue
  8. 20 3
      src/components/AddRules/src/components/NotiMethods.vue
  9. 17 7
      src/components/AddRules/src/components/RulesShipments.vue
  10. 3 0
      src/components/AddRules/src/components/ShipmentRange.vue
  11. BIN
      src/components/AddRules/src/images/illustration_system massage_darkmode@2x.png
  12. 225 92
      src/components/CreateAddRules/src/CreateAddRules.vue
  13. 1 0
      src/components/CreateAddRules/src/components/AddedrluesTag.vue
  14. 13 4
      src/components/CreateAddRules/src/components/DelayedType.vue
  15. 16 6
      src/components/CreateAddRules/src/components/ETDShipments.vue
  16. 5 1
      src/components/CreateAddRules/src/components/NotiFrequency.vue
  17. 10 2
      src/components/CreateAddRules/src/components/NotiMethods.vue
  18. 14 6
      src/components/CreateAddRules/src/components/RulesShipments.vue
  19. 35 9
      src/components/CreateAddRules/src/components/ShipmentRange.vue
  20. BIN
      src/components/CreateAddRules/src/images/illustration_system massage_darkmode@2x.png
  21. 165 9
      src/components/NotificationMessageCard/src/NotificationMessageCard.vue
  22. 66 51
      src/components/NotificationMessageCard/src/components/EventCard.vue
  23. 46 3
      src/components/NotificationMessageCard/src/components/FeatureUpdateCard.vue
  24. 24 6
      src/components/NotificationMessageCard/src/components/PasswordCard.vue
  25. BIN
      src/components/NotificationMessageCard/src/images/test.png
  26. 21 1
      src/components/VSliderVerification/src/VSliderVerification.vue
  27. 10 1
      src/router/index.ts
  28. 71 0
      src/stores/modules/notificationMessage.ts
  29. 2 0
      src/stores/modules/user.ts
  30. 3 4
      src/styles/elementui.scss
  31. 8 0
      src/styles/theme-g.scss
  32. 39 1
      src/styles/theme.scss
  33. 1 1
      src/views/Booking/src/components/BookingDetail/src/components/EmailView.vue
  34. 2 2
      src/views/Dashboard/src/components/RecentStatus.vue
  35. 16 4
      src/views/Layout/src/components/Header/HeaderView.vue
  36. 47 80
      src/views/Layout/src/components/Header/components/NotificationDrawer.vue
  37. 90 125
      src/views/Layout/src/components/Header/components/TrainingCard.vue
  38. 0 43
      src/views/Layout/src/components/Menu/MenuView.vue
  39. 2 1
      src/views/Login/src/loginView.vue
  40. 180 220
      src/views/SystemMessage/src/SystemMessage.vue
  41. 111 45
      src/views/SystemMessage/src/components/SystemMessageDetail.vue
  42. 14 14
      src/views/SystemSettings/src/SystemSettings.vue
  43. 3 3
      src/views/SystemSettings/src/components/CreateNewrule/src/CreateNewrule.vue
  44. 19 3
      src/views/SystemSettings/src/components/MonitoringTable/src/MonitoringTable.vue
  45. 2 1
      src/views/Tracking/src/components/PublicTracking/src/PublicTrackingSearch.vue
  46. 0 222
      src/views/Tracking/src/components/PublicTracking/src/components/SlideVerify.vue
  47. 11 3
      src/views/Tracking/src/components/TrackingDetail/src/TrackingDetail.vue
  48. 1 1
      src/views/Tracking/src/components/TrackingDetail/src/components/EmailDrawer.vue
  49. 1 1
      src/views/Tracking/src/components/TrackingDetail/src/components/UploadFilesDialog.vue
  50. 2 2
      src/views/Tracking/src/components/TrackingTable/src/TrackingTable.vue

+ 47 - 2
src/api/module/notificationMessage.ts

@@ -20,8 +20,9 @@ export const saveUserInfo = (params: any) => {
 }
 
 /**
- * 获取milestone消息列表
- * @param save_model profile 代表基本信息的save, no_profile 代表格式信息的save
+ * 获取notification消息列表
+ * @param rules_type 特定类型的消息,all查全部
+ * @param current_time 当前时间 轮询查询时使用
  */
 export const getNotificationList = (params: any) => {
   return HttpAxios.get(`${baseUrl}`, {
@@ -41,3 +42,47 @@ export const getNotificationDetails = (params: any) => {
     ...params
   })
 }
+
+/**
+ * 获取system message页面数据
+ */
+export const getSystemMessageData = (params: any) => {
+  return HttpAxios.get(`${baseUrl}`, {
+    action: 'notifications_rules',
+    operate: 'notifications_message_init',
+    ...params
+  })
+}
+
+/**
+ * 将notification消息标记为已读
+ * @param id 消息id Array
+ * @param read_type 如果标记全部为已读 = true. 否则其他情况为false
+ */
+export const setMessageRead = (params: any) => {
+  return HttpAxios.post(`${baseUrl}`, {
+    action: 'notifications_rules',
+    operate: 'notifications_read',
+    read_type: false,
+    ...params
+  })
+}
+
+/**
+ * 检测是否有新消息
+ */
+export const hasUnreadMessages = () => {
+  return HttpAxios.post(`${baseUrl}`, {
+    action: 'notifications_rules',
+    operate: 'check_notifications_message'
+  })
+}
+
+/**
+ * 获取feature message详情数据
+ */
+export const getFeatureMsgPdf = () => {
+  return HttpAxios.get(`${baseUrl}`, {
+    action: 'feature_update'
+  })
+}

+ 2 - 1
src/auto-imports.d.ts

@@ -3,6 +3,7 @@
 // @ts-nocheck
 // noinspection JSUnusedGlobalSymbols
 // Generated by unplugin-auto-import
+// biome-ignore lint: disable
 export {}
 declare global {
   const $api: typeof import('@/api/index')['default']
@@ -68,6 +69,6 @@ declare global {
 // for type re-export
 declare global {
   // @ts-ignore
-  export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
+  export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
   import('vue')
 }

+ 105 - 35
src/components/AddRules/src/AddRules.vue

@@ -17,18 +17,21 @@ interface Props {
   TitleType: String
 }
 const MilestoneOceanListInit = ref<CheckboxItem[]>([])
-const MilestoneOceanListChecked = ref()
+const MilestoneOceanListChecked = ref([])
 const MilestoneAirListInit = ref<CheckboxItem[]>([])
-const MilestoneAirListChecked = ref()
+const MilestoneAirListChecked = ref([])
 const ContainerOceanListInit = ref<CheckboxItem[]>([])
-const ContainerOceanListChecked = ref()
+const ContainerOceanListChecked = ref([])
 const props = defineProps<Props>()
 let savesubscribeobj: any = {}
 const SystemList = ref(props.SystemList)
 const RulesActive = ref(['SelectMilestone', 'NotificationFrequency', 'NotificationMethod'])
 const OceanCheckList = ref()
+const OceanCheckCode = ref()
 const AirCheckList = ref()
+const AirCheckListCode = ref()
 const ContainerOceanList = ref()
+const ContainerOceanCode = ref()
 const IsFirstActive = ref(true)
 const IsTwoActive = ref(true)
 const IsThreeActive = ref(true)
@@ -71,14 +74,41 @@ watch(
 // 初始赋值
 const Initdata = (val: any) => {
   MilestoneOceanListInit.value = val.Milestone_Update.OceanCheckBoxList
+  OceanCheckList.value = []
+  OceanCheckCode.value = []
   MilestoneOceanListChecked.value = val.Milestone_Update.OceanCheckedList
-  OceanCheckList.value = val.Milestone_Update.OceanCheckedList
+  OceanCheckCode.value = val.Milestone_Update.OceanCheckedList
+  // 遍历选中的value值,找到对应的label值
+  MilestoneOceanListChecked.value.forEach((value) => {
+    const option = MilestoneOceanListInit.value.find((item) => item.value === value)
+    if (option) {
+      OceanCheckList.value.push(option.label)
+    }
+  })
+  AirCheckListCode.value = []
   MilestoneAirListInit.value = val.Milestone_Update.AirCheckBoxList
   MilestoneAirListChecked.value = val.Milestone_Update.AirCheckedList
-  AirCheckList.value = val.Milestone_Update.AirCheckedList
+  AirCheckListCode.value = val.Milestone_Update.AirCheckedList
+  AirCheckList.value = []
+  // 遍历选中的value值,找到对应的label值
+  MilestoneAirListChecked.value.forEach((value) => {
+    const option = MilestoneAirListInit.value.find((item) => item.value === value)
+    if (option) {
+      AirCheckList.value.push(option.label)
+    }
+  })
+  ContainerOceanCode.value = []
   ContainerOceanListInit.value = val.Container_Status_Update.CtnrCheckBoxList
   ContainerOceanListChecked.value = val.Container_Status_Update.CtnrCheckedList
-  ContainerOceanList.value = val.Container_Status_Update.CtnrCheckedList
+  ContainerOceanCode.value = val.Container_Status_Update.CtnrCheckedList
+  ContainerOceanList.value = []
+  // 遍历选中的value值,找到对应的label值
+  ContainerOceanListChecked.value.forEach((value) => {
+    const option = ContainerOceanListInit.value.find((item) => item.value === value)
+    if (option) {
+      ContainerOceanList.value.push(option.label)
+    }
+  })
   let OceanObj: any = {}
   OceanObj.atd_etd = val['Departure/Arrival_Delay'].ocean_atd_sub_etd
   OceanObj.atd_etd_unit = val['Departure/Arrival_Delay'].ocean_atd_sub_etd_unit
@@ -110,11 +140,13 @@ const Initdata = (val: any) => {
 }
 
 // 给tag list赋值
-const ChangeCheckOceanRules = (val: any) => {
+const ChangeCheckOceanRules = (val: any, value: any) => {
   OceanCheckList.value = val
+  OceanCheckCode.value = value
 }
-const ChangeContainerRules = (val: any) => {
+const ChangeContainerRules = (val: any, value: any) => {
   ContainerOceanList.value = val
+  ContainerOceanCode.value = value
 }
 // delayed赋值
 const ChangeDeayedRules = (val: any) => {
@@ -155,8 +187,9 @@ const ChangeAirRules = (val: any) => {
     delete savesubscribeobj.air_ata_sub_eta_unit
   }
 }
-const ChangeCheckAirRules = (val: any) => {
+const ChangeCheckAirRules = (val: any, value: any) => {
   AirCheckList.value = val
+  AirCheckListCode.value = value
 }
 
 // 更改Frequency时间
@@ -198,6 +231,23 @@ const handleCloseRadio = (val: any) => {
   }
 }
 
+// 删除 Frequency tag
+const NotimethodsMil = ref()
+const NotimethodsCon = ref()
+const NotimethodsDep = ref()
+const NotimethodsETD = ref()
+const handleCloseMethods = (val: any) => {
+  if (val == 'Mil') {
+    NotimethodsMil.value.handleCloseMethods()
+  } else if (val == 'Con') {
+    NotimethodsCon.value.handleCloseMethods()
+  } else if (val == 'Dep') {
+    NotimethodsDep.value.handleCloseMethods()
+  } else {
+    NotimethodsETD.value.handleCloseMethods()
+  }
+}
+
 // methods 切换
 const MilMethodsList = ref()
 const ChangeMethodsAddMil = (val: any, type: any) => {
@@ -331,31 +381,37 @@ const SaveSuceessful = () => {
 }
 const Savesubscribe = () => {
   let str = ''
+  missingmessage.value = ''
   if (props.TitleType == 'Milestone') {
     savesubscribeobj.rules_type = 'Milestone_Update'
     if (
       OceanCheckList.value == undefined ||
       AirCheckList.value == undefined ||
+      OceanCheckList.value.length == 0 ||
+      AirCheckList.value.length == 0 ||
       MilFrequencyList.value.length == 0 ||
+      MilMethodsList.value == undefined ||
       MilMethodsList.value.length == 0
     ) {
-      if (OceanCheckList.value == undefined) {
+      if (OceanCheckList.value == undefined || OceanCheckList.value.length == 0) {
         missingmessage.value += 'Ocean Shipments, '
       }
-      if (AirCheckList.value == undefined) {
+      if (AirCheckList.value == undefined || AirCheckList.value.length == 0) {
         missingmessage.value += 'Air Shipments, '
       }
       if (MilFrequencyList.value.length == 0) {
         missingmessage.value += 'Notification Frequency, '
       }
-      if (MilMethodsList.value.length == 0) {
+      if (MilMethodsList.value.length == 0 || MilMethodsList.value == undefined) {
         missingmessage.value += 'Notification Method, '
       }
       missingmessage.value = missingmessage.value.substring(0, missingmessage.value.length - 2)
       UnableSaveVisible.value = true
     } else {
-      savesubscribeobj.ocean_milestone = OceanCheckList.value
-      savesubscribeobj.air_milestone = AirCheckList.value
+      console.log(OceanCheckCode.value)
+      console.log(AirCheckListCode.value)
+      savesubscribeobj.ocean_milestone = OceanCheckCode.value
+      savesubscribeobj.air_milestone = AirCheckListCode.value
       str =
         'Ocean Milestones: ' +
         OceanCheckList.value.join(',') +
@@ -369,22 +425,24 @@ const Savesubscribe = () => {
     savesubscribeobj.rules_type = 'Container_Status_Update'
     if (
       ContainerOceanList.value == undefined ||
+      ContainerOceanList.value.length == 0 ||
       ConFrequencyList.value.length == 0 ||
+      ConMethodsList.value == undefined ||
       ConMethodsList.value.length == 0
     ) {
-      if (ContainerOceanList.value == undefined) {
+      if (ContainerOceanList.value == undefined || ContainerOceanList.value.length == 0) {
         missingmessage.value += 'Ocean Shipments, '
       }
       if (ConFrequencyList.value.length == 0) {
         missingmessage.value += 'Notification Frequency, '
       }
-      if (ConMethodsList.value.length == 0) {
+      if (ConMethodsList.value.length == 0 || ConMethodsList.value == undefined) {
         missingmessage.value += 'Notification Method, '
       }
       missingmessage.value = missingmessage.value.substring(0, missingmessage.value.length - 2)
       UnableSaveVisible.value = true
     } else {
-      savesubscribeobj.ocean_ctnr_status = ContainerOceanList.value
+      savesubscribeobj.ocean_ctnr_status = ContainerOceanCode.value
       str = 'Ocean Container: ' + ContainerOceanList.value.join(',')
       savesubscribeobj.event_details = str
       SaveSuceessful()
@@ -393,20 +451,23 @@ const Savesubscribe = () => {
     savesubscribeobj.rules_type = 'Departure/Arrival_Delay'
     if (
       DelayedDeparturedList.value == undefined ||
+      DelayedDeparturedList.value.length == 0 ||
       DelayedAirdList.value == undefined ||
+      DelayedAirdList.value.length == 0 ||
       DepFrequencyList.value.length == 0 ||
+      DepMethodsList.value == undefined ||
       DepMethodsList.value.length == 0
     ) {
-      if (DelayedDeparturedList.value == undefined) {
+      if (DelayedDeparturedList.value == undefined || DelayedDeparturedList.value.length == 0) {
         missingmessage.value += 'Ocean Shipments, '
       }
-      if (DelayedAirdList.value == undefined) {
+      if (DelayedAirdList.value == undefined || DelayedAirdList.value.length == 0) {
         missingmessage.value += 'Air Shipments, '
       }
       if (DepFrequencyList.value.length == 0) {
         missingmessage.value += 'Notification Frequency, '
       }
-      if (DepMethodsList.value.length == 0) {
+      if (DepMethodsList.value.length == 0 || DepMethodsList.value == undefined) {
         missingmessage.value += 'Notification Method, '
       }
       missingmessage.value = missingmessage.value.substring(0, missingmessage.value.length - 2)
@@ -424,18 +485,19 @@ const Savesubscribe = () => {
       ETDAirList.value == undefined ||
       ETDAirList.value.length == 0 ||
       ETDFrequencyList.value.length == 0 ||
+      ETDMethodsList.value == undefined ||
       ETDMethodsList.value.length == 0
     ) {
-      if (ETDOceanList.value == undefined) {
+      if (ETDOceanList.value == undefined || ETDOceanList.value.length == 0) {
         missingmessage.value += 'Ocean Shipments, '
       }
-      if (ETDAirList.value == undefined) {
+      if (ETDAirList.value == undefined || ETDAirList.value.length == 0) {
         missingmessage.value += 'Air Shipments, '
       }
       if (ETDFrequencyList.value.length == 0) {
         missingmessage.value += 'Notification Frequency, '
       }
-      if (ETDMethodsList.value.length == 0) {
+      if (ETDMethodsList.value.length == 0 || ETDMethodsList.value == undefined) {
         missingmessage.value += 'Notification Method, '
       }
       missingmessage.value = missingmessage.value.substring(0, missingmessage.value.length - 2)
@@ -458,12 +520,14 @@ const clearData = (val: any) => {
     MilestoneOceanListChecked.value = []
     MilestoneAirListChecked.value = []
     handleCloseRadio('Mil')
+    handleCloseMethods('Mil')
   } else if (val == 'Container_Status_Update') {
     ContainerOceanList.value = []
     ConFrequencyList.value = []
     ConMethodsList.value = []
     ContainerOceanListChecked.value = []
     handleCloseRadio('Con')
+    handleCloseMethods('Con')
   } else if (val == 'Departure/Arrival_Delay') {
     DelayedDeparturedList.value = []
     DelayedAirdList.value = []
@@ -472,12 +536,14 @@ const clearData = (val: any) => {
     OceanDelayed.value.ClearData()
     AirDelayed.value.ClearData()
     handleCloseRadio('Dep')
+    handleCloseMethods('Dep')
   } else {
     ETDOceanList.value = []
     ETDAirList.value = []
     ETDFrequencyList.value = []
     ETDMethodsList.value = []
     handleCloseRadio('ETD')
+    handleCloseMethods('ETD')
     OceanETD.value.ClearData()
     AirETD.value.ClearData()
   }
@@ -499,7 +565,7 @@ defineExpose({
           <el-collapse-item name="SelectMilestone">
             <template #title>
               <div class="Rules_Title">
-                <span class="iconfont_icon">
+                <span class="iconfont_icon icon_dark">
                   <svg class="iconfont" aria-hidden="true">
                     <use
                       :xlink:href="IsFirstActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
@@ -535,7 +601,7 @@ defineExpose({
           <el-collapse-item name="SelectMilestone">
             <template #title>
               <div class="Rules_Title">
-                <span class="iconfont_icon">
+                <span class="iconfont_icon icon_dark">
                   <svg class="iconfont" aria-hidden="true">
                     <use
                       :xlink:href="IsFirstActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
@@ -563,7 +629,7 @@ defineExpose({
           <el-collapse-item name="SelectMilestone">
             <template #title>
               <div class="Rules_Title">
-                <span class="iconfont_icon">
+                <span class="iconfont_icon icon_dark">
                   <svg class="iconfont" aria-hidden="true">
                     <use
                       :xlink:href="IsFirstActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
@@ -599,7 +665,7 @@ defineExpose({
           <el-collapse-item name="SelectMilestone">
             <template #title>
               <div class="Rules_Title">
-                <span class="iconfont_icon">
+                <span class="iconfont_icon icon_dark">
                   <svg class="iconfont" aria-hidden="true">
                     <use
                       :xlink:href="IsFirstActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
@@ -635,7 +701,7 @@ defineExpose({
           <el-collapse-item name="NotificationFrequency">
             <template #title>
               <div class="Rules_Title">
-                <span class="iconfont_icon">
+                <span class="iconfont_icon icon_dark">
                   <svg class="iconfont" aria-hidden="true">
                     <use
                       :xlink:href="IsTwoActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
@@ -679,7 +745,7 @@ defineExpose({
           <el-collapse-item name="NotificationMethod">
             <template #title>
               <div class="Rules_Title">
-                <span class="iconfont_icon">
+                <span class="iconfont_icon icon_dark">
                   <svg class="iconfont" aria-hidden="true">
                     <use
                       :xlink:href="IsThreeActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
@@ -692,21 +758,25 @@ defineExpose({
             <NotiMethods
               v-if="props.TitleType == 'Milestone'"
               :MethodsData="MethodsDataMil"
+              ref="NotimethodsMil"
               @ChangeMethodsAdd="ChangeMethodsAddMil"
             ></NotiMethods>
             <NotiMethods
               v-if="props.TitleType == 'Container'"
               :MethodsData="MethodsDataCon"
+              ref="NotimethodsCon"
               @ChangeMethodsAdd="ChangeMethodsAddCon"
             ></NotiMethods>
             <NotiMethods
               v-if="props.TitleType == 'Departure'"
               :MethodsData="MethodsDataDep"
+              ref="NotimethodsDep"
               @ChangeMethodsAdd="ChangeMethodsAddDep"
             ></NotiMethods>
             <NotiMethods
               v-if="props.TitleType == 'ETDChange'"
               :MethodsData="MethodsDataETD"
+              ref="NotimethodsETD"
               @ChangeMethodsAdd="ChangeMethodsAddETD"
             ></NotiMethods>
           </el-collapse-item>
@@ -911,7 +981,7 @@ defineExpose({
   padding: 0 !important;
 }
 :deep(.el-collapse-item__header):hover {
-  background-color: #fff !important;
+  background-color: var(--color-system-color-bg) !important;
   border: none !important;
 }
 :deep(.el-collapse-item__arrow) {
@@ -937,26 +1007,26 @@ defineExpose({
   transform: rotate(0);
 }
 :deep(.Ocean_collapse .el-collapse-item__header) {
-  background-color: #fff !important;
+  background-color: var(--color-system-color-bg) !important;
   padding: 0 8px !important;
   height: 40px !important;
 }
 :deep(.Ocean_collapse .el-collapse-item) {
-  background-color: var(--color-dialog-header-bg);
+  background-color: var(--color-system-color-bg);
   border-radius: 12px;
 }
 :deep(.Ocean_collapse .el-collapse-item__wrap) {
   padding: 0 8px !important;
 }
 :deep(.Ocean_collapse .el-collapse-item__header.is-active) {
-  background-color: var(--color-dialog-header-bg) !important;
+  background-color: var(--color-system-color-bg) !important;
 }
 :deep(.el-checkbox__input.is-checked + .el-checkbox__label) {
   color: var(--color-neutral-1);
 }
 .Rules_buttom {
   padding: 8px;
-  border-top: 1px solid var(--color-border-1);
+  border-top: 1px solid var(--color-system-border-1);
 }
 .rules_button {
   width: 100px;
@@ -980,7 +1050,7 @@ defineExpose({
   align-items: center;
 }
 :deep(header.el-dialog__header) {
-  background-color: #fff;
+  background-color: var(--color-system-body-bg);
 }
 :deep(footer.el-dialog__footer) {
   border-top: none;

+ 1 - 0
src/components/AddRules/src/components/AddedrluesTag.vue

@@ -58,6 +58,7 @@ const handleClose = (tag: any) => {
   padding: 8px !important;
   max-height: 400px;
   overflow-y: scroll;
+  background-color: var(--color-system-card-bg);
 }
 :deep(.el-tag .el-tag__close svg) {
   width: 16px !important;

+ 10 - 4
src/components/AddRules/src/components/DelayedType.vue

@@ -228,13 +228,13 @@ defineExpose({
 
 <style lang="scss" scoped>
 :deep(.el-checkbox-group) {
-  border: 1px solid var(--color-select-border);
+  border: 1px solid var(--color-system-border);
   border-radius: 5px;
 }
 :deep(.el-checkbox) {
   width: 100%;
-  background-color: var(--color-drawer-body-bg);
-  border-bottom: 1px solid var(--color-select-border);
+  background-color: var(--color-system-body-bg);
+  border-bottom: 1px solid var(--color-system-border);
   padding: 8px;
 }
 :deep(.el-checkbox:first-child) {
@@ -264,7 +264,7 @@ defineExpose({
 }
 :deep(.el-input-group--append .el-input-group__append .el-select .el-select__wrapper) {
   padding: 5px 10px;
-  background-color: #fff;
+  background-color: var(--color-system-body-bg);
 }
 .delayedTitle {
   color: var(--color-neutral-2);
@@ -276,4 +276,10 @@ defineExpose({
 .oceanCheckbox {
   margin-bottom: 8px;
 }
+:deep(.el-input__wrapper) {
+  box-shadow: 0 0 0 1px var(--color-system-border-1) inset;
+}
+:deep(.el-input-group--append .el-input-group__append .el-select .el-select__wrapper) {
+  box-shadow: 0 1px 0 0 var(--color-system-border-1) inset,0 -1px 0 0 var(--color-system-border-1) inset,-1px 0 0 0 var(--color-system-border-1) inset;
+}
 </style>

+ 15 - 5
src/components/AddRules/src/components/ETDShipments.vue

@@ -273,13 +273,13 @@ const clampedETAValue = computed(() => {
 
 <style lang="scss" scoped>
 :deep(.el-checkbox-group) {
-  border: 1px solid var(--color-select-border);
+  border: 1px solid var(--color-system-border);
   border-radius: 5px;
 }
 :deep(.el-checkbox) {
   width: 100%;
-  background-color: var(--color-drawer-body-bg);
-  border-bottom: 1px solid var(--color-select-border);
+  background-color: var(--color-system-body-bg);
+  border-bottom: 1px solid var(--color-system-border);
   padding: 8px;
 }
 :deep(.el-checkbox:first-child) {
@@ -292,6 +292,9 @@ const clampedETAValue = computed(() => {
 :deep(.el-radio) {
   border: none !important;
 }
+:deep( .el-radio__inner) {
+  border: 1px solid var(--color-system-checkbox-border);
+}
 .delayedType {
   align-items: start;
   height: fit-content;
@@ -312,7 +315,7 @@ const clampedETAValue = computed(() => {
 }
 :deep(.el-input-group--append .el-input-group__append .el-select .el-select__wrapper) {
   padding: 5px 10px;
-  background-color: #fff;
+  background-color: var(--color-system-body-bg);
 }
 .delayedIcon {
   margin: 0 8px;
@@ -326,7 +329,8 @@ const clampedETAValue = computed(() => {
 :deep(.el-radio) {
   display: flex;
   min-height: 32px;
-  border: 1px solid var(--color-select-border);
+  border: 1px solid var(--color-system-border);
+  background-color: var(--color-system-body-bg);
   margin-bottom: 4px;
   border-radius: 6px;
   padding: 0 8px;
@@ -338,4 +342,10 @@ const clampedETAValue = computed(() => {
 :deep(.el-radio__input.is-checked + .el-radio__label) {
   color: var(--color-neutral-1);
 }
+:deep(.el-input__wrapper) {
+  box-shadow: 0 0 0 1px var(--color-system-border-1) inset;
+}
+:deep(.el-input-group--append .el-input-group__append .el-select .el-select__wrapper) {
+  box-shadow: 0 1px 0 0 var(--color-system-border-1) inset,0 -1px 0 0 var(--color-system-border-1) inset,-1px 0 0 0 var(--color-system-border-1) inset;
+}
 </style>

+ 5 - 1
src/components/AddRules/src/components/NotiFrequency.vue

@@ -362,7 +362,8 @@ defineExpose({
 :deep(.el-radio) {
   display: flex;
   min-height: 32px;
-  border: 1px solid var(--color-select-border);
+  border: 1px solid var(--color-system-border);
+  background-color: var(--color-system-body-bg);
   margin-bottom: 4px;
   border-radius: 6px;
   padding: 0 8px;
@@ -374,6 +375,9 @@ defineExpose({
 :deep(.el-radio__input.is-checked + .el-radio__label) {
   color: var(--color-neutral-1);
 }
+:deep( .el-radio__inner) {
+  border: 1px solid var(--color-system-checkbox-border);
+}
 .Daily {
   margin: 0 0 9px 0;
   display: flex;

+ 20 - 3
src/components/AddRules/src/components/NotiMethods.vue

@@ -1,5 +1,10 @@
 <script setup lang="ts">
-import { ref, watch } from 'vue'
+import { ref, watch, computed } from 'vue'
+import Light_methods from '../images/illustration_system massage@2x.png'
+import Dark_methods from '../images/illustration_system massage_darkmode@2x.png'
+import { useThemeStore } from '@/stores/modules/theme'
+
+const themeStore = useThemeStore()
 
 const checkMethodList = ref()
 checkMethodList.value = []
@@ -7,6 +12,9 @@ let savesubscribeobj: any = {}
 const props = defineProps({
   MethodsData: Object
 })
+const MethodsImg = computed(() => {
+  return themeStore.theme === 'dark' ? Dark_methods : Light_methods
+})
 
 const methods_data = ref(props.MethodsData)
 watch(
@@ -52,6 +60,15 @@ const changeMethod = (val: any) => {
   emits('ChangeMethodsAdd', checkMethodList.value, savesubscribeobj)
 }
 const user_type = localStorage.getItem('user_type')
+
+// 删除 Frequency tag
+const handleCloseMethods = () => {
+  checkMethodList.value = []
+}
+
+defineExpose({
+  handleCloseMethods
+})
 </script>
 <template>
   <div style="margin-top: 11px">
@@ -68,7 +85,7 @@ const user_type = localStorage.getItem('user_type')
         <el-checkbox class="methodcheckbox" value="By System Message">
           <div>By System Message</div>
           <div class="methos_image">
-            <img src="../images/illustration_system massage@2x.png" />
+            <img :src="MethodsImg" />
           </div>
         </el-checkbox>
       </el-checkbox-group>
@@ -82,7 +99,7 @@ const user_type = localStorage.getItem('user_type')
   height: fit-content;
   width: 49%;
   margin-right: 5px;
-  background-color: var(--color-dialog-header-bg);
+  background-color: var(--color-system-notification-bg);
   border-radius: 6px;
   padding: 11px 0 0 13px;
 }

+ 17 - 7
src/components/AddRules/src/components/RulesShipments.vue

@@ -29,9 +29,17 @@ watch(
 )
 
 const emit = defineEmits(['ChangeCheckRules'])
-const CheckChange = () => {
-  console.log(CheckedList.value)
-  emit('ChangeCheckRules', CheckedList.value)
+const selectedLables = ref([])
+const CheckChange = (val: any) => {
+  selectedLables.value = []
+  // 遍历选中的value值,找到对应的label值
+  val.forEach((value) => {
+    const option = CheckboxList.value.find((item) => item.value === value)
+    if (option) {
+      selectedLables.value.push(option.label)
+    }
+  })
+  emit('ChangeCheckRules', selectedLables.value, CheckedList.value)
 }
 </script>
 <template>
@@ -48,7 +56,9 @@ const CheckChange = () => {
               :key="item.label"
               :label="item.label"
               :value="item.value"
-            />
+            >
+              {{ item.label }}
+            </el-checkbox>
           </el-checkbox-group>
         </div>
       </el-collapse-item>
@@ -58,13 +68,13 @@ const CheckChange = () => {
 
 <style lang="scss" scoped>
 :deep(.el-checkbox-group) {
-  border: 1px solid var(--color-select-border);
+  border: 1px solid var(--color-system-border);
   border-radius: 5px;
 }
 :deep(.el-checkbox) {
   width: 100%;
-  background-color: var(--color-drawer-body-bg);
-  border-bottom: 1px solid var(--color-select-border);
+  background-color: var(--color-system-body-bg);
+  border-bottom: 1px solid var(--color-system-border);
   padding: 8px;
 }
 :deep(.el-checkbox:first-child) {

+ 3 - 0
src/components/AddRules/src/components/ShipmentRange.vue

@@ -193,4 +193,7 @@ defineExpose({
 :deep(.el-radio) {
   background-color: var(--color-drawer-body-bg);
 }
+:deep( .el-radio__inner) {
+  border: 1px solid var(--color-system-checkbox-border);
+}
 </style>

BIN
src/components/AddRules/src/images/illustration_system massage_darkmode@2x.png


+ 225 - 92
src/components/CreateAddRules/src/CreateAddRules.vue

@@ -8,6 +8,9 @@ import NotiFrequency from './components/NotiFrequency.vue'
 import ShipmentRange from './components/ShipmentRange.vue'
 import NotiMethods from './components/NotiMethods.vue'
 import submitsucessful from './images/submit_successful.png'
+import { useRouter } from 'vue-router'
+
+const router = useRouter()
 interface CheckboxItem {
   value: string
   label: string
@@ -17,11 +20,11 @@ interface Props {
   TitleType: String
 }
 const MilestoneOceanListInit = ref<CheckboxItem[]>([])
-const MilestoneOceanListChecked = ref()
+const MilestoneOceanListChecked = ref([])
 const MilestoneAirListInit = ref<CheckboxItem[]>([])
-const MilestoneAirListChecked = ref()
+const MilestoneAirListChecked = ref([])
 const ContainerOceanListInit = ref<CheckboxItem[]>([])
-const ContainerOceanListChecked = ref()
+const ContainerOceanListChecked = ref([])
 const props = defineProps<Props>()
 let savesubscribeobj: any = {}
 const RulesActive = ref([
@@ -31,13 +34,18 @@ const RulesActive = ref([
   'NotificationMethod'
 ])
 const OceanCheckList = ref()
+const OceanCheckListCode = ref()
 const AirCheckList = ref()
+const AirCheckListCode = ref()
 const ContainerOceanList = ref()
+const ContainerOceanCode = ref()
 const IsFirstActive = ref(true)
 const IsTwoActive = ref(true)
 const IsThreeActive = ref(true)
 const IsFourActive = ref(true)
 const UnableSaveVisible = ref(false)
+const SaveVisibleError = ref(false)
+const SaveVisibleDetected = ref(false)
 const SaveedVisible = ref(false)
 const DelayedDeparturedList = ref()
 const DelayedAirdList = ref()
@@ -56,6 +64,9 @@ const DelayedDataInitAir = ref()
 const OceanETDInit = ref()
 const AirETDInit = ref()
 const ShipmentRangeMil = ref()
+const ShipmentRangeCon = ref()
+const ShipmentRangeDep = ref()
+const ShipmentRangeETD = ref()
 
 const MonitoringList = ref()
 const getInitMonitoring = () => {
@@ -76,9 +87,11 @@ onMounted(() => {
   Initdata()
 })
 // 初始赋值
+const editTableidtwo = ref('')
 const Initdata = () => {
   if (sessionStorage.getItem('editTableid') != null) {
     const editTableid = sessionStorage.getItem('editTableid')
+    editTableidtwo.value = sessionStorage.getItem('editTableid')
     const editTablerules_type = sessionStorage.getItem('editTablerules_type')
     $api
       .EditMonitoringTable({
@@ -92,12 +105,82 @@ const Initdata = () => {
             MethodsDataMil.value = res.data.Milestone_Update
             ShipmentRangeMil.value = res.data.Milestone_Update
             MilestoneOceanListInit.value = res.data.Milestone_Update.OceanCheckBoxList
+            OceanCheckListCode.value = []
             MilestoneOceanListChecked.value = res.data.Milestone_Update.OceanCheckedList
-            OceanCheckList.value = res.data.Milestone_Update.OceanCheckedList
+            OceanCheckListCode.value = res.data.Milestone_Update.OceanCheckedList
+            OceanCheckList.value = []
+            // 遍历选中的value值,找到对应的label值
+            MilestoneOceanListChecked.value.forEach((value) => {
+              const option = MilestoneOceanListInit.value.find((item) => item.value === value)
+              if (option) {
+                OceanCheckList.value.push(option.label)
+              }
+            })
+            AirCheckListCode.value = []
             MilestoneAirListInit.value = res.data.Milestone_Update.AirCheckBoxList
             MilestoneAirListChecked.value = res.data.Milestone_Update.AirCheckedList
-            AirCheckList.value = res.data.Milestone_Update.AirCheckedList
+            AirCheckListCode.value = res.data.Milestone_Update.AirCheckedList
+            AirCheckList.value = []
+            // 遍历选中的value值,找到对应的label值
+            MilestoneAirListChecked.value.forEach((value) => {
+              const option = MilestoneAirListInit.value.find((item) => item.value === value)
+              if (option) {
+                AirCheckList.value.push(option.label)
+              }
+            })
             createListMilestone.value = res.data.Milestone_Update.shipment_details
+          } else if (editTablerules_type == 'Container_Status_Update') {
+            ContainerOceanCode.value = []
+            ContainerOceanListInit.value = res.data.Container_Status_Update.CtnrCheckBoxList
+            ContainerOceanListChecked.value = res.data.Container_Status_Update.CtnrCheckedList
+            ContainerOceanCode.value = res.data.Container_Status_Update.CtnrCheckedList
+            ContainerOceanList.value = []
+            // 遍历选中的value值,找到对应的label值
+            ContainerOceanListChecked.value.forEach((value) => {
+              const option = ContainerOceanListInit.value.find((item) => item.value === value)
+              if (option) {
+                ContainerOceanList.value.push(option.label)
+              }
+            })
+            FrequencyDataCon.value = res.data.Container_Status_Update
+            MethodsDataCon.value = res.data.Container_Status_Update
+            ShipmentRangeCon.value = res.data.Container_Status_Update
+          } else if (editTablerules_type == 'Departure/Arrival_Delay') {
+            let OceanObj: any = {}
+            OceanObj.atd_etd = res.data['Departure/Arrival_Delay'].ocean_atd_sub_etd
+            OceanObj.atd_etd_unit = res.data['Departure/Arrival_Delay'].ocean_atd_sub_etd_unit
+            OceanObj.ata_eta = res.data['Departure/Arrival_Delay'].ocean_ata_sub_eta
+            OceanObj.ata_eta_unit = res.data['Departure/Arrival_Delay'].ocean_ata_sub_eta_unit
+            DelayedDataInit.value = OceanObj
+            let AirObj: any = {}
+            AirObj.atd_etd = res.data['Departure/Arrival_Delay'].air_atd_sub_etd
+            AirObj.atd_etd_unit = res.data['Departure/Arrival_Delay'].air_atd_sub_etd_unit
+            AirObj.ata_eta = res.data['Departure/Arrival_Delay'].air_ata_sub_eta
+            AirObj.ata_eta_unit = res.data['Departure/Arrival_Delay'].air_ata_sub_eta_unit
+            DelayedDataInitAir.value = AirObj
+            FrequencyDataDep.value = res.data['Departure/Arrival_Delay']
+            MethodsDataDep.value = res.data['Departure/Arrival_Delay']
+            ShipmentRangeDep.value = res.data['Departure/Arrival_Delay']
+          } else if (editTablerules_type == 'ETD/ETA_Change') {
+            let OceanChange: any = {}
+            OceanChange.ETDradio = res.data['ETD/ETA_Change'].ocean_etd_change
+            OceanChange.etd_old_sub_new = res.data['ETD/ETA_Change'].ocean_etd_old_sub_new
+            OceanChange.etd_old_sub_new_unit = res.data['ETD/ETA_Change'].ocean_etd_old_sub_new_unit
+            OceanChange.ETAradio = res.data['ETD/ETA_Change'].ocean_eta_change
+            OceanChange.eta_old_sub_new = res.data['ETD/ETA_Change'].ocean_eta_old_sub_new
+            OceanChange.eta_old_sub_new_unit = res.data['ETD/ETA_Change'].ocean_eta_old_sub_new_unit
+            OceanETDInit.value = OceanChange
+            let AirChange: any = {}
+            AirChange.ETDradio = res.data['ETD/ETA_Change'].air_etd_change
+            AirChange.etd_old_sub_new = res.data['ETD/ETA_Change'].air_etd_old_sub_new
+            AirChange.etd_old_sub_new_unit = res.data['ETD/ETA_Change'].air_etd_old_sub_new_unit
+            AirChange.ETAradio = res.data['ETD/ETA_Change'].air_eta_change
+            AirChange.eta_old_sub_new = res.data['ETD/ETA_Change'].air_eta_old_sub_new
+            AirChange.eta_old_sub_new_unit = res.data['ETD/ETA_Change'].air_eta_old_sub_new_unit
+            AirETDInit.value = AirChange
+            FrequencyDataETD.value = res.data['ETD/ETA_Change']
+            MethodsDataETD.value = res.data['ETD/ETA_Change']
+            ShipmentRangeETD.value = res.data['ETD/ETA_Change']
           }
         }
       })
@@ -106,45 +189,16 @@ const Initdata = () => {
       sessionStorage.removeItem('editTablerules_type')
     }, 1000)
   }
-  // ContainerOceanListInit.value = val.CtnrCheckBoxList
-  // ContainerOceanListChecked.value = val.CtnrCheckedList
-  // ContainerOceanList.value = val.CtnrCheckedList
-  // let OceanObj: any = {}
-  // OceanObj.atd_etd = val.ocean_atd_sub_etd
-  // OceanObj.atd_etd_unit = val.ocean_atd_sub_etd_unit
-  // OceanObj.ata_eta = val.ocean_ata_sub_eta
-  // OceanObj.ata_eta_unit = val.ocean_ata_sub_eta_unit
-  // DelayedDataInit.value = OceanObj
-  // let AirObj: any = {}
-  // AirObj.atd_etd = val.air_atd_sub_etd
-  // AirObj.atd_etd_unit = val.air_atd_sub_etd_unit
-  // AirObj.ata_eta = val.air_ata_sub_eta
-  // AirObj.ata_eta_unit = val.air_ata_sub_eta_unit
-  // DelayedDataInitAir.value = AirObj
-  // let OceanChange: any = {}
-  // OceanChange.ETDradio = val.ocean_etd_change
-  // OceanChange.etd_old_sub_new = val.ocean_etd_old_sub_new
-  // OceanChange.etd_old_sub_new_unit = val.ocean_etd_old_sub_new_unit
-  // OceanChange.ETAradio = val.ocean_eta_change
-  // OceanChange.eta_old_sub_new = val.ocean_eta_old_sub_new
-  // OceanChange.eta_old_sub_new_unit = val.ocean_eta_old_sub_new_unit
-  // OceanETDInit.value = OceanChange
-  // let AirChange: any = {}
-  // AirChange.ETDradio = val.air_etd_change
-  // AirChange.etd_old_sub_new = val.air_etd_old_sub_new
-  // AirChange.etd_old_sub_new_unit = val.air_etd_old_sub_new_unit
-  // AirChange.ETAradio = val.air_eta_change
-  // AirChange.eta_old_sub_new = val.air_eta_old_sub_new
-  // AirChange.eta_old_sub_new_unit = val.air_eta_old_sub_new_unit
-  // AirETDInit.value = AirChange
 }
 
 // 给tag list赋值
-const ChangeCheckOceanRules = (val: any) => {
+const ChangeCheckOceanRules = (val: any, value: any) => {
   OceanCheckList.value = val
+  OceanCheckListCode.value = value
 }
-const ChangeContainerRules = (val: any) => {
+const ChangeContainerRules = (val: any, value: any) => {
   ContainerOceanList.value = val
+  ContainerOceanCode.value = value
 }
 // delayed赋值
 const ChangeDeayedRules = (val: any) => {
@@ -185,8 +239,9 @@ const ChangeAirRules = (val: any) => {
     delete savesubscribeobj.air_ata_sub_eta_unit
   }
 }
-const ChangeCheckAirRules = (val: any) => {
+const ChangeCheckAirRules = (val: any, value: any) => {
   AirCheckList.value = val
+  AirCheckListCode.value = value
 }
 
 //选择create new rules
@@ -478,24 +533,56 @@ const closeAirETD = (val: any) => {
   AirETD.value.closeETD(val)
 }
 
-const emits = defineEmits(['SavedAddedRules'])
-// 不保存修改的折叠面板
-
 // 保存subscribe配置
 const missingmessage = ref('')
 // 保存成功调用接口
 const SaveSuceessful = () => {
   $api
     .MonitoringSave({
-      ...savesubscribeobj
+      ...savesubscribeobj,
+      is_similar_rule: false,
+      id: editTableidtwo.value
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        console.log(res.data)
+        if(res.data.msg == 'Update Successful') {
+          SaveedVisible.value = true
+          setTimeout(() => {
+            SaveedVisible.value = false
+            sessionStorage.setItem('activeTab', 'Monitoring Settings')
+            router.push({
+              path: '/SystemSettings',
+              query: {}
+            })
+          }, 3000)
+        } else if(res.data.msg == 'Similar Rule Detected') {
+          SaveVisibleDetected.value = true
+        } else if(res.data.msg == 'Unable to Save')
+        SaveVisibleError.value = true
+      }
+    })
+}
+
+const HandelSaveVisibleDetected = () => {
+  SaveVisibleDetected.value = false
+  $api
+    .MonitoringSave({
+      ...savesubscribeobj,
+      is_similar_rule: true,
+      id: editTableidtwo.value
     })
     .then((res: any) => {
       if (res.code === 200) {
-        SaveedVisible.value = true
-        setTimeout(() => {
-          SaveedVisible.value = false
-          emits('SavedAddedRules', res.data.addedRules, savesubscribeobj.rules_type)
-        }, 3000)
+          SaveedVisible.value = true
+          setTimeout(() => {
+            SaveedVisible.value = false
+            sessionStorage.setItem('activeTab', 'Monitoring Settings')
+            router.push({
+              path: '/SystemSettings',
+              query: {}
+            })
+          }, 3000)
       }
     })
 }
@@ -507,22 +594,23 @@ const Savesubscribe = () => {
     if (
       OceanCheckList.value == undefined ||
       AirCheckList.value == undefined ||
+      OceanCheckList.value.length == 0 ||
+      AirCheckList.value.length == 0 ||
       MilFrequencyList.value.length == 0 ||
       MilMethodsList.value.length == 0 ||
       createObj.Transportstr == '' ||
       createObj.Timestr == ''
     ) {
-      console.log(createObj)
       if (createObj.Transportstr == '') {
         missingmessage.value += 'Transport Mode, '
       }
       if (createObj.Timestr == '') {
         missingmessage.value += 'Time, '
       }
-      if (OceanCheckList.value == undefined) {
+      if (OceanCheckList.value == undefined || OceanCheckList.value.length == 0) {
         missingmessage.value += 'Ocean Shipments, '
       }
-      if (AirCheckList.value == undefined) {
+      if (AirCheckList.value == undefined || AirCheckList.value.length == 0) {
         missingmessage.value += 'Air Shipments, '
       }
       if (MilFrequencyList.value.length == 0) {
@@ -534,8 +622,8 @@ const Savesubscribe = () => {
       missingmessage.value = missingmessage.value.substring(0, missingmessage.value.length - 2)
       UnableSaveVisible.value = true
     } else {
-      savesubscribeobj.ocean_milestone = OceanCheckList.value
-      savesubscribeobj.air_milestone = AirCheckList.value
+      savesubscribeobj.ocean_milestone = OceanCheckListCode.value
+      savesubscribeobj.air_milestone = AirCheckListCode.value
       str =
         'Ocean Milestones: ' +
         OceanCheckList.value.join(',') +
@@ -549,10 +637,11 @@ const Savesubscribe = () => {
     savesubscribeobj.rules_type = 'Container_Status_Update'
     if (
       ContainerOceanList.value == undefined ||
+      ContainerOceanList.value.length == 0 ||
       ConFrequencyList.value.length == 0 ||
       ConMethodsList.value.length == 0
     ) {
-      if (ContainerOceanList.value == undefined) {
+      if (ContainerOceanList.value == undefined || ContainerOceanList.value.length == 0) {
         missingmessage.value += 'Ocean Shipments, '
       }
       if (ConFrequencyList.value.length == 0) {
@@ -564,7 +653,7 @@ const Savesubscribe = () => {
       missingmessage.value = missingmessage.value.substring(0, missingmessage.value.length - 2)
       UnableSaveVisible.value = true
     } else {
-      savesubscribeobj.ocean_ctnr_status = ContainerOceanList.value
+      savesubscribeobj.ocean_ctnr_status = ContainerOceanCode.value
       str = 'Ocean Container: ' + ContainerOceanList.value.join(',')
       savesubscribeobj.event_details = str
       SaveSuceessful()
@@ -573,14 +662,16 @@ const Savesubscribe = () => {
     savesubscribeobj.rules_type = 'Departure/Arrival_Delay'
     if (
       DelayedDeparturedList.value == undefined ||
+      DelayedDeparturedList.value.length == 0 ||
       DelayedAirdList.value == undefined ||
+      DelayedAirdList.value.length == 0 ||
       DepFrequencyList.value.length == 0 ||
       DepMethodsList.value.length == 0
     ) {
-      if (DelayedDeparturedList.value == undefined) {
+      if (DelayedDeparturedList.value == undefined || DelayedDeparturedList.value.length == 0) {
         missingmessage.value += 'Ocean Shipments, '
       }
-      if (DelayedAirdList.value == undefined) {
+      if (DelayedAirdList.value == undefined || DelayedAirdList.value.length == 0) {
         missingmessage.value += 'Air Shipments, '
       }
       if (DepFrequencyList.value.length == 0) {
@@ -606,10 +697,10 @@ const Savesubscribe = () => {
       ETDFrequencyList.value.length == 0 ||
       ETDMethodsList.value.length == 0
     ) {
-      if (ETDOceanList.value == undefined) {
+      if (ETDOceanList.value == undefined || ETDOceanList.value.length == 0) {
         missingmessage.value += 'Ocean Shipments, '
       }
-      if (ETDAirList.value == undefined) {
+      if (ETDAirList.value == undefined || ETDAirList.value.length == 0) {
         missingmessage.value += 'Air Shipments, '
       }
       if (ETDFrequencyList.value.length == 0) {
@@ -673,7 +764,7 @@ defineExpose({
           <el-collapse-item name="ShipmentRange">
             <template #title>
               <div class="Rules_Title">
-                <span class="iconfont_icon">
+                <span class="iconfont_icon icon_dark">
                   <svg class="iconfont" aria-hidden="true">
                     <use
                       :xlink:href="IsFourActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
@@ -694,18 +785,21 @@ defineExpose({
               <ShipmentRange
                 ref="ShipmentRangeRef"
                 v-if="props.TitleType == 'Container'"
+                :ShipmentRangeData="ShipmentRangeCon"
                 @ChangeCheckRules="changecheckCreateRulesContainer"
                 @ChangeCheckTimeRules="ChangeCheckTimeRulesContainer"
               ></ShipmentRange>
               <ShipmentRange
                 ref="ShipmentRangeRef"
                 v-if="props.TitleType == 'Departure'"
+                :ShipmentRangeData="ShipmentRangeDep"
                 @ChangeCheckRules="changecheckCreateRulesDeparture"
                 @ChangeCheckTimeRules="ChangeCheckTimeRulesDeparture"
               ></ShipmentRange>
               <ShipmentRange
                 ref="ShipmentRangeRef"
                 v-if="props.TitleType == 'ETDChange'"
+                :ShipmentRangeData="ShipmentRangeETD"
                 @ChangeCheckRules="changecheckCreateRulesETDChange"
                 @ChangeCheckTimeRules="ChangeCheckTimeRulesETDChange"
               ></ShipmentRange>
@@ -720,7 +814,7 @@ defineExpose({
           <el-collapse-item name="SelectMilestone">
             <template #title>
               <div class="Rules_Title">
-                <span class="iconfont_icon">
+                <span class="iconfont_icon icon_dark">
                   <svg class="iconfont" aria-hidden="true">
                     <use
                       :xlink:href="IsFirstActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
@@ -756,7 +850,7 @@ defineExpose({
           <el-collapse-item name="SelectMilestone">
             <template #title>
               <div class="Rules_Title">
-                <span class="iconfont_icon">
+                <span class="iconfont_icon icon_dark">
                   <svg class="iconfont" aria-hidden="true">
                     <use
                       :xlink:href="IsFirstActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
@@ -784,7 +878,7 @@ defineExpose({
           <el-collapse-item name="SelectMilestone">
             <template #title>
               <div class="Rules_Title">
-                <span class="iconfont_icon">
+                <span class="iconfont_icon icon_dark">
                   <svg class="iconfont" aria-hidden="true">
                     <use
                       :xlink:href="IsFirstActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
@@ -820,7 +914,7 @@ defineExpose({
           <el-collapse-item name="SelectMilestone">
             <template #title>
               <div class="Rules_Title">
-                <span class="iconfont_icon">
+                <span class="iconfont_icon icon_dark">
                   <svg class="iconfont" aria-hidden="true">
                     <use
                       :xlink:href="IsFirstActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
@@ -856,7 +950,7 @@ defineExpose({
           <el-collapse-item name="NotificationFrequency">
             <template #title>
               <div class="Rules_Title">
-                <span class="iconfont_icon">
+                <span class="iconfont_icon icon_dark">
                   <svg class="iconfont" aria-hidden="true">
                     <use
                       :xlink:href="IsTwoActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
@@ -900,7 +994,7 @@ defineExpose({
           <el-collapse-item name="NotificationMethod">
             <template #title>
               <div class="Rules_Title">
-                <span class="iconfont_icon">
+                <span class="iconfont_icon icon_dark">
                   <svg class="iconfont" aria-hidden="true">
                     <use
                       :xlink:href="IsThreeActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
@@ -1075,6 +1169,65 @@ defineExpose({
     <div style="text-align: center"><el-image :src="submitsucessful" /></div>
     <div style="text-align: center; margin-top: 20px">Saved successfully</div>
   </el-dialog>
+  <!-- 保存失败 -->
+  <el-dialog v-model="SaveVisibleError" width="480">
+      <div>Duplicate Rule Error.</div>
+      <div>This rule exactly matches an existing rule.</div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button
+            class="el-button--danger"
+            @click="SaveVisibleError = false"
+            style="width: 100px"
+          >
+            OK
+          </el-button>
+        </div>
+      </template>
+      <template #header>
+        <div class="cancel_header">
+          <span class="iconfont_icon iconfont_warning">
+            <svg class="iconfont icon_danger" aria-hidden="true">
+              <use xlink:href="#icon-icon_fail_fill_b"></use>
+            </svg>
+          </span>
+          Unable to Save
+        </div>
+      </template>
+    </el-dialog>
+    <!-- 三项重合提示 -->
+  <el-dialog v-model="SaveVisibleDetected" width="480">
+      <div>A similar configuration rule already exists.</div>
+      <div>Would you like to proceed with creating this rule?</div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button
+            class="el-button--default"
+            @click="SaveVisibleDetected = false"
+            style="width: 100px"
+          >
+            Cancel
+          </el-button>
+          <el-button
+            class="el-button--warning"
+            @click="HandelSaveVisibleDetected"
+            style="width: 100px"
+          >
+            Save
+          </el-button>
+        </div>
+      </template>
+      <template #header>
+        <div class="cancel_header">
+          <span class="iconfont_icon iconfont_warning">
+            <svg class="iconfont icon_warning" aria-hidden="true">
+              <use xlink:href="#icon-icon_tipsfilled_b"></use>
+            </svg>
+          </span>
+          Similar Rule Detected
+        </div>
+      </template>
+    </el-dialog>
 </template>
 
 <style lang="scss" scoped>
@@ -1124,7 +1277,7 @@ defineExpose({
   padding: 0 !important;
 }
 :deep(.el-collapse-item__header):hover {
-  background-color: #fff !important;
+  background-color: var(--color-system-color-bg) !important;
   border: none !important;
 }
 :deep(.el-collapse-item__arrow) {
@@ -1150,26 +1303,26 @@ defineExpose({
   transform: rotate(0);
 }
 :deep(.Ocean_collapse .el-collapse-item__header) {
-  background-color: #fff !important;
+  background-color: var(--color-system-color-bg) !important;
   padding: 0 8px !important;
   height: 40px !important;
 }
 :deep(.Ocean_collapse .el-collapse-item) {
-  background-color: var(--color-dialog-header-bg);
+  background-color: var(--color-system-color-bg);
   border-radius: 12px;
 }
 :deep(.Ocean_collapse .el-collapse-item__wrap) {
   padding: 0 8px !important;
 }
 :deep(.Ocean_collapse .el-collapse-item__header.is-active) {
-  background-color: var(--color-dialog-header-bg) !important;
+  background-color: var(--color-system-color-bg) !important;
 }
 :deep(.el-checkbox__input.is-checked + .el-checkbox__label) {
   color: var(--color-neutral-1);
 }
 .Rules_buttom {
   padding: 8px;
-  border-top: 1px solid var(--color-border-1);
+  border-top: 1px solid var(--color-system-border-1);
 }
 .rules_button {
   width: 100px;
@@ -1193,7 +1346,7 @@ defineExpose({
   align-items: center;
 }
 :deep(header.el-dialog__header) {
-  background-color: #fff;
+  background-color: var(--color-system-body-bg);
 }
 :deep(footer.el-dialog__footer) {
   border-top: none;
@@ -1201,24 +1354,4 @@ defineExpose({
 :deep(.el-collapse) {
   margin-right: 8px;
 }
-
-:deep(.el-radio-group) {
-  display: block;
-}
-:deep(.el-radio) {
-  display: flex;
-  min-height: 32px;
-  border: 1px solid var(--color-select-border);
-  margin-bottom: 4px;
-  border-radius: 6px;
-  padding: 0 8px;
-  margin-right: 0;
-  height: fit-content;
-  line-height: 32px;
-  align-items: start;
-  align-items: start;
-}
-:deep(.el-radio__input.is-checked + .el-radio__label) {
-  color: var(--color-neutral-1);
-}
 </style>

+ 1 - 0
src/components/CreateAddRules/src/components/AddedrluesTag.vue

@@ -58,6 +58,7 @@ const handleClose = (tag: any) => {
   padding: 8px !important;
   max-height: 400px;
   overflow-y: scroll;
+  background-color: var(--color-system-card-bg);
 }
 :deep(.el-tag .el-tag__close svg) {
   width: 16px !important;

+ 13 - 4
src/components/CreateAddRules/src/components/DelayedType.vue

@@ -228,13 +228,13 @@ defineExpose({
 
 <style lang="scss" scoped>
 :deep(.el-checkbox-group) {
-  border: 1px solid var(--color-select-border);
+  border: 1px solid var(--color-system-border);
   border-radius: 5px;
 }
 :deep(.el-checkbox) {
   width: 100%;
-  background-color: var(--color-drawer-body-bg);
-  border-bottom: 1px solid var(--color-select-border);
+  background-color: var(--color-system-body-bg);
+  border-bottom: 1px solid var(--color-system-border);
   padding: 8px;
 }
 :deep(.el-checkbox:first-child) {
@@ -264,7 +264,7 @@ defineExpose({
 }
 :deep(.el-input-group--append .el-input-group__append .el-select .el-select__wrapper) {
   padding: 5px 10px;
-  background-color: #fff;
+  background-color: var(--color-system-body-bg);
 }
 .delayedTitle {
   color: var(--color-neutral-2);
@@ -276,4 +276,13 @@ defineExpose({
 .oceanCheckbox {
   margin-bottom: 8px;
 }
+:deep(.el-radio) {
+  align-items: center !important;
+}
+:deep(.el-input__wrapper) {
+  box-shadow: 0 0 0 1px var(--color-system-border-1) inset;
+}
+:deep(.el-input-group--append .el-input-group__append .el-select .el-select__wrapper) {
+  box-shadow: 0 1px 0 0 var(--color-system-border-1) inset,0 -1px 0 0 var(--color-system-border-1) inset,-1px 0 0 0 var(--color-system-border-1) inset;
+}
 </style>

+ 16 - 6
src/components/CreateAddRules/src/components/ETDShipments.vue

@@ -273,13 +273,13 @@ const clampedETAValue = computed(() => {
 
 <style lang="scss" scoped>
 :deep(.el-checkbox-group) {
-  border: 1px solid var(--color-select-border);
+  border: 1px solid var(--color-system-border);
   border-radius: 5px;
 }
 :deep(.el-checkbox) {
   width: 100%;
-  background-color: var(--color-drawer-body-bg);
-  border-bottom: 1px solid var(--color-select-border);
+  background-color: var(--color-system-body-bg);
+  border-bottom: 1px solid var(--color-system-border);
   padding: 8px;
 }
 :deep(.el-checkbox:first-child) {
@@ -292,6 +292,9 @@ const clampedETAValue = computed(() => {
 :deep(.el-radio) {
   border: none !important;
 }
+:deep( .el-radio__inner) {
+  border: 1px solid var(--color-system-checkbox-border);
+}
 .delayedType {
   align-items: start;
   height: fit-content;
@@ -312,7 +315,7 @@ const clampedETAValue = computed(() => {
 }
 :deep(.el-input-group--append .el-input-group__append .el-select .el-select__wrapper) {
   padding: 5px 10px;
-  background-color: #fff;
+  background-color: var(--color-system-body-bg);
 }
 .delayedIcon {
   margin: 0 8px;
@@ -326,16 +329,23 @@ const clampedETAValue = computed(() => {
 :deep(.el-radio) {
   display: flex;
   min-height: 32px;
-  border: 1px solid var(--color-select-border);
+  border: 1px solid var(--color-system-border);
+  background-color: var(--color-system-body-bg);
   margin-bottom: 4px;
   border-radius: 6px;
   padding: 0 8px;
   margin-right: 0;
   height: fit-content;
   line-height: 32px;
-  align-items: center;
+  align-items: center !important;
 }
 :deep(.el-radio__input.is-checked + .el-radio__label) {
   color: var(--color-neutral-1);
 }
+:deep(.el-input__wrapper) {
+  box-shadow: 0 0 0 1px var(--color-system-border-1) inset;
+}
+:deep(.el-input-group--append .el-input-group__append .el-select .el-select__wrapper) {
+  box-shadow: 0 1px 0 0 var(--color-system-border-1) inset,0 -1px 0 0 var(--color-system-border-1) inset,-1px 0 0 0 var(--color-system-border-1) inset;
+}
 </style>

+ 5 - 1
src/components/CreateAddRules/src/components/NotiFrequency.vue

@@ -362,7 +362,8 @@ defineExpose({
 :deep(.el-radio) {
   display: flex;
   min-height: 32px;
-  border: 1px solid var(--color-select-border);
+  border: 1px solid var(--color-system-border);
+  background-color: var(--color-system-body-bg);
   margin-bottom: 4px;
   border-radius: 6px;
   padding: 0 8px;
@@ -371,6 +372,9 @@ defineExpose({
   line-height: 32px;
   align-items: start;
 }
+:deep( .el-radio__inner) {
+  border: 1px solid var(--color-system-checkbox-border);
+}
 :deep(.el-radio__input.is-checked + .el-radio__label) {
   color: var(--color-neutral-1);
 }

+ 10 - 2
src/components/CreateAddRules/src/components/NotiMethods.vue

@@ -1,5 +1,10 @@
 <script setup lang="ts">
 import { ref, watch } from 'vue'
+import Light_methods from '../images/illustration_system massage@2x.png'
+import Dark_methods from '../images/illustration_system massage_darkmode@2x.png'
+import { useThemeStore } from '@/stores/modules/theme'
+
+const themeStore = useThemeStore()
 
 const checkMethodList = ref()
 checkMethodList.value = []
@@ -7,6 +12,9 @@ let savesubscribeobj: any = {}
 const props = defineProps({
   MethodsData: Object
 })
+const MethodsImg = computed(() => {
+  return themeStore.theme === 'dark' ? Dark_methods : Light_methods
+})
 
 const methods_data = ref(props.MethodsData)
 watch(
@@ -68,7 +76,7 @@ const user_type = localStorage.getItem('user_type')
         <el-checkbox class="methodcheckbox" value="By System Message">
           <div>By System Message</div>
           <div class="methos_image">
-            <img src="../images/illustration_system massage@2x.png" />
+            <img :src="MethodsImg" />
           </div>
         </el-checkbox>
       </el-checkbox-group>
@@ -82,7 +90,7 @@ const user_type = localStorage.getItem('user_type')
   height: fit-content;
   width: 49%;
   margin-right: 5px;
-  background-color: var(--color-dialog-header-bg);
+  background-color: var(--color-system-notification-bg);
   border-radius: 6px;
   padding: 11px 0 0 13px;
 }

+ 14 - 6
src/components/CreateAddRules/src/components/RulesShipments.vue

@@ -29,9 +29,17 @@ watch(
 )
 
 const emit = defineEmits(['ChangeCheckRules'])
-const CheckChange = () => {
-  console.log(CheckedList.value)
-  emit('ChangeCheckRules', CheckedList.value)
+const selectedLables = ref([])
+const CheckChange = (val: any) => {
+  selectedLables.value = []
+  // 遍历选中的value值,找到对应的label值
+  val.forEach((value) => {
+    const option = CheckboxList.value.find((item) => item.value === value)
+    if (option) {
+      selectedLables.value.push(option.label)
+    }
+  })
+  emit('ChangeCheckRules', selectedLables.value, CheckedList.value)
 }
 </script>
 <template>
@@ -58,13 +66,13 @@ const CheckChange = () => {
 
 <style lang="scss" scoped>
 :deep(.el-checkbox-group) {
-  border: 1px solid var(--color-select-border);
+  border: 1px solid var(--color-system-border);
   border-radius: 5px;
 }
 :deep(.el-checkbox) {
   width: 100%;
-  background-color: var(--color-drawer-body-bg);
-  border-bottom: 1px solid var(--color-select-border);
+  background-color: var(--color-system-body-bg);
+  border-bottom: 1px solid var(--color-system-border);
   padding: 8px;
 }
 :deep(.el-checkbox:first-child) {

+ 35 - 9
src/components/CreateAddRules/src/components/ShipmentRange.vue

@@ -45,12 +45,12 @@ const ShipmentRangeInit = () => {
     TransportCheckedList.value = ShipmentRange_data.value?.shipment_transport_mode.split(';')
     CheckChange(ShipmentRange_data.value?.shipment_transport_mode)
   }
-  if (ShipmentRange_data.value?.shipment_eta_limit != undefined) {
+  if (ShipmentRange_data.value?.shipment_eta_limit != '') {
     ETATime.value = ShipmentRange_data.value?.shipment_eta_limit
     TimeChecked.value = 2
     changeTime(2)
   }
-  if (ShipmentRange_data.value?.shipment_etd_limit != undefined) {
+  if (ShipmentRange_data.value?.shipment_etd_limit != '') {
     ETDTime.value = ShipmentRange_data.value?.shipment_etd_limit
     TimeChecked.value = 1
     changeTime(1)
@@ -90,12 +90,13 @@ const CheckChange = (val: any) => {
 const changeTime = (val: any) => {
   if (val == 1) {
     Timestr = 'ETD within ' + clampedETDValue.value + ' Day(s)'
+  emit('ChangeCheckTimeRules', Timestr, clampedETDValue.value)
   } else if (val == 2) {
     Timestr = 'ETA within ' + clampedETAValue.value + ' Day(s)'
+  emit('ChangeCheckTimeRules', Timestr, clampedETAValue.value)
   } else {
     Timestr = ''
   }
-  emit('ChangeCheckTimeRules', Timestr, clampedETDValue.value)
 }
 
 const handleCloseCreateRule = (val: any) => {
@@ -171,13 +172,13 @@ defineExpose({
 
 <style lang="scss" scoped>
 :deep(.el-checkbox-group) {
-  border: 1px solid var(--color-select-border);
+  border: 1px solid var(--color-system-border);
   border-radius: 5px;
 }
 :deep(.el-checkbox) {
   width: 100%;
-  background-color: var(--color-drawer-body-bg);
-  border-bottom: 1px solid var(--color-select-border);
+  background-color: var(--color-system-body-bg);
+  border-bottom: 1px solid var(--color-system-border);
   padding: 8px;
 }
 :deep(.el-checkbox:first-child) {
@@ -210,9 +211,9 @@ defineExpose({
 }
 .Days {
   width: 64px;
-  border: 1px solid var(--color-select-border);
+  border: 1px solid var(--color-system-border);
   border-radius: 0 6px 6px 0;
-  background-color: var(--color-drawer-body-bg);
+  background-color: var(--color-system-body-bg);
   border-left: 0;
   display: flex;
   align-items: center;
@@ -220,7 +221,32 @@ defineExpose({
   opacity: 0.8;
   height: 28px;
 }
+:deep(.el-radio-group) {
+  display: block;
+}
 :deep(.el-radio) {
-  background-color: var(--color-drawer-body-bg);
+  display: flex;
+  min-height: 32px;
+  border: 1px solid var(--color-system-border);
+  background-color: var(--color-system-body-bg);
+  margin-bottom: 4px;
+  border-radius: 6px;
+  padding: 0 8px;
+  margin-right: 0;
+  height: fit-content;
+  line-height: 32px;
+  align-items: center;
+}
+:deep( .el-radio__inner) {
+  border: 1px solid var(--color-system-checkbox-border);
+}
+:deep(.el-radio__input.is-checked + .el-radio__label) {
+  color: var(--color-neutral-1);
+}
+:deep(.el-input__wrapper) {
+  box-shadow: 0 0 0 1px var(--color-system-border-1) inset;
+}
+:deep(.el-input-group--append .el-input-group__append .el-select .el-select__wrapper) {
+  box-shadow: 0 1px 0 0 var(--color-system-border-1) inset,0 -1px 0 0 var(--color-system-border-1) inset,-1px 0 0 0 var(--color-system-border-1) inset;
 }
 </style>

BIN
src/components/CreateAddRules/src/images/illustration_system massage_darkmode@2x.png


+ 165 - 9
src/components/NotificationMessageCard/src/NotificationMessageCard.vue

@@ -2,19 +2,175 @@
 import EventCard from './components/EventCard.vue'
 import PasswordCard from './components/PasswordCard.vue'
 import FeatureUpdateCard from './components/FeatureUpdateCard.vue'
+import { useNotificationMessage } from '@/stores/modules/notificationMessage'
+import { cloneDeep } from 'lodash'
 
-const props = defineProps<{
-  data: any
-}>()
+const notificationMsgStore = useNotificationMessage()
+const props = withDefaults(
+  defineProps<{
+    data: any
+    isObserver?: boolean // 是否开启监听
+    updateReadCardsOnChange?: boolean // 是否在数据变化时请求接口更新已读卡片
+  }>(),
+  {
+    isObserver: true,
+    updateReadCardsOnChange: true
+  }
+)
+
+const pageData = ref<any[]>([])
+
+const emit = defineEmits<{ seeAll: []; hasCardRead: []; viewMore: []; jumpTracking: [] }>()
+const handleSeeAll = () => {
+  emit('seeAll')
+}
+
+// 创建一个新的 IntersectionObserver 实例
+const observer = new IntersectionObserver(
+  (entries) => {
+    entries.forEach((entry) => {
+      const cardId = entry.target?.dataset?.cardId
+      if (entry.isIntersecting) {
+        // 将卡片设置为已经展示
+        notificationMsgStore.setReadCardMap(cardId)
+        const index = pageData.value.findIndex((item) => item.info.id === cardId)
+        if (index > -1) {
+          //  在1秒钟后将消息卡片设置为已读
+          setTimeout(() => {
+            pageData.value[index].info.isRead = true
+            emit('hasCardRead')
+          }, 400)
+        }
+      }
+    })
+  },
+  {
+    // 配置选项
+    root: null, // 使用视窗作为根元素
+    threshold: 0.1 // 当至少10%的元素进入视图时触发回调
+  }
+)
+const curPageAllCards = ref<any>([])
+// 监听元素是否在可视区域内
+const watchCards = () => {
+  curPageAllCards.value = document?.querySelectorAll('.notification-message-card')
+  curPageAllCards.value.forEach((card: any) => {
+    // const index = notificationMsgStore.notificationMsgList.indexOf(card.dataset.cardId)
+    // index < 0 &&
+    if (card.dataset.cardIsread === 'false' && card.dataset.cardId) {
+      notificationMsgStore.concatNotificationMsgList([card.dataset.cardId])
+
+      observer.observe(card)
+    }
+  })
+}
+
+const clearReadData = (data) => {
+  const curData = data || []
+  // 将当前页面剩余未读消息id从监听列表中移除
+  const idsToRemove = curData.map((item: any) => item.info.id)
+  notificationMsgStore.removeNotificationMsgList(idsToRemove)
+
+  // 清除当前页面的所有卡片的监听
+  curPageAllCards.value.forEach((card: any) => {
+    observer.unobserve(card)
+  })
+}
+
+const compareIdsInArrays = (arr1, arr2) => {
+  // 如果两个数组长度不同,则它们不可能有相同的id集合
+  if (arr1.length !== arr2.length) {
+    return false
+  }
+
+  // 提取两个数组中的所有有效id并转换为Set
+  const ids1 = new Set(arr1.filter((item) => item.info && item.info.id).map((item) => item.info.id))
+  const ids2 = new Set(arr2.filter((item) => item.info && item.info.id).map((item) => item.info.id))
+
+  // 比较两个Set的大小是否相同
+  if (ids1.size !== ids2.size) {
+    return false
+  }
+
+  // 检查ids1中的每个id是否都存在于ids2中
+  for (let id of ids1) {
+    if (!ids2.has(id)) {
+      return false
+    }
+  }
+
+  return true
+}
+
+if (props.isObserver) {
+  watch(
+    () => props.data,
+    (newData, oldData) => {
+      // 因为当父组件中数据已读未读状态变化时,也会触发props.data变化,此时不需要更新页面数据,根据id是否相等来判断这种情况
+      if (compareIdsInArrays(oldData || [], newData || [])) return
+
+      pageData.value = cloneDeep(newData)
+      // 先清除旧数据中的卡片监听
+      clearReadData(oldData)
+
+      // 请求接口将旧数据中的新已读卡片上传服务器
+      props.data.updateReadCardsOnChange && notificationMsgStore.markMessageAsRead()
+
+      // 重新监听新数据中的卡片
+      nextTick(() => {
+        watchCards()
+      })
+    },
+    {
+      immediate: true,
+      deep: true
+    }
+  )
+}
+
+// 定时将消息卡片未读的置为已读,五分钟一次
+let timer = null
+onMounted(() => {
+  if (props.isObserver) {
+    timer = setInterval(() => {
+      notificationMsgStore.markMessageAsRead()
+    }, 300000)
+  }
+})
+onUnmounted(() => {
+  if (props.isObserver) {
+    notificationMsgStore.markMessageAsRead()
+    clearReadData(pageData.value)
+    clearInterval(timer)
+  }
+})
+
+const handleViewMore = () => {
+  emit('viewMore')
+}
 </script>
 
 <template>
-  <div class="notification-message-card">
-    <template v-for="(item, index) in data" :key="index">
-      <EventCard v-if="item.notificationType === 'event'" :data="item.info" />
-      <PasswordCard v-else-if="item.notificationType === 'password'" :data="item.info" />
-      <FeatureUpdateCard v-else-if="item.notificationType === 'feature'" :data="item.info" />
-    </template>
+  <div
+    class="notification-message-card"
+    :data-card-id="item.info.id"
+    :data-card-isread="item.info.isRead"
+    v-for="item in pageData"
+    :key="item.info.id || Math.random()"
+  >
+    <EventCard
+      @seeAll="handleSeeAll"
+      @jump-tracking="emit('jumpTracking')"
+      v-if="item.notificationType === 'event'"
+      :data="item.info"
+    />
+    <PasswordCard v-else-if="item.notificationType === 'password'" :data="item.info" />
+    <FeatureUpdateCard
+      @view-more="handleViewMore"
+      v-else-if="item.notificationType === 'feature'"
+      :data="item.info"
+    />
+    <slot></slot>
   </div>
 </template>
 

+ 66 - 51
src/components/NotificationMessageCard/src/components/EventCard.vue

@@ -18,7 +18,17 @@ interface EventCardPropsData {
   timezone?: string // 时区
   time: string
   timeLabel: string
-  previous?: Array<string>
+  serial_no?: string // 单号 用来跳转到Tracking详情页
+  order_from?: string // 订单来源 用来跳转到Tracking详情页
+  insert_date_format?: string // 用来跳转到System Message详情页
+  frequency_type?: string // 用来跳转到System Message详情页
+  rules_type?: string // 用来跳转到System Message详情页
+  previous?: {
+    date: string
+    tag: string
+    time: string
+    timezone: string
+  }
   info?: {
     route?: []
     etdOrdeparturNum?: number
@@ -35,14 +45,26 @@ const props = defineProps<{
   data: EventCardPropsData
 }>()
 
-const handleSeeAll = (data: any) => {
+const emit = defineEmits<{ seeAll: []; jumpTracking: [] }>()
+const handleSeeAll = (data: EventCardPropsData) => {
+  emit('seeAll')
   router.push({
     name: 'System Message Detail',
     query: {
-      frequency_type: data.frequency_type
+      frequency_type: data.frequency_type,
+      insert_date_format: data.insert_date_format,
+      rules_type: data.rules_type
     }
   })
 }
+
+const jumpTracking = (data: EventCardPropsData) => {
+  emit('jumpTracking')
+  router.push({
+    path: '/tracking/detail',
+    query: { a: data.serial_no, _schemas: data.order_from }
+  })
+}
 </script>
 
 <template>
@@ -54,48 +76,28 @@ const handleSeeAll = (data: any) => {
     <div class="content">
       <div
         class="more-tips"
-        v-if="(data.type === 'milestone' || 'container') && data.numericRecords"
+        v-if="(data.type === 'milestone' || data.type === 'container') && data.numericRecords"
       >
-        <span>Latest Status Updates ({{ data.numericRecords }})</span>
+        <span>Latest Status Updates ({{ data.numericRecords }}) </span>
         <el-button @click="handleSeeAll(data)" class="see-all-icon el-button--text">
           See All
           <span class="font_family icon-icon_next_b"></span>
         </el-button>
       </div>
-      <div
-        class="more-tips"
-        v-if="data.type === 'delay' && (data.info.etdOrdeparturNum || data.info.etaOrarrivalNum)"
-      >
+      <div class="more-tips" v-if="data.info?.etdOrdeparturNum || data.info?.etaOrarrivalNum">
         <div>
-          <span v-if="data.info.etdOrdeparturNum"
-            >Departure Delay ({{ data.info.etdOrdeparturNum }})</span
+          <span v-if="data.info?.etdOrdeparturNum"
+            >{{ data.type === 'delay' ? 'Departure Delay' : 'ETD Change' }} ({{
+              data.info?.etdOrdeparturNum
+            }})</span
           >
-          <span v-if="data.info.etdOrdeparturNum && data.info.etaOrarrivalNum">
+          <span v-if="data.info?.etdOrdeparturNum && data.info?.etaOrarrivalNum">
             &nbsp;&nbsp;|&nbsp;&nbsp;</span
           >
-          <span v-if="data.info.etaOrarrivalNum">
-            Arrival Delay ({{ data.info.etaOrarrivalNum }})
-          </span>
-        </div>
-        <el-button @click="handleSeeAll(data)" class="see-all-icon el-button--text">
-          See All
-          <span class="font_family icon-icon_next_b"></span>
-        </el-button>
-      </div>
-
-      <div
-        class="more-tips"
-        v-if="data.type === 'change' && (data.info.etdOrdeparturNum || data.info.etaOrarrivalNum)"
-      >
-        <div>
-          <span v-if="data.info.etdOrdeparturNum"
-            >ETD Change ({{ data.info.etdOrdeparturNum }})</span
-          >
-          <span v-if="data.info.etdOrdeparturNum && data.info.etaOrarrivalNum">
-            &nbsp;&nbsp;|&nbsp;&nbsp;</span
-          >
-          <span v-if="data.info.etaOrarrivalNum">
-            ETA Change ({{ data.info.etaOrarrivalNum }})
+          <span v-if="data.info?.etaOrarrivalNum">
+            {{ data.type === 'delay' ? 'Arrival Delay' : 'ETA Change' }} ({{
+              data.info?.etaOrarrivalNum
+            }})
           </span>
         </div>
         <el-button @click="handleSeeAll(data)" class="see-all-icon el-button--text">
@@ -108,7 +110,7 @@ const handleSeeAll = (data: any) => {
         <!-- 除了container类型,其他类型都显示运输方式图标 -->
         <div style="display: inline-block" v-if="data.type !== 'container'">
           <span class="font_family" :class="[`icon-${transportationMode?.[data.mode]}`]"></span>
-          <span class="no">HBOL: {{ data.no }}</span>
+          <span @click="jumpTracking(data)" class="no no-link">HBOL: {{ data.no }}</span>
         </div>
         <!-- container类型显示图标 -->
         <div v-else>
@@ -143,15 +145,15 @@ const handleSeeAll = (data: any) => {
       </div>
       <div
         :class="{ 'delay-time': data.type === 'delay', 'change-time': data.type === 'change' }"
-        v-if="(data.type === 'delay' || 'change') && data.info?.time"
+        v-if="(data.type === 'delay' || data.type === 'change') && data.info?.time"
       >
         <span
           v-if="data.type === 'delay'"
-          style="margin-right: 5px"
+          style="margin-right: 6px"
           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: 2px" v-if="data.info.timeLabel">{{ data.info.timeLabel }}:</span>
         <span style="margin-right: 3px">{{
           dayjs(data.info.time).format('MMM DD, YYYY hh:mm')
         }}</span>
@@ -163,13 +165,16 @@ const handleSeeAll = (data: any) => {
       </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" v-if="data.timeLabel">{{ 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>
-        <span>{{ data.previous }}</span>
+        <span
+          >{{ data.previous.tag }}&nbsp;({{ data.previous.time }}
+          {{ getTimezone(data.previous.timezone) }})</span
+        >
       </div>
     </div>
   </div>
@@ -201,6 +206,7 @@ const handleSeeAll = (data: any) => {
       margin-right: 10px;
     }
     .title {
+      flex: 1;
       font-weight: 700;
     }
   }
@@ -232,16 +238,24 @@ const handleSeeAll = (data: any) => {
         font-weight: 700;
         line-height: 18px;
       }
+      .no-link {
+        &:hover {
+          color: var(--color-theme);
+          cursor: pointer;
+        }
+      }
       .tag {
         display: flex;
         align-items: center;
+        height: 18px;
+        margin-top: 2px;
         margin-left: 4px;
         padding-left: 8px;
         padding-right: 6px;
-        background-color: #e6f1eb;
+        background-color: var(--color-milestone-tag-bg);
         border-radius: 3px;
         &.delay {
-          background-color: #f7e7e9;
+          background-color: var(--color-delay-tag-bg);
           .dot {
             background-color: #c9353f;
           }
@@ -250,7 +264,7 @@ const handleSeeAll = (data: any) => {
           }
         }
         &.change {
-          background-color: #f5f2e6;
+          background-color: var(--color-change-tag-bg);
           .dot {
             background-color: #edb82f;
           }
@@ -267,7 +281,6 @@ const handleSeeAll = (data: any) => {
           margin-right: 4px;
         }
         .text {
-          margin-top: 2px;
           font-size: 10px;
           font-weight: 600;
           color: #5bb462;
@@ -293,7 +306,7 @@ const handleSeeAll = (data: any) => {
       span {
         color: var(--color-neutral-2);
         font-size: 12px;
-        line-height: 16px;
+        line-height: 14px;
       }
       .font_family {
         font-size: 16px;
@@ -303,19 +316,22 @@ const handleSeeAll = (data: any) => {
       }
     }
     div.delay-time {
+      height: 19px;
       margin-top: 6px;
+      margin-bottom: -2px;
       span,
       .font_family {
         color: #c9353f;
       }
       span {
+        display: inline-block;
+        margin-top: 3px;
         line-height: 19px;
       }
       .font_family {
+        margin: 0 6px 0 -1px;
         line-height: 12px;
-        margin-left: -1px;
-        margin-right: 6px;
-        font-size: 19px;
+        font-size: 18px;
       }
     }
     div.change-time {
@@ -325,11 +341,10 @@ const handleSeeAll = (data: any) => {
       }
     }
     .previous {
-      height: 24px;
       margin-top: 8px;
       padding-left: 8px;
       line-height: 24px;
-      background-color: #e1e3e9;
+      background-color: var(--color-previous-bg);
       border-radius: 6px;
       span {
         font-size: 12px;

+ 46 - 3
src/components/NotificationMessageCard/src/components/FeatureUpdateCard.vue

@@ -1,5 +1,10 @@
 <script setup lang="ts">
+import { useRouter } from 'vue-router'
+
+const router = useRouter()
+
 interface FeatureUpdateCardPropsData {
+  id: string
   title: string
   header: string
   content: string
@@ -10,6 +15,21 @@ interface FeatureUpdateCardPropsData {
 const props = defineProps<{
   data: FeatureUpdateCardPropsData
 }>()
+
+const emit = defineEmits<{
+  viewMore: []
+}>()
+const handleViewMore = () => {
+  router.push({
+    name: 'System Message Detail',
+    query: {
+      type: 'feature',
+      title: props.data.header,
+      id: props.data.id
+    }
+  })
+  emit('viewMore')
+}
 </script>
 
 <template>
@@ -28,10 +48,16 @@ const props = defineProps<{
         <div class="content-text">
           <span>{{ data.content }}</span>
         </div>
-        <img src="../images/test.png" style="margin: 12px 0 16px 24px; border-radius: 8px" alt="" />
+        <div class="feature-img">
+          <el-image :src="data.imgSrc" fit="contain" alt="feature-img"></el-image>
+        </div>
 
         <div class="change-btn" style="text-align: center">
-          <el-button class="el-button--main" style="height: 40px; padding: 0 32px">
+          <el-button
+            @click="handleViewMore"
+            class="el-button--main"
+            style="height: 40px; padding: 0 32px"
+          >
             View more</el-button
           >
         </div>
@@ -70,7 +96,12 @@ const props = defineProps<{
   }
   .card-content {
     padding: 16px 16px 24px 8px;
-    background: linear-gradient(137deg, #fff4eb 12.41%, #f0f3ff 52.63%, #e0f7f9 93.28%);
+    background: linear-gradient(
+      137deg,
+      var(--color-feature-card-first-bg) 12.41%,
+      var(--color-feature-card-second-bg) 52.63%,
+      var(--color-feature-card-third-bg) 93.28%
+    );
     border-radius: 12px;
     .title {
       img {
@@ -96,6 +127,18 @@ const props = defineProps<{
     .content-text {
       margin: 8px 0 0px 24px;
     }
+    .feature-img {
+      margin-top: 12px;
+      margin-bottom: 16px;
+      padding-left: 24px;
+      .el-image {
+        display: block;
+        margin: 0 auto;
+        width: 352px;
+        height: 200px;
+        border-radius: 8px;
+      }
+    }
   }
 }
 </style>

+ 24 - 6
src/components/NotificationMessageCard/src/components/PasswordCard.vue

@@ -1,14 +1,21 @@
 <script setup lang="ts">
+import ChangePasswordDialog from '@/views/Layout/src/components/Header/components/ChangePasswordDialog.vue'
+
 interface PasswordCardPropsData {
   title: string
-  isExpiration: boolean
-  tips: string
+  isExpiration: boolean // true为红色,false为绿色
+  header: string
   isRead: boolean
   content: string
 }
 const props = defineProps<{
   data: PasswordCardPropsData
 }>()
+
+const changePasswordDialogRef = ref()
+const handleChangePassword = () => {
+  changePasswordDialogRef.value.openDialog()
+}
 </script>
 
 <template>
@@ -20,18 +27,19 @@ const props = defineProps<{
     <div class="card-content" :class="{ 'is-expired': data.isExpiration }">
       <div class="title">
         <span class="font_family icon-icon_password_b"></span>
-        <span>{{ data.tips }}</span>
+        <span>{{ data.header }}</span>
       </div>
       <div class="details">
         {{ data.content }}
       </div>
       <div class="change-btn" style="text-align: center">
-        <el-button class="el-button--main" style="height: 40px">
+        <el-button @click="handleChangePassword" class="el-button--main" style="height: 40px">
           <span class="font_family icon-icon_edit_b" style="margin-right: 4px"></span>
           <span>Change Password</span></el-button
         >
       </div>
     </div>
+    <ChangePasswordDialog ref="changePasswordDialogRef"></ChangePasswordDialog>
   </div>
 </template>
 
@@ -65,15 +73,24 @@ const props = defineProps<{
   }
   .card-content {
     padding: 16px 8px 24px;
-    background: linear-gradient(180deg, #ffe294 0%, #f6f8fa 100%);
+    background: linear-gradient(
+      180deg,
+      var(--color-password-card-first-bg) 0%,
+      var(--color-password-card-second-bg) 100%
+    );
     border-radius: 12px;
     &.is-expired {
-      background: linear-gradient(182deg, #ef99a0 2.2%, #f6f8fa 98.77%);
+      background: linear-gradient(
+        182deg,
+        var(--color-password-expired-card-first-bg) 2.2%,
+        var(--color-password-expired-card-second-bg) 98.77%
+      );
     }
     .title {
       span {
         vertical-align: middle;
         font-weight: 700;
+        color: #2b2f36;
       }
       .font_family {
         margin-right: 8px;
@@ -81,6 +98,7 @@ const props = defineProps<{
     }
     .details {
       margin: 8px 0 16px 24px;
+      color: #2b2f36;
     }
   }
 }

BIN
src/components/NotificationMessageCard/src/images/test.png


+ 21 - 1
src/components/VSliderVerification/src/VSliderVerification.vue

@@ -34,6 +34,10 @@ const addTipsNode = () => {
   const childNode = document.createElement('div')
   childNode.className = 'tips'
   childNode.innerHTML = `
+    <div style="margin-bottom: 15px; text-align: right;">
+      <span class="font_family icon-icon_reject_b close-icon" style="margin-right: -20px;">
+      </span>
+    </div>
     <p>Please drag the slider below to complete the</p>
     <p>verification to ensure normal access</p>
   `
@@ -43,6 +47,14 @@ const addTipsNode = () => {
   } else {
     parentNode.appendChild(childNode)
   }
+  nextTick(() => {
+    const targetNode: any = document.querySelector('.font_family.icon-icon_reject_b.close-icon')
+    targetNode.onclick = closeDialog
+  })
+}
+
+const closeDialog = () => {
+  emit('close')
 }
 
 const styleMap = {
@@ -88,6 +100,7 @@ const updateSliderBackground = (state: string) => {
 
 const emit = defineEmits<{
   close: []
+  success: []
 }>()
 // 监听验证成功事件,因为这里库中的成功事件有0.8秒的延迟,所以这里手动监听验证成功事件
 const onSuccess = () => {
@@ -101,7 +114,7 @@ const onSuccess = () => {
             updateSliderBackground('success')
             addSliderBtnNode('success')
             setTimeout(() => {
-              emit('close')
+              emit('success')
               isShow.value = false
             }, 500)
           }
@@ -163,12 +176,19 @@ defineExpose({
   width: 400px;
   height: 373px;
   padding: 40px;
+  padding-top: 20px;
   background-color: var(--color-slider-bg);
   border-radius: 16px;
   box-shadow: -2px 2px 12px 0 rgba(0, 0, 0, 0.5);
   .tips {
     margin-bottom: 16px;
     text-align: center;
+    .close-icon {
+      cursor: pointer;
+      &:hover {
+        color: var(--color-theme);
+      }
+    }
   }
   .icon-border {
     display: flex;

+ 10 - 1
src/router/index.ts

@@ -97,7 +97,8 @@ const router = createRouter({
           path: '/system-message-detail',
           name: 'System Message Detail',
           meta: {
-            breadName: 'Detail'
+            breadName: 'Detail',
+            activeMenu: '/system-message'
           },
           component: () => import('../views/SystemMessage/src/components/SystemMessageDetail.vue')
         },
@@ -132,6 +133,14 @@ router.beforeEach(async (to, from, next) => {
     sessionStorage.removeItem('bookingTablePageInfo')
   }
 
+  // 判断是否从systemMessage详情页跳转到systemMessage列表页,或者从systemMessage列表页跳转到systemMessage详情页
+  if (
+    !(from.name === 'System Message Detail' || from.name === 'System Message' || !from.name) ||
+    !(to.name === 'System Message' || to.name === 'System Message Detail')
+  ) {
+    sessionStorage.removeItem('activeCardTypeName')
+  }
+
   // 未登录白名单
   const whiteList = ['/login', '/public-tracking', '/public-tracking/detail', '/reset-password']
   // 判断是否登录

+ 71 - 0
src/stores/modules/notificationMessage.ts

@@ -0,0 +1,71 @@
+import { defineStore } from 'pinia'
+
+interface NotificationMessageState {
+  notificationMsgList: string[] // 页面中监听的未读消息id
+  readCardMap: string[] // 已读的消息id
+  hasNewMsg: boolean
+}
+
+export const useNotificationMessage = defineStore('notificationMessage', {
+  state: (): NotificationMessageState => ({
+    notificationMsgList: JSON.parse(localStorage.getItem('notificationMsgList')) || [],
+    readCardMap: JSON.parse(localStorage.getItem('readCardMap')) || [],
+    hasNewMsg: JSON.parse(localStorage.getItem('hasNewMsg')) || false
+  }),
+  getters: {},
+  actions: {
+    concatNotificationMsgList(array: any[]) {
+      this.notificationMsgList = [...new Set([...this.notificationMsgList, ...array])]
+      localStorage.setItem('notificationMsgList', JSON.stringify(this.notificationMsgList))
+    },
+    removeNotificationMsgList(array: any[]) {
+      this.notificationMsgList = this.notificationMsgList.filter((item) => !array.includes(item))
+
+      localStorage.setItem('notificationMsgList', JSON.stringify(this.notificationMsgList))
+    },
+    setReadCardMap(id: string) {
+      // 将页面中从未读到已读的消息id存入readCardMap
+      if (this.readCardMap.includes(id)) return
+      this.readCardMap.push(id)
+      localStorage.setItem('readCardMap', JSON.stringify(this.readCardMap))
+      // 将已读的消息从notificationMsgList中删除
+      this.notificationMsgList = this.notificationMsgList.filter((item) => {
+        return !this.readCardMap.includes(item)
+      })
+      localStorage.setItem('notificationMsgList', JSON.stringify(this.notificationMsgList))
+    },
+    hasUnreadMessages() {
+      $api.hasUnreadMessages().then((res) => {
+        if (res.code === 200) {
+          this.hasNewMsg = res.data.has_message
+          localStorage.setItem('hasNewMsg', JSON.stringify(this.hasNewMsg))
+        }
+      })
+    },
+    setHasNewMsg() {
+      this.hasNewMsg = true
+      localStorage.setItem('hasNewMsg', JSON.stringify(this.hasNewMsg))
+    },
+    async markMessageAsRead() {
+      if (this.readCardMap.length === 0) return
+
+      await $api.setMessageRead({ id: this.readCardMap }).then((res) => {
+        if (res.code === 200) {
+          this.readCardMap = []
+          localStorage.setItem('readCardMap', JSON.stringify(this.readCardMap))
+
+          // 在将消息标记为已读后,再次检查是否有新消息
+          this.hasUnreadMessages()
+        }
+      })
+    },
+    clearData() {
+      this.notificationMsgList = []
+      this.readCardMap = []
+      this.hasNewMsg = false
+      localStorage.removeItem('hasNewMsg')
+      localStorage.removeItem('notificationMsgList')
+      localStorage.removeItem('readCardMap')
+    }
+  }
+})

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

@@ -1,5 +1,6 @@
 import { defineStore } from 'pinia'
 import { useVisitedRowState } from './visitedRow'
+import { useNotificationMessage } from './notificationMessage'
 import dayjs from 'dayjs'
 
 interface UserInfo {
@@ -91,6 +92,7 @@ export const useUserStore = defineStore('user', {
       }
       this.isFirstLogin = false
       useVisitedRowState().clearVisitedRow()
+      useNotificationMessage().clearData()
     }
   }
 })

+ 3 - 4
src/styles/elementui.scss

@@ -338,7 +338,7 @@ div.el-drawer {
     height: 64px;
     padding: 16px;
     margin-bottom: 0;
-    background-color: var(--color-table-header-bg);
+    background-color: var(--color-header-bg);
     & > span {
       font-weight: 700;
       font-size: 24px;
@@ -400,11 +400,10 @@ div .el-dropdown__popper .el-dropdown__list {
   user-select: none;
 }
 div .el-checkbox__inner:hover {
-  border-color: var(--color-select-border);
+  border-color: var(--color-system-checkbox-border);
 }
 div .el-checkbox__inner {
-  border-color: var(--color-select-border);
-  background-color: #fff;
+  border: 1px solid var(--color-system-checkbox-border);
 }
 div .el-checkbox__input.is-checked .el-checkbox__inner:after {
   border-color: var(--color-mode);

+ 8 - 0
src/styles/theme-g.scss

@@ -69,4 +69,12 @@
   --input-disabled-text-color: #66696f;
   --color-recent-name: rgba(240, 241, 243, 0.1);
   --color-disabled-bg: #2b2b2c;
+
+  --color-system-color-bg:#3f434a;
+  --color-system-border: #3f434a;
+  --color-system-checkbox-border: #3f434a;
+  --color-system-body-bg:#30353c;
+  --color-system-notification-bg:#343a43;
+  --color-system-card-bg:#2B2F36;
+  --color-system-border-1: #3F434A;
 }

+ 39 - 1
src/styles/theme.scss

@@ -257,6 +257,29 @@
   --color-upload-file-border-bg: #f5b279;
 
   --color-personal-preference-bg: #f5f7fa;
+
+  --color-password-card-first-bg: #ffe294;
+  --color-password-card-second-bg: #f6f8fa;
+  --color-password-expired-card-first-bg: #ef99a0;
+  --color-password-expired-card-second-bg: #f6f8fa;
+  --color-feature-card-first-bg: #fff4eb;
+  --color-feature-card-second-bg: #f0f3ff;
+  --color-feature-card-third-bg: #f0f3ff;
+
+  --color-milestone-tag-bg: #e6f1eb;
+  --color-delay-tag-bg: #f7e7e9;
+  --color-change-tag-bg: #f5f2e6;
+
+  --color-previous-bg: #e1e3e9;
+  --color-system-color-bg: #f6f8fa;
+  --color-system-border: #eaebed;
+  --color-system-checkbox-bg: #fff;
+  --color-system-checkbox-border: #eaebed;
+  --color-system-body-bg: #fff;
+  --color-system-notification-bg: #f6f8fa;
+  --color-system-card-bg: #fff;
+  --color-system-border-1: #e8eaee;
+  --color-personal-preference-bg: #f5f7fa;
 }
 
 :root.dark {
@@ -302,7 +325,7 @@
   --color-user-config-title-bottom-border: #3f434a;
   --color-container-status-node-bg: #3e454f;
 
-  --color-btn-blue-bg: rgba(255, 255, 255, 0);
+  --color-btn-blue-bg: rgba(248, 249, 253, 0.1);
 
   --color-el-btn-pain-theme-border: #ed6d00;
   --color-el-btn-pain-theme-text: #ed6d00;
@@ -337,6 +360,21 @@
   --color-v-box-content-drag-bg: #2b2f36;
 
   --color-input-disabled-border: #656f7d;
+
+  --color-password-card-first-bg: #ffa000;
+  --color-password-card-second-bg: #d6c587;
+  --color-password-expired-card-first-bg: #f25a66;
+  --color-password-expired-card-second-bg: #cda9ac;
+  --color-feature-card-first-bg: #334181;
+  --color-feature-card-second-bg: #c651bd;
+  --color-feature-card-third-bg: #ffca6e;
+  --color-milestone-tag-bg: #3c5249;
+  --color-delay-tag-bg: #523942;
+  --color-change-tag-bg: #564f36;
+
+  --color-previous-bg: #454b54;
+
+  --color-system-message-nav-bg: #343a43;
   // 邮件
   --w-e-toolbar-bg-color: var(--color-email-bg);
   --w-e-textarea-bg-color: var(--color-email-bg);

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

@@ -106,7 +106,7 @@ onMounted(async () => {
     replaceSvgByDataKey(item.dataMenuKey, svgUrl)
   }
 })
-function replaceSvgByDataKey(dataMenuKey: any, svgUrl: any) {
+const replaceSvgByDataKey = (dataMenuKey: any, svgUrl: any) => {
   const observer = new MutationObserver((mutationsList, observer) => {
     const element = document.querySelector(`[data-menu-key="${dataMenuKey}"]`)
     if (element) {

+ 2 - 2
src/views/Dashboard/src/components/RecentStatus.vue

@@ -83,7 +83,7 @@ const SubscribeShipments = (val: any) => {
           <span>{{ item.bookingNumber }}</span>
         </div>
       </div>
-      <!-- <div class="recent-header-right">
+      <div class="recent-header-right">
         <el-button
           class="recent_button"
           @click="SubscribeShipments(item)"
@@ -101,7 +101,7 @@ const SubscribeShipments = (val: any) => {
           </span>
           <span class="Subscribe">Subscribe</span>
         </el-button>
-      </div> -->
+      </div>
     </div>
     <div class="recent_content">
       <!-- 左 -->

+ 16 - 4
src/views/Layout/src/components/Header/HeaderView.vue

@@ -8,15 +8,19 @@ import { useHeaderSearch } from '@/stores/modules/headerSearch'
 import { onBeforeRouteUpdate } from 'vue-router'
 import { useLoadingState } from '@/stores/modules/loadingState'
 import { useThemeStore } from '@/stores/modules/theme'
+import { useNotificationMessage } from '@/stores/modules/notificationMessage'
 import NotificationDrawer from './components/NotificationDrawer.vue'
 import TrainingCard from './components/TrainingCard.vue'
 
+const notificationMsgStore = useNotificationMessage()
 const themeStore = useThemeStore()
 const userStore = useUserStore()
 const route = useRoute()
 const router = useRouter()
 const headerSearch = useHeaderSearch()
 
+const trainingCardRef = ref()
+
 // 切换系统主题颜色
 const toggleThemeMode = (theme: string) => {
   themeStore.toggleTheme(theme, true)
@@ -177,6 +181,14 @@ const closePopover = () => {
 }
 
 const notificationDrawer = ref(false)
+
+onBeforeRouteUpdate((to, from, next) => {
+  if (from.name === 'Login' && userStore.userName) {
+    notificationMsgStore.hasUnreadMessages()
+  }
+
+  next()
+})
 </script>
 
 <template>
@@ -188,7 +200,7 @@ const notificationDrawer = ref(false)
     element-loading-background="rgb(43, 47, 54, 0.7)"
   >
     <VBreadcrumb></VBreadcrumb>
-    <TrainingCard></TrainingCard>
+    <TrainingCard ref="trainingCardRef"></TrainingCard>
     <div class="right-info">
       <el-input
         v-model="searchValue"
@@ -200,8 +212,8 @@ const notificationDrawer = ref(false)
           <span style="margin-top: -1px" class="font_family icon-icon_search_b"></span>
         </template>
       </el-input>
-      <!-- <div class="notice-icon" v-if="userStore.userInfo?.uname">
-        <span class="unread-tip-icon"></span>
+      <div class="notice-icon" v-if="userStore.userInfo?.uname">
+        <span v-if="notificationMsgStore.hasNewMsg" class="unread-tip-icon"></span>
         <el-button
           style="height: 40px; width: 40px; margin-right: 0px"
           class="el-button--text"
@@ -209,7 +221,7 @@ const notificationDrawer = ref(false)
         >
           <span class="font_family icon-icon_notice_b" style="font-size: 18px"></span>
         </el-button>
-      </div> -->
+      </div>
       <!-- 
       <span class="font_family icon-icon_language_b" style="font-size: 16px"></span> -->
       <el-popover

+ 47 - 80
src/views/Layout/src/components/Header/components/NotificationDrawer.vue

@@ -1,11 +1,12 @@
 <script setup lang="ts">
 import NotificationMessageCard from '@/components/NotificationMessageCard/src/NotificationMessageCard.vue'
 import { useRouter } from 'vue-router'
+import { useNotificationMessage } from '@/stores/modules/notificationMessage'
 
 const router = useRouter()
-const loading = ref(false)
+const notificationMsgStore = useNotificationMessage()
 
-const drawerModel = defineModel('drawerModel', { type: Boolean, default: false })
+const loading = ref(false)
 
 const notificationType = ref('all')
 const notificationTypeList = ref({
@@ -19,78 +20,19 @@ const notificationTypeList = ref({
 })
 
 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'
+      rules_type: notificationType.value
     })
     .then((res) => {
       if (res.code === 200) {
         notificationList.value = res.data
+        // nextTick(() => {
+        //   init()
+        // })
       }
     })
     .finally(() => {
@@ -98,37 +40,55 @@ const getNotificationList = () => {
     })
 }
 
+// 标记所有为已读
 const handleMarkAllRead = () => {
-  // 标记所有为已读
-  // notificationList.value.forEach((item) => {
-  //   item.isRead = true
-  // })
+  try {
+    $api.setMessageRead({ read_type: true })
+  } catch (error) {
+    console.error(error)
+  }
 }
 
+const drawerRef = ref()
 const handleViewAll = () => {
+  drawerRef.value.handleClose()
   router.push('/system-message')
 }
 
 const handleSettingMessage = () => {
-  // 跳转消息设置页面
-  // router.push('/')
+  drawerRef.value.handleClose()
+  router.push({
+    name: 'Monitoring Settings',
+    query: {
+      tab: 'Subscribe Notifications'
+    }
+  })
 }
 
-const clearData = () => {
+const closeDrawer = () => {
   notificationList.value = []
+  notificationType.value = 'all'
+  notificationMsgStore.markMessageAsRead()
 }
+
+const notificationListRef = ref<HTMLElement | null>(null)
 </script>
 
 <template>
   <el-drawer
+    ref="drawerRef"
     @open="getNotificationList"
-    @closed="clearData"
+    @closed="closeDrawer"
     class="notice-drawer"
-    v-model="drawerModel"
     size="432px"
   >
     <template #header>
-      <el-select size="large" v-model="notificationType" class="notification-type">
+      <el-select
+        size="large"
+        @change="getNotificationList"
+        v-model="notificationType"
+        class="notification-type"
+      >
         <el-option
           v-for="(label, value) in notificationTypeList"
           :key="value"
@@ -138,7 +98,7 @@ const clearData = () => {
       </el-select>
     </template>
     <template #default>
-      <div v-vloading="loading" style="height: 100%">
+      <el-scrollbar 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>
@@ -162,10 +122,15 @@ const clearData = () => {
             </el-button>
           </div>
         </div>
-        <div class="notification-content">
-          <NotificationMessageCard :data="notificationList" />
+        <div class="notification-content" ref="notificationListRef">
+          <NotificationMessageCard
+            @see-all="drawerRef.handleClose()"
+            @view-more="drawerRef.handleClose()"
+            @jump-tracking="drawerRef.handleClose()"
+            :data="notificationList"
+          />
         </div>
-      </div>
+      </el-scrollbar>
     </template>
   </el-drawer>
 </template>
@@ -173,6 +138,7 @@ const clearData = () => {
 div.layout-toolbar {
   .notification-content {
     padding: 16px;
+    background-color: var(--color-dialog-body-bg);
   }
   .password-notifications {
     margin-bottom: 16px;
@@ -285,8 +251,9 @@ div.layout-toolbar {
       top: 0;
       height: 40px;
       padding: 0 16px;
+      z-index: 3;
       line-height: 40px;
-      background-color: white;
+      background-color: var(--color-dialog-body-bg);
       border-bottom: 1px solid var(--color-border);
       .mark-all-read {
         span {

+ 90 - 125
src/views/Layout/src/components/Header/components/TrainingCard.vue

@@ -1,163 +1,124 @@
 <script setup lang="ts">
+import { onBeforeRouteUpdate } from 'vue-router'
 import NotificationMessageCard from '@/components/NotificationMessageCard/src/NotificationMessageCard.vue'
+import { useUserStore } from '@/stores/modules/user'
+import { useNotificationMessage } from '@/stores/modules/notificationMessage'
+import dayjs from 'dayjs'
 
-const notificationList = []
-// const notificationList = [
-//   {
-//     notificationType: 'feature',
-//     info: {
-//       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'
-//     }
-//   },
-//   {
-//     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: {
-//       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: {
-//       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.'
-//     }
-//   },
-//   {
-//     notificationType: 'event',
-//     info: {
-//       type: 'milestone',
-//       isMultiple: true,
-//       numericRecords: 3,
-//       isRead: true,
-//       title: 'Milestone Update 1',
-//       mode: 'Ocean Freight',
-//       no: 'HBOL: SHJN2301234',
-//       tag: 'Booking Confirmed',
-//       location: 'Hong Kong',
-//       time: 'Jan 10, 2025 14:30 UTC+8'
-//     }
-//   },
-//   {
-//     notificationType: 'event',
-//     info: {
-//       type: 'milestone',
-//       isMultiple: true,
-//       numericRecords: 3,
-//       isRead: true,
-//       title: 'Milestone Update 2',
-//       mode: 'Ocean Freight',
-//       no: 'HBOL: SHJN2301234',
-//       tag: 'Booking Confirmed',
-//       location: 'Hong Kong',
-//       time: 'Jan 10, 2025 14:30 UTC+8'
-//     }
-//   },
-//   {
-//     notificationType: 'event',
-//     info: {
-//       type: 'milestone',
-//       isMultiple: true,
-//       numericRecords: 3,
-//       isRead: true,
-//       title: 'Milestone Update 3',
-//       mode: 'Ocean Freight',
-//       no: 'HBOL: SHJN2301234',
-//       tag: 'Booking Confirmed',
-//       location: 'Hong Kong',
-//       time: 'Jan 10, 2025 14:30 UTC+8'
-//     }
-//   }
-// ]
-
-const getNotificationList = () => {
-  if (localStorage.getItem('showFeatureAfterLogin') !== 'true') return
-  // 获取数据
-
-  localStorage.removeItem('showFeatureAfterLogin')
-}
+const userStore = useUserStore()
+const notificationMsgStore = useNotificationMessage()
+
+const notificationList = ref([])
 
 const curCard = computed(() => {
-  return notificationList[curIndex.value] || null
+  return notificationList.value[curIndex.value] || null
 })
 const curIndex = ref(0)
 // 设置定时器进行自动轮播
-let intervalId = null
+let trainingIntervalId = null
+
+// 轮询最新未读消息
+let newMessageIntervalId = null
+// 轮询最新未读消息
+const pollingNewMessage = () => {
+  // 每隔5分钟轮询一次
+  newMessageIntervalId = setInterval(() => {
+    getNotificationList(dayjs().format('MM/DD/YYYY HH:mm:ss'))
+  }, 300000)
+}
+pollingNewMessage()
+
+// 登录后自动轮播消息
+const trainingCardAfterLogin = () => {
+  curIndex.value = curIndex.value + 1
+  // 如果消息展示完毕,清除定时器
+  if (curIndex.value >= notificationList.value.length) {
+    clearInterval(trainingIntervalId)
+  }
+}
 
 const nextNotification = () => {
   let result = true
   // 更新当前索引和卡片
   curIndex.value = curIndex.value + 1
-  // curCard.value = notificationList[curIndex.value]
   // 如果消息为password或者feature类型,暂停自动轮播
   if (
     curCard.value?.notificationType === 'password' ||
     curCard.value?.notificationType === 'feature'
   ) {
-    clearInterval(intervalId)
+    clearInterval(trainingIntervalId)
     result = false
   }
-
   // 如果到达最后一个消息,设置为null以清除显示
-  if (curIndex.value >= notificationList.length) {
-    clearInterval(intervalId)
-    // curCard.value = null
+  if (curIndex.value >= notificationList.value.length) {
+    clearInterval(trainingIntervalId)
     result = false
   }
   return result
 }
 
+// 轮询时的轮播定时器
 const initTrainingCard = () => {
   if (curCard.value?.notificationType === 'event') {
-    intervalId = setInterval(nextNotification, 2000)
+    trainingIntervalId = setInterval(nextNotification, 2000)
   }
 }
-initTrainingCard()
 
+onBeforeRouteUpdate((to, from, next) => {
+  if (from.name === 'Login' && userStore.userName) {
+    getNotificationList()
+  }
+  if (to.name === 'Login') {
+    clearInterval(trainingIntervalId)
+    clearInterval(newMessageIntervalId)
+    newMessageIntervalId = null
+  }
+  next()
+})
+
+const getNotificationList = (time?: string) => {
+  $api
+    .getNotificationList({
+      rules_type: 'all',
+      info_type: true,
+      current_time: time
+    })
+    .then((res) => {
+      if (res.code === 200) {
+        const data = res.data
+        curIndex.value = 0
+        notificationList.value = data
+
+        data.length > 0 && notificationMsgStore.setHasNewMsg()
+
+        if (time && data.length > 0) {
+          initTrainingCard()
+        } else {
+          trainingIntervalId = setInterval(trainingCardAfterLogin, 2000)
+        }
+      }
+    })
+}
+
+// 关闭消息,如果是登录后自动轮播的消息,则需清除定时器,如果不是则继续轮播下一条消息
 const closeMessage = () => {
+  if (!newMessageIntervalId) {
+    clearInterval(trainingIntervalId)
+    curIndex.value = -1
+    return
+  }
+
   // 如果当前消息为event类型,则需先清除定时器
   if (curCard.value?.notificationType === 'event') {
-    clearInterval(intervalId)
+    clearInterval(trainingIntervalId)
   }
 
   const result = nextNotification()
   if (result) {
-    intervalId = setInterval(nextNotification, 2000)
+    trainingIntervalId = setInterval(nextNotification, 2000)
   }
+  // 将当前消息标记为已读
+  // notificationMsgStore.setReadCardMap(curCard.value?.info?.id)
 }
 </script>
 
@@ -170,7 +131,11 @@ const closeMessage = () => {
     >
       <span class="font_family icon-icon_reject_b"></span>
     </el-button>
-    <NotificationMessageCard v-if="curCard" :data="[curCard]"></NotificationMessageCard>
+    <NotificationMessageCard
+      :isObserver="false"
+      v-if="curCard"
+      :data="[curCard]"
+    ></NotificationMessageCard>
   </div>
 </template>
 
@@ -179,13 +144,13 @@ const closeMessage = () => {
   position: absolute;
   top: 60px;
   right: 20px;
-  z-index: 2010;
+  z-index: 2300;
   width: 432px;
   padding: 16px;
   padding-bottom: 0;
-  background-color: #fff;
+  background-color: var(--color-dialog-body-bg);
   border-radius: 12px;
-  box-shadow: 4px 4px 16px 0px rgba(0, 0, 0, 0.1);
+  box-shadow: 4px 4px 32px 0px rgba(0, 0, 0, 0.2);
   .close-icon {
     position: absolute;
     top: 14px;

+ 0 - 43
src/views/Layout/src/components/Menu/MenuView.vue

@@ -22,49 +22,6 @@ const getMenuList = () => {
       menuList.value = res.data
     }
   })
-  // menuList.value = [
-  //   {
-  //     index: '1',
-  //     label: 'Dashboard',
-  //     icon: 'icon_data_fill_b',
-  //     path: '/dashboard'
-  //   },
-  //   {
-  //     index: '2',
-  //     label: 'Booking',
-  //     icon: 'icon_booking__fill_b',
-  //     path: '/booking'
-  //   },
-  //   {
-  //     index: '3',
-  //     label: 'Tracking',
-  //     icon: 'icon_tracking__fill_b',
-  //     path: '/tracking'
-  //   },
-  //   {
-  //     index: '4',
-  //     label: 'System Management',
-  //     icon: 'icon_system__management_fill_b',
-  //     type: 'list',
-  //     children: [
-  //       {
-  //         index: '4-1',
-  //         label: 'System Message',
-  //         path: '/system-message'
-  //       },
-  //       {
-  //         index: '4-2',
-  //         label: 'System Settings',
-  //         path: '/SystemSettings'
-  //       },
-  //       {
-  //         index: '4-3',
-  //         label: 'Operation Log',
-  //         path: '/Operationlog'
-  //       }
-  //     ]
-  //   }
-  // ]
 }
 getMenuList()
 //监听窗口大小

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

@@ -454,7 +454,8 @@ const firstLoginTipsRef = ref()
     </el-card>
     <VSliderVerification
       v-if="isShowSliderVerification"
-      @close="confirmVerification"
+      @success="confirmVerification"
+      @close="isShowSliderVerification = false"
       ref="sliderVerificationRef"
     ></VSliderVerification>
     <ErrorTips ref="errorTipsRef" @forget-password="status = 'reset'"></ErrorTips>

+ 180 - 220
src/views/SystemMessage/src/SystemMessage.vue

@@ -1,243 +1,203 @@
 <script setup lang="ts">
-import EventCard from '@/components/NotificationMessageCard/src/components/EventCard.vue'
+import NotificationMessageCard from '@/components/NotificationMessageCard/src/NotificationMessageCard.vue'
+import { useNotificationMessage } from '@/stores/modules/notificationMessage'
 
+const activeCardTypeName = ref(sessionStorage.getItem('activeCardTypeName') || 'Milestone Update')
+
+const notificationMsgStore = useNotificationMessage()
 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 tabCountList = ref([0, 0, 0, 0, 0])
+const curTabCount = ref([])
+
+const handleCount = (count: number) => {
+  if (!count) return ''
+  return count > 99 ? '99+' : count
+}
+const handleShowCount = (typeName: string, index: number) => {
+  // 在切换type类型时,防止点击的类型值为点击之前类型的值
+  if (curTabCount.value?.[index] > -1) {
+    return curTabCount.value[index]
   }
+  const count = tabCountList.value[index]
+  if (typeName === activeCardTypeName.value) {
+    return handleCount(unreadNotificationList.value.length)
+  }
+  return handleCount(count)
+}
+
+const navList = [
+  'Milestone Update',
+  'Container Status Update',
+  'Departure/Arrival Delay',
+  'ETD/ETA Change'
 ]
 
-const activeItem = ref('Milestone Update')
+const notificationTypeList = ref({
+  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',
+  Feature_Update: 'Feature Update'
+})
+
 const setActiveItem = (item: string) => {
-  activeItem.value = item
-}
+  navList.forEach((navItem, index) => {
+    curTabCount.value[index] = handleShowCount(navItem, index)
+  })
+  curTabCount.value[tabCountList.value.length - 1] = handleShowCount(
+    'Feature Update',
+    tabCountList.value.length - 1
+  )
 
-// const getNotificationList = () => {
-//   $api
-//     .getNotificationList({
-//       rules_type: 'Milestone_Update'
-//     })
-//     .then((res) => {
-//       if (res.code === 200) {
-//         notificationList.value = res.data.info
-//         console.log(res, 'test')
-//       }
-//     })
-// }
+  activeCardTypeName.value = item
+  sessionStorage.setItem('activeCardTypeName', item)
+  activeTabName.value = 'All Notifications'
+  getNotificationList()
+}
 
-const activeName = ref('first')
+const loading = ref(false)
+const notificationList = ref<any[]>([])
 
-const handleClick = () => {}
+const unreadNotificationList = computed(() => {
+  return notificationList.value.filter((item) => !item.info.isRead)
+})
+const readNotificationList = computed(() => {
+  return notificationList.value.filter((item) => item.info.isRead)
+})
+const getNotificationList = async () => {
+  loading.value = true
+  const rulesType = Object.entries(notificationTypeList.value).find(
+    (item) => item[1] === activeCardTypeName.value
+  )?.[0]
+  try {
+    await notificationMsgStore.markMessageAsRead()
+    $api
+      .getSystemMessageData({
+        rules_type: rulesType
+      })
+      .then((res) => {
+        if (res.code === 200) {
+          const data = res.data
+          notificationList.value = data.cardList
+          tabCountList.value = data.countList
+        }
+      })
+      .finally(() => {
+        loading.value = false
+        curTabCount.value = []
+      })
+  } catch (error) {
+    console.error(error)
+    loading.value = false
+    curTabCount.value = []
+  }
+}
 
-// const notificationList = ref<any[]>([])
-const notificationList = [
-  {
-    type: 'milestone',
-    isMultiple: true,
-    numericRecords: 3,
-    isRead: true,
-    title: 'Milestone Update',
-    mode: 'Ocean Freight',
-    no: 'SHJN2301234',
-    tag: 'Booking Confirmed',
-    location: 'Hong Kong',
-    timeLabel: 'ATA: ',
-    time: '2510-12-1 14:30 UTC+8',
-    timezone: 'Asia/Shanghai'
-  },
-  {
-    type: 'container',
-    isRead: false,
-    mode: '',
-    no: 'SHJN2301234',
-    tag: 'Unloaded From Vessel',
-    location: 'Hong Kong',
-    timeLabel: 'ATA: ',
-    time: '2510-12-1 14:30 UTC+8',
-    timezone: 'Asia/Shanghai',
-    previous: 'Previous: Departure from Shanghai (08:15 UTC+8)'
-  },
-  {
-    type: 'delay',
-    numericRecords: 0,
-    isRead: false,
-    title: 'Delay Daily Summary (Jan 10, 2025)',
-    mode: 'Air Freight',
-    no: 'SHJN2301234',
-    tag: 'Departure Delay',
-    location: 'Hong Kong',
-    timeLabel: 'ATA: ',
-    time: '2510-12-1 14:30 UTC+8',
-    timezone: 'Asia/Shanghai',
-    info: {
-      timeLabel: 'ATA: ',
-      time: '2510-12-1 14:30 UTC+8',
-      timezone: 'Asia/Shanghai',
-      departureDelayNum: 10,
-      arrivalDelayNum: 8
+const changeCardRead = () => {
+  const readCardMap = notificationMsgStore.readCardMap
+  notificationList.value.forEach((item) => {
+    if (readCardMap.includes(item.info.id)) {
+      item.info.isRead = true
     }
-  },
-  {
-    type: 'container',
-    isRead: false,
-    mode: '',
-    no: 'SHJN2301234',
-    tag: 'Unloaded From Vessel',
-    location: 'Hong Kong',
-    timeLabel: 'ATA: ',
-    time: '2510-12-1 14:30 UTC+8',
-    timezone: 'Asia/Shanghai',
-    previous: 'Previous: Departure from Shanghai (08:15 UTC+8)'
-  },
-  {
-    type: 'delay',
-    numericRecords: 0,
-    isRead: false,
-    title: 'Delay Daily Summary (Jan 10, 2025)',
-    mode: 'Air Freight',
-    no: 'SHJN2301234',
-    tag: 'Departure Delay',
-    location: 'Hong Kong',
-    timeLabel: 'ATA: ',
-    time: '2510-12-1 14:30 UTC+8',
-    timezone: 'Asia/Shanghai',
-    info: {
-      timeLabel: 'ATA: ',
-      time: '2510-12-1 14:30 UTC+8',
-      timezone: 'Asia/Shanghai',
-      departureDelayNum: 10,
-      arrivalDelayNum: 8
-    }
-  },
-  {
-    type: 'container',
-    isRead: false,
-    mode: '',
-    no: 'SHJN2301234',
-    tag: 'Unloaded From Vessel',
-    location: 'Hong Kong',
-    timeLabel: 'ATA: ',
-    time: '2510-12-1 14:30 UTC+8',
-    timezone: 'Asia/Shanghai',
-    previous: 'Previous: Departure from Shanghai (08:15 UTC+8)'
-  },
-  {
-    type: 'delay',
-    numericRecords: 0,
-    isRead: false,
-    title: 'Delay Daily Summary (Jan 10, 2025)',
-    mode: 'Air Freight',
-    no: 'SHJN2301234',
-    tag: 'Departure Delay',
-    location: 'Hong Kong',
-    timeLabel: 'ATA: ',
-    time: '2510-12-1 14:30 UTC+8',
-    timezone: 'Asia/Shanghai',
-    info: {
-      timeLabel: 'ATA: ',
-      time: '2510-12-1 14:30 UTC+8',
-      timezone: 'Asia/Shanghai',
-      departureDelayNum: 10,
-      arrivalDelayNum: 8
+  })
+}
+
+const activeTabName = ref('All Notifications')
+const handleTabChange = () => {
+  // 当前tab页切换时,更新数据
+  const readCardMap = notificationMsgStore.readCardMap
+  notificationList.value.forEach((item) => {
+    if (readCardMap.includes(item.info.id)) {
+      item.info.isRead = true
     }
-  },
-  {
-    type: 'change',
-    numericRecords: 0,
-    isRead: false,
-    title: 'ETD/ETA  Change Weekly Summary (Jan 4- 10, 2025) ',
-    mode: 'Air Freight',
-    no: 'SHJN2301234',
-    tag: 'ETD Change',
-    info: {
-      etdChangeNum: 20,
-      etaChangeNum: 10,
-      timeLabel: 'ATA: ',
-      time: '2510-12-1 14:30 UTC+8',
-      timezone: 'Asia/Shanghai'
-    },
-    location: 'Hong Kong',
-    changeTime: 'Updated ETD: Jan 17, 15:00',
-    timeLabel: 'ATA: ',
-    time: '2510-12-1 14:30 UTC+8',
-    timezone: 'Asia/Shanghai'
-  }
-]
-// onMounted(() => {
-//   getNotificationList()
-// })
+  })
+}
+
+onMounted(() => {
+  getNotificationList()
+})
 </script>
 
 <template>
-  <div class="Title">System Message</div>
-  <div class="system-message">
-    <div class="left-nav">
-      <el-collapse v-model="collapseVModel">
-        <el-collapse-item title="Event Notifications" name="1">
-          <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 v-vloading="loading" style="height: 100%; width: 100%">
+    <div class="Title">System Message</div>
+    <div class="system-message">
+      <div class="left-nav">
+        <el-collapse v-model="collapseVModel">
+          <el-collapse-item title="Event Notifications" name="1">
+            <div
+              @click="setActiveItem(item)"
+              class="collapse-item"
+              :class="{ 'is-active': item === activeCardTypeName }"
+              v-for="(item, index) in navList"
+              :key="item"
+            >
+              <div v-if="item === activeCardTypeName" class="active-sign"></div>
+              <span>{{ item }}</span>
+              <div class="count" v-if="handleShowCount(item, index)">
+                <span>{{ handleShowCount(item, index) }}</span>
+              </div>
             </div>
+          </el-collapse-item>
+        </el-collapse>
+        <div
+          @click="setActiveItem('Feature Update')"
+          class="collapse-item"
+          style="margin-top: 4px; font-weight: 700"
+          :class="{ 'is-active': activeCardTypeName === 'Feature Update' }"
+        >
+          <div v-if="activeCardTypeName === 'Feature Update'" class="active-sign"></div>
+          <span>Feature Update</span>
+          <div class="count" v-if="handleShowCount('Feature Update', tabCountList.length - 1)">
+            <span>{{ handleShowCount('Feature Update', tabCountList.length - 1) }}</span>
           </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="padding: 10px 140px 0px 16px">
-            <EventCard
-              v-for="(item, index) in notificationList"
-              :key="index"
-              :data="item"
-            ></EventCard>
-          </div>
-        </el-tab-pane>
-        <el-tab-pane label="Unread" name="second">
-          <template #label>
-            <span style="margin-right: 4px">Unread</span>
-            <div class="count">
-              <span>33</span>
+      <div class="right-content">
+        <el-tabs v-model="activeTabName" @tab-change="handleTabChange" class="demo-tabs">
+          <el-tab-pane label="All Notifications" name="All Notifications">
+            <template #label>
+              <span style="margin-right: 4px">All Notifications</span>
+            </template>
+            <div style="padding: 10px 140px 0px 16px" v-if="activeTabName === 'All Notifications'">
+              <NotificationMessageCard
+                v-if="activeTabName === 'All Notifications'"
+                :data="notificationList"
+                @hasCardRead="changeCardRead"
+                :updateReadCardsOnChange="false"
+              ></NotificationMessageCard>
             </div>
-          </template>
-        </el-tab-pane>
-        <el-tab-pane label="Read" name="third">Role</el-tab-pane>
-      </el-tabs>
+          </el-tab-pane>
+          <el-tab-pane label="Unread" name="Unread">
+            <template #label>
+              <span style="margin-right: 4px">Unread</span>
+              <div class="count" v-if="unreadNotificationList.length">
+                <span>{{ handleCount(unreadNotificationList.length) }}</span>
+              </div>
+            </template>
+            <div style="padding: 10px 140px 0px 16px" v-if="activeTabName === 'Unread'">
+              <NotificationMessageCard
+                v-if="activeTabName === 'Unread'"
+                :data="unreadNotificationList"
+                :updateReadCardsOnChange="false"
+              ></NotificationMessageCard>
+            </div>
+          </el-tab-pane>
+          <el-tab-pane label="Read" name="Read">
+            <template #label><span style="margin-right: 4px">Read</span> </template>
+            <div style="padding: 10px 140px 0px 16px" v-if="activeTabName === 'Read'">
+              <NotificationMessageCard
+                v-if="activeTabName === 'Read'"
+                :updateReadCardsOnChange="false"
+                :data="readNotificationList"
+              >
+              </NotificationMessageCard>
+            </div>
+          </el-tab-pane>
+        </el-tabs>
+      </div>
     </div>
   </div>
 </template>
@@ -257,10 +217,10 @@ const notificationList = [
   height: calc(100% - 68px);
   .count {
     display: inline-flex;
-    justify-content: center;
     height: 18px;
     min-width: 18px;
-    padding-left: 4px;
+    padding-top: 1px;
+    padding-left: 5px;
     padding-right: 5px;
     background-color: var(--color-theme);
     border-radius: 9px;

+ 111 - 45
src/views/SystemMessage/src/components/SystemMessageDetail.vue

@@ -1,76 +1,139 @@
 <script setup lang="ts">
+import { useRoute } from 'vue-router'
 import EventCard from '@/components/NotificationMessageCard/src/components/EventCard.vue'
+import { useNotificationMessage } from '@/stores/modules/notificationMessage'
 
-const notificationData: any = {
-  title: 'Milestone Update Daily Summary (Jan 10, 2025)',
-  numericRecords: 3,
-  notificationList: [
-    {
-      mode: 'Ocean Freight',
-      no: 'HBOL: SHJN2301234',
-      tag: 'Booking Confirmed',
-      location: 'Hong Kong',
-      timeLabel: 'ATA: ',
-      time: '2510-12-1 14:30 UTC+8',
-      timezone: 'Asia/Shanghai'
-    },
-    {
-      mode: 'Air Freight',
-      no: 'HBOL: SHJN2301234',
-      tag: 'Booking Confirmed',
-      location: 'Hong Kong',
-      timeLabel: 'ATA: ',
-      time: '2510-12-1 14:30 UTC+8',
-      timezone: 'Asia/Shanghai'
-    },
-    {
-      mode: 'Air Freight',
-      no: 'HBOL: SHJN2301234',
-      tag: 'Booking Confirmed',
-      location: 'Hong Kong',
-      timeLabel: 'ATA: ',
-      time: '2510-12-1 14:30 UTC+8',
-      timezone: 'Asia/Shanghai',
-      previous: 'Previous: Departure from Shanghai (08:15 UTC+8)'
-    }
-  ]
+const route = useRoute()
+const notificationMsgStore = useNotificationMessage()
+const notificationData = ref({
+  title: '',
+  type: 'delay',
+  etdOrdeparturNum: -1,
+  etaOrarrivalNum: -1,
+  numericRecords: -1,
+  notificationList: []
+})
+
+const loading = ref(false)
+if (route.query.type === 'feature') {
+  loading.value = true
+}
+const getNotificationList = async () => {
+  await $api
+    .getNotificationDetails({
+      rules_type: route.query.rules_type,
+      frequency_type: route.query.frequency_type,
+      insert_date_format: route.query.insert_date_format
+    })
+    .then((res) => {
+      if (res.code === 200) {
+        notificationData.value = res.data
+        notificationMsgStore.hasUnreadMessages()
+      }
+    })
+    .finally(() => {
+      loading.value = false
+    })
 }
 
-const getNotificationList = () => {
-  $api.getNotificationDetails().then((res) => {
-    if (res.code === 200) {
-      // console.log(res, 'test')
-    }
-  })
+const iframeUrl = ref()
+const init = async () => {
+  loading.value = true
+  if (route.query.type === 'feature') {
+    notificationData.value.title = (route.query.title as string) || ''
+    iframeUrl.value = `${import.meta.env.VITE_API_HOST}/main_new_version.php?action=feature_update&id=${route.query.id}`
+  } else {
+    await getNotificationList()
+  }
 }
-onMounted(() => {
-  getNotificationList()
+// onMounted(() => {
+//   init()
+// })
+const watchScope = watch(
+  () => route.query,
+  () => {
+    init()
+  },
+  {
+    immediate: true
+  }
+)
+onUnmounted(() => {
+  watchScope()
 })
+
+const iframeRef = ref(null)
+const handleIframeLoaded = () => {
+  nextTick(() => {
+    loading.value = false
+  })
+}
 </script>
 
 <template>
-  <div class="system-message-detail">
-    <div class="content">
+  <div class="system-message-detail" v-vloading="loading">
+    <!-- <el-button @click="handleIframeLoaded">测试</el-button> -->
+    <div class="content" v-if="route.query.type !== 'feature'">
       <div class="header" v-if="notificationData.title">
         <div class="status-icon"></div>
         <div class="title">{{ notificationData.title }}</div>
       </div>
-      <div class="total-tips">Latest Status Updates ({{ notificationData.numericRecords }})</div>
+      <div class="total-tips" v-if="notificationData.numericRecords > -1">
+        Latest Status Updates ({{ notificationData.numericRecords }})
+      </div>
+      <div
+        class="total-tips"
+        v-else-if="notificationData.etdOrdeparturNum > -1 || notificationData.etaOrarrivalNum > -1"
+      >
+        <div>
+          <span v-if="notificationData.etdOrdeparturNum"
+            >{{ notificationData.type === 'delay' ? 'Departure Delay' : 'ETD Change' }} ({{
+              notificationData.etdOrdeparturNum
+            }})</span
+          >
+          <span v-if="notificationData.etdOrdeparturNum && notificationData.etaOrarrivalNum">
+            &nbsp;&nbsp;|&nbsp;&nbsp;</span
+          >
+          <span v-if="notificationData.etaOrarrivalNum">
+            {{ notificationData.type === 'delay' ? 'Arrival Delay' : 'ETA Change' }} ({{
+              notificationData.etaOrarrivalNum
+            }})
+          </span>
+        </div>
+      </div>
       <EventCard
         v-for="(item, index) in notificationData.notificationList"
         :key="index"
         :data="item"
       />
     </div>
+    <div class="content" v-else>
+      <div class="header" v-if="notificationData.title">
+        <div class="status-icon"></div>
+        <div class="title">{{ notificationData.title }}</div>
+      </div>
+      <div class="feature-pdf">
+        <iframe
+          ref="iframeRef"
+          :src="iframeUrl"
+          width="100%"
+          height="100%"
+          @load="handleIframeLoaded"
+        ></iframe>
+      </div>
+    </div>
   </div>
 </template>
 
 <style lang="scss" scoped>
 .system-message-detail {
+  height: 100%;
   padding: 16px;
+  overflow: auto;
   .content {
     margin: auto;
-    width: 800px;
+    width: 1000px;
+    height: 100%;
   }
   .notification-card {
     max-width: 800px;
@@ -96,5 +159,8 @@ onMounted(() => {
     line-height: 40px;
     font-size: 12px;
   }
+  .feature-pdf {
+    height: calc(100% - 28px);
+  }
 }
 </style>

+ 14 - 14
src/views/SystemSettings/src/SystemSettings.vue

@@ -2,12 +2,12 @@
 import { ref, onMounted } from 'vue'
 import AddRSettingTableules from './components/SettingTable'
 import MonitoringTable from './components/MonitoringTable'
-import { useRouter } from 'vue-router'
+import { useRouter, useRoute } from 'vue-router'
 import PersonalProfile from './components/PersonalProfile.vue'
 
 const router = useRouter()
-
-const TabActive = ref('Personal Profile')
+const route = useRoute()
+const TabActive = ref(route.query.tab || 'Personal Profile')
 const isMilestoneChecked = ref(false)
 const isContainerChecked = ref(false)
 const isDepartureChecked = ref(false)
@@ -133,7 +133,7 @@ const SubShipmentsColumns = ref([
 // System Settings初始数据
 const SubShipmentsTable = ref()
 const AddRulesTable = ref()
-const subscribeInit = ref()
+const subscribeInit = ref({})
 const getsubscribe = () => {
   $api.getsubscribe({}).then((res: any) => {
     if (res.code === 200) {
@@ -160,7 +160,7 @@ const SavedAddedRules = (val: any, type: any) => {
     isMilestoneAdded.value = true
   } else if (type == 'Container_Status_Update') {
     isContainerAdded.value = true
-  } else if (type == 'Arrival_Delay') {
+  } else if (type == 'Departure/Arrival_Delay') {
     isDepartureAdded.value = true
   } else {
     isETDChangeAdded.value = true
@@ -202,19 +202,19 @@ const gettabledatalength = (val: any) => {
   tabledatalength.value = val
 }
 
-// onMounted(() => {
-//   getsubscribe()
-//   if (sessionStorage.getItem('activeTab') != null) {
-//     TabActive.value = sessionStorage.getItem('activeTab')
-//     sessionStorage.removeItem('activeTab')
-//   }
-// })
+onMounted(() => {
+  getsubscribe()
+  if (sessionStorage.getItem('activeTab') != null) {
+    TabActive.value = sessionStorage.getItem('activeTab')
+    sessionStorage.removeItem('activeTab')
+  }
+})
 </script>
 <template>
   <div class="Title">System Settings</div>
   <el-tabs v-model="TabActive" class="demo-tabs">
     <el-tab-pane label="Personal Profile" name="Personal Profile"><PersonalProfile /></el-tab-pane>
-    <!-- <el-tab-pane label="Subscribe Notifications" name="Subscribe Notifications">
+    <el-tab-pane label="Subscribe Notifications" name="Subscribe Notifications">
       <div class="subscribedTitle">Notification Events for Subscribed Shipments</div>
       <div class="SubscribeCollapse">
         <el-collapse v-model="CollapseActive" accordion @change="changeCollapse">
@@ -325,7 +325,7 @@ const gettabledatalength = (val: any) => {
         >
       </div>
       <MonitoringTable @gettabledatalength="gettabledatalength"></MonitoringTable>
-    </el-tab-pane> -->
+    </el-tab-pane>
   </el-tabs>
 </template>
 

+ 3 - 3
src/views/SystemSettings/src/components/CreateNewrule/src/CreateNewrule.vue

@@ -47,7 +47,7 @@ onMounted(() => {
     notificationvalue.value = sessionStorage.getItem('editTableoption')
     setTimeout(() => {
       sessionStorage.removeItem('editTableoption')
-    }, 3000)
+    }, 1000)
   }
 })
 </script>
@@ -55,7 +55,7 @@ onMounted(() => {
   <div class="Title">
     <div>Create New Rule</div>
     <div>
-      <el-button class="create_button" type="default" @click="CancelRulesVisible = true">
+      <el-button class="create_button" style="margin-right: 8px;" type="default" @click="CancelRulesVisible = true">
         <span class="iconfont_icon">
           <svg class="iconfont" aria-hidden="true">
             <use xlink:href="#icon-icon_return_b"></use>
@@ -256,7 +256,7 @@ onMounted(() => {
   align-items: center;
 }
 :deep(header.el-dialog__header) {
-  background-color: #fff;
+  background-color: var(--color-system-body-bg);
 }
 :deep(footer.el-dialog__footer) {
   border-top: none;

+ 19 - 3
src/views/SystemSettings/src/components/MonitoringTable/src/MonitoringTable.vue

@@ -145,7 +145,18 @@ const deleteMoniTable = (row: any) => {
 }
 
 // 编辑表格数据
-const handleedit = (row: any) => {
+const handleedit = ({ row }: any) => {
+  sessionStorage.setItem('activeTab', 'Monitoring Settings')
+  sessionStorage.setItem('editTableid', row.id)
+  sessionStorage.setItem('editTablerules_type', row.rules_type)
+  sessionStorage.setItem('editTableoption', row.notifications_option)
+  router.push({
+    path: '/SystemSettings/createnewrule',
+    query: {}
+  })
+}
+
+const handleedittow = (row: any) => {
   sessionStorage.setItem('activeTab', 'Monitoring Settings')
   sessionStorage.setItem('editTableid', row.id)
   sessionStorage.setItem('editTablerules_type', row.rules_type)
@@ -164,7 +175,12 @@ onMounted(() => {
 
 <template>
   <div class="SettingTable">
-    <vxe-grid ref="tableRef" :style="{ border: 'none' }" v-bind="tableData">
+    <vxe-grid
+      ref="tableRef"
+      :style="{ border: 'none' }"
+      v-bind="tableData"
+      @cell-dblclick="handleedit"
+    >
       <!-- 空数据时的插槽 -->
       <template #empty v-if="tableData.data.length === 0">
         <TableEmpty EmptyTitle="Customize your shipment tracking preferences.">
@@ -174,7 +190,7 @@ onMounted(() => {
         </TableEmpty>
       </template>
       <template #action="{ row }">
-        <el-button class="el-button--blue" style="height: 24px" @click="handleedit(row)">
+        <el-button class="el-button--blue" style="height: 24px" @click="handleedittow(row)">
           <span class="font_family icon-icon_edit_b"></span>
         </el-button>
         <el-popover trigger="click" :visible="row.visible" placement="left" :width="480">

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

@@ -138,7 +138,8 @@ const encryptPassword = (password) => {
     </div>
     <VSliderVerification
       v-if="isShowSliderVerification"
-      @close="confirmVerification"
+      @success="confirmVerification"
+      @close="isShowSliderVerification = false"
       ref="sliderVerificationRef"
     ></VSliderVerification>
   </div>

+ 0 - 222
src/views/Tracking/src/components/PublicTracking/src/components/SlideVerify.vue

@@ -1,222 +0,0 @@
-<script lang="ts" setup>
-const dialogVisible = ref(false)
-
-const openDialog = () => {
-  dialogVisible.value = true
-}
-
-const position = ref(0)
-const isDragging = ref(false)
-const verifyText = ref('Swipe right to verify')
-const sliderState = ref<'start' | 'success' | 'error' | 'dragging'>('start')
-const styleMap = {
-  start: {
-    thumbColor: 'var(--color-neutral-1)',
-    thumbIcon: 'icon-icon_drag__line_b',
-    trackBackground: '#87909e'
-  },
-  dragging: {
-    thumbColor: 'var(--color-neutral-1)',
-    thumbIcon: 'icon-icon_drag__line_b',
-    trackBackground: 'var(--color-success)'
-  },
-  success: {
-    thumbColor: '#fff',
-    thumbIcon: 'icon-icon_confirm_b',
-    trackBackground: 'var(--color-success)'
-  },
-  error: {
-    thumbColor: '#fff',
-    thumbIcon: 'icon-icon_reject_b',
-    trackBackground: '#c7353f'
-  }
-}
-const trackRef = ref<HTMLElement | null>(null)
-
-const getTrackBackground = () => {
-  const trackWidth = trackRef.value?.offsetWidth || 320
-  const progress = (position.value / (trackWidth - 40)) * 100 // 百分比
-  if (sliderState.value === 'start') {
-    return styleMap.start.trackBackground // 初始时灰色
-  } else if (sliderState.value === 'dragging') {
-    return `linear-gradient(90deg, ${styleMap.success.trackBackground} ${progress}%, ${styleMap.start.trackBackground} ${progress}%)`
-  } else if (sliderState.value === 'error') {
-    return `linear-gradient(90deg, ${styleMap.error.trackBackground} ${progress}%, ${styleMap.start.trackBackground} ${progress}%)`
-  }
-  return styleMap.success.trackBackground // 成功时整条绿色
-}
-
-const startDrag = () => {
-  if (sliderState.value === 'success') {
-    return
-  }
-
-  isDragging.value = true
-  document.addEventListener('mousemove', onDrag)
-  document.addEventListener('mouseup', stopDrag)
-}
-
-const onDrag = (event: MouseEvent) => {
-  if (isDragging.value) {
-    if (trackRef.value) {
-      sliderState.value = 'dragging'
-      verifyText.value = 'Swipe right to verify'
-      const rect = trackRef.value.getBoundingClientRect()
-      const offsetX = event.clientX - rect.left
-      position.value = Math.min(Math.max(offsetX, 0), rect.width - 40) // 40是滑块的宽度
-    }
-  }
-}
-
-const emit = defineEmits<{
-  verifySuccess: []
-}>()
-const stopDrag = () => {
-  isDragging.value = false
-  document.removeEventListener('mousemove', onDrag)
-  document.removeEventListener('mouseup', stopDrag)
-
-  if (trackRef.value) {
-    const trackWidth = trackRef.value.offsetWidth
-    if (position.value >= trackWidth - 40) {
-      sliderState.value = 'success'
-      verifyText.value = 'Verification successful'
-      setTimeout(() => {
-        dialogVisible.value = false
-        emit('verifySuccess')
-      }, 500)
-    } else {
-      sliderState.value = 'error'
-      verifyText.value = 'Verification failed'
-      setTimeout(() => {
-        sliderState.value = 'start'
-        verifyText.value = 'Swipe right to verify'
-        position.value = 0
-      }, 3000)
-    }
-  }
-}
-
-const moveSlider = (event: MouseEvent) => {
-  if (sliderState.value !== 'success') {
-    onDrag(event)
-  }
-}
-
-const clearData = () => {
-  sliderState.value = 'start'
-  verifyText.value = 'Swipe right to verify'
-  position.value = 0
-}
-defineExpose({
-  openDialog
-})
-</script>
-
-<template>
-  <el-dialog
-    top="30vh"
-    destroy-on-close
-    :close-on-click-modal="false"
-    :close-on-press-escape="false"
-    @closed="clearData"
-    v-model="dialogVisible"
-    width="400"
-    class="slide-verify-dialog"
-  >
-    <div class="content">
-      <p>Please drag the slider below to complete the</p>
-      <p>verification to ensure normal access</p>
-      <div class="slider-container">
-        <div
-          class="slider-track"
-          :style="{ background: getTrackBackground() }"
-          @click="moveSlider"
-          ref="trackRef"
-        >
-          {{ verifyText }}
-          <div
-            class="slider-thumb"
-            :style="{ left: `${position}px`, borderColor: styleMap[sliderState].trackBackground }"
-            @mousedown="startDrag"
-          >
-            <span
-              v-if="sliderState === 'start' || sliderState === 'dragging'"
-              class="font_family"
-              :style="{ color: styleMap[sliderState].thumbColor }"
-              :class="[styleMap[sliderState].thumbIcon]"
-            ></span>
-            <span
-              v-else
-              class="font_family other-state"
-              :style="{
-                color: styleMap[sliderState].thumbColor,
-                backgroundColor: styleMap[sliderState].trackBackground
-              }"
-              :class="[styleMap[sliderState].thumbIcon]"
-            ></span>
-          </div>
-        </div>
-      </div>
-    </div>
-  </el-dialog>
-</template>
-
-<style lang="scss" scoped>
-.content {
-  padding: 40px 0;
-  text-align: center;
-  & > p {
-    line-height: 21px;
-  }
-}
-
-.slider-container {
-  width: 320px;
-  margin: 16px auto 0;
-  text-align: center;
-  user-select: none;
-}
-.slider-track {
-  position: relative;
-  width: 100%;
-  height: 40px;
-  padding: 1px;
-  background: #868f9d;
-  border-radius: 6px;
-  line-height: 38px;
-  color: #fff;
-}
-.slider-thumb {
-  position: absolute;
-  top: 0px;
-  left: 10px;
-  width: 40px;
-  height: 40px;
-  background: #fff;
-  cursor: pointer;
-  border-radius: 6px;
-  border: 1px solid #868f9d;
-  .font_family {
-    font-size: 14px;
-    &.other-state {
-      height: 16px;
-      width: 16px;
-      padding: 1px;
-      border-radius: 50%;
-      font-size: 14px;
-    }
-  }
-}
-</style>
-
-<style lang="scss">
-.slide-verify-dialog {
-  .el-dialog__header {
-    display: none;
-  }
-  .el-dialog__body {
-    padding: 0;
-  }
-}
-</style>

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

@@ -109,7 +109,15 @@ const getData = () => {
       loading.value = false
     })
 }
-getData()
+watch(
+  () => route.query,
+  () => {
+    getData()
+  },
+  {
+    immediate: true
+  }
+)
 
 const originRef = ref()
 const destinationRef = ref()
@@ -167,7 +175,7 @@ const SubscribeShipments = () => {
             >AMS/ISF</el-button
           >
 
-          <!-- <el-button
+          <el-button
             class="recent_button"
             @click="SubscribeShipments"
             :class="is_subscribe ? 'IsSubscribe' : ''"
@@ -183,7 +191,7 @@ const SubscribeShipments = () => {
               </svg>
             </span>
             <span class="Subscribe">Subscribe</span>
-          </el-button> -->
+          </el-button>
         </div>
       </div>
       <div class="detail-info">

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

@@ -103,7 +103,7 @@ onMounted(async () => {
     replaceSvgByDataKey(item.dataMenuKey, svgUrl)
   }
 })
-function replaceSvgByDataKey(dataMenuKey: any, svgUrl: any) {
+const replaceSvgByDataKey = (dataMenuKey: any, svgUrl: any) => {
   const observer = new MutationObserver((mutationsList, observer) => {
     const element = document.querySelector(`[data-menu-key="${dataMenuKey}"]`)
     if (element) {

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

@@ -34,7 +34,7 @@ const changeFileList = (file: any, fileList: any) => {
   }
 }
 
-function bytesToKB(bytes: number) {
+const bytesToKB = (bytes: number) => {
   return (bytes / 1024).toFixed(0) // 将字节转换为KB,保留两位小数
 }
 

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

@@ -628,7 +628,7 @@ defineExpose({
           <span style="font-size: 12px">VGM</span>
         </el-button>
 
-        <!-- <el-button
+        <el-button
           class="recent_button el-button--blue"
           @click="SubscribeShipments(row)"
           :class="row.is_subscribe ? 'IsSubscribe' : ''"
@@ -643,7 +643,7 @@ defineExpose({
               <use xlink:href="#icon-icon_unmark_b"></use>
             </svg>
           </span>
-        </el-button> -->
+        </el-button>
       </template>
       <!-- Transportation Mode字段的插槽 -->
       <template #mode="{ row, column }">