This commit is contained in:
2026-09-11 11:15:04 +08:00
parent f702a819fa
commit 23e991722c
8 changed files with 115 additions and 29 deletions
+4 -2
View File
@@ -93,10 +93,12 @@ describe('RolesView', () => {
vi.mocked(createPlatformRole).mockReset()
})
it('keeps directory, menu, and action permissions independently checkable', () => {
it('links menu groups and permissions so partial selections use the half-checked state', () => {
const wrapper = mountView()
expect(wrapper.get('[data-testid="permission-tree"]').attributes('data-check-strictly')).toBe('true')
expect(wrapper.get('[data-testid="permission-tree"]').attributes('data-check-strictly')).toBe('false')
expect(rolesSource).not.toContain('check-strictly')
expect(rolesSource).not.toContain("disabled: 'disabled'")
})
it('shows the role code in the default list layout', () => {
+12 -6
View File
@@ -24,8 +24,9 @@ import ListPageLayout from '@/shared/components/ListPageLayout.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import { useUnsavedWorkRegistration } from '@/core/unsaved-work/useUnsavedWorkRegistration'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { adminNavigation } from '@/core/navigation/adminNavigation'
import type { Permission, Role } from '@/modules/iam/types'
import { buildPermissionTree, summarizePermissionSelection } from '@/modules/iam/utils/permissionTree'
import { buildNavigationPermissionTree, summarizePermissionSelection } from '@/modules/iam/utils/permissionTree'
import { mapPlatformPermission } from '@/modules/iam/utils/platformPermission'
interface TreeController {
@@ -88,7 +89,9 @@ const roleColumns: DataTableColumn[] = [
{ key: 'status', label: '状态', width: 88, slotName: 'status' },
]
const availablePermissions = computed(() => serverPermissions.value)
const permissionTree = computed(() => buildPermissionTree(availablePermissions.value))
const permissionTree = computed(() => buildNavigationPermissionTree(availablePermissions.value, adminNavigation))
const permissionGroupIds = computed(() => permissionTree.value.map((node) => node.id))
const assignablePermissionIds = computed(() => new Set(availablePermissions.value.map((permission) => permission.id)))
function applyFilters() {
appliedFilters.value = { ...filters.value }
@@ -144,8 +147,8 @@ function openEdit(row: Role) {
}
function selectedPermissionIds() {
const checked = treeRef.value?.getCheckedKeys(false).map(String) || []
const halfChecked = treeRef.value?.getHalfCheckedKeys().map(String) || []
const checked = treeRef.value?.getCheckedKeys(false).map(String).filter((id) => assignablePermissionIds.value.has(id)) || []
const halfChecked = treeRef.value?.getHalfCheckedKeys().map(String).filter((id) => assignablePermissionIds.value.has(id)) || []
return [...new Set([...checked, ...halfChecked])]
}
@@ -258,7 +261,7 @@ onMounted(loadRoles)
<el-form-item label="角色编码" prop="code" required><el-input v-model="form.code" :disabled="Boolean(form.id)" placeholder="如:ORDER_OPERATOR" maxlength="64" /></el-form-item>
<el-form-item label="角色说明"><el-input v-model="form.description" :disabled="Boolean(form.id) && !canEdit" type="textarea" :rows="3" placeholder="说明该角色的工作范围" maxlength="200" show-word-limit /></el-form-item>
</FormSection>
<FormSection v-if="canGrant" title="菜单与操作权限" :description="`保存时按后端契约全量替换当前角色权限;当前 ${form.permissionIds.length} 项授权。`" class="role-editor-permissions"><el-tree ref="treeRef" class="permission-tree" :data="permissionTree" node-key="id" show-checkbox check-strictly default-expand-all :props="{ label: 'name', children: 'children' }" @check="syncPermissionIds"><template #default="{ data }"><div class="permission-node"><span>{{ data.name }}</span><code>{{ data.code }}</code></div></template></el-tree></FormSection>
<FormSection v-if="canGrant" title="菜单与操作权限" :description="`按当前后台菜单分组;勾选上级可全选下级,部分选中时显示半选,当前 ${form.permissionIds.length} 项授权。`" class="role-editor-permissions"><el-tree ref="treeRef" class="permission-tree" :data="permissionTree" node-key="id" show-checkbox :default-expanded-keys="permissionGroupIds" :props="{ label: 'name', children: 'children' }" @check="syncPermissionIds"><template #default="{ data }"><div class="permission-node" :class="{ 'is-navigation-group': !data.permissionId }"><span>{{ data.name }}</span><code v-if="data.code">{{ data.code }}</code></div></template></el-tree></FormSection>
</div>
</el-form>
</AppDialog>
@@ -268,7 +271,10 @@ onMounted(loadRoles)
<style scoped>
.role-editor-layout { display: grid; grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.3fr); gap: 20px; }
.role-editor-permissions { min-width: 0; padding-left: 20px; border-left: 1px solid var(--cc-border); }
.permission-tree { max-height: 380px; overflow-y: auto; }
.permission-tree { max-height: 380px; overflow-x: hidden; overflow-y: auto; }
.permission-node span { min-width: 0; }
.permission-node code { max-width: 58%; white-space: normal; text-align: right; }
.permission-node.is-navigation-group span { font-weight: 650; }
:global(.role-editor-dialog) { max-height: min(760px, calc(100vh - 32px)); display: flex; flex-direction: column; }
:global(.role-editor-dialog .el-dialog__body) { min-height: 0; overflow-y: auto; }
:global(.role-editor-dialog .el-dialog__footer) { flex: 0 0 auto; }