
+++ client/resources/img/account.png
Binary file is not shown |
+++ client/resources/img/date.png
Binary file is not shown |
+++ client/resources/img/file.png
Binary file is not shown |
+++ client/resources/img/h3icon.png
Binary file is not shown |
+++ client/resources/img/icon.png
Binary file is not shown |
+++ client/resources/img/id.png
Binary file is not shown |
+++ client/resources/img/img.png
Binary file is not shown |
+++ client/resources/img/img1.png
Binary file is not shown |
+++ client/resources/img/loginicon.png
Binary file is not shown |
+++ client/resources/img/logo.png
Binary file is not shown |
+++ client/resources/img/logout.png
Binary file is not shown |
+++ client/resources/img/more.png
Binary file is not shown |
+++ client/resources/img/password.png
Binary file is not shown |
+++ client/resources/img/photo_icon.png
Binary file is not shown |
+++ client/resources/img/require.png
Binary file is not shown |
+++ client/resources/img/start.png
Binary file is not shown |
+++ client/resources/img/stop.png
Binary file is not shown |
+++ client/resources/scss/admin/content.scss
... | ... | @@ -0,0 +1,473 @@ |
1 | +.login{ | |
2 | + background-color: #EFF1FA; | |
3 | + height: 100vh; | |
4 | + .card{ | |
5 | + padding-top: 30rem; | |
6 | + .card-body{ | |
7 | + | |
8 | + background-color: #fff; | |
9 | + margin: 0 auto; | |
10 | + width: 600px; | |
11 | + height: 450px; | |
12 | + padding: 5rem; | |
13 | + border-radius: 5rem; | |
14 | + display: flex; | |
15 | + gap: 30px; | |
16 | + flex-direction: column; | |
17 | + } | |
18 | + .card-title{ | |
19 | + text-align: center; | |
20 | + } | |
21 | + .form-label{ | |
22 | + display: flex | |
23 | + ; | |
24 | + align-items: center; | |
25 | + font-weight: 700; | |
26 | + font-size: 15px; | |
27 | + margin-bottom: 10px; | |
28 | + img{margin-right: 3px;} | |
29 | + } | |
30 | + .box{margin-bottom: 30px; | |
31 | + button{width: 100%; background-color: #1F3F99; | |
32 | + img{margin-right: 10px;} | |
33 | + } | |
34 | + } | |
35 | + } | |
36 | +} | |
37 | +.sidemenu{ | |
38 | + width: 350px; | |
39 | + margin-right: 20px; | |
40 | + .myinfo{ | |
41 | + text-align: center; | |
42 | + padding: 25px 20px 20px 20px; | |
43 | + margin-bottom: 20px; | |
44 | + border-radius: 20px; | |
45 | + background: linear-gradient(to bottom, #1F3F99, #3354CE); | |
46 | + .name-box{ | |
47 | + margin-bottom: 30px; | |
48 | + .img-area{ | |
49 | + margin: 0 auto; | |
50 | + width: 165px; | |
51 | + | |
52 | + .name{ | |
53 | + font-size: 25px; | |
54 | + font-weight: 700; | |
55 | + color: #fff; | |
56 | + } | |
57 | + .info{ | |
58 | + background-color: #0A216D; | |
59 | + border-radius: 100px; | |
60 | + display: flex; | |
61 | + align-items: center; | |
62 | + gap: 10px; | |
63 | + justify-content: center; | |
64 | + | |
65 | + p{color: #fff; font-size: 15px;} | |
66 | + } | |
67 | + } | |
68 | + } | |
69 | + | |
70 | + .info-box{ | |
71 | + background-color: #fff; | |
72 | + border-radius: 15px; | |
73 | + display: flex; | |
74 | + justify-content: space-between; | |
75 | + li{text-align: center; border-right: 1px solid #E7EAF4; width: 25%; | |
76 | + p{font-size: 35px; font-weight: 700; margin: 20px 0;} | |
77 | + } | |
78 | + li:last-child{border: 0;} | |
79 | + .name{ | |
80 | + text-align: center; | |
81 | + display: flex; | |
82 | + justify-content: center; | |
83 | + justify-self: center; | |
84 | + margin-bottom: 12px; | |
85 | + width: 58px; | |
86 | + height: 25px; | |
87 | + font-size: 16px; | |
88 | + font-weight: 700; | |
89 | + background-color: #999999; | |
90 | + color: #fff; | |
91 | + border-radius: 100px; | |
92 | + } | |
93 | + .name.yellow{background-color:#EDAB0C;} | |
94 | + .name.blue{background-color:#1D75E1;} | |
95 | + .name.red{background-color:#E92727;} | |
96 | + } | |
97 | + } | |
98 | + .myinfo.profile{ | |
99 | + background: #fff; | |
100 | + .name-box{margin: 20px 0;} | |
101 | + .img-area{ | |
102 | + width: 100%; | |
103 | + .img{ | |
104 | + position: relative; | |
105 | + width: 150px; height: 190px; border: 1px solid #D7DCF1; | |
106 | + padding: 10px; | |
107 | + margin: 0 auto; | |
108 | + .close-btn{position: absolute; top: 0; right: 10px;} | |
109 | + img{height: 100%; object-fit: cover; } | |
110 | + } | |
111 | + .info{ | |
112 | + border-top: 1px solid #9498A94D; | |
113 | + margin: 0 auto; | |
114 | + padding-top: 30px; | |
115 | + margin-top: 30px; | |
116 | + width: 240px; | |
117 | + background-color: transparent; | |
118 | + border-radius: 0; | |
119 | + .file { | |
120 | + background-color: #EFF1FA; width: 120px; border: #213F9A 1px solid ; border-radius: 10px; padding: 7px 0; | |
121 | + margin-top: 10px; | |
122 | + } | |
123 | + | |
124 | + .file-label { | |
125 | + display: flex; | |
126 | + gap: 5px; | |
127 | + justify-content: center; | |
128 | + cursor: pointer; | |
129 | + p{color: #000;} | |
130 | + } | |
131 | + | |
132 | + input[type="file"] { | |
133 | + display: none; | |
134 | + } | |
135 | + } | |
136 | + } | |
137 | + } | |
138 | + .myinfo.simple{ | |
139 | + background: transparent; | |
140 | + .img-area{ | |
141 | + img{width: 75px; | |
142 | + height: 75px;} | |
143 | + .info{ | |
144 | + background-color: #DEE2F5; | |
145 | + p{color: #000;} | |
146 | + .fa-bars{ | |
147 | + width: 2px; | |
148 | + height: 14px; | |
149 | + background-color: #213F9A1A; | |
150 | + } | |
151 | + } | |
152 | + .name{color: #000;} | |
153 | + } | |
154 | + h2{ background: #213F9A; color: #fff; border-radius: 10px; font-size: 17px; text-align: left; padding: 10px 15px;} | |
155 | + } | |
156 | + .menu-box{ | |
157 | + margin-top: 20px; | |
158 | + li{ | |
159 | + margin-bottom: 10px; | |
160 | + a{display: flex; justify-content: space-between;} | |
161 | + a:focus{outline: 0;} | |
162 | + } | |
163 | + } | |
164 | + .boxs{ | |
165 | + background-color: #fff; border-radius: 20px; padding: 20px; margin-bottom: 20px; | |
166 | + .box{display: flex; justify-content: space-between; margin-bottom: 15px; | |
167 | + h3{text-align: center; | |
168 | + color: #fff; | |
169 | + font-size: 16px; | |
170 | + font-weight: 700;} | |
171 | + } | |
172 | + .img-area{margin: 0 12.5px;} | |
173 | + h4{ | |
174 | + font-size: 19px; | |
175 | + font-weight: 700; | |
176 | + text-align: center; | |
177 | + line-height: 22px; | |
178 | + img{margin-right: 10px;} | |
179 | + margin-bottom: 15px; | |
180 | + } | |
181 | + i.fa-bars{display: block; width: 2px; height: 14px; background-color: #D7DCF1;} | |
182 | + .sm-box{ | |
183 | + width: 185px; | |
184 | + background-color: #F4F6FD; | |
185 | + border-radius: 10px; | |
186 | + text-align: center; | |
187 | + h3{ | |
188 | + background-color: #213F9A; | |
189 | + border-radius: 10px; | |
190 | + height: 35px; | |
191 | + line-height: 35px; | |
192 | + } | |
193 | + p{ | |
194 | + font-size: 30px; | |
195 | + font-weight: 700; | |
196 | + } | |
197 | + } | |
198 | + .bd-box{ | |
199 | + border: 1px solid #D7DCF1; | |
200 | + border-radius: 10px; | |
201 | + padding: 19px 40px 19px 40px; | |
202 | + display: flex; | |
203 | + align-items: center; | |
204 | + gap: 30px; | |
205 | + div{font-size: 18px; | |
206 | + span{font-size: 18px; font-weight: 800; margin-left:23px;} | |
207 | + } | |
208 | + } | |
209 | + .color-boxs{ | |
210 | + display: flex; | |
211 | + justify-content: space-between; | |
212 | + .box{ | |
213 | + display: block; | |
214 | + text-align: center; | |
215 | + width: 96px; | |
216 | + background-color: #F9F9F9; | |
217 | + font-size: 30px; | |
218 | + font-weight: 700; | |
219 | + border-radius: 10px; | |
220 | + padding-bottom: 24px; | |
221 | + h3{background-color: #999999; border-top-left-radius: 10px; border-top-right-radius: 10px; width: 100%; height: 33px; | |
222 | + line-height: 33px; | |
223 | + margin-bottom: 17px;} | |
224 | + } | |
225 | + .box.red{ | |
226 | + background-color: #FEF3F3; | |
227 | + h3{background-color: #E92727;} | |
228 | + | |
229 | + } | |
230 | + .box.green{ | |
231 | + background-color: #EFF9FB; | |
232 | + h3{background-color: #3C97AB;} | |
233 | + | |
234 | + } | |
235 | + | |
236 | + } | |
237 | + | |
238 | + } | |
239 | + .router-link-active p { | |
240 | + color: #213F9A; /* 원하는 색상으로 변경 */ | |
241 | + font-weight: 700; | |
242 | + } | |
243 | + | |
244 | + | |
245 | +} | |
246 | +.content{ | |
247 | + .pagination{ | |
248 | + margin-top: 20px; | |
249 | + ul { | |
250 | + display: flex; | |
251 | + list-style: none; | |
252 | + padding: 0; | |
253 | + gap: 8px; | |
254 | + justify-content: center; | |
255 | + align-items: center; | |
256 | + } | |
257 | + | |
258 | + li { | |
259 | + width: 35px; | |
260 | + height: 35px; | |
261 | + text-align: center; | |
262 | + line-height: 35px; | |
263 | + border: 1px solid #BDCBE8; | |
264 | + border-radius: 4px; | |
265 | + cursor: pointer; | |
266 | + background-color: #fff; | |
267 | + color: #333; | |
268 | + transition: 0.2s; | |
269 | + } | |
270 | + | |
271 | + li:hover:not(.disabled) { | |
272 | + background-color: #f0f0f0; | |
273 | + } | |
274 | + | |
275 | + li.active { | |
276 | + background-color: #1D75E1; | |
277 | + color: white; | |
278 | + font-weight: bold; | |
279 | + } | |
280 | + | |
281 | + li.arrow { | |
282 | + font-weight: bold; | |
283 | + } | |
284 | + | |
285 | + li.disabled { | |
286 | + opacity: 0.5; | |
287 | + cursor: not-allowed; | |
288 | + } | |
289 | + } | |
290 | + | |
291 | + .title{ | |
292 | + display: flex; | |
293 | + justify-content: space-between; | |
294 | + align-items: center; | |
295 | + margin-bottom: 10px; | |
296 | + | |
297 | + h2{font-size: 24px; font-weight: 700; display: flex; gap: 20px; | |
298 | + span{border: 1px solid #E92727; border-radius: 100px; color: #E92727; font-size: 15px; text-align: center; padding: 5px 20px;} | |
299 | + } | |
300 | + .sub{display: flex; align-items: center; gap: 3px;} | |
301 | + } | |
302 | + .top-content{display: flex; justify-content: space-between; gap: 40px; margin-bottom: 15px;} | |
303 | + .boxs{ | |
304 | + width: 550px; | |
305 | + | |
306 | + .blue-box{ | |
307 | + background-color: #D7DCF1; | |
308 | + border-radius: 10px; | |
309 | + padding: 30px; | |
310 | + | |
311 | + .box{background-color: #fff; text-align: center; | |
312 | + padding: 29px 0; | |
313 | + border-radius: 10px; | |
314 | + .time{font-size: 35px; font-weight: 700;} | |
315 | + | |
316 | + } | |
317 | + .buttons{ | |
318 | + display: flex; gap: 25px; | |
319 | + align-items: center; | |
320 | + justify-content: center; | |
321 | + margin-top: 10px; | |
322 | + i.fa-bars{display: block; width: 1px; height: 40px; background-color: #fff;} | |
323 | + } | |
324 | + } | |
325 | + .board{ | |
326 | + border: 1px solid #DDDDDD; border-radius: 10px; | |
327 | + table{ | |
328 | + | |
329 | + tr{ border-bottom: 1px solid #DDDDDD;} | |
330 | + tr:last-child{border: 0;} | |
331 | + td{ | |
332 | + border-radius: 10px; | |
333 | + border: 0; | |
334 | + text-align: center; | |
335 | + p{ | |
336 | + padding: 4px 0; | |
337 | + border-radius: 5px; | |
338 | + width: 85px; | |
339 | + } | |
340 | + } | |
341 | + .category-service p{ | |
342 | + background-color: #FED9AC; | |
343 | + } | |
344 | + | |
345 | + .category-internal p{ | |
346 | + background-color: #EFF1FA; | |
347 | + } | |
348 | + | |
349 | + .category-government p{ | |
350 | + background-color: #DFD4F0; | |
351 | + } | |
352 | + .status p{ | |
353 | + border: 2px solid #AAAAAA; | |
354 | + border-radius: 100px; | |
355 | + color: #AAAAAA; | |
356 | + font-size: 15px; | |
357 | + } | |
358 | + .status-in-progress p{ border-color: #1D75E1; color: #1D75E1;} | |
359 | + .status-complete p{ color: rgb(39, 96, 55); border-color: rgb(39, 96, 55); } | |
360 | + | |
361 | + } | |
362 | + } | |
363 | + } | |
364 | + .card{ | |
365 | + | |
366 | + .sch-form-wrap { | |
367 | + justify-self: end; | |
368 | + margin: 20px 0 ; | |
369 | + .input-group{ | |
370 | + .form-select, .form-control{ | |
371 | + height: 40px; | |
372 | + border-color: #C7CFE3; | |
373 | + font-size: 16px; | |
374 | + } | |
375 | + } | |
376 | + | |
377 | + } | |
378 | + .sch-form-wrap.title-wrap{justify-content: space-between; width: 100%; | |
379 | + h3{ | |
380 | + img{margin: 5px 5px 0 0;} | |
381 | + } | |
382 | + .input-group{width: auto; | |
383 | + .form-control{padding-right: 30px;} | |
384 | + .ico-sch{right: 5px;} | |
385 | + } | |
386 | + } | |
387 | + .tbl-wrap{ | |
388 | + th,td{text-align: center !important; font-size: 16px !important;} | |
389 | + .status-approved { | |
390 | + color: #1D75E1; | |
391 | + font-weight: 700; | |
392 | + } | |
393 | + | |
394 | + .status-pending { | |
395 | + color: #E92727; | |
396 | + font-weight: 700; | |
397 | + } | |
398 | + .expired td{ | |
399 | + background-color: #F6F6F6; /* 연회색 배경 */ | |
400 | + filter: grayscale(50%); | |
401 | + opacity: 0.6; | |
402 | + } | |
403 | + } | |
404 | + p.require{ | |
405 | + text-align: right; | |
406 | + img{margin-top: 9px;} | |
407 | + } | |
408 | + .card-body{ | |
409 | + | |
410 | + form{ | |
411 | + margin-bottom:5rem; | |
412 | + border: 1px solid #C7CFE3; | |
413 | + border-radius: 10px; overflow: hidden;} | |
414 | + .col-12{ | |
415 | + width: 100%; | |
416 | + display: flex; | |
417 | + border-bottom: 1px solid #C7CFE3; | |
418 | + label{width: 140px ;background: #EFF1FA; font-weight: 600; font-size: 16px; text-align: center; line-height: 70px; flex-shrink: 0; position: relative;} | |
419 | + p.require{ | |
420 | + position: absolute; | |
421 | + right: 44px; | |
422 | + top: 26px; | |
423 | + } | |
424 | + select, input{margin: 15px 10px; border-color: #DDDDDD; height: var(--tk-input-h-sm);} | |
425 | + .form-control[readonly]{background-color: #EFF1FA;} | |
426 | + .invalid-feedback{color: #E92727; font-size: 13px;} | |
427 | + .box{ | |
428 | + margin: 15px 10px; | |
429 | + input{margin: 0;} | |
430 | + p{margin-top: 5px;} | |
431 | + } | |
432 | + } | |
433 | + .border-x{border: 0;} | |
434 | + .buttons{display: flex; gap: 10px; justify-content: end; | |
435 | + .btn-red{border-color: #E92727; color: #E92727; background-color: #EFF1FA;} | |
436 | + | |
437 | + } | |
438 | + | |
439 | + } | |
440 | + } | |
441 | +} | |
442 | + | |
443 | +.modal-overlay { | |
444 | + position: fixed; | |
445 | + top: 0; | |
446 | + left: 0; | |
447 | + width: 100%; | |
448 | + height: 100%; | |
449 | + background: rgba(0, 0, 0, 0.4); | |
450 | + display: flex; | |
451 | + align-items: center; | |
452 | + justify-content: center; | |
453 | + z-index: 1000; | |
454 | + } | |
455 | + | |
456 | + .modal-box { | |
457 | + background: white; | |
458 | + padding: 20px 30px; | |
459 | + border-radius: 8px; | |
460 | + text-align: center; | |
461 | + } | |
462 | + | |
463 | + .modal-box button { | |
464 | + margin: 10px; | |
465 | + padding: 8px 16px; | |
466 | + font-size: 14px; | |
467 | + } | |
468 | + | |
469 | + .modal-box .cancel { | |
470 | + background: #ccc; | |
471 | + } | |
472 | + | |
473 | + .primary{background-color: #213F9A;}(파일 끝에 줄바꿈 문자 없음) |
--- client/resources/scss/admin/layout.scss
+++ client/resources/scss/admin/layout.scss
... | ... | @@ -1,18 +1,37 @@ |
1 | 1 |
header{ |
2 |
- background-color: #f8f9fa; |
|
3 |
- padding: 10px 0; |
|
4 |
- border-bottom: 1px solid #e9ecef; |
|
2 |
+ display: flex; |
|
3 |
+ justify-content: space-between; |
|
4 |
+ margin-bottom: 20px; |
|
5 |
+ .title{width: 350px; flex-shrink: 0; text-align: center;} |
|
5 | 6 |
.user-info{ |
6 | 7 |
display: flex; |
7 |
- justify-content: flex-end; |
|
8 |
+ justify-self: flex-end; |
|
8 | 9 |
align-items: center; |
9 | 10 |
.user-name{ |
10 | 11 |
font-size: var(--fz-body-md); |
11 | 12 |
font-weight: 600; |
13 |
+ img{margin-right: 3.5px;} |
|
12 | 14 |
} |
13 | 15 |
.user-logout{ |
14 | 16 |
color: #007bff; |
15 | 17 |
cursor: pointer; |
18 |
+ margin-left: 20px; |
|
16 | 19 |
} |
17 | 20 |
} |
21 |
+} |
|
22 |
+.menu{ |
|
23 |
+ display: flex; |
|
24 |
+ align-items: center ; |
|
25 |
+ li{ |
|
26 |
+ width: 160px; |
|
27 |
+ text-align: center; |
|
28 |
+ a span{font-size: 18px; font-weight: 500;} |
|
29 |
+ } |
|
30 |
+ i.fa-bars{width: 1px; height: 16px; background-color: #8893B4;} |
|
31 |
+} |
|
32 |
+.container{height: 100%; padding: 2rem; background-color: #EFF1FA;} |
|
33 |
+.main-wrap{ |
|
34 |
+ display: flex; |
|
35 |
+ .sidemenu{flex-shrink: 0;} |
|
36 |
+ .content{width: 100%; padding: 30px; background-color: #fff; border-radius: 20px;} |
|
18 | 37 |
}(파일 끝에 줄바꿈 문자 없음) |
--- client/resources/scss/main.scss
+++ client/resources/scss/main.scss
... | ... | @@ -7,4 +7,5 @@ |
7 | 7 |
|
8 | 8 |
|
9 | 9 |
// admin |
10 |
-@import "./admin/layout.scss"(파일 끝에 줄바꿈 문자 없음) |
|
10 |
+@import "./admin/layout.scss"; |
|
11 |
+@import "./admin/content.scss"(파일 끝에 줄바꿈 문자 없음) |
--- client/views/component/GoogleCalendar.vue
+++ client/views/component/GoogleCalendar.vue
... | ... | @@ -1,6 +1,6 @@ |
1 | 1 |
<template> |
2 | 2 |
<div> |
3 |
- <FullCalendar :options="calendarOptions" /> |
|
3 |
+ <FullCalendar :options="calendarOptions" /> |
|
4 | 4 |
</div> |
5 | 5 |
</template> |
6 | 6 |
|
... | ... | @@ -33,11 +33,10 @@ |
33 | 33 |
plugins: [dayGridPlugin], |
34 | 34 |
initialView: 'dayGridMonth', |
35 | 35 |
events: [], |
36 |
- titleFormat: { // 월 제목을 원하는 형식으로 변경 |
|
37 |
- month: 'long', // 'long' 은 'February' 형식으로 |
|
38 |
- year: 'numeric', // '2025' 형식으로 |
|
39 |
- day: 'numeric' // '4' 형식으로 |
|
40 |
- }, |
|
36 |
+ titleFormat: { |
|
37 |
+ year: 'numeric', |
|
38 |
+ month: '2-digit' |
|
39 |
+}, |
|
41 | 40 |
locale: 'ko', // 한국어로 설정 |
42 | 41 |
headerToolbar: { |
43 | 42 |
left: 'prev,next today', |
... | ... | @@ -49,9 +48,11 @@ |
49 | 48 |
month: '월', |
50 | 49 |
week: '주', |
51 | 50 |
day: '일' |
52 |
- } |
|
51 |
+ }, |
|
52 |
+ showNonCurrentDates: false, |
|
53 |
+ fixedWeekCount: false, |
|
53 | 54 |
}); |
54 |
- |
|
55 |
+ |
|
55 | 56 |
const loadClient = () => { |
56 | 57 |
if (window.gapi) { |
57 | 58 |
window.gapi.client.init({ |
... | ... | @@ -107,8 +108,7 @@ |
107 | 108 |
</script> |
108 | 109 |
|
109 | 110 |
<style scoped> |
110 |
-/* FullCalendar 스타일을 CDN을 통해 불러오기 */ |
|
111 |
-@import url('https://cdn.jsdelivr.net/npm/@fullcalendar/core@3.10.2/main.css'); |
|
112 |
-@import url('https://cdn.jsdelivr.net/npm/@fullcalendar/daygrid@3.10.2/main.css'); |
|
113 |
-</style> |
|
114 |
-(파일 끝에 줄바꿈 문자 없음) |
|
111 |
+ .fc .fc-daygrid-day-frame { |
|
112 |
+ background-color: #fff !important; |
|
113 |
+} |
|
114 |
+</style>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/layout/AdminHeader.vue
+++ client/views/layout/AdminHeader.vue
... | ... | @@ -1,23 +1,34 @@ |
1 | 1 |
<template> |
2 | 2 |
<header> |
3 |
+ <div class="title"><img :src="logo" alt="로고"></div> |
|
4 |
+ <Menu></Menu> |
|
3 | 5 |
<div class="user-info"> |
4 | 6 |
<div class="user-name">관리자</div> |
5 |
- <button class="user-logout">로그아웃</button> |
|
7 |
+ <button @click="logout" class="user-logout">로그아웃</button> |
|
6 | 8 |
</div> |
7 | 9 |
</header> |
8 | 10 |
</template> |
9 | 11 |
|
10 | 12 |
<script> |
11 |
- |
|
13 |
+import Menu from '../layout/Menu.vue'; |
|
12 | 14 |
export default { |
13 | 15 |
data() { |
14 | 16 |
return { |
17 |
+ logo: "/client/resources/img/logo.png", |
|
15 | 18 |
}; |
16 | 19 |
}, |
17 |
- methods: {}, |
|
20 |
+ methods: { |
|
21 |
+ logout(){ |
|
22 |
+ localStorage.removeItem('isLoggedIn'); |
|
23 |
+this.$router.push('/login.page'); |
|
24 |
+ |
|
25 |
+ } |
|
26 |
+ }, |
|
18 | 27 |
watch: {}, |
19 | 28 |
computed: {}, |
20 |
- components: {}, |
|
29 |
+ components: { |
|
30 |
+ 'Menu': Menu, |
|
31 |
+ }, |
|
21 | 32 |
created() {}, |
22 | 33 |
mounted() {}, |
23 | 34 |
beforeUnmount() {}, |
--- client/views/layout/Header.vue
+++ client/views/layout/Header.vue
... | ... | @@ -1,54 +1,40 @@ |
1 | 1 |
<template> |
2 |
- <!-- ======= Header ======= --> |
|
3 |
- <header id="header" class="header d-flex fixed-top align-items-center"> |
|
4 |
- |
|
5 |
- <div class=" align-items-center logo"> |
|
6 |
- <a href="/" class=" align-items-center"> |
|
7 |
- <img :src="logo" alt=""> |
|
8 |
- <span class="d-none d-lg-block"></span> |
|
9 |
- </a> |
|
10 |
- <i class="bi bi-list toggle-sidebar-btn" @click="toggleSidebar"></i> |
|
11 |
- </div><!-- End Logo --> |
|
12 |
- |
|
13 |
- |
|
14 |
- <nav class="header-nav ms-auto"> |
|
15 |
- <ul class="d-flex align-items-center"> |
|
16 |
- |
|
17 |
- |
|
18 |
- |
|
19 |
- <li class="nav-item dropdown pe-3"> |
|
20 |
- |
|
21 |
- <a class="nav-link nav-profile d-flex align-items-center pe-0" href="#" data-bs-toggle="dropdown"> |
|
22 |
- <span class="d-none d-md-block dropdown-toggle ps-2">{{ userName }}</span> |
|
23 |
- </a><!-- End Profile Iamge Icon --> |
|
24 |
- |
|
25 |
- |
|
26 |
- </li><!-- End Profile Nav --> |
|
27 |
- |
|
28 |
- </ul> |
|
29 |
- </nav><!-- End Icons Navigation --> |
|
30 |
- |
|
31 |
- </header><!-- End Header --> |
|
2 |
+ <header> |
|
3 |
+ <div class="title"><router-link to="/"><img :src="logo" alt="로고"></router-link></div> |
|
4 |
+ <Menu></Menu> |
|
5 |
+ <div class="user-info"> |
|
6 |
+ <div class="user-name"> <router-link |
|
7 |
+ to="/MyPage.page"><img :src="accounticon" alt="">관리자</router-link></div> |
|
8 |
+ <button @click="logout" class="user-logout"><img :src="logouticon" alt=""></button> |
|
9 |
+ </div> |
|
10 |
+ </header> |
|
32 | 11 |
</template> |
33 | 12 |
|
34 | 13 |
<script> |
14 |
+import Menu from '../layout/Menu.vue'; |
|
35 | 15 |
export default { |
36 |
- name: "ProfileImage", |
|
37 | 16 |
data() { |
38 |
- return { |
|
39 |
- userName: '', |
|
40 |
- logo: "/client/resources/img/logo_t.png", // 경로를 Vue 프로젝트 구조에 맞게 설정 |
|
41 |
- }; |
|
17 |
+ return { |
|
18 |
+ logo: "/client/resources/img/logo.png", |
|
19 |
+ accounticon: "/client/resources/img/account.png", |
|
20 |
+ logouticon: "/client/resources/img/logout.png", |
|
21 |
+ }; |
|
42 | 22 |
}, |
43 | 23 |
methods: { |
44 |
- toggleSidebar() { |
|
45 |
- // `toggle-sidebar` 클래스가 있으면 제거, 없으면 추가 |
|
46 |
- document.body.classList.toggle("toggle-sidebar"); |
|
47 |
- }, |
|
48 |
- }, |
|
49 |
- components: { |
|
50 |
- }, |
|
51 |
-}; |
|
52 |
-</script> |
|
24 |
+ logout(){ |
|
25 |
+ localStorage.removeItem('isLoggedIn'); |
|
26 |
+this.$router.push('/login.page'); |
|
53 | 27 |
|
54 |
-<style></style>(파일 끝에 줄바꿈 문자 없음) |
|
28 |
+ } |
|
29 |
+ }, |
|
30 |
+ watch: {}, |
|
31 |
+ computed: {}, |
|
32 |
+ components: { |
|
33 |
+ 'Menu': Menu, |
|
34 |
+ }, |
|
35 |
+ created() {}, |
|
36 |
+ mounted() {}, |
|
37 |
+ beforeUnmount() {}, |
|
38 |
+}; |
|
39 |
+ |
|
40 |
+</script>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/layout/Menu.vue
... | ... | @@ -0,0 +1,78 @@ |
1 | +<template> | |
2 | + <ul class="menu"> | |
3 | + <li class="nav-item"> | |
4 | + <router-link to="/approval-management.page" class="nav-link " active-class="active"> | |
5 | + <span>결재관리</span></router-link> | |
6 | + </li><!-- End Dashboard Nav --> | |
7 | + <i class="fa-solid fa-bars"></i> | |
8 | + | |
9 | + <li class="nav-item"> | |
10 | + <router-link to="/attendance-management.page" class="nav-link " active-class="active"> | |
11 | + <span>근태관리</span></router-link> | |
12 | + </li> | |
13 | + <i class="fas fa-bars"></i> | |
14 | + <li class="nav-item"> | |
15 | + <router-link to="/task-management.page" class="nav-link " active-class="active"> | |
16 | + <span>업무관리</span></router-link> | |
17 | + </li> | |
18 | + <i class="fas fa-bars"></i> | |
19 | + <li class="nav-item"> | |
20 | + <router-link to="/financial-management.page" class="nav-link " active-class="active"> | |
21 | + <span>재무관리</span></router-link> | |
22 | + </li> | |
23 | + <i class="fas fa-bars"></i> | |
24 | + <li class="nav-item"> | |
25 | + <router-link to="/asset-management.page" class="nav-link " active-class="active"> | |
26 | + <span>자산관리</span></router-link> | |
27 | + </li><!-- End Register Page Nav --> | |
28 | + <i class="fas fa-bars"></i> | |
29 | + <li class="nav-item"> | |
30 | + <router-link to="/hr-management.page" class="nav-link " active-class="active"> | |
31 | + <span>인사관리</span></router-link> | |
32 | + </li><!-- End Login Page Nav --> | |
33 | + <i class="fas fa-bars"></i> | |
34 | + <li class="nav-item"> | |
35 | + <router-link to="/system-management.page" class="nav-link " active-class="active"> | |
36 | + <span>시스템관리</span></router-link> | |
37 | + </li> | |
38 | + | |
39 | + | |
40 | + </ul><!-- End Profile Dropdown Items --> | |
41 | + | |
42 | +</template> | |
43 | + | |
44 | +<script> | |
45 | +export default { | |
46 | + name: "ProfileImage", | |
47 | + data() { | |
48 | + return { | |
49 | + isLoggedIn: false, | |
50 | + userName: '', | |
51 | + logo: "/client/resources/img/logo.png", | |
52 | + }; | |
53 | + }, | |
54 | + methods: { | |
55 | + handleLogout() { | |
56 | + // Clear login-related data from localStorage | |
57 | + localStorage.removeItem('loggedInUser'); | |
58 | + this.isLoggedIn = false; // Update login status | |
59 | + this.$router.push("/login"); // Redirect to login page after logout | |
60 | + }, | |
61 | + toggleSidebar() { | |
62 | + // `toggle-sidebar` 클래스가 있으면 제거, 없으면 추가 | |
63 | + document.body.classList.toggle("toggle-sidebar"); | |
64 | + }, | |
65 | + }, | |
66 | + created() { | |
67 | + // Check if there is any login data in localStorage | |
68 | + const loggedInUser = localStorage.getItem('loggedInUser'); | |
69 | + if (loggedInUser) { | |
70 | + this.isLoggedIn = true; | |
71 | + } | |
72 | + }, | |
73 | + components: { | |
74 | + }, | |
75 | +}; | |
76 | +</script> | |
77 | + | |
78 | +<style></style>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/layout/SideBar.vue
... | ... | @@ -1,155 +0,0 @@ |
1 | -<template> | |
2 | - <!-- ======= Sidebar ======= --> | |
3 | - <aside id="sidebar" class="sidebar hide-in-mobile"> | |
4 | - <div class=" align-items-center logo"> | |
5 | - <a href="/" class=" align-items-center"> | |
6 | - <img :src="logo" alt=""> | |
7 | - <span class="d-none d-lg-block"></span> | |
8 | - </a> | |
9 | - <i class="bi bi-list toggle-sidebar-btn" @click="toggleSidebar"></i> | |
10 | - </div><!-- End Logo --> | |
11 | - <ul class="d-flex profile"> | |
12 | - | |
13 | - <li> | |
14 | - <router-link to="/MyPage" class="nav-link " active-class="active"><i class="bi bi-grid"></i> | |
15 | - <span>마이페이지</span></router-link> | |
16 | - </li> | |
17 | - | |
18 | - <li> | |
19 | - <a class="dropdown-item d-flex align-items-center" href="#"> | |
20 | - <i class="bi bi-box-arrow-right"></i> | |
21 | - <router-link v-if="!isLoggedIn" to="/Login" class="nav-link" active-class="active"> | |
22 | - <i class="bi bi-grid"></i> | |
23 | - <span>로그인</span> | |
24 | - </router-link> | |
25 | - | |
26 | - <router-link v-if="isLoggedIn" @click="handleLogout" class="nav-link" active-class="active"> | |
27 | - <i class="bi bi-box-arrow-right"></i> | |
28 | - <span>로그아웃</span> | |
29 | - </router-link> | |
30 | - </a> | |
31 | - </li> | |
32 | - | |
33 | - </ul><!-- End Profile Dropdown Items --> | |
34 | - <ul class="sidebar-nav" id="sidebar-nav"> | |
35 | - | |
36 | - <li class="nav-item"> | |
37 | - <router-link to="/" class="nav-link " active-class="active"><i class="bi bi-grid"></i> | |
38 | - <span>Home</span></router-link> | |
39 | - </li><!-- End Dashboard Nav --> | |
40 | - | |
41 | - | |
42 | - | |
43 | - <li class="nav-item"> | |
44 | - <a class="nav-link collapsed" data-bs-target="#Chuljang-nav" data-bs-toggle="collapse" href="#"> | |
45 | - <i class="bi bi-journal-text"></i> | |
46 | - <span>출장관리</span><i class="bi bi-chevron-down ms-auto"></i> | |
47 | - </a> | |
48 | - <ul id="Chuljang-nav" class="nav-content collapse " data-bs-parent="#sidebar-nav"> | |
49 | - <li> | |
50 | - <router-link to="/ChuljangInsert" class="nav-link " active-class="active"><i | |
51 | - class="bi bi-circle"></i><span>출장신청</span></router-link> | |
52 | - </li> | |
53 | - <li> | |
54 | - <router-link to="/ChuljangList" class="nav-link " active-class="active"><i | |
55 | - class="bi bi-circle"></i><span>출장내역</span></router-link> | |
56 | - </li> | |
57 | - | |
58 | - </ul> | |
59 | - </li> | |
60 | - <li class="nav-item"> | |
61 | - <a class="nav-link collapsed" data-bs-target="#hyuga-nav" data-bs-toggle="collapse" href="#"> | |
62 | - <i class="bi bi-journal-text"></i> | |
63 | - <span>휴가관리</span><i class="bi bi-chevron-down ms-auto"></i> | |
64 | - </a> | |
65 | - <ul id="hyuga-nav" class="nav-content collapse " data-bs-parent="#sidebar-nav"> | |
66 | - <li> | |
67 | - <router-link to="/HyugaInsert" class="nav-link " active-class="active"><i | |
68 | - class="bi bi-circle"></i><span>휴가신청</span></router-link> | |
69 | - </li> | |
70 | - <li> | |
71 | - <router-link to="/HyugaList" class="nav-link " active-class="active"><i | |
72 | - class="bi bi-circle"></i><span>휴가내역</span></router-link> | |
73 | - </li> | |
74 | - <li> | |
75 | - <router-link to="/HyugaOk" class="nav-link " active-class="active"><i | |
76 | - class="bi bi-circle"></i><span>휴가승인</span></router-link> | |
77 | - </li> | |
78 | - | |
79 | - </ul> | |
80 | - </li> | |
81 | - <li class="nav-item"> | |
82 | - <a class="nav-link collapsed" data-bs-target="#project-nav" data-bs-toggle="collapse" href="#"> | |
83 | - <i class="bi bi-journal-text"></i> | |
84 | - <span>프로젝트관리</span><i class="bi bi-chevron-down ms-auto"></i> | |
85 | - </a> | |
86 | - <ul id="project-nav" class="nav-content collapse " data-bs-parent="#sidebar-nav"> | |
87 | - <li> | |
88 | - <router-link to="/ProjectInsert" class="nav-link " active-class="active"><i | |
89 | - class="bi bi-circle"></i><span>프로젝트 등록</span></router-link> | |
90 | - </li> | |
91 | - | |
92 | - <li> | |
93 | - <router-link to="/ProjectList" class="nav-link " active-class="active"><i class="bi bi-journal-text"></i> | |
94 | - <span>프로젝트 내역</span></router-link> | |
95 | - </li> | |
96 | - | |
97 | - </ul> | |
98 | - </li> | |
99 | - | |
100 | - <li class="nav-item"> | |
101 | - <router-link to="/NoticeList" class="nav-link " active-class="active"><i class="bi bi-journal-text"></i> | |
102 | - <span>공지사항</span></router-link> | |
103 | - </li><!-- End Register Page Nav --> | |
104 | - | |
105 | - <li class="nav-item"> | |
106 | - <router-link to="/EmployeeList" class="nav-link " active-class="active"><i class="bi bi-person"></i> | |
107 | - <span>직원관리</span></router-link> | |
108 | - </li><!-- End Login Page Nav --> | |
109 | - | |
110 | - <li class="nav-item"> | |
111 | - <router-link to="/DeptList" class="nav-link " active-class="active"><i class="bi bi-person"></i> | |
112 | - <span>부서관리</span></router-link> | |
113 | - </li><!-- End Error 404 Page Nav --> | |
114 | - | |
115 | - | |
116 | - </ul> | |
117 | - | |
118 | - </aside><!-- End Sidebar--> | |
119 | -</template> | |
120 | - | |
121 | -<script> | |
122 | -export default { | |
123 | - name: "ProfileImage", | |
124 | - data() { | |
125 | - return { | |
126 | - isLoggedIn: false, | |
127 | - userName: '', | |
128 | - logo: "/client/resources/img/logo_t.png", // 경로를 Vue 프로젝트 구조에 맞게 설정 | |
129 | - }; | |
130 | - }, | |
131 | - methods: { | |
132 | - handleLogout() { | |
133 | - // Clear login-related data from localStorage | |
134 | - localStorage.removeItem('loggedInUser'); | |
135 | - this.isLoggedIn = false; // Update login status | |
136 | - this.$router.push("/login"); // Redirect to login page after logout | |
137 | - }, | |
138 | - toggleSidebar() { | |
139 | - // `toggle-sidebar` 클래스가 있으면 제거, 없으면 추가 | |
140 | - document.body.classList.toggle("toggle-sidebar"); | |
141 | - }, | |
142 | - }, | |
143 | - created() { | |
144 | - // Check if there is any login data in localStorage | |
145 | - const loggedInUser = localStorage.getItem('loggedInUser'); | |
146 | - if (loggedInUser) { | |
147 | - this.isLoggedIn = true; | |
148 | - } | |
149 | - }, | |
150 | - components: { | |
151 | - }, | |
152 | -}; | |
153 | -</script> | |
154 | - | |
155 | -<style></style>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/pages/App.vue
+++ client/views/pages/App.vue
... | ... | @@ -1,23 +1,21 @@ |
1 | 1 |
<template> |
2 |
- <div v-if="this.$route.path.startsWith('/adm')"> |
|
3 |
- <AdminHeader /> |
|
4 |
- <SideBar></SideBar> |
|
5 |
- <main class="main-wrap"> |
|
6 |
- <router-view/> |
|
7 |
- </main> |
|
2 |
+ <div v-if="isLoginPage" class="login"> |
|
3 |
+ <!-- 로그인 페이지에는 헤더 없음 --> |
|
4 |
+ <router-view /> |
|
8 | 5 |
</div> |
9 |
- <div v-else> |
|
10 |
- <main class="main-wrap"> |
|
11 |
- <SideBar></SideBar> |
|
12 |
- <router-view/> |
|
13 |
- </main> |
|
6 |
+ <div v-else class="container"> |
|
7 |
+ <Header /> |
|
8 |
+ |
|
9 |
+ <main class="main-wrap"> |
|
10 |
+ <router-view /> |
|
11 |
+ </main> |
|
14 | 12 |
</div> |
13 |
+ |
|
15 | 14 |
</template> |
16 | 15 |
|
17 | 16 |
<script> |
18 |
-import AdminHeader from '../layout/AdminHeader.vue'; |
|
19 | 17 |
import Header from '../layout/Header.vue'; |
20 |
-import SideBar from '../layout/SideBar.vue'; |
|
18 |
+ |
|
21 | 19 |
import Footer from '../layout/Footer.vue'; |
22 | 20 |
|
23 | 21 |
const App = { |
... | ... | @@ -26,11 +24,14 @@ |
26 | 24 |
}, |
27 | 25 |
methods: {}, |
28 | 26 |
watch: {}, |
29 |
- computed: {}, |
|
27 |
+ computed: { |
|
28 |
+ isLoginPage() { |
|
29 |
+ return this.$route.path === '/login.page'; |
|
30 |
+ }, |
|
31 |
+ }, |
|
30 | 32 |
components: { |
31 |
- 'AdminHeader': AdminHeader, |
|
32 | 33 |
'Header': Header, |
33 |
- 'SideBar': SideBar, |
|
34 |
+ |
|
34 | 35 |
'Footer': Footer, |
35 | 36 |
}, |
36 | 37 |
mounted: () => {} |
--- client/views/pages/AppRouter.js
+++ client/views/pages/AppRouter.js
... | ... | @@ -12,38 +12,58 @@ |
12 | 12 |
|
13 | 13 |
// 직원 |
14 | 14 |
import ChuljangList from '../pages/Employee/ChuljangList.vue'; |
15 |
-import ChuljangInsert from '../pages/Employee/ChuljangInsert.vue'; |
|
16 | 15 |
import HyugaList from '../pages/Employee/HyugaList.vue'; |
17 |
-import HyugaInsert from '../pages/Employee/HyugaInsert.vue'; |
|
18 | 16 |
import HyugaOk from '../pages/Employee/HyugaOk.vue'; |
19 |
-import ProjectList from '../pages/Employee/ProjectList.vue'; |
|
20 |
-import ProjectInsert from '../pages/Employee/ProjectInsert.vue'; |
|
21 | 17 |
// 관리자 |
22 |
-import DeptList from '../pages/Manager/DeptList.vue'; |
|
23 |
-import EmployeeList from '../pages/Manager/EmployeeList.vue'; |
|
24 |
-import NoticeList from '../pages/Manager/NoticeList.vue'; |
|
25 |
-import NoticeInsert from '../pages/Manager/NoticeInsert.vue'; |
|
26 |
-import PubHoliyday from '../pages/Manager/PubHoliyday.vue'; |
|
18 |
+import approval from '../pages/Manager/approval/approval.vue'; |
|
19 |
+import approvalList from '../pages/Manager/approval/approvalList.vue'; |
|
20 |
+import approvalRequest from '../pages/Manager/approval/approvalRequest.vue'; |
|
21 |
+import HyugaInsert from '../pages/Manager/approval/HyugaInsert.vue'; |
|
22 |
+import ChuljangInsert from '../pages/Manager/approval/ChuljangInsert.vue'; |
|
23 |
+import attendance from '../pages/Manager/attendance/attendance.vue'; |
|
24 |
+import task from '../pages/Manager/task/task.vue'; |
|
25 |
+import financial from '../pages/Manager/financial/financial.vue'; |
|
26 |
+import asset from '../pages/Manager/asset/asset.vue'; |
|
27 |
+import hr from '../pages/Manager/hr/hr.vue'; |
|
28 |
+import system from '../pages/Manager/system/system.vue'; |
|
27 | 29 |
|
28 | 30 |
const routes = [ |
29 | 31 |
/* 메인화면 */ |
30 | 32 |
{ path: '/', name: '/', component: Main}, |
31 |
- { path: '/Login', name: 'Login', component: Login}, |
|
32 |
- { path: '/Join', name: 'Join', component: Join}, |
|
33 |
- { path: '/MyPage', name: 'MyPage', component: MyPage}, |
|
33 |
+ { path: '/login.page', name: 'Login', component: Login}, |
|
34 |
+ { path: '/join.page', name: 'Join', component: Join}, |
|
35 |
+ { path: '/MyPage.page', name: 'MyPage', component: MyPage}, |
|
34 | 36 |
|
35 |
- { path: '/ChuljangList', name: 'ChuljangList', component: ChuljangList }, |
|
36 |
- { path: '/ChuljangInsert', name: 'ChuljangInsert', component: ChuljangInsert }, |
|
37 |
- { path: '/HyugaList', name: 'HyugaList', component: HyugaList }, |
|
38 |
- { path: '/HyugaInsert', name: 'HyugaInsert', component: HyugaInsert }, |
|
39 |
- { path: '/HyugaOk', name: 'HyugaOk', component: HyugaOk }, |
|
40 |
- { path: '/ProjectInsert', name: 'ProjectInsert', component: ProjectInsert }, |
|
41 |
- { path: '/ProjectList', name: 'ProjectList', component: ProjectList }, |
|
42 |
- { path: '/DeptList', name: 'DeptList', component: DeptList }, |
|
43 |
- { path: '/EmployeeList', name: 'EmployeeList', component: EmployeeList }, |
|
44 |
- { path: '/NoticeList', name: 'NoticeList', component: NoticeList }, |
|
45 |
- { path: '/NoticeInsert', name: 'NoticeInsert', component: NoticeInsert }, |
|
46 |
- { path: '/PubHoliyday', name: 'PubHoliyday', component: PubHoliyday }, |
|
37 |
+ { path: '/ChuljangList.page', name: 'ChuljangList', component: ChuljangList }, |
|
38 |
+ { path: '/HyugaList.page', name: 'HyugaList', component: HyugaList }, |
|
39 |
+ |
|
40 |
+ { path: '/HyugaOk.page', name: 'HyugaOk', component: HyugaOk }, |
|
41 |
+ |
|
42 |
+ { path: '/approval-management.page', name: 'approval', component: approval, |
|
43 |
+ children: [ |
|
44 |
+ { |
|
45 |
+ path: '/approvalRequest.page', // => /approval-management.page/list |
|
46 |
+ name: 'approvalRequest', |
|
47 |
+ component: approvalRequest, |
|
48 |
+ children: [ |
|
49 |
+ |
|
50 |
+ ] |
|
51 |
+ }, |
|
52 |
+ { |
|
53 |
+ path: '/approvalList.page', // => /approval-management.page/detail/123 |
|
54 |
+ name: 'approvalList', |
|
55 |
+ component: approvalList |
|
56 |
+ }, |
|
57 |
+ { path: '/ChuljangInsert.page', name: 'ChuljangInsert', component: ChuljangInsert }, |
|
58 |
+ { path: '/HyugaInsert.page', name: 'HyugaInsert', component: HyugaInsert }, |
|
59 |
+ ] |
|
60 |
+ }, //결재관리 |
|
61 |
+ { path: '/attendance-management.page', name: 'attendance', component: attendance }, //근태관리 |
|
62 |
+ { path: '/task-management.page', name: 'task', component: task }, //업무관리 |
|
63 |
+ { path: '/financial-management.page', name: 'financial', component: financial }, //재무관리 |
|
64 |
+ { path: '/asset-management.page', name: 'asset', component: asset }, //자산관리 |
|
65 |
+ { path: '/hr-management.page', name: 'hr', component: hr }, //인사관리 |
|
66 |
+ { path: '/system-management.page', name: 'system', component: system }, //시스템관리 |
|
47 | 67 |
]; |
48 | 68 |
|
49 | 69 |
|
... | ... | @@ -52,4 +72,23 @@ |
52 | 72 |
routes, |
53 | 73 |
}); |
54 | 74 |
|
75 |
+// Add navigation guard |
|
76 |
+AppRouter.beforeEach((to, from, next) => { |
|
77 |
+ // Check login status |
|
78 |
+ const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true'; |
|
79 |
+ |
|
80 |
+ // If not logged in and trying to access any page other than login, redirect to login page |
|
81 |
+ if (!isLoggedIn && to.path !== '/login.page') { |
|
82 |
+ next('/login.page'); |
|
83 |
+ } |
|
84 |
+ // If logged in and trying to access login page, redirect to main page |
|
85 |
+ else if (isLoggedIn && to.path === '/login.page') { |
|
86 |
+ next('/'); |
|
87 |
+ } |
|
88 |
+ else { |
|
89 |
+ next(); // Proceed to requested route |
|
90 |
+ } |
|
91 |
+ }); |
|
92 |
+ |
|
93 |
+ |
|
55 | 94 |
export default AppRouter;(파일 끝에 줄바꿈 문자 없음) |
--- client/views/pages/Manager/NoticeInsert copy.vue
... | ... | @@ -1,308 +0,0 @@ |
1 | -<template> | |
2 | - <div class="pagetitle"> | |
3 | - <h2>공지사항 등록</h2> | |
4 | - </div> | |
5 | - <section class="section"> | |
6 | - <div class="card"> | |
7 | - <div class="card-body pt-3"> | |
8 | - <table class="form-table" style="width: 100%;"> | |
9 | - <tbody> | |
10 | - <tr> | |
11 | - <th class="text-lf"> | |
12 | - <span>제목</span> | |
13 | - </th> | |
14 | - <td> | |
15 | - <input | |
16 | - type="text" | |
17 | - class="form-control" | |
18 | - v-model="notice.title" | |
19 | - placeholder="제목을 입력하세요." | |
20 | - /> | |
21 | - </td> | |
22 | - </tr> | |
23 | - <tr class="border-top"> | |
24 | - <th colspan="4" class="text-lf"> | |
25 | - <span>내용</span> | |
26 | - </th> | |
27 | - </tr> | |
28 | - <tr style="max-height: 600px"> | |
29 | - <td colspan="4" style="height: 100%" class="pt-0"> | |
30 | - <textarea name="editor" id="editor" class="form-control" style="width:100%"></textarea> | |
31 | - </td> | |
32 | - </tr> | |
33 | - | |
34 | - <!-- 첨부파일 --> | |
35 | - <tr class="border-top"> | |
36 | - <th class="text-lf"> | |
37 | - 첨부파일 | |
38 | - </th> | |
39 | - <td colspan="2"> | |
40 | - <div class="gd-12 pr0"> | |
41 | - <div class="gd-2 pl0 pr0"> | |
42 | - <label for="file" class="btn btn-outline-primary">파일찾기</label> | |
43 | - <input | |
44 | - type="file" | |
45 | - id="file" | |
46 | - ref="file" | |
47 | - @change="handleFileInsert" | |
48 | - multiple | |
49 | - /> | |
50 | - </div> | |
51 | - <div class="gd-12 pl0 pr0" v-if="fileList.length > 0"> | |
52 | - <ul> | |
53 | - <li | |
54 | - v-for="(file, idx) in fileList" | |
55 | - :key="idx" | |
56 | - class="pd-10 mt-10" | |
57 | - > | |
58 | - <div v-if="file.name" class="flex justify-between file-wrap"> | |
59 | - <p>{{ file.name }}</p> | |
60 | - <button class="del-btn" @click="handleFileDelete(file, idx)"> | |
61 | - X | |
62 | - </button> | |
63 | - </div> | |
64 | - </li> | |
65 | - </ul> | |
66 | - </div> | |
67 | - </div> | |
68 | - </td> | |
69 | - </tr> | |
70 | - | |
71 | - <!-- 공지글 --> | |
72 | - <tr class="border-top"> | |
73 | - <th class="text-lf"> | |
74 | - 공지글 | |
75 | - </th> | |
76 | - <td colspan="3"> | |
77 | - <div class="d-flex no-gutters"> | |
78 | - <div class="col-md-4"> | |
79 | - <input | |
80 | - type="radio" | |
81 | - name="notice" | |
82 | - id="notice-y" | |
83 | - class="mr5" | |
84 | - value="Y" | |
85 | - v-model="notice.isNotice" | |
86 | - /> | |
87 | - <label for="notice-y">사용</label> | |
88 | - </div> | |
89 | - <div class="col-md-4"> | |
90 | - <input | |
91 | - type="radio" | |
92 | - name="notice" | |
93 | - id="notice-n" | |
94 | - class="mr5" | |
95 | - value="N" | |
96 | - v-model="notice.isNotice" | |
97 | - /> | |
98 | - <label for="notice-n">미사용</label> | |
99 | - </div> | |
100 | - </div> | |
101 | - </td> | |
102 | - </tr> | |
103 | - | |
104 | - <!-- 공지글 게시기간 --> | |
105 | - <tr class="border-top"> | |
106 | - <th class="text-lf"> | |
107 | - 공지글 게시기간 | |
108 | - </th> | |
109 | - <td colspan="3"> | |
110 | - <div class="d-flex no-gutters"> | |
111 | - <div class="col-md-4"> | |
112 | - <input | |
113 | - type="datetime-local" | |
114 | - class="form-control" | |
115 | - v-model="notice.startDate" | |
116 | - @change="checkDateValidity('startDate', $event)" | |
117 | - /> | |
118 | - </div> | |
119 | - <div class="pd-1">-</div> | |
120 | - <div class="col-md-4"> | |
121 | - <input | |
122 | - type="datetime-local" | |
123 | - class="form-control" | |
124 | - v-model="notice.endDate" | |
125 | - @change="checkDateValidity('endDate', $event)" | |
126 | - /> | |
127 | - </div> | |
128 | - </div> | |
129 | - </td> | |
130 | - </tr> | |
131 | - | |
132 | - <!-- 비밀글 --> | |
133 | - <tr class="border-top"> | |
134 | - <th class="text-lf"> | |
135 | - 비밀글 | |
136 | - </th> | |
137 | - <td colspan="3"> | |
138 | - <div class="d-flex no-gutters"> | |
139 | - <div class="col-md-4"> | |
140 | - <input | |
141 | - type="radio" | |
142 | - name="private" | |
143 | - id="private-y" | |
144 | - class="mr5" | |
145 | - value="Y" | |
146 | - v-model="notice.isPrivate" | |
147 | - /> | |
148 | - <label for="private-y">사용</label> | |
149 | - </div> | |
150 | - <div class="col-md-4"> | |
151 | - <input | |
152 | - type="radio" | |
153 | - name="private" | |
154 | - id="private-n" | |
155 | - class="mr5" | |
156 | - value="N" | |
157 | - v-model="notice.isPrivate" | |
158 | - /> | |
159 | - <label for="private-n">미사용</label> | |
160 | - </div> | |
161 | - </div> | |
162 | - </td> | |
163 | - </tr> | |
164 | - </tbody> | |
165 | - </table> | |
166 | - <div class="text-end"> | |
167 | - <button class="btn btn-primary" @click="handleInsert"> | |
168 | - {{ notice.id == null ? "등록" : "수정" }} | |
169 | - </button> | |
170 | - <button class="btn btn-secondary" @click="handleCancel">취소</button> | |
171 | - </div> | |
172 | - </div> | |
173 | - </div> | |
174 | - </section> | |
175 | -</template> | |
176 | - | |
177 | -<script> | |
178 | -import UploadAdapter from './UploadAdapter.js' | |
179 | - | |
180 | - | |
181 | -export default { | |
182 | - data() { | |
183 | - return { | |
184 | - editor: null, | |
185 | - notice: { | |
186 | - id: null, | |
187 | - title: "", | |
188 | - isNotice: "Y", | |
189 | - startDate: null, | |
190 | - endDate: null, | |
191 | - isPrivate: "N", | |
192 | - | |
193 | - }, | |
194 | - fileList: [], | |
195 | - }; | |
196 | - }, | |
197 | - methods: { | |
198 | - handleCancel() { | |
199 | - if (!confirm("등록을 취소하시겠습니까?")) { | |
200 | - return; | |
201 | - } | |
202 | - this.$router.push({ path: "/list.page" }); | |
203 | - }, | |
204 | - | |
205 | - handleFileInsert() { | |
206 | - this.fileList = [...this.fileList, ...Array.from(this.$refs.file.files)]; | |
207 | - }, | |
208 | - | |
209 | - handleFileDelete(file, index) { | |
210 | - this.fileList.splice(index, 1); | |
211 | - }, | |
212 | - | |
213 | - handleInsert() { | |
214 | - if (!this.validate()) { | |
215 | - return; | |
216 | - } | |
217 | - | |
218 | - if (this.notice.id == null) { | |
219 | - this.saveNotice(); | |
220 | - } else { | |
221 | - this.updateNotice(); | |
222 | - } | |
223 | - }, | |
224 | - | |
225 | - saveNotice() { | |
226 | - alert("공지사항이 등록되었습니다."); | |
227 | - this.$router.push({ path: "/view.page", query: { pageId: this.notice.id } }); | |
228 | - }, | |
229 | - | |
230 | - updateNotice() { | |
231 | - alert("공지사항이 수정되었습니다."); | |
232 | - this.$router.push({ path: "/view.page", query: { pageId: this.notice.id } }); | |
233 | - }, | |
234 | - | |
235 | - validate() { | |
236 | - if (!this.notice.title.trim()) { | |
237 | - alert("제목을 입력해주세요."); | |
238 | - return false; | |
239 | - } | |
240 | - | |
241 | - if (!this.notice.content.trim()) { | |
242 | - alert("내용을 입력해주세요."); | |
243 | - return false; | |
244 | - } | |
245 | - | |
246 | - if (this.notice.isNotice === "Y" && (!this.notice.startDate || !this.notice.endDate)) { | |
247 | - alert("공지기간을 올바르게 설정해주세요."); | |
248 | - return false; | |
249 | - } | |
250 | - | |
251 | - return true; | |
252 | - }, | |
253 | - | |
254 | - checkDateValidity(field, event) { | |
255 | - const val = event.target.value; | |
256 | - if (field === "startDate" && this.notice.endDate && this.notice.endDate < val) { | |
257 | - alert("시작일은 종료일보다 클 수 없습니다."); | |
258 | - this.notice.startDate = null; | |
259 | - } else if (field === "endDate" && this.notice.startDate && this.notice.startDate > val) { | |
260 | - alert("종료일은 시작일보다 작을 수 없습니다."); | |
261 | - this.notice.endDate = null; | |
262 | - } | |
263 | - }, | |
264 | - | |
265 | - beforeUnmount() { | |
266 | - if (this.editor) { | |
267 | - this.editor.destroy().then(() => { | |
268 | - this.editor = null; | |
269 | - }); | |
270 | - } | |
271 | - }, | |
272 | - | |
273 | - MyCustomUploadAdapterPlugin: function(editor) { | |
274 | - editor.plugins.get('FileRepository').createUploadAdapter = (loader) => { | |
275 | - console.log('loader', loader); | |
276 | - return new UploadAdapter(loader); | |
277 | - } | |
278 | - } | |
279 | - }, | |
280 | - mounted() { | |
281 | -}, | |
282 | - | |
283 | -MyCustomUploadAdapterPlugin(editor) { | |
284 | - editor.plugins.get('FileRepository').createUploadAdapter = (loader) => { | |
285 | - console.log('loader', loader); | |
286 | - return new UploadAdapter(loader); | |
287 | - } | |
288 | -}, | |
289 | -}; | |
290 | -</script> | |
291 | - | |
292 | -<style scoped> | |
293 | -td, | |
294 | -th { | |
295 | - padding: 1rem; | |
296 | -} | |
297 | -th { | |
298 | - width: 10rem; | |
299 | -} | |
300 | -#file { | |
301 | - position: absolute; | |
302 | - width: 0; | |
303 | - height: 0; | |
304 | - padding: 0; | |
305 | - overflow: hidden; | |
306 | - border: 0; | |
307 | -} | |
308 | -</style> |
--- client/views/pages/Employee/ChuljangInsert.vue
+++ client/views/pages/Manager/approval/ChuljangInsert.vue
No changes |
--- client/views/pages/Employee/HyugaInsert.vue
+++ client/views/pages/Manager/approval/HyugaInsert.vue
No changes |
--- client/views/pages/Manager/DeptList.vue
+++ client/views/pages/Manager/approval/approval.vue
... | ... | @@ -1,64 +1,46 @@ |
1 | 1 |
<template> |
2 |
- <div class="pagetitle"> |
|
3 |
- <h2>부서관리</h2> |
|
2 |
+ <div class="sidemenu"> |
|
3 |
+ <div class="myinfo simple"> |
|
4 |
+ <div class="name-box"> |
|
5 |
+ <div class="img-area"> |
|
6 |
+ <div><img :src="photoicon" alt=""> |
|
7 |
+ <p class="name">OOO과장</p> |
|
8 |
+ </div> |
|
9 |
+ <div class="info"> |
|
10 |
+ <p>솔루션 개발팀</p> |
|
11 |
+ <i class="fa-bars"></i> |
|
12 |
+ <p>팀장</p> |
|
13 |
+ </div> |
|
14 |
+ </div> |
|
15 |
+ </div> |
|
16 |
+ |
|
17 |
+ <h2>결재</h2> |
|
18 |
+ <ul class="menu-box"> |
|
19 |
+ <li> |
|
20 |
+ <router-link to="/approvalRequest.page" exact-active-class="active-link" v-slot="{ isExactActive }"> |
|
21 |
+ <p>결재 요청</p> |
|
22 |
+ <div class="icon" v-if="isExactActive"> |
|
23 |
+ <img :src="menuicon" alt=""> |
|
24 |
+ </div> |
|
25 |
+ </router-link> |
|
26 |
+ |
|
27 |
+ </li> |
|
28 |
+ <li> |
|
29 |
+ <router-link to="/approvalList.page" exact-active-class="active-link" v-slot="{ isExactActive }"> |
|
30 |
+ <p>승인 대기 목록</p> |
|
31 |
+ <div class="icon" v-if="isExactActive"> |
|
32 |
+ <img :src="menuicon" alt=""> |
|
33 |
+ </div> |
|
34 |
+ </router-link> |
|
35 |
+ </li> |
|
36 |
+ </ul> |
|
37 |
+ </div> |
|
4 | 38 |
</div> |
5 | 39 |
<!-- End Page Title --> |
6 |
- <section class="section"> |
|
7 |
- <div class="row"> |
|
40 |
+ <div class="content"> |
|
41 |
+ <router-view></router-view> |
|
8 | 42 |
|
9 |
- |
|
10 |
- <div class="col-lg-12"> |
|
11 |
- <div class="card"> |
|
12 |
- <div class="card-body"> |
|
13 |
- <h5 class="card-title">부서관리</h5> |
|
14 |
- <div class="d-flex pb-3 justify-content-end "> |
|
15 |
- |
|
16 |
- <div class="d-flex justify-content-end "> |
|
17 |
- <button @click="onClickSubmit">api 요청하기</button> |
|
18 |
- <button type="button" class="btn btn-outline-primary" @click="registerLeave"> |
|
19 |
- 등록 |
|
20 |
- </button> |
|
21 |
- <button type="button" class="btn btn-outline-success" @click="saveChanges"> |
|
22 |
- 저장 |
|
23 |
- </button> |
|
24 |
- <button type="button" class="btn btn-outline-secondary" @click="deletePending"> |
|
25 |
- 삭제 |
|
26 |
- </button> |
|
27 |
- </div> |
|
28 |
- |
|
29 |
- </div> |
|
30 |
- <!-- Table --> |
|
31 |
- <table id="myTable" class="table datatable table-hover"> |
|
32 |
- <!-- 동적으로 <th> 생성 --> |
|
33 |
- <thead> |
|
34 |
- <tr> |
|
35 |
- <th>No </th> |
|
36 |
- <th>부서명</th> |
|
37 |
- <th>인원</th> |
|
38 |
- </tr> |
|
39 |
- </thead> |
|
40 |
- <!-- 동적으로 <td> 생성 --> |
|
41 |
- <tbody> |
|
42 |
- <tr v-for="(item, index) in DeptData"> |
|
43 |
- <td> |
|
44 |
- <div class="form-check"> |
|
45 |
- <label class="form-check-label" for="acceptTerms">{{ index + 1 }}</label> |
|
46 |
- <input v-model="item.acceptTerms" class="form-check-input" type="checkbox" /> |
|
47 |
- </div> |
|
48 |
- </td> |
|
49 |
- <td><input type="text" v-model="item.deptNM" /></td> |
|
50 |
- <td><input type="text" v-model="item.member" /></td> |
|
51 |
- |
|
52 |
- </tr> |
|
53 |
- </tbody> |
|
54 |
- </table> |
|
55 |
- |
|
56 |
- <!-- End Table --> |
|
57 |
- </div> |
|
58 |
- </div> |
|
59 |
- </div> |
|
60 |
- </div> |
|
61 |
- </section> |
|
43 |
+</div> |
|
62 | 44 |
</template> |
63 | 45 |
|
64 | 46 |
<script> |
... | ... | @@ -67,6 +49,8 @@ |
67 | 49 |
export default { |
68 | 50 |
data() { |
69 | 51 |
return { |
52 |
+ photoicon: "/client/resources/img/photo_icon.png", |
|
53 |
+ menuicon: "/client/resources/img/menuicon.png", |
|
70 | 54 |
// 데이터 초기화 |
71 | 55 |
years: [2023, 2024, 2025], // 연도 목록 |
72 | 56 |
months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 |
+++ client/views/pages/Manager/approval/approvalList.vue
... | ... | @@ -0,0 +1,233 @@ |
1 | +<template> | |
2 | + <div class="col-lg-12"> | |
3 | + <div class="card"> | |
4 | + <div class="card-body"> | |
5 | + <h2 class="card-title">승인 대기 목록</h2> | |
6 | + <div class="sch-form-wrap title-wrap"> | |
7 | + <h3><img :src="h3icon" alt="">승인 대기</h3> | |
8 | + <div class="input-group"> | |
9 | + <select name="" id="" class="form-select"> | |
10 | + <option value="">년도</option> | |
11 | + </select> | |
12 | + <select name="" id="" class="form-select"> | |
13 | + <option value="">월</option> | |
14 | + </select> | |
15 | + <div class="sch-input"> | |
16 | + <input type="text" class="form-control"> | |
17 | + <button class="ico-sch"><SearchOutlined /></button> | |
18 | + </div> | |
19 | + </div> | |
20 | + </div> | |
21 | + | |
22 | + <!-- Table --> | |
23 | + <div class="tbl-wrap"> | |
24 | + <table id="myTable" class="tbl data"> | |
25 | + <!-- 동적으로 <th> 생성 --> | |
26 | + <thead> | |
27 | + <tr> | |
28 | + <th>구분 </th> | |
29 | + <th>결재구분</th> | |
30 | + <th>신청자</th> | |
31 | + <th>기간</th> | |
32 | + <th>신청일</th> | |
33 | + <th>상태</th> | |
34 | + </tr> | |
35 | + </thead> | |
36 | + <!-- 동적으로 <td> 생성 --> | |
37 | + <tbody> | |
38 | + <tr v-for="(item, index) in listData" :key="index" :class="{ 'expired': isPastPeriod(item.period) }"> | |
39 | + <td>{{ item.type }}</td> | |
40 | + <td>{{ item.approvalType }}</td> | |
41 | + <td>{{ item.applicant }}</td> | |
42 | + <td>{{ item.period }}</td> | |
43 | + <td>{{ item.requestDate }}</td> | |
44 | + <td :class="getStatusClass(item.status)">{{ item.status }}</td> | |
45 | + </tr> | |
46 | + </tbody> | |
47 | + </table> | |
48 | + | |
49 | + </div> | |
50 | + <div class="pagination"> | |
51 | + <ul> | |
52 | + <!-- 왼쪽 화살표 (이전 페이지) --> | |
53 | + <li class="arrow" :class="{ disabled: currentPage === 1 }" @click="changePage(currentPage - 1)"> | |
54 | + < | |
55 | + </li> | |
56 | + | |
57 | + <!-- 페이지 번호 --> | |
58 | + <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }" | |
59 | + @click="changePage(page)"> | |
60 | + {{ page }} | |
61 | + </li> | |
62 | + | |
63 | + <!-- 오른쪽 화살표 (다음 페이지) --> | |
64 | + <li class="arrow" :class="{ disabled: currentPage === totalPages }" @click="changePage(currentPage + 1)"> | |
65 | + > | |
66 | + </li> | |
67 | + </ul> | |
68 | + </div> | |
69 | + <div class="sch-form-wrap title-wrap"> | |
70 | + <h3><img :src="h3icon" alt="">승인 이력</h3> | |
71 | + <div class="input-group"> | |
72 | + <select name="" id="" class="form-select"> | |
73 | + <option value="">년도</option> | |
74 | + </select> | |
75 | + <select name="" id="" class="form-select"> | |
76 | + <option value="">월</option> | |
77 | + </select> | |
78 | + <select name="" id="" class="form-select"> | |
79 | + <option value="">상태</option> | |
80 | + </select> | |
81 | + <div class="sch-input"> | |
82 | + <input type="text" class="form-control"> | |
83 | + <button class="ico-sch"><SearchOutlined /></button> | |
84 | + </div> | |
85 | + </div> | |
86 | + </div> | |
87 | + | |
88 | + <!-- Table --> | |
89 | + <div class="tbl-wrap"> | |
90 | + <table id="myTable" class="tbl data"> | |
91 | + <!-- 동적으로 <th> 생성 --> | |
92 | + <thead> | |
93 | + <tr> | |
94 | + <th>구분 </th> | |
95 | + <th>결재구분</th> | |
96 | + <th>신청자</th> | |
97 | + <th>기간</th> | |
98 | + <th>신청일</th> | |
99 | + <th>상태</th> | |
100 | + </tr> | |
101 | + </thead> | |
102 | + <!-- 동적으로 <td> 생성 --> | |
103 | + <tbody> | |
104 | + <tr v-for="(item, index) in listData" :key="index" :class="{ 'expired': isPastPeriod(item.period) }"> | |
105 | + <td>{{ item.type }}</td> | |
106 | + <td>{{ item.approvalType }}</td> | |
107 | + <td>{{ item.applicant }}</td> | |
108 | + <td>{{ item.period }}</td> | |
109 | + <td>{{ item.requestDate }}</td> | |
110 | + <td :class="getStatusClass(item.status)">{{ item.status }}</td> | |
111 | + </tr> | |
112 | + </tbody> | |
113 | + </table> | |
114 | + | |
115 | + </div> | |
116 | + <div class="pagination"> | |
117 | + <ul> | |
118 | + <!-- 왼쪽 화살표 (이전 페이지) --> | |
119 | + <li class="arrow" :class="{ disabled: currentPage === 1 }" @click="changePage(currentPage - 1)"> | |
120 | + < | |
121 | + </li> | |
122 | + | |
123 | + <!-- 페이지 번호 --> | |
124 | + <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }" | |
125 | + @click="changePage(page)"> | |
126 | + {{ page }} | |
127 | + </li> | |
128 | + | |
129 | + <!-- 오른쪽 화살표 (다음 페이지) --> | |
130 | + <li class="arrow" :class="{ disabled: currentPage === totalPages }" @click="changePage(currentPage + 1)"> | |
131 | + > | |
132 | + </li> | |
133 | + </ul> | |
134 | + </div> | |
135 | + </div> | |
136 | + </div> | |
137 | + </div> | |
138 | +</template> | |
139 | + | |
140 | +<script> | |
141 | +import { ref } from 'vue'; | |
142 | +import { SearchOutlined } from '@ant-design/icons-vue'; | |
143 | +export default { | |
144 | + data() { | |
145 | + return { | |
146 | + currentPage: 1, | |
147 | + totalPages: 3, | |
148 | + photoicon: "/client/resources/img/photo_icon.png", | |
149 | + h3icon: "/client/resources/img/h3icon.png", | |
150 | + // 데이터 초기화 | |
151 | + years: [2023, 2024, 2025], // 연도 목록 | |
152 | + months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 | |
153 | + selectedYear: '', | |
154 | + selectedMonth: '', | |
155 | + listData: [ | |
156 | + { | |
157 | + type: '연차', | |
158 | + approvalType: '결재', | |
159 | + applicant: '홍길동', | |
160 | + period: '2025-05-10 ~ 2025-15-03', | |
161 | + requestDate: '2025-04-25', | |
162 | + status: '대기' | |
163 | + }, { | |
164 | + type: '반차', | |
165 | + approvalType: '전결', | |
166 | + applicant: '홍길동', | |
167 | + period: '2025-05-01 ~ 2025-05-03', | |
168 | + requestDate: '2025-04-25', | |
169 | + status: '승인' | |
170 | + }], | |
171 | + filteredData: [], | |
172 | + }; | |
173 | + }, | |
174 | + computed: { | |
175 | + }, | |
176 | + components:{ | |
177 | + SearchOutlined | |
178 | + }, | |
179 | + methods: { | |
180 | + changePage(page) { | |
181 | + if (page < 1 || page > this.totalPages) return; | |
182 | + this.currentPage = page; | |
183 | + this.$emit('page-changed', page); // 필요 시 부모에 알림 | |
184 | + }, | |
185 | + async onClickSubmit() { | |
186 | + // `useMutation` 훅을 사용하여 mutation 함수 가져오기 | |
187 | + const { mutate, onDone, onError } = useMutation(mygql); | |
188 | + | |
189 | + try { | |
190 | + const result = await mutate(); | |
191 | + console.log(result); | |
192 | + } catch (error) { | |
193 | + console.error('Mutation error:', error); | |
194 | + } | |
195 | + }, | |
196 | + registerLeave() { | |
197 | + console.log("등록 버튼 클릭됨"); | |
198 | + | |
199 | + // Vue의 반응성 문제를 피하기 위해, 새로운 객체를 추가합니다. | |
200 | + this.DeptData = [ | |
201 | + ...this.DeptData, | |
202 | + { member: '', deptNM: '', acceptTerms: false } | |
203 | + ]; | |
204 | + | |
205 | + console.log(this.DeptData); // 배열 상태 출력 | |
206 | + }, | |
207 | + getStatusClass(status) { | |
208 | + if (status === '승인') return 'status-approved'; | |
209 | + if (status === '대기') return 'status-pending'; | |
210 | + return ''; | |
211 | + }, | |
212 | + isPastPeriod(period) { | |
213 | + // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출 | |
214 | + const endDateStr = period.split('~')[1]?.trim(); | |
215 | + if (!endDateStr) return false; | |
216 | + | |
217 | + const endDate = new Date(endDateStr); | |
218 | + const today = new Date(); | |
219 | + | |
220 | + // 현재 날짜보다 과거면 true | |
221 | + return endDate < today; | |
222 | + } | |
223 | + }, | |
224 | + created() { | |
225 | + }, | |
226 | + mounted() { | |
227 | + | |
228 | + | |
229 | + }, | |
230 | +}; | |
231 | +</script> | |
232 | + | |
233 | +<style scoped></style>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/pages/Manager/approval/approvalRequest.vue
... | ... | @@ -0,0 +1,186 @@ |
1 | +<template> | |
2 | +<div class="col-lg-12"> | |
3 | + <div class="card"> | |
4 | + <div class="card-body"> | |
5 | + <h2 class="card-title">결재 요청</h2> | |
6 | + <div class="sch-form-wrap"> | |
7 | + <div class="input-group"> | |
8 | + <select name="" id="" class="form-select"> | |
9 | + <option value="">년도</option> | |
10 | + </select> | |
11 | + <select name="" id="" class="form-select"> | |
12 | + <option value="">월</option> | |
13 | + </select> | |
14 | + <select name="" id="" class="form-select"> | |
15 | + <option value="">구분</option> | |
16 | + </select> | |
17 | + </div> | |
18 | + </div> | |
19 | + | |
20 | + <!-- Table --> | |
21 | + <div class="tbl-wrap"> | |
22 | + <table id="myTable" class="tbl data"> | |
23 | + <!-- 동적으로 <th> 생성 --> | |
24 | + <thead> | |
25 | + <tr> | |
26 | + <th>구분 </th> | |
27 | + <th>결재구분</th> | |
28 | + <th>신청자</th> | |
29 | + <th>기간</th> | |
30 | + <th>신청일</th> | |
31 | + <th>상태</th> | |
32 | + </tr> | |
33 | + </thead> | |
34 | + <!-- 동적으로 <td> 생성 --> | |
35 | + <tbody> | |
36 | + <tr v-for="(item, index) in listData" :key="index" :class="{ 'expired': isPastPeriod(item.period) }"> | |
37 | + <td>{{ item.type }}</td> | |
38 | + <td>{{ item.approvalType }}</td> | |
39 | + <td>{{ item.applicant }}</td> | |
40 | + <td>{{ item.period }}</td> | |
41 | + <td>{{ item.requestDate }}</td> | |
42 | + <td :class="getStatusClass(item.status)">{{ item.status }}</td> | |
43 | + </tr> | |
44 | + </tbody> | |
45 | + </table> | |
46 | + | |
47 | + </div> | |
48 | + <div class="pagination"> | |
49 | + <ul> | |
50 | + <!-- 왼쪽 화살표 (이전 페이지) --> | |
51 | + <li | |
52 | + class="arrow" | |
53 | + :class="{ disabled: currentPage === 1 }" | |
54 | + @click="changePage(currentPage - 1)" | |
55 | + > | |
56 | + < | |
57 | + </li> | |
58 | + | |
59 | + <!-- 페이지 번호 --> | |
60 | + <li | |
61 | + v-for="page in totalPages" | |
62 | + :key="page" | |
63 | + :class="{ active: currentPage === page }" | |
64 | + @click="changePage(page)" | |
65 | + > | |
66 | + {{ page }} | |
67 | + </li> | |
68 | + | |
69 | + <!-- 오른쪽 화살표 (다음 페이지) --> | |
70 | + <li | |
71 | + class="arrow" | |
72 | + :class="{ disabled: currentPage === totalPages }" | |
73 | + @click="changePage(currentPage + 1)" | |
74 | + > | |
75 | + > | |
76 | + </li> | |
77 | + </ul> | |
78 | + </div> | |
79 | + <!-- End Table --> | |
80 | + <div class="buttons"> | |
81 | + <button type="button" class="btn sm primary" @click="showOptions = true"> | |
82 | + 등록 | |
83 | + </button> | |
84 | + | |
85 | + <!-- 신청 종류 선택 모달 --> | |
86 | + <div v-if="showOptions" class="modal-overlay"> | |
87 | + <div class="modal-box"> | |
88 | + <p>신청 종류를 선택하세요</p> | |
89 | + <button @click="goToPage('휴가')">휴가신청</button> | |
90 | + <button @click="goToPage('출장')">출장신청</button> | |
91 | + <button class="cancel" @click="showOptions = false">취소</button> | |
92 | + </div> | |
93 | + </div> | |
94 | + </div> | |
95 | + </div> | |
96 | + </div> | |
97 | +</div> | |
98 | +</template> | |
99 | + | |
100 | +<script> | |
101 | +import { ref } from 'vue'; | |
102 | + | |
103 | +export default { | |
104 | + data() { | |
105 | + return { | |
106 | + showOptions: false, | |
107 | + currentPage: 1, | |
108 | + totalPages: 3, | |
109 | + photoicon: "/client/resources/img/photo_icon.png", | |
110 | + // 데이터 초기화 | |
111 | + years: [2023, 2024, 2025], // 연도 목록 | |
112 | + months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 | |
113 | + selectedYear: '', | |
114 | + selectedMonth: '', | |
115 | + listData: [ | |
116 | + { | |
117 | + type: '연차', | |
118 | + approvalType: '결재', | |
119 | + applicant: '홍길동', | |
120 | + period: '2025-05-10 ~ 2025-15-03', | |
121 | + requestDate: '2025-04-25', | |
122 | + status: '대기' | |
123 | + }, { | |
124 | + type: '반차', | |
125 | + approvalType: '전결', | |
126 | + applicant: '홍길동', | |
127 | + period: '2025-05-01 ~ 2025-05-03', | |
128 | + requestDate: '2025-04-25', | |
129 | + status: '승인' | |
130 | + }], | |
131 | + filteredData: [], | |
132 | + }; | |
133 | + }, | |
134 | + computed: { | |
135 | + }, | |
136 | + methods: { | |
137 | + changePage(page) { | |
138 | + if (page < 1 || page > this.totalPages) return; | |
139 | + this.currentPage = page; | |
140 | + this.$emit('page-changed', page); // 필요 시 부모에 알림 | |
141 | + }, | |
142 | + async onClickSubmit() { | |
143 | + // `useMutation` 훅을 사용하여 mutation 함수 가져오기 | |
144 | + const { mutate, onDone, onError } = useMutation(mygql); | |
145 | + | |
146 | + try { | |
147 | + const result = await mutate(); | |
148 | + console.log(result); | |
149 | + } catch (error) { | |
150 | + console.error('Mutation error:', error); | |
151 | + } | |
152 | + }, | |
153 | + goToPage(type) { | |
154 | + if (type === '휴가') { | |
155 | + this.$router.push('/HyugaInsert.page'); | |
156 | + } else if (type === '출장') { | |
157 | + this.$router.push('/ChuljangInsert.page'); | |
158 | + } | |
159 | +}, | |
160 | + getStatusClass(status) { | |
161 | + if (status === '승인') return 'status-approved'; | |
162 | + if (status === '대기') return 'status-pending'; | |
163 | + return ''; | |
164 | + }, | |
165 | + isPastPeriod(period) { | |
166 | + // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출 | |
167 | + const endDateStr = period.split('~')[1]?.trim(); | |
168 | + if (!endDateStr) return false; | |
169 | + | |
170 | + const endDate = new Date(endDateStr); | |
171 | + const today = new Date(); | |
172 | + | |
173 | + // 현재 날짜보다 과거면 true | |
174 | + return endDate < today; | |
175 | + } | |
176 | + }, | |
177 | + created() { | |
178 | + }, | |
179 | + mounted() { | |
180 | + | |
181 | + | |
182 | + }, | |
183 | +}; | |
184 | +</script> | |
185 | + | |
186 | +<style scoped></style> |
--- client/views/pages/Manager/PubHoliyday.vue
+++ client/views/pages/Manager/asset/asset.vue
No changes |
--- client/views/pages/Manager/EmployeeList.vue
+++ client/views/pages/Manager/attendance/attendance.vue
No changes |
--- client/views/pages/Manager/NoticeInsert.vue
+++ client/views/pages/Manager/financial/financial.vue
No changes |
--- client/views/pages/Employee/ProjectInsert.vue
+++ client/views/pages/Manager/hr/hr.vue
No changes |
--- client/views/pages/Employee/ProjectList.vue
+++ client/views/pages/Manager/system/system.vue
No changes |
--- client/views/pages/Manager/NoticeList.vue
+++ client/views/pages/Manager/task/task.vue
No changes |
--- client/views/pages/User/Join.vue
+++ client/views/pages/User/Join.vue
... | ... | @@ -87,7 +87,7 @@ |
87 | 87 |
level: '', |
88 | 88 |
acceptTerms: false, |
89 | 89 |
formSubmitted: false, |
90 |
- logo: "/client/resources/img/logo_t.png", // 경로를 Vue 프로젝트 구조에 맞게 설정 |
|
90 |
+ logo: "/client/resources/img/logo.png", // 경로를 Vue 프로젝트 구조에 맞게 설정 |
|
91 | 91 |
}; |
92 | 92 |
}, |
93 | 93 |
methods: { |
--- client/views/pages/User/Login.vue
+++ client/views/pages/User/Login.vue
... | ... | @@ -1,68 +1,30 @@ |
1 | 1 |
<template> |
2 |
- <div class="container"> |
|
3 |
- |
|
4 |
- <section class="section register min-vh-100 d-flex flex-column align-items-center justify-content-center py-4"> |
|
5 |
- <div class="container"> |
|
6 |
- <div class="row justify-content-center"> |
|
7 |
- <div class="col-lg-4 col-md-6 align-items-center justify-content-center"> |
|
8 |
- <div class="d-flex justify-content-center py-4"> |
|
9 |
- <a href="index.html" class="logo d-flex align-items-center w-auto"> |
|
10 |
- |
|
11 |
- <!-- <span class="d-none d-lg-block"> <img :src="logo" alt=""></span> --> |
|
12 |
- </a> |
|
13 |
- </div><!-- End Logo --> |
|
14 |
- |
|
15 |
- <div class="card mb-3"> |
|
16 |
- |
|
2 |
+ <div class=""> |
|
3 |
+ <div class="card mb-3"> |
|
17 | 4 |
<div class="card-body"> |
18 |
- |
|
19 |
- <div class=" pb-2"> |
|
20 |
- <h2 class="card-title text-center pb-0 fs-4">로그인</h2> |
|
5 |
+ <div class="pb-2"> |
|
6 |
+ <h2 class="card-title text-center pb-0 fs-4"><img :src="logo" alt="로고"></h2> |
|
21 | 7 |
</div> |
22 | 8 |
|
23 |
- <form class="row g-3 needs-validation" :class="{ 'was-validated': formSubmitted }" novalidate |
|
24 |
- @submit.prevent="handleLogin"> |
|
25 |
- |
|
26 |
- <div class="col-12"> |
|
27 |
- <label for="yourUsername" class="form-label">E-mail</label> |
|
9 |
+ <form class="row g-3 needs-validation" :class="{ 'was-validated': formSubmitted }" novalidate @submit.prevent="handleLogin"> |
|
10 |
+ <div class="box"> |
|
11 |
+ <label for="yourUsername" class="form-label"><img :src="idIcon" alt="아이디 아이콘">아이디</label> |
|
28 | 12 |
<div class="input-group has-validation"> |
29 |
- <input v-model="email" type="text" name="username" class="form-control" id="yourUsername" |
|
30 |
- required> |
|
31 |
- <div class="invalid-feedback">회사 이메일을 입력해주세요.</div> |
|
13 |
+ <input v-model="id" type="text" name="username" class="form-control" id="yourUsername" placeholder="아이디를 입력하세요." required> |
|
32 | 14 |
</div> |
33 | 15 |
</div> |
34 | 16 |
|
35 |
- <div class="col-12"> |
|
36 |
- <label for="yourPassword" class="form-label">Password</label> |
|
37 |
- <input v-model="password" type="password" name="password" class="form-control" id="yourPassword" |
|
38 |
- required> |
|
39 |
- <div class="invalid-feedback">비밀번호를 입력해주세요.</div> |
|
17 |
+ <div class="box"> |
|
18 |
+ <label for="yourPassword" class="form-label"><img :src="passwordIcon" alt="비밀번호 아이콘">비밀번호</label> |
|
19 |
+ <input v-model="password" type="password" name="password" class="form-control" id="yourPassword" placeholder="비밀번호를 입력하세요." required> |
|
40 | 20 |
</div> |
41 | 21 |
|
42 |
- <div class="col-12"> |
|
43 |
- <div class="form-check"> |
|
44 |
- <input class="form-check-input" type="checkbox" name="remember" value="true" id="rememberMe"> |
|
45 |
- <label class="form-check-label" for="rememberMe">로그인 유지하기</label> |
|
46 |
- </div> |
|
47 |
- </div> |
|
48 |
- <div class="col-12"> |
|
49 |
- <button class="btn btn-primary w-100" type="submit">로그인</button> |
|
50 |
- </div> |
|
51 |
- <div class="col-12"> |
|
52 |
- <p class="small mb-0"> <router-link to="Join">회원가입</router-link></p> |
|
22 |
+ <div class="box"> |
|
23 |
+ <button class="btn" type="submit"><img :src="loginIcon" alt="로그인 아이콘">로그인</button> |
|
53 | 24 |
</div> |
54 | 25 |
</form> |
55 |
- |
|
56 | 26 |
</div> |
57 | 27 |
</div> |
58 |
- |
|
59 |
- |
|
60 |
- </div> |
|
61 |
- </div> |
|
62 |
- </div> |
|
63 |
- |
|
64 |
- </section> |
|
65 |
- |
|
66 | 28 |
</div> |
67 | 29 |
</template> |
68 | 30 |
|
... | ... | @@ -71,40 +33,31 @@ |
71 | 33 |
data() { |
72 | 34 |
return { |
73 | 35 |
// 임시 로그인 정보 |
74 |
- email: '', |
|
75 |
- password: '', |
|
36 |
+ idIcon: "/client/resources/img/id.png", |
|
37 |
+ passwordIcon: "/client/resources/img/password.png", |
|
38 |
+ loginIcon: "/client/resources/img/loginicon.png", |
|
76 | 39 |
formSubmitted: false, |
77 |
- logo: "/client/resources/img/logo_t.png", // 경로를 Vue 프로젝트 구조에 맞게 설정 |
|
40 |
+ logo: "/client/resources/img/logo.png", // 경로를 Vue 프로젝트 구조에 맞게 설정 |
|
41 |
+ id: "admin", // 임시 아이디 |
|
42 |
+ password: "1234", // 임시 비밀번호 |
|
78 | 43 |
}; |
79 | 44 |
}, |
80 | 45 |
methods: { |
81 | 46 |
handleLogin() { |
82 | 47 |
this.formSubmitted = true; |
83 |
- if (!this.email.includes('@')) { |
|
84 |
- alert('이메일은 @를 포함해야 합니다.'); |
|
85 |
- return; // Stop further processing if email is invalid |
|
86 |
- } |
|
87 |
- console.log('Email:', this.email); |
|
88 |
- console.log('Password:', this.password); |
|
89 |
- const user = JSON.parse(localStorage.getItem("UserInfo")); |
|
90 |
- if (user) { |
|
91 |
- console.log('Stored Email:', user.email); |
|
92 |
- console.log('Stored Password:', user.password); |
|
93 | 48 |
|
94 |
- // 이메일과 비밀번호 비교 |
|
95 |
- if (this.email === user.email && this.password === user.password) { |
|
96 |
- alert('로그인 성공!'); |
|
97 |
- this.$router.push("/"); // 로그인 성공 후 메인 페이지로 이동 |
|
98 |
- } else { |
|
99 |
- alert('로그인 실패: 이메일 또는 비밀번호가 잘못되었습니다.'); |
|
100 |
- } |
|
49 |
+ // Check if credentials are correct |
|
50 |
+ if (this.id === "admin" && this.password === "1234") { |
|
51 |
+ // Set logged in status in localStorage |
|
52 |
+ localStorage.setItem('isLoggedIn', 'true'); |
|
53 |
+ |
|
54 |
+ // Redirect to the main page after successful login |
|
55 |
+ this.$router.push('/'); |
|
101 | 56 |
} else { |
102 |
- console.log('등록되지 않은 회원입니다.'); |
|
57 |
+ // Handle incorrect login attempt |
|
58 |
+ console.log("아이디 또는 비밀번호가 틀렸습니다."); |
|
103 | 59 |
} |
104 |
- |
|
105 | 60 |
}, |
106 | 61 |
}, |
107 | 62 |
}; |
108 | 63 |
</script> |
109 |
- |
|
110 |
-<style scoped></style>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/pages/User/MyPage.vue
+++ client/views/pages/User/MyPage.vue
... | ... | @@ -1,5 +1,23 @@ |
1 | 1 |
<template> |
2 |
- <div class="container"> |
|
2 |
+ <div class="sidemenu"> |
|
3 |
+ <div class="myinfo profile"> |
|
4 |
+ <div class="name-box"> |
|
5 |
+ <div class="img-area"> |
|
6 |
+ <div class="img"> |
|
7 |
+ <img :src="previewImg || placeholder" alt="미리보기 이미지" /> |
|
8 |
+ <button class="close-btn" @click="removeImage">×</button> |
|
9 |
+ </div> |
|
10 |
+ <div class="info"> |
|
11 |
+ <div class="file"> |
|
12 |
+ <label for="fileUpload" class="file-label"><div><img :src="file" alt=""></div><p>업로드</p></label> |
|
13 |
+ <input id="fileUpload" type="file" @change="handleFileUpload" accept="image/*" /> |
|
14 |
+ </div> |
|
15 |
+ </div> |
|
16 |
+ </div> |
|
17 |
+ </div> |
|
18 |
+ </div> |
|
19 |
+ </div> |
|
20 |
+ <div class="content"> |
|
3 | 21 |
|
4 | 22 |
<div class="d-flex justify-content-center py-4"> |
5 | 23 |
<a href="index.html" class="logo d-flex align-items-center w-auto"> |
... | ... | @@ -8,65 +26,69 @@ |
8 | 26 |
</div><!-- End Logo --> |
9 | 27 |
|
10 | 28 |
<div class="card mb-3"> |
11 |
- |
|
29 |
+ <p class="require"><img :src="require" alt=""> 필수입력</p> |
|
12 | 30 |
<div class="card-body"> |
13 |
- |
|
14 |
- <div class=" pb-2"> |
|
15 |
- <h2 class="card-title text-center pb-0 fs-4">마이페이지</h2> |
|
16 |
- </div> |
|
17 | 31 |
|
18 | 32 |
<form class="row g-3 needs-validation " :class="{ 'was-validated': formSubmitted }" @submit.prevent="handleRegister" novalidate> |
19 | 33 |
<div class="col-12"> |
20 |
- <label for="yourName" class="form-label">이름</label> |
|
21 |
- <input v-model="name" type="text" name="name" class="form-control" id="yourName" required> |
|
22 |
- <div class="invalid-feedback">이름을 입력해주세요.</div> |
|
34 |
+ <label for="yourName" class="form-label">아이디</label> |
|
35 |
+ <input v-model="name" type="text" name="name" class="form-control" id="yourName" required readonly placeholder="admin"> |
|
23 | 36 |
</div> |
24 |
- <div class="col-12"> |
|
25 |
- <label for="yourdept" class="form-label">부서</label> |
|
26 |
- <select v-model="dept" id="inputState" class="form-select"> |
|
27 |
- <option value="" disabled selected>부서를 선택하세요</option> |
|
28 |
- <option v-for="(item, index) in DeptData" :key="index" :value="item.deptNM"> |
|
29 |
- {{ item.deptNM }} |
|
30 |
- </option> |
|
31 |
- </select> |
|
37 |
+ <div class="col-12 "> |
|
38 |
+ <div class="col-12 border-x"> |
|
39 |
+ <label for="youremail" class="form-label ">이름<p class="require"><img :src="require" alt=""></p></label> |
|
40 |
+ <input v-model="email" type="text" name="username" class="form-control" id="youremail" required > |
|
41 |
+ </div> |
|
42 |
+ |
|
43 |
+ <div class="col-12 border-x"> |
|
44 |
+ <label for="yourPassword" class="form-label">부서</label> |
|
45 |
+ <input v-model="password" type="password" name="password" class="form-control" id="yourPassword" required readonly placeholder="주식회사 테이큰 소프트"> |
|
46 |
+ </div> |
|
47 |
+ </div> |
|
48 |
+ <div class="col-12"> |
|
49 |
+ <div class="col-12 border-x"> |
|
50 |
+ <label for="youremail" class="form-label">직급</label> |
|
51 |
+ <input v-model="email" type="text" name="username" class="form-control" id="youremail" required readonly placeholder="과장"> |
|
52 |
+ </div> |
|
53 |
+ |
|
54 |
+ <div class="col-12 border-x"> |
|
55 |
+ <label for="yourPassword" class="form-label">직책</label> |
|
56 |
+ <input v-model="password" type="password" name="password" class="form-control" id="yourPassword" required readonly placeholder="팀장"> |
|
57 |
+ </div> |
|
58 |
+ </div> |
|
59 |
+ <div class="col-12"> |
|
60 |
+ <label for="yourName" class="form-label">연락처</label> |
|
61 |
+ <input v-model="name" type="text" name="name" class="form-control" id="yourName" required> |
|
32 | 62 |
</div> |
33 | 63 |
<div class="col-12"> |
34 |
- <label for="yourlevel" class="form-label">직급</label> |
|
35 |
- <select v-model="level" id="yourlevel" class="form-select"> |
|
36 |
- <option value="" disabled selected>직급을 선택하세요</option> |
|
37 |
- <option value="level0">계약직(인턴)</option> |
|
38 |
- <option value="level1">사원</option> |
|
39 |
- <option value="level2">주임</option> |
|
40 |
- <option value="level3">대리</option> |
|
41 |
- <option value="level4">과장</option> |
|
42 |
- </select> |
|
43 |
- </div> |
|
44 |
- <div class="col-12"> |
|
45 |
- <label for="youremail" class="form-label">Email</label> |
|
46 |
- <div class="input-group has-validation"> |
|
47 |
- <input v-model="email" type="text" name="username" class="form-control" id="youremail" required readonly> |
|
48 |
- <div class="invalid-feedback">회사 이메일을 입력해주세요.</div> |
|
49 |
- </div> |
|
50 |
- </div> |
|
51 |
- |
|
52 |
- <div class="col-12"> |
|
53 |
- <label for="yourPassword" class="form-label">비밀번호 변경</label> |
|
54 |
- <input v-model="password" type="password" name="password" class="form-control" id="yourPassword" required > |
|
55 |
- <div class="invalid-feedback">비밀번호를 입력해주세요.</div> |
|
56 |
- </div> |
|
57 |
- |
|
58 |
- <div class="col-12"> |
|
59 |
- <div class="form-check"> |
|
60 |
- <!-- <input v-model="acceptTerms" class="form-check-input" name="terms" type="checkbox" value="" id="acceptTerms" required> --> |
|
61 |
- <!-- <label class="form-check-label" for="acceptTerms">이용약관에 동의합니다.</label> --> |
|
62 |
- <div class="invalid-feedback">이용약관에 동의하셔야 합니다.</div> |
|
63 |
- </div> |
|
64 |
- </div> |
|
65 |
- <div class="col-12"> |
|
66 |
- <button class="btn btn-primary w-100" type="submit">수정</button> |
|
67 |
- </div> |
|
64 |
+ <label for="yourName" class="form-label">생년월일</label> |
|
65 |
+ <input v-model="name" type="text" name="name" class="form-control" id="yourName" required> |
|
66 |
+ </div> |
|
67 |
+ <div class="col-12"> |
|
68 |
+ <label for="yourName" class="form-label">입사일</label> |
|
69 |
+ <input v-model="name" type="text" name="name" class="form-control" id="yourName" required readonly placeholder="2025-01-01"> |
|
70 |
+ </div> |
|
71 |
+ <div class="col-12"> |
|
72 |
+ <label for="yourName" class="form-label">현재 비밀번호</label> |
|
73 |
+ <input v-model="name" type="text" name="name" class="form-control" id="yourName" required> |
|
74 |
+ </div> |
|
75 |
+ <div class="col-12"> |
|
76 |
+ <label for="yourName" class="form-label">새 비밀번호</label> |
|
77 |
+ <div class="box"> |
|
78 |
+ <input v-model="name" type="text" name="name" class="form-control" id="yourName" required> |
|
79 |
+ <div class="invalid-feedback">※ 비밀번호는 6~12자의 영문자와 숫자, 특수기호조합으로 작성해주세요.</div> |
|
80 |
+ </div> |
|
81 |
+ </div> |
|
82 |
+ <div class="col-12 border-x"> |
|
83 |
+ <label for="yourName" class="form-label">연락처</label> |
|
84 |
+ <input v-model="name" type="text" name="name" class="form-control" id="yourName" required> |
|
85 |
+ </div> |
|
86 |
+ |
|
68 | 87 |
</form> |
69 |
- |
|
88 |
+ <div class="buttons"> |
|
89 |
+ <button class="btn btn-red w-100" type="submit">회원탈퇴</button> |
|
90 |
+ <button class="btn secondary w-100" type="submit">수정</button> |
|
91 |
+ </div> |
|
70 | 92 |
</div> |
71 | 93 |
</div> |
72 | 94 |
</div> |
... | ... | @@ -77,6 +99,10 @@ |
77 | 99 |
export default { |
78 | 100 |
data() { |
79 | 101 |
return { |
102 |
+ previewImg: null, |
|
103 |
+ placeholder: "/client/resources/img/img1.png", |
|
104 |
+ require: "/client/resources/img/require.png", |
|
105 |
+ file: "/client/resources/img/file.png", |
|
80 | 106 |
name: '', |
81 | 107 |
email: '', |
82 | 108 |
password: '', |
... | ... | @@ -84,10 +110,27 @@ |
84 | 110 |
level: '', |
85 | 111 |
acceptTerms: false, |
86 | 112 |
formSubmitted: false, |
87 |
- logo: "/client/resources/img/logo_t.png", // 경로를 Vue 프로젝트 구조에 맞게 설정 |
|
88 | 113 |
}; |
89 | 114 |
}, |
90 | 115 |
methods: { |
116 |
+ handleFileUpload(event) { |
|
117 |
+ const file = event.target.files[0]; |
|
118 |
+ if (file && file.type.startsWith('image/')) { |
|
119 |
+ const reader = new FileReader(); |
|
120 |
+ reader.onload = (e) => { |
|
121 |
+ this.previewImg = e.target.result; // 파일 읽기 완료 후 미리보기 이미지 설정 |
|
122 |
+ }; |
|
123 |
+ reader.readAsDataURL(file); // 파일을 데이터 URL로 읽기 |
|
124 |
+ } else { |
|
125 |
+ alert('이미지 파일만 선택할 수 있습니다.'); |
|
126 |
+ } |
|
127 |
+ }, |
|
128 |
+ |
|
129 |
+ // 이미지 삭제 함수 |
|
130 |
+ removeImage() { |
|
131 |
+ this.previewImg = null; // 미리보기 이미지 삭제 |
|
132 |
+ this.$refs.fileUpload.value = null; // 파일 input 초기화 |
|
133 |
+ }, |
|
91 | 134 |
handleRegister() { |
92 | 135 |
this.formSubmitted = true; |
93 | 136 |
// 이메일과 비밀번호가 빈 값이 아니어야 한다 |
--- client/views/pages/main/Main.vue
+++ client/views/pages/main/Main.vue
... | ... | @@ -1,17 +1,167 @@ |
1 | 1 |
<template> |
2 |
- <div> <GoogleCalendar/></div> |
|
2 |
+ <div class="sidemenu"> |
|
3 |
+ <div class="myinfo"> |
|
4 |
+ <div class="name-box"> |
|
5 |
+ <div class="img-area"> |
|
6 |
+ <div><img :src="photoicon" alt=""> |
|
7 |
+ <p class="name">OOO과장</p> |
|
8 |
+ </div> |
|
9 |
+ <div class="info"> |
|
10 |
+ <p>솔루션 개발팀</p> |
|
11 |
+ <i class="fa-bars"></i> |
|
12 |
+ <p>팀장</p> |
|
13 |
+ </div> |
|
14 |
+ </div> |
|
15 |
+ </div> |
|
16 |
+ <ul class="info-box"> |
|
17 |
+ <li> |
|
18 |
+ <p>5</p> |
|
19 |
+ <div class="name yellow">신청</div> |
|
20 |
+ </li> |
|
21 |
+ <li> |
|
22 |
+ <p>5</p> |
|
23 |
+ <div class="name ">신청</div> |
|
24 |
+ </li> |
|
25 |
+ <li> |
|
26 |
+ <p>5</p> |
|
27 |
+ <div class="name blue">신청</div> |
|
28 |
+ </li> |
|
29 |
+ <li> |
|
30 |
+ <p>5</p> |
|
31 |
+ <div class="name red">신청</div> |
|
32 |
+ </li> |
|
33 |
+ </ul> |
|
34 |
+ </div> |
|
35 |
+ <div class="boxs"> |
|
36 |
+ <div class="box"> |
|
37 |
+ <div class="img-area"><img :src="img1" alt=""></div> |
|
38 |
+ <div class="sm-box"> |
|
39 |
+ <h3>연차 잔여일수</h3> |
|
40 |
+ <p>11.5</p> |
|
41 |
+ </div> |
|
42 |
+ </div> |
|
43 |
+ <div class="bd-box"> |
|
44 |
+ <div>전체 <span>15</span></div> |
|
45 |
+ <i class="fa-bars"></i> |
|
46 |
+ <div>사용 <span>3.5</span></div> |
|
47 |
+ </div> |
|
48 |
+ </div> |
|
49 |
+ <div class="boxs"> |
|
50 |
+ <h4><img :src="icon1" alt="">01월 근태현황</h4> |
|
51 |
+ <div class="color-boxs"> |
|
52 |
+ <div class="box red"> |
|
53 |
+ <h3>지각</h3> |
|
54 |
+ 3 |
|
55 |
+ </div> |
|
56 |
+ <div class="box"> |
|
57 |
+ <h3>조기퇴근</h3> |
|
58 |
+ 3 |
|
59 |
+ </div> |
|
60 |
+ <div class="box green"> |
|
61 |
+ <h3>결근</h3> |
|
62 |
+ 3 |
|
63 |
+ </div> |
|
64 |
+ </div> |
|
65 |
+ </div> |
|
66 |
+ </div> |
|
67 |
+ <div class="content"> |
|
68 |
+ <div class="top-content"> |
|
69 |
+ <div class="boxs"> |
|
70 |
+ |
|
71 |
+ <div class="title"> |
|
72 |
+ <h2>근무체크 <span>출근전</span></h2> |
|
73 |
+ <div class="sub"><img :src="dateicon" alt="">{{ today }}</div> |
|
74 |
+ </div> |
|
75 |
+ <div class="blue-box"> |
|
76 |
+ <div class="box"> |
|
77 |
+ <p class="time">{{ time }}</p> |
|
78 |
+ </div> |
|
79 |
+ <div class="buttons"> |
|
80 |
+ <button><img :src="startbtn" alt=""></button> |
|
81 |
+ <i class="fa-bars"></i> |
|
82 |
+ <button><img :src="stopbtn" alt=""></button> |
|
83 |
+ </div> |
|
84 |
+ </div> |
|
85 |
+ |
|
86 |
+ </div> |
|
87 |
+ <div class="boxs"> |
|
88 |
+ <div class="title"> |
|
89 |
+ <h2>오늘의 주요 </h2> |
|
90 |
+ <div class="sub">더보기<img :src="moreicon" alt=""></div> |
|
91 |
+ </div> |
|
92 |
+ <div class="board tbl-wrap"> |
|
93 |
+ <table class="tbl data"> |
|
94 |
+ <tbody> |
|
95 |
+ <tr v-for="(item, index) in requestList" :key="index" > |
|
96 |
+ <td :class="getCategoryClass(item.category)"> |
|
97 |
+ <p>{{ item.category }}</p> |
|
98 |
+ </td> |
|
99 |
+ <td>{{ item.name }}</td> |
|
100 |
+ <td>{{ item.startdate }} ~ {{ item.enddate }}</td> |
|
101 |
+ <td class="status" :class="getStatusClass(item.status)"> |
|
102 |
+ <p>{{ item.status }}</p> |
|
103 |
+ </td> |
|
104 |
+ </tr> |
|
105 |
+ </tbody> |
|
106 |
+ </table> |
|
107 |
+ </div> |
|
108 |
+ </div> |
|
109 |
+ </div> |
|
110 |
+ <GoogleCalendar /> |
|
111 |
+ </div> |
|
3 | 112 |
</template> |
4 | 113 |
|
5 | 114 |
<script> |
6 | 115 |
import GoogleCalendar from "../../component/GoogleCalendar.vue" |
7 | 116 |
|
8 | 117 |
export default { |
9 |
- data () { |
|
118 |
+ data() { |
|
10 | 119 |
return { |
120 |
+ photoicon: "/client/resources/img/photo_icon.png", |
|
121 |
+ img1: "/client/resources/img/img.png", |
|
122 |
+ icon1: "/client/resources/img/icon.png", |
|
123 |
+ dateicon: "/client/resources/img/date.png", |
|
124 |
+ startbtn: "/client/resources/img/start.png", |
|
125 |
+ stopbtn: "/client/resources/img/stop.png", |
|
126 |
+ moreicon: "/client/resources/img/more.png", |
|
127 |
+ today: new Date().toLocaleDateString('ko-KR', { |
|
128 |
+ year: 'numeric', |
|
129 |
+ month: '2-digit', |
|
130 |
+ day: '2-digit', |
|
131 |
+ weekday: 'short', |
|
132 |
+ }), |
|
133 |
+ time: this.getCurrentTime(), |
|
134 |
+ requestList: [ |
|
135 |
+ { category: '용역', name: '프로젝트1', startdate: '2025-04-30', enddate: '2025-04-30', status: '진행중' }, |
|
136 |
+ { category: '내부', name: '프로젝트2', startdate: '2025-05-01', enddate: '2025-04-30', status: '진행전' }, |
|
137 |
+ { category: '국가과제', name: '프로젝트2', startdate: '2025-05-01', enddate: '2025-04-30', status: '완료' } |
|
138 |
+ ] |
|
11 | 139 |
} |
12 | 140 |
}, |
13 | 141 |
methods: { |
14 |
- |
|
142 |
+ getCurrentTime() { |
|
143 |
+ const now = new Date(); |
|
144 |
+ const hours = String(now.getHours()).padStart(2, '0'); |
|
145 |
+ const minutes = String(now.getMinutes()).padStart(2, '0'); |
|
146 |
+ const seconds = String(now.getSeconds()).padStart(2, '0'); |
|
147 |
+ return `${hours}:${minutes}:${seconds}`; |
|
148 |
+ }, |
|
149 |
+ getCategoryClass(category) { |
|
150 |
+ switch (category) { |
|
151 |
+ case '용역': return 'category-service'; |
|
152 |
+ case '내부': return 'category-internal'; |
|
153 |
+ case '국가과제': return 'category-government'; |
|
154 |
+ default: return ''; |
|
155 |
+ } |
|
156 |
+ }, |
|
157 |
+ getStatusClass(status) { |
|
158 |
+ switch (status) { |
|
159 |
+ case '진행중': return 'status-in-progress'; |
|
160 |
+ case '진행전': return 'status-not-started'; |
|
161 |
+ case '완료': return 'status-complete'; |
|
162 |
+ default: return ''; |
|
163 |
+ } |
|
164 |
+ } |
|
15 | 165 |
}, |
16 | 166 |
watch: { |
17 | 167 |
|
... | ... | @@ -24,6 +174,9 @@ |
24 | 174 |
}, |
25 | 175 |
mounted() { |
26 | 176 |
console.log('main mounted'); |
177 |
+ setInterval(() => { |
|
178 |
+ this.time = this.getCurrentTime(); |
|
179 |
+ }, 1000); |
|
27 | 180 |
} |
28 | 181 |
} |
29 | 182 |
</script>(파일 끝에 줄바꿈 문자 없음) |
--- package-lock.json
+++ package-lock.json
... | ... | @@ -1,5 +1,5 @@ |
1 | 1 |
{ |
2 |
- "name": "vue_setting_scss-master", |
|
2 |
+ "name": "calendar", |
|
3 | 3 |
"lockfileVersion": 3, |
4 | 4 |
"requires": true, |
5 | 5 |
"packages": { |
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?