feat: enhance user search functionality with pagination support
- Updated SearchUsers function to include pagination parameters (startIdx and num) for improved user search results. - Modified API response structure to return paginated data, including total user count and current page information. - Adjusted UsersTable component to handle pagination and search parameters, ensuring a seamless user experience. - Added internationalization support for new search functionality in the UI.
This commit is contained in:
@@ -274,7 +274,16 @@ func GetAllUsers(c *gin.Context) {
|
|||||||
func SearchUsers(c *gin.Context) {
|
func SearchUsers(c *gin.Context) {
|
||||||
keyword := c.Query("keyword")
|
keyword := c.Query("keyword")
|
||||||
group := c.Query("group")
|
group := c.Query("group")
|
||||||
users, err := model.SearchUsers(keyword, group)
|
p, _ := strconv.Atoi(c.Query("p"))
|
||||||
|
pageSize, _ := strconv.Atoi(c.Query("page_size"))
|
||||||
|
if p < 1 {
|
||||||
|
p = 1
|
||||||
|
}
|
||||||
|
if pageSize < 0 {
|
||||||
|
pageSize = common.ItemsPerPage
|
||||||
|
}
|
||||||
|
startIdx := (p - 1) * pageSize
|
||||||
|
users, total, err := model.SearchUsers(keyword, group, startIdx, pageSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
@@ -285,7 +294,12 @@ func SearchUsers(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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,36 +115,66 @@ func GetAllUsers(startIdx int, num int) (users []*User, total int64, err error)
|
|||||||
return users, total, nil
|
return users, total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SearchUsers(keyword string, group string) ([]*User, error) {
|
func SearchUsers(keyword string, group string, startIdx int, num int) ([]*User, int64, error) {
|
||||||
var users []*User
|
var users []*User
|
||||||
|
var total int64
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
// 开始事务
|
||||||
|
tx := DB.Begin()
|
||||||
|
if tx.Error != nil {
|
||||||
|
return nil, 0, tx.Error
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 构建基础查询
|
||||||
|
query := tx.Unscoped().Model(&User{})
|
||||||
|
|
||||||
// 尝试将关键字转换为整数ID
|
// 尝试将关键字转换为整数ID
|
||||||
keywordInt, err := strconv.Atoi(keyword)
|
keywordInt, err := strconv.Atoi(keyword)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// 如果转换成功,按照ID和可选的组别搜索用户
|
// 如果转换成功,按照ID和可选的组别搜索用户
|
||||||
query := DB.Unscoped().Omit("password").Where("id = ?", keywordInt)
|
|
||||||
if group != "" {
|
if group != "" {
|
||||||
query = query.Where(groupCol+" = ?", group) // 使用反引号包围group
|
query = query.Where("id = ? AND "+groupCol+" = ?", keywordInt, group)
|
||||||
|
} else {
|
||||||
|
query = query.Where("id = ?", keywordInt)
|
||||||
}
|
}
|
||||||
err = query.Find(&users).Error
|
|
||||||
if err != nil || len(users) > 0 {
|
|
||||||
return users, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = nil
|
|
||||||
|
|
||||||
query := DB.Unscoped().Omit("password")
|
|
||||||
likeCondition := "username LIKE ? OR email LIKE ? OR display_name LIKE ?"
|
|
||||||
if group != "" {
|
|
||||||
query = query.Where("("+likeCondition+") AND "+groupCol+" = ?", "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%", group)
|
|
||||||
} else {
|
} else {
|
||||||
query = query.Where(likeCondition, "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%")
|
// 如果不是ID搜索,则使用模糊匹配
|
||||||
|
likeCondition := "username LIKE ? OR email LIKE ? OR display_name LIKE ?"
|
||||||
|
if group != "" {
|
||||||
|
query = query.Where("("+likeCondition+") AND "+groupCol+" = ?",
|
||||||
|
"%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%", group)
|
||||||
|
} else {
|
||||||
|
query = query.Where(likeCondition,
|
||||||
|
"%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
err = query.Find(&users).Error
|
|
||||||
|
|
||||||
return users, err
|
// 获取总数
|
||||||
|
err = query.Count(&total).Error
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取分页数据
|
||||||
|
err = query.Omit("password").Order("id desc").Limit(num).Offset(startIdx).Find(&users).Error
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交事务
|
||||||
|
if err = tx.Commit().Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUserById(id int, selectAll bool) (*User, error) {
|
func GetUserById(id int, selectAll bool) (*User, error) {
|
||||||
|
|||||||
@@ -327,20 +327,22 @@ const UsersTable = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchUsers = async (searchKeyword, searchGroup) => {
|
const searchUsers = async (startIdx, pageSize, searchKeyword, searchGroup) => {
|
||||||
if (searchKeyword === '' && searchGroup === '') {
|
if (searchKeyword === '' && searchGroup === '') {
|
||||||
// if keyword is blank, load files instead.
|
// if keyword is blank, load files instead.
|
||||||
await loadUsers(activePage, pageSize);
|
await loadUsers(startIdx, pageSize);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setSearching(true);
|
setSearching(true);
|
||||||
const res = await API.get(`/api/user/search?keyword=${searchKeyword}&group=${searchGroup}`);
|
const res = await API.get(`/api/user/search?keyword=${searchKeyword}&group=${searchGroup}&p=${startIdx}&page_size=${pageSize}`);
|
||||||
const { success, message, data } = res.data;
|
const { success, message, data } = res.data;
|
||||||
if (success) {
|
if (success) {
|
||||||
setUsers(data);
|
const newPageData = data.items;
|
||||||
setActivePage(1);
|
setActivePage(data.page);
|
||||||
|
setUserCount(data.total);
|
||||||
|
setUserFormat(newPageData);
|
||||||
} else {
|
} else {
|
||||||
showError(message);
|
showError(message);
|
||||||
}
|
}
|
||||||
setSearching(false);
|
setSearching(false);
|
||||||
};
|
};
|
||||||
@@ -349,30 +351,15 @@ const UsersTable = () => {
|
|||||||
setSearchKeyword(value.trim());
|
setSearchKeyword(value.trim());
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortUser = (key) => {
|
|
||||||
if (users.length === 0) return;
|
|
||||||
setLoading(true);
|
|
||||||
let sortedUsers = [...users];
|
|
||||||
sortedUsers.sort((a, b) => {
|
|
||||||
return ('' + a[key]).localeCompare(b[key]);
|
|
||||||
});
|
|
||||||
if (sortedUsers[0].id === users[0].id) {
|
|
||||||
sortedUsers.reverse();
|
|
||||||
}
|
|
||||||
setUsers(sortedUsers);
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePageChange = (page) => {
|
const handlePageChange = (page) => {
|
||||||
setActivePage(page);
|
setActivePage(page);
|
||||||
loadUsers(page, pageSize).then((r) => {});
|
if (searchKeyword === '' && searchGroup === '') {
|
||||||
|
loadUsers(page, pageSize).then();
|
||||||
|
} else {
|
||||||
|
searchUsers(page, pageSize, searchKeyword, searchGroup).then();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const pageData = users.slice(
|
|
||||||
(activePage - 1) * ITEMS_PER_PAGE,
|
|
||||||
activePage * ITEMS_PER_PAGE,
|
|
||||||
);
|
|
||||||
|
|
||||||
const closeAddUser = () => {
|
const closeAddUser = () => {
|
||||||
setShowAddUser(false);
|
setShowAddUser(false);
|
||||||
};
|
};
|
||||||
@@ -438,29 +425,32 @@ const UsersTable = () => {
|
|||||||
></EditUser>
|
></EditUser>
|
||||||
<Form
|
<Form
|
||||||
onSubmit={() => {
|
onSubmit={() => {
|
||||||
searchUsers(searchKeyword, searchGroup);
|
searchUsers(activePage, pageSize, searchKeyword, searchGroup);
|
||||||
}}
|
}}
|
||||||
labelPosition='left'
|
labelPosition='left'
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex' }}>
|
<div style={{ display: 'flex' }}>
|
||||||
<Space>
|
<Space>
|
||||||
<Form.Input
|
<Tooltip content={t('支持搜索用户的 ID、用户名、显示名称和邮箱地址')}>
|
||||||
label={t('搜索关键字')}
|
<Form.Input
|
||||||
icon='search'
|
label={t('搜索关键字')}
|
||||||
field='keyword'
|
icon='search'
|
||||||
iconPosition='left'
|
field='keyword'
|
||||||
placeholder={t('搜索用户的 ID,用户名,显示名称,以及邮箱地址 ...')}
|
iconPosition='left'
|
||||||
value={searchKeyword}
|
placeholder={t('搜索关键字')}
|
||||||
loading={searching}
|
value={searchKeyword}
|
||||||
onChange={(value) => handleKeywordChange(value)}
|
loading={searching}
|
||||||
/>
|
onChange={(value) => handleKeywordChange(value)}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
<Form.Select
|
<Form.Select
|
||||||
field='group'
|
field='group'
|
||||||
label={t('分组')}
|
label={t('分组')}
|
||||||
optionList={groupOptions}
|
optionList={groupOptions}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setSearchGroup(value);
|
setSearchGroup(value);
|
||||||
searchUsers(searchKeyword, value);
|
searchUsers(activePage, pageSize, searchKeyword, value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1240,5 +1240,7 @@
|
|||||||
"时间范围": "Time range",
|
"时间范围": "Time range",
|
||||||
"批量设置标签": "Batch set tag",
|
"批量设置标签": "Batch set tag",
|
||||||
"请输入要设置的标签名称": "Please enter the tag name to be set",
|
"请输入要设置的标签名称": "Please enter the tag name to be set",
|
||||||
"请输入标签名称": "Please enter the tag name"
|
"请输入标签名称": "Please enter the tag name",
|
||||||
|
"支持搜索用户的 ID、用户名、显示名称和邮箱地址": "Support searching for user ID, username, display name, and email address",
|
||||||
|
"已注销": "Logged out"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user