Browse Source

Merge pull request #892 from colinin/rel-7.4.0

Rel 7.4.0
pull/914/head
yx lin 2 years ago
committed by GitHub
parent
commit
b0a58e7db1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .github/workflows/build.yml
  2. 4
      .github/workflows/release.yml
  3. 4
      apps/vue/.env.development
  4. 40
      apps/vue/src/api/feature-management/definitions/features/index.ts
  5. 36
      apps/vue/src/api/feature-management/definitions/features/model/index.ts
  6. 40
      apps/vue/src/api/feature-management/definitions/groups/index.ts
  7. 19
      apps/vue/src/api/feature-management/definitions/groups/model/index.ts
  8. 20
      apps/vue/src/api/feature-management/features/index.ts
  9. 44
      apps/vue/src/api/feature-management/features/model/index.ts
  10. 26
      apps/vue/src/api/feature/feature.ts
  11. 34
      apps/vue/src/api/feature/model/featureModel.ts
  12. 2
      apps/vue/src/api/messages/notifications.ts
  13. 3
      apps/vue/src/api/oss-management/oss.ts
  14. 40
      apps/vue/src/api/permission-management/definitions/groups/index.ts
  15. 19
      apps/vue/src/api/permission-management/definitions/groups/model/index.ts
  16. 40
      apps/vue/src/api/permission-management/definitions/permissions/index.ts
  17. 36
      apps/vue/src/api/permission-management/definitions/permissions/model/index.ts
  18. 44
      apps/vue/src/api/permission-management/model/permissionModel.ts
  19. 22
      apps/vue/src/api/permission-management/permission.ts
  20. 17
      apps/vue/src/api/permission-management/permissions/index.ts
  21. 44
      apps/vue/src/api/permission-management/permissions/model/index.ts
  22. 41
      apps/vue/src/api/settings-management/definitions/index.ts
  23. 32
      apps/vue/src/api/settings-management/definitions/model/index.ts
  24. 60
      apps/vue/src/api/settings-management/settings/index.ts
  25. 0
      apps/vue/src/api/settings-management/settings/model/index.ts
  26. 60
      apps/vue/src/api/settings/settings.ts
  27. 6
      apps/vue/src/api/text-templating/contents/index.ts
  28. 2
      apps/vue/src/api/text-templating/definitions/index.ts
  29. 5
      apps/vue/src/api/text-templating/definitions/model/index.ts
  30. 41
      apps/vue/src/api/webhooks/definitions/groups/index.ts
  31. 19
      apps/vue/src/api/webhooks/definitions/groups/model/index.ts
  32. 41
      apps/vue/src/api/webhooks/definitions/webhooks/index.ts
  33. 28
      apps/vue/src/api/webhooks/definitions/webhooks/model/index.ts
  34. 32
      apps/vue/src/api/webhooks/model/sendAttemptsModel.ts
  35. 45
      apps/vue/src/api/webhooks/model/subscriptionsModel.ts
  36. 73
      apps/vue/src/api/webhooks/send-attempts.ts
  37. 46
      apps/vue/src/api/webhooks/send-attempts/index.ts
  38. 40
      apps/vue/src/api/webhooks/send-attempts/model/index.ts
  39. 85
      apps/vue/src/api/webhooks/subscriptions.ts
  40. 55
      apps/vue/src/api/webhooks/subscriptions/index.ts
  41. 49
      apps/vue/src/api/webhooks/subscriptions/model/index.ts
  42. 255
      apps/vue/src/components/Abp/ExtraPropertyDictionary/ExtraPropertyDictionary.vue
  43. 3
      apps/vue/src/components/Abp/ExtraPropertyDictionary/index.ts
  44. 145
      apps/vue/src/components/Abp/FeatureModal/hooks/useFeatures.ts
  45. 0
      apps/vue/src/components/Abp/FeatureModal/index.ts
  46. 100
      apps/vue/src/components/Abp/FeatureModal/src/FeatureModal.vue
  47. 171
      apps/vue/src/components/Abp/LocalizableInput/LocalizableInput.vue
  48. 3
      apps/vue/src/components/Abp/LocalizableInput/index.ts
  49. 9
      apps/vue/src/components/Abp/LocalizableInput/props.ts
  50. 305
      apps/vue/src/components/Abp/SimpleStateChecking/SimpleStateChecking.vue
  51. 6
      apps/vue/src/components/Abp/SimpleStateChecking/index.ts
  52. 151
      apps/vue/src/components/Abp/SimpleStateChecking/src/features/RequireFeaturesSimpleStateChecker.vue
  53. 3
      apps/vue/src/components/Abp/SimpleStateChecking/src/features/index.ts
  54. 90
      apps/vue/src/components/Abp/SimpleStateChecking/src/globalFeatures/RequireGlobalFeaturesSimpleStateChecker.vue
  55. 3
      apps/vue/src/components/Abp/SimpleStateChecking/src/globalFeatures/index.ts
  56. 155
      apps/vue/src/components/Abp/SimpleStateChecking/src/permissions/RequirePermissionsSimpleStateChecker.vue
  57. 7
      apps/vue/src/components/Abp/SimpleStateChecking/src/permissions/index.ts
  58. 3
      apps/vue/src/components/Abp/SimpleStateChecking/src/permissions/typing.ts
  59. 3
      apps/vue/src/components/Abp/SimpleStateChecking/typing.ts
  60. 560
      apps/vue/src/components/Abp/StringValueType/StringValueTypeInput.vue
  61. 3
      apps/vue/src/components/Abp/StringValueType/index.ts
  62. 128
      apps/vue/src/components/Abp/StringValueType/validator.ts
  63. 122
      apps/vue/src/components/Abp/StringValueType/valueType.ts
  64. 5
      apps/vue/src/components/Abp/index.ts
  65. 7
      apps/vue/src/components/Form/src/componentMap.ts
  66. 4
      apps/vue/src/components/Form/src/types/index.ts
  67. 104
      apps/vue/src/components/Permission/src/PermissionModal.vue
  68. 4
      apps/vue/src/components/Permission/src/hooks/usePermissions.ts
  69. 2
      apps/vue/src/components/SettingManagement/src/SettingForm.vue
  70. 28
      apps/vue/src/hooks/abp/SimpleStateChecking/useRequireAuthenticatedSimpleStateChecker.ts
  71. 41
      apps/vue/src/hooks/abp/SimpleStateChecking/useRequireFeaturesSimpleStateChecker.ts
  72. 41
      apps/vue/src/hooks/abp/SimpleStateChecking/useRequireGlobalFeaturesSimpleStateChecker.ts
  73. 82
      apps/vue/src/hooks/abp/SimpleStateChecking/useRequirePermissionsSimpleStateChecker.ts
  74. 11
      apps/vue/src/hooks/abp/useAuthorization.ts
  75. 2
      apps/vue/src/hooks/abp/useDefineSettings.ts
  76. 34
      apps/vue/src/hooks/abp/useFeatures.ts
  77. 48
      apps/vue/src/hooks/abp/useGlobalFeatures.ts
  78. 24
      apps/vue/src/hooks/abp/useLocalization.ts
  79. 101
      apps/vue/src/hooks/abp/useLocalizationSerializer.ts
  80. 100
      apps/vue/src/hooks/abp/useSimpleStateCheck.ts
  81. 121
      apps/vue/src/locales/lang/en/component.ts
  82. 117
      apps/vue/src/locales/lang/zh-CN/component.ts
  83. 2
      apps/vue/src/store/modules/settings.ts
  84. 23
      apps/vue/src/utils/table.ts
  85. 4
      apps/vue/src/views/account/center/Setting.vue
  86. 433
      apps/vue/src/views/feature-management/definitions/features/components/FeatureDefinitionModal.vue
  87. 268
      apps/vue/src/views/feature-management/definitions/features/components/FeatureDefinitionTable.vue
  88. 22
      apps/vue/src/views/feature-management/definitions/features/datas/ModalData.ts
  89. 69
      apps/vue/src/views/feature-management/definitions/features/datas/TableData.ts
  90. 16
      apps/vue/src/views/feature-management/definitions/features/index.vue
  91. 199
      apps/vue/src/views/feature-management/definitions/groups/components/GroupDefinitionModal.vue
  92. 159
      apps/vue/src/views/feature-management/definitions/groups/components/GroupDefinitionTable.vue
  93. 15
      apps/vue/src/views/feature-management/definitions/groups/datas/ModalData.ts
  94. 35
      apps/vue/src/views/feature-management/definitions/groups/datas/TableData.ts
  95. 16
      apps/vue/src/views/feature-management/definitions/groups/index.vue
  96. 145
      apps/vue/src/views/feature/hooks/useFeature.ts
  97. 89
      apps/vue/src/views/feature/src/FeatureModal.vue
  98. 6
      apps/vue/src/views/localization/languages/components/LanguageTable.vue
  99. 6
      apps/vue/src/views/localization/resources/components/ResourceTable.vue
  100. 4
      apps/vue/src/views/localization/texts/components/ModalData.ts

2
.github/workflows/build.yml

@ -8,7 +8,7 @@ on:
- "**.csproj"
env:
DOTNET_VERSION: "7.0.102"
DOTNET_VERSION: "7.0.401"
jobs:
build:

4
.github/workflows/release.yml

@ -2,7 +2,7 @@ name: "Tagged Release"
on:
push:
branches: [ rel-7.3.3 ]
branches: [ rel-7.4.0 ]
jobs:
tagged-release:
@ -14,4 +14,4 @@ jobs:
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
prerelease: false
automatic_release_tag: "7.3.3"
automatic_release_tag: "7.4.0"

4
apps/vue/.env.development

@ -6,7 +6,7 @@ VITE_PUBLIC_PATH=/
# Cross-domain proxy, you can configure multiple
# Please note that no line breaks
VITE_PROXY=[["/connect","http://127.0.0.1:44385"],["/api","http://127.0.0.1:30000"],["/signalr-hubs","ws://127.0.0.1:30000"]]
VITE_PROXY=[["/connect","http://127.0.0.1:30000"],["/api","http://127.0.0.1:30000"],["/signalr-hubs","ws://127.0.0.1:30000"]]
# VITE_PROXY=[["/api","https://vvbin.cn/test"]]
# Delete console
@ -25,6 +25,6 @@ VITE_GLOB_API_URL_PREFIX=
VITE_GLOB_MULTITENANCY_KEY='__tenant'
# STS Connect
VITE_GLOB_AUTHORITY='http://127.0.0.1:44385'
VITE_GLOB_AUTHORITY='http://127.0.0.1:30000'
VITE_GLOB_CLIENT_ID='vue-admin-client'
VITE_GLOB_CLIENT_SECRET='1q2w3e*'

40
apps/vue/src/api/feature-management/definitions/features/index.ts

@ -0,0 +1,40 @@
import { defHttp } from '/@/utils/http/axios';
import {
FeatureDefinitionDto,
FeatureDefinitionCreateDto,
FeatureDefinitionUpdateDto,
FeatureDefinitionGetListInput,
} from './model';
export const CreateAsyncByInput = (input: FeatureDefinitionCreateDto) => {
return defHttp.post<FeatureDefinitionDto>({
url: '/api/feature-management/definitions',
data: input,
});
};
export const DeleteAsyncByName = (name: string) => {
return defHttp.delete<void>({
url: `/api/feature-management/definitions/${name}`,
});
};
export const GetAsyncByName = (name: string) => {
return defHttp.get<FeatureDefinitionDto>({
url: `/api/feature-management/definitions/${name}`,
});
};
export const GetListAsyncByInput = (input: FeatureDefinitionGetListInput) => {
return defHttp.get<ListResultDto<FeatureDefinitionDto>>({
url: '/api/feature-management/definitions',
params: input,
});
};
export const UpdateAsyncByNameAndInput = (name: string, input: FeatureDefinitionUpdateDto) => {
return defHttp.put<FeatureDefinitionDto>({
url: `/api/feature-management/definitions/${name}`,
data: input,
});
};

36
apps/vue/src/api/feature-management/definitions/features/model/index.ts

@ -0,0 +1,36 @@
interface FeatureDefinitionCreateOrUpdateDto extends IHasExtraProperties {
displayName: string;
parentName?: string;
description?: string;
defaultValue?: string;
valueType: string;
isVisibleToClients: boolean;
isAvailableToHost: boolean;
allowedProviders: string[];
}
export interface FeatureDefinitionCreateDto extends FeatureDefinitionCreateOrUpdateDto {
name: string;
groupName: string;
}
export interface FeatureDefinitionDto extends IHasExtraProperties {
name: string;
groupName: string;
displayName: string;
parentName?: string;
description?: string;
defaultValue?: string;
valueType: string;
isStatic: boolean;
isVisibleToClients: boolean;
isAvailableToHost: boolean;
allowedProviders: string[];
}
export interface FeatureDefinitionGetListInput {
filter?: string;
groupName?: string;
}
export type FeatureDefinitionUpdateDto = FeatureDefinitionCreateOrUpdateDto;

40
apps/vue/src/api/feature-management/definitions/groups/index.ts

@ -0,0 +1,40 @@
import { defHttp } from '/@/utils/http/axios';
import {
FeatureGroupDefinitionDto,
FeatureGroupDefinitionCreateDto,
FeatureGroupDefinitionUpdateDto,
FeatureGroupDefinitionGetListInput,
} from './model';
export const CreateAsyncByInput = (input: FeatureGroupDefinitionCreateDto) => {
return defHttp.post<FeatureGroupDefinitionDto>({
url: '/api/feature-management/definitions/groups',
data: input,
});
};
export const DeleteAsyncByName = (name: string) => {
return defHttp.delete<void>({
url: `/api/feature-management/definitions/groups/${name}`,
});
};
export const GetAsyncByName = (name: string) => {
return defHttp.get<FeatureGroupDefinitionDto>({
url: `/api/feature-management/definitions/groups/${name}`,
});
};
export const GetListAsyncByInput = (input: FeatureGroupDefinitionGetListInput) => {
return defHttp.get<ListResultDto<FeatureGroupDefinitionDto>>({
url: '/api/feature-management/definitions/groups',
params: input,
});
};
export const UpdateAsyncByNameAndInput = (name: string, input: FeatureGroupDefinitionUpdateDto) => {
return defHttp.put<FeatureGroupDefinitionDto>({
url: `/api/feature-management/definitions/groups/${name}`,
data: input,
});
};

19
apps/vue/src/api/feature-management/definitions/groups/model/index.ts

@ -0,0 +1,19 @@
interface FeatureGroupDefinitionCreateOrUpdateDto extends IHasExtraProperties {
displayName: string;
}
export interface FeatureGroupDefinitionCreateDto extends FeatureGroupDefinitionCreateOrUpdateDto {
name: string;
}
export interface FeatureGroupDefinitionDto extends IHasExtraProperties {
name: string;
displayName: string;
isStatic: boolean;
}
export interface FeatureGroupDefinitionGetListInput {
filter?: string;
}
export type FeatureGroupDefinitionUpdateDto = FeatureGroupDefinitionCreateOrUpdateDto;

20
apps/vue/src/api/feature-management/features/index.ts

@ -0,0 +1,20 @@
import { defAbpHttp } from '/@/utils/http/abp';
import { FeatureGroupResult, UpdateFeatures, FeatureUpdateByProvider, FeatureGetByProvider } from './model';
export const GetByProvider = (provider: FeatureGetByProvider) => {
return defAbpHttp.get<FeatureGroupResult>({
url: '/api/feature-management/features',
params: provider,
});
};
export const UpdateByProvider = (
provider: FeatureUpdateByProvider,
input: UpdateFeatures
) => {
return defAbpHttp.put<void>({
url: '/api/feature-management/features',
data: input,
params: provider,
});
};

44
apps/vue/src/api/feature-management/features/model/index.ts

@ -0,0 +1,44 @@
export interface Provider {
name: string;
key: string;
}
export interface Feature {
name: string;
displayName: string;
value: any;
provider: Provider;
description?: string;
valueType: ValueType;
depth: number;
parentName?: string;
}
export interface FeatureGroup {
name: string;
displayName: string;
features: Feature[];
}
export class FeatureGroupResult {
groups!: FeatureGroup[];
}
export interface UpdateFeature {
name: string;
value: string;
}
export interface UpdateFeatures {
features: UpdateFeature[];
}
export interface FeatureGetByProvider {
providerName: string;
providerKey: string | null;
}
export interface FeatureUpdateByProvider {
providerName: string;
providerKey: string | null;
}

26
apps/vue/src/api/feature/feature.ts

@ -1,26 +0,0 @@
import { defAbpHttp } from '/@/utils/http/abp';
import { FeatureGroupResult, UpdateFeatures } from './model/featureModel';
/** 与 multi-tenancy中不同,此为管理tenant api */
enum Api {
Get = '/api/feature-management/features',
Update = '/api/feature-management/features',
}
export const get = (provider: { providerName: string; providerKey: string | null }) => {
return defAbpHttp.get<FeatureGroupResult>({
url: Api.Get,
params: provider,
});
};
export const update = (
provider: { providerName: string; providerKey: string | null },
input: UpdateFeatures
) => {
return defAbpHttp.put<void>({
url: Api.Update,
data: input,
params: provider,
});
};

34
apps/vue/src/api/feature/model/featureModel.ts

@ -1,34 +0,0 @@
export interface Provider {
name: string;
key: string;
}
export interface Feature {
name: string;
displayName: string;
value: any;
provider: Provider;
description?: string;
valueType: ValueType;
depth: number;
parentName?: string;
}
export interface FeatureGroup {
name: string;
displayName: string;
features: Feature[];
}
export class FeatureGroupResult {
groups!: FeatureGroup[];
}
export interface UpdateFeature {
name: string;
value: string;
}
export interface UpdateFeatures {
features: UpdateFeature[];
}

2
apps/vue/src/api/messages/notifications.ts

@ -10,7 +10,7 @@ import { format } from '/@/utils/strings';
enum Api {
GetById = '/api/notifications/my-notifilers/{id}',
GetList = '/api/notifications/my-notifilers',
GetAssignableNotifiers = '/api/notifications/notifilers/assignables',
GetAssignableNotifiers = '/api/notifications/assignables',
Read = '/api/notifications/my-notifilers/{id}/read',
MarkReadState = '/api/notifications/my-notifilers/mark-read-state',
}

3
apps/vue/src/api/oss-management/oss.ts

@ -202,9 +202,6 @@ export const deleteObject = (input: GetOssObjectRequest) => {
url: Api.DeleteObject,
params: input,
},
{
joinParamsToUrl: true,
},
);
};

40
apps/vue/src/api/permission-management/definitions/groups/index.ts

@ -0,0 +1,40 @@
import { defHttp } from '/@/utils/http/axios';
import {
PermissionGroupDefinitionDto,
PermissionGroupDefinitionCreateDto,
PermissionGroupDefinitionUpdateDto,
PermissionGroupDefinitionGetListInput,
} from './model';
export const CreateAsyncByInput = (input: PermissionGroupDefinitionCreateDto) => {
return defHttp.post<PermissionGroupDefinitionDto>({
url: '/api/permission-management/definitions/groups',
data: input,
});
};
export const DeleteAsyncByName = (name: string) => {
return defHttp.delete<void>({
url: `/api/permission-management/definitions/groups/${name}`,
});
};
export const GetAsyncByName = (name: string) => {
return defHttp.get<PermissionGroupDefinitionDto>({
url: `/api/permission-management/definitions/groups/${name}`,
});
};
export const GetListAsyncByInput = (input: PermissionGroupDefinitionGetListInput) => {
return defHttp.get<ListResultDto<PermissionGroupDefinitionDto>>({
url: '/api/permission-management/definitions/groups',
params: input,
});
};
export const UpdateAsyncByNameAndInput = (name: string, input: PermissionGroupDefinitionUpdateDto) => {
return defHttp.put<PermissionGroupDefinitionDto>({
url: `/api/permission-management/definitions/groups/${name}`,
data: input,
});
};

19
apps/vue/src/api/permission-management/definitions/groups/model/index.ts

@ -0,0 +1,19 @@
interface PermissionGroupDefinitionCreateOrUpdateDto extends IHasExtraProperties {
displayName: string;
}
export interface PermissionGroupDefinitionCreateDto extends PermissionGroupDefinitionCreateOrUpdateDto {
name: string;
}
export interface PermissionGroupDefinitionDto extends IHasExtraProperties {
name: string;
displayName: string;
isStatic: boolean;
}
export interface PermissionGroupDefinitionGetListInput {
filter?: string;
}
export type PermissionGroupDefinitionUpdateDto = PermissionGroupDefinitionCreateOrUpdateDto;

40
apps/vue/src/api/permission-management/definitions/permissions/index.ts

@ -0,0 +1,40 @@
import { defHttp } from '/@/utils/http/axios';
import {
PermissionDefinitionDto,
PermissionDefinitionCreateDto,
PermissionDefinitionUpdateDto,
PermissionDefinitionGetListInput,
} from './model';
export const CreateAsyncByInput = (input: PermissionDefinitionCreateDto) => {
return defHttp.post<PermissionDefinitionDto>({
url: '/api/permission-management/definitions',
data: input,
});
};
export const DeleteAsyncByName = (name: string) => {
return defHttp.delete<void>({
url: `/api/permission-management/definitions/${name}`,
});
};
export const GetAsyncByName = (name: string) => {
return defHttp.get<PermissionDefinitionDto>({
url: `/api/permission-management/definitions/${name}`,
});
};
export const GetListAsyncByInput = (input: PermissionDefinitionGetListInput) => {
return defHttp.get<ListResultDto<PermissionDefinitionDto>>({
url: '/api/permission-management/definitions',
params: input,
});
};
export const UpdateAsyncByNameAndInput = (name: string, input: PermissionDefinitionUpdateDto) => {
return defHttp.put<PermissionDefinitionDto>({
url: `/api/permission-management/definitions/${name}`,
data: input,
});
};

36
apps/vue/src/api/permission-management/definitions/permissions/model/index.ts

@ -0,0 +1,36 @@
export enum MultiTenancySides {
Tenant = 0x1,
Host = 0x2,
Both = 0x3,
}
interface PermissionDefinitionCreateOrUpdateDto extends IHasExtraProperties {
displayName: string;
parentName?: string;
isEnabled: boolean;
providers: string[];
stateCheckers: string;
}
export interface PermissionDefinitionCreateDto extends PermissionDefinitionCreateOrUpdateDto {
groupName: string;
name: string;
}
export interface PermissionDefinitionDto extends IHasExtraProperties {
groupName: string;
name: string;
displayName: string;
parentName?: string;
isEnabled: boolean;
isStatic: boolean;
providers: string[];
stateCheckers: string;
}
export interface PermissionDefinitionGetListInput {
filter?: string;
groupName?: string;
}
export type PermissionDefinitionUpdateDto = PermissionDefinitionCreateOrUpdateDto;

44
apps/vue/src/api/permission-management/model/permissionModel.ts

@ -1,44 +0,0 @@
import { IPermission } from '../../model/baseModel';
export class PermissionProvider {
providerName!: string;
providerKey?: string;
}
export interface IPermissionGrant {
allowedProviders: string[];
grantedProviders: PermissionProvider[];
displayName: string;
isGranted: boolean;
name: string;
parentName?: string;
}
export class Permission implements IPermissionGrant {
allowedProviders: string[] = [];
grantedProviders: PermissionProvider[] = [];
displayName!: string;
isGranted!: boolean;
name!: string;
parentName?: string;
}
export interface PermissionGroup {
displayName: string;
name: string;
permissions: Permission[];
}
export class UpdatePermission implements IPermission {
name!: string;
isGranted!: boolean;
}
export class UpdatePermissions {
permissions!: UpdatePermission[];
}
export class PermissionResult {
entityDisplayName!: string;
groups: PermissionGroup[] = [];
}

