Files
Docker/WebVue/src/views/master/PreviewVideo.vue
T
2026-09-23 07:25:47 +08:00

148 lines
5.8 KiB
Vue

<template>
<div class="pv-page" :style="{
height: bsInfo.height - 120 + 'px',
}">
<VideoPlayer v-model:controls="controlsVisible" :videos="videos" />
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted, watch } from "vue";
import { useRoute } from "vue-router";
import VideoPlayer from "@/components/basic/VideoPlayer.vue";
import type { VideoItem } from "@/components/basic/VideoPlayer.vue";
import { bsInfo } from "@/utils/browser";
import publishVideoService from "@/service/master/publishVideoService";
import { PublishVideoData } from "@/data/auto/publishVideoData";
import { PublishVideoDetailDisplayData } from "@/data/auto/publishVideoDetailDisplayData";
import { DmsFileSearchCriteria } from "@/data/auto/dmsFileSearchCriteria";
import dmsFileService from "@/service/master/dmsFileService";
import { DmsFileUploadViewDisplayData } from "@/data/auto/dmsFileUploadViewDisplayData";
import { SearchFilter } from "@/data/auto/searchFilter";
import { FileTypePolicy } from "@/policy/custom/fileTypePolicy";
import { DmsFileResultData } from "@/data/auto/dmsFileResultData";
const route = useRoute();
const publishName = ref<string>(route.params.id as string);
const mode = ref<string>(route.params.mode as string);
// 视频列表:与 VinylAudioPlayer 的 tracks 一致,组件负责 UI,页面只负责数据
const videos = ref<VideoItem[]>([]);
/**
* 底部控制区显隐(点播放器控制栏里的眼睛图标切换)。
* 影视厅是整页用法,收起控制区时连 layout 顶部的两条栏(标题栏 + 页面头)一起藏掉,
* 视频才能占满整屏。class 挂在 body 上,离开本页/恢复显示时立即移除,不会影响其它页面。
*/
const controlsVisible = ref(true);
/** 退出全屏后让顶栏短暂不响应指针的定时器 */
let settleTimer = 0;
watch(controlsVisible, (visible) => {
document.body.classList.toggle("pv-fullscreen", !visible);
// 退出全屏时鼠标往往正好停在右上角(恢复按钮的位置,也是顶栏菜单的位置),
// 顶栏一恢复显示就会被 hover 展开下拉。让它先"冷静" 400ms,避开这次误触。
if (visible) {
window.clearTimeout(settleTimer);
document.body.classList.add("pv-settling");
settleTimer = window.setTimeout(() => {
document.body.classList.remove("pv-settling");
}, 400);
}
});
onUnmounted(() => {
document.body.classList.remove("pv-fullscreen");
document.body.classList.remove("pv-settling");
window.clearTimeout(settleTimer);
});
onMounted(async () => {
await loadVideos();
});
/**
* 发布专辑详情(FindPublishVideoDefault / FindByKey)里,Url 存的是
* OrderKey(形如 xxxx-3-3.mp4 的文件名),不是可播放地址;
* 真正的原码地址在 FormattedUrl 上(后端 3 参 PackUrl 现在固定返回原码)。
* 这里把 Url 补成地址,否则播放器切「原码」时会拿文件名当地址请求、直接 404。
* 网盘列表模式(mode=list)的 Url 本身就是完整地址,不受影响。
*/
const normalizeDetails = (details: PublishVideoDetailDisplayData[]) =>
(details ?? []).map((d) => {
if (d.Url && !/^(https?:)?\/\//i.test(d.Url)) {
d.Url = d.FormattedUrl || "";
}
return d;
});
const loadVideos = async () => {
try {
// 模式一:从网盘中心加载某个路径下的所有视频
if (mode.value === "list") {
const c = new DmsFileSearchCriteria();
c.limit = 100;
c.page = 1;
let filter: SearchFilter;
filter = new SearchFilter();
filter.Column = DmsFileUploadViewDisplayData.DB_FILE_PATH;
filter.Type = "SingleSelection";
filter.Operator = "single";
filter.Value = publishName.value;
c.Filter.push(filter);
filter = new SearchFilter();
filter.Column = DmsFileUploadViewDisplayData.DB_FILE_TYPE;
filter.Type = "SingleSelection";
filter.Operator = "single";
filter.Value = FileTypePolicy.VIDEO;
c.Filter.push(filter);
const dr: DmsFileResultData = await dmsFileService.FindBySearchCriteria(c);
if (dr && dr.Datas && dr.Datas.length > 0) {
for (const item of dr.Datas) {
const detail = new PublishVideoDetailDisplayData();
detail.Title = item.OrgFileName;
detail.Url = item.Url; // 原码
detail.FormattedM3u8 = item.FormattedM3u8; // 转码(10秒)
detail.FormattedUrl = item.FormattedM3u8 || item.Url;
// m3u8 通常配套同名 jpg/png 封面,这里把后缀替换成 .png 兜底
detail.FormattedPic = (item.FormattedPic || "").replace(/\.m3u8$/, ".png");
videos.value.push(detail);
}
}
}
// 默认发布视频
else if (!publishName.value || publishName.value.length === 0) {
const data: PublishVideoData = await publishVideoService.FindPublishVideoDefault();
videos.value = normalizeDetails(data?.Details ?? []);
}
// 按 key 加载
else {
const data: PublishVideoData = await publishVideoService.FindByKey(publishName.value);
videos.value = normalizeDetails(data?.Details ?? []);
}
} catch (error) {
console.log(error);
}
};
</script>
<style scoped>
.pv-page {
width: 100%;
/* 外层 .layout-content-view 只有 padding-left: 8px,这里补上右侧
同样的间距,播放器才会左右居中而不是偏右贴边 */
padding-right: 8px;
box-sizing: border-box;
}
</style>