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 "friendly_id", "~> 5.5"
|
||||
|
||||
gem 'kaminari', '~> 1.2'
|
||||
|
||||
# gem "whenever", "~> 1.0"
|
||||
gem "ruby-openai", "~> 7.3"
|
||||
gem "httparty", "~> 0.22.0"
|
||||
|
@ -494,6 +494,7 @@ DEPENDENCIES
|
||||
jbuilder
|
||||
jsbundling-rails
|
||||
kamal
|
||||
kaminari (~> 1.2)
|
||||
pg (~> 1.5)
|
||||
propshaft
|
||||
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
|
||||
|
||||
delegate :region, to: :country
|
||||
|
||||
validates :name, presence: true
|
||||
validates :latitude, 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>
|
@ -57,4 +57,12 @@
|
||||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
</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 class="flex-none">
|
||||
<%= 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>
|
||||
|
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 :weather_arts, path: "weather", only: [ :show ], param: :slug
|
||||
end
|
||||
resources :arts, only: [:index]
|
||||
|
||||
# namespace :admin do
|
||||
# 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