22
apps/vue/src/api/permission-management/permission.ts

@ -1,22 +0,0 @@
import { defAbpHttp } from '/@/utils/http/abp';
import { PermissionProvider, PermissionResult, UpdatePermissions } from './model/permissionModel';
enum Api {
Get = '/api/permission-management/permissions',
Update = '/api/permission-management/permissions',
}
export const get = (provider: PermissionProvider) => {
return defAbpHttp.get<PermissionResult>({
url: Api.Get,
params: provider,
});
};
export const update = (provider: PermissionProvider, input: UpdatePermissions) => {
return defAbpHttp.put<void>({
url: Api.Update,
data: input,
params: provider,
});
};

17
apps/vue/src/api/permission-management/permissions/index.ts

@ -0,0 +1,17 @@
import { defAbpHttp } from '/@/utils/http/abp';
import { PermissionProvider, PermissionResult, UpdatePermissions } from './model';
export const get = (provider: PermissionProvider) => {
return defAbpHttp.get<PermissionResult>({
url: '/api/permission-management/permissions',
params: provider,
});
};
export const update = (provider: PermissionProvider, input: UpdatePermissions) => {
return defAbpHttp.put<void>({
url: '/api/permission-management/permissions',
data: input,
params: provider,
});
};

44
apps/vue/src/api/permission-management/permissions/model/index.ts

@ -0,0 +1,44 @@
import { IPermission } from '/@/api/model/baseModel';
export class PermissionProvider {
providerName!: string;
providerKey?: string;
}
export interface IPermissionGrant {
allowedProviders: string[];
grantedProviders: PermissionProvider[];
displayName: string;
isGranted: boolean;
name: string;
parentName?: string;
}
export class Permission implements IPermissionGrant {
allowedProviders: string[] = [];
grantedProviders: PermissionProvider[] = [];
displayName!: string;
isGranted!: boolean;
name!: string;
parentName?: string;
}
export interface PermissionGroup {
displayName: string;
name: string;
permissions: Permission[];
}
export class UpdatePermission implements IPermission {
name!: string;
isGranted!: boolean;
}
export class UpdatePermissions {
permissions!: UpdatePermission[];
}
export class PermissionResult {
entityDisplayName!: string;
groups: PermissionGroup[] = [];
}

41
apps/vue/src/api/settings-management/definitions/index.ts

@ -0,0 +1,41 @@
import { defHttp } from '/@/utils/http/axios';
import {
SettingDefinitionDto,
SettingDefinitionCreateDto,
SettingDefinitionUpdateDto,
SettingDefinitionGetListInput,
} from './model';
export const GetAsyncByName = (name: string) => {
return defHttp.get<SettingDefinitionDto>({
url: `/api/setting-management/settings/definitions/${name}`,
});
};
export const GetListAsyncByInput = (input: SettingDefinitionGetListInput) => {
return defHttp.get<ListResultDto<SettingDefinitionDto>>({
url: `/api/setting-management/settings/definitions`,
params: input,
});
}
export const CreateAsyncByInput = (input: SettingDefinitionCreateDto) => {
return defHttp.post<SettingDefinitionDto>({
url: `/api/setting-management/settings/definitions`,
data: input,
});
};
export const UpdateAsyncByNameAndInput = (name: string, input: SettingDefinitionUpdateDto) => {
return defHttp.put<SettingDefinitionDto>({
url: `/api/setting-management/settings/definitions/${name}`,
data: input,
});
};
export const DeleteOrRestoreAsyncByName = (name: string) => {
return defHttp.delete<void>({
url: `/api/setting-management/settings/definitions/${name}`,
});
};

32
apps/vue/src/api/settings-management/definitions/model/index.ts

@ -0,0 +1,32 @@
export interface SettingDefinitionDto extends ExtensibleObject {
name: string;
displayName: string;
description?: string;
defaultValue?: string;
isVisibleToClients: boolean;
providers: string[];
isInherited: boolean;
isEncrypted: boolean;
isStatic: boolean;
}
export interface SettingDefinitionGetListInput {
filter?: string;
providerName?: string;
}
interface SettingDefinitionCreateOrUpdateDto extends IHasConcurrencyStamp, IHasExtraProperties {
displayName: string;
description?: string;
defaultValue?: string;
isInherited: boolean;
isEncrypted: boolean;
isVisibleToClients: boolean;
providers: string[];
}
export interface SettingDefinitionCreateDto extends SettingDefinitionCreateOrUpdateDto {
name: string;
}
export type SettingDefinitionUpdateDto = SettingDefinitionCreateOrUpdateDto;

60
apps/vue/src/api/settings-management/settings/index.ts

@ -0,0 +1,60 @@
import { defHttp } from '/@/utils/http/axios';
import { SettingGroupResult, SettingsUpdate } from './model';
enum Api {
GetGlobalSettings = '/api/setting-management/settings/by-global',
SetGlobalSettings = '/api/setting-management/settings/change-global',
GetCurrentTenantSettings = '/api/setting-management/settings/by-current-tenant',
SetCurrentTenantSettings = '/api/setting-management/settings/change-current-tenant',
GetCurrentUserSettings = '/api/setting-management/settings/by-current-user',
SetCurrentUserSettings = '/api/setting-management/settings/change-current-user',
SendTestEmail = '/api/setting-management/settings/send-test-email'
}
export const getGlobalSettings = () => {
return defHttp.get<SettingGroupResult>({
url: Api.GetGlobalSettings,
});
};
export const setGlobalSettings = (payload: SettingsUpdate) => {
return defHttp.put({
data: payload,
url: Api.SetGlobalSettings,
});
};
export const getCurrentTenantSettings = () => {
return defHttp.get<SettingGroupResult>({
url: Api.GetCurrentTenantSettings,
});
};
export const setCurrentTenantSettings = (payload: SettingsUpdate) => {
return defHttp.put({
data: payload,
url: Api.SetCurrentTenantSettings,
});
};
export const getCurrentUserSettings = () => {
return defHttp.get<SettingGroupResult>({
url: Api.GetCurrentUserSettings,
});
};
export const setCurrentUserSettings = (payload: SettingsUpdate) => {
return defHttp.put({
data: payload,
url: Api.SetCurrentUserSettings,
});
};
export const sendTestEmail = (emailAddress: string) => {
return defHttp.post({
data: {
emailAddress: emailAddress
},
url: Api.SendTestEmail,
});
}

0
apps/vue/src/api/settings/model/settingModel.ts → apps/vue/src/api/settings-management/settings/model/index.ts

60
apps/vue/src/api/settings/settings.ts

@ -1,60 +0,0 @@
import { defHttp } from '/@/utils/http/axios';
import { SettingGroupResult, SettingsUpdate } from './model/settingModel';
enum Api {
GetGlobalSettings = '/api/setting-management/settings/by-global',
SetGlobalSettings = '/api/setting-management/settings/change-global',
GetCurrentTenantSettings = '/api/setting-management/settings/by-current-tenant',
SetCurrentTenantSettings = '/api/setting-management/settings/change-current-tenant',
GetCurrentUserSettings = '/api/setting-management/settings/by-current-user',
SetCurrentUserSettings = '/api/setting-management/settings/change-current-user',
SendTestEmail = '/api/setting-management/settings/send-test-email'
}
export const getGlobalSettings = () => {
return defHttp.get<SettingGroupResult>({
url: Api.GetGlobalSettings,
});
};
export const setGlobalSettings = (payload: SettingsUpdate) => {
return defHttp.put({
data: payload,
url: Api.SetGlobalSettings,
});
};
export const getCurrentTenantSettings = () => {
return defHttp.get<SettingGroupResult>({
url: Api.GetCurrentTenantSettings,
});
};
export const setCurrentTenantSettings = (payload: SettingsUpdate) => {
return defHttp.put({
data: payload,
url: Api.SetCurrentTenantSettings,
});
};
export const getCurrentUserSettings = () => {
return defHttp.get<SettingGroupResult>({
url: Api.GetCurrentUserSettings,
});
};
export const setCurrentUserSettings = (payload: SettingsUpdate) => {
return defHttp.put({
data: payload,
url: Api.SetCurrentUserSettings,
});
};
export const sendTestEmail = (emailAddress: string) => {
return defHttp.post({
data: {
emailAddress: emailAddress
},
url: Api.SendTestEmail,
});
}

6
apps/vue/src/api/text-templating/contents/index.ts

