This commit is contained in:
itcsx committed 2026-09-21 21:31:53 +08:00
1 parent c10f8347e2
commit 70087c57cd
2 files changed
+104

No files matched your search

@@ -0,0 +1,65 @@
<template>
<dragModal v-model:open="visible" :width="width" :title="title" :footer="null" :mask-closable="false"
:fullscreen="fullscreen" wrap-class-name="vpm-wrap">
<div class="vpm-body">
<VideoPlayer :videos="videos" :start-index="startIndex" :auto-play="true" />
</div>
</dragModal>
</template>
<script lang="ts" setup>
import { useVModel } from "@vueuse/core";
import dragModal from "@/components/basic/DragModal.vue";
import VideoPlayer from "@/components/basic/VideoPlayer.vue";
import type { VideoItem } from "@/components/basic/VideoPlayer.vue";
/** 弹出式视频播放器:与 PreviewVideo 页面同一套 VideoPlayer,只是外面套了可拖动弹窗 */
const props = withDefaults(
defineProps<{
open?: boolean;
videos?: VideoItem[];
title?: string;
startIndex?: number;
width?: string | number;
fullscreen?: boolean;
}>(),
{
open: false,
videos: () => [],
title: "视频播放",
startIndex: 0,
width: "880px",
fullscreen: false,
},
);
const emit = defineEmits(["update:open", "close"]);
const visible = useVModel(props, "open", emit);
</script>
<style scoped>
/* VideoPlayer 内部是 flex 撑满父级;高度由下面的 vpm-wrap 样式链提供确定值 */
.vpm-body {
height: 100%;
min-height: 240px;
}
</style>
<style>
/* 播放弹窗专用(通过 wrapClassName 挂到 .ant-modal-wrap 上,不影响其它弹窗)。
.ant-modal 默认高度是 auto,height:100% 解析不出来,之前只能用视口魔数估算,
结果给 footer 预留的空白去不掉;这里直接给 .ant-modal 确定高度,
让 DragModal 已有的 .ant-modal-content{height:100%} 和 body 的 flex:auto 生效,
播放器即可精确撑满标题栏以下的全部空间,拖拽改大小/全屏时也会跟着变。 */
.ant-modal-wrap.vpm-wrap .ant-modal {
height: calc(100vh - 124px);
}
/* DragModal 全屏模式自带 height:100% !important,会自动覆盖上面的计算值,无需处理 */
.ant-modal-wrap.vpm-wrap .ant-modal .ant-modal-body {
height: auto;
overflow: hidden;
}
</style>
+39
View File
@@ -0,0 +1,39 @@
/**
* 图片加载失败重试。
*
* 背景:视频封面(jpg)是服务端 ffmpeg 异步生成的,列表查询返回时文件常常还没落盘,
* 首次请求会拿到 404;而 <img> 一旦失败不会自动重试,只有 src 变了才会再请求一次,
* 表现就是"必须手动刷新页面才有图"。
*
* 这里失败后按退避重试,重试时把查询参数换成一次性时间戳绕开缓存。
* 重试次数用 dataset 记在 <img> 上,src 变了(重新查询拿到新地址)就重新计数。
*/
const RETRY_DELAYS = [800, 2000, 4000];
export const retryImage = (e: Event, max: number = RETRY_DELAYS.length) => {
const img = e.target as HTMLImageElement | null;
if (!img) return;
const src = img.getAttribute("src") || img.src;
if (!src) return;
// 地址变了说明是新一轮数据,允许重新重试
if (img.dataset.retrySrc !== src) {
img.dataset.retrySrc = src;
img.dataset.retry = "0";
}
const times = Number(img.dataset.retry ?? 0);
if (times >= max) {
// 彻底拿不到就隐藏图片,露出底下的占位/播放图标,避免裂图
img.style.visibility = "hidden";
return;
}
img.dataset.retry = String(times + 1);
const base = src.split("?")[0];
window.setTimeout(() => {
img.src = `${base}?_t=${Date.now()}`;
img.style.visibility = "";
}, RETRY_DELAYS[Math.min(times, RETRY_DELAYS.length - 1)]);
};