![](/assets/images/project_default_logo.png)
![](/assets/images/default-avatar-128.png)
Merge branch 'master' of http://210.180.118.83/jichoi/lms_front
@bcd21ce0100ac5db86305272d57ec0180a9e7bbf
--- client/resources/css/common.css
+++ client/resources/css/common.css
... | ... | @@ -934,6 +934,10 @@ |
934 | 934 |
color: #999999; |
935 | 935 |
} |
936 | 936 |
|
937 |
+.black { |
|
938 |
+ color: #000000; |
|
939 |
+} |
|
940 |
+ |
|
937 | 941 |
.cursor { |
938 | 942 |
cursor: pointer; |
939 | 943 |
} |
--- client/resources/css/style.css
+++ client/resources/css/style.css
... | ... | @@ -219,7 +219,7 @@ |
219 | 219 |
} |
220 | 220 |
|
221 | 221 |
.menu a:first-child button.active { |
222 |
- background-image: url('../../resources/img/btn05_1_s.png'); |
|
222 |
+ background-image: url('../../resources/img/btn04_1_s.png'); |
|
223 | 223 |
width: 252px; |
224 | 224 |
/* 마이페이지 활성화 이미지 */ |
225 | 225 |
} |
... | ... | @@ -231,7 +231,7 @@ |
231 | 231 |
} |
232 | 232 |
|
233 | 233 |
.menu a:last-child button.active { |
234 |
- background-image: url('../../resources/img/btn04_1_s.png'); |
|
234 |
+ background-image: url('../../resources/img/btn05_1_s.png'); |
|
235 | 235 |
width: 259px; |
236 | 236 |
/* 학습일정계획 활성화 이미지 */ |
237 | 237 |
} |
... | ... | @@ -284,7 +284,7 @@ |
284 | 284 |
.mypage .photobook .title1{font-family: 'ONEMobilePOPOTF'; font-weight: 100;} |
285 | 285 |
.mypage .textbook .text {border-radius: 12px 12px 0px 0px; height: 68px; |
286 | 286 |
} |
287 |
-.mypage .textbook .box{height: 199px; text-align: center; padding: 10px;} |
|
287 |
+.mypage .textbook .box{height: fit-content; text-align: center; padding: 10px;} |
|
288 | 288 |
.mypage .book-red .text{background-color: #DB2B39;} |
289 | 289 |
/* .mypage .textbook:nth-child(2) .text */ |
290 | 290 |
.mypage .book-blue .text{background-color:#2D7DD2;} |
... | ... | @@ -674,7 +674,7 @@ |
674 | 674 |
.listenGroup img.bg { |
675 | 675 |
position: absolute; |
676 | 676 |
top: 0; |
677 |
- width: 1670px; |
|
677 |
+ width: 100%; |
|
678 | 678 |
height: 710px; |
679 | 679 |
} |
680 | 680 |
|
... | ... | @@ -690,14 +690,23 @@ |
690 | 690 |
top: 45%; |
691 | 691 |
left: 31%; |
692 | 692 |
transform: translate(-50%, -50%); |
693 |
- width: 900px; |
|
693 |
+ /* width: 900px; */ |
|
694 |
+} |
|
695 |
+.dropGroup .textbox { |
|
696 |
+ position: absolute; |
|
697 |
+ top: 45%; |
|
698 |
+ left: 53%; |
|
699 |
+ transform: translate(-50%, -50%); |
|
700 |
+ /* width: 900px; */ |
|
694 | 701 |
} |
695 | 702 |
|
696 | 703 |
.listenGroup .textbox p { |
697 | 704 |
font-size: 28px; |
698 | 705 |
line-height: 45px; |
706 |
+ width: 100%; |
|
707 |
+ |
|
699 | 708 |
} |
700 |
- |
|
709 |
+p.textbox {width: 100%;} |
|
701 | 710 |
.listenGroup p.title { |
702 | 711 |
font-size: 42px; |
703 | 712 |
text-shadow: 0px 0px 0 #fff, 0px 0px 0 #fff, 0px 0px 0 #fff, 0px 0px 0 #fff; |
... | ... | @@ -744,7 +753,7 @@ |
744 | 753 |
|
745 | 754 |
/* step2_2 */ |
746 | 755 |
.popTxt{position: relative; } |
747 |
-.popTxt img:last-child{position: absolute; top: 0; left: 0;} |
|
756 |
+ |
|
748 | 757 |
|
749 | 758 |
h4{font-size: 20px;} |
750 | 759 |
|
+++ client/resources/img/jumpingRabbit.gif
Binary file is not shown |
+++ client/views/component/TextToImage.vue
... | ... | @@ -0,0 +1,244 @@ |
1 | +<template> | |
2 | + <div class="flex justify-center align-center" style="margin-top : 30px"> | |
3 | + <div class="content title-box aibox"> | |
4 | + <p class="title mt25 title-bg">모르는 단어를 물어봐~!</p> | |
5 | + <div class="propmt-container"> | |
6 | + <div class="input-conatiner"> | |
7 | + <input v-model="word" @keyup.enter="startProgress(word)"/> | |
8 | + <button @click="startProgress(word)"> <p> 생성하기 </p> </button> | |
9 | + </div> | |
10 | + <div class="progress-container"> | |
11 | + <div class="progress-bar" :style="{ width: progressWidth + '%' }">{{ Math.floor(progressWidth) }}%</div> | |
12 | + | |
13 | + <!-- 로딩 이미지 --> | |
14 | + <div v-if="progressWidth < 100 && progressWidth > 1" class="loading-container"> | |
15 | + <img src="../../resources/img/jumpingRabbit.gif" alt="Loading" class="loading-gif"/> | |
16 | + <p> 잠깐만 기다려봐~ </p> | |
17 | + </div> | |
18 | + | |
19 | + </div> | |
20 | + <div v-if="visibleWord" class="result-container"> | |
21 | + <img :src="imageSrc"/> | |
22 | + <div class="word-container"> | |
23 | + <h2> {{ inputWord }} </h2> | |
24 | + </div> | |
25 | + </div> | |
26 | + </div> | |
27 | + </div> | |
28 | + </div> | |
29 | + | |
30 | +</template> | |
31 | + | |
32 | +<script> | |
33 | +import axios from 'axios'; | |
34 | + | |
35 | +export default { | |
36 | + data() { | |
37 | + return { | |
38 | + progressWidth: 0, | |
39 | + inputWord : "", | |
40 | + | |
41 | + // 하드 코딩 -> 수정 필요 : API | |
42 | + word : "", | |
43 | + imageSrc : "", | |
44 | + imageData: null, // 서버에서 받아온 이미지 데이터 저장 | |
45 | + | |
46 | + visibleWord : false, | |
47 | + }; | |
48 | + }, | |
49 | + mounted() { | |
50 | + | |
51 | + }, | |
52 | + methods: { | |
53 | + // 상태 진행 바(progress bar) | |
54 | + startProgress(word) { | |
55 | + if (this.progressWidth == 100) { | |
56 | + this.resetProgress(); | |
57 | + } | |
58 | + | |
59 | + if (this.progressWidth > 0) return; // 이미 진행 중이면 중복 실행 방지 | |
60 | + | |
61 | + this.setWord(word); | |
62 | + | |
63 | + const interval = 300; // 30ms | |
64 | + const totalDuration = 3000; // 총 시간 : 10초 | |
65 | + const steps = totalDuration / interval; | |
66 | + const increment = 100 / steps; | |
67 | + | |
68 | + const progressInterval = setInterval(() => { | |
69 | + this.progressWidth += increment; | |
70 | + if (this.progressWidth >= 100) { | |
71 | + this.progressWidth = 100; | |
72 | + clearInterval(progressInterval); | |
73 | + this.visibleWord = true; | |
74 | + | |
75 | + // 진행이 완료된 후에 이미지 데이터 렌더링 | |
76 | + if (this.imageData) { | |
77 | + this.imageSrc = this.imageUrl; | |
78 | + } | |
79 | + } | |
80 | + }, interval); | |
81 | + | |
82 | + this.getAiImage(); | |
83 | + }, | |
84 | + setWord(word) { | |
85 | + if (this.progressWidth > 0 && this.progressWidth < 100) return; // progressWidth가 0 또는 100이 아니면 실행 중지 | |
86 | + this.inputWord = word; | |
87 | + this.visibleWord = false; | |
88 | + }, | |
89 | + | |
90 | + resetProgress() { | |
91 | + this.progressWidth = 0; | |
92 | + this.visibleWord = false; | |
93 | + this.imageSrc = ""; // 이미지 URL 초기화 | |
94 | + this.imageData = null; // 이미지 데이터 초기화 | |
95 | + }, | |
96 | + | |
97 | + // 이미지 API | |
98 | + async getAiImage(){ | |
99 | + const url = "http://takensoftai.iptime.org:20200/generate_json"; | |
100 | + // console.log(`word : ${this.word}`); | |
101 | + try { | |
102 | + const response = await axios({ | |
103 | + url: url, | |
104 | + method: "post", | |
105 | + headers: { | |
106 | + "Content-Type": "application/json; charset=UTF-8", | |
107 | + }, | |
108 | + responseType: 'arraybuffer', | |
109 | + data: { | |
110 | + text: this.word | |
111 | + }, | |
112 | + }); | |
113 | + | |
114 | + // console.log(`응답 : ${response}`); | |
115 | + | |
116 | + // 바이너리 데이터 -> Blob으로 변환 | |
117 | + const blob = new Blob([response.data], { type: 'image/png' }); | |
118 | + | |
119 | + // Blob에서 객체 URL 생성 | |
120 | + const imageUrl = URL.createObjectURL(blob); | |
121 | + | |
122 | + // 이미지 URL 설정 | |
123 | + this.imageSrc = imageUrl; | |
124 | + } catch (err) { | |
125 | + console.log(err); | |
126 | + } | |
127 | + } | |
128 | + } | |
129 | +} | |
130 | +</script> | |
131 | + | |
132 | +<style> | |
133 | +.propmt-container{ | |
134 | + padding: 0px 50px 30px 50px; | |
135 | +} | |
136 | +/* 입력 컨테이너 */ | |
137 | +.input-conatiner{ | |
138 | + display: flex; | |
139 | + align-items: center; | |
140 | + gap: 30px; | |
141 | + height: 50px; | |
142 | +} | |
143 | +.input-conatiner input{ | |
144 | + width: 90%; | |
145 | + height: 100%; | |
146 | + padding: 10px 30px; | |
147 | + border: none; | |
148 | + border-radius: 10px; | |
149 | + box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px; | |
150 | + outline: none; | |
151 | + font-size : 28px; | |
152 | +} | |
153 | +.input-conatiner button{ | |
154 | + width: 15%; | |
155 | + height: 100%; | |
156 | + border: none; | |
157 | + border-radius: 10px; | |
158 | + cursor: pointer; | |
159 | + background-color: #ffba08; | |
160 | + display: flex; | |
161 | + justify-content: center; | |
162 | + align-items: center; | |
163 | +} | |
164 | + | |
165 | +.input-conatiner button p{ | |
166 | + font-size : 28px; | |
167 | +} | |
168 | + | |
169 | +/* 진행 상태바 */ | |
170 | +.progress-container { | |
171 | + width: 100%; | |
172 | + background-color: #ffffff; | |
173 | + border-radius: 5px; | |
174 | + overflow: hidden; | |
175 | + margin-top: 20px; | |
176 | + display : flex; | |
177 | + flex-direction: column; | |
178 | + gap :30px; | |
179 | +} | |
180 | + | |
181 | +.progress-bar { | |
182 | + height: 30px; | |
183 | + width: 0; | |
184 | + background-color: #4caf50; | |
185 | + text-align: center; | |
186 | + line-height: 30px; | |
187 | + color: white; | |
188 | + border : none; | |
189 | + border-radius: 5px; | |
190 | + transition: width 0.3s; | |
191 | + font-size : 20px; | |
192 | +} | |
193 | + | |
194 | +/* 로딩 gif */ | |
195 | +.loading-container{ | |
196 | + display : flex; | |
197 | + flex-direction: column; | |
198 | + align-items: center; | |
199 | + gap: 30px; | |
200 | + text-align: center; | |
201 | + margin-top : 30px; | |
202 | + padding-bottom : 40px; | |
203 | +} | |
204 | +.loading-gif{ | |
205 | + width: 25%; | |
206 | + border-radius: 500px; | |
207 | +} | |
208 | +.loading-container p { | |
209 | + font-size : 25px; | |
210 | +} | |
211 | + | |
212 | +/* 결과 container */ | |
213 | +.result-container{ | |
214 | + margin-top: 30px; | |
215 | + display: flex; | |
216 | + flex-direction: column; | |
217 | + align-items: center; | |
218 | + gap: 30px; | |
219 | +} | |
220 | +.result-container img{ | |
221 | + width : 30%; | |
222 | +} | |
223 | +.word-container{ | |
224 | + width: 30%; | |
225 | + text-align: center; | |
226 | + padding: 20px 0px; | |
227 | + border: 3px solid #4caf50; | |
228 | + border-radius: 8px; | |
229 | + display: flex; | |
230 | + flex-direction: column; | |
231 | + gap: 15px; | |
232 | +} | |
233 | +.word-container h2 { | |
234 | + font-size : 28px; | |
235 | +} | |
236 | + | |
237 | +.aibox{ | |
238 | + display: flex; | |
239 | + flex-direction: column; | |
240 | + justify-content: center; | |
241 | + height: auto; | |
242 | + padding: 30px 0px; | |
243 | +} | |
244 | +</style> |
--- client/views/layout/Menu.vue
+++ client/views/layout/Menu.vue
... | ... | @@ -1,20 +1,20 @@ |
1 | 1 |
<template> |
2 | 2 |
<div class="menu flex" style="gap: 30px;"> |
3 |
- <router-link to="/MyPage.page"> |
|
4 |
- <button :class="{ active: activeButton === 'mypage' }" type="submit" @click="goToApp('mypage')"> |
|
5 |
- <p :class="{ active: activeButton === 'mypage' }">마이페이지</p> |
|
6 |
- </button> |
|
3 |
+ <router-link to="/MyPlan.page"> |
|
4 |
+ <button :class="{ active: activeButton === 'schedule' }" type="submit" @click="goToApp('schedule')"> |
|
5 |
+ <p :class="{ active: activeButton === 'schedule' }">학습일정계획</p> |
|
6 |
+ </button> |
|
7 | 7 |
</router-link> |
8 | 8 |
<router-link to="/Dashboard.page"> |
9 | 9 |
<button :class="{ active: activeButton === 'dashboard' }" type="submit" @click="goToApp('dashboard')"> |
10 | 10 |
<p :class="{ active: activeButton === 'dashboard' }">Dashboard</p> |
11 | 11 |
</button> |
12 | 12 |
</router-link> |
13 |
- <router-link to="/MyPlan.page"> |
|
14 |
- <button :class="{ active: activeButton === 'schedule' }" type="submit" @click="goToApp('schedule')"> |
|
15 |
- <p :class="{ active: activeButton === 'schedule' }">학습일정계획</p> |
|
16 |
- </button> |
|
17 |
- </router-link> |
|
13 |
+ <router-link to="/MyPage.page"> |
|
14 |
+ <button :class="{ active: activeButton === 'mypage' }" type="submit" @click="goToApp('mypage')"> |
|
15 |
+ <p :class="{ active: activeButton === 'mypage' }">마이페이지</p> |
|
16 |
+ </button> |
|
17 |
+ </router-link> |
|
18 | 18 |
</div> |
19 | 19 |
</template> |
20 | 20 |
|
--- client/views/layout/Side.vue
+++ client/views/layout/Side.vue
... | ... | @@ -1,6 +1,8 @@ |
1 | 1 |
<template> |
2 | 2 |
<div class="side"> |
3 |
- <div class="logo mb25"><img src="../../resources/img/logo2.png" alt=""></div> |
|
3 |
+ <router-link to="/"> |
|
4 |
+ <div class="logo mb25"><img src="../../resources/img/logo2.png" alt=""></div> |
|
5 |
+ </router-link> |
|
4 | 6 |
<div class="profile mb30"> |
5 | 7 |
<div class="flex align-start"> |
6 | 8 |
<img src="../../resources/img/img16_s.png" alt=""> |
... | ... | @@ -14,7 +16,7 @@ |
14 | 16 |
</div> |
15 | 17 |
<hr> |
16 | 18 |
<p class="title2 mb25">최근 학습 히스토리</p> |
17 |
- <ul class="flex justify-between ml30"> |
|
19 |
+ <ul class="flex justify-between ml30 mb30"> |
|
18 | 20 |
<li>자학사 3학년 2학기</li> |
19 | 21 |
<li>자학사 3학년 2학기</li> |
20 | 22 |
</ul> |
... | ... | @@ -25,14 +27,14 @@ |
25 | 27 |
<div class="memo mb15"><textarea name="" id="" placeholder="궁금한 것을 적어보세요."></textarea></div> |
26 | 28 |
<div class="flex justify-end"><button>질문하기</button></div> |
27 | 29 |
</div> |
28 |
- <div class="btn-wrap flex justify-between"> |
|
30 |
+ <!-- <div class="btn-wrap flex justify-between"> |
|
29 | 31 |
<button class="login-btn" @click="handleClick" > |
30 | 32 |
<img src="../../resources/img/btn07_s.png" alt=""> |
31 | 33 |
<p>{{ buttonText }}</p> |
32 | 34 |
</button> |
33 | 35 |
|
34 | 36 |
<button class="login-btn" type="submit" ><img src="../../resources/img/btn07_s.png" alt=""> <p >오늘 할 다른 공부</p></button> |
35 |
- </div> |
|
37 |
+ </div> --> |
|
36 | 38 |
</div> |
37 | 39 |
</template> |
38 | 40 |
|
--- 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" |
... | ... | @@ -85,11 +86,7 @@ |
85 | 86 |
import TextDetail from "./teacher/TextDetail.vue"; |
86 | 87 |
import QuestionList from "./teacher/QuestionList.vue"; |
87 | 88 |
import QuestionInsert from "./teacher/QuestionInsert.vue"; |
88 |
-<<<<<<< HEAD |
|
89 |
-import QuestionDetail from "./teacher/QuestionDetail.vue"; |
|
90 |
-======= |
|
91 | 89 |
import QuestionDetail from "./teacher/QuestionDetail.vue"; |
92 |
->>>>>>> e47769b90c7ad4f0b34f38bb2a56a8a69a894941 |
|
93 | 90 |
import VocaList from "./teacher/VocaList.vue"; |
94 | 91 |
import VocaInsert from "./teacher/VocaInsert.vue"; |
95 | 92 |
import VocaDetail from "./teacher/VocaDetail.vue"; |
... | ... | @@ -114,6 +111,7 @@ |
114 | 111 |
component: Main, |
115 | 112 |
children: [ |
116 | 113 |
{ path: '/Dashboard.page', name: 'Dashboard', component: Dashboard }, |
114 |
+ { path: '/AIDashboard.page', name: 'AIDashboard', component: AIDashboard }, |
|
117 | 115 |
{ path: '/MyPage.page', name: 'MyPage', component: MyPage }, |
118 | 116 |
{ path: '/MyPlan.page', name: 'MyPlan', component: MyPlan }, |
119 | 117 |
{ path: '/MyPlan2.page', name: 'MyPlan2', component: MyPlan2 }, |
... | ... | @@ -198,11 +196,7 @@ |
198 | 196 |
{ path: '/TextDetail.page', name: 'TextDetail', component: TextDetail }, |
199 | 197 |
{ path: '/QuestionList.page', name: 'QuestionList', component: QuestionList }, |
200 | 198 |
{ path: '/QuestionInsert.page', name: 'QuestionInsert', component: QuestionInsert }, |
201 |
-<<<<<<< HEAD |
|
202 |
- { path: '/QuestionDetail.page', name: 'QuestionDetail', component: QuestionDetail }, |
|
203 |
-======= |
|
204 | 199 |
{ path: '/QuestionDetail.page', name: 'QuestionDetail', component: QuestionDetail }, |
205 |
->>>>>>> e47769b90c7ad4f0b34f38bb2a56a8a69a894941 |
|
206 | 200 |
{ path: '/VocaList.page', name: 'VocaList', component: VocaList }, |
207 | 201 |
{ path: '/VocaInsert.page', name: 'VocaInsert', component: VocaInsert }, |
208 | 202 |
{ path: '/VocaDetail.page', name: 'VocaDetail', component: VocaDetail }, |
+++ 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/Chapter/Chapter1.vue
+++ client/views/pages/main/Chapter/Chapter1.vue
... | ... | @@ -17,10 +17,12 @@ |
17 | 17 |
</div> |
18 | 18 |
<div class="next-btn" @click="goToPage('Chapter1_1')"><img src="../../../../resources/img/right.png" alt=""></div> |
19 | 19 |
</div> |
20 |
+ <TextToImage/> |
|
20 | 21 |
</div> |
21 | 22 |
</template> |
22 | 23 |
|
23 | 24 |
<script> |
25 |
+import TextToImage from '../../../component/TextToImage.vue'; |
|
24 | 26 |
export default { |
25 | 27 |
data() { |
26 | 28 |
return { |
... | ... | @@ -38,6 +40,7 @@ |
38 | 40 |
|
39 | 41 |
}, |
40 | 42 |
components: { |
43 |
+ TextToImage : TextToImage, |
|
41 | 44 |
}, |
42 | 45 |
mounted() { |
43 | 46 |
} |
--- client/views/pages/main/Chapter/Chapter1_2.vue
+++ client/views/pages/main/Chapter/Chapter1_2.vue
... | ... | @@ -49,10 +49,12 @@ |
49 | 49 |
</div> |
50 | 50 |
<div class="next-btn" @click="goToPage('Chapter1_3')"><img src="../../../../resources/img/right.png" alt=""></div> |
51 | 51 |
</div> |
52 |
+ <TextToImage/> |
|
52 | 53 |
</div> |
53 | 54 |
</template> |
54 | 55 |
|
55 | 56 |
<script> |
57 |
+import TextToImage from '../../../component/TextToImage.vue'; |
|
56 | 58 |
export default { |
57 | 59 |
data() { |
58 | 60 |
return { |
... | ... | @@ -70,6 +72,7 @@ |
70 | 72 |
|
71 | 73 |
}, |
72 | 74 |
components: { |
75 |
+ TextToImage : TextToImage |
|
73 | 76 |
}, |
74 | 77 |
mounted() { |
75 | 78 |
const textArea = this.$refs.textArea; |
--- client/views/pages/main/Chapter/Chapter1_3.vue
+++ client/views/pages/main/Chapter/Chapter1_3.vue
... | ... | @@ -20,10 +20,12 @@ |
20 | 20 |
</div> |
21 | 21 |
<div class="next-btn" @click="goToPage('Dashboard')"><img src="../../../../resources/img/right.png" alt=""></div> |
22 | 22 |
</div> |
23 |
+ <TextToImage/> |
|
23 | 24 |
</div> |
24 | 25 |
</template> |
25 | 26 |
|
26 | 27 |
<script> |
28 |
+import TextToImage from '../../../component/TextToImage.vue'; |
|
27 | 29 |
export default { |
28 | 30 |
data() { |
29 | 31 |
return { |
... | ... | @@ -41,8 +43,12 @@ |
41 | 43 |
|
42 | 44 |
}, |
43 | 45 |
components: { |
46 |
+ TextToImage : TextToImage |
|
44 | 47 |
}, |
45 | 48 |
mounted() { |
46 | 49 |
} |
47 | 50 |
} |
48 |
-</script>(No newline at end of file) |
|
51 |
+</script> |
|
52 |
+<style scoped> |
|
53 |
+.listenGroup .textbox { width: 900px}; |
|
54 |
+</style>(No newline at end of file) |
--- client/views/pages/main/Chapter/Chapter2_2.vue
+++ client/views/pages/main/Chapter/Chapter2_2.vue
... | ... | @@ -1,31 +1,40 @@ |
1 | 1 |
<template> |
2 |
- <div id="Chapter2_2" class="content-wrap"> |
|
3 |
- <div class="title-box mb25 flex align-center mt40"> |
|
4 |
- <span class="title mr40">1. Hello WORLD</span> |
|
5 |
- <span class="subtitle">my name is dd</span> |
|
6 |
- </div> |
|
7 |
- <div class="flex justify-between align-center"> |
|
8 |
- <div class="pre-btn" @click="goToPage('Chapter2_1')"><img src="../../../../resources/img/left.png" alt=""></div> |
|
9 |
- <div class="content title-box"> |
|
10 |
- <p class="title mt25 title-bg">step2</p> |
|
11 |
- <div class="flex align-center mb30"> |
|
12 |
- <p class="subtitle2 mr20">카드를 뒤집어 보세요.</p> |
|
13 |
- <!-- <button><img src="../../../../resources/img/btn10_s.png" alt=""> |
|
2 |
+ <div id="Chapter2_2" class="content-wrap"> |
|
3 |
+ <div class="title-box mb25 flex align-center mt40"> |
|
4 |
+ <span class="title mr40">1. Hello WORLD</span> |
|
5 |
+ <span class="subtitle">my name is dd</span> |
|
6 |
+ </div> |
|
7 |
+ <div class="flex justify-between align-center"> |
|
8 |
+ <div class="pre-btn" @click="goToPage('Chapter2_1')"><img src="../../../../resources/img/left.png" alt=""> |
|
9 |
+ </div> |
|
10 |
+ <div class="content title-box"> |
|
11 |
+ <p class="title mt25 title-bg">step2</p> |
|
12 |
+ <div class="flex align-center mb30"> |
|
13 |
+ <p class="subtitle2 mr20">카드를 뒤집어 보세요.</p> |
|
14 |
+ <!-- <button><img src="../../../../resources/img/btn10_s.png" alt=""> |
|
14 | 15 |
</button> --> |
15 |
- </div> |
|
16 |
+ </div> |
|
16 | 17 |
|
17 |
- <div class="imgGroup"> |
|
18 |
- <div class="flex justify-center" style="gap: 90px;"> |
|
19 |
- <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"> |
|
20 |
- <img :src="item.imgSrc1" > |
|
21 |
- <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }"> |
|
22 |
- </button> |
|
23 |
- </div> |
|
24 |
- </div> |
|
25 |
- </div> |
|
26 |
- <div class="next-btn" @click="goToPage('Chapter2_3')"><img src="../../../../resources/img/right.png" alt=""></div> |
|
27 |
- </div> |
|
28 |
- </div> |
|
18 |
+ <div class="imgGroup"> |
|
19 |
+ <div class="flex justify-center" style="gap: 90px;"> |
|
20 |
+ <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"> |
|
21 |
+ <div class="listenGroup"> |
|
22 |
+ <img :src="item.imgSrc1"> |
|
23 |
+ <p class="textbox"><img :src="item.imgSrc"></p> |
|
24 |
+ |
|
25 |
+ </div> |
|
26 |
+ <div class="listenGroup" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }"> |
|
27 |
+ <img :src="item.imgSrc2"> |
|
28 |
+ <p class="title4 textbox">{{ item.title }}</p> |
|
29 |
+ </div> |
|
30 |
+ </button> |
|
31 |
+ </div> |
|
32 |
+ </div> |
|
33 |
+ </div> |
|
34 |
+ <div class="next-btn" @click="goToPage('Chapter2_3')"><img src="../../../../resources/img/right.png" alt=""> |
|
35 |
+ </div> |
|
36 |
+ </div> |
|
37 |
+ </div> |
|
29 | 38 |
</template> |
30 | 39 |
|
31 | 40 |
|
... | ... | @@ -34,10 +43,10 @@ |
34 | 43 |
data() { |
35 | 44 |
return { |
36 | 45 |
items: [ |
37 |
- { imgSrc1: 'client/resources/img/img49_s_1.png', imgSrc2: 'client/resources/img/img50_s_2.png', isSecondImageVisible: false }, |
|
38 |
- { imgSrc1: 'client/resources/img/img49_s_2.png', imgSrc2: 'client/resources/img/img50_s_3.png', isSecondImageVisible: false }, |
|
39 |
- { imgSrc1: 'client/resources/img/img49_s_3.png', imgSrc2: 'client/resources/img/img50_s_4.png', isSecondImageVisible: false }, |
|
40 |
- { imgSrc1: 'client/resources/img/img49_s_4.png', imgSrc2: 'client/resources/img/img50_s_1.png', isSecondImageVisible: false } |
|
46 |
+ { imgSrc1: 'client/resources/img/img49_s.png', imgSrc2: 'client/resources/img/img50_s.png', imgSrc: 'client/resources/img/img59_36s.png', isSecondImageVisible: false, title: 'a' }, |
|
47 |
+ { imgSrc1: 'client/resources/img/img49_s.png', imgSrc2: 'client/resources/img/img50_s.png', imgSrc: 'client/resources/img/img59_36s.png', isSecondImageVisible: false, title: 'a' }, |
|
48 |
+ { imgSrc1: 'client/resources/img/img49_s.png', imgSrc2: 'client/resources/img/img50_s.png', imgSrc: 'client/resources/img/img59_36s.png', isSecondImageVisible: false, title: 'a' }, |
|
49 |
+ { imgSrc1: 'client/resources/img/img49_s.png', imgSrc2: 'client/resources/img/img50_s.png', imgSrc: 'client/resources/img/img59_36s.png', isSecondImageVisible: false, title: 'a' } |
|
41 | 50 |
] |
42 | 51 |
}; |
43 | 52 |
}, |
... | ... | @@ -51,4 +60,21 @@ |
51 | 60 |
} |
52 | 61 |
} |
53 | 62 |
|
54 |
-</script>(No newline at end of file) |
|
63 |
+</script> |
|
64 |
+<style scoped> |
|
65 |
+.popTxt { |
|
66 |
+ width: 295px; |
|
67 |
+ height: 406px; |
|
68 |
+} |
|
69 |
+ |
|
70 |
+.popTxt div:last-child img { |
|
71 |
+ position: absolute; |
|
72 |
+ top: 0; |
|
73 |
+ left: 0; |
|
74 |
+} |
|
75 |
+ |
|
76 |
+.listenGroup .textbox { |
|
77 |
+ top: 50%; |
|
78 |
+ left: 50%; |
|
79 |
+} |
|
80 |
+</style>(No newline at end of file) |
--- client/views/pages/main/Chapter/Chapter2_4.vue
+++ client/views/pages/main/Chapter/Chapter2_4.vue
... | ... | @@ -19,11 +19,15 @@ |
19 | 19 |
<div class="imgGroup"> |
20 | 20 |
|
21 | 21 |
<div class="flex justify-center"> |
22 |
- <div class="btnGroup flex" style="gap: 60px;"> |
|
22 |
+ <div class="listenGroup flex" style="gap: 60px;"> |
|
23 | 23 |
<button class="popTxt" v-for="(item, index) in items" :key="index" @click="updateContent(index)" |
24 | 24 |
:class="{ active: selectedIndex === index }"> |
25 | 25 |
<img :src="item.imgSrc1"> |
26 | 26 |
<img :src="item.imgSrc2" v-if="selectedIndex === index" style="display: block;"> |
27 |
+ <div class="textbox"> |
|
28 |
+ <div style="height: 80%; line-height: 200px;"> <img :src="item.imgSrc" alt=""></div> |
|
29 |
+ <p class="subtitle3" style="height: 20%;">{{ item.title }}</p> |
|
30 |
+ </div> |
|
27 | 31 |
</button> |
28 | 32 |
</div> |
29 | 33 |
</div> |
... | ... | @@ -43,10 +47,10 @@ |
43 | 47 |
data() { |
44 | 48 |
return { |
45 | 49 |
items: [ |
46 |
- { imgSrc1: 'client/resources/img/img61_36s_1.png', imgSrc2: 'client/resources/img/img61_36s_1_Click.png' }, |
|
47 |
- { imgSrc1: 'client/resources/img/img61_36s_2.png', imgSrc2: 'client/resources/img/img61_36s_2_Click.png' }, |
|
48 |
- { imgSrc1: 'client/resources/img/img61_36s_3.png', imgSrc2: 'client/resources/img/img61_36s_4_Click.png' }, |
|
49 |
- { imgSrc1: 'client/resources/img/img61_36s_4.png', imgSrc2: 'client/resources/img/img61_36s_3_Click.png' }, |
|
50 |
+ { imgSrc1: 'client/resources/img/img61_36s.png', imgSrc2: 'client/resources/img/img62_36s.png', imgSrc: 'client/resources/img/img59_36s.png', title: 'apple' }, |
|
51 |
+ { imgSrc1: 'client/resources/img/img61_36s.png', imgSrc2: 'client/resources/img/img62_36s.png', imgSrc: 'client/resources/img/img58_36s.png', title: 'a'}, |
|
52 |
+ { imgSrc1: 'client/resources/img/img61_36s.png', imgSrc2: 'client/resources/img/img62_36s.png', imgSrc: 'client/resources/img/img57_36s.png', title: 'a' }, |
|
53 |
+ { imgSrc1: 'client/resources/img/img61_36s.png', imgSrc2: 'client/resources/img/img62_36s.png', imgSrc: 'client/resources/img/img56_36s.png', title: 'a' }, |
|
50 | 54 |
], |
51 | 55 |
selectedIndex: 0, |
52 | 56 |
timer: "00", |
... | ... | @@ -68,10 +72,17 @@ |
68 | 72 |
</script> |
69 | 73 |
|
70 | 74 |
<style scoped> |
75 |
+.listenGroup{height: 305px;} |
|
76 |
+.listenGroup button{position: relative;} |
|
77 |
+.listenGroup .textbox { |
|
78 |
+ width: 184px; |
|
79 |
+ height: 206px; |
|
80 |
+ left: 46%; |
|
81 |
+ top: 43%;} |
|
71 | 82 |
.look-text{bottom: 50px;} |
72 | 83 |
.btnGroup{width: 1060px; height: 340px;} |
73 |
-.popTxt{width: 216px;} |
|
74 |
-.popTxt img { |
|
84 |
+.popTxt{width: 216px; } |
|
85 |
+.popTxt > img { |
|
75 | 86 |
position: absolute; |
76 | 87 |
top: 0; |
77 | 88 |
left: 0; |
--- client/views/pages/main/Chapter/Chapter2_9.vue
+++ client/views/pages/main/Chapter/Chapter2_9.vue
... | ... | @@ -17,15 +17,67 @@ |
17 | 17 |
<div class="text-ct"> |
18 | 18 |
<div class="dropGroup flex align-center justify-center"> |
19 | 19 |
<div style="position: relative;" > |
20 |
- <img src="../../../../resources/img/img72_41s_1.png" alt="" style="width: 90%;"> |
|
21 |
- <button style="left: 141px; |
|
22 |
- top: 60px;"><img src="../../../../resources/img/img78_41s.png" alt=""></button> |
|
23 |
- <button style=" left: 200px; |
|
24 |
- bottom: 86px;"><img src="../../../../resources/img/img79_41s.png" alt=""></button> |
|
25 |
- <button style=" right: 237px; |
|
26 |
- top: 159px;"><img src="../../../../resources/img/img80_41s.png" alt=""></button> |
|
27 |
- <button style=" right: 159px; |
|
28 |
- bottom: 46px;"><img src="../../../../resources/img/img81_41s.png" alt=""></button> |
|
20 |
+ <img src="../../../../resources/img/img72_41s.png" alt="" style="width: 93%;"> |
|
21 |
+ <div class="vocaGroup"> |
|
22 |
+ <div class="flex justify-between mb80"> |
|
23 |
+ <article class="flex align-center"> |
|
24 |
+ <div class="imgGroup mr30"><img src="../../../../resources/img/img74_41s.png" data-num="1"></div> |
|
25 |
+ <div class="flex align-start"> |
|
26 |
+ <div> |
|
27 |
+ <h3>sidewalk</h3> |
|
28 |
+ <div class="flex align-center mt10"> |
|
29 |
+ <span class="title1">보도</span> |
|
30 |
+ </div> |
|
31 |
+ </div> |
|
32 |
+ </div> |
|
33 |
+ </article> |
|
34 |
+ <article class="flex align-center"> |
|
35 |
+ <div class="imgGroup mr30"><img src="../../../../resources/img/img75_41s.png" data-num="1"></div> |
|
36 |
+ <div class="flex align-start"> |
|
37 |
+ <div> |
|
38 |
+ <h3>sidewalk</h3> |
|
39 |
+ <div class="flex align-center mt10"> |
|
40 |
+ <span class="title1">보도</span> |
|
41 |
+ </div> |
|
42 |
+ </div> |
|
43 |
+ </div> |
|
44 |
+ </article> |
|
45 |
+ </div> |
|
46 |
+ <div class="flex justify-between"> |
|
47 |
+ <article class="flex align-center"> |
|
48 |
+ <div class="imgGroup mr30"><img src="../../../../resources/img/img76_41s.png" data-num="1"></div> |
|
49 |
+ <div class="flex align-start"> |
|
50 |
+ <div> |
|
51 |
+ <h3>sidewalk</h3> |
|
52 |
+ <div class="flex align-center mt10"> |
|
53 |
+ <span class="title1">보도</span> |
|
54 |
+ </div> |
|
55 |
+ </div> |
|
56 |
+ </div> |
|
57 |
+ </article> |
|
58 |
+ <article class="flex align-center"> |
|
59 |
+ <div class="imgGroup mr30"><img src="../../../../resources/img/img77_41s.png" data-num="1"></div> |
|
60 |
+ <div class="flex align-start"> |
|
61 |
+ <div> |
|
62 |
+ <h3>sidewalk</h3> |
|
63 |
+ <div class="flex align-center mt10"> |
|
64 |
+ <span class="title1">보도</span> |
|
65 |
+ </div> |
|
66 |
+ </div> |
|
67 |
+ </div> |
|
68 |
+ </article> |
|
69 |
+ </div> |
|
70 |
+ </div> |
|
71 |
+ <div class="textbox"> |
|
72 |
+ <button style="left: 141px; |
|
73 |
+ top: 44px;"><img src="../../../../resources/img/img78_41s.png" alt=""></button> |
|
74 |
+ <button style=" left: 200px; |
|
75 |
+ bottom: 46px;"><img src="../../../../resources/img/img79_41s.png" alt=""></button> |
|
76 |
+ <button style=" right: 237px; |
|
77 |
+ top: 114px;"><img src="../../../../resources/img/img80_41s.png" alt=""></button> |
|
78 |
+ <button style=" right: 159px; |
|
79 |
+ bottom: 5px;"><img src="../../../../resources/img/img81_41s.png" alt=""></button> |
|
80 |
+ </div> |
|
29 | 81 |
|
30 | 82 |
</div> |
31 | 83 |
</div> |
... | ... | @@ -68,4 +120,8 @@ |
68 | 120 |
} |
69 | 121 |
</script> |
70 | 122 |
<style scoped> |
123 |
+ .vocaGroup{ width: 79%; |
|
124 |
+ position: absolute; |
|
125 |
+ top: 90px; |
|
126 |
+ left: 120px;} |
|
71 | 127 |
</style>(No newline at end of file) |
--- client/views/pages/main/Dashboard.vue
+++ client/views/pages/main/Dashboard.vue
... | ... | @@ -1,11 +1,28 @@ |
1 | 1 |
<template> |
2 |
- <div class="main"> |
|
3 |
- <div class="race-wrap"> |
|
4 |
- <div class="title-box"> |
|
5 |
- <p class="title">hello world!</p> |
|
6 |
- <p class="subtitle">hi my name is dd!</p> |
|
7 |
- </div> |
|
8 |
- <div class="race-box"> |
|
2 |
+ <div> |
|
3 |
+ <div v-if="roadViewTF"> |
|
4 |
+ <!-- 1번 템플릿 --> |
|
5 |
+ <div class="main"> |
|
6 |
+ <div class="race-wrap"> |
|
7 |
+ <div class="title-box"> |
|
8 |
+ <p class="title">hello world!</p> |
|
9 |
+ <p class="subtitle">hi my name is dd!</p> |
|
10 |
+ </div> |
|
11 |
+ <!-- |
|
12 |
+ <div class="race-box"> |
|
13 |
+ <div class="rabbit-start"><img src="../../../resources/img/img09_s.png" alt=""></div> |
|
14 |
+ <div class="rcon flex justify-between mb5"> |
|
15 |
+ <div class="race-btn" v-for="roadmap in roadmapData" :key="roadmap.learning_id" @click="goToPage('Chapter7')"> |
|
16 |
+ <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" data-num="2"> |
|
17 |
+ <img :src="item.imgSrc1"> |
|
18 |
+ <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }"> |
|
19 |
+ </button> |
|
20 |
+ <p>{{getNonNullColumn(roadmap)}}</p> |
|
21 |
+ </div> |
|
22 |
+ </div> |
|
23 |
+ </div> |
|
24 |
+ --> |
|
25 |
+ <div class="race-box"> |
|
9 | 26 |
<div class="rabbit-start"><img src="../../../resources/img/img09_s.png" alt=""></div> |
10 | 27 |
<div class="rcon flex justify-end mb5"> |
11 | 28 |
<div class="race-btn" @click="goToPage('Chapter1')"> |
... | ... | @@ -92,16 +109,16 @@ |
92 | 109 |
</button> |
93 | 110 |
<p>단어장</p> |
94 | 111 |
</div> |
95 |
- <div class="rcon flex"> |
|
96 |
- <div class="race-btn"> |
|
97 |
- <button class="popTxt" v-for="(item, index) in items" :key="index" |
|
98 |
- @click="toggleImageAndShowPopup(index, '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> |
|
112 |
+ |
|
113 |
+ <div class="race-btn"> |
|
114 |
+ <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" |
|
115 |
+ data-num="11"> |
|
116 |
+ <img :src="item.imgSrc3"> |
|
117 |
+ <img :src="item.imgSrc4" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }"> |
|
118 |
+ </button> |
|
119 |
+ <p class="long">최종 평가</p> |
|
104 | 120 |
</div> |
121 |
+ |
|
105 | 122 |
</div> |
106 | 123 |
</div> |
107 | 124 |
<!-- 팝업 --> |
... | ... | @@ -112,157 +129,146 @@ |
112 | 129 |
<svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon> |
113 | 130 |
</button> |
114 | 131 |
|
115 |
- <div class="mb30 text-ct"> |
|
116 |
- <p class="title1 mb20">1단원이 끝났습니다!</p> |
|
117 |
- <p class="title1"><em class="yellow">기념사진</em>을 촬영하러 가요</p> |
|
118 |
- </div> |
|
119 |
- <div class="flex justify-center"> |
|
120 |
- <button type="button" title="사진촬영" class="new-btn" @click="openCameraModal"> |
|
121 |
- 사진 촬영 |
|
122 |
- </button> |
|
123 |
- </div> |
|
124 |
- </div> |
|
125 |
- </div> |
|
126 |
- |
|
127 |
- <!-- 카메라 모달 --> |
|
128 |
- <article v-show="showCameraModal" class="popup-wrap"> |
|
129 |
- <div class="popup-box" style="top: 500px; left:500px"> |
|
130 |
- <div class="flex mb10 justify-between"> |
|
131 |
- <p class="popup-title">사진 촬영</p> |
|
132 |
- <button type="button" class="popup-close-btn" @click="closeModal"> |
|
133 |
- <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon> |
|
134 |
- </button> |
|
135 |
- </div> |
|
136 |
- <div class="box"> |
|
137 |
- <div style="width: 100%;"> |
|
138 |
- <!-- 여기에 카메라 기능을 구현 --> |
|
139 |
- <!-- <p>카메라 모듈이 여기에 위치합니다.</p> --> |
|
140 |
- |
|
141 |
- <div id="container" ref="container"> |
|
142 |
- <video v-if="!photoTaken" autoplay="true" ref="modalVideoElement" class="mirrored" |
|
143 |
- @canplay="onVideoLoaded"></video> |
|
144 |
- </div> |
|
145 |
- |
|
132 |
+ <div class="mb30 text-ct"> |
|
133 |
+ <p class="title1 mb20">1단원이 끝났습니다!</p> |
|
134 |
+ <p class="title1"><em class="yellow">기념사진</em>을 촬영하러 가요</p> |
|
135 |
+ </div> |
|
136 |
+ <div class="flex justify-center"> |
|
137 |
+ <button type="button" title="사진촬영" class="new-btn" @click="openCameraModal"> |
|
138 |
+ 사진 촬영 |
|
139 |
+ </button> |
|
146 | 140 |
</div> |
147 | 141 |
</div> |
148 |
- <div class="flex justify-center mt20"> |
|
149 |
- <button type="button" class="new-btn" v-if="!photoTaken" @click="capturePhoto" |
|
150 |
- :disabled="!videoReady"> |
|
151 |
- 사진 촬영 |
|
152 |
- </button> |
|
153 |
- |
|
154 |
- </div> |
|
155 | 142 |
</div> |
156 |
- </article> |
|
157 | 143 |
|
158 |
- <!-- 사진 모달 --> |
|
159 |
- <article v-show="showPhotoModal" class="popup-wrap"> |
|
160 |
- <div class="popup-box" style="top: 500px; left: auto"> |
|
161 |
- <div class="flex mb10 justify-between"> |
|
162 |
- <p class="popup-title">사진 꾸미기</p> |
|
163 |
- <button type="button" class="popup-close-btn" @click="closePhotoModal"> |
|
164 |
- <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon> |
|
165 |
- </button> |
|
144 |
+ <!-- 카메라 모달 --> |
|
145 |
+ <article v-show="showCameraModal" class="popup-wrap"> |
|
146 |
+ <div class="popup-box" style="top: 500px; left:500px"> |
|
147 |
+ <div class="flex mb10 justify-between"> |
|
148 |
+ <p class="popup-title">사진 촬영</p> |
|
149 |
+ <button type="button" class="popup-close-btn" @click="closeModal"> |
|
150 |
+ <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon> |
|
151 |
+ </button> |
|
152 |
+ </div> |
|
153 |
+ <div class="box"> |
|
154 |
+ <div style="width: 100%;"> |
|
155 |
+ <div id="container" ref="container"> |
|
156 |
+ <video v-if="!photoTaken" autoplay="true" ref="modalVideoElement" class="mirrored" |
|
157 |
+ @canplay="onVideoLoaded"></video> |
|
158 |
+ </div> |
|
159 |
+ </div> |
|
160 |
+ </div> |
|
161 |
+ <div class="flex justify-center mt20"> |
|
162 |
+ <button type="button" class="new-btn" v-if="!photoTaken" @click="capturePhoto" |
|
163 |
+ :disabled="!videoReady"> |
|
164 |
+ 사진 촬영 |
|
165 |
+ </button> |
|
166 |
+ </div> |
|
166 | 167 |
</div> |
167 |
- <div class="flex justify-between align-center" style="gap: 40px;"> |
|
168 |
- <div class="content" style="padding: 30px; min-width: 401px; min-height: 710px;"> |
|
169 |
- <div class="tool"> |
|
170 |
- <div class="flex justify-center mb20" style="gap: 20px;"> |
|
171 |
- <button class="popTxt" style="width: 101px;" v-for="(item, index) in items_photo" |
|
172 |
- :key="index" @click="updateContent(index)" |
|
173 |
- :class="{ active: selectedIndex === index }"> |
|
174 |
- <img :src="item.imgSrc1" style="display: block;"> |
|
175 |
- <img :src="item.imgSrc2" v-if="selectedIndex === index" style="display: block;"> |
|
168 |
+ </article> |
|
169 |
+ |
|
170 |
+ <!-- 사진 모달 --> |
|
171 |
+ <article v-show="showPhotoModal" class="popup-wrap"> |
|
172 |
+ <div class="popup-box" style="top: 500px; left: auto"> |
|
173 |
+ <div class="flex mb10 justify-between"> |
|
174 |
+ <p class="popup-title">사진 꾸미기</p> |
|
175 |
+ <button type="button" class="popup-close-btn" @click="closePhotoModal"> |
|
176 |
+ <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon> |
|
177 |
+ </button> |
|
178 |
+ </div> |
|
179 |
+ <div class="flex justify-between align-center" style="gap: 40px;"> |
|
180 |
+ <div class="content" style="padding: 30px; min-width: 401px; min-height: 710px;"> |
|
181 |
+ <div class="tool"> |
|
182 |
+ <div class="flex justify-center mb20" style="gap: 20px;"> |
|
183 |
+ <button class="popTxt" style="width: 101px;" v-for="(item, index) in items_photo" |
|
184 |
+ :key="index" @click="updateContent(index)" |
|
185 |
+ :class="{ active: selectedIndex === index }"> |
|
186 |
+ <img :src="item.imgSrc1" style="display: block;"> |
|
187 |
+ <img :src="item.imgSrc2" v-if="selectedIndex === index" style="display: block;"> |
|
188 |
+ </button> |
|
189 |
+ </div> |
|
190 |
+ </div> |
|
191 |
+ |
|
192 |
+ <div class="stickers" v-show="!stickersVisible"> |
|
193 |
+ <div class="toolbar"> |
|
194 |
+ <label for="brushSize" style="font-size: 9px;">펜 굵기</label> |
|
195 |
+ <input type="color" v-model="color" /> |
|
196 |
+ <input type="range" id="brushSize" min="1" max="10" v-model="brushSize" |
|
197 |
+ @input="updateBrushSize" style="width: 100px; margin-left: 5px;" /> |
|
198 |
+ <button class="new-btn" style="font-size: 9px;" @click="setTool('draw')">펜</button> |
|
199 |
+ <button class="new-btn" style="font-size: 9px;" @click="setTool('eraser')">지우개</button> |
|
200 |
+ <button class="new-btn" style="font-size: 9px;" @click="clearAll">전체 지우개</button> |
|
201 |
+ </div> |
|
202 |
+ </div> |
|
203 |
+ |
|
204 |
+ <div class="stickers" v-show="stickersVisible"> |
|
205 |
+ <button><img src="../../../resources/img/img146_75s.png" alt=""></button> |
|
206 |
+ <button><img src="../../../resources/img/img147_75s.png" alt=""></button> |
|
207 |
+ <button><img src="../../../resources/img/img148_75s.png" alt=""></button> |
|
208 |
+ <button><img src="../../../resources/img/img149_75s.png" alt=""></button> |
|
209 |
+ <button><img src="../../../resources/img/img150_75s.png" alt=""></button> |
|
210 |
+ <button><img src="../../../resources/img/img151_75s.png" alt=""></button> |
|
211 |
+ <button><img src="../../../resources/img/img152_75s.png" alt=""></button> |
|
212 |
+ <button><img src="../../../resources/img/img153_75s.png" alt=""></button> |
|
213 |
+ <button><img src="../../../resources/img/img154_75s.png" alt=""></button> |
|
214 |
+ <button><img src="../../../resources/img/img155_75s.png" alt=""></button> |
|
215 |
+ <button><img src="../../../resources/img/img156_75s.png" alt=""></button> |
|
216 |
+ <button><img src="../../../resources/img/img157_75s.png" alt=""></button> |
|
217 |
+ <button><img src="../../../resources/img/img158_75s.png" alt=""></button> |
|
218 |
+ </div> |
|
219 |
+ </div> |
|
220 |
+ <div> |
|
221 |
+ <div class="content" style="height: 549px; |
|
222 |
+ position: relative; |
|
223 |
+ width: 973px; |
|
224 |
+ display: flex; |
|
225 |
+ justify-content: center; |
|
226 |
+ align-items: center;"> |
|
227 |
+ <canvas ref="canvas" style="position: absolute;"></canvas> |
|
228 |
+ </div> |
|
229 |
+ <div class="btn-wrap flex justify-center mt40" style="gap: 40px;"> |
|
230 |
+ <button class="login-btn" @click="openCameraModal"> |
|
231 |
+ <img src="../../../resources/img/btn07_s.png" alt=""> |
|
232 |
+ <p>재촬영</p> |
|
233 |
+ </button> |
|
234 |
+ |
|
235 |
+ <button class="login-btn" type="submit" @click="goToPage('PhotoEdit')"> |
|
236 |
+ <img src="../../../resources/img/btn07_s.png" alt=""> |
|
237 |
+ <p>완성</p> |
|
176 | 238 |
</button> |
177 | 239 |
</div> |
178 | 240 |
</div> |
179 |
- |
|
180 |
- <div class="stickers" v-show="!stickersVisible"> |
|
181 |
- <div class="toolbar"> |
|
182 |
- <label for="brushSize" style="font-size: 9px;">펜 굵기</label> |
|
183 |
- <input type="color" v-model="color" /> |
|
184 |
- <input type="range" id="brushSize" min="1" max="10" v-model="brushSize" |
|
185 |
- @input="updateBrushSize" style="width: 100px; margin-left: 5px;" /> |
|
186 |
- <button class="new-btn" style="font-size: 9px;" @click="setTool('draw')">펜</button> |
|
187 |
- <button class="new-btn" style="font-size: 9px;" @click="setTool('eraser')">지우개</button> |
|
188 |
- <button class="new-btn" style="font-size: 9px;" @click="clearAll">전체 지우개</button> |
|
241 |
+ <div class="content" style="padding: 30px; min-width: 401px; min-height: 710px;"> |
|
242 |
+ <div class="mb20"> |
|
243 |
+ <p class="popup-title" style="font-size: 32px">랜덤 단어</p> |
|
189 | 244 |
</div> |
190 |
- </div> |
|
191 |
- |
|
192 |
- <div class="stickers" v-show="stickersVisible"> |
|
193 |
- <button><img src="../../../resources/img/img146_75s.png" alt=""></button> |
|
194 |
- <button><img src="../../../resources/img/img147_75s.png" alt=""></button> |
|
195 |
- <button><img src="../../../resources/img/img148_75s.png" alt=""></button> |
|
196 |
- <button><img src="../../../resources/img/img149_75s.png" alt=""></button> |
|
197 |
- <button><img src="../../../resources/img/img150_75s.png" alt=""></button> |
|
198 |
- <button><img src="../../../resources/img/img151_75s.png" alt=""></button> |
|
199 |
- <button><img src="../../../resources/img/img152_75s.png" alt=""></button> |
|
200 |
- <button><img src="../../../resources/img/img153_75s.png" alt=""></button> |
|
201 |
- <button><img src="../../../resources/img/img154_75s.png" alt=""></button> |
|
202 |
- <button><img src="../../../resources/img/img155_75s.png" alt=""></button> |
|
203 |
- <button><img src="../../../resources/img/img156_75s.png" alt=""></button> |
|
204 |
- <button><img src="../../../resources/img/img157_75s.png" alt=""></button> |
|
205 |
- <button><img src="../../../resources/img/img158_75s.png" alt=""></button> |
|
206 |
- </div> |
|
207 |
- </div> |
|
208 |
- <div> |
|
209 |
- <div class="content" style="height: 549px; |
|
210 |
- position: relative; |
|
211 |
- width: 973px; |
|
212 |
- display: flex; |
|
213 |
- justify-content: center; |
|
214 |
- align-items: center;"> |
|
215 |
- <canvas ref="canvas" style="position: absolute;"></canvas> |
|
216 |
- </div> |
|
217 |
- <div class="btn-wrap flex justify-center mt40" style="gap: 40px;"> |
|
218 |
- <button class="login-btn" @click="openCameraModal"> |
|
219 |
- <img src="../../../resources/img/btn07_s.png" alt=""> |
|
220 |
- <p>재촬영</p> |
|
221 |
- </button> |
|
222 |
- |
|
223 |
- <button class="login-btn" type="submit" @click="goToPage('PhotoEdit')"> |
|
224 |
- <img src="../../../resources/img/btn07_s.png" alt=""> |
|
225 |
- <p>완성</p> |
|
226 |
- </button> |
|
227 |
- </div> |
|
228 |
- </div> |
|
229 |
- <div class="content" style="padding: 30px; min-width: 401px; min-height: 710px;"> |
|
230 |
- <div class="mb20"> |
|
231 |
- <p class="popup-title" style="font-size: 32px">랜덤 단어</p> |
|
232 |
- </div> |
|
233 |
- <div class="flex-column" style="gap: 10px;"> |
|
234 |
- <button class="login-btn"><img src="../../../resources/img/img141_75s.png" alt=""> |
|
235 |
- <p class="title">a</p> |
|
236 |
- </button> |
|
237 |
- <button class="login-btn"><img src="../../../resources/img/img152_75s_01.png" alt=""> |
|
238 |
- <p class="title">a</p> |
|
239 |
- </button> |
|
240 |
- <button class="login-btn"><img src="../../../resources/img/img144_75s.png" alt=""> |
|
241 |
- <p class="title" style="color: #fff;">a</p> |
|
242 |
- </button> |
|
243 |
- <button class="login-btn"><img src="../../../resources/img/img145_75s.png" alt=""> |
|
244 |
- <p class="title mt20" style="color: #fff;">a</p> |
|
245 |
- </button> |
|
245 |
+ <div class="flex-column" style="gap: 10px;"> |
|
246 |
+ <button class="login-btn"><img src="../../../resources/img/img141_75s.png" alt=""> |
|
247 |
+ <p class="title">a</p> |
|
248 |
+ </button> |
|
249 |
+ <button class="login-btn"><img src="../../../resources/img/img152_75s_01.png" alt=""> |
|
250 |
+ <p class="title">a</p> |
|
251 |
+ </button> |
|
252 |
+ <button class="login-btn"><img src="../../../resources/img/img144_75s.png" alt=""> |
|
253 |
+ <p class="title" style="color: #fff;">a</p> |
|
254 |
+ </button> |
|
255 |
+ <button class="login-btn"><img src="../../../resources/img/img145_75s.png" alt=""> |
|
256 |
+ <p class="title mt20" style="color: #fff;">a</p> |
|
257 |
+ </button> |
|
258 |
+ </div> |
|
246 | 259 |
</div> |
247 | 260 |
</div> |
248 | 261 |
</div> |
249 |
- <!-- <div class="box"> |
|
250 |
- <div style="width: 100%;"> |
|
251 |
- <div id="container"> |
|
252 |
- <canvas ref="canvas"></canvas> |
|
253 |
- </div> |
|
254 |
- </div> |
|
255 |
- </div> --> |
|
256 |
- |
|
257 |
- </div> |
|
258 |
- </article> |
|
262 |
+ </article> |
|
263 |
+ </div> |
|
264 |
+ </div> |
|
259 | 265 |
|
260 | 266 |
</div> |
261 | 267 |
<div class="complete-wrap mt90 myphoto"> |
262 | 268 |
<h2 class="mb40">이 단원을 끝낸 친구들</h2> |
263 | 269 |
<article class=" flex-column" style="gap: 5px;"> |
264 | 270 |
<div class="flex" style="gap: 5px;"> |
265 |
- <div @click="buttonSearch2" class="photo" ><img src="../../../resources/img/img143_75s.png" alt=""></div> |
|
271 |
+ <div @click="buttonSearch2" class="photo"><img src="../../../resources/img/img143_75s.png" alt=""></div> |
|
266 | 272 |
<div @click="buttonSearch" class="photo"><img src="../../../resources/img/img143_75s.png" alt=""></div> |
267 | 273 |
</div> |
268 | 274 |
</article> |
... | ... | @@ -275,29 +281,16 @@ |
275 | 281 |
<svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon> |
276 | 282 |
</button> |
277 | 283 |
</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 | 284 |
</div> |
292 | 285 |
</article> |
293 | 286 |
</div> |
294 |
- |
|
295 | 287 |
</div> |
296 | 288 |
</template> |
297 | 289 |
|
298 | 290 |
<script> |
299 | 291 |
import SvgIcon from '@jamescoyle/vue-icon'; |
300 | 292 |
import { mdiMagnify, mdiHeart, mdiWindowClose } from '@mdi/js'; |
293 |
+import axios from 'axios'; |
|
301 | 294 |
|
302 | 295 |
export default { |
303 | 296 |
data() { |
... | ... | @@ -321,6 +314,7 @@ |
321 | 314 |
imgSrc2: 'client/resources/img/btn21_75s_click.png' //스티커 선택됨 |
322 | 315 |
}, |
323 | 316 |
], |
317 |
+ mdiMagnify: mdiMagnify, |
|
324 | 318 |
mdiWindowClose: mdiWindowClose, |
325 | 319 |
mdiHeart: mdiHeart, |
326 | 320 |
showModal: false, |
... | ... | @@ -353,19 +347,54 @@ |
353 | 347 |
canvasRect: { |
354 | 348 |
topLeft: { x: 0, y: 0 }, |
355 | 349 |
bottomRight: { x: 0, y: 0 } |
356 |
- } |
|
350 |
+ }, |
|
357 | 351 |
|
352 |
+ |
|
353 |
+ roadViewTF : false, |
|
354 |
+ roadmapData: [], |
|
358 | 355 |
} |
359 | 356 |
}, |
360 | 357 |
methods: { |
358 |
+ fetchRoadmapData() { |
|
359 |
+ const vm = this; |
|
360 |
+ axios({ |
|
361 |
+ url: "/unitLearning/find.json", |
|
362 |
+ method: "post", |
|
363 |
+ headers: { |
|
364 |
+ "Content-Type": "application/json; charset=UTF-8", |
|
365 |
+ }, |
|
366 |
+ data: { |
|
367 |
+ unit_id: "UNIT_000000000000001", |
|
368 |
+ book_id: "BOOK_000000000000004" |
|
369 |
+ } |
|
370 |
+ }) |
|
371 |
+ .then(response => { |
|
372 |
+ console.log("roadmap - response : ", response.data); |
|
373 |
+ vm.roadmapData = response.data; |
|
374 |
+ vm.roadViewTF = vm.roadmapData.length > 0; |
|
375 |
+ }) |
|
376 |
+ .catch(error => { |
|
377 |
+ console.error("Error fetching roadmap data:", error); |
|
378 |
+ }); |
|
379 |
+ }, |
|
380 |
+ getNonNullColumn(item) { |
|
381 |
+ if (item.prblm_id !== null) return '문제'; |
|
382 |
+ if (item.wd_book_id !== null) return '단어장'; |
|
383 |
+ if (item.text_id !== null) return '지문'; |
|
384 |
+ if (item.eval_id !== null) return '평가'; |
|
385 |
+ return ''; |
|
386 |
+ }, |
|
361 | 387 |
toggleImage(index) { |
362 | 388 |
this.items[index].isSecondImageVisible = !this.items[index].isSecondImageVisible; |
363 | 389 |
}, |
364 | 390 |
toggleImageAndShowPopup(index, dataNum) { |
365 | 391 |
this.toggleImage(index); |
366 |
- if (dataNum === '11') { // 최종 평가 버튼 클릭 시 |
|
367 |
- this.searchOpen2 = true; // 모달창 열기 |
|
392 |
+ if (dataNum === '11') { |
|
393 |
+ this.searchOpen2 = true; |
|
368 | 394 |
} |
395 |
+ }, |
|
396 |
+ ShowPopup() { |
|
397 |
+ this.searchOpen2 = true; // 촬영 여부 묻는 모달창 열기 |
|
369 | 398 |
}, |
370 | 399 |
updateContent(index) { |
371 | 400 |
this.selectedIndex = index; |
... | ... | @@ -401,12 +430,14 @@ |
401 | 430 |
console.log("error>>>>>>>>", error); |
402 | 431 |
}); |
403 | 432 |
}, |
404 |
- closeModal() { //웹캠 팝업 닫기 |
|
433 |
+ closeModal() { //웹캠 및 모든 팝업 닫기 |
|
405 | 434 |
// this.showModal = false; |
435 |
+ this.searchOpen = false; |
|
406 | 436 |
this.searchOpen2 = false; |
407 | 437 |
this.showCameraModal = false; |
408 | 438 |
this.photoTaken = false; |
409 | 439 |
this.photo = null; |
440 |
+ this.showPhotoModal = false; |
|
410 | 441 |
|
411 | 442 |
//스트림 종료 |
412 | 443 |
if (this.stream) { |
... | ... | @@ -733,16 +764,34 @@ |
733 | 764 |
}); |
734 | 765 |
}; |
735 | 766 |
}, |
767 |
+ getNonNullColumn(item) { |
|
768 |
+ if (item.prblm_id !== null) return '문제'; |
|
769 |
+ if (item.wd_book_id !== null) return '단어장'; |
|
770 |
+ if (item.text_id !== null) return '지문'; |
|
771 |
+ if (item.eval_id !== null) return '평가'; |
|
772 |
+ return ''; |
|
773 |
+ }, |
|
774 |
+ showConfirm(type) { |
|
775 |
+ let message = ''; |
|
776 |
+ if (type === 'cancel') { |
|
777 |
+ message = '삭제하시겠습니까?'; |
|
778 |
+ } else if (type === 'reset') { |
|
779 |
+ message = '초기화하시겠습니까?'; |
|
780 |
+ } else if (type === 'save') { |
|
781 |
+ message = '등록하시겠습니까?'; |
|
782 |
+ } |
|
736 | 783 |
|
737 |
- |
|
784 |
+ if (confirm(message)) { |
|
785 |
+ this.goBack(); |
|
786 |
+ } |
|
787 |
+ }, |
|
738 | 788 |
}, |
739 | 789 |
components: { |
740 | 790 |
SvgIcon, |
741 | 791 |
}, |
742 | 792 |
mounted() { |
743 | 793 |
console.log('main mounted'); |
744 |
- |
|
745 |
- |
|
794 |
+ this.fetchRoadmapData(); |
|
746 | 795 |
}, |
747 | 796 |
computed() { |
748 | 797 |
|
... | ... | @@ -841,4 +890,8 @@ |
841 | 890 |
.toolbar input { |
842 | 891 |
margin: 5px; |
843 | 892 |
} |
893 |
+ |
|
894 |
+.rabbit-end { |
|
895 |
+ cursor: pointer; |
|
896 |
+} |
|
844 | 897 |
</style>(No newline at end of file) |
--- client/views/pages/main/MyPage.vue
+++ client/views/pages/main/MyPage.vue
... | ... | @@ -98,7 +98,7 @@ |
98 | 98 |
</div> |
99 | 99 |
<div class="box " style="gap: 10px;"> |
100 | 100 |
<div><img src="../../../resources/img/img196_12p.png" alt=""></div> |
101 |
- <P class="title2 mt10">현재 30명 중 <em class="blue">2등</em>입니다.</P> |
|
101 |
+ <P class="title2 mt10">현재 {{ stdCount }}명 중 <em class="blue">{{ rank }}등</em>입니다.</P> |
|
102 | 102 |
</div> |
103 | 103 |
</div> |
104 | 104 |
<div class="textbook book-navy"> |
... | ... | @@ -121,11 +121,15 @@ |
121 | 121 |
import { mdiMagnify, } from '@mdi/js'; |
122 | 122 |
import { mdilArrowRight } from '@mdi/light-js'; |
123 | 123 |
import ProgressBar from '../../component/ProgressBar.vue'; |
124 |
+import axios from "axios"; |
|
124 | 125 |
|
125 | 126 |
|
126 | 127 |
export default { |
127 | 128 |
data() { |
128 | 129 |
return { |
130 |
+ stdCount : 0, |
|
131 |
+ rank: 0, |
|
132 |
+ |
|
129 | 133 |
mdiMagnify: mdiMagnify, |
130 | 134 |
mdilArrowRight: mdilArrowRight, |
131 | 135 |
timer: "00:00", |
... | ... | @@ -133,6 +137,51 @@ |
133 | 137 |
} |
134 | 138 |
}, |
135 | 139 |
methods: { |
140 |
+ classStdCount: function () { |
|
141 |
+ const vm = this; |
|
142 |
+ axios({ |
|
143 |
+ url: "/userclass/classStdCount.json", |
|
144 |
+ method: "post", |
|
145 |
+ headers:{ |
|
146 |
+ "Content-Type": "application/json; charset=UTF-8", |
|
147 |
+ }, |
|
148 |
+ data: { |
|
149 |
+ sclsId:"1" |
|
150 |
+ } |
|
151 |
+ }) |
|
152 |
+ .then(function (response) { |
|
153 |
+ console.log("classStdCount - response : ", response.data); |
|
154 |
+ vm.stdCount = response.data; |
|
155 |
+ }) |
|
156 |
+ .catch(function (error) { |
|
157 |
+ console.log("classStdCount - error : ", error); |
|
158 |
+ alert("반 학생 수 조회에 오류가 발생했습니다."); |
|
159 |
+ }); |
|
160 |
+ }, |
|
161 |
+ |
|
162 |
+ photoRankByLikeData: function () { |
|
163 |
+ const vm = this; |
|
164 |
+ axios({ |
|
165 |
+ url: "/photo/photoRankByLikeData.json", |
|
166 |
+ method: "post", |
|
167 |
+ headers:{ |
|
168 |
+ "Content-Type": "application/json; charset=UTF-8", |
|
169 |
+ }, |
|
170 |
+ data: { |
|
171 |
+ sclsId:"1", |
|
172 |
+ stdId:"1" |
|
173 |
+ } |
|
174 |
+ }) |
|
175 |
+ .then(function (response) { |
|
176 |
+ console.log("Rank - response : ", response.data); |
|
177 |
+ vm.rank = response.data; |
|
178 |
+ }) |
|
179 |
+ .catch(function (error) { |
|
180 |
+ console.log("Rank - error : ", error); |
|
181 |
+ alert("학생 사진 랭킹 조회에 오류가 발생했습니다."); |
|
182 |
+ }); |
|
183 |
+ }, |
|
184 |
+ |
|
136 | 185 |
goToPage(page) { |
137 | 186 |
this.$router.push({ name: page }); |
138 | 187 |
}, |
... | ... | @@ -168,6 +217,8 @@ |
168 | 217 |
}, |
169 | 218 |
mounted() { |
170 | 219 |
console.log('Main2 mounted'); |
220 |
+ this.classStdCount(); |
|
221 |
+ this.photoRankByLikeData(); |
|
171 | 222 |
} |
172 | 223 |
} |
173 | 224 |
</script> |
--- client/views/pages/main/MyPlan.vue
+++ client/views/pages/main/MyPlan.vue
... | ... | @@ -1,31 +1,72 @@ |
1 | 1 |
<template> |
2 |
- <div class="myplan"> |
|
3 |
- <div class="title-box flex justify-between mb40"> |
|
4 |
- <p class="title">오늘 공부할 내용을 확인해봅시다.</p> |
|
5 |
- <!-- <select name="" id=""> |
|
6 |
- <option value="">A반</option> |
|
7 |
- </select> --> |
|
8 |
- </div> |
|
9 |
- <div class="wrap" style="border-radius: 0; min-height: 197px;"> |
|
10 |
- <p class="title1"> 오늘 학습할 내용이 없습니다.</p> |
|
11 |
- </div> |
|
12 |
- <div class="yellow-box mt30"> |
|
13 |
- <div class="title-box flex justify-between align-center"> |
|
14 |
- <div > |
|
15 |
- <p class="title">오늘 공부를 계획해봅시다.</p> |
|
16 |
- <p class="title1 mt20"> 스스로 학습 일정을 바꿔볼까요?</p> |
|
2 |
+ |
|
3 |
+ <div class="main"> |
|
4 |
+ <div class="myplan"> |
|
5 |
+ <div class="title-box flex justify-between mb40"> |
|
6 |
+ <p class="title">오늘 공부할 내용을 확인해봅시다.</p> |
|
7 |
+ </div> |
|
8 |
+ <div class="wrap" style="border-radius: 0; min-height: 197px;"> |
|
9 |
+ <p class="title1" v-if="!schedules || schedules.length === 0"> 오늘 학습할 내용이 없습니다.</p> |
|
10 |
+ |
|
11 |
+ <div class="flex-column" style="gap: 20px;" v-else v-for="(schedule, index) in schedules" :key="index"> |
|
12 |
+ <div class="flex justify-between align-center" style="gap: 70px;"> |
|
13 |
+ <div><img src="../../../resources/img/img217_22s.png" alt=""></div> |
|
14 |
+ <div class="wrap cs-pt" :class="{ 'cs-pt-clicked': isClicked }" style="width: 100%;"> |
|
15 |
+ <div class="text-lf flex justify-between align-center"> |
|
16 |
+ <div> |
|
17 |
+ <p class="title2">grade 3</p> |
|
18 |
+ <div class="flex align-center mb10" style="gap: 10px;"> |
|
19 |
+ <p class="title2"><em class="gray-bd">{{ schedule.schdl_unit }}교시</em></p> |
|
20 |
+ <p class="title1">{{ schedule.schedule_time }}</p> |
|
21 |
+ </div> |
|
22 |
+ <div class="title-box mb10"> |
|
23 |
+ <span class="title">{{schedule.unit_nm}}</span> |
|
24 |
+ </div> |
|
25 |
+ <p class="title2">{{ schedule.book_nm }}</p> |
|
26 |
+ <!-- <p class="title2">wirte a</p> --> |
|
27 |
+ </div> |
|
28 |
+ <div><img src="../../../resources/img/img214_19s.png" alt=""></div> |
|
29 |
+ </div> |
|
30 |
+ </div> |
|
31 |
+ </div> |
|
17 | 32 |
</div> |
18 |
- <button type="button" title="바로가기" class="yellow-btn" @click="goToPage('MyPlan2')"> |
|
19 |
- 바로가기 |
|
20 |
- </button> |
|
33 |
+ </div> |
|
34 |
+ |
|
35 |
+ <div class="yellow-box mt30"> |
|
36 |
+ <div class="title-box flex justify-between align-center"> |
|
37 |
+ <div> |
|
38 |
+ <p class="title">오늘 공부를 계획해봅시다.</p> |
|
39 |
+ <p class="title1 mt20"> 스스로 학습 일정을 바꿔볼까요?</p> |
|
40 |
+ </div> |
|
41 |
+ <button type="button" title="바로가기" class="yellow-btn" @click="goToPage('MyPlan2')"> |
|
42 |
+ 바로가기 |
|
43 |
+ </button> |
|
44 |
+ </div> |
|
45 |
+ </div> |
|
46 |
+ <div class="wrap mt30"> |
|
47 |
+ <p class="title1 mb20"> AI 맞춤형 학습 코스는 어떨까요?</p> |
|
48 |
+ <div class="imgGroup flex justify-between"> |
|
49 |
+ <div class="text-lf" @click="goToPage('AIDashboard')"> |
|
50 |
+ <img src="../../../resources/img/img215_22s.png" alt=""> |
|
51 |
+ <p class="title2 mt10">추천 학습 단원</p> |
|
52 |
+ </div> |
|
53 |
+ <div class="text-lf"> |
|
54 |
+ <img src="../../../resources/img/img215_22s.png" alt=""> |
|
55 |
+ <p class="title2 mt10">추천 학습 단원</p> |
|
56 |
+ </div> |
|
57 |
+ <div class="text-lf"> |
|
58 |
+ <img src="../../../resources/img/img215_22s.png" alt=""> |
|
59 |
+ <p class="title2 mt10">추천 학습 단원</p> |
|
60 |
+ </div> |
|
61 |
+ </div> |
|
21 | 62 |
</div> |
22 | 63 |
</div> |
23 | 64 |
</div> |
24 | 65 |
|
25 |
- |
|
26 | 66 |
</template> |
27 | 67 |
|
28 | 68 |
<script> |
69 |
+import axios from "axios"; |
|
29 | 70 |
import SvgIcon from '@jamescoyle/vue-icon'; |
30 | 71 |
import { mdiMagnify, mdiWindowClose } from '@mdi/js'; |
31 | 72 |
|
... | ... | @@ -37,6 +78,16 @@ |
37 | 78 |
mdiWindowClose: mdiWindowClose, |
38 | 79 |
showModal: false, |
39 | 80 |
searchOpen: false, |
81 |
+ schedules: [], |
|
82 |
+ stdId: "2", |
|
83 |
+ timeList: [ |
|
84 |
+ { label: "1교시", time: "08:00 ~ 09:00", value: "1" }, |
|
85 |
+ { label: "2교시", time: "09:00 ~ 10:00", value: "2" }, |
|
86 |
+ { label: "3교시", time: "10:00 ~ 11:00", value: "3" }, |
|
87 |
+ { label: "4교시", time: "11:00 ~ 12:00", value: "4" }, |
|
88 |
+ { label: "5교시", time: "13:00 ~ 14:00", value: "5" }, |
|
89 |
+ { label: "6교시", time: "14:00 ~ 15:00", value: "6" }, |
|
90 |
+ ], |
|
40 | 91 |
} |
41 | 92 |
}, |
42 | 93 |
methods: { |
... | ... | @@ -66,8 +117,27 @@ |
66 | 117 |
closeBtn() { |
67 | 118 |
this.searchOpen = false; |
68 | 119 |
|
69 |
- }, |
|
70 |
- |
|
120 |
+ }, fetchSchedules() { |
|
121 |
+ axios.post("/schedule/selectSchedule.json", { stdId: this.stdId }, { |
|
122 |
+ headers: { |
|
123 |
+ "Content-Type": "application/json; charset=UTF-8", |
|
124 |
+ } |
|
125 |
+ }) |
|
126 |
+ .then(response => { |
|
127 |
+ this.schedules = response.data.map(schedule => { |
|
128 |
+ const matchingTime = this.timeList.find(time => time.value === schedule.schdl_unit); |
|
129 |
+ return { |
|
130 |
+ ...schedule, |
|
131 |
+ schedule_time: matchingTime ? matchingTime.time : "시간 정보 없음" |
|
132 |
+ }; |
|
133 |
+ }); |
|
134 |
+ console.log(this.schedules); |
|
135 |
+ }) |
|
136 |
+ .catch(error => { |
|
137 |
+ console.error("fetchUnits - error:", error); |
|
138 |
+ alert("단원 목록을 불러오는 중 오류가 발생했습니다."); |
|
139 |
+ }); |
|
140 |
+ } |
|
71 | 141 |
}, |
72 | 142 |
watch: { |
73 | 143 |
|
... | ... | @@ -80,6 +150,7 @@ |
80 | 150 |
}, |
81 | 151 |
mounted() { |
82 | 152 |
console.log('Main2 mounted'); |
153 |
+ this.fetchSchedules(); |
|
83 | 154 |
} |
84 | 155 |
} |
85 | 156 |
</script>(No newline at end of file) |
--- client/views/pages/main/MyPlan2.vue
+++ client/views/pages/main/MyPlan2.vue
... | ... | @@ -2,19 +2,13 @@ |
2 | 2 |
<div class=" main"> |
3 | 3 |
<div class="myplan"> |
4 | 4 |
<div class="title-box flex justify-between mb40"> |
5 |
- <p class="title">오늘 공부를 계획해봅시다.</p> |
|
6 |
- <!-- <select name="" id=""> |
|
7 |
- <option value="">A반</option> |
|
8 |
- </select> --> |
|
9 |
- </div> |
|
5 |
+ <p class="title">오늘 공부를 계획해봅시다.</p> |
|
6 |
+ </div> |
|
10 | 7 |
<div class="wrap"> |
11 | 8 |
<p class="title1"> 학습 교재를 선택합시다.</p> |
12 | 9 |
<div class="search-wrap flex justify-end mb20 mt20"> |
13 |
- <!-- <select name="" id="" class="mr10 data-wrap"> |
|
14 |
- <option value="">전체</option> |
|
15 |
- </select> --> |
|
16 |
- <input class="data-wrap" type="text" placeholder="검색하세요."> |
|
17 |
- <button type="button" title="위원회 검색"> |
|
10 |
+ <input class="data-wrap" type="text" placeholder="검색하세요." v-model="keyword"> |
|
11 |
+ <button type="button" title="위원회 검색" @click="searchBook"> |
|
18 | 12 |
<img src="../../../resources/img/look_t.png" alt=""> |
19 | 13 |
</button> |
20 | 14 |
</div> |
... | ... | @@ -26,93 +20,53 @@ |
26 | 20 |
<col style="width: 70%;"> |
27 | 21 |
<col style="width: 15%;"> |
28 | 22 |
</colgroup> |
29 |
- <tr> |
|
30 |
- <td><input type="checkbox" class="ui-checkbox mr10"></td> |
|
23 |
+ <tr v-for="(book, index) in books" :key="index"> |
|
24 |
+ <td><input type="checkbox" class="ui-checkbox mr10" v-model="book.isSelected"></td> |
|
31 | 25 |
<td><img src="../../../resources/img/img214_19s.png" alt=""></td> |
32 | 26 |
<td class="text-lf"> |
33 |
- <p class="title1">the best</p> |
|
34 |
- <p class="title2">wirte a</p> |
|
27 |
+ <p class="title1">{{ book.book_nm }}</p> |
|
28 |
+ <p class="title2">grade 3</p> |
|
35 | 29 |
</td> |
36 |
- <td> <button type="button" title="확인" class="new-btn" @click="buttonSearch"> |
|
37 |
- 시간입력 |
|
38 |
- </button></td> |
|
39 |
- </tr> |
|
40 |
- <tr> |
|
41 |
- <td><input type="checkbox" class="ui-checkbox mr10"></td> |
|
42 |
- <td><img src="../../../resources/img/img214_19s.png" alt=""></td> |
|
43 |
- <td class="text-lf"> |
|
44 |
- <p class="title1">the best</p> |
|
45 |
- <p class="title2">wirte a</p> |
|
46 |
- </td> |
|
47 |
- <td> <button type="button" title="확인" class="new-btn" @click="buttonSearch"> |
|
30 |
+ <td> <button type="button" title="확인" class="new-btn" |
|
31 |
+ @click="buttonSearch(book.book_nm, book.book_id)"> |
|
48 | 32 |
시간입력 |
49 | 33 |
</button></td> |
50 | 34 |
</tr> |
51 | 35 |
</table> |
52 | 36 |
<div class="flex justify-end"> |
53 |
- <button type="button" title="선택하기" class="yellow-btn mt30"> |
|
37 |
+ <button type="button" title="선택하기" class="yellow-btn mt30" @click="checkSchedule"> |
|
54 | 38 |
선택하기 |
55 | 39 |
</button> |
56 | 40 |
</div> |
57 | 41 |
</div> |
58 | 42 |
</div> |
59 |
- <div class="wrap mt30"> |
|
60 |
- <p class="title1 mb20"> AI 맞춤형 학습 코스는 어떨까요?</p> |
|
61 |
- <div class="imgGroup flex justify-between"> |
|
62 |
- <div class="text-lf"> |
|
63 |
- <img src="../../../resources/img/img215_22s.png" alt=""> |
|
64 |
- <p class="title2 mt10">추천 학습 단원</p> |
|
65 |
- </div> |
|
66 |
- <div class="text-lf"> |
|
67 |
- <img src="../../../resources/img/img215_22s.png" alt=""> |
|
68 |
- <p class="title2 mt10">추천 학습 단원</p> |
|
69 |
- </div> |
|
70 |
- <div class="text-lf"> |
|
71 |
- <img src="../../../resources/img/img215_22s.png" alt=""> |
|
72 |
- <p class="title2 mt10">추천 학습 단원</p> |
|
73 |
- </div> |
|
74 |
- </div> |
|
75 |
- </div> |
|
43 |
+ |
|
76 | 44 |
<div class="wrap mt30"> |
77 | 45 |
<p class="title1 mb20">학습일정을 확인해봅시다. </p> |
78 |
- <div class="flex-column" style="gap: 20px;"> |
|
46 |
+ <div class="flex-column" style="gap: 20px;" v-for="(book, index) in DataArray" :key="index"> |
|
79 | 47 |
<div class=" flex justify-between align-center " style="gap: 70px;"> |
80 | 48 |
<div><img src="../../../resources/img/img217_22s.png" alt=""></div> |
81 |
- <div class="wrap cs-pt" :class="{ 'cs-pt-clicked': isClicked }" @click="toggleClicked" style="width: 100%;"> |
|
49 |
+ <div class="wrap cs-pt" :class="{ 'cs-pt-clicked': isClicked }" style="width: 100%;"> |
|
82 | 50 |
<div class="text-lf flex justify-between align-center "> |
83 |
- <div> |
|
51 |
+ <div> |
|
84 | 52 |
<div class="flex align-center mb10" style="gap: 10px;"> |
85 |
- <p class="title2"><em class="gray-bd">1교시</em></p> |
|
86 |
- <p class="title1">9:00</p> |
|
87 |
- <p class="title1">~</p> |
|
88 |
- <p class="title1">10:00</p> |
|
53 |
+ <p class="title2"><em class="gray-bd">{{ book.scheduleUnit }}교시</em></p> |
|
54 |
+ <p class="title1">{{ book.scheduleTime }}</p> |
|
89 | 55 |
</div> |
90 |
- <div class="title-box mb10"> <span class="title">the best</span></div> |
|
56 |
+ <div class="title-box mb10"> <span class="title">{{ book.bookNm }}</span></div> |
|
91 | 57 |
<p class="title2">wirte a</p> |
92 |
- </div> |
|
93 |
- <div class=""> <img src="../../../resources/img/img214_19s.png" alt=""></div> |
|
58 |
+ </div> |
|
59 |
+ <div class=""> <img src="../../../resources/img/img214_19s.png" alt=""></div> |
|
94 | 60 |
</div> |
95 | 61 |
</div> |
96 | 62 |
</div> |
97 |
- |
|
98 | 63 |
</div> |
99 | 64 |
<div class="flex justify-end"> |
100 |
- <button type="button" title="선택하기" class="yellow-btn mt30"> |
|
101 |
- 선택하기 |
|
102 |
- </button> |
|
103 |
- </div> |
|
104 |
- </div> |
|
105 |
- <!-- <div class="yellow-box mt30"> |
|
106 |
- <div class="title-box flex justify-between align-center"> |
|
107 |
- <div> |
|
108 |
- <p class="title">학습 일정 변경</p> |
|
109 |
- <p class="title1 mt20"> 스스로 학습 일정을 바꿔볼까요?</p> |
|
110 |
- </div> |
|
111 |
- <button type="button" title="바로가기" class="yellow-btn" @click="goToPage('MyPlan2')"> |
|
112 |
- 바로가기 |
|
113 |
- </button> |
|
65 |
+ <button type="button" title="저장하기" class="yellow-btn mt30" @click="insertSchedule"> |
|
66 |
+ 저장하기 |
|
67 |
+ </button> |
|
114 | 68 |
</div> |
115 |
- </div> --> |
|
69 |
+ </div> |
|
116 | 70 |
<div class="popup-wrap" v-show="searchOpen"> |
117 | 71 |
<div class="popup-box "> |
118 | 72 |
<div class="flex mb10 justify-between"> |
... | ... | @@ -121,74 +75,25 @@ |
121 | 75 |
<svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon> |
122 | 76 |
</button> |
123 | 77 |
</div> |
124 |
- <article class=" mb20 flex-column " style="gap: 20px;"> |
|
125 |
- <div class="flex justify-between bg-gray"> |
|
126 |
- <div class="flex align-center"> |
|
127 |
- <div><input type="checkbox" class="ui-checkbox mr20"></div> |
|
128 |
- <div class="text-lf"> |
|
129 |
- <p class="title1">2교시</p> |
|
78 |
+ <article class="mb20 flex-column" style="gap: 20px;"> |
|
79 |
+ <div v-for="(item, index) in timeList" :key="item.value" class="flex justify-between bg-gray"> |
|
80 |
+ <div class="flex items-center"> |
|
81 |
+ <div> |
|
82 |
+ <input type="checkbox" class="ui-checkbox mr20" v-model="checkedItems" |
|
83 |
+ :value="item" /> |
|
84 |
+ </div> |
|
85 |
+ <div class="text-lg"> |
|
86 |
+ <p class="title1">{{ item.label }}</p> |
|
130 | 87 |
</div> |
131 | 88 |
</div> |
132 |
- <div> |
|
133 |
- <div class="flex align-center " style="gap: 10px;"><img |
|
134 |
- src="../../../resources/img/img215_20s.png" alt=""> |
|
135 |
- <p class="title1">9:00</p> |
|
136 |
- <p class="title1">~</p> |
|
137 |
- <p class="title1">10:00</p> |
|
138 |
- </div> |
|
139 |
- </div> |
|
140 |
- </div> |
|
141 |
- <div class="flex justify-between bg-gray"> |
|
142 |
- <div class="flex align-center"> |
|
143 |
- <div><input type="checkbox" class="ui-checkbox mr20"></div> |
|
144 |
- <div class="text-lf"> |
|
145 |
- <p class="title1">2교시</p> |
|
146 |
- </div> |
|
147 |
- </div> |
|
148 |
- <div> |
|
149 |
- <div class="flex align-center " style="gap: 10px;"><img |
|
150 |
- src="../../../resources/img/img215_20s.png" alt=""> |
|
151 |
- <p class="title1">9:00</p> |
|
152 |
- <p class="title1">~</p> |
|
153 |
- <p class="title1">10:00</p> |
|
154 |
- </div> |
|
155 |
- </div> |
|
156 |
- </div> |
|
157 |
- <div class="flex justify-between bg-gray"> |
|
158 |
- <div class="flex align-center"> |
|
159 |
- <div><input type="checkbox" class="ui-checkbox mr20"></div> |
|
160 |
- <div class="text-lf"> |
|
161 |
- <p class="title1">2교시</p> |
|
162 |
- </div> |
|
163 |
- </div> |
|
164 |
- <div> |
|
165 |
- <div class="flex align-center " style="gap: 10px;"><img |
|
166 |
- src="../../../resources/img/img215_20s.png" alt=""> |
|
167 |
- <p class="title1">9:00</p> |
|
168 |
- <p class="title1">~</p> |
|
169 |
- <p class="title1">10:00</p> |
|
170 |
- </div> |
|
171 |
- </div> |
|
172 |
- </div> |
|
173 |
- <div class="flex justify-between bg-gray"> |
|
174 |
- <div class="flex align-center"> |
|
175 |
- <div><input type="checkbox" class="ui-checkbox mr20"></div> |
|
176 |
- <div class="text-lf"> |
|
177 |
- <p class="title1">2교시</p> |
|
178 |
- </div> |
|
179 |
- </div> |
|
180 |
- <div> |
|
181 |
- <div class="flex align-center " style="gap: 10px;"><img |
|
182 |
- src="../../../resources/img/img215_20s.png" alt=""> |
|
183 |
- <p class="title1">9:00</p> |
|
184 |
- <p class="title1">~</p> |
|
185 |
- <p class="title1">10:00</p> |
|
186 |
- </div> |
|
89 |
+ <div class="flex align-center" style="gap: 10px;"> |
|
90 |
+ <img src="../../../resources/img/img215_20s.png" alt="" /> |
|
91 |
+ <p class="title1">{{ item.time }}</p> |
|
187 | 92 |
</div> |
188 | 93 |
</div> |
189 | 94 |
</article> |
190 | 95 |
<div class="flex justify-center mt20"> |
191 |
- <button type="button" title="선택" class="new-btn"> |
|
96 |
+ <button type="button" title="선택" class="new-btn" @click="timePick"> |
|
192 | 97 |
선택 |
193 | 98 |
</button> |
194 | 99 |
</div> |
... | ... | @@ -200,9 +105,9 @@ |
200 | 105 |
</template> |
201 | 106 |
|
202 | 107 |
<script> |
108 |
+import axios from "axios"; |
|
203 | 109 |
import SvgIcon from '@jamescoyle/vue-icon'; |
204 | 110 |
import { mdiMagnify, mdiWindowClose } from '@mdi/js'; |
205 |
- |
|
206 | 111 |
|
207 | 112 |
export default { |
208 | 113 |
data() { |
... | ... | @@ -212,12 +117,28 @@ |
212 | 117 |
mdiWindowClose: mdiWindowClose, |
213 | 118 |
showModal: false, |
214 | 119 |
searchOpen: false, |
120 |
+ books: [], |
|
121 |
+ timeList: [ |
|
122 |
+ { label: "1교시", time: "08:00 ~ 09:00", value: "1" }, |
|
123 |
+ { label: "2교시", time: "09:00 ~ 10:00", value: "2" }, |
|
124 |
+ { label: "3교시", time: "10:00 ~ 11:00", value: "3" }, |
|
125 |
+ { label: "4교시", time: "11:00 ~ 12:00", value: "4" }, |
|
126 |
+ { label: "5교시", time: "13:00 ~ 14:00", value: "5" }, |
|
127 |
+ { label: "6교시", time: "14:00 ~ 15:00", value: "6" }, |
|
128 |
+ ], |
|
129 |
+ checkedItems: [], |
|
130 |
+ newDataArray: [], |
|
131 |
+ DataArray: [], |
|
132 |
+ nowName: "", |
|
133 |
+ nowID: "", |
|
134 |
+ keyword: "", |
|
135 |
+ unitList: [] |
|
215 | 136 |
} |
216 | 137 |
}, |
217 | 138 |
methods: { |
218 | 139 |
toggleClicked() { |
219 |
- this.isClicked = !this.isClicked; |
|
220 |
- }, |
|
140 |
+ this.isClicked = !this.isClicked; |
|
141 |
+ }, |
|
221 | 142 |
goToPage(page) { |
222 | 143 |
this.$router.push({ name: page }); |
223 | 144 |
}, |
... | ... | @@ -238,32 +159,130 @@ |
238 | 159 |
closeModal() { |
239 | 160 |
this.showModal = false; |
240 | 161 |
}, |
241 |
- buttonSearch() { |
|
162 |
+ buttonSearch(book_nm, book_id) { |
|
163 |
+ this.nowID = book_id |
|
164 |
+ this.nowName = book_nm |
|
242 | 165 |
this.searchOpen = true; |
243 | 166 |
}, |
244 | 167 |
closeBtn() { |
245 | 168 |
this.searchOpen = false; |
246 |
- |
|
247 | 169 |
}, |
170 |
+ timePick() { |
|
171 |
+ const selectedBooks = this.books.filter(book => book.isSelected); |
|
172 |
+ if (selectedBooks.length <= 0) { |
|
173 |
+ alert('교재를 선택해 주세요.'); |
|
174 |
+ } else { |
|
175 |
+ if (this.checkedItems.length === 1) { |
|
176 |
+ if (this.unitList.find(item => item === this.checkedItems[0])) { |
|
177 |
+ alert('이미 지정된 시간입니다.'); |
|
178 |
+ } else { |
|
179 |
+ this.addToNewDataArray(this.checkedItems[0]); |
|
180 |
+ this.searchOpen = false; |
|
181 |
+ this.nowID = "" |
|
182 |
+ this.nowName = "" |
|
183 |
+ this.unitList.push(this.checkedItems[0]) |
|
184 |
+ this.checkedItems = [] |
|
185 |
+ } |
|
186 |
+ } else if (this.checkedItems.length > 1) { |
|
187 |
+ alert('시간은 하나만 선택할 수 있습니다.'); |
|
188 |
+ } else if (this.checkedItems.length <= 0) { |
|
189 |
+ alert('시간을 선택해 주세요.'); |
|
190 |
+ } |
|
191 |
+ } |
|
192 |
+ }, |
|
193 |
+ addToNewDataArray(selectedTime) { |
|
194 |
+ if (!this.newDataArray) { |
|
195 |
+ this.newDataArray = []; |
|
196 |
+ } |
|
197 |
+ const newEntry = { |
|
198 |
+ bookNm: this.nowName, |
|
199 |
+ scheduleTime: selectedTime.time, |
|
200 |
+ scheduleUnit: selectedTime.value, |
|
201 |
+ bookId: this.nowID, |
|
202 |
+ stdId: "2" |
|
203 |
+ }; |
|
204 |
+ this.newDataArray.push(newEntry); |
|
205 |
+ }, |
|
206 |
+ async insertSchedule() { |
|
248 | 207 |
|
208 |
+ this.newDataArray = this.DataArray.map(item => ({ |
|
209 |
+ scheduleUnit: item.scheduleUnit, |
|
210 |
+ bookId: item.bookId, |
|
211 |
+ stdId: item.stdId |
|
212 |
+ })); |
|
213 |
+ try { |
|
214 |
+ for (let data of this.newDataArray) { |
|
215 |
+ await axios.post('/schedule/insertSchedule.json', data); |
|
216 |
+ } |
|
217 |
+ alert('학습 일정이 등록됐어요!'); |
|
218 |
+ this.goToPage('MyPlan') |
|
219 |
+ } catch (error) { |
|
220 |
+ console.error('데이터 제출 중 오류 발생:', error); |
|
221 |
+ alert('데이터 제출 중 오류가 발생했습니다.'); |
|
222 |
+ } |
|
223 |
+ }, checkSchedule() { |
|
224 |
+ const sortedArray = this.newDataArray.slice().sort((a, b) => { |
|
225 |
+ return a.scheduleUnit - b.scheduleUnit; |
|
226 |
+ }); |
|
227 |
+ |
|
228 |
+ this.DataArray = sortedArray; |
|
229 |
+ }, |
|
230 |
+ fetchBooks() { |
|
231 |
+ axios({ |
|
232 |
+ url: "/book/findAll.json", |
|
233 |
+ method: "post", |
|
234 |
+ headers: { |
|
235 |
+ "Content-Type": "application/json; charset=UTF-8", |
|
236 |
+ }, |
|
237 |
+ }) |
|
238 |
+ .then(response => { |
|
239 |
+ this.books = response.data.map(book => ({ |
|
240 |
+ ...book, |
|
241 |
+ isSelected: false |
|
242 |
+ })); |
|
243 |
+ }) |
|
244 |
+ .catch(error => { |
|
245 |
+ console.error("fetchBooks - error: ", error); |
|
246 |
+ alert("교재 목록을 불러오는 중 오류가 발생했습니다."); |
|
247 |
+ }); |
|
248 |
+ }, |
|
249 |
+ searchBook() { |
|
250 |
+ this.books = "" |
|
251 |
+ axios.post("/book/search.json", { book_nm: this.keyword }, { |
|
252 |
+ headers: { |
|
253 |
+ "Content-Type": "application/json; charset=UTF-8", |
|
254 |
+ } |
|
255 |
+ }) |
|
256 |
+ .then(response => { |
|
257 |
+ this.books = response.data.list.map(book => ({ |
|
258 |
+ ...book, |
|
259 |
+ isSelected: false |
|
260 |
+ })); |
|
261 |
+ }) |
|
262 |
+ .catch(error => { |
|
263 |
+ console.error("fetchUnits - error:", error); |
|
264 |
+ alert("단원 목록을 불러오는 중 오류가 발생했습니다."); |
|
265 |
+ }); |
|
266 |
+ } |
|
249 | 267 |
}, |
250 | 268 |
watch: { |
251 |
- |
|
269 |
+ // Watchers for reactivity (if needed) |
|
252 | 270 |
}, |
253 | 271 |
computed: { |
254 |
- |
|
272 |
+ // Computed properties (if needed) |
|
255 | 273 |
}, |
256 | 274 |
components: { |
257 | 275 |
SvgIcon |
258 | 276 |
}, |
259 | 277 |
mounted() { |
260 | 278 |
console.log('Main2 mounted'); |
279 |
+ this.fetchBooks(); |
|
261 | 280 |
} |
262 | 281 |
} |
263 | 282 |
</script> |
283 |
+ |
|
264 | 284 |
<style scoped> |
265 | 285 |
.popup-box { |
266 | 286 |
width: 750px; |
267 |
- |
|
268 | 287 |
} |
269 | 288 |
</style>(No newline at end of file) |
+++ client/views/pages/parents/Barchart.vue
... | ... | @@ -0,0 +1,204 @@ |
1 | +<template> | |
2 | + <div ref="Barchart" style="width: 500px; height: 500px;"></div> | |
3 | + </template> | |
4 | + | |
5 | + <script> | |
6 | + import * as am5 from "@amcharts/amcharts5"; | |
7 | + import * as am5xy from "@amcharts/amcharts5/xy"; | |
8 | + import am5themes_Animated from "@amcharts/amcharts5/themes/Animated"; | |
9 | + | |
10 | + export default { | |
11 | + name: "Barchart", | |
12 | + mounted() { | |
13 | + this.createChart(); | |
14 | + }, | |
15 | + methods: { | |
16 | + createChart() { | |
17 | + // Initialize root | |
18 | + const root = am5.Root.new(this.$refs.Barchart); | |
19 | + | |
20 | + // Apply themes | |
21 | + const myTheme = am5.Theme.new(root); | |
22 | + myTheme.rule("Grid", ["base"]).setAll({ | |
23 | + strokeOpacity: 0.1, | |
24 | + }); | |
25 | + root.setThemes([ | |
26 | + am5themes_Animated.new(root), | |
27 | + myTheme, | |
28 | + ]); | |
29 | + | |
30 | + // Create chart | |
31 | + let chart = root.container.children.push( | |
32 | + am5xy.XYChart.new(root, { | |
33 | + panX: false, | |
34 | + panY: false, | |
35 | + wheelX: "none", | |
36 | + wheelY: "none", | |
37 | + paddingLeft: 0 | |
38 | + }) | |
39 | +); | |
40 | + | |
41 | + | |
42 | +// Create axes | |
43 | +// https://www.amcharts.com/docs/v5/charts/xy-chart/axes/ | |
44 | +let yRenderer = am5xy.AxisRendererY.new(root, { | |
45 | + minGridDistance: 30, | |
46 | + minorGridEnabled: true | |
47 | +}); | |
48 | +yRenderer.grid.template.set("location", 1); | |
49 | + | |
50 | +let yAxis = chart.yAxes.push( | |
51 | + am5xy.CategoryAxis.new(root, { | |
52 | + maxDeviation: 0, | |
53 | + categoryField: "country", | |
54 | + renderer: yRenderer | |
55 | + }) | |
56 | +); | |
57 | + | |
58 | +let xAxis = chart.xAxes.push( | |
59 | + am5xy.ValueAxis.new(root, { | |
60 | + maxDeviation: 0, | |
61 | + min: 0, | |
62 | + renderer: am5xy.AxisRendererX.new(root, { | |
63 | + visible: true, | |
64 | + strokeOpacity: 0.1, | |
65 | + minGridDistance: 80 | |
66 | + }) | |
67 | + }) | |
68 | +); | |
69 | + | |
70 | + | |
71 | +// Create series | |
72 | +// https://www.amcharts.com/docs/v5/charts/xy-chart/series/ | |
73 | +let series = chart.series.push( | |
74 | + am5xy.ColumnSeries.new(root, { | |
75 | + name: "Series 1", | |
76 | + xAxis: xAxis, | |
77 | + yAxis: yAxis, | |
78 | + valueXField: "value", | |
79 | + sequencedInterpolation: true, | |
80 | + categoryYField: "country" | |
81 | + }) | |
82 | +); | |
83 | + | |
84 | +let columnTemplate = series.columns.template; | |
85 | + | |
86 | +columnTemplate.setAll({ | |
87 | + draggable: true, | |
88 | + cursorOverStyle: "pointer", | |
89 | + tooltipText: "drag to rearrange", | |
90 | + cornerRadiusBR: 10, | |
91 | + cornerRadiusTR: 10, | |
92 | + strokeOpacity: 0 | |
93 | +}); | |
94 | +columnTemplate.adapters.add("fill", (fill, target) => { | |
95 | + return chart.get("colors").getIndex(series.columns.indexOf(target)); | |
96 | +}); | |
97 | + | |
98 | +columnTemplate.adapters.add("stroke", (stroke, target) => { | |
99 | + return chart.get("colors").getIndex(series.columns.indexOf(target)); | |
100 | +}); | |
101 | + | |
102 | +columnTemplate.events.on("dragstop", () => { | |
103 | + sortCategoryAxis(); | |
104 | +}); | |
105 | + | |
106 | +// Get series item by category | |
107 | +function getSeriesItem(category) { | |
108 | + for (var i = 0; i < series.dataItems.length; i++) { | |
109 | + let dataItem = series.dataItems[i]; | |
110 | + if (dataItem.get("categoryY") == category) { | |
111 | + return dataItem; | |
112 | + } | |
113 | + } | |
114 | +} | |
115 | + | |
116 | + | |
117 | +// Axis sorting | |
118 | +function sortCategoryAxis() { | |
119 | + // Sort by value | |
120 | + series.dataItems.sort(function (x, y) { | |
121 | + return y.get("graphics").y() - x.get("graphics").y(); | |
122 | + }); | |
123 | + | |
124 | + let easing = am5.ease.out(am5.ease.cubic); | |
125 | + | |
126 | + // Go through each axis item | |
127 | + am5.array.each(yAxis.dataItems, function (dataItem) { | |
128 | + // get corresponding series item | |
129 | + let seriesDataItem = getSeriesItem(dataItem.get("category")); | |
130 | + | |
131 | + if (seriesDataItem) { | |
132 | + // get index of series data item | |
133 | + let index = series.dataItems.indexOf(seriesDataItem); | |
134 | + | |
135 | + let column = seriesDataItem.get("graphics"); | |
136 | + | |
137 | + // position after sorting | |
138 | + let fy = | |
139 | + yRenderer.positionToCoordinate(yAxis.indexToPosition(index)) - | |
140 | + column.height() / 2; | |
141 | + | |
142 | + // set index to be the same as series data item index | |
143 | + if (index != dataItem.get("index")) { | |
144 | + dataItem.set("index", index); | |
145 | + | |
146 | + // current position | |
147 | + let x = column.x(); | |
148 | + let y = column.y(); | |
149 | + | |
150 | + column.set("dy", -(fy - y)); | |
151 | + column.set("dx", x); | |
152 | + | |
153 | + column.animate({ key: "dy", to: 0, duration: 600, easing: easing }); | |
154 | + column.animate({ key: "dx", to: 0, duration: 600, easing: easing }); | |
155 | + } else { | |
156 | + column.animate({ key: "y", to: fy, duration: 600, easing: easing }); | |
157 | + column.animate({ key: "x", to: 0, duration: 600, easing: easing }); | |
158 | + } | |
159 | + } | |
160 | + }); | |
161 | + | |
162 | + // Sort axis items by index. | |
163 | + // This changes the order instantly, but as dx and dy is set and animated, | |
164 | + // they keep in the same places and then animate to true positions. | |
165 | + yAxis.dataItems.sort(function (x, y) { | |
166 | + return x.get("index") - y.get("index"); | |
167 | + }); | |
168 | +} | |
169 | + | |
170 | +// Set data | |
171 | +let data = [{ | |
172 | + country: "USA", | |
173 | + value: 2025 | |
174 | +}, { | |
175 | + country: "China", | |
176 | + value: 1882 | |
177 | +}, { | |
178 | + country: "Japan", | |
179 | + value: 1809 | |
180 | +}, { | |
181 | + country: "Germany", | |
182 | + value: 1322 | |
183 | +}, { | |
184 | + country: "UK", | |
185 | + value: 1122 | |
186 | +}]; | |
187 | + | |
188 | +yAxis.data.setAll(data); | |
189 | +series.data.setAll(data); | |
190 | + | |
191 | + | |
192 | +// Make stuff animate on load | |
193 | +// https://www.amcharts.com/docs/v5/concepts/animations/ | |
194 | +series.appear(1000); | |
195 | +chart.appear(1000, 100); | |
196 | + }, | |
197 | + }, | |
198 | + }; | |
199 | + </script> | |
200 | + | |
201 | + <style scoped> | |
202 | + /* Add necessary styles here */ | |
203 | + </style> | |
204 | + (No newline at end of file) |
+++ client/views/pages/parents/Bubblechart.vue
... | ... | @@ -0,0 +1,222 @@ |
1 | +<template> | |
2 | + <div ref="Bubblechart" style="width: 500px; height: 500px;"></div> | |
3 | + </template> | |
4 | + | |
5 | + <script> | |
6 | + import * as am5 from "@amcharts/amcharts5"; | |
7 | + import * as am5xy from "@amcharts/amcharts5/xy"; | |
8 | + import am5themes_Animated from "@amcharts/amcharts5/themes/Animated"; | |
9 | + | |
10 | + export default { | |
11 | + name: "Bubblechart", | |
12 | + mounted() { | |
13 | + this.createChart(); | |
14 | + }, | |
15 | + methods: { | |
16 | + createChart() { | |
17 | + // Initialize root | |
18 | + const root = am5.Root.new(this.$refs.Bubblechart); | |
19 | + | |
20 | + // Apply themes | |
21 | + const myTheme = am5.Theme.new(root); | |
22 | + myTheme.rule("Grid", ["base"]).setAll({ | |
23 | + strokeOpacity: 0.1, | |
24 | + }); | |
25 | + root.setThemes([ | |
26 | + am5themes_Animated.new(root), | |
27 | + myTheme, | |
28 | + ]); | |
29 | + | |
30 | + // Create chart | |
31 | + let chart = root.container.children.push(am5xy.XYChart.new(root, { | |
32 | + panX: true, | |
33 | + panY: true, | |
34 | + wheelY: "zoomXY", | |
35 | + pinchZoomX:true, | |
36 | + pinchZoomY:true | |
37 | +})); | |
38 | + | |
39 | +chart.get("colors").set("step", 2); | |
40 | + | |
41 | +// Create axes | |
42 | +// https://www.amcharts.com/docs/v5/charts/xy-chart/axes/ | |
43 | +let xAxis = chart.xAxes.push(am5xy.ValueAxis.new(root, { | |
44 | + renderer: am5xy.AxisRendererX.new(root, { minGridDistance: 50 }), | |
45 | + tooltip: am5.Tooltip.new(root, {}) | |
46 | +})); | |
47 | + | |
48 | +let yAxis = chart.yAxes.push(am5xy.ValueAxis.new(root, { | |
49 | + renderer: am5xy.AxisRendererY.new(root, {}), | |
50 | + tooltip: am5.Tooltip.new(root, {}) | |
51 | +})); | |
52 | + | |
53 | +// Create series | |
54 | +// https://www.amcharts.com/docs/v5/charts/xy-chart/series/ | |
55 | +let series0 = chart.series.push(am5xy.LineSeries.new(root, { | |
56 | + calculateAggregates: true, | |
57 | + xAxis: xAxis, | |
58 | + yAxis: yAxis, | |
59 | + valueYField: "y", | |
60 | + valueXField: "x", | |
61 | + valueField: "value", | |
62 | + tooltip: am5.Tooltip.new(root, { | |
63 | + labelText: "x: {valueX}, y: {valueY}, value: {value}" | |
64 | + }) | |
65 | +})); | |
66 | + | |
67 | + | |
68 | +// Add bullet | |
69 | +// https://www.amcharts.com/docs/v5/charts/xy-chart/series/#Bullets | |
70 | +let circleTemplate = am5.Template.new({}); | |
71 | +series0.bullets.push(function() { | |
72 | + let graphics = am5.Circle.new(root, { | |
73 | + fill: series0.get("fill"), | |
74 | + }, circleTemplate); | |
75 | + return am5.Bullet.new(root, { | |
76 | + sprite: graphics | |
77 | + }); | |
78 | +}); | |
79 | + | |
80 | +// Add heat rule | |
81 | +// https://www.amcharts.com/docs/v5/concepts/settings/heat-rules/ | |
82 | +series0.set("heatRules", [{ | |
83 | + target: circleTemplate, | |
84 | + min: 3, | |
85 | + max: 35, | |
86 | + dataField: "value", | |
87 | + key: "radius" | |
88 | +}]); | |
89 | + | |
90 | + | |
91 | +// Create second series | |
92 | +// https://www.amcharts.com/docs/v5/charts/xy-chart/series/ | |
93 | +let series1 = chart.series.push(am5xy.LineSeries.new(root, { | |
94 | + calculateAggregates: true, | |
95 | + xAxis: xAxis, | |
96 | + yAxis: yAxis, | |
97 | + valueYField: "y2", | |
98 | + valueXField: "x2", | |
99 | + valueField: "value", | |
100 | + tooltip: am5.Tooltip.new(root, { | |
101 | + labelText: "x: {valueX}, y: {valueY}, value: {value}" | |
102 | + }) | |
103 | +})); | |
104 | + | |
105 | +// Add bullet | |
106 | +// https://www.amcharts.com/docs/v5/charts/xy-chart/series/#Bullets | |
107 | +let starTemplate = am5.Template.new({}); | |
108 | +series1.bullets.push(function() { | |
109 | + let graphics = am5.Star.new(root, { | |
110 | + fill: series1.get("fill"), | |
111 | + spikes: 8, | |
112 | + innerRadius: am5.percent(70), | |
113 | + }, starTemplate); | |
114 | + return am5.Bullet.new(root, { | |
115 | + sprite: graphics | |
116 | + }); | |
117 | +}); | |
118 | + | |
119 | + | |
120 | +// Add heat rule | |
121 | +// https://www.amcharts.com/docs/v5/concepts/settings/heat-rules/ | |
122 | +series1.set("heatRules", [{ | |
123 | + target: starTemplate, | |
124 | + min: 3, | |
125 | + max: 50, | |
126 | + dataField: "value", | |
127 | + key: "radius" | |
128 | +}]); | |
129 | + | |
130 | + | |
131 | +series0.strokes.template.set("strokeOpacity", 0); | |
132 | +series1.strokes.template.set("strokeOpacity", 0); | |
133 | + | |
134 | +// Add cursor | |
135 | +// https://www.amcharts.com/docs/v5/charts/xy-chart/cursor/ | |
136 | +chart.set("cursor", am5xy.XYCursor.new(root, { | |
137 | + xAxis: xAxis, | |
138 | + yAxis: yAxis, | |
139 | + snapToSeries: [series0, series1] | |
140 | +})); | |
141 | + | |
142 | +// Add scrollbars | |
143 | +// https://www.amcharts.com/docs/v5/charts/xy-chart/scrollbars/ | |
144 | +// chart.set("scrollbarX", am5.Scrollbar.new(root, { | |
145 | +// orientation: "horizontal" | |
146 | +// })); | |
147 | + | |
148 | +// chart.set("scrollbarY", am5.Scrollbar.new(root, { | |
149 | +// orientation: "vertical" | |
150 | +// })); | |
151 | + | |
152 | + | |
153 | +let data = [{ | |
154 | + "y": 10, | |
155 | + "x": 14, | |
156 | + "value": 59, | |
157 | + "y2": -5, | |
158 | + "x2": -3, | |
159 | + "value2": 44 | |
160 | +}, { | |
161 | + "y": 5, | |
162 | + "x": 3, | |
163 | + "value": 50, | |
164 | + "y2": -15, | |
165 | + "x2": -8, | |
166 | + "value2": 12 | |
167 | +}, { | |
168 | + "y": -10, | |
169 | + "x": 8, | |
170 | + "value": 19, | |
171 | + "y2": -4, | |
172 | + "x2": 6, | |
173 | + "value2": 35 | |
174 | +}, { | |
175 | + "y": -6, | |
176 | + "x": 5, | |
177 | + "value": 65, | |
178 | + "y2": -5, | |
179 | + "x2": -6, | |
180 | + "value2": 168 | |
181 | +}, { | |
182 | + "y": 15, | |
183 | + "x": -4, | |
184 | + "value": 92, | |
185 | + "y2": -10, | |
186 | + "x2": -8, | |
187 | + "value2": 102 | |
188 | +}, { | |
189 | + "y": 13, | |
190 | + "x": 1, | |
191 | + "value": 8, | |
192 | + "y2": -2, | |
193 | + "x2": 0, | |
194 | + "value2": 41 | |
195 | +}, { | |
196 | + "y": 1, | |
197 | + "x": 6, | |
198 | + "value": 35, | |
199 | + "y2": 0, | |
200 | + "x2": -3, | |
201 | + "value2": 16 | |
202 | +}] | |
203 | + | |
204 | +series0.data.setAll(data); | |
205 | +series1.data.setAll(data); | |
206 | + | |
207 | + | |
208 | +// Make stuff animate on load | |
209 | +// https://www.amcharts.com/docs/v5/concepts/animations/ | |
210 | +series0.appear(1000); | |
211 | +series1.appear(1000); | |
212 | + | |
213 | +chart.appear(1000, 100); | |
214 | + }, | |
215 | + }, | |
216 | + }; | |
217 | + </script> | |
218 | + | |
219 | + <style scoped> | |
220 | + /* Add necessary styles here */ | |
221 | + </style> | |
222 | + (No newline at end of file) |
+++ client/views/pages/parents/ColumnLineChart.vue
... | ... | @@ -0,0 +1,206 @@ |
1 | +<template> | |
2 | + <div ref="ColumnLineChart" style="width: 500px; height: 500px;"></div> | |
3 | + </template> | |
4 | + | |
5 | + <script> | |
6 | + import * as am5 from "@amcharts/amcharts5"; | |
7 | + import * as am5xy from "@amcharts/amcharts5/xy"; | |
8 | + import am5themes_Animated from "@amcharts/amcharts5/themes/Animated"; | |
9 | + | |
10 | + export default { | |
11 | + name: "ColumnLineChart", | |
12 | + mounted() { | |
13 | + this.createChart(); | |
14 | + }, | |
15 | + methods: { | |
16 | + createChart() { | |
17 | + // Initialize root | |
18 | + const root = am5.Root.new(this.$refs.ColumnLineChart); | |
19 | + | |
20 | + // Apply themes | |
21 | + const myTheme = am5.Theme.new(root); | |
22 | + myTheme.rule("Grid", ["base"]).setAll({ | |
23 | + strokeOpacity: 0.1, | |
24 | + }); | |
25 | + root.setThemes([ | |
26 | + am5themes_Animated.new(root), | |
27 | + myTheme, | |
28 | + ]); | |
29 | + | |
30 | + // Create chart | |
31 | + let chart = root.container.children.push( | |
32 | + am5xy.XYChart.new(root, { | |
33 | + panX: false, | |
34 | + panY: false, | |
35 | + wheelX: "panX", | |
36 | + wheelY: "zoomX", | |
37 | + paddingLeft: 0, | |
38 | + layout: root.verticalLayout | |
39 | + }) | |
40 | +); | |
41 | + | |
42 | +// Add scrollbar | |
43 | +// https://www.amcharts.com/docs/v5/charts/xy-chart/scrollbars/ | |
44 | +// chart.set( | |
45 | +// "scrollbarX", | |
46 | +// am5.Scrollbar.new(root, { | |
47 | +// orientation: "horizontal" | |
48 | +// }) | |
49 | +// ); | |
50 | + | |
51 | +let data = [ | |
52 | + { | |
53 | + year: "2016", | |
54 | + income: 23.5, | |
55 | + expenses: 21.1 | |
56 | + }, | |
57 | + { | |
58 | + year: "2017", | |
59 | + income: 26.2, | |
60 | + expenses: 30.5 | |
61 | + }, | |
62 | + { | |
63 | + year: "2018", | |
64 | + income: 30.1, | |
65 | + expenses: 34.9 | |
66 | + }, | |
67 | + { | |
68 | + year: "2019", | |
69 | + income: 29.5, | |
70 | + expenses: 31.1 | |
71 | + }, | |
72 | + { | |
73 | + year: "2020", | |
74 | + income: 30.6, | |
75 | + expenses: 28.2, | |
76 | + strokeSettings: { | |
77 | + stroke: chart.get("colors").getIndex(1), | |
78 | + strokeWidth: 3, | |
79 | + strokeDasharray: [5, 5] | |
80 | + } | |
81 | + }, | |
82 | + { | |
83 | + year: "2021", | |
84 | + income: 34.1, | |
85 | + expenses: 32.9, | |
86 | + columnSettings: { | |
87 | + strokeWidth: 1, | |
88 | + strokeDasharray: [5], | |
89 | + fillOpacity: 0.2 | |
90 | + }, | |
91 | + info: "(projection)" | |
92 | + } | |
93 | +]; | |
94 | + | |
95 | +// Create axes | |
96 | +// https://www.amcharts.com/docs/v5/charts/xy-chart/axes/ | |
97 | +let xRenderer = am5xy.AxisRendererX.new(root, { | |
98 | + minorGridEnabled: true, | |
99 | + minGridDistance: 60 | |
100 | +}); | |
101 | +let xAxis = chart.xAxes.push( | |
102 | + am5xy.CategoryAxis.new(root, { | |
103 | + categoryField: "year", | |
104 | + renderer: xRenderer, | |
105 | + tooltip: am5.Tooltip.new(root, {}) | |
106 | + }) | |
107 | +); | |
108 | +xRenderer.grid.template.setAll({ | |
109 | + location: 1 | |
110 | +}) | |
111 | + | |
112 | +xAxis.data.setAll(data); | |
113 | + | |
114 | +let yAxis = chart.yAxes.push( | |
115 | + am5xy.ValueAxis.new(root, { | |
116 | + min: 0, | |
117 | + extraMax: 0.1, | |
118 | + renderer: am5xy.AxisRendererY.new(root, { | |
119 | + strokeOpacity: 0.1 | |
120 | + }) | |
121 | + }) | |
122 | +); | |
123 | + | |
124 | + | |
125 | +// Add series | |
126 | +// https://www.amcharts.com/docs/v5/charts/xy-chart/series/ | |
127 | + | |
128 | +let series1 = chart.series.push( | |
129 | + am5xy.ColumnSeries.new(root, { | |
130 | + name: "Income", | |
131 | + xAxis: xAxis, | |
132 | + yAxis: yAxis, | |
133 | + valueYField: "income", | |
134 | + categoryXField: "year", | |
135 | + tooltip: am5.Tooltip.new(root, { | |
136 | + pointerOrientation: "horizontal", | |
137 | + labelText: "{name} in {categoryX}: {valueY} {info}" | |
138 | + }) | |
139 | + }) | |
140 | +); | |
141 | + | |
142 | +series1.columns.template.setAll({ | |
143 | + tooltipY: am5.percent(10), | |
144 | + templateField: "columnSettings" | |
145 | +}); | |
146 | + | |
147 | +series1.data.setAll(data); | |
148 | + | |
149 | +let series2 = chart.series.push( | |
150 | + am5xy.LineSeries.new(root, { | |
151 | + name: "Expenses", | |
152 | + xAxis: xAxis, | |
153 | + yAxis: yAxis, | |
154 | + valueYField: "expenses", | |
155 | + categoryXField: "year", | |
156 | + tooltip: am5.Tooltip.new(root, { | |
157 | + pointerOrientation: "horizontal", | |
158 | + labelText: "{name} in {categoryX}: {valueY} {info}" | |
159 | + }) | |
160 | + }) | |
161 | +); | |
162 | + | |
163 | +series2.strokes.template.setAll({ | |
164 | + strokeWidth: 3, | |
165 | + templateField: "strokeSettings" | |
166 | +}); | |
167 | + | |
168 | + | |
169 | +series2.data.setAll(data); | |
170 | + | |
171 | +series2.bullets.push(function () { | |
172 | + return am5.Bullet.new(root, { | |
173 | + sprite: am5.Circle.new(root, { | |
174 | + strokeWidth: 3, | |
175 | + stroke: series2.get("stroke"), | |
176 | + radius: 5, | |
177 | + fill: root.interfaceColors.get("background") | |
178 | + }) | |
179 | + }); | |
180 | +}); | |
181 | + | |
182 | +chart.set("cursor", am5xy.XYCursor.new(root, {})); | |
183 | + | |
184 | +// Add legend | |
185 | +// https://www.amcharts.com/docs/v5/charts/xy-chart/legend-xy-series/ | |
186 | +let legend = chart.children.push( | |
187 | + am5.Legend.new(root, { | |
188 | + centerX: am5.p50, | |
189 | + x: am5.p50 | |
190 | + }) | |
191 | +); | |
192 | +legend.data.setAll(chart.series.values); | |
193 | + | |
194 | +// Make stuff animate on load | |
195 | +// https://www.amcharts.com/docs/v5/concepts/animations/ | |
196 | +chart.appear(1000, 100); | |
197 | +series1.appear(); | |
198 | + }, | |
199 | + }, | |
200 | + }; | |
201 | + </script> | |
202 | + | |
203 | + <style scoped> | |
204 | + /* Add necessary styles here */ | |
205 | + </style> | |
206 | + (No newline at end of file) |
+++ client/views/pages/parents/Dounutchart.vue
... | ... | @@ -0,0 +1,81 @@ |
1 | +<template> | |
2 | + <div ref="Dounutchart" style="width: 500px; height: 500px;"></div> | |
3 | +</template> | |
4 | + | |
5 | +<script> | |
6 | +import * as am5 from "@amcharts/amcharts5"; | |
7 | +import * as am5percent from "@amcharts/amcharts5/percent"; | |
8 | +import am5themes_Animated from "@amcharts/amcharts5/themes/Animated"; | |
9 | + | |
10 | +export default { | |
11 | + name: "Dounutchart", | |
12 | + mounted() { | |
13 | + this.createChart(); | |
14 | + }, | |
15 | + methods: { | |
16 | + createChart() { | |
17 | + // Initialize root | |
18 | + const root = am5.Root.new(this.$refs.Dounutchart); | |
19 | + | |
20 | + // Apply themes | |
21 | + root.setThemes([ | |
22 | + am5themes_Animated.new(root) | |
23 | + ]); | |
24 | + | |
25 | + // Create chart | |
26 | + const chart = root.container.children.push(am5percent.PieChart.new(root, { | |
27 | + layout: root.verticalLayout, | |
28 | + innerRadius: am5.percent(50) // Adjusted innerRadius for a donut chart | |
29 | + })); | |
30 | + | |
31 | + // Create series | |
32 | + const series = chart.series.push(am5percent.PieSeries.new(root, { | |
33 | + valueField: "value", | |
34 | + categoryField: "category", | |
35 | + alignLabels: false | |
36 | + })); | |
37 | + | |
38 | + // Add static label | |
39 | + // const label = chart.plotContainer.children.push(am5.Label.new(root, { | |
40 | + // text: "Total", | |
41 | + // x: am5.p50, | |
42 | + // y: am5.p50, | |
43 | + // centerX: am5.p50, | |
44 | + // centerY: am5.p50, | |
45 | + // fill: am5.color(0x000000), | |
46 | + // fontSize: 20, | |
47 | + // fontWeight: "bold" | |
48 | + // })); | |
49 | + | |
50 | + series.labels.template.setAll({ | |
51 | + textType: "circular", | |
52 | + centerX: am5.p50, | |
53 | + centerY: am5.p50 | |
54 | + }); | |
55 | + | |
56 | + // Set data | |
57 | + series.data.setAll([ | |
58 | + { value: 10, category: "One" }, | |
59 | + { value: 9, category: "Two" }, | |
60 | + ]); | |
61 | + | |
62 | + // Create legend | |
63 | + const legend = chart.children.push(am5.Legend.new(root, { | |
64 | + centerX: am5.p50, | |
65 | + x: am5.p50, | |
66 | + marginTop: 15, | |
67 | + marginBottom: 15 | |
68 | + })); | |
69 | + | |
70 | + legend.data.setAll(series.dataItems); | |
71 | + | |
72 | + // Play initial series animation | |
73 | + series.appear(1000, 100); | |
74 | + } | |
75 | + } | |
76 | +}; | |
77 | +</script> | |
78 | + | |
79 | +<style scoped> | |
80 | +/* Add necessary styles here */ | |
81 | +</style> |
--- client/views/pages/parents/Main_p.vue
+++ client/views/pages/parents/Main_p.vue
... | ... | @@ -1,15 +1,195 @@ |
1 | 1 |
<template> |
2 |
- <div>Main.vue</div> |
|
2 |
+ <div> |
|
3 |
+ <!-- <Side_t></Side_t> --> |
|
4 |
+ <div style="padding: 15px 60px 120px 60px "> |
|
5 |
+ <div class="flex justify-between align-center"> |
|
6 |
+ <div class="logo mb25"><img src="../../../resources/img/logo2.png" alt=""></div> |
|
7 |
+ <Header></Header> |
|
8 |
+ </div> |
|
9 |
+ <div class="main-wrap flex justify-between"> |
|
10 |
+ <div class="gd-2"> |
|
11 |
+ <div class=" mb30"> |
|
12 |
+ <div> |
|
13 |
+ <img src="../../../resources/img/img16_s.png" alt=""> |
|
14 |
+ <div class="mt10" style="width: 100%;"> |
|
15 |
+ <p class="name mb10">학생이름</p> |
|
16 |
+ <p class="mb5">xx중학교 3학년 x반</p> |
|
17 |
+ <progress-bar :progress="progress"></progress-bar> |
|
18 |
+ <span @click="increaseProgress">오늘의 공부</span> |
|
19 |
+ <span class="brown ml10">{{ progress }}%</span> |
|
20 |
+ </div> |
|
21 |
+ </div> |
|
22 |
+ <hr> |
|
23 |
+ <p class="title2 mb25">최근 학습 히스토리</p> |
|
24 |
+ <ul class="flex justify-between ml30"> |
|
25 |
+ <li>자학사 3학년 2학기</li> |
|
26 |
+ <li>자학사 3학년 2학기</li> |
|
27 |
+ </ul> |
|
28 |
+ |
|
29 |
+ <hr> |
|
30 |
+ <div class="title-box flex justify-between mb20"> |
|
31 |
+ <p class="name">가나다학생 랭킹</p> |
|
32 |
+ </div> |
|
33 |
+ <div class="mypage mb30"> |
|
34 |
+ <div class=" flex-column " style="gap: 20px;"> |
|
35 |
+ <div class="textbook book-red"> |
|
36 |
+ <div class="text "> |
|
37 |
+ <p class="title1" style="color: #fff;">포토북 랭킹</p> |
|
38 |
+ </div> |
|
39 |
+ <div class="box"> |
|
40 |
+ <P class="title2 mt10">현재 30명 중 <em class="red">2등</em>입니다.</P> |
|
41 |
+ </div> |
|
42 |
+ </div> |
|
43 |
+ <div class="textbook "> |
|
44 |
+ <div class="text "> |
|
45 |
+ <p class="title1" style="color: #fff;">포토북 랭킹</p> |
|
46 |
+ </div> |
|
47 |
+ <div class="box"> |
|
48 |
+ <P class="title2 mt10">현재 30명 중 <em class="yellow">2등</em>입니다.</P> |
|
49 |
+ </div> |
|
50 |
+ </div> |
|
51 |
+ <div class="textbook book-blue"> |
|
52 |
+ <div class="text "> |
|
53 |
+ <p class="title1" style="color: #fff;">포토북 랭킹</p> |
|
54 |
+ </div> |
|
55 |
+ <div class="box"> |
|
56 |
+ <P class="title2 mt10">현재 30명 중 <em class="blue">2등</em>입니다.</P> |
|
57 |
+ </div> |
|
58 |
+ </div> |
|
59 |
+ <div class="textbook book-navy"> |
|
60 |
+ <div class="text "> |
|
61 |
+ <p class="title1" style="color: #fff;">포토북 랭킹</p> |
|
62 |
+ </div> |
|
63 |
+ <div class="box"> |
|
64 |
+ <P class="title2 mt10">현재 30명 중 <em class="navy">2등</em>입니다.</P> |
|
65 |
+ </div> |
|
66 |
+ </div> |
|
67 |
+ </div> |
|
68 |
+ </div> |
|
69 |
+ <hr> |
|
70 |
+ <div> |
|
71 |
+ <div class="title-box flex justify-between mb20"> |
|
72 |
+ <p class="title">사진첩</p> |
|
73 |
+ </div> |
|
74 |
+ <div class="photobook"> |
|
75 |
+ <div class="flex justify-between"> |
|
76 |
+ <div class="box" style="gap: 5px;"> |
|
77 |
+ <div><img src="../../../resources/img/img198_12p.png" alt=""></div> |
|
78 |
+ </div> |
|
79 |
+ <div class="text mt10"> |
|
80 |
+ <p class="title1 mb10">나의 사진첩</p> |
|
81 |
+ <button @click="selectedTab = 'tab4'; goToPage('PhotoBook')" type="button" |
|
82 |
+ title="글쓰기" class="new-btn"> |
|
83 |
+ 바로가기 |
|
84 |
+ </button> |
|
85 |
+ |
|
86 |
+ </div> |
|
87 |
+ </div> |
|
88 |
+ |
|
89 |
+ </div> |
|
90 |
+ </div> |
|
91 |
+ </div> |
|
92 |
+ </div> |
|
93 |
+ <div class="gd-9"> |
|
94 |
+ <div class="title-box flex justify-between mb40"> |
|
95 |
+ <p class="title">전체 진행률</p> |
|
96 |
+ </div> |
|
97 |
+ <div class="flex"> |
|
98 |
+ <div class="wrap"> |
|
99 |
+ <p class="name">학습 현황</p> |
|
100 |
+ <div><Dounutchart/></div> |
|
101 |
+ <div class="textbox"> |
|
102 |
+ <p class="title2">오늘의 학습현황</p> |
|
103 |
+ <p class="name">40%</p> |
|
104 |
+ </div> |
|
105 |
+ <p class="title2">학습시간</p> |
|
106 |
+ <p class="name">학습시간 0시간</p> |
|
107 |
+ </div> |
|
108 |
+ <div class="wrap"> |
|
109 |
+ <p class="name">이해/표현 점수</p> |
|
110 |
+ <div> |
|
111 |
+ <ColumnLineChart/> |
|
112 |
+ </div> |
|
113 |
+ </div> |
|
114 |
+ </div> |
|
115 |
+ <div class="wrap"> |
|
116 |
+ <p class="name">교재별 진행률</p> |
|
117 |
+ <div> |
|
118 |
+ <StackedBarChart /> |
|
119 |
+ </div> |
|
120 |
+ </div> |
|
121 |
+ <div class="flex"> |
|
122 |
+ <div class="wrap"> |
|
123 |
+ <p class="name">오늘의 학습 일정</p> |
|
124 |
+ <div class="flex-column" style="gap: 20px;"> |
|
125 |
+ <div class=" flex justify-between align-center " style="gap: 70px;"> |
|
126 |
+ <div><img src="../../../resources/img/img217_22s.png" alt=""></div> |
|
127 |
+ <div class="wrap cs-pt" :class="{ 'cs-pt-clicked': isClicked }" |
|
128 |
+ @click="toggleClicked" style="width: 100%;"> |
|
129 |
+ <div class="text-lf flex justify-between align-center "> |
|
130 |
+ <div> |
|
131 |
+ <div class="flex align-center mb10" style="gap: 10px;"> |
|
132 |
+ <p class="title2"><em class="gray-bd">1교시</em></p> |
|
133 |
+ <p class="title1">9:00</p> |
|
134 |
+ <p class="title1">~</p> |
|
135 |
+ <p class="title1">10:00</p> |
|
136 |
+ </div> |
|
137 |
+ <div class="title-box mb10"> <span class="title">the best</span></div> |
|
138 |
+ <p class="title2">wirte a</p> |
|
139 |
+ </div> |
|
140 |
+ <div class=""> <img src="../../../resources/img/img214_19s.png" alt=""> |
|
141 |
+ </div> |
|
142 |
+ </div> |
|
143 |
+ </div> |
|
144 |
+ </div> |
|
145 |
+ |
|
146 |
+ </div> |
|
147 |
+ </div> |
|
148 |
+ <div class="flex-column"> |
|
149 |
+ <div class="wrap"> |
|
150 |
+ <p class="name">교재별 오답률</p> |
|
151 |
+ <Barchart /> |
|
152 |
+ </div> |
|
153 |
+ |
|
154 |
+ <div class="wrap"> |
|
155 |
+ <p class="name">LC/RC 세부 점수</p> |
|
156 |
+ <Bubblechart /> |
|
157 |
+ </div> |
|
158 |
+ |
|
159 |
+ </div> |
|
160 |
+ |
|
161 |
+ </div> |
|
162 |
+ </div> |
|
163 |
+ </div> |
|
164 |
+ </div> |
|
165 |
+ </div> |
|
166 |
+ |
|
3 | 167 |
</template> |
4 | 168 |
|
5 | 169 |
<script> |
170 |
+import Header from '../../layout/Header.vue'; |
|
171 |
+import Menu from '../../layout/Menu.vue'; |
|
172 |
+import Side_t from '../../layout/Side_t.vue'; |
|
173 |
+import ProgressBar from '../../component/ProgressBar.vue'; |
|
174 |
+import StackedBarChart from './StackedBarChart.vue'; |
|
175 |
+import Barchart from './Barchart.vue'; |
|
176 |
+import Bubblechart from './Bubblechart.vue'; |
|
177 |
+import Dounutchart from './Dounutchart.vue'; |
|
178 |
+import ColumnLineChart from './ColumnLineChart.vue'; |
|
6 | 179 |
|
7 | 180 |
export default { |
8 |
- data () { |
|
181 |
+ data() { |
|
9 | 182 |
return { |
183 |
+ progress: 20 |
|
10 | 184 |
} |
11 | 185 |
}, |
12 | 186 |
methods: { |
187 |
+ increaseProgress() { |
|
188 |
+ if (this.progress < 100) { |
|
189 |
+ this.progress += 10; |
|
190 |
+ } |
|
191 |
+ }, |
|
192 |
+ |
|
13 | 193 |
|
14 | 194 |
}, |
15 | 195 |
watch: { |
... | ... | @@ -19,9 +199,27 @@ |
19 | 199 |
|
20 | 200 |
}, |
21 | 201 |
components: { |
202 |
+ Header: Header, |
|
203 |
+ Menu: Menu, |
|
204 |
+ // Footer:Footer, |
|
205 |
+ Side_t: Side_t, |
|
206 |
+ ProgressBar, |
|
207 |
+ StackedBarChart: StackedBarChart, |
|
208 |
+ Barchart: Barchart, |
|
209 |
+ Bubblechart: Bubblechart, |
|
210 |
+ Dounutchart: Dounutchart, |
|
211 |
+ ColumnLineChart: ColumnLineChart, |
|
212 |
+ |
|
22 | 213 |
}, |
23 | 214 |
mounted() { |
24 |
- console.log('main mounted'); |
|
25 | 215 |
} |
26 | 216 |
} |
27 |
-</script>(No newline at end of file) |
|
217 |
+</script> |
|
218 |
+<style scoped> |
|
219 |
+.main-wrap { |
|
220 |
+ margin-top: 20px; |
|
221 |
+ position: static; |
|
222 |
+ width: 100%; |
|
223 |
+ height: 100%; |
|
224 |
+} |
|
225 |
+</style>(No newline at end of file) |
+++ client/views/pages/parents/StackedBarChart.vue
... | ... | @@ -0,0 +1,129 @@ |
1 | +<template> | |
2 | + <div ref="StackedBarChart" style=" height: 500px;"></div> | |
3 | + </template> | |
4 | + | |
5 | + <script> | |
6 | + import * as am5 from "@amcharts/amcharts5"; | |
7 | + import * as am5xy from "@amcharts/amcharts5/xy"; | |
8 | + import am5themes_Animated from "@amcharts/amcharts5/themes/Animated"; | |
9 | + | |
10 | + export default { | |
11 | + name: "StackedBarChart", | |
12 | + mounted() { | |
13 | + this.createChart(); | |
14 | + }, | |
15 | + methods: { | |
16 | + createChart() { | |
17 | + // Initialize root | |
18 | + const root = am5.Root.new(this.$refs.StackedBarChart); | |
19 | + | |
20 | + // Apply themes | |
21 | + const myTheme = am5.Theme.new(root); | |
22 | + myTheme.rule("Grid", ["base"]).setAll({ | |
23 | + strokeOpacity: 0.1, | |
24 | + }); | |
25 | + root.setThemes([ | |
26 | + am5themes_Animated.new(root), | |
27 | + myTheme, | |
28 | + ]); | |
29 | + | |
30 | + // Create chart | |
31 | + const chart = root.container.children.push( | |
32 | + am5xy.XYChart.new(root, { | |
33 | + panX: false, | |
34 | + panY: false, | |
35 | + wheelX: "panY", | |
36 | + wheelY: "zoomY", | |
37 | + paddingLeft: 0, | |
38 | + layout: root.verticalLayout, | |
39 | + }) | |
40 | + ); | |
41 | + | |
42 | + // Define data | |
43 | + const data = [ | |
44 | + { year: "2021", europe: 2.5, namerica: 2.5, asia: 2.1, lamerica: 1, meast: 0.8, africa: 0.4 }, | |
45 | + ]; | |
46 | + | |
47 | + // Create Y Axis | |
48 | + const yRenderer = am5xy.AxisRendererY.new(root, {}); | |
49 | + const yAxis = chart.yAxes.push(am5xy.CategoryAxis.new(root, { | |
50 | + categoryField: "year", | |
51 | + renderer: yRenderer, | |
52 | + tooltip: am5.Tooltip.new(root, {}), | |
53 | + })); | |
54 | + yRenderer.grid.template.setAll({ | |
55 | + location: 1, | |
56 | + }); | |
57 | + yAxis.data.setAll(data); | |
58 | + | |
59 | + // Create X Axis | |
60 | + const xAxis = chart.xAxes.push(am5xy.ValueAxis.new(root, { | |
61 | + min: 0, | |
62 | + maxPrecision: 0, | |
63 | + renderer: am5xy.AxisRendererX.new(root, { | |
64 | + minGridDistance: 40, | |
65 | + strokeOpacity: 0.1, | |
66 | + }), | |
67 | + })); | |
68 | + | |
69 | + // Create legend | |
70 | + const legend = chart.children.push(am5.Legend.new(root, { | |
71 | + centerX: am5.p50, | |
72 | + x: am5.p50, | |
73 | + })); | |
74 | + | |
75 | + // Function to create series | |
76 | + const createSeries = (name, fieldName) => { | |
77 | + const series = chart.series.push(am5xy.ColumnSeries.new(root, { | |
78 | + name, | |
79 | + stacked: true, | |
80 | + xAxis, | |
81 | + yAxis, | |
82 | + baseAxis: yAxis, | |
83 | + valueXField: fieldName, | |
84 | + categoryYField: "year", | |
85 | + })); | |
86 | + | |
87 | + series.columns.template.setAll({ | |
88 | + tooltipText: "{name}, {categoryY}: {valueX}", | |
89 | + tooltipY: am5.percent(90), | |
90 | + }); | |
91 | + series.data.setAll(data); | |
92 | + | |
93 | + series.appear(); | |
94 | + | |
95 | + series.bullets.push(() => | |
96 | + am5.Bullet.new(root, { | |
97 | + sprite: am5.Label.new(root, { | |
98 | + text: "{valueX}", | |
99 | + fill: root.interfaceColors.get("alternativeText"), | |
100 | + centerY: am5.p50, | |
101 | + centerX: am5.p50, | |
102 | + populateText: true, | |
103 | + }), | |
104 | + }) | |
105 | + ); | |
106 | + | |
107 | + | |
108 | + legend.data.push(series); | |
109 | + }; | |
110 | + | |
111 | + // Create series | |
112 | + createSeries("Europe", "europe"); | |
113 | + createSeries("North America", "namerica"); | |
114 | + createSeries("Asia", "asia"); | |
115 | + createSeries("Latin America", "lamerica"); | |
116 | + createSeries("Middle East", "meast"); | |
117 | + createSeries("Africa", "africa"); | |
118 | + | |
119 | + // Chart animation | |
120 | + chart.appear(1000, 100); | |
121 | + }, | |
122 | + }, | |
123 | + }; | |
124 | + </script> | |
125 | + | |
126 | + <style scoped> | |
127 | + /* Add necessary styles here */ | |
128 | + </style> | |
129 | + (No newline at end of file) |
--- client/views/pages/teacher/C_TextBookDetail.vue
+++ client/views/pages/teacher/C_TextBookDetail.vue
... | ... | @@ -1,6 +1,6 @@ |
1 | 1 |
<template> |
2 | 2 |
<div class="title-box flex justify-between mb40"> |
3 |
- <p class="title">A교재</p> |
|
3 |
+ <p class="title">{{ bookDetails ? bookDetails.book_nm : 'Loading...' }}</p> |
|
4 | 4 |
<select name="" id=""> |
5 | 5 |
<option value="">A 반</option> |
6 | 6 |
</select> |
... | ... | @@ -66,7 +66,7 @@ |
66 | 66 |
import SvgIcon from '@jamescoyle/vue-icon'; |
67 | 67 |
import { mdiMagnify, mdilArrowRight } from '@mdi/js'; |
68 | 68 |
import ProgressBar from '../../component/ProgressBar.vue'; |
69 |
- |
|
69 |
+import axios from 'axios'; |
|
70 | 70 |
|
71 | 71 |
export default { |
72 | 72 |
data() { |
... | ... | @@ -74,7 +74,9 @@ |
74 | 74 |
mdiMagnify: mdiMagnify, |
75 | 75 |
mdilArrowRight: mdilArrowRight, |
76 | 76 |
timer: "00:00", |
77 |
- progress: 20 |
|
77 |
+ progress: 20, |
|
78 |
+ selectedBookId: this.$route.query.book_Id, |
|
79 |
+ bookDetails: null, |
|
78 | 80 |
} |
79 | 81 |
}, |
80 | 82 |
methods: { |
... | ... | @@ -100,6 +102,25 @@ |
100 | 102 |
this.goBack(); |
101 | 103 |
} |
102 | 104 |
}, |
105 |
+ fetchBookDetails() { |
|
106 |
+ axios({ |
|
107 |
+ url: "/book/find.json", |
|
108 |
+ method: "post", |
|
109 |
+ headers: { |
|
110 |
+ "Content-Type": "application/json; charset=UTF-8", |
|
111 |
+ }, |
|
112 |
+ data: { |
|
113 |
+ book_id: this.$route.query.book_id |
|
114 |
+ } |
|
115 |
+ }) |
|
116 |
+ .then(response => { |
|
117 |
+ console.log('Book details:', response.data); |
|
118 |
+ this.bookDetails = response.data; |
|
119 |
+ }) |
|
120 |
+ .catch(error => { |
|
121 |
+ console.error('Error fetching book details:', error); |
|
122 |
+ }); |
|
123 |
+ } |
|
103 | 124 |
}, |
104 | 125 |
watch: { |
105 | 126 |
|
... | ... | @@ -112,7 +133,8 @@ |
112 | 133 |
ProgressBar |
113 | 134 |
}, |
114 | 135 |
mounted() { |
115 |
- console.log('Main2 mounted'); |
|
136 |
+ console.log('Mounted with book_id:', this.$route.query.book_id); |
|
137 |
+ this.fetchBookDetails(); |
|
116 | 138 |
} |
117 | 139 |
} |
118 | 140 |
</script> |
--- client/views/pages/teacher/C_Textbook.vue
+++ client/views/pages/teacher/C_Textbook.vue
... | ... | @@ -1,62 +1,67 @@ |
1 | 1 |
<template> |
2 | 2 |
<div class="title-box flex justify-between mb40"> |
3 | 3 |
<p class="title">교재</p> |
4 |
- <select name="" id=""> |
|
5 |
- <option value="">A반</option> |
|
4 |
+ <select v-model="selectedClassId"> |
|
5 |
+ <option v-for="classItem in classList" :key="classItem.sclsId" :value="classItem.sclsId"> |
|
6 |
+ {{ classItem.sclsNm }} |
|
7 |
+ </option> |
|
6 | 8 |
</select> |
7 |
- </div> |
|
8 |
- <div class="content-t"> |
|
9 |
- <div class=" flex " style="gap: 50px;"> |
|
10 |
- <div class="textbook"> |
|
11 |
- <div class="box " style="gap: 10px;" @click="goToPage('C_TextBookDetail')"> |
|
12 |
- </div> |
|
13 |
- <div class="text "> |
|
14 |
- <p class="title1" style="color: #fff;">A 교재</p> |
|
15 |
- <div class="btnGroup mt15 flex align-center justify-end" style="gap: 10px;"> |
|
16 |
- <button>수정</button><p>|</p> |
|
17 |
- <button @click="showConfirm('delete')">삭제</button> |
|
18 |
- </div> |
|
19 |
- </div> |
|
9 |
+ </div> |
|
10 |
+ <div class="content-t"> |
|
11 |
+ <div class="flex" style="gap: 2.5%;" :style="{flexWrap: 'wrap'}"> |
|
12 |
+ <div class="textbook" v-for="textbookItem in textbookList" :key="textbookItem.book_id" style="width: 22.5%; margin-bottom: 30px;"> |
|
13 |
+ <div class="box" style="gap: 10px;" @click="goToPage('C_TextBookDetail', textbookItem.book_id)"> |
|
20 | 14 |
</div> |
21 |
- |
|
22 |
- <div class="textbook-add"> |
|
23 |
- <button @click="buttonSearch"><img src="../../../resources/img/btn32_98t_normal.png" alt=""></button> |
|
24 |
- |
|
15 |
+ <div class="text"> |
|
16 |
+ <p class="title1" style="color: #fff;">{{ textbookItem.book_nm }}</p> |
|
17 |
+ <div class="btnGroup mt15 flex align-center justify-end" style="gap: 10px;"> |
|
18 |
+ <button @click="deleteBook(textbookItem.book_id)">삭제</button> |
|
19 |
+ </div> |
|
25 | 20 |
</div> |
26 | 21 |
</div> |
27 |
- </div> |
|
28 |
- <div v-show="searchOpen" class="popup-wrap"> |
|
29 |
- <div class="popup-box "> |
|
30 |
- <div class="flex justify-between mb30"> |
|
31 |
- <p class="popup-title">교재 이름</p> |
|
32 |
- <button type="button" class="popup-close-btn" @click="closeBtn"> |
|
33 |
- <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon> |
|
34 | 22 |
|
35 |
- </button> |
|
36 |
- </div> |
|
37 |
- <div class="search-wrap mb30"> |
|
38 |
- <select v-model="selectedSearchOption" class="mr10 data-wrap"> |
|
39 |
- <option value="bbsTtl">제목</option> |
|
40 |
- <option value="bbsCnt">내용</option> |
|
41 |
- <option value="userNm">작성자</option> |
|
42 |
- <option value="bbsCls">카테고리</option> |
|
43 |
- </select> |
|
44 |
- </div> |
|
45 |
- <div class="flex justify-center "> |
|
46 |
- <button type="button" title="글쓰기" class="new-btn mr10"> |
|
47 |
- 취소 |
|
48 |
- </button> |
|
49 |
- <button type="button" title="글쓰기" class="new-btn"> |
|
50 |
- 등록 |
|
51 |
- </button> |
|
52 |
- </div> |
|
53 |
- </div> |
|
23 |
+ <div class="textbook-add" style="width: 22.5%; margin-bottom: 30px;"> |
|
24 |
+ <button @click="buttonSearch"> |
|
25 |
+ <img src="../../../resources/img/btn32_98t_normal.png" alt=""> |
|
26 |
+ </button> |
|
54 | 27 |
</div> |
28 |
+ </div> |
|
29 |
+ </div> |
|
30 |
+ <div v-show="searchOpen" class="popup-wrap"> |
|
31 |
+ <div class="popup-box"> |
|
32 |
+ <div class="flex justify-between mb30"> |
|
33 |
+ <p class="popup-title">교재 이름</p> |
|
34 |
+ <button type="button" class="popup-close-btn" @click="closeBtn"> |
|
35 |
+ <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon> |
|
36 |
+ </button> |
|
37 |
+ </div> |
|
38 |
+ <div class="search-wrap mb30"> |
|
39 |
+ <input v-model="searchKeyword" type="text" placeholder="검색하세요." @keyup.enter="bookDataSearch"/> |
|
40 |
+ <button type="button" @click="bookDataSearch()" title="교재 검색"> |
|
41 |
+ <img src="../../../resources/img/look_t.png" alt="" /> |
|
42 |
+ </button> |
|
43 |
+ </div> |
|
44 |
+ <div class="table-wrap"> |
|
45 |
+ <div v-if="searchResults.length"> |
|
46 |
+ <tbody> |
|
47 |
+ <tr v-for="book in searchResults" :key="book.book_id"> |
|
48 |
+ <td>{{ book.book_nm }}<button type="button" title="등록" class="new-btn" @click="insertBook(book.book_id)" style="margin-left: 10px;">추가</button></td> |
|
49 |
+ </tr> |
|
50 |
+ </tbody> |
|
51 |
+ </div> |
|
52 |
+ <div v-else> |
|
53 |
+ <p>검색 결과가 없습니다.</p> |
|
54 |
+ </div> |
|
55 |
+</div> |
|
56 |
+ |
|
57 |
+ </div> |
|
58 |
+</div> |
|
55 | 59 |
</template> |
56 | 60 |
|
57 | 61 |
<script> |
58 | 62 |
import SvgIcon from '@jamescoyle/vue-icon'; |
59 | 63 |
import { mdiMagnify, mdiWindowClose } from '@mdi/js'; |
64 |
+import axios from 'axios'; |
|
60 | 65 |
|
61 | 66 |
export default { |
62 | 67 |
data () { |
... | ... | @@ -64,21 +69,38 @@ |
64 | 69 |
mdiWindowClose: mdiWindowClose, |
65 | 70 |
showModal: false, |
66 | 71 |
searchOpen: false, |
72 |
+ classList: [], |
|
73 |
+ textbookList: [], |
|
74 |
+ newBookName: '', |
|
75 |
+ editMode: false, |
|
76 |
+ editBookId: null, |
|
77 |
+ selectedClassId: '', |
|
78 |
+ searchOpen: false, |
|
79 |
+ searchKeyword: '', |
|
80 |
+ searchResults: [], |
|
67 | 81 |
} |
68 | 82 |
}, |
69 | 83 |
methods: { |
70 |
- goToPage(page) { |
|
71 |
- this.$router.push({ name: page }); |
|
72 |
- }, |
|
73 |
- closeModal() { |
|
74 |
- this.showModal = false; |
|
84 |
+ goToPage(page, book_id) { |
|
85 |
+ this.$router.push({ name: page, query: { book_id: book_id } }); |
|
75 | 86 |
}, |
76 | 87 |
buttonSearch() { |
77 | 88 |
this.searchOpen = true; |
78 | 89 |
}, |
79 | 90 |
closeBtn() { |
80 | 91 |
this.searchOpen = false; |
81 |
- |
|
92 |
+ this.editMode = false; |
|
93 |
+ this.editBookId = null; |
|
94 |
+ this.newBookName = ''; |
|
95 |
+ }, |
|
96 |
+ editBook(book) { |
|
97 |
+ this.newBookName = book.book_nm; |
|
98 |
+ this.editMode = true; |
|
99 |
+ this.editBookId = book.book_id; |
|
100 |
+ this.searchOpen = true; |
|
101 |
+ }, |
|
102 |
+ closeModal() { |
|
103 |
+ this.showModal = false; |
|
82 | 104 |
}, |
83 | 105 |
showConfirm(type) { |
84 | 106 |
let message = ''; |
... | ... | @@ -94,22 +116,131 @@ |
94 | 116 |
this.goBack(); |
95 | 117 |
} |
96 | 118 |
}, |
119 |
+ bookDataSearch() { |
|
120 |
+ const vm = this; |
|
121 |
+ const searchPayload = { |
|
122 |
+ keyword: vm.searchKeyword, |
|
123 |
+ }; |
|
124 |
+ axios.post("/book/search.json", searchPayload) |
|
125 |
+ .then(function (res) { |
|
126 |
+ console.log("bookDataSearch - response : ", res.data); |
|
127 |
+ vm.searchResults = res.data.result; // 검색 결과 저장 |
|
128 |
+ }) |
|
129 |
+ .catch(function (error) { |
|
130 |
+ console.log("bookSearch - error : ", error); |
|
131 |
+ alert("책이 존재하지 않습니다."); |
|
132 |
+ }); |
|
133 |
+ }, |
|
134 |
+ stdClassesSelectList() { |
|
135 |
+ const vm = this; |
|
136 |
+ axios({ |
|
137 |
+ url: "/classes/selectClass.json", |
|
138 |
+ method: "post", |
|
139 |
+ headers:{ |
|
140 |
+ "Content-Type": "application/json; charset=UTF-8", |
|
141 |
+ }, |
|
142 |
+ data: { |
|
143 |
+ userId:"USID_000000000000001" |
|
144 |
+ } |
|
145 |
+ }) |
|
146 |
+ .then(function (response) { |
|
147 |
+ console.log("classList - response : ", response.data); |
|
148 |
+ vm.classList = response.data.data; |
|
149 |
+ }) |
|
150 |
+ .catch(function (error) { |
|
151 |
+ console.log("classList - error : ", error); |
|
152 |
+ alert("학생 반 조회에 오류가 발생했습니다."); |
|
153 |
+ }); |
|
154 |
+ }, |
|
155 |
+ bookList() { |
|
156 |
+ console.log('Fetching books for class ID:', this.selectedClassId); |
|
157 |
+ if (!this.selectedClassId) return; |
|
97 | 158 |
|
159 |
+ const vm = this; |
|
160 |
+ axios({ |
|
161 |
+ url: "/classBook/findAll.json", |
|
162 |
+ method: "post", |
|
163 |
+ headers: { |
|
164 |
+ "Content-Type": "application/json; charset=UTF-8", |
|
165 |
+ }, |
|
166 |
+ data: { |
|
167 |
+ sclsId: this.selectedClassId |
|
168 |
+ } |
|
169 |
+ }) |
|
170 |
+ .then(response => { |
|
171 |
+ console.log("bookList - response : ", response.data); |
|
172 |
+ vm.textbookList = response.data.result; |
|
173 |
+ }) |
|
174 |
+ .catch(error => { |
|
175 |
+ console.error('Error fetching books:', error); |
|
176 |
+ }); |
|
177 |
+ }, |
|
178 |
+ insertBook(bookId) { |
|
179 |
+ const bookToAdd = [{ |
|
180 |
+ bookId: bookId, |
|
181 |
+ sclsId: this.selectedClassId |
|
182 |
+ }]; |
|
183 |
+ |
|
184 |
+ axios.post('/classBook/register.json', bookToAdd) |
|
185 |
+ .then(response => { |
|
186 |
+ console.log('Book added successfully:', response.data); |
|
187 |
+ this.bookList(); |
|
188 |
+ }) |
|
189 |
+ .catch(error => { |
|
190 |
+ console.error('Error adding book:', error); |
|
191 |
+ }); |
|
192 |
+ }, |
|
193 |
+ deleteBook(bookId) { |
|
194 |
+ if (confirm('삭제하시겠습니까?')) { |
|
195 |
+ axios({ |
|
196 |
+ url: "/classBook/delete.json", |
|
197 |
+ method: "post", |
|
198 |
+ headers: { |
|
199 |
+ "Content-Type": "application/json; charset=UTF-8", |
|
200 |
+ }, |
|
201 |
+ data: { |
|
202 |
+ bookId: bookId, |
|
203 |
+ sclsId: this.selectedClassId |
|
204 |
+ }, |
|
205 |
+ }) |
|
206 |
+ .then(response => { |
|
207 |
+ this.bookList(); |
|
208 |
+ }) |
|
209 |
+ .catch(error => { |
|
210 |
+ console.error('Error deleting book:', error); |
|
211 |
+ }); |
|
212 |
+ } |
|
213 |
+ }, |
|
98 | 214 |
}, |
99 | 215 |
watch: { |
100 |
- |
|
101 |
- }, |
|
102 |
- computed: { |
|
103 |
- |
|
216 |
+ selectedClassId(newVal) { |
|
217 |
+ if (newVal) { |
|
218 |
+ this.bookList(); |
|
219 |
+ } |
|
220 |
+ } |
|
104 | 221 |
}, |
105 | 222 |
components: { |
106 | 223 |
SvgIcon |
107 | 224 |
}, |
108 | 225 |
mounted() { |
109 | 226 |
console.log('Main2 mounted'); |
227 |
+ this.stdClassesSelectList(); |
|
110 | 228 |
} |
111 | 229 |
} |
230 |
+ |
|
112 | 231 |
</script> |
232 |
+ |
|
113 | 233 |
<style scoped> |
114 |
-.textbook{width: 300px;} |
|
234 |
+.content-t { |
|
235 |
+ flex-wrap: wrap; |
|
236 |
+ height: 90%; |
|
237 |
+ overflow-y: scroll; |
|
238 |
+} |
|
239 |
+.flex { |
|
240 |
+ display: flex; |
|
241 |
+ flex-wrap: wrap; |
|
242 |
+} |
|
243 |
+.textbook, .textbook-add { |
|
244 |
+ margin-bottom: 30px; |
|
245 |
+} |
|
115 | 246 |
</style>(No newline at end of file) |
--- client/views/pages/teacher/QuestionDetail.vue
+++ client/views/pages/teacher/QuestionDetail.vue
... | ... | @@ -199,6 +199,7 @@ |
199 | 199 |
<script> |
200 | 200 |
import SvgIcon from '@jamescoyle/vue-icon'; |
201 | 201 |
import { mdiMagnify } from '@mdi/js'; |
202 |
+import axios from 'axios'; |
|
202 | 203 |
|
203 | 204 |
export default { |
204 | 205 |
data() { |
--- client/views/pages/teacher/TextBookDetail.vue
+++ client/views/pages/teacher/TextBookDetail.vue
... | ... | @@ -1,18 +1,13 @@ |
1 | 1 |
<template> |
2 | 2 |
<div class="title-box flex justify-between mb40"> |
3 |
- <p class="title">A교재</p> |
|
4 |
- <!-- <select name="" id=""> |
|
5 |
- <option value="">1단원</option> |
|
6 |
- </select> --> |
|
7 |
- </div> |
|
3 |
+ <p class="title">{{ bookDetails ? bookDetails.book_nm : 'Loading...' }}</p> |
|
4 |
+ </div> |
|
8 | 5 |
<label for="" class="title1">단원</label> |
9 | 6 |
<div class="unit-pagination flex mt10" style="gap: 10px;"> |
10 | 7 |
<button class="selected-btn">1</button> |
11 | 8 |
<button>2</button> |
12 | 9 |
<button>3</button> |
13 |
- <button ><svg-icon type="mdi" :path="mdiPlus" style=" padding-top: 6px; |
|
14 |
- width: 30px; |
|
15 |
- height: 30px;"></svg-icon></button> |
|
10 |
+ <button ><svg-icon type="mdi" :path="mdiPlus" style=" padding-top: 6px; width: 30px; height: 30px;"></svg-icon></button> |
|
16 | 11 |
</div> |
17 | 12 |
<div class="board-wrap mt30"> |
18 | 13 |
<div class="mb20 "> |
... | ... | @@ -177,57 +172,52 @@ |
177 | 172 |
|
178 | 173 |
<script> |
179 | 174 |
import SvgIcon from '@jamescoyle/vue-icon'; |
180 |
-import { mdiMagnify,mdiPlus } from '@mdi/js'; |
|
175 |
+import { mdiMagnify, mdiPlus } from '@mdi/js'; |
|
181 | 176 |
import { mdilArrowRight } from '@mdi/light-js'; |
182 | 177 |
import ProgressBar from '../../component/ProgressBar.vue'; |
183 |
- |
|
178 |
+import axios from 'axios'; |
|
184 | 179 |
|
185 | 180 |
export default { |
186 | 181 |
data() { |
187 | 182 |
return { |
188 |
- mdiPlus :mdiPlus , |
|
183 |
+ mdiPlus: mdiPlus, |
|
189 | 184 |
mdiMagnify: mdiMagnify, |
190 | 185 |
mdilArrowRight: mdilArrowRight, |
191 |
- timer: "00:00", |
|
192 |
- progress: 20 |
|
193 |
- } |
|
186 |
+ selectedBookId: this.$route.query.book_Id, |
|
187 |
+ bookDetails: null, |
|
188 |
+ }; |
|
194 | 189 |
}, |
195 | 190 |
methods: { |
196 | 191 |
goToPage(page) { |
197 | 192 |
this.$router.push({ name: page }); |
198 | 193 |
}, |
199 |
- increaseProgress() { |
|
200 |
- if (this.progress < 100) { |
|
201 |
- this.progress += 10; |
|
202 |
- } |
|
203 |
- }, |
|
204 |
- showConfirm(type) { |
|
205 |
- let message = ''; |
|
206 |
- if (type === 'delete') { |
|
207 |
- message = '삭제하시겠습니까?'; |
|
208 |
- } else if (type === 'reset') { |
|
209 |
- message = '초기화하시겠습니까?'; |
|
210 |
- } else if (type === 'save') { |
|
211 |
- message = '등록하시겠습니까?'; |
|
212 |
- } |
|
213 |
- |
|
214 |
- if (confirm(message)) { |
|
215 |
- this.goBack(); |
|
216 |
- } |
|
217 |
- }, |
|
194 |
+ fetchBookDetails() { |
|
195 |
+ axios({ |
|
196 |
+ url: "/book/find.json", |
|
197 |
+ method: "post", |
|
198 |
+ headers: { |
|
199 |
+ "Content-Type": "application/json; charset=UTF-8", |
|
200 |
+ }, |
|
201 |
+ data: { |
|
202 |
+ book_id: this.$route.query.book_id |
|
203 |
+ } |
|
204 |
+ }) |
|
205 |
+ .then(response => { |
|
206 |
+ console.log('Book details:', response.data); |
|
207 |
+ this.bookDetails = response.data; |
|
208 |
+ }) |
|
209 |
+ .catch(error => { |
|
210 |
+ console.error('Error fetching book details:', error); |
|
211 |
+ }); |
|
212 |
+ } |
|
218 | 213 |
}, |
219 |
- watch: { |
|
220 |
- |
|
221 |
- }, |
|
222 |
- computed: { |
|
223 |
- |
|
214 |
+ mounted() { |
|
215 |
+ console.log('Mounted with book_id:', this.$route.query.book_id); |
|
216 |
+ this.fetchBookDetails(); |
|
224 | 217 |
}, |
225 | 218 |
components: { |
226 | 219 |
SvgIcon, |
227 | 220 |
ProgressBar |
228 |
- }, |
|
229 |
- mounted() { |
|
230 |
- console.log('Main2 mounted'); |
|
231 | 221 |
} |
232 | 222 |
} |
233 | 223 |
</script> |
--- client/views/pages/teacher/VocaDetail.vue
+++ client/views/pages/teacher/VocaDetail.vue
... | ... | @@ -1,6 +1,6 @@ |
1 | 1 |
<template> |
2 | 2 |
<div class="title-box flex justify-between mb40"> |
3 |
- <p class="title">단어장 등록</p> |
|
3 |
+ <p class="title">단어장 수정</p> |
|
4 | 4 |
</div> |
5 | 5 |
<div class="board-wrap"> |
6 | 6 |
<div class="flex align-center mb20"> |
... | ... | @@ -36,7 +36,7 @@ |
36 | 36 |
</div> |
37 | 37 |
</div> |
38 | 38 |
<div class="flex justify-between mt50"> |
39 |
- <button type="button" title="글쓰기" class="new-btn" @click="goToPage('TextList')"> |
|
39 |
+ <button type="button" title="글쓰기" class="new-btn" @click="goToPage('VocaList')"> |
|
40 | 40 |
목록 |
41 | 41 |
</button> |
42 | 42 |
<div class="flex"> |
--- client/views/pages/teacher/VocaInsert.vue
+++ client/views/pages/teacher/VocaInsert.vue
... | ... | @@ -2,48 +2,56 @@ |
2 | 2 |
<div class="title-box flex justify-between mb40"> |
3 | 3 |
<p class="title">단어장 등록</p> |
4 | 4 |
</div> |
5 |
+ <div class="title2 gray flex mb40">{{ titleMessage }}</div> |
|
5 | 6 |
<div class="board-wrap"> |
6 | 7 |
<div class="flex align-center mb20"> |
7 | 8 |
<label for="" class="title2">지문</label> |
8 |
- <select v-model="selectedSearchOption" class="mr10 data-wrap"> |
|
9 |
- <option value="bbsTtl">제목</option> |
|
10 |
- <option value="bbsCnt">내용</option> |
|
11 |
- <option value="userNm">작성자</option> |
|
12 |
- <option value="bbsCls">카테고리</option> |
|
9 |
+ <select v-model="selectedTextId" class="mr10 data-wrap"> |
|
10 |
+ <option v-for="text in texts" :key="text.textId" :value="text.textId"> |
|
11 |
+ {{ text.textTtl }} |
|
12 |
+ </option> |
|
13 |
+ </select> |
|
14 |
+ </div> |
|
15 |
+ <div class="flex align-center mb20"> |
|
16 |
+ <label for="" class="title2">단어장 타입</label> |
|
17 |
+ <select v-model="selectedWdBookTypeId" class="mr10 data-wrap"> |
|
18 |
+ <option value="1">단어장 (일반)</option> |
|
19 |
+ <option value="2">단어장 (스피킹)</option> |
|
20 |
+ <option value="3">단어장 (숏폼)</option> |
|
21 |
+ <option value="4">단어장 (카드 뒤집기)</option> |
|
22 |
+ <option value="5">단어장 (카드 맞추기)</option> |
|
13 | 23 |
</select> |
14 | 24 |
</div> |
15 | 25 |
<div class="flex align-center"> |
16 | 26 |
<label for="" class="title2">단어 목록</label> |
17 | 27 |
<div class="flex-column" style="gap: 10px;"> |
18 | 28 |
<div class="flex align-center" style="gap: 10px;"> |
19 |
- <input type="text" class="data-wrap" placeholder="영어"> |
|
20 |
- <input type="text" class="data-wrap" placeholder="한글"> |
|
21 |
- <button type="button" @click="addThesis"> |
|
29 |
+ <input v-model="newWord.eng" type="text" class="data-wrap" placeholder="영어"> |
|
30 |
+ <input v-model="newWord.kor" type="text" class="data-wrap" placeholder="한글"> |
|
31 |
+ <button type="button" @click="addWord"> |
|
22 | 32 |
<img src="../../../resources/img/btn39_120t_normal.png" alt=""> |
23 |
- |
|
24 | 33 |
</button> |
25 | 34 |
</div> |
26 |
- <div class="flex align-center " style="gap: 10px;" v-for="(thesis, index) in thesised" :key="thesis.id"> |
|
27 |
- <input type="text" class="data-wrap" placeholder="영어"> |
|
28 |
- <input type="text" class="data-wrap" placeholder="한글"> |
|
29 |
- <button type="button" @click="removeThesis(thesis.id)"> |
|
30 |
- <img src="../../../resources/img/btn38_120t_normal.png" alt=""> |
|
31 |
- </button> |
|
32 | 35 |
|
36 |
+ <!-- 여기에 단어장에 소속될 단어들 태그 형태 리스트 --> |
|
37 |
+ <div v-for="(word, index) in words" :key="index" class="word-item flex align-center" style="gap: 10px;"> |
|
38 |
+ <span>{{ word.eng }} / {{ word.kor }}</span> |
|
39 |
+ <button type="button" @click="removeWord(index)">삭제</button> |
|
33 | 40 |
</div> |
41 |
+ |
|
34 | 42 |
</div> |
35 | 43 |
|
36 | 44 |
</div> |
37 | 45 |
</div> |
38 | 46 |
<div class="flex justify-between mt50"> |
39 |
- <button type="button" title="글쓰기" class="new-btn" @click="goToPage('TextList')"> |
|
47 |
+ <button type="button" title="목록" class="new-btn" @click="goToPage('VocaList')"> |
|
40 | 48 |
목록 |
41 | 49 |
</button> |
42 | 50 |
<div class="flex"> |
43 |
- <button type="button" title="글쓰기" class="new-btn mr10"> |
|
51 |
+ <button type="button" title="취소" class="new-btn mr10" @click="cancelAction"> |
|
44 | 52 |
취소 |
45 | 53 |
</button> |
46 |
- <button type="button" title="글쓰기" class="new-btn"> |
|
54 |
+ <button type="button" title="등록" class="new-btn" @click="registerWordBook"> |
|
47 | 55 |
등록 |
48 | 56 |
</button> |
49 | 57 |
</div> |
... | ... | @@ -52,41 +60,199 @@ |
52 | 60 |
|
53 | 61 |
<script> |
54 | 62 |
import SvgIcon from '@jamescoyle/vue-icon'; |
55 |
-import { mdiMagnify, mdiPlusCircleOutline, mdiWindowClose } from '@mdi/js'; |
|
56 |
- |
|
63 |
+import axios from "axios"; |
|
57 | 64 |
|
58 | 65 |
export default { |
59 | 66 |
data() { |
60 | 67 |
return { |
61 |
- thesised: [], |
|
62 |
- mdiPlusCircleOutline: mdiPlusCircleOutline, |
|
63 |
- mdiMagnify: mdiMagnify, |
|
64 |
- mdiWindowClose: mdiWindowClose, |
|
68 |
+ selectedBookId: null, // 추가될 단어장의 소속 책 |
|
69 |
+ selectedUnitId: null, // 추가될 단어장의 소속 단원 |
|
70 |
+ bookName: '', // 책 이름 |
|
71 |
+ unitName: '', // 단원 이름 |
|
72 |
+ titleMessage: '', // 등록 경로 메시지 |
|
73 |
+ texts: [], // 지문 목록 |
|
74 |
+ selectedTextId: null, // 선택된 지문 ID |
|
75 |
+ selectedWdBookTypeId: '1', // 선택된 단어장 타입 ID |
|
76 |
+ newWord: { eng: '', kor: '' }, // 입력된 새 단어 |
|
77 |
+ words: [], // 단어 목록 |
|
78 |
+ existingWords: [], // 기존 단어 목록 저장 |
|
79 |
+ userId: "2" |
|
65 | 80 |
} |
66 | 81 |
}, |
67 | 82 |
methods: { |
68 |
- // 논문실적 버튼 추가 |
|
69 |
- addThesis() { |
|
70 |
- // 고유 ID로 현재 시간의 타임스탬프를 사용 |
|
71 |
- const uniqueId = Date.now(); |
|
72 |
- this.thesised.push({ |
|
73 |
- id: uniqueId, // 고유 ID 추가 |
|
74 | 83 |
|
84 |
+ // 책과 단원 이름을 가져오는 메서드 |
|
85 |
+ fetchBookAndUnitNames() { |
|
86 |
+ // 책 이름 가져오기 |
|
87 |
+ axios.post('/book/findAll.json') |
|
88 |
+ .then(response => { |
|
89 |
+ const book = response.data.find(book => book.book_id === this.selectedBookId); |
|
90 |
+ if (book) { |
|
91 |
+ this.bookName = book.book_nm; |
|
92 |
+ this.updateTitleMessage(); // 책 이름을 가져온 후에 제목 업데이트 |
|
93 |
+ } |
|
94 |
+ }) |
|
95 |
+ .catch(error => { |
|
96 |
+ console.error("책 이름 가져오기 실패: ", error); |
|
97 |
+ }); |
|
98 |
+ |
|
99 |
+ // 단원 이름 가져오기 |
|
100 |
+ axios.post('/unit/unitList.json', { bookId: this.selectedBookId }) |
|
101 |
+ .then(response => { |
|
102 |
+ const unit = response.data.find(unit => unit.unitId === this.selectedUnitId); |
|
103 |
+ if (unit) { |
|
104 |
+ this.unitName = unit.unitName; |
|
105 |
+ this.updateTitleMessage(); // 단원 이름을 가져온 후에 제목 업데이트 |
|
106 |
+ } |
|
107 |
+ }) |
|
108 |
+ .catch(error => { |
|
109 |
+ console.error("단원 이름 가져오기 실패: ", error); |
|
110 |
+ }); |
|
111 |
+ }, |
|
112 |
+ |
|
113 |
+ // 등록 경로 메시지를 업데이트하는 메서드 |
|
114 |
+ updateTitleMessage() { |
|
115 |
+ this.titleMessage = `[${this.bookName}]책 > [${this.unitName}]단원`; |
|
116 |
+ }, |
|
117 |
+ |
|
118 |
+ // 지문 목록을 가져오는 메서드 |
|
119 |
+ fetchTexts() { |
|
120 |
+ axios.post('/text/selectTextList.json', { |
|
121 |
+ page: 1, |
|
122 |
+ pageSize: 100 |
|
123 |
+ }) |
|
124 |
+ .then(response => { |
|
125 |
+ this.texts = response.data.texts; |
|
126 |
+ }) |
|
127 |
+ .catch(error => { |
|
128 |
+ console.error("지문 목록 가져오기 실패: ", error); |
|
75 | 129 |
}); |
76 | 130 |
}, |
77 |
- removeThesis(thesisId) { |
|
78 |
- // ID를 기준으로 교육 정보 객체를 찾아서 삭제 |
|
79 |
- const index = this.thesised.findIndex(thesis => thesis.id === thesisId); |
|
80 |
- if (index !== -1) { |
|
81 |
- this.thesised.splice(index, 1); |
|
131 |
+ |
|
132 |
+ addWord() { // 단어 추가 |
|
133 |
+ if (this.newWord.eng && this.newWord.kor) { |
|
134 |
+ this.words.push({ ...this.newWord }); |
|
135 |
+ this.newWord.eng = ''; |
|
136 |
+ this.newWord.kor = ''; |
|
137 |
+ } else { |
|
138 |
+ console.log("단어 입력이 비어 있음"); |
|
82 | 139 |
} |
83 | 140 |
}, |
84 |
- // |
|
141 |
+ |
|
142 |
+ removeWord(index) { // 단어 삭제 |
|
143 |
+ this.words.splice(index, 1); |
|
144 |
+ }, |
|
145 |
+ |
|
85 | 146 |
goToPage(page) { |
86 | 147 |
this.$router.push({ name: page }); |
87 | 148 |
}, |
149 |
+ |
|
150 |
+ cancelAction() { |
|
151 |
+ this.$router.go(-1); |
|
152 |
+ }, |
|
153 |
+ |
|
154 |
+ // 기존 단어 조회 메서드 |
|
155 |
+ fetchExistingWords(wdBookId) { |
|
156 |
+ return axios.post('/word/getWordsByBookId.json', { wdBookId: wdBookId }) |
|
157 |
+ .then(response => { |
|
158 |
+ return response.data.words || []; |
|
159 |
+ }) |
|
160 |
+ .catch(error => { |
|
161 |
+ console.error('기존 단어 목록 가져오기 실패:', error); |
|
162 |
+ return []; |
|
163 |
+ }); |
|
164 |
+ }, |
|
165 |
+ |
|
166 |
+ async registerWordBook() { |
|
167 |
+ const vm = this; |
|
168 |
+ |
|
169 |
+ try { |
|
170 |
+ const response = await axios.post('/wordbook/insert.json', { |
|
171 |
+ wdBookTypeId: vm.selectedWdBookTypeId, |
|
172 |
+ textId: vm.selectedTextId, |
|
173 |
+ userId: vm.userId, |
|
174 |
+ bookId: vm.selectedBookId, |
|
175 |
+ unitId: vm.selectedUnitId |
|
176 |
+ }); |
|
177 |
+ |
|
178 |
+ const wdBookId = response.data.wdBookId; |
|
179 |
+ |
|
180 |
+ // 기존 단어 목록 조회 |
|
181 |
+ const existingWords = await vm.fetchExistingWords(wdBookId); |
|
182 |
+ vm.existingWords = existingWords; |
|
183 |
+ |
|
184 |
+ const existingWordNames = existingWords.map(word => word.wdNm); |
|
185 |
+ const wordsToInsert = []; |
|
186 |
+ const wordsToUpdate = []; |
|
187 |
+ const wordsToDelete = []; |
|
188 |
+ |
|
189 |
+ // 새로 추가된 단어와 기존 단어 비교 |
|
190 |
+ vm.words.forEach(word => { |
|
191 |
+ if (existingWordNames.includes(word.eng)) { |
|
192 |
+ wordsToUpdate.push(word); |
|
193 |
+ } else { |
|
194 |
+ wordsToInsert.push(word); |
|
195 |
+ } |
|
196 |
+ }); |
|
197 |
+ |
|
198 |
+ // 삭제된 단어 목록 찾기 |
|
199 |
+ existingWords.forEach(word => { |
|
200 |
+ if (!vm.words.find(newWord => newWord.eng === word.wdNm)) { |
|
201 |
+ wordsToDelete.push(word); |
|
202 |
+ } |
|
203 |
+ }); |
|
204 |
+ |
|
205 |
+ // 단어 삽입 |
|
206 |
+ for (const word of wordsToInsert) { |
|
207 |
+ await axios.post('/word/insert.json', { |
|
208 |
+ wdBookId: wdBookId, |
|
209 |
+ wdNm: word.eng, |
|
210 |
+ wdMnng: word.kor, |
|
211 |
+ fileMngId: '1' |
|
212 |
+ }); |
|
213 |
+ } |
|
214 |
+ |
|
215 |
+ // 단어 업데이트 |
|
216 |
+ for (const word of wordsToUpdate) { |
|
217 |
+ await axios.post('/word/update.json', { |
|
218 |
+ wdBookId: wdBookId, |
|
219 |
+ wdNm: word.eng, |
|
220 |
+ wdMnng: word.kor, |
|
221 |
+ fileMngId: '1' |
|
222 |
+ }); |
|
223 |
+ } |
|
224 |
+ |
|
225 |
+ // 단어 삭제 |
|
226 |
+ for (const word of wordsToDelete) { |
|
227 |
+ const wordToDelete = existingWords.find(existingWord => existingWord.wdNm === word.wdNm); |
|
228 |
+ if (wordToDelete) { |
|
229 |
+ await axios.post('/word/delete.json', { |
|
230 |
+ wdBookId: wdBookId, |
|
231 |
+ wdId: wordToDelete.wdId |
|
232 |
+ }); |
|
233 |
+ } |
|
234 |
+ } |
|
235 |
+ |
|
236 |
+ alert('단어장이 성공적으로 등록되었습니다.'); |
|
237 |
+ vm.goToPage('VocaList'); |
|
238 |
+ |
|
239 |
+ } catch (error) { |
|
240 |
+ console.error('단어장 등록 중 오류 발생:', error); |
|
241 |
+ alert('단어장 등록에 실패했습니다.'); |
|
242 |
+ } |
|
243 |
+ } |
|
244 |
+ |
|
88 | 245 |
}, |
89 | 246 |
watch: { |
247 |
+ |
|
248 |
+ // 데이터 변경 시 등록 경로 메시지 업데이트 |
|
249 |
+ selectedBookId() { |
|
250 |
+ this.fetchBookAndUnitNames(); |
|
251 |
+ }, |
|
252 |
+ |
|
253 |
+ selectedUnitId() { |
|
254 |
+ this.fetchBookAndUnitNames(); |
|
255 |
+ } |
|
90 | 256 |
|
91 | 257 |
}, |
92 | 258 |
computed: { |
... | ... | @@ -96,7 +262,12 @@ |
96 | 262 |
SvgIcon |
97 | 263 |
}, |
98 | 264 |
mounted() { |
99 |
- console.log('Main2 mounted'); |
|
265 |
+ console.log('VocaInsert mounted'); |
|
266 |
+ // 쿼리 전달 받기 |
|
267 |
+ this.selectedBookId = this.$route.query.selectedBookId || null; |
|
268 |
+ this.selectedUnitId = this.$route.query.selectedUnitId || null; |
|
269 |
+ this.fetchTexts(); |
|
270 |
+ this.fetchBookAndUnitNames(); |
|
100 | 271 |
} |
101 | 272 |
} |
102 | 273 |
</script>(No newline at end of file) |
--- client/views/pages/teacher/VocaList.vue
+++ client/views/pages/teacher/VocaList.vue
... | ... | @@ -1,25 +1,27 @@ |
1 | 1 |
<template> |
2 | 2 |
<div class="title-box flex justify-between mb40"> |
3 | 3 |
<p class="title">단어장</p> |
4 |
- <select name="" id=""> |
|
5 |
- <option value="UNIT_000000000000001">A교재</option> |
|
4 |
+ <select v-model="selectedBookId" @change="fetchUnits"> |
|
5 |
+ <option v-for="book in books" :key="book.book_id" :value="book.book_id"> |
|
6 |
+ {{ book.book_nm }} |
|
7 |
+ </option> |
|
6 | 8 |
</select> |
7 | 9 |
</div> |
8 | 10 |
<label for="" class="title2">단원</label> |
9 |
- <div class="unit-pagination flex mt10 mb20" style="gap: 10px;"> |
|
10 |
- <button class="selected-btn">1</button> |
|
11 |
- <button>2</button> |
|
12 |
- <button>3</button> |
|
13 |
- </div> |
|
11 |
+ <div class="unit-pagination flex mt10 mb20" style="gap: 10px;"> |
|
12 |
+ <button v-for="unit in units" :key="unit.unitId" @click="selectUnit(unit.unitId)" :class="{ 'selected-btn': selectedUnitId === unit.unitId }"> |
|
13 |
+ {{ unit.unitName }} |
|
14 |
+ </button> |
|
15 |
+ </div> |
|
14 | 16 |
<div class="search-wrap flex justify-between mb20 align-center"> |
15 |
- <div class="title2 gray">?단원 전체 목록</div> |
|
17 |
+ <div class="title2 gray flex">{{ titleMessage }}</div> |
|
16 | 18 |
<div> |
17 |
- <select name="" id="" class="mr10 data-wrap"> |
|
18 |
- <option value="">지문</option> |
|
19 |
- <option value="">단어</option> |
|
19 |
+ <select v-model="searchType" class="mr10 data-wrap"> |
|
20 |
+ <option value="text">지문</option> |
|
21 |
+ <option value="word">단어</option> |
|
20 | 22 |
</select> |
21 |
- <input type="text" placeholder="검색하세요."> |
|
22 |
- <button type="button" title="단어장 검색"> |
|
23 |
+ <input v-model="searchQuery" type="text" placeholder="검색하세요."> |
|
24 |
+ <button type="button" title="단어장 검색" @click="searchWordBooks"> |
|
23 | 25 |
<img src="../../../resources/img/look_t.png" alt=""> |
24 | 26 |
</button> |
25 | 27 |
</div> |
... | ... | @@ -31,15 +33,13 @@ |
31 | 33 |
<td>지문</td> |
32 | 34 |
<td>단어 목록</td> |
33 | 35 |
<td>작성자</td> |
34 |
- <td>등록일</td> |
|
35 | 36 |
</thead> |
36 | 37 |
<tbody> |
37 | 38 |
<tr v-for="(wordBook, index) in dataList" :key="wordBook.wdBookId" @click="goToViewPage('VocaDetail')"> |
38 | 39 |
<td>{{ createNo(index) }}</td> |
39 | 40 |
<td>{{ wordBook.textTtl }}</td> |
40 | 41 |
<td>{{ wordBook.wordsPreview }}</td> |
41 |
- <td>{{ wordBook.userId }}</td> |
|
42 |
- <td>{{ '' }}</td> |
|
42 |
+ <td>{{ wordBook.userNm }}</td> |
|
43 | 43 |
</tr> |
44 | 44 |
</tbody> |
45 | 45 |
</table> |
... | ... | @@ -55,7 +55,7 @@ |
55 | 55 |
</button> |
56 | 56 |
</article> |
57 | 57 |
<div class="flex justify-end "> |
58 |
- <button type="button" title="등록" class="new-btn" @click="goToPage('VocaInsert')"> |
|
58 |
+ <button type="button" title="등록" class="new-btn" @click="goToViewPage('VocaInsert')"> |
|
59 | 59 |
등록 |
60 | 60 |
</button> |
61 | 61 |
</div> |
... | ... | @@ -72,15 +72,62 @@ |
72 | 72 |
data () { |
73 | 73 |
return { |
74 | 74 |
mdiMagnify: mdiMagnify, |
75 |
+ books: [], |
|
76 |
+ units: [], |
|
77 |
+ selectedBookId: null, // 선택된 책 ID 저장 변수 |
|
78 |
+ selectedUnitId: null, // 선택된 단원 ID 저장 변수 |
|
79 |
+ selectedUnitName: '', // 선택된 단원의 이름 저장 변수 |
|
80 |
+ titleMessage: '', // 타이틀 메시지 변수 |
|
75 | 81 |
dataList: [], |
76 | 82 |
currentPage: 0, |
77 |
- itemsPerPage: 2, |
|
83 |
+ itemsPerPage: 5, |
|
78 | 84 |
totalPosts: 0, |
79 |
- unitId: "UNIT_000000000000001" |
|
85 |
+ searchType: 'text', // 검색 종류를 저장할 변수 |
|
86 |
+ searchQuery: '' // 검색어를 저장할 변수 |
|
80 | 87 |
} |
81 | 88 |
}, |
82 | 89 |
methods: { |
83 | 90 |
|
91 |
+ // 모든 책 목록을 가져오는 메서드 |
|
92 |
+ fetchBooks() { |
|
93 |
+ axios.post('/book/findAll.json') |
|
94 |
+ .then(response => { |
|
95 |
+ this.books = response.data; |
|
96 |
+ if (this.books.length > 0) { |
|
97 |
+ this.selectedBookId = this.books[1].book_id; // 두 번째 책을 선택하도록 기본 설정 |
|
98 |
+ this.fetchUnits(); // 책 선택 후 단원 목록 가져오기 |
|
99 |
+ } |
|
100 |
+ }) |
|
101 |
+ .catch(error => { |
|
102 |
+ console.error("책 목록 가져오기 실패: ", error); |
|
103 |
+ }); |
|
104 |
+ }, |
|
105 |
+ |
|
106 |
+ // 선택된 책의 단원 목록을 가져오는 메서드 |
|
107 |
+ fetchUnits() { |
|
108 |
+ axios.post('/unit/unitList.json', { bookId: this.selectedBookId }) |
|
109 |
+ .then(response => { |
|
110 |
+ this.units = response.data; |
|
111 |
+ if (this.units.length > 0) { |
|
112 |
+ this.selectUnit(this.units[4].unitId); // 다섯 번째 단원을 선택하도록 기본 설정 |
|
113 |
+ } |
|
114 |
+ }) |
|
115 |
+ .catch(error => { |
|
116 |
+ console.error("단원 목록 가져오기 실패: ", error); |
|
117 |
+ }); |
|
118 |
+ }, |
|
119 |
+ |
|
120 |
+ // 단원을 선택했을 때 호출되는 메서드 |
|
121 |
+ selectUnit(unitId) { |
|
122 |
+ this.selectedUnitId = unitId; |
|
123 |
+ const selectedUnit = this.units.find(unit => unit.unitId === unitId); |
|
124 |
+ this.selectedUnitName = selectedUnit ? selectedUnit.unitName : ''; |
|
125 |
+ this.searchQuery = ''; |
|
126 |
+ this.titleMessage = `[${this.selectedUnitName}] 단원 전체 목록`; |
|
127 |
+ this.dataSelectList(); // 단어장 목록 조회 |
|
128 |
+ }, |
|
129 |
+ |
|
130 |
+ // 단어장 목록 조회 메서드 |
|
84 | 131 |
dataSelectList() { |
85 | 132 |
const vm = this; |
86 | 133 |
axios({ |
... | ... | @@ -90,7 +137,7 @@ |
90 | 137 |
"Content-Type": "application/json; charset=UTF-8", |
91 | 138 |
}, |
92 | 139 |
data: { |
93 |
- unitId: vm.unitId, |
|
140 |
+ unitId: vm.selectedUnitId, |
|
94 | 141 |
page: vm.currentPage + 1, |
95 | 142 |
pageSize: vm.itemsPerPage |
96 | 143 |
}, |
... | ... | @@ -134,6 +181,8 @@ |
134 | 181 |
alert("단어장 목록 조회에 오류가 발생했습니다."); |
135 | 182 |
}); |
136 | 183 |
}, |
184 |
+ |
|
185 |
+ // 단어 목록 생략 String 생성 메서드 |
|
137 | 186 |
generateWordsPreview(words) { |
138 | 187 |
const maxLength = 20; // 최대 표시 길이 설정 |
139 | 188 |
const wordString = words.join(', '); |
... | ... | @@ -144,9 +193,13 @@ |
144 | 193 |
return wordString; |
145 | 194 |
} |
146 | 195 |
}, |
196 |
+ |
|
197 |
+ // 단어장 NO 생성 메서드 |
|
147 | 198 |
createNo(index) { |
148 | 199 |
return this.totalPosts - (this.currentPage * this.itemsPerPage + index); |
149 | 200 |
}, |
201 |
+ |
|
202 |
+ // 페이지네이션 이동 메서드 |
|
150 | 203 |
goToPage(page) { |
151 | 204 |
if (page < 0 || page >= this.totalPages) { |
152 | 205 |
return; |
... | ... | @@ -154,17 +207,96 @@ |
154 | 207 |
this.currentPage = page; |
155 | 208 |
this.dataSelectList(); |
156 | 209 |
}, |
210 |
+ |
|
211 |
+ // 페이지 이동 메서드 |
|
157 | 212 |
goToViewPage(page) { |
158 |
- this.$router.push({ name: page }); |
|
213 |
+ this.$router.push({ |
|
214 |
+ name: page, |
|
215 |
+ query: { |
|
216 |
+ selectedBookId: this.selectedBookId, |
|
217 |
+ selectedUnitId: this.selectedUnitId |
|
218 |
+ } |
|
219 |
+ }); |
|
220 |
+ }, |
|
221 |
+ |
|
222 |
+ // 검색 메서드 추가 |
|
223 |
+ searchWordBooks() { |
|
224 |
+ const vm = this; |
|
225 |
+ let url = ''; |
|
226 |
+ let data = {}; |
|
227 |
+ |
|
228 |
+ if (this.searchType === 'text') { |
|
229 |
+ // 지문으로 검색 |
|
230 |
+ url = '/wordbook/findByTextTitle.json'; |
|
231 |
+ data = { |
|
232 |
+ unitId: vm.selectedUnitId, |
|
233 |
+ textTitle: vm.searchQuery, |
|
234 |
+ page: vm.currentPage + 1, |
|
235 |
+ pageSize: vm.itemsPerPage |
|
236 |
+ }; |
|
237 |
+ this.titleMessage = `[${vm.searchQuery}]의 지문 검색 결과`; |
|
238 |
+ } else if (this.searchType === 'word') { |
|
239 |
+ // 단어로 검색 |
|
240 |
+ url = '/wordbook/findByWord.json'; |
|
241 |
+ data = { |
|
242 |
+ unitId: vm.selectedUnitId, |
|
243 |
+ word: vm.searchQuery, |
|
244 |
+ page: vm.currentPage + 1, |
|
245 |
+ pageSize: vm.itemsPerPage |
|
246 |
+ }; |
|
247 |
+ this.titleMessage = `[${vm.searchQuery}]의 단어 검색 결과`; |
|
248 |
+ } |
|
249 |
+ |
|
250 |
+ axios.post(url, data) |
|
251 |
+ .then(function (response) { |
|
252 |
+ console.log("searchWordBooks - response: ", response.data); |
|
253 |
+ const wordBooks = response.data.wordBooks; |
|
254 |
+ vm.totalPosts = response.data.totalWordBooks; |
|
255 |
+ |
|
256 |
+ // 지문 제목 및 단어 목록 가져오기 |
|
257 |
+ const fetchDataPromises = wordBooks.map(wordBook => { |
|
258 |
+ const textTitlePromise = axios.post("/text/selectOneText.json", { |
|
259 |
+ textId: wordBook.textId |
|
260 |
+ }).then(textResponse => { |
|
261 |
+ wordBook.textTtl = textResponse.data[0].text_ttl; |
|
262 |
+ }).catch(error => { |
|
263 |
+ console.error(`${wordBook.textId}으로 지문 제목 가져오기 실패: `, error); |
|
264 |
+ wordBook.textTtl = '제목값없음'; // 오류 시 기본값 설정 |
|
265 |
+ }); |
|
266 |
+ |
|
267 |
+ const wordsPromise = axios.post("/word/getWordsByBookId.json", { |
|
268 |
+ wdBookId: wordBook.wdBookId |
|
269 |
+ }).then(wordsResponse => { |
|
270 |
+ const words = wordsResponse.data.map(word => word.wdNm); |
|
271 |
+ wordBook.wordsPreview = vm.generateWordsPreview(words); |
|
272 |
+ }).catch(error => { |
|
273 |
+ console.error(`${wordBook.wdBookId}으로 단어 목록 가져오기 실패: `, error); |
|
274 |
+ wordBook.wordsPreview = '단어값없음'; // 오류 시 기본값 설정 |
|
275 |
+ }); |
|
276 |
+ |
|
277 |
+ return Promise.all([textTitlePromise, wordsPromise]); |
|
278 |
+ }); |
|
279 |
+ |
|
280 |
+ // 모든 데이터 가져오기 작업이 완료되면 dataList에 데이터 설정 |
|
281 |
+ Promise.all(fetchDataPromises).then(() => { |
|
282 |
+ vm.dataList = wordBooks; |
|
283 |
+ }); |
|
284 |
+ }) |
|
285 |
+ .catch(function (error) { |
|
286 |
+ console.log("searchWordBooks - error: ", error); |
|
287 |
+ alert("단어장 검색에 오류가 발생했습니다."); |
|
288 |
+ }); |
|
159 | 289 |
}, |
160 | 290 |
}, |
161 | 291 |
watch: { |
162 | 292 |
|
163 | 293 |
}, |
164 | 294 |
computed: { |
295 |
+ |
|
165 | 296 |
totalPages() { |
166 | 297 |
return Math.ceil(this.totalPosts / this.itemsPerPage); |
167 | 298 |
}, |
299 |
+ |
|
168 | 300 |
paginationButtons() { |
169 | 301 |
let start = Math.max(0, this.currentPage - 2); |
170 | 302 |
let end = Math.min(start + 5, this.totalPages); |
... | ... | @@ -175,16 +307,18 @@ |
175 | 307 |
|
176 | 308 |
return Array.from({ length: end - start }, (_, i) => start + i + 1); |
177 | 309 |
}, |
310 |
+ |
|
178 | 311 |
startIndex() { |
179 | 312 |
return this.currentPage * this.itemsPerPage; |
180 | 313 |
} |
314 |
+ |
|
181 | 315 |
}, |
182 | 316 |
components:{ |
183 | 317 |
SvgIcon |
184 | 318 |
}, |
185 | 319 |
mounted() { |
186 | 320 |
console.log('Voca Book List Component mounted'); |
187 |
- this.dataSelectList(); |
|
321 |
+ this.fetchBooks(); |
|
188 | 322 |
} |
189 | 323 |
} |
190 | 324 |
</script>(No newline at end of file) |
--- client/views/pages/teacher/textbook.vue
+++ client/views/pages/teacher/textbook.vue
... | ... | @@ -1,61 +1,56 @@ |
1 | 1 |
<template> |
2 | 2 |
<div class="title-box flex justify-between mb40"> |
3 |
- <p class="title">교재</p> |
|
4 |
- <select name="" id=""> |
|
5 |
- <option value="">A반</option> |
|
6 |
- </select> |
|
3 |
+ <p class="title">교재</p> |
|
7 | 4 |
</div> |
8 | 5 |
<div class="content-t"> |
9 |
- <div class="flex" style="gap: 2.5%;" :style="{flexWrap: 'wrap'}"> |
|
10 |
- <div class="textbook" v-for="textbookItem in textbookList" :key="textbookItem.book_id" style="width: 22.5%; margin-bottom: 30px;"> |
|
11 |
- <div class="box" style="gap: 10px;" @click="goToPage('TextBookDetail', textbookItem.book_id)"> |
|
12 |
- </div> |
|
13 |
- <div class="text"> |
|
14 |
- <p class="title1" style="color: #fff;">{{ textbookItem.book_nm }}</p> |
|
15 |
- <div class="btnGroup mt15 flex align-center justify-end" style="gap: 10px;"> |
|
16 |
- <button @click="editBook(textbookItem)">수정</button> |
|
17 |
- <p>|</p> |
|
18 |
- <button @click="deleteBook(textbookItem.book_id)">삭제</button> |
|
19 |
- </div> |
|
20 |
- </div> |
|
6 |
+ <div class="flex" style="gap: 2.5%;" :style="{flexWrap: 'wrap'}"> |
|
7 |
+ <div class="textbook" v-for="textbookItem in textbookList" :key="textbookItem.book_id" style="width: 22.5%; margin-bottom: 30px;"> |
|
8 |
+ <div class="box" style="gap: 10px;" @click="goToPage('TextBookDetail', textbookItem.book_id)"> |
|
9 |
+ </div> |
|
10 |
+ <div class="text"> |
|
11 |
+ <p class="title1" style="color: #fff;">{{ textbookItem.book_nm }}</p> |
|
12 |
+ <div class="btnGroup mt15 flex align-center justify-end" style="gap: 10px;"> |
|
13 |
+ <button @click="editBook(textbookItem)">수정</button> |
|
14 |
+ <p>|</p> |
|
15 |
+ <button @click="deleteBook(textbookItem.book_id)">삭제</button> |
|
21 | 16 |
</div> |
22 |
- |
|
23 |
- <div class="textbook-add" style="width: 22.5%; margin-bottom: 30px;"> |
|
24 |
- <button @click="buttonSearch"> |
|
25 |
- <img src="../../../resources/img/btn32_98t_normal.png" alt=""> |
|
26 |
- </button> |
|
27 |
- </div> |
|
28 |
- |
|
29 |
- <div v-show="searchOpen" class="popup-wrap"> |
|
30 |
- <div class="popup-box"> |
|
31 |
- <div class="flex justify-between mb30"> |
|
32 |
- <p class="popup-title">교재 이름</p> |
|
33 |
- <button type="button" class="popup-close-btn" @click="closeBtn"> |
|
34 |
- <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon> |
|
35 |
- </button> |
|
36 |
- </div> |
|
37 |
- <div class="search-wrap mb30"> |
|
38 |
- <input type="text" class="data-wrap" v-model="newBookName" placeholder="교재 이름을 입력하세요"> |
|
39 |
- <button type="button" title="교재 검색" @click="insertBook"> |
|
40 |
- <img src="../../../resources/img/look_t.png" alt=""> |
|
41 |
- </button> |
|
42 |
- </div> |
|
43 |
- <div class="flex justify-center"> |
|
44 |
- <button type="button" title="취소" class="new-btn mr10" @click="closeBtn"> |
|
45 |
- 취소 |
|
46 |
- </button> |
|
47 |
- <button type="button" title="생성" class="new-btn" @click="editMode ? updateBook() : insertBook()"> |
|
48 |
- {{ editMode ? '수정' : '생성' }} |
|
49 |
- </button> |
|
50 |
- </div> |
|
51 |
- </div> |
|
52 |
- </div> |
|
17 |
+ </div> |
|
53 | 18 |
</div> |
19 |
+ |
|
20 |
+ <div class="textbook-add" style="width: 22.5%; margin-bottom: 30px;"> |
|
21 |
+ <button @click="buttonSearch"> |
|
22 |
+ <img src="../../../resources/img/btn32_98t_normal.png" alt=""> |
|
23 |
+ </button> |
|
24 |
+ </div> |
|
25 |
+ |
|
26 |
+ <div v-show="searchOpen" class="popup-wrap"> |
|
27 |
+ <div class="popup-box"> |
|
28 |
+ <div class="flex justify-between mb30"> |
|
29 |
+ <p class="popup-title">교재 이름</p> |
|
30 |
+ <button type="button" class="popup-close-btn" @click="closeBtn"> |
|
31 |
+ <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon> |
|
32 |
+ </button> |
|
33 |
+ </div> |
|
34 |
+ <div class="search-wrap mb30"> |
|
35 |
+ <input type="text" class="data-wrap" v-model="newBookName" placeholder="교재 이름을 입력하세요"> |
|
36 |
+ <button type="button" title="교재 검색" @click="insertBook"> |
|
37 |
+ <img src="../../../resources/img/look_t.png" alt=""> |
|
38 |
+ </button> |
|
39 |
+ </div> |
|
40 |
+ <div class="flex justify-center"> |
|
41 |
+ <button type="button" title="취소" class="new-btn mr10" @click="closeBtn"> |
|
42 |
+ 취소 |
|
43 |
+ </button> |
|
44 |
+ <button type="button" title="생성" class="new-btn" @click="editMode ? updateBook() : insertBook()"> |
|
45 |
+ {{ editMode ? '수정' : '생성' }} |
|
46 |
+ </button> |
|
47 |
+ </div> |
|
48 |
+ </div> |
|
49 |
+ </div> |
|
50 |
+ </div> |
|
54 | 51 |
</div> |
55 | 52 |
</template> |
56 |
- |
|
57 |
- |
|
58 |
- |
|
53 |
+ |
|
59 | 54 |
<script> |
60 | 55 |
import SvgIcon from '@jamescoyle/vue-icon'; |
61 | 56 |
import { mdiWindowClose } from '@mdi/js'; |
... | ... | @@ -75,7 +70,7 @@ |
75 | 70 |
}, |
76 | 71 |
methods: { |
77 | 72 |
goToPage(page, book_id) { |
78 |
- this.$router.push({ name: page, query: { book_id }}); |
|
73 |
+ this.$router.push({ name: page, query: { book_id: book_id } }); |
|
79 | 74 |
}, |
80 | 75 |
buttonSearch() { |
81 | 76 |
this.searchOpen = true; |
... | ... | @@ -160,10 +155,7 @@ |
160 | 155 |
console.error('Error deleting book:', error); |
161 | 156 |
}); |
162 | 157 |
} |
163 |
- } |
|
164 |
- }, |
|
165 |
- computed: { |
|
166 |
- |
|
158 |
+ }, |
|
167 | 159 |
}, |
168 | 160 |
components: { |
169 | 161 |
SvgIcon |
... | ... | @@ -185,4 +177,4 @@ |
185 | 177 |
display: flex; |
186 | 178 |
flex-wrap: wrap; |
187 | 179 |
} |
188 |
-</style>(No newline at end of file) |
|
180 |
+</style> |
--- package-lock.json
+++ package-lock.json
... | ... | @@ -5,7 +5,7 @@ |
5 | 5 |
"packages": { |
6 | 6 |
"": { |
7 | 7 |
"dependencies": { |
8 |
- "@amcharts/amcharts4": "^4.10.39", |
|
8 |
+ "@amcharts/amcharts5": "^5.10.1", |
|
9 | 9 |
"@babel/cli": "7.19.3", |
10 | 10 |
"@babel/core": "7.19.3", |
11 | 11 |
"@jamescoyle/vue-icon": "^0.1.2", |
... | ... | @@ -34,26 +34,37 @@ |
34 | 34 |
"webpack-cli": "^5.1.4" |
35 | 35 |
} |
36 | 36 |
}, |
37 |
- "node_modules/@amcharts/amcharts4": { |
|
38 |
- "version": "4.10.39", |
|
39 |
- "resolved": "https://registry.npmjs.org/@amcharts/amcharts4/-/amcharts4-4.10.39.tgz", |
|
40 |
- "integrity": "sha512-5WbpZgI0m0Mf8Ydwlm1XWB8hIzkk6fJifzYmJqo5HLdA8jCQa+4I+8uOlGlvSMxbBTkvxanEgA2WX27+99X44w==", |
|
37 |
+ "node_modules/@amcharts/amcharts5": { |
|
38 |
+ "version": "5.10.1", |
|
39 |
+ "resolved": "https://registry.npmjs.org/@amcharts/amcharts5/-/amcharts5-5.10.1.tgz", |
|
40 |
+ "integrity": "sha512-oGTZ7QJ/AEiMgJ6W3xzX7dSTK47Zl4j44ZsHiWbdAU0BKSQPmzw/jGgj806/ki2Ym4wuxWvE8dCg6josmNnVDg==", |
|
41 | 41 |
"license": "SEE LICENSE IN LICENSE", |
42 | 42 |
"dependencies": { |
43 |
- "@babel/runtime": "^7.6.3", |
|
44 |
- "core-js": "^3.0.0", |
|
43 |
+ "@types/d3": "^7.0.0", |
|
44 |
+ "@types/d3-chord": "^3.0.0", |
|
45 |
+ "@types/d3-hierarchy": "3.1.1", |
|
46 |
+ "@types/d3-sankey": "^0.11.1", |
|
47 |
+ "@types/d3-shape": "^3.0.0", |
|
48 |
+ "@types/geojson": "^7946.0.8", |
|
49 |
+ "@types/polylabel": "^1.0.5", |
|
50 |
+ "@types/svg-arc-to-cubic-bezier": "^3.2.0", |
|
51 |
+ "d3": "^7.0.0", |
|
52 |
+ "d3-chord": "^3.0.0", |
|
45 | 53 |
"d3-force": "^3.0.0", |
46 |
- "d3-geo": "^3.0.1", |
|
47 |
- "d3-geo-projection": "^4.0.0", |
|
54 |
+ "d3-geo": "^3.0.0", |
|
55 |
+ "d3-hierarchy": "^3.0.0", |
|
56 |
+ "d3-sankey": "^0.12.3", |
|
48 | 57 |
"d3-selection": "^3.0.0", |
49 |
- "d3-transition": "^3.0.1", |
|
58 |
+ "d3-shape": "^3.0.0", |
|
59 |
+ "d3-transition": "^3.0.0", |
|
60 |
+ "d3-voronoi-treemap": "^1.1.2", |
|
61 |
+ "flatpickr": "^4.6.9", |
|
62 |
+ "markerjs2": "^2.29.4", |
|
50 | 63 |
"pdfmake": "^0.2.2", |
51 |
- "polylabel": "^1.0.2", |
|
52 |
- "raf": "^3.4.1", |
|
53 |
- "regression": "^2.0.1", |
|
54 |
- "rgbcolor": "^1.0.1", |
|
55 |
- "stackblur-canvas": "^2.0.0", |
|
56 |
- "tslib": "^2.0.1" |
|
64 |
+ "polylabel": "^1.1.0", |
|
65 |
+ "seedrandom": "^3.0.5", |
|
66 |
+ "svg-arc-to-cubic-bezier": "^3.2.0", |
|
67 |
+ "tslib": "^2.2.0" |
|
57 | 68 |
} |
58 | 69 |
}, |
59 | 70 |
"node_modules/@ampproject/remapping": { |
... | ... | @@ -292,18 +303,6 @@ |
292 | 303 |
"node": ">=6.0.0" |
293 | 304 |
} |
294 | 305 |
}, |
295 |
- "node_modules/@babel/runtime": { |
|
296 |
- "version": "7.25.0", |
|
297 |
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", |
|
298 |
- "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", |
|
299 |
- "license": "MIT", |
|
300 |
- "dependencies": { |
|
301 |
- "regenerator-runtime": "^0.14.0" |
|
302 |
- }, |
|
303 |
- "engines": { |
|
304 |
- "node": ">=6.9.0" |
|
305 |
- } |
|
306 |
- }, |
|
307 | 306 |
"node_modules/@babel/template": { |
308 | 307 |
"version": "7.25.0", |
309 | 308 |
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", |
... | ... | @@ -487,6 +486,283 @@ |
487 | 486 |
"license": "MIT", |
488 | 487 |
"optional": true |
489 | 488 |
}, |
489 |
+ "node_modules/@types/d3": { |
|
490 |
+ "version": "7.4.3", |
|
491 |
+ "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", |
|
492 |
+ "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", |
|
493 |
+ "license": "MIT", |
|
494 |
+ "dependencies": { |
|
495 |
+ "@types/d3-array": "*", |
|
496 |
+ "@types/d3-axis": "*", |
|
497 |
+ "@types/d3-brush": "*", |
|
498 |
+ "@types/d3-chord": "*", |
|
499 |
+ "@types/d3-color": "*", |
|
500 |
+ "@types/d3-contour": "*", |
|
501 |
+ "@types/d3-delaunay": "*", |
|
502 |
+ "@types/d3-dispatch": "*", |
|
503 |
+ "@types/d3-drag": "*", |
|
504 |
+ "@types/d3-dsv": "*", |
|
505 |
+ "@types/d3-ease": "*", |
|
506 |
+ "@types/d3-fetch": "*", |
|
507 |
+ "@types/d3-force": "*", |
|
508 |
+ "@types/d3-format": "*", |
|
509 |
+ "@types/d3-geo": "*", |
|
510 |
+ "@types/d3-hierarchy": "*", |
|
511 |
+ "@types/d3-interpolate": "*", |
|
512 |
+ "@types/d3-path": "*", |
|
513 |
+ "@types/d3-polygon": "*", |
|
514 |
+ "@types/d3-quadtree": "*", |
|
515 |
+ "@types/d3-random": "*", |
|
516 |
+ "@types/d3-scale": "*", |
|
517 |
+ "@types/d3-scale-chromatic": "*", |
|
518 |
+ "@types/d3-selection": "*", |
|
519 |
+ "@types/d3-shape": "*", |
|
520 |
+ "@types/d3-time": "*", |
|
521 |
+ "@types/d3-time-format": "*", |
|
522 |
+ "@types/d3-timer": "*", |
|
523 |
+ "@types/d3-transition": "*", |
|
524 |
+ "@types/d3-zoom": "*" |
|
525 |
+ } |
|
526 |
+ }, |
|
527 |
+ "node_modules/@types/d3-array": { |
|
528 |
+ "version": "3.2.1", |
|
529 |
+ "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", |
|
530 |
+ "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", |
|
531 |
+ "license": "MIT" |
|
532 |
+ }, |
|
533 |
+ "node_modules/@types/d3-axis": { |
|
534 |
+ "version": "3.0.6", |
|
535 |
+ "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", |
|
536 |
+ "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", |
|
537 |
+ "license": "MIT", |
|
538 |
+ "dependencies": { |
|
539 |
+ "@types/d3-selection": "*" |
|
540 |
+ } |
|
541 |
+ }, |
|
542 |
+ "node_modules/@types/d3-brush": { |
|
543 |
+ "version": "3.0.6", |
|
544 |
+ "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", |
|
545 |
+ "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", |
|
546 |
+ "license": "MIT", |
|
547 |
+ "dependencies": { |
|
548 |
+ "@types/d3-selection": "*" |
|
549 |
+ } |
|
550 |
+ }, |
|
551 |
+ "node_modules/@types/d3-chord": { |
|
552 |
+ "version": "3.0.6", |
|
553 |
+ "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", |
|
554 |
+ "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", |
|
555 |
+ "license": "MIT" |
|
556 |
+ }, |
|
557 |
+ "node_modules/@types/d3-color": { |
|
558 |
+ "version": "3.1.3", |
|
559 |
+ "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", |
|
560 |
+ "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", |
|
561 |
+ "license": "MIT" |
|
562 |
+ }, |
|
563 |
+ "node_modules/@types/d3-contour": { |
|
564 |
+ "version": "3.0.6", |
|
565 |
+ "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", |
|
566 |
+ "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", |
|
567 |
+ "license": "MIT", |
|
568 |
+ "dependencies": { |
|
569 |
+ "@types/d3-array": "*", |
|
570 |
+ "@types/geojson": "*" |
|
571 |
+ } |
|
572 |
+ }, |
|
573 |
+ "node_modules/@types/d3-delaunay": { |
|
574 |
+ "version": "6.0.4", |
|
575 |
+ "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", |
|
576 |
+ "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", |
|
577 |
+ "license": "MIT" |
|
578 |
+ }, |
|
579 |
+ "node_modules/@types/d3-dispatch": { |
|
580 |
+ "version": "3.0.6", |
|
581 |
+ "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", |
|
582 |
+ "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==", |
|
583 |
+ "license": "MIT" |
|
584 |
+ }, |
|
585 |
+ "node_modules/@types/d3-drag": { |
|
586 |
+ "version": "3.0.7", |
|
587 |
+ "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", |
|
588 |
+ "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", |
|
589 |
+ "license": "MIT", |
|
590 |
+ "dependencies": { |
|
591 |
+ "@types/d3-selection": "*" |
|
592 |
+ } |
|
593 |
+ }, |
|
594 |
+ "node_modules/@types/d3-dsv": { |
|
595 |
+ "version": "3.0.7", |
|
596 |
+ "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", |
|
597 |
+ "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", |
|
598 |
+ "license": "MIT" |
|
599 |
+ }, |
|
600 |
+ "node_modules/@types/d3-ease": { |
|
601 |
+ "version": "3.0.2", |
|
602 |
+ "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", |
|
603 |
+ "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", |
|
604 |
+ "license": "MIT" |
|
605 |
+ }, |
|
606 |
+ "node_modules/@types/d3-fetch": { |
|
607 |
+ "version": "3.0.7", |
|
608 |
+ "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", |
|
609 |
+ "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", |
|
610 |
+ "license": "MIT", |
|
611 |
+ "dependencies": { |
|
612 |
+ "@types/d3-dsv": "*" |
|
613 |
+ } |
|
614 |
+ }, |
|
615 |
+ "node_modules/@types/d3-force": { |
|
616 |
+ "version": "3.0.10", |
|
617 |
+ "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", |
|
618 |
+ "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", |
|
619 |
+ "license": "MIT" |
|
620 |
+ }, |
|
621 |
+ "node_modules/@types/d3-format": { |
|
622 |
+ "version": "3.0.4", |
|
623 |
+ "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", |
|
624 |
+ "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", |
|
625 |
+ "license": "MIT" |
|
626 |
+ }, |
|
627 |
+ "node_modules/@types/d3-geo": { |
|
628 |
+ "version": "3.1.0", |
|
629 |
+ "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", |
|
630 |
+ "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", |
|
631 |
+ "license": "MIT", |
|
632 |
+ "dependencies": { |
|
633 |
+ "@types/geojson": "*" |
|
634 |
+ } |
|
635 |
+ }, |
|
636 |
+ "node_modules/@types/d3-hierarchy": { |
|
637 |
+ "version": "3.1.1", |
|
638 |
+ "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.1.tgz", |
|
639 |
+ "integrity": "sha512-QwjxA3+YCKH3N1Rs3uSiSy1bdxlLB1uUiENXeJudBoAFvtDuswUxLcanoOaR2JYn1melDTuIXR8VhnVyI3yG/A==", |
|
640 |
+ "license": "MIT" |
|
641 |
+ }, |
|
642 |
+ "node_modules/@types/d3-interpolate": { |
|
643 |
+ "version": "3.0.4", |
|
644 |
+ "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", |
|
645 |
+ "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", |
|
646 |
+ "license": "MIT", |
|
647 |
+ "dependencies": { |
|
648 |
+ "@types/d3-color": "*" |
|
649 |
+ } |
|
650 |
+ }, |
|
651 |
+ "node_modules/@types/d3-path": { |
|
652 |
+ "version": "3.1.0", |
|
653 |
+ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", |
|
654 |
+ "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==", |
|
655 |
+ "license": "MIT" |
|
656 |
+ }, |
|
657 |
+ "node_modules/@types/d3-polygon": { |
|
658 |
+ "version": "3.0.2", |
|
659 |
+ "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", |
|
660 |
+ "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", |
|
661 |
+ "license": "MIT" |
|
662 |
+ }, |
|
663 |
+ "node_modules/@types/d3-quadtree": { |
|
664 |
+ "version": "3.0.6", |
|
665 |
+ "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", |
|
666 |
+ "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", |
|
667 |
+ "license": "MIT" |
|
668 |
+ }, |
|
669 |
+ "node_modules/@types/d3-random": { |
|
670 |
+ "version": "3.0.3", |
|
671 |
+ "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", |
|
672 |
+ "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", |
|
673 |
+ "license": "MIT" |
|
674 |
+ }, |
|
675 |
+ "node_modules/@types/d3-sankey": { |
|
676 |
+ "version": "0.11.2", |
|
677 |
+ "resolved": "https://registry.npmjs.org/@types/d3-sankey/-/d3-sankey-0.11.2.tgz", |
|
678 |
+ "integrity": "sha512-U6SrTWUERSlOhnpSrgvMX64WblX1AxX6nEjI2t3mLK2USpQrnbwYYK+AS9SwiE7wgYmOsSSKoSdr8aoKBH0HgQ==", |
|
679 |
+ "license": "MIT", |
|
680 |
+ "dependencies": { |
|
681 |
+ "@types/d3-shape": "^1" |
|
682 |
+ } |
|
683 |
+ }, |
|
684 |
+ "node_modules/@types/d3-sankey/node_modules/@types/d3-path": { |
|
685 |
+ "version": "1.0.11", |
|
686 |
+ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.11.tgz", |
|
687 |
+ "integrity": "sha512-4pQMp8ldf7UaB/gR8Fvvy69psNHkTpD/pVw3vmEi8iZAB9EPMBruB1JvHO4BIq9QkUUd2lV1F5YXpMNj7JPBpw==", |
|
688 |
+ "license": "MIT" |
|
689 |
+ }, |
|
690 |
+ "node_modules/@types/d3-sankey/node_modules/@types/d3-shape": { |
|
691 |
+ "version": "1.3.12", |
|
692 |
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-1.3.12.tgz", |
|
693 |
+ "integrity": "sha512-8oMzcd4+poSLGgV0R1Q1rOlx/xdmozS4Xab7np0eamFFUYq71AU9pOCJEFnkXW2aI/oXdVYJzw6pssbSut7Z9Q==", |
|
694 |
+ "license": "MIT", |
|
695 |
+ "dependencies": { |
|
696 |
+ "@types/d3-path": "^1" |
|
697 |
+ } |
|
698 |
+ }, |
|
699 |
+ "node_modules/@types/d3-scale": { |
|
700 |
+ "version": "4.0.8", |
|
701 |
+ "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", |
|
702 |
+ "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", |
|
703 |
+ "license": "MIT", |
|
704 |
+ "dependencies": { |
|
705 |
+ "@types/d3-time": "*" |
|
706 |
+ } |
|
707 |
+ }, |
|
708 |
+ "node_modules/@types/d3-scale-chromatic": { |
|
709 |
+ "version": "3.0.3", |
|
710 |
+ "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz", |
|
711 |
+ "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==", |
|
712 |
+ "license": "MIT" |
|
713 |
+ }, |
|
714 |
+ "node_modules/@types/d3-selection": { |
|
715 |
+ "version": "3.0.10", |
|
716 |
+ "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.10.tgz", |
|
717 |
+ "integrity": "sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==", |
|
718 |
+ "license": "MIT" |
|
719 |
+ }, |
|
720 |
+ "node_modules/@types/d3-shape": { |
|
721 |
+ "version": "3.1.6", |
|
722 |
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", |
|
723 |
+ "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", |
|
724 |
+ "license": "MIT", |
|
725 |
+ "dependencies": { |
|
726 |
+ "@types/d3-path": "*" |
|
727 |
+ } |
|
728 |
+ }, |
|
729 |
+ "node_modules/@types/d3-time": { |
|
730 |
+ "version": "3.0.3", |
|
731 |
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", |
|
732 |
+ "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==", |
|
733 |
+ "license": "MIT" |
|
734 |
+ }, |
|
735 |
+ "node_modules/@types/d3-time-format": { |
|
736 |
+ "version": "4.0.3", |
|
737 |
+ "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", |
|
738 |
+ "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", |
|
739 |
+ "license": "MIT" |
|
740 |
+ }, |
|
741 |
+ "node_modules/@types/d3-timer": { |
|
742 |
+ "version": "3.0.2", |
|
743 |
+ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", |
|
744 |
+ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", |
|
745 |
+ "license": "MIT" |
|
746 |
+ }, |
|
747 |
+ "node_modules/@types/d3-transition": { |
|
748 |
+ "version": "3.0.8", |
|
749 |
+ "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.8.tgz", |
|
750 |
+ "integrity": "sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==", |
|
751 |
+ "license": "MIT", |
|
752 |
+ "dependencies": { |
|
753 |
+ "@types/d3-selection": "*" |
|
754 |
+ } |
|
755 |
+ }, |
|
756 |
+ "node_modules/@types/d3-zoom": { |
|
757 |
+ "version": "3.0.8", |
|
758 |
+ "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", |
|
759 |
+ "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", |
|
760 |
+ "license": "MIT", |
|
761 |
+ "dependencies": { |
|
762 |
+ "@types/d3-interpolate": "*", |
|
763 |
+ "@types/d3-selection": "*" |
|
764 |
+ } |
|
765 |
+ }, |
|
490 | 766 |
"node_modules/@types/eslint": { |
491 | 767 |
"version": "9.6.0", |
492 | 768 |
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.0.tgz", |
... | ... | @@ -512,6 +788,12 @@ |
512 | 788 |
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", |
513 | 789 |
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" |
514 | 790 |
}, |
791 |
+ "node_modules/@types/geojson": { |
|
792 |
+ "version": "7946.0.14", |
|
793 |
+ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", |
|
794 |
+ "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==", |
|
795 |
+ "license": "MIT" |
|
796 |
+ }, |
|
515 | 797 |
"node_modules/@types/json-schema": { |
516 | 798 |
"version": "7.0.15", |
517 | 799 |
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", |
... | ... | @@ -526,6 +808,18 @@ |
526 | 808 |
"dependencies": { |
527 | 809 |
"undici-types": "~6.11.1" |
528 | 810 |
} |
811 |
+ }, |
|
812 |
+ "node_modules/@types/polylabel": { |
|
813 |
+ "version": "1.1.3", |
|
814 |
+ "resolved": "https://registry.npmjs.org/@types/polylabel/-/polylabel-1.1.3.tgz", |
|
815 |
+ "integrity": "sha512-9Zw2KoDpi+T4PZz2G6pO2xArE0m/GSMTW1MIxF2s8ZY8x9XDO6fv9um0ydRGvcbkFLlaq8yNK6eZxnmMZtDgWQ==", |
|
816 |
+ "license": "MIT" |
|
817 |
+ }, |
|
818 |
+ "node_modules/@types/svg-arc-to-cubic-bezier": { |
|
819 |
+ "version": "3.2.2", |
|
820 |
+ "resolved": "https://registry.npmjs.org/@types/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.2.tgz", |
|
821 |
+ "integrity": "sha512-XQtKy9lmkKlV+c3Jelo7kxNPw7qOqIq3GcnOhywGZHF7zw5D5m+Ssigbmf3Turbe/A8Ur+lRh8TYjuxXKvyivw==", |
|
822 |
+ "license": "MIT" |
|
529 | 823 |
}, |
530 | 824 |
"node_modules/@vue/compiler-core": { |
531 | 825 |
"version": "3.2.40", |
... | ... | @@ -1342,17 +1636,6 @@ |
1342 | 1636 |
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", |
1343 | 1637 |
"license": "MIT" |
1344 | 1638 |
}, |
1345 |
- "node_modules/core-js": { |
|
1346 |
- "version": "3.38.0", |
|
1347 |
- "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.38.0.tgz", |
|
1348 |
- "integrity": "sha512-XPpwqEodRljce9KswjZShh95qJ1URisBeKCjUdq27YdenkslVe7OO0ZJhlYXAChW7OhXaRLl8AAba7IBfoIHug==", |
|
1349 |
- "hasInstallScript": true, |
|
1350 |
- "license": "MIT", |
|
1351 |
- "funding": { |
|
1352 |
- "type": "opencollective", |
|
1353 |
- "url": "https://opencollective.com/core-js" |
|
1354 |
- } |
|
1355 |
- }, |
|
1356 | 1639 |
"node_modules/cross-spawn": { |
1357 | 1640 |
"version": "7.0.3", |
1358 | 1641 |
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", |
... | ... | @@ -1430,6 +1713,47 @@ |
1430 | 1713 |
"integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==", |
1431 | 1714 |
"license": "MIT" |
1432 | 1715 |
}, |
1716 |
+ "node_modules/d3": { |
|
1717 |
+ "version": "7.9.0", |
|
1718 |
+ "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", |
|
1719 |
+ "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", |
|
1720 |
+ "license": "ISC", |
|
1721 |
+ "dependencies": { |
|
1722 |
+ "d3-array": "3", |
|
1723 |
+ "d3-axis": "3", |
|
1724 |
+ "d3-brush": "3", |
|
1725 |
+ "d3-chord": "3", |
|
1726 |
+ "d3-color": "3", |
|
1727 |
+ "d3-contour": "4", |
|
1728 |
+ "d3-delaunay": "6", |
|
1729 |
+ "d3-dispatch": "3", |
|
1730 |
+ "d3-drag": "3", |
|
1731 |
+ "d3-dsv": "3", |
|
1732 |
+ "d3-ease": "3", |
|
1733 |
+ "d3-fetch": "3", |
|
1734 |
+ "d3-force": "3", |
|
1735 |
+ "d3-format": "3", |
|
1736 |
+ "d3-geo": "3", |
|
1737 |
+ "d3-hierarchy": "3", |
|
1738 |
+ "d3-interpolate": "3", |
|
1739 |
+ "d3-path": "3", |
|
1740 |
+ "d3-polygon": "3", |
|
1741 |
+ "d3-quadtree": "3", |
|
1742 |
+ "d3-random": "3", |
|
1743 |
+ "d3-scale": "4", |
|
1744 |
+ "d3-scale-chromatic": "3", |
|
1745 |
+ "d3-selection": "3", |
|
1746 |
+ "d3-shape": "3", |
|
1747 |
+ "d3-time": "3", |
|
1748 |
+ "d3-time-format": "4", |
|
1749 |
+ "d3-timer": "3", |
|
1750 |
+ "d3-transition": "3", |
|
1751 |
+ "d3-zoom": "3" |
|
1752 |
+ }, |
|
1753 |
+ "engines": { |
|
1754 |
+ "node": ">=12" |
|
1755 |
+ } |
|
1756 |
+ }, |
|
1433 | 1757 |
"node_modules/d3-array": { |
1434 | 1758 |
"version": "3.2.4", |
1435 | 1759 |
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", |
... | ... | @@ -1437,6 +1761,43 @@ |
1437 | 1761 |
"license": "ISC", |
1438 | 1762 |
"dependencies": { |
1439 | 1763 |
"internmap": "1 - 2" |
1764 |
+ }, |
|
1765 |
+ "engines": { |
|
1766 |
+ "node": ">=12" |
|
1767 |
+ } |
|
1768 |
+ }, |
|
1769 |
+ "node_modules/d3-axis": { |
|
1770 |
+ "version": "3.0.0", |
|
1771 |
+ "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", |
|
1772 |
+ "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", |
|
1773 |
+ "license": "ISC", |
|
1774 |
+ "engines": { |
|
1775 |
+ "node": ">=12" |
|
1776 |
+ } |
|
1777 |
+ }, |
|
1778 |
+ "node_modules/d3-brush": { |
|
1779 |
+ "version": "3.0.0", |
|
1780 |
+ "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", |
|
1781 |
+ "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", |
|
1782 |
+ "license": "ISC", |
|
1783 |
+ "dependencies": { |
|
1784 |
+ "d3-dispatch": "1 - 3", |
|
1785 |
+ "d3-drag": "2 - 3", |
|
1786 |
+ "d3-interpolate": "1 - 3", |
|
1787 |
+ "d3-selection": "3", |
|
1788 |
+ "d3-transition": "3" |
|
1789 |
+ }, |
|
1790 |
+ "engines": { |
|
1791 |
+ "node": ">=12" |
|
1792 |
+ } |
|
1793 |
+ }, |
|
1794 |
+ "node_modules/d3-chord": { |
|
1795 |
+ "version": "3.0.1", |
|
1796 |
+ "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", |
|
1797 |
+ "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", |
|
1798 |
+ "license": "ISC", |
|
1799 |
+ "dependencies": { |
|
1800 |
+ "d3-path": "1 - 3" |
|
1440 | 1801 |
}, |
1441 | 1802 |
"engines": { |
1442 | 1803 |
"node": ">=12" |
... | ... | @@ -1451,6 +1812,30 @@ |
1451 | 1812 |
"node": ">=12" |
1452 | 1813 |
} |
1453 | 1814 |
}, |
1815 |
+ "node_modules/d3-contour": { |
|
1816 |
+ "version": "4.0.2", |
|
1817 |
+ "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", |
|
1818 |
+ "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", |
|
1819 |
+ "license": "ISC", |
|
1820 |
+ "dependencies": { |
|
1821 |
+ "d3-array": "^3.2.0" |
|
1822 |
+ }, |
|
1823 |
+ "engines": { |
|
1824 |
+ "node": ">=12" |
|
1825 |
+ } |
|
1826 |
+ }, |
|
1827 |
+ "node_modules/d3-delaunay": { |
|
1828 |
+ "version": "6.0.4", |
|
1829 |
+ "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", |
|
1830 |
+ "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", |
|
1831 |
+ "license": "ISC", |
|
1832 |
+ "dependencies": { |
|
1833 |
+ "delaunator": "5" |
|
1834 |
+ }, |
|
1835 |
+ "engines": { |
|
1836 |
+ "node": ">=12" |
|
1837 |
+ } |
|
1838 |
+ }, |
|
1454 | 1839 |
"node_modules/d3-dispatch": { |
1455 | 1840 |
"version": "3.0.1", |
1456 | 1841 |
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", |
... | ... | @@ -1460,11 +1845,82 @@ |
1460 | 1845 |
"node": ">=12" |
1461 | 1846 |
} |
1462 | 1847 |
}, |
1848 |
+ "node_modules/d3-drag": { |
|
1849 |
+ "version": "3.0.0", |
|
1850 |
+ "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", |
|
1851 |
+ "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", |
|
1852 |
+ "license": "ISC", |
|
1853 |
+ "dependencies": { |
|
1854 |
+ "d3-dispatch": "1 - 3", |
|
1855 |
+ "d3-selection": "3" |
|
1856 |
+ }, |
|
1857 |
+ "engines": { |
|
1858 |
+ "node": ">=12" |
|
1859 |
+ } |
|
1860 |
+ }, |
|
1861 |
+ "node_modules/d3-dsv": { |
|
1862 |
+ "version": "3.0.1", |
|
1863 |
+ "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", |
|
1864 |
+ "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", |
|
1865 |
+ "license": "ISC", |
|
1866 |
+ "dependencies": { |
|
1867 |
+ "commander": "7", |
|
1868 |
+ "iconv-lite": "0.6", |
|
1869 |
+ "rw": "1" |
|
1870 |
+ }, |
|
1871 |
+ "bin": { |
|
1872 |
+ "csv2json": "bin/dsv2json.js", |
|
1873 |
+ "csv2tsv": "bin/dsv2dsv.js", |
|
1874 |
+ "dsv2dsv": "bin/dsv2dsv.js", |
|
1875 |
+ "dsv2json": "bin/dsv2json.js", |
|
1876 |
+ "json2csv": "bin/json2dsv.js", |
|
1877 |
+ "json2dsv": "bin/json2dsv.js", |
|
1878 |
+ "json2tsv": "bin/json2dsv.js", |
|
1879 |
+ "tsv2csv": "bin/dsv2dsv.js", |
|
1880 |
+ "tsv2json": "bin/dsv2json.js" |
|
1881 |
+ }, |
|
1882 |
+ "engines": { |
|
1883 |
+ "node": ">=12" |
|
1884 |
+ } |
|
1885 |
+ }, |
|
1886 |
+ "node_modules/d3-dsv/node_modules/commander": { |
|
1887 |
+ "version": "7.2.0", |
|
1888 |
+ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", |
|
1889 |
+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", |
|
1890 |
+ "license": "MIT", |
|
1891 |
+ "engines": { |
|
1892 |
+ "node": ">= 10" |
|
1893 |
+ } |
|
1894 |
+ }, |
|
1895 |
+ "node_modules/d3-dsv/node_modules/iconv-lite": { |
|
1896 |
+ "version": "0.6.3", |
|
1897 |
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", |
|
1898 |
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", |
|
1899 |
+ "license": "MIT", |
|
1900 |
+ "dependencies": { |
|
1901 |
+ "safer-buffer": ">= 2.1.2 < 3.0.0" |
|
1902 |
+ }, |
|
1903 |
+ "engines": { |
|
1904 |
+ "node": ">=0.10.0" |
|
1905 |
+ } |
|
1906 |
+ }, |
|
1463 | 1907 |
"node_modules/d3-ease": { |
1464 | 1908 |
"version": "3.0.1", |
1465 | 1909 |
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", |
1466 | 1910 |
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", |
1467 | 1911 |
"license": "BSD-3-Clause", |
1912 |
+ "engines": { |
|
1913 |
+ "node": ">=12" |
|
1914 |
+ } |
|
1915 |
+ }, |
|
1916 |
+ "node_modules/d3-fetch": { |
|
1917 |
+ "version": "3.0.1", |
|
1918 |
+ "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", |
|
1919 |
+ "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", |
|
1920 |
+ "license": "ISC", |
|
1921 |
+ "dependencies": { |
|
1922 |
+ "d3-dsv": "1 - 3" |
|
1923 |
+ }, |
|
1468 | 1924 |
"engines": { |
1469 | 1925 |
"node": ">=12" |
1470 | 1926 |
} |
... | ... | @@ -1483,6 +1939,15 @@ |
1483 | 1939 |
"node": ">=12" |
1484 | 1940 |
} |
1485 | 1941 |
}, |
1942 |
+ "node_modules/d3-format": { |
|
1943 |
+ "version": "3.1.0", |
|
1944 |
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", |
|
1945 |
+ "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", |
|
1946 |
+ "license": "ISC", |
|
1947 |
+ "engines": { |
|
1948 |
+ "node": ">=12" |
|
1949 |
+ } |
|
1950 |
+ }, |
|
1486 | 1951 |
"node_modules/d3-geo": { |
1487 | 1952 |
"version": "3.1.1", |
1488 | 1953 |
"resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", |
... | ... | @@ -1495,34 +1960,13 @@ |
1495 | 1960 |
"node": ">=12" |
1496 | 1961 |
} |
1497 | 1962 |
}, |
1498 |
- "node_modules/d3-geo-projection": { |
|
1499 |
- "version": "4.0.0", |
|
1500 |
- "resolved": "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-4.0.0.tgz", |
|
1501 |
- "integrity": "sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==", |
|
1963 |
+ "node_modules/d3-hierarchy": { |
|
1964 |
+ "version": "3.1.2", |
|
1965 |
+ "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", |
|
1966 |
+ "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", |
|
1502 | 1967 |
"license": "ISC", |
1503 |
- "dependencies": { |
|
1504 |
- "commander": "7", |
|
1505 |
- "d3-array": "1 - 3", |
|
1506 |
- "d3-geo": "1.12.0 - 3" |
|
1507 |
- }, |
|
1508 |
- "bin": { |
|
1509 |
- "geo2svg": "bin/geo2svg.js", |
|
1510 |
- "geograticule": "bin/geograticule.js", |
|
1511 |
- "geoproject": "bin/geoproject.js", |
|
1512 |
- "geoquantize": "bin/geoquantize.js", |
|
1513 |
- "geostitch": "bin/geostitch.js" |
|
1514 |
- }, |
|
1515 | 1968 |
"engines": { |
1516 | 1969 |
"node": ">=12" |
1517 |
- } |
|
1518 |
- }, |
|
1519 |
- "node_modules/d3-geo-projection/node_modules/commander": { |
|
1520 |
- "version": "7.2.0", |
|
1521 |
- "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", |
|
1522 |
- "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", |
|
1523 |
- "license": "MIT", |
|
1524 |
- "engines": { |
|
1525 |
- "node": ">= 10" |
|
1526 | 1970 |
} |
1527 | 1971 |
}, |
1528 | 1972 |
"node_modules/d3-interpolate": { |
... | ... | @@ -1537,6 +1981,24 @@ |
1537 | 1981 |
"node": ">=12" |
1538 | 1982 |
} |
1539 | 1983 |
}, |
1984 |
+ "node_modules/d3-path": { |
|
1985 |
+ "version": "3.1.0", |
|
1986 |
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", |
|
1987 |
+ "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", |
|
1988 |
+ "license": "ISC", |
|
1989 |
+ "engines": { |
|
1990 |
+ "node": ">=12" |
|
1991 |
+ } |
|
1992 |
+ }, |
|
1993 |
+ "node_modules/d3-polygon": { |
|
1994 |
+ "version": "3.0.1", |
|
1995 |
+ "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", |
|
1996 |
+ "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", |
|
1997 |
+ "license": "ISC", |
|
1998 |
+ "engines": { |
|
1999 |
+ "node": ">=12" |
|
2000 |
+ } |
|
2001 |
+ }, |
|
1540 | 2002 |
"node_modules/d3-quadtree": { |
1541 | 2003 |
"version": "3.0.1", |
1542 | 2004 |
"resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", |
... | ... | @@ -1546,11 +2008,125 @@ |
1546 | 2008 |
"node": ">=12" |
1547 | 2009 |
} |
1548 | 2010 |
}, |
2011 |
+ "node_modules/d3-random": { |
|
2012 |
+ "version": "3.0.1", |
|
2013 |
+ "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", |
|
2014 |
+ "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", |
|
2015 |
+ "license": "ISC", |
|
2016 |
+ "engines": { |
|
2017 |
+ "node": ">=12" |
|
2018 |
+ } |
|
2019 |
+ }, |
|
2020 |
+ "node_modules/d3-sankey": { |
|
2021 |
+ "version": "0.12.3", |
|
2022 |
+ "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", |
|
2023 |
+ "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", |
|
2024 |
+ "license": "BSD-3-Clause", |
|
2025 |
+ "dependencies": { |
|
2026 |
+ "d3-array": "1 - 2", |
|
2027 |
+ "d3-shape": "^1.2.0" |
|
2028 |
+ } |
|
2029 |
+ }, |
|
2030 |
+ "node_modules/d3-sankey/node_modules/d3-array": { |
|
2031 |
+ "version": "2.12.1", |
|
2032 |
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", |
|
2033 |
+ "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", |
|
2034 |
+ "license": "BSD-3-Clause", |
|
2035 |
+ "dependencies": { |
|
2036 |
+ "internmap": "^1.0.0" |
|
2037 |
+ } |
|
2038 |
+ }, |
|
2039 |
+ "node_modules/d3-sankey/node_modules/d3-path": { |
|
2040 |
+ "version": "1.0.9", |
|
2041 |
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", |
|
2042 |
+ "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", |
|
2043 |
+ "license": "BSD-3-Clause" |
|
2044 |
+ }, |
|
2045 |
+ "node_modules/d3-sankey/node_modules/d3-shape": { |
|
2046 |
+ "version": "1.3.7", |
|
2047 |
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", |
|
2048 |
+ "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", |
|
2049 |
+ "license": "BSD-3-Clause", |
|
2050 |
+ "dependencies": { |
|
2051 |
+ "d3-path": "1" |
|
2052 |
+ } |
|
2053 |
+ }, |
|
2054 |
+ "node_modules/d3-sankey/node_modules/internmap": { |
|
2055 |
+ "version": "1.0.1", |
|
2056 |
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", |
|
2057 |
+ "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", |
|
2058 |
+ "license": "ISC" |
|
2059 |
+ }, |
|
2060 |
+ "node_modules/d3-scale": { |
|
2061 |
+ "version": "4.0.2", |
|
2062 |
+ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", |
|
2063 |
+ "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", |
|
2064 |
+ "license": "ISC", |
|
2065 |
+ "dependencies": { |
|
2066 |
+ "d3-array": "2.10.0 - 3", |
|
2067 |
+ "d3-format": "1 - 3", |
|
2068 |
+ "d3-interpolate": "1.2.0 - 3", |
|
2069 |
+ "d3-time": "2.1.1 - 3", |
|
2070 |
+ "d3-time-format": "2 - 4" |
|
2071 |
+ }, |
|
2072 |
+ "engines": { |
|
2073 |
+ "node": ">=12" |
|
2074 |
+ } |
|
2075 |
+ }, |
|
2076 |
+ "node_modules/d3-scale-chromatic": { |
|
2077 |
+ "version": "3.1.0", |
|
2078 |
+ "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", |
|
2079 |
+ "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", |
|
2080 |
+ "license": "ISC", |
|
2081 |
+ "dependencies": { |
|
2082 |
+ "d3-color": "1 - 3", |
|
2083 |
+ "d3-interpolate": "1 - 3" |
|
2084 |
+ }, |
|
2085 |
+ "engines": { |
|
2086 |
+ "node": ">=12" |
|
2087 |
+ } |
|
2088 |
+ }, |
|
1549 | 2089 |
"node_modules/d3-selection": { |
1550 | 2090 |
"version": "3.0.0", |
1551 | 2091 |
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", |
1552 | 2092 |
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", |
1553 | 2093 |
"license": "ISC", |
2094 |
+ "engines": { |
|
2095 |
+ "node": ">=12" |
|
2096 |
+ } |
|
2097 |
+ }, |
|
2098 |
+ "node_modules/d3-shape": { |
|
2099 |
+ "version": "3.2.0", |
|
2100 |
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", |
|
2101 |
+ "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", |
|
2102 |
+ "license": "ISC", |
|
2103 |
+ "dependencies": { |
|
2104 |
+ "d3-path": "^3.1.0" |
|
2105 |
+ }, |
|
2106 |
+ "engines": { |
|
2107 |
+ "node": ">=12" |
|
2108 |
+ } |
|
2109 |
+ }, |
|
2110 |
+ "node_modules/d3-time": { |
|
2111 |
+ "version": "3.1.0", |
|
2112 |
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", |
|
2113 |
+ "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", |
|
2114 |
+ "license": "ISC", |
|
2115 |
+ "dependencies": { |
|
2116 |
+ "d3-array": "2 - 3" |
|
2117 |
+ }, |
|
2118 |
+ "engines": { |
|
2119 |
+ "node": ">=12" |
|
2120 |
+ } |
|
2121 |
+ }, |
|
2122 |
+ "node_modules/d3-time-format": { |
|
2123 |
+ "version": "4.1.0", |
|
2124 |
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", |
|
2125 |
+ "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", |
|
2126 |
+ "license": "ISC", |
|
2127 |
+ "dependencies": { |
|
2128 |
+ "d3-time": "1 - 3" |
|
2129 |
+ }, |
|
1554 | 2130 |
"engines": { |
1555 | 2131 |
"node": ">=12" |
1556 | 2132 |
} |
... | ... | @@ -1581,6 +2157,92 @@ |
1581 | 2157 |
}, |
1582 | 2158 |
"peerDependencies": { |
1583 | 2159 |
"d3-selection": "2 - 3" |
2160 |
+ } |
|
2161 |
+ }, |
|
2162 |
+ "node_modules/d3-voronoi-map": { |
|
2163 |
+ "version": "2.1.1", |
|
2164 |
+ "resolved": "https://registry.npmjs.org/d3-voronoi-map/-/d3-voronoi-map-2.1.1.tgz", |
|
2165 |
+ "integrity": "sha512-mCXfz/kD9IQxjHaU2IMjkO8fSo4J6oysPR2iL+omDsCy1i1Qn6BQ/e4hEAW8C6ms2kfuHwqtbNom80Hih94YsA==", |
|
2166 |
+ "license": "BSD-3-Clause", |
|
2167 |
+ "dependencies": { |
|
2168 |
+ "d3-dispatch": "2.*", |
|
2169 |
+ "d3-polygon": "2.*", |
|
2170 |
+ "d3-timer": "2.*", |
|
2171 |
+ "d3-weighted-voronoi": "1.*" |
|
2172 |
+ } |
|
2173 |
+ }, |
|
2174 |
+ "node_modules/d3-voronoi-map/node_modules/d3-dispatch": { |
|
2175 |
+ "version": "2.0.0", |
|
2176 |
+ "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-2.0.0.tgz", |
|
2177 |
+ "integrity": "sha512-S/m2VsXI7gAti2pBoLClFFTMOO1HTtT0j99AuXLoGFKO6deHDdnv6ZGTxSTTUTgO1zVcv82fCOtDjYK4EECmWA==", |
|
2178 |
+ "license": "BSD-3-Clause" |
|
2179 |
+ }, |
|
2180 |
+ "node_modules/d3-voronoi-map/node_modules/d3-polygon": { |
|
2181 |
+ "version": "2.0.0", |
|
2182 |
+ "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-2.0.0.tgz", |
|
2183 |
+ "integrity": "sha512-MsexrCK38cTGermELs0cO1d79DcTsQRN7IWMJKczD/2kBjzNXxLUWP33qRF6VDpiLV/4EI4r6Gs0DAWQkE8pSQ==", |
|
2184 |
+ "license": "BSD-3-Clause" |
|
2185 |
+ }, |
|
2186 |
+ "node_modules/d3-voronoi-map/node_modules/d3-timer": { |
|
2187 |
+ "version": "2.0.0", |
|
2188 |
+ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-2.0.0.tgz", |
|
2189 |
+ "integrity": "sha512-TO4VLh0/420Y/9dO3+f9abDEFYeCUr2WZRlxJvbp4HPTQcSylXNiL6yZa9FIUvV1yRiFufl1bszTCLDqv9PWNA==", |
|
2190 |
+ "license": "BSD-3-Clause" |
|
2191 |
+ }, |
|
2192 |
+ "node_modules/d3-voronoi-treemap": { |
|
2193 |
+ "version": "1.1.2", |
|
2194 |
+ "resolved": "https://registry.npmjs.org/d3-voronoi-treemap/-/d3-voronoi-treemap-1.1.2.tgz", |
|
2195 |
+ "integrity": "sha512-7odu9HdG/yLPWwzDteJq4yd9Q/NwgQV7IE/u36VQtcCK7k1sZwDqbkHCeMKNTBsq5mQjDwolTsrXcU0j8ZEMCA==", |
|
2196 |
+ "license": "BSD-3-Clause", |
|
2197 |
+ "dependencies": { |
|
2198 |
+ "d3-voronoi-map": "2.*" |
|
2199 |
+ } |
|
2200 |
+ }, |
|
2201 |
+ "node_modules/d3-weighted-voronoi": { |
|
2202 |
+ "version": "1.1.3", |
|
2203 |
+ "resolved": "https://registry.npmjs.org/d3-weighted-voronoi/-/d3-weighted-voronoi-1.1.3.tgz", |
|
2204 |
+ "integrity": "sha512-C3WdvSKl9aqhAy+f3QT3PPsQG6V+ajDfYO3BSclQDSD+araW2xDBFIH67aKzsSuuuKaX8K2y2dGq1fq/dWTVig==", |
|
2205 |
+ "license": "BSD-3-Clause", |
|
2206 |
+ "dependencies": { |
|
2207 |
+ "d3-array": "2", |
|
2208 |
+ "d3-polygon": "2" |
|
2209 |
+ } |
|
2210 |
+ }, |
|
2211 |
+ "node_modules/d3-weighted-voronoi/node_modules/d3-array": { |
|
2212 |
+ "version": "2.12.1", |
|
2213 |
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", |
|
2214 |
+ "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", |
|
2215 |
+ "license": "BSD-3-Clause", |
|
2216 |
+ "dependencies": { |
|
2217 |
+ "internmap": "^1.0.0" |
|
2218 |
+ } |
|
2219 |
+ }, |
|
2220 |
+ "node_modules/d3-weighted-voronoi/node_modules/d3-polygon": { |
|
2221 |
+ "version": "2.0.0", |
|
2222 |
+ "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-2.0.0.tgz", |
|
2223 |
+ "integrity": "sha512-MsexrCK38cTGermELs0cO1d79DcTsQRN7IWMJKczD/2kBjzNXxLUWP33qRF6VDpiLV/4EI4r6Gs0DAWQkE8pSQ==", |
|
2224 |
+ "license": "BSD-3-Clause" |
|
2225 |
+ }, |
|
2226 |
+ "node_modules/d3-weighted-voronoi/node_modules/internmap": { |
|
2227 |
+ "version": "1.0.1", |
|
2228 |
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", |
|
2229 |
+ "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", |
|
2230 |
+ "license": "ISC" |
|
2231 |
+ }, |
|
2232 |
+ "node_modules/d3-zoom": { |
|
2233 |
+ "version": "3.0.0", |
|
2234 |
+ "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", |
|
2235 |
+ "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", |
|
2236 |
+ "license": "ISC", |
|
2237 |
+ "dependencies": { |
|
2238 |
+ "d3-dispatch": "1 - 3", |
|
2239 |
+ "d3-drag": "2 - 3", |
|
2240 |
+ "d3-interpolate": "1 - 3", |
|
2241 |
+ "d3-selection": "2 - 3", |
|
2242 |
+ "d3-transition": "2 - 3" |
|
2243 |
+ }, |
|
2244 |
+ "engines": { |
|
2245 |
+ "node": ">=12" |
|
1584 | 2246 |
} |
1585 | 2247 |
}, |
1586 | 2248 |
"node_modules/debug": { |
... | ... | @@ -1652,6 +2314,15 @@ |
1652 | 2314 |
}, |
1653 | 2315 |
"funding": { |
1654 | 2316 |
"url": "https://github.com/sponsors/ljharb" |
2317 |
+ } |
|
2318 |
+ }, |
|
2319 |
+ "node_modules/delaunator": { |
|
2320 |
+ "version": "5.0.1", |
|
2321 |
+ "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", |
|
2322 |
+ "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", |
|
2323 |
+ "license": "ISC", |
|
2324 |
+ "dependencies": { |
|
2325 |
+ "robust-predicates": "^3.0.2" |
|
1655 | 2326 |
} |
1656 | 2327 |
}, |
1657 | 2328 |
"node_modules/delayed-stream": { |
... | ... | @@ -2105,6 +2776,12 @@ |
2105 | 2776 |
"bin": { |
2106 | 2777 |
"flat": "cli.js" |
2107 | 2778 |
} |
2779 |
+ }, |
|
2780 |
+ "node_modules/flatpickr": { |
|
2781 |
+ "version": "4.6.13", |
|
2782 |
+ "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.13.tgz", |
|
2783 |
+ "integrity": "sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw==", |
|
2784 |
+ "license": "MIT" |
|
2108 | 2785 |
}, |
2109 | 2786 |
"node_modules/follow-redirects": { |
2110 | 2787 |
"version": "1.15.6", |
... | ... | @@ -2786,6 +3463,12 @@ |
2786 | 3463 |
"semver": "bin/semver" |
2787 | 3464 |
} |
2788 | 3465 |
}, |
3466 |
+ "node_modules/markerjs2": { |
|
3467 |
+ "version": "2.32.1", |
|
3468 |
+ "resolved": "https://registry.npmjs.org/markerjs2/-/markerjs2-2.32.1.tgz", |
|
3469 |
+ "integrity": "sha512-OGBINMGhXwTXZF/k0ky9vciPm8C3/bsDZUJroZrIvoX0xv3OWYBEDiUSmgRpiLkCv5Z4Q7RaYxhza/iafc25zw==", |
|
3470 |
+ "license": "SEE LICENSE IN LICENSE" |
|
3471 |
+ }, |
|
2789 | 3472 |
"node_modules/media-typer": { |
2790 | 3473 |
"version": "0.3.0", |
2791 | 3474 |
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", |
... | ... | @@ -3113,12 +3796,6 @@ |
3113 | 3796 |
"node": ">=0.10.0" |
3114 | 3797 |
} |
3115 | 3798 |
}, |
3116 |
- "node_modules/performance-now": { |
|
3117 |
- "version": "2.1.0", |
|
3118 |
- "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", |
|
3119 |
- "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", |
|
3120 |
- "license": "MIT" |
|
3121 |
- }, |
|
3122 | 3799 |
"node_modules/pg": { |
3123 | 3800 |
"version": "8.8.0", |
3124 | 3801 |
"resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz", |
... | ... | @@ -3441,15 +4118,6 @@ |
3441 | 4118 |
"url": "https://github.com/sponsors/ljharb" |
3442 | 4119 |
} |
3443 | 4120 |
}, |
3444 |
- "node_modules/raf": { |
|
3445 |
- "version": "3.4.1", |
|
3446 |
- "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", |
|
3447 |
- "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", |
|
3448 |
- "license": "MIT", |
|
3449 |
- "dependencies": { |
|
3450 |
- "performance-now": "^2.1.0" |
|
3451 |
- } |
|
3452 |
- }, |
|
3453 | 4121 |
"node_modules/randombytes": { |
3454 | 4122 |
"version": "2.1.0", |
3455 | 4123 |
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", |
... | ... | @@ -3508,12 +4176,6 @@ |
3508 | 4176 |
"node": ">= 10.13.0" |
3509 | 4177 |
} |
3510 | 4178 |
}, |
3511 |
- "node_modules/regenerator-runtime": { |
|
3512 |
- "version": "0.14.1", |
|
3513 |
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", |
|
3514 |
- "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", |
|
3515 |
- "license": "MIT" |
|
3516 |
- }, |
|
3517 | 4179 |
"node_modules/regexp.prototype.flags": { |
3518 | 4180 |
"version": "1.5.2", |
3519 | 4181 |
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", |
... | ... | @@ -3531,12 +4193,6 @@ |
3531 | 4193 |
"funding": { |
3532 | 4194 |
"url": "https://github.com/sponsors/ljharb" |
3533 | 4195 |
} |
3534 |
- }, |
|
3535 |
- "node_modules/regression": { |
|
3536 |
- "version": "2.0.1", |
|
3537 |
- "resolved": "https://registry.npmjs.org/regression/-/regression-2.0.1.tgz", |
|
3538 |
- "integrity": "sha512-A4XYsc37dsBaNOgEjkJKzfJlE394IMmUPlI/p3TTI9u3T+2a+eox5Pr/CPUqF0eszeWZJPAc6QkroAhuUpWDJQ==", |
|
3539 |
- "license": "MIT" |
|
3540 | 4196 |
}, |
3541 | 4197 |
"node_modules/resolve": { |
3542 | 4198 |
"version": "1.22.8", |
... | ... | @@ -3578,14 +4234,17 @@ |
3578 | 4234 |
"node": ">=8" |
3579 | 4235 |
} |
3580 | 4236 |
}, |
3581 |
- "node_modules/rgbcolor": { |
|
3582 |
- "version": "1.0.1", |
|
3583 |
- "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", |
|
3584 |
- "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", |
|
3585 |
- "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", |
|
3586 |
- "engines": { |
|
3587 |
- "node": ">= 0.8.15" |
|
3588 |
- } |
|
4237 |
+ "node_modules/robust-predicates": { |
|
4238 |
+ "version": "3.0.2", |
|
4239 |
+ "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", |
|
4240 |
+ "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", |
|
4241 |
+ "license": "Unlicense" |
|
4242 |
+ }, |
|
4243 |
+ "node_modules/rw": { |
|
4244 |
+ "version": "1.3.3", |
|
4245 |
+ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", |
|
4246 |
+ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", |
|
4247 |
+ "license": "BSD-3-Clause" |
|
3589 | 4248 |
}, |
3590 | 4249 |
"node_modules/safe-buffer": { |
3591 | 4250 |
"version": "5.2.1", |
... | ... | @@ -3636,6 +4295,12 @@ |
3636 | 4295 |
"type": "opencollective", |
3637 | 4296 |
"url": "https://opencollective.com/webpack" |
3638 | 4297 |
} |
4298 |
+ }, |
|
4299 |
+ "node_modules/seedrandom": { |
|
4300 |
+ "version": "3.0.5", |
|
4301 |
+ "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", |
|
4302 |
+ "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==", |
|
4303 |
+ "license": "MIT" |
|
3639 | 4304 |
}, |
3640 | 4305 |
"node_modules/semver": { |
3641 | 4306 |
"version": "6.3.1", |
... | ... | @@ -3860,15 +4525,6 @@ |
3860 | 4525 |
"node": ">= 10.x" |
3861 | 4526 |
} |
3862 | 4527 |
}, |
3863 |
- "node_modules/stackblur-canvas": { |
|
3864 |
- "version": "2.7.0", |
|
3865 |
- "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", |
|
3866 |
- "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", |
|
3867 |
- "license": "MIT", |
|
3868 |
- "engines": { |
|
3869 |
- "node": ">=0.1.14" |
|
3870 |
- } |
|
3871 |
- }, |
|
3872 | 4528 |
"node_modules/statuses": { |
3873 | 4529 |
"version": "2.0.1", |
3874 | 4530 |
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", |
... | ... | @@ -3902,6 +4558,12 @@ |
3902 | 4558 |
"url": "https://github.com/sponsors/ljharb" |
3903 | 4559 |
} |
3904 | 4560 |
}, |
4561 |
+ "node_modules/svg-arc-to-cubic-bezier": { |
|
4562 |
+ "version": "3.2.0", |
|
4563 |
+ "resolved": "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz", |
|
4564 |
+ "integrity": "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==", |
|
4565 |
+ "license": "ISC" |
|
4566 |
+ }, |
|
3905 | 4567 |
"node_modules/tapable": { |
3906 | 4568 |
"version": "2.2.1", |
3907 | 4569 |
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", |
--- package.json
+++ package.json
... | ... | @@ -1,6 +1,6 @@ |
1 | 1 |
{ |
2 | 2 |
"dependencies": { |
3 |
- "@amcharts/amcharts4": "^4.10.39", |
|
3 |
+ "@amcharts/amcharts5": "^5.10.1", |
|
4 | 4 |
"@babel/cli": "7.19.3", |
5 | 5 |
"@babel/core": "7.19.3", |
6 | 6 |
"@jamescoyle/vue-icon": "^0.1.2", |
--- webpack.config.js
+++ webpack.config.js
... | ... | @@ -3,6 +3,7 @@ |
3 | 3 |
const {PROJECT_NAME, BASE_DIR, SERVICE_STATUS} = require('./Global'); |
4 | 4 |
|
5 | 5 |
module.exports = { |
6 |
+ |
|
6 | 7 |
name: PROJECT_NAME, |
7 | 8 |
mode: SERVICE_STATUS, |
8 | 9 |
devtool: 'eval', |
... | ... | @@ -12,6 +13,7 @@ |
12 | 13 |
}, |
13 | 14 |
|
14 | 15 |
module: { |
16 |
+ |
|
15 | 17 |
rules: [{ |
16 | 18 |
test: /\.vue?$/, |
17 | 19 |
loader: 'vue-loader', |
... | ... | @@ -39,4 +41,11 @@ |
39 | 41 |
path: `${BASE_DIR}/client/build`, // __dirname: webpack.config.js 파일이 위치한 경로 |
40 | 42 |
filename: 'bundle.js' |
41 | 43 |
}, |
44 |
+ configureWebpack: { |
|
45 |
+ resolve: { |
|
46 |
+ alias: { |
|
47 |
+ "@amcharts/amcharts5": "@amcharts/amcharts5/dist/amcharts5.esm.js" |
|
48 |
+ } |
|
49 |
+ } |
|
50 |
+ }, |
|
42 | 51 |
}(No newline at end of file) |
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?