@ -7,7 +7,7 @@ import {
} from './model';
const remoteServiceName = 'AbpTextTemplating';
const controllerName = 'TextTemplate';
const controllerName = 'TextTemplateContent';
export const GetAsyncByInput = (input: TextTemplateContentGetInput) => {
return defAbpHttp.request<TextTemplateContentDto>({
@ -15,7 +15,9 @@ export const GetAsyncByInput = (input: TextTemplateContentGetInput) => {
controller: controllerName,
action: 'GetAsync',
uniqueName: 'GetAsyncByInput',
params: input,
params: {
input: input,
},
});
};

2
apps/vue/src/api/text-templating/definitions/index.ts

@ -44,7 +44,7 @@ export const GetByNameAsyncByName = (name: string) => {
};
export const GetListAsyncByInput = (input: TextTemplateDefinitionGetListInput) => {
return defAbpHttp.request<PagedResultDto<TextTemplateDefinitionDto>>({
return defAbpHttp.request<ListResultDto<TextTemplateDefinitionDto>>({
service: remoteServiceName,
controller: controllerName,
action: 'GetListAsync',

5
apps/vue/src/api/text-templating/definitions/model/index.ts

@ -1,7 +1,6 @@
export interface TextTemplateDefinitionDto {
name: string;
displayName: string;
formatedDisplayName?: string;
defaultCultureName?: string;
isInlineLocalized: boolean;
isLayout: boolean;
@ -27,6 +26,8 @@ export interface TextTemplateDefinitionCreateDto extends TextTemplateDefinitionC
export interface TextTemplateDefinitionUpdateDto extends TextTemplateDefinitionCreateOrUpdateDto, IHasConcurrencyStamp { }
export interface TextTemplateDefinitionGetListInput extends PagedAndSortedResultRequestDto {
export interface TextTemplateDefinitionGetListInput {
filter?: string;
isLayout?: boolean;
isStatic?: boolean;
}

41
apps/vue/src/api/webhooks/definitions/groups/index.ts

@ -0,0 +1,41 @@
import { defHttp } from '/@/utils/http/axios';
import {
WebhookGroupDefinitionDto,
WebhookGroupDefinitionCreateDto,
WebhookGroupDefinitionUpdateDto,
WebhookGroupDefinitionGetListInput,
} from './model';
export const GetAsyncByName = (name: string) => {
return defHttp.get<WebhookGroupDefinitionDto>({
url: `/api/webhooks/definitions/groups/${name}`,
});
};
export const GetListAsyncByInput = (input: WebhookGroupDefinitionGetListInput) => {
return defHttp.get<ListResultDto<WebhookGroupDefinitionDto>>({
url: `/api/webhooks/definitions/groups`,
params: input,
});
}
export const CreateAsyncByInput = (input: WebhookGroupDefinitionCreateDto) => {
return defHttp.post<WebhookGroupDefinitionDto>({
url: `/api/webhooks/definitions/groups`,
data: input,
});
};
export const UpdateAsyncByNameAndInput = (name: string, input: WebhookGroupDefinitionUpdateDto) => {
return defHttp.put<WebhookGroupDefinitionDto>({
url: `/api/webhooks/definitions/groups/${name}`,
data: input,
});
};
export const DeleteAsyncByName = (name: string) => {
return defHttp.delete<void>({
url: `/api/webhooks/definitions/groups/${name}`,
});
};

19
apps/vue/src/api/webhooks/definitions/groups/model/index.ts

@ -0,0 +1,19 @@
interface WebhookGroupDefinitionCreateOrUpdateDto extends IHasExtraProperties {
displayName: string;
}
export interface WebhookGroupDefinitionCreateDto extends WebhookGroupDefinitionCreateOrUpdateDto {
name: string;
}
export interface WebhookGroupDefinitionDto extends IHasExtraProperties {
name: string;
displayName: string;
isStatic: boolean;
}
export interface WebhookGroupDefinitionGetListInput {
filter?: string;
}
export type WebhookGroupDefinitionUpdateDto = WebhookGroupDefinitionCreateOrUpdateDto;

41
apps/vue/src/api/webhooks/definitions/webhooks/index.ts

@ -0,0 +1,41 @@
import { defHttp } from '/@/utils/http/axios';
import {
WebhookDefinitionDto,
WebhookDefinitionCreateDto,
WebhookDefinitionUpdateDto,
WebhookDefinitionGetListInput,
} from './model';
export const GetAsyncByName = (name: string) => {
return defHttp.get<WebhookDefinitionDto>({
url: `/api/webhooks/definitions/${name}`,
});
};
export const GetListAsyncByInput = (input: WebhookDefinitionGetListInput) => {
return defHttp.get<ListResultDto<WebhookDefinitionDto>>({
url: `/api/webhooks/definitions`,
params: input,
});
}
export const CreateAsyncByInput = (input: WebhookDefinitionCreateDto) => {
return defHttp.post<WebhookDefinitionDto>({
url: `/api/webhooks/definitions`,
data: input,
});
};
export const UpdateAsyncByNameAndInput = (name: string, input: WebhookDefinitionUpdateDto) => {
return defHttp.put<WebhookDefinitionDto>({
url: `/api/webhooks/definitions/${name}`,
data: input,
});
};
export const DeleteAsyncByName = (name: string) => {
return defHttp.delete<void>({
url: `/api/webhooks/definitions/${name}`,
});
};

28
apps/vue/src/api/webhooks/definitions/webhooks/model/index.ts

@ -0,0 +1,28 @@
interface WebhookDefinitionCreateOrUpdateDto extends IHasExtraProperties {
displayName: string;
description?: string;
isEnabled: boolean;
requiredFeatures: string[];
}
export interface WebhookDefinitionDto extends IHasExtraProperties {
groupName: string;
name: string;
displayName: string;
description?: string;
isEnabled: boolean;
isStatic: boolean;
requiredFeatures: string[];
}
export interface WebhookDefinitionCreateDto extends WebhookDefinitionCreateOrUpdateDto {
groupName: string;
name: string;
}
export interface WebhookDefinitionGetListInput {
filter?: string;
groupName?: string;
}
export type WebhookDefinitionUpdateDto = WebhookDefinitionCreateOrUpdateDto;

32
apps/vue/src/api/webhooks/model/sendAttemptsModel.ts

@ -1,32 +0,0 @@
import { HttpStatusCode } from '/@/enums/httpEnum';
export interface WebhookEvent {
tenantId?: string;
webhookName: string;
data: string;
creationTime: Date;
}
export interface WebhookSendAttempt {
id: string;
tenantId?: string;
webhookEventId: string;
webhookSubscriptionId: string;
response: string;
responseStatusCode?: HttpStatusCode;
creationTime: Date;
lastModificationTime?: Date;
sendExactSameData: boolean;
requestHeaders: Record<string, string>;
responseHeaders: Record<string, string>;
webhookEvent: WebhookEvent;
}
export interface WebhookSendAttemptGetListInput extends PagedAndSortedResultRequestDto {
filter?: string;
webhookEventId?: string;
subscriptionId?: string;
responseStatusCode?: HttpStatusCode;
beginCreationTime?: Date;
endCreationTime?: Date;
}

45
apps/vue/src/api/webhooks/model/subscriptionsModel.ts

@ -1,45 +0,0 @@
export interface WebhookSubscription extends CreationAuditedEntityDto<string>, IHasConcurrencyStamp {
tenantId?: string;
webhookUri: string;
description?: string;
secret: string;
isActive: boolean;
webhooks: string[];
headers: Dictionary<string, string>;
}
export interface WebhookSubscriptionCreateOrUpdate {
webhookUri: string;
description?: string;
secret: string;
isActive: boolean;
webhooks: string[];
headers: Dictionary<string, string>;
}
export type CreateWebhookSubscription = WebhookSubscriptionCreateOrUpdate;
export interface UpdateWebhookSubscription extends WebhookSubscriptionCreateOrUpdate , IHasConcurrencyStamp {};
export interface WebhookAvailable {
name: string;
displayName: string;
description: string;
}
export interface WebhookAvailableGroup {
name: string;
displayName: string;
webhooks: WebhookAvailable[];
}
export interface WebhookSubscriptionGetListInput extends PagedAndSortedResultRequestDto {
filter?: string;
tenantId?: string;
webhookUri?: string;
secret?: string;
isActive?: boolean;
webhooks?: string;
beginCreationTime?: Date;
endCreationTime?: Date;
}

73
apps/vue/src/api/webhooks/send-attempts.ts

@ -1,73 +0,0 @@
import { defAbpHttp } from '/@/utils/http/abp';
import { WebhookSendAttempt, WebhookSendAttemptGetListInput } from './model/sendAttemptsModel';
const remoteServiceName = 'WebhooksManagement';
const controllerName = 'WebhookSendRecord';
export const getById = (id: string) => {
return defAbpHttp.request<WebhookSendAttempt>({
service: remoteServiceName,
controller: controllerName,
action: 'GetAsync',
params: {
id: id,
},
});
};
export const deleteById = (id: string) => {
return defAbpHttp.request<WebhookSendAttempt>({
service: remoteServiceName,
controller: controllerName,
action: 'DeleteAsync',
params: {
id: id,
},
});
}
export const deleteMany = (keys: string[]) => {
return defAbpHttp.request<void>({
service: remoteServiceName,
controller: controllerName,
action: 'DeleteManyAsync',
uniqueName: 'DeleteManyAsyncByInput',
data: {
recordIds: keys,
},
});
}
export const getList = (input: WebhookSendAttemptGetListInput) => {
return defAbpHttp.request<PagedResultDto<WebhookSendAttempt>>({
service: remoteServiceName,
controller: controllerName,
action: 'GetListAsync',
params: {
input: input,
},
});
};
export const resend = (id: string) => {
return defAbpHttp.request<void>({
service: remoteServiceName,
controller: controllerName,
action: 'ResendAsync',
params: {
id: id,
},
});
}
export const resendMany = (keys: string[]) => {
return defAbpHttp.request<void>({
service: remoteServiceName,
controller: controllerName,
action: 'ResendManyAsync',
uniqueName: 'ResendManyAsyncByInput',
data: {
recordIds: keys,
},
});
}

46
apps/vue/src/api/webhooks/send-attempts/index.ts

@ -0,0 +1,46 @@
import { defAbpHttp } from '/@/utils/http/abp';
import {
WebhookSendAttempt,
WebhookSendAttemptGetListInput,
WebhookSendRecordDeleteManyInput,
WebhookSendRecordResendManyInput,
} from './model';
export const GetAsyncById = (id: string) => {
return defAbpHttp.get<WebhookSendAttempt>({
url: `/api/webhooks/send-attempts/${id}`,
});
};
export const DeleteAsyncById = (id: string) => {
return defAbpHttp.delete<void>({
url: `/api/webhooks/send-attempts/${id}`,
});
};
export const DeleteManyAsyncByInput = (input: WebhookSendRecordDeleteManyInput) => {
return defAbpHttp.delete<void>({
url: `/api/webhooks/send-attempts/delete-many`,
data: input,
});
};
export const GetListAsyncByInput = (input: WebhookSendAttemptGetListInput) => {
return defAbpHttp.get<PagedResultDto<WebhookSendAttempt>>({
url: `/api/webhooks/send-attempts`,
params: input,
});
};
export const ResendAsyncById = (id: string) => {
return defAbpHttp.post<void>({
url: `/api/webhooks/send-attempts/${id}/resend`,
});
};
export const ResendManyAsyncByInput = (input: WebhookSendRecordResendManyInput) => {
return defAbpHttp.post<void>({
url: `/api/webhooks/send-attempts/resend-many`,
data: input,
});
};

40
apps/vue/src/api/webhooks/send-attempts/model/index.ts

@ -0,0 +1,40 @@
import { HttpStatusCode } from '/@/enums/httpEnum';
export interface WebhookEvent {
tenantId?: string;
webhookName: string;
data: string;
creationTime: Date;
}
export interface WebhookSendAttempt {
id: string;
tenantId?: string;
webhookEventId: string;
webhookSubscriptionId: string;
response: string;
responseStatusCode?: HttpStatusCode;
creationTime: Date;
lastModificationTime?: Date;
sendExactSameData: boolean;
requestHeaders: Record<string, string>;
responseHeaders: Record<string, string>;
webhookEvent: WebhookEvent;
}
export interface WebhookSendAttemptGetListInput extends PagedAndSortedResultRequestDto {
filter?: string;
webhookEventId?: string;
subscriptionId?: string;
responseStatusCode?: HttpStatusCode;
beginCreationTime?: Date;
endCreationTime?: Date;
}
export interface WebhookSendRecordDeleteManyInput {
recordIds: string[];
}
export interface WebhookSendRecordResendManyInput {
recordIds: string[];
}

85
apps/vue/src/api/webhooks/subscriptions.ts

@ -1,85 +0,0 @@
import { defAbpHttp } from '/@/utils/http/abp';
import {
WebhookSubscription,
WebhookAvailableGroup,
CreateWebhookSubscription,
UpdateWebhookSubscription,
WebhookSubscriptionGetListInput,
} from './model/subscriptionsModel';
const remoteServiceName = 'WebhooksManagement';
const controllerName = 'WebhookSubscription';
export const create = (input: CreateWebhookSubscription) => {
return defAbpHttp.request<WebhookSubscription>({
service: remoteServiceName,
controller: controllerName,
action: 'CreateAsync',
data: input,
});
};
export const update = (id: string, input: UpdateWebhookSubscription) => {
return defAbpHttp.request<WebhookSubscription>({
service: remoteServiceName,
controller: controllerName,
action: 'UpdateAsync',
data: input,
params: {
id: id,
},
});
};
export const getById = (id: string) => {
return defAbpHttp.request<WebhookSubscription>({
service: remoteServiceName,
controller: controllerName,
action: 'GetAsync',
params: {
id: id,
},
});
};
export const deleteById = (id: string) => {
return defAbpHttp.request<WebhookSubscription>({
service: remoteServiceName,
controller: controllerName,
action: 'DeleteAsync',
params: {
id: id,
},
});
};
export const deleteMany = (keys: string[]) => {
return defAbpHttp.request<void>({
service: remoteServiceName,
controller: controllerName,
action: 'DeleteManyAsync',
uniqueName: 'DeleteManyAsyncByInput',
data: {
recordIds: keys,
},
});
};
export const getList = (input: WebhookSubscriptionGetListInput) => {
return defAbpHttp.request<PagedResultDto<WebhookSubscription>>({
service: remoteServiceName,
controller: controllerName,
action: 'GetListAsync',
params: {
input: input,
},
});
};
export const getAllAvailableWebhooks = () => {
return defAbpHttp.request<ListResultDto<WebhookAvailableGroup>>({
service: remoteServiceName,
controller: controllerName,
action: 'GetAllAvailableWebhooksAsync',
});
};

55
apps/vue/src/api/webhooks/subscriptions/index.ts

@ -0,0 +1,55 @@
import { defAbpHttp } from '/@/utils/http/abp';
import {
WebhookSubscription,
WebhookAvailableGroup,
CreateWebhookSubscription,
UpdateWebhookSubscription,
WebhookSubscriptionGetListInput,
WebhookSubscriptionDeleteManyInput,
} from './model';
export const CreateAsyncByInput = (input: CreateWebhookSubscription) => {
return defAbpHttp.post<WebhookSubscription>({
url: `/api/webhooks/subscriptions`,
data: input,
});
};
export const UpdateAsyncByIdAndInput = (id: string, input: UpdateWebhookSubscription) => {
return defAbpHttp.put<WebhookSubscription>({
url: `/api/webhooks/subscriptions/${id}`,
data: input,
});
};
export const GetAsyncById = (id: string) => {
return defAbpHttp.get<WebhookSubscription>({
url: `/api/webhooks/subscriptions/${id}`,
});
};
export const DeleteAsyncById = (id: string) => {
return defAbpHttp.delete<void>({
url: `/api/webhooks/subscriptions/${id}`,
});
};
export const DeleteManyAsyncByInput = (input: WebhookSubscriptionDeleteManyInput) => {
return defAbpHttp.delete<void>({
url: `/api/webhooks/subscriptions/delete-many`,
data: input,
});
};
export const GetListAsyncByInput = (input: WebhookSubscriptionGetListInput) => {
return defAbpHttp.get<PagedResultDto<WebhookSubscription>>({
url: `/api/webhooks/subscriptions`,
params: input,
});
};
export const GetAllAvailableWebhooksAsync = () => {
return defAbpHttp.get<ListResultDto<WebhookAvailableGroup>>({
url: `/api/webhooks/subscriptions/availables`,
});
};

49
apps/vue/src/api/webhooks/subscriptions/model/index.ts

@ -0,0 +1,49 @@
export interface WebhookSubscription extends CreationAuditedEntityDto<string>, IHasConcurrencyStamp {
tenantId?: string;
webhookUri: string;
description?: string;
secret: string;
isActive: boolean;
webhooks: string[];
headers: Dictionary<string, string>;
}
export interface WebhookSubscriptionCreateOrUpdate {
webhookUri: string;
description?: string;
secret: string;
isActive: boolean;
webhooks: string[];
headers: Dictionary<string, string>;
}
export type CreateWebhookSubscription = WebhookSubscriptionCreateOrUpdate;
export interface UpdateWebhookSubscription extends WebhookSubscriptionCreateOrUpdate , IHasConcurrencyStamp {};
export interface WebhookAvailable {
name: string;
displayName: string;
description: string;
}
export interface WebhookAvailableGroup {
name: string;
displayName: string;
webhooks: WebhookAvailable[];
}
export interface WebhookSubscriptionGetListInput extends PagedAndSortedResultRequestDto {
filter?: string;
tenantId?: string;
webhookUri?: string;
secret?: string;
isActive?: boolean;
webhooks?: string;
beginCreationTime?: Date;
endCreationTime?: Date;
}
export interface WebhookSubscriptionDeleteManyInput {
recordIds: string[];
}

255
apps/vue/src/components/Abp/ExtraPropertyDictionary/ExtraPropertyDictionary.vue

@ -0,0 +1,255 @@
<template>
<div style="width: 100%">
<div :class="`${prefixCls}__toolbar`" v-if="!props.disabled">
<Button type="primary" @click="handleAddNew">{{ t('component.extra_property_dictionary.actions.create') }}</Button>
<Button v-if="props.allowDelete" danger @click="handleClean">{{ t('component.extra_property_dictionary.actions.clean') }}</Button>
</div>
<Card :title="t('component.extra_property_dictionary.title')">
<Table sticky rowKey="key" :columns="getTableColumns" :data-source="state.table.dataSource" :scroll="{ x: 1500 }">
<template v-if="!props.disabled" #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<div :class="`${prefixCls}__action`">
<Button v-if="props.allowEdit" type="link" @click="() => handleEdit(record)">
<template #icon>
<EditOutlined />
</template>
{{ t('component.extra_property_dictionary.actions.update') }}
</Button>
<Divider v-if="props.allowEdit && props.allowDelete" type="vertical" />
<Button v-if="props.allowDelete" type="link" @click="() => handleDelete(record)" class="ant-btn-error">
<template #icon>
<DeleteOutlined />
</template>
{{ t('component.extra_property_dictionary.actions.delete') }}
</Button>
</div>
</template>
</template>
</Table>
</Card>
<Modal v-bind="state.modal">
<Form
ref="formRef"
:class="`${prefixCls}__form`"
v-bind="state.form"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 18}"
>
<FormItem name="key" required :label="t('component.extra_property_dictionary.key')">
<Input :disabled="state.editFlag" v-model:value="state.form.model.key" />
</FormItem>
<FormItem name="value" required :label="t('component.extra_property_dictionary.value')">
<Input v-model:value="state.form.model.value" />
</FormItem>
</Form>
</Modal>
</div>
</template>
<script lang="ts" setup>
import type { RuleObject } from 'ant-design-vue/lib/form';
import type { ColumnsType } from 'ant-design-vue/lib/table/interface';
import { cloneDeep } from 'lodash-es';
import { computed, reactive, ref, unref, watch } from 'vue';
import { DeleteOutlined, EditOutlined } from '@ant-design/icons-vue';
import { Button, Card, Divider, Form, Input, Table, Modal } from 'ant-design-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { useI18n } from '/@/hooks/web/useI18n';
import { propTypes } from '/@/utils/propTypes';
const FormItem = Form.Item;
interface DataSource {
key: string;
value: string;
}
interface State {
editFlag: boolean,
modal: {
title?: string,
visible?: boolean,
maskClosable?: boolean,
width?: number,
minHeight?: number,
onOk?: (e: MouseEvent) => void,
onCancel?: (e: MouseEvent) => void,
},
form: {
model: any,
rules?: Dictionary<string, RuleObject>,
},
table: {
dataSource: DataSource[],
},
}
const emits = defineEmits(['change', 'update:value']);
const props = defineProps({
value: {
type: Object as PropType<Dictionary<string, string>>,
},
allowEdit: propTypes.bool.def(false),
allowDelete: propTypes.bool.def(false),
disabled: propTypes.bool.def(false),
});
const { prefixCls } = useDesign('extra-property-dictionary');
const { t } = useI18n();
const formRef = ref<any>();
const getTableColumns = computed(() => {
const columns: ColumnsType = [{
title: t('component.extra_property_dictionary.key'),
dataIndex: 'key',
align: 'left',
fixed: 'left',
width: 180,
},
{
title: t('component.extra_property_dictionary.value'),
dataIndex: 'value',
align: 'left',
fixed: 'left',
width: 'auto',
}];
return columns.concat(props.disabled
? []
: [{
width: 220,
title: t('component.extra_property_dictionary.actions.title'),
align: 'center',
dataIndex: 'action',
key: 'action',
fixed: 'right',
}]);
});
const state = reactive<State>({
editFlag: false,
modal: {
width: 600,
minHeight: 400,
visible: false,
maskClosable: false,
onOk: handleSubmit,
onCancel: handleCancel,
},
form: {
model: {},
rules: {
key: {
validator: (_rule, value) => {
if (!state.editFlag && state.table.dataSource && state.table.dataSource.findIndex(x => x.key === value) >= 0) {
return Promise.reject(t('component.extra_property_dictionary.validator.duplicateKey'));
}
return Promise.resolve();
}
},
},
},
table: {
dataSource: [],
},
});
watch(
() => props.value,
(dataSource) => {
if (!dataSource) {
state.table.dataSource = [];
return;
}
state.table.dataSource = Object.keys(dataSource).map((key) => {
return {
key: key,
value: dataSource[key],
};
});
},
{
immediate: true,
}
);
function handleAddNew() {
state.form.model = {};
state.editFlag = false;
state.modal.title = t('component.extra_property_dictionary.actions.create');
state.modal.visible = true;
}
function handleEdit(record) {
state.editFlag = true;
state.form.model = cloneDeep(record);
state.modal.title = t('component.extra_property_dictionary.actions.update');
state.modal.visible = true;
}
function handleClean() {
emits('change', {});
emits('update:value', {});
}
function handleDelete(record) {
const dataSource = state.table.dataSource ?? [];
const findIndex = dataSource.findIndex(x => x.key === record.key);
const changeData: Dictionary<string, string> = {};
dataSource.splice(findIndex, 1);
dataSource.forEach((item) => {
changeData[item.key] = item.value;
});
emits('change', changeData);
emits('update:value', changeData);
}
function handleCancel() {
const form = unref(formRef);
form?.resetFields();
state.modal.visible = false;
}
function handleSubmit() {
const form = unref(formRef);
form?.validate().then(() => {
const dataSource = state.table.dataSource ?? [];
const changeData: Dictionary<string, string> = {};
if (!state.editFlag) {
dataSource.push(state.form.model);
} else {
const findIndex = dataSource.findIndex(x => x.key === state.form.model.key);
dataSource[findIndex] = state.form.model;
}
dataSource.forEach((item) => {
changeData[item.key] = item.value;
});
emits('change', changeData);
emits('update:value', changeData);
form.resetFields();
state.modal.visible = false;
});
}
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-extra-property-dictionary';
.@{prefix-cls} {
&__toolbar {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
margin-bottom: 8px;
> * {
margin-right: 8px;
}
}
&__action {
display: flex;
align-items: center;
}
&__form {
margin: 10px;
}
}
</style>

3
apps/vue/src/components/Abp/ExtraPropertyDictionary/index.ts

@ -0,0 +1,3 @@
import ExtraPropertyDictionary from './ExtraPropertyDictionary.vue';
export { ExtraPropertyDictionary };

145
apps/vue/src/components/Abp/FeatureModal/hooks/useFeatures.ts

@ -0,0 +1,145 @@
import type { Ref } from 'vue';
import { watch, ref, unref } from 'vue';
import { message } from 'ant-design-vue';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { useValidation } from '/@/hooks/abp/useValidation';
import { FeatureGroup } from '/@/api/feature-management/features/model';
import { GetByProvider, UpdateByProvider } from '/@/api/feature-management/features';
import { ReturnInnerMethods } from '/@/components/Modal';
interface UseFeature {
providerName: Ref<string>;
providerKey: Ref<string | null>;
formRel: Ref<any>;
modalMethods: ReturnInnerMethods;
}
export function useFeatures({ providerName, providerKey, formRel, modalMethods }: UseFeature) {
const { L } = useLocalization('AbpFeatureManagement');
const { ruleCreator } = useValidation();
const featureGroup = ref<{ groups: FeatureGroup[] }>({
groups: [],
});
const featureGroupKey = ref(0);
watch(
() => unref(providerKey),
(key) => {
if (key !== undefined) {
const form = unref(formRel);
form.resetFields();
onGroupChange(0);
GetByProvider({
providerName: unref(providerName),
providerKey: key,
}).then((res) => {
featureGroup.value.groups = mapFeatures(res.groups);
});
}
},
);
function getFeatures(groups: FeatureGroup[]) {
const features: { name: string; value: string }[] = [];
groups.forEach((g) => {
g.features.forEach((f) => {
if (f.value !== null) {
features.push({
name: f.name,
value: String(f.value),
});
}
});
});
return features;
}
function mapFeatures(groups: FeatureGroup[]) {
groups.forEach((g) => {
g.features.forEach((f) => {
switch (f.valueType?.validator.name) {
case 'BOOLEAN':
f.value = String(f.value).toLocaleLowerCase() === 'true';
break;
case 'NUMERIC':
f.value = Number(f.value);
break;
}
});
});
return groups;
}
function validator(field: string, validator: Validator) {
const featureRules: { [key: string]: any }[] = new Array<{ [key: string]: any }>();
if (validator.properties) {
switch (validator.name) {
case 'NUMERIC':
featureRules.push(
...ruleCreator.fieldMustBeetWeen({
name: field,
start: Number(validator.properties.MinValue),
end: Number(validator.properties.MaxValue),
trigger: 'change',
}),
);
break;
case 'STRING':
if (
validator.properties.AllowNull &&
validator.properties.AllowNull.toLowerCase() === 'true'
) {
featureRules.push(
...ruleCreator.fieldRequired({
name: field,
trigger: 'blur',
}),
);
}
featureRules.push(
...ruleCreator.fieldMustBeStringWithMinimumLengthAndMaximumLength({
name: field,
minimum: Number(validator.properties.MinValue),
maximum: Number(validator.properties.MaxValue),
trigger: 'blur',
}),
);
break;
default:
break;
}
}
return featureRules;
}
function onGroupChange(activeKey) {
featureGroupKey.value = activeKey;
}
function handleSubmit() {
const form = unref(formRel);
form.validate().then(() => {
UpdateByProvider(
{
providerName: unref(providerName),
providerKey: unref(providerKey),
},
{
features: getFeatures(unref(featureGroup).groups),
},
).then(() => {
modalMethods.closeModal();
message.success(L('Successful'));
});
});
}
return {
featureGroup,
featureGroupKey,
validator,
handleSubmit,
onGroupChange,
};
}

0
apps/vue/src/views/feature/index.ts → apps/vue/src/components/Abp/FeatureModal/index.ts

100
apps/vue/src/components/Abp/FeatureModal/src/FeatureModal.vue

@ -0,0 +1,100 @@
<template>
<BasicModal
v-bind="$attrs"
@register="registerModal"
:title="L('ManageFeatures')"
:width="800"
:min-height="400"
:can-fullscreen="false"
@ok="handleSubmit"
@cancel="onGroupChange(0)"
>
<Form ref="formRel" :model="featureGroup">
<Tabs :class="`${prefixCls}__tabs`" tabPosition="left" v-model:activeKey="featureGroupKey" @change="onGroupChange">
<TabPane v-for="(group, gi) in featureGroup.groups" :key="gi" :tab="group.displayName">
<div v-for="(feature, fi) in group.features" :key="feature.name">
<FormItem
v-if="feature.valueType !== null"
:label="feature.displayName"
:name="['groups', gi, 'features', fi, 'value']"
:rules="validator(feature.displayName, feature.valueType.validator)"
:extra="feature.description"
>
<Checkbox
v-if="feature.valueType.name === 'ToggleStringValueType' && feature.valueType.validator.name === 'BOOLEAN'"
v-model:checked="feature.value"
>{{ feature.displayName }}</Checkbox
>
<div v-else-if="feature.valueType.name === 'FreeTextStringValueType'">
<InputNumber
v-if="feature.valueType.validator.name === 'NUMERIC'"
style="width: 100%"
v-model:value="feature.value"
/>
<BInput v-else v-model:value="feature.value" />
</div>
<Select
v-else-if="feature.valueType.name === 'SelectionStringValueType'"
:allow-clear="true"
v-model:value="feature.value"
>
<Option
v-for="valueItem in (feature.valueType as SelectionStringValueType).itemSource.items"
:key="valueItem.value"
v-model:value="valueItem.value"
:label="Lr(valueItem.displayText.resourceName, valueItem.displayText.name)"
/>
</Select>
</FormItem>
</div>
</TabPane>
</Tabs>
</Form>
</BasicModal>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { Checkbox, Form, InputNumber, Select, Tabs } from 'ant-design-vue';
import { Input } from '/@/components/Input';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { useFeatures } from '../hooks/useFeatures';
const FormItem = Form.Item;
const Option = Select.Option;
const TabPane = Tabs.TabPane;
const BInput = Input!;
const { prefixCls } = useDesign('feature-modal');
const { L, Lr } = useLocalization(['AbpFeatureManagement']);
const formRel = ref(null);
const providerName = ref('');
const providerKey = ref(null);
const [registerModal, modalMethods] = useModalInner((data) => {
providerName.value = data.providerName;
providerKey.value = data.providerKey;
});
const { featureGroup, featureGroupKey, validator, handleSubmit, onGroupChange } = useFeatures({
providerName,
providerKey,
formRel,
modalMethods,
});
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-feature-modal';
.@{prefix-cls} {
&__tabs {
height: 500px;
::v-deep(.ant-tabs-content-holder) {
overflow-y: auto !important;
overflow-x: hidden !important;
}
}
}
</style>

171
apps/vue/src/components/Abp/LocalizableInput/LocalizableInput.vue

@ -0,0 +1,171 @@
<template>
<div style="width: 100%">
<ItemReset>
<InputGroup>
<Row :gutter="4">
<Col :span="8">
<Select
:class="`${prefixCls}__resource`"
:disabled="props.disabled"
:allow-clear="props.allowClear"
:placeholder="t('component.localizable_input.placeholder')"
v-model:value="state.resourceName"
:options="getResources"
@change="(value) => handleResourceChange(value?.toString(), true)"
/>
</Col>
<Col :span="16">
<Input
v-if="getIsFixed"
:class="`${prefixCls}__name`"
:disabled="props.disabled"
:allow-clear="props.allowClear"
:placeholder="t('component.localizable_input.resources.fiexed.placeholder')"
:value="state.displayName"
@change="(e) => handleDisplayNameChange(e.target.value)"
/>
<Select
v-else
:class="`${prefixCls}__name`"
:disabled="props.disabled"
:allow-clear="props.allowClear"
:placeholder="t('component.localizable_input.resources.localization.placeholder')"
:value="state.displayName"
:options="state.displayNames"
@change="handleDisplayNameChange"
/>
</Col>
</Row>
</InputGroup>
</ItemReset>
</div>
</template>
<script setup lang="ts">
import type { DefaultOptionType } from 'ant-design-vue/lib/select';
import { computed, watch, shallowReactive } from 'vue';
import { Form, Row, Col, Input, Select } from 'ant-design-vue';
import { propTypes } from '/@/utils/propTypes';
import { useI18n } from '/@/hooks/web/useI18n';
import { useDesign } from '/@/hooks/web/useDesign';
import { useLocalizationSerializer } from '/@/hooks/abp/useLocalizationSerializer';
import { useAbpStoreWithOut } from '/@/store/modules/abp';
import { isNullOrWhiteSpace } from '/@/utils/strings';
const ItemReset = Form.ItemRest;
const InputGroup = Input.Group;
interface State {
resourceName?: string,
displayName?: string,
displayNames: DefaultOptionType[],
}
const emits = defineEmits(['update:value', 'change']);
const props = defineProps({
value: propTypes.string,
allowClear: propTypes.bool.def(false),
disabled: propTypes.bool.def(false),
});
const { t } = useI18n();
const abpStore = useAbpStoreWithOut();
const { prefixCls } = useDesign('localizable-input');
const formItemContext = Form.useInjectFormItemContext();
const { values: resources } = abpStore.getApplication.localization;
const { deserialize, serialize } = useLocalizationSerializer();
const state = shallowReactive<State>({
displayNames: [] as any[],
});
const getIsFixed = computed(() => {
return state.resourceName === 'Fixed';
});
const getResources = computed(() => {
const sources = Object.keys(resources).map((key) => {
return {
label: key,
value: key,
}
});
return [
{
label: t('component.localizable_input.resources.fiexed.group'),
value: 'F',
options: [{
label: t('component.localizable_input.resources.fiexed.label'),
value: 'Fixed',
}]
},
{
label: t('component.localizable_input.resources.localization.group'),
value: 'R',
options: sources,
}
];
});
watch(
() => props.value,
(value) => {
const info = deserialize(value);
if (state.resourceName !== info.resourceName) {
state.resourceName = isNullOrWhiteSpace(info.resourceName) ? undefined : info.resourceName;
handleResourceChange(state.resourceName, false);
}
if (state.displayName !== info.name) {
state.displayName = isNullOrWhiteSpace(info.name) ? undefined : info.name;
}
},
{
immediate: true,
}
);
function handleResourceChange(value?: string, triggerChanged?: boolean) {
state.displayNames = [];
if (value && resources[value]) {
state.displayNames = Object.keys(resources[value]).map((key) => {
return {
label: resources[value][key] ? String(resources[value][key]) : key,
value: key,
};
});
}
state.displayName = undefined;
if (triggerChanged === true) {
triggerDisplayNameChange(state.displayName);
}
}
function handleDisplayNameChange(value?: string) {
triggerDisplayNameChange(value);
}
function triggerDisplayNameChange(value?: string) {
const inputValue = value === undefined ? '' : value;
let updateValue = '';
if (getIsFixed.value) {
updateValue = !isNullOrWhiteSpace(value) ? `F:${value}` : 'F:' ;
} else if (!isNullOrWhiteSpace(state.resourceName)) {
const info: LocalizableStringInfo = {
resourceName: state.resourceName ?? '',
name: inputValue,
};
updateValue = serialize(info);
}
emits('change', updateValue);
emits('update:value', updateValue);
formItemContext.onFieldChange();
}
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-localizable-input';
.@{prefix-cls} {
&__resource {
width: 100% !important;
}
&__name {
width: 100% !important;
}
}
</style>

3
apps/vue/src/components/Abp/LocalizableInput/index.ts

@ -0,0 +1,3 @@
import LocalizableInput from './LocalizableInput.vue';
export { LocalizableInput };

9
apps/vue/src/components/Abp/LocalizableInput/props.ts

@ -0,0 +1,9 @@
export interface Props {
resources: string[],
value: string,
}
export interface Resource {
name: string,
value: string,
}

305
apps/vue/src/components/Abp/SimpleStateChecking/SimpleStateChecking.vue

@ -0,0 +1,305 @@
<template>
<div :class="`${prefixCls}__container`">
<div class="card">
<Card>
<template #title>
<Row>
<Col :span="12">
<span>{{ t('component.simple_state_checking.title') }}</span>
</Col>
<Col :span="12">
<div class="toolbar" v-if="!props.disabled">
<Button type="primary" @click="handleAddNew">{{ t('component.simple_state_checking.actions.create') }}</Button>
<Button danger @click="handleClean">{{ t('component.simple_state_checking.actions.clean') }}</Button>
</div>
</Col>
</Row>
</template>
<Table :columns="getTableColumns" :data-source="getSimpleCheckers">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'name'">
<span>{{ simpleCheckerMap[record.name] }}</span>
</template>
<template v-else-if="column.key === 'properties'">
<div v-if="record.name === 'F'">
<Tag v-for="feature in record.featureNames">{{ feature }}</Tag>
</div>
<div v-else-if="record.name === 'G'">
<Tag v-for="feature in record.globalFeatureNames">{{ feature }}</Tag>
</div>
<div v-else-if="record.name === 'P'">
<Tag v-for="permission in record.model.permissions">{{ permission }}</Tag>
</div>
<div v-else-if="record.name === 'A'">
<span>{{ t('component.simple_state_checking.requireAuthenticated.title') }}</span>
</div>
</template>
<template v-else-if="column.key === 'action'">
<div :class="`${prefixCls}__action`">
<Button v-if="props.allowEdit" type="link" @click="() => handleEdit(record)">
<template #icon>
<EditOutlined />
</template>
{{ t('component.simple_state_checking.actions.update') }}
</Button>
<Divider v-if="props.allowEdit && props.allowDelete" type="vertical" />
<Button v-if="props.allowDelete" type="link" @click="() => handleDelete(record)" class="ant-btn-error">
<template #icon>
<DeleteOutlined />
</template>
{{ t('component.simple_state_checking.actions.delete') }}
</Button>
</div>
</template>
</template>
</Table>
</Card>
</div>
<Modal :class="`${prefixCls}__modal`" v-bind="state.modal">
<Form
ref="formRef"
class="form"
v-bind="state.form"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 16}"
>
<FormItem name="name" required :label="t('component.simple_state_checking.form.name')">
<Select
:disabled="state.form.editFlag"
:options="getStateCheckerOptions"
v-model:value="state.form.model.name"
@change="handleStateCheckerChange"
/>
</FormItem>
<component :is="componentsRef[state.form.editComponent]" v-model:value="state.form.model.stateChecker" />
</Form>
</Modal>
</div>
</template>
<script setup lang="ts">
import type { RuleObject } from 'ant-design-vue/lib/form';
import type { ColumnsType } from 'ant-design-vue/lib/table';
import { cloneDeep } from 'lodash-es';
import { computed, reactive, ref, unref, shallowRef, shallowReactive } from 'vue';
import { DeleteOutlined, EditOutlined } from '@ant-design/icons-vue';
import { Button, Card, Col, Divider, Empty, Form, Modal, Row, Select, Table, Tag } from 'ant-design-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { useI18n } from '/@/hooks/web/useI18n';
import { isNullOrUnDef } from '/@/utils/is';
import { SimplaCheckStateBase } from './typing';
import { useSimpleStateCheck } from '/@/hooks/abp/useSimpleStateCheck';
import { propTypes } from '/@/utils/propTypes';
import RequireGlobalFeaturesSimpleStateChecker from './src/globalFeatures/RequireGlobalFeaturesSimpleStateChecker.vue';
import RequirePermissionsSimpleStateChecker from './src/permissions/RequirePermissionsSimpleStateChecker.vue';
import RequireFeaturesSimpleStateChecker from './src/features/RequireFeaturesSimpleStateChecker.vue';
const FormItem = Form.Item;
interface State {
modal: {
title?: string,
visible?: boolean,
maskClosable?: boolean,
width?: number,
minHeight?: number,
onOk?: (e: MouseEvent) => void,
onCancel?: (e: MouseEvent) => void,
},
form: {
model: any,
editFlag: boolean,
editComponent: string,
rules?: Dictionary<string, RuleObject>,
},
}
const emits = defineEmits(['change', 'update:value']);
const props = defineProps({
value: propTypes.string,
state: {
type: Object as PropType<SimplaCheckStateBase>,
required: true,
},
allowEdit: propTypes.bool.def(false),
allowDelete: propTypes.bool.def(false),
disabled: propTypes.bool.def(false),
});
const { t } = useI18n();
const { serializer } = useSimpleStateCheck();
const { prefixCls } = useDesign('simple-state-checking');
const formRef = ref<any>();
const componentsRef = shallowRef({
'Empty': Empty,
'F': RequireFeaturesSimpleStateChecker,
'P': RequirePermissionsSimpleStateChecker,
'G': RequireGlobalFeaturesSimpleStateChecker,
});
const simpleCheckerMap = shallowReactive({
'F': t('component.simple_state_checking.requireFeatures.title'),
'G': t('component.simple_state_checking.requireGlobalFeatures.title'),
'P': t('component.simple_state_checking.requirePermissions.title'),
'A': t('component.simple_state_checking.requireAuthenticated.title')
});
const state = reactive<State>({
modal: {
width: 600,
minHeight: 400,
visible: false,
maskClosable: false,
onOk: handleSubmit,
onCancel: handleCancel,
},
form: {
model: {},
editFlag: false,
editComponent: 'Empty',
},
});
const getTableColumns = computed(() => {
const columns: ColumnsType = [{
title: t('component.simple_state_checking.table.name'),
dataIndex: 'name',
key: 'name',
align: 'left',
fixed: 'left',
width: 130,
},
{
title: t('component.simple_state_checking.table.properties'),
dataIndex: 'properties',
key: 'properties',
align: 'left',
fixed: 'left',
width: 200,
}];
return columns.concat(props.disabled
? []
: [{
width: 230,
title: t('component.simple_state_checking.table.actions'),
align: 'center',
dataIndex: 'action',
key: 'action',
fixed: 'right',
}]);
});
const getSimpleCheckers = computed(() => {
if (isNullOrUnDef(props.value) || props.value.length === 0) {
return [];
}
const simpleCheckers = serializer.deserializeArray(props.value, props.state);
return simpleCheckers;
});
const getStateCheckerOptions = computed(() => {
const stateCheckers = unref(getSimpleCheckers);
return Object.keys(simpleCheckerMap).map((key) => {
return {
label: simpleCheckerMap[key],
disabled: stateCheckers.some(x => (x as any).name === key),
value: key,
}
});
});
function handleAddNew() {
state.form.editFlag = false;
state.form.editComponent = 'Empty';
state.form.model = {};
state.modal.title = t('component.simple_state_checking.title');
state.modal.visible = true;
}
function handleStateCheckerChange(value: string) {
const stateChecker = serializer.deserialize({
T: value,
A: true,
N: [],
}, props.state);
state.form.model.stateChecker = stateChecker;
state.form.editComponent = value;
}
function handleEdit(record) {
state.form.editFlag = true;
state.form.editComponent = record.name;
state.form.model = {
name: record.name,
stateChecker: cloneDeep(record),
};
state.modal.title = simpleCheckerMap[record.name];
state.modal.visible = true;
}
function handleDelete(record) {
const stateCheckers = unref(getSimpleCheckers);
const filtedStateCheckers = stateCheckers.filter(x => (x as any).name !== record.name);
if (filtedStateCheckers.length === 0) {
handleClean();
return;
}
const serializedCheckers = serializer.serializeArray(filtedStateCheckers);
emits('change', serializedCheckers);
emits('update:value', serializedCheckers);
}
function handleClean() {
emits('change', undefined);
emits('update:value', undefined);
}
function handleCancel() {
state.form.model = {};
state.form.editFlag = false;
state.form.editComponent = 'Empty';
state.modal.visible = false;
}
function handleSubmit() {
const form = unref(formRef);
form?.validate().then(() => {
const input = cloneDeep(state.form.model.stateChecker);
const stateCheckers = cloneDeep(unref(getSimpleCheckers));
const inputIndex = stateCheckers.findIndex(x => (x as any).name === input.name);
if (inputIndex >= 0) {
stateCheckers[inputIndex] = input;
} else {
stateCheckers.push(input);
}
const updateValue = serializer.serializeArray(stateCheckers);
emits('change', updateValue);
emits('update:value', updateValue);
form?.resetFields();
state.modal.visible = false;
});
}
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-simple-state-checking';
.@{prefix-cls} {
&__container {
width: 100%;
.card {
.toolbar {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
margin-bottom: 8px;
> * {
margin-right: 8px;
}
}
}
}
&__modal {
.form {
margin: 20px;
}
}
}
</style>

6
apps/vue/src/components/Abp/SimpleStateChecking/index.ts

@ -0,0 +1,6 @@
import SimpleStateChecking from './SimpleStateChecking.vue';
import { SimplaCheckStateBase } from './typing';
import { PermissionState } from './src/permissions';
export { SimplaCheckStateBase, PermissionState };
export { SimpleStateChecking };

151
apps/vue/src/components/Abp/SimpleStateChecking/src/features/RequireFeaturesSimpleStateChecker.vue

@ -0,0 +1,151 @@
<template>
<div>
<FormItem
:name="['stateChecker', 'requiresAll']"
:label="t('component.simple_state_checking.requireFeatures.requiresAll')"
:extra="t('component.simple_state_checking.requireFeatures.requiresAllDesc')"
>
<Checkbox
:checked="state.stateChecker.requiresAll"
@change="handleChangeRequiresAll"
>
{{ t('component.simple_state_checking.requireFeatures.requiresAll') }}
</Checkbox>
</FormItem>
<FormItem :name="['stateChecker', 'featureNames']" required :label="t('component.simple_state_checking.requireFeatures.featureNames')">
<TreeSelect
allow-clear
tree-checkable
tree-check-strictly
:tree-data="state.treeData"
:field-names="{
label: 'displayName',
value: 'name',
children: 'children',
}"
:value="getRequiredFeatures"
@change="handleChangeFeatures"
/>
</FormItem>
</div>
</template>
<script setup lang="ts">
import type { CheckboxChangeEvent } from 'ant-design-vue/lib/checkbox/interface';
import { cloneDeep } from 'lodash-es';
import { computed, reactive, onMounted, watchEffect } from 'vue';
import { Checkbox, Form, TreeSelect } from 'ant-design-vue';
import { GetListAsyncByInput } from '/@/api/feature-management/definitions/features';
import { FeatureDefinitionDto } from '/@/api/feature-management/definitions/features/model';
import { useLocalizationSerializer } from '/@/hooks/abp/useLocalizationSerializer';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { useI18n } from '/@/hooks/web/useI18n';
import { listToTree } from '/@/utils/helper/treeHelper';
import { groupBy } from '/@/utils/array';
import { valueTypeSerializer } from '../../../StringValueType/valueType';
const FormItem = Form.Item;
interface FeatureTreeData {
name: string;
groupName: string;
displayName: string;
children: FeatureTreeData[];
}
interface StateChecker {
name: string;
requiresAll: boolean;
featureNames: string[];
}
interface State {
features: FeatureDefinitionDto[];
treeData: FeatureTreeData[];
stateChecker: StateChecker;
}
const emits = defineEmits(['change', 'update:value']);
const props = defineProps({
value: {
type: Object as PropType<StateChecker>,
required: true,
},
});
const { t } = useI18n();
const { Lr } = useLocalization();
const { deserialize } = useLocalizationSerializer();
const state = reactive<State>({
treeData: [],
features: [],
stateChecker: {
name: 'F',
requiresAll: true,
featureNames: [],
}
});
const getRequiredFeatures = computed(() => {
return state.features
.filter((feature) => state.stateChecker.featureNames.includes(feature.name))
.map((feature) => {
return {
label: feature.displayName,
value: feature.name,
};
});
});
watchEffect(() => {
state.stateChecker = props.value;
});
onMounted(fetchFeatures);
function fetchFeatures() {
GetListAsyncByInput({}).then((res) => {
state.features = res.items;
formatDisplayName(state.features);
const featureGroup = groupBy(cloneDeep(res.items), 'groupName');
const featureTreeData: FeatureTreeData[] = [];
Object.keys(featureGroup).forEach((gk) => {
const featuresByGroup = featureGroup[gk].filter(feature => {
const valueType = valueTypeSerializer.deserialize(feature.valueType);
return valueType.validator.name === 'BOOLEAN';
});
const featureTree = listToTree(featuresByGroup, {
id: 'name',
pid: 'parentName',
});
featureTreeData.push(...featureTree);
});
state.treeData = featureTreeData;
});
}
function formatDisplayName(list: any[]) {
if (list && Array.isArray(list)) {
list.forEach((item) => {
if (Reflect.has(item, 'displayName')) {
const info = deserialize(item.displayName);
item.displayName = Lr(info.resourceName, info.name);
}
if (Reflect.has(item, 'children')) {
formatDisplayName(item.children);
}
});
}
}
function handleChangeRequiresAll(e: CheckboxChangeEvent) {
state.stateChecker.requiresAll = e.target.checked;
emits('change', state.stateChecker);
emits('update:value', state.stateChecker);
}
function handleChangeFeatures(value: any[]) {
state.stateChecker.featureNames = value.map((val) => val.value);
emits('change', state.stateChecker);
emits('update:value', state.stateChecker);
}
</script>
<style scoped>
</style>

3
apps/vue/src/components/Abp/SimpleStateChecking/src/features/index.ts

@ -0,0 +1,3 @@
import RequireFeaturesSimpleStateChecker from './RequireFeaturesSimpleStateChecker.vue';
export { RequireFeaturesSimpleStateChecker };

90
apps/vue/src/components/Abp/SimpleStateChecking/src/globalFeatures/RequireGlobalFeaturesSimpleStateChecker.vue

@ -0,0 +1,90 @@
<template>
<div>
<FormItem
:name="['stateChecker', 'requiresAll']"
:label="t('component.simple_state_checking.requireFeatures.requiresAll')"
:extra="t('component.simple_state_checking.requireFeatures.requiresAllDesc')"
>
<Checkbox
:checked="state.stateChecker.requiresAll"
@change="handleChangeRequiresAll"
>
{{ t('component.simple_state_checking.requireFeatures.requiresAll') }}
</Checkbox>
</FormItem>
<FormItem
:name="['stateChecker', 'featureNames']"
required
:label="t('component.simple_state_checking.requireGlobalFeatures.featureNames')"
>
<TextArea
allow-clear
:value="getRequiredFeatures"
@change="(e) => handleChangeFeatures(e.target.value)"
/>
</FormItem>
</div>
</template>
<script setup lang="ts">
import type { CheckboxChangeEvent } from 'ant-design-vue/lib/checkbox/interface';
import { computed, reactive, watchEffect } from 'vue';
import { Checkbox, Form, Input } from 'ant-design-vue';
import { useI18n } from '/@/hooks/web/useI18n';
const FormItem = Form.Item;
const TextArea = Input.TextArea;
interface StateChecker {
name: string;
requiresAll: boolean;
featureNames: string[];
}
interface State {
stateChecker: StateChecker;
}
const emits = defineEmits(['change', 'update:value']);
const props = defineProps({
value: {
type: Object as PropType<StateChecker>,
required: true,
},
});
const { t } = useI18n();
const state = reactive<State>({
stateChecker: {
name: 'G',
requiresAll: true,
featureNames: [],
}
});
const getRequiredFeatures = computed(() => {
let features = state.stateChecker.featureNames.join(',');
return features.length > 0 ? features.substring(0, features.length - 1) : features;
});
watchEffect(() => {
state.stateChecker = props.value;
});
function handleChangeRequiresAll(e: CheckboxChangeEvent) {
state.stateChecker.requiresAll = e.target.checked;
emits('change', state.stateChecker);
emits('update:value', state.stateChecker);
}
function handleChangeFeatures(value?: string) {
if (!value) {
state.stateChecker.featureNames = [];
return;
}
state.stateChecker.featureNames = value.split(',');
emits('change', state.stateChecker);
emits('update:value', state.stateChecker);
}
</script>
<style scoped>
</style>

3
apps/vue/src/components/Abp/SimpleStateChecking/src/globalFeatures/index.ts

@ -0,0 +1,3 @@
import RequireGlobalFeaturesSimpleStateChecker from './RequireGlobalFeaturesSimpleStateChecker.vue';
export { RequireGlobalFeaturesSimpleStateChecker };

155
apps/vue/src/components/Abp/SimpleStateChecking/src/permissions/RequirePermissionsSimpleStateChecker.vue

@ -0,0 +1,155 @@
<template>
<div>
<FormItem
:name="['stateChecker', 'model', 'featureNames']"
:label="t('component.simple_state_checking.requirePermissions.requiresAll')"
:extra="t('component.simple_state_checking.requirePermissions.requiresAllDesc')"
>
<Checkbox
:checked="state.stateChecker.model.requiresAll"
@change="handleChangeRequiresAll"
>
{{ t('component.simple_state_checking.requirePermissions.requiresAll') }}
</Checkbox>
</FormItem>
<FormItem
:name="['stateChecker', 'model', 'permissions']"
required
:label="t('component.simple_state_checking.requirePermissions.permissions')"
>
<TreeSelect
allow-clear
tree-checkable
tree-check-strictly
:tree-data="state.treeData"
:field-names="{
label: 'displayName',
value: 'name',
children: 'children',
}"
:value="getRequiredPermissions"
@change="handleChangePermissions"
/>
</FormItem>
</div>
</template>
<script setup lang="ts">
import type { CheckboxChangeEvent } from 'ant-design-vue/lib/checkbox/interface';
import { cloneDeep } from 'lodash-es';
import { computed, reactive, onMounted, watchEffect } from 'vue';
import { Checkbox, Form, TreeSelect } from 'ant-design-vue';
import { GetListAsyncByInput } from '/@/api/permission-management/definitions/permissions';
import { PermissionDefinitionDto } from '/@/api/permission-management/definitions/permissions/model';
import { useLocalizationSerializer } from '/@/hooks/abp/useLocalizationSerializer';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { useI18n } from '/@/hooks/web/useI18n';
import { listToTree } from '/@/utils/helper/treeHelper';
import { groupBy } from '/@/utils/array';
const FormItem = Form.Item;
interface TreeData {
name: string;
groupName: string;
displayName: string;
children: TreeData[];
}
interface StateCheckerModel {
requiresAll: boolean;
permissions: string[];
}
interface StateChecker {
name: string;
model: StateCheckerModel;
}
interface State {
permissions: PermissionDefinitionDto[];
treeData: TreeData[];
stateChecker: StateChecker;
}
const emits = defineEmits(['change', 'update:value']);
const props = defineProps({
value: {
type: Object as PropType<StateChecker>,
required: true,
},
});
const { t } = useI18n();
const { Lr } = useLocalization();
const { deserialize } = useLocalizationSerializer();
const state = reactive<State>({
treeData: [],
permissions: [],
stateChecker: {
name: 'P',
model: {
requiresAll: true,
permissions: [],
},
}
});
const getRequiredPermissions = computed(() => {
return state.permissions
.filter((permission) => state.stateChecker.model.permissions.includes(permission.name))
.map((permission) => {
return {
label: permission.displayName,
value: permission.name,
};
});
});
watchEffect(() => {
state.stateChecker = props.value;
});
onMounted(fetchFeatures);
function fetchFeatures() {
GetListAsyncByInput({}).then((res) => {
state.permissions = res.items;
formatDisplayName(state.permissions);
const permissionGroup = groupBy(cloneDeep(res.items), 'groupName');
const permissionGroupTreeData: TreeData[] = [];
Object.keys(permissionGroup).forEach((gk) => {
const featureTree = listToTree(permissionGroup[gk], {
id: 'name',
pid: 'parentName',
});
permissionGroupTreeData.push(...featureTree);
});
state.treeData = permissionGroupTreeData;
});
}
function formatDisplayName(list: any[]) {
if (list && Array.isArray(list)) {
list.forEach((item) => {
if (Reflect.has(item, 'displayName')) {
const info = deserialize(item.displayName);
item.displayName = Lr(info.resourceName, info.name);
}
if (Reflect.has(item, 'children')) {
formatDisplayName(item.children);
}
});
}
}
function handleChangeRequiresAll(e: CheckboxChangeEvent) {
state.stateChecker.model.requiresAll = e.target.checked;
emits('change', state.stateChecker);
emits('update:value', state.stateChecker);
}
function handleChangePermissions(value: any[]) {
state.stateChecker.model.permissions = value.map((val) => val.value);
emits('change', state.stateChecker);
emits('update:value', state.stateChecker);
}
</script>
<style scoped>
</style>

7
apps/vue/src/components/Abp/SimpleStateChecking/src/permissions/index.ts

@ -0,0 +1,7 @@
import RequirePermissionsSimpleStateChecker from './RequirePermissionsSimpleStateChecker.vue';
import { PermissionState } from './typing';
export {
RequirePermissionsSimpleStateChecker,
PermissionState,
};

3
apps/vue/src/components/Abp/SimpleStateChecking/src/permissions/typing.ts

@ -0,0 +1,3 @@
export class PermissionState implements IHasSimpleStateCheckers<PermissionState> {
stateCheckers: ISimpleStateChecker<PermissionState>[] = [];
}

3
apps/vue/src/components/Abp/SimpleStateChecking/typing.ts

@ -0,0 +1,3 @@
export class SimplaCheckStateBase implements IHasSimpleStateCheckers<SimplaCheckStateBase> {
stateCheckers: ISimpleStateChecker<SimplaCheckStateBase>[] = [];
}

560
apps/vue/src/components/Abp/StringValueType/StringValueTypeInput.vue

@ -0,0 +1,560 @@
<template>
<div style="width: 100%" :class="`${prefixCls}__container`">
<FormItemRest>
<Card>
<template #title>
<div :class="`${prefixCls}__type`">
<Row>
<Col :span="11">
<span>{{ t('component.value_type_nput.type.name') }}</span>
</Col>
<Col :span="2">
<div style="width: 100%"></div>
</Col>
<Col :span="11">
<span>{{ t('component.value_type_nput.validator.name') }}</span>
</Col>
</Row>
<Row>
<Col :span="11">
<Select :disabled="props.disabled" :value="state.valueType.name" @change="handleValueTypeChange">
<Option value="FreeTextStringValueType">{{ t('component.value_type_nput.type.FREE_TEXT.name') }}</Option>
<Option value="ToggleStringValueType">{{ t('component.value_type_nput.type.TOGGLE.name') }}</Option>
<Option value="SelectionStringValueType">{{ t('component.value_type_nput.type.SELECTION.name') }}</Option>
</Select>
</Col>
<Col :span="2">
<div style="width: 100%"></div>
</Col>
<Col :span="11">
<Select :disabled="props.disabled" :value="state.valueType.validator.name" @change="handleValidatorChange">
<Option value="NULL">{{ t('component.value_type_nput.validator.NULL.name') }}</Option>
<Option value="BOOLEAN" :disabled="state.valueType.name !== 'ToggleStringValueType'">
{{ t('component.value_type_nput.validator.BOOLEAN.name') }}
</Option>
<Option value="NUMERIC" :disabled="state.valueType.name !== 'FreeTextStringValueType'">
{{ t('component.value_type_nput.validator.NUMERIC.name') }}
</Option>
<Option value="STRING" :disabled="state.valueType.name !== 'FreeTextStringValueType'">
{{ t('component.value_type_nput.validator.STRING.name') }}
</Option>
</Select>
</Col>
</Row>
</div>
</template>
<div :class="`${prefixCls}__wrap`">
<Row>
<Col :span="24">
<div v-if="state.valueType.name === 'FreeTextStringValueType'">
<div v-if="state.valueType.validator.name === 'NUMERIC'" class="numeric">
<Row>
<Col :span="11">
<span>{{ t('component.value_type_nput.validator.NUMERIC.minValue') }}</span>
</Col>
<Col :span="2">
<div style="width: 100%"></div>
</Col>
<Col :span="11">
<span>{{ t('component.value_type_nput.validator.NUMERIC.maxValue') }}</span>
</Col>
</Row>
<Row>
<Col :span="11">
<InputNumber
:disabled="props.disabled"
style="width: 100%"
v-model:value="(state.valueType.validator as NumericValueValidator).minValue"
/>
</Col>
<Col :span="2">
<div style="width: 100%"></div>
</Col>
<Col :span="11">
<InputNumber
:disabled="props.disabled"
style="width: 100%"
v-model:value="(state.valueType.validator as NumericValueValidator).maxValue"
/>
</Col>
</Row>
</div>
<div v-else-if="state.valueType.validator.name === 'STRING'" class="string">
<Row style="margin-top: 10px;">
<Col :span="24">
<Checkbox
:disabled="props.disabled"
style="width: 100%"
v-model:checked="(state.valueType.validator as StringValueValidator).allowNull"
>
{{ t('component.value_type_nput.validator.STRING.allowNull') }}
</Checkbox>
</Col>
</Row>
<Row style="margin-top: 10px;">
<Col :span="24">
<span>{{ t('component.value_type_nput.validator.STRING.regularExpression') }}</span>
</Col>
</Row>
<Row>
<Col :span="24">
<Input
:disabled="props.disabled"
style="width: 100%"
v-model:value="(state.valueType.validator as StringValueValidator).regularExpression"
/>
</Col>
</Row>
<Row style="margin-top: 10px;">
<Col :span="11">
<span>{{ t('component.value_type_nput.validator.STRING.minLength') }}</span>
</Col>
<Col :span="2">
<div style="width: 100%"></div>
</Col>
<Col :span="11">
<span>{{ t('component.value_type_nput.validator.STRING.maxLength') }}</span>
</Col>
</Row>
<Row>
<Col :span="11">
<InputNumber
:disabled="props.disabled"
style="width: 100%"
v-model:value="(state.valueType.validator as StringValueValidator).minLength"
/>
</Col>
<Col :span="2">
<div style="width: 100%"></div>
</Col>
<Col :span="11">
<InputNumber
:disabled="props.disabled"
style="width: 100%"
v-model:value="(state.valueType.validator as StringValueValidator).maxLength"
/>
</Col>
</Row>
</div>
</div>
<div v-else-if="state.valueType.name === 'SelectionStringValueType'">
<Card class="selection">
<template #title>
<Row>
<Col :span="12">
<div class="valid" v-if="(state.valueType as SelectionStringValueType).itemSource.items.length <= 0">
<span>{{ t('component.value_type_nput.type.SELECTION.itemsNotBeEmpty') }}</span>
</div>
</Col>
<Col :span="12">
<div class="toolbar" v-if="!props.disabled">
<Button type="primary" @click="handleAddNew">{{ t('component.value_type_nput.type.SELECTION.actions.create') }}</Button>
<Button danger @click="handleClean">{{ t('component.value_type_nput.type.SELECTION.actions.clean') }}</Button>
</div>
</Col>
</Row>
</template>
<Table :columns="getTableColumns" :data-source="(state.valueType as SelectionStringValueType).itemSource.items">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'displayText'">
<span>{{ getDisplayName(record.displayText) }}</span>
</template>
<template v-else-if="column.key === 'action'">
<div :class="`${prefixCls}__action`">
<Button v-if="props.allowEdit" type="link" @click="() => handleEdit(record)">
<template #icon>
<EditOutlined />
</template>
{{ t('component.value_type_nput.type.SELECTION.actions.update') }}
</Button>
<Divider v-if="props.allowEdit && props.allowDelete" type="vertical" />
<Button v-if="props.allowDelete" type="link" @click="() => handleDelete(record)" class="ant-btn-error">
<template #icon>
<DeleteOutlined />
</template>
{{ t('component.value_type_nput.type.SELECTION.actions.delete') }}
</Button>
</div>
</template>
</template>
</Table>
</Card>
</div>
</Col>
</Row>
</div>
</Card>
</FormItemRest>
<Modal :class="`${prefixCls}__modal`" v-bind="state.selection.modal">
<Form
ref="formRef"
class="form"
v-bind="state.selection.form"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 18}"
>
<FormItem name="displayText" required :label="t('component.value_type_nput.type.SELECTION.displayText')">
<LocalizableInput :disabled="props.disabled || state.selection.form.editFlag" v-model:value="state.selection.form.model.displayText" />
</FormItem>
<FormItem name="value" required :label="t('component.value_type_nput.type.SELECTION.value')">
<Input :disabled="props.disabled" v-model:value="state.selection.form.model.value" />
</FormItem>
</Form>
</Modal>
</div>
</template>
<script setup lang="ts">
import type { ColumnsType } from 'ant-design-vue/lib/table';
import type { RuleObject } from 'ant-design-vue/lib/form';
import { computed, reactive, ref, unref, watch } from 'vue';
import { DeleteOutlined, EditOutlined } from '@ant-design/icons-vue';
import {
Button,
Card,
Checkbox,
Col,
Divider,
Form,
Input,
InputNumber,
Modal,
Row,
Select,
Table,
} from 'ant-design-vue';
import { useLocalizationSerializer } from '/@/hooks/abp/useLocalizationSerializer';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { useDesign } from '/@/hooks/web/useDesign';
import { useI18n } from '/@/hooks/web/useI18n';
import { isNullOrWhiteSpace } from '/@/utils/strings';
import { propTypes } from '/@/utils/propTypes';
import {
AlwaysValidValueValidator,
BooleanValueValidator,
NumericValueValidator,
StringValueValidator,
} from './validator';
import {
StringValueType,
FreeTextStringValueType,
SelectionStringValueType,
ToggleStringValueType,
valueTypeSerializer,
} from './valueType';
import { LocalizableInput } from '../LocalizableInput';
const FormItemRest = Form.ItemRest;
const FormItem = Form.Item;
const Option = Select.Option;
const emits = defineEmits(['change', 'update:value', 'change:valueType', 'change:validator', 'change:selection']);
const props = defineProps({
value: propTypes.string.def('{}'),
allowEdit: propTypes.bool.def(false),
allowDelete: propTypes.bool.def(false),
disabled: propTypes.bool.def(false),
});
const { t } = useI18n();
const { Lr } = useLocalization();
const { deserialize, serialize, validate: validateLocalizer } = useLocalizationSerializer();
const { prefixCls } = useDesign('string-value-type-input');
interface Selection {
modal: {
title?: string,
visible?: boolean,
maskClosable?: boolean,
width?: number,
minHeight?: number,
onOk?: (e: MouseEvent) => void,
onCancel?: (e: MouseEvent) => void,
},
form: {
model: any,
editFlag: boolean,
rules?: Dictionary<string, RuleObject>,
},
}
interface State {
value?: string;
valueType: StringValueType;
selection: Selection;
}
const formRef = ref<any>();
const state = reactive<State>({
valueType: new FreeTextStringValueType(),
selection: {
modal: {
title: t('component.value_type_nput.type.SELECTION.modal.title'),
width: 600,
minHeight: 400,
visible: false,
maskClosable: false,
onOk: handleSubmit,
onCancel: handleCancel,
},
form: {
model: {},
editFlag: false,
rules: {
displayText: {
validator: (_rule, value) => {
if (!validateLocalizer(value)) {
return Promise.reject(t('component.value_type_nput.type.SELECTION.displayTextNotBeEmpty'));
}
const items = (state.valueType as SelectionStringValueType).itemSource.items;
if (items.findIndex(x => serialize(x.displayText) === value) >= 0) {
return Promise.reject(t('component.value_type_nput.type.SELECTION.duplicateKeyOrValue'));
}
return Promise.resolve();
}
},
value: {
validator: (_rule, value) => {
const items = (state.valueType as SelectionStringValueType).itemSource.items;
if (items.findIndex(x => x.value === value) >= 0) {
return Promise.reject(t('component.value_type_nput.type.SELECTION.duplicateKeyOrValue'));
}
return Promise.resolve();
}
}
},
},
},
});
const getTableColumns = computed(() => {
const columns: ColumnsType = [{
title: t('component.value_type_nput.type.SELECTION.displayText'),
dataIndex: 'displayText',
key: 'displayText',
align: 'left',
fixed: 'left',
width: 180,
},
{
title: t('component.value_type_nput.type.SELECTION.value'),
dataIndex: 'value',
key: 'value',
align: 'left',
fixed: 'left',
width: 200,
}];
return columns.concat(props.disabled
? []
: [{
width: 180,
title: t('component.value_type_nput.type.SELECTION.actions.title'),
align: 'center',
dataIndex: 'action',
key: 'action',
fixed: 'right',
}]);
});
watch(
() => props.value,
(value) => {
if (isNullOrWhiteSpace(value) || value === '{}') {
state.valueType = new FreeTextStringValueType();
} else {
_formatValueType(value);
}
},
{
immediate: true,
}
);
watch(
() => state.valueType,
(valueType) => {
const isSelection = valueType.name === 'SelectionStringValueType';
if (isSelection &&
(valueType as SelectionStringValueType).itemSource.items.length < 0) {
return;
}
state.value = valueTypeSerializer.serialize(valueType);
emits('change:valueType', state.valueType.name);
emits('change:validator', state.valueType.validator.name);
if (isSelection) {
emits('change:selection', (valueType as SelectionStringValueType).itemSource.items);
}
},
{
deep: true,
immediate: false,
},
);
watch(
() => state.value,
(value) => {
emits('change', value);
emits('update:value', value);
},
);
const getDisplayName = (displayName: LocalizableStringInfo) => {
return Lr(displayName.resourceName, displayName.name);
};
function validate(value: any) {
if (state.valueType.name === 'SelectionStringValueType') {
const items = (state.valueType as SelectionStringValueType).itemSource.items;
if (items.length === 0) {
return Promise.reject(t('component.value_type_nput.type.SELECTION.itemsNotBeEmpty'));
}
if (value && items.findIndex((item) => item.value === value) < 0) {
return Promise.reject(t('component.value_type_nput.type.SELECTION.itemsNotFound'));
}
}
if (!state.valueType.validator.isValid(value)) {
return Promise.reject(
t('component.value_type_nput.validator.isInvalidValue',
[t(`component.value_type_nput.validator.${state.valueType.validator.name}.name`)]));
}
return Promise.resolve(value);
}
function _formatValueType(valueTypeString: string) {
try {
state.valueType = valueTypeSerializer.deserialize(valueTypeString);
} catch {
console.warn('Unable to serialize validator example, check that "valueType" value is valid');
}
}
function handleValueTypeChange(type: string) {
switch (type) {
case 'TOGGLE':
case 'ToggleStringValueType':
state.valueType = new ToggleStringValueType();
break;
case 'SELECTION':
case 'SelectionStringValueType':
state.valueType = new SelectionStringValueType();
break;
case 'FREE_TEXT':
case 'FreeTextStringValueType':
default:
state.valueType = new FreeTextStringValueType();
break;
}
}
function handleValidatorChange(validator: string) {
switch (validator) {
case 'NULL':
state.valueType.validator = new AlwaysValidValueValidator();
break;
case 'BOOLEAN':
state.valueType.validator = new BooleanValueValidator();
break;
case 'NUMERIC':
state.valueType.validator = new NumericValueValidator();
break;
case 'STRING':
default:
state.valueType.validator = new StringValueValidator();
break;
}
emits('change:validator', state.valueType.validator.name);
}
function handleAddNew() {
state.selection.form.model = {};
state.selection.form.editFlag = false;
state.selection.modal.visible = true;
}
function handleClean() {
const items = (state.valueType as SelectionStringValueType).itemSource.items;
items.length = 0;
emits('change:selection', items);
}
function handleEdit(record) {
state.selection.form.model = {
value: record.value,
displayText: serialize(record.displayText),
};
state.selection.form.editFlag = true;
state.selection.modal.visible = true;
}
function handleCancel() {
state.selection.form.model = {};
state.selection.form.editFlag = false;
state.selection.modal.visible = false;
}
function handleSubmit() {
const form = unref(formRef);
form?.validate().then(() => {
const displayText = state.selection.form.model.displayText;
const items = (state.valueType as SelectionStringValueType).itemSource.items;
if (!state.selection.form.editFlag) {
items.push({
displayText: deserialize(displayText),
value: state.selection.form.model.value,
});
} else {
const findIndex = items.findIndex(x => serialize(x.displayText) === displayText);
items[findIndex].value = state.selection.form.model.value;
}
form.resetFields();
state.selection.modal.visible = false;
emits('change:selection', items);
});
}
function handleDelete(record) {
const displayText = serialize(record.displayText);
const items = (state.valueType as SelectionStringValueType).itemSource.items;
const findIndex = items.findIndex(x => serialize(x.displayText) === displayText);
items.splice(findIndex, 1);
emits('change:selection', items);
}
defineExpose({
validate,
});
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-string-value-type-input';
.@{prefix-cls} {
&__type {
width: 100% !important;
}
&__wrap {
width: 100% !important;
.selection {
.valid {
color: red;
font-size: 14;
}
.toolbar {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
margin-bottom: 8px;
> * {
margin-right: 8px;
}
}
}
}
}
&__modal {
height: 500px;
.form {
margin: 10px;
}
}
</style>

3
apps/vue/src/components/Abp/StringValueType/index.ts

@ -0,0 +1,3 @@
import StringValueTypeInput from './StringValueTypeInput.vue';
export { StringValueTypeInput };

128
apps/vue/src/components/Abp/StringValueType/validator.ts

@ -0,0 +1,128 @@
import { isBoolean, isNullOrUnDef, isNumber } from "/@/utils/is";
import { isNullOrWhiteSpace } from "/@/utils/strings";
export interface ValueValidator {
name: string;
properties: Dictionary<string, any>;
isValid(value?: any): boolean;
}
export class AlwaysValidValueValidator implements ValueValidator {
name = "NULL";
properties: Dictionary<string, any>;
constructor() {
this.properties = {};
}
isValid(_value?: any): boolean {
return true;
}
}
export class BooleanValueValidator implements ValueValidator {
name = "BOOLEAN";
properties: Dictionary<string, any>;
constructor() {
this.properties = {};
}
isValid(value?: any): boolean {
if (isNullOrUnDef(value)) return true;
if (isBoolean(value)) return true;
const bolString = String(value).toLowerCase();
if (bolString === 'true' || bolString === 'false') return true;
return false;
}
}
export class NumericValueValidator implements ValueValidator {
name = "NUMERIC";
properties: Dictionary<string, any>;
constructor() {
this.properties = {};
}
get minValue(): number | undefined {
return Number(this.properties['MinValue']);
}
set minValue(value: number) {
this.properties['MinValue'] = value;
}
get maxValue(): number | undefined {
return Number(this.properties['MaxValue']);
}
set maxValue(value: number) {
this.properties['MaxValue'] = value;
}
isValid(value?: any): boolean {
if (isNullOrUnDef(value)) return true;
if (isNumber(value)) return this._isValidInternal(value);
const numString = String(value);
if (!isNullOrUnDef(numString)) {
const num = Number(numString);
if (num) return this._isValidInternal(num);
}
return false;
}
_isValidInternal(value: number): boolean {
if (this.minValue && value < this.minValue) return false;
if (this.maxValue && value > this.maxValue) return false;
return true;
}
}
export class StringValueValidator implements ValueValidator {
name = "STRING";
properties: Dictionary<string, any>;
constructor() {
this.properties = {};
}
get allowNull(): boolean {
return String(this.properties['AllowNull'] ?? 'true')?.toLowerCase() === 'true';
}
set allowNull(value: boolean) {
this.properties['AllowNull'] = value;
}
get minLength(): number | undefined {
return Number(this.properties['MinLength']);
}
set minLength(value: number) {
this.properties['MinLength'] = value;
}
get maxLength(): number | undefined {
return Number(this.properties['MaxLength']);
}
set maxLength(value: number) {
this.properties['MaxLength'] = value;
}
get regularExpression(): string {
return String(this.properties['RegularExpression'] ?? '');
}
set regularExpression(value: string) {
this.properties['RegularExpression'] = value
}
isValid(value?: any): boolean {
if (!this.allowNull && isNullOrUnDef(value)) return false;
const valueString = String(value);
if (!this.allowNull && isNullOrWhiteSpace(valueString.trim())) return false;
if (this.minLength && this.minLength > 0 && valueString.length < this.minLength) return false;
if (this.maxLength && this.maxLength > 0 && valueString.length > this.maxLength) return false;
if (!isNullOrWhiteSpace(this.regularExpression)) {
return RegExp(this.regularExpression).test(valueString);
}
return true;
}
}

122
apps/vue/src/components/Abp/StringValueType/valueType.ts

@ -0,0 +1,122 @@
import {
ValueValidator,
AlwaysValidValueValidator,
BooleanValueValidator,
NumericValueValidator,
StringValueValidator,
} from "./validator";
export interface StringValueType {
name: string;
properties: Dictionary<string, any>;
validator: ValueValidator;
}
export interface SelectionStringValueItem {
value: string;
displayText: LocalizableStringInfo;
}
export interface SelectionStringValueItemSource {
items: SelectionStringValueItem[];
}
export class FreeTextStringValueType implements StringValueType {
name = "FreeTextStringValueType";
properties: Dictionary<string, any>;
validator: ValueValidator;
constructor(validator?: ValueValidator) {
this.properties = {};
this.validator = validator ?? new AlwaysValidValueValidator();
}
}
export class ToggleStringValueType implements StringValueType {
name = "ToggleStringValueType";
properties: Dictionary<string, any>;
validator: ValueValidator;
constructor(validator?: ValueValidator) {
this.properties = {};
this.validator = validator ?? new BooleanValueValidator();
}
}
export class SelectionStringValueType implements StringValueType {
name = "SelectionStringValueType";
properties: Dictionary<string, any>;
validator: ValueValidator;
itemSource: SelectionStringValueItemSource;
constructor(validator?: ValueValidator) {
this.properties = {};
this.itemSource = {
items: [],
};
this.validator = validator ?? new AlwaysValidValueValidator();
}
}
class StringValueTypeSerializer {
serialize(value: StringValueType): string {
// console.log('serialize', value);
const valueTypeString = JSON.stringify(value);
// console.log('deserialize to obj', valueTypeString);
return valueTypeString;
}
deserialize(value: string): StringValueType {
let valueType: StringValueType;
const valueTypeObj = JSON.parse(value);
// console.log('deserialize', value);
// console.log('deserialize to obj', valueTypeObj);
switch (valueTypeObj.name) {
case 'TOGGLE':
case 'ToggleStringValueType':
// console.log('deserialize valueType to TOGGLE', valueTypeObj.name);
valueType = new ToggleStringValueType();
break;
case 'SELECTION':
case 'SelectionStringValueType':
// console.log('deserialize valueType to SELECTION', valueTypeObj.name);
valueType = new SelectionStringValueType();
(valueType as SelectionStringValueType).itemSource = valueTypeObj.itemSource;
break;
default:
case 'FREE_TEXT':
case 'FreeTextStringValueType':
// console.log('deserialize valueType to FREE_TEXT or default', valueTypeObj.name);
valueType = new FreeTextStringValueType();
break;
}
valueType.properties = valueTypeObj.properties;
valueType.validator = this._deserializeValidator(valueTypeObj.validator);
return valueType;
}
_deserializeValidator(validator: any): ValueValidator {
let convertValidator: ValueValidator = new AlwaysValidValueValidator();
if (validator.name) {
switch (validator.name) {
case 'BOOLEAN':
// console.log('deserialize validator to BOOLEAN', validator.name);
convertValidator = new BooleanValueValidator();
break;
case 'NUMERIC':
// console.log('deserialize validator to NUMERIC', validator.name);
convertValidator = new NumericValueValidator();
break;
case 'STRING':
// console.log('deserialize validator to STRING', validator.name);
convertValidator = new StringValueValidator();
break;
case 'NULL':
// console.log('deserialize validator to NULL', validator.name);
convertValidator = new AlwaysValidValueValidator();
break;
}
}
convertValidator.properties = validator.properties;
return convertValidator;
}
}
export const valueTypeSerializer = new StringValueTypeSerializer();

5
apps/vue/src/components/Abp/index.ts

@ -0,0 +1,5 @@
export * from './ExtraPropertyDictionary';
export * from './FeatureModal';
export * from './LocalizableInput';
export * from './SimpleStateChecking';
export * from './StringValueType';

7
apps/vue/src/components/Form/src/componentMap.ts

@ -34,6 +34,10 @@ import { IconPicker } from '/@/components/Icon';
import { CountdownInput } from '/@/components/CountDown';
import { Input as BInput } from '/@/components/Input';
import { CodeEditorX } from '/@/components/CodeEditor';
import {
ExtraPropertyDictionary,
LocalizableInput
} from '/@/components/Abp';
const componentMap = new Map<ComponentType, Component>();
const customComponentMap = new Map<ComponentType, Component>();
@ -76,6 +80,9 @@ componentMap.set('WeekPicker', DatePicker.WeekPicker);
componentMap.set('TimePicker', TimePicker);
componentMap.set('Divider', Divider);
componentMap.set('ExtraPropertyDictionary', ExtraPropertyDictionary);
componentMap.set('LocalizableInput', LocalizableInput);
customComponentMap.forEach((v, k) => {
componentMap.set(k, v);
});

4
apps/vue/src/components/Form/src/types/index.ts

@ -115,4 +115,6 @@ export type ComponentType =
| 'Rate'
| 'Divider'
| 'ApiTransfer'
| 'CodeEditorX';
| 'CodeEditorX'
| 'ExtraPropertyDictionary'
| 'LocalizableInput';

104
apps/vue/src/components/Permission/src/PermissionModal.vue

@ -5,63 +5,57 @@
@register="registerModal"
:title="getIdentity"
:width="800"
:min-height="600"
:min-height="400"
:mask-closable="false"
:can-fullscreen="false"
@ok="handleSubmit"
@visible-change="handleVisibleChange"
>
<Row ref="rowRef">
<Col :span="24" ref="preColRef">
<Checkbox
:checked="permissionTreeCheckState.checked"
:indeterminate="permissionTreeCheckState.indeterminate"
@change="handleGrantAllPermission"
>{{ L('SelectAllInAllTabs') }}</Checkbox
>
</Col>
<Divider />
<Col :span="24">
<Tabs v-model="activeKey" tab-position="left" type="card">
<TabPane
v-for="permission in permissionTree"
:key="permission.name"
:tab="permissionTab(permission)"
<Checkbox
:checked="permissionTreeCheckState.checked"
:indeterminate="permissionTreeCheckState.indeterminate"
@change="handleGrantAllPermission"
>{{ L('SelectAllInAllTabs') }}</Checkbox
>
<Divider />
<Tabs :class="`${prefixCls}__tabs`" v-model="activeKey" tab-position="left" type="card">
<TabPane
v-for="permission in permissionTree"
:key="permission.name"
:tab="permissionTab(permission)"
>
<Card :title="permission.displayName" :bordered="false">
<Checkbox
:checked="permissionTabCheckState(permission).checked"
:indeterminate="permissionTabCheckState(permission).indeterminate"
@change="(e) => handleGrantPermissions(permission, e)"
>{{ L('SelectAllInThisTab') }}</Checkbox
>
<Card :title="permission.displayName" :bordered="false">
<Checkbox
:checked="permissionTabCheckState(permission).checked"
:indeterminate="permissionTabCheckState(permission).indeterminate"
@change="(e) => handleGrantPermissions(permission, e)"
>{{ L('SelectAllInThisTab') }}</Checkbox
>
<Divider />
<BasicTree
:checkable="true"
:checkStrictly="true"
:clickRowToExpand="true"
:disabled="permissionTreeDisabled"
:treeData="permission.children"
:fieldNames="{
key: 'name',
title: 'displayName',
children: 'children',
}"
:value="permissionGrantKeys(permission)"
@check="
(selectKeys, event) => handlePermissionGranted(permission, selectKeys, event)
"
/>
</Card>
</TabPane>
</Tabs>
</Col>
</Row>
<Divider />
<BasicTree
:checkable="true"
:checkStrictly="true"
:clickRowToExpand="true"
:disabled="permissionTreeDisabled"
:treeData="permission.children"
:fieldNames="{
key: 'name',
title: 'displayName',
children: 'children',
}"
:value="permissionGrantKeys(permission)"
@check="(selectKeys, event) => handlePermissionGranted(permission, selectKeys, event)"
/>
</Card>
</TabPane>
</Tabs>
</BasicModal>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { Card, Checkbox, Col, Divider, Row, Tabs } from 'ant-design-vue';
import { Card, Checkbox, Divider, Tabs } from 'ant-design-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { useMessage } from '/@/hooks/web/useMessage';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { BasicModal, useModalInner } from '/@/components/Modal';
@ -78,6 +72,7 @@
identity: '',
};
const { prefixCls } = useDesign('permission-modal');
const { createMessage } = useMessage();
const { L } = useLocalization('AbpPermissionManagement');
const activeKey = ref('');
@ -125,3 +120,18 @@
});
}
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-permission-modal';
.@{prefix-cls} {
&__tabs {
height: 520px;
::v-deep(.ant-tabs-content-holder) {
overflow-y: auto !important;
overflow-x: hidden !important;
}
}
}
</style>

