feat: add city search functionality
- Implement search_by_name scope in City model - Add SearchController for handling search input - Include _search_city partial in cities index view - Update cities_controller to filter cities based on search query This commit introduces a new feature that allows users to search for cities by name using an input field. The search is implemented as a scope in the City model, and it is integrated into the existing CitiesController. A dedicated SearchController manages the input submission with a debouncing mechanism for better performance. The search field is rendered in the cities index view, enhancing user interactivity and experience.
This commit is contained in:
parent
51d626a67f
commit
799f3222a9
@ -6,6 +6,10 @@ class CitiesController < ApplicationController
|
||||
@regions = Region.includes(:countries).order(:name)
|
||||
@cities = City.includes(:country, country: :region).order(:name)
|
||||
|
||||
if params[:query].present?
|
||||
@cities = @cities.search_by_name(params[:query])
|
||||
end
|
||||
|
||||
if params[:region]
|
||||
@current_region = Region.friendly.find(params[:region])
|
||||
@cities = @cities.by_region(@current_region.id) if @current_region
|
||||
|
@ -6,7 +6,9 @@ import { application } from "./application"
|
||||
import HelloController from "./hello_controller"
|
||||
import PhotoSwipeLightBoxController from "./photo_swipe_lightbox_controller"
|
||||
import FlashMessageController from "./flash_controller"
|
||||
import SearchController from "./search_controller"
|
||||
|
||||
application.register("hello", HelloController)
|
||||
application.register("photo-swipe-lightbox", PhotoSwipeLightBoxController)
|
||||
application.register("flash", FlashMessageController)
|
||||
application.register("search", SearchController)
|
||||
|
13
app/javascript/controllers/search_controller.js
Normal file
13
app/javascript/controllers/search_controller.js
Normal file
@ -0,0 +1,13 @@
|
||||
// app/javascript/controllers/search_controller.js
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["input"]
|
||||
|
||||
submit() {
|
||||
clearTimeout(this.timeout)
|
||||
this.timeout = setTimeout(() => {
|
||||
this.element.requestSubmit()
|
||||
}, 300)
|
||||
}
|
||||
}
|
@ -97,6 +97,15 @@ class City < ApplicationRecord
|
||||
.order("COUNT(ahoy_events.id) DESC, cities.name ASC").limit(limit)
|
||||
end
|
||||
}
|
||||
scope :search_by_name, ->(query) {
|
||||
return all if query.blank?
|
||||
|
||||
decoded_query = URI.decode_www_form_component(query).downcase
|
||||
|
||||
where(
|
||||
"LOWER(cities.name) LIKE :query", query: "%#{decoded_query}%"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def to_s
|
||||
|
30
app/views/cities/_search_city.html.erb
Normal file
30
app/views/cities/_search_city.html.erb
Normal file
@ -0,0 +1,30 @@
|
||||
<div class="mt-8 max-w-2xl mx-auto">
|
||||
<%= form_with url: cities_path, method: :get, class: "relative", data: { controller: "search" } do |f| %>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none">
|
||||
<svg class="w-5 h-5 text-base-content/50" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<%= f.text_field :query,
|
||||
value: params[:query] ? URI.decode_www_form_component(params[:query]) : nil,
|
||||
class: "w-full pl-12 pr-4 py-3 rounded-full bg-base-200/50 backdrop-blur-sm border border-base-300 focus:outline-none focus:ring-2 focus:ring-primary/50",
|
||||
placeholder: "Search cities...",
|
||||
autocomplete: "off",
|
||||
data: {
|
||||
action: "input->search#submit",
|
||||
search_target: "input"
|
||||
} %>
|
||||
<% if params[:query].present? %>
|
||||
<%= link_to cities_path, class: "absolute inset-y-0 right-0 flex items-center pr-4" do %>
|
||||
<svg class="w-5 h-5 text-base-content/50 hover:text-base-content" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= f.hidden_field :region, value: params[:region] if params[:region] %>
|
||||
<%= f.hidden_field :country, value: params[:country] if params[:country] %>
|
||||
<% end %>
|
||||
</div>
|
@ -22,6 +22,8 @@
|
||||
Discover AI-generated weather art from cities around the world
|
||||
</p>
|
||||
|
||||
<%= render 'cities/search_city' %>
|
||||
|
||||
<!-- 特色图片信息 -->
|
||||
<% if featured_art %>
|
||||
<div class="inline-block mt-6 px-4 py-2 bg-base-100/80 backdrop-blur-sm rounded-full text-sm">
|
||||
@ -99,7 +101,7 @@
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="text-sm text-base-content/70">
|
||||
<div class="text-sm text-base-content/70 hidden">
|
||||
<%= @cities.count %> <%= 'city'.pluralize(@cities.count) %>
|
||||
<% if @current_country %>
|
||||
in <%= @current_country.name %>
|
||||
@ -111,11 +113,29 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="container mx-auto px-4 pb-16">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
<%= render partial: 'city', collection: @cities %>
|
||||
</div>
|
||||
|
||||
<% if @cities.empty? %>
|
||||
<div class="text-center py-16">
|
||||
<div class="text-base-content/50">
|
||||
<svg class="w-16 h-16 mx-auto mb-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z" />
|
||||
</svg>
|
||||
<h3 class="text-xl font-semibold mb-2">No cities found</h3>
|
||||
<p class="text-base-content/70">
|
||||
Try adjusting your search or filters to find what you're looking for.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
<!-- 现有的城市网格代码 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
<%= render partial: 'city', collection: @cities %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= render 'shared/pagination',
|
||||
collection: @cities,
|
||||
|
Loading…
Reference in New Issue
Block a user