优化页面
This commit is contained in:
@@ -93,6 +93,10 @@ describe('InternalUsersView unified platform user management', () => {
|
|||||||
expect(wrapper.text()).toContain('华远贸易')
|
expect(wrapper.text()).toContain('华远贸易')
|
||||||
expect(wrapper.text()).toContain('租户管理员')
|
expect(wrapper.text()).toContain('租户管理员')
|
||||||
expect(wrapper.text()).toContain('2026-06-01 10:30')
|
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('认证用户 ID')
|
||||||
expect(wrapper.text()).not.toContain('成员编号')
|
expect(wrapper.text()).not.toContain('成员编号')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { getApiErrorDetails, getApiErrorMessage, type ApiErrorDetails } from '@/
|
|||||||
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||||||
import StatusBadge from '@/shared/components/StatusBadge.vue'
|
import StatusBadge from '@/shared/components/StatusBadge.vue'
|
||||||
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.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 { useUnsavedWorkRegistration } from '@/core/unsaved-work/useUnsavedWorkRegistration'
|
||||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||||
import { useAuthStore } from '@/core/auth/auth'
|
import { useAuthStore } from '@/core/auth/auth'
|
||||||
@@ -33,7 +34,37 @@ const editingUser = ref<PlatformManagedUser | null>(null)
|
|||||||
const rowActionId = ref('')
|
const rowActionId = ref('')
|
||||||
const page = ref(1)
|
const page = ref(1)
|
||||||
const pageSize = ref(10)
|
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 = () => ({
|
const emptyForm = () => ({
|
||||||
username: '',
|
username: '',
|
||||||
@@ -96,13 +127,13 @@ const formRules = computed<FormRules>(() => ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
const filteredRows = computed(() => {
|
const filteredRows = computed(() => {
|
||||||
const keyword = filters.keyword.trim().toLowerCase()
|
const keyword = appliedFilters.value.keyword.trim().toLowerCase()
|
||||||
return users.value.filter((item) => {
|
return users.value.filter((item) => {
|
||||||
const matchesKeyword = !keyword || [item.username, item.displayName, item.phone ?? '', item.email ?? '', item.tenantName, ...item.roleNames]
|
const matchesKeyword = !keyword || [item.username, item.displayName, item.phone ?? '', item.email ?? '', item.tenantName, ...item.roleNames]
|
||||||
.some((value) => value.toLowerCase().includes(keyword))
|
.some((value) => value.toLowerCase().includes(keyword))
|
||||||
return matchesKeyword
|
return matchesKeyword
|
||||||
&& (!filters.tenantId || String(item.tenantId) === filters.tenantId)
|
&& (!appliedFilters.value.tenantId || String(item.tenantId) === appliedFilters.value.tenantId)
|
||||||
&& (!filters.status || item.status === filters.status)
|
&& (!appliedFilters.value.status || item.status === appliedFilters.value.status)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
const pagedRows = computed(() => filteredRows.value.slice((page.value - 1) * pageSize.value, page.value * pageSize.value))
|
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)
|
return value.replace('T', ' ').slice(0, 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetFilters() {
|
function applyFilters() {
|
||||||
filters.keyword = ''
|
appliedFilters.value = { ...filters.value }
|
||||||
filters.tenantId = ''
|
|
||||||
filters.status = ''
|
|
||||||
page.value = 1
|
page.value = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,26 +295,25 @@ onMounted(loadUsers)
|
|||||||
</template>
|
</template>
|
||||||
</el-alert>
|
</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">
|
<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>
|
<el-button v-if="canCreate" type="primary" @click="openCreate"><Plus :size="16" />新增用户</el-button>
|
||||||
</header>
|
</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"
|
<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 }">
|
<template #actions="{ row }">
|
||||||
<div class="table-actions">
|
<div class="table-actions">
|
||||||
<el-button v-if="canEdit || canChangeStatus || canResetPassword" link type="primary" @click="openEdit(row as PlatformManagedUser)">
|
<el-button v-if="canEdit || canChangeStatus || canResetPassword" link type="primary" @click="openEdit(row as PlatformManagedUser)">
|
||||||
@@ -369,9 +397,8 @@ onMounted(loadUsers)
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<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; }
|
.password-fields { margin-top: 16px; }
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
.query-toolbar, .form-grid.two-cols { grid-template-columns: 1fr; }
|
.form-grid.two-cols { grid-template-columns: 1fr; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user