박정하 박정하 03-28
250328 박정하 영상 썸네일 추가
@77d450443501b539adb9447df6d61654f59239ca
 
client/views/component/VideoThumbnail.vue (added)
+++ client/views/component/VideoThumbnail.vue
@@ -0,0 +1,127 @@
+<template>
+  <div>
+    <img v-if="thumbnailUrl" :src="thumbnailUrl" alt="비디오 썸네일">
+    <div v-else-if="error" class="error">썸네일 생성 실패</div>
+    <div v-else class="loading">로딩 중...</div>
+  </div>
+</template>
+<script>
+export default {
+  data() {
+    return {
+      thumbnailUrl: null,
+      error: false
+    }
+  },
+  props: {
+    filePath: String
+  },
+  methods: {
+    createThumbnail(videoBlob) {
+      return new Promise((resolve, reject) => {
+        // 동영상 요소 생성
+        const video = document.createElement('video');
+        video.style.display = 'none';
+        document.body.appendChild(video);
+
+        // 캔버스 요소 생성
+        const canvas = document.createElement('canvas');
+        canvas.width = 320;
+        canvas.height = 180;
+
+        // 동영상 파일로부터 URL 생성
+        const videoUrl = URL.createObjectURL(videoBlob);
+        video.src = videoUrl;
+
+        // 로드 시간 제한 설정 (10초)
+        const timeout = setTimeout(() => {
+          URL.revokeObjectURL(videoUrl);
+          document.body.removeChild(video);
+          reject(new Error('비디오 로드 시간 초과'));
+        }, 10000);
+
+        // 오류 처리
+        video.onerror = (e) => {
+          clearTimeout(timeout);
+          URL.revokeObjectURL(videoUrl);
+          document.body.removeChild(video);
+          reject(new Error('비디오 로드 실패: ' + e.message));
+        };
+
+        // 메타데이터 로드 후 처리
+        video.onloadedmetadata = () => {
+          // 첫 프레임이나 특정 시점으로 이동
+          video.currentTime = 0.5;
+        };
+
+        // 특정 시간으로 이동 완료 후 썸네일 생성
+        video.onseeked = () => {
+          try {
+            clearTimeout(timeout);
+
+            const ctx = canvas.getContext('2d');
+            ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
+
+            // 썸네일 URL 생성
+            const thumbnailUrl = canvas.toDataURL('image/jpeg', 0.7);
+
+            // 리소스 정리
+            URL.revokeObjectURL(videoUrl);
+            document.body.removeChild(video);
+
+            resolve(thumbnailUrl);
+          } catch (error) {
+            URL.revokeObjectURL(videoUrl);
+            document.body.removeChild(video);
+            reject(error);
+          }
+        };
+      });
+    }
+  },
+  async mounted() {
+    try {
+      console.log('비디오 파일 경로:', this.filePath);
+
+      // 백엔드에서 파일 가져오기
+      const response = await fetch(this.filePath, {
+        // credentials: 'include', // 필요한 경우 쿠키 포함
+        // mode: 'cors', // CORS 모드 설정
+      });
+
+      if (!response.ok) {
+        throw new Error(`HTTP 오류! 상태: ${response.status}`);
+      }
+
+      const blob = await response.blob();
+      console.log('비디오 Blob 크기:', blob.size);
+
+      // 썸네일 생성
+      this.thumbnailUrl = await this.createThumbnail(blob);
+    } catch (error) {
+      console.error('썸네일 생성 실패:', error);
+      this.error = true;
+    }
+  }
+}
+</script>
+<style scoped>
+.loading,
+.error {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 100%;
+  min-height: 180px;
+  background-color: #f0f0f0;
+}
+
+.loading {
+  color: #666;
+}
+
+.error {
+  color: #e53935;
+}
+</style>(파일 끝에 줄바꿈 문자 없음)
client/views/component/listLayout/CardStyleComponent.vue
--- client/views/component/listLayout/CardStyleComponent.vue
+++ client/views/component/listLayout/CardStyleComponent.vue
@@ -3,8 +3,9 @@
     <li v-for="(item, idx) in list" :key="idx" class="mb-30" @click="fnMoveTo(item)">
       <div class="result-box">
         <div class="main-img">
