
--- client/resources/css/style.css
+++ client/resources/css/style.css
... | ... | @@ -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/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
... | ... | @@ -118,17 +118,16 @@ |
118 | 118 |
</button> |
119 | 119 |
<p class="long">최종 평가</p> |
120 | 120 |
</div> |
121 |
+ |
|
121 | 122 |
</div> |
122 |
- <div class="rabbit-end" @click="ShowPopup"><img src="../../../resources/img/img138_72s.png" alt=""></div> |
|
123 | 123 |
</div> |
124 |
- </div> |
|
125 |
- <!-- 팝업 --> |
|
126 |
- <div v-show="searchOpen2" class="popup-wrap"> |
|
127 |
- <div class="popup-box"> |
|
128 |
- <button type="button" class="popup-close-btn" style="position:absolute; top:10px; right: 10px;" |
|
129 |
- @click="closeModal"> |
|
130 |
- <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon> |
|
131 |
- </button> |
|
124 |
+ <!-- 팝업 --> |
|
125 |
+ <div v-show="searchOpen2" class="popup-wrap"> |
|
126 |
+ <div class="popup-box"> |
|
127 |
+ <button type="button" class="popup-close-btn" style="position:absolute; top:10px; right: 10px;" |
|
128 |
+ @click="closeModal"> |
|
129 |
+ <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon> |
|
130 |
+ </button> |
|
132 | 131 |
|
133 | 132 |
<div class="mb30 text-ct"> |
134 | 133 |
<p class="title1 mb20">1단원이 끝났습니다!</p> |
... | ... | @@ -264,23 +263,22 @@ |
264 | 263 |
</div> |
265 | 264 |
</div> |
266 | 265 |
|
267 |
- <div v-else> |
|
268 |
- <!-- 2번 템플릿 --> |
|
269 |
- <div class="myplan"> |
|
270 |
- <div class="title-box flex justify-between mb40"> |
|
271 |
- <p class="title">지금은 학습 루트가 등록이 안됐어요 ! 학습 일정에서 학습루트를 등록해볼까요 ?</p> |
|
266 |
+ </div> |
|
267 |
+ <div class="complete-wrap mt90 myphoto"> |
|
268 |
+ <h2 class="mb40">이 단원을 끝낸 친구들</h2> |
|
269 |
+ <article class=" flex-column" style="gap: 5px;"> |
|
270 |
+ <div class="flex" style="gap: 5px;"> |
|
271 |
+ <div @click="buttonSearch2" class="photo"><img src="../../../resources/img/img143_75s.png" alt=""></div> |
|
272 |
+ <div @click="buttonSearch" class="photo"><img src="../../../resources/img/img143_75s.png" alt=""></div> |
|
272 | 273 |
</div> |
273 |
- <div class="wrap" style="border-radius: 0; min-height: 197px;"> |
|
274 |
- <p class="title1"> 오늘 학습할 내용이 없습니다.</p> |
|
275 |
- </div> |
|
276 |
- <div class="yellow-box mt30"> |
|
277 |
- <div class="title-box flex justify-between align-center"> |
|
278 |
- <div > |
|
279 |
- <p class="title">오늘 공부를 계획해봅시다.</p> |
|
280 |
- <p class="title1 mt20"> 스스로 학습 일정을 바꿔볼까요?</p> |
|
281 |
- </div> |
|
282 |
- <button type="button" title="바로가기" class="yellow-btn" @click="goToPage('MyPlan2')"> |
|
283 |
- 바로가기 |
|
274 |
+ </article> |
|
275 |
+ <!-- 팝업 --> |
|
276 |
+ <article class="popup-wrap" v-show="searchOpen"> |
|
277 |
+ <div class="popup-box "> |
|
278 |
+ <div class="flex mb10 justify-between"> |
|
279 |
+ <p class="popup-title">알림</p> |
|
280 |
+ <button type="button" class="popup-close-btn" @click="closeBtn2"> |
|
281 |
+ <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon> |
|
284 | 282 |
</button> |
285 | 283 |
</div> |
286 | 284 |
</div> |
... | ... | @@ -395,6 +393,9 @@ |
395 | 393 |
this.searchOpen2 = true; |
396 | 394 |
} |
397 | 395 |
}, |
396 |
+ ShowPopup() { |
|
397 |
+ this.searchOpen2 = true; // 촬영 여부 묻는 모달창 열기 |
|
398 |
+ }, |
|
398 | 399 |
updateContent(index) { |
399 | 400 |
this.selectedIndex = index; |
400 | 401 |
|
... | ... | @@ -429,12 +430,14 @@ |
429 | 430 |
console.log("error>>>>>>>>", error); |
430 | 431 |
}); |
431 | 432 |
}, |
432 |
- closeModal() { //웹캠 팝업 닫기 |
|
433 |
+ closeModal() { //웹캠 및 모든 팝업 닫기 |
|
433 | 434 |
// this.showModal = false; |
435 |
+ this.searchOpen = false; |
|
434 | 436 |
this.searchOpen2 = false; |
435 | 437 |
this.showCameraModal = false; |
436 | 438 |
this.photoTaken = false; |
437 | 439 |
this.photo = null; |
440 |
+ this.showPhotoModal = false; |
|
438 | 441 |
|
439 | 442 |
//스트림 종료 |
440 | 443 |
if (this.stream) { |
... | ... | @@ -887,4 +890,8 @@ |
887 | 890 |
.toolbar input { |
888 | 891 |
margin: 5px; |
889 | 892 |
} |
893 |
+ |
|
894 |
+.rabbit-end { |
|
895 |
+ cursor: pointer; |
|
896 |
+} |
|
890 | 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"> |
|
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/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
... | ... | @@ -14,14 +14,14 @@ |
14 | 14 |
</button> |
15 | 15 |
</div> |
16 | 16 |
<div class="search-wrap flex justify-between mb20 align-center"> |
17 |
- <div class="title2 gray flex"><div class="black">[{{ selectedUnitName }}]</div>단원 전체 목록</div> |
|
17 |
+ <div class="title2 gray flex">{{ titleMessage }}</div> |
|
18 | 18 |
<div> |
19 |
- <select name="" id="" class="mr10 data-wrap"> |
|
20 |
- <option value="">지문</option> |
|
21 |
- <option value="">단어</option> |
|
19 |
+ <select v-model="searchType" class="mr10 data-wrap"> |
|
20 |
+ <option value="text">지문</option> |
|
21 |
+ <option value="word">단어</option> |
|
22 | 22 |
</select> |
23 |
- <input type="text" placeholder="검색하세요."> |
|
24 |
- <button type="button" title="단어장 검색"> |
|
23 |
+ <input v-model="searchQuery" type="text" placeholder="검색하세요."> |
|
24 |
+ <button type="button" title="단어장 검색" @click="searchWordBooks"> |
|
25 | 25 |
<img src="../../../resources/img/look_t.png" alt=""> |
26 | 26 |
</button> |
27 | 27 |
</div> |
... | ... | @@ -77,10 +77,13 @@ |
77 | 77 |
selectedBookId: null, // 선택된 책 ID 저장 변수 |
78 | 78 |
selectedUnitId: null, // 선택된 단원 ID 저장 변수 |
79 | 79 |
selectedUnitName: '', // 선택된 단원의 이름 저장 변수 |
80 |
+ titleMessage: '', // 타이틀 메시지 변수 |
|
80 | 81 |
dataList: [], |
81 | 82 |
currentPage: 0, |
82 | 83 |
itemsPerPage: 2, |
83 |
- totalPosts: 0 |
|
84 |
+ totalPosts: 0, |
|
85 |
+ searchType: 'text', // 검색 종류를 저장할 변수 |
|
86 |
+ searchQuery: '' // 검색어를 저장할 변수 |
|
84 | 87 |
} |
85 | 88 |
}, |
86 | 89 |
methods: { |
... | ... | @@ -119,6 +122,8 @@ |
119 | 122 |
this.selectedUnitId = unitId; |
120 | 123 |
const selectedUnit = this.units.find(unit => unit.unitId === unitId); |
121 | 124 |
this.selectedUnitName = selectedUnit ? selectedUnit.unitName : ''; |
125 |
+ this.searchQuery = ''; |
|
126 |
+ this.titleMessage = `[${this.selectedUnitName}] 단원 전체 목록`; |
|
122 | 127 |
this.dataSelectList(); // 단어장 목록 조회 |
123 | 128 |
}, |
124 | 129 |
|
... | ... | @@ -205,16 +210,93 @@ |
205 | 210 |
|
206 | 211 |
// 페이지 이동 메서드 |
207 | 212 |
goToViewPage(page) { |
208 |
- 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 |
+ }); |
|
209 | 289 |
}, |
210 | 290 |
}, |
211 | 291 |
watch: { |
212 | 292 |
|
213 | 293 |
}, |
214 | 294 |
computed: { |
295 |
+ |
|
215 | 296 |
totalPages() { |
216 | 297 |
return Math.ceil(this.totalPosts / this.itemsPerPage); |
217 | 298 |
}, |
299 |
+ |
|
218 | 300 |
paginationButtons() { |
219 | 301 |
let start = Math.max(0, this.currentPage - 2); |
220 | 302 |
let end = Math.min(start + 5, this.totalPages); |
... | ... | @@ -225,9 +307,11 @@ |
225 | 307 |
|
226 | 308 |
return Array.from({ length: end - start }, (_, i) => start + i + 1); |
227 | 309 |
}, |
310 |
+ |
|
228 | 311 |
startIndex() { |
229 | 312 |
return this.currentPage * this.itemsPerPage; |
230 | 313 |
} |
314 |
+ |
|
231 | 315 |
}, |
232 | 316 |
components:{ |
233 | 317 |
SvgIcon |
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?