🚀 feat(pagination): unify backend-driven pagination & improve channel tag aggregation

SUMMARY
• Migrated Token, Task, Midjourney, Channel, Redemption tables to true server-side pagination.
• Added total / page / page_size metadata in API responses; switched all affected React tables to consume new structure.
• Implemented counting helpers:
  – model/token.go CountUserTokens
  – model/task.go TaskCountAllTasks / TaskCountAllUserTask
  – model/midjourney.go CountAllTasks / CountAllUserTask
  – model/channel.go CountAllChannels / CountAllTags
• Refactored controllers (token, task, midjourney, channel) for 1-based paging & aggregated returns.
• Redesigned `ChannelsTable.js`:
  – `loadChannels`, `syncPageData`, `enrichChannels` for tag-mode grouping without recursion.
  – Fixed runtime white-screen (maximum call-stack) by removing child duplication.
  – Pagination, search, tag-mode, idSort all hot-reload correctly.
• Removed unused `log` import in controller/midjourney.go.

BREAKING CHANGES
Front-end consumers must now expect data.items / total / page / page_size from list endpoints (`/api/channel`, `/api/task`, `/api/mj`, `/api/token`, etc.).
This commit is contained in:
Apple\Apple
2025-06-12 17:25:25 +08:00
parent 3123d4bb9b
commit dcefd7dfb4
12 changed files with 317 additions and 218 deletions

View File

@@ -43,22 +43,23 @@ type OpenAIModelsResponse struct {
func GetAllChannels(c *gin.Context) { func GetAllChannels(c *gin.Context) {
p, _ := strconv.Atoi(c.Query("p")) p, _ := strconv.Atoi(c.Query("p"))
pageSize, _ := strconv.Atoi(c.Query("page_size")) pageSize, _ := strconv.Atoi(c.Query("page_size"))
if p < 0 { if p < 1 {
p = 0 p = 1
} }
if pageSize < 0 { if pageSize < 1 {
pageSize = common.ItemsPerPage pageSize = common.ItemsPerPage
} }
channelData := make([]*model.Channel, 0) channelData := make([]*model.Channel, 0)
idSort, _ := strconv.ParseBool(c.Query("id_sort")) idSort, _ := strconv.ParseBool(c.Query("id_sort"))
enableTagMode, _ := strconv.ParseBool(c.Query("tag_mode")) enableTagMode, _ := strconv.ParseBool(c.Query("tag_mode"))
var total int64
if enableTagMode { if enableTagMode {
tags, err := model.GetPaginatedTags(p*pageSize, pageSize) // tag 分页:先分页 tag再取各 tag 下 channels
tags, err := model.GetPaginatedTags((p-1)*pageSize, pageSize)
if err != nil { if err != nil {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
"success": false,
"message": err.Error(),
})
return return
} }
for _, tag := range tags { for _, tag := range tags {
@@ -69,21 +70,27 @@ func GetAllChannels(c *gin.Context) {
} }
} }
} }
// 计算 tag 总数用于分页
total, _ = model.CountAllTags()
} else { } else {
channels, err := model.GetAllChannels(p*pageSize, pageSize, false, idSort) channels, err := model.GetAllChannels((p-1)*pageSize, pageSize, false, idSort)
if err != nil { if err != nil {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
"success": false,
"message": err.Error(),
})
return return
} }
channelData = channels channelData = channels
total, _ = model.CountAllChannels()
} }
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": true, "success": true,
"message": "", "message": "",
"data": channelData, "data": gin.H{
"items": channelData,
"total": total,
"page": p,
"page_size": pageSize,
},
}) })
return return
} }

View File

