feat: add pagination to weather arts gallery
- Introduce ArtsController with index action - Create index view for displaying weather arts - Implement Kaminari for pagination functionality - Add necessary routes for accessing arts - Update Gemfile to include Kaminari gem - Create views for pagination controls This implementation enhances the Weather Arts Gallery, allowing users to view and navigate through a collection of AI-generated weather arts easily. Pagination controls have been added to improve usability.
This commit is contained in:
parent
b3089856c2
commit
3a6d247451
2
Gemfile
2
Gemfile
@ -45,6 +45,8 @@ gem "devise", "~> 4.9"
|
|||||||
gem "activeadmin", "~> 3.2"
|
gem "activeadmin", "~> 3.2"
|
||||||
gem "friendly_id", "~> 5.5"
|
gem "friendly_id", "~> 5.5"
|
||||||
|
|
||||||
|
gem 'kaminari', '~> 1.2'
|
||||||
|
|
||||||
# gem "whenever", "~> 1.0"
|
# gem "whenever", "~> 1.0"
|
||||||
gem "ruby-openai", "~> 7.3"
|
gem "ruby-openai", "~> 7.3"
|
||||||
gem "httparty", "~> 0.22.0"
|
gem "httparty", "~> 0.22.0"
|
||||||
|
@ -494,6 +494,7 @@ DEPENDENCIES
|
|||||||
jbuilder
|
jbuilder
|
||||||
jsbundling-rails
|
jsbundling-rails
|
||||||
kamal
|
kamal
|
||||||
|
kaminari (~> 1.2)
|
||||||
pg (~> 1.5)
|
pg (~> 1.5)
|
||||||
propshaft
|
propshaft
|
||||||
puma (>= 5.0)
|
puma (>= 5.0)
|
||||||
|
8
app/controllers/arts_controller.rb
Normal file
8
app/controllers/arts_controller.rb
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
class ArtsController < ApplicationController
|
||||||
|
def index
|
||||||
|
@weather_arts = WeatherArt.includes(:city, city: [ :country, { country: :region } ])
|
||||||
|
.order(created_at: :desc)
|
||||||
|
.page(params[:page])
|
||||||
|
.per(2)
|
||||||
|
end
|
||||||
|
end
|
2
app/helpers/arts_helper.rb
Normal file
2
app/helpers/arts_helper.rb
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
module ArtsHelper
|
||||||
|
end
|
@ -5,6 +5,8 @@ class City < ApplicationRecord
|
|||||||
|
|
||||||
has_many :weather_arts, dependent: :destroy
|
has_many :weather_arts, dependent: :destroy
|
||||||
|
|
||||||
|
delegate :region, to: :country
|
||||||
|
|
||||||
validates :name, presence: true
|
validates :name, presence: true
|
||||||
validates :latitude, presence: true
|
validates :latitude, presence: true
|
||||||
validates :longitude, presence: true
|
validates :longitude, presence: true
|
||||||
|
80
app/views/arts/index.html.erb
Normal file
80
app/views/arts/index.html.erb
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<!-- app/views/arts/index.html.erb -->
|
||||||
|
<div class="min-h-screen">
|
||||||
|
<!-- 页面标题 -->
|
||||||
|
<div class="bg-gradient-to-r from-primary/10 to-secondary/10 py-16">
|
||||||
|
<div class="container mx-auto px-4">
|
||||||
|
<h1 class="text-4xl md:text-5xl font-display font-bold text-center mb-4">
|
||||||
|
Weather Arts Gallery
|
||||||
|
</h1>
|
||||||
|
<p class="text-xl text-center text-base-content/70 max-w-2xl mx-auto">
|
||||||
|
Explore our collection of AI-generated weather art from cities around the world
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container mx-auto px-4 py-12">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||||
|
<% @weather_arts.each do |art| %>
|
||||||
|
<div class="card bg-base-100 shadow-xl hover:shadow-2xl transition-all duration-300 group">
|
||||||
|
<!-- 图片 -->
|
||||||
|
<figure class="relative aspect-square overflow-hidden">
|
||||||
|
<% if art.image.attached? %>
|
||||||
|
<%= image_tag art.image,
|
||||||
|
class: "w-full h-full object-cover transform group-hover:scale-105 transition-transform duration-500" %>
|
||||||
|
<!-- 渐变遮罩 -->
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
|
|
||||||
|
<!-- 悬停时显示的信息 -->
|
||||||
|
<div class="absolute inset-0 p-6 flex flex-col justify-end opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||||
|
<h3 class="text-white font-display text-xl mb-2">
|
||||||
|
<%= art.city.name %>
|
||||||
|
</h3>
|
||||||
|
<p class="text-white/80 text-sm">
|
||||||
|
<%= art.weather_date.strftime("%B %d, %Y") %>
|
||||||
|
</p>
|
||||||
|
<p class="text-white/90 mt-2">
|
||||||
|
<%= art.temperature %>°C, <%= art.description %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<!-- 快速信息 -->
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div>
|
||||||
|
<h3 class="font-display font-bold">
|
||||||
|
<%= art.city.name %>
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm text-base-content/70">
|
||||||
|
<%= art.city.country.name %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-right">
|
||||||
|
<div class="text-lg font-bold"><%= art.temperature %>°C</div>
|
||||||
|
<div class="text-sm text-base-content/70">
|
||||||
|
<%= art.weather_date.strftime("%b %d") %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= link_to city_weather_art_path(art.city, art),
|
||||||
|
class: "btn btn-ghost btn-sm w-full mt-3" do %>
|
||||||
|
View Details
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<div class="flex justify-center mt-12">
|
||||||
|
<div class="btn-group">
|
||||||
|
<%= link_to_prev_page @weather_arts, 'Previous',
|
||||||
|
class: "btn btn-outline #{'btn-disabled' unless @weather_arts.prev_page}" %>
|
||||||
|
<%= link_to_next_page @weather_arts, 'Next',
|
||||||
|
class: "btn btn-outline #{'btn-disabled' unless @weather_arts.next_page}" %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -58,3 +58,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-center mt-12">
|
||||||
|
<%= link_to arts_path, class: "btn btn-primary btn-lg gap-2" do %>
|
||||||
|
View All Weather Arts
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
|
||||||
|
</svg>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
11
app/views/kaminari/_first_page.html.erb
Normal file
11
app/views/kaminari/_first_page.html.erb
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<%# Link to the "First" page
|
||||||
|
- available local variables
|
||||||
|
url: url to the first page
|
||||||
|
current_page: a page object for the currently displayed page
|
||||||
|
total_pages: total number of pages
|
||||||
|
per_page: number of items to fetch per page
|
||||||
|
remote: data-remote
|
||||||
|
-%>
|
||||||
|
<span class="first">
|
||||||
|
<%= link_to_unless current_page.first?, t('views.pagination.first').html_safe, url, remote: remote %>
|
||||||
|
</span>
|
8
app/views/kaminari/_gap.html.erb
Normal file
8
app/views/kaminari/_gap.html.erb
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<%# Non-link tag that stands for skipped pages...
|
||||||
|
- available local variables
|
||||||
|
current_page: a page object for the currently displayed page
|
||||||
|
total_pages: total number of pages
|
||||||
|
per_page: number of items to fetch per page
|
||||||
|
remote: data-remote
|
||||||
|
-%>
|
||||||
|
<span class="page gap"><%= t('views.pagination.truncate').html_safe %></span>
|
11
app/views/kaminari/_last_page.html.erb
Normal file
11
app/views/kaminari/_last_page.html.erb
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<%# Link to the "Last" page
|
||||||
|
- available local variables
|
||||||
|
url: url to the last page
|
||||||
|
current_page: a page object for the currently displayed page
|
||||||
|
total_pages: total number of pages
|
||||||
|
per_page: number of items to fetch per page
|
||||||
|
remote: data-remote
|
||||||
|
-%>
|
||||||
|
<span class="last">
|
||||||
|
<%= link_to_unless current_page.last?, t('views.pagination.last').html_safe, url, remote: remote %>
|
||||||
|
</span>
|
11
app/views/kaminari/_next_page.html.erb
Normal file
11
app/views/kaminari/_next_page.html.erb
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<%# Link to the "Next" page
|
||||||
|
- available local variables
|
||||||
|
url: url to the next page
|
||||||
|
current_page: a page object for the currently displayed page
|
||||||
|
total_pages: total number of pages
|
||||||
|
per_page: number of items to fetch per page
|
||||||
|
remote: data-remote
|
||||||
|
-%>
|
||||||
|
<span class="next">
|
||||||
|
<%= link_to_unless current_page.last?, t('views.pagination.next').html_safe, url, rel: 'next', remote: remote %>
|
||||||
|
</span>
|
12
app/views/kaminari/_page.html.erb
Normal file
12
app/views/kaminari/_page.html.erb
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<%# Link showing page number
|
||||||
|
- available local variables
|
||||||
|
page: a page object for "this" page
|
||||||
|
url: url to this page
|
||||||
|
current_page: a page object for the currently displayed page
|
||||||
|
total_pages: total number of pages
|
||||||
|
per_page: number of items to fetch per page
|
||||||
|
remote: data-remote
|
||||||
|
-%>
|
||||||
|
<span class="page<%= ' current' if page.current? %>">
|
||||||
|
<%= link_to_unless page.current?, page, url, {remote: remote, rel: page.rel} %>
|
||||||
|
</span>
|
25
app/views/kaminari/_paginator.html.erb
Normal file
25
app/views/kaminari/_paginator.html.erb
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<%# The container tag
|
||||||
|
- available local variables
|
||||||
|
current_page: a page object for the currently displayed page
|
||||||
|
total_pages: total number of pages
|
||||||
|
per_page: number of items to fetch per page
|
||||||
|
remote: data-remote
|
||||||
|
paginator: the paginator that renders the pagination tags inside
|
||||||
|
-%>
|
||||||
|
<%= paginator.render do -%>
|
||||||
|
<nav class="pagination" role="navigation" aria-label="pager">
|
||||||
|
<%= first_page_tag unless current_page.first? %>
|
||||||
|
<%= prev_page_tag unless current_page.first? %>
|
||||||
|
<% each_page do |page| -%>
|
||||||
|
<% if page.display_tag? -%>
|
||||||
|
<%= page_tag page %>
|
||||||
|
<% elsif !page.was_truncated? -%>
|
||||||
|
<%= gap_tag %>
|
||||||
|
<% end -%>
|
||||||
|
<% end -%>
|
||||||
|
<% unless current_page.out_of_range? %>
|
||||||
|
<%= next_page_tag unless current_page.last? %>
|
||||||
|
<%= last_page_tag unless current_page.last? %>
|
||||||
|
<% end %>
|
||||||
|
</nav>
|
||||||
|
<% end -%>
|
11
app/views/kaminari/_prev_page.html.erb
Normal file
11
app/views/kaminari/_prev_page.html.erb
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<%# Link to the "Previous" page
|
||||||
|
- available local variables
|
||||||
|
url: url to the previous page
|
||||||
|
current_page: a page object for the currently displayed page
|
||||||
|
total_pages: total number of pages
|
||||||
|
per_page: number of items to fetch per page
|
||||||
|
remote: data-remote
|
||||||
|
-%>
|
||||||
|
<span class="prev">
|
||||||
|
<%= link_to_unless current_page.first?, t('views.pagination.previous').html_safe, url, rel: 'prev', remote: remote %>
|
||||||
|
</span>
|
@ -33,6 +33,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex-none">
|
<div class="flex-none">
|
||||||
<%= link_to "Cities", cities_path, class: "btn btn-ghost font-sans" %>
|
<%= link_to "Cities", cities_path, class: "btn btn-ghost font-sans" %>
|
||||||
|
<%= link_to "Arts", arts_path, class: "btn btn-ghost font-sans" %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
14
config/initializers/kaminari_config.rb
Normal file
14
config/initializers/kaminari_config.rb
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
Kaminari.configure do |config|
|
||||||
|
# config.default_per_page = 25
|
||||||
|
# config.max_per_page = nil
|
||||||
|
# config.window = 4
|
||||||
|
# config.outer_window = 0
|
||||||
|
# config.left = 0
|
||||||
|
# config.right = 0
|
||||||
|
# config.page_method_name = :page
|
||||||
|
# config.param_name = :page
|
||||||
|
# config.max_pages = nil
|
||||||
|
# config.params_on_first_page = false
|
||||||
|
end
|
@ -6,6 +6,7 @@ Rails.application.routes.draw do
|
|||||||
resources :cities, only: [ :index, :show ] do
|
resources :cities, only: [ :index, :show ] do
|
||||||
resources :weather_arts, path: "weather", only: [ :show ], param: :slug
|
resources :weather_arts, path: "weather", only: [ :show ], param: :slug
|
||||||
end
|
end
|
||||||
|
resources :arts, only: [:index]
|
||||||
|
|
||||||
# namespace :admin do
|
# namespace :admin do
|
||||||
# resources :cities
|
# resources :cities
|
||||||
|
7
test/controllers/arts_controller_test.rb
Normal file
7
test/controllers/arts_controller_test.rb
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class ArtsControllerTest < ActionDispatch::IntegrationTest
|
||||||
|
# test "the truth" do
|
||||||
|
# assert true
|
||||||
|
# end
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user