浏览代码

Merge branch 'dev' into dev_g

AmandaG 9 月之前
父节点
当前提交
1767b3a247
共有 100 个文件被更改,包括 9227 次插入731 次删除
  1. 0 1
      package.json
  2. 5 1
      src/api/index.ts
  3. 43 0
      src/api/module/notificationMessage.ts
  4. 146 0
      src/api/module/system.ts
  5. 1 0
      src/components/AddRules/index.ts
  6. 991 0
      src/components/AddRules/src/AddRules.vue
  7. 78 0
      src/components/AddRules/src/components/AddedrluesTag.vue
  8. 279 0
      src/components/AddRules/src/components/DelayedType.vue
  9. 341 0
      src/components/AddRules/src/components/ETDShipments.vue
  10. 398 0
      src/components/AddRules/src/components/NotiFrequency.vue
  11. 92 0
      src/components/AddRules/src/components/NotiMethods.vue
  12. 83 0
      src/components/AddRules/src/components/RulesShipments.vue
  13. 196 0
      src/components/AddRules/src/components/ShipmentRange.vue
  14. 二进制
      src/components/AddRules/src/images/icon_collapse.png
  15. 二进制
      src/components/AddRules/src/images/icon_expand.png
  16. 二进制
      src/components/AddRules/src/images/illustration_email@2x.png
  17. 二进制
      src/components/AddRules/src/images/illustration_system massage@2x.png
  18. 二进制
      src/components/AddRules/src/images/submit_successful.png
  19. 1 0
      src/components/CreateAddRules/index.ts
  20. 1224 0
      src/components/CreateAddRules/src/CreateAddRules.vue
  21. 78 0
      src/components/CreateAddRules/src/components/AddedrluesTag.vue
  22. 279 0
      src/components/CreateAddRules/src/components/DelayedType.vue
  23. 341 0
      src/components/CreateAddRules/src/components/ETDShipments.vue
  24. 398 0
      src/components/CreateAddRules/src/components/NotiFrequency.vue
  25. 92 0
      src/components/CreateAddRules/src/components/NotiMethods.vue
  26. 83 0
      src/components/CreateAddRules/src/components/RulesShipments.vue
  27. 226 0
      src/components/CreateAddRules/src/components/ShipmentRange.vue
  28. 二进制
      src/components/CreateAddRules/src/images/icon_collapse.png
  29. 二进制
      src/components/CreateAddRules/src/images/icon_expand.png
  30. 二进制
      src/components/CreateAddRules/src/images/illustration_email@2x.png
  31. 二进制
      src/components/CreateAddRules/src/images/illustration_system massage@2x.png
  32. 二进制
      src/components/CreateAddRules/src/images/submit_successful.png
  33. 69 32
      src/components/DateRange/src/DateRange.vue
  34. 9 7
      src/components/DateRange/src/components/CalendarDate.vue
  35. 9 7
      src/components/DateRange/src/components/QuickCalendarDate.vue
  36. 1 0
      src/components/NotificationMessageCard/index.ts
  37. 21 0
      src/components/NotificationMessageCard/src/NotificationMessageCard.vue
  38. 349 0
      src/components/NotificationMessageCard/src/components/EventCard.vue
  39. 101 0
      src/components/NotificationMessageCard/src/components/FeatureUpdateCard.vue
  40. 87 0
      src/components/NotificationMessageCard/src/components/PasswordCard.vue
  41. 二进制
      src/components/NotificationMessageCard/src/images/icon_publish.png
  42. 二进制
      src/components/NotificationMessageCard/src/images/test.png
  43. 4 1
      src/components/ScoringGrade/src/ScoringGrade.vue
  44. 2 6
      src/components/ShipmentStatus/src/ShipmentStatus.vue
  45. 46 0
      src/components/TableEmpty/TableEmpty.vue
  46. 二进制
      src/components/TableEmpty/image/default_notification_setting.png
  47. 1 0
      src/components/TableEmpty/index.ts
  48. 29 3
      src/router/index.ts
  49. 22 2
      src/stores/modules/breadCrumb.ts
  50. 65 8
      src/stores/modules/user.ts
  51. 21 17
      src/styles/elementui.scss
  52. 44 4
      src/styles/icons/iconfont.css
  53. 0 0
      src/styles/icons/iconfont.js
  54. 20 0
      src/styles/icons/iconfont.svg
  55. 二进制
      src/styles/icons/iconfont.ttf
  56. 二进制
      src/styles/icons/iconfont.woff
  57. 二进制
      src/styles/icons/iconfont.woff2
  58. 29 0
      src/styles/theme.scss
  59. 0 6
      src/utils/axios.ts
  60. 27 0
      src/utils/timezone.ts
  61. 80 10
      src/utils/tools.ts
  62. 1 1
      src/views/Booking/src/components/BookingDetail/src/BookingDetail.vue
  63. 12 3
      src/views/Booking/src/components/BookingDetail/src/components/BasicInformation.vue
  64. 6 11
      src/views/Booking/src/components/BookingDetail/src/components/ContainersView.vue
  65. 3 3
      src/views/Booking/src/components/BookingDetail/src/components/EmailView.vue
  66. 9 12
      src/views/Booking/src/components/BookingTable/src/BookingTable.vue
  67. 6 2
      src/views/Dashboard/src/components/BarChart.vue
  68. 8 7
      src/views/Dashboard/src/components/DashFiters.vue
  69. 7 3
      src/views/Dashboard/src/components/PieChart.vue
  70. 19 4
      src/views/Dashboard/src/components/RecentStatus.vue
  71. 6 2
      src/views/Dashboard/src/components/RevenueChart.vue
  72. 5 1
      src/views/Dashboard/src/components/ScoringSystem.vue
  73. 12 5
      src/views/Dashboard/src/components/SellerChart.vue
  74. 2 1
      src/views/Dashboard/src/components/TopMap.vue
  75. 92 30
      src/views/Layout/src/components/Header/HeaderView.vue
  76. 9 0
      src/views/Layout/src/components/Header/components/ChangePasswordDialog.vue
  77. 1 0
      src/views/Layout/src/components/Header/components/LogoutDialog.vue
  78. 316 0
      src/views/Layout/src/components/Header/components/NotificationDrawer.vue
  79. 199 0
      src/views/Layout/src/components/Header/components/TrainingCard.vue
  80. 57 4
      src/views/Layout/src/components/Menu/MenuView.vue
  81. 1 4
      src/views/Login/src/components/ChangePasswordCard.vue
  82. 5 3
      src/views/Login/src/loginView.vue
  83. 9 17
      src/views/OperationLog/src/components/BookingTable/src/BookingTable.vue
  84. 0 510
      src/views/OperationLog/src/components/BookingTable/src/BookingTableColumns.ts
  85. 1 0
      src/views/SystemMessage/index.ts
  86. 350 0
      src/views/SystemMessage/src/SystemMessage.vue
  87. 100 0
      src/views/SystemMessage/src/components/SystemMessageDetail.vue
  88. 1 0
      src/views/SystemSettings/index.ts
  89. 466 0
      src/views/SystemSettings/src/SystemSettings.vue
  90. 1 0
      src/views/SystemSettings/src/components/CreateNewrule/index.ts
  91. 264 0
      src/views/SystemSettings/src/components/CreateNewrule/src/CreateNewrule.vue
  92. 1 0
      src/views/SystemSettings/src/components/MonitoringTable/index.ts
  93. 253 0
      src/views/SystemSettings/src/components/MonitoringTable/src/MonitoringTable.vue
  94. 369 0
      src/views/SystemSettings/src/components/PersonalProfile.vue
  95. 1 0
      src/views/SystemSettings/src/components/SettingTable/index.ts
  96. 211 0
      src/views/SystemSettings/src/components/SettingTable/src/SettingTable.vue
  97. 二进制
      src/views/SystemSettings/src/images/icon_collapse_colorful.png
  98. 二进制
      src/views/SystemSettings/src/images/icon_expand_colorful.png
  99. 二进制
      src/views/SystemSettings/src/images/xia.png
  100. 13 3
      src/views/Tracking/src/components/PublicTracking/src/components/BasicInformation.vue

+ 0 - 1
package.json

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

+ 5 - 1
src/api/index.ts

@@ -3,6 +3,8 @@ import * as tracking from './module/tracking'
 import * as common from './module/common'
 import * as login from './module/login'
 import * as other from './module/other'
+import * as notificationMessage from './module/notificationMessage'
+import * as system from './module/system'
 /**
  * api 对象接口定义
  */
@@ -19,7 +21,9 @@ const apis = generateApiMap({
   ...tracking,
   ...common,
   ...login,
-  ...other
+  ...other,
+  ...notificationMessage,
+  ...system
 })
 export default {
   ...apis // 取出所有可遍历属性赋值在新的对象上

+ 43 - 0
src/api/module/notificationMessage.ts

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

+ 146 - 0
src/api/module/system.ts

@@ -0,0 +1,146 @@
+import HttpAxios from '@/utils/axios'
+
+const base = import.meta.env.VITE_API_HOST
+const baseUrl = `${base}/main_new_version.php`
+
+
+/**
+ * 获取subscribe数据
+ */
+export const getsubscribe = (params: any, config: any) => {
+  return HttpAxios.get(
+    `${baseUrl}`,
+    {
+      action: 'system_setting',
+      operate: 'subscribe_notification_init',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * 提交subscribe数据
+ */
+export const Savesubscribe = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'system_setting',
+      operate: 'subscribe_notification_event_update',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * 删除AddedRules表格数据
+ */
+export const DeleteAddedRules = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'system_setting',
+      operate: 'subscribe_notification_rules_delete',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * 收藏
+ */
+export const SubscribeShipments = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'system_setting',
+      operate: 'subscribe_shipment',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * 收藏表格分页切换
+ */
+export const SubscribePagination = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'system_setting',
+      operate: 'subscribe_shipment_search',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * 初始化数据
+ */
+export const MonitoringInit = (params: any, config: any) => {
+  return HttpAxios.get(
+    `${baseUrl}`,
+    {
+      action: 'monitoring_setting',
+      operate: 'monitoring_rules_init',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * Monitoring保存数据
+ */
+export const MonitoringSave = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'monitoring_setting',
+      operate: 'monitoring_rules_do',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * Monitoring表格数据
+ */
+export const MonitoringTable = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'monitoring_setting',
+      operate: 'monitoring_rules_search',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * 删除Monitoring表格数据
+ */
+export const deleteMonitoringTable = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'monitoring_setting',
+      operate: 'monitoring_rules_delete',
+      ...params
+    },
+    config
+  )
+}
+/**
+ * 编辑Monitoring表格数据
+ */
+export const EditMonitoringTable = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'monitoring_setting',
+      operate: 'monitoring_rules_edit',
+      ...params
+    },
+    config
+  )
+}

+ 1 - 0
src/components/AddRules/index.ts

@@ -0,0 +1 @@
+export { default } from './src/AddRules.vue'

+ 991 - 0
src/components/AddRules/src/AddRules.vue

@@ -0,0 +1,991 @@
+<script lang="ts" setup>
+import { ref, watch } from 'vue'
+import RulesShipments from './components/RulesShipments.vue'
+import AddedrluesTag from './components/AddedrluesTag.vue'
+import DelayedType from './components/DelayedType.vue'
+import ETDShipments from './components/ETDShipments.vue'
+import NotiFrequency from './components/NotiFrequency.vue'
+import NotiMethods from './components/NotiMethods.vue'
+import submitsucessful from './images/submit_successful.png'
+interface CheckboxItem {
+  value: string
+  label: string
+}
+
+interface Props {
+  SystemList: Object
+  TitleType: String
+}
+const MilestoneOceanListInit = ref<CheckboxItem[]>([])
+const MilestoneOceanListChecked = ref()
+const MilestoneAirListInit = ref<CheckboxItem[]>([])
+const MilestoneAirListChecked = ref()
+const ContainerOceanListInit = ref<CheckboxItem[]>([])
+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 AirCheckList = ref()
+const ContainerOceanList = ref()
+const IsFirstActive = ref(true)
+const IsTwoActive = ref(true)
+const IsThreeActive = ref(true)
+const CancelRulesVisible = ref(false)
+const UnableSaveVisible = ref(false)
+const SaveedVisible = ref(false)
+const DelayedDeparturedList = ref()
+const DelayedAirdList = ref()
+const ETDOceanList = ref()
+const ETDAirList = ref()
+const FrequencyDataMil = ref()
+const FrequencyDataCon = ref()
+const FrequencyDataDep = ref()
+const FrequencyDataETD = ref()
+const MethodsDataMil = ref()
+const MethodsDataCon = ref()
+const MethodsDataDep = ref()
+const MethodsDataETD = ref()
+const DelayedDataInit = ref()
+const DelayedDataInitAir = ref()
+const OceanETDInit = ref()
+const AirETDInit = ref()
+
+watch(
+  () => props.SystemList,
+  (current) => {
+    SystemList.value = current
+    FrequencyDataMil.value = current['Milestone_Update']
+    MethodsDataMil.value = current['Milestone_Update']
+    FrequencyDataCon.value = current['Container_Status_Update']
+    MethodsDataCon.value = current['Container_Status_Update']
+    FrequencyDataDep.value = current['Departure/Arrival_Delay']
+    MethodsDataDep.value = current['Departure/Arrival_Delay']
+    FrequencyDataETD.value = current['ETD/ETA_Change']
+    MethodsDataETD.value = current['ETD/ETA_Change']
+    Initdata(current)
+  }
+)
+
+// 初始赋值
+const Initdata = (val: any) => {
+  MilestoneOceanListInit.value = val.Milestone_Update.OceanCheckBoxList
+  MilestoneOceanListChecked.value = val.Milestone_Update.OceanCheckedList
+  OceanCheckList.value = val.Milestone_Update.OceanCheckedList
+  MilestoneAirListInit.value = val.Milestone_Update.AirCheckBoxList
+  MilestoneAirListChecked.value = val.Milestone_Update.AirCheckedList
+  AirCheckList.value = val.Milestone_Update.AirCheckedList
+  ContainerOceanListInit.value = val.Container_Status_Update.CtnrCheckBoxList
+  ContainerOceanListChecked.value = val.Container_Status_Update.CtnrCheckedList
+  ContainerOceanList.value = val.Container_Status_Update.CtnrCheckedList
+  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
+  OceanObj.ata_eta = val['Departure/Arrival_Delay'].ocean_ata_sub_eta
+  OceanObj.ata_eta_unit = val['Departure/Arrival_Delay'].ocean_ata_sub_eta_unit
+  DelayedDataInit.value = OceanObj
+  let AirObj: any = {}
+  AirObj.atd_etd = val['Departure/Arrival_Delay'].air_atd_sub_etd
+  AirObj.atd_etd_unit = val['Departure/Arrival_Delay'].air_atd_sub_etd_unit
+  AirObj.ata_eta = val['Departure/Arrival_Delay'].air_ata_sub_eta
+  AirObj.ata_eta_unit = val['Departure/Arrival_Delay'].air_ata_sub_eta_unit
+  DelayedDataInitAir.value = AirObj
+  let OceanChange: any = {}
+  OceanChange.ETDradio = val['ETD/ETA_Change'].ocean_etd_change
+  OceanChange.etd_old_sub_new = val['ETD/ETA_Change'].ocean_etd_old_sub_new
+  OceanChange.etd_old_sub_new_unit = val['ETD/ETA_Change'].ocean_etd_old_sub_new_unit
+  OceanChange.ETAradio = val['ETD/ETA_Change'].ocean_eta_change
+  OceanChange.eta_old_sub_new = val['ETD/ETA_Change'].ocean_eta_old_sub_new
+  OceanChange.eta_old_sub_new_unit = val['ETD/ETA_Change'].ocean_eta_old_sub_new_unit
+  OceanETDInit.value = OceanChange
+  let AirChange: any = {}
+  AirChange.ETDradio = val['ETD/ETA_Change'].air_etd_change
+  AirChange.etd_old_sub_new = val['ETD/ETA_Change'].air_etd_old_sub_new
+  AirChange.etd_old_sub_new_unit = val['ETD/ETA_Change'].air_etd_old_sub_new_unit
+  AirChange.ETAradio = val['ETD/ETA_Change'].air_eta_change
+  AirChange.eta_old_sub_new = val['ETD/ETA_Change'].air_eta_old_sub_new
+  AirChange.eta_old_sub_new_unit = val['ETD/ETA_Change'].air_eta_old_sub_new_unit
+  AirETDInit.value = AirChange
+}
+
+// 给tag list赋值
+const ChangeCheckOceanRules = (val: any) => {
+  OceanCheckList.value = val
+}
+const ChangeContainerRules = (val: any) => {
+  ContainerOceanList.value = val
+}
+// delayed赋值
+const ChangeDeayedRules = (val: any) => {
+  DelayedDeparturedList.value = []
+  if (val.Departure != '') {
+    DelayedDeparturedList.value.push(val.Departure)
+    savesubscribeobj.ocean_atd_sub_etd = val.Departure.split(' ')[3]
+    savesubscribeobj.ocean_atd_sub_etd_unit = val.Departure.split(' ')[4]
+  } else {
+    delete savesubscribeobj.ocean_atd_sub_etd
+    delete savesubscribeobj.ocean_atd_sub_etd_unit
+  }
+  if (val.Arrival != '') {
+    DelayedDeparturedList.value.push(val.Arrival)
+    savesubscribeobj.ocean_ata_sub_eta = val.Arrival.split(' ')[4]
+    savesubscribeobj.ocean_ata_sub_eta_unit = val.Arrival.split(' ')[5]
+  } else {
+    delete savesubscribeobj.ocean_ata_sub_eta
+    delete savesubscribeobj.ocean_ata_sub_eta_unit
+  }
+}
+const ChangeAirRules = (val: any) => {
+  DelayedAirdList.value = []
+  if (val.Departure != '') {
+    DelayedAirdList.value.push(val.Departure)
+    savesubscribeobj.air_atd_sub_etd = val.Departure.split(' ')[3]
+    savesubscribeobj.air_atd_sub_etd_unit = val.Departure.split(' ')[4]
+  } else {
+    delete savesubscribeobj.air_atd_sub_etd
+    delete savesubscribeobj.air_atd_sub_etd_unit
+  }
+  if (val.Arrival != '') {
+    DelayedAirdList.value.push(val.Arrival)
+    savesubscribeobj.air_ata_sub_eta = val.Arrival.split(' ')[4]
+    savesubscribeobj.air_ata_sub_eta_unit = val.Arrival.split(' ')[5]
+  } else {
+    delete savesubscribeobj.air_ata_sub_eta
+    delete savesubscribeobj.air_ata_sub_eta_unit
+  }
+}
+const ChangeCheckAirRules = (val: any) => {
+  AirCheckList.value = val
+}
+
+// 更改Frequency时间
+const MilFrequencyList = ref()
+const ChangeMilFrequency = (val: any, type: any) => {
+  MilFrequencyList.value = val
+  savesubscribeobj = { ...savesubscribeobj, ...type }
+}
+const ConFrequencyList = ref()
+const ChangeConFrequency = (val: any, type: any) => {
+  ConFrequencyList.value = val
+  savesubscribeobj = { ...savesubscribeobj, ...type }
+}
+const DepFrequencyList = ref()
+const ChangeDepFrequency = (val: any, type: any) => {
+  DepFrequencyList.value = val
+  savesubscribeobj = { ...savesubscribeobj, ...type }
+}
+const ETDFrequencyList = ref()
+const ChangeETDFrequency = (val: any, type: any) => {
+  ETDFrequencyList.value = val
+  savesubscribeobj = { ...savesubscribeobj, ...type }
+}
+
+// 删除 Frequency tag
+const NotiFrequencyDeleteMil = ref()
+const NotiFrequencyDeleteCon = ref()
+const NotiFrequencyDeleteDep = ref()
+const NotiFrequencyDeleteETD = ref()
+const handleCloseRadio = (val: any) => {
+  if (val == 'Mil') {
+    NotiFrequencyDeleteMil.value.handleCloseRadioFrequency()
+  } else if (val == 'Con') {
+    NotiFrequencyDeleteCon.value.handleCloseRadioFrequency()
+  } else if (val == 'Dep') {
+    NotiFrequencyDeleteDep.value.handleCloseRadioFrequency()
+  } else {
+    NotiFrequencyDeleteETD.value.handleCloseRadioFrequency()
+  }
+}
+
+// methods 切换
+const MilMethodsList = ref()
+const ChangeMethodsAddMil = (val: any, type: any) => {
+  MilMethodsList.value = val
+  savesubscribeobj = { ...savesubscribeobj, ...type }
+}
+const ConMethodsList = ref()
+const ChangeMethodsAddCon = (val: any, type: any) => {
+  ConMethodsList.value = val
+  savesubscribeobj = { ...savesubscribeobj, ...type }
+}
+const DepMethodsList = ref()
+const ChangeMethodsAddDep = (val: any, type: any) => {
+  DepMethodsList.value = val
+  savesubscribeobj = { ...savesubscribeobj, ...type }
+}
+const ETDMethodsList = ref()
+const ChangeMethodsAddETD = (val: any, type: any) => {
+  ETDMethodsList.value = val
+  savesubscribeobj = { ...savesubscribeobj, ...type }
+}
+const OceanDelayed = ref()
+const AirDelayed = ref()
+
+const handleCloseDelayed = (val: any) => {
+  OceanDelayed.value.closeDelayed(val)
+}
+const handleCloseAirDelayed = (val: any) => {
+  AirDelayed.value.closeDelayed(val)
+}
+// ETD Change
+const ChangeETDOceanRules = (val: any) => {
+  ETDOceanList.value = []
+  if (val.ETD != '') {
+    if (val.ETD.indexOf('≥') != -1) {
+      savesubscribeobj.ocean_etd_change = false
+      savesubscribeobj.ocean_etd_old_sub_new = val.ETD.split(' ')[6]
+      savesubscribeobj.ocean_etd_old_sub_new_unit = val.ETD.split(' ')[7]
+    } else {
+      savesubscribeobj.ocean_etd_change = true
+    }
+    ETDOceanList.value.push(val.ETD)
+  } else {
+    delete savesubscribeobj.ocean_etd_change
+    delete savesubscribeobj.ocean_etd_old_sub_new
+    delete savesubscribeobj.ocean_etd_old_sub_new_unit
+  }
+  if (val.ETA != '') {
+    if (val.ETA.indexOf('≥') != -1) {
+      savesubscribeobj.ocean_eta_change = false
+      savesubscribeobj.ocean_eta_old_sub_new = val.ETA.split(' ')[6]
+      savesubscribeobj.ocean_eta_old_sub_new_unit = val.ETA.split(' ')[7]
+    } else {
+      savesubscribeobj.ocean_eta_change = true
+    }
+    ETDOceanList.value.push(val.ETA)
+  } else {
+    delete savesubscribeobj.ocean_eta_change
+    delete savesubscribeobj.ocean_eta_old_sub_new
+    delete savesubscribeobj.ocean_eta_old_sub_new_unit
+  }
+}
+// 删除ETD tag
+const OceanETD = ref()
+const closeOceanETD = (val: any) => {
+  OceanETD.value.closeETD(val)
+}
+// ETA Change
+const ChangeETDAirRules = (val: any) => {
+  ETDAirList.value = []
+  if (val.ETD != '') {
+    if (val.ETD.indexOf('≥') != -1) {
+      savesubscribeobj.air_etd_change = false
+      savesubscribeobj.air_etd_old_sub_new = val.ETD.split(' ')[6]
+      savesubscribeobj.air_etd_old_sub_new_unit = val.ETD.split(' ')[7]
+    } else {
+      savesubscribeobj.air_etd_change = true
+    }
+    ETDAirList.value.push(val.ETD)
+  } else {
+    delete savesubscribeobj.air_etd_change
+    delete savesubscribeobj.air_etd_old_sub_new
+    delete savesubscribeobj.air_etd_old_sub_new_unit
+  }
+  if (val.ETA != '') {
+    if (val.ETA.indexOf('≥') != -1) {
+      savesubscribeobj.air_eta_change = false
+      savesubscribeobj.air_eta_old_sub_new = val.ETA.split(' ')[6]
+      savesubscribeobj.air_eta_old_sub_new_unit = val.ETA.split(' ')[7]
+    } else {
+      savesubscribeobj.air_eta_change = true
+    }
+    ETDAirList.value.push(val.ETA)
+  } else {
+    delete savesubscribeobj.air_eta_change
+    delete savesubscribeobj.air_eta_old_sub_new
+    delete savesubscribeobj.air_eta_old_sub_new_unit
+  }
+}
+
+// 删除ETA tag
+const AirETD = ref()
+const closeAirETD = (val: any) => {
+  AirETD.value.closeETD(val)
+}
+
+const emits = defineEmits(['UnsavedCollapse', 'SavedAddedRules'])
+// 不保存修改的折叠面板
+const UnsavedCollapse = () => {
+  CancelRulesVisible.value = false
+  emits('UnsavedCollapse')
+}
+
+// 保存subscribe配置
+const missingmessage = ref('')
+// 保存成功调用接口
+const SaveSuceessful = () => {
+  $api
+    .Savesubscribe({
+      ...savesubscribeobj
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        SaveedVisible.value = true
+        setTimeout(() => {
+          SaveedVisible.value = false
+          emits('SavedAddedRules', res.data.addedRules, savesubscribeobj.rules_type)
+        }, 3000)
+      }
+    })
+}
+const Savesubscribe = () => {
+  let str = ''
+  if (props.TitleType == 'Milestone') {
+    savesubscribeobj.rules_type = 'Milestone_Update'
+    if (
+      OceanCheckList.value == undefined ||
+      AirCheckList.value == undefined ||
+      MilFrequencyList.value.length == 0 ||
+      MilMethodsList.value.length == 0
+    ) {
+      if (OceanCheckList.value == undefined) {
+        missingmessage.value += 'Ocean Shipments, '
+      }
+      if (AirCheckList.value == undefined) {
+        missingmessage.value += 'Air Shipments, '
+      }
+      if (MilFrequencyList.value.length == 0) {
+        missingmessage.value += 'Notification Frequency, '
+      }
+      if (MilMethodsList.value.length == 0) {
+        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 =
+        'Ocean Milestones: ' +
+        OceanCheckList.value.join(',') +
+        ';\nAir Milestones: ' +
+        AirCheckList.value.join(',') +
+        ';'
+      savesubscribeobj.event_details = str
+      SaveSuceessful()
+    }
+  } else if (props.TitleType == 'Container') {
+    savesubscribeobj.rules_type = 'Container_Status_Update'
+    if (
+      ContainerOceanList.value == undefined ||
+      ConFrequencyList.value.length == 0 ||
+      ConMethodsList.value.length == 0
+    ) {
+      if (ContainerOceanList.value == undefined) {
+        missingmessage.value += 'Ocean Shipments, '
+      }
+      if (ConFrequencyList.value.length == 0) {
+        missingmessage.value += 'Notification Frequency, '
+      }
+      if (ConMethodsList.value.length == 0) {
+        missingmessage.value += 'Notification Method, '
+      }
+      missingmessage.value = missingmessage.value.substring(0, missingmessage.value.length - 2)
+      UnableSaveVisible.value = true
+    } else {
+      savesubscribeobj.ocean_ctnr_status = ContainerOceanList.value
+      str = 'Ocean Container: ' + ContainerOceanList.value.join(',')
+      savesubscribeobj.event_details = str
+      SaveSuceessful()
+    }
+  } else if (props.TitleType == 'Departure') {
+    savesubscribeobj.rules_type = 'Departure/Arrival_Delay'
+    if (
+      DelayedDeparturedList.value == undefined ||
+      DelayedAirdList.value == undefined ||
+      DepFrequencyList.value.length == 0 ||
+      DepMethodsList.value.length == 0
+    ) {
+      if (DelayedDeparturedList.value == undefined) {
+        missingmessage.value += 'Ocean Shipments, '
+      }
+      if (DelayedAirdList.value == undefined) {
+        missingmessage.value += 'Air Shipments, '
+      }
+      if (DepFrequencyList.value.length == 0) {
+        missingmessage.value += 'Notification Frequency, '
+      }
+      if (DepMethodsList.value.length == 0) {
+        missingmessage.value += 'Notification Method, '
+      }
+      missingmessage.value = missingmessage.value.substring(0, missingmessage.value.length - 2)
+      UnableSaveVisible.value = true
+    } else {
+      str = DelayedDeparturedList.value.join(';\n') + ';\n' + DelayedAirdList.value.join(';\n')
+      savesubscribeobj.event_details = str
+      SaveSuceessful()
+    }
+  } else {
+    savesubscribeobj.rules_type = 'ETD/ETA_Change'
+    if (
+      ETDOceanList.value == undefined ||
+      ETDOceanList.value.length == 0 ||
+      ETDAirList.value == undefined ||
+      ETDAirList.value.length == 0 ||
+      ETDFrequencyList.value.length == 0 ||
+      ETDMethodsList.value.length == 0
+    ) {
+      if (ETDOceanList.value == undefined) {
+        missingmessage.value += 'Ocean Shipments, '
+      }
+      if (ETDAirList.value == undefined) {
+        missingmessage.value += 'Air Shipments, '
+      }
+      if (ETDFrequencyList.value.length == 0) {
+        missingmessage.value += 'Notification Frequency, '
+      }
+      if (ETDMethodsList.value.length == 0) {
+        missingmessage.value += 'Notification Method, '
+      }
+      missingmessage.value = missingmessage.value.substring(0, missingmessage.value.length - 2)
+      UnableSaveVisible.value = true
+    } else {
+      str = ETDOceanList.value.join(';\n') + ';\n' + ETDAirList.value.join(';\n')
+      savesubscribeobj.event_details = str
+      SaveSuceessful()
+    }
+  }
+}
+
+// 删除表格数据后清空所有数据
+const clearData = (val: any) => {
+  if (val == 'Milestone_Update') {
+    OceanCheckList.value = []
+    AirCheckList.value = []
+    MilFrequencyList.value = []
+    MilMethodsList.value = []
+    MilestoneOceanListChecked.value = []
+    MilestoneAirListChecked.value = []
+    handleCloseRadio('Mil')
+  } else if (val == 'Container_Status_Update') {
+    ContainerOceanList.value = []
+    ConFrequencyList.value = []
+    ConMethodsList.value = []
+    ContainerOceanListChecked.value = []
+    handleCloseRadio('Con')
+  } else if (val == 'Departure/Arrival_Delay') {
+    DelayedDeparturedList.value = []
+    DelayedAirdList.value = []
+    DepFrequencyList.value = []
+    DepMethodsList.value = []
+    OceanDelayed.value.ClearData()
+    AirDelayed.value.ClearData()
+    handleCloseRadio('Dep')
+  } else {
+    ETDOceanList.value = []
+    ETDAirList.value = []
+    ETDFrequencyList.value = []
+    ETDMethodsList.value = []
+    handleCloseRadio('ETD')
+    OceanETD.value.ClearData()
+    AirETD.value.ClearData()
+  }
+}
+
+defineExpose({
+  clearData
+})
+</script>
+<template>
+  <div class="Rules_flex">
+    <div class="Rules_left">
+      <div class="Rules_collapse">
+        <el-collapse
+          v-model="RulesActive"
+          @change="IsFirstActive = !IsFirstActive"
+          v-if="props.TitleType == 'Milestone'"
+        >
+          <el-collapse-item name="SelectMilestone">
+            <template #title>
+              <div class="Rules_Title">
+                <span class="iconfont_icon">
+                  <svg class="iconfont" aria-hidden="true">
+                    <use
+                      :xlink:href="IsFirstActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
+                    ></use>
+                  </svg>
+                </span>
+                <span class="stars_red">*</span>Select Milestone
+              </div>
+            </template>
+            <div>
+              <RulesShipments
+                Title="Ocean shipments"
+                @ChangeCheckRules="ChangeCheckOceanRules"
+                :CheckboxList="MilestoneOceanListInit"
+                :CheckedList="MilestoneOceanListChecked"
+              ></RulesShipments>
+            </div>
+            <div>
+              <RulesShipments
+                Title="Air shipments"
+                @ChangeCheckRules="ChangeCheckAirRules"
+                :CheckboxList="MilestoneAirListInit"
+                :CheckedList="MilestoneAirListChecked"
+              ></RulesShipments>
+            </div>
+          </el-collapse-item>
+        </el-collapse>
+        <el-collapse
+          v-model="RulesActive"
+          @change="IsFirstActive = !IsFirstActive"
+          v-if="props.TitleType == 'Container'"
+        >
+          <el-collapse-item name="SelectMilestone">
+            <template #title>
+              <div class="Rules_Title">
+                <span class="iconfont_icon">
+                  <svg class="iconfont" aria-hidden="true">
+                    <use
+                      :xlink:href="IsFirstActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
+                    ></use>
+                  </svg>
+                </span>
+                <span class="stars_red">*</span>Select Container Status
+              </div>
+            </template>
+            <div>
+              <RulesShipments
+                Title="Ocean shipments"
+                @ChangeCheckRules="ChangeContainerRules"
+                :CheckboxList="ContainerOceanListInit"
+                :CheckedList="ContainerOceanListChecked"
+              ></RulesShipments>
+            </div>
+          </el-collapse-item>
+        </el-collapse>
+        <el-collapse
+          v-model="RulesActive"
+          @change="IsFirstActive = !IsFirstActive"
+          v-if="props.TitleType == 'Departure'"
+        >
+          <el-collapse-item name="SelectMilestone">
+            <template #title>
+              <div class="Rules_Title">
+                <span class="iconfont_icon">
+                  <svg class="iconfont" aria-hidden="true">
+                    <use
+                      :xlink:href="IsFirstActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
+                    ></use>
+                  </svg>
+                </span>
+                <span class="stars_red">*</span>Select Delayed Type
+              </div>
+            </template>
+            <div>
+              <DelayedType
+                Title="Ocean shipments"
+                ref="OceanDelayed"
+                :DelayedData="DelayedDataInit"
+                @ChangeCheckRules="ChangeDeayedRules"
+              ></DelayedType>
+            </div>
+            <div>
+              <DelayedType
+                Title="Air shipments"
+                ref="AirDelayed"
+                :DelayedData="DelayedDataInitAir"
+                @ChangeCheckRules="ChangeAirRules"
+              ></DelayedType>
+            </div>
+          </el-collapse-item>
+        </el-collapse>
+        <el-collapse
+          v-model="RulesActive"
+          @change="IsFirstActive = !IsFirstActive"
+          v-if="props.TitleType == 'ETDChange'"
+        >
+          <el-collapse-item name="SelectMilestone">
+            <template #title>
+              <div class="Rules_Title">
+                <span class="iconfont_icon">
+                  <svg class="iconfont" aria-hidden="true">
+                    <use
+                      :xlink:href="IsFirstActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
+                    ></use>
+                  </svg>
+                </span>
+                <span class="stars_red">*</span>Select Delayed Type
+              </div>
+            </template>
+            <div>
+              <ETDShipments
+                Title="Ocean shipments"
+                ref="OceanETD"
+                :ETDData="OceanETDInit"
+                @ChangeCheckRules="ChangeETDOceanRules"
+              ></ETDShipments>
+            </div>
+            <div>
+              <ETDShipments
+                Title="Air shipments"
+                ref="AirETD"
+                :ETDData="AirETDInit"
+                @ChangeCheckRules="ChangeETDAirRules"
+              ></ETDShipments>
+            </div>
+          </el-collapse-item>
+        </el-collapse>
+        <el-collapse
+          style="margin-top: 17px; margin-right: 16px"
+          v-model="RulesActive"
+          @change="IsTwoActive = !IsTwoActive"
+        >
+          <el-collapse-item name="NotificationFrequency">
+            <template #title>
+              <div class="Rules_Title">
+                <span class="iconfont_icon">
+                  <svg class="iconfont" aria-hidden="true">
+                    <use
+                      :xlink:href="IsTwoActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
+                    ></use>
+                  </svg>
+                </span>
+                <span class="stars_red">*</span>Notification Frequency
+              </div>
+            </template>
+            <NotiFrequency
+              v-if="props.TitleType == 'Milestone'"
+              ref="NotiFrequencyDeleteMil"
+              :FrequencyData="FrequencyDataMil"
+              @ChangeFrequencyAdd="ChangeMilFrequency"
+            ></NotiFrequency>
+            <NotiFrequency
+              v-if="props.TitleType == 'Container'"
+              ref="NotiFrequencyDeleteCon"
+              :FrequencyData="FrequencyDataCon"
+              @ChangeFrequencyAdd="ChangeConFrequency"
+            ></NotiFrequency>
+            <NotiFrequency
+              v-if="props.TitleType == 'Departure'"
+              ref="NotiFrequencyDeleteDep"
+              :FrequencyData="FrequencyDataDep"
+              @ChangeFrequencyAdd="ChangeDepFrequency"
+            ></NotiFrequency>
+            <NotiFrequency
+              v-if="props.TitleType == 'ETDChange'"
+              ref="NotiFrequencyDeleteETD"
+              :FrequencyData="FrequencyDataETD"
+              @ChangeFrequencyAdd="ChangeETDFrequency"
+            ></NotiFrequency>
+          </el-collapse-item>
+        </el-collapse>
+        <el-collapse
+          style="margin: 17px 0"
+          v-model="RulesActive"
+          @change="IsThreeActive = !IsThreeActive"
+        >
+          <el-collapse-item name="NotificationMethod">
+            <template #title>
+              <div class="Rules_Title">
+                <span class="iconfont_icon">
+                  <svg class="iconfont" aria-hidden="true">
+                    <use
+                      :xlink:href="IsThreeActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
+                    ></use>
+                  </svg>
+                </span>
+                <span class="stars_red">*</span>Notification Method
+              </div>
+            </template>
+            <NotiMethods
+              v-if="props.TitleType == 'Milestone'"
+              :MethodsData="MethodsDataMil"
+              @ChangeMethodsAdd="ChangeMethodsAddMil"
+            ></NotiMethods>
+            <NotiMethods
+              v-if="props.TitleType == 'Container'"
+              :MethodsData="MethodsDataCon"
+              @ChangeMethodsAdd="ChangeMethodsAddCon"
+            ></NotiMethods>
+            <NotiMethods
+              v-if="props.TitleType == 'Departure'"
+              :MethodsData="MethodsDataDep"
+              @ChangeMethodsAdd="ChangeMethodsAddDep"
+            ></NotiMethods>
+            <NotiMethods
+              v-if="props.TitleType == 'ETDChange'"
+              :MethodsData="MethodsDataETD"
+              @ChangeMethodsAdd="ChangeMethodsAddETD"
+            ></NotiMethods>
+          </el-collapse-item>
+        </el-collapse>
+      </div>
+    </div>
+    <div class="Rules_right">
+      <div class="right_Title">Added Rules</div>
+      <AddedrluesTag
+        :CheckedList="DelayedDeparturedList"
+        v-if="props.TitleType == 'Departure'"
+        @handleCloseRadio="handleCloseDelayed"
+        Title="Ocean Shipments"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'Milestone'"
+        :CheckedList="OceanCheckList"
+        Title="Ocean Shipments"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'Container'"
+        :CheckedList="ContainerOceanList"
+        Title="Ocean Shipments"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        :CheckedList="ETDOceanList"
+        v-if="props.TitleType == 'ETDChange'"
+        Title="Ocean Shipments"
+        @handleCloseRadio="closeOceanETD"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        :CheckedList="ETDAirList"
+        v-if="props.TitleType == 'ETDChange'"
+        @handleCloseRadio="closeAirETD"
+        Title="Air Shipments"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        :CheckedList="DelayedAirdList"
+        v-if="props.TitleType == 'Departure'"
+        @handleCloseRadio="handleCloseAirDelayed"
+        Title="Air Shipments"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'Milestone'"
+        :CheckedList="AirCheckList"
+        Title="Air Shipments"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'Milestone'"
+        :CheckedList="MilFrequencyList"
+        Title="Notification Frequency"
+        @handleCloseRadio="handleCloseRadio('Mil')"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'Container'"
+        :CheckedList="ConFrequencyList"
+        Title="Notification Frequency"
+        @handleCloseRadio="handleCloseRadio('Con')"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'Departure'"
+        :CheckedList="DepFrequencyList"
+        Title="Notification Frequency"
+        @handleCloseRadio="handleCloseRadio('Dep')"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'ETDChange'"
+        :CheckedList="ETDFrequencyList"
+        Title="Notification Frequency"
+        @handleCloseRadio="handleCloseRadio('ETD')"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'Milestone'"
+        :CheckedList="MilMethodsList"
+        Title="Notification Method"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'Container'"
+        :CheckedList="ConMethodsList"
+        Title="Notification Method"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'Departure'"
+        :CheckedList="DepMethodsList"
+        Title="Notification Method"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'ETDChange'"
+        :CheckedList="ETDMethodsList"
+        Title="Notification Method"
+      ></AddedrluesTag>
+    </div>
+  </div>
+  <div class="Rules_buttom">
+    <el-button class="el-button--dark rules_button" @click="Savesubscribe">Save</el-button>
+    <el-button @click="CancelRulesVisible = true" class="rules_button" type="default"
+      >Cancel</el-button
+    >
+    <!-- 取消保存 -->
+    <el-dialog v-model="CancelRulesVisible" width="480">
+      <div>You have unsaved changes.</div>
+      <div>Are you sure you want to leave this page?</div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="default" @click="CancelRulesVisible = false" style="width: 100px"
+            >Cancel</el-button
+          >
+          <el-button class="el-button--warning" @click="UnsavedCollapse" style="width: 100px">
+            OK
+          </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>
+          Unsaved Changes
+        </div>
+      </template>
+    </el-dialog>
+    <!-- 保存失败 -->
+    <el-dialog v-model="UnableSaveVisible" width="480">
+      <div>{{ missingmessage }} missing.</div>
+      <div>Please complete all required fields.</div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button
+            class="el-button--danger"
+            @click="UnableSaveVisible = 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="SaveedVisible" width="320" style="height: 212px">
+      <div style="text-align: center"><el-image :src="submitsucessful" /></div>
+      <div style="text-align: center; margin-top: 20px">Saved successfully</div>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.Rules_flex {
+  padding: 0 0 0 8px;
+  display: flex;
+}
+.Rules_left {
+  width: 60%;
+  border-right: 1px solid var(--color-user-config-title-bottom-border);
+}
+.Rules_right {
+  width: 40%;
+  background-color: var(--more-filters-background-color);
+}
+.right_Title {
+  color: var(--color-neutral-1);
+  padding: 9px 8px;
+  font-size: 18px;
+  font-weight: 700;
+  border-bottom: 1px solid var(--color-user-config-title-bottom-border);
+  background-color: var(--color-drawer-body-bg);
+}
+.iconfont {
+  width: 16px;
+  height: 16px;
+  margin-right: 8px;
+}
+.icon_danger {
+  fill: var(--color-btn-danger-bg);
+}
+:deep(.Rules_Title) {
+  color: var(--color-neutral-1);
+  font-size: 14px;
+  font-weight: 700;
+  height: 22px;
+  display: flex;
+  align-items: center;
+}
+.stars_red {
+  color: var(--color-danger);
+}
+:deep(.el-collapse-item__header) {
+  height: 25px !important;
+  margin: 14px 0 0 0 !important;
+  border: none !important;
+  padding: 0 !important;
+}
+:deep(.el-collapse-item__header):hover {
+  background-color: #fff !important;
+  border: none !important;
+}
+:deep(.el-collapse-item__arrow) {
+  width: 0 !important;
+  height: 0 !important;
+}
+:deep(.el-icon svg) {
+  width: 0 !important;
+}
+:deep(.el-collapse-item__header.is-active) {
+  background-color: transparent !important;
+  border-color: transparent !important;
+}
+:deep(.el-collapse-item__arrow.is-active) {
+  transform: rotate(-180deg) !important;
+}
+:deep(.Ocean_collapse .el-collapse-item__arrow) {
+  width: 16px !important;
+  height: 16px !important;
+  background-image: url('../src/images/icon_expand.png') !important;
+  background-size: contain;
+  background-repeat: no-repeat;
+  transform: rotate(0);
+}
+:deep(.Ocean_collapse .el-collapse-item__header) {
+  background-color: #fff !important;
+  padding: 0 8px !important;
+  height: 40px !important;
+}
+:deep(.Ocean_collapse .el-collapse-item) {
+  background-color: var(--color-dialog-header-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;
+}
+: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);
+}
+.rules_button {
+  width: 100px;
+  height: 40px;
+}
+.cancel_header {
+  font-size: 18px;
+  font-weight: 700;
+  color: var(--color-neutral-1);
+  display: flex;
+  align-items: center;
+}
+.icon_warning {
+  width: 22px;
+  height: 22px;
+  margin-right: 0;
+  fill: var(--color-btn-warning-bg);
+}
+.iconfont_warning {
+  display: flex;
+  align-items: center;
+}
+:deep(header.el-dialog__header) {
+  background-color: #fff;
+}
+:deep(footer.el-dialog__footer) {
+  border-top: none;
+}
+:deep(.el-collapse) {
+  margin-right: 8px;
+}
+</style>

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

@@ -0,0 +1,78 @@
+<script setup lang="ts" >
+import { ref, watch } from 'vue'
+const props = defineProps({
+  CheckedList: Array,
+  Title: String
+})
+const CheckedList = ref(props.CheckedList)
+watch(
+  () => props.CheckedList,
+  (current) => {
+    CheckedList.value = current
+  }
+)
+
+const emit = defineEmits(['handleCloseRadio'])
+
+const handleClose = (tag: any) => {
+  CheckedList.value?.splice(CheckedList.value.indexOf(tag), 1)
+  emit('handleCloseRadio', tag)
+}
+</script>
+
+<template>
+  <div>
+    <el-card class="Rules_Card">
+      <div class="Card_Title">{{ props.Title }}</div>
+      <div class="right_flex">
+        <el-tag
+          class="tag"
+          v-for="(tag, index) in CheckedList"
+          :key="index"
+          closable
+          @close="handleClose(tag)"
+        >
+          {{ tag }}
+        </el-tag>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.tag {
+  border-radius: 3px;
+  margin-bottom: 4px;
+  color: var(--color-neutral-1);
+  height: 32px;
+  width: fit-content;
+  font-weight: 400;
+  font-size: 14px;
+  background-color: var(--tag-bg-color) !important;
+  border-color: var(--tag-bg-color);
+}
+.tag:last-child {
+  margin-bottom: 0;
+}
+:deep(.el-card__body) {
+  padding: 8px !important;
+  max-height: 400px;
+  overflow-y: scroll;
+}
+:deep(.el-tag .el-tag__close svg) {
+  width: 16px !important;
+}
+.right_flex {
+  display: flex;
+  flex-direction: column;
+}
+.Rules_Card {
+  margin: 8px;
+}
+.Card_Title {
+  color: var(--color-neutral-1);
+  font-size: 14px;
+  font-weight: 600;
+  margin: 9px 0 11px 0;
+}
+</style>

+ 279 - 0
src/components/AddRules/src/components/DelayedType.vue

@@ -0,0 +1,279 @@
+<script lang="ts" setup>
+import { ref, computed, watch } from 'vue'
+
+const props = defineProps({
+  Title: String,
+  DelayedData: Object
+})
+const delayed_data = ref(props.DelayedData)
+watch(
+  () => props.DelayedData,
+  (current) => {
+    delayed_data.value = current
+    DelayedInit()
+  }
+)
+
+const DelayedInit = () => {
+  let array2: any = []
+  OceanCheckedList.value = []
+  if (delayed_data.value?.atd_etd != '' && delayed_data.value?.atd_etd != undefined) {
+    isDeparture.value = true
+    DepartureTime.value = delayed_data.value?.atd_etd
+    DepartureSelect.value = delayed_data.value?.atd_etd_unit
+    OceanCheckedList.value.push('Departure Delayed')
+    array2.push('Departure')
+  }
+  if (delayed_data.value?.ata_eta != '' && delayed_data.value?.ata_eta != undefined) {
+    isArrival.value = true
+    ArrivalTime.value = delayed_data.value?.ata_eta
+    ArrivalSelect.value = delayed_data.value?.ata_eta_unit
+    OceanCheckedList.value.push('Arrival Delayed (ATA-ETA)')
+    array2.push('Arrival')
+  }
+  CheckChange(OceanCheckedList.value)
+  changedeparture(array2)
+}
+const isDeparture = ref(false)
+const isArrival = ref(false)
+const OceanActive = ref(['DelayedShipments'])
+const OceanCheckedList = ref()
+const DepartureTime = ref('')
+const DepartureSelect = ref('')
+const ArrivalTime = ref('')
+const ArrivalSelect = ref('')
+const emit = defineEmits(['ChangeCheckRules', 'closeDelayed'])
+const DepartureList = ref({
+  Departure: '',
+  Arrival: ''
+})
+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 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 CheckChange = (val: any) => {
+  if (val.includes('Departure Delayed')) {
+    isDeparture.value = true
+    Departurestr = 'Departure Delayed' + ' ≥ ' + clampedValue.value + ' ' + DepartureSelect.value
+    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 (ArrivalSelect.value != '') {
+        DepartureList.value.Arrival = Arrivalstr
+      }
+    } else {
+      isArrival.value = false
+      DepartureList.value.Arrival = ''
+    }
+  } else {
+    isDeparture.value = false
+    DepartureList.value.Departure = ''
+    if (val.includes('Arrival Delayed (ATA-ETA)')) {
+      isArrival.value = true
+      Arrivalstr =
+        'Arrival Delayed (ATA-ETA)' + ' ≥ ' + clampedArrivalValue.value + ' ' + ArrivalSelect.value
+      if (ArrivalSelect.value != '') {
+        DepartureList.value.Arrival = Arrivalstr
+      }
+    } else {
+      isArrival.value = false
+      DepartureList.value.Arrival = ''
+    }
+  }
+  emit('ChangeCheckRules', DepartureList.value)
+}
+const changedeparture = (val: any) => {
+  if (val == 'Departure') {
+    Departurestr = 'Departure Delayed' + ' ≥ ' + clampedValue.value + ' ' + DepartureSelect.value
+    if (DepartureSelect.value != '') {
+      DepartureList.value.Departure = Departurestr
+    }
+    if (val == 'Arrival') {
+      Arrivalstr =
+        'Arrival Delayed (ATA-ETA)' + ' ≥ ' + clampedArrivalValue.value + ' ' + ArrivalSelect.value
+      if (ArrivalSelect.value != '') {
+        DepartureList.value.Arrival = Arrivalstr
+      }
+    }
+  } else {
+    if (val == 'Arrival') {
+      Arrivalstr =
+        'Arrival Delayed (ATA-ETA)' + ' ≥ ' + clampedArrivalValue.value + ' ' + ArrivalSelect.value
+      if (ArrivalSelect.value != '') {
+        DepartureList.value.Arrival = Arrivalstr
+      }
+    }
+  }
+  emit('ChangeCheckRules', DepartureList.value)
+}
+const closeDelayed = (val: any) => {
+  if (val.includes('Departure')) {
+    OceanCheckedList.value.splice(OceanCheckedList.value.indexOf('Departure Delayed'), 1)
+    DepartureList.value.Departure = ''
+    isDeparture.value = false
+    DepartureTime.value = ''
+    DepartureSelect.value = ''
+  }
+  if (val.includes('Arrival')) {
+    OceanCheckedList.value.splice(OceanCheckedList.value.indexOf('Arrival Delayed (ATA-ETA)'), 1)
+    DepartureList.value.Arrival = ''
+    isArrival.value = false
+    ArrivalTime.value = ''
+    ArrivalSelect.value = ''
+  }
+  emit('ChangeCheckRules', DepartureList.value)
+}
+
+// 清除所有数据
+const ClearData = () => {
+  OceanCheckedList.value = []
+  DepartureList.value.Departure = ''
+  isDeparture.value = false
+  DepartureList.value.Arrival = ''
+  isArrival.value = false
+  DepartureTime.value = ''
+  DepartureSelect.value = ''
+  ArrivalTime.value = ''
+  ArrivalSelect.value = ''
+}
+
+defineExpose({
+  closeDelayed,
+  ClearData
+})
+</script>
+<template>
+  <div class="Ocean_collapse">
+    <el-collapse v-model="OceanActive">
+      <el-collapse-item name="DelayedShipments">
+        <template #title>
+          <div class="Rules_Title OceanTitle">{{ props.Title }}</div>
+        </template>
+        <div class="oceanCheckbox">
+          <el-checkbox-group @change="CheckChange" v-model="OceanCheckedList">
+            <el-checkbox class="delayedType" value="Departure Delayed">
+              <div>Departure Delayed (ATD-ETD)</div>
+              <div v-if="isDeparture" style="margin-top: 16px">
+                <span class="delayedTitle">Delayed Time</span>
+                <span class="delayedIcon">></span>
+                <el-input
+                  v-model="DepartureTime"
+                  class="input-with-select"
+                  :value="clampedValue"
+                  @input="changedeparture('Departure')"
+                >
+                  <template #append>
+                    <el-select
+                      v-model="DepartureSelect"
+                      placeholder="Select"
+                      @change="changedeparture('Departure')"
+                    >
+                      <el-option label="Day(s)" value="Day(s)" />
+                      <el-option label="Hour(s)" value="Hour(s)" />
+                    </el-select>
+                  </template>
+                </el-input>
+              </div>
+            </el-checkbox>
+            <el-checkbox class="delayedType" value="Arrival Delayed (ATA-ETA)">
+              <div>Arrival Delayed (ATA-ETA)</div>
+              <div v-if="isArrival" style="margin-top: 16px">
+                <span class="delayedTitle">Delayed Time</span>
+                <span class="delayedIcon">></span>
+                <el-input
+                  v-model="ArrivalTime"
+                  @input="changedeparture('Arrival')"
+                  :value="clampedArrivalValue"
+                  class="input-with-select"
+                >
+                  <template #append>
+                    <el-select
+                      v-model="ArrivalSelect"
+                      placeholder="Select"
+                      @change="changedeparture('Arrival')"
+                    >
+                      <el-option label="Day(s)" value="Day(s)" />
+                      <el-option label="Hour(s)" value="Hour(s)" />
+                    </el-select>
+                  </template>
+                </el-input>
+              </div>
+            </el-checkbox>
+          </el-checkbox-group>
+        </div>
+      </el-collapse-item>
+    </el-collapse>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+:deep(.el-checkbox-group) {
+  border: 1px solid var(--color-select-border);
+  border-radius: 5px;
+}
+:deep(.el-checkbox) {
+  width: 100%;
+  background-color: var(--color-drawer-body-bg);
+  border-bottom: 1px solid var(--color-select-border);
+  padding: 8px;
+}
+:deep(.el-checkbox:first-child) {
+  border-radius: 6px 6px 0 0;
+}
+:deep(.el-checkbox:last-child) {
+  border-radius: 0 0 6px 6px;
+  border-bottom: none;
+}
+.delayedType {
+  align-items: start;
+  height: fit-content;
+  margin-right: 5px;
+  border-radius: 6px;
+  padding: 8px;
+}
+.input-with-select {
+  width: 180px;
+}
+:deep(.el-input__wrapper) {
+  width: 50%;
+}
+:deep(.el-input-group__append) {
+  width: 50%;
+  padding: 0;
+  border: none;
+}
+:deep(.el-input-group--append .el-input-group__append .el-select .el-select__wrapper) {
+  padding: 5px 10px;
+  background-color: #fff;
+}
+.delayedTitle {
+  color: var(--color-neutral-2);
+  font-size: 12px;
+}
+.delayedIcon {
+  margin: 0 8px;
+}
+.oceanCheckbox {
+  margin-bottom: 8px;
+}
+</style>

+ 341 - 0
src/components/AddRules/src/components/ETDShipments.vue

@@ -0,0 +1,341 @@
+<script lang="ts" setup>
+import { ref, computed, watch } from 'vue'
+
+const props = defineProps({
+  Title: String,
+  ETDData: Object
+})
+const ETD_data = ref(props.ETDData)
+watch(
+  () => props.ETDData,
+  (current) => {
+    ETD_data.value = current
+    ETDInit()
+  }
+)
+
+const ETDInit = () => {
+  OceanCheckedList.value = []
+  if (ETD_data.value?.ETDradio == 't') {
+    ETDRadio.value = '1'
+    OceanCheckedList.value.push('ETD')
+  } else {
+    if (ETD_data.value?.etd_old_sub_new != '' && ETD_data.value?.etd_old_sub_new != undefined) {
+      ETDRadio.value = '2'
+      isETD.value = true
+      ETDTime.value = ETD_data.value?.etd_old_sub_new
+      ETDSelect.value = ETD_data.value?.etd_old_sub_new_unit
+      OceanCheckedList.value.push('ETD')
+    }
+  }
+  if (ETD_data.value?.ETAradio == 't') {
+    ETARadio.value = '1'
+    OceanCheckedList.value.push('ETA')
+  } else {
+    if (ETD_data.value?.eta_old_sub_new != '' && ETD_data.value?.eta_old_sub_new != undefined) {
+      ETARadio.value = '2'
+      isETA.value = true
+      ETATime.value = ETD_data.value?.eta_old_sub_new
+      ETASelect.value = ETD_data.value?.eta_old_sub_new_unit
+      OceanCheckedList.value.push('ETA')
+    }
+  }
+  CheckChange(OceanCheckedList.value)
+  changeETDRadio(ETDRadio.value)
+  changeETARadio(ETARadio.value)
+  changedeparture(OceanCheckedList.value)
+}
+const isETD = ref(false)
+const isETA = ref(false)
+const OceanActive = ref(['ETDShipments'])
+const OceanCheckedList = ref()
+const ETDTime = ref('')
+const ETDSelect = ref('')
+const ETATime = ref('')
+const ETASelect = ref('')
+const ETDRadio = ref()
+const ETARadio = ref()
+const emit = defineEmits(['ChangeCheckRules', 'closeETD'])
+const ETDETAList = ref({
+  ETD: '',
+  ETA: ''
+})
+let ETDstr: any = ''
+let ETAstr: any = ''
+const CheckChange = (val: any) => {
+  if (val.includes('ETD')) {
+    isETD.value = true
+    if (ETDSelect.value != '') {
+      ETDETAList.value.ETD = ETDstr
+    }
+    if (val.includes('ETA')) {
+      isETA.value = true
+      if (ETASelect.value != '') {
+        ETDETAList.value.ETA = ETAstr
+      }
+    } else {
+      isETA.value = false
+      ETDETAList.value.ETA = ''
+    }
+  } else {
+    isETD.value = false
+    ETDETAList.value.ETD = ''
+    if (val.includes('ETA')) {
+      isETA.value = true
+      if (ETASelect.value != '') {
+        ETDETAList.value.ETA = ETAstr
+      }
+    } else {
+      isETA.value = false
+      ETDETAList.value.ETA = ''
+    }
+  }
+  emit('ChangeCheckRules', ETDETAList.value)
+}
+const changeETDRadio = (val: any) => {
+  if (val == 1) {
+    ETDstr = 'ETD: Notify for all changes'
+    ETDETAList.value.ETD = ETDstr
+  } else if (val == 2) {
+    ETDstr = 'ETD: Notify for all changes ≥ ' + clampedValue.value + ' ' + ETDSelect.value
+    if (ETDSelect.value != '') {
+      ETDETAList.value.ETD = ETDstr
+    } else {
+      ETDETAList.value.ETD = ''
+    }
+  }
+  emit('ChangeCheckRules', ETDETAList.value)
+}
+const changeETARadio = (val: any) => {
+  if (val == 1) {
+    ETAstr = 'ETA: Notify for all changes'
+    ETDETAList.value.ETA = ETAstr
+  } else if (val == 2) {
+    ETAstr = 'ETA: Notify for all changes ≥ ' + clampedETAValue.value + ' ' + ETASelect.value
+    if (ETASelect.value != '') {
+      ETDETAList.value.ETA = ETAstr
+    } else {
+      ETDETAList.value.ETA = ''
+    }
+  }
+  emit('ChangeCheckRules', ETDETAList.value)
+}
+const changedeparture = (val: any) => {
+  if (val == 'ETD') {
+    ETDstr = 'ETD: Notify for all changes ≥ ' + clampedValue.value + ' ' + ETDSelect.value
+    if (ETDSelect.value != '') {
+      ETDETAList.value.ETD = ETDstr
+    }
+    if (val == 'ETA') {
+      ETAstr = 'ETA: Notify for all changes ≥ ' + clampedETAValue.value + ' ' + ETASelect.value
+      if (ETASelect.value != '') {
+        ETDETAList.value.ETA = ETAstr
+      }
+    }
+  } else {
+    if (val == 'ETA') {
+      ETAstr = 'ETA: Notify for all changes ≥ ' + clampedETAValue.value + ' ' + ETASelect.value
+      if (ETASelect.value != '') {
+        ETDETAList.value.ETA = ETAstr
+      }
+    }
+  }
+  emit('ChangeCheckRules', ETDETAList.value)
+}
+const closeETD = (val: any) => {
+  if (val.includes('ETD')) {
+    OceanCheckedList.value.splice(OceanCheckedList.value.indexOf('ETD'), 1)
+    ETDETAList.value.ETD = ''
+    isETD.value = false
+    ETDTime.value = ''
+    ETDSelect.value = ''
+    ETDRadio.value = 0
+  }
+  if (val.includes('ETA')) {
+    OceanCheckedList.value.splice(OceanCheckedList.value.indexOf('ETA'), 1)
+    ETDETAList.value.ETA = ''
+    isETA.value = false
+    ETATime.value = ''
+    ETASelect.value = ''
+    ETARadio.value = 0
+  }
+  emit('ChangeCheckRules', ETDETAList.value)
+}
+
+// 清除所有数据
+const ClearData = () => {
+  OceanCheckedList.value = []
+  ETDETAList.value.ETD = ''
+  isETD.value = false
+  ETDETAList.value.ETA = ''
+  isETA.value = false
+  ETDRadio.value = 0
+  ETARadio.value = 0
+  ETDTime.value = ''
+  ETDSelect.value = ''
+  ETATime.value = ''
+  ETASelect.value = ''
+}
+
+defineExpose({
+  closeETD,
+  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 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之间,但不更新原输入值仅用于显示
+  }
+})
+</script>
+<template>
+  <div class="Ocean_collapse">
+    <el-collapse v-model="OceanActive">
+      <el-collapse-item name="ETDShipments">
+        <template #title>
+          <div class="Rules_Title OceanTitle">{{ props.Title }}</div>
+        </template>
+        <div class="oceanCheckbox">
+          <el-checkbox-group @change="CheckChange" v-model="OceanCheckedList">
+            <el-checkbox class="delayedType" value="ETD">
+              <div>ETD</div>
+              <div v-if="isETD" style="margin-top: 16px">
+                <el-radio-group v-model="ETDRadio" @change="changeETDRadio">
+                  <el-radio value="1">Notify for all changes</el-radio>
+                  <el-radio value="2"
+                    >Notify only when time difference
+                    <span class="delayedIcon">></span>
+                    <el-input
+                      v-model="ETDTime"
+                      :value="clampedValue"
+                      class="input-with-select"
+                      @input="changedeparture('ETD')"
+                    >
+                      <template #append>
+                        <el-select
+                          v-model="ETDSelect"
+                          placeholder="Select"
+                          @change="changedeparture('ETD')"
+                        >
+                          <el-option label="Day(s)" value="Day(s)" />
+                          <el-option label="Hour(s)" value="Hour(s)" />
+                        </el-select>
+                      </template> </el-input
+                  ></el-radio>
+                </el-radio-group>
+              </div>
+            </el-checkbox>
+            <el-checkbox class="delayedType" value="ETA">
+              <div>ETA</div>
+              <div v-if="isETA" style="margin-top: 16px">
+                <el-radio-group v-model="ETARadio" @change="changeETARadio">
+                  <el-radio value="1">Notify for all changes</el-radio>
+                  <el-radio value="2"
+                    >Notify only when time difference
+                    <span class="delayedIcon">></span>
+                    <el-input
+                      v-model="ETATime"
+                      :value="clampedETAValue"
+                      class="input-with-select"
+                      @input="changedeparture('ETA')"
+                    >
+                      <template #append>
+                        <el-select
+                          v-model="ETASelect"
+                          placeholder="Select"
+                          @change="changedeparture('ETA')"
+                        >
+                          <el-option label="Day(s)" value="Day(s)" />
+                          <el-option label="Hour(s)" value="Hour(s)" />
+                        </el-select>
+                      </template> </el-input
+                  ></el-radio>
+                </el-radio-group>
+              </div>
+            </el-checkbox>
+          </el-checkbox-group>
+        </div>
+      </el-collapse-item>
+    </el-collapse>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+:deep(.el-checkbox-group) {
+  border: 1px solid var(--color-select-border);
+  border-radius: 5px;
+}
+:deep(.el-checkbox) {
+  width: 100%;
+  background-color: var(--color-drawer-body-bg);
+  border-bottom: 1px solid var(--color-select-border);
+  padding: 8px;
+}
+:deep(.el-checkbox:first-child) {
+  border-radius: 6px 6px 0 0;
+}
+:deep(.el-checkbox:last-child) {
+  border-radius: 0 0 6px 6px;
+  border-bottom: none;
+}
+:deep(.el-radio) {
+  border: none !important;
+}
+.delayedType {
+  align-items: start;
+  height: fit-content;
+  margin-right: 5px;
+  border-radius: 6px;
+  padding: 8px;
+}
+.input-with-select {
+  width: 180px;
+}
+:deep(.el-input__wrapper) {
+  width: 50%;
+}
+:deep(.el-input-group__append) {
+  width: 50%;
+  padding: 0;
+  border: none;
+}
+:deep(.el-input-group--append .el-input-group__append .el-select .el-select__wrapper) {
+  padding: 5px 10px;
+  background-color: #fff;
+}
+.delayedIcon {
+  margin: 0 8px;
+}
+.oceanCheckbox {
+  margin-bottom: 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: center;
+}
+:deep(.el-radio__input.is-checked + .el-radio__label) {
+  color: var(--color-neutral-1);
+}
+</style>

+ 398 - 0
src/components/AddRules/src/components/NotiFrequency.vue

@@ -0,0 +1,398 @@
+<script setup lang="ts">
+import { ref, watch } from 'vue'
+import moment from 'moment-timezone'
+import { defaultTimeZone } from '@/utils/timezone'
+
+const props = defineProps({
+  FrequencyData: Object
+})
+
+const frequency_data = ref(props.FrequencyData)
+const radio = ref(0)
+const FrequencyList = ref()
+const DailyTime = ref('')
+const WeeklyTime = ref('')
+const WeeklyDay = ref('')
+const WeekDay = ref([
+  {
+    label: 'Monday',
+    value: 'Monday'
+  },
+  {
+    label: 'Tuesday',
+    value: 'Tuesday'
+  },
+  {
+    label: 'Wednesday',
+    value: 'Wednesday'
+  },
+  {
+    label: 'Thursday',
+    value: 'Thursday'
+  },
+  {
+    label: 'Friday',
+    value: 'Friday'
+  },
+  {
+    label: 'Saturday',
+    value: 'Saturday'
+  },
+  {
+    label: 'Sunday',
+    value: 'Sunday'
+  }
+])
+const TimeZone = ref([
+  {
+    label: 'UTC-08',
+    value: 'UTC-08'
+  },
+  {
+    label: 'UTC-07',
+    value: 'UTC-07'
+  },
+  {
+    label: 'UTC-06',
+    value: 'UTC-06'
+  },
+  {
+    label: 'UTC-05',
+    value: 'UTC-05'
+  },
+  {
+    label: 'UTC-04',
+    value: 'UTC-04'
+  },
+  {
+    label: 'UTC-03',
+    value: 'UTC-03'
+  },
+  {
+    label: 'UTC-02',
+    value: 'UTC-02'
+  },
+  {
+    label: 'UTC-01',
+    value: 'UTC-01'
+  },
+  {
+    label: 'UTC-00',
+    value: 'UTC-00'
+  },
+  {
+    label: 'UTC+01',
+    value: 'UTC+01'
+  },
+  {
+    label: 'UTC+02',
+    value: 'UTC+02'
+  },
+  {
+    label: 'UTC+03',
+    value: 'UTC+03'
+  },
+  {
+    label: 'UTC+04',
+    value: 'UTC+04'
+  },
+  {
+    label: 'UTC+05',
+    value: 'UTC+05'
+  },
+  {
+    label: 'UTC+05:30',
+    value: 'UTC+05:30'
+  },
+  {
+    label: 'UTC+06',
+    value: 'UTC+06'
+  },
+  {
+    label: 'UTC+07',
+    value: 'UTC+07'
+  },
+  {
+    label: 'UTC+08',
+    value: 'UTC+08'
+  },
+  {
+    label: 'UTC+09',
+    value: 'UTC+09'
+  },
+  {
+    label: 'UTC+10',
+    value: 'UTC+10'
+  },
+  {
+    label: 'UTC+11',
+    value: 'UTC+11'
+  },
+  {
+    label: 'UTC+12',
+    value: 'UTC+12'
+  }
+])
+const TimeZoneDailySelect = ref()
+const TimeZoneWeeklySelect = ref()
+TimeZoneDailySelect.value = 'UTC' + moment().tz(moment.tz.guess()).format('Z').slice(0, 3)
+TimeZoneWeeklySelect.value = 'UTC' + moment().tz(moment.tz.guess()).format('Z').slice(0, 3)
+const isDaily = ref(false)
+const isWeekly = ref(false)
+let savesubscribeobj: any = {}
+
+watch(
+  () => props.FrequencyData,
+  (current) => {
+    frequency_data.value = current
+    FrequencyDataInit()
+  }
+)
+
+const emits = defineEmits(['ChangeFrequencyAdd'])
+
+// 初始设置Frequency
+const FrequencyDataInit = () => {
+  if (frequency_data.value?.frequency_type == 'Instant') {
+    radio.value = 1
+    ChangeFrequency(1)
+  } else if (frequency_data.value?.frequency_type == 'Daily') {
+    radio.value = 2
+    DailyTime.value = frequency_data.value?.daily_time
+    TimeZoneDailySelect.value = frequency_data.value?.daily_time_zone
+    ChangeFrequency(2)
+  } else if (frequency_data.value?.frequency_type == 'Weekly') {
+    radio.value = 3
+    WeeklyDay.value = frequency_data.value?.weekly_week
+    WeeklyTime.value = frequency_data.value?.weekly_time
+    TimeZoneWeeklySelect.value = frequency_data.value?.weekly_time_zone
+    ChangeFrequency(3)
+  } else {
+    radio.value = 0
+    ChangeFrequency(0)
+  }
+}
+
+// 更改Frequency时间
+const ChangeFrequency = (val: any) => {
+  FrequencyList.value = []
+  let str: any = ''
+  if (val == 1) {
+    isDaily.value = false
+    isWeekly.value = false
+    str = 'Instant notification for each update'
+    FrequencyList.value.push(str)
+    savesubscribeobj.frequency_type = 'Instant'
+    savesubscribeobj.frequency_display = str
+  } else if (val == 2) {
+    isDaily.value = true
+    isWeekly.value = false
+    str = 'Daily, ' + DailyTime.value + ', ' + TimeZoneDailySelect.value
+    if (DailyTime.value != '' && TimeZoneDailySelect.value !== '') {
+      FrequencyList.value.push(str)
+    }
+    savesubscribeobj.frequency_type = 'Daily'
+    savesubscribeobj.daily_time = DailyTime.value
+    savesubscribeobj.daily_time_zone = TimeZoneDailySelect.value
+    savesubscribeobj.frequency_display = str
+  } else if (val == 3) {
+    isDaily.value = false
+    isWeekly.value = true
+    str = 'Weekly, ' + WeeklyDay.value + ', ' + WeeklyTime.value + ', ' + TimeZoneWeeklySelect.value
+    if (WeeklyDay.value != '' && WeeklyTime.value != '' && TimeZoneWeeklySelect.value !== '') {
+      FrequencyList.value.push(str)
+    }
+    savesubscribeobj.frequency_type = 'Weekly'
+    if (WeeklyDay.value == 'Monday') {
+      savesubscribeobj.weekly_week = 1
+    } else if (WeeklyDay.value == 'Tuesday') {
+      savesubscribeobj.weekly_week = 2
+    } else if (WeeklyDay.value == 'Wednesday') {
+      savesubscribeobj.weekly_week = 3
+    } else if (WeeklyDay.value == 'Thursday') {
+      savesubscribeobj.weekly_week = 4
+    } else if (WeeklyDay.value == 'Friday') {
+      savesubscribeobj.weekly_week = 5
+    } else if (WeeklyDay.value == 'Saturday') {
+      savesubscribeobj.weekly_week = 6
+    } else if (WeeklyDay.value == 'Sunday') {
+      savesubscribeobj.weekly_week = 0
+    }
+    savesubscribeobj.weekly_time = WeeklyTime.value
+    savesubscribeobj.weekly_time_zone = TimeZoneWeeklySelect.value
+    savesubscribeobj.frequency_display = str
+  } else {
+    isDaily.value = false
+    isWeekly.value = false
+    DailyTime.value = ''
+    WeeklyTime.value = ''
+    WeeklyDay.value = ''
+    delete savesubscribeobj.frequency_type
+    delete savesubscribeobj.daily_time
+    delete savesubscribeobj.daily_time_zone
+    delete savesubscribeobj.weekly_week
+    delete savesubscribeobj.weekly_time
+    delete savesubscribeobj.weekly_time_zone
+  }
+  emits('ChangeFrequencyAdd', FrequencyList.value, savesubscribeobj)
+}
+const changeTime = (val: any) => {
+  if (val == 'Daily') {
+    ChangeFrequency(2)
+  } else {
+    ChangeFrequency(3)
+  }
+}
+
+// 删除 Frequency tag
+const handleCloseRadioFrequency = () => {
+  radio.value = 0
+  ChangeFrequency(0)
+}
+
+const user_type = localStorage.getItem('user_type')
+
+defineExpose({
+  handleCloseRadioFrequency,
+  FrequencyDataInit
+})
+</script>
+<template>
+  <div style="margin-top: 11px">
+    <el-radio-group v-model="radio" @change="ChangeFrequency">
+      <el-radio :value="1" v-if="user_type != null && user_type != 'customer'"
+        >Instant notification for each update</el-radio
+      >
+      <el-radio :value="2">
+        <div>Daily Summary</div>
+        <div class="Daily" v-if="isDaily">
+          <div class="Daily_left" style="margin-right: 8px">
+            Select Time
+            <div>
+              <el-time-select
+                v-model="DailyTime"
+                start="00:00"
+                step="00:30"
+                end="23:30"
+                prefix-icon=""
+                @change="changeTime('Daily')"
+                placeholder="Select Time"
+              ></el-time-select>
+            </div>
+          </div>
+          <div class="Daily_left">
+            Select Time Zone
+            <div>
+              <el-select
+                v-model="TimeZoneDailySelect"
+                placeholder="Select Time Zone"
+                @change="changeTime('Daily')"
+              >
+                <el-option
+                  v-for="item in TimeZone"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                />
+              </el-select>
+            </div>
+          </div>
+        </div>
+      </el-radio>
+      <el-radio :value="3">
+        <div>Weekly Summary</div>
+        <div class="Daily" v-if="isWeekly">
+          <div class="Weekly_left">
+            Select Day
+            <div>
+              <el-select
+                v-model="WeeklyDay"
+                @change="changeTime('Weekly')"
+                placeholder="Select Day"
+              >
+                <el-option
+                  v-for="item in WeekDay"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                />
+              </el-select>
+            </div>
+          </div>
+          <div class="Weekly_left" style="margin: 0 8px">
+            Select Time
+            <div>
+              <el-time-select
+                v-model="WeeklyTime"
+                @change="changeTime('Weekly')"
+                start="00:00"
+                step="00:30"
+                end="23:30"
+                prefix-icon=""
+                placeholder="Select time"
+              ></el-time-select>
+            </div>
+          </div>
+          <div class="Weekly_left">
+            Select Time Zone
+            <div>
+              <el-select
+                v-model="TimeZoneWeeklySelect"
+                placeholder="Select Time Zone"
+                @change="changeTime('Weekly')"
+              >
+                <el-option
+                  v-for="item in TimeZone"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                />
+              </el-select>
+            </div>
+          </div>
+        </div>
+      </el-radio>
+    </el-radio-group>
+  </div>
+</template>
+<style lang="scss" scoped>
+: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;
+}
+:deep(.el-radio__input.is-checked + .el-radio__label) {
+  color: var(--color-neutral-1);
+}
+.Daily {
+  margin: 0 0 9px 0;
+  display: flex;
+}
+.Daily_left {
+  color: var(--color-neutral-2);
+  width: 50%;
+}
+.Weekly_left {
+  color: var(--color-neutral-2);
+  width: 33%;
+}
+:deep(.el-select__icon.el-icon svg) {
+  width: 1em !important;
+}
+:deep(.el-select__wrapper.is-filterable) {
+  padding-left: 7px;
+}
+:deep(.el-radio__inner) {
+  margin-top: 7px;
+}
+</style>

+ 92 - 0
src/components/AddRules/src/components/NotiMethods.vue

@@ -0,0 +1,92 @@
+<script setup lang="ts">
+import { ref, watch } from 'vue'
+
+const checkMethodList = ref()
+checkMethodList.value = []
+let savesubscribeobj: any = {}
+const props = defineProps({
+  MethodsData: Object
+})
+
+const methods_data = ref(props.MethodsData)
+watch(
+  () => props.MethodsData,
+  (current) => {
+    methods_data.value = current
+    MethodsInit()
+  }
+)
+const emits = defineEmits(['ChangeMethodsAdd'])
+// 初始设置Methods
+const MethodsInit = () => {
+  if (methods_data.value?.method_display != undefined) {
+    if (methods_data.value?.method_display.indexOf('Email') != -1) {
+      checkMethodList.value.push('By Email')
+    }
+  }
+  if (methods_data.value?.method_display != undefined) {
+    if (methods_data.value?.method_display.indexOf('System Message') != -1) {
+      checkMethodList.value.push('By System Message')
+    }
+  }
+  changeMethod(checkMethodList.value)
+}
+
+// 选中Method
+const changeMethod = (val: any) => {
+  if (val.indexOf('By Email') != -1) {
+    savesubscribeobj.method_by_email = true
+  } else {
+    savesubscribeobj.method_by_email = false
+  }
+  if (val.indexOf('By System Message ') != -1) {
+    savesubscribeobj.method_by_message = true
+  } else {
+    savesubscribeobj.method_by_message = false
+  }
+  if (val.length != 0) {
+    savesubscribeobj.method_display = val.map((e: any) => e.replace('By ', '')).join(',')
+  } else {
+    savesubscribeobj.method_display = ''
+  }
+  emits('ChangeMethodsAdd', checkMethodList.value, savesubscribeobj)
+}
+const user_type = localStorage.getItem('user_type')
+</script>
+<template>
+  <div style="margin-top: 11px">
+    <div class="Method">
+      <el-checkbox-group v-model="checkMethodList" @change="changeMethod">
+        <el-checkbox
+          class="methodcheckbox"
+          value="By Email"
+          v-if="user_type != null && user_type != 'customer'"
+        >
+          <div>By Email</div>
+          <div class="methos_image"><img src="../images/illustration_email@2x.png" /></div>
+        </el-checkbox>
+        <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" />
+          </div>
+        </el-checkbox>
+      </el-checkbox-group>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.methodcheckbox {
+  align-items: start;
+  height: fit-content;
+  width: 49%;
+  margin-right: 5px;
+  background-color: var(--color-dialog-header-bg);
+  border-radius: 6px;
+  padding: 11px 0 0 13px;
+}
+.methos_image {
+  margin: 9px 0;
+}
+</style>

+ 83 - 0
src/components/AddRules/src/components/RulesShipments.vue

@@ -0,0 +1,83 @@
+<script lang="ts" setup>
+import { ref, watch } from 'vue'
+
+interface OceanCheckboxItem {
+  value: string
+  label: string
+}
+interface Props {
+  CheckboxList: OceanCheckboxItem[]
+  Title: String
+  CheckedList: Array<''>
+}
+const props = defineProps<Props>()
+
+const OceanActive = ref(['OceanShipments'])
+const CheckedList = ref(props.CheckedList)
+const CheckboxList = ref(props.CheckboxList)
+watch(
+  () => props.CheckboxList,
+  (current) => {
+    CheckboxList.value = current
+  }
+)
+watch(
+  () => props.CheckedList,
+  (current) => {
+    CheckedList.value = current
+  }
+)
+
+const emit = defineEmits(['ChangeCheckRules'])
+const CheckChange = () => {
+  console.log(CheckedList.value)
+  emit('ChangeCheckRules', CheckedList.value)
+}
+</script>
+<template>
+  <div class="Ocean_collapse">
+    <el-collapse v-model="OceanActive">
+      <el-collapse-item name="OceanShipments">
+        <template #title>
+          <div class="Rules_Title OceanTitle">{{ props.Title }}</div>
+        </template>
+        <div class="oceanCheckbox">
+          <el-checkbox-group @change="CheckChange" v-model="CheckedList">
+            <el-checkbox
+              v-for="item in CheckboxList"
+              :key="item.label"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-checkbox-group>
+        </div>
+      </el-collapse-item>
+    </el-collapse>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+:deep(.el-checkbox-group) {
+  border: 1px solid var(--color-select-border);
+  border-radius: 5px;
+}
+:deep(.el-checkbox) {
+  width: 100%;
+  background-color: var(--color-drawer-body-bg);
+  border-bottom: 1px solid var(--color-select-border);
+  padding: 8px;
+}
+:deep(.el-checkbox:first-child) {
+  border-radius: 6px 6px 0 0;
+}
+:deep(.el-checkbox:last-child) {
+  border-radius: 0 0 6px 6px;
+  border-bottom: none;
+}
+.oceanCheckbox {
+  max-height: 321px;
+  overflow-x: hidden;
+  margin-bottom: 8px;
+  overflow-y: scroll;
+}
+</style>

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

@@ -0,0 +1,196 @@
+<script lang="ts" setup>
+import { ref, computed } from 'vue'
+
+const OceanActive = ref(['TransportMode', 'Time'])
+const TransportCheckedList = ref([])
+interface OceanItem {
+  label: string
+  value: string
+}
+const TransportList = ref<OceanItem[]>([])
+TransportList.value = [
+  {
+    label: 'Ocean',
+    value: 'Ocean'
+  },
+  {
+    label: 'Air',
+    value: 'Air'
+  },
+  {
+    label: 'Road',
+    value: 'Road'
+  }
+]
+
+const TimeChecked = 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 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之间,但不更新原输入值仅用于显示
+  }
+})
+
+let Transportstr: any = ''
+let Timestr: any = ''
+const emit = defineEmits(['ChangeCheckRules', 'ChangeCheckTimeRules'])
+const CheckChange = (val: any) => {
+  if (val != '') {
+    Transportstr = 'Transport Mode: ' + val
+  } else {
+    Transportstr = ''
+  }
+  emit('ChangeCheckRules', Transportstr)
+}
+
+// 输入ETD、ETA
+const changeTime = (val: any) => {
+  if (val == 1) {
+    Timestr = 'ETD within ' + clampedETDValue.value + ' Day(s)'
+  } else if (val == 2) {
+    Timestr = 'ETA within ' + clampedETAValue.value + ' Day(s)'
+  } else {
+    Timestr = ''
+  }
+  emit('ChangeCheckTimeRules', Timestr)
+}
+
+const handleCloseCreateRule = (val: any) => {
+  if (val.indexOf('ETD') != -1 || val.indexOf('ETA') != -1) {
+    TimeChecked.value = 0
+    ETDTime.value = ''
+    ETATime.value = ''
+  } else if (val.indexOf('Transport') != -1) {
+    TransportCheckedList.value = []
+  }
+}
+
+defineExpose({
+  handleCloseCreateRule
+})
+</script>
+<template>
+  <div class="Ocean_collapse">
+    <el-collapse v-model="OceanActive">
+      <el-collapse-item name="TransportMode">
+        <template #title>
+          <div class="Rules_Title OceanTitle">Transport Mode</div>
+        </template>
+        <div class="oceanCheckbox">
+          <el-checkbox-group @change="CheckChange" v-model="TransportCheckedList">
+            <el-checkbox
+              v-for="item in TransportList"
+              :key="item.label"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-checkbox-group>
+        </div>
+      </el-collapse-item>
+      <el-collapse-item name="Time">
+        <template #title>
+          <div class="Rules_Title OceanTitle">Time</div>
+        </template>
+        <div class="oceanCheckbox">
+          <el-radio-group v-model="TimeChecked" @change="changeTime">
+            <el-radio value="1">
+              <div class="flex">
+                ETD within
+                <el-input
+                  @input="changeTime('1')"
+                  v-model="ETDTime"
+                  :value="clampedETDValue"
+                  class="input-with-select"
+                >
+                </el-input>
+                <div class="Days">Day(s)</div>
+              </div>
+            </el-radio>
+            <el-radio value="2">
+              <div class="flex">
+                ETA within
+                <el-input
+                  @input="changeTime('2')"
+                  v-model="ETATime"
+                  :value="clampedETAValue"
+                  class="input-with-select"
+                >
+                </el-input>
+                <div class="Days">Day(s)</div>
+              </div>
+            </el-radio>
+          </el-radio-group>
+        </div>
+      </el-collapse-item>
+    </el-collapse>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+:deep(.el-checkbox-group) {
+  border: 1px solid var(--color-select-border);
+  border-radius: 5px;
+}
+:deep(.el-checkbox) {
+  width: 100%;
+  background-color: var(--color-drawer-body-bg);
+  border-bottom: 1px solid var(--color-select-border);
+  padding: 8px;
+}
+:deep(.el-checkbox:first-child) {
+  border-radius: 6px 6px 0 0;
+}
+:deep(.el-checkbox:last-child) {
+  border-radius: 0 0 6px 6px;
+  border-bottom: none;
+}
+.oceanCheckbox {
+  max-height: 321px;
+  overflow-x: hidden;
+  margin-bottom: 8px;
+  overflow-y: scroll;
+}
+
+.input-with-select {
+  width: 96px;
+  border-radius: 6px 0 0 6px;
+}
+:deep(.el-input__wrapper) {
+  border-radius: 6px 0 0 6px;
+  opacity: 0.8;
+  height: 28px;
+}
+.flex {
+  display: flex;
+  align-items: center;
+}
+.Days {
+  width: 64px;
+  border: 1px solid var(--color-select-border);
+  border-radius: 0 6px 6px 0;
+  background-color: var(--color-drawer-body-bg);
+  border-left: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  opacity: 0.8;
+  height: 28px;
+}
+:deep(.el-radio) {
+  background-color: var(--color-drawer-body-bg);
+}
+</style>

二进制
src/components/AddRules/src/images/icon_collapse.png


二进制
src/components/AddRules/src/images/icon_expand.png


二进制
src/components/AddRules/src/images/illustration_email@2x.png


二进制
src/components/AddRules/src/images/illustration_system massage@2x.png


二进制
src/components/AddRules/src/images/submit_successful.png


+ 1 - 0
src/components/CreateAddRules/index.ts

@@ -0,0 +1 @@
+export { default } from './src/CreateAddRules.vue'

+ 1224 - 0
src/components/CreateAddRules/src/CreateAddRules.vue

@@ -0,0 +1,1224 @@
+<script lang="ts" setup>
+import { ref, watch, onMounted } from 'vue'
+import RulesShipments from './components/RulesShipments.vue'
+import AddedrluesTag from './components/AddedrluesTag.vue'
+import DelayedType from './components/DelayedType.vue'
+import ETDShipments from './components/ETDShipments.vue'
+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'
+interface CheckboxItem {
+  value: string
+  label: string
+}
+
+interface Props {
+  TitleType: String
+}
+const MilestoneOceanListInit = ref<CheckboxItem[]>([])
+const MilestoneOceanListChecked = ref()
+const MilestoneAirListInit = ref<CheckboxItem[]>([])
+const MilestoneAirListChecked = ref()
+const ContainerOceanListInit = ref<CheckboxItem[]>([])
+const ContainerOceanListChecked = ref()
+const props = defineProps<Props>()
+let savesubscribeobj: any = {}
+const RulesActive = ref([
+  'ShipmentRange',
+  'SelectMilestone',
+  'NotificationFrequency',
+  'NotificationMethod'
+])
+const OceanCheckList = ref()
+const AirCheckList = ref()
+const ContainerOceanList = ref()
+const IsFirstActive = ref(true)
+const IsTwoActive = ref(true)
+const IsThreeActive = ref(true)
+const IsFourActive = ref(true)
+const UnableSaveVisible = ref(false)
+const SaveedVisible = ref(false)
+const DelayedDeparturedList = ref()
+const DelayedAirdList = ref()
+const ETDOceanList = ref()
+const ETDAirList = ref()
+const FrequencyDataMil = ref()
+const FrequencyDataCon = ref()
+const FrequencyDataDep = ref()
+const FrequencyDataETD = ref()
+const MethodsDataMil = ref()
+const MethodsDataCon = ref()
+const MethodsDataDep = ref()
+const MethodsDataETD = ref()
+const DelayedDataInit = ref()
+const DelayedDataInitAir = ref()
+const OceanETDInit = ref()
+const AirETDInit = ref()
+const ShipmentRangeMil = ref()
+
+const MonitoringList = ref()
+const getInitMonitoring = () => {
+  $api
+    .MonitoringInit({})
+    .then((res: any) => {
+      if (res.code === 200) {
+        MonitoringList.value = res.data
+        MilestoneOceanListInit.value = res.data.OceanCheckBoxList
+        MilestoneAirListInit.value = res.data.AirCheckBoxList
+        ContainerOceanListInit.value = res.data.CtnrCheckBoxList
+      }
+    })
+    .finally(() => {})
+}
+onMounted(() => {
+  getInitMonitoring()
+  Initdata()
+})
+// 初始赋值
+const Initdata = () => {
+  if (sessionStorage.getItem('editTableid') != null) {
+    const editTableid = sessionStorage.getItem('editTableid')
+    const editTablerules_type = sessionStorage.getItem('editTablerules_type')
+    $api
+      .EditMonitoringTable({
+        id: editTableid,
+        rules_type: editTablerules_type
+      })
+      .then((res: any) => {
+        if (res.code === 200) {
+          if (editTablerules_type == 'Milestone_Update') {
+            FrequencyDataMil.value = res.data.Milestone_Update
+            MethodsDataMil.value = res.data.Milestone_Update
+            ShipmentRangeMil.value = res.data.Milestone_Update
+            MilestoneOceanListInit.value = res.data.Milestone_Update.OceanCheckBoxList
+            MilestoneOceanListChecked.value = res.data.Milestone_Update.OceanCheckedList
+            OceanCheckList.value = res.data.Milestone_Update.OceanCheckedList
+            MilestoneAirListInit.value = res.data.Milestone_Update.AirCheckBoxList
+            MilestoneAirListChecked.value = res.data.Milestone_Update.AirCheckedList
+            AirCheckList.value = res.data.Milestone_Update.AirCheckedList
+            createListMilestone.value = res.data.Milestone_Update.shipment_details
+          }
+        }
+      })
+    setTimeout(() => {
+      sessionStorage.removeItem('editTableid')
+      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) => {
+  OceanCheckList.value = val
+}
+const ChangeContainerRules = (val: any) => {
+  ContainerOceanList.value = val
+}
+// delayed赋值
+const ChangeDeayedRules = (val: any) => {
+  DelayedDeparturedList.value = []
+  if (val.Departure != '') {
+    DelayedDeparturedList.value.push(val.Departure)
+    savesubscribeobj.ocean_atd_sub_etd = val.Departure.split(' ')[3]
+    savesubscribeobj.ocean_atd_sub_etd_unit = val.Departure.split(' ')[4]
+  } else {
+    delete savesubscribeobj.ocean_atd_sub_etd
+    delete savesubscribeobj.ocean_atd_sub_etd_unit
+  }
+  if (val.Arrival != '') {
+    DelayedDeparturedList.value.push(val.Arrival)
+    savesubscribeobj.ocean_ata_sub_eta = val.Arrival.split(' ')[4]
+    savesubscribeobj.ocean_ata_sub_eta_unit = val.Arrival.split(' ')[5]
+  } else {
+    delete savesubscribeobj.ocean_ata_sub_eta
+    delete savesubscribeobj.ocean_ata_sub_eta_unit
+  }
+}
+const ChangeAirRules = (val: any) => {
+  DelayedAirdList.value = []
+  if (val.Departure != '') {
+    DelayedAirdList.value.push(val.Departure)
+    savesubscribeobj.air_atd_sub_etd = val.Departure.split(' ')[3]
+    savesubscribeobj.air_atd_sub_etd_unit = val.Departure.split(' ')[4]
+  } else {
+    delete savesubscribeobj.air_atd_sub_etd
+    delete savesubscribeobj.air_atd_sub_etd_unit
+  }
+  if (val.Arrival != '') {
+    DelayedAirdList.value.push(val.Arrival)
+    savesubscribeobj.air_ata_sub_eta = val.Arrival.split(' ')[4]
+    savesubscribeobj.air_ata_sub_eta_unit = val.Arrival.split(' ')[5]
+  } else {
+    delete savesubscribeobj.air_ata_sub_eta
+    delete savesubscribeobj.air_ata_sub_eta_unit
+  }
+}
+const ChangeCheckAirRules = (val: any) => {
+  AirCheckList.value = val
+}
+
+//选择create new rules
+const createListMilestone = ref()
+const createListContainer = ref()
+const createListDeparture = ref()
+const createListETDChange = ref()
+
+let createObj: any = {
+  Transportstr: '',
+  Timestr: ''
+}
+const changecheckCreateRulesMilestone = (val: any, value: any) => {
+  createListMilestone.value = []
+  createObj.Transportstr = val
+  if (createObj.Transportstr != '') {
+    createListMilestone.value.push(createObj.Transportstr)
+  }
+  if (createObj.Timestr != '') {
+    createListMilestone.value.push(createObj.Timestr)
+  }
+  savesubscribeobj.shipment_transport_mode = value
+  savesubscribeobj.shipment_details = createListMilestone.value.join(';\n')
+}
+const ChangeCheckTimeRulesMilestone = (val: any, time: any) => {
+  createListMilestone.value = []
+  createObj.Timestr = val
+  if (val.includes('ETD')) {
+    savesubscribeobj.shipment_etd_limit = time
+    savesubscribeobj.shipment_eta_limit = ''
+  }
+  if (val.includes('ETA')) {
+    savesubscribeobj.shipment_eta_limit = time
+    savesubscribeobj.shipment_etd_limit = ''
+  }
+  if (createObj.Transportstr != '') {
+    createListMilestone.value.push(createObj.Transportstr)
+  }
+  if (createObj.Timestr != '') {
+    createListMilestone.value.push(createObj.Timestr)
+  }
+  savesubscribeobj.shipment_details = createListMilestone.value.join(';\n')
+}
+const changecheckCreateRulesContainer = (val: any, value: any) => {
+  createListContainer.value = []
+  createObj.Transportstr = val
+  if (createObj.Transportstr != '') {
+    createListContainer.value.push(createObj.Transportstr)
+  }
+  if (createObj.Timestr != '') {
+    createListContainer.value.push(createObj.Timestr)
+  }
+  savesubscribeobj.shipment_transport_mode = value
+  savesubscribeobj.shipment_details = createListContainer.value.join(';\n')
+}
+const ChangeCheckTimeRulesContainer = (val: any, time: any) => {
+  createListContainer.value = []
+  createObj.Timestr = val
+  if (val.includes('ETD')) {
+    savesubscribeobj.shipment_etd_limit = time
+    savesubscribeobj.shipment_eta_limit = ''
+  }
+  if (val.includes('ETA')) {
+    savesubscribeobj.shipment_eta_limit = time
+    savesubscribeobj.shipment_etd_limit = ''
+  }
+  if (createObj.Transportstr != '') {
+    createListContainer.value.push(createObj.Transportstr)
+  }
+  if (createObj.Timestr != '') {
+    createListContainer.value.push(createObj.Timestr)
+  }
+  savesubscribeobj.shipment_details = createListContainer.value.join(';\n')
+}
+const changecheckCreateRulesDeparture = (val: any, value: any) => {
+  createListDeparture.value = []
+  createObj.Transportstr = val
+  if (createObj.Transportstr != '') {
+    createListDeparture.value.push(createObj.Transportstr)
+  }
+  if (createObj.Timestr != '') {
+    createListDeparture.value.push(createObj.Timestr)
+  }
+  savesubscribeobj.shipment_transport_mode = value
+  savesubscribeobj.shipment_details = createListDeparture.value.join(';\n')
+}
+const ChangeCheckTimeRulesDeparture = (val: any, time: any) => {
+  createListDeparture.value = []
+  createObj.Timestr = val
+  if (val.includes('ETD')) {
+    savesubscribeobj.shipment_etd_limit = time
+    savesubscribeobj.shipment_eta_limit = ''
+  }
+  if (val.includes('ETA')) {
+    savesubscribeobj.shipment_eta_limit = time
+    savesubscribeobj.shipment_etd_limit = ''
+  }
+  if (createObj.Transportstr != '') {
+    createListDeparture.value.push(createObj.Transportstr)
+  }
+  if (createObj.Timestr != '') {
+    createListDeparture.value.push(createObj.Timestr)
+  }
+  savesubscribeobj.shipment_details = createListDeparture.value.join(';\n')
+}
+const changecheckCreateRulesETDChange = (val: any, value: any) => {
+  createListETDChange.value = []
+  createObj.Transportstr = val
+  if (createObj.Transportstr != '') {
+    createListETDChange.value.push(createObj.Transportstr)
+  }
+  if (createObj.Timestr != '') {
+    createListETDChange.value.push(createObj.Timestr)
+  }
+  savesubscribeobj.shipment_transport_mode = value
+  savesubscribeobj.shipment_details = createListETDChange.value.join(';\n')
+}
+const ChangeCheckTimeRulesETDChange = (val: any, time: any) => {
+  createListETDChange.value = []
+  createObj.Timestr = val
+  if (val.includes('ETD')) {
+    savesubscribeobj.shipment_etd_limit = time
+    savesubscribeobj.shipment_eta_limit = ''
+  }
+  if (val.includes('ETA')) {
+    savesubscribeobj.shipment_eta_limit = time
+    savesubscribeobj.shipment_etd_limit = ''
+  }
+  if (createObj.Transportstr != '') {
+    createListETDChange.value.push(createObj.Transportstr)
+  }
+  if (createObj.Timestr != '') {
+    createListETDChange.value.push(createObj.Timestr)
+  }
+  savesubscribeobj.shipment_details = createListETDChange.value.join(';\n')
+}
+// 删除create tag
+const ShipmentRangeRef = ref()
+const handleCloseCreateRule = (val: any) => {
+  if (val.indexOf('ETD') != -1 || val.indexOf('ETA') != -1) {
+    createObj.Timestr = ''
+  } else if (val.indexOf('Transport') != -1) {
+    createObj.Transportstr = ''
+  }
+  ShipmentRangeRef.value.handleCloseCreateRule(val)
+}
+// 更改Frequency时间
+const MilFrequencyList = ref([])
+const ChangeMilFrequency = (val: any, type: any) => {
+  MilFrequencyList.value = val
+  savesubscribeobj = { ...savesubscribeobj, ...type }
+}
+const ConFrequencyList = ref([])
+const ChangeConFrequency = (val: any, type: any) => {
+  ConFrequencyList.value = val
+  savesubscribeobj = { ...savesubscribeobj, ...type }
+}
+const DepFrequencyList = ref([])
+const ChangeDepFrequency = (val: any, type: any) => {
+  DepFrequencyList.value = val
+  savesubscribeobj = { ...savesubscribeobj, ...type }
+}
+const ETDFrequencyList = ref([])
+const ChangeETDFrequency = (val: any, type: any) => {
+  ETDFrequencyList.value = val
+  savesubscribeobj = { ...savesubscribeobj, ...type }
+}
+
+// 删除 Frequency tag
+const NotiFrequencyDeleteMil = ref()
+const NotiFrequencyDeleteCon = ref()
+const NotiFrequencyDeleteDep = ref()
+const NotiFrequencyDeleteETD = ref()
+const handleCloseRadio = (val: any) => {
+  if (val == 'Mil') {
+    NotiFrequencyDeleteMil.value.handleCloseRadioFrequency()
+  } else if (val == 'Con') {
+    NotiFrequencyDeleteCon.value.handleCloseRadioFrequency()
+  } else if (val == 'Dep') {
+    NotiFrequencyDeleteDep.value.handleCloseRadioFrequency()
+  } else {
+    NotiFrequencyDeleteETD.value.handleCloseRadioFrequency()
+  }
+}
+
+// methods 切换
+const MilMethodsList = ref([])
+const ChangeMethodsAddMil = (val: any, type: any) => {
+  MilMethodsList.value = val
+  savesubscribeobj = { ...savesubscribeobj, ...type }
+}
+const ConMethodsList = ref([])
+const ChangeMethodsAddCon = (val: any, type: any) => {
+  ConMethodsList.value = val
+  savesubscribeobj = { ...savesubscribeobj, ...type }
+}
+const DepMethodsList = ref([])
+const ChangeMethodsAddDep = (val: any, type: any) => {
+  DepMethodsList.value = val
+  savesubscribeobj = { ...savesubscribeobj, ...type }
+}
+const ETDMethodsList = ref([])
+const ChangeMethodsAddETD = (val: any, type: any) => {
+  ETDMethodsList.value = val
+  savesubscribeobj = { ...savesubscribeobj, ...type }
+}
+const OceanDelayed = ref()
+const AirDelayed = ref()
+
+const handleCloseDelayed = (val: any) => {
+  OceanDelayed.value.closeDelayed(val)
+}
+const handleCloseAirDelayed = (val: any) => {
+  AirDelayed.value.closeDelayed(val)
+}
+// ETD Change
+const ChangeETDOceanRules = (val: any) => {
+  ETDOceanList.value = []
+  if (val.ETD != '') {
+    if (val.ETD.indexOf('≥') != -1) {
+      savesubscribeobj.ocean_etd_change = false
+      savesubscribeobj.ocean_etd_old_sub_new = val.ETD.split(' ')[6]
+      savesubscribeobj.ocean_etd_old_sub_new_unit = val.ETD.split(' ')[7]
+    } else {
+      savesubscribeobj.ocean_etd_change = true
+    }
+    ETDOceanList.value.push(val.ETD)
+  } else {
+    delete savesubscribeobj.ocean_etd_change
+    delete savesubscribeobj.ocean_etd_old_sub_new
+    delete savesubscribeobj.ocean_etd_old_sub_new_unit
+  }
+  if (val.ETA != '') {
+    if (val.ETA.indexOf('≥') != -1) {
+      savesubscribeobj.ocean_eta_change = false
+      savesubscribeobj.ocean_eta_old_sub_new = val.ETA.split(' ')[6]
+      savesubscribeobj.ocean_eta_old_sub_new_unit = val.ETA.split(' ')[7]
+    } else {
+      savesubscribeobj.ocean_eta_change = true
+    }
+    ETDOceanList.value.push(val.ETA)
+  } else {
+    delete savesubscribeobj.ocean_eta_change
+    delete savesubscribeobj.ocean_eta_old_sub_new
+    delete savesubscribeobj.ocean_eta_old_sub_new_unit
+  }
+}
+// 删除ETD tag
+const OceanETD = ref()
+const closeOceanETD = (val: any) => {
+  OceanETD.value.closeETD(val)
+}
+// ETA Change
+const ChangeETDAirRules = (val: any) => {
+  ETDAirList.value = []
+  if (val.ETD != '') {
+    if (val.ETD.indexOf('≥') != -1) {
+      savesubscribeobj.air_etd_change = false
+      savesubscribeobj.air_etd_old_sub_new = val.ETD.split(' ')[6]
+      savesubscribeobj.air_etd_old_sub_new_unit = val.ETD.split(' ')[7]
+    } else {
+      savesubscribeobj.air_etd_change = true
+    }
+    ETDAirList.value.push(val.ETD)
+  } else {
+    delete savesubscribeobj.air_etd_change
+    delete savesubscribeobj.air_etd_old_sub_new
+    delete savesubscribeobj.air_etd_old_sub_new_unit
+  }
+  if (val.ETA != '') {
+    if (val.ETA.indexOf('≥') != -1) {
+      savesubscribeobj.air_eta_change = false
+      savesubscribeobj.air_eta_old_sub_new = val.ETA.split(' ')[6]
+      savesubscribeobj.air_eta_old_sub_new_unit = val.ETA.split(' ')[7]
+    } else {
+      savesubscribeobj.air_eta_change = true
+    }
+    ETDAirList.value.push(val.ETA)
+  } else {
+    delete savesubscribeobj.air_eta_change
+    delete savesubscribeobj.air_eta_old_sub_new
+    delete savesubscribeobj.air_eta_old_sub_new_unit
+  }
+}
+
+// 删除ETA tag
+const AirETD = ref()
+const closeAirETD = (val: any) => {
+  AirETD.value.closeETD(val)
+}
+
+const emits = defineEmits(['SavedAddedRules'])
+// 不保存修改的折叠面板
+
+// 保存subscribe配置
+const missingmessage = ref('')
+// 保存成功调用接口
+const SaveSuceessful = () => {
+  $api
+    .MonitoringSave({
+      ...savesubscribeobj
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        SaveedVisible.value = true
+        setTimeout(() => {
+          SaveedVisible.value = false
+          emits('SavedAddedRules', res.data.addedRules, savesubscribeobj.rules_type)
+        }, 3000)
+      }
+    })
+}
+const Savesubscribe = () => {
+  missingmessage.value = ''
+  let str = ''
+  if (props.TitleType == 'Milestone') {
+    savesubscribeobj.rules_type = 'Milestone_Update'
+    if (
+      OceanCheckList.value == undefined ||
+      AirCheckList.value == undefined ||
+      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 (MilFrequencyList.value.length == 0) {
+        missingmessage.value += 'Notification Frequency, '
+      }
+      if (MilMethodsList.value.length == 0) {
+        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 =
+        'Ocean Milestones: ' +
+        OceanCheckList.value.join(',') +
+        ';\nAir Milestones: ' +
+        AirCheckList.value.join(',') +
+        ';'
+      savesubscribeobj.event_details = str
+      SaveSuceessful()
+    }
+  } else if (props.TitleType == 'Container') {
+    savesubscribeobj.rules_type = 'Container_Status_Update'
+    if (
+      ContainerOceanList.value == undefined ||
+      ConFrequencyList.value.length == 0 ||
+      ConMethodsList.value.length == 0
+    ) {
+      if (ContainerOceanList.value == undefined) {
+        missingmessage.value += 'Ocean Shipments, '
+      }
+      if (ConFrequencyList.value.length == 0) {
+        missingmessage.value += 'Notification Frequency, '
+      }
+      if (ConMethodsList.value.length == 0) {
+        missingmessage.value += 'Notification Method, '
+      }
+      missingmessage.value = missingmessage.value.substring(0, missingmessage.value.length - 2)
+      UnableSaveVisible.value = true
+    } else {
+      savesubscribeobj.ocean_ctnr_status = ContainerOceanList.value
+      str = 'Ocean Container: ' + ContainerOceanList.value.join(',')
+      savesubscribeobj.event_details = str
+      SaveSuceessful()
+    }
+  } else if (props.TitleType == 'Departure') {
+    savesubscribeobj.rules_type = 'Departure/Arrival_Delay'
+    if (
+      DelayedDeparturedList.value == undefined ||
+      DelayedAirdList.value == undefined ||
+      DepFrequencyList.value.length == 0 ||
+      DepMethodsList.value.length == 0
+    ) {
+      if (DelayedDeparturedList.value == undefined) {
+        missingmessage.value += 'Ocean Shipments, '
+      }
+      if (DelayedAirdList.value == undefined) {
+        missingmessage.value += 'Air Shipments, '
+      }
+      if (DepFrequencyList.value.length == 0) {
+        missingmessage.value += 'Notification Frequency, '
+      }
+      if (DepMethodsList.value.length == 0) {
+        missingmessage.value += 'Notification Method, '
+      }
+      missingmessage.value = missingmessage.value.substring(0, missingmessage.value.length - 2)
+      UnableSaveVisible.value = true
+    } else {
+      str = DelayedDeparturedList.value.join(';\n') + ';\n' + DelayedAirdList.value.join(';\n')
+      savesubscribeobj.event_details = str
+      SaveSuceessful()
+    }
+  } else {
+    savesubscribeobj.rules_type = 'ETD/ETA_Change'
+    if (
+      ETDOceanList.value == undefined ||
+      ETDOceanList.value.length == 0 ||
+      ETDAirList.value == undefined ||
+      ETDAirList.value.length == 0 ||
+      ETDFrequencyList.value.length == 0 ||
+      ETDMethodsList.value.length == 0
+    ) {
+      if (ETDOceanList.value == undefined) {
+        missingmessage.value += 'Ocean Shipments, '
+      }
+      if (ETDAirList.value == undefined) {
+        missingmessage.value += 'Air Shipments, '
+      }
+      if (ETDFrequencyList.value.length == 0) {
+        missingmessage.value += 'Notification Frequency, '
+      }
+      if (ETDMethodsList.value.length == 0) {
+        missingmessage.value += 'Notification Method, '
+      }
+      missingmessage.value = missingmessage.value.substring(0, missingmessage.value.length - 2)
+      UnableSaveVisible.value = true
+    } else {
+      str = ETDOceanList.value.join(';\n') + ';\n' + ETDAirList.value.join(';\n')
+      savesubscribeobj.event_details = str
+      SaveSuceessful()
+    }
+  }
+}
+
+// 删除表格数据后清空所有数据
+const clearData = (val: any) => {
+  if (val == 'Milestone') {
+    OceanCheckList.value = []
+    AirCheckList.value = []
+    MilFrequencyList.value = []
+    MilMethodsList.value = []
+    MilestoneOceanListChecked.value = []
+    MilestoneAirListChecked.value = []
+    createListMilestone.value = []
+  } else if (val == 'Container') {
+    ContainerOceanList.value = []
+    createListContainer.value = []
+    ConFrequencyList.value = []
+    ConMethodsList.value = []
+    ContainerOceanListChecked.value = []
+  } else if (val == 'Departure') {
+    DelayedDeparturedList.value = []
+    DelayedAirdList.value = []
+    DepFrequencyList.value = []
+    DepMethodsList.value = []
+    createListDeparture.value = []
+  } else {
+    ETDOceanList.value = []
+    ETDAirList.value = []
+    ETDFrequencyList.value = []
+    ETDMethodsList.value = []
+    createListETDChange.value = []
+  }
+}
+
+defineExpose({
+  clearData,
+  Savesubscribe,
+  Initdata
+})
+</script>
+<template>
+  <div class="Rules_flex">
+    <div class="Rules_left">
+      <div class="Rules_collapse">
+        <el-collapse v-model="RulesActive" @change="IsFourActive = !IsFourActive">
+          <el-collapse-item name="ShipmentRange">
+            <template #title>
+              <div class="Rules_Title">
+                <span class="iconfont_icon">
+                  <svg class="iconfont" aria-hidden="true">
+                    <use
+                      :xlink:href="IsFourActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
+                    ></use>
+                  </svg>
+                </span>
+                <span class="stars_red">*</span>Shipment Range
+              </div>
+            </template>
+            <div>
+              <ShipmentRange
+                ref="ShipmentRangeRef"
+                v-if="props.TitleType == 'Milestone'"
+                :ShipmentRangeData="ShipmentRangeMil"
+                @ChangeCheckRules="changecheckCreateRulesMilestone"
+                @ChangeCheckTimeRules="ChangeCheckTimeRulesMilestone"
+              ></ShipmentRange>
+              <ShipmentRange
+                ref="ShipmentRangeRef"
+                v-if="props.TitleType == 'Container'"
+                @ChangeCheckRules="changecheckCreateRulesContainer"
+                @ChangeCheckTimeRules="ChangeCheckTimeRulesContainer"
+              ></ShipmentRange>
+              <ShipmentRange
+                ref="ShipmentRangeRef"
+                v-if="props.TitleType == 'Departure'"
+                @ChangeCheckRules="changecheckCreateRulesDeparture"
+                @ChangeCheckTimeRules="ChangeCheckTimeRulesDeparture"
+              ></ShipmentRange>
+              <ShipmentRange
+                ref="ShipmentRangeRef"
+                v-if="props.TitleType == 'ETDChange'"
+                @ChangeCheckRules="changecheckCreateRulesETDChange"
+                @ChangeCheckTimeRules="ChangeCheckTimeRulesETDChange"
+              ></ShipmentRange>
+            </div>
+          </el-collapse-item>
+        </el-collapse>
+        <el-collapse
+          v-model="RulesActive"
+          @change="IsFirstActive = !IsFirstActive"
+          v-if="props.TitleType == 'Milestone'"
+        >
+          <el-collapse-item name="SelectMilestone">
+            <template #title>
+              <div class="Rules_Title">
+                <span class="iconfont_icon">
+                  <svg class="iconfont" aria-hidden="true">
+                    <use
+                      :xlink:href="IsFirstActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
+                    ></use>
+                  </svg>
+                </span>
+                <span class="stars_red">*</span>Select Milestone
+              </div>
+            </template>
+            <div>
+              <RulesShipments
+                Title="Ocean shipments"
+                @ChangeCheckRules="ChangeCheckOceanRules"
+                :CheckboxList="MilestoneOceanListInit"
+                :CheckedList="MilestoneOceanListChecked"
+              ></RulesShipments>
+            </div>
+            <div>
+              <RulesShipments
+                Title="Air shipments"
+                @ChangeCheckRules="ChangeCheckAirRules"
+                :CheckboxList="MilestoneAirListInit"
+                :CheckedList="MilestoneAirListChecked"
+              ></RulesShipments>
+            </div>
+          </el-collapse-item>
+        </el-collapse>
+        <el-collapse
+          v-model="RulesActive"
+          @change="IsFirstActive = !IsFirstActive"
+          v-if="props.TitleType == 'Container'"
+        >
+          <el-collapse-item name="SelectMilestone">
+            <template #title>
+              <div class="Rules_Title">
+                <span class="iconfont_icon">
+                  <svg class="iconfont" aria-hidden="true">
+                    <use
+                      :xlink:href="IsFirstActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
+                    ></use>
+                  </svg>
+                </span>
+                <span class="stars_red">*</span>Select Container Status
+              </div>
+            </template>
+            <div>
+              <RulesShipments
+                Title="Ocean shipments"
+                @ChangeCheckRules="ChangeContainerRules"
+                :CheckboxList="ContainerOceanListInit"
+                :CheckedList="ContainerOceanListChecked"
+              ></RulesShipments>
+            </div>
+          </el-collapse-item>
+        </el-collapse>
+        <el-collapse
+          v-model="RulesActive"
+          @change="IsFirstActive = !IsFirstActive"
+          v-if="props.TitleType == 'Departure'"
+        >
+          <el-collapse-item name="SelectMilestone">
+            <template #title>
+              <div class="Rules_Title">
+                <span class="iconfont_icon">
+                  <svg class="iconfont" aria-hidden="true">
+                    <use
+                      :xlink:href="IsFirstActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
+                    ></use>
+                  </svg>
+                </span>
+                <span class="stars_red">*</span>Select Delayed Type
+              </div>
+            </template>
+            <div>
+              <DelayedType
+                Title="Ocean shipments"
+                ref="OceanDelayed"
+                :DelayedData="DelayedDataInit"
+                @ChangeCheckRules="ChangeDeayedRules"
+              ></DelayedType>
+            </div>
+            <div>
+              <DelayedType
+                Title="Air shipments"
+                ref="AirDelayed"
+                :DelayedData="DelayedDataInitAir"
+                @ChangeCheckRules="ChangeAirRules"
+              ></DelayedType>
+            </div>
+          </el-collapse-item>
+        </el-collapse>
+        <el-collapse
+          v-model="RulesActive"
+          @change="IsFirstActive = !IsFirstActive"
+          v-if="props.TitleType == 'ETDChange'"
+        >
+          <el-collapse-item name="SelectMilestone">
+            <template #title>
+              <div class="Rules_Title">
+                <span class="iconfont_icon">
+                  <svg class="iconfont" aria-hidden="true">
+                    <use
+                      :xlink:href="IsFirstActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
+                    ></use>
+                  </svg>
+                </span>
+                <span class="stars_red">*</span>Select Delayed Type
+              </div>
+            </template>
+            <div>
+              <ETDShipments
+                Title="Ocean shipments"
+                ref="OceanETD"
+                :ETDData="OceanETDInit"
+                @ChangeCheckRules="ChangeETDOceanRules"
+              ></ETDShipments>
+            </div>
+            <div>
+              <ETDShipments
+                Title="Air shipments"
+                ref="AirETD"
+                :ETDData="AirETDInit"
+                @ChangeCheckRules="ChangeETDAirRules"
+              ></ETDShipments>
+            </div>
+          </el-collapse-item>
+        </el-collapse>
+        <el-collapse
+          style="margin-top: 17px; margin-right: 16px"
+          v-model="RulesActive"
+          @change="IsTwoActive = !IsTwoActive"
+        >
+          <el-collapse-item name="NotificationFrequency">
+            <template #title>
+              <div class="Rules_Title">
+                <span class="iconfont_icon">
+                  <svg class="iconfont" aria-hidden="true">
+                    <use
+                      :xlink:href="IsTwoActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
+                    ></use>
+                  </svg>
+                </span>
+                <span class="stars_red">*</span>Notification Frequency
+              </div>
+            </template>
+            <NotiFrequency
+              v-if="props.TitleType == 'Milestone'"
+              ref="NotiFrequencyDeleteMil"
+              :FrequencyData="FrequencyDataMil"
+              @ChangeFrequencyAdd="ChangeMilFrequency"
+            ></NotiFrequency>
+            <NotiFrequency
+              v-if="props.TitleType == 'Container'"
+              ref="NotiFrequencyDeleteCon"
+              :FrequencyData="FrequencyDataCon"
+              @ChangeFrequencyAdd="ChangeConFrequency"
+            ></NotiFrequency>
+            <NotiFrequency
+              v-if="props.TitleType == 'Departure'"
+              ref="NotiFrequencyDeleteDep"
+              :FrequencyData="FrequencyDataDep"
+              @ChangeFrequencyAdd="ChangeDepFrequency"
+            ></NotiFrequency>
+            <NotiFrequency
+              v-if="props.TitleType == 'ETDChange'"
+              ref="NotiFrequencyDeleteETD"
+              :FrequencyData="FrequencyDataETD"
+              @ChangeFrequencyAdd="ChangeETDFrequency"
+            ></NotiFrequency>
+          </el-collapse-item>
+        </el-collapse>
+        <el-collapse
+          style="margin: 17px 0"
+          v-model="RulesActive"
+          @change="IsThreeActive = !IsThreeActive"
+        >
+          <el-collapse-item name="NotificationMethod">
+            <template #title>
+              <div class="Rules_Title">
+                <span class="iconfont_icon">
+                  <svg class="iconfont" aria-hidden="true">
+                    <use
+                      :xlink:href="IsThreeActive ? '#icon-icon_dropdown_b' : '#icon-icon_up_b'"
+                    ></use>
+                  </svg>
+                </span>
+                <span class="stars_red">*</span>Notification Method
+              </div>
+            </template>
+            <NotiMethods
+              v-if="props.TitleType == 'Milestone'"
+              :MethodsData="MethodsDataMil"
+              @ChangeMethodsAdd="ChangeMethodsAddMil"
+            ></NotiMethods>
+            <NotiMethods
+              v-if="props.TitleType == 'Container'"
+              :MethodsData="MethodsDataCon"
+              @ChangeMethodsAdd="ChangeMethodsAddCon"
+            ></NotiMethods>
+            <NotiMethods
+              v-if="props.TitleType == 'Departure'"
+              :MethodsData="MethodsDataDep"
+              @ChangeMethodsAdd="ChangeMethodsAddDep"
+            ></NotiMethods>
+            <NotiMethods
+              v-if="props.TitleType == 'ETDChange'"
+              :MethodsData="MethodsDataETD"
+              @ChangeMethodsAdd="ChangeMethodsAddETD"
+            ></NotiMethods>
+          </el-collapse-item>
+        </el-collapse>
+      </div>
+    </div>
+    <div class="Rules_right">
+      <div class="right_Title">Added Rules</div>
+      <AddedrluesTag
+        v-if="props.TitleType == 'Milestone'"
+        :CheckedList="createListMilestone"
+        Title="Shipment Range"
+        @handleCloseRadio="handleCloseCreateRule"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'Container'"
+        :CheckedList="createListContainer"
+        Title="Shipment Range"
+        @handleCloseRadio="handleCloseCreateRule"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'Departure'"
+        :CheckedList="createListDeparture"
+        Title="Shipment Range"
+        @handleCloseRadio="handleCloseCreateRule"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'ETDChange'"
+        :CheckedList="createListETDChange"
+        Title="Shipment Range"
+        @handleCloseRadio="handleCloseCreateRule"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        :CheckedList="DelayedDeparturedList"
+        v-if="props.TitleType == 'Departure'"
+        @handleCloseRadio="handleCloseDelayed"
+        Title="Ocean Shipments"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'Milestone'"
+        :CheckedList="OceanCheckList"
+        Title="Ocean Shipments"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'Container'"
+        :CheckedList="ContainerOceanList"
+        Title="Ocean Shipments"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        :CheckedList="ETDOceanList"
+        v-if="props.TitleType == 'ETDChange'"
+        Title="Ocean Shipments"
+        @handleCloseRadio="closeOceanETD"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        :CheckedList="ETDAirList"
+        v-if="props.TitleType == 'ETDChange'"
+        @handleCloseRadio="closeAirETD"
+        Title="Air Shipments"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        :CheckedList="DelayedAirdList"
+        v-if="props.TitleType == 'Departure'"
+        @handleCloseRadio="handleCloseAirDelayed"
+        Title="Air Shipments"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'Milestone'"
+        :CheckedList="AirCheckList"
+        Title="Air Shipments"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'Milestone'"
+        :CheckedList="MilFrequencyList"
+        Title="Notification Frequency"
+        @handleCloseRadio="handleCloseRadio('Mil')"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'Container'"
+        :CheckedList="ConFrequencyList"
+        Title="Notification Frequency"
+        @handleCloseRadio="handleCloseRadio('Con')"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'Departure'"
+        :CheckedList="DepFrequencyList"
+        Title="Notification Frequency"
+        @handleCloseRadio="handleCloseRadio('Dep')"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'ETDChange'"
+        :CheckedList="ETDFrequencyList"
+        Title="Notification Frequency"
+        @handleCloseRadio="handleCloseRadio('ETD')"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'Milestone'"
+        :CheckedList="MilMethodsList"
+        Title="Notification Method"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'Container'"
+        :CheckedList="ConMethodsList"
+        Title="Notification Method"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'Departure'"
+        :CheckedList="DepMethodsList"
+        Title="Notification Method"
+      ></AddedrluesTag>
+      <AddedrluesTag
+        v-if="props.TitleType == 'ETDChange'"
+        :CheckedList="ETDMethodsList"
+        Title="Notification Method"
+      ></AddedrluesTag>
+    </div>
+  </div>
+  <el-dialog v-model="UnableSaveVisible" width="480">
+    <div>{{ missingmessage }} missing.</div>
+    <div>Please complete all required fields.</div>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button
+          class="el-button--danger"
+          @click="UnableSaveVisible = 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="SaveedVisible" width="320" style="height: 212px">
+    <div style="text-align: center"><el-image :src="submitsucessful" /></div>
+    <div style="text-align: center; margin-top: 20px">Saved successfully</div>
+  </el-dialog>
+</template>
+
+<style lang="scss" scoped>
+.Rules_flex {
+  padding: 0 0 0 8px;
+  display: flex;
+}
+.Rules_left {
+  width: 60%;
+  border-right: 1px solid var(--color-user-config-title-bottom-border);
+}
+.Rules_right {
+  width: 40%;
+  background-color: var(--more-filters-background-color);
+}
+.right_Title {
+  color: var(--color-neutral-1);
+  padding: 9px 8px;
+  font-size: 18px;
+  font-weight: 700;
+  border-bottom: 1px solid var(--color-user-config-title-bottom-border);
+  background-color: var(--color-drawer-body-bg);
+}
+.iconfont {
+  width: 16px;
+  height: 16px;
+  margin-right: 8px;
+}
+.icon_danger {
+  fill: var(--color-btn-danger-bg);
+}
+:deep(.Rules_Title) {
+  color: var(--color-neutral-1);
+  font-size: 14px;
+  font-weight: 700;
+  height: 22px;
+  display: flex;
+  align-items: center;
+}
+.stars_red {
+  color: var(--color-danger);
+}
+:deep(.el-collapse-item__header) {
+  height: 25px !important;
+  margin: 14px 0 0 0 !important;
+  border: none !important;
+  padding: 0 !important;
+}
+:deep(.el-collapse-item__header):hover {
+  background-color: #fff !important;
+  border: none !important;
+}
+:deep(.el-collapse-item__arrow) {
+  width: 0 !important;
+  height: 0 !important;
+}
+:deep(.el-icon svg) {
+  width: 0 !important;
+}
+:deep(.el-collapse-item__header.is-active) {
+  background-color: transparent !important;
+  border-color: transparent !important;
+}
+:deep(.el-collapse-item__arrow.is-active) {
+  transform: rotate(-180deg) !important;
+}
+:deep(.Ocean_collapse .el-collapse-item__arrow) {
+  width: 16px !important;
+  height: 16px !important;
+  background-image: url('../src/images/icon_expand.png') !important;
+  background-size: contain;
+  background-repeat: no-repeat;
+  transform: rotate(0);
+}
+:deep(.Ocean_collapse .el-collapse-item__header) {
+  background-color: #fff !important;
+  padding: 0 8px !important;
+  height: 40px !important;
+}
+:deep(.Ocean_collapse .el-collapse-item) {
+  background-color: var(--color-dialog-header-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;
+}
+: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);
+}
+.rules_button {
+  width: 100px;
+  height: 40px;
+}
+.cancel_header {
+  font-size: 18px;
+  font-weight: 700;
+  color: var(--color-neutral-1);
+  display: flex;
+  align-items: center;
+}
+.icon_warning {
+  width: 22px;
+  height: 22px;
+  margin-right: 0;
+  fill: var(--color-btn-warning-bg);
+}
+.iconfont_warning {
+  display: flex;
+  align-items: center;
+}
+:deep(header.el-dialog__header) {
+  background-color: #fff;
+}
+:deep(footer.el-dialog__footer) {
+  border-top: none;
+}
+: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>

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

@@ -0,0 +1,78 @@
+<script setup lang="ts" >
+import { ref, watch } from 'vue'
+const props = defineProps({
+  CheckedList: Array,
+  Title: String
+})
+const CheckedList = ref(props.CheckedList)
+watch(
+  () => props.CheckedList,
+  (current) => {
+    CheckedList.value = current
+  }
+)
+
+const emit = defineEmits(['handleCloseRadio'])
+
+const handleClose = (tag: any) => {
+  CheckedList.value?.splice(CheckedList.value.indexOf(tag), 1)
+  emit('handleCloseRadio', tag)
+}
+</script>
+
+<template>
+  <div>
+    <el-card class="Rules_Card">
+      <div class="Card_Title">{{ props.Title }}</div>
+      <div class="right_flex">
+        <el-tag
+          class="tag"
+          v-for="(tag, index) in CheckedList"
+          :key="index"
+          closable
+          @close="handleClose(tag)"
+        >
+          {{ tag }}
+        </el-tag>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.tag {
+  border-radius: 3px;
+  margin-bottom: 4px;
+  color: var(--color-neutral-1);
+  height: 32px;
+  width: fit-content;
+  font-weight: 400;
+  font-size: 14px;
+  background-color: var(--tag-bg-color) !important;
+  border-color: var(--tag-bg-color);
+}
+.tag:last-child {
+  margin-bottom: 0;
+}
+:deep(.el-card__body) {
+  padding: 8px !important;
+  max-height: 400px;
+  overflow-y: scroll;
+}
+:deep(.el-tag .el-tag__close svg) {
+  width: 16px !important;
+}
+.right_flex {
+  display: flex;
+  flex-direction: column;
+}
+.Rules_Card {
+  margin: 8px;
+}
+.Card_Title {
+  color: var(--color-neutral-1);
+  font-size: 14px;
+  font-weight: 600;
+  margin: 9px 0 11px 0;
+}
+</style>

+ 279 - 0
src/components/CreateAddRules/src/components/DelayedType.vue

@@ -0,0 +1,279 @@
+<script lang="ts" setup>
+import { ref, computed, watch } from 'vue'
+
+const props = defineProps({
+  Title: String,
+  DelayedData: Object
+})
+const delayed_data = ref(props.DelayedData)
+watch(
+  () => props.DelayedData,
+  (current) => {
+    delayed_data.value = current
+    DelayedInit()
+  }
+)
+
+const DelayedInit = () => {
+  let array2: any = []
+  OceanCheckedList.value = []
+  if (delayed_data.value?.atd_etd != '' && delayed_data.value?.atd_etd != undefined) {
+    isDeparture.value = true
+    DepartureTime.value = delayed_data.value?.atd_etd
+    DepartureSelect.value = delayed_data.value?.atd_etd_unit
+    OceanCheckedList.value.push('Departure Delayed')
+    array2.push('Departure')
+  }
+  if (delayed_data.value?.ata_eta != '' && delayed_data.value?.ata_eta != undefined) {
+    isArrival.value = true
+    ArrivalTime.value = delayed_data.value?.ata_eta
+    ArrivalSelect.value = delayed_data.value?.ata_eta_unit
+    OceanCheckedList.value.push('Arrival Delayed (ATA-ETA)')
+    array2.push('Arrival')
+  }
+  CheckChange(OceanCheckedList.value)
+  changedeparture(array2)
+}
+const isDeparture = ref(false)
+const isArrival = ref(false)
+const OceanActive = ref(['DelayedShipments'])
+const OceanCheckedList = ref()
+const DepartureTime = ref('')
+const DepartureSelect = ref('')
+const ArrivalTime = ref('')
+const ArrivalSelect = ref('')
+const emit = defineEmits(['ChangeCheckRules', 'closeDelayed'])
+const DepartureList = ref({
+  Departure: '',
+  Arrival: ''
+})
+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 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 CheckChange = (val: any) => {
+  if (val.includes('Departure Delayed')) {
+    isDeparture.value = true
+    Departurestr = 'Departure Delayed' + ' ≥ ' + clampedValue.value + ' ' + DepartureSelect.value
+    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 (ArrivalSelect.value != '') {
+        DepartureList.value.Arrival = Arrivalstr
+      }
+    } else {
+      isArrival.value = false
+      DepartureList.value.Arrival = ''
+    }
+  } else {
+    isDeparture.value = false
+    DepartureList.value.Departure = ''
+    if (val.includes('Arrival Delayed (ATA-ETA)')) {
+      isArrival.value = true
+      Arrivalstr =
+        'Arrival Delayed (ATA-ETA)' + ' ≥ ' + clampedArrivalValue.value + ' ' + ArrivalSelect.value
+      if (ArrivalSelect.value != '') {
+        DepartureList.value.Arrival = Arrivalstr
+      }
+    } else {
+      isArrival.value = false
+      DepartureList.value.Arrival = ''
+    }
+  }
+  emit('ChangeCheckRules', DepartureList.value)
+}
+const changedeparture = (val: any) => {
+  if (val == 'Departure') {
+    Departurestr = 'Departure Delayed' + ' ≥ ' + clampedValue.value + ' ' + DepartureSelect.value
+    if (DepartureSelect.value != '') {
+      DepartureList.value.Departure = Departurestr
+    }
+    if (val == 'Arrival') {
+      Arrivalstr =
+        'Arrival Delayed (ATA-ETA)' + ' ≥ ' + clampedArrivalValue.value + ' ' + ArrivalSelect.value
+      if (ArrivalSelect.value != '') {
+        DepartureList.value.Arrival = Arrivalstr
+      }
+    }
+  } else {
+    if (val == 'Arrival') {
+      Arrivalstr =
+        'Arrival Delayed (ATA-ETA)' + ' ≥ ' + clampedArrivalValue.value + ' ' + ArrivalSelect.value
+      if (ArrivalSelect.value != '') {
+        DepartureList.value.Arrival = Arrivalstr
+      }
+    }
+  }
+  emit('ChangeCheckRules', DepartureList.value)
+}
+const closeDelayed = (val: any) => {
+  if (val.includes('Departure')) {
+    OceanCheckedList.value.splice(OceanCheckedList.value.indexOf('Departure Delayed'), 1)
+    DepartureList.value.Departure = ''
+    isDeparture.value = false
+    DepartureTime.value = ''
+    DepartureSelect.value = ''
+  }
+  if (val.includes('Arrival')) {
+    OceanCheckedList.value.splice(OceanCheckedList.value.indexOf('Arrival Delayed (ATA-ETA)'), 1)
+    DepartureList.value.Arrival = ''
+    isArrival.value = false
+    ArrivalTime.value = ''
+    ArrivalSelect.value = ''
+  }
+  emit('ChangeCheckRules', DepartureList.value)
+}
+
+// 清除所有数据
+const ClearData = () => {
+  OceanCheckedList.value = []
+  DepartureList.value.Departure = ''
+  isDeparture.value = false
+  DepartureList.value.Arrival = ''
+  isArrival.value = false
+  DepartureTime.value = ''
+  DepartureSelect.value = ''
+  ArrivalTime.value = ''
+  ArrivalSelect.value = ''
+}
+
+defineExpose({
+  closeDelayed,
+  ClearData
+})
+</script>
+<template>
+  <div class="Ocean_collapse">
+    <el-collapse v-model="OceanActive">
+      <el-collapse-item name="DelayedShipments">
+        <template #title>
+          <div class="Rules_Title OceanTitle">{{ props.Title }}</div>
+        </template>
+        <div class="oceanCheckbox">
+          <el-checkbox-group @change="CheckChange" v-model="OceanCheckedList">
+            <el-checkbox class="delayedType" value="Departure Delayed">
+              <div>Departure Delayed (ATD-ETD)</div>
+              <div v-if="isDeparture" style="margin-top: 16px">
+                <span class="delayedTitle">Delayed Time</span>
+                <span class="delayedIcon">></span>
+                <el-input
+                  v-model="DepartureTime"
+                  class="input-with-select"
+                  :value="clampedValue"
+                  @input="changedeparture('Departure')"
+                >
+                  <template #append>
+                    <el-select
+                      v-model="DepartureSelect"
+                      placeholder="Select"
+                      @change="changedeparture('Departure')"
+                    >
+                      <el-option label="Day(s)" value="Day(s)" />
+                      <el-option label="Hour(s)" value="Hour(s)" />
+                    </el-select>
+                  </template>
+                </el-input>
+              </div>
+            </el-checkbox>
+            <el-checkbox class="delayedType" value="Arrival Delayed (ATA-ETA)">
+              <div>Arrival Delayed (ATA-ETA)</div>
+              <div v-if="isArrival" style="margin-top: 16px">
+                <span class="delayedTitle">Delayed Time</span>
+                <span class="delayedIcon">></span>
+                <el-input
+                  v-model="ArrivalTime"
+                  @input="changedeparture('Arrival')"
+                  :value="clampedArrivalValue"
+                  class="input-with-select"
+                >
+                  <template #append>
+                    <el-select
+                      v-model="ArrivalSelect"
+                      placeholder="Select"
+                      @change="changedeparture('Arrival')"
+                    >
+                      <el-option label="Day(s)" value="Day(s)" />
+                      <el-option label="Hour(s)" value="Hour(s)" />
+                    </el-select>
+                  </template>
+                </el-input>
+              </div>
+            </el-checkbox>
+          </el-checkbox-group>
+        </div>
+      </el-collapse-item>
+    </el-collapse>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+:deep(.el-checkbox-group) {
+  border: 1px solid var(--color-select-border);
+  border-radius: 5px;
+}
+:deep(.el-checkbox) {
+  width: 100%;
+  background-color: var(--color-drawer-body-bg);
+  border-bottom: 1px solid var(--color-select-border);
+  padding: 8px;
+}
+:deep(.el-checkbox:first-child) {
+  border-radius: 6px 6px 0 0;
+}
+:deep(.el-checkbox:last-child) {
+  border-radius: 0 0 6px 6px;
+  border-bottom: none;
+}
+.delayedType {
+  align-items: start;
+  height: fit-content;
+  margin-right: 5px;
+  border-radius: 6px;
+  padding: 8px;
+}
+.input-with-select {
+  width: 180px;
+}
+:deep(.el-input__wrapper) {
+  width: 50%;
+}
+:deep(.el-input-group__append) {
+  width: 50%;
+  padding: 0;
+  border: none;
+}
+:deep(.el-input-group--append .el-input-group__append .el-select .el-select__wrapper) {
+  padding: 5px 10px;
+  background-color: #fff;
+}
+.delayedTitle {
+  color: var(--color-neutral-2);
+  font-size: 12px;
+}
+.delayedIcon {
+  margin: 0 8px;
+}
+.oceanCheckbox {
+  margin-bottom: 8px;
+}
+</style>

+ 341 - 0
src/components/CreateAddRules/src/components/ETDShipments.vue

@@ -0,0 +1,341 @@
+<script lang="ts" setup>
+import { ref, computed, watch } from 'vue'
+
+const props = defineProps({
+  Title: String,
+  ETDData: Object
+})
+const ETD_data = ref(props.ETDData)
+watch(
+  () => props.ETDData,
+  (current) => {
+    ETD_data.value = current
+    ETDInit()
+  }
+)
+
+const ETDInit = () => {
+  OceanCheckedList.value = []
+  if (ETD_data.value?.ETDradio == 't') {
+    ETDRadio.value = '1'
+    OceanCheckedList.value.push('ETD')
+  } else {
+    if (ETD_data.value?.etd_old_sub_new != '' && ETD_data.value?.etd_old_sub_new != undefined) {
+      ETDRadio.value = '2'
+      isETD.value = true
+      ETDTime.value = ETD_data.value?.etd_old_sub_new
+      ETDSelect.value = ETD_data.value?.etd_old_sub_new_unit
+      OceanCheckedList.value.push('ETD')
+    }
+  }
+  if (ETD_data.value?.ETAradio == 't') {
+    ETARadio.value = '1'
+    OceanCheckedList.value.push('ETA')
+  } else {
+    if (ETD_data.value?.eta_old_sub_new != '' && ETD_data.value?.eta_old_sub_new != undefined) {
+      ETARadio.value = '2'
+      isETA.value = true
+      ETATime.value = ETD_data.value?.eta_old_sub_new
+      ETASelect.value = ETD_data.value?.eta_old_sub_new_unit
+      OceanCheckedList.value.push('ETA')
+    }
+  }
+  CheckChange(OceanCheckedList.value)
+  changeETDRadio(ETDRadio.value)
+  changeETARadio(ETARadio.value)
+  changedeparture(OceanCheckedList.value)
+}
+const isETD = ref(false)
+const isETA = ref(false)
+const OceanActive = ref(['ETDShipments'])
+const OceanCheckedList = ref()
+const ETDTime = ref('')
+const ETDSelect = ref('')
+const ETATime = ref('')
+const ETASelect = ref('')
+const ETDRadio = ref()
+const ETARadio = ref()
+const emit = defineEmits(['ChangeCheckRules', 'closeETD'])
+const ETDETAList = ref({
+  ETD: '',
+  ETA: ''
+})
+let ETDstr: any = ''
+let ETAstr: any = ''
+const CheckChange = (val: any) => {
+  if (val.includes('ETD')) {
+    isETD.value = true
+    if (ETDSelect.value != '') {
+      ETDETAList.value.ETD = ETDstr
+    }
+    if (val.includes('ETA')) {
+      isETA.value = true
+      if (ETASelect.value != '') {
+        ETDETAList.value.ETA = ETAstr
+      }
+    } else {
+      isETA.value = false
+      ETDETAList.value.ETA = ''
+    }
+  } else {
+    isETD.value = false
+    ETDETAList.value.ETD = ''
+    if (val.includes('ETA')) {
+      isETA.value = true
+      if (ETASelect.value != '') {
+        ETDETAList.value.ETA = ETAstr
+      }
+    } else {
+      isETA.value = false
+      ETDETAList.value.ETA = ''
+    }
+  }
+  emit('ChangeCheckRules', ETDETAList.value)
+}
+const changeETDRadio = (val: any) => {
+  if (val == 1) {
+    ETDstr = 'ETD: Notify for all changes'
+    ETDETAList.value.ETD = ETDstr
+  } else if (val == 2) {
+    ETDstr = 'ETD: Notify for all changes ≥ ' + clampedValue.value + ' ' + ETDSelect.value
+    if (ETDSelect.value != '') {
+      ETDETAList.value.ETD = ETDstr
+    } else {
+      ETDETAList.value.ETD = ''
+    }
+  }
+  emit('ChangeCheckRules', ETDETAList.value)
+}
+const changeETARadio = (val: any) => {
+  if (val == 1) {
+    ETAstr = 'ETA: Notify for all changes'
+    ETDETAList.value.ETA = ETAstr
+  } else if (val == 2) {
+    ETAstr = 'ETA: Notify for all changes ≥ ' + clampedETAValue.value + ' ' + ETASelect.value
+    if (ETASelect.value != '') {
+      ETDETAList.value.ETA = ETAstr
+    } else {
+      ETDETAList.value.ETA = ''
+    }
+  }
+  emit('ChangeCheckRules', ETDETAList.value)
+}
+const changedeparture = (val: any) => {
+  if (val == 'ETD') {
+    ETDstr = 'ETD: Notify for all changes ≥ ' + clampedValue.value + ' ' + ETDSelect.value
+    if (ETDSelect.value != '') {
+      ETDETAList.value.ETD = ETDstr
+    }
+    if (val == 'ETA') {
+      ETAstr = 'ETA: Notify for all changes ≥ ' + clampedETAValue.value + ' ' + ETASelect.value
+      if (ETASelect.value != '') {
+        ETDETAList.value.ETA = ETAstr
+      }
+    }
+  } else {
+    if (val == 'ETA') {
+      ETAstr = 'ETA: Notify for all changes ≥ ' + clampedETAValue.value + ' ' + ETASelect.value
+      if (ETASelect.value != '') {
+        ETDETAList.value.ETA = ETAstr
+      }
+    }
+  }
+  emit('ChangeCheckRules', ETDETAList.value)
+}
+const closeETD = (val: any) => {
+  if (val.includes('ETD')) {
+    OceanCheckedList.value.splice(OceanCheckedList.value.indexOf('ETD'), 1)
+    ETDETAList.value.ETD = ''
+    isETD.value = false
+    ETDTime.value = ''
+    ETDSelect.value = ''
+    ETDRadio.value = 0
+  }
+  if (val.includes('ETA')) {
+    OceanCheckedList.value.splice(OceanCheckedList.value.indexOf('ETA'), 1)
+    ETDETAList.value.ETA = ''
+    isETA.value = false
+    ETATime.value = ''
+    ETASelect.value = ''
+    ETARadio.value = 0
+  }
+  emit('ChangeCheckRules', ETDETAList.value)
+}
+
+// 清除所有数据
+const ClearData = () => {
+  OceanCheckedList.value = []
+  ETDETAList.value.ETD = ''
+  isETD.value = false
+  ETDETAList.value.ETA = ''
+  isETA.value = false
+  ETDRadio.value = 0
+  ETARadio.value = 0
+  ETDTime.value = ''
+  ETDSelect.value = ''
+  ETATime.value = ''
+  ETASelect.value = ''
+}
+
+defineExpose({
+  closeETD,
+  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 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之间,但不更新原输入值仅用于显示
+  }
+})
+</script>
+<template>
+  <div class="Ocean_collapse">
+    <el-collapse v-model="OceanActive">
+      <el-collapse-item name="ETDShipments">
+        <template #title>
+          <div class="Rules_Title OceanTitle">{{ props.Title }}</div>
+        </template>
+        <div class="oceanCheckbox">
+          <el-checkbox-group @change="CheckChange" v-model="OceanCheckedList">
+            <el-checkbox class="delayedType" value="ETD">
+              <div>ETD</div>
+              <div v-if="isETD" style="margin-top: 16px">
+                <el-radio-group v-model="ETDRadio" @change="changeETDRadio">
+                  <el-radio value="1">Notify for all changes</el-radio>
+                  <el-radio value="2"
+                    >Notify only when time difference
+                    <span class="delayedIcon">></span>
+                    <el-input
+                      v-model="ETDTime"
+                      :value="clampedValue"
+                      class="input-with-select"
+                      @input="changedeparture('ETD')"
+                    >
+                      <template #append>
+                        <el-select
+                          v-model="ETDSelect"
+                          placeholder="Select"
+                          @change="changedeparture('ETD')"
+                        >
+                          <el-option label="Day(s)" value="Day(s)" />
+                          <el-option label="Hour(s)" value="Hour(s)" />
+                        </el-select>
+                      </template> </el-input
+                  ></el-radio>
+                </el-radio-group>
+              </div>
+            </el-checkbox>
+            <el-checkbox class="delayedType" value="ETA">
+              <div>ETA</div>
+              <div v-if="isETA" style="margin-top: 16px">
+                <el-radio-group v-model="ETARadio" @change="changeETARadio">
+                  <el-radio value="1">Notify for all changes</el-radio>
+                  <el-radio value="2"
+                    >Notify only when time difference
+                    <span class="delayedIcon">></span>
+                    <el-input
+                      v-model="ETATime"
+                      :value="clampedETAValue"
+                      class="input-with-select"
+                      @input="changedeparture('ETA')"
+                    >
+                      <template #append>
+                        <el-select
+                          v-model="ETASelect"
+                          placeholder="Select"
+                          @change="changedeparture('ETA')"
+                        >
+                          <el-option label="Day(s)" value="Day(s)" />
+                          <el-option label="Hour(s)" value="Hour(s)" />
+                        </el-select>
+                      </template> </el-input
+                  ></el-radio>
+                </el-radio-group>
+              </div>
+            </el-checkbox>
+          </el-checkbox-group>
+        </div>
+      </el-collapse-item>
+    </el-collapse>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+:deep(.el-checkbox-group) {
+  border: 1px solid var(--color-select-border);
+  border-radius: 5px;
+}
+:deep(.el-checkbox) {
+  width: 100%;
+  background-color: var(--color-drawer-body-bg);
+  border-bottom: 1px solid var(--color-select-border);
+  padding: 8px;
+}
+:deep(.el-checkbox:first-child) {
+  border-radius: 6px 6px 0 0;
+}
+:deep(.el-checkbox:last-child) {
+  border-radius: 0 0 6px 6px;
+  border-bottom: none;
+}
+:deep(.el-radio) {
+  border: none !important;
+}
+.delayedType {
+  align-items: start;
+  height: fit-content;
+  margin-right: 5px;
+  border-radius: 6px;
+  padding: 8px;
+}
+.input-with-select {
+  width: 180px;
+}
+:deep(.el-input__wrapper) {
+  width: 50%;
+}
+:deep(.el-input-group__append) {
+  width: 50%;
+  padding: 0;
+  border: none;
+}
+:deep(.el-input-group--append .el-input-group__append .el-select .el-select__wrapper) {
+  padding: 5px 10px;
+  background-color: #fff;
+}
+.delayedIcon {
+  margin: 0 8px;
+}
+.oceanCheckbox {
+  margin-bottom: 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: center;
+}
+:deep(.el-radio__input.is-checked + .el-radio__label) {
+  color: var(--color-neutral-1);
+}
+</style>

+ 398 - 0
src/components/CreateAddRules/src/components/NotiFrequency.vue

@@ -0,0 +1,398 @@
+<script setup lang="ts">
+import { ref, watch } from 'vue'
+import moment from 'moment-timezone'
+import { defaultTimeZone } from '@/utils/timezone'
+
+const props = defineProps({
+  FrequencyData: Object
+})
+
+const frequency_data = ref(props.FrequencyData)
+const radio = ref(0)
+const FrequencyList = ref()
+const DailyTime = ref('')
+const WeeklyTime = ref('')
+const WeeklyDay = ref('')
+const WeekDay = ref([
+  {
+    label: 'Monday',
+    value: 'Monday'
+  },
+  {
+    label: 'Tuesday',
+    value: 'Tuesday'
+  },
+  {
+    label: 'Wednesday',
+    value: 'Wednesday'
+  },
+  {
+    label: 'Thursday',
+    value: 'Thursday'
+  },
+  {
+    label: 'Friday',
+    value: 'Friday'
+  },
+  {
+    label: 'Saturday',
+    value: 'Saturday'
+  },
+  {
+    label: 'Sunday',
+    value: 'Sunday'
+  }
+])
+const TimeZone = ref([
+  {
+    label: 'UTC-08',
+    value: 'UTC-08'
+  },
+  {
+    label: 'UTC-07',
+    value: 'UTC-07'
+  },
+  {
+    label: 'UTC-06',
+    value: 'UTC-06'
+  },
+  {
+    label: 'UTC-05',
+    value: 'UTC-05'
+  },
+  {
+    label: 'UTC-04',
+    value: 'UTC-04'
+  },
+  {
+    label: 'UTC-03',
+    value: 'UTC-03'
+  },
+  {
+    label: 'UTC-02',
+    value: 'UTC-02'
+  },
+  {
+    label: 'UTC-01',
+    value: 'UTC-01'
+  },
+  {
+    label: 'UTC-00',
+    value: 'UTC-00'
+  },
+  {
+    label: 'UTC+01',
+    value: 'UTC+01'
+  },
+  {
+    label: 'UTC+02',
+    value: 'UTC+02'
+  },
+  {
+    label: 'UTC+03',
+    value: 'UTC+03'
+  },
+  {
+    label: 'UTC+04',
+    value: 'UTC+04'
+  },
+  {
+    label: 'UTC+05',
+    value: 'UTC+05'
+  },
+  {
+    label: 'UTC+05:30',
+    value: 'UTC+05:30'
+  },
+  {
+    label: 'UTC+06',
+    value: 'UTC+06'
+  },
+  {
+    label: 'UTC+07',
+    value: 'UTC+07'
+  },
+  {
+    label: 'UTC+08',
+    value: 'UTC+08'
+  },
+  {
+    label: 'UTC+09',
+    value: 'UTC+09'
+  },
+  {
+    label: 'UTC+10',
+    value: 'UTC+10'
+  },
+  {
+    label: 'UTC+11',
+    value: 'UTC+11'
+  },
+  {
+    label: 'UTC+12',
+    value: 'UTC+12'
+  }
+])
+const TimeZoneDailySelect = ref()
+const TimeZoneWeeklySelect = ref()
+TimeZoneDailySelect.value = 'UTC' + moment().tz(moment.tz.guess()).format('Z').slice(0, 3)
+TimeZoneWeeklySelect.value = 'UTC' + moment().tz(moment.tz.guess()).format('Z').slice(0, 3)
+const isDaily = ref(false)
+const isWeekly = ref(false)
+let savesubscribeobj: any = {}
+
+watch(
+  () => props.FrequencyData,
+  (current) => {
+    frequency_data.value = current
+    FrequencyDataInit()
+  }
+)
+
+const emits = defineEmits(['ChangeFrequencyAdd'])
+
+// 初始设置Frequency
+const FrequencyDataInit = () => {
+  if (frequency_data.value?.frequency_type == 'Instant') {
+    radio.value = 1
+    ChangeFrequency(1)
+  } else if (frequency_data.value?.frequency_type == 'Daily') {
+    radio.value = 2
+    DailyTime.value = frequency_data.value?.daily_time
+    TimeZoneDailySelect.value = frequency_data.value?.daily_time_zone
+    ChangeFrequency(2)
+  } else if (frequency_data.value?.frequency_type == 'Weekly') {
+    radio.value = 3
+    WeeklyDay.value = frequency_data.value?.weekly_week
+    WeeklyTime.value = frequency_data.value?.weekly_time
+    TimeZoneWeeklySelect.value = frequency_data.value?.weekly_time_zone
+    ChangeFrequency(3)
+  } else {
+    radio.value = 0
+    ChangeFrequency(0)
+  }
+}
+
+// 更改Frequency时间
+const ChangeFrequency = (val: any) => {
+  FrequencyList.value = []
+  let str: any = ''
+  if (val == 1) {
+    isDaily.value = false
+    isWeekly.value = false
+    str = 'Instant notification for each update'
+    FrequencyList.value.push(str)
+    savesubscribeobj.frequency_type = 'Instant'
+    savesubscribeobj.frequency_display = str
+  } else if (val == 2) {
+    isDaily.value = true
+    isWeekly.value = false
+    str = 'Daily, ' + DailyTime.value + ', ' + TimeZoneDailySelect.value
+    if (DailyTime.value != '' && TimeZoneDailySelect.value !== '') {
+      FrequencyList.value.push(str)
+    }
+    savesubscribeobj.frequency_type = 'Daily'
+    savesubscribeobj.daily_time = DailyTime.value
+    savesubscribeobj.daily_time_zone = TimeZoneDailySelect.value
+    savesubscribeobj.frequency_display = str
+  } else if (val == 3) {
+    isDaily.value = false
+    isWeekly.value = true
+    str = 'Weekly, ' + WeeklyDay.value + ', ' + WeeklyTime.value + ', ' + TimeZoneWeeklySelect.value
+    if (WeeklyDay.value != '' && WeeklyTime.value != '' && TimeZoneWeeklySelect.value !== '') {
+      FrequencyList.value.push(str)
+    }
+    savesubscribeobj.frequency_type = 'Weekly'
+    if (WeeklyDay.value == 'Monday') {
+      savesubscribeobj.weekly_week = 1
+    } else if (WeeklyDay.value == 'Tuesday') {
+      savesubscribeobj.weekly_week = 2
+    } else if (WeeklyDay.value == 'Wednesday') {
+      savesubscribeobj.weekly_week = 3
+    } else if (WeeklyDay.value == 'Thursday') {
+      savesubscribeobj.weekly_week = 4
+    } else if (WeeklyDay.value == 'Friday') {
+      savesubscribeobj.weekly_week = 5
+    } else if (WeeklyDay.value == 'Saturday') {
+      savesubscribeobj.weekly_week = 6
+    } else if (WeeklyDay.value == 'Sunday') {
+      savesubscribeobj.weekly_week = 0
+    }
+    savesubscribeobj.weekly_time = WeeklyTime.value
+    savesubscribeobj.weekly_time_zone = TimeZoneWeeklySelect.value
+    savesubscribeobj.frequency_display = str
+  } else {
+    isDaily.value = false
+    isWeekly.value = false
+    DailyTime.value = ''
+    WeeklyTime.value = ''
+    WeeklyDay.value = ''
+    delete savesubscribeobj.frequency_type
+    delete savesubscribeobj.daily_time
+    delete savesubscribeobj.daily_time_zone
+    delete savesubscribeobj.weekly_week
+    delete savesubscribeobj.weekly_time
+    delete savesubscribeobj.weekly_time_zone
+  }
+  emits('ChangeFrequencyAdd', FrequencyList.value, savesubscribeobj)
+}
+const changeTime = (val: any) => {
+  if (val == 'Daily') {
+    ChangeFrequency(2)
+  } else {
+    ChangeFrequency(3)
+  }
+}
+
+// 删除 Frequency tag
+const handleCloseRadioFrequency = () => {
+  radio.value = 0
+  ChangeFrequency(0)
+}
+
+const user_type = localStorage.getItem('user_type')
+
+defineExpose({
+  handleCloseRadioFrequency,
+  FrequencyDataInit
+})
+</script>
+<template>
+  <div style="margin-top: 11px">
+    <el-radio-group v-model="radio" @change="ChangeFrequency">
+      <el-radio :value="1" v-if="user_type != null && user_type != 'customer'"
+        >Instant notification for each update</el-radio
+      >
+      <el-radio :value="2">
+        <div>Daily Summary</div>
+        <div class="Daily" v-if="isDaily">
+          <div class="Daily_left" style="margin-right: 8px">
+            Select Time
+            <div>
+              <el-time-select
+                v-model="DailyTime"
+                start="00:00"
+                step="00:30"
+                end="23:30"
+                prefix-icon=""
+                @change="changeTime('Daily')"
+                placeholder="Select Time"
+              ></el-time-select>
+            </div>
+          </div>
+          <div class="Daily_left">
+            Select Time Zone
+            <div>
+              <el-select
+                v-model="TimeZoneDailySelect"
+                placeholder="Select Time Zone"
+                @change="changeTime('Daily')"
+              >
+                <el-option
+                  v-for="item in TimeZone"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                />
+              </el-select>
+            </div>
+          </div>
+        </div>
+      </el-radio>
+      <el-radio :value="3">
+        <div>Weekly Summary</div>
+        <div class="Daily" v-if="isWeekly">
+          <div class="Weekly_left">
+            Select Day
+            <div>
+              <el-select
+                v-model="WeeklyDay"
+                @change="changeTime('Weekly')"
+                placeholder="Select Day"
+              >
+                <el-option
+                  v-for="item in WeekDay"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                />
+              </el-select>
+            </div>
+          </div>
+          <div class="Weekly_left" style="margin: 0 8px">
+            Select Time
+            <div>
+              <el-time-select
+                v-model="WeeklyTime"
+                @change="changeTime('Weekly')"
+                start="00:00"
+                step="00:30"
+                end="23:30"
+                prefix-icon=""
+                placeholder="Select time"
+              ></el-time-select>
+            </div>
+          </div>
+          <div class="Weekly_left">
+            Select Time Zone
+            <div>
+              <el-select
+                v-model="TimeZoneWeeklySelect"
+                placeholder="Select Time Zone"
+                @change="changeTime('Weekly')"
+              >
+                <el-option
+                  v-for="item in TimeZone"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                />
+              </el-select>
+            </div>
+          </div>
+        </div>
+      </el-radio>
+    </el-radio-group>
+  </div>
+</template>
+<style lang="scss" scoped>
+: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;
+}
+:deep(.el-radio__input.is-checked + .el-radio__label) {
+  color: var(--color-neutral-1);
+}
+.Daily {
+  margin: 0 0 9px 0;
+  display: flex;
+}
+.Daily_left {
+  color: var(--color-neutral-2);
+  width: 50%;
+}
+.Weekly_left {
+  color: var(--color-neutral-2);
+  width: 33%;
+}
+:deep(.el-select__icon.el-icon svg) {
+  width: 1em !important;
+}
+:deep(.el-select__wrapper.is-filterable) {
+  padding-left: 7px;
+}
+:deep(.el-radio__inner) {
+  margin-top: 7px;
+}
+</style>

+ 92 - 0
src/components/CreateAddRules/src/components/NotiMethods.vue

@@ -0,0 +1,92 @@
+<script setup lang="ts">
+import { ref, watch } from 'vue'
+
+const checkMethodList = ref()
+checkMethodList.value = []
+let savesubscribeobj: any = {}
+const props = defineProps({
+  MethodsData: Object
+})
+
+const methods_data = ref(props.MethodsData)
+watch(
+  () => props.MethodsData,
+  (current) => {
+    methods_data.value = current
+    MethodsInit()
+  }
+)
+const emits = defineEmits(['ChangeMethodsAdd'])
+// 初始设置Methods
+const MethodsInit = () => {
+  if (methods_data.value?.method_display != undefined) {
+    if (methods_data.value?.method_display.indexOf('Email') != -1) {
+      checkMethodList.value.push('By Email')
+    }
+  }
+  if (methods_data.value?.method_display != undefined) {
+    if (methods_data.value?.method_display.indexOf('System Message') != -1) {
+      checkMethodList.value.push('By System Message')
+    }
+  }
+  changeMethod(checkMethodList.value)
+}
+
+// 选中Method
+const changeMethod = (val: any) => {
+  if (val.indexOf('By Email') != -1) {
+    savesubscribeobj.method_by_email = true
+  } else {
+    savesubscribeobj.method_by_email = false
+  }
+  if (val.indexOf('By System Message ') != -1) {
+    savesubscribeobj.method_by_message = true
+  } else {
+    savesubscribeobj.method_by_message = false
+  }
+  if (val.length != 0) {
+    savesubscribeobj.method_display = val.map((e: any) => e.replace('By ', '')).join(',')
+  } else {
+    savesubscribeobj.method_display = ''
+  }
+  emits('ChangeMethodsAdd', checkMethodList.value, savesubscribeobj)
+}
+const user_type = localStorage.getItem('user_type')
+</script>
+<template>
+  <div style="margin-top: 11px">
+    <div class="Method">
+      <el-checkbox-group v-model="checkMethodList" @change="changeMethod">
+        <el-checkbox
+          class="methodcheckbox"
+          value="By Email"
+          v-if="user_type != null && user_type != 'customer'"
+        >
+          <div>By Email</div>
+          <div class="methos_image"><img src="../images/illustration_email@2x.png" /></div>
+        </el-checkbox>
+        <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" />
+          </div>
+        </el-checkbox>
+      </el-checkbox-group>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.methodcheckbox {
+  align-items: start;
+  height: fit-content;
+  width: 49%;
+  margin-right: 5px;
+  background-color: var(--color-dialog-header-bg);
+  border-radius: 6px;
+  padding: 11px 0 0 13px;
+}
+.methos_image {
+  margin: 9px 0;
+}
+</style>

+ 83 - 0
src/components/CreateAddRules/src/components/RulesShipments.vue

@@ -0,0 +1,83 @@
+<script lang="ts" setup>
+import { ref, watch } from 'vue'
+
+interface OceanCheckboxItem {
+  value: string
+  label: string
+}
+interface Props {
+  CheckboxList: OceanCheckboxItem[]
+  Title: String
+  CheckedList: Array<''>
+}
+const props = defineProps<Props>()
+
+const OceanActive = ref(['OceanShipments'])
+const CheckedList = ref(props.CheckedList)
+const CheckboxList = ref(props.CheckboxList)
+watch(
+  () => props.CheckboxList,
+  (current) => {
+    CheckboxList.value = current
+  }
+)
+watch(
+  () => props.CheckedList,
+  (current) => {
+    CheckedList.value = current
+  }
+)
+
+const emit = defineEmits(['ChangeCheckRules'])
+const CheckChange = () => {
+  console.log(CheckedList.value)
+  emit('ChangeCheckRules', CheckedList.value)
+}
+</script>
+<template>
+  <div class="Ocean_collapse">
+    <el-collapse v-model="OceanActive">
+      <el-collapse-item name="OceanShipments">
+        <template #title>
+          <div class="Rules_Title OceanTitle">{{ props.Title }}</div>
+        </template>
+        <div class="oceanCheckbox">
+          <el-checkbox-group @change="CheckChange" v-model="CheckedList">
+            <el-checkbox
+              v-for="item in CheckboxList"
+              :key="item.label"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-checkbox-group>
+        </div>
+      </el-collapse-item>
+    </el-collapse>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+:deep(.el-checkbox-group) {
+  border: 1px solid var(--color-select-border);
+  border-radius: 5px;
+}
+:deep(.el-checkbox) {
+  width: 100%;
+  background-color: var(--color-drawer-body-bg);
+  border-bottom: 1px solid var(--color-select-border);
+  padding: 8px;
+}
+:deep(.el-checkbox:first-child) {
+  border-radius: 6px 6px 0 0;
+}
+:deep(.el-checkbox:last-child) {
+  border-radius: 0 0 6px 6px;
+  border-bottom: none;
+}
+.oceanCheckbox {
+  max-height: 321px;
+  overflow-x: hidden;
+  margin-bottom: 8px;
+  overflow-y: scroll;
+}
+</style>

+ 226 - 0
src/components/CreateAddRules/src/components/ShipmentRange.vue

@@ -0,0 +1,226 @@
+<script lang="ts" setup>
+import { ref, computed, watch } from 'vue'
+
+const OceanActive = ref(['TransportMode', 'Time'])
+const TransportCheckedList = ref([])
+interface OceanItem {
+  label: string
+  value: string
+}
+const TransportList = ref<OceanItem[]>([])
+const props = defineProps({
+  ShipmentRangeData: Object
+})
+const ShipmentRange_data = ref(props.ShipmentRangeData)
+TransportList.value = [
+  {
+    label: 'Ocean',
+    value: 'Ocean'
+  },
+  {
+    label: 'Air',
+    value: 'Air'
+  },
+  {
+    label: 'Road',
+    value: 'Road'
+  }
+]
+
+const TimeChecked = ref()
+
+const ETDTime = ref('')
+const ETATime = ref('')
+
+watch(
+  () => props.ShipmentRangeData,
+  (current) => {
+    ShipmentRange_data.value = current
+    ShipmentRangeInit()
+  }
+)
+
+const ShipmentRangeInit = () => {
+  if (ShipmentRange_data.value?.shipment_transport_mode != undefined) {
+    TransportCheckedList.value = ShipmentRange_data.value?.shipment_transport_mode.split(';')
+    CheckChange(ShipmentRange_data.value?.shipment_transport_mode)
+  }
+  if (ShipmentRange_data.value?.shipment_eta_limit != undefined) {
+    ETATime.value = ShipmentRange_data.value?.shipment_eta_limit
+    TimeChecked.value = 2
+    changeTime(2)
+  }
+  if (ShipmentRange_data.value?.shipment_etd_limit != undefined) {
+    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 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之间,但不更新原输入值仅用于显示
+  }
+})
+
+let Transportstr: any = ''
+let Timestr: any = ''
+const emit = defineEmits(['ChangeCheckRules', 'ChangeCheckTimeRules'])
+const CheckChange = (val: any) => {
+  if (val != '') {
+    Transportstr = 'Transport Mode: ' + val
+  } else {
+    Transportstr = ''
+  }
+  emit('ChangeCheckRules', Transportstr, TransportCheckedList.value)
+}
+
+// 输入ETD、ETA
+const changeTime = (val: any) => {
+  if (val == 1) {
+    Timestr = 'ETD within ' + clampedETDValue.value + ' Day(s)'
+  } else if (val == 2) {
+    Timestr = 'ETA within ' + clampedETAValue.value + ' Day(s)'
+  } else {
+    Timestr = ''
+  }
+  emit('ChangeCheckTimeRules', Timestr, clampedETDValue.value)
+}
+
+const handleCloseCreateRule = (val: any) => {
+  if (val.indexOf('ETD') != -1 || val.indexOf('ETA') != -1) {
+    TimeChecked.value = 0
+    ETDTime.value = ''
+    ETATime.value = ''
+  } else if (val.indexOf('Transport') != -1) {
+    TransportCheckedList.value = []
+  }
+}
+
+defineExpose({
+  handleCloseCreateRule
+})
+</script>
+<template>
+  <div class="Ocean_collapse">
+    <el-collapse v-model="OceanActive">
+      <el-collapse-item name="TransportMode">
+        <template #title>
+          <div class="Rules_Title OceanTitle">Transport Mode</div>
+        </template>
+        <div class="oceanCheckbox">
+          <el-checkbox-group @change="CheckChange" v-model="TransportCheckedList">
+            <el-checkbox
+              v-for="item in TransportList"
+              :key="item.label"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-checkbox-group>
+        </div>
+      </el-collapse-item>
+      <el-collapse-item name="Time">
+        <template #title>
+          <div class="Rules_Title OceanTitle">Time</div>
+        </template>
+        <div class="oceanCheckbox">
+          <el-radio-group v-model="TimeChecked" @change="changeTime">
+            <el-radio :value="1">
+              <div class="flex">
+                <div style="width: 70px">ETD within</div>
+                <el-input
+                  @input="changeTime('1')"
+                  v-model="ETDTime"
+                  :value="clampedETDValue"
+                  class="input-with-select"
+                >
+                </el-input>
+                <div class="Days">Day(s)</div>
+              </div>
+            </el-radio>
+            <el-radio :value="2">
+              <div class="flex">
+                <div style="width: 70px">ETA within</div>
+                <el-input
+                  @input="changeTime('2')"
+                  v-model="ETATime"
+                  :value="clampedETAValue"
+                  class="input-with-select"
+                >
+                </el-input>
+                <div class="Days">Day(s)</div>
+              </div>
+            </el-radio>
+          </el-radio-group>
+        </div>
+      </el-collapse-item>
+    </el-collapse>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+:deep(.el-checkbox-group) {
+  border: 1px solid var(--color-select-border);
+  border-radius: 5px;
+}
+:deep(.el-checkbox) {
+  width: 100%;
+  background-color: var(--color-drawer-body-bg);
+  border-bottom: 1px solid var(--color-select-border);
+  padding: 8px;
+}
+:deep(.el-checkbox:first-child) {
+  border-radius: 6px 6px 0 0;
+}
+:deep(.el-checkbox:last-child) {
+  border-radius: 0 0 6px 6px;
+  border-bottom: none;
+}
+.oceanCheckbox {
+  max-height: 321px;
+  overflow-x: hidden;
+  margin-bottom: 8px;
+  overflow-y: scroll;
+}
+
+.input-with-select {
+  width: 96px;
+  border-radius: 6px 0 0 6px;
+  margin-left: 8px;
+}
+:deep(.el-input__wrapper) {
+  border-radius: 6px 0 0 6px;
+  opacity: 0.8;
+  height: 28px;
+}
+.flex {
+  display: flex;
+  align-items: center;
+}
+.Days {
+  width: 64px;
+  border: 1px solid var(--color-select-border);
+  border-radius: 0 6px 6px 0;
+  background-color: var(--color-drawer-body-bg);
+  border-left: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  opacity: 0.8;
+  height: 28px;
+}
+:deep(.el-radio) {
+  background-color: var(--color-drawer-body-bg);
+}
+</style>

二进制
src/components/CreateAddRules/src/images/icon_collapse.png


二进制
src/components/CreateAddRules/src/images/icon_expand.png


二进制
src/components/CreateAddRules/src/images/illustration_email@2x.png


二进制
src/components/CreateAddRules/src/images/illustration_system massage@2x.png


二进制
src/components/CreateAddRules/src/images/submit_successful.png


+ 69 - 32
src/components/DateRange/src/DateRange.vue

@@ -3,8 +3,11 @@ import emitter from '@/utils/bus'
 import { ref, watch, onMounted, onBeforeMount } from 'vue'
 import IconDropDown from '@/components/IconDropDown'
 import CalendarDate from './components/CalendarDate.vue'
-import moment from 'moment'
+import { formatTimezone } from '@/utils/tools'
 import dayjs from 'dayjs'
+import { useUserStore } from '@/stores/modules/user'
+
+const userStore = useUserStore()
 
 onMounted(() => {
   defaultDate()
@@ -70,13 +73,17 @@ const defaultDate = () => {
       if (sessionStorage.getItem('searchTableQeuryTracking') == null) {
         DateStart.value = [dayjs().subtract(2, 'month').startOf('month'), dayjs().add(1, 'month')]
         daterangeObj.ETD =
-          DateStart.value[0].format('MMM/DD/YYYY') +
+          DateStart.value[0].format(userStore.dateFormat) +
           ' To ' +
-          DateStart.value[1].format('MMM/DD/YYYY')
+          DateStart.value[1].format(userStore.dateFormat)
         const obj = {
           title: 'ETD',
-          data: [DateStart.value[0].format('MM/DD/YYYY'), DateStart.value[1].format('MM/DD/YYYY')]
+          data: [
+            DateStart.value[0].format(userStore.dateFormat),
+            DateStart.value[1].format(userStore.dateFormat)
+          ]
         }
+
         daterangeObj2.ETD = obj
       } else {
         searchTableQeuryTracking.value =
@@ -87,12 +94,15 @@ const defaultDate = () => {
             dayjs(searchTableQeuryTracking.value.etd_end)
           ]
           daterangeObj.ETD =
-            DateStart.value[0].format('MMM/DD/YYYY') +
+            DateStart.value[0].format(userStore.dateFormat) +
             ' To ' +
-            DateStart.value[1].format('MMM/DD/YYYY')
+            DateStart.value[1].format(userStore.dateFormat)
           const obj = {
             title: 'ETD',
-            data: [DateStart.value[0].format('MM/DD/YYYY'), DateStart.value[1].format('MM/DD/YYYY')]
+            data: [
+              DateStart.value[0].format(userStore.dateFormat),
+              DateStart.value[1].format(userStore.dateFormat)
+            ]
           }
           daterangeObj2.ETD = obj
         }
@@ -102,10 +112,15 @@ const defaultDate = () => {
             dayjs(searchTableQeuryTracking.value.eta_end)
           ]
           daterangeObj.ETA =
-            DateEnd.value[0].format('MMM/DD/YYYY') + ' To ' + DateEnd.value[1].format('MMM/DD/YYYY')
+            DateEnd.value[0].format(userStore.dateFormat) +
+            ' To ' +
+            DateEnd.value[1].format(userStore.dateFormat)
           const obj = {
             title: 'ETA',
-            data: [DateEnd.value[0].format('MM/DD/YYYY'), DateEnd.value[1].format('MM/DD/YYYY')]
+            data: [
+              DateEnd.value[0].format(userStore.dateFormat),
+              DateEnd.value[1].format(userStore.dateFormat)
+            ]
           }
           daterangeObj2.ETA = obj
         }
@@ -115,14 +130,14 @@ const defaultDate = () => {
             dayjs(searchTableQeuryTracking.value.created_time_end)
           ]
           daterangeObj['Creation Time'] =
-            DateCreation.value[0].format('MMM/DD/YYYY') +
+            DateCreation.value[0].format(userStore.dateFormat) +
             ' To ' +
-            DateCreation.value[1].format('MMM/DD/YYYY')
+            DateCreation.value[1].format(userStore.dateFormat)
           const obj = {
             title: 'Creation Time',
             data: [
-              DateCreation.value[0].format('MM/DD/YYYY'),
-              DateCreation.value[1].format('MM/DD/YYYY')
+              DateCreation.value[0].format(userStore.dateFormat),
+              DateCreation.value[1].format(userStore.dateFormat)
             ]
           }
           daterangeObj2['Creation Time'] = obj
@@ -136,23 +151,32 @@ const defaultDate = () => {
       if (data.eta_start) {
         DateEnd.value = [dayjs(data.eta_start), dayjs(data.eta_end)]
         daterangeObj.ETA =
-          DateEnd.value[0].format('MMM/DD/YYYY') + ' To ' + DateEnd.value[1].format('MMM/DD/YYYY')
+          DateEnd.value[0].format(userStore.dateFormat) +
+          ' To ' +
+          DateEnd.value[1].format(userStore.dateFormat)
         const obj = {
           title: 'ETA',
-          data: [DateEnd.value[0].format('MM/DD/YYYY'), DateEnd.value[1].format('MM/DD/YYYY')]
+          data: [
+            DateEnd.value[0].format(userStore.dateFormat),
+            DateEnd.value[1].format(userStore.dateFormat)
+          ]
         }
+
         daterangeObj2.ETA = obj
         emit('defaultDate', daterangeObj, daterangeObj2, searchTableQeuryTracking.value)
       }
       if (data.etd_start) {
         DateStart.value = [dayjs(data.etd_start), dayjs(data.etd_end)]
         daterangeObj.ETD =
-          DateStart.value[0].format('MMM/DD/YYYY') +
+          DateStart.value[0].format(userStore.dateFormat) +
           ' To ' +
-          DateStart.value[1].format('MMM/DD/YYYY')
+          DateStart.value[1].format(userStore.dateFormat)
         const obj = {
           title: 'ETD',
-          data: [DateStart.value[0].format('MM/DD/YYYY'), DateStart.value[1].format('MM/DD/YYYY')]
+          data: [
+            DateStart.value[0].format(userStore.dateFormat),
+            DateStart.value[1].format(userStore.dateFormat)
+          ]
         }
         daterangeObj2.ETD = obj
         emit('defaultDate', daterangeObj, daterangeObj2, searchTableQeuryTracking.value)
@@ -162,10 +186,15 @@ const defaultDate = () => {
     if (sessionStorage.getItem('searchTableQeury') == null) {
       DateStart.value = [dayjs().subtract(2, 'month').startOf('month'), dayjs().add(1, 'month')]
       daterangeObj.ETD =
-        DateStart.value[0].format('MMM/DD/YYYY') + ' To ' + DateStart.value[1].format('MMM/DD/YYYY')
+        DateStart.value[0].format(userStore.dateFormat) +
+        ' To ' +
+        DateStart.value[1].format(userStore.dateFormat)
       const obj = {
         title: 'ETD',
-        data: [DateStart.value[0].format('MM/DD/YYYY'), DateStart.value[1].format('MM/DD/YYYY')]
+        data: [
+          DateStart.value[0].format(userStore.dateFormat),
+          DateStart.value[1].format(userStore.dateFormat)
+        ]
       }
       daterangeObj2.ETD = obj
     } else {
@@ -177,12 +206,15 @@ const defaultDate = () => {
           dayjs(searchTableQeury.value.f_etd_end)
         ]
         daterangeObj.ETD =
-          DateStart.value[0].format('MMM/DD/YYYY') +
+          DateStart.value[0].format(userStore.dateFormat) +
           ' To ' +
-          DateStart.value[1].format('MMM/DD/YYYY')
+          DateStart.value[1].format(userStore.dateFormat)
         const obj = {
           title: 'ETD',
-          data: [DateStart.value[0].format('MM/DD/YYYY'), DateStart.value[1].format('MM/DD/YYYY')]
+          data: [
+            DateStart.value[0].format(userStore.dateFormat),
+            DateStart.value[1].format(userStore.dateFormat)
+          ]
         }
         daterangeObj2.ETD = obj
       }
@@ -192,10 +224,15 @@ const defaultDate = () => {
           dayjs(searchTableQeury.value.m_eta_end)
         ]
         daterangeObj.ETA =
-          DateEnd.value[0].format('MMM/DD/YYYY') + ' To ' + DateEnd.value[1].format('MMM/DD/YYYY')
+          DateEnd.value[0].format(userStore.dateFormat) +
+          ' To ' +
+          DateEnd.value[1].format(userStore.dateFormat)
         const obj = {
           title: 'ETA',
-          data: [DateEnd.value[0].format('MM/DD/YYYY'), DateEnd.value[1].format('MM/DD/YYYY')]
+          data: [
+            DateEnd.value[0].format(userStore.dateFormat),
+            DateEnd.value[1].format(userStore.dateFormat)
+          ]
         }
         daterangeObj2.ETA = obj
       }
@@ -205,14 +242,14 @@ const defaultDate = () => {
           dayjs(searchTableQeury.value.created_time_end)
         ]
         daterangeObj['Creation Time'] =
-          DateCreation.value[0].format('MMM/DD/YYYY') +
+          DateCreation.value[0].format(userStore.dateFormat) +
           ' To ' +
-          DateCreation.value[1].format('MMM/DD/YYYY')
+          DateCreation.value[1].format(userStore.dateFormat)
         const obj = {
           title: 'Creation Time',
           data: [
-            DateCreation.value[0].format('MM/DD/YYYY'),
-            DateCreation.value[1].format('MM/DD/YYYY')
+            DateCreation.value[0].format(userStore.dateFormat),
+            DateCreation.value[1].format(userStore.dateFormat)
           ]
         }
         daterangeObj2['Creation Time'] = obj
@@ -226,8 +263,8 @@ daterangedata.value = []
 let daterangeObj2: any = {}
 const DateRangeChange = (val: any) => {
   if (val.data != null) {
-    const date1 = moment(String(val.data[0])).format('MMM-DD-YYYY')
-    const date2 = moment(String(val.data[1])).format('MMM-DD-YYYY')
+    const date1 = formatTimezone(String(val.data[0]))
+    const date2 = formatTimezone(String(val.data[1]))
     daterangeObj[val.title] = date1 + ' To ' + date2
     daterangeObj2[val.title] = val
   } else {
@@ -506,4 +543,4 @@ const clearDaterangeObj = () => {
 .Date_type {
   color: var(--color-neutral-2);
 }
-</style>
+</style>

+ 9 - 7
src/components/DateRange/src/components/CalendarDate.vue

@@ -1,8 +1,10 @@
 <script lang="ts" setup>
 import dayjs, { Dayjs } from 'dayjs'
 import { ref, watch } from 'vue'
-import moment from 'moment'
+import { formatTimezone } from '@/utils/tools'
+import { useUserStore } from '@/stores/modules/user'
 
+const userStore = useUserStore()
 // type RangeValue = [Dayjs, Dayjs]
 // const ETDDate = ref<RangeValue>()
 const props = defineProps({
@@ -48,12 +50,12 @@ const ChangeToday = (val: any) => {
   if (val == 'Earliest') {
     // ETDDate.value = [dayjs(), dayjs()]
     ETDDate.value[0] = dayjs()
-    const date1 = moment(String(ETDDate.value[0])).format('MM/DD/YYYY')
+    const date1 = formatTimezone(String(ETDDate.value[0]))
     DateList.value[0] = date1
     daterange(DateList.value[1])
   } else {
     ETDDate.value[1] = dayjs()
-    const date1 = moment(String(ETDDate.value[1])).format('MM/DD/YYYY')
+    const date1 = formatTimezone(String(ETDDate.value[1]))
     DateList.value[1] = date1
     daterange(DateList.value[0])
   }
@@ -69,13 +71,13 @@ const handleCalendarOpen = (date: any) => {
 }
 const Earliest = () => {
   ETDDate.value[0] = dayjs('Oct-05-2009')
-  const date1 = moment(String(ETDDate.value[0])).format('MM/DD/YYYY')
+  const date1 = formatTimezone(String(ETDDate.value[0]))
   DateList.value[0] = date1
   daterange(DateList.value[1])
 }
 const Latest = () => {
   ETDDate.value[1] = dayjs()
-  const date1 = moment(String(ETDDate.value[1])).format('MM/DD/YYYY')
+  const date1 = formatTimezone(String(ETDDate.value[1]))
   DateList.value[1] = date1
   daterange(DateList.value[0])
 }
@@ -116,7 +118,7 @@ const isTwoDate = (date: any) => {
       :disabled="Disabled"
       @change="changeRangeData"
       :placeholder="['Start Time', 'End Time']"
-      format="MMM-DD-YYYY"
+      :format="userStore.dateFormat"
       valueFormat="MM/DD/YYYY"
       @openChange="handleCalendarOpen(ETDDate)"
       @panelChange="handlePanelChange"
@@ -182,4 +184,4 @@ const isTwoDate = (date: any) => {
 .icon_suffix {
   fill: var(--color-neutral-1);
 }
-</style>
+</style>

+ 9 - 7
src/components/DateRange/src/components/QuickCalendarDate.vue

@@ -1,8 +1,10 @@
 <script lang="ts" setup>
 import dayjs from 'dayjs'
 import { ref, watch, computed } from 'vue'
-import moment from 'moment'
+import { formatTimezone } from '@/utils/tools'
+import { useUserStore } from '@/stores/modules/user'
 
+const userStore = useUserStore()
 const props = defineProps({
   CalendarWidth: {
     type: String,
@@ -40,12 +42,12 @@ const ChangeToday = (val: any) => {
   if (val == 'Earliest') {
     // ETDDate.value = [dayjs(), dayjs()]
     ETDDate.value[0] = dayjs()
-    const date1 = moment(String(ETDDate.value[0])).format('MM/DD/YYYY')
+    const date1 = formatTimezone(String(ETDDate.value[0]))
     DateList.value[0] = date1
     daterange(DateList.value[1])
   } else {
     ETDDate.value[1] = dayjs()
-    const date1 = moment(String(ETDDate.value[1])).format('MM/DD/YYYY')
+    const date1 = formatTimezone(String(ETDDate.value[1]))
     DateList.value[1] = date1
     daterange(DateList.value[0])
   }
@@ -61,13 +63,13 @@ const handleCalendarOpen = (date: any) => {
 }
 const Earliest = () => {
   ETDDate.value[0] = dayjs('Oct-05-2009')
-  const date1 = moment(String(ETDDate.value[0])).format('MM/DD/YYYY')
+  const date1 = formatTimezone(String(ETDDate.value[0]))
   DateList.value[0] = date1
   daterange(DateList.value[1])
 }
 const Latest = () => {
   ETDDate.value[1] = dayjs()
-  const date1 = moment(String(ETDDate.value[1])).format('MM/DD/YYYY')
+  const date1 = formatTimezone(String(ETDDate.value[1]))
   DateList.value[1] = date1
   daterange(DateList.value[0])
 }
@@ -121,7 +123,7 @@ const placeholder = computed(() => {
       :disabled="props.isDisabled"
       @change="changeRangeData"
       :placeholder="placeholder"
-      format="MMM-DD-YYYY"
+      :format="userStore.dateFormat"
       valueFormat="MM/DD/YYYY"
       @openChange="handleCalendarOpen(ETDDate)"
       @panelChange="handlePanelChange"
@@ -181,4 +183,4 @@ const placeholder = computed(() => {
 :deep(.ant-picker-range) {
   padding: 4px 8px;
 }
-</style>
+</style>

+ 1 - 0
src/components/NotificationMessageCard/index.ts

@@ -0,0 +1 @@
+export { default } from './src/NotificationMessageCard.vue'

+ 21 - 0
src/components/NotificationMessageCard/src/NotificationMessageCard.vue

@@ -0,0 +1,21 @@
+<script setup lang="ts">
+import EventCard from './components/EventCard.vue'
+import PasswordCard from './components/PasswordCard.vue'
+import FeatureUpdateCard from './components/FeatureUpdateCard.vue'
+
+const props = defineProps<{
+  data: any
+}>()
+</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>
+</template>
+
+<style lang="scss" scoped></style>

+ 349 - 0
src/components/NotificationMessageCard/src/components/EventCard.vue

@@ -0,0 +1,349 @@
+<script setup lang="ts">
+import { transportationMode } from '@/components/transportationMode'
+import { useRouter } from 'vue-router'
+import { getTimezone } from '@/utils/tools'
+import dayjs from 'dayjs'
+
+const router = useRouter()
+
+interface EventCardPropsData {
+  type: string // 'milestone' | 'container' | 'delay' | 'change'
+  numericRecords?: number // 多条记录数 (Daily Update消息)
+  isRead?: boolean // 是否已读 (true 已读,false 未读)
+  title?: string // Milestone Update
+  mode?: string // 运输方式
+  no: string // HBOL: SHJN2301234
+  tag: string // tag  Booking Confirmed
+  location: string
+  timezone?: string // 时区
+  time: string
+  timeLabel: string
+  previous?: Array<string>
+  info?: {
+    route?: []
+    etdOrdeparturNum?: number
+    etaOrarrivalNum?: number
+    time: string
+    timeLabel: string
+    delayTimeTip: string
+    timezone?: string // 时区
+    leg?: []
+  }
+}
+
+const props = defineProps<{
+  data: EventCardPropsData
+}>()
+
+const handleSeeAll = (data: any) => {
+  router.push({
+    name: 'System Message Detail',
+    query: {
+      frequency_type: data.frequency_type
+    }
+  })
+}
+</script>
+
+<template>
+  <div class="notification-card" :class="{ 'is-read': data.isRead }">
+    <div class="header" v-if="data.title">
+      <div class="status-icon"></div>
+      <div class="title">{{ data.title }}</div>
+    </div>
+    <div class="content">
+      <div
+        class="more-tips"
+        v-if="(data.type === 'milestone' || 'container') && data.numericRecords"
+      >
+        <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>
+          <span v-if="data.info.etdOrdeparturNum"
+            >Departure Delay ({{ data.info.etdOrdeparturNum }})</span
+          >
+          <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>
+        </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="base-info">
+        <!-- 除了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>
+        </div>
+        <!-- container类型显示图标 -->
+        <div v-else>
+          <span class="font_family icon-icon_container__filled_b"></span>
+          <span class="no">Container: {{ data.no }}</span>
+        </div>
+        <div class="tag" :class="{ delay: data.type === 'delay', change: data.type === 'change' }">
+          <span class="dot"></span>
+          <span class="text">{{ data.tag }}</span>
+        </div>
+      </div>
+      <div class="route" v-if="data?.info?.route?.length > 0">
+        <span class="font_family icon-icon_route_b"></span>
+        <span>Route:&nbsp;</span>
+        <template v-for="(item, index) in data.info.route" :key="index">
+          <span>{{ item }}</span
+          ><span style="margin: 0 3px" v-if="index !== data.info.route.length - 1">→</span>
+        </template>
+      </div>
+      <!-- change多程情况中的Leg-->
+      <div class="location" v-if="data?.info?.leg?.length > 0">
+        <span class="font_family icon-icon_location_b"></span>
+        <span>Current Leg:&nbsp;</span>
+        <template v-for="(item, index) in data.info.leg" :key="index">
+          <span>{{ item }}</span
+          ><span style="margin: 0 3px" v-if="index !== data.info.leg.length - 1">→</span>
+        </template>
+      </div>
+      <div class="location" v-if="data.location">
+        <span class="font_family icon-icon_location_b"></span>
+        <span>{{ data.location }}</span>
+      </div>
+      <div
+        :class="{ 'delay-time': data.type === 'delay', 'change-time': data.type === 'change' }"
+        v-if="(data.type === 'delay' || 'change') && data.info?.time"
+      >
+        <span
+          v-if="data.type === 'delay'"
+          style="margin-right: 5px"
+          class="font_family icon-icon_delay_b"
+        ></span>
+        <span v-else class="font_family icon-icon_time_b"></span>
+        <span style="margin-right: 2px">{{ data.info.timeLabel }}&nbsp;</span>
+        <span style="margin-right: 3px">{{
+          dayjs(data.info.time).format('MMM DD, YYYY hh:mm')
+        }}</span>
+        <span>{{ getTimezone(data.info.timezone) }}</span>
+      </div>
+      <!-- <div class="change-time" v-if="data.type === 'change' && data.info?.time">
+        <span style="margin-left: 1px" class="font_family icon-icon_time_b"></span>
+        <span>{{ data.info?.time }}</span>
+      </div> -->
+      <div class="time" :class="{ grey: data.type === 'delay' || data.type === 'change' }">
+        <span class="font_family icon-icon_time_b"></span>
+        <span style="margin-right: 2px">{{ data.timeLabel }}</span>
+        <span style="margin-right: 3px">{{ dayjs(data.time).format('MMM DD, YYYY hh:mm') }}</span>
+        <span>{{ getTimezone(data.timezone) }}</span>
+      </div>
+      <div class="previous" v-if="data.previous">
+        <span class="previous-icon"></span>
+        <span>{{ data.previous }}</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.notification-card {
+  // max-width: 640px;
+  margin-bottom: 16px;
+  &.is-read {
+    & > .header {
+      .status-icon {
+        background-color: var(--color-border);
+      }
+      .title {
+        color: #b5b9bf;
+      }
+    }
+  }
+  .header {
+    display: flex;
+    align-items: center;
+    margin-bottom: 8px;
+    .status-icon {
+      width: 8px;
+      height: 8px;
+      border-radius: 50%;
+      background-color: var(--color-theme);
+      margin-right: 10px;
+    }
+    .title {
+      font-weight: 700;
+    }
+  }
+  .content {
+    padding: 16px 8px;
+    padding-top: 0;
+    background-color: var(--color-header-bg);
+    border-radius: 6px;
+    .more-tips {
+      display: flex;
+      justify-content: space-between;
+      padding: 8px 0;
+      border-bottom: 1px dashed var(--color-border);
+      font-size: 12px;
+      line-height: 24px;
+      .see-all-icon {
+        width: 68px;
+        height: 24px;
+        :deep(span) {
+          color: var(--color-theme);
+        }
+      }
+    }
+    .base-info {
+      display: flex;
+      padding-top: 16px;
+      .no {
+        margin-left: 8px;
+        font-weight: 700;
+        line-height: 18px;
+      }
+      .tag {
+        display: flex;
+        align-items: center;
+        margin-left: 4px;
+        padding-left: 8px;
+        padding-right: 6px;
+        background-color: #e6f1eb;
+        border-radius: 3px;
+        &.delay {
+          background-color: #f7e7e9;
+          .dot {
+            background-color: #c9353f;
+          }
+          .text {
+            color: #c9353f;
+          }
+        }
+        &.change {
+          background-color: #f5f2e6;
+          .dot {
+            background-color: #edb82f;
+          }
+          .text {
+            margin-top: 0;
+            color: #edb82f;
+          }
+        }
+        .dot {
+          width: 5px;
+          height: 5px;
+          border-radius: 50%;
+          background-color: #5bb462;
+          margin-right: 4px;
+        }
+        .text {
+          margin-top: 2px;
+          font-size: 10px;
+          font-weight: 600;
+          color: #5bb462;
+          line-height: 10px;
+          vertical-align: middle;
+        }
+      }
+    }
+    .route,
+    .location,
+    .change-time,
+    .delay-time,
+    .time {
+      display: flex;
+      align-items: center;
+      margin-top: 8px;
+      height: 16px;
+      &.grey {
+        span {
+          color: var(--color-neutral-3);
+        }
+      }
+      span {
+        color: var(--color-neutral-2);
+        font-size: 12px;
+        line-height: 16px;
+      }
+      .font_family {
+        font-size: 16px;
+        font-family: 'iconfont';
+        color: var(--color-neutral-2);
+        margin-right: 8px;
+      }
+    }
+    div.delay-time {
+      margin-top: 6px;
+      span,
+      .font_family {
+        color: #c9353f;
+      }
+      span {
+        line-height: 19px;
+      }
+      .font_family {
+        line-height: 12px;
+        margin-left: -1px;
+        margin-right: 6px;
+        font-size: 19px;
+      }
+    }
+    div.change-time {
+      span,
+      .font_family {
+        color: #edb82f;
+      }
+    }
+    .previous {
+      height: 24px;
+      margin-top: 8px;
+      padding-left: 8px;
+      line-height: 24px;
+      background-color: #e1e3e9;
+      border-radius: 6px;
+      span {
+        font-size: 12px;
+      }
+      .previous-icon {
+        display: inline-block;
+        width: 4px;
+        height: 4px;
+        background-color: var(--color-neutral-1);
+        border-radius: 50%;
+        margin-right: 8px;
+        vertical-align: middle;
+      }
+    }
+  }
+}
+</style>

+ 101 - 0
src/components/NotificationMessageCard/src/components/FeatureUpdateCard.vue

@@ -0,0 +1,101 @@
+<script setup lang="ts">
+interface FeatureUpdateCardPropsData {
+  title: string
+  header: string
+  content: string
+  isRead: boolean
+  imgSrc: string
+}
+
+const props = defineProps<{
+  data: FeatureUpdateCardPropsData
+}>()
+</script>
+
+<template>
+  <div>
+    <div class="feature-update" :class="{ 'is-read': data.isRead }">
+      <div class="header">
+        <div class="status-icon"></div>
+        <div class="title">{{ data.title }}</div>
+      </div>
+      <div class="card-content">
+        <div class="title">
+          <!-- <span class="font_family icon-icon_password_b"></span> -->
+          <img style="margin-right: 5px" src="../images/icon_publish.png" alt="" />
+          <span class="gradient-text">{{ data.header }}</span>
+        </div>
+        <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="change-btn" style="text-align: center">
+          <el-button class="el-button--main" style="height: 40px; padding: 0 32px">
+            View more</el-button
+          >
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.feature-update {
+  margin-bottom: 16px;
+  &.is-read {
+    & > .header {
+      .status-icon {
+        background-color: var(--color-border);
+      }
+      .title {
+        color: #b5b9bf;
+      }
+    }
+  }
+  .header {
+    display: flex;
+    align-items: center;
+    margin-bottom: 8px;
+    .status-icon {
+      width: 8px;
+      height: 8px;
+      border-radius: 50%;
+      background-color: var(--color-theme);
+      margin-right: 10px;
+    }
+    .title {
+      font-weight: 700;
+    }
+  }
+  .card-content {
+    padding: 16px 16px 24px 8px;
+    background: linear-gradient(137deg, #fff4eb 12.41%, #f0f3ff 52.63%, #e0f7f9 93.28%);
+    border-radius: 12px;
+    .title {
+      img {
+        vertical-align: middle;
+      }
+      span {
+        vertical-align: middle;
+        display: inline-block;
+        font-weight: 700;
+      }
+      .gradient-text {
+        background: linear-gradient(90deg, #dc6c6d 0%, #7959c8 46%, #ed6d00 100%);
+        background-clip: text;
+        -webkit-background-clip: text;
+        color: transparent;
+        font-size: 14px;
+        font-weight: bold;
+      }
+      .font_family {
+        margin-right: 8px;
+      }
+    }
+    .content-text {
+      margin: 8px 0 0px 24px;
+    }
+  }
+}
+</style>

+ 87 - 0
src/components/NotificationMessageCard/src/components/PasswordCard.vue

@@ -0,0 +1,87 @@
+<script setup lang="ts">
+interface PasswordCardPropsData {
+  title: string
+  isExpiration: boolean
+  tips: string
+  isRead: boolean
+  content: string
+}
+const props = defineProps<{
+  data: PasswordCardPropsData
+}>()
+</script>
+
+<template>
+  <div class="password-notifications" :class="{ 'is-read': data.isRead }">
+    <div class="header" v-if="data.title">
+      <div class="status-icon"></div>
+      <div class="title">{{ data.title }}</div>
+    </div>
+    <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>
+      </div>
+      <div class="details">
+        {{ data.content }}
+      </div>
+      <div class="change-btn" style="text-align: center">
+        <el-button 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>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.password-notifications {
+  margin-bottom: 16px;
+  &.is-read {
+    & > .header {
+      .status-icon {
+        background-color: var(--color-border);
+      }
+      .title {
+        color: #b5b9bf;
+      }
+    }
+  }
+  .header {
+    display: flex;
+    align-items: center;
+    margin-bottom: 8px;
+    .status-icon {
+      width: 8px;
+      height: 8px;
+      border-radius: 50%;
+      background-color: var(--color-theme);
+      margin-right: 10px;
+    }
+    .title {
+      font-weight: 700;
+    }
+  }
+  .card-content {
+    padding: 16px 8px 24px;
+    background: linear-gradient(180deg, #ffe294 0%, #f6f8fa 100%);
+    border-radius: 12px;
+    &.is-expired {
+      background: linear-gradient(182deg, #ef99a0 2.2%, #f6f8fa 98.77%);
+    }
+    .title {
+      span {
+        vertical-align: middle;
+        font-weight: 700;
+      }
+      .font_family {
+        margin-right: 8px;
+      }
+    }
+    .details {
+      margin: 8px 0 16px 24px;
+    }
+  }
+}
+</style>

二进制
src/components/NotificationMessageCard/src/images/icon_publish.png


二进制
src/components/NotificationMessageCard/src/images/test.png


+ 4 - 1
src/components/ScoringGrade/src/ScoringGrade.vue

@@ -14,6 +14,9 @@ import happyPng from '../image/score_happy.png'
 import happyPng2 from '../image/happy_2.png'
 import normalPng from '../image/score_normal.png'
 import submitsucessful from '../image/submit_successful.png'
+import { useUserStore } from '@/stores/modules/user'
+
+const userStore = useUserStore()
 
 // const isShow = ref(true)
 const visible = ref(false)
@@ -202,7 +205,7 @@ const changeSmileRadio = (val: any) => {
 }
 // 提交details
 const submitDetails = (val: any) => {
-  const username = localStorage.getItem('account') ? localStorage.getItem('account') : ''
+  const username = userStore.userName
   if (angryCheckbox.value.length) {
     $api
       .scoringgrade({

+ 2 - 6
src/components/ShipmentStatus/src/ShipmentStatus.vue

@@ -1,6 +1,6 @@
 <script setup lang="ts">
-import dayjs from 'dayjs'
 import { useOverflow } from '@/hooks/useOverflow'
+import { formatTimezone } from '@/utils/tools'
 
 const props = defineProps({
   data: Object
@@ -34,10 +34,6 @@ const getDateHeight = (index: number) => {
   return 42 + 26 * index
 }
 
-const formatDate = (date: string) => {
-  return date ? dayjs(date).format('MMM-DD-YYYY') : '--'
-}
-
 const pathRef = ref()
 
 const { isOverflow: isPathOverflow } = useOverflow(pathRef, props)
@@ -76,7 +72,7 @@ const { isOverflow: isPathOverflow } = useOverflow(pathRef, props)
             <div class="step-dot"></div>
             <div class="label">{{ dateItem.label }}</div>
             <div class="divider"></div>
-            <div class="date">{{ formatDate(dateItem.date) }}</div>
+            <div class="date">{{ formatTimezone(dateItem.date) }}</div>
           </div>
         </div>
       </div>

+ 46 - 0
src/components/TableEmpty/TableEmpty.vue

@@ -0,0 +1,46 @@
+<script setup lang="ts">
+import { computed } from 'vue'
+import lightPng from './image/default_notification_setting.png'
+import darkPng from './image/default_notification_setting.png'
+import { useThemeStore } from '@/stores/modules/theme'
+
+const themeStore = useThemeStore()
+// 判断当前系统主题模式
+const emptyImg = computed(() => {
+  return themeStore.theme === 'dark' ? darkPng : lightPng
+})
+
+const props = defineProps({
+  EmptyTitle: String
+})
+</script>
+
+<template>
+  <div class="v-empty">
+    <div class="empty-img">
+      <img :src="emptyImg" alt="" />
+    </div>
+    <p class="title">
+      <slot name="title">{{ props.EmptyTitle }}</slot>
+    </p>
+    <div>
+      <slot name="suggestion"></slot>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.v-empty {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  .empty-img {
+    margin-bottom: 16px;
+  }
+  .title {
+    margin-bottom: 8px;
+    font-weight: 400;
+    color: var(--color-neutral-2);
+  }
+}
+</style>

二进制
src/components/TableEmpty/image/default_notification_setting.png


+ 1 - 0
src/components/TableEmpty/index.ts

@@ -0,0 +1 @@
+export { default } from './src/TableEmpty.vue'

+ 29 - 3
src/router/index.ts

@@ -87,6 +87,32 @@ const router = createRouter({
           path: '/Operationlog',
           name: 'Operationlog',
           component: () => import('../views/OperationLog')
+        },
+        {
+          path: '/system-message',
+          name: 'System Message',
+          component: () => import('../views/SystemMessage')
+        },
+        {
+          path: '/system-message-detail',
+          name: 'System Message Detail',
+          meta: {
+            breadName: 'Detail'
+          },
+          component: () => import('../views/SystemMessage/src/components/SystemMessageDetail.vue')
+        },
+        {
+          path: '/SystemSettings',
+          name: 'Monitoring Settings',
+          component: () => import('../views/SystemSettings')
+        },
+        {
+          path: '/SystemSettings/createnewrule',
+          name: 'Create New Rule',
+          component: () => import('../views/SystemSettings/src/components/CreateNewrule'),
+          meta: {
+            activeMenu: '/SystemSettings'
+          }
         }
       ]
     }
@@ -96,10 +122,10 @@ const router = createRouter({
 // * 路由拦截 beforeEach
 router.beforeEach(async (to, from, next) => {
   useBreadCrumb().setRouteList(to)
+  const userStore = useUserStore()
   // 如果手动跳转登录页,清除登录信息
   if (to.path === '/login') {
-    if (localStorage.getItem('username')) {
-      const userStore = useUserStore()
+    if (userStore.userInfo?.uname) {
       await userStore.logout()
     }
     sessionStorage.removeItem('trackingTablePageInfo')
@@ -109,7 +135,7 @@ router.beforeEach(async (to, from, next) => {
   // 未登录白名单
   const whiteList = ['/login', '/public-tracking', '/public-tracking/detail', '/reset-password']
   // 判断是否登录
-  if (!whiteList.includes(to.path) && !localStorage.getItem('username')) {
+  if (!whiteList.includes(to.path) && !userStore.userInfo?.uname) {
     const userStore = useUserStore()
     await userStore.logout()
     if (whiteList.includes(from.path)) {

+ 22 - 2
src/stores/modules/breadCrumb.ts

@@ -9,7 +9,14 @@ interface BreadCrumb {
   routeList: Route[]
 }
 // 需要添加多级菜单的页面,值为route的name
-const whiteList = ['Booking Detail', 'Tracking Detail', 'Add VGM', 'Public Tracking Detail']
+const whiteList = [
+  'Booking Detail',
+  'Tracking Detail',
+  'Add VGM',
+  'Public Tracking Detail',
+  'Create New Rule',
+  'System Message Detail'
+]
 
 export const useBreadCrumb = defineStore('breadCrumb', {
   state: (): BreadCrumb => ({
@@ -34,9 +41,22 @@ export const useBreadCrumb = defineStore('breadCrumb', {
             query: toRoute.query
           }
         ]
+      } else if (toRoute.name === 'System Message Detail') {
+        this.routeList = [
+          {
+            label: 'System Message',
+            path: '/system-message',
+            query: ''
+          },
+          {
+            label: 'System Message Detail',
+            path: '/system-message/detail',
+            query: toRoute.query
+          }
+        ]
       } else if (toRoute.name && whiteList.includes(toRoute.name)) {
         this.routeList.push({
-          label: toRoute.name,
+          label: toRoute?.meta?.breadName || toRoute.name,
           path: toRoute.path,
           query: toRoute.query
         })

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

@@ -1,33 +1,90 @@
 import { defineStore } from 'pinia'
 import { useVisitedRowState } from './visitedRow'
+import dayjs from 'dayjs'
 
+interface UserInfo {
+  uname: string
+  first_name: string
+  last_name: string
+  user_type: string
+  email: string
+  expire_day: number
+  date_format: string
+  numbers_format: string
+  PASSWORD_CHANGE_CYCLE: number // 密码修改周期(多少天需要改一次)
+  last_pwd_change: string // 上次密码修改时间
+}
 interface UserState {
-  username: string
+  userInfo: UserInfo
   isFirstLogin: boolean
 }
+
+/**
+ * 根据用户地区判断日期格式
+ * @returns {string} - 返回日期格式
+ */
+const getDateFormat = () => {
+  const userLanguage = navigator.language || 'en-US' // 获取浏览器的语言设置
+  // 判断用户地区
+  if (userLanguage === 'en-US') {
+    return 'MM/DD/YYYY' // 美国使用 MM/DD/YYYY 格式
+  } else if (
+    userLanguage.startsWith('de') ||
+    userLanguage.startsWith('fr') ||
+    userLanguage.startsWith('it') ||
+    userLanguage.startsWith('es') ||
+    userLanguage.startsWith('pl') ||
+    userLanguage.startsWith('nl') ||
+    userLanguage.startsWith('pt') ||
+    userLanguage.startsWith('se')
+  ) {
+    return 'DD/MM/YYYY' // 其他欧洲国家(例:德语、法语、西班牙语等)使用 DD/MM/YYYY 格式
+  } else if (['zh-CN', 'ja-JP', 'ko-KR'].includes(userLanguage)) {
+    return 'YYYY-MM-DD' // 东亚国家(如简体中文、日语、韩语)使用 YYYY-MM-DD 格式
+  } else {
+    return 'DD/MM/YYYY' // 其他地区默认 DD/MM/YYYY 格式
+  }
+}
+
 export const useUserStore = defineStore('user', {
   state: (): UserState => ({
-    username: localStorage.getItem('username') || '',
+    userInfo: JSON.parse(localStorage.getItem('userInfo') || '{}'),
     isFirstLogin: localStorage.getItem('isFirstLogin')
       ? JSON.parse(localStorage.getItem('isFirstLogin'))
       : false
   }),
-  getters: {},
+  getters: {
+    userName(state) {
+      if (state.userInfo.first_name && state.userInfo.last_name) {
+        return `${state.userInfo.first_name} ${state.userInfo.last_name}`
+      } else {
+        return state.userInfo.uname || ''
+      }
+    },
+    dateFormat(state) {
+      return state.userInfo.date_format || getDateFormat()
+    },
+    expireDay(state) {
+      const userInfo = state.userInfo
+      return userInfo.PASSWORD_CHANGE_CYCLE - dayjs().diff(dayjs(userInfo.last_pwd_change), 'day')
+    }
+  },
   actions: {
-    setUsername(username: any, isFirstLogin?: boolean) {
-      localStorage.setItem('username', username)
-      this.username = username
+    setUserInfo(userInfo: any, isFirstLogin?: boolean) {
+      localStorage.setItem('userInfo', JSON.stringify(userInfo))
+      this.userInfo = userInfo
       if (isFirstLogin !== undefined) {
         localStorage.setItem('isFirstLogin', JSON.stringify(isFirstLogin))
         this.isFirstLogin = isFirstLogin
       }
     },
+
     async logout(isNeedLogout: boolean = true) {
       if (isNeedLogout) {
         await $api.logout().then(() => {})
       }
-      localStorage.removeItem('username')
-      this.username = ''
+      localStorage.removeItem('userInfo')
+      this.userInfo = {}
       if (localStorage.getItem('isFirstLogin')) {
         localStorage.removeItem('isFirstLogin')
       }

+ 21 - 17
src/styles/elementui.scss

@@ -49,7 +49,7 @@ button.el-button.el-button--text {
 
 .el-button--main.is-plain {
   background-color: var(--color-white);
-  border: 1px solid var(--color-border);
+  border: 1px solid var(--color-theme);
   span {
     color: var(--color-theme);
   }
@@ -75,19 +75,15 @@ button.el-button--main {
     fill: var(--color-white);
   }
 }
-
-button.el-button.el-button--pain-theme {
-  border: 1px solid var(--color-el-btn-pain-theme-border);
-  background-color: var(--color-el-btn-pain-theme-bg);
-
-  fill: var(--color-el-btn-pain-theme-text);
-  color: var(--color-el-btn-pain-theme-text);
+button.el-button--main.is-disabled {
+  border: none;
+  background-color: var(--color-btn-main-bg-disabled);
   span {
-    color: var(--color-el-btn-pain-theme-text);
+    color: var(--color-white);
   }
+  fill: var(--color-white);
   &:hover {
-    border-color: var(--color-el-btn-pain-theme-border);
-    background-color: var(--color-el-btn-pain-theme-bg-hover);
+    background-color: var(--color-btn-main-bg-disabled);
     color: var(--color-white);
     fill: var(--color-white);
   }
@@ -261,6 +257,7 @@ label.el-radio {
     background-color: var(--color-theme);
     border-color: var(--color-theme);
   }
+
   .el-radio__inner {
     height: 16px;
     width: 16px;
@@ -443,11 +440,11 @@ html.dark .el-checkbox.el-checkbox--large span.el-checkbox__inner::after {
   width: 5px; /* 打勾图标宽度 */
   height: 10px; /* 打勾图标高度 */
 }
-div .el-popper__arrow,
-div .el-popper__arrow:before {
-  // height: 0;
-  // width: 0;
-}
+// div .el-popper__arrow,
+// div .el-popper__arrow:before {
+// height: 0;
+// width: 0;
+// }
 .el-popper.is-dark,
 div.el-popper.is-dark > .el-popper__arrow:before {
   background-color: var(--color-el-popper-bg);
@@ -518,7 +515,6 @@ div .el-select-dropdown__item.is-hovering {
 .el-select-dropdown__item {
   border-radius: var(--border-radius-6);
   margin: 0 8px;
-
   margin-bottom: 4px;
   &:last-child {
     margin-bottom: 0;
@@ -745,6 +741,11 @@ div .el-space {
   flex-wrap: wrap;
   margin: 3px 0 0 0;
 }
+div.element-loading {
+  .el-loading-text {
+    color: var(--color-theme);
+  }
+}
 div .el-loading-spinner .path {
   stroke: var(--color-theme);
 }
@@ -782,3 +783,6 @@ div .DaterangeClass {
   border-color: var(--management-bg-color) !important;
   border-radius: 12px !important;
 }
+div .el-radio__label {
+  width: 100%;
+}

+ 44 - 4
src/styles/icons/iconfont.css

@@ -1,9 +1,9 @@
 @font-face {
   font-family: "font_family"; /* Project id 4672385 */
-  src: url('iconfont.woff2?t=1736324058852') format('woff2'),
-       url('iconfont.woff?t=1736324058852') format('woff'),
-       url('iconfont.ttf?t=1736324058852') format('truetype'),
-       url('iconfont.svg?t=1736324058852#font_family') format('svg');
+  src: url('iconfont.woff2?t=1740548496100') format('woff2'),
+       url('iconfont.woff?t=1740548496100') format('woff'),
+       url('iconfont.ttf?t=1740548496100') format('truetype'),
+       url('iconfont.svg?t=1740548496100#font_family') format('svg');
 }
 
 .font_family {
@@ -14,6 +14,46 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.icon-icon_collapse_b:before {
+  content: "\e707";
+}
+
+.icon-icon_cancel_b1:before {
+  content: "\e706";
+}
+
+.icon-icon_container__maintenance_b:before {
+  content: "\e705";
+}
+
+.icon-icon_up_b:before {
+  content: "\e704";
+}
+
+.icon-icon_route_b:before {
+  content: "\e700";
+}
+
+.icon-icon_model_b:before {
+  content: "\e701";
+}
+
+.icon-icon_delay_b:before {
+  content: "\e702";
+}
+
+.icon-icon_container__filled_b:before {
+  content: "\e703";
+}
+
+.icon-icon_po__sscc_b:before {
+  content: "\e6fe";
+}
+
+.icon-icon_po__request_b:before {
+  content: "\e6ff";
+}
+
 .icon-icon_currentlink_b:before {
   content: "\e6fc";
 }

文件差异内容过多而无法显示
+ 0 - 0
src/styles/icons/iconfont.js


+ 20 - 0
src/styles/icons/iconfont.svg

@@ -14,6 +14,26 @@
     />
       <missing-glyph />
       
+      <glyph glyph-name="icon_collapse_b" unicode="&#59143;" d="M199.111111 726.300444a91.022222 91.022222 0 0 1-91.022222-91.022222v-502.556444a91.022222 91.022222 0 0 1 91.022222-91.022222h625.777778a91.022222 91.022222 0 0 1 91.022222 91.022222V635.278222a91.022222 91.022222 0 0 1-91.022222 91.022222h-625.777778z m-22.755555-91.022222c0 12.515556 10.24 22.755556 22.755555 22.755556h217.144889v-548.067556H199.111111a22.755556 22.755556 0 0 0-22.755555 22.755556V635.278222z m308.167111-525.312V658.033778H824.888889c12.515556 0 22.755556-10.24 22.755555-22.755556v-502.556444a22.755556 22.755556 0 0 0-22.755555-22.755556H484.522667z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_cancel_b1" unicode="&#59142;" d="M235.690667 704c0 12.515556 10.24 22.755556 22.755555 22.755556h492.942222c12.515556 0 22.755556-10.24 22.755556-22.755556v-407.324444h68.266667v407.324444a91.022222 91.022222 0 0 1-91.022223 91.022222H258.446222a91.022222 91.022222 0 0 1-91.022222-91.022222v-625.777778a91.022222 91.022222 0 0 1 91.022222-91.022222h246.499556v68.266667H258.446222a22.755556 22.755556 0 0 0-22.755555 22.755555v625.777778z m380.643555-442.424889l95.971556-96.028444 95.971555 95.971555 48.241778-48.241778-95.914667-95.971555 95.971556-95.971556-48.298667-48.241777-95.971555 95.914666-95.971556-95.971555-48.298666 48.298666 96.028444 95.971556-96.028444 95.971555 48.298666 48.298667z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_container__maintenance_b" unicode="&#59141;" d="M416.448 697.664a32 32 0 0 1-6.848-0.704l-302.4-59.584a32 32 0 0 1-25.856-31.36v-445.76a32 32 0 0 1 25.856-31.424l302.4-59.584a32 32 0 0 1 10.56-0.512l228.096 22.976-6.4 63.68-193.408-19.52V630.272l528.896-53.248v-107.776h64V605.952a32 32 0 0 1-28.8 31.872l-592.384 59.648a32.128 32.128 0 0 1-3.712 0.192zM283.712 606.912l100.736 19.84v-487.296l-100.736 19.84V358.144h31.872v51.2h-31.872V606.912z m-51.2-437.568l-87.168 17.216V579.648l87.168 17.152v-187.456H198.4v-51.2h34.112v-188.8z m517.312 247.488a28.8 28.8 0 0 0 24.96 14.4h167.68c10.24 0 19.84-5.44 24.96-14.4l83.84-145.216c5.12-8.96 5.12-19.84 0-28.8l-83.84-145.216a28.8 28.8 0 0 0-24.96-14.4h-167.68a28.8 28.8 0 0 0-24.96 14.4l-83.84 145.28a28.8 28.8 0 0 0 0 28.8l83.84 145.152z m41.6-43.2l-67.2-116.416 67.2-116.416h134.4l67.2 116.48-67.2 116.352h-134.4z m67.2-79.104a37.312 37.312 0 1 1 0-74.688 37.312 37.312 0 0 1 0 74.688z m-94.912-37.376a94.976 94.976 0 1 0 189.888 0 94.976 94.976 0 0 0-189.888 0z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_up_b" unicode="&#59140;" d="M503.488 519.104a32 32 0 0 0 44.992 0l227.072-224.64a32 32 0 0 0-22.528-54.784H299.008A32 32 0 0 0 276.48 294.4l227.008 224.704z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_route_b" unicode="&#59136;" d="M428.544 674.816a131.008 131.008 0 1 0-0.768-76.8H332.672a89.024 89.024 0 0 1 0-178.112h387.2a165.312 165.312 0 0 0 0-330.56h-43.072a130.944 130.944 0 1 0 3.2 76.8h39.872a88.512 88.512 0 1 1 0 176.96h-387.2a165.824 165.824 0 1 0 0 331.712h95.872z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_model_b" unicode="&#59137;" d="M540.096 858.112A28.8 28.8 0 0 1 515.904 858.24L80 657.6a28.8 28.8 0 0 1-14.272-37.76v-483.264a28.8 28.8 0 0 1 16.64-26.112l428.928-200.32a28.8 28.8 0 0 1 24.768-0.32l435.968 200.576a28.8 28.8 0 0 1 16.704 26.176V631.424a28.8 28.8 0 0 1-18.88 27.072L540.096 858.24z m12.8-445.76l378.24 174.08v-431.424l-378.24-174.08v431.36z m-429.568 172.8l371.84-172.8v-431.104l-371.84 173.632V585.152z m767.872 46.336L524.16 462.72 161.792 631.808l366.08 168.448 363.328-168.768z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_delay_b" unicode="&#59138;" d="M489.984 631.182222a56.888889 56.888889 0 0 0 96.142222-0.227555l288.483556-458.126223a56.888889 56.888889 0 0 0-48.128-87.210666H247.068444A56.888889 56.888889 0 0 0 199.111111 172.942222l290.929778 458.183111z m48.014222-30.492444l-290.929778-458.183111h579.413334L537.998222 600.689778z m27.136-146.773334v-180.906666h-56.888889V453.973333h56.888889z m0-275.000888v47.104h-56.888889v-47.104h56.888889z"  horiz-adv-x="1080" />
+      
+      <glyph glyph-name="icon_container__filled_b" unicode="&#59139;" d="M422.336 113.728V654.272a12.8 12.8 0 0 0 15.104 12.608l526.08-95.36a12.8 12.8 0 0 0 10.496-12.544v-344.064a12.8 12.8 0 0 0-10.432-12.544l-526.08-101.248a12.8 12.8 0 0 0-15.168 12.608z m78.464 57.856l23.168 3.584V596.672l-23.168 3.712v-428.8z m102.976 15.488l23.168 3.2V580.928l-23.168 3.776v-397.632z m102.976 381.44v-365.504l23.232 3.2V565.12l-23.232 3.328z m102.976-349.76l23.232 3.456V549.12l-23.232 3.52v-333.824z m103.04 318.08v-301.952l23.168 3.328v295.04l-23.232 3.584zM396.352 652.352v-537.6a12.8 12.8 0 0 0-14.976-12.672l-134.4 22.976V375.36h73.216v23.232H246.976V644.096l134.656 20.864a12.8 12.8 0 0 0 14.784-12.608z m-172.672-523.392v246.4H141.888v23.232h81.92v241.92l-134.976-20.928a12.8 12.8 0 0 1-10.88-12.672v-442.24a12.8 12.8 0 0 1 10.688-12.608l135.104-23.04z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_po__sscc_b" unicode="&#59134;" d="M216.768 864a32 32 0 0 1-32-32v-896a32 32 0 0 1 32-32h284.8v64h-252.8v832H579.2v-160.064a128 128 0 0 1 128-128h160.064v-176.384h64v204.8 1.92a32 32 0 0 1-9.344 24.32L633.728 854.528A32 32 0 0 1 608.448 864h-391.68z m426.432-112.128l175.424-176H707.2a64 64 0 0 0-64 64v112z m-63.552-640a143.808 143.808 0 1 0 287.616-0.064 143.808 143.808 0 0 0-287.616 0z m143.808 207.744a207.808 207.808 0 1 1 0-415.616 207.808 207.808 0 0 1 0 415.616z m80-207.808a80 80 0 1 0-160 0 80 80 0 0 0 160 0z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_po__request_b" unicode="&#59135;" d="M184.768 832a32 32 0 0 0 32 32h391.68a32 32 0 0 0 25.344-9.344l288.128-288.128a32 32 0 0 0 9.28-24.32l0.064-1.792v-270.784h-64V511.936H707.2a128 128 0 0 0-128 128V800H248.768v-832h309.248v-64H216.768a32 32 0 0 0-32 32V832z m633.856-256.064L643.2 751.872v-112a64 64 0 0 1 64-64h111.424z m-23.104-672v135.808h135.744v64H795.52v135.744h-64v-135.744H595.776v-64h135.68V-96h64z"  horiz-adv-x="1088" />
+      
       <glyph glyph-name="icon_currentlink_b" unicode="&#59132;" d="M102.4 384a409.6 409.6 0 1 0 819.2 0A409.6 409.6 0 0 0 102.4 384zM512 870.4a486.4 486.4 0 1 1 0-972.8A486.4 486.4 0 0 1 512 870.4z m0-742.336a256 256 0 1 1 0 512 256 256 0 0 1 0-512z"  horiz-adv-x="1024" />
       
       <glyph glyph-name="icon_jumplink_b1" unicode="&#59133;" d="M636.416 633.28h-252.16v96h368a48 48 0 0 0 48-48v-368h-96v252.16l-370.56-370.624-67.904 67.84 370.56 370.624z m305.6-610.24h-832v96h832v-96z"  horiz-adv-x="1088" />

二进制
src/styles/icons/iconfont.ttf


二进制
src/styles/icons/iconfont.woff


二进制
src/styles/icons/iconfont.woff2


+ 29 - 0
src/styles/theme.scss

@@ -96,12 +96,14 @@
   --color-btn-main-plain-bg-hover: hsl(26, 100%, 95%);
   // main
   --color-btn-main-bg-hover: #d56200;
+  --color-btn-main-bg-disabled: rgba(237, 109, 0, 0.3);
   // success
   --color-btn-success-bg-hover: #009765;
   // warning
   --color-btn-warning-bg: #e9b227;
   --color-btn-warning-bg-hover: #d2a023;
   // danger
+  --color-btn-danger-bg: #c9353f;
   --color-btn-danger-bg-hover: #b53039;
 
   --color-tag-checked-all: #fff2e8;
@@ -246,10 +248,15 @@
   --color-vxe-table-visited-row-bg: #f2f2f2;
 
   --color-public-tracking-empty-bg: #fff;
+  --color-dot-unchecked: #eeeeee;
+  --color-dot-checked: #ed6d00;
 
+  --color-system-message-nav-bg: #f6f6f6;
   --color-upload-file-bg: #fef8f2;
   --color-upload-file-color: #b5b9bf;
   --color-upload-file-border-bg: #f5b279;
+
+  --color-personal-preference-bg: #f5f7fa;
 }
 
 :root.dark {
@@ -316,6 +323,8 @@
   --color-upload-file-bg: rgba(237, 109, 0, 0.2);
   --color-upload-file-color: rgba(240, 241, 243, 0.7);
   --color-upload-file-border-bg: rgba(237, 109, 0, 0.5);
+
+  --color-personal-preference-bg: #343a43;
   // 滚动条
   --color-scrollbar-thumb: #656f7d;
 
@@ -359,6 +368,9 @@
   .el-input {
     --el-border: #656f7d;
   }
+  .el-radio {
+    --el-radio-input-border: #656f7d;
+  }
   --el-radio-input-border: #656f7d;
   --color-table-header-bg: #34383f;
   --el-input-focus: #2b2f36;
@@ -386,4 +398,21 @@
   --vxe-ui-input-border-color: #656f7d;
   --vxe-ui-table-menu-background-color: #3e454f;
   --color-vxe-table-visited-row-bg: #3c4149;
+
+  button.el-button.el-button--pain-theme {
+    border: 1px solid var(--color-el-btn-pain-theme-border);
+    background-color: var(--color-el-btn-pain-theme-bg);
+
+    fill: var(--color-el-btn-pain-theme-text);
+    color: var(--color-el-btn-pain-theme-text);
+    span {
+      color: var(--color-el-btn-pain-theme-text);
+    }
+    &:hover {
+      border-color: var(--color-el-btn-pain-theme-border);
+      background-color: var(--color-el-btn-pain-theme-bg-hover);
+      color: var(--color-white);
+      fill: var(--color-white);
+    }
+  }
 }

+ 0 - 6
src/utils/axios.ts

@@ -2,10 +2,6 @@ import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse
 import router from '@/router'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { useUserStore } from '@/stores/modules/user'
-// import {
-//   showFullScreenLoading,
-//   tryHideFullScreenLoading
-// } from '@monorepo/shared/utils/serviceLoading'
 
 interface codeMessage {
   [key: number]: string
@@ -156,5 +152,3 @@ class HttpAxios {
 }
 
 export default new HttpAxios({})
-
-// export default new HttpAxios({})

+ 27 - 0
src/utils/timezone.ts

@@ -0,0 +1,27 @@
+export const defaultTimeZone = (zone: any) => {
+  if(zone == 'zh-CN' || zone == 'zh-TW' || zone == 'zh-HK'|| zone == 'en-AU'|| zone == 'ms-MY' || zone == 'id-ID' ) {
+    return 'UTC+08'
+  } else if(zone == 'ja-JP' || zone == 'ko-KR') {
+    return 'UTC+09'
+  } else if(zone == 'en-US') {
+    return 'UTC-08'
+  } else if(zone == 'en-GB' || zone == 'pt-PT') {
+    return 'UTC+00'
+  } else if(zone == 'en-NZ' ) {
+    return 'UTC+12'
+  } else if(zone == 'de-DE' || zone == 'fr-FR' || zone == 'es-ES'|| zone == 'it-IT'|| zone == 'nl-NL'|| zone == 'pl-PL'|| zone == 'sv-SE'|| zone == 'da-DK'|| zone == 'no-NO') {
+    return 'UTC+01'
+  } else if(zone == 'ru-RU' || zone == 'fi-FI' || zone == 'he-IL'|| zone == 'el-GR') {
+    return 'UTC+02'
+  } else if(zone == 'es-MX') {
+    return 'UTC-06'
+  } else if(zone == 'pt-BR') {
+    return 'UTC-03'
+  } else if(zone == 'ar-SA' || zone == 'tr-TR') {
+    return 'UTC+03'
+  } else if(zone == 'hi-IN') {
+    return 'UTC+05:30'
+  } else if(zone == 'th-TH' || zone == 'vi-VN') {
+    return 'UTC+07'
+  }
+}

+ 80 - 10
src/utils/tools.ts

@@ -1,30 +1,44 @@
 import moment from 'moment-timezone'
+import { useUserStore } from '@/stores/modules/user'
 
-export const formatTimezone = (time: string, timezone: string) => {
+const userStore = useUserStore()
+const formatString = userStore.dateFormat || 'MM/DD/YYYY'
+
+export const formatTimezone = (time: string, timezone?: string) => {
   if (!time) return '--'
   let formattedTime = ''
   if (time.length > 12) {
-    formattedTime = moment(time).format('MMM-DD-YYYY hh:mm A')
+    formattedTime = moment(time).format(`${formatString} hh:mm A`)
     if (!timezone) {
       return formattedTime
     }
-    let gmtOffset = ''
-    const timeZoneOffset = moment().tz(timezone).format('Z')
-    // 替换 "+07:00" 为 "GMT+07"
-    gmtOffset = `(GMT${timeZoneOffset.slice(0, 3)})`
-    return `${formattedTime} ${gmtOffset}`
+    let utcOffset = ''
+    const timeZoneOffset = moment.tz(`${moment().year()}-01-01`, timezone).format('Z')
+    // 替换 "+07:00" 为 "UTC+07"
+    utcOffset = `(UTC${timeZoneOffset.slice(0, 3)})`
+    return `${formattedTime} ${utcOffset}`
   } else {
-    formattedTime = moment(time).format('MMM-DD-YYYY')
+    formattedTime = moment(time).format(formatString)
     return formattedTime
   }
 }
 
+/**
+ * 返回传入地区的UTC时区格式化
+ * @param timezone
+ * @returns
+ */
+export const getTimezone = (timezone: string): string => {
+  if (!timezone) return ''
+  const offset = moment.tz(`${moment().year()}-01-01`, timezone).format('Z')
+  return `UTC${offset.slice(0, 3)}`
+}
+
 export const formatTimezoneByUTCorGMT = (time: string, timezone: string) => {
   if (!time) return '--'
   let formattedTime = ''
-  formattedTime = moment(time).format('MMM-DD-YYYY hh:mm A')
+  formattedTime = moment(time).format(`${formatString} hh:mm A`)
   let gmtOffset = ''
-  console.log(timezone, formattedTime)
   if (timezone != null) {
     const timeZoneOffset = moment().tz(timezone).format('Z')
     // 替换 "+07:00" 为 "GMT+07"
@@ -36,3 +50,59 @@ export const formatTimezoneByUTCorGMT = (time: string, timezone: string) => {
     return `${formattedTime} ${gmtOffset}`
   }
 }
+
+/**
+ * 判断是否是欧洲地区
+ */
+export const isEuropean = () => {
+  const userLanguage = navigator.language || 'en-US'
+  const europeanLocales = ['de', 'fr', 'it', 'es', 'nl', 'pl'] // 例:德语、法语、西班牙语等
+  return europeanLocales.some((locale) => userLanguage.startsWith(locale))
+}
+
+/**
+ * 根据传入的地区 格式化数字
+ * @param number - 需要格式化的数字
+ * @param digits - 小数位数
+ * @param isEuropean - 是否为欧洲地区
+ */
+export const formatNumber = (number: number, digits?: number): string => {
+  const userLanguage = userStore.userInfo?.numbers_format === 'European' ? 'de-DE' : 'zh-CN'
+
+  // 设置数字格式化的选项
+  const options: Intl.NumberFormatOptions = {
+    style: 'decimal'
+    // minimumFractionDigits: digits
+    // maximumFractionDigits: 3
+  }
+  digits ?? (options.minimumFractionDigits = digits)
+  // 其他地区使用默认格式
+  return new Intl.NumberFormat(userLanguage, options).format(number)
+}
+
+/**
+ * 根据用户地区判断日期格式
+ * @returns {string} - 返回日期格式
+ */
+export const getDateFormat = () => {
+  const userLanguage = navigator.language || 'en-US' // 获取浏览器的语言设置
+  // 判断用户地区
+  if (userLanguage === 'en-US') {
+    return 'MM/DD/YYYY' // 美国使用 MM/DD/YYYY 格式
+  } else if (
+    userLanguage.startsWith('de') ||
+    userLanguage.startsWith('fr') ||
+    userLanguage.startsWith('it') ||
+    userLanguage.startsWith('es') ||
+    userLanguage.startsWith('pl') ||
+    userLanguage.startsWith('nl') ||
+    userLanguage.startsWith('pt') ||
+    userLanguage.startsWith('se')
+  ) {
+    return 'DD/MM/YYYY' // 其他欧洲国家(例:德语、法语、西班牙语等)使用 DD/MM/YYYY 格式
+  } else if (['zh-CN', 'ja-JP', 'ko-KR'].includes(userLanguage)) {
+    return 'YYYY-MM-DD' // 东亚国家(如简体中文、日语、韩语)使用 YYYY-MM-DD 格式
+  } else {
+    return 'DD/MM/YYYY' // 其他地区默认 DD/MM/YYYY 格式
+  }
+}

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

@@ -4,7 +4,7 @@ import BasicInformation from './components/BasicInformation.vue'
 import ContainersView from './components/ContainersView.vue'
 import EmailView from './components/EmailView.vue'
 import { cloneDeep } from 'lodash'
-import { transportationMode } from '@/components/TransportationMode'
+import { transportationMode } from '@/components/transportationMode'
 import { useRoute } from 'vue-router'
 import { useOverflow } from '@/hooks/useOverflow'
 import { formatTimezone } from '@/utils/tools'

+ 12 - 3
src/views/Booking/src/components/BookingDetail/src/components/BasicInformation.vue

@@ -2,6 +2,7 @@
 import { useRouter } from 'vue-router'
 import XEClipboard from 'xe-clipboard'
 import AddReferenceDialog from './AddReferenceDialog.vue'
+import { formatNumber } from '@/utils/tools'
 
 const router = useRouter()
 
@@ -108,6 +109,14 @@ const allData: any = ref({
   ]
 })
 
+// 从开始位置截取字符串,并格式化数字(因为后端接口返回的内容带有字符串,所以需要截取)
+const substringFromStart = (str: string, start: number) => {
+  if (!str) {
+    return formatNumber(0, 3)
+  }
+  return formatNumber(Number(str.slice(0, start)), 3)
+}
+
 const convertData = (data: any) => {
   return {
     basicInformation: {
@@ -186,15 +195,15 @@ const convertData = (data: any) => {
       },
       {
         label: 'G. Weight',
-        content: data.packing['G. Weight'] || '--'
+        content: substringFromStart(data.packing['G. Weight'], -4) + ' KGS'
       },
       {
         label: 'Ch. Weight',
-        content: data.packing['Ch. Weight'] || '--'
+        content: substringFromStart(data.packing['Ch. Weight'], -4) + ' KGS'
       },
       {
         label: 'Volume',
-        content: data.packing['Volume'] || '--'
+        content: substringFromStart(data.packing['Volume'], -4) + ' CBM'
       }
     ],
     marksAndDescription: [

+ 6 - 11
src/views/Booking/src/components/BookingDetail/src/components/ContainersView.vue

@@ -1,8 +1,8 @@
 <script setup lang="ts">
-import dayjs from 'dayjs'
 import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
-import { autoWidth } from '@/utils/table'
+// import { autoWidth } from '@/utils/table'
 import { useRowClickStyle } from '@/hooks/rowClickStyle'
+import { formatTimezone, formatNumber } from '@/utils/tools'
 
 const props = defineProps({
   data: Object
@@ -35,20 +35,15 @@ const handleColumns = (columns: any) => {
     }
 
     // 格式化
-    if (item.formatter === 'date') {
+    if (item.formatter === 'date' || item.formatter === 'dateTime') {
       curColumn = {
         ...curColumn,
-        sortBy: ({ row, column }: any) => {
-          return dayjs(row[column.field]).unix()
-        },
-        formatter: ({ cellValue }: any) =>
-          cellValue ? dayjs(cellValue).format('MMM-DD-YYYY ') : '--'
+        formatter: ({ cellValue }: any) => formatTimezone(cellValue)
       }
-    } else if (item.formatter === 'dateTime') {
+    } else if (item.formatter === 'number') {
       curColumn = {
         ...curColumn,
-        formatter: ({ cellValue }: any) =>
-          cellValue ? dayjs(cellValue).format('MMM-DD-YYYY HH:mm:ss') : '--'
+        formatter: ({ cellValue }: any) => formatNumber(Number(cellValue), item?.digits)
       }
     }
     return curColumn

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

@@ -2,7 +2,7 @@
 import '@wangeditor/editor/dist/css/style.css'
 import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
 import { i18nChangeLanguage, DomEditor } from '@wangeditor/editor'
-import dayjs from 'dayjs'
+import { formatTimezone } from '@/utils/tools'
 
 i18nChangeLanguage('en')
 
@@ -152,7 +152,7 @@ const sendEmail = () => {
         emailRecords.value = res.data.emailRecords
       }
     })
-    .catch((err: any) => {
+    .catch(() => {
       ElMessage.error('Failed to send email')
     })
 }
@@ -221,7 +221,7 @@ const sendEmail = () => {
             <div>{{ item.name?.slice(0, 1) }}</div>
           </div>
           <div class="name">{{ item.name }}</div>
-          <div class="date">{{ dayjs(item.creatTime).format('MM-DD-YYYY HH:mm:ss') }}</div>
+          <div class="date">{{ formatTimezone(item.creatTime) }}</div>
         </div>
         <div class="content">
           {{ item.content }}

+ 9 - 12
src/views/Booking/src/components/BookingTable/src/BookingTable.vue

@@ -6,9 +6,10 @@ import { useRowClickStyle } from '@/hooks/rowClickStyle'
 import dayjs from 'dayjs'
 import { ref, onMounted } from 'vue'
 import { useRouter } from 'vue-router'
-import { transportationMode } from '@/components/TransportationMode'
+import { transportationMode } from '@/components/transportationMode'
 import { useThemeStore } from '@/stores/modules/theme'
 import { useVisitedRowState } from '@/stores/modules/visitedRow'
+import { formatTimezone, formatNumber } from '@/utils/tools'
 
 const visitedRowState = useVisitedRowState()
 const themeStore = useThemeStore()
@@ -58,17 +59,15 @@ const handleColumns = (columns: any, status?: string) => {
       }
     }
     // 格式化
-    if (item.formatter === 'date') {
+    if (item.formatter === 'date' || item.formatter === 'dateTime') {
       curColumn = {
         ...curColumn,
-        formatter: ({ cellValue }: any) =>
-          cellValue ? dayjs(cellValue).format('MMM-DD-YYYY ') : '--'
+        formatter: ({ cellValue }: any) => formatTimezone(cellValue)
       }
-    } else if (item.formatter === 'dateTime') {
+    } else if (item.formatter === 'number') {
       curColumn = {
         ...curColumn,
-        formatter: ({ cellValue }: any) =>
-          cellValue ? dayjs(cellValue).format('MMM-DD-YYYY HH:mm:ss') : '--'
+        formatter: ({ cellValue }: any) => formatNumber(Number(cellValue), item?.digits)
       }
     }
     return curColumn
@@ -468,16 +467,14 @@ defineExpose({
     style="padding: 0px 24px"
     class="booking-table"
     v-loading.fullscreen.lock="exportLoading"
+    element-loading-text="Loading..."
+    element-loading-custom-class="element-loading"
     element-loading-background="rgb(43, 47, 54, 0.7)"
   >
     <div class="table-tools">
       <div class="left-total-records">{{ selectedNumber }} Selected</div>
       <div class="right-tools-btn">
-        <el-button
-          :class="{ 'el-button--pain-theme': themeStore.theme === 'dark' }"
-          class="el-button--main"
-          @click="handleDownload"
-        >
+        <el-button class="el-button--main el-button--pain-theme" @click="handleDownload">
           <span style="margin-right: 8px" class="font_family icon-icon_download_b"></span>
           Download
         </el-button>

+ 6 - 2
src/views/Dashboard/src/components/BarChart.vue

@@ -3,6 +3,7 @@
 import * as echarts from 'echarts'
 import { useThemeStore } from '@/stores/modules/theme'
 import { onMounted, ref, reactive, watch, computed } from 'vue'
+import { formatNumber } from '@/utils/tools'
 const themeStore = useThemeStore()
 const props = defineProps({
   BarData: Object,
@@ -101,7 +102,7 @@ const initOption = reactive({
           item.marker +
           item.seriesName +
           ': ' +
-          item.value +
+          formatNumber(item.value) +
           '</div>'
       })
       allnum = allnum.toFixed(2)
@@ -150,7 +151,10 @@ const initOption = reactive({
     },
     axisLabel: {
       fontFamily: 'Lato-Light',
-      color: '#B5B9BF'
+      color: '#B5B9BF',
+      formatter: function (value: any) {
+        return formatNumber(value, 0)
+      }
     },
     min: 0, // 最小值
     max: Max.value, // 最大值

+ 8 - 7
src/views/Dashboard/src/components/DashFiters.vue

@@ -2,6 +2,7 @@
 import { ref, watch, onMounted, computed } from 'vue'
 import moment from 'moment'
 import dayjs from 'dayjs'
+import { formatTimezone } from '@/utils/tools'
 
 const props = defineProps({
   defaultData: {
@@ -82,10 +83,10 @@ const getdefaultdata = () => {
       dashboardObj.date_end = defaultfiltersData.value?.date_end
       dashboardObj.date_end_two = defaultfiltersData.value?.date_end_two
     } else {
-      dashboardObj.date_start = dayjs(DashDate.value[0]).format('MM/DD/YYYY')
-      dashboardObj.date_start_two = dayjs(DashDate.value[0]).format('YYYY-MM-DD')
-      dashboardObj.date_end = dayjs(DashDate.value[1]).format('MM/DD/YYYY')
-      dashboardObj.date_end_two = dayjs(DashDate.value[1]).format('YYYY-MM-DD')
+      dashboardObj.date_start = formatTimezone(DashDate.value[0])
+      dashboardObj.date_start_two = formatTimezone(DashDate.value[0])
+      dashboardObj.date_end = formatTimezone(DashDate.value[1])
+      dashboardObj.date_end_two = formatTimezone(DashDate.value[1])
     }
   }
   dashboardObj.transportation = checkboxGroup1.value
@@ -112,9 +113,9 @@ const changeCheckboxGroup2 = (val: any) => {
 const emit = defineEmits(['FilterSearch'])
 const DateChange = (value: any) => {
   dashboardObj.date_start = value[0]
-  dashboardObj.date_start_two = dayjs(value[0]).format('YYYY-MM-DD')
+  dashboardObj.date_start_two = formatTimezone(value[0])
   dashboardObj.date_end = value[1]
-  dashboardObj.date_end_two = dayjs(value[1]).format('YYYY-MM-DD')
+  dashboardObj.date_end_two = formatTimezone(value[1])
 }
 const StartChange = (val: any) => {
   if (!props.isETDToETA) {
@@ -404,4 +405,4 @@ const DateRangeSearch = () => {
 :deep(.el-input__wrapper) {
   box-shadow: 0 0 0 1px var(--color-select-border) inset;
 }
-</style>
+</style>

+ 7 - 3
src/views/Dashboard/src/components/PieChart.vue

@@ -1,8 +1,9 @@
 <!-- 饼状图 -->
- <script lang="ts" setup>
+<script lang="ts" setup>
 import * as echarts from 'echarts'
 import { useThemeStore } from '@/stores/modules/theme'
 import { onMounted, ref, reactive, watch, computed } from 'vue'
+import { formatNumber } from '@/utils/tools'
 const props = defineProps({
   PieData: Object
 })
@@ -83,7 +84,7 @@ const initOption: any = reactive({
         params.marker +
         params.percent +
         '% (' +
-        params.value +
+        formatNumber(params.value) +
         ')</div>'
       return str
     }
@@ -114,7 +115,10 @@ const initOption: any = reactive({
     label: {
       show: true,
       // formatter: '{d}%({c})',
-      formatter: '{name|{d}%}\n{time|({c})}',
+      formatter: (params) => {
+        console.log(params, 'params')
+        return `{name|${params.percent}%}\n {time|(${formatNumber(params.value)})}`
+      },
       alignTo: 'labelLine',
       rich: {
         name: {

+ 19 - 4
src/views/Dashboard/src/components/RecentStatus.vue

@@ -7,7 +7,7 @@ interface RecentItem {
   Title: string
   name: string
   bookingNumber: string
-  IsSubscribe: boolean
+  is_subscribe: boolean
   shipperName: string
   consigneeName: string
   startStation: string
@@ -34,6 +34,21 @@ const RouteToDetail = (val: any) => {
     }
   })
 }
+
+// 订阅收藏
+const SubscribeShipments = (val: any) => {
+  val.is_subscribe = !val.is_subscribe
+  $api
+    .SubscribeShipments({
+      serial_no: val.a,
+      is_subscribe: val.is_subscribe
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        console.log(res.data)
+      }
+    })
+}
 </script>
 <template>
   <div class="recent_route" v-for="(item, index) in props.RecentStatusList" :key="index">
@@ -71,10 +86,10 @@ const RouteToDetail = (val: any) => {
       <!-- <div class="recent-header-right">
         <el-button
           class="recent_button"
-          @click="item.IsSubscribe = !item.IsSubscribe"
-          :class="item.IsSubscribe ? 'IsSubscribe' : ''"
+          @click="SubscribeShipments(item)"
+          :class="item.is_subscribe ? 'IsSubscribe' : ''"
         >
-          <span v-if="item.IsSubscribe" class="iconfont_icon">
+          <span v-if="item.is_subscribe" class="iconfont_icon">
             <svg class="iconfont" aria-hidden="true">
               <use xlink:href="#icon-icon_marked_b"></use>
             </svg>

+ 6 - 2
src/views/Dashboard/src/components/RevenueChart.vue

@@ -5,6 +5,7 @@ import { useThemeStore } from '@/stores/modules/theme'
 import { onMounted, ref, reactive, watch, computed } from 'vue'
 import updateIcon from '../image/xiazai.png'
 import * as XLSX from 'xlsx'
+import { formatNumber } from '@/utils/tools'
 
 const themeStore = useThemeStore()
 const props = defineProps({
@@ -198,7 +199,7 @@ const initOption = reactive({
           item.marker +
           item.seriesName +
           ': ' +
-          item.value +
+          formatNumber(item.value) +
           '</div>'
       })
       allnum = allnum.toFixed(2)
@@ -247,7 +248,10 @@ const initOption = reactive({
     },
     axisLabel: {
       fontFamily: 'Lato-Light',
-      color: '#B5B9BF'
+      color: '#B5B9BF',
+      formatter: function (value: any) {
+        return formatNumber(value, 0)
+      }
     },
     min: 0, // 最小值
     max: Max.value, // 最大值

+ 5 - 1
src/views/Dashboard/src/components/ScoringSystem.vue

@@ -11,6 +11,10 @@ import hhhPng2 from '../image/hhh_2.png'
 import happyPng from '../image/score_happy.png'
 import happyPng2 from '../image/happy_2.png'
 import submitsucessful from '../image/submit_successful.png'
+import { useUserStore } from '@/stores/modules/user'
+
+const userStore = useUserStore()
+
 const dialogVisible = ref(false)
 const innerVisible = ref(false)
 const isShowAngry = ref(false)
@@ -235,7 +239,7 @@ const changeSmileRadio = (title: any, value: any) => {
 }
 // 提交details
 const submitDetails = (val: any) => {
-  const username = localStorage.getItem('account') ? localStorage.getItem('account') : ''
+  const username = userStore.userName
   if (checkboxGroup1.value.length) {
     $api
       .scoringgrade({

+ 12 - 5
src/views/Dashboard/src/components/SellerChart.vue

@@ -3,6 +3,7 @@
 import * as echarts from 'echarts'
 import { useThemeStore } from '@/stores/modules/theme'
 import { onMounted, ref, reactive, watch, computed } from 'vue'
+import { formatNumber } from '@/utils/tools'
 const themeStore = useThemeStore()
 const props = defineProps({
   SellerData: Array,
@@ -75,7 +76,12 @@ const initOption = reactive({
     borderColor: '#2b2f36',
     formatter: function (params: any) {
       var str =
-        params.name + '<div style= ' + 'color:#FFF>' + params.marker + params.value + '</div>'
+        params.name +
+        '<div style= ' +
+        'color:#FFF>' +
+        params.marker +
+        formatNumber(params.value) +
+        '</div>'
       return str
     },
     textStyle: {
@@ -99,7 +105,10 @@ const initOption = reactive({
     },
     axisLabel: {
       fontFamily: 'Lato-Light',
-      color: '#B5B9BF'
+      color: '#B5B9BF',
+      formatter: function (value: any) {
+        return formatNumber(value, 0)
+      }
     },
     min: 0, // 最小值
     max: Max.value, // 最大值
@@ -150,9 +159,7 @@ const initOption = reactive({
         fontFamily: 'Lato-Light',
         // 数据每三位加一个逗号
         formatter: function (data: { value: { toString: () => string } }) {
-          const reg = /(\d)(?=(?:\d{3})+$)/g
-          const newNumber = data.value.toString().replace(reg, '$1,')
-          return newNumber
+          return formatNumber(Number(data.value.toString()))
         }
       }
     }

+ 2 - 1
src/views/Dashboard/src/components/TopMap.vue

@@ -3,6 +3,7 @@ import { onMounted, ref, watch } from 'vue'
 import OriginIcon from '../image/hhh_2.png'
 import L from 'leaflet'
 import { useThemeStore } from '@/stores/modules/theme'
+import { formatNumber } from '@/utils/tools'
 
 const themeStore = useThemeStore()
 const MapDataList = ref([])
@@ -70,7 +71,7 @@ const addMarkersToMap = () => {
     <div class="popup-content" style="background-color:${item.color}">
       <div class="popup-content-text">
         <p class="popup-label" style="color:${item.textcolor}">${item.name}</p>
-        <p class="popup-value" style="color:${item.textcolor}">${item.value}</p>
+        <p class="popup-value" style="color:${item.textcolor}">${formatNumber(item.value)}</p>
       </div>
     </div>
   `

+ 92 - 30
src/views/Layout/src/components/Header/HeaderView.vue

@@ -8,6 +8,8 @@ import { useHeaderSearch } from '@/stores/modules/headerSearch'
 import { onBeforeRouteUpdate } from 'vue-router'
 import { useLoadingState } from '@/stores/modules/loadingState'
 import { useThemeStore } from '@/stores/modules/theme'
+import NotificationDrawer from './components/NotificationDrawer.vue'
+import TrainingCard from './components/TrainingCard.vue'
 
 const themeStore = useThemeStore()
 const userStore = useUserStore()
@@ -15,7 +17,6 @@ const route = useRoute()
 const router = useRouter()
 const headerSearch = useHeaderSearch()
 
-const themePopoverRef = ref()
 // 切换系统主题颜色
 const toggleThemeMode = (theme: string) => {
   themeStore.toggleTheme(theme, true)
@@ -30,7 +31,7 @@ const handleSearch = () => {
   }
   // 先判断是否登录
   // 未登录
-  if (!localStorage.getItem('username')) {
+  if (!userStore.userInfo?.uname) {
     $api.getPublicTrackingDetail({ reference_number: searchValue.value }).then((res) => {
       if (res.code === 200) {
         const { data } = res
@@ -111,22 +112,39 @@ const handleChangePassword = () => {
   changePasswordDialogRef.value.openDialog()
 }
 
+const userManualLoading = ref(false)
 const handleUserManual = () => {
+  userManualLoading.value = true
   try {
-    $api.getUserGuide().then(async (res) => {
-      if (res.status !== 200) {
-        ElMessageBox.alert('The request failed. Please try again later', 'Prompt', {
-          confirmButtonText: 'OK',
-          confirmButtonClass: 'el-button--dark'
-        })
-        return
-      }
-      // 创建一个 Blob 对象
-      const blob = new Blob([res.data], { type: 'application/pdf' })
-      const url = URL.createObjectURL(blob)
-      // 在新标签页中打开 PDF
-      window.open(url, '_blank')
-    })
+    $api
+      .getUserGuide()
+      .then(async (res) => {
+        if (res.status !== 200) {
+          ElMessageBox.alert('The request failed. Please try again later', 'Prompt', {
+            confirmButtonText: 'OK',
+            confirmButtonClass: 'el-button--dark'
+          })
+          return
+        }
+        if (res.data?.code === 403) {
+          sessionStorage.clear()
+          router.push('/login')
+          userStore.logout()
+          ElMessage.warning({
+            message: 'Please log in to use this feature.',
+            grouping: true
+          })
+          return
+        }
+        // 创建一个 Blob 对象
+        const blob = new Blob([res.data], { type: 'application/pdf' })
+        const url = URL.createObjectURL(blob)
+        // 在新标签页中打开 PDF
+        window.open(url, '_blank')
+      })
+      .finally(() => {
+        userManualLoading.value = false
+      })
   } catch (error) {
     ElMessageBox.alert('The request failed. Please try again later', 'Prompt', {
       confirmButtonText: 'OK',
@@ -157,11 +175,20 @@ const togglePopover = () => {
 const closePopover = () => {
   isPopoverVisible.value = false
 }
+
+const notificationDrawer = ref(false)
 </script>
 
 <template>
-  <div class="layout-toolbar">
+  <div
+    class="layout-toolbar"
+    v-loading.fullscreen.lock="userManualLoading"
+    element-loading-text="Loading..."
+    element-loading-custom-class="element-loading"
+    element-loading-background="rgb(43, 47, 54, 0.7)"
+  >
     <VBreadcrumb></VBreadcrumb>
+    <TrainingCard></TrainingCard>
     <div class="right-info">
       <el-input
         v-model="searchValue"
@@ -173,7 +200,17 @@ const closePopover = () => {
           <span style="margin-top: -1px" class="font_family icon-icon_search_b"></span>
         </template>
       </el-input>
-      <!-- <span class="font_family icon-icon_notice_b" style="font-size: 18px"></span>
+      <!-- <div class="notice-icon" v-if="userStore.userInfo?.uname">
+        <span class="unread-tip-icon"></span>
+        <el-button
+          style="height: 40px; width: 40px; margin-right: 0px"
+          class="el-button--text"
+          @click="notificationDrawer = true"
+        >
+          <span class="font_family icon-icon_notice_b" style="font-size: 18px"></span>
+        </el-button>
+      </div> -->
+      <!-- 
       <span class="font_family icon-icon_language_b" style="font-size: 16px"></span> -->
       <el-popover
         placement="bottom-end"
@@ -256,9 +293,9 @@ const closePopover = () => {
       >
         <div class="title">
           <div class="avatar">
-            <span>{{ userStore.username?.slice(0, 1) }}</span>
+            <span>{{ userStore.userName?.slice(0, 1) }}</span>
           </div>
-          <span class="name">{{ userStore.username }}</span>
+          <span class="name">{{ userStore.userName }}</span>
         </div>
         <div class="options">
           <div class="item" @click="handleChangePassword">
@@ -275,16 +312,18 @@ const closePopover = () => {
           </div>
         </div>
         <template #reference>
-          <div class="header-avatar" v-if="userStore.username && userStore.isFirstLogin !== true">
-            <div>{{ userStore.username?.slice(0, 1) }}</div>
+          <div class="header-avatar" v-if="userStore.userName && userStore.isFirstLogin !== true">
+            <div>{{ userStore.userName.slice(0, 1) }}</div>
           </div>
         </template>
       </el-popover>
       <el-button
-        :class="{ 'el-button--pain-theme': themeStore.theme === 'dark' }"
-        v-if="!userStore.username || (userStore.username && userStore.isFirstLogin === true)"
-        class="el-button--main"
-        style="padding: 8px 10px; margin-right: 10px; margin-left: 0"
+        v-if="
+          !userStore.userInfo?.uname ||
+          (userStore.userInfo?.uname && userStore.isFirstLogin === true)
+        "
+        class="el-button--main el-button--pain-theme"
+        style="padding: 8px 10px; margin-right: 20px; margin-left: 0"
         plain
         @click="handleDownload"
       >
@@ -292,7 +331,10 @@ const closePopover = () => {
         Download KLN Portal
       </el-button>
       <el-button
-        v-if="!userStore.username || (userStore.username && userStore.isFirstLogin === true)"
+        v-if="
+          !userStore.userInfo?.uname ||
+          (userStore.userInfo?.uname && userStore.isFirstLogin === true)
+        "
         class="el-button--main"
         style="margin-left: -10px"
         @click="handleLogin"
@@ -302,6 +344,7 @@ const closePopover = () => {
     <DownloadKLNPortal ref="downloadKLNPortalRef"></DownloadKLNPortal>
     <ChangePasswordDialog ref="changePasswordDialogRef"></ChangePasswordDialog>
     <LogoutDialog ref="logoutDialogRef"></LogoutDialog>
+    <NotificationDrawer v-model="notificationDrawer"></NotificationDrawer>
   </div>
 </template>
 
@@ -309,6 +352,7 @@ const closePopover = () => {
 .header-avatar {
   width: 24px;
   height: 24px;
+  margin-left: 8px;
   text-align: center;
   border-radius: 50%;
   background-color: var(--color-theme);
@@ -326,15 +370,15 @@ const closePopover = () => {
 .layout-toolbar {
   display: flex;
   justify-content: space-between;
+  position: relative;
   height: 100%;
 }
 .right-info {
   display: flex;
   align-items: center;
   justify-content: center;
-  gap: 8px;
   height: 100%;
-
+  gap: 8px;
   .el-input {
     height: 32px;
     width: 400px;
@@ -353,6 +397,24 @@ const closePopover = () => {
       height: 32px;
     }
   }
+
+  .notice-icon {
+    position: relative;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-top: 2px;
+    cursor: pointer;
+    .unread-tip-icon {
+      position: absolute;
+      top: 9px;
+      right: 8px;
+      width: 5px;
+      height: 5px;
+      border-radius: 50%;
+      background-color: var(--color-theme);
+    }
+  }
 }
 </style>
 <style lang="scss">
@@ -495,4 +557,4 @@ div.el-popover.el-popper.user-config-popover {
 div.el-popper.theme-popper-class {
   padding: 3px 4px;
 }
-</style>
+</style>

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

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

+ 1 - 0
src/views/Layout/src/components/Header/components/LogoutDialog.vue

@@ -12,6 +12,7 @@ const handleLogout = () => {
   dialogVisible.value = false
   router.push('/login')
   sessionStorage.clear()
+  localStorage.removeItem('user_type')
 }
 defineExpose({
   openDialog

+ 316 - 0
src/views/Layout/src/components/Header/components/NotificationDrawer.vue

@@ -0,0 +1,316 @@
+<script setup lang="ts">
+import NotificationMessageCard from '@/components/NotificationMessageCard/src/NotificationMessageCard.vue'
+import { useRouter } from 'vue-router'
+
+const router = useRouter()
+const loading = ref(false)
+
+const drawerModel = defineModel('drawerModel', { type: Boolean, default: false })
+
+const notificationType = ref('all')
+const notificationTypeList = ref({
+  all: 'All Notifications',
+  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 notificationList = ref<any[]>([])
+// const notificationList = [
+//   {
+//     notificationType: 'feature',
+//     info: {
+//       isRead: true,
+//       title: 'Feature Update',
+//       header: 'New feature online: Quick search has been released!',
+//       content:
+//         'We are pleased to announce that the quick search function is now officially online! You can now quickly find what you need by entering keywords, greatly improving your work efficiency. Go and experience it!'
+//     }
+//   },
+//   {
+//     notificationType: 'event',
+//     info: {
+//       type: 'milestone',
+//       isMultiple: true,
+//       numericRecords: 3,
+//       isRead: true,
+//       title: 'Milestone Update',
+//       mode: 'Ocean Freight',
+//       no: 'HBOL: SHJN2301234',
+//       tag: 'Booking Confirmed',
+//       location: 'Hong Kong',
+//       time: 'Jan 10, 2025 14:30 UTC+8',
+//       info: {
+//         route: ['Hong Kong', 'Shanghai', 'Ningbo']
+//       }
+//     }
+//   },
+//   {
+//     notificationType: 'password',
+//     info: {
+//       title: 'Password Notifications',
+//       isExpiration: true,
+//       tips: 'Password Expiration in 311 Days',
+//       content:
+//         'Your password will expire in 7 days. To ensure the security of your account, please change your password as soon as possible.'
+//     }
+//   },
+//   {
+//     notificationType: 'password',
+//     info: {
+//       isRead: false,
+//       title: 'Password Notifications',
+//       isExpiration: false,
+//       tips: 'Password Expiration in 3 Days',
+//       content:
+//         'Your password will expire in 7 days. To ensure the security of your account, please change your password as soon as possible.'
+//     }
+//   },
+//   {
+//     notificationType: 'password',
+//     info: {
+//       isRead: true,
+//       title: 'Password Notifications',
+//       isExpiration: true,
+//       tips: 'Password Expiration in 31111 Days',
+//       content:
+//         'Your password will expire in 7 days. To ensure the security of your account, please change your password as soon as possible.'
+//     }
+//   }
+// ]
+
+const getNotificationList = () => {
+  loading.value = true
+  $api
+    .getNotificationList({
+      rules_type: 'Milestone_Update'
+    })
+    .then((res) => {
+      if (res.code === 200) {
+        notificationList.value = res.data
+      }
+    })
+    .finally(() => {
+      loading.value = false
+    })
+}
+
+const handleMarkAllRead = () => {
+  // 标记所有为已读
+  // notificationList.value.forEach((item) => {
+  //   item.isRead = true
+  // })
+}
+
+const handleViewAll = () => {
+  router.push('/system-message')
+}
+
+const handleSettingMessage = () => {
+  // 跳转消息设置页面
+  // router.push('/')
+}
+
+const clearData = () => {
+  notificationList.value = []
+}
+</script>
+
+<template>
+  <el-drawer
+    @open="getNotificationList"
+    @closed="clearData"
+    class="notice-drawer"
+    v-model="drawerModel"
+    size="432px"
+  >
+    <template #header>
+      <el-select size="large" v-model="notificationType" class="notification-type">
+        <el-option
+          v-for="(label, value) in notificationTypeList"
+          :key="value"
+          :label="label"
+          :value="value"
+        />
+      </el-select>
+    </template>
+    <template #default>
+      <div v-vloading="loading" style="height: 100%">
+        <div class="notification-header">
+          <el-button @click="handleMarkAllRead" class="mark-all-read el-button--text" size="small">
+            <span class="font_family icon-icon_confirm_b show-icon"></span>
+            <span>Mark all read</span>
+          </el-button>
+          <div class="view-all">
+            <el-button
+              style="height: 24px; width: 86px"
+              class="el-button--text"
+              @click="handleViewAll"
+            >
+              <span class="font_family icon-icon_confirm_b show-icon"></span>
+              <span>View all</span>
+            </el-button>
+            <el-button
+              class="el-button--text"
+              style="height: 24px; width: 24px; padding: 0"
+              @click="handleSettingMessage"
+            >
+              <span class="font_family icon-icon_administration_b"></span>
+            </el-button>
+          </div>
+        </div>
+        <div class="notification-content">
+          <NotificationMessageCard :data="notificationList" />
+        </div>
+      </div>
+    </template>
+  </el-drawer>
+</template>
+<style lang="scss" scoped>
+div.layout-toolbar {
+  .notification-content {
+    padding: 16px;
+  }
+  .password-notifications {
+    margin-bottom: 16px;
+    .header {
+      display: flex;
+      align-items: center;
+      margin-bottom: 8px;
+      .status-icon {
+        width: 8px;
+        height: 8px;
+        border-radius: 50%;
+        background-color: var(--color-theme);
+        margin-right: 10px;
+      }
+      .title {
+        font-weight: 700;
+      }
+    }
+    .card-content {
+      padding: 16px 8px 24px;
+      background: linear-gradient(180deg, #ffe294 0%, #f6f8fa 100%);
+      border-radius: 12px;
+      &.is-expired {
+        background: linear-gradient(182deg, #ef99a0 2.2%, #f6f8fa 98.77%);
+      }
+      .title {
+        span {
+          vertical-align: middle;
+          font-weight: 700;
+        }
+        .font_family {
+          margin-right: 8px;
+        }
+      }
+      .details {
+        margin: 8px 0 16px 24px;
+      }
+    }
+  }
+  .feature-update {
+    margin-bottom: 16px;
+
+    .header {
+      display: flex;
+      align-items: center;
+      margin-bottom: 8px;
+      .status-icon {
+        width: 8px;
+        height: 8px;
+        border-radius: 50%;
+        background-color: var(--color-theme);
+        margin-right: 10px;
+      }
+      .title {
+        font-weight: 700;
+      }
+    }
+    .card-content {
+      padding: 16px 8px 24px;
+      background: linear-gradient(137deg, #fff4eb 12.41%, #f0f3ff 52.63%, #e0f7f9 93.28%);
+      border-radius: 12px;
+      .title {
+        img {
+          vertical-align: middle;
+        }
+        span {
+          vertical-align: middle;
+          display: inline-block;
+          font-weight: 700;
+        }
+        .font_family {
+          margin-right: 8px;
+        }
+      }
+      .details {
+        margin: 8px 0 16px 24px;
+      }
+    }
+  }
+}
+</style>
+<style lang="scss">
+div.layout-toolbar {
+  .el-overlay:has(> .notice-drawer) {
+    background-color: transparent;
+  }
+  .notice-drawer {
+    top: 48px;
+    height: calc(100% - 48px);
+    .el-drawer__header {
+      gap: 52px;
+      .el-drawer__close {
+        color: var(--color-neutral-1);
+        font-size: 16px;
+      }
+    }
+    .el-drawer__body {
+      padding: 0;
+    }
+    .notification-type {
+      .el-select__wrapper {
+        width: 320px !important;
+      }
+    }
+    .notification-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      position: sticky;
+      top: 0;
+      height: 40px;
+      padding: 0 16px;
+      line-height: 40px;
+      background-color: white;
+      border-bottom: 1px solid var(--color-border);
+      .mark-all-read {
+        span {
+          color: var(--color-theme);
+          font-size: 14px;
+          vertical-align: middle;
+        }
+        .show-icon {
+          font-size: 16px;
+          margin-right: 2px;
+        }
+      }
+      .view-all {
+        span {
+          vertical-align: middle;
+        }
+        .show-icon {
+          margin-right: 2px;
+        }
+        .setting {
+          margin-left: 16px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 199 - 0
src/views/Layout/src/components/Header/components/TrainingCard.vue

@@ -0,0 +1,199 @@
+<script setup lang="ts">
+import NotificationMessageCard from '@/components/NotificationMessageCard/src/NotificationMessageCard.vue'
+
+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 curCard = computed(() => {
+  return notificationList[curIndex.value] || null
+})
+const curIndex = ref(0)
+// 设置定时器进行自动轮播
+let intervalId = null
+
+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)
+    result = false
+  }
+
+  // 如果到达最后一个消息,设置为null以清除显示
+  if (curIndex.value >= notificationList.length) {
+    clearInterval(intervalId)
+    // curCard.value = null
+    result = false
+  }
+  return result
+}
+
+const initTrainingCard = () => {
+  if (curCard.value?.notificationType === 'event') {
+    intervalId = setInterval(nextNotification, 2000)
+  }
+}
+initTrainingCard()
+
+const closeMessage = () => {
+  // 如果当前消息为event类型,则需先清除定时器
+  if (curCard.value?.notificationType === 'event') {
+    clearInterval(intervalId)
+  }
+
+  const result = nextNotification()
+  if (result) {
+    intervalId = setInterval(nextNotification, 2000)
+  }
+}
+</script>
+
+<template>
+  <div class="training-card" v-if="curCard">
+    <el-button
+      @click="closeMessage"
+      style="height: 18px; width: 18px"
+      class="el-button--text close-icon"
+    >
+      <span class="font_family icon-icon_reject_b"></span>
+    </el-button>
+    <NotificationMessageCard v-if="curCard" :data="[curCard]"></NotificationMessageCard>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.training-card {
+  position: absolute;
+  top: 60px;
+  right: 20px;
+  z-index: 2010;
+  width: 432px;
+  padding: 16px;
+  padding-bottom: 0;
+  background-color: #fff;
+  border-radius: 12px;
+  box-shadow: 4px 4px 16px 0px rgba(0, 0, 0, 0.1);
+  .close-icon {
+    position: absolute;
+    top: 14px;
+    right: 16px;
+    padding-top: 2px;
+    padding-bottom: 0px;
+    font-size: 18px;
+    cursor: pointer;
+  }
+}
+</style>

+ 57 - 4
src/views/Layout/src/components/Menu/MenuView.vue

@@ -11,7 +11,7 @@ const isCollapse = defineModel<boolean>()
 
 const menuList = ref()
 watch(
-  () => userStore.username,
+  () => userStore.userInfo?.uname,
   () => {
     getMenuList()
   }
@@ -22,6 +22,49 @@ 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()
 //监听窗口大小
@@ -39,6 +82,14 @@ handler()
 
 const activeMenu = ref()
 activeMenu.value = (route.meta?.activeMenu as string) || route.path
+// 默认展开的菜单
+const openeds = computed(() => {
+  if (!activeMenu.value) return []
+  const parentItem = menuList.value?.find((item: any) =>
+    item.children?.some((child: any) => child.path === activeMenu.value)
+  )
+  return parentItem ? [parentItem.index] : []
+})
 
 // 未登录白名单
 const whiteList = ['/login', '/public-tracking', '/public-tracking/detail', '/reset-password']
@@ -46,7 +97,7 @@ const whiteList = ['/login', '/public-tracking', '/public-tracking/detail', '/re
 // 判断是否允许跳转
 const isAllowJump = (path: any) => {
   // 判断是否登录
-  if (!whiteList.includes(path) && !localStorage.getItem('username')) {
+  if (!whiteList.includes(path) && !userStore.userInfo?.uname) {
     ElMessage.warning({
       message: 'Please log in to use this feature.',
       grouping: true
@@ -86,7 +137,7 @@ const changeRouter = (path: any) => {
   emits('changeVisible', isVisible.value)
   isVisible.value = false
   let toPath = path
-  if (path === '/tracking' && !localStorage.getItem('username')) {
+  if (path === '/tracking' && !userStore.userInfo?.uname) {
     toPath = '/public-tracking'
   }
   // 如果允许跳转,执行跳转
@@ -127,6 +178,8 @@ const jumpLink = (link: string) => {
       class="layout-menu"
       @select="changeRouter"
       :default-active="activeMenu"
+      :default-openeds="openeds"
+      :unique-opened="true"
       :collapse="isCollapse"
     >
       <template v-for="item in menuList" :key="item.index">
@@ -140,7 +193,7 @@ const jumpLink = (link: string) => {
           <span class="font_family" :class="[`icon-${item.icon}`]"></span>
           <template #title>{{ item.label }}</template>
         </el-menu-item>
-        <el-sub-menu v-else :index="item.path">
+        <el-sub-menu v-else :index="item.index">
           <template #title>
             <div class="font_family" style="font-size: 16px" :class="[`icon-${item.icon}`]"></div>
             <span>{{ item.label }}</span>

+ 1 - 4
src/views/Login/src/components/ChangePasswordCard.vue

@@ -21,7 +21,7 @@ const loginForm = ref({
   newPassword: '',
   confirmPassword: ''
 })
-loginForm.value.username = localStorage.getItem('username') || ''
+loginForm.value.username = userStore.userInfo?.uname || ''
 if (!loginForm.value.username) {
   router.push({
     name: 'Login'
@@ -321,9 +321,6 @@ onUnmounted(() => {
       background-color: transparent;
     }
     &.is-disabled {
-      :deep(.el-input__wrapper) {
-        // background-color: #f4f4f4;
-      }
       :deep(.el-input__inner) {
         -webkit-text-fill-color: var(--color-neutral-1);
         color: var(--color-neutral-1);

+ 5 - 3
src/views/Login/src/loginView.vue

@@ -210,8 +210,9 @@ const handleResult = (res: any) => {
         }
       })
     }
-    userStore.setUsername(res.data.uname || '')
+    userStore.setUserInfo(res.data?.user_info || {})
     router.push('/')
+    localStorage.setItem('showTrainingCardAfterLogin', 'true')
   } else if (res.code === 400) {
     const { data } = res
     if (data.msg === 'passwordExpires') {
@@ -220,12 +221,12 @@ const handleResult = (res: any) => {
         type: 'warning',
         confirmButtonClass: 'el-button--dark'
       })
-      userStore.setUsername(res.data.uname || '')
+      userStore.setUserInfo(res.data?.user_info || {})
       router.push({
         name: 'Reset Password'
       })
     } else if (data.msg === 'First login, please change your password') {
-      userStore.setUsername(res.data.uname, true)
+      userStore.setUserInfo(res.data?.user_info || {}, true)
       firstLoginTipsRef.value.openDialog()
     }
   }
@@ -243,6 +244,7 @@ const handleLoginAfterVerify = () => {
       if (res.code === 200) {
         if (isRememerPwd.value) {
           saveCredentials()
+          localStorage.setItem('user_type', res.data.user_info.user_type)
         } else {
           clearCredentials()
         }

+ 9 - 17
src/views/OperationLog/src/components/BookingTable/src/BookingTable.vue

@@ -2,10 +2,11 @@
 import { ref, nextTick, onMounted } from 'vue'
 import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
 import DownloadDialog from './components/DownloadDialog.vue'
-import { autoWidth } from '@/utils/table'
+// import { autoWidth } from '@/utils/table'
 import { useRowClickStyle } from '@/hooks/rowClickStyle'
 import dayjs from 'dayjs'
 import { useThemeStore } from '@/stores/modules/theme'
+import { formatTimezone } from '@/utils/tools'
 
 const themeStore = useThemeStore()
 
@@ -46,17 +47,10 @@ const handleColumns = (columns: any, status?: string) => {
       }
     }
     // 格式化
-    if (item.formatter === 'date') {
+    if (item.formatter === 'date' || item.formatter === 'dateTime') {
       curColumn = {
         ...curColumn,
-        formatter: ({ cellValue }: any) =>
-          cellValue ? dayjs(cellValue).format('MMM-DD-YYYY ') : '--'
-      }
-    } else if (item.formatter === 'dateTime') {
-      curColumn = {
-        ...curColumn,
-        formatter: ({ cellValue }: any) =>
-          cellValue ? dayjs(cellValue).format('YYYY/MM/DD HH:mm:ss') : '--'
+        formatter: ({ cellValue }: any) => formatTimezone(cellValue)
       }
     }
     return curColumn
@@ -111,7 +105,7 @@ const getTableData = async (isPageChange?: boolean) => {
     .SearchOperationLog({
       cp: pageInfo.value.pageNo,
       ps: pageInfo.value.pageSize,
-      rc: -1,
+      rc,
       ...searchdata
     })
     .then((res: any) => {
@@ -153,7 +147,7 @@ const SearchOperationLog = (val: any) => {
     })
 }
 onMounted(() => {
-  Promise.all([getTableColumns(), getTableData()]).finally(() => {
+  Promise.all([getTableColumns(), getTableData(false)]).finally(() => {
     nextTick(() => {
       // tableRef.value && autoWidth(bookingTable.value, tableRef.value)
     })
@@ -351,16 +345,14 @@ defineExpose({
     style="padding: 0px 20px"
     class="booking-table"
     v-loading.fullscreen.lock="exportLoading"
+    element-loading-text="Loading..."
+    element-loading-custom-class="element-loading"
     element-loading-background="rgb(43, 47, 54, 0.7)"
   >
     <div class="table-tools">
       <div class="left-total-records">{{ selectedNumber }} Selected</div>
       <div class="right-tools-btn">
-        <el-button
-          :class="{ 'el-button--pain-theme': themeStore.theme === 'dark' }"
-          class="el-button--main"
-          @click="handleDownload"
-        >
+        <el-button class="el-button--main el-button--pain-theme" @click="handleDownload">
           <span style="margin-right: 8px" class="font_family icon-icon_download_b"></span>
           Download
         </el-button>

+ 0 - 510
src/views/OperationLog/src/components/BookingTable/src/BookingTableColumns.ts

@@ -1,510 +0,0 @@
-import dayjs from 'dayjs'
-
-const BookingTableColumns: any = [
-  {
-    field: 'booking_no',
-    title: 'Booking No.',
-    slots: {
-      default: 'bookingNo'
-    },
-    type: 'link'
-  },
-  {
-    field: 'm_bol',
-    title: 'MBOL No.'
-  },
-  {
-    field: 'h_bol',
-    title: 'HBOL No.'
-  },
-  {
-    field: 'po_no',
-    title: 'PO No.'
-  },
-  {
-    field: 'quote_no',
-    title: 'Quote No.'
-  },
-  {
-    field: 'carrier_booking',
-    title: 'Carrier Booking No.'
-  },
-  {
-    field: 'contract',
-    title: 'Contract No.'
-  },
-  {
-    field: 'mode',
-    title: 'Transportation Mode',
-    type: 'mode',
-    slots: {
-      default: 'mode'
-    }
-  },
-  {
-    field: 'status',
-    title: 'Status',
-    type: 'status',
-    slots: {
-      default: 'status'
-    }
-  },
-  {
-    field: 'shipper',
-    title: 'Shipper'
-  },
-  {
-    field: 'consignee',
-    title: 'Consignee'
-  },
-  {
-    field: 'origin',
-    title: 'Origin Agent'
-  },
-  {
-    field: 'agent',
-    title: 'Destination Agent'
-  },
-  {
-    field: 'sales_rep',
-    title: 'Sales'
-  },
-  {
-    field: 'created_time',
-    title: 'Creation Time',
-    formatter: ({ cellValue }: any) => {
-      return cellValue ? dayjs(cellValue).format('MMM-DD-YYYY HH:mm:ss') : '--'
-    }
-  },
-  {
-    field: 'confirmation_time',
-    title: 'Confirmation Time',
-    formatter: ({ cellValue }: any) => {
-      return cellValue ? dayjs(cellValue).format('MMM-DD-YYYY HH:mm:ss') : '--'
-    }
-  },
-  {
-    field: 'f_etd',
-    title: 'ETD',
-    formatter: ({ cellValue }: any) => {
-      return cellValue ? dayjs(cellValue).format('MMM-DD-YYYY') : '--'
-    }
-  },
-  {
-    field: 'f_eta',
-    title: 'ETA',
-    formatter: ({ cellValue }: any) => {
-      return cellValue ? dayjs(cellValue).format('MMM-DD-YYYY') : '--'
-    }
-  },
-  {
-    field: 'place_of_receipt_exp',
-    title: 'Place of Receipt'
-  },
-  {
-    field: 'fport_of_loading_exp',
-    title: 'Port of Loading'
-  },
-  {
-    field: 'place_of_delivery_exp',
-    title: 'Place of Delivery'
-  },
-  {
-    field: 'final_desination_exp',
-    title: 'Destination'
-  },
-  {
-    field: 'from_station',
-    title: 'Origin'
-  },
-  {
-    field: 'f_carrier',
-    title: 'Carrier'
-  },
-  {
-    field: 'f_voyage',
-    title: 'Voyage'
-  },
-  {
-    field: 'f_vessel',
-    title: 'Vessel'
-  },
-  {
-    field: 'week',
-    title: 'Week'
-  },
-  {
-    field: 'd20',
-    title: 'D20'
-  },
-  {
-    field: 'sd40',
-    title: 'D40'
-  },
-  {
-    field: 'd45',
-    title: 'D45'
-  },
-  {
-    field: 'rd40',
-    title: 'RD'
-  },
-  {
-    field: 'hq40',
-    title: 'HQ'
-  },
-  {
-    field: 'created_by',
-    title: 'Created by'
-  },
-  {
-    field: 'terms',
-    title: 'Other info'
-  }
-]
-
-const addOtherConfig = () => {
-  BookingTableColumns.forEach((item: any) => {
-    item.sortable = true
-  })
-}
-addOtherConfig()
-
-const defaultColumns = [
-  'Transportation Mode',
-  'Status',
-  'Booking No.',
-  'MBOL No.',
-  'HBOL No.',
-  'PO No.',
-  'Shipper',
-  'Consignee',
-  'Origin',
-  'Destination',
-  'Creation Time',
-  'ETD',
-  'ETA',
-  'Week',
-  'Vessel',
-  'Voyage',
-  'Created by',
-  'Other info'
-]
-
-// 分组
-const groupColumns = [
-  {
-    name: 'All',
-    children: [
-      {
-        label: 'Booking No.',
-        field: 'booking_no'
-      },
-      {
-        label: 'MBOL No.',
-        field: 'm_bol'
-      },
-      {
-        label: 'HBOL No.',
-        field: 'h_bol'
-      },
-      {
-        label: 'PO No.',
-        field: 'po_no'
-      },
-      {
-        label: 'Quote No.',
-        field: 'quote_no'
-      },
-      {
-        label: 'Carrier Booking No.',
-        field: 'carrier_booking'
-      },
-      {
-        label: 'Contract No.',
-        field: 'contract'
-      },
-      {
-        label: 'Transportation Mode',
-        field: 'mode'
-      },
-      {
-        label: 'Status',
-        field: 'status'
-      },
-      {
-        label: 'Shipper',
-        field: 'shipper'
-      },
-      {
-        label: 'Consignee',
-        field: 'consignee'
-      },
-      {
-        label: 'Origin Agent',
-        field: 'origin'
-      },
-      {
-        label: 'Destination Agent',
-        field: 'agent'
-      },
-      {
-        label: 'Sales',
-        field: 'sales_rep'
-      },
-      {
-        label: 'Creation Time',
-        field: 'created_time'
-      },
-      {
-        label: 'Confirmation Time',
-        field: 'confirmation_time'
-      },
-      {
-        label: 'ETD',
-        field: 'f_etd'
-      },
-      {
-        label: 'ETA',
-        field: 'f_eta'
-      },
-      {
-        label: 'Place of Receipt',
-        field: 'place_of_receipt_exp'
-      },
-      {
-        label: 'Port of Loading',
-        field: 'fport_of_loading_exp'
-      },
-      {
-        label: 'Place of Delivery',
-        field: 'place_of_delivery_exp'
-      },
-      {
-        label: 'Destination',
-        field: 'final_desination_exp'
-      },
-      {
-        label: 'Origin',
-        field: 'from_station'
-      },
-      {
-        label: 'Carrier',
-        field: 'f_carrier'
-      },
-      {
-        label: 'Voyage',
-        field: 'f_voyage'
-      },
-      {
-        label: 'Vessel',
-        field: 'f_vessel'
-      },
-      {
-        label: 'Week',
-        field: 'week'
-      },
-      {
-        label: 'D20',
-        field: 'd20'
-      },
-      {
-        label: 'D40',
-        field: 'sd40'
-      },
-      {
-        label: 'D45',
-        field: 'd45'
-      },
-      {
-        label: 'RD',
-        field: 'rd40'
-      },
-      {
-        label: 'HQ',
-        field: 'hq40'
-      },
-      {
-        label: 'Created by',
-        field: 'created_by'
-      },
-      {
-        label: 'Other info',
-        field: 'terms'
-      }
-    ]
-  },
-  {
-    name: 'Reference No.',
-    children: [
-      {
-        label: 'Booking No.',
-        field: 'booking_no'
-      },
-      {
-        label: 'MBOL No.',
-        field: 'm_bol'
-      },
-      {
-        label: 'HBOL No.',
-        field: 'h_bol'
-      },
-      {
-        label: 'PO No.',
-        field: 'po_no'
-      },
-      {
-        label: 'Quote No.',
-        field: 'quote_no'
-      },
-      {
-        label: 'Carrier Booking No.',
-        field: 'carrier_booking'
-      },
-      {
-        label: 'Contract No.',
-        field: 'contract'
-      }
-    ]
-  },
-  {
-    name: 'General',
-    children: [
-      {
-        label: 'Transportation Mode',
-        field: 'mode'
-      },
-      {
-        label: 'Status',
-        field: 'status'
-      }
-    ]
-  },
-  {
-    name: 'Parties',
-    children: [
-      {
-        label: 'Shipper',
-        field: 'shipper'
-      },
-      {
-        label: 'Consignee',
-        field: 'consignee'
-      },
-      {
-        label: 'Origin Agent',
-        field: 'origin'
-      },
-      {
-        label: 'Destination Agent',
-        field: 'agent'
-      },
-      {
-        label: 'Sales',
-        field: 'sales_rep'
-      }
-    ]
-  },
-  {
-    name: 'Time',
-    children: [
-      {
-        label: 'Creation Time',
-        field: 'created_time'
-      },
-      {
-        label: 'Confirmation Time',
-        field: 'confirmation_time'
-      },
-      {
-        label: 'ETD',
-        field: 'f_etd'
-      },
-      {
-        label: 'ETA',
-        field: 'f_eta'
-      }
-    ]
-  },
-  {
-    name: 'Places',
-    children: [
-      {
-        label: 'Place of Receipt',
-        field: 'place_of_receipt_exp'
-      },
-      {
-        label: 'Port of Loading',
-        field: 'fport_of_loading_exp'
-      },
-      {
-        label: 'Place of Delivery',
-        field: 'place_of_delivery_exp'
-      },
-      {
-        label: 'Destination',
-        field: 'final_desination_exp'
-      },
-      {
-        label: 'Origin',
-        field: 'from_station'
-      }
-    ]
-  },
-  {
-    name: 'Transportation',
-    children: [
-      {
-        label: 'Carrier',
-        field: 'f_carrier'
-      },
-      {
-        label: 'Voyage',
-        field: 'voyage_m_voyage'
-      },
-      {
-        label: 'Vessel',
-        field: 'vessel_m_vessel'
-      },
-      {
-        label: 'Week',
-        field: 'week'
-      },
-      {
-        label: 'D20',
-        field: 'd20'
-      },
-      {
-        label: 'D40',
-        field: 'sd40'
-      },
-      {
-        label: 'D45',
-        field: 'd45'
-      },
-      {
-        label: 'RD',
-        field: 'rd40'
-      },
-      {
-        label: 'HQ',
-        field: 'hq40'
-      }
-    ]
-  },
-  {
-    name: 'Others',
-    children: [
-      {
-        label: 'Created by',
-        field: 'created_by'
-      },
-      {
-        label: 'Other info',
-        field: 'terms'
-      }
-    ]
-  }
-]
-
-export { BookingTableColumns, defaultColumns, groupColumns }

+ 1 - 0
src/views/SystemMessage/index.ts

@@ -0,0 +1 @@
+export { default } from './src/SystemMessage.vue'

+ 350 - 0
src/views/SystemMessage/src/SystemMessage.vue

@@ -0,0 +1,350 @@
+<script setup lang="ts">
+import EventCard from '@/components/NotificationMessageCard/src/components/EventCard.vue'
+
+const collapseVModel = ref<string[]>(['1'])
+
+const navList = [
+  {
+    title: 'Milestone Update',
+    count: 2
+  },
+  {
+    title: 'Container Status Update',
+    count: 1
+  },
+  {
+    title: 'Departure/Arrival Delay',
+    count: '99+'
+  },
+  {
+    title: 'ETD/ETA Change',
+    count: 0
+  }
+]
+
+const activeItem = ref('Milestone Update')
+const setActiveItem = (item: string) => {
+  activeItem.value = item
+}
+
+// const getNotificationList = () => {
+//   $api
+//     .getNotificationList({
+//       rules_type: 'Milestone_Update'
+//     })
+//     .then((res) => {
+//       if (res.code === 200) {
+//         notificationList.value = res.data.info
+//         console.log(res, 'test')
+//       }
+//     })
+// }
+
+const activeName = ref('first')
+
+const handleClick = () => {}
+
+// const notificationList = ref<any[]>([])
+const notificationList = [
+  {
+    type: 'milestone',
+    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
+    }
+  },
+  {
+    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
+    }
+  },
+  {
+    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()
+// })
+</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>
+          </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>
+          </template>
+        </el-tab-pane>
+        <el-tab-pane label="Read" name="third">Role</el-tab-pane>
+      </el-tabs>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.Title {
+  display: flex;
+  height: 68px;
+  border-bottom: 1px solid var(--color-border);
+  font-size: var(--font-size-6);
+  font-weight: 700;
+  padding: 0 24px;
+  align-items: center;
+}
+.system-message {
+  display: flex;
+  height: calc(100% - 68px);
+  .count {
+    display: inline-flex;
+    justify-content: center;
+    height: 18px;
+    min-width: 18px;
+    padding-left: 4px;
+    padding-right: 5px;
+    background-color: var(--color-theme);
+    border-radius: 9px;
+    font-size: 12px;
+    line-height: 18px;
+    text-align: center;
+    span {
+      color: var(--color-white);
+      font-weight: 700;
+    }
+  }
+}
+.left-nav {
+  width: 280px;
+  padding: 24px;
+  padding-right: 0;
+  border-right: 1px solid var(--color-border);
+  .el-collapse {
+    padding-right: 16px;
+    border-top: none;
+    :deep(.el-collapse-item__header) {
+      font-weight: 700;
+    }
+  }
+  .collapse-item {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    position: relative;
+    width: 240px;
+    height: 48px;
+    margin-bottom: 4px;
+    padding: 0 16px;
+    border-radius: 12px;
+    &:hover {
+      background-color: var(--color-system-message-nav-bg);
+    }
+    .active-sign {
+      position: absolute;
+      top: 50%;
+      left: 0;
+      transform: translateY(-50%);
+      width: 4px;
+      height: 21px;
+      border-radius: 12px;
+      background-color: var(--color-theme);
+    }
+
+    &.is-active {
+      background-color: var(--color-system-message-nav-bg);
+      & > span {
+        font-weight: 700;
+        color: var(--color-theme);
+      }
+    }
+    &:last-child {
+      margin-bottom: 8px;
+    }
+  }
+  :deep(.el-collapse-item__header) {
+    width: 240px;
+    padding: 16px;
+  }
+}
+
+.right-content {
+  flex: 1;
+  padding-top: 24px;
+  :deep(.el-tabs__nav-scroll) {
+    padding-left: 16px;
+    border-bottom: 1px solid var(--color-border);
+    .el-tabs__item {
+      font-weight: 400;
+      color: var(--color-neutral-1);
+      &.is-active {
+        font-weight: 700;
+      }
+    }
+  }
+  :deep(.el-tabs) {
+    height: calc(100%);
+    .el-tabs__content {
+      overflow-y: auto;
+    }
+  }
+}
+</style>

+ 100 - 0
src/views/SystemMessage/src/components/SystemMessageDetail.vue

@@ -0,0 +1,100 @@
+<script setup lang="ts">
+import EventCard from '@/components/NotificationMessageCard/src/components/EventCard.vue'
+
+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 getNotificationList = () => {
+  $api.getNotificationDetails().then((res) => {
+    if (res.code === 200) {
+      // console.log(res, 'test')
+    }
+  })
+}
+onMounted(() => {
+  getNotificationList()
+})
+</script>
+
+<template>
+  <div class="system-message-detail">
+    <div class="content">
+      <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>
+      <EventCard
+        v-for="(item, index) in notificationData.notificationList"
+        :key="index"
+        :data="item"
+      />
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.system-message-detail {
+  padding: 16px;
+  .content {
+    margin: auto;
+    width: 800px;
+  }
+  .notification-card {
+    max-width: 800px;
+  }
+  .header {
+    display: flex;
+    align-items: center;
+    margin-bottom: 8px;
+    .status-icon {
+      width: 8px;
+      height: 8px;
+      border-radius: 50%;
+      background-color: var(--color-theme);
+      margin-right: 10px;
+    }
+    .title {
+      font-weight: 700;
+      font-size: 24px;
+    }
+  }
+  .total-tips {
+    height: 40px;
+    line-height: 40px;
+    font-size: 12px;
+  }
+}
+</style>

+ 1 - 0
src/views/SystemSettings/index.ts

@@ -0,0 +1 @@
+export { default } from './src/SystemSettings.vue'

+ 466 - 0
src/views/SystemSettings/src/SystemSettings.vue

@@ -0,0 +1,466 @@
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import AddRSettingTableules from './components/SettingTable'
+import MonitoringTable from './components/MonitoringTable'
+import { useRouter } from 'vue-router'
+import PersonalProfile from './components/PersonalProfile.vue'
+
+const router = useRouter()
+
+const TabActive = ref('Personal Profile')
+const isMilestoneChecked = ref(false)
+const isContainerChecked = ref(false)
+const isDepartureChecked = ref(false)
+const isETDChangeChecked = ref(false)
+const isMilestoneAdded = ref(false)
+const isContainerAdded = ref(false)
+const isDepartureAdded = ref(false)
+const isETDChangeAdded = ref(false)
+const CollapseActive = ref()
+const isActiveCollapse = ref({
+  Milestone: false,
+  Container: false,
+  Departure: false,
+  ETDChange: false
+})
+// 展开与折叠
+const isActive = (name: any) => {
+  isContainerChecked.value = false
+  isMilestoneChecked.value = false
+  isETDChangeChecked.value = false
+  isDepartureChecked.value = false
+  isActiveCollapse.value.ETDChange = false
+  isActiveCollapse.value.Milestone = false
+  isActiveCollapse.value.Departure = false
+  isActiveCollapse.value.Container = false
+  isActiveCollapse.value[name] = true
+}
+const changeCollapse = (val: any) => {
+  if (val == 'Milestone') {
+    isActive('Milestone')
+    isMilestoneChecked.value = true
+  } else if (val == 'Container') {
+    isActive('Container')
+    isContainerChecked.value = true
+  } else if (val == 'Departure') {
+    isActive('Departure')
+    isDepartureChecked.value = true
+  } else if (val == 'ETDChange') {
+    isActive('ETDChange')
+    isETDChangeChecked.value = true
+  } else {
+    isContainerChecked.value = false
+    isMilestoneChecked.value = false
+    isETDChangeChecked.value = false
+    isDepartureChecked.value = false
+    isActiveCollapse.value.ETDChange = false
+    isActiveCollapse.value.Milestone = false
+    isActiveCollapse.value.Departure = false
+    isActiveCollapse.value.Container = false
+  }
+}
+
+const UnsavedCollapse = () => {
+  CollapseActive.value = ''
+}
+
+// 表格列
+const AddedRulesColumns = ref([
+  {
+    field: 'Event',
+    title: 'Event',
+    type: 'normal',
+    formatter: ''
+  },
+  {
+    field: 'Event Details',
+    title: 'Event Details',
+    type: 'normal',
+    formatter: ''
+  },
+  {
+    field: 'Frequency',
+    title: 'Frequency',
+    type: 'normal',
+    formatter: ''
+  },
+  {
+    field: 'Methods',
+    title: 'Methods',
+    type: 'normal',
+    formatter: ''
+  }
+])
+const SubShipmentsColumns = ref([
+  {
+    field: 'h_bol',
+    title: 'HBOL/HAWB',
+    type: 'normal',
+    formatter: ''
+  },
+  {
+    field: 'shipper',
+    title: 'Shipper',
+    type: 'normal',
+    formatter: ''
+  },
+  {
+    field: 'consignee',
+    title: 'Consignee',
+    type: 'normal',
+    formatter: ''
+  },
+  {
+    field: 'etd',
+    title: 'ETD',
+    type: 'normal',
+    formatter: 'date'
+  },
+  {
+    field: 'eta',
+    title: 'ETA',
+    type: 'normal',
+    formatter: 'date'
+  },
+  {
+    field: 'recent_milestone',
+    title: 'Recent Milestone',
+    type: 'normal',
+    formatter: ''
+  }
+])
+
+// System Settings初始数据
+const SubShipmentsTable = ref()
+const AddRulesTable = ref()
+const subscribeInit = ref()
+const getsubscribe = () => {
+  $api.getsubscribe({}).then((res: any) => {
+    if (res.code === 200) {
+      subscribeInit.value = res.data
+      SubShipmentsTable.value.getTableData(res.data.subscribeShipmentWithPage)
+      AddRulesTable.value.getTableData(res.data.addedRules)
+      isMilestoneAdded.value = res.data.Milestone_Update.is_display
+      isContainerAdded.value = res.data.Container_Status_Update.is_display
+      isDepartureAdded.value = res.data['Departure/Arrival_Delay'].is_display
+      isETDChangeAdded.value = res.data['ETD/ETA_Change'].is_display
+    }
+  })
+}
+
+// 保存成功后更改表单数据
+const SavedAddedRules = (val: any, type: any) => {
+  AddRulesTable.value.getTableData(val)
+  CollapseActive.value = ''
+  isMilestoneChecked.value = false
+  isContainerChecked.value = false
+  isDepartureChecked.value = false
+  isETDChangeChecked.value = false
+  if (type == 'Milestone_Update') {
+    isMilestoneAdded.value = true
+  } else if (type == 'Container_Status_Update') {
+    isContainerAdded.value = true
+  } else if (type == 'Arrival_Delay') {
+    isDepartureAdded.value = true
+  } else {
+    isETDChangeAdded.value = true
+  }
+}
+
+// 删除表格后更改数据
+const Milestone = ref()
+const Container = ref()
+const Departure = ref()
+const ETDChange = ref()
+const deleteAddedRules = (val: any) => {
+  if (val == 'Milestone_Update') {
+    isMilestoneAdded.value = false
+    Milestone.value.clearData(val)
+  } else if (val == 'Container_Status_Update') {
+    isContainerAdded.value = false
+    Container.value.clearData(val)
+  } else if (val == 'Departure/Arrival_Delay') {
+    isDepartureAdded.value = false
+    Departure.value.clearData(val)
+  } else {
+    isETDChangeAdded.value = false
+    ETDChange.value.clearData(val)
+  }
+}
+
+// 跳转Create New Rule页面
+const ToCreateRule = () => {
+  router.push({
+    path: '/SystemSettings/createnewrule',
+    query: {}
+  })
+  sessionStorage.setItem('activeTab', 'Monitoring Settings')
+}
+
+const tabledatalength = ref()
+const gettabledatalength = (val: any) => {
+  tabledatalength.value = val
+}
+
+// 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">
+      <div class="subscribedTitle">Notification Events for Subscribed Shipments</div>
+      <div class="SubscribeCollapse">
+        <el-collapse v-model="CollapseActive" accordion @change="changeCollapse">
+          <el-collapse-item name="Milestone" :class="isMilestoneChecked ? 'border_ischecked' : ''">
+            <template #title>
+              <div class="flex">
+                <div class="collapse_left" :class="isMilestoneAdded ? 'text_ischecked' : ''">
+                  <div class="dot" :class="isMilestoneAdded ? 'dot_ischecked' : ''"></div>
+                  Milestone Update
+                </div>
+                <div class="collapse_edit">{{ isMilestoneAdded ? 'Edit' : 'Add' }}</div>
+              </div>
+            </template>
+            <div>
+              <AddRules
+                TitleType="Milestone"
+                @UnsavedCollapse="UnsavedCollapse"
+                :SystemList="subscribeInit"
+                @SavedAddedRules="SavedAddedRules"
+                ref="Milestone"
+              ></AddRules>
+            </div>
+          </el-collapse-item>
+          <el-collapse-item name="Container" :class="isContainerChecked ? 'border_ischecked' : ''">
+            <template #title>
+              <div class="flex">
+                <div class="collapse_left" :class="isContainerAdded ? 'text_ischecked' : ''">
+                  <div class="dot" :class="isContainerAdded ? 'dot_ischecked' : ''"></div>
+                  Container Status Update
+                </div>
+                <div class="collapse_edit">{{ isContainerAdded ? 'Edit' : 'Add' }}</div>
+              </div>
+            </template>
+            <div>
+              <AddRules
+                TitleType="Container"
+                ref="Container"
+                @UnsavedCollapse="UnsavedCollapse"
+                :SystemList="subscribeInit"
+                @SavedAddedRules="SavedAddedRules"
+              ></AddRules>
+            </div>
+          </el-collapse-item>
+          <el-collapse-item name="Departure" :class="isDepartureChecked ? 'border_ischecked' : ''">
+            <template #title>
+              <div class="flex">
+                <div class="collapse_left" :class="isDepartureAdded ? 'text_ischecked' : ''">
+                  <div class="dot" :class="isDepartureAdded ? 'dot_ischecked' : ''"></div>
+                  Departure/Arrival Delay
+                </div>
+                <div class="collapse_edit">{{ isDepartureAdded ? 'Edit' : 'Add' }}</div>
+              </div>
+            </template>
+            <div>
+              <AddRules
+                TitleType="Departure"
+                ref="Departure"
+                @UnsavedCollapse="UnsavedCollapse"
+                :SystemList="subscribeInit"
+                @SavedAddedRules="SavedAddedRules"
+              ></AddRules>
+            </div>
+          </el-collapse-item>
+          <el-collapse-item name="ETDChange" :class="isETDChangeChecked ? 'border_ischecked' : ''">
+            <template #title>
+              <div class="flex">
+                <div class="collapse_left" :class="isETDChangeAdded ? 'text_ischecked' : ''">
+                  <div class="dot" :class="isETDChangeAdded ? 'dot_ischecked' : ''"></div>
+                  ETD/ETA Change
+                </div>
+                <div class="collapse_edit">{{ isETDChangeAdded ? 'Edit' : 'Add' }}</div>
+              </div>
+            </template>
+            <div>
+              <AddRules
+                TitleType="ETDChange"
+                ref="ETDChange"
+                @UnsavedCollapse="UnsavedCollapse"
+                :SystemList="subscribeInit"
+                @SavedAddedRules="SavedAddedRules"
+              ></AddRules>
+            </div>
+          </el-collapse-item>
+        </el-collapse>
+      </div>
+      <div class="TableTitle">Added Rules</div>
+      <AddRSettingTableules
+        :propsType="true"
+        ref="AddRulesTable"
+        :ColumnsList="AddedRulesColumns"
+        @deleteAddedRules="deleteAddedRules"
+      ></AddRSettingTableules>
+      <div class="TableTitle subscribetitle">Subscribed Shipments</div>
+      <AddRSettingTableules
+        :ColumnsList="SubShipmentsColumns"
+        ref="SubShipmentsTable"
+      ></AddRSettingTableules>
+    </el-tab-pane>
+    <el-tab-pane label="Monitoring Settings" name="Monitoring Settings">
+      <div class="monitoring_flex">
+        <div class="subscribedTitle">Added Rules</div>
+        <el-button
+          class="el-button--main"
+          style="height: 40px"
+          v-if="tabledatalength != 0 && tabledatalength != null"
+          @click="ToCreateRule"
+          >+ Add Rule</el-button
+        >
+      </div>
+      <MonitoringTable @gettabledatalength="gettabledatalength"></MonitoringTable>
+    </el-tab-pane> -->
+  </el-tabs>
+</template>
+
+<style lang="scss" scoped>
+.Title {
+  display: flex;
+  height: 68px;
+  font-size: var(--font-size-6);
+  font-weight: 700;
+  padding: 0 24px;
+  align-items: center;
+}
+.TableTitle {
+  font-size: 18px;
+  font-weight: 700;
+  padding: 21px 24px 13px 24px;
+}
+.subscribetitle {
+  padding-top: 30px;
+}
+.SubscribeCollapse {
+  padding: 0 24px 24px 24px;
+  border-bottom: 1px solid var(--color-select-border);
+}
+:deep(.el-tabs__nav-wrap:after) {
+  height: 2px;
+}
+:deep(.el-tabs__item) {
+  color: var(--color-neutral-1);
+  font-weight: 400;
+  font-size: 18px;
+  padding: 0 64px;
+}
+.subscribedTitle {
+  color: var(--color-neutral-1);
+  font-weight: 700;
+  font-size: 18px;
+  padding: 0 24px;
+  margin: 23px 0 20px 0;
+}
+:deep(.el-collapse) {
+  border: none;
+}
+:deep(.el-collapse-item__wrap) {
+  border: none;
+  background-color: transparent;
+}
+:deep(.el-collapse-item) {
+  border: none;
+  width: 100%;
+  border-radius: 6px;
+  margin: 8px 0;
+}
+:deep(.el-collapse-item__header) {
+  border: 1px solid var(--color-select-border);
+  padding: 0 24px 0 12px;
+  border-radius: 6px;
+  color: var(--color-neutral-1);
+  font-size: 14px;
+  font-weight: 700;
+  height: 64px;
+  display: flex;
+  align-items: center;
+  background-color: transparent;
+}
+.border_ischecked {
+  border: 2px solid var(--color-theme);
+}
+:deep(.el-tabs__nav) {
+  padding-left: 24px;
+}
+:deep(.el-collapse-item__header):hover {
+  background-color: var(--border-hover-color);
+  border-color: var(--border-hover-color);
+}
+:deep(.el-collapse-item__header.is-active) {
+  background-color: var(--border-hover-color);
+  border-color: var(--border-hover-color);
+}
+.flex {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  width: 98%;
+  margin-right: 8px;
+}
+.collapse_left {
+  display: flex;
+  align-items: center;
+}
+.dot {
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  background-color: var(--color-dot-unchecked);
+  margin: 0 15px 0 9px;
+}
+.dot_ischecked {
+  background-color: var(--color-dot-checked);
+}
+.text_ischecked {
+  color: var(--color-theme);
+}
+.collapse_edit {
+  color: var(--color-theme);
+  font-weight: 400;
+}
+:deep(.el-collapse-item__arrow) {
+  width: 16px;
+  height: 16px;
+  background-image: url('../src/images/icon_expand_colorful.png');
+  background-size: contain;
+  background-repeat: no-repeat;
+  transform: rotate(0);
+}
+:deep(.el-collapse-item__arrow.is-active) {
+  transform: rotate(-180deg);
+}
+:deep(.el-icon svg) {
+  width: 0;
+}
+.monitoring_flex {
+  display: flex;
+  justify-content: space-between;
+  padding-right: 24px;
+  align-items: end;
+}
+.el-button--main {
+  margin-bottom: 4px;
+}
+
+.demo-tabs {
+  height: calc(100% - 68px);
+  :deep(.el-tabs__content) {
+    overflow-y: auto;
+  }
+}
+</style>

+ 1 - 0
src/views/SystemSettings/src/components/CreateNewrule/index.ts

@@ -0,0 +1 @@
+export { default } from './src/CreateNewrule.vue'

+ 264 - 0
src/views/SystemSettings/src/components/CreateNewrule/src/CreateNewrule.vue

@@ -0,0 +1,264 @@
+<script lang="ts" setup>
+import { ref, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+
+const router = useRouter()
+
+const notificationvalue = ref()
+const notificationoptions = ref([
+  {
+    label: 'Milestone Update',
+    value: 'Milestone'
+  },
+  {
+    label: 'Container Status Update',
+    value: 'Container'
+  },
+  {
+    label: 'Departure/Arrival Delay',
+    value: 'Departure'
+  },
+  {
+    label: 'ETD/ETA Change',
+    value: 'ETDChange'
+  }
+])
+const CancelRulesVisible = ref()
+const ChangeRulesVisible = ref()
+// 保存
+const CreateAddRulesRef = ref()
+const saveMonitoring = () => {
+  CreateAddRulesRef.value.Savesubscribe()
+}
+let select: any = ''
+const changenotification = (val: any) => {
+  select = val
+  if (notificationvalue.value != undefined) {
+    ChangeRulesVisible.value = true
+  }
+}
+const ChangeRulesVisibleOK = () => {
+  ChangeRulesVisible.value = false
+  notificationvalue.value = select
+  CreateAddRulesRef.value.clearData(select)
+}
+onMounted(() => {
+  if (sessionStorage.getItem('editTableoption') != null) {
+    notificationvalue.value = sessionStorage.getItem('editTableoption')
+    setTimeout(() => {
+      sessionStorage.removeItem('editTableoption')
+    }, 3000)
+  }
+})
+</script>
+<template>
+  <div class="Title">
+    <div>Create New Rule</div>
+    <div>
+      <el-button class="create_button" type="default" @click="CancelRulesVisible = true">
+        <span class="iconfont_icon">
+          <svg class="iconfont" aria-hidden="true">
+            <use xlink:href="#icon-icon_return_b"></use>
+          </svg>
+        </span>
+        Cancel
+      </el-button>
+      <!-- 取消保存 -->
+      <el-dialog v-model="CancelRulesVisible" width="480">
+        <div style="font-weight: 400">You have unsaved changes.</div>
+        <div style="font-weight: 400">Are you sure you want to leave this page?</div>
+        <template #footer>
+          <div class="dialog-footer">
+            <el-button type="default" @click="CancelRulesVisible = false" style="width: 100px"
+              >Cancel</el-button
+            >
+            <el-button class="el-button--warning" @click="router.back()" style="width: 100px">
+              OK
+            </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>
+            Unsaved Changes
+          </div>
+        </template>
+      </el-dialog>
+      <!-- 切换select -->
+      <el-dialog v-model="ChangeRulesVisible" width="480">
+        <div style="font-weight: 400">You have unsaved changes.</div>
+        <div style="font-weight: 400">Are you sure you want to leave this page?</div>
+        <template #footer>
+          <div class="dialog-footer">
+            <el-button type="default" @click="ChangeRulesVisible = false" style="width: 100px"
+              >Cancel</el-button
+            >
+            <el-button
+              class="el-button--warning"
+              @click="ChangeRulesVisibleOK"
+              style="width: 100px"
+            >
+              OK
+            </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>
+            Unsaved Changes
+          </div>
+        </template>
+      </el-dialog>
+      <el-button
+        :disabled="notificationvalue == '' || notificationvalue == undefined"
+        class="el-button--main create_button"
+        @click="saveMonitoring"
+      >
+        <span class="iconfont_icon">
+          <svg class="iconfont" aria-hidden="true">
+            <use xlink:href="#icon-icon_save_b"></use>
+          </svg>
+        </span>
+        Save
+      </el-button>
+    </div>
+  </div>
+  <div class="contetnt">
+    <div class="notification">
+      <div class="notification_title">Notification Events</div>
+      <div>
+        <el-select v-model="notificationvalue" placeholder="Select event" style="width: 400px">
+          <el-option
+            v-for="item in notificationoptions"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+            @mousedown="changenotification(item.value)"
+          />
+        </el-select>
+      </div>
+    </div>
+    <div class="setting">
+      <div class="setting_header">Setting</div>
+      <TableEmpty
+        v-if="notificationvalue == '' || notificationvalue == undefined"
+        style="margin-top: 100px"
+        EmptyTitle="Please select Notification Events first"
+      ></TableEmpty>
+      <div v-else>
+        <CreateAddRules ref="CreateAddRulesRef" :TitleType="notificationvalue"></CreateAddRules>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.Title {
+  display: flex;
+  background-color: var(--color-mode);
+  height: 68px;
+  font-size: var(--font-size-6);
+  font-weight: 700;
+  padding: 0 24px;
+  align-items: center;
+  justify-content: space-between;
+  border-bottom: 1px solid var(--color-border);
+}
+.create_button {
+  width: 115px;
+  height: 40px;
+}
+.contetnt {
+  padding: 0 24px;
+  margin-top: 16px;
+  margin-bottom: 14px;
+}
+.notification {
+  border: 1px solid var(--color-border);
+  padding: 16px;
+  border-radius: 12px;
+}
+.notification_title {
+  font-size: 18px;
+  font-weight: 700;
+  margin-bottom: 21px;
+}
+.setting {
+  margin-top: 8px;
+  border: 1px solid var(--color-border);
+  border-radius: 12px;
+  min-height: 741px;
+}
+.setting_header {
+  font-size: 18px;
+  font-weight: 700;
+  background-color: var(--color-dialog-header-bg);
+  height: 48px;
+  padding: 13px 16px;
+  border-radius: 12px 12px 0 0;
+}
+:deep(.el-collapse-item__header):hover {
+  background-color: var(--border-hover-color);
+  border-color: var(--border-hover-color);
+}
+:deep(.el-collapse-item__header.is-active) {
+  background-color: var(--border-hover-color);
+  border-color: var(--border-hover-color);
+}
+:deep(.el-collapse) {
+  border: none;
+}
+:deep(.el-collapse-item__wrap) {
+  border: none;
+  background-color: transparent;
+}
+:deep(.el-collapse-item) {
+  border: none;
+  width: 100%;
+  border-radius: 6px;
+  margin: 8px 0;
+}
+:deep(.el-collapse-item__header) {
+  border: 1px solid var(--color-select-border);
+  padding: 0 24px 0 12px;
+  border-radius: 6px;
+  color: var(--color-neutral-1);
+  font-size: 14px;
+  font-weight: 700;
+  height: 64px;
+  display: flex;
+  align-items: center;
+  background-color: transparent;
+}
+.cancel_header {
+  font-size: 18px;
+  font-weight: 700;
+  color: var(--color-neutral-1);
+  display: flex;
+  align-items: center;
+}
+.icon_warning {
+  width: 22px;
+  height: 22px;
+  margin-right: 0;
+  fill: var(--color-btn-warning-bg);
+}
+.iconfont_warning {
+  display: flex;
+  align-items: center;
+}
+:deep(header.el-dialog__header) {
+  background-color: #fff;
+}
+:deep(footer.el-dialog__footer) {
+  border-top: none;
+}
+</style>

+ 1 - 0
src/views/SystemSettings/src/components/MonitoringTable/index.ts

@@ -0,0 +1 @@
+export { default } from './src/MonitoringTable.vue'

+ 253 - 0
src/views/SystemSettings/src/components/MonitoringTable/src/MonitoringTable.vue

@@ -0,0 +1,253 @@
+<script setup lang="ts">
+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'
+
+const router = useRouter()
+
+const emits = defineEmits(['gettabledatalength'])
+
+const columnstest = ref([
+  {
+    field: '_rules_type_display',
+    title: 'Event',
+    type: 'normal',
+    formatter: ''
+  },
+  {
+    field: 'shipment_details',
+    title: 'Shipment Range',
+    type: 'normal',
+    formatter: ''
+  },
+  {
+    field: 'event_details',
+    title: 'Event Details',
+    type: 'normal',
+    formatter: ''
+  },
+  {
+    field: 'frequency_display',
+    title: 'Frequency',
+    type: 'normal',
+    formatter: ''
+  },
+  {
+    field: 'method_display',
+    title: 'Methods',
+    type: 'normal',
+    formatter: ''
+  }
+])
+const datatest = ref([])
+const tableData = ref<VxeGridProps<any>>({
+  border: true,
+  round: true,
+  minHeight: 700,
+  maxHeight: 700,
+  columns: [],
+  data: [],
+  scrollY: { enabled: true, oSize: 20, gt: 30 },
+  stripe: true,
+  emptyText: ' ',
+  showHeaderOverflow: true,
+  showOverflow: true,
+  headerRowStyle: {
+    backgroundColor: 'var(--color-table-header-bg)'
+  },
+  columnConfig: { resizable: true, useKey: true },
+  rowConfig: { isHover: true }
+})
+
+const tableRef = ref<VxeGridInstance | null>(null)
+const pageInfo = ref({ pageNo: 1, pageSize: 15, total: 0 })
+
+const handleColumns = (columns: any) => {
+  const newColumns = columns.map((item: any) => {
+    let curColumn: any = {
+      title: item.title,
+      field: item.field
+    }
+    // 格式化
+    if (item.formatter === 'date') {
+      curColumn = {
+        ...curColumn,
+        formatter: ({ cellValue }: any) => formatTimezone(cellValue)
+      }
+    }
+    return curColumn
+  })
+  return newColumns
+}
+// 获取表格列
+const getTableColumns = async () => {
+  tableData.value.columns = handleColumns(columnstest.value)
+  tableData.value.columns?.push({
+    title: 'Operation',
+    fixed: 'right',
+    width: 100,
+    slots: { default: 'action' }
+  })
+}
+// 获取表格数据
+const getTableData = async () => {
+  // 保存页长以及当前页码
+  tableData.value.data = datatest.value
+  $api
+    .MonitoringTable({
+      cp: pageInfo.value.pageNo,
+      ps: pageInfo.value.pageSize
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        pageInfo.value.total = Number(res.data.rc)
+        pageInfo.value.pageNo = res.data.cp
+        pageInfo.value.pageSize = res.data.ps
+        tableData.value.data = res.data.monitoringRules
+        emits('gettabledatalength', res.data.monitoringRules.length)
+      }
+    })
+    .finally(() => {})
+}
+
+// 实现行点击样式
+useRowClickStyle(tableRef)
+
+// 点击删除
+const handleDelete = (row: any) => {
+  row.visible = true
+}
+
+// 跳转Create New Rule页面
+const ToCreateRule = () => {
+  router.push({
+    path: '/SystemSettings/createnewrule',
+    query: {}
+  })
+  sessionStorage.setItem('activeTab', 'Monitoring Settings')
+}
+
+// 删除表格数据
+const deleteMoniTable = (row: any) => {
+  row.visible = false
+  $api
+    .deleteMonitoringTable({
+      id: row.id
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        tableData.value.data = tableData.value.data?.filter((item) => item.id !== row.id)
+        emits('gettabledatalength', tableData.value.data?.length)
+      }
+    })
+}
+
+// 编辑表格数据
+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: {}
+  })
+}
+
+onMounted(() => {
+  getTableColumns()
+  getTableData()
+})
+</script>
+
+<template>
+  <div class="SettingTable">
+    <vxe-grid ref="tableRef" :style="{ border: 'none' }" v-bind="tableData">
+      <!-- 空数据时的插槽 -->
+      <template #empty v-if="tableData.data.length === 0">
+        <TableEmpty EmptyTitle="Customize your shipment tracking preferences.">
+          <template #suggestion>
+            <el-button class="el-button--main" @click="ToCreateRule">+ Add Rule</el-button>
+          </template>
+        </TableEmpty>
+      </template>
+      <template #action="{ row }">
+        <el-button class="el-button--blue" style="height: 24px" @click="handleedit(row)">
+          <span class="font_family icon-icon_edit_b"></span>
+        </el-button>
+        <el-popover trigger="click" :visible="row.visible" placement="left" :width="480">
+          <div class="delete_title">
+            <span class="font_family icon_alert icon-icon_tipsfilled_b"></span>
+            Delete Rules
+          </div>
+          <p class="delete_content">Are you sure to delete this notification event?</p>
+          <div style="text-align: right; margin: 0; padding: 8px">
+            <el-button style="width: 100px" class="el-button--default" @click="row.visible = false"
+              >cancel</el-button
+            >
+            <el-button style="width: 100px" type="warning" @click="deleteMoniTable(row)">
+              OK
+            </el-button>
+          </div>
+          <template #reference>
+            <el-button @click="handleDelete(row)" class="el-button--blue" style="height: 24px">
+              <span class="font_family icon-icon_delete_b"></span>
+            </el-button>
+          </template>
+        </el-popover>
+      </template>
+    </vxe-grid>
+  </div>
+  <div class="pagination">
+    <span>Total {{ pageInfo.total }}</span>
+    <el-pagination
+      v-model:current-page="pageInfo.pageNo"
+      v-model:page-size="pageInfo.pageSize"
+      layout="prev, pager, next"
+      :total="pageInfo.total"
+      :pager-count="5"
+      @size-change="getTableData"
+      @current-change="getTableData"
+      background
+    />
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.SettingTable {
+  padding: 0 24px 0 24px;
+}
+.icon-icon_delete_b::before {
+  color: var(--color-btn-danger-bg);
+}
+.icon_alert::before {
+  color: var(--color-btn-warning-bg);
+}
+.delete_title {
+  font-size: 18px;
+  font-weight: 700;
+  padding: 20px 16px;
+  color: var(--color-neutral-1);
+}
+.delete_content {
+  font-size: 14px;
+  font-weight: 400;
+  color: var(--color-neutral-1);
+  padding: 15px 0 33px 37px;
+}
+.pagination {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  border: 1px solid var(--color-border);
+  border-top: none;
+  margin: 0 24px 0 24px;
+  padding: 4px 8px;
+  border-radius: 0 0 6px 6px;
+}
+:deep(.el-icon svg) {
+  width: 1em !important;
+}
+</style>

+ 369 - 0
src/views/SystemSettings/src/components/PersonalProfile.vue

@@ -0,0 +1,369 @@
+<script setup lang="ts">
+import dayjs from 'dayjs'
+import { isEuropean, getDateFormat } from '@/utils/tools'
+import ChangePasswordDialog from '@/views/Layout/src/components/Header/components/ChangePasswordDialog.vue'
+import { useUserStore } from '@/stores/modules/user'
+import { useThemeStore } from '@/stores/modules/theme'
+
+const themeStore = useThemeStore()
+const userStore = useUserStore()
+const form = reactive({
+  firstName: userStore.userInfo?.first_name,
+  lastName: userStore.userInfo?.last_name,
+  username: userStore.userInfo?.uname,
+  email: userStore.userInfo?.email,
+  password: '**************'
+})
+
+const segmented = ref('dateTime')
+const handleSegmented = (type: string) => {
+  segmented.value = type
+}
+
+const changePasswordDialogRef = ref()
+const handleChangePassword = () => {
+  changePasswordDialogRef.value.openDialog()
+}
+
+const monthMap = {
+  'MMM/DD/YYYY': 'MM/DD/YYYY',
+  'DD/MMM/YYYY': 'DD/MM/YYYY',
+  'YYYY-MMM-DD': 'YYYY-MM-DD'
+}
+
+const dateFormat = ref(monthMap[userStore.dateFormat] || userStore.dateFormat)
+const monthFormat = ref(userStore.dateFormat)
+const dateFormatExample = computed(() => {
+  return {
+    'MM/DD/YYYY': [
+      {
+        label: dayjs().format('MM/DD/YYYY'),
+        value: 'MM/DD/YYYY'
+      },
+      {
+        label: dayjs().format('MMM/DD/YYYY'),
+        value: 'MMM/DD/YYYY'
+      }
+    ],
+    'DD/MM/YYYY': [
+      {
+        label: dayjs().format('DD/MM/YYYY'),
+        value: 'DD/MM/YYYY'
+      },
+      {
+        label: dayjs().format('DD/MMM/YYYY'),
+        value: 'DD/MMM/YYYY'
+      }
+    ],
+    'YYYY-MM-DD': [
+      {
+        label: dayjs().format('YYYY-MM-DD'),
+        value: 'YYYY-MM-DD'
+      },
+      {
+        label: dayjs().format('YYYY-MMM-DD'),
+        value: 'YYYY-MMM-DD'
+      }
+    ]
+  }
+})
+
+const handleDateFormat = (value: string) => {
+  monthFormat.value = dateFormatExample.value[value][0].value
+}
+
+const initNumbersFormat = () => {
+  return userStore.userInfo?.numbers_format || (isEuropean() ? 'European' : 'US/UK')
+}
+const numbersFormat = ref(initNumbersFormat())
+
+const saveConfig = (model: string) => {
+  let params = {}
+  if (model === 'profile') {
+    params = {
+      save_model: 'profile',
+      first_name: form.firstName,
+      last_name: form.lastName
+    }
+  } else {
+    params = {
+      save_model: 'no_profile',
+      date_format: monthFormat.value,
+      numbers_format: numbersFormat.value
+    }
+  }
+  $api.saveUserInfo(params).then((res: any) => {
+    if (res.code === 200) {
+      const updatedInfo =
+        model === 'profile'
+          ? {
+              ...userStore.userInfo,
+              first_name: form.firstName,
+              last_name: form.lastName
+            }
+          : {
+              ...userStore.userInfo,
+              date_format: monthFormat.value,
+              numbers_format: numbersFormat.value
+            }
+
+      userStore.setUserInfo(updatedInfo)
+      ElMessage.success('Save successfully')
+    } else {
+      ElMessage.error('Save failed')
+    }
+  })
+}
+</script>
+
+<template>
+  <div class="personal-profile">
+    <div class="basic-information">
+      <div class="title">Basic Information</div>
+      <div class="content">
+        <div class="row">
+          <div class="item">
+            <p class="label">First Name</p>
+            <el-input size="large" v-model="form.firstName" placeholder="Please enter..." />
+          </div>
+          <div class="item">
+            <p class="label">Last Name</p>
+            <el-input size="large" v-model="form.lastName" placeholder="Please enter..." />
+          </div>
+        </div>
+        <div class="row">
+          <div class="item">
+            <p class="label">User Name</p>
+            <el-input size="large" :disabled="true" v-model="form.username" />
+          </div>
+          <div class="item">
+            <p class="label">Email</p>
+            <el-input size="large" :disabled="true" v-model="form.email" />
+          </div>
+        </div>
+        <div class="row">
+          <div class="item">
+            <p class="label">
+              Password
+              <span style="margin: 0 2px 0 8px" class="font_family icon-icon_time_b"></span>
+              <span>Your password will be expire in</span>
+              <span style="margin-left: 4px; color: var(--color-theme)"
+                >{{ userStore.expireDay }} day(s)</span
+              >
+            </p>
+            <div class="password-change">
+              <el-input
+                size="large"
+                type="password"
+                style="width: 330px"
+                :disabled="true"
+                v-model="form.password"
+              />
+              <el-button
+                @click="handleChangePassword"
+                class="el-button--main el-button--pain-theme"
+                plain
+                size="large"
+                >Change Password</el-button
+              >
+            </div>
+          </div>
+        </div>
+        <div class="row">
+          <el-button @click="saveConfig('profile')" class="el-button--dark save-icon" size="large"
+            >Save</el-button
+          >
+        </div>
+      </div>
+    </div>
+    <div class="personal-preferences">
+      <div class="title">Personal Preferences</div>
+      <div class="segmented">
+        <div
+          style="width: 121px"
+          :class="{ 'is-active': segmented === 'dateTime' }"
+          @click="handleSegmented('dateTime')"
+          class="item"
+        >
+          Date & Time
+        </div>
+        <div
+          style="width: 162px"
+          :class="{ 'is-active': segmented === 'numbersFormat' }"
+          @click="handleSegmented('numbersFormat')"
+          class="item"
+        >
+          Numbers Format
+        </div>
+      </div>
+      <div class="date-format" v-if="segmented === 'dateTime'">
+        <div class="title">Date & Time</div>
+        <div class="content">
+          <el-radio-group v-model="dateFormat" label="string" @change="handleDateFormat">
+            <el-row>
+              <el-col v-for="(list, key) in dateFormatExample" :key="key">
+                ><el-radio :value="key">
+                  <template #default>
+                    <span>{{ key }}</span>
+                    <el-select
+                      style="margin-left: 28px; width: 320px"
+                      v-if="dateFormat === key"
+                      size="large"
+                      v-model="monthFormat"
+                    >
+                      <el-option
+                        v-for="item in list"
+                        :key="item.value"
+                        :label="item.label"
+                        :value="item.value"
+                      ></el-option>
+                    </el-select>
+                  </template>
+                </el-radio>
+              </el-col>
+
+              <el-col>
+                <el-button
+                  @click="saveConfig('no_profile')"
+                  style="padding: 0 40px"
+                  class="el-button--dark save-icon"
+                  size="large"
+                  >Save</el-button
+                >
+              </el-col>
+            </el-row>
+          </el-radio-group>
+        </div>
+      </div>
+      <div class="numbers-format" v-if="segmented === 'numbersFormat'">
+        <div class="title">Numbers Format</div>
+        <div class="content">
+          <el-radio-group v-model="numbersFormat" label="string">
+            <el-row>
+              <el-col
+                ><el-radio value="US/UK">
+                  <template #default>
+                    <span>1,234.56 (US/UK)</span>
+                  </template>
+                </el-radio></el-col
+              >
+              <el-col
+                ><el-radio value="European">
+                  <template #default>
+                    <span>1.234,56 (European)</span>
+                  </template>
+                </el-radio></el-col
+              >
+              <el-col>
+                <el-button
+                  @click="saveConfig('no_profile')"
+                  style="padding: 0 40px"
+                  class="el-button--dark save-icon"
+                  size="large"
+                  >Save</el-button
+                >
+              </el-col>
+            </el-row>
+          </el-radio-group>
+        </div>
+      </div>
+    </div>
+
+    <ChangePasswordDialog ref="changePasswordDialogRef"></ChangePasswordDialog>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.personal-profile {
+  padding: 24px;
+  padding-top: 9px;
+  .basic-information,
+  .personal-preferences {
+    border: 1px solid var(--color-border);
+    border-radius: 12px;
+    padding: 16px 16px 32px;
+    & > .title {
+      margin-bottom: 21px;
+      font-size: 18px;
+      font-weight: 700;
+    }
+  }
+}
+.basic-information {
+  .content {
+    .row {
+      display: flex;
+      gap: 8px;
+      .item {
+        flex: 1;
+        margin-bottom: 16px;
+        .label {
+          margin-bottom: 4px;
+          font-size: 12px;
+          color: var(--color-neutral-2);
+          & > span {
+            font-size: 12px;
+            color: var(--color-neutral-3);
+          }
+        }
+        .password-change {
+          display: flex;
+          gap: 8px;
+        }
+      }
+    }
+    .save-icon {
+      margin-top: 16px;
+      padding: 0 40px;
+    }
+  }
+}
+.personal-profile {
+  div.personal-preferences {
+    margin-top: 16px;
+    padding-bottom: 8px;
+    .segmented {
+      width: 291px;
+      height: 40px;
+      margin-bottom: 8px;
+      padding: 4px;
+      border-radius: 12px;
+      background-color: var(--color-personal-preference-bg);
+      .item {
+        display: inline-block;
+        height: 32px;
+        line-height: 32px;
+        font-weight: 700;
+        font-size: 16px;
+        text-align: center;
+        border-radius: 6px;
+        color: var(--color-neutral-2);
+        cursor: pointer;
+        &.is-active {
+          background-color: var(--color-table-stripe-bg);
+          color: var(--color-neutral-1);
+        }
+      }
+    }
+    .date-format,
+    .numbers-format {
+      padding: 0px 16px 32px;
+      background-color: var(--color-personal-preference-bg);
+      border-radius: 12px;
+      .title {
+        height: 40px;
+        font-size: 14px;
+        font-weight: 700;
+        line-height: 40px;
+      }
+      .el-col {
+        margin-bottom: 8px;
+        &:last-child {
+          margin-bottom: 0;
+          margin-top: 32px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 1 - 0
src/views/SystemSettings/src/components/SettingTable/index.ts

@@ -0,0 +1 @@
+export { default } from './src/SettingTable.vue'

+ 211 - 0
src/views/SystemSettings/src/components/SettingTable/src/SettingTable.vue

@@ -0,0 +1,211 @@
+<script setup lang="ts">
+import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
+import { useRowClickStyle } from '@/hooks/rowClickStyle'
+import { formatTimezone } from '@/utils/tools'
+import { ref, onMounted } from 'vue'
+interface ColumnsListItem {
+  field: String
+  title: String
+  type: String
+  formatter: String
+}
+const props = defineProps({
+  propsType: Boolean,
+  ColumnsList: Array<ColumnsListItem>
+})
+
+const columnstest = ref(props.ColumnsList)
+
+const tableData = ref<VxeGridProps<any>>({
+  border: true,
+  round: true,
+  minHeight: 70,
+  maxHeight: 500,
+  columns: [],
+  data: [],
+  scrollY: { enabled: true, oSize: 20, gt: 30 },
+  stripe: true,
+  emptyText: ' ',
+  showHeaderOverflow: true,
+  showOverflow: true,
+  headerRowStyle: {
+    backgroundColor: 'var(--color-table-header-bg)'
+  },
+  columnConfig: { resizable: true, useKey: true },
+  rowConfig: { isHover: true }
+})
+const pageInfo = ref({ pageNo: 1, pageSize: 10, total: 0 })
+
+const tableRef = ref<VxeGridInstance | null>(null)
+
+const handleColumns = (columns: any) => {
+  const newColumns = columns.map((item: any) => {
+    let curColumn: any = {
+      title: item.title,
+      field: item.field
+    }
+    // 格式化
+    if (item.formatter === 'date') {
+      curColumn = {
+        ...curColumn,
+        formatter: ({ cellValue }: any) => formatTimezone(cellValue)
+      }
+    }
+    return curColumn
+  })
+  return newColumns
+}
+// 获取表格列
+const getTableColumns = async () => {
+  tableData.value.columns = handleColumns(columnstest.value)
+  if (props.propsType) {
+    tableData.value.columns?.push({
+      title: 'Action',
+      fixed: 'right',
+      width: 80,
+      slots: { default: 'action' }
+    })
+  }
+}
+// 获取表格数据
+const getTableData = (val: any) => {
+  tableData.value.data = val.tableData
+  if (!props.propsType) {
+    pageInfo.value.pageNo = val.cp
+    pageInfo.value.pageSize = val.ps
+    pageInfo.value.total = val.rc
+  }
+}
+
+// 实现行点击样式
+useRowClickStyle(tableRef)
+
+const emits = defineEmits(['deleteAddedRules'])
+// 点击删除
+const handleDelete = (row: any) => {
+  row.visible = false
+  $api
+    .DeleteAddedRules({
+      rules_type: row.Event
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        tableData.value.data = tableData.value.data?.filter((item) => item.Event !== row.Event)
+        emits('deleteAddedRules', row.Event)
+      }
+    })
+}
+
+onMounted(() => {
+  getTableColumns()
+})
+defineExpose({
+  getTableData
+})
+const getpaginationTableData = () => {
+  $api
+    .SubscribePagination({
+      cp: pageInfo.value.pageNo,
+      ps: pageInfo.value.pageSize
+    })
+    .then((res: any) => {
+      if (res.code === 200) {
+        pageInfo.value.total = Number(res.data.rc)
+        pageInfo.value.pageNo = res.data.cp
+        pageInfo.value.pageSize = res.data.ps
+        tableData.value.data = res.data.tableData
+      }
+    })
+}
+</script>
+
+<template>
+  <div class="SettingTable">
+    <vxe-grid
+      :class="props.propsType ? 'radius-bottom' : ''"
+      ref="tableRef"
+      :style="{ border: 'none' }"
+      v-bind="tableData"
+    >
+      <template #empty>
+        <div class="empty">No data</div>
+      </template>
+
+      <template #action="{ row }">
+        <el-popover :visible="row.visible" placement="left" :width="480">
+          <div class="delete_title">
+            <span class="font_family icon_alert icon-icon_tipsfilled_b"></span>
+            Delete Rules
+          </div>
+          <p class="delete_content">Are you sure to delete this notification event?</p>
+          <div style="text-align: right; margin: 0; padding: 8px">
+            <el-button style="width: 100px" tupe="default" @click="row.visible = false"
+              >cancel</el-button
+            >
+            <el-button style="width: 100px" type="warning" @click="handleDelete(row)">
+              OK
+            </el-button>
+          </div>
+          <template #reference>
+            <el-button @click="row.visible = true" class="el-button--blue" style="height: 24px">
+              <span class="font_family icon-icon_delete_b"></span>
+            </el-button>
+          </template>
+        </el-popover>
+      </template>
+    </vxe-grid>
+  </div>
+  <div class="pagination" v-if="!props.propsType">
+    <span>Total {{ pageInfo.total }}</span>
+    <el-pagination
+      v-model:current-page="pageInfo.pageNo"
+      v-model:page-size="pageInfo.pageSize"
+      layout="prev, pager, next"
+      :total="pageInfo.total"
+      :pager-count="5"
+      @size-change="getpaginationTableData"
+      @current-change="getpaginationTableData"
+      background
+    />
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.SettingTable {
+  padding: 0 24px 0 24px;
+}
+.radius-bottom {
+  border-radius: 6px 6px 0 0;
+}
+.font_family::before {
+  color: var(--color-btn-danger-bg);
+}
+.icon_alert::before {
+  color: var(--color-btn-warning-bg);
+}
+.delete_title {
+  font-size: 18px;
+  font-weight: 700;
+  padding: 20px 16px;
+  color: var(--color-neutral-1);
+}
+.delete_content {
+  font-size: 14px;
+  font-weight: 400;
+  color: var(--color-neutral-1);
+  padding: 15px 0 33px 37px;
+}
+.pagination {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  border: 1px solid var(--color-border);
+  border-top: none;
+  margin: 0 24px 20px 24px;
+  padding: 4px 8px;
+  border-radius: 0 0 6px 6px;
+}
+:deep(.el-icon svg) {
+  width: 1em !important;
+}
+</style>

二进制
src/views/SystemSettings/src/images/icon_collapse_colorful.png


二进制
src/views/SystemSettings/src/images/icon_expand_colorful.png


二进制
src/views/SystemSettings/src/images/xia.png


+ 13 - 3
src/views/Tracking/src/components/PublicTracking/src/components/BasicInformation.vue

@@ -1,5 +1,6 @@
 <script setup lang="ts">
 import XEClipboard from 'xe-clipboard'
+import { formatNumber } from '@/utils/tools'
 
 const props = defineProps({
   data: Object
@@ -77,6 +78,15 @@ const allData: any = ref({
     }
   ]
 })
+
+// 从开始位置截取字符串,并格式化数字(因为后端接口返回的内容带有字符串,所以需要截取)
+const substringFromStart = (str: string, start: number) => {
+  if (!str) {
+    return formatNumber(0, 3)
+  }
+  return formatNumber(Number(str.slice(0, start)), 3)
+}
+
 const convertData = (data: any) => {
   return {
     basicInformation: {
@@ -128,15 +138,15 @@ const convertData = (data: any) => {
       },
       {
         label: 'G. Weight',
-        content: data.packing['G. Weight'] || '--'
+        content: substringFromStart(data.packing['G. Weight'], -4) + ' KGS'
       },
       {
         label: 'Ch. Weight',
-        content: data.packing['Ch. Weight'] || '--'
+        content: substringFromStart(data.packing['Ch. Weight'], -4) + ' KGS'
       },
       {
         label: 'Volume',
-        content: data.packing.Volume || '--'
+        content: substringFromStart(data.packing['Volume'], -4) + ' CBM'
       }
     ]
   }

部分文件因为文件数量过多而无法显示