ow
This commit is contained in:
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>
|
||||
@@ -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)]);
|
||||
};
|
||||
Reference in new issue
Block a user