Browse Source

feat(vben5): 增加数据权限演示

pull/1168/head
colin 10 months ago
parent
commit
7356238e15
  1. 2
      apps/vben5/apps/app-antd/package.json
  2. 8
      apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json
  3. 8
      apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json
  4. 40
      apps/vben5/apps/app-antd/src/router/routes/modules/abp.ts
  5. 15
      apps/vben5/apps/app-antd/src/views/data-protection/entity-type-infos/index.vue
  6. 15
      apps/vben5/apps/app-antd/src/views/demos/books/index.vue
  7. 1
      apps/vben5/packages/@abp/core/src/types/dto.ts
  8. 36
      apps/vben5/packages/@abp/data-protection/package.json
  9. 3
      apps/vben5/packages/@abp/data-protection/src/api/index.ts
  10. 33
      apps/vben5/packages/@abp/data-protection/src/api/useEntityTypeInfosApi.ts
  11. 44
      apps/vben5/packages/@abp/data-protection/src/api/useRoleEntityRulesApi.ts
  12. 49
      apps/vben5/packages/@abp/data-protection/src/api/useSubjectStrategysApi.ts
  13. 84
      apps/vben5/packages/@abp/data-protection/src/components/entity-rules/EntityRuleModal.vue
  14. 114
      apps/vben5/packages/@abp/data-protection/src/components/entity-type-infos/EntityTypeInfoTable.vue
  15. 2
      apps/vben5/packages/@abp/data-protection/src/components/index.ts
  16. 3
      apps/vben5/packages/@abp/data-protection/src/index.ts
  17. 101
      apps/vben5/packages/@abp/data-protection/src/types/entityRules.ts
  18. 38
      apps/vben5/packages/@abp/data-protection/src/types/entityTypeInfos.ts
  19. 4
      apps/vben5/packages/@abp/data-protection/src/types/index.ts
  20. 38
      apps/vben5/packages/@abp/data-protection/src/types/roleEntityRules.ts
  21. 42
      apps/vben5/packages/@abp/data-protection/src/types/strategys.ts
  22. 6
      apps/vben5/packages/@abp/data-protection/tsconfig.json
  23. 37
      apps/vben5/packages/@abp/demo/package.json
  24. 2
      apps/vben5/packages/@abp/demo/src/api/index.ts
  25. 23
      apps/vben5/packages/@abp/demo/src/api/useAuthorsApi.ts
  26. 71
      apps/vben5/packages/@abp/demo/src/api/useBooksApi.ts
  27. 212
      apps/vben5/packages/@abp/demo/src/components/books/BookModal.vue
  28. 204
      apps/vben5/packages/@abp/demo/src/components/books/BookTable.vue
  29. 1
      apps/vben5/packages/@abp/demo/src/components/index.ts
  30. 8
      apps/vben5/packages/@abp/demo/src/constants/permissions.ts
  31. 3
      apps/vben5/packages/@abp/demo/src/index.ts
  32. 13
      apps/vben5/packages/@abp/demo/src/types/authors.ts
  33. 41
      apps/vben5/packages/@abp/demo/src/types/books.ts
  34. 1
      apps/vben5/packages/@abp/demo/src/types/index.ts
  35. 6
      apps/vben5/packages/@abp/demo/tsconfig.json
  36. 1
      apps/vben5/packages/@abp/identity/package.json
  37. 2
      apps/vben5/packages/@abp/identity/src/api/useOrganizationUnitsApi.ts
  38. 528
      apps/vben5/packages/@abp/identity/src/components/roles/RoleRuleDrawer.vue
  39. 13
      apps/vben5/packages/@abp/identity/src/components/roles/RoleTable.vue
  40. 22
      apps/vben5/packages/@abp/identity/src/components/users/UserModal.vue
  41. 6
      apps/vben5/packages/@abp/ui/src/components/vxe-table/index.ts

2
apps/vben5/apps/app-antd/package.json

@ -29,6 +29,8 @@
"@abp/account": "workspace:*",
"@abp/auditing": "workspace:*",
"@abp/core": "workspace:*",
"@abp/data-protection": "workspace:*",
"@abp/demo": "workspace:*",
"@abp/features": "workspace:*",
"@abp/identity": "workspace:*",
"@abp/localization": "workspace:*",

8
apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json

@ -51,6 +51,10 @@
"resources": "Resources",
"languages": "Languages",
"texts": "Texts"
},
"dataProtection": {
"title": "Data Protection",
"entityTypeInfos": "Entity Type Infos"
}
},
"openiddict": {
@ -98,5 +102,9 @@
"title": "Saas",
"editions": "Editions",
"tenants": "Tenants"
},
"demo": {
"title": "Demo",
"books": "Books"
}
}

8
apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json

@ -51,6 +51,10 @@
"resources": "资源管理",
"languages": "语言管理",
"texts": "文档管理"
},
"dataProtection": {
"title": "数据权限",
"entityTypeInfos": "实体列表"
}
},
"openiddict": {
@ -98,5 +102,9 @@
"title": "Saas",
"editions": "版本管理",
"tenants": "租户管理"
},
"demo": {
"title": "演示",
"books": "书籍列表"
}
}

40
apps/vben5/apps/app-antd/src/router/routes/modules/abp.ts

@ -214,6 +214,27 @@ const routes: RouteRecordRaw[] = [
},
],
},
{
meta: {
title: $t('abp.manage.dataProtection.title'),
icon: 'icon-park-outline:protect',
},
name: 'DataProtectionManagement',
path: '/manage/data-protection',
children: [
{
meta: {
title: $t('abp.manage.dataProtection.entityTypeInfos'),
icon: 'iconamoon:type',
keepAlive: true,
},
name: 'EntityTypeInfos',
path: '/manage/data-protection/entity-type-infos',
component: () =>
import('#/views/data-protection/entity-type-infos/index.vue'),
},
],
},
{
meta: {
title: $t('abp.manage.identity.auditLogs'),
@ -401,6 +422,25 @@ const routes: RouteRecordRaw[] = [
},
],
},
{
name: 'AbpDemo',
path: '/abp/demos',
meta: {
title: $t('abp.demo.title'),
icon: 'carbon:demo',
},
children: [
{
meta: {
title: $t('abp.demo.books'),
icon: 'mingcute:book-line',
},
name: 'DemoBooks',
path: '/abp/demos/books',
component: () => import('#/views/demos/books/index.vue'),
},
],
},
],
},
];

15
apps/vben5/apps/app-antd/src/views/data-protection/entity-type-infos/index.vue

@ -0,0 +1,15 @@
<script lang="ts" setup>
import { Page } from '@vben/common-ui';
import { EntityTypeInfoTable } from '@abp/data-protection';
defineOptions({
name: 'EntityTypeInfos',
});
</script>
<template>
<Page>
<EntityTypeInfoTable />
</Page>
</template>

