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:
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user