wangrong
2026-08-30 8954db4cf4969166961c72569dfa24645276b947
src/modules/monitor/views/cameras.vue
@@ -1,17 +1,17 @@
<template>
  <div class="camera-page">
    <div class="scroll-wrapper">
      <el-button class="arrow left" @click="scrollLeft" :disabled="canScrollLeft" circle>
      <el-button class="arrow left" @click="scrollLeftManual" :disabled="canScrollLeft" circle>
        <el-icon><ArrowLeft /></el-icon>
      </el-button>
      <div class="scroll-viewport" ref="viewportRef">
      <div class="scroll-viewport" ref="viewportRef" @mouseenter="pauseAuto" @mouseleave="resumeAuto">
        <div class="scroll-track" :style="{ transform: `translateX(${-scrollPos}px)` }">
          <div v-for="dev in deviceList" :key="dev.id" class="player-card">
            <ezuikit-player
              v-if="ysToken"
              :key="'p_' + dev.id + '_' + ysToken"
              :container-id="'player_' + dev.id"
              :key="'p_' + dev.id + '_' + ysToken + '_' + playerRemountKey"
              :container-id="'player_' + dev.id + '_' + playerRemountKey"
              :access-token="ysToken"
              :url="`ezopen://open.ys7.com/${dev.iotCode || dev.code}/1.hd.live`"
              :pause-after-first-frame="true"
@@ -20,7 +20,7 @@
        </div>
      </div>
      <el-button class="arrow right" @click="scrollRight" :disabled="canScrollRight" circle>
      <el-button class="arrow right" @click="scrollRightManual" :disabled="canScrollRight" circle>
        <el-icon><ArrowRight /></el-icon>
      </el-button>
    </div>
@@ -44,42 +44,100 @@
const loading = ref(false);
const scrollPos = ref(0);
const viewportRef = ref<HTMLElement | null>(null);
/** 全屏切换后递增,强制重建萤石播放器 */
const playerRemountKey = ref(0);
// 每次滚动的距离(一个摄像头的宽度 + 间距)
function onScreenFullscreenChange() {
  playerRemountKey.value += 1;
}
// 自动滚动控制
const AUTO_INTERVAL = 3000; // 每次滚动间隔(ms)
const autoPlay = ref(true); // 是否自动滚动
let autoTimer: ReturnType<typeof setInterval> | null = null;
// 每次滚动的距离
const scrollStep = computed(() => {
  const cardWidth = 312; // 300px 宽度 + 12px 间距的一半
  const cardWidth = 300; // 与 player-card 宽度一致
  const gap = 12;
  return cardWidth + gap;
});
// 是否可以向左滚动
const canScrollLeft = computed(() => scrollPos.value <= 0);
// 是否可以向右滚动
const canScrollRight = computed(() => {
  if (!viewportRef.value) return true;
// 可滚动的最大距离
const maxScroll = computed(() => {
  if (!viewportRef.value) return 0;
  const viewportWidth = viewportRef.value.clientWidth;
  const trackWidth = deviceList.value.length * scrollStep.value;
  // 增加 2px 容差
  return scrollPos.value >= trackWidth - viewportWidth - 2;
  return Math.max(0, trackWidth - viewportWidth);
});
// 是否可以向左/向右滚动(手动箭头用)
const canScrollLeft = computed(() => scrollPos.value <= 0);
const canScrollRight = computed(() => scrollPos.value >= maxScroll.value - 2);
// 自动滚动方向: 1 向右, -1 向左
const direction = ref(1);
function scrollLeft() {
  scrollPos.value = Math.max(0, scrollPos.value - scrollStep.value);
}
function scrollRight() {
  if (!viewportRef.value) return;
  const viewportWidth = viewportRef.value.clientWidth;
  const trackWidth = deviceList.value.length * scrollStep.value;
  const maxScroll = trackWidth - viewportWidth;
  // 如果剩余距离小于一个步长,直接滚动到底部
  if (scrollPos.value + scrollStep.value >= maxScroll) {
    scrollPos.value = maxScroll;
  const max = maxScroll.value;
  if (scrollPos.value + scrollStep.value >= max) {
    scrollPos.value = max;
  } else {
    scrollPos.value += scrollStep.value;
  }
}
// 执行一步自动滚动(到边界自动换向,回到起点循环)
function autoTick() {
  if (deviceList.value.length === 0) return;
  if (maxScroll.value <= 0) return; // 一屏装下全部,不滚动
  scrollPos.value += scrollStep.value * direction.value;
  if (scrollPos.value >= maxScroll.value) {
    scrollPos.value = maxScroll.value;
    direction.value = -1; // 到最右,回头向左
  } else if (scrollPos.value <= 0) {
    scrollPos.value = 0;
    direction.value = 1; // 回到起点,循环向右
    pauseAuto();          // 回到起点停一下(可选,见下方 startAuto)
    startAuto();          // 立即开始新一轮
  }
}
function startAuto() {
  stopAuto();
  if (!autoPlay.value) return;
  autoTimer = setInterval(autoTick, AUTO_INTERVAL);
}
function stopAuto() {
  if (autoTimer) {
    clearInterval(autoTimer);
    autoTimer = null;
  }
}
// 鼠标悬停暂停,移开继续
function pauseAuto() {
  stopAuto();
}
function resumeAuto() {
  startAuto();
}
// 手动点击箭头:重置自动滚动定时器,避免与自动节奏冲突
function scrollLeftManual() {
  scrollLeft();
  startAuto();
}
function scrollRightManual() {
  scrollRight();
  startAuto();
}
async function fetchYsToken() {
@@ -98,6 +156,9 @@
    const res = await service.basicdata.iot.getCameras();
    deviceList.value = (res || []) as any[];
    scrollPos.value = 0;
    direction.value = 1;
    // 数据加载完再启动自动滚动
    startAuto();
  } catch (e: any) {
    console.error('获取摄像头列表失败', e?.response?.data?.message || e.message);
    deviceList.value = [];
@@ -107,22 +168,26 @@
}
async function refreshDevices() {
  stopAuto();
  await fetchDevices();
}
function handleKeydown(e: KeyboardEvent) {
  if (e.key === 'ArrowLeft') scrollLeft();
  if (e.key === 'ArrowRight') scrollRight();
  if (e.key === 'ArrowLeft') { scrollLeftManual(); }
  if (e.key === 'ArrowRight') { scrollRightManual(); }
}
onMounted(async () => {
  window.addEventListener('keydown', handleKeydown);
  window.addEventListener('report-screen-fullscreen-change', onScreenFullscreenChange);
  await fetchYsToken();
  await fetchDevices();
  await fetchDevices(); // 内部会在数据就绪后 startAuto
});
onUnmounted(() => {
  stopAuto();
  window.removeEventListener('keydown', handleKeydown);
  window.removeEventListener('report-screen-fullscreen-change', onScreenFullscreenChange);
});
</script>