owe
This commit is contained in:
1 parent
9b9f07dea5
commit
2e1974c35d
15 files changed
+406
-169
No files matched your search
@@ -89,12 +89,13 @@ namespace CloudBuilder.Master.Service
|
|||||||
//bool isAttached = Debugger.IsAttached;
|
//bool isAttached = Debugger.IsAttached;
|
||||||
foreach (var item in list)
|
foreach (var item in list)
|
||||||
{
|
{
|
||||||
item.Url = FileTypePolicy.PackUrl(service, item.CreatedBy, item.OrderKey, item.TranscodeIndc);
|
item.Url = FileTypePolicy.PackUrl(service, item.CreatedBy, item.OrderKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
data.Datas = list.Select(x => new DmsFileUploadViewDisplayData(x)
|
data.Datas = list.Select(x => new DmsFileUploadViewDisplayData(x)
|
||||||
{
|
{
|
||||||
FormattedPic = FileTypePolicy.PackPicUrl(service, x.CreatedBy, x.OrderKey)
|
FormattedPic = FileTypePolicy.PackPicUrl(service, x.CreatedBy, x.OrderKey),
|
||||||
|
FormattedM3u8 = FileTypePolicy.PackM3u8Url(service, x.CreatedBy, x.OrderKey)
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,12 +158,13 @@ namespace CloudBuilder.Master.Service
|
|||||||
//bool isAttached = Debugger.IsAttached;
|
//bool isAttached = Debugger.IsAttached;
|
||||||
foreach (var item in list)
|
foreach (var item in list)
|
||||||
{
|
{
|
||||||
item.Url = FileTypePolicy.PackUrl(service, item.CreatedBy, item.OrderKey, item.TranscodeIndc);
|
item.Url = FileTypePolicy.PackUrl(service, item.CreatedBy, item.OrderKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
data.Datas = list.Select(x => new DmsFileUploadViewDisplayData(x)
|
data.Datas = list.Select(x => new DmsFileUploadViewDisplayData(x)
|
||||||
{
|
{
|
||||||
FormattedPic = FileTypePolicy.PackPicUrl(service, x.CreatedBy, x.OrderKey)
|
FormattedPic = FileTypePolicy.PackPicUrl(service, x.CreatedBy, x.OrderKey),
|
||||||
|
FormattedM3u8 = FileTypePolicy.PackM3u8Url(service, x.CreatedBy, x.OrderKey)
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,12 +223,13 @@ namespace CloudBuilder.Master.Service
|
|||||||
|
|
||||||
foreach (var item in list)
|
foreach (var item in list)
|
||||||
{
|
{
|
||||||
item.Url = FileTypePolicy.PackUrl(service, item.CreatedBy, item.OrderKey, item.TranscodeIndc);
|
item.Url = FileTypePolicy.PackUrl(service, item.CreatedBy, item.OrderKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
data.Logs = list.Select(x => new DmsFileUploadDeleteLogViewDisplayData(x)
|
data.Logs = list.Select(x => new DmsFileUploadDeleteLogViewDisplayData(x)
|
||||||
{
|
{
|
||||||
FormattedPic = FileTypePolicy.PackPicUrl(service, x.CreatedBy, x.OrderKey)
|
FormattedPic = FileTypePolicy.PackPicUrl(service, x.CreatedBy, x.OrderKey),
|
||||||
|
FormattedM3u8 = FileTypePolicy.PackM3u8Url(service, x.CreatedBy, x.OrderKey)
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,13 +324,14 @@ namespace CloudBuilder.Master.Service
|
|||||||
|
|
||||||
foreach (var item in list)
|
foreach (var item in list)
|
||||||
{
|
{
|
||||||
item.Url = FileTypePolicy.PackUrl(service, item.CreatedBy, item.OrderKey, item.TranscodeIndc);
|
item.Url = FileTypePolicy.PackUrl(service, item.CreatedBy, item.OrderKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
data.Families = list.Select(x => new FamilyDmsFileViewDisplayData(x)
|
data.Families = list.Select(x => new FamilyDmsFileViewDisplayData(x)
|
||||||
{
|
{
|
||||||
FileType = FileTypePolicy.GetType(x.OrderKey),
|
FileType = FileTypePolicy.GetType(x.OrderKey),
|
||||||
FormattedPic = FileTypePolicy.PackPicUrl(service, x.CreatedBy, x.OrderKey)
|
FormattedPic = FileTypePolicy.PackPicUrl(service, x.CreatedBy, x.OrderKey),
|
||||||
|
FormattedM3u8 = FileTypePolicy.PackM3u8Url(service, x.CreatedBy, x.OrderKey)
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,11 +84,11 @@ namespace CloudBuilder.Master.Service
|
|||||||
}
|
}
|
||||||
if (!string.IsNullOrEmpty(item.Url) && FileTypePolicy.GetType(item.Url!) == FileTypePolicy.AUDIO)
|
if (!string.IsNullOrEmpty(item.Url) && FileTypePolicy.GetType(item.Url!) == FileTypePolicy.AUDIO)
|
||||||
{
|
{
|
||||||
item.FormattedUrl = FileTypePolicy.PackUrl(service, item.CreatedBy, item.Url, YesNoPolicy.NO);
|
item.FormattedUrl = FileTypePolicy.PackUrl(service, item.CreatedBy, item.Url);
|
||||||
}
|
}
|
||||||
if (!string.IsNullOrEmpty(item.Lrc) && FileTypePolicy.GetType(item.Lrc!) == FileTypePolicy.LYRIC)
|
if (!string.IsNullOrEmpty(item.Lrc) && FileTypePolicy.GetType(item.Lrc!) == FileTypePolicy.LYRIC)
|
||||||
{
|
{
|
||||||
item.FormattedLrc = FileTypePolicy.PackUrl(service, item.CreatedBy, item.Lrc, YesNoPolicy.NO);
|
item.FormattedLrc = FileTypePolicy.PackUrl(service, item.CreatedBy, item.Lrc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ namespace CloudBuilder.Master.Service
|
|||||||
//暂时不支持转码
|
//暂时不支持转码
|
||||||
DmsFileEntity dmsFileEntity = repositoryDmsFile.DetachedEntities.Where(x => x.OrderKey == item.Url).FirstOrDefault();
|
DmsFileEntity dmsFileEntity = repositoryDmsFile.DetachedEntities.Where(x => x.OrderKey == item.Url).FirstOrDefault();
|
||||||
item.FormattedPic = FileTypePolicy.PackPicUrl(service, item.CreatedBy, item.Url);
|
item.FormattedPic = FileTypePolicy.PackPicUrl(service, item.CreatedBy, item.Url);
|
||||||
item.FormattedUrl = FileTypePolicy.PackUrl(service, item.CreatedBy, item.Url, dmsFileEntity.TranscodeIndc);
|
item.FormattedUrl = FileTypePolicy.PackUrl(service, item.CreatedBy, item.Url);
|
||||||
|
item.FormattedM3u8 = FileTypePolicy.PackM3u8Url(service, item.CreatedBy, item.Url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,5 +33,6 @@ namespace CloudBuilder.Master.Data
|
|||||||
public string? IsPause { get; set; }
|
public string? IsPause { get; set; }
|
||||||
|
|
||||||
public string? FormattedPic { get; set; }
|
public string? FormattedPic { get; set; }
|
||||||
|
public string? FormattedM3u8 { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,5 +32,7 @@ namespace CloudBuilder.Master.Data
|
|||||||
public string? IsPause { get; set; }
|
public string? IsPause { get; set; }
|
||||||
|
|
||||||
public string? FormattedPic { get; set; }
|
public string? FormattedPic { get; set; }
|
||||||
|
|
||||||
|
public string? FormattedM3u8 { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,5 +34,6 @@ namespace CloudBuilder.Master.Data
|
|||||||
public string? IsPause { get; set; }
|
public string? IsPause { get; set; }
|
||||||
|
|
||||||
public string? FormattedPic { get; set; }
|
public string? FormattedPic { get; set; }
|
||||||
|
public string? FormattedM3u8 { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,5 +25,7 @@ namespace CloudBuilder.Master.Data
|
|||||||
public string? FormattedUrl { get; set; }
|
public string? FormattedUrl { get; set; }
|
||||||
|
|
||||||
public string? FormattedPic { get; set; }
|
public string? FormattedPic { get; set; }
|
||||||
|
|
||||||
|
public string? FormattedM3u8 { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,14 +78,18 @@ namespace CloudBuilder.Security.Policy
|
|||||||
{
|
{
|
||||||
return GetType(value) == LYRIC;
|
return GetType(value) == LYRIC;
|
||||||
}
|
}
|
||||||
|
public static string PackM3u8Url(IApplicationService service, string createdBy, string orderKey)
|
||||||
|
{
|
||||||
|
if (!IsVideo(orderKey)) return null;
|
||||||
|
return PackUrl(service, createdBy, orderKey, YesNoPolicy.YES, false);
|
||||||
|
}
|
||||||
public static string PackPicUrl(IApplicationService service, string createdBy, string orderKey)
|
public static string PackPicUrl(IApplicationService service, string createdBy, string orderKey)
|
||||||
{
|
{
|
||||||
return PackUrl(service, createdBy, orderKey, YesNoPolicy.YES, true);
|
return PackUrl(service, createdBy, orderKey, YesNoPolicy.YES, true);
|
||||||
}
|
}
|
||||||
public static string PackUrl(IApplicationService service, string createdBy, string orderKey, string transcodeIndc)
|
public static string PackUrl(IApplicationService service, string createdBy, string orderKey)
|
||||||
{
|
{
|
||||||
return PackUrl(service, createdBy, orderKey, transcodeIndc, false);
|
return PackUrl(service, createdBy, orderKey, YesNoPolicy.NO, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string PackUrl(IApplicationService service, string createdBy, string orderKey, string transcodeIndc, bool isPic)
|
public static string PackUrl(IApplicationService service, string createdBy, string orderKey, string transcodeIndc, bool isPic)
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
<template #closeIcon>
|
<template #closeIcon>
|
||||||
<slot name="closeIcon">
|
<slot name="closeIcon">
|
||||||
<Space class="ant-modal-operate" @click.stop>
|
<Space class="ant-modal-operate" @click.stop>
|
||||||
|
<!-- 可选插槽:放在「全屏」左边,供调用方追加自己的按钮。不传则什么都不渲染 -->
|
||||||
|
<slot name="operate" />
|
||||||
<FullscreenOutlined v-if="!fullscreenModel" @click="fullscreenModel = true" />
|
<FullscreenOutlined v-if="!fullscreenModel" @click="fullscreenModel = true" />
|
||||||
<FullscreenExitOutlined v-else @click="restore" />
|
<FullscreenExitOutlined v-else @click="restore" />
|
||||||
<CloseOutlined @click="closeModal" />
|
<CloseOutlined @click="closeModal" />
|
||||||
|
|||||||
@@ -1,14 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<dragModal v-model:open="visible" :width="width" :title="title" :footer="null" :mask-closable="false"
|
<dragModal v-model:open="visible" :width="width" :title="title" :footer="null" :mask-closable="false"
|
||||||
:fullscreen="fullscreen" wrap-class-name="vpm-wrap">
|
:fullscreen="fullscreen" wrap-class-name="vpm-wrap">
|
||||||
|
<!-- 弹窗右上角追加「页面全屏」按钮,位置在 DragModal 自带的「全屏」左边。
|
||||||
|
它的作用是把底部控制区隐藏、让画面占满整个弹窗,再点一次恢复 -->
|
||||||
|
<template #operate>
|
||||||
|
<span class="vpm-page-full" :class="{ 'is-on': !controlsVisible }"
|
||||||
|
:title="controlsVisible ? '页面全屏(隐藏控制区)' : '退出页面全屏(显示控制区)'"
|
||||||
|
@click.stop="controlsVisible = !controlsVisible">
|
||||||
|
<EyeOutlined v-if="!controlsVisible" />
|
||||||
|
<EyeInvisibleOutlined v-else />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<div class="vpm-body">
|
<div class="vpm-body">
|
||||||
<VideoPlayer :videos="videos" :start-index="startIndex" :auto-play="true" />
|
<VideoPlayer v-model:controls="controlsVisible" :videos="videos" :start-index="startIndex" :auto-play="true" />
|
||||||
</div>
|
</div>
|
||||||
</dragModal>
|
</dragModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref } from "vue";
|
||||||
import { useVModel } from "@vueuse/core";
|
import { useVModel } from "@vueuse/core";
|
||||||
|
import { EyeInvisibleOutlined, EyeOutlined } from "@ant-design/icons-vue";
|
||||||
|
|
||||||
import dragModal from "@/components/basic/DragModal.vue";
|
import dragModal from "@/components/basic/DragModal.vue";
|
||||||
import VideoPlayer from "@/components/basic/VideoPlayer.vue";
|
import VideoPlayer from "@/components/basic/VideoPlayer.vue";
|
||||||
@@ -37,6 +49,13 @@ const props = withDefaults(
|
|||||||
const emit = defineEmits(["update:open", "close"]);
|
const emit = defineEmits(["update:open", "close"]);
|
||||||
|
|
||||||
const visible = useVModel(props, "open", emit);
|
const visible = useVModel(props, "open", emit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 底部控制区显隐(右上角「页面全屏」按钮):
|
||||||
|
* 隐藏后播放器的画面区会吃掉控制区占的高度,画面铺满弹窗;再点一次恢复。
|
||||||
|
* 单击画面区、播放器控制栏里的眼睛图标也是同一个动作,状态互通。
|
||||||
|
*/
|
||||||
|
const controlsVisible = ref(true);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -45,6 +64,32 @@ const visible = useVModel(props, "open", emit);
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 240px;
|
min-height: 240px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 右上角的「页面全屏」图标。
|
||||||
|
DragModal 的关闭按钮(.ant-modal-close)宽度固定(close-x 50px),
|
||||||
|
把这个图标塞进它的图标排布里会把「全屏」「关闭」挤变形甚至挤出边界,
|
||||||
|
所以用绝对定位挂到关闭按钮左侧:不占它的布局空间。
|
||||||
|
尺寸/垂直对齐按关闭按钮的盒子(高 50px、图标 16px)写死,保证和旁边两个一致 */
|
||||||
|
.vpm-page-full {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: calc(100% + 4px);
|
||||||
|
width: 32px;
|
||||||
|
height: 50px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: -10px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 已经进入页面全屏(控制区隐藏)时高亮,和播放器里的图标状态保持一致 */
|
||||||
|
.vpm-page-full.is-on {
|
||||||
|
color: #13c2c2;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -62,4 +107,16 @@ const visible = useVModel(props, "open", emit);
|
|||||||
height: auto;
|
height: auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 文件名可能很长:单行显示,超出用 ... 截断,不再折行把标题栏撑高。
|
||||||
|
右侧留出「页面全屏 + 全屏 + 关闭」三个按钮的宽度,避免长标题被按钮压住 */
|
||||||
|
.ant-modal-wrap.vpm-wrap .ant-modal .ant-modal-header {
|
||||||
|
padding-right: 128px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-wrap.vpm-wrap .ant-modal .ant-modal-title {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 画面区:原生 video 只负责解码播放,控制栏全部自绘(与 VinylAudioPlayer 一致) -->
|
<!-- 画面区:原生 video 只负责解码播放,控制栏全部自绘(与 VinylAudioPlayer 一致) -->
|
||||||
|
<!-- 单击画面区 = 播放/暂停(收起控制区改用「页面全屏」/眼睛按钮) -->
|
||||||
<div v-else class="vp-stage" ref="stageRef" @click="toggle">
|
<div v-else class="vp-stage" ref="stageRef" @click="toggle">
|
||||||
<!-- 不绑 :src:m3u8 要交给 hls.js 解码,播放源统一由 attachSource 挂接 -->
|
<!-- 不绑 :src:m3u8 要交给 hls.js 解码,播放源统一由 attachSource 挂接 -->
|
||||||
<video ref="videoRef" class="vp-media" :class="{ 'is-rot': rotated }" :style="rotStyle"
|
<video ref="videoRef" class="vp-media" :class="{ 'is-rot': rotated }" :style="rotStyle"
|
||||||
@@ -25,13 +26,15 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 控制区 -->
|
<!-- 控制区:单击画面区可隐藏,隐藏后画面区吃掉这部分空间 -->
|
||||||
<div v-if="current" class="vp-body">
|
<div v-if="current && controlsVisible" class="vp-body">
|
||||||
<!-- 功能图标行 -->
|
<!-- 功能图标行 -->
|
||||||
<div class="vp-actions">
|
<div class="vp-actions">
|
||||||
<button class="vp-act" :class="{ 'is-fav': isFaved }" type="button" title="喜欢" @click="onFav">
|
<!-- 原码/转码切换:默认播转码(FormattedM3u8),点这里切到原码(Url) -->
|
||||||
<HeartFilled v-if="isFaved" />
|
<button class="vp-act" :class="{ 'is-on': useOriginal }" type="button" :title="sourceTitle"
|
||||||
<HeartOutlined v-else />
|
@click="toggleSource">
|
||||||
|
<VideoCameraOutlined />
|
||||||
|
<span class="vp-badge" :class="{ 'is-on': useOriginal }">{{ useOriginal ? "原码" : "转码" }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="vp-act" type="button" title="下载" @click="onDownload">
|
<button class="vp-act" type="button" title="下载" @click="onDownload">
|
||||||
<DownloadOutlined />
|
<DownloadOutlined />
|
||||||
@@ -41,7 +44,13 @@
|
|||||||
<FullscreenExitOutlined v-if="isFullscreen" />
|
<FullscreenExitOutlined v-if="isFullscreen" />
|
||||||
<FullscreenOutlined v-else />
|
<FullscreenOutlined v-else />
|
||||||
</button>
|
</button>
|
||||||
<button class="vp-act" :class="{ 'is-on': rotated }" type="button" :title="rotTitle"
|
<!-- 隐藏/显示控制区:收起后画面区吃掉这部分空间(弹窗右上角的「页面全屏」也是同一个动作) -->
|
||||||
|
<button class="vp-act" :class="{ 'is-on': !controlsVisible }" type="button"
|
||||||
|
:title="controlsVisible ? '隐藏控制区' : '显示控制区'" @click="toggleControls">
|
||||||
|
<EyeInvisibleOutlined v-if="controlsVisible" />
|
||||||
|
<EyeOutlined v-else />
|
||||||
|
</button>
|
||||||
|
<button class="vp-act" :class="{ 'is-on': rotated }" type="button" title="横竖屏切换"
|
||||||
@click="cycleRot">
|
@click="cycleRot">
|
||||||
<RotateRightOutlined />
|
<RotateRightOutlined />
|
||||||
<span v-if="rotMode === 'auto'" class="vp-rot-auto">A</span>
|
<span v-if="rotMode === 'auto'" class="vp-rot-auto">A</span>
|
||||||
@@ -147,16 +156,18 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from "vue";
|
import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from "vue";
|
||||||
|
import { useVModel } from "@vueuse/core";
|
||||||
import { message } from "ant-design-vue";
|
import { message } from "ant-design-vue";
|
||||||
import Hls from "hls.js";
|
import Hls from "hls.js";
|
||||||
import { request } from "@/utils/request";
|
import { request } from "@/utils/request";
|
||||||
import type { PublishVideoDetailDisplayData } from "@/data/auto/publishVideoDetailDisplayData";
|
import type { PublishVideoDetailDisplayData } from "@/data/auto/publishVideoDetailDisplayData";
|
||||||
import {
|
import {
|
||||||
HeartOutlined, HeartFilled, DownloadOutlined, FieldTimeOutlined,
|
DownloadOutlined, FieldTimeOutlined,
|
||||||
AudioOutlined, AudioMutedOutlined, EllipsisOutlined, RotateRightOutlined,
|
AudioOutlined, AudioMutedOutlined, EllipsisOutlined, RotateRightOutlined,
|
||||||
RetweetOutlined, SwapOutlined, StepBackwardFilled, StepForwardFilled,
|
RetweetOutlined, SwapOutlined, StepBackwardFilled, StepForwardFilled,
|
||||||
CaretRightFilled, PauseOutlined, UnorderedListOutlined, CloseOutlined,
|
CaretRightFilled, PauseOutlined, UnorderedListOutlined, CloseOutlined,
|
||||||
FullscreenOutlined, FullscreenExitOutlined, VideoCameraOutlined,
|
FullscreenOutlined, FullscreenExitOutlined, VideoCameraOutlined,
|
||||||
|
EyeOutlined, EyeInvisibleOutlined,
|
||||||
} from "@ant-design/icons-vue";
|
} from "@ant-design/icons-vue";
|
||||||
|
|
||||||
/** 一个视频 */
|
/** 一个视频 */
|
||||||
@@ -169,10 +180,14 @@ const props = withDefaults(
|
|||||||
startIndex?: number;
|
startIndex?: number;
|
||||||
/** 挂上播放源后是否立即播放(弹窗里默认自动播) */
|
/** 挂上播放源后是否立即播放(弹窗里默认自动播) */
|
||||||
autoPlay?: boolean;
|
autoPlay?: boolean;
|
||||||
|
/** 底部控制区是否显示,可用 v-model:controls 从外部控制(单击画面区也会切换) */
|
||||||
|
controls?: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{ videos: () => [], startIndex: 0, autoPlay: false },
|
{ videos: () => [], startIndex: 0, autoPlay: false, controls: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{ (e: "update:controls", value: boolean): void }>();
|
||||||
|
|
||||||
/* ---------------- 基础状态 ---------------- */
|
/* ---------------- 基础状态 ---------------- */
|
||||||
const videoRef = ref<HTMLVideoElement | null>(null);
|
const videoRef = ref<HTMLVideoElement | null>(null);
|
||||||
const index = ref(props.startIndex);
|
const index = ref(props.startIndex);
|
||||||
@@ -184,6 +199,15 @@ const muted = ref(false);
|
|||||||
const rate = ref(1);
|
const rate = ref(1);
|
||||||
const mode = ref<"list" | "one" | "random">("list");
|
const mode = ref<"list" | "one" | "random">("list");
|
||||||
const showList = ref(false);
|
const showList = ref(false);
|
||||||
|
/**
|
||||||
|
* 底部控制区是否显示:由控制栏的眼睛图标 / 弹窗右上角的「页面全屏」按钮切换。
|
||||||
|
*
|
||||||
|
* passive: true 是关键:不传的话 useVModel 直接读写 props,
|
||||||
|
* 父组件没绑 v-model:controls 时(例如影视厅 PreviewVideo 页面)
|
||||||
|
* 内部改了值也只会 emit 出去、props 不变,按钮就"点了没反应"。
|
||||||
|
* passive 会保留本地状态,父组件绑了 v-model 时依然双向同步。
|
||||||
|
*/
|
||||||
|
const controlsVisible = useVModel(props, "controls", emit, { passive: true });
|
||||||
const menu = ref<"" | "timer" | "volume" | "more">("");
|
const menu = ref<"" | "timer" | "volume" | "more">("");
|
||||||
const downloaded = ref(false);
|
const downloaded = ref(false);
|
||||||
/** 是否正在缓冲:数据不够时会卡住画面,必须让用户看得见 */
|
/** 是否正在缓冲:数据不够时会卡住画面,必须让用户看得见 */
|
||||||
@@ -280,6 +304,15 @@ const toggle = () => {
|
|||||||
if (v.paused) tryPlay();
|
if (v.paused) tryPlay();
|
||||||
else v.pause();
|
else v.pause();
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* 切换底部控制区(功能图标行/进度条/主控制)的显示。
|
||||||
|
* 收起后 .vp-stage 的 flex:1 会吃掉这部分高度,画面最大化。
|
||||||
|
* 入口:控制栏里的眼睛图标、弹窗右上角的「页面全屏」按钮。
|
||||||
|
*/
|
||||||
|
const toggleControls = () => {
|
||||||
|
menu.value = ""; // 顺手收起可能打开的弹层(定时/音量/倍速)
|
||||||
|
controlsVisible.value = !controlsVisible.value;
|
||||||
|
};
|
||||||
const playAt = (i: number) => {
|
const playAt = (i: number) => {
|
||||||
if (i === index.value) {
|
if (i === index.value) {
|
||||||
toggle();
|
toggle();
|
||||||
@@ -320,6 +353,11 @@ const onLoadedMetadata = () => {
|
|||||||
const v = videoRef.value;
|
const v = videoRef.value;
|
||||||
if (!v) return;
|
if (!v) return;
|
||||||
if (isFinite(v.duration)) duration.value = v.duration;
|
if (isFinite(v.duration)) duration.value = v.duration;
|
||||||
|
// 切换原码/转码后恢复播放位置
|
||||||
|
if (pendingSeek != null) {
|
||||||
|
v.currentTime = pendingSeek;
|
||||||
|
pendingSeek = null;
|
||||||
|
}
|
||||||
// 自动识别横竖屏:宽 > 高 视为横屏,auto 模式下旋转90°播放
|
// 自动识别横竖屏:宽 > 高 视为横屏,auto 模式下旋转90°播放
|
||||||
landscape.value = v.videoWidth > v.videoHeight;
|
landscape.value = v.videoWidth > v.videoHeight;
|
||||||
};
|
};
|
||||||
@@ -358,7 +396,8 @@ const clearStallTimer = () => {
|
|||||||
* 第 2 个用 ffmpeg 重封装(不重新编码)即可解决,几乎不吃 CPU。
|
* 第 2 个用 ffmpeg 重封装(不重新编码)即可解决,几乎不吃 CPU。
|
||||||
*/
|
*/
|
||||||
const diagnoseStall = async () => {
|
const diagnoseStall = async () => {
|
||||||
const url = current.value?.FormattedUrl;
|
// 诊断当前真正在播的那个源(转码或原码),不是写死 FormattedUrl
|
||||||
|
const url = sourceUrl.value;
|
||||||
if (!url || isHlsSource(url)) return;
|
if (!url || isHlsSource(url)) return;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(url, { headers: { Range: "bytes=0-1" }, credentials: "include" });
|
const res = await fetch(url, { headers: { Range: "bytes=0-1" }, credentials: "include" });
|
||||||
@@ -487,39 +526,60 @@ const onSleep = (v: number) => {
|
|||||||
menu.value = "";
|
menu.value = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ---------------- 喜欢 / 下载 / 信息 ---------------- */
|
/* ---------------- 原码 / 转码切换 ---------------- */
|
||||||
const FAV_KEY = "vp-favs";
|
/** false = 播转码(FormattedM3u8,10 秒预览,默认);true = 播原码(Url) */
|
||||||
const loadFavs = (): Set<string> => {
|
const useOriginal = ref(false);
|
||||||
try {
|
const sourceTitle = computed(() =>
|
||||||
return new Set<string>(JSON.parse(localStorage.getItem(FAV_KEY) || "[]"));
|
useOriginal.value ? "正在播放原码,点击切回转码" : "正在播放转码,点击切换原码"
|
||||||
} catch {
|
);
|
||||||
return new Set<string>();
|
/** 只有像地址(http(s)://、//、/)的才拿去播,避免把 OrderKey 这类文件名当 URL 请求 */
|
||||||
}
|
const isPlayableUrl = (u?: string) => !!u && /^(https?:)?\/\//i.test(u);
|
||||||
};
|
|
||||||
const favs = ref<Set<string>>(loadFavs());
|
/** 当前应使用的播放地址:默认转码;切原码但该视频没有可用原码时回退转码 */
|
||||||
const isFaved = computed(() => !!current.value && favs.value.has(current.value.FormattedUrl));
|
const sourceUrl = computed(() => {
|
||||||
const onFav = () => {
|
const t = current.value;
|
||||||
|
if (!t) return "";
|
||||||
|
if (useOriginal.value && isPlayableUrl(t.Url)) return t.Url;
|
||||||
|
return t.FormattedM3u8 || t.FormattedUrl || "";
|
||||||
|
});
|
||||||
|
/** 切换播放源后要恢复到的播放位置(秒) */
|
||||||
|
let pendingSeek: number | null = null;
|
||||||
|
|
||||||
|
const toggleSource = () => {
|
||||||
|
const v = videoRef.value;
|
||||||
if (!current.value) return;
|
if (!current.value) return;
|
||||||
const s = new Set(favs.value);
|
if (useOriginal.value && !current.value.Url) {
|
||||||
if (s.has(current.value.FormattedUrl)) s.delete(current.value.FormattedUrl);
|
message.info("该视频没有原码");
|
||||||
else s.add(current.value.FormattedUrl);
|
return;
|
||||||
favs.value = s;
|
}
|
||||||
localStorage.setItem(FAV_KEY, JSON.stringify([...s]));
|
// 记住播放位置,新源的元数据加载完后再跳回去(见 onLoadedMetadata)
|
||||||
|
pendingSeek = v?.currentTime ?? 0;
|
||||||
|
const wasPlaying = isPlaying.value;
|
||||||
|
useOriginal.value = !useOriginal.value;
|
||||||
|
// 重新挂接播放源:m3u8 走 hls.js,原码 mp4 走 src(内部会销毁上一个 hls 实例)
|
||||||
|
attachSource(sourceUrl.value);
|
||||||
|
if (wasPlaying) tryPlay();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDownload = async () => {
|
const onDownload = async () => {
|
||||||
if (!current.value) return;
|
if (!current.value) return;
|
||||||
|
// 优先下载原码;m3u8 是切片清单,下载下来没法直接看
|
||||||
|
const downloadUrl = current.value.Url || current.value.FormattedUrl || sourceUrl.value;
|
||||||
// 参考 DmsFileEnquiry.vue:先取回字节流,再用 Blob + <a download> 落盘
|
// 参考 DmsFileEnquiry.vue:先取回字节流,再用 Blob + <a download> 落盘
|
||||||
try {
|
try {
|
||||||
const res = await request<ArrayBuffer>({
|
const res = await request<ArrayBuffer>({
|
||||||
url: current.value.FormattedUrl,
|
url: downloadUrl,
|
||||||
method: "get",
|
method: "get",
|
||||||
responseType: "arraybuffer",
|
responseType: "arraybuffer",
|
||||||
});
|
});
|
||||||
const blobUrl = window.URL.createObjectURL(new Blob([res]));
|
const blobUrl = window.URL.createObjectURL(new Blob([res]));
|
||||||
const ext = (current.value.FormattedUrl.match(/\.(\w{2,5})(?:\?|#|$)/) || [])[1];
|
const ext = (downloadUrl.match(/\.(\w{2,5})(?:\?|#|$)/) || [])[1];
|
||||||
|
// Title 通常是原始文件名、自带扩展名,先把已有扩展名剥掉再拼,
|
||||||
|
// 否则会得到「xxx.mp4.mp4」这样的双后缀
|
||||||
|
const base = (current.value.Title || "video").replace(/\.[^.]+$/, "");
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
a.href = blobUrl;
|
a.href = blobUrl;
|
||||||
a.download = `${current.value.Title || "video"}.${ext || "mp4"}`;
|
a.download = ext ? `${base}.${ext}` : base;
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
a.remove();
|
a.remove();
|
||||||
@@ -537,13 +597,12 @@ const landscape = ref(false);
|
|||||||
const rotated = computed(
|
const rotated = computed(
|
||||||
() => rotMode.value === "on" ? true : rotMode.value === "off" ? false : landscape.value
|
() => rotMode.value === "on" ? true : rotMode.value === "off" ? false : landscape.value
|
||||||
);
|
);
|
||||||
const rotTitle = computed(() => ({
|
|
||||||
auto: "自动旋转(横屏视频转90°)",
|
|
||||||
on: "强制旋转90°",
|
|
||||||
off: "关闭旋转",
|
|
||||||
}[rotMode.value]));
|
|
||||||
const cycleRot = () => {
|
const cycleRot = () => {
|
||||||
rotMode.value = rotMode.value === "auto" ? "on" : rotMode.value === "on" ? "off" : "auto";
|
// 单击立即翻转画面方向。
|
||||||
|
// 之前是 auto→on→off 轮转,但横屏视频在 auto 和 on 下画面完全相同,
|
||||||
|
// 第一次点击看不出任何变化,用户以为失灵、要按两次才生效。
|
||||||
|
// 这里直接按「当前画面是否已旋转」取反:转着 → 关,没转 → 转。
|
||||||
|
rotMode.value = rotated.value ? "off" : "on";
|
||||||
};
|
};
|
||||||
/** 旋转后 video 宽高对调,横屏视频即可铺满竖版画面区 */
|
/** 旋转后 video 宽高对调,横屏视频即可铺满竖版画面区 */
|
||||||
const stageRef = ref<HTMLElement | null>(null);
|
const stageRef = ref<HTMLElement | null>(null);
|
||||||
@@ -573,14 +632,16 @@ const onFullscreen = async () => {
|
|||||||
const v = videoRef.value;
|
const v = videoRef.value;
|
||||||
if (!v) return;
|
if (!v) return;
|
||||||
try {
|
try {
|
||||||
if (document.fullscreenElement) await document.exitFullscreen();
|
// 只在「video 元素自己在全屏」时才退出;页面全屏(documentElement)时直接切到 video 全屏
|
||||||
|
if (document.fullscreenElement === v) await document.exitFullscreen();
|
||||||
else await v.requestFullscreen();
|
else await v.requestFullscreen();
|
||||||
} catch {
|
} catch {
|
||||||
message.error("当前浏览器不支持全屏");
|
message.error("当前浏览器不支持全屏");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const onFullscreenChange = () => {
|
const onFullscreenChange = () => {
|
||||||
isFullscreen.value = !!document.fullscreenElement;
|
// 只认 video 元素自己的全屏,否则弹窗的「页面全屏」会让这里的图标状态错乱
|
||||||
|
isFullscreen.value = document.fullscreenElement === videoRef.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ---------------- 弹层开合 ---------------- */
|
/* ---------------- 弹层开合 ---------------- */
|
||||||
@@ -608,8 +669,9 @@ const prepare = async (t: VideoItem | null, play: boolean) => {
|
|||||||
if (!t) return;
|
if (!t) return;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
ensureStageRO();
|
ensureStageRO();
|
||||||
// 重新挂接播放源:m3u8 走 hls.js,普通文件走 src(内部会销毁上一个 hls 实例)
|
// 重新挂接播放源:m3u8 走 hls.js,原码 mp4 走 src(内部会销毁上一个 hls 实例)
|
||||||
attachSource(t.FormattedUrl);
|
// 播放源按「转码优先」取:默认 FormattedM3u8,切了原码用 Url
|
||||||
|
attachSource(sourceUrl.value);
|
||||||
if (play || props.autoPlay) {
|
if (play || props.autoPlay) {
|
||||||
resumeAfterSwitch = false;
|
resumeAfterSwitch = false;
|
||||||
tryPlay();
|
tryPlay();
|
||||||
@@ -799,6 +861,8 @@ onBeforeUnmount(() => {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* ---------- 功能图标行 ---------- */
|
/* ---------- 功能图标行 ---------- */
|
||||||
.vp-actions {
|
.vp-actions {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ import { Howl } from "howler";
|
|||||||
import { PublishVideoEntity } from "@/data/auto/publishVideoEntity";
|
import { PublishVideoEntity } from "@/data/auto/publishVideoEntity";
|
||||||
import publishVideoService from "@/service/master/publishVideoService";
|
import publishVideoService from "@/service/master/publishVideoService";
|
||||||
import { useWindowSize } from "@vueuse/core";
|
import { useWindowSize } from "@vueuse/core";
|
||||||
|
import { request } from "@/utils/request";
|
||||||
import fileTypePolicy from "@/policy/custom/fileTypePolicy";
|
import fileTypePolicy from "@/policy/custom/fileTypePolicy";
|
||||||
import { PublishAudioEntity } from "@/data/auto/publishAudioEntity";
|
import { PublishAudioEntity } from "@/data/auto/publishAudioEntity";
|
||||||
import { FamilyFolderFavoriteViewEntity } from "@/data/auto/familyFolderFavoriteViewEntity";
|
import { FamilyFolderFavoriteViewEntity } from "@/data/auto/familyFolderFavoriteViewEntity";
|
||||||
@@ -292,18 +293,20 @@ const onChangeFolder = async (fvalue: string) => {
|
|||||||
onSearchByFilePath(fvalue);
|
onSearchByFilePath(fvalue);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** DMS 记录 → 播放列表项(m3u8 的封面用同名 png 兜底) */
|
/** DMS 记录 → 播放列表项:Url=原码、FormattedM3u8=转码(10秒),封面用同名 png 兜底 */
|
||||||
const toVideoItem = (
|
const toVideoItem = (
|
||||||
record: DmsFileUploadDeleteLogViewDisplayData,
|
record: DmsFileUploadDeleteLogViewDisplayData,
|
||||||
): VideoItem => {
|
): VideoItem => {
|
||||||
const item = new PublishVideoDetailDisplayData();
|
const item = new PublishVideoDetailDisplayData();
|
||||||
item.Title = record.OrgFileName;
|
item.Title = record.OrgFileName;
|
||||||
item.FormattedUrl = record.Url;
|
item.Url = record.Url;
|
||||||
|
item.FormattedM3u8 = record.FormattedM3u8;
|
||||||
|
item.FormattedUrl = record.FormattedM3u8 || record.Url;
|
||||||
item.FormattedPic = (record.FormattedPic || "").replace(/\.m3u8$/, ".png");
|
item.FormattedPic = (record.FormattedPic || "").replace(/\.m3u8$/, ".png");
|
||||||
return item;
|
return item;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 当前目录里已转码(有 Url)的视频,作为弹窗播放器的播放列表 */
|
/** 当前目录里的视频,作为弹窗播放器的播放列表 */
|
||||||
const buildVideoList = (
|
const buildVideoList = (
|
||||||
records: DmsFileUploadDeleteLogViewDisplayData[],
|
records: DmsFileUploadDeleteLogViewDisplayData[],
|
||||||
): VideoItem[] =>
|
): VideoItem[] =>
|
||||||
@@ -312,7 +315,7 @@ const buildVideoList = (
|
|||||||
(x) =>
|
(x) =>
|
||||||
x.IsDirectoryIndc != "Y" &&
|
x.IsDirectoryIndc != "Y" &&
|
||||||
fileTypePolicy.isVideo(x.OrderKey || x.OrgFileName) &&
|
fileTypePolicy.isVideo(x.OrderKey || x.OrgFileName) &&
|
||||||
!!x.Url,
|
!!(x.FormattedM3u8 || x.Url),
|
||||||
)
|
)
|
||||||
.map(toVideoItem);
|
.map(toVideoItem);
|
||||||
|
|
||||||
@@ -328,27 +331,6 @@ const openVideoPlay = (list: VideoItem[], index: number, title: string) => {
|
|||||||
videoPlayModal.value = true;
|
videoPlayModal.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 未转码的视频没有 Url,直接取源片路径播放 */
|
|
||||||
const onSourceVideoPlay = async (
|
|
||||||
record: DmsFileUploadDeleteLogViewDisplayData,
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
loading.value = true;
|
|
||||||
const url = await dmsFileService.FindDeleteLogM3u8Path(record.FileId);
|
|
||||||
const item = toVideoItem(record);
|
|
||||||
item.FormattedUrl = url;
|
|
||||||
openVideoPlay([item], 0, record.OrgFileName);
|
|
||||||
} catch (error) {
|
|
||||||
notification.info({
|
|
||||||
message: "系统提示:",
|
|
||||||
description: "视频加载失败",
|
|
||||||
placement: "topRight",
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onPreviewFile = async (
|
const onPreviewFile = async (
|
||||||
usernameOrRecord: any,
|
usernameOrRecord: any,
|
||||||
orderKey?: string,
|
orderKey?: string,
|
||||||
@@ -483,8 +465,57 @@ const onShareFamily = async (record: Record<string, any>) => {
|
|||||||
showModalShare.value = true;
|
showModalShare.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDownloadFile = async (fid: number, fname: string) => {
|
/** 按 URL 直接下载(视频走这条:Url 就是原码文件) */
|
||||||
GetOrgPictureFile(fid, fname, false);
|
const downloadByUrl = async (url: string, fileName: string) => {
|
||||||
|
if (!url) {
|
||||||
|
notification.info({
|
||||||
|
message: "系统提示:",
|
||||||
|
description: "该文件没有可下载的地址",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
spinTip.value = "下载中...";
|
||||||
|
// 与 VideoPlayer 一致:先取回字节流,再用 Blob + <a download> 落盘
|
||||||
|
const res = await request<ArrayBuffer>({
|
||||||
|
url,
|
||||||
|
method: "get",
|
||||||
|
responseType: "arraybuffer",
|
||||||
|
});
|
||||||
|
const blobUrl = window.URL.createObjectURL(new Blob([res]));
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = blobUrl;
|
||||||
|
link.setAttribute("download", fileName);
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
setTimeout(() => window.URL.revokeObjectURL(blobUrl), 1000);
|
||||||
|
} catch (error) {
|
||||||
|
notification.info({
|
||||||
|
message: "系统提示:",
|
||||||
|
description: "下载失败",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
spinTip.value = "加载中...";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 下载一条记录:视频下原码,其它文件走原来的分片合并下载 */
|
||||||
|
const onDownloadFile = async (
|
||||||
|
record: DmsFileUploadDeleteLogViewDisplayData,
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
fileTypePolicy.isVideo(record.OrgFileName) ||
|
||||||
|
fileTypePolicy.isVideo(record.OrderKey)
|
||||||
|
) {
|
||||||
|
await downloadByUrl(record.Url, record.OrgFileName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GetOrgPictureFile(record.FileId, record.OrgFileName, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const GetOrgPictureFile = async (
|
const GetOrgPictureFile = async (
|
||||||
@@ -975,16 +1006,19 @@ const onOpenFile = (record: DmsFileUploadDeleteLogViewDisplayData) => {
|
|||||||
} else if (fileTypePolicy.isAudio(record.OrgFileName)) {
|
} else if (fileTypePolicy.isAudio(record.OrgFileName)) {
|
||||||
onPlayAudio(toUploadView(record));
|
onPlayAudio(toUploadView(record));
|
||||||
} else {
|
} else {
|
||||||
onDownloadFile(record.FileId, record.OrgFileName);
|
onDownloadFile(record);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDownloadSelected = (items: DmsFileUploadDeleteLogViewDisplayData[]) => {
|
const onDownloadSelected = async (
|
||||||
items.forEach((item) => {
|
items: DmsFileUploadDeleteLogViewDisplayData[],
|
||||||
|
) => {
|
||||||
|
// 逐个串行下载:并发触发多个 <a download> 浏览器会拦截后面的
|
||||||
|
for (const item of items) {
|
||||||
if (item.IsDirectoryIndc != "Y") {
|
if (item.IsDirectoryIndc != "Y") {
|
||||||
onDownloadFile(item.FileId, item.OrgFileName);
|
await onDownloadFile(item);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onShareSelected = (items: DmsFileUploadDeleteLogViewDisplayData[]) => {
|
const onShareSelected = (items: DmsFileUploadDeleteLogViewDisplayData[]) => {
|
||||||
@@ -1018,15 +1052,9 @@ const onViewPreviewSelected = (items: DmsFileUploadDeleteLogViewDisplayData[]) =
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** 播放视频:把当前目录的视频串成播放列表,从点中的那一个开始 */
|
/** 播放视频:把当前目录的视频串成播放列表,从点中的那一个开始 */
|
||||||
const onUrlVideoPlayItem = async (
|
const onUrlVideoPlayItem = (record: DmsFileUploadDeleteLogViewDisplayData) => {
|
||||||
record: DmsFileUploadDeleteLogViewDisplayData,
|
|
||||||
) => {
|
|
||||||
if (!record.Url) {
|
|
||||||
await onSourceVideoPlay(record);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const list = buildVideoList(datas.value);
|
const list = buildVideoList(datas.value);
|
||||||
let index = list.findIndex((x) => x.FormattedUrl === record.Url);
|
let index = list.findIndex((x) => x.Url === record.Url);
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
list.unshift(toVideoItem(record));
|
list.unshift(toVideoItem(record));
|
||||||
index = 0;
|
index = 0;
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ import { Howl } from "howler";
|
|||||||
import { PublishVideoEntity } from "@/data/auto/publishVideoEntity";
|
import { PublishVideoEntity } from "@/data/auto/publishVideoEntity";
|
||||||
import publishVideoService from "@/service/master/publishVideoService";
|
import publishVideoService from "@/service/master/publishVideoService";
|
||||||
import { useWindowSize } from "@vueuse/core";
|
import { useWindowSize } from "@vueuse/core";
|
||||||
|
import { request } from "@/utils/request";
|
||||||
import fileTypePolicy from "@/policy/custom/fileTypePolicy";
|
import fileTypePolicy from "@/policy/custom/fileTypePolicy";
|
||||||
import { PublishAudioEntity } from "@/data/auto/publishAudioEntity";
|
import { PublishAudioEntity } from "@/data/auto/publishAudioEntity";
|
||||||
import { FamilyFolderFavoriteViewEntity } from "@/data/auto/familyFolderFavoriteViewEntity";
|
import { FamilyFolderFavoriteViewEntity } from "@/data/auto/familyFolderFavoriteViewEntity";
|
||||||
@@ -462,16 +463,18 @@ const onChangeFolder = async (fvalue: string) => {
|
|||||||
onSearchByFilePath(fvalue);
|
onSearchByFilePath(fvalue);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** DMS 记录 → 播放列表项(m3u8 的封面用同名 png 兜底) */
|
/** DMS 记录 → 播放列表项:Url=原码、FormattedM3u8=转码(10秒),封面用同名 png 兜底 */
|
||||||
const toVideoItem = (record: DmsFileUploadViewDisplayData): VideoItem => {
|
const toVideoItem = (record: DmsFileUploadViewDisplayData): VideoItem => {
|
||||||
const item = new PublishVideoDetailDisplayData();
|
const item = new PublishVideoDetailDisplayData();
|
||||||
item.Title = record.OrgFileName;
|
item.Title = record.OrgFileName;
|
||||||
item.FormattedUrl = record.Url;
|
item.Url = record.Url;
|
||||||
|
item.FormattedM3u8 = record.FormattedM3u8;
|
||||||
|
item.FormattedUrl = record.FormattedM3u8 || record.Url;
|
||||||
item.FormattedPic = (record.FormattedPic || "").replace(/\.m3u8$/, ".png");
|
item.FormattedPic = (record.FormattedPic || "").replace(/\.m3u8$/, ".png");
|
||||||
return item;
|
return item;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 当前目录里已转码(有 Url)的视频,作为弹窗播放器的播放列表 */
|
/** 当前目录里的视频,作为弹窗播放器的播放列表 */
|
||||||
const buildVideoList = (
|
const buildVideoList = (
|
||||||
records: DmsFileUploadViewDisplayData[],
|
records: DmsFileUploadViewDisplayData[],
|
||||||
): VideoItem[] =>
|
): VideoItem[] =>
|
||||||
@@ -480,7 +483,7 @@ const buildVideoList = (
|
|||||||
(x) =>
|
(x) =>
|
||||||
x.IsDirectoryIndc != "Y" &&
|
x.IsDirectoryIndc != "Y" &&
|
||||||
fileTypePolicy.isVideo(x.OrderKey || x.OrgFileName) &&
|
fileTypePolicy.isVideo(x.OrderKey || x.OrgFileName) &&
|
||||||
!!x.Url,
|
!!(x.FormattedM3u8 || x.Url),
|
||||||
)
|
)
|
||||||
.map(toVideoItem);
|
.map(toVideoItem);
|
||||||
|
|
||||||
@@ -496,25 +499,6 @@ const openVideoPlay = (list: VideoItem[], index: number, title: string) => {
|
|||||||
videoPlayModal.value = true;
|
videoPlayModal.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 未转码的视频没有 Url,直接取源片路径播放 */
|
|
||||||
const onSourceVideoPlay = async (record: DmsFileUploadViewDisplayData) => {
|
|
||||||
try {
|
|
||||||
loading.value = true;
|
|
||||||
const url = await dmsFileService.FindSourceVideoPath(record.FileId);
|
|
||||||
const item = toVideoItem(record);
|
|
||||||
item.FormattedUrl = url;
|
|
||||||
openVideoPlay([item], 0, record.OrgFileName);
|
|
||||||
} catch (error) {
|
|
||||||
notification.info({
|
|
||||||
message: "系统提示:",
|
|
||||||
description: "视频加载失败",
|
|
||||||
placement: "topRight",
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onPreviewFile = async (usernameOrRecord: any, orderKey?: string) => {
|
const onPreviewFile = async (usernameOrRecord: any, orderKey?: string) => {
|
||||||
let username: string;
|
let username: string;
|
||||||
let fname: string;
|
let fname: string;
|
||||||
@@ -643,8 +627,53 @@ const onShareFamily = async (records: DmsFileUploadViewDisplayData[]) => {
|
|||||||
showModalShare.value = true;
|
showModalShare.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDownloadFile = async (fid: number, fname: string) => {
|
/** 按 URL 直接下载(视频走这条:Url 就是原码文件) */
|
||||||
GetOrgPictureFile(fid, fname, false);
|
const downloadByUrl = async (url: string, fileName: string) => {
|
||||||
|
if (!url) {
|
||||||
|
notification.info({
|
||||||
|
message: "系统提示:",
|
||||||
|
description: "该文件没有可下载的地址",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
spinTip.value = "下载中...";
|
||||||
|
// 与 VideoPlayer 一致:先取回字节流,再用 Blob + <a download> 落盘
|
||||||
|
const res = await request<ArrayBuffer>({
|
||||||
|
url,
|
||||||
|
method: "get",
|
||||||
|
responseType: "arraybuffer",
|
||||||
|
});
|
||||||
|
const blobUrl = window.URL.createObjectURL(new Blob([res]));
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = blobUrl;
|
||||||
|
link.setAttribute("download", fileName);
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
// 立刻 revoke 部分浏览器会来不及触发下载,延后释放
|
||||||
|
setTimeout(() => window.URL.revokeObjectURL(blobUrl), 1000);
|
||||||
|
} catch (error) {
|
||||||
|
notification.info({
|
||||||
|
message: "系统提示:",
|
||||||
|
description: "下载失败",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
spinTip.value = "加载中...";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 下载一条记录:视频下原码,其它文件走原来的分片合并下载 */
|
||||||
|
const onDownloadFile = async (record: DmsFileUploadViewDisplayData) => {
|
||||||
|
if (fileTypePolicy.isVideo(record.OrgFileName) || fileTypePolicy.isVideo(record.OrderKey)) {
|
||||||
|
await downloadByUrl(record.Url, record.OrgFileName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GetOrgPictureFile(record.FileId, record.OrgFileName, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const GetOrgPictureFile = async (
|
const GetOrgPictureFile = async (
|
||||||
@@ -1164,16 +1193,17 @@ const onOpenFile = (record: DmsFileUploadViewDisplayData) => {
|
|||||||
} else if (fileTypePolicy.isAudio(record.OrgFileName)) {
|
} else if (fileTypePolicy.isAudio(record.OrgFileName)) {
|
||||||
onPlayAudio(toUploadView(record));
|
onPlayAudio(toUploadView(record));
|
||||||
} else {
|
} else {
|
||||||
onDownloadFile(record.FileId, record.OrgFileName);
|
onDownloadFile(record);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDownloadSelected = (items: DmsFileUploadViewDisplayData[]) => {
|
const onDownloadSelected = async (items: DmsFileUploadViewDisplayData[]) => {
|
||||||
items.forEach((item) => {
|
// 逐个串行下载:并发触发多个 <a download> 浏览器会拦截后面的
|
||||||
|
for (const item of items) {
|
||||||
if (item.IsDirectoryIndc != "Y") {
|
if (item.IsDirectoryIndc != "Y") {
|
||||||
onDownloadFile(item.FileId, item.OrgFileName);
|
await onDownloadFile(item);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onShareSelected = (items: DmsFileUploadViewDisplayData[]) => {
|
const onShareSelected = (items: DmsFileUploadViewDisplayData[]) => {
|
||||||
@@ -1231,13 +1261,9 @@ const onViewPreviewSelected = (items: DmsFileUploadViewDisplayData[]) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** 播放视频:把当前目录的视频串成播放列表,从点中的那一个开始 */
|
/** 播放视频:把当前目录的视频串成播放列表,从点中的那一个开始 */
|
||||||
const onUrlVideoPlayItem = async (record: DmsFileUploadViewDisplayData) => {
|
const onUrlVideoPlayItem = (record: DmsFileUploadViewDisplayData) => {
|
||||||
if (!record.Url) {
|
|
||||||
await onSourceVideoPlay(record);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const list = buildVideoList(datas.value);
|
const list = buildVideoList(datas.value);
|
||||||
let index = list.findIndex((x) => x.FormattedUrl === record.Url);
|
let index = list.findIndex((x) => x.Url === record.Url);
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
list.unshift(toVideoItem(record));
|
list.unshift(toVideoItem(record));
|
||||||
index = 0;
|
index = 0;
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ import { Howl } from "howler";
|
|||||||
import { PublishVideoEntity } from "@/data/auto/publishVideoEntity";
|
import { PublishVideoEntity } from "@/data/auto/publishVideoEntity";
|
||||||
import publishVideoService from "@/service/master/publishVideoService";
|
import publishVideoService from "@/service/master/publishVideoService";
|
||||||
import { useWindowSize } from "@vueuse/core";
|
import { useWindowSize } from "@vueuse/core";
|
||||||
|
import { request } from "@/utils/request";
|
||||||
import fileTypePolicy from "@/policy/custom/fileTypePolicy";
|
import fileTypePolicy from "@/policy/custom/fileTypePolicy";
|
||||||
import { PublishAudioEntity } from "@/data/auto/publishAudioEntity";
|
import { PublishAudioEntity } from "@/data/auto/publishAudioEntity";
|
||||||
import { FamilyFolderFavoriteViewEntity } from "@/data/auto/familyFolderFavoriteViewEntity";
|
import { FamilyFolderFavoriteViewEntity } from "@/data/auto/familyFolderFavoriteViewEntity";
|
||||||
@@ -292,16 +293,18 @@ const onChangeFolder = async (fvalue: string) => {
|
|||||||
onSearchByFilePath(fvalue);
|
onSearchByFilePath(fvalue);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** DMS 记录 → 播放列表项(m3u8 的封面用同名 png 兜底) */
|
/** DMS 记录 → 播放列表项:Url=原码、FormattedM3u8=转码(10秒),封面用同名 png 兜底 */
|
||||||
const toVideoItem = (record: FamilyDmsFileViewDisplayData): VideoItem => {
|
const toVideoItem = (record: FamilyDmsFileViewDisplayData): VideoItem => {
|
||||||
const item = new PublishVideoDetailDisplayData();
|
const item = new PublishVideoDetailDisplayData();
|
||||||
item.Title = record.OrgFileName;
|
item.Title = record.OrgFileName;
|
||||||
item.FormattedUrl = record.Url;
|
item.Url = record.Url;
|
||||||
|
item.FormattedM3u8 = record.FormattedM3u8;
|
||||||
|
item.FormattedUrl = record.FormattedM3u8 || record.Url;
|
||||||
item.FormattedPic = (record.FormattedPic || "").replace(/\.m3u8$/, ".png");
|
item.FormattedPic = (record.FormattedPic || "").replace(/\.m3u8$/, ".png");
|
||||||
return item;
|
return item;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 当前目录里已转码(有 Url)的视频,作为弹窗播放器的播放列表 */
|
/** 当前目录里的视频,作为弹窗播放器的播放列表 */
|
||||||
const buildVideoList = (
|
const buildVideoList = (
|
||||||
records: FamilyDmsFileViewDisplayData[],
|
records: FamilyDmsFileViewDisplayData[],
|
||||||
): VideoItem[] =>
|
): VideoItem[] =>
|
||||||
@@ -310,7 +313,7 @@ const buildVideoList = (
|
|||||||
(x) =>
|
(x) =>
|
||||||
x.IsDirectoryIndc != "Y" &&
|
x.IsDirectoryIndc != "Y" &&
|
||||||
fileTypePolicy.isVideo(x.OrderKey || x.OrgFileName) &&
|
fileTypePolicy.isVideo(x.OrderKey || x.OrgFileName) &&
|
||||||
!!x.Url,
|
!!(x.FormattedM3u8 || x.Url),
|
||||||
)
|
)
|
||||||
.map(toVideoItem);
|
.map(toVideoItem);
|
||||||
|
|
||||||
@@ -326,25 +329,6 @@ const openVideoPlay = (list: VideoItem[], index: number, title: string) => {
|
|||||||
videoPlayModal.value = true;
|
videoPlayModal.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 未转码的视频没有 Url,直接取源片路径播放 */
|
|
||||||
const onSourceVideoPlay = async (record: FamilyDmsFileViewDisplayData) => {
|
|
||||||
try {
|
|
||||||
loading.value = true;
|
|
||||||
const url = await dmsFileService.FindSourceVideoPath(record.FileId);
|
|
||||||
const item = toVideoItem(record);
|
|
||||||
item.FormattedUrl = url;
|
|
||||||
openVideoPlay([item], 0, record.OrgFileName);
|
|
||||||
} catch (error) {
|
|
||||||
notification.info({
|
|
||||||
message: "系统提示:",
|
|
||||||
description: "视频加载失败",
|
|
||||||
placement: "topRight",
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onPreviewFile = async (usernameOrRecord: any, orderKey?: string) => {
|
const onPreviewFile = async (usernameOrRecord: any, orderKey?: string) => {
|
||||||
let username: string;
|
let username: string;
|
||||||
let fname: string;
|
let fname: string;
|
||||||
@@ -473,8 +457,55 @@ const onShareFamily = async (record: Record<string, any>) => {
|
|||||||
showModalShare.value = true;
|
showModalShare.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDownloadFile = async (fid: number, fname: string) => {
|
/** 按 URL 直接下载(视频走这条:Url 就是原码文件) */
|
||||||
GetOrgPictureFile(fid, fname, false);
|
const downloadByUrl = async (url: string, fileName: string) => {
|
||||||
|
if (!url) {
|
||||||
|
notification.info({
|
||||||
|
message: "系统提示:",
|
||||||
|
description: "该文件没有可下载的地址",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
spinTip.value = "下载中...";
|
||||||
|
// 与 VideoPlayer 一致:先取回字节流,再用 Blob + <a download> 落盘
|
||||||
|
const res = await request<ArrayBuffer>({
|
||||||
|
url,
|
||||||
|
method: "get",
|
||||||
|
responseType: "arraybuffer",
|
||||||
|
});
|
||||||
|
const blobUrl = window.URL.createObjectURL(new Blob([res]));
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = blobUrl;
|
||||||
|
link.setAttribute("download", fileName);
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
setTimeout(() => window.URL.revokeObjectURL(blobUrl), 1000);
|
||||||
|
} catch (error) {
|
||||||
|
notification.info({
|
||||||
|
message: "系统提示:",
|
||||||
|
description: "下载失败",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
spinTip.value = "加载中...";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 下载一条记录:视频下原码,其它文件走原来的分片合并下载 */
|
||||||
|
const onDownloadFile = async (record: FamilyDmsFileViewDisplayData) => {
|
||||||
|
if (
|
||||||
|
fileTypePolicy.isVideo(record.OrgFileName) ||
|
||||||
|
fileTypePolicy.isVideo(record.OrderKey)
|
||||||
|
) {
|
||||||
|
await downloadByUrl(record.Url, record.OrgFileName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GetOrgPictureFile(record.FileId, record.OrgFileName, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const GetOrgPictureFile = async (
|
const GetOrgPictureFile = async (
|
||||||
@@ -1076,16 +1107,17 @@ const onOpenFile = (record: FamilyDmsFileViewDisplayData) => {
|
|||||||
} else if (fileTypePolicy.isAudio(record.OrgFileName)) {
|
} else if (fileTypePolicy.isAudio(record.OrgFileName)) {
|
||||||
onPlayAudio(toUploadView(record));
|
onPlayAudio(toUploadView(record));
|
||||||
} else {
|
} else {
|
||||||
onDownloadFile(record.FileId, record.OrgFileName);
|
onDownloadFile(record);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDownloadSelected = (items: FamilyDmsFileViewDisplayData[]) => {
|
const onDownloadSelected = async (items: FamilyDmsFileViewDisplayData[]) => {
|
||||||
items.forEach((item) => {
|
// 逐个串行下载:并发触发多个 <a download> 浏览器会拦截后面的
|
||||||
|
for (const item of items) {
|
||||||
if (item.IsDirectoryIndc != "Y") {
|
if (item.IsDirectoryIndc != "Y") {
|
||||||
onDownloadFile(item.FileId, item.OrgFileName);
|
await onDownloadFile(item);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onShareSelected = (items: FamilyDmsFileViewDisplayData[]) => {
|
const onShareSelected = (items: FamilyDmsFileViewDisplayData[]) => {
|
||||||
@@ -1137,13 +1169,9 @@ const onViewPreviewSelected = (items: FamilyDmsFileViewDisplayData[]) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** 播放视频:把当前目录的视频串成播放列表,从点中的那一个开始 */
|
/** 播放视频:把当前目录的视频串成播放列表,从点中的那一个开始 */
|
||||||
const onUrlVideoPlayItem = async (record: FamilyDmsFileViewDisplayData) => {
|
const onUrlVideoPlayItem = (record: FamilyDmsFileViewDisplayData) => {
|
||||||
if (!record.Url) {
|
|
||||||
await onSourceVideoPlay(record);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const list = buildVideoList(datas.value);
|
const list = buildVideoList(datas.value);
|
||||||
let index = list.findIndex((x) => x.FormattedUrl === record.Url);
|
let index = list.findIndex((x) => x.Url === record.Url);
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
list.unshift(toVideoItem(record));
|
list.unshift(toVideoItem(record));
|
||||||
index = 0;
|
index = 0;
|
||||||
|
|||||||
@@ -36,6 +36,21 @@ onMounted(async () => {
|
|||||||
await loadVideos();
|
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 () => {
|
const loadVideos = async () => {
|
||||||
try {
|
try {
|
||||||
// 模式一:从网盘中心加载某个路径下的所有视频
|
// 模式一:从网盘中心加载某个路径下的所有视频
|
||||||
@@ -65,7 +80,9 @@ const loadVideos = async () => {
|
|||||||
for (const item of dr.Datas) {
|
for (const item of dr.Datas) {
|
||||||
const detail = new PublishVideoDetailDisplayData();
|
const detail = new PublishVideoDetailDisplayData();
|
||||||
detail.Title = item.OrgFileName;
|
detail.Title = item.OrgFileName;
|
||||||
detail.FormattedUrl = item.Url;
|
detail.Url = item.Url; // 原码
|
||||||
|
detail.FormattedM3u8 = item.FormattedM3u8; // 转码(10秒)
|
||||||
|
detail.FormattedUrl = item.FormattedM3u8 || item.Url;
|
||||||
// m3u8 通常配套同名 jpg/png 封面,这里把后缀替换成 .png 兜底
|
// m3u8 通常配套同名 jpg/png 封面,这里把后缀替换成 .png 兜底
|
||||||
detail.FormattedPic = (item.FormattedPic || "").replace(/\.m3u8$/, ".png");
|
detail.FormattedPic = (item.FormattedPic || "").replace(/\.m3u8$/, ".png");
|
||||||
videos.value.push(detail);
|
videos.value.push(detail);
|
||||||
@@ -75,12 +92,12 @@ const loadVideos = async () => {
|
|||||||
// 默认发布视频
|
// 默认发布视频
|
||||||
else if (!publishName.value || publishName.value.length === 0) {
|
else if (!publishName.value || publishName.value.length === 0) {
|
||||||
const data: PublishVideoData = await publishVideoService.FindPublishVideoDefault();
|
const data: PublishVideoData = await publishVideoService.FindPublishVideoDefault();
|
||||||
videos.value = data?.Details ?? [];
|
videos.value = normalizeDetails(data?.Details ?? []);
|
||||||
}
|
}
|
||||||
// 按 key 加载
|
// 按 key 加载
|
||||||
else {
|
else {
|
||||||
const data: PublishVideoData = await publishVideoService.FindByKey(publishName.value);
|
const data: PublishVideoData = await publishVideoService.FindByKey(publishName.value);
|
||||||
videos.value = data?.Details ?? [];
|
videos.value = normalizeDetails(data?.Details ?? []);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|||||||
Reference in new issue
Block a user