优化
This commit is contained in:
@@ -94,13 +94,13 @@ const managedUser: PlatformManagedUser = {
|
||||
displayName: '张伟',
|
||||
phone: '13800138000',
|
||||
email: 'zhangwei@example.com',
|
||||
tenantId: '10001',
|
||||
tenantName: '华远贸易',
|
||||
memberType: 'EMPLOYEE',
|
||||
memberNo: 'MEMBER-3000002',
|
||||
tenantId: '0',
|
||||
tenantName: '平台',
|
||||
memberType: 'INTERNAL',
|
||||
memberNo: 'PLATFORM-3000002',
|
||||
status: 'ACTIVE',
|
||||
roleIds: ['40001'],
|
||||
roleNames: ['租户管理员'],
|
||||
roleNames: ['运营管理员'],
|
||||
lastLoginAt: '2026-06-01T10:30:00',
|
||||
protectedAccount: false,
|
||||
}
|
||||
@@ -204,32 +204,37 @@ describe('platform IAM roles', () => {
|
||||
expect(http.put).toHaveBeenNthCalledWith(4, `/platform/v1/iam/internal-users/${internalUser.membershipId}/roles`, { roleIds: [4000001] })
|
||||
})
|
||||
|
||||
it('maintains platform and tenant users through the unified user contract', async () => {
|
||||
it('maintains paginated platform users through the unified user contract', async () => {
|
||||
const userOptions = {
|
||||
tenants: [{ id: '10001', name: '华远贸易', status: 'ACTIVE' }],
|
||||
roles: [{ id: '40001', tenantId: '10001', name: '租户管理员', status: 'ACTIVE' }],
|
||||
tenants: [{ id: '0', name: '平台', status: 'ACTIVE' }],
|
||||
roles: [{ id: '40001', tenantId: '0', name: '运营管理员', status: 'ACTIVE' }],
|
||||
}
|
||||
const create = {
|
||||
username: 'zhangwei', displayName: '张伟', phone: '13800138000', email: 'zhangwei@example.com',
|
||||
tenantId: '10001', roleIds: ['40001'], password: 'Password1', status: 'ACTIVE' as const,
|
||||
tenantId: '0', roleIds: ['40001'], password: 'Password1', status: 'ACTIVE' as const,
|
||||
}
|
||||
const update = {
|
||||
displayName: '张伟', phone: '13800138000', email: 'zhangwei@example.com',
|
||||
tenantId: '10001', roleIds: ['40001'],
|
||||
tenantId: '0', roleIds: ['40001'],
|
||||
}
|
||||
vi.mocked(http.get).mockResolvedValueOnce({ code: '200', message: 'success', data: [managedUser] })
|
||||
const userPage = { records: [managedUser], page: 2, size: 10, total: 11, pages: 2 }
|
||||
vi.mocked(http.get).mockResolvedValueOnce({
|
||||
code: '200', message: 'success', data: { ...userPage, page: '2', size: '10', total: '11', pages: '2' },
|
||||
})
|
||||
.mockResolvedValueOnce({ code: '200', message: 'success', data: userOptions })
|
||||
vi.mocked(http.post).mockResolvedValue({ code: '200', message: 'success', data: managedUser })
|
||||
vi.mocked(http.put).mockResolvedValue({ code: '200', message: 'success', data: managedUser })
|
||||
|
||||
await expect(listPlatformManagedUsers()).resolves.toEqual([managedUser])
|
||||
await expect(listPlatformManagedUsers({ page: 2, size: 10, keyword: '张伟', status: 'ACTIVE' })).resolves.toEqual(userPage)
|
||||
await expect(getPlatformUserOptions()).resolves.toEqual(userOptions)
|
||||
await expect(createPlatformManagedUser(create)).resolves.toEqual(managedUser)
|
||||
await expect(updatePlatformManagedUser(managedUser.membershipId, update)).resolves.toEqual(managedUser)
|
||||
await expect(changePlatformManagedUserStatus(managedUser.membershipId, 'DISABLED')).resolves.toEqual(managedUser)
|
||||
await expect(resetPlatformManagedUserPassword(managedUser.membershipId, 'ChangeMe123')).resolves.toEqual(managedUser)
|
||||
|
||||
expect(http.get).toHaveBeenNthCalledWith(1, '/platform/v1/iam/users')
|
||||
expect(http.get).toHaveBeenNthCalledWith(1, '/platform/v1/iam/users', {
|
||||
params: { page: 2, size: 10, keyword: '张伟', status: 'ACTIVE' },
|
||||
})
|
||||
expect(http.get).toHaveBeenNthCalledWith(2, '/platform/v1/iam/users/options')
|
||||
expect(http.post).toHaveBeenCalledWith('/platform/v1/iam/users', create)
|
||||
expect(http.put).toHaveBeenNthCalledWith(1, `/platform/v1/iam/users/${managedUser.membershipId}`, update)
|
||||
|
||||
@@ -211,6 +211,21 @@ export interface PlatformManagedUser {
|
||||
protectedAccount: boolean
|
||||
}
|
||||
|
||||
export interface PlatformManagedUserPage {
|
||||
records: PlatformManagedUser[]
|
||||
page: number
|
||||
size: number
|
||||
total: number
|
||||
pages: number
|
||||
}
|
||||
|
||||
export interface PlatformManagedUserQuery {
|
||||
page: number
|
||||
size: number
|
||||
keyword?: string
|
||||
status?: PlatformManagedUser['status']
|
||||
}
|
||||
|
||||
export interface PlatformUserOptions {
|
||||
tenants: Array<{ id: number | string; name: string; status: string }>
|
||||
roles: Array<{ id: number | string; tenantId: number | string; name: string; status: string }>
|
||||
@@ -422,8 +437,26 @@ export function assignPlatformInternalUserRoles(membershipId: number | string, r
|
||||
return putData<PlatformInternalUser>(`/platform/v1/iam/internal-users/${membershipId}/roles`, { roleIds })
|
||||
}
|
||||
|
||||
export function listPlatformManagedUsers() {
|
||||
return getData<PlatformManagedUser[]>('/platform/v1/iam/users')
|
||||
export async function listPlatformManagedUsers(query: PlatformManagedUserQuery) {
|
||||
const response = await http.get<ApiEnvelope<PlatformManagedUserPage>, ApiEnvelope<PlatformManagedUserPage>>(
|
||||
'/platform/v1/iam/users',
|
||||
{
|
||||
params: {
|
||||
page: query.page,
|
||||
size: query.size,
|
||||
...(query.keyword ? { keyword: query.keyword } : {}),
|
||||
...(query.status ? { status: query.status } : {}),
|
||||
},
|
||||
},
|
||||
)
|
||||
const data = unwrapApiResponse(response)
|
||||
return {
|
||||
...data,
|
||||
page: Number(data.page),
|
||||
size: Number(data.size),
|
||||
total: Number(data.total),
|
||||
pages: Number(data.pages),
|
||||
}
|
||||
}
|
||||
|
||||
export function getPlatformUserOptions() {
|
||||
|
||||
@@ -40,9 +40,9 @@ function createMemoryStorage(): Storage {
|
||||
|
||||
const user: PlatformManagedUser = {
|
||||
membershipId: '30', userId: '20', authUserId: '10', username: 'zhangwei', displayName: '张伟',
|
||||
phone: '13800138000', email: 'zhangwei@example.com', tenantId: '10001', tenantName: '华远贸易',
|
||||
memberType: 'EMPLOYEE', memberNo: 'MEMBER-30', status: 'ACTIVE', roleIds: ['40'],
|
||||
roleNames: ['租户管理员'], lastLoginAt: '2026-06-01T10:30:00',
|
||||
phone: '13800138000', email: 'zhangwei@example.com', tenantId: '0', tenantName: '平台',
|
||||
memberType: 'INTERNAL', memberNo: 'PLATFORM-30', status: 'ACTIVE', roleIds: ['40'],
|
||||
roleNames: ['运营管理员'], lastLoginAt: '2026-06-01T10:30:00',
|
||||
protectedAccount: false,
|
||||
}
|
||||
|
||||
@@ -69,39 +69,47 @@ async function mountView() {
|
||||
enableAutoUnmount(afterEach)
|
||||
afterAll(() => vi.unstubAllGlobals())
|
||||
|
||||
describe('InternalUsersView unified platform user management', () => {
|
||||
describe('InternalUsersView platform internal user management', () => {
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal('ResizeObserver', ResizeObserverStub)
|
||||
document.body.innerHTML = ''
|
||||
vi.mocked(listPlatformManagedUsers).mockReset().mockResolvedValue([user])
|
||||
vi.mocked(listPlatformManagedUsers).mockReset().mockResolvedValue({
|
||||
records: [user], page: 1, size: 10, total: 21, pages: 3,
|
||||
})
|
||||
vi.mocked(getPlatformUserOptions).mockReset().mockResolvedValue({
|
||||
tenants: [
|
||||
{ id: '0', name: '平台', status: 'ACTIVE' },
|
||||
{ id: '10001', name: '华远贸易', status: 'ACTIVE' },
|
||||
],
|
||||
roles: [{ id: '40', tenantId: '10001', name: '租户管理员', status: 'ACTIVE' }],
|
||||
roles: [
|
||||
{ id: '40', tenantId: '0', name: '运营管理员', status: 'ACTIVE' },
|
||||
{ id: '41', tenantId: '10001', name: '租户管理员', status: 'ACTIVE' },
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it('loads and renders prototype fields from the unified API', async () => {
|
||||
it('loads a server page and only renders internal-user fields', async () => {
|
||||
const wrapper = await mountView()
|
||||
|
||||
expect(listPlatformManagedUsers).toHaveBeenCalledOnce()
|
||||
expect(listPlatformManagedUsers).toHaveBeenCalledWith({ page: 1, size: 10 })
|
||||
expect(getPlatformUserOptions).toHaveBeenCalledOnce()
|
||||
expect(wrapper.text()).toContain('zhangwei')
|
||||
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.find('[aria-label="用户筛选"]').exists()).toBe(true)
|
||||
expect(wrapper.find('[aria-label="用户列表"]').exists()).toBe(true)
|
||||
expect(wrapper.find('[aria-label="内部用户列表"]').exists()).toBe(true)
|
||||
expect(wrapper.text()).toContain('共 21 条')
|
||||
expect(wrapper.text()).toContain('查询')
|
||||
expect(wrapper.text()).toContain('重置')
|
||||
expect(wrapper.text()).not.toContain('认证用户 ID')
|
||||
expect(wrapper.text()).not.toContain('成员编号')
|
||||
expect(wrapper.text()).not.toContain('所属租户')
|
||||
expect(wrapper.text()).not.toContain('华远贸易')
|
||||
})
|
||||
|
||||
it('opens the complete prototype create form', async () => {
|
||||
it('opens an internal-user form without a tenant selector', async () => {
|
||||
const wrapper = await mountView()
|
||||
const createButton = wrapper.findAll('button').find((button) => button.text().trim() === '新增用户')
|
||||
await createButton?.trigger('click')
|
||||
@@ -111,11 +119,26 @@ describe('InternalUsersView unified platform user management', () => {
|
||||
expect(document.querySelector('input[placeholder="真实姓名"]')).not.toBeNull()
|
||||
expect(document.querySelector('input[placeholder="手机号码"]')).not.toBeNull()
|
||||
expect(document.querySelector('input[placeholder="邮箱地址"]')).not.toBeNull()
|
||||
expect(document.body.textContent).toContain('所属租户')
|
||||
expect(document.body.textContent).not.toContain('所属租户')
|
||||
expect(document.body.textContent).toContain('初始密码')
|
||||
expect(document.body.textContent).toContain('确认添加')
|
||||
})
|
||||
|
||||
it('requests server-side pages and filters instead of slicing locally', async () => {
|
||||
const wrapper = await mountView()
|
||||
await wrapper.find('button[aria-label="第 2 页"]').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(listPlatformManagedUsers).toHaveBeenLastCalledWith({ page: 2, size: 10 })
|
||||
|
||||
await wrapper.find('input[aria-label="用户关键词"]').setValue(' 张伟 ')
|
||||
const searchButton = wrapper.findAll('button').find((button) => button.text().trim() === '查询')
|
||||
await searchButton?.trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(listPlatformManagedUsers).toHaveBeenLastCalledWith({ page: 1, size: 10, keyword: '张伟' })
|
||||
})
|
||||
|
||||
it('shows a recoverable API error without fake users', async () => {
|
||||
vi.mocked(listPlatformManagedUsers).mockRejectedValueOnce(new Error('Gateway unavailable'))
|
||||
const wrapper = await mountView()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
|
||||
import { Pencil, Plus } from '@lucide/vue'
|
||||
import {
|
||||
@@ -32,28 +32,20 @@ const editorOpen = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const editingUser = ref<PlatformManagedUser | null>(null)
|
||||
const rowActionId = ref('')
|
||||
let loadRequestId = 0
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
type UserFilters = QueryModel & { keyword: string; tenantId: string; status: string }
|
||||
const filters = ref<UserFilters>({ keyword: '', tenantId: '', status: '' })
|
||||
const total = ref(0)
|
||||
type UserFilters = QueryModel & { keyword: string; status: string }
|
||||
const filters = ref<UserFilters>({ keyword: '', status: '' })
|
||||
const appliedFilters = ref<UserFilters>({ ...filters.value })
|
||||
const queryFields = computed<QueryField[]>(() => [
|
||||
{
|
||||
key: 'keyword',
|
||||
type: 'input',
|
||||
placeholder: '用户名、姓名、手机号、租户或角色',
|
||||
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',
|
||||
@@ -71,7 +63,7 @@ const emptyForm = () => ({
|
||||
displayName: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
tenantId: '' as string,
|
||||
tenantId: '0',
|
||||
roleIds: [] as string[],
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
@@ -95,11 +87,8 @@ useUnsavedWorkRegistration({
|
||||
requiredPermission: computed(() => editingUser.value ? 'platform:user:edit' : 'platform:user:create'),
|
||||
})
|
||||
|
||||
const enabledTenants = computed(() => options.value.tenants.filter((tenant) =>
|
||||
tenant.status === 'ACTIVE' || String(tenant.id) === form.tenantId,
|
||||
))
|
||||
const availableRoles = computed(() => options.value.roles.filter((role) =>
|
||||
String(role.tenantId) === form.tenantId
|
||||
String(role.tenantId) === '0'
|
||||
&& (role.status === 'ACTIVE' || form.roleIds.includes(String(role.id))),
|
||||
))
|
||||
|
||||
@@ -114,7 +103,6 @@ const formRules = computed<FormRules>(() => ({
|
||||
{ pattern: /^1\d{10}$/, message: '请输入正确的 11 位手机号', trigger: 'blur' },
|
||||
],
|
||||
email: editingUser.value && !canEdit.value ? [] : [{ type: 'email', message: '请输入有效的邮箱地址', trigger: 'blur' }],
|
||||
tenantId: editingUser.value && !canEdit.value ? [] : [{ required: true, message: '请选择所属租户', trigger: 'change' }],
|
||||
roleIds: editingUser.value && !canEdit.value ? [] : [{ type: 'array', required: true, min: 1, message: '请选择角色', trigger: 'change' }],
|
||||
password: editingUser.value && !form.resetPassword ? [] : [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
@@ -126,50 +114,71 @@ const formRules = computed<FormRules>(() => ({
|
||||
],
|
||||
}))
|
||||
|
||||
const filteredRows = computed(() => {
|
||||
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
|
||||
&& (!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))
|
||||
|
||||
const userColumns: DataTableColumn[] = [
|
||||
{ key: 'actions', label: '操作', width: 170, fixed: 'left', slotName: 'actions' },
|
||||
{ prop: 'username', label: '用户名', minWidth: 145 },
|
||||
{ prop: 'displayName', label: '姓名', minWidth: 130 },
|
||||
{ prop: 'tenantName', label: '所属租户', minWidth: 190 },
|
||||
{ key: 'roles', label: '角色', minWidth: 180, slotName: 'roles' },
|
||||
{ key: 'status', label: '状态', width: 100, slotName: 'status' },
|
||||
{ key: 'lastLoginAt', label: '最后登录', minWidth: 170, slotName: 'lastLoginAt' },
|
||||
]
|
||||
|
||||
function userQuery() {
|
||||
return {
|
||||
page: page.value,
|
||||
size: pageSize.value,
|
||||
...(appliedFilters.value.keyword.trim() ? { keyword: appliedFilters.value.keyword.trim() } : {}),
|
||||
...(appliedFilters.value.status ? { status: appliedFilters.value.status as PlatformManagedUser['status'] } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
async function loadUsers() {
|
||||
const requestId = ++loadRequestId
|
||||
loading.value = true
|
||||
loadError.value = null
|
||||
try {
|
||||
const [userRecords, optionRecords] = await Promise.all([
|
||||
listPlatformManagedUsers(),
|
||||
getPlatformUserOptions(),
|
||||
])
|
||||
users.value = userRecords
|
||||
options.value = optionRecords
|
||||
const result = await listPlatformManagedUsers(userQuery())
|
||||
if (requestId !== loadRequestId) return
|
||||
users.value = result.records
|
||||
total.value = result.total
|
||||
} catch (error) {
|
||||
if (requestId !== loadRequestId) return
|
||||
users.value = []
|
||||
total.value = 0
|
||||
loadError.value = getApiErrorDetails(error, '用户列表加载失败,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
if (requestId === loadRequestId) loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadInitialData() {
|
||||
const requestId = ++loadRequestId
|
||||
loading.value = true
|
||||
loadError.value = null
|
||||
try {
|
||||
const [result, optionRecords] = await Promise.all([
|
||||
listPlatformManagedUsers(userQuery()),
|
||||
getPlatformUserOptions(),
|
||||
])
|
||||
options.value = {
|
||||
tenants: optionRecords.tenants.filter((tenant) => String(tenant.id) === '0'),
|
||||
roles: optionRecords.roles.filter((role) => String(role.tenantId) === '0'),
|
||||
}
|
||||
if (requestId !== loadRequestId) return
|
||||
users.value = result.records
|
||||
total.value = result.total
|
||||
} catch (error) {
|
||||
if (requestId !== loadRequestId) return
|
||||
users.value = []
|
||||
total.value = 0
|
||||
loadError.value = getApiErrorDetails(error, '内部用户列表加载失败,请稍后重试')
|
||||
} finally {
|
||||
if (requestId === loadRequestId) loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
Object.assign(form, emptyForm())
|
||||
const defaultTenant = options.value.tenants.find((tenant) => tenant.status === 'ACTIVE')
|
||||
form.tenantId = defaultTenant ? String(defaultTenant.id) : ''
|
||||
editingUser.value = null
|
||||
editorOpen.value = true
|
||||
nextTick(() => formRef.value?.clearValidate())
|
||||
@@ -182,7 +191,7 @@ function openEdit(row: PlatformManagedUser) {
|
||||
displayName: row.displayName,
|
||||
phone: row.phone ?? '',
|
||||
email: row.email ?? '',
|
||||
tenantId: String(row.tenantId),
|
||||
tenantId: '0',
|
||||
roleIds: row.roleIds.map(String),
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
@@ -193,11 +202,6 @@ function openEdit(row: PlatformManagedUser) {
|
||||
nextTick(() => formRef.value?.clearValidate())
|
||||
}
|
||||
|
||||
function handleTenantChange() {
|
||||
const validRoleIds = new Set(availableRoles.value.map((role) => String(role.id)))
|
||||
form.roleIds = form.roleIds.filter((roleId) => validRoleIds.has(roleId))
|
||||
}
|
||||
|
||||
function isCurrentUser(row: PlatformManagedUser | null) {
|
||||
if (!row) return false
|
||||
return String(row.userId) === auth.user?.id || String(row.authUserId) === auth.user?.id
|
||||
@@ -218,7 +222,7 @@ async function saveUser() {
|
||||
displayName: form.displayName.trim(),
|
||||
phone: form.phone.trim(),
|
||||
email: form.email.trim(),
|
||||
tenantId: form.tenantId,
|
||||
tenantId: '0',
|
||||
roleIds: form.roleIds,
|
||||
password: form.password,
|
||||
status: form.status,
|
||||
@@ -231,7 +235,7 @@ async function saveUser() {
|
||||
displayName: form.displayName.trim(),
|
||||
phone: form.phone.trim(),
|
||||
email: form.email.trim(),
|
||||
tenantId: form.tenantId,
|
||||
tenantId: '0',
|
||||
roleIds: form.roleIds,
|
||||
})
|
||||
}
|
||||
@@ -280,10 +284,12 @@ function formatLogin(value: string | null) {
|
||||
|
||||
function applyFilters() {
|
||||
appliedFilters.value = { ...filters.value }
|
||||
page.value = 1
|
||||
if (page.value === 1) void loadUsers()
|
||||
else page.value = 1
|
||||
}
|
||||
|
||||
onMounted(loadUsers)
|
||||
watch([page, pageSize], () => void loadUsers())
|
||||
onMounted(loadInitialData)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -291,7 +297,7 @@ onMounted(loadUsers)
|
||||
<el-alert v-if="loadError" type="error" :closable="false" show-icon role="alert">
|
||||
<template #title>
|
||||
<div class="api-error-alert-title"><span>{{ loadError.message }}</span><ApiErrorMeta :details="loadError" /></div>
|
||||
<el-button link type="danger" @click="loadUsers">重新加载</el-button>
|
||||
<el-button link type="danger" @click="loadInitialData">重新加载</el-button>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
@@ -306,14 +312,14 @@ onMounted(loadUsers)
|
||||
<section class="surface-panel table-panel" :aria-busy="loading">
|
||||
<header class="panel-heading">
|
||||
<div>
|
||||
<h2>用户列表 <span class="secondary-text">共 {{ filteredRows.length }} 条</span></h2>
|
||||
<p>统一维护平台与企业租户账号、所属角色和使用状态。</p>
|
||||
<h2>内部用户列表 <span class="secondary-text">共 {{ total }} 条</span></h2>
|
||||
<p>维护平台内部员工账号、平台角色和使用状态。</p>
|
||||
</div>
|
||||
<el-button v-if="canCreate" type="primary" @click="openCreate"><Plus :size="16" />新增用户</el-button>
|
||||
</header>
|
||||
|
||||
<DataTable v-model:page="page" v-model:page-size="pageSize" :data="pagedRows" :columns="userColumns"
|
||||
:loading="loading" :total="filteredRows.length" aria-label="用户列表" row-key="membershipId" empty-text="没有符合筛选条件的用户">
|
||||
<DataTable v-model:page="page" v-model:page-size="pageSize" :data="users" :columns="userColumns"
|
||||
:loading="loading" :total="total" 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)">
|
||||
@@ -354,11 +360,6 @@ onMounted(loadUsers)
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="form.email" :disabled="Boolean(editingUser) && !canEdit" placeholder="邮箱地址" inputmode="email" maxlength="128" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属租户" prop="tenantId" required>
|
||||
<el-select v-model="form.tenantId" :disabled="Boolean(editingUser) && (!canEdit || !canGrantRoles || editingUser?.protectedAccount)" placeholder="选择所属租户" @change="handleTenantChange">
|
||||
<el-option v-for="tenant in enabledTenants" :key="tenant.id" :label="tenant.name" :value="String(tenant.id)" :disabled="tenant.status !== 'ACTIVE'" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色" prop="roleIds" required>
|
||||
<el-select v-model="form.roleIds" multiple collapse-tags collapse-tags-tooltip
|
||||
:disabled="Boolean(editingUser) && (!canEdit || !canGrantRoles || editingUser?.protectedAccount)" placeholder="选择角色">
|
||||
|
||||
Reference in New Issue
Block a user