Browse Source

feat: 悬浮公告提示弹窗

zhangyuhan 1 năm trước cách đây
mục cha
commit
a3346288d2

+ 28 - 3
apps/bigmember_pc/src/components/time-line/PoverTimeLine.vue

@@ -1,5 +1,9 @@
 <template>
-  <div class="j-pover-step">
+  <div
+    class="j-pover-step"
+    @mouseenter="isHover = true"
+    @mouseleave="isHover = false"
+  >
     <el-popover
       popper-class="poverStep"
       placement="bottom-start"
@@ -7,11 +11,12 @@
       :append-to-body="false"
       :width="poperWidth"
       @show="show"
-      trigger="hover"
+      :trigger="trigger"
+      v-model="popoverShow"
     >
       <slot name="content" slot="reference"></slot>
       <el-card class="project-content" v-show="stepList.length !== 0">
-        <div slot="header" class="p-h-title">项目公告</div>
+        <div slot="header" class="p-h-title">{{ title }}</div>
         <div class="p-c-main">
           <TimeLine poverType="card" :stepList="stepList" />
         </div>
@@ -32,6 +37,14 @@ export default {
     TimeLine
   },
   props: {
+    trigger: {
+      type: String,
+      default: 'hover'
+    },
+    title: {
+      type: String,
+      default: '项目公告'
+    },
     stepList: {
       type: Array,
       default() {
@@ -45,9 +58,21 @@ export default {
       }
     }
   },
+  data() {
+    return {
+      popoverShow: false,
+      isHover: false
+    }
+  },
   methods: {
     show() {
       this.$emit('show')
+    },
+    doChangePopover(state) {
+      if (this.isHover) {
+        return
+      }
+      this.popoverShow = state
     }
   }
 }

+ 110 - 0
apps/bigmember_pc/src/views/article-content/composables/useHoverElementClientRect.js

@@ -0,0 +1,110 @@
+import { ref, onMounted, onUnmounted, computed } from 'vue'
+
+// useHoverElementClientRect hook
+export function useHoverElementClientRect(element) {
+  // 创建响应式引用
+  const rect = ref(null)
+  const isHover = ref(false)
+  const elementState = computed(() => {
+    return {
+      isHover: isHover.value,
+      rect: rect.value
+    }
+  })
+
+  // 监听器,用于更新元素的尺寸和位置
+  const updateRect = () => {
+    if (element.value) {
+      rect.value = element.value.getBoundingClientRect()
+    }
+  }
+
+  // 事件处理函数,用于设置isHover状态
+  const handleMouseEnter = () => {
+    isHover.value = true
+    updateRect()
+  }
+
+  const handleMouseLeave = () => {
+    isHover.value = false
+  }
+
+  onMounted(() => {
+    // 使用事件委托,将事件监听绑定到document上
+    element.value.addEventListener('mouseenter', handleMouseEnter)
+    element.value.addEventListener('mouseleave', handleMouseLeave)
+  })
+
+  onUnmounted(() => {
+    // 移除事件监听
+    element.value.removeEventListener('mouseenter', handleMouseEnter)
+    element.value.removeEventListener('mouseleave', handleMouseLeave)
+  })
+
+  // 返回响应式对象
+  return {
+    elementState
+  }
+}
+
+export function useHoverHighlightTextPopover({
+  parentSelector,
+  hasClass,
+  onChangeHover
+}) {
+  // 创建响应式引用
+  const rect = ref(null)
+  const isHover = ref(false)
+  const elementState = computed(() => {
+    return {
+      isHover: isHover.value,
+      rect: rect.value,
+      style: rect.value
+        ? {
+            position: 'fixed',
+            background: 'transparent',
+            zIndex: -1,
+            top: `${rect.value.y}px`,
+            left: `${rect.value.x}px`,
+            width: `${rect.value.width}px`,
+            height: `${rect.value.height}px`
+          }
+        : {}
+    }
+  })
+
+  const handleMouseOver = (e) => {
+    if (event.target.className.indexOf(hasClass) !== -1) {
+      // 检测到指定的类名
+      const activeElement = event.target
+      const activeElementRect = activeElement?.getBoundingClientRect()
+      if (activeElementRect) {
+        rect.value = activeElementRect
+        isHover.value = true
+        onChangeHover(isHover.value)
+      } else {
+        isHover.value = false
+        onChangeHover(isHover.value)
+      }
+    } else {
+      isHover.value = false
+      onChangeHover(isHover.value)
+    }
+  }
+
+  onMounted(() => {
+    // 使用事件委托,将事件监听绑定到 parentElement 上
+    const parentElement = document.querySelector(parentSelector)
+    parentElement.addEventListener('mouseover', handleMouseOver)
+  })
+
+  onUnmounted(() => {
+    // 移除事件监听
+    const parentElement = document.querySelector(parentSelector)
+    parentElement.removeEventListener('mouseover', handleMouseOver)
+  })
+
+  return {
+    elementState
+  }
+}