@@ -7,7 +7,6 @@ import (
"fmt" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"io" "io"
"log"
"net/http" "net/http"
"one-api/common" "one-api/common"
"one-api/dto" "one-api/dto"
@@ -215,8 +214,12 @@ func checkMjTaskNeedUpdate(oldTask *model.Midjourney, newTask dto.MidjourneyDto)
func GetAllMidjourney(c *gin.Context) { func GetAllMidjourney(c *gin.Context) {
p, _ := strconv.Atoi(c.Query("p")) p, _ := strconv.Atoi(c.Query("p"))
if p < 0 { if p < 1 {
p = 0 p = 1
}
pageSize, _ := strconv.Atoi(c.Query("page_size"))
if pageSize <= 0 {
pageSize = common.ItemsPerPage
} }
// 解析其他查询参数 // 解析其他查询参数
@@ -227,31 +230,38 @@ func GetAllMidjourney(c *gin.Context) {
EndTimestamp: c.Query("end_timestamp"), EndTimestamp: c.Query("end_timestamp"),
} }
logs := model.GetAllTasks(p*common.ItemsPerPage, common.ItemsPerPage, queryParams) items := model.GetAllTasks((p-1)*pageSize, pageSize, queryParams)
if logs == nil { total := model.CountAllTasks(queryParams)
logs = make([]*model.Midjourney, 0)
}
if setting.MjForwardUrlEnabled { if setting.MjForwardUrlEnabled {
for i, midjourney := range logs { for i, midjourney := range items {
midjourney.ImageUrl = setting.ServerAddress + "/mj/image/" + midjourney.MjId midjourney.ImageUrl = setting.ServerAddress + "/mj/image/" + midjourney.MjId
logs[i] = midjourney items[i] = midjourney
} }
} }
c.JSON(200, gin.H{ c.JSON(200, gin.H{
"success": true, "success": true,
"message": "", "message": "",
"data": logs, "data": gin.H{
"items": items,
"total": total,
"page": p,
"page_size": pageSize,
},
}) })
} }
func GetUserMidjourney(c *gin.Context) { func GetUserMidjourney(c *gin.Context) {
p, _ := strconv.Atoi(c.Query("p")) p, _ := strconv.Atoi(c.Query("p"))
if p < 0 { if p < 1 {
p = 0 p = 1
}
pageSize, _ := strconv.Atoi(c.Query("page_size"))
if pageSize <= 0 {
pageSize = common.ItemsPerPage
} }
userId := c.GetInt("id") userId := c.GetInt("id")
log.Printf("userId = %d \n", userId)
queryParams := model.TaskQueryParams{ queryParams := model.TaskQueryParams{
MjID: c.Query("mj_id"), MjID: c.Query("mj_id"),
@@ -259,19 +269,23 @@ func GetUserMidjourney(c *gin.Context) {
EndTimestamp: c.Query("end_timestamp"), EndTimestamp: c.Query("end_timestamp"),
} }
logs := model.GetAllUserTask(userId, p*common.ItemsPerPage, common.ItemsPerPage, queryParams) items := model.GetAllUserTask(userId, (p-1)*pageSize, pageSize, queryParams)
if logs == nil { total := model.CountAllUserTask(userId, queryParams)
logs = make([]*model.Midjourney, 0)
}
if setting.MjForwardUrlEnabled { if setting.MjForwardUrlEnabled {
for i, midjourney := range logs { for i, midjourney := range items {
midjourney.ImageUrl = setting.ServerAddress + "/mj/image/" + midjourney.MjId midjourney.ImageUrl = setting.ServerAddress + "/mj/image/" + midjourney.MjId
logs[i] = midjourney items[i] = midjourney
} }
} }
c.JSON(200, gin.H{ c.JSON(200, gin.H{
"success": true, "success": true,
"message": "", "message": "",
"data": logs, "data": gin.H{
"items": items,
"total": total,
"page": p,
"page_size": pageSize,
},
}) })
} }

View File

@@ -224,9 +224,14 @@ func checkTaskNeedUpdate(oldTask *model.Task, newTask dto.SunoDataResponse) bool
func GetAllTask(c *gin.Context) { func GetAllTask(c *gin.Context) {
p, _ := strconv.Atoi(c.Query("p")) p, _ := strconv.Atoi(c.Query("p"))
if p < 0 { if p < 1 {
p = 0 p = 1
} }
pageSize, _ := strconv.Atoi(c.Query("page_size"))
if pageSize <= 0 {
pageSize = common.ItemsPerPage
}
startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64) startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64) endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
// 解析其他查询参数 // 解析其他查询参数
@@ -237,24 +242,32 @@ func GetAllTask(c *gin.Context) {
Action: c.Query("action"), Action: c.Query("action"),
StartTimestamp: startTimestamp, StartTimestamp: startTimestamp,
EndTimestamp: endTimestamp, EndTimestamp: endTimestamp,
ChannelID: c.Query("channel_id"),
} }
logs := model.TaskGetAllTasks(p*common.ItemsPerPage, common.ItemsPerPage, queryParams) items := model.TaskGetAllTasks((p-1)*pageSize, pageSize, queryParams)
if logs == nil { total := model.TaskCountAllTasks(queryParams)
logs = make([]*model.Task, 0)
}
c.JSON(200, gin.H{ c.JSON(200, gin.H{
"success": true, "success": true,
"message": "", "message": "",
"data": logs, "data": gin.H{
"items": items,
"total": total,
"page": p,
"page_size": pageSize,
},
}) })
} }
func GetUserTask(c *gin.Context) { func GetUserTask(c *gin.Context) {
p, _ := strconv.Atoi(c.Query("p")) p, _ := strconv.Atoi(c.Query("p"))
if p < 0 { if p < 1 {
p = 0 p = 1
}
pageSize, _ := strconv.Atoi(c.Query("page_size"))
if pageSize <= 0 {
pageSize = common.ItemsPerPage
} }
userId := c.GetInt("id") userId := c.GetInt("id")
@@ -271,14 +284,17 @@ func GetUserTask(c *gin.Context) {
EndTimestamp: endTimestamp, EndTimestamp: endTimestamp,
} }
logs := model.TaskGetAllUserTask(userId, p*common.ItemsPerPage, common.ItemsPerPage, queryParams) items := model.TaskGetAllUserTask(userId, (p-1)*pageSize, pageSize, queryParams)
if logs == nil { total := model.TaskCountAllUserTask(userId, queryParams)
logs = make([]*model.Task, 0)
}
c.JSON(200, gin.H{ c.JSON(200, gin.H{
"success": true, "success": true,
"message": "", "message": "",
"data": logs, "data": gin.H{
"items": items,
"total": total,
"page": p,
"page_size": pageSize,
},
}) })
} }

