36 changed files with 1380 additions and 25 deletions
@ -0,0 +1,8 @@ |
|||
{ |
|||
"hash": "62932afa", |
|||
"configHash": "fdef0d8d", |
|||
"lockfileHash": "5ba0e736", |
|||
"browserHash": "cfcea6c4", |
|||
"optimized": {}, |
|||
"chunks": {} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
{ |
|||
"type": "module" |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
<script lang="ts" setup> |
|||
import { Page } from '@vben/common-ui'; |
|||
|
|||
import { OrganizationUnitPage } from '@abp/identity'; |
|||
|
|||
defineOptions({ |
|||
name: 'IdentityOrganizationUnits', |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<Page> |
|||
<OrganizationUnitPage /> |
|||
</Page> |
|||
</template> |
|||
@ -1 +1,2 @@ |
|||
export * from './date'; |
|||
export * from './tree'; |
|||
|
|||
@ -0,0 +1,37 @@ |
|||
interface TreeHelperConfig { |
|||
children: string; |
|||
id: string; |
|||
pid: string; |
|||
} |
|||
const DEFAULT_CONFIG: TreeHelperConfig = { |
|||
id: 'id', |
|||
pid: 'pid', |
|||
children: 'children', |
|||
}; |
|||
|
|||
const getConfig = (config: Partial<TreeHelperConfig>) => |
|||
Object.assign({}, DEFAULT_CONFIG, config); |
|||
|
|||
// tree from list
|
|||
export function listToTree<T = any>( |
|||
list: any[], |
|||
config: Partial<TreeHelperConfig> = {}, |
|||
): T[] { |
|||
const conf = getConfig(config) as TreeHelperConfig; |
|||
const nodeMap = new Map(); |
|||
const result: T[] = []; |
|||
const { id, pid, children } = conf; |
|||
|
|||
for (const node of list) { |
|||
node[children] = node[children] || []; |
|||
nodeMap.set(node[id], node); |
|||
} |
|||
for (const node of list) { |
|||
const parent = nodeMap.get(node[pid]); |
|||
(parent ? parent[children] : result).push(node); |
|||
if (parent) { |
|||
parent.hasChildren = true; |
|||
} |
|||
} |
|||
return result; |
|||
} |
|||
@ -0,0 +1,227 @@ |
|||
import type { ListResultDto, PagedResultDto } from '@abp/core'; |
|||
|
|||
import type { IdentityRoleDto, IdentityUserDto } from '../types'; |
|||
import type { |
|||
GetIdentityRolesInput, |
|||
GetIdentityUsersInput, |
|||
GetOrganizationUnitPagedListInput, |
|||
GetUnaddedRoleListInput, |
|||
GetUnaddedUserListInput, |
|||
OrganizationUnitAddRoleDto, |
|||
OrganizationUnitAddUserDto, |
|||
OrganizationUnitCreateDto, |
|||
OrganizationUnitDto, |
|||
OrganizationUnitGetChildrenDto, |
|||
OrganizationUnitUpdateDto, |
|||
} from '../types/organization-units'; |
|||
|
|||
import { requestClient } from '@abp/request'; |
|||
|
|||
/** |
|||
* 新增组织机构 |
|||
* @param input 参数 |
|||
* @returns 组织机构实体数据传输对象 |
|||
*/ |
|||
export function createApi( |
|||
input: OrganizationUnitCreateDto, |
|||
): Promise<OrganizationUnitDto> { |
|||
return requestClient.post<OrganizationUnitDto>( |
|||
'/api/identity/organization-units', |
|||
input, |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* 删除组织机构 |
|||
* @param id 组织机构id |
|||
*/ |
|||
export function deleteApi(id: string): Promise<void> { |
|||
return requestClient.delete(`/api/identity/organization-units/${id}`); |
|||
} |
|||
|
|||
/** |
|||
* 查询组织机构 |
|||
* @param id 组织机构id |
|||
* @returns 组织机构实体数据传输对象 |
|||
*/ |
|||
export function getApi(id: string): Promise<OrganizationUnitDto> { |
|||
return requestClient.get<OrganizationUnitDto>( |
|||
`/api/identity/organization-units/${id}`, |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* 更新组织机构 |
|||
* @param id 组织机构id |
|||
* @returns 组织机构实体数据传输对象 |
|||
*/ |
|||
export function updateApi( |
|||
id: string, |
|||
input: OrganizationUnitUpdateDto, |
|||
): Promise<OrganizationUnitDto> { |
|||
return requestClient.put<OrganizationUnitDto>( |
|||
`/api/identity/organization-units/${id}`, |
|||
input, |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* 查询组织机构分页列表 |
|||
* @param input 过滤参数 |
|||
* @returns 组织机构实体数据传输对象分页列表 |
|||
*/ |
|||
export function getPagedListApi( |
|||
input?: GetOrganizationUnitPagedListInput, |
|||
): Promise<PagedResultDto<OrganizationUnitDto>> { |
|||
return requestClient.get<PagedResultDto<OrganizationUnitDto>>( |
|||
`/api/identity/organization-units`, |
|||
{ |
|||
params: input, |
|||
}, |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* 查询根组织机构列表 |
|||
* @returns 组织机构实体数据传输对象列表 |
|||
*/ |
|||
export function getRootListApi(): Promise<ListResultDto<OrganizationUnitDto>> { |
|||
return requestClient.get<ListResultDto<OrganizationUnitDto>>( |
|||
`/api/identity/organization-units/root-node`, |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* 查询组织机构列表 |
|||
* @returns 组织机构实体数据传输对象列表 |
|||
*/ |
|||
export function getAllListApi(): Promise<ListResultDto<OrganizationUnitDto>> { |
|||
return requestClient.get<ListResultDto<OrganizationUnitDto>>( |
|||
`/api/identity/organization-units/all`, |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* 查询下级组织机构列表 |
|||
* @param input 查询参数 |
|||
* @returns 组织机构实体数据传输对象列表 |
|||
*/ |
|||
export function getChildrenApi( |
|||
input: OrganizationUnitGetChildrenDto, |
|||
): Promise<ListResultDto<OrganizationUnitDto>> { |
|||
return requestClient.get<ListResultDto<OrganizationUnitDto>>( |
|||
`/api/identity/organization-units/find-children`, |
|||
{ |
|||
params: input, |
|||
}, |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* 查询组织机构用户列表 |
|||
* @param id 组织机构id |
|||
* @param input 查询过滤参数 |
|||
* @returns 用户实体数据传输对象分页列表 |
|||
*/ |
|||
export function getUserListApi( |
|||
id: string, |
|||
input?: GetIdentityUsersInput, |
|||
): Promise<PagedResultDto<IdentityUserDto>> { |
|||
return requestClient.get<PagedResultDto<IdentityUserDto>>( |
|||
`/api/identity/organization-units/${id}/users`, |
|||
{ |
|||
params: input, |
|||
}, |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* 查询未加入组织机构的用户列表 |
|||
* @param input 查询过滤参数 |
|||
* @returns 用户实体数据传输对象分页列表 |
|||
*/ |
|||
export function getUnaddedUserListApi( |
|||
input: GetUnaddedUserListInput, |
|||
): Promise<PagedResultDto<IdentityUserDto>> { |
|||
return requestClient.get<PagedResultDto<IdentityUserDto>>( |
|||
`/api/identity/organization-units/${input.id}/unadded-users`, |
|||
{ |
|||
params: input, |
|||
}, |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* 用户添加到组织机构 |
|||
* @param id 组织机构id |
|||
* @param input 用户id列表 |
|||
*/ |
|||
export function addMembers( |
|||
id: string, |
|||
input: OrganizationUnitAddUserDto, |
|||
): Promise<void> { |
|||
return requestClient.post( |
|||
`/api/identity/organization-units/${id}/users`, |
|||
input, |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* 查询组织机构角色列表 |
|||
* @param id 组织机构id |
|||
* @param input 查询过滤参数 |
|||
* @returns 角色实体数据传输对象分页列表 |
|||
*/ |
|||
export function getRoleListApi( |
|||
id: string, |
|||
input?: GetIdentityRolesInput, |
|||
): Promise<PagedResultDto<IdentityRoleDto>> { |
|||
return requestClient.get<PagedResultDto<IdentityRoleDto>>( |
|||
`/api/identity/organization-units/${id}/roles`, |
|||
{ |
|||
params: input, |
|||
}, |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* 查询未加入组织机构的角色列表 |
|||
* @param input 查询过滤参数 |
|||
* @returns 角色实体数据传输对象分页列表 |
|||
*/ |
|||
export function getUnaddedRoleListApi( |
|||
input: GetUnaddedRoleListInput, |
|||
): Promise<PagedResultDto<IdentityRoleDto>> { |
|||
return requestClient.get<PagedResultDto<IdentityRoleDto>>( |
|||
`/api/identity/organization-units/${input.id}/unadded-roles`, |
|||
{ |
|||
params: input, |
|||
}, |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* 角色添加到组织机构 |
|||
* @param id 组织机构id |
|||
* @param input 角色id列表 |
|||
*/ |
|||
export function addRoles( |
|||
id: string, |
|||
input: OrganizationUnitAddRoleDto, |
|||
): Promise<void> { |
|||
return requestClient.post( |
|||
`/api/identity/organization-units/${id}/roles`, |
|||
input, |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* 移动组织机构 |
|||
* @param id 组织机构id |
|||
* @param parentId 父级组织机构id |
|||
*/ |
|||
export function moveTo(id: string, parentId?: string): Promise<void> { |
|||
return requestClient.put(`api/identity/organization-units/${id}/move`, { |
|||
parentId, |
|||
}); |
|||
} |
|||
@ -1,4 +1,5 @@ |
|||
export { default as ClaimTypeTable } from './claim-types/ClaimTypeTable.vue'; |
|||
export { default as RoleTable } from './role/RoleTable.vue'; |
|||
export { default as OrganizationUnitPage } from './organization-units/OrganizationUnitPage.vue'; |
|||
export { default as RoleTable } from './roles/RoleTable.vue'; |
|||
export { default as SecurityLogTable } from './security-logs/SecurityLogTable.vue'; |
|||
export { default as UserTable } from './user/UserTable.vue'; |
|||
export { default as UserTable } from './users/UserTable.vue'; |
|||
|
|||
@ -0,0 +1,11 @@ |
|||
<script setup lang="ts"> |
|||
defineOptions({ |
|||
name: 'OrganizationUnitModal', |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<div></div> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,29 @@ |
|||
<script setup lang="ts"> |
|||
import { ref } from 'vue'; |
|||
|
|||
import OrganizationUnitTable from './OrganizationUnitTable.vue'; |
|||
import OrganizationUnitTree from './OrganizationUnitTree.vue'; |
|||
|
|||
defineOptions({ |
|||
name: 'OrganizationUnitPage', |
|||
}); |
|||
|
|||
const selectedKey = ref<string>(); |
|||
|
|||
const onSelected = (id?: string) => { |
|||
selectedKey.value = id; |
|||
}; |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="flex flex-row gap-2"> |
|||
<div class="basis-1/3"> |
|||
<OrganizationUnitTree @selected="onSelected" /> |
|||
</div> |
|||
<div class="basis-2/3"> |
|||
<OrganizationUnitTable :selected-key="selectedKey" /> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,181 @@ |
|||
<script setup lang="ts"> |
|||
import type { IdentityRoleDto } from '../../types/roles'; |
|||
|
|||
import { computed, defineAsyncComponent, h, nextTick, watchEffect } from 'vue'; |
|||
|
|||
import { useAccess } from '@vben/access'; |
|||
import { useVbenModal } from '@vben/common-ui'; |
|||
import { $t } from '@vben/locales'; |
|||
|
|||
import { |
|||
useVbenVxeGrid, |
|||
type VxeGridListeners, |
|||
type VxeGridProps, |
|||
} from '@abp/ui'; |
|||
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue'; |
|||
import { Button, Modal } from 'ant-design-vue'; |
|||
|
|||
import { addRoles, getRoleListApi } from '../../api/organization-units'; |
|||
import { removeOrganizationUnit } from '../../api/roles'; |
|||
|
|||
defineOptions({ |
|||
name: 'OrganizationUnitRoleTable', |
|||
}); |
|||
|
|||
const props = defineProps<{ |
|||
selectedKey?: string; |
|||
}>(); |
|||
|
|||
const SelectRoleModal = defineAsyncComponent( |
|||
() => import('./SelectRoleModal.vue'), |
|||
); |
|||
|
|||
const { hasAccessByCodes } = useAccess(); |
|||
|
|||
const getAddRoleEnabled = computed(() => { |
|||
return ( |
|||
props.selectedKey && |
|||
hasAccessByCodes(['AbpIdentity.OrganizationUnits.ManageRoles']) |
|||
); |
|||
}); |
|||
|
|||
const gridOptions: VxeGridProps<IdentityRoleDto> = { |
|||
columns: [ |
|||
{ |
|||
field: 'name', |
|||
minWidth: '100px', |
|||
title: $t('AbpIdentity.DisplayName:RoleName'), |
|||
}, |
|||
{ |
|||
field: 'action', |
|||
fixed: 'right', |
|||
slots: { default: 'actions' }, |
|||
title: $t('AbpUi.Actions'), |
|||
width: 180, |
|||
}, |
|||
], |
|||
exportConfig: {}, |
|||
keepSource: true, |
|||
proxyConfig: { |
|||
ajax: { |
|||
query: async ({ page }, formValues) => { |
|||
if (!props.selectedKey) { |
|||
return { |
|||
totalCount: 0, |
|||
items: [], |
|||
}; |
|||
} |
|||
return await getRoleListApi(props.selectedKey!, { |
|||
maxResultCount: page.pageSize, |
|||
skipCount: (page.currentPage - 1) * page.pageSize, |
|||
...formValues, |
|||
}); |
|||
}, |
|||
}, |
|||
response: { |
|||
total: 'totalCount', |
|||
list: 'items', |
|||
}, |
|||
}, |
|||
toolbarConfig: { |
|||
custom: true, |
|||
export: true, |
|||
// import: true, |
|||
refresh: true, |
|||
zoom: true, |
|||
}, |
|||
}; |
|||
|
|||
const gridEvents: VxeGridListeners<IdentityRoleDto> = { |
|||
cellClick: () => {}, |
|||
}; |
|||
const [Grid, { query, setLoading }] = useVbenVxeGrid({ |
|||
gridEvents, |
|||
gridOptions, |
|||
}); |
|||
|
|||
const [RoleModal, roleModalApi] = useVbenModal({ |
|||
connectedComponent: SelectRoleModal, |
|||
}); |
|||
|
|||
const onRefresh = () => { |
|||
nextTick(query); |
|||
}; |
|||
|
|||
const onDelete = (row: IdentityRoleDto) => { |
|||
Modal.confirm({ |
|||
centered: true, |
|||
content: $t('AbpIdentity.OrganizationUnit:AreYouSureRemoveRole', [ |
|||
row.name, |
|||
]), |
|||
onOk: () => { |
|||
setLoading(true); |
|||
return removeOrganizationUnit(row.id, props.selectedKey!) |
|||
.then(onRefresh) |
|||
.finally(() => setLoading(false)); |
|||
}, |
|||
title: $t('AbpUi.AreYouSure'), |
|||
}); |
|||
}; |
|||
|
|||
const onShowRole = () => { |
|||
roleModalApi.setData({ |
|||
id: props.selectedKey, |
|||
}); |
|||
roleModalApi.open(); |
|||
}; |
|||
|
|||
const onCreateRole = (roles: IdentityRoleDto[]) => { |
|||
roleModalApi.setState({ |
|||
closable: false, |
|||
confirmLoading: true, |
|||
}); |
|||
addRoles(props.selectedKey!, { |
|||
roleIds: roles.map((item) => item.id), |
|||
}) |
|||
.then(() => { |
|||
roleModalApi.close(); |
|||
query(); |
|||
}) |
|||
.finally(() => { |
|||
roleModalApi.setState({ |
|||
closable: true, |
|||
confirmLoading: false, |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
watchEffect(() => { |
|||
props.selectedKey && onRefresh(); |
|||
!props.selectedKey && onRefresh(); |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<Grid :table-title="$t('AbpIdentity.Roles')"> |
|||
<template #toolbar-tools> |
|||
<Button |
|||
v-if="getAddRoleEnabled" |
|||
:icon="h(PlusOutlined)" |
|||
type="primary" |
|||
@click="onShowRole" |
|||
> |
|||
{{ $t('AbpIdentity.OrganizationUnit:AddRole') }} |
|||
</Button> |
|||
</template> |
|||
<template #actions="{ row }"> |
|||
<Button |
|||
:icon="h(DeleteOutlined)" |
|||
danger |
|||
type="link" |
|||
v-access:code="['AbpIdentity.OrganizationUnits.ManageRoles']" |
|||
@click="onDelete(row)" |
|||
> |
|||
{{ $t('AbpUi.Delete') }} |
|||
</Button> |
|||
</template> |
|||
</Grid> |
|||
<RoleModal @confirm="onCreateRole" /> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,36 @@ |
|||
<script setup lang="ts"> |
|||
import { ref } from 'vue'; |
|||
|
|||
import { Card, Tabs } from 'ant-design-vue'; |
|||
|
|||
import OrganizationUnitRoleTable from './OrganizationUnitRoleTable.vue'; |
|||
import OrganizationUnitUserTable from './OrganizationUnitUserTable.vue'; |
|||
|
|||
defineOptions({ |
|||
name: 'OrganizationUnitTable', |
|||
}); |
|||
|
|||
const props = defineProps<{ |
|||
selectedKey?: string; |
|||
}>(); |
|||
|
|||
const TabPane = Tabs.TabPane; |
|||
|
|||
const activeTab = ref<'roles' | 'users'>('users'); |
|||
</script> |
|||
|
|||
<template> |
|||
<Card> |
|||
<Tabs v-model:active-key="activeTab"> |
|||
<TabPane key="users" :tab="$t('AbpIdentity.Users')" /> |
|||
<TabPane key="roles" :tab="$t('AbpIdentity.Roles')" /> |
|||
</Tabs> |
|||
<OrganizationUnitUserTable |
|||
v-if="activeTab === 'users'" |
|||
:selected-key="props.selectedKey" |
|||
/> |
|||
<OrganizationUnitRoleTable v-else :selected-key="props.selectedKey" /> |
|||
</Card> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,203 @@ |
|||
<script setup lang="ts"> |
|||
import type { |
|||
AntTreeNodeDropEvent, |
|||
DataNode, |
|||
EventDataNode, |
|||
} from 'ant-design-vue/es/tree'; |
|||
import type { Key } from 'ant-design-vue/es/vc-table/interface'; |
|||
|
|||
import { h, onMounted, ref, watchEffect } from 'vue'; |
|||
|
|||
import { createIconifyIcon } from '@vben/icons'; |
|||
import { $t } from '@vben/locales'; |
|||
|
|||
import { |
|||
DeleteOutlined, |
|||
EditOutlined, |
|||
PlusOutlined, |
|||
RedoOutlined, |
|||
} from '@ant-design/icons-vue'; |
|||
import { Button, Card, Dropdown, Menu, Modal, Tree } from 'ant-design-vue'; |
|||
|
|||
import { |
|||
deleteApi, |
|||
getChildrenApi, |
|||
getRootListApi, |
|||
moveTo, |
|||
} from '../../api/organization-units'; |
|||
|
|||
defineOptions({ |
|||
name: 'OrganizationUnitTree', |
|||
}); |
|||
|
|||
const emits = defineEmits<{ |
|||
(event: 'selected', id?: string): void; |
|||
}>(); |
|||
|
|||
const MenuItem = Menu.Item; |
|||
const PermissionsOutlined = createIconifyIcon('icon-park-outline:permissions'); |
|||
|
|||
interface ContextMenuActionMap { |
|||
[key: string]: (id: string) => Promise<void> | void; |
|||
} |
|||
const actionsMap: ContextMenuActionMap = { |
|||
create: onCreate, |
|||
delete: onDelete, |
|||
permissions: onPermissions, |
|||
refresh: onRefresh, |
|||
update: onUpdate, |
|||
}; |
|||
|
|||
const organizationUnits = ref<DataNode[]>([]); |
|||
const expandedKeys = ref<string[]>([]); |
|||
const loadedKeys = ref<string[]>([]); |
|||
const selectedKey = ref<string>(); |
|||
|
|||
/** 刷新组织机构树 */ |
|||
async function onRefresh() { |
|||
loadedKeys.value = []; |
|||
expandedKeys.value = []; |
|||
const { items } = await getRootListApi(); |
|||
organizationUnits.value = items.map((item) => { |
|||
return { |
|||
isLeaf: false, |
|||
key: item.id, |
|||
title: item.displayName, |
|||
children: [], |
|||
}; |
|||
}); |
|||
} |
|||
|
|||
/** 加载组织机构树节点 */ |
|||
async function onLoad(node: EventDataNode) { |
|||
const nodeKey = String(node.key); |
|||
const { items } = await getChildrenApi({ id: nodeKey }); |
|||
node.dataRef!.isLeaf = items.length === 0; |
|||
node.dataRef!.children = items.map((item): DataNode => { |
|||
return { |
|||
isLeaf: false, |
|||
key: item.id, |
|||
title: item.displayName, |
|||
children: [], |
|||
}; |
|||
}); |
|||
organizationUnits.value = [...organizationUnits.value]; |
|||
expandedKeys.value.push(nodeKey); |
|||
loadedKeys.value.push(nodeKey); |
|||
} |
|||
|
|||
/** 右键点击事件 */ |
|||
function onRightClick() { |
|||
// 阻止默认事件 |
|||
} |
|||
|
|||
/** 创建组织机构树 */ |
|||
function onCreate(parentId?: string) { |
|||
!parentId && console.warn('create root method not implemented!'); |
|||
parentId && console.warn('create children method not implemented!'); |
|||
} |
|||
|
|||
/** 编辑组织机构树 */ |
|||
function onUpdate(id: string) { |
|||
console.warn('update method not implemented!', id); |
|||
} |
|||
|
|||
/** 编辑组织机构树权限 */ |
|||
function onPermissions(id: string) { |
|||
console.warn('permissions method not implemented!', id); |
|||
} |
|||
|
|||
/** 删除组织机构 */ |
|||
function onDelete(id: string) { |
|||
Modal.confirm({ |
|||
centered: true, |
|||
content: $t('AbpUi.ItemWillBeDeletedMessage'), |
|||
maskClosable: false, |
|||
onOk: () => { |
|||
return deleteApi(id).then(() => onRefresh()); |
|||
}, |
|||
title: $t('AbpUi.AreYouSure'), |
|||
}); |
|||
} |
|||
|
|||
/** 右键菜单点击事件 */ |
|||
function onMenuClick(id: string, eventKey: Key) { |
|||
actionsMap[eventKey]!(id); |
|||
} |
|||
|
|||
/** 组织机构选择变化事件 */ |
|||
function onSelectChange(selectedKeys: Key[]) { |
|||
if (selectedKeys.length === 0) { |
|||
selectedKey.value = undefined; |
|||
return; |
|||
} |
|||
selectedKey.value = String(selectedKeys[0]); |
|||
} |
|||
|
|||
/** 组织机构节点拖动事件 */ |
|||
function onDrop(info: AntTreeNodeDropEvent) { |
|||
if (!info.dragNode.eventKey) { |
|||
return; |
|||
} |
|||
const eventKey = String(info.dragNode.eventKey); |
|||
const api = |
|||
info.dropPosition === -1 |
|||
? moveTo(eventKey) // parent |
|||
: moveTo(eventKey, String(info.node.eventKey)); // children |
|||
api.then(() => onRefresh()); |
|||
} |
|||
|
|||
onMounted(onRefresh); |
|||
|
|||
watchEffect(() => { |
|||
emits('selected', selectedKey.value); |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<Card :title="$t('AbpIdentity.OrganizationUnit:Tree')"> |
|||
<template #extra> |
|||
<Button :icon="h(PlusOutlined)" type="primary" @click="() => onCreate()"> |
|||
{{ $t('AbpIdentity.OrganizationUnit:AddRoot') }} |
|||
</Button> |
|||
</template> |
|||
<Tree |
|||
:expanded-keys="expandedKeys" |
|||
:load-data="onLoad" |
|||
:loaded-keys="loadedKeys" |
|||
:tree-data="organizationUnits" |
|||
block-node |
|||
draggable |
|||
@drop="onDrop" |
|||
@right-click="onRightClick" |
|||
@select="onSelectChange" |
|||
> |
|||
<template #title="{ key: treeKey, title }"> |
|||
<Dropdown :trigger="['contextmenu']"> |
|||
<span> {{ title }}</span> |
|||
<template #overlay> |
|||
<Menu @click="({ key: menuKey }) => onMenuClick(treeKey, menuKey)"> |
|||
<MenuItem key="update" :icon="h(EditOutlined)"> |
|||
{{ $t('AbpUi.Edit') }} |
|||
</MenuItem> |
|||
<MenuItem key="create" :icon="h(PlusOutlined)"> |
|||
{{ $t('AbpIdentity.OrganizationUnit:AddChildren') }} |
|||
</MenuItem> |
|||
<MenuItem key="delete" :icon="h(DeleteOutlined)"> |
|||
{{ $t('AbpUi.Delete') }} |
|||
</MenuItem> |
|||
<MenuItem key="permissions" :icon="h(PermissionsOutlined)"> |
|||
{{ $t('AbpIdentity.Permissions') }} |
|||
</MenuItem> |
|||
<MenuItem key="refresh" :icon="h(RedoOutlined)"> |
|||
{{ $t('AbpIdentity.OrganizationUnit:RefreshRoot') }} |
|||
</MenuItem> |
|||
</Menu> |
|||
</template> |
|||
</Dropdown> |
|||
</template> |
|||
</Tree> |
|||
</Card> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,183 @@ |
|||
<script setup lang="ts"> |
|||
import type { VxeGridListeners, VxeGridProps } from '@abp/ui'; |
|||
|
|||
import type { IdentityUserDto } from '../../types/users'; |
|||
|
|||
import { computed, defineAsyncComponent, h, nextTick, watchEffect } from 'vue'; |
|||
|
|||
import { useAccess } from '@vben/access'; |
|||
import { useVbenModal } from '@vben/common-ui'; |
|||
import { $t } from '@vben/locales'; |
|||
|
|||
import { useVbenVxeGrid } from '@abp/ui'; |
|||
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue'; |
|||
import { Button, Modal } from 'ant-design-vue'; |
|||
|
|||
import { addMembers, getUserListApi } from '../../api/organization-units'; |
|||
import { removeOrganizationUnit } from '../../api/users'; |
|||
|
|||
defineOptions({ |
|||
name: 'OrganizationUnitUserTable', |
|||
}); |
|||
|
|||
const props = defineProps<{ |
|||
selectedKey?: string; |
|||
}>(); |
|||
|
|||
const SelectMemberModal = defineAsyncComponent( |
|||
() => import('./SelectMemberModal.vue'), |
|||
); |
|||
|
|||
const { hasAccessByCodes } = useAccess(); |
|||
|
|||
const getAddMemberEnabled = computed(() => { |
|||
return ( |
|||
props.selectedKey && |
|||
hasAccessByCodes(['AbpIdentity.OrganizationUnits.ManageUsers']) |
|||
); |
|||
}); |
|||
|
|||
const gridOptions: VxeGridProps<IdentityUserDto> = { |
|||
columns: [ |
|||
{ |
|||
field: 'userName', |
|||
minWidth: '100px', |
|||
title: $t('AbpIdentity.DisplayName:UserName'), |
|||
}, |
|||
{ |
|||
field: 'email', |
|||
minWidth: '120px', |
|||
title: $t('AbpIdentity.DisplayName:Email'), |
|||
}, |
|||
{ |
|||
field: 'action', |
|||
fixed: 'right', |
|||
slots: { default: 'actions' }, |
|||
title: $t('AbpUi.Actions'), |
|||
width: 120, |
|||
}, |
|||
], |
|||
exportConfig: {}, |
|||
keepSource: true, |
|||
proxyConfig: { |
|||
ajax: { |
|||
query: async ({ page }, formValues) => { |
|||
if (!props.selectedKey) { |
|||
return { |
|||
totalCount: 0, |
|||
items: [], |
|||
}; |
|||
} |
|||
return await getUserListApi(props.selectedKey!, { |
|||
maxResultCount: page.pageSize, |
|||
skipCount: (page.currentPage - 1) * page.pageSize, |
|||
...formValues, |
|||
}); |
|||
}, |
|||
}, |
|||
response: { |
|||
total: 'totalCount', |
|||
list: 'items', |
|||
}, |
|||
}, |
|||
toolbarConfig: { |
|||
custom: true, |
|||
export: true, |
|||
// import: true, |
|||
refresh: true, |
|||
zoom: true, |
|||
}, |
|||
}; |
|||
|
|||
const gridEvents: VxeGridListeners<IdentityUserDto> = { |
|||
cellClick: () => {}, |
|||
}; |
|||
const [Grid, { query, setLoading }] = useVbenVxeGrid({ |
|||
gridEvents, |
|||
gridOptions, |
|||
}); |
|||
const [MemberModal, userModalApi] = useVbenModal({ |
|||
connectedComponent: SelectMemberModal, |
|||
}); |
|||
|
|||
const onRefresh = () => { |
|||
nextTick(query); |
|||
}; |
|||
|
|||
const onDelete = (row: IdentityUserDto) => { |
|||
Modal.confirm({ |
|||
centered: true, |
|||
content: $t('AbpIdentity.OrganizationUnit:AreYouSureRemoveUser', [ |
|||
row.userName, |
|||
]), |
|||
onOk: () => { |
|||
setLoading(true); |
|||
return removeOrganizationUnit(row.id, props.selectedKey!) |
|||
.then(onRefresh) |
|||
.finally(() => setLoading(false)); |
|||
}, |
|||
title: $t('AbpUi.AreYouSure'), |
|||
}); |
|||
}; |
|||
|
|||
const onShowMember = () => { |
|||
userModalApi.setData({ |
|||
id: props.selectedKey, |
|||
}); |
|||
userModalApi.open(); |
|||
}; |
|||
|
|||
const onCreateMember = (users: IdentityUserDto[]) => { |
|||
userModalApi.setState({ |
|||
closable: false, |
|||
confirmLoading: true, |
|||
}); |
|||
addMembers(props.selectedKey!, { |
|||
userIds: users.map((item) => item.id), |
|||
}) |
|||
.then(() => { |
|||
userModalApi.close(); |
|||
query(); |
|||
}) |
|||
.finally(() => { |
|||
userModalApi.setState({ |
|||
closable: true, |
|||
confirmLoading: false, |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
watchEffect(() => { |
|||
props.selectedKey && onRefresh(); |
|||
!props.selectedKey && onRefresh(); |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<Grid :table-title="$t('AbpIdentity.Users')"> |
|||
<template #toolbar-tools> |
|||
<Button |
|||
v-if="getAddMemberEnabled" |
|||
:icon="h(PlusOutlined)" |
|||
type="primary" |
|||
@click="onShowMember" |
|||
> |
|||
{{ $t('AbpIdentity.OrganizationUnit:AddMember') }} |
|||
</Button> |
|||
</template> |
|||
<template #actions="{ row }"> |
|||
<Button |
|||
:icon="h(DeleteOutlined)" |
|||
danger |
|||
type="link" |
|||
v-access:code="['AbpIdentity.OrganizationUnits.ManageUsers']" |
|||
@click="onDelete(row)" |
|||
> |
|||
{{ $t('AbpUi.Delete') }} |
|||
</Button> |
|||
</template> |
|||
</Grid> |
|||
<MemberModal @confirm="onCreateMember" /> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,134 @@ |
|||
<script setup lang="ts"> |
|||
import type { IdentityUserDto } from '../../types/users'; |
|||
|
|||
import { defineEmits, defineOptions, nextTick, ref, toValue } from 'vue'; |
|||
|
|||
import { useVbenModal, type VbenFormProps } from '@vben/common-ui'; |
|||
import { $t } from '@vben/locales'; |
|||
|
|||
import { |
|||
useVbenVxeGrid, |
|||
type VxeGridListeners, |
|||
type VxeGridProps, |
|||
} from '@abp/ui'; |
|||
|
|||
import { getUnaddedUserListApi } from '../../api/organization-units'; |
|||
|
|||
defineOptions({ |
|||
name: 'SelectMemberModal', |
|||
}); |
|||
const emits = defineEmits<{ |
|||
(event: 'confirm', items: IdentityUserDto[]): void; |
|||
}>(); |
|||
|
|||
const selectedUsers = ref<IdentityUserDto[]>([]); |
|||
|
|||
const formOptions: VbenFormProps = { |
|||
// 默认展开 |
|||
collapsed: false, |
|||
resetButtonOptions: { |
|||
show: false, |
|||
}, |
|||
schema: [ |
|||
{ |
|||
component: 'Input', |
|||
componentProps: { |
|||
allowClear: true, |
|||
autocomplete: 'off', |
|||
}, |
|||
fieldName: 'filter', |
|||
formItemClass: 'col-span-2 items-baseline', |
|||
label: $t('AbpUi.Search'), |
|||
}, |
|||
], |
|||
// 控制表单是否显示折叠按钮 |
|||
showCollapseButton: false, |
|||
// 按下回车时是否提交表单 |
|||
submitOnEnter: true, |
|||
}; |
|||
const [Modal, modalApi] = useVbenModal({ |
|||
closeOnClickModal: false, |
|||
closeOnPressEscape: false, |
|||
draggable: true, |
|||
fullscreenButton: false, |
|||
onCancel() { |
|||
modalApi.close(); |
|||
}, |
|||
onConfirm: async () => { |
|||
emits('confirm', toValue(selectedUsers)); |
|||
}, |
|||
onOpenChange: async (isOpen: boolean) => { |
|||
isOpen && nextTick(onRefresh); |
|||
}, |
|||
title: $t('AbpIdentity.Users'), |
|||
}); |
|||
|
|||
const gridOptions: VxeGridProps<IdentityUserDto> = { |
|||
checkboxConfig: { |
|||
highlight: true, |
|||
labelField: 'userName', |
|||
}, |
|||
columns: [ |
|||
{ |
|||
align: 'left', |
|||
field: 'userName', |
|||
minWidth: '100px', |
|||
title: $t('AbpIdentity.DisplayName:UserName'), |
|||
type: 'checkbox', |
|||
}, |
|||
{ |
|||
align: 'left', |
|||
field: 'email', |
|||
minWidth: '120px', |
|||
title: $t('AbpIdentity.DisplayName:Email'), |
|||
}, |
|||
], |
|||
exportConfig: {}, |
|||
keepSource: true, |
|||
proxyConfig: { |
|||
ajax: { |
|||
query: async ({ page }, formValues) => { |
|||
const state = modalApi.getData<Record<string, any>>(); |
|||
return await getUnaddedUserListApi({ |
|||
id: state.id, |
|||
maxResultCount: page.pageSize, |
|||
skipCount: (page.currentPage - 1) * page.pageSize, |
|||
...formValues, |
|||
}); |
|||
}, |
|||
}, |
|||
autoLoad: false, |
|||
response: { |
|||
total: 'totalCount', |
|||
list: 'items', |
|||
}, |
|||
}, |
|||
toolbarConfig: {}, |
|||
}; |
|||
|
|||
const gridEvents: VxeGridListeners<IdentityUserDto> = { |
|||
checkboxAll: (e) => { |
|||
selectedUsers.value = e.records; |
|||
}, |
|||
checkboxChange: (e) => { |
|||
selectedUsers.value = e.records; |
|||
}, |
|||
}; |
|||
const [Grid, { query }] = useVbenVxeGrid({ |
|||
formOptions, |
|||
gridEvents, |
|||
gridOptions, |
|||
}); |
|||
|
|||
function onRefresh() { |
|||
nextTick(query); |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<Modal> |
|||
<Grid /> |
|||
</Modal> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,127 @@ |
|||
<script setup lang="ts"> |
|||
import type { IdentityRoleDto } from '../../types/roles'; |
|||
|
|||
import { defineEmits, defineOptions, nextTick, ref, toValue } from 'vue'; |
|||
|
|||
import { useVbenModal, type VbenFormProps } from '@vben/common-ui'; |
|||
import { $t } from '@vben/locales'; |
|||
|
|||
import { |
|||
useVbenVxeGrid, |
|||
type VxeGridListeners, |
|||
type VxeGridProps, |
|||
} from '@abp/ui'; |
|||
|
|||
import { getUnaddedRoleListApi } from '../../api/organization-units'; |
|||
|
|||
defineOptions({ |
|||
name: 'SelectRoleModal', |
|||
}); |
|||
const emits = defineEmits<{ |
|||
(event: 'confirm', items: IdentityRoleDto[]): void; |
|||
}>(); |
|||
|
|||
const selectedRoles = ref<IdentityRoleDto[]>([]); |
|||
|
|||
const formOptions: VbenFormProps = { |
|||
// 默认展开 |
|||
collapsed: false, |
|||
resetButtonOptions: { |
|||
show: false, |
|||
}, |
|||
schema: [ |
|||
{ |
|||
component: 'Input', |
|||
componentProps: { |
|||
allowClear: true, |
|||
autocomplete: 'off', |
|||
}, |
|||
fieldName: 'filter', |
|||
formItemClass: 'col-span-2 items-baseline', |
|||
label: $t('AbpUi.Search'), |
|||
}, |
|||
], |
|||
// 控制表单是否显示折叠按钮 |
|||
showCollapseButton: false, |
|||
// 按下回车时是否提交表单 |
|||
submitOnEnter: true, |
|||
}; |
|||
const [Modal, modalApi] = useVbenModal({ |
|||
closeOnClickModal: false, |
|||
closeOnPressEscape: false, |
|||
draggable: true, |
|||
fullscreenButton: false, |
|||
onCancel() { |
|||
modalApi.close(); |
|||
}, |
|||
onConfirm: async () => { |
|||
emits('confirm', toValue(selectedRoles)); |
|||
}, |
|||
onOpenChange: async (isOpen: boolean) => { |
|||
isOpen && nextTick(onRefresh); |
|||
}, |
|||
title: $t('AbpIdentity.Roles'), |
|||
}); |
|||
|
|||
const gridOptions: VxeGridProps<IdentityRoleDto> = { |
|||
checkboxConfig: { |
|||
highlight: true, |
|||
labelField: 'name', |
|||
}, |
|||
columns: [ |
|||
{ |
|||
align: 'left', |
|||
field: 'name', |
|||
title: $t('AbpIdentity.DisplayName:RoleName'), |
|||
type: 'checkbox', |
|||
}, |
|||
], |
|||
exportConfig: {}, |
|||
keepSource: true, |
|||
proxyConfig: { |
|||
ajax: { |
|||
query: async ({ page }, formValues) => { |
|||
const state = modalApi.getData<Record<string, any>>(); |
|||
return await getUnaddedRoleListApi({ |
|||
id: state.id, |
|||
maxResultCount: page.pageSize, |
|||
skipCount: (page.currentPage - 1) * page.pageSize, |
|||
...formValues, |
|||
}); |
|||
}, |
|||
}, |
|||
autoLoad: false, |
|||
response: { |
|||
total: 'totalCount', |
|||
list: 'items', |
|||
}, |
|||
}, |
|||
toolbarConfig: {}, |
|||
}; |
|||
|
|||
const gridEvents: VxeGridListeners<IdentityRoleDto> = { |
|||
checkboxAll: (e) => { |
|||
selectedRoles.value = e.records; |
|||
}, |
|||
checkboxChange: (e) => { |
|||
selectedRoles.value = e.records; |
|||
}, |
|||
}; |
|||
const [Grid, { query }] = useVbenVxeGrid({ |
|||
formOptions, |
|||
gridEvents, |
|||
gridOptions, |
|||
}); |
|||
|
|||
function onRefresh() { |
|||
nextTick(query); |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<Modal> |
|||
<Grid /> |
|||
</Modal> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,7 @@ |
|||
<script setup lang="ts"></script> |
|||
|
|||
<template> |
|||
<div></div> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -1,4 +1,5 @@ |
|||
export * from './claim-types'; |
|||
export * from './organization-units'; |
|||
export * from './roles'; |
|||
export * from './security-logs'; |
|||
export * from './user'; |
|||
export * from './user'; |
|||
export * from './users'; |
|||
|
|||
@ -0,0 +1,96 @@ |
|||
import type { |
|||
AuditedEntityDto, |
|||
PagedAndSortedResultRequestDto, |
|||
} from '@abp/core'; |
|||
|
|||
import type { GetUserPagedListInput } from './users'; |
|||
|
|||
/** 组织机构 */ |
|||
interface OrganizationUnitDto extends AuditedEntityDto<string> { |
|||
/** 编号 */ |
|||
code: string; |
|||
/** 显示名称 */ |
|||
displayName: string; |
|||
/** 父节点标识 */ |
|||
parentId?: string; |
|||
} |
|||
|
|||
/** 组织机构分页查询对象 */ |
|||
interface GetOrganizationUnitPagedListInput |
|||
extends PagedAndSortedResultRequestDto { |
|||
/** 过滤字符 */ |
|||
filter?: string; |
|||
} |
|||
|
|||
/** 组织机构创建对象 */ |
|||
interface OrganizationUnitCreateDto { |
|||
/** 显示名称 */ |
|||
displayName: string; |
|||
/** 父节点标识 */ |
|||
parentId?: string; |
|||
} |
|||
|
|||
/** 组织机构变更对象 */ |
|||
interface OrganizationUnitUpdateDto { |
|||
/** 显示名称 */ |
|||
displayName: string; |
|||
} |
|||
|
|||
/** 组织机构增加部门对象 */ |
|||
interface OrganizationUnitAddRoleInput { |
|||
/** 角色标识列表 */ |
|||
roleIds: string[]; |
|||
} |
|||
|
|||
/** 组织机构增加用户对象 */ |
|||
interface OrganizationUnitAddUserInput { |
|||
/** 用户标识列表 */ |
|||
userIds: string[]; |
|||
} |
|||
|
|||
interface OrganizationUnitGetChildrenDto { |
|||
/** 上级组织机构id */ |
|||
id: string; |
|||
/** 递归查询子级 */ |
|||
recursive?: boolean; |
|||
} |
|||
|
|||
interface GetIdentityUsersInput extends PagedAndSortedResultRequestDto { |
|||
filter?: string; |
|||
} |
|||
|
|||
interface GetIdentityRolesInput extends PagedAndSortedResultRequestDto { |
|||
filter?: string; |
|||
} |
|||
|
|||
interface GetUnaddedUserListInput extends GetUserPagedListInput { |
|||
id: string; |
|||
} |
|||
|
|||
interface GetUnaddedRoleListInput extends GetUserPagedListInput { |
|||
id: string; |
|||
} |
|||
|
|||
interface OrganizationUnitAddUserDto { |
|||
userIds: string[]; |
|||
} |
|||
|
|||
interface OrganizationUnitAddRoleDto { |
|||
roleIds: string[]; |
|||
} |
|||
|
|||
export type { |
|||
GetIdentityRolesInput, |
|||
GetIdentityUsersInput, |
|||
GetOrganizationUnitPagedListInput, |
|||
GetUnaddedRoleListInput, |
|||
GetUnaddedUserListInput, |
|||
OrganizationUnitAddRoleDto, |
|||
OrganizationUnitAddRoleInput, |
|||
OrganizationUnitAddUserDto, |
|||
OrganizationUnitAddUserInput, |
|||
OrganizationUnitCreateDto, |
|||
OrganizationUnitDto, |
|||
OrganizationUnitGetChildrenDto, |
|||
OrganizationUnitUpdateDto, |
|||
}; |
|||
Loading…
Reference in new issue