<template> <p class="title1" v-show="isHidden">오늘 공부를 다했어요! 너무 고생했어요!</p> <div class="main" v-show="!isHidden"> <div class="race-wrap-ai"> <div class="race-box"> <div class="rabbit-start"> <img src="../../../resources/img/new_img/rabbit.png" alt="" /> </div> <!-- 1번째줄 --> <div class="rcon flex justify-end mb5" style="position: relative; top: 24px" > <!-- 1 --> <div class="race-btn" @click="goToPage('Chapter1')"> <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" data-num="1" > <div class="image-container"> <img src="../../../resources/img/new_img/ai_board/racebtn_1.png" /> <img src="../../../resources/img/new_img/icon/clear_img.png" :style="{ display: item.isSecondImageVisible ? 'block' : 'none', }" class="clear-img" /> </div> <p :class="!item.isSecondImageVisible ? 'before-clear' : 'clear'"> <img v-if="item.isSecondImageVisible" src="../../../resources/img/new_img/icon/complete_icon.png" alt="Complete Icon" class="complete-icon" /> 지문1 </p> </button> <!-- <p>지문1</p> --> </div> <!-- 2 --> <div class="race-btn" @click="goToPage('Chapter2')"> <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" data-num="2" > <div class="image-container"> <img src="../../../resources/img/new_img/ai_board/racebtn_2.png" /> <img src="../../../resources/img/new_img/icon/clear_img.png" :style="{ display: item.isSecondImageVisible ? 'block' : 'none', }" class="clear-img" style="position: absolute; left: 16px" /> </div> <p :class="!item.isSecondImageVisible ? 'before-clear' : 'clear'"> <img v-if="item.isSecondImageVisible" src="../../../resources/img/new_img/icon/complete_icon.png" alt="Complete Icon" class="complete-icon" /> 단어장 </p> </button> <!-- <p>단어장</p> --> </div> </div> <!-- 2번째 줄 --> <div class="lcon flex justify-between mb5"> <!-- 7 --> <div class="race-btn" @click="goToPage('Chapter7')"> <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" data-num="7" > <div class="image-container"> <img src="../../../resources/img/new_img/ai_board/racebtn_7.png" /> <img src="../../../resources/img/new_img/icon/clear_img.png" :style="{ display: item.isSecondImageVisible ? 'block' : 'none', }" class="clear-img" style="position: absolute; top: 85px" /> </div> <p :class="!item.isSecondImageVisible ? 'before-clear' : 'clear'"> <img v-if="item.isSecondImageVisible" src="../../../resources/img/new_img/icon/complete_icon.png" alt="Complete Icon" class="complete-icon" /> 문제1 </p> </button> <!-- <p>문제1</p> --> </div> <!-- 6 --> <div class="race-btn" @click="goToPage('Chapter6')"> <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" data-num="6" > <div class="image-container"> <img src="../../../resources/img/new_img/ai_board/racebtn_6.png" /> <img src="../../../resources/img/new_img/icon/clear_img.png" :style="{ display: item.isSecondImageVisible ? 'block' : 'none', }" class="clear-img" /> </div> <p :class="!item.isSecondImageVisible ? 'before-clear' : 'clear'"> <img v-if="item.isSecondImageVisible" src="../../../resources/img/new_img/icon/complete_icon.png" alt="Complete Icon" class="complete-icon" /> 단어장 </p> </button> <!-- <p>단어장</p> --> </div> <!-- 5 --> <div class="race-btn" @click="goToPage('Chapter5')" style="position: relative; left: -31px" > <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" data-num="5" style="position: absolute; top: -77px; left: 30px" > <div class="image-container"> <img src="../../../resources/img/new_img/ai_board/racebtn_5.png" /> <img src="../../../resources/img/new_img/icon/clear_img.png" :style="{ display: item.isSecondImageVisible ? 'block' : 'none', }" class="clear-img" /> </div> <p :class="!item.isSecondImageVisible ? 'before-clear' : 'clear'"> <img v-if="item.isSecondImageVisible" src="../../../resources/img/new_img/icon/complete_icon.png" alt="Complete Icon" class="complete-icon" /> 지문2 </p> </button> <!-- <p>지문2</p> --> </div> <!-- 4 --> <div class="race-btn" @click="goToPage('Chapter4')"> <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" data-num="4" > <div class="image-container"> <img src="../../../resources/img/new_img/ai_board/racebtn_4.png" /> <img src="../../../resources/img/new_img/icon/clear_img.png" :style="{ display: item.isSecondImageVisible ? 'block' : 'none', }" class="clear-img" /> </div> <p :class="!item.isSecondImageVisible ? 'before-clear' : 'clear'"> <img v-if="item.isSecondImageVisible" src="../../../resources/img/new_img/icon/complete_icon.png" alt="Complete Icon" class="complete-icon" /> 문제2 </p> </button> <!-- <p>문제2</p> --> </div> <!-- 3 --> <div class="race-btn" @click="goToPage('Chapter3')" style="right: 12px" > <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" data-num="3" style="top: -46px; position: relative" > <div class="image-container"> <img src="../../../resources/img/new_img/ai_board/racebtn_3.png" /> <img src="../../../resources/img/new_img/icon/clear_img.png" :style="{ display: item.isSecondImageVisible ? 'block' : 'none', }" class="clear-img" /> </div> <p :class="!item.isSecondImageVisible ? 'before-clear' : 'clear'"> <img v-if="item.isSecondImageVisible" src="../../../resources/img/new_img/icon/complete_icon.png" alt="Complete Icon" class="complete-icon" /> 문제1 </p> </button> <!-- <p>문제1</p> --> </div> </div> <!-- 3번째 줄 --> <div class="rcon flex" style="position: relative; top: -23px"> <!-- 8 --> <div class="race-btn" @click="goToPage('Chapter8')"> <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" data-num="8" > <div class="image-container"> <img src="../../../resources/img/new_img/ai_board/racebtn_8.png" /> <img src="../../../resources/img/new_img/icon/clear_img.png" :style="{ display: item.isSecondImageVisible ? 'block' : 'none', }" class="clear-img" /> </div> <p :class="!item.isSecondImageVisible ? 'before-clear' : 'clear'"> <img v-if="item.isSecondImageVisible" src="../../../resources/img/new_img/icon/complete_icon.png" alt="Complete Icon" class="complete-icon" /> 중간 평가 </p> </button> <!-- <p class="long">중간 평가</p> --> </div> <!-- 9 --> <div class="race-btn" @click="goToPage('Chapter9')"> <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" data-num="9" > <div class="image-container"> <img src="../../../resources/img/new_img/ai_board/racebtn_9.png" /> <img src="../../../resources/img/new_img/icon/clear_img.png" :style="{ display: item.isSecondImageVisible ? 'block' : 'none', }" class="clear-img" /> </div> <p :class="!item.isSecondImageVisible ? 'before-clear' : 'clear'"> <img v-if="item.isSecondImageVisible" src="../../../resources/img/new_img/icon/complete_icon.png" alt="Complete Icon" class="complete-icon" /> 지문3 </p> </button> <!-- <p>지문3</p> --> </div> <!-- 10 --> <div class="race-btn" @click="goToPage('Chapter10')"> <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" data-num="10" > <div class="image-container"> <img src="../../../resources/img/new_img/ai_board/racebtn_10.png" /> <img src="../../../resources/img/new_img/icon/clear_img.png" :style="{ display: item.isSecondImageVisible ? 'block' : 'none', }" class="clear-img" /> </div> <p :class="!item.isSecondImageVisible ? 'before-clear' : 'clear'"> <img v-if="item.isSecondImageVisible" src="../../../resources/img/new_img/icon/complete_icon.png" alt="Complete Icon" class="complete-icon" /> 단어장 </p> </button> <!-- <p>단어장</p> --> </div> <!-- 11 --> <div class="race-btn"> <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" data-num="11" > <div class="image-container"> <img src="../../../resources/img/new_img/ai_board/racebtn_11.png" /> <img src="../../../resources/img/new_img/icon/clear_img.png" :style="{ display: item.isSecondImageVisible ? 'block' : 'none', }" class="clear-img" /> </div> <p :class="!item.isSecondImageVisible ? 'before-clear' : 'clear'"> <img v-if="item.isSecondImageVisible" src="../../../resources/img/new_img/icon/complete_icon.png" alt="Complete Icon" class="complete-icon" /> 최종 평가 </p> </button> <!-- <p class="long">최종 평가</p> --> </div> </div> <div class="rabbit-end" @click="ShowPopup"> <!-- <img src="../../../resources/img/img138_72s.png" alt="" /> --> </div> </div> <!-- 팝업 --> <div v-show="searchOpen2" class="popup-wrap"> <div class="popup-box"> <button type="button" class="popup-close-btn" style="position: absolute; top: 10px; right: 10px" @click="closeModal" > <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn" ></svg-icon> </button> <div class="mb30 text-ct"> <p class="title1 mb20">1단원이 끝났습니다!</p> <p class="title1"> <em class="yellow">기념사진</em>을 촬영하러 가요 </p> </div> <div class="flex justify-center"> <button type="button" title="사진촬영" class="new-btn" @click="openCameraModal" > 사진 촬영 </button> </div> </div> </div> <!-- 카메라 모달 --> <article v-show="showCameraModal" class="popup-wrap"> <div class="popup-box" style="top: 500px; left: 500px"> <div class="flex mb10 justify-between"> <p class="popup-title">사진 촬영</p> <button type="button" class="popup-close-btn" @click="closeModal"> <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn" ></svg-icon> </button> </div> <div class="box"> <div style="width: 100%"> <div id="container" ref="container"> <video v-if="!photoTaken" autoplay="true" ref="modalVideoElement" class="mirrored" @canplay="onVideoLoaded" style="position: absolute; height: 100%" ></video> <img src="../../../resources/img/camera-rabbit.png" class="camera-rabbit" style="position: absolute" /> <canvas ref="canvas" style="pointer-events: none"></canvas> </div> </div> </div> <div class="flex justify-center mt20"> <button class="new-btn" @click="takePhoto">사진 찍기</button> </div> </div> </article> </div> <div class="complete-wrap myphoto"> <button class="login-btn mt10" type="submit" style="width: 100%" @click="finishSchedule" > <img src="../../../resources/img/btn07_s.png" alt="" style="height: 100px" /> <p>학습 종료하기</p> </button> <h2 class="mb40 mt10">이 단원을 끝낸 친구들</h2> <article class="flex-column"> <div class="flex-row"> <div class="flex" style="gap: 5px; flex-wrap: wrap"> <div v-for="(image, index) in images" :key="image.fileId" @click="buttonSearch(image)" class="photo" style="margin-bottom: 5px" > <img :src="image.url" :alt="image.fileNm" reloadable="true" style="height: 100%" /> </div> </div> </div> </article> <article class="popup-wrap" v-show="searchOpen"> <div class="popup-box"> <div class="flex mb10 justify-between"> <p class="popup-title">알림</p> <button type="button" class="popup-close-btn" @click="closeModal"> <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn" ></svg-icon> </button> </div> <div class="box"> <div style="width: 910px"> <img src="../../../resources/img/img140_747s.png" alt="" /> </div> </div> <div class="flex justify-between mt20"> <div class="text flex"> <p class="title2 date ml30">2024-08-06</p> <span class="title1 ml30" >1단원을 마친 <em class="yellow">가나다</em>친구</span > </div> <div class="title2 flex align-center" style="gap: 10px"> <svg-icon type="mdi" :path="mdiHeart" style="color: #ffba08" ></svg-icon> <p><em class="yellow">1</em></p> </div> </div> </div> </article> </div> </div> </template> <script> import SvgIcon from "@jamescoyle/vue-icon"; import { mdiMagnify, mdiHeart, mdiWindowClose } from "@mdi/js"; import axios from "axios"; //은진 export default { data() { return { items: [ { imgSrc1: "client/resources/img/img11_1_s.png", imgSrc2: "client/resources/img/img12_1_s.png", imgSrc3: "client/resources/img/img11_2_s.png", imgSrc4: "client/resources/img/img12_2_s.png", isSecondImageVisible: false, }, ], mdiWindowClose: mdiWindowClose, mdiHeart: mdiHeart, showModal: false, searchOpen: false, // 사진 상세보기 모달창 searchOpen2: false, // 단원 마친 후, 사진 촬영 여부 선택 모달창 showCameraModal: false, // 카메라 모달창 photoTaken: false, photo: null, //캡쳐 사진 stream: null, isHidden: false, images: [], }; }, methods: { async findFile(file_mng_id) { try { const res = await axios({ url: "/file/find.json", method: "post", headers: { "Content-Type": "application/json; charset=UTF-8", }, data: { file_mng_id: file_mng_id, }, }); return res.data.list[0]; } catch (error) { console.log("result - error : ", error); return null; } }, fetchImage() { axios({ url: "/photo/photoUnitList.json", method: "post", headers: { "Content-Type": "application/json; charset=UTF-8", }, data: { unitId: "UNIT_000000000000001", // 수정해야함 sclsId: "1", }, }) .then((response) => { this.file_mng_id = response.data; const findFilePromises = this.file_mng_id.map((id) => this.findFile(id.file_mng_id) ); return Promise.all(findFilePromises); }) .then((fileResults) => { // Format file results to include image URL this.images = fileResults .map((file) => { if (file) { return { url: "http://165.229.169.113:9080/" + `${file.fileRpath}`, fileId: file.fileId, fileNm: file.fileNm, // Add any other properties you need here }; } return null; }) .filter((image) => image !== null); }) .catch((error) => { console.error("Error fetching images:", error); }); }, goToPageImg(page) { const canvas = document.querySelector("canvas"); const dataURL = canvas.toDataURL("image/png"); this.$router.push({ name: page, query: { image: encodeURIComponent(dataURL) }, }); }, finishSchedule() { this.isHidden = true; }, toggleImage(index) { this.items[index].isSecondImageVisible = !this.items[index].isSecondImageVisible; }, ShowPopup() { this.searchOpen2 = true; }, goToPage(page) { this.$router.push({ name: page }); }, openCameraModal() { this.closeModal(); this.showCameraModal = true; navigator.mediaDevices .getUserMedia({ video: true }) .then((stream) => { const modalVideo = this.$refs.modalVideoElement; modalVideo.srcObject = stream; this.stream = stream; }) .catch((error) => { console.log("error>>>>>>>>", error); }); }, closeModal() { //웹캠 및 모든 팝업 닫기 // this.showModal = false; this.searchOpen = false; this.searchOpen2 = false; this.showCameraModal = false; this.photoTaken = false; this.photo = null; //스트림 종료 if (this.stream) { let tracks = this.stream.getTracks(); tracks.forEach((track) => track.stop()); this.stream = null; } }, //은진 onVideoLoaded() { const video = this.$refs.modalVideoElement; const canvas = this.$refs.canvas; const ctx = canvas.getContext("2d"); canvas.width = video.videoWidth; canvas.height = video.videoHeight; }, takePhoto() { const video = this.$refs.modalVideoElement; const canvas = this.$refs.canvas; const ctx = canvas.getContext("2d"); ctx.save(); // 현재 상태 저장 // 캔버스 좌우 반전 ctx.scale(-1, 1); ctx.drawImage(video, -canvas.width, 0, canvas.width, canvas.height); ctx.restore(); const overlayImg = new Image(); overlayImg.src = "client/resources/img/camera-rabbit.png"; overlayImg.onload = () => { const overlayWidth = canvas.width * 0.4; const overlayHeight = (overlayImg.height / overlayImg.width) * overlayWidth; const overlayX = canvas.width - overlayWidth; const overlayY = canvas.height - overlayHeight; // 오버레이 이미지 그리기 ctx.drawImage( overlayImg, overlayX, overlayY, overlayWidth, overlayHeight ); // 사진 저장 함수 호출 // this.savePhoto('PhotoEdit'); const dataURL = canvas.toDataURL("image/png"); this.$router.push({ name: "PhotoEdit", query: { image: encodeURIComponent(dataURL) }, }); }; }, buttonSearch() { this.searchOpen = true; }, buttonSearch2() { this.searchOpen2 = true; }, closeBtn() { this.searchOpen = false; }, }, components: { SvgIcon, }, mounted() { console.log("main mounted"); this.onVideoLoaded(); this.fetchImage(); if (this.$route.query.reCapture == "true") { this.openCameraModal(); } }, computed() {}, }; </script> <style> .body { width: 1435px; height: auto; margin: 0 auto; } #container { position: relative; margin: auto; border: 10px #333 solid; display: flex; justify-content: center; align-items: center; z-index: 100; } video { width: 100%; height: 100%; background-color: #666; } .mirrored { transform: scaleX(-1); } .new-btn:disabled { background-color: #fff3d7; cursor: not-allowed; } /* button { margin: auto; padding: 5px 10px; font-size: 13px; cursor: pointer; display: flex; justify-content: center; text-align: center; } */ .sticker { position: absolute; cursor: move; } .sticker-handle { width: 15px; height: 15px; background: rgba(255, 255, 255, 0.521); position: absolute; bottom: 0; right: 0; cursor: nwse-resize; font-size: 13px; font-weight: bolder; color: rgb(63, 63, 63); } .sticker-delete { position: absolute; top: 0; right: 0; background: rgba(255, 0, 0, 0.425); color: white; padding: 5px; cursor: pointer; } .toolbar { display: flex; justify-content: center; margin-top: 10px; } .toolbar button { margin: 5px; padding: 5px 10px; cursor: pointer; } .toolbar input { margin: 5px; } .rabbit-end { cursor: pointer; } .camera-rabbit { position: absolute; right: 0; bottom: 0; width: 40%; } .rabbit-start img { transform: scaleX(-1); } .image-container { position: relative; display: inline-block; } .clear-img { position: absolute; top: -27px; left: 0; width: 100px; height: 100px; z-index: 1; } .race-btn p.before-clear { /* width: auto; */ font-size: 20px; line-height: 1.6; color: #ffffff; background-color: rgba(48, 48, 48, 0.562); padding: 6px; border-radius: 8px; /* border: 1px solid rgba(255, 255, 255, 0.2); */ margin-bottom: 20px; /* box-shadow: none; */ position: static; } .race-btn p.clear { /* width: auto; */ font-size: 20px; line-height: 1.6; color: #333; background-color: #f9f9f9; padding: 6px; border-radius: 8px; /* box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); */ margin-bottom: 20px; position: static; display: flex; align-items: center; gap: 0.5rem; } .race-box .lcon { display: flex; align-items: center; } .complete-wrap img { width: auto; } </style>