15
apps/vben5/apps/app-antd/src/views/demos/books/index.vue

@ -0,0 +1,15 @@
<script lang="ts" setup>
import { Page } from '@vben/common-ui';
import { BookTable } from '@abp/demo';
defineOptions({
name: 'DemoBooks',
});
</script>
<template>
<Page>
<BookTable />
</Page>
</template>

1
apps/vben5/packages/@abp/core/src/types/dto.ts

@ -53,6 +53,7 @@ interface ExtensibleObject {
}
/** 实体数据传输对象 */
interface EntityDto<TPrimaryKey> {
[key: string]: any;
/** 实体标识 */
id: TPrimaryKey;
}

36
apps/vben5/packages/@abp/data-protection/package.json

@ -0,0 +1,36 @@
{
"name": "@abp/data-protection",
"version": "9.0.4",
"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/data-protection"
},
"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/common-ui": "workspace:*",
"@vben/hooks": "workspace:*",
"@vben/icons": "workspace:*",
"@vben/layouts": "workspace:*",
"@vben/locales": "workspace:*",
"ant-design-vue": "catalog:",
"dayjs": "catalog:",
"vue": "catalog:*"
}
}

3
apps/vben5/packages/@abp/data-protection/src/api/index.ts

@ -0,0 +1,3 @@
export { useEntityTypeInfosApi } from './useEntityTypeInfosApi';
export { useRoleEntityRulesApi } from './useRoleEntityRulesApi';
export { useSubjectStrategysApi } from './useSubjectStrategysApi';

33
apps/vben5/packages/@abp/data-protection/src/api/useEntityTypeInfosApi.ts

@ -0,0 +1,33 @@
import type { PagedResultDto } from '@abp/core';
import type {
EntityTypeInfoDto,
GetEntityTypeInfoListInput,
} from '../types/entityTypeInfos';
import { useRequest } from '@abp/request';
export function useEntityTypeInfosApi() {
const { cancel, request } = useRequest();
function getApi(id: string): Promise<EntityTypeInfoDto> {
return request(`/api/data-protection-management/entity-type-infos/${id}`, {
method: 'GET',
});
}
function getPagedListApi(
input?: GetEntityTypeInfoListInput,
): Promise<PagedResultDto<EntityTypeInfoDto>> {
return request(`/api/data-protection-management/entity-type-infos`, {
method: 'GET',
params: input,
});
}
return {
cancel,
getApi,
getPagedListApi,
};
}

44
apps/vben5/packages/@abp/data-protection/src/api/useRoleEntityRulesApi.ts

@ -0,0 +1,44 @@
import type {
RoleEntityRuleCreateDto,
RoleEntityRuleDto,
RoleEntityRuleGetInput,
} from '../types/roleEntityRules';
import { useRequest } from '@abp/request';
export function useRoleEntityRulesApi() {
const { cancel, request } = useRequest();
function getApi(input: RoleEntityRuleGetInput): Promise<RoleEntityRuleDto> {
return request('/api/data-protection-management/entity-rule/roles', {
method: 'GET',
params: input,
});
}
function createApi(
input: RoleEntityRuleCreateDto,
): Promise<RoleEntityRuleDto> {
return request('/api/data-protection-management/entity-rule/roles', {
data: input,
method: 'POST',
});
}
function updateApi(
id: string,
input: RoleEntityRuleCreateDto,
): Promise<RoleEntityRuleDto> {
return request(`/api/data-protection-management/entity-rule/roles/${id}`, {
data: input,
method: 'PUT',
});
}
return {
cancel,
createApi,
getApi,
updateApi,
};
}

49
apps/vben5/packages/@abp/data-protection/src/api/useSubjectStrategysApi.ts

@ -0,0 +1,49 @@
import type {
SubjectStrategyDto,
SubjectStrategyGetInput,
SubjectStrategySetInput,
} from '../types';
import { useRequest } from '@abp/request';
export function useSubjectStrategysApi() {
const { cancel, request } = useRequest();
/**
*
* @param input
* @returns
*/
function getApi(
input: SubjectStrategyGetInput,
): Promise<null | SubjectStrategyDto> {
return request<null | SubjectStrategyDto>(
'/api/data-protection-management/subject-strategys',
{
method: 'GET',
params: input,
},
);
}
/**
*
* @param input
* @returns
*/
function setApi(input: SubjectStrategySetInput): Promise<SubjectStrategyDto> {
return request<SubjectStrategyDto>(
'/api/data-protection-management/subject-strategys',
{
data: input,
method: 'PUT',
},
);
}
return {
cancel,
getApi,
setApi,
};
}

84
apps/vben5/packages/@abp/data-protection/src/components/entity-rules/EntityRuleModal.vue

@ -0,0 +1,84 @@
<script setup lang="ts">
import type { EntityTypeInfoDto } from '../../types';
import { onMounted, ref } from 'vue';
import { useVbenForm, useVbenModal } from '@vben/common-ui';
import { Select } from 'ant-design-vue';
import { useEntityTypeInfosApi } from '../../api/useEntityTypeInfosApi';
import { DataAccessOperation } from '../../types/entityRules';
const emits = defineEmits<{
(event: 'entityChange', id: string): void;
}>();
const entityTypes = ref<EntityTypeInfoDto[]>([]);
const [Form, formApi] = useVbenForm({
handleSubmit: onSubmit,
schema: [
{
component: 'Select',
fieldName: 'entityTypeId',
},
{
component: 'Select',
componentProps: {
options: [
{
label: '查询',
value: DataAccessOperation.Read,
},
{
label: '编辑',
value: DataAccessOperation.Write,
},
{
label: '删除',
value: DataAccessOperation.Delete,
},
],
},
fieldName: 'operation',
},
],
showDefaultActions: false,
});
const [Modal] = useVbenModal({
async onConfirm() {
await formApi.validateAndSubmitForm();
},
});
const { getPagedListApi } = useEntityTypeInfosApi();
async function onInit(filter?: string) {
const { items } = await getPagedListApi({ filter });
entityTypes.value = items;
}
async function onSubmit(values: Record<string, any>) {
console.log(values);
}
onMounted(onInit);
</script>
<template>
<Modal>
<Form>
<template #entityTypeId="{ model, field }">
<Select
v-model:value="model[field]"
:options="entityTypes"
:field-names="{ label: 'displayName', value: 'id' }"
@change="(value) => emits('entityChange', value!.toString())"
/>
</template>
<slot name="subject"></slot>
</Form>
</Modal>
</template>
<style scoped></style>

114
apps/vben5/packages/@abp/data-protection/src/components/entity-type-infos/EntityTypeInfoTable.vue

