update
This commit is contained in:
153
internal/pkg/pagination/pagination.go
Normal file
153
internal/pkg/pagination/pagination.go
Normal file
@ -0,0 +1,153 @@
|
||||
// Package pagination 提供后端分页的通用封装:查询参数解析、分页元数据计算、SQL 偏移量,
|
||||
// 以及后台 HTML 模板可复用的翻页链接构造。
|
||||
//
|
||||
// 目标:消除各 handler 里重复的「page/size 解析 + 上/下一页计算 + 越界钳制」样板,
|
||||
// 同时保证对外协议(JSON 字段名、HTML 展示文案)完全不变。
|
||||
package pagination
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Params 是一页请求的规范化参数(page 已钳制到 >=1;size 已钳制到 [1, maxSize])。
|
||||
type Params struct {
|
||||
Page int
|
||||
PageSize int
|
||||
}
|
||||
|
||||
// Parse 从 gin 查询串读取并规范化分页参数。
|
||||
//
|
||||
// pageKey/sizeKey 为查询参数名;sizeKey 为空表示不读取条数(固定用 defSize,不校验上限)。
|
||||
// 解析规则(与既有后台列表页、history/favorite 接口保持一致):
|
||||
// - page 缺失或非数字 → defPage;<1 → defPage
|
||||
// - size 缺失或非数字 → defSize;<1 → defSize;>maxSize → maxSize
|
||||
func Parse(c *gin.Context, pageKey, sizeKey string, defPage, defSize, maxSize int) Params {
|
||||
page := atoiDefault(c.Query(pageKey), defPage)
|
||||
if page < 1 {
|
||||
page = defPage
|
||||
}
|
||||
size := defSize
|
||||
if sizeKey != "" {
|
||||
size = atoiDefault(c.Query(sizeKey), defSize)
|
||||
if size < 1 {
|
||||
size = defSize
|
||||
}
|
||||
if maxSize > 0 && size > maxSize {
|
||||
size = maxSize
|
||||
}
|
||||
}
|
||||
return Params{Page: page, PageSize: size}
|
||||
}
|
||||
|
||||
// Offset 返回 GORM 翻页用的 SQL 偏移量:(page-1)*size。
|
||||
func (p Params) Offset() int { return (p.Page - 1) * p.PageSize }
|
||||
|
||||
// Meta 是分页元数据(总页数、上/下一页、是否可翻)。
|
||||
//
|
||||
// NewMeta 不会修改传入的 Page(与既有 brands/runways/street/reviews 行为一致:越界页码
|
||||
// 照常透传给数据库,返回空列表,仅 HasNext 据此计算)。需要越界钳制显示(ingest-jobs)
|
||||
// 的调用方拿到 Meta 后调用 Clamp。
|
||||
type Meta struct {
|
||||
Page int
|
||||
PageSize int
|
||||
Total int64
|
||||
TotalPages int
|
||||
PrevPage int
|
||||
NextPage int
|
||||
HasPrev bool
|
||||
HasNext bool
|
||||
}
|
||||
|
||||
// NewMeta 计算分页元数据。
|
||||
//
|
||||
// HasNext 等价于「page < 总页数」,与原有 `int64(page*size) < total` 在 total>0 时一致;
|
||||
// total==0 时总页数记为 1,HasNext 恒为 false(与原逻辑相同)。
|
||||
func (p Params) NewMeta(total int64) Meta {
|
||||
totalPages := LastPage(total, p.PageSize)
|
||||
m := Meta{
|
||||
Page: p.Page,
|
||||
PageSize: p.PageSize,
|
||||
Total: total,
|
||||
TotalPages: totalPages,
|
||||
}
|
||||
if p.Page > 1 {
|
||||
m.PrevPage = p.Page - 1
|
||||
m.HasPrev = true
|
||||
}
|
||||
if p.Page < totalPages {
|
||||
m.NextPage = p.Page + 1
|
||||
m.HasNext = true
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Clamp 把页码钳制到 [1, TotalPages],并重新计算 Prev/Next。
|
||||
// 用于「越界页码应展示最后一页」的场景(ingest-jobs),不改变数据库查询用的原始 offset。
|
||||
func (m *Meta) Clamp() {
|
||||
if m.TotalPages > 0 && m.Page > m.TotalPages {
|
||||
m.Page = m.TotalPages
|
||||
m.HasPrev = m.Page > 1
|
||||
m.HasNext = false
|
||||
m.PrevPage = 0
|
||||
m.NextPage = 0
|
||||
if m.HasPrev {
|
||||
m.PrevPage = m.Page - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LastPage 计算总页数;无数据时返回 1(与原项目一致,避免前端出现 0 页)。
|
||||
func LastPage(total int64, size int) int {
|
||||
if size <= 0 {
|
||||
return 1
|
||||
}
|
||||
last := int(total) / size
|
||||
if int(total)%size > 0 {
|
||||
last++
|
||||
}
|
||||
if last == 0 {
|
||||
last = 1
|
||||
}
|
||||
return last
|
||||
}
|
||||
|
||||
// Pager 是后台 HTML 模板复用的翻页视图。
|
||||
//
|
||||
// 由 handler 用 PageLink/PageLinkWith 生成 PrevURL/NextURL 后塞进模板数据,
|
||||
// 模板只需写一行 {{template "pager" .Pager}}。
|
||||
type Pager struct {
|
||||
PrevURL string
|
||||
NextURL string
|
||||
HasPrev bool
|
||||
HasNext bool
|
||||
Label string // 页码信息文案(如「第 3 页 · 共 120 条」),可为空
|
||||
}
|
||||
|
||||
// PageLink 基于当前请求构造仅替换 page 的翻页链接,保留其余查询参数。
|
||||
// 用于 brands/runways/street/reviews 等(URL 路径本身已含 kind 段,无需额外拼接)。
|
||||
func PageLink(c *gin.Context, page int) string {
|
||||
q := c.Request.URL.Query()
|
||||
q.Set("page", strconv.Itoa(page))
|
||||
return c.Request.URL.Path + "?" + q.Encode()
|
||||
}
|
||||
|
||||
// PageLinkWith 除替换 page 外,额外固定一个参数(如 ingest-jobs 的 pageSize)。
|
||||
func PageLinkWith(c *gin.Context, page int, extraKey string, extraVal int) string {
|
||||
q := c.Request.URL.Query()
|
||||
q.Set("page", strconv.Itoa(page))
|
||||
q.Set(extraKey, strconv.Itoa(extraVal))
|
||||
return c.Request.URL.Path + "?" + q.Encode()
|
||||
}
|
||||
|
||||
func atoiDefault(s string, def int) int {
|
||||
if s == "" {
|
||||
return def
|
||||
}
|
||||
n, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return n
|
||||
}
|
||||
Reference in New Issue
Block a user