-          <img v-if="item.hasOwnProperty('files') && item.files.length > 0" :src="item.files[0].filePath" :alt="item.sj + ' 첫 번째 이미지'">
+          <video-thumbnail v-if="name === 'V'" :file-path="item.files[0].filePath" :alt="item.sj + ' 영상 썸네일'" />
           <img v-else-if="name === 'M'" :src="getYouTubeThumbnail(item.link)" alt="영상 썸네일">
+          <img v-else-if="item.hasOwnProperty('files') && item.files.length > 0" :src="item.files[0].filePath" :alt="item.sj + ' 첫 번째 이미지'">
           <img v-else src="client/resources/images/img6.png" alt="Not found image">
         </div>
         <div class="text-box">
@@ -30,10 +31,15 @@
 </template>
 <script>
 import { getYouTubeThumbnail } from '../../../resources/js/youtubeUtils';
+import VideoThumbnail from '../VideoThumbnail.vue';
 
 export default {
   name: "CardStyleComponent",
 
+  components: {
+    VideoThumbnail,
+  },
+
   props: {
     name: {
       type: String,
client/views/pages/main/Main.vue
--- client/views/pages/main/Main.vue
+++ client/views/pages/main/Main.vue
@@ -43,7 +43,7 @@
     <div class="board w1500">
       <ul>
         <template v-for="(item, idx) of icons" :key="idx">
-          <li>
+          <li @click="fnMoveTo(item.routeName)">
             <div class="labeling"><img :src="item.url" :alt="item.name + '아이콘'"><span>{{ item.name }}</span></div>
             <div :class="{ 'count': true, 'all': item.id === 'TOTAL' }">{{ item.rowCo }}</div>
           </li>
@@ -70,7 +70,8 @@
             <div class="new-pic">
               <div v-for="(item, idx2) in tabContent.list" :key="idx2" class="box-wrap">
                 <div class="box" @click="fnMoveTo(tabContent.view, item.dcryId)">
-                  <img :src="item.hasOwnProperty('files') && item.files.length > 0 ? item.files[0].filePath : null" :alt="item.sj" class="tab-image" />
+                  <img v-if="tabContent.id === 'newPhoto'" :src="item.files[0].filePath" :alt="item.sj" class="tab-image" />
+                  <video-thumbnail v-if="tabContent.id === 'newVideo'" :file-path="item.files[0].filePath" :alt="item.sj" class="tab-image" />
                   <div class="info">
                     <p>{{ item.sj }}</p>
                     <span>{{ $dotFormatDate(item.rgsde) }}</span>
@@ -155,9 +156,8 @@
 
 // 메인화면 조회
 import { findAllSttusesProc } from "../../../resources/api/main";
-
-// 유투브 유틸
 import { getYouTubeThumbnail } from '../../../resources/js/youtubeUtils';
+import VideoThumbnail from '../../component/VideoThumbnail.vue';
 
 export default {
   components: {
@@ -165,6 +165,7 @@
     SwiperSlide,
     PauseOutlined,
     CaretRightOutlined,
+    VideoThumbnail,
   },
   setup() {
     return {
@@ -203,26 +204,31 @@
           id: "TOTAL",
           name: "전체",
           url: 'client/resources/images/icon/icon1.png',
+          routeName: 'TotalSearch',
         },
         {
           id: "dcry_photo",
           name: "사진",
           url: 'client/resources/images/icon/icon2.png',
+          routeName: 'PicHistorySearch',
         },
         {
           id: "dcry_vido",
           name: "영상",
           url: 'client/resources/images/icon/icon3.png',
+          routeName: 'VideoHistorySearch',
         },
         {
           id: "media_vido",
           name: "미디어",
           url: 'client/resources/images/icon/icon4.png',
+          routeName: 'MediaVideoSearch',
         },
         {
           id: "nes_dta",
           name: "보도자료",
           url: 'client/resources/images/icon/icon5.png',
+          routeName: 'NewsReleaseSearch',
         },
       ],
       slides: [
@@ -331,7 +337,7 @@
           this.$router.push({ name: page, query: { id: id } });
         }
       }
-    }
+    },
   },
 };
 </script>
(파일 끝에 줄바꿈 문자 없음)
Add a comment
List