View File

@@ -12,15 +12,15 @@ func GetAllTokens(c *gin.Context) {
userId := c.GetInt("id") userId := c.GetInt("id")
p, _ := strconv.Atoi(c.Query("p")) p, _ := strconv.Atoi(c.Query("p"))
size, _ := strconv.Atoi(c.Query("size")) size, _ := strconv.Atoi(c.Query("size"))
if p < 0 { if p < 1 {
p = 0 p = 1
} }
if size <= 0 { if size <= 0 {
size = common.ItemsPerPage size = common.ItemsPerPage
} else if size > 100 { } else if size > 100 {
size = 100 size = 100
} }
tokens, err := model.GetAllUserTokens(userId, p*size, size) tokens, err := model.GetAllUserTokens(userId, (p-1)*size, size)
if err != nil { if err != nil {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": false, "success": false,
@@ -28,10 +28,18 @@ func GetAllTokens(c *gin.Context) {
}) })
return return
} }
// Get total count for pagination
total, _ := model.CountUserTokens(userId)
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": true, "success": true,
"message": "", "message": "",
"data": tokens, "data": gin.H{
"items": tokens,
"total": total,
"page": p,
"page_size": size,
},
}) })
return return
} }

View File

@@ -583,3 +583,17 @@ func BatchSetChannelTag(ids []int, tag *string) error {
// 提交事务 // 提交事务
return tx.Commit().Error return tx.Commit().Error
} }
// CountAllChannels returns total channels in DB
func CountAllChannels() (int64, error) {
var total int64
err := DB.Model(&Channel{}).Count(&total).Error
return total, err
}
// CountAllTags returns number of non-empty distinct tags
func CountAllTags() (int64, error) {
var total int64
err := DB.Model(&Channel{}).Where("tag is not null AND tag != ''").Distinct("tag").Count(&total).Error
return total, err
}

View File

@@ -166,3 +166,40 @@ func MjBulkUpdateByTaskIds(taskIDs []int, params map[string]any) error {
Where("id in (?)", taskIDs). Where("id in (?)", taskIDs).
Updates(params).Error Updates(params).Error
} }
// CountAllTasks returns total midjourney tasks for admin query
func CountAllTasks(queryParams TaskQueryParams) int64 {
var total int64
query := DB.Model(&Midjourney{})
if queryParams.ChannelID != "" {
query = query.Where("channel_id = ?", queryParams.ChannelID)
}
if queryParams.MjID != "" {
query = query.Where("mj_id = ?", queryParams.MjID)
}
if queryParams.StartTimestamp != "" {
query = query.Where("submit_time >= ?", queryParams.StartTimestamp)
}
if queryParams.EndTimestamp != "" {
query = query.Where("submit_time <= ?", queryParams.EndTimestamp)
}
_ = query.Count(&total).Error
return total
}
// CountAllUserTask returns total midjourney tasks for user
func CountAllUserTask(userId int, queryParams TaskQueryParams) int64 {
var total int64
query := DB.Model(&Midjourney{}).Where("user_id = ?", userId)
if queryParams.MjID != "" {
query = query.Where("mj_id = ?", queryParams.MjID)
}
if queryParams.StartTimestamp != "" {
query = query.Where("submit_time >= ?", queryParams.StartTimestamp)
}
if queryParams.EndTimestamp != "" {
query = query.Where("submit_time <= ?", queryParams.EndTimestamp)
}
_ = query.Count(&total).Error
return total
}

View File

