Forráskód Böngészése

Merge branch 'dev_zyh' of United_Software/k_online_ui into dev

Jack Zhou 5 hónapja
szülő
commit
59276b3151

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

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

+ 0 - 1
src/components/MoreFilters/src/MoreFilters.vue

@@ -1018,7 +1018,6 @@ import { useThemeStore } from '@/stores/modules/theme'
 const themeStore = useThemeStore()
 
 const moreFiltersGuideImg = computed(() => {
-  console.log('props.pageMode', props.pageMode)
   if (props.pageMode === 'tracking') {
     return themeStore.theme === 'dark' ? trackingMoreFiltersImgDark : trackingMoreFiltersImgLight
   } else {

+ 68 - 17
src/components/NotificationMessageCard/src/NotificationMessageCard.vue

@@ -4,7 +4,7 @@ import PasswordCard from './components/PasswordCard.vue'
 import FeatureUpdateCard from './components/FeatureUpdateCard.vue'
 import { useNotificationMessage } from '@/stores/modules/notificationMessage'
 import { cloneDeep } from 'lodash'
-import { DynamicScroller, DynamicScrollerItem } from 'vue3-virtual-scroller'
+// import { DynamicScroller, DynamicScrollerItem } from 'vue3-virtual-scroller'
 import 'vue3-virtual-scroller/dist/vue3-virtual-scroller.css'
 import { formatTimezone } from '@/utils/tools'
 
@@ -27,7 +27,13 @@ const props = withDefaults(
 )
 const pageData = ref<any[]>([])
 
-const emit = defineEmits<{ seeAll: []; hasCardRead: []; viewMore: []; jumpTracking: [] }>()
+const emit = defineEmits<{
+  seeAll: []
+  hasCardRead: []
+  viewMore: []
+  jumpTracking: []
+  loading: []
+}>()
 const handleSeeAll = () => {
   emit('seeAll')
 }
@@ -166,9 +172,7 @@ const setAllMessageRead = () => {
     item.info.isRead = true
   })
 }
-defineExpose({
-  setAllMessageRead
-})
+
 // 定时将消息卡片未读的置为已读,五分钟一次
 let timer = null
 onMounted(() => {
@@ -195,21 +199,55 @@ const parentHeight = computed(() => {
 const scrollParentBoxStyle = computed(() => {
   return props.topOffset ? { height: `${parentHeight.value}px` } : {}
 })
+
+const scrollContainerRef = ref<HTMLElement | null>(null)
+const loading = ref(false)
+const finished = ref(false)
+const prevScrollTop = ref(0)
+
+const loadMore = async () => {
+  if (loading.value || finished.value) return
+  loading.value = true
+  emit('loading')
+}
+
+const onScroll = () => {
+  const el = scrollContainerRef.value
+  if (!el || loading.value || finished.value) return
+
+  prevScrollTop.value = el.scrollTop + 50
+
+  const threshold = 50 // 提前50px触发
+  if (el.scrollHeight - el.scrollTop - el.clientHeight <= threshold) {
+    loadMore()
+  }
+}
+
+const sentinel = ref<HTMLElement | null>(null)
+
+const adjustScrollTop = () => {
+  const el = scrollContainerRef.value
+  if (el) {
+    // 如果滚动容器存在,调整其 scrollTop
+    el.scrollTop = prevScrollTop.value
+  }
+}
+
+defineExpose({
+  loading,
+  finished,
+  scrollContainerRef,
+  adjustScrollTop,
+  setAllMessageRead
+})
 </script>
 
 <template>
-  <DynamicScroller
-    class="scroller"
-    :style="scrollParentBoxStyle"
-    :items="pageData"
-    :min-item-size="100"
-    key-field="id"
-  >
-    <template v-slot="{ item, index, active }">
-      <DynamicScrollerItem
+  <div class="scroller" :style="scrollParentBoxStyle" ref="scrollContainerRef" @scroll="onScroll">
+    <template v-for="item in pageData" :key="item.id">
+      <div
         :class="{ 'scroll-padding': props.isScrollPadding }"
         :item="item"
-        :active="active"
         :size-dependencies="[item.info.isRead]"
       >
         <div
@@ -234,9 +272,18 @@ const scrollParentBoxStyle = computed(() => {
             {{ formatTimezone(item.info.first_notifiation_date) }}
           </div>
         </div>
-      </DynamicScrollerItem>
+      </div>
     </template>
-  </DynamicScroller>
+    <div
+      class="footer"
+      v-if="pageData?.[0]?.notificationType !== 'feature' && pageData?.length > 0"
+    >
+      <el-divider v-if="loading"> loading... </el-divider>
+      <el-divider v-if="finished && pageData.length > 0">
+        Only display the message data within three months
+      </el-divider>
+    </div>
+  </div>
 </template>
 
 <style lang="scss" scoped>
@@ -246,6 +293,10 @@ const scrollParentBoxStyle = computed(() => {
   .scroll-padding {
     padding: 0px 140px 0px 16px;
   }
+  .footer {
+    padding: 0 16px;
+    text-align: center;
+  }
 }
 .notification-message-card {
   position: relative;

+ 1 - 1
src/components/NotificationMessageCard/src/components/EventCard.vue

@@ -161,7 +161,7 @@ const jumpTracking = (data: EventCardPropsData) => {
           dayjs(data.info.time).format('MMM DD, YYYY HH:mm')
         }}</span>
         <span>{{ getTimezone(data.info.timezone, data.info.time) }}</span>
-        <span>&nbsp;({{ data.info.delayTimeTip }})</span>
+        <span v-if="data.info.delayTimeTip">&nbsp;({{ data.info.delayTimeTip }})</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>

+ 8 - 8
src/stores/modules/notificationMessage.ts

@@ -49,15 +49,15 @@ export const useNotificationMessage = defineStore('notificationMessage', {
     async markMessageAsRead() {
       if (this.readCardMap.length === 0) return
 
-      await $api.setMessageRead({ id: this.readCardMap }).then((res) => {
-        if (res.code === 200) {
-          this.readCardMap = []
-          localStorage.setItem('readCardMap', JSON.stringify(this.readCardMap))
+      // await $api.setMessageRead({ id: this.readCardMap }).then((res) => {
+      //   if (res.code === 200) {
+      //     this.readCardMap = []
+      //     localStorage.setItem('readCardMap', JSON.stringify(this.readCardMap))
 
-          // 在将消息标记为已读后,再次检查是否有新消息
-          this.hasUnreadMessages()
-        }
-      })
+      //     // 在将消息标记为已读后,再次检查是否有新消息
+      //     this.hasUnreadMessages()
+      //   }
+      // })
     },
     clearData() {
       this.notificationMsgList = []

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

@@ -23,8 +23,6 @@ const route = useRoute()
 const router = useRouter()
 const headerSearch = useHeaderSearch()
 
-const trainingCardRef = ref()
-
 // 切换系统主题颜色
 const toggleThemeMode = (theme: string) => {
   themeStore.toggleTheme(theme, true)
@@ -199,6 +197,24 @@ onMounted(() => {
     notificationMsgStore.hasUnreadMessages()
   }
 })
+const themePopoverRef = ref()
+// // 点击外部关闭逻辑
+const handleClickOutside = (event) => {
+  const popoverElementRef = themePopoverRef.value?.popperRef?.contentRef
+  if (popoverElementRef && !popoverElementRef.contains(event.target)) {
+    isPopoverVisible.value = false
+  }
+}
+
+const handleShowThemePopover = () => {
+  isPopoverVisible.value = true
+  document.addEventListener('click', handleClickOutside)
+}
+
+const handleCloseThemePopover = () => {
+  isPopoverVisible.value = false
+  document.removeEventListener('click', handleClickOutside)
+}
 </script>
 
 <template>
@@ -244,10 +260,12 @@ onMounted(() => {
         placement="bottom-end"
         :width="400"
         trigger="click"
+        themePopoverRef
+        ref="themePopoverRef"
         :visible="isPopoverVisible"
         popper-class="toggle-theme-popover"
-        @show="isPopoverVisible = true"
-        @hide="isPopoverVisible = false"
+        @show="handleShowThemePopover"
+        @hide="handleCloseThemePopover"
       >
         <div>
           <!-- Popover content remains the same -->

+ 23 - 7
src/views/Layout/src/components/Header/components/NotificationDrawer.vue

@@ -19,16 +19,25 @@ const notificationTypeList = ref({
 })
 
 const notificationList = ref<any[]>([])
-
+const notificationMessageCardRef = ref()
+const pageInfo = ref({
+  cp: 1,
+  ps: 30
+})
 const getNotificationList = () => {
   loading.value = true
   $api
     .getNotificationList({
-      rules_type: notificationType.value
+      rules_type: notificationType.value,
+      cp: pageInfo.value.cp,
+      ps: pageInfo.value.ps
     })
     .then((res) => {
       if (res.code === 200) {
-        notificationList.value = res.data
+        notificationList.value = [...notificationList.value, ...res.data]
+        if (res.data.length === 0 || res.data.length < pageInfo.value.ps) {
+          notificationMessageCardRef.value.finished = true
+        }
         // nextTick(() => {
         //   init()
         // })
@@ -36,6 +45,10 @@ const getNotificationList = () => {
     })
     .finally(() => {
       loading.value = false
+      pageInfo.value.cp += 1
+      notificationMessageCardRef.value.loading = false
+
+      notificationMessageCardRef.value.adjustScrollTop()
     })
 }
 
@@ -45,7 +58,7 @@ const handleMarkAllRead = () => {
     $api.setMessageRead({ read_type: true }).then((res) => {
       if (res.code === 200) {
         notificationMsgStore.hasUnreadMessages()
-        notificationMessageCardsRef.value.setAllMessageRead()
+        notificationMessageCardRef.value.setAllMessageRead()
       }
     })
   } catch (error) {
@@ -73,9 +86,8 @@ const closeDrawer = () => {
   notificationList.value = []
   notificationType.value = 'all'
   notificationMsgStore.markMessageAsRead()
+  pageInfo.value.cp = 1
 }
-
-const notificationMessageCardsRef = ref()
 </script>
 
 <template>
@@ -131,9 +143,10 @@ const notificationMessageCardsRef = ref()
             @see-all="drawerRef.handleClose()"
             @view-more="drawerRef.handleClose()"
             @jump-tracking="drawerRef.handleClose()"
+            @loading="getNotificationList"
             :data="notificationList"
             :topOffset="185"
-            ref="notificationMessageCardsRef"
+            ref="notificationMessageCardRef"
           />
         </div>
       </el-scrollbar>
@@ -223,6 +236,9 @@ div.layout-toolbar {
     }
   }
 }
+:deep(.footer) {
+  margin-top: 40px;
+}
 </style>
 <style lang="scss">
 div.layout-toolbar {

+ 97 - 24
src/views/SystemMessage/src/SystemMessage.vue

@@ -13,19 +13,25 @@ const tabCountList = ref([0, 0, 0, 0, 0])
 const curTabCount = ref([])
 
 const handleCount = (count: number) => {
-  if (!count) return ''
+  if (!count || count < 0) return ''
   return count > 99 ? '99+' : count
 }
+// 计算未读卡片数量
+const unreadCardCount = computed(() => {
+  const curEventNotificationIndex = navList.findIndex((navItem) => {
+    return navItem === activeCardTypeName.value
+  })
+  return tabCountList.value[curEventNotificationIndex] - notificationMsgStore.readCardMap.length
+})
 const handleShowCount = (typeName: string, index: number) => {
   // 在切换type类型时,防止点击的类型值为点击之前类型的值
   if (curTabCount.value?.[index] > -1) {
     return curTabCount.value[index]
   }
-  const count = tabCountList.value[index]
   if (typeName === activeCardTypeName.value) {
-    return handleCount(unreadNotificationList.value.length)
+    return handleCount(unreadCardCount.value)
   }
-  return handleCount(count)
+  return handleCount(tabCountList.value[index])
 }
 
 const navList = [
@@ -52,49 +58,106 @@ const setActiveItem = (item: string) => {
     tabCountList.value.length - 1
   )
 
+  // 滚动到顶部
+  if (notificationMessageCardRef.value) {
+    notificationMessageCardRef.value.scrollContainerRef.scrollTo(0, 0)
+  }
   activeCardTypeName.value = item
   sessionStorage.setItem('activeCardTypeName', item)
   activeTabName.value = 'All Notifications'
-  getNotificationList()
+  getNotificationList(activeTabName.value, true)
 }
 
 const loading = ref(false)
 const notificationList = ref<any[]>([])
 
-const unreadNotificationList = computed(() => {
-  return notificationList.value.filter((item) => !item.info.isRead)
-})
-const readNotificationList = computed(() => {
-  return notificationList.value.filter((item) => item.info.isRead)
+const unreadNotificationList = ref<any[]>([])
+
+const readNotificationList = ref<any[]>([])
+const pageInfo = ref({
+  cp: 0,
+  ps: 30
 })
-const getNotificationList = async () => {
+const getNotificationList = async (
+  tabType: string = 'All Notifications',
+  isChangeNav: boolean = false
+) => {
   loading.value = true
+  if (isChangeNav) {
+    pageInfo.value.cp = 1
+    notificationMessageCardRef.value.finished = false
+    unreadNotificationList.value = []
+    readNotificationList.value = []
+    notificationList.value = []
+    if (activeCardTypeName.value === 'Feature Update') {
+      pageInfo.value.ps = 100
+    } else {
+      pageInfo.value.ps = 30
+    }
+  } else {
+    pageInfo.value.cp += 1
+  }
   const rulesType = Object.entries(notificationTypeList.value).find(
     (item) => item[1] === activeCardTypeName.value
   )?.[0]
+  let info_type: null | boolean = null
+  if (tabType === 'All Notifications') {
+    info_type = null
+  } else if (tabType === 'Unread') {
+    info_type = true
+  } else {
+    info_type = false
+  }
+
   try {
     await notificationMsgStore.markMessageAsRead()
     $api
       .getSystemMessageData({
-        rules_type: rulesType
+        rules_type: rulesType,
+        cp: pageInfo.value.cp,
+        ps: pageInfo.value.ps,
+        info_type
       })
       .then((res) => {
         if (res.code === 200) {
           const data = res.data
-          notificationList.value = data.cardList
-          tabCountList.value = data.countList
+
+          // 判断是否结束加载
+          if (!data?.cardList?.length || data.cardList.length < pageInfo.value.ps) {
+            notificationMessageCardRef.value.finished = true
+          }
+
+          const lists = {
+            'All Notifications': { list: notificationList, count: tabCountList },
+            Unread: { list: unreadNotificationList },
+            Read: { list: readNotificationList }
+          }
+
+          const config = lists[tabType]
+          if (config) {
+            config.list.value = isChangeNav
+              ? [...data.cardList]
+              : [...config.list.value, ...data.cardList]
+          }
+
+          if (config?.count) {
+            config.count.value = data.countList
+          }
         }
       })
       .finally(() => {
         loading.value = false
         curTabCount.value = []
+        notificationMessageCardRef.value.loading = false
+
+        notificationMessageCardRef.value.adjustScrollTop()
       })
   } catch (error) {
-    console.error(error)
     loading.value = false
     curTabCount.value = []
   }
 }
+const notificationMessageCardRef = ref()
 
 const changeCardRead = () => {
   const readCardMap = notificationMsgStore.readCardMap
@@ -108,12 +171,15 @@ const changeCardRead = () => {
 const activeTabName = ref('All Notifications')
 const handleTabChange = () => {
   // 当前tab页切换时,更新数据
-  const readCardMap = notificationMsgStore.readCardMap
-  notificationList.value.forEach((item) => {
-    if (readCardMap.includes(item.info.id)) {
-      item.info.isRead = true
-    }
-  })
+  // const readCardMap = notificationMsgStore.readCardMap
+  // notificationList.value.forEach((item) => {
+  //   if (readCardMap.includes(item.info.id)) {
+  //     item.info.isRead = true
+  //   }
+  // })
+
+  pageInfo.value.cp = 1
+  getNotificationList(activeTabName.value, true)
 }
 
 onMounted(() => {
@@ -174,10 +240,12 @@ onMounted(() => {
               <NotificationMessageCard
                 v-if="activeTabName === 'All Notifications'"
                 :data="notificationList"
-                @hasCardRead="changeCardRead"
                 :isScrollPadding="true"
                 :isShowInsertionTime="true"
                 :updateReadCardsOnChange="false"
+                @hasCardRead="changeCardRead"
+                @loading="getNotificationList('All Notifications')"
+                ref="notificationMessageCardRef"
               ></NotificationMessageCard>
             </div>
           </el-tab-pane>
@@ -187,9 +255,10 @@ onMounted(() => {
               <div
                 class="count"
                 :style="{ 'padding-top': isMac ? 0 : '1px' }"
-                v-if="unreadNotificationList.length"
+                v-if="unreadNotificationList.length > 0 && unreadCardCount > 0"
               >
-                <span>{{ handleCount(unreadNotificationList.length) }}</span>
+                <!-- handleCount(unreadNotificationList.length) || -->
+                <span>{{ handleCount(unreadCardCount) }}</span>
               </div>
             </template>
             <div style="padding-bottom: 20px" v-if="activeTabName === 'Unread'">
@@ -199,6 +268,8 @@ onMounted(() => {
                 :isScrollPadding="true"
                 :isShowInsertionTime="true"
                 :updateReadCardsOnChange="false"
+                @loading="getNotificationList('Unread')"
+                ref="notificationMessageCardRef"
               ></NotificationMessageCard>
             </div>
           </el-tab-pane>
@@ -211,6 +282,8 @@ onMounted(() => {
                 :isScrollPadding="true"
                 :isShowInsertionTime="true"
                 :data="readNotificationList"
+                @loading="getNotificationList('Read')"
+                ref="notificationMessageCardRef"
               >
               </NotificationMessageCard>
             </div>

+ 72 - 8
src/views/SystemMessage/src/components/SystemMessageDetail.vue

@@ -15,24 +15,64 @@ const notificationData = ref({
 })
 
 const loading = ref(false)
+const finished = ref(false)
 if (route.query.type === 'feature') {
   loading.value = true
 }
+const pageInfo = ref({
+  cp: 0,
+  ps: 30
+})
+const scrollContainerRef = ref<HTMLElement | null>(null)
+const isScrollLoading = ref(false)
+const prevScrollTop = ref(0)
+
+const onScroll = async () => {
+  const el = scrollContainerRef.value
+  if (!el || isScrollLoading.value || finished.value) return
+
+  prevScrollTop.value = el.scrollTop + 50
+
+  const threshold = 50 // 提前50px触发
+  if (el.scrollHeight - el.scrollTop - el.clientHeight <= threshold) {
+    loading.value = true
+    isScrollLoading.value = true
+    await getNotificationList()
+  }
+}
+
 const getNotificationList = async () => {
+  pageInfo.value.cp += 1
   await $api
     .getNotificationDetails({
       rules_type: route.query.rules_type,
       frequency_type: route.query.frequency_type,
-      insert_date_format: route.query.insert_date_format
+      insert_date_format: route.query.insert_date_format,
+      cp: pageInfo.value.cp,
+      ps: pageInfo.value.ps
     })
     .then((res) => {
       if (res.code === 200) {
-        notificationData.value = res.data
+        const data = res.data
+        if (data?.length === 0 || !data) {
+          finished.value = true
+          return
+        }
+        const messageList = notificationData.value.notificationList || []
+        // 合并新数据和旧数据
+        notificationData.value = data
+        notificationData.value.notificationList = [...messageList, ...data.notificationList]
         notificationMsgStore.hasUnreadMessages()
+        // 判断是否结束加载
+        if (!data.notificationList?.length || data.notificationList?.length < pageInfo.value.ps) {
+          finished.value = true
+        }
+        scrollContainerRef.value.scrollTop = prevScrollTop.value
       }
     })
     .finally(() => {
       loading.value = false
+      isScrollLoading.value = false
     })
 }
 
@@ -72,7 +112,7 @@ const handleIframeLoaded = () => {
 
 <template>
   <div class="system-message-detail" v-vloading="loading">
-    <div class="content" v-if="route.query.type !== 'feature'">
+    <div class="content message-list-box" v-if="route.query.type !== 'feature'">
       <div class="header" v-if="notificationData.title">
         <div class="status-icon"></div>
         <div class="title">{{ notificationData.title }}</div>
@@ -100,11 +140,26 @@ const handleIframeLoaded = () => {
           </span>
         </div>
       </div>
-      <EventCard
-        v-for="(item, index) in notificationData.notificationList"
-        :key="index"
-        :data="item"
-      />
+      <div
+        style="height: calc(100% - 70px); overflow: auto"
+        ref="scrollContainerRef"
+        @scroll="onScroll"
+      >
+        <EventCard
+          v-for="(item, index) in notificationData.notificationList"
+          :key="index"
+          :data="item"
+        />
+        <div class="footer">
+          <el-divider style="margin-bottom: 0px" v-if="isScrollLoading"> loading... </el-divider>
+          <el-divider
+            style="margin-bottom: 0px"
+            v-if="finished && notificationData.notificationList.length > 0"
+          >
+            Only display the message data within three months
+          </el-divider>
+        </div>
+      </div>
     </div>
     <div class="content" style="height: 100%" v-else>
       <div class="header" v-if="notificationData.title">
@@ -133,6 +188,11 @@ const handleIframeLoaded = () => {
     margin: auto;
     width: 1000px;
   }
+  .message-list-box {
+    height: 100%;
+    padding: 16px;
+    background-color: var(--color-dialog-body-bg);
+  }
   .notification-card {
     max-width: 800px;
   }
@@ -158,6 +218,10 @@ const handleIframeLoaded = () => {
     line-height: 40px;
     font-size: 12px;
   }
+  .footer {
+    padding-right: 16px;
+    text-align: center;
+  }
   .feature-pdf {
     height: calc(100% - 28px);
   }