/** * 可见性监听混入 * 提供元素可见性监听功能,支持H5、微信小程序、App端 * 用于实现浏览记录等功能 */ import { VIEWPORT_CONFIG, createBrowseRecordParams } from '@/config/browseConfig.js' export default { data() { return { hasRecordedView: false, // 标记是否已记录浏览 viewTimer: null, // 浏览计时器 isInViewport: false, // 是否在可视区域内 observer: null, // 观察者实例 } }, mounted() { // 组件挂载后,使用Intersection Observer监听可见性 this.observeVisibility() }, beforeDestroy() { // 组件销毁前清理observer和计时器 this.cleanupObserver() }, methods: { /** * 监听元素可见性 * @param {String} selector - 要监听的元素选择器,默认为'.works' * @param {Object} config - 配置参数 */ observeVisibility(selector = '.works', config = {}) { const { threshold = VIEWPORT_CONFIG.THRESHOLD, dwellTime = VIEWPORT_CONFIG.DWELL_TIME, margins = VIEWPORT_CONFIG.VIEWPORT_MARGINS } = config // #ifdef H5 if (typeof IntersectionObserver !== 'undefined') { this.observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting && !this.hasRecordedView) { // 元素进入可视区域且未记录过浏览 this.handleViewportEnter(dwellTime) } else if (!entry.isIntersecting && this.isInViewport) { // 元素离开可视区域 this.handleViewportLeave() } }) }, { threshold: threshold }) this.observer.observe(this.$el) } else { // 降级方案:延迟记录浏览 setTimeout(() => { if (!this.hasRecordedView) { this.recordBrowseView() } }, dwellTime) } // #endif // #ifdef MP-WEIXIN // 使用微信小程序原生的IntersectionObserver API this.observer = wx.createIntersectionObserver(this, { thresholds: [threshold], initialRatio: 0, observeAll: false }) // 指定页面显示区域作为参照区域 this.observer.relativeToViewport(margins) // 开始监听目标节点 this.observer.observe(selector, (res) => { if (res.intersectionRatio > threshold && !this.hasRecordedView) { // 当元素达到配置阈值以上进入可视区域且未记录过浏览时,处理进入可视区域事件 this.handleViewportEnter(dwellTime) } else if (res.intersectionRatio <= threshold && this.isInViewport) { // 元素离开可视区域 this.handleViewportLeave() } }) // #endif // #ifdef APP-PLUS // App端使用uni-app的createIntersectionObserver this.observer = uni.createIntersectionObserver(this, { thresholds: [threshold], initialRatio: 0, observeAll: false }) this.observer.relativeToViewport(margins) this.observer.observe(selector, (res) => { if (res.intersectionRatio > threshold && !this.hasRecordedView) { this.handleViewportEnter(dwellTime) } else if (res.intersectionRatio <= threshold && this.isInViewport) { this.handleViewportLeave() } }) // #endif }, /** * 处理进入可视区域事件 * @param {Number} dwellTime - 停留时间 */ handleViewportEnter(dwellTime = VIEWPORT_CONFIG.DWELL_TIME) { if (this.hasRecordedView) return this.isInViewport = true // 设置延迟计时器,确保用户真正浏览了内容 this.viewTimer = setTimeout(() => { if (this.isInViewport && !this.hasRecordedView) { this.recordBrowseView() } }, dwellTime) }, /** * 处理离开可视区域事件 */ handleViewportLeave() { this.isInViewport = false // 清除计时器,如果用户快速滚动过去,不记录浏览 if (this.viewTimer) { clearTimeout(this.viewTimer) this.viewTimer = null } }, /** * 记录浏览行为 * @param {String} itemType - 项目类型,默认为'dynamic' * @param {String} itemId - 项目ID,默认从this.item.id获取 */ recordBrowseView(itemType = 'dynamic', itemId = null) { const id = itemId || (this.item && this.item.id) if (this.hasRecordedView || !id) return this.hasRecordedView = true // 清除计时器 if (this.viewTimer) { clearTimeout(this.viewTimer) this.viewTimer = null } // 使用配置文件中的参数创建API请求参数 const params = createBrowseRecordParams(id, itemType) // 调用浏览记录API this.$api('addBrowseRecord', params, res => { if (res.code === 200) { console.log('浏览记录已保存:', id) } }) }, /** * 清理观察者和计时器 */ cleanupObserver() { if (this.observer) { // #ifdef H5 if (typeof this.observer.disconnect === 'function') { this.observer.disconnect() } // #endif // #ifdef MP-WEIXIN || APP-PLUS if (typeof this.observer.disconnect === 'function') { this.observer.disconnect() } // #endif this.observer = null } if (this.viewTimer) { clearTimeout(this.viewTimer) this.viewTimer = null } }, /** * 重置浏览记录状态 */ resetBrowseRecord() { this.hasRecordedView = false this.isInViewport = false this.cleanupObserver() } } }