|
|
@@ -5,15 +5,30 @@
|
|
|
position: 'relative',
|
|
|
display: 'flex',
|
|
|
alignItems: 'center',
|
|
|
- justifyContent: 'center'
|
|
|
- }">
|
|
|
- <div class="media-asset-icon">
|
|
|
+ justifyContent: 'center',
|
|
|
+ overflow: 'hidden'
|
|
|
+ }" @click="$emit('click', $event)">
|
|
|
+ <!-- 预览窗口 -->
|
|
|
+ <div v-if="mediaItems.length > 0" class="preview-container">
|
|
|
+ <template v-for="(item, index) in mediaItems" :key="index">
|
|
|
+ <div v-show="currentMediaIndex === index" class="media-item" :class="{ 'active': currentMediaIndex === index }">
|
|
|
+ <img v-if="item.type === 1" :src="item.url" :alt="item.name" class="media-content" />
|
|
|
+ <video v-else-if="item.type === 2" :src="item.url" class="media-content" preload="metadata" muted
|
|
|
+ @loadeddata="onVideoLoaded"></video>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 无媒体时的默认状态 -->
|
|
|
+ <div v-else class="media-asset-icon">
|
|
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none">
|
|
|
<rect x="3" y="3" width="26" height="26" rx="6" fill="#f3f6fa" stroke="#409eff" stroke-width="2" />
|
|
|
<path d="M8 22l6-6 4 4 6-6" stroke="#409eff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
|
<circle cx="11" cy="12" r="2" fill="#409eff" />
|
|
|
</svg>
|
|
|
</div>
|
|
|
+
|
|
|
+ <!-- Resize -->
|
|
|
<template v-if="selected">
|
|
|
<div v-for="dir in ['nw', 'ne', 'sw', 'se']" :key="dir" class="resize-handle" :class="'resize-' + dir"
|
|
|
@mousedown.stop="onResizeMouseDown(dir, $event)" />
|
|
|
@@ -22,30 +37,90 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { computed, withDefaults, defineEmits } from 'vue';
|
|
|
-const emit = defineEmits(['resize']);
|
|
|
+import { computed, ref, watch, onMounted, onUnmounted } from 'vue';
|
|
|
+
|
|
|
+const emit = defineEmits(['resize', 'click']);
|
|
|
+
|
|
|
interface Props {
|
|
|
width?: number;
|
|
|
height?: number;
|
|
|
mediaId?: string;
|
|
|
selected?: boolean;
|
|
|
}
|
|
|
+
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
|
width: 120,
|
|
|
- height: 120
|
|
|
+ height: 120,
|
|
|
+ mediaId: ''
|
|
|
});
|
|
|
+
|
|
|
const selected = computed(() => !!props.selected);
|
|
|
+const mediaItems = ref<any[]>([]);
|
|
|
+const currentMediaIndex = ref(0);
|
|
|
+let slideInterval: number | null = null;
|
|
|
+const SLIDE_DURATION = 10000; // 暂且先写死 10s
|
|
|
+
|
|
|
+const parseMediaItems = () => {
|
|
|
+ try {
|
|
|
+ if (props.mediaId) {
|
|
|
+ const parsed = JSON.parse(props.mediaId);
|
|
|
+ mediaItems.value = Array.isArray(parsed) ? parsed : [parsed];
|
|
|
+ } else {
|
|
|
+ mediaItems.value = [];
|
|
|
+ }
|
|
|
+ currentMediaIndex.value = 0;
|
|
|
+ startSlideShow();
|
|
|
+ } catch (e) {
|
|
|
+ console.error('Error parsing media items:', e);
|
|
|
+ mediaItems.value = [];
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const startSlideShow = () => {
|
|
|
+ clearSlideShow();
|
|
|
+ if (mediaItems.value.length <= 1) return;
|
|
|
|
|
|
+ slideInterval = window.setInterval(() => {
|
|
|
+ currentMediaIndex.value = (currentMediaIndex.value + 1) % mediaItems.value.length;
|
|
|
+ }, SLIDE_DURATION);
|
|
|
+};
|
|
|
+
|
|
|
+const clearSlideShow = () => {
|
|
|
+ if (slideInterval !== null) {
|
|
|
+ clearInterval(slideInterval);
|
|
|
+ slideInterval = null;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const onVideoLoaded = (event: Event) => {
|
|
|
+ const video = event.target as HTMLVideoElement;
|
|
|
+ video.pause();
|
|
|
+ video.currentTime = 0;
|
|
|
+};
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => props.mediaId,
|
|
|
+ () => {
|
|
|
+ parseMediaItems();
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+);
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ clearSlideShow();
|
|
|
+});
|
|
|
let startX = 0,
|
|
|
startY = 0,
|
|
|
startW = 0,
|
|
|
startH = 0;
|
|
|
+
|
|
|
function onResizeMouseDown(dir: string, e: MouseEvent) {
|
|
|
e.stopPropagation();
|
|
|
startX = e.clientX;
|
|
|
startY = e.clientY;
|
|
|
startW = props.width;
|
|
|
startH = props.height;
|
|
|
+
|
|
|
function onMouseMove(ev: MouseEvent) {
|
|
|
let dx = ev.clientX - startX;
|
|
|
let dy = ev.clientY - startY;
|
|
|
@@ -57,10 +132,12 @@ function onResizeMouseDown(dir: string, e: MouseEvent) {
|
|
|
if (dir.includes('n')) newH = Math.max(40, startH - dy);
|
|
|
emit('resize', { width: newW, height: newH });
|
|
|
}
|
|
|
+
|
|
|
function onMouseUp() {
|
|
|
window.removeEventListener('mousemove', onMouseMove);
|
|
|
window.removeEventListener('mouseup', onMouseUp);
|
|
|
}
|
|
|
+
|
|
|
window.addEventListener('mousemove', onMouseMove);
|
|
|
window.addEventListener('mouseup', onMouseUp);
|
|
|
}
|
|
|
@@ -73,18 +150,50 @@ function onResizeMouseDown(dir: string, e: MouseEvent) {
|
|
|
border-radius: 10px;
|
|
|
transition: border-color 0.2s;
|
|
|
position: relative;
|
|
|
+ overflow: hidden;
|
|
|
}
|
|
|
|
|
|
.media-asset-board-wrapper.selected {
|
|
|
border-color: #409eff;
|
|
|
}
|
|
|
|
|
|
+.preview-container {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.media-item {
|
|
|
+ position: absolute;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ opacity: 0;
|
|
|
+ transition: opacity 0.5s ease-in-out;
|
|
|
+}
|
|
|
+
|
|
|
+.media-item.active {
|
|
|
+ opacity: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.media-content {
|
|
|
+ max-width: 100%;
|
|
|
+ max-height: 100%;
|
|
|
+ object-fit: contain;
|
|
|
+}
|
|
|
+
|
|
|
.media-asset-icon {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
+ background: #f5f7fa;
|
|
|
}
|
|
|
|
|
|
.resize-handle {
|