@@ -302,3 +302,64 @@ func SumUsedTaskQuota(queryParams SyncTaskQueryParams) (stat []TaskQuotaUsage, e
err = query.Select("mode, sum(quota) as count").Group("mode").Find(&stat).Error err = query.Select("mode, sum(quota) as count").Group("mode").Find(&stat).Error
return stat, err return stat, err
} }
// TaskCountAllTasks returns total tasks that match the given query params (admin usage)
func TaskCountAllTasks(queryParams SyncTaskQueryParams) int64 {
var total int64
query := DB.Model(&Task{})
if queryParams.ChannelID != "" {
query = query.Where("channel_id = ?", queryParams.ChannelID)
}
if queryParams.Platform != "" {
query = query.Where("platform = ?", queryParams.Platform)
}
if queryParams.UserID != "" {
query = query.Where("user_id = ?", queryParams.UserID)
}
if len(queryParams.UserIDs) != 0 {
query = query.Where("user_id in (?)", queryParams.UserIDs)
}
if queryParams.TaskID != "" {
query = query.Where("task_id = ?", queryParams.TaskID)
}
if queryParams.Action != "" {
query = query.Where("action = ?", queryParams.Action)
}
if queryParams.Status != "" {
query = query.Where("status = ?", queryParams.Status)
}
if queryParams.StartTimestamp != 0 {
query = query.Where("submit_time >= ?", queryParams.StartTimestamp)
}
if queryParams.EndTimestamp != 0 {
query = query.Where("submit_time <= ?", queryParams.EndTimestamp)
}
_ = query.Count(&total).Error
return total
}
// TaskCountAllUserTask returns total tasks for given user
func TaskCountAllUserTask(userId int, queryParams SyncTaskQueryParams) int64 {
var total int64
query := DB.Model(&Task{}).Where("user_id = ?", userId)
if queryParams.TaskID != "" {
query = query.Where("task_id = ?", queryParams.TaskID)
}
if queryParams.Action != "" {
query = query.Where("action = ?", queryParams.Action)
}
if queryParams.Status != "" {
query = query.Where("status = ?", queryParams.Status)
}
if queryParams.Platform != "" {
query = query.Where("platform = ?", queryParams.Platform)
}
if queryParams.StartTimestamp != 0 {
query = query.Where("submit_time >= ?", queryParams.StartTimestamp)
}
if queryParams.EndTimestamp != 0 {
query = query.Where("submit_time <= ?", queryParams.EndTimestamp)
}
_ = query.Count(&total).Error
return total
}

View File

@@ -320,3 +320,10 @@ func decreaseTokenQuota(id int, quota int) (err error) {
).Error ).Error
return err return err
} }
// CountUserTokens returns total number of tokens for the given user, used for pagination
func CountUserTokens(userId int) (int64, error) {
var total int64
err := DB.Model(&Token{}).Where("user_id = ?", userId).Count(&total).Error
return total, err
}

View File

