diff --git a/src/views/Agent/AgentDetail.vue b/src/views/Agent/AgentDetail.vue index 98a6bd3..d80257b 100644 --- a/src/views/Agent/AgentDetail.vue +++ b/src/views/Agent/AgentDetail.vue @@ -289,6 +289,8 @@ export default { searchQuery: '', searchItems: [], searchLoading: false, + // 디바운스 타이머 보관 + searchDebounceTimer: null, }, unassignDialog: { @@ -316,6 +318,13 @@ export default { created() { this.fetchList(1) }, + beforeDestroy() { + // 검색 디바운스 타이머 정리 + if (this.assignDialog && this.assignDialog.searchDebounceTimer) { + clearTimeout(this.assignDialog.searchDebounceTimer) + this.assignDialog.searchDebounceTimer = null + } + }, methods: { notifyError(message) { // vuetify-dialog 플러그인을 통해 오류 노출 @@ -366,31 +375,61 @@ export default { }, closeAssignDialog() { this.assignDialog.visible = false + // 다이얼로그 닫힐 때 디바운스 및 로딩 상태 초기화 + if (this.assignDialog.searchDebounceTimer) { + clearTimeout(this.assignDialog.searchDebounceTimer) + this.assignDialog.searchDebounceTimer = null + } + this.assignDialog.searchLoading = false }, - async onSearchCreators(q) { + onSearchCreators(q) { const query = (q || '').trim() this.assignDialog.searchQuery = query + + // 기존 타이머가 있으면 취소 + if (this.assignDialog.searchDebounceTimer) { + clearTimeout(this.assignDialog.searchDebounceTimer) + this.assignDialog.searchDebounceTimer = null + } + if (!query) { + // 검색어 없으면 결과/로딩 초기화 this.assignDialog.searchItems = [] + this.assignDialog.searchLoading = false return } - this.assignDialog.searchLoading = true - try { - const res = await searchAdminAgentAssignableCreators(query) - if (res && res.status === 200 && res.data && res.data.success === true) { - const data = (res.data && res.data.data) || { totalCount: 0, items: [] } - this.assignDialog.searchItems = Array.isArray(data.items) ? data.items : [] - } else { - this.assignDialog.searchItems = [] - const msg = res && res.data && (res.data.message || res.data.error || res.data.msg) - this.notifyError(msg || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') + + // 디바운스: 300ms 이후 최신 검색어로 요청 + this.assignDialog.searchDebounceTimer = setTimeout(async () => { + // 요청 직전 로딩 시작 + this.assignDialog.searchLoading = true + const currentQuery = this.assignDialog.searchQuery + try { + const res = await searchAdminAgentAssignableCreators(currentQuery) + // 사용자가 그 사이에 검색어를 바꿨다면 이 응답은 무시 + if (this.assignDialog.searchQuery !== currentQuery) return + + if (res && res.status === 200 && res.data && res.data.success === true) { + const data = (res.data && res.data.data) || { totalCount: 0, items: [] } + this.assignDialog.searchItems = Array.isArray(data.items) ? data.items : [] + } else { + this.assignDialog.searchItems = [] + const msg = res && res.data && (res.data.message || res.data.error || res.data.msg) + this.notifyError(msg || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') + } + } catch (e) { + // 에러 시에도 최신 검색어와 일치할 때만 처리 + if (this.assignDialog.searchQuery === currentQuery) { + this.assignDialog.searchItems = [] + this.notifyError(this.getErrorMessage(e)) + } + } finally { + // 최신 검색어와 일치할 때만 로딩 해제 + if (this.assignDialog.searchQuery === currentQuery) { + this.assignDialog.searchLoading = false + } } - } catch (e) { - this.assignDialog.searchItems = [] - this.notifyError(this.getErrorMessage(e)) - } finally { - this.assignDialog.searchLoading = false - } + }, 300) }, async onAssign() { if (!this.canAssign) return