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.
This commit is contained in:
CalciumIon
2024-12-31 15:28:25 +08:00
parent 014fb7edab
commit 4f196a62e1
5 changed files with 147 additions and 75 deletions

View File

@@ -1,19 +1,24 @@
package controller package controller
import ( import (
"github.com/gin-gonic/gin"
"net/http" "net/http"
"one-api/common" "one-api/common"
"one-api/model" "one-api/model"
"strconv" "strconv"
"github.com/gin-gonic/gin"
) )
func GetAllRedemptions(c *gin.Context) { func GetAllRedemptions(c *gin.Context) {
p, _ := strconv.Atoi(c.Query("p")) p, _ := strconv.Atoi(c.Query("p"))
pageSize, _ := strconv.Atoi(c.Query("page_size"))
if p < 0 { if p < 0 {
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 { if err != nil {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": false, "success": false,
@@ -24,14 +29,27 @@ func GetAllRedemptions(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": true, "success": true,
"message": "", "message": "",
"data": redemptions, "data": gin.H{
"items": redemptions,
"total": total,
"page": p,
"page_size": pageSize,
},
}) })
return return
} }
func SearchRedemptions(c *gin.Context) { func SearchRedemptions(c *gin.Context) {
keyword := c.Query("keyword") 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 { if err != nil {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": false, "success": false,
@@ -42,7 +60,12 @@ func SearchRedemptions(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": true, "success": true,
"message": "", "message": "",
"data": redemptions, "data": gin.H{
"items": redemptions,
"total": total,
"page": p,
"page_size": pageSize,
},
}) })
return return
} }

View File

