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 === 'notRegistered'">지금은 학습 루트가 등록이 안됐어요! 학습 일정에서 학습루트를 등록해볼까요?</p>
<p class="title1" v-else-if="state === 'noProblem'">교재에 등록된 문제가 없습니다.</p>
<div v-else class="main">
<div class="race-wrap">
<div class="title-box">
<p class="title" style="margin-top: 7rem;">{{ 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=""
:style="{ display: rabbitPos[0] ? 'block' : 'none' }"></div>
<div class="rcon flex justify-end mb5">
<div class="race-btn" @click="goToPage('Chapter1')">
<div class="rabbit-running"><img src="../../../resources/img/img09_s.png" alt=""
:style="{ display: rabbitPos[1] ? 'block' : 'none' }"></div>
<button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
data-num="1">
<img :src="item.imgSrc1" :style="{ display: !rabbitCompl[1] ? 'block' : 'none' }">
<img :src="item.imgSrc2" :style="{ display: rabbitCompl[1] ? 'block' : 'none' }">
</button>
<p>{{ labeledItems[0] }}</p>
</div>
<div class="race-btn" @click="goToPage('Chapter2')">
<div class="rabbit-running"><img src="../../../resources/img/img09_s.png" alt=""
:style="{ display: rabbitPos[2] ? 'block' : 'none' }"></div>
<button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
data-num="2">
<img :src="item.imgSrc1" :style="{ display: !rabbitCompl[2] ? 'block' : 'none' }">
<img :src="item.imgSrc2" :style="{ display: rabbitCompl[2] ? 'block' : 'none' }">
</button>
<p>{{ labeledItems[1] }}</p>
</div>
</div>
<div class="lcon flex justify-between mb5">
<div class="race-btn" @click="goToPage('Chapter2_8')">
<div class="rabbit-running"><img src="../../../resources/img/img09_s.png" alt=""
:style="{ display: rabbitPos[7] ? 'block' : 'none' }"></div>
<button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
data-num="7">
<img :src="item.imgSrc1" :style="{ display: !rabbitCompl[7] ? 'block' : 'none' }">
<img :src="item.imgSrc2" :style="{ display: rabbitCompl[7] ? 'block' : 'none' }">
</button>
<p>{{ labeledItems[6] }}</p>
</div>
<div class="race-btn" @click="goToPage('Chapter6')">
<div class="rabbit-running"><img src="../../../resources/img/img09_s.png" alt=""
:style="{ display: rabbitPos[6] ? 'block' : 'none' }"></div>
<button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
data-num="6">
<img :src="item.imgSrc1" :style="{ display: !rabbitCompl[6] ? 'block' : 'none' }">
<img :src="item.imgSrc2" :style="{ display: rabbitCompl[6] ? 'block' : 'none' }">
</button>
<p>{{ labeledItems[5] }}</p>
</div>
<div class="race-btn" @click="goToPage('Chapter2_8')">
<div class="rabbit-running"><img src="../../../resources/img/img09_s.png" alt=""
:style="{ display: rabbitPos[5] ? 'block' : 'none' }"></div>
<button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
data-num="5">
<img :src="item.imgSrc1" :style="{ display: !rabbitCompl[5] ? 'block' : 'none' }">
<img :src="item.imgSrc2" :style="{ display: rabbitCompl[5] ? 'block' : 'none' }">
</button>
<p>{{ labeledItems[4] }}</p>
</div>
<div class="race-btn" @click="goToPage('Chapter2_7')">
<div class="rabbit-running"><img src="../../../resources/img/img09_s.png" alt=""
:style="{ display: rabbitPos[4] ? 'block' : 'none' }"></div>
<button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
data-num="4">
<img :src="item.imgSrc1" :style="{ display: !rabbitCompl[4] ? 'block' : 'none' }">
<img :src="item.imgSrc2" :style="{ display: rabbitCompl[4] ? 'block' : 'none' }">
</button>
<p>{{ labeledItems[3] }}</p>
</div>
<div class="race-btn" @click="goToPage('Chapter2_8')">
<div class="rabbit-running"><img src="../../../resources/img/img09_s.png" alt=""
:style="{ display: rabbitPos[3] ? 'block' : 'none' }"></div>
<button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
data-num="3">
<img :src="item.imgSrc1" :style="{ display: !rabbitCompl[3] ? 'block' : 'none' }">
<img :src="item.imgSrc2" :style="{ display: rabbitCompl[3] ? 'block' : 'none' }">
</button>
<p>{{ labeledItems[2] }}</p>
</div>
</div>
<div class="rcon flex">
<div class="race-btn" @click="goToPage('Chapter8')">
<div class="rabbit-running"><img src="../../../resources/img/img09_s.png" alt=""
:style="{ display: rabbitPos[8] ? 'block' : 'none' }"></div>
<button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
data-num="8">
<img :src="item.imgSrc3" :style="{ display: !rabbitCompl[8] ? 'block' : 'none' }">
<img :src="item.imgSrc4" :style="{ display: rabbitCompl[8] ? 'block' : 'none' }">
</button>
<p class="long">{{ labeledItems[7] }}</p>
</div>
<div class="race-btn" @click="goToPage('Chapter2_8')">
<div class="rabbit-running"><img src="../../../resources/img/img09_s.png" alt=""
:style="{ display: rabbitPos[9] ? 'block' : 'none' }"></div>
<button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
data-num="9">
<img :src="item.imgSrc1" :style="{ display: !rabbitCompl[9] ? 'block' : 'none' }">
<img :src="item.imgSrc2" :style="{ display: rabbitCompl[9] ? 'block' : 'none' }">
</button>
<p>{{ labeledItems[8] }}</p>
</div>
<div class="race-btn" @click="goToPage('Chapter10')">
<div class="rabbit-running"><img src="../../../resources/img/img09_s.png" alt=""
:style="{ display: rabbitPos[10] ? 'block' : 'none' }"></div>
<button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
data-num="10">
<img :src="item.imgSrc1" :style="{ display: !rabbitCompl[10] ? 'block' : 'none' }">
<img :src="item.imgSrc2" :style="{ display: rabbitCompl[10] ? 'block' : 'none' }">
</button>
<p>{{ labeledItems[9] }}</p>
</div>
<div class="race-btn" @click="goToPage('Chapter2_8')">
<div class="rabbit-running"><img src="../../../resources/img/img09_s.png" alt=""
:style="{ display: rabbitPos[11] ? 'block' : 'none' }"></div>
<button class="popTxt" v-for="(item, index) in items" :key="index"
@click="toggleImageAndShowPopup(index, '11')" data-num="11">
<img :src="item.imgSrc3" :style="{ display: !rabbitCompl[11] ? 'block' : 'none' }">
<img :src="item.imgSrc4" :style="{ display: rabbitCompl[11] ? 'block' : 'none' }">
</button>
<p class="long">{{ labeledItems[10] }}</p>
</div>
</div>
<div class="race-btn">
<div class="rabbit-running" style=" display: flex;"><img class="rabbit-end"
src="../../../resources/img/img138_72s.png" alt=""
:style="{ display: rabbitEnd ? 'block' : 'none' }">
<img class="fireworks-end" src="../../../resources/img/fireworks.gif" alt=""
:style="{ display: rabbitEnd ? 'block' : 'none' }">
</div>
</div>
<div class="complete-wrap smt50 myphoto">
<!-- 팝업 -->
<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" style="position: absolute;">
</video>
<img src="../../../resources/img/camera-rabbit.png" class="camera-rabbit"
style="position: absolute; ">
<canvas ref="canvas" style="pointer-events: none;"></canvas>
</div>
</div>
</div>
<div class="flex justify-center mt20">
<button class="new-btn" @click="takePhoto">사진 찍기</button>
</div>
</div>
</article>
</div>
<div class="complete-wrap myphoto">
<button class="login-btn mt10" type="submit" style="width: 100%;" @click="finishSchedule"><img
src="../../../resources/img/btn07_s.png" alt="" style="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; flex-wrap: wrap;">
<div v-for="(image, index) in images" :key="image.fileId" @click="buttonSearch(image)"
class="photo">
<img :src="image.url" :alt="image.fileNm" reloadable="true" />
</div>
</div>
</article>
<article class="popup-wrap" v-show="searchOpen">
<div class="popup-box ">
<div class="flex mb10 justify-between">
<p class="popup-title">알림</p>
<button type="button" class="popup-close-btn" @click="closeModal">
<svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon>
</button>
</div>
<div class="box">
<div style="width: 910px;"><img src="../../../resources/img/img140_747s.png" alt=""></div>
</div>
<div class="flex justify-between mt20">
<div class="text flex">
<p class="title2 date ml30">2024-08-06</p>
<span class=" title1 ml30">1단원을 마친 <em class="yellow">가나다</em>친구</span>
</div>
<div class="title2 flex align-center" style="gap: 10px;"><svg-icon type="mdi" :path="mdiHeart"
style="color: #FFBA08;"></svg-icon>
<p><em class="yellow">1</em></p>
</div>
</div>
</div>
</article>
</div>
</div>
</template>
<script>
import SvgIcon from '@jamescoyle/vue-icon';
import { mdiMagnify, mdiHeart, mdiWindowClose } from '@mdi/js';
import axios from 'axios';
export default {
data() {
return {
items: [
{
imgSrc1: 'client/resources/img/img11_1_s.png',
imgSrc2: 'client/resources/img/img12_1_s.png',
imgSrc3: 'client/resources/img/img11_2_s.png',
imgSrc4: 'client/resources/img/img12_2_s.png',
isSecondImageVisible: false
},
],
mdiMagnify: mdiMagnify,
mdiWindowClose: mdiWindowClose,
mdiHeart: mdiHeart,
showModal: false,
searchOpen: false, // 사진 상세보기 모달창
searchOpen2: false, // 단원 마친 후, 사진 촬영 여부 선택 모달창
showCameraModal: false, // 카메라 모달창
photoTaken: false,
photo: null, //캡쳐 사진
stream: null,
roadmapData: [],
labeledItems: [],
problemCounter: 0,
wordCounter: 0,
textCounter: 0,
evalCounter: 0,
book_id: null,
unit_id: null,
schedules: [],
nowSchedule: "",
state: '',
rabbitPos: [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
rabbitCompl: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
rabbitEnd: false,
images: [],
unit_id: "",
book_id: "",
}
},
methods: {
//은진
async findFile(file_mng_id) {
try {
const res = await axios({
url: "/file/find.json",
method: "post",
headers: {
"Content-Type": "application/json; charset=UTF-8",
},
data: {
file_mng_id: file_mng_id,
},
});
return res.data.list[0];
} catch (error) {
console.log("result - error : ", error);
return null;
}
},
fetchImage() {
axios({
url: "/photo/photoUnitList.json",
method: "post",
headers: {
"Content-Type": "application/json; charset=UTF-8",
},
data: {
unitId: "UNIT_000000000000001",
sclsId: "1"
}
})
.then(response => {
this.file_mng_id = response.data;
const findFilePromises = this.file_mng_id.map(id =>
this.findFile(id.file_mng_id)
);
return Promise.all(findFilePromises);
})
.then(fileResults => {
// Format file results to include image URL
this.images = fileResults.map(file => {
if (file) {
return {
url: "http://localhost:9080/" + `${file.fileRpath}`,
fileId: file.fileId,
fileNm: file.fileNm,
// Add any other properties you need here
};
}
return null;
}).filter(image => image !== null);
})
.catch(error => {
console.error("Error fetching images:", error);
});
},
goToPageImg(page) {
const canvas = document.querySelector('canvas');
const dataURL = canvas.toDataURL('image/png');
this.$router.push({ name: page, query: { image: encodeURIComponent(dataURL) } });
},
fetchRabbit() {
for (var i = 0; i < 12; i++) {
this.rabbitPos[i] = false
}
if (this.$route.query.value) {
console.log(`자리 : ${this.$route.query.value}`);
this.rabbitPos[parseInt(this.$route.query.value, 10) + 1] = true
for (var i = 0; i < this.$route.query.value; i++) {
this.rabbitCompl[i + 1] = true
}
if (this.$route.query.value === '11') {
this.rabbitEnd = true;
setTimeout(() => {
this.searchOpen2 = true;
}, 1000);
}
}
else
this.rabbitPos[0] = true
},
fetchSchedule(unit_id, book_id) {
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 = 'notRegistered';
} else {
const allFinished = this.schedules.every(schedule => schedule.finish === "T");
if (allFinished) {
this.state = 'finish';
} else {
this.nowSchedule = this.schedules.find(schedule => schedule.finish === null || schedule.finish === "F");
if (this.nowSchedule) {
this.fetchRoadmapData(unit_id, book_id);
this.state = 'studying';
} else {
this.state = 'notRegistered';
}
}
}
console.log(this.state);
})
.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 => {
const nextSchedule = this.schedules.find(schedule => schedule.schdl_id > this.nowSchedule.schdl_id);
alert("학습을 완료했습니다!");
if (nextSchedule) {
this.nowSchedule = nextSchedule;
this.fetchSchedule(nextSchedule.unit_id, nextSchedule.book_id);
this.fetchRoadmapData(nextSchedule.unit_id, nextSchedule.book_id);
this.$router.push({ name: 'Dashboard', query: { unit_id: nextSchedule.unit_id, book_id: nextSchedule.book_id } });
} else {
alert("모든 학습을 완료했습니다!");
this.state = 'finish';
}
})
.catch(error => {
console.error("Error updating schedule:", error);
});
},
fetchRoadmapData(unit_id, book_id) {
axios({
url: "/unitLearning/find.json",
method: "post",
headers: {
"Content-Type": "application/json; charset=UTF-8",
},
data: {
unit_id: unit_id,
book_id: book_id
}
})
.then(response => {
if (response.data.length != 0) {
this.roadmapData = response.data;
this.labeledItems = this.processedRoadmap;
} else {
this.state = "noProblem"
}
})
.catch(error => {
this.state = "noProblem"
console.error("Error fetching roadmap data:", error);
});
},
toggleImage(index) {
this.items[index].isSecondImageVisible = !this.items[index].isSecondImageVisible;
},
ShowPopup() {
this.searchOpen2 = true; // 촬영 여부 묻는 모달창 열기
},
goToPage(page) {
const { unit_id, book_id } = this.$route.query;
this.$router.push({ name: page, query: { unit_id, book_id } });
},
openCameraModal() {
this.closeModal();
this.showCameraModal = true;
navigator.mediaDevices.getUserMedia({ video: true })
.then(stream => {
const modalVideo = this.$refs.modalVideoElement;
modalVideo.srcObject = stream;
this.stream = stream;
})
.catch(error => {
console.log("error>>>>>>>>", error);
});
},
closeModal() { //웹캠 및 모든 팝업 닫기
// this.showModal = false;
this.searchOpen = false;
this.searchOpen2 = false;
this.showCameraModal = false;
this.photoTaken = false;
this.photo = null;
//스트림 종료
if (this.stream) {
let tracks = this.stream.getTracks();
tracks.forEach(track => track.stop());
this.stream = null;
}
},
onVideoLoaded() {
const video = this.$refs.modalVideoElement;
const canvas = this.$refs.canvas;
const ctx = canvas.getContext('2d');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
}, takePhoto() {
const video = this.$refs.modalVideoElement;
const canvas = this.$refs.canvas;
const ctx = canvas.getContext('2d');
ctx.save(); // 현재 상태 저장
// 캔버스 좌우 반전
ctx.scale(-1, 1);
ctx.drawImage(video, -canvas.width, 0, canvas.width, canvas.height);
ctx.restore();
const overlayImg = new Image();
overlayImg.src = 'client/resources/img/camera-rabbit.png';
overlayImg.onload = () => {
const overlayWidth = canvas.width * 0.4;
const overlayHeight = (overlayImg.height / overlayImg.width) * overlayWidth;
const overlayX = canvas.width - overlayWidth;
const overlayY = canvas.height - overlayHeight;
// 오버레이 이미지 그리기
ctx.drawImage(overlayImg, overlayX, overlayY, overlayWidth, overlayHeight);
// 사진 저장 함수 호출
const dataURL = canvas.toDataURL('image/png');
this.$router.push({ name: 'PhotoEdit', query: { image: encodeURIComponent(dataURL), unit_id: this.unit_id, book_id: this.book_id } });
};
},
buttonSearch() {
this.searchOpen = true;
},
buttonSearch2() {
this.searchOpen2 = true;
},
closeBtn() {
this.searchOpen = false;
},
},
components: {
SvgIcon,
},
mounted() {
this.fetchImage();
const { book_id, unit_id } = this.$route.query;
console.log('main mounted');
this.fetchSchedule(unit_id, book_id);
this.fetchRoadmapData(unit_id, book_id);
this.fetchRabbit();
this.onVideoLoaded();
this.unit_id = unit_id
this.book_id = book_id
if (this.$route.query.reCapture == 'true') {
this.openCameraModal()
}
},
computed: {
processedRoadmap() {
let problemCounter = 0;
let wordCounter = 0;
let textCounter = 0;
let evalCounter = 0;
return this.roadmapData.map(item => {
if (item.wd_book_id !== null) {
wordCounter++;
return `단어장${wordCounter}`;
} else if (item.text_id !== null) {
textCounter++;
return `지문${textCounter}`;
} else if (item.eval_id !== null) {
evalCounter++;
return evalCounter === 1 ? '중간평가' : '최종평가';
} else {
problemCounter++;
return `문제${problemCounter}`;
}
});
}
},
beforeDestroy() {
// 컴포넌트가 파괴되기 전에 리스너 제거
window.removeEventListener('resize', this.updateCanvasRect);
this.$refs.canvas.removeEventListener('click', this.handleCanvasClick);
}
}
</script>
<style scoped>
.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;
}
.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;
display: block;
position: absolute;
bottom: 0px;
left: -15px;
z-index: 10000;
}
.rabbit-running {
position: absolute;
bottom: 40px;
right: 110px;
z-index: 10000;
transform: scaleX(-1);
transition: all 0.5s ease-in-out;
}
.fireworks-end {
position: absolute;
bottom: 70px;
left: -40px;
width: 20rem;
}
.camera-rabbit {
position: absolute;
right: 0;
bottom: 0;
width: 40%;
}
</style>