add: 添加前端对焦交互逻辑

This commit is contained in:
alivender
2025-07-15 20:04:16 +08:00
parent 474151d412
commit 0f850c3ae7
6 changed files with 4455 additions and 4169 deletions

View File

@@ -161,8 +161,13 @@
</h2>
<div
class="relative bg-black rounded-lg overflow-hidden"
class="relative bg-black rounded-lg overflow-hidden cursor-pointer"
:class="[
focusAnimationClass,
{ 'cursor-not-allowed': !isPlaying || hasVideoError }
]"
style="aspect-ratio: 4/3"
@click="handleVideoClick"
>
<!-- 视频播放器 - 使用img标签直接显示MJPEG流 -->
<div
@@ -178,6 +183,14 @@
/>
</div>
<!-- 对焦提示 -->
<div
v-if="isPlaying && !hasVideoError"
class="absolute top-4 right-4 text-white text-sm bg-black bg-opacity-50 px-2 py-1 rounded"
>
{{ isFocusing ? '对焦中...' : '点击画面对焦' }}
</div>
<!-- 错误信息显示 -->
<div
v-if="hasVideoError"
@@ -353,6 +366,10 @@ const isPlaying = ref(false);
const hasVideoError = ref(false);
const videoStatus = ref('点击"播放视频流"按钮开始查看实时视频');
// 对焦相关状态
const isFocusing = ref(false);
const focusAnimationClass = ref('');
// 分辨率相关状态
const changingResolution = ref(false);
const loadingResolutions = ref(false);
@@ -565,6 +582,67 @@ const tryReconnect = () => {
currentVideoSource.value = `${streamInfo.value.mjpegUrl}?t=${new Date().getTime()}`;
};
// 执行对焦
const performFocus = async () => {
if (isFocusing.value || !isPlaying.value) return;
try {
isFocusing.value = true;
focusAnimationClass.value = 'focus-starting';
addLog("info", "正在执行自动对焦...");
// 调用对焦API
const response = await fetch('/api/VideoStream/Focus');
const result = await response.json();
if (result.success) {
// 对焦成功动画
focusAnimationClass.value = 'focus-success';
addLog("success", "自动对焦执行成功");
// 2秒后消失
setTimeout(() => {
focusAnimationClass.value = '';
}, 2000);
} else {
// 对焦失败动画
focusAnimationClass.value = 'focus-error';
addLog("error", `自动对焦执行失败: ${result.message || '未知错误'}`);
// 2秒后消失
setTimeout(() => {
focusAnimationClass.value = '';
}, 2000);
}
} catch (error) {
// 对焦失败动画
focusAnimationClass.value = 'focus-error';
addLog("error", `自动对焦执行失败: ${error}`);
console.error("自动对焦执行失败:", error);
// 2秒后消失
setTimeout(() => {
focusAnimationClass.value = '';
}, 2000);
} finally {
// 1秒后重置对焦状态
setTimeout(() => {
isFocusing.value = false;
}, 1000);
}
};
// 处理视频点击事件
const handleVideoClick = (event: MouseEvent) => {
// 只在播放状态下才允许对焦
if (!isPlaying.value || hasVideoError.value) return;
// 防止重复点击
if (isFocusing.value) return;
performFocus();
};
// 启动视频流
const startStream = async () => {
try {
@@ -711,4 +789,69 @@ img {
* {
transition: all 500ms ease-in-out;
}
/* 对焦动画样式 */
.focus-starting {
border: 3px solid transparent;
animation: focus-starting-animation 0.5s ease-in-out forwards;
}
.focus-success {
border: 3px solid transparent;
animation: focus-success-animation 2s ease-in-out forwards;
}
.focus-error {
border: 3px solid transparent;
animation: focus-error-animation 2s ease-in-out forwards;
}
@keyframes focus-starting-animation {
0% {
border-color: transparent;
}
100% {
border-color: #fbbf24; /* 黄色 */
box-shadow: 0 0 20px rgba(251, 191, 36, 0.5);
}
}
@keyframes focus-success-animation {
0% {
border-color: #fbbf24; /* 黄色 */
box-shadow: 0 0 20px rgba(251, 191, 36, 0.5);
}
50% {
border-color: #10b981; /* 绿色 */
box-shadow: 0 0 20px rgba(16, 185, 129, 0.5);
}
100% {
border-color: transparent;
box-shadow: none;
}
}
@keyframes focus-error-animation {
0% {
border-color: #fbbf24; /* 黄色 */
box-shadow: 0 0 20px rgba(251, 191, 36, 0.5);
}
50% {
border-color: #ef4444; /* 红色 */
box-shadow: 0 0 20px rgba(239, 68, 68, 0.5);
}
100% {
border-color: transparent;
box-shadow: none;
}
}
/* 对焦状态下的鼠标指针 */
.cursor-pointer {
cursor: pointer;
}
.cursor-not-allowed {
cursor: not-allowed;
}
</style>