瀏覽代碼

Merge branch 'dev' into dev_g

AmandaG 8 月之前
父節點
當前提交
46f2157cd4
共有 53 個文件被更改,包括 1918 次插入1210 次删除
  1. 47 2
      src/api/module/notificationMessage.ts
  2. 136 40
      src/components/AddRules/src/AddRules.vue
  3. 1 0
      src/components/AddRules/src/components/AddedrluesTag.vue
  4. 62 34
      src/components/AddRules/src/components/DelayedType.vue
  5. 69 33
      src/components/AddRules/src/components/ETDShipments.vue
  6. 11 1
      src/components/AddRules/src/components/NotiFrequency.vue
  7. 20 3
      src/components/AddRules/src/components/NotiMethods.vue
  8. 27 7
      src/components/AddRules/src/components/RulesShipments.vue
  9. 33 20
      src/components/AddRules/src/components/ShipmentRange.vue
  10. 二進制
      src/components/AddRules/src/images/illustration_system massage_darkmode@2x.png
  11. 258 97
      src/components/CreateAddRules/src/CreateAddRules.vue
  12. 1 0
      src/components/CreateAddRules/src/components/AddedrluesTag.vue
  13. 63 32
      src/components/CreateAddRules/src/components/DelayedType.vue
  14. 70 34
      src/components/CreateAddRules/src/components/ETDShipments.vue
  15. 11 1
      src/components/CreateAddRules/src/components/NotiFrequency.vue
  16. 10 2
      src/components/CreateAddRules/src/components/NotiMethods.vue
  17. 23 6
      src/components/CreateAddRules/src/components/RulesShipments.vue
  18. 66 30
      src/components/CreateAddRules/src/components/ShipmentRange.vue
  19. 二進制
      src/components/CreateAddRules/src/images/illustration_system massage_darkmode@2x.png
  20. 165 9
      src/components/NotificationMessageCard/src/NotificationMessageCard.vue
  21. 66 51
      src/components/NotificationMessageCard/src/components/EventCard.vue
  22. 46 3
      src/components/NotificationMessageCard/src/components/FeatureUpdateCard.vue
  23. 24 6
      src/components/NotificationMessageCard/src/components/PasswordCard.vue
  24. 二進制
      src/components/NotificationMessageCard/src/images/test.png
  25. 21 1
      src/components/VSliderVerification/src/VSliderVerification.vue
  26. 19 1
      src/router/index.ts
  27. 71 0
      src/stores/modules/notificationMessage.ts
  28. 2 0
      src/stores/modules/user.ts
  29. 3 4
      src/styles/elementui.scss
  30. 6 0
      src/styles/index.scss
  31. 8 0
      src/styles/theme-g.scss
  32. 41 2
      src/styles/theme.scss
  33. 0 1
      src/utils/tools.ts
  34. 1 1
      src/views/Booking/src/components/BookingDetail/src/components/EmailView.vue
  35. 1 1
      src/views/Booking/src/components/BookingTable/src/BookingTable.vue
  36. 2 2
      src/views/Dashboard/src/components/RecentStatus.vue
  37. 21 4
      src/views/Layout/src/components/Header/HeaderView.vue
  38. 47 80
      src/views/Layout/src/components/Header/components/NotificationDrawer.vue
  39. 90 125
      src/views/Layout/src/components/Header/components/TrainingCard.vue
  40. 0 43
      src/views/Layout/src/components/Menu/MenuView.vue
  41. 2 1
      src/views/Login/src/loginView.vue
  42. 180 220
      src/views/SystemMessage/src/SystemMessage.vue
  43. 109 45
      src/views/SystemMessage/src/components/SystemMessageDetail.vue
  44. 15 15
      src/views/SystemSettings/src/SystemSettings.vue
  45. 3 3
      src/views/SystemSettings/src/components/CreateNewrule/src/CreateNewrule.vue
  46. 19 3
      src/views/SystemSettings/src/components/MonitoringTable/src/MonitoringTable.vue
  47. 29 1
      src/views/SystemSettings/src/components/SettingTable/src/SettingTable.vue
  48. 2 1
      src/views/Tracking/src/components/PublicTracking/src/PublicTrackingSearch.vue
  49. 0 222
      src/views/Tracking/src/components/PublicTracking/src/components/SlideVerify.vue
  50. 12 17
      src/views/Tracking/src/components/TrackingDetail/src/TrackingDetail.vue
  51. 1 1
      src/views/Tracking/src/components/TrackingDetail/src/components/EmailDrawer.vue
  52. 1 1
      src/views/Tracking/src/components/TrackingDetail/src/components/UploadFilesDialog.vue
  53. 3 4
      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'
+  })
+}

+ 136 - 40
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,37 +381,48 @@ const SaveSuceessful = () => {
 }
 const Savesubscribe = () => {
   let str = ''
+  missingmessage.value = ''
   if (props.TitleType == 'Milestone') {
     savesubscribeobj.rules_type = 'Milestone_Update'
     if (
-      OceanCheckList.value == undefined ||
+      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) {
-        missingmessage.value += 'Ocean Shipments, '
-      }
-      if (AirCheckList.value == undefined) {
-        missingmessage.value += 'Air Shipments, '
+      if (OceanCheckList.value == undefined && AirCheckList.value == undefined || OceanCheckList.value.length == 0 && AirCheckList.value.length == 0) {
+        missingmessage.value += 'Select Milstone, '
       }
       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
-      str =
+      savesubscribeobj.ocean_milestone = OceanCheckCode.value
+      savesubscribeobj.air_milestone = AirCheckListCode.value
+      if(OceanCheckList.value.length == 0) {
+        str =
+        'Air Milestones: ' +
+        AirCheckList.value.join(',')
+      } else if(AirCheckList.value.length == 0) {
+        str =
+        'Ocean Milestones: ' +
+        OceanCheckList.value.join(',')
+      } else {
+        str =
         'Ocean Milestones: ' +
         OceanCheckList.value.join(',') +
         ';\nAir Milestones: ' +
         AirCheckList.value.join(',') +
         ';'
+      }
       savesubscribeobj.event_details = str
       SaveSuceessful()
     }
@@ -369,22 +430,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 +456,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 +490,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 +525,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,17 +541,34 @@ 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()
   }
 }
 
+// 删除Oceam shipments
+const MilOceanref = ref()
+const MilAirref = ref()
+const ContainerOcean = ref()
+const handleCloseMilestoneOcean = (val:any) => {
+  MilOceanref.value.hadleclose(val)
+}
+const handleCloseMilestoneAir = (val:any) => {
+  MilAirref.value.hadleclose(val)
+}
+
+const handleCloseContainer = (val:any) => {
+  ContainerOcean.value.hadleclose(val)
+}
+
 defineExpose({
   clearData
 })