4
apps/vue/src/components/Permission/src/hooks/usePermissions.ts

@ -3,8 +3,8 @@ import type { PermissionProps, PermissionTree } from '../types/permission';
import { computed, watch, unref, ref } from 'vue';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { get, update } from '/@/api/permission-management/permission';
import { PermissionProvider } from '/@/api/permission-management/model/permissionModel';
import { get, update } from '/@/api/permission-management/permissions';
import { PermissionProvider } from '/@/api/permission-management/permissions/model';
import {
generatePermissionTree,
getGrantedPermissionKeys,

2
apps/vue/src/components/SettingManagement/src/SettingForm.vue

@ -115,7 +115,7 @@
} from 'ant-design-vue';
import { Input as BInput } from '/@/components/Input';
import { formatToDate } from '/@/utils/dateUtil';
import { SettingGroup, SettingsUpdate } from '/@/api/settings/model/settingModel';
import { SettingGroup, SettingsUpdate } from '/@/api/settings-management/settings/model';
const CollapsePanel = Collapse.Panel;
const FormItem = Form.Item;

28
apps/vue/src/hooks/abp/SimpleStateChecking/useRequireAuthenticatedSimpleStateChecker.ts

@ -0,0 +1,28 @@
import { useAbpStoreWithOut } from '/@/store/modules/abp';
export interface RequireAuthenticatedStateChecker {
name: string;
}
export class RequireAuthenticatedSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>> implements RequireAuthenticatedStateChecker, ISimpleStateChecker<TState> {
name = "A";
_currentUser: CurrentUser;
constructor(currentUser: CurrentUser) {
this._currentUser = currentUser;
}
isEnabled(_context: SimpleStateCheckerContext<TState>): boolean {
return this._currentUser.isAuthenticated;
}
serialize(): string {
return JSON.stringify({
"T": this.name,
});
}
}
export function useRequireAuthenticatedSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>>(): ISimpleStateChecker<TState> {
const abpStore = useAbpStoreWithOut();
const { currentUser } = abpStore.getApplication;
return new RequireAuthenticatedSimpleStateChecker<TState>(currentUser);
}