@ -0,0 +1,114 @@
<script setup lang="ts">
import type { VxeGridListeners, VxeGridProps } from '@abp/ui';
import type { EntityTypeInfoDto } from '../../types/entityTypeInfos';
import { h } from 'vue';
import { $t } from '@vben/locales';
import { useLocalization, useLocalizationSerializer } from '@abp/core';
import { useVbenVxeGrid } from '@abp/ui';
import { EditOutlined } from '@ant-design/icons-vue';
import { Button } from 'ant-design-vue';
import { useEntityTypeInfosApi } from '../../api/useEntityTypeInfosApi';
defineOptions({
name: 'GdprTable',
});
const { getPagedListApi } = useEntityTypeInfosApi();
const { Lr } = useLocalization();
const { deserialize } = useLocalizationSerializer();
const gridOptions: VxeGridProps<EntityTypeInfoDto> = {
columns: [
{
align: 'left',
field: 'name',
minWidth: 150,
title: $t('DataProtection.DisplayName:Name'),
},
{
align: 'left',
field: 'displayName',
minWidth: 150,
title: $t('DataProtection.DisplayName:DisplayName'),
},
{
align: 'left',
field: 'typeFullName',
minWidth: 200,
title: $t('DataProtection.DisplayName:TypeFullName'),
},
{
align: 'left',
field: 'isAuditEnabled',
minWidth: 150,
title: $t('DataProtection.DisplayName:IsAuditEnabled'),
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: $t('AbpUi.Actions'),
width: 180,
},
],
exportConfig: {},
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
const { totalCount, items } = await getPagedListApi({
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
});
return {
totalCount,
items: items.map((item) => {
const displayName = deserialize(item.displayName);
return {
...item,
displayName: Lr(displayName.resourceName, displayName.name),
};
}),
};
},
},
response: {
total: 'totalCount',
list: 'items',
},
},
toolbarConfig: {
refresh: true,
},
};
const gridEvents: VxeGridListeners<EntityTypeInfoDto> = {
cellClick: () => {},
};
const [Grid] = useVbenVxeGrid({
gridEvents,
gridOptions,
});
function onEdit(_record: EntityTypeInfoDto) {}
</script>
<template>
<Grid :table-title="$t('DataProtection.EntityTypeInfos')">
<template #action="{ row }">
<div class="flex flex-row">
<Button :icon="h(EditOutlined)" block type="link" @click="onEdit(row)">
{{ $t('AbpUi.Edit') }}
</Button>
</div>
</template>
</Grid>
</template>
<style lang="scss" scoped></style>

2
apps/vben5/packages/@abp/data-protection/src/components/index.ts

@ -0,0 +1,2 @@
export { default as EntityRuleModal } from './entity-rules/EntityRuleModal.vue';
export { default as EntityTypeInfoTable } from './entity-type-infos/EntityTypeInfoTable.vue';

3
apps/vben5/packages/@abp/data-protection/src/index.ts

@ -0,0 +1,3 @@
export * from './api';
export * from './components';
export * from './types';

101
apps/vben5/packages/@abp/data-protection/src/types/entityRules.ts

@ -0,0 +1,101 @@
import type { AuditedEntityDto } from '@abp/core';
enum DataAccessOperation {
/** 删除 */
Delete = 2,
/** 查询 */
Read = 0,
/** 更新 */
Write = 1,
}
enum DataAccessFilterOperate {
/** 包含 */
Contains = 9,
/** 右包含 */
EndsWith = 8,
/** 等于 */
Equal = 1,
/** 大于 */
Greater = 5,
/** 大于或等于 */
GreaterOrEqual = 6,
/** 小于 */
Less = 3,
/** 小于或等于 */
LessOrEqual = 4,
/** 不包含 */
NotContains = 10,
/** 不等于 */
NotEqual = 2,
/** 左包含 */
StartsWith = 7,
}
enum DataAccessFilterLogic {
/** 且 */
And = 0,
/** 或 */
Or = 1,
}
interface DataAccessFilterRule {
[key: string]: any;
field: string;
isLeft: boolean;
javaScriptType: string;
operate: DataAccessFilterOperate;
typeFullName: string;
value: string;
}
interface DataAccessFilterGroup {
groups: DataAccessFilterGroup[];
logic: DataAccessFilterLogic;
rules: DataAccessFilterRule[];
}
interface EntityRuleDtoBase extends AuditedEntityDto<string> {
accessedProperties: string[];
entityTypeFullName: string;
entityTypeId: string;
filterGroup: DataAccessFilterGroup;
isEnabled: boolean;
operation: DataAccessOperation;
tenantId?: string;
}
interface EntityEnumInfoModel {
key: string;
value: any;
}
interface EntityPropertyInfoModel {
displayName: string;
enums: EntityEnumInfoModel[];
javaScriptName: string;
javaScriptType: string;
name: string;
operates: DataAccessFilterOperate[];
typeFullName: string;
}
interface EntityTypeInfoModel {
displayName: string;
name: string;
properties: EntityPropertyInfoModel[];
}
interface EntityTypeInfoGetModel {
operation: DataAccessOperation;
}
export { DataAccessFilterLogic, DataAccessFilterOperate, DataAccessOperation };
export type {
DataAccessFilterGroup,
DataAccessFilterRule,
EntityRuleDtoBase,
EntityTypeInfoGetModel,
EntityTypeInfoModel,
};

38
apps/vben5/packages/@abp/data-protection/src/types/entityTypeInfos.ts

@ -0,0 +1,38 @@
import type {
AuditedEntityDto,
EntityDto,
PagedAndSortedResultRequestDto,
} from '@abp/core';
interface EntityEnumInfoDto extends EntityDto<string> {
displayName: string;
name: string;
value: string;
}
interface EntityPropertyInfoDto extends EntityDto<string> {
displayName: string;
enums: EntityEnumInfoDto[];
javaScriptType: string;
name: string;
typeFullName: string;
}
interface EntityTypeInfoDto extends AuditedEntityDto<string> {
displayName: string;
isAuditEnabled: boolean;
name: string;
properties: EntityPropertyInfoDto[];
typeFullName: string;
}
interface GetEntityTypeInfoListInput extends PagedAndSortedResultRequestDto {
filter?: string;
isAuditEnabled?: boolean;
}
export type {
EntityPropertyInfoDto,
EntityTypeInfoDto,
GetEntityTypeInfoListInput,
};

4
apps/vben5/packages/@abp/data-protection/src/types/index.ts

@ -0,0 +1,4 @@
export * from './entityRules';
export * from './entityTypeInfos';
export * from './roleEntityRules';
export * from './strategys';

38
apps/vben5/packages/@abp/data-protection/src/types/roleEntityRules.ts

