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:
songtianlun 2025-01-23 14:10:13 +08:00
parent b3089856c2
commit 3a6d247451
18 changed files with 215 additions and 0 deletions

View File

@ -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"

View File

@ -494,6 +494,7 @@ DEPENDENCIES
jbuilder
jsbundling-rails
kamal
kaminari (~> 1.2)
pg (~> 1.5)
propshaft
puma (>= 5.0)

View 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

View File

@ -0,0 +1,2 @@
module ArtsHelper
end

View File

@ -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

View 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>

View File

@ -58,3 +58,11 @@
</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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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 -%>

View 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>

View File

@ -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>

View 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

View File

@ -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

View File

@ -0,0 +1,7 @@
require "test_helper"
class ArtsControllerTest < ActionDispatch::IntegrationTest
# test "the truth" do
# assert true
# end
end