File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
<template>
<p class="title1" v-if="state === 'finish'">오늘 공부를 다했어요! 너무 고생했어요!</p>
<p class="title1" v-else-if="state === ''">지금은 학습 루트가 등록이 안됐어요! 학습 일정에서 학습루트를 등록해볼까요?</p>
<!-- 1번 템플릿 -->
<div v-else class="main">
<div class="race-wrap">
<div class="title-box">
<p class="title">{{ roadmapData[0].unit_nm }}</p>
<p class="subtitle">{{ roadmapData[0].book_nm }}</p>
</div>
<!--
<div class="race-box">
<div class="rabbit-start"><img src="../../../resources/img/img09_s.png" alt=""></div>
<div class="rcon flex justify-between mb5">
<div class="race-btn" v-for="roadmap in roadmapData" :key="roadmap.learning_id" @click="goToPage('Chapter7')">
<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>{{getNonNullColumn(roadmap)}}</p>
</div>
</div>
</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>
<button class="login-btn mt50" type="submit" style="width: 100%;" @click="finishSchedule"><img
src="../../../resources/img/btn07_s.png" alt="" style="width: 100%; height: 100px;">
<p>학습 종료하기</p>
</button>
<div class="complete-wrap smt50 myphoto">
<h2 class="mb40">이 단원을 끝낸 친구들</h2>
<article class=" flex-column" style="gap: 5px;">
<div class="flex" style="gap: 5px;">
<div @click="buttonSearch2" 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="closeBtn2">
<svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon>
</button>
</div>
</div>
</article>
</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"></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>
</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
},
],
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' //스티커 선택됨
},
],
mdiMagnify: mdiMagnify,
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 }
},
roadViewTF: false,
roadmapData: [],
schedules: [],
nowSchedule: "",
state: ''
}
},
methods: {
//은진
fetchSchedule() {
axios({
url: "/schedule/selectSchedule.json",
method: "post",
headers: {
"Content-Type": "application/json; charset=UTF-8",
},
data: {
stdId: "2",
}
}).then(response => {
this.schedules = response.data;
if (this.schedules.length == 0) {
this.state = ''
} else {
this.nowSchedule = this.schedules.find(schedule => schedule.finish === null || schedule.finish === "");
if (this.nowSchedule) {
this.fetchRoadmapData(); // 진행 중인 스케줄이 있을 때 데이터를 가져오는 함수 호출
this.state = 'studying'
} else {
this.state = 'finish'
}
}
})
.catch(error => {
console.error("Error fetching roadmap data:", error);
});
},
finishSchedule() {
axios({
url: "/schedule/scheduleUpdate.json",
method: "post",
headers: {
"Content-Type": "application/json; charset=UTF-8",
},
data: {
scheduleId: this.nowSchedule.schdl_id,
finish: "T"
}
})
.then(response => {
window.location.reload();
})
.catch(error => {
console.error("Error fetching roadmap data:", error);
});
},
fetchRoadmapData() {
const vm = this;
axios({
url: "/unitLearning/find.json",
method: "post",
headers: {
"Content-Type": "application/json; charset=UTF-8",
},
data: {
unit_id: this.schedules[0].unit_id,
book_id: this.schedules[0].book_id
}
})
.then(response => {
this.roadmapData = response.data;
console.log(this.roadmapData)
this.roadViewTF = vm.roadmapData.length > 0;
})
.catch(error => {
console.error("Error fetching roadmap data:", error);
});
},
getNonNullColumn(item) {
if (item.prblm_id !== null) return '문제';
if (item.wd_book_id !== null) return '단어장';
if (item.text_id !== null) return '지문';
if (item.eval_id !== null) return '평가';
return '';
},
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);
});
};
},
getNonNullColumn(item) {
if (item.prblm_id !== null) return '문제';
if (item.wd_book_id !== null) return '단어장';
if (item.text_id !== null) return '지문';
if (item.eval_id !== null) return '평가';
return '';
},
showConfirm(type) {
let message = '';
if (type === 'cancel') {
message = '삭제하시겠습니까?';
} else if (type === 'reset') {
message = '초기화하시겠습니까?';
} else if (type === 'save') {
message = '등록하시겠습니까?';
}
if (confirm(message)) {
this.goBack();
}
},
},
components: {
SvgIcon,
},
mounted() {
console.log('main mounted');
this.fetchSchedule();
},
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>