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