41
apps/vue/src/hooks/abp/SimpleStateChecking/useRequireFeaturesSimpleStateChecker.ts

@ -0,0 +1,41 @@
import { IFeatureChecker, useFeatures } from '/@/hooks/abp/useFeatures';
export interface RequireFeaturesStateChecker {
name: string;
requiresAll: boolean;
featureNames: string[];
}
export class RequireFeaturesSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>> implements RequireFeaturesStateChecker, ISimpleStateChecker<TState> {
name: string = 'F';
_featureChecker: IFeatureChecker;
featureNames: string[];
requiresAll: boolean;
constructor(
featureChecker: IFeatureChecker,
featureNames: string[],
requiresAll: boolean = false) {
this._featureChecker = featureChecker;
this.featureNames = featureNames;
this.requiresAll = requiresAll;
}
isEnabled(_context: SimpleStateCheckerContext<TState>): boolean {
return this._featureChecker.isEnabled(this.featureNames, this.requiresAll);
}
serialize(): string {
return JSON.stringify({
"T": this.name,
"A": this.requiresAll,
"N": this.featureNames,
});
}
}
export function useRequireFeaturesSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>>(
featureNames: string[],
requiresAll: boolean = false,
): ISimpleStateChecker<TState> {
const { featureChecker } = useFeatures();
return new RequireFeaturesSimpleStateChecker(featureChecker, featureNames, requiresAll);
}