+ 44 - 2
apps/bigmember_pc/src/views/article-content/pages/Article.vue

@@ -6,8 +6,10 @@ import {
   onMounted,
   onBeforeMount,
   computed,
-  getCurrentInstance
+  getCurrentInstance,
+  watch
 } from 'vue'
+import { debounce } from 'lodash'
 import { useRoute, useRouter } from 'vue-router/composables'
 import Reward from '@/views/article-content/components/Reward.vue'
 import TimeLine from '@/components/time-line/TimeLine.vue'
@@ -39,6 +41,9 @@ import FooterAd from '@/views/article-content/components/FooterAd.vue'
 import OriginLink from '@/views/article-content/components/OriginLink.vue'
 import RecommendServes from '@/views/article-content/components/RecommendServes.vue'
 import ContentHeaderSkeleton from '@/views/article-content/components/ContentHeaderSkeleton.vue'
+import PoverTimeLine from '@/components/time-line/PoverTimeLine.vue'
+import { useHoverHighlightTextPopover } from '@/views/article-content/composables/useHoverElementClientRect'
+import attachmentDownload from '@/composables/attachment-download/component/AttachmentDownload.vue'
 
 useContentStore()
 // 判断在哪个容器
@@ -194,7 +199,12 @@ const contentId = useRoute().params.id.replace('.html', '')
 // 打开留资弹窗
 const collectElement = ref(null)
 function doOpenCollectDialog(key) {
-  collectElement.value?.noCallApiFn(key, false)
+  if (typeof key === 'string') {
+    collectElement.value?.noCallApiFn(key, false)
+  } else if (typeof key === 'object') {
+    const { source, reload = false } = key
+    collectElement.value?.noCallApiFn(source, reload)
+  }
 }
 
 // 查看原文
@@ -236,6 +246,22 @@ const canShowMask = computed(() => {
     type: type
   }
 })
+
+const popoverElement = ref(null)
+const popoverTriggerElement = ref(null)
+
+// 项目名称悬浮窗口
+const projectTimeLine = computed(() => {
+  return timeLineList.value
+})
+
+const { elementState } = useHoverHighlightTextPopover({
+  parentSelector: '.article-page-container',
+  hasClass: 'project-name',
+  onChangeHover: debounce((isHover) => {
+    popoverElement.value.doChangePopover(isHover)
+  }, 150)
+})
 </script>
 <template>
   <ContentLayout :need-ad="true" class="article-page-container">
@@ -330,6 +356,15 @@ const canShowMask = computed(() => {
                     查看原文链接
                   </el-button>
 
+                  <div>
+                    <attachment-download
+                      :id="contentId"
+                      :title="ContentModel.title"
+                      :attachment-list="ContentModel.attachments"
+                      @doOpenCollect="doOpenCollectDialog"
+                    ></attachment-download>
+                  </div>
+
                   <Reward />
                 </div>
               </div>
@@ -420,6 +455,13 @@ const canShowMask = computed(() => {
       </template>
     </el-skeleton>
 
+    <pover-time-line
+      trigger="manual"
+      :stepList="projectTimeLine"
+      ref="popoverElement"
+    >
+      <div slot="content" :style="elementState.style"></div>
+    </pover-time-line>
     <collect-info ref="collectElement"></collect-info>
     <footer-ad
       v-if="!canShowMask.show"