@@ -3,8 +3,10 @@ package model
import ( import (
"errors" "errors"
"fmt" "fmt"
"gorm.io/gorm"
"one-api/common" "one-api/common"
"strconv"
"gorm.io/gorm"
) )
type Redemption struct { type Redemption struct {
@@ -21,16 +23,80 @@ type Redemption struct {
DeletedAt gorm.DeletedAt `gorm:"index"` DeletedAt gorm.DeletedAt `gorm:"index"`
} }
func GetAllRedemptions(startIdx int, num int) ([]*Redemption, error) { func GetAllRedemptions(startIdx int, num int) (redemptions []*Redemption, total int64, err error) {
var redemptions []*Redemption // 开始事务
var err error tx := DB.Begin()
err = DB.Order("id desc").Limit(num).Offset(startIdx).Find(&redemptions).Error if tx.Error != nil {
return redemptions, err 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) { func SearchRedemptions(keyword string, startIdx int, num int) (redemptions []*Redemption, total int64, err error) {
err = DB.Where("id = ? or name LIKE ?", keyword, keyword+"%").Find(&redemptions).Error tx := DB.Begin()
return redemptions, err 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) { func GetRedemptionById(id int) (*Redemption, error) {

View File

@@ -829,7 +829,7 @@ const LogsTable = () => {
t('第 {{start}} - {{end}} 条,共 {{total}} 条', { t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
start: page.currentStart, start: page.currentStart,
end: page.currentEnd, end: page.currentEnd,
total: users.length total: logs.length
}), }),
currentPage: activePage, currentPage: activePage,
pageSize: pageSize, pageSize: pageSize,

View File

@@ -178,6 +178,7 @@ const RedemptionsTable = () => {
const [searching, setSearching] = useState(false); const [searching, setSearching] = useState(false);
const [tokenCount, setTokenCount] = useState(ITEMS_PER_PAGE); const [tokenCount, setTokenCount] = useState(ITEMS_PER_PAGE);
const [selectedKeys, setSelectedKeys] = useState([]); const [selectedKeys, setSelectedKeys] = useState([]);
const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE);
const [editingRedemption, setEditingRedemption] = useState({ const [editingRedemption, setEditingRedemption] = useState({
id: undefined, id: undefined,
}); });
@@ -187,40 +188,20 @@ const RedemptionsTable = () => {
setShowEdit(false); setShowEdit(false);
}; };
// const setCount = (data) => {
// if (data.length >= (activePage) * ITEMS_PER_PAGE) {
// setTokenCount(data.length + 1);
// } else {
// setTokenCount(data.length);
// }
// }
const setRedemptionFormat = (redeptions) => { const setRedemptionFormat = (redeptions) => {
// for (let i = 0; i < redeptions.length; i++) {
// redeptions[i].key = '' + redeptions[i].id;
// }
// data.key = '' + data.id
setRedemptions(redeptions); setRedemptions(redeptions);
if (redeptions.length >= activePage * ITEMS_PER_PAGE) {
setTokenCount(redeptions.length + 1);
} else {
setTokenCount(redeptions.length);
}
}; };
const loadRedemptions = async (startIdx) => { const loadRedemptions = async (startIdx, pageSize) => {
const res = await API.get(`/api/redemption/?p=${startIdx}`); const res = await API.get(`/api/redemption/?p=${startIdx}&page_size=${pageSize}`);
const { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {
if (startIdx === 0) { const newPageData = data.items;
setRedemptionFormat(data); setActivePage(data.page);
} else { setTokenCount(data.total);
let newRedemptions = redemptions; setRedemptionFormat(newPageData);
newRedemptions.push(...data);
setRedemptionFormat(newRedemptions);
}
} else { } else {
showError(message); showError(message);
} }
setLoading(false); setLoading(false);
}; };
@@ -248,16 +229,15 @@ const RedemptionsTable = () => {
const onPaginationChange = (e, { activePage }) => { const onPaginationChange = (e, { activePage }) => {
(async () => { (async () => {
if (activePage === Math.ceil(redemptions.length / ITEMS_PER_PAGE) + 1) { if (activePage === Math.ceil(redemptions.length / pageSize) + 1) {
// In this case we have to load more data and then append them. await loadRedemptions(activePage - 1, pageSize);
await loadRedemptions(activePage - 1);
} }
setActivePage(activePage); setActivePage(activePage);
})(); })();
}; };
useEffect(() => { useEffect(() => {
loadRedemptions(0) loadRedemptions(0, pageSize)
.then() .then()
.catch((reason) => { .catch((reason) => {
showError(reason); showError(reason);
@@ -265,7 +245,7 @@ const RedemptionsTable = () => {
}, []); }, []);
const refresh = async () => { const refresh = async () => {
await loadRedemptions(activePage - 1); await loadRedemptions(activePage - 1, pageSize);
}; };
const manageRedemption = async (id, action, record) => { const manageRedemption = async (id, action, record) => {
@@ -300,23 +280,21 @@ const RedemptionsTable = () => {
} }
}; };
const searchRedemptions = async () => { const searchRedemptions = async (keyword, page, pageSize) => {
if (searchKeyword === '') { if (searchKeyword === '') {
// if keyword is blank, load files instead. await loadRedemptions(page, pageSize);
await loadRedemptions(0); return;
setActivePage(1);
return;
} }
setSearching(true); setSearching(true);
const res = await API.get( const res = await API.get(`/api/redemption/search?keyword=${keyword}&p=${page}&page_size=${pageSize}`);
`/api/redemption/search?keyword=${searchKeyword}`,
);
const { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {
setRedemptions(data); const newPageData = data.items;
setActivePage(1); setActivePage(data.page);
setTokenCount(data.total);
setRedemptionFormat(newPageData);
} else { } else {
showError(message); showError(message);
} }
setSearching(false); setSearching(false);
}; };
@@ -341,16 +319,14 @@ const RedemptionsTable = () => {
const handlePageChange = (page) => { const handlePageChange = (page) => {
setActivePage(page); setActivePage(page);
if (page === Math.ceil(redemptions.length / ITEMS_PER_PAGE) + 1) { if (searchKeyword === '') {
// In this case we have to load more data and then append them. loadRedemptions(page, pageSize).then();
loadRedemptions(page - 1).then((r) => {}); } else {
searchRedemptions(searchKeyword, page, pageSize).then();
} }
}; };
let pageData = redemptions.slice( let pageData = redemptions;
(activePage - 1) * ITEMS_PER_PAGE,
activePage * ITEMS_PER_PAGE,
);
const rowSelection = { const rowSelection = {
onSelect: (record, selected) => {}, onSelect: (record, selected) => {},
onSelectAll: (selected, selectedRows) => {}, onSelectAll: (selected, selectedRows) => {},
@@ -379,7 +355,9 @@ const RedemptionsTable = () => {
visiable={showEdit} visiable={showEdit}
handleClose={closeEdit} handleClose={closeEdit}
></EditRedemption> ></EditRedemption>
<Form onSubmit={searchRedemptions}> <Form onSubmit={()=> {
searchRedemptions(searchKeyword, activePage, pageSize).then();
}}>
<Form.Input <Form.Input
label={t('搜索关键字')} label={t('搜索关键字')}
field='keyword' field='keyword'
@@ -431,20 +409,25 @@ const RedemptionsTable = () => {
dataSource={pageData} dataSource={pageData}
pagination={{ pagination={{
currentPage: activePage, currentPage: activePage,
pageSize: ITEMS_PER_PAGE, pageSize: pageSize,
total: tokenCount, total: tokenCount,
// showSizeChanger: true, showSizeChanger: true,
// pageSizeOptions: [10, 20, 50, 100], pageSizeOpts: [2, 20, 50, 100],
formatPageText: (page) => formatPageText: (page) =>
t('第 {{start}} - {{end}} 条,共 {{total}} 条', { t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
start: page.currentStart, start: page.currentStart,
end: page.currentEnd, end: page.currentEnd,
total: redemptions.length total: tokenCount
}), }),
// onPageSizeChange: (size) => { onPageSizeChange: (size) => {
// setPageSize(size); setPageSize(size);
// setActivePage(1); setActivePage(1);
// }, if (searchKeyword === '') {
loadRedemptions(1, size).then();
} else {
searchRedemptions(searchKeyword, 1, size).then();
}
},
onPageChange: handlePageChange, onPageChange: handlePageChange,
}} }}
loading={loading} loading={loading}

View File

@@ -487,7 +487,7 @@ const UsersTable = () => {
currentPage: activePage, currentPage: activePage,
pageSize: pageSize, pageSize: pageSize,
total: userCount, total: userCount,
pageSizeOpts: [2, 20, 50, 100], pageSizeOpts: [10, 20, 50, 100],
showSizeChanger: true, showSizeChanger: true,
onPageSizeChange: (size) => { onPageSizeChange: (size) => {
handlePageSizeChange(size); handlePageSizeChange(size);