@ -0,0 +1,38 @@
import type {
DataAccessFilterGroup,
DataAccessOperation,
EntityRuleDtoBase,
} from './entityRules';
interface RoleEntityRuleGetInput {
entityTypeId: string;
operation: DataAccessOperation;
roleName: string;
}
interface EntityRuleCreateOrUpdateDto {
accessedProperties?: string[];
filterGroup: DataAccessFilterGroup;
isEnabled: boolean;
operation: DataAccessOperation;
}
interface RoleEntityRuleCreateDto extends EntityRuleCreateOrUpdateDto {
entityTypeId: string;
roleId: string;
roleName: string;
}
type RoleEntityRuleUpdateDto = EntityRuleCreateOrUpdateDto;
interface RoleEntityRuleDto extends EntityRuleDtoBase {
roleId: string;
roleName: string;
}
export type {
RoleEntityRuleCreateDto,
RoleEntityRuleDto,
RoleEntityRuleGetInput,
RoleEntityRuleUpdateDto,
};

42
apps/vben5/packages/@abp/data-protection/src/types/strategys.ts

@ -0,0 +1,42 @@
/** 数据访问策略 */
enum DataAccessStrategy {
/** 可以访问所有数据 */
All = 0,
/** 仅当前用户组织机构及下级机构 */
CurrentAndSubOrganizationUnits = 5,
/** 仅当前用户组织机构 */
CurrentOrganizationUnits = 4,
/** 仅当前用户角色 */
CurrentRoles = 3,
/** 仅当前用户 */
CurrentUser = 2,
/** 自定义规则 */
Custom = 1,
}
interface SubjectStrategyGetInput {
subjectId: string;
subjectName: string;
}
interface SubjectStrategySetInput {
isEnabled: boolean;
strategy: DataAccessStrategy;
subjectId: string;
subjectName: string;
}
interface SubjectStrategyDto {
isEnabled: boolean;
strategy: DataAccessStrategy;
subjectId: string;
subjectName: string;
}
export { DataAccessStrategy };
export type {
SubjectStrategyDto,
SubjectStrategyGetInput,
SubjectStrategySetInput,
};

6
apps/vben5/packages/@abp/data-protection/tsconfig.json

@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/web.json",
"include": ["src"],
"exclude": ["node_modules"]
}

37
apps/vben5/packages/@abp/demo/package.json

@ -0,0 +1,37 @@
{
"name": "@abp/demo",
"version": "9.0.4",
"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/demo"
},
"license": "MIT",
"type": "module",
"sideEffects": [
"**/*.css"
],
"exports": {
".": {
"types": "./src/index.ts",
"default": "./src/index.ts"
}
},
"dependencies": {
"@abp/core": "workspace:*",
"@abp/data-protection": "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/locales": "workspace:*",
"ant-design-vue": "catalog:",
"dayjs": "catalog:",
"vue": "catalog:*"
}
}

2
apps/vben5/packages/@abp/demo/src/api/index.ts

@ -0,0 +1,2 @@
export { useAuthorsApi } from './useAuthorsApi';
export { useBooksApi } from './useBooksApi';

23
apps/vben5/packages/@abp/demo/src/api/useAuthorsApi.ts

@ -0,0 +1,23 @@
import type { PagedResultDto } from '@abp/core';
import type { AuthorDto, GetAuthorPagedListInput } from '../types/authors';
import { useRequest } from '@abp/request';
export function useAuthorsApi() {
const { cancel, request } = useRequest();
function getPagedListApi(
input?: GetAuthorPagedListInput,
): Promise<PagedResultDto<AuthorDto>> {
return request<PagedResultDto<AuthorDto>>('/api/demo/authors', {
method: 'GET',
params: input,
});
}
return {
cancel,
getPagedListApi,
};
}

71
apps/vben5/packages/@abp/demo/src/api/useBooksApi.ts

@ -0,0 +1,71 @@
import type { PagedResultDto } from '@abp/core';
import type {
EntityTypeInfoGetModel,
EntityTypeInfoModel,
} from '@abp/data-protection';
import type {
BookDto,
CreateUpdateBookDto,
GetBookPagedListInput,
} from '../types/books';
import { useRequest } from '@abp/request';
export function useBooksApi() {
const { cancel, request } = useRequest();
function createApi(input: CreateUpdateBookDto): Promise<BookDto> {
return request<BookDto>(`/api/demo/books`, {
data: input,
method: 'POST',
});
}
function deleteApi(id: string): Promise<void> {
return request(`/api/demo/books/${id}`, {
method: 'DELETE',
});
}
function getApi(id: string): Promise<BookDto> {
return request<BookDto>(`/api/demo/books/${id}`, {
method: 'GET',
});
}
function getPagedListApi(
input?: GetBookPagedListInput,
): Promise<PagedResultDto<BookDto>> {
return request<PagedResultDto<BookDto>>('/api/demo/books', {
method: 'GET',
params: input,
});
}
function getEntityInfoApi(
input: EntityTypeInfoGetModel,
): Promise<EntityTypeInfoModel> {
return request<EntityTypeInfoModel>('/api/demo/books/entity', {
method: 'GET',
params: input,
});
}
function updateApi(id: string, input: CreateUpdateBookDto): Promise<BookDto> {
return request<BookDto>(`/api/demo/books/${id}`, {
data: input,
method: 'PUT',
});
}
return {
cancel,
createApi,
deleteApi,
getApi,
getEntityInfoApi,
getPagedListApi,
updateApi,
};
}

212
apps/vben5/packages/@abp/demo/src/components/books/BookModal.vue

