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:
CalciumIon
2024-12-31 15:02:59 +08:00
parent be0b2f6a64
commit 014fb7edab
4 changed files with 97 additions and 61 deletions

View File

@@ -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
} }

View File

@@ -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) {

View File

@@ -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

View File

@@ -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"
} }