

240812 권지수 문제 화면
@0e35d907031627a63b35d9216bd66ca23ecd1004
--- client/views/pages/AppRouter.js
+++ client/views/pages/AppRouter.js
... | ... | @@ -83,6 +83,7 @@ |
83 | 83 |
import TextInsert from "./teacher/TextInsert.vue"; |
84 | 84 |
import QuestionList from "./teacher/QuestionList.vue"; |
85 | 85 |
import QuestionInsert from "./teacher/QuestionInsert.vue"; |
86 |
+import QuestionDetail from "./teacher/QuestionDetail.vue"; |
|
86 | 87 |
import VocaList from "./teacher/VocaList.vue"; |
87 | 88 |
import ExamList from "./teacher/ExamList.vue"; |
88 | 89 |
import ExamDetail from "./teacher/ExamDetail.vue"; |
... | ... | @@ -186,6 +187,7 @@ |
186 | 187 |
{ path: '/TextInsert.page', name: 'TextInsert', component: TextInsert }, |
187 | 188 |
{ path: '/QuestionList.page', name: 'QuestionList', component: QuestionList }, |
188 | 189 |
{ path: '/QuestionInsert.page', name: 'QuestionInsert', component: QuestionInsert }, |
190 |
+ { path: '/QuestionDetail.page', name: 'QuestionDetail', component: QuestionDetail }, |
|
189 | 191 |
{ path: '/VocaList.page', name: 'VocaList', component: VocaList }, |
190 | 192 |
{ path: '/ExamList.page', name: 'ExamList', component: ExamList }, |
191 | 193 |
{ path: '/ExamDetail.page', name: 'ExamDetail', component: ExamDetail }, |
+++ client/views/pages/teacher/QuestionDetail.vue
... | ... | @@ -0,0 +1,232 @@ |
1 | +<template> | |
2 | + <div class="title-box flex justify-between mb40"> | |
3 | + <p class="title">문제 조회</p> | |
4 | + </div> | |
5 | + <div class="board-wrap"> | |
6 | + <div class="flex align-center"> | |
7 | + <label for="" class="title2">문제</label> | |
8 | + <p class="data-wrap">{{ questionExpln }}</p> | |
9 | + </div> | |
10 | + <hr> | |
11 | + <div class="flex align-center"> | |
12 | + <label for="" class="title2">유형</label> | |
13 | + <p class="data-wrap">{{ questionTypeId }}</p> | |
14 | + </div> | |
15 | + <div class="flex align-center"> | |
16 | + <label for="" class="title2">카테고리</label> | |
17 | + <p class="data-wrap">{{ questionCategoryId }}</p> | |
18 | + </div> | |
19 | + <div class="flex align-center"> | |
20 | + <label for="" class="title2">사용자 아이디</label> | |
21 | + <p class="data-wrap">{{ userId }}</p> | |
22 | + </div> | |
23 | + <div class="flex align-center"> | |
24 | + <label for="" class="title2">책 아이디</label> | |
25 | + <p class="data-wrap">{{ bookId }}</p> | |
26 | + </div> | |
27 | + <div class="flex align-center"> | |
28 | + <label for="" class="title2">단원 아이디</label> | |
29 | + <p class="data-wrap">{{ unitId }}</p> | |
30 | + </div> | |
31 | + <hr> | |
32 | + <div class="flex align-center"> | |
33 | + <label for="" class="title2">문제 점수</label> | |
34 | + <p class="data-wrap">{{ questionScore }}</p> | |
35 | + </div> | |
36 | + <div class="flex align-center"> | |
37 | + <label for="" class="title2">문제 힌트</label> | |
38 | + <p class="data-wrap">{{ questionHint }}</p> | |
39 | + </div> | |
40 | + <div class="flex align-center"> | |
41 | + <label for="" class="title2">문제 해설</label> | |
42 | + <p class="data-wrap">{{ questionExplanation }}</p> | |
43 | + </div> | |
44 | + <hr> | |
45 | + <div class="flex align-center"> | |
46 | + <label for="" class="title2">첨부파일</label> | |
47 | + <p class="data-wrap">{{ questionFile }}</p> | |
48 | + </div> | |
49 | + <div class="flex align-center mb20"> | |
50 | + <label for="" class="title2">답</label> | |
51 | + <p class="data-wrap">{{ questionAnswer }}</p> | |
52 | + </div> | |
53 | + <div> | |
54 | + <label for="" class="title2">오답 학생</label> | |
55 | + <div class="table-wrap mt20"> | |
56 | + <table> | |
57 | + <thead> | |
58 | + <tr> | |
59 | + <td>No.</td> | |
60 | + <td>이름</td> | |
61 | + <td>학년</td> | |
62 | + <td>반</td> | |
63 | + <td>오답</td> | |
64 | + </tr> | |
65 | + </thead> | |
66 | + <tbody> | |
67 | + <tr v-for="(student, index) in wrongStudents" :key="index" @click="goToPage('noticeDetail')"> | |
68 | + <td>{{ index + 1 }}</td> | |
69 | + <td>{{ student.name }}</td> | |
70 | + <td>{{ student.grade }}</td> | |
71 | + <td>{{ student.class }}</td> | |
72 | + <td>{{ student.wrongAnswer }}</td> | |
73 | + </tr> | |
74 | + </tbody> | |
75 | + </table> | |
76 | + </div> | |
77 | + </div> | |
78 | + </div> | |
79 | + <div class="flex justify-between mt50"> | |
80 | + <button type="button" title="글쓰기" class="new-btn" @click="goToPage('QuestionList')"> | |
81 | + 목록 | |
82 | + </button> | |
83 | + <div class="flex"> | |
84 | + <button type="button" title="글쓰기" class="new-btn mr10" @click="editQuestion"> | |
85 | + 수정 | |
86 | + </button> | |
87 | + <button type="button" title="글쓰기" class="new-btn" @click="confirmDelete"> | |
88 | + 삭제 | |
89 | + </button> | |
90 | + </div> | |
91 | + </div> | |
92 | + | |
93 | + <!-- 모달 창 --> | |
94 | + <div v-if="showModal" class="modal-overlay"> | |
95 | + <div class="modal-content"> | |
96 | + <p>삭제하시겠습니까?</p> | |
97 | + <button @click="deleteQuestion">예, 삭제</button> | |
98 | + <button @click="cancelDelete">취소</button> | |
99 | + </div> | |
100 | + </div> | |
101 | +</template> | |
102 | + | |
103 | +<script> | |
104 | +import SvgIcon from '@jamescoyle/vue-icon'; | |
105 | +import { mdiMagnify } from '@mdi/js'; | |
106 | +import axios from 'axios'; | |
107 | + | |
108 | +export default { | |
109 | + data() { | |
110 | + return { | |
111 | + mdiMagnify: mdiMagnify, | |
112 | + questionTitle: '샘플 제목', | |
113 | + questionExpln: '샘플 내용', | |
114 | + questionFile: null, // 파일 URL을 여기에 저장 | |
115 | + questionAnswer: '샘플 답', | |
116 | + wrongStudents: [], | |
117 | + showModal: false, | |
118 | + parsedData: null, // parsedData 추가 | |
119 | + questionTypeId: '', | |
120 | + questionCategoryId: '', | |
121 | + userId: '', | |
122 | + bookId: '', | |
123 | + unitId: '', | |
124 | + questionScore: '', | |
125 | + questionHint: '', | |
126 | + questionExplanation: '' | |
127 | + } | |
128 | + }, | |
129 | + methods: { | |
130 | + goToPage(page) { | |
131 | + this.$router.push({ name: page }); | |
132 | + }, | |
133 | + editQuestion() { | |
134 | + // 수정 로직 추가 | |
135 | + console.log('수정 버튼 클릭'); | |
136 | + }, | |
137 | + confirmDelete() { | |
138 | + this.showModal = true; | |
139 | + }, | |
140 | + async deleteQuestion() { | |
141 | + try { | |
142 | + const prblmId = this.parsedData.prblmId; // this.parsedData 사용 | |
143 | + const response = await axios.post('/problem/deleteProblem.json', { prblmId : prblmId }); | |
144 | + console.log('삭제 완료:', response.data); | |
145 | + this.showModal = false; | |
146 | + this.goToPage('QuestionList'); | |
147 | + } catch (error) { | |
148 | + console.error('삭제 실패:', error); | |
149 | + } | |
150 | + }, | |
151 | + cancelDelete() { | |
152 | + this.showModal = false; | |
153 | + }, | |
154 | + loadFromLocalStorage() { | |
155 | + const data = sessionStorage.getItem('selectQuestionList'); | |
156 | + if (data) { | |
157 | + this.parsedData = JSON.parse(data); // this.parsedData 설정 | |
158 | + console.log('Loaded data from local storage:', this.parsedData); | |
159 | + | |
160 | + this.questionExpln = this.parsedData.prblmExpln || '내용 없음'; | |
161 | + this.questionFile = this.parsedData.fileMngId || '첨부파일 없음'; // 파일 경로는 실제 경로에 맞게 수정 | |
162 | + this.questionAnswer = this.parsedData.prblmCmmt || '답 없음'; | |
163 | + this.wrongStudents = this.parsedData.wrongStudents || []; | |
164 | + this.questionTypeId = this.parsedData.prblmTypeId || '유형 아이디 없음'; | |
165 | + this.questionCategoryId = this.parsedData.prblmCtgryId || '카테고리 아이디 없음'; | |
166 | + this.userId = this.parsedData.userId || '사용자 아이디 없음'; | |
167 | + this.bookId = this.parsedData.bookId || '책 아이디 없음'; | |
168 | + this.unitId = this.parsedData.unitId || '단원 아이디 없음'; | |
169 | + this.questionScore = this.parsedData.prblmScr || '점수 없음', | |
170 | + this.questionHint = this.parsedData.prblmHint || '힌트 없음', | |
171 | + this.questionExplanation = this.parsedData.prblmExpln || '해설 없음' | |
172 | + } else { | |
173 | + console.log('No data found in local storage'); | |
174 | + } | |
175 | + }, | |
176 | + downloadFile() { | |
177 | + window.open(this.questionFile, '_blank'); | |
178 | + }, | |
179 | + getProblemId() { | |
180 | + // 문제 ID를 얻는 로직을 추가하세요 | |
181 | + return 'sampleProblemId'; | |
182 | + } | |
183 | + }, | |
184 | + components: { | |
185 | + SvgIcon | |
186 | + }, | |
187 | + mounted() { | |
188 | + this.loadFromLocalStorage(); | |
189 | + } | |
190 | +} | |
191 | +</script> | |
192 | + | |
193 | +<style> | |
194 | + .data-wrap { | |
195 | + width: -webkit-fill-available; | |
196 | + padding: 1.2rem; | |
197 | + } | |
198 | + .download-btn { | |
199 | + background-color: #007bff; | |
200 | + color: white; | |
201 | + border: none; | |
202 | + padding: 10px 20px; | |
203 | + cursor: pointer; | |
204 | + text-decoration: none; | |
205 | + font-size: 1rem; | |
206 | + } | |
207 | + .download-btn:hover { | |
208 | + background-color: #0056b3; | |
209 | + } | |
210 | + .modal-overlay { | |
211 | + position: fixed; | |
212 | + top: 0; | |
213 | + left: 0; | |
214 | + width: 100%; | |
215 | + height: 100%; | |
216 | + background-color: rgba(0, 0, 0, 0.5); | |
217 | + display: flex; | |
218 | + justify-content: center; | |
219 | + align-items: center; | |
220 | + } | |
221 | + .modal-content { | |
222 | + background-color: white; | |
223 | + padding: 20px; | |
224 | + border-radius: 5px; | |
225 | + text-align: center; | |
226 | + } | |
227 | + .modal-content button { | |
228 | + margin: 10px; | |
229 | + padding: 10px 20px; | |
230 | + cursor: pointer; | |
231 | + } | |
232 | +</style> |
--- client/views/pages/teacher/QuestionList.vue
+++ client/views/pages/teacher/QuestionList.vue
... | ... | @@ -6,66 +6,84 @@ |
6 | 6 |
</select> |
7 | 7 |
</div> |
8 | 8 |
<div class="search-wrap flex justify-end mb20"> |
9 |
- <select name="" id="" class="mr10 data-wrap"> |
|
10 |
- <option value="">전체</option> |
|
11 |
- </select> |
|
12 |
- <input type="text" placeholder="검색하세요."> |
|
13 |
- <button type="button" title="위원회 검색"> |
|
14 |
- <img src="../../../resources/img/look_t.png" alt=""> |
|
15 |
- </button> |
|
16 |
- </div> |
|
17 |
- <div class="table-wrap"> |
|
18 |
- <table> |
|
19 |
- <thead> |
|
9 |
+ <select name="" id="" class="mr10 data-wrap" v-model="searchOption"> |
|
10 |
+ <option value="">전체</option> |
|
11 |
+ <option value="">제목</option> |
|
12 |
+ <option value="prblm_expln">문제</option> |
|
13 |
+ <option value="user_id">작성자</option> |
|
14 |
+ <option value="">등록일</option> |
|
15 |
+ </select> |
|
16 |
+ <input type="text" placeholder="검색하세요." v-model="searchKeyword"> |
|
17 |
+ <button type="button" title="위원회 검색" @click="searchProblems"> |
|
18 |
+ <img src="../../../resources/img/look_t.png" alt=""> |
|
19 |
+ </button> |
|
20 |
+ </div> |
|
21 |
+ <div class="table-wrap"> |
|
22 |
+ <table> |
|
23 |
+ <thead> |
|
24 |
+ <tr> |
|
20 | 25 |
<td>No.</td> |
21 | 26 |
<td>제목</td> |
22 | 27 |
<td>문제</td> |
23 | 28 |
<td>작성자</td> |
24 | 29 |
<td>오답률</td> |
25 | 30 |
<td>등록일</td> |
26 |
- </thead> |
|
27 |
- <tbody> |
|
28 |
- <tr @click="goToPage('QuestionInsert')"> |
|
29 |
- <td></td> |
|
30 |
- <td></td> |
|
31 |
- <td></td> |
|
32 |
- <td></td> |
|
33 |
- <td></td> |
|
34 |
- <td></td> |
|
35 |
- </tr> |
|
36 |
- </tbody> |
|
37 |
- </table> |
|
38 |
- <article class="table-pagination flex justify-center align-center mb20 mt30" style="gap: 10px;"> |
|
39 |
- <button><img src="../../../resources/img/btn27_90t_normal.png" alt=""></button> |
|
40 |
- <button class="selected-btn">1</button> |
|
41 |
- <button>2</button> |
|
42 |
- <button>3</button> |
|
43 |
- <button><img src="../../../resources/img/btn28_90t_normal.png" alt=""></button> |
|
44 |
- </article> |
|
45 |
- <div class="flex justify-end "> |
|
46 |
- <button type="button" title="등록" class="new-btn" @click="goToPage('QuestionInsert')"> |
|
47 |
- 등록 |
|
48 |
- </button> |
|
31 |
+ </tr> |
|
32 |
+ </thead> |
|
33 |
+ <tbody> |
|
34 |
+ <tr v-for="(problem, index) in problems" :key="problem.prblmId" @click="[goToPage('QuestionDetail', selectQuestionList(problem))]"> |
|
35 |
+ <td>{{ (currentPage - 1) * 10 + index + 1 }}</td> |
|
36 |
+ <td>제목</td> |
|
37 |
+ <td>{{ problem.prblmExpln }}</td> |
|
38 |
+ <td>{{ problem.userId }}</td> |
|
39 |
+ <td>오답률</td> |
|
40 |
+ <td>등록일</td> |
|
41 |
+ </tr> |
|
42 |
+ </tbody> |
|
43 |
+ </table> |
|
44 |
+ <article class="table-pagination flex justify-center align-center mb20 mt30" style="gap: 10px;"> |
|
45 |
+ <button @click="changePage(currentPage - 1)" :disabled="currentPage === 1"> |
|
46 |
+ <img src="../../../resources/img/btn27_90t_normal.png" alt=""> |
|
47 |
+ </button> |
|
48 |
+ <button :class="{ 'selected-btn': currentPage === 1 }" @click="changePage(1)">1</button> |
|
49 |
+ <button :class="{ 'selected-btn': currentPage === 2 }" @click="changePage(2)">2</button> |
|
50 |
+ <button :class="{ 'selected-btn': currentPage === 3 }" @click="changePage(3)">3</button> |
|
51 |
+ <button @click="changePage(currentPage + 1)" :disabled="currentPage === totalPages"> |
|
52 |
+ <img src="../../../resources/img/btn28_90t_normal.png" alt=""> |
|
53 |
+ </button> |
|
54 |
+ </article> |
|
55 |
+ <div class="flex justify-end"> |
|
56 |
+ <button type="button" title="등록" class="new-btn" @click="goToPage('QuestionInsert')"> |
|
57 |
+ 등록 |
|
58 |
+ </button> |
|
49 | 59 |
</div> |
50 |
- </div> |
|
60 |
+ </div> |
|
51 | 61 |
</template> |
52 | 62 |
|
53 | 63 |
<script> |
54 | 64 |
import SvgIcon from '@jamescoyle/vue-icon'; |
55 |
-import { mdiMagnify} from '@mdi/js'; |
|
56 |
- |
|
65 |
+import { mdiMagnify } from '@mdi/js'; |
|
66 |
+import axios from 'axios'; |
|
57 | 67 |
|
58 | 68 |
export default { |
59 |
- data () { |
|
69 |
+ data() { |
|
60 | 70 |
return { |
71 |
+ problems: [], |
|
61 | 72 |
mdiMagnify: mdiMagnify, |
73 |
+ currentPage: 1, |
|
74 |
+ totalPages: 3, |
|
75 |
+ searchOption: '', |
|
76 |
+ searchKeyword: '', |
|
62 | 77 |
} |
63 | 78 |
}, |
64 | 79 |
methods: { |
65 | 80 |
goToPage(page) { |
66 |
- this.$router.push({ name: page }); |
|
67 |
- }, |
|
68 |
- showConfirm(type) { |
|
81 |
+ this.$router.push({ name: page }); |
|
82 |
+ }, |
|
83 |
+ selectQuestionList(item) { |
|
84 |
+ sessionStorage.setItem("selectQuestionList", JSON.stringify(item)); |
|
85 |
+ }, |
|
86 |
+ showConfirm(type) { |
|
69 | 87 |
let message = ''; |
70 | 88 |
if (type === 'cancel') { |
71 | 89 |
message = '삭제하시겠습니까?'; |
... | ... | @@ -79,19 +97,32 @@ |
79 | 97 |
this.goBack(); |
80 | 98 |
} |
81 | 99 |
}, |
82 |
- |
|
83 |
- }, |
|
84 |
- watch: { |
|
85 |
- |
|
86 |
- }, |
|
87 |
- computed: { |
|
88 |
- |
|
89 |
- }, |
|
90 |
- components:{ |
|
91 |
- SvgIcon |
|
100 |
+ async fetchProblems(page = 1) { |
|
101 |
+ try { |
|
102 |
+ const response = await axios.post('/problem/problemList.json', { |
|
103 |
+ option: this.searchOption, |
|
104 |
+ keyword: this.searchKeyword, |
|
105 |
+ pageSize: 10, |
|
106 |
+ startIndex: (page - 1) * 10 |
|
107 |
+ }); |
|
108 |
+ this.problems = response.data; |
|
109 |
+ this.currentPage = page; |
|
110 |
+ } catch (error) { |
|
111 |
+ console.error('문제 목록을 불러오는 중 오류가 발생했습니다.', error); |
|
112 |
+ } |
|
113 |
+ }, |
|
114 |
+ changePage(page) { |
|
115 |
+ if (page < 1 || page > this.totalPages) return; |
|
116 |
+ this.fetchProblems(page); |
|
117 |
+ this.currentPage = page; |
|
118 |
+ }, |
|
119 |
+ searchProblems() { |
|
120 |
+ this.fetchProblems(1); |
|
121 |
+ } |
|
92 | 122 |
}, |
93 | 123 |
mounted() { |
94 | 124 |
console.log('Main2 mounted'); |
125 |
+ this.fetchProblems(); |
|
95 | 126 |
} |
96 | 127 |
} |
97 |
-</script>(No newline at end of file) |
|
128 |
+</script> |
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?