From 4f196a62e1226d3643fe36a1b5b77607fdd709c1 Mon Sep 17 00:00:00 2001 From: CalciumIon <1808837298@qq.com> Date: Tue, 31 Dec 2024 15:28:25 +0800 Subject: [PATCH] feat: implement pagination and total count for redemptions API #386 - Updated GetAllRedemptions and SearchRedemptions functions to return total count along with paginated results. - Modified API endpoints to accept page size as a parameter, enhancing flexibility in data retrieval. - Adjusted RedemptionsTable component to support pagination and display total count, improving user experience. - Ensured consistent handling of pagination across related components, including LogsTable and UsersTable. --- controller/redemption.go | 33 ++++++-- model/redemption.go | 84 +++++++++++++++++--- web/src/components/LogsTable.js | 2 +- web/src/components/RedemptionsTable.js | 101 ++++++++++--------------- web/src/components/UsersTable.js | 2 +- 5 files changed, 147 insertions(+), 75 deletions(-) diff --git a/controller/redemption.go b/controller/redemption.go index 0f656be0..a7e09a8a 100644 --- a/controller/redemption.go +++ b/controller/redemption.go @@ -1,19 +1,24 @@ package controller import ( - "github.com/gin-gonic/gin" "net/http" "one-api/common" "one-api/model" "strconv" + + "github.com/gin-gonic/gin" ) func GetAllRedemptions(c *gin.Context) { p, _ := strconv.Atoi(c.Query("p")) + pageSize, _ := strconv.Atoi(c.Query("page_size")) if p < 0 { p = 0 } - redemptions, err := model.GetAllRedemptions(p*common.ItemsPerPage, common.ItemsPerPage) + if pageSize < 1 { + pageSize = common.ItemsPerPage + } + redemptions, total, err := model.GetAllRedemptions((p-1)*pageSize, pageSize) if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, @@ -24,14 +29,27 @@ func GetAllRedemptions(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "success": true, "message": "", - "data": redemptions, + "data": gin.H{ + "items": redemptions, + "total": total, + "page": p, + "page_size": pageSize, + }, }) return } func SearchRedemptions(c *gin.Context) { keyword := c.Query("keyword") - redemptions, err := model.SearchRedemptions(keyword) + p, _ := strconv.Atoi(c.Query("p")) + pageSize, _ := strconv.Atoi(c.Query("page_size")) + if p < 0 { + p = 0 + } + if pageSize < 1 { + pageSize = common.ItemsPerPage + } + redemptions, total, err := model.SearchRedemptions(keyword, (p-1)*pageSize, pageSize) if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, @@ -42,7 +60,12 @@ func SearchRedemptions(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "success": true, "message": "", - "data": redemptions, + "data": gin.H{ + "items": redemptions, + "total": total, + "page": p, + "page_size": pageSize, + }, }) return } diff --git a/model/redemption.go b/model/redemption.go index 0906915a..89c4ac8c 100644 --- a/model/redemption.go +++ b/model/redemption.go @@ -3,8 +3,10 @@ package model import ( "errors" "fmt" - "gorm.io/gorm" "one-api/common" + "strconv" + + "gorm.io/gorm" ) type Redemption struct { @@ -21,16 +23,80 @@ type Redemption struct { DeletedAt gorm.DeletedAt `gorm:"index"` } -func GetAllRedemptions(startIdx int, num int) ([]*Redemption, error) { - var redemptions []*Redemption - var err error - err = DB.Order("id desc").Limit(num).Offset(startIdx).Find(&redemptions).Error - return redemptions, err +func GetAllRedemptions(startIdx int, num int) (redemptions []*Redemption, total int64, err error) { + // 开始事务 + tx := DB.Begin() + if tx.Error != nil { + return nil, 0, tx.Error + } + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + // 获取总数 + err = tx.Model(&Redemption{}).Count(&total).Error + if err != nil { + tx.Rollback() + return nil, 0, err + } + + // 获取分页数据 + err = tx.Order("id desc").Limit(num).Offset(startIdx).Find(&redemptions).Error + if err != nil { + tx.Rollback() + return nil, 0, err + } + + // 提交事务 + if err = tx.Commit().Error; err != nil { + return nil, 0, err + } + + return redemptions, total, nil } -func SearchRedemptions(keyword string) (redemptions []*Redemption, err error) { - err = DB.Where("id = ? or name LIKE ?", keyword, keyword+"%").Find(&redemptions).Error - return redemptions, err +func SearchRedemptions(keyword string, startIdx int, num int) (redemptions []*Redemption, total int64, err error) { + tx := DB.Begin() + if tx.Error != nil { + return nil, 0, tx.Error + } + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + // Build query based on keyword type + query := tx.Model(&Redemption{}) + + // Only try to convert to ID if the string represents a valid integer + if id, err := strconv.Atoi(keyword); err == nil { + query = query.Where("id = ? OR name LIKE ?", id, keyword+"%") + } else { + query = query.Where("name LIKE ?", keyword+"%") + } + + // Get total count + err = query.Count(&total).Error + if err != nil { + tx.Rollback() + return nil, 0, err + } + + // Get paginated data + err = query.Order("id desc").Limit(num).Offset(startIdx).Find(&redemptions).Error + if err != nil { + tx.Rollback() + return nil, 0, err + } + + if err = tx.Commit().Error; err != nil { + return nil, 0, err + } + + return redemptions, total, nil } func GetRedemptionById(id int) (*Redemption, error) { diff --git a/web/src/components/LogsTable.js b/web/src/components/LogsTable.js index b467089d..452585a1 100644 --- a/web/src/components/LogsTable.js +++ b/web/src/components/LogsTable.js @@ -829,7 +829,7 @@ const LogsTable = () => { t('第 {{start}} - {{end}} 条,共 {{total}} 条', { start: page.currentStart, end: page.currentEnd, - total: users.length + total: logs.length }), currentPage: activePage, pageSize: pageSize, diff --git a/web/src/components/RedemptionsTable.js b/web/src/components/RedemptionsTable.js index 01dcd70a..560a822e 100644 --- a/web/src/components/RedemptionsTable.js +++ b/web/src/components/RedemptionsTable.js @@ -178,6 +178,7 @@ const RedemptionsTable = () => { const [searching, setSearching] = useState(false); const [tokenCount, setTokenCount] = useState(ITEMS_PER_PAGE); const [selectedKeys, setSelectedKeys] = useState([]); + const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE); const [editingRedemption, setEditingRedemption] = useState({ id: undefined, }); @@ -187,40 +188,20 @@ const RedemptionsTable = () => { setShowEdit(false); }; - // const setCount = (data) => { - // if (data.length >= (activePage) * ITEMS_PER_PAGE) { - // setTokenCount(data.length + 1); - // } else { - // setTokenCount(data.length); - // } - // } - const setRedemptionFormat = (redeptions) => { - // for (let i = 0; i < redeptions.length; i++) { - // redeptions[i].key = '' + redeptions[i].id; - // } - // data.key = '' + data.id setRedemptions(redeptions); - if (redeptions.length >= activePage * ITEMS_PER_PAGE) { - setTokenCount(redeptions.length + 1); - } else { - setTokenCount(redeptions.length); - } }; - const loadRedemptions = async (startIdx) => { - const res = await API.get(`/api/redemption/?p=${startIdx}`); + const loadRedemptions = async (startIdx, pageSize) => { + const res = await API.get(`/api/redemption/?p=${startIdx}&page_size=${pageSize}`); const { success, message, data } = res.data; if (success) { - if (startIdx === 0) { - setRedemptionFormat(data); - } else { - let newRedemptions = redemptions; - newRedemptions.push(...data); - setRedemptionFormat(newRedemptions); - } + const newPageData = data.items; + setActivePage(data.page); + setTokenCount(data.total); + setRedemptionFormat(newPageData); } else { - showError(message); + showError(message); } setLoading(false); }; @@ -248,16 +229,15 @@ const RedemptionsTable = () => { const onPaginationChange = (e, { activePage }) => { (async () => { - if (activePage === Math.ceil(redemptions.length / ITEMS_PER_PAGE) + 1) { - // In this case we have to load more data and then append them. - await loadRedemptions(activePage - 1); + if (activePage === Math.ceil(redemptions.length / pageSize) + 1) { + await loadRedemptions(activePage - 1, pageSize); } setActivePage(activePage); })(); }; useEffect(() => { - loadRedemptions(0) + loadRedemptions(0, pageSize) .then() .catch((reason) => { showError(reason); @@ -265,7 +245,7 @@ const RedemptionsTable = () => { }, []); const refresh = async () => { - await loadRedemptions(activePage - 1); + await loadRedemptions(activePage - 1, pageSize); }; const manageRedemption = async (id, action, record) => { @@ -300,23 +280,21 @@ const RedemptionsTable = () => { } }; - const searchRedemptions = async () => { + const searchRedemptions = async (keyword, page, pageSize) => { if (searchKeyword === '') { - // if keyword is blank, load files instead. - await loadRedemptions(0); - setActivePage(1); - return; + await loadRedemptions(page, pageSize); + return; } setSearching(true); - const res = await API.get( - `/api/redemption/search?keyword=${searchKeyword}`, - ); + const res = await API.get(`/api/redemption/search?keyword=${keyword}&p=${page}&page_size=${pageSize}`); const { success, message, data } = res.data; if (success) { - setRedemptions(data); - setActivePage(1); + const newPageData = data.items; + setActivePage(data.page); + setTokenCount(data.total); + setRedemptionFormat(newPageData); } else { - showError(message); + showError(message); } setSearching(false); }; @@ -341,16 +319,14 @@ const RedemptionsTable = () => { const handlePageChange = (page) => { setActivePage(page); - if (page === Math.ceil(redemptions.length / ITEMS_PER_PAGE) + 1) { - // In this case we have to load more data and then append them. - loadRedemptions(page - 1).then((r) => {}); + if (searchKeyword === '') { + loadRedemptions(page, pageSize).then(); + } else { + searchRedemptions(searchKeyword, page, pageSize).then(); } }; - let pageData = redemptions.slice( - (activePage - 1) * ITEMS_PER_PAGE, - activePage * ITEMS_PER_PAGE, - ); + let pageData = redemptions; const rowSelection = { onSelect: (record, selected) => {}, onSelectAll: (selected, selectedRows) => {}, @@ -379,7 +355,9 @@ const RedemptionsTable = () => { visiable={showEdit} handleClose={closeEdit} > -