feat: enhance user management and pagination features #518
- Updated GetAllUsers function to return total user count along with paginated results, improving data handling in user retrieval. - Modified GetAllUsers API endpoint to accept page size as a parameter, allowing for dynamic pagination. - Enhanced UsersTable component to support customizable page sizes and improved pagination logic. - Added error handling for empty username and password in AddUser component. - Updated LogsTable component to display pagination information in a user-friendly format.
This commit is contained in:
@@ -11,9 +11,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"one-api/constant"
|
||||||
|
|
||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"one-api/constant"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type LoginRequest struct {
|
type LoginRequest struct {
|
||||||
@@ -242,10 +243,14 @@ func Register(c *gin.Context) {
|
|||||||
|
|
||||||
func GetAllUsers(c *gin.Context) {
|
func GetAllUsers(c *gin.Context) {
|
||||||
p, _ := strconv.Atoi(c.Query("p"))
|
p, _ := strconv.Atoi(c.Query("p"))
|
||||||
if p < 0 {
|
pageSize, _ := strconv.Atoi(c.Query("page_size"))
|
||||||
p = 0
|
if p < 1 {
|
||||||
|
p = 1
|
||||||
}
|
}
|
||||||
users, err := model.GetAllUsers(p*common.ItemsPerPage, common.ItemsPerPage)
|
if pageSize < 0 {
|
||||||
|
pageSize = common.ItemsPerPage
|
||||||
|
}
|
||||||
|
users, total, err := model.GetAllUsers((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,
|
||||||
@@ -256,7 +261,12 @@ func GetAllUsers(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": true,
|
"success": true,
|
||||||
"message": "",
|
"message": "",
|
||||||
"data": users,
|
"data": gin.H{
|
||||||
|
"items": users,
|
||||||
|
"total": total,
|
||||||
|
"page": p,
|
||||||
|
"page_size": pageSize,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,9 +81,38 @@ func GetMaxUserId() int {
|
|||||||
return user.Id
|
return user.Id
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllUsers(startIdx int, num int) (users []*User, err error) {
|
func GetAllUsers(startIdx int, num int) (users []*User, total int64, err error) {
|
||||||
err = DB.Unscoped().Order("id desc").Limit(num).Offset(startIdx).Omit("password").Find(&users).Error
|
// Start transaction
|
||||||
return users, err
|
tx := DB.Begin()
|
||||||
|
if tx.Error != nil {
|
||||||
|
return nil, 0, tx.Error
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Get total count within transaction
|
||||||
|
err = tx.Unscoped().Model(&User{}).Count(&total).Error
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get paginated users within same transaction
|
||||||
|
err = tx.Unscoped().Order("id desc").Limit(num).Offset(startIdx).Omit("password").Find(&users).Error
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit transaction
|
||||||
|
if err = tx.Commit().Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SearchUsers(keyword string, group string) ([]*User, error) {
|
func SearchUsers(keyword string, group string) ([]*User, error) {
|
||||||
|
|||||||
@@ -825,6 +825,12 @@ const LogsTable = () => {
|
|||||||
dataSource={logs}
|
dataSource={logs}
|
||||||
rowKey="key"
|
rowKey="key"
|
||||||
pagination={{
|
pagination={{
|
||||||
|
formatPageText: (page) =>
|
||||||
|
t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
|
||||||
|
start: page.currentStart,
|
||||||
|
end: page.currentEnd,
|
||||||
|
total: users.length
|
||||||
|
}),
|
||||||
currentPage: activePage,
|
currentPage: activePage,
|
||||||
pageSize: pageSize,
|
pageSize: pageSize,
|
||||||
total: logCount,
|
total: logCount,
|
||||||
|
|||||||
@@ -231,6 +231,7 @@ const UsersTable = () => {
|
|||||||
const [users, setUsers] = useState([]);
|
const [users, setUsers] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [activePage, setActivePage] = useState(1);
|
const [activePage, setActivePage] = useState(1);
|
||||||
|
const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE);
|
||||||
const [searchKeyword, setSearchKeyword] = useState('');
|
const [searchKeyword, setSearchKeyword] = useState('');
|
||||||
const [searching, setSearching] = useState(false);
|
const [searching, setSearching] = useState(false);
|
||||||
const [searchGroup, setSearchGroup] = useState('');
|
const [searchGroup, setSearchGroup] = useState('');
|
||||||
@@ -242,14 +243,6 @@ const UsersTable = () => {
|
|||||||
id: undefined,
|
id: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const setCount = (data) => {
|
|
||||||
if (data.length >= activePage * ITEMS_PER_PAGE) {
|
|
||||||
setUserCount(data.length + 1);
|
|
||||||
} else {
|
|
||||||
setUserCount(data.length);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeRecord = (key) => {
|
const removeRecord = (key) => {
|
||||||
let newDataSource = [...users];
|
let newDataSource = [...users];
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
@@ -263,37 +256,30 @@ const UsersTable = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadUsers = async (startIdx) => {
|
const setUserFormat = (users) => {
|
||||||
const res = await API.get(`/api/user/?p=${startIdx}`);
|
for (let i = 0; i < users.length; i++) {
|
||||||
|
users[i].key = users[i].id;
|
||||||
|
}
|
||||||
|
setUsers(users);
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadUsers = async (startIdx, pageSize) => {
|
||||||
|
const res = await API.get(`/api/user/?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;
|
||||||
setUsers(data);
|
setActivePage(data.page);
|
||||||
setCount(data);
|
setUserCount(data.total);
|
||||||
} else {
|
setUserFormat(newPageData);
|
||||||
let newUsers = users;
|
|
||||||
newUsers.push(...data);
|
|
||||||
setUsers(newUsers);
|
|
||||||
setCount(newUsers);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPaginationChange = (e, { activePage }) => {
|
|
||||||
(async () => {
|
|
||||||
if (activePage === Math.ceil(users.length / ITEMS_PER_PAGE) + 1) {
|
|
||||||
// In this case we have to load more data and then append them.
|
|
||||||
await loadUsers(activePage - 1);
|
|
||||||
}
|
|
||||||
setActivePage(activePage);
|
|
||||||
})();
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadUsers(0)
|
loadUsers(0, pageSize)
|
||||||
.then()
|
.then()
|
||||||
.catch((reason) => {
|
.catch((reason) => {
|
||||||
showError(reason);
|
showError(reason);
|
||||||
@@ -344,8 +330,7 @@ const UsersTable = () => {
|
|||||||
const searchUsers = async (searchKeyword, searchGroup) => {
|
const searchUsers = async (searchKeyword, searchGroup) => {
|
||||||
if (searchKeyword === '' && searchGroup === '') {
|
if (searchKeyword === '' && searchGroup === '') {
|
||||||
// if keyword is blank, load files instead.
|
// if keyword is blank, load files instead.
|
||||||
await loadUsers(0);
|
await loadUsers(activePage, pageSize);
|
||||||
setActivePage(1);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setSearching(true);
|
setSearching(true);
|
||||||
@@ -380,10 +365,7 @@ const UsersTable = () => {
|
|||||||
|
|
||||||
const handlePageChange = (page) => {
|
const handlePageChange = (page) => {
|
||||||
setActivePage(page);
|
setActivePage(page);
|
||||||
if (page === Math.ceil(users.length / ITEMS_PER_PAGE) + 1) {
|
loadUsers(page, pageSize).then((r) => {});
|
||||||
// In this case we have to load more data and then append them.
|
|
||||||
loadUsers(page - 1).then((r) => {});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const pageData = users.slice(
|
const pageData = users.slice(
|
||||||
@@ -403,8 +385,9 @@ const UsersTable = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const refresh = async () => {
|
const refresh = async () => {
|
||||||
|
setActivePage(1)
|
||||||
if (searchKeyword === '') {
|
if (searchKeyword === '') {
|
||||||
await loadUsers(activePage - 1);
|
await loadUsers(activePage, pageSize);
|
||||||
} else {
|
} else {
|
||||||
await searchUsers(searchKeyword, searchGroup);
|
await searchUsers(searchKeyword, searchGroup);
|
||||||
}
|
}
|
||||||
@@ -429,6 +412,17 @@ const UsersTable = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePageSizeChange = async (size) => {
|
||||||
|
localStorage.setItem('page-size', size + '');
|
||||||
|
setPageSize(size);
|
||||||
|
setActivePage(1);
|
||||||
|
loadUsers(activePage, size)
|
||||||
|
.then()
|
||||||
|
.catch((reason) => {
|
||||||
|
showError(reason);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AddUser
|
<AddUser
|
||||||
@@ -492,7 +486,7 @@ const UsersTable = () => {
|
|||||||
|
|
||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={pageData}
|
dataSource={users}
|
||||||
pagination={{
|
pagination={{
|
||||||
formatPageText: (page) =>
|
formatPageText: (page) =>
|
||||||
t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
|
t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
|
||||||
@@ -501,9 +495,13 @@ const UsersTable = () => {
|
|||||||
total: users.length
|
total: users.length
|
||||||
}),
|
}),
|
||||||
currentPage: activePage,
|
currentPage: activePage,
|
||||||
pageSize: ITEMS_PER_PAGE,
|
pageSize: pageSize,
|
||||||
total: userCount,
|
total: userCount,
|
||||||
pageSizeOpts: [10, 20, 50, 100],
|
pageSizeOpts: [2, 20, 50, 100],
|
||||||
|
showSizeChanger: true,
|
||||||
|
onPageSizeChange: (size) => {
|
||||||
|
handlePageSizeChange(size);
|
||||||
|
},
|
||||||
onPageChange: handlePageChange,
|
onPageChange: handlePageChange,
|
||||||
}}
|
}}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
|||||||
@@ -19,7 +19,11 @@ const AddUser = (props) => {
|
|||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
if (inputs.username === '' || inputs.password === '') return;
|
if (inputs.username === '' || inputs.password === '') {
|
||||||
|
setLoading(false);
|
||||||
|
showError('用户名和密码不能为空!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const res = await API.post(`/api/user/`, inputs);
|
const res = await API.post(`/api/user/`, inputs);
|
||||||
const { success, message } = res.data;
|
const { success, message } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
|
|||||||
Reference in New Issue
Block a user