在 Vue3 項目中實現定時刷新 vxe-table 數據,並在頁面不可見時暫停刷新,可以通過以下方案實現:

方案一:使用 Page Visibility API + setInterval

<template>
  <div>
    <vxe-table
      :data="tableData"
      :loading="loading"
    >
      <!-- 表格列配置 -->
      <vxe-column field="id" title="ID"></vxe-column>
      <vxe-column field="name" title="名稱"></vxe-column>
      <vxe-column field="status" title="狀態"></vxe-column>
    </vxe-table>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { VxeTable, VxeColumn } from 'vxe-table'

const tableData = ref([])
const loading = ref(false)
let refreshTimer = null
const refreshInterval = 10000 // 10秒

// 獲取表格數據
const fetchData = async () => {
  try {
    loading.value = true
    // 模擬API請求
    const response = await fetch('/api/your-data-endpoint')
    const data = await response.json()
    tableData.value = data
  } catch (error) {
    console.error('獲取數據失敗:', error)
  } finally {
    loading.value = false
  }
}

// 啓動定時刷新
const startAutoRefresh = () => {
  if (refreshTimer) {
    clearInterval(refreshTimer)
  }
  refreshTimer = setInterval(() => {
    if (!document.hidden) {
      fetchData()
    }
  }, refreshInterval)
}

// 停止定時刷新
const stopAutoRefresh = () => {
  if (refreshTimer) {
    clearInterval(refreshTimer)
    refreshTimer = null
  }
}

// 處理頁面可見性變化
const handleVisibilityChange = () => {
  if (document.hidden) {
    // 頁面不可見,停止刷新
    stopAutoRefresh()
  } else {
    // 頁面可見,開始刷新
    startAutoRefresh()
    // 立即刷新一次數據
    fetchData()
  }
}

onMounted(() => {
  // 初始加載數據
  fetchData()
  
  // 啓動定時刷新
  startAutoRefresh()
  
  // 監聽頁面可見性變化
  document.addEventListener('visibilitychange', handleVisibilityChange)
  
  // 監聽頁面卸載
  window.addEventListener('beforeunload', stopAutoRefresh)
})

onUnmounted(() => {
  // 清理定時器和監聽器
  stopAutoRefresh()
  document.removeEventListener('visibilitychange', handleVisibilityChange)
  window.removeEventListener('beforeunload', stopAutoRefresh)
})
</script>

方案二:使用 Web Worker 實現更精確的定時

// utils/refreshWorker.js
let refreshTimer = null
let interval = 10000
let isPageVisible = true

self.onmessage = function(e) {
  const { type, data } = e.data
  
  switch (type) {
    case 'START':
      interval = data.interval || 10000
      startTimer()
      break
    case 'STOP':
      stopTimer()
      break
    case 'VISIBILITY_CHANGE':
      isPageVisible = data.visible
      if (isPageVisible && !refreshTimer) {
        startTimer()
      }
      break
    case 'UPDATE_INTERVAL':
      interval = data.interval
      if (refreshTimer) {
        stopTimer()
        startTimer()
      }
      break
  }
}

function startTimer() {
  if (refreshTimer) return
  
  refreshTimer = setInterval(() => {
    if (isPageVisible) {
      self.postMessage({ type: 'REFRESH' })
    }
  }, interval)
  
  // 立即觸發一次
  if (isPageVisible) {
    self.postMessage({ type: 'REFRESH' })
  }
}

function stopTimer() {
  if (refreshTimer) {
    clearInterval(refreshTimer)
    refreshTimer = null
  }
}
<template>
  <div>
    <div class="refresh-controls">
      <span>刷新間隔:</span>
      <select v-model="selectedInterval" @change="updateInterval">
        <option :value="5000">5秒</option>
        <option :value="10000">10秒</option>
        <option :value="30000">30秒</option>
      </select>
      <button @click="toggleAutoRefresh">
        {{ autoRefreshEnabled ? '停止' : '開始' }}自動刷新
      </button>
    </div>
    
    <vxe-table
      :data="tableData"
      :loading="loading"
    >
      <!-- 表格列配置 -->
    </vxe-table>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue'

const tableData = ref([])
const loading = ref(false)
const autoRefreshEnabled = ref(true)
const selectedInterval = ref(10000)

let refreshWorker = null

// 獲取數據
const fetchData = async () => {
  try {
    loading.value = true
    // 這裏替換為實際的API調用
    const response = await fetch('/api/data')
    const data = await response.json()
    tableData.value = data
  } catch (error) {
    console.error('獲取數據失敗:', error)
  } finally {
    loading.value = false
  }
}

// 初始化Web Worker
const initWorker = () => {
  if (typeof Worker !== 'undefined') {
    refreshWorker = new Worker(
      new URL('../utils/refreshWorker.js', import.meta.url)
    )
    
    refreshWorker.onmessage = (e) => {
      if (e.data.type === 'REFRESH') {
        fetchData()
      }
    }
    
    // 啓動定時器
    refreshWorker.postMessage({
      type: 'START',
      data: { interval: selectedInterval.value }
    })
  }
}

