优化页面

This commit is contained in:
2026-08-03 19:05:40 +08:00
parent 44c33a6280
commit d2370650e7
2 changed files with 56 additions and 25 deletions
@@ -93,6 +93,10 @@ describe('InternalUsersView unified platform user management', () => {
expect(wrapper.text()).toContain('华远贸易')
expect(wrapper.text()).toContain('租户管理员')
expect(wrapper.text()).toContain('2026-06-01 10:30')
expect(wrapper.find('[aria-label="用户筛选"]').exists()).toBe(true)
expect(wrapper.find('[aria-label="用户列表"]').exists()).toBe(true)
expect(wrapper.text()).toContain('查询')
expect(wrapper.text()).toContain('重置')
expect(wrapper.text()).not.toContain('认证用户 ID')
expect(wrapper.text()).not.toContain('成员编号')
})
+52 -25
View File
@@ -16,6 +16,7 @@ import { getApiErrorDetails, getApiErrorMessage, type ApiErrorDetails } from '@/
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import StatusBadge from '@/shared/components/StatusBadge.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
import { useUnsavedWorkRegistration } from '@/core/unsaved-work/useUnsavedWorkRegistration'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { useAuthStore } from '@/core/auth/auth'
@@ -33,7 +34,37 @@ const editingUser = ref<PlatformManagedUser | null>(null)
const rowActionId = ref('')
const page = ref(1)
const pageSize = ref(10)
const filters = reactive({ keyword: '', tenantId: '', status: '' })
type UserFilters = QueryModel & { keyword: string; tenantId: string; status: string }
const filters = ref<UserFilters>({ keyword: '', tenantId: '', status: '' })
const appliedFilters = ref<UserFilters>({ ...filters.value })
const queryFields = computed<QueryField[]>(() => [
{
key: 'keyword',
type: 'input',
placeholder: '用户名、姓名、手机号、租户或角色',
props: { maxlength: 128, 'aria-label': '用户关键词' },
},
{
key: 'tenantId',
type: 'select',
placeholder: '全部租户',
props: { 'aria-label': '所属租户' },
options: options.value.tenants.map((tenant) => ({
label: tenant.name,
value: String(tenant.id),
})),
},
{
key: 'status',
type: 'select',
placeholder: '全部状态',
props: { 'aria-label': '用户状态' },
options: [
{ label: '正常', value: 'ACTIVE' },
{ label: '禁用', value: 'DISABLED' },
],
},
])
const emptyForm = () => ({
username: '',
@@ -96,13 +127,13 @@ const formRules = computed<FormRules>(() => ({
}))
const filteredRows = computed(() => {
const keyword = filters.keyword.trim().toLowerCase()
const keyword = appliedFilters.value.keyword.trim().toLowerCase()
return users.value.filter((item) => {
const matchesKeyword = !keyword || [item.username, item.displayName, item.phone ?? '', item.email ?? '', item.tenantName, ...item.roleNames]
.some((value) => value.toLowerCase().includes(keyword))
return matchesKeyword
&& (!filters.tenantId || String(item.tenantId) === filters.tenantId)
&& (!filters.status || item.status === filters.status)
&& (!appliedFilters.value.tenantId || String(item.tenantId) === appliedFilters.value.tenantId)
&& (!appliedFilters.value.status || item.status === appliedFilters.value.status)
})
})
const pagedRows = computed(() => filteredRows.value.slice((page.value - 1) * pageSize.value, page.value * pageSize.value))
@@ -247,10 +278,8 @@ function formatLogin(value: string | null) {
return value.replace('T', ' ').slice(0, 16)
}
function resetFilters() {
filters.keyword = ''
filters.tenantId = ''
filters.status = ''
function applyFilters() {
appliedFilters.value = { ...filters.value }
page.value = 1
}
@@ -266,26 +295,25 @@ onMounted(loadUsers)
</template>
</el-alert>
<section class="surface-panel table-panel">
<QueryToolbar
v-model="filters"
class="surface-panel"
ariaLabel="用户筛选"
:fields="queryFields"
@search="applyFilters"
/>
<section class="surface-panel table-panel" :aria-busy="loading">
<header class="panel-heading">
<div><h2>用户管理 <span class="secondary-text">共 {{ filteredRows.length }} 条</span></h2></div>
<div>
<h2>用户列表 <span class="secondary-text">共 {{ filteredRows.length }} 条</span></h2>
<p>统一维护平台与企业租户账号、所属角色和使用状态。</p>
</div>
<el-button v-if="canCreate" type="primary" @click="openCreate"><Plus :size="16" />新增用户</el-button>
</header>
<div class="query-toolbar">
<el-input v-model="filters.keyword" clearable placeholder="用户名、姓名、手机号、租户或角色" @keyup.enter="page = 1" />
<el-select v-model="filters.tenantId" clearable placeholder="全部租户" @change="page = 1">
<el-option v-for="tenant in options.tenants" :key="tenant.id" :label="tenant.name" :value="String(tenant.id)" />
</el-select>
<el-select v-model="filters.status" clearable placeholder="全部状态" @change="page = 1">
<el-option label="正常" value="ACTIVE" />
<el-option label="禁用" value="DISABLED" />
</el-select>
<el-button @click="resetFilters">重置</el-button>
</div>
<DataTable v-model:page="page" v-model:page-size="pageSize" :data="pagedRows" :columns="userColumns"
:loading="loading" :total="filteredRows.length" row-key="membershipId" empty-text="暂无用户数据">
:loading="loading" :total="filteredRows.length" aria-label="用户列表" row-key="membershipId" empty-text="没有符合筛选条件的用户">
<template #actions="{ row }">
<div class="table-actions">
<el-button v-if="canEdit || canChangeStatus || canResetPassword" link type="primary" @click="openEdit(row as PlatformManagedUser)">
@@ -369,9 +397,8 @@ onMounted(loadUsers)
</template>
<style scoped>
.query-toolbar { display: grid; grid-template-columns: minmax(240px, 1fr) 220px 160px auto; gap: 12px; padding: 0 20px 16px; }
.password-fields { margin-top: 16px; }
@media (max-width: 720px) {
.query-toolbar, .form-grid.two-cols { grid-template-columns: 1fr; }
.form-grid.two-cols { grid-template-columns: 1fr; }
}
</style>