merge: 用户管理分页 + 工具分页上限确认
This commit is contained in:
@@ -16,6 +16,7 @@ const session = reactive({ name: '', csrfToken: '' })
|
||||
const loginForm = reactive({ account: '', password: '' })
|
||||
const users = ref([])
|
||||
const userKeyword = ref('')
|
||||
const userPage = reactive({ page: 1, pageSize: 20, total: 0 })
|
||||
const selectedUser = ref(null)
|
||||
const permission = ref(null)
|
||||
const tokens = ref([])
|
||||
@@ -80,14 +81,22 @@ async function switchMenu(id) {
|
||||
if (id === 'audit') await Promise.all([loadAudit(), api('/tools').then(data => { tools.value = data })])
|
||||
}
|
||||
|
||||
async function loadUsers() {
|
||||
users.value = await api(`/users${qs({ keyword: userKeyword.value })}`)
|
||||
async function loadUsers(resetPage = false) {
|
||||
if (resetPage) userPage.page = 1
|
||||
const data = await api(`/users${qs({ keyword: userKeyword.value, page: userPage.page, pageSize: userPage.pageSize })}`)
|
||||
users.value = data.records
|
||||
userPage.total = data.total
|
||||
const existing = users.value.find(user => user.id === selectedUser.value?.id)
|
||||
if (existing) selectedUser.value = existing
|
||||
else if (users.value.length) await selectUser(users.value[0])
|
||||
else { selectedUser.value = null; permission.value = null; tokens.value = [] }
|
||||
}
|
||||
|
||||
async function changeUserPage(page) {
|
||||
userPage.page = page
|
||||
await run(loadUsers)
|
||||
}
|
||||
|
||||
async function selectUser(user) {
|
||||
selectedUser.value = user
|
||||
;[permission.value, tokens.value, scopes.value] = await Promise.all([
|
||||
@@ -248,8 +257,8 @@ onMounted(async () => {
|
||||
<div class="card-title"><span>人员</span><small>来自现有 user 表</small></div>
|
||||
<div class="toolbar compact">
|
||||
<el-input v-model="userKeyword" clearable placeholder="姓名 / 手机号" aria-label="搜索人员"
|
||||
data-testid="admin-input-user-search" @keyup.enter="run(loadUsers)" />
|
||||
<el-button data-testid="admin-user-search-button" @click="run(loadUsers)">搜索</el-button>
|
||||
data-testid="admin-input-user-search" @keyup.enter="run(() => loadUsers(true))" />
|
||||
<el-button data-testid="admin-user-search-button" @click="run(() => loadUsers(true))">搜索</el-button>
|
||||
</div>
|
||||
<div class="user-list">
|
||||
<button v-for="user in users" :key="user.id" :class="['user-row', { selected: selectedUser?.id === user.id }]"
|
||||
@@ -259,6 +268,9 @@ onMounted(async () => {
|
||||
</button>
|
||||
<el-empty v-if="!users.length" :image-size="60" description="未找到人员" />
|
||||
</div>
|
||||
<el-pagination class="user-pager" layout="prev, pager, next, total" :total="userPage.total"
|
||||
:page-size="userPage.pageSize" :current-page="userPage.page" small background
|
||||
data-testid="admin-user-pager" @current-change="changeUserPage" />
|
||||
</article>
|
||||
<article class="card permission-card">
|
||||
<div class="card-title"><span>数据权限归属</span><small>只读 · 在现有系统维护</small></div>
|
||||
|
||||
@@ -40,6 +40,7 @@ button, input { font: inherit; }
|
||||
.user-row.selected { background: #ecf5ff; }
|
||||
.user-row span:first-child { display: flex; flex-direction: column; gap: 4px; }
|
||||
.user-row small { color: #909399; }
|
||||
.user-pager { margin-top: 10px; justify-content: flex-end; }
|
||||
.permission-card dl { margin: 0; border-top: 1px solid #ebeef5; }
|
||||
.permission-card dl > div { display: grid; grid-template-columns: 92px 1fr; border-bottom: 1px solid #ebeef5; }
|
||||
.permission-card dt, .permission-card dd { margin: 0; padding: 10px 6px; }
|
||||
|
||||
@@ -82,19 +82,26 @@ public class AdminConsoleController {
|
||||
}
|
||||
|
||||
@GetMapping("/users")
|
||||
public List<UserView> users(@RequestParam(defaultValue = "") String keyword) {
|
||||
public UserPageView users(@RequestParam(defaultValue = "") String keyword,
|
||||
@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "20") int pageSize) {
|
||||
if (keyword.length() > 100) bad("搜索关键词最多 100 字");
|
||||
int pageNo = Math.max(page, 1);
|
||||
int size = pageSize < 1 ? 20 : Math.min(pageSize, 100);
|
||||
LambdaQueryWrapper<User> query = new LambdaQueryWrapper<User>().eq(User::getSoftDelete, 0);
|
||||
if (!keyword.isBlank()) query.and(w -> w.like(User::getName, keyword.trim())
|
||||
.or().like(User::getMobile, keyword.trim()));
|
||||
List<User> users = userMapper.selectList(query.orderByDesc(User::getId).last("LIMIT 100"));
|
||||
Long total = userMapper.selectCount(query);
|
||||
List<User> users = userMapper.selectList(query.orderByDesc(User::getId)
|
||||
.last("LIMIT " + (long) (pageNo - 1) * size + "," + size));
|
||||
Set<Long> ids = users.stream().map(User::getId).collect(Collectors.toSet());
|
||||
Map<Long, Long> counts = ids.isEmpty() ? Map.of() : tokenMapper.selectList(
|
||||
new LambdaQueryWrapper<AgentApiKey>().in(AgentApiKey::getUserId, ids)
|
||||
.eq(AgentApiKey::getSoftDelete, 0))
|
||||
.stream().collect(Collectors.groupingBy(AgentApiKey::getUserId, Collectors.counting()));
|
||||
return users.stream().map(user -> new UserView(user.getId(), user.getName(), maskMobile(user.getMobile()),
|
||||
counts.getOrDefault(user.getId(), 0L))).toList();
|
||||
List<UserView> records = users.stream().map(user -> new UserView(user.getId(), user.getName(),
|
||||
maskMobile(user.getMobile()), counts.getOrDefault(user.getId(), 0L))).toList();
|
||||
return new UserPageView(total, pageNo, size, records);
|
||||
}
|
||||
|
||||
@GetMapping("/users/{id}/permission")
|
||||
@@ -375,6 +382,7 @@ public class AdminConsoleController {
|
||||
}
|
||||
|
||||
public record UserView(Long id, String name, String mobile, long tokenCount) {}
|
||||
public record UserPageView(long total, int page, int pageSize, List<UserView> records) {}
|
||||
public record PermissionView(String authorityName, Integer isAgent, String agentName,
|
||||
Integer enterpriseType, String visibleRange) {}
|
||||
public record TokenRequest(String memo, List<Long> scopeIds, Date expireTime) {}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package me.iiv.agent.admin;
|
||||
|
||||
import me.iiv.agent.mapper.m0.AgentMapper;
|
||||
import me.iiv.agent.mapper.m0.AuthorityMapper;
|
||||
import me.iiv.agent.mapper.m0.UserMapper;
|
||||
import me.iiv.agent.mapper.svc.AgentApiKeyMapper;
|
||||
import me.iiv.agent.mapper.svc.AgentScopeMapper;
|
||||
import me.iiv.agent.mapper.svc.AuditLogMapper;
|
||||
import me.iiv.agent.mapper.svc.ScopeToolMapper;
|
||||
import me.iiv.agent.mapper.svc.TokenScopeMapper;
|
||||
import me.iiv.agent.mapper.svc.ToolMapper;
|
||||
import me.iiv.agent.permission.PermissionService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AdminConsoleControllerTest {
|
||||
|
||||
@Mock UserMapper userMapper;
|
||||
@Mock AuthorityMapper authorityMapper;
|
||||
@Mock AgentMapper agentMapper;
|
||||
@Mock AgentApiKeyMapper tokenMapper;
|
||||
@Mock AgentScopeMapper scopeMapper;
|
||||
@Mock ToolMapper toolMapper;
|
||||
@Mock ScopeToolMapper scopeToolMapper;
|
||||
@Mock AuditLogMapper auditMapper;
|
||||
@Mock TokenScopeMapper tokenScopeMapper;
|
||||
@Mock PermissionService permissionService;
|
||||
@Mock AdminTokenService tokenService;
|
||||
|
||||
AdminConsoleController controller;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
controller = new AdminConsoleController(userMapper, authorityMapper, agentMapper, tokenMapper,
|
||||
scopeMapper, toolMapper, scopeToolMapper, auditMapper, tokenScopeMapper,
|
||||
permissionService, tokenService);
|
||||
when(userMapper.selectCount(any())).thenReturn(0L);
|
||||
when(userMapper.selectList(any())).thenReturn(List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
void usersClampsPageSizeToHundred() {
|
||||
AdminConsoleController.UserPageView view = controller.users("", 1, 500);
|
||||
assertEquals(100, view.pageSize());
|
||||
assertEquals(1, view.page());
|
||||
assertEquals(0, view.total());
|
||||
}
|
||||
|
||||
@Test
|
||||
void usersDefaultsInvalidPageAndSize() {
|
||||
AdminConsoleController.UserPageView view = controller.users("", 0, 0);
|
||||
assertEquals(1, view.page());
|
||||
assertEquals(20, view.pageSize());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user