zmj 1 долоо хоног өмнө
parent
commit
f13aebba24

+ 50 - 1
src/components/IdlePlayer.vue

@@ -306,8 +306,42 @@ const onScreenClick = () => {
 
 // 播放方案:切换下一条素材(带淡入淡出)
 const goNextMedia = () => {
-  if (mediaCount.value <= 1) return
   isTransitioning.value = true
+
+  const totalItems = screenStore.playPlan?.items?.length || 0
+
+  // 只有方案有多个素材时才检查是否完成一轮
+  // 单素材方案:每次切换都视为"完成一轮"
+  if (totalItems <= 1) {
+    // 单素材方案:标记当前方案已完成一轮
+    screenStore.markCurrentPlanCycleComplete()
+  } else {
+    // 多素材方案:当索引即将回到 0 时标记完成一轮
+    const nextIndex = (screenStore.currentMediaIndex + 1) % totalItems
+    if (nextIndex === 0) {
+      screenStore.markCurrentPlanCycleComplete()
+    }
+  }
+
+  // 检查是否可以切换到新方案
+  const canSwitchToPending =
+    screenStore.pendingPlayPlan &&
+    screenStore.currentPlanCycleComplete
+
+  if (canSwitchToPending) {
+    // 当前方案完成一轮,且有待切换方案,切换到新方案
+    setTimeout(() => {
+      screenStore.applyPendingPlayPlan()
+      currentIndex.value = 0
+      isVideoPlaying.value = false
+      setTimeout(() => {
+        isTransitioning.value = false
+      }, 50)
+    }, 200)
+    return
+  }
+
+  // 无待切换方案,或当前方案未完成一轮,切换到下一个素材
   setTimeout(() => {
     screenStore.nextMedia()
     currentIndex.value = screenStore.currentMediaIndex
@@ -372,6 +406,21 @@ watch(() => screenStore.currentMediaIndex, (newIdx) => {
   isVideoPlaying.value = false
 })
 
+// 播放方案:监听待切换方案,在当前素材播放结束后切换
+watch(() => screenStore.pendingPlayPlan, (pendingPlan) => {
+  if (pendingPlan) {
+    console.log('[IdlePlayer] 检测到待切换的播放方案,将在本素材播放结束后切换')
+  }
+})
+
+// 播放方案:监听播放方案是否还有效(用于处理方案被禁用的情况)
+watch(() => screenStore.hasPlayPlan, (hasPlan, oldHasPlan) => {
+  if (oldHasPlan && !hasPlan) {
+    // 播放方案被禁用或清空,回退到欢迎模式(hasPlayPlan 为 false 时自动回退)
+    console.log('[IdlePlayer] 播放方案已无效,回退到欢迎模式')
+  }
+})
+
 const updateClock = () => {
   currentTime.value = formatTimeShort()
   currentDate.value = formatDateCN()

+ 93 - 10
src/stores/screen.js

@@ -16,6 +16,12 @@ export const useScreenStore = defineStore('screen', () => {
   const playPlan = ref(null)
   const currentMediaIndex = ref(0)
   const isPlaying = ref(true)
+  // 播放方案版本号(用于检测方案是否变化)
+  const playPlanVersion = ref('')
+  // 待切换的新播放方案(version 变化时暂存)
+  const pendingPlayPlan = ref(null)
+  // 当前播放方案是否已完成至少一轮(所有素材都播放过一次)
+  const currentPlanCycleComplete = ref(false)
 
   // 待机页模式: 'welcome' | 'playlist'
   const idleMode = ref('welcome')
@@ -119,23 +125,94 @@ export const useScreenStore = defineStore('screen', () => {
       const res = await api.getPlayPlan()
       // 处理 RuoYi 风格的响应结构 { msg, code, data }
       const data = res && res.data !== undefined ? res.data : res
-      
-      playPlan.value = data
-      currentMediaIndex.value = 0
-      hasPlayPlan.value = !!(
-        data &&
-        data.enabled !== false &&
-        Array.isArray(data.items) &&
-        data.items.length > 0
-      )
-      console.log('[Store] 播放方案加载成功', hasPlayPlan.value ? '有播放方案' : '无播放方案', data)
+
+      // 检查版本是否变化(支持 version 字段或不带 version 的兼容处理)
+      const newVersion = data?.version || ''
+      const hasVersionField = data && 'version' in data
+
+      // 如果是首次加载(playPlanVersion 为空),直接应用方案
+      if (!playPlanVersion.value) {
+        playPlan.value = data
+        playPlanVersion.value = newVersion
+        pendingPlayPlan.value = null
+        currentMediaIndex.value = 0
+        hasPlayPlan.value = !!(
+          data &&
+          data.enabled !== false &&
+          Array.isArray(data.items) &&
+          data.items.length > 0
+        )
+        console.log('[Store] 首次加载播放方案', hasPlayPlan.value ? '有播放方案' : '无播放方案')
+        return
+      }
+
+      // 后续轮询:检查是否有 version 字段
+      if (!hasVersionField) {
+        // 接口没有 version 字段,保持当前播放进度不变
+        console.log('[Store] 接口无 version 字段,保持当前播放状态')
+        return
+      }
+
+      // 有 version 字段时,进行版本对比
+      const isVersionChanged = newVersion && playPlanVersion.value !== newVersion
+
+      if (isVersionChanged) {
+        // 版本变化,暂存新方案
+        pendingPlayPlan.value = data
+        console.log('[Store] 播放方案版本变化,暂存待切换方案:', newVersion)
+      } else {
+        // 版本未变化,保持当前播放进度不变
+        console.log('[Store] 播放方案版本未变化,保持当前播放进度')
+      }
     } catch (e) {
       console.error('[Store] 播放方案加载失败:', e)
       playPlan.value = null
+      pendingPlayPlan.value = null
       hasPlayPlan.value = false
     }
   }
 
+  /**
+   * 切换到待生效的新播放方案
+   * 在当前方案完成一轮后调用
+   */
+  function applyPendingPlayPlan() {
+    if (pendingPlayPlan.value) {
+      console.log('[Store] 应用待切换的播放方案')
+      playPlan.value = pendingPlayPlan.value
+      playPlanVersion.value = pendingPlayPlan.value?.version || ''
+      pendingPlayPlan.value = null
+      currentMediaIndex.value = 0
+      currentPlanCycleComplete.value = false
+      hasPlayPlan.value = !!(
+        playPlan.value &&
+        playPlan.value.enabled !== false &&
+        Array.isArray(playPlan.value.items) &&
+        playPlan.value.items.length > 0
+      )
+    }
+  }
+
+  /**
+   * 标记当前方案已完成一轮播放
+   */
+  function markCurrentPlanCycleComplete() {
+    if (!currentPlanCycleComplete.value && pendingPlayPlan.value) {
+      currentPlanCycleComplete.value = true
+      console.log('[Store] 当前方案已完成一轮,可切换新方案')
+    }
+  }
+
+  /**
+   * 丢弃待切换的播放方案(当原方案被取消时)
+   */
+  function discardPendingPlayPlan() {
+    if (pendingPlayPlan.value) {
+      console.log('[Store] 丢弃待切换的播放方案')
+      pendingPlayPlan.value = null
+    }
+  }
+
   async function fetchBroadcastState() {
     try {
       const res = await api.getBroadcastState()
@@ -223,6 +300,9 @@ export const useScreenStore = defineStore('screen', () => {
     playPlan,
     currentMediaIndex,
     isPlaying,
+    playPlanVersion,
+    pendingPlayPlan,
+    currentPlanCycleComplete,
     broadcastState,
     latestCommand,
     globalAlert,
@@ -245,6 +325,9 @@ export const useScreenStore = defineStore('screen', () => {
     fetchScreenTheme,
     fetchRobotStatus,
     fetchPlayPlan,
+    applyPendingPlayPlan,
+    discardPendingPlayPlan,
+    markCurrentPlanCycleComplete,
     fetchBroadcastState,
     fetchLatestCommand,
     ackCommand,

+ 30 - 1
src/views/idle/Index.vue

@@ -5,7 +5,7 @@
 </template>
 
 <script setup>
-import { onMounted } from 'vue'
+import { ref, onMounted, onUnmounted } from 'vue'
 import { useRouter } from 'vue-router'
 import { useScreenStore } from '@/stores/screen'
 import IdlePlayer from '@/components/IdlePlayer.vue'
@@ -13,14 +13,43 @@ import IdlePlayer from '@/components/IdlePlayer.vue'
 const router = useRouter()
 const screenStore = useScreenStore()
 
+// 播放方案轮询定时器
+const playPlanPollingTimer = ref(null)
+const PLAY_PLAN_POLL_INTERVAL = 60000 // 60 秒轮询一次
+
 const goToMenu = () => {
   router.push('/menu')
 }
 
+// 停止播放方案轮询
+const stopPlayPlanPolling = () => {
+  if (playPlanPollingTimer.value) {
+    clearInterval(playPlanPollingTimer.value)
+    playPlanPollingTimer.value = null
+  }
+}
+
+// 开始播放方案轮询(待机页每 60 秒重新请求一次播放方案)
+const startPlayPlanPolling = () => {
+  stopPlayPlanPolling()
+  playPlanPollingTimer.value = setInterval(() => {
+    console.log('[Idle] 轮询播放方案...')
+    screenStore.fetchPlayPlan()
+  }, PLAY_PLAN_POLL_INTERVAL)
+}
+
 onMounted(() => {
+  // 首次加载时请求配置
   screenStore.fetchScreenTheme()
   screenStore.fetchPlayPlan()
   screenStore.fetchRobotStatus()
+  // 开始轮询播放方案
+  startPlayPlanPolling()
+})
+
+onUnmounted(() => {
+  // 组件销毁时停止轮询
+  stopPlayPlanPolling()
 })
 </script>