@ -0,0 +1,212 @@
<script setup lang="ts">
import type { EntityTypeInfoModel } from '@abp/data-protection';
import type { FormSchema } from '../../../../../@core/ui-kit/form-ui/src/types';
import type { BookDto, CreateUpdateBookDto } from '../../types/books';
import { nextTick, ref } from 'vue';
import { useVbenForm, useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { DataAccessOperation } from '@abp/data-protection';
import { message } from 'ant-design-vue';
import { useAuthorsApi } from '../../api/useAuthorsApi';
import { useBooksApi } from '../../api/useBooksApi';
import { BookType } from '../../types/books';
const emits = defineEmits<{
(event: 'change', data: BookDto): void;
}>();
const { getPagedListApi } = useAuthorsApi();
const { createApi, getApi, getEntityInfoApi, updateApi } = useBooksApi();
const allowShowFields = ref<string[]>([]);
const defaultFormSchema: FormSchema[] = [
{
component: 'Input',
fieldName: 'id',
formItemClass: 'hidden',
label: 'id',
},
{
component: 'Input',
fieldName: 'name',
label: $t('Demo.DisplayName:Name'),
rules: 'required',
},
{
component: 'Select',
componentProps: {
options: [
{
label: $t('Demo.BookType:Undefined'),
value: BookType.Undefined,
},
{
label: $t('Demo.BookType:Adventure'),
value: BookType.Adventure,
},
{
label: $t('Demo.BookType:Biography'),
value: BookType.Biography,
},
{
label: $t('Demo.BookType:Dystopia'),
value: BookType.Dystopia,
},
{
label: $t('Demo.BookType:Fantastic'),
value: BookType.Fantastic,
},
{
label: $t('Demo.BookType:Horror'),
value: BookType.Horror,
},
{
label: $t('Demo.BookType:Science'),
value: BookType.Science,
},
{
label: $t('Demo.BookType:ScienceFiction'),
value: BookType.ScienceFiction,
},
{
label: $t('Demo.BookType:Poetry'),
value: BookType.Poetry,
},
],
},
fieldName: 'type',
label: $t('Demo.DisplayName:Type'),
rules: 'selectRequired',
},
{
component: 'DatePicker',
componentProps: {
valueFormat: 'YYYY-MM-DD',
},
fieldName: 'publishDate',
label: $t('Demo.DisplayName:PublishDate'),
rules: 'selectRequired',
},
{
component: 'InputNumber',
fieldName: 'price',
label: $t('Demo.DisplayName:Price'),
rules: 'required',
},
{
component: 'ApiSelect',
componentProps: {
api: getPagedListApi,
labelField: 'name',
resultField: 'items',
valueField: 'id',
},
fieldName: 'authorId',
label: $t('Demo.DisplayName:AuthorId'),
rules: 'selectRequired',
},
];
const [Form, formApi] = useVbenForm({
commonConfig: {
colon: true,
controlClass: 'w-full',
},
handleSubmit: onSubmit,
schema: [],
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
await formApi.validateAndSubmitForm();
},
async onOpenChange(isOpen) {
if (isOpen) {
await onInit();
await onGet();
}
},
});
async function onInit() {
const { id } = modalApi.getData<BookDto>();
if (!id) {
nextTick(() => {
formApi.setState({
schema: [...defaultFormSchema],
});
});
return;
}
const [entityReadRule, entityWriteRule] = await Promise.all([
getEntityInfoApi({
operation: DataAccessOperation.Read,
}),
getEntityInfoApi({
operation: DataAccessOperation.Write,
}),
]);
updateSchema(entityReadRule, entityWriteRule);
}
function updateSchema(
readRule: EntityTypeInfoModel,
writeRule: EntityTypeInfoModel,
) {
const readProps = readRule.properties.map((x) => x.javaScriptName);
const writeProps = new Set(writeRule.properties.map((x) => x.javaScriptName));
const schemas = defaultFormSchema
.filter((schema) => readProps.includes(schema.fieldName))
.map((schema) => {
return {
...schema,
disabled: !writeProps.has(schema.fieldName),
};
});
allowShowFields.value = readProps;
nextTick(() => {
formApi.setState({
schema: schemas,
});
});
}
async function onGet() {
formApi.resetForm();
const { id } = modalApi.getData<BookDto>();
if (id) {
const dto = await getApi(id);
allowShowFields.value.forEach((field) => {
formApi.setFieldValue(field, Reflect.get(dto, field));
});
}
}
async function onSubmit(values: Record<string, any>) {
const input = values as CreateUpdateBookDto;
try {
modalApi.setState({ submitting: true });
const api = values.id ? updateApi(values.id, input) : createApi(input);
const dto = await api;
message.success($t('AbpUi.SavedSuccessfully'));
emits('change', dto);
modalApi.close();
} finally {
modalApi.setState({ submitting: false });
}
}
</script>
<template>
<Modal :title="$t('Demo.Book')">
<Form />
</Modal>
</template>
<style scoped></style>

204
apps/vben5/packages/@abp/demo/src/components/books/BookTable.vue

@ -0,0 +1,204 @@
<script setup lang="ts">
import type { VxeGridListeners, VxeGridProps, VxeGridPropTypes } from '@abp/ui';
import type { VbenFormProps } from '@vben/common-ui';
import type { BookDto } from '../../types/books';
import { defineAsyncComponent, h, nextTick, onMounted, reactive } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { DataAccessOperation } from '@abp/data-protection';
import { useVbenVxeGrid } from '@abp/ui';
import { DeleteOutlined, EditOutlined } from '@ant-design/icons-vue';
import { Button, message, Modal } from 'ant-design-vue';
import { useBooksApi } from '../../api/useBooksApi';
import { BookPermissions } from '../../constants/permissions';
defineOptions({
name: 'GdprTable',
});
const { deleteApi, getEntityInfoApi, getPagedListApi } = useBooksApi();
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 actionColumns = reactive<VxeGridPropTypes.Columns<BookDto>>([
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: $t('AbpUi.Actions'),
width: 180,
},
]);
const baseColumns = reactive<VxeGridPropTypes.Columns<BookDto>>([
{
align: 'left',
field: 'name',
minWidth: 150,
title: $t('Demo.DisplayName:Name'),
},
{
align: 'left',
field: 'authorName',
minWidth: 150,
title: $t('Demo.DisplayName:AuthorId'),
},
{
align: 'left',
field: 'publishDate',
minWidth: 200,
title: $t('Demo.DisplayName:PublishDate'),
},
{
align: 'left',
field: 'price',
minWidth: 150,
title: $t('Demo.DisplayName:Price'),
},
]);
const gridOptions: VxeGridProps<BookDto> = {
columns: [...baseColumns, ...actionColumns],
exportConfig: {},
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getPagedListApi({
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
});
},
},
response: {
total: 'totalCount',
list: 'items',
},
},
toolbarConfig: {
refresh: true,
},
};
const gridEvents: VxeGridListeners<BookDto> = {
cellClick: () => {},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridEvents,
gridOptions,
});
const [BookModal, modalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(() => import('./BookModal.vue')),
});
function onCreate() {
modalApi.setData({});
modalApi.open();
}
function onEdit(record: BookDto) {
modalApi.setData(record);
modalApi.open();
}
async function onDelete(record: BookDto) {
Modal.confirm({
centered: true,
content: $t('AbpUi.ItemWillBeDeletedMessageWithFormat', [record.name]),
maskClosable: true,
onOk: async () => {
await deleteApi(record.id);
message.success($t('AbpUi.DeletedSuccessfully'));
await gridApi.query();
},
title: $t('AbpUi.AreYouSure'),
});
}
async function onInit() {
const entityRule = await getEntityInfoApi({
operation: DataAccessOperation.Read,
});
if (!entityRule.properties?.length) {
return;
}
const allowProperties = new Set(
entityRule.properties.map((x) => x.javaScriptName),
);
nextTick(() => {
gridApi.setGridOptions({
columns: [
...baseColumns.filter((x) => allowProperties.has(x.field!)),
...actionColumns,
],
});
});
}
onMounted(onInit);
</script>
<template>
<Grid :table-title="$t('Demo.Book')">
<template #toolbar-tools>
<Button
type="primary"
v-access:code="[BookPermissions.Create]"
@click="onCreate"
>
{{ $t('Demo.NewBook') }}
</Button>
</template>
<template #action="{ row }">
<div class="flex flex-row">
<Button
v-access:code="[BookPermissions.Update]"
:icon="h(EditOutlined)"
type="link"
block
@click="onEdit(row)"
>
{{ $t('AbpUi.Edit') }}
</Button>
<Button
v-access:code="[BookPermissions.Delete]"
:icon="h(DeleteOutlined)"
type="link"
danger
block
@click="onDelete(row)"
>
{{ $t('AbpUi.Delete') }}
</Button>
</div>
</template>
</Grid>
<BookModal @change="() => gridApi.query()" />
</template>
<style lang="scss" scoped></style>