41
apps/vue/src/hooks/abp/SimpleStateChecking/useRequireGlobalFeaturesSimpleStateChecker.ts

@ -0,0 +1,41 @@
import { IGlobalFeatureChecker, useGlobalFeatures } from '/@/hooks/abp/useGlobalFeatures';
export interface RequireGlobalFeaturesStateChecker {
name: string;
requiresAll: boolean;
featureNames: string[];
}
export class RequireGlobalFeaturesSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>> implements RequireGlobalFeaturesStateChecker, ISimpleStateChecker<TState> {
name: string = 'G';
_globalFeatureChecker: IGlobalFeatureChecker;
featureNames: string[];
requiresAll: boolean;
constructor(
globalFeatureChecker: IGlobalFeatureChecker,
featureNames: string[],
requiresAll: boolean = false) {
this._globalFeatureChecker = globalFeatureChecker;
this.featureNames = featureNames;
this.requiresAll = requiresAll;
}
isEnabled(_context: SimpleStateCheckerContext<TState>): boolean {
return this._globalFeatureChecker.isEnabled(this.featureNames, this.requiresAll);
}
serialize(): string {
return JSON.stringify({
"T": this.name,
"A": this.requiresAll,
"N": this.featureNames,
});
}
}
export function useRequireGlobalFeaturesSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>>(
featureNames: string[],
requiresAll: boolean = false,
): ISimpleStateChecker<TState> {
const globalFeatureChecker = useGlobalFeatures();
return new RequireGlobalFeaturesSimpleStateChecker(globalFeatureChecker, featureNames, requiresAll);
}

82
apps/vue/src/hooks/abp/SimpleStateChecking/useRequirePermissionsSimpleStateChecker.ts

@ -0,0 +1,82 @@
import { PermissionChecker, useAuthorization } from '/@/hooks/abp/useAuthorization';
export interface RequirePermissionsStateChecker<TState extends IHasSimpleStateCheckers<TState>> {
name: string;
model: RequirePermissionsSimpleBatchStateCheckerModel<TState>;
}
export class RequirePermissionsSimpleBatchStateCheckerModel<TState extends IHasSimpleStateCheckers<TState>> {
state: TState;
requiresAll: boolean;
permissions: string[];
constructor(
state: TState,
permissions: string[],
requiresAll: boolean = true,
) {
this.state = state;
this.permissions = permissions;
this.requiresAll = requiresAll;
}
}
export class RequirePermissionsSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>> implements RequirePermissionsStateChecker<TState>, ISimpleStateChecker<TState> {
name: string = 'P';
model: RequirePermissionsSimpleBatchStateCheckerModel<TState>;
_permissionChecker: PermissionChecker;
constructor(
permissionChecker: PermissionChecker,
model: RequirePermissionsSimpleBatchStateCheckerModel<TState>) {
this.model = model;
this._permissionChecker = permissionChecker;
}
isEnabled(_context: SimpleStateCheckerContext<TState>): boolean {
return this._permissionChecker.isGranted(this.model.permissions, this.model.requiresAll);
}
serialize(): string {
return JSON.stringify({
"T": this.name,
"A": this.model.requiresAll,
"N": this.model.permissions,
});
}
}
export class RequirePermissionsSimpleBatchStateChecker<TState extends IHasSimpleStateCheckers<TState>> implements ISimpleBatchStateChecker<TState> {
name: string = 'P';
models: RequirePermissionsSimpleBatchStateCheckerModel<TState>[];
_permissionChecker: PermissionChecker;
constructor(
permissionChecker: PermissionChecker,
models: RequirePermissionsSimpleBatchStateCheckerModel<TState>[]) {
this.models = models;
this._permissionChecker = permissionChecker;
}
isEnabled(context: SimpleBatchStateCheckerContext<TState>) {
const result: SimpleStateCheckerResult<TState> = {};
context.states.forEach((state) => {
const model = this.models.find(x => x.state === state);
if (model) {
result[model.state as TState] = this._permissionChecker.isGranted(model.permissions, model.requiresAll);
}
});
return result;
}
serialize(): string | undefined {
return undefined;
}
}
export function useRequirePermissionsSimpleStateChecker<TState extends IHasSimpleStateCheckers<TState>>(
model: RequirePermissionsSimpleBatchStateCheckerModel<TState>): ISimpleStateChecker<TState> {
const permissionChecker = useAuthorization();
return new RequirePermissionsSimpleStateChecker<TState>(permissionChecker, model);
}
export function useRequirePermissionsSimpleBatchStateChecker<TState extends IHasSimpleStateCheckers<TState>>(
models: RequirePermissionsSimpleBatchStateCheckerModel<TState>[]): ISimpleBatchStateChecker<TState> {
const permissionChecker = useAuthorization();
return new RequirePermissionsSimpleBatchStateChecker<TState>(permissionChecker, models);
}

11
apps/vue/src/hooks/abp/useAuthorization.ts

