Pārlūkot izejas kodu

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.
CalciumIon 1 gadu atpakaļ
vecāks
revīzija
4f196a62e1

+ 28 - 5
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
 }

+ 75 - 9
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) {

+ 1 - 1
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,

+ 42 - 59
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}
       ></EditRedemption>
-      <Form onSubmit={searchRedemptions}>
+      <Form onSubmit={()=> {
+        searchRedemptions(searchKeyword, activePage, pageSize).then();
+      }}>
         <Form.Input
           label={t('搜索关键字')}
           field='keyword'
@@ -431,20 +409,25 @@ const RedemptionsTable = () => {
         dataSource={pageData}
         pagination={{
           currentPage: activePage,
-          pageSize: ITEMS_PER_PAGE,
+          pageSize: pageSize,
           total: tokenCount,
-          // showSizeChanger: true,
-          // pageSizeOptions: [10, 20, 50, 100],
+          showSizeChanger: true,
+          pageSizeOpts: [2, 20, 50, 100],
           formatPageText: (page) =>
             t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
               start: page.currentStart,
               end: page.currentEnd,
-              total: redemptions.length
+              total: tokenCount
             }),
-          // onPageSizeChange: (size) => {
-          //   setPageSize(size);
-          //   setActivePage(1);
-          // },
+          onPageSizeChange: (size) => {
+            setPageSize(size);
+            setActivePage(1);
+            if (searchKeyword === '') {
+              loadRedemptions(1, size).then();
+            } else {
+              searchRedemptions(searchKeyword, 1, size).then();
+            }
+          },
           onPageChange: handlePageChange,
         }}
         loading={loading}

+ 1 - 1
web/src/components/UsersTable.js

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