1
apps/vben5/packages/@abp/demo/src/components/index.ts

@ -0,0 +1 @@
export { default as BookTable } from './books/BookTable.vue';

8
apps/vben5/packages/@abp/demo/src/constants/permissions.ts

@ -0,0 +1,8 @@
export const BookPermissions = {
/** 新增 */
Create: 'Demo.Books.Create',
/** 删除 */
Delete: 'Demo.Books.Delete',
/** 更新 */
Update: 'Demo.Books.Edit',
};

3
apps/vben5/packages/@abp/demo/src/index.ts

@ -0,0 +1,3 @@
export * from './api';
export * from './components';
export * from './types';

13
apps/vben5/packages/@abp/demo/src/types/authors.ts

@ -0,0 +1,13 @@
import type { EntityDto, PagedAndSortedResultRequestDto } from '@abp/core';
interface AuthorDto extends EntityDto<string> {
birthDate: string;
name: string;
shortBio?: string;
}
interface GetAuthorPagedListInput extends PagedAndSortedResultRequestDto {
filter?: string;
}
export type { AuthorDto, GetAuthorPagedListInput };

41
apps/vben5/packages/@abp/demo/src/types/books.ts

@ -0,0 +1,41 @@
import type {
AuditedEntityDto,
PagedAndSortedResultRequestDto,
} from '@abp/core';
enum BookType {
Undefined,
Adventure,
Biography,
Dystopia,
Fantastic,
Horror,
Science,
ScienceFiction,
Poetry,
}
interface BookDto extends AuditedEntityDto<string> {
authorId: string;
authorName: string;
name: string;
price?: number;
publishDate: string;
type: BookType;
}
interface CreateUpdateBookDto {
authorId: string;
name: string;
price: number;
publishDate: string;
type: BookType;
}
interface GetBookPagedListInput extends PagedAndSortedResultRequestDto {
filter?: string;
}
export { BookType };
export type { BookDto, CreateUpdateBookDto, GetBookPagedListInput };

1
apps/vben5/packages/@abp/demo/src/types/index.ts

@ -0,0 +1 @@
export * from './authors';

6
apps/vben5/packages/@abp/demo/tsconfig.json

@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/web.json",
"include": ["src"],
"exclude": ["node_modules"]
}

1
apps/vben5/packages/@abp/identity/package.json

