feat(phase3): complete loading workbenches
This commit is contained in:
@@ -18,7 +18,7 @@ const permission: PlatformPermissionResource = {
|
||||
tenantId: 0,
|
||||
appCode: 'PLATFORM',
|
||||
permissionType: 'MENU',
|
||||
parentId: 5100000,
|
||||
parentId: 0,
|
||||
permissionCode: 'platform:customer:list',
|
||||
permissionName: '企业客户',
|
||||
routePath: '/customers',
|
||||
@@ -27,12 +27,20 @@ const permission: PlatformPermissionResource = {
|
||||
status: 'ACTIVE',
|
||||
}
|
||||
|
||||
function mountServerView() {
|
||||
const allActionPermissions = [
|
||||
'platform:permission:list',
|
||||
'platform:permission:create',
|
||||
'platform:permission:edit',
|
||||
'platform:permission:status',
|
||||
'platform:permission:delete',
|
||||
]
|
||||
|
||||
function mountServerView(permissionCodes = ['platform:permission:list', 'platform:permission:create']) {
|
||||
const pinia = createPinia()
|
||||
setActivePinia(pinia)
|
||||
const auth = useAuthStore()
|
||||
auth.isServerSession = true
|
||||
auth.permissions = ['platform:permission:list', 'platform:permission:create']
|
||||
auth.permissions = permissionCodes
|
||||
return shallowMount(PermissionsView, {
|
||||
global: {
|
||||
plugins: [pinia],
|
||||
@@ -43,12 +51,18 @@ function mountServerView() {
|
||||
FormSection: { template: '<section><slot /></section>' },
|
||||
AppDrawer: { template: '<section><slot /><slot name="footer" /></section>' },
|
||||
LoadErrorState: { props: ['message'], template: '<div role="alert">{{ message }}<slot name="details" /></div>' },
|
||||
DataTable: { props: ['data'], template: '<section><slot name="toolbar-actions" /><span data-testid="row-count">{{ data.length }}</span></section>' },
|
||||
DataTable: {
|
||||
props: ['data'],
|
||||
template: '<section><slot name="toolbar-actions" /><div v-for="row in data" :key="row.id" class="row-actions" :data-row-id="row.id"><slot name="actions" :row="row" /></div><span data-testid="row-count">{{ data.length }}</span></section>',
|
||||
},
|
||||
'el-alert': { props: ['title'], template: '<div role="alert">{{ title }}<slot name="title" /></div>' },
|
||||
'el-input': true,
|
||||
'el-option': true,
|
||||
'el-select': true,
|
||||
'el-button': true,
|
||||
'el-button': { props: ['disabled'], template: '<button :disabled="disabled"><slot /></button>' },
|
||||
'el-dropdown': { template: '<div class="dropdown"><slot /><slot name="dropdown" /></div>' },
|
||||
'el-dropdown-menu': { template: '<div><slot /></div>' },
|
||||
'el-dropdown-item': { props: ['disabled'], template: '<button class="dropdown-item" :disabled="disabled"><slot /></button>' },
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-input-number': true,
|
||||
@@ -85,4 +99,111 @@ describe('PermissionsView IAM mode', () => {
|
||||
|
||||
expect(wrapper.findAll('[role="alert"]').map((item) => item.text()).join(' ')).toContain('IAM unavailable')
|
||||
})
|
||||
|
||||
it('shows only add-child directly for a built-in menu even with all permissions', async () => {
|
||||
vi.mocked(listPlatformPermissions).mockResolvedValue([{ ...permission, builtIn: true }])
|
||||
|
||||
const wrapper = mountServerView(allActionPermissions)
|
||||
await flushPromises()
|
||||
|
||||
const actions = wrapper.get('[data-row-id="5100010"]')
|
||||
expect(actions.text()).toContain('添加下级')
|
||||
expect(actions.text()).toContain('系统内置')
|
||||
expect(actions.text()).not.toContain('编辑')
|
||||
expect(actions.text()).not.toContain('停用')
|
||||
expect(actions.text()).not.toContain('删除')
|
||||
expect(actions.find('.dropdown').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('keeps three frequent actions direct and moves delete into more for a custom menu', async () => {
|
||||
vi.mocked(listPlatformPermissions).mockResolvedValue([{ ...permission, builtIn: false }])
|
||||
|
||||
const wrapper = mountServerView(allActionPermissions)
|
||||
await flushPromises()
|
||||
|
||||
const actions = wrapper.get('[data-row-id="5100010"]')
|
||||
expect(actions.text()).toContain('添加下级')
|
||||
expect(actions.text()).toContain('编辑')
|
||||
expect(actions.text()).toContain('停用')
|
||||
expect(actions.get('.dropdown').text()).toContain('删除')
|
||||
expect(actions.get('.dropdown-item').classes()).toContain('danger-menu-item')
|
||||
expect(actions.get('[aria-label="企业客户的更多操作"]').attributes('title')).toBe('企业客户的更多操作')
|
||||
})
|
||||
|
||||
it('shows edit, status, and delete directly for a custom non-menu resource', async () => {
|
||||
vi.mocked(listPlatformPermissions).mockResolvedValue([{
|
||||
...permission,
|
||||
permissionType: 'BUTTON',
|
||||
permissionCode: 'platform:customer:edit',
|
||||
permissionName: '编辑企业客户',
|
||||
routePath: null,
|
||||
builtIn: false,
|
||||
}])
|
||||
|
||||
const wrapper = mountServerView(allActionPermissions)
|
||||
await flushPromises()
|
||||
|
||||
const actions = wrapper.get('[data-row-id="5100010"]')
|
||||
expect(actions.text()).not.toContain('添加下级')
|
||||
expect(actions.text()).toContain('编辑')
|
||||
expect(actions.text()).toContain('停用')
|
||||
expect(actions.text()).toContain('删除')
|
||||
expect(actions.find('.dropdown').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('shows all three actually available menu actions directly when permissions are reduced', async () => {
|
||||
vi.mocked(listPlatformPermissions).mockResolvedValue([{ ...permission, builtIn: false }])
|
||||
|
||||
const wrapper = mountServerView([
|
||||
'platform:permission:list',
|
||||
'platform:permission:create',
|
||||
'platform:permission:edit',
|
||||
'platform:permission:delete',
|
||||
])
|
||||
await flushPromises()
|
||||
|
||||
const actions = wrapper.get('[data-row-id="5100010"]')
|
||||
expect(actions.text()).toContain('添加下级')
|
||||
expect(actions.text()).toContain('编辑')
|
||||
expect(actions.text()).toContain('删除')
|
||||
expect(actions.text()).not.toContain('停用')
|
||||
expect(actions.find('.dropdown').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('falls back from a missing name and code to the row id for the more-action label', async () => {
|
||||
vi.mocked(listPlatformPermissions).mockResolvedValue([{
|
||||
...permission,
|
||||
permissionName: '',
|
||||
permissionCode: '',
|
||||
builtIn: false,
|
||||
}])
|
||||
|
||||
const wrapper = mountServerView(allActionPermissions)
|
||||
await flushPromises()
|
||||
|
||||
const moreButton = wrapper.get('[data-row-id="5100010"] [aria-label="5100010的更多操作"]')
|
||||
expect(moreButton.attributes('title')).toBe('5100010的更多操作')
|
||||
expect(moreButton.attributes('aria-label')).not.toMatch(/undefined|null/)
|
||||
})
|
||||
|
||||
it('gives more-action buttons distinct accessible names across rows', async () => {
|
||||
vi.mocked(listPlatformPermissions).mockResolvedValue([
|
||||
{ ...permission, builtIn: false },
|
||||
{
|
||||
...permission,
|
||||
id: 5100020,
|
||||
permissionCode: 'platform:role:list',
|
||||
permissionName: '角色管理',
|
||||
routePath: '/roles',
|
||||
builtIn: false,
|
||||
},
|
||||
])
|
||||
|
||||
const wrapper = mountServerView(allActionPermissions)
|
||||
await flushPromises()
|
||||
|
||||
const labels = wrapper.findAll('[aria-label$="的更多操作"]').map((button) => button.attributes('aria-label'))
|
||||
expect(labels).toEqual(['企业客户的更多操作', '角色管理的更多操作'])
|
||||
expect(new Set(labels).size).toBe(labels.length)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
|
||||
import { Braces, FolderTree, Pencil, Plus, Power, ShieldCheck, Trash2 } from '@lucide/vue'
|
||||
import { Braces, FolderTree, MoreHorizontal, Pencil, Plus, Power, ShieldCheck, Trash2 } from '@lucide/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'
|
||||
@@ -231,6 +231,37 @@ async function removePermission(row: Permission) {
|
||||
}
|
||||
function typeClass(type: PermissionType) { return { '目录': 'directory', '菜单': 'menu', '按钮': 'action', '接口': 'api' }[type] }
|
||||
|
||||
function canAddChild(row: Permission) {
|
||||
return canCreate.value && row.type === '菜单'
|
||||
}
|
||||
function canEditRow(row: Permission) {
|
||||
return canEdit.value && !row.builtIn
|
||||
}
|
||||
function canChangeStatusRow(row: Permission) {
|
||||
return canChangeStatus.value && !row.builtIn
|
||||
}
|
||||
function canDeleteRow(row: Permission) {
|
||||
return canDelete.value && !row.builtIn
|
||||
}
|
||||
function permissionActionCount(row: Permission) {
|
||||
return Number(canAddChild(row))
|
||||
+ Number(canEditRow(row))
|
||||
+ Number(canChangeStatusRow(row))
|
||||
+ Number(canDeleteRow(row))
|
||||
}
|
||||
function showDeleteDirect(row: Permission) {
|
||||
return canDeleteRow(row) && permissionActionCount(row) <= 3
|
||||
}
|
||||
function showDeleteMore(row: Permission) {
|
||||
return canDeleteRow(row) && permissionActionCount(row) > 3
|
||||
}
|
||||
function moreActionsLabel(row: Permission) {
|
||||
const resourceLabel = [row.name, row.code, row.id]
|
||||
.find((value) => typeof value === 'string' && value.trim())
|
||||
?.trim() || '当前权限'
|
||||
return `${resourceLabel}的更多操作`
|
||||
}
|
||||
|
||||
onMounted(loadPermissions)
|
||||
</script>
|
||||
|
||||
@@ -246,8 +277,22 @@ onMounted(loadPermissions)
|
||||
|
||||
<section v-if="!loadError" class="surface-panel table-panel">
|
||||
<DataTable :key="tableRenderKey" v-model:page="page" v-model:page-size="pageSize" :data="pagedTreeRows" :columns="permissionColumns" preference-key="permissions-table" :loading="loading" :total="treeRows.length" row-key="id" :expand-row-keys="expandedRowKeys" :tree-props="{ children: 'children' }" empty-text="没有符合条件的权限资源">
|
||||
<template #toolbar-actions><div class="permission-tree-heading-actions"><div class="permission-tree-tools"><div class="legend-list"><span><i class="is-menu" />目录 / 菜单</span><span><i class="is-action" />操作</span><span><i class="is-api" />接口预留</span></div><div class="tree-view-actions"><el-button link type="primary" @click="expandAllRows">展开全部</el-button><el-button link @click="collapseActionRows">收起操作权限</el-button></div></div><el-button v-if="canCreate" type="primary" @click="openCreate(null)"><Plus :size="16" />新增权限</el-button></div></template>
|
||||
<template #actions="{ row }"><div v-if="canCreate || canEdit || canChangeStatus || canDelete" class="table-actions"><el-button v-if="canCreate && (row as Permission).type === '菜单'" link type="primary" @click="openCreate((row as Permission).id)"><Plus :size="15" />添加下级</el-button><el-button v-if="canEdit" link type="primary" :disabled="(row as Permission).builtIn || rowActionId === (row as Permission).id" @click="openEdit(row as Permission)"><Pencil :size="15" />编辑</el-button><el-button v-if="canChangeStatus" link :type="(row as Permission).status === '启用' ? 'danger' : 'success'" :loading="rowActionId === (row as Permission).id" :disabled="(row as Permission).builtIn" @click="toggleStatus(row as Permission)"><Power :size="15" />{{ (row as Permission).status === '启用' ? '停用' : '启用' }}</el-button><el-button v-if="canDelete" link type="danger" :disabled="(row as Permission).builtIn || rowActionId === (row as Permission).id" @click="removePermission(row as Permission)"><Trash2 :size="15" />删除</el-button><span v-if="(row as Permission).builtIn" class="secondary-text">系统内置</span></div><span v-else class="secondary-text">仅查看</span></template>
|
||||
<template #toolbar-start><div class="permission-tree-tools"><div class="legend-list"><span><i class="is-menu" />目录 / 菜单</span><span><i class="is-action" />操作</span><span><i class="is-api" />接口预留</span></div><div class="tree-view-actions"><el-button link type="primary" @click="expandAllRows">展开全部</el-button><el-button link @click="collapseActionRows">收起操作权限</el-button></div></div></template>
|
||||
<template #toolbar-actions><el-button v-if="canCreate" type="primary" @click="openCreate(null)"><Plus :size="16" />新增权限</el-button></template>
|
||||
<template #actions="{ row }">
|
||||
<div v-if="permissionActionCount(row as Permission) > 0 || (row as Permission).builtIn" class="table-actions">
|
||||
<el-button v-if="canAddChild(row as Permission)" link type="primary" @click="openCreate((row as Permission).id)"><Plus :size="15" />添加下级</el-button>
|
||||
<el-button v-if="canEditRow(row as Permission)" link type="primary" :disabled="rowActionId === (row as Permission).id" @click="openEdit(row as Permission)"><Pencil :size="15" />编辑</el-button>
|
||||
<el-button v-if="canChangeStatusRow(row as Permission)" link :type="(row as Permission).status === '启用' ? 'danger' : 'success'" :loading="rowActionId === (row as Permission).id" @click="toggleStatus(row as Permission)"><Power :size="15" />{{ (row as Permission).status === '启用' ? '停用' : '启用' }}</el-button>
|
||||
<el-button v-if="showDeleteDirect(row as Permission)" link type="danger" :disabled="rowActionId === (row as Permission).id" @click="removePermission(row as Permission)"><Trash2 :size="15" />删除</el-button>
|
||||
<el-dropdown v-if="showDeleteMore(row as Permission)" trigger="click" @command="(command: string) => command === 'delete' && removePermission(row as Permission)">
|
||||
<el-button link :aria-label="moreActionsLabel(row as Permission)" :title="moreActionsLabel(row as Permission)"><MoreHorizontal :size="16" /></el-button>
|
||||
<template #dropdown><el-dropdown-menu><el-dropdown-item command="delete" :disabled="rowActionId === (row as Permission).id" class="danger-menu-item"><Trash2 :size="15" />删除</el-dropdown-item></el-dropdown-menu></template>
|
||||
</el-dropdown>
|
||||
<span v-if="(row as Permission).builtIn" class="secondary-text">系统内置</span>
|
||||
</div>
|
||||
<span v-else class="secondary-text">仅查看</span>
|
||||
</template>
|
||||
<template #name="{ row }"><div class="permission-name"><span :class="`is-${typeClass((row as Permission).type)}`"><FolderTree v-if="(row as Permission).type === '目录' || (row as Permission).type === '菜单'" :size="16" /><ShieldCheck v-else-if="(row as Permission).type === '按钮'" :size="16" /><Braces v-else :size="16" /></span><div><strong>{{ (row as Permission).name }}</strong><small v-if="(row as PermissionTreeNode).children?.length">{{ permissionSummary(row as PermissionTreeNode) }}</small></div></div></template>
|
||||
<template #code="{ row }"><code class="code-value">{{ (row as Permission).code }}</code></template>
|
||||
<template #endpoint="{ row }"><div class="endpoint-cell"><span v-if="(row as Permission).method !== '-'">{{ (row as Permission).method }}</span><code>{{ (row as Permission).path || '-' }}</code></div></template>
|
||||
@@ -277,16 +322,14 @@ onMounted(loadPermissions)
|
||||
|
||||
<style scoped>
|
||||
.permission-tree-tools { display: flex; align-items: center; gap: 16px; }
|
||||
.permission-tree-heading-actions { display: flex; align-items: center; gap: 12px; }
|
||||
.tree-view-actions { display: flex; align-items: center; gap: 4px; white-space: nowrap; }
|
||||
.tree-view-actions .el-button + .el-button { margin-left: 0; }
|
||||
.permission-name > div { display: flex; min-width: 0; flex-direction: column; gap: 2px; }
|
||||
.permission-name small { color: var(--cc-muted); font-size: 11px; line-height: 1.35; }
|
||||
:deep(.danger-menu-item:not(.is-disabled)) { color: var(--el-color-danger); }
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.permission-tree-heading-actions { width: 100%; align-items: flex-start; flex-direction: column; gap: 8px; }
|
||||
.permission-tree-tools { width: 100%; align-items: flex-start; flex-direction: column; gap: 8px; }
|
||||
.permission-tree-heading-actions > .el-button { min-height: 40px; }
|
||||
}
|
||||
|
||||
@media (max-width: 420px) {
|
||||
|
||||
@@ -5,13 +5,13 @@ export const iamRoutes = [
|
||||
path: 'system/users',
|
||||
name: 'users',
|
||||
component: () => import('@/modules/iam/pages/InternalUsersView.vue'),
|
||||
meta: { title: '用户管理', description: '统一管理平台和企业租户用户、状态及角色', permission: 'platform:user:list' },
|
||||
meta: { title: '用户管理', description: '统一管理平台内部用户、状态及角色', permission: 'platform:user:list' },
|
||||
},
|
||||
{
|
||||
path: 'system/roles',
|
||||
name: 'roles',
|
||||
component: () => import('@/modules/iam/pages/RolesView.vue'),
|
||||
meta: { title: '角色管理', description: '维护平台角色并配置菜单及操作权限', permission: 'platform:role:list' },
|
||||
meta: { title: '角色权限', description: '维护平台角色并配置菜单及操作权限', permission: 'platform:role:list' },
|
||||
},
|
||||
{
|
||||
path: 'system/permissions',
|
||||
|
||||
Reference in New Issue
Block a user