
--- client/views/pages/AppRouter.js
+++ client/views/pages/AppRouter.js
... | ... | @@ -8,6 +8,7 @@ |
8 | 8 |
import MyPlan2 from './main/MyPlan2.vue'; |
9 | 9 |
import PhotoBook from './main/PhotoBook.vue'; |
10 | 10 |
import Dashboard from './main/Dashboard.vue'; |
11 |
+import AIDashboard from './main/AIDashboard.vue'; |
|
11 | 12 |
import Camera from "./main/Camera.vue" |
12 | 13 |
import PhotoDesign from "./main/PhotoDesign.vue" |
13 | 14 |
import PhotoEdit from "./main/PhotoEdit.vue" |
... | ... | @@ -110,6 +111,7 @@ |
110 | 111 |
component: Main, |
111 | 112 |
children: [ |
112 | 113 |
{ path: '/Dashboard.page', name: 'Dashboard', component: Dashboard }, |
114 |
+ { path: '/AIDashboard.page', name: 'AIDashboard', component: AIDashboard }, |
|
113 | 115 |
{ path: '/MyPage.page', name: 'MyPage', component: MyPage }, |
114 | 116 |
{ path: '/MyPlan.page', name: 'MyPlan', component: MyPlan }, |
115 | 117 |
{ path: '/MyPlan2.page', name: 'MyPlan2', component: MyPlan2 }, |
+++ client/views/pages/main/AIDashboard.vue
... | ... | @@ -0,0 +1,853 @@ |
1 | +<template> | |
2 | + <div class="main"> | |
3 | + <div class="race-wrap"> | |
4 | + <div class="title-box"> | |
5 | + <p class="title">AI 학습 보드</p> | |
6 | + <p class="subtitle">hi my name is dd!</p> | |
7 | + </div> | |
8 | + <div class="race-box"> | |
9 | + <div class="rabbit-start"><img src="../../../resources/img/img09_s.png" alt=""></div> | |
10 | + <div class="rcon flex justify-end mb5"> | |
11 | + <div class="race-btn" @click="goToPage('Chapter1')"> | |
12 | + <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" | |
13 | + data-num="1"> | |
14 | + <img :src="item.imgSrc1"> | |
15 | + <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }"> | |
16 | + </button> | |
17 | + <p>지문1</p> | |
18 | + </div> | |
19 | + <div class="race-btn" @click="goToPage('Chapter2')"> | |
20 | + <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" | |
21 | + data-num="2"> | |
22 | + <img :src="item.imgSrc1"> | |
23 | + <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }"> | |
24 | + </button> | |
25 | + <p>단어장</p> | |
26 | + </div> | |
27 | + </div> | |
28 | + <div class="lcon flex justify-between mb5"> | |
29 | + <div class="race-btn" @click="goToPage('Chapter7')"> | |
30 | + <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" | |
31 | + data-num="7"> | |
32 | + <img :src="item.imgSrc1"> | |
33 | + <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }"> | |
34 | + </button> | |
35 | + <p>문제1</p> | |
36 | + </div> | |
37 | + <div class="race-btn" @click="goToPage('Chapter6')"> | |
38 | + <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" | |
39 | + data-num="6"> | |
40 | + <img :src="item.imgSrc1"> | |
41 | + <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }"> | |
42 | + </button> | |
43 | + <p>단어장</p> | |
44 | + </div> | |
45 | + <div class="race-btn" @click="goToPage('Chapter5')"> | |
46 | + <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" | |
47 | + data-num="5"> | |
48 | + <img :src="item.imgSrc1"> | |
49 | + <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }"> | |
50 | + </button> | |
51 | + <p>지문2</p> | |
52 | + </div> | |
53 | + <div class="race-btn" @click="goToPage('Chapter4')"> | |
54 | + <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" | |
55 | + data-num="4"> | |
56 | + <img :src="item.imgSrc1"> | |
57 | + <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }"> | |
58 | + </button> | |
59 | + <p>문제2</p> | |
60 | + </div> | |
61 | + <div class="race-btn" @click="goToPage('Chapter3')"> | |
62 | + <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" | |
63 | + data-num="3"> | |
64 | + <img :src="item.imgSrc1"> | |
65 | + <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }"> | |
66 | + </button> | |
67 | + <p>문제1</p> | |
68 | + </div> | |
69 | + </div> | |
70 | + <div class="rcon flex"> | |
71 | + <div class="race-btn" @click="goToPage('Chapter8')"> | |
72 | + <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" | |
73 | + data-num="8"> | |
74 | + <img :src="item.imgSrc3"> | |
75 | + <img :src="item.imgSrc4" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }"> | |
76 | + </button> | |
77 | + <p class="long">중간 평가</p> | |
78 | + </div> | |
79 | + <div class="race-btn" @click="goToPage('Chapter9')"> | |
80 | + <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" | |
81 | + data-num="9"> | |
82 | + <img :src="item.imgSrc1"> | |
83 | + <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }"> | |
84 | + </button> | |
85 | + <p>지문3</p> | |
86 | + </div> | |
87 | + <div class="race-btn" @click="goToPage('Chapter10')"> | |
88 | + <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" | |
89 | + data-num="10"> | |
90 | + <img :src="item.imgSrc1"> | |
91 | + <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }"> | |
92 | + </button> | |
93 | + <p>단어장</p> | |
94 | + </div> | |
95 | + | |
96 | + <div class="race-btn"> | |
97 | + <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" | |
98 | + data-num="11"> | |
99 | + <img :src="item.imgSrc3"> | |
100 | + <img :src="item.imgSrc4" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }"> | |
101 | + </button> | |
102 | + <p class="long">최종 평가</p> | |
103 | + </div> | |
104 | + </div> | |
105 | + <div class="rabbit-end" @click="ShowPopup"><img src="../../../resources/img/img138_72s.png" alt=""></div> | |
106 | + </div> | |
107 | + | |
108 | + <!-- 팝업 --> | |
109 | + <div v-show="searchOpen2" class="popup-wrap"> | |
110 | + <div class="popup-box"> | |
111 | + <button type="button" class="popup-close-btn" style="position:absolute; top:10px; right: 10px;" | |
112 | + @click="closeModal"> | |
113 | + <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon> | |
114 | + </button> | |
115 | + | |
116 | + <div class="mb30 text-ct"> | |
117 | + <p class="title1 mb20">1단원이 끝났습니다!</p> | |
118 | + <p class="title1"><em class="yellow">기념사진</em>을 촬영하러 가요</p> | |
119 | + </div> | |
120 | + <div class="flex justify-center"> | |
121 | + <button type="button" title="사진촬영" class="new-btn" @click="openCameraModal"> | |
122 | + 사진 촬영 | |
123 | + </button> | |
124 | + </div> | |
125 | + </div> | |
126 | + </div> | |
127 | + | |
128 | + <!-- 카메라 모달 --> | |
129 | + <article v-show="showCameraModal" class="popup-wrap"> | |
130 | + <div class="popup-box" style="top: 500px; left:500px"> | |
131 | + <div class="flex mb10 justify-between"> | |
132 | + <p class="popup-title">사진 촬영</p> | |
133 | + <button type="button" class="popup-close-btn" @click="closeModal"> | |
134 | + <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon> | |
135 | + </button> | |
136 | + </div> | |
137 | + <div class="box"> | |
138 | + <div style="width: 100%;"> | |
139 | + <!-- 여기에 카메라 기능을 구현 --> | |
140 | + <!-- <p>카메라 모듈이 여기에 위치합니다.</p> --> | |
141 | + | |
142 | + <div id="container" ref="container"> | |
143 | + <video v-if="!photoTaken" autoplay="true" ref="modalVideoElement" class="mirrored" | |
144 | + @canplay="onVideoLoaded"></video> | |
145 | + </div> | |
146 | + | |
147 | + </div> | |
148 | + </div> | |
149 | + <div class="flex justify-center mt20"> | |
150 | + <button type="button" class="new-btn" v-if="!photoTaken" @click="capturePhoto" | |
151 | + :disabled="!videoReady"> | |
152 | + 사진 촬영 | |
153 | + </button> | |
154 | + | |
155 | + </div> | |
156 | + </div> | |
157 | + </article> | |
158 | + | |
159 | + <!-- 사진 모달 --> | |
160 | + <article v-show="showPhotoModal" class="popup-wrap"> | |
161 | + <div class="popup-box" style="top: 500px; left: auto"> | |
162 | + <div class="flex mb10 justify-between"> | |
163 | + <p class="popup-title">사진 꾸미기</p> | |
164 | + <button type="button" class="popup-close-btn" @click="closePhotoModal"> | |
165 | + <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon> | |
166 | + </button> | |
167 | + </div> | |
168 | + <div class="flex justify-between align-center" style="gap: 40px;"> | |
169 | + <div class="content" style="padding: 30px; min-width: 401px; min-height: 710px;"> | |
170 | + <div class="tool"> | |
171 | + <div class="flex justify-center mb20" style="gap: 20px;"> | |
172 | + <button class="popTxt" style="width: 101px;" v-for="(item, index) in items_photo" | |
173 | + :key="index" @click="updateContent(index)" | |
174 | + :class="{ active: selectedIndex === index }"> | |
175 | + <img :src="item.imgSrc1" style="display: block;"> | |
176 | + <img :src="item.imgSrc2" v-if="selectedIndex === index" style="display: block;"> | |
177 | + </button> | |
178 | + </div> | |
179 | + </div> | |
180 | + | |
181 | + <div class="stickers" v-show="!stickersVisible"> | |
182 | + <div class="toolbar"> | |
183 | + <label for="brushSize" style="font-size: 9px;">펜 굵기</label> | |
184 | + <input type="color" v-model="color" /> | |
185 | + <input type="range" id="brushSize" min="1" max="10" v-model="brushSize" | |
186 | + @input="updateBrushSize" style="width: 100px; margin-left: 5px;" /> | |
187 | + <button class="new-btn" style="font-size: 9px;" @click="setTool('draw')">펜</button> | |
188 | + <button class="new-btn" style="font-size: 9px;" @click="setTool('eraser')">지우개</button> | |
189 | + <button class="new-btn" style="font-size: 9px;" @click="clearAll">전체 지우개</button> | |
190 | + </div> | |
191 | + </div> | |
192 | + | |
193 | + <div class="stickers" v-show="stickersVisible"> | |
194 | + <button><img src="../../../resources/img/img146_75s.png" alt=""></button> | |
195 | + <button><img src="../../../resources/img/img147_75s.png" alt=""></button> | |
196 | + <button><img src="../../../resources/img/img148_75s.png" alt=""></button> | |
197 | + <button><img src="../../../resources/img/img149_75s.png" alt=""></button> | |
198 | + <button><img src="../../../resources/img/img150_75s.png" alt=""></button> | |
199 | + <button><img src="../../../resources/img/img151_75s.png" alt=""></button> | |
200 | + <button><img src="../../../resources/img/img152_75s.png" alt=""></button> | |
201 | + <button><img src="../../../resources/img/img153_75s.png" alt=""></button> | |
202 | + <button><img src="../../../resources/img/img154_75s.png" alt=""></button> | |
203 | + <button><img src="../../../resources/img/img155_75s.png" alt=""></button> | |
204 | + <button><img src="../../../resources/img/img156_75s.png" alt=""></button> | |
205 | + <button><img src="../../../resources/img/img157_75s.png" alt=""></button> | |
206 | + <button><img src="../../../resources/img/img158_75s.png" alt=""></button> | |
207 | + </div> | |
208 | + </div> | |
209 | + <div> | |
210 | + <div class="content" style="height: 549px; | |
211 | + position: relative; | |
212 | + width: 973px; | |
213 | + display: flex; | |
214 | + justify-content: center; | |
215 | + align-items: center;"> | |
216 | + <canvas ref="canvas" style="position: absolute;"></canvas> | |
217 | + </div> | |
218 | + <div class="btn-wrap flex justify-center mt40" style="gap: 40px;"> | |
219 | + <button class="login-btn" @click="openCameraModal"> | |
220 | + <img src="../../../resources/img/btn07_s.png" alt=""> | |
221 | + <p>재촬영</p> | |
222 | + </button> | |
223 | + | |
224 | + <button class="login-btn" type="submit" @click="goToPage('PhotoEdit')"> | |
225 | + <img src="../../../resources/img/btn07_s.png" alt=""> | |
226 | + <p>완성</p> | |
227 | + </button> | |
228 | + </div> | |
229 | + </div> | |
230 | + <div class="content" style="padding: 30px; min-width: 401px; min-height: 710px;"> | |
231 | + <div class="mb20"> | |
232 | + <p class="popup-title" style="font-size: 32px">랜덤 단어</p> | |
233 | + </div> | |
234 | + <div class="flex-column" style="gap: 10px;"> | |
235 | + <button class="login-btn"><img src="../../../resources/img/img141_75s.png" alt=""> | |
236 | + <p class="title">a</p> | |
237 | + </button> | |
238 | + <button class="login-btn"><img src="../../../resources/img/img152_75s_01.png" alt=""> | |
239 | + <p class="title">a</p> | |
240 | + </button> | |
241 | + <button class="login-btn"><img src="../../../resources/img/img144_75s.png" alt=""> | |
242 | + <p class="title" style="color: #fff;">a</p> | |
243 | + </button> | |
244 | + <button class="login-btn"><img src="../../../resources/img/img145_75s.png" alt=""> | |
245 | + <p class="title mt20" style="color: #fff;">a</p> | |
246 | + </button> | |
247 | + </div> | |
248 | + </div> | |
249 | + </div> | |
250 | + <!-- <div class="box"> | |
251 | + <div style="width: 100%;"> | |
252 | + <div id="container"> | |
253 | + <canvas ref="canvas"></canvas> | |
254 | + </div> | |
255 | + </div> | |
256 | + </div> --> | |
257 | + | |
258 | + </div> | |
259 | + </article> | |
260 | + </div> | |
261 | + <div class="complete-wrap myphoto"> | |
262 | + <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> | |
263 | + <h2 class="mb40 mt10">이 단원을 끝낸 친구들</h2> | |
264 | + <article class=" flex-column" style="gap: 5px;"> | |
265 | + <div class="flex" style="gap: 5px;"> | |
266 | + <div @click="buttonSearch" class="photo"><img src="../../../resources/img/img143_75s.png" alt=""></div> | |
267 | + <div @click="buttonSearch" class="photo"><img src="../../../resources/img/img143_75s.png" alt=""></div> | |
268 | + </div> | |
269 | + </article> | |
270 | + <article class="popup-wrap" v-show="searchOpen"> | |
271 | + <div class="popup-box "> | |
272 | + <div class="flex mb10 justify-between"> | |
273 | + <p class="popup-title">알림</p> | |
274 | + <button type="button" class="popup-close-btn" @click="closeModal"> | |
275 | + <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon> | |
276 | + </button> | |
277 | + </div> | |
278 | + <div class="box"> | |
279 | + <div style="width: 910px;"><img src="../../../resources/img/img140_747s.png" alt=""></div> | |
280 | + </div> | |
281 | + <div class="flex justify-between mt20"> | |
282 | + <div class="text flex"> | |
283 | + <p class="title2 date ml30">2024-08-06</p> | |
284 | + <span class=" title1 ml30">1단원을 마친 <em class="yellow">가나다</em>친구</span> | |
285 | + </div> | |
286 | + <div class="title2 flex align-center" style="gap: 10px;"><svg-icon type="mdi" :path="mdiHeart" | |
287 | + style="color: #FFBA08;"></svg-icon> | |
288 | + <p><em class="yellow">1</em></p> | |
289 | + </div> | |
290 | + </div> | |
291 | + </div> | |
292 | + </article> | |
293 | + </div> | |
294 | + | |
295 | + </div> | |
296 | +</template> | |
297 | + | |
298 | +<script> | |
299 | +import SvgIcon from '@jamescoyle/vue-icon'; | |
300 | +import { mdiMagnify, mdiHeart, mdiWindowClose } from '@mdi/js'; | |
301 | + | |
302 | +export default { | |
303 | + data() { | |
304 | + return { | |
305 | + items: [ | |
306 | + { | |
307 | + imgSrc1: 'client/resources/img/img11_1_s.png', | |
308 | + imgSrc2: 'client/resources/img/img12_1_s.png', | |
309 | + imgSrc3: 'client/resources/img/img11_2_s.png', | |
310 | + imgSrc4: 'client/resources/img/img12_2_s.png', | |
311 | + isSecondImageVisible: false | |
312 | + }, | |
313 | + ], | |
314 | + items_photo: [ | |
315 | + { | |
316 | + imgSrc1: 'client/resources/img/btn20_75s_normal.png', //펜 선택되지 않음 | |
317 | + imgSrc2: 'client/resources/img/btn20_75s_click.png' //펜 선택됨 | |
318 | + }, | |
319 | + { | |
320 | + imgSrc1: 'client/resources/img/btn21_75s_normal.png', //스티커 선택되지 않음 | |
321 | + imgSrc2: 'client/resources/img/btn21_75s_click.png' //스티커 선택됨 | |
322 | + }, | |
323 | + ], | |
324 | + mdiWindowClose: mdiWindowClose, | |
325 | + mdiHeart: mdiHeart, | |
326 | + showModal: false, | |
327 | + searchOpen: false, // 사진 상세보기 모달창 | |
328 | + searchOpen2: false, // 단원 마친 후, 사진 촬영 여부 선택 모달창 | |
329 | + showCameraModal: false, // 카메라 모달창 | |
330 | + showPhotoModal: false, // 사진꾸미기 모달창 | |
331 | + photoTaken: false, | |
332 | + photo: null, //캡쳐 사진 | |
333 | + videoReady: false, // 비디오 준비 상태를 나타내는 플래그 | |
334 | + stream: null, | |
335 | + canvasWidth: 0, | |
336 | + canvasHeight: 0, | |
337 | + selectedIndex: 0, //툴 선택 여부 인덱스 | |
338 | + stickersVisible: false, // 스티커 표시 여부 | |
339 | + | |
340 | + //사진 꾸미기 관련 변수 | |
341 | + drawHistory: [], //도형 기록 | |
342 | + tempLines: [], //펜 기록 | |
343 | + stickers: [], //스티커 파일 기록 | |
344 | + draggingStickerIndex: null, //스티커 드래그 | |
345 | + activeStickerIndex: null, // 현재 활성화된 스티커의 인덱스 | |
346 | + nextLineId: 0, //획 아이디 | |
347 | + tool: 'draw', //툴 결정 | |
348 | + color: '#000000', //펜 기본 색상 | |
349 | + isDrawing: false, //그리는 중인지 판단하는 변수 | |
350 | + brushSize: 5, // 초기 펜 굵기 | |
351 | + startX: 0, | |
352 | + startY: 0, | |
353 | + canvasRect: { | |
354 | + topLeft: { x: 0, y: 0 }, | |
355 | + bottomRight: { x: 0, y: 0 } | |
356 | + } | |
357 | + | |
358 | + } | |
359 | + }, | |
360 | + methods: { | |
361 | + toggleImage(index) { | |
362 | + this.items[index].isSecondImageVisible = !this.items[index].isSecondImageVisible; | |
363 | + }, | |
364 | + toggleImageAndShowPopup(index, dataNum) { | |
365 | + this.toggleImage(index); | |
366 | + if (dataNum === '11') { // 최종 평가 버튼 클릭 시 | |
367 | + this.searchOpen2 = true; // 모달창 열기 | |
368 | + } | |
369 | + }, | |
370 | + ShowPopup() { | |
371 | + this.searchOpen2 = true; // 촬영 여부 묻는 모달창 열기 | |
372 | + }, | |
373 | + updateContent(index) { | |
374 | + this.selectedIndex = index; | |
375 | + | |
376 | + // 선택된 버튼이 스티커 버튼(인덱스 1)인지 확인 | |
377 | + if (index === 1) { | |
378 | + this.stickersVisible = true; // 스티커 툴 보이기 | |
379 | + } else { | |
380 | + this.stickersVisible = false; // 스티커 툴 숨기기 | |
381 | + } | |
382 | + }, | |
383 | + goToPage(page) { | |
384 | + this.$router.push({ name: page }); | |
385 | + }, | |
386 | + openCameraModal() { | |
387 | + this.closeModal(); | |
388 | + this.closePhotoModal(); | |
389 | + | |
390 | + this.drawHistory = []; | |
391 | + this.stickers = []; | |
392 | + this.tempLines = []; | |
393 | + this.videoReady = false; // 비디오 준비 상태 초기화 | |
394 | + | |
395 | + this.showCameraModal = true; | |
396 | + navigator.mediaDevices.getUserMedia({ video: true }) | |
397 | + .then(stream => { | |
398 | + const modalVideo = this.$refs.modalVideoElement; | |
399 | + modalVideo.srcObject = stream; | |
400 | + this.stream = stream; | |
401 | + modalVideo.addEventListener('loadedmetadata', this.adjustContainerSize); | |
402 | + }) | |
403 | + .catch(error => { | |
404 | + console.log("error>>>>>>>>", error); | |
405 | + }); | |
406 | + }, | |
407 | + closeModal() { //웹캠 및 모든 팝업 닫기 | |
408 | + // this.showModal = false; | |
409 | + this.searchOpen = false; | |
410 | + this.searchOpen2 = false; | |
411 | + this.showCameraModal = false; | |
412 | + this.photoTaken = false; | |
413 | + this.photo = null; | |
414 | + this.showPhotoModal = false; | |
415 | + | |
416 | + //스트림 종료 | |
417 | + if (this.stream) { | |
418 | + let tracks = this.stream.getTracks(); | |
419 | + tracks.forEach(track => track.stop()); | |
420 | + this.stream = null; | |
421 | + } | |
422 | + }, | |
423 | + closePhotoModal() { //사진꾸미기 팝업 닫기 | |
424 | + this.showPhotoModal = false; | |
425 | + this.closeModal(); | |
426 | + }, | |
427 | + onVideoLoaded() { | |
428 | + this.videoReady = true; | |
429 | + this.adjustContainerSize(); | |
430 | + }, | |
431 | + adjustContainerSize() { | |
432 | + const video = this.$refs.modalVideoElement; | |
433 | + const container = this.$refs.container; | |
434 | + const body = this.$refs.body; | |
435 | + if (video && container) { | |
436 | + container.style.width = `${video.videoWidth}px`; | |
437 | + container.style.height = `${video.videoHeight}px`; | |
438 | + body.style.height = `${video.videoHeight}px`; | |
439 | + } | |
440 | + }, | |
441 | + | |
442 | + buttonSearch() { | |
443 | + this.searchOpen = true; | |
444 | + }, | |
445 | + buttonSearch2() { | |
446 | + this.searchOpen2 = true; | |
447 | + }, | |
448 | + closeBtn() { | |
449 | + this.searchOpen = false; | |
450 | + }, | |
451 | + capturePhoto() { | |
452 | + // 사진 촬영 기능 구현 | |
453 | + console.log("cam on"); | |
454 | + | |
455 | + if (!this.videoReady) return; // 비디오가 준비되지 않았으면 사진을 찍지 않음 | |
456 | + | |
457 | + const video = this.$refs.modalVideoElement; | |
458 | + const canvas = document.createElement('canvas'); | |
459 | + | |
460 | + canvas.width = video.videoWidth; | |
461 | + canvas.height = video.videoHeight; | |
462 | + this.canvasWidth = video.videoWidth; | |
463 | + this.canvasHeight = video.videoHeight; | |
464 | + const context = canvas.getContext('2d'); | |
465 | + context.translate(canvas.width, 0); | |
466 | + context.scale(-1, 1); | |
467 | + context.drawImage(video, 0, 0, canvas.width, canvas.height); | |
468 | + this.photo = canvas.toDataURL('image/png'); | |
469 | + this.photoTaken = true; | |
470 | + this.showPhotoModal = true; | |
471 | + console.log("PhotoModal open"); | |
472 | + this.$nextTick(() => { | |
473 | + console.log("canvas setup"); | |
474 | + // console.log("Photo data>>>>", this.photo); | |
475 | + this.setupCanvas(); | |
476 | + }); | |
477 | + | |
478 | + }, | |
479 | + setupCanvas() { | |
480 | + const canvas = this.$refs.canvas; | |
481 | + // const container = this.$refs.container; | |
482 | + if (!canvas) { | |
483 | + console.error("Canvas reference not found"); | |
484 | + resolve(); | |
485 | + return; | |
486 | + } | |
487 | + const context = canvas.getContext('2d'); | |
488 | + if (!context) { | |
489 | + console.error("Canvas context not found"); | |
490 | + return; | |
491 | + } | |
492 | + const image = new Image(); | |
493 | + image.src = this.photo; | |
494 | + // console.log("Photo data>>>>", image.src); | |
495 | + image.onload = () => { | |
496 | + console.log("Image loaded successfully"); | |
497 | + // this.canvasWidth = image.width; | |
498 | + // this.canvasHeight = image.height; | |
499 | + //이미지 크기가 캔버스와 안맞으면 이미지 불러오는데에 에러 남 | |
500 | + // container.style.width = this.canvasWidth; | |
501 | + // container.style.height = this.canvasHeight; | |
502 | + | |
503 | + canvas.width = this.canvasWidth; | |
504 | + canvas.height = this.canvasHeight; | |
505 | + | |
506 | + const rect = canvas.getBoundingClientRect(); | |
507 | + // 좌측 상단 좌표 | |
508 | + const topLeft = { | |
509 | + x: rect.left, | |
510 | + y: rect.top | |
511 | + }; | |
512 | + | |
513 | + // 우측 하단 좌표 | |
514 | + const bottomRight = { | |
515 | + x: rect.right, | |
516 | + y: rect.bottom | |
517 | + }; | |
518 | + | |
519 | + // 캔버스 크기 초기화 | |
520 | + this.updateCanvasRect(); | |
521 | + | |
522 | + // 윈도우 리사이즈 이벤트 리스너 추가 | |
523 | + window.addEventListener('resize', this.updateCanvasRect); | |
524 | + | |
525 | + // 클릭 이벤트 핸들러 추가 | |
526 | + this.$refs.canvas.addEventListener('click', this.handleCanvasClick); | |
527 | + | |
528 | + | |
529 | + // canvas.width = canvas.clientWidth; | |
530 | + // canvas.height = canvas.clientHeight; | |
531 | + // this.canvasWidth = canvas.clientWidth; | |
532 | + // this.canvasHeight = canvas.clientHeight; | |
533 | + | |
534 | + context.clearRect(0, 0, canvas.width, canvas.height); // 이전 이미지 있으면 초기화 | |
535 | + context.drawImage(image, 0, 0, this.canvasWidth, this.canvasHeight); | |
536 | + this.addCanvasEventListeners(); //추가해야함 | |
537 | + }; | |
538 | + image.onerror = (error) => { | |
539 | + console.error("Error loading image: ", error); | |
540 | + }; | |
541 | + }, | |
542 | + | |
543 | + addCanvasEventListeners() { | |
544 | + const canvas = this.$refs.canvas; | |
545 | + canvas.addEventListener('mousedown', this.onMouseDown); | |
546 | + canvas.addEventListener('mouseup', this.onMouseUp); | |
547 | + canvas.addEventListener('mousemove', this.onMouseMove); | |
548 | + canvas.addEventListener('click', this.onCanvasClick); | |
549 | + }, | |
550 | + setTool(tool) { | |
551 | + this.tool = tool; | |
552 | + }, | |
553 | + updateBrushSize() { | |
554 | + // 펜 굵기 변경 로직 | |
555 | + if (this.tool === 'draw') { | |
556 | + this.setBrushSize(this.brushSize); | |
557 | + } | |
558 | + }, | |
559 | + setBrushSize(size) { | |
560 | + this.brushSize = size; | |
561 | + const context = this.$refs.canvas.getContext('2d'); | |
562 | + context.lineWidth = size; | |
563 | + }, | |
564 | + // 캔버스 크기 갱신 함수 | |
565 | + updateCanvasRect() { | |
566 | + const rect = this.$refs.canvas.getBoundingClientRect(); | |
567 | + this.canvasRect = { | |
568 | + topLeft: { x: rect.left, y: rect.top }, | |
569 | + bottomRight: { x: rect.right, y: rect.bottom } | |
570 | + }; | |
571 | + console.log(">>>>>>>>>2222", rect.left); | |
572 | + }, | |
573 | + getCanvasPosition(event) { | |
574 | + | |
575 | + const rect = this.canvasRect; | |
576 | + console.log(">>>>>>>>>", this.canvasRect); | |
577 | + this.updateCanvasRect(); | |
578 | + | |
579 | + // 윈도우 리사이즈 이벤트 리스너 추가 | |
580 | + window.addEventListener('resize', this.updateCanvasRect); | |
581 | + | |
582 | + // 클릭 이벤트 핸들러 추가 | |
583 | + this.$refs.canvas.addEventListener('click', this.handleCanvasClick); | |
584 | + | |
585 | + // // 좌측 상단 좌표 | |
586 | + // const topLeft = { | |
587 | + // x: rect.left, | |
588 | + // y: rect.top | |
589 | + // }; | |
590 | + | |
591 | + // // 우측 하단 좌표 | |
592 | + // const bottomRight = { | |
593 | + // x: rect.right, | |
594 | + // y: rect.bottom | |
595 | + // }; | |
596 | + | |
597 | + // console.log(this.scrollLeft) | |
598 | + | |
599 | + const x = event.clientX - rect.topLeft.x | |
600 | + const y = event.clientY - rect.topLeft.y | |
601 | + | |
602 | + | |
603 | + console.log(`클릭한 좌표: x=${event.clientX}, y=${event.clientY}`); | |
604 | + console.log(`계산베이스 좌표: x=${rect.topLeft.x}, y=${rect.topLeft.y}`); | |
605 | + console.log(`계산베이스 좌표: x=${rect.topLeft.x}, y=${rect.topLeft.y}`); | |
606 | + console.log(`계산된 좌표: x=${x}, y=${y}`); | |
607 | + return { | |
608 | + x, y | |
609 | + }; | |
610 | + }, | |
611 | + onMouseDown(event) { | |
612 | + | |
613 | + // 캔버스 크기 초기화 | |
614 | + this.updateCanvasRect(); | |
615 | + | |
616 | + // 윈도우 리사이즈 이벤트 리스너 추가 | |
617 | + window.addEventListener('resize', this.updateCanvasRect); | |
618 | + | |
619 | + // 클릭 이벤트 핸들러 추가 | |
620 | + this.$refs.canvas.addEventListener('click', this.handleCanvasClick); | |
621 | + | |
622 | + const { x, y } = this.getCanvasPosition(event); | |
623 | + this.startX = x; | |
624 | + this.startY = y; | |
625 | + const context = this.$refs.canvas.getContext('2d'); | |
626 | + context.strokeStyle = this.color; | |
627 | + context.lineWidth = this.brushSize; // 브러시 크기 설정 | |
628 | + if (this.tool === 'draw') { | |
629 | + context.beginPath(); | |
630 | + context.moveTo(this.startX, this.startY); | |
631 | + this.nextLineId++; | |
632 | + } | |
633 | + this.isDrawing = true; | |
634 | + }, | |
635 | + onMouseUp(event) { | |
636 | + if (!this.isDrawing) return; | |
637 | + const { x, y } = this.getCanvasPosition(event); | |
638 | + const context = this.$refs.canvas.getContext('2d'); | |
639 | + context.strokeStyle = this.color; | |
640 | + context.lineWidth = this.brushSize; // 브러시 크기 설정 | |
641 | + if (this.tool === 'rectangle') { | |
642 | + context.strokeRect(this.startX, this.startY, x - this.startX, y - this.startY); | |
643 | + this.drawHistory.push({ type: 'rectangle', startX: this.startX, startY: this.startY, endX: x, endY: y, color: this.color }); | |
644 | + } else if (this.tool === 'circle') { | |
645 | + context.beginPath(); | |
646 | + const radius = Math.sqrt(Math.pow((x - this.startX), 2) + Math.pow((y - this.startY), 2)); | |
647 | + context.arc(this.startX, this.startY, radius, 0, 2 * Math.PI); | |
648 | + context.stroke(); | |
649 | + this.drawHistory.push({ type: 'circle', startX: this.startX, startY: this.startY, radius, color: this.color }); | |
650 | + } else if (this.tool === 'draw') { | |
651 | + context.lineTo(x, y); | |
652 | + context.stroke(); | |
653 | + this.tempLines.push({ id: this.nextLineId, startX: this.startX, startY: this.startY, endX: x, endY: y, color: this.color }); | |
654 | + } | |
655 | + this.isDrawing = false; | |
656 | + }, | |
657 | + onMouseMove(event) { | |
658 | + if (!this.isDrawing || this.tool !== 'draw') return; | |
659 | + const { x, y } = this.getCanvasPosition(event); | |
660 | + const context = this.$refs.canvas.getContext('2d'); | |
661 | + context.strokeStyle = this.color; | |
662 | + context.lineWidth = this.brushSize; // 브러시 크기 설정 | |
663 | + context.lineTo(x, y); | |
664 | + context.stroke(); | |
665 | + this.tempLines.push({ id: this.nextLineId, startX: this.startX, startY: this.startY, endX: x, endY: y, color: this.color }); | |
666 | + this.startX = x; | |
667 | + this.startY = y; | |
668 | + }, | |
669 | + onCanvasClick(event) { | |
670 | + if (this.tool === 'eraser') { | |
671 | + const { x, y } = this.getCanvasPosition(event); | |
672 | + this.eraseDrawing(x, y); | |
673 | + } | |
674 | + }, | |
675 | + eraseDrawing(x, y) { | |
676 | + const eraserRadius = 10; | |
677 | + this.drawHistory = this.drawHistory.filter(item => { | |
678 | + if (item.type === 'rectangle') { | |
679 | + return !(x >= item.startX && x <= item.endX && y >= item.startY && y <= item.endY); | |
680 | + } else if (item.type === 'circle') { | |
681 | + const distance = Math.sqrt(Math.pow((x - item.startX), 2) + Math.pow((y - item.startY), 2)); | |
682 | + return !(distance <= item.radius); | |
683 | + } | |
684 | + }); | |
685 | + const linesToDelete = this.tempLines.filter(line => { | |
686 | + const distanceToLine = this.distanceToLineSegment(line.startX, line.startY, line.endX, line.endY, x, y); | |
687 | + return distanceToLine <= 10; | |
688 | + }).map(line => line.id); | |
689 | + this.tempLines = this.tempLines.filter(line => !linesToDelete.includes(line.id)); | |
690 | + this.redraw(); | |
691 | + }, | |
692 | + distanceToLineSegment(x1, y1, x2, y2, px, py) { | |
693 | + const lengthSquared = Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2); | |
694 | + if (lengthSquared === 0) return Math.sqrt(Math.pow(px - x1, 2) + Math.pow(py - y1, 2)); | |
695 | + const t = Math.max(0, Math.min(1, ((px - x1) * (x2 - x1) + (py - y1) * (y2 - y1)) / lengthSquared)); | |
696 | + const projX = x1 + t * (x2 - x1); | |
697 | + const projY = y1 + t * (y2 - y1); | |
698 | + return Math.sqrt(Math.pow(px - projX, 2) + Math.pow(py - projY, 2)); | |
699 | + }, | |
700 | + clearAll() { | |
701 | + this.drawHistory = []; | |
702 | + this.stickers = []; | |
703 | + this.tempLines = []; | |
704 | + this.redraw(); | |
705 | + }, | |
706 | + redraw() { | |
707 | + const canvas = this.$refs.canvas; | |
708 | + const context = canvas.getContext('2d'); | |
709 | + const image = new Image(); | |
710 | + image.src = this.photo; | |
711 | + image.onload = () => { | |
712 | + context.clearRect(0, 0, this.canvasWidth, this.canvasHeight); | |
713 | + context.drawImage(image, 0, 0, this.canvasWidth, this.canvasHeight); | |
714 | + this.drawHistory.forEach(item => { | |
715 | + context.strokeStyle = item.color; | |
716 | + if (item.type === 'draw') { | |
717 | + context.beginPath(); | |
718 | + context.moveTo(item.startX, item.startY); | |
719 | + context.lineTo(item.endX, item.endY); | |
720 | + context.stroke(); | |
721 | + } else if (item.type === 'rectangle') { | |
722 | + context.strokeRect(item.startX, item.startY, item.endX - item.startX, item.endY - item.startY); | |
723 | + } else if (item.type === 'circle') { | |
724 | + context.beginPath(); | |
725 | + context.arc(item.startX, item.startY, item.radius, 0, 2 * Math.PI); | |
726 | + context.stroke(); | |
727 | + } | |
728 | + }); | |
729 | + this.tempLines.forEach(line => { | |
730 | + context.strokeStyle = line.color; | |
731 | + context.beginPath(); | |
732 | + context.moveTo(line.startX, line.startY); | |
733 | + context.lineTo(line.endX, line.endY); | |
734 | + context.stroke(); | |
735 | + }); | |
736 | + this.stickers.forEach((sticker, index) => { | |
737 | + context.drawImage(sticker.img, sticker.x, sticker.y, sticker.width, sticker.height); | |
738 | + }); | |
739 | + }; | |
740 | + }, | |
741 | + | |
742 | + | |
743 | + }, | |
744 | + components: { | |
745 | + SvgIcon, | |
746 | + }, | |
747 | + mounted() { | |
748 | + console.log('main mounted'); | |
749 | + | |
750 | + | |
751 | + }, | |
752 | + computed() { | |
753 | + | |
754 | + }, | |
755 | + beforeDestroy() { | |
756 | + // 컴포넌트가 파괴되기 전에 리스너 제거 | |
757 | + window.removeEventListener('resize', this.updateCanvasRect); | |
758 | + this.$refs.canvas.removeEventListener('click', this.handleCanvasClick); | |
759 | + } | |
760 | +} | |
761 | +</script> | |
762 | + | |
763 | +<style> | |
764 | +.body { | |
765 | + width: 1435px; | |
766 | + height: auto; | |
767 | + margin: 0 auto; | |
768 | +} | |
769 | + | |
770 | +#container { | |
771 | + position: relative; | |
772 | + margin: auto; | |
773 | + border: 10px #333 solid; | |
774 | + display: flex; | |
775 | + justify-content: center; | |
776 | + align-items: center; | |
777 | + z-index: 100; | |
778 | +} | |
779 | + | |
780 | + | |
781 | +video { | |
782 | + width: 100%; | |
783 | + height: auto; | |
784 | + background-color: #666; | |
785 | +} | |
786 | + | |
787 | +.mirrored { | |
788 | + transform: scaleX(-1); | |
789 | +} | |
790 | + | |
791 | +.new-btn:disabled { | |
792 | + background-color: #FFF3D7; | |
793 | + cursor: not-allowed; | |
794 | +} | |
795 | + | |
796 | +/* button { | |
797 | + margin: auto; | |
798 | + padding: 5px 10px; | |
799 | + font-size: 13px; | |
800 | + cursor: pointer; | |
801 | + display: flex; | |
802 | + justify-content: center; | |
803 | + text-align: center; | |
804 | +} */ | |
805 | + | |
806 | +.sticker { | |
807 | + position: absolute; | |
808 | + cursor: move; | |
809 | +} | |
810 | + | |
811 | +.sticker-handle { | |
812 | + width: 15px; | |
813 | + height: 15px; | |
814 | + background: rgba(255, 255, 255, 0.521); | |
815 | + position: absolute; | |
816 | + bottom: 0; | |
817 | + right: 0; | |
818 | + cursor: nwse-resize; | |
819 | + font-size: 13px; | |
820 | + font-weight: bolder; | |
821 | + color: rgb(63, 63, 63); | |
822 | +} | |
823 | + | |
824 | +.sticker-delete { | |
825 | + position: absolute; | |
826 | + top: 0; | |
827 | + right: 0; | |
828 | + background: rgba(255, 0, 0, 0.425); | |
829 | + color: white; | |
830 | + padding: 5px; | |
831 | + cursor: pointer; | |
832 | +} | |
833 | + | |
834 | +.toolbar { | |
835 | + display: flex; | |
836 | + justify-content: center; | |
837 | + margin-top: 10px; | |
838 | +} | |
839 | + | |
840 | +.toolbar button { | |
841 | + margin: 5px; | |
842 | + padding: 5px 10px; | |
843 | + cursor: pointer; | |
844 | +} | |
845 | + | |
846 | +.toolbar input { | |
847 | + margin: 5px; | |
848 | +} | |
849 | + | |
850 | +.rabbit-end { | |
851 | + cursor: pointer; | |
852 | +} | |
853 | +</style>(No newline at end of file) |
--- client/views/pages/main/MyPlan.vue
+++ client/views/pages/main/MyPlan.vue
... | ... | @@ -46,7 +46,7 @@ |
46 | 46 |
<div class="wrap mt30"> |
47 | 47 |
<p class="title1 mb20"> AI 맞춤형 학습 코스는 어떨까요?</p> |
48 | 48 |
<div class="imgGroup flex justify-between"> |
49 |
- <div class="text-lf"> |
|
49 |
+ <div class="text-lf" @click="goToPage('AIDashboard')"> |
|
50 | 50 |
<img src="../../../resources/img/img215_22s.png" alt=""> |
51 | 51 |
<p class="title2 mt10">추천 학습 단원</p> |
52 | 52 |
</div> |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?