@ -22,6 +22,7 @@
"dependencies": {
"@abp/auditing": "workspace:*",
"@abp/core": "workspace:*",
"@abp/data-protection": "workspace:*",
"@abp/permissions": "workspace:*",
"@abp/request": "workspace:*",
"@abp/ui": "workspace:*",

2
apps/vben5/packages/@abp/identity/src/api/useOrganizationUnitsApi.ts

@ -234,7 +234,7 @@ export function useOrganizationUnitsApi() {
): Promise<void> {
return request(`/api/identity/organization-units/${id}/roles`, {
data: input,
method: 'GET',
method: 'POST',
});
}

528
apps/vben5/packages/@abp/identity/src/components/roles/RoleRuleDrawer.vue

@ -0,0 +1,528 @@
<script setup lang="ts">
import type {
DataAccessFilterGroup,
DataAccessFilterRule,
EntityPropertyInfoDto,
EntityTypeInfoDto,
RoleEntityRuleDto,
} from '@abp/data-protection';
import type { FormInstance } from 'ant-design-vue/es/form/Form';
import type { IdentityRoleDto } from '../../types';
import { h, reactive, ref, toValue, useTemplateRef } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { useLocalization, useLocalizationSerializer } from '@abp/core';
import {
DataAccessFilterLogic,
DataAccessFilterOperate,
DataAccessOperation,
DataAccessStrategy,
useEntityTypeInfosApi,
useRoleEntityRulesApi,
useSubjectStrategysApi,
} from '@abp/data-protection';
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue';
import {
Button,
Checkbox,
DatePicker,
Form,
FormItem,
FormItemRest,
Input,
InputNumber,
message,
Popconfirm,
Select,
Switch,
} from 'ant-design-vue';
const defaultModel: RoleEntityRuleDto = {
accessedProperties: [],
creationTime: new Date(),
entityTypeFullName: '',
entityTypeId: '',
filterGroup: {
groups: [],
logic: DataAccessFilterLogic.Or,
rules: [],
},
id: '',
isEnabled: true,
operation: DataAccessOperation.Read,
roleId: '',
roleName: '',
strategy: DataAccessStrategy.All,
};
const form = useTemplateRef<FormInstance>('form');
const formModel = ref<RoleEntityRuleDto>({ ...defaultModel });
const entityTypes = ref<EntityTypeInfoDto[]>([]);
const entityTypeProps = ref<EntityPropertyInfoDto[]>([]);
const strategyOptions = reactive([
{
label: '可以访问所有数据',
value: DataAccessStrategy.All,
},
{
label: '自定义规则',
value: DataAccessStrategy.Custom,
},
{
label: '仅当前用户',
value: DataAccessStrategy.CurrentUser,
},
{
label: '仅当前用户角色',
value: DataAccessStrategy.CurrentRoles,
},
{
label: '仅当前用户组织机构',
value: DataAccessStrategy.CurrentOrganizationUnits,
},
{
label: '仅当前用户组织机构及下级机构',
value: DataAccessStrategy.CurrentAndSubOrganizationUnits,
},
]);
const operationOptions = reactive([
{
label: '查询',
value: DataAccessOperation.Read,
},
{
label: '编辑',
value: DataAccessOperation.Write,
},
{
label: '删除',
value: DataAccessOperation.Delete,
},
]);
const logicOptions = reactive([
{
label: '且',
value: DataAccessFilterLogic.And,
},
{
label: '或',
value: DataAccessFilterLogic.Or,
},
]);
const dataAccessFilterOptions = reactive([
{
label: '等于',
value: DataAccessFilterOperate.Equal,
},
{
label: '大于',
value: DataAccessFilterOperate.Greater,
},
{
label: '大于等于',
value: DataAccessFilterOperate.GreaterOrEqual,
},
{
label: '小于',
value: DataAccessFilterOperate.Less,
},
{
label: '小于等于',
value: DataAccessFilterOperate.LessOrEqual,
},
{
label: '不等于',
value: DataAccessFilterOperate.NotEqual,
},
{
label: '左包含',
value: DataAccessFilterOperate.StartsWith,
},
{
label: '右包含',
value: DataAccessFilterOperate.EndsWith,
},
{
label: '不包含',
value: DataAccessFilterOperate.NotContains,
},
{
label: '不等于',
value: DataAccessFilterOperate.NotEqual,
},
]);
const [Drawer, drawerApi] = useVbenDrawer({
class: 'w-1/2',
onConfirm: onSubmit,
async onOpenChange(isOpen) {
entityTypeProps.value = [];
formModel.value = { ...defaultModel };
if (isOpen) {
await onInitStrategys();
}
},
});
const { getApi: getEntityTypeInfoApi, getPagedListApi } =
useEntityTypeInfosApi();
const {
createApi,
getApi: getRoleEntityRuleApi,
updateApi,
} = useRoleEntityRulesApi();
const { getApi: getStrategysApi, setApi: setStrategysApi } =
useSubjectStrategysApi();
const { deserialize } = useLocalizationSerializer();
const { Lr } = useLocalization();
async function onInitStrategys() {
const roleInfo = drawerApi.getData<IdentityRoleDto>();
const result = await getStrategysApi({
subjectId: roleInfo.name,
subjectName: 'R',
});
if (result) {
formModel.value.isEnabled = result.isEnabled;
formModel.value.strategy = result.strategy;
await onInitEntityTypes();
}
}
async function onInitEntityTypes(filter?: string) {
const { items } = await getPagedListApi({ filter });
entityTypes.value = items.map((item) => {
const displayName = deserialize(item.displayName);
return {
...item,
displayName: Lr(displayName.resourceName, displayName.name),
};
});
}
function onNewGroup(logic: DataAccessFilterLogic) {
formModel.value.filterGroup.groups.push({
groups: [],
logic,
rules: [],
});
}
function onDeleteGroup(index: number) {
formModel.value.filterGroup.groups.splice(index, 1);
}
function onNewRule(group: DataAccessFilterGroup) {
group.rules.push({
field: '',
isLeft: false,
javaScriptType: '',
operate: DataAccessFilterOperate.Equal,
typeFullName: '',
value: '',
});
}
function onDeleteRule(group: DataAccessFilterGroup, index: number) {
group.rules.splice(index, 1);
}
async function onSubmit() {
try {
await form.value?.validate();
drawerApi.setState({ confirmLoading: true });
const input = toValue(formModel);
await (input.strategy === DataAccessStrategy.Custom
? submitCustomRule(input)
: submitStrategys(input));
message.success($t('AbpUi.SavedSuccessfully'));
drawerApi.close();
} finally {
drawerApi.setState({ confirmLoading: false });
}
}
async function submitStrategys(input: RoleEntityRuleDto) {
const roleInfo = drawerApi.getData<IdentityRoleDto>();
await setStrategysApi({
isEnabled: input.isEnabled,
strategy: input.strategy,
subjectId: roleInfo.name,
subjectName: 'R',
});
}
async function submitCustomRule(input: RoleEntityRuleDto) {
const roleInfo = drawerApi.getData<IdentityRoleDto>();
const api = input.id
? updateApi(input.id, {
accessedProperties: input.accessedProperties,
entityTypeId: input.entityTypeId,
filterGroup: input.filterGroup,
isEnabled: input.isEnabled,
operation: input.operation,
roleId: roleInfo.id,
roleName: roleInfo.id,
})
: createApi({
accessedProperties: input.accessedProperties,
entityTypeId: input.entityTypeId,
filterGroup: input.filterGroup,
isEnabled: input.isEnabled,
operation: input.operation,
roleId: roleInfo.id,
roleName: roleInfo.name,
});
await api;
}
async function onGetEntityType() {
try {
drawerApi.setState({ confirmLoading: true, loading: true });
const { entityTypeId, operation } = formModel.value;
const roleInfo = drawerApi.getData<IdentityRoleDto>();
const ruleEntityRule = await getRoleEntityRuleApi({
entityTypeId,
operation,
roleName: roleInfo.name,
});
formModel.value = ruleEntityRule?.id
? {
...ruleEntityRule,
strategy: DataAccessStrategy.Custom,
}
: {
...defaultModel,
entityTypeId,
operation,
};
} finally {
drawerApi.setState({ confirmLoading: false, loading: false });
}
}
async function onStrategyChange(strategy: DataAccessStrategy) {
if (strategy === DataAccessStrategy.Custom) {
await onInitEntityTypes();
}
}
async function onEntityTypeChange(entityTypeId: string) {
const entityTypeInfo = await getEntityTypeInfoApi(entityTypeId);
entityTypeProps.value = entityTypeInfo.properties.map((item) => {
const displayName = deserialize(item.displayName);
if (item.enums) {
item.enums = item.enums.map((enumItem) => {
const enumDisplayName = deserialize(enumItem.displayName);
return {
...enumItem,
displayName: Lr(enumDisplayName.resourceName, enumDisplayName.name),
};
});
}
return {
...item,
displayName: Lr(displayName.resourceName, displayName.name),
};
});
await onGetEntityType();
}
//
function onPropertyTypeChange(
rule: DataAccessFilterRule,
option: EntityPropertyInfoDto,
) {
rule.value = '';
rule.typeFullName = option.typeFullName;
rule.javaScriptType = option.javaScriptType;
}
</script>
<template>
<Drawer title="数据权限">
<Form
:model="formModel"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 24 }"
>
<FormItem name="isEnabled" label="是否启用">
<Checkbox v-model:checked="formModel.isEnabled">是否启用</Checkbox>
</FormItem>
<FormItem name="isEnabled" label="数据权限策略">
<Select
class="w-full"
:options="strategyOptions"
v-model:value="formModel.strategy"
@change="(value) => onStrategyChange(Number(value!))"
/>
</FormItem>
<template v-if="formModel.strategy === DataAccessStrategy.Custom">
<FormItem required name="entityTypeId" label="实体类型">
<Select
class="w-full"
:options="entityTypes"
:field-names="{ label: 'displayName', value: 'id' }"
v-model:value="formModel.entityTypeId"
@change="(value) => onEntityTypeChange(value!.toString())"
/>
</FormItem>
<FormItem required name="operation" label="操作">
<Select
class="w-full"
:options="operationOptions"
v-model:value="formModel.operation"
@change="onGetEntityType"
/>
</FormItem>
<FormItem name="accessedProperties" label="可访问字段">
<Select
class="w-full"
mode="multiple"
:options="entityTypeProps"
v-model:value="formModel.accessedProperties"
:field-names="{ label: 'displayName', value: 'name' }"
/>
</FormItem>
<FormItemRest>
<!-- 分组 -->
<div class="flex w-full flex-col">
<div class="w-full items-center divide-dashed border-2">
<div class="m-2 text-sm">数据访问规则</div>
<template
v-for="(group, gi) in formModel.filterGroup.groups"
:key="gi"
>
<div class="flex flex-row items-center justify-items-center">
<!-- 条件 -->
<div class="m-2 flex w-full flex-row">
<div class="w-full border-2 border-dashed">
<template v-for="(rule, ri) in group.rules" :key="ri">
<div
class="m-2 flex flex-row items-center justify-items-center gap-1"
>
<div class="basis-2/5">
<Select
class="w-full"
:options="entityTypeProps"
v-model:value="rule.field"
:field-names="{
label: 'displayName',
value: 'name',
}"
@change="
(_, option: any) =>
onPropertyTypeChange(rule, option)
"
/>
</div>
<div class="basis-1/5">
<Select
class="w-full"
:options="dataAccessFilterOptions"
v-model:value="rule.operate"
/>
</div>
<div class="basis-2/5">
<InputNumber
v-if="rule.javaScriptType === 'number'"
class="w-full"
v-model:value="rule.value"
/>
<Switch
v-else-if="rule.javaScriptType === 'boolean'"
v-model:checked="rule.value"
/>
<DatePicker
v-else-if="rule.javaScriptType === 'Date'"
class="w-full"
v-model:value="rule.value"
value-format="YYYY-MM-DDT00:00:00"
/>
<Input
v-else
class="w-full"
v-model:value="rule.value"
/>
</div>
<div class="basis-1/5">
<Popconfirm
title="你确定吗?"
description="将删除此过滤条件"
@confirm="onDeleteRule(group, ri)"
>
<Button
type="link"
danger
:icon="h(DeleteOutlined)"
/>
</Popconfirm>
</div>
</div>
</template>
<div
class="flex flex-row items-center justify-items-center gap-2"
>
<div class="m-2 min-w-[60px]">
<Select
size="small"
class="w-full"
v-model:value="group.logic"
:options="logicOptions"
/>
</div>
<Button
type="link"
:icon="h(PlusOutlined)"
@click="onNewRule(group)"
>
增加条件
</Button>
<Popconfirm
title="你确定吗?"
description="将删除此条件分组"
@confirm="onDeleteGroup(gi)"
>
<Button type="link" :icon="h(DeleteOutlined)" danger>
删除分组
</Button>
</Popconfirm>
</div>
</div>
</div>
</div>
</template>
<div
class="flex flex-row items-center justify-items-center gap-2"
>
<div class="m-2 min-w-[60px]">
<Select
size="small"
class="w-full"
v-model:value="formModel.filterGroup.logic"
:options="logicOptions"
/>
</div>
<div>
<Button
size="small"
type="link"
:icon="h(PlusOutlined)"
@click="onNewGroup(formModel.filterGroup.logic)"
>
增加分组
</Button>
</div>
</div>
</div>
</div>
</FormItemRest>
</template>
</Form>
</Drawer>
</template>
<style scoped></style>

