14 changed files with 621 additions and 0 deletions
@ -0,0 +1,36 @@ |
|||
{ |
|||
"name": "@abp/permission", |
|||
"version": "8.3.2", |
|||
"homepage": "https://github.com/colinin/abp-next-admin", |
|||
"bugs": "https://github.com/colinin/abp-next-admin/issues", |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "git+https://github.com/colinin/abp-next-admin.git", |
|||
"directory": "packages/@abp/permission" |
|||
}, |
|||
"license": "MIT", |
|||
"type": "module", |
|||
"sideEffects": [ |
|||
"**/*.css" |
|||
], |
|||
"exports": { |
|||
".": { |
|||
"types": "./src/index.ts", |
|||
"default": "./src/index.ts" |
|||
} |
|||
}, |
|||
"dependencies": { |
|||
"@abp/core": "workspace:*", |
|||
"@abp/request": "workspace:*", |
|||
"@abp/ui": "workspace:*", |
|||
"@ant-design/icons-vue": "catalog:", |
|||
"@vben/access": "workspace:*", |
|||
"@vben/common-ui": "workspace:*", |
|||
"@vben/hooks": "workspace:*", |
|||
"@vben/icons": "workspace:*", |
|||
"@vben/layouts": "workspace:*", |
|||
"@vben/locales": "workspace:*", |
|||
"ant-design-vue": "catalog:", |
|||
"vue": "catalog:*" |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
import type { |
|||
PermissionProvider, |
|||
PermissionResultDto, |
|||
PermissionsUpdateDto, |
|||
} from '../types/permissions'; |
|||
|
|||
import { requestClient } from '@abp/request'; |
|||
|
|||
/** |
|||
* 查询权限 |
|||
* @param provider |
|||
* @returns 权限实体数据传输对象 |
|||
*/ |
|||
export function getApi( |
|||
provider: PermissionProvider, |
|||
): Promise<PermissionResultDto> { |
|||
return requestClient.get<PermissionResultDto>( |
|||
`/api/permission-management/permissions`, |
|||
{ |
|||
params: provider, |
|||
}, |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* 更新权限 |
|||
* @param provider |
|||
* @param input |
|||
*/ |
|||
export function updateApi( |
|||
provider: PermissionProvider, |
|||
input: PermissionsUpdateDto, |
|||
): Promise<void> { |
|||
return requestClient.put(`/api/permission-management/permissions`, input, { |
|||
params: provider, |
|||
}); |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
<script setup lang="ts"></script> |
|||
|
|||
<template> |
|||
<div></div> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,7 @@ |
|||
<script setup lang="ts"></script> |
|||
|
|||
<template> |
|||
<div></div> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,7 @@ |
|||
<script setup lang="ts"></script> |
|||
|
|||
<template> |
|||
<div></div> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,7 @@ |
|||
<script setup lang="ts"></script> |
|||
|
|||
<template> |
|||
<div></div> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,3 @@ |
|||
export { default as PermissionGroupDefinitionTable } from './definitions/groups/PermissionGroupDefinitionTable.vue'; |
|||
export { default as PermissionDefinitionTable } from './definitions/permissions/PermissionDefinitionTable.vue'; |
|||
export { default as PermissionModal } from './permissions/PermissionModal.vue'; |
|||
@ -0,0 +1,301 @@ |
|||
<script setup lang="ts"> |
|||
import type { CheckboxChangeEvent } from 'ant-design-vue/es/checkbox/interface'; |
|||
import type { |
|||
DataNode, |
|||
EventDataNode, |
|||
} from 'ant-design-vue/es/vc-tree/interface'; |
|||
import type { CheckInfo } from 'ant-design-vue/es/vc-tree/props'; |
|||
|
|||
import type { PermissionTree } from '../../types/permissions'; |
|||
|
|||
import { computed, ref } from 'vue'; |
|||
|
|||
import { useVbenModal } from '@vben/common-ui'; |
|||
import { $t } from '@vben/locales'; |
|||
|
|||
import { Card, Checkbox, Divider, message, Tabs, Tree } from 'ant-design-vue'; |
|||
|
|||
import { getApi, updateApi } from '../../api/permissions'; |
|||
import { |
|||
generatePermissionTree, |
|||
getGrantedPermissionKeys, |
|||
getGrantPermissionCount, |
|||
getGrantPermissionsCount, |
|||
getParentList, |
|||
getPermissionCount, |
|||
getPermissionsCount, |
|||
toPermissionList, |
|||
} from '../../utils'; |
|||
|
|||
defineOptions({ |
|||
name: 'PermissionModal', |
|||
}); |
|||
|
|||
const TabPane = Tabs.TabPane; |
|||
|
|||
interface ModalState { |
|||
displayName?: string; |
|||
providerKey?: string; |
|||
providerName: string; |
|||
readonly?: boolean; |
|||
} |
|||
|
|||
const modelState = ref<ModalState>(); |
|||
const expandNodeKeys = ref<string[]>([]); |
|||
const checkedNodeKeys = ref<string[]>([]); |
|||
const permissionTree = ref<PermissionTree[]>([]); |
|||
|
|||
const getPermissionTab = computed(() => { |
|||
return (tree: PermissionTree) => { |
|||
const grantCount = getGrantPermissionCount(tree); |
|||
return `${tree.displayName} (${grantCount})`; |
|||
}; |
|||
}); |
|||
|
|||
const getPermissionState = computed(() => { |
|||
const treeList = permissionTree.value; |
|||
const grantCount = getGrantPermissionsCount(treeList); |
|||
const permissionCount = getPermissionsCount(treeList); |
|||
return { |
|||
checked: grantCount === permissionCount, |
|||
indeterminate: grantCount > 0 && grantCount < permissionCount, |
|||
}; |
|||
}); |
|||
|
|||
const getPermissionNodeState = computed(() => { |
|||
return (tree: PermissionTree) => { |
|||
const grantCount = getGrantPermissionCount(tree); |
|||
const permissionCount = getPermissionCount(tree); |
|||
return { |
|||
checked: grantCount === permissionCount, |
|||
indeterminate: grantCount > 0 && grantCount < permissionCount, |
|||
}; |
|||
}; |
|||
}); |
|||
|
|||
const [Modal, modalApi] = useVbenModal({ |
|||
class: 'w-1/2', |
|||
closeOnClickModal: false, |
|||
closeOnPressEscape: false, |
|||
draggable: true, |
|||
fullscreenButton: false, |
|||
onCancel() { |
|||
modalApi.close(); |
|||
}, |
|||
onConfirm: async () => { |
|||
const permissions = toPermissionList(permissionTree.value); |
|||
try { |
|||
modalApi.setState({ |
|||
closable: false, |
|||
confirmLoading: true, |
|||
}); |
|||
await updateApi( |
|||
{ |
|||
providerKey: modelState.value!.providerKey, |
|||
providerName: modelState.value!.providerName, |
|||
}, |
|||
{ |
|||
permissions, |
|||
}, |
|||
); |
|||
message.success($t('AbpUi.SavedSuccessfully')); |
|||
modalApi.close(); |
|||
} finally { |
|||
modalApi.setState({ |
|||
closable: true, |
|||
confirmLoading: false, |
|||
}); |
|||
} |
|||
}, |
|||
async onOpenChange(isOpen: boolean) { |
|||
permissionTree.value = []; |
|||
modelState.value = undefined; |
|||
if (isOpen) { |
|||
const state = modalApi.getData<ModalState>(); |
|||
modelState.value = state; |
|||
const dto = await getApi({ |
|||
providerKey: state.providerKey, |
|||
providerName: state.providerName, |
|||
}); |
|||
modalApi.setState({ |
|||
title: `${$t('AbpPermissionManagement.Permissions')} - ${state.displayName ?? dto.entityDisplayName}`, |
|||
}); |
|||
permissionTree.value = generatePermissionTree(dto.groups); |
|||
checkedNodeKeys.value = getGrantedPermissionKeys(permissionTree.value); |
|||
} |
|||
}, |
|||
title: $t('AbpPermissionManagement.Permissions'), |
|||
}); |
|||
|
|||
/** 全选所有节点权限 */ |
|||
function onCheckAll(e: CheckboxChangeEvent) { |
|||
checkedNodeKeys.value = []; |
|||
permissionTree.value.forEach((current) => { |
|||
const children = getChildren(current.children); |
|||
children.forEach((permission) => { |
|||
permission.isGranted = e.target.checked; |
|||
if (e.target.checked) { |
|||
checkedNodeKeys.value.push(permission.name); |
|||
} |
|||
}); |
|||
current.isGranted = e.target.checked; |
|||
}); |
|||
} |
|||
|
|||
/** 全选当前节点权限 */ |
|||
function onCheckNodeAll(e: CheckboxChangeEvent, permission: PermissionTree) { |
|||
const children = getChildren(permission.children ?? []); |
|||
children.forEach((permission) => { |
|||
permission.isGranted = e.target.checked; |
|||
}); |
|||
const childKeys = children.map((node) => node.name); |
|||
checkedNodeKeys.value = e.target.checked |
|||
? [...checkedNodeKeys.value, ...childKeys] |
|||
: checkedNodeKeys.value.filter((key) => !childKeys.includes(key)); |
|||
permission.isGranted = e.target.checked; |
|||
} |
|||
|
|||
function onSelectNode( |
|||
_: any, |
|||
info: { |
|||
node: EventDataNode; |
|||
selectedNodes: DataNode[]; |
|||
}, |
|||
) { |
|||
const nodeKey = String(info.node.key); |
|||
const index = expandNodeKeys.value.indexOf(nodeKey); |
|||
expandNodeKeys.value = |
|||
index === -1 |
|||
? [...expandNodeKeys.value, nodeKey] |
|||
: expandNodeKeys.value.filter((key) => key !== nodeKey); |
|||
} |
|||
|
|||
function onCheckNode(permission: PermissionTree, _keys: any, info: CheckInfo) { |
|||
const nodeKey = String(info.node.key); |
|||
const index = checkedNodeKeys.value.indexOf(nodeKey); |
|||
checkedNodeKeys.value = |
|||
index === -1 |
|||
? [...checkedNodeKeys.value, nodeKey] |
|||
: checkedNodeKeys.value.filter((key) => key !== nodeKey); |
|||
const currentPermission = info.node.dataRef as unknown as PermissionTree; |
|||
// 上级权限联动 |
|||
checkParentGrant(permission, currentPermission, info.checked); |
|||
// 下级权限联动 |
|||
checkChildrenGrant(currentPermission, info.checked); |
|||
// 自身授权 |
|||
currentPermission.isGranted = info.checked; |
|||
} |
|||
|
|||
function checkChildrenGrant(current: PermissionTree, isGranted: boolean) { |
|||
const children = getChildren(current.children); |
|||
// 应取消子权限的所有授权 |
|||
if (!isGranted) { |
|||
const childKeys: string[] = []; |
|||
children.forEach((node) => { |
|||
!isGranted && (node.isGranted = false); |
|||
childKeys.push(node.name); |
|||
}); |
|||
checkedNodeKeys.value = checkedNodeKeys.value.filter( |
|||
(key) => !childKeys.includes(key), |
|||
); |
|||
} |
|||
} |
|||
|
|||
function checkParentGrant( |
|||
root: PermissionTree, |
|||
current: PermissionTree, |
|||
isGranted: boolean, |
|||
) { |
|||
if (!isGranted || !current.parentName) { |
|||
return; |
|||
} |
|||
const parentNodes = getParentList(root.children, current.parentName); |
|||
if (parentNodes) { |
|||
const parentKeys: string[] = []; |
|||
parentNodes.forEach((node) => { |
|||
node.isGranted = true; |
|||
parentKeys.push(node.name); |
|||
if (!checkedNodeKeys.value.includes(node.name)) { |
|||
parentKeys.push(node.name); |
|||
} |
|||
}); |
|||
checkedNodeKeys.value = [...checkedNodeKeys.value, ...parentKeys]; |
|||
} |
|||
} |
|||
|
|||
function getChildren(permissions: PermissionTree[]): PermissionTree[] { |
|||
const children: PermissionTree[] = []; |
|||
permissions.forEach((permission) => { |
|||
children.push(permission); |
|||
permission.children && children.push(...getChildren(permission.children)); |
|||
}); |
|||
return children; |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<Modal> |
|||
<div class="flex flex-col content-center justify-center"> |
|||
<div> |
|||
<Checkbox |
|||
:disabled="modelState?.readonly" |
|||
@change="onCheckAll" |
|||
v-bind="getPermissionState" |
|||
> |
|||
{{ $t('AbpPermissionManagement.SelectAllInAllTabs') }} |
|||
</Checkbox> |
|||
</div> |
|||
<Divider /> |
|||
<Tabs tab-position="left" type="card"> |
|||
<template v-for="permission in permissionTree" :key="permission.name"> |
|||
<TabPane |
|||
:tab="getPermissionTab(permission)" |
|||
:tab-key="permission.name" |
|||
> |
|||
<Card :bordered="false" :title="permission.displayName"> |
|||
<div class="flex flex-col"> |
|||
<Checkbox |
|||
:disabled="modelState?.readonly" |
|||
v-bind="getPermissionNodeState(permission)" |
|||
@change="(e) => onCheckNodeAll(e, permission)" |
|||
> |
|||
{{ $t('AbpPermissionManagement.SelectAllInThisTab') }} |
|||
</Checkbox> |
|||
<Divider /> |
|||
<Tree |
|||
:check-strictly="true" |
|||
:checkable="true" |
|||
:checked-keys="checkedNodeKeys" |
|||
:disabled="modelState?.readonly" |
|||
:expanded-keys="expandNodeKeys" |
|||
:field-names="{ |
|||
key: 'name', |
|||
title: 'displayName', |
|||
children: 'children', |
|||
}" |
|||
:tree-data="permission.children" |
|||
@check="(keys, info) => onCheckNode(permission, keys, info)" |
|||
@select="onSelectNode" |
|||
/> |
|||
</div> |
|||
</Card> |
|||
</TabPane> |
|||
</template> |
|||
</Tabs> |
|||
</div> |
|||
</Modal> |
|||
</template> |
|||
|
|||
<style lang="scss" scoped> |
|||
:deep(.ant-tabs) { |
|||
max-height: 34rem; |
|||
|
|||
.ant-tabs-nav { |
|||
max-width: 14rem; |
|||
} |
|||
|
|||
.ant-tabs-content-holder { |
|||
overflow: hidden auto !important; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,2 @@ |
|||
export * from './components'; |
|||
export * from './types'; |
|||
@ -0,0 +1 @@ |
|||
export * from './permissions'; |
|||
@ -0,0 +1,60 @@ |
|||
interface PermissionProvider { |
|||
providerKey?: string; |
|||
providerName: string; |
|||
} |
|||
|
|||
interface PermissionDto { |
|||
allowedProviders: string[]; |
|||
displayName: string; |
|||
grantedProviders: PermissionProvider[]; |
|||
isGranted: boolean; |
|||
name: string; |
|||
parentName?: string; |
|||
} |
|||
|
|||
interface PermissionGroupDto { |
|||
displayName: string; |
|||
name: string; |
|||
permissions: PermissionDto[]; |
|||
} |
|||
|
|||
interface PermissionUpdateDto { |
|||
/** 是否授权 */ |
|||
isGranted: boolean; |
|||
/** 权限名称 */ |
|||
name: string; |
|||
} |
|||
|
|||
interface PermissionsUpdateDto { |
|||
permissions: PermissionUpdateDto[]; |
|||
} |
|||
|
|||
interface PermissionResultDto { |
|||
entityDisplayName: string; |
|||
groups: PermissionGroupDto[]; |
|||
} |
|||
|
|||
interface PermissionTree { |
|||
/** 子节点 */ |
|||
children: PermissionTree[]; |
|||
/** 是否禁用 */ |
|||
disabled: boolean; |
|||
/** 显示名称 */ |
|||
displayName: string; |
|||
/** 是否授权 */ |
|||
isGranted?: boolean; |
|||
isRoot: boolean; |
|||
/** 权限标识 */ |
|||
name: string; |
|||
/** 父节点 */ |
|||
parentName?: string; |
|||
} |
|||
|
|||
export type { |
|||
PermissionDto, |
|||
PermissionGroupDto, |
|||
PermissionProvider, |
|||
PermissionResultDto, |
|||
PermissionsUpdateDto, |
|||
PermissionTree, |
|||
}; |
|||
@ -0,0 +1,143 @@ |
|||
import type { |
|||
PermissionDto, |
|||
PermissionGroupDto, |
|||
PermissionTree, |
|||
} from '../types/permissions'; |
|||
|
|||
import { listToTree } from '@abp/core'; |
|||
|
|||
export function generatePermissionTree( |
|||
permissionGroups: PermissionGroupDto[], |
|||
): PermissionTree[] { |
|||
const trees: PermissionTree[] = []; |
|||
permissionGroups.forEach((g) => { |
|||
const tree: PermissionTree = { |
|||
disabled: false, |
|||
displayName: g.displayName, |
|||
isRoot: true, |
|||
name: g.name, |
|||
parentName: g.name, |
|||
children: [], |
|||
}; |
|||
tree.children = listToTree(g.permissions, { |
|||
id: 'name', |
|||
pid: 'parentName', |
|||
}); |
|||
trees.push(tree); |
|||
}); |
|||
return trees; |
|||
} |
|||
|
|||
export function findNode( |
|||
children: PermissionTree[], |
|||
key: string, |
|||
): PermissionTree | undefined { |
|||
let findC: PermissionTree | undefined; |
|||
for (const child of children) { |
|||
if (child.name === key) { |
|||
findC = child; |
|||
return findC; |
|||
} |
|||
findC = findNode(child.children, key); |
|||
if (findC) { |
|||
return findC; |
|||
} |
|||
} |
|||
return findC; |
|||
} |
|||
|
|||
export function getPermissionsCount(children: PermissionTree[]): number { |
|||
let count = 0; |
|||
children.forEach((c) => { |
|||
count += getPermissionCount(c); |
|||
}); |
|||
return count; |
|||
} |
|||
|
|||
export function getPermissionCount(tree: PermissionTree): number { |
|||
let count = tree.children.length; |
|||
tree.children.forEach((c) => { |
|||
count += getPermissionCount(c); |
|||
}); |
|||
return count; |
|||
} |
|||
|
|||
export function getGrantPermissionsCount(children: PermissionTree[]): number { |
|||
let count = 0; |
|||
children.forEach((c) => { |
|||
count += getGrantPermissionCount(c); |
|||
}); |
|||
return count; |
|||
} |
|||
|
|||
export function getGrantPermissionCount(tree: PermissionTree): number { |
|||
return getGrantedPermissionKeys(tree.children).length; |
|||
} |
|||
|
|||
export function getGrantedPermissionKeys(children: PermissionTree[]): string[] { |
|||
const keys: string[] = []; |
|||
children.forEach((c) => { |
|||
if (c.isGranted === true) { |
|||
keys.push(c.name); |
|||
} |
|||
keys.push(...getGrantedPermissionKeys(c.children)); |
|||
}); |
|||
return keys; |
|||
} |
|||
|
|||
export function getParentList( |
|||
children: PermissionTree[], |
|||
name: string, |
|||
): PermissionTree[] | undefined { |
|||
for (const child of children) { |
|||
if (child.name === name) { |
|||
return [child]; |
|||
} |
|||
if (child.children) { |
|||
const node = getParentList(child.children, name); |
|||
if (node) { |
|||
return [...node, child]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
export function toPermissionList(treeList: PermissionTree[]) { |
|||
const permissions: PermissionDto[] = []; |
|||
for (const element of treeList) { |
|||
if (!element.isRoot && element.isGranted !== undefined) { |
|||
permissions.push({ |
|||
allowedProviders: [], |
|||
displayName: element.displayName, |
|||
grantedProviders: [], |
|||
isGranted: element.isGranted, |
|||
name: element.name, |
|||
}); |
|||
} |
|||
permissions.push(...toPermissionList(element.children)); |
|||
} |
|||
return permissions; |
|||
} |
|||
|
|||
export function updateParentGrant( |
|||
tree: PermissionTree, |
|||
name: string, |
|||
grant: boolean, |
|||
) { |
|||
const parentList = getParentList(tree.children, name); |
|||
if (parentList && Array.isArray(parentList)) { |
|||
for (const element of parentList) { |
|||
element.isGranted = grant; |
|||
} |
|||
} |
|||
} |
|||
|
|||
export function updateChildrenGrant( |
|||
children: PermissionTree[], |
|||
grant: boolean, |
|||
) { |
|||
for (const child of children) { |
|||
child.isGranted = grant; |
|||
updateChildrenGrant(child.children, grant); |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
{ |
|||
"$schema": "https://json.schemastore.org/tsconfig", |
|||
"extends": "@vben/tsconfig/web.json", |
|||
"include": ["src"], |
|||
"exclude": ["node_modules"] |
|||
} |
|||
Loading…
Reference in new issue