// app/javascript/controllers/search_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = ["input", "clearButton", "spinner", "statusIcon"] connect() { this.pendingRequest = null } submit() { clearTimeout(this.timeout) this.timeout = setTimeout(() => { // 如果有待处理的请求,则中止它 if (this.pendingRequest) { this.pendingRequest.abort() } const form = this.element const searchInput = this.inputTarget const encodedValue = encodeURIComponent(searchInput.value) // 更新 URL const url = new URL(window.location) url.searchParams.set('query', encodedValue) window.history.pushState({}, '', url) // 显示加载状态 this.showLoadingState() // 发送请求 this.pendingRequest = new AbortController() fetch(form.action + '?' + new URLSearchParams(new FormData(form)), { headers: { 'Accept': 'text/vnd.turbo-stream.html', 'Turbo-Frame': 'cities_results' }, signal: this.pendingRequest.signal }) .then(response => response.text()) .then(html => { Turbo.renderStreamMessage(html) }) .catch(error => { if (error.name === 'AbortError') return console.error('Search error:', error) }) .finally(() => { this.hideLoadingState() this.pendingRequest = null }) }, 300) } showLoadingState() { if (this.hasClearButtonTarget) { this.clearButtonTarget.classList.add('hidden') } if (this.hasSpinnerTarget) { this.spinnerTarget.classList.remove('hidden') } } hideLoadingState() { if (this.hasClearButtonTarget) { this.clearButtonTarget.classList.remove('hidden') } if (this.hasSpinnerTarget) { this.spinnerTarget.classList.add('hidden') } } }