jichoi / lms_front star
이은진 이은진 2024-08-13
240813 이은진 AI맞춤형 학습코스
@4b5f407076e0db15b61e4004a40d3686d1efcc46
client/views/pages/AppRouter.js
--- client/views/pages/AppRouter.js
+++ client/views/pages/AppRouter.js
@@ -8,6 +8,7 @@
 import MyPlan2 from './main/MyPlan2.vue';
 import PhotoBook from './main/PhotoBook.vue';
 import Dashboard from './main/Dashboard.vue';
+import AIDashboard from './main/AIDashboard.vue';
 import Camera from "./main/Camera.vue"
 import PhotoDesign from "./main/PhotoDesign.vue"
 import PhotoEdit from "./main/PhotoEdit.vue"
@@ -110,6 +111,7 @@
         component: Main,
         children: [
             { path: '/Dashboard.page', name: 'Dashboard', component: Dashboard },
+            { path: '/AIDashboard.page', name: 'AIDashboard', component: AIDashboard },
             { path: '/MyPage.page', name: 'MyPage', component: MyPage },
             { path: '/MyPlan.page', name: 'MyPlan', component: MyPlan },
             { path: '/MyPlan2.page', name: 'MyPlan2', component: MyPlan2 },
 
client/views/pages/main/AIDashboard.vue (added)
+++ client/views/pages/main/AIDashboard.vue
@@ -0,0 +1,853 @@
+<template>
+    <div class="main">
+        <div class="race-wrap">
+            <div class="title-box">
+                <p class="title">AI 학습 보드</p>
+                <p class="subtitle">hi my name is dd!</p>
+            </div>
+            <div class="race-box">
+                <div class="rabbit-start"><img src="../../../resources/img/img09_s.png" alt=""></div>
+                <div class="rcon flex justify-end mb5">
+                    <div class="race-btn" @click="goToPage('Chapter1')">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                            data-num="1">
+                            <img :src="item.imgSrc1">
+                            <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </button>
+                        <p>지문1</p>
+                    </div>
+                    <div class="race-btn" @click="goToPage('Chapter2')">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                            data-num="2">
+                            <img :src="item.imgSrc1">
+                            <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </button>
+                        <p>단어장</p>
+                    </div>
+                </div>
+                <div class="lcon flex justify-between mb5">
+                    <div class="race-btn" @click="goToPage('Chapter7')">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                            data-num="7">
+                            <img :src="item.imgSrc1">
+                            <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </button>
+                        <p>문제1</p>
+                    </div>
+                    <div class="race-btn" @click="goToPage('Chapter6')">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                            data-num="6">
+                            <img :src="item.imgSrc1">
+                            <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </button>
+                        <p>단어장</p>
+                    </div>
+                    <div class="race-btn" @click="goToPage('Chapter5')">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                            data-num="5">
+                            <img :src="item.imgSrc1">
+                            <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </button>
+                        <p>지문2</p>
+                    </div>
+                    <div class="race-btn" @click="goToPage('Chapter4')">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                            data-num="4">
+                            <img :src="item.imgSrc1">
+                            <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </button>
+                        <p>문제2</p>
+                    </div>
+                    <div class="race-btn" @click="goToPage('Chapter3')">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                            data-num="3">
+                            <img :src="item.imgSrc1">
+                            <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </button>
+                        <p>문제1</p>
+                    </div>
+                </div>
+                <div class="rcon flex">
+                    <div class="race-btn" @click="goToPage('Chapter8')">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                            data-num="8">
+                            <img :src="item.imgSrc3">
+                            <img :src="item.imgSrc4" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </button>
+                        <p class="long">중간 평가</p>
+                    </div>
+                    <div class="race-btn" @click="goToPage('Chapter9')">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                            data-num="9">
+                            <img :src="item.imgSrc1">
+                            <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </button>
+                        <p>지문3</p>
+                    </div>
+                    <div class="race-btn" @click="goToPage('Chapter10')">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                            data-num="10">
+                            <img :src="item.imgSrc1">
+                            <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </button>
+                        <p>단어장</p>
+                    </div>
+
+                    <div class="race-btn">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                        data-num="11">
+                            <img :src="item.imgSrc3">
+                            <img :src="item.imgSrc4" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </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%;">
+                            <!-- 여기에 카메라 기능을 구현 -->
+                            <!-- <p>카메라 모듈이 여기에 위치합니다.</p> -->
+
+                            <div id="container" ref="container">
+                                <video v-if="!photoTaken" autoplay="true" ref="modalVideoElement" class="mirrored"
+                                    @canplay="onVideoLoaded"></video>
+                            </div>
+
+                        </div>
+                    </div>
+                    <div class="flex justify-center mt20">
+                        <button type="button" class="new-btn" v-if="!photoTaken" @click="capturePhoto"
+                            :disabled="!videoReady">
+                            사진 촬영
+                        </button>
+
+                    </div>
+                </div>
+            </article>
+
+            <!-- 사진 모달 -->
+            <article v-show="showPhotoModal" class="popup-wrap">
+                <div class="popup-box" style="top: 500px; left: auto">
+                    <div class="flex mb10 justify-between">
+                        <p class="popup-title">사진 꾸미기</p>
+                        <button type="button" class="popup-close-btn" @click="closePhotoModal">
+                            <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon>
+                        </button>
+                    </div>
+                    <div class="flex justify-between align-center" style="gap: 40px;">
+                        <div class="content" style="padding: 30px; min-width: 401px; min-height: 710px;">
+                            <div class="tool">
+                                <div class="flex justify-center mb20" style="gap: 20px;">
+                                    <button class="popTxt" style="width: 101px;" v-for="(item, index) in items_photo"
+                                        :key="index" @click="updateContent(index)"
+                                        :class="{ active: selectedIndex === index }">
+                                        <img :src="item.imgSrc1" style="display: block;">
+                                        <img :src="item.imgSrc2" v-if="selectedIndex === index" style="display: block;">
+                                    </button>
+                                </div>
+                            </div>
+
+                            <div class="stickers" v-show="!stickersVisible">
+                                <div class="toolbar">
+                                    <label for="brushSize" style="font-size: 9px;">펜 굵기</label>
+                                    <input type="color" v-model="color" />
+                                    <input type="range" id="brushSize" min="1" max="10" v-model="brushSize"
+                                        @input="updateBrushSize" style="width: 100px; margin-left: 5px;" />
+                                    <button class="new-btn" style="font-size: 9px;" @click="setTool('draw')">펜</button>
+                                    <button class="new-btn" style="font-size: 9px;" @click="setTool('eraser')">지우개</button>
+                                    <button class="new-btn" style="font-size: 9px;" @click="clearAll">전체 지우개</button>
+                                </div>
+                            </div>
+
+                            <div class="stickers" v-show="stickersVisible">
+                                <button><img src="../../../resources/img/img146_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img147_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img148_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img149_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img150_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img151_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img152_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img153_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img154_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img155_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img156_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img157_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img158_75s.png" alt=""></button>
+                            </div>
+                        </div>
+                        <div>
+                            <div class="content" style="height: 549px; 
+                                position: relative;
+                                width: 973px; 
+                                display: flex; 
+                                justify-content: center; 
+                                align-items: center;">
+                                <canvas ref="canvas" style="position: absolute;"></canvas>
+                            </div>
+                            <div class="btn-wrap flex justify-center mt40" style="gap: 40px;">
+                                <button class="login-btn" @click="openCameraModal">
+                                    <img src="../../../resources/img/btn07_s.png" alt="">
+                                    <p>재촬영</p>
+                                </button>
+
+                                <button class="login-btn" type="submit" @click="goToPage('PhotoEdit')">
+                                    <img src="../../../resources/img/btn07_s.png" alt="">
+                                    <p>완성</p>
+                                </button>
+                            </div>
+                        </div>
+                        <div class="content" style="padding: 30px; min-width: 401px; min-height: 710px;">
+                            <div class="mb20">
+                                <p class="popup-title" style="font-size: 32px">랜덤 단어</p>
+                            </div>
+                            <div class="flex-column" style="gap: 10px;">
+                                <button class="login-btn"><img src="../../../resources/img/img141_75s.png" alt="">
+                                    <p class="title">a</p>
+                                </button>
+                                <button class="login-btn"><img src="../../../resources/img/img152_75s_01.png" alt="">
+                                    <p class="title">a</p>
+                                </button>
+                                <button class="login-btn"><img src="../../../resources/img/img144_75s.png" alt="">
+                                    <p class="title" style="color: #fff;">a</p>
+                                </button>
+                                <button class="login-btn"><img src="../../../resources/img/img145_75s.png" alt="">
+                                    <p class="title mt20" style="color: #fff;">a</p>
+                                </button>
+                            </div>
+                        </div>
+                    </div>
+                    <!-- <div class="box">
+                        <div style="width: 100%;">
+                            <div id="container">
+                                <canvas ref="canvas"></canvas>
+                            </div>
+                        </div>
+                    </div> -->
+
+                </div>
+            </article>
+        </div>
+         <div class="complete-wrap  myphoto">
+            <button  class="login-btn mt10" type="submit" style="width: 100%;" @click="goToPage('MyPlan')"><img src="../../../resources/img/btn07_s.png" alt="" style="width: 100%; height: 100px;"> <p>학습 종료하기</p></button>
+            <h2 class="mb40 mt10">이 단원을 끝낸 친구들</h2>
+            <article class=" flex-column" style="gap: 5px;">
+                <div class="flex" style="gap: 5px;">
+                    <div @click="buttonSearch" class="photo"><img src="../../../resources/img/img143_75s.png" alt=""></div>
+                    <div @click="buttonSearch" class="photo"><img src="../../../resources/img/img143_75s.png" alt=""></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';
+
+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
+                },
+            ],
+            items_photo: [
+                {
+                    imgSrc1: 'client/resources/img/btn20_75s_normal.png',   //펜 선택되지 않음
+                    imgSrc2: 'client/resources/img/btn20_75s_click.png' //펜 선택됨
+                },
+                {
+                    imgSrc1: 'client/resources/img/btn21_75s_normal.png',   //스티커 선택되지 않음
+                    imgSrc2: 'client/resources/img/btn21_75s_click.png'     //스티커 선택됨
+                },
+            ],
+            mdiWindowClose: mdiWindowClose,
+            mdiHeart: mdiHeart,
+            showModal: false,
+            searchOpen: false,  // 사진 상세보기 모달창
+            searchOpen2: false, // 단원 마친 후, 사진 촬영 여부 선택 모달창
+            showCameraModal: false, // 카메라 모달창
+            showPhotoModal: false, // 사진꾸미기 모달창
+            photoTaken: false,
+            photo: null,    //캡쳐 사진
+            videoReady: false, // 비디오 준비 상태를 나타내는 플래그
+            stream: null,
+            canvasWidth: 0,
+            canvasHeight: 0,
+            selectedIndex: 0,   //툴 선택 여부 인덱스
+            stickersVisible: false, // 스티커 표시 여부 
+
+            //사진 꾸미기 관련 변수
+            drawHistory: [],    //도형 기록
+            tempLines: [],  //펜 기록
+            stickers: [],   //스티커 파일 기록
+            draggingStickerIndex: null, //스티커 드래그
+            activeStickerIndex: null,  // 현재 활성화된 스티커의 인덱스
+            nextLineId: 0,  //획 아이디
+            tool: 'draw',   //툴 결정
+            color: '#000000',   //펜 기본 색상
+            isDrawing: false,   //그리는 중인지 판단하는 변수
+            brushSize: 5,  // 초기 펜 굵기
+            startX: 0,
+            startY: 0,
+            canvasRect: {
+                topLeft: { x: 0, y: 0 },
+                bottomRight: { x: 0, y: 0 }
+            }
+
+        }
+    },
+    methods: {
+        toggleImage(index) {
+            this.items[index].isSecondImageVisible = !this.items[index].isSecondImageVisible;
+        },
+        toggleImageAndShowPopup(index, dataNum) {
+            this.toggleImage(index);
+            if (dataNum === '11') {  // 최종 평가 버튼 클릭 시
+                this.searchOpen2 = true;  // 모달창 열기
+            }
+        },
+        ShowPopup() {
+            this.searchOpen2 = true;  // 촬영 여부 묻는 모달창 열기
+        },
+        updateContent(index) {
+            this.selectedIndex = index;
+
+            // 선택된 버튼이 스티커 버튼(인덱스 1)인지 확인
+            if (index === 1) {
+                this.stickersVisible = true;  // 스티커 툴 보이기
+            } else {
+                this.stickersVisible = false; // 스티커 툴 숨기기
+            }
+        },
+        goToPage(page) {
+            this.$router.push({ name: page });
+        },
+        openCameraModal() {
+            this.closeModal();
+            this.closePhotoModal();
+
+            this.drawHistory = [];
+            this.stickers = [];
+            this.tempLines = [];
+            this.videoReady = false; // 비디오 준비 상태 초기화
+
+            this.showCameraModal = true;
+            navigator.mediaDevices.getUserMedia({ video: true })
+                .then(stream => {
+                    const modalVideo = this.$refs.modalVideoElement;
+                    modalVideo.srcObject = stream;
+                    this.stream = stream;
+                    modalVideo.addEventListener('loadedmetadata', this.adjustContainerSize);
+                })
+                .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;
+            this.showPhotoModal = false;
+
+            //스트림 종료
+            if (this.stream) {
+                let tracks = this.stream.getTracks();
+                tracks.forEach(track => track.stop());
+                this.stream = null;
+            }
+        },
+        closePhotoModal() { //사진꾸미기 팝업 닫기
+            this.showPhotoModal = false;
+            this.closeModal();
+        },
+        onVideoLoaded() {
+            this.videoReady = true;
+            this.adjustContainerSize();
+        },
+        adjustContainerSize() {
+            const video = this.$refs.modalVideoElement;
+            const container = this.$refs.container;
+            const body = this.$refs.body;
+            if (video && container) {
+                container.style.width = `${video.videoWidth}px`;
+                container.style.height = `${video.videoHeight}px`;
+                body.style.height = `${video.videoHeight}px`;
+            }
+        },
+
+        buttonSearch() {
+            this.searchOpen = true;
+        },
+        buttonSearch2() {
+            this.searchOpen2 = true;
+        },
+        closeBtn() {
+            this.searchOpen = false;
+        },
+        capturePhoto() {
+            // 사진 촬영 기능 구현
+            console.log("cam on");
+
+            if (!this.videoReady) return; // 비디오가 준비되지 않았으면 사진을 찍지 않음
+
+            const video = this.$refs.modalVideoElement;
+            const canvas = document.createElement('canvas');
+
+            canvas.width = video.videoWidth;
+            canvas.height = video.videoHeight;
+            this.canvasWidth = video.videoWidth;
+            this.canvasHeight = video.videoHeight;
+            const context = canvas.getContext('2d');
+            context.translate(canvas.width, 0);
+            context.scale(-1, 1);
+            context.drawImage(video, 0, 0, canvas.width, canvas.height);
+            this.photo = canvas.toDataURL('image/png');
+            this.photoTaken = true;
+            this.showPhotoModal = true;
+            console.log("PhotoModal open");
+            this.$nextTick(() => {
+                console.log("canvas setup");
+                // console.log("Photo data>>>>", this.photo);
+                this.setupCanvas();
+            });
+
+        },
+        setupCanvas() {
+            const canvas = this.$refs.canvas;
+            // const container = this.$refs.container;
+            if (!canvas) {
+                console.error("Canvas reference not found");
+                resolve();
+                return;
+            }
+            const context = canvas.getContext('2d');
+            if (!context) {
+                console.error("Canvas context not found");
+                return;
+            }
+            const image = new Image();
+            image.src = this.photo;
+            // console.log("Photo data>>>>", image.src);
+            image.onload = () => {
+                console.log("Image loaded successfully");
+                // this.canvasWidth = image.width;
+                // this.canvasHeight = image.height;
+                //이미지 크기가 캔버스와 안맞으면 이미지 불러오는데에 에러 남
+                // container.style.width = this.canvasWidth;
+                // container.style.height = this.canvasHeight;
+
+                canvas.width = this.canvasWidth;
+                canvas.height = this.canvasHeight;
+
+                const rect = canvas.getBoundingClientRect();
+                // 좌측 상단 좌표
+                const topLeft = {
+                    x: rect.left,
+                    y: rect.top
+                };
+
+                // 우측 하단 좌표
+                const bottomRight = {
+                    x: rect.right,
+                    y: rect.bottom
+                };
+
+                // 캔버스 크기 초기화
+                this.updateCanvasRect();
+
+                // 윈도우 리사이즈 이벤트 리스너 추가
+                window.addEventListener('resize', this.updateCanvasRect);
+
+                // 클릭 이벤트 핸들러 추가
+                this.$refs.canvas.addEventListener('click', this.handleCanvasClick);
+
+
+                // canvas.width = canvas.clientWidth;
+                // canvas.height = canvas.clientHeight;
+                // this.canvasWidth = canvas.clientWidth;
+                // this.canvasHeight = canvas.clientHeight;
+
+                context.clearRect(0, 0, canvas.width, canvas.height);  // 이전 이미지 있으면 초기화
+                context.drawImage(image, 0, 0, this.canvasWidth, this.canvasHeight);
+                this.addCanvasEventListeners(); //추가해야함
+            };
+            image.onerror = (error) => {
+                console.error("Error loading image: ", error);
+            };
+        },
+
+        addCanvasEventListeners() {
+            const canvas = this.$refs.canvas;
+            canvas.addEventListener('mousedown', this.onMouseDown);
+            canvas.addEventListener('mouseup', this.onMouseUp);
+            canvas.addEventListener('mousemove', this.onMouseMove);
+            canvas.addEventListener('click', this.onCanvasClick);
+        },
+        setTool(tool) {
+            this.tool = tool;
+        },
+        updateBrushSize() {
+            // 펜 굵기 변경 로직
+            if (this.tool === 'draw') {
+                this.setBrushSize(this.brushSize);
+            }
+        },
+        setBrushSize(size) {
+            this.brushSize = size;
+            const context = this.$refs.canvas.getContext('2d');
+            context.lineWidth = size;
+        },
+        // 캔버스 크기 갱신 함수
+        updateCanvasRect() {
+            const rect = this.$refs.canvas.getBoundingClientRect();
+            this.canvasRect = {
+                topLeft: { x: rect.left, y: rect.top },
+                bottomRight: { x: rect.right, y: rect.bottom }
+            };
+            console.log(">>>>>>>>>2222", rect.left);
+        },
+        getCanvasPosition(event) {
+
+            const rect = this.canvasRect;
+            console.log(">>>>>>>>>", this.canvasRect);
+            this.updateCanvasRect();
+
+            // 윈도우 리사이즈 이벤트 리스너 추가
+            window.addEventListener('resize', this.updateCanvasRect);
+
+            // 클릭 이벤트 핸들러 추가
+            this.$refs.canvas.addEventListener('click', this.handleCanvasClick);
+
+            // // 좌측 상단 좌표
+            // const topLeft = {
+            //     x: rect.left,
+            //     y: rect.top
+            // };
+
+            // // 우측 하단 좌표
+            // const bottomRight = {
+            //     x: rect.right,
+            //     y: rect.bottom
+            // };
+
+            // console.log(this.scrollLeft)
+
+            const x = event.clientX - rect.topLeft.x
+            const y = event.clientY - rect.topLeft.y
+
+
+            console.log(`클릭한 좌표: x=${event.clientX}, y=${event.clientY}`);
+            console.log(`계산베이스 좌표: x=${rect.topLeft.x}, y=${rect.topLeft.y}`);
+            console.log(`계산베이스 좌표: x=${rect.topLeft.x}, y=${rect.topLeft.y}`);
+            console.log(`계산된 좌표: x=${x}, y=${y}`);
+            return {
+                x, y
+            };
+        },
+        onMouseDown(event) {
+
+            // 캔버스 크기 초기화
+            this.updateCanvasRect();
+
+            // 윈도우 리사이즈 이벤트 리스너 추가
+            window.addEventListener('resize', this.updateCanvasRect);
+
+            // 클릭 이벤트 핸들러 추가
+            this.$refs.canvas.addEventListener('click', this.handleCanvasClick);
+
+            const { x, y } = this.getCanvasPosition(event);
+            this.startX = x;
+            this.startY = y;
+            const context = this.$refs.canvas.getContext('2d');
+            context.strokeStyle = this.color;
+            context.lineWidth = this.brushSize;  // 브러시 크기 설정
+            if (this.tool === 'draw') {
+                context.beginPath();
+                context.moveTo(this.startX, this.startY);
+                this.nextLineId++;
+            }
+            this.isDrawing = true;
+        },
+        onMouseUp(event) {
+            if (!this.isDrawing) return;
+            const { x, y } = this.getCanvasPosition(event);
+            const context = this.$refs.canvas.getContext('2d');
+            context.strokeStyle = this.color;
+            context.lineWidth = this.brushSize;  // 브러시 크기 설정
+            if (this.tool === 'rectangle') {
+                context.strokeRect(this.startX, this.startY, x - this.startX, y - this.startY);
+                this.drawHistory.push({ type: 'rectangle', startX: this.startX, startY: this.startY, endX: x, endY: y, color: this.color });
+            } else if (this.tool === 'circle') {
+                context.beginPath();
+                const radius = Math.sqrt(Math.pow((x - this.startX), 2) + Math.pow((y - this.startY), 2));
+                context.arc(this.startX, this.startY, radius, 0, 2 * Math.PI);
+                context.stroke();
+                this.drawHistory.push({ type: 'circle', startX: this.startX, startY: this.startY, radius, color: this.color });
+            } else if (this.tool === 'draw') {
+                context.lineTo(x, y);
+                context.stroke();
+                this.tempLines.push({ id: this.nextLineId, startX: this.startX, startY: this.startY, endX: x, endY: y, color: this.color });
+            }
+            this.isDrawing = false;
+        },
+        onMouseMove(event) {
+            if (!this.isDrawing || this.tool !== 'draw') return;
+            const { x, y } = this.getCanvasPosition(event);
+            const context = this.$refs.canvas.getContext('2d');
+            context.strokeStyle = this.color;
+            context.lineWidth = this.brushSize;  // 브러시 크기 설정
+            context.lineTo(x, y);
+            context.stroke();
+            this.tempLines.push({ id: this.nextLineId, startX: this.startX, startY: this.startY, endX: x, endY: y, color: this.color });
+            this.startX = x;
+            this.startY = y;
+        },
+        onCanvasClick(event) {
+            if (this.tool === 'eraser') {
+                const { x, y } = this.getCanvasPosition(event);
+                this.eraseDrawing(x, y);
+            }
+        },
+        eraseDrawing(x, y) {
+            const eraserRadius = 10;
+            this.drawHistory = this.drawHistory.filter(item => {
+                if (item.type === 'rectangle') {
+                    return !(x >= item.startX && x <= item.endX && y >= item.startY && y <= item.endY);
+                } else if (item.type === 'circle') {
+                    const distance = Math.sqrt(Math.pow((x - item.startX), 2) + Math.pow((y - item.startY), 2));
+                    return !(distance <= item.radius);
+                }
+            });
+            const linesToDelete = this.tempLines.filter(line => {
+                const distanceToLine = this.distanceToLineSegment(line.startX, line.startY, line.endX, line.endY, x, y);
+                return distanceToLine <= 10;
+            }).map(line => line.id);
+            this.tempLines = this.tempLines.filter(line => !linesToDelete.includes(line.id));
+            this.redraw();
+        },
+        distanceToLineSegment(x1, y1, x2, y2, px, py) {
+            const lengthSquared = Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2);
+            if (lengthSquared === 0) return Math.sqrt(Math.pow(px - x1, 2) + Math.pow(py - y1, 2));
+            const t = Math.max(0, Math.min(1, ((px - x1) * (x2 - x1) + (py - y1) * (y2 - y1)) / lengthSquared));
+            const projX = x1 + t * (x2 - x1);
+            const projY = y1 + t * (y2 - y1);
+            return Math.sqrt(Math.pow(px - projX, 2) + Math.pow(py - projY, 2));
+        },
+        clearAll() {
+            this.drawHistory = [];
+            this.stickers = [];
+            this.tempLines = [];
+            this.redraw();
+        },
+        redraw() {
+            const canvas = this.$refs.canvas;
+            const context = canvas.getContext('2d');
+            const image = new Image();
+            image.src = this.photo;
+            image.onload = () => {
+                context.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
+                context.drawImage(image, 0, 0, this.canvasWidth, this.canvasHeight);
+                this.drawHistory.forEach(item => {
+                    context.strokeStyle = item.color;
+                    if (item.type === 'draw') {
+                        context.beginPath();
+                        context.moveTo(item.startX, item.startY);
+                        context.lineTo(item.endX, item.endY);
+                        context.stroke();
+                    } else if (item.type === 'rectangle') {
+                        context.strokeRect(item.startX, item.startY, item.endX - item.startX, item.endY - item.startY);
+                    } else if (item.type === 'circle') {
+                        context.beginPath();
+                        context.arc(item.startX, item.startY, item.radius, 0, 2 * Math.PI);
+                        context.stroke();
+                    }
+                });
+                this.tempLines.forEach(line => {
+                    context.strokeStyle = line.color;
+                    context.beginPath();
+                    context.moveTo(line.startX, line.startY);
+                    context.lineTo(line.endX, line.endY);
+                    context.stroke();
+                });
+                this.stickers.forEach((sticker, index) => {
+                    context.drawImage(sticker.img, sticker.x, sticker.y, sticker.width, sticker.height);
+                });
+            };
+        },
+
+
+    },
+    components: {
+        SvgIcon,
+    },
+    mounted() {
+        console.log('main mounted');
+
+
+    },
+    computed() {
+
+    },
+    beforeDestroy() {
+        // 컴포넌트가 파괴되기 전에 리스너 제거
+        window.removeEventListener('resize', this.updateCanvasRect);
+        this.$refs.canvas.removeEventListener('click', this.handleCanvasClick);
+    }
+}
+</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: auto;
+    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;
+}
+</style>(No newline at end of file)
client/views/pages/main/MyPlan.vue
--- client/views/pages/main/MyPlan.vue
+++ client/views/pages/main/MyPlan.vue
@@ -46,7 +46,7 @@
             <div class="wrap mt30">
                 <p class="title1 mb20"> AI 맞춤형 학습 코스는 어떨까요?</p>
                 <div class="imgGroup flex justify-between">
-                    <div class="text-lf">
+                    <div class="text-lf" @click="goToPage('AIDashboard')">
                         <img src="../../../resources/img/img215_22s.png" alt="">
                         <p class="title2 mt10">추천 학습 단원</p>
                     </div>
Add a comment
List