// 更新刷新間隔
const updateInterval = () => {
  if (refreshWorker) {
    refreshWorker.postMessage({
      type: 'UPDATE_INTERVAL',
      data: { interval: selectedInterval.value }
    })
  }
}

// 切換自動刷新
const toggleAutoRefresh = () => {
  autoRefreshEnabled.value = !autoRefreshEnabled.value
  if (refreshWorker) {
    if (autoRefreshEnabled.value) {
      refreshWorker.postMessage({
        type: 'START',
        data: { interval: selectedInterval.value }
      })
    } else {
      refreshWorker.postMessage({ type: 'STOP' })
    }
  }
}

// 處理頁面可見性
const handleVisibilityChange = () => {
  if (refreshWorker) {
    refreshWorker.postMessage({
      type: 'VISIBILITY_CHANGE',
      data: { visible: !document.hidden }
    })
  }
}

onMounted(() => {
  // 初始加載數據
  fetchData()
  
  // 初始化Web Worker
  initWorker()
  
  // 監聽頁面可見性變化
  document.addEventListener('visibilitychange', handleVisibilityChange)
  
  // 監聽窗口聚焦事件(可選)
  window.addEventListener('focus', () => {
    if (refreshWorker && autoRefreshEnabled.value) {
      refreshWorker.postMessage({
        type: 'VISIBILITY_CHANGE',
        data: { visible: true }
      })
    }
  })
})

onUnmounted(() => {
  // 清理
  if (refreshWorker) {
    refreshWorker.terminate()
    refreshWorker = null
  }
  document.removeEventListener('visibilitychange', handleVisibilityChange)
})
</script>

方案三:使用 Vue Composition API 封裝為可複用 Hook

// composables/useAutoRefresh.js
import { ref, onMounted, onUnmounted } from 'vue'

export function useAutoRefresh(callback, options = {}) {
  const {
    interval = 10000,
    immediate = true,
    autoStart = true
  } = options

  const isActive = ref(autoStart)
  const isPageVisible = ref(!document.hidden)
  let timer = null

  // 啓動刷新
  const start = () => {
    if (timer) return
    isActive.value = true
    
    const executeRefresh = () => {
      if (isActive.value && isPageVisible.value) {
        callback()
      }
    }
    
    // 立即執行一次
    if (immediate) {
      executeRefresh()
    }
    
    // 設置定時器
    timer = setInterval(executeRefresh, interval)
  }

  // 停止刷新
  const stop = () => {
    isActive.value = false
    if (timer) {
      clearInterval(timer)
      timer = null
    }
  }

  // 處理頁面可見性變化
  const handleVisibilityChange = () => {
    isPageVisible.value = !document.hidden
  }

  // 更新間隔時間
  const updateInterval = (newInterval) => {
    stop()
    options.interval = newInterval
    if (isActive.value) {
      start()
    }
  }

  onMounted(() => {
    // 監聽頁面可見性變化
    document.addEventListener('visibilitychange', handleVisibilityChange)
    
    if (autoStart) {
      start()
    }
  })

  onUnmounted(() => {
    stop()
    document.removeEventListener('visibilitychange', handleVisibilityChange)
  })

  return {
    isActive,
    start,
    stop,
    updateInterval
  }
}
<template>
  <div>
    <div class="control-panel">
      <button @click="toggleAutoRefresh">
        {{ autoRefreshControls.isActive ? '暫停' : '繼續' }}自動刷新
      </button>
      <span>最後刷新時間: {{ lastRefreshTime }}</span>
    </div>

    <vxe-table :data="tableData">
      <!-- 表格列 -->
    </vxe-table>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'
import { useAutoRefresh } from '@/composables/useAutoRefresh'

const tableData = ref([])
const lastRefreshTime = ref(null)

// 獲取數據
const fetchTableData = async () => {
  try {
    const response = await fetch('/api/table-data')
    tableData.value = await response.json()
    lastRefreshTime.value = new Date().toLocaleTimeString()
  } catch (error) {
    console.error('刷新數據失敗:', error)
  }
}

// 使用自動刷新Hook
const autoRefreshControls = useAutoRefresh(fetchTableData, {
  interval: 10000,
  immediate: true,
  autoStart: true
})

// 切換自動刷新
const toggleAutoRefresh = () => {
  if (autoRefreshControls.isActive) {
    autoRefreshControls.stop()
  } else {
    autoRefreshControls.start()
  }
}
</script>

關鍵點總結

  1. 頁面可見性檢測:使用 document.hiddenvisibilitychange 事件
  2. 資源清理:在組件卸載時清除定時器和事件監聽器
  3. 錯誤處理:在數據刷新時添加適當的錯誤處理
  4. 用户體驗:提供手動控制自動刷新的開關
  5. 性能優化:頁面不可見時停止定時器,減少不必要的請求

建議使用方案一作為基礎實現,如果需要更復雜的控制或複用邏輯,可以考慮使用方案三的 Composition API 封裝。