@ -1,8 +1,8 @@
import { computed } from 'vue';
import { useAbpStoreWithOut } from '/@/store/modules/abp';
interface PermissionChecker {
isGranted(name: string | string[]): boolean;
export interface PermissionChecker {
isGranted(name: string | string[], requiresAll?: boolean): boolean;
authorize(name: string | string[]): void;
}
@ -12,10 +12,13 @@ export function useAuthorization(): PermissionChecker {
return abpStore.getApplication.auth.grantedPolicies ?? {};
});
function isGranted(name: string | string[]): boolean {
function isGranted(name: string | string[], requiresAll?: boolean): boolean {
const grantedPolicies = getGrantedPolicies.value;
if (Array.isArray(name)) {
return name.every((name) => grantedPolicies[name]);
if (requiresAll === undefined || requiresAll === true) {
return name.every((name) => grantedPolicies[name]);
}
return name.some((name) => grantedPolicies[name]);
}
return grantedPolicies[name]
}

2
apps/vue/src/hooks/abp/useDefineSettings.ts

@ -1,5 +1,5 @@
import { computed, onMounted } from 'vue';
import { SettingGroup } from '/@/api/settings/model/settingModel';
import { SettingGroup } from '/@/api/settings-management/settings/model';
import { useSettingManagementStoreWithOut } from '/@/store/modules/settings';
import { useSettings as useAbpSettings, ISettingProvider } from '/@/hooks/abp/useSettings';

34
apps/vue/src/hooks/abp/useFeatures.ts

@ -6,12 +6,13 @@ type FeatureValue = NameValue<string>;
/**
*
*/
interface IFeatureChecker {
export interface IFeatureChecker {
/**
*
* @param name
* @param featureNames
* @param requiresAll
*/
isEnabled(name: string): boolean;
isEnabled(featureNames: string | string[], requiresAll?: boolean): boolean;
/**
*
* @param name
@ -37,13 +38,34 @@ export function useFeatures() {
return getFeatures.value.find((feature) => name === feature.name);
}
function _isEnabled(name: string): boolean {
var setting = get(name);
return setting?.value.toLowerCase() === 'true';
}
const featureChecker: IFeatureChecker = {
getOrEmpty(name: string) {
return get(name)?.value ?? '';
},
isEnabled(name: string) {
var setting = get(name);
return setting?.value.toLowerCase() === 'true';
isEnabled(featureNames: string | string[], requiresAll?: boolean) {
if (Array.isArray(featureNames)) {
if (featureNames.length === 0) return true;
if (requiresAll === undefined || requiresAll === true) {
for (let index = 0; index < featureNames.length; index++) {
if (!_isEnabled(featureNames[index])) return false;
}
return true;
}
for (let index = 0; index < featureNames.length; index++) {
if (_isEnabled(featureNames[index])) return true;
}
} else {
return _isEnabled(featureNames);
}
return false;
},
};

48
apps/vue/src/hooks/abp/useGlobalFeatures.ts

@ -0,0 +1,48 @@
import { computed } from 'vue';
import { useAbpStoreWithOut } from '/@/store/modules/abp';
import { isNullOrWhiteSpace } from '/@/utils/strings';
export interface IGlobalFeatureChecker {
isEnabled(featureNames: string | string[], requiresAll?: boolean): boolean;
}
export function useGlobalFeatures() {
const getGlobalFeatures = computed(() => {
const abpStore = useAbpStoreWithOut();
const enabledFeatures = abpStore.getApplication.globalFeatures.enabledFeatures ?? [];
return enabledFeatures;
});
function get(name: string): string | undefined {
return getGlobalFeatures.value.find((feature) => name === feature);
}
function _isEnabled(name: string): boolean {
var feature = get(name);
return !isNullOrWhiteSpace(feature);
}
function isEnabled(featureNames: string | string[], requiresAll?: boolean): boolean {
if (Array.isArray(featureNames)) {
if (featureNames.length === 0) return true;
if (requiresAll === undefined || requiresAll === true) {
for (let index = 0; index < featureNames.length; index++) {
if (!_isEnabled(featureNames[index])) return false;
}
return true;
}
for (let index = 0; index < featureNames.length; index++) {
if (_isEnabled(featureNames[index])) return true;
}
} else {
return _isEnabled(featureNames);
}
return false;
}
return {
isEnabled,
};
}

24
apps/vue/src/hooks/abp/useLocalization.ts

@ -1,10 +1,11 @@
import { computed } from 'vue';
import { merge } from 'lodash-es';
import { useAbpStoreWithOut } from '/@/store/modules/abp';
import { format } from '/@/utils/strings';
import { useAbpStoreWithOut } from '/@/store/modules/abp';
interface IStringLocalizer {
L(key: string, args?: Recordable | any[] | undefined): string;
Lr(resource: string, key: string, args?: Recordable | any[] | undefined): string;
}
export function useLocalization(resourceNames?: string | string[]) {
@ -29,6 +30,16 @@ export function useLocalization(resourceNames?: string | string[]) {
return resource;
});
const getResourceByName = computed(() => {
return (resource: string): Dictionary<string, string> => {
const abpStore = useAbpStoreWithOut();
const { values } = abpStore.getApplication.localization;
if (Reflect.has(values, resource)) {
return values[resource];
}
return {};
};
});
function L(key: string, args?: Recordable | any[] | undefined) {
if (!key) return '';
@ -37,9 +48,18 @@ export function useLocalization(resourceNames?: string | string[]) {
return format(getResource.value[key], args ?? []);
}
function Lr(resource: string, key: string, args?: Recordable | any[] | undefined) {
if (!key) return '';
const findResource = getResourceByName.value(resource);
if (!findResource) return key;
if (!Reflect.has(findResource, key)) return key;
return format(findResource[key], args ?? []);
}
const localizer: IStringLocalizer = {
L: L,
Lr: Lr,
};
return { L, localizer };
return { L, Lr, localizer };
}

101
apps/vue/src/hooks/abp/useLocalizationSerializer.ts

@ -0,0 +1,101 @@
import { isNullOrWhiteSpace } from "/@/utils/strings";
interface ValidateOptions {
required?: boolean;
}
interface ILocalizableStringSerializer {
serialize(value?: LocalizableStringInfo): string;
deserialize(value?: string): LocalizableStringInfo;
validate(value?: string, opt?: ValidateOptions): boolean;
}
export function useLocalizationSerializer(): ILocalizableStringSerializer {
function Validate(value?: string, opt?: ValidateOptions): boolean {
if (!value || isNullOrWhiteSpace(value)) {
if (!opt || (opt.required === undefined || opt.required === true)) {
return false;
}
return true;
}
if (value.length < 3 || value[1] !== ':') {
return false;
}
const type = value[0];
switch (type) {
case 'F':
return !isNullOrWhiteSpace(value.substring(2).trim());
case 'L':
const commaPosition = value.indexOf(',', 2);
if (commaPosition == -1) {
return false;
}
const name = value.substring(commaPosition + 1);
if (isNullOrWhiteSpace(name)) {
return false;
}
return true;
default:
return false;
}
}
function Serialize(value?: LocalizableStringInfo): string {
if (!value) return '';
return `L:${value.resourceName},${value.name}`;
}
function Deserialize(value?: string): LocalizableStringInfo {
if (!value || isNullOrWhiteSpace(value)) {
return {
resourceName: '',
name: '',
};
}
if (value.length < 2 || value[1] !== ':') {
return {
resourceName: '',
name: value,
};
}
const type = value[0];
switch (type) {
case 'F':
return {
resourceName: 'Fixed',
name: value.substring(2),
};
case 'L':
const commaPosition = value.indexOf(',', 2);
if (commaPosition == -1) {
return {
resourceName: 'Default',
name: value,
};
}
const resourceName = value.substring(2, commaPosition);
const name = value.substring(commaPosition + 1);
if (isNullOrWhiteSpace(resourceName)) {
return {
resourceName: 'Default',
name: value,
};
}
return {
resourceName: resourceName,
name: name,
};
default:
return {
resourceName: 'Default',
name: value,
};
}
}
return {
validate: Validate,
serialize: Serialize,
deserialize: Deserialize,
}
}

100
apps/vue/src/hooks/abp/useSimpleStateCheck.ts

@ -0,0 +1,100 @@
import {
useRequireAuthenticatedSimpleStateChecker
} from "./SimpleStateChecking/useRequireAuthenticatedSimpleStateChecker";
import {
useRequirePermissionsSimpleStateChecker
} from "./SimpleStateChecking/useRequirePermissionsSimpleStateChecker";
import {
useRequireFeaturesSimpleStateChecker
} from "./SimpleStateChecking/useRequireFeaturesSimpleStateChecker";
import {
useRequireGlobalFeaturesSimpleStateChecker
} from "./SimpleStateChecking/useRequireGlobalFeaturesSimpleStateChecker";
import { isNullOrUnDef } from "/@/utils/is";
import { isNullOrWhiteSpace } from "/@/utils/strings";
class SimpleStateCheckerSerializer implements ISimpleStateCheckerSerializer {
serialize<TState extends IHasSimpleStateCheckers<TState>>(checker: ISimpleStateChecker<TState>): string | undefined {
return checker.serialize();
}
serializeArray<TState extends IHasSimpleStateCheckers<TState>>(stateCheckers: ISimpleStateChecker<TState>[]): string | undefined {
if (stateCheckers.length === 0) return undefined;
if (stateCheckers.length === 1) {
const single = stateCheckers[0].serialize();
if (isNullOrUnDef(single)) return undefined;
return `[${single}]`;
}
let serializedCheckers: string = '';
stateCheckers.forEach((checker) => {
const serializedChecker = checker.serialize();
if (!isNullOrUnDef(serializedChecker)) {
serializedCheckers += serializedChecker + ',';
}
});
if (serializedCheckers.endsWith(',')) {
serializedCheckers = serializedCheckers.substring(0, serializedCheckers.length - 1);
}
return serializedCheckers.length > 0 ? `[${serializedCheckers}]` : undefined;
}
deserialize<TState extends IHasSimpleStateCheckers<TState>>(jsonObject: any, state: TState): ISimpleStateChecker<TState> | undefined {
if (isNullOrUnDef(jsonObject) || !Reflect.has(jsonObject, 'T')) {
return undefined;
}
switch (String(jsonObject['T'])) {
case 'A':
return useRequireAuthenticatedSimpleStateChecker();
case 'P':
const permissions = jsonObject['N'] as string[];
if (permissions === undefined) {
throw Error(("'N' is not an array in the serialized state checker! JsonObject: " + jsonObject));
}
return useRequirePermissionsSimpleStateChecker({
permissions: permissions,
requiresAll: jsonObject['A'] === true,
state,
});
case 'F':
const features = jsonObject['N'] as string[];
if (features === undefined) {
throw Error(("'N' is not an array in the serialized state checker! JsonObject: " + jsonObject));
}
return useRequireFeaturesSimpleStateChecker(
features,
jsonObject['A'] === true,
);
case 'G':
const globalFeatures = jsonObject['N'] as string[];
if (globalFeatures === undefined) {
throw Error(("'N' is not an array in the serialized state checker! JsonObject: " + jsonObject));
}
return useRequireGlobalFeaturesSimpleStateChecker(
globalFeatures,
jsonObject['A'] === true,
);
default: return undefined;
}
}
deserializeArray<TState extends IHasSimpleStateCheckers<TState>>(value: string, state: TState): ISimpleStateChecker<TState>[] {
if (isNullOrWhiteSpace(value)) return [];
const jsonObject = JSON.parse(value);
if (isNullOrUnDef(jsonObject)) return [];
if (Array.isArray(jsonObject)) {
if (jsonObject.length === 0) return [];
return jsonObject.map((json) => this.deserialize(json, state))
.filter(checker => !isNullOrUnDef(checker))
.map(checker => checker!);
}
const stateChecker = this.deserialize(jsonObject, state);
if (!stateChecker) return [];
return [stateChecker];
}
}
export function useSimpleStateCheck<TState extends IHasSimpleStateCheckers<TState>>() {
return {
serializer: new SimpleStateCheckerSerializer(),
};
}

121
apps/vue/src/locales/lang/en/component.ts

@ -150,10 +150,127 @@ export default {
reUploadFailed: 'Re-upload failed files',
},
verify: {
error: 'verification failed',
time: 'The verification is successful and it takes {time} seconds',
error: 'verification failed!',
time: 'The verification is successful and it takes {time} seconds!',
redoTip: 'Click the picture to refresh',
dragText: 'Hold down the slider and drag',
successText: 'Verified',
},
localizable_input: {
placeholder: 'Select localized resources',
resources: {
fiexed: {
group: 'Define',
label: 'Fiexed',
placeholder: 'Please enter custom content',
},
localization: {
group: 'Localization',
placeholder: 'Please select a name',
}
}
},
extra_property_dictionary: {
title: 'Extra properties',
key: 'Key',
value: 'Value',
actions: {
title: 'Actions',
create: 'Add',
update: 'Edit',
delete: 'Delete',
clean: 'Clean',
},
validator: {
duplicateKey: 'A key of the same name has been added',
},
},
value_type_nput: {
type: {
name: 'Type',
FREE_TEXT: {
name: 'Free Text',
},
TOGGLE: {
name: 'Toggle',
},
SELECTION: {
name: 'Selection',
displayText: 'Display Text',
displayTextNotBeEmpty: 'Display text cannot be empty',
value: 'value',
duplicateKeyOrValue: 'The name or value of the selection is not allowed to be repeated',
itemsNotBeEmpty: 'Selectable items cannot be empty',
itemsNotFound: 'The selection is not included in the optional list',
actions: {
title: 'Actions',
create: 'Add',
update: 'Edit',
delete: 'Delete',
clean: 'Clean',
},
modal: {
title: 'Selection',
},
},
},
validator: {
name: 'Validator',
isInvalidValue: 'The value failed to pass {0}; check the validator options.',
NULL: {
name: 'None',
},
BOOLEAN: {
name: 'Boolean',
},
NUMERIC: {
name: 'Numeric',
minValue: 'Min Value',
maxValue: 'Max Value',
},
STRING: {
name: 'String',
allowNull: 'Allow Null',
minLength: 'Min Length',
maxLength: 'Max Length',
regularExpression: 'Regular Expression',
},
},
},
simple_state_checking: {
title: 'State checking',
actions: {
create: 'Add',
update: 'Edit',
delete: 'Delete',
clean: 'Clean',
},
table: {
name: 'Name',
properties: 'Properties',
actions: 'Actions',
},
form: {
name: 'State checking',
},
requireAuthenticated: {
title: 'Require Authenticated',
},
requireFeatures: {
title: 'Require Features',
requiresAll: 'Requires All',
requiresAllDesc: 'If checked, all selected features need to be enabled.',
featureNames: 'Required features',
},
requireGlobalFeatures: {
title: 'Require Global Features',
featureNames: 'Required Global Features',
},
requirePermissions: {
title: 'Require Permissions',
requiresAll: 'Requires All',
requiresAllDesc: 'If checked, you need to have all the selected permissions.',
permissions: 'Required Permissions',
},
}
};

117
apps/vue/src/locales/lang/zh-CN/component.ts

@ -159,4 +159,121 @@ export default {
dragText: '请按住滑块拖动',
successText: '验证通过',
},
localizable_input: {
placeholder: '请选择本地化资源',
resources: {
fiexed: {
group: '自定义',
label: '固定内容',
placeholder: '请输入自定义内容',
},
localization: {
group: '本地化',
placeholder: '请选择名称',
}
}
},
extra_property_dictionary: {
title: '扩展属性',
key: '名称',
value: '键值',
actions: {
title: '操作',
create: '新增',
update: '编辑',
delete: '删除',
clean: '清除'
},
validator: {
duplicateKey: '已经添加了一个相同名称的键',
},
},
value_type_nput: {
type: {
name: '类型',
FREE_TEXT: {
name: '自由文本',
},
TOGGLE: {
name: '切换',
},
SELECTION: {
name: '选择',
displayText: '显示名称',
displayTextNotBeEmpty: '显示名称不可为空',
value: '选择项',
duplicateKeyOrValue: '选择项的名称或值不允许重复',
itemsNotBeEmpty: '可选择项列表不能为空',
itemsNotFound: '选择项不包含在可选列表中',
actions: {
title: '操作',
create: '新增',
update: '编辑',
delete: '删除',
clean: '清除',
},
modal: {
title: '选择项',
},
},
},
validator: {
name: '验证器',
isInvalidValue: '值未能通过 {0} 校验, 请检查验证器选项.',
NULL: {
name: '未定义',
},
BOOLEAN: {
name: '布尔类型',
},
NUMERIC: {
name: '数值类型',
minValue: '最小值',
maxValue: '最大值',
},
STRING: {
name: '字符类型',
allowNull: '允许空值',
minLength: '最小长度',
maxLength: '最大长度',
regularExpression: '正则表达式',
},
},
},
simple_state_checking: {
title: '状态检查',
actions: {
create: '新增',
update: '编辑',
delete: '删除',
clean: '清除'
},
table: {
name: '名称',
properties: '属性',
actions: '操作',
},
form: {
name: '状态检查器',
},
requireAuthenticated: {
title: '需要用户认证',
},
requireFeatures: {
title: '检查所需功能',
requiresAll: '要求所有',
requiresAllDesc: '如果勾选,则需要启用所有选择的功能.',
featureNames: '需要的功能',
},
requireGlobalFeatures: {
title: '检查全局功能',
featureNames: '需要的全局功能',
},
requirePermissions: {
title: '检查所需权限',
requiresAll: '要求所有',
requiresAllDesc: '如果勾选,则需要拥有所有选择的权限.',
permissions: '需要的权限',
},
}
};

2
apps/vue/src/store/modules/settings.ts

@ -1,7 +1,7 @@
import { defineStore } from 'pinia';
import { store } from '/@/store';
import { createLocalStorage } from '/@/utils/cache';
import { SettingGroup } from '/@/api/settings/model/settingModel';
import { SettingGroup } from '/@/api/settings-management/settings/model';
const ls = createLocalStorage();
const SETTING_ID = 'setting-management';

23
apps/vue/src/utils/table.ts

@ -0,0 +1,23 @@
import { isDate, isNumber } from "./is";
export function sorter(a: Recordable<any>, b: Recordable<any>, field: string) : number {
if (!a[field] && !b[field]) {
return 0;
}
if (a[field] && !b[field]) {
return 1;
}
if (b[field] && !a[field]) {
return -1;
}
if (isDate(a[field])) {
return a[field] - b[field];
}
if (isNumber(a[field])) {
return a[field] - b[field];
}
if (Array.isArray(a[field])) {
return a[field].length - b[field].length;
}
return String(a[field]).localeCompare(String(b[field]));
}

4
apps/vue/src/views/account/center/Setting.vue

@ -6,8 +6,8 @@
import { useAbpStoreWithOut } from '/@/store/modules/abp';
import { SettingForm } from '/@/components/SettingManagement';
import { SettingGroup } from '/@/api/settings/model/settingModel';
import { getCurrentUserSettings, setCurrentUserSettings } from '/@/api/settings/settings';
import { SettingGroup } from '/@/api/settings-management/settings/model';
import { getCurrentUserSettings, setCurrentUserSettings } from '/@/api/settings-management/settings';
interface ISettingForm {
providerName: string;

433
apps/vue/src/views/feature-management/definitions/features/components/FeatureDefinitionModal.vue

@ -0,0 +1,433 @@
<template>
<BasicModal
@register="registerModal"
:title="L('FeatureDefinitions')"
:can-fullscreen="false"
:width="800"
:height="500"
:close-func="handleBeforeClose"
@ok="handleSubmit"
>
<Form
ref="formRef"
:model="state.entity"
:rules="state.entityRules"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 18 }"
>
<Tabs v-model:active-key="state.activeTab">
<TabPane key="basic" :tab="L('BasicInfo')">
<FormItem name="groupName" :label="L('DisplayName:GroupName')">
<Select
:disabled="!state.allowedChange"
:allow-clear="true"
v-model:value="state.entity.groupName"
:options="getGroupOptions"
@change="handleGroupChange"
/>
</FormItem>
<FormItem name="parentName" :label="L('DisplayName:ParentName')">
<TreeSelect
:disabled="!state.allowedChange"
:allow-clear="true"
:tree-data="state.availableFeatures"
v-model:value="state.entity.parentName"
:field-names="{
label: 'displayName',
value: 'name',
children: 'children',
}"
@change="handleParentChange"
/>
</FormItem>
<FormItem name="name" :label="L('DisplayName:Name')">
<Input :disabled="state.entityEditFlag && !state.allowedChange" :allow-clear="true" v-model:value="state.entity.name" />
</FormItem>
<FormItem name="displayName" :label="L('DisplayName:DisplayName')">
<LocalizableInput :disabled="!state.allowedChange" :allow-clear="true" v-model:value="state.entity.displayName" />
</FormItem>
<FormItem name="description" :label="L('DisplayName:Description')">
<LocalizableInput :disabled="!state.allowedChange" :allow-clear="true" v-model:value="state.entity.description" />
</FormItem>
<FormItem name="defaultValue" :label="L('DisplayName:DefaultValue')">
<Select
v-if="valueTypeNameRef === 'SelectionStringValueType'"
:disabled="!state.allowedChange"
:allow-clear="true"
v-model:value="state.entity.defaultValue"
:options="state.selectionDataSource"
/>
<TextArea
v-else-if="valueTypeNameRef === 'FreeTextStringValueType' && (validatorNameRef === 'NULL' || validatorNameRef === 'STRING')"
:disabled="state.entityEditFlag && !state.allowedChange"
:allow-clear="true"
:auto-size="{ minRows: 3 }"
v-model:value="state.entity.defaultValue"
/>
<InputNumber
v-else-if="valueTypeNameRef === 'FreeTextStringValueType' && validatorNameRef === 'NUMERIC'"
style="width: 100%;"
:disabled="state.entityEditFlag && !state.allowedChange"
v-model:value="state.entity.defaultValue"
/>
<Checkbox
v-else-if="valueTypeNameRef === 'ToggleStringValueType' && validatorNameRef === 'BOOLEAN'"
:disabled="state.entityEditFlag && !state.allowedChange"
:checked="state.entity.defaultValue === 'true'"
@change="(e) => state.entity.defaultValue = String(e.target.checked).toLowerCase()"
>
{{ L('DisplayName:DefaultValue') }}
</Checkbox>
</FormItem>
<FormItem name="IsVisibleToClients" :label="L('DisplayName:IsVisibleToClients')" :extra="L('Description:IsVisibleToClients')">
<Checkbox
:disabled="!state.allowedChange"
v-model:checked="state.entity.isVisibleToClients"
>{{ L('DisplayName:IsVisibleToClients') }}
</Checkbox>
</FormItem>
<FormItem name="IsAvailableToHost" :label="L('DisplayName:IsAvailableToHost')" :extra="L('Description:IsAvailableToHost')">
<Checkbox
:disabled="!state.allowedChange"
v-model:checked="state.entity.isAvailableToHost"
>{{ L('DisplayName:IsAvailableToHost') }}
</Checkbox>
</FormItem>
</TabPane>
<TabPane key="valueType" :tab="L('ValueValidator')" force-render>
<FormItem name="valueType" label="" :label-col="{ span: 0 }" :wrapper-col="{ span: 24 }">
<StringValueTypeInput
ref="valueTypeRef"
:disabled="!state.allowedChange"
:allow-delete="true"
:allow-edit="true"
v-model:value="state.entity.valueType"
@change:value-type="handleValueTypeNameChange"
@change:validator="handleValidatorNameChange"
@change:selection="handleSelectionChange"
/>
</FormItem>
</TabPane>
<TabPane key="propertites" :tab="L('Properties')" force-render>
<FormItem name="extraProperties" label="" :label-col="{ span: 0 }" :wrapper-col="{ span: 24 }">
<ExtraPropertyDictionary :disabled="!state.allowedChange" :allow-delete="true" :allow-edit="true" v-model:value="state.entity.extraProperties" />
</FormItem>
</TabPane>
</Tabs>
</Form>
</BasicModal>
</template>
<script setup lang="ts">
import type { Rule } from 'ant-design-vue/lib/form';
import { cloneDeep } from 'lodash-es';
import { computed, ref, reactive, unref, nextTick, watch } from 'vue';
import { Checkbox, Form, Input, InputNumber, Select, Tabs, TreeSelect } from 'ant-design-vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { LocalizableInput, ExtraPropertyDictionary, StringValueTypeInput } from '/@/components/Abp';
import { FreeTextStringValueType, SelectionStringValueItem, valueTypeSerializer } from '/@/components/Abp/StringValueType/valueType';
import { useMessage } from '/@/hooks/web/useMessage';
import { ValidationEnum, useValidation } from '/@/hooks/abp/useValidation';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { useLocalizationSerializer } from '/@/hooks/abp/useLocalizationSerializer';
import {
GetAsyncByName,
CreateAsyncByInput,
UpdateAsyncByNameAndInput
} from '/@/api/feature-management/definitions/features';
import {
FeatureDefinitionUpdateDto,
FeatureDefinitionCreateDto
} from '/@/api/feature-management/definitions/features/model';
import { GetListAsyncByInput } from '/@/api/feature-management/definitions/features';
import { FeatureGroupDefinitionDto } from '/@/api/feature-management/definitions/groups/model';
import { listToTree } from '/@/utils/helper/treeHelper';
import { isBoolean, isNullOrUnDef } from '/@/utils/is';
import { groupBy } from '/@/utils/array';
const FormItem = Form.Item;
const TabPane = Tabs.TabPane;
const TextArea = Input.TextArea;
interface FeatureSelectData {
label: string;
value: string;
}
interface FeatureTreeData {
name: string;
groupName: string;
displayName: string;
children: FeatureTreeData[];
}
interface State {
activeTab: string,
allowedChange: boolean,
entity: Recordable,
entityRules?: Dictionary<string, Rule>,
entityChanged: boolean,
entityEditFlag: boolean,
defaultGroup?: string,
availableFeatures: FeatureTreeData[],
availableGroups: FeatureGroupDefinitionDto[],
selectionDataSource: FeatureSelectData[],
}
const emits = defineEmits(['register', 'change']);
const { ruleCreator } = useValidation();
const { deserialize, validate } = useLocalizationSerializer();
const { createConfirm, createMessage } = useMessage();
const { L, Lr } = useLocalization(['AbpFeatureManagement', 'AbpUi']);
const formRef = ref<any>();
const valueTypeRef = ref<any>();
const validatorNameRef = ref<string>('NULL');
const valueTypeNameRef = ref<string>('FreeTextStringValueType');
const state = reactive<State>({
activeTab: 'basic',
entity: {},
allowedChange: false,
entityChanged: false,
entityEditFlag: false,
availableFeatures: [],
availableGroups: [],
selectionDataSource: [],
entityRules: {
groupName: ruleCreator.fieldRequired({
name: 'GroupName',
prefix: 'DisplayName',
resourceName: 'WebhooksManagement',
trigger: 'blur',
}),
name: ruleCreator.fieldRequired({
name: 'Name',
prefix: 'DisplayName',
resourceName: 'WebhooksManagement',
trigger: 'blur',
}),
displayName: ruleCreator.defineValidator({
required: true,
trigger: 'blur',
validator(_rule, value) {
if (!validate(value)) {
return Promise.reject(L(ValidationEnum.FieldRequired, [L('DisplayName:DisplayName')]));
}
return Promise.resolve();
},
}),
description: ruleCreator.defineValidator({
trigger: 'blur',
validator(_rule, value) {
if (!validate(value, { required: false })) {
return Promise.reject(L(ValidationEnum.FieldRequired, [L('DisplayName:Description')]));
}
return Promise.resolve();
},
}),
defaultValue: ruleCreator.defineValidator({
trigger: 'change',
validator(_rule, value) {
const valueType = unref(valueTypeRef);
if (valueType) {
return valueType.validate(value);
}
return Promise.resolve();
},
}),
},
});
const getGroupOptions = computed(() => {
return state.availableGroups.map((group) => {
const info = deserialize(group.displayName);
return {
label: Lr(info.resourceName, info.name),
value: group.name,
};
});
});
watch(
() => state.entity,
() => {
state.entityChanged = true;
},
{
deep: true,
},
);
const [registerModal, { closeModal, changeLoading, changeOkLoading }] = useModalInner((data) => {
state.defaultGroup = data.groupName;
state.availableGroups = data.groups;
nextTick(() => {
fetchFeatures(state.defaultGroup);
fetch(data.record?.name);
});
});
function fetch(name?: string) {
state.activeTab = 'basic';
state.entityEditFlag = false;
if (!name) {
state.entity = {
isVisibleToClients: true,
isAvailableToHost: true,
groupName: state.defaultGroup,
requiredFeatures: [],
valueType: valueTypeSerializer.serialize(new FreeTextStringValueType())
};
state.allowedChange = true;
nextTick(() => state.entityChanged = false);
return;
}
changeLoading(true);
changeOkLoading(true);
GetAsyncByName(name).then((record) => {
state.entity = record;
state.entityEditFlag = true;
state.allowedChange = !record.isStatic;
}).finally(() => {
changeLoading(false);
changeOkLoading(false);
nextTick(() => state.entityChanged = false);
});
}
function fetchFeatures(groupName?: string) {
GetListAsyncByInput({
groupName: groupName,
}).then((res) => {
const featureGroup = groupBy(res.items, 'groupName');
const featureTreeData: FeatureTreeData[] = [];
Object.keys(featureGroup).forEach((gk) => {
const featureTree = listToTree(featureGroup[gk], {
id: 'name',
pid: 'parentName',
});
formatDisplayName(featureTree);
featureTreeData.push(...featureTree);
});
state.availableFeatures = featureTreeData;
});
}
function formatDisplayName(list: any[]) {
if (list && Array.isArray(list)) {
list.forEach((item) => {
if (Reflect.has(item, 'displayName')) {
const info = deserialize(item.displayName);
item.displayName = Lr(info.resourceName, info.name);
}
if (Reflect.has(item, 'children')) {
formatDisplayName(item.children);
}
});
}
}
function handleGroupChange(value?: string) {
state.entity.parentName = undefined;
fetchFeatures(value);
}
function handleParentChange(value?: string) {
if (!value) return;
GetAsyncByName(value).then((res) => {
state.entity.groupName = res.groupName;
const form = unref(formRef);
form?.clearValidate(['groupName']);
});
}
function handleValueTypeNameChange(valueTypeName: string) {
valueTypeNameRef.value = valueTypeName;
switch (valueTypeName) {
case 'ToggleStringValueType':
if (!isBoolean(state.entity.defaultValue)) {
state.entity.defaultValue = false;
}
break;
default:
if (isBoolean(state.entity.defaultValue)) {
state.entity.defaultValue = undefined;
}
break;
}
}
function handleValidatorNameChange(validatorName: string) {
validatorNameRef.value = validatorName;
}
function handleSelectionChange(items: SelectionStringValueItem[]) {
if (items.length === 0) {
state.entity.defaultValue = undefined;
state.selectionDataSource = [];
return;
}
state.selectionDataSource = items.map((item) => {
return {
label: Lr(item.displayText.resourceName, item.displayText.name),
value: item.value,
};
});
if (!items.find(item => item.value === state.entity.defaultValue)) {
state.entity.defaultValue = undefined;
}
}
function handleBeforeClose(): Promise<boolean> {
return new Promise((resolve) => {
if (!state.entityChanged) {
const form = unref(formRef);
form?.resetFields();
return resolve(true);
}
createConfirm({
iconType: 'warning',
title: L('AreYouSure'),
content: L('AreYouSureYouWantToCancelEditingWarningMessage'),
onOk: () => {
const form = unref(formRef);
form?.resetFields();
state.allowedChange = false;
resolve(true);
},
onCancel: () => {
resolve(false);
},
afterClose: () => {
state.defaultGroup = undefined;
},
});
});
}
function handleSubmit() {
if (!state.allowedChange) {
closeModal();
return;
}
const form = unref(formRef);
form?.validate().then(() => {
changeOkLoading(true);
const input = cloneDeep(state.entity);
if (!isNullOrUnDef(input.defaultValue)) {
input.defaultValue = String(input.defaultValue);
}
const api = state.entityEditFlag
? UpdateAsyncByNameAndInput(state.entity.name, input as FeatureDefinitionUpdateDto)
: CreateAsyncByInput(input as FeatureDefinitionCreateDto);
api.then((res) => {
createMessage.success(L('Successful'));
emits('change', res);
form.resetFields();
closeModal();
}).finally(() => {
changeOkLoading(false);
})
});
}
</script>
<style scoped>
</style>

268
apps/vue/src/views/feature-management/definitions/features/components/FeatureDefinitionTable.vue

@ -0,0 +1,268 @@
<template>
<div>
<BasicTable @register="registerTable">
<template #form-groupName="{ model, field }">
<FormItem name="groupName">
<Select v-model:value="model[field]" :options="getGroupOptions" />
</FormItem>
</template>
<template #toolbar>
<Button
v-auth="['FeatureManagement.Definitions.Create']"
type="primary"
@click="handleAddNew"
>
{{ L('FeatureDefinitions:AddNew') }}
</Button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'displayName'">
<span>{{ getGroupDisplayName(record.displayName) }}</span>
</template>
</template>
<template #expandedRowRender="{ record }">
<BasicTable @register="registerSubTable" :data-source="record.features">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'groupName'">
<span>{{ getGroupDisplayName(record.groupName) }}</span>
</template>
<template v-else-if="column.key === 'parentName'">
<span>{{ getDisplayName(record.parentName) }}</span>
</template>
<template v-else-if="column.key === 'displayName'">
<span>{{ getDisplayName(record.displayName) }}</span>
</template>
<template v-else-if="column.key === 'description'">
<span>{{ getDisplayName(record.description) }}</span>
</template>
<template v-else-if="column.key === 'isVisibleToClients'">
<CheckOutlined v-if="record.isVisibleToClients" class="enable" />
<CloseOutlined v-else class="disable" />
</template>
<template v-else-if="column.key === 'isAvailableToHost'">
<CheckOutlined v-if="record.isAvailableToHost" class="enable" />
<CloseOutlined v-else class="disable" />
</template>
<template v-else-if="column.key === 'isStatic'">
<CheckOutlined v-if="record.isStatic" class="enable" />
<CloseOutlined v-else class="disable" />
</template>
<template v-else-if="column.key === 'action'">
<TableAction
:stop-button-propagation="true"
:actions="[
{
auth: 'FeatureManagement.Definitions.Update',
label: L('Edit'),
icon: 'ant-design:edit-outlined',
onClick: handleEdit.bind(null, record),
},
{
auth: 'FeatureManagement.Definitions.Delete',
label: L('Delete'),
color: 'error',
icon: 'ant-design:delete-outlined',
ifShow: !record.isStatic,
onClick: handleDelete.bind(null, record),
},
]"
/>
</template>
</template>
</BasicTable>
</template>
</BasicTable>
<FeatureDefinitionModal @register="registerModal" @change="fetch" />
</div>
</template>
<script lang="ts" setup>
import { cloneDeep } from 'lodash-es';
import { computed, reactive, onMounted } from 'vue';
import { Button, Form, Select } from 'ant-design-vue';
import { CheckOutlined, CloseOutlined } from '@ant-design/icons-vue';
import { BasicTable, TableAction, useTable } from '/@/components/Table';
import { getDataColumns } from '../datas/TableData';
import { useModal } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { useLocalizationSerializer } from '/@/hooks/abp/useLocalizationSerializer';
import { GetListAsyncByInput as getGroupDefinitions } from '/@/api/feature-management/definitions/groups';
import { FeatureGroupDefinitionDto } from '/@/api/feature-management/definitions/groups/model';
import { GetListAsyncByInput, DeleteAsyncByName } from '/@/api/feature-management/definitions/features';
import { getSearchFormSchemas } from '../datas/ModalData';
import { listToTree } from '/@/utils/helper/treeHelper';
import { groupBy } from '/@/utils/array';
import { sorter } from '/@/utils/table';
import FeatureDefinitionModal from './FeatureDefinitionModal.vue';
const FormItem = Form.Item;
interface Feature {
name: string;
groupName: string;
displayName: string;
parentName?: string;
description?: string;
defaultValue?: string;
valueType: string;
isStatic: boolean;
isVisibleToClients: boolean;
isAvailableToHost: boolean;
allowedProviders: string[];
children: Feature[];
}
interface FeatureGroup {
name: string;
displayName: string;
features: Feature[];
}
interface State {
groups: FeatureGroupDefinitionDto[];
}
const { deserialize } = useLocalizationSerializer();
const { L, Lr } = useLocalization(['AbpFeatureManagement', 'AbpUi']);
const { createConfirm, createMessage } = useMessage();
const [registerModal, { openModal }] = useModal();
const [registerTable, { setLoading, getForm, setTableData }] = useTable({
rowKey: 'name',
title: L('FeatureDefinitions'),
columns: [
{
title: L('DisplayName:DisplayName'),
dataIndex: 'displayName',
align: 'left',
width: 180,
resizable: true,
sorter: (a, b) => sorter(a, b, 'displayName'),
},
],
pagination: true,
striped: false,
useSearchForm: true,
showIndexColumn: false,
showTableSetting: true,
tableSetting: {
redo: false,
},
formConfig: {
labelWidth: 100,
submitFunc: fetch,
schemas: getSearchFormSchemas(),
},
bordered: true,
canResize: true,
immediate: false,
});
const [registerSubTable] = useTable({
rowKey: 'name',
columns: getDataColumns(),
pagination: false,
striped: false,
useSearchForm: false,
showIndexColumn: true,
immediate: false,
scroll: { x: 2000, y: 900 },
actionColumn: {
width: 150,
title: L('Actions'),
dataIndex: 'action',
},
});
const state = reactive<State>({
groups: [],
});
const getGroupOptions = computed(() => {
return state.groups.map((group) => {
const info = deserialize(group.displayName);
return {
label: Lr(info.resourceName, info.name),
value: group.name,
};
});
});
const getGroupDisplayName = computed(() => {
return (groupName: string) => {
const group = state.groups.find(x => x.name === groupName);
if (!group) return groupName;
const info = deserialize(group.displayName);
return Lr(info.resourceName, info.name);
};
});
const getDisplayName = computed(() => {
return (displayName?: string) => {
if (!displayName) return displayName;
const info = deserialize(displayName);
return Lr(info.resourceName, info.name);
};
});
onMounted(() => {
fetch();
fetchGroups();
});
function fetch() {
const form = getForm();
return form.validate().then(() => {
setLoading(true);
setTableData([]);
var input = form.getFieldsValue();
GetListAsyncByInput(input).then((res) => {
const featureGroup = groupBy(res.items, 'groupName');
const featureGroupData: FeatureGroup[] = [];
Object.keys(featureGroup).forEach((gk) => {
const groupData: FeatureGroup = {
name: gk,
displayName: gk,
features: [],
};
const featureTree = listToTree(featureGroup[gk], {
id: 'name',
pid: 'parentName',
});
featureTree.forEach((tk) => {
groupData.features.push(tk);
});
featureGroupData.push(groupData);
});
setTableData(featureGroupData);
}).finally(() => {
setLoading(false);
});
});
}
function fetchGroups() {
getGroupDefinitions({}).then((res) => {
state.groups = res.items;
});
}
function handleAddNew() {
openModal(true, {
groups: cloneDeep(state.groups),
});
}
function handleEdit(record) {
openModal(true, {
record: record,
groups: cloneDeep(state.groups),
});
}
function handleDelete(record) {
createConfirm({
iconType: 'warning',
title: L('AreYouSure'),
content: L('ItemWillBeDeleteOrRestoreMessage'),
onOk: () => {
return DeleteAsyncByName(record.name).then(() => {
createMessage.success(L('Successful'));
fetch();
});
},
});
}
</script>

22
apps/vue/src/views/feature-management/definitions/features/datas/ModalData.ts

@ -0,0 +1,22 @@
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { FormSchema } from '/@/components/Form';
const { L } = useLocalization(['AbpFeatureManagement', 'AbpUi']);
export function getSearchFormSchemas():FormSchema[] {
return [
{
field: 'groupName',
component: 'ApiSelect',
label: L('DisplayName:GroupName'),
colProps: { span: 6 },
slot: 'groupName',
},
{
field: 'filter',
component: 'Input',
label: L('Search'),
colProps: { span: 18 },
},
];
}

69
apps/vue/src/views/feature-management/definitions/features/datas/TableData.ts

@ -0,0 +1,69 @@
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { BasicColumn } from '/@/components/Table';
import { sorter } from '/@/utils/table';
const { L } = useLocalization(['AbpFeatureManagement']);
export function getDataColumns(): BasicColumn[] {
return [
{
title: L('DisplayName:Name'),
dataIndex: 'name',
align: 'left',
width: 180,
resizable: true,
sorter: (a, b) => sorter(a, b, 'name'),
},
{
title: L('DisplayName:DisplayName'),
dataIndex: 'displayName',
align: 'left',
width: 180,
resizable: true,
sorter: (a, b) => sorter(a, b, 'displayName'),
},
{
title: L('DisplayName:Description'),
dataIndex: 'description',
align: 'left',
width: 250,
resizable: true,
sorter: (a, b) => sorter(a, b, 'description'),
},
{
title: L('DisplayName:DefaultValue'),
dataIndex: 'defaultValue',
align: 'left',
width: 150,
resizable: true,
sorter: (a, b) => sorter(a, b, 'defaultValue'),
},
{
title: L('DisplayName:IsVisibleToClients'),
dataIndex: 'isVisibleToClients',
align: 'center',
width: 150,
resizable: true,
defaultHidden: true,
sorter: (a, b) => sorter(a, b, 'isVisibleToClients'),
},
{
title: L('DisplayName:IsAvailableToHost'),
dataIndex: 'isAvailableToHost',
align: 'center',
width: 150,
resizable: true,
defaultHidden: true,
sorter: (a, b) => sorter(a, b, 'isAvailableToHost'),
},
{
title: L('DisplayName:IsStatic'),
dataIndex: 'isStatic',
align: 'center',
width: 150,
resizable: true,
defaultHidden: true,
sorter: (a, b) => sorter(a, b, 'isStatic'),
},
];
}

16
apps/vue/src/views/feature-management/definitions/features/index.vue

@ -0,0 +1,16 @@
<template>
<FeatureDefinitionTable />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import FeatureDefinitionTable from './components/FeatureDefinitionTable.vue';
export default defineComponent({
name: 'FeaturesDefinitions',
components: {
FeatureDefinitionTable,
},
setup() {},
});
</script>

199
apps/vue/src/views/feature-management/definitions/groups/components/GroupDefinitionModal.vue

@ -0,0 +1,199 @@
<template>
<BasicModal
@register="registerModal"
:title="L('GroupDefinitions')"
:can-fullscreen="false"
:width="800"
:height="500"
:close-func="handleBeforeClose"
@ok="handleSubmit"
>
<Form
ref="formRef"
:model="state.entity"
:rules="state.entityRules"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 18 }"
>
<Tabs v-model:active-key="state.activeTab">
<TabPane key="basic" :tab="L('BasicInfo')">
<FormItem name="name" :label="L('DisplayName:Name')">
<Input :disabled="state.entityEditFlag && !state.allowedChange" :allow-clear="true" v-model:value="state.entity.name" />
</FormItem>
<FormItem name="displayName" :label="L('DisplayName:DisplayName')">
<LocalizableInput :disabled="!state.allowedChange" :allow-clear="true" v-model:value="state.entity.displayName" />
</FormItem>
</TabPane>
<TabPane key="propertites" :tab="L('Properties')">
<FormItem name="extraProperties" label="" :label-col="{ span: 0 }" :wrapper-col="{ span: 24 }">
<ExtraPropertyDictionary :disabled="!state.allowedChange" :allow-delete="true" :allow-edit="true" v-model:value="state.entity.extraProperties" />
</FormItem>
</TabPane>
</Tabs>
</Form>
</BasicModal>
</template>
<script setup lang="ts">
import type { Rule } from 'ant-design-vue/lib/form';
import { cloneDeep } from 'lodash-es';
import { ref, reactive, unref, nextTick, watch } from 'vue';
import { Form, Input, Tabs } from 'ant-design-vue';
import { ExtraPropertyDictionary } from '/@/components/Abp';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { LocalizableInput } from '/@/components/Abp';
import { useMessage } from '/@/hooks/web/useMessage';
import { ValidationEnum, useValidation } from '/@/hooks/abp/useValidation';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { useLocalizationSerializer } from '/@/hooks/abp/useLocalizationSerializer';
import {
GetAsyncByName,
CreateAsyncByInput,
UpdateAsyncByNameAndInput
} from '/@/api/feature-management/definitions/groups';
import {
FeatureGroupDefinitionUpdateDto,
FeatureGroupDefinitionCreateDto
} from '/@/api/feature-management/definitions/groups/model';
const FormItem = Form.Item;
const TabPane = Tabs.TabPane;
interface State {
activeTab: string,
allowedChange: boolean,
entity: Recordable,
entityRules?: Dictionary<string, Rule>,
entityChanged: boolean,
entityEditFlag: boolean,
}
const emits = defineEmits(['register', 'change']);
const { ruleCreator } = useValidation();
const { validate } = useLocalizationSerializer();
const { createConfirm, createMessage } = useMessage();
const { L } = useLocalization(['AbpFeatureManagement', 'AbpUi']);
const formRef = ref<any>();
const state = reactive<State>({
activeTab: 'basic',
entity: {},
allowedChange: false,
entityChanged: false,
entityEditFlag: false,
entityRules: {
name: ruleCreator.fieldRequired({
name: 'Name',
prefix: 'DisplayName',
resourceName: 'AbpFeatureManagement',
trigger: 'blur',
}),
displayName: ruleCreator.defineValidator({
required: true,
trigger: 'blur',
validator(_rule, value) {
if (!validate(value)) {
return Promise.reject(L(ValidationEnum.FieldRequired, [L('DisplayName:DisplayName')]));
}
return Promise.resolve();
},
}),
description: ruleCreator.defineValidator({
trigger: 'blur',
validator(_rule, value) {
if (!validate(value, { required: false })) {
return Promise.reject(L(ValidationEnum.FieldRequired, [L('DisplayName:Description')]));
}
return Promise.resolve();
},
}),
},
});
watch(
() => state.entity,
() => {
state.entityChanged = true;
},
{
deep: true,
},
);
const [registerModal, { closeModal, changeLoading, changeOkLoading }] = useModalInner((record) => {
nextTick(() => fetch(record.name));
});
function fetch(name?: string) {
state.activeTab = 'basic';
state.entityEditFlag = false;
if (!name) {
state.entity = {};
state.allowedChange = true;
nextTick(() => state.entityChanged = false);
return;
}
changeLoading(true);
changeOkLoading(true);
GetAsyncByName(name).then((record) => {
state.entity = record;
state.entityEditFlag = true;
state.allowedChange = !record.isStatic;
}).finally(() => {
changeLoading(false);
changeOkLoading(false);
nextTick(() => state.entityChanged = false);
});
}
function handleBeforeClose(): Promise<boolean> {
return new Promise((resolve) => {
if (!state.entityChanged) {
const form = unref(formRef);
form?.resetFields();
return resolve(true);
}
createConfirm({
iconType: 'warning',
title: L('AreYouSure'),
content: L('AreYouSureYouWantToCancelEditingWarningMessage'),
onOk: () => {
const form = unref(formRef);
form?.resetFields();
resolve(true);
},
onCancel: () => {
resolve(false);
},
afterClose: () => {
state.allowedChange = false;
},
});
});
}
function handleSubmit() {
if (!state.allowedChange) {
closeModal();
return;
}
const form = unref(formRef);
form?.validate().then(() => {
changeOkLoading(true);
const api = state.entityEditFlag
? UpdateAsyncByNameAndInput(state.entity.name, cloneDeep(state.entity) as FeatureGroupDefinitionUpdateDto)
: CreateAsyncByInput(cloneDeep(state.entity) as FeatureGroupDefinitionCreateDto);
api.then((res) => {
createMessage.success(L('Successful'));
emits('change', res);
form.resetFields();
closeModal();
}).finally(() => {
changeOkLoading(false);
})
});
}
</script>
<style scoped>
</style>

159
apps/vue/src/views/feature-management/definitions/groups/components/GroupDefinitionTable.vue

@ -0,0 +1,159 @@
<template>
<div>
<BasicTable @register="registerTable">
<template #toolbar>
<Button
v-auth="['FeatureManagement.GroupDefinitions.Create']"
type="primary"
@click="handleAddNew"
>
{{ L('GroupDefinitions:AddNew') }}
</Button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'displayName'">
<span>{{ getDisplayName(record.displayName) }}</span>
</template>
<template v-else-if="column.key === 'action'">
<TableAction
:stop-button-propagation="true"
:actions="[
{
auth: 'FeatureManagement.GroupDefinitions.Update',
label: L('Edit'),
icon: 'ant-design:edit-outlined',
onClick: handleEdit.bind(null, record),
},
{
auth: 'FeatureManagement.GroupDefinitions.Delete',
label: L('Delete'),
color: 'error',
icon: 'ant-design:delete-outlined',
ifShow: !record.isStatic,
onClick: handleDelete.bind(null, record),
},
]"
:dropDownActions="[
{
auth: 'FeatureManagement.Definitions.Create',
label: L('FeatureDefinitions:AddNew'),
icon: 'ant-design:edit-outlined',
onClick: handleAddFeature.bind(null, record),
},
]"
/>
</template>
</template>
</BasicTable>
<GroupDefinitionModal @register="registerModal" @change="fetch" />
<FeatureDefinitionModal @register="registerFeatureModal" />
</div>
</template>
<script lang="ts" setup>
import { cloneDeep } from 'lodash-es';
import { computed, reactive, onMounted } from 'vue';
import { Button } from 'ant-design-vue';
import { BasicTable, TableAction, useTable } from '/@/components/Table';
import { getDataColumns } from '../datas/TableData';
import { useModal } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { useLocalizationSerializer } from '/@/hooks/abp/useLocalizationSerializer';
import { FeatureGroupDefinitionDto } from '/@/api/feature-management/definitions/groups/model';
import { GetListAsyncByInput, DeleteAsyncByName } from '/@/api/feature-management/definitions/groups';
import { getSearchFormSchemas } from '../datas/ModalData';
import GroupDefinitionModal from './GroupDefinitionModal.vue';
import FeatureDefinitionModal from '../../features/components/FeatureDefinitionModal.vue';
interface State {
groups: FeatureGroupDefinitionDto[];
}
const state = reactive<State>({
groups: [],
});
const { deserialize } = useLocalizationSerializer();
const { L, Lr } = useLocalization(['AbpFeatureManagement', 'AbpUi']);
const { createConfirm, createMessage } = useMessage();
const [registerModal, { openModal }] = useModal();
const [registerFeatureModal, { openModal: openFeatureModal }] = useModal();
const [registerTable, { setLoading, getForm, setTableData }] = useTable({
rowKey: 'name',
title: L('GroupDefinitions'),
columns: getDataColumns(),
pagination: true,
striped: false,
useSearchForm: true,
showIndexColumn: false,
showTableSetting: true,
tableSetting: {
redo: false,
},
formConfig: {
labelWidth: 100,
submitFunc: fetch,
schemas: getSearchFormSchemas(),
},
bordered: true,
canResize: true,
immediate: false,
actionColumn: {
width: 120,
title: L('Actions'),
dataIndex: 'action',
},
});
const getDisplayName = computed(() => {
return (displayName?: string) => {
if (!displayName) return displayName;
const info = deserialize(displayName);
return Lr(info.resourceName, info.name);
};
});
onMounted(fetch);
function fetch() {
const form = getForm();
return form.validate().then(() => {
setLoading(true);
state.groups = [];
var input = form.getFieldsValue();
GetListAsyncByInput(input).then((res) => {
state.groups = res.items;
}).finally(() => {
setTableData(state.groups);
setLoading(false);
});
});
}
function handleAddNew() {
openModal(true, {});
}
function handleEdit(record) {
openModal(true, record);
}
function handleAddFeature(record) {
openFeatureModal(true, {
groupName: record.name,
groups: cloneDeep(state.groups),
});
}
function handleDelete(record) {
createConfirm({
iconType: 'warning',
title: L('AreYouSure'),
content: L('ItemWillBeDeleteOrRestoreMessage'),
onOk: () => {
return DeleteAsyncByName(record.name).then(() => {
createMessage.success(L('Successful'));
fetch();
});
},
});
}
</script>

15
apps/vue/src/views/feature-management/definitions/groups/datas/ModalData.ts

@ -0,0 +1,15 @@
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { FormSchema } from '/@/components/Form';
const { L } = useLocalization(['AbpUi']);
export function getSearchFormSchemas():FormSchema[] {
return [
{
field: 'filter',
component: 'Input',
label: L('Search'),
colProps: { span: 24 },
},
];
}

35
apps/vue/src/views/feature-management/definitions/groups/datas/TableData.ts

@ -0,0 +1,35 @@
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { BasicColumn } from '/@/components/Table';
import { sorter } from '/@/utils/table';
const { L } = useLocalization(['AbpFeatureManagement']);
export function getDataColumns(): BasicColumn[] {
return [
{
title: L('DisplayName:Name'),
dataIndex: 'name',
align: 'left',
width: 180,
resizable: true,
sorter: (a, b) => sorter(a, b, 'name'),
},
{
title: L('DisplayName:DisplayName'),
dataIndex: 'displayName',
align: 'left',
width: 350,
resizable: true,
sorter: (a, b) => sorter(a, b, 'displayName'),
},
{
title: L('DisplayName:IsStatic'),
dataIndex: 'isStatic',
align: 'center',
width: 150,
resizable: true,
defaultHidden: true,
sorter: (a, b) => sorter(a, b, 'isStatic'),
},
];
}

16
apps/vue/src/views/feature-management/definitions/groups/index.vue

@ -0,0 +1,16 @@
<template>
<GroupDefinitionTable />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import GroupDefinitionTable from './components/GroupDefinitionTable.vue';
export default defineComponent({
name: 'FeatruesGroupDefinitions',
components: {
GroupDefinitionTable,
},
setup() {},
});
</script>

145
apps/vue/src/views/feature/hooks/useFeature.ts

@ -1,145 +0,0 @@
import type { Ref } from 'vue';
import { watch, ref, unref } from 'vue';
import { message } from 'ant-design-vue';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { useValidation } from '/@/hooks/abp/useValidation';
import { FeatureGroup } from '/@/api/feature/model/featureModel';
import { get, update } from '/@/api/feature/feature';
import { ReturnInnerMethods } from '/@/components/Modal';
interface UseFeature {
providerName: Ref<string>;
providerKey: Ref<string | null>;
formRel: Ref<any>;
modalMethods: ReturnInnerMethods;
}
export function useFeature({ providerName, providerKey, formRel, modalMethods }: UseFeature) {
const { L } = useLocalization('AbpFeatureManagement');
const { ruleCreator } = useValidation();
const featureGroup = ref<{ groups: FeatureGroup[] }>({
groups: [],
});
const featureGroupKey = ref(0);
watch(
() => unref(providerKey),
(key) => {
if (key !== undefined) {
const form = unref(formRel);
form.resetFields();
onGroupChange(0);
get({
providerName: unref(providerName),
providerKey: key,
}).then((res) => {
featureGroup.value.groups = mapFeatures(res.groups);
});
}
},
);
function getFeatures(groups: FeatureGroup[]) {
const features: { name: string; value: string }[] = [];
groups.forEach((g) => {
g.features.forEach((f) => {
if (f.value !== null) {
features.push({
name: f.name,
value: String(f.value),
});
}
});
});
return features;
}
function mapFeatures(groups: FeatureGroup[]) {
groups.forEach((g) => {
g.features.forEach((f) => {
switch (f.valueType?.validator.name) {
case 'BOOLEAN':
f.value = String(f.value).toLocaleLowerCase() === 'true';
break;
case 'NUMERIC':
f.value = Number(f.value);
break;
}
});
});
return groups;
}
function validator(field: string, validator: Validator) {
const featureRules: { [key: string]: any }[] = new Array<{ [key: string]: any }>();
if (validator.properties) {
switch (validator.name) {
case 'NUMERIC':
featureRules.push(
...ruleCreator.fieldMustBeetWeen({
name: field,
start: Number(validator.properties.MinValue),
end: Number(validator.properties.MaxValue),
trigger: 'change',
}),
);
break;
case 'STRING':
if (
validator.properties.AllowNull &&
validator.properties.AllowNull.toLowerCase() === 'true'
) {
featureRules.push(
...ruleCreator.fieldRequired({
name: field,
trigger: 'blur',
}),
);
}
featureRules.push(
...ruleCreator.fieldMustBeStringWithMinimumLengthAndMaximumLength({
name: field,
minimum: Number(validator.properties.MinValue),
maximum: Number(validator.properties.MaxValue),
trigger: 'blur',
}),
);
break;
default:
break;
}
}
return featureRules;
}
function onGroupChange(activeKey) {
featureGroupKey.value = activeKey;
}
function handleSubmit() {
const form = unref(formRel);
form.validate().then(() => {
update(
{
providerName: unref(providerName),
providerKey: unref(providerKey),
},
{
features: getFeatures(unref(featureGroup).groups),
},
).then(() => {
modalMethods.closeModal();
message.success(L('Successful'));
});
});
}
return {
featureGroup,
featureGroupKey,
validator,
handleSubmit,
onGroupChange,
};
}

89
apps/vue/src/views/feature/src/FeatureModal.vue

@ -1,89 +0,0 @@
<template>
<BasicModal
v-bind="$attrs"
@register="registerModal"
:title="L('ManageFeatures')"
:width="800"
:height="400"
:min-height="400"
@ok="handleSubmit"
@cancel="onGroupChange(0)"
>
<Form ref="formRel" :model="featureGroup">
<Tabs tabPosition="left" v-model:activeKey="featureGroupKey" @change="onGroupChange">
<TabPane v-for="(group, gi) in featureGroup.groups" :key="gi" :tab="group.displayName">
<div v-for="(feature, fi) in group.features" :key="feature.name">
<FormItem
v-if="feature.valueType !== null"
:label="feature.displayName"
:name="['groups', gi, 'features', fi, 'value']"
:rules="validator(feature.displayName, feature.valueType.validator)"
:extra="feature.description"
>
<Checkbox
v-if="feature.valueType.name === 'ToggleStringValueType' && feature.valueType.validator.name === 'BOOLEAN'"
v-model:checked="feature.value"
>{{ feature.displayName }}</Checkbox
>
<div v-else-if="feature.valueType.name === 'FreeTextStringValueType'">
<InputNumber
v-if="feature.valueType.validator.name === 'NUMERIC'"
style="width: 100%"
v-model:value="feature.value"
/>
<BInput v-else v-model:value="feature.value" />
</div>
<Select
v-else-if="feature.valueType.name === 'SelectionStringValueType'"
v-model="feature.value"
>
<Option
v-for="valueItem in (feature.valueType as SelectionStringValueType).itemSource.items"
:key="valueItem.value"
v-model:value="valueItem.value"
:label="localizer(valueItem.displayText.resourceName, valueItem.displayText.name)"
/>
</Select>
</FormItem>
</div>
</TabPane>
</Tabs>
</Form>
</BasicModal>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { Checkbox, Form, InputNumber, Select, Tabs } from 'ant-design-vue';
import { Input } from '/@/components/Input';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { useFeature } from '../hooks/useFeature';
const FormItem = Form.Item;
const Option = Select.Option;
const TabPane = Tabs.TabPane;
const BInput = Input!;
const { L } = useLocalization('AbpFeatureManagement');
// TODO: ?
const localizer = computed(() => {
return (resourceName: string, key: string) => {
const { L: RL } = useLocalization(resourceName);
return RL(key);
};
});
const formRel = ref(null);
const providerName = ref('');
const providerKey = ref(null);
const [registerModal, modalMethods] = useModalInner((data) => {
providerName.value = data.providerName;
providerKey.value = data.providerKey;
});
const { featureGroup, featureGroupKey, validator, handleSubmit, onGroupChange } = useFeature({
providerName,
providerKey,
formRel,
modalMethods,
});
</script>

6
apps/vue/src/views/localization/languages/components/LanguageTable.vue

@ -53,7 +53,7 @@
const { createConfirm, createMessage } = useMessage();
const { L } = useLocalization(['LocalizationManagement', 'AbpLocalization', 'AbpUi']);
const [registerModal, { openModal }] = useModal();
const [registerTable, { setTableData, getForm }] = useTable({
const [registerTable, { setLoading, setTableData, getForm }] = useTable({
rowKey: 'cultureName',
title: L('Languages'),
columns: getDataColumns(),
@ -90,8 +90,12 @@
function fetchLanguages() {
const form = getForm();
return form.validate().then((input) => {
setLoading(true);
setTableData([]);
return getList(input).then((res) => {
setTableData(res.items);
}).finally(() => {
setLoading(false);
});
});
}

6
apps/vue/src/views/localization/resources/components/ResourceTable.vue

@ -53,7 +53,7 @@
const { createConfirm, createMessage } = useMessage();
const { L } = useLocalization(['LocalizationManagement', 'AbpLocalization', 'AbpUi']);
const [registerModal, { openModal }] = useModal();
const [registerTable, { setTableData, getForm }] = useTable({
const [registerTable, { setLoading, setTableData, getForm }] = useTable({
rowKey: 'name',
title: L('Resources'),
columns: getDataColumns(),
@ -89,8 +89,12 @@
function fetchResources() {
const form = getForm();
return form.validate().then((input) => {
setLoading(true);
setTableData([]);
return getList(input).then((res) => {
setTableData(res.items);
}).finally(() => {
setLoading(false);
});
});
}

4
apps/vue/src/views/localization/texts/components/ModalData.ts

@ -58,12 +58,12 @@ export function getSearchFormSchemas(submitFunc?: () => Promise<void>): Partial<
{
key: L('DisplayName:Any'),
label: L('DisplayName:Any'),
value: 0,
value: false,
},
{
key: L('DisplayName:OnlyNull'),
label: L('DisplayName:OnlyNull'),
value: 1,
value: true,
},
],
},

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save