

250519 김혜민 로그인실패시 확인 keyup 오류 수정
@41f04d841fcb286c547391d00d8d94987b206869
--- client/resources/css/component.css
+++ client/resources/css/component.css
... | ... | @@ -427,6 +427,8 @@ |
427 | 427 |
justify-content: center; |
428 | 428 |
align-items: center; |
429 | 429 |
z-index: 100000; |
430 |
+ /* 키보드 이벤트를 위한 포커스 관련 스타일 */ |
|
431 |
+ outline: none; |
|
430 | 432 |
} |
431 | 433 |
|
432 | 434 |
.modal-container { |
--- client/views/component/common/AlertModal.vue
+++ client/views/component/common/AlertModal.vue
... | ... | @@ -1,5 +1,5 @@ |
1 | 1 |
<template> |
2 |
- <div v-show="isModalOpen" class="modal-wrapper"> |
|
2 |
+ <div v-show="isModalOpen" class="modal-wrapper" tabindex="-1" ref="modalContainer"> |
|
3 | 3 |
<div class="modal-container small-modal"> |
4 | 4 |
<div class="modal-title text-ct"> |
5 | 5 |
<h2>{{ modalTitle }}</h2> |
... | ... | @@ -17,13 +17,14 @@ |
17 | 17 |
</div> |
18 | 18 |
</div> |
19 | 19 |
<div class="modal-end flex justify-center" style="flex-wrap: nowrap;"> |
20 |
- <button class="blue-btn large-btn" @click="onConfirm" @keyup.enter="onConfirm" autofocus v-if="isModalOpen">확인</button> |
|
20 |
+ <button class="blue-btn large-btn" @click="onConfirm" ref="confirmBtn" @keydown="handleKeydown" v-if="isModalOpen">확인</button> |
|
21 | 21 |
<button class="gray-btn large-btn" @click="onCancel" v-show="modalType === 'confirm'">취소</button> |
22 | 22 |
<button class="gray-btn large-btn" @click="onRadioCancel" v-show="modalType === 'radio'">취소</button> |
23 | 23 |
</div> |
24 | 24 |
</div> |
25 | 25 |
</div> |
26 | 26 |
</template> |
27 |
+ |
|
27 | 28 |
<script> |
28 | 29 |
export default { |
29 | 30 |
props: { |
... | ... | @@ -46,20 +47,107 @@ |
46 | 47 |
type: "move", |
47 | 48 |
checkBox: false |
48 | 49 |
}, |
49 |
- currentPromise: null |
|
50 |
+ currentPromise: null, |
|
51 |
+ keyboardEventHandler: null, |
|
52 |
+ lastActiveElement: null |
|
50 | 53 |
} |
51 | 54 |
}, |
52 | 55 |
methods: { |
56 |
+ // 키보드 이벤트 처리기 |
|
57 |
+ handleKeydown(event) { |
|
58 |
+ // 모달 안에서 엔터키가 눌렸을 때 |
|
59 |
+ if (this.isModalOpen && event.key === 'Enter') { |
|
60 |
+ event.preventDefault(); |
|
61 |
+ event.stopPropagation(); // 다른 요소로 이벤트 전파 방지 |
|
62 |
+ this.onConfirm(); // 확인 버튼 클릭 효과 |
|
63 |
+ return false; |
|
64 |
+ } |
|
65 |
+ }, |
|
66 |
+ |
|
67 |
+ // 모달 열릴 때 전역 이벤트 핸들러 추가 |
|
68 |
+ addGlobalKeyboardHandler() { |
|
69 |
+ if (!this.keyboardEventHandler) { |
|
70 |
+ this.keyboardEventHandler = (event) => { |
|
71 |
+ // 모달이 열려있고 엔터키를 누를 때만 처리 |
|
72 |
+ if (this.isModalOpen && (event.key === 'Enter')) { |
|
73 |
+ event.preventDefault(); |
|
74 |
+ event.stopPropagation(); |
|
75 |
+ |
|
76 |
+ if (event.key === 'Enter') { |
|
77 |
+ this.onConfirm(); |
|
78 |
+ } |
|
79 |
+ |
|
80 |
+ return false; |
|
81 |
+ } |
|
82 |
+ }; |
|
83 |
+ |
|
84 |
+ // 캡처 단계에서 이벤트를 잡아서 처리 |
|
85 |
+ document.addEventListener('keydown', this.keyboardEventHandler, true); |
|
86 |
+ } |
|
87 |
+ }, |
|
88 |
+ |
|
89 |
+ // 모달 닫힐 때 전역 이벤트 핸들러 제거 |
|
90 |
+ removeGlobalKeyboardHandler() { |
|
91 |
+ if (this.keyboardEventHandler) { |
|
92 |
+ document.removeEventListener('keydown', this.keyboardEventHandler, true); |
|
93 |
+ this.keyboardEventHandler = null; |
|
94 |
+ } |
|
95 |
+ }, |
|
96 |
+ |
|
97 |
+ // 현재 활성 요소 저장 |
|
98 |
+ saveActiveElement() { |
|
99 |
+ this.lastActiveElement = document.activeElement; |
|
100 |
+ }, |
|
101 |
+ |
|
102 |
+ // 이전 활성 요소로 포커스 복원 |
|
103 |
+ restoreActiveElement() { |
|
104 |
+ if (this.lastActiveElement && this.lastActiveElement.focus) { |
|
105 |
+ setTimeout(() => { |
|
106 |
+ try { |
|
107 |
+ this.lastActiveElement.focus(); |
|
108 |
+ } catch (e) { |
|
109 |
+ console.error('포커스 복원 중 오류:', e); |
|
110 |
+ } |
|
111 |
+ }, 100); |
|
112 |
+ } |
|
113 |
+ }, |
|
114 |
+ |
|
53 | 115 |
// 모달 닫기 |
54 | 116 |
closeModal() { |
55 | 117 |
this.isModalOpen = false; |
56 | 118 |
this.modalType = 'alert'; |
119 |
+ this.removeGlobalKeyboardHandler(); |
|
120 |
+ this.restoreActiveElement(); |
|
121 |
+ |
|
122 |
+ // 확실한 정리를 위한 추가 타이머 |
|
123 |
+ setTimeout(() => { |
|
124 |
+ if (this.keyboardEventHandler) { |
|
125 |
+ this.removeGlobalKeyboardHandler(); |
|
126 |
+ } |
|
127 |
+ }, 500); |
|
128 |
+ |
|
129 |
+ // 모달 닫힘 이벤트 발생 (선택적) |
|
130 |
+ this.$emit('modal-closed'); |
|
57 | 131 |
}, |
58 | 132 |
|
59 | 133 |
// 일반 모달 표시 |
60 | 134 |
showModal() { |
135 |
+ this.saveActiveElement(); |
|
61 | 136 |
this.modalType = 'alert'; |
62 | 137 |
this.isModalOpen = true; |
138 |
+ this.addGlobalKeyboardHandler(); |
|
139 |
+ |
|
140 |
+ // 모달에 포커스 설정 |
|
141 |
+ this.$nextTick(() => { |
|
142 |
+ if (this.$refs.confirmBtn) { |
|
143 |
+ this.$refs.confirmBtn.focus(); |
|
144 |
+ } else if (this.$refs.modalContainer) { |
|
145 |
+ this.$refs.modalContainer.focus(); |
|
146 |
+ } |
|
147 |
+ }); |
|
148 |
+ |
|
149 |
+ // 모달 열림 이벤트 발생 (선택적) |
|
150 |
+ this.$emit('modal-opened'); |
|
63 | 151 |
}, |
64 | 152 |
|
65 | 153 |
// 확인 버튼 클릭 핸들러 |
... | ... | @@ -112,17 +200,47 @@ |
112 | 200 |
|
113 | 201 |
// confirm 모달 표시 (확인/취소) |
114 | 202 |
async showConfirm() { |
203 |
+ this.saveActiveElement(); |
|
115 | 204 |
this.modalType = 'confirm'; |
116 | 205 |
this.isModalOpen = true; |
206 |
+ this.addGlobalKeyboardHandler(); |
|
207 |
+ |
|
208 |
+ // 모달에 포커스 설정 |
|
209 |
+ this.$nextTick(() => { |
|
210 |
+ if (this.$refs.confirmBtn) { |
|
211 |
+ this.$refs.confirmBtn.focus(); |
|
212 |
+ } else if (this.$refs.modalContainer) { |
|
213 |
+ this.$refs.modalContainer.focus(); |
|
214 |
+ } |
|
215 |
+ }); |
|
216 |
+ |
|
217 |
+ // 모달 열림 이벤트 발생 (선택적) |
|
218 |
+ this.$emit('modal-opened'); |
|
219 |
+ |
|
117 | 220 |
return this.createPromise(); |
118 | 221 |
}, |
119 | 222 |
|
120 | 223 |
// radio confirm 모달 표시 (라디오 옵션) |
121 | 224 |
async showRadioConfirm() { |
225 |
+ this.saveActiveElement(); |
|
122 | 226 |
this.modalType = 'radio'; |
123 | 227 |
this.isModalOpen = true; |
124 | 228 |
this.moveOrCopy.type = "move"; |
125 | 229 |
this.moveOrCopy.checkBox = false; |
230 |
+ this.addGlobalKeyboardHandler(); |
|
231 |
+ |
|
232 |
+ // 모달에 포커스 설정 |
|
233 |
+ this.$nextTick(() => { |
|
234 |
+ if (this.$refs.confirmBtn) { |
|
235 |
+ this.$refs.confirmBtn.focus(); |
|
236 |
+ } else if (this.$refs.modalContainer) { |
|
237 |
+ this.$refs.modalContainer.focus(); |
|
238 |
+ } |
|
239 |
+ }); |
|
240 |
+ |
|
241 |
+ // 모달 열림 이벤트 발생 (선택적) |
|
242 |
+ this.$emit('modal-opened'); |
|
243 |
+ |
|
126 | 244 |
return this.createPromise(); |
127 | 245 |
}, |
128 | 246 |
|
... | ... | @@ -145,6 +263,10 @@ |
145 | 263 |
message(newVal) { |
146 | 264 |
this.modalMessage = newVal; |
147 | 265 |
} |
266 |
+ }, |
|
267 |
+ // 컴포넌트가 제거될 때 이벤트 리스너 정리 |
|
268 |
+ beforeDestroy() { |
|
269 |
+ this.removeGlobalKeyboardHandler(); |
|
148 | 270 |
} |
149 | 271 |
} |
150 | 272 |
</script>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/pages/AppRouter.js
+++ client/views/pages/AppRouter.js
... | ... | @@ -187,7 +187,11 @@ |
187 | 187 |
userAuthCheck(to, from, next); |
188 | 188 |
} else { |
189 | 189 |
// 로그인 상태가 아니거나 세션이 만료된 경우 |
190 |
+ const wasLoggedIn = store.state.loginUser !== null; |
|
190 | 191 |
store.commit("setLoginUser", null); // 로그인 정보 초기화 |
192 |
+ if (wasLoggedIn) { |
|
193 |
+ alert("로그인 세션이 만료되었습니다. 다시 로그인해주세요."); |
|
194 |
+ } |
|
191 | 195 |
next("/login.page"); |
192 | 196 |
} |
193 | 197 |
}) |
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?