@@ -499,7 +585,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'"
@@ -512,6 +598,7 @@ defineExpose({
             <div>
               <RulesShipments
                 Title="Ocean shipments"
+                ref="MilOceanref"
                 @ChangeCheckRules="ChangeCheckOceanRules"
                 :CheckboxList="MilestoneOceanListInit"
                 :CheckedList="MilestoneOceanListChecked"
@@ -520,6 +607,7 @@ defineExpose({
             <div>
               <RulesShipments
                 Title="Air shipments"
+                ref="MilAirref"
                 @ChangeCheckRules="ChangeCheckAirRules"
                 :CheckboxList="MilestoneAirListInit"
                 :CheckedList="MilestoneAirListChecked"
@@ -535,7 +623,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'"
@@ -548,6 +636,7 @@ defineExpose({
             <div>
               <RulesShipments
                 Title="Ocean shipments"
+                ref="ContainerOcean"
                 @ChangeCheckRules="ChangeContainerRules"
                 :CheckboxList="ContainerOceanListInit"
                 :CheckedList="ContainerOceanListChecked"
@@ -563,7 +652,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 +688,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 +724,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 +768,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 +781,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>
@@ -724,11 +817,13 @@ defineExpose({
       <AddedrluesTag
         v-if="props.TitleType == 'Milestone'"
         :CheckedList="OceanCheckList"
+        @handleCloseRadio="handleCloseMilestoneOcean"
         Title="Ocean Shipments"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Container'"
         :CheckedList="ContainerOceanList"
+        @handleCloseRadio="handleCloseContainer"
         Title="Ocean Shipments"
       ></AddedrluesTag>
       <AddedrluesTag
@@ -751,6 +846,7 @@ defineExpose({
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Milestone'"
+        @handleCloseRadio="handleCloseMilestoneAir"
         :CheckedList="AirCheckList"
         Title="Air Shipments"
       ></AddedrluesTag>
@@ -911,7 +1007,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 +1033,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 +1076,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;

+ 62 - 34
src/components/AddRules/src/components/DelayedType.vue

@@ -38,9 +38,9 @@ const isDeparture = ref(false)
 const isArrival = ref(false)
 const OceanActive = ref(['DelayedShipments'])
 const OceanCheckedList = ref()
-const DepartureTime = ref('')
+const DepartureTime = ref()
 const DepartureSelect = ref('')
-const ArrivalTime = ref('')
+const ArrivalTime = ref()
 const ArrivalSelect = ref('')
 const emit = defineEmits(['ChangeCheckRules', 'closeDelayed'])
 const DepartureList = ref({
@@ -50,34 +50,45 @@ const DepartureList = ref({
 let Departurestr: any = ''
 let Arrivalstr: any = ''
 
-const clampedValue = computed(() => {
-  if (DepartureTime.value == '') {
-    return 0
-  } else {
-    const numericValue = DepartureTime.value.replace(/[^0-9]/g, 0) // 移除非数字字符
-    return Math.min(Math.max(parseInt(numericValue, 10), 0), 365) // 确保值在0到1000之间,但不更新原输入值仅用于显示
+const clampedValue = computed({
+  get: () => DepartureTime.value,
+  set: (newVal) => {
+    const isValid = /^[1-9]\d*$/.test(newVal);
+    DepartureTime.value = isValid ? newVal : '';
+    if( DepartureTime.value!='') {
+      DepartureTime.value = Math.min(Math.max(parseInt(DepartureTime.value, 10), 1), 365)
+    }
   }
 })
-const clampedArrivalValue = computed(() => {
-  if (ArrivalTime.value == '') {
-    return 0
-  } else {
-    const numericValue = ArrivalTime.value.replace(/[^0-9]/g, 0) // 移除非数字字符
-    return Math.min(Math.max(parseInt(numericValue, 10), 0), 365) // 确保值在0到1000之间,但不更新原输入值仅用于显示
+const clampedArrivalValue = computed({
+  get: () => ArrivalTime.value,
+  set: (newVal) => {
+    const isValid = /^[1-9]\d*$/.test(newVal);
+    ArrivalTime.value = isValid ? newVal : '';
+    if( ArrivalTime.value!='') {
+      ArrivalTime.value = Math.min(Math.max(parseInt(ArrivalTime.value, 10), 1), 365)
+    }
   }
 })
 
 const CheckChange = (val: any) => {
   if (val.includes('Departure Delayed')) {
     isDeparture.value = true
-    Departurestr = 'Departure Delayed' + ' ≥ ' + clampedValue.value + ' ' + DepartureSelect.value
+    if(clampedValue.value != '' && clampedValue.value!= undefined) {
+      Departurestr = 'Departure Delayed' + ' ≥ ' + clampedValue.value + ' ' + DepartureSelect.value
+    } else {
+      Departurestr = ''
+    }
     if (DepartureSelect.value != '') {
       DepartureList.value.Departure = Departurestr
     }
     if (val.includes('Arrival Delayed (ATA-ETA)')) {
       isArrival.value = true
-      Arrivalstr =
-        'Arrival Delayed (ATA-ETA)' + ' ≥ ' + clampedArrivalValue.value + ' ' + ArrivalSelect.value
+      if(clampedArrivalValue.value != '' && clampedArrivalValue.value!= undefined) {
+        Arrivalstr = 'Departure Delayed' + ' ≥ ' + clampedArrivalValue.value + ' ' + DepartureSelect.value
+      } else {
+        Arrivalstr = ''
+      }
       if (ArrivalSelect.value != '') {
         DepartureList.value.Arrival = Arrivalstr
       }
@@ -90,8 +101,11 @@ const CheckChange = (val: any) => {
     DepartureList.value.Departure = ''
     if (val.includes('Arrival Delayed (ATA-ETA)')) {
       isArrival.value = true
-      Arrivalstr =
-        'Arrival Delayed (ATA-ETA)' + ' ≥ ' + clampedArrivalValue.value + ' ' + ArrivalSelect.value
+      if(clampedArrivalValue.value != '' && clampedArrivalValue.value!= undefined) {
+        Arrivalstr = 'Departure Delayed' + ' ≥ ' + clampedArrivalValue.value + ' ' + DepartureSelect.value
+      } else {
+        Arrivalstr = ''
+      }
       if (ArrivalSelect.value != '') {
         DepartureList.value.Arrival = Arrivalstr
       }
@@ -104,21 +118,31 @@ const CheckChange = (val: any) => {
 }
 const changedeparture = (val: any) => {
   if (val == 'Departure') {
-    Departurestr = 'Departure Delayed' + ' ≥ ' + clampedValue.value + ' ' + DepartureSelect.value
+    if(clampedValue.value != '' && clampedValue.value!= undefined) {
+      Departurestr = 'Departure Delayed' + ' ≥ ' + clampedValue.value + ' ' + DepartureSelect.value
+    } else {
+      Departurestr = ''
+    }
     if (DepartureSelect.value != '') {
       DepartureList.value.Departure = Departurestr
     }
     if (val == 'Arrival') {
-      Arrivalstr =
-        'Arrival Delayed (ATA-ETA)' + ' ≥ ' + clampedArrivalValue.value + ' ' + ArrivalSelect.value
+      if(clampedArrivalValue.value != '' && clampedArrivalValue.value!= undefined) {
+        Arrivalstr = 'Departure Delayed' + ' ≥ ' + clampedArrivalValue.value + ' ' + DepartureSelect.value
+      } else {
+        Arrivalstr = ''
+      }
       if (ArrivalSelect.value != '') {
         DepartureList.value.Arrival = Arrivalstr
       }
     }
   } else {
     if (val == 'Arrival') {
-      Arrivalstr =
-        'Arrival Delayed (ATA-ETA)' + ' ≥ ' + clampedArrivalValue.value + ' ' + ArrivalSelect.value
+      if(clampedArrivalValue.value != '' && clampedArrivalValue.value!= undefined) {
+        Arrivalstr = 'Departure Delayed' + ' ≥ ' + clampedArrivalValue.value + ' ' + DepartureSelect.value
+      } else {
+        Arrivalstr = ''
+      }
       if (ArrivalSelect.value != '') {
         DepartureList.value.Arrival = Arrivalstr
       }
@@ -177,9 +201,8 @@ defineExpose({
                 <span class="delayedTitle">Delayed Time</span>
                 <span class="delayedIcon">></span>
                 <el-input
-                  v-model="DepartureTime"
+                  v-model="clampedValue"
                   class="input-with-select"
-                  :value="clampedValue"
                   @input="changedeparture('Departure')"
                 >
                   <template #append>
@@ -189,7 +212,7 @@ defineExpose({
                       @change="changedeparture('Departure')"
                     >
                       <el-option label="Day(s)" value="Day(s)" />
-                      <el-option label="Hour(s)" value="Hour(s)" />
+                      <el-option v-if="props.Title == 'Air shipments'" label="Hour(s)" value="Hour(s)" />
                     </el-select>
                   </template>
                 </el-input>
@@ -201,9 +224,8 @@ defineExpose({
                 <span class="delayedTitle">Delayed Time</span>
                 <span class="delayedIcon">></span>
                 <el-input
-                  v-model="ArrivalTime"
+                  v-model="clampedArrivalValue"
                   @input="changedeparture('Arrival')"
-                  :value="clampedArrivalValue"
                   class="input-with-select"
                 >
                   <template #append>
@@ -213,7 +235,7 @@ defineExpose({
                       @change="changedeparture('Arrival')"
                     >
                       <el-option label="Day(s)" value="Day(s)" />
-                      <el-option label="Hour(s)" value="Hour(s)" />
+                      <el-option v-if="props.Title == 'Air shipments'" label="Hour(s)" value="Hour(s)" />
                     </el-select>
                   </template>
                 </el-input>
@@ -228,13 +250,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 +286,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 +298,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>

+ 69 - 33
src/components/AddRules/src/components/ETDShipments.vue

@@ -49,9 +49,9 @@ const isETD = ref(false)
 const isETA = ref(false)
 const OceanActive = ref(['ETDShipments'])
 const OceanCheckedList = ref()
-const ETDTime = ref('')
+const ETDTime = ref()
 const ETDSelect = ref('')
-const ETATime = ref('')
+const ETATime = ref()
 const ETASelect = ref('')
 const ETDRadio = ref()
 const ETARadio = ref()
@@ -65,13 +65,25 @@ let ETAstr: any = ''
 const CheckChange = (val: any) => {
   if (val.includes('ETD')) {
     isETD.value = true
-    if (ETDSelect.value != '') {
+    if(ETDstr.includes('all changes')) {
       ETDETAList.value.ETD = ETDstr
+    } else {
+      if (ETDSelect.value != ''  && clampedValue.value!= '') {
+        ETDETAList.value.ETD = ETDstr
+      } else {
+        ETDETAList.value.ETD = ''
+      }
     }
     if (val.includes('ETA')) {
       isETA.value = true
-      if (ETASelect.value != '') {
+      if(ETAstr.includes('all changes')) {
         ETDETAList.value.ETA = ETAstr
+      } else {
+        if (ETASelect.value != ''  && clampedETAValue.value!= '') {
+          ETDETAList.value.ETA = ETAstr
+        } else {
+          ETDETAList.value.ETA = ''
+        }
       }
     } else {
       isETA.value = false
@@ -82,8 +94,14 @@ const CheckChange = (val: any) => {
     ETDETAList.value.ETD = ''
     if (val.includes('ETA')) {
       isETA.value = true
-      if (ETASelect.value != '') {
+      if(ETAstr.includes('all changes')) {
         ETDETAList.value.ETA = ETAstr
+      } else {
+        if (ETASelect.value != ''  && clampedETAValue.value!= '') {
+          ETDETAList.value.ETA = ETAstr
+        } else {
+          ETDETAList.value.ETA = ''
+        }
       }
     } else {
       isETA.value = false
@@ -98,7 +116,7 @@ const changeETDRadio = (val: any) => {
     ETDETAList.value.ETD = ETDstr
   } else if (val == 2) {
     ETDstr = 'ETD: Notify for all changes ≥ ' + clampedValue.value + ' ' + ETDSelect.value
-    if (ETDSelect.value != '') {
+    if (ETDSelect.value != '' && clampedValue.value!= '') {
       ETDETAList.value.ETD = ETDstr
     } else {
       ETDETAList.value.ETD = ''
@@ -112,7 +130,7 @@ const changeETARadio = (val: any) => {
     ETDETAList.value.ETA = ETAstr
   } else if (val == 2) {
     ETAstr = 'ETA: Notify for all changes ≥ ' + clampedETAValue.value + ' ' + ETASelect.value
-    if (ETASelect.value != '') {
+    if (ETASelect.value != '' && clampedValue.value!= '') {
       ETDETAList.value.ETA = ETAstr
     } else {
       ETDETAList.value.ETA = ''
@@ -123,20 +141,26 @@ const changeETARadio = (val: any) => {
 const changedeparture = (val: any) => {
   if (val == 'ETD') {
     ETDstr = 'ETD: Notify for all changes ≥ ' + clampedValue.value + ' ' + ETDSelect.value
-    if (ETDSelect.value != '') {
+    if (ETDSelect.value != '' && clampedValue.value!= '') {
       ETDETAList.value.ETD = ETDstr
+    } else {
+      ETDETAList.value.ETD = ''
     }
     if (val == 'ETA') {
       ETAstr = 'ETA: Notify for all changes ≥ ' + clampedETAValue.value + ' ' + ETASelect.value
-      if (ETASelect.value != '') {
+      if (ETASelect.value != '' && clampedETAValue.value!= '') {
         ETDETAList.value.ETA = ETAstr
+      } else {
+        ETDETAList.value.ETA = ''
       }
     }
   } else {
     if (val == 'ETA') {
       ETAstr = 'ETA: Notify for all changes ≥ ' + clampedETAValue.value + ' ' + ETASelect.value
-      if (ETASelect.value != '') {
+      if (ETASelect.value != '' && clampedETAValue.value!= '') {
         ETDETAList.value.ETA = ETAstr
+      } else {
+        ETDETAList.value.ETA = ''
       }
     }
   }
@@ -182,20 +206,24 @@ defineExpose({
   ClearData
 })
 
-const clampedValue = computed(() => {
-  if (ETDTime.value == '') {
-    return 0
-  } else {
-    const numericValue = ETDTime.value.replace(/[^0-9]/g, 0) // 移除非数字字符
-    return Math.min(Math.max(parseInt(numericValue, 10), 0), 365) // 确保值在0到1000之间,但不更新原输入值仅用于显示
+const clampedValue = computed({
+  get: () => ETDTime.value,
+  set: (newVal) => {
+    const isValid = /^[1-9]\d*$/.test(newVal);
+    ETDTime.value = isValid ? newVal : '';
+    if( ETDTime.value!='') {
+      ETDTime.value = Math.min(Math.max(parseInt(ETDTime.value, 10), 1), 365)
+    }
   }
 })
-const clampedETAValue = computed(() => {
-  if (ETATime.value == '') {
-    return 0
-  } else {
-    const numericValue = ETATime.value.replace(/[^0-9]/g, 0) // 移除非数字字符
-    return Math.min(Math.max(parseInt(numericValue, 10), 0), 365) // 确保值在0到1000之间,但不更新原输入值仅用于显示
+const clampedETAValue = computed({
+  get: () => ETATime.value,
+  set: (newVal) => {
+    const isValid = /^[1-9]\d*$/.test(newVal);
+    ETATime.value = isValid ? newVal : '';
+    if( ETATime.value!='') {
+      ETATime.value = Math.min(Math.max(parseInt(ETATime.value, 10), 1), 365)
+    }
   }
 })
 </script>
@@ -217,8 +245,7 @@ const clampedETAValue = computed(() => {
                     >Notify only when time difference
                     <span class="delayedIcon">></span>
                     <el-input
-                      v-model="ETDTime"
-                      :value="clampedValue"
+                      v-model="clampedValue"
                       class="input-with-select"
                       @input="changedeparture('ETD')"
                     >
@@ -229,7 +256,7 @@ const clampedETAValue = computed(() => {
                           @change="changedeparture('ETD')"
                         >
                           <el-option label="Day(s)" value="Day(s)" />
-                          <el-option label="Hour(s)" value="Hour(s)" />
+                          <el-option v-if="props.Title == 'Air shipments'" label="Hour(s)" value="Hour(s)" />
                         </el-select>
                       </template> </el-input
                   ></el-radio>
@@ -245,8 +272,7 @@ const clampedETAValue = computed(() => {
                     >Notify only when time difference
                     <span class="delayedIcon">></span>
                     <el-input
-                      v-model="ETATime"
-                      :value="clampedETAValue"
+                      v-model="clampedETAValue"
                       class="input-with-select"
                       @input="changedeparture('ETA')"
                     >
@@ -257,7 +283,7 @@ const clampedETAValue = computed(() => {
                           @change="changedeparture('ETA')"
                         >
                           <el-option label="Day(s)" value="Day(s)" />
-                          <el-option label="Hour(s)" value="Hour(s)" />
+                          <el-option v-if="props.Title == 'Air shipments'" label="Hour(s)" value="Hour(s)" />
                         </el-select>
                       </template> </el-input
                   ></el-radio>
@@ -273,13 +299,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 +318,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 +341,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 +355,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 +368,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>

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

@@ -180,6 +180,9 @@ const ChangeFrequency = (val: any) => {
   if (val == 1) {
     isDaily.value = false
     isWeekly.value = false
+    DailyTime.value = ''
+    WeeklyDay.value = ''
+    WeeklyTime.value = ''
     str = 'Instant notification for each update'
     FrequencyList.value.push(str)
     savesubscribeobj.frequency_type = 'Instant'
@@ -187,6 +190,8 @@ const ChangeFrequency = (val: any) => {
   } else if (val == 2) {
     isDaily.value = true
     isWeekly.value = false
+    WeeklyDay.value = ''
+    WeeklyTime.value = ''
     str = 'Daily, ' + DailyTime.value + ', ' + TimeZoneDailySelect.value
     if (DailyTime.value != '' && TimeZoneDailySelect.value !== '') {
       FrequencyList.value.push(str)
@@ -198,6 +203,7 @@ const ChangeFrequency = (val: any) => {
   } else if (val == 3) {
     isDaily.value = false
     isWeekly.value = true
+    DailyTime.value = ''
     str = 'Weekly, ' + WeeklyDay.value + ', ' + WeeklyTime.value + ', ' + TimeZoneWeeklySelect.value
     if (WeeklyDay.value != '' && WeeklyTime.value != '' && TimeZoneWeeklySelect.value !== '') {
       FrequencyList.value.push(str)
@@ -362,7 +368,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 +381,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;
 }

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

@@ -29,10 +29,28 @@ 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)
 }
+
+const hadleclose = (val:any) => {
+  selectedLables.value = []
+  // 遍历选中的value值,找到对应的code值
+  const option = CheckboxList.value.find((item) => item.label === val)
+  CheckedList.value = CheckedList.value.filter((item) => item !== option.value)
+}
+defineExpose({
+  hadleclose
+})
 </script>
 <template>
   <div class="Ocean_collapse">
@@ -48,7 +66,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 +78,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) {

+ 33 - 20
src/components/AddRules/src/components/ShipmentRange.vue

@@ -25,23 +25,27 @@ TransportList.value = [
 
 const TimeChecked = ref()
 
-const ETDTime = ref('')
-const ETATime = ref('')
+const ETDTime = ref()
+const ETATime = ref()
 
-const clampedETDValue = computed(() => {
-  if (ETDTime.value == '') {
-    return 0
-  } else {
-    const numericValue = ETDTime.value.replace(/[^0-9]/g, 0) // 移除非数字字符
-    return Math.min(Math.max(parseInt(numericValue, 10), 0), 365) // 确保值在0到1000之间,但不更新原输入值仅用于显示
+const clampedETDValue = computed({
+  get: () => ETDTime.value,
+  set: (newVal) => {
+    const isValid = /^[1-9]\d*$/.test(newVal);
+    ETDTime.value = isValid ? newVal : '';
+    if( ETDTime.value!='') {
+      ETDTime.value = Math.min(Math.max(parseInt(ETDTime.value, 10), 1), 365)
+    }
   }
 })
-const clampedETAValue = computed(() => {
-  if (ETATime.value == '') {
-    return 0
-  } else {
-    const numericValue = ETATime.value.replace(/[^0-9]/g, 0) // 移除非数字字符
-    return Math.min(Math.max(parseInt(numericValue, 10), 0), 365) // 确保值在0到1000之间,但不更新原输入值仅用于显示
+const clampedETAValue = computed({
+  get: () => ETATime.value,
+  set: (newVal) => {
+    const isValid = /^[1-9]\d*$/.test(newVal);
+    ETATime.value = isValid ? newVal : '';
+    if( ETATime.value!='') {
+      ETATime.value = Math.min(Math.max(parseInt(ETATime.value, 10), 1), 365)
+    }
   }
 })
 
@@ -60,9 +64,17 @@ const CheckChange = (val: any) => {
 // 输入ETD、ETA
 const changeTime = (val: any) => {
   if (val == 1) {
-    Timestr = 'ETD within ' + clampedETDValue.value + ' Day(s)'
+    if(clampedETDValue.value != '' && clampedETDValue.value != undefined) {
+      Timestr = 'ETD within ' + clampedETDValue.value + ' Day(s)'
+    } else{
+      Timestr = ''
+    }
   } else if (val == 2) {
-    Timestr = 'ETA within ' + clampedETAValue.value + ' Day(s)'
+    if(clampedETAValue.value != '' && clampedETAValue.value != undefined) {
+      Timestr = 'ETA within ' + clampedETAValue.value + ' Day(s)'
+    } else{
+      Timestr = ''
+    }
   } else {
     Timestr = ''
   }
@@ -112,8 +124,7 @@ defineExpose({
                 ETD within
                 <el-input
                   @input="changeTime('1')"
-                  v-model="ETDTime"
-                  :value="clampedETDValue"
+                  v-model="clampedETDValue"
                   class="input-with-select"
                 >
                 </el-input>
@@ -125,8 +136,7 @@ defineExpose({
                 ETA within
                 <el-input
                   @input="changeTime('2')"
-                  v-model="ETATime"
-                  :value="clampedETAValue"
+                  v-model="clampedETAValue"
                   class="input-with-select"
                 >
                 </el-input>
@@ -193,4 +203,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>

二進制
src/components/AddRules/src/images/illustration_system massage_darkmode@2x.png


+ 258 - 97
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)
       }
     })
 }
@@ -505,25 +592,23 @@ const Savesubscribe = () => {
   if (props.TitleType == 'Milestone') {
     savesubscribeobj.rules_type = 'Milestone_Update'
     if (
-      OceanCheckList.value == undefined ||
+      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) {
-        missingmessage.value += 'Ocean Shipments, '
-      }
-      if (AirCheckList.value == undefined) {
-        missingmessage.value += 'Air Shipments, '
+      if (OceanCheckList.value == undefined && AirCheckList.value == undefined || OceanCheckList.value.length == 0 && AirCheckList.value.length == 0 ) {
+        missingmessage.value += 'Select Milstone, '
       }
       if (MilFrequencyList.value.length == 0) {
         missingmessage.value += 'Notification Frequency, '
@@ -534,14 +619,24 @@ 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
-      str =
+      savesubscribeobj.ocean_milestone = OceanCheckListCode.value
+      savesubscribeobj.air_milestone = AirCheckListCode.value
+      if(OceanCheckList.value.length == 0) {
+        str =
+        'Air Milestones: ' +
+        AirCheckList.value.join(',')
+      } else if(AirCheckList.value.length == 0) {
+        str =
+        'Ocean Milestones: ' +
+        OceanCheckList.value.join(',')
+      } else {
+        str =
         'Ocean Milestones: ' +
         OceanCheckList.value.join(',') +
         ';\nAir Milestones: ' +
         AirCheckList.value.join(',') +
         ';'
+      }
       savesubscribeobj.event_details = str
       SaveSuceessful()
     }
@@ -549,10 +644,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 +660,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 +669,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 +704,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) {
@@ -659,6 +757,21 @@ const clearData = (val: any) => {
   }
 }
 
+// 删除Oceam shipments
+const MilOceanref = ref()
+const MilAirref = ref()
+const ContainerOcean = ref()
+const handleCloseMilestoneOcean = (val:any) => {
+  MilOceanref.value.hadleclose(val)
+}
+const handleCloseMilestoneAir = (val:any) => {
+  MilAirref.value.hadleclose(val)
+}
+
+const handleCloseContainer = (val:any) => {
+  ContainerOcean.value.hadleclose(val)
+}
+
 defineExpose({
   clearData,
   Savesubscribe,
@@ -673,7 +786,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 +807,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 +836,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'"
@@ -733,6 +849,7 @@ defineExpose({
             <div>
               <RulesShipments
                 Title="Ocean shipments"
+                ref="MilOceanref"
                 @ChangeCheckRules="ChangeCheckOceanRules"
                 :CheckboxList="MilestoneOceanListInit"
                 :CheckedList="MilestoneOceanListChecked"
@@ -741,6 +858,7 @@ defineExpose({
             <div>
               <RulesShipments
                 Title="Air shipments"
+                ref="MilAirref"
                 @ChangeCheckRules="ChangeCheckAirRules"
                 :CheckboxList="MilestoneAirListInit"
                 :CheckedList="MilestoneAirListChecked"
@@ -756,7 +874,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'"
@@ -769,6 +887,7 @@ defineExpose({
             <div>
               <RulesShipments
                 Title="Ocean shipments"
+                ref="ContainerOcean"
                 @ChangeCheckRules="ChangeContainerRules"
                 :CheckboxList="ContainerOceanListInit"
                 :CheckedList="ContainerOceanListChecked"
@@ -784,7 +903,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 +939,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 +975,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 +1019,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'"
@@ -968,12 +1087,14 @@ defineExpose({
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Milestone'"
+        @handleCloseRadio="handleCloseMilestoneOcean"
         :CheckedList="OceanCheckList"
         Title="Ocean Shipments"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Container'"
         :CheckedList="ContainerOceanList"
+        @handleCloseRadio="handleCloseContainer"
         Title="Ocean Shipments"
       ></AddedrluesTag>
       <AddedrluesTag
@@ -997,6 +1118,7 @@ defineExpose({
       <AddedrluesTag
         v-if="props.TitleType == 'Milestone'"
         :CheckedList="AirCheckList"
+        @handleCloseRadio="handleCloseMilestoneAir"
         Title="Air Shipments"
       ></AddedrluesTag>
       <AddedrluesTag
@@ -1075,6 +1197,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 +1305,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 +1331,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 +1374,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 +1382,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;

+ 63 - 32
src/components/CreateAddRules/src/components/DelayedType.vue

@@ -38,9 +38,9 @@ const isDeparture = ref(false)
 const isArrival = ref(false)
 const OceanActive = ref(['DelayedShipments'])
 const OceanCheckedList = ref()
-const DepartureTime = ref('')
+const DepartureTime = ref()
 const DepartureSelect = ref('')
-const ArrivalTime = ref('')
+const ArrivalTime = ref()
 const ArrivalSelect = ref('')
 const emit = defineEmits(['ChangeCheckRules', 'closeDelayed'])
 const DepartureList = ref({
@@ -50,34 +50,45 @@ const DepartureList = ref({
 let Departurestr: any = ''
 let Arrivalstr: any = ''
 
-const clampedValue = computed(() => {
-  if (DepartureTime.value == '') {
-    return 0
-  } else {
-    const numericValue = DepartureTime.value.replace(/[^0-9]/g, 0) // 移除非数字字符
-    return Math.min(Math.max(parseInt(numericValue, 10), 0), 365) // 确保值在0到1000之间,但不更新原输入值仅用于显示
+const clampedValue = computed({
+  get: () => DepartureTime.value,
+  set: (newVal) => {
+    const isValid = /^[1-9]\d*$/.test(newVal);
+    DepartureTime.value = isValid ? newVal : '';
+    if( DepartureTime.value!='') {
+      DepartureTime.value = Math.min(Math.max(parseInt(DepartureTime.value, 10), 1), 365)
+    }
   }
 })
-const clampedArrivalValue = computed(() => {
-  if (ArrivalTime.value == '') {
-    return 0
-  } else {
-    const numericValue = ArrivalTime.value.replace(/[^0-9]/g, 0) // 移除非数字字符
-    return Math.min(Math.max(parseInt(numericValue, 10), 0), 365) // 确保值在0到1000之间,但不更新原输入值仅用于显示
+const clampedArrivalValue = computed({
+  get: () => ArrivalTime.value,
+  set: (newVal) => {
+    const isValid = /^[1-9]\d*$/.test(newVal);
+    ArrivalTime.value = isValid ? newVal : '';
+    if( ArrivalTime.value!='') {
+      ArrivalTime.value = Math.min(Math.max(parseInt(ArrivalTime.value, 10), 1), 365)
+    }
   }
 })
 
 const CheckChange = (val: any) => {
   if (val.includes('Departure Delayed')) {
     isDeparture.value = true
-    Departurestr = 'Departure Delayed' + ' ≥ ' + clampedValue.value + ' ' + DepartureSelect.value
+    if(clampedValue.value != '' && clampedValue.value!= undefined) {
+      Departurestr = 'Departure Delayed' + ' ≥ ' + clampedValue.value + ' ' + DepartureSelect.value
+    } else {
+      Departurestr = ''
+    }
     if (DepartureSelect.value != '') {
       DepartureList.value.Departure = Departurestr
     }
     if (val.includes('Arrival Delayed (ATA-ETA)')) {
       isArrival.value = true
-      Arrivalstr =
-        'Arrival Delayed (ATA-ETA)' + ' ≥ ' + clampedArrivalValue.value + ' ' + ArrivalSelect.value
+      if(clampedArrivalValue.value != '' && clampedArrivalValue.value!= undefined) {
+        Arrivalstr = 'Departure Delayed' + ' ≥ ' + clampedArrivalValue.value + ' ' + DepartureSelect.value
+      } else {
+        Arrivalstr = ''
+      }
       if (ArrivalSelect.value != '') {
         DepartureList.value.Arrival = Arrivalstr
       }
@@ -90,8 +101,11 @@ const CheckChange = (val: any) => {
     DepartureList.value.Departure = ''
     if (val.includes('Arrival Delayed (ATA-ETA)')) {
       isArrival.value = true
-      Arrivalstr =
-        'Arrival Delayed (ATA-ETA)' + ' ≥ ' + clampedArrivalValue.value + ' ' + ArrivalSelect.value
+      if(clampedArrivalValue.value != '' && clampedArrivalValue.value!= undefined) {
+        Arrivalstr = 'Departure Delayed' + ' ≥ ' + clampedArrivalValue.value + ' ' + DepartureSelect.value
+      } else {
+        Arrivalstr = ''
+      }
       if (ArrivalSelect.value != '') {
         DepartureList.value.Arrival = Arrivalstr
       }
@@ -104,21 +118,31 @@ const CheckChange = (val: any) => {
 }
 const changedeparture = (val: any) => {
   if (val == 'Departure') {
-    Departurestr = 'Departure Delayed' + ' ≥ ' + clampedValue.value + ' ' + DepartureSelect.value
+    if(clampedValue.value != '' && clampedValue.value!= undefined) {
+      Departurestr = 'Departure Delayed' + ' ≥ ' + clampedValue.value + ' ' + DepartureSelect.value
+    } else {
+      Departurestr = ''
+    }
     if (DepartureSelect.value != '') {
       DepartureList.value.Departure = Departurestr
     }
     if (val == 'Arrival') {
-      Arrivalstr =
-        'Arrival Delayed (ATA-ETA)' + ' ≥ ' + clampedArrivalValue.value + ' ' + ArrivalSelect.value
+      if(clampedArrivalValue.value != '' && clampedArrivalValue.value!= undefined) {
+        Arrivalstr = 'Departure Delayed' + ' ≥ ' + clampedArrivalValue.value + ' ' + DepartureSelect.value
+      } else {
+        Arrivalstr = ''
+      }
       if (ArrivalSelect.value != '') {
         DepartureList.value.Arrival = Arrivalstr
       }
     }
   } else {
     if (val == 'Arrival') {
-      Arrivalstr =
-        'Arrival Delayed (ATA-ETA)' + ' ≥ ' + clampedArrivalValue.value + ' ' + ArrivalSelect.value
+      if(clampedArrivalValue.value != '' && clampedArrivalValue.value!= undefined) {
+        Arrivalstr = 'Departure Delayed' + ' ≥ ' + clampedArrivalValue.value + ' ' + DepartureSelect.value
+      } else {
+        Arrivalstr = ''
+      }
       if (ArrivalSelect.value != '') {
         DepartureList.value.Arrival = Arrivalstr
       }
@@ -177,9 +201,8 @@ defineExpose({
                 <span class="delayedTitle">Delayed Time</span>
                 <span class="delayedIcon">></span>
                 <el-input
-                  v-model="DepartureTime"
+                  v-model="clampedValue"
                   class="input-with-select"
-                  :value="clampedValue"
                   @input="changedeparture('Departure')"
                 >
                   <template #append>
@@ -201,9 +224,8 @@ defineExpose({
                 <span class="delayedTitle">Delayed Time</span>
                 <span class="delayedIcon">></span>
                 <el-input
-                  v-model="ArrivalTime"
+                  v-model="clampedArrivalValue"
                   @input="changedeparture('Arrival')"
-                  :value="clampedArrivalValue"
                   class="input-with-select"
                 >
                   <template #append>
@@ -228,13 +250,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 +286,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 +298,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>

+ 70 - 34
src/components/CreateAddRules/src/components/ETDShipments.vue

@@ -49,9 +49,9 @@ const isETD = ref(false)
 const isETA = ref(false)
 const OceanActive = ref(['ETDShipments'])
 const OceanCheckedList = ref()
-const ETDTime = ref('')
+const ETDTime = ref()
 const ETDSelect = ref('')
-const ETATime = ref('')
+const ETATime = ref()
 const ETASelect = ref('')
 const ETDRadio = ref()
 const ETARadio = ref()
@@ -65,13 +65,25 @@ let ETAstr: any = ''
 const CheckChange = (val: any) => {
   if (val.includes('ETD')) {
     isETD.value = true
-    if (ETDSelect.value != '') {
+    if(ETDstr.includes('all changes')) {
       ETDETAList.value.ETD = ETDstr
+    } else {
+      if (ETDSelect.value != ''  && clampedValue.value!= '') {
+        ETDETAList.value.ETD = ETDstr
+      } else {
+        ETDETAList.value.ETD = ''
+      }
     }
     if (val.includes('ETA')) {
       isETA.value = true
-      if (ETASelect.value != '') {
+      if(ETAstr.includes('all changes')) {
         ETDETAList.value.ETA = ETAstr
+      } else {
+        if (ETASelect.value != ''  && clampedETAValue.value!= '') {
+          ETDETAList.value.ETA = ETAstr
+        } else {
+          ETDETAList.value.ETA = ''
+        }
       }
     } else {
       isETA.value = false
@@ -82,8 +94,14 @@ const CheckChange = (val: any) => {
     ETDETAList.value.ETD = ''
     if (val.includes('ETA')) {
       isETA.value = true
-      if (ETASelect.value != '') {
+      if(ETAstr.includes('all changes')) {
         ETDETAList.value.ETA = ETAstr
+      } else {
+        if (ETASelect.value != ''  && clampedETAValue.value!= '') {
+          ETDETAList.value.ETA = ETAstr
+        } else {
+          ETDETAList.value.ETA = ''
+        }
       }
     } else {
       isETA.value = false
@@ -98,7 +116,7 @@ const changeETDRadio = (val: any) => {
     ETDETAList.value.ETD = ETDstr
   } else if (val == 2) {
     ETDstr = 'ETD: Notify for all changes ≥ ' + clampedValue.value + ' ' + ETDSelect.value
-    if (ETDSelect.value != '') {
+    if (ETDSelect.value != '' && clampedValue.value!= '') {
       ETDETAList.value.ETD = ETDstr
     } else {
       ETDETAList.value.ETD = ''
@@ -112,7 +130,7 @@ const changeETARadio = (val: any) => {
     ETDETAList.value.ETA = ETAstr
   } else if (val == 2) {
     ETAstr = 'ETA: Notify for all changes ≥ ' + clampedETAValue.value + ' ' + ETASelect.value
-    if (ETASelect.value != '') {
+    if (ETASelect.value != '' && clampedValue.value!= '') {
       ETDETAList.value.ETA = ETAstr
     } else {
       ETDETAList.value.ETA = ''
@@ -123,20 +141,26 @@ const changeETARadio = (val: any) => {
 const changedeparture = (val: any) => {
   if (val == 'ETD') {
     ETDstr = 'ETD: Notify for all changes ≥ ' + clampedValue.value + ' ' + ETDSelect.value
-    if (ETDSelect.value != '') {
+    if (ETDSelect.value != '' && clampedValue.value!= '') {
       ETDETAList.value.ETD = ETDstr
+    } else {
+      ETDETAList.value.ETD = ''
     }
     if (val == 'ETA') {
       ETAstr = 'ETA: Notify for all changes ≥ ' + clampedETAValue.value + ' ' + ETASelect.value
-      if (ETASelect.value != '') {
+      if (ETASelect.value != '' && clampedETAValue.value!= '') {
         ETDETAList.value.ETA = ETAstr
+      } else {
+        ETDETAList.value.ETA = ''
       }
     }
   } else {
     if (val == 'ETA') {
       ETAstr = 'ETA: Notify for all changes ≥ ' + clampedETAValue.value + ' ' + ETASelect.value
-      if (ETASelect.value != '') {
+      if (ETASelect.value != '' && clampedETAValue.value!= '') {
         ETDETAList.value.ETA = ETAstr
+      } else {
+        ETDETAList.value.ETA = ''
       }
     }
   }
@@ -182,20 +206,24 @@ defineExpose({
   ClearData
 })
 
-const clampedValue = computed(() => {
-  if (ETDTime.value == '') {
-    return 0
-  } else {
-    const numericValue = ETDTime.value.replace(/[^0-9]/g, 0) // 移除非数字字符
-    return Math.min(Math.max(parseInt(numericValue, 10), 0), 365) // 确保值在0到1000之间,但不更新原输入值仅用于显示
+const clampedValue = computed({
+  get: () => ETDTime.value,
+  set: (newVal) => {
+    const isValid = /^[1-9]\d*$/.test(newVal);
+    ETDTime.value = isValid ? newVal : '';
+    if( ETDTime.value!='') {
+      ETDTime.value = Math.min(Math.max(parseInt(ETDTime.value, 10), 1), 365)
+    }
   }
 })
-const clampedETAValue = computed(() => {
-  if (ETATime.value == '') {
-    return 0
-  } else {
-    const numericValue = ETATime.value.replace(/[^0-9]/g, 0) // 移除非数字字符
-    return Math.min(Math.max(parseInt(numericValue, 10), 0), 365) // 确保值在0到1000之间,但不更新原输入值仅用于显示
+const clampedETAValue = computed({
+  get: () => ETATime.value,
+  set: (newVal) => {
+    const isValid = /^[1-9]\d*$/.test(newVal);
+    ETATime.value = isValid ? newVal : '';
+    if( ETATime.value!='') {
+      ETATime.value = Math.min(Math.max(parseInt(ETATime.value, 10), 1), 365)
+    }
   }
 })
 </script>
@@ -217,8 +245,7 @@ const clampedETAValue = computed(() => {
                     >Notify only when time difference
                     <span class="delayedIcon">></span>
                     <el-input
-                      v-model="ETDTime"
-                      :value="clampedValue"
+                      v-model="clampedValue"
                       class="input-with-select"
                       @input="changedeparture('ETD')"
                     >
@@ -229,7 +256,7 @@ const clampedETAValue = computed(() => {
                           @change="changedeparture('ETD')"
                         >
                           <el-option label="Day(s)" value="Day(s)" />
-                          <el-option label="Hour(s)" value="Hour(s)" />
+                          <el-option v-if="props.Title == 'Air shipments'" label="Hour(s)" value="Hour(s)" />
                         </el-select>
                       </template> </el-input
                   ></el-radio>
@@ -245,8 +272,7 @@ const clampedETAValue = computed(() => {
                     >Notify only when time difference
                     <span class="delayedIcon">></span>
                     <el-input
-                      v-model="ETATime"
-                      :value="clampedETAValue"
+                      v-model="clampedETAValue"
                       class="input-with-select"
                       @input="changedeparture('ETA')"
                     >
@@ -257,7 +283,7 @@ const clampedETAValue = computed(() => {
                           @change="changedeparture('ETA')"
                         >
                           <el-option label="Day(s)" value="Day(s)" />
-                          <el-option label="Hour(s)" value="Hour(s)" />
+                          <el-option v-if="props.Title == 'Air shipments'" label="Hour(s)" value="Hour(s)" />
                         </el-select>
                       </template> </el-input
                   ></el-radio>
@@ -273,13 +299,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 +318,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 +341,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 +355,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>

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

@@ -180,6 +180,9 @@ const ChangeFrequency = (val: any) => {
   if (val == 1) {
     isDaily.value = false
     isWeekly.value = false
+    DailyTime.value = ''
+    WeeklyDay.value = ''
+    WeeklyTime.value = ''
     str = 'Instant notification for each update'
     FrequencyList.value.push(str)
     savesubscribeobj.frequency_type = 'Instant'
@@ -187,6 +190,8 @@ const ChangeFrequency = (val: any) => {
   } else if (val == 2) {
     isDaily.value = true
     isWeekly.value = false
+    WeeklyDay.value = ''
+    WeeklyTime.value = ''
     str = 'Daily, ' + DailyTime.value + ', ' + TimeZoneDailySelect.value
     if (DailyTime.value != '' && TimeZoneDailySelect.value !== '') {
       FrequencyList.value.push(str)
@@ -198,6 +203,7 @@ const ChangeFrequency = (val: any) => {
   } else if (val == 3) {
     isDaily.value = false
     isWeekly.value = true
+    DailyTime.value = ''
     str = 'Weekly, ' + WeeklyDay.value + ', ' + WeeklyTime.value + ', ' + TimeZoneWeeklySelect.value
     if (WeeklyDay.value != '' && WeeklyTime.value != '' && TimeZoneWeeklySelect.value !== '') {
       FrequencyList.value.push(str)
@@ -362,7 +368,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 +378,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;
 }

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

@@ -29,10 +29,27 @@ 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)
 }
+const hadleclose = (val:any) => {
+  selectedLables.value = []
+  // 遍历选中的value值,找到对应的code值
+  const option = CheckboxList.value.find((item) => item.label === val)
+  CheckedList.value = CheckedList.value.filter((item) => item !== option.value)
+}
+defineExpose({
+  hadleclose
+})
 </script>
 <template>
   <div class="Ocean_collapse">
@@ -58,13 +75,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) {

+ 66 - 30
src/components/CreateAddRules/src/components/ShipmentRange.vue

@@ -29,9 +29,6 @@ TransportList.value = [
 
 const TimeChecked = ref()
 
-const ETDTime = ref('')
-const ETATime = ref('')
-
 watch(
   () => props.ShipmentRangeData,
   (current) => {
@@ -45,32 +42,39 @@ 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)
   }
 }
 
-const clampedETDValue = computed(() => {
-  if (ETDTime.value == '') {
-    return 0
-  } else {
-    const numericValue = ETDTime.value.replace(/[^0-9]/g, 0) // 移除非数字字符
-    return Math.min(Math.max(parseInt(numericValue, 10), 0), 365) // 确保值在0到1000之间,但不更新原输入值仅用于显示
+const ETDTime = ref()
+const ETATime = ref()
+
+const clampedETDValue = computed({
+  get: () => ETDTime.value,
+  set: (newVal) => {
+    const isValid = /^[1-9]\d*$/.test(newVal);
+    ETDTime.value = isValid ? newVal : '';
+    if( ETDTime.value!='') {
+      ETDTime.value = Math.min(Math.max(parseInt(ETDTime.value, 10), 1), 365)
+    }
   }
 })
-const clampedETAValue = computed(() => {
-  if (ETATime.value == '') {
-    return 0
-  } else {
-    const numericValue = ETATime.value.replace(/[^0-9]/g, 0) // 移除非数字字符
-    return Math.min(Math.max(parseInt(numericValue, 10), 0), 365) // 确保值在0到1000之间,但不更新原输入值仅用于显示
+const clampedETAValue = computed({
+  get: () => ETATime.value,
+  set: (newVal) => {
+    const isValid = /^[1-9]\d*$/.test(newVal);
+    ETATime.value = isValid ? newVal : '';
+    if( ETATime.value!='') {
+      ETATime.value = Math.min(Math.max(parseInt(ETATime.value, 10), 1), 365)
+    }
   }
 })
 
@@ -89,13 +93,22 @@ const CheckChange = (val: any) => {
 // 输入ETD、ETA
 const changeTime = (val: any) => {
   if (val == 1) {
-    Timestr = 'ETD within ' + clampedETDValue.value + ' Day(s)'
+    if(clampedETDValue.value != '' && clampedETDValue.value != undefined) {
+      Timestr = 'ETD within ' + clampedETDValue.value + ' Day(s)'
+    } else{
+      Timestr = ''
+    }
+    emit('ChangeCheckTimeRules', Timestr, clampedETDValue.value)
   } else if (val == 2) {
-    Timestr = 'ETA within ' + clampedETAValue.value + ' Day(s)'
+    if(clampedETAValue.value != '' && clampedETAValue.value != undefined) {
+      Timestr = 'ETA within ' + clampedETAValue.value + ' Day(s)'
+    } else{
+      Timestr = ''
+    }
+    emit('ChangeCheckTimeRules', Timestr, clampedETAValue.value)
   } else {
     Timestr = ''
   }
-  emit('ChangeCheckTimeRules', Timestr, clampedETDValue.value)
 }
 
 const handleCloseCreateRule = (val: any) => {
@@ -141,8 +154,7 @@ defineExpose({
                 <div style="width: 70px">ETD within</div>
                 <el-input
                   @input="changeTime('1')"
-                  v-model="ETDTime"
-                  :value="clampedETDValue"
+                  v-model="clampedETDValue"
                   class="input-with-select"
                 >
                 </el-input>
@@ -154,8 +166,7 @@ defineExpose({
                 <div style="width: 70px">ETA within</div>
                 <el-input
                   @input="changeTime('2')"
-                  v-model="ETATime"
-                  :value="clampedETAValue"
+                  v-model="clampedETAValue"
                   class="input-with-select"
                 >
                 </el-input>
@@ -171,13 +182,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 +221,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 +231,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>

二進制
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;
     }
   }
 }

二進制
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;

+ 19 - 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,23 @@ 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 ||
+      from.name === 'Tracking Detail'
+    ) ||
+    !(
+      to.name === 'System Message' ||
+      to.name === 'System Message Detail' ||
+      to.name === 'Tracking 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);

+ 6 - 0
src/styles/index.scss

@@ -23,6 +23,12 @@
   font-family: 'Lato-Light', 'Microsoft JhengHei', Arial, sans-serif !important;
 }
 
+.vxe-tooltip {
+  background-color: #ff5722 !important; /* 设置背景颜色 */
+  color: #ffffff !important; /* 设置文字颜色 */
+  border-radius: 4px; /* 设置圆角 */
+}
+
 .iconfont_icon {
   margin-right: 2.42px;
 }

+ 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;
 }

+ 41 - 2
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);
@@ -382,7 +420,7 @@
   }
   --color-el-popper-bg: #1a1c20;
   // --el-bg-color: var(--color-neutral-1);
-  --el-bg-color-overlay: #30353c;
+  --el-bg-color-overlay: #3e454f;
   div.el-popper.is-dark {
     --el-bg-color: var(--color-neutral-1);
   }
@@ -398,6 +436,7 @@
   --vxe-ui-input-border-color: #656f7d;
   --vxe-ui-table-menu-background-color: #3e454f;
   --color-vxe-table-visited-row-bg: #3c4149;
+  --vxe-ui-tooltip-dark-background-color: #3e454f;
 
   button.el-button.el-button--pain-theme {
     border: 1px solid var(--color-el-btn-pain-theme-border);

+ 0 - 1
src/utils/tools.ts

@@ -66,7 +66,6 @@ export const isEuropean = () => {
  * 根据传入的地区 格式化数字
  * @param number - 需要格式化的数字
  * @param digits - 小数位数
- * @param isEuropean - 是否为欧洲地区
  */
 export const formatNumber = (number: number, digits?: number): string => {
   if (isNaN(number)) return '0'

+ 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) {

+ 1 - 1
src/views/Booking/src/components/BookingTable/src/BookingTable.vue

@@ -386,7 +386,7 @@ const handleCustomizeColumns = () => {
       model_name: 'Booking_Search'
     }
   }
-  CustomizeColumnsRef.value.openDialog(params, -153)
+  CustomizeColumnsRef.value.openDialog(params, -220)
 }
 // 定制表格
 const customizeColumns = async () => {

+ 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">
       <!-- 左 -->

+ 21 - 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,19 @@ const closePopover = () => {
 }
 
 const notificationDrawer = ref(false)
+
+onBeforeRouteUpdate((to, from, next) => {
+  if (from.name === 'Login' && userStore.userName) {
+    notificationMsgStore.hasUnreadMessages()
+  }
+  next()
+})
+onMounted(() => {
+  // 检查是否有新消息 (在页面刷新时重新检查)
+  if (userStore.userName) {
+    notificationMsgStore.hasUnreadMessages()
+  }
+})
 </script>
 
 <template>
@@ -188,7 +205,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 +217,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 +226,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;

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

@@ -1,76 +1,137 @@
 <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">
+    <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" style="height: 100%" 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;
   }
   .notification-card {
     max-width: 800px;
@@ -96,5 +157,8 @@ onMounted(() => {
     line-height: 40px;
     font-size: 12px;
   }
+  .feature-pdf {
+    height: calc(100% - 28px);
+  }
 }
 </style>

+ 15 - 15
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)
@@ -95,7 +95,7 @@ const SubShipmentsColumns = ref([
   {
     field: 'h_bol',
     title: 'HBOL/HAWB',
-    type: 'normal',
+    type: 'link',
     formatter: ''
   },
   {
@@ -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">

+ 29 - 1
src/views/SystemSettings/src/components/SettingTable/src/SettingTable.vue

@@ -3,6 +3,11 @@ import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
 import { useRowClickStyle } from '@/hooks/rowClickStyle'
 import { formatTimezone } from '@/utils/tools'
 import { ref, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import { useVisitedRowState } from '@/stores/modules/visitedRow'
+
+const visitedRowState = useVisitedRowState()
+const router = useRouter()
 interface ColumnsListItem {
   field: String
   title: String
@@ -50,6 +55,11 @@ const handleColumns = (columns: any) => {
         ...curColumn,
         formatter: ({ cellValue }: any) => formatTimezone(cellValue)
       }
+    }  else if (item.type === 'link') {
+      curColumn = {
+        ...curColumn,
+        slots: { default: 'trackingNo' }
+      }
     }
     return curColumn
   })
@@ -96,6 +106,16 @@ const handleDelete = (row: any) => {
     })
 }
 
+// 点击link字段
+const handleLinkClick = (row: any) => {
+  console.log(row)
+  router.push({
+      path: '/tracking/detail',
+      query: { a: row.__serial_no, _schemas: row._schemas }
+    })
+    visitedRowState.setTrackingTableData(row['__serial_no'])
+}
+
 onMounted(() => {
   getTableColumns()
 })
@@ -130,7 +150,15 @@ const getpaginationTableData = () => {
       <template #empty>
         <div class="empty">No data</div>
       </template>
-
+      <!-- Tracking No字段的插槽 -->
+      <template #trackingNo="{ row, column }">
+        <span
+          style="color: var(--color-theme); cursor: pointer"
+          @click="handleLinkClick(row)"
+        >
+          {{ row[column.field] }}
+        </span>
+      </template>
       <template #action="{ row }">
         <el-popover :visible="row.visible" placement="left" :width="480">
           <div class="delete_title">

+ 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>

+ 12 - 17
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()
@@ -166,9 +174,8 @@ const SubscribeShipments = () => {
             <span class="font_family icon-icon_log_b" style="margin-right: 4px"></span
             >AMS/ISF</el-button
           >
-
-          <!-- <el-button
-            class="recent_button"
+          <el-button
+            class="el-button--default"
             @click="SubscribeShipments"
             :class="is_subscribe ? 'IsSubscribe' : ''"
           >
@@ -183,7 +190,7 @@ const SubscribeShipments = () => {
               </svg>
             </span>
             <span class="Subscribe">Subscribe</span>
-          </el-button> -->
+          </el-button>
         </div>
       </div>
       <div class="detail-info">
@@ -550,18 +557,6 @@ const SubscribeShipments = () => {
   text-overflow: ellipsis; /* 超出部分显示省略号 */
 }
 
-.recent_button {
-  background-color: var(--color-white);
-  border: 1px solid var(--color-border);
-  &:hover {
-    border: 1px solid var(--color-btn-main-plain-bg-hover);
-    background-color: var(--color-btn-main-plain-bg-hover);
-    fill: var(--color-theme);
-    .Subscribe {
-      color: var(--color-theme);
-    }
-  }
-}
 .IsSubscribe {
   border: 1px solid var(--color-theme);
   background-color: var(--color-theme);

+ 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,保留两位小数
 }
 

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

@@ -464,7 +464,7 @@ const handleCustomizeColumns = () => {
       model_name: 'Ocean_Search'
     }
   }
-  CustomizeColumnsRef.value.openDialog(params, -153)
+  CustomizeColumnsRef.value.openDialog(params, -220)
 }
 
 // 定制表格
@@ -627,8 +627,7 @@ defineExpose({
           <span class="font_family icon-icon_vgm_b"></span>
           <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 +642,7 @@ defineExpose({
               <use xlink:href="#icon-icon_unmark_b"></use>
             </svg>
           </span>
-        </el-button> -->
+        </el-button>
       </template>
       <!-- Transportation Mode字段的插槽 -->
       <template #mode="{ row, column }">