@@ -865,32 +865,22 @@ const ChannelsTable = () => {
tagChannelDates.response_time = tagChannelDates.response_time / 2; tagChannelDates.response_time = tagChannelDates.response_time / 2;
} }
} }
// data.key = '' + data.id
setChannels(channelDates); setChannels(channelDates);
if (channelDates.length >= pageSize) {
setChannelCount(channelDates.length + pageSize);
} else {
setChannelCount(channelDates.length);
}
}; };
const loadChannels = async (startIdx, pageSize, idSort, enableTagMode) => { const loadChannels = async (page, pageSize, idSort, enableTagMode) => {
setLoading(true); setLoading(true);
const res = await API.get( const res = await API.get(
`/api/channel/?p=${startIdx}&page_size=${pageSize}&id_sort=${idSort}&tag_mode=${enableTagMode}`, `/api/channel/?p=${page}&page_size=${pageSize}&id_sort=${idSort}&tag_mode=${enableTagMode}`,
); );
if (res === undefined) { if (res === undefined) {
return; return;
} }
const { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {
if (startIdx === 0) { const { items, total } = data;
setChannelFormat(data, enableTagMode); setChannelFormat(items, enableTagMode);
} else { setChannelCount(total);
let newChannels = [...channels];
newChannels.splice(startIdx * pageSize, data.length, ...data);
setChannelFormat(newChannels, enableTagMode);
}
} else { } else {
showError(message); showError(message);
} }
@@ -903,7 +893,6 @@ const ChannelsTable = () => {
channelToCopy.created_time = null; channelToCopy.created_time = null;
channelToCopy.balance = 0; channelToCopy.balance = 0;
channelToCopy.used_quota = 0; channelToCopy.used_quota = 0;
// 删除可能导致类型不匹配的字段
delete channelToCopy.test_time; delete channelToCopy.test_time;
delete channelToCopy.response_time; delete channelToCopy.response_time;
if (!channelToCopy) { if (!channelToCopy) {
@@ -927,7 +916,7 @@ const ChannelsTable = () => {
const refresh = async () => { const refresh = async () => {
const { searchKeyword, searchGroup, searchModel } = getFormValues(); const { searchKeyword, searchGroup, searchModel } = getFormValues();
if (searchKeyword === '' && searchGroup === '' && searchModel === '') { if (searchKeyword === '' && searchGroup === '' && searchModel === '') {
await loadChannels(activePage - 1, pageSize, idSort, enableTagMode); await loadChannels(activePage, pageSize, idSort, enableTagMode);
} else { } else {
await searchChannels(enableTagMode); await searchChannels(enableTagMode);
} }
@@ -944,7 +933,7 @@ const ChannelsTable = () => {
setPageSize(localPageSize); setPageSize(localPageSize);
setEnableTagMode(localEnableTagMode); setEnableTagMode(localEnableTagMode);
setEnableBatchDelete(localEnableBatchDelete); setEnableBatchDelete(localEnableBatchDelete);
loadChannels(0, localPageSize, localIdSort, localEnableTagMode) loadChannels(1, localPageSize, localIdSort, localEnableTagMode)
.then() .then()
.catch((reason) => { .catch((reason) => {
showError(reason); showError(reason);
@@ -1052,7 +1041,6 @@ const ChannelsTable = () => {
try { try {
if (searchKeyword === '' && searchGroup === '' && searchModel === '') { if (searchKeyword === '' && searchGroup === '' && searchModel === '') {
await loadChannels(activePage - 1, pageSize, idSort, enableTagMode); await loadChannels(activePage - 1, pageSize, idSort, enableTagMode);
// setActivePage(1);
return; return;
} }
@@ -1191,24 +1179,18 @@ const ChannelsTable = () => {
} }
}; };
let pageData = channels.slice( let pageData = channels;
(activePage - 1) * pageSize,
activePage * pageSize,
);
const handlePageChange = (page) => { const handlePageChange = (page) => {
setActivePage(page); setActivePage(page);
if (page === Math.ceil(channels.length / pageSize) + 1) { loadChannels(page, pageSize, idSort, enableTagMode).then(() => { });
// In this case we have to load more data and then append them.
loadChannels(page - 1, pageSize, idSort, enableTagMode).then((r) => { });
}
}; };
const handlePageSizeChange = async (size) => { const handlePageSizeChange = async (size) => {
localStorage.setItem('page-size', size + ''); localStorage.setItem('page-size', size + '');
setPageSize(size); setPageSize(size);
setActivePage(1); setActivePage(1);
loadChannels(0, size, idSort, enableTagMode) loadChannels(1, size, idSort, enableTagMode)
.then() .then()
.catch((reason) => { .catch((reason) => {
showError(reason); showError(reason);
@@ -1218,8 +1200,6 @@ const ChannelsTable = () => {
const fetchGroups = async () => { const fetchGroups = async () => {
try { try {
let res = await API.get(`/api/group/`); let res = await API.get(`/api/group/`);
// add 'all' option
// res.data.data.unshift('all');
if (res === undefined) { if (res === undefined) {
return; return;
} }
@@ -1514,7 +1494,7 @@ const ChannelsTable = () => {
onChange={(v) => { onChange={(v) => {
localStorage.setItem('id-sort', v + ''); localStorage.setItem('id-sort', v + '');
setIdSort(v); setIdSort(v);
loadChannels(0, pageSize, v, enableTagMode); loadChannels(activePage, pageSize, v, enableTagMode);
}} }}
/> />
</div> </div>
@@ -1541,7 +1521,8 @@ const ChannelsTable = () => {
onChange={(v) => { onChange={(v) => {
localStorage.setItem('enable-tag-mode', v + ''); localStorage.setItem('enable-tag-mode', v + '');
setEnableTagMode(v); setEnableTagMode(v);
loadChannels(0, pageSize, idSort, v); setActivePage(1);
loadChannels(1, pageSize, idSort, v);
}} }}
/> />
</div> </div>
@@ -1703,7 +1684,7 @@ const ChannelsTable = () => {
formatPageText: (page) => t('第 {{start}} - {{end}} 条,共 {{total}} 条', { formatPageText: (page) => t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
start: page.currentStart, start: page.currentStart,
end: page.currentEnd, end: page.currentEnd,
total: channels.length, total: channelCount,
}), }),
onPageSizeChange: (size) => { onPageSizeChange: (size) => {
handlePageSizeChange(size); handlePageSizeChange(size);

View File

@@ -601,7 +601,7 @@ const LogsTable = () => {
const [logs, setLogs] = useState([]); const [logs, setLogs] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [activePage, setActivePage] = useState(1); const [activePage, setActivePage] = useState(1);
const [logCount, setLogCount] = useState(ITEMS_PER_PAGE); const [logCount, setLogCount] = useState(0);
const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE); const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE);
const [isModalOpenurl, setIsModalOpenurl] = useState(false); const [isModalOpenurl, setIsModalOpenurl] = useState(false);
const [showBanner, setShowBanner] = useState(false); const [showBanner, setShowBanner] = useState(false);
@@ -649,69 +649,53 @@ const LogsTable = () => {
}; };
}; };
const setLogsFormat = (logs) => { const enrichLogs = (items) => {
for (let i = 0; i < logs.length; i++) { return items.map((log) => ({
logs[i].timestamp2string = timestamp2string(logs[i].created_at); ...log,
logs[i].key = '' + logs[i].id; timestamp2string: timestamp2string(log.created_at),
} key: '' + log.id,
// data.key = '' + data.id }));
setLogs(logs);
setLogCount(logs.length + pageSize);
// console.log(logCount);
}; };
const loadLogs = async (startIdx, pageSize = ITEMS_PER_PAGE) => { const syncPageData = (payload) => {
setLoading(true); const items = enrichLogs(payload.items || []);
setLogs(items);
setLogCount(payload.total || 0);
setActivePage(payload.page || 1);
setPageSize(payload.page_size || pageSize);
};
let url = ''; const loadLogs = async (page = 1, size = pageSize) => {
setLoading(true);
const { channel_id, mj_id, start_timestamp, end_timestamp } = getFormValues(); const { channel_id, mj_id, start_timestamp, end_timestamp } = getFormValues();
let localStartTimestamp = Date.parse(start_timestamp); let localStartTimestamp = Date.parse(start_timestamp);
let localEndTimestamp = Date.parse(end_timestamp); let localEndTimestamp = Date.parse(end_timestamp);
if (isAdminUser) { const url = isAdminUser
url = `/api/mj/?p=${startIdx}&page_size=${pageSize}&channel_id=${channel_id}&mj_id=${mj_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`; ? `/api/mj/?p=${page}&page_size=${size}&channel_id=${channel_id}&mj_id=${mj_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`
} else { : `/api/mj/self/?p=${page}&page_size=${size}&mj_id=${mj_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`;
url = `/api/mj/self/?p=${startIdx}&page_size=${pageSize}&mj_id=${mj_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`;
}
const res = await API.get(url); const res = await API.get(url);
const { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {
if (startIdx === 0) { syncPageData(data);
setLogsFormat(data);
} else {
let newLogs = [...logs];
newLogs.splice(startIdx * pageSize, data.length, ...data);
setLogsFormat(newLogs);
}
} else { } else {
showError(message); showError(message);
} }
setLoading(false); setLoading(false);
}; };
const pageData = logs.slice( const pageData = logs;
(activePage - 1) * pageSize,
activePage * pageSize,
);
const handlePageChange = (page) => { const handlePageChange = (page) => {
setActivePage(page); loadLogs(page, pageSize).then();
if (page === Math.ceil(logs.length / pageSize) + 1) {
// In this case we have to load more data and then append them.
loadLogs(page - 1, pageSize).then((r) => { });
}
}; };
const handlePageSizeChange = async (size) => { const handlePageSizeChange = async (size) => {
localStorage.setItem('mj-page-size', size + ''); localStorage.setItem('mj-page-size', size + '');
setPageSize(size); await loadLogs(1, size);
setActivePage(1);
await loadLogs(0, size);
}; };
const refresh = async () => { const refresh = async () => {
// setLoading(true); await loadLogs(1, pageSize);
setActivePage(1);
await loadLogs(0, pageSize);
}; };
const copyText = async (text) => { const copyText = async (text) => {
@@ -726,7 +710,7 @@ const LogsTable = () => {
useEffect(() => { useEffect(() => {
const localPageSize = parseInt(localStorage.getItem('mj-page-size')) || ITEMS_PER_PAGE; const localPageSize = parseInt(localStorage.getItem('mj-page-size')) || ITEMS_PER_PAGE;
setPageSize(localPageSize); setPageSize(localPageSize);
loadLogs(0, localPageSize).then(); loadLogs(1, localPageSize).then();
}, []); }, []);
useEffect(() => { useEffect(() => {
@@ -936,7 +920,7 @@ const LogsTable = () => {
> >
<Table <Table
columns={getVisibleColumns()} columns={getVisibleColumns()}
dataSource={pageData} dataSource={logs}
rowKey='key' rowKey='key'
loading={loading} loading={loading}
scroll={{ x: 'max-content' }} scroll={{ x: 'max-content' }}
@@ -962,9 +946,7 @@ const LogsTable = () => {
total: logCount, total: logCount,
pageSizeOptions: [10, 20, 50, 100], pageSizeOptions: [10, 20, 50, 100],
showSizeChanger: true, showSizeChanger: true,
onPageSizeChange: (size) => { onPageSizeChange: handlePageSizeChange,
handlePageSizeChange(size);
},
onPageChange: handlePageChange, onPageChange: handlePageChange,
}} }}
/> />

View File

@@ -451,10 +451,16 @@ const LogsTable = () => {
return allColumns.filter((column) => visibleColumns[column.key]); return allColumns.filter((column) => visibleColumns[column.key]);
}; };
const [logs, setLogs] = useState([]);
const [loading, setLoading] = useState(true);
const [activePage, setActivePage] = useState(1); const [activePage, setActivePage] = useState(1);
const [logCount, setLogCount] = useState(ITEMS_PER_PAGE); const [logCount, setLogCount] = useState(0);
const [logs, setLogs] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const localPageSize = parseInt(localStorage.getItem('task-page-size')) || ITEMS_PER_PAGE;
setPageSize(localPageSize);
loadLogs(1, localPageSize).then();
}, []);
let now = new Date(); let now = new Date();
// 初始化start_timestamp为前一天 // 初始化start_timestamp为前一天
@@ -494,67 +500,53 @@ const LogsTable = () => {
}; };
}; };
const setLogsFormat = (logs) => { const enrichLogs = (items) => {
for (let i = 0; i < logs.length; i++) { return items.map((log) => ({
logs[i].timestamp2string = timestamp2string(logs[i].created_at); ...log,
logs[i].key = '' + logs[i].id; timestamp2string: timestamp2string(log.created_at),
} key: '' + log.id,
// data.key = '' + data.id }));
setLogs(logs);
setLogCount(logs.length + ITEMS_PER_PAGE);
// console.log(logCount);
}; };
const loadLogs = async (startIdx, pageSize = ITEMS_PER_PAGE) => { const syncPageData = (payload) => {
setLoading(true); const items = enrichLogs(payload.items || []);
setLogs(items);
setLogCount(payload.total || 0);
setActivePage(payload.page || 1);
setPageSize(payload.page_size || pageSize);
};
let url = ''; const loadLogs = async (page = 1, size = pageSize) => {
setLoading(true);
const { channel_id, task_id, start_timestamp, end_timestamp } = getFormValues(); const { channel_id, task_id, start_timestamp, end_timestamp } = getFormValues();
let localStartTimestamp = parseInt(Date.parse(start_timestamp) / 1000); let localStartTimestamp = parseInt(Date.parse(start_timestamp) / 1000);
let localEndTimestamp = parseInt(Date.parse(end_timestamp) / 1000); let localEndTimestamp = parseInt(Date.parse(end_timestamp) / 1000);
if (isAdminUser) { let url = isAdminUser
url = `/api/task/?p=${startIdx}&page_size=${pageSize}&channel_id=${channel_id}&task_id=${task_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`; ? `/api/task/?p=${page}&page_size=${size}&channel_id=${channel_id}&task_id=${task_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`
} else { : `/api/task/self?p=${page}&page_size=${size}&task_id=${task_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`;
url = `/api/task/self?p=${startIdx}&page_size=${pageSize}&task_id=${task_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`;
}
const res = await API.get(url); const res = await API.get(url);
let { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {
if (startIdx === 0) { syncPageData(data);
setLogsFormat(data);
} else {
let newLogs = [...logs];
newLogs.splice(startIdx * pageSize, data.length, ...data);
setLogsFormat(newLogs);
}
} else { } else {
showError(message); showError(message);
} }
setLoading(false); setLoading(false);
}; };
const pageData = logs.slice( const pageData = logs;
(activePage - 1) * pageSize,
activePage * pageSize,
);
const handlePageChange = (page) => { const handlePageChange = (page) => {
setActivePage(page); loadLogs(page, pageSize).then();
if (page === Math.ceil(logs.length / pageSize) + 1) {
loadLogs(page - 1, pageSize).then((r) => { });
}
}; };
const handlePageSizeChange = async (size) => { const handlePageSizeChange = async (size) => {
localStorage.setItem('task-page-size', size + ''); localStorage.setItem('task-page-size', size + '');
setPageSize(size); await loadLogs(1, size);
setActivePage(1);
await loadLogs(0, size);
}; };
const refresh = async () => { const refresh = async () => {
setActivePage(1); await loadLogs(1, pageSize);
await loadLogs(0, pageSize);
}; };
const copyText = async (text) => { const copyText = async (text) => {
@@ -565,12 +557,6 @@ const LogsTable = () => {
} }
}; };
useEffect(() => {
const localPageSize = parseInt(localStorage.getItem('task-page-size')) || ITEMS_PER_PAGE;
setPageSize(localPageSize);
loadLogs(0, localPageSize).then();
}, []);
// 列选择器模态框 // 列选择器模态框
const renderColumnSelector = () => { const renderColumnSelector = () => {
return ( return (
@@ -763,7 +749,7 @@ const LogsTable = () => {
> >
<Table <Table
columns={getVisibleColumns()} columns={getVisibleColumns()}
dataSource={pageData} dataSource={logs}
rowKey='key' rowKey='key'
loading={loading} loading={loading}
scroll={{ x: 'max-content' }} scroll={{ x: 'max-content' }}
@@ -789,9 +775,7 @@ const LogsTable = () => {
total: logCount, total: logCount,
pageSizeOptions: [10, 20, 50, 100], pageSizeOptions: [10, 20, 50, 100],
showSizeChanger: true, showSizeChanger: true,
onPageSizeChange: (size) => { onPageSizeChange: handlePageSizeChange,
handlePageSizeChange(size);
},
onPageChange: handlePageChange, onPageChange: handlePageChange,
}} }}
/> />

View File

@@ -408,31 +408,20 @@ const TokensTable = () => {
}, 500); }, 500);
}; };
const setTokensFormat = (tokens) => { // 将后端返回的数据写入状态
setTokens(tokens); const syncPageData = (payload) => {
if (tokens.length >= pageSize) { setTokens(payload.items || []);
setTokenCount(tokens.length + pageSize); setTokenCount(payload.total || 0);
} else { setActivePage(payload.page || 1);
setTokenCount(tokens.length); setPageSize(payload.page_size || pageSize);
}
}; };
let pageData = tokens.slice( const loadTokens = async (page = 1, size = pageSize) => {
(activePage - 1) * pageSize,
activePage * pageSize,
);
const loadTokens = async (startIdx) => {
setLoading(true); setLoading(true);
const res = await API.get(`/api/token/?p=${startIdx}&size=${pageSize}`); const res = await API.get(`/api/token/?p=${page}&size=${size}`);
const { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {
if (startIdx === 0) { syncPageData(data);
setTokensFormat(data);
} else {
let newTokens = [...tokens];
newTokens.splice(startIdx * pageSize, data.length, ...data);
setTokensFormat(newTokens);
}
} else { } else {
showError(message); showError(message);
} }
@@ -440,7 +429,7 @@ const TokensTable = () => {
}; };
const refresh = async () => { const refresh = async () => {
await loadTokens(activePage - 1); await loadTokens(1);
}; };
const copyText = async (text) => { const copyText = async (text) => {
@@ -473,7 +462,7 @@ const TokensTable = () => {
}; };
useEffect(() => { useEffect(() => {
loadTokens(0) loadTokens(1)
.then() .then()
.catch((reason) => { .catch((reason) => {
showError(reason); showError(reason);
@@ -487,7 +476,7 @@ const TokensTable = () => {
if (idx > -1) { if (idx > -1) {
newDataSource.splice(idx, 1); newDataSource.splice(idx, 1);
setTokensFormat(newDataSource); setTokens(newDataSource);
} }
} }
}; };
@@ -518,7 +507,7 @@ const TokensTable = () => {
} else { } else {
record.status = token.status; record.status = token.status;
} }
setTokensFormat(newTokens); setTokens(newTokens);
} else { } else {
showError(message); showError(message);
} }
@@ -528,8 +517,7 @@ const TokensTable = () => {
const searchTokens = async () => { const searchTokens = async () => {
const { searchKeyword, searchToken } = getFormValues(); const { searchKeyword, searchToken } = getFormValues();
if (searchKeyword === '' && searchToken === '') { if (searchKeyword === '' && searchToken === '') {
await loadTokens(0); await loadTokens(1);
setActivePage(1);
return; return;
} }
setSearching(true); setSearching(true);
@@ -538,7 +526,8 @@ const TokensTable = () => {
); );
const { success, message, data } = res.data; const { success, message, data } = res.data;
if (success) { if (success) {
setTokensFormat(data); setTokens(data);
setTokenCount(data.length);
setActivePage(1); setActivePage(1);
} else { } else {
showError(message); showError(message);
@@ -561,10 +550,12 @@ const TokensTable = () => {
}; };
const handlePageChange = (page) => { const handlePageChange = (page) => {
setActivePage(page); loadTokens(page, pageSize).then();
if (page === Math.ceil(tokens.length / pageSize) + 1) { };
loadTokens(page - 1).then((r) => { });
} const handlePageSizeChange = async (size) => {
setPageSize(size);
await loadTokens(1, size);
}; };
const rowSelection = { const rowSelection = {
@@ -707,7 +698,7 @@ const TokensTable = () => {
> >
<Table <Table
columns={columns} columns={columns}
dataSource={pageData} dataSource={tokens}
scroll={{ x: 'max-content' }} scroll={{ x: 'max-content' }}
pagination={{ pagination={{
currentPage: activePage, currentPage: activePage,
@@ -719,12 +710,9 @@ const TokensTable = () => {
t('第 {{start}} - {{end}} 条,共 {{total}} 条', { t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
start: page.currentStart, start: page.currentStart,
end: page.currentEnd, end: page.currentEnd,
total: tokens.length, total: tokenCount,
}), }),
onPageSizeChange: (size) => { onPageSizeChange: handlePageSizeChange,
setPageSize(size);
setActivePage(1);
},
onPageChange: handlePageChange, onPageChange: handlePageChange,
}} }}
loading={loading} loading={loading}