<template>
|
<div class="camera-page">
|
<div class="scroll-wrapper">
|
<el-button class="arrow left" @click="scrollLeft" :disabled="canScrollLeft" circle>
|
<el-icon><ArrowLeft /></el-icon>
|
</el-button>
|
|
<div class="scroll-viewport" ref="viewportRef">
|
<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"
|
:access-token="ysToken"
|
:url="`ezopen://open.ys7.com/${dev.iotCode || dev.code}/1.hd.live`"
|
:pause-after-first-frame="true"
|
/>
|
</div>
|
</div>
|
</div>
|
|
<el-button class="arrow right" @click="scrollRight" :disabled="canScrollRight" circle>
|
<el-icon><ArrowRight /></el-icon>
|
</el-button>
|
</div>
|
</div>
|
</template>
|
|
<script lang="ts" setup>
|
defineOptions({
|
name: 'camera-monitor',
|
});
|
|
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
import { useCool } from '/@/cool';
|
import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue';
|
import EzuikitPlayer from '../../ezuikit/components/ezuikit-player.vue';
|
|
const { service } = useCool();
|
|
const deviceList = ref<any[]>([]);
|
const ysToken = ref('');
|
const loading = ref(false);
|
const scrollPos = ref(0);
|
const viewportRef = ref<HTMLElement | null>(null);
|
|
// 每次滚动的距离(一个摄像头的宽度 + 间距)
|
const scrollStep = computed(() => {
|
const cardWidth = 312; // 300px 宽度 + 12px 间距的一半
|
const gap = 12;
|
return cardWidth + gap;
|
});
|
|
// 是否可以向左滚动
|
const canScrollLeft = computed(() => scrollPos.value <= 0);
|
|
// 是否可以向右滚动
|
const canScrollRight = computed(() => {
|
if (!viewportRef.value) return true;
|
const viewportWidth = viewportRef.value.clientWidth;
|
const trackWidth = deviceList.value.length * scrollStep.value;
|
// 增加 2px 容差
|
return scrollPos.value >= trackWidth - viewportWidth - 2;
|
});
|
|
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;
|
} else {
|
scrollPos.value += scrollStep.value;
|
}
|
}
|
|
async function fetchYsToken() {
|
if (ysToken.value) return;
|
try {
|
const res = await service.base.sys.param.stringbykey({ key: 'ys7.accessToken' });
|
ysToken.value = res as string;
|
} catch (e: any) {
|
console.error('萤石 Token 获取失败', e?.response?.data?.message || e.message);
|
}
|
}
|
|
async function fetchDevices() {
|
loading.value = true;
|
try {
|
const res = await service.basicdata.iot.getCameras();
|
deviceList.value = (res || []) as any[];
|
scrollPos.value = 0;
|
} catch (e: any) {
|
console.error('获取摄像头列表失败', e?.response?.data?.message || e.message);
|
deviceList.value = [];
|
} finally {
|
loading.value = false;
|
}
|
}
|
|
async function refreshDevices() {
|
await fetchDevices();
|
}
|
|
function handleKeydown(e: KeyboardEvent) {
|
if (e.key === 'ArrowLeft') scrollLeft();
|
if (e.key === 'ArrowRight') scrollRight();
|
}
|
|
onMounted(async () => {
|
window.addEventListener('keydown', handleKeydown);
|
await fetchYsToken();
|
await fetchDevices();
|
});
|
|
onUnmounted(() => {
|
window.removeEventListener('keydown', handleKeydown);
|
});
|
</script>
|
|
<style lang="scss" scoped>
|
.camera-page {
|
height: 100%;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
padding: 16px;
|
}
|
|
.scroll-wrapper {
|
position: relative;
|
display: flex;
|
align-items: center;
|
width: 100%;
|
/* 移除 max-width 限制,让容器占满可用宽度 */
|
}
|
|
.scroll-viewport {
|
overflow: hidden;
|
flex: 1;
|
margin: 0 42px;
|
}
|
|
.scroll-track {
|
display: flex;
|
gap: 12px;
|
transition: transform 0.35s ease;
|
}
|
|
.player-card {
|
flex-shrink: 0;
|
width: 300px;
|
height: 360px;
|
border-radius: 8px;
|
overflow: hidden;
|
background: #000;
|
}
|
|
.arrow {
|
flex-shrink: 0;
|
z-index: 10;
|
background: #00ff00 !important;
|
border-color: #00ff00 !important;
|
color: #000 !important;
|
font-size: 18px;
|
width: 32px;
|
height: 96px; /* 加高箭头,更易点击 */
|
border-radius: 4px !important;
|
}
|
|
.arrow:hover {
|
background: #33ff33 !important;
|
border-color: #33ff33 !important;
|
}
|
|
.arrow:active {
|
background: #00cc00 !important;
|
border-color: #00cc00 !important;
|
}
|
|
.el-button.is-disabled {
|
opacity: 0.25;
|
background: #555 !important;
|
border-color: #555 !important;
|
color: #888 !important;
|
}
|
</style>
|