Browse Source

支持媒资组件预览

Shinohara Haruna 5 months ago
parent
commit
ff7a9d0dac
1 changed files with 115 additions and 6 deletions
  1. 115 6
      smsb-plus-ui/src/views/smsb/itemProgram/component/MediaAssetBoard.vue

+ 115 - 6
smsb-plus-ui/src/views/smsb/itemProgram/component/MediaAssetBoard.vue

@@ -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 {