新增对视频的支持
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { onMounted, ref, computed, watch } from "vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -17,34 +17,80 @@ const props = withDefaults(
|
||||
const isLoading = ref(false);
|
||||
const error = ref(false);
|
||||
|
||||
const loadImage = async () => {
|
||||
const image = new Image();
|
||||
image.src = props.src;
|
||||
// 更加严谨的后缀判断
|
||||
const isVideo = computed(() => {
|
||||
if (!props.src) return false;
|
||||
const videoExtensions = ['mp4', 'webm', 'ogg', 'mov', 'm4v', 'avi'];
|
||||
try {
|
||||
const url = new URL(props.src, window.location.origin);
|
||||
const pathname = url.pathname;
|
||||
const extension = pathname.split('.').pop()?.toLowerCase();
|
||||
return extension ? videoExtensions.includes(extension) : false;
|
||||
} catch (e) {
|
||||
// 如果 URL 格式不规范,回退到简单字符串判断
|
||||
return videoExtensions.some(ext => props.src.toLowerCase().includes(`.${ext}`));
|
||||
}
|
||||
});
|
||||
|
||||
const loadMedia = () => {
|
||||
if (!props.src) return Promise.reject("Empty src");
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
image.onload = () => resolve(image);
|
||||
image.onerror = (err) => reject(err);
|
||||
if (isVideo.value) {
|
||||
const video = document.createElement('video');
|
||||
video.src = props.src;
|
||||
video.preload = 'metadata';
|
||||
video.onloadeddata = () => resolve(true);
|
||||
video.onerror = () => reject(new Error("Video load error"));
|
||||
} else {
|
||||
const image = new Image();
|
||||
image.src = props.src;
|
||||
image.onload = () => resolve(true);
|
||||
image.onerror = () => reject(new Error("Image load error"));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
const init = async () => {
|
||||
if (!props.src) return;
|
||||
isLoading.value = true;
|
||||
error.value = false;
|
||||
try {
|
||||
await loadImage();
|
||||
await loadMedia();
|
||||
} catch (e) {
|
||||
console.warn("Media preview load failed:", props.src);
|
||||
error.value = true;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(init);
|
||||
|
||||
// 如果 src 发生变化(比如在弹窗中切换上一张/下一张),重新加载
|
||||
watch(() => props.src, init);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="classes">
|
||||
<template v-if="isLoading">
|
||||
<slot name="loading"> loading... </slot>
|
||||
</template>
|
||||
<template v-else-if="error">
|
||||
<slot name="error"> error </slot>
|
||||
<slot name="error">
|
||||
<div class=":uno: flex items-center justify-center text-red-400 text-xs">加载失败</div>
|
||||
</slot>
|
||||
</template>
|
||||
|
||||
<video
|
||||
v-else-if="isVideo"
|
||||
class=":uno: size-full object-cover"
|
||||
:src="`${src}#t=0.1`"
|
||||
muted
|
||||
playsinline
|
||||
preload="metadata"
|
||||
></video>
|
||||
|
||||
<img v-else class=":uno: size-full object-cover" :src="src" :alt="alt" />
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
Reference in New Issue
Block a user