13
apps/vben5/packages/@abp/identity/src/components/roles/RoleTable.vue

@ -39,6 +39,7 @@ const AuditLogIcon = createIconifyIcon('fluent-mdl2:compliance-audit');
const RoleModal = defineAsyncComponent(() => import('./RoleModal.vue'));
const ClaimModal = defineAsyncComponent(() => import('./RoleClaimModal.vue'));
const RuleModal = defineAsyncComponent(() => import('./RoleRuleDrawer.vue'));
const abpStore = useAbpStore();
const { publish } = useEventBus();
@ -52,6 +53,9 @@ const [RolePermissionModal, permissionModalApi] = useVbenModal({
const [RoleClaimModal, claimModalApi] = useVbenModal({
connectedComponent: ClaimModal,
});
const [RoleRuleDrawer, roleRuleDrawerApi] = useVbenDrawer({
connectedComponent: RuleModal,
});
const [RoleChangeDrawer, roleChangeDrawerApi] = useVbenDrawer({
connectedComponent: EntityChangeDrawer,
});
@ -169,6 +173,11 @@ const handleMenuClick = async (row: IdentityRoleDto, info: MenuInfo) => {
roleChangeDrawerApi.open();
break;
}
case 'entity-rules': {
roleRuleDrawerApi.setData(row);
roleRuleDrawerApi.open();
break;
}
case 'permissions': {
const roles = abpStore.application?.currentUser.roles ?? [];
permissionModalApi.setData({
@ -272,6 +281,9 @@ function onPermissionChange(_name: string, key: string) {
>
{{ $t('AbpAuditLogging.EntitiesChanged') }}
</MenuItem>
<MenuItem key="entity-rules">
{{ '数据权限' }}
</MenuItem>
</Menu>
</template>
<Button :icon="h(EllipsisOutlined)" type="link" />
@ -283,6 +295,7 @@ function onPermissionChange(_name: string, key: string) {
<RoleClaimModal @change="query" />
<RolePermissionModal @change="onPermissionChange" />
<RoleChangeDrawer />
<RoleRuleDrawer />
</template>
<style lang="scss" scoped></style>

22
apps/vben5/packages/@abp/identity/src/components/users/UserModal.vue

@ -107,7 +107,6 @@ const [Modal, modalApi] = useVbenModal({
if (userDto?.id) {
await Promise.all([
initUserInfo(userDto.id),
manageRolePolicy && initUserRoles(userDto.id),
manageRolePolicy && initAssignableRoles(),
checkManageOuPolicy() && initOrganizationUnitTree(userDto.id),
]);
@ -142,22 +141,19 @@ function checkManageOuPolicy() {
* @param userId 用户id
*/
async function initUserInfo(userId: string) {
const dto = await getApi(userId);
formModel.value = dto;
const [userInfo, userRoleResult] = await Promise.all([
getApi(userId),
getRolesApi(userId),
]);
formModel.value = {
...userInfo,
roleNames: userRoleResult.items.map((item) => item.name),
};
modalApi.setState({
title: `${$t('AbpIdentity.Users')} - ${dto.userName}`,
title: `${$t('AbpIdentity.Users')} - ${userInfo.userName}`,
});
}
/**
* 初始化用户角色
* @param userId 用户id
*/
async function initUserRoles(userId: string) {
const { items } = await getRolesApi(userId);
formModel.value.roleNames = items.map((item) => item.name);
}
/** 初始化可用角色列表 */
async function initAssignableRoles() {
const { items } = await getAssignableRolesApi();

6
apps/vben5/packages/@abp/ui/src/components/vxe-table/index.ts

@ -2,4 +2,8 @@ export { setupVbenVxeTable } from './init';
export type { VxeTableGridOptions } from './types';
export { default as VbenVxeGrid } from './use-vxe-grid.vue';
export type { VxeGridListeners, VxeGridProps } from 'vxe-table';
export type {
VxeGridListeners,
VxeGridProps,
VxeGridPropTypes,
} from 'vxe-table';

Loading…
Cancel
Save