41 changed files with 1865 additions and 15 deletions
@ -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> |
|||
@ -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> |
|||
@ -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:*" |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
export { useEntityTypeInfosApi } from './useEntityTypeInfosApi'; |
|||
export { useRoleEntityRulesApi } from './useRoleEntityRulesApi'; |
|||
export { useSubjectStrategysApi } from './useSubjectStrategysApi'; |
|||
@ -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, |
|||
}; |
|||
} |
|||
@ -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, |
|||
}; |
|||
} |
|||
@ -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, |
|||
}; |
|||
} |
|||
@ -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> |
|||
@ -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> |
|||
@ -0,0 +1,2 @@ |
|||
export { default as EntityRuleModal } from './entity-rules/EntityRuleModal.vue'; |
|||
export { default as EntityTypeInfoTable } from './entity-type-infos/EntityTypeInfoTable.vue'; |
|||
@ -0,0 +1,3 @@ |
|||
export * from './api'; |
|||
export * from './components'; |
|||
export * from './types'; |
|||
@ -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, |
|||
}; |
|||
@ -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, |
|||
}; |
|||
@ -0,0 +1,4 @@ |
|||
export * from './entityRules'; |
|||
export * from './entityTypeInfos'; |
|||
export * from './roleEntityRules'; |
|||
export * from './strategys'; |
|||
@ -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, |
|||
}; |
|||
@ -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, |
|||
}; |
|||
@ -0,0 +1,6 @@ |
|||
{ |
|||
"$schema": "https://json.schemastore.org/tsconfig", |
|||
"extends": "@vben/tsconfig/web.json", |
|||
"include": ["src"], |
|||
"exclude": ["node_modules"] |
|||
} |
|||
@ -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:*" |
|||
} |
|||
} |
|||
@ -0,0 +1,2 @@ |
|||
export { useAuthorsApi } from './useAuthorsApi'; |
|||
export { useBooksApi } from './useBooksApi'; |
|||
@ -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, |
|||
}; |
|||
} |
|||
@ -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, |
|||
}; |
|||
} |
|||
@ -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> |
|||
@ -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> |
|||
@ -0,0 +1 @@ |
|||
export { default as BookTable } from './books/BookTable.vue'; |
|||
@ -0,0 +1,8 @@ |
|||
export const BookPermissions = { |
|||
/** 新增 */ |
|||
Create: 'Demo.Books.Create', |
|||
/** 删除 */ |
|||
Delete: 'Demo.Books.Delete', |
|||
/** 更新 */ |
|||
Update: 'Demo.Books.Edit', |
|||
}; |
|||
@ -0,0 +1,3 @@ |
|||
export * from './api'; |
|||
export * from './components'; |
|||
export * from './types'; |
|||
@ -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 }; |
|||
@ -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 }; |
|||
@ -0,0 +1 @@ |
|||
export * from './authors'; |
|||
@ -0,0 +1,6 @@ |
|||
{ |
|||
"$schema": "https://json.schemastore.org/tsconfig", |
|||
"extends": "@vben/tsconfig/web.json", |
|||
"include": ["src"], |
|||
"exclude": ["node_modules"] |
|||
} |
|||
@ -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> |
|||
Loading…
Reference in new issue